rexcode/mips: R6 PC-relative loads LWPC/LWUPC/LDPC

New REL19/REL18 operand types + BRANCH_19/BRANCH_18 encodings + REL_PC19/
REL_PC18 relocations (R6 PC-relative semantics: offset is relative to the
instruction's own address, no delay-slot adjustment; LDPC aligns the PC
down to 8 and scales by 8). LWPC (mips32r6), LWUPC/LDPC (mips64r6).
Byte-exact vs llvm-mc and decode-clean; 281 tests green.
This commit is contained in:
Brendan Punsky
2026-06-18 04:05:32 -04:00
committed by Flāvius
parent eab483a527
commit ff2bf13121
14 changed files with 935 additions and 847 deletions

View File

@@ -267,6 +267,18 @@ extract_operand_inline :: #force_inline proc "contextless" (
if rel26 & (1 << 25) != 0 { rel26 |= ~i32(0x3FFFFFF) }
target := u32(i32(pc) + 4 + (rel26 << 2))
return Operand{relative = i64(target), kind = .RELATIVE, size = 4}
case .BRANCH_19:
// R6 PC-relative load: relative to this instruction (no +4), << 2.
rel19 := i32(word & 0x7FFFF)
if rel19 & (1 << 18) != 0 { rel19 |= ~i32(0x7FFFF) }
target := u32(i32(pc) + (rel19 << 2))
return Operand{relative = i64(target), kind = .RELATIVE, size = 4}
case .BRANCH_18:
// LDPC: relative to this instruction aligned down to 8, << 3.
rel18 := i32(word & 0x3FFFF)
if rel18 & (1 << 17) != 0 { rel18 |= ~i32(0x3FFFF) }
target := u32((i32(pc) &~ i32(7)) + (rel18 << 3))
return Operand{relative = i64(target), kind = .RELATIVE, size = 4}
// Misc small immediates -------------------------------------------------
case .FCC_BC:

View File

@@ -225,7 +225,7 @@ operand_matches_inline :: #force_inline proc "contextless" (
case .IMM5, .IMM16S, .IMM16U, .IMM20, .IMM26, .SEL, .FCC,
.GTE_SF, .GTE_MX, .GTE_V, .GTE_CV, .GTE_LM:
return op.kind == .IMMEDIATE
case .REL16, .REL21, .REL26, .REL_J26:
case .REL16, .REL21, .REL26, .REL_J26, .REL19, .REL18:
return op.kind == .RELATIVE
case .MEM:
return op.kind == .MEMORY
@@ -310,6 +310,18 @@ pack_operand_inline :: #force_inline proc(
type = .REL26, size = 4, inst_idx = inst_idx,
})
return 0
case .BRANCH_19:
append(relocs, Relocation{
offset = pc, label_id = u32(op.relative),
type = .REL_PC19, size = 4, inst_idx = inst_idx,
})
return 0
case .BRANCH_18:
append(relocs, Relocation{
offset = pc, label_id = u32(op.relative),
type = .REL_PC18, size = 4, inst_idx = inst_idx,
})
return 0
// FP condition-code field (BC1*, MOVF/MOVT, C.cond.fmt).
case .FCC_BC:
@@ -492,6 +504,36 @@ resolve_relocation_inline :: #force_inline proc(
}
word = (word &~ 0x3FFFFFF) | (u32(rel) & 0x3FFFFFF)
case .REL_PC19:
// R6 PC-relative load: offset is relative to the instruction's own
// address (no delay-slot adjustment), scaled by 4, 19-bit signed.
rel := i32(target) - i32(relocation.offset)
if rel & 3 != 0 {
append(errors, Error{inst_idx = u32(relocation.inst_idx), code = .LABEL_OUT_OF_RANGE})
return true
}
rel >>= 2
if rel < -(1<<18) || rel > (1<<18)-1 {
append(errors, Error{inst_idx = u32(relocation.inst_idx), code = .LABEL_OUT_OF_RANGE})
return true
}
word = (word &~ 0x7FFFF) | (u32(rel) & 0x7FFFF)
case .REL_PC18:
// LDPC: relative to the instruction's address aligned down to 8, scaled
// by 8, 18-bit signed.
rel := i32(target) - (i32(relocation.offset) &~ i32(7))
if rel & 7 != 0 {
append(errors, Error{inst_idx = u32(relocation.inst_idx), code = .LABEL_OUT_OF_RANGE})
return true
}
rel >>= 3
if rel < -(1<<17) || rel > (1<<17)-1 {
append(errors, Error{inst_idx = u32(relocation.inst_idx), code = .LABEL_OUT_OF_RANGE})
return true
}
word = (word &~ 0x3FFFF) | (u32(rel) & 0x3FFFF)
case .J26:
// J/JAL: target = ((PC+4)[31:28] << 28) | (encoded_field << 2)
if target & 3 != 0 {

View File

@@ -125,6 +125,8 @@ Operand_Type :: enum u8 {
REL_J26, // 26-bit region jump (J / JAL)
REL21, // R6 21-bit compact branch (BC1EQZ/NEZ)
REL26, // R6 26-bit compact branch (BC/BALC)
REL19, // R6 PC-relative load (LWPC/LWUPC, 19-bit << 2)
REL18, // R6 PC-relative load (LDPC, 18-bit << 3)
// Memory: base GPR + 16-bit signed displacement
MEM,
@@ -164,6 +166,8 @@ Operand_Encoding :: enum u8 {
BRANCH_16, // bits 15-0 as PC-relative word offset (delay-slot adjusted)
BRANCH_21, // R6 compact branch: bits 20-0
BRANCH_26, // R6 compact branch: bits 25-0
BRANCH_19, // R6 PC-relative load: bits 18-0 (LWPC/LWUPC)
BRANCH_18, // R6 PC-relative load: bits 17-0 (LDPC)
// FP condition code
FCC_BC, // bits 20-18 (FP branches, MOVF/MOVT)

View File

@@ -412,6 +412,12 @@ inst_lsa_r_r_r_i5 :: #force_inline proc "contextless" (dst: GPR, src: G
emit_lsa_r_r_r_i5 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: GPR, src: GPR, src2: GPR, imm: i64) { append(instructions, inst_lsa_r_r_r_i5(dst, src, src2, imm)) }
inst_dlsa_r_r_r_i5 :: #force_inline proc "contextless" (dst: GPR, src: GPR, src2: GPR, imm: i64) -> Instruction { return Instruction{mnemonic = .DLSA, operand_count = 4, length = 4, ops = {op_gpr(dst), op_gpr(src), op_gpr(src2), op_imm(imm, 1)}} }
emit_dlsa_r_r_r_i5 :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: GPR, src: GPR, src2: GPR, imm: i64) { append(instructions, inst_dlsa_r_r_r_i5(dst, src, src2, imm)) }
inst_lwpc_r_rel :: #force_inline proc "contextless" (dst: GPR, target: u32) -> Instruction { return Instruction{mnemonic = .LWPC, operand_count = 2, length = 4, ops = {op_gpr(dst), op_label(target), {}, {}}} }
emit_lwpc_r_rel :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: GPR, target: u32) { append(instructions, inst_lwpc_r_rel(dst, target)) }
inst_lwupc_r_rel :: #force_inline proc "contextless" (dst: GPR, target: u32) -> Instruction { return Instruction{mnemonic = .LWUPC, operand_count = 2, length = 4, ops = {op_gpr(dst), op_label(target), {}, {}}} }
emit_lwupc_r_rel :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: GPR, target: u32) { append(instructions, inst_lwupc_r_rel(dst, target)) }
inst_ldpc_r_rel :: #force_inline proc "contextless" (dst: GPR, target: u32) -> Instruction { return Instruction{mnemonic = .LDPC, operand_count = 2, length = 4, ops = {op_gpr(dst), op_label(target), {}, {}}} }
emit_ldpc_r_rel :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: GPR, target: u32) { append(instructions, inst_ldpc_r_rel(dst, target)) }
inst_seleqz_r_r_r :: #force_inline proc "contextless" (dst: GPR, src: GPR, src2: GPR) -> Instruction { return Instruction{mnemonic = .SELEQZ, operand_count = 3, length = 4, ops = {op_gpr(dst), op_gpr(src), op_gpr(src2), {}}} }
emit_seleqz_r_r_r :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: GPR, src: GPR, src2: GPR) { append(instructions, inst_seleqz_r_r_r(dst, src, src2)) }
inst_selnez_r_r_r :: #force_inline proc "contextless" (dst: GPR, src: GPR, src2: GPR) -> Instruction { return Instruction{mnemonic = .SELNEZ, operand_count = 3, length = 4, ops = {op_gpr(dst), op_gpr(src), op_gpr(src2), {}}} }
@@ -2427,6 +2433,12 @@ inst_lsa :: inst_lsa_r_r_r_i5
emit_lsa :: emit_lsa_r_r_r_i5
inst_dlsa :: inst_dlsa_r_r_r_i5
emit_dlsa :: emit_dlsa_r_r_r_i5
inst_lwpc :: inst_lwpc_r_rel
emit_lwpc :: emit_lwpc_r_rel
inst_lwupc :: inst_lwupc_r_rel
emit_lwupc :: emit_lwupc_r_rel
inst_ldpc :: inst_ldpc_r_rel
emit_ldpc :: emit_ldpc_r_rel
inst_seleqz :: inst_seleqz_r_r_r
emit_seleqz :: emit_seleqz_r_r_r
inst_selnez :: inst_selnez_r_r_r

