From d565cf9b0fa9b3c78ddedcabd6e0d479b283cbaf Mon Sep 17 00:00:00 2001 From: kromych Date: Wed, 1 Jul 2026 12:04:33 -0700 Subject: [PATCH 01/15] preprocessor: track #if operand signedness, diagnose div-by-zero, enforce 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) --- src/c5/preprocessor.rs | 374 ++++++++++++++++++++++++++++++++++------- 1 file changed, 314 insertions(+), 60 deletions(-) diff --git a/src/c5/preprocessor.rs b/src/c5/preprocessor.rs index cfb32f16a..38ffbe594 100644 --- a/src/c5/preprocessor.rs +++ b/src/c5/preprocessor.rs @@ -51,7 +51,7 @@ use alloc::format; use alloc::string::{String, ToString}; use alloc::vec::Vec; -use core::cell::Cell; +use core::cell::{Cell, RefCell}; use hashbrown::HashMap; use super::codegen::Target; @@ -265,6 +265,11 @@ pub(crate) struct Preprocessor { /// per call site. Lives in a `Cell` because the substitution /// path takes `&self`. pub(crate) counter: Cell, + /// First macro-expansion diagnostic of a substitution pass (C99 + /// 6.10.3p4 argument/parameter count mismatch). The substitution path + /// returns a `String`, so the error is parked here and drained by the + /// Result-returning caller (`process_named`, `eval_condition`). + pending_error: RefCell>, /// MSVC-style `#pragma warning(disable : N)` IDs currently /// suppressed. Push/pop variants nest via `warning_stack`. /// c5 doesn't number its own warnings, so the IDs in here @@ -348,6 +353,11 @@ impl Preprocessor { /// * `__BADC_TARGET__` -- the canonical target id (e.g. /// `"macos-aarch64"`), as a string literal. Used to gate /// target-specific code at the source level. + /// + /// Comparing these string-literal predefines with `#if X == "..."` + /// is a c5 extension over C99 6.10.1p4, which restricts a `#if` + /// controlling expression to an integer constant expression; see + /// std-conformance.md. /// * CPU-architecture macros, all defined to `1` when active so /// `#if __aarch64__` works the same way it does in gcc/clang: /// * AArch64 targets get `__aarch64__` and `__arm64__` (the @@ -562,6 +572,7 @@ impl Preprocessor { entrypoint: None, subsystem: None, counter: Cell::new(0), + pending_error: RefCell::new(None), warning_disabled: BTreeSet::new(), warning_stack: Vec::new(), warn_disabled: BTreeSet::new(), @@ -1301,6 +1312,8 @@ impl Preprocessor { } } + self.take_pending_error()?; + if !cond_stack.is_empty() { return Err(C5Error::Compile(crate::c5::error::fmt_internal_err( "preprocessor: unterminated `#if` / `#ifdef` block", @@ -1324,6 +1337,60 @@ impl Preprocessor { self.substitute_with_blocklist(line, filename, line_no, &Blocklist::Nil) } + /// Record the first macro-expansion diagnostic of a pass; later + /// errors are dropped so the earliest source-order one wins. + fn record_pp_error(&self, err: C5Error) { + let mut slot = self.pending_error.borrow_mut(); + if slot.is_none() { + *slot = Some(err); + } + } + + /// Drain any parked macro-expansion diagnostic. + fn take_pending_error(&self) -> Result<(), C5Error> { + match self.pending_error.borrow_mut().take() { + Some(e) => Err(e), + None => Ok(()), + } + } + + /// C99 6.10.3p4: the invocation's argument count must match the + /// macro's parameter count; record a diagnostic on a mismatch. + fn check_macro_arity( + &self, + name: &str, + def: &FnMacro, + args: &[String], + filename: &str, + line_no: usize, + ) { + if macro_arg_count_ok(def, args) { + return; + } + let want = def.params.len(); + let got = args.len(); + let plural = |n: usize| if n == 1 { "" } else { "s" }; + let msg = if def.is_variadic { + format!( + "macro `{name}` requires at least {want} argument{}, but {got} given", + plural(want) + ) + } else if got > want { + format!( + "macro `{name}` passed {got} argument{}, but takes just {want}", + plural(got) + ) + } else { + format!( + "macro `{name}` requires {want} argument{}, but only {got} given", + plural(want) + ) + }; + self.record_pp_error(C5Error::Compile(super::error::fmt_compile_err( + filename, line_no, &msg, + ))); + } + /// Process C99 6.10.9 `_Pragma` operators in already-macro-expanded /// text. `_Pragma ( string-literal )` is destringized (the optional /// `L` prefix and the surrounding quotes are removed, `\"` becomes @@ -1595,6 +1662,7 @@ impl Preprocessor { self.substitute_with_blocklist(a, filename, line_no, blocklist) }) .collect(); + self.check_macro_arity(ident, macro_def, &args, filename, line_no); let expanded = expand_fn_macro(macro_def, &expanded_args, &args); // C99 6.10.3.4 "blue paint": any macro that // fired during arg pre-expansion stays on @@ -1670,6 +1738,13 @@ impl Preprocessor { ) }) .collect(); + self.check_macro_arity( + trimmed, + inner_def, + &inner_args, + filename, + line_no, + ); let inner_body = expand_fn_macro(inner_def, &inner_expanded_args, &inner_args); let deeper = Blocklist::Cons(trimmed, blocklist); @@ -1739,6 +1814,9 @@ impl Preprocessor { ) }) .collect(); + self.check_macro_arity( + trimmed, macro_def, &args, filename, line_no, + ); let body_expanded = expand_fn_macro(macro_def, &expanded_args, &args); let deeper = Blocklist::Cons(trimmed, &nested); @@ -1932,6 +2010,7 @@ impl Preprocessor { // pre-pass that converts it to a literal 0/1 since // substitute would otherwise expand X away. let prepared = self.expand_for_if(expr, line_no); + self.take_pending_error()?; let mut p = IfExprParser::new(&prepared, self); let v = p.parse_ternary()?; p.skip_ws(); @@ -3888,20 +3967,38 @@ fn macro_call_unclosed( /// equality / inequality -- mixing them in arithmetic is rejected. #[derive(Debug, Clone)] enum IfValue { - Int(i64), + /// C99 6.10.1p4: `#if` operands evaluate in (u)intmax_t. `unsigned` + /// records the operand signedness so the right shift (6.5.7p5) and + /// the division / remainder (6.2.5) select the correct interpretation. + Int { + val: i64, + unsigned: bool, + }, Str(String), } impl IfValue { + fn signed(v: i64) -> IfValue { + IfValue::Int { + val: v, + unsigned: false, + } + } + fn with_sign(v: i64, unsigned: bool) -> IfValue { + IfValue::Int { val: v, unsigned } + } + fn is_unsigned(&self) -> bool { + matches!(self, IfValue::Int { unsigned: true, .. }) + } fn truthy(&self) -> bool { match self { - IfValue::Int(n) => *n != 0, + IfValue::Int { val, .. } => *val != 0, IfValue::Str(s) => !s.is_empty(), } } fn as_int(&self) -> i64 { match self { - IfValue::Int(n) => *n, + IfValue::Int { val, .. } => *val, IfValue::Str(s) => { // String coerced to int: 0 unless the bytes happen // to parse as a number. Real c programs rarely @@ -3945,6 +4042,11 @@ struct IfExprParser<'a> { /// cycle in the grammar passes through `parse_unary`, so the bound is /// checked there. depth: usize, + /// False while parsing a short-circuited (`&&`/`||`) or not-taken + /// (`?:`) subexpression. C99 6.6p4 forbids division by zero in a + /// constant expression, but an unevaluated operand must not trigger + /// the diagnostic (gcc/clang accept `1 ? 2 : 1/0`). + live: bool, } impl<'a> IfExprParser<'a> { @@ -3954,6 +4056,7 @@ impl<'a> IfExprParser<'a> { pos: 0, pp, depth: 0, + live: true, } } fn at_end(&self) -> bool { @@ -3998,8 +4101,11 @@ impl<'a> IfExprParser<'a> { loop { self.skip_ws(); if self.eat("||") { + let saved = self.live; + self.live = saved && !left.truthy(); let right = self.parse_and()?; - left = IfValue::Int((left.truthy() || right.truthy()) as i64); + self.live = saved; + left = IfValue::signed((left.truthy() || right.truthy()) as i64); } else { break; } @@ -4008,24 +4114,30 @@ impl<'a> IfExprParser<'a> { } /// C99 6.10.1p1 / 6.5.15: `#if` accepts a ternary at the top of - /// the expression precedence. `cond ? then : else` -- both - /// branches are evaluated unconditionally and the picked one is - /// returned (no short-circuit semantics inside a constant - /// expression). Right-associative, so the `else` arm recurses. + /// the expression precedence. `cond ? then : else` -- both arms are + /// parsed and the picked one is returned. The not-taken arm is parsed + /// with `live` cleared so a division by zero there is not diagnosed + /// (6.6p4 applies to the evaluated operand only). Right-associative, + /// so the `else` arm recurses. fn parse_ternary(&mut self) -> Result { let cond = self.parse_or()?; self.skip_ws(); if !self.eat_byte(b'?') { return Ok(cond); } + let saved = self.live; + self.live = saved && cond.truthy(); let then_v = self.parse_ternary()?; + self.live = saved; self.skip_ws(); if !self.eat_byte(b':') { return Err(C5Error::Compile( "preprocessor: missing `:` in `#if` ternary expression".to_string(), )); } + self.live = saved && !cond.truthy(); let else_v = self.parse_ternary()?; + self.live = saved; Ok(if cond.truthy() { then_v } else { else_v }) } @@ -4034,8 +4146,11 @@ impl<'a> IfExprParser<'a> { loop { self.skip_ws(); if self.eat("&&") { + let saved = self.live; + self.live = saved && left.truthy(); let right = self.parse_bitor()?; - left = IfValue::Int((left.truthy() && right.truthy()) as i64); + self.live = saved; + left = IfValue::signed((left.truthy() && right.truthy()) as i64); } else { break; } @@ -4054,7 +4169,8 @@ impl<'a> IfExprParser<'a> { { self.pos += 1; let right = self.parse_bitxor()?; - left = IfValue::Int(left.as_int() | right.as_int()); + let uns = left.is_unsigned() || right.is_unsigned(); + left = IfValue::with_sign(left.as_int() | right.as_int(), uns); } else { break; } @@ -4068,7 +4184,8 @@ impl<'a> IfExprParser<'a> { self.skip_ws(); if self.eat_byte(b'^') { let right = self.parse_bitand()?; - left = IfValue::Int(left.as_int() ^ right.as_int()); + let uns = left.is_unsigned() || right.is_unsigned(); + left = IfValue::with_sign(left.as_int() ^ right.as_int(), uns); } else { break; } @@ -4085,7 +4202,8 @@ impl<'a> IfExprParser<'a> { { self.pos += 1; let right = self.parse_eq()?; - left = IfValue::Int(left.as_int() & right.as_int()); + let uns = left.is_unsigned() || right.is_unsigned(); + left = IfValue::with_sign(left.as_int() & right.as_int(), uns); } else { break; } @@ -4099,10 +4217,10 @@ impl<'a> IfExprParser<'a> { self.skip_ws(); if self.eat("==") { let right = self.parse_rel()?; - left = IfValue::Int(if_value_eq(&left, &right) as i64); + left = IfValue::signed(if_value_eq(&left, &right) as i64); } else if self.eat("!=") { let right = self.parse_rel()?; - left = IfValue::Int(!if_value_eq(&left, &right) as i64); + left = IfValue::signed(!if_value_eq(&left, &right) as i64); } else { break; } @@ -4116,22 +4234,22 @@ impl<'a> IfExprParser<'a> { self.skip_ws(); if self.eat("<=") { let right = self.parse_shift()?; - left = IfValue::Int((left.as_int() <= right.as_int()) as i64); + left = IfValue::signed(!if_value_lt(&right, &left) as i64); } else if self.eat(">=") { let right = self.parse_shift()?; - left = IfValue::Int((left.as_int() >= right.as_int()) as i64); + left = IfValue::signed(!if_value_lt(&left, &right) as i64); } else if self.peek_byte() == Some(b'<') && self.src.as_bytes().get(self.pos + 1) != Some(&b'<') { self.pos += 1; let right = self.parse_shift()?; - left = IfValue::Int((left.as_int() < right.as_int()) as i64); + left = IfValue::signed(if_value_lt(&left, &right) as i64); } else if self.peek_byte() == Some(b'>') && self.src.as_bytes().get(self.pos + 1) != Some(&b'>') { self.pos += 1; let right = self.parse_shift()?; - left = IfValue::Int((left.as_int() > right.as_int()) as i64); + left = IfValue::signed(if_value_lt(&right, &left) as i64); } else { break; } @@ -4145,31 +4263,28 @@ impl<'a> IfExprParser<'a> { self.skip_ws(); if self.eat("<<") { let right = self.parse_addsub()?; - // Use the wrapping bit-pattern shift so a left - // shift past bit 63 doesn't panic. The - // preprocessor stores both signed and unsigned - // values in `i64` per 6.10.1p4 widest-integer - // semantics; left shift on either type is - // bit-pattern identical. + // Left shift is bit-pattern identical for signed and + // unsigned operands; the wrapping form avoids a panic + // past bit 63. The result keeps the left operand's sign. let shift = (right.as_int() & 63) as u32; let n = (left.as_int() as u64).wrapping_shl(shift) as i64; - left = IfValue::Int(n); + left = IfValue::with_sign(n, left.is_unsigned()); } else if self.eat(">>") { let right = self.parse_addsub()?; - // C99 6.5.7p5: the right shift of a signed - // negative value is implementation-defined. - // c5's preprocessor uses logical (zero-fill) - // shift in both modes so that bit-pattern - // literals like `ULONG_MAX >> 31` -- where - // ULONG_MAX is stored as the wrap of `u64::MAX` - // -- yield their unsigned answer rather than - // the arithmetic-shift `-1`. The - // `((ULONG_MAX >> 31) >> 31) == 3` 64-bit-host - // probe shape standard in C library headers - // relies on the unsigned interpretation. + // C99 6.5.7p5: right shift of a signed value propagates + // the sign (arithmetic); an unsigned operand zero-fills + // (logical). Tracking the operand sign lets `-2 >> 1` + // yield -1 while an unsigned bit-pattern literal such as + // the `((SIZE_MAX >> 31) >> 31) == 3` probe still yields + // its zero-filled result. let shift = (right.as_int() & 63) as u32; - let n = (left.as_int() as u64).wrapping_shr(shift) as i64; - left = IfValue::Int(n); + let uns = left.is_unsigned(); + let n = if uns { + (left.as_int() as u64).wrapping_shr(shift) as i64 + } else { + left.as_int().wrapping_shr(shift) + }; + left = IfValue::with_sign(n, uns); } else { break; } @@ -4183,10 +4298,12 @@ impl<'a> IfExprParser<'a> { self.skip_ws(); if self.eat_byte(b'+') { let right = self.parse_muldiv()?; - left = IfValue::Int(left.as_int().wrapping_add(right.as_int())); + let uns = left.is_unsigned() || right.is_unsigned(); + left = IfValue::with_sign(left.as_int().wrapping_add(right.as_int()), uns); } else if self.eat_byte(b'-') { let right = self.parse_muldiv()?; - left = IfValue::Int(left.as_int().wrapping_sub(right.as_int())); + let uns = left.is_unsigned() || right.is_unsigned(); + left = IfValue::with_sign(left.as_int().wrapping_sub(right.as_int()), uns); } else { break; } @@ -4200,15 +4317,18 @@ impl<'a> IfExprParser<'a> { self.skip_ws(); if self.eat_byte(b'*') { let right = self.parse_unary()?; - left = IfValue::Int(left.as_int().wrapping_mul(right.as_int())); + let uns = left.is_unsigned() || right.is_unsigned(); + left = IfValue::with_sign(left.as_int().wrapping_mul(right.as_int()), uns); } else if self.eat_byte(b'/') { let right = self.parse_unary()?; + let uns = left.is_unsigned() || right.is_unsigned(); let r = right.as_int(); - left = IfValue::Int(if r == 0 { 0 } else { left.as_int() / r }); + left = IfValue::with_sign(self.div_or_diag(left.as_int(), r, uns, false)?, uns); } else if self.eat_byte(b'%') { let right = self.parse_unary()?; + let uns = left.is_unsigned() || right.is_unsigned(); let r = right.as_int(); - left = IfValue::Int(if r == 0 { 0 } else { left.as_int() % r }); + left = IfValue::with_sign(self.div_or_diag(left.as_int(), r, uns, true)?, uns); } else { break; } @@ -4216,6 +4336,30 @@ impl<'a> IfExprParser<'a> { Ok(left) } + /// C99 6.6p4: a constant expression with a zero divisor is not a + /// valid constant expression. Diagnose it when the operand is + /// evaluated (`live`); a short-circuited or not-taken zero divisor + /// keeps folding to 0. `rem` selects remainder over division; + /// `unsigned` selects the unsigned interpretation (6.3.1.8). + fn div_or_diag(&self, lhs: i64, rhs: i64, unsigned: bool, rem: bool) -> Result { + if rhs == 0 { + if self.live { + return Err(C5Error::Compile( + "preprocessor: division by zero in `#if` expression".to_string(), + )); + } + return Ok(0); + } + Ok(if unsigned { + let (a, b) = (lhs as u64, rhs as u64); + (if rem { a % b } else { a / b }) as i64 + } else if rem { + lhs.wrapping_rem(rhs) + } else { + lhs.wrapping_div(rhs) + }) + } + fn parse_unary(&mut self) -> Result { // Every recursive cycle (parentheses through the precedence // cascade, ternary arms, and unary chains) reaches `parse_unary`, @@ -4236,15 +4380,18 @@ impl<'a> IfExprParser<'a> { self.skip_ws(); if self.eat_byte(b'!') { let v = self.parse_unary()?; - return Ok(IfValue::Int((!v.truthy()) as i64)); + return Ok(IfValue::signed((!v.truthy()) as i64)); } if self.eat_byte(b'~') { let v = self.parse_unary()?; - return Ok(IfValue::Int(!v.as_int())); + return Ok(IfValue::with_sign(!v.as_int(), v.is_unsigned())); } if self.eat_byte(b'-') { let v = self.parse_unary()?; - return Ok(IfValue::Int(-v.as_int())); + return Ok(IfValue::with_sign( + v.as_int().wrapping_neg(), + v.is_unsigned(), + )); } if self.eat_byte(b'+') { return self.parse_unary(); @@ -4300,7 +4447,7 @@ impl<'a> IfExprParser<'a> { while let Some(b) = self.peek_byte() { if b == b'\'' { self.pos += 1; - return Ok(IfValue::Int(acc)); + return Ok(IfValue::signed(acc)); } if b == b'\\' && self.pos + 1 < bytes.len() { self.pos += 2; @@ -4374,9 +4521,11 @@ impl<'a> IfExprParser<'a> { self.pos += 1; } // Eat any integer suffix (uUlL combinations) without - // touching the value. + // touching the value; a u/U suffix marks the literal unsigned. + let mut has_u = false; while let Some(b) = self.peek_byte() { if matches!(b, b'u' | b'U' | b'l' | b'L') { + has_u |= matches!(b, b'u' | b'U'); self.pos += 1; } else { break; @@ -4400,18 +4549,22 @@ impl<'a> IfExprParser<'a> { } else { (body.trim_start_matches('0'), radix) }; + // 6.10.1p4: intmax_t is the signed widest type. A literal that + // overflows it but fits u64 takes the unsigned interpretation, + // matching gcc/clang and keeping the unsigned bit-pattern probes + // (ULONG_MAX / UINT64_MAX) logical-shifting correctly. let v = if digits.is_empty() { - Ok(0i64) + Ok((0i64, has_u)) } else if let Ok(signed) = i64::from_str_radix(digits, raw_radix) { - Ok(signed) + Ok((signed, has_u)) } else if let Ok(unsigned) = u64::from_str_radix(digits, raw_radix) { - Ok(unsigned as i64) + Ok((unsigned as i64, true)) } else { Err(()) }; let _ = body_start; match v { - Ok(n) => Ok(IfValue::Int(n)), + Ok((n, uns)) => Ok(IfValue::with_sign(n, uns)), Err(()) => Err(C5Error::Compile(alloc::format!( "preprocessor: malformed integer literal {body:?} in `#if`", ))), @@ -4455,7 +4608,7 @@ impl<'a> IfExprParser<'a> { )); } } - return Ok(IfValue::Int(self.pp.macros.contains_key(&id) as i64)); + return Ok(IfValue::signed(self.pp.macros.contains_key(&id) as i64)); } // C23 6.10.1 / universal compiler practice: `__has_include()` // and `__has_include("h")` evaluate to 1 when the header is @@ -4496,7 +4649,7 @@ impl<'a> IfExprParser<'a> { )); } let found = self.pp.find_include(&header, None).is_some(); - return Ok(IfValue::Int(found as i64)); + return Ok(IfValue::signed(found as i64)); } // Identifier -- look up in the macro table. Function-like // macros are skipped (they need an argument list which the @@ -4509,7 +4662,7 @@ impl<'a> IfExprParser<'a> { } // Numeric? Try parsing. if let Ok(n) = value.parse::() { - return Ok(IfValue::Int(n)); + return Ok(IfValue::signed(n)); } // The macro might itself be a name; recursively expand // (bounded) and try once more. The bare-identifier case @@ -4518,19 +4671,20 @@ impl<'a> IfExprParser<'a> { // string-shaped value. let expanded = self.pp.expand_or_self(name); if let Ok(n) = expanded.parse::() { - return Ok(IfValue::Int(n)); + return Ok(IfValue::signed(n)); } return Ok(IfValue::Str(expanded)); } - Ok(IfValue::Int(0)) + Ok(IfValue::signed(0)) } } fn if_value_eq(a: &IfValue, b: &IfValue) -> bool { match (a, b) { - (IfValue::Int(x), IfValue::Int(y)) => x == y, + (IfValue::Int { val: x, .. }, IfValue::Int { val: y, .. }) => x == y, (IfValue::Str(x), IfValue::Str(y)) => x == y, - (IfValue::Int(x), IfValue::Str(y)) | (IfValue::Str(y), IfValue::Int(x)) => { + (IfValue::Int { val: x, .. }, IfValue::Str(y)) + | (IfValue::Str(y), IfValue::Int { val: x, .. }) => { // Mixed: prefer int interpretation if the string parses, // else compare numerically with 0. y.trim_matches('"').parse::().ok() == Some(*x) @@ -4538,6 +4692,17 @@ fn if_value_eq(a: &IfValue, b: &IfValue) -> bool { } } +/// C99 6.3.1.8 usual arithmetic conversions: `a < b` compares unsigned +/// when either operand is unsigned, signed otherwise. Strings coerce to +/// their signed `as_int` value (0 unless numeric). +fn if_value_lt(a: &IfValue, b: &IfValue) -> bool { + if a.is_unsigned() || b.is_unsigned() { + (a.as_int() as u64) < (b.as_int() as u64) + } else { + a.as_int() < b.as_int() + } +} + /// True when a body identifier names the variadic tail: the standard /// `__VA_ARGS__`, or the GCC named-rest parameter (`#define foo(rest...)` /// reaches the tail through `rest`). @@ -4545,6 +4710,21 @@ fn is_va_token(def: &FnMacro, word: &str) -> bool { word == "__VA_ARGS__" || def.va_name.as_deref() == Some(word) } +/// C99 6.10.3p4. A variadic macro's `...` absorbs any surplus, so the +/// invocation needs at least as many arguments as named parameters. A +/// zero-parameter macro invoked as `NAME()` supplies a single empty +/// argument, which counts as zero (the single-empty-argument rule). +fn macro_arg_count_ok(def: &FnMacro, args: &[String]) -> bool { + let params = def.params.len(); + if def.is_variadic { + args.len() >= params + } else if params == 0 { + args.len() == 1 && args[0].is_empty() + } else { + args.len() == params + } +} + fn expand_fn_macro(def: &FnMacro, args: &[String], raw_args: &[String]) -> String { // Pre-compute the comma-joined string for __VA_ARGS__. Empty when // the macro isn't variadic or no trailing args were supplied. The @@ -4728,6 +4908,80 @@ mod tests { pp.process(source).expect("preprocessor failed") } + fn process_err(source: &str) -> String { + let mut pp = Preprocessor::new("macos-aarch64", Target::MacOSAarch64, "0.1.0"); + match pp.process(source) { + Ok(out) => panic!("expected error, got: {out}"), + Err(e) => format!("{e}"), + } + } + + #[test] + fn if_signed_right_shift_is_arithmetic() { + for e in ["(-2 >> 1) == -1", "(-1 >> 1) == -1", "(-8 >> 2) == -2"] { + let out = process(&format!("#if {e}\nTAKEN\n#else\nNOT\n#endif\n")); + assert!(out.contains("TAKEN"), "{e}: {out}"); + } + // Unsigned operands keep the logical (zero-fill) shift: an + // over-i64 literal and a `u`-suffixed literal are both unsigned. + let out = process("#if ((18446744073709551615 >> 31) >> 31) == 3\nY\n#else\nN\n#endif\n"); + assert!(out.contains('Y'), "{out}"); + let out = process("#if (0xFFFFFFFFFFFFFFFFu >> 63) == 1\nY\n#else\nN\n#endif\n"); + assert!(out.contains('Y'), "{out}"); + } + + #[test] + fn if_division_by_zero_is_error() { + for src in [ + "#if 1/0\nX\n#endif\n", + "#if 1%0\nX\n#endif\n", + "#if 3/(2-2)\nX\n#endif\n", + ] { + assert!( + process_err(src).contains("division by zero"), + "expected division-by-zero error: {src}" + ); + } + // A short-circuited or not-taken zero divisor is unevaluated and + // must not be diagnosed (gcc/clang accept these). + for src in [ + "#if 1 ? 2 : 1/0\nX\n#endif\n", + "#if 1 || 1/0\nX\n#endif\n", + "#if 0 && 1/0\nZ\n#endif\n", + ] { + let mut pp = Preprocessor::new("macos-aarch64", Target::MacOSAarch64, "0.1.0"); + assert!(pp.process(src).is_ok(), "dead branch must not error: {src}"); + } + } + + #[test] + fn if_string_literal_equality_extension() { + // c5 extension over C99 6.10.1p4: string-literal `==` / `!=`. + let out = process("#if __BADC_TARGET__ == \"macos-aarch64\"\nT\n#else\nF\n#endif\n"); + assert!(out.contains('T'), "{out}"); + let out = process("#if __BADC_VERSION__ == \"0.1.0\"\nT\n#else\nF\n#endif\n"); + assert!(out.contains('T'), "{out}"); + let out = process("#if __BADC_TARGET__ != \"win-x64\"\nT\n#else\nF\n#endif\n"); + assert!(out.contains('T'), "{out}"); + } + + #[test] + fn function_like_macro_arity_is_checked() { + // C99 6.10.3p4: argument count must match parameter count. + assert!(process_err("#define ID(x) (x)\nint a = ID(1, 2);\n").contains("ID")); + assert!(process_err("#define ADD(a,b) ((a)+(b))\nint x = ADD(1);\n").contains("ADD")); + assert!(process_err("#define FOO() 42\nint x = FOO(1);\n").contains("FOO")); + // A fixed parameter with no argument is rejected even for a + // variadic macro. + assert!(process_err("#define F(a, b, ...) g(a, b)\nint x = F(1);\n").contains('F')); + // Valid arities: zero-param called empty, variadic empty tail, + // variadic surplus absorbed. + assert!(process("#define FOO() 42\nint x = FOO();\n").contains("42")); + let out = + process("#define LOG(fmt, ...) f(fmt, __VA_ARGS__)\nLOG(\"a\");\nLOG(\"a\", 1, 2);\n"); + assert!(out.contains("f("), "{out}"); + } + #[test] fn predefined_macros_expand() { let out = process("char *t = __BADC_TARGET__;\nchar *v = __BADC_VERSION__;\n"); From 67c33e493900faca565d6c52ee08d9883133a93d Mon Sep 17 00:00:00 2001 From: kromych Date: Wed, 1 Jul 2026 12:06:32 -0700 Subject: [PATCH 02/15] lexer: UTF-16 surrogate pairs for wide strings, reject digitless hex 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) --- src/c5/lexer.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/src/c5/lexer.rs b/src/c5/lexer.rs index ec9c5c6a9..47f97a6d2 100644 --- a/src/c5/lexer.rs +++ b/src/c5/lexer.rs @@ -624,10 +624,22 @@ impl Lexer { } if quote == b'"' { // A wide string stores one code point per element at - // the target's `wchar_t` width (4 bytes on Unix, 2 - // on Windows / UTF-16). - for k in 0..self.wchar_bytes { - data.push((val >> (k * 8)) as u8); + // the target's `wchar_t` width (4 bytes on Unix, 2 on + // Windows / UTF-16). A UTF-16 element cannot hold a + // code point above U+FFFF; C99 6.4.5 requires the + // surrogate-pair encoding (Unicode 3.9 D91). + if self.wchar_bytes == 2 && (0x10000..=0x10FFFF).contains(&val) { + let v = val - 0x10000; + let hi = 0xD800 + (v >> 10); + let lo = 0xDC00 + (v & 0x3FF); + data.push(hi as u8); + data.push((hi >> 8) as u8); + data.push(lo as u8); + data.push((lo >> 8) as u8); + } else { + for k in 0..self.wchar_bytes { + data.push((val >> (k * 8)) as u8); + } } } else { char_value = val; @@ -1155,6 +1167,15 @@ impl Lexer { self.tk = Tok(Token::FloatNum as i64); return self.end_number(); } + // C99 6.4.4.1: at least one hex digit must follow + // `0x`/`0X`. Checked after the hex-float branch so a + // digit-less integer part of a hex float (`0x.5p0`) + // stays valid. + if self.pos == hex_body_start { + return Err(C5Error::Compile(crate::c5::error::fmt_internal_err( + &format!("{}: hex literal `0x` has no digits", self.line), + ))); + } // Hex literals can carry the standard integer suffix // letters (u/U/l/L plus ll/LL combinations such as // 0xFFFFULL). Per C99 6.4.4.1 record the longness @@ -2161,6 +2182,42 @@ mod tests { ); } + #[test] + fn wide_string_astral_point_encodes_utf16_surrogate_pair_on_windows() { + // U+1F600 with a 2-byte wchar_t -> D83D DE00 (LE) + 2-byte nul. + assert_eq!( + lex_string_literal_w("L\"\u{1F600}\"", 2), + vec![0x3D, 0xD8, 0x00, 0xDE, 0, 0] + ); + // A 4-byte wchar_t keeps the single UTF-32 element (no surrogates). + assert_eq!( + lex_string_literal("L\"\u{1F600}\""), + vec![0x00, 0xF6, 0x01, 0x00, 0, 0, 0, 0] + ); + } + + #[test] + fn hex_prefix_without_digits_is_an_error() { + // C99 6.4.4.1: `0x`/`0X` must be followed by at least one digit. + for src in ["0x", "0X", "0xg", "int x = 0x;"] { + let mut lex = Lexer::new(src.to_string()); + let mut symbols: Vec = Vec::new(); + let mut index = SymbolIndex::new(); + let mut data: Vec = Vec::new(); + let mut saw_err = false; + loop { + if lex.next(&mut symbols, &mut index, &mut data).is_err() { + saw_err = true; + break; + } + if lex.tk == 0 { + break; + } + } + assert!(saw_err, "expected error for {src:?}"); + } + } + #[test] fn wide_string_literal_absorbs_adjacent_wide_chunks() { // The lexer concatenates adjacent `L"..."` literals into one From dd6049a53dd84236d8a8f2f3ba12d2778d20050e Mon Sep 17 00:00:00 2001 From: kromych Date: Wed, 1 Jul 2026 12:06:54 -0700 Subject: [PATCH 03/15] vm: write printf %s/%c bytes without Latin-1 re-encoding 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 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) --- src/c5/vm/ssa.rs | 100 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 30 deletions(-) diff --git a/src/c5/vm/ssa.rs b/src/c5/vm/ssa.rs index 0f94eab7f..da40d3fff 100644 --- a/src/c5/vm/ssa.rs +++ b/src/c5/vm/ssa.rs @@ -1292,9 +1292,9 @@ fn dispatch_callext( "vm_ssa: printf: bad fmt addr 0x{fmt_addr:x}", ))); } - let fmt = read_cstring(mem, fmt_addr as usize)?; + let fmt = read_cstring_bytes(mem, fmt_addr as usize)?; let out = format_printf(&fmt, &args[1..], mem)?; - let _ = host.write(1, out.as_bytes()); + let _ = host.write(1, &out); Ok(0) } // `int putchar(int c)` -- write one byte to stdout, returns @@ -1429,11 +1429,12 @@ fn dispatch_callext( /// Read a NUL-terminated C string out of memory. Bounds-checks /// up to the data segment / stack / heap end so a stray pointer /// returns a runtime error rather than reading past the buffer. -fn read_cstring(mem: &Memory, addr: usize) -> Result { - let mut s = alloc::string::String::new(); +/// Read the NUL-terminated C string at `addr` as raw bytes. A C string +/// is an arbitrary byte sequence; the caller decides how to interpret +/// bytes >= 0x80 rather than forcing a Latin-1 -> UTF-8 round-trip. +fn read_cstring_bytes(mem: &Memory, addr: usize) -> Result, C5Error> { let mut i = addr; while i < mem.bytes.len() && mem.bytes[i] != 0 { - s.push(mem.bytes[i] as char); i += 1; } if i >= mem.bytes.len() { @@ -1441,7 +1442,15 @@ fn read_cstring(mem: &Memory, addr: usize) -> Result= 0x80 that are not valid UTF-8 become U+FFFD, giving defined +/// behavior rather than the prior plausible-but-wrong Latin-1 mapping. +fn read_cstring(mem: &Memory, addr: usize) -> Result { + let bytes = read_cstring_bytes(mem, addr)?; + Ok(alloc::string::String::from_utf8_lossy(&bytes).into_owned()) } /// Minimal printf formatter. Walks `fmt`'s `%c / %d / %u / %x / @@ -1449,21 +1458,25 @@ fn read_cstring(mem: &Memory, addr: usize) -> Result Result { +fn format_printf(fmt: &[u8], args: &[i64], mem: &Memory) -> Result, C5Error> { use core::fmt::Write; - let mut out = alloc::string::String::new(); + let mut out: Vec = Vec::new(); + let mut num = alloc::string::String::new(); let mut arg_idx = 0usize; - let mut chars = fmt.chars(); - while let Some(c) = chars.next() { - if c != '%' { + let mut i = 0usize; + while i < fmt.len() { + let c = fmt[i]; + i += 1; + if c != b'%' { out.push(c); continue; } - let Some(spec) = chars.next() else { + let Some(&spec) = fmt.get(i) else { break; }; - if spec == '%' { - out.push('%'); + i += 1; + if spec == b'%' { + out.push(b'%'); continue; } let Some(val) = args.get(arg_idx).copied() else { @@ -1474,37 +1487,47 @@ fn format_printf(fmt: &str, args: &[i64], mem: &Memory) -> Result { - let _ = write!(out, "{}", val as i32 as i64); + b'd' | b'i' => { + num.clear(); + let _ = write!(num, "{}", val as i32 as i64); + out.extend_from_slice(num.as_bytes()); } - 'u' => { - let _ = write!(out, "{}", val as u32 as u64); + b'u' => { + num.clear(); + let _ = write!(num, "{}", val as u32 as u64); + out.extend_from_slice(num.as_bytes()); } - 'x' => { - let _ = write!(out, "{:x}", val as u32); + b'x' => { + num.clear(); + let _ = write!(num, "{:x}", val as u32); + out.extend_from_slice(num.as_bytes()); } - 'X' => { - let _ = write!(out, "{:X}", val as u32); + b'X' => { + num.clear(); + let _ = write!(num, "{:X}", val as u32); + out.extend_from_slice(num.as_bytes()); } - 'c' => { - out.push(val as u8 as char); + b'c' => { + out.push(val as u8); } - 's' => { + b's' => { if val < 0 { return Err(C5Error::Runtime(format!( "vm_ssa: printf %s: bad ptr 0x{val:x}", ))); } - let s = read_cstring(mem, val as usize)?; - out.push_str(&s); + let s = read_cstring_bytes(mem, val as usize)?; + out.extend_from_slice(&s); } - 'p' => { - let _ = write!(out, "0x{:x}", val as u64); + b'p' => { + num.clear(); + let _ = write!(num, "0x{:x}", val as u64); + out.extend_from_slice(num.as_bytes()); } other => { // Unrecognised conversion: emit the literal // `%X` so the caller sees what was wrong. - out.push('%'); + out.push(b'%'); out.push(other); } } @@ -1956,6 +1979,23 @@ mod tests { use super::*; use crate::Compiler; + #[test] + fn printf_string_preserves_high_bytes() { + // 0xC3 0xA9 (the UTF-8 encoding of U+00E9) must reach stdout as + // those two raw bytes, not the 4-byte Latin-1 -> UTF-8 re-encoding + // the old path produced. + let mem = Memory::new(b"\xC3\xA9\x00"); + let out = format_printf(b"%s", &[0], &mem).expect("format"); + assert_eq!(out, alloc::vec![0xC3u8, 0xA9]); + } + + #[test] + fn printf_char_preserves_high_byte() { + let mem = Memory::new(b"\x00"); + let out = format_printf(b"%c", &[0x80], &mem).expect("format"); + assert_eq!(out, alloc::vec![0x80u8]); + } + fn ssa_main_of(src: &str) -> FunctionSsa { let program = Compiler::new(src.to_string()) .compile() From 11530f14220d7cc004e166aeee0d6b7bddee6738 Mon Sep 17 00:00:00 2001 From: kromych Date: Wed, 1 Jul 2026 12:07:16 -0700 Subject: [PATCH 04/15] mem2reg: leave read-before-write slots in memory, drop orphan phis 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) --- src/c5/codegen/ssa/mem2reg.rs | 83 ++ tests/snapshots/asm/c4.aarch64.asm | 389 +++++---- tests/snapshots/asm/c4.x64.asm | 460 +++++------ tests/snapshots/ssa/c4.ssa | 1180 ++++++++++++++-------------- 4 files changed, 1049 insertions(+), 1063 deletions(-) diff --git a/src/c5/codegen/ssa/mem2reg.rs b/src/c5/codegen/ssa/mem2reg.rs index 8d153557f..645016e09 100644 --- a/src/c5/codegen/ssa/mem2reg.rs +++ b/src/c5/codegen/ssa/mem2reg.rs @@ -844,6 +844,21 @@ pub(crate) fn run(func: &mut FunctionSsa) -> Vec { if promotable.is_empty() { return Vec::new(); } + // A slot live-in to the entry block (block 0) is read on some path + // before any store defines it (C99 6.2.4: an indeterminate value). + // It has no reaching definition on that path, so promoting it would + // inject a join phi with an undefined predecessor edge -- a dead phi + // that still holds a register and drives a predecessor-exit move. + // Exclude such slots so they stay entirely in memory; this is the + // up-front form of the rename's `failed` detection. + let entry_live_in = slot_live_in_sets(func, &promotable)[0].clone(); + let promotable: BTreeSet = promotable + .into_iter() + .filter(|s| !entry_live_in.contains(s)) + .collect(); + if promotable.is_empty() { + return Vec::new(); + } // Write-only address-free slots: every store is dead per C99 // 6.2.4p2 (the slot's value is never observed). Neutralise each // such store to `Inst::Imm(0)`; the dead-pure check in the emit @@ -1958,6 +1973,74 @@ mod tests { ); } + #[test] + #[cfg(feature = "std")] + fn read_before_write_slot_is_left_in_memory_without_orphan_phi() { + // `int f(int c){ int x; if(c) x=7; return x+1; }`: slot -1 is + // stored only on the taken arm but read unconditionally, so it + // is live-in to the entry block. It must not be promoted; no + // Inst::Phi may be injected, and its load/store stay resident. + let insts = alloc::vec![ + Inst::Imm(0), // block 0: cond + Inst::Imm(7), // block 1 + Inst::StoreLocal { + off: -1, + value: 1, + kind: StoreKind::I64, + }, + Inst::LoadLocal { + off: -1, + kind: LoadKind::I64, + }, // block 2 + ]; + let blocks = alloc::vec![ + Block { + start_pc: 0, + inst_range: 0..1, + terminator: Terminator::Bz { + cond: 0, + target: 2, + fall_through: 1, + }, + exit_acc: 0, + }, + Block { + start_pc: 0, + inst_range: 1..3, + terminator: Terminator::Jmp(2), + exit_acc: NO_VALUE, + }, + Block { + start_pc: 0, + inst_range: 3..4, + terminator: Terminator::Return(3), + exit_acc: 3, + }, + ]; + let mut f = func_with(insts, blocks); + let promoted = with_phi_promote_override(true, || run(&mut f)); + assert!( + !promoted.contains(&-1), + "read-before-write slot must not be promoted" + ); + assert!( + !f.insts.iter().any(|i| matches!(i, Inst::Phi { .. })), + "no orphan phi may remain for the failed slot" + ); + assert!( + f.insts + .iter() + .any(|i| matches!(i, Inst::StoreLocal { off: -1, .. })), + "the store must stay resident in memory" + ); + assert!( + f.insts + .iter() + .any(|i| matches!(i, Inst::LoadLocal { off: -1, .. })), + "the load must stay resident in memory" + ); + } + #[test] fn predecessors_invert_terminator_successors() { // Block 0 conditionally branches to 1 (target) / 2 diff --git a/tests/snapshots/asm/c4.aarch64.asm b/tests/snapshots/asm/c4.aarch64.asm index a51827302..8436ad765 100644 --- a/tests/snapshots/asm/c4.aarch64.asm +++ b/tests/snapshots/asm/c4.aarch64.asm @@ -4329,11 +4329,11 @@ Disassembly of section .text: str x21, [x1] sub x1, x1, #0x8 str x22, [x1] - sub x22, x1, #0x8 - str x0, [x22] + sub x21, x1, #0x8 + str x0, [x21] mov x20, #0x0 // =0 - add x24, x25, #0x8 - ldr x25, [x25] + add x22, x25, #0x8 + ldr x24, [x25] add x20, x20, #0x1 adrp x0, add x0, x0, @@ -4358,20 +4358,20 @@ Disassembly of section .text: adrp x1, add x1, x1, mov x17, #0x5 // =5 - mul x2, x25, x17 + mul x2, x24, x17 add x2, x1, x2 mov x1, x20 bl sxtw x0, w0 - cmp x25, #0x7 + cmp x24, #0x7 b.gt b - cmp x25, #0x0 + cmp x24, #0x0 b.ne b adrp x0, add x0, x0, - ldr x1, [x24] + ldr x1, [x22] bl sxtw x0, w0 b @@ -4380,300 +4380,297 @@ Disassembly of section .text: bl sxtw x0, w0 b - add x25, x24, #0x8 - ldr x0, [x24] + add x25, x22, #0x8 + ldr x0, [x22] lsl x0, x0, #3 - add x21, x23, x0 - stur x21, [x29, #-0x48] + add x0, x23, x0 + stur x0, [x29, #-0x48] b - cmp x25, #0x1 + cmp x24, #0x1 b.ne - add x25, x24, #0x8 - ldr x21, [x24] - stur x21, [x29, #-0x48] + add x25, x22, #0x8 + ldr x0, [x22] + stur x0, [x29, #-0x48] b - cmp x25, #0x2 + cmp x24, #0x2 b.ne - ldr x25, [x24] + ldr x25, [x22] b - cmp x25, #0x3 + cmp x24, #0x3 b.ne - sub x22, x22, #0x8 - add x0, x24, #0x8 - str x0, [x22] - ldr x25, [x24] + sub x21, x21, #0x8 + add x0, x22, #0x8 + str x0, [x21] + ldr x25, [x22] b - cmp x25, #0x4 + cmp x24, #0x4 b.ne ldur x0, [x29, #-0x48] cbz x0, b b - cmp x25, #0x5 + cmp x24, #0x5 b.ne b - add x25, x24, #0x8 + add x25, x22, #0x8 b - ldr x25, [x24] + ldr x25, [x22] b ldur x0, [x29, #-0x48] cbz x0, b b - cmp x25, #0x6 + cmp x24, #0x6 b.ne b - ldr x25, [x24] + ldr x25, [x22] b - add x25, x24, #0x8 + add x25, x22, #0x8 b - sub x0, x22, #0x8 + sub x0, x21, #0x8 str x23, [x0] - add x25, x24, #0x8 - ldr x1, [x24] + add x25, x22, #0x8 + ldr x1, [x22] lsl x1, x1, #3 - sub x22, x0, x1 + sub x21, x0, x1 mov x23, x0 b - cmp x25, #0x7 + cmp x24, #0x7 b.ne - add x25, x24, #0x8 - ldr x0, [x24] + add x25, x22, #0x8 + ldr x0, [x22] lsl x0, x0, #3 - add x22, x22, x0 + add x21, x21, x0 b - cmp x25, #0x8 + cmp x24, #0x8 b.ne add x0, x23, #0x8 ldr x23, [x23] - add x22, x0, #0x8 + add x21, x0, #0x8 ldr x25, [x0] b - cmp x25, #0x9 + cmp x24, #0x9 b.ne ldur x0, [x29, #-0x48] - ldr x21, [x0] - stur x21, [x29, #-0x48] - mov x25, x24 + ldr x0, [x0] + stur x0, [x29, #-0x48] + mov x25, x22 b - cmp x25, #0xa + cmp x24, #0xa b.ne ldur x0, [x29, #-0x48] - ldrb w21, [x0] - stur x21, [x29, #-0x48] + ldrb w0, [x0] + stur x0, [x29, #-0x48] b - cmp x25, #0xb + cmp x24, #0xb b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] str x2, [x1] - mov x22, x0 + mov x21, x0 b - cmp x25, #0xc + cmp x24, #0xc b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] strb w2, [x1] mov x17, #0xff // =255 - and x21, x2, x17 - stur x21, [x29, #-0x48] - mov x22, x0 + and x1, x2, x17 + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0xd + cmp x24, #0xd b.ne - sub x22, x22, #0x8 + sub x21, x21, #0x8 ldur x0, [x29, #-0x48] - str x0, [x22] + str x0, [x21] b - cmp x25, #0xe + cmp x24, #0xe b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] - orr x21, x1, x2 - stur x21, [x29, #-0x48] - mov x22, x0 + orr x1, x1, x2 + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0xf + cmp x24, #0xf b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] - eor x21, x1, x2 - stur x21, [x29, #-0x48] - mov x22, x0 + eor x1, x1, x2 + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0x10 + cmp x24, #0x10 b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] - and x21, x1, x2 - stur x21, [x29, #-0x48] - mov x22, x0 + and x1, x1, x2 + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0x11 + cmp x24, #0x11 b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] cmp x1, x2 - cset x21, eq - stur x21, [x29, #-0x48] - mov x22, x0 + cset x1, eq + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0x12 + cmp x24, #0x12 b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] cmp x1, x2 - cset x21, ne - stur x21, [x29, #-0x48] - mov x22, x0 + cset x1, ne + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0x13 + cmp x24, #0x13 b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] cmp x1, x2 - cset x21, lt - stur x21, [x29, #-0x48] - mov x22, x0 + cset x1, lt + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0x14 + cmp x24, #0x14 b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] cmp x1, x2 - cset x21, gt - stur x21, [x29, #-0x48] - mov x22, x0 + cset x1, gt + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0x15 + cmp x24, #0x15 b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] cmp x1, x2 - cset x21, le - stur x21, [x29, #-0x48] - mov x22, x0 + cset x1, le + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0x16 + cmp x24, #0x16 b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] cmp x1, x2 - cset x21, ge - stur x21, [x29, #-0x48] - mov x22, x0 + cset x1, ge + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0x17 + cmp x24, #0x17 b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] - lsl x21, x1, x2 - stur x21, [x29, #-0x48] - mov x22, x0 + lsl x1, x1, x2 + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0x18 + cmp x24, #0x18 b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] - asr x21, x1, x2 - stur x21, [x29, #-0x48] - mov x22, x0 + asr x1, x1, x2 + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0x19 + cmp x24, #0x19 b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] - add x21, x1, x2 - stur x21, [x29, #-0x48] - mov x22, x0 + add x1, x1, x2 + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0x1a + cmp x24, #0x1a b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] - sub x21, x1, x2 - stur x21, [x29, #-0x48] - mov x22, x0 + sub x1, x1, x2 + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0x1b + cmp x24, #0x1b b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] - mul x21, x1, x2 - stur x21, [x29, #-0x48] - mov x22, x0 + mul x1, x1, x2 + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0x1c + cmp x24, #0x1c b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] - sdiv x21, x1, x2 - stur x21, [x29, #-0x48] - mov x22, x0 + sdiv x1, x1, x2 + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0x1d + cmp x24, #0x1d b.ne - add x0, x22, #0x8 - ldr x1, [x22] + add x0, x21, #0x8 + ldr x1, [x21] ldur x2, [x29, #-0x48] sdiv x17, x1, x2 - msub x21, x17, x2, x1 - stur x21, [x29, #-0x48] - mov x22, x0 + msub x1, x17, x2, x1 + stur x1, [x29, #-0x48] + mov x21, x0 b - cmp x25, #0x1e + cmp x24, #0x1e b.ne - ldr x0, [x22, #0x8] - ldr x1, [x22] + ldr x0, [x21, #0x8] + ldr x1, [x21] sxtw x1, w1 bl sxtw x0, w0 - mov x21, x0 - stur x21, [x29, #-0x48] + stur x0, [x29, #-0x48] b - cmp x25, #0x1f + cmp x24, #0x1f b.ne - ldr x0, [x22, #0x10] + ldr x0, [x21, #0x10] sxtw x0, w0 - ldr x1, [x22, #0x8] - ldr x2, [x22] + ldr x1, [x21, #0x8] + ldr x2, [x21] sxtw x2, w2 bl sxtw x0, w0 - mov x21, x0 - stur x21, [x29, #-0x48] + stur x0, [x29, #-0x48] b - cmp x25, #0x20 + cmp x24, #0x20 b.ne - ldr x0, [x22] + ldr x0, [x21] sxtw x0, w0 bl sxtw x0, w0 - mov x21, x0 - stur x21, [x29, #-0x48] + stur x0, [x29, #-0x48] b - cmp x25, #0x21 + cmp x24, #0x21 b.ne - ldr x0, [x24, #0x8] + ldr x0, [x22, #0x8] lsl x0, x0, #3 - add x0, x22, x0 + add x0, x21, x0 sub x1, x0, #0x8 ldr x1, [x1] sub x2, x0, #0x10 @@ -4695,54 +4692,50 @@ Disassembly of section .text: mov x0, x16 bl sxtw x0, w0 - mov x21, x0 - stur x21, [x29, #-0x48] + stur x0, [x29, #-0x48] b - cmp x25, #0x22 + cmp x24, #0x22 b.ne - ldr x0, [x22] + ldr x0, [x21] sxtw x0, w0 bl - mov x21, x0 - stur x21, [x29, #-0x48] + stur x0, [x29, #-0x48] b - cmp x25, #0x23 + cmp x24, #0x23 b.ne - ldr x0, [x22] + ldr x0, [x21] bl sxtw x0, w0 b - cmp x25, #0x24 + cmp x24, #0x24 b.ne - ldr x0, [x22, #0x10] - ldr x1, [x22, #0x8] + ldr x0, [x21, #0x10] + ldr x1, [x21, #0x8] sxtw x1, w1 - ldr x2, [x22] + ldr x2, [x21] sxtw x2, w2 bl - mov x21, x0 - stur x21, [x29, #-0x48] + stur x0, [x29, #-0x48] b - cmp x25, #0x25 + cmp x24, #0x25 b.ne - ldr x0, [x22, #0x10] - ldr x1, [x22, #0x8] - ldr x2, [x22] + ldr x0, [x21, #0x10] + ldr x1, [x21, #0x8] + ldr x2, [x21] sxtw x2, w2 bl sxtw x0, w0 - mov x21, x0 - stur x21, [x29, #-0x48] + stur x0, [x29, #-0x48] b - cmp x25, #0x26 + cmp x24, #0x26 b.ne adrp x0, add x0, x0, - ldr x1, [x22] + ldr x1, [x21] mov x2, x20 bl sxtw x0, w0 - ldr x0, [x22] + ldr x0, [x21] ldr x20, [sp] ldr x21, [sp, #0x8] ldr x22, [sp, #0x10] @@ -4758,7 +4751,7 @@ Disassembly of section .text: b adrp x0, add x0, x0, - mov x1, x25 + mov x1, x24 mov x2, x20 bl sxtw x0, w0 diff --git a/tests/snapshots/asm/c4.x64.asm b/tests/snapshots/asm/c4.x64.asm index a0e0dfebf..85f095c07 100644 --- a/tests/snapshots/asm/c4.x64.asm +++ b/tests/snapshots/asm/c4.x64.asm @@ -3892,14 +3892,13 @@ Disassembly of section .text: movq %r12, (%rcx) addq $-0x8, %rcx movq %r13, (%rcx) - leaq -0x8(%rcx), %r13 - movq %rax, (%r13) + leaq -0x8(%rcx), %r12 + movq %rax, (%r12) xorq %rbx, %rbx - movq 0x48(%rsp), %r15 - addq $0x8, %r15 + movq 0x48(%rsp), %r13 + addq $0x8, %r13 movq 0x48(%rsp), %r10 - movq (%r10), %r10 - movq %r10, 0x48(%rsp) + movq (%r10), %r15 incq %rbx leaq , %rax movq (%rax), %rax @@ -3917,23 +3916,20 @@ Disassembly of section .text: retq leaq , %rdi leaq , %rax - movq 0x48(%rsp), %rcx - leaq (%rcx,%rcx,4), %rcx + leaq (%r15,%r15,4), %rcx leaq (%rax,%rcx), %rdx movq %rbx, %rsi movb $0x0, %al callq movslq %eax, %rax - movq 0x48(%rsp), %rax - cmpq $0x7, %rax + cmpq $0x7, %r15 jg jmp - movq 0x48(%rsp), %rax - testq %rax, %rax + testq %r15, %r15 jne jmp leaq , %rdi - movq (%r15), %rsi + movq (%r13), %rsi movb $0x0, %al callq movslq %eax, %rax @@ -3943,52 +3939,47 @@ Disassembly of section .text: callq movslq %eax, %rax jmp - leaq 0x8(%r15), %r10 + leaq 0x8(%r13), %r10 movq %r10, 0x48(%rsp) - movq (%r15), %rax + movq (%r13), %rax shlq $0x3, %rax - leaq (%r14,%rax), %r12 - movq %r12, -0x48(%rbp) + addq %r14, %rax + movq %rax, -0x48(%rbp) jmp - movq 0x48(%rsp), %rax - cmpq $0x1, %rax + cmpq $0x1, %r15 jne - leaq 0x8(%r15), %r10 + leaq 0x8(%r13), %r10 movq %r10, 0x48(%rsp) - movq (%r15), %r12 - movq %r12, -0x48(%rbp) + movq (%r13), %rax + movq %rax, -0x48(%rbp) jmp - movq 0x48(%rsp), %rax - cmpq $0x2, %rax + cmpq $0x2, %r15 jne - movq (%r15), %r10 + movq (%r13), %r10 movq %r10, 0x48(%rsp) jmp - movq 0x48(%rsp), %rax - cmpq $0x3, %rax + cmpq $0x3, %r15 jne - addq $-0x8, %r13 - leaq 0x8(%r15), %rax - movq %rax, (%r13) - movq (%r15), %r10 + addq $-0x8, %r12 + leaq 0x8(%r13), %rax + movq %rax, (%r12) + movq (%r13), %r10 movq %r10, 0x48(%rsp) jmp - movq 0x48(%rsp), %rax - cmpq $0x4, %rax + cmpq $0x4, %r15 jne movq -0x48(%rbp), %rax testq %rax, %rax je jmp jmp - movq 0x48(%rsp), %rax - cmpq $0x5, %rax + cmpq $0x5, %r15 jne jmp - leaq 0x8(%r15), %r10 + leaq 0x8(%r13), %r10 movq %r10, 0x48(%rsp) jmp - movq (%r15), %r10 + movq (%r13), %r10 movq %r10, 0x48(%rsp) jmp movq -0x48(%rbp), %rax @@ -3996,252 +3987,226 @@ Disassembly of section .text: je jmp jmp - movq 0x48(%rsp), %rax - cmpq $0x6, %rax + cmpq $0x6, %r15 jne jmp - movq (%r15), %r10 + movq (%r13), %r10 movq %r10, 0x48(%rsp) jmp - leaq 0x8(%r15), %r10 + leaq 0x8(%r13), %r10 movq %r10, 0x48(%rsp) jmp - leaq -0x8(%r13), %rax + leaq -0x8(%r12), %rax movq %r14, (%rax) - leaq 0x8(%r15), %r10 + leaq 0x8(%r13), %r10 movq %r10, 0x48(%rsp) - movq (%r15), %rcx + movq (%r13), %rcx shlq $0x3, %rcx - movq %rax, %r13 - subq %rcx, %r13 + movq %rax, %r12 + subq %rcx, %r12 movq %rax, %r14 jmp - movq 0x48(%rsp), %rax - cmpq $0x7, %rax + cmpq $0x7, %r15 jne - leaq 0x8(%r15), %r10 + leaq 0x8(%r13), %r10 movq %r10, 0x48(%rsp) - movq (%r15), %rax + movq (%r13), %rax shlq $0x3, %rax - addq %rax, %r13 + addq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0x8, %rax + cmpq $0x8, %r15 jne leaq 0x8(%r14), %rax movq (%r14), %r14 - leaq 0x8(%rax), %r13 + leaq 0x8(%rax), %r12 movq (%rax), %r10 movq %r10, 0x48(%rsp) jmp - movq 0x48(%rsp), %rax - cmpq $0x9, %rax + cmpq $0x9, %r15 jne movq -0x48(%rbp), %rax - movq (%rax), %r12 - movq %r12, -0x48(%rbp) - movq %r15, 0x48(%rsp) + movq (%rax), %rax + movq %rax, -0x48(%rbp) + movq %r13, 0x48(%rsp) jmp - movq 0x48(%rsp), %rax - cmpq $0xa, %rax + cmpq $0xa, %r15 jne movq -0x48(%rbp), %rax - movsbq (%rax), %r12 - movq %r12, -0x48(%rbp) + movsbq (%rax), %rax + movq %rax, -0x48(%rbp) jmp - movq 0x48(%rsp), %rax - cmpq $0xb, %rax + cmpq $0xb, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx movq %rdx, (%rcx) - movq %rax, %r13 + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0xc, %rax + cmpq $0xc, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx movb %dl, (%rcx) - movsbq %dl, %r12 - movq %r12, -0x48(%rbp) - movq %rax, %r13 + movsbq %dl, %rcx + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0xd, %rax + cmpq $0xd, %r15 jne - addq $-0x8, %r13 + addq $-0x8, %r12 movq -0x48(%rbp), %rax - movq %rax, (%r13) + movq %rax, (%r12) jmp - movq 0x48(%rsp), %rax - cmpq $0xe, %rax + cmpq $0xe, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx - movq %rcx, %r12 - orq %rdx, %r12 - movq %r12, -0x48(%rbp) - movq %rax, %r13 + orq %rdx, %rcx + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0xf, %rax + cmpq $0xf, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx - movq %rcx, %r12 - xorq %rdx, %r12 - movq %r12, -0x48(%rbp) - movq %rax, %r13 + xorq %rdx, %rcx + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0x10, %rax + cmpq $0x10, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx - movq %rcx, %r12 - andq %rdx, %r12 - movq %r12, -0x48(%rbp) - movq %rax, %r13 + andq %rdx, %rcx + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0x11, %rax + cmpq $0x11, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx cmpq %rdx, %rcx - sete %r12b - movzbq %r12b, %r12 - movq %r12, -0x48(%rbp) - movq %rax, %r13 + sete %cl + movzbq %cl, %rcx + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0x12, %rax + cmpq $0x12, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx cmpq %rdx, %rcx - setne %r12b - movzbq %r12b, %r12 - movq %r12, -0x48(%rbp) - movq %rax, %r13 + setne %cl + movzbq %cl, %rcx + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0x13, %rax + cmpq $0x13, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx cmpq %rdx, %rcx - setl %r12b - movzbq %r12b, %r12 - movq %r12, -0x48(%rbp) - movq %rax, %r13 + setl %cl + movzbq %cl, %rcx + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0x14, %rax + cmpq $0x14, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx cmpq %rdx, %rcx - setg %r12b - movzbq %r12b, %r12 - movq %r12, -0x48(%rbp) - movq %rax, %r13 + setg %cl + movzbq %cl, %rcx + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0x15, %rax + cmpq $0x15, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx cmpq %rdx, %rcx - setle %r12b - movzbq %r12b, %r12 - movq %r12, -0x48(%rbp) - movq %rax, %r13 + setle %cl + movzbq %cl, %rcx + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0x16, %rax + cmpq $0x16, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx cmpq %rdx, %rcx - setge %r12b - movzbq %r12b, %r12 - movq %r12, -0x48(%rbp) - movq %rax, %r13 + setge %cl + movzbq %cl, %rcx + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0x17, %rax + cmpq $0x17, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx - movq %rcx, %r12 + movq %rcx, %r11 movq %rdx, %rcx - shlq %cl, %r12 - movq %r12, -0x48(%rbp) - movq %rax, %r13 + shlq %cl, %r11 + movq %r11, %rcx + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0x18, %rax + cmpq $0x18, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx - movq %rcx, %r12 + movq %rcx, %r11 movq %rdx, %rcx - sarq %cl, %r12 - movq %r12, -0x48(%rbp) - movq %rax, %r13 + sarq %cl, %r11 + movq %r11, %rcx + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0x19, %rax + cmpq $0x19, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx - leaq (%rcx,%rdx), %r12 - movq %r12, -0x48(%rbp) - movq %rax, %r13 + addq %rdx, %rcx + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0x1a, %rax + cmpq $0x1a, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx - movq %rcx, %r12 - subq %rdx, %r12 - movq %r12, -0x48(%rbp) - movq %rax, %r13 + subq %rdx, %rcx + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0x1b, %rax + cmpq $0x1b, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx - movq %rcx, %r12 - imulq %rdx, %r12 - movq %r12, -0x48(%rbp) - movq %rax, %r13 + imulq %rdx, %rcx + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0x1c, %rax + cmpq $0x1c, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx movq %rdx, %r10 pushq %rax @@ -4249,17 +4214,16 @@ Disassembly of section .text: movq %rcx, %rax cqto idivq %r10 - movq %rax, %r12 + movq %rax, %rcx popq %rdx popq %rax - movq %r12, -0x48(%rbp) - movq %rax, %r13 + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0x1d, %rax + cmpq $0x1d, %r15 jne - leaq 0x8(%r13), %rax - movq (%r13), %rcx + leaq 0x8(%r12), %rax + movq (%r12), %rcx movq -0x48(%rbp), %rdx movq %rdx, %r10 pushq %rax @@ -4267,55 +4231,48 @@ Disassembly of section .text: movq %rcx, %rax cqto idivq %r10 - movq %rdx, %r12 + movq %rdx, %rcx popq %rdx popq %rax - movq %r12, -0x48(%rbp) - movq %rax, %r13 + movq %rcx, -0x48(%rbp) + movq %rax, %r12 jmp - movq 0x48(%rsp), %rax - cmpq $0x1e, %rax + cmpq $0x1e, %r15 jne - movq 0x8(%r13), %rdi - movq (%r13), %rax + movq 0x8(%r12), %rdi + movq (%r12), %rax movslq %eax, %rsi movb $0x0, %al callq movslq %eax, %rax - movq %rax, %r12 - movq %r12, -0x48(%rbp) + movq %rax, -0x48(%rbp) jmp - movq 0x48(%rsp), %rax - cmpq $0x1f, %rax + cmpq $0x1f, %r15 jne - movq 0x10(%r13), %rax + movq 0x10(%r12), %rax movslq %eax, %rdi - movq 0x8(%r13), %rsi - movq (%r13), %rax + movq 0x8(%r12), %rsi + movq (%r12), %rax movslq %eax, %rdx xorl %eax, %eax callq movslq %eax, %rax - movq %rax, %r12 - movq %r12, -0x48(%rbp) + movq %rax, -0x48(%rbp) jmp - movq 0x48(%rsp), %rax - cmpq $0x20, %rax + cmpq $0x20, %r15 jne - movq (%r13), %rax + movq (%r12), %rax movslq %eax, %rdi xorl %eax, %eax callq movslq %eax, %rax - movq %rax, %r12 - movq %r12, -0x48(%rbp) + movq %rax, -0x48(%rbp) jmp - movq 0x48(%rsp), %rax - cmpq $0x21, %rax + cmpq $0x21, %r15 jne - movq 0x8(%r15), %rax + movq 0x8(%r13), %rax shlq $0x3, %rax - addq %r13, %rax + addq %r12, %rax leaq -0x8(%rax), %rcx movq (%rcx), %rdi leaq -0x10(%rax), %rcx @@ -4331,63 +4288,54 @@ Disassembly of section .text: movb $0x0, %al callq movslq %eax, %rax - movq %rax, %r12 - movq %r12, -0x48(%rbp) + movq %rax, -0x48(%rbp) jmp - movq 0x48(%rsp), %rax - cmpq $0x22, %rax + cmpq $0x22, %r15 jne - movq (%r13), %rax + movq (%r12), %rax movslq %eax, %rdi xorl %eax, %eax callq - movq %rax, %r12 - movq %r12, -0x48(%rbp) + movq %rax, -0x48(%rbp) jmp - movq 0x48(%rsp), %rax - cmpq $0x23, %rax + cmpq $0x23, %r15 jne - movq (%r13), %rdi + movq (%r12), %rdi xorl %eax, %eax callq movslq %eax, %rax jmp - movq 0x48(%rsp), %rax - cmpq $0x24, %rax + cmpq $0x24, %r15 jne - movq 0x10(%r13), %rdi - movq 0x8(%r13), %rax + movq 0x10(%r12), %rdi + movq 0x8(%r12), %rax movslq %eax, %rsi - movq (%r13), %rax + movq (%r12), %rax movslq %eax, %rdx xorl %eax, %eax callq - movq %rax, %r12 - movq %r12, -0x48(%rbp) + movq %rax, -0x48(%rbp) jmp - movq 0x48(%rsp), %rax - cmpq $0x25, %rax + cmpq $0x25, %r15 jne - movq 0x10(%r13), %rdi - movq 0x8(%r13), %rsi - movq (%r13), %rax + movq 0x10(%r12), %rdi + movq 0x8(%r12), %rsi + movq (%r12), %rax movslq %eax, %rdx xorl %eax, %eax callq movslq %eax, %rax - movq %rax, %r12 - movq %r12, -0x48(%rbp) + movq %rax, -0x48(%rbp) jmp - movq 0x48(%rsp), %rax - cmpq $0x26, %rax + cmpq $0x26, %r15 jne leaq , %rdi - movq (%r13), %rsi + movq (%r12), %rsi movq %rbx, %rdx movb $0x0, %al callq movslq %eax, %rax - movq (%r13), %rax + movq (%r12), %rax movq (%rsp), %rbx movq 0x8(%rsp), %r12 movq 0x10(%rsp), %r13 @@ -4398,8 +4346,8 @@ Disassembly of section .text: retq jmp leaq , %rdi + movq %r15, %rsi movq %rbx, %rdx - movq 0x48(%rsp), %rsi movb $0x0, %al callq movslq %eax, %rax @@ -4424,4 +4372,4 @@ Disassembly of section .text: jmp jmp jmp - addb %al, 0x41(%rdx) + addb %al, (%rax) diff --git a/tests/snapshots/ssa/c4.ssa b/tests/snapshots/ssa/c4.ssa index fbfbf9b32..ac7b93bcd 100644 --- a/tests/snapshots/ssa/c4.ssa +++ b/tests/snapshots/ssa/c4.ssa @@ -4490,7 +4490,7 @@ fn ent_pc=9 n_params=2 variadic=false locals=27 v733 LoadLocal { off=3, kind=I64 } -> x2 v734 Store { addr=v731, disp=0, value=v76, kind=I64 } -> - v735 LoadLocal { off=-7, kind=I64 } -> x2 - v736 BinopI { op=add, lhs=v731, rhs_imm=-8 } -> x13 + v736 BinopI { op=add, lhs=v731, rhs_imm=-8 } -> x12 v737 Imm(0) -> x1 v738 LoadLocal { off=-12, kind=I64 } -> x1 v739 Store { addr=v736, disp=0, value=v720, kind=I64 } -> - @@ -4498,787 +4498,749 @@ fn ent_pc=9 n_params=2 variadic=false locals=27 v741 Imm(0) -> x0 terminator Jmp(b129) (exit_acc=v740) block 129 start_pc=0 - v742 Phi { incoming=[b128:v740, b138:v754], kind=I64 } -> x3 - v743 Phi { incoming=[b138:v784], kind=I64 } -> x12 - v744 Phi { incoming=[b128:v711, b138:v785], kind=I64 } -> x14 - v745 Phi { incoming=[b128:v736, b138:v786], kind=I64 } -> x13 - v746 Phi { incoming=[b128:v262, b138:v787], kind=I64 } -> [spill 0] - v747 Imm(1) -> x0 - terminator Jmp(b130) (exit_acc=v747) + v742 Phi { incoming=[b128:v740, b138:v753], kind=I64 } -> x3 + v743 Phi { incoming=[b128:v711, b138:v783], kind=I64 } -> x14 + v744 Phi { incoming=[b128:v736, b138:v784], kind=I64 } -> x12 + v745 Phi { incoming=[b128:v262, b138:v785], kind=I64 } -> [spill 0] + v746 Imm(1) -> x0 + terminator Jmp(b130) (exit_acc=v746) block 130 start_pc=0 - v748 LoadLocal { off=-6, kind=I64 } -> x0 - v749 BinopI { op=add, lhs=v746, rhs_imm=8 } -> x15 - v750 Imm(0) -> x0 - v751 Load { addr=v746, disp=0, kind=I64 } -> [spill 0] - v752 Imm(0) -> x0 - v753 LoadLocal { off=-10, kind=I64 } -> x0 - v754 BinopI { op=add, lhs=v742, rhs_imm=1 } -> x3 - v755 Imm(0) -> x0 - v756 ImmData(1880) -> x0 - v757 Load { addr=v756, disp=0, kind=I64 } -> x0 - terminator Bz { cond=v757, target=b133, fall=b132 } (exit_acc=v757) + v747 LoadLocal { off=-6, kind=I64 } -> x0 + v748 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x13 + v749 Imm(0) -> x0 + v750 Load { addr=v745, disp=0, kind=I64 } -> x15 + v751 Imm(0) -> x0 + v752 LoadLocal { off=-10, kind=I64 } -> x0 + v753 BinopI { op=add, lhs=v742, rhs_imm=1 } -> x3 + v754 Imm(0) -> x0 + v755 ImmData(1880) -> x0 + v756 Load { addr=v755, disp=0, kind=I64 } -> x0 + terminator Bz { cond=v756, target=b133, fall=b132 } (exit_acc=v756) block 131 start_pc=0 - v758 Imm(0) -> x0 - terminator Return(v758) (exit_acc=v758) + v757 Imm(0) -> x0 + terminator Return(v757) (exit_acc=v757) block 132 start_pc=0 - v759 ImmData(1493) -> x7 - v760 LoadLocal { off=-10, kind=I64 } -> x0 - v761 ImmData(1502) -> x0 - v762 LoadLocal { off=-11, kind=I64 } -> x1 - v763 BinopI { op=mul, lhs=v751, rhs_imm=5 } -> x1 - v764 Binop { op=add, lhs=v761, rhs=v763 } -> x2 - v765 CallExt { binding_idx=0, args=[v759, v754, v764], fp_arg_mask=0x0 } -> x0 - v766 LoadLocal { off=-11, kind=I64 } -> x0 - v767 BinopI { op=le, lhs=v751, rhs_imm=7 } -> x0 - terminator Bz { cond=v767, target=b136, fall=b134 } (exit_acc=v767) + v758 ImmData(1493) -> x7 + v759 LoadLocal { off=-10, kind=I64 } -> x0 + v760 ImmData(1502) -> x0 + v761 LoadLocal { off=-11, kind=I64 } -> x1 + v762 BinopI { op=mul, lhs=v750, rhs_imm=5 } -> x1 + v763 Binop { op=add, lhs=v760, rhs=v762 } -> x2 + v764 CallExt { binding_idx=0, args=[v758, v753, v763], fp_arg_mask=0x0 } -> x0 + v765 LoadLocal { off=-11, kind=I64 } -> x0 + v766 BinopI { op=le, lhs=v750, rhs_imm=7 } -> x0 + terminator Bz { cond=v766, target=b136, fall=b134 } (exit_acc=v766) block 133 start_pc=0 - v768 LoadLocal { off=-11, kind=I64 } -> x0 - v769 BinopI { op=eq, lhs=v751, rhs_imm=0 } -> x0 - terminator Bz { cond=v769, target=b139, fall=b137 } (exit_acc=v769) + v767 LoadLocal { off=-11, kind=I64 } -> x0 + v768 BinopI { op=eq, lhs=v750, rhs_imm=0 } -> x0 + terminator Bz { cond=v768, target=b139, fall=b137 } (exit_acc=v768) block 134 start_pc=0 - v770 ImmData(1698) -> x7 - v771 LoadLocal { off=-6, kind=I64 } -> x0 - v772 Load { addr=v749, disp=0, kind=I64 } -> x6 - v773 CallExt { binding_idx=0, args=[v770, v772], fp_arg_mask=0x0 } -> x0 - terminator Jmp(b135) (exit_acc=v773) + v769 ImmData(1698) -> x7 + v770 LoadLocal { off=-6, kind=I64 } -> x0 + v771 Load { addr=v748, disp=0, kind=I64 } -> x6 + v772 CallExt { binding_idx=0, args=[v769, v771], fp_arg_mask=0x0 } -> x0 + terminator Jmp(b135) (exit_acc=v772) block 135 start_pc=0 terminator Jmp(b133) block 136 start_pc=0 - v774 ImmData(1703) -> x7 - v775 CallExt { binding_idx=0, args=[v774], fp_arg_mask=0x0 } -> x0 - terminator Jmp(b135) (exit_acc=v775) + v773 ImmData(1703) -> x7 + v774 CallExt { binding_idx=0, args=[v773], fp_arg_mask=0x0 } -> x0 + terminator Jmp(b135) (exit_acc=v774) block 137 start_pc=0 - v776 LoadLocal { off=-8, kind=I64 } -> x0 - v777 LoadLocal { off=-6, kind=I64 } -> x0 - v778 BinopI { op=add, lhs=v749, rhs_imm=8 } -> [spill 0] - v779 Imm(0) -> x0 - v780 Load { addr=v749, disp=0, kind=I64 } -> x0 - v781 BinopI { op=shl, lhs=v780, rhs_imm=3 } -> x0 - v782 Binop { op=add, lhs=v744, rhs=v781 } -> x12 - v783 StoreLocal { off=-9, value=v782, kind=I64 } -> - - terminator Jmp(b138) (exit_acc=v783) + v775 LoadLocal { off=-8, kind=I64 } -> x0 + v776 LoadLocal { off=-6, kind=I64 } -> x0 + v777 BinopI { op=add, lhs=v748, rhs_imm=8 } -> [spill 0] + v778 Imm(0) -> x0 + v779 Load { addr=v748, disp=0, kind=I64 } -> x0 + v780 BinopI { op=shl, lhs=v779, rhs_imm=3 } -> x0 + v781 Binop { op=add, lhs=v743, rhs=v780 } -> x0 + v782 StoreLocal { off=-9, value=v781, kind=I64 } -> - + terminator Jmp(b138) (exit_acc=v782) block 138 start_pc=0 - v784 Phi { incoming=[b137:v782, b141:v795], kind=I64 } -> x12 - v785 Phi { incoming=[b137:v744, b141:v796], kind=I64 } -> x14 - v786 Phi { incoming=[b137:v745, b141:v797], kind=I64 } -> x13 - v787 Phi { incoming=[b137:v778, b141:v798], kind=I64 } -> [spill 0] + v783 Phi { incoming=[b137:v743, b141:v793], kind=I64 } -> x14 + v784 Phi { incoming=[b137:v744, b141:v794], kind=I64 } -> x12 + v785 Phi { incoming=[b137:v777, b141:v795], kind=I64 } -> [spill 0] terminator Jmp(b129) block 139 start_pc=0 - v788 LoadLocal { off=-11, kind=I64 } -> x0 - v789 BinopI { op=eq, lhs=v751, rhs_imm=1 } -> x0 - terminator Bz { cond=v789, target=b142, fall=b140 } (exit_acc=v789) + v786 LoadLocal { off=-11, kind=I64 } -> x0 + v787 BinopI { op=eq, lhs=v750, rhs_imm=1 } -> x0 + terminator Bz { cond=v787, target=b142, fall=b140 } (exit_acc=v787) block 140 start_pc=0 - v790 LoadLocal { off=-6, kind=I64 } -> x0 - v791 BinopI { op=add, lhs=v749, rhs_imm=8 } -> [spill 0] - v792 Imm(0) -> x0 - v793 Load { addr=v749, disp=0, kind=I64 } -> x12 - v794 StoreLocal { off=-9, value=v793, kind=I64 } -> - - terminator Jmp(b141) (exit_acc=v794) + v788 LoadLocal { off=-6, kind=I64 } -> x0 + v789 BinopI { op=add, lhs=v748, rhs_imm=8 } -> [spill 0] + v790 Imm(0) -> x0 + v791 Load { addr=v748, disp=0, kind=I64 } -> x0 + v792 StoreLocal { off=-9, value=v791, kind=I64 } -> - + terminator Jmp(b141) (exit_acc=v792) block 141 start_pc=0 - v795 Phi { incoming=[b140:v793, b144:v804], kind=I64 } -> x12 - v796 Phi { incoming=[b140:v744, b144:v805], kind=I64 } -> x14 - v797 Phi { incoming=[b140:v745, b144:v806], kind=I64 } -> x13 - v798 Phi { incoming=[b140:v791, b144:v807], kind=I64 } -> [spill 0] + v793 Phi { incoming=[b140:v743, b144:v801], kind=I64 } -> x14 + v794 Phi { incoming=[b140:v744, b144:v802], kind=I64 } -> x12 + v795 Phi { incoming=[b140:v789, b144:v803], kind=I64 } -> [spill 0] terminator Jmp(b138) block 142 start_pc=0 - v799 LoadLocal { off=-11, kind=I64 } -> x0 - v800 BinopI { op=eq, lhs=v751, rhs_imm=2 } -> x0 - terminator Bz { cond=v800, target=b145, fall=b143 } (exit_acc=v800) + v796 LoadLocal { off=-11, kind=I64 } -> x0 + v797 BinopI { op=eq, lhs=v750, rhs_imm=2 } -> x0 + terminator Bz { cond=v797, target=b145, fall=b143 } (exit_acc=v797) block 143 start_pc=0 - v801 LoadLocal { off=-6, kind=I64 } -> x0 - v802 Load { addr=v749, disp=0, kind=I64 } -> [spill 0] - v803 Imm(0) -> x0 - terminator Jmp(b144) (exit_acc=v802) + v798 LoadLocal { off=-6, kind=I64 } -> x0 + v799 Load { addr=v748, disp=0, kind=I64 } -> [spill 0] + v800 Imm(0) -> x0 + terminator Jmp(b144) (exit_acc=v799) block 144 start_pc=0 - v804 Phi { incoming=[b143:v743, b147:v820], kind=I64 } -> x12 - v805 Phi { incoming=[b143:v744, b147:v821], kind=I64 } -> x14 - v806 Phi { incoming=[b143:v745, b147:v822], kind=I64 } -> x13 - v807 Phi { incoming=[b143:v802, b147:v823], kind=I64 } -> [spill 0] + v801 Phi { incoming=[b143:v743, b147:v816], kind=I64 } -> x14 + v802 Phi { incoming=[b143:v744, b147:v817], kind=I64 } -> x12 + v803 Phi { incoming=[b143:v799, b147:v818], kind=I64 } -> [spill 0] terminator Jmp(b141) block 145 start_pc=0 - v808 LoadLocal { off=-11, kind=I64 } -> x0 - v809 BinopI { op=eq, lhs=v751, rhs_imm=3 } -> x0 - terminator Bz { cond=v809, target=b148, fall=b146 } (exit_acc=v809) + v804 LoadLocal { off=-11, kind=I64 } -> x0 + v805 BinopI { op=eq, lhs=v750, rhs_imm=3 } -> x0 + terminator Bz { cond=v805, target=b148, fall=b146 } (exit_acc=v805) block 146 start_pc=0 - v810 LoadLocal { off=-7, kind=I64 } -> x0 - v811 BinopI { op=add, lhs=v745, rhs_imm=-8 } -> x13 - v812 Imm(0) -> x0 + v806 LoadLocal { off=-7, kind=I64 } -> x0 + v807 BinopI { op=add, lhs=v744, rhs_imm=-8 } -> x12 + v808 Imm(0) -> x0 + v809 LoadLocal { off=-6, kind=I64 } -> x0 + v810 Imm(8) -> x0 + v811 BinopI { op=add, lhs=v748, rhs_imm=8 } -> x0 + v812 Store { addr=v807, disp=0, value=v811, kind=I64 } -> - v813 LoadLocal { off=-6, kind=I64 } -> x0 - v814 Imm(8) -> x0 - v815 BinopI { op=add, lhs=v749, rhs_imm=8 } -> x0 - v816 Store { addr=v811, disp=0, value=v815, kind=I64 } -> - - v817 LoadLocal { off=-6, kind=I64 } -> x0 - v818 Load { addr=v749, disp=0, kind=I64 } -> [spill 0] - v819 Imm(0) -> x0 - terminator Jmp(b147) (exit_acc=v818) + v814 Load { addr=v748, disp=0, kind=I64 } -> [spill 0] + v815 Imm(0) -> x0 + terminator Jmp(b147) (exit_acc=v814) block 147 start_pc=0 - v820 Phi { incoming=[b146:v743, b150:v827], kind=I64 } -> x12 - v821 Phi { incoming=[b146:v744, b150:v828], kind=I64 } -> x14 - v822 Phi { incoming=[b146:v811, b150:v829], kind=I64 } -> x13 - v823 Phi { incoming=[b146:v818, b150:v830], kind=I64 } -> [spill 0] + v816 Phi { incoming=[b146:v743, b150:v822], kind=I64 } -> x14 + v817 Phi { incoming=[b146:v807, b150:v823], kind=I64 } -> x12 + v818 Phi { incoming=[b146:v814, b150:v824], kind=I64 } -> [spill 0] terminator Jmp(b144) block 148 start_pc=0 - v824 LoadLocal { off=-11, kind=I64 } -> x0 - v825 BinopI { op=eq, lhs=v751, rhs_imm=4 } -> x0 - terminator Bz { cond=v825, target=b151, fall=b149 } (exit_acc=v825) + v819 LoadLocal { off=-11, kind=I64 } -> x0 + v820 BinopI { op=eq, lhs=v750, rhs_imm=4 } -> x0 + terminator Bz { cond=v820, target=b151, fall=b149 } (exit_acc=v820) block 149 start_pc=0 - v826 LoadLocal { off=-9, kind=I64 } -> x0 - terminator Bz { cond=v826, target=b153, fall=b152 } (exit_acc=v826) + v821 LoadLocal { off=-9, kind=I64 } -> x0 + terminator Bz { cond=v821, target=b153, fall=b152 } (exit_acc=v821) block 150 start_pc=0 - v827 Phi { incoming=[b154:v743, b156:v844], kind=I64 } -> x12 - v828 Phi { incoming=[b154:v744, b156:v845], kind=I64 } -> x14 - v829 Phi { incoming=[b154:v745, b156:v846], kind=I64 } -> x13 - v830 Phi { incoming=[b154:v840, b156:v847], kind=I64 } -> [spill 0] + v822 Phi { incoming=[b154:v743, b156:v838], kind=I64 } -> x14 + v823 Phi { incoming=[b154:v744, b156:v839], kind=I64 } -> x12 + v824 Phi { incoming=[b154:v834, b156:v840], kind=I64 } -> [spill 0] terminator Jmp(b147) block 151 start_pc=0 - v831 LoadLocal { off=-11, kind=I64 } -> x0 - v832 BinopI { op=eq, lhs=v751, rhs_imm=5 } -> x0 - terminator Bz { cond=v832, target=b157, fall=b155 } (exit_acc=v832) + v825 LoadLocal { off=-11, kind=I64 } -> x0 + v826 BinopI { op=eq, lhs=v750, rhs_imm=5 } -> x0 + terminator Bz { cond=v826, target=b157, fall=b155 } (exit_acc=v826) block 152 start_pc=0 - v833 LoadLocal { off=-6, kind=I64 } -> x0 - v834 Imm(8) -> x0 - v835 BinopI { op=add, lhs=v749, rhs_imm=8 } -> [spill 0] - v836 Imm(0) -> x0 - terminator Jmp(b154) (exit_acc=v835) + v827 LoadLocal { off=-6, kind=I64 } -> x0 + v828 Imm(8) -> x0 + v829 BinopI { op=add, lhs=v748, rhs_imm=8 } -> [spill 0] + v830 Imm(0) -> x0 + terminator Jmp(b154) (exit_acc=v829) block 153 start_pc=0 - v837 LoadLocal { off=-6, kind=I64 } -> x0 - v838 Load { addr=v749, disp=0, kind=I64 } -> [spill 0] - v839 Imm(0) -> x0 - terminator Jmp(b154) (exit_acc=v838) + v831 LoadLocal { off=-6, kind=I64 } -> x0 + v832 Load { addr=v748, disp=0, kind=I64 } -> [spill 0] + v833 Imm(0) -> x0 + terminator Jmp(b154) (exit_acc=v832) block 154 start_pc=0 - v840 Phi { incoming=[b152:v835, b153:v838], kind=I64 } -> [spill 0] - v841 LoadLocal { off=-26, kind=I64 } -> x0 - v842 Imm(0) -> x0 - terminator Jmp(b150) (exit_acc=v840) + v834 Phi { incoming=[b152:v829, b153:v832], kind=I64 } -> [spill 0] + v835 LoadLocal { off=-26, kind=I64 } -> x0 + v836 Imm(0) -> x0 + terminator Jmp(b150) (exit_acc=v834) block 155 start_pc=0 - v843 LoadLocal { off=-9, kind=I64 } -> x0 - terminator Bz { cond=v843, target=b159, fall=b158 } (exit_acc=v843) + v837 LoadLocal { off=-9, kind=I64 } -> x0 + terminator Bz { cond=v837, target=b159, fall=b158 } (exit_acc=v837) block 156 start_pc=0 - v844 Phi { incoming=[b160:v743, b162:v874], kind=I64 } -> x12 - v845 Phi { incoming=[b160:v744, b162:v875], kind=I64 } -> x14 - v846 Phi { incoming=[b160:v745, b162:v876], kind=I64 } -> x13 - v847 Phi { incoming=[b160:v857, b162:v877], kind=I64 } -> [spill 0] + v838 Phi { incoming=[b160:v743, b162:v867], kind=I64 } -> x14 + v839 Phi { incoming=[b160:v744, b162:v868], kind=I64 } -> x12 + v840 Phi { incoming=[b160:v850, b162:v869], kind=I64 } -> [spill 0] terminator Jmp(b150) block 157 start_pc=0 - v848 LoadLocal { off=-11, kind=I64 } -> x0 - v849 BinopI { op=eq, lhs=v751, rhs_imm=6 } -> x0 - terminator Bz { cond=v849, target=b163, fall=b161 } (exit_acc=v849) + v841 LoadLocal { off=-11, kind=I64 } -> x0 + v842 BinopI { op=eq, lhs=v750, rhs_imm=6 } -> x0 + terminator Bz { cond=v842, target=b163, fall=b161 } (exit_acc=v842) block 158 start_pc=0 - v850 LoadLocal { off=-6, kind=I64 } -> x0 - v851 Load { addr=v749, disp=0, kind=I64 } -> [spill 0] - v852 Imm(0) -> x0 - terminator Jmp(b160) (exit_acc=v851) + v843 LoadLocal { off=-6, kind=I64 } -> x0 + v844 Load { addr=v748, disp=0, kind=I64 } -> [spill 0] + v845 Imm(0) -> x0 + terminator Jmp(b160) (exit_acc=v844) block 159 start_pc=0 - v853 LoadLocal { off=-6, kind=I64 } -> x0 - v854 Imm(8) -> x0 - v855 BinopI { op=add, lhs=v749, rhs_imm=8 } -> [spill 0] - v856 Imm(0) -> x0 - terminator Jmp(b160) (exit_acc=v855) + v846 LoadLocal { off=-6, kind=I64 } -> x0 + v847 Imm(8) -> x0 + v848 BinopI { op=add, lhs=v748, rhs_imm=8 } -> [spill 0] + v849 Imm(0) -> x0 + terminator Jmp(b160) (exit_acc=v848) block 160 start_pc=0 - v857 Phi { incoming=[b158:v851, b159:v855], kind=I64 } -> [spill 0] - v858 LoadLocal { off=-27, kind=I64 } -> x0 - v859 Imm(0) -> x0 - terminator Jmp(b156) (exit_acc=v857) + v850 Phi { incoming=[b158:v844, b159:v848], kind=I64 } -> [spill 0] + v851 LoadLocal { off=-27, kind=I64 } -> x0 + v852 Imm(0) -> x0 + terminator Jmp(b156) (exit_acc=v850) block 161 start_pc=0 - v860 LoadLocal { off=-7, kind=I64 } -> x0 - v861 BinopI { op=add, lhs=v745, rhs_imm=-8 } -> x0 + v853 LoadLocal { off=-7, kind=I64 } -> x0 + v854 BinopI { op=add, lhs=v744, rhs_imm=-8 } -> x0 + v855 Imm(0) -> x1 + v856 LoadLocal { off=-8, kind=I64 } -> x1 + v857 Store { addr=v854, disp=0, value=v743, kind=I64 } -> - + v858 LoadLocal { off=-7, kind=I64 } -> x1 + v859 Imm(0) -> x1 + v860 LoadLocal { off=-6, kind=I64 } -> x1 + v861 BinopI { op=add, lhs=v748, rhs_imm=8 } -> [spill 0] v862 Imm(0) -> x1 - v863 LoadLocal { off=-8, kind=I64 } -> x1 - v864 Store { addr=v861, disp=0, value=v744, kind=I64 } -> - - v865 LoadLocal { off=-7, kind=I64 } -> x1 + v863 Load { addr=v748, disp=0, kind=I64 } -> x1 + v864 BinopI { op=shl, lhs=v863, rhs_imm=3 } -> x1 + v865 Binop { op=sub, lhs=v854, rhs=v864 } -> x12 v866 Imm(0) -> x1 - v867 LoadLocal { off=-6, kind=I64 } -> x1 - v868 BinopI { op=add, lhs=v749, rhs_imm=8 } -> [spill 0] - v869 Imm(0) -> x1 - v870 Load { addr=v749, disp=0, kind=I64 } -> x1 - v871 BinopI { op=shl, lhs=v870, rhs_imm=3 } -> x1 - v872 Binop { op=sub, lhs=v861, rhs=v871 } -> x13 - v873 Imm(0) -> x1 - terminator Jmp(b162) (exit_acc=v872) + terminator Jmp(b162) (exit_acc=v865) block 162 start_pc=0 - v874 Phi { incoming=[b161:v743, b165:v888], kind=I64 } -> x12 - v875 Phi { incoming=[b161:v861, b165:v889], kind=I64 } -> x14 - v876 Phi { incoming=[b161:v872, b165:v890], kind=I64 } -> x13 - v877 Phi { incoming=[b161:v868, b165:v891], kind=I64 } -> [spill 0] + v867 Phi { incoming=[b161:v854, b165:v880], kind=I64 } -> x14 + v868 Phi { incoming=[b161:v865, b165:v881], kind=I64 } -> x12 + v869 Phi { incoming=[b161:v861, b165:v882], kind=I64 } -> [spill 0] terminator Jmp(b156) block 163 start_pc=0 - v878 LoadLocal { off=-11, kind=I64 } -> x0 - v879 BinopI { op=eq, lhs=v751, rhs_imm=7 } -> x0 - terminator Bz { cond=v879, target=b166, fall=b164 } (exit_acc=v879) + v870 LoadLocal { off=-11, kind=I64 } -> x0 + v871 BinopI { op=eq, lhs=v750, rhs_imm=7 } -> x0 + terminator Bz { cond=v871, target=b166, fall=b164 } (exit_acc=v871) block 164 start_pc=0 - v880 LoadLocal { off=-7, kind=I64 } -> x0 - v881 LoadLocal { off=-6, kind=I64 } -> x0 - v882 BinopI { op=add, lhs=v749, rhs_imm=8 } -> [spill 0] - v883 Imm(0) -> x0 - v884 Load { addr=v749, disp=0, kind=I64 } -> x0 - v885 BinopI { op=shl, lhs=v884, rhs_imm=3 } -> x0 - v886 Binop { op=add, lhs=v745, rhs=v885 } -> x13 - v887 Imm(0) -> x0 - terminator Jmp(b165) (exit_acc=v886) + v872 LoadLocal { off=-7, kind=I64 } -> x0 + v873 LoadLocal { off=-6, kind=I64 } -> x0 + v874 BinopI { op=add, lhs=v748, rhs_imm=8 } -> [spill 0] + v875 Imm(0) -> x0 + v876 Load { addr=v748, disp=0, kind=I64 } -> x0 + v877 BinopI { op=shl, lhs=v876, rhs_imm=3 } -> x0 + v878 Binop { op=add, lhs=v744, rhs=v877 } -> x12 + v879 Imm(0) -> x0 + terminator Jmp(b165) (exit_acc=v878) block 165 start_pc=0 - v888 Phi { incoming=[b164:v743, b168:v906], kind=I64 } -> x12 - v889 Phi { incoming=[b164:v744, b168:v907], kind=I64 } -> x14 - v890 Phi { incoming=[b164:v886, b168:v908], kind=I64 } -> x13 - v891 Phi { incoming=[b164:v882, b168:v909], kind=I64 } -> [spill 0] + v880 Phi { incoming=[b164:v743, b168:v897], kind=I64 } -> x14 + v881 Phi { incoming=[b164:v878, b168:v898], kind=I64 } -> x12 + v882 Phi { incoming=[b164:v874, b168:v899], kind=I64 } -> [spill 0] terminator Jmp(b162) block 166 start_pc=0 - v892 LoadLocal { off=-11, kind=I64 } -> x0 - v893 BinopI { op=eq, lhs=v751, rhs_imm=8 } -> x0 - terminator Bz { cond=v893, target=b169, fall=b167 } (exit_acc=v893) + v883 LoadLocal { off=-11, kind=I64 } -> x0 + v884 BinopI { op=eq, lhs=v750, rhs_imm=8 } -> x0 + terminator Bz { cond=v884, target=b169, fall=b167 } (exit_acc=v884) block 167 start_pc=0 - v894 LoadLocal { off=-8, kind=I64 } -> x0 - v895 Imm(0) -> x0 - v896 LoadLocal { off=-7, kind=I64 } -> x0 - v897 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 - v898 Imm(0) -> x1 - v899 Load { addr=v744, disp=0, kind=I64 } -> x14 - v900 Imm(0) -> x1 - v901 LoadLocal { off=-7, kind=I64 } -> x1 - v902 BinopI { op=add, lhs=v897, rhs_imm=8 } -> x13 - v903 Imm(0) -> x1 - v904 Load { addr=v897, disp=0, kind=I64 } -> [spill 0] - v905 Imm(0) -> x0 - terminator Jmp(b168) (exit_acc=v904) + v885 LoadLocal { off=-8, kind=I64 } -> x0 + v886 Imm(0) -> x0 + v887 LoadLocal { off=-7, kind=I64 } -> x0 + v888 BinopI { op=add, lhs=v743, rhs_imm=8 } -> x0 + v889 Imm(0) -> x1 + v890 Load { addr=v743, disp=0, kind=I64 } -> x14 + v891 Imm(0) -> x1 + v892 LoadLocal { off=-7, kind=I64 } -> x1 + v893 BinopI { op=add, lhs=v888, rhs_imm=8 } -> x12 + v894 Imm(0) -> x1 + v895 Load { addr=v888, disp=0, kind=I64 } -> [spill 0] + v896 Imm(0) -> x0 + terminator Jmp(b168) (exit_acc=v895) block 168 start_pc=0 - v906 Phi { incoming=[b167:v743, b171:v915], kind=I64 } -> x12 - v907 Phi { incoming=[b167:v899, b171:v744], kind=I64 } -> x14 - v908 Phi { incoming=[b167:v902, b171:v916], kind=I64 } -> x13 - v909 Phi { incoming=[b167:v904, b171:v749], kind=I64 } -> [spill 0] + v897 Phi { incoming=[b167:v890, b171:v743], kind=I64 } -> x14 + v898 Phi { incoming=[b167:v893, b171:v905], kind=I64 } -> x12 + v899 Phi { incoming=[b167:v895, b171:v748], kind=I64 } -> [spill 0] terminator Jmp(b165) block 169 start_pc=0 - v910 LoadLocal { off=-11, kind=I64 } -> x0 - v911 BinopI { op=eq, lhs=v751, rhs_imm=9 } -> x0 - terminator Bz { cond=v911, target=b172, fall=b170 } (exit_acc=v911) + v900 LoadLocal { off=-11, kind=I64 } -> x0 + v901 BinopI { op=eq, lhs=v750, rhs_imm=9 } -> x0 + terminator Bz { cond=v901, target=b172, fall=b170 } (exit_acc=v901) block 170 start_pc=0 - v912 LoadLocal { off=-9, kind=I64 } -> x0 - v913 Load { addr=v912, disp=0, kind=I64 } -> x12 - v914 StoreLocal { off=-9, value=v913, kind=I64 } -> - - terminator Jmp(b171) (exit_acc=v914) + v902 LoadLocal { off=-9, kind=I64 } -> x0 + v903 Load { addr=v902, disp=0, kind=I64 } -> x0 + v904 StoreLocal { off=-9, value=v903, kind=I64 } -> - + terminator Jmp(b171) (exit_acc=v904) block 171 start_pc=0 - v915 Phi { incoming=[b170:v913, b174:v922], kind=I64 } -> x12 - v916 Phi { incoming=[b170:v745, b174:v923], kind=I64 } -> x13 + v905 Phi { incoming=[b170:v744, b174:v911], kind=I64 } -> x12 terminator Jmp(b168) block 172 start_pc=0 - v917 LoadLocal { off=-11, kind=I64 } -> x0 - v918 BinopI { op=eq, lhs=v751, rhs_imm=10 } -> x0 - terminator Bz { cond=v918, target=b175, fall=b173 } (exit_acc=v918) + v906 LoadLocal { off=-11, kind=I64 } -> x0 + v907 BinopI { op=eq, lhs=v750, rhs_imm=10 } -> x0 + terminator Bz { cond=v907, target=b175, fall=b173 } (exit_acc=v907) block 173 start_pc=0 - v919 LoadLocal { off=-9, kind=I64 } -> x0 - v920 Load { addr=v919, disp=0, kind=I8 } -> x12 - v921 StoreLocal { off=-9, value=v920, kind=I64 } -> - - terminator Jmp(b174) (exit_acc=v921) + v908 LoadLocal { off=-9, kind=I64 } -> x0 + v909 Load { addr=v908, disp=0, kind=I8 } -> x0 + v910 StoreLocal { off=-9, value=v909, kind=I64 } -> - + terminator Jmp(b174) (exit_acc=v910) block 174 start_pc=0 - v922 Phi { incoming=[b173:v920, b177:v932], kind=I64 } -> x12 - v923 Phi { incoming=[b173:v745, b177:v933], kind=I64 } -> x13 + v911 Phi { incoming=[b173:v744, b177:v920], kind=I64 } -> x12 terminator Jmp(b171) block 175 start_pc=0 - v924 LoadLocal { off=-11, kind=I64 } -> x0 - v925 BinopI { op=eq, lhs=v751, rhs_imm=11 } -> x0 - terminator Bz { cond=v925, target=b178, fall=b176 } (exit_acc=v925) + v912 LoadLocal { off=-11, kind=I64 } -> x0 + v913 BinopI { op=eq, lhs=v750, rhs_imm=11 } -> x0 + terminator Bz { cond=v913, target=b178, fall=b176 } (exit_acc=v913) block 176 start_pc=0 - v926 LoadLocal { off=-7, kind=I64 } -> x0 - v927 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v928 Imm(0) -> x1 - v929 Load { addr=v745, disp=0, kind=I64 } -> x1 - v930 LoadLocal { off=-9, kind=I64 } -> x2 - v931 Store { addr=v929, disp=0, value=v930, kind=I64 } -> - - terminator Jmp(b177) (exit_acc=v931) + v914 LoadLocal { off=-7, kind=I64 } -> x0 + v915 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v916 Imm(0) -> x1 + v917 Load { addr=v744, disp=0, kind=I64 } -> x1 + v918 LoadLocal { off=-9, kind=I64 } -> x2 + v919 Store { addr=v917, disp=0, value=v918, kind=I64 } -> - + terminator Jmp(b177) (exit_acc=v919) block 177 start_pc=0 - v932 Phi { incoming=[b176:v743, b180:v945], kind=I64 } -> x12 - v933 Phi { incoming=[b176:v927, b180:v946], kind=I64 } -> x13 + v920 Phi { incoming=[b176:v915, b180:v932], kind=I64 } -> x12 terminator Jmp(b174) block 178 start_pc=0 - v934 LoadLocal { off=-11, kind=I64 } -> x0 - v935 BinopI { op=eq, lhs=v751, rhs_imm=12 } -> x0 - terminator Bz { cond=v935, target=b181, fall=b179 } (exit_acc=v935) + v921 LoadLocal { off=-11, kind=I64 } -> x0 + v922 BinopI { op=eq, lhs=v750, rhs_imm=12 } -> x0 + terminator Bz { cond=v922, target=b181, fall=b179 } (exit_acc=v922) block 179 start_pc=0 - v936 LoadLocal { off=-7, kind=I64 } -> x0 - v937 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v938 Imm(0) -> x1 - v939 Load { addr=v745, disp=0, kind=I64 } -> x1 - v940 LoadLocal { off=-9, kind=I64 } -> x2 - v941 Store { addr=v939, disp=0, value=v940, kind=I8 } -> - - v942 BinopI { op=shl, lhs=v940, rhs_imm=56 } -> x1 - v943 Extend { value=v940, kind=I8 } -> x12 - v944 StoreLocal { off=-9, value=v943, kind=I64 } -> - - terminator Jmp(b180) (exit_acc=v944) + v923 LoadLocal { off=-7, kind=I64 } -> x0 + v924 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v925 Imm(0) -> x1 + v926 Load { addr=v744, disp=0, kind=I64 } -> x1 + v927 LoadLocal { off=-9, kind=I64 } -> x2 + v928 Store { addr=v926, disp=0, value=v927, kind=I8 } -> - + v929 BinopI { op=shl, lhs=v927, rhs_imm=56 } -> x1 + v930 Extend { value=v927, kind=I8 } -> x1 + v931 StoreLocal { off=-9, value=v930, kind=I64 } -> - + terminator Jmp(b180) (exit_acc=v931) block 180 start_pc=0 - v945 Phi { incoming=[b179:v943, b183:v954], kind=I64 } -> x12 - v946 Phi { incoming=[b179:v937, b183:v955], kind=I64 } -> x13 + v932 Phi { incoming=[b179:v924, b183:v940], kind=I64 } -> x12 terminator Jmp(b177) block 181 start_pc=0 - v947 LoadLocal { off=-11, kind=I64 } -> x0 - v948 BinopI { op=eq, lhs=v751, rhs_imm=13 } -> x0 - terminator Bz { cond=v948, target=b184, fall=b182 } (exit_acc=v948) + v933 LoadLocal { off=-11, kind=I64 } -> x0 + v934 BinopI { op=eq, lhs=v750, rhs_imm=13 } -> x0 + terminator Bz { cond=v934, target=b184, fall=b182 } (exit_acc=v934) block 182 start_pc=0 - v949 LoadLocal { off=-7, kind=I64 } -> x0 - v950 BinopI { op=add, lhs=v745, rhs_imm=-8 } -> x13 - v951 Imm(0) -> x0 - v952 LoadLocal { off=-9, kind=I64 } -> x0 - v953 Store { addr=v950, disp=0, value=v952, kind=I64 } -> - - terminator Jmp(b183) (exit_acc=v953) + v935 LoadLocal { off=-7, kind=I64 } -> x0 + v936 BinopI { op=add, lhs=v744, rhs_imm=-8 } -> x12 + v937 Imm(0) -> x0 + v938 LoadLocal { off=-9, kind=I64 } -> x0 + v939 Store { addr=v936, disp=0, value=v938, kind=I64 } -> - + terminator Jmp(b183) (exit_acc=v939) block 183 start_pc=0 - v954 Phi { incoming=[b182:v743, b186:v965], kind=I64 } -> x12 - v955 Phi { incoming=[b182:v950, b186:v966], kind=I64 } -> x13 + v940 Phi { incoming=[b182:v936, b186:v950], kind=I64 } -> x12 terminator Jmp(b180) block 184 start_pc=0 - v956 LoadLocal { off=-11, kind=I64 } -> x0 - v957 BinopI { op=eq, lhs=v751, rhs_imm=14 } -> x0 - terminator Bz { cond=v957, target=b187, fall=b185 } (exit_acc=v957) + v941 LoadLocal { off=-11, kind=I64 } -> x0 + v942 BinopI { op=eq, lhs=v750, rhs_imm=14 } -> x0 + terminator Bz { cond=v942, target=b187, fall=b185 } (exit_acc=v942) block 185 start_pc=0 - v958 LoadLocal { off=-7, kind=I64 } -> x0 - v959 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v960 Imm(0) -> x1 - v961 Load { addr=v745, disp=0, kind=I64 } -> x1 - v962 LoadLocal { off=-9, kind=I64 } -> x2 - v963 Binop { op=or, lhs=v961, rhs=v962 } -> x12 - v964 StoreLocal { off=-9, value=v963, kind=I64 } -> - - terminator Jmp(b186) (exit_acc=v964) + v943 LoadLocal { off=-7, kind=I64 } -> x0 + v944 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v945 Imm(0) -> x1 + v946 Load { addr=v744, disp=0, kind=I64 } -> x1 + v947 LoadLocal { off=-9, kind=I64 } -> x2 + v948 Binop { op=or, lhs=v946, rhs=v947 } -> x1 + v949 StoreLocal { off=-9, value=v948, kind=I64 } -> - + terminator Jmp(b186) (exit_acc=v949) block 186 start_pc=0 - v965 Phi { incoming=[b185:v963, b189:v976], kind=I64 } -> x12 - v966 Phi { incoming=[b185:v959, b189:v977], kind=I64 } -> x13 + v950 Phi { incoming=[b185:v944, b189:v960], kind=I64 } -> x12 terminator Jmp(b183) block 187 start_pc=0 - v967 LoadLocal { off=-11, kind=I64 } -> x0 - v968 BinopI { op=eq, lhs=v751, rhs_imm=15 } -> x0 - terminator Bz { cond=v968, target=b190, fall=b188 } (exit_acc=v968) + v951 LoadLocal { off=-11, kind=I64 } -> x0 + v952 BinopI { op=eq, lhs=v750, rhs_imm=15 } -> x0 + terminator Bz { cond=v952, target=b190, fall=b188 } (exit_acc=v952) block 188 start_pc=0 - v969 LoadLocal { off=-7, kind=I64 } -> x0 - v970 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v971 Imm(0) -> x1 - v972 Load { addr=v745, disp=0, kind=I64 } -> x1 - v973 LoadLocal { off=-9, kind=I64 } -> x2 - v974 Binop { op=xor, lhs=v972, rhs=v973 } -> x12 - v975 StoreLocal { off=-9, value=v974, kind=I64 } -> - - terminator Jmp(b189) (exit_acc=v975) + v953 LoadLocal { off=-7, kind=I64 } -> x0 + v954 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v955 Imm(0) -> x1 + v956 Load { addr=v744, disp=0, kind=I64 } -> x1 + v957 LoadLocal { off=-9, kind=I64 } -> x2 + v958 Binop { op=xor, lhs=v956, rhs=v957 } -> x1 + v959 StoreLocal { off=-9, value=v958, kind=I64 } -> - + terminator Jmp(b189) (exit_acc=v959) block 189 start_pc=0 - v976 Phi { incoming=[b188:v974, b192:v987], kind=I64 } -> x12 - v977 Phi { incoming=[b188:v970, b192:v988], kind=I64 } -> x13 + v960 Phi { incoming=[b188:v954, b192:v970], kind=I64 } -> x12 terminator Jmp(b186) block 190 start_pc=0 - v978 LoadLocal { off=-11, kind=I64 } -> x0 - v979 BinopI { op=eq, lhs=v751, rhs_imm=16 } -> x0 - terminator Bz { cond=v979, target=b193, fall=b191 } (exit_acc=v979) + v961 LoadLocal { off=-11, kind=I64 } -> x0 + v962 BinopI { op=eq, lhs=v750, rhs_imm=16 } -> x0 + terminator Bz { cond=v962, target=b193, fall=b191 } (exit_acc=v962) block 191 start_pc=0 - v980 LoadLocal { off=-7, kind=I64 } -> x0 - v981 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v982 Imm(0) -> x1 - v983 Load { addr=v745, disp=0, kind=I64 } -> x1 - v984 LoadLocal { off=-9, kind=I64 } -> x2 - v985 Binop { op=and, lhs=v983, rhs=v984 } -> x12 - v986 StoreLocal { off=-9, value=v985, kind=I64 } -> - - terminator Jmp(b192) (exit_acc=v986) + v963 LoadLocal { off=-7, kind=I64 } -> x0 + v964 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v965 Imm(0) -> x1 + v966 Load { addr=v744, disp=0, kind=I64 } -> x1 + v967 LoadLocal { off=-9, kind=I64 } -> x2 + v968 Binop { op=and, lhs=v966, rhs=v967 } -> x1 + v969 StoreLocal { off=-9, value=v968, kind=I64 } -> - + terminator Jmp(b192) (exit_acc=v969) block 192 start_pc=0 - v987 Phi { incoming=[b191:v985, b195:v998], kind=I64 } -> x12 - v988 Phi { incoming=[b191:v981, b195:v999], kind=I64 } -> x13 + v970 Phi { incoming=[b191:v964, b195:v980], kind=I64 } -> x12 terminator Jmp(b189) block 193 start_pc=0 - v989 LoadLocal { off=-11, kind=I64 } -> x0 - v990 BinopI { op=eq, lhs=v751, rhs_imm=17 } -> x0 - terminator Bz { cond=v990, target=b196, fall=b194 } (exit_acc=v990) + v971 LoadLocal { off=-11, kind=I64 } -> x0 + v972 BinopI { op=eq, lhs=v750, rhs_imm=17 } -> x0 + terminator Bz { cond=v972, target=b196, fall=b194 } (exit_acc=v972) block 194 start_pc=0 - v991 LoadLocal { off=-7, kind=I64 } -> x0 - v992 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v993 Imm(0) -> x1 - v994 Load { addr=v745, disp=0, kind=I64 } -> x1 - v995 LoadLocal { off=-9, kind=I64 } -> x2 - v996 Binop { op=eq, lhs=v994, rhs=v995 } -> x12 - v997 StoreLocal { off=-9, value=v996, kind=I64 } -> - - terminator Jmp(b195) (exit_acc=v997) + v973 LoadLocal { off=-7, kind=I64 } -> x0 + v974 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v975 Imm(0) -> x1 + v976 Load { addr=v744, disp=0, kind=I64 } -> x1 + v977 LoadLocal { off=-9, kind=I64 } -> x2 + v978 Binop { op=eq, lhs=v976, rhs=v977 } -> x1 + v979 StoreLocal { off=-9, value=v978, kind=I64 } -> - + terminator Jmp(b195) (exit_acc=v979) block 195 start_pc=0 - v998 Phi { incoming=[b194:v996, b198:v1009], kind=I64 } -> x12 - v999 Phi { incoming=[b194:v992, b198:v1010], kind=I64 } -> x13 + v980 Phi { incoming=[b194:v974, b198:v990], kind=I64 } -> x12 terminator Jmp(b192) block 196 start_pc=0 - v1000 LoadLocal { off=-11, kind=I64 } -> x0 - v1001 BinopI { op=eq, lhs=v751, rhs_imm=18 } -> x0 - terminator Bz { cond=v1001, target=b199, fall=b197 } (exit_acc=v1001) + v981 LoadLocal { off=-11, kind=I64 } -> x0 + v982 BinopI { op=eq, lhs=v750, rhs_imm=18 } -> x0 + terminator Bz { cond=v982, target=b199, fall=b197 } (exit_acc=v982) block 197 start_pc=0 - v1002 LoadLocal { off=-7, kind=I64 } -> x0 - v1003 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v1004 Imm(0) -> x1 - v1005 Load { addr=v745, disp=0, kind=I64 } -> x1 - v1006 LoadLocal { off=-9, kind=I64 } -> x2 - v1007 Binop { op=ne, lhs=v1005, rhs=v1006 } -> x12 - v1008 StoreLocal { off=-9, value=v1007, kind=I64 } -> - - terminator Jmp(b198) (exit_acc=v1008) + v983 LoadLocal { off=-7, kind=I64 } -> x0 + v984 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v985 Imm(0) -> x1 + v986 Load { addr=v744, disp=0, kind=I64 } -> x1 + v987 LoadLocal { off=-9, kind=I64 } -> x2 + v988 Binop { op=ne, lhs=v986, rhs=v987 } -> x1 + v989 StoreLocal { off=-9, value=v988, kind=I64 } -> - + terminator Jmp(b198) (exit_acc=v989) block 198 start_pc=0 - v1009 Phi { incoming=[b197:v1007, b201:v1020], kind=I64 } -> x12 - v1010 Phi { incoming=[b197:v1003, b201:v1021], kind=I64 } -> x13 + v990 Phi { incoming=[b197:v984, b201:v1000], kind=I64 } -> x12 terminator Jmp(b195) block 199 start_pc=0 - v1011 LoadLocal { off=-11, kind=I64 } -> x0 - v1012 BinopI { op=eq, lhs=v751, rhs_imm=19 } -> x0 - terminator Bz { cond=v1012, target=b202, fall=b200 } (exit_acc=v1012) + v991 LoadLocal { off=-11, kind=I64 } -> x0 + v992 BinopI { op=eq, lhs=v750, rhs_imm=19 } -> x0 + terminator Bz { cond=v992, target=b202, fall=b200 } (exit_acc=v992) block 200 start_pc=0 - v1013 LoadLocal { off=-7, kind=I64 } -> x0 - v1014 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v1015 Imm(0) -> x1 - v1016 Load { addr=v745, disp=0, kind=I64 } -> x1 - v1017 LoadLocal { off=-9, kind=I64 } -> x2 - v1018 Binop { op=lt, lhs=v1016, rhs=v1017 } -> x12 - v1019 StoreLocal { off=-9, value=v1018, kind=I64 } -> - - terminator Jmp(b201) (exit_acc=v1019) + v993 LoadLocal { off=-7, kind=I64 } -> x0 + v994 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v995 Imm(0) -> x1 + v996 Load { addr=v744, disp=0, kind=I64 } -> x1 + v997 LoadLocal { off=-9, kind=I64 } -> x2 + v998 Binop { op=lt, lhs=v996, rhs=v997 } -> x1 + v999 StoreLocal { off=-9, value=v998, kind=I64 } -> - + terminator Jmp(b201) (exit_acc=v999) block 201 start_pc=0 - v1020 Phi { incoming=[b200:v1018, b204:v1031], kind=I64 } -> x12 - v1021 Phi { incoming=[b200:v1014, b204:v1032], kind=I64 } -> x13 + v1000 Phi { incoming=[b200:v994, b204:v1010], kind=I64 } -> x12 terminator Jmp(b198) block 202 start_pc=0 - v1022 LoadLocal { off=-11, kind=I64 } -> x0 - v1023 BinopI { op=eq, lhs=v751, rhs_imm=20 } -> x0 - terminator Bz { cond=v1023, target=b205, fall=b203 } (exit_acc=v1023) + v1001 LoadLocal { off=-11, kind=I64 } -> x0 + v1002 BinopI { op=eq, lhs=v750, rhs_imm=20 } -> x0 + terminator Bz { cond=v1002, target=b205, fall=b203 } (exit_acc=v1002) block 203 start_pc=0 - v1024 LoadLocal { off=-7, kind=I64 } -> x0 - v1025 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v1026 Imm(0) -> x1 - v1027 Load { addr=v745, disp=0, kind=I64 } -> x1 - v1028 LoadLocal { off=-9, kind=I64 } -> x2 - v1029 Binop { op=gt, lhs=v1027, rhs=v1028 } -> x12 - v1030 StoreLocal { off=-9, value=v1029, kind=I64 } -> - - terminator Jmp(b204) (exit_acc=v1030) + v1003 LoadLocal { off=-7, kind=I64 } -> x0 + v1004 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v1005 Imm(0) -> x1 + v1006 Load { addr=v744, disp=0, kind=I64 } -> x1 + v1007 LoadLocal { off=-9, kind=I64 } -> x2 + v1008 Binop { op=gt, lhs=v1006, rhs=v1007 } -> x1 + v1009 StoreLocal { off=-9, value=v1008, kind=I64 } -> - + terminator Jmp(b204) (exit_acc=v1009) block 204 start_pc=0 - v1031 Phi { incoming=[b203:v1029, b207:v1042], kind=I64 } -> x12 - v1032 Phi { incoming=[b203:v1025, b207:v1043], kind=I64 } -> x13 + v1010 Phi { incoming=[b203:v1004, b207:v1020], kind=I64 } -> x12 terminator Jmp(b201) block 205 start_pc=0 - v1033 LoadLocal { off=-11, kind=I64 } -> x0 - v1034 BinopI { op=eq, lhs=v751, rhs_imm=21 } -> x0 - terminator Bz { cond=v1034, target=b208, fall=b206 } (exit_acc=v1034) + v1011 LoadLocal { off=-11, kind=I64 } -> x0 + v1012 BinopI { op=eq, lhs=v750, rhs_imm=21 } -> x0 + terminator Bz { cond=v1012, target=b208, fall=b206 } (exit_acc=v1012) block 206 start_pc=0 - v1035 LoadLocal { off=-7, kind=I64 } -> x0 - v1036 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v1037 Imm(0) -> x1 - v1038 Load { addr=v745, disp=0, kind=I64 } -> x1 - v1039 LoadLocal { off=-9, kind=I64 } -> x2 - v1040 Binop { op=le, lhs=v1038, rhs=v1039 } -> x12 - v1041 StoreLocal { off=-9, value=v1040, kind=I64 } -> - - terminator Jmp(b207) (exit_acc=v1041) + v1013 LoadLocal { off=-7, kind=I64 } -> x0 + v1014 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v1015 Imm(0) -> x1 + v1016 Load { addr=v744, disp=0, kind=I64 } -> x1 + v1017 LoadLocal { off=-9, kind=I64 } -> x2 + v1018 Binop { op=le, lhs=v1016, rhs=v1017 } -> x1 + v1019 StoreLocal { off=-9, value=v1018, kind=I64 } -> - + terminator Jmp(b207) (exit_acc=v1019) block 207 start_pc=0 - v1042 Phi { incoming=[b206:v1040, b210:v1053], kind=I64 } -> x12 - v1043 Phi { incoming=[b206:v1036, b210:v1054], kind=I64 } -> x13 + v1020 Phi { incoming=[b206:v1014, b210:v1030], kind=I64 } -> x12 terminator Jmp(b204) block 208 start_pc=0 - v1044 LoadLocal { off=-11, kind=I64 } -> x0 - v1045 BinopI { op=eq, lhs=v751, rhs_imm=22 } -> x0 - terminator Bz { cond=v1045, target=b211, fall=b209 } (exit_acc=v1045) + v1021 LoadLocal { off=-11, kind=I64 } -> x0 + v1022 BinopI { op=eq, lhs=v750, rhs_imm=22 } -> x0 + terminator Bz { cond=v1022, target=b211, fall=b209 } (exit_acc=v1022) block 209 start_pc=0 - v1046 LoadLocal { off=-7, kind=I64 } -> x0 - v1047 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v1048 Imm(0) -> x1 - v1049 Load { addr=v745, disp=0, kind=I64 } -> x1 - v1050 LoadLocal { off=-9, kind=I64 } -> x2 - v1051 Binop { op=ge, lhs=v1049, rhs=v1050 } -> x12 - v1052 StoreLocal { off=-9, value=v1051, kind=I64 } -> - - terminator Jmp(b210) (exit_acc=v1052) + v1023 LoadLocal { off=-7, kind=I64 } -> x0 + v1024 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v1025 Imm(0) -> x1 + v1026 Load { addr=v744, disp=0, kind=I64 } -> x1 + v1027 LoadLocal { off=-9, kind=I64 } -> x2 + v1028 Binop { op=ge, lhs=v1026, rhs=v1027 } -> x1 + v1029 StoreLocal { off=-9, value=v1028, kind=I64 } -> - + terminator Jmp(b210) (exit_acc=v1029) block 210 start_pc=0 - v1053 Phi { incoming=[b209:v1051, b213:v1064], kind=I64 } -> x12 - v1054 Phi { incoming=[b209:v1047, b213:v1065], kind=I64 } -> x13 + v1030 Phi { incoming=[b209:v1024, b213:v1040], kind=I64 } -> x12 terminator Jmp(b207) block 211 start_pc=0 - v1055 LoadLocal { off=-11, kind=I64 } -> x0 - v1056 BinopI { op=eq, lhs=v751, rhs_imm=23 } -> x0 - terminator Bz { cond=v1056, target=b214, fall=b212 } (exit_acc=v1056) + v1031 LoadLocal { off=-11, kind=I64 } -> x0 + v1032 BinopI { op=eq, lhs=v750, rhs_imm=23 } -> x0 + terminator Bz { cond=v1032, target=b214, fall=b212 } (exit_acc=v1032) block 212 start_pc=0 - v1057 LoadLocal { off=-7, kind=I64 } -> x0 - v1058 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v1059 Imm(0) -> x1 - v1060 Load { addr=v745, disp=0, kind=I64 } -> x1 - v1061 LoadLocal { off=-9, kind=I64 } -> x2 - v1062 Binop { op=shl, lhs=v1060, rhs=v1061 } -> x12 - v1063 StoreLocal { off=-9, value=v1062, kind=I64 } -> - - terminator Jmp(b213) (exit_acc=v1063) + v1033 LoadLocal { off=-7, kind=I64 } -> x0 + v1034 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v1035 Imm(0) -> x1 + v1036 Load { addr=v744, disp=0, kind=I64 } -> x1 + v1037 LoadLocal { off=-9, kind=I64 } -> x2 + v1038 Binop { op=shl, lhs=v1036, rhs=v1037 } -> x1 + v1039 StoreLocal { off=-9, value=v1038, kind=I64 } -> - + terminator Jmp(b213) (exit_acc=v1039) block 213 start_pc=0 - v1064 Phi { incoming=[b212:v1062, b216:v1075], kind=I64 } -> x12 - v1065 Phi { incoming=[b212:v1058, b216:v1076], kind=I64 } -> x13 + v1040 Phi { incoming=[b212:v1034, b216:v1050], kind=I64 } -> x12 terminator Jmp(b210) block 214 start_pc=0 - v1066 LoadLocal { off=-11, kind=I64 } -> x0 - v1067 BinopI { op=eq, lhs=v751, rhs_imm=24 } -> x0 - terminator Bz { cond=v1067, target=b217, fall=b215 } (exit_acc=v1067) + v1041 LoadLocal { off=-11, kind=I64 } -> x0 + v1042 BinopI { op=eq, lhs=v750, rhs_imm=24 } -> x0 + terminator Bz { cond=v1042, target=b217, fall=b215 } (exit_acc=v1042) block 215 start_pc=0 - v1068 LoadLocal { off=-7, kind=I64 } -> x0 - v1069 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v1070 Imm(0) -> x1 - v1071 Load { addr=v745, disp=0, kind=I64 } -> x1 - v1072 LoadLocal { off=-9, kind=I64 } -> x2 - v1073 Binop { op=shr, lhs=v1071, rhs=v1072 } -> x12 - v1074 StoreLocal { off=-9, value=v1073, kind=I64 } -> - - terminator Jmp(b216) (exit_acc=v1074) + v1043 LoadLocal { off=-7, kind=I64 } -> x0 + v1044 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v1045 Imm(0) -> x1 + v1046 Load { addr=v744, disp=0, kind=I64 } -> x1 + v1047 LoadLocal { off=-9, kind=I64 } -> x2 + v1048 Binop { op=shr, lhs=v1046, rhs=v1047 } -> x1 + v1049 StoreLocal { off=-9, value=v1048, kind=I64 } -> - + terminator Jmp(b216) (exit_acc=v1049) block 216 start_pc=0 - v1075 Phi { incoming=[b215:v1073, b219:v1086], kind=I64 } -> x12 - v1076 Phi { incoming=[b215:v1069, b219:v1087], kind=I64 } -> x13 + v1050 Phi { incoming=[b215:v1044, b219:v1060], kind=I64 } -> x12 terminator Jmp(b213) block 217 start_pc=0 - v1077 LoadLocal { off=-11, kind=I64 } -> x0 - v1078 BinopI { op=eq, lhs=v751, rhs_imm=25 } -> x0 - terminator Bz { cond=v1078, target=b220, fall=b218 } (exit_acc=v1078) + v1051 LoadLocal { off=-11, kind=I64 } -> x0 + v1052 BinopI { op=eq, lhs=v750, rhs_imm=25 } -> x0 + terminator Bz { cond=v1052, target=b220, fall=b218 } (exit_acc=v1052) block 218 start_pc=0 - v1079 LoadLocal { off=-7, kind=I64 } -> x0 - v1080 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v1081 Imm(0) -> x1 - v1082 Load { addr=v745, disp=0, kind=I64 } -> x1 - v1083 LoadLocal { off=-9, kind=I64 } -> x2 - v1084 Binop { op=add, lhs=v1082, rhs=v1083 } -> x12 - v1085 StoreLocal { off=-9, value=v1084, kind=I64 } -> - - terminator Jmp(b219) (exit_acc=v1085) + v1053 LoadLocal { off=-7, kind=I64 } -> x0 + v1054 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v1055 Imm(0) -> x1 + v1056 Load { addr=v744, disp=0, kind=I64 } -> x1 + v1057 LoadLocal { off=-9, kind=I64 } -> x2 + v1058 Binop { op=add, lhs=v1056, rhs=v1057 } -> x1 + v1059 StoreLocal { off=-9, value=v1058, kind=I64 } -> - + terminator Jmp(b219) (exit_acc=v1059) block 219 start_pc=0 - v1086 Phi { incoming=[b218:v1084, b222:v1097], kind=I64 } -> x12 - v1087 Phi { incoming=[b218:v1080, b222:v1098], kind=I64 } -> x13 + v1060 Phi { incoming=[b218:v1054, b222:v1070], kind=I64 } -> x12 terminator Jmp(b216) block 220 start_pc=0 - v1088 LoadLocal { off=-11, kind=I64 } -> x0 - v1089 BinopI { op=eq, lhs=v751, rhs_imm=26 } -> x0 - terminator Bz { cond=v1089, target=b223, fall=b221 } (exit_acc=v1089) + v1061 LoadLocal { off=-11, kind=I64 } -> x0 + v1062 BinopI { op=eq, lhs=v750, rhs_imm=26 } -> x0 + terminator Bz { cond=v1062, target=b223, fall=b221 } (exit_acc=v1062) block 221 start_pc=0 - v1090 LoadLocal { off=-7, kind=I64 } -> x0 - v1091 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v1092 Imm(0) -> x1 - v1093 Load { addr=v745, disp=0, kind=I64 } -> x1 - v1094 LoadLocal { off=-9, kind=I64 } -> x2 - v1095 Binop { op=sub, lhs=v1093, rhs=v1094 } -> x12 - v1096 StoreLocal { off=-9, value=v1095, kind=I64 } -> - - terminator Jmp(b222) (exit_acc=v1096) + v1063 LoadLocal { off=-7, kind=I64 } -> x0 + v1064 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v1065 Imm(0) -> x1 + v1066 Load { addr=v744, disp=0, kind=I64 } -> x1 + v1067 LoadLocal { off=-9, kind=I64 } -> x2 + v1068 Binop { op=sub, lhs=v1066, rhs=v1067 } -> x1 + v1069 StoreLocal { off=-9, value=v1068, kind=I64 } -> - + terminator Jmp(b222) (exit_acc=v1069) block 222 start_pc=0 - v1097 Phi { incoming=[b221:v1095, b225:v1108], kind=I64 } -> x12 - v1098 Phi { incoming=[b221:v1091, b225:v1109], kind=I64 } -> x13 + v1070 Phi { incoming=[b221:v1064, b225:v1080], kind=I64 } -> x12 terminator Jmp(b219) block 223 start_pc=0 - v1099 LoadLocal { off=-11, kind=I64 } -> x0 - v1100 BinopI { op=eq, lhs=v751, rhs_imm=27 } -> x0 - terminator Bz { cond=v1100, target=b226, fall=b224 } (exit_acc=v1100) + v1071 LoadLocal { off=-11, kind=I64 } -> x0 + v1072 BinopI { op=eq, lhs=v750, rhs_imm=27 } -> x0 + terminator Bz { cond=v1072, target=b226, fall=b224 } (exit_acc=v1072) block 224 start_pc=0 - v1101 LoadLocal { off=-7, kind=I64 } -> x0 - v1102 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v1103 Imm(0) -> x1 - v1104 Load { addr=v745, disp=0, kind=I64 } -> x1 - v1105 LoadLocal { off=-9, kind=I64 } -> x2 - v1106 Binop { op=mul, lhs=v1104, rhs=v1105 } -> x12 - v1107 StoreLocal { off=-9, value=v1106, kind=I64 } -> - - terminator Jmp(b225) (exit_acc=v1107) + v1073 LoadLocal { off=-7, kind=I64 } -> x0 + v1074 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v1075 Imm(0) -> x1 + v1076 Load { addr=v744, disp=0, kind=I64 } -> x1 + v1077 LoadLocal { off=-9, kind=I64 } -> x2 + v1078 Binop { op=mul, lhs=v1076, rhs=v1077 } -> x1 + v1079 StoreLocal { off=-9, value=v1078, kind=I64 } -> - + terminator Jmp(b225) (exit_acc=v1079) block 225 start_pc=0 - v1108 Phi { incoming=[b224:v1106, b228:v1119], kind=I64 } -> x12 - v1109 Phi { incoming=[b224:v1102, b228:v1120], kind=I64 } -> x13 + v1080 Phi { incoming=[b224:v1074, b228:v1090], kind=I64 } -> x12 terminator Jmp(b222) block 226 start_pc=0 - v1110 LoadLocal { off=-11, kind=I64 } -> x0 - v1111 BinopI { op=eq, lhs=v751, rhs_imm=28 } -> x0 - terminator Bz { cond=v1111, target=b229, fall=b227 } (exit_acc=v1111) + v1081 LoadLocal { off=-11, kind=I64 } -> x0 + v1082 BinopI { op=eq, lhs=v750, rhs_imm=28 } -> x0 + terminator Bz { cond=v1082, target=b229, fall=b227 } (exit_acc=v1082) block 227 start_pc=0 - v1112 LoadLocal { off=-7, kind=I64 } -> x0 - v1113 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v1114 Imm(0) -> x1 - v1115 Load { addr=v745, disp=0, kind=I64 } -> x1 - v1116 LoadLocal { off=-9, kind=I64 } -> x2 - v1117 Binop { op=div, lhs=v1115, rhs=v1116 } -> x12 - v1118 StoreLocal { off=-9, value=v1117, kind=I64 } -> - - terminator Jmp(b228) (exit_acc=v1118) + v1083 LoadLocal { off=-7, kind=I64 } -> x0 + v1084 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v1085 Imm(0) -> x1 + v1086 Load { addr=v744, disp=0, kind=I64 } -> x1 + v1087 LoadLocal { off=-9, kind=I64 } -> x2 + v1088 Binop { op=div, lhs=v1086, rhs=v1087 } -> x1 + v1089 StoreLocal { off=-9, value=v1088, kind=I64 } -> - + terminator Jmp(b228) (exit_acc=v1089) block 228 start_pc=0 - v1119 Phi { incoming=[b227:v1117, b231:v1130], kind=I64 } -> x12 - v1120 Phi { incoming=[b227:v1113, b231:v1131], kind=I64 } -> x13 + v1090 Phi { incoming=[b227:v1084, b231:v1100], kind=I64 } -> x12 terminator Jmp(b225) block 229 start_pc=0 - v1121 LoadLocal { off=-11, kind=I64 } -> x0 - v1122 BinopI { op=eq, lhs=v751, rhs_imm=29 } -> x0 - terminator Bz { cond=v1122, target=b232, fall=b230 } (exit_acc=v1122) + v1091 LoadLocal { off=-11, kind=I64 } -> x0 + v1092 BinopI { op=eq, lhs=v750, rhs_imm=29 } -> x0 + terminator Bz { cond=v1092, target=b232, fall=b230 } (exit_acc=v1092) block 230 start_pc=0 - v1123 LoadLocal { off=-7, kind=I64 } -> x0 - v1124 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v1125 Imm(0) -> x1 - v1126 Load { addr=v745, disp=0, kind=I64 } -> x1 - v1127 LoadLocal { off=-9, kind=I64 } -> x2 - v1128 Binop { op=mod, lhs=v1126, rhs=v1127 } -> x12 - v1129 StoreLocal { off=-9, value=v1128, kind=I64 } -> - - terminator Jmp(b231) (exit_acc=v1129) + v1093 LoadLocal { off=-7, kind=I64 } -> x0 + v1094 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v1095 Imm(0) -> x1 + v1096 Load { addr=v744, disp=0, kind=I64 } -> x1 + v1097 LoadLocal { off=-9, kind=I64 } -> x2 + v1098 Binop { op=mod, lhs=v1096, rhs=v1097 } -> x1 + v1099 StoreLocal { off=-9, value=v1098, kind=I64 } -> - + terminator Jmp(b231) (exit_acc=v1099) block 231 start_pc=0 - v1130 Phi { incoming=[b230:v1128, b234:v1143], kind=I64 } -> x12 - v1131 Phi { incoming=[b230:v1124, b234:v745], kind=I64 } -> x13 + v1100 Phi { incoming=[b230:v1094, b234:v744], kind=I64 } -> x12 terminator Jmp(b228) block 232 start_pc=0 - v1132 LoadLocal { off=-11, kind=I64 } -> x0 - v1133 BinopI { op=eq, lhs=v751, rhs_imm=30 } -> x0 - terminator Bz { cond=v1133, target=b235, fall=b233 } (exit_acc=v1133) + v1101 LoadLocal { off=-11, kind=I64 } -> x0 + v1102 BinopI { op=eq, lhs=v750, rhs_imm=30 } -> x0 + terminator Bz { cond=v1102, target=b235, fall=b233 } (exit_acc=v1102) block 233 start_pc=0 - v1134 LoadLocal { off=-7, kind=I64 } -> x0 - v1135 Imm(8) -> x0 - v1136 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v1137 Load { addr=v745, disp=8, kind=I64 } -> x7 - v1138 Load { addr=v745, disp=0, kind=I64 } -> x0 - v1139 BinopI { op=shl, lhs=v1138, rhs_imm=32 } -> x1 - v1140 Extend { value=v1138, kind=I32 } -> x6 - v1141 CallExt { binding_idx=144, args=[v1137, v1140], fp_arg_mask=0x0 } -> x12 - v1142 StoreLocal { off=-9, value=v1141, kind=I64 } -> - - terminator Jmp(b234) (exit_acc=v1142) + v1103 LoadLocal { off=-7, kind=I64 } -> x0 + v1104 Imm(8) -> x0 + v1105 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v1106 Load { addr=v744, disp=8, kind=I64 } -> x7 + v1107 Load { addr=v744, disp=0, kind=I64 } -> x0 + v1108 BinopI { op=shl, lhs=v1107, rhs_imm=32 } -> x1 + v1109 Extend { value=v1107, kind=I32 } -> x6 + v1110 CallExt { binding_idx=144, args=[v1106, v1109], fp_arg_mask=0x0 } -> x0 + v1111 StoreLocal { off=-9, value=v1110, kind=I64 } -> - + terminator Jmp(b234) (exit_acc=v1111) block 234 start_pc=0 - v1143 Phi { incoming=[b233:v1141, b237:v1160], kind=I64 } -> x12 terminator Jmp(b231) block 235 start_pc=0 - v1144 LoadLocal { off=-11, kind=I64 } -> x0 - v1145 BinopI { op=eq, lhs=v751, rhs_imm=31 } -> x0 - terminator Bz { cond=v1145, target=b238, fall=b236 } (exit_acc=v1145) + v1112 LoadLocal { off=-11, kind=I64 } -> x0 + v1113 BinopI { op=eq, lhs=v750, rhs_imm=31 } -> x0 + terminator Bz { cond=v1113, target=b238, fall=b236 } (exit_acc=v1113) block 236 start_pc=0 - v1146 LoadLocal { off=-7, kind=I64 } -> x0 - v1147 Imm(16) -> x0 - v1148 BinopI { op=add, lhs=v745, rhs_imm=16 } -> x0 - v1149 Load { addr=v745, disp=16, kind=I64 } -> x0 - v1150 BinopI { op=shl, lhs=v1149, rhs_imm=32 } -> x1 - v1151 Extend { value=v1149, kind=I32 } -> x7 - v1152 Imm(8) -> x0 - v1153 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v1154 Load { addr=v745, disp=8, kind=I64 } -> x6 - v1155 Load { addr=v745, disp=0, kind=I64 } -> x0 - v1156 BinopI { op=shl, lhs=v1155, rhs_imm=32 } -> x1 - v1157 Extend { value=v1155, kind=I32 } -> x2 - v1158 CallExt { binding_idx=145, args=[v1151, v1154, v1157], fp_arg_mask=0x0 } -> x12 - v1159 StoreLocal { off=-9, value=v1158, kind=I64 } -> - - terminator Jmp(b237) (exit_acc=v1159) + v1114 LoadLocal { off=-7, kind=I64 } -> x0 + v1115 Imm(16) -> x0 + v1116 BinopI { op=add, lhs=v744, rhs_imm=16 } -> x0 + v1117 Load { addr=v744, disp=16, kind=I64 } -> x0 + v1118 BinopI { op=shl, lhs=v1117, rhs_imm=32 } -> x1 + v1119 Extend { value=v1117, kind=I32 } -> x7 + v1120 Imm(8) -> x0 + v1121 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v1122 Load { addr=v744, disp=8, kind=I64 } -> x6 + v1123 Load { addr=v744, disp=0, kind=I64 } -> x0 + v1124 BinopI { op=shl, lhs=v1123, rhs_imm=32 } -> x1 + v1125 Extend { value=v1123, kind=I32 } -> x2 + v1126 CallExt { binding_idx=145, args=[v1119, v1122, v1125], fp_arg_mask=0x0 } -> x0 + v1127 StoreLocal { off=-9, value=v1126, kind=I64 } -> - + terminator Jmp(b237) (exit_acc=v1127) block 237 start_pc=0 - v1160 Phi { incoming=[b236:v1158, b240:v1169], kind=I64 } -> x12 terminator Jmp(b234) block 238 start_pc=0 - v1161 LoadLocal { off=-11, kind=I64 } -> x0 - v1162 BinopI { op=eq, lhs=v751, rhs_imm=32 } -> x0 - terminator Bz { cond=v1162, target=b241, fall=b239 } (exit_acc=v1162) + v1128 LoadLocal { off=-11, kind=I64 } -> x0 + v1129 BinopI { op=eq, lhs=v750, rhs_imm=32 } -> x0 + terminator Bz { cond=v1129, target=b241, fall=b239 } (exit_acc=v1129) block 239 start_pc=0 - v1163 LoadLocal { off=-7, kind=I64 } -> x0 - v1164 Load { addr=v745, disp=0, kind=I64 } -> x0 - v1165 BinopI { op=shl, lhs=v1164, rhs_imm=32 } -> x1 - v1166 Extend { value=v1164, kind=I32 } -> x7 - v1167 CallExt { binding_idx=148, args=[v1166], fp_arg_mask=0x0 } -> x12 - v1168 StoreLocal { off=-9, value=v1167, kind=I64 } -> - - terminator Jmp(b240) (exit_acc=v1168) + v1130 LoadLocal { off=-7, kind=I64 } -> x0 + v1131 Load { addr=v744, disp=0, kind=I64 } -> x0 + v1132 BinopI { op=shl, lhs=v1131, rhs_imm=32 } -> x1 + v1133 Extend { value=v1131, kind=I32 } -> x7 + v1134 CallExt { binding_idx=148, args=[v1133], fp_arg_mask=0x0 } -> x0 + v1135 StoreLocal { off=-9, value=v1134, kind=I64 } -> - + terminator Jmp(b240) (exit_acc=v1135) block 240 start_pc=0 - v1169 Phi { incoming=[b239:v1167, b243:v1201], kind=I64 } -> x12 terminator Jmp(b237) block 241 start_pc=0 - v1170 LoadLocal { off=-11, kind=I64 } -> x0 - v1171 BinopI { op=eq, lhs=v751, rhs_imm=33 } -> x0 - terminator Bz { cond=v1171, target=b244, fall=b242 } (exit_acc=v1171) + v1136 LoadLocal { off=-11, kind=I64 } -> x0 + v1137 BinopI { op=eq, lhs=v750, rhs_imm=33 } -> x0 + terminator Bz { cond=v1137, target=b244, fall=b242 } (exit_acc=v1137) block 242 start_pc=0 - v1172 LoadLocal { off=-7, kind=I64 } -> x0 - v1173 LoadLocal { off=-6, kind=I64 } -> x0 - v1174 Imm(8) -> x0 - v1175 BinopI { op=add, lhs=v749, rhs_imm=8 } -> x0 - v1176 Load { addr=v749, disp=8, kind=I64 } -> x0 - v1177 BinopI { op=shl, lhs=v1176, rhs_imm=3 } -> x0 - v1178 Binop { op=add, lhs=v745, rhs=v1177 } -> x0 - v1179 Imm(0) -> x1 - v1180 LoadLocal { off=-12, kind=I64 } -> x1 - v1181 Imm(-8) -> x1 - v1182 BinopI { op=add, lhs=v1178, rhs_imm=-8 } -> x1 - v1183 Load { addr=v1182, disp=0, kind=I64 } -> x7 - v1184 Imm(-16) -> x1 - v1185 BinopI { op=add, lhs=v1178, rhs_imm=-16 } -> x1 - v1186 Load { addr=v1185, disp=0, kind=I64 } -> x6 - v1187 Imm(-24) -> x1 - v1188 BinopI { op=add, lhs=v1178, rhs_imm=-24 } -> x1 - v1189 Load { addr=v1188, disp=0, kind=I64 } -> x2 - v1190 Imm(-32) -> x1 - v1191 BinopI { op=add, lhs=v1178, rhs_imm=-32 } -> x1 - v1192 Load { addr=v1191, disp=0, kind=I64 } -> x1 - v1193 Imm(-40) -> x8 - v1194 BinopI { op=add, lhs=v1178, rhs_imm=-40 } -> x8 - v1195 Load { addr=v1194, disp=0, kind=I64 } -> x8 - v1196 Imm(-48) -> x9 - v1197 BinopI { op=add, lhs=v1178, rhs_imm=-48 } -> x0 - v1198 Load { addr=v1197, disp=0, kind=I64 } -> x9 - v1199 CallExt { binding_idx=0, args=[v1183, v1186, v1189, v1192, v1195, v1198], fp_arg_mask=0x0 } -> x12 - v1200 StoreLocal { off=-9, value=v1199, kind=I64 } -> - - terminator Jmp(b243) (exit_acc=v1200) + v1138 LoadLocal { off=-7, kind=I64 } -> x0 + v1139 LoadLocal { off=-6, kind=I64 } -> x0 + v1140 Imm(8) -> x0 + v1141 BinopI { op=add, lhs=v748, rhs_imm=8 } -> x0 + v1142 Load { addr=v748, disp=8, kind=I64 } -> x0 + v1143 BinopI { op=shl, lhs=v1142, rhs_imm=3 } -> x0 + v1144 Binop { op=add, lhs=v744, rhs=v1143 } -> x0 + v1145 Imm(0) -> x1 + v1146 LoadLocal { off=-12, kind=I64 } -> x1 + v1147 Imm(-8) -> x1 + v1148 BinopI { op=add, lhs=v1144, rhs_imm=-8 } -> x1 + v1149 Load { addr=v1148, disp=0, kind=I64 } -> x7 + v1150 Imm(-16) -> x1 + v1151 BinopI { op=add, lhs=v1144, rhs_imm=-16 } -> x1 + v1152 Load { addr=v1151, disp=0, kind=I64 } -> x6 + v1153 Imm(-24) -> x1 + v1154 BinopI { op=add, lhs=v1144, rhs_imm=-24 } -> x1 + v1155 Load { addr=v1154, disp=0, kind=I64 } -> x2 + v1156 Imm(-32) -> x1 + v1157 BinopI { op=add, lhs=v1144, rhs_imm=-32 } -> x1 + v1158 Load { addr=v1157, disp=0, kind=I64 } -> x1 + v1159 Imm(-40) -> x8 + v1160 BinopI { op=add, lhs=v1144, rhs_imm=-40 } -> x8 + v1161 Load { addr=v1160, disp=0, kind=I64 } -> x8 + v1162 Imm(-48) -> x9 + v1163 BinopI { op=add, lhs=v1144, rhs_imm=-48 } -> x0 + v1164 Load { addr=v1163, disp=0, kind=I64 } -> x9 + v1165 CallExt { binding_idx=0, args=[v1149, v1152, v1155, v1158, v1161, v1164], fp_arg_mask=0x0 } -> x0 + v1166 StoreLocal { off=-9, value=v1165, kind=I64 } -> - + terminator Jmp(b243) (exit_acc=v1166) block 243 start_pc=0 - v1201 Phi { incoming=[b242:v1199, b246:v1210], kind=I64 } -> x12 terminator Jmp(b240) block 244 start_pc=0 - v1202 LoadLocal { off=-11, kind=I64 } -> x0 - v1203 BinopI { op=eq, lhs=v751, rhs_imm=34 } -> x0 - terminator Bz { cond=v1203, target=b247, fall=b245 } (exit_acc=v1203) + v1167 LoadLocal { off=-11, kind=I64 } -> x0 + v1168 BinopI { op=eq, lhs=v750, rhs_imm=34 } -> x0 + terminator Bz { cond=v1168, target=b247, fall=b245 } (exit_acc=v1168) block 245 start_pc=0 - v1204 LoadLocal { off=-7, kind=I64 } -> x0 - v1205 Load { addr=v745, disp=0, kind=I64 } -> x0 - v1206 BinopI { op=shl, lhs=v1205, rhs_imm=32 } -> x1 - v1207 Extend { value=v1205, kind=I32 } -> x7 - v1208 CallExt { binding_idx=54, args=[v1207], fp_arg_mask=0x0 } -> x12 - v1209 StoreLocal { off=-9, value=v1208, kind=I64 } -> - - terminator Jmp(b246) (exit_acc=v1209) + v1169 LoadLocal { off=-7, kind=I64 } -> x0 + v1170 Load { addr=v744, disp=0, kind=I64 } -> x0 + v1171 BinopI { op=shl, lhs=v1170, rhs_imm=32 } -> x1 + v1172 Extend { value=v1170, kind=I32 } -> x7 + v1173 CallExt { binding_idx=54, args=[v1172], fp_arg_mask=0x0 } -> x0 + v1174 StoreLocal { off=-9, value=v1173, kind=I64 } -> - + terminator Jmp(b246) (exit_acc=v1174) block 246 start_pc=0 - v1210 Phi { incoming=[b245:v1208, b249:v1216], kind=I64 } -> x12 terminator Jmp(b243) block 247 start_pc=0 - v1211 LoadLocal { off=-11, kind=I64 } -> x0 - v1212 BinopI { op=eq, lhs=v751, rhs_imm=35 } -> x0 - terminator Bz { cond=v1212, target=b250, fall=b248 } (exit_acc=v1212) + v1175 LoadLocal { off=-11, kind=I64 } -> x0 + v1176 BinopI { op=eq, lhs=v750, rhs_imm=35 } -> x0 + terminator Bz { cond=v1176, target=b250, fall=b248 } (exit_acc=v1176) block 248 start_pc=0 - v1213 LoadLocal { off=-7, kind=I64 } -> x0 - v1214 Load { addr=v745, disp=0, kind=I64 } -> x7 - v1215 CallExt { binding_idx=57, args=[v1214], fp_arg_mask=0x0 } -> x0 - terminator Jmp(b249) (exit_acc=v1215) + v1177 LoadLocal { off=-7, kind=I64 } -> x0 + v1178 Load { addr=v744, disp=0, kind=I64 } -> x7 + v1179 CallExt { binding_idx=57, args=[v1178], fp_arg_mask=0x0 } -> x0 + terminator Jmp(b249) (exit_acc=v1179) block 249 start_pc=0 - v1216 Phi { incoming=[b248:v743, b252:v1233], kind=I64 } -> x12 terminator Jmp(b246) block 250 start_pc=0 - v1217 LoadLocal { off=-11, kind=I64 } -> x0 - v1218 BinopI { op=eq, lhs=v751, rhs_imm=36 } -> x0 - terminator Bz { cond=v1218, target=b253, fall=b251 } (exit_acc=v1218) + v1180 LoadLocal { off=-11, kind=I64 } -> x0 + v1181 BinopI { op=eq, lhs=v750, rhs_imm=36 } -> x0 + terminator Bz { cond=v1181, target=b253, fall=b251 } (exit_acc=v1181) block 251 start_pc=0 - v1219 LoadLocal { off=-7, kind=I64 } -> x0 - v1220 Imm(16) -> x0 - v1221 BinopI { op=add, lhs=v745, rhs_imm=16 } -> x0 - v1222 Load { addr=v745, disp=16, kind=I64 } -> x7 - v1223 Imm(8) -> x0 - v1224 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v1225 Load { addr=v745, disp=8, kind=I64 } -> x0 - v1226 BinopI { op=shl, lhs=v1225, rhs_imm=32 } -> x1 - v1227 Extend { value=v1225, kind=I32 } -> x6 - v1228 Load { addr=v745, disp=0, kind=I64 } -> x0 - v1229 BinopI { op=shl, lhs=v1228, rhs_imm=32 } -> x1 - v1230 Extend { value=v1228, kind=I32 } -> x2 - v1231 CallExt { binding_idx=95, args=[v1222, v1227, v1230], fp_arg_mask=0x0 } -> x12 - v1232 StoreLocal { off=-9, value=v1231, kind=I64 } -> - - terminator Jmp(b252) (exit_acc=v1232) + v1182 LoadLocal { off=-7, kind=I64 } -> x0 + v1183 Imm(16) -> x0 + v1184 BinopI { op=add, lhs=v744, rhs_imm=16 } -> x0 + v1185 Load { addr=v744, disp=16, kind=I64 } -> x7 + v1186 Imm(8) -> x0 + v1187 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v1188 Load { addr=v744, disp=8, kind=I64 } -> x0 + v1189 BinopI { op=shl, lhs=v1188, rhs_imm=32 } -> x1 + v1190 Extend { value=v1188, kind=I32 } -> x6 + v1191 Load { addr=v744, disp=0, kind=I64 } -> x0 + v1192 BinopI { op=shl, lhs=v1191, rhs_imm=32 } -> x1 + v1193 Extend { value=v1191, kind=I32 } -> x2 + v1194 CallExt { binding_idx=95, args=[v1185, v1190, v1193], fp_arg_mask=0x0 } -> x0 + v1195 StoreLocal { off=-9, value=v1194, kind=I64 } -> - + terminator Jmp(b252) (exit_acc=v1195) block 252 start_pc=0 - v1233 Phi { incoming=[b251:v1231, b255:v1246], kind=I64 } -> x12 terminator Jmp(b249) block 253 start_pc=0 - v1234 LoadLocal { off=-11, kind=I64 } -> x0 - v1235 BinopI { op=eq, lhs=v751, rhs_imm=37 } -> x0 - terminator Bz { cond=v1235, target=b256, fall=b254 } (exit_acc=v1235) + v1196 LoadLocal { off=-11, kind=I64 } -> x0 + v1197 BinopI { op=eq, lhs=v750, rhs_imm=37 } -> x0 + terminator Bz { cond=v1197, target=b256, fall=b254 } (exit_acc=v1197) block 254 start_pc=0 - v1236 LoadLocal { off=-7, kind=I64 } -> x0 - v1237 Imm(16) -> x0 - v1238 BinopI { op=add, lhs=v745, rhs_imm=16 } -> x0 - v1239 Load { addr=v745, disp=16, kind=I64 } -> x7 - v1240 Imm(8) -> x0 - v1241 BinopI { op=add, lhs=v745, rhs_imm=8 } -> x0 - v1242 Load { addr=v745, disp=8, kind=I64 } -> x6 - v1243 Load { addr=v745, disp=0, kind=I64 } -> x0 - v1244 BinopI { op=shl, lhs=v1243, rhs_imm=32 } -> x1 - v1245 Extend { value=v1243, kind=I32 } -> x2 - v1246 CallExt { binding_idx=96, args=[v1239, v1242, v1245], fp_arg_mask=0x0 } -> x12 - v1247 StoreLocal { off=-9, value=v1246, kind=I64 } -> - - terminator Jmp(b255) (exit_acc=v1247) + v1198 LoadLocal { off=-7, kind=I64 } -> x0 + v1199 Imm(16) -> x0 + v1200 BinopI { op=add, lhs=v744, rhs_imm=16 } -> x0 + v1201 Load { addr=v744, disp=16, kind=I64 } -> x7 + v1202 Imm(8) -> x0 + v1203 BinopI { op=add, lhs=v744, rhs_imm=8 } -> x0 + v1204 Load { addr=v744, disp=8, kind=I64 } -> x6 + v1205 Load { addr=v744, disp=0, kind=I64 } -> x0 + v1206 BinopI { op=shl, lhs=v1205, rhs_imm=32 } -> x1 + v1207 Extend { value=v1205, kind=I32 } -> x2 + v1208 CallExt { binding_idx=96, args=[v1201, v1204, v1207], fp_arg_mask=0x0 } -> x0 + v1209 StoreLocal { off=-9, value=v1208, kind=I64 } -> - + terminator Jmp(b255) (exit_acc=v1209) block 255 start_pc=0 terminator Jmp(b252) block 256 start_pc=0 - v1248 LoadLocal { off=-11, kind=I64 } -> x0 - v1249 BinopI { op=eq, lhs=v751, rhs_imm=38 } -> x0 - terminator Bz { cond=v1249, target=b259, fall=b257 } (exit_acc=v1249) + v1210 LoadLocal { off=-11, kind=I64 } -> x0 + v1211 BinopI { op=eq, lhs=v750, rhs_imm=38 } -> x0 + terminator Bz { cond=v1211, target=b259, fall=b257 } (exit_acc=v1211) block 257 start_pc=0 - v1250 ImmData(1705) -> x7 - v1251 LoadLocal { off=-7, kind=I64 } -> x0 - v1252 Load { addr=v745, disp=0, kind=I64 } -> x6 - v1253 LoadLocal { off=-10, kind=I64 } -> x0 - v1254 CallExt { binding_idx=0, args=[v1250, v1252, v754], fp_arg_mask=0x0 } -> x0 - v1255 LoadLocal { off=-7, kind=I64 } -> x0 - v1256 Load { addr=v745, disp=0, kind=I64 } -> x0 - terminator Return(v1256) (exit_acc=v1256) + v1212 ImmData(1705) -> x7 + v1213 LoadLocal { off=-7, kind=I64 } -> x0 + v1214 Load { addr=v744, disp=0, kind=I64 } -> x6 + v1215 LoadLocal { off=-10, kind=I64 } -> x0 + v1216 CallExt { binding_idx=0, args=[v1212, v1214, v753], fp_arg_mask=0x0 } -> x0 + v1217 LoadLocal { off=-7, kind=I64 } -> x0 + v1218 Load { addr=v744, disp=0, kind=I64 } -> x0 + terminator Return(v1218) (exit_acc=v1218) block 258 start_pc=0 terminator Jmp(b255) block 259 start_pc=0 - v1257 ImmData(1726) -> x7 - v1258 LoadLocal { off=-11, kind=I64 } -> x0 - v1259 LoadLocal { off=-10, kind=I64 } -> x0 - v1260 CallExt { binding_idx=0, args=[v1257, v751, v754], fp_arg_mask=0x0 } -> x0 - v1261 Imm(-1) -> x0 - terminator Return(v1261) (exit_acc=v1261) + v1219 ImmData(1726) -> x7 + v1220 LoadLocal { off=-11, kind=I64 } -> x0 + v1221 LoadLocal { off=-10, kind=I64 } -> x0 + v1222 CallExt { binding_idx=0, args=[v1219, v750, v753], fp_arg_mask=0x0 } -> x0 + v1223 Imm(-1) -> x0 + terminator Return(v1223) (exit_acc=v1223) block 260 start_pc=0 terminator Jmp(b2) block 261 start_pc=0 From 96ef2a699c7ea318fc01a87a243c194fe0ce89e0 Mon Sep 17 00:00:00 2001 From: kromych Date: Wed, 1 Jul 2026 12:07:38 -0700 Subject: [PATCH 05/15] elf: give .gnu.version/.gnu.version_r real section headers 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) --- src/c5/object/elf.rs | 73 ++++++++++++++++++---- src/c5/tests/native_elf_x64.rs | 108 +++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 13 deletions(-) diff --git a/src/c5/object/elf.rs b/src/c5/object/elf.rs index 6aeea1153..909cc500f 100644 --- a/src/c5/object/elf.rs +++ b/src/c5/object/elf.rs @@ -192,6 +192,8 @@ const SHT_RELA: u32 = 4; const SHT_HASH: u32 = 5; const SHT_DYNAMIC: u32 = 6; const SHT_DYNSYM: u32 = 11; +const SHT_GNU_VERNEED: u32 = 0x6fff_fffe; +const SHT_GNU_VERSYM: u32 = 0x6fff_ffff; // Section header flags (Elf64_Shdr.sh_flags). const SHF_WRITE: u64 = 0x1; @@ -646,6 +648,7 @@ fn build_plt_symtab( build: &super::Build, text_vmaddr: u64, trampoline_size: u64, + text_shndx: u16, ) -> (Vec, Vec) { let imports = &build.imports.imports; debug_assert_eq!( @@ -693,11 +696,10 @@ fn build_plt_symtab( // local (the SHT_SYMTAB rule). st_info: (STB_LOCAL << 4) | STT_FUNC, st_other: 0, - // .text section index. Hard-coded to match the - // section-header table: NULL=0, .interp=1, - // .dynsym=2, .dynstr=3, .hash=4, .rela.dyn=5, - // .text=6. - st_shndx: 6, + // .text section index. Shifts by two when the version + // sections precede .rela.dyn (has_versions), so it is + // passed in rather than hard-coded. + st_shndx: text_shndx, st_value, st_size: trampoline_size, }, @@ -736,7 +738,7 @@ fn build_plt_symtab( st_name, st_info: (STB_LOCAL << 4) | STT_FUNC, st_other: 0, - st_shndx: 6, + st_shndx: text_shndx, st_value: text_vmaddr + start, st_size: end.saturating_sub(start), }, @@ -1472,6 +1474,11 @@ pub(super) fn write( import_versym[i] = idx; } let has_versions = !verneed_groups.is_empty(); + // Two extra allocated sections (.gnu.version / .gnu.version_r) precede + // .rela.dyn when has_versions, shifting .text onward by two. Symbols + // whose st_shndx names .text must use this shifted index. + let ver_shdrs: u16 = if has_versions { 2 } else { 0 }; + let text_shndx: u16 = 6 + ver_shdrs; let (gnu_version, gnu_version_r): (Vec, Vec) = if has_versions { let mut versym: Vec = Vec::with_capacity(total_dynsym * 2); versym.extend_from_slice(&0u16.to_le_bytes()); // null symbol @@ -1779,7 +1786,7 @@ pub(super) fn write( super::Machine::X86_64 => 6, // jmp qword ptr [rip+disp32] }; let (plt_symtab_bytes, plt_strtab_bytes) = if emit_plt_symtab { - build_plt_symtab(build, dwarf_text_vmaddr, trampoline_size) + build_plt_symtab(build, dwarf_text_vmaddr, trampoline_size, text_shndx) } else { (Vec::new(), alloc::vec![0u8]) }; @@ -1812,6 +1819,8 @@ pub(super) fn write( ".dynsym", ".dynstr", ".hash", + ".gnu.version", + ".gnu.version_r", ".rela.dyn", ".text", ".tdata", @@ -1872,15 +1881,16 @@ pub(super) fn write( // Section-header indices of the allocated sections, in the order // emitted below: .text=6, then optional .tdata, .dynamic, .got, then // optional .data, .tbss, .bss. Used to attribute each .dynsym export - // to its real section. - const TEXT_SHNDX: u16 = 6; - let data_shndx: u16 = 9 + has_tdata as u16; - let bss_shndx: u16 = 9 + has_tdata as u16 + has_data as u16 + has_tbss as u16; + // to its real section. `ver_shdrs` / `text_shndx` are computed near + // `has_versions` above so the PLT symtab can share the shifted index. + let data_shndx: u16 = 9 + ver_shdrs + has_tdata as u16; + let bss_shndx: u16 = 9 + ver_shdrs + has_tdata as u16 + has_data as u16 + has_tbss as u16; let n_section_headers: u64 = 1 // NULL + 1 // .interp + 1 // .dynsym + 1 // .dynstr + 1 // .hash + + ver_shdrs as u64 // .gnu.version + .gnu.version_r + 1 // .rela.dyn + 1 // .text + (if has_tdata { 1 } else { 0 }) // .tdata @@ -2064,7 +2074,7 @@ pub(super) fn write( ©_addrs, ©_sizes, ©_is_bss, - TEXT_SHNDX, + text_shndx, data_shndx, bss_shndx, ); @@ -2317,7 +2327,7 @@ pub(super) fn write( let dynsym_shdr_idx: u16 = 2; let dynstr_shdr_idx: u16 = 3; let _hash_shdr_idx: u16 = 4; - let _rela_shdr_idx: u16 = 5; + let _rela_shdr_idx: u16 = 5 + ver_shdrs; let _ = (dynsym_shdr_idx, dynstr_shdr_idx); // [0] NULL sentinel. @@ -2409,6 +2419,43 @@ pub(super) fn write( }, ); + // [optional] .gnu.version / .gnu.version_r -- symbol-version tables + // (SHT_GNU_versym / SHT_GNU_verneed). Emitted between .hash and + // .rela.dyn so the section-header order tracks the file/address + // order; present only when an import carries a version requirement. + if has_versions { + write_struct( + &mut out, + &Elf64Shdr { + sh_name: name_off(".gnu.version"), + sh_type: SHT_GNU_VERSYM, + sh_flags: SHF_ALLOC, + sh_addr: gnu_version_vmaddr, + sh_offset: gnu_version_off, + sh_size: gnu_version.len() as u64, + sh_link: dynsym_shdr_idx as u32, + sh_info: 0, + sh_addralign: 2, + sh_entsize: 2, + }, + ); + write_struct( + &mut out, + &Elf64Shdr { + sh_name: name_off(".gnu.version_r"), + sh_type: SHT_GNU_VERNEED, + sh_flags: SHF_ALLOC, + sh_addr: gnu_version_r_vmaddr, + sh_offset: gnu_version_r_off, + sh_size: gnu_version_r.len() as u64, + sh_link: dynstr_shdr_idx as u32, + sh_info: verneed_groups.len() as u32, + sh_addralign: 8, + sh_entsize: 0, + }, + ); + } + // [5] .rela.dyn -- relocations resolved at load time. write_struct( &mut out, diff --git a/src/c5/tests/native_elf_x64.rs b/src/c5/tests/native_elf_x64.rs index 6d4cd53f9..792a9e450 100644 --- a/src/c5/tests/native_elf_x64.rs +++ b/src/c5/tests/native_elf_x64.rs @@ -669,6 +669,114 @@ fn fixture_parity() { ); } +/// When a dynamic import binds a versioned default symbol, the writer +/// wires DT_VERSYM / DT_VERNEED into `.dynamic` and must also give the +/// `.gnu.version` / `.gnu.version_r` payloads real section headers so +/// section-based tooling (readelf -V, objcopy) can find them. Ties the +/// expectation to DT_VERSYM so the check is non-vacuous only when the +/// host glibc actually resolves a versioned default. +#[test] +fn versioned_import_emits_gnu_version_section_headers() { + const SHT_STRTAB: u32 = 3; + const SHT_DYNAMIC: u32 = 6; + const SHT_DYNSYM: u32 = 11; + const SHT_GNU_VERNEED: u32 = 0x6fff_fffe; + const SHT_GNU_VERSYM: u32 = 0x6fff_ffff; + let src = super::load_fixture("elf_symbol_version_default.c"); + let program = Compiler::new(super::with_prelude(&src)) + .compile() + .expect("compile"); + let bytes = emit_native(&program, Target::LinuxX64).expect("emit_native"); + let rd_u16 = |o: usize| u16::from_le_bytes(bytes[o..o + 2].try_into().unwrap()); + let rd_u32 = |o: usize| u32::from_le_bytes(bytes[o..o + 4].try_into().unwrap()); + let rd_u64 = |o: usize| u64::from_le_bytes(bytes[o..o + 8].try_into().unwrap()); + let e_shoff = rd_u64(0x28) as usize; + let e_shnum = rd_u16(0x3C) as usize; + let e_shstrndx = rd_u16(0x3E) as usize; + let shdr = |i: usize| e_shoff + i * 64; + let sh_name = |i: usize| rd_u32(shdr(i)); + let sh_type = |i: usize| rd_u32(shdr(i) + 4); + let sh_offset = |i: usize| rd_u64(shdr(i) + 24) as usize; + let sh_size = |i: usize| rd_u64(shdr(i) + 32) as usize; + let sh_link = |i: usize| rd_u32(shdr(i) + 40) as usize; + let sh_entsize = |i: usize| rd_u64(shdr(i) + 56); + let shstr_off = sh_offset(e_shstrndx); + let name_at = |noff: u32| -> String { + let start = shstr_off + noff as usize; + let len = bytes[start..].iter().position(|&b| b == 0).unwrap(); + String::from_utf8_lossy(&bytes[start..start + len]).into_owned() + }; + // Does `.dynamic` carry DT_VERSYM (0x6fff_fff0)? If so, the payloads + // exist and their section headers are required. + let mut has_dt_versym = false; + if let Some(dyn_i) = (0..e_shnum).find(|&i| sh_type(i) == SHT_DYNAMIC) { + let (off, size) = (sh_offset(dyn_i), sh_size(dyn_i)); + let mut p = off; + while p + 16 <= off + size { + let d_tag = rd_u64(p); + if d_tag == 0x6fff_fff0 { + has_dt_versym = true; + break; + } + if d_tag == 0 { + break; + } + p += 16; + } + } + if !has_dt_versym { + return; // no versioned import resolved on this host + } + let versym = (0..e_shnum) + .find(|&i| sh_type(i) == SHT_GNU_VERSYM) + .expect("DT_VERSYM present but no SHT_GNU_versym header"); + let verneed = (0..e_shnum) + .find(|&i| sh_type(i) == SHT_GNU_VERNEED) + .expect("DT_VERNEED present but no SHT_GNU_verneed header"); + assert_eq!(name_at(sh_name(versym)), ".gnu.version"); + assert_eq!(name_at(sh_name(verneed)), ".gnu.version_r"); + assert_eq!(sh_entsize(versym), 2); + assert_eq!( + sh_type(sh_link(versym)), + SHT_DYNSYM, + "versym.sh_link -> .dynsym" + ); + assert_eq!(sh_type(sh_link(verneed)), SHT_STRTAB); + assert_eq!(name_at(sh_name(sh_link(verneed))), ".dynstr"); + assert_eq!(e_shstrndx, e_shnum - 1, "shstrtab must be the last section"); + + // The version sections shift .text's index; the PLT .symtab's + // function symbols (e.g. `main`) must name the shifted .text index, + // not the pre-shift one, or a debugger / objdump attributes them to + // the wrong section. + const SHT_SYMTAB: u32 = 2; + let text_idx = (0..e_shnum) + .find(|&i| sh_type(i) == 1 && name_at(sh_name(i)) == ".text") // SHT_PROGBITS + .expect(".text section present") as u16; + let symtab = (0..e_shnum) + .find(|&i| sh_type(i) == SHT_SYMTAB) + .expect(".symtab present when a versioned import resolves"); + let (sym_off, sym_size) = (sh_offset(symtab), sh_size(symtab)); + let str_off = sh_offset(sh_link(symtab)); + let sym_name = |noff: u32| -> String { + let start = str_off + noff as usize; + let len = bytes[start..].iter().position(|&b| b == 0).unwrap(); + String::from_utf8_lossy(&bytes[start..start + len]).into_owned() + }; + let mut saw_main = false; + let mut p = sym_off; + while p + 24 <= sym_off + sym_size { + let st_name = rd_u32(p); + let st_shndx = rd_u16(p + 6); + if sym_name(st_name) == "main" { + saw_main = true; + assert_eq!(st_shndx, text_idx, "`main` st_shndx must name .text"); + } + p += 24; + } + assert!(saw_main, "`main` must appear in the PLT .symtab"); +} + /// Post-call sub-word extension on the libc return register. /// See the matching test in `super::native::atoi_negative_sign_extends`. /// The x86_64 ELF backend uses `movsxd` for `Sign32`; glibc From 515588383deae8fbfa224ec9eeb5506b3c0029d8 Mon Sep 17 00:00:00 2001 From: kromych Date: Wed, 1 Jul 2026 12:08:00 -0700 Subject: [PATCH 06/15] pe: correct x64 unwind rationale and lock the current shape 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) --- src/c5/object/pe.rs | 106 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 97 insertions(+), 9 deletions(-) diff --git a/src/c5/object/pe.rs b/src/c5/object/pe.rs index 97b7c5506..6d2842791 100644 --- a/src/c5/object/pe.rs +++ b/src/c5/object/pe.rs @@ -2373,6 +2373,11 @@ const UWOP_PUSH_NONVOL: u8 = 0; const UWOP_ALLOC_LARGE: u8 = 1; const UWOP_ALLOC_SMALL: u8 = 2; const UWOP_SET_FPREG: u8 = 3; +/// Non-volatile GPR save at a scaled RSP offset. Not emitted today (see +/// `build_unwind_codes`); referenced by the regression test that locks +/// the current no-SAVE_NONVOL shape until the prologue restructure. +#[cfg(test)] +const UWOP_SAVE_NONVOL: u8 = 4; /// Register encoding for rbp in `OpInfo` / `FrameRegister`. const UNWIND_REG_RBP: u8 = 5; @@ -2416,15 +2421,25 @@ fn push_alloc_code(codes: &mut Vec, code_offset: u8, size: u32) { /// the final alloc reverses the arg-spill so RSP and the return /// address land at the caller's frame. /// -/// TODO: the callee-saved GPRs this backend stores with `mov -/// [rsp+i*8],reg` (below rbp, after the frame allocation) are not -/// described. RIP/RSP/RBP recover exactly at any body fault, but a -/// debugger unwinding past this frame does not recover those -/// register values. Encoding them as `UWOP_SAVE_NONVOL` needs a -/// frame pointer that points into the frame (a nonzero scaled -/// `FrameOffset`), which this backend's `mov rbp,rsp` (offset 0, -/// rbp at the frame top) cannot express with the required positive -/// save offsets; that awaits a prologue change. +/// The callee-saved GPRs this backend stores with `mov [rsp+off],reg` +/// at the frame bottom (after the frame allocation) are not described. +/// RIP/RSP/RBP recover exactly at any body fault through the frame +/// pointer, but a debugger / profiler / SEH / C++ unwind crossing this +/// frame does not recover those GPR values. `UWOP_SAVE_NONVOL` cannot +/// describe the current saves: (a) unwind codes must be listed in +/// descending prologue offset, so the GPR saves (last in the prologue) +/// are always processed before `UWOP_SET_FPREG` and thus resolve against +/// the running `context->Rsp` rather than the reconstructed frame RSP; +/// (b) the body lowers each call as `sub rsp,scratch; call; add +/// rsp,scratch` with per-site scratch, so at a call return address +/// `context->Rsp` is `frame_rsp - scratch` and no fixed save offset is +/// correct at every sample point. A faithful description requires saving +/// the GPRs with `push` before the frame pointer is established so each +/// recovers via `UWOP_PUSH_NONVOL` (processed after `UWOP_SET_FPREG` +/// resets RSP to rbp). TODO: that push-before-setframe prologue +/// restructure (prologue + epilogue + the decoder in lockstep, plus an +/// 8*count shift of every rbp-relative local/spill offset). badc emits +/// no exception-using code today, so execution is unaffected until then. fn build_unwind_codes(uw: &super::FnUnwind) -> (Vec, u8, u8) { if uw.leaf { return (Vec::new(), 0, 0); @@ -3369,6 +3384,79 @@ mod tests { } } + /// Locks the documented unwind-metadata limitation: a non-leaf x64 + /// Windows function that spills callee-saved GPRs describes only the + /// frame-pointer prologue (SET_FPREG + PUSH_NONVOL rbp), never a + /// `UWOP_SAVE_NONVOL` for the GPR spills. When the push-before-setframe + /// prologue restructure lands, this assertion must flip to REQUIRE a + /// PUSH_NONVOL per spilled GPR. + #[test] + fn win64_gpr_spill_unwind_omits_save_nonvol() { + use crate::Compiler; + // Many simultaneously-live longs across a call force the allocator + // to spill callee-saved GPRs in a non-leaf frame. + let src = " + long g(long); + long f(long a, long b, long c, long d, long e, long h, long i) { + long r = g(a) + g(b); + return r + a * b + c * d + e * h + i * a + b * c + d * e; + } + int main(void) { return (int)f(1, 2, 3, 4, 5, 6, 7); } + "; + let program = Compiler::new(super::super::super::tests::with_prelude(src)) + .compile() + .expect("compile"); + let build = lower_for( + &program, + super::super::Target::WindowsX64, + super::super::NativeOptions::default(), + ) + .expect("lower"); + // Walk an UNWIND_CODE byte stream node-by-node, returning the op + // nibble of each node (ALLOC_LARGE carries extra size slots). + fn ops_of(codes: &[u8]) -> Vec { + let mut ops = Vec::new(); + let mut i = 0; + while i + 1 < codes.len() { + let op = codes[i + 1] & 0x0F; + let opinfo = codes[i + 1] >> 4; + ops.push(op); + let slots = match op { + 1 => { + if opinfo == 0 { + 2 + } else { + 3 + } + } // ALLOC_LARGE + _ => 1, + }; + i += slots * 2; + } + ops + } + let mut saw_non_leaf = false; + for uw in &build.fn_unwind { + if uw.leaf { + continue; + } + saw_non_leaf = true; + let (codes, _size_of_prolog, frame_reg) = build_unwind_codes(uw); + assert_eq!(frame_reg, UNWIND_REG_RBP); + let ops = ops_of(&codes); + assert!( + ops.contains(&UWOP_SET_FPREG), + "non-leaf must set the frame register" + ); + assert!(ops.contains(&UWOP_PUSH_NONVOL), "non-leaf must save rbp"); + assert!( + !ops.contains(&UWOP_SAVE_NONVOL), + "GPR spills are not (yet) described by UWOP_SAVE_NONVOL" + ); + } + assert!(saw_non_leaf, "expected at least one non-leaf frame"); + } + /// `DYNAMIC_BASE` / `HIGH_ENTROPY_VA` stay on for every /// image we emit, including TLS-using ones. TLS-free /// images have no absolute pointers in the file (every From a8646e13de44d6000e59fcf935020bb4064a30a6 Mon Sep 17 00:00:00 2001 From: kromych Date: Wed, 1 Jul 2026 12:08:23 -0700 Subject: [PATCH 07/15] mach-o: ULEB dylib ordinals past 15, flat-lookup nlist ordinal 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) --- src/c5/object/mach_o.rs | 132 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 120 insertions(+), 12 deletions(-) diff --git a/src/c5/object/mach_o.rs b/src/c5/object/mach_o.rs index b406d4b00..d823b1a54 100644 --- a/src/c5/object/mach_o.rs +++ b/src/c5/object/mach_o.rs @@ -158,6 +158,9 @@ const SDK_MACOS: u32 = 11 << 16; const BIND_OPCODE_DONE: u8 = 0x00; const BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: u8 = 0x10; +/// ULEB128 dylib ordinal, for 1-based ordinals past 15 that do not fit +/// the IMM opcode's 4-bit operand (). +const BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: u8 = 0x20; /// Select a special pseudo-dylib by signed immediate. Used for the /// flat-namespace lookup ordinal (`BIND_SPECIAL_DYLIB_FLAT_LOOKUP`, /// -2), which dyld resolves by searching every loaded image -- the @@ -203,6 +206,10 @@ const N_UNDF: u8 = 0x0; const N_SECT: u8 = 0xE; const N_EXT: u8 = 0x01; const NO_SECT: u8 = 0; +/// `DYNAMIC_LOOKUP_ORDINAL` (): the n_desc library +/// ordinal for a symbol resolved through the flat namespace rather than +/// a specific LC_LOAD_DYLIB. +const DYNAMIC_LOOKUP_ORDINAL: u8 = 0xFE; /// 1-based index of `__TEXT,__text` within the per-image /// section table. Mach-O numbers sections globally across /// all segments in declaration order; `__text` is the first @@ -1654,6 +1661,33 @@ fn build_rebase_opcodes( out } +/// Source library a bind opcode selects: the flat-lookup pseudo-dylib or +/// a 1-based LC_LOAD_DYLIB ordinal. +#[derive(PartialEq, Clone, Copy)] +enum BindSource { + Flat, + Dylib(u64), +} + +/// Emit the dylib-selection opcode for `source`. Uses the compact IMM +/// form for ordinals <= 15 (its operand is 4 bits) and the ULEB form +/// otherwise, so an ordinal past 15 binds against the right library +/// instead of wrapping. +fn push_bind_source(out: &mut Vec, source: BindSource) { + match source { + BindSource::Flat => { + out.push(BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | BIND_SPECIAL_DYLIB_FLAT_LOOKUP_IMM); + } + BindSource::Dylib(ord) if ord <= 0x0F => { + out.push(BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | (ord as u8)); + } + BindSource::Dylib(ord) => { + out.push(BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); + put_uleb128(out, ord); + } + } +} + fn build_bind_opcodes( imports: &super::ResolvedImports, segment: u8, @@ -1665,16 +1699,16 @@ fn build_bind_opcodes( // imports from the same source don't repeat it. A flat-lookup import // selects the special pseudo-dylib instead of an LC_LOAD_DYLIB // ordinal. - let mut current_selector: Option = None; + let mut current_source: Option = None; for (i, imp) in imports.imports.iter().enumerate() { - let selector = if imp.flat_lookup { - BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | BIND_SPECIAL_DYLIB_FLAT_LOOKUP_IMM + let source = if imp.flat_lookup { + BindSource::Flat } else { - BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | (((imp.dylib_index + 1) as u8) & 0x0F) + BindSource::Dylib((imp.dylib_index + 1) as u64) }; - if current_selector != Some(selector) { - out.push(selector); - current_selector = Some(selector); + if current_source != Some(source) { + push_bind_source(&mut out, source); + current_source = Some(source); } out.push(BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // flags = 0 out.extend_from_slice(imp.real_symbol.as_bytes()); @@ -1695,11 +1729,11 @@ fn build_bind_opcodes( .dylibs .iter() .position(|d| d.path.contains("libSystem")) - .map(|i| (i + 1) as u8) + .map(|i| (i + 1) as u64) .unwrap_or(1); - let bootstrap_selector = BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | (bootstrap_ordinal & 0x0F); - if current_selector != Some(bootstrap_selector) { - out.push(bootstrap_selector); + let bootstrap_source = BindSource::Dylib(bootstrap_ordinal); + if current_source != Some(bootstrap_source) { + push_bind_source(&mut out, bootstrap_source); } out.push(BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // flags = 0 out.extend_from_slice(TLV_BOOTSTRAP_SYMBOL.as_bytes()); @@ -1718,6 +1752,18 @@ fn build_bind_opcodes( out } +/// The nlist library ordinal for an import, mirroring the bind stream's +/// flat-lookup branch so the symbol table and the bind opcodes agree on +/// the symbol's provenance: a flat-lookup import carries +/// `DYNAMIC_LOOKUP_ORDINAL`, a two-level import its 1-based dylib ordinal. +fn import_library_ordinal(imp: &super::ResolvedImport) -> u8 { + if imp.flat_lookup { + DYNAMIC_LOOKUP_ORDINAL + } else { + (imp.dylib_index + 1) as u8 + } +} + /// One `nlist_64` for an undefined external symbol. `n_strx` is the /// byte offset of the symbol's name in the string table; `ordinal` /// is the 1-based dylib ordinal (which library exports the symbol). @@ -2202,7 +2248,7 @@ pub(super) fn write(program: &Program, build: &Build) -> Result, C5Error // `str_indices` are shifted past the locals + exports. for (j, imp) in build.imports.imports.iter().enumerate() { let n_strx = str_indices[n_locals + n_exports + j]; - let ordinal = (imp.dylib_index + 1) as u8; + let ordinal = import_library_ordinal(imp); symtab.extend_from_slice(&nlist_undef(n_strx, ordinal)); } if tls_present { @@ -2944,6 +2990,68 @@ mod tests { } } + fn sample_import(dylib_index: usize, flat_lookup: bool) -> super::super::ResolvedImport { + use super::super::ResolvedImport; + ResolvedImport { + binding_idx: dylib_index as i64, + local_name: format!("s{dylib_index}"), + real_symbol: format!("_s{dylib_index}"), + dylib_index, + flat_lookup, + is_variadic: false, + fixed_args: 0, + return_type_tag: 0, + returns_long_double: false, + param_types: Vec::new(), + } + } + + #[test] + fn bind_dylib_ordinal_past_15_uses_uleb() { + use super::super::ResolvedImports; + use crate::c5::codegen::ResolvedDylib; + // 20 distinct dylibs: ordinals 16 and 17 (dylib_index 15 / 16) + // exceed the IMM opcode's 4-bit operand and must switch to the + // ULEB form instead of wrapping to a wrong library. + let dylibs: Vec = (0..20) + .map(|i| ResolvedDylib { + name: format!("lib{i}"), + path: format!("/lib{i}.dylib"), + }) + .collect(); + let imports = ResolvedImports { + data_bindings: Default::default(), + imports: (0..20).map(|i| sample_import(i, false)).collect(), + dylibs, + }; + let out = build_bind_opcodes(&imports, 1, None); + assert!( + out.windows(2) + .any(|w| w == [BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB, 0x10]), + "ordinal 16 must use the ULEB selector" + ); + assert!( + out.windows(2) + .any(|w| w == [BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB, 0x11]), + "ordinal 17 must use the ULEB selector" + ); + } + + #[test] + fn flat_lookup_nlist_carries_dynamic_lookup_ordinal() { + // A flat-lookup import's nlist library ordinal must match the + // bind stream's flat-lookup provenance (DYNAMIC_LOOKUP_ORDINAL), + // not a two-level ordinal pointing at the first dylib. + let flat = sample_import(0, true); + assert_eq!(import_library_ordinal(&flat), DYNAMIC_LOOKUP_ORDINAL); + let bytes = nlist_undef(0, import_library_ordinal(&flat)); + let n_desc = u16::from_le_bytes([bytes[6], bytes[7]]); + assert_eq!((n_desc >> 8) as u8, DYNAMIC_LOOKUP_ORDINAL); + // A two-level import still reports its 1-based dylib ordinal. + let two_level = sample_import(0, false); + assert_eq!(import_library_ordinal(&two_level), 1); + } + #[test] fn export_trie_round_trips() { // Shared prefixes ("_Init...") exercise the radix edge splitting. From 360db1637423bb7fe5ec33250d1df76b8793ad5f Mon Sep 17 00:00:00 2001 From: kromych Date: Wed, 1 Jul 2026 12:08:48 -0700 Subject: [PATCH 08/15] link: resolve weak defined symbols with strong-wins precedence 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) --- src/c5/linker/link.rs | 149 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 2 deletions(-) diff --git a/src/c5/linker/link.rs b/src/c5/linker/link.rs index ee0659497..62e015c36 100644 --- a/src/c5/linker/link.rs +++ b/src/c5/linker/link.rs @@ -383,10 +383,18 @@ pub fn link_native_objects_with_options( // definitions (same name in two units) error out -- the // ELF rule for `STB_GLOBAL` is "exactly one definition". let mut defined: BTreeMap = BTreeMap::new(); + // STB_WEAK (binding 2) defined symbols. ELF/SysV treats a weak + // definition as a real but overridable definition: a strong + // (STB_GLOBAL) definition of the same name wins with no + // multiple-definition error, and a weak definition on its own + // satisfies a reference. Collected separately, then folded into + // `defined` for every name no strong definition claims. + let mut weak_defined: BTreeMap = BTreeMap::new(); for (i, obj) in objs.iter().enumerate() { for sym in &obj.symbols { - if sym.binding != 1 { - // STB_GLOBAL = 1 + // STB_LOCAL (0) routes through the static-func pass; only + // STB_GLOBAL (1) and STB_WEAK (2) join the merged table here. + if sym.binding != 1 && sym.binding != 2 { continue; } if matches!(sym.section, NativeSymSection::Undef | NativeSymSection::Abs) { @@ -406,6 +414,11 @@ pub fn link_native_objects_with_options( value: base as u64 + sym.value, size: sym.size, }; + if sym.binding == 2 { + // Multiple weak definitions -- keep the first, no error. + weak_defined.entry(sym.name.clone()).or_insert(merged); + continue; + } if let Some(prev) = defined.get(&sym.name) { return Err(link_err(&format!( "multiple definition of `{}` (first at offset 0x{:x}, also at 0x{:x})", @@ -415,6 +428,12 @@ pub fn link_native_objects_with_options( defined.insert(sym.name.clone(), merged); } } + // Fold weak definitions in behind strong ones: a name a strong def + // already claims keeps the strong entry (strong wins, silently); + // every other weak name becomes its own defined entry. + for (name, merged) in weak_defined { + defined.entry(name).or_insert(merged); + } // Pass 2.1 -- collect synthetic prologue-end anchors. The // per-`.o` writer (`elf_reloc.rs`) emits one @@ -2242,6 +2261,132 @@ mod tests { assert_eq!(merged.bss_size, 0, "Common dropped, no bss slot"); } + // STB_WEAK defined symbols from a foreign object must resolve a + // reference rather than being dropped (ELF/SysV: a weak definition + // is a real, overridable definition). A strong definition of the + // same name wins, order-independently, with no multiple-definition + // error. + #[test] + fn weak_defined_symbol_resolves_and_yields_to_strong() { + use super::super::object::{NativeReloc, NativeSymbol}; + let null_sym = || NativeSymbol { + name: alloc::string::String::new(), + section: NativeSymSection::Undef, + value: 0, + size: 0, + binding: 0, + kind: 0, + }; + let mk = |text: alloc::vec::Vec, + data: alloc::vec::Vec, + symbols: alloc::vec::Vec, + text_relocs: alloc::vec::Vec| + -> NativeObject { + NativeObject { + machine: NativeMachine::X86_64, + text, + data, + bss_size: 0, + tls_data: alloc::vec::Vec::new(), + tls_bss_size: 0, + symbols, + text_relocs, + data_relocs: alloc::vec::Vec::new(), + dylibs: alloc::vec::Vec::new(), + import_dylib_map: alloc::vec::Vec::new(), + exports: alloc::vec::Vec::new(), + tls_index_fixups: alloc::vec::Vec::new(), + macho_tlv_descriptors: alloc::vec::Vec::new(), + macho_tlv_fixups: alloc::vec::Vec::new(), + tls_symbols: alloc::vec::Vec::new(), + macho_tlv_descriptor_syms: alloc::vec::Vec::new(), + elf_tpoff_fixups: alloc::vec::Vec::new(), + copy_relocs: alloc::vec::Vec::new(), + debug_info: alloc::vec::Vec::new(), + debug_abbrev: alloc::vec::Vec::new(), + debug_line: alloc::vec::Vec::new(), + debug_str: alloc::vec::Vec::new(), + debug_info_relocs: alloc::vec::Vec::new(), + debug_line_relocs: alloc::vec::Vec::new(), + } + }; + // Weak definition of `weak_target` in `.text`. + let weak_unit = || { + mk( + alloc::vec![0xC3], + alloc::vec::Vec::new(), + alloc::vec![ + null_sym(), + NativeSymbol { + name: "weak_target".to_string(), + section: NativeSymSection::Text, + value: 0, + size: 1, + binding: 2, + kind: 2, + }, + ], + alloc::vec::Vec::new(), + ) + }; + // `call weak_target` (R_X86_64_PC32) with an UNDEF reference. + let ref_unit = mk( + alloc::vec![0xE8, 0, 0, 0, 0], + alloc::vec::Vec::new(), + alloc::vec![ + null_sym(), + NativeSymbol { + name: "weak_target".to_string(), + section: NativeSymSection::Undef, + value: 0, + size: 0, + binding: 1, + kind: 0, + }, + ], + alloc::vec![NativeReloc { + offset: 1, + sym_idx: 1, + rtype: 2, + addend: -4, + }], + ); + // A weak def now satisfies the reference (previously "undefined + // reference to `weak_target`"). + let merged = link_native_objects(&[weak_unit(), ref_unit]).expect("weak def resolves"); + let def = merged.defined.get("weak_target").expect("weak def present"); + assert!(matches!(def.section, NativeSymSection::Text)); + + // Strong definition in `.data`. + let strong_unit = || { + mk( + alloc::vec::Vec::new(), + alloc::vec![0u8; 4], + alloc::vec![ + null_sym(), + NativeSymbol { + name: "weak_target".to_string(), + section: NativeSymSection::Data, + value: 0, + size: 4, + binding: 1, + kind: 1, + }, + ], + alloc::vec::Vec::new(), + ) + }; + // Strong wins over weak, independent of link order, no error. + for pair in [[weak_unit(), strong_unit()], [strong_unit(), weak_unit()]] { + let merged = link_native_objects(&pair).expect("strong+weak links"); + let def = merged.defined.get("weak_target").expect("resolved"); + assert!( + matches!(def.section, NativeSymSection::Data), + "strong definition must win" + ); + } + } + // A code reference and a data initializer that both name the same // wholly-zero global must resolve to the same byte in the `.bss` // region. Before the fix the code reference parked a bss-relative From e184a85919a09c4e0201c3854e3a120e136fbfd2 Mon Sep 17 00:00:00 2001 From: kromych Date: Wed, 1 Jul 2026 12:08:59 -0700 Subject: [PATCH 09/15] driver: diagnose unrecognized input extensions in compile modes 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) --- src/main.rs | 24 +++++++++++++++---- tests/cli_fixture_smoke.rs | 49 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3f1732988..272c6124b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -609,6 +609,9 @@ fn main() { let mut objects: Vec = Vec::new(); let mut archives: Vec = Vec::new(); let mut prog_args_start: usize = args.len(); + // Program argv is consumed only by --jit / --interp; every other + // mode links or preprocesses its inputs and has no argv tail. + let takes_prog_args = matches!(mode, Mode::Jit | Mode::Interp); for (i, a) in args.iter().enumerate().skip(1) { if a == "-" { sources.push(a.clone()); @@ -623,11 +626,22 @@ fn main() { "o" => objects.push(a.clone()), "a" => archives.push(a.clone()), _ => { - // First unrecognised entry marks the boundary; - // everything from here on is the C program's - // argv (so `badc foo.c arg1 arg2` still works). - prog_args_start = i; - break; + // In --jit / --interp the first unrecognised entry marks + // the boundary; everything after it is the program's argv + // (so `badc --jit foo.c arg1 arg2` still works). Every + // other mode links or preprocesses its inputs and has no + // argv, so an unrecognised extension is a mistyped or + // unsupported input and is reported rather than silently + // dropped along with every input after it. + if takes_prog_args { + prog_args_start = i; + break; + } + eprint_diagnostic(format!( + "badc: error: unrecognized input file extension: `{a}` \ + (expected a `.c` source, `.o` object, or `.a` archive)" + )); + std::process::exit(1); } } } diff --git a/tests/cli_fixture_smoke.rs b/tests/cli_fixture_smoke.rs index 36e7d1af1..b25fb049c 100644 --- a/tests/cli_fixture_smoke.rs +++ b/tests/cli_fixture_smoke.rs @@ -378,3 +378,52 @@ fn unknown_option_is_rejected() { assert!(ok.status.success(), "valid build must still succeed"); let _ = std::fs::remove_dir_all(&dir); } + +#[cfg(any(target_os = "linux", target_os = "macos"))] +#[test] +fn unrecognized_input_extension_is_rejected() { + // A compile/link mode must diagnose an unrecognized input extension + // rather than silently reclassifying it (and every input after it) + // as the program's argv. + let badc = env!("CARGO_BIN_EXE_badc"); + let dir = std::env::temp_dir().join(format!("badc-unkext-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).expect("create temp dir"); + let a = dir.join("a.c"); + let b = dir.join("b.c"); + let cc = dir.join("foo.cc"); + std::fs::write(&a, "int helper(void); int main(void){ return helper(); }\n").expect("write a"); + std::fs::write(&b, "int helper(void){ return 0; }\n").expect("write b"); + std::fs::write(&cc, "int nope(void){ return 0; }\n").expect("write cc"); + let out = Command::new(badc) + .arg(&a) + .arg(&cc) + .arg(&b) + .arg("-o") + .arg(dir.join("prog")) + .output() + .expect("run badc"); + assert!( + !out.status.success(), + "unrecognized input extension must fail the build" + ); + let stderr = String::from_utf8_lossy(&out.stderr); + assert!( + stderr.contains("unrecognized input file extension") && stderr.contains("foo.cc"), + "expected a diagnostic naming foo.cc, got: {stderr}" + ); + // Two valid `.c` inputs still link (b.c is not silently dropped in + // the valid case). + let ok = Command::new(badc) + .arg(&a) + .arg(&b) + .arg("-o") + .arg(dir.join("prog2")) + .output() + .expect("run badc"); + assert!( + ok.status.success(), + "valid multi-input native link must succeed" + ); + let _ = std::fs::remove_dir_all(&dir); +} From b5bbc28222e70d5272bd30dd3dca66dc305b0953 Mon Sep 17 00:00:00 2001 From: kromych Date: Wed, 1 Jul 2026 12:09:04 -0700 Subject: [PATCH 10/15] stdint: use the -MAX-1 idiom for INT32_MIN / INT64_MIN 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) --- headers/include/stdint.h | 8 +- .../c/stdint_min_macros_type_and_value.c | 21 ++++ ...dint_min_macros_type_and_value.aarch64.asm | 49 ++++++++ .../stdint_min_macros_type_and_value.x64.asm | 52 ++++++++ .../ssa/stdint_min_macros_type_and_value.ssa | 118 ++++++++++++++++++ 5 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/c/stdint_min_macros_type_and_value.c create mode 100644 tests/snapshots/asm/stdint_min_macros_type_and_value.aarch64.asm create mode 100644 tests/snapshots/asm/stdint_min_macros_type_and_value.x64.asm create mode 100644 tests/snapshots/ssa/stdint_min_macros_type_and_value.ssa diff --git a/headers/include/stdint.h b/headers/include/stdint.h index 78f6a00fd..15a900dbe 100644 --- a/headers/include/stdint.h +++ b/headers/include/stdint.h @@ -70,8 +70,12 @@ typedef uint64_t uint_fast64_t; #define INT8_MIN (-128) #define INT16_MIN (-32768) -#define INT32_MIN (-2147483648) -#define INT64_MIN (-9223372036854775808) +/* C99 7.18.2: written as `-MAX-1` so the positive operand of unary minus + stays in range for the macro's exact-width signed type (matching the + limits.h idiom). `2147483648` exceeds INT_MAX and `9223372036854775808` + is unrepresentable in any signed type. */ +#define INT32_MIN (-2147483647-1) +#define INT64_MIN (-9223372036854775807LL-1) #define INT8_MAX 127 #define INT16_MAX 32767 diff --git a/tests/fixtures/c/stdint_min_macros_type_and_value.c b/tests/fixtures/c/stdint_min_macros_type_and_value.c new file mode 100644 index 000000000..827e8f76c --- /dev/null +++ b/tests/fixtures/c/stdint_min_macros_type_and_value.c @@ -0,0 +1,21 @@ +// C99 7.18.2 exact-width minimum macros. INT32_MIN / INT64_MIN must use +// the `-MAX-1` idiom so the positive operand of unary minus stays in +// range for the macro's signed exact-width type; the direct literal +// form makes INT32_MIN take a wider type and INT64_MIN unrepresentable +// in any signed type. +#include + +// Compile-time type lock: a wrong promoted type makes the array +// dimension non-positive and fails compilation. +typedef char check_i32_width[sizeof(INT32_MIN) == 4 ? 1 : -1]; +typedef char check_i64_width[sizeof(INT64_MIN) == 8 ? 1 : -1]; + +int main(void) { + if (INT32_MIN >= 0) return 21; + if (INT32_MIN + INT32_MAX != -1) return 22; + if (INT64_MIN >= 0) return 10; + if (INT64_MIN + INT64_MAX != -1) return 11; + // Every derived signed minimum aliases INT64_MIN. + if (INTMAX_MIN >= 0 || INTPTR_MIN >= 0 || PTRDIFF_MIN >= 0) return 30; + return 0; +} diff --git a/tests/snapshots/asm/stdint_min_macros_type_and_value.aarch64.asm b/tests/snapshots/asm/stdint_min_macros_type_and_value.aarch64.asm new file mode 100644 index 000000000..5c323a604 --- /dev/null +++ b/tests/snapshots/asm/stdint_min_macros_type_and_value.aarch64.asm @@ -0,0 +1,49 @@ + +stdint_min_macros_type_and_value.aarch64: file format elf64-littleaarch64 + +Disassembly of section .text: + +<.text>: + mov x29, #0x0 // =0 + mov x0, sp + mov x1, #0x220 // =544 + movk x1, #0x0, lsl #16 + b + brk #: + stp x29, x30, [sp, #-0x10]! + mov x29, sp + sub sp, sp, #0x10 + b + mov x0, #0x15 // =21 + add sp, sp, #0x10 + ldp x29, x30, [sp], #0x10 + ret + b + mov x0, #0x16 // =22 + add sp, sp, #0x10 + ldp x29, x30, [sp], #0x10 + ret + b + mov x0, #0xa // =10 + add sp, sp, #0x10 + ldp x29, x30, [sp], #0x10 + ret + b + mov x0, #0xb // =11 + add sp, sp, #0x10 + ldp x29, x30, [sp], #0x10 + ret + mov x2, #0x1 // =1 + mov x2, #0x0 // =0 + cbnz x2, + mov x2, #0x0 // =0 + cbz x2, + mov x0, #0x1e // =30 + add sp, sp, #0x10 + ldp x29, x30, [sp], #0x10 + ret + mov x0, #0x0 // =0 + add sp, sp, #0x10 + ldp x29, x30, [sp], #0x10 + ret + b diff --git a/tests/snapshots/asm/stdint_min_macros_type_and_value.x64.asm b/tests/snapshots/asm/stdint_min_macros_type_and_value.x64.asm new file mode 100644 index 000000000..3b08365d0 --- /dev/null +++ b/tests/snapshots/asm/stdint_min_macros_type_and_value.x64.asm @@ -0,0 +1,52 @@ + +stdint_min_macros_type_and_value.x64: file format elf64-x86-64 + +Disassembly of section .text: + +<.text>: + xorl %ebp, %ebp + movq %rsp, %rdi + movl $, %esi + callq + ud2 + +
: + pushq %rbp + movq %rsp, %rbp + subq $0x10, %rsp + jmp + movl $0x15, %eax + addq $0x10, %rsp + popq %rbp + retq + jmp + movl $0x16, %eax + addq $0x10, %rsp + popq %rbp + retq + jmp + movl $0xa, %eax + addq $0x10, %rsp + popq %rbp + retq + jmp + movl $0xb, %eax + addq $0x10, %rsp + popq %rbp + retq + movl $0x1, %edx + xorq %rdx, %rdx + testq %rdx, %rdx + jne + xorq %rdx, %rdx + testq %rdx, %rdx + je + movl $0x1e, %eax + addq $0x10, %rsp + popq %rbp + retq + xorq %rax, %rax + addq $0x10, %rsp + popq %rbp + retq + jmp diff --git a/tests/snapshots/ssa/stdint_min_macros_type_and_value.ssa b/tests/snapshots/ssa/stdint_min_macros_type_and_value.ssa new file mode 100644 index 000000000..0eee05999 --- /dev/null +++ b/tests/snapshots/ssa/stdint_min_macros_type_and_value.ssa @@ -0,0 +1,118 @@ +; --- SSA dump (ok=true) ent_pc=0 --- +; name=main +fn ent_pc=0 n_params=0 variadic=false locals=2 + spill_count=0 gpr_used=[] fp_used=[] + block 0 start_pc=0 + v0 AllocaInit(0) -> - + v1 Imm(-2147483648) -> x0 + v2 Imm(-9223372036854775808) -> x0 + v3 Imm(0) -> x0 + terminator Jmp(b2) (exit_acc=v3) + block 1 start_pc=0 + v4 Imm(21) -> x0 + terminator Return(v4) (exit_acc=v4) + block 2 start_pc=0 + v5 Imm(-2147483648) -> x0 + v6 Imm(-9223372036854775808) -> x0 + v7 Imm(-1) -> x0 + v8 Imm(-4294967296) -> x0 + v9 Imm(0) -> x0 + terminator Jmp(b4) (exit_acc=v9) + block 3 start_pc=0 + v10 Imm(22) -> x0 + terminator Return(v10) (exit_acc=v10) + block 4 start_pc=0 + v11 Imm(-9223372036854775808) -> x0 + v12 Imm(0) -> x0 + terminator Jmp(b6) (exit_acc=v12) + block 5 start_pc=0 + v13 Imm(10) -> x0 + terminator Return(v13) (exit_acc=v13) + block 6 start_pc=0 + v14 Imm(-9223372036854775808) -> x0 + v15 Imm(-1) -> x0 + v16 Imm(0) -> x0 + terminator Jmp(b8) (exit_acc=v16) + block 7 start_pc=0 + v17 Imm(11) -> x0 + terminator Return(v17) (exit_acc=v17) + block 8 start_pc=0 + v18 Imm(-9223372036854775808) -> x0 + v19 Imm(0) -> x0 + v20 Imm(1) -> x2 + v21 Imm(0) -> x1 + terminator Jmp(b9) (exit_acc=v19) + block 9 start_pc=0 + v22 Imm(-9223372036854775808) -> x0 + v23 Imm(0) -> x2 + v24 Imm(0) -> x0 + terminator Jmp(b10) (exit_acc=v23) + block 10 start_pc=0 + v25 Phi { incoming=[b8:v20, b9:v23], kind=I64 } -> x2 + v26 LoadLocal { off=-2, kind=I64 } -> x0 + v27 Imm(0) -> x0 + terminator Bnz { cond=v25, target=b15, fall=b11 } (exit_acc=v25) + block 11 start_pc=0 + v28 Imm(-9223372036854775808) -> x0 + v29 Imm(0) -> x2 + v30 Imm(0) -> x0 + terminator Jmp(b12) (exit_acc=v29) + block 12 start_pc=0 + v31 Phi { incoming=[b15:v25, b11:v29], kind=I64 } -> x2 + v32 LoadLocal { off=-1, kind=I64 } -> x0 + terminator Bz { cond=v31, target=b14, fall=b13 } (exit_acc=v31) + block 13 start_pc=0 + v33 Imm(30) -> x0 + terminator Return(v33) (exit_acc=v33) + block 14 start_pc=0 + v34 Imm(0) -> x0 + terminator Return(v34) (exit_acc=v34) + block 15 start_pc=0 + terminator Jmp(b12) +; --- SSA dump (ok=true) ent_pc=0 --- +; name=__c5_exit +fn ent_pc=0 n_params=1 variadic=false locals=1 + spill_count=0 gpr_used=[] fp_used=[] + block 0 start_pc=0 + v0 AllocaInit(0) -> - + v1 ParamRef(0, kind=I32) -> x7 + v2 Imm(0) -> x0 + v3 LoadLocal { off=2, kind=I32 } -> x0 + v4 CallExt { binding_idx=0, args=[v1], fp_arg_mask=0x0 } -> x0 + v5 Imm(0) -> x0 + terminator Return(v5) (exit_acc=v5) +; --- SSA dump (ok=true) ent_pc=1 --- +; name=__c5_entry +fn ent_pc=1 n_params=2 variadic=false locals=6 + spill_count=0 gpr_used=[3] fp_used=[] + block 0 start_pc=0 + v0 AllocaInit(0) -> - + v1 ParamRef(0, kind=I64) -> x7 + v2 Imm(0) -> x0 + v3 ParamRef(1, kind=I64) -> x6 + v4 Imm(0) -> x0 + v5 LoadLocal { off=3, kind=I64 } -> x0 + v6 BinopI { op=and, lhs=v3, rhs_imm=255 } -> x0 + v7 LoadLocal { off=2, kind=I64 } -> x0 + v8 Imm(0) -> x0 + v9 LoadLocal { off=-1, kind=I64 } -> x0 + v10 Imm(0) -> x3 + v11 Load { addr=v1, disp=0, kind=I64 } -> x0 + v12 BinopI { op=shl, lhs=v11, rhs_imm=32 } -> x1 + v13 Extend { value=v11, kind=I32 } -> x0 + v14 Imm(0) -> x1 + v15 Imm(8) -> x1 + v16 BinopI { op=add, lhs=v1, rhs_imm=8 } -> x6 + v17 Imm(0) -> x1 + v18 ImmData(24) -> x1 + v19 LoadLocal { off=-3, kind=I64 } -> x2 + v20 LoadLocal { off=-2, kind=I32 } -> x2 + v21 BinopI { op=shl, lhs=v13, rhs_imm=3 } -> x2 + v22 Binop { op=add, lhs=v16, rhs=v21 } -> x2 + v23 BinopI { op=add, lhs=v22, rhs_imm=8 } -> x2 + v24 Store { addr=v18, disp=0, value=v23, kind=I64 } -> - + v25 LoadLocal { off=-2, kind=I32 } -> x1 + v26 LoadLocal { off=-3, kind=I64 } -> x1 + v27 Call { target_pc=3, args=[v13, v16], fixed_args=2, fp_return=false, fp_arg_mask=0x0 } -> x7 + v28 Call { target_pc=0, args=[v27], fixed_args=1, fp_return=false, fp_arg_mask=0x0 } -> x0 + terminator Return(v10) (exit_acc=v10) From 28c272681469d89b7855dbd5471dda23d556651f Mon Sep 17 00:00:00 2001 From: kromych Date: Wed, 1 Jul 2026 12:09:15 -0700 Subject: [PATCH 11/15] docs: record the #if string-literal extension and the x64 unwind gap 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) --- README.md | 4 ++++ std-conformance.md | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/README.md b/README.md index ca792321e..a55f7b3c9 100644 --- a/README.md +++ b/README.md @@ -335,6 +335,10 @@ with user identifiers: __linux__ // Linux targets only ``` +Comparing the string-literal predefines with `#if X == "..."` is a c5 +extension over C99, which restricts a `#if` controlling expression to an +integer constant expression (see `std-conformance.md`). + The MSVC/MinGW mimicry surface (`_MSC_VER` / `__MINGW32__` / `__int64` / `__declspec` / etc.) lives in `headers/include/msvc_compat.h` and is opted into per translation unit with `-include msvc_compat.h`. diff --git a/std-conformance.md b/std-conformance.md index 998d674d6..7da1ae9df 100644 --- a/std-conformance.md +++ b/std-conformance.md @@ -169,6 +169,12 @@ is C99 plus the C11 features real code gates on this macro (`_Static_assert`, documentation-only includes keep compiling. clang / gcc treat both as fatal. - `__BADC_VERSION__`, `__BADC_TARGET__`, `__BADC_WINDOWS__` predefines. +- Extension: a `#if` / `#elif` controlling expression accepts string-literal + operands to `==` / `!=` (e.g. `#if __BADC_TARGET__ == "macos-aarch64"`, + `#if __BADC_VERSION__ == "0.1.0"`). C99 6.10.1p4 restricts `#if` to an + integer constant expression; c5 permits string equality so the + string-valued `__BADC_TARGET__` / `__BADC_VERSION__` predefines can gate + source. Strings remain rejected in every other operator context. ## Roadmap @@ -176,3 +182,9 @@ is C99 plus the C11 features real code gates on this macro (`_Static_assert`, returns a struct by value (the by-value argument direction already rides the host ABI). 2. `volatile`-honored loads / stores. +3. x86_64 Windows UNWIND_INFO describes only the frame-pointer prologue + (RIP/RSP/RBP recover exactly); callee-saved GPR spills are not yet + described, so a debugger / profiler / SEH unwind crossing such a frame + does not recover those registers. Program execution is unaffected -- + badc emits no exception-using code. A faithful description needs a + push-before-setframe prologue restructure. From 4591b7e4b7aa8db413c4b27ca931aafe0adf617b Mon Sep 17 00:00:00 2001 From: kromych Date: Wed, 1 Jul 2026 14:46:09 -0700 Subject: [PATCH 12/15] link: route data-binding imports through the host name and owning dylib 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 --- src/c5/codegen/mod.rs | 17 +++-- src/c5/linker/synth_build.rs | 15 +++- src/c5/object/elf_reloc.rs | 14 +++- src/c5/object/mod.rs | 5 +- src/c5/tests/codegen.rs | 130 +++++++++++++++++++++++++++++++++++ 5 files changed, 169 insertions(+), 12 deletions(-) diff --git a/src/c5/codegen/mod.rs b/src/c5/codegen/mod.rs index a526f7bcd..dd02e3704 100644 --- a/src/c5/codegen/mod.rs +++ b/src/c5/codegen/mod.rs @@ -854,11 +854,13 @@ pub(crate) struct ResolvedImports { pub dylibs: Vec, /// Data symbols bound to a host data object via `#pragma /// binding(data ::, "")`. Each entry is - /// `(local_name, host_symbol)`: the image-side symbol the source - /// references and the dynamic symbol it resolves to. The ELF - /// writer turns each into a COPY relocation; unlike `imports`, - /// these are not call sites and carry no GOT/PLT slot. - pub data_bindings: Vec<(String, String)>, + /// `(local_name, host_symbol, dylib_index)`: the image-side symbol + /// the source references, the dynamic symbol it resolves to, and + /// the owning entry in `dylibs`. The ELF writer turns each into a + /// COPY relocation; PE / Mach-O route the reference through a + /// loader-filled import slot. Unlike `imports`, these are not call + /// sites and carry no GOT/PLT slot of their own. + pub data_bindings: Vec<(String, String, usize)>, } impl ResolvedImports { @@ -1070,11 +1072,12 @@ impl ResolvedImports { // emits a COPY relocation only when the final image defines the // local symbol (the runtime supplies `environ` for every hosted // image, so the binding binds it to the host's data object). - let mut data_bindings: Vec<(String, String)> = Vec::new(); + let mut data_bindings: Vec<(String, String, usize)> = Vec::new(); for spec in &program.dylibs { for b in &spec.bindings { if b.is_data { - let entry = (b.local_name.clone(), b.real_symbol.clone()); + let dylib_index = dylibs.iter().position(|d| d.name == spec.name).unwrap_or(0); + let entry = (b.local_name.clone(), b.real_symbol.clone(), dylib_index); if !data_bindings.contains(&entry) { data_bindings.push(entry); } diff --git a/src/c5/linker/synth_build.rs b/src/c5/linker/synth_build.rs index b5d4cebfd..397b16751 100644 --- a/src/c5/linker/synth_build.rs +++ b/src/c5/linker/synth_build.rs @@ -479,6 +479,17 @@ fn synth_imports(merged: &MergedNative, target: Target) -> ResolvedImports { }) .collect() }; + // A data-binding import is recorded under its local name (the + // UNDEF symbol the source references); the loader resolves the + // binding's host symbol. Map local -> host so the import table + // carries the name the dynamic linker actually looks up. The host + // string is already in the target's final shape (`"_environ"` on + // PE and Mach-O alike), so no per-target prefixing applies. + let data_binding_hosts: alloc::collections::BTreeMap<&str, &str> = merged + .copy_relocs + .iter() + .map(|(local, host)| (local.as_str(), host.as_str())) + .collect(); let imports: Vec = merged .imports .iter() @@ -498,7 +509,9 @@ fn synth_imports(merged: &MergedNative, target: Target) -> ResolvedImports { // not a `#pragma binding`'s pre-shaped `real_symbol`. Mach-O // prepends the leading underscore the loader matches against // the host's exported `_name`; ELF uses the name verbatim. - real_symbol: if merged.flat_imports.contains(name) && target == Target::MacOSAarch64 { + real_symbol: if let Some(host) = data_binding_hosts.get(name.as_str()) { + (*host).to_string() + } else if merged.flat_imports.contains(name) && target == Target::MacOSAarch64 { alloc::format!("_{name}") } else { name.clone() diff --git a/src/c5/object/elf_reloc.rs b/src/c5/object/elf_reloc.rs index 3411c93c7..d0c40872a 100644 --- a/src/c5/object/elf_reloc.rs +++ b/src/c5/object/elf_reloc.rs @@ -1434,7 +1434,7 @@ fn build_badc_note( // Record 2: per-import dylib map. Skip when there are no // imports -- the parser tolerates a missing record so the // older shape (dylibs note only) still round-trips. - if !imports.imports.is_empty() { + if !imports.imports.is_empty() || !imports.data_bindings.is_empty() { let mut bm_desc: Vec = Vec::new(); for imp in &imports.imports { let idx = imp.dylib_index as u32; @@ -1442,6 +1442,16 @@ fn build_badc_note( bm_desc.extend_from_slice(imp.real_symbol.as_bytes()); bm_desc.push(0); } + // Data bindings route through the same map, keyed by the + // local name: that is the UNDEF symbol the linker records as + // the import when no unit defines the local (PE / Mach-O). + // Without an entry the import falls back to dylib 0. + for (local, _host, dylib_index) in &imports.data_bindings { + let idx = *dylib_index as u32; + bm_desc.extend_from_slice(&idx.to_le_bytes()); + bm_desc.extend_from_slice(local.as_bytes()); + bm_desc.push(0); + } out.extend_from_slice(&(name.len() as u32).to_le_bytes()); out.extend_from_slice(&(bm_desc.len() as u32).to_le_bytes()); out.extend_from_slice(&NT_BADC_BINDING_MAP.to_le_bytes()); @@ -1591,7 +1601,7 @@ fn build_badc_note( // NUL-terminated string pairs from `#pragma binding(data ...)`. if !imports.data_bindings.is_empty() { let mut desc: Vec = Vec::new(); - for (local, host) in &imports.data_bindings { + for (local, host, _dylib_index) in &imports.data_bindings { desc.extend_from_slice(local.as_bytes()); desc.push(0); desc.extend_from_slice(host.as_bytes()); diff --git a/src/c5/object/mod.rs b/src/c5/object/mod.rs index e441cee0e..a3d78dbfc 100644 --- a/src/c5/object/mod.rs +++ b/src/c5/object/mod.rs @@ -97,7 +97,8 @@ pub fn emit_native_with_options( /// `.data`-relative address and faults at runtime. /// /// Mach-O only: ELF binds the local copy through an `R_*_COPY` -/// relocation and the PE writer has no data-import path. +/// relocation; PE routes the reference through a loader-filled IAT +/// slot on the regular import path (`is_data_load` GOT fixups). #[cfg(feature = "native-emit")] fn route_single_tu_data_imports(build: &mut Build, target: Target) { if target != Target::MacOSAarch64 || build.output_kind == OutputKind::Relocatable { @@ -111,7 +112,7 @@ fn route_single_tu_data_imports(build: &mut Build, target: Target) { .imports .data_bindings .iter() - .map(|(l, h)| (l.clone(), h.clone())) + .map(|(l, h, _dylib)| (l.clone(), h.clone())) .collect(); let mut import_for: alloc::collections::BTreeMap = alloc::collections::BTreeMap::new(); diff --git a/src/c5/tests/codegen.rs b/src/c5/tests/codegen.rs index b832060aa..0dd1f7fb0 100644 --- a/src/c5/tests/codegen.rs +++ b/src/c5/tests/codegen.rs @@ -322,6 +322,73 @@ fn data_import_lowers_as_iat_load_not_thunk_on_windows_x64() { ); } +/// A data binding whose local name differs from the host symbol must +/// put the HOST name in the import table: the loader resolves the +/// import-by-name entry against the DLL's export table, and msvcrt +/// exports `_environ`, not the local alias. Emitting the local name +/// loads with STATUS_ENTRYPOINT_NOT_FOUND (0xC0000139). +#[test] +fn data_import_renamed_binding_uses_export_name_on_windows_x64() { + use crate::{CompileOptions, Compiler, NativeOptions, Target}; + // Mirror the CLI: user TUs compile with `no_entry_point` so an + // `extern` data declaration stays UNDEF instead of taking a + // tentative local slot, letting the link admit the data import. + let program = Compiler::with_options( + "#pragma dylib(msvcrt, \"msvcrt.dll\")\n\ + #pragma binding(data msvcrt::__badc_env_alias, \"_environ\")\n\ + extern char **__badc_env_alias;\n\ + int main(void){return __badc_env_alias == 0;}\n" + .to_string(), + Target::WindowsX64, + CompileOptions::default().with_no_entry_point(true), + ) + .compile() + .expect("compile renamed data-binding TU for WindowsX64"); + let image = + super::link_executable_with_runtime(&program, Target::WindowsX64, NativeOptions::default()) + .expect("link WindowsX64 executable"); + assert!( + pe_iat_slot_rva(&image, "_environ").is_some(), + "renamed data binding must import the host symbol `_environ`" + ); + assert!( + pe_iat_slot_rva(&image, "__badc_env_alias").is_none(), + "the local alias must not appear in the import table" + ); +} + +/// A data binding must route to its declaring dylib's import +/// descriptor. Data imports carry no call site, so their routing rides +/// the `.note.badc` binding map; without an entry the import falls +/// back to descriptor 0, which here is kernel32.dll -- a DLL that does +/// not export `_environ`, so the image would fail to load. +#[test] +fn data_import_routes_to_declaring_dylib_on_windows_x64() { + use crate::{CompileOptions, Compiler, NativeOptions, Target}; + let program = Compiler::with_options( + "#pragma dylib(kernel32, \"kernel32.dll\")\n\ + #pragma binding(kernel32::GetCurrentProcess, \"GetCurrentProcess\")\n\ + void *GetCurrentProcess(void);\n\ + #pragma dylib(msvcrt, \"msvcrt.dll\")\n\ + #pragma binding(data msvcrt::__badc_env_alias, \"_environ\")\n\ + extern char **__badc_env_alias;\n\ + int main(void){return GetCurrentProcess() != 0 && __badc_env_alias == 0;}\n" + .to_string(), + Target::WindowsX64, + CompileOptions::default().with_no_entry_point(true), + ) + .compile() + .expect("compile dylib-routing TU for WindowsX64"); + let image = + super::link_executable_with_runtime(&program, Target::WindowsX64, NativeOptions::default()) + .expect("link WindowsX64 executable"); + assert_eq!( + pe_import_dll_of(&image, "_environ").as_deref(), + Some("msvcrt.dll"), + "`_environ` must sit under msvcrt.dll's import descriptor" + ); +} + /// Return the `(VirtualAddress, raw bytes)` of the PE `.text` /// section. RVA-relative byte scans use the VirtualAddress; the raw /// bytes are the section's file image. @@ -396,6 +463,69 @@ fn bss_segregation_extends_pe_data_virtual_size() { } } +/// Walk a PE32+ import table and return the DLL name whose descriptor +/// carries the named import. Same walk as [`pe_iat_slot_rva`], reading +/// each descriptor's Name field instead of the IAT slot. +fn pe_import_dll_of(image: &[u8], want: &str) -> Option { + let u16a = |o: usize| u16::from_le_bytes(image[o..o + 2].try_into().unwrap()); + let u32a = |o: usize| u32::from_le_bytes(image[o..o + 4].try_into().unwrap()); + let u64a = |o: usize| u64::from_le_bytes(image[o..o + 8].try_into().unwrap()); + let pe = u32a(0x3c) as usize; + let opt = pe + 24; + let import_dir_rva = u32a(opt + 112 + 8); + if import_dir_rva == 0 { + return None; + } + let num_sections = u16a(pe + 6) as usize; + let opt_size = u16a(pe + 20) as usize; + let sec_table = pe + 24 + opt_size; + let rva_to_off = |rva: u32| -> Option { + for s in 0..num_sections { + let sh = sec_table + s * 40; + let va = u32a(sh + 12); + let vsize = u32a(sh + 8); + let raw_off = u32a(sh + 20); + if rva >= va && rva < va + vsize { + return Some((raw_off + (rva - va)) as usize); + } + } + None + }; + let cstr_at = |off: usize| -> String { + let end = image[off..] + .iter() + .position(|&c| c == 0) + .map_or(off, |n| off + n); + String::from_utf8_lossy(&image[off..end]).into_owned() + }; + let import_dir_off = rva_to_off(import_dir_rva)?; + let mut d = import_dir_off; + loop { + let ilt_rva = u32a(d); + let name_rva = u32a(d + 12); + if ilt_rva == 0 && name_rva == 0 && u32a(d + 16) == 0 { + break; + } + let ilt_off = rva_to_off(ilt_rva)?; + let mut k = 0usize; + loop { + let entry = u64a(ilt_off + k * 8); + if entry == 0 { + break; + } + if entry & (1u64 << 63) == 0 { + let name_off = rva_to_off(entry as u32)? + 2; + if cstr_at(name_off) == want { + return Some(cstr_at(rva_to_off(name_rva)?)); + } + } + k += 1; + } + d += 20; + } + None +} + /// Walk a PE32+ import table and return the RVA of the IAT slot for /// the named import. Follows the format: data directory 1 -> import /// descriptors; each descriptor's OriginalFirstThunk (ILT) entries From 51ea4377cd5cc1f34a7cf64a7a45146c99c0ecce Mon Sep 17 00:00:00 2001 From: kromych Date: Wed, 1 Jul 2026 14:53:23 -0700 Subject: [PATCH 13/15] windows: serve environ/_environ/_wenviron from msvcrt, drop the local slots The runtime defined environ and _environ as zeroed locals that shadowed '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 --- headers/include/stdlib.h | 62 +++++++++++++++++------------- lib/runtime.c | 15 ++++---- src/c5/tests/codegen.rs | 81 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 33 deletions(-) diff --git a/headers/include/stdlib.h b/headers/include/stdlib.h index 730b944a8..f7a56aa68 100644 --- a/headers/include/stdlib.h +++ b/headers/include/stdlib.h @@ -163,8 +163,10 @@ #pragma binding(msvcrt::abort, "abort") #pragma binding(msvcrt::system, "system") #pragma binding(msvcrt::getenv, "getenv") -// msvcrt's `setenv` is the underscored `_putenv_s`. Same shape: +// POSIX putenv is msvcrt's underscored `_putenv`; msvcrt's `setenv` +// is `_putenv_s`. Same shapes: (string) -> int and // (name, value, overwrite) -> int. +#pragma binding(msvcrt::putenv, "_putenv") #pragma binding(msvcrt::setenv, "_putenv_s") #pragma binding(msvcrt::_wputenv_s, "_wputenv_s") #pragma binding(msvcrt::qsort, "qsort") @@ -187,16 +189,42 @@ #pragma binding(data msvcrt::_sys_nerr, "_sys_nerr") #pragma binding(data msvcrt::_sys_errlist, "_sys_errlist") #if defined(__aarch64__) -// The legacy arm64 msvcrt.dll does not export the `_wenviron` data -// symbol; UCRT exposes the wide environment only via this accessor. -#pragma dylib(ucrtbase, "ucrtbase.dll") -#pragma binding(ucrtbase::__p__wenviron, "__p__wenviron") -unsigned short ***__p__wenviron(void); -#define _wenviron (*__p__wenviron()) +// arm64 msvcrt.dll exports no environment data symbols, and ucrtbase's +// `__p__environ` / `__p__wenviron` accessors read UCRT's separate copy +// that msvcrt's getenv / _putenv / _wgetenv never update. Reach the +// vectors through msvcrt's own accessor functions; the wide vector +// stays NULL until the first wide-environment use, matching the CRT's +// initialize-on-demand behaviour. +#pragma binding(msvcrt::_get_environ, "_get_environ") +#pragma binding(msvcrt::_get_wenviron, "_get_wenviron") +int _get_environ(char ***out); +int _get_wenviron(unsigned short ***out); +static inline char **__badc_environ(void) { + char **v = 0; + _get_environ(&v); + return v; +} +static inline unsigned short **__badc_wenviron(void) { + unsigned short **v = 0; + _get_wenviron(&v); + return v; +} +#define environ (__badc_environ()) +#define _environ (__badc_environ()) +#define _wenviron (__badc_wenviron()) #else -#pragma binding(data msvcrt::_wenviron, "_wenviron") +// x64 msvcrt.dll exports the vectors as data: bind each as a +// loader-filled import so reads see the CRT's live view (getenv / +// putenv mutations included). POSIX `environ` aliases the `_environ` +// export. No unit may define these locally -- a local definition +// shadows the import with a slot nothing populates. +#pragma binding(data msvcrt::_wenviron, "_wenviron") +#pragma binding(data msvcrt::_environ, "_environ") +#pragma binding(data msvcrt::environ, "_environ") +extern unsigned short **_wenviron; +extern char **_environ; +extern char **environ; #endif -#pragma binding(data msvcrt::_environ, "_environ") // `_doserrno` is an SDK macro over the exported accessor `__doserrno`, // which returns a pointer to the thread's OS error code. #pragma binding(msvcrt::__doserrno, "__doserrno") @@ -207,10 +235,6 @@ extern char *_sys_errlist[]; // Deprecated non-underscore aliases the msvcrt headers still expose. #define sys_nerr _sys_nerr #define sys_errlist _sys_errlist -#if !defined(__aarch64__) -extern unsigned short **_wenviron; -#endif -extern char **_environ; // MSVC CRT size limits (_makepath / _splitpath / environment). #define _MAX_PATH 260 #define _MAX_DRIVE 3 @@ -428,16 +452,4 @@ static inline void __clear_cache(void *begin, void *end) { FlushInstructionCache(GetCurrentProcess(), begin, (long long)((char *)end - (char *)begin)); } -// msvcrt exposes the environment vector through the `_environ` -// data symbol. Both `environ` and `_environ` live in -// `lib/runtime.c` as single canonical definitions; user code -// declares each `extern` here so every TU resolves through the -// same slot rather than contributing a tentative def of its -// own. -// TODO: bind msvcrt's `_environ` directly via `#pragma binding`'s -// data form. The form is wired for ELF (COPY relocation) and Mach-O -// (GOT import); the PE writer has no data-import path yet, so the -// local slot stays until that lands. -extern char **environ; -extern char **_environ; #endif diff --git a/lib/runtime.c b/lib/runtime.c index ce528d195..21d2ab387 100644 --- a/lib/runtime.c +++ b/lib/runtime.c @@ -32,10 +32,14 @@ // multiple-definition collision. // // macOS binds `environ` as a GOT data import to libSystem's `_environ` -// (see ), so the symbol must stay undefined here for the -// reference to route through the import slot rather than a local cell. -#ifndef __APPLE__ +// (see ) and Windows binds `environ` / `_environ` / +// `_wenviron` to msvcrt's live vectors (see ), so on both +// the symbols must stay undefined here for references to route +// through the import rather than a local cell nothing populates. +#if !defined(__APPLE__) && !defined(_WIN32) char **environ; +#endif +#ifndef __APPLE__ // POSIX `tzset` outputs. Like `environ`, the Linux bindings route these // through a COPY relocation against the C library's symbols so a read // after `tzset()` sees what the library wrote; the local slots here are @@ -45,11 +49,6 @@ long timezone; int daylight; #endif -// msvcrt's environment-vector alias on Windows. ``'s -// `_WIN32` section declares it `extern char **_environ;` for the same -// reason `environ` lives here. -char **_environ; - // `__c5_exit` runs libc's `exit` so the atexit chain (including the // stdio flush) executes before the kernel reaps the process. The // per-OS dylib name mirrors ``'s convention. diff --git a/src/c5/tests/codegen.rs b/src/c5/tests/codegen.rs index 0dd1f7fb0..2f69ae5c5 100644 --- a/src/c5/tests/codegen.rs +++ b/src/c5/tests/codegen.rs @@ -357,6 +357,87 @@ fn data_import_renamed_binding_uses_export_name_on_windows_x64() { ); } +/// The bundled `` binds the Windows/x64 environment vectors +/// as msvcrt data imports: `_environ` / `_wenviron` under their export +/// names and POSIX `environ` aliased to the `_environ` export. No unit +/// (the runtime included) may define them locally -- a local slot +/// shadows the import and reads NULL, which upstream surfaced as an +/// empty environment in every spawned child process. +#[test] +fn windows_x64_environ_family_resolves_to_msvcrt_data_imports() { + use crate::{CompileOptions, Compiler, NativeOptions, Target}; + let program = Compiler::with_options( + "#include \n\ + int main(void){return (environ != 0) + (_environ != 0) + (_wenviron != 0);}\n" + .to_string(), + Target::WindowsX64, + CompileOptions::default().with_no_entry_point(true), + ) + .compile() + .expect("compile environ TU for WindowsX64"); + let image = + super::link_executable_with_runtime(&program, Target::WindowsX64, NativeOptions::default()) + .expect("link WindowsX64 executable"); + assert_eq!( + pe_import_dll_of(&image, "_environ").as_deref(), + Some("msvcrt.dll"), + "`_environ` must be a msvcrt data import" + ); + assert_eq!( + pe_import_dll_of(&image, "_wenviron").as_deref(), + Some("msvcrt.dll"), + "`_wenviron` must be a msvcrt data import" + ); + assert!( + pe_iat_slot_rva(&image, "environ").is_none(), + "POSIX `environ` must alias the `_environ` export, not import a bare `environ`" + ); +} + +/// On Windows/arm64 msvcrt.dll exports no environment data symbols, so +/// `` maps the family to msvcrt's `_get_environ` / +/// `_get_wenviron` accessor functions. ucrtbase's `__p__*` accessors +/// must not appear: they read UCRT's separate environment copy, which +/// msvcrt's getenv / _putenv / _wgetenv never update. +#[test] +fn windows_arm64_environ_family_lowers_via_msvcrt_accessors() { + use crate::{CompileOptions, Compiler, NativeOptions, Target}; + let program = Compiler::with_options( + "#include \n\ + int main(void){return (environ != 0) + (_environ != 0) + (_wenviron != 0);}\n" + .to_string(), + Target::WindowsAarch64, + CompileOptions::default().with_no_entry_point(true), + ) + .compile() + .expect("compile environ TU for WindowsAarch64"); + let image = super::link_executable_with_runtime( + &program, + Target::WindowsAarch64, + NativeOptions::default(), + ) + .expect("link WindowsAarch64 executable"); + for accessor in ["_get_environ", "_get_wenviron"] { + assert_eq!( + pe_import_dll_of(&image, accessor).as_deref(), + Some("msvcrt.dll"), + "`{accessor}` must be imported from msvcrt.dll" + ); + } + for absent in [ + "environ", + "_environ", + "_wenviron", + "__p__wenviron", + "__p__environ", + ] { + assert!( + pe_iat_slot_rva(&image, absent).is_none(), + "`{absent}` must not appear in the arm64 import table" + ); + } +} + /// A data binding must route to its declaring dylib's import /// descriptor. Data imports carry no call site, so their routing rides /// the `.note.badc` binding map; without an entry the import falls From 6f4617cf28084b17f03cfc4308ec20f0826fb9cc Mon Sep 17 00:00:00 2001 From: kromych Date: Wed, 1 Jul 2026 15:14:58 -0700 Subject: [PATCH 14/15] link: reject a data binding whose local symbol the image defines (PE, 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 --- src/c5/linker/synth_build.rs | 23 ++++++++++++++++++----- src/c5/tests/codegen.rs | 34 ++++++++++++++++++++++++++++++---- src/c5/tests/linker.rs | 14 ++++++++------ 3 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/c5/linker/synth_build.rs b/src/c5/linker/synth_build.rs index 397b16751..5a98dab1d 100644 --- a/src/c5/linker/synth_build.rs +++ b/src/c5/linker/synth_build.rs @@ -254,16 +254,29 @@ fn synth_program_and_build( }; // Resolve each data-import copy relocation against the merged - // symbol table. The local data object named in the binding must be - // defined in the image (e.g. runtime.c's `environ`); carry its - // section + offset so the writer can place the host symbol and the - // R_*_COPY at the object's runtime address. A binding whose local - // symbol the image does not define is dropped silently. + // symbol table. On ELF the local data object named in the binding + // must be defined in the image (e.g. runtime.c's `tzname`); carry + // its section + offset so the writer can place the host symbol and + // the R_*_COPY at the object's runtime address. A binding whose + // local symbol the image does not define is dropped silently. + // + // PE and Mach-O have no copy semantics: the reference must stay + // undefined so it routes through a loader-filled import slot. A + // definition anywhere in the image wins symbol resolution and + // silently shadows the binding with a slot nothing populates, so + // reject the collision instead of shipping the dead read. + let elf_target = matches!(target, Target::LinuxX64 | Target::LinuxAarch64); let mut copy_relocs: Vec = Vec::new(); for (local, host) in &merged.copy_relocs { let Some(sym) = merged.defined.get(local) else { continue; }; + if !elf_target { + return Err(synth_err(&alloc::format!( + "`{local}` is bound as a data import of `{host}` but the image also \ + defines it; remove the definition or the `#pragma binding(data ...)`" + ))); + } let is_bss = matches!(sym.section, NativeSymSection::Bss); if !is_bss && !matches!(sym.section, NativeSymSection::Data) { continue; diff --git a/src/c5/tests/codegen.rs b/src/c5/tests/codegen.rs index 2f69ae5c5..12104e193 100644 --- a/src/c5/tests/codegen.rs +++ b/src/c5/tests/codegen.rs @@ -237,10 +237,8 @@ fn environ_data_binding_records_copy_relocation() { /// trampoline. The msvcrt `_sys_errlist` array is the bundled /// `` example (`extern char *_sys_errlist[]` bound to /// `msvcrt::_sys_errlist` under `_WIN32`); the binding is declared -/// inline here so the TU pulls in only the data import under test -/// and not the header's `environ` / `_environ` block, whose -/// PE-local-slot lowering still collides with the runtime's -/// `environ` definition (a separate, pre-existing gap). Compile a TU +/// inline here so the TU pulls in only the data import under test. +/// Compile a TU /// that indexes the array, link a complete PE, locate the import's /// IAT slot via the standard import-table walk, and confirm the /// referencing `.text` instruction is a RIP-relative `mov` @@ -322,6 +320,34 @@ fn data_import_lowers_as_iat_load_not_thunk_on_windows_x64() { ); } +/// PE has no COPY-relocation semantics: a symbol both bound as a data +/// import and defined in the image would resolve to the local slot and +/// silently drop the binding, so the link must reject the collision. +/// (On ELF the same dual is the design: the local definition is the +/// COPY destination.) +#[test] +fn data_binding_with_local_definition_is_a_link_error_on_windows_x64() { + use crate::{CompileOptions, Compiler, NativeOptions, Target}; + let program = Compiler::with_options( + "#pragma dylib(msvcrt, \"msvcrt.dll\")\n\ + #pragma binding(data msvcrt::__badc_env_alias, \"_environ\")\n\ + char **__badc_env_alias;\n\ + int main(void){return __badc_env_alias == 0;}\n" + .to_string(), + Target::WindowsX64, + CompileOptions::default().with_no_entry_point(true), + ) + .compile() + .expect("compile colliding data-binding TU for WindowsX64"); + let err = + super::link_executable_with_runtime(&program, Target::WindowsX64, NativeOptions::default()) + .expect_err("a data binding shadowed by a local definition must fail the link"); + assert!( + err.contains("bound as a data import"), + "diagnostic must name the collision; got: {err}" + ); +} + /// A data binding whose local name differs from the host symbol must /// put the HOST name in the import table: the loader resolves the /// import-by-name entry against the DLL's export table, and msvcrt diff --git a/src/c5/tests/linker.rs b/src/c5/tests/linker.rs index 063a6af95..3aacace98 100644 --- a/src/c5/tests/linker.rs +++ b/src/c5/tests/linker.rs @@ -1088,16 +1088,18 @@ fn win64_dll_records_requested_name() { // at runtime; the name comes from the requested `-o` basename, not a // fixed default. (The runtime's `exit` binding resolving through // msvcrt.dll rather than ucrtbase.dll is exercised by the Windows - // demos, which link the embedded runtime.) + // demos, which link the embedded runtime.) No prelude: the host's + // headers would carry host-OS data bindings into this PE-target + // link, which the linker rejects as binding/definition collisions. use crate::c5::linker::{ emit_x86_64_plt, link_native_objects, parse_native_elf, write_native_image_from_merged, }; use crate::c5::{NativeOptions, OutputKind, Target, emit_native_with_options}; - let program = Compiler::new(alloc::format!( - "{TEST_PRELUDE}\ - #pragma export(api_fn)\n\ - int api_fn(int x) {{ return x + 1; }}\n" - )) + let program = Compiler::new( + "#pragma export(api_fn)\n\ + int api_fn(int x) { return x + 1; }\n" + .to_string(), + ) .compile() .expect("compile"); let opts = NativeOptions { From c46871ff53e53ba34be998299a42a2dd0bf05d72 Mon Sep 17 00:00:00 2001 From: kromych Date: Wed, 1 Jul 2026 15:14:58 -0700 Subject: [PATCH 15/15] demos/python: run test_gh_120161 on windows-arm64 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 --- demos/python/build.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/demos/python/build.py b/demos/python/build.py index a1969f917..f991d37d6 100644 --- a/demos/python/build.py +++ b/demos/python/build.py @@ -192,16 +192,7 @@ # (test_strftime_y2k, test_strftime_y2k_c99). # TODO: a C99-conforming CRT or a math/time shim removes every entry here. "windows-x64": {"windows": True, "tier2_ignore": _WIN_CRT_IGNORE}, - # arm64 also ignores test_gh_120161. The test helper spawns the interpreter - # as a child with a sanitized environment block (neither PATH nor SystemRoot - # present); the child's `import asyncio` then loads _overlapped, whose - # WSAStartup initializes the Winsock provider catalog. On Windows arm64 that - # init fails with WinError 10106 (WSAEPROVIDERFAILEDINIT) under the stripped - # environment, while x64 tolerates it. The byte-identical interpreter imports - # _overlapped in every normal invocation, so this is an arm64 Winsock-startup - # property of the spawn environment, not a code-generation defect. - "windows-arm64": {"windows": True, - "tier2_ignore": _WIN_CRT_IGNORE + ["test_gh_120161"]}, + "windows-arm64": {"windows": True, "tier2_ignore": _WIN_CRT_IGNORE}, }