diff --git a/core/rexcode/isa/arm64/instructions.odin b/core/rexcode/isa/arm64/instructions.odin index fc7910658..e52db3d27 100644 --- a/core/rexcode/isa/arm64/instructions.odin +++ b/core/rexcode/isa/arm64/instructions.odin @@ -118,8 +118,10 @@ inst_branch :: #force_inline proc "contextless" (m: Mnemonic, label_id: u32) -> ops = {op_label(label_id, 4), {}, {}, {}}} } -// NOTE: inst_b_cond / inst_cbz (+cbnz) / inst_tbz (+tbnz) / -// inst_csel (+csinc/csinv/csneg) are now generated per-mnemonic in -// mnemonic_builders.odin (e.g. inst_cbz(rt, label), inst_cbnz(rt, label), -// inst_csinc(rd, rn, rm, cond)). They are no longer hand-written here so the -// generator can own those names for full mnemonic coverage. +// NOTE: the conditional branches, inst_cbz (+cbnz), inst_tbz (+tbnz) and +// inst_csel (+csinc/csinv/csneg) are generated per-mnemonic in +// mnemonic_builders.odin, so the generator owns those names. A conditional +// branch is one builder per condition -- inst_b_le(label), inst_bc_ne(label) +// -- because the condition is part of the mnemonic, not an operand; the +// condition-operand builders are the select/compare family, which really do +// take one (inst_csinc(rd, rn, rm, cond)). diff --git a/core/rexcode/isa/arm64/tablegen/specgen.lua b/core/rexcode/isa/arm64/tablegen/specgen.lua index 13197ba8a..8d90b11e8 100644 --- a/core/rexcode/isa/arm64/tablegen/specgen.lua +++ b/core/rexcode/isa/arm64/tablegen/specgen.lua @@ -71,10 +71,13 @@ local CANON_SUF = { "_II","_IR","_RI","_RR","_X2","_X4","_SR","_ER","_ZA", "_Z","_P","_V","_H","_S","_D","_B","_3","_4", } -local CANON_KEEP = { B_COND=true, BC_COND=true } +-- A conditional branch is one mnemonic per condition (B_LE -> `b.le`), so +-- nothing about those names should be canonicalized away. +local function is_cond_branch(name) return name:match("^BC?_%u%u$") ~= nil end +local CANON_KEEP = {} local CANON_RENAME = { LSLV="LSL", LSRV="LSR", ASRV="ASR", RORV="ROR" } local function canon(name) - if CANON_KEEP[name] then return name end + if CANON_KEEP[name] or is_cond_branch(name) then return name end for _, k in ipairs({"DC_","IC_","AT_","TLBI_","BTI_","PSB_","TSB_"}) do if name:sub(1, #k) == k then return name end end diff --git a/core/rexcode/isa/arm64/tools/verify_against_llvm.odin b/core/rexcode/isa/arm64/tools/verify_against_llvm.odin index 051166b3f..08a0088b3 100644 --- a/core/rexcode/isa/arm64/tools/verify_against_llvm.odin +++ b/core/rexcode/isa/arm64/tools/verify_against_llvm.odin @@ -37,6 +37,13 @@ normalize_our :: proc(name: string) -> string { break } } + // A conditional branch keeps its condition: B_LE is `b.le`, not `b`. + // Truncating at the underscore would collapse all sixteen onto one name + // and make every one of them compare equal to every other. + if strings.has_prefix(s, "b_") || strings.has_prefix(s, "bc_") { + return strings.concatenate({s[:strings.index_byte(s, '_')], ".", + s[strings.index_byte(s, '_')+1:]}, context.temp_allocator) + } if i := strings.index_byte(s, '_'); i >= 0 { s = s[:i] } @@ -146,15 +153,6 @@ is_known_alias :: proc(ours, llvm: string) -> bool { {"and", "mov"}, {"facge", "facge"}, {"facgt", "facgt"}, - // Conditional branches: our table has B_COND but LLVM prints b.eq/b.ne/... - {"b", "b.eq"}, {"b", "b.ne"}, {"b", "b.cs"}, {"b", "b.cc"}, - {"b", "b.mi"}, {"b", "b.pl"}, {"b", "b.vs"}, {"b", "b.vc"}, - {"b", "b.hi"}, {"b", "b.ls"}, {"b", "b.ge"}, {"b", "b.lt"}, - {"b", "b.gt"}, {"b", "b.le"}, {"b", "b.al"}, {"b", "b.nv"}, - {"bc", "bc.eq"}, {"bc", "bc.ne"}, {"bc", "bc.cs"}, {"bc", "bc.cc"}, - {"bc", "bc.mi"}, {"bc", "bc.pl"}, {"bc", "bc.vs"}, {"bc", "bc.vc"}, - {"bc", "bc.hi"}, {"bc", "bc.ls"}, {"bc", "bc.ge"}, {"bc", "bc.lt"}, - {"bc", "bc.gt"}, {"bc", "bc.le"}, {"bc", "bc.al"}, {"bc", "bc.nv"}, // FCSEL is canonical for FCSET/FCINC variants if any {"fmov", "fmov"}, // Atomics: many of these LLVM might print without the size suffix