github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go (about) 1 package amd64 2 3 import ( 4 "github.com/tetratelabs/wazero/internal/engine/wazevo/backend" 5 "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc" 6 "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa" 7 "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" 8 ) 9 10 var ( 11 executionContextPtrReg = raxVReg 12 13 // Followings are callee saved registers. They can be used freely in the entry preamble 14 // since the preamble is called via Go assembly function which has stack-based ABI. 15 16 // savedExecutionContextPtr also must be a callee-saved reg so that they can be used in the prologue and epilogue. 17 savedExecutionContextPtr = rdxVReg 18 // paramResultSlicePtr must match with entrypoint function in abi_entry_amd64.s. 19 paramResultSlicePtr = r12VReg 20 // goAllocatedStackPtr must match with entrypoint function in abi_entry_amd64.s. 21 goAllocatedStackPtr = r13VReg 22 // functionExecutable must match with entrypoint function in abi_entry_amd64.s. 23 functionExecutable = r14VReg 24 tmpIntReg = r15VReg 25 tmpXmmReg = xmm15VReg 26 ) 27 28 // CompileEntryPreamble implements backend.Machine. 29 func (m *machine) CompileEntryPreamble(sig *ssa.Signature) []byte { 30 root := m.compileEntryPreamble(sig) 31 m.encodeWithoutSSA(root) 32 buf := m.c.Buf() 33 return buf 34 } 35 36 func (m *machine) compileEntryPreamble(sig *ssa.Signature) *instruction { 37 abi := backend.FunctionABI{} 38 abi.Init(sig, intArgResultRegs, floatArgResultRegs) 39 40 root := m.allocateNop() 41 42 //// ----------------------------------- prologue ----------------------------------- //// 43 44 // First, we save executionContextPtrReg into a callee-saved register so that it can be used in epilogue as well. 45 // mov %executionContextPtrReg, %savedExecutionContextPtr 46 cur := m.move64(executionContextPtrReg, savedExecutionContextPtr, root) 47 48 // Next is to save the original RBP and RSP into the execution context. 49 cur = m.saveOriginalRSPRBP(cur) 50 51 // Now set the RSP to the Go-allocated stack pointer. 52 // mov %goAllocatedStackPtr, %rsp 53 cur = m.move64(goAllocatedStackPtr, rspVReg, cur) 54 55 if stackSlotSize := abi.AlignedArgResultStackSlotSize(); stackSlotSize > 0 { 56 // Allocate stack slots for the arguments and return values. 57 // sub $stackSlotSize, %rsp 58 spDec := m.allocateInstr().asAluRmiR(aluRmiROpcodeSub, newOperandImm32(uint32(stackSlotSize)), rspVReg, true) 59 cur = linkInstr(cur, spDec) 60 } 61 62 var offset uint32 63 for i := range abi.Args { 64 if i < 2 { 65 // module context ptr and execution context ptr are passed in rax and rbx by the Go assembly function. 66 continue 67 } 68 arg := &abi.Args[i] 69 cur = m.goEntryPreamblePassArg(cur, paramResultSlicePtr, offset, arg) 70 if arg.Type == ssa.TypeV128 { 71 offset += 16 72 } else { 73 offset += 8 74 } 75 } 76 77 // Zero out RBP so that the unwind/stack growth code can correctly detect the end of the stack. 78 zerosRbp := m.allocateInstr().asAluRmiR(aluRmiROpcodeXor, newOperandReg(rbpVReg), rbpVReg, true) 79 cur = linkInstr(cur, zerosRbp) 80 81 // Now ready to call the real function. Note that at this point stack pointer is already set to the Go-allocated, 82 // which is aligned to 16 bytes. 83 call := m.allocateInstr().asCallIndirect(newOperandReg(functionExecutable), &abi) 84 cur = linkInstr(cur, call) 85 86 //// ----------------------------------- epilogue ----------------------------------- //// 87 88 // Read the results from regs and the stack, and set them correctly into the paramResultSlicePtr. 89 offset = 0 90 for i := range abi.Rets { 91 r := &abi.Rets[i] 92 cur = m.goEntryPreamblePassResult(cur, paramResultSlicePtr, offset, r, uint32(abi.ArgStackSize)) 93 if r.Type == ssa.TypeV128 { 94 offset += 16 95 } else { 96 offset += 8 97 } 98 } 99 100 // Finally, restore the original RBP and RSP. 101 cur = m.restoreOriginalRSPRBP(cur) 102 103 ret := m.allocateInstr().asRet() 104 linkInstr(cur, ret) 105 return root 106 } 107 108 // saveOriginalRSPRBP saves the original RSP and RBP into the execution context. 109 func (m *machine) saveOriginalRSPRBP(cur *instruction) *instruction { 110 // mov %rbp, wazevoapi.ExecutionContextOffsetOriginalFramePointer(%executionContextPtrReg) 111 // mov %rsp, wazevoapi.ExecutionContextOffsetOriginalStackPointer(%executionContextPtrReg) 112 cur = m.loadOrStore64AtExecutionCtx(executionContextPtrReg, wazevoapi.ExecutionContextOffsetOriginalFramePointer, rbpVReg, true, cur) 113 cur = m.loadOrStore64AtExecutionCtx(executionContextPtrReg, wazevoapi.ExecutionContextOffsetOriginalStackPointer, rspVReg, true, cur) 114 return cur 115 } 116 117 // restoreOriginalRSPRBP restores the original RSP and RBP from the execution context. 118 func (m *machine) restoreOriginalRSPRBP(cur *instruction) *instruction { 119 // mov wazevoapi.ExecutionContextOffsetOriginalFramePointer(%executionContextPtrReg), %rbp 120 // mov wazevoapi.ExecutionContextOffsetOriginalStackPointer(%executionContextPtrReg), %rsp 121 cur = m.loadOrStore64AtExecutionCtx(savedExecutionContextPtr, wazevoapi.ExecutionContextOffsetOriginalFramePointer, rbpVReg, false, cur) 122 cur = m.loadOrStore64AtExecutionCtx(savedExecutionContextPtr, wazevoapi.ExecutionContextOffsetOriginalStackPointer, rspVReg, false, cur) 123 return cur 124 } 125 126 func (m *machine) move64(src, dst regalloc.VReg, prev *instruction) *instruction { 127 mov := m.allocateInstr().asMovRR(src, dst, true) 128 return linkInstr(prev, mov) 129 } 130 131 func (m *machine) loadOrStore64AtExecutionCtx(execCtx regalloc.VReg, offset wazevoapi.Offset, r regalloc.VReg, store bool, prev *instruction) *instruction { 132 mem := newOperandMem(m.newAmodeImmReg(offset.U32(), execCtx)) 133 instr := m.allocateInstr() 134 if store { 135 instr.asMovRM(r, mem, 8) 136 } else { 137 instr.asMov64MR(mem, r) 138 } 139 return linkInstr(prev, instr) 140 } 141 142 // This is for debugging. 143 func (m *machine) linkUD2(cur *instruction) *instruction { //nolint 144 return linkInstr(cur, m.allocateInstr().asUD2()) 145 } 146 147 func (m *machine) goEntryPreamblePassArg(cur *instruction, paramSlicePtr regalloc.VReg, offsetInParamSlice uint32, arg *backend.ABIArg) *instruction { 148 var dst regalloc.VReg 149 argTyp := arg.Type 150 if arg.Kind == backend.ABIArgKindStack { 151 // Caller saved registers ca 152 switch argTyp { 153 case ssa.TypeI32, ssa.TypeI64: 154 dst = tmpIntReg 155 case ssa.TypeF32, ssa.TypeF64, ssa.TypeV128: 156 dst = tmpXmmReg 157 default: 158 panic("BUG") 159 } 160 } else { 161 dst = arg.Reg 162 } 163 164 load := m.allocateInstr() 165 a := newOperandMem(m.newAmodeImmReg(offsetInParamSlice, paramSlicePtr)) 166 switch arg.Type { 167 case ssa.TypeI32: 168 load.asMovzxRmR(extModeLQ, a, dst) 169 case ssa.TypeI64: 170 load.asMov64MR(a, dst) 171 case ssa.TypeF32: 172 load.asXmmUnaryRmR(sseOpcodeMovss, a, dst) 173 case ssa.TypeF64: 174 load.asXmmUnaryRmR(sseOpcodeMovsd, a, dst) 175 case ssa.TypeV128: 176 load.asXmmUnaryRmR(sseOpcodeMovdqu, a, dst) 177 } 178 179 cur = linkInstr(cur, load) 180 if arg.Kind == backend.ABIArgKindStack { 181 // Store back to the stack. 182 store := m.allocateInstr() 183 a := newOperandMem(m.newAmodeImmReg(uint32(arg.Offset), rspVReg)) 184 switch arg.Type { 185 case ssa.TypeI32: 186 store.asMovRM(dst, a, 4) 187 case ssa.TypeI64: 188 store.asMovRM(dst, a, 8) 189 case ssa.TypeF32: 190 store.asXmmMovRM(sseOpcodeMovss, dst, a) 191 case ssa.TypeF64: 192 store.asXmmMovRM(sseOpcodeMovsd, dst, a) 193 case ssa.TypeV128: 194 store.asXmmMovRM(sseOpcodeMovdqu, dst, a) 195 } 196 cur = linkInstr(cur, store) 197 } 198 return cur 199 } 200 201 func (m *machine) goEntryPreamblePassResult(cur *instruction, resultSlicePtr regalloc.VReg, offsetInResultSlice uint32, result *backend.ABIArg, resultStackSlotBeginOffset uint32) *instruction { 202 var r regalloc.VReg 203 if result.Kind == backend.ABIArgKindStack { 204 // Load the value to the temporary. 205 load := m.allocateInstr() 206 offset := resultStackSlotBeginOffset + uint32(result.Offset) 207 a := newOperandMem(m.newAmodeImmReg(offset, rspVReg)) 208 switch result.Type { 209 case ssa.TypeI32: 210 r = tmpIntReg 211 load.asMovzxRmR(extModeLQ, a, r) 212 case ssa.TypeI64: 213 r = tmpIntReg 214 load.asMov64MR(a, r) 215 case ssa.TypeF32: 216 r = tmpXmmReg 217 load.asXmmUnaryRmR(sseOpcodeMovss, a, r) 218 case ssa.TypeF64: 219 r = tmpXmmReg 220 load.asXmmUnaryRmR(sseOpcodeMovsd, a, r) 221 case ssa.TypeV128: 222 r = tmpXmmReg 223 load.asXmmUnaryRmR(sseOpcodeMovdqu, a, r) 224 default: 225 panic("BUG") 226 } 227 cur = linkInstr(cur, load) 228 } else { 229 r = result.Reg 230 } 231 232 store := m.allocateInstr() 233 a := newOperandMem(m.newAmodeImmReg(offsetInResultSlice, resultSlicePtr)) 234 switch result.Type { 235 case ssa.TypeI32: 236 store.asMovRM(r, a, 4) 237 case ssa.TypeI64: 238 store.asMovRM(r, a, 8) 239 case ssa.TypeF32: 240 store.asXmmMovRM(sseOpcodeMovss, r, a) 241 case ssa.TypeF64: 242 store.asXmmMovRM(sseOpcodeMovsd, r, a) 243 case ssa.TypeV128: 244 store.asXmmMovRM(sseOpcodeMovdqu, r, a) 245 } 246 247 return linkInstr(cur, store) 248 }