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  }