kit

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

commit ad5fdfd09243c2d1bcbb95f3b1c5d99c1b7e5b3c
parent 645999b8a7c37d67a91aece265d39b44ac68d229
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 18 Jun 2026 16:27:44 -0700

Clear release correctness blockers

Diffstat:
MREADME.md | 9+++++----
Mdoc/CODEGEN.md | 4++--
Mdoc/DBG.md | 13+++++++++++++
Mdoc/DRIVER.md | 31++++++++++++++++++++-----------
Mdoc/IR.md | 4++--
Mdoc/LINK.md | 5+++++
Mdoc/OBJ.md | 7+++++++
Mdoc/OPT.md | 5++---
Mdoc/WASM.md | 30++++++++++++++++++++++++++++--
Mdoc/plan/RELEASE.md | 40++++++++++++++++++++--------------------
Mdoc/plan/TODO.md | 87++++++++++++-------------------------------------------------------------------
Mdriver/cmd/build.c | 38+++++++++++++++++++++++++-------------
Mdriver/cmd/cc.c | 17++++++++++++-----
Mdriver/cmd/dbg.c | 26++++++++++++++++----------
Mdriver/cmd/emu.c | 5+++--
Mdriver/cmd/ld.c | 15+++++++++------
Mdriver/cmd/run.c | 10++++++----
Mdriver/driver.h | 25++++++++++++++-----------
Mdriver/env/posix.c | 2+-
Mdriver/env/windows.c | 2+-
Mdriver/lib/install_links.c | 1+
Mdriver/main.c | 9++++++---
Minclude/kit/core.h | 2+-
Minclude/kit/target.h | 8+++++---
Mmk/flags.mk | 7++-----
Msrc/api/core.c | 5+++++
Msrc/arch/wasm/arch.c | 2+-
Msrc/cg/cgir.h | 2+-
Msrc/cg/control.c | 1-
Msrc/cg/session.c | 1+
Mtest/api/cg_fp_cmp_test.c | 5+++++
Mtest/api/target_test.c | 21+++++++++++++++++++++
Mtest/arch/aa64_inline_test.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/arch/inline_public_test.h | 15++++++++++++---
Mtest/arch/x64_inline_test.c | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/dbg/cases/repl-help/expected | 2+-
Mtest/driver/run.sh | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/wasm/run.sh | 64+++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
38 files changed, 583 insertions(+), 199 deletions(-)

