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