123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176 |
- #
- #
- # 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
|