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  }