From 2b650f48452f5f11be9faeaded7b592a81e85705 Mon Sep 17 00:00:00 2001 From: Karolis Stasaitis Date: Sat, 5 Sep 2026 13:54:50 +0200 Subject: [PATCH] compilers: add support for SDCC --- cross/sdcc-mcs51.txt | 21 +++++++ docs/markdown/Reference-tables.md | 12 ++++ mesonbuild/backend/backends.py | 3 +- mesonbuild/backend/ninjabackend.py | 2 +- mesonbuild/build.py | 4 ++ mesonbuild/compilers/c.py | 28 +++++++++ mesonbuild/compilers/compilers.py | 4 ++ mesonbuild/compilers/detect.py | 15 +++++ mesonbuild/compilers/mixins/sdcc.py | 96 +++++++++++++++++++++++++++++ mesonbuild/envconfig.py | 10 +++ mesonbuild/linkers/linkers.py | 39 ++++++++++++ 11 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 cross/sdcc-mcs51.txt create mode 100644 mesonbuild/compilers/mixins/sdcc.py diff --git a/cross/sdcc-mcs51.txt b/cross/sdcc-mcs51.txt new file mode 100644 index 000000000..d74d159eb --- /dev/null +++ b/cross/sdcc-mcs51.txt @@ -0,0 +1,21 @@ +# This file assumes that path to the SDCC toolchain is added +# to the environment(PATH) variable, so that Meson can find +# sdcc and sdar while building. +[binaries] +c = 'sdcc' +ar = 'sdar' + +[host_machine] +system = 'bare metal' +cpu_family = 'mcs51' +cpu = '8051' +endian = 'little' + +[properties] +needs_exe_wrapper = true + +[built-in options] +# SDCC selects the target port with '-m'; it must be passed when +# compiling and when linking. +c_args = ['-mmcs51'] +c_link_args = ['-mmcs51'] diff --git a/docs/markdown/Reference-tables.md b/docs/markdown/Reference-tables.md index 0812e6d47..2086a408a 100644 --- a/docs/markdown/Reference-tables.md +++ b/docs/markdown/Reference-tables.md @@ -36,6 +36,7 @@ These are return values of the `get_id` (Compiler family) and | pathscale | The Pathscale Fortran compiler | | | pgi | Portland PGI C/C++/Fortran compilers | | | rustc | Rust compiler | | +| sdcc | Small Device C Compiler | | | sun | Sun Fortran compiler | | | c2000 | Texas Instruments C/C++ Compiler (C2000) | | | c6000 | Texas Instruments C/C++ Compiler (C6000) | | @@ -86,6 +87,7 @@ These are return values of the `get_linker_id` method in a compiler object. | mwldarm | The Metrowerks Linker with the ARM interface, used with mwccarm only | | mwldeppc | The Metrowerks Linker with the PowerPC interface, used with mwcceppc only | | tasking | TASKING VX-toolset | +| sdcc | SDCC used as the linker driver | For languages that don't have separate dynamic linkers such as C# and Java, the `get_linker_id` will return the compiler name. @@ -119,19 +121,25 @@ set in the cross file. | csky | 32 bit CSky processor | | dspic | 16 bit Microchip dsPIC | | e2k | MCST Elbrus processor | +| f8 | 8 bit SDCC F8 | | ft32 | 32 bit Bridgetek MCU | +| hc08 | 8 bit Motorola HC08/S08 | | ia64 | Itanium processor | | loongarch64 | 64 bit Loongson processor| | m68k | Motorola 68000 processor | +| mcs51 | 8 bit Intel MCS-51 | | microblaze | MicroBlaze processor | | mips | 32 bit MIPS processor | | mips64 | 64 bit MIPS processor | +| mos6502 | 8 bit MOS 6502/65C02 | | msp430 | 16 bit MSP430 processor | +| padauk | 8 bit Padauk PDK | | parisc | HP PA-RISC processor | | pic24 | 16 bit Microchip PIC24 | | pic32 | 32 bit Microchip PIC32 | | ppc | 32 bit PPC processors | | ppc64 | 64 bit PPC processors | +| rabbit | 8 bit Rabbit 2000/3000 | | riscv32 | 32 bit RISC-V Open ISA | | riscv64 | 64 bit RISC-V Open ISA | | rl78 | Renesas RL78 | @@ -139,13 +147,17 @@ set in the cross file. | s390 | IBM zSystem s390 | | s390x | IBM zSystem s390x | | sh4 | SuperH SH-4 | +| sm83 | 8 bit Sharp SM83 | | sparc | 32 bit SPARC | | sparc64 | SPARC v9 processor | +| stm8 | 8 bit STMicro STM8 | | sw_64 | 64 bit sunway processor | +| tlcs90 | 8 bit Toshiba TLCS-90 | | wasm32 | 32 bit Webassembly | | wasm64 | 64 bit Webassembly | | x86 | 32 bit x86 processor | | x86_64 | 64 bit x86 processor | +| z80 | 8 bit Zilog Z80 family | | tricore | Tricore 32 bit processor | diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 141862a3e..33b86682c 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -943,8 +943,7 @@ class Backend: else: gen_source = os.path.relpath(os.path.join(build_dir, rel_src), os.path.join(self.environment.get_source_dir(), target.get_subdir())) - machine = self.environment.machines[target.for_machine] - object_suffix = machine.get_object_suffix() + object_suffix = compiler.get_object_suffix() # For the TASKING compiler, in case of LTO or prelinking the object suffix has to be .mil if compiler.get_id() == 'tasking': use_lto = self.get_target_option(target, 'b_lto') diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index d2e91471e..635b99621 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -3093,7 +3093,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) src_filename = src obj_basename = self.canonicalize_filename(src_filename) rel_obj = os.path.join(self.get_target_private_dir(target), obj_basename) - rel_obj += '.' + self.environment.machines[target.for_machine].get_object_suffix() + rel_obj += '.' + compiler.get_object_suffix() commands += self.get_compile_debugfile_args(compiler, target, rel_obj) if isinstance(src, File): if src.is_built: diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 7d5ea37aa..7a9328b3f 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -2242,6 +2242,8 @@ class Executable(BuildTarget, LinkableTarget): self.suffix = 'nef' elif ('c' in self.compilers and self.compilers['c'].get_id() == 'tasking'): self.suffix = 'elf' + elif ('c' in self.compilers and self.compilers['c'].get_id() == 'sdcc'): + self.suffix = 'ihx' else: self.suffix = machine.get_exe_suffix() self.filename = self.name @@ -2407,6 +2409,8 @@ class StaticLibrary(BuildTarget, LinkableTarget): suffix = 'a' elif self.environment.machines[self.for_machine].is_os2() and self.environment.coredata.optstore.get_value_for(OptionKey('os2_emxomf')): suffix = 'lib' + elif 'c' in self.compilers and self.compilers['c'].get_id() == 'sdcc': + suffix = 'lib' else: suffix = 'a' if 'c' in self.compilers and self.compilers['c'].get_id() == 'tasking' and not self.prelink: diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index dd8a758b5..377358a83 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -17,6 +17,7 @@ from .mixins.clike import CLikeCompiler from .mixins.ccrx import CcrxCompiler from .mixins.microchip import Xc16Compiler, Xc32Compiler, Xc32CStds from .mixins.compcert import CompCertCompiler +from .mixins.sdcc import SdccCompiler from .mixins.ti import TICompiler from .mixins.arm import ArmCompiler, ArmclangCompiler from .mixins.visualstudio import MSVCCompiler, ClangClCompiler @@ -680,6 +681,33 @@ class CompCertCCompiler(CompCertCompiler, CCompiler): path = '.' return ['-I' + path] + +class SdccCCompiler(SdccCompiler, CCompiler): + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, + linker: T.Optional['DynamicLinker'] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) + SdccCompiler.__init__(self) + + def get_options(self) -> 'MutableKeyedOptionDictType': + opts = super().get_options() + key = self.form_compileropt_key('std') + std_opt = opts[key] + assert isinstance(std_opt, options.UserStdOption), 'for mypy' + std_opt.set_versions(['c89', 'c90', 'c99', 'c11', 'c17', 'c23', 'c2y']) + return opts + + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: + args = [] + std = self.get_compileropt_value('std', target, subproject) + assert isinstance(std, str) + if std != 'none': + args.append('--std-' + std) + return args + + class TICCompiler(TICompiler, CCompiler): def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, env: Environment, diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 3f74219c3..299442f85 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -1549,6 +1549,10 @@ class Compiler(HoldableObject, metaclass=SimpleABC): def get_depfile_suffix(self) -> str: raise EnvironmentException(f'{self.id} does not implement get_depfile_suffix') + def get_object_suffix(self) -> str: + """Suffix for object files.""" + return self.environment.machines[self.for_machine].get_object_suffix() + def get_no_stdinc_args(self) -> T.List[str]: """Arguments to turn off default inclusion of standard libraries.""" return [] diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py index 0c0f943b2..56f3b7333 100644 --- a/mesonbuild/compilers/detect.py +++ b/mesonbuild/compilers/detect.py @@ -82,6 +82,7 @@ defaults['cuda_static_linker'] = ['nvlink'] defaults['gcc_static_linker'] = ['gcc-ar'] defaults['clang_static_linker'] = ['llvm-ar'] defaults['emxomf_static_linker'] = ['emxomfar'] +defaults['sdcc_static_linker'] = ['sdar'] defaults['nasm'] = ['nasm', 'yasm'] @@ -212,6 +213,8 @@ def detect_static_linker(env: 'Environment', compiler: Compiler) -> StaticLinker trials = [defaults['clang_cl_static_linker']] + trials if 'vs_static_linker' in defaults: trials = [defaults['vs_static_linker']] + trials + elif compiler.id == 'sdcc': + trials = [defaults['sdcc_static_linker']] + trials popen_exceptions = {} for linker in trials: @@ -254,6 +257,8 @@ def detect_static_linker(env: 'Environment', compiler: Compiler) -> StaticLinker return linkers.Xc16Linker(linker, env) elif 'xc32-ar' in linker_name: return linkers.Xc32ArLinker(compiler.for_machine, linker, env) + elif 'sdar' in linker_name: + return linkers.SdccLinker(linker, env) if 'Texas Instruments Incorporated' in out: if 'ar2000' in linker_name: return linkers.C2000Linker(linker, env) @@ -628,6 +633,16 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin ccache, compiler, version, for_machine, env, full_version=full_version, linker=linker) + if out.startswith('SDCC'): + if lang != 'c': + raise EnvironmentException(f'SDCC does not support {lang}') + cls = c.SdccCCompiler + env.add_lang_args(cls.language, cls, for_machine) + linker = linkers.SdccDynamicLinker(compiler, env, for_machine, version=version) + return cls( + ccache, compiler, version, for_machine, env, + full_version=full_version, linker=linker) + if 'Metrowerks C/C++' in out or 'Freescale C/C++' in out: if 'ARM' in out: cls = c.MetrowerksCCompilerARM if lang == 'c' else cpp.MetrowerksCPPCompilerARM diff --git a/mesonbuild/compilers/mixins/sdcc.py b/mesonbuild/compilers/mixins/sdcc.py new file mode 100644 index 000000000..e93617295 --- /dev/null +++ b/mesonbuild/compilers/mixins/sdcc.py @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2025 The Meson development team + +from __future__ import annotations + +"""Representations specific to the Small Device C Compiler (SDCC).""" + +import os +import typing as T + +if T.TYPE_CHECKING: + from ...compilers.compilers import Compiler +else: + Compiler = object + +sdcc_optimization_args: T.Dict[str, T.List[str]] = { + 'plain': [], + '0': [], + 'g': [], + '1': [], + '2': ['--opt-code-speed'], + '3': ['--opt-code-speed'], + 's': ['--opt-code-size'], +} + +sdcc_debug_args: T.Dict[bool, T.List[str]] = { + False: [], + True: ['--debug'], +} + + +class SdccCompiler(Compiler): + + id = 'sdcc' + + def __init__(self) -> None: + self.warn_args: T.Dict[str, T.List[str]] = { + '0': [], + '1': [], + '2': [], + '3': [], + 'everything': [], + } + + def get_always_args(self) -> T.List[str]: + return [] + + def get_pic_args(self) -> T.List[str]: + return [] + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + # SDCC's own -M flags suppress codegen; -Wp routes them to sdcpp, which also compiles. + return ['-Wp,-MD', '-Wp,-MF', '-Wp,' + outfile, '-Wp,-MQ', '-Wp,' + outtarget] + + def get_no_optimization_args(self) -> T.List[str]: + return [] + + def get_object_suffix(self) -> str: + return 'rel' + + def _sanity_check_filenames(self) -> T.Tuple[str, T.Optional[str], str]: + cross_or_not = '_cross' if self.is_cross else '' + return ('sanity_check_for_c.c', None, f'sanity_check_for_c{cross_or_not}.ihx') + + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + return sdcc_optimization_args[optimization_level] + + def get_debug_args(self, is_debug: bool) -> T.List[str]: + return sdcc_debug_args[is_debug] + + def get_preprocess_only_args(self) -> T.List[str]: + return ['-E'] + + def get_werror_args(self) -> T.List[str]: + return ['--Werror'] + + def get_no_stdinc_args(self) -> T.List[str]: + return ['--nostdinc'] + + def get_no_stdlib_link_args(self) -> T.List[str]: + return ['--nostdlib'] + + def get_include_args(self, path: str, is_system: bool) -> T.List[str]: + if not path: + path = '.' + return ['-I' + path] + + def thread_flags(self) -> T.List[str]: + return [] + + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], + build_dir: str) -> T.List[str]: + for idx, i in enumerate(parameter_list): + if i[:2] in {'-I', '-L'}: + parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) + return parameter_list diff --git a/mesonbuild/envconfig.py b/mesonbuild/envconfig.py index 334c15e75..1cdd8ec8b 100644 --- a/mesonbuild/envconfig.py +++ b/mesonbuild/envconfig.py @@ -47,19 +47,25 @@ known_cpu_families = ( 'csky', 'dspic', 'e2k', + 'f8', 'ft32', + 'hc08', 'ia64', 'loongarch64', 'm68k', + 'mcs51', 'microblaze', 'mips', 'mips64', + 'mos6502', 'msp430', + 'padauk', 'parisc', 'pic24', 'pic32', 'ppc', 'ppc64', + 'rabbit', 'riscv32', 'riscv64', 'rl78', @@ -67,13 +73,17 @@ known_cpu_families = ( 's390', 's390x', 'sh4', + 'sm83', 'sparc', 'sparc64', + 'stm8', 'sw_64', + 'tlcs90', 'wasm32', 'wasm64', 'x86', 'x86_64', + 'z80', 'tricore' ) diff --git a/mesonbuild/linkers/linkers.py b/mesonbuild/linkers/linkers.py index 6838a1f41..d29ddea7c 100644 --- a/mesonbuild/linkers/linkers.py +++ b/mesonbuild/linkers/linkers.py @@ -499,6 +499,22 @@ class Xc16Linker(StaticLinker): return ['rcs'] +class SdccLinker(StaticLinker): + + """Static linker for the Small Device C Compiler.""" + + id = 'sdar' + + def can_linker_accept_rsp(self) -> bool: + return False + + def get_output_args(self, target: str) -> T.List[str]: + return [target] + + def get_linker_always_args(self) -> T.List[str]: + return ['-rc'] + + class Xc32ArLinker(ArLinker): """Static linker for Microchip XC32 compiler.""" @@ -1215,6 +1231,29 @@ class Xc32DynamicLinker(GnuDynamicLinker): return [] +class SdccDynamicLinker(DynamicLinker): + + """Linker for the Small Device C Compiler.""" + + id = 'sdcc' + + def __init__(self, exelist: T.List[str], env: Environment, for_machine: mesonlib.MachineChoice, + *, version: str = 'unknown version'): + super().__init__(exelist, env, for_machine, None, [], version=version) + + def get_accepts_rsp(self) -> bool: + return False + + def get_allow_undefined_args(self) -> T.List[str]: + return [] + + def get_output_args(self, outputname: str) -> T.List[str]: + return ['-o', outputname] + + def get_search_args(self, dirname: str) -> T.List[str]: + return ['-L' + dirname] + + class CompCertDynamicLinker(DynamicLinker): """Linker for CompCert C compiler."""