github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/engine/wazevo/backend/isa/arm64/abi_entry_preamble.go (about) 1 package arm64 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 // CompileEntryPreamble implements backend.Machine. This assumes `entrypoint` function (in abi_go_entry_arm64.s) passes: 11 // 12 // 1. First (execution context ptr) and Second arguments are already passed in x0, and x1. 13 // 2. param/result slice ptr in x19; the pointer to []uint64{} which is used to pass arguments and accept return values. 14 // 3. Go-allocated stack slice ptr in x26. 15 // 4. Function executable in x24. 16 // 17 // also SP and FP are correct Go-runtime-based values, and LR is the return address to the Go-side caller. 18 func (m *machine) CompileEntryPreamble(signature *ssa.Signature) []byte { 19 root := m.constructEntryPreamble(signature) 20 m.encode(root) 21 return m.compiler.Buf() 22 } 23 24 var ( 25 executionContextPtrReg = x0VReg 26 // callee-saved regs so that they can be used in the prologue and epilogue. 27 paramResultSlicePtr = x19VReg 28 savedExecutionContextPtr = x20VReg 29 // goAllocatedStackPtr is not used in the epilogue. 30 goAllocatedStackPtr = x26VReg 31 // paramResultSliceCopied is not used in the epilogue. 32 paramResultSliceCopied = x25VReg 33 // tmpRegVReg is not used in the epilogue. 34 functionExecutable = x24VReg 35 ) 36 37 func (m *machine) goEntryPreamblePassArg(cur *instruction, paramSlicePtr regalloc.VReg, arg *backend.ABIArg, argStartOffsetFromSP int64) *instruction { 38 typ := arg.Type 39 bits := typ.Bits() 40 isStackArg := arg.Kind == backend.ABIArgKindStack 41 42 var loadTargetReg operand 43 if !isStackArg { 44 loadTargetReg = operandNR(arg.Reg) 45 } else { 46 switch typ { 47 case ssa.TypeI32, ssa.TypeI64: 48 loadTargetReg = operandNR(x15VReg) 49 case ssa.TypeF32, ssa.TypeF64, ssa.TypeV128: 50 loadTargetReg = operandNR(v15VReg) 51 default: 52 panic("TODO?") 53 } 54 } 55 56 var postIndexImm int64 57 if typ == ssa.TypeV128 { 58 postIndexImm = 16 // v128 is represented as 2x64-bit in Go slice. 59 } else { 60 postIndexImm = 8 61 } 62 loadMode := addressMode{kind: addressModeKindPostIndex, rn: paramSlicePtr, imm: postIndexImm} 63 64 instr := m.allocateInstr() 65 switch typ { 66 case ssa.TypeI32: 67 instr.asULoad(loadTargetReg, loadMode, 32) 68 case ssa.TypeI64: 69 instr.asULoad(loadTargetReg, loadMode, 64) 70 case ssa.TypeF32: 71 instr.asFpuLoad(loadTargetReg, loadMode, 32) 72 case ssa.TypeF64: 73 instr.asFpuLoad(loadTargetReg, loadMode, 64) 74 case ssa.TypeV128: 75 instr.asFpuLoad(loadTargetReg, loadMode, 128) 76 } 77 cur = linkInstr(cur, instr) 78 79 if isStackArg { 80 var storeMode addressMode 81 cur, storeMode = m.resolveAddressModeForOffsetAndInsert(cur, argStartOffsetFromSP+arg.Offset, bits, spVReg, true) 82 toStack := m.allocateInstr() 83 toStack.asStore(loadTargetReg, storeMode, bits) 84 cur = linkInstr(cur, toStack) 85 } 86 return cur 87 } 88 89 func (m *machine) goEntryPreamblePassResult(cur *instruction, resultSlicePtr regalloc.VReg, result *backend.ABIArg, resultStartOffsetFromSP int64) *instruction { 90 isStackArg := result.Kind == backend.ABIArgKindStack 91 typ := result.Type 92 bits := typ.Bits() 93 94 var storeTargetReg operand 95 if !isStackArg { 96 storeTargetReg = operandNR(result.Reg) 97 } else { 98 switch typ { 99 case ssa.TypeI32, ssa.TypeI64: 100 storeTargetReg = operandNR(x15VReg) 101 case ssa.TypeF32, ssa.TypeF64, ssa.TypeV128: 102 storeTargetReg = operandNR(v15VReg) 103 default: 104 panic("TODO?") 105 } 106 } 107 108 var postIndexImm int64 109 if typ == ssa.TypeV128 { 110 postIndexImm = 16 // v128 is represented as 2x64-bit in Go slice. 111 } else { 112 postIndexImm = 8 113 } 114 115 if isStackArg { 116 var loadMode addressMode 117 cur, loadMode = m.resolveAddressModeForOffsetAndInsert(cur, resultStartOffsetFromSP+result.Offset, bits, spVReg, true) 118 toReg := m.allocateInstr() 119 switch typ { 120 case ssa.TypeI32, ssa.TypeI64: 121 toReg.asULoad(storeTargetReg, loadMode, bits) 122 case ssa.TypeF32, ssa.TypeF64, ssa.TypeV128: 123 toReg.asFpuLoad(storeTargetReg, loadMode, bits) 124 default: 125 panic("TODO?") 126 } 127 cur = linkInstr(cur, toReg) 128 } 129 130 mode := addressMode{kind: addressModeKindPostIndex, rn: resultSlicePtr, imm: postIndexImm} 131 instr := m.allocateInstr() 132 instr.asStore(storeTargetReg, mode, bits) 133 cur = linkInstr(cur, instr) 134 return cur 135 } 136 137 func (m *machine) constructEntryPreamble(sig *ssa.Signature) (root *instruction) { 138 abi := backend.FunctionABI{} 139 abi.Init(sig, intParamResultRegs, floatParamResultRegs) 140 141 root = m.allocateNop() 142 143 //// ----------------------------------- prologue ----------------------------------- //// 144 145 // First, we save executionContextPtrReg into a callee-saved register so that it can be used in epilogue as well. 146 // mov savedExecutionContextPtr, x0 147 cur := m.move64(savedExecutionContextPtr, executionContextPtrReg, root) 148 149 // Next, save the current FP, SP and LR into the wazevo.executionContext: 150 // str fp, [savedExecutionContextPtr, #OriginalFramePointer] 151 // mov tmp, sp ;; sp cannot be str'ed directly. 152 // str sp, [savedExecutionContextPtr, #OriginalStackPointer] 153 // str lr, [savedExecutionContextPtr, #GoReturnAddress] 154 cur = m.loadOrStoreAtExecutionContext(fpVReg, wazevoapi.ExecutionContextOffsetOriginalFramePointer, true, cur) 155 cur = m.move64(tmpRegVReg, spVReg, cur) 156 cur = m.loadOrStoreAtExecutionContext(tmpRegVReg, wazevoapi.ExecutionContextOffsetOriginalStackPointer, true, cur) 157 cur = m.loadOrStoreAtExecutionContext(lrVReg, wazevoapi.ExecutionContextOffsetGoReturnAddress, true, cur) 158 159 // Then, move the Go-allocated stack pointer to SP: 160 // mov sp, goAllocatedStackPtr 161 cur = m.move64(spVReg, goAllocatedStackPtr, cur) 162 163 prReg := paramResultSlicePtr 164 if len(abi.Args) > 2 && len(abi.Rets) > 0 { 165 // paramResultSlicePtr is modified during the execution of goEntryPreamblePassArg, 166 // so copy it to another reg. 167 cur = m.move64(paramResultSliceCopied, paramResultSlicePtr, cur) 168 prReg = paramResultSliceCopied 169 } 170 171 stackSlotSize := int64(abi.AlignedArgResultStackSlotSize()) 172 for i := range abi.Args { 173 if i < 2 { 174 // module context ptr and execution context ptr are passed in x0 and x1 by the Go assembly function. 175 continue 176 } 177 arg := &abi.Args[i] 178 cur = m.goEntryPreamblePassArg(cur, prReg, arg, -stackSlotSize) 179 } 180 181 // Call the real function. 182 bl := m.allocateInstr() 183 bl.asCallIndirect(functionExecutable, &abi) 184 cur = linkInstr(cur, bl) 185 186 ///// ----------------------------------- epilogue ----------------------------------- ///// 187 188 // Store the register results into paramResultSlicePtr. 189 for i := range abi.Rets { 190 cur = m.goEntryPreamblePassResult(cur, paramResultSlicePtr, &abi.Rets[i], abi.ArgStackSize-stackSlotSize) 191 } 192 193 // Finally, restore the FP, SP and LR, and return to the Go code. 194 // ldr fp, [savedExecutionContextPtr, #OriginalFramePointer] 195 // ldr tmp, [savedExecutionContextPtr, #OriginalStackPointer] 196 // mov sp, tmp ;; sp cannot be str'ed directly. 197 // ldr lr, [savedExecutionContextPtr, #GoReturnAddress] 198 // ret ;; --> return to the Go code 199 cur = m.loadOrStoreAtExecutionContext(fpVReg, wazevoapi.ExecutionContextOffsetOriginalFramePointer, false, cur) 200 cur = m.loadOrStoreAtExecutionContext(tmpRegVReg, wazevoapi.ExecutionContextOffsetOriginalStackPointer, false, cur) 201 cur = m.move64(spVReg, tmpRegVReg, cur) 202 cur = m.loadOrStoreAtExecutionContext(lrVReg, wazevoapi.ExecutionContextOffsetGoReturnAddress, false, cur) 203 retInst := m.allocateInstr() 204 retInst.asRet() 205 linkInstr(cur, retInst) 206 return 207 } 208 209 func (m *machine) move64(dst, src regalloc.VReg, prev *instruction) *instruction { 210 instr := m.allocateInstr() 211 instr.asMove64(dst, src) 212 return linkInstr(prev, instr) 213 } 214 215 func (m *machine) loadOrStoreAtExecutionContext(d regalloc.VReg, offset wazevoapi.Offset, store bool, prev *instruction) *instruction { 216 instr := m.allocateInstr() 217 mode := addressMode{kind: addressModeKindRegUnsignedImm12, rn: savedExecutionContextPtr, imm: offset.I64()} 218 if store { 219 instr.asStore(operandNR(d), mode, 64) 220 } else { 221 instr.asULoad(operandNR(d), mode, 64) 222 } 223 return linkInstr(prev, instr) 224 } 225 226 func linkInstr(prev, next *instruction) *instruction { 227 prev.next = next 228 next.prev = prev 229 return next 230 }