Files
Nim/compiler/nir/nirvm.nim
2023-11-05 20:25:25 +01:00

1176 lines
34 KiB
Nim

#
#
# The Nim Compiler
# (c) Copyright 2023 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
##[ NIR is a little too high level to interpret it efficiently. Thus
we compute `addresses` for SymIds, labels and offsets for object fields
in a preprocessing step.
We also split the instruction stream into separate (code, debug) seqs while
we're at it.
]##
import std / [syncio, assertions, tables, intsets]
import ".." / ic / bitabs
import nirinsts, nirtypes, nirfiles, nirlineinfos
type
OpcodeM = enum
ImmediateValM,
IntValM,
StrValM,
LoadLocalM, # with local ID
LoadGlobalM,
LoadProcM,
TypedM, # with type ID
PragmaIdM, # with Pragma ID, possible values: see PragmaKey enum
NilValM,
AllocLocals,
SummonParamM,
GotoM,
CheckedGotoM, # last atom
ArrayConstrM,
ObjConstrM,
RetM,
YldM,
SelectM,
SelectPairM, # ((values...), Label)
SelectListM, # (values...)
SelectValueM, # (value)
SelectRangeM, # (valueA..valueB)
AddrOfM,
ArrayAtM, # (elemSize, addr(a), i)
DerefArrayAtM,
FieldAtM, # addr(obj.field)
DerefFieldAtM,
LoadM, # a[]
AsgnM, # a = b
StoreM, # a[] = b
SetExcM,
TestExcM,
CheckedRangeM,
CheckedIndexM,
CallM,
CheckedAddM, # with overflow checking etc.
CheckedSubM,
CheckedMulM,
CheckedDivM,
CheckedModM,
AddM,
SubM,
MulM,
DivM,
ModM,
BitShlM,
BitShrM,
BitAndM,
BitOrM,
BitXorM,
BitNotM,
BoolNotM,
EqM,
LeM,
LtM,
CastM,
NumberConvM,
CheckedObjConvM,
ObjConvM,
TestOfM,
ProcDeclM,
PragmaPairM
const
LastAtomicValue = CheckedGotoM
OpcodeBits = 8'u32
OpcodeMask = (1'u32 shl OpcodeBits) - 1'u32
type
Instr = distinct uint32
template kind(n: Instr): OpcodeM = OpcodeM(n.uint32 and OpcodeMask)
template operand(n: Instr): uint32 = (n.uint32 shr OpcodeBits)
template toIns(k: OpcodeM; operand: uint32): Instr =
Instr(uint32(k) or (operand shl OpcodeBits))
template toIns(k: OpcodeM; operand: LitId): Instr =
Instr(uint32(k) or (operand.uint32 shl OpcodeBits))
type
NimStrPayloadVM = object
cap: int
data: UncheckedArray[char]
NimStringVM = object
len: int
p: ptr NimStrPayloadVM
const
GlobalsSize = 1024*24
type
PatchPos = distinct int
CodePos = distinct int
Bytecode* = object
code: seq[Instr]
debug: seq[PackedLineInfo]
m: ref NirModule
procs: Table[SymId, CodePos]
globals: Table[SymId, (uint32, int)]
strings: Table[LitId, NimStringVM]
globalData: pointer
globalsAddr: uint32
typeImpls: Table[string, TypeId]
offsets: Table[TypeId, seq[(int, TypeId)]]
sizes: Table[TypeId, (int, int)] # (size, alignment)
oldTypeLen: int
procUsagesToPatch: Table[SymId, seq[CodePos]]
interactive*: bool
Universe* = object ## all units: For interpretation we need that
units: seq[Bytecode]
unitNames: Table[string, int]
current: int
proc initBytecode*(m: ref NirModule): Bytecode = Bytecode(m: m, globalData: alloc0(GlobalsSize))
proc debug(bc: Bytecode; t: TypeId) =
var buf = ""
toString buf, bc.m.types, t
echo buf
proc debug(bc: Bytecode; info: PackedLineInfo) =
let (litId, line, col) = bc.m.man.unpack(info)
echo bc.m.lit.strings[litId], ":", line, ":", col
proc debug(bc: Bytecode; t: Tree; n: NodePos) =
var buf = ""
toString(t, n, bc.m.lit.strings, bc.m.lit.numbers, bc.m.symnames, buf)
echo buf
template `[]`(t: seq[Instr]; n: CodePos): Instr = t[n.int]
proc traverseObject(b: var Bytecode; t, offsetKey: TypeId) =
var size = -1
var align = -1
for x in sons(b.m.types, t):
case b.m.types[x].kind
of FieldDecl:
var offset = -1
for y in sons(b.m.types, x):
if b.m.types[y].kind == OffsetVal:
offset = int(b.m.lit.numbers[b.m.types[y].litId])
break
b.offsets.mgetOrPut(offsetKey, @[]).add (offset, x.firstSon)
of SizeVal:
size = int(b.m.lit.numbers[b.m.types[x].litId])
of AlignVal:
align = int(b.m.lit.numbers[b.m.types[x].litId])
of ObjectTy:
# inheritance
let impl = b.typeImpls.getOrDefault(b.m.lit.strings[b.m.types[x].litId])
assert impl.int > 0
traverseObject b, impl, offsetKey
else: discard
if t == offsetKey:
b.sizes[t] = (size, align)
proc computeSize(b: var Bytecode; t: TypeId): (int, int) =
case b.m.types[t].kind
of ObjectDecl, UnionDecl:
result = b.sizes[t]
of ObjectTy, UnionTy:
let impl = b.typeImpls[b.m.lit.strings[b.m.types[t].litId]]
result = computeSize(b, impl)
of IntTy, UIntTy, FloatTy, BoolTy, CharTy:
let s = b.m.types[t].integralBits div 8
result = (s, s)
of APtrTy, UPtrTy, AArrayPtrTy, UArrayPtrTy, ProcTy:
result = (sizeof(pointer), sizeof(pointer))
of ArrayTy:
let e = elementType(b.m.types, t)
let n = arrayLen(b.m.types, t)
let inner = computeSize(b, e)
result = (inner[0] * n.int, inner[1])
else:
result = (0, 0)
proc computeElemSize(b: var Bytecode; t: TypeId): int =
case b.m.types[t].kind
of ArrayTy, APtrTy, UPtrTy, AArrayPtrTy, UArrayPtrTy, LastArrayTy:
result = computeSize(b, elementType(b.m.types, t))[0]
else:
raiseAssert "not an array type"
proc traverseTypes(b: var Bytecode) =
for t in allTypes(b.m.types, b.oldTypeLen):
if b.m.types[t].kind in {ObjectDecl, UnionDecl}:
assert b.m.types[t.firstSon].kind == NameVal
b.typeImpls[b.m.lit.strings[b.m.types[t.firstSon].litId]] = t
for t in allTypes(b.m.types, b.oldTypeLen):
if b.m.types[t].kind in {ObjectDecl, UnionDecl}:
assert b.m.types[t.firstSon].kind == NameVal
traverseObject b, t, t
b.oldTypeLen = b.m.types.len
const
InvalidPatchPos* = PatchPos(-1)
proc isValid(p: PatchPos): bool {.inline.} = p.int != -1
proc prepare(bc: var Bytecode; info: PackedLineInfo; kind: OpcodeM): PatchPos =
result = PatchPos bc.code.len
bc.code.add toIns(kind, 1'u32)
bc.debug.add info
proc add(bc: var Bytecode; info: PackedLineInfo; kind: OpcodeM; raw: uint32) =
bc.code.add toIns(kind, raw)
bc.debug.add info
proc add(bc: var Bytecode; info: PackedLineInfo; kind: OpcodeM; lit: LitId) =
add bc, info, kind, uint32(lit)
proc isAtom(bc: Bytecode; pos: int): bool {.inline.} = bc.code[pos].kind <= LastAtomicValue
proc isAtom(bc: Bytecode; pos: CodePos): bool {.inline.} = bc.code[pos.int].kind <= LastAtomicValue
proc patch(bc: var Bytecode; pos: PatchPos) =
let pos = pos.int
let k = bc.code[pos].kind
assert k > LastAtomicValue
let distance = int32(bc.code.len - pos)
assert distance > 0
bc.code[pos] = toIns(k, cast[uint32](distance))
template build(bc: var Bytecode; info: PackedLineInfo; kind: OpcodeM; body: untyped) =
let pos = prepare(bc, info, kind)
body
patch(bc, pos)
proc len*(bc: Bytecode): int {.inline.} = bc.code.len
template rawSpan(n: Instr): int = int(operand(n))
proc nextChild(bc: Bytecode; pos: var int) {.inline.} =
if bc.code[pos].kind > LastAtomicValue:
assert bc.code[pos].operand > 0'u32
inc pos, bc.code[pos].rawSpan
else:
inc pos
proc next(bc: Bytecode; pos: var CodePos) {.inline.} = nextChild bc, int(pos)
iterator sons(bc: Bytecode; n: CodePos): CodePos =
var pos = n.int
assert bc.code[pos].kind > LastAtomicValue
let last = pos + bc.code[pos].rawSpan
inc pos
while pos < last:
yield CodePos pos
nextChild bc, pos
iterator sonsFrom1(bc: Bytecode; n: CodePos): CodePos =
var pos = n.int
assert bc.code[pos].kind > LastAtomicValue
let last = pos + bc.code[pos].rawSpan
inc pos
nextChild bc, pos
while pos < last:
yield CodePos pos
nextChild bc, pos
iterator sonsFrom2(bc: Bytecode; n: CodePos): CodePos =
var pos = n.int
assert bc.code[pos].kind > LastAtomicValue
let last = pos + bc.code[pos].rawSpan
inc pos
nextChild bc, pos
nextChild bc, pos
while pos < last:
yield CodePos pos
nextChild bc, pos
template firstSon(n: CodePos): CodePos = CodePos(n.int+1)
template `[]`(t: Bytecode; n: CodePos): Instr = t.code[n.int]
proc span(bc: Bytecode; pos: int): int {.inline.} =
if bc.code[pos].kind <= LastAtomicValue: 1 else: int(bc.code[pos].operand)
iterator triples*(bc: Bytecode; n: CodePos): (uint32, int, CodePos) =
var pos = n.int
assert bc.code[pos].kind > LastAtomicValue
let last = pos + bc.code[pos].rawSpan
inc pos
while pos < last:
let offset = bc.code[pos].operand
nextChild bc, pos
let size = bc.code[pos].operand.int
nextChild bc, pos
let val = CodePos pos
yield (offset, size, val)
nextChild bc, pos
proc toString*(t: Bytecode; pos: CodePos;
r: var string; nesting = 0) =
if r.len > 0 and r[r.len-1] notin {' ', '\n', '(', '[', '{'}:
r.add ' '
case t[pos].kind
of ImmediateValM:
r.add $t[pos].operand
of IntValM:
r.add "IntVal "
r.add $t.m.lit.numbers[LitId t[pos].operand]
of StrValM:
escapeToNimLit(t.m.lit.strings[LitId t[pos].operand], r)
of LoadLocalM, LoadGlobalM, LoadProcM, AllocLocals, SummonParamM:
r.add $t[pos].kind
r.add ' '
r.add $t[pos].operand
of PragmaIdM:
r.add $cast[PragmaKey](t[pos].operand)
of TypedM:
r.add "T<"
r.add $t[pos].operand
r.add ">"
of NilValM:
r.add "NilVal"
of GotoM, CheckedGotoM:
r.add $t[pos].kind
r.add " L"
r.add $t[pos].operand
else:
r.add $t[pos].kind
r.add "{\n"
for i in 0..<(nesting+1)*2: r.add ' '
for p in sons(t, pos):
toString t, p, r, nesting+1
r.add "\n"
for i in 0..<nesting*2: r.add ' '
r.add "}"
proc debug(b: Bytecode; pos: CodePos) =
var buf = ""
toString(b, pos, buf)
echo buf
type
Preprocessing = object
u: ref Universe
known: Table[LabelId, CodePos]
toPatch: Table[LabelId, seq[CodePos]]
locals: Table[SymId, (uint32, int)] # address, size
thisModule: uint32
localsAddr: uint32
markedWithLabel: IntSet
proc align(address, alignment: uint32): uint32 =
result = (address + (alignment - 1'u32)) and not (alignment - 1'u32)
proc genGoto(c: var Preprocessing; bc: var Bytecode; info: PackedLineInfo; lab: LabelId; opc: OpcodeM) =
let dest = c.known.getOrDefault(lab, CodePos(-1))
if dest.int >= 0:
bc.add info, opc, uint32 dest
else:
let here = CodePos(bc.code.len)
c.toPatch.mgetOrPut(lab, @[]).add here
bc.add info, opc, 1u32 # will be patched once we traversed the label
type
AddrMode = enum
InDotExpr, WantAddr
template maybeDeref(doDeref: bool; size: int; body: untyped) =
var pos = PatchPos(-1)
if doDeref:
pos = prepare(bc, info, LoadM)
bc.add info, ImmediateValM, uint32 size
body
if doDeref:
patch(bc, pos)
proc toReadonlyString(s: string): NimStringVM =
if s.len == 0:
result = NimStringVM(len: 0, p: nil)
else:
result = NimStringVM(len: s.len, p: cast[ptr NimStrPayloadVM](alloc(s.len+1+sizeof(int))))
copyMem(addr result.p.data[0], addr s[0], s.len+1)
result.p.cap = s.len or (1 shl (8 * 8 - 2)) # see also NIM_STRLIT_FLAG
const
ForwardedProc = 10_000_000'u32
proc preprocess(c: var Preprocessing; bc: var Bytecode; t: Tree; n: NodePos; flags: set[AddrMode]) =
let info = t[n].info
template recurse(opc) =
build bc, info, opc:
for ch in sons(t, n): preprocess(c, bc, t, ch, {WantAddr})
case t[n].kind
of Nop, ForeignDecl, ForeignProcDecl:
discard "don't use Nop"
of ImmediateVal:
bc.add info, ImmediateValM, t[n].rawOperand
of IntVal:
bc.add info, IntValM, t[n].rawOperand
of StrVal:
let litId = LitId t[n].rawOperand
if not bc.strings.hasKey(litId):
bc.strings[litId] = toReadonlyString(bc.m.lit.strings[litId])
bc.add info, StrValM, t[n].rawOperand
of SymDef:
discard "happens for proc decls. Don't copy the node as we don't need it"
of SymUse:
let s = t[n].symId
if c.locals.hasKey(s):
let (address, size) = c.locals[s]
maybeDeref(WantAddr notin flags, size):
bc.add info, LoadLocalM, address
elif bc.procs.hasKey(s):
bc.add info, LoadProcM, uint32 bc.procs[s]
elif bc.globals.hasKey(s):
let (address, size) = bc.globals[s]
maybeDeref(WantAddr notin flags, size):
bc.add info, LoadGlobalM, address
else:
let here = CodePos(bc.code.len)
bc.add info, LoadProcM, ForwardedProc + uint32(s)
bc.procUsagesToPatch.mgetOrPut(s, @[]).add here
#raiseAssert "don't understand SymUse ID " & $int(s)
of ModuleSymUse:
when false:
let (x, y) = sons2(t, n)
let unit = c.u.unitNames.getOrDefault(bc.m.lit.strings[t[x].litId], -1)
let s = t[y].symId
if c.u.units[unit].procs.hasKey(s):
bc.add info, LoadProcM, uint32 c.u.units[unit].procs[s]
elif bc.globals.hasKey(s):
maybeDeref(WantAddr notin flags):
build bc, info, LoadGlobalM:
bc.add info, ImmediateValM, uint32 unit
bc.add info, LoadLocalM, uint32 s
else:
raiseAssert "don't understand ModuleSymUse ID"
raiseAssert "don't understand ModuleSymUse ID"
of Typed:
bc.add info, TypedM, t[n].rawOperand
of PragmaId:
bc.add info, PragmaIdM, t[n].rawOperand
of NilVal:
bc.add info, NilValM, t[n].rawOperand
of LoopLabel, Label:
let lab = t[n].label
let here = CodePos(bc.code.len)
c.known[lab] = here
var p: seq[CodePos] = @[]
if c.toPatch.take(lab, p):
for x in p: (bc.code[x]) = toIns(bc.code[x].kind, uint32 here)
c.markedWithLabel.incl here.int # for toString()
of Goto, GotoLoop:
c.genGoto(bc, info, t[n].label, GotoM)
of CheckedGoto:
c.genGoto(bc, info, t[n].label, CheckedGotoM)
of ArrayConstr:
let typ = t[n.firstSon].typeId
let s = computeElemSize(bc, typ)
build bc, info, ArrayConstrM:
bc.add info, ImmediateValM, uint32 s
for ch in sonsFrom1(t, n):
preprocess(c, bc, t, ch, {WantAddr})
of ObjConstr:
#debug bc, t, n
var i = 0
let typ = t[n.firstSon].typeId
build bc, info, ObjConstrM:
for ch in sons(t, n):
if i > 0:
if (i mod 2) == 1:
let (offset, typ) = bc.offsets[typ][t[ch].immediateVal]
let size = computeSize(bc, typ)[0]
bc.add info, ImmediateValM, uint32(offset)
bc.add info, ImmediateValM, uint32(size)
else:
preprocess(c, bc, t, ch, {WantAddr})
inc i
of Ret:
recurse RetM
of Yld:
recurse YldM
of Select:
recurse SelectM
of SelectPair:
recurse SelectPairM
of SelectList:
recurse SelectListM
of SelectValue:
recurse SelectValueM
of SelectRange:
recurse SelectRangeM
of SummonGlobal, SummonThreadLocal, SummonConst:
let (typ, sym) = sons2(t, n)
let s = t[sym].symId
let tid = t[typ].typeId
let (size, alignment) = computeSize(bc, tid)
let global = align(bc.globalsAddr, uint32 alignment)
bc.globals[s] = (global, size)
bc.globalsAddr += uint32 size
assert bc.globalsAddr < GlobalsSize
of Summon:
let (typ, sym) = sons2(t, n)
let s = t[sym].symId
let tid = t[typ].typeId
let (size, alignment) = computeSize(bc, tid)
let local = align(c.localsAddr, uint32 alignment)
c.locals[s] = (local, size)
c.localsAddr += uint32 size
# allocation is combined into the frame allocation so there is no
# instruction to emit
of SummonParam, SummonResult:
let (typ, sym) = sons2(t, n)
let s = t[sym].symId
let tid = t[typ].typeId
let (size, alignment) = computeSize(bc, tid)
let local = align(c.localsAddr, uint32 alignment)
c.locals[s] = (local, size)
c.localsAddr += uint32 size
bc.add info, SummonParamM, local
bc.add info, ImmediateValM, uint32 size
of Kill:
discard "we don't care about Kill instructions"
of AddrOf:
let (_, arg) = sons2(t, n)
preprocess(c, bc, t, arg, {WantAddr})
# the address of x is what the VM works with all the time so there is
# nothing to compute.
of ArrayAt:
let (arrayType, a, i) = sons3(t, n)
let tid = t[arrayType].typeId
let size = uint32 computeElemSize(bc, tid)
build bc, info, ArrayAtM:
bc.add info, ImmediateValM, size
preprocess(c, bc, t, a, {WantAddr})
preprocess(c, bc, t, i, {WantAddr})
of DerefArrayAt:
let (arrayType, a, i) = sons3(t, n)
let tid = t[arrayType].typeId
let size = uint32 computeElemSize(bc, tid)
build bc, info, DerefArrayAtM:
bc.add info, ImmediateValM, size
preprocess(c, bc, t, a, {WantAddr})
preprocess(c, bc, t, i, {WantAddr})
of FieldAt:
let (typ, a, b) = sons3(t, n)
let offset = bc.offsets[t[typ].typeId][t[b].immediateVal][0]
build bc, info, FieldAtM:
preprocess(c, bc, t, a, flags+{WantAddr})
bc.add info, ImmediateValM, uint32(offset)
of DerefFieldAt:
let (typ, a, b) = sons3(t, n)
let offset = bc.offsets[t[typ].typeId][t[b].immediateVal][0]
build bc, info, DerefFieldAtM:
preprocess(c, bc, t, a, flags+{WantAddr})
bc.add info, ImmediateValM, uint32(offset)
of Load:
let (elemType, a) = sons2(t, n)
let tid = t[elemType].typeId
build bc, info, LoadM:
bc.add info, ImmediateValM, uint32 computeSize(bc, tid)[0]
preprocess(c, bc, t, a, {})
of Store:
raiseAssert "Assumption was that Store is unused!"
of Asgn:
let (elemType, dest, src) = sons3(t, n)
let tid = t[elemType].typeId
if t[src].kind in {Call, IndirectCall}:
# No support for return values, these are mapped to `var T` parameters!
build bc, info, CallM:
preprocess(c, bc, t, src.skipTyped, {WantAddr})
preprocess(c, bc, t, dest, {WantAddr})
for ch in sonsFromN(t, src, 2): preprocess(c, bc, t, ch, {WantAddr})
elif t[src].kind in {CheckedCall, CheckedIndirectCall}:
let (_, gotoInstr, fn) = sons3(t, src)
build bc, info, CallM:
preprocess(c, bc, t, fn, {WantAddr})
preprocess(c, bc, t, dest, {WantAddr})
for ch in sonsFromN(t, src, 3): preprocess(c, bc, t, ch, {WantAddr})
preprocess c, bc, t, gotoInstr, {}
elif t[dest].kind == Load:
let (typ, a) = sons2(t, dest)
let s = computeSize(bc, tid)[0]
build bc, info, StoreM:
bc.add info, ImmediateValM, uint32 s
preprocess(c, bc, t, a, {WantAddr})
preprocess(c, bc, t, src, {})
else:
let s = computeSize(bc, tid)[0]
build bc, info, AsgnM:
bc.add info, ImmediateValM, uint32 s
preprocess(c, bc, t, dest, {WantAddr})
preprocess(c, bc, t, src, {})
of SetExc:
recurse SetExcM
of TestExc:
recurse TestExcM
of CheckedRange:
recurse CheckedRangeM
of CheckedIndex:
recurse CheckedIndexM
of Call, IndirectCall:
# avoid the Typed thing at position 0:
build bc, info, CallM:
for ch in sonsFrom1(t, n): preprocess(c, bc, t, ch, {WantAddr})
of CheckedCall, CheckedIndirectCall:
# avoid the Typed thing at position 0:
let (_, gotoInstr, fn) = sons3(t, n)
build bc, info, CallM:
preprocess(c, bc, t, fn, {WantAddr})
for ch in sonsFromN(t, n, 3): preprocess(c, bc, t, ch, {WantAddr})
preprocess c, bc, t, gotoInstr, {WantAddr}
of CheckedAdd:
recurse CheckedAddM
of CheckedSub:
recurse CheckedSubM
of CheckedMul:
recurse CheckedMulM
of CheckedDiv:
recurse CheckedDivM
of CheckedMod:
recurse CheckedModM
of Add:
recurse AddM
of Sub:
recurse SubM
of Mul:
recurse MulM
of Div:
recurse DivM
of Mod:
recurse ModM
of BitShl:
recurse BitShlM
of BitShr:
recurse BitShrM
of BitAnd:
recurse BitAndM
of BitOr:
recurse BitOrM
of BitXor:
recurse BitXorM
of BitNot:
recurse BitNotM
of BoolNot:
recurse BoolNotM
of Eq:
recurse EqM
of Le:
recurse LeM
of Lt:
recurse LtM
of Cast:
recurse CastM
of NumberConv:
recurse NumberConvM
of CheckedObjConv:
recurse CheckedObjConvM
of ObjConv:
recurse ObjConvM
of TestOf:
recurse TestOfM
of Emit:
raiseAssert "cannot interpret: Emit"
of ProcDecl:
var c2 = Preprocessing(u: c.u, thisModule: c.thisModule)
let sym = t[n.firstSon].symId
let here = CodePos(bc.len)
var p: seq[CodePos] = @[]
if bc.procUsagesToPatch.take(sym, p):
for x in p: (bc.code[x]) = toIns(bc.code[x].kind, uint32 here)
bc.procs[sym] = here
build bc, info, ProcDeclM:
let toPatch = bc.code.len
bc.add info, AllocLocals, 0'u32
for ch in sons(t, n): preprocess(c2, bc, t, ch, {})
bc.code[toPatch] = toIns(AllocLocals, c2.localsAddr)
when false:
if here.int == 39850:
debug bc, t, n
debug bc, here
of PragmaPair:
recurse PragmaPairM
const PayloadSize = 128
type
StackFrame = ref object
locals: pointer # usually points into `payload` if size is small enough, otherwise it's `alloc`'ed.
payload: array[PayloadSize, byte]
caller: StackFrame
returnAddr: CodePos
jumpTo: CodePos # exception handling
u: ref Universe
proc newStackFrame(size: int; caller: StackFrame; returnAddr: CodePos): StackFrame =
result = StackFrame(caller: caller, returnAddr: returnAddr, u: caller.u)
if size <= PayloadSize:
result.locals = addr(result.payload)
else:
result.locals = alloc0(size)
proc popStackFrame(s: StackFrame): StackFrame =
if s.locals != addr(s.payload):
dealloc s.locals
result = s.caller
template `+!`(p: pointer; diff: uint): pointer = cast[pointer](cast[uint](p) + diff)
proc isAtom(tree: seq[Instr]; pos: CodePos): bool {.inline.} = tree[pos.int].kind <= LastAtomicValue
proc span(bc: seq[Instr]; pos: int): int {.inline.} =
if bc[pos].kind <= LastAtomicValue: 1 else: int(bc[pos].operand)
proc sons2(tree: seq[Instr]; n: CodePos): (CodePos, CodePos) =
assert(not isAtom(tree, n))
let a = n.int+1
let b = a + span(tree, a)
result = (CodePos a, CodePos b)
proc sons3(tree: seq[Instr]; n: CodePos): (CodePos, CodePos, CodePos) =
assert(not isAtom(tree, n))
let a = n.int+1
let b = a + span(tree, a)
let c = b + span(tree, b)
result = (CodePos a, CodePos b, CodePos c)
proc sons4(tree: seq[Instr]; n: CodePos): (CodePos, CodePos, CodePos, CodePos) =
assert(not isAtom(tree, n))
let a = n.int+1
let b = a + span(tree, a)
let c = b + span(tree, b)
let d = c + span(tree, c)
result = (CodePos a, CodePos b, CodePos c, CodePos d)
proc typeId*(ins: Instr): TypeId {.inline.} =
assert ins.kind == TypedM
result = TypeId(ins.operand)
proc immediateVal*(ins: Instr): int {.inline.} =
assert ins.kind == ImmediateValM
result = cast[int](ins.operand)
proc litId*(ins: Instr): LitId {.inline.} =
assert ins.kind in {StrValM, IntValM}
result = LitId(ins.operand)
proc eval(c: Bytecode; pc: CodePos; s: StackFrame; result: pointer; size: int)
proc evalAddr(c: Bytecode; pc: CodePos; s: StackFrame): pointer =
case c.code[pc].kind
of LoadLocalM:
result = s.locals +! c.code[pc].operand
of FieldAtM:
let (x, offset) = sons2(c.code, pc)
result = evalAddr(c, x, s)
result = result +! c.code[offset].operand
of DerefFieldAtM:
let (x, offset) = sons2(c.code, pc)
let p = evalAddr(c, x, s)
result = cast[ptr pointer](p)[] +! c.code[offset].operand
of ArrayAtM:
let (e, a, i) = sons3(c.code, pc)
let elemSize = c.code[e].operand
result = evalAddr(c, a, s)
var idx: int = 0
eval(c, i, s, addr idx, sizeof(int))
result = result +! (uint32(idx) * elemSize)
of DerefArrayAtM:
let (e, a, i) = sons3(c.code, pc)
let elemSize = c.code[e].operand
var p = evalAddr(c, a, s)
var idx: int = 0
eval(c, i, s, addr idx, sizeof(int))
result = cast[ptr pointer](p)[] +! (uint32(idx) * elemSize)
of LoadGlobalM:
result = c.globalData +! c.code[pc].operand
else:
raiseAssert("unimplemented addressing mode")
proc `div`(x, y: float32): float32 {.inline.} = x / y
proc `div`(x, y: float64): float64 {.inline.} = x / y
from std / math import `mod`
template binop(opr) {.dirty.} =
template impl(typ) {.dirty.} =
var x = default(typ)
var y = default(typ)
eval c, a, s, addr x, sizeof(typ)
eval c, b, s, addr y, sizeof(typ)
cast[ptr typ](result)[] = opr(x, y)
let (t, a, b) = sons3(c.code, pc)
let tid = TypeId c.code[t].operand
case tid
of Bool8Id, Char8Id, UInt8Id: impl uint8
of Int8Id: impl int8
of Int16Id: impl int16
of Int32Id: impl int32
of Int64Id: impl int64
of UInt16Id: impl uint16
of UInt32Id: impl uint32
of UInt64Id: impl uint64
of Float32Id: impl float32
of Float64Id: impl float64
else: discard
template checkedBinop(opr) {.dirty.} =
template impl(typ) {.dirty.} =
var x = default(typ)
var y = default(typ)
eval c, a, s, addr x, sizeof(typ)
eval c, b, s, addr y, sizeof(typ)
try:
cast[ptr typ](result)[] = opr(x, y)
except OverflowDefect, DivByZeroDefect:
s.jumpTo = CodePos c.code[j].operand
let (t, j, a, b) = sons4(c.code, pc)
let tid = TypeId c.code[t].operand
case tid
of Bool8Id, Char8Id, UInt8Id: impl uint8
of Int8Id: impl int8
of Int16Id: impl int16
of Int32Id: impl int32
of Int64Id: impl int64
of UInt16Id: impl uint16
of UInt32Id: impl uint32
of UInt64Id: impl uint64
of Float32Id: impl float32
of Float64Id: impl float64
else: discard
template bitop(opr) {.dirty.} =
template impl(typ) {.dirty.} =
var x = default(typ)
var y = default(typ)
eval c, a, s, addr x, sizeof(typ)
eval c, b, s, addr y, sizeof(typ)
cast[ptr typ](result)[] = opr(x, y)
let (t, a, b) = sons3(c.code, pc)
let tid = c.code[t].typeId
case tid
of Bool8Id, Char8Id, UInt8Id: impl uint8
of Int8Id: impl int8
of Int16Id: impl int16
of Int32Id: impl int32
of Int64Id: impl int64
of UInt16Id: impl uint16
of UInt32Id: impl uint32
of UInt64Id: impl uint64
else: discard
template cmpop(opr) {.dirty.} =
template impl(typ) {.dirty.} =
var x = default(typ)
var y = default(typ)
eval c, a, s, addr x, sizeof(typ)
eval c, b, s, addr y, sizeof(typ)
cast[ptr bool](result)[] = opr(x, y)
let (t, a, b) = sons3(c.code, pc)
let tid = c.code[t].typeId
case tid
of Bool8Id, Char8Id, UInt8Id: impl uint8
of Int8Id: impl int8
of Int16Id: impl int16
of Int32Id: impl int32
of Int64Id: impl int64
of UInt16Id: impl uint16
of UInt32Id: impl uint32
of UInt64Id: impl uint64
of Float32Id: impl float32
of Float64Id: impl float64
else: discard
proc evalSelect(c: Bytecode; pc: CodePos; s: StackFrame): CodePos =
template impl(typ) {.dirty.} =
var selector = default(typ)
eval c, sel, s, addr selector, sizeof(typ)
for pair in sonsFrom2(c, pc):
assert c.code[pair].kind == SelectPairM
let (values, action) = sons2(c.code, pair)
if c.code[values].kind == SelectValueM:
var a = default(typ)
eval c, values.firstSon, s, addr a, sizeof(typ)
if selector == a:
return CodePos c.code[action].operand
else:
assert c.code[values].kind == SelectListM, $c.code[values].kind
for v in sons(c, values):
case c.code[v].kind
of SelectValueM:
var a = default(typ)
eval c, v.firstSon, s, addr a, sizeof(typ)
if selector == a:
return CodePos c.code[action].operand
of SelectRangeM:
let (va, vb) = sons2(c.code, v)
var a = default(typ)
eval c, va, s, addr a, sizeof(typ)
var b = default(typ)
eval c, vb, s, addr a, sizeof(typ)
if a <= selector and selector <= b:
return CodePos c.code[action].operand
else: raiseAssert "unreachable"
result = CodePos(-1)
let (t, sel) = sons2(c.code, pc)
let tid = c.code[t].typeId
case tid
of Bool8Id, Char8Id, UInt8Id: impl uint8
of Int8Id: impl int8
of Int16Id: impl int16
of Int32Id: impl int32
of Int64Id: impl int64
of UInt16Id: impl uint16
of UInt32Id: impl uint32
of UInt64Id: impl uint64
else: raiseAssert "unreachable"
proc eval(c: Bytecode; pc: CodePos; s: StackFrame; result: pointer; size: int) =
case c.code[pc].kind
of LoadLocalM:
let src = s.locals +! c.code[pc].operand
copyMem result, src, size
of FieldAtM, DerefFieldAtM, ArrayAtM, DerefArrayAtM, LoadGlobalM:
let src = evalAddr(c, pc, s)
copyMem result, src, size
of LoadProcM:
let procAddr = c.code[pc].operand
cast[ptr pointer](result)[] = cast[pointer](procAddr)
of LoadM:
let (_, arg) = sons2(c.code, pc)
let src = evalAddr(c, arg, s)
copyMem result, src, size
of CheckedAddM: checkedBinop `+`
of CheckedSubM: checkedBinop `-`
of CheckedMulM: checkedBinop `*`
of CheckedDivM: checkedBinop `div`
of CheckedModM: checkedBinop `mod`
of AddM: binop `+`
of SubM: binop `-`
of MulM: binop `*`
of DivM: binop `div`
of ModM: binop `mod`
of BitShlM: bitop `shl`
of BitShrM: bitop `shr`
of BitAndM: bitop `and`
of BitOrM: bitop `or`
of BitXorM: bitop `xor`
of EqM: cmpop `==`
of LeM: cmpop `<=`
of LtM: cmpop `<`
of StrValM:
# binary compatible and no deep copy required:
copyMem(cast[ptr string](result), addr(c.strings[c[pc].litId]), sizeof(string))
of ObjConstrM:
for offset, size, val in triples(c, pc):
eval c, val, s, result+!offset, size
of ArrayConstrM:
let elemSize = c.code[pc.firstSon].operand
var r = result
for ch in sonsFrom1(c, pc):
eval c, ch, s, r, elemSize.int
r = r+!elemSize # can even do strength reduction here!
of NumberConvM:
let (t, x) = sons2(c.code, pc)
let word = if c[x].kind == NilValM: 0'i64 else: c.m.lit.numbers[c[x].litId]
template impl(typ: typedesc) {.dirty.} =
cast[ptr typ](result)[] = cast[typ](word)
let tid = c.code[t].typeId
case tid
of Bool8Id, Char8Id, UInt8Id: impl uint8
of Int8Id: impl int8
of Int16Id: impl int16
of Int32Id: impl int32
of Int64Id: impl int64
of UInt16Id: impl uint16
of UInt32Id: impl uint32
of UInt64Id: impl uint64
of Float32Id: impl float32
of Float64Id: impl float64
else:
case c.m.types[tid].kind
of ProcTy, UPtrTy, APtrTy, AArrayPtrTy, UArrayPtrTy:
# the VM always uses 64 bit pointers:
impl uint64
else:
raiseAssert "cannot happen: " & $c.m.types[tid].kind
else:
#debug c, c.debug[pc.int]
raiseAssert "cannot happen: " & $c.code[pc].kind
proc evalProc(c: Bytecode; pc: CodePos; s: StackFrame): CodePos =
assert c.code[pc].kind == LoadProcM
let procSym = c[pc].operand
when false:
if procSym >= ForwardedProc:
for k, v in c.procUsagesToPatch:
if uint32(k) == procSym - ForwardedProc:
echo k.int, " ", v.len, " <-- this one"
else:
echo k.int, " ", v.len
assert procSym < ForwardedProc
result = CodePos(procSym)
proc echoImpl(c: Bytecode; pc: CodePos; frame: StackFrame) =
var s = default(NimStringVM)
for a in sonsFrom1(c, pc):
assert c[a].kind == ArrayConstrM
let elemSize = c.code[a.firstSon].operand.int
for ch in sonsFrom1(c, a):
eval c, ch, frame, addr s, elemSize
if s.len > 0:
discard stdout.writeBuffer(addr(s.p.data[0]), s.len)
stdout.write "\n"
stdout.flushFile()
type
EvalBuiltinState = enum
DidNothing, DidEval, DidError
proc evalBuiltin(c: Bytecode; pc: CodePos; s: StackFrame; prc: CodePos; state: var EvalBuiltinState): CodePos =
var prc = prc
while true:
case c[prc].kind
of PragmaPairM:
let (x, y) = sons2(c.code, prc)
let key = cast[PragmaKey](c[x].operand)
case key
of CoreName:
let lit = c[y].litId
case c.m.lit.strings[lit]
of "echoBinSafe": echoImpl(c, pc, s)
else:
raiseAssert "cannot eval: " & c.m.lit.strings[lit]
state = DidEval
of HeaderImport, DllImport:
let lit = c[y].litId
raiseAssert "cannot eval: " & c.m.lit.strings[lit]
else: discard
of PragmaIdM, AllocLocals: discard
else: break
next c, prc
result = prc
proc exec(c: Bytecode; pc: CodePos; u: ref Universe) =
var pc = pc
var frame = StackFrame(u: u)
while pc.int < c.code.len:
when false: # c.interactive:
echo "running: ", pc.int
debug c, pc
case c.code[pc].kind
of GotoM:
pc = CodePos(c.code[pc].operand)
of AsgnM:
let (sz, a, b) = sons3(c.code, pc)
let dest = evalAddr(c, a, frame)
eval(c, b, frame, dest, c.code[sz].operand.int)
next c, pc
of StoreM:
let (sz, a, b) = sons3(c.code, pc)
let destPtr = evalAddr(c, a, frame)
let dest = cast[ptr pointer](destPtr)[]
eval(c, b, frame, dest, c.code[sz].operand.int)
next c, pc
of CallM:
# No support for return values, these are mapped to `var T` parameters!
var prc = evalProc(c, pc.firstSon, frame)
assert c.code[prc.firstSon].kind == AllocLocals
let frameSize = int c.code[prc.firstSon].operand
# skip stupid stuff:
var evalState = DidNothing
prc = evalBuiltin(c, pc, frame, prc.firstSon, evalState)
if evalState != DidNothing:
next c, pc
if pc.int < c.code.len and c.code[pc].kind == CheckedGotoM:
if evalState == DidEval:
next c, pc
else:
pc = CodePos(c.code[pc].operand)
else:
# setup storage for the proc already:
let callInstr = pc
next c, pc
let s2 = newStackFrame(frameSize, frame, pc)
for a in sonsFrom1(c, callInstr):
assert c[prc].kind == SummonParamM
let paramAddr = c[prc].operand
next c, prc
assert c[prc].kind == ImmediateValM
let paramSize = c[prc].operand.int
next c, prc
eval(c, a, s2, s2.locals +! paramAddr, paramSize)
frame = s2
pc = prc
of RetM:
pc = frame.returnAddr
if c.code[pc].kind == CheckedGotoM:
pc = frame.jumpTo
frame = popStackFrame(frame)
of SelectM:
let pc2 = evalSelect(c, pc, frame)
if pc2.int >= 0:
pc = pc2
else:
next c, pc
of ProcDeclM:
next c, pc
else:
#debug c, c.debug[pc.int]
raiseAssert "unreachable: " & $c.code[pc].kind
proc execCode*(bc: var Bytecode; t: Tree; n: NodePos) =
traverseTypes bc
var c = Preprocessing(u: nil, thisModule: 1'u32)
let start = CodePos(bc.code.len)
var pc = n
while pc.int < t.len:
#if bc.interactive:
# echo "RUnning: "
# debug bc, t, pc
preprocess c, bc, t, pc, {}
next t, pc
exec bc, start, nil