Skip to content

Fix 15 pre-release-audit issues (GH 201-244): preprocessor, lexer, VM, mem2reg, object writers, linker, driver, headers#267

Merged
kromych merged 15 commits into
masterfrom
fix/201-244
Jul 1, 2026
Merged

Fix 15 pre-release-audit issues (GH 201-244): preprocessor, lexer, VM, mem2reg, object writers, linker, driver, headers#267
kromych merged 15 commits into
masterfrom
fix/201-244

Conversation

@kromych

@kromych kromych commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Fixes the 15 open issues from the v0.0.9 pre-release audit in the GH 201–244 range. Each is a structural fix motivated by C99 or existing platform practice, with a regression test. 12 commits, one per subsystem cluster.

Fixes by area

Commit Issues Change
preprocessor 201, 202, 205, 244 IfValue::Int gains a signedness flag → arithmetic vs logical >> and unsigned-aware relational (C99 6.5.7p5 / 6.3.1.8); #if div/mod-by-zero diagnosed with a live flag for short-circuited arms (6.6p4); function-like macro arity checked (6.10.3p4)
lexer 204, 205 UTF-16 surrogate pairs for astral code points; reject 0x with no hex digits (6.4.4.1)
vm 213 printf %s/%c carry raw bytes end-to-end; no Latin-1↔UTF-8 round-trip
mem2reg 214 exclude entry-block-live-in (read-before-write) slots from promotion → no orphan phi
elf 219 real SHT_GNU_versym/verneed section headers when a versioned import is present
pe 221 corrected x64 unwind rationale, locked the current shape with a test (see below)
mach-o 222, 223 ULEB dylib ordinal past 15; flat-lookup nlist carries DYNAMIC_LOOKUP_ORDINAL
link 229 weak defined symbols resolved with strong-wins, order-independent precedence
driver 234 unrecognized input extensions diagnosed in compile/-c/--ar modes
stdint 239 (-MAX-1) idiom for INT32_MIN/INT64_MIN
runtime 240 Windows environ/_environ/_wenviron published from __getmainargs envp
docs 244, 221 #if string-literal extension and the x64 unwind gap recorded in std-conformance.md

Notes

  • x86_64 UNWIND_INFO omits callee-saved GPR saves; RtlVirtualUnwind recovers a wrong caller context for nonvo... #221 — the faithful description of callee-saved GPR spills needs a Win64 push-before-setframe prologue restructure (prologue + epilogue + unwind decoder + all rbp-relative offsets in lockstep); UWOP_SAVE_NONVOL cannot describe the current mov-below-rbp saves given the descending-offset rule and the body's per-call RSP moves. It has zero execution impact (badc emits no exception-using code), so this PR takes the issue's documented-limitation option: accurate rationale + a test that locks the current no-SAVE_NONVOL shape (to be flipped when the restructure lands).
  • Only c4.* SSA/asm snapshots change (the read-before-write fixture): the orphan single-incoming phi and its register are gone (−52 x64 instructions); the other 595 fixtures are byte-identical.

Validation

  • cargo test (debug): 1018 passed, 0 failed
  • cargo test --release --lib: passed
  • 6 demo smokes green at default, N=2, and N=3 GPR pressure
  • cargo fmt --check + clippy -D warnings --all-targets: clean
  • Box validation (Linux x64/arm64 + Windows x64/arm64) before push

Closes #201
Closes #202
Closes #204
Closes #205
Closes #213
Closes #214
Closes #219
Closes #221
Closes #222
Closes #223
Closes #229
Closes #234
Closes #239
Closes #240
Closes #244

Closes #240 #268 #269 #270
🤖 Generated with Claude Code

kromych and others added 4 commits July 1, 2026 12:05
…orce macro arity

The #if constant-expression evaluator carried its integer value in a
bare i64 with no signedness, so `>>` forced a logical shift on every
operand (C99 6.5.7p5 requires arithmetic for a signed value) and the
relational operators compared signed regardless of operand type.
IfValue::Int now carries an `unsigned` flag set from a u/U suffix or an
over-i64 literal; `>>` and the relational operators select the
interpretation from it (6.3.1.8).

A zero divisor silently folded to 0; it is now a diagnostic (6.6p4),
suppressed by a `live` flag while parsing a short-circuited or not-taken
subexpression so a dead operand (`1 ? 2 : 1/0`) still compiles.

Function-like macro calls accepted any argument count; a mismatch is now
diagnosed per 6.10.3p4 (variadic and the zero-parameter single-empty-arg
rule honored), routed through a parked-error slot drained at the
Result-returning chokepoints.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…prefix

