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