Skip to content

Commit

Permalink
generate method dispatchers prior to code-gen (#734)
Browse files Browse the repository at this point in the history
## Summary

Move the method dispatcher generation out of the code generators (`cgen`
and `jsgen`) and into the orchestrators. This is another step towards
unifying the backend processing.

Methods are now also subject to dead-code elimination, meaning that if
none of the methods attached to an object hierarchy are called, no code
will be generated for them.

## Details

The dispatchers do not appear in calls prior to `transf`, so the DCE
implementation used by IC backend cannot analyze them. For this reason,
`cgen` continues to special-case methods.

In addition, remove the unused `objHasKidsValid` enum value.

### Future Direction

Given that all backends need to generate method dispatchers, this step
(lowering methods into procedures) should happen through some common
facility in the future.
  • Loading branch information
zerbina authored Jun 1, 2023
1 parent ee47f9d commit 96ebe3d
Show file tree
Hide file tree
Showing 7 changed files with 27 additions and 29 deletions.
3 changes: 3 additions & 0 deletions compiler/backend/cbackend.nim
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import
compiler/backend/[
cgen,
cgendata,
cgmeth,
extccomp
],
compiler/front/[
Expand All @@ -36,6 +37,8 @@ proc generateCode*(graph: ModuleGraph, mlist: sink ModuleList) =
let
config = graph.config

generateMethodDispatchers(graph)

var g = newModuleList(graph)

# first create a module list entry for each input module. This has to happen
Expand Down
6 changes: 2 additions & 4 deletions compiler/backend/ccgexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2268,8 +2268,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
var sym = n.sym
case sym.kind
of skMethod:
if useAliveDataFromDce in p.module.flags or {sfDispatcher, sfForward} * sym.flags != {}:
# we cannot produce code for the dispatcher yet:
if useAliveDataFromDce in p.module.flags or {sfForward} * sym.flags != {}:
fillProcLoc(p.module, n)
genProcPrototype(p.module, sym)
else:
Expand Down Expand Up @@ -2471,8 +2470,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
genProc(p.module, prc)
elif prc.skipGenericOwner.kind == skModule and sfCompileTime notin prc.flags:
if ({sfExportc, sfCompilerProc} * prc.flags == {sfExportc}) or
(sfExportc in prc.flags and lfExportLib in prc.loc.flags) or
(prc.kind == skMethod):
(sfExportc in prc.flags and lfExportLib in prc.loc.flags):
# due to a bug/limitation in the lambda lifting, unused inner procs
# are not transformed correctly. We work around this issue (#411) here
# by ensuring it's no inner proc (owner is a module).
Expand Down
9 changes: 6 additions & 3 deletions compiler/backend/cgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1747,9 +1747,12 @@ proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: PNode) =
if emulatedThreadVars(m.config) and m.config.target.targetOS != osStandalone:
discard cgsym(m, "initThreadVarsEmulation")

incl m.flags, objHasKidsValid
let disp = generateMethodDispatchers(graph)
for x in disp: genProcAux(m, x.sym)
if useAliveDataFromDce in m.flags:
# methods need to be special-cased for IC, as whether a dispatcher is
# alive is only know after ``transf`` (phase-ordering problem)
generateMethodDispatchers(graph)
for disp in dispatchers(graph):
genProcAux(m, disp)

# for compatibility, the code generator still manages its own "closed order"
# list, but this should be phased out eventually
Expand Down
1 change: 0 additions & 1 deletion compiler/backend/cgendata.nim
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ type
## a frame var twice in an init proc
isHeaderFile, ## C source file is the header file
includesStringh, ## C source file already includes ``<string.h>``
objHasKidsValid ## whether we can rely on tfObjHasKids
useAliveDataFromDce ## use the `alive: IntSet` field instead of
## computing alive data on our own.

Expand Down
19 changes: 13 additions & 6 deletions compiler/backend/cgmeth.nim
Original file line number Diff line number Diff line change
Expand Up @@ -258,9 +258,10 @@ proc sortBucket(a: var seq[PSym], relevantCols: IntSet) =
a[j] = v
if h == 1: break

proc genDispatcher(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet): PSym =
proc genDispatcher(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet) =
var base = methods[0].ast[dispatcherPos].sym
result = base
# XXX: `base` is not the method marked with ``.base``, but rather the
# *dispatcher*
var paramLen = base.typ.len
var nilchecks = newNodeI(nkStmtList, base.info)
var disp = newNodeI(nkIfStmt, base.info)
Expand Down Expand Up @@ -315,10 +316,12 @@ proc genDispatcher(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet): PS
disp = ret
nilchecks.add disp
nilchecks.flags.incl nfTransf # should not be further transformed
result.ast[bodyPos] = nilchecks
base.ast[bodyPos] = nilchecks

proc generateMethodDispatchers*(g: ModuleGraph): PNode =
result = newNode(nkStmtList)
proc generateMethodDispatchers*(g: ModuleGraph) =
## For each method dispatcher, generates the body and updates the definition.
## This procedure must only be called once, and only *after* all methods were
## registered.
for bucket in 0..<g.methods.len:
var relevantCols = initIntSet()
for col in 1..<g.methods[bucket].methods[0].typ.len:
Expand All @@ -327,4 +330,8 @@ proc generateMethodDispatchers*(g: ModuleGraph): PNode =
# if multi-methods are not enabled, we are interested only in the first field
break
sortBucket(g.methods[bucket].methods, relevantCols)
result.add newSymNode(genDispatcher(g, g.methods[bucket].methods, relevantCols))
genDispatcher(g, g.methods[bucket].methods, relevantCols)

iterator dispatchers*(g: ModuleGraph): PSym =
for bucket in g.methods.items:
yield bucket.dispatcher
3 changes: 3 additions & 0 deletions compiler/backend/jsbackend.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import
json
],
compiler/backend/[
cgmeth,
jsgen
],
compiler/front/[
Expand Down Expand Up @@ -48,6 +49,8 @@ proc generateCode*(graph: ModuleGraph, mlist: sink ModuleList) =
let
globals = newGlobals()

generateMethodDispatchers(graph)

# generate the code for all modules:
for index in mlist.modulesClosed.items:
let
Expand Down
15 changes: 0 additions & 15 deletions compiler/backend/jsgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ import
],
compiler/backend/[
ccgutils,
cgmeth,
]

