commit ecb49dcb57f8a6b04c57e726b7ced60ac5a5c78a
parent 66b750642967895f088399bec805c3260e26b4ae
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 17 Jun 2026 11:11:32 -0700
Tier 2: single arch/OS descriptor tables for triple parse/render + diag name
Diffstat:
4 files changed, 247 insertions(+), 124 deletions(-)
diff --git a/include/kit/target.h b/include/kit/target.h
@@ -61,6 +61,14 @@ KIT_API bool kit_target_from_triple(const char* triple, KitTargetSpec* out);
* to hold the triple plus its NUL. */
KIT_API bool kit_target_to_triple(KitTargetSpec spec, char* buf, size_t cap);
+/* Short diagnostic name for an architecture (e.g. "x86_64", "arm32",
+ * "aarch64", "riscv64", "wasm"). This is the human-facing spelling used in
+ * error messages and is distinct from the canonical triple component emitted
+ * by kit_target_to_triple (e.g. diag "arm32" vs triple "arm"). The single
+ * source for arch diagnostic names; arch_kind_name() in the arch registry
+ * delegates here. Never returns NULL ("unknown" for an unknown kind). */
+KIT_API const char* kit_target_arch_diag_name(KitArchKind arch);
+
/* Map a -mcmodel= value to a KitCodeModel. Accepts the x86 spellings
* (small/medium/large), the RISC-V aliases (medlow->small, medany->medium),
* and kernel as a conservative alias for large. Returns true and writes *out on
diff --git a/src/api/target.c b/src/api/target.c
@@ -58,6 +58,7 @@ static bool triple_tok_is_android(const char* s, size_t n,
static bool riscv_arch_tok(const char* s, size_t n, const char* base) {
size_t l = strlen(base);
size_t i;
+ if (!base) return false;
if (n < l || memcmp(s, base, l) != 0) return false;
for (i = l; i < n; ++i) {
if ((s[i] < 'a' || s[i] > 'z') && (s[i] < '0' || s[i] > '9'))
@@ -66,51 +67,104 @@ static bool riscv_arch_tok(const char* s, size_t n, const char* base) {
return true;
}
+/* Single arch descriptor table: the one place that enumerates the arch set for
+ * triple parsing, triple rendering, and diagnostic naming.
+ *
+ * - `aliases` : every exact arch-component spelling arch_from_tok accepts.
+ * NULL-terminated; pad unused slots with NULL.
+ * - `riscv_base`: optional ISA-string prefix also accepted (e.g. "riscv64gc"
+ * on top of the riscv64 base), NULL when there is none.
+ * - `triple_name`: the canonical spelling kit_target_to_triple emits. NULL
+ * means the renderer derives it from pointer size (wasm).
+ * - `diag_name` : the human-facing name arch_kind_name returns.
+ * - `ptr_size` : natural pointer size; 0 means "derive from the spelling"
+ * (wasm32 vs wasm64), handled per-alias below.
+ */
+#define ARCH_ALIAS_MAX 4
+struct ArchSpec {
+ KitArchKind kind;
+ const char* triple_name;
+ const char* diag_name;
+ uint8_t ptr_size;
+ const char* riscv_base;
+ const char* aliases[ARCH_ALIAS_MAX];
+};
+
+static const struct ArchSpec arch_specs[] = {
+ {KIT_ARCH_X86_64, "x86_64", "x86_64", 8, NULL,
+ {"x86_64", "amd64", "x64", NULL}},
+ {KIT_ARCH_X86_32, "i386", "x86_32", 4, NULL,
+ {"i386", "i486", "i586", "i686"}},
+ {KIT_ARCH_ARM_64, "aarch64", "aarch64", 8, NULL,
+ {"aarch64", "arm64", "aa64", NULL}},
+ {KIT_ARCH_ARM_32, "arm", "arm32", 4, NULL, {"arm", "armv7", NULL, NULL}},
+ {KIT_ARCH_RV64, "riscv64", "riscv64", 8, "riscv64",
+ {"riscv64", "rv64", NULL, NULL}},
+ {KIT_ARCH_RV32, "riscv32", "riscv32", 4, "riscv32",
+ {"riscv32", "rv32", NULL, NULL}},
+ /* wasm carries two spellings differing only by pointer size; triple_name
+ * is NULL so the renderer picks wasm64/wasm32 from ptr_size. */
+ {KIT_ARCH_WASM, NULL, "wasm", 4, NULL, {"wasm32", NULL, NULL, NULL}},
+ {KIT_ARCH_WASM, NULL, "wasm", 8, NULL, {"wasm64", NULL, NULL, NULL}},
+};
+
+static const size_t arch_specs_count =
+ sizeof arch_specs / sizeof arch_specs[0];
+
/* Recognize an architecture token, the single authority for the arch-name
* spellings kit accepts. Writes arch + natural pointer size on a hit. Returns
* true on success, false for an unrecognized token. Shared by the triple parser
* and the public kit_arch_from_name. */
static bool arch_from_tok(const char* s, size_t n, KitArchKind* arch_out,
uint8_t* ptr_size_out) {
- KitArchKind arch;
- uint8_t ptr_size;
- if (triple_tok_eq(s, n, "x86_64") || triple_tok_eq(s, n, "amd64") ||
- triple_tok_eq(s, n, "x64")) {
- arch = KIT_ARCH_X86_64;
- ptr_size = 8;
- } else if (triple_tok_eq(s, n, "i386") || triple_tok_eq(s, n, "i486") ||
- triple_tok_eq(s, n, "i586") || triple_tok_eq(s, n, "i686")) {
- arch = KIT_ARCH_X86_32;
- ptr_size = 4;
- } else if (triple_tok_eq(s, n, "aarch64") || triple_tok_eq(s, n, "arm64") ||
- triple_tok_eq(s, n, "aa64")) {
- arch = KIT_ARCH_ARM_64;
- ptr_size = 8;
- } else if (triple_tok_eq(s, n, "arm") || triple_tok_eq(s, n, "armv7")) {
- arch = KIT_ARCH_ARM_32;
- ptr_size = 4;
- } else if (triple_tok_eq(s, n, "riscv64") ||
- triple_tok_eq(s, n, "rv64") ||
- riscv_arch_tok(s, n, "riscv64")) {
- arch = KIT_ARCH_RV64;
- ptr_size = 8;
- } else if (triple_tok_eq(s, n, "riscv32") ||
- triple_tok_eq(s, n, "rv32") ||
- riscv_arch_tok(s, n, "riscv32")) {
- arch = KIT_ARCH_RV32;
- ptr_size = 4;
- } else if (triple_tok_eq(s, n, "wasm32")) {
- arch = KIT_ARCH_WASM;
- ptr_size = 4;
- } else if (triple_tok_eq(s, n, "wasm64")) {
- arch = KIT_ARCH_WASM;
- ptr_size = 8;
- } else {
- return false;
+ size_t si;
+ for (si = 0; si < arch_specs_count; ++si) {
+ const struct ArchSpec* a = &arch_specs[si];
+ size_t ai;
+ bool hit = false;
+ for (ai = 0; ai < ARCH_ALIAS_MAX && a->aliases[ai]; ++ai) {
+ if (triple_tok_eq(s, n, a->aliases[ai])) {
+ hit = true;
+ break;
+ }
+ }
+ if (!hit && a->riscv_base && riscv_arch_tok(s, n, a->riscv_base))
+ hit = true;
+ if (hit) {
+ if (arch_out) *arch_out = a->kind;
+ if (ptr_size_out) *ptr_size_out = a->ptr_size;
+ return true;
+ }
}
- if (arch_out) *arch_out = arch;
- if (ptr_size_out) *ptr_size_out = ptr_size;
- return true;
+ return false;
+}
+
+const char* kit_target_arch_diag_name(KitArchKind arch) {
+ size_t si;
+ for (si = 0; si < arch_specs_count; ++si) {
+ if (arch_specs[si].kind == arch) return arch_specs[si].diag_name;
+ }
+ return "unknown";
+}
+
+/* Canonical arch component for the triple renderer: the table's triple_name,
+ * or the pointer-size-derived spelling when triple_name is NULL (wasm).
+ * For the ptr-size-derived case (wasm) the historical rule is "8 -> wasm64,
+ * everything else -> wasm32", so we select the alias of the row whose ptr_size
+ * matches, falling back to the smallest-ptr-size row. */
+static const char* arch_triple_name(KitArchKind arch, uint8_t ptr_size) {
+ size_t si;
+ const char* fallback = "unknown";
+ for (si = 0; si < arch_specs_count; ++si) {
+ const struct ArchSpec* a = &arch_specs[si];
+ if (a->kind != arch) continue;
+ if (a->triple_name) return a->triple_name;
+ /* triple_name == NULL: pick the row whose pointer size matches, but keep
+ * the 4-byte (wasm32) spelling as the default for any other size. */
+ if (a->ptr_size == ptr_size) return a->aliases[0];
+ if (a->ptr_size == 4) fallback = a->aliases[0];
+ }
+ return fallback;
}
bool kit_arch_from_name(const char* name, KitArchKind* arch_out,
@@ -119,6 +173,59 @@ bool kit_arch_from_name(const char* name, KitArchKind* arch_out,
return arch_from_tok(name, strlen(name), arch_out, ptr_size_out);
}
+/* Single OS descriptor table: the one place that ties an OS to its object
+ * format and its canonical triple-render spelling.
+ *
+ * - `triple_render`: the OS component kit_target_to_triple emits. Android is
+ * special-cased in the renderer (it carries an API-level
+ * suffix), so its triple_render is unused/NULL here.
+ * - `obj` : the object format the OS implies. KIT_OBJ_WASM is used as
+ * a sentinel on KIT_OS_FREESTANDING meaning "derive from
+ * arch" (wasm -> WASM, otherwise ELF); see os_objfmt.
+ *
+ * The token-matching for OSes with version suffixes (android API level, the iOS
+ * version, the ios-simulator look-ahead) stays in the triple_tok_* helpers,
+ * which set the os; the obj and the render spelling come from this table. */
+struct OsSpec {
+ KitOSKind os;
+ const char* triple_render;
+ KitObjFmt obj;
+};
+
+static const struct OsSpec os_specs[] = {
+ {KIT_OS_LINUX, "linux", KIT_OBJ_ELF},
+ {KIT_OS_MACOS, "apple-darwin", KIT_OBJ_MACHO},
+ {KIT_OS_IOS, "apple-ios", KIT_OBJ_MACHO},
+ {KIT_OS_IOS_SIMULATOR, "apple-ios-simulator", KIT_OBJ_MACHO},
+ {KIT_OS_WINDOWS, "windows", KIT_OBJ_COFF},
+ {KIT_OS_FREEBSD, "freebsd", KIT_OBJ_ELF},
+ {KIT_OS_ANDROID, NULL, KIT_OBJ_ELF},
+ {KIT_OS_WASI, "wasi", KIT_OBJ_WASM},
+ /* FREESTANDING carries no fixed object format: WASM arch -> WASM, else ELF.
+ * The WASM entry here is a sentinel resolved by os_objfmt(). The renderer
+ * emits the historical "elf" spelling for this OS. */
+ {KIT_OS_FREESTANDING, "elf", KIT_OBJ_WASM},
+};
+
+static const size_t os_specs_count = sizeof os_specs / sizeof os_specs[0];
+
+static const struct OsSpec* os_spec_for(KitOSKind os) {
+ size_t i;
+ for (i = 0; i < os_specs_count; ++i) {
+ if (os_specs[i].os == os) return &os_specs[i];
+ }
+ return NULL;
+}
+
+/* Object format an OS implies for a given arch. Pure table lookup, except
+ * FREESTANDING which derives from the arch (wasm -> WASM, otherwise ELF). */
+static KitObjFmt os_objfmt(KitOSKind os, KitArchKind arch) {
+ const struct OsSpec* s = os_spec_for(os);
+ if (os == KIT_OS_FREESTANDING)
+ return (arch == KIT_ARCH_WASM) ? KIT_OBJ_WASM : KIT_OBJ_ELF;
+ return s ? s->obj : KIT_OBJ_ELF;
+}
+
KitPic kit_target_default_pic(KitObjFmt obj, KitOSKind os) {
/* WASM has no PIC/PIE concept; freestanding targets have no dynamic
* loader to apply load-time relocations. Everything else is hosted and
@@ -192,13 +299,16 @@ bool kit_target_from_triple(const char* triple, KitTargetSpec* out) {
if (!arch_from_tok(parts[0], plen[0], &t.arch, &t.ptr_size)) return false;
+ /* Match an OS token, set t.os (+ any version suffix), then derive t.obj from
+ * the OS table via os_objfmt() rather than open-coding the format per branch.
+ * The token matchers (android API level, iOS version, ios-simulator
+ * look-ahead) stay as triple_tok_* helpers. */
os_set = 0;
for (i = 1; i < np; ++i) {
{
uint8_t api = 0;
if (triple_tok_is_android(parts[i], plen[i], &api)) {
t.os = KIT_OS_ANDROID;
- t.obj = KIT_OBJ_ELF;
t.os_version_major = api;
os_set = 1;
break;
@@ -215,7 +325,6 @@ bool kit_target_from_triple(const char* triple, KitTargetSpec* out) {
}
}
t.os = android ? KIT_OS_ANDROID : KIT_OS_LINUX;
- t.obj = KIT_OBJ_ELF;
t.os_version_major = android ? api : 0;
os_set = 1;
break;
@@ -223,13 +332,11 @@ bool kit_target_from_triple(const char* triple, KitTargetSpec* out) {
if (triple_tok_eq(parts[i], plen[i], "darwin") ||
triple_tok_eq(parts[i], plen[i], "macos")) {
t.os = KIT_OS_MACOS;
- t.obj = KIT_OBJ_MACHO;
os_set = 1;
break;
}
if (triple_tok_is_ios_sim(parts[i], plen[i])) {
t.os = KIT_OS_IOS_SIMULATOR;
- t.obj = KIT_OBJ_MACHO;
os_set = 1;
break;
}
@@ -243,14 +350,12 @@ bool kit_target_from_triple(const char* triple, KitTargetSpec* out) {
}
}
t.os = sim ? KIT_OS_IOS_SIMULATOR : KIT_OS_IOS;
- t.obj = KIT_OBJ_MACHO;
os_set = 1;
break;
}
if (triple_tok_eq(parts[i], plen[i], "windows") ||
triple_tok_eq(parts[i], plen[i], "win32")) {
t.os = KIT_OS_WINDOWS;
- t.obj = KIT_OBJ_COFF;
os_set = 1;
break;
}
@@ -262,21 +367,18 @@ bool kit_target_from_triple(const char* triple, KitTargetSpec* out) {
for (j = 0; j < rem && ver[j] >= '0' && ver[j] <= '9'; ++j)
v = v * 10 + (unsigned)(ver[j] - '0');
t.os = KIT_OS_FREEBSD;
- t.obj = KIT_OBJ_ELF;
t.os_version_major = (uint8_t)(v > 255 ? 0 : v);
os_set = 1;
break;
}
if (triple_tok_eq(parts[i], plen[i], "wasi")) {
t.os = KIT_OS_WASI;
- t.obj = KIT_OBJ_WASM;
os_set = 1;
break;
}
if (triple_tok_eq(parts[i], plen[i], "none") ||
triple_tok_eq(parts[i], plen[i], "freestanding")) {
t.os = KIT_OS_FREESTANDING;
- t.obj = (t.arch == KIT_ARCH_WASM) ? KIT_OBJ_WASM : KIT_OBJ_ELF;
os_set = 1;
break;
}
@@ -290,8 +392,8 @@ bool kit_target_from_triple(const char* triple, KitTargetSpec* out) {
* table, so erroring here would regress valid bare-metal triples. Left as a
* silent freestanding default by design; see findings note for B15. */
t.os = KIT_OS_FREESTANDING;
- t.obj = (t.arch == KIT_ARCH_WASM) ? KIT_OBJ_WASM : KIT_OBJ_ELF;
}
+ t.obj = os_objfmt(t.os, t.arch);
t.ptr_align = t.ptr_size;
t.big_endian = 0;
@@ -305,71 +407,29 @@ bool kit_target_from_triple(const char* triple, KitTargetSpec* out) {
bool kit_target_to_triple(KitTargetSpec target, char* buf, size_t cap) {
const char* arch;
const char* os;
+ const struct OsSpec* os_spec;
int n;
if (!buf || cap == 0) return false;
- switch (target.arch) {
- case KIT_ARCH_X86_64:
- arch = "x86_64";
- break;
- case KIT_ARCH_X86_32:
- arch = "i386";
- break;
- case KIT_ARCH_ARM_64:
- arch = "aarch64";
- break;
- case KIT_ARCH_ARM_32:
- arch = "arm";
- break;
- case KIT_ARCH_RV64:
- arch = "riscv64";
- break;
- case KIT_ARCH_RV32:
- arch = "riscv32";
- break;
- case KIT_ARCH_WASM:
- arch = target.ptr_size == 8 ? "wasm64" : "wasm32";
- break;
- default:
- arch = "unknown";
- break;
- }
+ arch = arch_triple_name(target.arch, target.ptr_size);
- switch (target.os) {
- case KIT_OS_LINUX:
- os = "linux";
- break;
- case KIT_OS_MACOS:
- os = "apple-darwin";
- break;
- case KIT_OS_IOS:
- os = "apple-ios";
- break;
- case KIT_OS_IOS_SIMULATOR:
- os = "apple-ios-simulator";
- break;
- case KIT_OS_WINDOWS:
- os = "windows";
- break;
- case KIT_OS_FREEBSD:
- os = "freebsd";
- break;
- case KIT_OS_ANDROID:
- if (target.os_version_major)
- n = snprintf(buf, cap, "%s-linux-android%u", arch,
- (unsigned)target.os_version_major);
- else
- n = snprintf(buf, cap, "%s-linux-android", arch);
- return !(n < 0 || (size_t)n >= cap);
- case KIT_OS_WASI:
- os = "wasi";
- break;
- case KIT_OS_FREESTANDING:
- default:
- os = "elf";
- break;
+ /* Android renders the "<arch>-linux-android[<api>]" form with an optional
+ * API-level suffix; it is the one OS whose spelling is not a plain table
+ * literal, so it stays special-cased here. */
+ if (target.os == KIT_OS_ANDROID) {
+ if (target.os_version_major)
+ n = snprintf(buf, cap, "%s-linux-android%u", arch,
+ (unsigned)target.os_version_major);
+ else
+ n = snprintf(buf, cap, "%s-linux-android", arch);
+ return !(n < 0 || (size_t)n >= cap);
}
+ os_spec = os_spec_for(target.os);
+ /* Unknown OS or one without a render spelling falls back to the historical
+ * freestanding "elf" component. */
+ os = (os_spec && os_spec->triple_render) ? os_spec->triple_render : "elf";
+
n = snprintf(buf, cap, "%s-%s", arch, os);
return !(n < 0 || (size_t)n >= cap);
}
diff --git a/src/arch/registry.c b/src/arch/registry.c
@@ -17,6 +17,7 @@
#include "arch/arch.h"
#include "kit/config.h"
+#include "kit/target.h"
#if KIT_ARCH_AA64_ENABLED
extern const ArchImpl arch_impl_aa64;
@@ -77,23 +78,9 @@ static u32 arch_impls_count(void) {
}
const char* arch_kind_name(KitArchKind arch) {
- switch (arch) {
- case KIT_ARCH_X86_32:
- return "x86_32";
- case KIT_ARCH_X86_64:
- return "x86_64";
- case KIT_ARCH_ARM_32:
- return "arm32";
- case KIT_ARCH_ARM_64:
- return "aarch64";
- case KIT_ARCH_RV32:
- return "riscv32";
- case KIT_ARCH_RV64:
- return "riscv64";
- case KIT_ARCH_WASM:
- return "wasm";
- }
- return "unknown";
+ /* The arch diagnostic-name table lives with the triple parser/renderer in
+ * src/api/target.c so all arch-name spellings have one source. */
+ return kit_target_arch_diag_name(arch);
}
const ArchImpl* arch_lookup(KitArchKind arch) {
diff --git a/test/api/target_test.c b/test/api/target_test.c
@@ -100,6 +100,74 @@ static void check_target_triple_parse(void) {
EXPECT(strcmp(buf, "aarch64-linux-android24") == 0,
"Android target renders canonical API triple");
}
+
+ /* arm/armv7 both map to KIT_ARCH_ARM_32; the canonical triple spelling is
+ * "arm" (distinct from the "arm32" diagnostic name). */
+ EXPECT(kit_arch_from_name("arm", &arch, &ptr_size) &&
+ arch == KIT_ARCH_ARM_32 && ptr_size == 4,
+ "arm arch spelling maps to arm32");
+ EXPECT(kit_arch_from_name("armv7", &arch, &ptr_size) &&
+ arch == KIT_ARCH_ARM_32 && ptr_size == 4,
+ "armv7 arch spelling maps to arm32");
+
+ memset(&t, 0, sizeof t);
+ EXPECT(kit_target_from_triple("armv7-unknown-linux-gnueabihf", &t),
+ "armv7 Linux triple parses");
+ EXPECT(t.arch == KIT_ARCH_ARM_32 && t.os == KIT_OS_LINUX &&
+ t.obj == KIT_OBJ_ELF,
+ "armv7 Linux triple maps to ELF Linux arm32");
+ {
+ char buf[64];
+ EXPECT(kit_target_to_triple(t, buf, sizeof buf), "arm32 target renders");
+ EXPECT(strcmp(buf, "arm-linux") == 0,
+ "arm32 target renders canonical \"arm\" component");
+ }
+
+ /* Plain (non-simulator) iOS round-trips to "aarch64-apple-ios". */
+ memset(&t, 0, sizeof t);
+ EXPECT(kit_target_from_triple("arm64-apple-ios", &t), "arm64 iOS bare parses");
+ EXPECT(t.arch == KIT_ARCH_ARM_64 && t.os == KIT_OS_IOS &&
+ t.obj == KIT_OBJ_MACHO,
+ "arm64 iOS bare maps to Mach-O iOS");
+ {
+ char buf[64];
+ EXPECT(kit_target_to_triple(t, buf, sizeof buf), "iOS target renders");
+ EXPECT(strcmp(buf, "aarch64-apple-ios") == 0,
+ "iOS target renders canonical triple");
+ }
+
+ /* iphonesimulator look-ahead form resolves to the simulator OS + Mach-O. */
+ memset(&t, 0, sizeof t);
+ EXPECT(kit_target_from_triple("arm64-apple-ios-simulator", &t),
+ "arm64 iOS simulator (no version) parses");
+ EXPECT(t.arch == KIT_ARCH_ARM_64 && t.os == KIT_OS_IOS_SIMULATOR &&
+ t.obj == KIT_OBJ_MACHO,
+ "arm64 iOS simulator (no version) maps to Mach-O iOS simulator");
+
+ /* Bare android env component (no preceding linux token) parses to ELF. */
+ memset(&t, 0, sizeof t);
+ EXPECT(kit_target_from_triple("aarch64-android", &t),
+ "aarch64 bare android triple parses");
+ EXPECT(t.arch == KIT_ARCH_ARM_64 && t.os == KIT_OS_ANDROID &&
+ t.obj == KIT_OBJ_ELF && t.os_version_major == 0,
+ "aarch64 bare android maps to ELF Android, no API level");
+ {
+ char buf[64];
+ EXPECT(kit_target_to_triple(t, buf, sizeof buf),
+ "Android (no API) target renders");
+ EXPECT(strcmp(buf, "aarch64-linux-android") == 0,
+ "Android (no API) renders canonical triple without suffix");
+ }
+
+ /* Diagnostic names: distinct from the triple spelling for arm. */
+ EXPECT(strcmp(kit_target_arch_diag_name(KIT_ARCH_ARM_32), "arm32") == 0,
+ "arm32 diag name");
+ EXPECT(strcmp(kit_target_arch_diag_name(KIT_ARCH_ARM_64), "aarch64") == 0,
+ "aarch64 diag name");
+ EXPECT(strcmp(kit_target_arch_diag_name(KIT_ARCH_X86_32), "x86_32") == 0,
+ "x86_32 diag name");
+ EXPECT(strcmp(kit_target_arch_diag_name(KIT_ARCH_WASM), "wasm") == 0,
+ "wasm diag name");
}
static void check_x64_defaults_and_isa(void) {