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  }