wa-lang.org/wazero@v1.0.2/internal/asm/amd64/impl_staticconst.go (about) 1 package amd64 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "math" 7 8 "wa-lang.org/wazero/internal/asm" 9 ) 10 11 // defaultMaxDisplacementForConstantPool is the maximum displacement allowed for literal move instructions which access 12 // the constant pool. This is set as 2 ^30 conservatively while the actual limit is 2^31 since we actually allow this 13 // limit plus max(length(c) for c in the pool) so we must ensure that limit is less than 2^31. 14 const defaultMaxDisplacementForConstantPool = 1 << 30 15 16 func (a *AssemblerImpl) maybeFlushConstants(isEndOfFunction bool) { 17 if a.pool.FirstUseOffsetInBinary == nil { 18 return 19 } 20 21 if isEndOfFunction || 22 // If the distance between (the first use in binary) and (end of constant pool) can be larger 23 // than MaxDisplacementForConstantPool, we have to emit the constant pool now, otherwise 24 // a const might be unreachable by a literal move whose maximum offset is +- 2^31. 25 ((a.pool.PoolSizeInBytes+a.buf.Len())-int(*a.pool.FirstUseOffsetInBinary)) >= a.MaxDisplacementForConstantPool { 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 a.buf.WriteByte(0xe9) 34 a.WriteConst(int64(a.pool.PoolSizeInBytes), 32) 35 } else { 36 // short jump: https://www.felixcloutier.com/x86/jmp 37 a.buf.WriteByte(0xeb) 38 a.WriteConst(int64(a.pool.PoolSizeInBytes), 8) 39 } 40 } 41 42 for _, c := range a.pool.Consts { 43 c.SetOffsetInBinary(uint64(a.buf.Len())) 44 a.buf.Write(c.Raw) 45 } 46 47 a.pool = asm.NewStaticConstPool() // reset 48 } 49 } 50 51 type staticConstOpcode struct { 52 opcode []byte 53 mandatoryPrefix byte 54 rex RexPrefix 55 } 56 57 var registerToStaticConstOpcodes = map[asm.Instruction]staticConstOpcode{ 58 // https://www.felixcloutier.com/x86/cmp 59 CMPL: {opcode: []byte{0x3b}}, 60 CMPQ: {opcode: []byte{0x3b}, rex: RexPrefixW}, 61 } 62 63 func (a *AssemblerImpl) encodeRegisterToStaticConst(n *nodeImpl) (err error) { 64 opc, ok := registerToStaticConstOpcodes[n.instruction] 65 if !ok { 66 return errorEncodingUnsupported(n) 67 } 68 return a.encodeStaticConstImpl(n, opc.opcode, opc.rex, opc.mandatoryPrefix) 69 } 70 71 var staticConstToRegisterOpcodes = map[asm.Instruction]struct { 72 opcode []byte 73 mandatoryPrefix byte 74 rex RexPrefix 75 }{ 76 // https://www.felixcloutier.com/x86/movdqu:vmovdqu8:vmovdqu16:vmovdqu32:vmovdqu64 77 MOVDQU: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x6f}}, 78 // https://www.felixcloutier.com/x86/lea 79 LEAQ: {opcode: []byte{0x8d}, rex: RexPrefixW}, 80 // https://www.felixcloutier.com/x86/movupd 81 MOVUPD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x10}}, 82 // https://www.felixcloutier.com/x86/mov 83 MOVL: {opcode: []byte{0x8b}}, 84 MOVQ: {opcode: []byte{0x8b}, rex: RexPrefixW}, 85 // https://www.felixcloutier.com/x86/ucomisd 86 UCOMISD: {opcode: []byte{0x0f, 0x2e}, mandatoryPrefix: 0x66}, 87 // https://www.felixcloutier.com/x86/ucomiss 88 UCOMISS: {opcode: []byte{0x0f, 0x2e}}, 89 // https://www.felixcloutier.com/x86/subss 90 SUBSS: {opcode: []byte{0x0f, 0x5c}, mandatoryPrefix: 0xf3}, 91 // https://www.felixcloutier.com/x86/subsd 92 SUBSD: {opcode: []byte{0x0f, 0x5c}, mandatoryPrefix: 0xf2}, 93 // https://www.felixcloutier.com/x86/cmp 94 CMPL: {opcode: []byte{0x39}}, 95 CMPQ: {opcode: []byte{0x39}, rex: RexPrefixW}, 96 // https://www.felixcloutier.com/x86/add 97 ADDL: {opcode: []byte{0x03}}, 98 ADDQ: {opcode: []byte{0x03}, rex: RexPrefixW}, 99 } 100 101 var staticConstToVectorRegisterOpcodes = map[asm.Instruction]staticConstOpcode{ 102 // https://www.felixcloutier.com/x86/mov 103 MOVL: {opcode: []byte{0x0f, 0x6e}, mandatoryPrefix: 0x66}, 104 MOVQ: {opcode: []byte{0x0f, 0x7e}, mandatoryPrefix: 0xf3}, 105 } 106 107 func (a *AssemblerImpl) encodeStaticConstToRegister(n *nodeImpl) (err error) { 108 var opc staticConstOpcode 109 var ok bool 110 if IsVectorRegister(n.dstReg) && (n.instruction == MOVL || n.instruction == MOVQ) { 111 opc, ok = staticConstToVectorRegisterOpcodes[n.instruction] 112 } else { 113 opc, ok = staticConstToRegisterOpcodes[n.instruction] 114 } 115 if !ok { 116 return errorEncodingUnsupported(n) 117 } 118 return a.encodeStaticConstImpl(n, opc.opcode, opc.rex, opc.mandatoryPrefix) 119 } 120 121 // encodeStaticConstImpl encodes an instruction where mod:r/m points to the memory location of the static constant n.staticConst, 122 // and the other operand is the register given at n.srcReg or n.dstReg. 123 func (a *AssemblerImpl) encodeStaticConstImpl(n *nodeImpl, opcode []byte, rex RexPrefix, mandatoryPrefix byte) (err error) { 124 a.pool.AddConst(n.staticConst, uint64(a.buf.Len())) 125 126 var reg asm.Register 127 if n.dstReg != asm.NilRegister { 128 reg = n.dstReg 129 } else { 130 reg = n.srcReg 131 } 132 133 reg3Bits, rexPrefix, err := register3bits(reg, registerSpecifierPositionModRMFieldReg) 134 if err != nil { 135 return err 136 } 137 138 rexPrefix |= rex 139 140 var inst []byte 141 n.staticConst.AddOffsetFinalizedCallback(func(offsetOfConstInBinary uint64) { 142 bin := a.buf.Bytes() 143 displacement := int(offsetOfConstInBinary) - int(n.OffsetInBinary()) - len(inst) 144 displacementOffsetInInstruction := n.OffsetInBinary() + uint64(len(inst)-4) 145 binary.LittleEndian.PutUint32(bin[displacementOffsetInInstruction:], uint32(int32(displacement))) 146 }) 147 148 // https://wiki.osdev.org/X86-64_Instruction_Encoding#32.2F64-bit_addressing 149 modRM := 0b00_000_101 | // Indicate "[RIP + 32bit displacement]" encoding. 150 (reg3Bits << 3) // Place the reg on ModRM:reg. 151 152 if mandatoryPrefix != 0 { 153 inst = append(inst, mandatoryPrefix) 154 } 155 156 if rexPrefix != RexPrefixNone { 157 inst = append(inst, rexPrefix) 158 } 159 160 inst = append(inst, opcode...) 161 inst = append(inst, modRM, 162 0x0, 0x0, 0x0, 0x0, // Preserve 4 bytes for displacement. 163 ) 164 165 a.buf.Write(inst) 166 return 167 } 168 169 // CompileStaticConstToRegister implements Assembler.CompileStaticConstToRegister. 170 func (a *AssemblerImpl) CompileStaticConstToRegister(instruction asm.Instruction, c *asm.StaticConst, dstReg asm.Register) (err error) { 171 if len(c.Raw)%2 != 0 { 172 err = fmt.Errorf("the length of a static constant must be even but was %d", len(c.Raw)) 173 return 174 } 175 176 n := a.newNode(instruction, operandTypesStaticConstToRegister) 177 n.dstReg = dstReg 178 n.staticConst = c 179 return 180 } 181 182 // CompileRegisterToStaticConst implements Assembler.CompileRegisterToStaticConst. 183 func (a *AssemblerImpl) CompileRegisterToStaticConst(instruction asm.Instruction, srcReg asm.Register, c *asm.StaticConst) (err error) { 184 if len(c.Raw)%2 != 0 { 185 err = fmt.Errorf("the length of a static constant must be even but was %d", len(c.Raw)) 186 return 187 } 188 189 n := a.newNode(instruction, operandTypesRegisterToStaticConst) 190 n.srcReg = srcReg 191 n.staticConst = c 192 return 193 }