A wide-string element was written at wchar_t width unconditionally, so a
code point above U+FFFF truncated to its low 16 bits on a 2-byte wchar_t
target. It is now emitted as a UTF-16 surrogate pair (C99 6.4.5, Unicode
D91). Separately, `0x` with no following digit was accepted as 0; C99
6.4.4.1 requires at least one hex digit, checked after the hex-float
branch so `0x.5p0` stays valid.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
read_cstring widened each C-string byte to a Rust char (Latin-1), then
printf re-encoded it as UTF-8, mangling any byte >= 0x80. The formatter
now carries a Vec<u8> end to end via read_cstring_bytes and writes raw
bytes; the &str host-bridge callers use a from_utf8_lossy wrapper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A slot read on a path with no dominating store was promoted, injecting a
join phi with an undefined predecessor edge that the rename could not
complete; the dead phi still held a register and drove a predecessor-exit
move. Such slots are now excluded from promotion up front by testing
entry-block live-in (the liveness the pruned-SSA placer already computes),
which is exactly the rename's failed set. Only c4 snapshots change: the
orphan phi and its register are gone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kromych kromych changed the title Fix issues that didn't make it to 0.1.0 Fix 15 pre-release-audit issues (GH 201-244): preprocessor, lexer, VM, mem2reg, object writers, linker, driver, headers Jul 1, 2026
kromych and others added 6 commits July 1, 2026 13:16
The symbol-version payloads were wired into PT_DYNAMIC but had no entries
in the section-header table or shstrtab, so section-based tooling could
not locate them. SHT_GNU_versym / SHT_GNU_verneed headers are now emitted
when a versioned import is present, with a single ver_shdrs term shifting
every following section index (including the export st_shndx) uniformly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The comment attributed the missing callee-saved GPR descriptions to a
zero FrameOffset; the real blockers are the descending-prologue-offset
rule and the body's per-call RSP moves, so a faithful description needs a
push-before-setframe prologue restructure. Corrected the rationale and
added a regression test asserting the current no-UWOP_SAVE_NONVOL shape,
to be flipped when that restructure lands. Execution is unaffected; badc
emits no exception-using code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bind-opcode dylib selector only used the 4-bit IMM form and masked
the ordinal, so more than 15 dylibs would bind against the wrong library.
It now emits BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB past 15. The nlist entry
for a flat-lookup import now shares the bind stream's flat-lookup branch
and carries DYNAMIC_LOOKUP_ORDINAL instead of a two-level ordinal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The defined-symbol pass skipped every binding other than STB_GLOBAL, so a
weak definition in a foreign object was dropped and a reference to it
reported as undefined. Weak definitions are now collected into a separate
table and folded behind strong ones: a strong definition wins silently
and order-independently, a weak definition on its own satisfies a
reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Input classification treated the first token with an unrecognized
extension, and everything after it, as the program's argv, but only
--jit / --interp consume argv; a mistyped source in a native/-c/--ar
build was silently dropped. Program-argv collection is now gated on the
run modes; other modes report the unrecognized input.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
INT32_MIN and INT64_MIN were unary minus applied to an out-of-range
positive literal, giving the macro a wider-than-intended type (and an
unrepresentable operand for the 64-bit case). They now use the
`(-MAX-1)` form, matching C99 7.18.2 and the limits.h sibling; every
derived signed minimum aliases these.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Note in std-conformance.md and the README/preprocessor docs that string
equality in a #if controlling expression is a c5 extension over C99
6.10.1p4 (the string-valued __BADC_TARGET__ / __BADC_VERSION__ gating
relies on it), and record the x86_64 Windows unwind limitation so it is
not mistaken for working SEH support.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kromych and others added 4 commits July 1, 2026 14:46
A #pragma binding(data dll::local, "export") admitted as a PE import
recorded the local name in the import table and fell back to dylib 0:
the loader then looked up the local name in the wrong DLL and failed
with STATUS_ENTRYPOINT_NOT_FOUND when the two names differed. Carry
each data binding's dylib index through the .note.badc binding map
(keyed by the local name, which is the UNDEF the linker records) and
resolve the import's real_symbol from the copy-reloc local->host pair
at synth time. Every shipped binding names local == export and lives
in the first dylib, so current images are byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… slots

The runtime defined environ and _environ as zeroed locals that
shadowed <stdlib.h>'s msvcrt data bindings, so direct iteration saw an
empty environment; a spawned child inheriting that view got a block
without SystemRoot and Winsock init failed in it (WSAEPROVIDERFAILEDINIT).
On arm64 the same emptiness came from _wenviron reading ucrtbase's
separate environment copy, which msvcrt's _wgetenv priming never
touches.

x64 msvcrt exports the vectors as data: bind _environ / _wenviron by
export name and alias POSIX environ to the _environ export, all
loader-filled imports of the live CRT view. arm64 msvcrt exports no
environment data, so route the family through its _get_environ /
_get_wenviron accessors instead of ucrtbase's. The runtime defines
none of them, and putenv now binds to msvcrt _putenv.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… Mach-O)

Symbol resolution prefers a definition, so a #pragma binding(data ...)
local defined anywhere in the image silently lost the binding and read
a slot nothing populates; on ELF that dual is the COPY-relocation
design, but PE and Mach-O have no copy semantics. Fail the link with
the collision named instead of shipping the dead read.

The Win64 DLL-name test drops its host prelude: on a Linux host the
prelude's time.h data bindings ride into the PE-target link and are
now rejected as collisions with the tentative tz slots.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ignore attributed the child's WSAStartup failure (WinError 10106)
to the spawn environment, but script_helper copies os.environ into the
child verbatim: a block missing SystemRoot meant the parent's
os.environ was already empty. That was the wide-environment defect --
_wenviron read ucrtbase's copy, which msvcrt's _wgetenv priming never
populates -- fixed by routing the family through msvcrt's accessors,
so the test now passes where it runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kromych kromych merged commit 476ae91 into master Jul 1, 2026
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment