diff --git a/amd64_lower_arith.go b/amd64_lower_arith.go index c9b7197..5385adf 100644 --- a/amd64_lower_arith.go +++ b/amd64_lower_arith.go @@ -1495,6 +1495,40 @@ func (c *amd64Ctx) lowerArith(op Op, ins Instr) (ok bool, terminated bool, err e fmt.Fprintf(c.b, " %%%s = zext i32 %%%s to i64\n", z, rot) return true, false, c.storeReg(dst, "%"+z) + case "NOTB": + // NOT does not modify flags. For a register operand, only the selected + // low byte is changed and the remaining register bits are preserved. + if len(ins.Args) != 1 { + return true, false, fmt.Errorf("amd64 NOTB expects one operand: %q", ins.Raw) + } + switch ins.Args[0].Kind { + case OpReg: + r := ins.Args[0].Reg + v64, err := c.loadReg(r) + if err != nil { + return true, false, err + } + v8 := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i64 %s to i8\n", v8, v64) + not := c.newTmp() + fmt.Fprintf(c.b, " %%%s = xor i8 %%%s, -1\n", not, v8) + return true, false, c.storeRegSized(r, I8, "%"+not) + case OpMem: + addr, err := c.addrFromMem(ins.Args[0].Mem) + if err != nil { + return true, false, err + } + p := c.ptrFromAddrI64(addr) + load := c.newTmp() + fmt.Fprintf(c.b, " %%%s = load i8, ptr %s, align 1\n", load, p) + not := c.newTmp() + fmt.Fprintf(c.b, " %%%s = xor i8 %%%s, -1\n", not, load) + fmt.Fprintf(c.b, " store i8 %%%s, ptr %s, align 1\n", not, p) + return true, false, nil + default: + return true, false, fmt.Errorf("amd64 NOTB expects reg or mem: %q", ins.Raw) + } + case "NOTL": // 32-bit bitwise NOT, result zero-extended to 64-bit. if len(ins.Args) != 1 || ins.Args[0].Kind != OpReg { diff --git a/amd64_lower_branch.go b/amd64_lower_branch.go index 6e8e625..3df68b5 100644 --- a/amd64_lower_branch.go +++ b/amd64_lower_branch.go @@ -265,6 +265,7 @@ func (c *amd64Ctx) callSym(symOp Operand) error { // a known no-op runtime scheduler hook above. return fmt.Errorf("amd64 call missing signature for %q", callee) } + callee = funcSigSymbol(callee, csig) args := make([]string, 0, len(csig.Args)) for i := 0; i < len(csig.Args); i++ { @@ -350,6 +351,7 @@ func (c *amd64Ctx) tailCallAndRet(symOp Operand) error { csig = c.sig csig.Name = callee } + callee = funcSigSymbol(callee, csig) args := make([]string, 0, len(csig.Args)) for i := 0; i < len(csig.Args); i++ { diff --git a/amd64_new_opfamilies_test.go b/amd64_new_opfamilies_test.go index ec0dd3c..096e191 100644 --- a/amd64_new_opfamilies_test.go +++ b/amd64_new_opfamilies_test.go @@ -3,7 +3,42 @@ package plan9asm -import "testing" +import ( + "strings" + "testing" +) + +func TestTranslateAMD64NOTB(t *testing.T) { + src := ` +TEXT notb(SB),NOSPLIT,$0-0 + MOVQ $0x1234, R11 + NOTB R11 + NOTB 3(AX) + RET +` + file, err := Parse(ArchAMD64, src) + if err != nil { + t.Fatal(err) + } + ir, err := Translate(file, Options{ + TargetTriple: "x86_64-unknown-linux-gnu", + Sigs: map[string]FuncSig{ + "notb": {Name: "notb", Ret: Void}, + }, + Goarch: "amd64", + }) + if err != nil { + t.Fatal(err) + } + if got := strings.Count(ir, "xor i8"); got != 2 { + t.Fatalf("NOTB xor count = %d, want 2:\n%s", got, ir) + } + for _, want := range []string{"and i64", "or i64", "load i8", "store i8"} { + if !strings.Contains(ir, want) { + t.Fatalf("NOTB output missing %q:\n%s", want, ir) + } + } +} func TestTranslateAMD64SHA1Family(t *testing.T) { src := ` diff --git a/arm64_blocks.go b/arm64_blocks.go index c27aba6..46a9f3b 100644 --- a/arm64_blocks.go +++ b/arm64_blocks.go @@ -30,7 +30,7 @@ func arm64SplitBlocks(fn Func) []arm64Block { op = op[:dot] } switch Op(op) { - case "B", "JMP", "BEQ", "BNE", "BLO", "BHI", "BLT", "BGE", "BLE", "BGT", "BHS", "BLS", + case "B", "JMP", "BEQ", "BNE", "BLO", "BHI", "BLT", "BGE", "BLE", "BGT", "BHS", "BLS", "BMI", "BPL", "BCC", "CBZ", "CBNZ", "TBZ", "TBNZ": return true default: diff --git a/arm64_ctx.go b/arm64_ctx.go index 386f741..ca10b59 100644 --- a/arm64_ctx.go +++ b/arm64_ctx.go @@ -105,6 +105,18 @@ func arm64ParseVReg(r Reg) (idx int, ok bool) { return n, true } +func arm64ParseFReg(r Reg) (idx int, ok bool) { + s := strings.ToUpper(strings.TrimSpace(string(r))) + if !strings.HasPrefix(s, "F") { + return 0, false + } + n, err := strconv.Atoi(strings.TrimPrefix(s, "F")) + if err != nil || n < 0 || n > 31 { + return 0, false + } + return n, true +} + func (c *arm64Ctx) scanUsedRegs() { markReg := func(r Reg) { if r == "" { @@ -114,6 +126,12 @@ func (c *arm64Ctx) scanUsedRegs() { c.usedVRegs[idx] = true return } + // ARM64 F registers alias the corresponding 128-bit V registers. Keep + // the scalar slot for existing FP lowering and make the vector slot + // available to pair loads such as FLDPQ (F0, F1). + if idx, ok := arm64ParseFReg(r); ok { + c.usedVRegs[idx] = true + } c.usedRegs[r] = true } markOp := func(op Operand) { diff --git a/arm64_flags.go b/arm64_flags.go index cff9cd2..078f2a3 100644 --- a/arm64_flags.go +++ b/arm64_flags.go @@ -157,6 +157,10 @@ func (c *arm64Ctx) condValue(cond string) (string, error) { return and(not(z), eq(n, v)), nil case "LE": return or(z, xor(n, v)), nil + case "MI": + return n, nil + case "PL": + return not(n), nil default: return "", fmt.Errorf("arm64: unsupported condition %q", cond) } diff --git a/arm64_helper_edge_test.go b/arm64_helper_edge_test.go index fe2a615..010a2fe 100644 --- a/arm64_helper_edge_test.go +++ b/arm64_helper_edge_test.go @@ -32,6 +32,51 @@ func arm64MemOp(base Reg, off int64) Operand { } func arm64RegListOp(regs ...Reg) Operand { return Operand{Kind: OpRegList, RegList: regs} } +func TestARM64FPVectorPairValidation(t *testing.T) { + if idx, ok := arm64ParseFReg("F31"); !ok || idx != 31 { + t.Fatalf("arm64ParseFReg(F31) = (%d, %v)", idx, ok) + } + for _, r := range []Reg{"R0", "F32", "Fbad"} { + if _, ok := arm64ParseFReg(r); ok { + t.Fatalf("arm64ParseFReg(%s) unexpectedly succeeded", r) + } + } + + c, _ := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{Name: "example.vecerrors", Ret: Void}, nil) + if ok, _, err := c.lowerVec("FLDPQ", false, Instr{Raw: "FLDPQ"}); !ok || err == nil { + t.Fatalf("invalid FLDPQ = (%v, %v)", ok, err) + } + if ok, _, err := c.lowerVec("FLDPQ", false, Instr{ + Raw: "FLDPQ (BAD), (F0, F1)", + Args: []Operand{arm64MemOp("BAD", 0), arm64RegListOp("F0", "F1")}, + }); !ok || err == nil { + t.Fatalf("FLDPQ with bad base = (%v, %v)", ok, err) + } + if ok, _, err := c.lowerVec("FLDPQ", false, Instr{ + Raw: "FLDPQ (R1), (R0, R1)", + Args: []Operand{arm64MemOp("R1", 0), arm64RegListOp("R0", "R1")}, + }); !ok || err == nil { + t.Fatalf("FLDPQ with GPR pair = (%v, %v)", ok, err) + } + if ok, _, err := c.lowerVec("FLDPQ", false, Instr{ + Raw: "FLDPQ (R1), (F0, F1)", + Args: []Operand{arm64MemOp("R1", 0), arm64RegListOp("F0", "F1")}, + }); !ok || err == nil { + t.Fatalf("FLDPQ without vector slots = (%v, %v)", ok, err) + } + + for _, ins := range []Instr{ + {Raw: "VMOVI"}, + {Raw: "VMOVI $256, V0.B16", Args: []Operand{arm64ImmOp(256), arm64RegOp("V0.B16")}}, + {Raw: "VMOVI $1, V0.D2", Args: []Operand{arm64ImmOp(1), arm64RegOp("V0.D2")}}, + {Raw: "VMOVI $1, V0.B8", Args: []Operand{arm64ImmOp(1), arm64RegOp("V0.B8")}}, + } { + if ok, _, err := c.lowerVec("VMOVI", false, ins); !ok || err == nil { + t.Fatalf("invalid %q = (%v, %v)", ins.Raw, ok, err) + } + } +} + func mustLowerARM64(t *testing.T, kind string, ins Instr, ok bool, err error) { t.Helper() if err != nil { @@ -244,6 +289,7 @@ func TestARM64ArithmeticCoverage(t *testing.T) { {Op: "LSL", Args: []Operand{arm64ImmOp(3), arm64RegOp("R6"), arm64RegOp("R7")}, Raw: "LSL $3, R6, R7"}, {Op: "LSR", Args: []Operand{arm64RegOp("R0"), arm64RegOp("R7"), arm64RegOp("R8")}, Raw: "LSR R0, R7, R8"}, {Op: "LSLW", Args: []Operand{arm64RegOp("R1"), arm64RegOp("R8"), arm64RegOp("R9")}, Raw: "LSLW R1, R8, R9"}, + {Op: "LSRW", Args: []Operand{arm64ImmOp(2), arm64RegOp("R9"), arm64RegOp("R10")}, Raw: "LSRW $2, R9, R10"}, {Op: "ASR", Args: []Operand{arm64ImmOp(2), arm64RegOp("R9"), arm64RegOp("R10")}, Raw: "ASR $2, R9, R10"}, {Op: "UDIV", Args: []Operand{arm64RegOp("R2"), arm64RegOp("R10"), arm64RegOp("R0")}, Raw: "UDIV R2, R10, R0"}, {Op: "EXTR", Args: []Operand{arm64ImmOp(9), arm64RegOp("R0"), arm64RegOp("R1"), arm64RegOp("R2")}, Raw: "EXTR $9, R0, R1, R2"}, @@ -268,6 +314,12 @@ func TestARM64ArithmeticCoverage(t *testing.T) { if got, err := c.condValue("HI"); err != nil || got == "" { t.Fatalf("condValue(HI) = (%q, %v)", got, err) } + if got, err := c.condValue("MI"); err != nil || got == "" { + t.Fatalf("condValue(MI) = (%q, %v)", got, err) + } + if got, err := c.condValue("PL"); err != nil || got == "" { + t.Fatalf("condValue(PL) = (%q, %v)", got, err) + } if _, err := (&arm64Ctx{}).condValue("EQ"); err == nil { t.Fatalf("condValue without flags unexpectedly succeeded") } @@ -286,6 +338,7 @@ func TestARM64ArithmeticCoverage(t *testing.T) { `asm sideeffect "mrs $0, TPIDR_EL0"`, `asm sideeffect "msr S3_3_C4_C2_5, $0"`, "lshr i64", + "lshr i32", "shl i64", "ashr i64", "udiv i64", @@ -480,6 +533,8 @@ func TestARM64DataVectorAndBranchCoverage(t *testing.T) { {Op: "BLE", Args: []Operand{arm64IdentOp("done")}, Raw: "BLE done"}, {Op: "BCC", Args: []Operand{arm64IdentOp("done")}, Raw: "BCC done"}, {Op: "BCS", Args: []Operand{arm64IdentOp("done")}, Raw: "BCS done"}, + {Op: "BMI", Args: []Operand{arm64IdentOp("done")}, Raw: "BMI done"}, + {Op: "BPL", Args: []Operand{arm64IdentOp("done")}, Raw: "BPL done"}, {Op: "CBZ", Args: []Operand{arm64RegOp("R2"), arm64IdentOp("done")}, Raw: "CBZ R2, done"}, {Op: "CBNZ", Args: []Operand{arm64RegOp("R3"), arm64MemOp(PC, 4)}, Raw: "CBNZ R3, 4(PC)"}, {Op: "TBZ", Args: []Operand{arm64ImmOp(1), arm64RegOp("R4"), arm64IdentOp("done")}, Raw: "TBZ $1, R4, done"}, diff --git a/arm64_lower_arith.go b/arm64_lower_arith.go index ce5ebdf..20b8747 100644 --- a/arm64_lower_arith.go +++ b/arm64_lower_arith.go @@ -346,6 +346,23 @@ func (c *arm64Ctx) lowerArith(op Op, ins Instr) (ok bool, terminated bool, err e } return true, false, nil + case "TST": + if len(ins.Args) != 2 { + return true, false, fmt.Errorf("arm64 TST expects 2 operands: %q", ins.Raw) + } + a, err := c.eval64(ins.Args[0], false) + if err != nil { + return true, false, err + } + bval, err := c.eval64(ins.Args[1], false) + if err != nil { + return true, false, err + } + res := c.newTmp() + fmt.Fprintf(c.b, " %%%s = and i64 %s, %s\n", res, bval, a) + c.setFlagsLogic("%" + res) + return true, false, nil + case "ANDSW": if len(ins.Args) != 2 && len(ins.Args) != 3 { return true, false, fmt.Errorf("arm64 ANDSW expects 2 or 3 operands: %q", ins.Raw) @@ -796,10 +813,10 @@ func (c *arm64Ctx) lowerArith(op Op, ins Instr) (ok bool, terminated bool, err e } return true, false, c.storeReg(dstReg, "%"+t) - case "LSLW": - // LSLW shift, dstReg or LSLW shift, srcReg, dstReg + case "LSLW", "LSRW": + // LSLW/LSRW shift, dstReg or LSLW/LSRW shift, srcReg, dstReg if len(ins.Args) != 2 && len(ins.Args) != 3 { - return true, false, fmt.Errorf("arm64 LSLW expects 2 or 3 operands: %q", ins.Raw) + return true, false, fmt.Errorf("arm64 %s expects 2 or 3 operands: %q", op, ins.Raw) } var srcReg Reg var dstReg Reg @@ -807,13 +824,13 @@ func (c *arm64Ctx) lowerArith(op Op, ins Instr) (ok bool, terminated bool, err e if len(ins.Args) == 2 { sh = ins.Args[0] if ins.Args[1].Kind != OpReg { - return true, false, fmt.Errorf("arm64 LSLW expects shift, dstReg: %q", ins.Raw) + return true, false, fmt.Errorf("arm64 %s expects shift, dstReg: %q", op, ins.Raw) } srcReg, dstReg = ins.Args[1].Reg, ins.Args[1].Reg } else { sh = ins.Args[0] if ins.Args[1].Kind != OpReg || ins.Args[2].Kind != OpReg { - return true, false, fmt.Errorf("arm64 LSLW expects shift, srcReg, dstReg: %q", ins.Raw) + return true, false, fmt.Errorf("arm64 %s expects shift, srcReg, dstReg: %q", op, ins.Raw) } srcReg, dstReg = ins.Args[1].Reg, ins.Args[2].Reg } @@ -838,10 +855,14 @@ func (c *arm64Ctx) lowerArith(op Op, ins Instr) (ok bool, terminated bool, err e fmt.Fprintf(c.b, " %%%s = and i32 %%%s, 31\n", m, st) sh32 = "%" + m default: - return true, false, fmt.Errorf("arm64 LSLW unsupported shift operand: %q", ins.Raw) + return true, false, fmt.Errorf("arm64 %s unsupported shift operand: %q", op, ins.Raw) } t := c.newTmp() - fmt.Fprintf(c.b, " %%%s = shl i32 %%%s, %s\n", t, src32, sh32) + if op == "LSLW" { + fmt.Fprintf(c.b, " %%%s = shl i32 %%%s, %s\n", t, src32, sh32) + } else { + fmt.Fprintf(c.b, " %%%s = lshr i32 %%%s, %s\n", t, src32, sh32) + } z := c.newTmp() fmt.Fprintf(c.b, " %%%s = zext i32 %%%s to i64\n", z, t) return true, false, c.storeReg(dstReg, "%"+z) diff --git a/arm64_lower_branch.go b/arm64_lower_branch.go index 7fe71be..a6269db 100644 --- a/arm64_lower_branch.go +++ b/arm64_lower_branch.go @@ -90,7 +90,7 @@ func (c *arm64Ctx) lowerBranch(bi int, op Op, ins Instr, emitBr arm64EmitBr, emi emitBr(tgt) return true, true, nil - case "BEQ", "BNE", "BLO", "BLT", "BHI", "BHS", "BLS", "BGE", "BGT", "BLE", "BCC", "BCS": + case "BEQ", "BNE", "BLO", "BLT", "BHI", "BHS", "BLS", "BGE", "BGT", "BLE", "BCC", "BCS", "BMI", "BPL": if len(ins.Args) != 1 { return true, false, fmt.Errorf("arm64 %s expects label: %q", op, ins.Raw) } @@ -140,6 +140,10 @@ func (c *arm64Ctx) lowerBranch(bi int, op Op, ins Instr, emitBr arm64EmitBr, emi cond = "GT" case "BLE": cond = "LE" + case "BMI": + cond = "MI" + case "BPL": + cond = "PL" } if err := emitCondBr(cond, tgt, fall); err != nil { return true, false, err @@ -301,6 +305,7 @@ func (c *arm64Ctx) callSym(symOp Operand) error { // Default for external runtime helpers not discovered in this asm file. csig = FuncSig{Name: callee, Ret: Void} } + callee = funcSigSymbol(callee, csig) args := make([]string, 0, len(csig.Args)) regCursor := 0 for i := 0; i < len(csig.Args); i++ { @@ -375,6 +380,7 @@ func (c *arm64Ctx) tailCallAndRet(symOp Operand) error { csig = c.sig csig.Name = callee } + callee = funcSigSymbol(callee, csig) args := make([]string, 0, len(csig.Args)) regCursor := 0 diff --git a/arm64_lower_vec.go b/arm64_lower_vec.go index 851e9b6..e1f31c9 100644 --- a/arm64_lower_vec.go +++ b/arm64_lower_vec.go @@ -44,6 +44,68 @@ func arm64ParseVRegLane(r Reg) (kind byte, lane int, ok bool) { // We model V0..V31 as <16 x i8>. func (c *arm64Ctx) lowerVec(op Op, postInc bool, ins Instr) (ok bool, terminated bool, err error) { switch op { + case "FLDPQ": + if len(ins.Args) != 2 || ins.Args[0].Kind != OpMem || ins.Args[1].Kind != OpRegList || len(ins.Args[1].RegList) != 2 { + return true, false, fmt.Errorf("arm64 FLDPQ expects mem, (Freg,Freg): %q", ins.Raw) + } + mem := ins.Args[0].Mem + addr, base, inc, err := c.addrI64(mem, postInc) + if err != nil { + return true, false, err + } + for i, f := range ins.Args[1].RegList { + idx, ok := arm64ParseFReg(f) + if !ok { + return true, false, fmt.Errorf("arm64 FLDPQ expects FP register pair: %q", ins.Raw) + } + loadAddr := addr + if i != 0 { + next := c.newTmp() + fmt.Fprintf(c.b, " %%%s = add i64 %s, 16\n", next, addr) + loadAddr = "%" + next + } + ptr := c.newTmp() + fmt.Fprintf(c.b, " %%%s = inttoptr i64 %s to ptr\n", ptr, loadAddr) + value := c.newTmp() + fmt.Fprintf(c.b, " %%%s = load <16 x i8>, ptr %%%s, align 1\n", value, ptr) + if err := c.storeVReg(Reg(fmt.Sprintf("V%d", idx)), "%"+value); err != nil { + return true, false, err + } + } + if postInc { + if err := c.updatePostInc(base, inc); err != nil { + return true, false, err + } + } + return true, false, nil + + case "VMOVI": + if len(ins.Args) != 2 || ins.Args[0].Kind != OpImm || ins.Args[1].Kind != OpReg { + return true, false, fmt.Errorf("arm64 VMOVI expects $imm, Vreg.B8/B16: %q", ins.Raw) + } + imm := ins.Args[0].Imm + if imm < 0 || imm > 255 { + return true, false, fmt.Errorf("arm64 VMOVI byte immediate out of range: %q", ins.Raw) + } + dst := strings.ToUpper(string(ins.Args[1].Reg)) + activeLanes := 16 + switch { + case strings.Contains(dst, ".B8"): + activeLanes = 8 + case strings.Contains(dst, ".B16"): + default: + return true, false, fmt.Errorf("arm64 VMOVI unsupported vector arrangement: %q", ins.Raw) + } + elems := make([]string, 16) + for i := range elems { + value := int64(0) + if i < activeLanes { + value = imm + } + elems[i] = fmt.Sprintf("i8 %d", value) + } + return true, false, c.storeVReg(ins.Args[1].Reg, "<"+strings.Join(elems, ", ")+">") + case "AESE", "AESD", "AESMC", "AESIMC", "SHA1C", "SHA1H", "SHA1M", "SHA1P", "SHA1SU0", "SHA1SU1", "SHA256H", "SHA256H2", "SHA256SU0", "SHA256SU1", diff --git a/arm64_lower_word.go b/arm64_lower_word.go new file mode 100644 index 0000000..fc79f7d --- /dev/null +++ b/arm64_lower_word.go @@ -0,0 +1,53 @@ +package plan9asm + +import "fmt" + +// lowerRawWord models the small set of flag-writing instructions emitted as +// raw encodings by generated Go assembly. Unknown WORD values remain ignored, +// matching the translator's existing permissive behavior for embedded opcodes. +func (c *arm64Ctx) lowerRawWord(ins Instr) error { + if len(ins.Args) != 1 || ins.Args[0].Kind != OpImm || ins.Args[0].ImmRaw != "" { + return nil + } + + word := uint32(ins.Args[0].Imm) + switch { + case word == 0xea00001f: // TST X0, X0 (ANDS XZR, X0, X0) + value, err := c.loadReg("R0") + if err != nil { + return err + } + c.setFlagsLogic(value) + return nil + + case word&0xff800000 == 0xf1000000: // SUBS Xd, Xn, #imm{, LSL #12} + rn := (word >> 5) & 31 + rd := word & 31 + imm := int64((word >> 10) & 0xfff) + if word&(1<<22) != 0 { + imm <<= 12 + } + + srcReg := Reg(fmt.Sprintf("R%d", rn)) + if rn == 31 { + srcReg = SP + } + src, err := c.loadReg(srcReg) + if err != nil { + return err + } + + resTmp := c.newTmp() + fmt.Fprintf(c.b, " %%%s = sub i64 %s, %d\n", resTmp, src, imm) + res := "%" + resTmp + if rd != 31 { + if err := c.storeReg(Reg(fmt.Sprintf("R%d", rd)), res); err != nil { + return err + } + } + c.setFlagsSub(src, fmt.Sprintf("%d", imm), res) + return nil + } + + return nil +} diff --git a/arm64_new_opfamilies_test.go b/arm64_new_opfamilies_test.go index 38c143e..0234ec1 100644 --- a/arm64_new_opfamilies_test.go +++ b/arm64_new_opfamilies_test.go @@ -3,7 +3,128 @@ package plan9asm -import "testing" +import ( + "strings" + "testing" +) + +func TestTranslateARM64BranchMinus(t *testing.T) { + src := ` +TEXT branchminus(SB),NOSPLIT,$0-0 +loop: + SUBS $32, R2 + BMI complete + SUBS $32, R2 + BPL loop +complete: + RET +` + file, err := Parse(ArchARM64, src) + if err != nil { + t.Fatal(err) + } + ll, err := Translate(file, Options{ + TargetTriple: "aarch64-unknown-linux-gnu", + Sigs: map[string]FuncSig{ + "branchminus": {Name: "branchminus", Ret: Void}, + }, + Goarch: "arm64", + }) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(ll, "br i1") { + t.Fatalf("BMI did not lower to a conditional branch:\n%s", ll) + } +} + +func TestTranslateARM64RawFlagWords(t *testing.T) { + src := ` +TEXT rawflags(SB),NOSPLIT,$0-0 + MOVD $2, R0 + WORD $0xea00001f // TST X0, X0 + BEQ done +loop: + WORD $0xf1000400 // SUBS X0, X0, #1 + BNE loop +done: + RET +` + file, err := Parse(ArchARM64, src) + if err != nil { + t.Fatal(err) + } + ll, err := Translate(file, Options{ + TargetTriple: "aarch64-unknown-linux-gnu", + Sigs: map[string]FuncSig{ + "rawflags": {Name: "rawflags", Ret: Void}, + }, + Goarch: "arm64", + }) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(ll, "sub i64") || strings.Count(ll, "br i1") != 2 { + t.Fatalf("raw TST/SUBS flag words did not lower as expected:\n%s", ll) + } +} + +func TestTranslateARM64TST(t *testing.T) { + src := ` +TEXT testflags(SB),NOSPLIT,$0-0 + MOVD $1, R0 + TST R0, R0 + BEQ done +done: + RET +` + file, err := Parse(ArchARM64, src) + if err != nil { + t.Fatal(err) + } + ll, err := Translate(file, Options{ + TargetTriple: "aarch64-unknown-linux-gnu", + Sigs: map[string]FuncSig{ + "testflags": {Name: "testflags", Ret: Void}, + }, + Goarch: "arm64", + }) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(ll, "and i64") || !strings.Contains(ll, "br i1") { + t.Fatalf("TST did not lower to flags and a conditional branch:\n%s", ll) + } +} + +func TestTranslateARM64FLDPQ(t *testing.T) { + src := ` +TEXT pairload(SB),NOSPLIT,$0-0 + MOVD $4096, R1 + FLDPQ (R1), (F0, F1) + FLDPQ.P 32(R1), (F0, F1) + VMOVI $7, V2.B16 + VMOVI $3, V3.B8 + RET +` + file, err := Parse(ArchARM64, src) + if err != nil { + t.Fatal(err) + } + ll, err := Translate(file, Options{ + TargetTriple: "aarch64-unknown-linux-gnu", + Sigs: map[string]FuncSig{ + "pairload": {Name: "pairload", Ret: Void}, + }, + Goarch: "arm64", + }) + if err != nil { + t.Fatal(err) + } + if strings.Count(ll, "load <16 x i8>") != 4 || !strings.Contains(ll, "store <16 x i8>") || !strings.Contains(ll, "i8 7") || !strings.Contains(ll, "i8 3") { + t.Fatalf("FLDPQ/VMOVI did not lower as expected:\n%s", ll) + } +} func TestTranslateARM64SHA3Families(t *testing.T) { src := ` diff --git a/arm64_translate.go b/arm64_translate.go index 41f3eb8..220bf88 100644 --- a/arm64_translate.go +++ b/arm64_translate.go @@ -122,7 +122,9 @@ func (c *arm64Ctx) lowerInstr(bi int, ins Instr, emitBr arm64EmitBr, emitCondBr return false, nil case OpRET: return true, c.lowerRET() - case "PCALIGN", "NO_LOCAL_POINTERS", "PCDATA", "FUNCDATA", "WORD", "DMB", "DSB", "ISB", "DC", "PRFM", + case "WORD": + return false, c.lowerRawWord(ins) + case "PCALIGN", "NO_LOCAL_POINTERS", "PCDATA", "FUNCDATA", "DMB", "DSB", "ISB", "DC", "PRFM", "BREAK", "BRK", "UNDEF", "#UNDEF", "YIELD", "NOP", "FLDPD", "FSTPD", "FMOVS", "STY", "P256ADDINLINE", "P256MULBY2INLINE", "MOV", "CCMP", diff --git a/arm_lower_branch.go b/arm_lower_branch.go index d596a6e..c0b8613 100644 --- a/arm_lower_branch.go +++ b/arm_lower_branch.go @@ -127,6 +127,7 @@ func (c *armCtx) tailCallAndRet(symOp Operand) error { if !ok { return fmt.Errorf("arm tailcall missing signature for %q", callee) } + callee = funcSigSymbol(callee, csig) args := make([]string, 0, len(csig.Args)) for i := 0; i < len(csig.Args); i++ { r := Reg(fmt.Sprintf("R%d", i)) @@ -180,6 +181,7 @@ func (c *armCtx) callSym(symOp Operand) error { if !ok { csig = FuncSig{Name: callee, Ret: Void} } + callee = funcSigSymbol(callee, csig) args := make([]string, 0, len(csig.Args)) for i := 0; i < len(csig.Args); i++ { r := Reg(fmt.Sprintf("R%d", i)) diff --git a/go_translate_test.go b/go_translate_test.go index a472ed9..c5554b0 100644 --- a/go_translate_test.go +++ b/go_translate_test.go @@ -82,6 +82,40 @@ TEXT indexbytebody<>(SB),NOSPLIT,$0 } } +func TestTranslateGoModule_UsesManualSigExternalName(t *testing.T) { + pkg := mustGoPackage(t, "test/pkg", `package testpkg +func Call() +`) + asm := []byte(`TEXT ·Call(SB),NOSPLIT,$0-0 + CALL runtime·memmove(SB) + RET +`) + + for _, goarch := range []string{"amd64", "arm64"} { + t.Run(goarch, func(t *testing.T) { + tr, err := TranslateGoModule(pkg, asm, GoModuleOptions{ + FileName: "call_" + goarch + ".s", + GOARCH: goarch, + ResolveSym: testResolveSym("test/pkg"), + ManualSig: func(resolved string) (FuncSig, bool) { + if resolved != "runtime.memmove" { + return FuncSig{}, false + } + return FuncSig{Name: "memmove", Args: []LLVMType{Ptr, Ptr, I64}, Ret: Ptr}, true + }, + }) + if err != nil { + t.Fatal(err) + } + defer tr.Module.Dispose() + ir := tr.Module.String() + if !strings.Contains(ir, "@memmove") || strings.Contains(ir, "runtime.memmove") { + t.Fatalf("manual external name not applied:\n%s", ir) + } + }) + } +} + func mustGoPackage(t *testing.T, pkgPath, src string) GoPackage { t.Helper() fset := token.NewFileSet() diff --git a/parser_test.go b/parser_test.go index ae59b0c..c62cd06 100644 --- a/parser_test.go +++ b/parser_test.go @@ -163,3 +163,31 @@ RET t.Fatalf("second expanded op=%s, want %s", file.Funcs[0].Instrs[2].Op, Op("ADDL")) } } + +func TestParseZeroArgFunctionLikeMacroCall(t *testing.T) { + src := ` +#define CHECK() \ + CMP R1, R2 \ + BGT corrupt +TEXT ·FnMacro(SB),NOSPLIT,$0 +CHECK() +corrupt: +RET +` + file, err := Parse(ArchARM64, src) + if err != nil { + t.Fatal(err) + } + if len(file.Funcs) != 1 { + t.Fatalf("Funcs=%d, want 1", len(file.Funcs)) + } + var branchTarget string + for _, ins := range file.Funcs[0].Instrs { + if ins.Op == Op("BGT") && len(ins.Args) == 1 { + branchTarget = ins.Args[0].Ident + } + } + if branchTarget != "corrupt" { + t.Fatalf("BGT target=%q, want %q", branchTarget, "corrupt") + } +} diff --git a/preprocess.go b/preprocess.go index 33600a8..4bcbbc7 100644 --- a/preprocess.go +++ b/preprocess.go @@ -304,7 +304,7 @@ func expandPPLine(line string, macros map[string]ppMacro, macroNames []string, d } for _, name := range macroNames { m := macros[name] - if len(m.params) == 0 { + if m.params == nil { continue } args, ok := parseMacroCall(trimLine, name, len(m.params)) @@ -319,7 +319,7 @@ func expandPPLine(line string, macros map[string]ppMacro, macroNames []string, d } return out } - if m, ok := macros[trimLine]; ok && len(m.params) == 0 { + if m, ok := macros[trimLine]; ok && m.params == nil { chunks := strings.Split(m.body, "\n") out := make([]string, 0, len(chunks)) for _, ch := range chunks { @@ -332,7 +332,7 @@ func expandPPLine(line string, macros map[string]ppMacro, macroNames []string, d inlineChanged := false for _, name := range macroNames { m := macros[name] - if len(m.params) == 0 { + if m.params == nil { continue } nl, changed := expandInlineMacroCalls(line, name, m) @@ -351,7 +351,7 @@ func expandPPLine(line string, macros map[string]ppMacro, macroNames []string, d // Expand immediate macro refs in-place: $NAME -> $. for _, name := range macroNames { m := macros[name] - if len(m.params) != 0 { + if m.params != nil { continue } body := strings.TrimSpace(m.body) @@ -377,7 +377,7 @@ func expandIdentMacros(line string, macros map[string]ppMacro, macroNames []stri changed := false for _, name := range macroNames { m := macros[name] - if len(m.params) != 0 { + if m.params != nil { continue } body := strings.TrimSpace(m.body) @@ -463,7 +463,7 @@ func replaceMacroIdents(expr string, macros map[string]ppMacro) string { j++ } name := expr[i:j] - if m, ok := macros[name]; ok && len(m.params) == 0 && strings.TrimSpace(m.body) != "" { + if m, ok := macros[name]; ok && m.params == nil && strings.TrimSpace(m.body) != "" { out.WriteString(strings.TrimSpace(m.body)) } else { out.WriteString(name) @@ -499,6 +499,9 @@ func parseMacroDefine(rest string) (name string, params []string, body string, e } name = rest[:i] if i < len(rest) && rest[i] == '(' { + // Keep an empty non-nil slice for function-like macros with no + // parameters. A nil slice identifies object-like macros. + params = []string{} j := i + 1 depth := 1 for ; j < len(rest); j++ { @@ -580,7 +583,7 @@ closeFound: } func expandInlineMacroCalls(line, name string, m ppMacro) (string, bool) { - if len(m.params) == 0 || line == "" { + if m.params == nil || line == "" { return line, false } var out strings.Builder diff --git a/translate.go b/translate.go index 94a6264..6ecc1d4 100644 --- a/translate.go +++ b/translate.go @@ -213,7 +213,7 @@ func emitExternFuncDecls(b *strings.Builder, file *File, resolve func(string) st if sig.Ret == "" { continue } - fmt.Fprintf(b, "declare %s %s(", sig.Ret, llvmGlobal(name)) + fmt.Fprintf(b, "declare %s %s(", sig.Ret, llvmGlobal(funcSigSymbol(name, sig))) for i, t := range sig.Args { if i > 0 { b.WriteString(", ") @@ -231,6 +231,13 @@ func emitExternFuncDecls(b *strings.Builder, file *File, resolve func(string) st } } +func funcSigSymbol(resolved string, sig FuncSig) string { + if sig.Name != "" { + return sig.Name + } + return resolved +} + func emitExternSBGlobals(b *strings.Builder, file *File, resolve func(string) string, sigs map[string]FuncSig) { resolveSBBase := func(base string) string { base = strings.TrimSpace(base)