Repository: tetratelabs/wazero
Pull request #1909 (2024-01-10 19:28:24)

wazevo(amd64): call (direct, indirect) and ud2 encodings


Patch:
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr.go b/internal/engine/wazevo/backend/isa/amd64/instr.go
index 6225c401..1fd00bcb 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
 )
 
 type instruction struct {
@@ -12,7 +13,7 @@ type instruction struct {
 	prev, next          *instruction
 	abi                 *backend.FunctionABI
 	op1, op2            operand
-	u1                  uint64
+	u1, u2              uint64
 	b1                  bool
 	addedBeforeRegAlloc bool
 }
@@ -137,7 +138,15 @@ func (i *instruction) String() string {
 	case trapIf:
 		panic("TODO")
 	case ud2:
-		panic("TODO")
+		return "ud2"
+	case call:
+		if i.u2 > 0 {
+			return fmt.Sprintf("call $%d", int32(i.u2))
+		} else {
+			return fmt.Sprintf("call %s", ssa.FuncRef(i.u1))
+		}
+	case callIndirect:
+		return fmt.Sprintf("callq *%s", i.op1.format(true))
 	default:
 		panic(fmt.Sprintf("BUG: %v", i.kind))
 	}
@@ -320,6 +329,7 @@ const (
 	xmmRmRImm
 
 	// Direct call: call simm32.
+	// Note that the offset is the relative to the *current RIP*, which points to the first byte of the next instruction.
 	call
 
 	// Indirect call: callq (reg mem).
@@ -493,6 +503,23 @@ func (i *instruction) asLEA(a amode, rd regalloc.VReg) *instruction {
 	return i
 }
 
+func (i *instruction) asCall(ref ssa.FuncRef, abi *backend.FunctionABI) *instruction {
+	i.kind = call
+	i.abi = abi
+	i.u1 = uint64(ref)
+	return i
+}
+
+func (i *instruction) asCallIndirect(ptr operand, abi *backend.FunctionABI) *instruction {
+	if ptr.kind != operandKindReg && ptr.kind != operandKindMem {
+		panic("BUG")
+	}
+	i.kind = callIndirect
+	i.abi = abi
+	i.op1 = ptr
+	return i
+}
+
 func (i *instruction) asRet(abi *backend.FunctionABI) *instruction {
 	i.kind = ret
 	i.abi = abi
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
index 2054bad3..4838e626 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
 )
 
 // Encode implements backend.Machine Encode.
@@ -552,8 +553,48 @@ func (i *instruction) encode(c backend.Compiler) {
 		panic("TODO")
 	case trapIf:
 		panic("TODO")
+
 	case ud2:
-		panic("TODO")
+		c.EmitByte(0x0f)
+		c.EmitByte(0x0b)
+
+	case call:
+		c.EmitByte(0xe8)
+		if i.u2 == 0 { // Meaning that the call target is a function value, and requires relocation.
+			c.AddRelocationInfo(ssa.FuncRef(i.u1))
+		}
+		// Note that this is zero as a placeholder for the call target if it's a function value.
+		c.Emit4Bytes(uint32(i.u2))
+
+	case callIndirect:
+		op := i.op1
+
+		const opcodeNum = 1
+		const opcode = 0xff
+		rex := rexInfo(0).clearW()
+		switch op.kind {
+		case operandKindReg:
+			dst := regEncodings[op.r.RealReg()]
+			encodeRegReg(c,
+				legacyPrefixesNone,
+				opcode, opcodeNum,
+				regEnc(2),
+				dst,
+				rex,
+			)
+		case operandKindMem:
+			m := op.amode
+			encodeRegMem(c,
+				legacyPrefixesNone,
+				opcode, opcodeNum,
+				regEnc(2),
+				m,
+				rex,
+			)
+		default:
+			panic("BUG: invalid operand kind")
+		}
+
 	default:
 		panic(fmt.Sprintf("TODO: %v", i.kind))
 	}


Test patch:
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go b/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
index fd07d0d4..7ad63184 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
@@ -1370,6 +1370,40 @@ func TestInstruction_format_encode(t *testing.T) {
 			want:       "4c8d1d08000000",
 			wantFormat: "lea 8(%rip), %r11",
 		},
+		{
+			setup:      func(i *instruction) { i.kind = ud2 },
+			want:       "0f0b",
+			wantFormat: "ud2",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asCall(0, nil)
+				i.u2 = 0xff
+			},
+			want:       "e8ff000000",
+			wantFormat: "call $255",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asCallIndirect(newOperandReg(r12VReg), nil)
+			},
+			want:       "41ffd4",
+			wantFormat: "callq *%r12",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asCallIndirect(newOperandMem(newAmodeImmReg(0, raxVReg)), nil)
+			},
+			want:       "ff10",
+			wantFormat: "callq *(%rax)",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asCallIndirect(newOperandMem(newAmodeImmReg(0xffff_0000, raxVReg)), nil)
+			},
+			want:       "ff900000ffff",
+			wantFormat: "callq *-65536(%rax)",
+		},
 	} {
 		tc := tc
 		t.Run(tc.wantFormat, func(t *testing.T) {