diff --git a/README.md b/README.md @@ -12,9 +12,10 @@ It features: - A single-pass C11 parser and code generator - A JIT compiler, linker, and in-process executor - An IR interpreter (`run --no-jit`) -- A user-mode ELF emulator (per-basic-block JIT translation) - A lightweight optimizer (`-O1`) with SSA construction, register allocation, and local cleanup +- `-O2` accepted as a v1 compatibility spelling for `-O1` (not a distinct + optimization level yet) - Cross-compiling backends for aarch64, x86-64, and riscv64, plus WebAssembly and a portable C-source backend - Support for object files and executables (PE/COFF, ELF, Mach-O) @@ -35,7 +36,7 @@ It features: - A single multi-call binary, with an `install` command that drops per-tool symlinks (hard links on Windows) into a directory for drop-in toolchain use - Debug info generation and consumption (DWARF) -- An interactive debugger +- An interactive JIT debugger on the hard-green `test-dbg` host lanes - Header dependency generation - Reproducible builds - Signed, content-addressed code distribution (`.kpkg`) @@ -57,6 +58,6 @@ And kit-specific extensions: Start with [`doc/DESIGN.md`](doc/DESIGN.md) — the entrypoint that covers kit's design principles, layered architecture, and primary data flows, and indexes a design doc for every major subsystem (frontends, codegen, IR, optimizer, arch -backends, object formats, linker, JIT, emulator, debug info, debugger, runtime, -driver, packaging, …). Forward-looking roadmaps live in [`doc/plan/`](doc/plan/). +backends, object formats, linker, JIT, debug info, debugger, runtime, driver, +packaging, …). Forward-looking roadmaps live in [`doc/plan/`](doc/plan/). Code-interface detail lives in the public headers under `include/kit/`. diff --git a/doc/CODEGEN.md b/doc/CODEGEN.md @@ -26,7 +26,7 @@ Codegen has exactly two stable interfaces, stacked: v CgTarget realization |-- NativeDirectTarget -O0 direct emit -> NativeOps + NativeTarget - |-- CgIrRecorder -O1/-O2/interp -> recorded IR -> optimizer -> NativeTarget + |-- CgIrRecorder -O1/interp -> recorded IR -> optimizer -> NativeTarget |-- C-source target / wasm (semantic, source-like output) ``` @@ -190,7 +190,7 @@ then conditionally wraps it: - **`-O0`, native arch:** the backend's `make` returns a `NativeDirectTarget` (see below). No IR is recorded; semantic ops emit machine code immediately. -- **`-O1`/`-O2` or interpreter:** `session.c` wraps the base target with +- **`-O1` (and `-O2`, currently an `-O1` alias) or interpreter:** `session.c` wraps the base target with `opt_cgtarget_new` (`src/opt/opt.c`), which returns a `CgIrRecorder` (`src/cg/ir_recorder.c`). Recording does not emit; at `kit_cg_finish` the optimizer replays optimized IR. The recorder still holds the unwrapped native target so diff --git a/doc/DBG.md b/doc/DBG.md @@ -10,6 +10,19 @@ C11, like all of `src/`); the host primitives and the REPL live in the driver. See [JIT.md](JIT.md) for how the image is produced and [DWARF.md](DWARF.md) for the line/CFI/variable tables the source-level features consume. +## V1 support boundary + +The V1 release claim is limited to host/arch pairs where `make test-dbg` runs +hard-green. Today that scripted lane runs on Darwin and Linux aarch64 hosts +(`arm64` / `aarch64`) and validates breakpoints, instruction and source-line +stepping, backtraces, registers, variables, memory access, interrupt handling, +and teardown behavior. + +Other arch hooks and host adapters may exist in the tree, but they are +implementation work until their debugger lane proves the same behavior. The +source-level features are claimed for `-O0 -g`; optimized-source debugging is +not a V1 release claim. + ## Layering ``` diff --git a/doc/DRIVER.md b/doc/DRIVER.md @@ -2,7 +2,7 @@ The `kit` multitool is the toolchain's only executable: a single binary that dispatches to ~27 named tools (compiler, assembler, linker, archive/object -utilities, byte utilities, JIT runner, debugger, emulator, packager, and an +utilities, byte utilities, JIT runner, debugger, packager, and an `install` command that lays down the per-tool links). It is also the first and canonical *consumer* of libkit — it depends only on the public API under `include/kit/`, never on `src/`. Everything that the OS provides (heap, file @@ -73,7 +73,7 @@ prototypes in `driver/driver.h`, the `cmd/<tool>.c`, and a Makefile stanza. Exit-code convention across all tools: `0` success, `1` tool-reported error, `2` bad usage. Help requests are detected by `driver_argv_wants_help`, which stops scanning at a literal `--` so that a `--help` meant for a JITed program -(`run`, `dbg`) or an emulated guest (`emu`) is not hijacked by the driver. +(`run`, `dbg`) is not hijacked by the driver. ## The tools (cmd/) @@ -83,15 +83,15 @@ tool reaches into compiler internals. | Tool | Role | |------|------| -| `cc` | C compiler driver: compile, optionally link; preprocess (`-E`), dep-emit (`-M*`), `-shared`. GCC flag subset. Resolves `-l`/`-L` to concrete archive paths. | +| `cc` | C compiler driver: compile, optionally link; preprocess (`-E`), dep-emit (`-M*`), ELF `-shared`. GCC flag subset. Resolves `-l`/`-L` to concrete archive paths. | | `check` | Run the C frontend checks with no code emission. | -| `build-exe` | Kit-native build verb: compile a polyglot source set (C / asm / toy / wasm, per file) in memory and link it — with any `.o`/`.a`/`.so` inputs — into an executable. No intermediate files. | -| `build-lib` | Compile a polyglot source set in memory into a static `.a` (default) or, with `-dynamic`, a shared library. | +| `build-exe` | Kit-native build verb: compile a polyglot source set (C / asm / wasm, per file) in memory and link it — with any `.o`/`.a`/`.so` inputs — into an executable. No intermediate files. | +| `build-lib` | Compile a polyglot source set in memory into a static `.a` (default) or, with `-dynamic`, an ELF shared library. | | `build-obj` | Compile sources to one object (or `--emit=asm\|c\|ir`, or `-fsyntax-only` check); multiple sources combine into one relocatable object (`ld -r`). The kit-native replacement for the retired `compile` tool. | | `install` | Lay down per-tool links (symlinks; hard links on Windows) in a target dir so the toolchain works under bare names (`cc`, `ld`, `nm`, …). Default set is the toolchain + standard-named byte utils; `--all` / explicit names override. | | `cpp` | Standalone preprocessor (alias for `cc -E` without link scaffolding). | | `as` | Assemble one GAS-subset text source to a relocatable object. | -| `ld` | Link objects/archives into an executable, shared library, or relocatable object; parses `-T` scripts into structured form. | +| `ld` | Link objects/archives into an executable, ELF shared library, or relocatable object; parses `-T` scripts into structured form. | | `ar` / `ranlib` | Create/modify/list/extract `ar` archives; refresh the symbol index. | | `strip` / `objcopy` | Drop debug/symbols; rename/remove sections, reformat. | | `objdump` / `nm` / `size` | Inspect sections, symbols, disassembly, relocations, sizes. | @@ -105,13 +105,12 @@ tool reaches into compiler internals. | `disas` | Disassemble a raw, headerless byte buffer (file/stdin/inline `-x` hex) for a `-target` arch. | | `mc` | Assemble one instruction and show its encoding (llvm-mc style); lists any relocations. | | `run` | JIT-compile inputs and call the entry symbol in-process. | -| `dbg` | Interactive JIT debugger (REPL over a `KitDebugSession`). | -| `emu` | Run a guest user-mode ELF (aarch64/riscv64) via per-block JIT translation. | +| `dbg` | Interactive JIT debugger on hard-green `test-dbg` host lanes (REPL over a `KitDebugSession`). | | `cas` / `pkg` | Content-addressed store and signed `.kpkg` distribution. | -`run`, `dbg`, and `emu` share the `--`-terminated argv convention: flags before -`--` configure the tool, tokens after `--` become the JITed program's / guest's -argv. `cc` and `run` overlap heavily on input shape and the preprocessor flag +`run` and `dbg` share the `--`-terminated argv convention: flags before +`--` configure the tool, tokens after `--` become the JITed program's argv. +`cc` and `run` overlap heavily on input shape and the preprocessor flag family — that overlap is exactly what `driver/lib/` factors out. `cc` vs `build-*`: `cc` is the GCC-compatible C driver — a drop-in `cc`/`clang` @@ -129,6 +128,16 @@ flags) that apply to the whole build, and a small scopable set (`-I`/`-isystem`/ and the same language-neutral per-source compile step (`driver/lib/compile_engine.c`). +For v1, `-O0` and `-O1` are the real optimization levels. `-O2` is accepted by +the driver and public CG API as a compatibility spelling, but it aliases `-O1` +and does not enable a separate optimizer schedule. This is intentional release +policy and may change after v1. + +The emulator command and Toy frontend are internal for v1: they may remain +compiled for development and regression tests, but they are not listed in +top-level help, installed by `install --all`, or presented as public release +surface. + `run` doubles as a `#!` script interpreter so a C file can be made executable and run directly. The kernel's shebang mechanism appends the script path *and* the user's arguments after the interpreter's flags, with no way to inject a diff --git a/doc/IR.md b/doc/IR.md @@ -74,14 +74,14 @@ frontend -> CgTarget (semantic codegen interface) |-> direct native target (O0 emit) |-> direct C-source target (--emit=c) - \-> IR recorder -> CgIrModule (O1/O2, interpreter) + \-> IR recorder -> CgIrModule (O1, O2-as-O1, interpreter) |-> opt: derive Func (CFG/SSA/MIR) -> native emit \-> opt: derive Func (reduced) -> interpreter ``` `KitCg` lowers the frontend's stack/lvalue source operations into flat `CgTarget` calls. At O0 those calls hit a direct target and become code right -away. At O1/O2 and under the interpreter they hit the recorder and become a +away. At O1, O2-as-O1, and under the interpreter they hit the recorder and become a `CgIrModule`. The recorder is created by the optimizer (src/opt/opt.c calls `cg_ir_recorder_new`); it notifies the optimizer per completed function and at finalize through callbacks so cross-function work (inlining, reachability, diff --git a/doc/LINK.md b/doc/LINK.md @@ -24,6 +24,11 @@ sysroots) lives entirely in the driver — the library boundary is byte-buffer-shaped. Every bytes input is read through `Compiler.env-> file_io` by the driver before it reaches the linker. +For v1, shared-library **creation** is supported only for ELF targets. Mach-O +and COFF/PE image and DSO readers still support executable links against +existing dylibs/DLL import surfaces, but the driver rejects non-ELF +`-shared`/`-dynamic` output requests. + The two central abstractions: - **`Linker`** — the mutable accumulator. Holds the registered inputs diff --git a/doc/OBJ.md b/doc/OBJ.md @@ -312,6 +312,9 @@ linker's `-l` path: `read_macho_dso` (MH_DYLIB exports) and `tbd_read.c` (Apple model, the `__DWARF` segment section-name spellings — are concentrated in `obj_secnames.c`/`obj_tls.c` and the writer, not the backends. +V1 reads Mach-O dylib metadata for executable links, but does not create Mach-O +shared libraries; driver `-shared`/`-dynamic` output is ELF-only. + Mach-O objects deliberately carry **no `__eh_frame`** (the `emits_eh_frame` target gate in `src/api/core.c` excludes `KIT_OBJ_MACHO`). arm64 Mach-O has no pcrel-32 data relocation for a DWARF FDE pc-begin, and ld64's legacy @@ -344,6 +347,10 @@ string, and `Characteristics`→`SecKind` helpers shared by the `.obj`, DSO, and image readers. `read_dso.c` walks raw PE DLL export directories (and forwarder ENT entries, surfaced as defined symbols so the OS loader chases the chain at runtime). + +V1 reads PE/COFF DLL and import metadata for executable links, but does not +create PE DLLs; driver `-shared`/`-dynamic` output is ELF-only. + `archive.c` implements the registry's archive-ingestion hooks: it classifies import-library members, routing Microsoft short-import records (`Sig1=0, Sig2=0xFFFF`) through `read_coff` to synthesize the imported symbols diff --git a/doc/OPT.md b/doc/OPT.md @@ -296,9 +296,8 @@ implemented, but it is not on the shipped code path: **`opt_cleanup` has no caller.** The finalize sweep (`opt_whole_module_finalize`) always runs the O1 native pipeline (`opt_o1_native_prepare`/`_finish`) regardless of the requested `opt_level`, so no compilation ever selects the SSA schedule and every -`opt_level >= 1` request runs the O1 native path. (`-O2` therefore produces the -same output as `-O1` today; the level is recorded on `OptImpl` but only gates -whole-program mode, which `-O1` already enables.) +`opt_level >= 1` request runs the O1 native path. Public `opt_level == 2` is +normalized to `1`, so `-O2` produces the same output as `-O1` today. The rationale for keeping it parked is isolation. Keeping the O2 schedule defined and its passes maintained means the SSA representation and its diff --git a/doc/WASM.md b/doc/WASM.md @@ -20,7 +20,7 @@ module model serves both: and JITs/links like any other frontend. The module's runtime state — memory, globals, tables, imports — is reified as an explicit instance struct. -- **Wasm as target.** C or toy is compiled *to* a Wasm module via +- **Wasm as target.** C is compiled *to* a Wasm module via `KIT_ARCH_WASM` / `KIT_OBJ_WASM`. The backend in `src/arch/wasm` is a codegen target that records into IR and replays into a private Wasm emitter, producing a tool-conventions-shaped `.wasm` file. @@ -28,6 +28,28 @@ module model serves both: Neither direction builds a private reader or writer: both go through the one `src/wasm` layer. That non-duplication is the central design constraint. +## V1 support boundary + +The shipped target boundary is intentionally narrow. `wasm32` is the only Wasm +output target for v1. `wasm64` remains a reserved triple/ISA spelling so the +parser can name it, but target construction rejects it with an explicit +unsupported diagnostic. + +Wasm/WAT input is supported as a source language and `kit run` can execute those +modules with an explicit instance and deny-by-default host imports. The WASI +surface is partial WASI Preview1: the runner binds only the configured subset of +imports and host resources selected by `--wasm-imports=wasi` and the +`--wasm-*` resource flags. Unsupported WASI calls are not treated as a complete +WASI implementation. + +For C-to-Wasm output, same-invocation multi-source `wasm32` is supported through +the source-batch path (`build-obj -target wasm32-none ...` or +`build-exe -target wasm32-none ...`). Those sources are merged before the final +module is emitted. Separate-object/static Wasm linking is not supported in v1: +`build-exe -target wasm32-none` rejects `.o`, `.a`, `-l`, DSO, and framework +inputs, and `emit_wasm` does not create relocatable Wasm objects with +`linking`/`reloc.*` metadata. + ``` src/wasm/ (format mechanics + WasmModule model) decode.c encode.c wat.c validate.c insn.c module.c @@ -160,7 +182,9 @@ function declarations, active tables/elements for `call_indirect`, start functions, growable single-memory state, bulk-memory ops (`memory.copy`/`fill`/`init`, `data.drop`, the table equivalents) with bounds-check prologues, and non-trapping float-to-int conversion. Bulk ops and -non-trapping conversions are gated behind their `WasmFeatureSet` bits. +non-trapping conversions are gated behind their `WasmFeatureSet` bits. This is +frontend/runtime coverage, not a claim of complete WASI or every post-MVP Wasm +proposal. **Host imports.** When a frontend-lowered module declares imports, an embedder binds them by name through the public API in `include/kit/wasm.h` @@ -252,6 +276,8 @@ The object backend is intentionally small and one-directional today. `emit.c`'s is attached, writes a bare magic+version header). It does **not** synthesize a relocatable object: `emit_wasm` produces a single-TU *final module* with no `linking`/`reloc.*` custom sections and no separate object-metadata structure. +Multi-source output is handled before this point by the source-batch build path, +not by linking independently emitted Wasm objects. `read.c`'s `read_wasm` is the reverse glue used by the linker/objdump path ([LINK.md](LINK.md)): it mirrors each binary section into a format-neutral diff --git a/doc/plan/RELEASE.md b/doc/plan/RELEASE.md @@ -82,7 +82,7 @@ directory, then inspected from the saved logs. - [ ] Deferred portability lanes are listed with owner/status: `make test-cross DEPTH=full`, `make test-selfhost DEPTH=smoke`, and `make test-selfhost DEPTH=full`. -- [ ] Release-gate configuration has `emu` and user-facing Toy hidden/disabled, +- [x] Release-gate configuration has `emu` and user-facing Toy hidden/disabled, while internal Toy tests remain available if still useful. - [ ] No public v1 doc or help text claims a feature outside this checklist. @@ -91,23 +91,23 @@ directory, then inspected from the saved logs. These block v1 until fixed, removed from the public claim, or explicitly converted into a non-public unsupported feature. -- [ ] Fix the latent `kit run` uninitialized-stack read. -- [ ] Fix the c_target Wasm `memory.grow` backing-store bug. -- [ ] Fix aa64 optimized inline-asm staging panics or remove the affected +- [x] Fix the latent `kit run` uninitialized-stack read. +- [x] Fix the c_target Wasm `memory.grow` backing-store bug. +- [x] Fix aa64 optimized inline-asm staging panics or remove the affected optimized inline-asm claim. -- [ ] Fix x64 optimized inline-asm staging panics or remove the affected +- [x] Fix x64 optimized inline-asm staging panics or remove the affected optimized inline-asm claim. -- [ ] Make `-O2` behavior explicit everywhere: alias to `-O1`, not a separate +- [x] Make `-O2` behavior explicit everywhere: alias to `-O1`, not a separate optimizer level. -- [ ] Hide or remove user-facing `emu` exposure from README, driver help, +- [x] Hide or remove user-facing `emu` exposure from README, driver help, install defaults, and release docs. -- [ ] Hide Toy from user-facing docs, help, examples, and install defaults. -- [ ] Reject or hide non-ELF shared-library creation paths for `cc`, `ld`, and +- [x] Hide Toy from user-facing docs, help, examples, and install defaults. +- [x] Reject or hide non-ELF shared-library creation paths for `cc`, `ld`, and `build-lib`; keep ELF DSO creation enabled and tested. -- [ ] Update docs/help/tests for the Wasm boundary: same-invocation multi-source +- [x] Update docs/help/tests for the Wasm boundary: same-invocation multi-source `wasm32` is supported; separate-object/static Wasm linking, wasm64, and complete WASI are not. -- [ ] Keep debugger support claims hard-green only. Any host/arch pair without +- [x] Keep debugger support claims hard-green only. Any host/arch pair without proven breakpoints, stepping, backtrace, registers, variables, memory access, interrupt, and teardown is removed from the v1 claim. @@ -117,18 +117,18 @@ converted into a non-public unsupported feature. - [ ] README, `kit --help`, per-tool help, and install output match the V1 public surface exactly. -- [ ] [../DRIVER.md](../DRIVER.md) documents `-O2` as a temporary `-O1` alias, +- [x] [../DRIVER.md](../DRIVER.md) documents `-O2` as a temporary `-O1` alias, ELF-only shared-library creation, and hidden `emu`/Toy policy. -- [ ] [../LINK.md](../LINK.md) and [../OBJ.md](../OBJ.md) distinguish ELF DSO +- [x] [../LINK.md](../LINK.md) and [../OBJ.md](../OBJ.md) distinguish ELF DSO creation from executable links against DSOs on all object formats. -- [ ] [../WASM.md](../WASM.md) states the `wasm32`, WAT/Wasm input, runtime, +- [x] [../WASM.md](../WASM.md) states the `wasm32`, WAT/Wasm input, runtime, partial-WASI, and same-invocation multi-source boundary. - [ ] [../BUILD_COORDINATOR.md](../BUILD_COORDINATOR.md), [../DISTRIBUTE.md](../DISTRIBUTE.md), and release packaging docs cover `build`, `cas`, `pkg`, `install`, and `update`. - [ ] [../KERNEL.md](../KERNEL.md) matches the shipped `image` and `cpio` tool surface. -- [ ] [../DBG.md](../DBG.md) contains only debugger platforms that are +- [x] [../DBG.md](../DBG.md) contains only debugger platforms that are hard-green. ### Toolchain and linker @@ -138,20 +138,20 @@ converted into a non-public unsupported feature. - [ ] ELF DSO creation passes targeted tests for supported ELF targets. - [ ] ELF executable links against DSOs pass targeted tests for supported ELF targets. -- [ ] Non-ELF shared-library creation rejects clearly in `cc`, `ld`, and +- [x] Non-ELF shared-library creation rejects clearly in `cc`, `ld`, and `build-lib`. -- [ ] `-O2` alias behavior is covered by driver/help tests and optimizer tests. +- [x] `-O2` alias behavior is covered by driver/help tests and optimizer tests. - [ ] aa64, x64, rv64, rv32, and arm32 compile/link claims are covered by the hard release gates or explicitly scoped as freestanding smoke lanes. ### WebAssembly and WASI -- [ ] Targeted `wasm32` single-source C output tests pass. -- [ ] Targeted same-invocation multi-source `wasm32` tests pass. +- [x] Targeted `wasm32` single-source C output tests pass. +- [x] Targeted same-invocation multi-source `wasm32` tests pass. - [ ] Wasm/WAT input and `kit run` execution tests pass. - [ ] WASI Preview1 supported imports are listed and tested; unsupported calls fail with feature-naming diagnostics. -- [ ] Separate-object/static Wasm linking and wasm64 attempts reject clearly. +- [x] Separate-object/static Wasm linking and wasm64 attempts reject clearly. ### Utility and distribution tools diff --git a/doc/plan/TODO.md b/doc/plan/TODO.md @@ -9,37 +9,6 @@ Add new deferred fixes below as they are discovered. ## Known bugs / correctness gaps -- **Latent uninitialized-stack-variable read in the `kit run` in-process compile+link - pipeline (layout-sensitive heisenbug). MITIGATED by `-ftrivial-auto-var-init=zero`; - not yet root-caused.** `kit run` on an FP program flakily dies with `link: undefined - reference to '__muldf3'` (or `__divdf3` / `__floatsidf` / `__fixdfdi` — whichever - soft-float compiler-rt helper the program needs). **Pre-existing and DORMANT at HEAD** - (0 failures in 240+ runs), but woken by memory-layout shifts: e.g. the D.3+D.4 - obj-format dedups trip it (~10–100% per build, varying run-to-run) when built WITHOUT - the hardening flag, while 4 dummy padding fields in the same struct do not — a latent - landmine, not caused by any one refactor. **`-ftrivial-auto-var-init=zero` (mk/flags.mk - `AUTO_INIT_CFLAGS`) fully suppresses it:** a clean build with D.3+D.4 + the flag shows - 0/120 genuine link-error flakes and `test-toy` 1394 pass / 0 fail across repeated - runs, so the read is from an uninitialized *stack* auto. The flag is the standard - zero-init hardening and is now on; this entry stays open because the flag MASKS the UB - rather than fixing the specific read. (Note: an earlier "8/80 with the flag" reading - was a probe bug — case `140_fp_callee_save_bottom_frame` has `.expected`=964, which - wraps to exit code 196 under an 8-bit exit-code compare; it is not a real failure.) - Characterization (all verified): - - Only manifests in the multi-source in-process pipeline. Compiling `rt/lib/fp/fp.c` - (which defines the missing helper) *alone* is 100% deterministic (50/50 clean); - the flake needs many sources compiled+linked in one process. - - **ASAN + UBSan never fire** → it is an uninitialized-memory READ (those sanitizers - don't detect uninit reads). It is a STACK auto: `-ftrivial-auto-var-init=zero` - (which only touches automatic storage) fully suppresses it. `segvec` zeroes its - slots and `obj_symbol_make` is clean, so the ObjSym/LinkSymbol tables are NOT the - source — look at per-source-compile locals in the driver pipeline. - - Real-fix path: the definitive tool is **MemorySanitizer**, unsupported on - macOS/arm64. On the arm64 Linux container (scripts/linux_bootstrap.sh), build kit - under `-fsanitize=memory`, TEMPORARILY drop `AUTO_INIT_CFLAGS`, and run a flaky FP - repro (`kit run -O0 test/toy/cases/54_scalar_intrinsics.toy` in a loop) to get the - exact file:line, then initialize that local. The hardening flag can stay regardless. - - **`-ftrivial-auto-var-init=pattern` in `kit cc`.** `=zero` and `=uninitialized` are implemented: the C frontend zero-inits every automatic variable that has no explicit initializer (`KitCodeOptions.trivial_auto_var_init`, threaded through @@ -51,49 +20,16 @@ Add new deferred fixes below as they are discovered. FP bit pattern), i.e. generalizing `zero_init_at` to a fill value. Lower priority: the hardening uses `=zero`. -- **c_target `memory.grow` backing-store bug.** `memory_grow_large/C` fails - (`expected 42 got 139`, SIGSEGV): a `(memory 1 300)` module grows to 300 pages - then stores/loads at ~18.7 MB and segfaults. Pre-existing (confirmed failing - identically at `de9bface`); surfaces in `test-cbackend`'s wasm-front sub-suite. - **Diagnosis (do NOT "add realloc to the lowering"):** the `memory.grow` lowering in - `lang/wasm/cg.c` is backend-shared and correct — the native lanes pass - `memory_grow_large`. It just bumps `memory.pages`; the backing store is allocated - by the *host/instantiation*, and native pre-allocates `max_pages` of linear memory - (so a store at page 299 is in-bounds) while the C-backend host evidently allocates - only the initial `min_pages`. The fix is in the C-backend wasm memory instantiation - (allocate `max_pages`, matching native), not the shared lowering — adding a realloc - there would wrongly perturb the native path. - -- **aa64/x64 inline-asm operand-staging gap (A.3).** A register-constrained asm - operand that regalloc could not keep in a hard register (e.g. an address-taken - / spilled local) is staged to a scratch reg and succeeds on rv64, but **panics** - on aa64/x64 (`"register asm operand not in a register"`). Three gaps in the - optimizer-path `*_asm_block_native`: (a) register-constrained **outputs** in a - non-reg loc have no fallback on aa64 (`src/arch/aa64/native.c:4883`) / x64 - (`:4021`), only rv64 (`src/arch/riscv/native.c:3780`); (b) **FP-constrained - inputs** (`"w"` aa64, `"x"/"v"` x64) in a non-reg loc; (c) x64 **`"q"`** inputs - (input-stage test is raw `body[0]=='r'`, so `q`→INT,allowed=0 skips staging then - panics). rv64's extra staging branch is correct (it has dedicated stage regs - `RV_TMP2/3`+`RV_FTMP0/1`). Fix aa64/x64 to match, with test cases — this changes - which programs compile vs panic, so it must be a reviewed change, NOT folded into - a dedup. Unblocks A.2 (below). **Blocker (register budget):** the gap is not a - localized edit — rv64 has *dedicated* stage regs separate from its mem-scratch, - but aa64 has only `AA_TMP0/1` (x16/x17, int, which double as the `"m"` mem-scratch) - and *no* FP scratch, and x64 likewise has only `R10/R11` + `XMM14/15` doubling as - mem-scratch. Matching rv64 means either permanently reserving more scratch regs on - aa64/x64 (a codegen-quality cost on every function) or collision-aware sharing of - the existing scratch between mem-staging and operand-staging — a register-policy - decision, hence "reviewed change." - ## Deferred dedups & abstraction cleanups ("use the shared seam") - **A.2 — optimizer-path inline-asm dedup.** The three `aa_/x64_/rv_asm_block_native` share an out/in/bind/run/store skeleton but diverge by *policy* (staging coverage + structurally different scratch-register models: aa64/x64 reuse 2 mem-scratch regs via a shared `ntmp`; rv64 uses 4 dedicated stage regs). A hooks dedup would relocate, not - unify, behavior and add miscompile risk in a hot path. Do **A.3** first (make the three - staging policies identical), then a shared `native_asm_bind_optimizer_operands` becomes - safe. (The -O0 direct path is already shared via `native_asm_bind_direct_operands`.) + unify, behavior and add miscompile risk in a hot path. Now that the aa64/x64 + staging gaps are closed, a shared `native_asm_bind_optimizer_operands` can be + considered. (The -O0 direct path is already shared via + `native_asm_bind_direct_operands`.) - **E.3 — OS-neutral env code duplicated** between `driver/env/posix.c` and `windows.c` (stdio writer + thunks, `env→{context,jit_host,dbg_host}` trio, dir-handle structs + read/close, `read_stdin` grow/shrink). Move to `driver/env/common.c`. **Blocked on a @@ -194,12 +130,15 @@ doc at `HEAD~`). - **Object backend** (largest gap): `src/obj/wasm` lacks relocatable-object + linker-metadata support at ELF/Mach-O/COFF parity. -- **Static linker**: no Wasm linker — `kit_link_exe` always builds a native - `Linker`; need multi-TU Wasm merge + relocation apply. -- **Feature gaps**: cross-TU references, atomics, wrapper ABI; frontend lowering - beyond the staged MVP; validator diagnostics for unsupported proposals. -- **wasm64 + WASI**: recognized-but-unsupported until the wasm32 object/link path - exists. **Cleanup**: move the shared Wasm core `lang/wasm/` → `src/wasm/`. +- **Static linker**: no separate-object/static Wasm linker — `kit_link_exe` + always builds a native `Linker`. Same-invocation source batches are merged + before final module emission; independently emitted Wasm objects still need + linker metadata + relocation apply. +- **Feature gaps**: atomics, wrapper ABI; frontend lowering beyond the staged + MVP; validator diagnostics for unsupported proposals. +- **wasm64 + WASI**: wasm64 is a reserved spelling rejected by target + construction, and WASI remains partial until the wasm32 object/link path + grows. **Cleanup**: move the shared Wasm core `lang/wasm/` → `src/wasm/`. ### Windows x64 self-host + bootstrap (was `windows.md`) diff --git a/driver/cmd/build.c b/driver/cmd/build.c @@ -18,7 +18,7 @@ /* `kit build-exe` / `build-lib` / `build-obj` — the kit-native build verbs. * - * Each command is polyglot (C / asm / toy / wasm resolved per file), compiles + * Each command is polyglot (C / asm / wasm resolved per file), compiles * entirely in memory, and writes no intermediate files. They share one * parse+run parameterized by output kind: * @@ -165,7 +165,7 @@ static int build_lang_from_name(BuildOptions* o, const char* name, KitLanguage* out) { /* Resolve off the compile-time default frontend set (no compiler exists at * arg-parse time). Value-equivalent to the former explicit map: - * c->C, asm/s->ASM, toy->TOY, wasm/wat->WASM. */ + * c->C, asm/s->ASM, wasm/wat->WASM. */ KitLanguage lang = kit_frontend_registry_language_for_name(o->frontends, name); if (lang == KIT_LANG_UNKNOWN) return 1; *out = lang; @@ -284,7 +284,7 @@ static int build_classify_positional(BuildOptions* o, const char* a) { return 0; } /* Otherwise a source: a recognized source suffix, or any file at all when a - * language is forced in scope (so `-x toy mykernel` compiles an extensionless + * language is forced in scope (so `-x c mykernel` compiles an extensionless * or odd-suffix file). */ if (build_is_source(o, a) || build_scope_forced_lang(o, o->cur_group) >= 0) { BuildSource* s = &o->sources[o->nsources]; @@ -528,7 +528,7 @@ static int build_parse(int argc, char** argv, BuildOptions* o) { if (driver_streq(a, "-O2") || driver_streq(a, "-O") || driver_streq(a, "-O3") || driver_streq(a, "-Os") || driver_streq(a, "-Oz") || driver_streq(a, "-Ofast")) { - o->opt_level = 2; + o->opt_level = 1; continue; } if (driver_streq(a, "-Werror") || driver_strneq(a, "-Werror=", 8)) { @@ -2413,6 +2413,10 @@ static int build_main(int argc, char** argv, int kind, const char* tool, o.shared = (o.kind == BUILD_OUT_LIB && o.dynamic); if (o.shared && !o.pic_explicit) o.target.pic = KIT_PIC_PIC; + if (o.shared && o.target.obj != KIT_OBJ_ELF) { + driver_errf(tool, "-shared output is supported only for ELF targets in v1"); + goto done; + } if (o.shared && o.target.pic == KIT_PIC_NONE) { driver_errf(tool, "-shared requires PIC input; remove -fno-pic/-static"); goto done; @@ -2565,11 +2569,14 @@ void driver_help_build_exe(void) { "DESCRIPTION\n" " Compiles registered source languages (language per file) in memory\n" " and links them with any .o/.a/.so inputs into one executable. No\n" - " intermediate files.\n" + " intermediate files. For -target wasm32-none, inputs must be\n" + " sources supplied in this invocation; .o/.a/-l Wasm linking is not\n" + " a v1 feature.\n" "\n" "OPTIONS (selection)\n" " -o PATH Output (default a.out / a.exe)\n" - " -O0 -O1 -O2 -g Optimization / debug info\n" + " -O0 -O1 -O2 -g Optimization / debug info (-O2 aliases " + "-O1)\n" " -target TRIPLE Cross-compile target\n" " -arch ARCH Darwin-style target architecture\n" " -platform_version P MIN SDK\n" @@ -2590,21 +2597,23 @@ void driver_help_build_lib(void) { driver_printf( "%.*s", KIT_SLICE_ARG(KIT_SLICE_LIT( - "kit build-lib — build a static library (.a)\n" + "kit build-lib — build a static library (.a) or ELF shared library\n" "\n" "USAGE\n" " kit build-lib -o LIB.a [options] sources...\n" + " kit build-lib -dynamic -o LIB.so [options] sources...\n" "\n" "DESCRIPTION\n" " Compiles a polyglot source set in memory and archives the " "objects\n" - " into a static library (.a) with a symbol index. Dynamic/shared\n" - " libraries are not yet supported.\n" + " into a static library (.a), or links an ELF shared library with\n" + " -dynamic/-shared. Non-ELF shared-library output is rejected.\n" "\n" "OPTIONS (selection)\n" " -o PATH Output archive (required)\n" " -fPIC Position-independent code\n" - " -O0 -O1 -O2 -g Optimization / debug info\n" + " -O0 -O1 -O2 -g Optimization / debug info (-O2 aliases " + "-O1)\n" " -flto Link-time optimization for source inputs\n" " -target TRIPLE Cross-compile target\n" " --group [flags] -- sources... Scope compile flags to sources\n" @@ -2624,8 +2633,10 @@ void driver_help_build_obj(void) { "DESCRIPTION\n" " Compiles each source (registered language by suffix or -x) to an\n" " object. Multiple sources with --emit=obj combine into one\n" - " relocatable object (ld -r). The kit-native replacement for the\n" - " retired `compile` tool.\n" + " relocatable object (ld -r), except -target wasm32-none merges the\n" + " same source batch into one final wasm32 module. Separate Wasm\n" + " object/static linking is not a v1 feature. The kit-native\n" + " replacement for the retired `compile` tool.\n" "\n" "OPTIONS\n" " -o PATH Output (default <base>.o; required for a\n" @@ -2633,7 +2644,8 @@ void driver_help_build_obj(void) { " --emit=obj|asm|c|ir Output form (ir requires -O1+)\n" " -S Alias for --emit=asm\n" " -fsyntax-only Check only; write no output\n" - " -O0 -O1 -O2 -g Optimization / debug info\n" + " -O0 -O1 -O2 -g Optimization / debug info (-O2 aliases " + "-O1)\n" " -flto Link-time optimization for multi-source " "obj\n" " -target TRIPLE Cross-compile target\n" diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c @@ -21,7 +21,7 @@ * without -c compiles all C sources, links any .o/.a inputs alongside, and * emits an executable. The flag surface is a GCC subset: * - * -c -E -o -O0/1/2 -g + * -c -E -o -O0/1 (-O2 aliases -O1 for v1) -g * -fsyntax-only * -I -isystem -D -U * -M -MM -MD -MMD -MF -MT -MQ -MP @@ -87,7 +87,7 @@ typedef struct CcOptions { int emit_c_source; /* --emit=c */ int emit_ir; /* --emit=ir */ int emit_asm_source; /* -S */ - int opt_level; /* -O0/-O1/-O2 */ + int opt_level; /* -O0/-O1; -O2 aliases -O1 */ int debug_info; /* -g */ int function_sections; /* -ffunction-sections */ int data_sections; /* -fdata-sections */ @@ -179,7 +179,7 @@ void driver_help_cc(void) { ".o\n" " kit cc -E [options] input.c preprocess to -o\n" " kit cc -fsyntax-only [options] inputs... check only\n" - " kit cc -shared [options] inputs... link a shared " + " kit cc -shared [options] inputs... link an ELF shared " "library\n" " kit cc -M|-MM [options] input.c print header deps; " "no " @@ -198,6 +198,8 @@ void driver_help_cc(void) { "source\n" " --emit=ir -O1 [options] input.c emit semantic IR " "dump\n" + " -O0 | -O1 | -O2 optimization " + "(-O2 aliases -O1 for v1)\n" " -flto link-time " "optimization for all source inputs\n" "\n" @@ -672,13 +674,13 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { continue; } if (driver_streq(a, "-O2")) { - o->opt_level = 2; + o->opt_level = 1; continue; } if (driver_streq(a, "-O") || driver_streq(a, "-O3") || driver_streq(a, "-Os") || driver_streq(a, "-Oz") || driver_streq(a, "-Ofast")) { - o->opt_level = 2; + o->opt_level = 1; continue; } @@ -1299,6 +1301,11 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { "(shared-library LTO output is not exercised)"); return 1; } + if (o->shared && o->target.obj != KIT_OBJ_ELF) { + driver_errf(CC_TOOL, + "-shared output is supported only for ELF targets in v1"); + return 1; + } if (o->shared && o->target.pic == KIT_PIC_NONE) { driver_errf(CC_TOOL, "-shared requires PIC input; remove -fno-pic/-static"); return 1; diff --git a/driver/cmd/dbg.c b/driver/cmd/dbg.c @@ -85,7 +85,7 @@ void driver_help_dbg(void) { "kit dbg — interactive JIT debugger\n" "\n" "USAGE\n" - " kit dbg [options] [input.{c,toy,s} ...] [-- prog-arg ...]\n" + " kit dbg [options] [input.{c,s,wat,wasm} ...] [-- prog-arg ...]\n" "\n" "DESCRIPTION\n" " Mirrors `kit run` for compile flags and argv shape, but " @@ -96,6 +96,8 @@ void driver_help_dbg(void) { " finish, backtrace, registers, locals/args, variable read/write,\n" " and raw memory examine. -g is forced on so source lines and\n" " variable locations are available at runtime.\n" + " V1 support is limited to host/arch lanes where test-dbg runs\n" + " hard-green: Darwin/Linux aarch64 hosts today.\n" "\n" " Anything after `--` is passed to the JITed program as argv.\n" " With no input files, dbg starts an empty JIT session; append " @@ -103,10 +105,11 @@ void driver_help_dbg(void) { " with `jit` or evaluate expressions directly from the REPL.\n" "\n" "COMPILE OPTIONS\n" - " -O0 -O1 -O2 Optimization level (default -O0)\n" + " -O0 -O1 -O2 Optimization level; -O2 aliases -O1 " + "(default -O0)\n" " -g Emit DWARF (forced on)\n" " -e SYMBOL Entry symbol (default `main`)\n" - " -x LANG Default REPL language: c, toy, asm, wasm/wat\n" + " -x LANG Default REPL language: c, asm, wasm/wat\n" " --language LANG Same as -x\n" " -I DIR Add quoted-include search path\n" " -isystem DIR Add system-include search path\n" @@ -151,7 +154,10 @@ void driver_help_dbg(void) { " info reg, info registers dump registers\n" " info locals | info args list locals / args at current PC\n" " info functions [PATTERN] list JIT functions matching PATTERN\n" - " info variables [PATTERN] list JIT globals matching PATTERN\n" + " info variables [PATTERN] list JIT globals matching PATTERN\n"))); + driver_printf( + "%.*s", + KIT_SLICE_ARG(KIT_SLICE_LIT( "\n" "BATCH / SCRIPTING\n" " --script FILE execute debugger commands from FILE " @@ -281,7 +287,7 @@ static int dbg_parse(int argc, char** argv, DbgOpts* o) { continue; } if (driver_streq(a, "-O2")) { - o->opt_level = 2; + o->opt_level = 1; continue; } @@ -1836,7 +1842,7 @@ static void dbg_cmd_jit(DbgState* s, const char* rest) { if (dbg_parse_jit_lang_arg(s, rest, &lang, &input_name, &p) != 0) return; if (*p != '{') { - dbg_errf(s, "usage: jit [c|toy|asm|name.ext] { ... }"); + dbg_errf(s, "usage: jit [c|asm|name.ext] { ... }"); return; } ++p; @@ -1895,7 +1901,7 @@ static void dbg_cmd_edit(DbgState* s, const char* rest) { if (dbg_parse_jit_lang_arg(s, rest, &lang, &input_name, &p) != 0) return; if (*p) { - dbg_errf(s, "usage: edit [c|toy|asm|name.ext]"); + dbg_errf(s, "usage: edit [c|asm|name.ext]"); return; } if (!driver_edit_temp( @@ -1944,7 +1950,7 @@ static void dbg_cmd_language(DbgState* s, const char* rest) { return; } if (n >= sizeof(tmp)) { - dbg_errf(s, "usage: :language c|toy|asm|wat|wasm"); + dbg_errf(s, "usage: :language c|asm|wat|wasm"); return; } driver_memcpy(tmp, p, n); @@ -2501,7 +2507,7 @@ static void dbg_cmd_help(void) { "Commands (abbrev. shown):\n" " h, help show this help\n" " q, quit exit (Ctrl-D also works)\n" - " :language c|toy|asm|wasm/wat\n" + " :language c|asm|wasm/wat\n" " select language for jit/expr input\n" " r, run start fresh execution at entry\n" " c, cont continue after a stop\n" @@ -3002,7 +3008,7 @@ static void dbg_complete_info(DriverLineCompletionList* out, const char* prefix, static void dbg_complete_languages(DriverLineCompletionList* out, const char* prefix, size_t prefix_len) { - static const char* const langs[] = {"c", "toy", "asm", "wasm", "wat"}; + static const char* const langs[] = {"c", "asm", "wasm", "wat"}; size_t i; for (i = 0; i < sizeof(langs) / sizeof(langs[0]); ++i) dbg_completion_add_cstr(out, prefix, prefix_len, langs[i]); diff --git a/driver/cmd/emu.c b/driver/cmd/emu.c @@ -73,7 +73,8 @@ void driver_help_emu(void) { " to the guest executable path.\n" "\n" "OPTIONS\n" - " -O0 -O1 -O2 Translator optimization level (default -O0)\n" + " -O0 -O1 -O2 Translator optimization level; -O2 aliases " + "-O1 (default -O0)\n" " -interp Run translated blocks through the IR " "interpreter\n" " instead of JITing them (forces -O1)\n" @@ -150,7 +151,7 @@ static int emu_parse(int argc, char** argv, EmuOptions* o) { continue; } if (driver_streq(a, "-O2")) { - o->opt_level = 2; + o->opt_level = 1; continue; } diff --git a/driver/cmd/ld.c b/driver/cmd/ld.c @@ -134,7 +134,7 @@ void driver_help_ld(void) { driver_printf( "%.*s", KIT_SLICE_ARG(KIT_SLICE_LIT( - "kit ld — link objects/archives into an executable or shared " + "kit ld — link objects/archives into an executable or ELF shared " "library\n" "\n" "USAGE\n" @@ -145,16 +145,14 @@ void driver_help_ld(void) { "DESCRIPTION\n" " Loads each input via host file I/O, optionally parses a -T " "linker\n" - " script, and emits an executable (default) or shared library\n" + " script, and emits an executable (default) or ELF shared library\n" " (-shared). The full link surface is exposed: per-archive flags,\n" " cyclic-resolution groups, build-id, soname/rpath/exports.\n" "\n" "OUTPUT\n" " -o PATH Output path (required, exactly one)\n" - " -shared Emit a position-independent shared " - "library " - "/\n" - " dylib instead of an executable\n" + " -shared Emit a position-independent ELF shared\n" + " library instead of an executable\n" " -r, --relocatable Emit a relocatable partial-link object\n" "\n" "ENTRY / SCRIPT\n" @@ -2665,6 +2663,11 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { "-nostartfiles"); return 1; } + if (o->shared && o->target.obj != KIT_OBJ_ELF) { + driver_errf(LD_TOOL, + "-shared output is supported only for ELF targets in v1"); + return 1; + } if (o->relocatable) { if (o->shared) { driver_errf(LD_TOOL, "-r and -shared are incompatible"); diff --git a/driver/cmd/run.c b/driver/cmd/run.c @@ -20,7 +20,7 @@ * .o objects, .a archives) and target/diagnostic flag surface; libkit's * JIT path forces PIC regardless of `-fPIC`/`-fPIE`/`-mcmodel`. * - * Native host-symbol fallback (so JITed C/Toy/object code can call libc) goes + * Native host-symbol fallback (so JITed C/object code can call libc) goes * through driver_dlsym_resolver in driver/env.c. Wasm source inputs do not get * that fallback; host access is controlled by the Wasm import policy. The * driver returns whatever the entry returns, or 1 on a compile/link/lookup @@ -178,7 +178,8 @@ void driver_help_run(void) { " are accepted but have no observable effect.\n" "\n" "COMPILE OPTIONS\n" - " -O0 -O1 -O2 Optimization level (default -O0)\n" + " -O0 -O1 -O2 Optimization level; -O2 aliases -O1 " + "(default -O0)\n" " --no-jit Execute the entry through the IR interpreter\n" " instead of JIT-compiled native code (forces " "-O1\n" @@ -226,7 +227,8 @@ void driver_help_run(void) { " Import resolver policy (default deny). `test` " "binds\n" " only the in-tree env.host_add test shim.\n" - " `wasi` binds the configured WASI Preview1 " + " `wasi` binds the configured partial WASI " + "Preview1 " "shim.\n" " --wasm-wasi Alias for --wasm-imports=wasi\n" " --wasm-env=none|allowlist|inherit\n" @@ -504,7 +506,7 @@ static int run_parse(int argc, char** argv, RunOptions* o) { continue; } if (driver_streq(a, "-O2")) { - o->opt_level = 2; + o->opt_level = 1; continue; } diff --git a/driver/driver.h b/driver/driver.h @@ -176,6 +176,7 @@ typedef enum DriverToolGroup { unsigned driver_tool_count(void); const char* driver_tool_name(unsigned index); /* NULL if out of range */ unsigned driver_tool_groups(unsigned index); /* 0 if out of range */ +int driver_tool_public(unsigned index); /* listed in help/install --all */ /* Index of the tool named `name`, or -1 if there is no such tool. */ int driver_tool_find(const char* name); @@ -193,13 +194,14 @@ int driver_argv_wants_help(int argc, char** argv, int accept_short_h); /* Parse a target triple string (`<arch>[-<vendor>]-<os>[-<env>]`) into a * KitTargetSpec. Recognized arches: x86_64/amd64, i386/i486/i586/i686, aarch64/ - * arm64, arm/armv7, riscv64, riscv32, wasm32, wasm64. Recognized OSes (scanned - * across the remaining components, so vendor tokens like `pc`/`apple`/ - * `unknown` are skipped): linux, android, darwin/macos, ios/iphoneos, - * iphonesimulator/ios-simulator, windows/win32, wasi, none/freestanding. Sets - * arch/os/obj/ptr_size/ptr_align/big_endian; pic and code_model are left at - * their defaults. Returns 0 on success, nonzero on unrecognized arch or NULL - * inputs. */ + * arm64, arm/armv7, riscv64, riscv32, wasm32, wasm64. `wasm64` is recognized as + * a reserved spelling, but compiler target construction rejects it in v1. + * Recognized OSes (scanned across the remaining components, so vendor tokens + * like `pc`/`apple`/`unknown` are skipped): linux, android, darwin/macos, + * ios/iphoneos, iphonesimulator/ios-simulator, windows/win32, wasi, + * none/freestanding. Sets arch/os/obj/ptr_size/ptr_align/big_endian; pic and + * code_model are left at their defaults. Returns 0 on success, nonzero on + * unrecognized arch or NULL inputs. */ int driver_target_from_triple(const char* triple, KitTargetSpec* out); /* Render a canonical driver target triple into `buf`. Returns 0 on success, @@ -208,10 +210,11 @@ int driver_target_to_triple(KitTargetSpec target, char* buf, size_t cap); /* Map an architecture-name literal (the arch component of a triple: * x86_64/amd64, i386/i486/i586/i686, aarch64/arm64, arm/armv7, riscv64, - * riscv32, wasm32, wasm64) to its KitArchKind and natural pointer size. The - * single authority for arch-name spellings; driver_target_from_triple uses - * the same table. Returns 0 on success, nonzero on an unrecognized name or a - * NULL argument. Out-pointers may be NULL. */ + * riscv32, wasm32, wasm64) to its KitArchKind and natural pointer size. + * `wasm64` is reserved but unsupported for v1 code generation. The single + * authority for arch-name spellings; driver_target_from_triple uses the same + * table. Returns 0 on success, nonzero on an unrecognized name or a NULL + * argument. Out-pointers may be NULL. */ int driver_arch_from_name(const char* name, KitArchKind* arch_out, uint8_t* ptr_size_out); diff --git a/driver/env/posix.c b/driver/env/posix.c @@ -1663,7 +1663,7 @@ static KitArchKind host_arch_self(void) { } KitTargetSpec driver_host_target(void) { - KitTargetSpec t; + KitTargetSpec t = {0}; t.arch = host_arch_self(); os_host_target_fill(&t); t.ptr_size = (uint8_t)sizeof(void*); diff --git a/driver/env/windows.c b/driver/env/windows.c @@ -2309,7 +2309,7 @@ static KitArchKind host_arch_self_win(void) { } KitTargetSpec driver_host_target(void) { - KitTargetSpec t; + KitTargetSpec t = {0}; t.arch = host_arch_self_win(); t.os = KIT_OS_WINDOWS; t.obj = KIT_OBJ_COFF; diff --git a/driver/lib/install_links.c b/driver/lib/install_links.c @@ -80,6 +80,7 @@ unsigned driver_install_links(DriverEnv* env, const char* bindir, unsigned done_count = 0, failures = 0; for (j = 0; j < n; ++j) { + if (group_mask == DRIVER_GROUP_ALL && !driver_tool_public(j)) continue; if (group_mask != DRIVER_GROUP_ALL && (driver_tool_groups(j) & group_mask) == 0) continue; diff --git a/driver/main.c b/driver/main.c @@ -119,9 +119,7 @@ static const DriverToolDesc driver_tools[] = { DRIVER_GROUP_OTHER}, #endif #if KIT_TOOL_EMU_ENABLED - {"emu", driver_emu, NULL, driver_help_emu, - "Run a guest user-mode ELF (aarch64/riscv64) on the host", - DRIVER_GROUP_OTHER}, + {"emu", driver_emu, NULL, driver_help_emu, NULL, DRIVER_GROUP_OTHER}, #endif #if KIT_TOOL_NM_ENABLED {"nm", driver_nm, NULL, driver_help_nm, "List symbols from object files", @@ -223,6 +221,10 @@ unsigned driver_tool_groups(unsigned index) { return driver_tools[index].groups; } +int driver_tool_public(unsigned index) { + return index < driver_tool_count() && driver_tools[index].summary != NULL; +} + int driver_tool_find(const char* name) { unsigned i; if (!name) return -1; @@ -323,6 +325,7 @@ void driver_help_top(void) { "\n" "TOOLS\n"); for (i = 0; i < driver_tool_count(); ++i) { + if (!driver_tool_public(i)) continue; driver_printf(" %-9s %s\n", driver_tools[i].name, driver_tools[i].summary); } driver_printf( diff --git a/include/kit/core.h b/include/kit/core.h @@ -274,7 +274,7 @@ typedef enum KitAutoVarInit { } KitAutoVarInit; typedef struct KitCodeOptions { - int opt_level; /* 0 direct; 1+ require KIT_OPT_ENABLED */ + int opt_level; /* 0 direct; 1 optimized; 2 accepted as an alias for 1 */ bool debug_info; /* emit source/debug records when supported */ /* Run the frontend and CG validation path without emitting target code. * Drivers use this for syntax/semantic checking modes. */ diff --git a/include/kit/target.h b/include/kit/target.h @@ -30,7 +30,8 @@ * riscv64 / rv64 / riscv64<isa> -> KIT_ARCH_RV64 (8) * riscv32 / rv32 / riscv32<isa> -> KIT_ARCH_RV32 (4) * wasm32 -> KIT_ARCH_WASM (4) - * wasm64 -> KIT_ARCH_WASM (8) + * wasm64 -> KIT_ARCH_WASM (8; reserved, + * unsupported in v1) * The single authority for arch-name spellings; kit_target_from_triple() uses * the same table. Returns true on a hit (writing through non-NULL out * pointers), false on an unrecognized name or a NULL `name`. Out pointers may @@ -51,8 +52,9 @@ KIT_API bool kit_arch_from_name(const char* name, KitArchKind* arch_out, * Sets arch/os/obj/ptr_size/ptr_align/big_endian/os_version_major, and derives * pic via kit_target_default_pic; code_model is left at KIT_CM_DEFAULT. The * resolved data-model fields (long_size, wchar_size, ...) are NOT filled in - * here — they come from kit_target_new(). Returns true on success, false on an - * unrecognized arch or a NULL `triple`/`out`. */ + * here — they come from kit_target_new(). `wasm64` is parseable as a reserved + * spelling but kit_target_new() rejects it in v1. Returns true on success, + * false on an unrecognized arch or a NULL `triple`/`out`. */ KIT_API bool kit_target_from_triple(const char* triple, KitTargetSpec* out); /* Render a canonical target triple for `spec` into `buf` (e.g. diff --git a/mk/flags.mk b/mk/flags.mk @@ -46,11 +46,8 @@ endif # Auto-initialize automatic (stack) variables to zero. Hardening that defines # the otherwise-UB of reading an uninitialized local — the same default the # Linux kernel / Android / Chrome ship; =zero is deterministic and effectively -# free at our opt levels. It also neutralizes the latent layout-sensitive -# uninit-read in the kit-run pipeline documented in doc/plan/TODO.md (that -# entry stays open until MSan pinpoints and fixes the actual read). Probed so a -# compiler without the flag — notably $(CC)=kit cc in bootstrap stages — simply -# omits it instead of erroring. +# free at our opt levels. Probed so a compiler without the flag — notably +# $(CC)=kit cc in bootstrap stages — simply omits it instead of erroring. AUTO_INIT_CFLAGS = $(shell $(CC) -ftrivial-auto-var-init=zero -x c -E /dev/null >/dev/null 2>&1 && printf '%s' -ftrivial-auto-var-init=zero) CFLAGS_COMMON = $(HOST_OPTFLAGS) $(HOST_MODE_CPPFLAGS) $(HOST_MODE_CFLAGS) \ diff --git a/src/api/core.c b/src/api/core.c @@ -45,6 +45,11 @@ KitStatus kit_target_new(const KitContext* ctx, const KitTargetOptions* opts, (unsigned)opts->spec.arch); return KIT_UNSUPPORTED; } + if (opts->spec.arch == KIT_ARCH_WASM && opts->spec.ptr_size != 4u) { + kit_ctx_diagf(ctx, + "wasm64 is not supported in v1; use a wasm32 target"); + return KIT_UNSUPPORTED; + } h = ctx->heap; t = (KitTarget*)h->alloc(h, sizeof(*t), _Alignof(KitTarget)); diff --git a/src/arch/wasm/arch.c b/src/arch/wasm/arch.c @@ -48,7 +48,7 @@ static KitStatus wasm_target_feature_apply_isa(const Target* target, (void)target; (void)words; (void)nwords; - if (kit_slice_eq_cstr(isa, "wasm32") || kit_slice_eq_cstr(isa, "wasm64")) + if (kit_slice_eq_cstr(isa, "wasm32")) return KIT_OK; return KIT_UNSUPPORTED; } diff --git a/src/cg/cgir.h b/src/cg/cgir.h @@ -494,7 +494,7 @@ typedef struct CGSwitchDesc { const CGSwitchCase* cases; u32 ncases; u8 hint; /* KitCgSwitchHint */ - u8 opt_level; /* 0/1/2; reads policy in cg_lower_switch_default */ + u8 opt_level; /* 0 direct, 1 optimized; public 2 is normalized to 1 */ u8 pad[2]; } CGSwitchDesc; diff --git a/src/cg/control.c b/src/cg/control.c @@ -226,7 +226,6 @@ static CGSwitchPlan cg_plan_switch(KitCg* g, const CGSwitchDesc* d) { } /* TARGET_DEFAULT: O0 keeps the chain; O1+ runs the density check. */ if (d->opt_level == 0) return plan; - if (d->opt_level >= 2 && plan.span != d->ncases) return plan; if (d->ncases < CG_SWITCH_TABLE_MIN_CASES_O1) return plan; if (plan.span > CG_SWITCH_TABLE_MAX_SPAN_O1) return plan; if (plan.span > (u64)d->ncases * CG_SWITCH_TABLE_DENSITY_RECIP_O1) diff --git a/src/cg/session.c b/src/cg/session.c @@ -126,6 +126,7 @@ KitStatus kit_cg_begin(KitCg* g, KitObjBuilder* out, compiler_panic((Compiler*)c, api_no_loc(), "KitCg: unsupported opt_level %d", opt_level); } + if (opt_level > 1) opt_level = 1; if (opts && opts->emit_ir && opt_level < 1) { compiler_panic((Compiler*)c, api_no_loc(), "KitCg: emit_ir requires opt_level >= 1 " diff --git a/test/api/cg_fp_cmp_test.c b/test/api/cg_fp_cmp_test.c @@ -245,6 +245,11 @@ static void run_emit(KitArchKind arch, KitOSKind os, KitObjFmt fmt, int i; char nm[40]; + if (arch == KIT_ARCH_WASM) { + tgt.ptr_size = 4; + tgt.ptr_align = 4; + } + if (kit_unit_compiler_new(&g_u, tgt, &c) != KIT_OK || !c) { EXPECT(0, "%s/O%d: compiler_new failed", tag, opt_level); return; diff --git a/test/api/target_test.c b/test/api/target_test.c @@ -212,6 +212,7 @@ static void check_rv64_isa_and_overrides(void) { static void check_wasm_features(void) { KitTargetSpec spec = target_spec(KIT_ARCH_WASM, KIT_OS_WASI, KIT_OBJ_WASM); + KitTargetSpec parsed; KitTargetFeature disable_tail_calls[] = { {KIT_SLICE_LIT("tail-calls"), false}, }; @@ -229,6 +230,26 @@ static void check_wasm_features(void) { EXPECT(has(t, "bulk-memory"), "wasm default still has bulk-memory"); EXPECT(!has(t, "not-a-feature"), "unknown queried feature is absent"); kit_target_free(t); + + memset(&parsed, 0, sizeof parsed); + EXPECT(kit_target_from_triple("wasm64-wasi", &parsed), + "wasm64 triple remains parseable as a reserved spelling"); + EXPECT(parsed.arch == KIT_ARCH_WASM && parsed.ptr_size == 8, + "wasm64 triple carries 8-byte pointer size"); + g_u.last_diag[0] = '\0'; + EXPECT(make_target(parsed, KIT_SLICE_NULL, NULL, 0, &t) == KIT_UNSUPPORTED, + "wasm64 target construction rejected"); + EXPECT(t == NULL, "wasm64 rejected target leaves target NULL"); + EXPECT(strstr(g_u.last_diag, "wasm64 is not supported") != NULL, + "wasm64 unsupported diagnostic"); + + g_u.last_diag[0] = '\0'; + EXPECT(make_target(spec, KIT_SLICE_LIT("wasm64"), NULL, 0, &t) == + KIT_INVALID, + "wasm64 ISA spelling rejected"); + EXPECT(t == NULL, "wasm64 ISA rejected target leaves target NULL"); + EXPECT(strstr(g_u.last_diag, "unsupported ISA/profile") != NULL, + "wasm64 ISA diagnostic"); } static void check_errors(void) { diff --git a/test/arch/aa64_inline_test.c b/test/arch/aa64_inline_test.c @@ -1,6 +1,7 @@ /* Public-API unit test for the aarch64 inline-asm backend. */ #include <stdint.h> +#include <string.h> #include "inline_public_test.h" @@ -48,6 +49,49 @@ static void aa64_bad_ldr_zr(KitCompiler* c, KitCg* cg, KitCgTypeId i64_ty) { it_inline_asm(c, cg, "ldr w0, [xzr]", NULL, 0, NULL, 0, NULL, 0); } +static void aa64_push_local_inputs(KitCompiler* c, KitCg* cg, KitCgTypeId ty, + const char* constraint, + KitCgAsmOperand* ins, uint32_t n) { + KitCgLocal locals[24]; + KitCgLocalAttrs attrs; + KitCgMemAccess mem; + memset(&attrs, 0, sizeof attrs); + memset(&mem, 0, sizeof mem); + mem.type = ty; + mem.align = kit_cg_type_align(c, ty); + for (uint32_t i = 0; i < n; ++i) { + locals[i] = kit_cg_local(cg, ty, attrs); + if (ty == kit_cg_type_builtin(c, KIT_CG_BUILTIN_F64)) + kit_cg_push_float(cg, (double)i + 0.5, ty); + else + kit_cg_push_int(cg, i + 1u, ty); + kit_cg_local_write(cg, locals[i], mem); + } + for (uint32_t i = 0; i < n; ++i) { + kit_cg_local_read(cg, locals[i], mem); + ins[i] = it_asm_op(c, constraint, NULL, ty, KIT_CG_ASM_IN); + } +} + +static void aa64_optimized_stage(KitCompiler* c, KitCg* cg, + KitCgTypeId i64_ty) { + KitCgTypeId f64_ty = kit_cg_type_builtin(c, KIT_CG_BUILTIN_F64); + KitCgAsmOperand ins[24]; + KitCgAsmOperand outs[6]; + + aa64_push_local_inputs(c, cg, i64_ty, "r", ins, 20); + it_inline_asm(c, cg, "", NULL, 0, ins, 20, NULL, 0); + + aa64_push_local_inputs(c, cg, f64_ty, "w", ins, 24); + it_inline_asm(c, cg, "", NULL, 0, ins, 24, NULL, 0); + + for (uint32_t i = 0; i < 6u; ++i) + outs[i] = it_asm_op(c, i & 1u ? "=w" : "=r", NULL, + i & 1u ? f64_ty : i64_ty, KIT_CG_ASM_OUT); + it_inline_asm(c, cg, "", outs, 6, NULL, 0, NULL, 0); + for (uint32_t i = 0; i < 6u; ++i) kit_cg_drop(cg); +} + int main(void) { InlineTestEnv env; InlineText text; @@ -91,6 +135,15 @@ int main(void) { aa64_bad_ldr_zr, "zero register"), "expected ldr w0, [xzr] to panic"); + { + InlineText opt; + IT_EXPECT(&env, + it_emit_text_opt(&env, KIT_ARCH_ARM_64, "aa64_optimized_stage", + aa64_optimized_stage, 1, &opt), + "failed to emit optimized aa64 inline-asm staging case"); + it_text_close(&opt); + } + if (env.fails) { fprintf(stderr, "%d failure(s)\n", env.fails); return 1; diff --git a/test/arch/inline_public_test.h b/test/arch/inline_public_test.h @@ -37,6 +37,7 @@ typedef struct InlineEmit { KitObjBuilder* ob; InlineBodyFn body; const char* name; + int opt_level; } InlineEmit; static inline KitTargetSpec it_target(KitArchKind arch) { @@ -61,6 +62,7 @@ static inline KitStatus it_emit_func(KitCompiler* c, void* user) { if (kit_obj_builder_new(c, &emit->ob) != KIT_OK) return KIT_ERR; if (kit_cg_new(c, &cg) != KIT_OK || !cg) return KIT_ERR; memset(&opts, 0, sizeof opts); + opts.opt_level = emit->opt_level; if (kit_cg_begin(cg, emit->ob, &opts) != KIT_OK) return KIT_ERR; memset(&sig, 0, sizeof sig); @@ -89,9 +91,9 @@ static inline KitStatus it_emit_func(KitCompiler* c, void* user) { return KIT_OK; } -static inline int it_emit_text(InlineTestEnv* env, KitArchKind arch, - const char* name, InlineBodyFn body, - InlineText* text) { +static inline int it_emit_text_opt(InlineTestEnv* env, KitArchKind arch, + const char* name, InlineBodyFn body, + int opt_level, InlineText* text) { KitCompiler* c = NULL; KitTarget* target = NULL; KitTargetOptions target_opts; @@ -106,6 +108,7 @@ static inline int it_emit_text(InlineTestEnv* env, KitArchKind arch, emit.env = env; emit.body = body; emit.name = name; + emit.opt_level = opt_level; memset(&target_opts, 0, sizeof target_opts); target_opts.spec = it_target(arch); @@ -179,6 +182,12 @@ done: return ok; } +static inline int it_emit_text(InlineTestEnv* env, KitArchKind arch, + const char* name, InlineBodyFn body, + InlineText* text) { + return it_emit_text_opt(env, arch, name, body, 0, text); +} + static inline void it_text_close(InlineText* text) { if (text->file) kit_obj_free(text->file); if (text->writer) kit_writer_close(text->writer); diff --git a/test/arch/x64_inline_test.c b/test/arch/x64_inline_test.c @@ -1,6 +1,7 @@ /* Public-API unit test for the x86_64 inline-asm backend. */ #include <stdint.h> +#include <string.h> #include "inline_public_test.h" @@ -34,6 +35,51 @@ static void x64_rax_pin(KitCompiler* c, KitCg* cg, KitCgTypeId i64_ty) { it_inline_asm(c, cg, "syscall", NULL, 0, &in, 1, clob, 3); } +static void x64_push_local_inputs(KitCompiler* c, KitCg* cg, KitCgTypeId ty, + const char* constraint, + KitCgAsmOperand* ins, uint32_t n) { + KitCgLocal locals[18]; + KitCgLocalAttrs attrs; + KitCgMemAccess mem; + memset(&attrs, 0, sizeof attrs); + memset(&mem, 0, sizeof mem); + mem.type = ty; + mem.align = kit_cg_type_align(c, ty); + for (uint32_t i = 0; i < n; ++i) { + locals[i] = kit_cg_local(cg, ty, attrs); + if (ty == kit_cg_type_builtin(c, KIT_CG_BUILTIN_F64)) + kit_cg_push_float(cg, (double)i + 0.5, ty); + else + kit_cg_push_int(cg, i + 1u, ty); + kit_cg_local_write(cg, locals[i], mem); + } + for (uint32_t i = 0; i < n; ++i) { + kit_cg_local_read(cg, locals[i], mem); + ins[i] = it_asm_op(c, constraint, NULL, ty, KIT_CG_ASM_IN); + } +} + +static void x64_optimized_stage(KitCompiler* c, KitCg* cg, KitCgTypeId i64_ty) { + KitCgTypeId f64_ty = kit_cg_type_builtin(c, KIT_CG_BUILTIN_F64); + KitCgAsmOperand ins[18]; + KitCgAsmOperand outs[6]; + + x64_push_local_inputs(c, cg, i64_ty, "r", ins, 14); + it_inline_asm(c, cg, "", NULL, 0, ins, 14, NULL, 0); + + x64_push_local_inputs(c, cg, i64_ty, "q", ins, 14); + it_inline_asm(c, cg, "", NULL, 0, ins, 14, NULL, 0); + + x64_push_local_inputs(c, cg, f64_ty, "x", ins, 18); + it_inline_asm(c, cg, "", NULL, 0, ins, 18, NULL, 0); + + for (uint32_t i = 0; i < 6u; ++i) + outs[i] = it_asm_op(c, i & 1u ? "=x" : "=r", NULL, + i & 1u ? f64_ty : i64_ty, KIT_CG_ASM_OUT); + it_inline_asm(c, cg, "", outs, 6, NULL, 0, NULL, 0); + for (uint32_t i = 0; i < 6u; ++i) kit_cg_drop(cg); +} + int main(void) { static const uint8_t nops[] = {0x90u, 0x90u}; static const uint8_t movq_rcx_rax[] = {0x48u, 0x89u, 0xc8u}; @@ -79,6 +125,15 @@ int main(void) { it_text_close(&sc); } + { + InlineText opt; + IT_EXPECT(&env, + it_emit_text_opt(&env, KIT_ARCH_X86_64, "x64_optimized_stage", + x64_optimized_stage, 1, &opt), + "failed to emit optimized x64 inline-asm staging case"); + it_text_close(&opt); + } + if (env.fails) { fprintf(stderr, "%d failure(s)\n", env.fails); return 1; diff --git a/test/dbg/cases/repl-help/expected b/test/dbg/cases/repl-help/expected @@ -2,7 +2,7 @@ kit dbg — 'h' for help, 'q' to quit Commands (abbrev. shown): h, help show this help q, quit exit (Ctrl-D also works) - :language c|toy|asm|wasm/wat + :language c|asm|wasm/wat select language for jit/expr input r, run start fresh execution at entry c, cont continue after a stop diff --git a/test/driver/run.sh b/test/driver/run.sh @@ -217,6 +217,108 @@ else not_ok "cc-dumpmachine-probe" "$work/cc-dumpmachine.err" fi +# ---- shared-library creation is ELF-only for v1 ---- +if "$KIT" cc -target x86_64-macos -shared -nostdlib "$work/main.c" \ + -o "$work/libbad.dylib" > "$work/cc-shared-macho.out" \ + 2> "$work/cc-shared-macho.err"; then + echo "expected non-ELF -shared to fail" > "$work/cc-shared-macho.diag" + not_ok "cc-rejects-nonelf-shared" "$work/cc-shared-macho.diag" +elif grep -q "only for ELF" "$work/cc-shared-macho.err"; then + ok "cc-rejects-nonelf-shared" +else + not_ok "cc-rejects-nonelf-shared" "$work/cc-shared-macho.err" +fi + +if "$KIT" cc -target x86_64-macos -c "$work/main.c" \ + -o "$work/main-macho.o" > "$work/main-macho.out" \ + 2> "$work/main-macho.err"; then + if "$KIT" ld -target x86_64-macos -shared "$work/main-macho.o" \ + -o "$work/libbad2.dylib" > "$work/ld-shared-macho.out" \ + 2> "$work/ld-shared-macho.err"; then + echo "expected non-ELF ld -shared to fail" \ + > "$work/ld-shared-macho.diag" + not_ok "ld-rejects-nonelf-shared" "$work/ld-shared-macho.diag" + elif grep -q "only for ELF" "$work/ld-shared-macho.err"; then + ok "ld-rejects-nonelf-shared" + else + not_ok "ld-rejects-nonelf-shared" "$work/ld-shared-macho.err" + fi +else + not_ok "ld-rejects-nonelf-shared" "$work/main-macho.err" +fi + +if "$KIT" build-lib -target x86_64-macos -dynamic "$work/main.c" \ + -o "$work/libbad3.dylib" > "$work/buildlib-shared-macho.out" \ + 2> "$work/buildlib-shared-macho.err"; then + echo "expected non-ELF build-lib -dynamic to fail" \ + > "$work/buildlib-shared-macho.diag" + not_ok "build-lib-rejects-nonelf-shared" \ + "$work/buildlib-shared-macho.diag" +elif grep -q "only for ELF" "$work/buildlib-shared-macho.err"; then + ok "build-lib-rejects-nonelf-shared" +else + not_ok "build-lib-rejects-nonelf-shared" \ + "$work/buildlib-shared-macho.err" +fi + +# ---- WebAssembly v1 boundary: wasm32 source batches only, no wasm64/link ---- +if "$KIT" cc -target wasm64-none -c "$work/main.c" \ + -o "$work/bad64.wasm" > "$work/cc-wasm64.out" \ + 2> "$work/cc-wasm64.err"; then + echo "expected wasm64 compile to fail" > "$work/cc-wasm64.diag" + not_ok "cc-rejects-wasm64" "$work/cc-wasm64.diag" +elif grep -q "wasm64 is not supported" "$work/cc-wasm64.err"; then + ok "cc-rejects-wasm64" +else + not_ok "cc-rejects-wasm64" "$work/cc-wasm64.err" +fi + +if "$KIT" cc -target x86_64-linux -c "$work/main.c" \ + -o "$work/wasm-boundary-main.o" > "$work/wasm-boundary-cc.out" \ + 2> "$work/wasm-boundary-cc.err"; then + if "$KIT" build-exe -target wasm32-none "$work/wasm-boundary-main.o" \ + -o "$work/bad-linked.wasm" > "$work/wasm-object-link.out" \ + 2> "$work/wasm-object-link.err"; then + echo "expected wasm32 object-link attempt to fail" \ + > "$work/wasm-object-link.diag" + not_ok "build-exe-rejects-wasm-object-link" \ + "$work/wasm-object-link.diag" + elif grep -q "accepts only source files" "$work/wasm-object-link.err"; then + ok "build-exe-rejects-wasm-object-link" + else + not_ok "build-exe-rejects-wasm-object-link" \ + "$work/wasm-object-link.err" + fi + + if "$KIT" ar rc "$work/libwasm-boundary.a" \ + "$work/wasm-boundary-main.o" > "$work/wasm-boundary-ar.out" \ + 2> "$work/wasm-boundary-ar.err"; then + if "$KIT" build-exe -target wasm32-none \ + "$work/libwasm-boundary.a" -o "$work/bad-archive.wasm" \ + > "$work/wasm-archive-link.out" \ + 2> "$work/wasm-archive-link.err"; then + echo "expected wasm32 archive-link attempt to fail" \ + > "$work/wasm-archive-link.diag" + not_ok "build-exe-rejects-wasm-archive-link" \ + "$work/wasm-archive-link.diag" + elif grep -q "accepts only source files" \ + "$work/wasm-archive-link.err"; then + ok "build-exe-rejects-wasm-archive-link" + else + not_ok "build-exe-rejects-wasm-archive-link" \ + "$work/wasm-archive-link.err" + fi + else + not_ok "build-exe-rejects-wasm-archive-link" \ + "$work/wasm-boundary-ar.err" + fi +else + not_ok "build-exe-rejects-wasm-object-link" \ + "$work/wasm-boundary-cc.err" + not_ok "build-exe-rejects-wasm-archive-link" \ + "$work/wasm-boundary-cc.err" +fi + # ---- cc -print-file-name probe ---- if "$KIT" cc -print-file-name=crt1.o \ > "$work/cc-print-file-name.out" 2> "$work/cc-print-file-name.err" && @@ -710,6 +812,21 @@ else not_ok "run-source-archive-demand" "$work/run-setup.diag" fi +cat > "$work/run-fp.c" <<'SRC' +int main(void) { + volatile double a = 3.0; + volatile double b = 2.0; + double c = (a * b) + (a / b); + int scaled = (int)(c * 10.0); + return scaled == 75 ? 0 : 1; +} +SRC +if "$KIT" run "$work/run-fp.c" > "$work/run-fp.out" 2> "$work/run-fp.err"; then + ok "run-host-fp-uses-hardware-abi" +else + not_ok "run-host-fp-uses-hardware-abi" "$work/run-fp.err" +fi + # ---- run --script: #! shebang interpreter, argv passthrough, implicit -lc ---- # Make a .c file executable with a `#!` line and run it directly. The kernel # launches the interpreter and appends the script path + the user's args, so @@ -1254,6 +1371,37 @@ else not_ok "cc-emit-ir" "$work/ir-emit.err" fi +if "$KIT" cc --help > "$work/cc-help.out" 2> "$work/cc-help.err" && + grep -q -- "-O2 aliases -O1" "$work/cc-help.out"; then + ok "cc-help-o2-alias" +else + not_ok "cc-help-o2-alias" "$work/cc-help.err" +fi + +if "$KIT" --help > "$work/kit-help.out" 2> "$work/kit-help.err" && + ! grep -q "^ emu[[:space:]]" "$work/kit-help.out"; then + ok "kit-help-hides-emu" +else + not_ok "kit-help-hides-emu" "$work/kit-help.out" +fi + +if "$KIT" dbg --help > "$work/dbg-help.out" 2> "$work/dbg-help.err" && + ! grep -q "toy" "$work/dbg-help.out"; then + ok "dbg-help-hides-toy" +else + not_ok "dbg-help-hides-toy" "$work/dbg-help.out" +fi + +if "$KIT" cc -O2 --emit=ir -c "$work/ir.c" -o "$work/ir-o2.out" \ + > "$work/ir-o2-emit.out" 2> "$work/ir-o2-emit.err" && + cmp -s "$work/ir.out" "$work/ir-o2.out"; then + ok "cc-o2-aliases-o1-ir" +else + { echo "expected -O2 IR to match -O1"; cat "$work/ir-o2-emit.err"; } \ + > "$work/ir-o2.diag" + not_ok "cc-o2-aliases-o1-ir" "$work/ir-o2.diag" +fi + # --emit=ir without -O1 must be rejected (no IR tape is recorded at -O0). if "$KIT" cc --emit=ir -c "$work/ir.c" -o "$work/ir-o0.out" \ > "$work/ir-o0.out.log" 2> "$work/ir-o0.err"; then @@ -1291,6 +1439,12 @@ fi inst_all="$work/inst-all" run_ok "install-all" "$KIT" install --all "$inst_all" assert_file_exists "install-all-has-mc" "$inst_all/mc" +if [ -e "$inst_all/emu" ]; then + echo "emu present in install --all" > "$work/install-all-emu.diag" + not_ok "install-all-hides-emu" "$work/install-all-emu.diag" +else + ok "install-all-hides-emu" +fi # Existing entries are an error without -f, replaced with it. run_fail "install-existing-without-force" "$KIT" install "$inst_dir" diff --git a/test/wasm/run.sh b/test/wasm/run.sh @@ -153,16 +153,62 @@ static int bind_canned_imports(void *instance) { typedef struct { unsigned char *data; unsigned long long pages; unsigned long long max_pages; unsigned int flags; } WasmStartMemoryPrefix; -#define WASM_START_MEMORY_PREFIX_COUNT 8u -#define WASM_START_INSTANCE_SIZE (64u * 1024u) -#define WASM_START_MEMORY_SIZE (16u * 1024u * 1024u) +typedef struct { + unsigned long long offset; + unsigned long long min_pages; + unsigned long long max_pages; + unsigned int flags; + unsigned int reserved; +} WasmStartMemoryLayout; +extern const unsigned long long __kit_wasm_instance_size; +extern const unsigned int __kit_wasm_nmemories; +__attribute__((weak)) const WasmStartMemoryLayout + __kit_wasm_memory_layouts[1] = {{0, 0, 0, 0, 0}}; + +#define WASM_START_PAGE_SIZE (64ull * 1024ull) +#define WASM_START_MAX_INSTANCE_SIZE (64ull * 1024ull * 1024ull) +#define WASM_START_MAX_TOTAL_MEMORY_SIZE (1024ull * 1024ull * 1024ull) + +static int setup_instance(void **instance_out) { + unsigned long long instance_size = __kit_wasm_instance_size ? + __kit_wasm_instance_size : 1ull; + unsigned long long total_memory = 0; + void *instance; + if (instance_size > WASM_START_MAX_INSTANCE_SIZE || + instance_size > (unsigned long long)SIZE_MAX) + return 0; + instance = calloc(1, (size_t)instance_size); + if (!instance) return 0; + for (unsigned int i = 0; i < __kit_wasm_nmemories; ++i) { + const WasmStartMemoryLayout *ml = &__kit_wasm_memory_layouts[i]; + WasmStartMemoryPrefix *rec; + unsigned long long bytes; + void *memory = NULL; + if (ml->max_pages < ml->min_pages) return 0; + if (ml->max_pages > (~0ull / WASM_START_PAGE_SIZE)) return 0; + bytes = ml->max_pages * WASM_START_PAGE_SIZE; + if (bytes > WASM_START_MAX_TOTAL_MEMORY_SIZE || + total_memory > WASM_START_MAX_TOTAL_MEMORY_SIZE - bytes || + bytes > (unsigned long long)SIZE_MAX) + return 0; + if (ml->offset > instance_size || + instance_size - ml->offset < sizeof(WasmStartMemoryPrefix)) + return 0; + total_memory += bytes; + if (bytes) { + memory = calloc(1, (size_t)bytes); + if (!memory) return 0; + } + rec = (WasmStartMemoryPrefix *)((unsigned char *)instance + ml->offset); + rec->data = (unsigned char *)memory; + } + *instance_out = instance; + return 1; +} + int main(void) { - void *instance = calloc(1, WASM_START_INSTANCE_SIZE); - unsigned char *memory = calloc(1, WASM_START_MEMORY_SIZE); - if (!instance || !memory) return 1; - for (unsigned int i = 0; i < WASM_START_MEMORY_PREFIX_COUNT; ++i) - ((WasmStartMemoryPrefix *)instance)[i].data = - memory + i * (WASM_START_MEMORY_SIZE / WASM_START_MEMORY_PREFIX_COUNT); + void *instance = NULL; + if (!setup_instance(&instance)) return 1; /* After the memory prefix (which can overlap import slots for memory-less * modules), before init (which consumes the bound slots). */ if (!bind_canned_imports(instance)) return 1;