Skip to content

Commit

Permalink
implement legacy:jsNoLambdaLifting for compatibility (#23727)
Browse files Browse the repository at this point in the history
  • Loading branch information
ringabout authored Jun 17, 2024
1 parent ae4b47c commit 4867931
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 19 deletions.
2 changes: 2 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

- `bindMethod` in `std/jsffi` is deprecated, don't use it with closures.

- JS backend now supports lambda lifting for closures. Use `--legacy:jsNoLambdaLifting` to emulate old behavior.

## Standard library additions and changes

[//]: # "Changes:"
Expand Down
79 changes: 61 additions & 18 deletions compiler/jsgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,25 @@ type
blocks: seq[TBlock]
extraIndent: int
previousFileName: string # For frameInfo inside templates.
# legacy: generatedParamCopies and up fields are used for jsNoLambdaLifting
generatedParamCopies: IntSet
up: PProc # up the call chain; required for closure support

template config*(p: PProc): ConfigRef = p.module.config

proc indentLine(p: PProc, r: Rope): Rope =
var p = p
let ind = p.blocks.len + p.extraIndent
result = repeat(' ', ind*2) & r
if jsNoLambdaLifting in p.config.legacyFeatures:
var ind = 0
while true:
inc ind, p.blocks.len + p.extraIndent
if p.up == nil or p.up.prc != p.prc.owner:
break
p = p.up
result = repeat(' ', ind*2) & r
else:
let ind = p.blocks.len + p.extraIndent
result = repeat(' ', ind*2) & r

template line(p: PProc, added: string) =
p.body.add(indentLine(p, rope(added)))
Expand Down Expand Up @@ -1200,12 +1212,13 @@ proc genIf(p: PProc, n: PNode, r: var TCompRes) =
proc generateHeader(p: PProc, prc: PSym): Rope =
result = ""
let typ = prc.typ
if typ.callConv == ccClosure:
# we treat Env as the `this` parameter of the function
# to keep it simple
let env = prc.ast[paramsPos].lastSon
assert env.kind == nkSym, "env is missing"
env.sym.loc.r = "this"
if jsNoLambdaLifting notin p.config.legacyFeatures:
if typ.callConv == ccClosure:
# we treat Env as the `this` parameter of the function
# to keep it simple
let env = prc.ast[paramsPos].lastSon
assert env.kind == nkSym, "env is missing"
env.sym.loc.r = "this"

for i in 1..<typ.n.len:
assert(typ.n[i].kind == nkSym)
Expand Down Expand Up @@ -1239,7 +1252,8 @@ const

proc needsNoCopy(p: PProc; y: PNode): bool =
return y.kind in nodeKindsNeedNoCopy or
((mapType(y.typ) != etyBaseIndex) and
((mapType(y.typ) != etyBaseIndex or
(jsNoLambdaLifting in p.config.legacyFeatures and y.kind == nkSym and y.sym.kind == skParam)) and
(skipTypes(y.typ, abstractInst).kind in
{tyRef, tyPtr, tyLent, tyVar, tyCstring, tyProc, tyOwned, tyOpenArray} + IntegralTypes))

Expand Down Expand Up @@ -1590,7 +1604,30 @@ proc attachProc(p: PProc; s: PSym) =

proc genProcForSymIfNeeded(p: PProc, s: PSym) =
if not p.g.generatedSyms.containsOrIncl(s.id):
attachProc(p, s)
if jsNoLambdaLifting in p.config.legacyFeatures:
let newp = genProc(p, s)
var owner = p
while owner != nil and owner.prc != s.owner:
owner = owner.up
if owner != nil: owner.locals.add(newp)
else: attachProc(p, newp, s)
else:
attachProc(p, s)

proc genCopyForParamIfNeeded(p: PProc, n: PNode) =
let s = n.sym
if p.prc == s.owner or needsNoCopy(p, n):
return
var owner = p.up
while true:
if owner == nil:
internalError(p.config, n.info, "couldn't find the owner proc of the closed over param: " & s.name.s)
if owner.prc == s.owner:
if not owner.generatedParamCopies.containsOrIncl(s.id):
let copy = "$1 = nimCopy(null, $1, $2);$n" % [s.loc.r, genTypeInfo(p, s.typ)]
owner.locals.add(owner.indentLine(copy))
return
owner = owner.up

proc genVarInit(p: PProc, v: PSym, n: PNode)

Expand All @@ -1602,6 +1639,8 @@ proc genSym(p: PProc, n: PNode, r: var TCompRes) =
internalError(p.config, n.info, "symbol has no generated name: " & s.name.s)
if sfCompileTime in s.flags:
genVarInit(p, s, if s.astdef != nil: s.astdef else: newNodeI(nkEmpty, s.info))
if jsNoLambdaLifting in p.config.legacyFeatures and s.kind == skParam:
genCopyForParamIfNeeded(p, n)
let k = mapType(p, s.typ)
if k == etyBaseIndex:
r.typ = etyBaseIndex
Expand Down Expand Up @@ -2688,6 +2727,7 @@ proc genProc(oldProc: PProc, prc: PSym): Rope =
#if gVerbosity >= 3:
# echo "BEGIN generating code for: " & prc.name.s
var p = newProc(oldProc.g, oldProc.module, prc.ast, prc.options)
p.up = oldProc
var returnStmt: Rope = ""
var resultAsgn: Rope = ""
var name = mangleName(p.module, prc)
Expand Down Expand Up @@ -2911,14 +2951,17 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
else:
genCall(p, n, r)
of nkClosure:
let tmp = getTemp(p)
var a: TCompRes = default(TCompRes)
var b: TCompRes = default(TCompRes)
gen(p, n[0], a)
gen(p, n[1], b)
lineF(p, "$1 = $2.bind($3); $1.ClP_0 = $2; $1.ClE_0 = $3;$n", [tmp, a.rdLoc, b.rdLoc])
r.res = tmp
r.kind = resVal
if jsNoLambdaLifting in p.config.legacyFeatures:
gen(p, n[0], r)
else:
let tmp = getTemp(p)
var a: TCompRes = default(TCompRes)
var b: TCompRes = default(TCompRes)
gen(p, n[0], a)
gen(p, n[1], b)
lineF(p, "$1 = $2.bind($3); $1.ClP_0 = $2; $1.ClE_0 = $3;$n", [tmp, a.rdLoc, b.rdLoc])
r.res = tmp
r.kind = resVal
of nkCurly: genSetConstr(p, n, r)
of nkBracket: genArrayConstr(p, n, r)
of nkPar, nkTupleConstr: genTupleConstr(p, n, r)
Expand Down
10 changes: 9 additions & 1 deletion compiler/lambdalifting.nim
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,11 @@ proc interestingIterVar(s: PSym): bool {.inline.} =
template isIterator*(owner: PSym): bool =
owner.kind == skIterator and owner.typ.callConv == ccClosure

template liftingHarmful(conf: ConfigRef; owner: PSym): bool =
## lambda lifting can be harmful for JS-like code generators.
let isCompileTime = sfCompileTime in owner.flags or owner.kind == skMacro
jsNoLambdaLifting in conf.legacyFeatures and conf.backend == backendJs and not isCompileTime

proc createTypeBoundOpsLL(g: ModuleGraph; refType: PType; info: TLineInfo; idgen: IdGenerator; owner: PSym) =
if owner.kind != skMacro:
createTypeBoundOps(g, nil, refType.elementType, info, idgen)
Expand All @@ -255,6 +260,7 @@ proc genCreateEnv(env: PNode): PNode =

proc liftIterSym*(g: ModuleGraph; n: PNode; idgen: IdGenerator; owner: PSym): PNode =
# transforms (iter) to (let env = newClosure[iter](); (iter, env))
if liftingHarmful(g.config, owner): return n
let iter = n.sym
assert iter.isIterator

Expand Down Expand Up @@ -879,7 +885,8 @@ proc liftLambdas*(g: ModuleGraph; fn: PSym, body: PNode; tooEarly: var bool;
idgen: IdGenerator; flags: TransformFlags): PNode =
let isCompileTime = sfCompileTime in fn.flags or fn.kind == skMacro

if body.kind == nkEmpty or
if body.kind == nkEmpty or (jsNoLambdaLifting in g.config.legacyFeatures and
g.config.backend == backendJs and not isCompileTime) or
(fn.skipGenericOwner.kind != skModule and force notin flags):

# ignore forward declaration:
Expand Down Expand Up @@ -939,6 +946,7 @@ proc liftForLoop*(g: ModuleGraph; body: PNode; idgen: IdGenerator; owner: PSym):
break
...
"""
if liftingHarmful(g.config, owner): return body
if not (body.kind == nkForStmt and body[^2].kind in nkCallKinds):
localError(g.config, body.info, "ignored invalid for loop")
return body
Expand Down
2 changes: 2 additions & 0 deletions compiler/options.nim
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ type
emitGenerics
## generics are emitted in the module that contains them.
## Useful for libraries that rely on local passC
jsNoLambdaLifting
## Old transformation for closures in JS backend

SymbolFilesOption* = enum
disabledSf, writeOnlySf, readOnlySf, v2Sf, stressTest
Expand Down
1 change: 1 addition & 0 deletions compiler/transf.nim
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ proc generateThunk(c: PTransf; prc: PNode, dest: PType): PNode =

# we cannot generate a proper thunk here for GC-safety reasons
# (see internal documentation):
if jsNoLambdaLifting in c.graph.config.legacyFeatures and c.graph.config.backend == backendJs: return prc
result = newNodeIT(nkClosure, prc.info, dest)
var conv = newNodeIT(nkHiddenSubConv, prc.info, dest)
conv.add(newNodeI(nkEmpty, prc.info))
Expand Down
1 change: 1 addition & 0 deletions tests/js/tjsffi.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
discard """
matrix: "--legacy:jsnolambdalifting;"
output: '''
3
2
Expand Down

0 comments on commit 4867931

Please sign in to comment.