Files
wazero/internal/asm/amd64/impl.go
2022-06-01 12:18:08 +09:00

2416 lines
82 KiB
Go

package amd64
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"github.com/tetratelabs/wazero/internal/asm"
)
// NodeImpl implements asm.Node for amd64.
type NodeImpl struct {
// NOTE: fields here are exported for testing with the amd64_debug package.
Instruction asm.Instruction
OffsetInBinaryField asm.NodeOffsetInBinary // Field suffix to dodge conflict with OffsetInBinary
// JumpTarget holds the target node in the linked for the jump-kind instruction.
JumpTarget *NodeImpl
Flag NodeFlag
// next holds the next node from this node in the assembled linked list.
Next *NodeImpl
Types OperandTypes
SrcReg, DstReg asm.Register
SrcConst, DstConst asm.ConstantValue
SrcMemIndex, DstMemIndex asm.Register
SrcMemScale, DstMemScale byte
Arg byte
// readInstructionAddressBeforeTargetInstruction holds the instruction right before the target of
// read instruction address instruction. See asm.assemblerBase.CompileReadInstructionAddress.
readInstructionAddressBeforeTargetInstruction asm.Instruction
// JumpOrigins hold all the nodes trying to jump into this node. In other words, all the nodes with .JumpTarget == this.
JumpOrigins map[*NodeImpl]struct{}
staticConst asm.StaticConst
}
type NodeFlag byte
const (
// NodeFlagInitializedForEncoding is always set to indicate that node is already initialized. Notably, this is used to judge
// whether a jump is backward or forward before encoding.
NodeFlagInitializedForEncoding NodeFlag = 1 << iota
NodeFlagBackwardJump
// NodeFlagShortForwardJump is set to false by default and only used by forward branch jumps, which means .JumpTarget != nil and
// the target node is encoded after this node. False by default means that we Encode all the jumps with JumpTarget
// as short jump (i.e. relative signed 8-bit integer offset jump) and try to Encode as small as possible.
NodeFlagShortForwardJump
)
func (n *NodeImpl) isInitializedForEncoding() bool {
return n.Flag&NodeFlagInitializedForEncoding != 0
}
func (n *NodeImpl) isJumpNode() bool {
return n.JumpTarget != nil
}
func (n *NodeImpl) isBackwardJump() bool {
return n.isJumpNode() && (n.Flag&NodeFlagBackwardJump != 0)
}
func (n *NodeImpl) isForwardJump() bool {
return n.isJumpNode() && (n.Flag&NodeFlagBackwardJump == 0)
}
func (n *NodeImpl) isForwardShortJump() bool {
return n.isForwardJump() && n.Flag&NodeFlagShortForwardJump != 0
}
// AssignJumpTarget implements asm.Node.AssignJumpTarget.
func (n *NodeImpl) AssignJumpTarget(target asm.Node) {
n.JumpTarget = target.(*NodeImpl)
}
// AssignDestinationConstant implements asm.Node.AssignDestinationConstant.
func (n *NodeImpl) AssignDestinationConstant(value asm.ConstantValue) {
n.DstConst = value
}
// AssignSourceConstant implements asm.Node.AssignSourceConstant.
func (n *NodeImpl) AssignSourceConstant(value asm.ConstantValue) {
n.SrcConst = value
}
// OffsetInBinary implements asm.Node.OffsetInBinary.
func (n *NodeImpl) OffsetInBinary() asm.NodeOffsetInBinary {
return n.OffsetInBinaryField
}
// String implements fmt.Stringer.
//
// This is for debugging purpose, and the format is almost same as the AT&T assembly syntax,
// meaning that this should look like "INSTRUCTION ${from}, ${to}" where each operand
// might be embraced by '[]' to represent the memory location.
func (n *NodeImpl) String() (ret string) {
instName := InstructionName(n.Instruction)
switch n.Types {
case OperandTypesNoneToNone:
ret = instName
case OperandTypesNoneToRegister:
ret = fmt.Sprintf("%s %s", instName, RegisterName(n.DstReg))
case OperandTypesNoneToMemory:
if n.DstMemIndex != asm.NilRegister {
ret = fmt.Sprintf("%s [%s + 0x%x + %s*0x%x]", instName,
RegisterName(n.DstReg), n.DstConst, RegisterName(n.DstMemIndex), n.DstMemScale)
} else {
ret = fmt.Sprintf("%s [%s + 0x%x]", instName, RegisterName(n.DstReg), n.DstConst)
}
case OperandTypesNoneToBranch:
ret = fmt.Sprintf("%s {%v}", instName, n.JumpTarget)
case OperandTypesRegisterToNone:
ret = fmt.Sprintf("%s %s", instName, RegisterName(n.SrcReg))
case OperandTypesRegisterToRegister:
ret = fmt.Sprintf("%s %s, %s", instName, RegisterName(n.SrcReg), RegisterName(n.DstReg))
case OperandTypesRegisterToMemory:
if n.DstMemIndex != asm.NilRegister {
ret = fmt.Sprintf("%s %s, [%s + 0x%x + %s*0x%x]", instName, RegisterName(n.SrcReg),
RegisterName(n.DstReg), n.DstConst, RegisterName(n.DstMemIndex), n.DstMemScale)
} else {
ret = fmt.Sprintf("%s %s, [%s + 0x%x]", instName, RegisterName(n.SrcReg), RegisterName(n.DstReg), n.DstConst)
}
case OperandTypesRegisterToConst:
ret = fmt.Sprintf("%s %s, 0x%x", instName, RegisterName(n.SrcReg), n.DstConst)
case OperandTypesMemoryToRegister:
if n.SrcMemIndex != asm.NilRegister {
ret = fmt.Sprintf("%s [%s + %d + %s*0x%x], %s", instName,
RegisterName(n.SrcReg), n.SrcConst, RegisterName(n.SrcMemIndex), n.SrcMemScale, RegisterName(n.DstReg))
} else {
ret = fmt.Sprintf("%s [%s + 0x%x], %s", instName, RegisterName(n.SrcReg), n.SrcConst, RegisterName(n.DstReg))
}
case OperandTypesMemoryToConst:
if n.SrcMemIndex != asm.NilRegister {
ret = fmt.Sprintf("%s [%s + %d + %s*0x%x], 0x%x", instName,
RegisterName(n.SrcReg), n.SrcConst, RegisterName(n.SrcMemIndex), n.SrcMemScale, n.DstConst)
} else {
ret = fmt.Sprintf("%s [%s + 0x%x], 0x%x", instName, RegisterName(n.SrcReg), n.SrcConst, n.DstConst)
}
case OperandTypesConstToMemory:
if n.DstMemIndex != asm.NilRegister {
ret = fmt.Sprintf("%s 0x%x, [%s + 0x%x + %s*0x%x]", instName, n.SrcConst,
RegisterName(n.DstReg), n.DstConst, RegisterName(n.DstMemIndex), n.DstMemScale)
} else {
ret = fmt.Sprintf("%s 0x%x, [%s + 0x%x]", instName, n.SrcConst, RegisterName(n.DstReg), n.DstConst)
}
case OperandTypesConstToRegister:
ret = fmt.Sprintf("%s 0x%x, %s", instName, n.SrcConst, RegisterName(n.DstReg))
}
return
}
// OperandType represents where an operand is placed for an instruction.
// Note: this is almost the same as obj.AddrType in GO assembler.
type OperandType byte
const (
OperandTypeNone OperandType = iota
OperandTypeRegister
OperandTypeMemory
OperandTypeConst
OperandTypeStaticConst
OperandTypeBranch
)
func (o OperandType) String() (ret string) {
switch o {
case OperandTypeNone:
ret = "none"
case OperandTypeRegister:
ret = "register"
case OperandTypeMemory:
ret = "memory"
case OperandTypeConst:
ret = "const"
case OperandTypeBranch:
ret = "branch"
case OperandTypeStaticConst:
ret = "static-const"
}
return
}
// OperandTypes represents the only combinations of two OperandTypes used by wazero
type OperandTypes struct{ src, dst OperandType }
var (
OperandTypesNoneToNone = OperandTypes{OperandTypeNone, OperandTypeNone}
OperandTypesNoneToRegister = OperandTypes{OperandTypeNone, OperandTypeRegister}
OperandTypesNoneToMemory = OperandTypes{OperandTypeNone, OperandTypeMemory}
OperandTypesNoneToBranch = OperandTypes{OperandTypeNone, OperandTypeBranch}
OperandTypesRegisterToNone = OperandTypes{OperandTypeRegister, OperandTypeNone}
OperandTypesRegisterToRegister = OperandTypes{OperandTypeRegister, OperandTypeRegister}
OperandTypesRegisterToMemory = OperandTypes{OperandTypeRegister, OperandTypeMemory}
OperandTypesRegisterToConst = OperandTypes{OperandTypeRegister, OperandTypeConst}
OperandTypesMemoryToRegister = OperandTypes{OperandTypeMemory, OperandTypeRegister}
OperandTypesMemoryToConst = OperandTypes{OperandTypeMemory, OperandTypeConst}
OperandTypesConstToRegister = OperandTypes{OperandTypeConst, OperandTypeRegister}
OperandTypesConstToMemory = OperandTypes{OperandTypeConst, OperandTypeMemory}
OperandTypesStaticConstToRegister = OperandTypes{OperandTypeStaticConst, OperandTypeRegister}
)
// String implements fmt.Stringer
func (o OperandTypes) String() string {
return fmt.Sprintf("from:%s,to:%s", o.src, o.dst)
}
// AssemblerImpl implements Assembler.
type AssemblerImpl struct {
asm.BaseAssemblerImpl
EnablePadding bool
Root, Current *NodeImpl
nodeCount int
Buf *bytes.Buffer
ForceReAssemble bool
// MaxDisplacementForConstantPool is fixed to defaultMaxDisplacementForConstantPool
// but have it as a field here for testability.
MaxDisplacementForConstantPool int
pool constPool
}
// compile-time check to ensure AssemblerImpl implements Assembler.
var _ Assembler = &AssemblerImpl{}
func NewAssemblerImpl() *AssemblerImpl {
return &AssemblerImpl{Buf: bytes.NewBuffer(nil), EnablePadding: true, pool: newConstPool(),
MaxDisplacementForConstantPool: defaultMaxDisplacementForConstantPool}
}
// newNode creates a new Node and appends it into the linked list.
func (a *AssemblerImpl) newNode(instruction asm.Instruction, types OperandTypes) *NodeImpl {
n := &NodeImpl{
Instruction: instruction,
Next: nil,
Types: types,
JumpOrigins: map[*NodeImpl]struct{}{},
}
a.addNode(n)
a.nodeCount++
return n
}
// addNode appends the new node into the linked list.
func (a *AssemblerImpl) addNode(node *NodeImpl) {
if a.Root == nil {
a.Root = node
a.Current = node
} else {
parent := a.Current
parent.Next = node
a.Current = node
}
for _, o := range a.SetBranchTargetOnNextNodes {
origin := o.(*NodeImpl)
origin.JumpTarget = node
}
a.SetBranchTargetOnNextNodes = nil
}
// EncodeNode encodes the given node into writer.
func (a *AssemblerImpl) EncodeNode(n *NodeImpl) (err error) {
switch n.Types {
case OperandTypesNoneToNone:
err = a.encodeNoneToNone(n)
case OperandTypesNoneToRegister:
err = a.EncodeNoneToRegister(n)
case OperandTypesNoneToMemory:
err = a.EncodeNoneToMemory(n)
case OperandTypesNoneToBranch:
// Branching operand can be encoded as relative jumps.
err = a.EncodeRelativeJump(n)
case OperandTypesRegisterToNone:
err = a.EncodeRegisterToNone(n)
case OperandTypesRegisterToRegister:
err = a.EncodeRegisterToRegister(n)
case OperandTypesRegisterToMemory:
err = a.EncodeRegisterToMemory(n)
case OperandTypesRegisterToConst:
err = a.EncodeRegisterToConst(n)
case OperandTypesMemoryToRegister:
err = a.EncodeMemoryToRegister(n)
case OperandTypesConstToRegister:
err = a.EncodeConstToRegister(n)
case OperandTypesConstToMemory:
err = a.EncodeConstToMemory(n)
case OperandTypesMemoryToConst:
err = a.EncodeMemoryToConst(n)
case OperandTypesStaticConstToRegister:
err = a.encodeStaticConstToRegister(n)
default:
err = fmt.Errorf("encoder undefined for [%s] operand type", n.Types)
}
return
}
// Assemble implements asm.AssemblerBase
func (a *AssemblerImpl) Assemble() ([]byte, error) {
a.InitializeNodesForEncoding()
// Continue encoding until we are not forced to re-assemble which happens when
// a short relative jump ends up the offset larger than 8-bit length.
for {
err := a.Encode()
if err != nil {
return nil, err
}
if !a.ForceReAssemble {
break
} else {
// We reset the length of buffer but don't delete the underlying slice since
// the binary size will roughly the same after reassemble.
a.Buf.Reset()
// Reset the re-assemble Flag in order to avoid the infinite loop!
a.ForceReAssemble = false
}
}
code := a.Buf.Bytes()
for _, cb := range a.OnGenerateCallbacks {
if err := cb(code); err != nil {
return nil, err
}
}
return code, nil
}
// InitializeNodesForEncoding initializes NodeImpl.Flag and determine all the jumps
// are forward or backward jump.
func (a *AssemblerImpl) InitializeNodesForEncoding() {
for n := a.Root; n != nil; n = n.Next {
n.Flag |= NodeFlagInitializedForEncoding
if target := n.JumpTarget; target != nil {
if target.isInitializedForEncoding() {
// This means the target exists behind.
n.Flag |= NodeFlagBackwardJump
} else {
// Otherwise, this is forward jump.
// We start with assuming that the jump can be short (8-bit displacement).
// If it doens't fit, we change this Flag in resolveRelativeForwardJump.
n.Flag |= NodeFlagShortForwardJump
}
}
}
// Roughly allocate the buffer by assuming an instruction has 5-bytes length on average.
a.Buf.Grow(a.nodeCount * 5)
}
func (a *AssemblerImpl) Encode() (err error) {
for n := a.Root; n != nil; n = n.Next {
// If an instruction needs NOP padding, we do so before encoding it.
// https://www.intel.com/content/dam/support/us/en/documents/processors/mitigations-jump-conditional-code-erratum.pdf
if a.EnablePadding {
if err = a.maybeNOPPadding(n); err != nil {
return
}
}
// After the padding, we can finalize the offset of this instruction in the binary.
n.OffsetInBinaryField = uint64(a.Buf.Len())
if err = a.EncodeNode(n); err != nil {
return
}
err = a.ResolveForwardRelativeJumps(n)
if err != nil {
err = fmt.Errorf("invalid relative forward jumps: %w", err)
break
}
a.maybeFlushConstants(n.Next == nil)
}
return
}
// maybeNOPPadding maybe appends NOP instructions before the node `n`.
// This is necessary to avoid Intel's jump erratum:
// https://www.intel.com/content/dam/support/us/en/documents/processors/mitigations-jump-conditional-code-erratum.pdf
func (a *AssemblerImpl) maybeNOPPadding(n *NodeImpl) (err error) {
var instructionLen int32
// See in Section 2.1 in for when we have to pad NOP.
// https://www.intel.com/content/dam/support/us/en/documents/processors/mitigations-jump-conditional-code-erratum.pdf
switch n.Instruction {
case RET, JMP, JCC, JCS, JEQ, JGE, JGT, JHI, JLE, JLS, JLT, JMI, JNE, JPC, JPS:
// In order to know the instruction length before writing into the binary,
// we try encoding it with the temporary buffer.
saved := a.Buf
a.Buf = bytes.NewBuffer(nil)
// Assign the temporary offset which may or may not be correct depending on the padding decision.
n.OffsetInBinaryField = uint64(saved.Len())
// Encode the node and get the instruction length.
if err = a.EncodeNode(n); err != nil {
return
}
instructionLen = int32(a.Buf.Len())
// Revert the temporary buffer.
a.Buf = saved
case // The possible fused jump instructions if the next node is a conditional jump instruction.
CMPL, CMPQ, TESTL, TESTQ, ADDL, ADDQ, SUBL, SUBQ, ANDL, ANDQ, INCQ, DECQ:
instructionLen, err = a.fusedInstructionLength(n)
if err != nil {
return err
}
}
if instructionLen == 0 {
return
}
const boundaryInBytes int32 = 32
const mask int32 = boundaryInBytes - 1
var padNum int
currentPos := int32(a.Buf.Len())
if used := currentPos & mask; used+instructionLen >= boundaryInBytes {
padNum = int(boundaryInBytes - used)
}
a.padNOP(padNum)
return
}
// fusedInstructionLength returns the length of "macro fused instruction" if the
// instruction sequence starting from `n` can be fused by processor. Otherwise,
// returns zero.
func (a *AssemblerImpl) fusedInstructionLength(n *NodeImpl) (ret int32, err error) {
// Find the next non-NOP instruction.
next := n.Next
for ; next != nil && next.Instruction == NOP; next = next.Next {
}
if next == nil {
return
}
inst, jmpInst := n.Instruction, next.Instruction
if !(jmpInst == JCC || jmpInst == JCS || jmpInst == JEQ || jmpInst == JGE || jmpInst == JGT ||
jmpInst == JHI || jmpInst == JLE || jmpInst == JLS || jmpInst == JLT || jmpInst == JMI ||
jmpInst == JNE || jmpInst == JPC || jmpInst == JPS) {
// If the next instruction is not jump kind, the instruction will not be fused.
return
}
// How to determine whether the instruction can be fused is described in
// Section 3.4.2.2 of "Intel Optimization Manual":
// https://www.intel.com/content/dam/doc/manual/64-ia-32-architectures-optimization-manual.pdf
isTest := inst == TESTL || inst == TESTQ
isCmp := inst == CMPQ || inst == CMPL
isTestCmp := isTest || isCmp
if isTestCmp && ((n.Types.src == OperandTypeMemory && n.Types.dst == OperandTypeConst) ||
(n.Types.src == OperandTypeConst && n.Types.dst == OperandTypeMemory)) {
// The manual says: "CMP and TEST can not be fused when comparing MEM-IMM".
return
}
// Implement the decision according to the table 3-1 in the manual.
isAnd := inst == ANDL || inst == ANDQ
if !isTest && !isAnd {
if jmpInst == JMI || jmpInst == JPL || jmpInst == JPS || jmpInst == JPC {
// These jumps are only fused for TEST or AND.
return
}
isAdd := inst == ADDL || inst == ADDQ
isSub := inst == SUBL || inst == SUBQ
if !isCmp && !isAdd && !isSub {
if jmpInst == JCS || jmpInst == JCC || jmpInst == JHI || jmpInst == JLS {
// Thses jumpst are only fused for TEST, AND, CMP, ADD, or SUB.
return
}
}
}
// Now the instruction is ensured to be fused by the processor.
// In order to know the fused instruction length before writing into the binary,
// we try encoding it with the temporary buffer.
saved := a.Buf
savedLen := uint64(saved.Len())
a.Buf = bytes.NewBuffer(nil)
for _, fused := range []*NodeImpl{n, next} {
// Assign the temporary offset which may or may not be correct depending on the padding decision.
fused.OffsetInBinaryField = savedLen + uint64(a.Buf.Len())
// Encode the node into the temporary buffer.
if err = a.EncodeNode(fused); err != nil {
return
}
}
ret = int32(a.Buf.Len())
// Revert the temporary buffer.
a.Buf = saved
return
}
// nopOpcodes is the multi byte NOP instructions table derived from section 5.8 "Code Padding with Operand-Size Override and Multibyte NOP"
// in "AMD Software Optimization Guide for AMD Family 15h Processors" https://www.amd.com/system/files/TechDocs/47414_15h_sw_opt_guide.pdf
//
// Note: We use up to 9 bytes NOP variant to line our implementation with Go's assembler.
// TODO: After golang-asm removal, add 9, 10 and 11 bytes variants.
var nopOpcodes = [][9]byte{
{0x90},
{0x66, 0x90},
{0x0f, 0x1f, 0x00},
{0x0f, 0x1f, 0x40, 0x00},
{0x0f, 0x1f, 0x44, 0x00, 0x00},
{0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00},
{0x0f, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00},
{0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00},
}
func (a *AssemblerImpl) padNOP(num int) {
for num > 0 {
singleNopNum := num
if singleNopNum > len(nopOpcodes) {
singleNopNum = len(nopOpcodes)
}
a.Buf.Write(nopOpcodes[singleNopNum-1][:singleNopNum])
num -= singleNopNum
}
}
// CompileStandAlone implements the same method as documented on asm.AssemblerBase.
func (a *AssemblerImpl) CompileStandAlone(instruction asm.Instruction) asm.Node {
return a.newNode(instruction, OperandTypesNoneToNone)
}
// CompileConstToRegister implements the same method as documented on asm.AssemblerBase.
func (a *AssemblerImpl) CompileConstToRegister(
instruction asm.Instruction,
value asm.ConstantValue,
destinationReg asm.Register,
) (inst asm.Node) {
n := a.newNode(instruction, OperandTypesConstToRegister)
n.SrcConst = value
n.DstReg = destinationReg
return n
}
// CompileRegisterToRegister implements the same method as documented on asm.AssemblerBase.
func (a *AssemblerImpl) CompileRegisterToRegister(instruction asm.Instruction, from, to asm.Register) {
n := a.newNode(instruction, OperandTypesRegisterToRegister)
n.SrcReg = from
n.DstReg = to
}
// CompileMemoryToRegister implements the same method as documented on asm.AssemblerBase.
func (a *AssemblerImpl) CompileMemoryToRegister(
instruction asm.Instruction,
sourceBaseReg asm.Register,
sourceOffsetConst asm.ConstantValue,
destinationReg asm.Register,
) {
n := a.newNode(instruction, OperandTypesMemoryToRegister)
n.SrcReg = sourceBaseReg
n.SrcConst = sourceOffsetConst
n.DstReg = destinationReg
}
// CompileRegisterToMemory implements the same method as documented on asm.AssemblerBase.
func (a *AssemblerImpl) CompileRegisterToMemory(
instruction asm.Instruction,
sourceRegister, destinationBaseRegister asm.Register,
destinationOffsetConst asm.ConstantValue,
) {
n := a.newNode(instruction, OperandTypesRegisterToMemory)
n.SrcReg = sourceRegister
n.DstReg = destinationBaseRegister
n.DstConst = destinationOffsetConst
}
// CompileJump implements the same method as documented on asm.AssemblerBase.
func (a *AssemblerImpl) CompileJump(jmpInstruction asm.Instruction) asm.Node {
return a.newNode(jmpInstruction, OperandTypesNoneToBranch)
}
// CompileJumpToMemory implements the same method as documented on asm.AssemblerBase.
func (a *AssemblerImpl) CompileJumpToMemory(
jmpInstruction asm.Instruction,
baseReg asm.Register,
offset asm.ConstantValue,
) {
n := a.newNode(jmpInstruction, OperandTypesNoneToMemory)
n.DstReg = baseReg
n.DstConst = offset
}
// CompileJumpToRegister implements the same method as documented on asm.AssemblerBase.
func (a *AssemblerImpl) CompileJumpToRegister(jmpInstruction asm.Instruction, reg asm.Register) {
n := a.newNode(jmpInstruction, OperandTypesNoneToRegister)
n.DstReg = reg
}
// CompileReadInstructionAddress implements the same method as documented on asm.AssemblerBase.
func (a *AssemblerImpl) CompileReadInstructionAddress(
destinationRegister asm.Register,
beforeAcquisitionTargetInstruction asm.Instruction,
) {
n := a.newNode(LEAQ, OperandTypesMemoryToRegister)
n.DstReg = destinationRegister
n.readInstructionAddressBeforeTargetInstruction = beforeAcquisitionTargetInstruction
}
// CompileRegisterToRegisterWithArg implements the same method as documented on amd64.Assembler.
func (a *AssemblerImpl) CompileRegisterToRegisterWithArg(
instruction asm.Instruction,
from, to asm.Register,
arg byte,
) {
n := a.newNode(instruction, OperandTypesRegisterToRegister)
n.SrcReg = from
n.DstReg = to
n.Arg = arg
}
// CompileMemoryWithIndexToRegister implements the same method as documented on amd64.Assembler.
func (a *AssemblerImpl) CompileMemoryWithIndexToRegister(
instruction asm.Instruction,
srcBaseReg asm.Register,
srcOffsetConst asm.ConstantValue,
srcIndex asm.Register,
srcScale int16,
dstReg asm.Register,
) {
n := a.newNode(instruction, OperandTypesMemoryToRegister)
n.SrcReg = srcBaseReg
n.SrcConst = srcOffsetConst
n.SrcMemIndex = srcIndex
n.SrcMemScale = byte(srcScale)
n.DstReg = dstReg
}
// CompileMemoryWithIndexAndArgToRegister implements the same method as documented on amd64.Assembler.
func (a *AssemblerImpl) CompileMemoryWithIndexAndArgToRegister(
instruction asm.Instruction,
srcBaseReg asm.Register,
srcOffsetConst asm.ConstantValue,
srcIndex asm.Register,
srcScale int16,
dstReg asm.Register,
arg byte,
) {
n := a.newNode(instruction, OperandTypesMemoryToRegister)
n.SrcReg = srcBaseReg
n.SrcConst = srcOffsetConst
n.SrcMemIndex = srcIndex
n.SrcMemScale = byte(srcScale)
n.DstReg = dstReg
n.Arg = arg
}
// CompileRegisterToMemoryWithIndex implements the same method as documented on amd64.Assembler.
func (a *AssemblerImpl) CompileRegisterToMemoryWithIndex(
instruction asm.Instruction,
srcReg, dstBaseReg asm.Register,
dstOffsetConst asm.ConstantValue,
dstIndex asm.Register,
dstScale int16,
) {
n := a.newNode(instruction, OperandTypesRegisterToMemory)
n.SrcReg = srcReg
n.DstReg = dstBaseReg
n.DstConst = dstOffsetConst
n.DstMemIndex = dstIndex
n.DstMemScale = byte(dstScale)
}
// CompileRegisterToMemoryWithIndexAndArg implements the same method as documented on amd64.Assembler.
func (a *AssemblerImpl) CompileRegisterToMemoryWithIndexAndArg(
instruction asm.Instruction,
srcReg, dstBaseReg asm.Register,
dstOffsetConst asm.ConstantValue,
dstIndex asm.Register,
dstScale int16,
arg byte,
) {
n := a.newNode(instruction, OperandTypesRegisterToMemory)
n.SrcReg = srcReg
n.DstReg = dstBaseReg
n.DstConst = dstOffsetConst
n.DstMemIndex = dstIndex
n.DstMemScale = byte(dstScale)
n.Arg = arg
}
// CompileRegisterToConst implements the same method as documented on amd64.Assembler.
func (a *AssemblerImpl) CompileRegisterToConst(
instruction asm.Instruction,
srcRegister asm.Register,
value asm.ConstantValue,
) asm.Node {
n := a.newNode(instruction, OperandTypesRegisterToConst)
n.SrcReg = srcRegister
n.DstConst = value
return n
}
// CompileRegisterToNone implements the same method as documented on amd64.Assembler.
func (a *AssemblerImpl) CompileRegisterToNone(instruction asm.Instruction, register asm.Register) {
n := a.newNode(instruction, OperandTypesRegisterToNone)
n.SrcReg = register
}
// CompileNoneToRegister implements the same method as documented on amd64.Assembler.
func (a *AssemblerImpl) CompileNoneToRegister(instruction asm.Instruction, register asm.Register) {
n := a.newNode(instruction, OperandTypesNoneToRegister)
n.DstReg = register
}
// CompileNoneToMemory implements the same method as documented on amd64.Assembler.
func (a *AssemblerImpl) CompileNoneToMemory(
instruction asm.Instruction,
baseReg asm.Register,
offset asm.ConstantValue,
) {
n := a.newNode(instruction, OperandTypesNoneToMemory)
n.DstReg = baseReg
n.DstConst = offset
}
// CompileConstToMemory implements the same method as documented on amd64.Assembler.
func (a *AssemblerImpl) CompileConstToMemory(
instruction asm.Instruction,
value asm.ConstantValue,
dstbaseReg asm.Register,
dstOffset asm.ConstantValue,
) asm.Node {
n := a.newNode(instruction, OperandTypesConstToMemory)
n.SrcConst = value
n.DstReg = dstbaseReg
n.DstConst = dstOffset
return n
}
// CompileMemoryToConst implements the same method as documented on amd64.Assembler.
func (a *AssemblerImpl) CompileMemoryToConst(
instruction asm.Instruction,
srcBaseReg asm.Register,
srcOffset, value asm.ConstantValue,
) asm.Node {
n := a.newNode(instruction, OperandTypesMemoryToConst)
n.SrcReg = srcBaseReg
n.SrcConst = srcOffset
n.DstConst = value
return n
}
func errorEncodingUnsupported(n *NodeImpl) error {
return fmt.Errorf("%s is unsupported for %s type", InstructionName(n.Instruction), n.Types)
}
func (a *AssemblerImpl) encodeNoneToNone(n *NodeImpl) (err error) {
switch n.Instruction {
case CDQ:
// https://www.felixcloutier.com/x86/cwd:cdq:cqo
err = a.Buf.WriteByte(0x99)
case CQO:
// https://www.felixcloutier.com/x86/cwd:cdq:cqo
_, err = a.Buf.Write([]byte{RexPrefixW, 0x99})
case NOP:
// Simply optimize out the NOP instructions.
case RET:
// https://www.felixcloutier.com/x86/ret
err = a.Buf.WriteByte(0xc3)
case UD2:
// https://mudongliang.github.io/x86/html/file_module_x86_id_318.html
_, err = a.Buf.Write([]byte{0x0f, 0x0b})
default:
err = errorEncodingUnsupported(n)
}
return
}
func (a *AssemblerImpl) EncodeNoneToRegister(n *NodeImpl) (err error) {
regBits, prefix, err := register3bits(n.DstReg, registerSpecifierPositionModRMFieldRM)
if err != nil {
return err
}
// https://wiki.osdev.org/X86-64_Instruction_Encoding#ModR.2FM
modRM := 0b11_000_000 | // Specifying that opeand is register.
regBits
if n.Instruction == JMP {
// JMP's Opcode is defined as "FF /4" meaning that we have to have "4"
// in 4-6th bits in the ModRM byte. https://www.felixcloutier.com/x86/jmp
modRM |= 0b00_100_000
} else if n.Instruction == NEGQ {
prefix |= RexPrefixW
modRM |= 0b00_011_000
} else if n.Instruction == INCQ {
prefix |= RexPrefixW
} else if n.Instruction == DECQ {
prefix |= RexPrefixW
modRM |= 0b00_001_000
} else {
if RegSP <= n.DstReg && n.DstReg <= RegDI {
// If the destination is one byte length register, we need to have the default prefix.
// https: //wiki.osdev.org/X86-64_Instruction_Encoding#Registers
prefix |= RexPrefixDefault
}
}
if prefix != RexPrefixNone {
// https://wiki.osdev.org/X86-64_Instruction_Encoding#Encoding
if err = a.Buf.WriteByte(prefix); err != nil {
return
}
}
switch n.Instruction {
case JMP:
// https://www.felixcloutier.com/x86/jmp
_, err = a.Buf.Write([]byte{0xff, modRM})
case SETCC:
// https://www.felixcloutier.com/x86/setcc
_, err = a.Buf.Write([]byte{0x0f, 0x93, modRM})
case SETCS:
// https://www.felixcloutier.com/x86/setcc
_, err = a.Buf.Write([]byte{0x0f, 0x92, modRM})
case SETEQ:
// https://www.felixcloutier.com/x86/setcc
_, err = a.Buf.Write([]byte{0x0f, 0x94, modRM})
case SETGE:
// https://www.felixcloutier.com/x86/setcc
_, err = a.Buf.Write([]byte{0x0f, 0x9d, modRM})
case SETGT:
// https://www.felixcloutier.com/x86/setcc
_, err = a.Buf.Write([]byte{0x0f, 0x9f, modRM})
case SETHI:
// https://www.felixcloutier.com/x86/setcc
_, err = a.Buf.Write([]byte{0x0f, 0x97, modRM})
case SETLE:
// https://www.felixcloutier.com/x86/setcc
_, err = a.Buf.Write([]byte{0x0f, 0x9e, modRM})
case SETLS:
// https://www.felixcloutier.com/x86/setcc
_, err = a.Buf.Write([]byte{0x0f, 0x96, modRM})
case SETLT:
// https://www.felixcloutier.com/x86/setcc
_, err = a.Buf.Write([]byte{0x0f, 0x9c, modRM})
case SETNE:
// https://www.felixcloutier.com/x86/setcc
_, err = a.Buf.Write([]byte{0x0f, 0x95, modRM})
case SETPC:
// https://www.felixcloutier.com/x86/setcc
_, err = a.Buf.Write([]byte{0x0f, 0x9b, modRM})
case SETPS:
// https://www.felixcloutier.com/x86/setcc
_, err = a.Buf.Write([]byte{0x0f, 0x9a, modRM})
case NEGQ:
// https://www.felixcloutier.com/x86/neg
_, err = a.Buf.Write([]byte{0xf7, modRM})
case INCQ:
// https://www.felixcloutier.com/x86/inc
_, err = a.Buf.Write([]byte{0xff, modRM})
case DECQ:
// https://www.felixcloutier.com/x86/dec
_, err = a.Buf.Write([]byte{0xff, modRM})
default:
err = errorEncodingUnsupported(n)
}
return
}
func (a *AssemblerImpl) EncodeNoneToMemory(n *NodeImpl) (err error) {
RexPrefix, modRM, sbi, displacementWidth, err := n.GetMemoryLocation()
if err != nil {
return err
}
var opcode byte
switch n.Instruction {
case INCQ:
// https://www.felixcloutier.com/x86/inc
RexPrefix |= RexPrefixW
opcode = 0xff
case DECQ:
// https://www.felixcloutier.com/x86/dec
RexPrefix |= RexPrefixW
modRM |= 0b00_001_000 // DEC needs "/1" extension in ModRM.
opcode = 0xff
case JMP:
// https://www.felixcloutier.com/x86/jmp
modRM |= 0b00_100_000 // JMP needs "/4" extension in ModRM.
opcode = 0xff
default:
return errorEncodingUnsupported(n)
}
if RexPrefix != RexPrefixNone {
a.Buf.WriteByte(RexPrefix)
}
a.Buf.Write([]byte{opcode, modRM})
if sbi != nil {
a.Buf.WriteByte(*sbi)
}
if displacementWidth != 0 {
a.WriteConst(n.DstConst, displacementWidth)
}
return
}
type relativeJumpOpcode struct{ short, long []byte }
func (o relativeJumpOpcode) instructionLen(short bool) int64 {
if short {
return int64(len(o.short)) + 1 // 1 byte = 8 bit offset
} else {
return int64(len(o.long)) + 4 // 4 byte = 32 bit offset
}
}
var relativeJumpOpcodes = map[asm.Instruction]relativeJumpOpcode{
// https://www.felixcloutier.com/x86/jcc
JCC: {short: []byte{0x73}, long: []byte{0x0f, 0x83}},
JCS: {short: []byte{0x72}, long: []byte{0x0f, 0x82}},
JEQ: {short: []byte{0x74}, long: []byte{0x0f, 0x84}},
JGE: {short: []byte{0x7d}, long: []byte{0x0f, 0x8d}},
JGT: {short: []byte{0x7f}, long: []byte{0x0f, 0x8f}},
JHI: {short: []byte{0x77}, long: []byte{0x0f, 0x87}},
JLE: {short: []byte{0x7e}, long: []byte{0x0f, 0x8e}},
JLS: {short: []byte{0x76}, long: []byte{0x0f, 0x86}},
JLT: {short: []byte{0x7c}, long: []byte{0x0f, 0x8c}},
JMI: {short: []byte{0x78}, long: []byte{0x0f, 0x88}},
JPL: {short: []byte{0x79}, long: []byte{0x0f, 0x89}},
JNE: {short: []byte{0x75}, long: []byte{0x0f, 0x85}},
JPC: {short: []byte{0x7b}, long: []byte{0x0f, 0x8b}},
JPS: {short: []byte{0x7a}, long: []byte{0x0f, 0x8a}},
// https://www.felixcloutier.com/x86/jmp
JMP: {short: []byte{0xeb}, long: []byte{0xe9}},
}
func (a *AssemblerImpl) ResolveForwardRelativeJumps(target *NodeImpl) (err error) {
offsetInBinary := int64(target.OffsetInBinary())
for origin := range target.JumpOrigins {
shortJump := origin.isForwardShortJump()
op := relativeJumpOpcodes[origin.Instruction]
instructionLen := op.instructionLen(shortJump)
// Calculate the offset from the EIP (at the time of executing this jump instruction)
// to the target instruction. This value is always >= 0 as here we only handle forward jumps.
offset := offsetInBinary - (int64(origin.OffsetInBinary()) + instructionLen)
if shortJump {
if offset > math.MaxInt8 {
// This forces reassemble in the outer loop inside AssemblerImpl.Assemble().
a.ForceReAssemble = true
// From the next reAssemble phases, this forward jump will be encoded long jump and
// allocate 32-bit offset bytes by default. This means that this `origin` node
// will always enter the "long jump offset encoding" block below
origin.Flag ^= NodeFlagShortForwardJump
} else {
a.Buf.Bytes()[origin.OffsetInBinary()+uint64(instructionLen)-1] = byte(offset)
}
} else { // long jump offset encoding.
if offset > math.MaxInt32 {
return fmt.Errorf("too large jump offset %d for encoding %s", offset, InstructionName(origin.Instruction))
}
binary.LittleEndian.PutUint32(a.Buf.Bytes()[origin.OffsetInBinary()+uint64(instructionLen)-4:], uint32(offset))
}
}
return nil
}
func (a *AssemblerImpl) EncodeRelativeJump(n *NodeImpl) (err error) {
if n.JumpTarget == nil {
err = fmt.Errorf("jump target must not be nil for relative %s", InstructionName(n.Instruction))
return
}
op, ok := relativeJumpOpcodes[n.Instruction]
if !ok {
return errorEncodingUnsupported(n)
}
var isShortJump bool
// offsetOfEIP means the offset of EIP register at the time of executing this jump instruction.
// Relative jump instructions can be encoded with the signed 8-bit or 32-bit integer offsets from the EIP.
var offsetOfEIP int64 = 0 // We set zero and resolve later once the target instruction is encoded for forward jumps
if n.isBackwardJump() {
// If this is the backward jump, we can calculate the exact offset now.
offsetOfJumpInstruction := int64(n.JumpTarget.OffsetInBinary()) - int64(n.OffsetInBinary())
isShortJump = offsetOfJumpInstruction-2 >= math.MinInt8
offsetOfEIP = offsetOfJumpInstruction - op.instructionLen(isShortJump)
} else {
// For forward jumps, we resolve the offset when we Encode the target node. See AssemblerImpl.ResolveForwardRelativeJumps.
n.JumpTarget.JumpOrigins[n] = struct{}{}
isShortJump = n.isForwardShortJump()
}
if offsetOfEIP < math.MinInt32 { // offsetOfEIP is always <= 0 as we don't calculate it for forward jump here.
return fmt.Errorf("too large jump offset %d for encoding %s", offsetOfEIP, InstructionName(n.Instruction))
}
if isShortJump {
a.Buf.Write(op.short)
a.WriteConst(offsetOfEIP, 8)
} else {
a.Buf.Write(op.long)
a.WriteConst(offsetOfEIP, 32)
}
return
}
func (a *AssemblerImpl) EncodeRegisterToNone(n *NodeImpl) (err error) {
regBits, prefix, err := register3bits(n.SrcReg, registerSpecifierPositionModRMFieldRM)
if err != nil {
return err
}
// https://wiki.osdev.org/X86-64_Instruction_Encoding#ModR.2FM
modRM := 0b11_000_000 | // Specifying that opeand is register.
regBits
var opcode byte
switch n.Instruction {
case DIVL:
// https://www.felixcloutier.com/x86/div
modRM |= 0b00_110_000
opcode = 0xf7
case DIVQ:
// https://www.felixcloutier.com/x86/div
prefix |= RexPrefixW
modRM |= 0b00_110_000
opcode = 0xf7
case IDIVL:
// https://www.felixcloutier.com/x86/idiv
modRM |= 0b00_111_000
opcode = 0xf7
case IDIVQ:
// https://www.felixcloutier.com/x86/idiv
prefix |= RexPrefixW
modRM |= 0b00_111_000
opcode = 0xf7
case MULL:
// https://www.felixcloutier.com/x86/mul
modRM |= 0b00_100_000
opcode = 0xf7
case MULQ:
// https://www.felixcloutier.com/x86/mul
prefix |= RexPrefixW
modRM |= 0b00_100_000
opcode = 0xf7
default:
err = errorEncodingUnsupported(n)
}
if prefix != RexPrefixNone {
a.Buf.WriteByte(prefix)
}
a.Buf.Write([]byte{opcode, modRM})
return
}
var registerToRegisterOpcode = map[asm.Instruction]struct {
opcode []byte
rPrefix RexPrefix
mandatoryPrefix byte
srcOnModRMReg bool
isSrc8bit bool
needArg bool
requireSrcFloat, requireDstFloat bool
}{
// https://www.felixcloutier.com/x86/add
ADDL: {opcode: []byte{0x1}, srcOnModRMReg: true},
ADDQ: {opcode: []byte{0x1}, rPrefix: RexPrefixW, srcOnModRMReg: true},
// https://www.felixcloutier.com/x86/and
ANDL: {opcode: []byte{0x21}, srcOnModRMReg: true},
ANDQ: {opcode: []byte{0x21}, rPrefix: RexPrefixW, srcOnModRMReg: true},
// https://www.felixcloutier.com/x86/cmp
CMPL: {opcode: []byte{0x39}},
CMPQ: {opcode: []byte{0x39}, rPrefix: RexPrefixW},
// https://www.felixcloutier.com/x86/cmovcc
CMOVQCS: {opcode: []byte{0x0f, 0x42}, rPrefix: RexPrefixW},
// https://www.felixcloutier.com/x86/addsd
ADDSD: {mandatoryPrefix: 0xf2, opcode: []byte{0x0f, 0x58}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/addss
ADDSS: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x58}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/addpd
ANDPD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x54}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/addps
ANDPS: {opcode: []byte{0x0f, 0x54}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/bsr
BSRL: {opcode: []byte{0xf, 0xbd}},
BSRQ: {opcode: []byte{0xf, 0xbd}, rPrefix: RexPrefixW},
// https://www.felixcloutier.com/x86/comisd
COMISD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x2f}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/comiss
COMISS: {opcode: []byte{0x0f, 0x2f}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/cvtsd2ss
CVTSD2SS: {mandatoryPrefix: 0xf2, opcode: []byte{0x0f, 0x5a}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/cvtsi2sd
CVTSL2SD: {mandatoryPrefix: 0xf2, opcode: []byte{0x0f, 0x2a}, requireDstFloat: true},
// https://www.felixcloutier.com/x86/cvtsi2sd
CVTSQ2SD: {mandatoryPrefix: 0xf2, opcode: []byte{0x0f, 0x2a}, rPrefix: RexPrefixW, requireDstFloat: true},
// https://www.felixcloutier.com/x86/cvtsi2ss
CVTSL2SS: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x2a}, requireDstFloat: true},
// https://www.felixcloutier.com/x86/cvtsi2ss
CVTSQ2SS: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x2a}, rPrefix: RexPrefixW, requireDstFloat: true},
// https://www.felixcloutier.com/x86/cvtss2sd
CVTSS2SD: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x5a}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/cvttsd2si
CVTTSD2SL: {mandatoryPrefix: 0xf2, opcode: []byte{0x0f, 0x2c}, requireSrcFloat: true},
CVTTSD2SQ: {mandatoryPrefix: 0xf2, opcode: []byte{0x0f, 0x2c}, rPrefix: RexPrefixW, requireSrcFloat: true},
// https://www.felixcloutier.com/x86/cvttss2si
CVTTSS2SL: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x2c}, requireSrcFloat: true},
CVTTSS2SQ: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x2c}, rPrefix: RexPrefixW, requireSrcFloat: true},
// https://www.felixcloutier.com/x86/divsd
DIVSD: {mandatoryPrefix: 0xf2, opcode: []byte{0x0f, 0x5e}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/divss
DIVSS: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x5e}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/lzcnt
LZCNTL: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0xbd}},
LZCNTQ: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0xbd}, rPrefix: RexPrefixW},
// https://www.felixcloutier.com/x86/maxsd
MAXSD: {mandatoryPrefix: 0xf2, opcode: []byte{0x0f, 0x5f}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/maxss
MAXSS: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x5f}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/minsd
MINSD: {mandatoryPrefix: 0xf2, opcode: []byte{0x0f, 0x5d}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/minss
MINSS: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x5d}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/movsx:movsxd
MOVBLSX: {opcode: []byte{0x0f, 0xbe}, isSrc8bit: true},
// https://www.felixcloutier.com/x86/movzx
MOVBLZX: {opcode: []byte{0x0f, 0xb6}, isSrc8bit: true},
// https://www.felixcloutier.com/x86/movzx
MOVWLZX: {opcode: []byte{0x0f, 0xb7}, isSrc8bit: true},
// https://www.felixcloutier.com/x86/movsx:movsxd
MOVBQSX: {opcode: []byte{0x0f, 0xbe}, rPrefix: RexPrefixW, isSrc8bit: true},
// https://www.felixcloutier.com/x86/movsx:movsxd
MOVLQSX: {opcode: []byte{0x63}, rPrefix: RexPrefixW},
// https://www.felixcloutier.com/x86/movsx:movsxd
MOVWQSX: {opcode: []byte{0x0f, 0xbf}, rPrefix: RexPrefixW},
// https://www.felixcloutier.com/x86/movsx:movsxd
MOVWLSX: {opcode: []byte{0x0f, 0xbf}},
// https://www.felixcloutier.com/x86/mulss
MULSS: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x59}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/mulsd
MULSD: {mandatoryPrefix: 0xf2, opcode: []byte{0x0f, 0x59}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/or
ORL: {opcode: []byte{0x09}, srcOnModRMReg: true},
ORQ: {opcode: []byte{0x09}, rPrefix: RexPrefixW, srcOnModRMReg: true},
// https://www.felixcloutier.com/x86/orpd
ORPD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x56}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/orps
ORPS: {opcode: []byte{0x0f, 0x56}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/popcnt
POPCNTL: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0xb8}},
POPCNTQ: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0xb8}, rPrefix: RexPrefixW},
// https://www.felixcloutier.com/x86/roundss
ROUNDSS: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x3a, 0x0a}, needArg: true, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/roundsd
ROUNDSD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x3a, 0x0b}, needArg: true, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/sqrtss
SQRTSS: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x51}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/sqrtsd
SQRTSD: {mandatoryPrefix: 0xf2, opcode: []byte{0x0f, 0x51}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/sub
SUBL: {opcode: []byte{0x29}, srcOnModRMReg: true},
SUBQ: {opcode: []byte{0x29}, rPrefix: RexPrefixW, srcOnModRMReg: true},
// https://www.felixcloutier.com/x86/subss
SUBSS: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x5c}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/subsd
SUBSD: {mandatoryPrefix: 0xf2, opcode: []byte{0x0f, 0x5c}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/test
TESTL: {opcode: []byte{0x85}, srcOnModRMReg: true},
TESTQ: {opcode: []byte{0x85}, rPrefix: RexPrefixW, srcOnModRMReg: true},
// https://www.felixcloutier.com/x86/tzcnt
TZCNTL: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0xbc}},
TZCNTQ: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0xbc}, rPrefix: RexPrefixW},
// https://www.felixcloutier.com/x86/ucomisd
UCOMISD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x2e}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/ucomiss
UCOMISS: {opcode: []byte{0x0f, 0x2e}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/xor
XORL: {opcode: []byte{0x31}, srcOnModRMReg: true},
XORQ: {opcode: []byte{0x31}, rPrefix: RexPrefixW, srcOnModRMReg: true},
// https://www.felixcloutier.com/x86/xorpd
XORPD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x57}, requireSrcFloat: true, requireDstFloat: true},
XORPS: {opcode: []byte{0x0f, 0x57}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/pinsrb:pinsrd:pinsrq
PINSRB: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x3a, 0x20}, requireSrcFloat: false, requireDstFloat: true, needArg: true},
// https://www.felixcloutier.com/x86/pinsrw
PINSRW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xc4}, requireSrcFloat: false, requireDstFloat: true, needArg: true},
// https://www.felixcloutier.com/x86/pinsrb:pinsrd:pinsrq
PINSRD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x3a, 0x22}, requireSrcFloat: false, requireDstFloat: true, needArg: true},
// https://www.felixcloutier.com/x86/pinsrb:pinsrd:pinsrq
PINSRQ: {mandatoryPrefix: 0x66, rPrefix: RexPrefixW, opcode: []byte{0x0f, 0x3a, 0x22}, requireSrcFloat: false, requireDstFloat: true, needArg: true},
// https://www.felixcloutier.com/x86/movdqu:vmovdqu8:vmovdqu16:vmovdqu32:vmovdqu64
MOVDQU: {mandatoryPrefix: 0xf3, opcode: []byte{0x0f, 0x6f}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/movdqa:vmovdqa32:vmovdqa64
MOVDQA: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x6f}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/paddb:paddw:paddd:paddq
PADDB: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xfc}, requireSrcFloat: true, requireDstFloat: true},
PADDW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xfd}, requireSrcFloat: true, requireDstFloat: true},
PADDL: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xfe}, requireSrcFloat: true, requireDstFloat: true},
PADDQ: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xd4}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/psubb:psubw:psubd
PSUBB: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xf8}, requireSrcFloat: true, requireDstFloat: true},
PSUBW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xf9}, requireSrcFloat: true, requireDstFloat: true},
PSUBL: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xfa}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/psubq
PSUBQ: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xfb}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/addps
ADDPS: {opcode: []byte{0x0f, 0x58}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/addpd
ADDPD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x58}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/subps
SUBPS: {opcode: []byte{0x0f, 0x5c}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/subpd
SUBPD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x5c}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/pxor
PXOR: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xef}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/pshufb
PSHUFB: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x38, 0x0}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/pshufd
PSHUFD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x70}, requireSrcFloat: true, requireDstFloat: true, needArg: true},
// https://www.felixcloutier.com/x86/pextrb:pextrd:pextrq
PEXTRB: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x3a, 0x14}, requireSrcFloat: true, requireDstFloat: false, needArg: true, srcOnModRMReg: true},
// https://www.felixcloutier.com/x86/pextrw
PEXTRW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xc5}, requireSrcFloat: true, requireDstFloat: false, needArg: true},
// https://www.felixcloutier.com/x86/pextrb:pextrd:pextrq
PEXTRD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x3a, 0x16}, requireSrcFloat: true, requireDstFloat: false, needArg: true, srcOnModRMReg: true},
// https://www.felixcloutier.com/x86/pextrb:pextrd:pextrq
PEXTRQ: {rPrefix: RexPrefixW, mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x3a, 0x16}, requireSrcFloat: true, requireDstFloat: false, needArg: true, srcOnModRMReg: true},
// https://www.felixcloutier.com/x86/insertps
INSERTPS: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x3a, 0x21}, requireSrcFloat: true, requireDstFloat: true, needArg: true},
// https://www.felixcloutier.com/x86/movlhps
MOVLHPS: {opcode: []byte{0x0f, 0x16}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/ptest
PTEST: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x38, 0x17}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/pcmpeqb:pcmpeqw:pcmpeqd
PCMPEQB: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x74}, requireSrcFloat: true, requireDstFloat: true},
PCMPEQW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x75}, requireSrcFloat: true, requireDstFloat: true},
PCMPEQD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x76}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/pcmpeqq
PCMPEQQ: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x38, 0x29}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/paddusb:paddusw
PADDUSB: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xdc}, requireSrcFloat: true, requireDstFloat: true},
// https://www.felixcloutier.com/x86/movsd
MOVSD: {mandatoryPrefix: 0xf2, opcode: []byte{0x0f, 0x10}, requireSrcFloat: true, requireDstFloat: true},
}
var RegisterToRegisterShiftOpcode = map[asm.Instruction]struct {
opcode []byte
rPrefix RexPrefix
modRMExtension byte
}{
// https://www.felixcloutier.com/x86/rcl:rcr:rol:ror
ROLL: {opcode: []byte{0xd3}},
ROLQ: {opcode: []byte{0xd3}, rPrefix: RexPrefixW},
RORL: {opcode: []byte{0xd3}, modRMExtension: 0b00_001_000},
RORQ: {opcode: []byte{0xd3}, modRMExtension: 0b00_001_000, rPrefix: RexPrefixW},
// https://www.felixcloutier.com/x86/sal:sar:shl:shr
SARL: {opcode: []byte{0xd3}, modRMExtension: 0b00_111_000},
SARQ: {opcode: []byte{0xd3}, modRMExtension: 0b00_111_000, rPrefix: RexPrefixW},
SHLL: {opcode: []byte{0xd3}, modRMExtension: 0b00_100_000},
SHLQ: {opcode: []byte{0xd3}, modRMExtension: 0b00_100_000, rPrefix: RexPrefixW},
SHRL: {opcode: []byte{0xd3}, modRMExtension: 0b00_101_000},
SHRQ: {opcode: []byte{0xd3}, modRMExtension: 0b00_101_000, rPrefix: RexPrefixW},
}
type registerToRegisterMOVOpcode struct {
opcode []byte
mandatoryPrefix byte
srcOnModRMReg bool
rPrefix RexPrefix
}
var registerToRegisterMOVOpcodes = map[asm.Instruction]struct {
i2i, i2f, f2i, f2f registerToRegisterMOVOpcode
}{
MOVL: {
// https://www.felixcloutier.com/x86/mov
i2i: registerToRegisterMOVOpcode{opcode: []byte{0x89}, srcOnModRMReg: true},
// https://www.felixcloutier.com/x86/movd:movq
i2f: registerToRegisterMOVOpcode{opcode: []byte{0x0f, 0x6e}, mandatoryPrefix: 0x66, srcOnModRMReg: false},
f2i: registerToRegisterMOVOpcode{opcode: []byte{0x0f, 0x7e}, mandatoryPrefix: 0x66, srcOnModRMReg: true},
},
MOVQ: {
// https://www.felixcloutier.com/x86/mov
i2i: registerToRegisterMOVOpcode{opcode: []byte{0x89}, srcOnModRMReg: true, rPrefix: RexPrefixW},
// https://www.felixcloutier.com/x86/movd:movq
i2f: registerToRegisterMOVOpcode{opcode: []byte{0x0f, 0x6e}, mandatoryPrefix: 0x66, srcOnModRMReg: false, rPrefix: RexPrefixW},
f2i: registerToRegisterMOVOpcode{opcode: []byte{0x0f, 0x7e}, mandatoryPrefix: 0x66, srcOnModRMReg: true, rPrefix: RexPrefixW},
// https://www.felixcloutier.com/x86/movq
f2f: registerToRegisterMOVOpcode{opcode: []byte{0x0f, 0x7e}, mandatoryPrefix: 0xf3},
},
}
func (a *AssemblerImpl) EncodeRegisterToRegister(n *NodeImpl) (err error) {
// Alias for readability
inst := n.Instruction
if op, ok := registerToRegisterMOVOpcodes[inst]; ok {
var opcode registerToRegisterMOVOpcode
srcIsFloat, dstIsFloat := IsVectorRegister(n.SrcReg), IsVectorRegister(n.DstReg)
if srcIsFloat && dstIsFloat {
if inst == MOVL {
return errors.New("MOVL for float to float is undefined")
}
opcode = op.f2f
} else if srcIsFloat && !dstIsFloat {
opcode = op.f2i
} else if !srcIsFloat && dstIsFloat {
opcode = op.i2f
} else {
opcode = op.i2i
}
rexPrefix, modRM, err := n.GetRegisterToRegisterModRM(opcode.srcOnModRMReg)
if err != nil {
return err
}
rexPrefix |= opcode.rPrefix
if opcode.mandatoryPrefix != 0 {
a.Buf.WriteByte(opcode.mandatoryPrefix)
}
if rexPrefix != RexPrefixNone {
a.Buf.WriteByte(rexPrefix)
}
a.Buf.Write(opcode.opcode)
a.Buf.WriteByte(modRM)
return nil
} else if op, ok := registerToRegisterOpcode[inst]; ok {
srcIsFloat, dstIsFloat := IsVectorRegister(n.SrcReg), IsVectorRegister(n.DstReg)
if op.requireSrcFloat && !srcIsFloat {
return fmt.Errorf("%s require float src register but got %s", InstructionName(inst), RegisterName(n.SrcReg))
} else if op.requireDstFloat && !dstIsFloat {
return fmt.Errorf("%s require float dst register but got %s", InstructionName(inst), RegisterName(n.DstReg))
} else if !op.requireSrcFloat && srcIsFloat {
return fmt.Errorf("%s require integer src register but got %s", InstructionName(inst), RegisterName(n.SrcReg))
} else if !op.requireDstFloat && dstIsFloat {
return fmt.Errorf("%s require integer dst register but got %s", InstructionName(inst), RegisterName(n.DstReg))
}
rexPrefix, modRM, err := n.GetRegisterToRegisterModRM(op.srcOnModRMReg)
if err != nil {
return err
}
rexPrefix |= op.rPrefix
if op.isSrc8bit && RegSP <= n.SrcReg && n.SrcReg <= RegDI {
// If an operand register is 8-bit length of SP, BP, DI, or SI register, we need to have the default prefix.
// https: //wiki.osdev.org/X86-64_Instruction_Encoding#Registers
rexPrefix |= RexPrefixDefault
}
if op.mandatoryPrefix != 0 {
a.Buf.WriteByte(op.mandatoryPrefix)
}
if rexPrefix != RexPrefixNone {
a.Buf.WriteByte(rexPrefix)
}
a.Buf.Write(op.opcode)
a.Buf.WriteByte(modRM)
if op.needArg {
a.WriteConst(int64(n.Arg), 8)
}
return nil
} else if op, ok := RegisterToRegisterShiftOpcode[inst]; ok {
if n.SrcReg != RegCX {
return fmt.Errorf("shifting instruction %s require CX register as src but got %s", InstructionName(inst), RegisterName(n.SrcReg))
} else if IsVectorRegister(n.DstReg) {
return fmt.Errorf("shifting instruction %s require integer register as dst but got %s", InstructionName(inst), RegisterName(n.SrcReg))
}
reg3bits, rexPrefix, err := register3bits(n.DstReg, registerSpecifierPositionModRMFieldRM)
if err != nil {
return err
}
rexPrefix |= op.rPrefix
if rexPrefix != RexPrefixNone {
a.Buf.WriteByte(rexPrefix)
}
// https://wiki.osdev.org/X86-64_Instruction_Encoding#ModR.2FM
modRM := 0b11_000_000 |
(op.modRMExtension) |
reg3bits
a.Buf.Write(append(op.opcode, modRM))
return nil
} else {
return errorEncodingUnsupported(n)
}
}
func (a *AssemblerImpl) EncodeRegisterToMemory(n *NodeImpl) (err error) {
rexPrefix, modRM, sbi, displacementWidth, err := n.GetMemoryLocation()
if err != nil {
return err
}
var opcode []byte
var mandatoryPrefix byte
var isShiftInstruction bool
var needArg bool
switch n.Instruction {
case CMPL:
// https://www.felixcloutier.com/x86/cmp
opcode = []byte{0x3b}
case CMPQ:
// https://www.felixcloutier.com/x86/cmp
rexPrefix |= RexPrefixW
opcode = []byte{0x3b}
case MOVB:
// https://www.felixcloutier.com/x86/mov
opcode = []byte{0x88}
// 1 byte register operands need default prefix for the following registers.
if n.SrcReg >= RegSP && n.SrcReg <= RegDI {
rexPrefix |= RexPrefixDefault
}
case MOVL:
if IsVectorRegister(n.SrcReg) {
// https://www.felixcloutier.com/x86/movd:movq
opcode = []byte{0x0f, 0x7e}
mandatoryPrefix = 0x66
} else {
// https://www.felixcloutier.com/x86/mov
opcode = []byte{0x89}
}
case MOVQ:
if IsVectorRegister(n.SrcReg) {
// https://www.felixcloutier.com/x86/movq
opcode = []byte{0x0f, 0xd6}
mandatoryPrefix = 0x66
} else {
// https://www.felixcloutier.com/x86/mov
rexPrefix |= RexPrefixW
opcode = []byte{0x89}
}
case MOVW:
// https://www.felixcloutier.com/x86/mov
// Note: Need 0x66 to indicate that the operand size is 16-bit.
// https://wiki.osdev.org/X86-64_Instruction_Encoding#Operand-size_and_address-size_override_prefix
mandatoryPrefix = 0x66
opcode = []byte{0x89}
case SARL:
// https://www.felixcloutier.com/x86/sal:sar:shl:shr
modRM |= 0b00_111_000
opcode = []byte{0xd3}
isShiftInstruction = true
case SARQ:
// https://www.felixcloutier.com/x86/sal:sar:shl:shr
rexPrefix |= RexPrefixW
modRM |= 0b00_111_000
opcode = []byte{0xd3}
isShiftInstruction = true
case SHLL:
// https://www.felixcloutier.com/x86/sal:sar:shl:shr
modRM |= 0b00_100_000
opcode = []byte{0xd3}
isShiftInstruction = true
case SHLQ:
// https://www.felixcloutier.com/x86/sal:sar:shl:shr
rexPrefix |= RexPrefixW
modRM |= 0b00_100_000
opcode = []byte{0xd3}
isShiftInstruction = true
case SHRL:
// https://www.felixcloutier.com/x86/sal:sar:shl:shr
modRM |= 0b00_101_000
opcode = []byte{0xd3}
isShiftInstruction = true
case SHRQ:
// https://www.felixcloutier.com/x86/sal:sar:shl:shr
rexPrefix |= RexPrefixW
modRM |= 0b00_101_000
opcode = []byte{0xd3}
isShiftInstruction = true
case ROLL:
// https://www.felixcloutier.com/x86/rcl:rcr:rol:ror
opcode = []byte{0xd3}
isShiftInstruction = true
case ROLQ:
// https://www.felixcloutier.com/x86/rcl:rcr:rol:ror
rexPrefix |= RexPrefixW
opcode = []byte{0xd3}
isShiftInstruction = true
case RORL:
// https://www.felixcloutier.com/x86/rcl:rcr:rol:ror
modRM |= 0b00_001_000
opcode = []byte{0xd3}
isShiftInstruction = true
case RORQ:
// https://www.felixcloutier.com/x86/rcl:rcr:rol:ror
rexPrefix |= RexPrefixW
opcode = []byte{0xd3}
modRM |= 0b00_001_000
isShiftInstruction = true
case MOVDQU:
// https://www.felixcloutier.com/x86/movdqu:vmovdqu8:vmovdqu16:vmovdqu32:vmovdqu64
mandatoryPrefix = 0xf3
opcode = []byte{0x0f, 0x7f}
case PEXTRB: // https://www.felixcloutier.com/x86/pextrb:pextrd:pextrq
mandatoryPrefix = 0x66
opcode = []byte{0x0f, 0x3a, 0x14}
needArg = true
case PEXTRW: // https://www.felixcloutier.com/x86/pextrw
mandatoryPrefix = 0x66
opcode = []byte{0x0f, 0x3a, 0x15}
needArg = true
case PEXTRD: // https://www.felixcloutier.com/x86/pextrb:pextrd:pextrq
mandatoryPrefix = 0x66
opcode = []byte{0x0f, 0x3a, 0x16}
needArg = true
case PEXTRQ: // https://www.felixcloutier.com/x86/pextrb:pextrd:pextrq
mandatoryPrefix = 0x66
rexPrefix |= RexPrefixW // REX.W
opcode = []byte{0x0f, 0x3a, 0x16}
needArg = true
default:
return errorEncodingUnsupported(n)
}
if !isShiftInstruction {
srcReg3Bits, prefix, err := register3bits(n.SrcReg, registerSpecifierPositionModRMFieldReg)
if err != nil {
return err
}
rexPrefix |= prefix
modRM |= srcReg3Bits << 3 // Place the source register on ModRM:reg
} else {
if n.SrcReg != RegCX {
return fmt.Errorf("shifting instruction %s require CX register as src but got %s", InstructionName(n.Instruction), RegisterName(n.SrcReg))
}
}
if mandatoryPrefix != 0 {
// https://wiki.osdev.org/X86-64_Instruction_Encoding#Mandatory_prefix
a.Buf.WriteByte(mandatoryPrefix)
}
if rexPrefix != RexPrefixNone {
a.Buf.WriteByte(rexPrefix)
}
a.Buf.Write(opcode)
a.Buf.WriteByte(modRM)
if sbi != nil {
a.Buf.WriteByte(*sbi)
}
if displacementWidth != 0 {
a.WriteConst(n.DstConst, displacementWidth)
}
if needArg {
a.WriteConst(int64(n.Arg), 8)
}
return
}
func (a *AssemblerImpl) EncodeRegisterToConst(n *NodeImpl) (err error) {
regBits, prefix, err := register3bits(n.SrcReg, registerSpecifierPositionModRMFieldRM)
if err != nil {
return err
}
switch n.Instruction {
case CMPL, CMPQ:
if n.Instruction == CMPQ {
prefix |= RexPrefixW
}
if prefix != RexPrefixNone {
a.Buf.WriteByte(prefix)
}
is8bitConst := fitInSigned8bit(n.DstConst)
// https://www.felixcloutier.com/x86/cmp
if n.SrcReg == RegAX && !is8bitConst {
a.Buf.Write([]byte{0x3d})
} else {
// https://wiki.osdev.org/X86-64_Instruction_Encoding#ModR.2FM
modRM := 0b11_000_000 | // Specifying that opeand is register.
0b00_111_000 | // CMP with immediate needs "/7" extension.
regBits
if is8bitConst {
a.Buf.Write([]byte{0x83, modRM})
} else {
a.Buf.Write([]byte{0x81, modRM})
}
}
default:
err = errorEncodingUnsupported(n)
}
if fitInSigned8bit(n.DstConst) {
a.WriteConst(n.DstConst, 8)
} else {
a.WriteConst(n.DstConst, 32)
}
return
}
func (a *AssemblerImpl) encodeReadInstructionAddress(n *NodeImpl) error {
dstReg3Bits, rexPrefix, err := register3bits(n.DstReg, registerSpecifierPositionModRMFieldReg)
if err != nil {
return err
}
a.AddOnGenerateCallBack(func(code []byte) error {
// Find the target instruction node.
targetNode := n
for ; targetNode != nil; targetNode = targetNode.Next {
if targetNode.Instruction == n.readInstructionAddressBeforeTargetInstruction {
targetNode = targetNode.Next
break
}
}
if targetNode == nil {
return errors.New("BUG: target instruction not found for read instruction address")
}
offset := targetNode.OffsetInBinary() - (n.OffsetInBinary() + 7 /* 7 = the length of the LEAQ instruction */)
if offset >= math.MaxInt32 {
return errors.New("BUG: too large offset for LEAQ instruction")
}
binary.LittleEndian.PutUint32(code[n.OffsetInBinary()+3:], uint32(int32(offset)))
return nil
})
// https://www.felixcloutier.com/x86/lea
opcode := byte(0x8d)
rexPrefix |= RexPrefixW
// https://wiki.osdev.org/X86-64_Instruction_Encoding#32.2F64-bit_addressing
modRM := 0b00_000_101 | // Indicate "LEAQ [RIP + 32bit displacement], DstReg" encoding.
(dstReg3Bits << 3) // Place the DstReg on ModRM:reg.
a.Buf.Write([]byte{rexPrefix, opcode, modRM})
a.WriteConst(int64(0), 32) // Preserve
return nil
}
func (a *AssemblerImpl) EncodeMemoryToRegister(n *NodeImpl) (err error) {
if n.Instruction == LEAQ && n.readInstructionAddressBeforeTargetInstruction != NONE {
return a.encodeReadInstructionAddress(n)
}
rexPrefix, modRM, sbi, displacementWidth, err := n.GetMemoryLocation()
if err != nil {
return err
}
dstReg3Bits, prefix, err := register3bits(n.DstReg, registerSpecifierPositionModRMFieldReg)
if err != nil {
return err
}
rexPrefix |= prefix
modRM |= dstReg3Bits << 3 // Place the destination register on ModRM:reg
var mandatoryPrefix byte
var opcode []byte
var needArg bool
switch n.Instruction {
case ADDL:
// https://www.felixcloutier.com/x86/add
opcode = []byte{0x03}
case ADDQ:
// https://www.felixcloutier.com/x86/add
rexPrefix |= RexPrefixW
opcode = []byte{0x03}
case CMPL:
// https://www.felixcloutier.com/x86/cmp
opcode = []byte{0x39}
case CMPQ:
// https://www.felixcloutier.com/x86/cmp
rexPrefix |= RexPrefixW
opcode = []byte{0x39}
case LEAQ:
// https://www.felixcloutier.com/x86/lea
rexPrefix |= RexPrefixW
opcode = []byte{0x8d}
case MOVBLSX:
// https://www.felixcloutier.com/x86/movsx:movsxd
opcode = []byte{0x0f, 0xbe}
case MOVBLZX:
// https://www.felixcloutier.com/x86/movzx
opcode = []byte{0x0f, 0xb6}
case MOVBQSX:
// https://www.felixcloutier.com/x86/movsx:movsxd
rexPrefix |= RexPrefixW
opcode = []byte{0x0f, 0xbe}
case MOVBQZX:
// https://www.felixcloutier.com/x86/movzx
rexPrefix |= RexPrefixW
opcode = []byte{0x0f, 0xb6}
case MOVLQSX:
// https://www.felixcloutier.com/x86/movsx:movsxd
rexPrefix |= RexPrefixW
opcode = []byte{0x63}
case MOVLQZX:
// https://www.felixcloutier.com/x86/mov
// Note: MOVLQZX means zero extending 32bit reg to 64-bit reg and
// that is semantically equivalent to MOV 32bit to 32bit.
opcode = []byte{0x8B}
case MOVL:
// https://www.felixcloutier.com/x86/mov
// Note: MOVLQZX means zero extending 32bit reg to 64-bit reg and
// that is semantically equivalent to MOV 32bit to 32bit.
if IsVectorRegister(n.DstReg) {
// https://www.felixcloutier.com/x86/movd:movq
opcode = []byte{0x0f, 0x6e}
mandatoryPrefix = 0x66
} else {
// https://www.felixcloutier.com/x86/mov
opcode = []byte{0x8B}
}
case MOVQ:
if IsVectorRegister(n.DstReg) {
// https://www.felixcloutier.com/x86/movq
opcode = []byte{0x0f, 0x7e}
mandatoryPrefix = 0xf3
} else {
// https://www.felixcloutier.com/x86/mov
rexPrefix |= RexPrefixW
opcode = []byte{0x8B}
}
case MOVWLSX:
// https://www.felixcloutier.com/x86/movsx:movsxd
opcode = []byte{0x0f, 0xbf}
case MOVWLZX:
// https://www.felixcloutier.com/x86/movzx
opcode = []byte{0x0f, 0xb7}
case MOVWQSX:
// https://www.felixcloutier.com/x86/movsx:movsxd
rexPrefix |= RexPrefixW
opcode = []byte{0x0f, 0xbf}
case MOVWQZX:
// https://www.felixcloutier.com/x86/movzx
rexPrefix |= RexPrefixW
opcode = []byte{0x0f, 0xb7}
case SUBQ:
// https://www.felixcloutier.com/x86/sub
rexPrefix |= RexPrefixW
opcode = []byte{0x2b}
case SUBSD:
// https://www.felixcloutier.com/x86/subsd
opcode = []byte{0x0f, 0x5c}
mandatoryPrefix = 0xf2
case SUBSS:
// https://www.felixcloutier.com/x86/subss
opcode = []byte{0x0f, 0x5c}
mandatoryPrefix = 0xf3
case UCOMISD:
// https://www.felixcloutier.com/x86/ucomisd
opcode = []byte{0x0f, 0x2e}
mandatoryPrefix = 0x66
case UCOMISS:
// https://www.felixcloutier.com/x86/ucomiss
opcode = []byte{0x0f, 0x2e}
case MOVDQU:
// https://www.felixcloutier.com/x86/movdqu:vmovdqu8:vmovdqu16:vmovdqu32:vmovdqu64
mandatoryPrefix = 0xf3
opcode = []byte{0x0f, 0x6f}
case PMOVSXBW: // https://www.felixcloutier.com/x86/pmovsx
mandatoryPrefix = 0x66
opcode = []byte{0x0f, 0x38, 0x20}
case PMOVSXWD: // https://www.felixcloutier.com/x86/pmovsx
mandatoryPrefix = 0x66
opcode = []byte{0x0f, 0x38, 0x23}
case PMOVSXDQ: // https://www.felixcloutier.com/x86/pmovsx
mandatoryPrefix = 0x66
opcode = []byte{0x0f, 0x38, 0x25}
case PMOVZXBW: // https://www.felixcloutier.com/x86/pmovzx
mandatoryPrefix = 0x66
opcode = []byte{0x0f, 0x38, 0x30}
case PMOVZXWD: // https://www.felixcloutier.com/x86/pmovzx
mandatoryPrefix = 0x66
opcode = []byte{0x0f, 0x38, 0x33}
case PMOVZXDQ: // https://www.felixcloutier.com/x86/pmovzx
mandatoryPrefix = 0x66
opcode = []byte{0x0f, 0x38, 0x35}
case PINSRB: // https://www.felixcloutier.com/x86/pinsrb:pinsrd:pinsrq
mandatoryPrefix = 0x66
opcode = []byte{0x0f, 0x3a, 0x20}
needArg = true
case PINSRW: // https://www.felixcloutier.com/x86/pinsrw
mandatoryPrefix = 0x66
opcode = []byte{0x0f, 0xc4}
needArg = true
case PINSRD: // https://www.felixcloutier.com/x86/pinsrb:pinsrd:pinsrq
mandatoryPrefix = 0x66
opcode = []byte{0x0f, 0x3a, 0x22}
needArg = true
case PINSRQ: // https://www.felixcloutier.com/x86/pinsrb:pinsrd:pinsrq
rexPrefix |= RexPrefixW
mandatoryPrefix = 0x66
opcode = []byte{0x0f, 0x3a, 0x22}
needArg = true
default:
return errorEncodingUnsupported(n)
}
if mandatoryPrefix != 0 {
// https://wiki.osdev.org/X86-64_Instruction_Encoding#Mandatory_prefix
a.Buf.WriteByte(mandatoryPrefix)
}
if rexPrefix != RexPrefixNone {
a.Buf.WriteByte(rexPrefix)
}
a.Buf.Write(opcode)
a.Buf.WriteByte(modRM)
if sbi != nil {
a.Buf.WriteByte(*sbi)
}
if displacementWidth != 0 {
a.WriteConst(n.SrcConst, displacementWidth)
}
if needArg {
a.WriteConst(int64(n.Arg), 8)
}
return
}
func (a *AssemblerImpl) EncodeConstToRegister(n *NodeImpl) (err error) {
regBits, rexPrefix, err := register3bits(n.DstReg, registerSpecifierPositionModRMFieldRM)
if err != nil {
return err
}
isFloatReg := IsVectorRegister(n.DstReg)
switch n.Instruction {
case PSLLL, PSLLQ, PSRLL, PSRLQ:
if !isFloatReg {
return fmt.Errorf("%s needs float register but got %s", InstructionName(n.Instruction), RegisterName(n.DstReg))
}
default:
if isFloatReg {
return fmt.Errorf("%s needs int register but got %s", InstructionName(n.Instruction), RegisterName(n.DstReg))
}
}
if n.Instruction != MOVQ && !FitIn32bit(n.SrcConst) {
return fmt.Errorf("constant must fit in 32-bit integer for %s, but got %d", InstructionName(n.Instruction), n.SrcConst)
} else if (n.Instruction == SHLQ || n.Instruction == SHRQ) && (n.SrcConst < 0 || n.SrcConst > math.MaxUint8) {
return fmt.Errorf("constant must fit in positive 8-bit integer for %s, but got %d", InstructionName(n.Instruction), n.SrcConst)
} else if (n.Instruction == PSLLL ||
n.Instruction == PSLLQ ||
n.Instruction == PSRLL ||
n.Instruction == PSRLQ) && (n.SrcConst < math.MinInt8 || n.SrcConst > math.MaxInt8) {
return fmt.Errorf("constant must fit in signed 8-bit integer for %s, but got %d", InstructionName(n.Instruction), n.SrcConst)
}
isSigned8bitConst := fitInSigned8bit(n.SrcConst)
switch inst := n.Instruction; inst {
case ADDQ:
// https://www.felixcloutier.com/x86/add
rexPrefix |= RexPrefixW
if n.DstReg == RegAX && !isSigned8bitConst {
a.Buf.Write([]byte{rexPrefix, 0x05})
} else {
modRM := 0b11_000_000 | // Specifying that opeand is register.
regBits
if isSigned8bitConst {
a.Buf.Write([]byte{rexPrefix, 0x83, modRM})
} else {
a.Buf.Write([]byte{rexPrefix, 0x81, modRM})
}
}
if isSigned8bitConst {
a.WriteConst(n.SrcConst, 8)
} else {
a.WriteConst(n.SrcConst, 32)
}
case ANDQ:
// https://www.felixcloutier.com/x86/and
rexPrefix |= RexPrefixW
if n.DstReg == RegAX && !isSigned8bitConst {
a.Buf.Write([]byte{rexPrefix, 0x25})
} else {
modRM := 0b11_000_000 | // Specifying that opeand is register.
0b00_100_000 | // AND with immediate needs "/4" extension.
regBits
if isSigned8bitConst {
a.Buf.Write([]byte{rexPrefix, 0x83, modRM})
} else {
a.Buf.Write([]byte{rexPrefix, 0x81, modRM})
}
}
if fitInSigned8bit(n.SrcConst) {
a.WriteConst(n.SrcConst, 8)
} else {
a.WriteConst(n.SrcConst, 32)
}
case MOVL:
// https://www.felixcloutier.com/x86/mov
if rexPrefix != RexPrefixNone {
a.Buf.WriteByte(rexPrefix)
}
a.Buf.Write([]byte{0xb8 | regBits})
a.WriteConst(n.SrcConst, 32)
case MOVQ:
// https://www.felixcloutier.com/x86/mov
if FitIn32bit(n.SrcConst) {
if n.SrcConst > math.MaxInt32 {
if rexPrefix != RexPrefixNone {
a.Buf.WriteByte(rexPrefix)
}
a.Buf.Write([]byte{0xb8 | regBits})
} else {
rexPrefix |= RexPrefixW
modRM := 0b11_000_000 | // Specifying that opeand is register.
regBits
a.Buf.Write([]byte{rexPrefix, 0xc7, modRM})
}
a.WriteConst(n.SrcConst, 32)
} else {
rexPrefix |= RexPrefixW
a.Buf.Write([]byte{rexPrefix, 0xb8 | regBits})
a.WriteConst(n.SrcConst, 64)
}
case SHLQ:
// https://www.felixcloutier.com/x86/sal:sar:shl:shr
rexPrefix |= RexPrefixW
modRM := 0b11_000_000 | // Specifying that opeand is register.
0b00_100_000 | // SHL with immediate needs "/4" extension.
regBits
if n.SrcConst == 1 {
a.Buf.Write([]byte{rexPrefix, 0xd1, modRM})
} else {
a.Buf.Write([]byte{rexPrefix, 0xc1, modRM})
a.WriteConst(n.SrcConst, 8)
}
case SHRQ:
// https://www.felixcloutier.com/x86/sal:sar:shl:shr
rexPrefix |= RexPrefixW
modRM := 0b11_000_000 | // Specifying that opeand is register.
0b00_101_000 | // SHR with immediate needs "/5" extension.
regBits
if n.SrcConst == 1 {
a.Buf.Write([]byte{rexPrefix, 0xd1, modRM})
} else {
a.Buf.Write([]byte{rexPrefix, 0xc1, modRM})
a.WriteConst(n.SrcConst, 8)
}
case PSLLL:
// https://www.felixcloutier.com/x86/psllw:pslld:psllq
modRM := 0b11_000_000 | // Specifying that opeand is register.
0b00_110_000 | // PSLL with immediate needs "/6" extension.
regBits
if rexPrefix != RexPrefixNone {
a.Buf.Write([]byte{0x66, rexPrefix, 0x0f, 0x72, modRM})
a.WriteConst(n.SrcConst, 8)
} else {
a.Buf.Write([]byte{0x66, 0x0f, 0x72, modRM})
a.WriteConst(n.SrcConst, 8)
}
case PSLLQ:
// https://www.felixcloutier.com/x86/psllw:pslld:psllq
modRM := 0b11_000_000 | // Specifying that opeand is register.
0b00_110_000 | // PSLL with immediate needs "/6" extension.
regBits
if rexPrefix != RexPrefixNone {
a.Buf.Write([]byte{0x66, rexPrefix, 0x0f, 0x73, modRM})
a.WriteConst(n.SrcConst, 8)
} else {
a.Buf.Write([]byte{0x66, 0x0f, 0x73, modRM})
a.WriteConst(n.SrcConst, 8)
}
case PSRLL:
// https://www.felixcloutier.com/x86/psrlw:psrld:psrlq
// https://www.felixcloutier.com/x86/psllw:pslld:psllq
modRM := 0b11_000_000 | // Specifying that opeand is register.
0b00_010_000 | // PSRL with immediate needs "/2" extension.
regBits
if rexPrefix != RexPrefixNone {
a.Buf.Write([]byte{0x66, rexPrefix, 0x0f, 0x72, modRM})
a.WriteConst(n.SrcConst, 8)
} else {
a.Buf.Write([]byte{0x66, 0x0f, 0x72, modRM})
a.WriteConst(n.SrcConst, 8)
}
case PSRLQ:
// https://www.felixcloutier.com/x86/psrlw:psrld:psrlq
modRM := 0b11_000_000 | // Specifying that opeand is register.
0b00_010_000 | // PSRL with immediate needs "/2" extension.
regBits
if rexPrefix != RexPrefixNone {
a.Buf.Write([]byte{0x66, rexPrefix, 0x0f, 0x73, modRM})
a.WriteConst(n.SrcConst, 8)
} else {
a.Buf.Write([]byte{0x66, 0x0f, 0x73, modRM})
a.WriteConst(n.SrcConst, 8)
}
case XORL, XORQ:
// https://www.felixcloutier.com/x86/xor
if inst == XORQ {
rexPrefix |= RexPrefixW
}
if rexPrefix != RexPrefixNone {
a.Buf.WriteByte(rexPrefix)
}
if n.DstReg == RegAX && !isSigned8bitConst {
a.Buf.Write([]byte{0x35})
} else {
modRM := 0b11_000_000 | // Specifying that opeand is register.
0b00_110_000 | // XOR with immediate needs "/6" extension.
regBits
if isSigned8bitConst {
a.Buf.Write([]byte{0x83, modRM})
} else {
a.Buf.Write([]byte{0x81, modRM})
}
}
if fitInSigned8bit(n.SrcConst) {
a.WriteConst(n.SrcConst, 8)
} else {
a.WriteConst(n.SrcConst, 32)
}
default:
err = errorEncodingUnsupported(n)
}
return
}
func (a *AssemblerImpl) EncodeMemoryToConst(n *NodeImpl) (err error) {
if !FitIn32bit(n.DstConst) {
return fmt.Errorf("too large target const %d for %s", n.DstConst, InstructionName(n.Instruction))
}
rexPrefix, modRM, sbi, displacementWidth, err := n.GetMemoryLocation()
if err != nil {
return err
}
// Alias for readability.
c := n.DstConst
var opcode, constWidth byte
switch n.Instruction {
case CMPL:
// https://www.felixcloutier.com/x86/cmp
if fitInSigned8bit(c) {
opcode = 0x83
constWidth = 8
} else {
opcode = 0x81
constWidth = 32
}
modRM |= 0b00_111_000
default:
return errorEncodingUnsupported(n)
}
if rexPrefix != RexPrefixNone {
a.Buf.WriteByte(rexPrefix)
}
a.Buf.Write([]byte{opcode, modRM})
if sbi != nil {
a.Buf.WriteByte(*sbi)
}
if displacementWidth != 0 {
a.WriteConst(n.SrcConst, displacementWidth)
}
a.WriteConst(c, constWidth)
return
}
func (a *AssemblerImpl) EncodeConstToMemory(n *NodeImpl) (err error) {
rexPrefix, modRM, sbi, displacementWidth, err := n.GetMemoryLocation()
if err != nil {
return err
}
// Alias for readability.
inst := n.Instruction
c := n.SrcConst
if inst == MOVB && !fitInSigned8bit(c) {
return fmt.Errorf("too large load target const %d for MOVB", c)
} else if !FitIn32bit(c) {
return fmt.Errorf("too large load target const %d for %s", c, InstructionName(n.Instruction))
}
var constWidth, opcode byte
switch inst {
case MOVB:
opcode = 0xc6
constWidth = 8
case MOVL:
opcode = 0xc7
constWidth = 32
case MOVQ:
rexPrefix |= RexPrefixW
opcode = 0xc7
constWidth = 32
default:
return errorEncodingUnsupported(n)
}
if rexPrefix != RexPrefixNone {
a.Buf.WriteByte(rexPrefix)
}
a.Buf.Write([]byte{opcode, modRM})
if sbi != nil {
a.Buf.WriteByte(*sbi)
}
if displacementWidth != 0 {
a.WriteConst(n.DstConst, displacementWidth)
}
a.WriteConst(c, constWidth)
return
}
func (a *AssemblerImpl) WriteConst(v int64, length byte) {
switch length {
case 8:
a.Buf.WriteByte(byte(int8(v)))
case 32:
// TODO: any way to directly put little endian bytes into bytes.Buffer?
offsetBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(offsetBytes, uint32(int32(v)))
a.Buf.Write(offsetBytes)
case 64:
// TODO: any way to directly put little endian bytes into bytes.Buffer?
offsetBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(offsetBytes, uint64(v))
a.Buf.Write(offsetBytes)
default:
panic("BUG: length must be one of 8, 32 or 64")
}
}
func (n *NodeImpl) GetMemoryLocation() (p RexPrefix, modRM byte, sbi *byte, displacementWidth byte, err error) {
var baseReg, indexReg asm.Register
var offset asm.ConstantValue
var scale byte
if n.Types.dst == OperandTypeMemory {
baseReg, offset, indexReg, scale = n.DstReg, n.DstConst, n.DstMemIndex, n.DstMemScale
} else if n.Types.src == OperandTypeMemory {
baseReg, offset, indexReg, scale = n.SrcReg, n.SrcConst, n.SrcMemIndex, n.SrcMemScale
} else {
err = fmt.Errorf("memory location is not supported for %s", n.Types)
return
}
if !FitIn32bit(offset) {
err = errors.New("offset does not fit in 32-bit integer")
return
}
if baseReg == asm.NilRegister && indexReg != asm.NilRegister {
// [(index*scale) + displacement] addressing is possible, but we haven't used it for now.
err = errors.New("addressing without base register but with index is not implemented")
} else if baseReg == asm.NilRegister {
modRM = 0b00_000_100 // Indicate that the memory location is specified by SIB.
sbiValue := byte(0b00_100_101)
sbi = &sbiValue
displacementWidth = 32
} else if indexReg == asm.NilRegister {
modRM, p, err = register3bits(baseReg, registerSpecifierPositionModRMFieldRM)
if err != nil {
return
}
// Create ModR/M byte so that this instruction takes [R/M + displacement] operand if displacement !=0
// and otherwise [R/M].
withoutDisplacement := offset == 0 &&
// If the target register is R13 or BP, we have to keep [R/M + displacement] even if the value
// is zero since it's not [R/M] operand is not defined for these two registers.
// https://wiki.osdev.org/X86-64_Instruction_Encoding#32.2F64-bit_addressing
baseReg != RegR13 && baseReg != RegBP
if withoutDisplacement {
// https://wiki.osdev.org/X86-64_Instruction_Encoding#ModR.2FM
modRM |= 0b00_000_000 // Specifying that operand is memory without displacement
displacementWidth = 0
} else if fitInSigned8bit(offset) {
// https://wiki.osdev.org/X86-64_Instruction_Encoding#ModR.2FM
modRM |= 0b01_000_000 // Specifying that operand is memory + 8bit displacement.
displacementWidth = 8
} else {
// https://wiki.osdev.org/X86-64_Instruction_Encoding#ModR.2FM
modRM |= 0b10_000_000 // Specifying that operand is memory + 32bit displacement.
displacementWidth = 32
}
// For SP and R12 register, we have [SIB + displacement] if the const is non-zero, otherwise [SIP].
// https://wiki.osdev.org/X86-64_Instruction_Encoding#32.2F64-bit_addressing
//
// Thefore we emit the SIB byte before the const so that [SIB + displacement] ends up [register + displacement].
// https://wiki.osdev.org/X86-64_Instruction_Encoding#32.2F64-bit_addressing_2
if baseReg == RegSP || baseReg == RegR12 {
sbiValue := byte(0b00_100_100)
sbi = &sbiValue
}
} else {
if indexReg == RegSP {
err = errors.New("SP cannot be used for SIB index")
return
}
modRM = 0b00_000_100 // Indicate that the memory location is specified by SIB.
withoutDisplacement := offset == 0 &&
// For R13 and BP, base registers cannot be encoded "without displacement" mod (i.e. 0b00 mod).
baseReg != RegR13 && baseReg != RegBP
if withoutDisplacement {
// https://wiki.osdev.org/X86-64_Instruction_Encoding#ModR.2FM
modRM |= 0b00_000_000 // Specifying that operand is SIB without displacement
displacementWidth = 0
} else if fitInSigned8bit(offset) {
// https://wiki.osdev.org/X86-64_Instruction_Encoding#ModR.2FM
modRM |= 0b01_000_000 // Specifying that operand is SIB + 8bit displacement.
displacementWidth = 8
} else {
// https://wiki.osdev.org/X86-64_Instruction_Encoding#ModR.2FM
modRM |= 0b10_000_000 // Specifying that operand is SIB + 32bit displacement.
displacementWidth = 32
}
var baseRegBits byte
baseRegBits, p, err = register3bits(baseReg, registerSpecifierPositionModRMFieldRM)
if err != nil {
return
}
var indexRegBits byte
var indexRegPrefix RexPrefix
indexRegBits, indexRegPrefix, err = register3bits(indexReg, registerSpecifierPositionSIBIndex)
if err != nil {
return
}
p |= indexRegPrefix
sbiValue := baseRegBits | (indexRegBits << 3)
switch scale {
case 1:
sbiValue |= 0b00_000_000
case 2:
sbiValue |= 0b01_000_000
case 4:
sbiValue |= 0b10_000_000
case 8:
sbiValue |= 0b11_000_000
default:
err = fmt.Errorf("scale in SIB must be one of 1, 2, 4, 8 but got %d", scale)
return
}
sbi = &sbiValue
}
return
}
// GetRegisterToRegisterModRM does XXXX
//
// TODO: srcOnModRMReg can be deleted after golang-asm removal. This is necessary to match our implementation
// with golang-asm, but in practice, there are equivalent opcodes to always have src on ModRM:reg without ambiguity.
func (n *NodeImpl) GetRegisterToRegisterModRM(srcOnModRMReg bool) (RexPrefix, modRM byte, err error) {
var reg3bits, rm3bits byte
if srcOnModRMReg {
reg3bits, RexPrefix, err = register3bits(n.SrcReg,
// Indicate that SrcReg will be specified by ModRM:reg.
registerSpecifierPositionModRMFieldReg)
if err != nil {
return
}
var dstRexPrefix byte
rm3bits, dstRexPrefix, err = register3bits(n.DstReg,
// Indicate that DstReg will be specified by ModRM:r/m.
registerSpecifierPositionModRMFieldRM)
if err != nil {
return
}
RexPrefix |= dstRexPrefix
} else {
rm3bits, RexPrefix, err = register3bits(n.SrcReg,
// Indicate that SrcReg will be specified by ModRM:r/m.
registerSpecifierPositionModRMFieldRM)
if err != nil {
return
}
var dstRexPrefix byte
reg3bits, dstRexPrefix, err = register3bits(n.DstReg,
// Indicate that DstReg will be specified by ModRM:reg.
registerSpecifierPositionModRMFieldReg)
if err != nil {
return
}
RexPrefix |= dstRexPrefix
}
// https://wiki.osdev.org/X86-64_Instruction_Encoding#ModR.2FM
modRM = 0b11_000_000 | // Specifying that dst operand is register.
(reg3bits << 3) |
rm3bits
return
}
// RexPrefix represents REX prefix https://wiki.osdev.org/X86-64_Instruction_Encoding#REX_prefix
type RexPrefix = byte
// REX prefixes are independent of each other and can be combined with OR.
const (
RexPrefixNone RexPrefix = 0x0000_0000 // Indicates that the instruction doesn't need RexPrefix.
RexPrefixDefault RexPrefix = 0b0100_0000
RexPrefixW = 0b0000_1000 | RexPrefixDefault // REX.W
RexPrefixR = 0b0000_0100 | RexPrefixDefault // REX.R
RexPrefixX = 0b0000_0010 | RexPrefixDefault // REX.X
RexPrefixB = 0b0000_0001 | RexPrefixDefault // REX.B
)
// registerSpecifierPosition represents the position in the instruction bytes where an operand register is placed.
type registerSpecifierPosition byte
const (
registerSpecifierPositionModRMFieldReg registerSpecifierPosition = iota
registerSpecifierPositionModRMFieldRM
registerSpecifierPositionSIBIndex
)
func register3bits(
reg asm.Register,
registerSpecifierPosition registerSpecifierPosition,
) (bits byte, prefix RexPrefix, err error) {
prefix = RexPrefixNone
if RegR8 <= reg && reg <= RegR15 || RegX8 <= reg && reg <= RegX15 {
// https://wiki.osdev.org/X86-64_Instruction_Encoding#REX_prefix
switch registerSpecifierPosition {
case registerSpecifierPositionModRMFieldReg:
prefix = RexPrefixR
case registerSpecifierPositionModRMFieldRM:
prefix = RexPrefixB
case registerSpecifierPositionSIBIndex:
prefix = RexPrefixX
}
}
// https://wiki.osdev.org/X86-64_Instruction_Encoding#Registers
switch reg {
case RegAX, RegR8, RegX0, RegX8:
bits = 0b000
case RegCX, RegR9, RegX1, RegX9:
bits = 0b001
case RegDX, RegR10, RegX2, RegX10:
bits = 0b010
case RegBX, RegR11, RegX3, RegX11:
bits = 0b011
case RegSP, RegR12, RegX4, RegX12:
bits = 0b100
case RegBP, RegR13, RegX5, RegX13:
bits = 0b101
case RegSI, RegR14, RegX6, RegX14:
bits = 0b110
case RegDI, RegR15, RegX7, RegX15:
bits = 0b111
default:
err = fmt.Errorf("invalid register [%s]", RegisterName(reg))
}
return
}
func FitIn32bit(v int64) bool {
return math.MinInt32 <= v && v <= math.MaxUint32
}
func fitInSigned8bit(v int64) bool {
return math.MinInt8 <= v && v <= math.MaxInt8
}
func IsVectorRegister(r asm.Register) bool {
return RegX0 <= r && r <= RegX15
}