github.com/tetratelabs/wazero@v1.7.1/internal/engine/wazevo/backend/isa/arm64/machine_relocation.go (about)

     1  package arm64
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"math"
     7  	"sort"
     8  
     9  	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
    10  	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
    11  )
    12  
    13  const (
    14  	// trampolineCallSize is the size of the trampoline instruction sequence for each function in an island.
    15  	trampolineCallSize = 4*4 + 4 // Four instructions + 32-bit immediate.
    16  
    17  	// Unconditional branch offset is encoded as divided by 4 in imm26.
    18  	// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/BL--Branch-with-Link-?lang=en
    19  
    20  	maxUnconditionalBranchOffset = maxSignedInt26 * 4
    21  	minUnconditionalBranchOffset = minSignedInt26 * 4
    22  
    23  	// trampolineIslandInterval is the range of the trampoline island.
    24  	// Half of the range is used for the trampoline island, and the other half is used for the function.
    25  	trampolineIslandInterval = maxUnconditionalBranchOffset / 2
    26  
    27  	// maxNumFunctions explicitly specifies the maximum number of functions that can be allowed in a single executable.
    28  	maxNumFunctions = trampolineIslandInterval >> 6
    29  
    30  	// maxFunctionExecutableSize is the maximum size of a function that can exist in a trampoline island.
    31  	// Conservatively set to 1/4 of the trampoline island interval.
    32  	maxFunctionExecutableSize = trampolineIslandInterval >> 2
    33  )
    34  
    35  // CallTrampolineIslandInfo implements backend.Machine CallTrampolineIslandInfo.
    36  func (m *machine) CallTrampolineIslandInfo(numFunctions int) (interval, size int, err error) {
    37  	if numFunctions > maxNumFunctions {
    38  		return 0, 0, fmt.Errorf("too many functions: %d > %d", numFunctions, maxNumFunctions)
    39  	}
    40  	return trampolineIslandInterval, trampolineCallSize * numFunctions, nil
    41  }
    42  
    43  // ResolveRelocations implements backend.Machine ResolveRelocations.
    44  func (m *machine) ResolveRelocations(
    45  	refToBinaryOffset map[ssa.FuncRef]int,
    46  	executable []byte,
    47  	relocations []backend.RelocationInfo,
    48  	callTrampolineIslandOffsets []int,
    49  ) {
    50  	for _, islandOffset := range callTrampolineIslandOffsets {
    51  		encodeCallTrampolineIsland(refToBinaryOffset, islandOffset, executable)
    52  	}
    53  
    54  	for _, r := range relocations {
    55  		instrOffset := r.Offset
    56  		calleeFnOffset := refToBinaryOffset[r.FuncRef]
    57  		diff := int64(calleeFnOffset) - (instrOffset)
    58  		// Check if the diff is within the range of the branch instruction.
    59  		if diff < minUnconditionalBranchOffset || diff > maxUnconditionalBranchOffset {
    60  			// Find the near trampoline island from callTrampolineIslandOffsets.
    61  			islandOffset := searchTrampolineIsland(callTrampolineIslandOffsets, int(instrOffset))
    62  			islandTargetOffset := islandOffset + trampolineCallSize*int(r.FuncRef)
    63  			diff = int64(islandTargetOffset) - (instrOffset)
    64  			if diff < minUnconditionalBranchOffset || diff > maxUnconditionalBranchOffset {
    65  				panic("BUG in trampoline placement")
    66  			}
    67  		}
    68  		binary.LittleEndian.PutUint32(executable[instrOffset:instrOffset+4], encodeUnconditionalBranch(true, diff))
    69  	}
    70  }
    71  
    72  // encodeCallTrampolineIsland encodes a trampoline island for the given functions.
    73  // Each island consists of a trampoline instruction sequence for each function.
    74  // Each trampoline instruction sequence consists of 4 instructions + 32-bit immediate.
    75  func encodeCallTrampolineIsland(refToBinaryOffset map[ssa.FuncRef]int, islandOffset int, executable []byte) {
    76  	for i := 0; i < len(refToBinaryOffset); i++ {
    77  		trampolineOffset := islandOffset + trampolineCallSize*i
    78  
    79  		fnOffset := refToBinaryOffset[ssa.FuncRef(i)]
    80  		diff := fnOffset - (trampolineOffset + 16)
    81  		if diff > math.MaxInt32 || diff < math.MinInt32 {
    82  			// This case even amd64 can't handle. 4GB is too big.
    83  			panic("too big binary")
    84  		}
    85  
    86  		// The tmpReg, tmpReg2 is safe to overwrite (in fact any caller-saved register is safe to use).
    87  		tmpReg, tmpReg2 := regNumberInEncoding[tmpRegVReg.RealReg()], regNumberInEncoding[x11]
    88  
    89  		// adr tmpReg, PC+16: load the address of #diff into tmpReg.
    90  		binary.LittleEndian.PutUint32(executable[trampolineOffset:], encodeAdr(tmpReg, 16))
    91  		// ldrsw tmpReg2, [tmpReg]: Load #diff into tmpReg2.
    92  		binary.LittleEndian.PutUint32(executable[trampolineOffset+4:],
    93  			encodeLoadOrStore(sLoad32, tmpReg2, addressMode{kind: addressModeKindRegUnsignedImm12, rn: tmpRegVReg}))
    94  		// add tmpReg, tmpReg2, tmpReg: add #diff to the address of #diff, getting the absolute address of the function.
    95  		binary.LittleEndian.PutUint32(executable[trampolineOffset+8:],
    96  			encodeAluRRR(aluOpAdd, tmpReg, tmpReg, tmpReg2, true, false))
    97  		// br tmpReg: branch to the function without overwriting the link register.
    98  		binary.LittleEndian.PutUint32(executable[trampolineOffset+12:], encodeUnconditionalBranchReg(tmpReg, false))
    99  		// #diff
   100  		binary.LittleEndian.PutUint32(executable[trampolineOffset+16:], uint32(diff))
   101  	}
   102  }
   103  
   104  // searchTrampolineIsland finds the nearest trampoline island from callTrampolineIslandOffsets.
   105  // Note that even if the offset is in the middle of two islands, it returns the latter one.
   106  // That is ok because the island is always placed in the middle of the range.
   107  //
   108  // precondition: callTrampolineIslandOffsets is sorted in ascending order.
   109  func searchTrampolineIsland(callTrampolineIslandOffsets []int, offset int) int {
   110  	l := len(callTrampolineIslandOffsets)
   111  	n := sort.Search(l, func(i int) bool {
   112  		return callTrampolineIslandOffsets[i] >= offset
   113  	})
   114  	if n == l {
   115  		n = l - 1
   116  	}
   117  	return callTrampolineIslandOffsets[n]
   118  }