# xxx: reports are a code smell meaning data types are misplaced
Expand Down Expand Up @@ -1436,9 +1435,6 @@ proc genSym(p: PProc, n: PNode, r: var TCompRes) =
if lfNoDecl in s.loc.flags or s.magic notin {mNone, mIsolate} or
{sfImportc, sfInfixCall} * s.flags != {}:
discard
elif s.kind == skMethod and getBody(p.module.graph, s).kind == nkEmpty:
# we cannot produce code for the dispatcher yet:
discard
else:
# unresolved borrow or forward declarations must not reach here
assert {sfForward, sfBorrow} * s.flags == {}
Expand Down Expand Up @@ -2676,14 +2672,6 @@ proc genTopLevelStmt*(globals: PGlobals, m: BModule, n: PNode) =
p.g.code.add(p.locals)
p.g.code.add(p.body)

proc finishMainModule(graph: ModuleGraph, globals: PGlobals, m: BModule) =
var disp = generateMethodDispatchers(graph)
for i in 0..<disp.len:
let prc = disp[i].sym
if not globals.generatedSyms.containsOrIncl(prc.id):
var p = newInitProc(globals, m)
attachProc(p, prc)

proc finalCodegenActions*(graph: ModuleGraph; globals: PGlobals, m: BModule) =
if sfMainModule in m.module.flags and graph.globalDestructors.len > 0:
let n = newNode(nkStmtList)
Expand All @@ -2692,8 +2680,5 @@ proc finalCodegenActions*(graph: ModuleGraph; globals: PGlobals, m: BModule) =

genTopLevelStmt(globals, m, n)

if sfMainModule in m.module.flags:
finishMainModule(graph, globals, m)

proc wholeCode*(globals: PGlobals): Rope =
result = globals.typeInfo & globals.constants & globals.code

0 comments on commit 96ebe3d

Please sign in to comment.