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:
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