kit

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

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:
Minclude/kit/target.h | 8++++++++
Msrc/api/target.c | 274++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Msrc/arch/registry.c | 21++++-----------------
Mtest/api/target_test.c | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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) {