# # # 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 ; 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 ; 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)