Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vmjit: use MIR-based dependency discovery #810

Merged
merged 3 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions compiler/backend/backends.nim
Original file line number Diff line number Diff line change
Expand Up @@ -903,10 +903,30 @@ func register*(data: var DiscoveryData, prc: PSym) =
## of known procedures.
register(data, procedures, prc)

func registerGlobal*(data: var DiscoveryData, sym: PSym) {.inline.} =
## If not already known to `data`, adds the global `sym` to the list of
## known globals.
register(data, globals, sym)

func registerLate*(discovery: var DiscoveryData, prc: PSym, module: FileIndex) =
## Registers a late late-dependency with `data`. These are dependencies
## that were raised while processing some code fragment, but that are not
## directly related to said fragment. They should be kept to a minimum, and
## `register <#register,DiscoveryData,PSym>`_ should be preferred whenever
## possible.
discovery.additional.add (module, prc)

func rewind*(data: var DiscoveryData) =
## Un-discovers all not-yet-processed procedures, globals, threadvars,
## and constants. After the call to ``rewind``, it will appear as if the
## dropped entities were never registered with `data`.
template rewindQueue(q: Queue[PSym]) =
for _, it in peek(q):
data.seen.excl(it.id)

q.data.setLen(q.progress) # remove all unprocessed items

rewindQueue(data.procedures)
rewindQueue(data.constants)
rewindQueue(data.globals)
rewindQueue(data.threadvars)
23 changes: 5 additions & 18 deletions compiler/mir/mirbridge.nim
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ proc rewriteGlobalDefs*(body: var MirTree, sourceMap: var SourceMap,
sym = restoreGlobal(body[def].sym)
typ = sym.typ
changes.seek(i)
if hasInput(body, Operation i):
# HACK: ``vmjit`` currently passes us expressions where a 'def' can
# be the very first node, something that ``hasInput`` doesn't
# support. We thus have to guard against i == 0
if i.int > 0 and hasInput(body, Operation i):
# the global has a starting value
changes.replaceMulti(buf):
let tmp = changes.getTemp()
Expand Down Expand Up @@ -171,20 +174,4 @@ proc canonicalize*(graph: ModuleGraph, idgen: IdGenerator, owner: PSym,

# step 2: translate it back
result = generateAST(graph, idgen, owner, tree, sourceMap)
echoOutput(graph.config, owner, result)

proc canonicalizeSingle*(graph: ModuleGraph, idgen: IdGenerator, owner: PSym,
n: PNode, options: set[GenOption]): PNode =
## Similar to ``canonicalize``, but accepts a freestanding expression or
## statement. The `owner` is used as the owner when generating the necessary
## new symbols or types
var
tree: MirTree
sourceMap: SourceMap

# step 1: generate a ``MirTree`` from the input AST
generateCode(graph, options, n, tree, sourceMap)
# step 2: translate it back, but only if there is something to translate
result =
if tree.len > 0: generateAST(graph, idgen, owner, tree, sourceMap)
else: newNode(nkEmpty)
echoOutput(graph.config, owner, result)
70 changes: 38 additions & 32 deletions compiler/vm/compilerbridge.nim
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import
idioms
],
compiler/vm/[
vmaux,
vmcompilerserdes,
vmdef,
vmjit,
Expand Down Expand Up @@ -94,6 +93,7 @@ type
## it. An ``EvalContext`` instance makes up everything that is required
## for running code at compile-time.
vm*: TCtx
jit*: JitState

oldErrorCount: int

Expand All @@ -103,9 +103,13 @@ const evalMacroLimit = 1000
# prevent a default `$` implementation from being generated
func `$`(e: ExecErrorReport): string {.error.}

proc putIntoReg(dest: var TFullReg; c: var TCtx, n: PNode, formal: PType) =
proc putIntoReg(dest: var TFullReg; jit: var JitState, c: var TCtx, n: PNode,
formal: PType) =
## Put the value that is represented by `n` (but not the node itself) into
## `dest`. Implicit conversion is also performed, if necessary.
# XXX: requring access to the JIT state here is all kinds of wrong and
# indicates that ``putIntoReg`` is not a good idea to begin with. The
# XXX comment below describes a good way to get out of this mess
let t = formal.skipTypes(abstractInst+{tyStatic}-{tyTypeDesc})

# XXX: instead of performing conversion here manually, sem could generate a
Expand All @@ -114,22 +118,22 @@ proc putIntoReg(dest: var TFullReg; c: var TCtx, n: PNode, formal: PType) =
# the code here obsolete while also eliminating unnecessary
# deserialize/serialize round-trips

proc registerProcs(c: var TCtx, n: PNode) =
proc registerProcs(jit: var JitState, c: var TCtx, n: PNode) =
# note: this kind of scanning only works for AST representing concrete
# values
case n.kind
of nkSym:
if n.sym.kind in routineKinds:
discard registerProc(c, n.sym)
discard registerProcedure(jit, c, n.sym)
of nkWithoutSons - {nkSym}:
discard "not relevant"
of nkWithSons:
for it in n.items:
registerProcs(c, it)
registerProcs(jit, c, it)

# create a function table entry for each procedure referenced by `n` --
# ``serialize`` depends on it
registerProcs(c, n)
registerProcs(jit, c, n)

case t.kind
of tyBool, tyChar, tyEnum, tyInt..tyInt64, tyUInt..tyUInt64:
Expand Down Expand Up @@ -311,7 +315,7 @@ proc createLegacyStackTrace(
location: some source(c, thread),
reportInst: toReportLineInfo(instLoc))

proc execute(c: var TCtx, start: int, frame: sink TStackFrame;
proc execute(jit: var JitState, c: var TCtx, start: int, frame: sink TStackFrame;
cb: proc(c: TCtx, r: TFullReg): PNode
): ExecutionResult {.inline.} =
## This is the entry point for invoking the VM to execute code at
Expand Down Expand Up @@ -357,7 +361,7 @@ proc execute(c: var TCtx, start: int, frame: sink TStackFrame;
of yrkMissingProcedure:
# a stub entry was encountered -> generate the code for the
# corresponding procedure
let res = compile(c, r.entry)
let res = compile(jit, c, r.entry)
if res.isErr:
# code-generation failed
result.initFailure:
Expand All @@ -378,10 +382,10 @@ proc execute(c: var TCtx, start: int, frame: sink TStackFrame;

dispose(c, thread)

proc execute(c: var TCtx, info: CodeInfo): ExecutionResult =
proc execute(jit: var JitState, c: var TCtx, info: CodeInfo): ExecutionResult =
var tos = TStackFrame(prc: nil, comesFrom: 0)
tos.slots.newSeq(info.regCount)
execute(c, info.start, tos,
execute(jit, c, info.start, tos,
proc(c: TCtx, r: TFullReg): PNode = c.graph.emptyNode)

template returnOnErr(res: VmGenResult, config: ConfigRef, node: PNode): CodeInfo =
Expand Down Expand Up @@ -434,14 +438,14 @@ template mkCallback(cn, rn, body): untyped =
let p = proc(cn: TCtx, rn: TFullReg): PNode = body
p

proc evalStmt(c: var TCtx, n: PNode): PNode =
proc evalStmt(jit: var JitState, c: var TCtx, n: PNode): PNode =
let n = transformExpr(c.graph, c.idgen, c.module, n)
let info = genStmt(c, n).returnOnErr(c.config, n)
let info = genStmt(jit, c, n).returnOnErr(c.config, n)

# execute new instructions; this redundant opcEof check saves us lots
# of allocations in 'execute':
if c.code[info.start].opcode != opcEof:
result = execute(c, info).unpackResult(c.config, n)
result = execute(jit, c, info).unpackResult(c.config, n)
else:
result = c.graph.emptyNode

Expand All @@ -462,21 +466,21 @@ proc setupGlobalCtx*(module: PSym; graph: ModuleGraph; idgen: IdGenerator) =
let c = PEvalContext(graph.vm)
refresh(c.vm, module, idgen)

proc eval(c: var TCtx; prc: PSym, n: PNode): PNode =
proc eval(jit: var JitState, c: var TCtx; prc: PSym, n: PNode): PNode =
let
n = transformExpr(c.graph, c.idgen, c.module, n)
requiresValue = c.mode != emStaticStmt
r =
if requiresValue: genExpr(c, n)
else: genStmt(c, n)
if requiresValue: genExpr(jit, c, n)
else: genStmt(jit, c, n)

let (start, regCount) = r.returnOnErr(c.config, n)

if c.code[start].opcode == opcEof: return newNodeI(nkEmpty, n.info)
assert c.code[start].opcode != opcEof
when defined(nimVMDebugGenerate):
c.config.localReport():
initVmCodeListingReport(c[], prc, n)
initVmCodeListingReport(c, prc, n)

var tos = TStackFrame(prc: prc, comesFrom: 0)
tos.slots.newSeq(regCount)
Expand All @@ -487,7 +491,7 @@ proc eval(c: var TCtx; prc: PSym, n: PNode): PNode =
else:
mkCallback(c, r): newNodeI(nkEmpty, n.info)

result = execute(c, start, tos, cb).unpackResult(c.config, n)
result = execute(jit, c, start, tos, cb).unpackResult(c.config, n)

proc evalConstExprAux(module: PSym, idgen: IdGenerator, g: ModuleGraph,
prc: PSym, n: PNode,
Expand All @@ -501,7 +505,7 @@ proc evalConstExprAux(module: PSym, idgen: IdGenerator, g: ModuleGraph,

# update the mode, and restore it once we're done
c.vm.mode = mode
result = eval(c.vm, prc, n)
result = eval(c.jit, c.vm, prc, n)
c.vm.mode = oldMode

proc evalConstExpr*(module: PSym; idgen: IdGenerator; g: ModuleGraph; e: PNode): PNode {.inline.} =
Expand All @@ -518,18 +522,19 @@ proc setupCompileTimeVar*(module: PSym; idgen: IdGenerator; g: ModuleGraph; n: P
# TODO: the node needs to be returned to the caller instead
reportIfError(g.config, r)

proc setupMacroParam(reg: var TFullReg, c: var TCtx, x: PNode, typ: PType) =
proc setupMacroParam(reg: var TFullReg, jit: var JitState, c: var TCtx, x: PNode, typ: PType) =
case typ.kind
of tyStatic:
putIntoReg(reg, c, x, typ)
putIntoReg(reg, jit, c, x, typ)
else:
var n = x
if n.kind in {nkHiddenSubConv, nkHiddenStdConv}: n = n[1]
# TODO: is anyone on the callsite dependent on this modifiction of `x`?
n.typ = x.typ
reg = TFullReg(kind: rkNimNode, nimNode: n)

proc evalMacroCall*(c: var TCtx, call, args: PNode, sym: PSym): PNode =
proc evalMacroCall*(jit: var JitState, c: var TCtx, call, args: PNode,
sym: PSym): PNode =
## Evaluates a call to the macro `sym` with arguments `arg` with the VM.
##
## `call` is the original call expression, which is used as the ``wrongNode``
Expand All @@ -549,7 +554,7 @@ proc evalMacroCall*(c: var TCtx, call, args: PNode, sym: PSym): PNode =
c.mode = oldMode
c.callsite = nil

let (start, regCount) = loadProc(c, sym).returnOnErr(c.config, call)
let (start, regCount) = loadProc(jit, c, sym).returnOnErr(c.config, call)

var tos = TStackFrame(prc: sym, comesFrom: 0)
tos.slots.newSeq(regCount)
Expand All @@ -559,7 +564,7 @@ proc evalMacroCall*(c: var TCtx, call, args: PNode, sym: PSym): PNode =

# put the normal arguments into registers
for i in 1..<sym.typ.len:
setupMacroParam(tos.slots[i], c, args[i - 1], sym.typ[i])
setupMacroParam(tos.slots[i], jit, c, args[i - 1], sym.typ[i])

# put the generic arguments into registers
let gp = sym.ast[genericParamsPos]
Expand All @@ -568,10 +573,10 @@ proc evalMacroCall*(c: var TCtx, call, args: PNode, sym: PSym): PNode =
# signature
if tfImplicitTypeParam notin gp[i].sym.typ.flags:
let idx = sym.typ.len + i
setupMacroParam(tos.slots[idx], c, args[idx - 1], gp[i].sym.typ)
setupMacroParam(tos.slots[idx], jit, c, args[idx - 1], gp[i].sym.typ)

let cb = mkCallback(c, r): r.nimNode
result = execute(c, start, tos, cb).unpackResult(c.config, call)
result = execute(jit, c, start, tos, cb).unpackResult(c.config, call)

if result.kind != nkError and cyclicTree(result):
result = c.config.newError(call, PAstDiag(kind: adCyclicTree))
Expand All @@ -586,14 +591,15 @@ proc evalMacroCall*(module: PSym; idgen: IdGenerator; g: ModuleGraph;
let c = PEvalContext(g.vm)
c.vm.templInstCounter = templInstCounter

result = evalMacroCall(c.vm, call, args, sym)
result = evalMacroCall(c.jit, c.vm, call, args, sym)

# ----------- the VM-related compilerapi -----------

# NOTE: it might make sense to move the VM-related compilerapi into
# ``nimeval.nim`` -- the compiler itself doesn't depend on or uses it

proc execProc*(c: var TCtx; sym: PSym; args: openArray[PNode]): PNode =
proc execProc*(jit: var JitState, c: var TCtx; sym: PSym;
args: openArray[PNode]): PNode =
# XXX: `localReport` is still used here since execProc is only used by the
# VM's compilerapi (`nimeval`) whose users don't know about nkError yet

Expand All @@ -611,7 +617,7 @@ proc execProc*(c: var TCtx; sym: PSym; args: openArray[PNode]): PNode =
let (start, maxSlots) = block:
# XXX: `returnOnErr` should be used here instead, but isn't for
# backwards compatiblity
let r = loadProc(c, sym)
let r = loadProc(jit, c, sym)
if unlikely(r.isErr):
localReport(c.config, vmGenDiagToLegacyVmReport(r.takeErr))
return nil
Expand All @@ -627,7 +633,7 @@ proc execProc*(c: var TCtx; sym: PSym; args: openArray[PNode]): PNode =
tos.slots[0].initLocReg(typ, c.memory)
# XXX We could perform some type checking here.
for i in 1..<sym.typ.len:
putIntoReg(tos.slots[i], c, args[i-1], sym.typ[i])
putIntoReg(tos.slots[i], jit, c, args[i-1], sym.typ[i])

let cb =
if not isEmptyType(sym.typ[0]):
Expand All @@ -638,7 +644,7 @@ proc execProc*(c: var TCtx; sym: PSym; args: openArray[PNode]): PNode =
else:
mkCallback(c, r): newNodeI(nkEmpty, sym.info)

let r = execute(c, start, tos, cb)
let r = execute(jit, c, start, tos, cb)
result = r.unpackResult(c.config, c.graph.emptyNode)
reportIfError(c.config, result)
if result.isError:
Expand Down Expand Up @@ -686,7 +692,7 @@ proc myProcess(c: PPassContext, n: PNode): PNode =
let c = PEvalContext(c)
# don't eval errornous code:
if c.oldErrorCount == c.vm.config.errorCounter and not n.isError:
let r = evalStmt(c.vm, n)
let r = evalStmt(c.jit, c.vm, n)
reportIfError(c.vm.config, r)
# TODO: use the node returned by evalStmt as the result and don't report
# the error here
Expand Down
2 changes: 1 addition & 1 deletion compiler/vm/nimeval.nim
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ proc selectRoutine*(i: Interpreter; name: string): PSym =
proc callRoutine*(i: Interpreter; routine: PSym; args: openArray[PNode]): PNode =
assert i != nil
let c = PEvalContext(i.graph.vm)
result = execProc(c.vm, routine, args)
result = execProc(c.jit, c.vm, routine, args)

proc getGlobalValue*(i: Interpreter; letOrVar: PSym): PNode =
let c = PEvalContext(i.graph.vm)
Expand Down
8 changes: 4 additions & 4 deletions compiler/vm/vmbackend.nim
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ proc declareGlobal(c: var TCtx, sym: PSym) =
# make sure the type is generated and register the global in the
# link table
discard getOrCreate(c, sym.typ)
registerLinkItem(c.symToIndexTbl, c.linkState.newGlobals, sym,
c.linkState.nextGlobal)
c.symToIndexTbl[sym.id] = LinkIndex(c.collectedGlobals.len)
c.collectedGlobals.add sym

proc prepare(c: var TCtx, data: var DiscoveryData) =
## Registers with the link table all procedures, constants, globals,
Expand Down Expand Up @@ -287,8 +287,8 @@ proc generateCode*(g: ModuleGraph, mlist: sink ModuleList) =
# Pros: no need for the `globals` and `consts` seqs
# Cons: (probably) higher I-cache pressure, slightly more complex logic

var globals = newSeq[PVmType](c.linkState.newGlobals.len)
for i, sym in c.linkState.newGlobals.pairs:
var globals = newSeq[PVmType](c.collectedGlobals.len)
for i, sym in c.collectedGlobals.pairs:
let typ = c.typeInfoCache.lookup(conf, sym.typ)
# the type was already created during vmgen
globals[i] = typ.unsafeGet
Expand Down
Loading