From 24cfffccac1ca4c19294a7d5b8bca753119b4315 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sat, 14 Mar 2026 07:41:59 +0800 Subject: [PATCH 1/6] Add deep ARM coverage tests --- arm_deep_coverage_test.go | 479 ++++++++++++++++++++++++++++++++++++++ arm_helper_edge_test.go | 290 +++++++++++++++++++++++ arm_more_test.go | 392 +++++++++++++++++++++++++++++++ 3 files changed, 1161 insertions(+) create mode 100644 arm_deep_coverage_test.go create mode 100644 arm_helper_edge_test.go create mode 100644 arm_more_test.go diff --git a/arm_deep_coverage_test.go b/arm_deep_coverage_test.go new file mode 100644 index 0000000..9a352a2 --- /dev/null +++ b/arm_deep_coverage_test.go @@ -0,0 +1,479 @@ +package plan9asm + +import ( + "strings" + "testing" +) + +func TestARMEvalCoverage(t *testing.T) { + c, b := newARMCtxForTest(t, FuncSig{ + Name: "example.eval", + Args: []LLVMType{Ptr, I1, I8, I16, I32, I64, "{ ptr, i64 }"}, + Ret: Void, + Frame: FrameLayout{ + Params: []FrameSlot{ + {Offset: 0, Type: Ptr, Index: 0, Field: -1}, + {Offset: 4, Type: I1, Index: 1, Field: -1}, + {Offset: 8, Type: I8, Index: 2, Field: -1}, + {Offset: 12, Type: I16, Index: 3, Field: -1}, + {Offset: 16, Type: I32, Index: 4, Field: -1}, + {Offset: 20, Type: I64, Index: 5, Field: -1}, + {Offset: 24, Type: I64, Index: 6, Field: 1}, + }, + Results: []FrameSlot{{Offset: 32, Type: I32, Index: 0}}, + }, + }, nil) + + if got, base, inc, err := c.addrI32(MemRef{Base: "R1", Index: "R2", Scale: 4, Off: 8}, true); err != nil || got == "" || base != "R1" || inc != 8 { + t.Fatalf("addrI32() = (%q, %q, %d, %v)", got, base, inc, err) + } + if err := c.updatePostInc("R1", 8); err != nil { + t.Fatalf("updatePostInc() error = %v", err) + } + if _, err := c.loadMem(MemRef{Base: "R3"}, 64, false, false); err != nil { + t.Fatalf("loadMem(64) error = %v", err) + } + if _, err := c.loadMem(MemRef{Base: "R4", Off: 1}, 8, false, true); err != nil { + t.Fatalf("loadMem(8 signed) error = %v", err) + } + if err := c.storeMem(MemRef{Base: "R5"}, 32, false, "7"); err != nil { + t.Fatalf("storeMem(32) error = %v", err) + } + if err := c.storeMem(MemRef{Base: "R6"}, 8, false, "9"); err != nil { + t.Fatalf("storeMem(8) error = %v", err) + } + + ops := []Operand{ + {Kind: OpImm, Imm: 7}, + {Kind: OpReg, Reg: "R0"}, + {Kind: OpRegShift, Reg: "R1", ShiftOp: ShiftLeft, ShiftAmount: 2}, + {Kind: OpFP, FPName: "p", FPOffset: 0}, + {Kind: OpFP, FPName: "b", FPOffset: 4}, + {Kind: OpFP, FPName: "c", FPOffset: 8}, + {Kind: OpFP, FPName: "d", FPOffset: 12}, + {Kind: OpFP, FPName: "e", FPOffset: 16}, + {Kind: OpFP, FPName: "f", FPOffset: 20}, + {Kind: OpFP, FPName: "agg", FPOffset: 24}, + {Kind: OpFPAddr, FPName: "ret", FPOffset: 32}, + {Kind: OpMem, Mem: MemRef{Base: "R7", Off: 4}}, + {Kind: OpSym, Sym: "$runtime·main+4(SB)"}, + {Kind: OpIdent, Ident: "CS"}, + } + for _, op := range ops { + if got, err := c.eval32(op, op.Kind == OpMem); err != nil || got == "" { + t.Fatalf("eval32(%s) = (%q, %v)", op.String(), got, err) + } + } + if _, err := c.eval32(Operand{Kind: OpImm, ImmRaw: "$(bad)"}, false); err == nil { + t.Fatalf("eval32(unresolved imm) unexpectedly succeeded") + } + if _, err := c.evalShift(Operand{Kind: OpRegShift, Reg: "R0", ShiftOp: ShiftRotate, ShiftReg: "R1"}); err != nil { + t.Fatalf("evalShift(register rotate) error = %v", err) + } + + out := b.String() + for _, want := range []string{ + "mul i32", + "sext i8", + "store i32 7, ptr", + "trunc i32 9 to i8", + "extractvalue { ptr, i64 } %arg6, 1", + `getelementptr i8, ptr @"runtime.main", i32 4`, + "ptrtoint ptr", + "call i32 @llvm.fshr.i32", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestARMFPAndRetCoverage(t *testing.T) { + t.Run("StoreAndLoadFPResults", func(t *testing.T) { + c, b := newARMCtxForTest(t, FuncSig{ + Name: "example.fp", + Ret: I64, + Frame: FrameLayout{ + Results: []FrameSlot{ + {Offset: 8, Type: I32, Index: 0}, + {Offset: 16, Type: I1, Index: 1}, + {Offset: 24, Type: Ptr, Index: 2}, + {Offset: 32, Type: I64, Index: 3}, + }, + }, + }, nil) + + if _, ok := c.fpResultSlotByOffset(8); !ok { + t.Fatalf("fpResultSlotByOffset(8) not found") + } + if _, ok := c.fpResultSlotByOffset(99); ok { + t.Fatalf("fpResultSlotByOffset(99) unexpectedly found") + } + if err := c.storeFPResult32(8, "11"); err != nil { + t.Fatalf("storeFPResult32(i32) error = %v", err) + } + if err := c.storeFPResult32(16, "1"); err != nil { + t.Fatalf("storeFPResult32(i1) error = %v", err) + } + if err := c.storeFPResult32(24, "12"); err != nil { + t.Fatalf("storeFPResult32(ptr) error = %v", err) + } + if err := c.storeFPResult32(32, "13"); err != nil { + t.Fatalf("storeFPResult32(i64) error = %v", err) + } + if _, err := c.loadFPResult(FrameSlot{Index: 3, Type: I64}); err != nil { + t.Fatalf("loadFPResult() error = %v", err) + } + if _, err := c.loadFPResult(FrameSlot{Index: 99, Type: I64}); err == nil { + t.Fatalf("loadFPResult(missing) unexpectedly succeeded") + } + if _, err := c.loadRetSlotFallback(FrameSlot{Type: I16}); err != nil { + t.Fatalf("loadRetSlotFallback(i16) error = %v", err) + } + + out := b.String() + for _, want := range []string{ + "store i32 11, ptr %fp_ret_0", + "trunc i32 1 to i1", + "inttoptr i32 12 to ptr", + "zext i32 13 to i64", + "load i64, ptr %fp_ret_3", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } + }) + + t.Run("LowerRETVariants", func(t *testing.T) { + for _, tc := range []struct { + ret LLVMType + want string + }{ + {ret: Void, want: "ret void"}, + {ret: I1, want: "ret i1"}, + {ret: I8, want: "ret i8"}, + {ret: I16, want: "ret i16"}, + {ret: I32, want: "ret i32"}, + {ret: Ptr, want: "ret ptr"}, + } { + c, b := newARMCtxForTest(t, FuncSig{Name: "example.ret", Ret: tc.ret}, nil) + if err := c.lowerRET(); err != nil { + t.Fatalf("lowerRET(%s) error = %v", tc.ret, err) + } + if !strings.Contains(b.String(), tc.want) { + t.Fatalf("lowerRET(%s) missing %q in:\n%s", tc.ret, tc.want, b.String()) + } + } + + c1, b1 := newARMCtxForTest(t, FuncSig{ + Name: "example.ret1", + Ret: I32, + Frame: FrameLayout{ + Results: []FrameSlot{{Offset: 8, Type: I32, Index: 0}}, + }, + }, nil) + if err := c1.storeFPResult32(8, "99"); err != nil { + t.Fatalf("storeFPResult32() error = %v", err) + } + if err := c1.lowerRET(); err != nil { + t.Fatalf("lowerRET(single written) error = %v", err) + } + if !strings.Contains(b1.String(), "ret i32 %t") { + t.Fatalf("lowerRET(single written) missing loaded return in:\n%s", b1.String()) + } + + c2, b2 := newARMCtxForTest(t, FuncSig{ + Name: "example.rettuple", + Ret: "{ i32, ptr }", + Frame: FrameLayout{ + Results: []FrameSlot{ + {Offset: 8, Type: I32, Index: 0}, + {Offset: 16, Type: Ptr, Index: 1}, + }, + }, + }, nil) + if err := c2.storeFPResult32(8, "5"); err != nil { + t.Fatalf("storeFPResult32(tuple) error = %v", err) + } + c2.markFPResultAddrTaken(16) + if err := c2.lowerRET(); err != nil { + t.Fatalf("lowerRET(tuple) error = %v", err) + } + for _, want := range []string{"insertvalue { i32, ptr }", "ret { i32, ptr }"} { + if !strings.Contains(b2.String(), want) { + t.Fatalf("lowerRET(tuple) missing %q in:\n%s", want, b2.String()) + } + } + + c3, _ := newARMCtxForTest(t, FuncSig{Name: "example.bad", Ret: LLVMType("token")}, nil) + if err := c3.lowerRET(); err == nil { + t.Fatalf("lowerRET(unsupported) unexpectedly succeeded") + } + }) +} + +func TestARMArithCoverageDeep(t *testing.T) { + c, b := newARMCtxForTest(t, FuncSig{Name: "example.arith", Ret: Void}, nil) + + if err := c.lowerARMCompare("CMP", Instr{Raw: "CMP R0, R1", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}}}); err != nil { + t.Fatalf("lowerARMCompare(CMP) error = %v", err) + } + for _, ins := range []struct { + op string + cond string + setFlags bool + ins Instr + }{ + {op: "ADD", setFlags: true, ins: Instr{Raw: "ADD $1, R0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: "R0"}}}}, + {op: "SUB", ins: Instr{Raw: "SUB R0, R1, R2", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R2"}}}}, + {op: "AND", cond: "EQ", setFlags: true, ins: Instr{Raw: "AND.EQ.S R0, R2, R3", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R2"}, {Kind: OpReg, Reg: "R3"}}}}, + {op: "ORR", ins: Instr{Raw: "ORR R1, R3, R4", Args: []Operand{{Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R3"}, {Kind: OpReg, Reg: "R4"}}}}, + {op: "EOR", ins: Instr{Raw: "EOR R2, R4, R5", Args: []Operand{{Kind: OpReg, Reg: "R2"}, {Kind: OpReg, Reg: "R4"}, {Kind: OpReg, Reg: "R5"}}}}, + {op: "RSB", ins: Instr{Raw: "RSB $10, R5, R6", Args: []Operand{{Kind: OpImm, Imm: 10}, {Kind: OpReg, Reg: "R5"}, {Kind: OpReg, Reg: "R6"}}}}, + {op: "BIC", setFlags: true, ins: Instr{Raw: "BIC.S $1, R6, R7", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: "R6"}, {Kind: OpReg, Reg: "R7"}}}}, + } { + if err := c.lowerARMALU(ins.op, ins.cond, ins.setFlags, ins.ins); err != nil { + t.Fatalf("lowerARMALU(%s) error = %v", ins.op, err) + } + } + for _, op := range []string{"CMN", "TST", "TEQ"} { + if err := c.lowerARMCompare(op, Instr{Raw: op + " R0, R1", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}}}); err != nil { + t.Fatalf("lowerARMCompare(%s) error = %v", op, err) + } + } + if err := c.lowerARMMVN("EQ", true, Instr{Raw: "MVN.EQ.S R0, R8", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R8"}}}); err != nil { + t.Fatalf("lowerARMMVN() error = %v", err) + } + if err := c.lowerARMADCSBC("ADC", "EQ", true, Instr{Raw: "ADC.EQ.S R1, R8, R9", Args: []Operand{{Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R8"}, {Kind: OpReg, Reg: "R9"}}}); err != nil { + t.Fatalf("lowerARMADCSBC(ADC) error = %v", err) + } + if err := c.lowerARMADCSBC("SBC", "", true, Instr{Raw: "SBC.S R1, R9, R10", Args: []Operand{{Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R9"}, {Kind: OpReg, Reg: "R10"}}}); err != nil { + t.Fatalf("lowerARMADCSBC(SBC) error = %v", err) + } + if err := c.lowerARMMUL("", Instr{Raw: "MUL R0, R1", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}}}); err != nil { + t.Fatalf("lowerARMMUL(2-arg) error = %v", err) + } + if err := c.lowerARMMUL("EQ", Instr{Raw: "MUL.EQ R0, R1, R2", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R2"}}}); err != nil { + t.Fatalf("lowerARMMUL(3-arg) error = %v", err) + } + if err := c.lowerARMMULLU("EQ", Instr{Raw: "MULLU.EQ R0, R1, (R3, R4)", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}, {Kind: OpRegList, RegList: []Reg{"R3", "R4"}}}}); err != nil { + t.Fatalf("lowerARMMULLU() error = %v", err) + } + if err := c.lowerARMMULA("", Instr{Raw: "MULA R0, R1, R2, R5", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R2"}, {Kind: OpReg, Reg: "R5"}}}); err != nil { + t.Fatalf("lowerARMMULA() error = %v", err) + } + if err := c.lowerARMMULAL("EQ", Instr{Raw: "MULAL.EQ R0, R1, (R6, R7)", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}, {Kind: OpRegList, RegList: []Reg{"R6", "R7"}}}}); err != nil { + t.Fatalf("lowerARMMULAL() error = %v", err) + } + if err := c.lowerARMMULAWT("", Instr{Raw: "MULAWT R0, R1, R2, R8", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R2"}, {Kind: OpReg, Reg: "R8"}}}); err != nil { + t.Fatalf("lowerARMMULAWT() error = %v", err) + } + if err := c.lowerARMDIVUHW("EQ", Instr{Raw: "DIVUHW.EQ R1, R2, R9", Args: []Operand{{Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R2"}, {Kind: OpReg, Reg: "R9"}}}); err != nil { + t.Fatalf("lowerARMDIVUHW() error = %v", err) + } + if err := c.lowerARMCLZ("", Instr{Raw: "CLZ R9, R10", Args: []Operand{{Kind: OpReg, Reg: "R9"}, {Kind: OpReg, Reg: "R10"}}}); err != nil { + t.Fatalf("lowerARMCLZ() error = %v", err) + } + if err := c.lowerARMMRC(Instr{Raw: "MRC $15, $0, R11, $1, $0, $0", Args: []Operand{{Kind: OpImm, Imm: 15}, {Kind: OpImm, Imm: 0}, {Kind: OpReg, Reg: "R11"}, {Kind: OpImm, Imm: 1}, {Kind: OpImm, Imm: 0}, {Kind: OpImm, Imm: 0}}}); err != nil { + t.Fatalf("lowerARMMRC() error = %v", err) + } + if ok, _, err := c.lowerArith("MULU", "", false, Instr{Raw: "MULU R0, R1", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}}}); !ok || err != nil { + t.Fatalf("lowerArith(MULU) = (%v, %v)", ok, err) + } + if ok, _, err := c.lowerArith("UNKNOWN", "", false, Instr{}); ok || err != nil { + t.Fatalf("lowerArith(UNKNOWN) = (%v, %v), want (false, nil)", ok, err) + } + if err := c.selectRegPairWrite("R1", "R2", "EQ", "1", "2"); err != nil { + t.Fatalf("selectRegPairWrite(EQ) error = %v", err) + } + if err := c.lowerARMALU("ADD", "", false, Instr{Raw: "ADD R0", Args: []Operand{{Kind: OpReg, Reg: "R0"}}}); err == nil { + t.Fatalf("lowerARMALU(invalid) unexpectedly succeeded") + } + if err := c.lowerARMCompare("CMP", Instr{Raw: "CMP R0", Args: []Operand{{Kind: OpReg, Reg: "R0"}}}); err == nil { + t.Fatalf("lowerARMCompare(invalid) unexpectedly succeeded") + } + if err := c.lowerARMMVN("", false, Instr{Raw: "MVN R0, $1", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpImm, Imm: 1}}}); err == nil { + t.Fatalf("lowerARMMVN(invalid dst) unexpectedly succeeded") + } + if err := c.lowerARMADCSBC("ADC", "", false, Instr{Raw: "ADC R0, $1", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpImm, Imm: 1}}}); err == nil { + t.Fatalf("lowerARMADCSBC(invalid dst) unexpectedly succeeded") + } + if err := c.lowerARMMULLU("", Instr{Raw: "MULLU R0, R1, R2", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R2"}}}); err == nil { + t.Fatalf("lowerARMMULLU(invalid) unexpectedly succeeded") + } + if err := c.lowerARMMULA("", Instr{Raw: "MULA R0, R1, R2", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R2"}}}); err == nil { + t.Fatalf("lowerARMMULA(invalid) unexpectedly succeeded") + } + if err := c.lowerARMMULAL("", Instr{Raw: "MULAL R0, R1, R2", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R2"}}}); err == nil { + t.Fatalf("lowerARMMULAL(invalid) unexpectedly succeeded") + } + if err := c.lowerARMMULAWT("", Instr{Raw: "MULAWT R0, R1, R2", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R2"}}}); err == nil { + t.Fatalf("lowerARMMULAWT(invalid) unexpectedly succeeded") + } + if err := c.lowerARMDIVUHW("", Instr{Raw: "DIVUHW R0, R1", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}}}); err == nil { + t.Fatalf("lowerARMDIVUHW(invalid) unexpectedly succeeded") + } + if err := c.lowerARMCLZ("", Instr{Raw: "CLZ R0, $1", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpImm, Imm: 1}}}); err == nil { + t.Fatalf("lowerARMCLZ(invalid) unexpectedly succeeded") + } + if err := c.lowerARMMRC(Instr{Raw: "MRC $15, R0", Args: []Operand{{Kind: OpImm, Imm: 15}, {Kind: OpReg, Reg: "R0"}}}); err == nil { + t.Fatalf("lowerARMMRC(invalid) unexpectedly succeeded") + } + + out := b.String() + for _, want := range []string{ + "add i32", + "sub i32", + "and i32", + "or i32", + "xor i32", + "mul i64", + "udiv i32", + "call i32 @llvm.ctlz.i32", + `asm sideeffect "mrc p15, 0, $0, 1, 0, 0"`, + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestARMBranchMovmAndSyscallCoverage(t *testing.T) { + sigs := map[string]FuncSig{ + "example.helper": {Name: "example.helper", Ret: Void}, + "example.tail": {Name: "example.tail", Ret: Void}, + } + c, b := newARMCtxForTest(t, FuncSig{Name: "example.branch", Ret: Void}, sigs) + c.flagsWritten = true + c.blocks = []armBlock{{name: "entry"}, {name: "fall"}} + + var gotBr string + var gotCond [3]string + emitBr := func(target string) { gotBr = target } + emitCondBr := func(cond, target, fall string) error { + gotCond = [3]string{cond, target, fall} + return nil + } + if ok, term, err := c.lowerBranch(0, "JMP", "", Instr{Raw: "JMP loop", Args: []Operand{{Kind: OpIdent, Ident: "loop"}}}, emitBr, emitCondBr); !ok || !term || err != nil { + t.Fatalf("lowerBranch(JMP) = (%v, %v, %v)", ok, term, err) + } + if gotBr != "loop" { + t.Fatalf("emitBr target = %q, want loop", gotBr) + } + if ok, term, err := c.lowerBranch(0, "B", "EQ", Instr{Raw: "B.EQ done", Args: []Operand{{Kind: OpIdent, Ident: "done"}}}, emitBr, emitCondBr); !ok || !term || err != nil { + t.Fatalf("lowerBranch(B.EQ) = (%v, %v, %v)", ok, term, err) + } + if gotCond != [3]string{"EQ", "done", "fall"} { + t.Fatalf("emitCondBr args = %#v", gotCond) + } + if ok, term, err := c.lowerBranch(0, "B", "", Instr{Raw: "B tail(SB)", Args: []Operand{{Kind: OpSym, Sym: "tail(SB)"}}}, emitBr, emitCondBr); !ok || !term || err != nil { + t.Fatalf("lowerBranch(B sym) = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := c.lowerBranch(0, "BEQ", "", Instr{Raw: "BEQ done", Args: []Operand{{Kind: OpIdent, Ident: "done"}}}, emitBr, emitCondBr); !ok || !term || err != nil { + t.Fatalf("lowerBranch(BEQ) = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := c.lowerBranch(0, "BHI", "", Instr{Raw: "BHI done", Args: []Operand{{Kind: OpIdent, Ident: "done"}}}, emitBr, emitCondBr); !ok || !term || err != nil { + t.Fatalf("lowerBranch(BHI) = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := c.lowerBranch(0, "CALL", "", Instr{Raw: "CALL helper(SB)", Args: []Operand{{Kind: OpSym, Sym: "helper(SB)"}}}, emitBr, emitCondBr); !ok || term || err != nil { + t.Fatalf("lowerBranch(CALL sym) = (%v, %v, %v)", ok, term, err) + } + + if ok, _, err := c.lowerMOVM("MOVM.IB", Instr{Raw: "MOVM.IB (R13), [R0,R1]", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: "R13"}}, {Kind: OpRegList, RegList: []Reg{"R0", "R1"}}}}); !ok || err != nil { + t.Fatalf("lowerMOVM(IB load) = (%v, %v)", ok, err) + } + if ok, _, err := c.lowerMOVM("MOVM.DA", Instr{Raw: "MOVM.DA [R2,R3], (R13)", Args: []Operand{{Kind: OpRegList, RegList: []Reg{"R2", "R3"}}, {Kind: OpMem, Mem: MemRef{Base: "R13"}}}}); !ok || err != nil { + t.Fatalf("lowerMOVM(DA store) = (%v, %v)", ok, err) + } + if ok, _, err := c.lowerSyscall("SWI", Instr{Raw: "SWI $1", Args: []Operand{{Kind: OpImm, Imm: 1}}}); !ok || err != nil { + t.Fatalf("lowerSyscall(SWI imm) = (%v, %v)", ok, err) + } + if ok, _, err := c.lowerSyscall("SWI", Instr{Raw: "SWI R0", Args: []Operand{{Kind: OpReg, Reg: "R0"}}}); !ok || err == nil { + t.Fatalf("lowerSyscall(SWI invalid) = (%v, %v), want error", ok, err) + } + + c2, _ := newARMCtxForTest(t, FuncSig{Name: "example.callers", Ret: I32}, map[string]FuncSig{ + "example.ret32": {Name: "example.ret32", Ret: I32}, + "example.retVoid": {Name: "example.retVoid", Ret: Void}, + "example.retBad": {Name: "example.retBad", Ret: LLVMType("vector")}, + "example.tail64": {Name: "example.tail64", Ret: I64}, + }) + if err := c2.callSym(Operand{Kind: OpSym, Sym: "ret32(SB)"}); err != nil { + t.Fatalf("callSym(ret32) error = %v", err) + } + if err := c2.callSym(Operand{Kind: OpSym, Sym: "retVoid(SB)"}); err != nil { + t.Fatalf("callSym(retVoid) error = %v", err) + } + if err := c2.callSym(Operand{Kind: OpSym, Sym: "retBad(SB)"}); err == nil { + t.Fatalf("callSym(retBad) unexpectedly succeeded") + } + if err := c2.callSym(Operand{Kind: OpReg, Reg: "R0"}); err == nil { + t.Fatalf("callSym(non-sym) unexpectedly succeeded") + } + if err := c2.callSym(Operand{Kind: OpSym, Sym: "ret32"}); err == nil { + t.Fatalf("callSym(no sb) unexpectedly succeeded") + } + if err := c2.tailCallAndRet(Operand{Kind: OpReg, Reg: "R0"}); err == nil { + t.Fatalf("tailCallAndRet(non-sym) unexpectedly succeeded") + } + if err := c2.tailCallAndRet(Operand{Kind: OpSym, Sym: "ret32"}); err == nil { + t.Fatalf("tailCallAndRet(no sb) unexpectedly succeeded") + } + if err := c2.tailCallAndRet(Operand{Kind: OpSym, Sym: "missing(SB)"}); err == nil { + t.Fatalf("tailCallAndRet(missing sig) unexpectedly succeeded") + } + if err := c2.tailCallAndRet(Operand{Kind: OpSym, Sym: "tail64(SB)"}); err == nil { + t.Fatalf("tailCallAndRet(mismatch) unexpectedly succeeded") + } + + out := b.String() + for _, want := range []string{ + `call void @"example.tail"()`, + "ret void", + "load i32, ptr", + "store i32", + "call i64 @syscall(", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestTranslateARMLinearCoverageDeep(t *testing.T) { + ll := translateARMForTest(t, `TEXT ·linear(SB),NOSPLIT,$0-0 + MOVB $1, R1 + MOVBU $2, R2 + ADD R5, R4, R7 + SUB R1, R7 + AND R2, R7, R8 + ORR R8, R7, R9 + EOR R9, R8, R10 + RSB $1, R10, R11 + MOVW R11<<2, R0 + RET +`, map[string]FuncSig{ + "example.linear": { + Name: "example.linear", + Args: []LLVMType{I32, I32, I32}, + ArgRegs: []Reg{"R4", "R5", "R6"}, + Ret: I32, + }, + }) + for _, want := range []string{ + "trunc i64 1 to i8", + "zext i8", + "add i32 %arg0, %arg1", + "sub i32", + "and i32", + "or i32", + "xor i32", + "shl i32", + "ret i32", + } { + if !strings.Contains(ll, want) { + t.Fatalf("missing %q in output:\n%s", want, ll) + } + } +} diff --git a/arm_helper_edge_test.go b/arm_helper_edge_test.go new file mode 100644 index 0000000..7371c6f --- /dev/null +++ b/arm_helper_edge_test.go @@ -0,0 +1,290 @@ +package plan9asm + +import ( + "strings" + "testing" +) + +func newARMCtxWithFuncForTest(t *testing.T, fn Func, sig FuncSig, sigs map[string]FuncSig) (*armCtx, *strings.Builder) { + t.Helper() + if sig.Name == "" { + sig.Name = "example.f" + } + if sigs == nil { + sigs = map[string]FuncSig{} + } + var b strings.Builder + c := newARMCtx(&b, fn, sig, func(sym string) string { + sym = goStripABISuffix(sym) + sym = strings.ReplaceAll(sym, "∕", "/") + if strings.HasPrefix(sym, "runtime·") { + return strings.ReplaceAll(sym, "·", ".") + } + if strings.HasPrefix(sym, "·") { + return "example." + strings.TrimPrefix(sym, "·") + } + if !strings.Contains(sym, "·") && !strings.Contains(sym, ".") && !strings.Contains(sym, "/") { + return "example." + sym + } + return strings.ReplaceAll(sym, "·", ".") + }, sigs, false) + if err := c.emitEntryAllocasAndArgInit(); err != nil { + t.Fatalf("emitEntryAllocasAndArgInit() error = %v", err) + } + return c, &b +} + +func TestARMParserFlagAndHelperEdges(t *testing.T) { + if !isSymbolicImmPlaceholder("$(16 + callbackArgs__size)") { + t.Fatalf("isSymbolicImmPlaceholder() rejected symbolic immediate") + } + if isSymbolicImmPlaceholder("$123") { + t.Fatalf("isSymbolicImmPlaceholder() accepted numeric immediate") + } + if base, sop, amt, shiftReg, ok := parseRegShift("R1@>R2"); !ok || base != "R1" || sop != ShiftRotate || shiftReg != "R2" || amt != 0 { + t.Fatalf("parseRegShift(@>) = (%q, %q, %d, %q, %v)", base, sop, amt, shiftReg, ok) + } + if base, sop, amt, shiftReg, ok := parseRegShift("R3->1"); !ok || base != "R3" || sop != ShiftArith || shiftReg != "" || amt != 1 { + t.Fatalf("parseRegShift(->) = (%q, %q, %d, %q, %v)", base, sop, amt, shiftReg, ok) + } + if regs, ok := expandRegRange("R7-R4"); !ok || len(regs) != 4 || regs[0] != "R7" || regs[3] != "R4" { + t.Fatalf("expandRegRange(desc) = (%v, %v)", regs, ok) + } + if p, idx, ok := regRangeParts("F12"); !ok || p != "F" || idx != 12 { + t.Fatalf("regRangeParts(F12) = (%q, %d, %v)", p, idx, ok) + } + if _, _, ok := regRangeParts("SP"); ok { + t.Fatalf("regRangeParts(SP) unexpectedly succeeded") + } + if got := absInt(-9); got != 9 { + t.Fatalf("absInt(-9) = %d, want 9", got) + } + if op, err := parseOperand("$(16 + callbackArgs__size)"); err != nil || op.ImmRaw == "" { + t.Fatalf("parseOperand(symbolic imm) = (%#v, %v)", op, err) + } + if op, err := parseOperand("[R0-R2,R4]"); err != nil || op.Kind != OpRegList || len(op.RegList) != 4 { + t.Fatalf("parseOperand(reglist) = (%#v, %v)", op, err) + } + if op, err := parseOperand("R1<freg) = (%v, %v, %v)", ok, term, err) + } + if ok, _, err := c.lowerData("MOVD", "", false, Instr{Raw: "MOVD R0, R1", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}}}); !ok || err == nil { + t.Fatalf("lowerData(MOVD invalid) = (%v, %v), want error", ok, err) + } + + emitBr := func(string) {} + emitCondBr := func(string, string, string) error { return nil } + if term, err := c.lowerInstr(0, Instr{Op: "PCDATA", Raw: "PCDATA"}, emitBr, emitCondBr); term || err != nil { + t.Fatalf("lowerInstr(PCDATA) = (%v, %v)", term, err) + } + if term, err := c.lowerInstr(0, Instr{Op: "UNDEF", Raw: "UNDEF"}, emitBr, emitCondBr); !term || err != nil { + t.Fatalf("lowerInstr(UNDEF) = (%v, %v)", term, err) + } + if _, err := c.lowerInstr(0, Instr{Op: "UNKNOWN", Raw: "UNKNOWN"}, emitBr, emitCondBr); err == nil { + t.Fatalf("lowerInstr(UNKNOWN) unexpectedly succeeded") + } + + out := b.String() + for _, want := range []string{"store i64", `call void asm sideeffect "udf #0"`} { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestTranslateFuncLinearEdgeCases(t *testing.T) { + var b strings.Builder + err := translateFuncLinear(&b, ArchARM, Func{ + Sym: "linear_err", + Instrs: []Instr{ + {Op: "MOVW", Raw: "MOVW $(bad), R0", Args: []Operand{{Kind: OpImm, ImmRaw: "$(bad)"}, {Kind: OpReg, Reg: "R0"}}}, + {Op: OpRET, Raw: "RET"}, + }, + }, FuncSig{Name: "linear_err", Ret: I32}, false) + if err == nil { + t.Fatalf("translateFuncLinear(unresolved imm) unexpectedly succeeded") + } + + b.Reset() + err = translateFuncLinear(&b, ArchAMD64, Func{ + Sym: "linear_shift", + Instrs: []Instr{ + {Op: "MOVQ", Raw: "MOVQ AX<<1, AX", Args: []Operand{{Kind: OpRegShift, Reg: AX, ShiftOp: ShiftLeft, ShiftAmount: 1}, {Kind: OpReg, Reg: AX}}}, + {Op: OpRET, Raw: "RET"}, + }, + }, FuncSig{Name: "linear_shift", Ret: I64}, false) + if err == nil { + t.Fatalf("translateFuncLinear(non-arm shift) unexpectedly succeeded") + } + + b.Reset() + err = translateFuncLinear(&b, ArchARM, Func{ + Sym: "linear_bad_add", + Instrs: []Instr{ + {Op: "ADD", Raw: "ADD R0", Args: []Operand{{Kind: OpReg, Reg: "R0"}}}, + {Op: OpRET, Raw: "RET"}, + }, + }, FuncSig{Name: "linear_bad_add", Ret: I32}, false) + if err == nil { + t.Fatalf("translateFuncLinear(bad add) unexpectedly succeeded") + } +} + +func TestTranslateFuncLinearAdditionalPaths(t *testing.T) { + var b strings.Builder + err := translateFuncLinear(&b, ArchARM, Func{ + Sym: "linear_more", + Instrs: []Instr{ + {Op: "MOVW", Raw: "MOVW 8(R1)(R2*4), R3", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: "R1", Index: "R2", Scale: 4, Off: 8}}, {Kind: OpReg, Reg: "R3"}}}, + {Op: "MOVW", Raw: "MOVW $runtime·main(SB), R4", Args: []Operand{{Kind: OpSym, Sym: "$runtime·main(SB)"}, {Kind: OpReg, Reg: "R4"}}}, + {Op: "MOVW", Raw: "MOVW missing+99(FP), R5", Args: []Operand{{Kind: OpFP, FPName: "missing", FPOffset: 99}, {Kind: OpReg, Reg: "R5"}}}, + {Op: "MOVW", Raw: "MOVW R5, ret+8(FP)", Args: []Operand{{Kind: OpReg, Reg: "R5"}, {Kind: OpFP, FPName: "ret", FPOffset: 8}}}, + {Op: "MOVBU", Raw: "MOVBU R5, sink<>(SB)", Args: []Operand{{Kind: OpReg, Reg: "R5"}, {Kind: OpSym, Sym: "sink<>(SB)"}}}, + {Op: OpRET, Raw: "RET"}, + }, + }, FuncSig{ + Name: "linear_more", + Ret: I32, + Frame: FrameLayout{ + Results: []FrameSlot{{Offset: 8, Type: I32, Index: 0}}, + }, + }, false) + if err != nil { + t.Fatalf("translateFuncLinear(extra paths) error = %v", err) + } + out := b.String() + for _, want := range []string{ + "mul i64", + "inttoptr i64", + "load i64, ptr", + "trunc i64 0 to i32", + "ret i32", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestARMBranchAndSyscallExtraEdges(t *testing.T) { + c, _ := newARMCtxForTest(t, FuncSig{Name: "example.branch2", Ret: Void}, map[string]FuncSig{ + "example.argbad": {Name: "example.argbad", Args: []LLVMType{LLVMType("vec")}, Ret: Void}, + }) + c.blocks = []armBlock{{name: "entry"}} + + emitBr := func(string) {} + emitCondBr := func(string, string, string) error { return nil } + if ok, _, err := c.lowerBranch(0, "CALL", "EQ", Instr{Raw: "CALL.EQ R0", Args: []Operand{{Kind: OpReg, Reg: "R0"}}}, emitBr, emitCondBr); !ok || err == nil { + t.Fatalf("lowerBranch(CALL cond) = (%v, %v), want error", ok, err) + } + if ok, _, err := c.lowerBranch(0, "CALL", "", Instr{Raw: "CALL", Args: nil}, emitBr, emitCondBr); !ok || err == nil { + t.Fatalf("lowerBranch(CALL no args) = (%v, %v), want error", ok, err) + } + if ok, _, err := c.lowerBranch(0, "CALL", "", Instr{Raw: "CALL $1", Args: []Operand{{Kind: OpImm, Imm: 1}}}, emitBr, emitCondBr); !ok || err == nil { + t.Fatalf("lowerBranch(CALL bad target) = (%v, %v), want error", ok, err) + } + if ok, _, err := c.lowerBranch(0, "B", "EQ", Instr{Raw: "B.EQ done", Args: []Operand{{Kind: OpIdent, Ident: "done"}}}, emitBr, emitCondBr); !ok || err == nil { + t.Fatalf("lowerBranch(B.EQ no fallthrough) = (%v, %v), want error", ok, err) + } + if ok, _, err := c.lowerBranch(0, "BCC", "", Instr{Raw: "BCC done", Args: []Operand{{Kind: OpIdent, Ident: "done"}}}, emitBr, emitCondBr); !ok || err == nil { + t.Fatalf("lowerBranch(BCC no fallthrough) = (%v, %v), want error", ok, err) + } + if err := c.callSym(Operand{Kind: OpSym, Sym: "argbad(SB)"}); err == nil { + t.Fatalf("callSym(argbad) unexpectedly succeeded") + } + + c2, b2 := newARMCtxForTest(t, FuncSig{Name: "example.sys", Ret: I32}, nil) + delete(c2.regSlot, Reg("R4")) + delete(c2.regSlot, Reg("R5")) + if ok, _, err := c2.lowerSyscall("SWI", Instr{Raw: "SWI $0", Args: []Operand{{Kind: OpImm, Imm: 0}}}); !ok || err != nil { + t.Fatalf("lowerSyscall(SWI $0) = (%v, %v)", ok, err) + } + out := b2.String() + for _, want := range []string{"call i64 @syscall(", "zext i32 0 to i64", "store i32", "store i32 0, ptr %reg_R1"} { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} diff --git a/arm_more_test.go b/arm_more_test.go new file mode 100644 index 0000000..b42e409 --- /dev/null +++ b/arm_more_test.go @@ -0,0 +1,392 @@ +package plan9asm + +import ( + "strings" + "testing" +) + +func newARMCtxForTest(t *testing.T, sig FuncSig, sigs map[string]FuncSig) (*armCtx, *strings.Builder) { + t.Helper() + if sig.Name == "" { + sig.Name = "example.f" + } + if sigs == nil { + sigs = map[string]FuncSig{} + } + var b strings.Builder + c := newARMCtx(&b, Func{}, sig, func(sym string) string { + sym = goStripABISuffix(sym) + sym = strings.ReplaceAll(sym, "∕", "/") + if strings.HasPrefix(sym, "runtime·") { + return strings.ReplaceAll(sym, "·", ".") + } + if strings.HasPrefix(sym, "·") { + return "example." + strings.TrimPrefix(sym, "·") + } + if !strings.Contains(sym, "·") && !strings.Contains(sym, ".") && !strings.Contains(sym, "/") { + return "example." + sym + } + return strings.ReplaceAll(sym, "·", ".") + }, sigs, false) + if err := c.emitEntryAllocasAndArgInit(); err != nil { + t.Fatalf("emitEntryAllocasAndArgInit() error = %v", err) + } + return c, &b +} + +func translateARMForTest(t *testing.T, src string, sigs map[string]FuncSig) string { + t.Helper() + file, err := Parse(ArchARM, src) + if err != nil { + t.Fatalf("Parse() error = %v", err) + } + ll, err := Translate(file, Options{ + TargetTriple: "armv7-unknown-linux-gnueabihf", + Goarch: "arm", + ResolveSym: func(sym string) string { + sym = goStripABISuffix(sym) + sym = strings.ReplaceAll(sym, "∕", "/") + if strings.HasPrefix(sym, "runtime·") { + return strings.ReplaceAll(sym, "·", ".") + } + if strings.HasPrefix(sym, "·") { + return "example." + strings.TrimPrefix(sym, "·") + } + if !strings.Contains(sym, "·") && !strings.Contains(sym, ".") && !strings.Contains(sym, "/") { + return "example." + sym + } + return strings.ReplaceAll(sym, "·", ".") + }, + Sigs: sigs, + }) + if err != nil { + t.Fatalf("Translate() error = %v", err) + } + return ll +} + +func TestTranslateARMExtendedArithmetic(t *testing.T) { + ll := translateARMForTest(t, `TEXT ·ops(SB),NOSPLIT,$0-0 + CMP R0, R0 + MVN.EQ.S R0, R1 + ADC.S R1, R2, R3 + SBC.S R1, R2, R4 + MUL R0, R1, R5 + MULLU.EQ R0, R1, (R6, R7) + MULA R0, R1, R2, R8 + MULAL.EQ R0, R1, (R9, R10) + MULAWT R0, R1, R2, R11 + DIVUHW R1, R2, R12 + CLZ R12, R0 + MRC $15, $0, R1, $1, $0, $0 + RET +`, map[string]FuncSig{ + "example.ops": {Name: "example.ops", Ret: Void}, + }) + for _, want := range []string{ + "mul i32", + "mul i64", + "select i1", + "call i32 @llvm.ctlz.i32", + `asm sideeffect "mrc p15, 0, $0, 1, 0, 0"`, + } { + if !strings.Contains(ll, want) { + t.Fatalf("missing %q in output:\n%s", want, ll) + } + } +} + +func TestARMHelperCoverage(t *testing.T) { + t.Run("DecodeAndValidate", func(t *testing.T) { + cases := []struct { + raw string + wantMode string + wantWrite bool + }{ + {raw: "MOVM.IA.W", wantMode: "IA", wantWrite: true}, + {raw: "MOVM.DBW", wantMode: "DB", wantWrite: true}, + {raw: "MOVM.WP", wantMode: "DB", wantWrite: true}, + {raw: "MOVM.IB", wantMode: "IB", wantWrite: false}, + {raw: "MOVM.DA", wantMode: "DA", wantWrite: false}, + {raw: "MOVM", wantMode: "IA", wantWrite: false}, + } + for _, tc := range cases { + mode, writeback := armDecodeMOVM(tc.raw) + if mode != tc.wantMode || writeback != tc.wantWrite { + t.Fatalf("armDecodeMOVM(%q) = (%q, %v), want (%q, %v)", tc.raw, mode, writeback, tc.wantMode, tc.wantWrite) + } + } + if !armRegListAllGPR([]Reg{"R0", "R7", "R15"}) { + t.Fatalf("armRegListAllGPR() rejected general-purpose list") + } + if armRegListAllGPR([]Reg{"R0", "F0"}) { + t.Fatalf("armRegListAllGPR() accepted mixed register list") + } + + if got, ok := armBranchTarget(Operand{Kind: OpIdent, Ident: "loop"}); !ok || got != "loop" { + t.Fatalf("armBranchTarget(ident) = (%q, %v)", got, ok) + } + if got, ok := armBranchTarget(Operand{Kind: OpSym, Sym: "helper<>(SB)"}); !ok || got != "helper" { + t.Fatalf("armBranchTarget(sym) = (%q, %v)", got, ok) + } + if _, ok := armBranchTarget(Operand{Kind: OpReg, Reg: "R0"}); ok { + t.Fatalf("armBranchTarget(reg) unexpectedly succeeded") + } + + base, cond, postInc, setFlags := armDecodeOp("ADD.EQ.S.P") + if base != "ADD" || cond != "EQ" || !postInc || !setFlags { + t.Fatalf("armDecodeOp() = (%q, %q, %v, %v)", base, cond, postInc, setFlags) + } + + if got, err := armNextReg("R7"); err != nil || got != "R8" { + t.Fatalf("armNextReg(R7) = (%q, %v), want (R8, nil)", got, err) + } + if _, err := armNextReg("R15"); err == nil { + t.Fatalf("armNextReg(R15) unexpectedly succeeded") + } + if _, err := armNextReg("F0"); err == nil { + t.Fatalf("armNextReg(F0) unexpectedly succeeded") + } + + if got, ok := parseReg("W3"); !ok || got != "R3" { + t.Fatalf("parseReg(W3) = (%q, %v)", got, ok) + } + if got, ok := parseReg("LR"); !ok || got != "R30" { + t.Fatalf("parseReg(LR) = (%q, %v)", got, ok) + } + if got, ok := parseReg("G"); !ok || got != "R28" { + t.Fatalf("parseReg(G) = (%q, %v)", got, ok) + } + if got := (Operand{Kind: OpImm, ImmRaw: "$(a+b)"}).String(); got != "$(a+b)" { + t.Fatalf("Operand.String() = %q, want unresolved symbolic immediate", got) + } + + fn := Func{Instrs: []Instr{{Args: []Operand{{Kind: OpImm, ImmRaw: "$(sym)"}}}}} + if err := validateResolvedImmediates(ArchAMD64, fn); err != nil { + t.Fatalf("validateResolvedImmediates(amd64) error = %v", err) + } + if err := validateResolvedImmediates(ArchARM, fn); err == nil { + t.Fatalf("validateResolvedImmediates(arm) unexpectedly succeeded") + } + }) + + t.Run("CtxRetAndStore", func(t *testing.T) { + c, b := newARMCtxForTest(t, FuncSig{ + Name: "example.f", + Ret: I16, + Frame: FrameLayout{ + Results: []FrameSlot{{Offset: 8, Type: I16, Index: 0}}, + }, + }, nil) + c.flagsWritten = true + + if err := c.selectRegWrite("R1", "EQ", "7"); err != nil { + t.Fatalf("selectRegWrite(EQ) error = %v", err) + } + if err := c.selectRegWrite("R2", "AL", "9"); err != nil { + t.Fatalf("selectRegWrite(AL) error = %v", err) + } + if err := c.storeARMValue(Operand{Kind: OpFP, FPOffset: 8}, "42", 32, "", false, "MOVW R0, ret+8(FP)"); err != nil { + t.Fatalf("storeARMValue(FP) error = %v", err) + } + if err := c.storeARMValue(Operand{Kind: OpMem, Mem: MemRef{Base: "R3", Off: 4}}, "55", 8, "", false, "MOVB R0, 4(R3)"); err != nil { + t.Fatalf("storeARMValue(mem) error = %v", err) + } + if err := c.storeARMValue(Operand{Kind: OpSym, Sym: "ignore<>(SB)"}, "1", 32, "", false, "MOVW R0, ignore<>(SB)"); err != nil { + t.Fatalf("storeARMValue(sym) error = %v", err) + } + if err := c.storeARMValue(Operand{Kind: OpMem, Mem: MemRef{Base: "R3"}}, "1", 32, "EQ", false, "MOVW.EQ R0, (R3)"); err == nil { + t.Fatalf("storeARMValue(cond mem) unexpectedly succeeded") + } + if err := c.storeARMValue(Operand{Kind: OpIdent, Ident: "bad"}, "1", 32, "", false, "bad"); err == nil { + t.Fatalf("storeARMValue(invalid) unexpectedly succeeded") + } + if got, err := c.ptrFromSB("$runtime·main+8(SB)"); err != nil || got == "" { + t.Fatalf("ptrFromSB() = (%q, %v)", got, err) + } + if got, err := c.loadRetSlotFallback(FrameSlot{Type: Ptr}); err != nil || got == "" { + t.Fatalf("loadRetSlotFallback(ptr) = (%q, %v)", got, err) + } + if got, err := c.loadRetSlotFallback(FrameSlot{Type: I64}); err != nil || got == "" { + t.Fatalf("loadRetSlotFallback(i64) = (%q, %v)", got, err) + } + if _, err := c.loadRetSlotFallback(FrameSlot{Type: LLVMType("v4i32")}); err == nil { + t.Fatalf("loadRetSlotFallback(invalid) unexpectedly succeeded") + } + c.lowerRetZero() + + out := b.String() + for _, want := range []string{ + "select i1", + "trunc i32 42 to i16", + "store i8", + `getelementptr i8, ptr @"runtime.main", i32 8`, + "inttoptr i32", + "zext i32", + "ret i16 0", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } + }) + + t.Run("BranchCallAndCastPaths", func(t *testing.T) { + sigs := map[string]FuncSig{ + "example.retBool": {Name: "example.retBool", Ret: I1}, + "example.retPtr": {Name: "example.retPtr", Ret: Ptr}, + "example.ret64": {Name: "example.ret64", Ret: I64}, + "example.sink": { + Name: "example.sink", + Args: []LLVMType{I1, I8, I16, I32, Ptr, I64}, + ArgRegs: []Reg{"R0", "R1", "R2", "R3", "R4", "R5"}, + Ret: Void, + }, + } + c, b := newARMCtxForTest(t, FuncSig{Name: "example.caller", Ret: Void}, sigs) + + if err := c.callSym(Operand{Kind: OpSym, Sym: "retBool(SB)"}); err != nil { + t.Fatalf("callSym(retBool) error = %v", err) + } + if err := c.callSym(Operand{Kind: OpSym, Sym: "retPtr(SB)"}); err != nil { + t.Fatalf("callSym(retPtr) error = %v", err) + } + if err := c.callSym(Operand{Kind: OpSym, Sym: "ret64(SB)"}); err != nil { + t.Fatalf("callSym(ret64) error = %v", err) + } + if err := c.callSym(Operand{Kind: OpSym, Sym: "runtime·entersyscall(SB)"}); err != nil { + t.Fatalf("callSym(runtime·entersyscall) error = %v", err) + } + if err := c.tailCallAndRet(Operand{Kind: OpSym, Sym: "sink(SB)"}); err != nil { + t.Fatalf("tailCallAndRet() error = %v", err) + } + + emitBr := func(string) {} + emitCondBr := func(string, string, string) error { return nil } + if ok, term, err := c.lowerBranch(0, "CALL", "", Instr{ + Raw: "CALL R1", + Args: []Operand{{Kind: OpReg, Reg: "R1"}}, + }, emitBr, emitCondBr); !ok || term || err != nil { + t.Fatalf("lowerBranch(CALL reg) = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := c.lowerBranch(0, "BL", "", Instr{ + Raw: "BL (R1)", + Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: "R1"}}}, + }, emitBr, emitCondBr); !ok || term || err != nil { + t.Fatalf("lowerBranch(BL mem) = (%v, %v, %v)", ok, term, err) + } + + out := b.String() + for _, want := range []string{ + "zext i1", + "ptrtoint ptr", + "trunc i64", + `call void @"example.sink"(`, + "ret void", + "asm sideeffect \"blx $0\"", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } + }) + + t.Run("ARMValueAsI32AndLowerRetZeroVariants", func(t *testing.T) { + tests := []struct { + ret LLVMType + want string + }{ + {ret: Void, want: "ret void"}, + {ret: I1, want: "ret i1 false"}, + {ret: Ptr, want: "ret ptr null"}, + {ret: I32, want: "ret i32 0"}, + } + for _, tc := range tests { + c, b := newARMCtxForTest(t, FuncSig{Name: "example.retzero", Ret: tc.ret}, nil) + c.lowerRetZero() + if !strings.Contains(b.String(), tc.want) { + t.Fatalf("lowerRetZero(%s) missing %q in:\n%s", tc.ret, tc.want, b.String()) + } + } + + c, b := newARMCtxForTest(t, FuncSig{Name: "example.cast", Ret: Void}, nil) + for _, ty := range []LLVMType{Ptr, I1, I8, I16, I32, I64} { + if got, ok, err := armValueAsI32(c, ty, "%v"); err != nil || !ok || got == "" { + t.Fatalf("armValueAsI32(%s) = (%q, %v, %v)", ty, got, ok, err) + } + } + if _, ok, err := armValueAsI32(c, LLVMType("v2i32"), "%v"); err != nil || ok { + t.Fatalf("armValueAsI32(unsupported) = (_, %v, %v), want (_, false, nil)", ok, err) + } + out := b.String() + for _, want := range []string{"ptrtoint ptr %v to i32", "zext i1 %v to i32", "trunc i64 %v to i32"} { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } + }) +} + +func TestTranslateARMBranchesCallsAndShiftForms(t *testing.T) { + ll := translateARMForTest(t, `TEXT ·caller(SB),NOSPLIT,$0-0 + CMP R0, R0 + BEQ ok + MOVW $1, R0 +ok: + MOVW R1<<2, R2 + MOVW R2>>1, R3 + MOVW R3->1, R4 + MOVW R4@>8, R5 + MOVW R5< Date: Sat, 14 Mar 2026 08:53:35 +0800 Subject: [PATCH 2/6] Raise ARM patch coverage above 85 percent --- arm_helper_edge_test.go | 243 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) diff --git a/arm_helper_edge_test.go b/arm_helper_edge_test.go index 7371c6f..ea67474 100644 --- a/arm_helper_edge_test.go +++ b/arm_helper_edge_test.go @@ -1,10 +1,13 @@ package plan9asm import ( + "errors" "strings" "testing" ) +var errTestSentinel = errors.New("sentinel") + func newARMCtxWithFuncForTest(t *testing.T, fn Func, sig FuncSig, sigs map[string]FuncSig) (*armCtx, *strings.Builder) { t.Helper() if sig.Name == "" { @@ -288,3 +291,243 @@ func TestARMBranchAndSyscallExtraEdges(t *testing.T) { } } } + +func TestARMBranchExactErrorCoverage(t *testing.T) { + mk := func(ret LLVMType) *armCtx { + c, _ := newARMCtxForTest(t, FuncSig{Name: "example.branch_exact", Ret: ret}, map[string]FuncSig{ + "example.voidsink": {Name: "example.voidsink", Ret: Void}, + "example.badarg": {Name: "example.badarg", Args: []LLVMType{LLVMType("vec")}, Ret: Void}, + }) + c.blocks = []armBlock{{name: "entry"}, {name: "fall"}} + return c + } + emitBr := func(string) {} + errCond := func(string, string, string) error { return errTestSentinel } + + c := mk(Void) + delete(c.regSlot, Reg("R0")) + if _, _, err := c.lowerBranch(0, "CALL", "", Instr{Raw: "CALL R0", Args: []Operand{{Kind: OpReg, Reg: "R0"}}}, emitBr, func(string, string, string) error { return nil }); err == nil { + t.Fatalf("lowerBranch(CALL reg load error) unexpectedly succeeded") + } + + c = mk(Void) + delete(c.regSlot, Reg("R1")) + if _, _, err := c.lowerBranch(0, "CALL", "", Instr{Raw: "CALL (R1)", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: "R1"}}}}, emitBr, func(string, string, string) error { return nil }); err == nil { + t.Fatalf("lowerBranch(CALL mem addr error) unexpectedly succeeded") + } + + c = mk(Void) + if _, _, err := c.lowerBranch(0, "B", "", Instr{Raw: "B", Args: nil}, emitBr, func(string, string, string) error { return nil }); err == nil { + t.Fatalf("lowerBranch(B missing arg) unexpectedly succeeded") + } + if _, _, err := c.lowerBranch(0, "B", "EQ", Instr{Raw: "B.EQ R0", Args: []Operand{{Kind: OpReg, Reg: "R0"}}}, emitBr, func(string, string, string) error { return nil }); err == nil { + t.Fatalf("lowerBranch(B.EQ bad target) unexpectedly succeeded") + } + if _, _, err := c.lowerBranch(0, "B", "EQ", Instr{Raw: "B.EQ done", Args: []Operand{{Kind: OpIdent, Ident: "done"}}}, emitBr, errCond); err == nil { + t.Fatalf("lowerBranch(B.EQ emit error) unexpectedly succeeded") + } + if _, _, err := c.lowerBranch(0, "B", "", Instr{Raw: "B $1", Args: []Operand{{Kind: OpImm, Imm: 1}}}, emitBr, func(string, string, string) error { return nil }); err == nil { + t.Fatalf("lowerBranch(B bad target) unexpectedly succeeded") + } + if _, _, err := c.lowerBranch(0, "BEQ", "", Instr{Raw: "BEQ", Args: nil}, emitBr, func(string, string, string) error { return nil }); err == nil { + t.Fatalf("lowerBranch(BEQ missing arg) unexpectedly succeeded") + } + if _, _, err := c.lowerBranch(0, "BEQ", "", Instr{Raw: "BEQ R0", Args: []Operand{{Kind: OpReg, Reg: "R0"}}}, emitBr, func(string, string, string) error { return nil }); err == nil { + t.Fatalf("lowerBranch(BEQ bad target) unexpectedly succeeded") + } + + var got string + condCapture := func(cond, _, _ string) error { got = cond; return errTestSentinel } + if _, _, err := c.lowerBranch(0, "BCS", "", Instr{Raw: "BCS done", Args: []Operand{{Kind: OpIdent, Ident: "done"}}}, emitBr, condCapture); err == nil || got != "HS" { + t.Fatalf("lowerBranch(BCS) = (%q, %v)", got, err) + } + got = "" + if _, _, err := c.lowerBranch(0, "BCC", "", Instr{Raw: "BCC done", Args: []Operand{{Kind: OpIdent, Ident: "done"}}}, emitBr, condCapture); err == nil || got != "LO" { + t.Fatalf("lowerBranch(BCC) = (%q, %v)", got, err) + } + + c = mk(I32) + if err := c.tailCallAndRet(Operand{Kind: OpSym, Sym: "voidsink(SB)"}); err != nil { + t.Fatalf("tailCallAndRet(void sink) error = %v", err) + } + if err := c.callSym(Operand{Kind: OpSym, Sym: "missing(SB)"}); err != nil { + t.Fatalf("callSym(missing sig) error = %v", err) + } + if err := c.callSym(Operand{Kind: OpSym, Sym: "badarg(SB)"}); err == nil { + t.Fatalf("callSym(bad arg type) unexpectedly succeeded") + } +} + +func TestTranslateFuncLinearMissedBranches(t *testing.T) { + var b strings.Builder + + file, err := Parse(ArchARM, `TEXT ·cfgbad(SB),NOSPLIT,$0-0 + CMP R0, R0 + BAD + RET +`) + if err != nil { + t.Fatalf("Parse() error = %v", err) + } + _, err = Translate(file, Options{ + TargetTriple: "armv7-unknown-linux-gnueabihf", + Goarch: "arm", + ResolveSym: func(sym string) string { return "example." + strings.TrimPrefix(sym, "·") }, + Sigs: map[string]FuncSig{ + "example.cfgbad": {Name: "example.cfgbad", Ret: Void}, + }, + }) + if err == nil { + t.Fatalf("Translate(cfgbad) unexpectedly succeeded") + } + + err = translateFuncLinear(&b, ArchARM, Func{ + Sym: "shift_ok", + Instrs: []Instr{ + {Op: "MOVW", Raw: "MOVW R1>>1, R0", Args: []Operand{{Kind: OpRegShift, Reg: "R1", ShiftOp: ShiftRight, ShiftAmount: 1}, {Kind: OpReg, Reg: "R0"}}}, + {Op: "MOVW", Raw: "MOVW R0->1, R0", Args: []Operand{{Kind: OpRegShift, Reg: "R0", ShiftOp: ShiftArith, ShiftAmount: 1}, {Kind: OpReg, Reg: "R0"}}}, + {Op: "MOVW", Raw: "MOVW R0@>1, R0", Args: []Operand{{Kind: OpRegShift, Reg: "R0", ShiftOp: ShiftRotate, ShiftAmount: 1}, {Kind: OpReg, Reg: "R0"}}}, + {Op: OpRET, Raw: "RET"}, + }, + }, FuncSig{Name: "shift_ok", Ret: I32}, false) + if err != nil { + t.Fatalf("translateFuncLinear(shift ok) error = %v", err) + } + + b.Reset() + err = translateFuncLinear(&b, ArchARM, Func{ + Sym: "shift_bad_base", + Instrs: []Instr{ + {Op: "MOVW", Raw: "MOVW R0<<1, R0", Args: []Operand{{Kind: OpRegShift, Reg: "R0", ShiftOp: ShiftLeft, ShiftAmount: 1}, {Kind: OpReg, Reg: "R0"}}}, + }, + }, FuncSig{Name: "shift_bad_base", Args: []LLVMType{LLVMType("{ i32, i32 }")}, ArgRegs: []Reg{"R0"}, Ret: I32}, false) + if err == nil { + t.Fatalf("translateFuncLinear(shift bad base) unexpectedly succeeded") + } + + b.Reset() + err = translateFuncLinear(&b, ArchARM, Func{ + Sym: "shift_bad_reg", + Instrs: []Instr{ + {Op: "MOVW", Raw: "MOVW R0<(SB)", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpSym, Sym: "sink<>(SB)"}}}, + {Op: "MOVBU", Raw: "MOVBU R0, sink<>(SB)", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpSym, Sym: "sink<>(SB)"}}}, + {Op: "ADD", Raw: "ADD R0, ret+8(FP)", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpFP, FPName: "ret", FPOffset: 8}}}, + } { + b.Reset() + err := translateFuncLinear(&b, ArchARM, Func{Sym: "skip2", Instrs: []Instr{tc, {Op: OpRET, Raw: "RET"}}}, FuncSig{ + Name: "skip2", + Ret: I32, + Frame: FrameLayout{ + Results: []FrameSlot{{Offset: 8, Type: I32, Index: 0}}, + }, + }, false) + if err == nil && tc.Op == "ADD" { + t.Fatalf("translateFuncLinear(bad add dst) unexpectedly succeeded") + } + if err != nil && tc.Op != "ADD" { + t.Fatalf("translateFuncLinear(skip arm %s) error = %v", tc.Op, err) + } + } +} + +func TestARMArithErrorCoverage(t *testing.T) { + c, _ := newARMCtxForTest(t, FuncSig{Name: "example.arith_err", Ret: Void}, nil) + if err := c.lowerARMALU("ADD", "", false, Instr{Raw: "ADD R0, $1", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpImm, Imm: 1}}}); err == nil { + t.Fatalf("lowerARMALU(dst not reg 2-arg) unexpectedly succeeded") + } + if err := c.lowerARMALU("ADD", "", false, Instr{Raw: "ADD R0, R1, $2", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}, {Kind: OpImm, Imm: 2}}}); err == nil { + t.Fatalf("lowerARMALU(dst not reg 3-arg) unexpectedly succeeded") + } + if err := c.lowerARMALU("ADD", "", false, Instr{Raw: "ADD bad+8(FP), R0", Args: []Operand{{Kind: OpFPAddr, FPName: "bad", FPOffset: 8}, {Kind: OpReg, Reg: "R0"}}}); err == nil { + t.Fatalf("lowerARMALU(src eval err) unexpectedly succeeded") + } + delete(c.regSlot, Reg("R0")) + if err := c.lowerARMALU("ADD", "", false, Instr{Raw: "ADD $1, R0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: "R0"}}}); err == nil { + t.Fatalf("lowerARMALU(loadReg err) unexpectedly succeeded") + } + + c, _ = newARMCtxForTest(t, FuncSig{Name: "example.arith_err2", Ret: Void}, nil) + if err := c.lowerARMALU("RSB", "", true, Instr{Raw: "RSB.S $1, R0, R1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}}}); err != nil { + t.Fatalf("lowerARMALU(RSB.S) error = %v", err) + } + if err := c.lowerARMALU("ORR", "", true, Instr{Raw: "ORR.S R0, R1, R2", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R2"}}}); err != nil { + t.Fatalf("lowerARMALU(ORR.S) error = %v", err) + } + if err := c.lowerARMCompare("CMP", Instr{Raw: "CMP bad+8(FP), R0", Args: []Operand{{Kind: OpFPAddr, FPName: "bad", FPOffset: 8}, {Kind: OpReg, Reg: "R0"}}}); err == nil { + t.Fatalf("lowerARMCompare(src err) unexpectedly succeeded") + } + if err := c.lowerARMCompare("CMP", Instr{Raw: "CMP R0, bad+8(FP)", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpFPAddr, FPName: "bad", FPOffset: 8}}}); err == nil { + t.Fatalf("lowerARMCompare(lhs err) unexpectedly succeeded") + } + if err := c.lowerARMMVN("", false, Instr{Raw: "MVN", Args: nil}); err == nil { + t.Fatalf("lowerARMMVN(len err) unexpectedly succeeded") + } + c3, _ := newARMCtxForTest(t, FuncSig{Name: "example.arith_err3", Ret: Void}, nil) + if err := c3.lowerARMMVN("EQ", false, Instr{Raw: "MVN.EQ R0, R1", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}}}); err == nil { + t.Fatalf("lowerARMMVN(select err) unexpectedly succeeded") + } + if err := c.lowerARMADCSBC("ADC", "", false, Instr{Raw: "ADC", Args: nil}); err == nil { + t.Fatalf("lowerARMADCSBC(len err) unexpectedly succeeded") + } + if err := c.lowerARMADCSBC("ADC", "", false, Instr{Raw: "ADC bad+8(FP), R0", Args: []Operand{{Kind: OpFPAddr, FPName: "bad", FPOffset: 8}, {Kind: OpReg, Reg: "R0"}}}); err == nil { + t.Fatalf("lowerARMADCSBC(src err) unexpectedly succeeded") + } + if err := c.lowerARMADCSBC("ADC", "", false, Instr{Raw: "ADC R0, bad+8(FP), R1", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpFPAddr, FPName: "bad", FPOffset: 8}, {Kind: OpReg, Reg: "R1"}}}); err == nil { + t.Fatalf("lowerARMADCSBC(lhs err) unexpectedly succeeded") + } + + c4, _ := newARMCtxForTest(t, FuncSig{Name: "example.pair_err", Ret: Void}, nil) + delete(c4.regSlot, Reg("R1")) + if err := c4.selectRegPairWrite("R1", "R2", "", "1", "2"); err == nil { + t.Fatalf("selectRegPairWrite(store hi err) unexpectedly succeeded") + } + c5, _ := newARMCtxForTest(t, FuncSig{Name: "example.pair_err2", Ret: Void}, nil) + if err := c5.selectRegPairWrite("R1", "R2", "EQ", "1", "2"); err == nil { + t.Fatalf("selectRegPairWrite(cond err) unexpectedly succeeded") + } + c6, _ := newARMCtxForTest(t, FuncSig{Name: "example.pair_err3", Ret: Void}, nil) + c6.flagsWritten = true + delete(c6.regSlot, Reg("R1")) + if err := c6.selectRegPairWrite("R1", "R2", "EQ", "1", "2"); err == nil { + t.Fatalf("selectRegPairWrite(load hi err) unexpectedly succeeded") + } + c7, _ := newARMCtxForTest(t, FuncSig{Name: "example.pair_err4", Ret: Void}, nil) + c7.flagsWritten = true + delete(c7.regSlot, Reg("R2")) + if err := c7.selectRegPairWrite("R1", "R2", "EQ", "1", "2"); err == nil { + t.Fatalf("selectRegPairWrite(load lo err) unexpectedly succeeded") + } +} From fadda508e7051239f93e136e4626144b2bb6b0c4 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sat, 14 Mar 2026 09:54:27 +0800 Subject: [PATCH 3/6] Raise coverage with scan and amd64 helper tests --- amd64_helper_edge_test.go | 664 ++++++++++++++++++++++++++++++++++ cmd/plan9asmscan/main_test.go | 156 ++++++++ 2 files changed, 820 insertions(+) create mode 100644 amd64_helper_edge_test.go diff --git a/amd64_helper_edge_test.go b/amd64_helper_edge_test.go new file mode 100644 index 0000000..b0e2392 --- /dev/null +++ b/amd64_helper_edge_test.go @@ -0,0 +1,664 @@ +package plan9asm + +import ( + "strings" + "testing" +) + +func newAMD64CtxWithFuncForTest(t *testing.T, fn Func, sig FuncSig, sigs map[string]FuncSig) (*amd64Ctx, *strings.Builder) { + t.Helper() + if sig.Name == "" { + sig.Name = "example.f" + } + if sigs == nil { + sigs = map[string]FuncSig{} + } + var b strings.Builder + c := newAMD64Ctx(&b, fn, sig, func(sym string) string { + sym = goStripABISuffix(sym) + sym = strings.ReplaceAll(sym, "∕", "/") + if strings.HasPrefix(sym, "runtime·") { + return strings.ReplaceAll(sym, "·", ".") + } + if strings.HasPrefix(sym, "·") { + return "example." + strings.TrimPrefix(sym, "·") + } + if !strings.Contains(sym, "·") && !strings.Contains(sym, ".") && !strings.Contains(sym, "/") { + return "example." + sym + } + return strings.ReplaceAll(sym, "·", ".") + }, sigs, false) + if err := c.emitEntryAllocas(); err != nil { + t.Fatalf("emitEntryAllocas() error = %v", err) + } + return c, &b +} + +func TestAMD64CtxHelperEdges(t *testing.T) { + fn := Func{ + Instrs: []Instr{ + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: BX}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("Y2")}, {Kind: OpReg, Reg: Reg("Y3")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("Z4")}, {Kind: OpReg, Reg: Reg("Z5")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("K1")}, {Kind: OpReg, Reg: Reg("K2")}}}, + }, + } + sig := FuncSig{ + Name: "example.ctx", + Args: []LLVMType{Ptr, I1, I8, I16, I32, I64, LLVMType("double"), LLVMType("float")}, + Ret: LLVMType("{i32, ptr, double, float, i16}"), + Frame: FrameLayout{ + Params: []FrameSlot{ + {Offset: 0, Type: Ptr, Index: 0, Field: -1}, + {Offset: 8, Type: I1, Index: 1, Field: -1}, + {Offset: 16, Type: I8, Index: 2, Field: -1}, + {Offset: 24, Type: I16, Index: 3, Field: -1}, + {Offset: 32, Type: I32, Index: 4, Field: -1}, + {Offset: 40, Type: I64, Index: 5, Field: -1}, + {Offset: 48, Type: LLVMType("double"), Index: 6, Field: -1}, + {Offset: 56, Type: LLVMType("float"), Index: 7, Field: -1}, + }, + Results: []FrameSlot{ + {Offset: 80, Type: I32, Index: 0, Field: -1}, + {Offset: 88, Type: Ptr, Index: 1, Field: -1}, + {Offset: 96, Type: LLVMType("double"), Index: 2, Field: -1}, + {Offset: 104, Type: LLVMType("float"), Index: 3, Field: -1}, + {Offset: 112, Type: I16, Index: 4, Field: -1}, + }, + }, + } + c, b := newAMD64CtxWithFuncForTest(t, fn, sig, nil) + + for _, ty := range []LLVMType{Ptr, I1, I8, I16, I32, I64} { + if got, ok, err := amd64ValueAsI64(c, ty, "%v"); err != nil || !ok || got == "" { + t.Fatalf("amd64ValueAsI64(%s) = (%q, %v, %v)", ty, got, ok, err) + } + } + if got, ok, err := amd64ValueAsI64(c, LLVMType("v2i64"), "%v"); err != nil || ok || got != "" { + t.Fatalf("amd64ValueAsI64(unsupported) = (%q, %v, %v)", got, ok, err) + } + + c.pushI64("7") + if got := c.popI64(); got == "" { + t.Fatalf("popI64() returned empty value") + } + + if got, err := c.loadX("X0"); err != nil || got == "" { + t.Fatalf("loadX(X0) = (%q, %v)", got, err) + } + if err := c.storeX("X1", "<16 x i8> zeroinitializer"); err != nil { + t.Fatalf("storeX(X1) error = %v", err) + } + if _, err := c.loadX("AX"); err == nil { + t.Fatalf("loadX(AX) unexpectedly succeeded") + } + if err := c.storeX("AX", "<16 x i8> zeroinitializer"); err == nil { + t.Fatalf("storeX(AX) unexpectedly succeeded") + } + + if got, err := c.loadY("Y2"); err != nil || got == "" { + t.Fatalf("loadY(Y2) = (%q, %v)", got, err) + } + if err := c.storeY("Y3", "<32 x i8> zeroinitializer"); err != nil { + t.Fatalf("storeY(Y3) error = %v", err) + } + if _, err := c.loadY("AX"); err == nil { + t.Fatalf("loadY(AX) unexpectedly succeeded") + } + if err := c.storeY("AX", "<32 x i8> zeroinitializer"); err == nil { + t.Fatalf("storeY(AX) unexpectedly succeeded") + } + + if got, err := c.loadZ("Z4"); err != nil || got == "" { + t.Fatalf("loadZ(Z4) = (%q, %v)", got, err) + } + if err := c.storeZ("Z5", "<64 x i8> zeroinitializer"); err != nil { + t.Fatalf("storeZ(Z5) error = %v", err) + } + if _, err := c.loadZ("AX"); err == nil { + t.Fatalf("loadZ(AX) unexpectedly succeeded") + } + if err := c.storeZ("AX", "<64 x i8> zeroinitializer"); err == nil { + t.Fatalf("storeZ(AX) unexpectedly succeeded") + } + + if got, err := c.loadK("K1"); err != nil || got == "" { + t.Fatalf("loadK(K1) = (%q, %v)", got, err) + } + if err := c.storeK("K2", "9"); err != nil { + t.Fatalf("storeK(K2) error = %v", err) + } + if _, err := c.loadK("AX"); err == nil { + t.Fatalf("loadK(AX) unexpectedly succeeded") + } + if err := c.storeK("AX", "9"); err == nil { + t.Fatalf("storeK(AX) unexpectedly succeeded") + } + + c.setZFlagFromI64("1") + c.setZSFlagsFromI64("2") + c.setZSFlagsFromI32("3") + c.setCmpFlags("4", "5") + if got := c.loadFlag(c.flagsZSlot); got == "" { + t.Fatalf("loadFlag() returned empty value") + } + + if slot, ok := c.fpParam(32); !ok || slot.Type != I32 { + t.Fatalf("fpParam(32) = (%#v, %v)", slot, ok) + } + if _, ok := c.fpParam(999); ok { + t.Fatalf("fpParam(999) unexpectedly succeeded") + } + if alloca, ty, ok := c.fpResultAlloca(80); !ok || alloca == "" || ty != I32 { + t.Fatalf("fpResultAlloca(80) = (%q, %q, %v)", alloca, ty, ok) + } + if _, _, ok := c.fpResultAlloca(999); ok { + t.Fatalf("fpResultAlloca(999) unexpectedly succeeded") + } + c.markFPResultAddrTaken(88) + c.markFPResultWritten(80) + + for _, off := range []int64{0, 8, 16, 24, 32, 40, 48, 56} { + if got, err := c.evalFPToI64(off); err != nil || got == "" { + t.Fatalf("evalFPToI64(%d) = (%q, %v)", off, got, err) + } + } + c.fpParams[64] = FrameSlot{Offset: 64, Type: LLVMType("v4i32"), Index: 0} + if _, err := c.evalFPToI64(64); err == nil { + t.Fatalf("evalFPToI64(unsupported type) unexpectedly succeeded") + } + c.fpParams[72] = FrameSlot{Offset: 72, Type: I64, Index: 99} + if _, err := c.evalFPToI64(72); err == nil { + t.Fatalf("evalFPToI64(invalid index) unexpectedly succeeded") + } + + if err := c.storeFPResult(80, I64, "11"); err != nil { + t.Fatalf("storeFPResult(i64->i32) error = %v", err) + } + if err := c.storeFPResult(88, I64, "12"); err != nil { + t.Fatalf("storeFPResult(i64->ptr) error = %v", err) + } + if err := c.storeFPResult(96, I64, "13"); err != nil { + t.Fatalf("storeFPResult(i64->double) error = %v", err) + } + if err := c.storeFPResult(104, LLVMType("float"), "%f"); err != nil { + t.Fatalf("storeFPResult(float->float) error = %v", err) + } + if err := c.storeFPResult(112, I8, "15"); err != nil { + t.Fatalf("storeFPResult(i8->i16) error = %v", err) + } + if err := c.storeFPResult(96, LLVMType("double"), "%x"); err != nil { + t.Fatalf("storeFPResult(double->double) error = %v", err) + } + if err := c.storeFPResult(80, Ptr, "%x"); err == nil { + t.Fatalf("storeFPResult(ptr->i32) unexpectedly succeeded") + } + + if got, err := c.loadFPResult(FrameSlot{Index: 0, Type: I32}); err != nil || got == "" { + t.Fatalf("loadFPResult() = (%q, %v)", got, err) + } + if _, err := c.loadFPResult(FrameSlot{Index: 99, Type: I32}); err == nil { + t.Fatalf("loadFPResult(missing) unexpectedly succeeded") + } + + if !isAMD64FloatRetTy(LLVMType("double")) || !isAMD64FloatRetTy(LLVMType("float")) || isAMD64FloatRetTy(I64) { + t.Fatalf("isAMD64FloatRetTy() mismatch") + } + if got, ok := c.retIntRegByOrd(1); !ok || got != BX { + t.Fatalf("retIntRegByOrd(1) = (%q, %v)", got, ok) + } + if _, ok := c.retIntRegByOrd(-1); ok { + t.Fatalf("retIntRegByOrd(-1) unexpectedly succeeded") + } + + if err := c.storeReg(AX, "21"); err != nil { + t.Fatalf("storeReg(AX) error = %v", err) + } + if err := c.storeReg(BX, "22"); err != nil { + t.Fatalf("storeReg(BX) error = %v", err) + } + if err := c.storeX("X0", "<16 x i8> zeroinitializer"); err != nil { + t.Fatalf("storeX(X0) error = %v", err) + } + for _, tc := range []struct { + ord int + ty LLVMType + }{ + {0, I64}, + {1, I32}, + {0, I16}, + {0, I8}, + {0, I1}, + {0, Ptr}, + {0, LLVMType("double")}, + {0, LLVMType("float")}, + } { + if got, err := c.loadRetIntRegTyped(tc.ord, tc.ty); err != nil || got == "" { + t.Fatalf("loadRetIntRegTyped(%d, %s) = (%q, %v)", tc.ord, tc.ty, got, err) + } + } + if got, err := c.loadRetIntRegTyped(99, I64); err != nil || got != "0" { + t.Fatalf("loadRetIntRegTyped(oob) = (%q, %v)", got, err) + } + if _, err := c.loadRetIntRegTyped(0, LLVMType("v2i64")); err == nil { + t.Fatalf("loadRetIntRegTyped(unsupported) unexpectedly succeeded") + } + + if got, err := c.loadRetFloatRegTyped(0, LLVMType("double")); err != nil || got == "" { + t.Fatalf("loadRetFloatRegTyped(double) = (%q, %v)", got, err) + } + if got, err := c.loadRetFloatRegTyped(0, LLVMType("float")); err != nil || got == "" { + t.Fatalf("loadRetFloatRegTyped(float) = (%q, %v)", got, err) + } + if got, err := c.loadRetFloatRegTyped(99, LLVMType("double")); err != nil || got != "0.000000e+00" { + t.Fatalf("loadRetFloatRegTyped(oob) = (%q, %v)", got, err) + } + if _, err := c.loadRetFloatRegTyped(0, I64); err == nil { + t.Fatalf("loadRetFloatRegTyped(unsupported) unexpectedly succeeded") + } + + if isFloat, ord := c.retClassOrdinal(FrameSlot{Index: 3, Type: LLVMType("float")}); !isFloat || ord != 1 { + t.Fatalf("retClassOrdinal(float) = (%v, %d)", isFloat, ord) + } + if got, err := c.loadRetSlotFallback(FrameSlot{Index: 0, Type: I32}); err != nil || got == "" { + t.Fatalf("loadRetSlotFallback(int) = (%q, %v)", got, err) + } + if got, err := c.loadRetSlotFallback(FrameSlot{Index: 2, Type: LLVMType("double")}); err != nil || got == "" { + t.Fatalf("loadRetSlotFallback(float) = (%q, %v)", got, err) + } + + for _, tc := range []struct { + in string + wantOK bool + wantOff int64 + }{ + {in: "foo<>(SB)", wantOK: true}, + {in: "foo+8(SB)", wantOK: true, wantOff: 8}, + {in: "bare_symbol", wantOK: true}, + {in: "4(AX)", wantOK: false}, + {in: "", wantOK: false}, + } { + _, off, ok := parseSBRef(tc.in) + if ok != tc.wantOK || off != tc.wantOff { + t.Fatalf("parseSBRef(%q) = (%d, %v)", tc.in, off, ok) + } + } + + out := b.String() + for _, want := range []string{ + "alloca <16 x i8>", + "alloca <32 x i8>", + "alloca <64 x i8>", + "alloca i64", + "ptrtoint ptr %arg0 to i64", + "bitcast double %arg6 to i64", + "bitcast float %arg7 to i32", + "trunc i64 11 to i32", + "inttoptr i64 12 to ptr", + "bitcast i64 13 to double", + "store float %f", + "zext i8 15 to i16", + "extractelement <2 x i64>", + "extractelement <4 x i32>", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestAMD64AtomicAndBranchEdges(t *testing.T) { + fn := Func{ + Instrs: []Instr{ + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: BX}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: CX}, {Kind: OpReg, Reg: DI}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: SI}, {Kind: OpReg, Reg: DX}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("R8")}, {Kind: OpReg, Reg: Reg("R9")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}, + }, + } + sigs := map[string]FuncSig{ + "example.helper": { + Name: "example.helper", + Args: []LLVMType{I64, I1, I8, I16, I32, Ptr}, + ArgRegs: []Reg{DI, SI, DX, CX, Reg("R8"), Reg("R9")}, + Ret: Ptr, + }, + "example.cast": { + Name: "example.cast", + Args: []LLVMType{I32, I64}, + Ret: I64, + }, + "example.voidret": { + Name: "example.voidret", + Args: []LLVMType{I64}, + Ret: Void, + }, + "example.badarg": { + Name: "example.badarg", + Args: []LLVMType{LLVMType("v2i64")}, + Ret: I64, + }, + "example.badret": { + Name: "example.badret", + Args: []LLVMType{I64}, + Ret: LLVMType("double"), + }, + } + sig := FuncSig{ + Name: "example.caller", + Args: []LLVMType{I64, Ptr}, + Ret: I64, + Frame: FrameLayout{ + Results: []FrameSlot{{Offset: 8, Type: I64, Index: 0}}, + }, + } + c, b := newAMD64CtxWithFuncForTest(t, fn, sig, sigs) + c.blocks = []amd64Block{{name: "entry"}, {name: "fall"}, {name: "target"}} + c.blockBase = []int{0, 1, 2} + c.blockByIdx = map[int]int{0: 0, 1: 1, 2: 2} + + if ok, term, err := c.lowerAtomic("LOCK", Instr{Raw: "LOCK"}); !ok || term || err != nil { + t.Fatalf("lowerAtomic(LOCK) = (%v, %v, %v)", ok, term, err) + } + if ok, _, err := c.lowerAtomic("CMPXCHGL", Instr{ + Raw: "CMPXCHGL CX, 8(BX)", + Args: []Operand{ + {Kind: OpReg, Reg: CX}, + {Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}, + }, + }); !ok || err != nil { + t.Fatalf("lowerAtomic(CMPXCHGL) = (%v, %v)", ok, err) + } + if ok, _, err := c.lowerAtomic("XADDQ", Instr{ + Raw: "XADDQ AX, 16(BX)", + Args: []Operand{ + {Kind: OpReg, Reg: AX}, + {Kind: OpMem, Mem: MemRef{Base: BX, Off: 16}}, + }, + }); !ok || err != nil { + t.Fatalf("lowerAtomic(XADDQ) = (%v, %v)", ok, err) + } + if ok, _, err := c.lowerAtomic("XCHGB", Instr{ + Raw: "XCHGB AX, BX", + Args: []Operand{ + {Kind: OpReg, Reg: AX}, + {Kind: OpReg, Reg: BX}, + }, + }); !ok || err != nil { + t.Fatalf("lowerAtomic(XCHGB reg) = (%v, %v)", ok, err) + } + if ok, _, err := c.lowerAtomic("XCHGQ", Instr{ + Raw: "XCHGQ AX, global<>(SB)", + Args: []Operand{ + {Kind: OpReg, Reg: AX}, + {Kind: OpSym, Sym: "global<>(SB)"}, + }, + }); !ok || err != nil { + t.Fatalf("lowerAtomic(XCHGQ sym) = (%v, %v)", ok, err) + } + if ok, _, err := c.lowerAtomic("ANDB", Instr{ + Raw: "ANDB $1, 4(BX)", + Args: []Operand{ + {Kind: OpImm, Imm: 1}, + {Kind: OpMem, Mem: MemRef{Base: BX, Off: 4}}, + }, + }); !ok || err != nil { + t.Fatalf("lowerAtomic(ANDB) = (%v, %v)", ok, err) + } + if ok, term, err := c.lowerAtomic("ORQ", Instr{ + Raw: "ORQ AX, BX", + Args: []Operand{ + {Kind: OpReg, Reg: AX}, + {Kind: OpReg, Reg: BX}, + }, + }); ok || term || err != nil { + t.Fatalf("lowerAtomic(non-mem ORQ) = (%v, %v, %v)", ok, term, err) + } + if _, err := c.amd64AtomicTruncFromI64("%x", LLVMType("v2i64")); err == nil { + t.Fatalf("amd64AtomicTruncFromI64(unsupported) unexpectedly succeeded") + } + if _, err := c.amd64AtomicExtendToI64("%x", LLVMType("v2i64")); err == nil { + t.Fatalf("amd64AtomicExtendToI64(unsupported) unexpectedly succeeded") + } + + emitBr := func(target string) { + b.WriteString(" br label %" + amd64LLVMBlockName(target) + "\n") + } + emitCondBr := func(cond string, target string, fall string) error { + b.WriteString(" br i1 " + cond + ", label %" + amd64LLVMBlockName(target) + ", label %" + amd64LLVMBlockName(fall) + "\n") + return nil + } + if ok, term, err := c.lowerBranch(0, 0, "CALL", Instr{ + Raw: "CALL helper(SB)", + Args: []Operand{{Kind: OpSym, Sym: "helper(SB)"}}, + }, emitBr, emitCondBr); !ok || term || err != nil { + t.Fatalf("lowerBranch(CALL sym) = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := c.lowerBranch(0, 0, "CALL", Instr{ + Raw: "CALL AX", + Args: []Operand{{Kind: OpReg, Reg: AX}}, + }, emitBr, emitCondBr); !ok || term || err != nil { + t.Fatalf("lowerBranch(CALL reg) = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := c.lowerBranch(0, 0, "CALL", Instr{ + Raw: "CALL 8(BX)", + Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}}, + }, emitBr, emitCondBr); !ok || term || err != nil { + t.Fatalf("lowerBranch(CALL mem) = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := c.lowerBranch(0, 0, "JEQ", Instr{ + Raw: "JEQ target", + Args: []Operand{{Kind: OpIdent, Ident: "target"}}, + }, emitBr, emitCondBr); !ok || !term || err != nil { + t.Fatalf("lowerBranch(JEQ ident) = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := c.lowerBranch(0, 0, "JMP", Instr{ + Raw: "JMP 2(PC)", + Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: PC, Off: 2}}}, + }, emitBr, emitCondBr); !ok || !term || err != nil { + t.Fatalf("lowerBranch(JMP pc-rel) = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := c.lowerBranch(0, 0, "JMP", Instr{ + Raw: "JMP AX", + Args: []Operand{{Kind: OpReg, Reg: AX}}, + }, emitBr, emitCondBr); !ok || !term || err != nil { + t.Fatalf("lowerBranch(JMP reg) = (%v, %v, %v)", ok, term, err) + } + if ok, _, err := c.lowerBranch(0, 0, "JEQ", Instr{ + Raw: "JEQ AX", + Args: []Operand{{Kind: OpReg, Reg: AX}}, + }, emitBr, emitCondBr); !ok || err == nil { + t.Fatalf("lowerBranch(JEQ reg) = (%v, %v), want error", ok, err) + } + + if err := c.callSym(Operand{Kind: OpSym, Sym: "runtime·entersyscall(SB)"}); err != nil { + t.Fatalf("callSym(entersyscall) error = %v", err) + } + if err := c.callSym(Operand{Kind: OpSym, Sym: "helper(SB)"}); err != nil { + t.Fatalf("callSym(helper) error = %v", err) + } + if err := c.callSym(Operand{Kind: OpSym, Sym: "badarg(SB)"}); err == nil { + t.Fatalf("callSym(badarg) unexpectedly succeeded") + } + if err := c.callSym(Operand{Kind: OpSym, Sym: "badret(SB)"}); err == nil { + t.Fatalf("callSym(badret) unexpectedly succeeded") + } + if err := c.callSym(Operand{Kind: OpReg, Reg: AX}); err == nil { + t.Fatalf("callSym(non-sym) unexpectedly succeeded") + } + if err := c.callSym(Operand{Kind: OpSym, Sym: "missing"}); err == nil { + t.Fatalf("callSym(missing suffix) unexpectedly succeeded") + } + + if err := c.tailCallAndRet(Operand{Kind: OpSym, Sym: "cast(SB)"}); err != nil { + t.Fatalf("tailCallAndRet(cast) error = %v", err) + } + if err := c.tailCallAndRet(Operand{Kind: OpSym, Sym: "helper(SB)"}); err != nil { + t.Fatalf("tailCallAndRet(helper) error = %v", err) + } + cErr, _ := newAMD64CtxWithFuncForTest(t, fn, FuncSig{Name: "example.errcaller", Args: []LLVMType{I64}, Ret: I64}, sigs) + if err := cErr.tailCallAndRet(Operand{Kind: OpSym, Sym: "voidret(SB)"}); err == nil { + t.Fatalf("tailCallAndRet(voidret) unexpectedly succeeded") + } + if err := c.tailCallAndRet(Operand{Kind: OpReg, Reg: AX}); err == nil { + t.Fatalf("tailCallAndRet(non-sym) unexpectedly succeeded") + } + if err := c.tailCallIndirectAddrAndRet("123"); err != nil { + t.Fatalf("tailCallIndirectAddrAndRet() error = %v", err) + } + + out := b.String() + for _, want := range []string{ + "cmpxchg ptr", + "atomicrmw add ptr", + "atomicrmw xchg ptr", + "atomicrmw and ptr", + "call i64", + "call ptr @\"example.helper\"", + "call i64 @\"example.cast\"", + "ret i64", + "br i1", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestAMD64ArithmeticCoverage(t *testing.T) { + fn := Func{ + Instrs: []Instr{ + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: BX}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: CX}, {Kind: OpReg, Reg: DX}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: DI}, {Kind: OpReg, Reg: SI}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("R8")}, {Kind: OpReg, Reg: Reg("R9")}}}, + }, + } + sig := FuncSig{ + Name: "example.arith", + Ret: Void, + Frame: FrameLayout{ + Results: []FrameSlot{ + {Offset: 8, Type: I1, Index: 0, Field: -1}, + {Offset: 16, Type: I64, Index: 1, Field: -1}, + }, + }, + } + c, b := newAMD64CtxWithFuncForTest(t, fn, sig, nil) + mustLower := func(op Op, ins Instr) { + t.Helper() + if ok, term, err := c.lowerArith(op, ins); !ok || term || err != nil { + t.Fatalf("lowerArith(%s) = (%v, %v, %v)", op, ok, term, err) + } + } + + mustLower("PUSHQ", Instr{Raw: "PUSHQ $1", Args: []Operand{{Kind: OpImm, Imm: 1}}}) + mustLower("POPQ", Instr{Raw: "POPQ CX", Args: []Operand{{Kind: OpReg, Reg: CX}}}) + mustLower("PUSHFQ", Instr{Raw: "PUSHFQ"}) + mustLower("POPFQ", Instr{Raw: "POPFQ"}) + mustLower("LFENCE", Instr{Raw: "LFENCE"}) + mustLower("UNDEF", Instr{Raw: "UNDEF"}) + mustLower("RDTSC", Instr{Raw: "RDTSC"}) + mustLower(OpCPUID, Instr{Raw: "CPUID"}) + mustLower(OpXGETBV, Instr{Raw: "XGETBV"}) + mustLower("RDTSCP", Instr{Raw: "RDTSCP"}) + mustLower("MOVSB", Instr{Raw: "MOVSB"}) + mustLower("MOVSQ", Instr{Raw: "MOVSQ"}) + mustLower("STOSQ", Instr{Raw: "STOSQ"}) + mustLower("NEGL", Instr{Raw: "NEGL AX", Args: []Operand{{Kind: OpReg, Reg: AX}}}) + mustLower("NEGL", Instr{Raw: "NEGL 4(BX)", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 4}}}}) + mustLower("RCRQ", Instr{Raw: "RCRQ $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("ADDQ", Instr{Raw: "ADDQ $2, AX", Args: []Operand{{Kind: OpImm, Imm: 2}, {Kind: OpReg, Reg: AX}}}) + mustLower("SUBQ", Instr{Raw: "SUBQ CX, 8(BX)", Args: []Operand{{Kind: OpReg, Reg: CX}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}}}) + mustLower("XORQ", Instr{Raw: "XORQ $3, AX", Args: []Operand{{Kind: OpImm, Imm: 3}, {Kind: OpReg, Reg: AX}}}) + mustLower("ANDQ", Instr{Raw: "ANDQ $4, AX", Args: []Operand{{Kind: OpImm, Imm: 4}, {Kind: OpReg, Reg: AX}}}) + mustLower("ORQ", Instr{Raw: "ORQ $5, AX", Args: []Operand{{Kind: OpImm, Imm: 5}, {Kind: OpReg, Reg: AX}}}) + mustLower("ADCQ", Instr{Raw: "ADCQ $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("SBBQ", Instr{Raw: "SBBQ $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("ADCXQ", Instr{Raw: "ADCXQ $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("ADOXQ", Instr{Raw: "ADOXQ $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("ADDL", Instr{Raw: "ADDL $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("SUBL", Instr{Raw: "SUBL CX, 12(BX)", Args: []Operand{{Kind: OpReg, Reg: CX}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 12}}}}) + mustLower("XORL", Instr{Raw: "XORL $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("ANDL", Instr{Raw: "ANDL $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("ORL", Instr{Raw: "ORL $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("XORB", Instr{Raw: "XORB $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("ANDB", Instr{Raw: "ANDB $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("ORB", Instr{Raw: "ORB $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("INCQ", Instr{Raw: "INCQ AX", Args: []Operand{{Kind: OpReg, Reg: AX}}}) + mustLower("DECQ", Instr{Raw: "DECQ AX", Args: []Operand{{Kind: OpReg, Reg: AX}}}) + mustLower("INCL", Instr{Raw: "INCL AX", Args: []Operand{{Kind: OpReg, Reg: AX}}}) + mustLower("DECL", Instr{Raw: "DECL AX", Args: []Operand{{Kind: OpReg, Reg: AX}}}) + mustLower("LEAQ", Instr{Raw: "LEAQ 8(BX)(CX*2), DI", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Index: CX, Scale: 2, Off: 8}}, {Kind: OpReg, Reg: DI}}}) + mustLower("LEAL", Instr{Raw: "LEAL 4(BX), SI", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 4}}, {Kind: OpReg, Reg: SI}}}) + mustLower("LEAQ", Instr{Raw: "LEAQ ret+16(FP), R8", Args: []Operand{{Kind: OpFP, FPOffset: 16}, {Kind: OpReg, Reg: Reg("R8")}}}) + mustLower("LEAQ", Instr{Raw: "LEAQ $ret+16(FP), R9", Args: []Operand{{Kind: OpFPAddr, FPOffset: 16}, {Kind: OpReg, Reg: Reg("R9")}}}) + mustLower("LEAQ", Instr{Raw: "LEAQ global<>(SB), AX", Args: []Operand{{Kind: OpSym, Sym: "global<>(SB)"}, {Kind: OpReg, Reg: AX}}}) + mustLower("POPCNTL", Instr{Raw: "POPCNTL AX, BX", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: BX}}}) + mustLower("POPCNTQ", Instr{Raw: "POPCNTQ AX, CX", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: CX}}}) + mustLower("BSFQ", Instr{Raw: "BSFQ AX, BX", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: BX}}}) + mustLower("BSRQ", Instr{Raw: "BSRQ AX, CX", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: CX}}}) + mustLower("BSWAPQ", Instr{Raw: "BSWAPQ AX", Args: []Operand{{Kind: OpReg, Reg: AX}}}) + mustLower("BSFL", Instr{Raw: "BSFL AX, DX", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: DX}}}) + mustLower("BSRL", Instr{Raw: "BSRL AX, DI", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: DI}}}) + mustLower("SETEQ", Instr{Raw: "SETEQ AX", Args: []Operand{{Kind: OpReg, Reg: AX}}}) + mustLower("SETGT", Instr{Raw: "SETGT ret+8(FP)", Args: []Operand{{Kind: OpFP, FPOffset: 8}}}) + mustLower("SETHI", Instr{Raw: "SETHI BX", Args: []Operand{{Kind: OpReg, Reg: BX}}}) + mustLower("CMOVQEQ", Instr{Raw: "CMOVQEQ CX, AX", Args: []Operand{{Kind: OpReg, Reg: CX}, {Kind: OpReg, Reg: AX}}}) + mustLower("CMOVQNE", Instr{Raw: "CMOVQNE CX, AX", Args: []Operand{{Kind: OpReg, Reg: CX}, {Kind: OpReg, Reg: AX}}}) + mustLower("CMOVQCS", Instr{Raw: "CMOVQCS CX, AX", Args: []Operand{{Kind: OpReg, Reg: CX}, {Kind: OpReg, Reg: AX}}}) + mustLower("CMOVQCC", Instr{Raw: "CMOVQCC CX, AX", Args: []Operand{{Kind: OpReg, Reg: CX}, {Kind: OpReg, Reg: AX}}}) + mustLower("CMOVQGT", Instr{Raw: "CMOVQGT CX, AX", Args: []Operand{{Kind: OpReg, Reg: CX}, {Kind: OpReg, Reg: AX}}}) + mustLower("ANDNL", Instr{Raw: "ANDNL AX, BX, CX", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: BX}, {Kind: OpReg, Reg: CX}}}) + mustLower("ANDNQ", Instr{Raw: "ANDNQ AX, BX, DX", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: BX}, {Kind: OpReg, Reg: DX}}}) + mustLower("SHRQ", Instr{Raw: "SHRQ $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("SHLQ", Instr{Raw: "SHLQ CX, AX", Args: []Operand{{Kind: OpReg, Reg: CX}, {Kind: OpReg, Reg: AX}}}) + mustLower("SARQ", Instr{Raw: "SARQ $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("SHLL", Instr{Raw: "SHLL $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("SHRL", Instr{Raw: "SHRL CX, AX", Args: []Operand{{Kind: OpReg, Reg: CX}, {Kind: OpReg, Reg: AX}}}) + mustLower("SALQ", Instr{Raw: "SALQ $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("SALL", Instr{Raw: "SALL $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("ROLL", Instr{Raw: "ROLL $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("ROLQ", Instr{Raw: "ROLQ CX, AX", Args: []Operand{{Kind: OpReg, Reg: CX}, {Kind: OpReg, Reg: AX}}}) + mustLower("RORQ", Instr{Raw: "RORQ $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + mustLower("RORL", Instr{Raw: "RORL CX, AX", Args: []Operand{{Kind: OpReg, Reg: CX}, {Kind: OpReg, Reg: AX}}}) + mustLower("RORXL", Instr{Raw: "RORXL $1, AX, BX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: BX}}}) + mustLower("RORXQ", Instr{Raw: "RORXQ $1, AX, CX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: CX}}}) + mustLower("NOTL", Instr{Raw: "NOTL AX", Args: []Operand{{Kind: OpReg, Reg: AX}}}) + mustLower("NOTQ", Instr{Raw: "NOTQ AX", Args: []Operand{{Kind: OpReg, Reg: AX}}}) + mustLower("NOTQ", Instr{Raw: "NOTQ 20(BX)", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 20}}}}) + mustLower("BSWAPL", Instr{Raw: "BSWAPL AX", Args: []Operand{{Kind: OpReg, Reg: AX}}}) + mustLower("MULQ", Instr{Raw: "MULQ CX", Args: []Operand{{Kind: OpReg, Reg: CX}}}) + mustLower("MULXQ", Instr{Raw: "MULXQ BX, CX, DI", Args: []Operand{{Kind: OpReg, Reg: BX}, {Kind: OpReg, Reg: CX}, {Kind: OpReg, Reg: DI}}}) + mustLower("MULL", Instr{Raw: "MULL CX", Args: []Operand{{Kind: OpReg, Reg: CX}}}) + mustLower("DIVL", Instr{Raw: "DIVL CX", Args: []Operand{{Kind: OpReg, Reg: CX}}}) + mustLower("IMULQ", Instr{Raw: "IMULQ CX", Args: []Operand{{Kind: OpReg, Reg: CX}}}) + mustLower("IMULQ", Instr{Raw: "IMULQ CX, DX", Args: []Operand{{Kind: OpReg, Reg: CX}, {Kind: OpReg, Reg: DX}}}) + mustLower("IMUL3Q", Instr{Raw: "IMUL3Q $3, CX, DI", Args: []Operand{{Kind: OpImm, Imm: 3}, {Kind: OpReg, Reg: CX}, {Kind: OpReg, Reg: DI}}}) + mustLower("NEGQ", Instr{Raw: "NEGQ AX", Args: []Operand{{Kind: OpReg, Reg: AX}}}) + + out := b.String() + for _, want := range []string{ + `asm sideeffect "cpuid"`, + `asm sideeffect "xgetbv"`, + "call i32 @llvm.ctpop.i32", + "call i64 @llvm.ctpop.i64", + "call i64 @llvm.cttz.i64", + "call i32 @llvm.ctlz.i32", + "call i64 @llvm.bswap.i64", + "call i32 @llvm.bswap.i32", + "mul i128", + "udiv i64", + "urem i64", + "select i1", + "ptrtoint ptr @\"example.global\" to i64", + "store i8", + "ashr i64", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} diff --git a/cmd/plan9asmscan/main_test.go b/cmd/plan9asmscan/main_test.go index 6dbf836..ca0665d 100644 --- a/cmd/plan9asmscan/main_test.go +++ b/cmd/plan9asmscan/main_test.go @@ -1,10 +1,12 @@ package main import ( + "encoding/json" "os" "path/filepath" "reflect" "runtime" + "strings" "testing" "github.com/goplus/plan9asm" @@ -151,3 +153,157 @@ func TestFamilyOfAMD64(t *testing.T) { } } } + +func TestListStdPackages(t *testing.T) { + pkgs, err := listStdPackages(runtime.GOOS, runtime.GOARCH) + if err != nil { + t.Fatalf("listStdPackages() error = %v", err) + } + if len(pkgs) == 0 { + t.Fatalf("listStdPackages() returned no packages") + } + foundRuntime := false + for _, p := range pkgs { + if p.ImportPath == "runtime" { + foundRuntime = true + break + } + } + if !foundRuntime { + t.Fatalf("listStdPackages() missing runtime package") + } +} + +func TestPackageSFilesAndAddOpStat(t *testing.T) { + pkg := pkgJSON{ + ImportPath: "example/p", + Dir: "/tmp/pkg", + SFiles: []string{"a.s", "b.S", filepath.Join("/abs", "c.s")}, + } + got := packageSFiles(pkg) + want := []string{filepath.Join("/tmp/pkg", "a.s"), filepath.Join("/abs", "c.s")} + if !reflect.DeepEqual(got, want) { + t.Fatalf("packageSFiles() = %#v, want %#v", got, want) + } + + ops := map[string]*opStat{} + addOpStat(ops, "MOVD", "a.s", "example/p", 2) + addOpStat(ops, "bad*", "a.s", "example/p", 2) + addOpStat(ops, "RET", "a.s", "example/p", 1) + addOpStat(ops, "MOVD", "a.s", "example/p", 0) + if got := ops["MOVD"].Count; got != 2 { + t.Fatalf("MOVD count = %d, want 2", got) + } + if got := ops["RET"].Count; got != 1 { + t.Fatalf("RET count = %d, want 1", got) + } + if _, ok := ops["BAD"]; ok { + t.Fatalf("invalid op unexpectedly added") + } +} + +func TestScanPackagesAndBuildReport(t *testing.T) { + dir := t.TempDir() + good := `TEXT ·f(SB),NOSPLIT,$0-0 + MOVQ $1, AX + NOP + RET +` + bad := `DATA foo(SB), $1 +` + dataOnly := `TEXT ·datafn(SB),NOSPLIT,$0-0 + RET +DATA foo+0(SB)/8, $1 +GLOBL foo(SB), RODATA, $8 +` + if err := os.WriteFile(filepath.Join(dir, "good.s"), []byte(good), 0o644); err != nil { + t.Fatalf("write good.s: %v", err) + } + if err := os.WriteFile(filepath.Join(dir, "bad.s"), []byte(bad), 0o644); err != nil { + t.Fatalf("write bad.s: %v", err) + } + if err := os.WriteFile(filepath.Join(dir, "data.s"), []byte(dataOnly), 0o644); err != nil { + t.Fatalf("write data.s: %v", err) + } + + pkgs := []pkgJSON{{ + ImportPath: "example/p", + Dir: dir, + SFiles: []string{"good.s", "bad.s", "data.s"}, + }} + ops, parseErrs, pkgsWithS, asmFiles, err := scanPackages(pkgs, plan9asm.ArchAMD64) + if err != nil { + t.Fatalf("scanPackages() error = %v", err) + } + if pkgsWithS != 1 { + t.Fatalf("pkgWithSFiles = %d, want 1", pkgsWithS) + } + if asmFiles != 3 { + t.Fatalf("asmFiles = %d, want 3", asmFiles) + } + if len(parseErrs) != 1 { + t.Fatalf("parseErrs = %#v, want 1 entry", parseErrs) + } + for _, op := range []string{"MOVQ", "NOP", "RET", "DATA", "GLOBL"} { + if _, ok := ops[op]; !ok { + t.Fatalf("scanPackages() missing %q in ops %#v", op, ops) + } + } + + rep := buildReport("linux", "amd64", 10, pkgsWithS, asmFiles, ops, map[string]struct{}{ + "RET": {}, + "MOVQ": {}, + }, parseErrs) + if rep.Goos != "linux" || rep.Goarch != "amd64" { + t.Fatalf("buildReport() wrong target: %#v", rep) + } + if rep.ParseErrCount != 1 || len(rep.ParseErrs) != 1 { + t.Fatalf("buildReport() parse errs = %#v", rep.ParseErrs) + } + if len(rep.Unsupported) == 0 { + t.Fatalf("buildReport() expected unsupported ops for NOP") + } + if len(rep.ClusterStats) == 0 || len(rep.FamilyStats) == 0 { + t.Fatalf("buildReport() expected cluster/family stats") + } + + md := string(renderMarkdown(rep)) + for _, want := range []string{ + "# Plan9 Asm Scan Report (linux/amd64)", + "## Cluster Summary", + "## Unsupported Ops (vs current lowerers)", + "good.s", + } { + if !strings.Contains(md, want) { + t.Fatalf("renderMarkdown() missing %q in:\n%s", want, md) + } + } +} + +func TestBuildReportAndJSONShape(t *testing.T) { + ops := map[string]*opStat{ + "CALL": {Count: 3, Files: map[string]int{"a.s": 2}, Pkgs: map[string]int{"p": 3}}, + "VPXORQ": {Count: 5, Files: map[string]int{"b.s": 5}, Pkgs: map[string]int{"p": 5}}, + "DATA": {Count: 1, Files: map[string]int{"c.s": 1}, Pkgs: map[string]int{"p": 1}}, + } + rep := buildReport("linux", "amd64", 3, 1, 2, ops, map[string]struct{}{"CALL": {}}, []parseErr{{File: "bad.s", Err: "boom"}}) + if rep.UniqueOps != 3 { + t.Fatalf("UniqueOps = %d, want 3", rep.UniqueOps) + } + if len(rep.OpsByFreq) != 3 { + t.Fatalf("OpsByFreq len = %d, want 3", len(rep.OpsByFreq)) + } + if rep.OpsByFreq[0].Op != "VPXORQ" { + t.Fatalf("OpsByFreq[0] = %#v", rep.OpsByFreq[0]) + } + if len(rep.Unsupported) != 1 { + t.Fatalf("Unsupported len = %d, want 1", len(rep.Unsupported)) + } + js, err := json.Marshal(rep) + if err != nil { + t.Fatalf("json.Marshal(report) error = %v", err) + } + if !strings.Contains(string(js), `"goarch":"amd64"`) { + t.Fatalf("json output missing goarch: %s", js) + } +} From cf113c55e3b12c8c9a34d562ff2775f7082ca19c Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sat, 14 Mar 2026 10:18:13 +0800 Subject: [PATCH 4/6] Expand coverage with amd64 vector sweep tests --- amd64_helper_edge_test.go | 189 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) diff --git a/amd64_helper_edge_test.go b/amd64_helper_edge_test.go index b0e2392..2c21c48 100644 --- a/amd64_helper_edge_test.go +++ b/amd64_helper_edge_test.go @@ -662,3 +662,192 @@ func TestAMD64ArithmeticCoverage(t *testing.T) { } } } + +func TestAMD64VectorCoverage(t *testing.T) { + fn := Func{ + Instrs: []Instr{ + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: BX}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: CX}, {Kind: OpReg, Reg: DX}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: DI}, {Kind: OpReg, Reg: SI}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("R8")}, {Kind: OpReg, Reg: Reg("R9")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("X2")}, {Kind: OpReg, Reg: Reg("X3")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("Y2")}, {Kind: OpReg, Reg: Reg("Y3")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("Z2")}, {Kind: OpReg, Reg: Reg("Z3")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("K1")}, {Kind: OpReg, Reg: Reg("K2")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("K3")}, {Kind: OpReg, Reg: Reg("X4")}}}, + }, + } + c, b := newAMD64CtxWithFuncForTest(t, fn, FuncSig{Name: "example.vec", Ret: Void}, nil) + mustLower := func(op Op, ins Instr) { + t.Helper() + if ok, term, err := c.lowerVec(op, ins); !ok || term || err != nil { + t.Fatalf("lowerVec(%s) = (%v, %v, %v)", op, ok, term, err) + } + } + + mustLower("MOVL", Instr{Raw: "MOVL $1, X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}}}) + mustLower("MOVD", Instr{Raw: "MOVD AX, X1", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("MOVQ", Instr{Raw: "MOVQ BX, X2", Args: []Operand{{Kind: OpReg, Reg: BX}, {Kind: OpReg, Reg: Reg("X2")}}}) + mustLower("MOVQ", Instr{Raw: "MOVQ X2, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("X2")}, {Kind: OpReg, Reg: AX}}}) + mustLower("KXORQ", Instr{Raw: "KXORQ K1, K2, K3", Args: []Operand{{Kind: OpReg, Reg: Reg("K1")}, {Kind: OpReg, Reg: Reg("K2")}, {Kind: OpReg, Reg: Reg("K3")}}}) + mustLower("KMOVB", Instr{Raw: "KMOVB AX, K1", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("K1")}}}) + mustLower("KMOVW", Instr{Raw: "KMOVW K1, 8(BX)", Args: []Operand{{Kind: OpReg, Reg: Reg("K1")}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}}}) + mustLower("KMOVQ", Instr{Raw: "KMOVQ K1, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("K1")}, {Kind: OpReg, Reg: AX}}}) + mustLower("VPERMB", Instr{Raw: "VPERMB Z0, Z1, Z2", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Z2")}}}) + mustLower("VPERMB", Instr{Raw: "VPERMB Z0, Z1, K1, Z2", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("K1")}, {Kind: OpReg, Reg: Reg("Z2")}}}) + mustLower("VGF2P8AFFINEQB", Instr{Raw: "VGF2P8AFFINEQB $1, Z0, Z1, Z2", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Z2")}}}) + mustLower("VPERMI2B", Instr{Raw: "VPERMI2B Z0, Z1, Z2", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Z2")}}}) + mustLower("VPERMI2B", Instr{Raw: "VPERMI2B Z0, Z1, K1, Z2", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("K1")}, {Kind: OpReg, Reg: Reg("Z2")}}}) + mustLower("VPOPCNTB", Instr{Raw: "VPOPCNTB Z0, Z1", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}}}) + mustLower("VPCMPUQ", Instr{Raw: "VPCMPUQ $1, Z0, Z1, K1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("K1")}}}) + mustLower("VPCOMPRESSQ", Instr{Raw: "VPCOMPRESSQ Z0, K1, Z2", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("K1")}, {Kind: OpReg, Reg: Reg("Z2")}}}) + mustLower("VPXORQ", Instr{Raw: "VPXORQ Z0, Z1, Z2", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Z2")}}}) + mustLower("VPANDQ", Instr{Raw: "VPANDQ Z0, Z1, Z2", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Z2")}}}) + mustLower("VPORQ", Instr{Raw: "VPORQ Z0, Z1, Z2", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Z2")}}}) + mustLower("VPXOR", Instr{Raw: "VPXOR Y0, Y1, Y2", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("Y2")}}}) + mustLower("VPOR", Instr{Raw: "VPOR Y0, Y1, Y2", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("Y2")}}}) + mustLower("VPADDD", Instr{Raw: "VPADDD Y0, Y1, Y2", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("Y2")}}}) + mustLower("VPADDQ", Instr{Raw: "VPADDQ Y0, Y1, Y2", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("Y2")}}}) + mustLower("VPXOR", Instr{Raw: "VPXOR X0, X1, X2", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}, {Kind: OpReg, Reg: Reg("X2")}}}) + mustLower("VPSHUFB", Instr{Raw: "VPSHUFB Y0, Y1, Y2", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("Y2")}}}) + mustLower("VPSHUFB", Instr{Raw: "VPSHUFB X0, X1, X2", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}, {Kind: OpReg, Reg: Reg("X2")}}}) + mustLower("VPSHUFD", Instr{Raw: "VPSHUFD $0x1b, Y0, Y1", Args: []Operand{{Kind: OpImm, Imm: 0x1b}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}) + mustLower("VPSLLD", Instr{Raw: "VPSLLD $2, Y0, Y1", Args: []Operand{{Kind: OpImm, Imm: 2}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}) + mustLower("VPSRLD", Instr{Raw: "VPSRLD $2, Y0, Y1", Args: []Operand{{Kind: OpImm, Imm: 2}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}) + mustLower("VPSRLQ", Instr{Raw: "VPSRLQ $2, Y0, Y1", Args: []Operand{{Kind: OpImm, Imm: 2}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}) + mustLower("VPSLLQ", Instr{Raw: "VPSLLQ $2, Y0, Y1", Args: []Operand{{Kind: OpImm, Imm: 2}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}) + mustLower("VPALIGNR", Instr{Raw: "VPALIGNR $3, Y0, Y1, Y2", Args: []Operand{{Kind: OpImm, Imm: 3}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("Y2")}}}) + mustLower("VPERM2I128", Instr{Raw: "VPERM2I128 $0x21, Y0, Y1, Y2", Args: []Operand{{Kind: OpImm, Imm: 0x21}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("Y2")}}}) + mustLower("VINSERTI128", Instr{Raw: "VINSERTI128 $1, X0, Y0, Y1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}) + mustLower("VMOVNTDQ", Instr{Raw: "VMOVNTDQ Y0, 32(BX)", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 32}}}}) + mustLower("AESENC", Instr{Raw: "AESENC X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("AESENCLAST", Instr{Raw: "AESENCLAST X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("AESDEC", Instr{Raw: "AESDEC X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("AESDECLAST", Instr{Raw: "AESDECLAST X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("AESIMC", Instr{Raw: "AESIMC X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("AESKEYGENASSIST", Instr{Raw: "AESKEYGENASSIST $1, X0, X1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("VPTEST", Instr{Raw: "VPTEST Y0, Y1", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}) + mustLower("PCMPESTRI", Instr{Raw: "PCMPESTRI $0x0c, 48(BX), X0", Args: []Operand{{Kind: OpImm, Imm: 0x0c}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 48}}, {Kind: OpReg, Reg: Reg("X0")}}}) + mustLower("VPAND", Instr{Raw: "VPAND Y0, Y1, Y2", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("Y2")}}}) + mustLower("VPBLENDD", Instr{Raw: "VPBLENDD $0xaa, Y0, Y1, Y2", Args: []Operand{{Kind: OpImm, Imm: 0xaa}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("Y2")}}}) + mustLower("VPBROADCASTB", Instr{Raw: "VPBROADCASTB X0, Y0", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}}}) + mustLower("VPSRLDQ", Instr{Raw: "VPSRLDQ $3, Y0, Y1", Args: []Operand{{Kind: OpImm, Imm: 3}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}) + mustLower("VPSLLDQ", Instr{Raw: "VPSLLDQ $3, Y0, Y1", Args: []Operand{{Kind: OpImm, Imm: 3}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}) + mustLower("PUNPCKLBW", Instr{Raw: "PUNPCKLBW X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PSHUFL", Instr{Raw: "PSHUFL $0x1b, X0, X1", Args: []Operand{{Kind: OpImm, Imm: 0x1b}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PSHUFHW", Instr{Raw: "PSHUFHW $0x1b, X0, X1", Args: []Operand{{Kind: OpImm, Imm: 0x1b}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("SHUFPS", Instr{Raw: "SHUFPS $0x1b, X0, X1", Args: []Operand{{Kind: OpImm, Imm: 0x1b}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PBLENDW", Instr{Raw: "PBLENDW $0xaa, X0, X1", Args: []Operand{{Kind: OpImm, Imm: 0xaa}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("SHA256MSG1", Instr{Raw: "SHA256MSG1 X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("SHA256MSG2", Instr{Raw: "SHA256MSG2 X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("SHA1NEXTE", Instr{Raw: "SHA1NEXTE X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("SHA1MSG1", Instr{Raw: "SHA1MSG1 X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("SHA1MSG2", Instr{Raw: "SHA1MSG2 X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("SHA1RNDS4", Instr{Raw: "SHA1RNDS4 $1, X0, X1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("SHA256RNDS2", Instr{Raw: "SHA256RNDS2 X0, X1, X2", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}, {Kind: OpReg, Reg: Reg("X2")}}}) + mustLower("VMOVDQU64", Instr{Raw: "VMOVDQU64 Z0, 64(BX)", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 64}}}}) + mustLower("VMOVDQU64", Instr{Raw: "VMOVDQU64 64(BX), Z1", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 64}}, {Kind: OpReg, Reg: Reg("Z1")}}}) + mustLower("VMOVDQU64", Instr{Raw: "VMOVDQU64 Z1, Z2", Args: []Operand{{Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Z2")}}}) + mustLower("VMOVDQU", Instr{Raw: "VMOVDQU Y0, 80(BX)", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 80}}}}) + mustLower("VMOVDQU", Instr{Raw: "VMOVDQU 80(BX), Y1", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 80}}, {Kind: OpReg, Reg: Reg("Y1")}}}) + mustLower("VMOVDQU", Instr{Raw: "VMOVDQU X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("VPCMPEQB", Instr{Raw: "VPCMPEQB Y0, Y1, Y2", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("Y2")}}}) + mustLower("VPMOVMSKB", Instr{Raw: "VPMOVMSKB Y0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: AX}}}) + mustLower("MOVOU", Instr{Raw: "MOVOU 96(BX), X0", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 96}}, {Kind: OpReg, Reg: Reg("X0")}}}) + mustLower("MOVOU", Instr{Raw: "MOVOU X0, 112(BX)", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 112}}}}) + mustLower("PXOR", Instr{Raw: "PXOR X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PAND", Instr{Raw: "PAND X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PANDN", Instr{Raw: "PANDN X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PADDL", Instr{Raw: "PADDL X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PADDQ", Instr{Raw: "PADDQ X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PSUBL", Instr{Raw: "PSUBL X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PSLLL", Instr{Raw: "PSLLL $1, X1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PSRLL", Instr{Raw: "PSRLL $1, X1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PSRAL", Instr{Raw: "PSRAL $1, X1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PCMPEQL", Instr{Raw: "PCMPEQL X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("VPCLMULQDQ", Instr{Raw: "VPCLMULQDQ $0x11, Z0, Z1, Z2", Args: []Operand{{Kind: OpImm, Imm: 0x11}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Z2")}}}) + mustLower("VPTERNLOGD", Instr{Raw: "VPTERNLOGD $0x96, Z0, Z1, Z2", Args: []Operand{{Kind: OpImm, Imm: 0x96}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Z2")}}}) + mustLower("VEXTRACTF32X4", Instr{Raw: "VEXTRACTF32X4 $1, Z0, X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("X0")}}}) + mustLower("PCLMULQDQ", Instr{Raw: "PCLMULQDQ $0x11, X0, X1", Args: []Operand{{Kind: OpImm, Imm: 0x11}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PCMPEQB", Instr{Raw: "PCMPEQB X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PMOVMSKB", Instr{Raw: "PMOVMSKB X0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: AX}}}) + mustLower("PSHUFB", Instr{Raw: "PSHUFB X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PINSRQ", Instr{Raw: "PINSRQ $1, AX, X1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PINSRD", Instr{Raw: "PINSRD $1, AX, X1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PINSRW", Instr{Raw: "PINSRW $1, AX, X1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PINSRB", Instr{Raw: "PINSRB $1, AX, X1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PEXTRB", Instr{Raw: "PEXTRB $1, X1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X1")}, {Kind: OpReg, Reg: AX}}}) + mustLower("PEXTRB", Instr{Raw: "PEXTRB $1, X1, 128(BX)", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X1")}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 128}}}}) + mustLower("PALIGNR", Instr{Raw: "PALIGNR $3, X0, X1", Args: []Operand{{Kind: OpImm, Imm: 3}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PSRLDQ", Instr{Raw: "PSRLDQ $3, X1", Args: []Operand{{Kind: OpImm, Imm: 3}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PSLLDQ", Instr{Raw: "PSLLDQ $3, X1", Args: []Operand{{Kind: OpImm, Imm: 3}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PSRLQ", Instr{Raw: "PSRLQ $1, X1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X1")}}}) + mustLower("PEXTRD", Instr{Raw: "PEXTRD $1, X1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X1")}, {Kind: OpReg, Reg: AX}}}) + mustLower("PEXTRD", Instr{Raw: "PEXTRD $1, X1, 136(BX)", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X1")}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 136}}}}) + + if _, err := c.loadXVecOperand(Operand{Kind: OpReg, Reg: AX}); err == nil { + t.Fatalf("loadXVecOperand(non-x) unexpectedly succeeded") + } + if _, err := c.loadYVecOperand(Operand{Kind: OpReg, Reg: AX}); err == nil { + t.Fatalf("loadYVecOperand(non-y) unexpectedly succeeded") + } + if _, err := c.loadZVecOperand(Operand{Kind: OpReg, Reg: AX}); err == nil { + t.Fatalf("loadZVecOperand(non-z) unexpectedly succeeded") + } + if got := llvmShiftRightBytesMask(3); !strings.Contains(got, "i32 3") || !strings.Contains(got, "i32 16") { + t.Fatalf("llvmShiftRightBytesMask(3) = %q", got) + } + if got := llvmShiftLeftBytesMask(3); !strings.Contains(got, "i32 16") { + t.Fatalf("llvmShiftLeftBytesMask(3) = %q", got) + } + if got := llvmAlignRightBytesMask(20); !strings.Contains(got, "i32 20") { + t.Fatalf("llvmAlignRightBytesMask(20) = %q", got) + } + if got := llvmAllOnesI8Vec(4); got != "" { + t.Fatalf("llvmAllOnesI8Vec(4) = %q", got) + } + if !isAMD64ZReg(Reg("Z0")) || isAMD64ZReg(AX) { + t.Fatalf("isAMD64ZReg() mismatch") + } + if got := amd64SelectZByAnyMask(c, "zeroinitializer", "1"); got == "" { + t.Fatalf("amd64SelectZByAnyMask() returned empty value") + } + pred := c.newTmp() + b.WriteString(" %" + pred + " = icmp eq <8 x i64> zeroinitializer, zeroinitializer\n") + if got := amd64PackI1x8ToI64(c, "%"+pred); got == "" { + t.Fatalf("amd64PackI1x8ToI64() returned empty value") + } + if got := amd64BytePopcountZ(c, "zeroinitializer"); got == "" { + t.Fatalf("amd64BytePopcountZ() returned empty value") + } + + out := b.String() + for _, want := range []string{ + "@llvm.x86.aesni.aesenc", + "@llvm.x86.aesni.aesenclast", + "@llvm.x86.aesni.aesdec", + "@llvm.x86.aesni.aesdeclast", + "@llvm.x86.aesni.aesimc", + "@llvm.x86.aesni.aeskeygenassist", + "@llvm.x86.ssse3.pshuf.b.128", + "@llvm.x86.sse2.pmovmskb.128", + "@llvm.x86.pclmulqdq", + "shufflevector <64 x i8>", + "bitcast <64 x i8>", + "select <16 x i1>", + "select <32 x i1>", + "store <64 x i8>", + "store <32 x i8>", + "store <16 x i8>", + "extractelement <16 x i8>", + "insertelement <2 x i64>", + "icmp ult <8 x i64>", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} From 0e8377ff72a5f2445d4254a2416cb51eba983913 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sat, 14 Mar 2026 15:15:44 +0800 Subject: [PATCH 5/6] Raise overall coverage to 85 percent --- amd64_helper_edge_test.go | 1080 ++++++++++++++++++++ arm64_helper_edge_test.go | 1699 +++++++++++++++++++++++++++++++ cmd/plan9asmscan/main_test.go | 119 +++ feature_attrs_test.go | 39 +- floatlit_test.go | 17 + go_translate_deep_test.go | 322 ++++++ small_coverage_test.go | 117 +++ translate_deep_coverage_test.go | 1551 ++++++++++++++++++++++++++++ types_deep_test.go | 218 ++++ 9 files changed, 5161 insertions(+), 1 deletion(-) create mode 100644 arm64_helper_edge_test.go create mode 100644 go_translate_deep_test.go create mode 100644 small_coverage_test.go create mode 100644 translate_deep_coverage_test.go create mode 100644 types_deep_test.go diff --git a/amd64_helper_edge_test.go b/amd64_helper_edge_test.go index 2c21c48..cb85a30 100644 --- a/amd64_helper_edge_test.go +++ b/amd64_helper_edge_test.go @@ -851,3 +851,1083 @@ func TestAMD64VectorCoverage(t *testing.T) { } } } + +func TestAMD64TranslateReturnCoverage(t *testing.T) { + fn := Func{ + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·edge(SB),NOSPLIT,$0-0"}, + {Op: "GET_TLS(CX)", Raw: "GET_TLS(CX)"}, + {Op: "NOP", Raw: "NOP"}, + }, + } + var translated strings.Builder + if err := translateFuncAMD64(&translated, fn, FuncSig{Name: "example.edge", Ret: I32}, testResolveSym("example"), nil, true); err != nil { + t.Fatalf("translateFuncAMD64() error = %v", err) + } + if !strings.Contains(translated.String(), "ret i32 0") || !strings.Contains(translated.String(), "; s: NOP") { + t.Fatalf("translateFuncAMD64() output = \n%s", translated.String()) + } + + for _, tc := range []struct { + name string + sig FuncSig + want string + }{ + {"void", FuncSig{Name: "example.retvoid", Ret: Void}, "ret void"}, + {"i1", FuncSig{Name: "example.reti1", Ret: I1}, "ret i1"}, + {"i8", FuncSig{Name: "example.reti8", Ret: I8}, "ret i8"}, + {"i16", FuncSig{Name: "example.reti16", Ret: I16}, "ret i16"}, + {"i32", FuncSig{Name: "example.reti32", Ret: I32}, "ret i32"}, + {"i64", FuncSig{Name: "example.reti64", Ret: I64}, "ret i64"}, + } { + c, b := newAMD64CtxWithFuncForTest(t, Func{}, tc.sig, nil) + if tc.sig.Ret != Void { + if err := c.storeReg(AX, "19"); err != nil { + t.Fatalf("storeReg(AX) error = %v", err) + } + } + if err := c.lowerRET(); err != nil { + t.Fatalf("lowerRET(%s) error = %v", tc.name, err) + } + if !strings.Contains(b.String(), tc.want) { + t.Fatalf("lowerRET(%s) output = \n%s", tc.name, b.String()) + } + } + + cAgg, bAgg := newAMD64CtxWithFuncForTest(t, Func{}, FuncSig{ + Name: "example.retagg", + Ret: LLVMType("{ i64, i32 }"), + Frame: FrameLayout{ + Results: []FrameSlot{ + {Offset: 8, Type: I64, Index: 0}, + {Offset: 16, Type: I32, Index: 1}, + }, + }, + }, nil) + if err := cAgg.storeFPResult(8, I64, "21"); err != nil { + t.Fatalf("storeFPResult(8) error = %v", err) + } + if err := cAgg.lowerRET(); err != nil { + t.Fatalf("lowerRET(aggregate) error = %v", err) + } + if !strings.Contains(bAgg.String(), "insertvalue { i64, i32 }") { + t.Fatalf("lowerRET(aggregate) output = \n%s", bAgg.String()) + } + + cz, bz := newAMD64CtxWithFuncForTest(t, Func{}, FuncSig{Name: "example.zero", Ret: I64}, nil) + cz.lowerRetZero() + if !strings.Contains(bz.String(), "ret i64 0") { + t.Fatalf("lowerRetZero() output = \n%s", bz.String()) + } +} + +func TestAMD64FPMovCoverage(t *testing.T) { + fn := Func{ + Instrs: []Instr{ + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: BX}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("X2")}, {Kind: OpReg, Reg: Reg("X3")}}}, + }, + } + sig := FuncSig{ + Name: "example.fpmov", + Args: []LLVMType{I64, LLVMType("double"), I64, I64}, + Ret: Void, + Frame: FrameLayout{ + Params: []FrameSlot{ + {Offset: 0, Type: I64, Index: 0, Field: -1}, + {Offset: 8, Type: LLVMType("double"), Index: 1, Field: -1}, + {Offset: 16, Type: I64, Index: 2, Field: -1}, + {Offset: 24, Type: I64, Index: 3, Field: -1}, + }, + Results: []FrameSlot{ + {Offset: 40, Type: LLVMType("double"), Index: 0, Field: -1}, + {Offset: 48, Type: I64, Index: 1, Field: -1}, + {Offset: 56, Type: I16, Index: 2, Field: -1}, + {Offset: 64, Type: I8, Index: 3, Field: -1}, + }, + }, + } + c, b := newAMD64CtxWithFuncForTest(t, fn, sig, nil) + check := func(kind string, ins Instr, ok bool, err error) { + t.Helper() + if err != nil { + t.Fatalf("%s %q error = %v", kind, ins.Raw, err) + } + if !ok { + t.Fatalf("%s %q returned ok=false", kind, ins.Raw) + } + } + for _, tc := range []struct { + r Reg + v string + }{ + {AX, "11"}, + {BX, "12"}, + {CX, "13"}, + {DX, "14"}, + {SI, "15"}, + {DI, "16"}, + {Reg("R8"), "17"}, + {Reg("R9"), "18"}, + {Reg("R10"), "19"}, + {Reg("R11"), "20"}, + } { + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + for _, xr := range []Reg{"X0", "X1", "X2", "X3"} { + if err := c.storeX(xr, "<16 x i8> zeroinitializer"); err != nil { + t.Fatalf("storeX(%s) error = %v", xr, err) + } + } + + for _, ins := range []Instr{ + {Op: "MOVAPD", Args: []Operand{{Kind: OpSym, Sym: "example.vec(SB)"}, {Kind: OpReg, Reg: "X0"}}, Raw: "MOVAPD example.vec(SB), X0"}, + {Op: "ANDPD", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: AX, Off: 8}}, {Kind: OpReg, Reg: "X0"}}, Raw: "ANDPD 8(AX), X0"}, + {Op: "ANDNPD", Args: []Operand{{Kind: OpReg, Reg: "X1"}, {Kind: OpReg, Reg: "X0"}}, Raw: "ANDNPD X1, X0"}, + {Op: "ORPD", Args: []Operand{{Kind: OpReg, Reg: "X1"}, {Kind: OpReg, Reg: "X0"}}, Raw: "ORPD X1, X0"}, + {Op: "XORPS", Args: []Operand{{Kind: OpReg, Reg: "X1"}, {Kind: OpReg, Reg: "X0"}}, Raw: "XORPS X1, X0"}, + {Op: "MOVSD", Args: []Operand{{Kind: OpImm, Imm: 7}, {Kind: OpReg, Reg: "X0"}}, Raw: "MOVSD $7, X0"}, + {Op: "MOVSD", Args: []Operand{{Kind: OpReg, Reg: "X0"}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 16}}}, Raw: "MOVSD X0, 16(BX)"}, + {Op: "MOVSD", Args: []Operand{{Kind: OpReg, Reg: "X0"}, {Kind: OpSym, Sym: "example.f64(SB)"}}, Raw: "MOVSD X0, example.f64(SB)"}, + {Op: "MOVSD", Args: []Operand{{Kind: OpReg, Reg: "X0"}, {Kind: OpFP, FPOffset: 40}}, Raw: "MOVSD X0, ret+40(FP)"}, + {Op: "ADDSD", Args: []Operand{{Kind: OpReg, Reg: "X0"}, {Kind: OpReg, Reg: "X1"}}, Raw: "ADDSD X0, X1"}, + {Op: "SUBSD", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: CX, Off: 8}}, {Kind: OpReg, Reg: "X1"}}, Raw: "SUBSD 8(CX), X1"}, + {Op: "MULSD", Args: []Operand{{Kind: OpSym, Sym: "example.f64(SB)"}, {Kind: OpReg, Reg: "X1"}}, Raw: "MULSD example.f64(SB), X1"}, + {Op: "DIVSD", Args: []Operand{{Kind: OpFP, FPOffset: 8}, {Kind: OpReg, Reg: "X1"}}, Raw: "DIVSD arg+8(FP), X1"}, + {Op: "MAXSD", Args: []Operand{{Kind: OpReg, Reg: "X0"}, {Kind: OpReg, Reg: "X1"}}, Raw: "MAXSD X0, X1"}, + {Op: "MINSD", Args: []Operand{{Kind: OpReg, Reg: "X0"}, {Kind: OpReg, Reg: "X1"}}, Raw: "MINSD X0, X1"}, + {Op: "SQRTSD", Args: []Operand{{Kind: OpReg, Reg: "X1"}, {Kind: OpReg, Reg: "X2"}}, Raw: "SQRTSD X1, X2"}, + {Op: "COMISD", Args: []Operand{{Kind: OpReg, Reg: "X0"}, {Kind: OpReg, Reg: "X1"}}, Raw: "COMISD X0, X1"}, + {Op: "CMPSD", Args: []Operand{{Kind: OpReg, Reg: "X0"}, {Kind: OpReg, Reg: "X1"}, {Kind: OpImm, Imm: 7}}, Raw: "CMPSD X0, X1, $7"}, + {Op: "CMPSD", Args: []Operand{{Kind: OpReg, Reg: "X0"}, {Kind: OpReg, Reg: "X1"}, {Kind: OpSym, Sym: "5"}}, Raw: "CMPSD X0, X1, 5"}, + {Op: "CVTTSD2SQ", Args: []Operand{{Kind: OpReg, Reg: "X0"}, {Kind: OpReg, Reg: AX}}, Raw: "CVTTSD2SQ X0, AX"}, + {Op: "CVTSQ2SD", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: "X2"}}, Raw: "CVTSQ2SD AX, X2"}, + {Op: "CVTSD2SL", Args: []Operand{{Kind: OpReg, Reg: "X1"}, {Kind: OpReg, Reg: BX}}, Raw: "CVTSD2SL X1, BX"}, + {Op: "CVTSL2SD", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: "X0"}}, Raw: "CVTSL2SD AX, X0"}, + {Op: "VADDSD", Args: []Operand{{Kind: OpReg, Reg: "X0"}, {Kind: OpReg, Reg: "X1"}, {Kind: OpReg, Reg: "X2"}}, Raw: "VADDSD X0, X1, X2"}, + {Op: "VFMADD213SD", Args: []Operand{{Kind: OpReg, Reg: "X0"}, {Kind: OpReg, Reg: "X1"}, {Kind: OpReg, Reg: "X2"}}, Raw: "VFMADD213SD X0, X1, X2"}, + {Op: "VFNMADD231SD", Args: []Operand{{Kind: OpReg, Reg: "X0"}, {Kind: OpReg, Reg: "X1"}, {Kind: OpReg, Reg: "X2"}}, Raw: "VFNMADD231SD X0, X1, X2"}, + } { + ok, _, err := c.lowerFP(ins.Op, ins) + check("lowerFP", ins, ok, err) + } + + c.setCmpFlags("1", "2") + for _, ins := range []Instr{ + {Op: "CMOVQLT", Args: []Operand{{Kind: OpReg, Reg: CX}, {Kind: OpReg, Reg: DX}}, Raw: "CMOVQLT CX, DX"}, + {Op: "MOVLQSX", Args: []Operand{{Kind: OpImm, Imm: 21}, {Kind: OpReg, Reg: AX}}, Raw: "MOVLQSX $21, AX"}, + {Op: "MOVLQSX", Args: []Operand{{Kind: OpReg, Reg: BX}, {Kind: OpReg, Reg: CX}}, Raw: "MOVLQSX BX, CX"}, + {Op: "MOVLQSX", Args: []Operand{{Kind: OpFP, FPOffset: 16}, {Kind: OpReg, Reg: DX}}, Raw: "MOVLQSX arg+16(FP), DX"}, + {Op: "MOVLQSX", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: SI, Off: 4}}, {Kind: OpReg, Reg: SI}}, Raw: "MOVLQSX 4(SI), SI"}, + {Op: "MOVLQSX", Args: []Operand{{Kind: OpSym, Sym: "example.i32(SB)"}, {Kind: OpReg, Reg: DI}}, Raw: "MOVLQSX example.i32(SB), DI"}, + {Op: "MOVQ", Args: []Operand{{Kind: OpImm, Imm: 31}, {Kind: OpReg, Reg: AX}}, Raw: "MOVQ $31, AX"}, + {Op: "MOVQ", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}, {Kind: OpReg, Reg: BX}}, Raw: "MOVQ 8(BX), BX"}, + {Op: "MOVQ", Args: []Operand{{Kind: OpSym, Sym: "example.i64(SB)"}, {Kind: OpReg, Reg: CX}}, Raw: "MOVQ example.i64(SB), CX"}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpFP, FPOffset: 48}}, Raw: "MOVQ AX, ret+48(FP)"}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpMem, Mem: MemRef{Base: DX, Off: 8}}}, Raw: "MOVQ AX, 8(DX)"}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpSym, Sym: "example.i64(SB)"}}, Raw: "MOVQ AX, example.i64(SB)"}, + {Op: "MOVL", Args: []Operand{{Kind: OpImm, Imm: 41}, {Kind: OpReg, Reg: DX}}, Raw: "MOVL $41, DX"}, + {Op: "MOVL", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: SI, Off: 8}}, {Kind: OpReg, Reg: Reg("R8")}}, Raw: "MOVL 8(SI), R8"}, + {Op: "MOVL", Args: []Operand{{Kind: OpSym, Sym: "example.i32(SB)"}, {Kind: OpReg, Reg: Reg("R9")}}, Raw: "MOVL example.i32(SB), R9"}, + {Op: "MOVL", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpFP, FPOffset: 48}}, Raw: "MOVL AX, ret+48(FP)"}, + {Op: "MOVL", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpMem, Mem: MemRef{Base: DI, Off: 8}}}, Raw: "MOVL AX, 8(DI)"}, + {Op: "MOVL", Args: []Operand{{Kind: OpImm, Imm: 0xf1}, {Kind: OpImm, Imm: 0xf1}}, Raw: "MOVL $0xf1, 0xf1"}, + {Op: "MOVL", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpSym, Sym: "example.i32(SB)"}}, Raw: "MOVL AX, example.i32(SB)"}, + {Op: "MOVLQZX", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: AX, Off: 12}}, {Kind: OpReg, Reg: Reg("R10")}}, Raw: "MOVLQZX 12(AX), R10"}, + {Op: "MOVBQZX", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: AX, Off: 13}}, {Kind: OpReg, Reg: Reg("R11")}}, Raw: "MOVBQZX 13(AX), R11"}, + {Op: "MOVB", Args: []Operand{{Kind: OpImm, Imm: 9}, {Kind: OpReg, Reg: BX}}, Raw: "MOVB $9, BX"}, + {Op: "MOVB", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 2}}, {Kind: OpReg, Reg: CX}}, Raw: "MOVB 2(BX), CX"}, + {Op: "MOVB", Args: []Operand{{Kind: OpFP, FPOffset: 16}, {Kind: OpReg, Reg: DX}}, Raw: "MOVB arg+16(FP), DX"}, + {Op: "MOVB", Args: []Operand{{Kind: OpSym, Sym: "example.b(SB)"}, {Kind: OpReg, Reg: SI}}, Raw: "MOVB example.b(SB), SI"}, + {Op: "MOVB", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpFP, FPOffset: 64}}, Raw: "MOVB AX, ret+64(FP)"}, + {Op: "MOVB", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpMem, Mem: MemRef{Base: DI, Off: 3}}}, Raw: "MOVB AX, 3(DI)"}, + {Op: "MOVB", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpSym, Sym: "example.b(SB)"}}, Raw: "MOVB AX, example.b(SB)"}, + {Op: "MOVW", Args: []Operand{{Kind: OpImm, Imm: 10}, {Kind: OpReg, Reg: SI}}, Raw: "MOVW $10, SI"}, + {Op: "MOVW", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: SI, Off: 2}}, {Kind: OpReg, Reg: DI}}, Raw: "MOVW 2(SI), DI"}, + {Op: "MOVW", Args: []Operand{{Kind: OpFP, FPOffset: 16}, {Kind: OpReg, Reg: Reg("R8")}}, Raw: "MOVW arg+16(FP), R8"}, + {Op: "MOVW", Args: []Operand{{Kind: OpSym, Sym: "example.w(SB)"}, {Kind: OpReg, Reg: Reg("R9")}}, Raw: "MOVW example.w(SB), R9"}, + {Op: "MOVW", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpFP, FPOffset: 56}}, Raw: "MOVW AX, ret+56(FP)"}, + {Op: "MOVW", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpMem, Mem: MemRef{Base: DI, Off: 4}}}, Raw: "MOVW AX, 4(DI)"}, + {Op: "MOVW", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpSym, Sym: "example.w(SB)"}}, Raw: "MOVW AX, example.w(SB)"}, + } { + ok, _, err := c.lowerMov(ins.Op, ins) + check("lowerMov", ins, ok, err) + } + + if got, err := c.loadXLowI64("X0"); err != nil || got == "" { + t.Fatalf("loadXLowI64(X0) = (%q, %v)", got, err) + } + if got, err := c.loadXLowF64("X0"); err != nil || got == "" { + t.Fatalf("loadXLowF64(X0) = (%q, %v)", got, err) + } + if err := c.storeXLowI64("X1", "77"); err != nil { + t.Fatalf("storeXLowI64(X1) error = %v", err) + } + if err := c.storeXLowF64("X1", "1.500000e+00"); err != nil { + t.Fatalf("storeXLowF64(X1) error = %v", err) + } + if got, err := c.evalF64(Operand{Kind: OpFP, FPOffset: 8}); err != nil || got == "" { + t.Fatalf("evalF64(double fp) = (%q, %v)", got, err) + } + if got, err := c.evalF64(Operand{Kind: OpFP, FPOffset: 16}); err != nil || got == "" { + t.Fatalf("evalF64(i64 fp) = (%q, %v)", got, err) + } + c.fpParams[32] = FrameSlot{Offset: 32, Type: I32, Index: 2, Field: -1} + if _, err := c.evalF64(Operand{Kind: OpFP, FPOffset: 32}); err == nil { + t.Fatalf("evalF64(i32 fp) unexpectedly succeeded") + } + + out := b.String() + for _, want := range []string{ + "fadd double", + "fsub double", + "fmul double", + "fdiv double", + "fneg double", + "fcmp ueq double", + "fcmp ord double", + "call double @llvm.sqrt.f64", + "call double @llvm.rint.f64", + "sitofp i64", + "sitofp i32", + "fptosi double", + "select i1", + "bitcast i64", + "bitcast double", + "store double", + "store i64", + "store i32", + "store i16", + "store i8", + `load <16 x i8>, ptr @"example.vec"`, + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestAMD64CmpBtCoverage(t *testing.T) { + sig := FuncSig{ + Name: "example.cmpbt", + Args: []LLVMType{I64}, + Ret: Void, + Frame: FrameLayout{ + Params: []FrameSlot{{Offset: 0, Type: I64, Index: 0}}, + Results: []FrameSlot{{Offset: 8, Type: I64, Index: 0}}, + }, + } + c, b := newAMD64CtxWithFuncForTest(t, Func{}, sig, nil) + for _, tc := range []struct { + r Reg + v string + }{ + {AX, "11"}, + {BX, "12"}, + {CX, "13"}, + {DX, "14"}, + {SI, "15"}, + {DI, "16"}, + } { + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + + check := func(op Op, ins Instr) { + t.Helper() + if ok, term, err := c.lowerCmpBt(op, ins); !ok || term || err != nil { + t.Fatalf("lowerCmpBt(%s %q) = (%v, %v, %v)", op, ins.Raw, ok, term, err) + } + } + + check("CMPB", Instr{Raw: "CMPB $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + check("CMPW", Instr{Raw: "CMPW 4(BX), CX", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 4}}, {Kind: OpReg, Reg: CX}}}) + check("CMPL", Instr{Raw: "CMPL arg+0(FP), DI", Args: []Operand{{Kind: OpFP, FPOffset: 0}, {Kind: OpReg, Reg: DI}}}) + check("CMPQ", Instr{Raw: "CMPQ example.global(SB), SI", Args: []Operand{{Kind: OpSym, Sym: "example.global(SB)"}, {Kind: OpReg, Reg: SI}}}) + check("TESTB", Instr{Raw: "TESTB AX, BX", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: BX}}}) + check("TESTW", Instr{Raw: "TESTW $7, arg+0(FP)", Args: []Operand{{Kind: OpImm, Imm: 7}, {Kind: OpFP, FPOffset: 0}}}) + check("TESTL", Instr{Raw: "TESTL $const, 8(BX)", Args: []Operand{{Kind: OpSym, Sym: "$const"}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}}}) + check("TESTQ", Instr{Raw: "TESTQ example.global(SB), DI", Args: []Operand{{Kind: OpSym, Sym: "example.global(SB)"}, {Kind: OpReg, Reg: DI}}}) + check("BTQ", Instr{Raw: "BTQ $3, AX", Args: []Operand{{Kind: OpImm, Imm: 3}, {Kind: OpReg, Reg: AX}}}) + + if ok, term, err := c.lowerCmpBt("BAD", Instr{}); ok || term || err != nil { + t.Fatalf("lowerCmpBt(BAD) = (%v, %v, %v)", ok, term, err) + } + if _, _, err := c.lowerCmpBt("CMPQ", Instr{Raw: "CMPQ AX", Args: []Operand{{Kind: OpReg, Reg: AX}}}); err == nil { + t.Fatalf("short CMPQ unexpectedly succeeded") + } + if _, _, err := c.lowerCmpBt("TESTQ", Instr{Raw: "TESTQ AX", Args: []Operand{{Kind: OpReg, Reg: AX}}}); err == nil { + t.Fatalf("short TESTQ unexpectedly succeeded") + } + if _, _, err := c.lowerCmpBt("BTQ", Instr{Raw: "BTQ AX, BX", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: BX}}}); err == nil { + t.Fatalf("bad BTQ unexpectedly succeeded") + } + if got, err := c.evalIntSized(Operand{Kind: OpSym, Sym: "$const"}, I32); err != nil || got != "0" { + t.Fatalf("evalIntSized($const) = (%q, %v)", got, err) + } + if _, err := c.evalIntSized(Operand{Kind: OpSym, Sym: "bad"}, I32); err == nil { + t.Fatalf("evalIntSized(bad sym) unexpectedly succeeded") + } + if _, err := c.evalIntSized(Operand{Kind: OpIdent, Ident: "label"}, I32); err == nil { + t.Fatalf("evalIntSized(ident) unexpectedly succeeded") + } + + out := b.String() + for _, want := range []string{ + "icmp eq i8", + "icmp slt i16", + "icmp ult i32", + "and i64", + "store i1 false, ptr %flags_cf", + "lshr i64", + "load i64, ptr @\"example.global\"", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestAMD64MovSyscallAndCRC32Coverage(t *testing.T) { + sig := FuncSig{ + Name: "example.mov", + Args: []LLVMType{I64}, + Ret: Void, + Frame: FrameLayout{ + Params: []FrameSlot{{Offset: 0, Type: I64, Index: 0}}, + Results: []FrameSlot{{Offset: 8, Type: I64, Index: 0}, {Offset: 16, Type: I16, Index: 1}, {Offset: 24, Type: I8, Index: 2}}, + }, + } + c, b := newAMD64CtxWithFuncForTest(t, Func{}, sig, nil) + for _, tc := range []struct { + r Reg + v string + }{ + {AX, "21"}, + {BX, "22"}, + {CX, "23"}, + {DX, "24"}, + {SI, "25"}, + {DI, "26"}, + {Reg("R8"), "27"}, + {Reg("R9"), "28"}, + {Reg("R10"), "29"}, + } { + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + + checkMov := func(op Op, ins Instr) { + t.Helper() + if ok, term, err := c.lowerMov(op, ins); !ok || term || err != nil { + t.Fatalf("lowerMov(%s %q) = (%v, %v, %v)", op, ins.Raw, ok, term, err) + } + } + + checkMov("MOVLQSX", Instr{Raw: "MOVLQSX $7, AX", Args: []Operand{{Kind: OpImm, Imm: 7}, {Kind: OpReg, Reg: AX}}}) + checkMov("MOVLQSX", Instr{Raw: "MOVLQSX BX, CX", Args: []Operand{{Kind: OpReg, Reg: BX}, {Kind: OpReg, Reg: CX}}}) + checkMov("MOVLQSX", Instr{Raw: "MOVLQSX arg+0(FP), DX", Args: []Operand{{Kind: OpFP, FPOffset: 0}, {Kind: OpReg, Reg: DX}}}) + checkMov("MOVLQSX", Instr{Raw: "MOVLQSX 8(BX), SI", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}, {Kind: OpReg, Reg: SI}}}) + checkMov("MOVLQSX", Instr{Raw: "MOVLQSX example.global(SB), DI", Args: []Operand{{Kind: OpSym, Sym: "example.global(SB)"}, {Kind: OpReg, Reg: DI}}}) + b.WriteString(" store i1 true, ptr " + c.flagsSltSlot + "\n") + checkMov("CMOVQLT", Instr{Raw: "CMOVQLT AX, BX", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: BX}}}) + checkMov("MOVB", Instr{Raw: "MOVB $1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}) + checkMov("MOVB", Instr{Raw: "MOVB BX, ret+24(FP)", Args: []Operand{{Kind: OpReg, Reg: BX}, {Kind: OpFP, FPOffset: 24}}}) + checkMov("MOVW", Instr{Raw: "MOVW arg+0(FP), 8(BX)", Args: []Operand{{Kind: OpFP, FPOffset: 0}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}}}) + checkMov("MOVW", Instr{Raw: "MOVW example.global(SB), example.dest(SB)", Args: []Operand{{Kind: OpSym, Sym: "example.global(SB)"}, {Kind: OpSym, Sym: "example.dest(SB)"}}}) + checkMov("MOVQ", Instr{Raw: "MOVQ example.global(SB), CX", Args: []Operand{{Kind: OpSym, Sym: "example.global(SB)"}, {Kind: OpReg, Reg: CX}}}) + checkMov("MOVQ", Instr{Raw: "MOVQ DX, ret+8(FP)", Args: []Operand{{Kind: OpReg, Reg: DX}, {Kind: OpFP, FPOffset: 8}}}) + checkMov("MOVQ", Instr{Raw: "MOVQ $9, 16(BX)", Args: []Operand{{Kind: OpImm, Imm: 9}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 16}}}}) + checkMov("MOVQ", Instr{Raw: "MOVQ SI, example.dest(SB)", Args: []Operand{{Kind: OpReg, Reg: SI}, {Kind: OpSym, Sym: "example.dest(SB)"}}}) + checkMov("MOVL", Instr{Raw: "MOVL $10, AX", Args: []Operand{{Kind: OpImm, Imm: 10}, {Kind: OpReg, Reg: AX}}}) + checkMov("MOVL", Instr{Raw: "MOVL BX, ret+8(FP)", Args: []Operand{{Kind: OpReg, Reg: BX}, {Kind: OpFP, FPOffset: 8}}}) + checkMov("MOVL", Instr{Raw: "MOVL arg+0(FP), 20(BX)", Args: []Operand{{Kind: OpFP, FPOffset: 0}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 20}}}}) + checkMov("MOVL", Instr{Raw: "MOVL 24(BX), example.dest(SB)", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 24}}, {Kind: OpSym, Sym: "example.dest(SB)"}}}) + checkMov("MOVL", Instr{Raw: "MOVL $0xf1, 0xf1", Args: []Operand{{Kind: OpImm, Imm: 0xf1}, {Kind: OpImm, Imm: 0xf1}}}) + + if ok, term, err := c.lowerMov("MOVQ", Instr{Raw: "MOVQ X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}); ok || term || err != nil { + t.Fatalf("lowerMov(vector) = (%v, %v, %v)", ok, term, err) + } + if _, _, err := c.lowerMov("MOVLQSX", Instr{Raw: "MOVLQSX AX, 8(BX)", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}}}); err == nil { + t.Fatalf("MOVLQSX non-reg dst unexpectedly succeeded") + } + if _, _, err := c.lowerMov("MOVB", Instr{Raw: "MOVB label, AX", Args: []Operand{{Kind: OpIdent, Ident: "label"}, {Kind: OpReg, Reg: AX}}}); err == nil { + t.Fatalf("MOVB bad src unexpectedly succeeded") + } + if _, _, err := c.lowerMov("MOVW", Instr{Raw: "MOVW AX, label", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpSym, Sym: "label"}}}); err == nil { + t.Fatalf("MOVW bad sym dst unexpectedly succeeded") + } + if _, _, err := c.lowerMov("MOVQ", Instr{Raw: "MOVQ AX, label", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpLabel, Sym: "label"}}}); err == nil { + t.Fatalf("MOVQ bad dst unexpectedly succeeded") + } + if _, _, err := c.lowerMov("MOVL", Instr{Raw: "MOVL label, AX", Args: []Operand{{Kind: OpIdent, Ident: "label"}, {Kind: OpReg, Reg: AX}}}); err == nil { + t.Fatalf("MOVL bad src unexpectedly succeeded") + } + if _, _, err := c.lowerMov("MOVQ", Instr{Raw: "MOVQ AX", Args: []Operand{{Kind: OpReg, Reg: AX}}}); err == nil { + t.Fatalf("MOVQ short arg list unexpectedly succeeded") + } + if ok, term, err := c.lowerMov("MOVQ", Instr{Raw: "MOVQ Y0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: AX}}}); ok || term || err != nil { + t.Fatalf("MOVQ yreg src = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := c.lowerMov("MOVQ", Instr{Raw: "MOVQ AX, Y1", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("Y1")}}}); ok || term || err != nil { + t.Fatalf("MOVQ yreg dst = (%v, %v, %v)", ok, term, err) + } + + cBadReg, _ := newAMD64CtxWithFuncForTest(t, Func{}, sig, nil) + if ok, term, err := cBadReg.lowerMov("MOVB", Instr{Raw: "MOVB BAD, ret+24(FP)", Args: []Operand{{Kind: OpReg, Reg: Reg("BAD")}, {Kind: OpFP, FPOffset: 24}}}); !ok || term || err != nil { + t.Fatalf("MOVB missing src reg = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := cBadReg.lowerMov("CMOVQLT", Instr{Raw: "CMOVQLT BAD, BX", Args: []Operand{{Kind: OpReg, Reg: Reg("BAD")}, {Kind: OpReg, Reg: BX}}}); !ok || term || err != nil { + t.Fatalf("CMOVQLT missing regs = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := cBadReg.lowerMov("MOVQ", Instr{Raw: "MOVQ BAD, 8(BX)", Args: []Operand{{Kind: OpReg, Reg: Reg("BAD")}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}}}); !ok || term || err != nil { + t.Fatalf("MOVQ missing mem base = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := cBadReg.lowerMov("MOVL", Instr{Raw: "MOVL BAD, ret+8(FP)", Args: []Operand{{Kind: OpReg, Reg: Reg("BAD")}, {Kind: OpFP, FPOffset: 8}}}); !ok || term || err != nil { + t.Fatalf("MOVL missing reg = (%v, %v, %v)", ok, term, err) + } + + cBadMem, _ := newAMD64CtxWithFuncForTest(t, Func{}, sig, nil) + if ok, term, err := cBadMem.lowerMov("MOVLQSX", Instr{Raw: "MOVLQSX 8(BAD), SI", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: Reg("BAD"), Off: 8}}, {Kind: OpReg, Reg: SI}}}); !ok || term || err != nil { + t.Fatalf("MOVLQSX bad mem = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := cBadMem.lowerMov("MOVW", Instr{Raw: "MOVW arg+0(FP), 8(BAD)", Args: []Operand{{Kind: OpFP, FPOffset: 0}, {Kind: OpMem, Mem: MemRef{Base: Reg("BAD"), Off: 8}}}}); !ok || term || err != nil { + t.Fatalf("MOVW bad dst mem = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := cBadMem.lowerMov("MOVQ", Instr{Raw: "MOVQ 8(BAD), AX", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: Reg("BAD"), Off: 8}}, {Kind: OpReg, Reg: AX}}}); !ok || term || err != nil { + t.Fatalf("MOVQ bad src mem = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := cBadMem.lowerMov("MOVL", Instr{Raw: "MOVL 24(BAD), example.dest(SB)", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: Reg("BAD"), Off: 24}}, {Kind: OpSym, Sym: "example.dest(SB)"}}}); !ok || term || err != nil { + t.Fatalf("MOVL bad src mem = (%v, %v, %v)", ok, term, err) + } + + if ok, term, err := c.lowerMov("MOVLQSX", Instr{Raw: "MOVLQSX broken, AX", Args: []Operand{{Kind: OpSym, Sym: "broken"}, {Kind: OpReg, Reg: AX}}}); !ok || term || err != nil { + t.Fatalf("MOVLQSX bad sym = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := c.lowerMov("MOVB", Instr{Raw: "MOVB broken, AX", Args: []Operand{{Kind: OpSym, Sym: "broken"}, {Kind: OpReg, Reg: AX}}}); !ok || term || err != nil { + t.Fatalf("MOVB bad sym = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := c.lowerMov("MOVQ", Instr{Raw: "MOVQ broken, AX", Args: []Operand{{Kind: OpSym, Sym: "broken"}, {Kind: OpReg, Reg: AX}}}); !ok || term || err != nil { + t.Fatalf("MOVQ bad sym src = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := c.lowerMov("MOVQ", Instr{Raw: "MOVQ AX, broken", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpSym, Sym: "broken"}}}); !ok || term || err != nil { + t.Fatalf("MOVQ bad sym dst = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := c.lowerMov("MOVL", Instr{Raw: "MOVL broken, AX", Args: []Operand{{Kind: OpSym, Sym: "broken"}, {Kind: OpReg, Reg: AX}}}); !ok || term || err != nil { + t.Fatalf("MOVL bad sym src = (%v, %v, %v)", ok, term, err) + } + if _, _, err := c.lowerMov("MOVL", Instr{Raw: "MOVL AX, label", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpLabel, Sym: "label"}}}); err == nil { + t.Fatalf("MOVL bad dst unexpectedly succeeded") + } + + sysc, sysb := newAMD64CtxWithFuncForTest(t, Func{}, FuncSig{Name: "example.sys", Ret: Void}, nil) + for _, tc := range []struct { + r Reg + v string + }{ + {AX, "1"}, + {DI, "2"}, + {SI, "3"}, + {DX, "4"}, + {Reg("R10"), "5"}, + {Reg("R8"), "6"}, + {Reg("R9"), "7"}, + {BX, "256"}, + } { + if err := sysc.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("sys storeReg(%s) error = %v", tc.r, err) + } + } + if ok, term, err := sysc.lowerSyscall("INT", Instr{Raw: "INT $3"}); !ok || !term || err != nil { + t.Fatalf("lowerSyscall(INT) = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := sysc.lowerSyscall("SYSCALL", Instr{Raw: "SYSCALL"}); !ok || term || err != nil { + t.Fatalf("lowerSyscall(SYSCALL) = (%v, %v, %v)", ok, term, err) + } + for _, tc := range []Instr{ + {Op: "CRC32B", Raw: "CRC32B 8(BX), AX", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}, {Kind: OpReg, Reg: AX}}}, + {Op: "CRC32W", Raw: "CRC32W 8(BX), AX", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}, {Kind: OpReg, Reg: AX}}}, + {Op: "CRC32L", Raw: "CRC32L 8(BX), AX", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}, {Kind: OpReg, Reg: AX}}}, + {Op: "CRC32Q", Raw: "CRC32Q 8(BX), AX", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}, {Kind: OpReg, Reg: AX}}}, + } { + if ok, term, err := sysc.lowerCrc32(tc.Op, tc); !ok || term || err != nil { + t.Fatalf("lowerCrc32(%s) = (%v, %v, %v)", tc.Op, ok, term, err) + } + } + if _, _, err := sysc.lowerSyscall("SYSCALL", Instr{Raw: "SYSCALL AX", Args: []Operand{{Kind: OpReg, Reg: AX}}}); err == nil { + t.Fatalf("SYSCALL with args unexpectedly succeeded") + } + if ok, term, err := sysc.lowerSyscall("BAD", Instr{}); ok || term || err != nil { + t.Fatalf("lowerSyscall(BAD) = (%v, %v, %v)", ok, term, err) + } + if _, _, err := sysc.lowerCrc32("CRC32B", Instr{Raw: "CRC32B AX, BX", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: BX}}}); err == nil { + t.Fatalf("bad CRC32B unexpectedly succeeded") + } + if ok, term, err := sysc.lowerCrc32("BAD", Instr{}); ok || term || err != nil { + t.Fatalf("lowerCrc32(BAD) = (%v, %v, %v)", ok, term, err) + } + + out := b.String() + sysb.String() + for _, want := range []string{ + "sext i32", + "select i1 %t", + "zext i8", + "store i16", + "store i32", + "@syscall(i64", + "@cliteErrno()", + "@llvm.x86.sse42.crc32.32.8", + "@llvm.x86.sse42.crc32.32.16", + "@llvm.x86.sse42.crc32.32.32", + "@llvm.x86.sse42.crc32.64.64", + "unreachable", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestAMD64BranchCoverageDeep(t *testing.T) { + fn := Func{Instrs: []Instr{{Op: "NOP"}, {Op: "NOP"}, {Op: "NOP"}, {Op: "NOP"}}} + sigs := map[string]FuncSig{ + "example.tail": {Name: "example.tail", Args: []LLVMType{I64}, Ret: I64}, + } + c, b := newAMD64CtxWithFuncForTest(t, fn, FuncSig{Name: "example.branch", Args: []LLVMType{I64}, Ret: I64}, sigs) + for _, tc := range []struct { + r Reg + v string + }{ + {AX, "31"}, + {BX, "32"}, + {CX, "33"}, + {DX, "34"}, + {DI, "35"}, + {SI, "36"}, + {Reg("R8"), "37"}, + {Reg("R9"), "38"}, + } { + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + c.blocks = []amd64Block{{name: "entry"}, {name: "fall"}, {name: "V1"}, {name: "tail"}} + c.blockBase = []int{0, 1, 2, 3} + c.blockByIdx = map[int]int{0: 0, 1: 1, 2: 2, 3: 3} + c.setCmpFlags("1", "2") + + emitBr := func(target string) { b.WriteString(" br label %" + amd64LLVMBlockName(target) + "\n") } + emitCondBr := func(cond string, target string, fall string) error { + b.WriteString(" br i1 " + cond + ", label %" + amd64LLVMBlockName(target) + ", label %" + amd64LLVMBlockName(fall) + "\n") + return nil + } + for _, tc := range []struct { + op Op + ins Instr + }{ + {"JE", Instr{Raw: "JE V1", Args: []Operand{{Kind: OpIdent, Ident: "V1"}}}}, + {"JNE", Instr{Raw: "JNE tail", Args: []Operand{{Kind: OpIdent, Ident: "tail"}}}}, + {"JL", Instr{Raw: "JL V1<>(SB)", Args: []Operand{{Kind: OpSym, Sym: "V1<>(SB)"}}}}, + {"JGE", Instr{Raw: "JGE 2(PC)", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: PC, Off: 2}}}}}, + {"JLE", Instr{Raw: "JLE tail", Args: []Operand{{Kind: OpIdent, Ident: "tail"}}}}, + {"JG", Instr{Raw: "JG V1", Args: []Operand{{Kind: OpIdent, Ident: "V1"}}}}, + {"JB", Instr{Raw: "JB tail", Args: []Operand{{Kind: OpIdent, Ident: "tail"}}}}, + {"JNC", Instr{Raw: "JNC V1", Args: []Operand{{Kind: OpIdent, Ident: "V1"}}}}, + {"JAE", Instr{Raw: "JAE tail", Args: []Operand{{Kind: OpIdent, Ident: "tail"}}}}, + {"JBE", Instr{Raw: "JBE V1", Args: []Operand{{Kind: OpIdent, Ident: "V1"}}}}, + {"JA", Instr{Raw: "JA tail", Args: []Operand{{Kind: OpIdent, Ident: "tail"}}}}, + {"JMP", Instr{Raw: "JMP V1", Args: []Operand{{Kind: OpReg, Reg: Reg("V1")}}}}, + {"JMP", Instr{Raw: "JMP tail(SB)", Args: []Operand{{Kind: OpSym, Sym: "tail(SB)"}}}}, + } { + if ok, term, err := c.lowerBranch(0, 0, tc.op, tc.ins, emitBr, emitCondBr); !ok || !term || err != nil { + t.Fatalf("lowerBranch(%s %q) = (%v, %v, %v)", tc.op, tc.ins.Raw, ok, term, err) + } + } + if _, _, err := c.lowerBranch(0, 0, "CALL", Instr{Raw: "CALL $1", Args: []Operand{{Kind: OpImm, Imm: 1}}}, emitBr, emitCondBr); err == nil { + t.Fatalf("CALL imm unexpectedly succeeded") + } + if _, _, err := c.lowerBranch(0, 0, "JMP", Instr{Raw: "JMP $1", Args: []Operand{{Kind: OpImm, Imm: 1}}}, emitBr, emitCondBr); err == nil { + t.Fatalf("JMP imm unexpectedly succeeded") + } + if _, _, err := c.lowerBranch(0, 0, "JEQ", Instr{Raw: "JEQ", Args: nil}, emitBr, emitCondBr); err == nil { + t.Fatalf("JEQ without target unexpectedly succeeded") + } + oneBlock, _ := newAMD64CtxWithFuncForTest(t, Func{}, FuncSig{Name: "example.one", Ret: Void}, nil) + oneBlock.blocks = []amd64Block{{name: "solo"}} + oneBlock.blockBase = []int{0} + oneBlock.blockByIdx = map[int]int{0: 0} + oneBlock.setCmpFlags("1", "1") + if _, _, err := oneBlock.lowerBranch(0, 0, "JEQ", Instr{Raw: "JEQ solo", Args: []Operand{{Kind: OpIdent, Ident: "solo"}}}, emitBr, emitCondBr); err == nil { + t.Fatalf("JEQ without fallthrough unexpectedly succeeded") + } + + out := b.String() + for _, want := range []string{ + "xor i1", + "or i1", + "call i64 @\"example.tail\"", + "ret i64", + "br label %V1", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestAMD64FPExtraCoverage(t *testing.T) { + fn := Func{ + Instrs: []Instr{ + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}}}, + }, + } + sig := FuncSig{ + Name: "example.fpextra", + Args: []LLVMType{I64, LLVMType("double")}, + Ret: Void, + Frame: FrameLayout{ + Params: []FrameSlot{ + {Offset: 0, Type: I64, Index: 0}, + {Offset: 8, Type: LLVMType("double"), Index: 1}, + }, + }, + } + c, b := newAMD64CtxWithFuncForTest(t, fn, sig, nil) + for _, tc := range []struct { + r Reg + v string + }{ + {AX, "51"}, + {BX, "52"}, + {Reg("X0"), "<16 x i8> zeroinitializer"}, + {Reg("X1"), "<16 x i8> zeroinitializer"}, + {Reg("Y0"), "<32 x i8> zeroinitializer"}, + {Reg("Y1"), "<32 x i8> zeroinitializer"}, + {Reg("Z0"), "<64 x i8> zeroinitializer"}, + {Reg("Z1"), "<64 x i8> zeroinitializer"}, + } { + switch { + case strings.HasPrefix(string(tc.r), "X"): + if err := c.storeX(tc.r, tc.v); err != nil { + t.Fatalf("storeX(%s) error = %v", tc.r, err) + } + case strings.HasPrefix(string(tc.r), "Y"): + if err := c.storeY(tc.r, tc.v); err != nil { + t.Fatalf("storeY(%s) error = %v", tc.r, err) + } + case strings.HasPrefix(string(tc.r), "Z"): + if err := c.storeZ(tc.r, tc.v); err != nil { + t.Fatalf("storeZ(%s) error = %v", tc.r, err) + } + default: + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + } + + if got, err := c.loadYVecOperand(Operand{Kind: OpReg, Reg: Reg("Y0")}); err != nil || got == "" { + t.Fatalf("loadYVecOperand(reg) = (%q, %v)", got, err) + } + if got, err := c.loadYVecOperand(Operand{Kind: OpMem, Mem: MemRef{Base: AX, Off: 8}}); err != nil || got == "" { + t.Fatalf("loadYVecOperand(mem) = (%q, %v)", got, err) + } + if got, err := c.loadYVecOperand(Operand{Kind: OpSym, Sym: "example.vec32(SB)"}); err != nil || got == "" { + t.Fatalf("loadYVecOperand(sym) = (%q, %v)", got, err) + } + if got, err := c.loadZVecOperand(Operand{Kind: OpReg, Reg: Reg("Z0")}); err != nil || got == "" { + t.Fatalf("loadZVecOperand(reg) = (%q, %v)", got, err) + } + if got, err := c.loadZVecOperand(Operand{Kind: OpMem, Mem: MemRef{Base: AX, Off: 16}}); err != nil || got == "" { + t.Fatalf("loadZVecOperand(mem) = (%q, %v)", got, err) + } + if got, err := c.loadZVecOperand(Operand{Kind: OpSym, Sym: "example.vec64(SB)"}); err != nil || got == "" { + t.Fatalf("loadZVecOperand(sym) = (%q, %v)", got, err) + } + + check := func(op Op, ins Instr) { + t.Helper() + if ok, term, err := c.lowerFP(op, ins); !ok || term || err != nil { + t.Fatalf("lowerFP(%s %q) = (%v, %v, %v)", op, ins.Raw, ok, term, err) + } + } + for _, pred := range []int64{0, 1, 2, 3, 4, 6} { + check("CMPSD", Instr{Raw: "CMPSD X0, X1, $pred", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}, {Kind: OpImm, Imm: pred}}}) + } + check("VADDSD", Instr{Raw: "VADDSD X0, X1, X0", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}, {Kind: OpReg, Reg: Reg("X0")}}}) + check("VFMADD213SD", Instr{Raw: "VFMADD213SD X0, X1, X0", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}, {Kind: OpReg, Reg: Reg("X0")}}}) + check("VFNMADD231SD", Instr{Raw: "VFNMADD231SD X0, X1, X0", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}, {Kind: OpReg, Reg: Reg("X0")}}}) + + if ok, term, err := c.lowerFP("MOVAPD", Instr{Raw: "MOVAPD X0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: AX}}}); ok || term || err != nil { + t.Fatalf("MOVAPD non-X dst = (%v, %v, %v)", ok, term, err) + } + if _, _, err := c.lowerFP("MOVSD", Instr{Raw: "MOVSD X0, label", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpIdent, Ident: "label"}}}); err == nil { + t.Fatalf("MOVSD bad dst unexpectedly succeeded") + } + if ok, term, err := c.lowerFP("ADDSD", Instr{Raw: "ADDSD X0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: AX}}}); ok || term || err != nil { + t.Fatalf("ADDSD non-X dst = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := c.lowerFP("SQRTSD", Instr{Raw: "SQRTSD X0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: AX}}}); ok || term || err != nil { + t.Fatalf("SQRTSD non-X dst = (%v, %v, %v)", ok, term, err) + } + if _, _, err := c.lowerFP("COMISD", Instr{Raw: "COMISD X0", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}}}); err == nil { + t.Fatalf("COMISD short form unexpectedly succeeded") + } + if _, _, err := c.lowerFP("CMPSD", Instr{Raw: "CMPSD X0, X1, bad", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}, {Kind: OpSym, Sym: "bad"}}}); err == nil { + t.Fatalf("CMPSD bad sym predicate unexpectedly succeeded") + } + if _, _, err := c.lowerFP("CMPSD", Instr{Raw: "CMPSD X0, X1, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}, {Kind: OpReg, Reg: AX}}}); err == nil { + t.Fatalf("CMPSD reg predicate unexpectedly succeeded") + } + if ok, term, err := c.lowerFP("CVTSQ2SD", Instr{Raw: "CVTSQ2SD AX, AX", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: AX}}}); ok || term || err != nil { + t.Fatalf("CVTSQ2SD non-X dst = (%v, %v, %v)", ok, term, err) + } + if ok, term, err := c.lowerFP("CVTSL2SD", Instr{Raw: "CVTSL2SD AX, AX", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: AX}}}); ok || term || err != nil { + t.Fatalf("CVTSL2SD non-X dst = (%v, %v, %v)", ok, term, err) + } + + out := b.String() + for _, want := range []string{ + "load <32 x i8>", + "load <64 x i8>", + "fcmp oeq double", + "fcmp olt double", + "fcmp ole double", + "fcmp uno double", + "fcmp une double", + "fcmp ugt double", + "fadd double", + "fmul double", + "fneg double", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestAMD64VectorAliasAndErrorCoverage(t *testing.T) { + fn := Func{ + Instrs: []Instr{ + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("X2")}, {Kind: OpReg, Reg: Reg("X3")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("Y2")}, {Kind: OpReg, Reg: Reg("Y3")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("Z2")}, {Kind: OpReg, Reg: Reg("Z3")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("K1")}, {Kind: OpReg, Reg: Reg("K2")}}}, + {Op: "MOVQ", Args: []Operand{{Kind: OpReg, Reg: Reg("K3")}, {Kind: OpReg, Reg: Reg("K4")}}}, + }, + } + sig := FuncSig{ + Name: "example.vecalias", + Args: []LLVMType{I64}, + Ret: Void, + Frame: FrameLayout{ + Params: []FrameSlot{{Offset: 0, Type: I64, Index: 0}}, + }, + } + c, b := newAMD64CtxWithFuncForTest(t, fn, sig, nil) + for _, tc := range []struct { + r Reg + v string + }{ + {AX, "61"}, + {BX, "62"}, + {CX, "63"}, + {DX, "64"}, + {Reg("K1"), "1"}, + {Reg("K2"), "2"}, + } { + if strings.HasPrefix(string(tc.r), "K") { + if err := c.storeK(tc.r, tc.v); err != nil { + t.Fatalf("storeK(%s) error = %v", tc.r, err) + } + continue + } + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + + check := func(op Op, ins Instr) { + t.Helper() + if ok, term, err := c.lowerVec(op, ins); !ok || term || err != nil { + t.Fatalf("lowerVec(%s %q) = (%v, %v, %v)", op, ins.Raw, ok, term, err) + } + } + + check("VZEROUPPER", Instr{Raw: "VZEROUPPER"}) + check("VZEROALL", Instr{Raw: "VZEROALL"}) + check("VMOVDQA", Instr{Raw: "VMOVDQA Y0, Y1", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}) + check("VMOVDQA64", Instr{Raw: "VMOVDQA64 Z0, Z1", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}}}) + check("VPERM2F128", Instr{Raw: "VPERM2F128 $1, Y0, Y1, Y2", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("Y2")}}}) + check("VMOVAPS", Instr{Raw: "VMOVAPS Z0, Z1", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}}}) + check("VMOVAPD", Instr{Raw: "VMOVAPD Y0, Y1", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}) + check("VPXORQ", Instr{Raw: "VPXORQ Y0, Y1, Y2", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("Y2")}}}) + check("VPANDQ", Instr{Raw: "VPANDQ Y0, Y1, Y2", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("Y2")}}}) + check("VPORQ", Instr{Raw: "VPORQ Y0, Y1, Y2", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("Y2")}}}) + check("MOVUPS", Instr{Raw: "MOVUPS 8(BX), X0", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}, {Kind: OpReg, Reg: Reg("X0")}}}) + check("MOVAPS", Instr{Raw: "MOVAPS X0, 16(BX)", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 16}}}}) + check("MOVO", Instr{Raw: "MOVO X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}) + check("MOVQ", Instr{Raw: "MOVQ arg+0(FP), X0", Args: []Operand{{Kind: OpFP, FPOffset: 0}, {Kind: OpReg, Reg: Reg("X0")}}}) + check("MOVQ", Instr{Raw: "MOVQ 24(BX), X1", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 24}}, {Kind: OpReg, Reg: Reg("X1")}}}) + check("MOVQ", Instr{Raw: "MOVQ example.global(SB), X2", Args: []Operand{{Kind: OpSym, Sym: "example.global(SB)"}, {Kind: OpReg, Reg: Reg("X2")}}}) + check("MOVQ", Instr{Raw: "MOVQ X0, 32(BX)", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 32}}}}) + check("MOVOU", Instr{Raw: "MOVOU example.global(SB), X0", Args: []Operand{{Kind: OpSym, Sym: "example.global(SB)"}, {Kind: OpReg, Reg: Reg("X0")}}}) + check("MOVOA", Instr{Raw: "MOVOA X0, example.out(SB)", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpSym, Sym: "example.out(SB)"}}}) + check("KMOVB", Instr{Raw: "KMOVB 8(BX), K1", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}, {Kind: OpReg, Reg: Reg("K1")}}}) + check("KMOVQ", Instr{Raw: "KMOVQ 8(BX), AX", Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}, {Kind: OpReg, Reg: AX}}}) + check("VMOVDQU64", Instr{Raw: "VMOVDQU64 example.zin(SB), Z0", Args: []Operand{{Kind: OpSym, Sym: "example.zin(SB)"}, {Kind: OpReg, Reg: Reg("Z0")}}}) + check("VMOVDQU64", Instr{Raw: "VMOVDQU64 Z0, example.zout(SB)", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpSym, Sym: "example.zout(SB)"}}}) + check("VMOVDQU", Instr{Raw: "VMOVDQU example.yin(SB), Y0", Args: []Operand{{Kind: OpSym, Sym: "example.yin(SB)"}, {Kind: OpReg, Reg: Reg("Y0")}}}) + check("VMOVDQU", Instr{Raw: "VMOVDQU Y0, example.yout(SB)", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpSym, Sym: "example.yout(SB)"}}}) + check("VMOVDQU", Instr{Raw: "VMOVDQU example.xin(SB), X0", Args: []Operand{{Kind: OpSym, Sym: "example.xin(SB)"}, {Kind: OpReg, Reg: Reg("X0")}}}) + check("VMOVDQU", Instr{Raw: "VMOVDQU X0, example.xout(SB)", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpSym, Sym: "example.xout(SB)"}}}) + check("PEXTRB", Instr{Raw: "PEXTRB $1, X1, example.byteout(SB)", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X1")}, {Kind: OpSym, Sym: "example.byteout(SB)"}}}) + + if _, _, err := c.lowerVec("MOVL", Instr{Raw: "MOVL label, X0", Args: []Operand{{Kind: OpIdent, Ident: "label"}, {Kind: OpReg, Reg: Reg("X0")}}}); err == nil { + t.Fatalf("MOVL bad src unexpectedly succeeded") + } + if _, _, err := c.lowerVec("MOVQ", Instr{Raw: "MOVQ label, X0", Args: []Operand{{Kind: OpIdent, Ident: "label"}, {Kind: OpReg, Reg: Reg("X0")}}}); err == nil { + t.Fatalf("MOVQ bad src unexpectedly succeeded") + } + if _, _, err := c.lowerVec("MOVQ", Instr{Raw: "MOVQ X0, label", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpIdent, Ident: "label"}}}); err == nil { + t.Fatalf("MOVQ bad dst unexpectedly succeeded") + } + if ok, term, err := c.lowerVec("KXORQ", Instr{Raw: "KXORQ AX, K1, K2", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("K1")}, {Kind: OpReg, Reg: Reg("K2")}}}); ok || term || err != nil { + t.Fatalf("KXORQ non-k src = (%v, %v, %v)", ok, term, err) + } + if _, _, err := c.lowerVec("KMOVB", Instr{Raw: "KMOVB AX", Args: []Operand{{Kind: OpReg, Reg: AX}}}); err == nil { + t.Fatalf("KMOVB short form unexpectedly succeeded") + } + if _, _, err := c.lowerVec("KMOVB", Instr{Raw: "KMOVB label, AX", Args: []Operand{{Kind: OpIdent, Ident: "label"}, {Kind: OpReg, Reg: AX}}}); err == nil { + t.Fatalf("KMOVB bad src unexpectedly succeeded") + } + if _, _, err := c.lowerVec("KMOVB", Instr{Raw: "KMOVB AX, label", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpIdent, Ident: "label"}}}); err == nil { + t.Fatalf("KMOVB bad dst unexpectedly succeeded") + } + if _, _, err := c.lowerVec("VPERMB", Instr{Raw: "VPERMB Z0", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}}}); err == nil { + t.Fatalf("VPERMB short form unexpectedly succeeded") + } + if _, _, err := c.lowerVec("VPERMB", Instr{Raw: "VPERMB Z0, Z1, 7", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpImm, Imm: 7}}}); err == nil { + t.Fatalf("VPERMB non-reg dst unexpectedly succeeded") + } + if ok, term, err := c.lowerVec("VPERMB", Instr{Raw: "VPERMB Z0, Z1, Y0", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Y0")}}}); ok || term || err != nil { + t.Fatalf("VPERMB wrong dst reg = (%v, %v, %v)", ok, term, err) + } + if _, _, err := c.lowerVec("VPERMB", Instr{Raw: "VPERMB Z0, Z1, 7, Z2", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpImm, Imm: 7}, {Kind: OpReg, Reg: Reg("Z2")}}}); err == nil { + t.Fatalf("VPERMB non-reg mask unexpectedly succeeded") + } + if ok, term, err := c.lowerVec("VPERMB", Instr{Raw: "VPERMB Z0, Z1, AX, Z2", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("Z2")}}}); ok || term || err != nil { + t.Fatalf("VPERMB wrong mask reg = (%v, %v, %v)", ok, term, err) + } + if _, _, err := c.lowerVec("VGF2P8AFFINEQB", Instr{Raw: "VGF2P8AFFINEQB Z0, Z1, Z2", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Z2")}}}); err == nil { + t.Fatalf("VGF2P8AFFINEQB short form unexpectedly succeeded") + } + if ok, term, err := c.lowerVec("VGF2P8AFFINEQB", Instr{Raw: "VGF2P8AFFINEQB $1, Z0, Z1, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Y0")}}}); ok || term || err != nil { + t.Fatalf("VGF2P8AFFINEQB wrong dst reg = (%v, %v, %v)", ok, term, err) + } + if _, _, err := c.lowerVec("VPERMI2B", Instr{Raw: "VPERMI2B Z0", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}}}); err == nil { + t.Fatalf("VPERMI2B short form unexpectedly succeeded") + } + if _, _, err := c.lowerVec("VPERMI2B", Instr{Raw: "VPERMI2B Z0, Z1, 7", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpImm, Imm: 7}}}); err == nil { + t.Fatalf("VPERMI2B non-reg dst unexpectedly succeeded") + } + if ok, term, err := c.lowerVec("VPERMI2B", Instr{Raw: "VPERMI2B Z0, Z1, Y0", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Y0")}}}); ok || term || err != nil { + t.Fatalf("VPERMI2B wrong dst reg = (%v, %v, %v)", ok, term, err) + } + if _, _, err := c.lowerVec("VPERMI2B", Instr{Raw: "VPERMI2B Z0, Z1, 7, Z2", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpImm, Imm: 7}, {Kind: OpReg, Reg: Reg("Z2")}}}); err == nil { + t.Fatalf("VPERMI2B non-reg mask unexpectedly succeeded") + } + if ok, term, err := c.lowerVec("VPERMI2B", Instr{Raw: "VPERMI2B Z0, Z1, AX, Z2", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("Z2")}}}); ok || term || err != nil { + t.Fatalf("VPERMI2B wrong mask reg = (%v, %v, %v)", ok, term, err) + } + for _, tc := range []struct { + op Op + ins Instr + want string + }{ + {"VPOPCNTB", Instr{Raw: "VPOPCNTB Z0, Y0", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst"}, + {"VPCMPUQ", Instr{Raw: "VPCMPUQ $3, Z0, Z1, K1", Args: []Operand{{Kind: OpImm, Imm: 3}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("K1")}}}, "bad imm"}, + {"VPCMPUQ", Instr{Raw: "VPCMPUQ $1, Z0, Z1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: AX}}}, "wrong dst reg"}, + {"VPCOMPRESSQ", Instr{Raw: "VPCOMPRESSQ Z0, AX, Z1", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("Z1")}}}, "wrong mask"}, + {"VPCOMPRESSQ", Instr{Raw: "VPCOMPRESSQ Z0, K1, Y0", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("K1")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst"}, + {"VPXORQ", Instr{Raw: "VPXORQ Z0, Z1, Y0", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong z dst"}, + {"VPSHUFB", Instr{Raw: "VPSHUFB Y0, Y1, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: AX}}}, "wrong dst"}, + {"VPSHUFD", Instr{Raw: "VPSHUFD $1, X0, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong src class"}, + {"VPSLLD", Instr{Raw: "VPSLLD Y0, Y1", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}, "short form"}, + {"VPERM2I128", Instr{Raw: "VPERM2I128 $1, Y0, Y1, X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong dst"}, + {"VINSERTI128", Instr{Raw: "VINSERTI128 $1, X0, Y0, X1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X1")}}}, "wrong dst"}, + {"VMOVNTDQ", Instr{Raw: "VMOVNTDQ Y0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: AX}}}, "bad dst"}, + {"AESKEYGENASSIST", Instr{Raw: "AESKEYGENASSIST X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}, "missing imm"}, + {"VPTEST", Instr{Raw: "VPTEST X0, Y0", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong src class"}, + {"PCMPESTRI", Instr{Raw: "PCMPESTRI $1, 8(BX), X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}, {Kind: OpReg, Reg: Reg("X0")}}}, "unsupported imm"}, + {"PCMPESTRI", Instr{Raw: "PCMPESTRI $0x0c, AX, X0", Args: []Operand{{Kind: OpImm, Imm: 0x0c}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}}}, "bad mem operand"}, + {"VPBLENDD", Instr{Raw: "VPBLENDD $1, Y0, Y1, X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong dst"}, + {"VPBROADCASTB", Instr{Raw: "VPBROADCASTB Y0, Y1", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}, "wrong src"}, + {"VPSRLDQ", Instr{Raw: "VPSRLDQ $2, X0, Y0", Args: []Operand{{Kind: OpImm, Imm: 2}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong src class"}, + {"PUNPCKLBW", Instr{Raw: "PUNPCKLBW Y0, X0", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong src class"}, + {"PSHUFHW", Instr{Raw: "PSHUFHW $1, X0, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst"}, + {"SHUFPS", Instr{Raw: "SHUFPS $1, X0, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst"}, + {"MOVOU", Instr{Raw: "MOVOU AX, X0", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong src class"}, + {"MOVOU", Instr{Raw: "MOVOU X0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: AX}}}, "wrong dst class"}, + {"PXOR", Instr{Raw: "PXOR X0", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}}}, "short form"}, + {"PADDL", Instr{Raw: "PADDL X0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: AX}}}, "wrong dst class"}, + {"PSLLL", Instr{Raw: "PSLLL AX, X0", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}}}, "non-imm shift"}, + {"PCMPEQL", Instr{Raw: "PCMPEQL Y0, X0", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong src class"}, + {"VPCLMULQDQ", Instr{Raw: "VPCLMULQDQ $1, Z0, Z1, X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong dst class"}, + {"VPTERNLOGD", Instr{Raw: "VPTERNLOGD $0x95, Z0, Z1, Z2", Args: []Operand{{Kind: OpImm, Imm: 0x95}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Z2")}}}, "unsupported imm"}, + {"VEXTRACTF32X4", Instr{Raw: "VEXTRACTF32X4 $1, X0, X1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}, "wrong src class"}, + {"PCLMULQDQ", Instr{Raw: "PCLMULQDQ AX, X0, X1", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}, "missing imm"}, + {"PCMPEQB", Instr{Raw: "PCMPEQB Y0, X0", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong src class"}, + {"PMOVMSKB", Instr{Raw: "PMOVMSKB Y0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: AX}}}, "wrong src class"}, + {"PSHUFB", Instr{Raw: "PSHUFB Y0, X0", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong mask class"}, + {"PINSRQ", Instr{Raw: "PINSRQ AX, X0", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}}}, "short form"}, + {"PINSRD", Instr{Raw: "PINSRD $1, AX, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst class"}, + {"PINSRW", Instr{Raw: "PINSRW $1, AX, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst class"}, + {"PINSRB", Instr{Raw: "PINSRB $1, AX, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst class"}, + {"PEXTRB", Instr{Raw: "PEXTRB AX, X0, BX", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: BX}}}, "missing imm"}, + {"PALIGNR", Instr{Raw: "PALIGNR $1, Y0, X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong src class"}, + {"PSRLDQ", Instr{Raw: "PSRLDQ $17, X0", Args: []Operand{{Kind: OpImm, Imm: 17}, {Kind: OpReg, Reg: Reg("X0")}}}, "invalid imm"}, + {"PSLLDQ", Instr{Raw: "PSLLDQ $17, X0", Args: []Operand{{Kind: OpImm, Imm: 17}, {Kind: OpReg, Reg: Reg("X0")}}}, "invalid imm"}, + {"PSRLQ", Instr{Raw: "PSRLQ AX, X0", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}}}, "non-imm shift"}, + {"PEXTRD", Instr{Raw: "PEXTRD $1, X0, label", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpIdent, Ident: "label"}}}, "bad dst kind"}, + } { + ok, term, err := c.lowerVec(tc.op, tc.ins) + if ok && err == nil { + t.Fatalf("%s %s unexpectedly succeeded (term=%v)", tc.op, tc.want, term) + } + } + + out := b.String() + for _, want := range []string{ + "load i64, ptr @\"example.global\"", + "load i8, ptr", + "and i64", + "store i64", + "zext i8", + "shufflevector <4 x i64>", + "load <64 x i8>", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestAMD64CtxAliasAndFPFallbackCoverage(t *testing.T) { + sig := FuncSig{ + Name: "example.ctxfallback", + Ret: Void, + Frame: FrameLayout{ + Results: []FrameSlot{ + {Offset: 8, Type: I1, Index: 0}, + {Offset: 16, Type: I8, Index: 1}, + {Offset: 24, Type: I16, Index: 2}, + {Offset: 32, Type: I32, Index: 3}, + {Offset: 40, Type: I64, Index: 4}, + {Offset: 48, Type: Ptr, Index: 5}, + }, + }, + } + c, b := newAMD64CtxWithFuncForTest(t, Func{}, sig, nil) + + for _, tc := range []struct { + r Reg + v string + }{ + {AX, "255"}, + {BX, "511"}, + {CX, "1023"}, + {DX, "2047"}, + } { + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + for _, r := range []Reg{AL, BL, CL, DL} { + if got, err := c.loadReg(r); err != nil || got == "" { + t.Fatalf("loadReg(%s) = (%q, %v)", r, got, err) + } + } + if got, err := c.loadReg(Reg("MISSING")); err != nil || got != "0" { + t.Fatalf("loadReg(MISSING) = (%q, %v)", got, err) + } + for _, tc := range []struct { + r Reg + v string + }{ + {AL, "17"}, + {BL, "18"}, + {CL, "19"}, + {DL, "20"}, + } { + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + if err := c.storeReg(Reg("MISSING"), "21"); err != nil { + t.Fatalf("storeReg(MISSING) error = %v", err) + } + + for _, tc := range []struct { + off int64 + ty LLVMType + val string + }{ + {8, I1, "1"}, + {16, I8, "2"}, + {24, I16, "3"}, + {32, I32, "4"}, + {40, I64, "5"}, + {48, I64, "6"}, + } { + if err := c.storeFPResult(tc.off, tc.ty, tc.val); err != nil { + t.Fatalf("storeFPResult(%d, %s) error = %v", tc.off, tc.ty, err) + } + } + for _, tc := range []struct { + off int64 + ty LLVMType + val string + }{ + {8, I64, "7"}, + {32, I16, "8"}, + {40, LLVMType("double"), "%dbl"}, + } { + if err := c.storeFPResult(tc.off, tc.ty, tc.val); err != nil { + t.Fatalf("storeFPResult(extra %d, %s) error = %v", tc.off, tc.ty, err) + } + } + if err := c.storeFPResult(40, Ptr, "%arg0"); err != nil { + t.Fatalf("storeFPResult(ptr->i64) error = %v", err) + } + for _, off := range []int64{8, 16, 24, 32, 40, 48, 56} { + if got, err := c.evalFPToI64(off); err != nil || got == "" { + t.Fatalf("evalFPToI64(%d) = (%q, %v)", off, got, err) + } + } + + out := b.String() + for _, want := range []string{ + "and i64", + "zext i1", + "zext i8", + "zext i16", + "zext i32", + "ptrtoint ptr", + "inttoptr i64", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in ctx fallback output:\n%s", want, out) + } + } +} diff --git a/arm64_helper_edge_test.go b/arm64_helper_edge_test.go new file mode 100644 index 0000000..2444655 --- /dev/null +++ b/arm64_helper_edge_test.go @@ -0,0 +1,1699 @@ +package plan9asm + +import ( + "fmt" + "strings" + "testing" +) + +func newARM64CtxWithFuncForTest(t *testing.T, fn Func, sig FuncSig, sigs map[string]FuncSig) (*arm64Ctx, *strings.Builder) { + t.Helper() + if sig.Name == "" { + sig.Name = "example.f" + } + if sigs == nil { + sigs = map[string]FuncSig{} + } + var b strings.Builder + c := newARM64Ctx(&b, fn, sig, func(sym string) string { + sym = goStripABISuffix(sym) + sym = strings.ReplaceAll(sym, "∕", "/") + if strings.HasPrefix(sym, "runtime·") { + return strings.ReplaceAll(sym, "·", ".") + } + if strings.HasPrefix(sym, "·") { + return "example." + strings.TrimPrefix(sym, "·") + } + if !strings.Contains(sym, "·") && !strings.Contains(sym, ".") && !strings.Contains(sym, "/") { + return "example." + sym + } + return strings.ReplaceAll(sym, "·", ".") + }, sigs, false) + if err := c.emitEntryAllocasAndArgInit(); err != nil { + t.Fatalf("emitEntryAllocasAndArgInit() error = %v", err) + } + return c, &b +} + +func arm64RegOp(r Reg) Operand { return Operand{Kind: OpReg, Reg: r} } +func arm64ImmOp(v int64) Operand { return Operand{Kind: OpImm, Imm: v} } +func arm64IdentOp(s string) Operand { return Operand{Kind: OpIdent, Ident: s} } +func arm64SymOp(s string) Operand { return Operand{Kind: OpSym, Sym: s} } +func arm64FPOp(off int64) Operand { return Operand{Kind: OpFP, FPOffset: off} } +func arm64MemOp(base Reg, off int64) Operand { + return Operand{Kind: OpMem, Mem: MemRef{Base: base, Off: off}} +} +func arm64RegListOp(regs ...Reg) Operand { return Operand{Kind: OpRegList, RegList: regs} } + +func mustLowerARM64(t *testing.T, kind string, ins Instr, ok bool, err error) { + t.Helper() + if err != nil { + t.Fatalf("%s %q error = %v", kind, ins.Raw, err) + } + if !ok { + t.Fatalf("%s %q returned ok=false", kind, ins.Raw) + } +} + +func arm64TestEmitBr(c *arm64Ctx) arm64EmitBr { + return func(target string) { + fmt.Fprintf(c.b, " br label %%%s\n", arm64LLVMBlockName(target)) + } +} + +func arm64TestEmitCondBr(c *arm64Ctx) arm64EmitCondBr { + return func(cond string, target string, fall string) error { + cv, err := c.condValue(cond) + if err != nil { + return err + } + fmt.Fprintf(c.b, " br i1 %s, label %%%s, label %%%s\n", cv, arm64LLVMBlockName(target), arm64LLVMBlockName(fall)) + return nil + } +} + +func TestARM64AtomicCoverage(t *testing.T) { + c, b := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{Name: "example.atomic", Ret: Void}, nil) + for _, tc := range []struct { + r Reg + v string + }{ + {"R0", "1"}, + {"R1", "2"}, + {"R2", "3"}, + {"R3", "4"}, + {"R4", "5"}, + {"R5", "6"}, + {"R6", "7"}, + {"R7", "8"}, + {"R8", "9"}, + {"R9", "10"}, + {"R10", "1024"}, + } { + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + + mem := arm64MemOp("R10", 32) + for _, tc := range []Instr{ + {Op: "LDARW", Args: []Operand{mem, arm64RegOp("R0")}, Raw: "LDARW 32(R10), R0"}, + {Op: "LDARB", Args: []Operand{mem, arm64RegOp("R1")}, Raw: "LDARB 32(R10), R1"}, + {Op: "LDAR", Args: []Operand{mem, arm64RegOp("R2")}, Raw: "LDAR 32(R10), R2"}, + {Op: "LDAXRW", Args: []Operand{mem, arm64RegOp("R3")}, Raw: "LDAXRW 32(R10), R3"}, + {Op: "LDAXRB", Args: []Operand{mem, arm64RegOp("R4")}, Raw: "LDAXRB 32(R10), R4"}, + {Op: "LDAXR", Args: []Operand{mem, arm64RegOp("R5")}, Raw: "LDAXR 32(R10), R5"}, + {Op: "STLRW", Args: []Operand{arm64RegOp("R0"), mem}, Raw: "STLRW R0, 32(R10)"}, + {Op: "STLRB", Args: []Operand{arm64RegOp("R1"), mem}, Raw: "STLRB R1, 32(R10)"}, + {Op: "STLR", Args: []Operand{arm64RegOp("R2"), mem}, Raw: "STLR R2, 32(R10)"}, + {Op: "STLXRW", Args: []Operand{arm64RegOp("R3"), mem, arm64RegOp("R6")}, Raw: "STLXRW R3, 32(R10), R6"}, + {Op: "STLXRB", Args: []Operand{arm64RegOp("R4"), mem, arm64RegOp("R7")}, Raw: "STLXRB R4, 32(R10), R7"}, + {Op: "STLXR", Args: []Operand{arm64RegOp("R5"), mem, arm64RegOp("R8")}, Raw: "STLXR R5, 32(R10), R8"}, + {Op: "SWPALB", Args: []Operand{arm64RegOp("R0"), mem, arm64RegOp("R1")}, Raw: "SWPALB R0, 32(R10), R1"}, + {Op: "SWPALW", Args: []Operand{arm64RegOp("R2"), mem, arm64RegOp("R3")}, Raw: "SWPALW R2, 32(R10), R3"}, + {Op: "SWPALD", Args: []Operand{arm64RegOp("R4"), mem, arm64RegOp("R5")}, Raw: "SWPALD R4, 32(R10), R5"}, + {Op: "LDADDALW", Args: []Operand{arm64RegOp("R0"), mem, arm64RegOp("R1")}, Raw: "LDADDALW R0, 32(R10), R1"}, + {Op: "LDADDALD", Args: []Operand{arm64RegOp("R2"), mem, arm64RegOp("R3")}, Raw: "LDADDALD R2, 32(R10), R3"}, + {Op: "LDORALB", Args: []Operand{arm64RegOp("R4"), mem, arm64RegOp("R5")}, Raw: "LDORALB R4, 32(R10), R5"}, + {Op: "LDORALW", Args: []Operand{arm64RegOp("R6"), mem, arm64RegOp("R7")}, Raw: "LDORALW R6, 32(R10), R7"}, + {Op: "LDORALD", Args: []Operand{arm64RegOp("R8"), mem, arm64RegOp("R9")}, Raw: "LDORALD R8, 32(R10), R9"}, + {Op: "LDCLRALB", Args: []Operand{arm64RegOp("R0"), mem, arm64RegOp("R1")}, Raw: "LDCLRALB R0, 32(R10), R1"}, + {Op: "LDCLRALW", Args: []Operand{arm64RegOp("R2"), mem, arm64RegOp("R3")}, Raw: "LDCLRALW R2, 32(R10), R3"}, + {Op: "LDCLRALD", Args: []Operand{arm64RegOp("R4"), mem, arm64RegOp("R5")}, Raw: "LDCLRALD R4, 32(R10), R5"}, + {Op: "CASALW", Args: []Operand{arm64RegOp("R6"), mem, arm64RegOp("R7")}, Raw: "CASALW R6, 32(R10), R7"}, + {Op: "CASALD", Args: []Operand{arm64RegOp("R8"), mem, arm64RegOp("R9")}, Raw: "CASALD R8, 32(R10), R9"}, + } { + ok, _, err := c.lowerAtomic(tc.Op, tc) + mustLowerARM64(t, "lowerAtomic", tc, ok, err) + } + + if ptr, err := c.atomicMemPtr(MemRef{Base: "R10", Off: 48}); err != nil || ptr == "" { + t.Fatalf("atomicMemPtr() = (%q, %v)", ptr, err) + } + if ty, align := arm64AtomicLoadStoreType("LDAXRB"); ty != I64 || align != 8 { + t.Fatalf("arm64AtomicLoadStoreType(LDAXRB) = (%s, %d)", ty, align) + } + if ty, align := arm64AtomicStoreExclusiveType("STLXRB"); ty != I8 || align != 1 { + t.Fatalf("arm64AtomicStoreExclusiveType(STLXRB) = (%s, %d)", ty, align) + } + if ty, err := arm64AtomicRMWType("LDCLRALW"); err != nil || ty != I32 { + t.Fatalf("arm64AtomicRMWType(LDCLRALW) = (%s, %v)", ty, err) + } + if _, err := arm64AtomicRMWType("BAD"); err == nil { + t.Fatalf("arm64AtomicRMWType(BAD) unexpectedly succeeded") + } + if size, err := arm64AtomicTypeSize(I64); err != nil || size != 8 { + t.Fatalf("arm64AtomicTypeSize(I64) = (%d, %v)", size, err) + } + if _, err := arm64AtomicTypeSize(Ptr); err == nil { + t.Fatalf("arm64AtomicTypeSize(ptr) unexpectedly succeeded") + } + if got, err := c.atomicTruncFromI64("99", I16); err != nil || got == "" { + t.Fatalf("atomicTruncFromI64() = (%q, %v)", got, err) + } + if _, err := c.atomicTruncFromI64("99", Ptr); err == nil { + t.Fatalf("atomicTruncFromI64(ptr) unexpectedly succeeded") + } + if got, err := c.atomicExtendToI64("%x", I8); err != nil || got == "" { + t.Fatalf("atomicExtendToI64() = (%q, %v)", got, err) + } + if _, err := c.atomicExtendToI64("%x", Ptr); err == nil { + t.Fatalf("atomicExtendToI64(ptr) unexpectedly succeeded") + } + + out := b.String() + for _, want := range []string{ + "load atomic i32", + "load atomic i8", + "load atomic i64", + "store atomic i32", + "store atomic i8", + "cmpxchg ptr", + "atomicrmw xchg", + "atomicrmw add", + "atomicrmw or", + "atomicrmw and", + "phi i64", + "store i1 false, ptr %exclusive_valid", + "zext i8", + "trunc i64", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestARM64ArithmeticCoverage(t *testing.T) { + c, b := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{Name: "example.arith", Ret: Void}, nil) + for _, tc := range []struct { + r Reg + v string + }{ + {"R0", "11"}, + {"R1", "12"}, + {"R2", "13"}, + {"R3", "14"}, + {"R4", "15"}, + {"R5", "16"}, + {"R6", "17"}, + {"R7", "18"}, + {"R8", "19"}, + {"R9", "20"}, + {"R10", "21"}, + } { + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + c.storeFlag(c.flagsCSlot, "true") + c.flagsWritten = true + + for _, tc := range []Instr{ + {Op: "MRS_TPIDR_R0", Raw: "MRS_TPIDR_R0"}, + {Op: "MRS", Args: []Operand{arm64IdentOp("MIDR_EL1"), arm64RegOp("R1")}, Raw: "MRS MIDR_EL1, R1"}, + {Op: "MRS", Args: []Operand{arm64IdentOp("TPIDR_EL0"), arm64RegOp("R2")}, Raw: "MRS TPIDR_EL0, R2"}, + {Op: "MSR", Args: []Operand{arm64ImmOp(1), arm64IdentOp("DIT")}, Raw: "MSR $1, DIT"}, + {Op: "MSR", Args: []Operand{arm64RegOp("R3"), arm64IdentOp("TPIDR_EL0")}, Raw: "MSR R3, TPIDR_EL0"}, + {Op: "UBFX", Args: []Operand{arm64ImmOp(4), arm64RegOp("R4"), arm64ImmOp(8), arm64RegOp("R5")}, Raw: "UBFX $4, R4, $8, R5"}, + {Op: "ADD", Args: []Operand{arm64ImmOp(2), arm64RegOp("R0")}, Raw: "ADD $2, R0"}, + {Op: "SUB", Args: []Operand{arm64ImmOp(3), arm64RegOp("R1"), arm64RegOp("R2")}, Raw: "SUB $3, R1, R2"}, + {Op: "ADDS", Args: []Operand{arm64ImmOp(4), arm64RegOp("R2"), arm64RegOp("R3")}, Raw: "ADDS $4, R2, R3"}, + {Op: "ADC", Args: []Operand{arm64ImmOp(5), arm64RegOp("R3")}, Raw: "ADC $5, R3"}, + {Op: "ADCS", Args: []Operand{arm64ImmOp(6), arm64RegOp("R3"), arm64RegOp("R4")}, Raw: "ADCS $6, R3, R4"}, + {Op: "SBC", Args: []Operand{arm64ImmOp(1), arm64RegOp("R4")}, Raw: "SBC $1, R4"}, + {Op: "SBCS", Args: []Operand{arm64ImmOp(2), arm64RegOp("R4"), arm64RegOp("R5")}, Raw: "SBCS $2, R4, R5"}, + {Op: "ADDW", Args: []Operand{arm64ImmOp(7), arm64RegOp("R5"), arm64RegOp("R6")}, Raw: "ADDW $7, R5, R6"}, + {Op: "SUBW", Args: []Operand{arm64ImmOp(3), arm64RegOp("R6")}, Raw: "SUBW $3, R6"}, + {Op: "AND", Args: []Operand{arm64ImmOp(15), arm64RegOp("R6"), arm64RegOp("R7")}, Raw: "AND $15, R6, R7"}, + {Op: "ANDS", Args: []Operand{arm64ImmOp(3), arm64RegOp("R7")}, Raw: "ANDS $3, R7"}, + {Op: "EOR", Args: []Operand{arm64ImmOp(8), arm64RegOp("R7"), arm64RegOp("R8")}, Raw: "EOR $8, R7, R8"}, + {Op: "ORR", Args: []Operand{arm64ImmOp(9), arm64RegOp("R8")}, Raw: "ORR $9, R8"}, + {Op: "ANDW", Args: []Operand{arm64ImmOp(7), arm64RegOp("R8"), arm64RegOp("R9")}, Raw: "ANDW $7, R8, R9"}, + {Op: "EORW", Args: []Operand{arm64ImmOp(5), arm64RegOp("R9")}, Raw: "EORW $5, R9"}, + {Op: "ORRW", Args: []Operand{arm64ImmOp(6), arm64RegOp("R9"), arm64RegOp("R10")}, Raw: "ORRW $6, R9, R10"}, + {Op: "ANDSW", Args: []Operand{arm64ImmOp(3), arm64RegOp("R10"), arm64RegOp("R0")}, Raw: "ANDSW $3, R10, R0"}, + {Op: "SUBS", Args: []Operand{arm64ImmOp(4), arm64RegOp("R0"), arm64RegOp("R1")}, Raw: "SUBS $4, R0, R1"}, + {Op: "BIC", Args: []Operand{arm64ImmOp(8), arm64RegOp("R1"), arm64RegOp("R2")}, Raw: "BIC $8, R1, R2"}, + {Op: "BICW", Args: []Operand{arm64ImmOp(8), arm64RegOp("R2"), arm64RegOp("R3")}, Raw: "BICW $8, R2, R3"}, + {Op: "MVN", Args: []Operand{arm64RegOp("R3"), arm64RegOp("R4")}, Raw: "MVN R3, R4"}, + {Op: "MVNW", Args: []Operand{arm64RegOp("R4"), arm64RegOp("R5")}, Raw: "MVNW R4, R5"}, + {Op: "CRC32B", Args: []Operand{arm64RegOp("R0"), arm64RegOp("R1")}, Raw: "CRC32B R0, R1"}, + {Op: "CRC32H", Args: []Operand{arm64RegOp("R1"), arm64RegOp("R2")}, Raw: "CRC32H R1, R2"}, + {Op: "CRC32W", Args: []Operand{arm64RegOp("R2"), arm64RegOp("R3")}, Raw: "CRC32W R2, R3"}, + {Op: "CRC32X", Args: []Operand{arm64RegOp("R3"), arm64RegOp("R4")}, Raw: "CRC32X R3, R4"}, + {Op: "CRC32CB", Args: []Operand{arm64RegOp("R4"), arm64RegOp("R5")}, Raw: "CRC32CB R4, R5"}, + {Op: "CRC32CH", Args: []Operand{arm64RegOp("R5"), arm64RegOp("R6")}, Raw: "CRC32CH R5, R6"}, + {Op: "CRC32CW", Args: []Operand{arm64RegOp("R6"), arm64RegOp("R7")}, Raw: "CRC32CW R6, R7"}, + {Op: "CRC32CX", Args: []Operand{arm64RegOp("R7"), arm64RegOp("R8")}, Raw: "CRC32CX R7, R8"}, + {Op: "CMP", Args: []Operand{arm64RegOp("R0"), arm64RegOp("R1")}, Raw: "CMP R0, R1"}, + {Op: "CMPW", Args: []Operand{arm64RegOp("R1"), arm64RegOp("R2")}, Raw: "CMPW R1, R2"}, + {Op: "CMN", Args: []Operand{arm64RegOp("R2"), arm64RegOp("R3")}, Raw: "CMN R2, R3"}, + {Op: "NEG", Args: []Operand{arm64RegOp("R3"), arm64RegOp("R4")}, Raw: "NEG R3, R4"}, + {Op: "MUL", Args: []Operand{arm64ImmOp(2), arm64RegOp("R4"), arm64RegOp("R5")}, Raw: "MUL $2, R4, R5"}, + {Op: "UMULH", Args: []Operand{arm64RegOp("R5"), arm64RegOp("R6"), arm64RegOp("R7")}, Raw: "UMULH R5, R6, R7"}, + {Op: "MADD", Args: []Operand{arm64RegOp("R0"), arm64RegOp("R1"), arm64RegOp("R2"), arm64RegOp("R3")}, Raw: "MADD R0, R1, R2, R3"}, + {Op: "MSUB", Args: []Operand{arm64RegOp("R3"), arm64RegOp("R4"), arm64RegOp("R5"), arm64RegOp("R6")}, Raw: "MSUB R3, R4, R5, R6"}, + {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: "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"}, + {Op: "RORW", Args: []Operand{arm64RegOp("R3"), arm64RegOp("R2"), arm64RegOp("R3")}, Raw: "RORW R3, R2, R3"}, + {Op: "RBIT", Args: []Operand{arm64RegOp("R3"), arm64RegOp("R4")}, Raw: "RBIT R3, R4"}, + {Op: "CLZ", Args: []Operand{arm64RegOp("R4"), arm64RegOp("R5")}, Raw: "CLZ R4, R5"}, + {Op: "REV", Args: []Operand{arm64RegOp("R5"), arm64RegOp("R6")}, Raw: "REV R5, R6"}, + } { + ok, _, err := c.lowerArith(tc.Op, tc) + mustLowerARM64(t, "lowerArith", tc, ok, err) + } + + if _, _, err := c.lowerArith("UBFX", Instr{Op: "UBFX", Args: []Operand{arm64ImmOp(63), arm64RegOp("R0"), arm64ImmOp(2), arm64RegOp("R1")}, Raw: "UBFX $63, R0, $2, R1"}); err == nil { + t.Fatalf("invalid UBFX unexpectedly succeeded") + } + if got, err := c.condValue("GT"); err != nil || got == "" { + t.Fatalf("condValue(GT) = (%q, %v)", got, err) + } + if got, err := c.condValue("LE"); err != nil || got == "" { + t.Fatalf("condValue(LE) = (%q, %v)", got, err) + } + if got, err := c.condValue("HI"); err != nil || got == "" { + t.Fatalf("condValue(HI) = (%q, %v)", got, err) + } + if _, err := (&arm64Ctx{}).condValue("EQ"); err == nil { + t.Fatalf("condValue without flags unexpectedly succeeded") + } + if got := arm64CanonicalSysReg("DIT"); got != "S3_3_C4_C2_5" { + t.Fatalf("arm64CanonicalSysReg(DIT) = %q", got) + } + if v, ok := arm64CompileSafeMRSValue("MIDR_EL1"); !ok || v != "0" { + t.Fatalf("arm64CompileSafeMRSValue(MIDR_EL1) = (%q, %v)", v, ok) + } + if _, ok := arm64CompileSafeMRSValue("TPIDR_EL0"); ok { + t.Fatalf("arm64CompileSafeMRSValue(TPIDR_EL0) unexpectedly succeeded") + } + + out := b.String() + for _, want := range []string{ + `asm sideeffect "mrs $0, TPIDR_EL0"`, + `asm sideeffect "msr S3_3_C4_C2_5, $0"`, + "lshr i64", + "shl i64", + "ashr i64", + "udiv i64", + "mul i128", + "call i64 @llvm.bitreverse.i64", + "call i64 @llvm.ctlz.i64", + "call i64 @llvm.bswap.i64", + "@llvm.aarch64.crc32b", + "@llvm.aarch64.crc32cx", + "xor i32", + "zext i32", + "trunc i64", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestARM64DataVectorAndBranchCoverage(t *testing.T) { + fn := Func{ + Instrs: []Instr{ + {Op: "VMOV", Args: []Operand{arm64RegOp("V0"), arm64RegOp("V1")}}, + {Op: "VMOV", Args: []Operand{arm64RegOp("V2"), arm64RegOp("V3")}}, + {Op: "VMOV", Args: []Operand{arm64RegOp("V4"), arm64RegOp("V5")}}, + {Op: "VMOV", Args: []Operand{arm64RegOp("V6"), arm64RegOp("V7")}}, + {Op: OpLABEL, Args: []Operand{{Kind: OpLabel, Sym: "loop"}}}, + {Op: "NOP", Raw: "NOP"}, + {Op: OpLABEL, Args: []Operand{{Kind: OpLabel, Sym: "next"}}}, + {Op: "NOP", Raw: "NOP"}, + {Op: OpLABEL, Args: []Operand{{Kind: OpLabel, Sym: "done"}}}, + {Op: "NOP", Raw: "NOP"}, + }, + } + sigs := map[string]FuncSig{ + "example.helper": {Name: "example.helper", Ret: I64}, + "example.sink": { + Name: "example.sink", + Args: []LLVMType{I1, I8, I16, I32, Ptr, I64}, + ArgRegs: []Reg{"R0", "R1", "R2", "R3", "R4", "R5"}, + Ret: Void, + }, + "example.sameSig": { + Name: "example.sameSig", + Args: []LLVMType{I64, I32, I16, I8}, + Ret: I64, + }, + } + sig := FuncSig{ + Name: "example.mix", + Args: []LLVMType{I64, I32, I16, I8}, + Ret: I64, + Frame: FrameLayout{ + Params: []FrameSlot{ + {Offset: 24, Type: I64, Index: 0}, + {Offset: 32, Type: I32, Index: 1}, + {Offset: 40, Type: I16, Index: 2}, + {Offset: 48, Type: I8, Index: 3}, + }, + Results: []FrameSlot{ + {Offset: 8, Type: I64, Index: 0}, + {Offset: 16, Type: I64, Index: 1}, + }, + }, + } + c, b := newARM64CtxWithFuncForTest(t, fn, sig, sigs) + for _, tc := range []struct { + r Reg + v string + }{ + {"R0", "33"}, + {"R1", "34"}, + {"R2", "35"}, + {"R3", "36"}, + {"R4", "37"}, + {"R5", "38"}, + {"R6", "39"}, + {"R7", "40"}, + {"R8", "41"}, + {"R9", "42"}, + {"R10", "43"}, + {"R20", "2048"}, + {"R21", "4096"}, + } { + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + + for _, tc := range []struct { + op Op + postInc bool + ins Instr + kind string + }{ + {"MOVD", false, Instr{Op: "MOVD", Args: []Operand{arm64ImmOp(99), arm64RegOp("R0")}, Raw: "MOVD $99, R0"}, "data"}, + {"MOVD", false, Instr{Op: "MOVD", Args: []Operand{arm64RegOp("R0"), arm64MemOp("R20", 0)}, Raw: "MOVD R0, (R20)"}, "data"}, + {"MOVD", false, Instr{Op: "MOVD", Args: []Operand{arm64RegOp("R1"), arm64FPOp(8)}, Raw: "MOVD R1, ret+8(FP)"}, "data"}, + {"MOVD", false, Instr{Op: "MOVD", Args: []Operand{arm64RegOp("R2"), arm64SymOp("ignored<>(SB)")}, Raw: "MOVD R2, ignored<>(SB)"}, "data"}, + {"MOVB", false, Instr{Op: "MOVB", Args: []Operand{arm64MemOp("R20", 1), arm64RegOp("R2")}, Raw: "MOVB 1(R20), R2"}, "data"}, + {"MOVB", false, Instr{Op: "MOVB", Args: []Operand{arm64RegOp("R2"), arm64MemOp("R20", 2)}, Raw: "MOVB R2, 2(R20)"}, "data"}, + {"MOVB", false, Instr{Op: "MOVB", Args: []Operand{arm64RegOp("R2"), arm64FPOp(16)}, Raw: "MOVB R2, ret+16(FP)"}, "data"}, + {"MOVW", false, Instr{Op: "MOVW", Args: []Operand{arm64MemOp("R20", 4), arm64RegOp("R3")}, Raw: "MOVW 4(R20), R3"}, "data"}, + {"MOVW", false, Instr{Op: "MOVW", Args: []Operand{arm64RegOp("R3"), arm64MemOp("R20", 8)}, Raw: "MOVW R3, 8(R20)"}, "data"}, + {"MOVW", false, Instr{Op: "MOVW", Args: []Operand{arm64RegOp("R3"), arm64FPOp(8)}, Raw: "MOVW R3, ret+8(FP)"}, "data"}, + {"MOVH", false, Instr{Op: "MOVH", Args: []Operand{arm64MemOp("R20", 12), arm64RegOp("R4")}, Raw: "MOVH 12(R20), R4"}, "data"}, + {"MOVHU", false, Instr{Op: "MOVHU", Args: []Operand{arm64MemOp("R20", 14), arm64RegOp("R5")}, Raw: "MOVHU 14(R20), R5"}, "data"}, + {"MOVHU", false, Instr{Op: "MOVHU", Args: []Operand{arm64RegOp("R5"), arm64MemOp("R20", 16)}, Raw: "MOVHU R5, 16(R20)"}, "data"}, + {"MOVWU", false, Instr{Op: "MOVWU", Args: []Operand{arm64FPOp(32), arm64RegOp("R6")}, Raw: "MOVWU arg+32(FP), R6"}, "data"}, + {"MOVWU", true, Instr{Op: "MOVWU.P", Args: []Operand{arm64RegOp("R6"), arm64MemOp("R20", 4)}, Raw: "MOVWU.P R6, 4(R20)"}, "data"}, + {"MOVWU", false, Instr{Op: "MOVWU", Args: []Operand{arm64RegOp("R6"), arm64FPOp(16)}, Raw: "MOVWU R6, ret+16(FP)"}, "data"}, + {"MOVBU", false, Instr{Op: "MOVBU", Args: []Operand{arm64MemOp("R20", 18), arm64RegOp("R7")}, Raw: "MOVBU 18(R20), R7"}, "data"}, + {"MOVBU", false, Instr{Op: "MOVBU", Args: []Operand{arm64FPOp(48), arm64RegOp("R8")}, Raw: "MOVBU arg+48(FP), R8"}, "data"}, + {"MOVBU", false, Instr{Op: "MOVBU", Args: []Operand{arm64SymOp("example.global(SB)"), arm64RegOp("R9")}, Raw: "MOVBU example.global(SB), R9"}, "data"}, + {"MOVBU", false, Instr{Op: "MOVBU", Args: []Operand{arm64RegOp("R9"), arm64RegOp("R10")}, Raw: "MOVBU R9, R10"}, "data"}, + {"MOVBU", false, Instr{Op: "MOVBU", Args: []Operand{arm64RegOp("R10"), arm64MemOp("R20", 20)}, Raw: "MOVBU R10, 20(R20)"}, "data"}, + {"MOVBU", false, Instr{Op: "MOVBU", Args: []Operand{arm64RegOp("R10"), arm64FPOp(16)}, Raw: "MOVBU R10, ret+16(FP)"}, "data"}, + {"LDP", true, Instr{Op: "LDP.P", Args: []Operand{arm64MemOp("R20", 16), arm64RegListOp("R0", "R1")}, Raw: "LDP.P 16(R20), [R0, R1]"}, "data"}, + {"LDP", false, Instr{Op: "LDP", Args: []Operand{arm64FPOp(24), arm64RegListOp("R2", "R3")}, Raw: "LDP arg+24(FP), [R2, R3]"}, "data"}, + {"LDP", false, Instr{Op: "LDP", Args: []Operand{arm64SymOp("example.global(SB)"), arm64RegListOp("R4", "R5")}, Raw: "LDP example.global(SB), [R4, R5]"}, "data"}, + {"LDPW", true, Instr{Op: "LDPW.P", Args: []Operand{arm64MemOp("R20", 8), arm64RegListOp("R6", "R7")}, Raw: "LDPW.P 8(R20), [R6, R7]"}, "data"}, + {"STPW", true, Instr{Op: "STPW.P", Args: []Operand{arm64RegListOp("R6", "R7"), arm64MemOp("R20", 8)}, Raw: "STPW.P [R6, R7], 8(R20)"}, "data"}, + {"STP", true, Instr{Op: "STP.P", Args: []Operand{arm64RegListOp("R8", "R9"), arm64MemOp("R20", 16)}, Raw: "STP.P [R8, R9], 16(R20)"}, "data"}, + {"VMOV", false, Instr{Op: "VMOV", Args: []Operand{arm64RegOp("R0"), arm64RegOp("V0.B16")}, Raw: "VMOV R0, V0.B16"}, "vec"}, + {"VMOV", false, Instr{Op: "VMOV", Args: []Operand{arm64RegOp("R1"), arm64RegOp("V1.S4")}, Raw: "VMOV R1, V1.S4"}, "vec"}, + {"VMOV", false, Instr{Op: "VMOV", Args: []Operand{arm64RegOp("R2"), arm64RegOp("V2.D[1]")}, Raw: "VMOV R2, V2.D[1]"}, "vec"}, + {"VMOV", false, Instr{Op: "VMOV", Args: []Operand{arm64RegOp("V2.D[1]"), arm64RegOp("R3")}, Raw: "VMOV V2.D[1], R3"}, "vec"}, + {"VMOV", false, Instr{Op: "VMOV", Args: []Operand{arm64RegOp("V1"), arm64RegOp("V3")}, Raw: "VMOV V1, V3"}, "vec"}, + {"VEOR", false, Instr{Op: "VEOR", Args: []Operand{arm64RegOp("V0"), arm64RegOp("V1"), arm64RegOp("V4")}, Raw: "VEOR V0, V1, V4"}, "vec"}, + {"VORR", false, Instr{Op: "VORR", Args: []Operand{arm64RegOp("V1"), arm64RegOp("V2"), arm64RegOp("V5")}, Raw: "VORR V1, V2, V5"}, "vec"}, + {"VLD1", true, Instr{Op: "VLD1.P", Args: []Operand{arm64MemOp("R21", 0), arm64RegOp("V4.B[3]")}, Raw: "VLD1.P (R21), V4.B[3]"}, "vec"}, + {"VLD1", true, Instr{Op: "VLD1.P", Args: []Operand{arm64MemOp("R21", 0), arm64RegListOp("V4", "V5", "V6", "V7")}, Raw: "VLD1.P (R21), [V4, V5, V6, V7]"}, "vec"}, + {"VST1", true, Instr{Op: "VST1.P", Args: []Operand{arm64RegListOp("V4", "V5"), arm64MemOp("R21", 0)}, Raw: "VST1.P [V4, V5], (R21)"}, "vec"}, + {"VCMEQ", false, Instr{Op: "VCMEQ", Args: []Operand{arm64RegOp("V4.B16"), arm64RegOp("V5.B16"), arm64RegOp("V6.B16")}, Raw: "VCMEQ V4.B16, V5.B16, V6.B16"}, "vec"}, + {"VCMEQ", false, Instr{Op: "VCMEQ", Args: []Operand{arm64RegOp("V4.D2"), arm64RegOp("V5.D2"), arm64RegOp("V7.D2")}, Raw: "VCMEQ V4.D2, V5.D2, V7.D2"}, "vec"}, + {"VAND", false, Instr{Op: "VAND", Args: []Operand{arm64RegOp("V4"), arm64RegOp("V5"), arm64RegOp("V6")}, Raw: "VAND V4, V5, V6"}, "vec"}, + {"VADDP", false, Instr{Op: "VADDP", Args: []Operand{arm64RegOp("V4.B16"), arm64RegOp("V5.B16"), arm64RegOp("V6.B16")}, Raw: "VADDP V4.B16, V5.B16, V6.B16"}, "vec"}, + {"VADDP", false, Instr{Op: "VADDP", Args: []Operand{arm64RegOp("V4.D2"), arm64RegOp("V5.D2"), arm64RegOp("V7.D2")}, Raw: "VADDP V4.D2, V5.D2, V7.D2"}, "vec"}, + {"VUADDLV", false, Instr{Op: "VUADDLV", Args: []Operand{arm64RegOp("V6.B16"), arm64RegOp("V7")}, Raw: "VUADDLV V6.B16, V7"}, "vec"}, + {"VADD", false, Instr{Op: "VADD", Args: []Operand{arm64RegOp("V4.S4"), arm64RegOp("V5.S4"), arm64RegOp("V6.S4")}, Raw: "VADD V4.S4, V5.S4, V6.S4"}, "vec"}, + {"VADD", false, Instr{Op: "VADD", Args: []Operand{arm64RegOp("V6.D2"), arm64RegOp("V7.D2")}, Raw: "VADD V6.D2, V7.D2"}, "vec"}, + {"AESE", false, Instr{Op: "AESE", Args: []Operand{arm64RegOp("V0"), arm64RegOp("V1"), arm64RegOp("V2")}, Raw: "AESE V0, V1, V2"}, "vec"}, + } { + switch tc.kind { + case "data": + ok, _, err := c.lowerData(tc.op, tc.postInc, tc.ins) + mustLowerARM64(t, "lowerData", tc.ins, ok, err) + case "vec": + ok, _, err := c.lowerVec(tc.op, tc.postInc, tc.ins) + mustLowerARM64(t, "lowerVec", tc.ins, ok, err) + } + } + + if kind, lane, ok := arm64ParseVRegLane("V0.B[15]"); !ok || kind != 'B' || lane != 15 { + t.Fatalf("arm64ParseVRegLane(V0.B[15]) = (%q, %d, %v)", kind, lane, ok) + } + if _, _, ok := arm64ParseVRegLane("V0.X[0]"); ok { + t.Fatalf("arm64ParseVRegLane(V0.X[0]) unexpectedly succeeded") + } + if got, ok := arm64BranchTarget(arm64IdentOp("loop")); !ok || got != "loop" { + t.Fatalf("arm64BranchTarget(ident) = (%q, %v)", got, ok) + } + if got, ok := arm64BranchTarget(arm64SymOp("next<>(SB)")); !ok || got != "next" { + t.Fatalf("arm64BranchTarget(sym) = (%q, %v)", got, ok) + } + if _, ok := arm64BranchTarget(arm64RegOp("R0")); ok { + t.Fatalf("arm64BranchTarget(reg) unexpectedly succeeded") + } + if got := arm64LLVMBlockName("9.loop<>"); got != "bb_9_loop__" { + t.Fatalf("arm64LLVMBlockName() = %q", got) + } + + c.setFlagsSub("10", "4", "6") + emitBr := arm64TestEmitBr(c) + emitCondBr := arm64TestEmitCondBr(c) + for _, tc := range []Instr{ + {Op: "BL", Args: []Operand{arm64RegOp("R0")}, Raw: "BL R0"}, + {Op: "CALL", Args: []Operand{arm64MemOp("R20", 8)}, Raw: "CALL 8(R20)"}, + {Op: "BL", Args: []Operand{arm64SymOp("helper(SB)")}, Raw: "BL helper(SB)"}, + {Op: "B", Args: []Operand{arm64RegOp("R1")}, Raw: "B R1"}, + {Op: "JMP", Args: []Operand{arm64MemOp("R20", 8)}, Raw: "JMP 8(R20)"}, + {Op: "B", Args: []Operand{arm64SymOp("sink(SB)")}, Raw: "B sink(SB)"}, + {Op: "BEQ", Args: []Operand{arm64IdentOp("done")}, Raw: "BEQ done"}, + {Op: "BNE", Args: []Operand{arm64IdentOp("done")}, Raw: "BNE done"}, + {Op: "BLO", Args: []Operand{arm64IdentOp("done")}, Raw: "BLO done"}, + {Op: "BHI", Args: []Operand{arm64IdentOp("done")}, Raw: "BHI done"}, + {Op: "BHS", Args: []Operand{arm64IdentOp("done")}, Raw: "BHS done"}, + {Op: "BLS", Args: []Operand{arm64IdentOp("done")}, Raw: "BLS done"}, + {Op: "BLT", Args: []Operand{arm64IdentOp("done")}, Raw: "BLT done"}, + {Op: "BGE", Args: []Operand{arm64IdentOp("done")}, Raw: "BGE done"}, + {Op: "BGT", Args: []Operand{arm64IdentOp("done")}, Raw: "BGT done"}, + {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: "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"}, + {Op: "TBNZ", Args: []Operand{arm64ImmOp(2), arm64RegOp("R5"), arm64MemOp(PC, 0)}, Raw: "TBNZ $2, R5, 0(PC)"}, + {Op: "CBZW", Args: []Operand{arm64RegOp("R6"), arm64IdentOp("done")}, Raw: "CBZW R6, done"}, + {Op: "CBNZW", Args: []Operand{arm64RegOp("R7"), arm64IdentOp("done")}, Raw: "CBNZW R7, done"}, + } { + ok, _, err := c.lowerBranch(1, tc.Op, tc, emitBr, emitCondBr) + mustLowerARM64(t, "lowerBranch", tc, ok, err) + } + + if tgt, ok := c.resolveBranchTarget(1, arm64MemOp(PC, -4)); !ok || tgt != c.blocks[1].name { + t.Fatalf("resolveBranchTarget(-4(PC)) = (%q, %v)", tgt, ok) + } + if tgt, ok := c.resolveBranchTarget(1, arm64MemOp(PC, 4)); !ok || tgt != c.blocks[2].name { + t.Fatalf("resolveBranchTarget(4(PC)) = (%q, %v)", tgt, ok) + } + if got, err := c.castI64RegToArg("9", I32); err != nil || got == "" { + t.Fatalf("castI64RegToArg(i32) = (%q, %v)", got, err) + } + if got, err := c.castI64RegToArg("9", Ptr); err != nil || got == "" { + t.Fatalf("castI64RegToArg(ptr) = (%q, %v)", got, err) + } + if _, err := c.castI64RegToArg("9", LLVMType("double")); err == nil { + t.Fatalf("castI64RegToArg(double) unexpectedly succeeded") + } + cursor := 0 + if agg, err := c.structArgFromSequentialRegs(LLVMType("{i32, i64}"), &cursor); err != nil || agg == "" || cursor != 2 { + t.Fatalf("structArgFromSequentialRegs() = (%q, %d, %v)", agg, cursor, err) + } + if _, err := c.structArgFromSequentialRegs(LLVMType("v2i64"), &cursor); err == nil { + t.Fatalf("structArgFromSequentialRegs(v2i64) unexpectedly succeeded") + } + if err := c.callSym(arm64SymOp("helper(SB)")); err != nil { + t.Fatalf("callSym(helper) error = %v", err) + } + if err := c.callSym(arm64SymOp("runtime·entersyscall(SB)")); err != nil { + t.Fatalf("callSym(runtime·entersyscall) error = %v", err) + } + if err := c.tailCallAndRet(arm64SymOp("sameSig(SB)")); err != nil { + t.Fatalf("tailCallAndRet(sameSig) error = %v", err) + } + if err := c.tailCallAndRet(arm64SymOp("sink(SB)")); err != nil { + t.Fatalf("tailCallAndRet(sink) error = %v", err) + } + + out := b.String() + for _, want := range []string{ + "store i64 99, ptr %reg_R0", + "store i8", + "store i16", + "store i32", + "load i64, ptr", + "load <16 x i8>, ptr", + "store <16 x i8>", + "bitcast <16 x i8> ", + "icmp eq <16 x i8>", + "sext <16 x i1>", + "call void asm sideeffect \"blr $0\"", + "call void asm sideeffect \"br $0\"", + `call i64 @"example.helper"()`, + `call void @"example.sink"(`, + "insertvalue {i32, i64}", + "br i1", + `call i64 @"example.sameSig"(i64 %arg0, i32 %arg1, i16 %arg2, i8 %arg3)`, + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestARM64FPOpsCoverage(t *testing.T) { + sig := FuncSig{ + Name: "example.fpops", + Args: []LLVMType{LLVMType("double"), LLVMType("float"), I64, I32, I8, Ptr}, + Ret: Void, + Frame: FrameLayout{ + Params: []FrameSlot{ + {Offset: 0, Type: LLVMType("double"), Index: 0}, + {Offset: 8, Type: LLVMType("float"), Index: 1}, + {Offset: 16, Type: I64, Index: 2}, + {Offset: 24, Type: I32, Index: 3}, + {Offset: 32, Type: I8, Index: 4}, + {Offset: 40, Type: Ptr, Index: 5}, + }, + Results: []FrameSlot{ + {Offset: 48, Type: I64, Index: 0}, + {Offset: 56, Type: I64, Index: 1}, + }, + }, + } + c, b := newARM64CtxWithFuncForTest(t, Func{}, sig, nil) + check := func(kind string, ins Instr, ok bool, err error) { + t.Helper() + if err != nil { + t.Fatalf("%s %q error = %v", kind, ins.Raw, err) + } + if !ok { + t.Fatalf("%s %q returned ok=false", kind, ins.Raw) + } + } + for _, tc := range []struct { + r Reg + v string + }{ + {"R0", "11"}, + {"R1", "12"}, + {"R2", "13"}, + {"R3", "14"}, + {"R4", "15"}, + } { + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + + for _, ins := range []Instr{ + {Op: "FMOVD", Args: []Operand{arm64SymOp("$1.5"), arm64RegOp("R0")}, Raw: "FMOVD $1.5, R0"}, + {Op: "FMOVD", Args: []Operand{arm64SymOp("$0x10"), arm64FPOp(48)}, Raw: "FMOVD $0x10, ret+48(FP)"}, + {Op: "FCMPD", Args: []Operand{arm64FPOp(0), arm64RegOp("R0")}, Raw: "FCMPD arg+0(FP), R0"}, + {Op: "FCSELD", Args: []Operand{arm64IdentOp("EQ"), arm64FPOp(0), arm64FPOp(8), arm64RegOp("R1")}, Raw: "FCSELD EQ, arg+0(FP), arg+8(FP), R1"}, + {Op: "FADDD", Args: []Operand{arm64FPOp(0), arm64RegOp("R1")}, Raw: "FADDD arg+0(FP), R1"}, + {Op: "FSUBD", Args: []Operand{arm64FPOp(8), arm64RegOp("R1"), arm64RegOp("R2")}, Raw: "FSUBD arg+8(FP), R1, R2"}, + {Op: "FMULD", Args: []Operand{arm64FPOp(16), arm64RegOp("R2")}, Raw: "FMULD arg+16(FP), R2"}, + {Op: "FDIVD", Args: []Operand{arm64FPOp(24), arm64RegOp("R2"), arm64FPOp(48)}, Raw: "FDIVD arg+24(FP), R2, ret+48(FP)"}, + {Op: "FMAXD", Args: []Operand{arm64FPOp(32), arm64RegOp("R2")}, Raw: "FMAXD arg+32(FP), R2"}, + {Op: "FMIND", Args: []Operand{arm64FPOp(40), arm64RegOp("R2"), arm64RegOp("R3")}, Raw: "FMIND arg+40(FP), R2, R3"}, + {Op: "FMADDD", Args: []Operand{arm64FPOp(0), arm64FPOp(8), arm64FPOp(16), arm64RegOp("R3")}, Raw: "FMADDD arg+0(FP), arg+8(FP), arg+16(FP), R3"}, + {Op: "FMSUBD", Args: []Operand{arm64FPOp(8), arm64FPOp(16), arm64FPOp(24), arm64RegOp("R4")}, Raw: "FMSUBD arg+8(FP), arg+16(FP), arg+24(FP), R4"}, + {Op: "FNMSUBD", Args: []Operand{arm64FPOp(16), arm64FPOp(24), arm64FPOp(32), arm64FPOp(56)}, Raw: "FNMSUBD arg+16(FP), arg+24(FP), arg+32(FP), ret+56(FP)"}, + {Op: "FNMULD", Args: []Operand{arm64FPOp(0), arm64RegOp("R3")}, Raw: "FNMULD arg+0(FP), R3"}, + {Op: "FNMULD", Args: []Operand{arm64FPOp(8), arm64FPOp(16), arm64RegOp("R4")}, Raw: "FNMULD arg+8(FP), arg+16(FP), R4"}, + {Op: "FABSD", Args: []Operand{arm64RegOp("R4"), arm64FPOp(48)}, Raw: "FABSD R4, ret+48(FP)"}, + {Op: "FRINTZD", Args: []Operand{arm64FPOp(0), arm64RegOp("R0")}, Raw: "FRINTZD arg+0(FP), R0"}, + {Op: "FRINTMD", Args: []Operand{arm64FPOp(8), arm64RegOp("R1")}, Raw: "FRINTMD arg+8(FP), R1"}, + {Op: "FRINTPD", Args: []Operand{arm64FPOp(16), arm64FPOp(56)}, Raw: "FRINTPD arg+16(FP), ret+56(FP)"}, + {Op: "FCVTZSD", Args: []Operand{arm64FPOp(0), arm64RegOp("R2")}, Raw: "FCVTZSD arg+0(FP), R2"}, + {Op: "SCVTFD", Args: []Operand{arm64RegOp("R2"), arm64RegOp("R3")}, Raw: "SCVTFD R2, R3"}, + } { + ok, _, err := c.lowerFP(ins.Op, ins) + check("lowerFP", ins, ok, err) + } + + if got, err := c.evalFMOVDBits(arm64SymOp("$2.5")); err != nil || got == "" { + t.Fatalf("evalFMOVDBits($2.5) = (%q, %v)", got, err) + } + if got, err := c.evalFMOVDBits(arm64SymOp("$0x20")); err != nil || got != "32" { + t.Fatalf("evalFMOVDBits($0x20) = (%q, %v)", got, err) + } + if got, err := c.evalF64(arm64RegOp("R0")); err != nil || got == "" { + t.Fatalf("evalF64(reg) = (%q, %v)", got, err) + } + if got, err := c.evalF64(arm64ImmOp(3)); err != nil || got == "" { + t.Fatalf("evalF64(imm) = (%q, %v)", got, err) + } + if got, err := c.evalF64(arm64SymOp("$4.5")); err != nil || got == "" { + t.Fatalf("evalF64($4.5) = (%q, %v)", got, err) + } + for _, off := range []int64{0, 8, 16, 24, 32, 40} { + if got, err := c.evalF64(arm64FPOp(off)); err != nil || got == "" { + t.Fatalf("evalF64(+%d(FP)) = (%q, %v)", off, got, err) + } + } + if _, err := c.evalF64(arm64SymOp("bad(SB)")); err == nil { + t.Fatalf("evalF64(bad sym) unexpectedly succeeded") + } + if _, err := c.evalF64(arm64FPOp(99)); err == nil { + t.Fatalf("evalF64(bad fp) unexpectedly succeeded") + } + if err := c.storeF64(arm64RegOp("R0"), "1.000000e+00"); err != nil { + t.Fatalf("storeF64(reg) error = %v", err) + } + if err := c.storeF64(arm64FPOp(48), "2.000000e+00"); err != nil { + t.Fatalf("storeF64(fp) error = %v", err) + } + if err := c.storeF64(arm64MemOp("R0", 0), "3.000000e+00"); err == nil { + t.Fatalf("storeF64(mem) unexpectedly succeeded") + } + if got, ok := arm64ParseDollarFloat("$3.125"); !ok || got != 3.125 { + t.Fatalf("arm64ParseDollarFloat() = (%v, %v)", got, ok) + } + if got, ok := arm64ParseDollarFloat("$1e3"); !ok || got != 1000 { + t.Fatalf("arm64ParseDollarFloat($1e3) = (%v, %v)", got, ok) + } + for _, sym := range []string{"", "$", "3.125", "$10", "$nope"} { + if _, ok := arm64ParseDollarFloat(sym); ok { + t.Fatalf("arm64ParseDollarFloat(%q) unexpectedly succeeded", sym) + } + } + if _, ok := arm64ParseDollarFloat("$0x10"); ok { + t.Fatalf("arm64ParseDollarFloat($0x10) unexpectedly succeeded") + } + if got, ok := arm64ParseDollarInt64("$0x20"); !ok || got != 32 { + t.Fatalf("arm64ParseDollarInt64() = (%d, %v)", got, ok) + } + if got, ok := arm64ParseDollarInt64("$18446744073709551615"); !ok || got != -1 { + t.Fatalf("arm64ParseDollarInt64(uint64 max) = (%d, %v)", got, ok) + } + for _, sym := range []string{"", "$", "17", "$18446744073709551616"} { + if _, ok := arm64ParseDollarInt64(sym); ok { + t.Fatalf("arm64ParseDollarInt64(%q) unexpectedly succeeded", sym) + } + } + if _, ok := arm64ParseDollarInt64("$1.5"); ok { + t.Fatalf("arm64ParseDollarInt64($1.5) unexpectedly succeeded") + } + + out := b.String() + for _, want := range []string{ + "fcmp oeq double", + "fcmp uno double", + "select i1", + "fadd double", + "fsub double", + "fmul double", + "fdiv double", + "fneg double", + "@llvm.maxnum.f64", + "@llvm.minnum.f64", + "@llvm.fabs.f64", + "@llvm.trunc.f64", + "@llvm.floor.f64", + "@llvm.ceil.f64", + "fptosi double", + "sitofp i64", + "fpext float", + "ptrtoint ptr", + "bitcast i64", + "bitcast double", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestARM64BranchAndReturnEdgeCoverage(t *testing.T) { + fn := Func{ + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·edge(SB),NOSPLIT,$0-0"}, + {Op: "NOP", Raw: "NOP"}, + }, + } + var translated strings.Builder + if err := translateFuncARM64(&translated, fn, FuncSig{Name: "example.edge", Ret: I64}, testResolveSym("example"), nil, true); err != nil { + t.Fatalf("translateFuncARM64() error = %v", err) + } + if !strings.Contains(translated.String(), "ret i64 0") || !strings.Contains(translated.String(), "; s: NOP") { + t.Fatalf("translateFuncARM64() output = \n%s", translated.String()) + } + + for _, tc := range []struct { + name string + sig FuncSig + want string + }{ + {"void", FuncSig{Name: "example.retvoid", Ret: Void}, "ret void"}, + {"i1", FuncSig{Name: "example.reti1", Ret: I1}, "ret i1"}, + {"i8", FuncSig{Name: "example.reti8", Ret: I8}, "ret i8"}, + {"i16", FuncSig{Name: "example.reti16", Ret: I16}, "ret i16"}, + {"i32", FuncSig{Name: "example.reti32", Ret: I32}, "ret i32"}, + {"i64", FuncSig{Name: "example.reti64", Ret: I64}, "ret i64"}, + } { + c, b := newARM64CtxWithFuncForTest(t, Func{}, tc.sig, nil) + if tc.sig.Ret != Void { + if err := c.storeReg("R0", "17"); err != nil { + t.Fatalf("storeReg(R0) error = %v", err) + } + } + if err := c.lowerRET(); err != nil { + t.Fatalf("lowerRET(%s) error = %v", tc.name, err) + } + if !strings.Contains(b.String(), tc.want) { + t.Fatalf("lowerRET(%s) output = \n%s", tc.name, b.String()) + } + } + + cAgg, bAgg := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{ + Name: "example.retagg", + Ret: LLVMType("{ i64, i32 }"), + Frame: FrameLayout{ + Results: []FrameSlot{ + {Offset: 8, Type: I64, Index: 0}, + {Offset: 16, Type: I32, Index: 1}, + }, + }, + }, nil) + if ok, _, err := cAgg.lowerData("MOVD", false, Instr{ + Op: "MOVD", + Args: []Operand{arm64ImmOp(21), arm64FPOp(8)}, + Raw: "MOVD $21, ret+8(FP)", + }); !ok || err != nil { + t.Fatalf("lowerData(MOVD ret+8) = (%v, %v)", ok, err) + } + if ok, _, err := cAgg.lowerData("MOVW", false, Instr{ + Op: "MOVW", + Args: []Operand{arm64ImmOp(7), arm64FPOp(16)}, + Raw: "MOVW $7, ret+16(FP)", + }); !ok || err != nil { + t.Fatalf("lowerData(MOVW ret+16) = (%v, %v)", ok, err) + } + if err := cAgg.lowerRET(); err != nil { + t.Fatalf("lowerRET(aggregate) error = %v", err) + } + if !strings.Contains(bAgg.String(), "insertvalue { i64, i32 }") { + t.Fatalf("lowerRET(aggregate) output = \n%s", bAgg.String()) + } + + cz, bz := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{Name: "example.zero", Ret: I64}, nil) + cz.lowerRetZero() + if !strings.Contains(bz.String(), "ret i64 0") { + t.Fatalf("lowerRetZero() output = \n%s", bz.String()) + } + + sigs := map[string]FuncSig{ + "example.structSink": {Name: "example.structSink", Args: []LLVMType{LLVMType("{ i32, i64 }")}, Ret: Void}, + "example.badarg": {Name: "example.badarg", Args: []LLVMType{LLVMType("double")}, Ret: Void}, + "example.badret": {Name: "example.badret", Args: []LLVMType{I64}, Ret: LLVMType("double")}, + "example.voidsame": {Name: "example.voidsame", Args: []LLVMType{I64}, Ret: Void}, + "example.badtailret": {Name: "example.badtailret", Args: []LLVMType{I64}, Ret: LLVMType("double")}, + } + cCalls, _ := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{ + Name: "example.caller", + Args: []LLVMType{I64}, + Ret: I64, + Frame: FrameLayout{ + Results: []FrameSlot{{Offset: 8, Type: I64, Index: 0}}, + }, + }, sigs) + if err := cCalls.storeReg("R0", "3"); err != nil { + t.Fatalf("storeReg(R0) error = %v", err) + } + if err := cCalls.storeReg("R1", "4"); err != nil { + t.Fatalf("storeReg(R1) error = %v", err) + } + if err := cCalls.callSym(arm64SymOp("unknown_helper(SB)")); err != nil { + t.Fatalf("callSym(unknown) error = %v", err) + } + if err := cCalls.callSym(arm64SymOp("structSink(SB)")); err != nil { + t.Fatalf("callSym(structSink) error = %v", err) + } + if err := cCalls.callSym(arm64SymOp("badarg(SB)")); err == nil { + t.Fatalf("callSym(badarg) unexpectedly succeeded") + } + if err := cCalls.callSym(arm64SymOp("badret(SB)")); err == nil { + t.Fatalf("callSym(badret) unexpectedly succeeded") + } + if err := cCalls.callSym(arm64RegOp("R0")); err == nil { + t.Fatalf("callSym(non-sym) unexpectedly succeeded") + } + if err := cCalls.callSym(arm64SymOp("broken")); err == nil { + t.Fatalf("callSym(missing suffix) unexpectedly succeeded") + } + if err := cCalls.tailCallAndRet(arm64SymOp("missingTail(SB)")); err != nil { + t.Fatalf("tailCallAndRet(missing) error = %v", err) + } + cMismatch, _ := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{Name: "example.mismatch", Args: []LLVMType{I64}, Ret: I64}, map[string]FuncSig{ + "example.badtailret": {Name: "example.badtailret", Args: []LLVMType{I64}, Ret: LLVMType("double")}, + }) + if err := cMismatch.storeReg("R0", "1"); err != nil { + t.Fatalf("storeReg(R0) error = %v", err) + } + if err := cMismatch.tailCallAndRet(arm64SymOp("badtailret(SB)")); err == nil { + t.Fatalf("tailCallAndRet(badtailret) unexpectedly succeeded") + } +} + +func TestARM64FPEdgeErrors(t *testing.T) { + c, _ := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{ + Name: "example.fperr", + Args: []LLVMType{LLVMType("double"), LLVMType("double")}, + Ret: LLVMType("double"), + Frame: FrameLayout{ + Params: []FrameSlot{ + {Offset: 0, Type: LLVMType("double"), Index: 0}, + {Offset: 8, Type: LLVMType("double"), Index: 1}, + }, + Results: []FrameSlot{{Offset: 16, Type: I64, Index: 0}}, + }, + }, nil) + + cases := []struct { + op Op + ins Instr + }{ + {"FMOVD", Instr{Op: "FMOVD", Args: []Operand{arm64SymOp("$1.5")}, Raw: "FMOVD $1.5"}}, + {"FMOVD", Instr{Op: "FMOVD", Args: []Operand{arm64SymOp("$1.5"), arm64MemOp("R0", 0)}, Raw: "FMOVD $1.5, 0(R0)"}}, + {"FCMPD", Instr{Op: "FCMPD", Args: []Operand{arm64FPOp(0)}, Raw: "FCMPD arg+0(FP)"}}, + {"FCSELD", Instr{Op: "FCSELD", Args: []Operand{arm64RegOp("R0"), arm64FPOp(0), arm64FPOp(8), arm64RegOp("R1")}, Raw: "FCSELD R0, arg+0(FP), arg+8(FP), R1"}}, + {"FADDD", Instr{Op: "FADDD", Args: []Operand{arm64FPOp(0)}, Raw: "FADDD arg+0(FP)"}}, + {"FMADDD", Instr{Op: "FMADDD", Args: []Operand{arm64FPOp(0), arm64FPOp(8), arm64RegOp("R0")}, Raw: "FMADDD arg+0(FP), arg+8(FP), R0"}}, + {"FNMULD", Instr{Op: "FNMULD", Args: []Operand{arm64FPOp(0)}, Raw: "FNMULD arg+0(FP)"}}, + {"FABSD", Instr{Op: "FABSD", Args: []Operand{arm64RegOp("R0")}, Raw: "FABSD R0"}}, + {"FRINTZD", Instr{Op: "FRINTZD", Args: []Operand{arm64FPOp(0)}, Raw: "FRINTZD arg+0(FP)"}}, + {"FCVTZSD", Instr{Op: "FCVTZSD", Args: []Operand{arm64FPOp(0), arm64FPOp(16)}, Raw: "FCVTZSD arg+0(FP), ret+16(FP)"}}, + {"SCVTFD", Instr{Op: "SCVTFD", Args: []Operand{arm64FPOp(0), arm64RegOp("R0")}, Raw: "SCVTFD arg+0(FP), R0"}}, + } + for _, tc := range cases { + if ok, term, err := c.lowerFP(tc.op, tc.ins); !ok || term || err == nil { + t.Fatalf("lowerFP(%s %q) = (%v, %v, %v)", tc.op, tc.ins.Raw, ok, term, err) + } + } + + if got, err := c.evalFMOVDBits(arm64ImmOp(9)); err != nil || got != "9" { + t.Fatalf("evalFMOVDBits(imm) = (%q, %v)", got, err) + } + if _, err := c.evalFMOVDBits(Operand{Kind: OpRegShift, Reg: "R0", ShiftOp: "BAD", ShiftAmount: 1}); err == nil { + t.Fatalf("evalFMOVDBits(bad shift) unexpectedly succeeded") + } +} + +func TestARM64SyscallCoverage(t *testing.T) { + c, b := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{Name: "example.syscall", Ret: Void}, nil) + for _, tc := range []struct { + r Reg + v string + }{ + {"R0", "1"}, + {"R1", "2"}, + {"R2", "3"}, + {"R3", "4"}, + {"R4", "5"}, + {"R5", "6"}, + {"R8", "7"}, + {"R16", "8"}, + } { + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + + if ok, term, err := c.lowerSyscall("SVC", Instr{Raw: "SVC", Args: nil}); !ok || term || err != nil { + t.Fatalf("lowerSyscall(linux) = (%v, %v, %v)", ok, term, err) + } + + darwin, db := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{Name: "example.darwin", Ret: Void}, nil) + delete(darwin.regSlot, Reg("R8")) + for _, tc := range []struct { + r Reg + v string + }{ + {"R0", "11"}, + {"R1", "12"}, + {"R2", "13"}, + {"R3", "14"}, + {"R4", "15"}, + {"R5", "16"}, + {"R16", "17"}, + } { + if err := darwin.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("darwin storeReg(%s) error = %v", tc.r, err) + } + } + if ok, term, err := darwin.lowerSyscall("SVC", Instr{Raw: "SVC $0x80", Args: []Operand{{Kind: OpImm, Imm: 0x80}}}); !ok || term || err != nil { + t.Fatalf("lowerSyscall(darwin) = (%v, %v, %v)", ok, term, err) + } + if _, _, err := c.lowerSyscall("SVC", Instr{Raw: "SVC R0", Args: []Operand{{Kind: OpReg, Reg: "R0"}}}); err == nil { + t.Fatalf("bad SVC unexpectedly succeeded") + } + if ok, term, err := c.lowerSyscall("BAD", Instr{}); ok || term || err != nil { + t.Fatalf("lowerSyscall(BAD) = (%v, %v, %v)", ok, term, err) + } + + out := b.String() + db.String() + for _, want := range []string{ + "call i64 @syscall(i64 %t", + "@cliteErrno()", + "sub i64 0", + "store i1 %t", + "store i1 false, ptr %flags_v", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestARM64ArithmeticErrorCoverage(t *testing.T) { + c, _ := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{Name: "example.aritherr", Ret: Void}, nil) + for _, tc := range []Instr{ + {Op: "MRS", Raw: "MRS R0, R1", Args: []Operand{arm64RegOp("R0"), arm64RegOp("R1")}}, + {Op: "MSR", Raw: "MSR $1, R0", Args: []Operand{arm64ImmOp(1), arm64RegOp("R0")}}, + {Op: "MSR", Raw: "MSR label, TPIDR_EL0", Args: []Operand{{Kind: OpIdent, Ident: "label"}, arm64IdentOp("TPIDR_EL0")}}, + {Op: "UBFX", Raw: "UBFX $1, R0, R1", Args: []Operand{arm64ImmOp(1), arm64RegOp("R0"), arm64RegOp("R1")}}, + {Op: "ADD", Raw: "ADD $1", Args: []Operand{arm64ImmOp(1)}}, + {Op: "ADD", Raw: "ADD $1, label", Args: []Operand{arm64ImmOp(1), arm64IdentOp("label")}}, + {Op: "ADD", Raw: "ADD $1, R0, label", Args: []Operand{arm64ImmOp(1), arm64RegOp("R0"), arm64IdentOp("label")}}, + {Op: "ADC", Raw: "ADC $1", Args: []Operand{arm64ImmOp(1)}}, + {Op: "ADC", Raw: "ADC $1, label", Args: []Operand{arm64ImmOp(1), arm64IdentOp("label")}}, + {Op: "ADC", Raw: "ADC $1, R0, label", Args: []Operand{arm64ImmOp(1), arm64RegOp("R0"), arm64IdentOp("label")}}, + {Op: "ADDW", Raw: "ADDW $1", Args: []Operand{arm64ImmOp(1)}}, + {Op: "ADDW", Raw: "ADDW $1, label", Args: []Operand{arm64ImmOp(1), arm64IdentOp("label")}}, + {Op: "SUBW", Raw: "SUBW $1, R0, label", Args: []Operand{arm64ImmOp(1), arm64RegOp("R0"), arm64IdentOp("label")}}, + {Op: "AND", Raw: "AND $1", Args: []Operand{arm64ImmOp(1)}}, + {Op: "AND", Raw: "AND $1, label", Args: []Operand{arm64ImmOp(1), arm64IdentOp("label")}}, + {Op: "AND", Raw: "AND $1, R0, label", Args: []Operand{arm64ImmOp(1), arm64RegOp("R0"), arm64IdentOp("label")}}, + {Op: "ANDSW", Raw: "ANDSW $1", Args: []Operand{arm64ImmOp(1)}}, + {Op: "ANDSW", Raw: "ANDSW $1, label", Args: []Operand{arm64ImmOp(1), arm64IdentOp("label")}}, + {Op: "ANDSW", Raw: "ANDSW $1, R0, label", Args: []Operand{arm64ImmOp(1), arm64RegOp("R0"), arm64IdentOp("label")}}, + {Op: "SUBS", Raw: "SUBS $1", Args: []Operand{arm64ImmOp(1)}}, + {Op: "SUBS", Raw: "SUBS $1, label", Args: []Operand{arm64ImmOp(1), arm64IdentOp("label")}}, + {Op: "SUBS", Raw: "SUBS $1, R0, label", Args: []Operand{arm64ImmOp(1), arm64RegOp("R0"), arm64IdentOp("label")}}, + {Op: "BIC", Raw: "BIC $1, label", Args: []Operand{arm64ImmOp(1), arm64IdentOp("label")}}, + {Op: "BIC", Raw: "BIC $1, R0, label", Args: []Operand{arm64ImmOp(1), arm64RegOp("R0"), arm64IdentOp("label")}}, + {Op: "BICW", Raw: "BICW $1, label", Args: []Operand{arm64ImmOp(1), arm64IdentOp("label")}}, + {Op: "BICW", Raw: "BICW $1, R0, label", Args: []Operand{arm64ImmOp(1), arm64RegOp("R0"), arm64IdentOp("label")}}, + {Op: "MVN", Raw: "MVN $1, label", Args: []Operand{arm64ImmOp(1), arm64IdentOp("label")}}, + {Op: "MVNW", Raw: "MVNW $1, label", Args: []Operand{arm64ImmOp(1), arm64IdentOp("label")}}, + {Op: "CRC32B", Raw: "CRC32B $1, R0", Args: []Operand{arm64ImmOp(1), arm64RegOp("R0")}}, + {Op: "CMP", Raw: "CMP R0", Args: []Operand{arm64RegOp("R0")}}, + {Op: "CMN", Raw: "CMN R0", Args: []Operand{arm64RegOp("R0")}}, + {Op: "NEG", Raw: "NEG R0, label", Args: []Operand{arm64RegOp("R0"), arm64IdentOp("label")}}, + {Op: "MUL", Raw: "MUL $1", Args: []Operand{arm64ImmOp(1)}}, + {Op: "MUL", Raw: "MUL $1, label", Args: []Operand{arm64ImmOp(1), arm64IdentOp("label")}}, + {Op: "MUL", Raw: "MUL $1, R0, label", Args: []Operand{arm64ImmOp(1), arm64RegOp("R0"), arm64IdentOp("label")}}, + {Op: "UMULH", Raw: "UMULH R0, R1", Args: []Operand{arm64RegOp("R0"), arm64RegOp("R1")}}, + {Op: "MADD", Raw: "MADD R0, R1, R2", Args: []Operand{arm64RegOp("R0"), arm64RegOp("R1"), arm64RegOp("R2")}}, + } { + if _, _, err := c.lowerArith(tc.Op, tc); err == nil { + t.Fatalf("%s %q unexpectedly succeeded", tc.Op, tc.Raw) + } + } +} + +func TestARM64VectorEdgeCoverage(t *testing.T) { + fn := Func{ + Instrs: []Instr{ + {Op: "VMOV", Args: []Operand{arm64RegOp("V0"), arm64RegOp("V1")}}, + {Op: "VMOV", Args: []Operand{arm64RegOp("V2"), arm64RegOp("V3")}}, + {Op: "VMOV", Args: []Operand{arm64RegOp("V4"), arm64RegOp("V5")}}, + {Op: "VMOV", Args: []Operand{arm64RegOp("V6"), arm64RegOp("V7")}}, + {Op: "VMOV", Args: []Operand{arm64RegOp("V8"), arm64RegOp("V9")}}, + }, + } + c, b := newARM64CtxWithFuncForTest(t, fn, FuncSig{Name: "example.vecedge", Ret: Void}, nil) + for _, tc := range []struct { + r Reg + v string + }{ + {"R0", "41"}, + {"R1", "42"}, + {"R2", "43"}, + {"R3", "44"}, + {"R4", "45"}, + {"R5", "46"}, + {"R20", "4096"}, + } { + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + + check := func(op Op, postInc bool, ins Instr) { + t.Helper() + if ok, term, err := c.lowerVec(op, postInc, ins); !ok || term || err != nil { + t.Fatalf("lowerVec(%s %q) = (%v, %v, %v)", op, ins.Raw, ok, term, err) + } + } + + check("VLD1R", false, Instr{Op: "VLD1R", Raw: "VLD1R (R20), V0"}) + check("VMOV", false, Instr{Op: "VMOV", Raw: "VMOV R0, V0.S4", Args: []Operand{arm64RegOp("R0"), arm64RegOp("V0.S4")}}) + check("VMOV", false, Instr{Op: "VMOV", Raw: "VMOV V0.S[2], R3", Args: []Operand{arm64RegOp("V0.S[2]"), arm64RegOp("R3")}}) + check("VMOV", false, Instr{Op: "VMOV", Raw: "VMOV V1.H[4], R4", Args: []Operand{arm64RegOp("V1.H[4]"), arm64RegOp("R4")}}) + check("VMOV", false, Instr{Op: "VMOV", Raw: "VMOV V2.B[7], R5", Args: []Operand{arm64RegOp("V2.B[7]"), arm64RegOp("R5")}}) + check("VMOV", false, Instr{Op: "VMOV", Raw: "VMOV V2, R0", Args: []Operand{arm64RegOp("V2"), arm64RegOp("R0")}}) + check("VLD1", true, Instr{Op: "VLD1.P", Raw: "VLD1.P (R20), V3.D[1]", Args: []Operand{arm64MemOp("R20", 0), arm64RegOp("V3.D[1]")}}) + check("VLD1", true, Instr{Op: "VLD1.P", Raw: "VLD1.P (R20), V4.S[1]", Args: []Operand{arm64MemOp("R20", 0), arm64RegOp("V4.S[1]")}}) + check("VLD1", true, Instr{Op: "VLD1.P", Raw: "VLD1.P (R20), V5.H[3]", Args: []Operand{arm64MemOp("R20", 0), arm64RegOp("V5.H[3]")}}) + check("VLD1", true, Instr{Op: "VLD1.P (R20), [V6]", Args: []Operand{arm64MemOp("R20", 0), arm64RegListOp("V6")}}) + check("VLD1", true, Instr{Op: "VLD1.P (R20), [V7, V8, V9]", Args: []Operand{arm64MemOp("R20", 0), arm64RegListOp("V7", "V8", "V9")}}) + check("VST1", true, Instr{Op: "VST1.P", Raw: "VST1.P [V0], (R20)", Args: []Operand{arm64RegListOp("V0"), arm64MemOp("R20", 0)}}) + check("VST1", true, Instr{Op: "VST1.P", Raw: "VST1.P [V1, V2, V3], (R20)", Args: []Operand{arm64RegListOp("V1", "V2", "V3"), arm64MemOp("R20", 0)}}) + + for _, tc := range []Instr{ + {Op: "VMOV", Raw: "VMOV R0", Args: []Operand{arm64RegOp("R0")}}, + {Op: "VMOV", Raw: "VMOV V0.Q[0], R0", Args: []Operand{arm64RegOp("V0.Q[0]"), arm64RegOp("R0")}}, + {Op: "VMOV", Raw: "VMOV label, R0", Args: []Operand{arm64IdentOp("label"), arm64RegOp("R0")}}, + {Op: "VEOR", Raw: "VEOR V0, V1", Args: []Operand{arm64RegOp("V0"), arm64RegOp("V1")}}, + {Op: "VORR", Raw: "VORR V0, V1", Args: []Operand{arm64RegOp("V0"), arm64RegOp("V1")}}, + {Op: "VLD1", Raw: "VLD1 (R20), V0", Args: []Operand{arm64MemOp("R20", 0), arm64RegOp("V0")}}, + {Op: "VLD1", Raw: "VLD1 (R20), [V0,V1,V2,V3,V4]", Args: []Operand{arm64MemOp("R20", 0), arm64RegListOp("V0", "V1", "V2", "V3", "V4")}}, + {Op: "VST1", Raw: "VST1 V0, (R20)", Args: []Operand{arm64RegOp("V0"), arm64MemOp("R20", 0)}}, + {Op: "VST1", Raw: "VST1 [V0,V1,V2,V3,V4], (R20)", Args: []Operand{arm64RegListOp("V0", "V1", "V2", "V3", "V4"), arm64MemOp("R20", 0)}}, + {Op: "VCMEQ", Raw: "VCMEQ V0, V1", Args: []Operand{arm64RegOp("V0"), arm64RegOp("V1")}}, + {Op: "VAND", Raw: "VAND V0, V1", Args: []Operand{arm64RegOp("V0"), arm64RegOp("V1")}}, + {Op: "VADDP", Raw: "VADDP V0, V1", Args: []Operand{arm64RegOp("V0"), arm64RegOp("V1")}}, + {Op: "VUADDLV", Raw: "VUADDLV V0", Args: []Operand{arm64RegOp("V0")}}, + {Op: "VADD", Raw: "VADD V0", Args: []Operand{arm64RegOp("V0")}}, + } { + if _, _, err := c.lowerVec(tc.Op, false, tc); err == nil { + t.Fatalf("%s %q unexpectedly succeeded", tc.Op, tc.Raw) + } + } + + out := b.String() + for _, want := range []string{ + "bitcast <16 x i8> ", + "insertelement <8 x i16>", + "insertelement <4 x i32>", + "extractelement <8 x i16>", + "extractelement <4 x i32>", + "load i64, ptr %t", + "load i32, ptr %t", + "load i16, ptr %t", + "load <16 x i8>, ptr", + "store <16 x i8>", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestARM64BranchErrorCoverage(t *testing.T) { + fn := Func{ + Instrs: []Instr{ + {Op: OpLABEL, Args: []Operand{{Kind: OpLabel, Sym: "entry"}}}, + {Op: OpLABEL, Args: []Operand{{Kind: OpLabel, Sym: "fall"}}}, + {Op: OpLABEL, Args: []Operand{{Kind: OpLabel, Sym: "done"}}}, + }, + } + sigs := map[string]FuncSig{ + "example.ret8": {Name: "example.ret8", Args: []LLVMType{I64}, Ret: I8}, + "example.retptr": {Name: "example.retptr", Args: []LLVMType{I64}, Ret: Ptr}, + "example.badarg": {Name: "example.badarg", Args: []LLVMType{LLVMType("double")}, Ret: Void}, + "example.badret": {Name: "example.badret", Args: []LLVMType{I64}, Ret: LLVMType("double")}, + "example.badagg": {Name: "example.badagg", Args: []LLVMType{LLVMType("{ i32, double }")}, Ret: Void}, + "example.badargreg": {Name: "example.badargreg", Args: []LLVMType{I64}, ArgRegs: []Reg{"BAD"}, Ret: Void}, + "example.casttail": {Name: "example.casttail", Args: []LLVMType{I32, I64, I64, Ptr}, Ret: I64}, + "example.badtailtype": {Name: "example.badtailtype", Args: []LLVMType{LLVMType("double")}, ArgRegs: []Reg{"R0"}, Ret: I64}, + "example.structtail": {Name: "example.structtail", Args: []LLVMType{LLVMType("{ i32, i64 }")}, Ret: I64}, + "example.badtailreg": {Name: "example.badtailreg", Args: []LLVMType{LLVMType("double")}, ArgRegs: []Reg{"BAD"}, Ret: I64}, + "example.voidsink2": {Name: "example.voidsink2", Args: []LLVMType{I64}, Ret: Void}, + "example.nonvoid": {Name: "example.nonvoid", Args: []LLVMType{I64}, Ret: I64}, + } + sig := FuncSig{Name: "example.brancherr", Args: []LLVMType{I64, I8, Ptr, I64}, Ret: I64} + c, _ := newARM64CtxWithFuncForTest(t, fn, sig, sigs) + c.blocks = []arm64Block{{name: "entry"}, {name: "fall"}, {name: "done"}} + for _, tc := range []struct { + r Reg + v string + }{ + {"R0", "11"}, + {"R1", "12"}, + {"R2", "13"}, + {"R3", "14"}, + } { + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + emitBr := arm64TestEmitBr(c) + emitCondBr := arm64TestEmitCondBr(c) + + if tgt, ok := c.resolveBranchTarget(2, arm64MemOp(PC, 4)); !ok || tgt != c.blocks[2].name { + t.Fatalf("resolveBranchTarget(last,+4) = (%q, %v)", tgt, ok) + } + if tgt, ok := c.resolveBranchTarget(0, arm64MemOp(PC, 0)); !ok || tgt != c.blocks[0].name { + t.Fatalf("resolveBranchTarget(0(PC)) = (%q, %v)", tgt, ok) + } + if _, ok := c.resolveBranchTarget(0, arm64ImmOp(1)); ok { + t.Fatalf("resolveBranchTarget($1) unexpectedly succeeded") + } + if ok, term, err := c.lowerBranch(1, "B", Instr{Raw: "B 0(PC)", Args: []Operand{arm64MemOp(PC, 0)}}, emitBr, emitCondBr); !ok || term || err == nil { + t.Fatalf("lowerBranch(B 0(PC)) = (%v, %v, %v), want error", ok, term, err) + } + + errCases := []struct { + name string + run func() error + }{ + {"BL no args", func() error { + _, _, err := c.lowerBranch(1, "BL", Instr{Raw: "BL", Args: nil}, emitBr, emitCondBr) + return err + }}, + {"BL bad target", func() error { + _, _, err := c.lowerBranch(1, "BL", Instr{Raw: "BL $1", Args: []Operand{arm64ImmOp(1)}}, emitBr, emitCondBr) + return err + }}, + {"B no args", func() error { + _, _, err := c.lowerBranch(1, "B", Instr{Raw: "B", Args: nil}, emitBr, emitCondBr) + return err + }}, + {"B bad target", func() error { + _, _, err := c.lowerBranch(1, "B", Instr{Raw: "B $1", Args: []Operand{arm64ImmOp(1)}}, emitBr, emitCondBr) + return err + }}, + {"BEQ missing arg", func() error { + _, _, err := c.lowerBranch(1, "BEQ", Instr{Raw: "BEQ", Args: nil}, emitBr, emitCondBr) + return err + }}, + {"BEQ invalid target", func() error { + _, _, err := c.lowerBranch(1, "BEQ", Instr{Raw: "BEQ R0", Args: []Operand{arm64RegOp("R0")}}, emitBr, emitCondBr) + return err + }}, + {"BEQ emit error", func() error { + _, _, err := c.lowerBranch(1, "BEQ", Instr{Raw: "BEQ done", Args: []Operand{arm64IdentOp("done")}}, emitBr, func(string, string, string) error { + return fmt.Errorf("emit cond failure") + }) + return err + }}, + {"CBZ bad args", func() error { + _, _, err := c.lowerBranch(1, "CBZ", Instr{Raw: "CBZ $1, done", Args: []Operand{arm64ImmOp(1), arm64IdentOp("done")}}, emitBr, emitCondBr) + return err + }}, + {"TBZ bad args", func() error { + _, _, err := c.lowerBranch(1, "TBZ", Instr{Raw: "TBZ R0, done", Args: []Operand{arm64RegOp("R0"), arm64IdentOp("done")}}, emitBr, emitCondBr) + return err + }}, + {"CBZW bad args", func() error { + _, _, err := c.lowerBranch(1, "CBZW", Instr{Raw: "CBZW $1, done", Args: []Operand{arm64ImmOp(1), arm64IdentOp("done")}}, emitBr, emitCondBr) + return err + }}, + } + for _, tc := range errCases { + if err := tc.run(); err == nil { + t.Fatalf("%s unexpectedly succeeded", tc.name) + } + } + + cNoNext, _ := newARM64CtxWithFuncForTest(t, Func{}, sig, sigs) + cNoNext.blocks = []arm64Block{{name: "solo"}} + if _, _, err := cNoNext.lowerBranch(0, "BEQ", Instr{Raw: "BEQ done", Args: []Operand{arm64IdentOp("done")}}, arm64TestEmitBr(cNoNext), arm64TestEmitCondBr(cNoNext)); err == nil { + t.Fatalf("BEQ without fallthrough unexpectedly succeeded") + } + if _, _, err := cNoNext.lowerBranch(0, "CBZ", Instr{Raw: "CBZ R0, done", Args: []Operand{arm64RegOp("R0"), arm64IdentOp("done")}}, arm64TestEmitBr(cNoNext), arm64TestEmitCondBr(cNoNext)); err == nil { + t.Fatalf("CBZ without fallthrough unexpectedly succeeded") + } + if _, _, err := cNoNext.lowerBranch(0, "TBZ", Instr{Raw: "TBZ $1, R0, done", Args: []Operand{arm64ImmOp(1), arm64RegOp("R0"), arm64IdentOp("done")}}, arm64TestEmitBr(cNoNext), arm64TestEmitCondBr(cNoNext)); err == nil { + t.Fatalf("TBZ without fallthrough unexpectedly succeeded") + } + if _, _, err := cNoNext.lowerBranch(0, "CBZW", Instr{Raw: "CBZW R0, done", Args: []Operand{arm64RegOp("R0"), arm64IdentOp("done")}}, arm64TestEmitBr(cNoNext), arm64TestEmitCondBr(cNoNext)); err == nil { + t.Fatalf("CBZW without fallthrough unexpectedly succeeded") + } + + cBadReg, _ := newARM64CtxWithFuncForTest(t, fn, sig, sigs) + cBadReg.blocks = []arm64Block{{name: "entry"}, {name: "fall"}, {name: "done"}} + delete(cBadReg.regSlot, Reg("R0")) + if _, _, err := cBadReg.lowerBranch(1, "BL", Instr{Raw: "BL R0", Args: []Operand{arm64RegOp("R0")}}, arm64TestEmitBr(cBadReg), arm64TestEmitCondBr(cBadReg)); err == nil { + t.Fatalf("BL with missing reg unexpectedly succeeded") + } + if _, _, err := cBadReg.lowerBranch(1, "CBZ", Instr{Raw: "CBZ R0, done", Args: []Operand{arm64RegOp("R0"), arm64IdentOp("done")}}, arm64TestEmitBr(cBadReg), arm64TestEmitCondBr(cBadReg)); err == nil { + t.Fatalf("CBZ with missing reg unexpectedly succeeded") + } + if _, _, err := cBadReg.lowerBranch(1, "TBZ", Instr{Raw: "TBZ $1, R0, done", Args: []Operand{arm64ImmOp(1), arm64RegOp("R0"), arm64IdentOp("done")}}, arm64TestEmitBr(cBadReg), arm64TestEmitCondBr(cBadReg)); err == nil { + t.Fatalf("TBZ with missing reg unexpectedly succeeded") + } + if _, _, err := cBadReg.lowerBranch(1, "CBZW", Instr{Raw: "CBZW R0, done", Args: []Operand{arm64RegOp("R0"), arm64IdentOp("done")}}, arm64TestEmitBr(cBadReg), arm64TestEmitCondBr(cBadReg)); err == nil { + t.Fatalf("CBZW with missing reg unexpectedly succeeded") + } + + cBadMem, _ := newARM64CtxWithFuncForTest(t, fn, sig, sigs) + cBadMem.blocks = []arm64Block{{name: "entry"}, {name: "fall"}, {name: "done"}} + delete(cBadMem.regSlot, Reg("R9")) + if _, _, err := cBadMem.lowerBranch(1, "BL", Instr{Raw: "BL 8(R9)", Args: []Operand{arm64MemOp("R9", 8)}}, arm64TestEmitBr(cBadMem), arm64TestEmitCondBr(cBadMem)); err == nil { + t.Fatalf("BL with bad mem unexpectedly succeeded") + } + if _, _, err := cBadMem.lowerBranch(1, "B", Instr{Raw: "B 8(R9)", Args: []Operand{arm64MemOp("R9", 8)}}, arm64TestEmitBr(cBadMem), arm64TestEmitCondBr(cBadMem)); err == nil { + t.Fatalf("B with bad mem unexpectedly succeeded") + } + + if err := c.callSym(arm64SymOp("ret8(SB)")); err != nil { + t.Fatalf("callSym(ret8) error = %v", err) + } + if err := c.callSym(arm64SymOp("retptr(SB)")); err != nil { + t.Fatalf("callSym(retptr) error = %v", err) + } + if err := c.callSym(arm64RegOp("R0")); err == nil { + t.Fatalf("callSym(non-sym) unexpectedly succeeded") + } + if err := c.callSym(arm64SymOp("broken")); err == nil { + t.Fatalf("callSym(missing suffix) unexpectedly succeeded") + } + if err := c.callSym(arm64SymOp("badarg(SB)")); err == nil { + t.Fatalf("callSym(badarg) unexpectedly succeeded") + } + if err := c.callSym(arm64SymOp("badret(SB)")); err == nil { + t.Fatalf("callSym(badret) unexpectedly succeeded") + } + if err := c.callSym(arm64SymOp("badagg(SB)")); err == nil { + t.Fatalf("callSym(badagg) unexpectedly succeeded") + } + if err := c.callSym(arm64SymOp("badargreg(SB)")); err == nil { + t.Fatalf("callSym(badargreg) unexpectedly succeeded") + } + + cTailCast, bTailCast := newARM64CtxWithFuncForTest(t, fn, sig, sigs) + if err := cTailCast.tailCallAndRet(arm64SymOp("casttail(SB)")); err != nil { + t.Fatalf("tailCallAndRet(casttail) error = %v", err) + } + for _, want := range []string{ + "trunc i64 %t3 to i32", + "load i64, ptr %reg_R1", + "load i64, ptr %reg_R2", + "inttoptr i64 %t7 to ptr", + } { + if !strings.Contains(bTailCast.String(), want) { + t.Fatalf("tailCallAndRet(casttail) missing %q:\n%s", want, bTailCast.String()) + } + } + + cBadTailType, _ := newARM64CtxWithFuncForTest(t, fn, FuncSig{ + Name: "example.badtailtypecaller", + Args: []LLVMType{I64}, + Ret: I64, + }, sigs) + if err := cBadTailType.tailCallAndRet(arm64SymOp("badtailtype(SB)")); err == nil { + t.Fatalf("tailCallAndRet(badtailtype) unexpectedly succeeded") + } + + cStructTail, _ := newARM64CtxWithFuncForTest(t, fn, FuncSig{Name: "example.structcaller", Args: []LLVMType{I64}, Ret: I64}, sigs) + if err := cStructTail.storeReg("R0", "21"); err != nil { + t.Fatalf("storeReg(R0) error = %v", err) + } + if err := cStructTail.storeReg("R1", "22"); err != nil { + t.Fatalf("storeReg(R1) error = %v", err) + } + if err := cStructTail.tailCallAndRet(arm64SymOp("structtail(SB)")); err != nil { + t.Fatalf("tailCallAndRet(structtail) error = %v", err) + } + + cBadTailReg, _ := newARM64CtxWithFuncForTest(t, fn, FuncSig{Name: "example.badtailregcaller", Args: []LLVMType{I64}, Ret: I64}, sigs) + delete(cBadTailReg.regSlot, Reg("BAD")) + if err := cBadTailReg.tailCallAndRet(arm64SymOp("badtailreg(SB)")); err == nil { + t.Fatalf("tailCallAndRet(badtailreg) unexpectedly succeeded") + } + + cZeroRet, bZeroRet := newARM64CtxWithFuncForTest(t, fn, FuncSig{Name: "example.zeroTail", Args: []LLVMType{I64}, Ret: I64}, sigs) + if err := cZeroRet.tailCallAndRet(arm64SymOp("voidsink2(SB)")); err != nil { + t.Fatalf("tailCallAndRet(voidsink2) error = %v", err) + } + if !strings.Contains(bZeroRet.String(), "ret i64 0") { + t.Fatalf("tailCallAndRet(voidsink2) output = \n%s", bZeroRet.String()) + } + + cVoidCaller, bVoidCaller := newARM64CtxWithFuncForTest(t, fn, FuncSig{Name: "example.voidCaller", Args: []LLVMType{I64}, Ret: Void}, sigs) + if err := cVoidCaller.tailCallAndRet(arm64SymOp("nonvoid(SB)")); err != nil { + t.Fatalf("tailCallAndRet(nonvoid) error = %v", err) + } + if !strings.Contains(bVoidCaller.String(), "ret void") { + t.Fatalf("tailCallAndRet(nonvoid) output = \n%s", bVoidCaller.String()) + } + + cVoidTail, bVoidTail := newARM64CtxWithFuncForTest(t, fn, FuncSig{Name: "example.voidTail", Args: []LLVMType{I64}, Ret: Void}, sigs) + if err := cVoidTail.tailCallAndRet(arm64SymOp("voidsink2(SB)")); err != nil { + t.Fatalf("tailCallAndRet(void->void) error = %v", err) + } + if !strings.Contains(bVoidTail.String(), "ret void") { + t.Fatalf("tailCallAndRet(void->void) output = \n%s", bVoidTail.String()) + } +} + +func TestARM64CondCoverage(t *testing.T) { + c, b := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{Name: "example.cond", Ret: Void}, nil) + for _, tc := range []struct { + r Reg + v string + }{ + {"R0", "5"}, + {"R1", "6"}, + {"R2", "7"}, + {"R3", "8"}, + {"R4", "9"}, + } { + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + c.setFlagsSub("9", "3", "6") + + for _, ins := range []Instr{ + {Op: "CSEL", Args: []Operand{arm64IdentOp("EQ"), arm64RegOp("R0"), arm64RegOp("R1"), arm64RegOp("R2")}, Raw: "CSEL EQ, R0, R1, R2"}, + {Op: "CSELW", Args: []Operand{arm64IdentOp("NE"), arm64RegOp("R1"), arm64RegOp("R2"), arm64RegOp("R3")}, Raw: "CSELW NE, R1, R2, R3"}, + {Op: "CSET", Args: []Operand{arm64IdentOp("GT"), arm64RegOp("R4")}, Raw: "CSET GT, R4"}, + {Op: "CNEG", Args: []Operand{arm64IdentOp("LT"), arm64RegOp("R0"), arm64RegOp("R1")}, Raw: "CNEG LT, R0, R1"}, + {Op: "CINC", Args: []Operand{arm64IdentOp("HI"), arm64RegOp("R1"), arm64RegOp("R2")}, Raw: "CINC HI, R1, R2"}, + } { + if ok, term, err := c.lowerCond(ins.Op, ins); !ok || term || err != nil { + t.Fatalf("lowerCond(%s %q) = (%v, %v, %v)", ins.Op, ins.Raw, ok, term, err) + } + } + + badCases := []Instr{ + {Op: "CSEL", Args: []Operand{arm64RegOp("R0"), arm64RegOp("R1"), arm64RegOp("R2"), arm64RegOp("R3")}, Raw: "CSEL R0, R1, R2, R3"}, + {Op: "CSELW", Args: []Operand{arm64IdentOp("EQ"), arm64RegOp("R0"), arm64RegOp("R1")}, Raw: "CSELW EQ, R0, R1"}, + {Op: "CSET", Args: []Operand{arm64IdentOp("EQ"), arm64ImmOp(1)}, Raw: "CSET EQ, $1"}, + {Op: "CNEG", Args: []Operand{arm64IdentOp("EQ"), arm64ImmOp(1), arm64RegOp("R0")}, Raw: "CNEG EQ, $1, R0"}, + {Op: "CINC", Args: []Operand{arm64IdentOp("EQ"), arm64RegOp("R0"), arm64ImmOp(1)}, Raw: "CINC EQ, R0, $1"}, + } + for _, ins := range badCases { + if ok, term, err := c.lowerCond(ins.Op, ins); !ok || term || err == nil { + t.Fatalf("lowerCond(%s bad) = (%v, %v, %v)", ins.Op, ok, term, err) + } + } + + cBadFlags, _ := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{Name: "example.badflags", Ret: Void}, nil) + if ok, term, err := cBadFlags.lowerCond("CSET", Instr{Op: "CSET", Args: []Operand{arm64IdentOp("EQ"), arm64RegOp("R0")}, Raw: "CSET EQ, R0"}); !ok || term || err == nil { + t.Fatalf("lowerCond(CSET no flags) = (%v, %v, %v)", ok, term, err) + } + + cBadReg, _ := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{Name: "example.badreg", Ret: Void}, nil) + cBadReg.setFlagsSub("9", "3", "6") + delete(cBadReg.regSlot, Reg("R0")) + if ok, term, err := cBadReg.lowerCond("CNEG", Instr{Op: "CNEG", Args: []Operand{arm64IdentOp("EQ"), arm64RegOp("R0"), arm64RegOp("R1")}, Raw: "CNEG EQ, R0, R1"}); !ok || term || err == nil { + t.Fatalf("lowerCond(CNEG missing reg) = (%v, %v, %v)", ok, term, err) + } + + out := b.String() + for _, want := range []string{ + "select i1", + "trunc i64", + "zext i32", + "select i1 %", // CSET/CSEL + "sub i64 0", + "add i64", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestARM64EvalCoverage(t *testing.T) { + sig := FuncSig{ + Name: "example.eval64", + Args: []LLVMType{Ptr, I1, I8, I16, I32, I64, LLVMType("double"), LLVMType("float"), LLVMType("{ ptr, i64 }")}, + Ret: Void, + Frame: FrameLayout{ + Params: []FrameSlot{ + {Offset: 0, Type: Ptr, Index: 0}, + {Offset: 8, Type: I1, Index: 1}, + {Offset: 16, Type: I8, Index: 2}, + {Offset: 24, Type: I16, Index: 3}, + {Offset: 32, Type: I32, Index: 4}, + {Offset: 40, Type: I64, Index: 5}, + {Offset: 48, Type: LLVMType("double"), Index: 6}, + {Offset: 56, Type: LLVMType("float"), Index: 7}, + {Offset: 64, Type: I64, Index: 8, Field: 1}, + }, + Results: []FrameSlot{{Offset: 80, Type: I64, Index: 0}}, + }, + } + c, b := newARM64CtxWithFuncForTest(t, Func{}, sig, nil) + for _, tc := range []struct { + r Reg + v string + }{ + {"R0", "11"}, + {"R1", "12"}, + {"R2", "13"}, + {"R3", "14"}, + {"R4", "15"}, + } { + if err := c.storeReg(tc.r, tc.v); err != nil { + t.Fatalf("storeReg(%s) error = %v", tc.r, err) + } + } + + if got, base, inc, err := c.addrI64(MemRef{Base: "R0", Index: "R1", Scale: 4, Off: 8}, true); err != nil || got == "" || base != "R0" || inc != 8 { + t.Fatalf("addrI64() = (%q, %q, %d, %v)", got, base, inc, err) + } + if err := c.updatePostInc("R0", 8); err != nil { + t.Fatalf("updatePostInc(8) error = %v", err) + } + if err := c.updatePostInc("R0", 0); err != nil { + t.Fatalf("updatePostInc(0) error = %v", err) + } + if _, err := c.loadMem(MemRef{Base: "R2"}, 64, false); err != nil { + t.Fatalf("loadMem(64) error = %v", err) + } + if _, err := c.loadMem(MemRef{Base: "R2", Off: 4}, 32, true); err != nil { + t.Fatalf("loadMem(32) error = %v", err) + } + if _, err := c.loadMem(MemRef{Base: "R3"}, 16, false); err != nil { + t.Fatalf("loadMem(16) error = %v", err) + } + if _, err := c.loadMem(MemRef{Base: "R4"}, 8, false); err != nil { + t.Fatalf("loadMem(8) error = %v", err) + } + if _, err := c.loadMem(MemRef{Base: "R4"}, 7, false); err == nil { + t.Fatalf("loadMem(7) unexpectedly succeeded") + } + if err := c.storeMem(MemRef{Base: "R2"}, 64, false, "21"); err != nil { + t.Fatalf("storeMem(64) error = %v", err) + } + if err := c.storeMem(MemRef{Base: "R2", Off: 4}, 32, true, "22"); err != nil { + t.Fatalf("storeMem(32) error = %v", err) + } + if err := c.storeMem(MemRef{Base: "R3"}, 16, false, "23"); err != nil { + t.Fatalf("storeMem(16) error = %v", err) + } + if err := c.storeMem(MemRef{Base: "R4"}, 8, false, "24"); err != nil { + t.Fatalf("storeMem(8) error = %v", err) + } + if err := c.storeMem(MemRef{Base: "R4"}, 1, false, "1"); err != nil { + t.Fatalf("storeMem(1) error = %v", err) + } + if err := c.storeMem(MemRef{Base: "R4"}, 7, false, "1"); err == nil { + t.Fatalf("storeMem(7) unexpectedly succeeded") + } + + ops := []Operand{ + {Kind: OpImm, Imm: 7}, + {Kind: OpReg, Reg: "R0"}, + {Kind: OpRegShift, Reg: "R1", ShiftOp: ShiftLeft, ShiftAmount: 2}, + {Kind: OpRegShift, Reg: "R1", ShiftOp: ShiftRight, ShiftAmount: 1}, + {Kind: OpFP, FPOffset: 0}, + {Kind: OpFP, FPOffset: 8}, + {Kind: OpFP, FPOffset: 16}, + {Kind: OpFP, FPOffset: 24}, + {Kind: OpFP, FPOffset: 32}, + {Kind: OpFP, FPOffset: 40}, + {Kind: OpFP, FPOffset: 48}, + {Kind: OpFP, FPOffset: 56}, + {Kind: OpFP, FPOffset: 64}, + {Kind: OpFPAddr, FPOffset: 80}, + {Kind: OpMem, Mem: MemRef{Base: "R2", Off: 8}}, + {Kind: OpSym, Sym: "$runtime·main+8(SB)"}, + {Kind: OpSym, Sym: "8(R1)"}, + {Kind: OpSym, Sym: "plain_symbol"}, + {Kind: OpIdent, Ident: "NZCV"}, + } + for _, op := range ops { + if got, err := c.eval64(op, op.Kind == OpMem); err != nil || got == "" { + t.Fatalf("eval64(%s) = (%q, %v)", op.String(), got, err) + } + } + if got, err := c.eval64(Operand{Kind: OpFPAddr, FPOffset: 88}, false); err != nil || got != "0" { + t.Fatalf("eval64(missing fpaddr) = (%q, %v)", got, err) + } + if _, err := c.eval64(Operand{Kind: OpRegShift, Reg: "R1", ShiftOp: ShiftRotate, ShiftReg: "R2"}, false); err == nil { + t.Fatalf("eval64(register shift) unexpectedly succeeded") + } + if _, err := c.eval64(Operand{Kind: OpRegShift, Reg: "R1", ShiftOp: ShiftRotate, ShiftAmount: 1}, false); err == nil { + t.Fatalf("eval64(rotate) unexpectedly succeeded") + } + if _, err := c.eval64(Operand{Kind: OpLabel, Sym: "loop"}, false); err == nil { + t.Fatalf("eval64(label) unexpectedly succeeded") + } + + cBadSlot, _ := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{ + Name: "example.badslot", + Args: []LLVMType{LLVMType("{ ptr, { i64, i64 } }")}, + Ret: Void, + Frame: FrameLayout{ + Params: []FrameSlot{{Offset: 0, Type: LLVMType("{ i64, i64 }"), Index: 0, Field: 1}}, + }, + }, nil) + if _, err := cBadSlot.eval64(Operand{Kind: OpFP, FPOffset: 0}, false); err == nil { + t.Fatalf("eval64(bad field type) unexpectedly succeeded") + } + + cBadIndex, _ := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{ + Name: "example.badidx", + Args: []LLVMType{I64}, + Ret: Void, + Frame: FrameLayout{ + Params: []FrameSlot{{Offset: 0, Type: I64, Index: 2}}, + }, + }, nil) + if _, err := cBadIndex.eval64(Operand{Kind: OpFP, FPOffset: 0}, false); err == nil { + t.Fatalf("eval64(bad fp index) unexpectedly succeeded") + } + if _, err := cBadIndex.eval64(Operand{Kind: OpMem, Mem: MemRef{Base: "BAD"}}, false); err == nil { + t.Fatalf("eval64(bad mem base) unexpectedly succeeded") + } + + out := b.String() + for _, want := range []string{ + "mul i64", + "add i64", + "load i64, ptr", + "load i32, ptr", + "load i16, ptr", + "load i8, ptr", + "store i64 21, ptr", + "store i32", + "store i16", + "store i8", + "store i1", + "extractvalue { ptr, i64 } %arg8, 1", + `getelementptr i8, ptr @"runtime.main", i64 8`, + "ptrtoint ptr", + "bitcast double %arg6 to i64", + "bitcast float %arg7 to i32", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} + +func TestARM64FPResultCoverage(t *testing.T) { + c, b := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{ + Name: "example.fpresults", + Ret: Void, + Frame: FrameLayout{ + Results: []FrameSlot{ + {Offset: 8, Type: I64, Index: 0}, + {Offset: 16, Type: I32, Index: 1}, + {Offset: 24, Type: Ptr, Index: 2}, + {Offset: 32, Type: LLVMType("double"), Index: 3}, + {Offset: 40, Type: LLVMType("float"), Index: 4}, + }, + }, + }, nil) + if _, ok := c.fpResultSlotByOffset(99); ok { + t.Fatalf("fpResultSlotByOffset(99) unexpectedly found") + } + if err := c.storeFPResult64(8, "21"); err != nil { + t.Fatalf("storeFPResult64(i64) error = %v", err) + } + if err := c.storeFPResult64(16, "22"); err != nil { + t.Fatalf("storeFPResult64(i32) error = %v", err) + } + if err := c.storeFPResult64(24, "23"); err != nil { + t.Fatalf("storeFPResult64(ptr) error = %v", err) + } + if err := c.storeFPResult64(32, "24"); err != nil { + t.Fatalf("storeFPResult64(double) error = %v", err) + } + if err := c.storeFPResult64(40, "25"); err != nil { + t.Fatalf("storeFPResult64(float) error = %v", err) + } + if _, err := c.loadFPResult(FrameSlot{Index: 99, Type: I64}); err == nil { + t.Fatalf("loadFPResult(missing) unexpectedly succeeded") + } + for _, slot := range []FrameSlot{ + {Index: 0, Type: I64}, + {Index: 1, Type: I32}, + {Index: 2, Type: Ptr}, + {Index: 3, Type: LLVMType("double")}, + {Index: 4, Type: LLVMType("float")}, + } { + if got, err := c.loadFPResult(slot); err != nil || got == "" { + t.Fatalf("loadFPResult(%v) = (%q, %v)", slot, got, err) + } + } + + if err := c.storeReg("R0", "31"); err != nil { + t.Fatalf("storeReg(R0) error = %v", err) + } + if err := c.storeReg("R1", "32"); err != nil { + t.Fatalf("storeReg(R1) error = %v", err) + } + if err := c.storeReg("R2", "33"); err != nil { + t.Fatalf("storeReg(R2) error = %v", err) + } + if err := c.storeReg("R3", "34"); err != nil { + t.Fatalf("storeReg(R3) error = %v", err) + } + if got, err := c.loadRetSlotFallback(FrameSlot{Index: 40, Type: I16}); err != nil || got != "0" { + t.Fatalf("loadRetSlotFallback(out-of-range) = (%q, %v)", got, err) + } + for _, slot := range []FrameSlot{ + {Index: 0, Type: I64}, + {Index: 1, Type: Ptr}, + {Index: 2, Type: LLVMType("double")}, + {Index: 3, Type: LLVMType("float")}, + {Index: 1, Type: I8}, + } { + if got, err := c.loadRetSlotFallback(slot); err != nil || got == "" { + t.Fatalf("loadRetSlotFallback(%v) = (%q, %v)", slot, got, err) + } + } + if _, err := c.loadRetSlotFallback(FrameSlot{Index: 0, Type: LLVMType("{ i64, i64 }")}); err == nil { + t.Fatalf("loadRetSlotFallback(aggregate) unexpectedly succeeded") + } + + cMissingMeta, _ := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{Name: "example.missingmeta", Ret: Void}, nil) + cMissingMeta.fpResAllocaOff[8] = "%fake" + if err := cMissingMeta.storeFPResult64(8, "1"); err == nil { + t.Fatalf("storeFPResult64(missing meta) unexpectedly succeeded") + } + cBadType, _ := newARM64CtxWithFuncForTest(t, Func{}, FuncSig{ + Name: "example.badtype", + Ret: Void, + Frame: FrameLayout{ + Results: []FrameSlot{{Offset: 8, Type: LLVMType("{ i64, i64 }"), Index: 0}}, + }, + }, nil) + if err := cBadType.storeFPResult64(8, "1"); err == nil { + t.Fatalf("storeFPResult64(bad type) unexpectedly succeeded") + } + if err := cBadType.storeFPResult64(99, "1"); err == nil { + t.Fatalf("storeFPResult64(bad off) unexpectedly succeeded") + } + + out := b.String() + for _, want := range []string{ + "store i64 21, ptr %fp_ret_0", + "trunc i64 22 to i32", + "inttoptr i64 23 to ptr", + "bitcast i64 24 to double", + "bitcast i32 %", // float path + "load ptr, ptr %fp_ret_2", + "bitcast i64 %", // fallback double + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in output:\n%s", want, out) + } + } +} diff --git a/cmd/plan9asmscan/main_test.go b/cmd/plan9asmscan/main_test.go index ca0665d..6f0e176 100644 --- a/cmd/plan9asmscan/main_test.go +++ b/cmd/plan9asmscan/main_test.go @@ -2,7 +2,9 @@ package main import ( "encoding/json" + "flag" "os" + "os/exec" "path/filepath" "reflect" "runtime" @@ -307,3 +309,120 @@ func TestBuildReportAndJSONShape(t *testing.T) { t.Fatalf("json output missing goarch: %s", js) } } + +func TestClusterAndFamilyCoverageMore(t *testing.T) { + clusterCases := []struct { + goarch string + op string + want string + }{ + {"amd64", "RET", "x86-control"}, + {"amd64", "VZEROUPPER", "x86-simd"}, + {"amd64", "CRC32Q", "x86-crc"}, + {"amd64", "LOCK", "x86-atomic"}, + {"amd64", "BTQ", "x86-bit-shift"}, + {"amd64", "MOVQ", "x86-scalar"}, + {"arm64", "VADD", "arm64-neon"}, + {"arm64", "CBNZ", "arm64-control"}, + {"arm64", "SWPALD", "arm64-atomic"}, + {"arm64", "RBIT", "arm64-bit-shift"}, + {"arm64", "ADD", "arm64-scalar"}, + {"other", "MOV", "other"}, + } + for _, tc := range clusterCases { + if got := clusterOf(tc.goarch, tc.op); got != tc.want { + t.Fatalf("clusterOf(%q, %q) = %q, want %q", tc.goarch, tc.op, got, tc.want) + } + } + + familyCases := []struct { + goarch string + op string + want string + }{ + {"amd64", "AESENC", "aes"}, + {"amd64", "SHA256MSG2", "sha"}, + {"amd64", "VGF2P8AFFINEQB", "gfni"}, + {"amd64", "KORW", "avx512-mask"}, + {"amd64", "VZEROALL", "avx-vector"}, + {"amd64", "MOVAPS", "sse-simd"}, + {"amd64", "ADCXQ", "bmi2-adx"}, + {"amd64", "MFENCE", "atomic-memory"}, + {"amd64", "JCS", "branch-alias"}, + {"amd64", "SHRQ", "bit-rotate-shift"}, + {"amd64", "ADJSP", "move-pseudo"}, + {"amd64", "ADDQ", "scalar-misc"}, + {"arm64", "AESD", "crypto"}, + {"arm64", "VEOR", "neon"}, + {"arm64", "TBZ", "branch"}, + {"arm64", "CASALD", "atomic-memory"}, + {"arm64", "ADD", "scalar-misc"}, + {"other", "X", "other"}, + } + for _, tc := range familyCases { + if got := familyOf(tc.goarch, tc.op); got != tc.want { + t.Fatalf("familyOf(%q, %q) = %q, want %q", tc.goarch, tc.op, got, tc.want) + } + } +} + +func TestMainAndFatalfSubprocess(t *testing.T) { + testBin := os.Args[0] + if os.Getenv("PLAN9ASMSCAN_MAIN_HELPER") == "1" { + oldArgs := os.Args + defer func() { os.Args = oldArgs }() + flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) + os.Args = []string{ + "plan9asmscan", + "-goos=linux", + "-goarch=amd64", + "-format=json", + "-repo-root=../..", + "-out=" + filepath.Join(os.TempDir(), "plan9asmscan-test.json"), + } + main() + return + } + if os.Getenv("PLAN9ASMSCAN_FATALF_HELPER") == "1" { + fatalf("boom %d", 7) + return + } + + outPath := filepath.Join(t.TempDir(), "report.json") + oldArgs := os.Args + oldFlags := flag.CommandLine + defer func() { + os.Args = oldArgs + flag.CommandLine = oldFlags + }() + flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) + os.Args = []string{ + "plan9asmscan", + "-goos=linux", + "-goarch=amd64", + "-format=json", + "-repo-root=../..", + "-out=" + outPath, + } + main() + if data, err := os.ReadFile(outPath); err != nil || !strings.Contains(string(data), `"goarch": "amd64"`) { + t.Fatalf("main() output = (%q, %v)", string(data), err) + } + + cmd := exec.Command(testBin, "-test.run=TestMainAndFatalfSubprocess") + cmd.Env = append(os.Environ(), "PLAN9ASMSCAN_MAIN_HELPER=1") + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("main helper failed: %v\n%s", err, out) + } + + fcmd := exec.Command(testBin, "-test.run=TestMainAndFatalfSubprocess") + fcmd.Env = append(os.Environ(), "PLAN9ASMSCAN_FATALF_HELPER=1") + fout, err := fcmd.CombinedOutput() + if err == nil { + t.Fatalf("fatalf helper unexpectedly succeeded") + } + if !strings.Contains(string(fout), "boom 7") { + t.Fatalf("fatalf output = %q, want boom 7", string(fout)) + } +} diff --git a/feature_attrs_test.go b/feature_attrs_test.go index 2e12d58..e741190 100644 --- a/feature_attrs_test.go +++ b/feature_attrs_test.go @@ -3,7 +3,10 @@ package plan9asm -import "testing" +import ( + "strings" + "testing" +) func TestInferFuncTargetFeatures(t *testing.T) { tests := []struct { @@ -68,3 +71,37 @@ func TestInferFuncTargetFeatures(t *testing.T) { }) } } + +func TestFeatureAttrRegistry(t *testing.T) { + r := newFeatureAttrRegistry() + if got := r.ref(""); got != "" { + t.Fatalf("ref(\"\") = %q", got) + } + if got := r.ref("+aes"); got != "#200" { + t.Fatalf("ref(+aes) = %q", got) + } + if got := r.ref("+crc"); got != "#201" { + t.Fatalf("ref(+crc) = %q", got) + } + if got := r.ref("+aes"); got != "#200" { + t.Fatalf("ref(+aes repeat) = %q", got) + } + + var b strings.Builder + r.emit(&b) + out := b.String() + for _, want := range []string{ + `attributes #200 = { "target-features"="+aes" }`, + `attributes #201 = { "target-features"="+crc" }`, + } { + if !strings.Contains(out, want) { + t.Fatalf("emit() missing %q in:\n%s", want, out) + } + } + + var empty strings.Builder + newFeatureAttrRegistry().emit(&empty) + if empty.String() != "" { + t.Fatalf("emit(empty) = %q", empty.String()) + } +} diff --git a/floatlit_test.go b/floatlit_test.go index f651dcc..d542987 100644 --- a/floatlit_test.go +++ b/floatlit_test.go @@ -44,3 +44,20 @@ func TestFormatLLVMFloat64LiteralSpecial(t *testing.T) { } } } + +func TestFormatLLVMFloat64LiteralExactForms(t *testing.T) { + cases := []struct { + in float64 + want string + }{ + {1, "1.0e+00"}, + {2.5, "2.5e+00"}, + {-3, "-3.0e+00"}, + {0, "0.0e+00"}, + } + for _, tc := range cases { + if got := formatLLVMFloat64Literal(tc.in); got != tc.want { + t.Fatalf("formatLLVMFloat64Literal(%v) = %q, want %q", tc.in, got, tc.want) + } + } +} diff --git a/go_translate_deep_test.go b/go_translate_deep_test.go new file mode 100644 index 0000000..c5b55d9 --- /dev/null +++ b/go_translate_deep_test.go @@ -0,0 +1,322 @@ +package plan9asm + +import ( + "go/ast" + "go/constant" + "go/parser" + "go/token" + "go/types" + "strings" + "testing" +) + +func mustGoPackageWithImports(t *testing.T, pkgPath string, files map[string]string) GoPackage { + t.Helper() + fset := token.NewFileSet() + astFiles := make([]*ast.File, 0, len(files)) + for name, src := range files { + f, err := parser.ParseFile(fset, name, src, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + astFiles = append(astFiles, f) + } + conf := types.Config{Importer: nil} + pkg, err := conf.Check(pkgPath, fset, astFiles, nil) + if err != nil { + t.Fatal(err) + } + return GoPackage{Path: pkgPath, Types: pkg, Syntax: astFiles} +} + +func TestGoTranslateHelperCoverage(t *testing.T) { + if _, err := goArchFor("mips64"); err == nil { + t.Fatalf("goArchFor(mips64) unexpectedly succeeded") + } + + for _, tc := range []struct { + in string + base string + off int64 + tail bool + validOp Op + }{ + {"name+8", "name", 8, false, ""}, + {"name-4", "name", -4, false, ""}, + {"plain", "plain", 0, false, ""}, + {"target<>(SB)", "target<>", 0, false, "CALL"}, + } { + if tc.validOp == "" { + gotBase, gotOff := goSplitSymPlusOff(tc.in) + if gotBase != tc.base || gotOff != tc.off { + t.Fatalf("goSplitSymPlusOff(%q) = (%q, %d), want (%q, %d)", tc.in, gotBase, gotOff, tc.base, tc.off) + } + continue + } + base, tail, ok := goReferencedFunc(Instr{Op: tc.validOp, Args: []Operand{{Kind: OpSym, Sym: tc.in}}}) + if !ok || base != tc.base || tail != tc.tail { + t.Fatalf("goReferencedFunc(%q) = (%q, %v, %v)", tc.in, base, tail, ok) + } + } + for _, ins := range []Instr{ + {Op: "MOVQ", Args: []Operand{{Kind: OpSym, Sym: "x(SB)"}}}, + {Op: "CALL", Args: []Operand{{Kind: OpImm, Imm: 1}}}, + {Op: "CALL", Args: []Operand{{Kind: OpSym, Sym: "x+4(SB)"}}}, + {Op: "CALL", Args: []Operand{{Kind: OpSym, Sym: "x"}}}, + } { + if _, _, ok := goReferencedFunc(ins); ok { + t.Fatalf("goReferencedFunc(%q) unexpectedly succeeded", ins.Op) + } + } + + if fs, ok := goLookupManualSig(nil, "x"); ok || fs.Name != "" { + t.Fatalf("goLookupManualSig(nil) = (%v, %v)", fs, ok) + } + fs, ok := goLookupManualSig(func(resolved string) (FuncSig, bool) { + if resolved != "pkg.f" { + return FuncSig{}, false + } + return FuncSig{Ret: I64}, true + }, "pkg.f") + if !ok || fs.Name != "pkg.f" || fs.Ret != I64 { + t.Fatalf("goLookupManualSig() = (%v, %v)", fs, ok) + } + + if got, err := goDeclNameForSymbol("·local", nil); err != nil || got != "local" { + t.Fatalf("goDeclNameForSymbol(local) = (%q, %v)", got, err) + } + if got, err := goDeclNameForSymbol("runtime·cmp", map[string]string{"runtime.cmp": "cmp"}); err != nil || got != "cmp" { + t.Fatalf("goDeclNameForSymbol(linkname) = (%q, %v)", got, err) + } + if _, err := goDeclNameForSymbol("runtime·cmp", nil); err == nil { + t.Fatalf("goDeclNameForSymbol(missing linkname) unexpectedly succeeded") + } + + files := []*ast.File{ + nil, + {Comments: []*ast.CommentGroup{{List: []*ast.Comment{ + {Text: "//go:linkname local runtime.remote"}, + {Text: "//go:linkname malformed"}, + }}}}, + } + links := goLinknameRemoteToLocal(files) + if links["runtime.remote"] != "local" { + t.Fatalf("goLinknameRemoteToLocal() = %#v", links) + } +} + +func TestGoTranslateTypeCoverage(t *testing.T) { + word32 := types.Typ[types.Int] + namedObj := types.NewTypeName(token.NoPos, nil, "MyInt", nil) + named := types.NewNamed(namedObj, types.Typ[types.Int32], nil) + for _, tc := range []struct { + typ types.Type + goarch string + want LLVMType + ok bool + }{ + {types.Typ[types.Bool], "amd64", I1, true}, + {types.Typ[types.UnsafePointer], "amd64", Ptr, true}, + {types.Typ[types.Int8], "amd64", I8, true}, + {types.Typ[types.Uint16], "amd64", I16, true}, + {types.Typ[types.Int32], "amd64", I32, true}, + {types.Typ[types.Uint64], "amd64", I64, true}, + {word32, "arm", I32, true}, + {types.Typ[types.Uintptr], "arm64", I64, true}, + {types.Typ[types.Float32], "amd64", LLVMType("float"), true}, + {types.Typ[types.Float64], "amd64", LLVMType("double"), true}, + {types.Typ[types.String], "arm", LLVMType("{ ptr, i32 }"), true}, + {types.NewPointer(types.Typ[types.Int]), "amd64", Ptr, true}, + {types.NewSlice(types.Typ[types.Byte]), "arm64", LLVMType("{ ptr, i64, i64 }"), true}, + {types.NewInterfaceType(nil, nil), "amd64", LLVMType("{ ptr, ptr }"), true}, + {named, "amd64", I32, true}, + {types.Typ[types.Complex64], "amd64", "", false}, + {types.NewStruct(nil, nil), "amd64", "", false}, + } { + got, err := goLLVMTypeForType(tc.typ, tc.goarch) + if (err == nil) != tc.ok || got != tc.want { + t.Fatalf("goLLVMTypeForType(%s,%s) = (%q, %v), want (%q, ok=%v)", tc.typ, tc.goarch, got, err, tc.want, tc.ok) + } + } + + if parts, ok := goFramePartsForType(types.Typ[types.String], "amd64"); !ok || len(parts) != 2 || parts[1].Offset != 8 { + t.Fatalf("goFramePartsForType(string) = (%v, %v)", parts, ok) + } + if parts, ok := goFramePartsForType(types.NewSlice(types.Typ[types.Int]), "arm"); !ok || len(parts) != 3 || parts[2].Field != 2 { + t.Fatalf("goFramePartsForType(slice) = (%v, %v)", parts, ok) + } + if parts, ok := goFramePartsForType(types.NewInterfaceType(nil, nil), "amd64"); !ok || len(parts) != 2 { + t.Fatalf("goFramePartsForType(interface) = (%v, %v)", parts, ok) + } + if _, ok := goFramePartsForType(types.Typ[types.Int], "amd64"); ok { + t.Fatalf("goFramePartsForType(int) unexpectedly succeeded") + } + + tup := types.NewTuple( + types.NewVar(token.NoPos, nil, "s", types.Typ[types.String]), + types.NewVar(token.NoPos, nil, "b", types.NewSlice(types.Typ[types.Byte])), + types.NewVar(token.NoPos, nil, "n", types.Typ[types.Int32]), + ) + sz := types.SizesFor("gc", "arm64") + args, slots, next, err := goLLVMArgsAndFrameSlotsForTuple(tup, "arm64", sz, 8, false) + if err != nil || len(args) != 3 || len(slots) != 6 || next <= 8 { + t.Fatalf("goLLVMArgsAndFrameSlotsForTuple(flat=false) = (%v, %v, %d, %v)", args, slots, next, err) + } + args, slots, next, err = goLLVMArgsAndFrameSlotsForTuple(tup, "arm64", sz, 8, true) + if err != nil || len(args) != 6 || len(slots) != 6 || next <= 8 { + t.Fatalf("goLLVMArgsAndFrameSlotsForTuple(flat=true) = (%v, %v, %d, %v)", args, slots, next, err) + } + + if goWordSize("arm") != 4 || goWordSize("arm64") != 8 { + t.Fatalf("goWordSize() mismatch") + } + if got := goAlignOff(5, 4); got != 8 { + t.Fatalf("goAlignOff(5,4) = %d", got) + } +} + +func TestGoTranslateSignatureCoverage(t *testing.T) { + pkg := mustGoPackage(t, "test/pkg", `package testpkg +type S struct{} +func (S) Method(x int) int { return x } +func Variadic(x ...int) {} +func Plain(a string, b []byte, c any, d uintptr) (int, string) { return 0, "" } +func helper(a int) int { return a } +//go:linkname cmp runtime.cmp +func cmp(a, b int) int { return a } +`) + scope := pkg.Types.Scope() + plain := scope.Lookup("Plain").(*types.Func) + sig, err := goFuncSigForDeclaredFunc("test/pkg.Plain", plain, "arm64", types.SizesFor("gc", "arm64"), true) + if err != nil || sig.Ret == Void || len(sig.Frame.Params) == 0 || len(sig.Frame.Results) == 0 { + t.Fatalf("goFuncSigForDeclaredFunc(Plain) = (%v, %v)", sig, err) + } + variadic := scope.Lookup("Variadic").(*types.Func) + if _, err := goFuncSigForDeclaredFunc("test/pkg.Variadic", variadic, "amd64", types.SizesFor("gc", "amd64"), false); err == nil { + t.Fatalf("goFuncSigForDeclaredFunc(Variadic) unexpectedly succeeded") + } + named := scope.Lookup("S").Type().(*types.Named) + if _, err := goFuncSigForDeclaredFunc("test/pkg.Method", named.Method(0), "amd64", types.SizesFor("gc", "amd64"), false); err == nil { + t.Fatalf("goFuncSigForDeclaredFunc(Method) unexpectedly succeeded") + } + + file := &File{ + Arch: ArchARM64, + Funcs: []Func{ + {Sym: "·Plain", Instrs: []Instr{{Op: OpTEXT}, {Op: "CALL", Args: []Operand{{Kind: OpSym, Sym: "runtime·cmp(SB)"}}}, {Op: OpRET}}}, + {Sym: "localhelper<>", Instrs: []Instr{{Op: OpTEXT}, {Op: "B", Args: []Operand{{Kind: OpSym, Sym: "helper<>(SB)"}}}, {Op: OpRET}}}, + }, + } + sigs, err := goSigsForAsmFile(pkg, file, testResolveSym("test/pkg"), "arm64", func(resolved string) (FuncSig, bool) { + if resolved == "test/pkg.localhelper" { + return FuncSig{Name: resolved, Args: []LLVMType{I64}, Ret: I64}, true + } + return FuncSig{}, false + }) + if err != nil { + t.Fatalf("goSigsForAsmFile() error = %v", err) + } + for _, want := range []string{"test/pkg.Plain", "runtime.cmp", "test/pkg.localhelper"} { + if _, ok := sigs[want]; !ok { + t.Fatalf("missing signature %q", want) + } + } + + builder := goSigBuilder{ + sigs: map[string]FuncSig{}, + scope: scope, + sz: types.SizesFor("gc", "arm64"), + linknames: map[string]string{"runtime.cmp": "cmp"}, + pkgPath: "test/pkg", + resolve: testResolveSym("test/pkg"), + goarch: "arm64", + } + if err := builder.addGoDeclSig("runtime·cmp"); err != nil { + t.Fatalf("addGoDeclSig(linkname) error = %v", err) + } + if err := builder.addGoDeclSig("·helper"); err != nil { + t.Fatalf("addGoDeclSig(local) error = %v", err) + } + if err := builder.addGoDeclSig("missing.remote"); err != nil { + t.Fatalf("addGoDeclSig(missing) error = %v", err) + } + if _, ok := builder.sigs["runtime.cmp"]; !ok { + t.Fatalf("addGoDeclSig(linkname) did not record runtime.cmp") + } + + badPkg := mustGoPackage(t, "bad/pkg", `package badpkg +var helper int +`) + badBuilder := goSigBuilder{ + sigs: map[string]FuncSig{}, + scope: badPkg.Types.Scope(), + sz: types.SizesFor("gc", "arm64"), + pkgPath: "bad/pkg", + resolve: testResolveSym("bad/pkg"), + goarch: "arm64", + } + badFile := &File{Funcs: []Func{{Sym: "·helper"}}} + if err := badBuilder.addDeclaredFuncSigs(badFile); err == nil { + t.Fatalf("addDeclaredFuncSigs(non-func) unexpectedly succeeded") + } + if _, err := goSigsForAsmFile(pkg, file, testResolveSym("test/pkg"), "madeup", nil); err == nil { + t.Fatalf("goSigsForAsmFile(madeup) unexpectedly succeeded") + } +} + +func TestTranslateGoModuleErrorCoverage(t *testing.T) { + pkg := mustGoPackage(t, "test/pkg", `package testpkg +const Answer = 42 +func F(x int) int { return x } +`) + if _, err := TranslateGoModule(GoPackage{}, nil, GoModuleOptions{GOARCH: "arm64"}); err == nil { + t.Fatalf("TranslateGoModule(empty pkg) unexpectedly succeeded") + } + if _, err := TranslateGoModule(GoPackage{Path: "x"}, nil, GoModuleOptions{GOARCH: "arm64"}); err == nil { + t.Fatalf("TranslateGoModule(missing types) unexpectedly succeeded") + } + if _, err := TranslateGoModule(pkg, nil, GoModuleOptions{}); err == nil { + t.Fatalf("TranslateGoModule(empty arch) unexpectedly succeeded") + } + if _, err := TranslateGoModule(pkg, []byte("TEXT ·F(SB"), GoModuleOptions{GOARCH: "arm64"}); err == nil { + t.Fatalf("TranslateGoModule(parse error) unexpectedly succeeded") + } + if _, err := TranslateGoModule(pkg, []byte(`TEXT ·F(SB),NOSPLIT,$0-0 +RET +`), GoModuleOptions{ + GOARCH: "arm64", + KeepFunc: func(textSym, resolved string) bool { + return false + }, + }); err == nil || !strings.Contains(err.Error(), "empty file") { + t.Fatalf("TranslateGoModule(KeepFunc=false) error = %v", err) + } + + tr, err := TranslateGoModule(pkg, []byte(`TEXT ·F(SB),NOSPLIT,$0-8 +MOVD $const_Answer, R0 +RET +`), GoModuleOptions{ + FileName: "f_arm64.s", + GOARCH: "arm64", + TargetTriple: "aarch64-unknown-linux-gnu", + ResolveSym: testResolveSym("test/pkg"), + }) + if err != nil { + t.Fatalf("TranslateGoModule(const expansion) error = %v", err) + } + defer tr.Module.Dispose() + if len(tr.Functions) != 1 || tr.Signatures["test/pkg.F"].Name != "test/pkg.F" { + t.Fatalf("TranslateGoModule() returned unexpected metadata: %#v %#v", tr.Functions, tr.Signatures["test/pkg.F"]) + } + + pkgTypes := pkg.Types + imports := map[string]*types.Package{ + "other/pkg": types.NewPackage("other/pkg", "other"), + } + cobj := types.NewConst(token.NoPos, imports["other/pkg"], "Limit", types.Typ[types.Int], constant.MakeInt64(7)) + imports["other/pkg"].Scope().Insert(cobj) + expanded := string(goExpandConsts([]byte("MOVD other/pkg.Value+const_Limit(SB), R0\nMOVD $const_Answer, R1\n"), pkgTypes, imports)) + if !strings.Contains(expanded, "other/pkg.Value+7(SB)") || !strings.Contains(expanded, "$42") { + t.Fatalf("goExpandConsts() = %q", expanded) + } +} diff --git a/small_coverage_test.go b/small_coverage_test.go new file mode 100644 index 0000000..67425bc --- /dev/null +++ b/small_coverage_test.go @@ -0,0 +1,117 @@ +package plan9asm + +import ( + "strings" + "testing" +) + +func TestEmitIRSourceCommentEdges(t *testing.T) { + var b strings.Builder + emitIRSourceComment(&b, "") + if b.Len() != 0 { + t.Fatalf("empty comment emitted %q", b.String()) + } + emitIRSourceComment(&b, " \n\tMOVQ AX, BX\n\nRET\t\n") + out := b.String() + for _, want := range []string{ + "; s: MOVQ AX, BX", + "; s: RET", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in:\n%s", want, out) + } + } +} + +func TestParseIRModuleError(t *testing.T) { + if _, err := parseIRModule("this is not valid llvm ir"); err == nil { + t.Fatalf("parseIRModule(invalid) unexpectedly succeeded") + } +} + +func TestNeedCFGHelpers(t *testing.T) { + if !funcNeedsAMD64CFG(Func{Instrs: []Instr{{Op: OpMOVQ, Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: AX}}, {Kind: OpReg, Reg: BX}}}}}) { + t.Fatalf("funcNeedsAMD64CFG(mem mov) = false") + } + if !funcNeedsAMD64CFG(Func{Instrs: []Instr{{Op: "CRC32B"}}}) { + t.Fatalf("funcNeedsAMD64CFG(crc32) = false") + } + if funcNeedsAMD64CFG(Func{Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVQ, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}}, {Op: OpRET}}}) { + t.Fatalf("funcNeedsAMD64CFG(straight-line) = true") + } + + if !funcNeedsARM64CFG(Func{Instrs: []Instr{{Op: "MOVD.P"}}}) { + t.Fatalf("funcNeedsARM64CFG(dot suffix) = false") + } + if !funcNeedsARM64CFG(Func{Instrs: []Instr{{Op: "B"}}}) { + t.Fatalf("funcNeedsARM64CFG(branch) = false") + } + if funcNeedsARM64CFG(Func{Instrs: []Instr{{Op: OpTEXT}, {Op: OpMRS}, {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: "R0"}}}, {Op: OpRET}}}) { + t.Fatalf("funcNeedsARM64CFG(linear subset) = true") + } + + if !funcNeedsARMCFG(Func{Instrs: []Instr{{Op: "ADD.EQ"}}}) { + t.Fatalf("funcNeedsARMCFG(cond exec) = false") + } + if !funcNeedsARMCFG(Func{Instrs: []Instr{{Op: "BL"}}}) { + t.Fatalf("funcNeedsARMCFG(branch) = false") + } + if funcNeedsARMCFG(Func{Instrs: []Instr{{Op: OpTEXT}, {Op: "MOVW"}, {Op: "ADD"}, {Op: OpRET}}}) { + t.Fatalf("funcNeedsARMCFG(linear subset) = true") + } +} + +func TestAMD64EvalI64Coverage(t *testing.T) { + sig := FuncSig{ + Name: "example.eval", + Args: []LLVMType{I64}, + Ret: I64, + Frame: FrameLayout{ + Params: []FrameSlot{{Offset: 0, Type: I64, Index: 0}}, + }, + } + c, b := newAMD64CtxWithFuncForTest(t, Func{}, sig, nil) + if err := c.storeReg(AX, "17"); err != nil { + t.Fatalf("storeReg(AX) error = %v", err) + } + if err := c.storeReg(BX, "128"); err != nil { + t.Fatalf("storeReg(BX) error = %v", err) + } + + if got, err := c.evalI64(Operand{Kind: OpImm, Imm: 7}); err != nil || got != "7" { + t.Fatalf("evalI64(imm) = (%q, %v)", got, err) + } + if got, err := c.evalI64(Operand{Kind: OpReg, Reg: AX}); err != nil || got == "" { + t.Fatalf("evalI64(reg) = (%q, %v)", got, err) + } + if got, err := c.evalI64(Operand{Kind: OpFP, FPOffset: 0}); err != nil || got == "" { + t.Fatalf("evalI64(fp) = (%q, %v)", got, err) + } + if got, err := c.evalI64(Operand{Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}); err != nil || got == "" { + t.Fatalf("evalI64(mem) = (%q, %v)", got, err) + } + if got, err := c.evalI64(Operand{Kind: OpSym, Sym: "value<>(SB)"}); err != nil || got == "" { + t.Fatalf("evalI64(sym) = (%q, %v)", got, err) + } + if got, err := c.evalI64(Operand{Kind: OpSym, Sym: "$value<>(SB)"}); err != nil || got == "" { + t.Fatalf("evalI64(addr sym) = (%q, %v)", got, err) + } + if got, err := c.evalI64(Operand{Kind: OpSym, Sym: "bad sym"}); err != nil || got != "0" { + t.Fatalf("evalI64(unresolved bare sym) = (%q, %v)", got, err) + } + if _, err := c.evalI64(Operand{Kind: OpLabel, Sym: "loop"}); err == nil { + t.Fatalf("evalI64(label) unexpectedly succeeded") + } + + out := b.String() + for _, want := range []string{ + "load i64, ptr %reg_AX", + "load i64, ptr %t", + "load i64, ptr @\"example.value\"", + "ptrtoint ptr @\"example.value\" to i64", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in:\n%s", want, out) + } + } +} diff --git a/translate_deep_coverage_test.go b/translate_deep_coverage_test.go new file mode 100644 index 0000000..50fbe11 --- /dev/null +++ b/translate_deep_coverage_test.go @@ -0,0 +1,1551 @@ +package plan9asm + +import ( + "errors" + "strings" + "testing" + + "github.com/goplus/llvm" +) + +func TestPreprocessCoverageEdges(t *testing.T) { + src := ` +#define VALUE 7 +#define PAIR(a, b) MOVQ a, b \ +ADDQ a, b +#define KEEP AX +/* block +comment */ +#ifdef VALUE +PAIR (KEEP, BX) +#else +MOVQ $0, DX +#endif +#if !defined(MISSING) +MOVQ $(VALUE + 1), CX +#elif defined(VALUE) +MOVQ $9, SI +#endif +#ifndef MISSING +MOVQ VALUE, DI +#endif +TEXT foo(SB),NOSPLIT,$0-0 // trailing comment + PAIR(AX, DX); MOVQ KEEP, R8 + RET +` + pp, err := preprocess(src) + if err != nil { + t.Fatalf("preprocess() error = %v", err) + } + for _, want := range []string{ + "MOVQ AX, BX", + "ADDQ AX, BX", + "MOVQ $(7 + 1), CX", + "MOVQ 7, DI", + "MOVQ AX, DX", + "MOVQ AX, R8", + "TEXT foo(SB),NOSPLIT,$0-0", + } { + if !strings.Contains(pp, want) { + t.Fatalf("missing %q in preprocessed output:\n%s", want, pp) + } + } + for _, bad := range []string{"MOVQ $0, DX", "/*", "//"} { + if strings.Contains(pp, bad) { + t.Fatalf("unexpected %q in preprocessed output:\n%s", bad, pp) + } + } + + if _, err := preprocess("#else\n"); err == nil { + t.Fatalf("stray #else unexpectedly succeeded") + } + if _, err := preprocess("#if VALUE\nMOVQ AX, BX\n"); err == nil { + t.Fatalf("unterminated #if unexpectedly succeeded") + } + if _, err := preprocess("#if VALUE\n#else\n#else\n#endif\n"); err == nil { + t.Fatalf("duplicate #else unexpectedly succeeded") + } + + if name, params, body, err := parseMacroDefine("F(a, b) MOVQ a, b"); err != nil || name != "F" || len(params) != 2 || body != "MOVQ a, b" { + t.Fatalf("parseMacroDefine() = (%q, %v, %q, %v)", name, params, body, err) + } + if _, _, _, err := parseMacroDefine("1BAD x"); err == nil { + t.Fatalf("parseMacroDefine(invalid) unexpectedly succeeded") + } + if args, ok := parseMacroCall("PAIR(AX, BX)", "PAIR", 2); !ok || len(args) != 2 || args[0] != "AX" || args[1] != "BX" { + t.Fatalf("parseMacroCall() = (%v, %v)", args, ok) + } + if _, ok := parseMacroCall("PAIR(AX)", "PAIR", 2); ok { + t.Fatalf("parseMacroCall with wrong arity unexpectedly succeeded") + } + if got, changed := expandInlineMacroCalls("PAIR(AX, BX); RET", "PAIR", ppMacro{ + body: "MOVQ a, b", + params: []string{"a", "b"}, + }); !changed || !strings.Contains(got, "MOVQ AX, BX") { + t.Fatalf("expandInlineMacroCalls() = (%q, %v)", got, changed) + } + if got, changed := expandIdentMacros("MOVQ VALUE, AX", map[string]ppMacro{"VALUE": {body: "9"}}, []string{"VALUE"}); !changed || got != "MOVQ 9, AX" { + t.Fatalf("expandIdentMacros() = (%q, %v)", got, changed) + } + if got := expandImmExprMacros("MOVQ $(VALUE+1), AX", map[string]ppMacro{"VALUE": {body: "9"}}); got != "MOVQ $(9+1), AX" { + t.Fatalf("expandImmExprMacros() = %q", got) + } + if got := replaceMacroParams("ADDQ a, b", []string{"a", "b"}, []string{"CX", "DX"}); got != "ADDQ CX, DX" { + t.Fatalf("replaceMacroParams() = %q", got) + } + if got := replaceMacroIdents("VALUE+KEEP", map[string]ppMacro{ + "VALUE": {body: "5"}, + "KEEP": {body: "AX"}, + }); got != "5+AX" { + t.Fatalf("replaceMacroIdents() = %q", got) + } + if out := expandPPLine("WRAP(AX, BX)", map[string]ppMacro{ + "WRAP": {body: "PAIR(a, b)", params: []string{"a", "b"}}, + "PAIR": {body: "MOVQ a, b", params: []string{"a", "b"}}, + }, []string{"WRAP", "PAIR"}, 0); len(out) != 1 || out[0] != "MOVQ AX, BX" { + t.Fatalf("expandPPLine() = %#v", out) + } +} + +func TestTranslateIRTextCoverage(t *testing.T) { + resolve := testResolveSym("example") + if _, err := translateIRText(nil, Options{}); err == nil { + t.Fatalf("translateIRText(nil) unexpectedly succeeded") + } + if _, err := translateIRText(&File{Arch: ArchAMD64}, Options{}); err == nil { + t.Fatalf("translateIRText(empty) unexpectedly succeeded") + } + + file := &File{ + Arch: ArchAMD64, + Funcs: []Func{{ + Sym: "·f", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·f(SB),NOSPLIT,$0-0"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpSym, Sym: "other+8(SB)"}, {Kind: OpReg, Reg: AX}}, Raw: "MOVQ other+8(SB), AX"}, + {Op: OpRET, Raw: "RET"}, + }, + }}, + Data: []DataStmt{{Sym: "tbl", Off: 0, Width: 4, Value: 0x11223344}}, + Globl: []GloblStmt{{Sym: "tbl", Flags: "RODATA", Size: 8}}, + } + opt := Options{ + TargetTriple: "x86_64-unknown-linux-gnu", + ResolveSym: resolve, + Goarch: "amd64", + Sigs: map[string]FuncSig{ + "example.f": {Name: "example.f", Ret: I64}, + "example.helper": {Name: "example.helper", Args: []LLVMType{I64}, Ret: Void}, + }, + } + ir, err := translateIRText(file, opt) + if err != nil { + t.Fatalf("translateIRText() error = %v", err) + } + for _, want := range []string{ + `target triple = "x86_64-unknown-linux-gnu"`, + `@"example.other" = external global i8`, + `@"example.tbl" = constant [8 x i8] [i8 68, i8 51, i8 34, i8 17`, + `declare void @"example.helper"(i64)`, + `define i64 @"example.f"()`, + } { + if !strings.Contains(ir, want) { + t.Fatalf("missing %q in IR:\n%s", want, ir) + } + } + + mismatch := *file + if _, err := translateIRText(&mismatch, Options{ + ResolveSym: resolve, + Sigs: map[string]FuncSig{ + "example.f": {Name: "bad.name", Ret: I64}, + }, + }); err == nil { + t.Fatalf("translateIRText(name mismatch) unexpectedly succeeded") + } + if _, err := translateIRText(&mismatch, Options{ + ResolveSym: resolve, + Sigs: map[string]FuncSig{ + "example.f": {Name: "example.f"}, + }, + }); err == nil { + t.Fatalf("translateIRText(missing ret) unexpectedly succeeded") + } + + armFile := &File{ + Arch: ArchARM, + Funcs: []Func{{ + Sym: "·armbad", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·armbad(SB),NOSPLIT,$0-0"}, + {Op: "MOVW", Args: []Operand{{Kind: OpImm, ImmRaw: "$(sym)"}}, Raw: "MOVW $(sym), R0"}, + {Op: OpRET, Raw: "RET"}, + }, + }}, + } + if _, err := translateIRText(armFile, Options{ + ResolveSym: resolve, + Sigs: map[string]FuncSig{ + "example.armbad": {Name: "example.armbad", Ret: Void}, + }, + }); err == nil { + t.Fatalf("translateIRText(unresolved arm imm) unexpectedly succeeded") + } + + if err := validateResolvedImmediates(ArchARM, Func{ + Instrs: []Instr{{Args: []Operand{{Kind: OpImm, ImmRaw: "$(sym)"}}}}, + }); err == nil { + t.Fatalf("validateResolvedImmediates(arm) unexpectedly succeeded") + } + if err := validateResolvedImmediates(ArchAMD64, Func{ + Instrs: []Instr{{Args: []Operand{{Kind: OpImm, ImmRaw: "$(sym)"}}}}, + }); err != nil { + t.Fatalf("validateResolvedImmediates(amd64) error = %v", err) + } + + var dataIR strings.Builder + if err := emitDataGlobals(&dataIR, &File{ + Data: []DataStmt{{Sym: "bad", Off: 0, Width: -1, Value: 1}}, + }, resolve); err == nil { + t.Fatalf("emitDataGlobals(invalid width) unexpectedly succeeded") + } + if err := emitDataGlobals(&dataIR, &File{ + Globl: []GloblStmt{{Sym: "bad", Size: 2}}, + Data: []DataStmt{{Sym: "bad", Off: -1, Width: 1, Value: 1}}, + }, resolve); err == nil { + t.Fatalf("emitDataGlobals(oob) unexpectedly succeeded") + } + + if got := bestAlign(32); got != 16 { + t.Fatalf("bestAlign(32) = %d", got) + } + if got := bestAlign(3); got != 1 { + t.Fatalf("bestAlign(3) = %d", got) + } + if got := llvmI8ArrayInit(nil); got != "zeroinitializer" { + t.Fatalf("llvmI8ArrayInit(nil) = %q", got) + } + if got := llvmI8ArrayInit([]byte{1, 2, 3}); got != "[i8 1, i8 2, i8 3]" { + t.Fatalf("llvmI8ArrayInit([1 2 3]) = %q", got) + } +} + +func TestTranslateHelpersCoverage(t *testing.T) { + resolve := testResolveSym("example") + + file := &File{ + Arch: ArchAMD64, + Funcs: []Func{{ + Sym: "·entry", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·entry(SB),NOSPLIT,$0-0"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpSym, Sym: "data<>(SB)"}, {Kind: OpReg, Reg: AX}}, Raw: "MOVQ data<>(SB), AX"}, + {Op: "CALL", Args: []Operand{{Kind: OpSym, Sym: "helper(SB)"}}, Raw: "CALL helper(SB)"}, + {Op: OpRET, Raw: "RET"}, + }, + }}, + Globl: []GloblStmt{{Sym: "data", Size: 8}}, + Data: []DataStmt{{Sym: "data", Off: 0, Width: 8, Value: 1}}, + } + got, err := Translate(file, Options{ + ResolveSym: resolve, + Goarch: "amd64", + Sigs: map[string]FuncSig{ + "example.entry": {Name: "example.entry", Ret: I64}, + "example.helper": {Name: "example.helper", Ret: Void}, + }, + }) + if err != nil { + t.Fatalf("Translate() error = %v", err) + } + for _, want := range []string{`@example.entry`, `@example.data`} { + if !strings.Contains(got, want) { + t.Fatalf("Translate() missing %q in:\n%s", want, got) + } + } + if _, err := Translate(file, Options{ + ResolveSym: resolve, + Goarch: "amd64", + Sigs: map[string]FuncSig{}, + }); err == nil { + t.Fatalf("Translate(missing sig) unexpectedly succeeded") + } + + var decls strings.Builder + emitExternFuncDecls(&decls, file, resolve, map[string]FuncSig{ + "example.entry": {Name: "example.entry", Ret: I64}, + "example.helper": {Name: "example.helper", Ret: Void, Args: []LLVMType{I64}, Attrs: "#7"}, + "example.zed": {Name: "example.zed"}, + }) + declOut := decls.String() + if strings.Contains(declOut, "example.entry") || (!strings.Contains(declOut, `declare void @example.helper(i64) #7`) && !strings.Contains(declOut, `declare void @"example.helper"(i64) #7`)) { + t.Fatalf("emitExternFuncDecls() output = %q", declOut) + } + + var exts strings.Builder + emitExternSBGlobals(&exts, &File{ + Funcs: []Func{{ + Sym: "·f", + Instrs: []Instr{ + {Op: OpMOVQ, Args: []Operand{{Kind: OpSym, Sym: "data+8(SB)"}, {Kind: OpReg, Reg: AX}}}, + {Op: "CALL", Args: []Operand{{Kind: OpSym, Sym: "callee(SB)"}}}, + {Op: "JMP", Args: []Operand{{Kind: OpSym, Sym: "skip(SB)"}}}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpSym, Sym: "pkg/path.sym(SB)"}, {Kind: OpReg, Reg: AX}}}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpSym, Sym: "$const+4(SB)"}, {Kind: OpReg, Reg: AX}}}, + }, + }}, + }, resolve, map[string]FuncSig{ + "example.callee": {Name: "example.callee", Ret: Void}, + }) + extOut := exts.String() + for _, want := range []string{`@"example.data" = external global i8`, `@"pkg/path.sym" = external global i8`, `@"example.const" = external global i8`} { + if !strings.Contains(extOut, want) { + t.Fatalf("emitExternSBGlobals() missing %q in:\n%s", want, extOut) + } + } + for _, bad := range []string{`@"example.callee" = external global i8`, `@"example.skip" = external global i8`} { + if strings.Contains(extOut, bad) { + t.Fatalf("emitExternSBGlobals() unexpectedly included %q in:\n%s", bad, extOut) + } + } +} + +func TestTranslateModuleDirectAndFallbackCoverage(t *testing.T) { + resolve := testResolveSym("example") + if _, err := translateModuleDirect(nil, Options{}); err == nil { + t.Fatalf("translateModuleDirect(nil) unexpectedly succeeded") + } + if _, err := translateModuleDirect(&File{Arch: ArchAMD64}, Options{}); err == nil { + t.Fatalf("translateModuleDirect(empty) unexpectedly succeeded") + } + if _, err := translateModuleDirect(&File{ + Arch: ArchAMD64, + Funcs: []Func{{Sym: "·f", Instrs: []Instr{{Op: OpTEXT}, {Op: OpRET}}}}, + }, Options{ + AnnotateSource: true, + ResolveSym: resolve, + Sigs: map[string]FuncSig{"example.f": {Name: "example.f", Ret: Void}}, + }); !errors.Is(err, errDirectModuleUnsupported) { + t.Fatalf("translateModuleDirect(annotate) error = %v", err) + } + + file := &File{ + Arch: ArchAMD64, + Funcs: []Func{ + { + Sym: "·linear", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·linear(SB),NOSPLIT,$0-0"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}, Raw: "MOVQ $1, AX"}, + {Op: OpMOVL, Args: []Operand{{Kind: OpImm, Imm: 2}, {Kind: OpReg, Reg: BX}}, Raw: "MOVL $2, BX"}, + {Op: OpADDQ, Args: []Operand{{Kind: OpReg, Reg: BX}, {Kind: OpReg, Reg: AX}}, Raw: "ADDQ BX, AX"}, + {Op: OpSUBQ, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}, Raw: "SUBQ $1, AX"}, + {Op: OpXORQ, Args: []Operand{{Kind: OpReg, Reg: BX}, {Kind: OpReg, Reg: AX}}, Raw: "XORQ BX, AX"}, + {Op: OpCPUID, Raw: "CPUID"}, + {Op: OpXGETBV, Raw: "XGETBV"}, + {Op: OpRET, Raw: "RET"}, + }, + }, + { + Sym: "·pair", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·pair(SB),NOSPLIT,$0-16"}, + {Op: OpMOVL, Args: []Operand{{Kind: OpImm, Imm: 7}, {Kind: OpFP, FPOffset: 8}}, Raw: "MOVL $7, ret+8(FP)"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpImm, Imm: 9}, {Kind: OpFP, FPOffset: 16}}, Raw: "MOVQ $9, ret+16(FP)"}, + {Op: OpRET, Raw: "RET"}, + }, + }, + }, + Data: []DataStmt{{Sym: "blob", Off: 0, Width: 2, Value: 0x2211}}, + Globl: []GloblStmt{{Sym: "blob", Size: 4}}, + } + mod, err := translateModuleDirect(file, Options{ + ResolveSym: resolve, + Goarch: "amd64", + Sigs: map[string]FuncSig{ + "example.linear": {Name: "example.linear", Ret: I64}, + "example.pair": { + Name: "example.pair", + Ret: LLVMType("{i32, i64}"), + Frame: FrameLayout{ + Results: []FrameSlot{ + {Offset: 8, Type: I32, Index: 0, Field: -1}, + {Offset: 16, Type: I64, Index: 1, Field: -1}, + }, + }, + }, + }, + }) + if err != nil { + t.Fatalf("translateModuleDirect(amd64) error = %v", err) + } + defer mod.Dispose() + modIR := mod.String() + for _, want := range []string{`@example.linear`, `@example.pair`, `@example.blob`} { + if !strings.Contains(modIR, want) { + t.Fatalf("missing %q in direct module:\n%s", want, modIR) + } + } + + arm64File := &File{ + Arch: ArchARM64, + Funcs: []Func{{ + Sym: "·arm64m", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·arm64m(SB),NOSPLIT,$0-0"}, + {Op: OpMRS, Args: []Operand{{Kind: OpIdent, Ident: "MIDR_EL1"}, {Kind: OpReg, Reg: "R1"}}, Raw: "MRS MIDR_EL1, R1"}, + {Op: OpMOVD, Args: []Operand{{Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R0"}}, Raw: "MOVD R1, R0"}, + {Op: OpRET, Raw: "RET"}, + }, + }}, + } + armMod, err := translateModuleDirect(arm64File, Options{ + ResolveSym: resolve, + Goarch: "arm64", + Sigs: map[string]FuncSig{"example.arm64m": {Name: "example.arm64m", Ret: I64}}, + }) + if err != nil { + t.Fatalf("translateModuleDirect(arm64) error = %v", err) + } + defer armMod.Dispose() + if !strings.Contains(armMod.String(), `@example.arm64m`) { + t.Fatalf("arm64 direct module missing function:\n%s", armMod.String()) + } + + cfgFile := &File{ + Arch: ArchAMD64, + Funcs: []Func{{ + Sym: "·cfg", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·cfg(SB),NOSPLIT,$0-0"}, + {Op: OpLABEL, Args: []Operand{{Kind: OpLabel, Sym: "loop"}}, Raw: "loop:"}, + {Op: OpRET, Raw: "RET"}, + }, + }}, + } + fallbackMod, err := TranslateModule(cfgFile, Options{ + ResolveSym: resolve, + Goarch: "amd64", + Sigs: map[string]FuncSig{"example.cfg": {Name: "example.cfg", Ret: I64}}, + }) + if err != nil { + t.Fatalf("TranslateModule(fallback) error = %v", err) + } + defer fallbackMod.Dispose() + if !strings.Contains(fallbackMod.String(), `@example.cfg`) { + t.Fatalf("fallback module missing function:\n%s", fallbackMod.String()) + } + + if _, err := translateModuleDirect(&File{ + Arch: ArchAMD64, + Funcs: []Func{{ + Sym: "·noret", + Instrs: []Instr{{Op: OpTEXT, Raw: "TEXT ·noret(SB),NOSPLIT,$0-0"}}, + }}, + }, Options{ + ResolveSym: resolve, + Goarch: "amd64", + Sigs: map[string]FuncSig{"example.noret": {Name: "example.noret", Ret: I64}}, + }); !errors.Is(err, errDirectModuleUnsupported) { + t.Fatalf("translateModuleDirect(noret) error = %v", err) + } + if _, err := translateModuleDirect(&File{ + Arch: ArchAMD64, + Funcs: []Func{{ + Sym: "·afterret", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpRET}, {Op: OpMOVQ}}, + }}, + }, Options{ + ResolveSym: resolve, + Goarch: "amd64", + Sigs: map[string]FuncSig{"example.afterret": {Name: "example.afterret", Ret: I64}}, + }); !errors.Is(err, errDirectModuleUnsupported) { + t.Fatalf("translateModuleDirect(afterret) error = %v", err) + } + + ctx := llvm.GlobalContext() + tmpMod := ctx.NewModule("tmp") + defer tmpMod.Dispose() + if err := emitDataGlobalsModule(tmpMod, &File{ + Data: []DataStmt{{Sym: "bad", Width: -1, Value: 1}}, + }, resolve); err == nil { + t.Fatalf("emitDataGlobalsModule(invalid width) unexpectedly succeeded") + } + if err := emitDataGlobalsModule(tmpMod, &File{ + Globl: []GloblStmt{{Sym: "bad", Size: 2}}, + Data: []DataStmt{{Sym: "bad", Off: -1, Width: 1, Value: 1}}, + }, resolve); err == nil { + t.Fatalf("emitDataGlobalsModule(oob) unexpectedly succeeded") + } + + if ty, err := llvmTypeFromLLVMType(ctx, LLVMType("{i32, i64}")); err != nil || ty.C == nil { + t.Fatalf("llvmTypeFromLLVMType(struct) = (%v, %v)", ty, err) + } + if _, err := llvmTypeFromLLVMType(ctx, LLVMType("v2i64")); !errors.Is(err, errDirectModuleUnsupported) { + t.Fatalf("llvmTypeFromLLVMType(v2i64) error = %v", err) + } + if bits, ok := llvmIntBits(I32); !ok || bits != 32 { + t.Fatalf("llvmIntBits(I32) = (%d, %v)", bits, ok) + } + if _, ok := llvmIntBits(Ptr); ok { + t.Fatalf("llvmIntBits(ptr) unexpectedly succeeded") + } +} + +func TestTranslateModuleDirectErrorCoverage(t *testing.T) { + resolve := testResolveSym("example") + + baseFile := &File{ + Arch: ArchAMD64, + Funcs: []Func{{ + Sym: "·f", + Instrs: []Instr{{Op: OpTEXT, Raw: "TEXT ·f(SB),NOSPLIT,$0-0"}, {Op: OpRET, Raw: "RET"}}, + }}, + } + if _, err := translateModuleDirect(baseFile, Options{ResolveSym: resolve, Goarch: "amd64"}); err == nil { + t.Fatalf("translateModuleDirect(missing sig) unexpectedly succeeded") + } + if _, err := translateModuleDirect(baseFile, Options{ + ResolveSym: resolve, + Goarch: "amd64", + Sigs: map[string]FuncSig{"example.f": {Name: "bad.name", Ret: I64}}, + }); err == nil { + t.Fatalf("translateModuleDirect(name mismatch) unexpectedly succeeded") + } + if _, err := translateModuleDirect(baseFile, Options{ + ResolveSym: resolve, + Goarch: "amd64", + Sigs: map[string]FuncSig{"example.f": {Name: "example.f"}}, + }); err == nil { + t.Fatalf("translateModuleDirect(missing ret) unexpectedly succeeded") + } + + for _, tc := range []struct { + name string + file *File + opt Options + }{ + { + name: "unresolved-arm-imm", + file: &File{Arch: ArchARM, Funcs: []Func{{ + Sym: "·armbad", + Instrs: []Instr{{Op: OpTEXT}, {Op: "MOVW", Args: []Operand{{Kind: OpImm, ImmRaw: "$(sym)"}, {Kind: OpReg, Reg: "R0"}}}, {Op: OpRET}}, + }}}, + opt: Options{ResolveSym: resolve, Sigs: map[string]FuncSig{"example.armbad": {Name: "example.armbad", Ret: Void}}}, + }, + { + name: "arm-cfg", + file: &File{Arch: ArchARM, Funcs: []Func{{ + Sym: "·armcfg", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpLABEL, Args: []Operand{{Kind: OpLabel, Sym: "loop"}}}, {Op: OpRET}}, + }}}, + opt: Options{ResolveSym: resolve, Sigs: map[string]FuncSig{"example.armcfg": {Name: "example.armcfg", Ret: Void}}}, + }, + { + name: "arm64-cfg", + file: &File{Arch: ArchARM64, Funcs: []Func{{ + Sym: "·arm64cfg", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpLABEL, Args: []Operand{{Kind: OpLabel, Sym: "loop"}}}, {Op: OpRET}}, + }}}, + opt: Options{ResolveSym: resolve, Goarch: "arm64", Sigs: map[string]FuncSig{"example.arm64cfg": {Name: "example.arm64cfg", Ret: Void}}}, + }, + { + name: "amd64-cfg", + file: &File{Arch: ArchAMD64, Funcs: []Func{{ + Sym: "·amd64cfg", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpLABEL, Args: []Operand{{Kind: OpLabel, Sym: "loop"}}}, {Op: OpRET}}, + }}}, + opt: Options{ResolveSym: resolve, Goarch: "amd64", Sigs: map[string]FuncSig{"example.amd64cfg": {Name: "example.amd64cfg", Ret: I64}}}, + }, + } { + if _, err := translateModuleDirect(tc.file, tc.opt); !errors.Is(err, errDirectModuleUnsupported) { + t.Fatalf("%s error = %v", tc.name, err) + } + } + + ctx := llvm.GlobalContext() + mod := ctx.NewModule("direct-errors") + defer mod.Dispose() + for _, tc := range []struct { + name string + fn Func + sig FuncSig + }{ + { + name: "mrs-arity", + fn: Func{Sym: "example.bad1", Instrs: []Instr{{Op: OpTEXT}, {Op: OpMRS, Args: []Operand{{Kind: OpIdent, Ident: "MIDR_EL1"}}}, {Op: OpRET}}}, + sig: FuncSig{Name: "example.bad1", Ret: I64}, + }, + { + name: "movd-bad-dst", + fn: Func{Sym: "example.bad2", Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpIdent, Ident: "label"}}}, {Op: OpRET}}}, + sig: FuncSig{Name: "example.bad2", Ret: I64}, + }, + { + name: "movl-bad-dst", + fn: Func{Sym: "example.bad3", Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVL, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpIdent, Ident: "label"}}}, {Op: OpRET}}}, + sig: FuncSig{Name: "example.bad3", Ret: I64}, + }, + { + name: "addq-bad-dst", + fn: Func{Sym: "example.bad4", Instrs: []Instr{{Op: OpTEXT}, {Op: OpADDQ, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpIdent, Ident: "label"}}}, {Op: OpRET}}}, + sig: FuncSig{Name: "example.bad4", Ret: I64}, + }, + { + name: "invalid-op-kind", + fn: Func{Sym: "example.bad5", Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVQ, Args: []Operand{{Kind: OpLabel, Sym: "x"}, {Kind: OpReg, Reg: AX}}}, {Op: OpRET}}}, + sig: FuncSig{Name: "example.bad5", Ret: I64}, + }, + { + name: "bad-result-index", + fn: Func{Sym: "example.bad6", Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVQ, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpFP, FPOffset: 8}}}, {Op: OpRET}}}, + sig: FuncSig{Name: "example.bad6", Ret: I64, Frame: FrameLayout{Results: []FrameSlot{{Offset: 8, Type: I64, Index: 3}}}}, + }, + { + name: "bad-fp-read", + fn: Func{Sym: "example.bad7", Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVQ, Args: []Operand{{Kind: OpFP, FPOffset: 8}, {Kind: OpReg, Reg: AX}}}, {Op: OpRET}}}, + sig: FuncSig{Name: "example.bad7", Ret: I64, Frame: FrameLayout{Params: []FrameSlot{{Offset: 8, Type: I64, Index: 9}}}}, + }, + { + name: "unsupported-insn", + fn: Func{Sym: "example.bad8", Instrs: []Instr{{Op: OpTEXT}, {Op: "UNDEF"}, {Op: OpRET}}}, + sig: FuncSig{Name: "example.bad8", Ret: I64}, + }, + } { + if err := translateFuncLinearModule(mod, ArchAMD64, tc.fn, tc.sig); !errors.Is(err, errDirectModuleUnsupported) { + t.Fatalf("%s error = %v", tc.name, err) + } + } + + okMod := ctx.NewModule("direct-ok") + defer okMod.Dispose() + if err := translateFuncLinearModule(okMod, ArchAMD64, Func{ + Sym: "example.byteok", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpRET}, {Op: OpBYTE}}, + }, FuncSig{Name: "example.byteok", Ret: I64}); err != nil { + t.Fatalf("translateFuncLinearModule(byte-after-ret) error = %v", err) + } +} + +func TestTranslateFuncLinearCoverage(t *testing.T) { + t.Run("AMD64AggregateAndCasts", func(t *testing.T) { + fn := Func{ + Sym: "·linearAmd64", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·linearAmd64(SB),NOSPLIT,$0-16"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpFP, FPOffset: 0}, {Kind: OpReg, Reg: AX}}, Raw: "MOVQ arg+0(FP), AX"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpFP, FPOffset: 56}, {Kind: OpReg, Reg: BX}}, Raw: "MOVQ arg+56(FP), BX"}, + {Op: OpMOVL, Args: []Operand{{Kind: OpFP, FPOffset: 24}, {Kind: OpReg, Reg: CX}}, Raw: "MOVL arg+24(FP), CX"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: AX, Index: BX, Scale: 2, Off: 8}}, {Kind: OpReg, Reg: DX}}, Raw: "MOVQ 8(AX)(BX*2), DX"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpSym, Sym: "other(SB)"}, {Kind: OpReg, Reg: SI}}, Raw: "MOVQ other(SB), SI"}, + {Op: OpADDQ, Args: []Operand{{Kind: OpReg, Reg: BX}, {Kind: OpReg, Reg: AX}}, Raw: "ADDQ BX, AX"}, + {Op: OpSUBQ, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}, Raw: "SUBQ $1, AX"}, + {Op: OpXORQ, Args: []Operand{{Kind: OpReg, Reg: DX}, {Kind: OpReg, Reg: AX}}, Raw: "XORQ DX, AX"}, + {Op: OpCPUID, Raw: "CPUID"}, + {Op: OpXGETBV, Raw: "XGETBV"}, + {Op: OpMOVL, Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpFP, FPOffset: 80}}, Raw: "MOVL AX, ret+80(FP)"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpFP, FPOffset: 88}}, Raw: "MOVQ AX, ret+88(FP)"}, + {Op: OpRET, Raw: "RET"}, + }, + } + sig := FuncSig{ + Name: "example.linearAmd64", + Args: []LLVMType{Ptr, I1, I8, I16, I32, I64, LLVMType("float"), LLVMType("double")}, + Ret: LLVMType("{i32, i64}"), + Frame: FrameLayout{ + Params: []FrameSlot{ + {Offset: 0, Type: Ptr, Index: 0, Field: -1}, + {Offset: 8, Type: I1, Index: 1, Field: -1}, + {Offset: 16, Type: I8, Index: 2, Field: -1}, + {Offset: 24, Type: I16, Index: 3, Field: -1}, + {Offset: 32, Type: I32, Index: 4, Field: -1}, + {Offset: 40, Type: I64, Index: 5, Field: -1}, + {Offset: 48, Type: LLVMType("float"), Index: 6, Field: -1}, + {Offset: 56, Type: LLVMType("double"), Index: 7, Field: -1}, + }, + Results: []FrameSlot{ + {Offset: 80, Type: I32, Index: 0, Field: -1}, + {Offset: 88, Type: I64, Index: 1, Field: -1}, + }, + }, + } + var b strings.Builder + if err := translateFuncLinear(&b, ArchAMD64, fn, sig, true); err != nil { + t.Fatalf("translateFuncLinear(amd64) error = %v", err) + } + out := b.String() + for _, want := range []string{ + `define {i32, i64} @"example.linearAmd64"(ptr %arg0, i1 %arg1, i8 %arg2, i16 %arg3, i32 %arg4, i64 %arg5, float %arg6, double %arg7)`, + "; s: MOVQ arg+0(FP), AX", + "ptrtoint ptr %arg0 to i64", + "fptoui double %arg7 to i64", + "zext i16 %arg3 to i32", + "mul i64", + "inttoptr i64", + "call { i32, i32, i32, i32 } asm sideeffect", + "call { i32, i32 } asm sideeffect", + "insertvalue {i32, i64}", + "ret {i32, i64}", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in amd64 linear output:\n%s", want, out) + } + } + }) + + t.Run("ARMRegShiftAndRet", func(t *testing.T) { + fn := Func{ + Sym: "·linearArm", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·linearArm(SB),NOSPLIT,$0-4"}, + {Op: "MOVW", Args: []Operand{{Kind: OpFP, FPOffset: 0}, {Kind: OpReg, Reg: "R0"}}, Raw: "MOVW arg+0(FP), R0"}, + {Op: "MOVBU", Args: []Operand{{Kind: OpFP, FPOffset: 8}, {Kind: OpReg, Reg: "R1"}}, Raw: "MOVBU arg+8(FP), R1"}, + {Op: "ADD", Args: []Operand{{Kind: OpRegShift, Reg: "R1", ShiftOp: ShiftLeft, ShiftAmount: 2}, {Kind: OpReg, Reg: "R0"}}, Raw: "ADD R1<<2, R0"}, + {Op: "SUB", Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R2"}}, Raw: "SUB R0, R1, R2"}, + {Op: "AND", Args: []Operand{{Kind: OpReg, Reg: "R2"}, {Kind: OpReg, Reg: "R1"}}, Raw: "AND R2, R1"}, + {Op: "ORR", Args: []Operand{{Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R2"}, {Kind: OpReg, Reg: "R3"}}, Raw: "ORR R1, R2, R3"}, + {Op: "EOR", Args: []Operand{{Kind: OpReg, Reg: "R3"}, {Kind: OpReg, Reg: "R2"}, {Kind: OpReg, Reg: "R4"}}, Raw: "EOR R3, R2, R4"}, + {Op: "RSB", Args: []Operand{{Kind: OpReg, Reg: "R4"}, {Kind: OpReg, Reg: "R3"}, {Kind: OpReg, Reg: "R5"}}, Raw: "RSB R4, R3, R5"}, + {Op: "MOVW", Args: []Operand{{Kind: OpReg, Reg: "R5"}, {Kind: OpFP, FPOffset: 16}}, Raw: "MOVW R5, ret+16(FP)"}, + {Op: OpRET, Raw: "RET"}, + }, + } + sig := FuncSig{ + Name: "example.linearArm", + Args: []LLVMType{I32, I8}, + Ret: I32, + Frame: FrameLayout{ + Params: []FrameSlot{ + {Offset: 0, Type: I32, Index: 0, Field: -1}, + {Offset: 8, Type: I8, Index: 1, Field: -1}, + }, + Results: []FrameSlot{{Offset: 16, Type: I32, Index: 0, Field: -1}}, + }, + } + var b strings.Builder + if err := translateFuncLinear(&b, ArchARM, fn, sig, false); err != nil { + t.Fatalf("translateFuncLinear(arm) error = %v", err) + } + out := b.String() + for _, want := range []string{ + "shl i32", + "add i32", + "sub i32", + "and i32", + "or i32", + "xor i32", + "ret i32", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in arm linear output:\n%s", want, out) + } + } + }) + + t.Run("ARM64MRSAndDefaultReturn", func(t *testing.T) { + fn := Func{ + Sym: "·linearArm64", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·linearArm64(SB),NOSPLIT,$0-8"}, + {Op: OpMRS, Args: []Operand{{Kind: OpIdent, Ident: "MIDR_EL1"}, {Kind: OpReg, Reg: "R1"}}, Raw: "MRS MIDR_EL1, R1"}, + {Op: OpMOVD, Args: []Operand{{Kind: OpFP, FPOffset: 0}, {Kind: OpReg, Reg: "R0"}}, Raw: "MOVD arg+0(FP), R0"}, + {Op: OpBYTE, Raw: "BYTE $0"}, + }, + } + sig := FuncSig{ + Name: "example.linearArm64", + Args: []LLVMType{I64}, + Ret: I64, + Frame: FrameLayout{ + Params: []FrameSlot{{Offset: 0, Type: I64, Index: 0, Field: -1}}, + }, + } + var b strings.Builder + if err := translateFuncLinear(&b, ArchARM64, fn, sig, false); err != nil { + t.Fatalf("translateFuncLinear(arm64) error = %v", err) + } + out := b.String() + for _, want := range []string{ + `define i64 @"example.linearArm64"(i64 %arg0)`, + "ret i64 0", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in arm64 linear output:\n%s", want, out) + } + } + }) + + t.Run("VoidFastPath", func(t *testing.T) { + var b strings.Builder + if err := translateFuncLinear(&b, ArchAMD64, Func{Sym: "·void"}, FuncSig{Name: "example.void", Ret: Void}, false); err != nil { + t.Fatalf("translateFuncLinear(void) error = %v", err) + } + if !strings.Contains(b.String(), "ret void") { + t.Fatalf("void fast path missing ret void:\n%s", b.String()) + } + }) +} + +func TestDirectModuleTypeHelperCoverage(t *testing.T) { + ctx := llvm.GlobalContext() + for _, tc := range []struct { + ty LLVMType + ok bool + bits int + }{ + {Void, true, 0}, + {I1, true, 1}, + {I8, true, 8}, + {I16, true, 16}, + {I32, true, 32}, + {I64, true, 64}, + {Ptr, true, 0}, + {LLVMType("float"), true, 0}, + {LLVMType("double"), true, 0}, + {LLVMType("{ i32, i64 }"), true, 0}, + {LLVMType("v2i64"), false, 0}, + } { + _, err := llvmTypeFromLLVMType(ctx, tc.ty) + if (err == nil) != tc.ok { + t.Fatalf("llvmTypeFromLLVMType(%q) error = %v, want ok=%v", tc.ty, err, tc.ok) + } + if bits, ok := llvmIntBits(tc.ty); tc.bits == 0 { + if (tc.ty == I1 || tc.ty == I8 || tc.ty == I16 || tc.ty == I32 || tc.ty == I64) && (!ok || bits != tc.bits) { + t.Fatalf("llvmIntBits(%q) = (%d, %v)", tc.ty, bits, ok) + } + } else if bits != tc.bits || !ok { + t.Fatalf("llvmIntBits(%q) = (%d, %v), want (%d, true)", tc.ty, bits, ok, tc.bits) + } + } + if bits, ok := llvmIntBits(Ptr); ok || bits != 0 { + t.Fatalf("llvmIntBits(ptr) = (%d, %v)", bits, ok) + } + + mod := ctx.NewModule("data-helper") + defer mod.Dispose() + if err := emitDataGlobalsModule(mod, &File{Globl: []GloblStmt{{Sym: "huge", Size: (1 << 31) + 1}}}, testResolveSym("example")); err == nil { + t.Fatalf("emitDataGlobalsModule(huge) unexpectedly succeeded") + } + if err := emitDataGlobalsModule(mod, &File{Data: []DataStmt{{Sym: "bad", Width: 0}}}, testResolveSym("example")); err == nil { + t.Fatalf("emitDataGlobalsModule(width=0) unexpectedly succeeded") + } + if err := emitDataGlobalsModule(mod, &File{ + Globl: []GloblStmt{{Sym: "small", Size: 1}}, + Data: []DataStmt{{Sym: "small", Off: -1, Width: 1, Value: 1}}, + }, testResolveSym("example")); err == nil { + t.Fatalf("emitDataGlobalsModule(oob) unexpectedly succeeded") + } +} + +func TestTranslateFuncLinearModuleSuccessCoverage(t *testing.T) { + ctx := llvm.GlobalContext() + + mod1 := ctx.NewModule("direct-success-1") + defer mod1.Dispose() + err := translateFuncLinearModule(mod1, ArchARM64, Func{ + Sym: "example.aggregate", + Instrs: []Instr{ + {Op: OpTEXT}, + {Op: OpMOVD, Args: []Operand{{Kind: OpFP, FPOffset: 0}, {Kind: OpReg, Reg: "R2"}}, Raw: "MOVD pair+0(FP), R2"}, + {Op: OpMOVL, Args: []Operand{{Kind: OpFP, FPOffset: 8}, {Kind: OpReg, Reg: "R3"}}, Raw: "MOVL pair+8(FP), R3"}, + {Op: OpMOVD, Args: []Operand{{Kind: OpReg, Reg: "R2"}, {Kind: OpFP, FPOffset: 24}}, Raw: "MOVD R2, ret+24(FP)"}, + {Op: OpMOVL, Args: []Operand{{Kind: OpReg, Reg: "R3"}, {Kind: OpFP, FPOffset: 32}}, Raw: "MOVL R3, ret+32(FP)"}, + {Op: OpRET}, + }, + }, FuncSig{ + Name: "example.aggregate", + Args: []LLVMType{LLVMType("{ ptr, i32 }")}, + Ret: LLVMType("{ i64, i32 }"), + Frame: FrameLayout{ + Params: []FrameSlot{ + {Offset: 0, Type: Ptr, Index: 0, Field: 0}, + {Offset: 8, Type: I32, Index: 0, Field: 1}, + }, + Results: []FrameSlot{ + {Offset: 24, Type: I64, Index: 0}, + {Offset: 32, Type: I32, Index: 1}, + }, + }, + }) + if err != nil { + t.Fatalf("translateFuncLinearModule(aggregate) error = %v", err) + } + + mod2 := ctx.NewModule("direct-success-2") + defer mod2.Dispose() + err = translateFuncLinearModule(mod2, ArchARM, Func{ + Sym: "example.armret", + Instrs: []Instr{ + {Op: OpTEXT}, + {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 5}, {Kind: OpReg, Reg: "R0"}}, Raw: "MOVD $5, R0"}, + {Op: OpRET}, + }, + }, FuncSig{Name: "example.armret", Ret: I64}) + if err != nil { + t.Fatalf("translateFuncLinearModule(arm-ret) error = %v", err) + } + + mod3 := ctx.NewModule("direct-success-3") + defer mod3.Dispose() + err = translateFuncLinearModule(mod3, ArchAMD64, Func{ + Sym: "example.zero", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpRET}}, + }, FuncSig{Name: "example.zero", Ret: I64}) + if err != nil { + t.Fatalf("translateFuncLinearModule(zero-ret) error = %v", err) + } + + mod4 := ctx.NewModule("direct-success-4") + defer mod4.Dispose() + err = translateFuncLinearModule(mod4, ArchAMD64, Func{ + Sym: "example.ptrcast", + Instrs: []Instr{ + {Op: OpTEXT}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpImm, Imm: 9}, {Kind: OpFP, FPOffset: 8}}, Raw: "MOVQ $9, ret+8(FP)"}, + {Op: OpRET}, + }, + }, FuncSig{ + Name: "example.ptrcast", + Ret: Ptr, + Frame: FrameLayout{ + Results: []FrameSlot{{Offset: 8, Type: Ptr, Index: 0}}, + }, + }) + if err != nil { + t.Fatalf("translateFuncLinearModule(ptrcast) error = %v", err) + } +} + +func TestTranslateFuncLinearCastAndErrorCoverage(t *testing.T) { + t.Run("CastMatrix", func(t *testing.T) { + fn := Func{ + Sym: "·casts", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·casts(SB),NOSPLIT,$0-0"}, + {Op: OpMOVL, Args: []Operand{{Kind: OpFP, FPOffset: 0}, {Kind: OpReg, Reg: AX}}, Raw: "MOVL argp+0(FP), AX"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpFP, FPOffset: 8}, {Kind: OpReg, Reg: BX}}, Raw: "MOVQ arg1+8(FP), BX"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpFP, FPOffset: 16}, {Kind: OpReg, Reg: CX}}, Raw: "MOVQ arg8+16(FP), CX"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpFP, FPOffset: 24}, {Kind: OpReg, Reg: DX}}, Raw: "MOVQ arg16+24(FP), DX"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpFP, FPOffset: 32}, {Kind: OpReg, Reg: SI}}, Raw: "MOVQ arg32+32(FP), SI"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpFP, FPOffset: 40}, {Kind: OpReg, Reg: DI}}, Raw: "MOVQ argptr+40(FP), DI"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpFP, FPOffset: 48}, {Kind: OpReg, Reg: Reg("R8")}}, Raw: "MOVQ argf32+48(FP), R8"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpFP, FPOffset: 56}, {Kind: OpReg, Reg: Reg("R9")}}, Raw: "MOVQ argf64+56(FP), R9"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpImm, Imm: 123}, {Kind: OpFP, FPOffset: 80}}, Raw: "MOVQ $123, ret+80(FP)"}, + {Op: OpMOVL, Args: []Operand{{Kind: OpImm, Imm: 9}, {Kind: OpFP, FPOffset: 88}}, Raw: "MOVL $9, ret+88(FP)"}, + {Op: OpMOVL, Args: []Operand{{Kind: OpFP, FPOffset: 40}, {Kind: OpFP, FPOffset: 96}}, Raw: "MOVL argptr+40(FP), ret+96(FP)"}, + {Op: OpRET, Raw: "RET"}, + }, + } + sig := FuncSig{ + Name: "example.casts", + Args: []LLVMType{Ptr, I1, I8, I16, I32, Ptr, LLVMType("float"), LLVMType("double")}, + Ret: LLVMType("{ptr, ptr, i32}"), + Frame: FrameLayout{ + Params: []FrameSlot{ + {Offset: 0, Type: Ptr, Index: 0, Field: -1}, + {Offset: 8, Type: I1, Index: 1, Field: -1}, + {Offset: 16, Type: I8, Index: 2, Field: -1}, + {Offset: 24, Type: I16, Index: 3, Field: -1}, + {Offset: 32, Type: I32, Index: 4, Field: -1}, + {Offset: 40, Type: Ptr, Index: 5, Field: -1}, + {Offset: 48, Type: LLVMType("float"), Index: 6, Field: -1}, + {Offset: 56, Type: LLVMType("double"), Index: 7, Field: -1}, + }, + Results: []FrameSlot{ + {Offset: 80, Type: Ptr, Index: 0, Field: -1}, + {Offset: 88, Type: Ptr, Index: 1, Field: -1}, + {Offset: 96, Type: I32, Index: 2, Field: -1}, + }, + }, + } + var b strings.Builder + if err := translateFuncLinear(&b, ArchAMD64, fn, sig, false); err != nil { + t.Fatalf("translateFuncLinear(cast matrix) error = %v", err) + } + out := b.String() + for _, want := range []string{ + "ptrtoint ptr %arg0 to i32", + "zext i1 %arg1 to i64", + "zext i8 %arg2 to i64", + "zext i16 %arg3 to i64", + "zext i32 %arg4 to i64", + "ptrtoint ptr %arg5 to i64", + "fptoui float %arg6 to i64", + "fptoui double %arg7 to i64", + "inttoptr i64 %t9 to ptr", + "inttoptr i32 %t11 to ptr", + "insertvalue {ptr, ptr, i32}", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in cast matrix output:\n%s", want, out) + } + } + }) + + t.Run("AggregateAndFallbacks", func(t *testing.T) { + fn := Func{ + Sym: "·aggLinear", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·aggLinear(SB),NOSPLIT,$0-0"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpFP, FPOffset: 0}, {Kind: OpReg, Reg: AX}}, Raw: "MOVQ pair+0(FP), AX"}, + {Op: OpMOVL, Args: []Operand{{Kind: OpFP, FPOffset: 8}, {Kind: OpReg, Reg: BX}}, Raw: "MOVL pair+8(FP), BX"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpFP, FPOffset: 99}, {Kind: OpReg, Reg: CX}}, Raw: "MOVQ missing+99(FP), CX"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: AX, Index: BX, Scale: 0, Off: 4}}, {Kind: OpReg, Reg: DX}}, Raw: "MOVQ 4(AX)(BX*0), DX"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpReg, Reg: DX}, {Kind: OpFP, FPOffset: 24}}, Raw: "MOVQ DX, ret+24(FP)"}, + {Op: OpRET, Raw: "RET"}, + }, + } + sig := FuncSig{ + Name: "example.aggLinear", + Args: []LLVMType{LLVMType("{i64, i32}")}, + Ret: I64, + Frame: FrameLayout{ + Params: []FrameSlot{ + {Offset: 0, Type: I64, Index: 0, Field: 0}, + {Offset: 8, Type: I32, Index: 0, Field: 1}, + }, + Results: []FrameSlot{{Offset: 24, Type: I64, Index: 0}}, + }, + } + var b strings.Builder + if err := translateFuncLinear(&b, ArchAMD64, fn, sig, false); err != nil { + t.Fatalf("translateFuncLinear(aggregate) error = %v", err) + } + out := b.String() + for _, want := range []string{ + "extractvalue {i64, i32} %arg0, 0", + "extractvalue {i64, i32} %arg0, 1", + "mul i64", + "add i64", + "load i64, ptr", + "ret i64", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in aggregate output:\n%s", want, out) + } + } + }) + + t.Run("ErrorAndFallbackPaths", func(t *testing.T) { + cases := []struct { + name string + arch Arch + fn Func + sig FuncSig + wantErr string + wantIR string + }{ + { + name: "AggregateMemBaseFallsBackToZero", + arch: ArchAMD64, + fn: Func{ + Sym: "·aggmem", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·aggmem(SB),NOSPLIT,$0-0"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: AX}}, {Kind: OpReg, Reg: AX}}, Raw: "MOVQ (AX), AX"}, + {Op: OpRET, Raw: "RET"}, + }, + }, + sig: FuncSig{ + Name: "example.aggmem", + Args: []LLVMType{LLVMType("{i64, i32}")}, + ArgRegs: []Reg{AX}, + Ret: I64, + }, + wantIR: "add i64 0, 0", + }, + { + name: "BadSetResultCast", + arch: ArchAMD64, + fn: Func{ + Sym: "·badres", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·badres(SB),NOSPLIT,$0-0"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpFP, FPOffset: 8}}, Raw: "MOVQ $1, ret+8(FP)"}, + {Op: OpRET, Raw: "RET"}, + }, + }, + sig: FuncSig{ + Name: "example.badres", + Ret: LLVMType("v2i64"), + Frame: FrameLayout{ + Results: []FrameSlot{{Offset: 8, Type: LLVMType("v2i64"), Index: 0}}, + }, + }, + wantErr: "unsupported cast i64 -> v2i64", + }, + { + name: "BadAddDst", + arch: ArchAMD64, + fn: Func{ + Sym: "·badadd", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·badadd(SB),NOSPLIT,$0-0"}, + {Op: OpADDQ, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpFP, FPOffset: 8}}, Raw: "ADDQ $1, ret+8(FP)"}, + {Op: OpRET, Raw: "RET"}, + }, + }, + sig: FuncSig{Name: "example.badadd", Ret: I64}, + wantErr: "dst must be register", + }, + { + name: "BadMRSArgs", + arch: ArchARM64, + fn: Func{ + Sym: "·badmrs", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·badmrs(SB),NOSPLIT,$0-0"}, + {Op: OpMRS, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: "R0"}}, Raw: "MRS $1, R0"}, + {Op: OpRET, Raw: "RET"}, + }, + }, + sig: FuncSig{Name: "example.badmrs", Ret: I64}, + wantErr: "MRS expects ident, reg", + }, + { + name: "BadScalarReturnCast", + arch: ArchAMD64, + fn: Func{ + Sym: "·badretcast", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·badretcast(SB),NOSPLIT,$0-0"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpImm, Imm: 7}, {Kind: OpReg, Reg: AX}}, Raw: "MOVQ $7, AX"}, + {Op: OpRET, Raw: "RET"}, + }, + }, + sig: FuncSig{Name: "example.badretcast", Ret: LLVMType("v2i64")}, + wantErr: "unsupported cast i64 -> v2i64", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + var b strings.Builder + err := translateFuncLinear(&b, tc.arch, tc.fn, tc.sig, false) + if tc.wantErr != "" { + if err == nil || !strings.Contains(err.Error(), tc.wantErr) { + t.Fatalf("translateFuncLinear(%s) error = %v, want substring %q", tc.name, err, tc.wantErr) + } + return + } + if err != nil { + t.Fatalf("translateFuncLinear(%s) error = %v", tc.name, err) + } + if tc.wantIR != "" && !strings.Contains(b.String(), tc.wantIR) { + t.Fatalf("translateFuncLinear(%s) missing %q:\n%s", tc.name, tc.wantIR, b.String()) + } + }) + } + }) + + t.Run("CPUIDAndXGETBVZeroInputs", func(t *testing.T) { + fn := Func{ + Sym: "·cpux", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·cpux(SB),NOSPLIT,$0-0"}, + {Op: OpCPUID, Raw: "CPUID"}, + {Op: OpXGETBV, Raw: "XGETBV"}, + {Op: OpRET, Raw: "RET"}, + }, + } + var b strings.Builder + if err := translateFuncLinear(&b, ArchAMD64, fn, FuncSig{Name: "example.cpux", Ret: I64}, false); err != nil { + t.Fatalf("translateFuncLinear(cpux) error = %v", err) + } + out := b.String() + for _, want := range []string{ + "add i32 0, 0", + "call { i32, i32, i32, i32 } asm sideeffect", + "call { i32, i32 } asm sideeffect", + "zext i32", + } { + if !strings.Contains(out, want) { + t.Fatalf("missing %q in cpux output:\n%s", want, out) + } + } + }) +} + +func TestTranslateFuncLinearModuleEdgeCoverage(t *testing.T) { + ctx := llvm.GlobalContext() + + t.Run("DirectSuccessAndFallbacks", func(t *testing.T) { + mod := ctx.NewModule("direct-edge-success") + defer mod.Dispose() + + err := translateFuncLinearModule(mod, ArchARM64, Func{ + Sym: "example.directedge", + Instrs: []Instr{ + {Op: OpTEXT}, + {Op: OpMOVD, Args: []Operand{{Kind: OpFP, FPOffset: 0}, {Kind: OpReg, Reg: "R0"}}, Raw: "MOVD arg+0(FP), R0"}, + {Op: OpMOVL, Args: []Operand{{Kind: OpFP, FPOffset: 8}, {Kind: OpReg, Reg: "R1"}}, Raw: "MOVL arg+8(FP), R1"}, + {Op: OpADDQ, Args: []Operand{{Kind: OpReg, Reg: "R1"}, {Kind: OpReg, Reg: "R0"}}, Raw: "ADDQ R1, R0"}, + {Op: OpMOVD, Args: []Operand{{Kind: OpReg, Reg: "R0"}, {Kind: OpFP, FPOffset: 24}}, Raw: "MOVD R0, ret+24(FP)"}, + {Op: OpRET}, + }, + }, FuncSig{ + Name: "example.directedge", + Args: []LLVMType{Ptr, I32}, + Ret: I64, + Frame: FrameLayout{ + Params: []FrameSlot{ + {Offset: 0, Type: Ptr, Index: 0, Field: -1}, + {Offset: 8, Type: I32, Index: 1, Field: -1}, + }, + Results: []FrameSlot{{Offset: 24, Type: I64, Index: 0}}, + }, + }) + if err != nil { + t.Fatalf("translateFuncLinearModule(success) error = %v", err) + } + }) + + t.Run("DirectCPUXGETBVAndMRS", func(t *testing.T) { + mod := ctx.NewModule("direct-edge-cpu-mrs") + defer mod.Dispose() + + err := translateFuncLinearModule(mod, ArchARM64, Func{ + Sym: "example.directcpumrs", + Instrs: []Instr{ + {Op: OpTEXT}, + {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 7}, {Kind: OpReg, Reg: AX}}, Raw: "MOVD $7, AX"}, + {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 11}, {Kind: OpReg, Reg: CX}}, Raw: "MOVD $11, CX"}, + {Op: OpCPUID, Raw: "CPUID"}, + {Op: OpXGETBV, Raw: "XGETBV"}, + {Op: OpMRS, Args: []Operand{{Kind: OpIdent, Ident: "MIDR_EL1"}, {Kind: OpReg, Reg: "R0"}}, Raw: "MRS MIDR_EL1, R0"}, + {Op: OpMRS, Args: []Operand{{Kind: OpIdent, Ident: "TPIDR_EL0"}, {Kind: OpReg, Reg: "R1"}}, Raw: "MRS TPIDR_EL0, R1"}, + {Op: OpRET}, + }, + }, FuncSig{Name: "example.directcpumrs", Ret: I64}) + if err != nil { + t.Fatalf("translateFuncLinearModule(cpu+mrs) error = %v", err) + } + + ir := mod.String() + for _, want := range []string{ + "call { i32, i32, i32, i32 } asm sideeffect \"cpuid\"", + "call { i32, i32 } asm sideeffect \"xgetbv\"", + "call i64 asm \"mrs $0, TPIDR_EL0\"", + "ret i64 0", + } { + if !strings.Contains(ir, want) { + t.Fatalf("missing %q in module IR:\n%s", want, ir) + } + } + }) + + t.Run("DirectErrors", func(t *testing.T) { + cases := []struct { + name string + fn Func + sig FuncSig + }{ + { + name: "UnresolvedImm", + fn: Func{ + Sym: "badimm", + Instrs: []Instr{ + {Op: OpTEXT}, + {Op: OpMOVD, Args: []Operand{{Kind: OpImm, ImmRaw: "sym+4(SB)"}, {Kind: OpReg, Reg: AX}}, Raw: "MOVD $sym+4(SB), AX"}, + {Op: OpRET}, + }, + }, + sig: FuncSig{Name: "example.badimm", Ret: I64}, + }, + { + name: "BadOperandKind", + fn: Func{ + Sym: "badopkind", + Instrs: []Instr{ + {Op: OpTEXT}, + {Op: OpMOVD, Args: []Operand{{Kind: OpLabel, Sym: "target"}, {Kind: OpReg, Reg: AX}}, Raw: "MOVD target, AX"}, + {Op: OpRET}, + }, + }, + sig: FuncSig{Name: "example.badopkind", Ret: I64}, + }, + { + name: "BadRetType", + fn: Func{Sym: "badret", Instrs: []Instr{{Op: OpTEXT}, {Op: OpRET}}}, + sig: FuncSig{Name: "example.badret", Ret: LLVMType("v2i64")}, + }, + { + name: "BadArgType", + fn: Func{Sym: "badarg", Instrs: []Instr{{Op: OpTEXT}, {Op: OpRET}}}, + sig: FuncSig{Name: "example.badarg", Args: []LLVMType{LLVMType("v2i64")}, Ret: I64}, + }, + { + name: "BadFPReadSlot", + fn: Func{ + Sym: "badfprd", + Instrs: []Instr{ + {Op: OpTEXT}, + {Op: OpMOVD, Args: []Operand{{Kind: OpFP, FPOffset: 8}, {Kind: OpReg, Reg: AX}}, Raw: "MOVD arg+8(FP), AX"}, + {Op: OpRET}, + }, + }, + sig: FuncSig{Name: "example.badfprd", Ret: I64}, + }, + { + name: "BadFPArgIndex", + fn: Func{ + Sym: "badfpidx", + Instrs: []Instr{ + {Op: OpTEXT}, + {Op: OpMOVD, Args: []Operand{{Kind: OpFP, FPOffset: 8}, {Kind: OpReg, Reg: AX}}, Raw: "MOVD arg+8(FP), AX"}, + {Op: OpRET}, + }, + }, + sig: FuncSig{ + Name: "example.badfpidx", + Args: []LLVMType{I64}, + Ret: I64, + Frame: FrameLayout{ + Params: []FrameSlot{{Offset: 8, Type: I64, Index: 2}}, + }, + }, + }, + { + name: "BadFPWriteSlot", + fn: Func{ + Sym: "badfpwr", + Instrs: []Instr{ + {Op: OpTEXT}, + {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpFP, FPOffset: 8}}, Raw: "MOVD $1, ret+8(FP)"}, + {Op: OpRET}, + }, + }, + sig: FuncSig{Name: "example.badfpwr", Ret: I64}, + }, + { + name: "BadFPResultIndex", + fn: Func{ + Sym: "badresidx", + Instrs: []Instr{ + {Op: OpTEXT}, + {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpFP, FPOffset: 8}}, Raw: "MOVD $1, ret+8(FP)"}, + {Op: OpRET}, + }, + }, + sig: FuncSig{ + Name: "example.badresidx", + Ret: I64, + Frame: FrameLayout{ + Results: []FrameSlot{{Offset: 8, Type: I64, Index: 2}}, + }, + }, + }, + { + name: "BadMRSArgs", + fn: Func{ + Sym: "badmrs", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpMRS, Args: []Operand{{Kind: OpImm, Imm: 1}}, Raw: "MRS $1"}, {Op: OpRET}}, + }, + sig: FuncSig{Name: "example.badmrs", Ret: I64}, + }, + { + name: "BadMRSKinds", + fn: Func{ + Sym: "badmrskind", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpMRS, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}, Raw: "MRS $1, AX"}, {Op: OpRET}}, + }, + sig: FuncSig{Name: "example.badmrskind", Ret: I64}, + }, + { + name: "BadMOVDArgs", + fn: Func{ + Sym: "badmovd", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 1}}, Raw: "MOVD $1"}, {Op: OpRET}}, + }, + sig: FuncSig{Name: "example.badmovd", Ret: I64}, + }, + { + name: "BadMOVDDst", + fn: Func{ + Sym: "badmovddst", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpIdent, Ident: "label"}}, Raw: "MOVD $1, label"}, {Op: OpRET}}, + }, + sig: FuncSig{Name: "example.badmovddst", Ret: I64}, + }, + { + name: "BadMOVLArgs", + fn: Func{ + Sym: "badmovl", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVL, Args: []Operand{{Kind: OpImm, Imm: 1}}, Raw: "MOVL $1"}, {Op: OpRET}}, + }, + sig: FuncSig{Name: "example.badmovl", Ret: I64}, + }, + { + name: "BadMOVLDst", + fn: Func{ + Sym: "badmovldst", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVL, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpIdent, Ident: "label"}}, Raw: "MOVL $1, label"}, {Op: OpRET}}, + }, + sig: FuncSig{Name: "example.badmovldst", Ret: I64}, + }, + { + name: "BadADDArgs", + fn: Func{ + Sym: "badaddargs", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpADDQ, Args: []Operand{{Kind: OpImm, Imm: 1}}, Raw: "ADDQ $1"}, {Op: OpRET}}, + }, + sig: FuncSig{Name: "example.badaddargs", Ret: I64}, + }, + { + name: "BadADDDst", + fn: Func{ + Sym: "badadddst", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpADDQ, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpFP, FPOffset: 8}}, Raw: "ADDQ $1, ret+8(FP)"}, {Op: OpRET}}, + }, + sig: FuncSig{Name: "example.badadddst", Ret: I64}, + }, + { + name: "UnsupportedInstruction", + fn: Func{ + Sym: "badop", + Instrs: []Instr{{Op: OpTEXT}, {Op: "NOP", Raw: "NOP"}, {Op: OpRET}}, + }, + sig: FuncSig{Name: "example.badop", Ret: I64}, + }, + { + name: "InstructionAfterRET", + fn: Func{ + Sym: "afterret", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpRET}, {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}, Raw: "MOVD $1, AX"}}, + }, + sig: FuncSig{Name: "example.afterret", Ret: I64}, + }, + { + name: "NoRET", + fn: Func{ + Sym: "noret", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}, Raw: "MOVD $1, AX"}}, + }, + sig: FuncSig{Name: "example.noret", Ret: I64}, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + mod := ctx.NewModule("direct-" + tc.name) + defer mod.Dispose() + if err := translateFuncLinearModule(mod, ArchAMD64, tc.fn, tc.sig); !errors.Is(err, errDirectModuleUnsupported) { + t.Fatalf("translateFuncLinearModule(%s) error = %v", tc.name, err) + } + }) + } + }) +} + +func TestTranslateFuncLinearReturnCastCoverage(t *testing.T) { + cases := []struct { + name string + sig FuncSig + want []string + }{ + { + name: "I1ToDouble", + sig: FuncSig{Name: "example.i1tod", Args: []LLVMType{I1}, ArgRegs: []Reg{AX}, Ret: LLVMType("double")}, + want: []string{"zext i1 %arg0 to i32", "uitofp i32 %"}, + }, + { + name: "I8ToFloat", + sig: FuncSig{Name: "example.i8tof", Args: []LLVMType{I8}, ArgRegs: []Reg{AX}, Ret: LLVMType("float")}, + want: []string{"zext i8 %arg0 to i32", "uitofp i32 %"}, + }, + { + name: "I16ToDouble", + sig: FuncSig{Name: "example.i16tod", Args: []LLVMType{I16}, ArgRegs: []Reg{AX}, Ret: LLVMType("double")}, + want: []string{"zext i16 %arg0 to i32", "uitofp i32 %"}, + }, + { + name: "I32ToI1", + sig: FuncSig{Name: "example.i32toi1", Args: []LLVMType{I32}, ArgRegs: []Reg{AX}, Ret: I1}, + want: []string{"trunc i32 %arg0 to i1", "ret i1 %"}, + }, + { + name: "I32ToI8", + sig: FuncSig{Name: "example.i32toi8", Args: []LLVMType{I32}, ArgRegs: []Reg{AX}, Ret: I8}, + want: []string{"trunc i32 %arg0 to i8", "ret i8 %"}, + }, + { + name: "I32ToI16", + sig: FuncSig{Name: "example.i32toi16", Args: []LLVMType{I32}, ArgRegs: []Reg{AX}, Ret: I16}, + want: []string{"trunc i32 %arg0 to i16", "ret i16 %"}, + }, + { + name: "PtrToI32", + sig: FuncSig{Name: "example.ptoi32", Args: []LLVMType{Ptr}, ArgRegs: []Reg{AX}, Ret: I32}, + want: []string{"ptrtoint ptr %arg0 to i32", "ret i32 %"}, + }, + { + name: "PtrToI64", + sig: FuncSig{Name: "example.ptoi64", Args: []LLVMType{Ptr}, ArgRegs: []Reg{AX}, Ret: I64}, + want: []string{"ptrtoint ptr %arg0 to i64", "ret i64 %"}, + }, + { + name: "I32ToPtr", + sig: FuncSig{Name: "example.i32top", Args: []LLVMType{I32}, ArgRegs: []Reg{AX}, Ret: Ptr}, + want: []string{"inttoptr i32 %arg0 to ptr", "ret ptr %"}, + }, + { + name: "FloatToI32", + sig: FuncSig{Name: "example.ftoi32", Args: []LLVMType{LLVMType("float")}, ArgRegs: []Reg{AX}, Ret: I32}, + want: []string{"fptoui float %arg0 to i32", "ret i32 %"}, + }, + { + name: "DoubleToI64", + sig: FuncSig{Name: "example.dtoi64", Args: []LLVMType{LLVMType("double")}, ArgRegs: []Reg{AX}, Ret: I64}, + want: []string{"fptoui double %arg0 to i64", "ret i64 %"}, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + var b strings.Builder + if err := translateFuncLinear(&b, ArchAMD64, Func{ + Sym: "·retcast", + Instrs: []Instr{{Op: OpTEXT, Raw: "TEXT ·retcast(SB),NOSPLIT,$0-0"}, {Op: OpRET, Raw: "RET"}}, + }, tc.sig, false); err != nil { + t.Fatalf("translateFuncLinear(%s) error = %v", tc.name, err) + } + out := b.String() + for _, want := range tc.want { + if !strings.Contains(out, want) { + t.Fatalf("%s missing %q:\n%s", tc.name, want, out) + } + } + }) + } + + t.Run("AggregateZeroAndIgnoredWrite", func(t *testing.T) { + var b strings.Builder + err := translateFuncLinear(&b, ArchAMD64, Func{ + Sym: "·aggzero", + Instrs: []Instr{ + {Op: OpTEXT, Raw: "TEXT ·aggzero(SB),NOSPLIT,$0-0"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpFP, FPOffset: 99}}, Raw: "MOVQ $1, ignored+99(FP)"}, + {Op: OpRET, Raw: "RET"}, + }, + }, FuncSig{ + Name: "example.aggzero", + Ret: LLVMType("{i64, i32}"), + Frame: FrameLayout{ + Results: []FrameSlot{ + {Offset: 8, Type: I64, Index: 0}, + {Offset: 16, Type: I32, Index: 1}, + }, + }, + }, false) + if err != nil { + t.Fatalf("translateFuncLinear(aggzero) error = %v", err) + } + out := b.String() + for _, want := range []string{ + "add i64 0, 0", + "add i32 0, 0", + "insertvalue {i64, i32}", + } { + if !strings.Contains(out, want) { + t.Fatalf("aggzero missing %q:\n%s", want, out) + } + } + }) +} diff --git a/types_deep_test.go b/types_deep_test.go new file mode 100644 index 0000000..10e32df --- /dev/null +++ b/types_deep_test.go @@ -0,0 +1,218 @@ +package plan9asm + +import "testing" + +func TestTypeHelperCoverage(t *testing.T) { + cases := []struct { + op Operand + want string + }{ + {Operand{Kind: OpImm, Imm: 7}, "$7"}, + {Operand{Kind: OpImm, ImmRaw: "$(A+1)"}, "$(A+1)"}, + {Operand{Kind: OpReg, Reg: AX}, "AX"}, + {Operand{Kind: OpRegShift, Reg: "R1", ShiftOp: ShiftLeft, ShiftAmount: 2}, "R1<<2"}, + {Operand{Kind: OpRegShift, Reg: "R2", ShiftOp: ShiftRight, ShiftReg: "R3"}, "R2>>R3"}, + {Operand{Kind: OpFP, FPName: "arg", FPOffset: 8}, "arg+8(FP)"}, + {Operand{Kind: OpFPAddr, FPName: "arg", FPOffset: 16}, "$arg+16(FP)"}, + {Operand{Kind: OpIdent, Ident: "MIDR_EL1"}, "MIDR_EL1"}, + {Operand{Kind: OpSym, Sym: "foo(SB)"}, "foo(SB)"}, + {Operand{Kind: OpLabel, Sym: "loop"}, "loop:"}, + {Operand{Kind: OpMem, Mem: MemRef{Base: SI, Off: 8}}, "8(SI)"}, + {Operand{Kind: OpMem, Mem: MemRef{Base: BX, Off: -4, Index: CX, Scale: 2}}, "-4(BX)(CX*2)"}, + {Operand{Kind: OpRegList, RegList: []Reg{"R0", "R1"}}, "(R0, R1)"}, + {Operand{}, ""}, + } + for _, tc := range cases { + if got := tc.op.String(); got != tc.want { + t.Fatalf("Operand.String() = %q, want %q", got, tc.want) + } + } + + for _, tc := range []struct { + in string + want bool + }{ + {"foo(SB)", true}, + {"label<>", true}, + {"$foo+4(SB)", true}, + {"pkg/path.sym[SB]", true}, + {"runtime·bar+8(SB)", true}, + {"plainIdent", false}, + {"bad sym with space", false}, + } { + _, ok := parseSym(tc.in) + if ok != tc.want { + t.Fatalf("parseSym(%q) ok = %v, want %v", tc.in, ok, tc.want) + } + } + + for _, tc := range []struct { + in string + want bool + }{ + {"8(SI)", true}, + {"-4(BX)(CX*2)", true}, + {"(AX)(BX)", true}, + {"-1(AX*2)", true}, + {"(0*8)(R8)(BX*8)", true}, + {"(symSize)(R14)", true}, + {"not-mem", false}, + } { + _, ok := parseMem(tc.in) + if ok != tc.want { + t.Fatalf("parseMem(%q) ok = %v, want %v", tc.in, ok, tc.want) + } + } + + for _, tc := range []struct { + in string + want uint64 + ok bool + }{ + {"1+2*3", 7, true}, + {"8/2", 4, true}, + {"9%4", 1, true}, + {"1<<65", 0, true}, + {"8>>65", 0, true}, + {"7&3", 3, true}, + {"7|8", 15, true}, + {"7^3", 4, true}, + {"7&^3", 4, true}, + {"~1", ^uint64(1), true}, + {"1/0", 0, false}, + } { + got, ok := parseImmExpr(tc.in) + if ok != tc.ok || (ok && got != tc.want) { + t.Fatalf("parseImmExpr(%q) = (%d, %v), want (%d, %v)", tc.in, got, ok, tc.want, tc.ok) + } + } + + for _, tc := range []struct { + in string + want float64 + ok bool + }{ + {"1.5+2.5", 4.0, true}, + {"-(3.0)", -3.0, true}, + {"6/2", 3.0, true}, + {"1/0", 0, false}, + } { + got, ok := parseImmFloatExpr(tc.in) + if ok != tc.ok || (ok && got != tc.want) { + t.Fatalf("parseImmFloatExpr(%q) = (%v, %v), want (%v, %v)", tc.in, got, ok, tc.want, tc.ok) + } + } +} + +func TestTypeParserEdgeCoverage(t *testing.T) { + for _, tc := range []struct { + in string + want Reg + ok bool + }{ + {"rax", AX, true}, + {"w3", "R3", true}, + {"g", "R28", true}, + {"lr", "R30", true}, + {"r18_platform", "R18", true}, + {"k7", "K7", true}, + {"v2.b16", "V2.B16", true}, + {"f31", "F31", true}, + {"zz", "", false}, + } { + got, ok := parseReg(tc.in) + if got != tc.want || ok != tc.ok { + t.Fatalf("parseReg(%q) = (%q, %v), want (%q, %v)", tc.in, got, ok, tc.want, tc.ok) + } + } + + for _, tc := range []struct { + in string + want int64 + raw bool + valid bool + }{ + {"$0xffffffffffffffff", -1, false, true}, + {"$1.25", 4608308318706860032, false, true}, + {"$(16 + callbackArgs__size)", 0, true, true}, + {"$()", 0, false, false}, + } { + got, ok := parseImm(tc.in) + if got != tc.want || ok != tc.valid { + t.Fatalf("parseImm(%q) = (%d, %v), want (%d, %v)", tc.in, got, ok, tc.want, tc.valid) + } + if tc.valid && isSymbolicImmPlaceholder(tc.in) != tc.raw { + t.Fatalf("isSymbolicImmPlaceholder(%q) = %v, want %v", tc.in, !tc.raw, tc.raw) + } + } + if isSymbolicImmPlaceholder("$symbol") { + t.Fatalf("isSymbolicImmPlaceholder($symbol) unexpectedly succeeded") + } + + for _, tc := range []struct { + in string + want bool + }{ + {"arg+8(FP)", true}, + {"arg+8(SP)", false}, + {"$ret+16(FP)", true}, + {"$ret(FP)", false}, + } { + if _, _, ok := parseFP(tc.in); ok != tc.want && tc.in[0] != '$' { + t.Fatalf("parseFP(%q) ok = %v, want %v", tc.in, ok, tc.want) + } + if tc.in[0] == '$' { + if _, _, ok := parseFPAddr(tc.in); ok != tc.want { + t.Fatalf("parseFPAddr(%q) ok = %v, want %v", tc.in, ok, tc.want) + } + } + } + + for _, tc := range []struct { + in string + want bool + }{ + {"R1@>3", true}, + {"R2->R3", true}, + {"R4<<1+2", true}, + {"R4<(SB)", OpSym}, + {"R1@>2", OpRegShift}, + } { + op, err := parseOperand(tc.in) + if err != nil || op.Kind != tc.want { + t.Fatalf("parseOperand(%q) = (%v, %v), want kind %v", tc.in, err, op.Kind, tc.want) + } + } + if _, err := parseOperand("[]"); err == nil { + t.Fatalf("parseOperand([]) unexpectedly succeeded") + } + if _, err := parseOperand("[R0, bad]"); err == nil { + t.Fatalf("parseOperand([R0, bad]) unexpectedly succeeded") + } +} From 57e19251198b083c37ed69afa22a2d1e7e9102e1 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sat, 14 Mar 2026 16:00:00 +0800 Subject: [PATCH 6/6] Address PR review feedback --- amd64_helper_edge_test.go | 364 ++++++++++++++++---------------- arm64_helper_edge_test.go | 19 +- arm_helper_edge_test.go | 18 +- arm_more_test.go | 15 +- cmd/plan9asmscan/main_test.go | 15 +- translate_deep_coverage_test.go | 144 ++++++------- 6 files changed, 274 insertions(+), 301 deletions(-) diff --git a/amd64_helper_edge_test.go b/amd64_helper_edge_test.go index cb85a30..bf14835 100644 --- a/amd64_helper_edge_test.go +++ b/amd64_helper_edge_test.go @@ -14,20 +14,7 @@ func newAMD64CtxWithFuncForTest(t *testing.T, fn Func, sig FuncSig, sigs map[str sigs = map[string]FuncSig{} } var b strings.Builder - c := newAMD64Ctx(&b, fn, sig, func(sym string) string { - sym = goStripABISuffix(sym) - sym = strings.ReplaceAll(sym, "∕", "/") - if strings.HasPrefix(sym, "runtime·") { - return strings.ReplaceAll(sym, "·", ".") - } - if strings.HasPrefix(sym, "·") { - return "example." + strings.TrimPrefix(sym, "·") - } - if !strings.Contains(sym, "·") && !strings.Contains(sym, ".") && !strings.Contains(sym, "/") { - return "example." + sym - } - return strings.ReplaceAll(sym, "·", ".") - }, sigs, false) + c := newAMD64Ctx(&b, fn, sig, testResolveSym("example"), sigs, false) if err := c.emitEntryAllocas(); err != nil { t.Fatalf("emitEntryAllocas() error = %v", err) } @@ -84,133 +71,139 @@ func TestAMD64CtxHelperEdges(t *testing.T) { t.Fatalf("popI64() returned empty value") } - if got, err := c.loadX("X0"); err != nil || got == "" { - t.Fatalf("loadX(X0) = (%q, %v)", got, err) - } - if err := c.storeX("X1", "<16 x i8> zeroinitializer"); err != nil { - t.Fatalf("storeX(X1) error = %v", err) - } - if _, err := c.loadX("AX"); err == nil { - t.Fatalf("loadX(AX) unexpectedly succeeded") - } - if err := c.storeX("AX", "<16 x i8> zeroinitializer"); err == nil { - t.Fatalf("storeX(AX) unexpectedly succeeded") - } - - if got, err := c.loadY("Y2"); err != nil || got == "" { - t.Fatalf("loadY(Y2) = (%q, %v)", got, err) - } - if err := c.storeY("Y3", "<32 x i8> zeroinitializer"); err != nil { - t.Fatalf("storeY(Y3) error = %v", err) - } - if _, err := c.loadY("AX"); err == nil { - t.Fatalf("loadY(AX) unexpectedly succeeded") - } - if err := c.storeY("AX", "<32 x i8> zeroinitializer"); err == nil { - t.Fatalf("storeY(AX) unexpectedly succeeded") - } + t.Run("VectorRegs", func(t *testing.T) { + if got, err := c.loadX("X0"); err != nil || got == "" { + t.Fatalf("loadX(X0) = (%q, %v)", got, err) + } + if err := c.storeX("X1", "<16 x i8> zeroinitializer"); err != nil { + t.Fatalf("storeX(X1) error = %v", err) + } + if _, err := c.loadX("AX"); err == nil { + t.Fatalf("loadX(AX) unexpectedly succeeded") + } + if err := c.storeX("AX", "<16 x i8> zeroinitializer"); err == nil { + t.Fatalf("storeX(AX) unexpectedly succeeded") + } - if got, err := c.loadZ("Z4"); err != nil || got == "" { - t.Fatalf("loadZ(Z4) = (%q, %v)", got, err) - } - if err := c.storeZ("Z5", "<64 x i8> zeroinitializer"); err != nil { - t.Fatalf("storeZ(Z5) error = %v", err) - } - if _, err := c.loadZ("AX"); err == nil { - t.Fatalf("loadZ(AX) unexpectedly succeeded") - } - if err := c.storeZ("AX", "<64 x i8> zeroinitializer"); err == nil { - t.Fatalf("storeZ(AX) unexpectedly succeeded") - } + if got, err := c.loadY("Y2"); err != nil || got == "" { + t.Fatalf("loadY(Y2) = (%q, %v)", got, err) + } + if err := c.storeY("Y3", "<32 x i8> zeroinitializer"); err != nil { + t.Fatalf("storeY(Y3) error = %v", err) + } + if _, err := c.loadY("AX"); err == nil { + t.Fatalf("loadY(AX) unexpectedly succeeded") + } + if err := c.storeY("AX", "<32 x i8> zeroinitializer"); err == nil { + t.Fatalf("storeY(AX) unexpectedly succeeded") + } - if got, err := c.loadK("K1"); err != nil || got == "" { - t.Fatalf("loadK(K1) = (%q, %v)", got, err) - } - if err := c.storeK("K2", "9"); err != nil { - t.Fatalf("storeK(K2) error = %v", err) - } - if _, err := c.loadK("AX"); err == nil { - t.Fatalf("loadK(AX) unexpectedly succeeded") - } - if err := c.storeK("AX", "9"); err == nil { - t.Fatalf("storeK(AX) unexpectedly succeeded") - } + if got, err := c.loadZ("Z4"); err != nil || got == "" { + t.Fatalf("loadZ(Z4) = (%q, %v)", got, err) + } + if err := c.storeZ("Z5", "<64 x i8> zeroinitializer"); err != nil { + t.Fatalf("storeZ(Z5) error = %v", err) + } + if _, err := c.loadZ("AX"); err == nil { + t.Fatalf("loadZ(AX) unexpectedly succeeded") + } + if err := c.storeZ("AX", "<64 x i8> zeroinitializer"); err == nil { + t.Fatalf("storeZ(AX) unexpectedly succeeded") + } - c.setZFlagFromI64("1") - c.setZSFlagsFromI64("2") - c.setZSFlagsFromI32("3") - c.setCmpFlags("4", "5") - if got := c.loadFlag(c.flagsZSlot); got == "" { - t.Fatalf("loadFlag() returned empty value") - } + if got, err := c.loadK("K1"); err != nil || got == "" { + t.Fatalf("loadK(K1) = (%q, %v)", got, err) + } + if err := c.storeK("K2", "9"); err != nil { + t.Fatalf("storeK(K2) error = %v", err) + } + if _, err := c.loadK("AX"); err == nil { + t.Fatalf("loadK(AX) unexpectedly succeeded") + } + if err := c.storeK("AX", "9"); err == nil { + t.Fatalf("storeK(AX) unexpectedly succeeded") + } + }) + + t.Run("FlagsAndFP", func(t *testing.T) { + c.setZFlagFromI64("1") + c.setZSFlagsFromI64("2") + c.setZSFlagsFromI32("3") + c.setCmpFlags("4", "5") + if got := c.loadFlag(c.flagsZSlot); got == "" { + t.Fatalf("loadFlag() returned empty value") + } - if slot, ok := c.fpParam(32); !ok || slot.Type != I32 { - t.Fatalf("fpParam(32) = (%#v, %v)", slot, ok) - } - if _, ok := c.fpParam(999); ok { - t.Fatalf("fpParam(999) unexpectedly succeeded") - } - if alloca, ty, ok := c.fpResultAlloca(80); !ok || alloca == "" || ty != I32 { - t.Fatalf("fpResultAlloca(80) = (%q, %q, %v)", alloca, ty, ok) - } - if _, _, ok := c.fpResultAlloca(999); ok { - t.Fatalf("fpResultAlloca(999) unexpectedly succeeded") - } - c.markFPResultAddrTaken(88) - c.markFPResultWritten(80) + if slot, ok := c.fpParam(32); !ok || slot.Type != I32 { + t.Fatalf("fpParam(32) = (%#v, %v)", slot, ok) + } + if _, ok := c.fpParam(999); ok { + t.Fatalf("fpParam(999) unexpectedly succeeded") + } + if alloca, ty, ok := c.fpResultAlloca(80); !ok || alloca == "" || ty != I32 { + t.Fatalf("fpResultAlloca(80) = (%q, %q, %v)", alloca, ty, ok) + } + if _, _, ok := c.fpResultAlloca(999); ok { + t.Fatalf("fpResultAlloca(999) unexpectedly succeeded") + } + c.markFPResultAddrTaken(88) + c.markFPResultWritten(80) - for _, off := range []int64{0, 8, 16, 24, 32, 40, 48, 56} { - if got, err := c.evalFPToI64(off); err != nil || got == "" { - t.Fatalf("evalFPToI64(%d) = (%q, %v)", off, got, err) + for _, off := range []int64{0, 8, 16, 24, 32, 40, 48, 56} { + if got, err := c.evalFPToI64(off); err != nil || got == "" { + t.Fatalf("evalFPToI64(%d) = (%q, %v)", off, got, err) + } + } + c.fpParams[64] = FrameSlot{Offset: 64, Type: LLVMType("v4i32"), Index: 0} + if _, err := c.evalFPToI64(64); err == nil { + t.Fatalf("evalFPToI64(unsupported type) unexpectedly succeeded") + } + c.fpParams[72] = FrameSlot{Offset: 72, Type: I64, Index: 99} + if _, err := c.evalFPToI64(72); err == nil { + t.Fatalf("evalFPToI64(invalid index) unexpectedly succeeded") } - } - c.fpParams[64] = FrameSlot{Offset: 64, Type: LLVMType("v4i32"), Index: 0} - if _, err := c.evalFPToI64(64); err == nil { - t.Fatalf("evalFPToI64(unsupported type) unexpectedly succeeded") - } - c.fpParams[72] = FrameSlot{Offset: 72, Type: I64, Index: 99} - if _, err := c.evalFPToI64(72); err == nil { - t.Fatalf("evalFPToI64(invalid index) unexpectedly succeeded") - } - if err := c.storeFPResult(80, I64, "11"); err != nil { - t.Fatalf("storeFPResult(i64->i32) error = %v", err) - } - if err := c.storeFPResult(88, I64, "12"); err != nil { - t.Fatalf("storeFPResult(i64->ptr) error = %v", err) - } - if err := c.storeFPResult(96, I64, "13"); err != nil { - t.Fatalf("storeFPResult(i64->double) error = %v", err) - } - if err := c.storeFPResult(104, LLVMType("float"), "%f"); err != nil { - t.Fatalf("storeFPResult(float->float) error = %v", err) - } - if err := c.storeFPResult(112, I8, "15"); err != nil { - t.Fatalf("storeFPResult(i8->i16) error = %v", err) - } - if err := c.storeFPResult(96, LLVMType("double"), "%x"); err != nil { - t.Fatalf("storeFPResult(double->double) error = %v", err) - } - if err := c.storeFPResult(80, Ptr, "%x"); err == nil { - t.Fatalf("storeFPResult(ptr->i32) unexpectedly succeeded") - } + if err := c.storeFPResult(80, I64, "11"); err != nil { + t.Fatalf("storeFPResult(i64->i32) error = %v", err) + } + if err := c.storeFPResult(88, I64, "12"); err != nil { + t.Fatalf("storeFPResult(i64->ptr) error = %v", err) + } + if err := c.storeFPResult(96, I64, "13"); err != nil { + t.Fatalf("storeFPResult(i64->double) error = %v", err) + } + if err := c.storeFPResult(104, LLVMType("float"), "%f"); err != nil { + t.Fatalf("storeFPResult(float->float) error = %v", err) + } + if err := c.storeFPResult(112, I8, "15"); err != nil { + t.Fatalf("storeFPResult(i8->i16) error = %v", err) + } + if err := c.storeFPResult(96, LLVMType("double"), "%x"); err != nil { + t.Fatalf("storeFPResult(double->double) error = %v", err) + } + if err := c.storeFPResult(80, Ptr, "%x"); err == nil { + t.Fatalf("storeFPResult(ptr->i32) unexpectedly succeeded") + } - if got, err := c.loadFPResult(FrameSlot{Index: 0, Type: I32}); err != nil || got == "" { - t.Fatalf("loadFPResult() = (%q, %v)", got, err) - } - if _, err := c.loadFPResult(FrameSlot{Index: 99, Type: I32}); err == nil { - t.Fatalf("loadFPResult(missing) unexpectedly succeeded") - } + if got, err := c.loadFPResult(FrameSlot{Index: 0, Type: I32}); err != nil || got == "" { + t.Fatalf("loadFPResult() = (%q, %v)", got, err) + } + if _, err := c.loadFPResult(FrameSlot{Index: 99, Type: I32}); err == nil { + t.Fatalf("loadFPResult(missing) unexpectedly succeeded") + } + }) - if !isAMD64FloatRetTy(LLVMType("double")) || !isAMD64FloatRetTy(LLVMType("float")) || isAMD64FloatRetTy(I64) { - t.Fatalf("isAMD64FloatRetTy() mismatch") - } - if got, ok := c.retIntRegByOrd(1); !ok || got != BX { - t.Fatalf("retIntRegByOrd(1) = (%q, %v)", got, ok) - } - if _, ok := c.retIntRegByOrd(-1); ok { - t.Fatalf("retIntRegByOrd(-1) unexpectedly succeeded") - } + t.Run("ReturnHelpers", func(t *testing.T) { + if !isAMD64FloatRetTy(LLVMType("double")) || !isAMD64FloatRetTy(LLVMType("float")) || isAMD64FloatRetTy(I64) { + t.Fatalf("isAMD64FloatRetTy() mismatch") + } + if got, ok := c.retIntRegByOrd(1); !ok || got != BX { + t.Fatalf("retIntRegByOrd(1) = (%q, %v)", got, ok) + } + if _, ok := c.retIntRegByOrd(-1); ok { + t.Fatalf("retIntRegByOrd(-1) unexpectedly succeeded") + } + }) if err := c.storeReg(AX, "21"); err != nil { t.Fatalf("storeReg(AX) error = %v", err) @@ -1751,59 +1744,66 @@ func TestAMD64VectorAliasAndErrorCoverage(t *testing.T) { t.Fatalf("VPERMI2B wrong mask reg = (%v, %v, %v)", ok, term, err) } for _, tc := range []struct { - op Op - ins Instr - want string + op Op + ins Instr + want string + wantHandled bool }{ - {"VPOPCNTB", Instr{Raw: "VPOPCNTB Z0, Y0", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst"}, - {"VPCMPUQ", Instr{Raw: "VPCMPUQ $3, Z0, Z1, K1", Args: []Operand{{Kind: OpImm, Imm: 3}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("K1")}}}, "bad imm"}, - {"VPCMPUQ", Instr{Raw: "VPCMPUQ $1, Z0, Z1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: AX}}}, "wrong dst reg"}, - {"VPCOMPRESSQ", Instr{Raw: "VPCOMPRESSQ Z0, AX, Z1", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("Z1")}}}, "wrong mask"}, - {"VPCOMPRESSQ", Instr{Raw: "VPCOMPRESSQ Z0, K1, Y0", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("K1")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst"}, - {"VPXORQ", Instr{Raw: "VPXORQ Z0, Z1, Y0", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong z dst"}, - {"VPSHUFB", Instr{Raw: "VPSHUFB Y0, Y1, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: AX}}}, "wrong dst"}, - {"VPSHUFD", Instr{Raw: "VPSHUFD $1, X0, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong src class"}, - {"VPSLLD", Instr{Raw: "VPSLLD Y0, Y1", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}, "short form"}, - {"VPERM2I128", Instr{Raw: "VPERM2I128 $1, Y0, Y1, X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong dst"}, - {"VINSERTI128", Instr{Raw: "VINSERTI128 $1, X0, Y0, X1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X1")}}}, "wrong dst"}, - {"VMOVNTDQ", Instr{Raw: "VMOVNTDQ Y0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: AX}}}, "bad dst"}, - {"AESKEYGENASSIST", Instr{Raw: "AESKEYGENASSIST X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}, "missing imm"}, - {"VPTEST", Instr{Raw: "VPTEST X0, Y0", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong src class"}, - {"PCMPESTRI", Instr{Raw: "PCMPESTRI $1, 8(BX), X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}, {Kind: OpReg, Reg: Reg("X0")}}}, "unsupported imm"}, - {"PCMPESTRI", Instr{Raw: "PCMPESTRI $0x0c, AX, X0", Args: []Operand{{Kind: OpImm, Imm: 0x0c}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}}}, "bad mem operand"}, - {"VPBLENDD", Instr{Raw: "VPBLENDD $1, Y0, Y1, X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong dst"}, - {"VPBROADCASTB", Instr{Raw: "VPBROADCASTB Y0, Y1", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}, "wrong src"}, - {"VPSRLDQ", Instr{Raw: "VPSRLDQ $2, X0, Y0", Args: []Operand{{Kind: OpImm, Imm: 2}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong src class"}, - {"PUNPCKLBW", Instr{Raw: "PUNPCKLBW Y0, X0", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong src class"}, - {"PSHUFHW", Instr{Raw: "PSHUFHW $1, X0, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst"}, - {"SHUFPS", Instr{Raw: "SHUFPS $1, X0, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst"}, - {"MOVOU", Instr{Raw: "MOVOU AX, X0", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong src class"}, - {"MOVOU", Instr{Raw: "MOVOU X0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: AX}}}, "wrong dst class"}, - {"PXOR", Instr{Raw: "PXOR X0", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}}}, "short form"}, - {"PADDL", Instr{Raw: "PADDL X0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: AX}}}, "wrong dst class"}, - {"PSLLL", Instr{Raw: "PSLLL AX, X0", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}}}, "non-imm shift"}, - {"PCMPEQL", Instr{Raw: "PCMPEQL Y0, X0", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong src class"}, - {"VPCLMULQDQ", Instr{Raw: "VPCLMULQDQ $1, Z0, Z1, X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong dst class"}, - {"VPTERNLOGD", Instr{Raw: "VPTERNLOGD $0x95, Z0, Z1, Z2", Args: []Operand{{Kind: OpImm, Imm: 0x95}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Z2")}}}, "unsupported imm"}, - {"VEXTRACTF32X4", Instr{Raw: "VEXTRACTF32X4 $1, X0, X1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}, "wrong src class"}, - {"PCLMULQDQ", Instr{Raw: "PCLMULQDQ AX, X0, X1", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}, "missing imm"}, - {"PCMPEQB", Instr{Raw: "PCMPEQB Y0, X0", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong src class"}, - {"PMOVMSKB", Instr{Raw: "PMOVMSKB Y0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: AX}}}, "wrong src class"}, - {"PSHUFB", Instr{Raw: "PSHUFB Y0, X0", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong mask class"}, - {"PINSRQ", Instr{Raw: "PINSRQ AX, X0", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}}}, "short form"}, - {"PINSRD", Instr{Raw: "PINSRD $1, AX, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst class"}, - {"PINSRW", Instr{Raw: "PINSRW $1, AX, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst class"}, - {"PINSRB", Instr{Raw: "PINSRB $1, AX, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst class"}, - {"PEXTRB", Instr{Raw: "PEXTRB AX, X0, BX", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: BX}}}, "missing imm"}, - {"PALIGNR", Instr{Raw: "PALIGNR $1, Y0, X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong src class"}, - {"PSRLDQ", Instr{Raw: "PSRLDQ $17, X0", Args: []Operand{{Kind: OpImm, Imm: 17}, {Kind: OpReg, Reg: Reg("X0")}}}, "invalid imm"}, - {"PSLLDQ", Instr{Raw: "PSLLDQ $17, X0", Args: []Operand{{Kind: OpImm, Imm: 17}, {Kind: OpReg, Reg: Reg("X0")}}}, "invalid imm"}, - {"PSRLQ", Instr{Raw: "PSRLQ AX, X0", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}}}, "non-imm shift"}, - {"PEXTRD", Instr{Raw: "PEXTRD $1, X0, label", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpIdent, Ident: "label"}}}, "bad dst kind"}, + {"VPOPCNTB", Instr{Raw: "VPOPCNTB Z0, Y0", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst", false}, + {"VPCMPUQ", Instr{Raw: "VPCMPUQ $3, Z0, Z1, K1", Args: []Operand{{Kind: OpImm, Imm: 3}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("K1")}}}, "bad imm", true}, + {"VPCMPUQ", Instr{Raw: "VPCMPUQ $1, Z0, Z1, AX", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: AX}}}, "wrong dst reg", false}, + {"VPCOMPRESSQ", Instr{Raw: "VPCOMPRESSQ Z0, AX, Z1", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("Z1")}}}, "wrong mask", false}, + {"VPCOMPRESSQ", Instr{Raw: "VPCOMPRESSQ Z0, K1, Y0", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("K1")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst", false}, + {"VPXORQ", Instr{Raw: "VPXORQ Z0, Z1, Y0", Args: []Operand{{Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong z dst", true}, + {"VPSHUFB", Instr{Raw: "VPSHUFB Y0, Y1, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: AX}}}, "wrong dst", false}, + {"VPSHUFD", Instr{Raw: "VPSHUFD $1, X0, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong src class", false}, + {"VPSLLD", Instr{Raw: "VPSLLD Y0, Y1", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}, "short form", true}, + {"VPERM2I128", Instr{Raw: "VPERM2I128 $1, Y0, Y1, X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong dst", false}, + {"VINSERTI128", Instr{Raw: "VINSERTI128 $1, X0, Y0, X1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X1")}}}, "wrong dst", false}, + {"VMOVNTDQ", Instr{Raw: "VMOVNTDQ Y0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: AX}}}, "bad dst", true}, + {"AESKEYGENASSIST", Instr{Raw: "AESKEYGENASSIST X0, X1", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}, "missing imm", true}, + {"VPTEST", Instr{Raw: "VPTEST X0, Y0", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong src class", false}, + {"PCMPESTRI", Instr{Raw: "PCMPESTRI $1, 8(BX), X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpMem, Mem: MemRef{Base: BX, Off: 8}}, {Kind: OpReg, Reg: Reg("X0")}}}, "unsupported imm", true}, + {"PCMPESTRI", Instr{Raw: "PCMPESTRI $0x0c, AX, X0", Args: []Operand{{Kind: OpImm, Imm: 0x0c}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}}}, "bad mem operand", true}, + {"VPBLENDD", Instr{Raw: "VPBLENDD $1, Y0, Y1, X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong dst", false}, + {"VPBROADCASTB", Instr{Raw: "VPBROADCASTB Y0, Y1", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("Y1")}}}, "wrong src", false}, + {"VPSRLDQ", Instr{Raw: "VPSRLDQ $2, X0, Y0", Args: []Operand{{Kind: OpImm, Imm: 2}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong src class", false}, + {"PUNPCKLBW", Instr{Raw: "PUNPCKLBW Y0, X0", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong src class", false}, + {"PSHUFHW", Instr{Raw: "PSHUFHW $1, X0, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst", false}, + {"SHUFPS", Instr{Raw: "SHUFPS $1, X0, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst", false}, + {"MOVOU", Instr{Raw: "MOVOU AX, X0", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong src class", true}, + {"MOVOU", Instr{Raw: "MOVOU X0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: AX}}}, "wrong dst class", false}, + {"PXOR", Instr{Raw: "PXOR X0", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}}}, "short form", true}, + {"PADDL", Instr{Raw: "PADDL X0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: AX}}}, "wrong dst class", false}, + {"PSLLL", Instr{Raw: "PSLLL AX, X0", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}}}, "non-imm shift", true}, + {"PCMPEQL", Instr{Raw: "PCMPEQL Y0, X0", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong src class", false}, + {"VPCLMULQDQ", Instr{Raw: "VPCLMULQDQ $1, Z0, Z1, X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong dst class", false}, + {"VPTERNLOGD", Instr{Raw: "VPTERNLOGD $0x95, Z0, Z1, Z2", Args: []Operand{{Kind: OpImm, Imm: 0x95}, {Kind: OpReg, Reg: Reg("Z0")}, {Kind: OpReg, Reg: Reg("Z1")}, {Kind: OpReg, Reg: Reg("Z2")}}}, "unsupported imm", true}, + {"VEXTRACTF32X4", Instr{Raw: "VEXTRACTF32X4 $1, X0, X1", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}, "wrong src class", false}, + {"PCLMULQDQ", Instr{Raw: "PCLMULQDQ AX, X0, X1", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: Reg("X1")}}}, "missing imm", true}, + {"PCMPEQB", Instr{Raw: "PCMPEQB Y0, X0", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong src class", false}, + {"PMOVMSKB", Instr{Raw: "PMOVMSKB Y0, AX", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: AX}}}, "wrong src class", false}, + {"PSHUFB", Instr{Raw: "PSHUFB Y0, X0", Args: []Operand{{Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong mask class", true}, + {"PINSRQ", Instr{Raw: "PINSRQ AX, X0", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}}}, "short form", true}, + {"PINSRD", Instr{Raw: "PINSRD $1, AX, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst class", false}, + {"PINSRW", Instr{Raw: "PINSRW $1, AX, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst class", false}, + {"PINSRB", Instr{Raw: "PINSRB $1, AX, Y0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("Y0")}}}, "wrong dst class", false}, + {"PEXTRB", Instr{Raw: "PEXTRB AX, X0, BX", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpReg, Reg: BX}}}, "missing imm", true}, + {"PALIGNR", Instr{Raw: "PALIGNR $1, Y0, X0", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("Y0")}, {Kind: OpReg, Reg: Reg("X0")}}}, "wrong src class", false}, + {"PSRLDQ", Instr{Raw: "PSRLDQ $17, X0", Args: []Operand{{Kind: OpImm, Imm: 17}, {Kind: OpReg, Reg: Reg("X0")}}}, "invalid imm", true}, + {"PSLLDQ", Instr{Raw: "PSLLDQ $17, X0", Args: []Operand{{Kind: OpImm, Imm: 17}, {Kind: OpReg, Reg: Reg("X0")}}}, "invalid imm", true}, + {"PSRLQ", Instr{Raw: "PSRLQ AX, X0", Args: []Operand{{Kind: OpReg, Reg: AX}, {Kind: OpReg, Reg: Reg("X0")}}}, "non-imm shift", true}, + {"PEXTRD", Instr{Raw: "PEXTRD $1, X0, label", Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: Reg("X0")}, {Kind: OpIdent, Ident: "label"}}}, "bad dst kind", true}, } { ok, term, err := c.lowerVec(tc.op, tc.ins) - if ok && err == nil { - t.Fatalf("%s %s unexpectedly succeeded (term=%v)", tc.op, tc.want, term) + if tc.wantHandled { + if !ok || err == nil { + t.Fatalf("%s %s = (%v, %v, %v), want handled error", tc.op, tc.want, ok, term, err) + } + continue + } + if ok || err != nil { + t.Fatalf("%s %s = (%v, %v, %v), want unhandled failure", tc.op, tc.want, ok, term, err) } } diff --git a/arm64_helper_edge_test.go b/arm64_helper_edge_test.go index 2444655..fc05ee8 100644 --- a/arm64_helper_edge_test.go +++ b/arm64_helper_edge_test.go @@ -15,20 +15,7 @@ func newARM64CtxWithFuncForTest(t *testing.T, fn Func, sig FuncSig, sigs map[str sigs = map[string]FuncSig{} } var b strings.Builder - c := newARM64Ctx(&b, fn, sig, func(sym string) string { - sym = goStripABISuffix(sym) - sym = strings.ReplaceAll(sym, "∕", "/") - if strings.HasPrefix(sym, "runtime·") { - return strings.ReplaceAll(sym, "·", ".") - } - if strings.HasPrefix(sym, "·") { - return "example." + strings.TrimPrefix(sym, "·") - } - if !strings.Contains(sym, "·") && !strings.Contains(sym, ".") && !strings.Contains(sym, "/") { - return "example." + sym - } - return strings.ReplaceAll(sym, "·", ".") - }, sigs, false) + c := newARM64Ctx(&b, fn, sig, testResolveSym("example"), sigs, false) if err := c.emitEntryAllocasAndArgInit(); err != nil { t.Fatalf("emitEntryAllocasAndArgInit() error = %v", err) } @@ -1062,8 +1049,8 @@ func TestARM64VectorEdgeCoverage(t *testing.T) { check("VLD1", true, Instr{Op: "VLD1.P", Raw: "VLD1.P (R20), V3.D[1]", Args: []Operand{arm64MemOp("R20", 0), arm64RegOp("V3.D[1]")}}) check("VLD1", true, Instr{Op: "VLD1.P", Raw: "VLD1.P (R20), V4.S[1]", Args: []Operand{arm64MemOp("R20", 0), arm64RegOp("V4.S[1]")}}) check("VLD1", true, Instr{Op: "VLD1.P", Raw: "VLD1.P (R20), V5.H[3]", Args: []Operand{arm64MemOp("R20", 0), arm64RegOp("V5.H[3]")}}) - check("VLD1", true, Instr{Op: "VLD1.P (R20), [V6]", Args: []Operand{arm64MemOp("R20", 0), arm64RegListOp("V6")}}) - check("VLD1", true, Instr{Op: "VLD1.P (R20), [V7, V8, V9]", Args: []Operand{arm64MemOp("R20", 0), arm64RegListOp("V7", "V8", "V9")}}) + check("VLD1", true, Instr{Op: "VLD1.P", Raw: "VLD1.P (R20), [V6]", Args: []Operand{arm64MemOp("R20", 0), arm64RegListOp("V6")}}) + check("VLD1", true, Instr{Op: "VLD1.P", Raw: "VLD1.P (R20), [V7, V8, V9]", Args: []Operand{arm64MemOp("R20", 0), arm64RegListOp("V7", "V8", "V9")}}) check("VST1", true, Instr{Op: "VST1.P", Raw: "VST1.P [V0], (R20)", Args: []Operand{arm64RegListOp("V0"), arm64MemOp("R20", 0)}}) check("VST1", true, Instr{Op: "VST1.P", Raw: "VST1.P [V1, V2, V3], (R20)", Args: []Operand{arm64RegListOp("V1", "V2", "V3"), arm64MemOp("R20", 0)}}) diff --git a/arm_helper_edge_test.go b/arm_helper_edge_test.go index ea67474..57a01e8 100644 --- a/arm_helper_edge_test.go +++ b/arm_helper_edge_test.go @@ -6,8 +6,6 @@ import ( "testing" ) -var errTestSentinel = errors.New("sentinel") - func newARMCtxWithFuncForTest(t *testing.T, fn Func, sig FuncSig, sigs map[string]FuncSig) (*armCtx, *strings.Builder) { t.Helper() if sig.Name == "" { @@ -17,20 +15,7 @@ func newARMCtxWithFuncForTest(t *testing.T, fn Func, sig FuncSig, sigs map[strin sigs = map[string]FuncSig{} } var b strings.Builder - c := newARMCtx(&b, fn, sig, func(sym string) string { - sym = goStripABISuffix(sym) - sym = strings.ReplaceAll(sym, "∕", "/") - if strings.HasPrefix(sym, "runtime·") { - return strings.ReplaceAll(sym, "·", ".") - } - if strings.HasPrefix(sym, "·") { - return "example." + strings.TrimPrefix(sym, "·") - } - if !strings.Contains(sym, "·") && !strings.Contains(sym, ".") && !strings.Contains(sym, "/") { - return "example." + sym - } - return strings.ReplaceAll(sym, "·", ".") - }, sigs, false) + c := newARMCtx(&b, fn, sig, testResolveSym("example"), sigs, false) if err := c.emitEntryAllocasAndArgInit(); err != nil { t.Fatalf("emitEntryAllocasAndArgInit() error = %v", err) } @@ -293,6 +278,7 @@ func TestARMBranchAndSyscallExtraEdges(t *testing.T) { } func TestARMBranchExactErrorCoverage(t *testing.T) { + errTestSentinel := errors.New("sentinel") mk := func(ret LLVMType) *armCtx { c, _ := newARMCtxForTest(t, FuncSig{Name: "example.branch_exact", Ret: ret}, map[string]FuncSig{ "example.voidsink": {Name: "example.voidsink", Ret: Void}, diff --git a/arm_more_test.go b/arm_more_test.go index b42e409..6a0f846 100644 --- a/arm_more_test.go +++ b/arm_more_test.go @@ -14,20 +14,7 @@ func newARMCtxForTest(t *testing.T, sig FuncSig, sigs map[string]FuncSig) (*armC sigs = map[string]FuncSig{} } var b strings.Builder - c := newARMCtx(&b, Func{}, sig, func(sym string) string { - sym = goStripABISuffix(sym) - sym = strings.ReplaceAll(sym, "∕", "/") - if strings.HasPrefix(sym, "runtime·") { - return strings.ReplaceAll(sym, "·", ".") - } - if strings.HasPrefix(sym, "·") { - return "example." + strings.TrimPrefix(sym, "·") - } - if !strings.Contains(sym, "·") && !strings.Contains(sym, ".") && !strings.Contains(sym, "/") { - return "example." + sym - } - return strings.ReplaceAll(sym, "·", ".") - }, sigs, false) + c := newARMCtx(&b, Func{}, sig, testResolveSym("example"), sigs, false) if err := c.emitEntryAllocasAndArgInit(); err != nil { t.Fatalf("emitEntryAllocasAndArgInit() error = %v", err) } diff --git a/cmd/plan9asmscan/main_test.go b/cmd/plan9asmscan/main_test.go index 6f0e176..0e3c956 100644 --- a/cmd/plan9asmscan/main_test.go +++ b/cmd/plan9asmscan/main_test.go @@ -389,6 +389,7 @@ func TestMainAndFatalfSubprocess(t *testing.T) { } outPath := filepath.Join(t.TempDir(), "report.json") + helperHome := t.TempDir() oldArgs := os.Args oldFlags := flag.CommandLine defer func() { @@ -410,14 +411,24 @@ func TestMainAndFatalfSubprocess(t *testing.T) { } cmd := exec.Command(testBin, "-test.run=TestMainAndFatalfSubprocess") - cmd.Env = append(os.Environ(), "PLAN9ASMSCAN_MAIN_HELPER=1") + cmd.Env = []string{ + "PATH=" + os.Getenv("PATH"), + "HOME=" + helperHome, + "GOCACHE=" + filepath.Join(helperHome, "gocache"), + "PLAN9ASMSCAN_MAIN_HELPER=1", + } out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("main helper failed: %v\n%s", err, out) } fcmd := exec.Command(testBin, "-test.run=TestMainAndFatalfSubprocess") - fcmd.Env = append(os.Environ(), "PLAN9ASMSCAN_FATALF_HELPER=1") + fcmd.Env = []string{ + "PATH=" + os.Getenv("PATH"), + "HOME=" + helperHome, + "GOCACHE=" + filepath.Join(helperHome, "gocache"), + "PLAN9ASMSCAN_FATALF_HELPER=1", + } fout, err := fcmd.CombinedOutput() if err == nil { t.Fatalf("fatalf helper unexpectedly succeeded") diff --git a/translate_deep_coverage_test.go b/translate_deep_coverage_test.go index 50fbe11..7ff220d 100644 --- a/translate_deep_coverage_test.go +++ b/translate_deep_coverage_test.go @@ -528,7 +528,7 @@ func TestTranslateModuleDirectErrorCoverage(t *testing.T) { { name: "unresolved-arm-imm", file: &File{Arch: ArchARM, Funcs: []Func{{ - Sym: "·armbad", + Sym: "·armbad", Instrs: []Instr{{Op: OpTEXT}, {Op: "MOVW", Args: []Operand{{Kind: OpImm, ImmRaw: "$(sym)"}, {Kind: OpReg, Reg: "R0"}}}, {Op: OpRET}}, }}}, opt: Options{ResolveSym: resolve, Sigs: map[string]FuncSig{"example.armbad": {Name: "example.armbad", Ret: Void}}}, @@ -536,7 +536,7 @@ func TestTranslateModuleDirectErrorCoverage(t *testing.T) { { name: "arm-cfg", file: &File{Arch: ArchARM, Funcs: []Func{{ - Sym: "·armcfg", + Sym: "·armcfg", Instrs: []Instr{{Op: OpTEXT}, {Op: OpLABEL, Args: []Operand{{Kind: OpLabel, Sym: "loop"}}}, {Op: OpRET}}, }}}, opt: Options{ResolveSym: resolve, Sigs: map[string]FuncSig{"example.armcfg": {Name: "example.armcfg", Ret: Void}}}, @@ -544,7 +544,7 @@ func TestTranslateModuleDirectErrorCoverage(t *testing.T) { { name: "arm64-cfg", file: &File{Arch: ArchARM64, Funcs: []Func{{ - Sym: "·arm64cfg", + Sym: "·arm64cfg", Instrs: []Instr{{Op: OpTEXT}, {Op: OpLABEL, Args: []Operand{{Kind: OpLabel, Sym: "loop"}}}, {Op: OpRET}}, }}}, opt: Options{ResolveSym: resolve, Goarch: "arm64", Sigs: map[string]FuncSig{"example.arm64cfg": {Name: "example.arm64cfg", Ret: Void}}}, @@ -552,7 +552,7 @@ func TestTranslateModuleDirectErrorCoverage(t *testing.T) { { name: "amd64-cfg", file: &File{Arch: ArchAMD64, Funcs: []Func{{ - Sym: "·amd64cfg", + Sym: "·amd64cfg", Instrs: []Instr{{Op: OpTEXT}, {Op: OpLABEL, Args: []Operand{{Kind: OpLabel, Sym: "loop"}}}, {Op: OpRET}}, }}}, opt: Options{ResolveSym: resolve, Goarch: "amd64", Sigs: map[string]FuncSig{"example.amd64cfg": {Name: "example.amd64cfg", Ret: I64}}}, @@ -564,8 +564,6 @@ func TestTranslateModuleDirectErrorCoverage(t *testing.T) { } ctx := llvm.GlobalContext() - mod := ctx.NewModule("direct-errors") - defer mod.Dispose() for _, tc := range []struct { name string fn Func @@ -612,15 +610,19 @@ func TestTranslateModuleDirectErrorCoverage(t *testing.T) { sig: FuncSig{Name: "example.bad8", Ret: I64}, }, } { - if err := translateFuncLinearModule(mod, ArchAMD64, tc.fn, tc.sig); !errors.Is(err, errDirectModuleUnsupported) { - t.Fatalf("%s error = %v", tc.name, err) - } + t.Run(tc.name, func(t *testing.T) { + mod := ctx.NewModule("direct-errors-" + tc.name) + defer mod.Dispose() + if err := translateFuncLinearModule(mod, ArchAMD64, tc.fn, tc.sig); !errors.Is(err, errDirectModuleUnsupported) { + t.Fatalf("%s error = %v", tc.name, err) + } + }) } okMod := ctx.NewModule("direct-ok") defer okMod.Dispose() if err := translateFuncLinearModule(okMod, ArchAMD64, Func{ - Sym: "example.byteok", + Sym: "example.byteok", Instrs: []Instr{{Op: OpTEXT}, {Op: OpRET}, {Op: OpBYTE}}, }, FuncSig{Name: "example.byteok", Ret: I64}); err != nil { t.Fatalf("translateFuncLinearModule(byte-after-ret) error = %v", err) @@ -888,7 +890,7 @@ func TestTranslateFuncLinearModuleSuccessCoverage(t *testing.T) { mod3 := ctx.NewModule("direct-success-3") defer mod3.Dispose() err = translateFuncLinearModule(mod3, ArchAMD64, Func{ - Sym: "example.zero", + Sym: "example.zero", Instrs: []Instr{{Op: OpTEXT}, {Op: OpRET}}, }, FuncSig{Name: "example.zero", Ret: I64}) if err != nil { @@ -1042,7 +1044,7 @@ func TestTranslateFuncLinearCastAndErrorCoverage(t *testing.T) { Sym: "·aggmem", Instrs: []Instr{ {Op: OpTEXT, Raw: "TEXT ·aggmem(SB),NOSPLIT,$0-0"}, - {Op: OpMOVQ, Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: AX}}, {Kind: OpReg, Reg: AX}}, Raw: "MOVQ (AX), AX"}, + {Op: OpMOVQ, Args: []Operand{{Kind: OpMem, Mem: MemRef{Base: AX}}, {Kind: OpReg, Reg: AX}}, Raw: "MOVQ (AX), AX"}, {Op: OpRET, Raw: "RET"}, }, }, @@ -1085,7 +1087,7 @@ func TestTranslateFuncLinearCastAndErrorCoverage(t *testing.T) { {Op: OpRET, Raw: "RET"}, }, }, - sig: FuncSig{Name: "example.badadd", Ret: I64}, + sig: FuncSig{Name: "example.badadd", Ret: I64}, wantErr: "dst must be register", }, { @@ -1239,35 +1241,35 @@ func TestTranslateFuncLinearModuleEdgeCoverage(t *testing.T) { fn Func sig FuncSig }{ - { - name: "UnresolvedImm", - fn: Func{ - Sym: "badimm", - Instrs: []Instr{ - {Op: OpTEXT}, - {Op: OpMOVD, Args: []Operand{{Kind: OpImm, ImmRaw: "sym+4(SB)"}, {Kind: OpReg, Reg: AX}}, Raw: "MOVD $sym+4(SB), AX"}, - {Op: OpRET}, - }, + { + name: "UnresolvedImm", + fn: Func{ + Sym: "badimm", + Instrs: []Instr{ + {Op: OpTEXT}, + {Op: OpMOVD, Args: []Operand{{Kind: OpImm, ImmRaw: "sym+4(SB)"}, {Kind: OpReg, Reg: AX}}, Raw: "MOVD $sym+4(SB), AX"}, + {Op: OpRET}, }, - sig: FuncSig{Name: "example.badimm", Ret: I64}, }, - { - name: "BadOperandKind", - fn: Func{ - Sym: "badopkind", - Instrs: []Instr{ - {Op: OpTEXT}, - {Op: OpMOVD, Args: []Operand{{Kind: OpLabel, Sym: "target"}, {Kind: OpReg, Reg: AX}}, Raw: "MOVD target, AX"}, - {Op: OpRET}, - }, + sig: FuncSig{Name: "example.badimm", Ret: I64}, + }, + { + name: "BadOperandKind", + fn: Func{ + Sym: "badopkind", + Instrs: []Instr{ + {Op: OpTEXT}, + {Op: OpMOVD, Args: []Operand{{Kind: OpLabel, Sym: "target"}, {Kind: OpReg, Reg: AX}}, Raw: "MOVD target, AX"}, + {Op: OpRET}, }, - sig: FuncSig{Name: "example.badopkind", Ret: I64}, - }, - { - name: "BadRetType", - fn: Func{Sym: "badret", Instrs: []Instr{{Op: OpTEXT}, {Op: OpRET}}}, - sig: FuncSig{Name: "example.badret", Ret: LLVMType("v2i64")}, }, + sig: FuncSig{Name: "example.badopkind", Ret: I64}, + }, + { + name: "BadRetType", + fn: Func{Sym: "badret", Instrs: []Instr{{Op: OpTEXT}, {Op: OpRET}}}, + sig: FuncSig{Name: "example.badret", Ret: LLVMType("v2i64")}, + }, { name: "BadArgType", fn: Func{Sym: "badarg", Instrs: []Instr{{Op: OpTEXT}, {Op: OpRET}}}, @@ -1337,7 +1339,7 @@ func TestTranslateFuncLinearModuleEdgeCoverage(t *testing.T) { { name: "BadMRSArgs", fn: Func{ - Sym: "badmrs", + Sym: "badmrs", Instrs: []Instr{{Op: OpTEXT}, {Op: OpMRS, Args: []Operand{{Kind: OpImm, Imm: 1}}, Raw: "MRS $1"}, {Op: OpRET}}, }, sig: FuncSig{Name: "example.badmrs", Ret: I64}, @@ -1345,7 +1347,7 @@ func TestTranslateFuncLinearModuleEdgeCoverage(t *testing.T) { { name: "BadMRSKinds", fn: Func{ - Sym: "badmrskind", + Sym: "badmrskind", Instrs: []Instr{{Op: OpTEXT}, {Op: OpMRS, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}, Raw: "MRS $1, AX"}, {Op: OpRET}}, }, sig: FuncSig{Name: "example.badmrskind", Ret: I64}, @@ -1353,7 +1355,7 @@ func TestTranslateFuncLinearModuleEdgeCoverage(t *testing.T) { { name: "BadMOVDArgs", fn: Func{ - Sym: "badmovd", + Sym: "badmovd", Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 1}}, Raw: "MOVD $1"}, {Op: OpRET}}, }, sig: FuncSig{Name: "example.badmovd", Ret: I64}, @@ -1361,7 +1363,7 @@ func TestTranslateFuncLinearModuleEdgeCoverage(t *testing.T) { { name: "BadMOVDDst", fn: Func{ - Sym: "badmovddst", + Sym: "badmovddst", Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpIdent, Ident: "label"}}, Raw: "MOVD $1, label"}, {Op: OpRET}}, }, sig: FuncSig{Name: "example.badmovddst", Ret: I64}, @@ -1369,7 +1371,7 @@ func TestTranslateFuncLinearModuleEdgeCoverage(t *testing.T) { { name: "BadMOVLArgs", fn: Func{ - Sym: "badmovl", + Sym: "badmovl", Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVL, Args: []Operand{{Kind: OpImm, Imm: 1}}, Raw: "MOVL $1"}, {Op: OpRET}}, }, sig: FuncSig{Name: "example.badmovl", Ret: I64}, @@ -1377,7 +1379,7 @@ func TestTranslateFuncLinearModuleEdgeCoverage(t *testing.T) { { name: "BadMOVLDst", fn: Func{ - Sym: "badmovldst", + Sym: "badmovldst", Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVL, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpIdent, Ident: "label"}}, Raw: "MOVL $1, label"}, {Op: OpRET}}, }, sig: FuncSig{Name: "example.badmovldst", Ret: I64}, @@ -1385,7 +1387,7 @@ func TestTranslateFuncLinearModuleEdgeCoverage(t *testing.T) { { name: "BadADDArgs", fn: Func{ - Sym: "badaddargs", + Sym: "badaddargs", Instrs: []Instr{{Op: OpTEXT}, {Op: OpADDQ, Args: []Operand{{Kind: OpImm, Imm: 1}}, Raw: "ADDQ $1"}, {Op: OpRET}}, }, sig: FuncSig{Name: "example.badaddargs", Ret: I64}, @@ -1393,39 +1395,39 @@ func TestTranslateFuncLinearModuleEdgeCoverage(t *testing.T) { { name: "BadADDDst", fn: Func{ - Sym: "badadddst", + Sym: "badadddst", Instrs: []Instr{{Op: OpTEXT}, {Op: OpADDQ, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpFP, FPOffset: 8}}, Raw: "ADDQ $1, ret+8(FP)"}, {Op: OpRET}}, }, sig: FuncSig{Name: "example.badadddst", Ret: I64}, }, - { - name: "UnsupportedInstruction", - fn: Func{ - Sym: "badop", - Instrs: []Instr{{Op: OpTEXT}, {Op: "NOP", Raw: "NOP"}, {Op: OpRET}}, - }, - sig: FuncSig{Name: "example.badop", Ret: I64}, + { + name: "UnsupportedInstruction", + fn: Func{ + Sym: "badop", + Instrs: []Instr{{Op: OpTEXT}, {Op: "NOP", Raw: "NOP"}, {Op: OpRET}}, }, - { - name: "InstructionAfterRET", - fn: Func{ - Sym: "afterret", - Instrs: []Instr{{Op: OpTEXT}, {Op: OpRET}, {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}, Raw: "MOVD $1, AX"}}, - }, - sig: FuncSig{Name: "example.afterret", Ret: I64}, + sig: FuncSig{Name: "example.badop", Ret: I64}, + }, + { + name: "InstructionAfterRET", + fn: Func{ + Sym: "afterret", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpRET}, {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}, Raw: "MOVD $1, AX"}}, }, - { - name: "NoRET", - fn: Func{ - Sym: "noret", - Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}, Raw: "MOVD $1, AX"}}, - }, - sig: FuncSig{Name: "example.noret", Ret: I64}, + sig: FuncSig{Name: "example.afterret", Ret: I64}, + }, + { + name: "NoRET", + fn: Func{ + Sym: "noret", + Instrs: []Instr{{Op: OpTEXT}, {Op: OpMOVD, Args: []Operand{{Kind: OpImm, Imm: 1}, {Kind: OpReg, Reg: AX}}, Raw: "MOVD $1, AX"}}, }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - mod := ctx.NewModule("direct-" + tc.name) + sig: FuncSig{Name: "example.noret", Ret: I64}, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + mod := ctx.NewModule("direct-" + tc.name) defer mod.Dispose() if err := translateFuncLinearModule(mod, ArchAMD64, tc.fn, tc.sig); !errors.Is(err, errDirectModuleUnsupported) { t.Fatalf("translateFuncLinearModule(%s) error = %v", tc.name, err)