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  }