github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/asm/amd64/impl_staticconst.go (about) 1 package amd64 2 3 import ( 4 "fmt" 5 "math" 6 7 "github.com/wasilibs/wazerox/internal/asm" 8 ) 9 10 // defaultMaxDisplacementForConstantPool is the maximum displacement allowed for literal move instructions which access 11 // the constant pool. This is set as 2 ^30 conservatively while the actual limit is 2^31 since we actually allow this 12 // limit plus max(length(c) for c in the pool) so we must ensure that limit is less than 2^31. 13 const defaultMaxDisplacementForConstantPool = 1 << 30 14 15 func (a *AssemblerImpl) maybeFlushConstants(buf asm.Buffer, isEndOfFunction bool) { 16 if a.pool.Empty() { 17 return 18 } 19 20 if isEndOfFunction || 21 // If the distance between (the first use in binary) and (end of constant pool) can be larger 22 // than MaxDisplacementForConstantPool, we have to emit the constant pool now, otherwise 23 // a const might be unreachable by a literal move whose maximum offset is +- 2^31. 24 ((a.pool.PoolSizeInBytes+buf.Len())-int(a.pool.FirstUseOffsetInBinary)) >= a.MaxDisplacementForConstantPool { 25 26 if !isEndOfFunction { 27 // Adds the jump instruction to skip the constants if this is not the end of function. 28 // 29 // TODO: consider NOP padding for this jump, though this rarely happens as most functions should be 30 // small enough to fit all consts after the end of function. 31 if a.pool.PoolSizeInBytes >= math.MaxInt8-2 { 32 // long (near-relative) jump: https://www.felixcloutier.com/x86/jmp 33 buf.AppendByte(0xe9) 34 buf.AppendUint32(uint32(a.pool.PoolSizeInBytes)) 35 } else { 36 // short jump: https://www.felixcloutier.com/x86/jmp 37 buf.AppendByte(0xeb) 38 buf.AppendByte(byte(a.pool.PoolSizeInBytes)) 39 } 40 } 41 42 for _, c := range a.pool.Consts { 43 c.SetOffsetInBinary(uint64(buf.Len())) 44 buf.AppendBytes(c.Raw) 45 } 46 47 a.pool.Reset() 48 } 49 } 50 51 func (a *AssemblerImpl) encodeRegisterToStaticConst(buf asm.Buffer, n *nodeImpl) (err error) { 52 var opc []byte 53 var rex byte 54 switch n.instruction { 55 case CMPL: 56 opc, rex = []byte{0x3b}, rexPrefixNone 57 case CMPQ: 58 opc, rex = []byte{0x3b}, rexPrefixW 59 default: 60 return errorEncodingUnsupported(n) 61 } 62 return a.encodeStaticConstImpl(buf, n, opc, rex, 0) 63 } 64 65 var staticConstToRegisterOpcodes = [...]struct { 66 opcode, vopcode []byte 67 mandatoryPrefix, vmandatoryPrefix byte 68 rex rexPrefix 69 }{ 70 // https://www.felixcloutier.com/x86/movdqu:vmovdqu8:vmovdqu16:vmovdqu32:vmovdqu64 71 MOVDQU: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x6f}}, 72 // https://www.felixcloutier.com/x86/lea 73 LEAQ: {opcode: []byte{0x8d}, rex: rexPrefixW}, 74 // https://www.felixcloutier.com/x86/movupd 75 MOVUPD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x10}}, 76 // https://www.felixcloutier.com/x86/mov 77 MOVL: {opcode: []byte{0x8b}, vopcode: []byte{0x0f, 0x6e}, vmandatoryPrefix: 0x66}, 78 MOVQ: {opcode: []byte{0x8b}, rex: rexPrefixW, vopcode: []byte{0x0f, 0x7e}, vmandatoryPrefix: 0xf3}, 79 // https://www.felixcloutier.com/x86/ucomisd 80 UCOMISD: {opcode: []byte{0x0f, 0x2e}, mandatoryPrefix: 0x66}, 81 // https://www.felixcloutier.com/x86/ucomiss 82 UCOMISS: {opcode: []byte{0x0f, 0x2e}}, 83 // https://www.felixcloutier.com/x86/subss 84 SUBSS: {opcode: []byte{0x0f, 0x5c}, mandatoryPrefix: 0xf3}, 85 // https://www.felixcloutier.com/x86/subsd 86 SUBSD: {opcode: []byte{0x0f, 0x5c}, mandatoryPrefix: 0xf2}, 87 // https://www.felixcloutier.com/x86/cmp 88 CMPL: {opcode: []byte{0x39}}, 89 CMPQ: {opcode: []byte{0x39}, rex: rexPrefixW}, 90 // https://www.felixcloutier.com/x86/add 91 ADDL: {opcode: []byte{0x03}}, 92 ADDQ: {opcode: []byte{0x03}, rex: rexPrefixW}, 93 } 94 95 func (a *AssemblerImpl) encodeStaticConstToRegister(buf asm.Buffer, n *nodeImpl) (err error) { 96 var opc []byte 97 var rex, mandatoryPrefix byte 98 info := staticConstToRegisterOpcodes[n.instruction] 99 switch n.instruction { 100 case MOVL, MOVQ: 101 if isVectorRegister(n.dstReg) { 102 opc, mandatoryPrefix = info.vopcode, info.vmandatoryPrefix 103 break 104 } 105 fallthrough 106 default: 107 opc, rex, mandatoryPrefix = info.opcode, info.rex, info.mandatoryPrefix 108 } 109 return a.encodeStaticConstImpl(buf, n, opc, rex, mandatoryPrefix) 110 } 111 112 // encodeStaticConstImpl encodes an instruction where mod:r/m points to the memory location of the static constant n.staticConst, 113 // and the other operand is the register given at n.srcReg or n.dstReg. 114 func (a *AssemblerImpl) encodeStaticConstImpl(buf asm.Buffer, n *nodeImpl, opcode []byte, rex rexPrefix, mandatoryPrefix byte) error { 115 a.pool.AddConst(n.staticConst, uint64(buf.Len())) 116 117 var reg asm.Register 118 if n.dstReg != asm.NilRegister { 119 reg = n.dstReg 120 } else { 121 reg = n.srcReg 122 } 123 124 reg3Bits, rexPrefix := register3bits(reg, registerSpecifierPositionModRMFieldReg) 125 rexPrefix |= rex 126 127 base := buf.Len() 128 code := buf.Append(len(opcode) + 7)[:0] 129 130 if mandatoryPrefix != 0 { 131 code = append(code, mandatoryPrefix) 132 } 133 134 if rexPrefix != rexPrefixNone { 135 code = append(code, rexPrefix) 136 } 137 138 code = append(code, opcode...) 139 140 // https://wiki.osdev.org/X86-64_Instruction_Encoding#32.2F64-bit_addressing 141 modRM := 0b00_000_101 | // Indicate "[RIP + 32bit displacement]" encoding. 142 (reg3Bits << 3) // Place the reg on ModRM:reg. 143 code = append(code, modRM) 144 145 // Preserve 4 bytes for displacement which will be filled after we finalize the location. 146 code = append(code, 0, 0, 0, 0) 147 148 if !n.staticConstReferrersAdded { 149 a.staticConstReferrers = append(a.staticConstReferrers, staticConstReferrer{n: n, instLen: len(code)}) 150 n.staticConstReferrersAdded = true 151 } 152 153 buf.Truncate(base + len(code)) 154 return nil 155 } 156 157 // CompileStaticConstToRegister implements Assembler.CompileStaticConstToRegister. 158 func (a *AssemblerImpl) CompileStaticConstToRegister(instruction asm.Instruction, c *asm.StaticConst, dstReg asm.Register) (err error) { 159 if len(c.Raw)%2 != 0 { 160 err = fmt.Errorf("the length of a static constant must be even but was %d", len(c.Raw)) 161 return 162 } 163 164 n := a.newNode(instruction, operandTypesStaticConstToRegister) 165 n.dstReg = dstReg 166 n.staticConst = c 167 return 168 } 169 170 // CompileRegisterToStaticConst implements Assembler.CompileRegisterToStaticConst. 171 func (a *AssemblerImpl) CompileRegisterToStaticConst(instruction asm.Instruction, srcReg asm.Register, c *asm.StaticConst) (err error) { 172 if len(c.Raw)%2 != 0 { 173 err = fmt.Errorf("the length of a static constant must be even but was %d", len(c.Raw)) 174 return 175 } 176 177 n := a.newNode(instruction, operandTypesRegisterToStaticConst) 178 n.srcReg = srcReg 179 n.staticConst = c 180 return 181 }