kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 0af956d9bd290eca6860723a0590e3e6296bd34e
parent 51a0605d7da6f4b5bdb045921c315598ab57ec06
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 17 Jun 2026 10:58:58 -0700

arm32: full -mcpu/-march/-mfpu/-mfloat-abi parsing + __ARM_* macros (apply_cpu + feature_predefines hooks)

Diffstat:
Mdriver/lib/target.c | 41++++++++++++++++++++++++++++++++++++-----
Minclude/kit/compile.h | 11+++++++++++
Mlang/cpp/pp/pp.c | 12++++++++++++
Mmk/test.mk | 4++++
Mmk/test_unit.mk | 4+++-
Msrc/api/compile.c | 15+++++++++++++++
Msrc/api/core.c | 13+++++++++++++
Msrc/arch/arch.h | 36++++++++++++++++++++++++++++++++++++
Msrc/arch/arm32/arch.c | 295+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/arch/registry.c | 11+++++++++++
Atest/api/arm32_target_features_test.c | 200+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/smoke/arm32_macros.sh | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
12 files changed, 722 insertions(+), 50 deletions(-)

diff --git a/driver/lib/target.c b/driver/lib/target.c @@ -157,6 +157,33 @@ static int target_features_parse_mattr(DriverTargetFeatures* tf, DriverEnv* env, return 0; } +/* Map an ARM -mfpu= name onto the backend's VFP target features. `none` records + * nothing (soft-float). `fpv4-sp-d16` (Cortex-M4F) records `vfp`; `fpv5-d16` / + * `fpv5-sp-d16` (Cortex-M7) record `vfp`+`fpv5`. An unknown name is an error. + * The recorded features flow through opts->features[] into kit_target_new, where + * resolve_float_abi requires them for -mfloat-abi=hard/softfp and the + * feature_predefines hook keys __ARM_FP/__ARM_FPV5__ on them. */ +static int target_features_record_mfpu(DriverTargetFeatures* tf, DriverEnv* env, + const char* tool, const char* fpu) { + if (driver_streq(fpu, "none")) return 0; + if (driver_streq(fpu, "fpv4-sp-d16")) { + return target_features_record_feature(tf, env, KIT_SLICE_LIT("vfp"), 1) == 0 + ? 0 + : 1; + } + if (driver_streq(fpu, "fpv5-d16") || driver_streq(fpu, "fpv5-sp-d16")) { + if (target_features_record_feature(tf, env, KIT_SLICE_LIT("vfp"), 1) != 0) + return 1; + return target_features_record_feature(tf, env, KIT_SLICE_LIT("fpv5"), 1) == 0 + ? 0 + : 1; + } + driver_errf(tool, "-mfpu=%s is unsupported (none, fpv4-sp-d16, fpv5-d16, " + "fpv5-sp-d16)", + fpu); + return 1; +} + int driver_target_features_try_consume(DriverTargetFeatures* tf, DriverEnv* env, const char* tool, int argc, char** argv, int* i) { @@ -226,8 +253,10 @@ int driver_target_features_try_consume(DriverTargetFeatures* tf, DriverEnv* env, * (FP via the AEABI helpers); the arch's resolve_float_abi pins float_abi to * SOFT. Consume the flag here so it does not fall through to the generic * -m<feature> recorder (which would reject "float-abi=soft" as an unknown - * feature). hard/softfp are a follow-on. -mfpu only matters with hard-float, - * so it is accepted and ignored for the soft-float build. */ + * feature). hard/softfp are a follow-on. -mfpu records the VFP target + * features so the FP-feature machinery (resolve_float_abi requiring vfp/fpv5, + * the __ARM_FP/__ARM_FPV5__ macros) is exercisable at the kit_target_new + * level, even though -mfloat-abi=hard/softfp is still rejected below. */ if (driver_strneq(a, "-mfloat-abi=", 12)) { if (!driver_streq(a + 12, "soft")) { driver_errf(tool, @@ -250,10 +279,12 @@ int driver_target_features_try_consume(DriverTargetFeatures* tf, DriverEnv* env, } return 1; } - if (driver_strneq(a, "-mfpu=", 6)) return 1; + if (driver_strneq(a, "-mfpu=", 6)) + return target_features_record_mfpu(tf, env, tool, a + 6) == 0 ? 1 : -1; if (driver_streq(a, "-mfpu")) { - if (!target_features_pull_value(tool, argc, argv, i, 5, "-mfpu")) return -1; - return 1; + v = target_features_pull_value(tool, argc, argv, i, 5, "-mfpu"); + if (!v) return -1; + return target_features_record_mfpu(tf, env, tool, v) == 0 ? 1 : -1; } if (driver_strneq(a, "-mno-", 5) && a[5] != '\0') { if (target_features_record_feature(tf, env, kit_slice_cstr(a + 5), 0) != diff --git a/include/kit/compile.h b/include/kit/compile.h @@ -323,6 +323,17 @@ KIT_API uint32_t kit_compiler_arch_predefines(KitCompiler*, KIT_API uint32_t kit_compiler_arch_float_predefines( KitCompiler*, const KitPredefinedMacro** out); +/* Feature-keyed predefined macros for the compiler's resolved target, selected + * from the resolved -march/-mcpu/-mattr feature words (and spec). Separate from + * kit_compiler_arch_predefines for the same reason as the float getter: a single + * static table cannot encode every extension profile while the preprocessor must + * agree with the codegen feature set (e.g. ARM's __ARM_ARCH_7M__ vs + * __ARM_ARCH_7EM__ and __ARM_FEATURE_DSP). Sets *out to a borrowed static- + * lifetime array and returns its length; 0 when the target's arch has no + * feature-keyed macros. */ +KIT_API uint32_t kit_compiler_arch_feature_predefines( + KitCompiler*, const KitPredefinedMacro** out); + typedef struct KitCompileSessionOptions { KitLanguage lang; KitFrontendCompileOptions compile; diff --git a/lang/cpp/pp/pp.c b/lang/cpp/pp/pp.c @@ -870,6 +870,18 @@ static void pp_register_target_predefined(Pp* pp) { } } + /* Feature-keyed arch macros (e.g. ARM's __ARM_ARCH_7M__ vs __ARM_ARCH_7EM__, + * __ARM_FEATURE_DSP) come from a separate getter because the static table + * above can encode only one extension profile; these track the resolved + * -march/-mcpu/-mattr feature words. */ + { + const KitPredefinedMacro* feat_defs = NULL; + uint32_t nfeat_defs = kit_compiler_arch_feature_predefines(pp->c, &feat_defs); + for (i = 0; i < nfeat_defs; ++i) { + pp_define(pp, feat_defs[i].name.s, feat_defs[i].body.s); + } + } + /* __USER_LABEL_PREFIX__ is the C source-symbol prefix the object format * prepends ("_" for Mach-O, "" else); read it from the CG target rather * than re-deriving from the object-format identity. */ diff --git a/mk/test.mk b/mk/test.mk @@ -1264,7 +1264,11 @@ test-smoke-rv32: # codegen under qemu-system-arm -machine mps2-an385 (single soft-float lane, # i64 + soft-double + a <=4-int-arg call + a switch). Skips if the arm32 # toolchain/qemu prerequisites are absent (see test/lib/check_arm32_env.sh). +# The macro lane (arm32_macros.sh) verifies the -march/-mcpu/-mfpu/-mfloat-abi +# __ARM_* predefines via a #error-guarded compile; it needs only `kit` (and +# host clang, when present, for the cross-check) — no qemu/lld. test-smoke-arm32: + bash test/smoke/arm32_macros.sh bash test/smoke/arm32.sh # The whole-toolchain bare-metal smoke (compile + assemble reset stub + link + diff --git a/mk/test_unit.mk b/mk/test_unit.mk @@ -30,7 +30,8 @@ UNIT_CFLAGS_INTERNAL = $(HOST_CFLAGS) -Iinclude -Isrc -Itest # ---- registrations: stem lists + per-stem source --------------------------- UNIT_TESTS_PUBLIC := \ - ar_test target_test cg_api_test cg_switch_test cg_fp_cmp_test \ + ar_test target_test arm32_target_features_test cg_api_test cg_switch_test \ + cg_fp_cmp_test \ cg_control_test cg_const_test hash_test \ panic_recovery_test profile_test \ link_script_test \ @@ -39,6 +40,7 @@ UNIT_TESTS_PUBLIC := \ strength_reduce_test ar_test_SRC := test/ar/ar_test.c target_test_SRC := test/api/target_test.c +arm32_target_features_test_SRC := test/api/arm32_target_features_test.c hash_test_SRC := test/api/hash_test.c panic_recovery_test_SRC := test/api/panic_recovery_test.c profile_test_SRC := test/api/profile_test.c diff --git a/src/api/compile.c b/src/api/compile.c @@ -505,6 +505,21 @@ uint32_t kit_compiler_arch_float_predefines(KitCompiler* c, return arch_float_predefines(arch, &spec, out); } +uint32_t kit_compiler_arch_feature_predefines(KitCompiler* c, + const KitPredefinedMacro** out) { + const ArchImpl* arch; + const KitTarget* tgt; + KitTargetSpec spec; + if (out) *out = NULL; + if (!c) return 0; + arch = arch_for_compiler((Compiler*)c); + tgt = ((Compiler*)c)->target_ref; + spec = kit_compiler_target_spec(c); + return arch_feature_predefines(arch, &spec, + tgt ? tgt->feature_words : NULL, + tgt ? tgt->nfeature_words : 0, out); +} + static void validate_bytes(Compiler* c, const KitSourceInput* in) { if (!in->name.s) panic_bad_options(c, "input name is NULL"); if (!in->bytes.data && in->bytes.len != 0) { diff --git a/src/api/core.c b/src/api/core.c @@ -109,6 +109,19 @@ KitStatus kit_target_new(const KitContext* ctx, const KitTargetOptions* opts, } } + /* -mcpu= after -march= so a CPU selector refines (or, for ARM, re-pins) the + * ISA profile; explicit -mattr/-m<feature> overrides come last. */ + if (opts->cpu.s && opts->cpu.len) { + KitStatus st = arch_target_feature_apply_cpu( + arch, &t->spec, opts->cpu, t->feature_words, t->nfeature_words); + if (st != KIT_OK) { + kit_ctx_diagf(ctx, "unsupported CPU for %s: %.*s", arch->name, + KIT_SLICE_ARG(opts->cpu)); + kit_target_free(t); + return st == KIT_UNSUPPORTED ? KIT_INVALID : st; + } + } + for (i = 0; i < opts->nfeatures; ++i) { u32 idx; KitSlice name = opts->features[i].name; diff --git a/src/arch/arch.h b/src/arch/arch.h @@ -301,6 +301,13 @@ typedef struct ArchImpl { void (*target_feature_defaults)(const Target*, u64* words, u32 nwords); KitStatus (*target_feature_apply_isa)(const Target*, KitSlice isa, u64* words, u32 nwords); + /* Apply a CPU/core selector (-mcpu=) onto the feature words, the same way + * target_feature_apply_isa applies -march=. A CPU typically pins an ISA + * profile plus extensions (e.g. ARM cortex-m4 -> armv7e-m + dsp). Returns + * KIT_OK on success, KIT_UNSUPPORTED for an unknown CPU. NULL hook means the + * arch has no CPU axis (arch_target_feature_apply_cpu no-ops to KIT_OK). */ + KitStatus (*target_feature_apply_cpu)(const Target*, KitSlice cpu, u64* words, + u32 nwords); const char* (*register_name)(uint32_t dwarf_idx); int (*register_index)(const char* name, uint32_t* idx_out); @@ -372,6 +379,21 @@ typedef struct ArchImpl { * returned entries are static-lifetime. Read via arch_float_predefines. */ u32 (*float_predefines)(const struct ArchImpl* impl, const KitTargetSpec* spec, const KitPredefinedMacro** out); + + /* Feature-keyed predefined macros for the resolved target, selected from the + * resolved -march/-mcpu/-mattr feature words (`words`/`nwords`) and the spec + * (so an arch may also key on spec->float_abi here). The same single-static- + * table limitation that motivates float_predefines applies to any macro that + * varies by extension (e.g. ARM's __ARM_ARCH_7M__ vs __ARM_ARCH_7EM__ and + * __ARM_FEATURE_DSP). An arch must return a STATIC-lifetime table selected + * among precomputed `static const` tables per discrete config; the returned + * entries are borrowed. NULL hook means the arch's static `predefined_macros` + * table is complete. ARM folds its float macros into this hook (and leaves + * float_predefines NULL) because they share the feature words. Read via + * arch_feature_predefines. */ + u32 (*feature_predefines)(const struct ArchImpl* impl, + const KitTargetSpec* spec, const u64* words, + u32 nwords, const KitPredefinedMacro** out); } ArchImpl; const ArchImpl* arch_lookup(KitArchKind); @@ -381,6 +403,8 @@ void arch_target_feature_defaults(const ArchImpl*, const Target*, u64* words, u32 nwords); KitStatus arch_target_feature_apply_isa(const ArchImpl*, const Target*, KitSlice isa, u64* words, u32 nwords); +KitStatus arch_target_feature_apply_cpu(const ArchImpl*, const Target*, + KitSlice cpu, u64* words, u32 nwords); /* Resolve & validate `spec->float_abi` from the explicit -mabi string and the * resolved -march feature bits, dispatching to `impl->resolve_float_abi`. When @@ -413,6 +437,18 @@ static inline u32 arch_float_predefines(const ArchImpl* impl, return impl->float_predefines(impl, spec, out); } +/* Feature-keyed predefined macros for `spec` + resolved feature `words`/`nwords` + * (see ArchImpl.feature_predefines). Sets *out to a borrowed static-lifetime + * array and returns its length; 0 (with *out=NULL) when the arch sets no hook. */ +static inline u32 arch_feature_predefines(const ArchImpl* impl, + const KitTargetSpec* spec, + const u64* words, u32 nwords, + const KitPredefinedMacro** out) { + if (out) *out = NULL; + if (!impl || !impl->feature_predefines) return 0; + return impl->feature_predefines(impl, spec, words, nwords, out); +} + /* Spelling for a relocated operand in `cc -S` text, for the compiler's target * arch+format. Returns 1 and fills *out when symbolizable, 0 to keep numeric * (also when the arch provides no asm_ops). Thin dispatch over ArchAsmOps. */ diff --git a/src/arch/arm32/arch.c b/src/arch/arm32/arch.c @@ -3,7 +3,10 @@ * The backend is built on the shared NativeDirectTarget/MCEmitter substrate; * arch.c wires the factories, register metadata, CFI defaults, predefined * macros, target features, and the intra-function Thumb-2 branch label-fixup - * patcher. Soft-float v1; ARMv7-M baseline (Cortex-M3). See doc/plan/ARM32.md. */ + * patcher. The -march/-mcpu/-mfpu/-mfloat-abi axes resolve here: -march/-mcpu + * set the dsp feature (ARMv7-M baseline vs ARMv7E-M), -mfpu sets vfp/fpv5, and + * feature_predefines emits the matching __ARM_* macros. Soft-float v1 (the + * driver still rejects -mfloat-abi=hard/softfp). See doc/plan/ARM32.md. */ #include "arch/arch.h" #include <string.h> @@ -14,6 +17,7 @@ #include "cg/native_direct_target.h" #include "core/bytes.h" #include "core/core.h" +#include "core/strbuf.h" #include "link/link_arch.h" #include "obj/obj.h" @@ -107,93 +111,290 @@ static int arm32_apply_label_fixup(Compiler* c, const ArchLabelFixup* fx) { return 0; } -/* Mirrors `clang --target=arm-none-eabi -march=armv7-m -mfloat-abi=soft -E -dM` - * for the in-scope ARMv7-M (Cortex-M3) soft-float profile. Arch-identity + - * feature macros only; the ILP32 size/type macros are emitted by the - * preprocessor from target.ptr_size=4. Static (ptr,count) table consumed - * without a Target, so it reflects this one default profile (soft-float - * Cortex-M3); -mcpu/-march cannot retune it (cf. rv32's identical limitation). - * Soft-float codegen correctness is driven by c->target.float_abi. */ +/* The always-on base of the arm32 predefined-macro set: everything that does + * NOT vary with the -march/-mcpu DSP extension or the -mfpu/-mfloat-abi float + * configuration. Validated against + * `clang --target=arm-none-eabi -march=armv7-m -mfloat-abi=soft -E -dM` (and the + * armv7e-m / hard-float variants — see arm32_feature_predefines). The ILP32 + * size/type macros are emitted by the preprocessor from target.ptr_size=4. + * + * The feature-keyed macros — the arch revision (__ARM_ARCH_7M__ vs + * __ARM_ARCH_7EM__), the DSP set, and the float set (__ARM_FP / __SOFTFP__ / + * __ARM_PCS_VFP / ...) — are NOT in this table; they are selected by + * arm32_feature_predefines from the resolved feature words + spec->float_abi. */ static const KitPredefinedMacro arm32_predefined_macros[] = { {KIT_SLICE_LIT("__arm__"), KIT_SLICE_LIT("1")}, {KIT_SLICE_LIT("__ARMEL__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__THUMBEL__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__thumb__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__thumb2__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__THUMB_INTERWORK__"), KIT_SLICE_LIT("1")}, /* arm-none-eabi is an ELF target with no symbol-name prefix; gcc/clang * define both. Without them, hand-written runtime asm that branches on the * object format (compiler-rt's assembly.h) falls through to the COFF path * (`.def/.scl/.endef`, a leading `_`). __USER_LABEL_PREFIX__ is empty. */ {KIT_SLICE_LIT("__ELF__"), KIT_SLICE_LIT("1")}, {KIT_SLICE_LIT("__USER_LABEL_PREFIX__"), KIT_SLICE_LIT("")}, - {KIT_SLICE_LIT("__thumb__"), KIT_SLICE_LIT("1")}, - {KIT_SLICE_LIT("__thumb2__"), KIT_SLICE_LIT("1")}, - {KIT_SLICE_LIT("__THUMBEL__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_32BIT_STATE"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_ACLE"), KIT_SLICE_LIT("200")}, {KIT_SLICE_LIT("__ARM_ARCH"), KIT_SLICE_LIT("7")}, - {KIT_SLICE_LIT("__ARM_ARCH_7M__"), KIT_SLICE_LIT("1")}, {KIT_SLICE_LIT("__ARM_ARCH_PROFILE"), KIT_SLICE_LIT("'M'")}, {KIT_SLICE_LIT("__ARM_ARCH_ISA_THUMB"), KIT_SLICE_LIT("2")}, {KIT_SLICE_LIT("__ARM_ARCH_ISA_ARM"), KIT_SLICE_LIT("0")}, - {KIT_SLICE_LIT("__ARM_32BIT_STATE"), KIT_SLICE_LIT("1")}, {KIT_SLICE_LIT("__ARM_EABI__"), KIT_SLICE_LIT("1")}, {KIT_SLICE_LIT("__ARM_PCS"), KIT_SLICE_LIT("1")}, - {KIT_SLICE_LIT("__SOFTFP__"), KIT_SLICE_LIT("1")}, - /* __ARM_FP is a bitmask of hardware FP precisions; on soft-float it is left - * UNDEFINED (not 0), matching clang/gcc — `#ifdef __ARM_FP` then correctly - * selects the integer code path (e.g. the coroutine register-save asm skips - * the d8..d15 VFP saves). A hard-float variant will define it. */ {KIT_SLICE_LIT("__ARM_FEATURE_CLZ"), KIT_SLICE_LIT("1")}, - {KIT_SLICE_LIT("__ARM_FEATURE_IDIV"), KIT_SLICE_LIT("1")}, - {KIT_SLICE_LIT("__ARM_FEATURE_UNALIGNED"), KIT_SLICE_LIT("1")}, {KIT_SLICE_LIT("__ARM_FEATURE_QBIT"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FEATURE_SAT"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FEATURE_UNALIGNED"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FEATURE_LDREX"), KIT_SLICE_LIT("0x7")}, + {KIT_SLICE_LIT("__ARM_FEATURE_COPROC"), KIT_SLICE_LIT("0xf")}, + {KIT_SLICE_LIT("__ARM_FEATURE_IDIV"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_ARCH_EXT_IDIV__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FP16_FORMAT_IEEE"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FP16_ARGS"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_SIZEOF_WCHAR_T"), KIT_SLICE_LIT("4")}, + {KIT_SLICE_LIT("__ARM_SIZEOF_MINIMAL_ENUM"), KIT_SLICE_LIT("4")}, + /* __VFP_FP__ denotes the VFP *number format* (IEEE-754, little-endian word + * order), not FPU presence; clang/gcc define it even on soft-float. */ + {KIT_SLICE_LIT("__VFP_FP__"), KIT_SLICE_LIT("1")}, {KIT_SLICE_LIT("__ORDER_LITTLE_ENDIAN__"), KIT_SLICE_LIT("1234")}, {KIT_SLICE_LIT("__ORDER_BIG_ENDIAN__"), KIT_SLICE_LIT("4321")}, {KIT_SLICE_LIT("__BYTE_ORDER__"), KIT_SLICE_LIT("__ORDER_LITTLE_ENDIAN__")}, {KIT_SLICE_LIT("__LITTLE_ENDIAN__"), KIT_SLICE_LIT("1")}, }; +/* Feature-word indices — MUST match arm32_target_features[] order below. */ +enum { + ARM32_FEAT_DSP = 0, /* ARMv7E-M saturating + packed-SIMD (the DSP extension) */ + ARM32_FEAT_VFP = 1, /* VFP present (FPv4-SP, Cortex-M4F) */ + ARM32_FEAT_FPV5 = 2, /* FPv5 (Cortex-M7) — implies VFP */ +}; + static const ArchTargetFeature arm32_target_features[] = { - {"dsp"}, /* ARMv7E-M saturating/SIMD (follow-on) */ - {"vfp"}, /* hard-float FPv4-SP (follow-on) */ + {"dsp"}, + {"vfp"}, + {"fpv5"}, }; +static void arm32_feature_set(u64* words, u32 nwords, u32 idx) { + if (!words || idx / 64u >= nwords) return; + words[idx / 64u] |= 1ull << (idx % 64u); +} + +static void arm32_feature_clear(u64* words, u32 nwords, u32 idx) { + if (!words || idx / 64u >= nwords) return; + words[idx / 64u] &= ~(1ull << (idx % 64u)); +} + +static int arm32_feature_get(const u64* words, u32 nwords, u32 idx) { + if (!words || idx / 64u >= nwords) return 0; + return (words[idx / 64u] & (1ull << (idx % 64u))) != 0; +} + static void arm32_target_feature_defaults(const Target* t, u64* words, u32 nwords) { (void)t; (void)words; - (void)nwords; /* baseline ARMv7-M soft: no features default-on. */ + (void)nwords; /* baseline ARMv7-M (Cortex-M3) soft: no features default-on. */ } +/* -march= profile -> the DSP feature bit. armv7-m / thumbv7m is the ARMv7-M + * baseline (no DSP); armv7e-m / thumbv7em is ARMv7E-M (DSP). The FP features + * come from -mfpu= (an explicit feature), not -march=. */ static KitStatus arm32_target_feature_apply_isa(const Target* t, KitSlice isa, u64* words, u32 nwords) { (void)t; - (void)words; - (void)nwords; - if (kit_slice_eq_cstr(isa, "armv7-m") || kit_slice_eq_cstr(isa, "armv7e-m") || - kit_slice_eq_cstr(isa, "thumbv7m") || kit_slice_eq_cstr(isa, "thumbv7em")) + if (kit_slice_eq_cstr(isa, "armv7-m") || kit_slice_eq_cstr(isa, "thumbv7m")) { + arm32_feature_clear(words, nwords, ARM32_FEAT_DSP); + return KIT_OK; + } + if (kit_slice_eq_cstr(isa, "armv7e-m") || + kit_slice_eq_cstr(isa, "thumbv7em")) { + arm32_feature_set(words, nwords, ARM32_FEAT_DSP); + return KIT_OK; + } + return KIT_UNSUPPORTED; +} + +/* -mcpu= core -> ISA profile (the DSP bit). cortex-m3 is ARMv7-M (no DSP); + * cortex-m4/m4f/m7/m7f are ARMv7E-M (DSP). The trailing 'f' selects an FPU + * (hard-float, a follow-on) — it does not change the integer/DSP profile, and + * the FP features still arrive via -mfpu=, so cortex-m4 and cortex-m4f resolve + * the same DSP bit here. */ +static KitStatus arm32_target_feature_apply_cpu(const Target* t, KitSlice cpu, + u64* words, u32 nwords) { + (void)t; + if (kit_slice_eq_cstr(cpu, "cortex-m3")) { + arm32_feature_clear(words, nwords, ARM32_FEAT_DSP); return KIT_OK; + } + if (kit_slice_eq_cstr(cpu, "cortex-m4") || + kit_slice_eq_cstr(cpu, "cortex-m4f") || + kit_slice_eq_cstr(cpu, "cortex-m7") || + kit_slice_eq_cstr(cpu, "cortex-m7f")) { + arm32_feature_set(words, nwords, ARM32_FEAT_DSP); + return KIT_OK; + } return KIT_UNSUPPORTED; } -/* arm-none-eabi v1 is soft-float only: `float` and `double` are lowered through - * the AEABI / compiler-rt helpers regardless of -mfpu, and FP args travel in the - * core registers (AAPCS soft). So float_abi resolves unconditionally to SOFT — - * this is what gates the arch-neutral soft-float lowering in src/cg/arith.c - * (api_target_has_no_hw_double). Without this hook the spec would stay at - * KIT_FLOAT_ABI_DEFAULT and `double`/`float` ops would take the (unimplemented) - * hardware path. The driver rejects -mfloat-abi=hard/softfp before we get here; - * single-precision hard-float (FPv4-SP) is a follow-on that will thread the - * requested ABI through this hook. */ +/* Resolve & validate spec->float_abi for arm-none-eabi. + * (empty) / "soft" -> SOFT (no FPU; float/double via AEABI helpers). + * "softfp" / "hard" -> SINGLE (FPv4-SP / FPv5 single-precision hard-float), + * requiring a VFP/FPV5 feature (else KIT_INVALID). + * SOFT gates the arch-neutral soft-float lowering in src/cg/arith.c + * (api_target_has_no_hw_double). The driver still rejects -mfloat-abi=hard/softfp + * (hard-float codegen is a separate work item), so the non-SOFT branches are + * written and unit-testable at the kit_target_new API level but unreachable via + * the CLI today. `double` stays soft on FPv4-SP (the FPV5 axis would extend + * this), mirroring rv32 ilp32f. */ static KitStatus arm32_resolve_float_abi(const ArchImpl* impl, KitTargetSpec* spec, const u64* feature_words, u32 nfeature_words, KitSlice abi, char* err, size_t errcap) { + StrBuf sb; + int has_vfp; + strbuf_init(&sb, err, errcap); + has_vfp = arm32_feature_get(feature_words, nfeature_words, ARM32_FEAT_VFP) || + arm32_feature_get(feature_words, nfeature_words, ARM32_FEAT_FPV5); + + if (!abi.s || abi.len == 0 || kit_slice_eq_cstr(abi, "soft")) { + if (spec) spec->float_abi = (uint8_t)KIT_FLOAT_ABI_SOFT; + return KIT_OK; + } + if (kit_slice_eq_cstr(abi, "softfp") || kit_slice_eq_cstr(abi, "hard")) { + if (!has_vfp) { + strbuf_puts(&sb, "hardware float ABI ("); + strbuf_put_slice(&sb, abi); + strbuf_puts(&sb, ") requires an FPU (-mfpu=fpv4-sp-d16 / fpv5-d16) for "); + strbuf_puts(&sb, impl->name); + return KIT_INVALID; + } + if (spec) spec->float_abi = (uint8_t)KIT_FLOAT_ABI_SINGLE; + return KIT_OK; + } + strbuf_puts(&sb, "unsupported float ABI for "); + strbuf_puts(&sb, impl->name); + strbuf_puts(&sb, ": "); + strbuf_put_slice(&sb, abi); + return KIT_INVALID; +} + +/* ---- Feature-keyed predefined macros ------------------------------------- + * Each discrete {arch-rev} x {float-config} combination is a fully precomputed + * `static const` table; the hook selects exactly one and returns it (no mutable + * state — same rodata-table contract as arm32_predefined_macros). Validated + * against host clang (see arm32_predefined_macros). + * + * Arch revision is mutually exclusive on the DSP bit: no-DSP -> __ARM_ARCH_7M__; + * DSP -> __ARM_ARCH_7EM__ + __ARM_FEATURE_DSP + __ARM_FEATURE_SIMD32. + * + * Float config keys on float_abi + (vfp | fpv5). SOFT defines only __SOFTFP__ + * and leaves __ARM_FP UNDEFINED (so `#ifdef __ARM_FP` selects the integer path — + * e.g. the coro asm skips the d8..d15 VFP saves). A resolved hardware ABI + * defines __ARM_FP + __ARM_FEATURE_FMA + __ARM_PCS_VFP + the legacy + * __ARM_VFPV2/3/4__ aliases; FPV5 (Cortex-M7) raises __ARM_FP to 0xe and adds + * __ARM_FPV5__. */ + +/* macros: __ARM_ARCH_7M__ */ +static const KitPredefinedMacro arm32_macros_7m_soft[] = { + {KIT_SLICE_LIT("__ARM_ARCH_7M__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__SOFTFP__"), KIT_SLICE_LIT("1")}, +}; +static const KitPredefinedMacro arm32_macros_7m_v4hard[] = { + {KIT_SLICE_LIT("__ARM_ARCH_7M__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FP"), KIT_SLICE_LIT("0x6")}, + {KIT_SLICE_LIT("__ARM_FEATURE_FMA"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_VFPV2__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_VFPV3__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_VFPV4__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_PCS_VFP"), KIT_SLICE_LIT("1")}, +}; +static const KitPredefinedMacro arm32_macros_7m_v5hard[] = { + {KIT_SLICE_LIT("__ARM_ARCH_7M__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FP"), KIT_SLICE_LIT("0xe")}, + {KIT_SLICE_LIT("__ARM_FEATURE_FMA"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FPV5__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_VFPV2__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_VFPV3__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_VFPV4__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_PCS_VFP"), KIT_SLICE_LIT("1")}, +}; + +/* macros: __ARM_ARCH_7EM__ + DSP */ +static const KitPredefinedMacro arm32_macros_7em_soft[] = { + {KIT_SLICE_LIT("__ARM_ARCH_7EM__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FEATURE_DSP"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FEATURE_SIMD32"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__SOFTFP__"), KIT_SLICE_LIT("1")}, +}; +static const KitPredefinedMacro arm32_macros_7em_v4hard[] = { + {KIT_SLICE_LIT("__ARM_ARCH_7EM__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FEATURE_DSP"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FEATURE_SIMD32"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FP"), KIT_SLICE_LIT("0x6")}, + {KIT_SLICE_LIT("__ARM_FEATURE_FMA"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_VFPV2__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_VFPV3__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_VFPV4__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_PCS_VFP"), KIT_SLICE_LIT("1")}, +}; +static const KitPredefinedMacro arm32_macros_7em_v5hard[] = { + {KIT_SLICE_LIT("__ARM_ARCH_7EM__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FEATURE_DSP"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FEATURE_SIMD32"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FP"), KIT_SLICE_LIT("0xe")}, + {KIT_SLICE_LIT("__ARM_FEATURE_FMA"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_FPV5__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_VFPV2__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_VFPV3__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_VFPV4__"), KIT_SLICE_LIT("1")}, + {KIT_SLICE_LIT("__ARM_PCS_VFP"), KIT_SLICE_LIT("1")}, +}; + +static u32 arm32_feature_predefines(const ArchImpl* impl, + const KitTargetSpec* spec, const u64* words, + u32 nwords, const KitPredefinedMacro** out) { + int dsp = arm32_feature_get(words, nwords, ARM32_FEAT_DSP); + int vfp = arm32_feature_get(words, nwords, ARM32_FEAT_VFP); + int fpv5 = arm32_feature_get(words, nwords, ARM32_FEAT_FPV5); + KitFloatAbi fa = spec ? (KitFloatAbi)spec->float_abi : KIT_FLOAT_ABI_SOFT; + /* Hardware float = a resolved single/double ABI with a VFP feature present. + * kit's KitFloatAbi has no distinct softfp encoding, so a resolved hardware + * ABI follows the hard-float macro convention (__ARM_PCS_VFP). FPV5 raises the + * profile. With no VFP feature or an unresolved/soft ABI, the float set is + * soft (__SOFTFP__, __ARM_FP undefined). */ + int hardfp = + (fa == KIT_FLOAT_ABI_SINGLE || fa == KIT_FLOAT_ABI_DOUBLE) && (vfp || fpv5); (void)impl; - (void)feature_words; - (void)nfeature_words; - (void)abi; - (void)err; - (void)errcap; - if (spec) spec->float_abi = (uint8_t)KIT_FLOAT_ABI_SOFT; - return KIT_OK; + + if (!dsp) { + if (!hardfp) { + *out = arm32_macros_7m_soft; + return (u32)(sizeof arm32_macros_7m_soft / sizeof arm32_macros_7m_soft[0]); + } + if (fpv5) { + *out = arm32_macros_7m_v5hard; + return (u32)(sizeof arm32_macros_7m_v5hard / + sizeof arm32_macros_7m_v5hard[0]); + } + *out = arm32_macros_7m_v4hard; + return (u32)(sizeof arm32_macros_7m_v4hard / sizeof arm32_macros_7m_v4hard[0]); + } + if (!hardfp) { + *out = arm32_macros_7em_soft; + return (u32)(sizeof arm32_macros_7em_soft / sizeof arm32_macros_7em_soft[0]); + } + if (fpv5) { + *out = arm32_macros_7em_v5hard; + return (u32)(sizeof arm32_macros_7em_v5hard / + sizeof arm32_macros_7em_v5hard[0]); + } + *out = arm32_macros_7em_v4hard; + return (u32)(sizeof arm32_macros_7em_v4hard / + sizeof arm32_macros_7em_v4hard[0]); } static int arm32_supports_call_conv(const Compiler* c, KitCgCallConv cc) { @@ -288,6 +489,7 @@ const ArchImpl arch_impl_arm32 = { (u32)(sizeof arm32_target_features / sizeof arm32_target_features[0]), .target_feature_defaults = arm32_target_feature_defaults, .target_feature_apply_isa = arm32_target_feature_apply_isa, + .target_feature_apply_cpu = arm32_target_feature_apply_cpu, .register_name = arm32_register_name, .register_index = arm32_register_index, .register_count = arm32_register_iter_size, @@ -304,6 +506,11 @@ const ArchImpl arch_impl_arm32 = { .atomic_lock_free_max = 4u, /* no LDREXD/STREXD on M-profile -> 8B spinlock */ .supports_call_conv = arm32_supports_call_conv, .supports_intrinsic = arm32_supports_intrinsic, - /* v1 is soft-float: float_abi resolves to SOFT (gates cg soft-float). */ + /* float_abi resolves to SOFT unless an FPU feature + a hardware ABI is set + * (gates cg soft-float). The driver still rejects -mfloat-abi=hard/softfp. */ .resolve_float_abi = arm32_resolve_float_abi, + /* arm32 folds its float macros into feature_predefines (it has both the + * feature words and spec->float_abi there), so float_predefines stays NULL. + * feature_predefines selects __ARM_ARCH_7M__/7EM__ + DSP + the float set. */ + .feature_predefines = arm32_feature_predefines, }; diff --git a/src/arch/registry.c b/src/arch/registry.c @@ -123,6 +123,17 @@ KitStatus arch_target_feature_apply_isa(const ArchImpl* a, const Target* target, return a->target_feature_apply_isa(target, isa, words, nwords); } +KitStatus arch_target_feature_apply_cpu(const ArchImpl* a, const Target* target, + KitSlice cpu, u64* words, u32 nwords) { + if (!a || !cpu.s || cpu.len == 0) return KIT_OK; + if (!words && nwords != 0) return KIT_INVALID; + /* An arch with no CPU axis ignores -mcpu= (no-op success), matching the way + * non-RISC-V arches ignore -march= today (the spec selectors are advisory for + * those targets). */ + if (!a->target_feature_apply_cpu) return KIT_OK; + return a->target_feature_apply_cpu(target, cpu, words, nwords); +} + int arch_reloc_operand(const Compiler* c, u16 reloc_kind, ArchRelocOperand* out) { const ArchImpl* a = arch_for_compiler(c); diff --git a/test/api/arm32_target_features_test.c b/test/api/arm32_target_features_test.c @@ -0,0 +1,200 @@ +/* arm32_target_features_test — the arm-none-eabi -march/-mcpu/-mfpu/-mfloat-abi + * resolution at the public kit_target_new level: the apply_cpu hook (-mcpu=) + * and the vfp/fpv5 features (-mfpu=) that drive the dsp feature, the float ABI + * resolution, and (indirectly) the feature_predefines macro selection. + * + * Reds before the apply_cpu hook + arm32 wiring land; green after. */ + +#include <kit/core.h> +#include <kit/target.h> +#include <string.h> + +#include "lib/kit_unit.h" + +static KitUnit g_u; +#define EXPECT(c, ...) CU_EXPECT(&g_u, c, __VA_ARGS__) + +static KitTargetSpec arm32_spec(void) { + KitTargetSpec t = kit_unit_target(KIT_ARCH_ARM_32, KIT_OS_FREESTANDING, + KIT_OBJ_ELF); + t.ptr_size = 4; + t.ptr_align = 4; + return t; +} + +/* Build an arm32 target with optional -march (isa), -mcpu (cpu), -mabi (abi), + * and an explicit feature list (the -mfpu lowering records vfp/fpv5 there). */ +static KitStatus make_arm32(KitSlice isa, KitSlice cpu, KitSlice abi, + const KitTargetFeature* features, uint32_t nfeatures, + KitTarget** out) { + KitTargetOptions opts; + memset(&opts, 0, sizeof opts); + opts.spec = arm32_spec(); + opts.isa = isa; + opts.cpu = cpu; + opts.abi = abi; + opts.features = features; + opts.nfeatures = nfeatures; + return kit_target_new(&g_u.ctx, &opts, out); +} + +static int has(KitTarget* t, const char* name) { + return kit_target_has_feature(t, kit_slice_cstr(name)); +} + +static void check_cpu_dsp(void) { + KitTarget* t = NULL; + + /* cortex-m3 = ARMv7-M baseline: no DSP. */ + EXPECT(make_arm32(KIT_SLICE_NULL, KIT_SLICE_LIT("cortex-m3"), KIT_SLICE_NULL, + NULL, 0, &t) == KIT_OK, + "cortex-m3 target"); + EXPECT(!has(t, "dsp"), "cortex-m3 lacks dsp"); + EXPECT(!has(t, "vfp"), "cortex-m3 lacks vfp (soft)"); + kit_target_free(t); + + /* cortex-m4 = ARMv7E-M: DSP. */ + t = NULL; + EXPECT(make_arm32(KIT_SLICE_NULL, KIT_SLICE_LIT("cortex-m4"), KIT_SLICE_NULL, + NULL, 0, &t) == KIT_OK, + "cortex-m4 target"); + EXPECT(has(t, "dsp"), "cortex-m4 has dsp"); + kit_target_free(t); + + /* cortex-m7 = ARMv7E-M: DSP. */ + t = NULL; + EXPECT(make_arm32(KIT_SLICE_NULL, KIT_SLICE_LIT("cortex-m7"), KIT_SLICE_NULL, + NULL, 0, &t) == KIT_OK, + "cortex-m7 target"); + EXPECT(has(t, "dsp"), "cortex-m7 has dsp"); + kit_target_free(t); +} + +static void check_march_dsp(void) { + KitTarget* t = NULL; + + /* -march=armv7-m: no DSP. */ + EXPECT(make_arm32(KIT_SLICE_LIT("armv7-m"), KIT_SLICE_NULL, KIT_SLICE_NULL, + NULL, 0, &t) == KIT_OK, + "armv7-m target"); + EXPECT(!has(t, "dsp"), "armv7-m lacks dsp"); + kit_target_free(t); + + /* -march=armv7e-m: DSP. */ + t = NULL; + EXPECT(make_arm32(KIT_SLICE_LIT("armv7e-m"), KIT_SLICE_NULL, KIT_SLICE_NULL, + NULL, 0, &t) == KIT_OK, + "armv7e-m target"); + EXPECT(has(t, "dsp"), "armv7e-m has dsp"); + kit_target_free(t); + + /* thumbv7em alias: DSP. */ + t = NULL; + EXPECT(make_arm32(KIT_SLICE_LIT("thumbv7em"), KIT_SLICE_NULL, KIT_SLICE_NULL, + NULL, 0, &t) == KIT_OK, + "thumbv7em target"); + EXPECT(has(t, "dsp"), "thumbv7em has dsp"); + kit_target_free(t); +} + +static void check_mfpu_features(void) { + /* -mfpu=fpv4-sp-d16 lowers to the "vfp" feature (the driver does the + * name->feature mapping; here we record it directly). cortex-m4 + vfp = + * dsp + vfp. */ + KitTargetFeature vfp[] = { + {KIT_SLICE_LIT("vfp"), true}, + }; + KitTargetFeature fpv5[] = { + {KIT_SLICE_LIT("vfp"), true}, + {KIT_SLICE_LIT("fpv5"), true}, + }; + KitTarget* t = NULL; + + EXPECT(make_arm32(KIT_SLICE_NULL, KIT_SLICE_LIT("cortex-m4"), KIT_SLICE_NULL, + vfp, 1, &t) == KIT_OK, + "cortex-m4 + vfp target"); + EXPECT(has(t, "dsp"), "cortex-m4 + vfp has dsp"); + EXPECT(has(t, "vfp"), "cortex-m4 + vfp has vfp"); + EXPECT(!has(t, "fpv5"), "cortex-m4 + vfp lacks fpv5"); + kit_target_free(t); + + /* cortex-m7 + fpv5 (the fpv5-d16 fpu). */ + t = NULL; + EXPECT(make_arm32(KIT_SLICE_NULL, KIT_SLICE_LIT("cortex-m7"), KIT_SLICE_NULL, + fpv5, 2, &t) == KIT_OK, + "cortex-m7 + fpv5 target"); + EXPECT(has(t, "dsp"), "cortex-m7 + fpv5 has dsp"); + EXPECT(has(t, "vfp"), "cortex-m7 + fpv5 has vfp"); + EXPECT(has(t, "fpv5"), "cortex-m7 + fpv5 has fpv5"); + kit_target_free(t); +} + +static void check_float_abi(void) { + KitTargetFeature vfp[] = { + {KIT_SLICE_LIT("vfp"), true}, + }; + KitTarget* t = NULL; + KitTargetSpec resolved; + + /* Default / soft: float_abi resolves to SOFT (gates soft-float lowering). */ + EXPECT(make_arm32(KIT_SLICE_NULL, KIT_SLICE_LIT("cortex-m3"), KIT_SLICE_NULL, + NULL, 0, &t) == KIT_OK, + "cortex-m3 soft target"); + resolved = kit_target_spec(t); + EXPECT(resolved.float_abi == (uint8_t)KIT_FLOAT_ABI_SOFT, + "cortex-m3 resolves SOFT float ABI"); + kit_target_free(t); + + /* -mabi=hard with a VFP feature resolves to a hardware (single) float ABI. + * (The driver still rejects -mfloat-abi=hard, but the resolver is exercisable + * here at the API level.) */ + t = NULL; + EXPECT(make_arm32(KIT_SLICE_NULL, KIT_SLICE_LIT("cortex-m4"), + KIT_SLICE_LIT("hard"), vfp, 1, &t) == KIT_OK, + "cortex-m4 hard+vfp target"); + resolved = kit_target_spec(t); + EXPECT(resolved.float_abi == (uint8_t)KIT_FLOAT_ABI_SINGLE, + "cortex-m4 hard+vfp resolves SINGLE float ABI"); + kit_target_free(t); + + /* -mabi=hard WITHOUT a VFP feature is invalid. */ + t = NULL; + g_u.last_diag[0] = '\0'; + EXPECT(make_arm32(KIT_SLICE_NULL, KIT_SLICE_LIT("cortex-m4"), + KIT_SLICE_LIT("hard"), NULL, 0, &t) == KIT_INVALID, + "hard ABI without FPU rejected"); + EXPECT(t == NULL, "hard ABI without FPU leaves target NULL"); + kit_target_free(t); +} + +static void check_errors(void) { + KitTarget* t = NULL; + + /* Unknown CPU is rejected (KIT_INVALID, with a diagnostic). */ + g_u.last_diag[0] = '\0'; + EXPECT(make_arm32(KIT_SLICE_NULL, KIT_SLICE_LIT("cortex-a9"), KIT_SLICE_NULL, + NULL, 0, &t) == KIT_INVALID, + "unknown cpu rejected"); + EXPECT(t == NULL, "unknown cpu leaves target NULL"); + EXPECT(strstr(g_u.last_diag, "unsupported CPU") != NULL, + "unknown cpu diagnostic"); + + /* Unknown -march is rejected. */ + t = NULL; + g_u.last_diag[0] = '\0'; + EXPECT(make_arm32(KIT_SLICE_LIT("armv6-m"), KIT_SLICE_NULL, KIT_SLICE_NULL, + NULL, 0, &t) == KIT_INVALID, + "unknown march rejected"); + EXPECT(t == NULL, "unknown march leaves target NULL"); +} + +int main(void) { + kit_unit_init(&g_u); + check_cpu_dsp(); + check_march_dsp(); + check_mfpu_features(); + check_float_abi(); + check_errors(); + kit_unit_summary(&g_u, "arm32_target_features_test"); + return kit_unit_status(&g_u); +} diff --git a/test/smoke/arm32_macros.sh b/test/smoke/arm32_macros.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +# test/smoke/arm32_macros.sh — verify kit's arm-none-eabi __ARM_* predefined +# macro set for the -march / -mcpu / -mfpu / -mfloat-abi axes. +# +# kit's `cc -E` has no `-dM` dump, so the macro set is asserted with a +# #error-guarded compile: a probe TU enumerates the expected (and the +# must-NOT-be-defined) macros for a configuration; `kit cc -fsyntax-only` +# succeeds iff the predefines match. This needs only `kit` (no qemu/lld), so it +# runs independently of the behavioral-oracle prerequisites. +# +# When host clang has the arm-none-eabi target, the probe set is ALSO +# cross-checked against `clang -dM -E` for the same configuration — clang is the +# authority for the __ARM_* values. If clang is absent the cross-check is +# skipped (the kit-internal #error assertions still run). + +set -u +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +BUILD_DIR="$ROOT/build/test/smoke-arm32-macros" +mkdir -p "$BUILD_DIR" + +KIT_KIT_DIR="$ROOT/test/lib" +# shellcheck source=../lib/kit_sh_kit.sh +. "$ROOT/test/lib/kit_sh_kit.sh" +kit_report_init +KIT_SKIP_IS_FAILURE=0 + +KIT="$ROOT/build/kit" +CLANG="${ARM32_CLANG:-clang}" + +# Does host clang have the arm-none-eabi target (for the cross-check)? +HAVE_CLANG=0 +if command -v "$CLANG" >/dev/null 2>&1 && + echo 'int e(void){return 0;}' | + "$CLANG" --target=arm-none-eabi -mcpu=cortex-m3 -mthumb -mfloat-abi=soft \ + -ffreestanding -nostdlib -c -x c - -o /dev/null >/dev/null 2>&1; then + HAVE_CLANG=1 +fi + +# kit_macro_case <name> <kit-flags...> -- <expect-defined...> // <expect-undef...> +# Builds a probe TU and asserts kit's predefines via #error, then (if clang is +# available) confirms clang agrees on the same defined/undefined split. +kit_macro_case() { + local name="$1"; shift + local flags=() + while [ "$1" != "--" ]; do flags+=("$1"); shift; done + shift + local defined=() + while [ "$1" != "//" ]; do defined+=("$1"); shift; done + shift + local undef=("$@") + + local src="$BUILD_DIR/$name.c" + : > "$src" + local m + for m in "${defined[@]}"; do + printf '#if !defined(%s)\n#error "%s: expected %s defined"\n#endif\n' \ + "$m" "$name" "$m" >> "$src" + done + for m in "${undef[@]}"; do + printf '#if defined(%s)\n#error "%s: did not expect %s defined"\n#endif\n' \ + "$m" "$name" "$m" >> "$src" + done + printf 'int probe_%s;\n' "$(echo "$name" | tr -c 'A-Za-z0-9_' '_')" >> "$src" + + if "$KIT" cc -target arm-none-eabi "${flags[@]}" -ffreestanding \ + -fsyntax-only "$src" >/dev/null 2>"$BUILD_DIR/$name.kit.err"; then + kit_pass "macros/$name (kit)" + else + kit_fail "macros/$name (kit)" "$(cat "$BUILD_DIR/$name.kit.err")" + fi + + if [ "$HAVE_CLANG" -eq 1 ]; then + # kit is soft-float-only; clang defaults some cores (e.g. cortex-m4) to an + # FPU, so pin -mfloat-abi=soft to make the comparison apples-to-apples. + local dm + dm="$("$CLANG" --target=arm-none-eabi "${flags[@]}" -mfloat-abi=soft \ + -ffreestanding -dM -E - </dev/null 2>/dev/null)" + local ok=1 msg="" + for m in "${defined[@]}"; do + if ! printf '%s\n' "$dm" | grep -q "^#define $m\b"; then + ok=0; msg="$msg clang-missing:$m" + fi + done + for m in "${undef[@]}"; do + if printf '%s\n' "$dm" | grep -q "^#define $m\b"; then + ok=0; msg="$msg clang-has:$m" + fi + done + if [ "$ok" -eq 1 ]; then + kit_pass "macros/$name (clang agrees)" + else + kit_fail "macros/$name (clang agrees)" "$msg" + fi + fi +} + +# ---- armv7-m baseline (Cortex-M3, soft) ----------------------------------- +kit_macro_case "cortex-m3" -mcpu=cortex-m3 -- \ + __arm__ __ARMEL__ __THUMBEL__ __thumb__ __thumb2__ __THUMB_INTERWORK__ \ + __ELF__ __ARM_32BIT_STATE __ARM_ACLE __ARM_ARCH __ARM_ARCH_PROFILE \ + __ARM_ARCH_ISA_THUMB __ARM_EABI__ __ARM_PCS __ARM_FEATURE_CLZ \ + __ARM_FEATURE_QBIT __ARM_FEATURE_SAT __ARM_FEATURE_UNALIGNED \ + __ARM_FEATURE_LDREX __ARM_FEATURE_COPROC __ARM_FEATURE_IDIV \ + __ARM_ARCH_EXT_IDIV__ __ARM_FP16_FORMAT_IEEE __ARM_FP16_ARGS \ + __ARM_SIZEOF_WCHAR_T __ARM_SIZEOF_MINIMAL_ENUM __VFP_FP__ \ + __ARM_ARCH_7M__ __SOFTFP__ \ + // \ + __ARM_ARCH_7EM__ __ARM_FEATURE_DSP __ARM_FEATURE_SIMD32 __ARM_FP \ + __ARM_PCS_VFP __ARM_FP_FAST + +# ---- armv7e-m via -march (DSP, soft) -------------------------------------- +kit_macro_case "march-armv7e-m" -march=armv7e-m -- \ + __ARM_ARCH_7EM__ __ARM_FEATURE_DSP __ARM_FEATURE_SIMD32 __SOFTFP__ \ + // \ + __ARM_ARCH_7M__ __ARM_FP __ARM_PCS_VFP + +# ---- cortex-m4 (DSP via -mcpu, soft) -------------------------------------- +kit_macro_case "cortex-m4" -mcpu=cortex-m4 -- \ + __ARM_ARCH_7EM__ __ARM_FEATURE_DSP __ARM_FEATURE_SIMD32 __SOFTFP__ \ + // \ + __ARM_ARCH_7M__ __ARM_FP __ARM_PCS_VFP + +# ---- cortex-m7 (DSP via -mcpu, soft) -------------------------------------- +kit_macro_case "cortex-m7" -mcpu=cortex-m7 -- \ + __ARM_ARCH_7EM__ __ARM_FEATURE_DSP __ARM_FEATURE_SIMD32 __SOFTFP__ \ + // \ + __ARM_ARCH_7M__ __ARM_FP __ARM_PCS_VFP + +kit_summary test-smoke-arm32-macros +kit_exit