Skip to content

Instantly share code, notes, and snippets.

@mratsim
Created April 21, 2023 14:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mratsim/18e1ed9c5158b6a93c2103916de24f30 to your computer and use it in GitHub Desktop.
Save mratsim/18e1ed9c5158b6a93c2103916de24f30 to your computer and use it in GitHub Desktop.
Compile-Time x86 ASM Assembler
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import std/[macros, strutils, sets, hashes, algorithm]
# A compile-time inline assembler
# We need Intel syntax.
# When using memory operand with displacement the default AT&T syntax is
# 8(%[identifier])
# GCC accepts 8+(%[identifier]) as well but not Clang
# However when constant are propagated, 8(%[identifier]) can also expand to
# 8BLS12_381_Modulus(%rip) i.e. the compiler tries to forward to the linker the relative address of a constant
# and due to naive string mixin it fails.
{.passC:"-masm=intel".}
# No exceptions allowed
{.push raises: [].}
type
RM* = enum
## Register or Memory operand
# https://gcc.gnu.org/onlinedocs/gcc/Simple-Constraints.html
# We don't use the "Any" constraint like rm, g, oi or ri. It's unsure how to mix the differing semantics
Reg = "r"
Mem = "m"
Imm = "i"
MemOffsettable = "o" # 'o' constraint might not work fully https://groups.google.com/g/llvm-dev/c/dfsPzWP_H1E
PointerInReg = "r" # Store an array pointer. ⚠️ for const arrays, this may generate incorrect code with LTO and constant folding.
ElemsInReg = "r" # Store each individual array element in reg
# Specific registers
RCX = "c"
RDX = "d"
R8 = "r8"
RAX = "a"
# Flags
CarryFlag = "@ccc"
# Clobbered register
ClobberedReg
when sizeof(int) == 8 and not defined(Constantine32):
type
Register* = enum
rbx
rdx
r8
rax
xmm0
else:
type
Register* = enum
rbx = "ebx"
rdx = "edx"
r8 = "r8d"
rax = "eax"
xmm0
type
Constraint* = enum
## GCC extended assembly modifier
asmInput = ""
asmInputCommutative = "%"
asmOutputOverwrite = "="
asmOutputEarlyClobber = "=&"
asmInputOutput = "+"
asmInputOutputEarlyClobber = "+&" # For register asmInputOutput, clang needs "+&" bug?
asmClobberedRegister
MemIndirectAccess* = enum
memNoAccess
memRead
memWrite
memReadWrite
OpKind = enum
kRegister
kFromArray
kArrayAddr
k2dArrayAddr
Operand* = object
desc: OperandDesc
case kind: OpKind
of kRegister:
discard
of kFromArray:
offset: int
of kArrayAddr:
buf: seq[Operand]
of k2dArrayAddr:
dims: array[2, int]
buf2d: seq[Operand]
OperandDesc = ref object
asmId: string # [a] - ASM id
nimSymbol: NimNode # a - Nim nimSymbol
rm: RM
constraint: Constraint
constraintString: string # C emit for example a->limbs
memClobbered: seq[(MemIndirectAccess, string)]
OperandArray* = object
nimSymbol: NimNode
buf: seq[Operand]
OperandReuse* = object
# Allow reusing a register
asmId: string
Assembler_x86* = object
code: string
operands: HashSet[OperandDesc]
wordBitWidth*: int
wordSize: int
areFlagsClobbered: bool
isStackClobbered: bool
regClobbers: set[Register]
Stack* = object
const SpecificRegisters = {RCX, RDX, R8, RAX}
const OutputReg = {asmOutputEarlyClobber, asmInputOutput, asmInputOutputEarlyClobber, asmOutputOverwrite, asmClobberedRegister}
func toString*(nimSymbol: NimNode): string =
# We need to dereference the hidden pointer of var param
let isPtr = nimSymbol.kind in {nnkHiddenDeref, nnkPtrTy}
let isAddr = nimSymbol.kind in {nnkInfix, nnkCall} and (nimSymbol[0].eqIdent"addr" or nimSymbol[0].eqIdent"unsafeAddr")
let nimSymbol = if isPtr: nimSymbol[0]
elif isAddr: nimSymbol[1]
else: nimSymbol
{.noSideEffect.}:
try: # Why does this raise a generic exception?
return $nimSymbol
except:
raise newException(Defect, "Broke Nim!")
func hash(od: OperandDesc): Hash =
{.noSideEffect.}:
try: # Why does this raise a generic exception?
hash(od.nimSymbol.toString())
except:
raise newException(Defect, "Broke Nim")
func len*(opArray: OperandArray): int =
opArray.buf.len
func len*(opArray: Operand): int =
opArray.buf.len
func rotateLeft*(opArray: var OperandArray) =
opArray.buf.rotateLeft(1)
func rotateRight*(opArray: var OperandArray) =
opArray.buf.rotateLeft(-1)
proc `[]`*(opArray: OperandArray, index: int): Operand =
opArray.buf[index]
func `[]`*(opArray: var OperandArray, index: int): var Operand =
opArray.buf[index]
func `[]`*(arrAddr: Operand, index: int): Operand =
arrAddr.buf[index]
func `[]`*(arrAddr: var Operand, index: int): var Operand =
arrAddr.buf[index]
func `[]`*(arr2dAddr: Operand, i, j: int): Operand =
arr2dAddr.buf2d[i*arr2dAddr.dims[1] + j]
func `[]`*(arr2dAddr: var Operand, i, j: int): var Operand =
arr2dAddr.buf2d[i*arr2dAddr.dims[1] + j]
func init*(T: type Assembler_x86, Word: typedesc[SomeUnsignedInt]): Assembler_x86 =
result.wordSize = sizeof(Word)
result.wordBitWidth = result.wordSize * 8
func setConstraintString(desc: OperandDesc, symbolString: string) =
# [a] "rbx" (`a`) for specific registers
# [a] "+r" (`*a_ptr`) for pointer to memory
# [a] "+r" (`a[0]`) for array cells
desc.constraintString = desc.asmId & "\"" & $desc.constraint & $desc.rm & "\"" &
" (`" & symbolString & "`)"
func genMemClobber(nimSymbol: NimNode, len: int, memIndirect: MemIndirectAccess): string =
let baseType = nimSymbol.getTypeImpl()[2].getTypeImpl()[0]
let cBaseType = if baseType.sameType(getType(uint64)): "NU64"
else: "NU32"
let symStr = nimSymbol.toString()
case memIndirect
of memRead:
return "\"m\" (`*(const " & cBaseType & " (*)[" & $len & "]) " & symStr & "`)"
of memWrite:
return "\"=m\" (`*(" & cBaseType & " (*)[" & $len & "]) " & symStr & "`)"
of memReadWrite:
return "\"+m\" (`*(" & cBaseType & " (*)[" & $len & "]) " & symStr & "`)"
else:
doAssert false, "Indirect access kind not specified"
func asmValue*(nimSymbol: NimNode, rm: RM, constraint: Constraint): Operand =
{.noSideEffect.}:
let symStr = try: # Why does this raise a generic exception?
$nimSymbol
except:
raise newException(Defect, "Broke Nim!")
let desc = OperandDesc(
asmId: "[" & symStr & "]",
nimSymbol: nimSymbol,
rm: rm,
constraint: constraint)
if rm in {Mem, MemOffsettable}:
desc.setConstraintString("*&" & symStr)
else:
desc.setConstraintString(symStr)
return Operand(desc: desc)
func asmArray*(nimSymbol: NimNode, len: int, rm: RM, constraint: Constraint, memIndirect = memNoAccess): OperandArray =
doAssert rm in {MemOffsettable, PointerInReg, ElemsInReg}
doAssert (rm == PointerInReg) xor (memIndirect == memNoAccess)
let symStr = nimSymbol.toString()
result.nimSymbol = nimSymbol
result.buf.setLen(len)
if rm == PointerInReg:
let desc = OperandDesc(
asmId: "[" & symStr & "]",
nimSymbol: nimSymbol,
rm: rm,
constraint: constraint,
memClobbered: @[(memIndirect, genMemClobber(nimSymbol, len, memIndirect))])
desc.setConstraintString(symStr)
for i in 0 ..< len:
result.buf[i] = Operand(
desc: desc,
kind: kFromArray,
offset: i)
elif rm == MemOffsettable:
# For MemOffsettable
# Creating a base address like PointerInReg works with GCC but LLVM miscompiles
# so we create individual memory locations.
# With MemOffsettable it's actually direct access, translate
let memIndirect = if constraint == asmInput: memRead
elif constraint == asmOutputOverwrite: memWrite
elif constraint == asmInputOutput: memReadWrite
else: raise newException(Defect, "Invalid constraint for MemOffsettable: " & $constraint)
# https://stackoverflow.com/questions/67993984/clang-errors-expected-register-with-inline-x86-assembly-works-with-gcc#comment120189933_67995035
# We dereference+cast to "+m" (*(NU64 (*)[6]) myArray)
# to ensure same treatment of "NU64* myArray" and "NU64 myArray[6]" as in C
let desc = OperandDesc(
asmId: "[" & symStr & "]",
nimSymbol: nimSymbol,
rm: rm,
constraint: constraint,
constraintString: "[" & symStr & "] " & genMemClobber(nimSymbol, len, memIndirect))
for i in 0 ..< len:
# let desc = OperandDesc(
# asmId: "[" & symStr & $i & "]",
# nimSymbol: ident(symStr & $i),
# rm: rm,
# constraint: constraint)
# desc.setConstraintString(symStr & "[" & $i & "]")
result.buf[i] = Operand(
desc: desc,
kind: kFromArray,
offset: i)
else:
# For ElemsInReg
# We can't store an array in register so we create assign individual register
# per array elements instead
for i in 0 ..< len:
let desc = OperandDesc(
asmId: "[" & symStr & $i & "]",
nimSymbol: ident(symStr & $i),
rm: rm,
constraint: constraint)
desc.setConstraintString(symStr & "[" & $i & "]")
result.buf[i] = Operand(
desc: desc,
kind: kRegister)
func asArrayAddr*(op: Operand, memPointer: NimNode, len: int, memIndirect: MemIndirectAccess): Operand =
## Use the value stored in an operand as an array address
doAssert op.desc.rm in {Reg, PointerInReg, ElemsInReg}+SpecificRegisters
result = Operand(
kind: kArrayAddr,
desc: nil,
buf: newSeq[Operand](len))
op.desc.memClobbered.add (memIndirect, genMemClobber(memPointer, len, memIndirect))
for i in 0 ..< len:
result.buf[i] = Operand(
desc: op.desc,
kind: kFromArray,
offset: i)
func asArrayAddr*(op: Register, memPointer: NimNode, len: int, memIndirect: MemIndirectAccess): Operand =
## Use the value stored in an operand as an array address
result = Operand(
kind: kArrayAddr,
desc: nil,
buf: newSeq[Operand](len))
let desc = OperandDesc(
asmId: $op,
rm: ClobberedReg,
constraint: asmClobberedRegister)
desc.memClobbered = @[(memIndirect, genMemClobber(memPointer, len, memIndirect))]
for i in 0 ..< len:
result.buf[i] = Operand(
desc: desc,
kind: kFromArray,
offset: i)
func as2dArrayAddr*(op: Operand, memPointer: NimNode, rows, cols: int, memIndirect: MemIndirectAccess): Operand =
## Use the value stored in an operand as an array address
doAssert op.desc.rm in {Reg, PointerInReg, ElemsInReg}+SpecificRegisters
result = Operand(
kind: k2dArrayAddr,
desc: nil,
dims: [rows, cols],
buf2d: newSeq[Operand](rows*cols))
op.desc.memClobbered.add (memIndirect, genMemClobber(memPointer, rows*cols, memIndirect))
for i in 0 ..< rows*cols:
result.buf2d[i] = Operand(
desc: op.desc,
kind: kFromArray,
offset: i)
# Code generation
# ------------------------------------------------------------------------------------------------------------
func setToCarryFlag*(a: var Assembler_x86, carry: NimNode) =
# We need to dereference the hidden pointer of var param
let isHiddenDeref = carry.kind == nnkHiddenDeref
let nimSymbol = if isHiddenDeref: carry[0]
else: carry
{.noSideEffect.}:
let symStr = try: # Why does this raise a generic exception?
$nimSymbol
except:
raise newException(Defect, "Broke Nim!")
let desc = OperandDesc(
asmId: "",
nimSymbol: ident(symStr),
rm: CarryFlag,
constraint: asmOutputOverwrite)
desc.setConstraintString(symStr)
a.operands.incl(desc)
func generate*(a: Assembler_x86): NimNode =
## Generate the inline assembly code from
## the desired instruction
var
outOperands: seq[string]
inOperands: seq[string]
memClobbered = false
for odesc in a.operands.items():
if odesc.constraint in {asmInput, asmInputCommutative}:
inOperands.add odesc.constraintString
else:
outOperands.add odesc.constraintString
for (memIndirect, memDesc) in odesc.memClobbered:
# TODO: precise clobbering.
# GCC and Clang complain about impossible constraints or reaching coloring depth
# when we do precise constraints for inputs
# If only out clobbers, the Poly1305 MAC test fails without mem clobbers
if memIndirect != memRead:
memClobbered = true
break
# if memIndirect == memRead:
# inOperands.add memDesc
# else:
# outOperands.add memDesc
var params: string
params.add ": " & outOperands.join(", ") & '\n'
params.add ": " & inOperands.join(", ") & '\n'
let clobbers = [(a.isStackClobbered, "sp"),
(a.areFlagsClobbered, "cc"),
(memClobbered, "memory")]
var clobberList = ": "
for (clobbered, str) in clobbers:
if clobbered:
if clobberList.len == 2:
clobberList.add "\"" & str & '\"'
else:
clobberList.add ", \"" & str & '\"'
for reg in a.regClobbers:
if clobberList.len == 2:
clobberList.add "\"" & $reg & '\"'
else:
clobberList.add ", \"" & $reg & '\"'
params.add clobberList
# GCC will optimize ASM away if there are no
# memory operand or volatile + memory clobber
# https://stackoverflow.com/questions/34244185/looping-over-arrays-with-inline-assembly
# result = nnkAsmStmt.newTree(
# newEmptyNode(),
# newLit(asmStmt & params)
# )
var asmStmt = "\"" & a.code.replace("\n", "\\n\"\n\"")
asmStmt.setLen(asmStmt.len - 1) # drop the last quote
result = nnkPragma.newTree(
nnkExprColonExpr.newTree(
ident"emit",
newLit(
"asm volatile(\n" & asmStmt & params & ");"
)
)
)
result = nnkBlockStmt.newTree(
newEmptyNode(),
result)
func getStrOffset(a: Assembler_x86, op: Operand): string =
if op.kind != kFromArray:
if op.kind in {kArrayAddr, k2dArrayAddr}:
# We are operating on an array pointer
# instead of array elements
if op.buf[0].desc.constraint == asmClobberedRegister:
return op.buf[0].desc.asmId
else:
return "%" & op.buf[0].desc.asmId
else:
return "%" & op.desc.asmId
# Beware GCC / Clang differences with displacements
# https://lists.llvm.org/pipermail/llvm-dev/2017-August/116202.html
# - Memory operand:
# - 8+%[variable] works with GCC
# - 8%[variable] works with Clang
# - Pointer operand
# - 8(%rax) works with both
#
# In Clang
# for 8[M], it might become:
# - invalid: 8BLS12_381_Order(%rip) with LTO constant propagation
# - or valid: 8(%rax)
# for 8+[M], it might become:
# - valid 8+BLS12_381_Order(%rip) with LTO constant propagation
# - or invalid: 8+(%rax)
# also warning about 'o' constraint: https://groups.google.com/g/llvm-dev/c/dfsPzWP_H1E
# and https://stackoverflow.com/questions/34446928/llvm-reports-unsupported-inline-asm-input-with-type-void-matching-output-w
#
# So we use the q/k modifier: https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#x86Operandmodifiers
# so that the PointerInReg are passed correctly with register names to linker even with constant folding
if op.desc.rm in {Mem, MemOffsettable}:
# Directly accessing memory
if a.wordBitWidth == 64:
if op.offset == 0:
return "%q" & op.desc.asmId
else:
return "%q" & op.desc.asmId & " + " & $(op.offset * a.wordSize)
else:
if op.offset == 0:
return "%d" & op.desc.asmId
else:
return "%d" & op.desc.asmId & " + " & $(op.offset * a.wordSize)
elif op.desc.rm == PointerInReg or
op.desc.rm in SpecificRegisters or
(op.desc.rm == ElemsInReg and op.kind == kFromArray):
if a.wordBitWidth == 64:
if op.offset == 0:
return "QWORD ptr [%" & op.desc.asmId & ']'
return "QWORD ptr [%" & op.desc.asmId & " + " & $(op.offset * a.wordSize) & ']'
else:
if op.offset == 0:
return "DWORD ptr [%" & op.desc.asmId & ']'
return "DWORD ptr [%" & op.desc.asmId & " + " & $(op.offset * a.wordSize) & ']'
elif op.desc.rm == ClobberedReg: # Array in clobbered register
if a.wordBitWidth == 64:
if op.offset == 0:
return "QWORD ptr [" & op.desc.asmId & ']'
return "QWORD ptr [" & op.desc.asmId & " + " & $(op.offset * a.wordSize) & ']'
else:
if op.offset == 0:
return "DWORD ptr [" & op.desc.asmId & ']'
return "DWORD ptr [" & op.desc.asmId & " + " & $(op.offset * a.wordSize) & ']'
else:
error "Unsupported: " & $op.desc.rm.ord
func codeFragment(a: var Assembler_x86, instr: string, op: Operand) =
# Generate a code fragment
let off = a.getStrOffset(op)
a.code &= instr & " " & off & '\n'
if op.desc.constraint != asmClobberedRegister:
a.operands.incl op.desc
func codeFragment(a: var Assembler_x86, instr: string, op0, op1: Operand) =
# Generate a code fragment
# ⚠️ Warning:
# The caller should deal with destination/source operand
# so that it fits Intel Assembly
let off0 = a.getStrOffset(op0)
let off1 = a.getStrOffset(op1)
a.code &= instr & " " & off0 & ", " & off1 & '\n'
if op0.desc.constraint != asmClobberedRegister:
a.operands.incl op0.desc
if op1.desc.constraint != asmClobberedRegister:
a.operands.incl op1.desc
func codeFragment(a: var Assembler_x86, instr: string, reg: Register, op: Operand) =
# Generate a code fragment
# ⚠️ Warning:
# The caller should deal with destination/source operand
# so that it fits Intel Assembly
let off = a.getStrOffset(op)
a.code &= instr & " " & $reg & ", " & off & '\n'
# op.desc can be nil for renamed registers (using asArrayAddr)
if not op.desc.isNil and op.desc.constraint != asmClobberedRegister:
a.operands.incl op.desc
a.regClobbers.incl reg
func codeFragment(a: var Assembler_x86, instr: string, op: Operand, reg: Register) =
# Generate a code fragment
# ⚠️ Warning:
# The caller should deal with destination/source operand
# so that it fits Intel Assembly
let off = a.getStrOffset(op)
a.code &= instr & " " & off & ", " & $reg & '\n'
if op.desc.constraint != asmClobberedRegister:
a.operands.incl op.desc
a.regClobbers.incl reg
func codeFragment(a: var Assembler_x86, instr: string, reg0, reg1: Register) =
# Generate a code fragment
# ⚠️ Warning:
# The caller should deal with destination/source operand
# so that it fits Intel Assembly
a.code &= instr & " " & $reg0 & ", " & $reg1 & '\n'
a.regClobbers.incl reg0
a.regClobbers.incl reg1
func codeFragment(a: var Assembler_x86, instr: string, op: Operand, imm: int) =
# Generate a code fragment
# ⚠️ Warning:
# The caller should deal with destination/source operand
# so that it fits Intel Assembly
let off = a.getStrOffset(op)
a.code &= instr & " " & off & ", " & $imm & '\n'
if op.desc.constraint != asmClobberedRegister:
a.operands.incl op.desc
func codeFragment(a: var Assembler_x86, instr: string, op: OperandReuse, reg: Register) =
# Generate a code fragment
# ⚠️ Warning:
# The caller should deal with destination/source operand
# so that it fits Intel Assembly
a.code &= instr & " %" & $op.asmId & ", " & $reg & '\n'
a.regClobbers.incl reg
func codeFragment(a: var Assembler_x86, instr: string, reg: Register, op: OperandReuse) =
# Generate a code fragment
# ⚠️ Warning:
# The caller should deal with destination/source operand
# so that it fits Intel Assembly
a.code &= instr & " " & $reg & ", %" & $op.asmId & '\n'
a.regClobbers.incl reg
func codeFragment(a: var Assembler_x86, instr: string, reg: Register, imm: int) =
# Generate a code fragment
# ⚠️ Warning:
# The caller should deal with destination/source operand
# so that it fits Intel Assembly
a.code &= instr & " " & $reg & ", " & $imm & '\n'
a.regClobbers.incl reg
func codeFragment(a: var Assembler_x86, instr: string, reg: OperandReuse, imm: int) =
# Generate a code fragment
# ⚠️ Warning:
# The caller should deal with destination/source operand
# so that it fits Intel Assembly
a.code &= instr & " %" & $reg.asmId & ", " & $imm & '\n'
func codeFragment(a: var Assembler_x86, instr: string, reg0, reg1: OperandReuse) =
# Generate a code fragment
# ⚠️ Warning:
# The caller should deal with destination/source operand
# so that it fits GNU Assembly
a.code &= instr & " %" & $reg0.asmId & ", %" & $reg1.asmId & '\n'
func codeFragment(a: var Assembler_x86, instr: string, op0: Operand, op1: OperandReuse) =
# Generate a code fragment
# ⚠️ Warning:
# The caller should deal with destination/source operand
# so that it fits GNU Assembly
let off0 = a.getStrOffset(op0)
a.code &= instr & " " & off0 & ", %" & $op1.asmId & '\n'
if op0.desc.constraint != asmClobberedRegister:
a.operands.incl op0.desc
func codeFragment(a: var Assembler_x86, instr: string, op0: OperandReuse, op1: Operand) =
# Generate a code fragment
# ⚠️ Warning:
# The caller should deal with destination/source operand
# so that it fits GNU Assembly
let off1 = a.getStrOffset(op1)
a.code &= instr & " %" & $op0.asmId & ", " & off1 & '\n'
if op1.desc.constraint != asmClobberedRegister:
a.operands.incl op1.desc
func reuseRegister*(reg: OperandArray): OperandReuse =
doAssert reg.buf[0].desc.constraint == asmInputOutput
result.asmId = reg.buf[0].desc.asmId
func comment*(a: var Assembler_x86, comment: string) =
# Add a comment
a.code &= "# " & comment & '\n'
func repackRegisters*(regArr: OperandArray, regs: varargs[Operand]): OperandArray =
## Extend an array of registers with extra registers
result.buf = regArr.buf
result.buf.add regs
result.nimSymbol = nil
func isOutput(op: Operand): bool =
if op.desc.constraint in OutputReg:
return true
if op.desc.rm == PointerInReg:
doAssert op.desc.memClobbered.len == 1
if op.desc.memClobbered[0][0] in {memWrite, memReadWrite}:
return true
# Currently there is no facility to track writes through an ElemsInReg + asArrayAddr
return false
# Instructions
# ------------------------------------------------------------------------------------------------------------
func add*(a: var Assembler_x86, dst, src: Operand) =
## Does: dst <- dst + src
doAssert dst.isOutput()
a.codeFragment("add", dst, src)
a.areFlagsClobbered = true
func add*(a: var Assembler_x86, dst, src: Register) =
## Does: dst <- dst + src
a.codeFragment("add", dst, src)
a.areFlagsClobbered = true
func add*(a: var Assembler_x86, dst: Operand, src: Register) =
## Does: dst <- dst + src
doAssert dst.isOutput()
a.codeFragment("add", dst, src)
a.areFlagsClobbered = true
func add*(a: var Assembler_x86, dst: Register, src: Operand) =
## Does: dst <- dst + src
a.codeFragment("add", dst, src)
a.areFlagsClobbered = true
func adc*(a: var Assembler_x86, dst, src: Operand) =
## Does: dst <- dst + src + carry
doAssert dst.isOutput()
doAssert dst.desc.rm notin {Mem, MemOffsettable},
"Using addcarry with a memory destination, this incurs significant performance penalties."
a.codeFragment("adc", dst, src)
a.areFlagsClobbered = true
func adc*(a: var Assembler_x86, dst, src: Register) =
## Does: dst <- dst + src + carry
a.codeFragment("adc", dst, src)
a.areFlagsClobbered = true
func adc*(a: var Assembler_x86, dst: Operand, imm: int) =
## Does: dst <- dst + imm + borrow
doAssert dst.isOutput()
doAssert dst.desc.rm notin {Mem, MemOffsettable},
"Using addcarry with a memory destination, this incurs significant performance penalties."
a.codeFragment("adc", dst, imm)
a.areFlagsClobbered = true
func adc*(a: var Assembler_x86, dst: Operand, src: Register) =
## Does: dst <- dst + src
doAssert dst.isOutput()
a.codeFragment("adc", dst, src)
a.areFlagsClobbered = true
func adc*(a: var Assembler_x86, dst: Register, imm: int) =
## Does: dst <- dst + src
a.codeFragment("adc", dst, imm)
a.areFlagsClobbered = true
func sub*(a: var Assembler_x86, dst, src: Operand) =
## Does: dst <- dst - src
doAssert dst.isOutput()
a.codeFragment("sub", dst, src)
a.areFlagsClobbered = true
func sbb*(a: var Assembler_x86, dst, src: Operand) =
## Does: dst <- dst - src - borrow
doAssert dst.isOutput()
doAssert dst.desc.rm notin {Mem, MemOffsettable},
"Using subborrow with a memory destination, this incurs significant performance penalties."
a.codeFragment("sbb", dst, src)
a.areFlagsClobbered = true
func sbb*(a: var Assembler_x86, dst: Operand, imm: int) =
## Does: dst <- dst - imm - borrow
doAssert dst.isOutput()
doAssert dst.desc.rm notin {Mem, MemOffsettable},
"Using subborrow with a memory destination, this incurs significant performance penalties."
a.codeFragment("sbb", dst, imm)
a.areFlagsClobbered = true
func sbb*(a: var Assembler_x86, dst: Register, imm: int) =
## Does: dst <- dst - imm - borrow
a.codeFragment("sbb", dst, imm)
a.areFlagsClobbered = true
func sbb*(a: var Assembler_x86, dst, src: Register) =
## Does: dst <- dst - imm - borrow
a.codeFragment("sbb", dst, src)
a.areFlagsClobbered = true
func sbb*(a: var Assembler_x86, dst: OperandReuse, imm: int) =
## Does: dst <- dst - imm - borrow
a.codeFragment("sbb", dst, imm)
a.areFlagsClobbered = true
func sbb*(a: var Assembler_x86, dst, src: OperandReuse) =
## Does: dst <- dst - imm - borrow
a.codeFragment("sbb", dst, src)
a.areFlagsClobbered = true
func sar*(a: var Assembler_x86, dst: Operand, imm: int) =
## Does Arithmetic Right Shift (i.e. with sign extension)
doAssert dst.isOutput()
a.codeFragment("sar", dst, imm)
a.areFlagsClobbered = true
func `and`*(a: var Assembler_x86, dst: Operand, src: Register) =
## Compute the bitwise AND of x and y and
## set the Sign, Zero and Parity flags
a.codeFragment("and", dst, src)
a.areFlagsClobbered = true
func `and`*(a: var Assembler_x86, dst: OperandReuse, imm: int) =
## Compute the bitwise AND of x and y and
## set the Sign, Zero and Parity flags
a.codeFragment("and", dst, imm)
a.areFlagsClobbered = true
func `and`*(a: var Assembler_x86, dst, src: Operand) =
## Compute the bitwise AND of x and y and
## set the Sign, Zero and Parity flags
a.codeFragment("and", dst, src)
a.areFlagsClobbered = true
func `and`*(a: var Assembler_x86, dst: Operand, src: OperandReuse) =
## Compute the bitwise AND of x and y and
## set the Sign, Zero and Parity flags
a.codeFragment("and", dst, src)
a.areFlagsClobbered = true
func test*(a: var Assembler_x86, x, y: Operand) =
## Compute the bitwise AND of x and y and
## set the Sign, Zero and Parity flags
a.codeFragment("test", x, y)
a.areFlagsClobbered = true
func test*(a: var Assembler_x86, x, y: OperandReuse) =
## Compute the bitwise AND of x and y and
## set the Sign, Zero and Parity flags
a.codeFragment("test", x, y)
a.areFlagsClobbered = true
func `or`*(a: var Assembler_x86, dst: Register, src: Operand) =
## Compute the bitwise or of x and y and
## reset all flags
a.codeFragment("or", dst, src)
a.areFlagsClobbered = true
func `or`*(a: var Assembler_x86, dst, src: Operand) =
## Compute the bitwise or of x and y and
## reset all flags
a.codeFragment("or", dst, src)
a.areFlagsClobbered = true
func `or`*(a: var Assembler_x86, dst: OperandReuse, src: Operand) =
## Compute the bitwise or of x and y and
## reset all flags
a.codeFragment("or", dst, src)
a.areFlagsClobbered = true
func `xor`*(a: var Assembler_x86, dst, src: Operand) =
## Compute the bitwise xor of x and y and
## reset all flags
a.codeFragment("xor", dst, src)
a.areFlagsClobbered = true
func `xor`*(a: var Assembler_x86, dst, src: Register) =
## Compute the bitwise xor of x and y and
## reset all flags
a.codeFragment("xor", dst, src)
a.areFlagsClobbered = true
func mov*(a: var Assembler_x86, dst, src: Operand) =
## Does: dst <- src
doAssert dst.isOutput(), $dst.repr
a.codeFragment("mov", dst, src)
# No clobber
func mov*(a: var Assembler_x86, dst: Operand, src: OperandReuse) =
## Does: dst <- src
doAssert dst.isOutput(), $dst.repr
a.codeFragment("mov", dst, src)
# No clobber
func mov*(a: var Assembler_x86, dst: OperandReuse, src: Operand) =
## Does: dst <- src
# doAssert dst.isOutput(), $dst.repr
a.codeFragment("mov", dst, src)
# No clobber
func mov*(a: var Assembler_x86, dst: Operand, imm: int) =
## Does: dst <- imm
doAssert dst.isOutput(), $dst.repr
a.codeFragment("mov", dst, imm)
# No clobber
func mov*(a: var Assembler_x86, dst: Register, imm: int) =
## Does: dst <- src with dst a fixed register
a.codeFragment("mov", dst, imm)
func mov*(a: var Assembler_x86, dst: Register, src: Operand) =
## Does: dst <- src with dst a fixed register
a.codeFragment("mov", dst, src)
func mov*(a: var Assembler_x86, dst: Operand, src: Register) =
## Does: dst <- src with dst a fixed register
a.codeFragment("mov", dst, src)
func mov*(a: var Assembler_x86, dst: Register, src: OperandReuse) =
## Does: dst <- src with dst a fixed register
a.codeFragment("mov", dst, src)
func mov*(a: var Assembler_x86, dst: OperandReuse, src: Register) =
## Does: dst <- imm
# doAssert dst.isOutput(), $dst.repr
a.codeFragment("mov", dst, src)
func cmovc*(a: var Assembler_x86, dst, src: Operand) =
## Does: dst <- src if the carry flag is set
doAssert dst.desc.rm in {Reg, ElemsInReg}, "The destination operand must be a register: " & $dst.repr
doAssert dst.isOutput(), $dst.repr
a.codeFragment("cmovc", dst, src)
# No clobber
func cmovnc*(a: var Assembler_x86, dst, src: Operand) =
## Does: dst <- src if the carry flag is not set
doAssert dst.desc.rm in {Reg, ElemsInReg}, "The destination operand must be a register: " & $dst.repr
doAssert dst.isOutput(), $dst.repr
a.codeFragment("cmovnc", dst, src)
# No clobber
func cmovz*(a: var Assembler_x86, dst: Operand, src: Register) =
## Does: dst <- src if the zero flag is not set
doAssert dst.desc.rm in {Reg, ElemsInReg}, "The destination operand must be a register: " & $dst.repr
doAssert dst.isOutput(), $dst.repr
a.codeFragment("cmovz", dst, src)
# No clobber
func cmovz*(a: var Assembler_x86, dst, src: Operand) =
## Does: dst <- src if the zero flag is not set
doAssert dst.desc.rm in {Reg, ElemsInReg}, "The destination operand must be a register: " & $dst.repr
doAssert dst.isOutput(), $dst.repr
a.codeFragment("cmovz", dst, src)
# No clobber
func cmovz*(a: var Assembler_x86, dst: Operand, src: OperandReuse) =
## Does: dst <- src if the zero flag is not set
doAssert dst.desc.rm in {Reg, ElemsInReg}, "The destination operand must be a register: " & $dst.repr
doAssert dst.isOutput(), $dst.repr
a.codeFragment("cmovz", dst, src)
# No clobber
func cmovnz*(a: var Assembler_x86, dst, src: Operand) =
## Does: dst <- src if the zero flag is not set
doAssert dst.desc.rm in {Reg, ElemsInReg}, "The destination operand must be a register: " & $dst.repr
doAssert dst.isOutput(), $dst.repr
a.codeFragment("cmovnz", dst, src)
# No clobber
func cmovnz*(a: var Assembler_x86, dst: Operand, src: OperandReuse) =
## Does: dst <- src if the zero flag is not set
doAssert dst.desc.rm in {Reg, ElemsInReg}, "The destination operand must be a register: " & $dst.repr
doAssert dst.isOutput(), $dst.repr
a.codeFragment("cmovnz", dst, src)
# No clobber
func cmovs*(a: var Assembler_x86, dst, src: Operand) =
## Does: dst <- src if the sign flag
doAssert dst.desc.rm in {Reg, ElemsInReg}, "The destination operand must be a register: " & $dst.repr
doAssert dst.isOutput(), $dst.repr
a.codeFragment("cmovs", dst, src)
# No clobber
func mul*(a: var Assembler_x86, dHi, dLo: Register, src0: Operand, src1: Register) =
## Does (dHi, dLo) <- src0 * src1
doAssert src1 == rax, "MUL requires the RAX register"
doAssert dHi == rdx, "MUL requires the RDX register"
doAssert dLo == rax, "MUL requires the RAX register"
a.regClobbers.incl rax
a.regClobbers.incl rdx
a.codeFragment("mul", src0)
func imul*(a: var Assembler_x86, dst, src: Operand) =
## Does dst <- dst * src, keeping only the low half
doAssert dst.desc.rm in {Reg, ElemsInReg}+SpecificRegisters, "The destination operand must be a register: " & $dst.repr
doAssert dst.isOutput(), $dst.repr
a.codeFragment("imul", dst, src)
func imul*(a: var Assembler_x86, dst: Register, src: Operand) =
## Does dst <- dst * src, keeping only the low half
a.codeFragment("imul", dst, src)
func mulx*(a: var Assembler_x86, dHi, dLo, src0: Operand, src1: Register) =
## Does (dHi, dLo) <- src0 * src1
doAssert src1 == rdx, "MULX requires the RDX register"
a.regClobbers.incl rdx
doAssert dHi.desc.rm in {Reg, ElemsInReg}+SpecificRegisters,
"The destination operand must be a register " & $dHi.repr
doAssert dLo.desc.rm in {Reg, ElemsInReg}+SpecificRegisters,
"The destination operand must be a register " & $dLo.repr
doAssert dHi.desc.constraint in OutputReg
doAssert dLo.desc.constraint in OutputReg
let off0 = a.getStrOffset(src0)
a.code &= "mulx %" & $dHi.desc.asmId & ", %" & $dLo.desc.asmId & ", " & off0 & '\n'
a.operands.incl src0.desc
func mulx*(a: var Assembler_x86, dHi: Operand, dLo: Register, src0: Operand, src1: Register) =
## Does (dHi, dLo) <- src0 * src1
doAssert src1 == rdx, "MULX requires the RDX register"
a.regClobbers.incl rdx
doAssert dHi.desc.rm in {Reg, ElemsInReg}+SpecificRegisters,
"The destination operand must be a register " & $dHi.repr
doAssert dHi.desc.constraint in OutputReg
let off0 = a.getStrOffset(src0)
a.code &= "mulx %" & $dHi.desc.asmId & ", " & $dLo & ", " & off0 & '\n'
a.operands.incl src0.desc
a.regClobbers.incl dLo
func mulx*(a: var Assembler_x86, dHi: OperandReuse, dLo, src0: Operand, src1: Register) =
## Does (dHi, dLo) <- src0 * src1
doAssert src1 == rdx, "MULX requires the RDX register"
a.regClobbers.incl rdx
doAssert dLo.desc.rm in {Reg, ElemsInReg}+SpecificRegisters,
"The destination operand must be a register " & $dLo.repr
doAssert dLo.desc.constraint in OutputReg
let off0 = a.getStrOffset(src0)
a.code &= "mulx %" & $dHi.asmId & ", %" & $dLo.desc.asmId & ", " & off0 & '\n'
a.operands.incl src0.desc
func mulx*(a: var Assembler_x86, dHi: OperandReuse, dLo: Register, src0: Operand, src1: Register) =
## Does (dHi, dLo) <- src0 * src1
doAssert src1 == rdx, "MULX requires the RDX register"
a.regClobbers.incl rdx
let off0 = a.getStrOffset(src0)
a.code &= "mulx %" & $dHi.asmId & ", " & $dLo & ", " & off0 & '\n'
a.operands.incl src0.desc
a.regClobbers.incl dLo
func mulx*(a: var Assembler_x86, dHi, dLo: Register, src0: Operand, src1: Register) =
## Does (dHi, dLo) <- src0 * src1
doAssert src1 == rdx, "MULX requires the RDX register"
a.regClobbers.incl rdx
let off0 = a.getStrOffset(src0)
a.code &= "mulx " & $dHi & ", " & $dLo & ", " & off0 & '\n'
a.operands.incl src0.desc
a.regClobbers.incl dHi
a.regClobbers.incl dLo
func adcx*(a: var Assembler_x86, dst: Operand|OperandReuse|Register, src: Operand|OperandReuse|Register) =
## Does: dst <- dst + src + carry
## and only sets the carry flag
when dst is Operand:
doAssert dst.isOutput(), $dst.repr
doAssert dst.desc.rm in {Reg, ElemsInReg}+SpecificRegisters, "The destination operand must be a register: " & $dst.repr
a.codeFragment("adcx", dst, src)
a.areFlagsClobbered = true
func adox*(a: var Assembler_x86, dst: Operand|OperandReuse|Register, src: Operand|OperandReuse|Register) =
## Does: dst <- dst + src + overflow
## and only sets the overflow flag
when dst is Operand:
doAssert dst.isOutput(), $dst.repr
doAssert dst.desc.rm in {Reg, ElemsInReg}+SpecificRegisters, "The destination operand must be a register: " & $dst.repr
a.codeFragment("adox", dst, src)
a.areFlagsClobbered = true
func push*(a: var Assembler_x86, _: type Stack, reg: Operand) =
## Push the content of register on the stack
doAssert reg.desc.rm in {Reg, PointerInReg, ElemsInReg}+SpecificRegisters, "The destination operand must be a register: " & $reg.repr
a.codeFragment("push", reg)
a.isStackClobbered = true
func pop*(a: var Assembler_x86, _: type Stack, reg: Operand) =
## Pop the content of register on the stack
doAssert reg.desc.rm in {Reg, PointerInReg, ElemsInReg}+SpecificRegisters, "The destination operand must be a register: " & $reg.repr
a.codeFragment("pop", reg)
a.isStackClobbered = true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment