Files
Nim/compiler/pipelines.nim
ringabout 30cf33f04d rework the vtable implementation embedding the vtable array directly with new strictions on methods (#22991)
**TODO**
- [x] fixes changelog
With the new option `nimPreviewVtables`, `methods` are confined in the
same module where the type of the first parameter is defined

- [x] make it opt in after CI checks its feasibility

## In the following-up PRs

- [ ] in the following PRs, refactor code into a more efficient one

- [ ] cpp needs special treatments since it cannot embed array in light
of the preceding limits: ref
https://github.com/nim-lang/Nim/pull/20977#discussion_r1035528927; we
can support cpp backends with vtable implementations later on the
comprise that uses indirect vtable access

---------

Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
2023-11-28 15:11:43 +01:00

321 lines
12 KiB
Nim

import sem, cgen, modulegraphs, ast, llstream, parser, msgs,
lineinfos, reorder, options, semdata, cgendata, modules, pathutils,
packages, syntaxes, depends, vm, pragmas, idents, lookups, wordrecg,
liftdestructors
import pipelineutils
when not defined(leanCompiler):
import jsgen, docgen2
import std/[syncio, objectdollar, assertions, tables, strutils]
import renderer
import ic/replayer
import nir/nir
proc setPipeLinePass*(graph: ModuleGraph; pass: PipelinePass) =
graph.pipelinePass = pass
proc processPipeline(graph: ModuleGraph; semNode: PNode; bModule: PPassContext): PNode =
case graph.pipelinePass
of CgenPass:
result = semNode
if bModule != nil:
genTopLevelStmt(BModule(bModule), result)
of JSgenPass:
when not defined(leanCompiler):
result = processJSCodeGen(bModule, semNode)
else:
result = nil
of GenDependPass:
result = addDotDependency(bModule, semNode)
of SemPass:
result = graph.emptyNode
of Docgen2Pass, Docgen2TexPass:
when not defined(leanCompiler):
result = processNode(bModule, semNode)
else:
result = nil
of Docgen2JsonPass:
when not defined(leanCompiler):
result = processNodeJson(bModule, semNode)
else:
result = nil
of EvalPass, InterpreterPass:
result = interpreterCode(bModule, semNode)
of NirReplPass:
result = runCode(bModule, semNode)
of NirPass:
result = nirBackend(bModule, semNode)
of NonePass:
raiseAssert "use setPipeLinePass to set a proper PipelinePass"
proc processImplicitImports(graph: ModuleGraph; implicits: seq[string], nodeKind: TNodeKind,
m: PSym, ctx: PContext, bModule: PPassContext, idgen: IdGenerator,
) =
# XXX fixme this should actually be relative to the config file!
let relativeTo = toFullPath(graph.config, m.info)
for module in items(implicits):
# implicit imports should not lead to a module importing itself
if m.position != resolveMod(graph.config, module, relativeTo).int32:
var importStmt = newNodeI(nodeKind, m.info)
var str = newStrNode(nkStrLit, module)
str.info = m.info
importStmt.add str
message(graph.config, importStmt.info, hintProcessingStmt, $idgen[])
let semNode = semWithPContext(ctx, importStmt)
if semNode == nil or processPipeline(graph, semNode, bModule) == nil:
break
proc prePass(c: PContext; n: PNode) =
for son in n:
if son.kind == nkPragma:
for s in son:
var key = if s.kind in nkPragmaCallKinds and s.len > 1: s[0] else: s
if key.kind in {nkBracketExpr, nkCast} or key.kind notin nkIdentKinds:
continue
let ident = whichKeyword(considerQuotedIdent(c, key))
case ident
of wReorder:
pragmaNoForward(c, s, flag = sfReorder)
of wExperimental:
if isTopLevel(c) and s.kind in nkPragmaCallKinds and s.len == 2:
let name = c.semConstExpr(c, s[1])
case name.kind
of nkStrLit, nkRStrLit, nkTripleStrLit:
try:
let feature = parseEnum[Feature](name.strVal)
if feature == codeReordering:
c.features.incl feature
c.module.flags.incl sfReorder
except ValueError:
discard
else:
discard
else:
discard
proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator;
stream: PLLStream): bool =
if graph.stopCompile(): return true
var
p: Parser
s: PLLStream
fileIdx = module.fileIdx
prepareConfigNotes(graph, module)
let ctx = preparePContext(graph, module, idgen)
let bModule: PPassContext =
case graph.pipelinePass
of CgenPass:
setupCgen(graph, module, idgen)
of NirPass:
openNirBackend(graph, module, idgen)
of JSgenPass:
when not defined(leanCompiler):
setupJSgen(graph, module, idgen)
else:
nil
of EvalPass, InterpreterPass:
setupEvalGen(graph, module, idgen)
of NirReplPass:
setupNirReplGen(graph, module, idgen)
of GenDependPass:
setupDependPass(graph, module, idgen)
of Docgen2Pass:
when not defined(leanCompiler):
openHtml(graph, module, idgen)
else:
nil
of Docgen2TexPass:
when not defined(leanCompiler):
openTex(graph, module, idgen)
else:
nil
of Docgen2JsonPass:
when not defined(leanCompiler):
openJson(graph, module, idgen)
else:
nil
of SemPass:
nil
of NonePass:
raiseAssert "use setPipeLinePass to set a proper PipelinePass"
if stream == nil:
let filename = toFullPathConsiderDirty(graph.config, fileIdx)
s = llStreamOpen(filename, fmRead)
if s == nil:
rawMessage(graph.config, errCannotOpenFile, filename.string)
return false
graph.interactive = false
else:
s = stream
graph.interactive = stream.kind == llsStdIn
while true:
syntaxes.openParser(p, fileIdx, s, graph.cache, graph.config)
if not belongsToStdlib(graph, module) or (belongsToStdlib(graph, module) and module.name.s == "distros"):
# XXX what about caching? no processing then? what if I change the
# modules to include between compilation runs? we'd need to track that
# in ROD files. I think we should enable this feature only
# for the interactive mode.
if module.name.s != "nimscriptapi":
processImplicitImports graph, graph.config.implicitImports, nkImportStmt, module, ctx, bModule, idgen
processImplicitImports graph, graph.config.implicitIncludes, nkIncludeStmt, module, ctx, bModule, idgen
checkFirstLineIndentation(p)
block processCode:
if graph.stopCompile(): break processCode
var n = parseTopLevelStmt(p)
if n.kind == nkEmpty: break processCode
# read everything, no streaming possible
var sl = newNodeI(nkStmtList, n.info)
sl.add n
while true:
var n = parseTopLevelStmt(p)
if n.kind == nkEmpty: break
sl.add n
prePass(ctx, sl)
if sfReorder in module.flags or codeReordering in graph.config.features:
sl = reorder(graph, sl, module)
if graph.pipelinePass != EvalPass:
message(graph.config, sl.info, hintProcessingStmt, $idgen[])
var semNode = semWithPContext(ctx, sl)
discard processPipeline(graph, semNode, bModule)
closeParser(p)
if s.kind != llsStdIn: break
let finalNode = closePContext(graph, ctx, nil)
case graph.pipelinePass
of CgenPass:
if bModule != nil:
let m = BModule(bModule)
finalCodegenActions(graph, m, finalNode)
if graph.dispatchers.len > 0:
let ctx = preparePContext(graph, module, idgen)
for disp in getDispatchers(graph):
let retTyp = disp.typ[0]
if retTyp != nil:
# TODO: properly semcheck the code of dispatcher?
createTypeBoundOps(graph, ctx, retTyp, disp.ast.info, idgen)
genProcAux(m, disp)
discard closePContext(graph, ctx, nil)
of JSgenPass:
when not defined(leanCompiler):
discard finalJSCodeGen(graph, bModule, finalNode)
of EvalPass, InterpreterPass:
discard interpreterCode(bModule, finalNode)
of NirReplPass:
discard runCode(bModule, finalNode)
of NirPass:
closeNirBackend(bModule, finalNode)
of SemPass, GenDependPass:
discard
of Docgen2Pass, Docgen2TexPass:
when not defined(leanCompiler):
discard closeDoc(graph, bModule, finalNode)
of Docgen2JsonPass:
when not defined(leanCompiler):
discard closeJson(graph, bModule, finalNode)
of NonePass:
raiseAssert "use setPipeLinePass to set a proper PipelinePass"
if graph.config.backend notin {backendC, backendCpp, backendObjc}:
# We only write rod files here if no C-like backend is active.
# The C-like backends have been patched to support the IC mechanism.
# They are responsible for closing the rod files. See `cbackend.nim`.
closeRodFile(graph, module)
result = true
proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags; fromModule: PSym = nil): PSym =
var flags = flags
if fileIdx == graph.config.projectMainIdx2: flags.incl sfMainModule
result = graph.getModule(fileIdx)
template processModuleAux(moduleStatus) =
onProcessing(graph, fileIdx, moduleStatus, fromModule = fromModule)
var s: PLLStream = nil
if sfMainModule in flags:
if graph.config.projectIsStdin: s = stdin.llStreamOpen
elif graph.config.projectIsCmd: s = llStreamOpen(graph.config.cmdInput)
discard processPipelineModule(graph, result, idGeneratorFromModule(result), s)
if result == nil:
var cachedModules: seq[FileIndex] = @[]
result = moduleFromRodFile(graph, fileIdx, cachedModules)
let filename = AbsoluteFile toFullPath(graph.config, fileIdx)
if result == nil:
result = newModule(graph, fileIdx)
result.flags.incl flags
registerModule(graph, result)
processModuleAux("import")
else:
if sfSystemModule in flags:
graph.systemModule = result
if sfMainModule in flags and graph.config.cmd == cmdM:
result.flags.incl flags
registerModule(graph, result)
processModuleAux("import")
partialInitModule(result, graph, fileIdx, filename)
for m in cachedModules:
registerModuleById(graph, m)
if sfMainModule in flags and graph.config.cmd == cmdM:
discard
else:
replayStateChanges(graph.packed.pm[m.int].module, graph)
replayGenericCacheInformation(graph, m.int)
elif graph.isDirty(result):
result.flags.excl sfDirty
# reset module fields:
initStrTables(graph, result)
result.ast = nil
processModuleAux("import(dirty)")
graph.markClientsDirty(fileIdx)
proc importPipelineModule(graph: ModuleGraph; s: PSym, fileIdx: FileIndex): PSym =
# this is called by the semantic checking phase
assert graph.config != nil
result = compilePipelineModule(graph, fileIdx, {}, s)
graph.addDep(s, fileIdx)
# keep track of import relationships
if graph.config.hcrOn:
graph.importDeps.mgetOrPut(FileIndex(s.position), @[]).add(fileIdx)
#if sfSystemModule in result.flags:
# localError(result.info, errAttemptToRedefine, result.name.s)
# restore the notes for outer module:
graph.config.notes =
if graph.config.belongsToProjectPackage(s) or isDefined(graph.config, "booting"): graph.config.mainPackageNotes
else: graph.config.foreignPackageNotes
proc connectPipelineCallbacks*(graph: ModuleGraph) =
graph.includeFileCallback = modules.includeModule
graph.importModuleCallback = importPipelineModule
proc compilePipelineSystemModule*(graph: ModuleGraph) =
if graph.systemModule == nil:
connectPipelineCallbacks(graph)
graph.config.m.systemFileIdx = fileInfoIdx(graph.config,
graph.config.libpath / RelativeFile"system.nim")
discard graph.compilePipelineModule(graph.config.m.systemFileIdx, {sfSystemModule})
proc compilePipelineProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIdx) =
connectPipelineCallbacks(graph)
let conf = graph.config
wantMainModule(conf)
configComplete(graph)
let systemFileIdx = fileInfoIdx(conf, conf.libpath / RelativeFile"system.nim")
let projectFile = if projectFileIdx == InvalidFileIdx: conf.projectMainIdx else: projectFileIdx
conf.projectMainIdx2 = projectFile
let packSym = getPackage(graph, projectFile)
graph.config.mainPackageId = packSym.getPackageId
graph.importStack.add projectFile
if projectFile == systemFileIdx:
discard graph.compilePipelineModule(projectFile, {sfMainModule, sfSystemModule})
else:
graph.compilePipelineSystemModule()
discard graph.compilePipelineModule(projectFile, {sfMainModule})