View File

@@ -17,6 +17,8 @@ Relocation_Type :: enum u8 {
REL16, // 16-bit signed PC-rel branch offset (BEQ/BNE/BLEZ/BGTZ/...)
REL21, // 21-bit signed PC-rel compact branch (R6 BEQZC/BNEZC)
REL26, // 26-bit signed PC-rel compact branch (R6 BC/BALC)
REL_PC19, // 19-bit R6 PC-relative load offset ((target - PC) >> 2) (LWPC/LWUPC)
REL_PC18, // 18-bit R6 PC-relative load offset ((target - (PC & ~7)) >> 3) (LDPC)
J26, // 26-bit J-type region target ((target_addr >> 2) & 0x3FFFFFF)
HI16, // upper 16 of 32-bit absolute (LUI rt, %hi(sym)+0x8000 if LO16 paired)
LO16, // lower 16 of 32-bit absolute (ADDIU rt, rt, %lo(sym))

View File

@@ -1680,5 +1680,8 @@ ENCODING_TABLE := #partial [Mnemonic][]Encoding{
.BNZ_W = { {.BNZ_W, {.MSA_VEC,.REL16,.NONE,.NONE}, {.WT,.BRANCH_16,.NONE,.NONE}, 0x47C00000, 0xFFE00000, .MSA, {}} },
.BNZ_D = { {.BNZ_D, {.MSA_VEC,.REL16,.NONE,.NONE}, {.WT,.BRANCH_16,.NONE,.NONE}, 0x47E00000, 0xFFE00000, .MSA, {}} },
.BNZ_V = { {.BNZ_V, {.MSA_VEC,.REL16,.NONE,.NONE}, {.WT,.BRANCH_16,.NONE,.NONE}, 0x45E00000, 0xFFE00000, .MSA, {}} },
.LWPC = { {.LWPC, {.GPR,.REL19,.NONE,.NONE}, {.RS,.BRANCH_19,.NONE,.NONE}, 0xEC080000, 0xFC180000, .MIPS32_R6, {}} },
.LWUPC = { {.LWUPC, {.GPR,.REL19,.NONE,.NONE}, {.RS,.BRANCH_19,.NONE,.NONE}, 0xEC100000, 0xFC180000, .MIPS64_R6, {}} },
.LDPC = { {.LDPC, {.GPR,.REL18,.NONE,.NONE}, {.RS,.BRANCH_18,.NONE,.NONE}, 0xEC180000, 0xFC1C0000, .MIPS64_R6, {}} },
// SPECGEN:END
}

View File

