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/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}, } 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/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/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/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/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 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 diff --git a/src/c5/linker/synth_build.rs b/src/c5/linker/synth_build.rs index b5d4cebfd..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; @@ -479,6 +492,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 +522,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.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/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/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. 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/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 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"); diff --git a/src/c5/tests/codegen.rs b/src/c5/tests/codegen.rs index b832060aa..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,182 @@ 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 +/// 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" + ); +} + +/// 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 +/// 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 +570,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 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 { 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 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() 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/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. 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); +} 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/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/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/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 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)