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 }