@@ -8,7 +8,7 @@ package rexcode_mips_generated
import lib "../.."
@(rodata)
DECODE_ENTRIES := [1005]lib.Decode_Entry{
DECODE_ENTRIES := [1008]lib.Decode_Entry{
{ .NOP, {.NONE,.NONE,.NONE,.NONE}, {.NONE,.NONE,.NONE,.NONE}, 0x00000000, 0xFFFFFFFF, .MIPS_I, {} },
{ .SSNOP, {.NONE,.NONE,.NONE,.NONE}, {.NONE,.NONE,.NONE,.NONE}, 0x00000040, 0xFFFFFFFF, .MIPS32_R1, {} },
{ .EHB, {.NONE,.NONE,.NONE,.NONE}, {.NONE,.NONE,.NONE,.NONE}, 0x000000C0, 0xFFFFFFFF, .MIPS32_R2, {} },
@@ -975,6 +975,9 @@ DECODE_ENTRIES := [1005]lib.Decode_Entry{
{ .SV_S, {.VFPU_S,.MEM,.NONE,.NONE}, {.VFPU_VT_MEM,.VFPU_OFFSET_BASE,.NONE,.NONE}, 0xE8000000, 0xFC000000, .VFPU_PSP, {} },
{ .AUIPC, {.GPR,.IMM16S,.NONE,.NONE}, {.RS,.IMM_16,.NONE,.NONE}, 0xEC1E0000, 0xFC1F0000, .MIPS32_R6, {} },
{ .ALUIPC, {.GPR,.IMM16S,.NONE,.NONE}, {.RS,.IMM_16,.NONE,.NONE}, 0xEC1F0000, 0xFC1F0000, .MIPS32_R6, {} },
{ .LDPC, {.GPR,.REL18,.NONE,.NONE}, {.RS,.BRANCH_18,.NONE,.NONE}, 0xEC180000, 0xFC1C0000, .MIPS64_R6, {} },
{ .LWPC, {.GPR,.REL19,.NONE,.NONE}, {.RS,.BRANCH_19,.NONE,.NONE}, 0xEC080000, 0xFC180000, .MIPS32_R6, {} },
{ .LWUPC, {.GPR,.REL19,.NONE,.NONE}, {.RS,.BRANCH_19,.NONE,.NONE}, 0xEC100000, 0xFC180000, .MIPS64_R6, {} },
{ .VMIDT_P, {.VFPU_M_P,.NONE,.NONE,.NONE}, {.VFPU_VD,.NONE,.NONE,.NONE}, 0xF3830080, 0xFFFFFF80, .VFPU_PSP, {} },
{ .VMIDT_T, {.VFPU_M_T,.NONE,.NONE,.NONE}, {.VFPU_VD,.NONE,.NONE,.NONE}, 0xF3838000, 0xFFFFFF80, .VFPU_PSP, {} },
{ .VMIDT_Q, {.VFPU_M_Q,.NONE,.NONE,.NONE}, {.VFPU_VD,.NONE,.NONE,.NONE}, 0xF3838080, 0xFFFFFF80, .VFPU_PSP, {} },
@@ -1077,11 +1080,11 @@ DECODE_INDEX_PRIMARY := [64]lib.Decode_Index{
0x38 = { 959, 1},
0x39 = { 960, 1},
0x3A = { 961, 3},
0x3B = { 964, 2},
0x3C = { 966, 27},
0x3D = { 993, 3},
0x3E = { 996, 5},
0x3F = {1001, 4},
0x3B = { 964, 5},
0x3C = { 969, 27},
0x3D = { 996, 3},
0x3E = { 999, 5},
0x3F = {1004, 4},
}
@(rodata)

File diff suppressed because it is too large Load Diff

View File

@@ -350,27 +350,23 @@ end
-- ---- Branches: derive bits/regs, then mark the PC-relative offset variable.
-- Compact (R6) branches need the r6 ISA, so each family passes its own mattr.
local function bword(line, mattr)
local p = io.popen(string.format("printf '%%s\\n' '%s' | llvm-mc --assemble --triple=mips --mattr=%s --show-encoding 2>/dev/null", line, mattr))
local out = p:read("*a"); p:close()
local b1,b2,b3,b4 = out:match("0x(%x%x),0x(%x%x),0x(%x%x),0x(%x%x)")
if not b1 then return nil end
return tonumber(b1..b2..b3..b4, 16)
end
local function branch_block(mnem, ops, enc, feat, asm, maxes, offbits, mattr)
local function branch_block(mnem, ops, enc, feat, asm, maxes, offbits, cmd)
local zero={}; for i=1,#maxes do zero[i]=0 end
local b0 = bword(asm(zero), mattr)
local b0 = word(asm(zero), cmd)
if not b0 then skips[#skips+1]=mnem; return end
local vs={}
for i=1,#maxes do
local v={}; for j=1,#maxes do v[j]=0 end; v[i]=maxes[i]
local w=bword(asm(v), mattr); if not w then skips[#skips+1]=mnem; return end; vs[#vs+1]=w
local w=word(asm(v), cmd); if not w then skips[#skips+1]=mnem; return end; vs[#vs+1]=w
end
local m = bit.band(mask_of(b0,vs), bit.bnot((2^offbits)-1)) -- offset field = variable
n_forms=n_forms+1
sections[#sections+1]=string.format(" .%s = { {.%s, %s, %s, 0x%s, 0x%s, .%s, {}} },",
mnem, mnem, ops, enc, bit.tohex(b0):upper(), bit.tohex(m):upper(), feat)
end
local LLVM_MSA = "llvm-mc --assemble --triple=mips --mattr=+msa --show-encoding"
local LLVM_R6 = "llvm-mc --assemble --triple=mips --mattr=+mips32r6 --show-encoding"
local LLVM_64R6 = "llvm-mc --assemble --triple=mips64 --mattr=+mips64r6 --show-encoding"
-- NOTE: the R6 two-/one-register compact branches (BEQC/BNEC/BLTC/BGEC/BLTUC/
-- BGEUC/BLEZC/BGTZC/BGEZC/BLTZC) are intentionally NOT generated here. They
@@ -381,10 +377,18 @@ end
for _, b in ipairs({{"BZ","bz"},{"BNZ","bnz"}}) do
for _, d in ipairs({{"B","b"},{"H","h"},{"W","w"},{"D","d"},{"V","v"}}) do
branch_block(b[1].."_"..d[1], "{.MSA_VEC,.REL16,.NONE,.NONE}", "{.WT,.BRANCH_16,.NONE,.NONE}", "MSA",
function(v) return string.format("%s.%s $w%d,0", b[2], d[2], v[1]) end, {31}, 16, "+msa")
function(v) return string.format("%s.%s $w%d,0", b[2], d[2], v[1]) end, {31}, 16, LLVM_MSA)
end
end
-- ---- R6 PC-relative loads (offset is a 19-/18-bit PC-relative label) --------
branch_block("LWPC", "{.GPR,.REL19,.NONE,.NONE}", "{.RS,.BRANCH_19,.NONE,.NONE}", "MIPS32_R6",
function(v) return string.format("lwpc $%d,0", v[1]) end, {31}, 19, LLVM_R6)
branch_block("LWUPC", "{.GPR,.REL19,.NONE,.NONE}", "{.RS,.BRANCH_19,.NONE,.NONE}", "MIPS64_R6",
function(v) return string.format("lwupc $%d,0", v[1]) end, {31}, 19, LLVM_64R6)
branch_block("LDPC", "{.GPR,.REL18,.NONE,.NONE}", "{.RS,.BRANCH_18,.NONE,.NONE}", "MIPS64_R6",
function(v) return string.format("ldpc $%d,0", v[1]) end, {31}, 18, LLVM_64R6)
-- ---- splice into the SoT ---------------------------------------------------
local region = " // SPECGEN:BEGIN\n" .. table.concat(sections, "\n") .. "\n // SPECGEN:END"
local fh = assert(io.open(TABLE, "r")); local src = fh:read("*a"); fh:close()

View File

@@ -277,7 +277,7 @@ can_generate_operand :: proc(op: mips.Operand_Type) -> bool {
return true
case .IMM5, .IMM16S, .IMM16U, .IMM20, .SEL, .FCC:
return true
case .REL16, .REL21, .REL26, .REL_J26:
case .REL16, .REL21, .REL26, .REL_J26, .REL19, .REL18:
return true
case .MEM:
return true
@@ -313,7 +313,7 @@ operand_suffix :: proc(op: mips.Operand_Type) -> string {
case .IMM20: return "i20"
case .SEL: return "sel"
case .FCC: return "cc"
case .REL16: return "rel"
case .REL16, .REL19, .REL18: return "rel"
case .REL21: return "rel21"
case .REL26: return "rel26"
case .REL_J26: return "j"
@@ -338,7 +338,7 @@ operand_param_type :: proc(op: mips.Operand_Type) -> string {
return "Register"
case .IMM5, .IMM16S, .IMM16U, .IMM20, .SEL, .FCC:
return "i64"
case .REL16, .REL21, .REL26, .REL_J26:
case .REL16, .REL21, .REL26, .REL_J26, .REL19, .REL18:
return "u32"
case .MEM: return "Memory"
}
@@ -377,7 +377,7 @@ write_op_expr :: proc(sb: ^strings.Builder, op: mips.Operand_Type, name: string)
fmt.sbprintf(sb, "op_reg(%s)", name)
case .IMM5, .IMM16S, .IMM16U, .IMM20, .SEL, .FCC:
fmt.sbprintf(sb, "op_imm(%s, %d)", name, operand_imm_size(op))
case .REL16, .REL21, .REL26, .REL_J26:
case .REL16, .REL21, .REL26, .REL_J26, .REL19, .REL18:
fmt.sbprintf(sb, "op_label(%s)", name)
case .MEM:
fmt.sbprintf(sb, "op_mem(%s, 4)", name)
@@ -403,7 +403,7 @@ param_names :: proc(sig: Operand_Signature) -> [4]string {
case .IMM5, .IMM16S, .IMM16U, .IMM20, .SEL, .FCC:
result[i] = imm_n == 0 ? "imm" : fmt.tprintf("imm%d", imm_n + 1)
imm_n += 1
case .REL16, .REL21, .REL26, .REL_J26:
case .REL16, .REL21, .REL26, .REL_J26, .REL19, .REL18:
result[i] = rel_n == 0 ? "target" : fmt.tprintf("target%d", rel_n + 1)
rel_n += 1
case .MEM: