Files
Nim/compiler/nir/nirvm.nim
2023-10-16 00:01:33 +02:00

468 lines
11 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 / [tables, intsets]
import ".." / ic / bitabs
import nirinsts, nirtypes
type
OpcodeM = enum
ImmediateValM,
IntValM,
StrValM,
LoadLocalM, # with local ID
TypedM, # with type ID
PragmaIdM, # with Pragma ID, possible values: see PragmaKey enum
NilValM,
GotoM,
CheckedGotoM, # last atom
LoadProcM,
LoadGlobalM, # `"module".x`
ArrayConstrM,
ObjConstrM,
RetM,
YldM,
SelectM,
SelectPairM, # ((values...), Label)
SelectListM, # (values...)
SelectValueM, # (value)
SelectRangeM, # (valueA..valueB)
SummonGlobalM,
SummonThreadLocalM,
SummonM, # x = Summon Typed <Type ID>; x begins to live
SummonParamM,
AddrOfM,
ArrayAtM, # addr(a[i])
FieldAtM, # addr(obj.field)
LoadM, # a[]
StoreM, # a[] = b
AsgnM, # a = b
SetExcM,
TestExcM,
CheckedRangeM,
CheckedIndexM,
CallM,
IndirectCallM,
CheckedCallM, # call that can raise
CheckedIndirectCallM, # call that can raise
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 and OpcodeMask)
template operand(n: Instr): uint32 = (n 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
PatchPos = distinct int
CodePos = distinct int
Unit = ref object ## a NIR module
procs: Table[SymId, CodePos]
globals: Table[SymId, uint32]
integers: BiTable[int64]
strings: BiTable[string]
globalsGen: uint32
Universe* = object ## all units: For interpretation we need that
units: Table[string, Unit]
Bytecode = object
code: seq[Instr]
debug: seq[PackedLineInfo]
u: Unit
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, uint(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
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
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)
type
Preprocessing = object
known: Table[LabelId, CodePos]
toPatch: Table[LabelId, seq[CodePos]]
locals: Table[SymId, uint32]
c: Bytecode # to be moved out
thisModule: LitId
markedWithLabel: IntSet
proc genGoto(c: var Preprocessing; lab: LabelId; opc: OpcodeM) =
let dest = c.known.getOrDefault(lab, CodePos(-1))
if dest.int >= 0:
c.bc.add info, opc, uint32 dest
else:
let here = CodePos(c.bc.code.len)
c.toPatch.mgetOrPut(lab, @[]).add here
c.bc.add info, opc, 1u32 # will be patched once we traversed the label
proc preprocess(c: var Preprocessing; u: var Universe; t: Tree; n: NodePos) =
let info = t[n].info
template recurse(opc) =
build c.bc, info, opc:
for c in sons(t, n): preprocess(c, u, t, c)
case t[n].kind
of Nop:
discard "don't use Nop"
of ImmediateVal:
c.bc.add info, ImmediateValM, t[n].rawOperand
of IntVal:
c.bc.add info, IntValM, t[n].rawOperand
of StrVal:
c.bc.add info, StrValM, t[n].rawOperand
of SymDef:
assert false, "SymDef outside of declaration context"
of SymUse:
let s = t[n].symId
if c.locals.hasKey(s):
c.bc.add info, LoadLocalM, c.locals[s]
elif c.bc.u.procs.hasKey(s):
build c.bc, info, LoadProcM:
c.bc.add info, StrValM, thisModule
c.bc.add info, LoadLocalM, uint32 c.bc.u.procs[s]
elif c.bc.u.globals.hasKey(s):
build c.bc, info, LoadGlobalM:
c.bc.add info, StrValM, thisModule
c.bc.add info, LoadLocalM, uint32 s
else:
assert false, "don't understand SymUse ID"
of ModuleSymUse:
let moduleName {.cursor.} = c.bc.u.strings[t[n.firstSon].litId]
let unit = u.units.getOrDefault(moduleName)
of Typed:
c.bc.add info, TypedM, t[n].rawOperand
of PragmaId:
c.bc.add info, TypedM, t[n].rawOperand
of NilVal:
c.bc.add info, NilValM, t[n].rawOperand
of LoopLabel, Label:
let lab = t[n].label
let here = CodePos(c.bc.code.len-1)
c.known[lab] = here
var p: seq[CodePos]
if c.toPatch.take(lab, p):
for x in p: c.bc.code[x] = toIns(c.bc.code[x].kind, here)
c.markedWithLabel.incl here.int # for toString()
of Goto, GotoLoop:
c.genGoto(t[n].label, GotoM)
of CheckedGoto:
c.genGoto(t[n].label, CheckedGotoM)
of ArrayConstr:
recurse ArrayConstrM
of ObjConstr:
recurse ObjConstrM
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 s =
discard "xxx"
of Summon, SummonParam:
# x = Summon Typed <Type ID>; x begins to live
discard "xxx"
of Kill:
discard "we don't care about Kill instructions"
of AddrOf:
recurse AddrOfM
of ArrayAt:
recurse ArrayAtM
of FieldAt:
recurse FieldAtM
of Load:
recurse LoadM
of Store:
recurse StoreM
of Asgn:
recurse AsgnM
of SetExc:
recurse SetExcM
of TestExc:
recurse TestExcM
of CheckedRange:
recurse CheckedRangeM
of CheckedIndex:
recurse CheckedIndexM
of Call:
recurse CallM
of IndirectCall:
recurse IndirectCallM
of CheckedCall:
recurse CheckedCallM
of CheckedIndirectCall:
recurse CheckedIndirectCallM
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:
assert false, "cannot interpret: Emit"
of ProcDecl:
recurse ProcDeclM
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
proc newStackFrame(size: int; caller: StackFrame; returnAddr: CodePos): StackFrame =
result = StackFrame(caller: caller, returnAddr: returnAddr)
if size <= PayloadSize:
result.locals = addr(result.payload)
else:
result.locals = alloc0(size)
proc popStackFrame(s: StackFrame): StackFrame =
if result.locals != addr(result.payload):
dealloc result.locals
result = s.caller
template `+!`(p: pointer; diff: uint): pointer = cast[pointer](cast[uint](p) + diff)
proc eval(c: seq[Instr]; pc: CodePos; s: StackFrame; result: pointer)
proc evalAddr(c: seq[Instr]; pc: CodePos; s: StackFrame): pointer =
case c[pc].kind
of LoadLocalM:
result = s.locals +! c[pc].operand
of FieldAtM:
result = eval(c, pc+1, s)
result = result +! c[pc+2].operand
of ArrayAtM:
let elemSize = c[pc+1].operand
result = eval(c, pc+2, s)
var idx: int
eval(c, pc+3, addr idx)
result = result +! (idx * elemSize)
proc eval(c: seq[Instr]; pc: CodePos; s: StackFrame; result: pointer) =
case c[pc].kind
of AddM:
# assume `int` here for now:
var x, y: int
eval c, pc+1, s, addr x
eval c, pc+2, s, addr y
cast[ptr int](res)[] = x + y
of StrValM:
cast[ptr StringDesc](res)[] = addr(c.strings[c[pc].litId])
of ObjConstrM:
for ch in sons(c, pc):
let offset = c[ch]
eval c, ch+2, s, result+!offset
of ArrayConstrM:
let elemSize = c[pc+1].operand
var r = result
for ch in sons(c, pc):
eval c, ch, s, r
r = r+!elemSize # can even do strength reduction here!
else:
assert false, "cannot happen"
proc exec(c: seq[Instr]; pc: CodePos) =
var pc = pc
var currentFrame: StackFrame = nil
while true:
case c[pc].kind
of GotoM:
pc = CodePos(c[pc].operand)
of Asgn:
let (size, a, b) = sons3(c, pc)
let dest = evalAddr(c, a, s)
eval(c, b, s, dest)
of CallM:
# No support for return values, these are mapped to `var T` parameters!
let prc = evalProc(c, pc+1)
# setup storage for the proc already:
let s2 = newStackFrame(prc.frameSize, currentFrame, pc)
var i = 0
for a in sons(c, pc):
eval(c, a, s2, paramAddr(s2, i))
inc i
currentFrame = s2
pc = pcOf(prc)
of RetM:
pc = currentFrame.returnAddr
currentFrame = popStackFrame(currentFrame)
of SelectM:
var x: bool
eval(c, b, addr x)
# follow the selection instructions...
pc = activeBranch(c, b, x)