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 }