diff options
Diffstat (limited to 'compiler/jsgen.nim')
-rw-r--r-- | compiler/jsgen.nim | 2886 |
1 files changed, 1907 insertions, 979 deletions
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index ef54841ae..713944def 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -14,34 +14,45 @@ The JS code generator contains only 2 tricks: Trick 1 ------- -Some locations (for example 'var int') require "fat pointers" (``etyBaseIndex``) +Some locations (for example 'var int') require "fat pointers" (`etyBaseIndex`) which are pairs (array, index). The derefence operation is then 'array[index]'. -Check ``mapType`` for the details. +Check `mapType` for the details. Trick 2 ------- It is preferable to generate '||' and '&&' if possible since that is more idiomatic and hence should be friendlier for the JS JIT implementation. However -code like ``foo and (let bar = baz())`` cannot be translated this way. Instead -the expressions need to be transformed into statements. ``isSimpleExpr`` +code like `foo and (let bar = baz())` cannot be translated this way. Instead +the expressions need to be transformed into statements. `isSimpleExpr` implements the required case distinction. """ import - ast, astalgo, strutils, hashes, trees, platform, magicsys, extccomp, options, - nversion, nimsets, msgs, std / sha1, bitsets, idents, types, os, tables, - times, ropes, math, passes, ccgutils, wordrecg, renderer, - intsets, cgmeth, lowerings, sighashes, lineinfos, rodutils + ast, trees, magicsys, options, + nversion, msgs, idents, types, + ropes, wordrecg, renderer, + cgmeth, lowerings, sighashes, modulegraphs, lineinfos, + transf, injectdestructors, sourcemap, astmsgs, pushpoppragmas, + mangleutils -from modulegraphs import ModuleGraph +import pipelineutils + +import std/[json, sets, math, tables, intsets] +import std/strutils except addf + +when defined(nimPreviewSlimSystem): + import std/[assertions, syncio] + +import std/formatfloat type - TJSGen = object of TPassContext + TJSGen = object of PPassContext module: PSym graph: ModuleGraph config: ConfigRef sigConflicts: CountTable[SigHash] + initProc: PProc BModule = ref TJSGen TJSTypeKind = enum # necessary JS "types" @@ -66,6 +77,12 @@ type res: Rope # result part; index if this is an # (address, index)-tuple address: Rope # address of an (address, index)-tuple + tmpLoc: Rope # tmp var which stores the (address, index) + # pair to prevent multiple evals. + # the tmp is initialized upon evaling the + # address. + # might be nil. + # (see `maybeMakeTemp`) TBlock = object id: int # the ID of the label; positive means that it @@ -77,8 +94,8 @@ type forwarded: seq[PSym] generatedSyms: IntSet typeInfoGenerated: IntSet - classes: seq[(PType, Rope)] unique: int # for temp identifier generation + inSystem: bool PProc = ref TProc TProc = object @@ -86,35 +103,39 @@ type prc: PSym globals, locals, body: Rope options: TOptions + optionsStack: seq[(TOptions, TNoteKinds)] module: BModule g: PGlobals beforeRetNeeded: bool unique: int # for temp identifier generation 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 - declaredGlobals: IntSet template config*(p: PProc): ConfigRef = p.module.config proc indentLine(p: PProc, r: Rope): Rope = - result = r var p = p - while true: - for i in countup(0, p.blocks.len - 1 + p.extraIndent): - prepend(result, "\t".rope) - if p.up == nil or p.up.prc != p.prc.owner: - break - p = p.up + 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) = - add(p.body, indentLine(p, rope(added))) - -template line(p: PProc, added: Rope) = - add(p.body, indentLine(p, added)) + p.body.add(indentLine(p, rope(added))) template lineF(p: PProc, frmt: FormatStr, args: varargs[Rope]) = - add(p.body, indentLine(p, ropes.`%`(frmt, args))) + p.body.add(indentLine(p, ropes.`%`(frmt, args))) template nested(p, body) = inc p.extraIndent @@ -122,40 +143,42 @@ template nested(p, body) = dec p.extraIndent proc newGlobals(): PGlobals = - new(result) - result.forwarded = @[] - result.generatedSyms = initIntSet() - result.typeInfoGenerated = initIntSet() - result.classes = @[] - -proc initCompRes(r: var TCompRes) = - r.address = nil - r.res = nil - r.typ = etyNone - r.kind = resNone + result = PGlobals(forwarded: @[], + generatedSyms: initIntSet(), + typeInfoGenerated: initIntSet() + ) + +proc initCompRes(): TCompRes = + result = TCompRes(address: "", res: "", + tmpLoc: "", typ: etyNone, kind: resNone + ) proc rdLoc(a: TCompRes): Rope {.inline.} = - result = a.res - when false: - if a.typ != etyBaseIndex: - result = a.res - else: - result = "$1[$2]" % [a.address, a.res] + if a.typ != etyBaseIndex: + result = a.res + else: + result = "$1[$2]" % [a.address, a.res] proc newProc(globals: PGlobals, module: BModule, procDef: PNode, options: TOptions): PProc = result = PProc( blocks: @[], + optionsStack: if module.initProc != nil: module.initProc.optionsStack + else: @[], options: options, module: module, procDef: procDef, g: globals, extraIndent: int(procDef != nil)) - if procDef != nil: result.prc = procDef.sons[namePos].sym + if procDef != nil: result.prc = procDef[namePos].sym -proc declareGlobal(p: PProc; id: int; r: Rope) = - if p.prc != nil and not p.declaredGlobals.containsOrIncl(id): - p.locals.addf("global $1;$n", [r]) +proc initProcOptions(module: BModule): TOptions = + result = module.config.options + if PGlobals(module.graph.backend).inSystem: + result.excl(optStackTrace) + +proc newInitProc(globals: PGlobals, module: BModule): PProc = + result = newProc(globals, module, nil, initProcOptions(module)) const MappedToObject = {tyObject, tyArray, tyTuple, tyOpenArray, @@ -164,38 +187,40 @@ const proc mapType(typ: PType): TJSTypeKind = let t = skipTypes(typ, abstractInst) case t.kind - of tyVar, tyRef, tyPtr, tyLent: - if skipTypes(t.lastSon, abstractInst).kind in MappedToObject: + of tyVar, tyRef, tyPtr: + if skipTypes(t.elementType, abstractInst).kind in MappedToObject: result = etyObject else: result = etyBaseIndex of tyPointer: # treat a tyPointer like a typed pointer to an array of bytes result = etyBaseIndex - of tyRange, tyDistinct, tyOrdinal, tyProxy: - result = mapType(t.sons[0]) + of tyRange, tyDistinct, tyOrdinal, tyError, tyLent: + # tyLent is no-op as JS has pass-by-reference semantics + result = mapType(skipModifier t) of tyInt..tyInt64, tyUInt..tyUInt64, tyEnum, tyChar: result = etyInt of tyBool: result = etyBool of tyFloat..tyFloat128: result = etyFloat of tySet: result = etyObject # map a set to a table - of tyString, tySequence, tyOpt: result = etySeq - of tyObject, tyArray, tyTuple, tyOpenArray, tyVarargs: + of tyString, tySequence: result = etySeq + of tyObject, tyArray, tyTuple, tyOpenArray, tyVarargs, tyUncheckedArray: result = etyObject of tyNil: result = etyNull of tyGenericParam, tyGenericBody, tyGenericInvocation, tyNone, tyFromExpr, tyForward, tyEmpty, - tyExpr, tyStmt, tyTypeDesc, tyBuiltInTypeClass, tyCompositeTypeClass, + tyUntyped, tyTyped, tyTypeDesc, tyBuiltInTypeClass, tyCompositeTypeClass, tyAnd, tyOr, tyNot, tyAnything, tyVoid: result = etyNone of tyGenericInst, tyInferred, tyAlias, tyUserTypeClass, tyUserTypeClassInst, - tySink: - result = mapType(typ.lastSon) + tySink, tyOwned: + result = mapType(typ.skipModifier) of tyStatic: - if t.n != nil: result = mapType(lastSon t) + if t.n != nil: result = mapType(skipModifier t) else: result = etyNone of tyProc: result = etyProc - of tyCString: result = etyString - of tyUnused, tyOptAsRef: doAssert(false, "mapType") + of tyCstring: result = etyString + of tyConcept, tyIterable: + raiseAssert "unreachable" proc mapType(p: PProc; typ: PType): TJSTypeKind = result = mapType(typ) @@ -221,8 +246,8 @@ proc mangleName(m: BModule, s: PSym): Rope = for chr in name: if chr notin {'A'..'Z','a'..'z','_','$','0'..'9'}: return false - result = s.loc.r - if result == nil: + result = s.loc.snippet + if result == "": if s.kind == skField and s.name.s.validJsName: result = rope(s.name.s) elif s.kind == skTemp: @@ -233,25 +258,26 @@ proc mangleName(m: BModule, s: PSym): Rope = while i < s.name.s.len: let c = s.name.s[i] case c - of 'A'..'Z': - if i > 0 and s.name.s[i-1] in {'a'..'z'}: - x.add '_' - x.add(chr(c.ord - 'A'.ord + 'a'.ord)) - of 'a'..'z', '_', '0'..'9': + of 'A'..'Z', 'a'..'z', '_', '0'..'9': x.add c else: x.add("HEX" & toHex(ord(c), 2)) inc i result = rope(x) - if s.name.s != "this" and s.kind != skField: - if optHotCodeReloading in m.config.options: + # From ES5 on reserved words can be used as object field names + if s.kind != skField: + if m.config.hcrOn: # When hot reloading is enabled, we must ensure that the names # of functions and types will be preserved across rebuilds: - add(result, idOrSig(s, m.module.name.s, m.sigConflicts)) + result.add(idOrSig(s, m.module.name.s, m.sigConflicts, m.config)) + elif s.kind == skParam: + result.add mangleParamExt(s) + elif s.kind in routineKinds: + result.add mangleProcNameExt(m.graph, s) else: - add(result, "_") - add(result, rope(s.id)) - s.loc.r = result + result.add("_") + result.add(rope(s.id)) + s.loc.snippet = result proc escapeJSString(s: string): string = result = newStringOfCap(s.len + s.len shr 2) @@ -267,17 +293,30 @@ proc escapeJSString(s: string): string = of '\v': result.add("\\v") of '\\': result.add("\\\\") of '\"': result.add("\\\"") - else: add(result, c) + else: result.add(c) result.add("\"") proc makeJSString(s: string, escapeNonAscii = true): Rope = - if s.isNil: - result = "null".rope - elif escapeNonAscii: + if escapeNonAscii: result = strutils.escape(s).rope else: result = escapeJSString(s).rope +proc makeJsNimStrLit(s: string): Rope = + var x = newStringOfCap(4*s.len+1) + x.add "[" + var i = 0 + if i < s.len: + x.addInt int64(s[i]) + inc i + while i < s.len: + x.add "," + x.addInt int64(s[i]) + inc i + x.add "]" + result = rope(x) + + include jstypes proc gen(p: PProc, n: PNode, r: var TCompRes) @@ -292,7 +331,7 @@ proc useMagic(p: PProc, name: string) = internalAssert p.config, s.kind in {skProc, skFunc, skMethod, skConverter} if not p.g.generatedSyms.containsOrIncl(s.id): let code = genProc(p, s) - add(p.g.constants, code) + p.g.constants.add(code) else: if p.prc != nil: globalError(p.config, p.prc.info, "system module needs: " & name) @@ -301,23 +340,30 @@ proc useMagic(p: PProc, name: string) = proc isSimpleExpr(p: PProc; n: PNode): bool = # calls all the way down --> can stay expression based - if n.kind in nkCallKinds+{nkBracketExpr, nkDotExpr, nkPar, nkTupleConstr} or - (n.kind in {nkObjConstr, nkBracket, nkCurly}): + case n.kind + of nkCallKinds, nkBracketExpr, nkDotExpr, nkPar, nkTupleConstr, + nkObjConstr, nkBracket, nkCurly, + nkDerefExpr, nkHiddenDeref, nkAddr, nkHiddenAddr, + nkConv, nkHiddenStdConv, nkHiddenSubConv: for c in n: if not p.isSimpleExpr(c): return false result = true - elif n.isAtom: - result = true + of nkStmtListExpr: + for i in 0..<n.len-1: + if n[i].kind notin {nkCommentStmt, nkEmpty}: return false + result = isSimpleExpr(p, n.lastSon) + else: + result = n.isAtom proc getTemp(p: PProc, defineInLocals: bool = true): Rope = inc(p.unique) - result = "Tmp$1" % [rope(p.unique)] + result = "Temporary$1" % [rope(p.unique)] if defineInLocals: - add(p.locals, p.indentLine("var $1;$n" % [result])) + p.locals.add(p.indentLine("var $1;$n" % [result])) proc genAnd(p: PProc, a, b: PNode, r: var TCompRes) = assert r.kind == resNone - var x, y: TCompRes + var x, y: TCompRes = default(TCompRes) if p.isSimpleExpr(a) and p.isSimpleExpr(b): gen(p, a, x) gen(p, b, y) @@ -344,7 +390,7 @@ proc genAnd(p: PProc, a, b: PNode, r: var TCompRes) = proc genOr(p: PProc, a, b: PNode, r: var TCompRes) = assert r.kind == resNone - var x, y: TCompRes + var x, y: TCompRes = default(TCompRes) if p.isSimpleExpr(a) and p.isSimpleExpr(b): gen(p, a, x) gen(p, b, y) @@ -361,169 +407,458 @@ proc genOr(p: PProc, a, b: PNode, r: var TCompRes) = line(p, "}") type - TMagicFrmt = array[0..3, string] + TMagicFrmt = array[0..1, string] TMagicOps = array[mAddI..mStrToStr, TMagicFrmt] -const # magic checked op; magic unchecked op; checked op; unchecked op - jsOps: TMagicOps = [ - ["addInt", "", "addInt($1, $2)", "($1 + $2)"], # AddI - ["subInt", "", "subInt($1, $2)", "($1 - $2)"], # SubI - ["mulInt", "", "mulInt($1, $2)", "($1 * $2)"], # MulI - ["divInt", "", "divInt($1, $2)", "Math.trunc($1 / $2)"], # DivI - ["modInt", "", "modInt($1, $2)", "Math.trunc($1 % $2)"], # ModI - ["addInt", "", "addInt($1, $2)", "($1 + $2)"], # Succ - ["subInt", "", "subInt($1, $2)", "($1 - $2)"], # Pred - ["", "", "($1 + $2)", "($1 + $2)"], # AddF64 - ["", "", "($1 - $2)", "($1 - $2)"], # SubF64 - ["", "", "($1 * $2)", "($1 * $2)"], # MulF64 - ["", "", "($1 / $2)", "($1 / $2)"], # DivF64 - ["", "", "", ""], # ShrI - ["", "", "($1 << $2)", "($1 << $2)"], # ShlI - ["", "", "($1 & $2)", "($1 & $2)"], # BitandI - ["", "", "($1 | $2)", "($1 | $2)"], # BitorI - ["", "", "($1 ^ $2)", "($1 ^ $2)"], # BitxorI - ["nimMin", "nimMin", "nimMin($1, $2)", "nimMin($1, $2)"], # MinI - ["nimMax", "nimMax", "nimMax($1, $2)", "nimMax($1, $2)"], # MaxI - ["nimMin", "nimMin", "nimMin($1, $2)", "nimMin($1, $2)"], # MinF64 - ["nimMax", "nimMax", "nimMax($1, $2)", "nimMax($1, $2)"], # MaxF64 - ["", "", "", ""], # addU - ["", "", "", ""], # subU - ["", "", "", ""], # mulU - ["", "", "", ""], # divU - ["", "", "($1 % $2)", "($1 % $2)"], # modU - ["", "", "($1 == $2)", "($1 == $2)"], # EqI - ["", "", "($1 <= $2)", "($1 <= $2)"], # LeI - ["", "", "($1 < $2)", "($1 < $2)"], # LtI - ["", "", "($1 == $2)", "($1 == $2)"], # EqF64 - ["", "", "($1 <= $2)", "($1 <= $2)"], # LeF64 - ["", "", "($1 < $2)", "($1 < $2)"], # LtF64 - ["", "", "($1 <= $2)", "($1 <= $2)"], # leU - ["", "", "($1 < $2)", "($1 < $2)"], # ltU - ["", "", "($1 <= $2)", "($1 <= $2)"], # leU64 - ["", "", "($1 < $2)", "($1 < $2)"], # ltU64 - ["", "", "($1 == $2)", "($1 == $2)"], # EqEnum - ["", "", "($1 <= $2)", "($1 <= $2)"], # LeEnum - ["", "", "($1 < $2)", "($1 < $2)"], # LtEnum - ["", "", "($1 == $2)", "($1 == $2)"], # EqCh - ["", "", "($1 <= $2)", "($1 <= $2)"], # LeCh - ["", "", "($1 < $2)", "($1 < $2)"], # LtCh - ["", "", "($1 == $2)", "($1 == $2)"], # EqB - ["", "", "($1 <= $2)", "($1 <= $2)"], # LeB - ["", "", "($1 < $2)", "($1 < $2)"], # LtB - ["", "", "($1 == $2)", "($1 == $2)"], # EqRef - ["", "", "($1 == $2)", "($1 == $2)"], # EqUntracedRef - ["", "", "($1 <= $2)", "($1 <= $2)"], # LePtr - ["", "", "($1 < $2)", "($1 < $2)"], # LtPtr - ["", "", "($1 != $2)", "($1 != $2)"], # Xor - ["", "", "($1 == $2)", "($1 == $2)"], # EqCString - ["", "", "($1 == $2)", "($1 == $2)"], # EqProc - ["negInt", "", "negInt($1)", "-($1)"], # UnaryMinusI - ["negInt64", "", "negInt64($1)", "-($1)"], # UnaryMinusI64 - ["absInt", "", "absInt($1)", "Math.abs($1)"], # AbsI - ["", "", "!($1)", "!($1)"], # Not - ["", "", "+($1)", "+($1)"], # UnaryPlusI - ["", "", "~($1)", "~($1)"], # BitnotI - ["", "", "+($1)", "+($1)"], # UnaryPlusF64 - ["", "", "-($1)", "-($1)"], # UnaryMinusF64 - ["", "", "Math.abs($1)", "Math.abs($1)"], # AbsF64 - ["Ze8ToI", "Ze8ToI", "Ze8ToI($1)", "Ze8ToI($1)"], # mZe8ToI - ["Ze8ToI64", "Ze8ToI64", "Ze8ToI64($1)", "Ze8ToI64($1)"], # mZe8ToI64 - ["Ze16ToI", "Ze16ToI", "Ze16ToI($1)", "Ze16ToI($1)"], # mZe16ToI - ["Ze16ToI64", "Ze16ToI64", "Ze16ToI64($1)", "Ze16ToI64($1)"], # mZe16ToI64 - ["Ze32ToI64", "Ze32ToI64", "Ze32ToI64($1)", "Ze32ToI64($1)"], # mZe32ToI64 - ["ZeIToI64", "ZeIToI64", "ZeIToI64($1)", "ZeIToI64($1)"], # mZeIToI64 - ["toU8", "toU8", "toU8($1)", "toU8($1)"], # toU8 - ["toU16", "toU16", "toU16($1)", "toU16($1)"], # toU16 - ["toU32", "toU32", "toU32($1)", "toU32($1)"], # toU32 - ["", "", "$1", "$1"], # ToFloat - ["", "", "$1", "$1"], # ToBiggestFloat - ["", "", "Math.trunc($1)", "Math.trunc($1)"], # ToInt - ["", "", "Math.trunc($1)", "Math.trunc($1)"], # ToBiggestInt - ["nimCharToStr", "nimCharToStr", "nimCharToStr($1)", "nimCharToStr($1)"], - ["nimBoolToStr", "nimBoolToStr", "nimBoolToStr($1)", "nimBoolToStr($1)"], - ["cstrToNimstr", "cstrToNimstr", "cstrToNimstr(($1)+\"\")", "cstrToNimstr(($1)+\"\")"], - ["cstrToNimstr", "cstrToNimstr", "cstrToNimstr(($1)+\"\")", "cstrToNimstr(($1)+\"\")"], - ["cstrToNimstr", "cstrToNimstr", "cstrToNimstr(($1)+\"\")", "cstrToNimstr(($1)+\"\")"], - ["cstrToNimstr", "cstrToNimstr", "cstrToNimstr($1)", "cstrToNimstr($1)"], - ["", "", "$1", "$1"]] - -proc binaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) = - var x, y: TCompRes +const # magic checked op; magic unchecked op; + jsMagics: TMagicOps = [ + mAddI: ["addInt", ""], + mSubI: ["subInt", ""], + mMulI: ["mulInt", ""], + mDivI: ["divInt", ""], + mModI: ["modInt", ""], + mSucc: ["addInt", ""], + mPred: ["subInt", ""], + mAddF64: ["", ""], + mSubF64: ["", ""], + mMulF64: ["", ""], + mDivF64: ["", ""], + mShrI: ["", ""], + mShlI: ["", ""], + mAshrI: ["", ""], + mBitandI: ["", ""], + mBitorI: ["", ""], + mBitxorI: ["", ""], + mMinI: ["nimMin", "nimMin"], + mMaxI: ["nimMax", "nimMax"], + mAddU: ["", ""], + mSubU: ["", ""], + mMulU: ["", ""], + mDivU: ["", ""], + mModU: ["", ""], + mEqI: ["", ""], + mLeI: ["", ""], + mLtI: ["", ""], + mEqF64: ["", ""], + mLeF64: ["", ""], + mLtF64: ["", ""], + mLeU: ["", ""], + mLtU: ["", ""], + mEqEnum: ["", ""], + mLeEnum: ["", ""], + mLtEnum: ["", ""], + mEqCh: ["", ""], + mLeCh: ["", ""], + mLtCh: ["", ""], + mEqB: ["", ""], + mLeB: ["", ""], + mLtB: ["", ""], + mEqRef: ["", ""], + mLePtr: ["", ""], + mLtPtr: ["", ""], + mXor: ["", ""], + mEqCString: ["", ""], + mEqProc: ["", ""], + mUnaryMinusI: ["negInt", ""], + mUnaryMinusI64: ["negInt64", ""], + mAbsI: ["absInt", ""], + mNot: ["", ""], + mUnaryPlusI: ["", ""], + mBitnotI: ["", ""], + mUnaryPlusF64: ["", ""], + mUnaryMinusF64: ["", ""], + mCharToStr: ["nimCharToStr", "nimCharToStr"], + mBoolToStr: ["nimBoolToStr", "nimBoolToStr"], + mCStrToStr: ["cstrToNimstr", "cstrToNimstr"], + mStrToStr: ["", ""]] + +proc needsTemp(p: PProc; n: PNode): bool = + # check if n contains a call to determine + # if a temp should be made to prevent multiple evals + result = false + if n.kind in nkCallKinds + {nkTupleConstr, nkObjConstr, nkBracket, nkCurly}: + return true + for c in n: + if needsTemp(p, c): + return true + +proc maybeMakeTemp(p: PProc, n: PNode; x: TCompRes): tuple[a, tmp: Rope] = + var + a = x.rdLoc + b = a + if needsTemp(p, n): + # if we have tmp just use it + if x.tmpLoc != "" and (mapType(n.typ) == etyBaseIndex or n.kind in {nkHiddenDeref, nkDerefExpr}): + b = "$1[0][$1[1]]" % [x.tmpLoc] + (a: a, tmp: b) + else: + let tmp = p.getTemp + b = tmp + a = "($1 = $2, $1)" % [tmp, a] + (a: a, tmp: b) + else: + (a: a, tmp: b) + +proc maybeMakeTempAssignable(p: PProc, n: PNode; x: TCompRes): tuple[a, tmp: Rope] = + var + a = x.rdLoc + b = a + if needsTemp(p, n): + # if we have tmp just use it + if x.tmpLoc != "" and (mapType(n.typ) == etyBaseIndex or n.kind in {nkHiddenDeref, nkDerefExpr}): + b = "$1[0][$1[1]]" % [x.tmpLoc] + result = (a: a, tmp: b) + elif x.tmpLoc != "" and n.kind == nkBracketExpr: + # genArrayAddr + var + address, index: TCompRes = default(TCompRes) + first: Int128 = Zero + gen(p, n[0], address) + gen(p, n[1], index) + let (m1, tmp1) = maybeMakeTemp(p, n[0], address) + let typ = skipTypes(n[0].typ, abstractPtrs) + if typ.kind == tyArray: + first = firstOrd(p.config, typ.indexType) + if optBoundsCheck in p.options: + useMagic(p, "chckIndx") + if first == 0: # save a couple chars + index.res = "chckIndx($1, 0, ($2).length - 1)" % [index.res, tmp1] + else: + index.res = "chckIndx($1, $2, ($3).length + ($2) - 1) - ($2)" % [ + index.res, rope(first), tmp1] + elif first != 0: + index.res = "($1) - ($2)" % [index.res, rope(first)] + else: + discard # index.res = index.res + let (n1, tmp2) = maybeMakeTemp(p, n[1], index) + result = (a: "$1[$2]" % [m1, n1], tmp: "$1[$2]" % [tmp1, tmp2]) + # could also put here: nkDotExpr -> genFieldAccess, nkCheckedFieldExpr -> genCheckedFieldOp + # but the uses of maybeMakeTempAssignable don't need them + else: + result = (a: a, tmp: b) + else: + result = (a: a, tmp: b) + +template binaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string, + reassign = false) = + # $1 and $2 in the `frmt` string bind to lhs and rhs of the expr, + # if $3 or $4 are present they will be substituted with temps for + # lhs and rhs respectively + var x, y: TCompRes = default(TCompRes) useMagic(p, magic) - gen(p, n.sons[1], x) - gen(p, n.sons[2], y) - r.res = frmt % [x.rdLoc, y.rdLoc] + gen(p, n[1], x) + gen(p, n[2], y) + + var + a, tmp = x.rdLoc + b, tmp2 = y.rdLoc + when reassign: + (a, tmp) = maybeMakeTempAssignable(p, n[1], x) + else: + when "$3" in frmt: (a, tmp) = maybeMakeTemp(p, n[1], x) + when "$4" in frmt: (b, tmp2) = maybeMakeTemp(p, n[2], y) + + r.res = frmt % [a, b, tmp, tmp2] r.kind = resExpr -proc unsignedTrimmerJS(size: BiggestInt): Rope = +proc unsignedTrimmer(size: BiggestInt): string = case size - of 1: rope"& 0xff" - of 2: rope"& 0xffff" - of 4: rope">>> 0" - else: rope"" - + of 1: "& 0xff" + of 2: "& 0xffff" + of 4: ">>> 0" + else: "" -template unsignedTrimmer(size: BiggestInt): Rope = - size.unsignedTrimmerJS +proc signedTrimmer(size: BiggestInt): string = + # sign extension is done by shifting to the left and then back to the right + "<< $1 >> $1" % [$(32 - size * 8)] proc binaryUintExpr(p: PProc, n: PNode, r: var TCompRes, op: string, - reassign = false) = - var x, y: TCompRes - gen(p, n.sons[1], x) - gen(p, n.sons[2], y) - let trimmer = unsignedTrimmer(n[1].typ.skipTypes(abstractRange).size) - if reassign: - r.res = "$1 = (($1 $2 $3) $4)" % [x.rdLoc, rope op, y.rdLoc, trimmer] + reassign: static[bool] = false) = + var x, y: TCompRes = default(TCompRes) + gen(p, n[1], x) + gen(p, n[2], y) + let size = n[1].typ.skipTypes(abstractRange).size + when reassign: + let (a, tmp) = maybeMakeTempAssignable(p, n[1], x) + if size == 8 and optJsBigInt64 in p.config.globalOptions: + r.res = "$1 = BigInt.asUintN(64, ($4 $2 $3))" % [a, rope op, y.rdLoc, tmp] + else: + let trimmer = unsignedTrimmer(size) + r.res = "$1 = (($5 $2 $3) $4)" % [a, rope op, y.rdLoc, trimmer, tmp] else: - r.res = "(($1 $2 $3) $4)" % [x.rdLoc, rope op, y.rdLoc, trimmer] + if size == 8 and optJsBigInt64 in p.config.globalOptions: + r.res = "BigInt.asUintN(64, ($1 $2 $3))" % [x.rdLoc, rope op, y.rdLoc] + else: + let trimmer = unsignedTrimmer(size) + r.res = "(($1 $2 $3) $4)" % [x.rdLoc, rope op, y.rdLoc, trimmer] + r.kind = resExpr -proc ternaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) = +template ternaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) = var x, y, z: TCompRes useMagic(p, magic) - gen(p, n.sons[1], x) - gen(p, n.sons[2], y) - gen(p, n.sons[3], z) + gen(p, n[1], x) + gen(p, n[2], y) + gen(p, n[3], z) r.res = frmt % [x.rdLoc, y.rdLoc, z.rdLoc] r.kind = resExpr -proc unaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) = +template unaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) = + # $1 binds to n[1], if $2 is present it will be substituted to a tmp of $1 useMagic(p, magic) - gen(p, n.sons[1], r) - r.res = frmt % [r.rdLoc] + gen(p, n[1], r) + var a, tmp = r.rdLoc + if "$2" in frmt: (a, tmp) = maybeMakeTemp(p, n[1], r) + r.res = frmt % [a, tmp] + r.kind = resExpr + +proc genBreakState(p: PProc, n: PNode, r: var TCompRes) = + var a: TCompRes = default(TCompRes) + # mangle `:state` properly somehow + if n.kind == nkClosure: + gen(p, n[1], a) + r.res = "(($1).HEX3Astate < 0)" % [rdLoc(a)] + else: + gen(p, n, a) + r.res = "((($1.ClE_0).HEX3Astate) < 0)" % [rdLoc(a)] r.kind = resExpr proc arithAux(p: PProc, n: PNode, r: var TCompRes, op: TMagic) = var - x, y: TCompRes + x, y: TCompRes = default(TCompRes) + xLoc, yLoc: Rope = "" let i = ord(optOverflowCheck notin p.options) - useMagic(p, jsOps[op][i]) - if sonsLen(n) > 2: - gen(p, n.sons[1], x) - gen(p, n.sons[2], y) - r.res = jsOps[op][i + 2] % [x.rdLoc, y.rdLoc] + useMagic(p, jsMagics[op][i]) + if n.len > 2: + gen(p, n[1], x) + gen(p, n[2], y) + xLoc = x.rdLoc + yLoc = y.rdLoc else: - gen(p, n.sons[1], r) - r.res = jsOps[op][i + 2] % [r.rdLoc] + gen(p, n[1], r) + xLoc = r.rdLoc + + template applyFormat(frmt) = + r.res = frmt % [xLoc, yLoc] + template applyFormat(frmtA, frmtB) = + if i == 0: applyFormat(frmtA) else: applyFormat(frmtB) + + template bitwiseExpr(op: string) = + let typ = n[1].typ.skipTypes(abstractVarRange) + if typ.kind in {tyUInt, tyUInt32}: + r.res = "(($1 $2 $3) >>> 0)" % [xLoc, op, yLoc] + else: + r.res = "($1 $2 $3)" % [xLoc, op, yLoc] + + case op + of mAddI: + if i == 0: + if n[1].typ.size == 8 and optJsBigInt64 in p.config.globalOptions: + useMagic(p, "addInt64") + applyFormat("addInt64($1, $2)") + else: + applyFormat("addInt($1, $2)") + else: + applyFormat("($1 + $2)") + of mSubI: + if i == 0: + if n[1].typ.size == 8 and optJsBigInt64 in p.config.globalOptions: + useMagic(p, "subInt64") + applyFormat("subInt64($1, $2)") + else: + applyFormat("subInt($1, $2)") + else: + applyFormat("($1 - $2)") + of mMulI: + if i == 0: + if n[1].typ.size == 8 and optJsBigInt64 in p.config.globalOptions: + useMagic(p, "mulInt64") + applyFormat("mulInt64($1, $2)") + else: + applyFormat("mulInt($1, $2)") + else: + applyFormat("($1 * $2)") + of mDivI: + if n[1].typ.size == 8 and optJsBigInt64 in p.config.globalOptions: + useMagic(p, "divInt64") + applyFormat("divInt64($1, $2)", "$1 / $2") + else: + applyFormat("divInt($1, $2)", "Math.trunc($1 / $2)") + of mModI: + if n[1].typ.size == 8 and optJsBigInt64 in p.config.globalOptions: + useMagic(p, "modInt64") + applyFormat("modInt64($1, $2)", "$1 % $2") + else: + applyFormat("modInt($1, $2)", "Math.trunc($1 % $2)") + of mSucc: + let typ = n[1].typ.skipTypes(abstractVarRange) + case typ.kind + of tyUInt..tyUInt32: + binaryUintExpr(p, n, r, "+") + of tyUInt64: + if optJsBigInt64 in p.config.globalOptions: + applyFormat("BigInt.asUintN(64, $1 + BigInt($2))") + else: binaryUintExpr(p, n, r, "+") + elif typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + if optOverflowCheck notin p.options: + applyFormat("BigInt.asIntN(64, $1 + BigInt($2))") + else: binaryExpr(p, n, r, "addInt64", "addInt64($1, BigInt($2))") + else: + if optOverflowCheck notin p.options: applyFormat("$1 + $2") + else: binaryExpr(p, n, r, "addInt", "addInt($1, $2)") + of mPred: + let typ = n[1].typ.skipTypes(abstractVarRange) + case typ.kind + of tyUInt..tyUInt32: + binaryUintExpr(p, n, r, "-") + of tyUInt64: + if optJsBigInt64 in p.config.globalOptions: + applyFormat("BigInt.asUintN(64, $1 - BigInt($2))") + else: binaryUintExpr(p, n, r, "-") + elif typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + if optOverflowCheck notin p.options: + applyFormat("BigInt.asIntN(64, $1 - BigInt($2))") + else: binaryExpr(p, n, r, "subInt64", "subInt64($1, BigInt($2))") + else: + if optOverflowCheck notin p.options: applyFormat("$1 - $2") + else: binaryExpr(p, n, r, "subInt", "subInt($1, $2)") + of mAddF64: applyFormat("($1 + $2)", "($1 + $2)") + of mSubF64: applyFormat("($1 - $2)", "($1 - $2)") + of mMulF64: applyFormat("($1 * $2)", "($1 * $2)") + of mDivF64: applyFormat("($1 / $2)", "($1 / $2)") + of mShrI: + let typ = n[1].typ.skipTypes(abstractVarRange) + if typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + applyFormat("BigInt.asIntN(64, BigInt.asUintN(64, $1) >> BigInt($2))") + elif typ.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions: + applyFormat("($1 >> BigInt($2))") + else: + if typ.kind in {tyInt..tyInt32}: + let trimmerU = unsignedTrimmer(typ.size) + let trimmerS = signedTrimmer(typ.size) + r.res = "((($1 $2) >>> $3) $4)" % [xLoc, trimmerU, yLoc, trimmerS] + else: + applyFormat("($1 >>> $2)") + of mShlI: + let typ = n[1].typ.skipTypes(abstractVarRange) + if typ.size == 8: + if typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + applyFormat("BigInt.asIntN(64, $1 << BigInt($2))") + elif typ.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions: + applyFormat("BigInt.asUintN(64, $1 << BigInt($2))") + else: + applyFormat("($1 * Math.pow(2, $2))") + else: + if typ.kind in {tyUInt..tyUInt32}: + let trimmer = unsignedTrimmer(typ.size) + r.res = "(($1 << $2) $3)" % [xLoc, yLoc, trimmer] + else: + let trimmer = signedTrimmer(typ.size) + r.res = "(($1 << $2) $3)" % [xLoc, yLoc, trimmer] + of mAshrI: + let typ = n[1].typ.skipTypes(abstractVarRange) + if typ.size == 8: + if optJsBigInt64 in p.config.globalOptions: + applyFormat("($1 >> BigInt($2))") + else: + applyFormat("Math.floor($1 / Math.pow(2, $2))") + else: + if typ.kind in {tyUInt..tyUInt32}: + applyFormat("($1 >>> $2)") + else: + applyFormat("($1 >> $2)") + of mBitandI: bitwiseExpr("&") + of mBitorI: bitwiseExpr("|") + of mBitxorI: bitwiseExpr("^") + of mMinI: applyFormat("nimMin($1, $2)", "nimMin($1, $2)") + of mMaxI: applyFormat("nimMax($1, $2)", "nimMax($1, $2)") + of mAddU: applyFormat("", "") + of mSubU: applyFormat("", "") + of mMulU: applyFormat("", "") + of mDivU: applyFormat("", "") + of mModU: applyFormat("($1 % $2)", "($1 % $2)") + of mEqI: applyFormat("($1 == $2)", "($1 == $2)") + of mLeI: applyFormat("($1 <= $2)", "($1 <= $2)") + of mLtI: applyFormat("($1 < $2)", "($1 < $2)") + of mEqF64: applyFormat("($1 == $2)", "($1 == $2)") + of mLeF64: applyFormat("($1 <= $2)", "($1 <= $2)") + of mLtF64: applyFormat("($1 < $2)", "($1 < $2)") + of mLeU: applyFormat("($1 <= $2)", "($1 <= $2)") + of mLtU: applyFormat("($1 < $2)", "($1 < $2)") + of mEqEnum: applyFormat("($1 == $2)", "($1 == $2)") + of mLeEnum: applyFormat("($1 <= $2)", "($1 <= $2)") + of mLtEnum: applyFormat("($1 < $2)", "($1 < $2)") + of mEqCh: applyFormat("($1 == $2)", "($1 == $2)") + of mLeCh: applyFormat("($1 <= $2)", "($1 <= $2)") + of mLtCh: applyFormat("($1 < $2)", "($1 < $2)") + of mEqB: applyFormat("($1 == $2)", "($1 == $2)") + of mLeB: applyFormat("($1 <= $2)", "($1 <= $2)") + of mLtB: applyFormat("($1 < $2)", "($1 < $2)") + of mEqRef: applyFormat("($1 == $2)", "($1 == $2)") + of mLePtr: applyFormat("($1 <= $2)", "($1 <= $2)") + of mLtPtr: applyFormat("($1 < $2)", "($1 < $2)") + of mXor: applyFormat("($1 != $2)", "($1 != $2)") + of mEqCString: applyFormat("($1 == $2)", "($1 == $2)") + of mEqProc: applyFormat("($1 == $2)", "($1 == $2)") + of mUnaryMinusI: applyFormat("negInt($1)", "-($1)") + of mUnaryMinusI64: applyFormat("negInt64($1)", "-($1)") + of mAbsI: + let typ = n[1].typ.skipTypes(abstractVarRange) + if typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + useMagic(p, "absInt64") + applyFormat("absInt64($1)", "absInt64($1)") + else: + applyFormat("absInt($1)", "Math.abs($1)") + of mNot: applyFormat("!($1)", "!($1)") + of mUnaryPlusI: applyFormat("+($1)", "+($1)") + of mBitnotI: + let typ = n[1].typ.skipTypes(abstractVarRange) + if typ.kind in {tyUInt..tyUInt64}: + if typ.size == 8 and optJsBigInt64 in p.config.globalOptions: + applyFormat("BigInt.asUintN(64, ~($1))") + else: + let trimmer = unsignedTrimmer(typ.size) + r.res = "(~($1) $2)" % [xLoc, trimmer] + else: + applyFormat("~($1)") + of mUnaryPlusF64: applyFormat("+($1)", "+($1)") + of mUnaryMinusF64: applyFormat("-($1)", "-($1)") + of mCharToStr: applyFormat("nimCharToStr($1)", "nimCharToStr($1)") + of mBoolToStr: applyFormat("nimBoolToStr($1)", "nimBoolToStr($1)") + of mCStrToStr: applyFormat("cstrToNimstr($1)", "cstrToNimstr($1)") + of mStrToStr, mUnown, mIsolate, mFinished: applyFormat("$1", "$1") + else: + assert false, $op proc arith(p: PProc, n: PNode, r: var TCompRes, op: TMagic) = case op of mAddU: binaryUintExpr(p, n, r, "+") of mSubU: binaryUintExpr(p, n, r, "-") of mMulU: binaryUintExpr(p, n, r, "*") - of mDivU: binaryUintExpr(p, n, r, "/") + of mDivU: + binaryUintExpr(p, n, r, "/") + if optJsBigInt64 notin p.config.globalOptions and + n[1].typ.skipTypes(abstractRange).size == 8: + # bigint / already truncates + r.res = "Math.trunc($1)" % [r.res] of mDivI: arithAux(p, n, r, op) of mModI: arithAux(p, n, r, op) - of mShrI: - var x, y: TCompRes - gen(p, n.sons[1], x) - gen(p, n.sons[2], y) - let trimmer = unsignedTrimmer(n[1].typ.skipTypes(abstractRange).size) - r.res = "(($1 $2) >>> $3)" % [x.rdLoc, trimmer, y.rdLoc] - of mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, mFloatToStr, - mCStrToStr, mStrToStr, mEnumToStr: + of mCharToStr, mBoolToStr, mCStrToStr, mStrToStr, mEnumToStr: arithAux(p, n, r, op) + of mEqRef: + if mapType(n[1].typ) != etyBaseIndex: + arithAux(p, n, r, op) + else: + var x, y: TCompRes = default(TCompRes) + gen(p, n[1], x) + gen(p, n[2], y) + r.res = "($# == $# && $# == $#)" % [x.address, y.address, x.res, y.res] + of mEqProc: + if skipTypes(n[1].typ, abstractInst).callConv == ccClosure: + binaryExpr(p, n, r, "cmpClosures", "cmpClosures($1, $2)") + else: + arithAux(p, n, r, op) else: arithAux(p, n, r, op) r.kind = resExpr @@ -532,36 +867,42 @@ proc hasFrameInfo(p: PProc): bool = ({optLineTrace, optStackTrace} * p.options == {optLineTrace, optStackTrace}) and ((p.prc == nil) or not (sfPure in p.prc.flags)) +proc lineDir(config: ConfigRef, info: TLineInfo, line: int): Rope = + "/* line $2:$3 \"$1\" */$n" % [ + rope(toFullPath(config, info)), rope(line), rope(info.toColumn) + ] + proc genLineDir(p: PProc, n: PNode) = let line = toLinenumber(n.info) - if optLineDir in p.options: - lineF(p, "// line $2 \"$1\"$n", - [rope(toFilename(p.config, n.info)), rope(line)]) - if {optStackTrace, optEndb} * p.options == {optStackTrace, optEndb} and - ((p.prc == nil) or sfPure notin p.prc.flags): - useMagic(p, "endb") - lineF(p, "endb($1);$n", [rope(line)]) - elif hasFrameInfo(p): + if line < 0: + return + if optEmbedOrigSrc in p.config.globalOptions: + lineF(p, "//$1$n", [sourceLine(p.config, n.info)]) + if optLineDir in p.options or optLineDir in p.config.options: + lineF(p, "$1", [lineDir(p.config, n.info, line)]) + if hasFrameInfo(p): lineF(p, "F.line = $1;$n", [rope(line)]) + let currentFileName = toFilename(p.config, n.info) + if p.previousFileName != currentFileName: + lineF(p, "F.filename = $1;$n", [makeJSString(currentFileName)]) + p.previousFileName = currentFileName proc genWhileStmt(p: PProc, n: PNode) = - var - cond: TCompRes + var cond: TCompRes = default(TCompRes) internalAssert p.config, isEmptyType(n.typ) genLineDir(p, n) inc(p.unique) - var length = len(p.blocks) - setLen(p.blocks, length + 1) - p.blocks[length].id = -p.unique - p.blocks[length].isLoop = true + setLen(p.blocks, p.blocks.len + 1) + p.blocks[^1].id = -p.unique + p.blocks[^1].isLoop = true let labl = p.unique.rope - lineF(p, "L$1: while (true) {$n", [labl]) - p.nested: gen(p, n.sons[0], cond) - lineF(p, "if (!$1) break L$2;$n", + lineF(p, "Label$1: while (true) {$n", [labl]) + p.nested: gen(p, n[0], cond) + lineF(p, "if (!$1) break Label$2;$n", [cond.res, labl]) - p.nested: genStmt(p, n.sons[1]) + p.nested: genStmt(p, n[1]) lineF(p, "}$n", [labl]) - setLen(p.blocks, length) + setLen(p.blocks, p.blocks.len - 1) proc moveInto(p: PProc, src: var TCompRes, dest: TCompRes) = if src.kind != resNone: @@ -570,7 +911,7 @@ proc moveInto(p: PProc, src: var TCompRes, dest: TCompRes) = else: lineF(p, "$1;$n", [src.rdLoc]) src.kind = resNone - src.res = nil + src.res = "" proc genTry(p: PProc, n: PNode, r: var TCompRes) = # code to generate: @@ -580,8 +921,8 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) = # try { # stmts; # --excHandler; - # } catch (EXC) { - # var prevJSError = lastJSError; lastJSError = EXC; + # } catch (EXCEPTION) { + # var prevJSError = lastJSError; lastJSError = EXCEPTION; # framePtr = tmpFramePtr; # --excHandler; # if (e.typ && e.typ == NTI433 || e.typ == NTI2321) { @@ -602,45 +943,68 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) = r.res = getTemp(p) inc(p.unique) var i = 1 - var length = sonsLen(n) - var catchBranchesExist = length > 1 and n.sons[i].kind == nkExceptBranch + var catchBranchesExist = n.len > 1 and n[i].kind == nkExceptBranch if catchBranchesExist: - add(p.body, "++excHandler;\L") + p.body.add("++excHandler;\L") var tmpFramePtr = rope"F" - if optStackTrace notin p.options: - tmpFramePtr = p.getTemp(true) - line(p, tmpFramePtr & " = framePtr;\L") lineF(p, "try {$n", []) - var a: TCompRes - gen(p, n.sons[0], a) + var a: TCompRes = default(TCompRes) + gen(p, n[0], a) moveInto(p, a, r) var generalCatchBranchExists = false - let dollar = rope("") if catchBranchesExist: - addf(p.body, "--excHandler;$n} catch (EXC) {$n var prevJSError = lastJSError;$n" & - " lastJSError = EXC;$n --excHandler;$n", []) - line(p, "framePtr = $1;$n" % [tmpFramePtr]) - while i < length and n.sons[i].kind == nkExceptBranch: - let blen = sonsLen(n.sons[i]) - if blen == 1: + p.body.addf("--excHandler;$n} catch (EXCEPTION) {$n var prevJSError = lastJSError;$n" & + " lastJSError = EXCEPTION;$n --excHandler;$n", []) + if hasFrameInfo(p): + line(p, "framePtr = $1;$n" % [tmpFramePtr]) + while i < n.len and n[i].kind == nkExceptBranch: + if n[i].len == 1: # general except section: generalCatchBranchExists = true if i > 1: lineF(p, "else {$n", []) - gen(p, n.sons[i].sons[0], a) + gen(p, n[i][0], a) moveInto(p, a, r) if i > 1: lineF(p, "}$n", []) else: - var orExpr: Rope = nil + var orExpr: Rope = "" + var excAlias: PNode = nil + useMagic(p, "isObj") - for j in countup(0, blen - 2): - if n.sons[i].sons[j].kind != nkType: + for j in 0..<n[i].len - 1: + var throwObj: PNode + let it = n[i][j] + + if it.isInfixAs(): + throwObj = it[1] + excAlias = it[2] + # If this is a ``except exc as sym`` branch there must be no following + # nodes + doAssert orExpr == "" + elif it.kind == nkType: + throwObj = it + else: + throwObj = nil internalError(p.config, n.info, "genTryStmt") - if orExpr != nil: add(orExpr, "||") - addf(orExpr, "isObj($2lastJSError.m_type, $1)", - [genTypeInfo(p, n.sons[i].sons[j].typ), dollar]) + + if orExpr != "": orExpr.add("||") + # Generate the correct type checking code depending on whether this is a + # NIM-native or a JS-native exception + # if isJsObject(throwObj.typ): + if isImportedException(throwObj.typ, p.config): + orExpr.addf("lastJSError instanceof $1", + [throwObj.typ.sym.loc.snippet]) + else: + orExpr.addf("isObj(lastJSError.m_type, $1)", + [genTypeInfo(p, throwObj.typ)]) + if i > 1: line(p, "else ") - lineF(p, "if ($1lastJSError && ($2)) {$n", [dollar, orExpr]) - gen(p, n.sons[i].sons[blen - 1], a) + lineF(p, "if (lastJSError && ($1)) {$n", [orExpr]) + # If some branch requires a local alias introduce it here. This is needed + # since JS cannot do ``catch x as y``. + if excAlias != nil: + excAlias.sym.loc.snippet = mangleName(p.module, excAlias.sym) + lineF(p, "var $1 = lastJSError;$n", excAlias.sym.loc.snippet) + gen(p, n[i][^1], a) moveInto(p, a, r) lineF(p, "}$n", []) inc(i) @@ -650,113 +1014,163 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) = line(p, "else {\L") line(p, "\treraiseException();\L") line(p, "}\L") - addf(p.body, "$1lastJSError = $1prevJSError;$n", [dollar]) + lineF(p, "lastJSError = prevJSError;$n") line(p, "} finally {\L") - line(p, "framePtr = $1;$n" % [tmpFramePtr]) - if i < length and n.sons[i].kind == nkFinally: - genStmt(p, n.sons[i].sons[0]) + if hasFrameInfo(p): + line(p, "framePtr = $1;$n" % [tmpFramePtr]) + if i < n.len and n[i].kind == nkFinally: + genStmt(p, n[i][0]) line(p, "}\L") proc genRaiseStmt(p: PProc, n: PNode) = - genLineDir(p, n) - if n.sons[0].kind != nkEmpty: - var a: TCompRes - gen(p, n.sons[0], a) - let typ = skipTypes(n.sons[0].typ, abstractPtrs) + if n[0].kind != nkEmpty: + var a: TCompRes = default(TCompRes) + gen(p, n[0], a) + let typ = skipTypes(n[0].typ, abstractPtrs) + genLineDir(p, n) useMagic(p, "raiseException") lineF(p, "raiseException($1, $2);$n", [a.rdLoc, makeJSString(typ.sym.name.s)]) else: + genLineDir(p, n) useMagic(p, "reraiseException") line(p, "reraiseException();\L") proc genCaseJS(p: PProc, n: PNode, r: var TCompRes) = var - cond, stmt: TCompRes + a, b, cond, stmt: TCompRes = default(TCompRes) genLineDir(p, n) - gen(p, n.sons[0], cond) - let stringSwitch = skipTypes(n.sons[0].typ, abstractVar).kind == tyString - if stringSwitch: + gen(p, n[0], cond) + let typeKind = skipTypes(n[0].typ, abstractVar+{tyRange}).kind + var transferRange = false + let anyString = typeKind in {tyString, tyCstring} + case typeKind + of tyString: useMagic(p, "toJSStr") lineF(p, "switch (toJSStr($1)) {$n", [cond.rdLoc]) + of tyFloat..tyFloat128, tyInt..tyInt64, tyUInt..tyUInt64: + transferRange = true else: lineF(p, "switch ($1) {$n", [cond.rdLoc]) if not isEmptyType(n.typ): r.kind = resVal r.res = getTemp(p) - for i in countup(1, sonsLen(n) - 1): - let it = n.sons[i] + for i in 1..<n.len: + let it = n[i] + let itLen = it.len case it.kind of nkOfBranch: - for j in countup(0, sonsLen(it) - 2): - let e = it.sons[j] + if transferRange: + if i == 1: + lineF(p, "if (", []) + else: + lineF(p, "else if (", []) + for j in 0..<itLen - 1: + let e = it[j] if e.kind == nkRange: - var v = copyNode(e.sons[0]) - while v.intVal <= e.sons[1].intVal: - gen(p, v, cond) - lineF(p, "case $1:$n", [cond.rdLoc]) - inc(v.intVal) + if transferRange: + gen(p, e[0], a) + gen(p, e[1], b) + if j != itLen - 2: + lineF(p, "$1 >= $2 && $1 <= $3 || $n", [cond.rdLoc, a.rdLoc, b.rdLoc]) + else: + lineF(p, "$1 >= $2 && $1 <= $3", [cond.rdLoc, a.rdLoc, b.rdLoc]) + else: + var v = copyNode(e[0]) + while v.intVal <= e[1].intVal: + gen(p, v, cond) + lineF(p, "case $1:$n", [cond.rdLoc]) + inc(v.intVal) else: - if stringSwitch: + if anyString: case e.kind of nkStrLit..nkTripleStrLit: lineF(p, "case $1:$n", [makeJSString(e.strVal, false)]) + of nkNilLit: lineF(p, "case null:$n", []) else: internalError(p.config, e.info, "jsgen.genCaseStmt: 2") else: - gen(p, e, cond) - lineF(p, "case $1:$n", [cond.rdLoc]) + if transferRange: + gen(p, e, a) + if j != itLen - 2: + lineF(p, "$1 == $2 || $n", [cond.rdLoc, a.rdLoc]) + else: + lineF(p, "$1 == $2", [cond.rdLoc, a.rdLoc]) + else: + gen(p, e, a) + lineF(p, "case $1:$n", [a.rdLoc]) + if transferRange: + lineF(p, "){", []) p.nested: gen(p, lastSon(it), stmt) moveInto(p, stmt, r) - lineF(p, "break;$n", []) + if transferRange: + lineF(p, "}$n", []) + else: + lineF(p, "break;$n", []) of nkElse: - lineF(p, "default: $n", []) + if transferRange: + if n.len == 2: # a dangling else for a case statement + transferRange = false + lineF(p, "switch ($1) {$n", [cond.rdLoc]) + lineF(p, "default: $n", []) + else: + lineF(p, "else{$n", []) + else: + lineF(p, "default: $n", []) p.nested: - gen(p, it.sons[0], stmt) + gen(p, it[0], stmt) moveInto(p, stmt, r) - lineF(p, "break;$n", []) + if transferRange: + lineF(p, "}$n", []) + else: + lineF(p, "break;$n", []) else: internalError(p.config, it.info, "jsgen.genCaseStmt") - lineF(p, "}$n", []) + if not transferRange: + lineF(p, "}$n", []) proc genBlock(p: PProc, n: PNode, r: var TCompRes) = inc(p.unique) - let idx = len(p.blocks) - if n.sons[0].kind != nkEmpty: + let idx = p.blocks.len + if n[0].kind != nkEmpty: # named block? - if (n.sons[0].kind != nkSym): internalError(p.config, n.info, "genBlock") - var sym = n.sons[0].sym + if (n[0].kind != nkSym): internalError(p.config, n.info, "genBlock") + var sym = n[0].sym sym.loc.k = locOther sym.position = idx+1 let labl = p.unique - lineF(p, "L$1: do {$n", [labl.rope]) + lineF(p, "Label$1: {$n", [labl.rope]) setLen(p.blocks, idx + 1) p.blocks[idx].id = - p.unique # negative because it isn't used yet - gen(p, n.sons[1], r) + gen(p, n[1], r) setLen(p.blocks, idx) - lineF(p, "} while(false);$n", [labl.rope]) + lineF(p, "};$n", [labl.rope]) proc genBreakStmt(p: PProc, n: PNode) = var idx: int genLineDir(p, n) - if n.sons[0].kind != nkEmpty: + if n[0].kind != nkEmpty: # named break? - assert(n.sons[0].kind == nkSym) - let sym = n.sons[0].sym + assert(n[0].kind == nkSym) + let sym = n[0].sym assert(sym.loc.k == locOther) idx = sym.position-1 else: # an unnamed 'break' can only break a loop after 'transf' pass: - idx = len(p.blocks) - 1 + idx = p.blocks.len - 1 while idx >= 0 and not p.blocks[idx].isLoop: dec idx if idx < 0 or not p.blocks[idx].isLoop: internalError(p.config, n.info, "no loop to break") p.blocks[idx].id = abs(p.blocks[idx].id) # label is used - lineF(p, "break L$1;$n", [rope(p.blocks[idx].id)]) + lineF(p, "break Label$1;$n", [rope(p.blocks[idx].id)]) -proc genAsmOrEmitStmt(p: PProc, n: PNode) = +proc genAsmOrEmitStmt(p: PProc, n: PNode; isAsmStmt = false) = genLineDir(p, n) - p.body.add p.indentLine(nil) - for i in countup(0, sonsLen(n) - 1): + p.body.add p.indentLine("") + let offset = + if isAsmStmt: 1 # first son is pragmas + else: 0 + + for i in offset..<n.len: let it = n[i] case it.kind of nkStrLit..nkTripleStrLit: @@ -764,59 +1178,81 @@ proc genAsmOrEmitStmt(p: PProc, n: PNode) = of nkSym: let v = it.sym # for backwards compatibility we don't deref syms here :-( - if v.kind in {skVar, skLet, skTemp, skConst, skResult, skParam, skForVar}: - p.body.add mangleName(p.module, v) + if false: + discard else: - var r: TCompRes + var r = default(TCompRes) gen(p, it, r) + + if it.typ.kind == tyPointer: + # A fat pointer is disguised as an array + r.res = r.address + r.address = "" + r.typ = etyNone + elif r.typ == etyBaseIndex: + # Deference first + r.res = "$1[$2]" % [r.address, r.res] + r.address = "" + r.typ = etyNone + p.body.add(r.rdLoc) else: - var r: TCompRes + var r: TCompRes = default(TCompRes) gen(p, it, r) p.body.add(r.rdLoc) p.body.add "\L" proc genIf(p: PProc, n: PNode, r: var TCompRes) = - var cond, stmt: TCompRes + var cond, stmt: TCompRes = default(TCompRes) var toClose = 0 if not isEmptyType(n.typ): r.kind = resVal r.res = getTemp(p) - for i in countup(0, sonsLen(n) - 1): - let it = n.sons[i] - if sonsLen(it) != 1: + for i in 0..<n.len: + let it = n[i] + if it.len != 1: if i > 0: lineF(p, "else {$n", []) inc(toClose) - p.nested: gen(p, it.sons[0], cond) + p.nested: gen(p, it[0], cond) lineF(p, "if ($1) {$n", [cond.rdLoc]) - gen(p, it.sons[1], stmt) + gen(p, it[1], stmt) else: # else part: lineF(p, "else {$n", []) - p.nested: gen(p, it.sons[0], stmt) + p.nested: gen(p, it[0], stmt) moveInto(p, stmt, r) lineF(p, "}$n", []) line(p, repeat('}', toClose) & "\L") -proc generateHeader(p: PProc, typ: PType): Rope = - result = nil - for i in countup(1, sonsLen(typ.n) - 1): - assert(typ.n.sons[i].kind == nkSym) - var param = typ.n.sons[i].sym +proc generateHeader(p: PProc, prc: PSym): Rope = + result = "" + let typ = prc.typ + 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.snippet = "this" + + for i in 1..<typ.n.len: + assert(typ.n[i].kind == nkSym) + var param = typ.n[i].sym if isCompileTimeOnly(param.typ): continue - if result != nil: add(result, ", ") + if result != "": result.add(", ") var name = mangleName(p.module, param) - add(result, name) + result.add(name) if mapType(param.typ) == etyBaseIndex: - add(result, ", ") - add(result, name) - add(result, "_Idx") + result.add(", ") + result.add(name) + result.add("_Idx") proc countJsParams(typ: PType): int = - for i in countup(1, sonsLen(typ.n) - 1): - assert(typ.n.sons[i].kind == nkSym) - var param = typ.n.sons[i].sym + result = 0 + for i in 1..<typ.n.len: + assert(typ.n[i].kind == nkSym) + var param = typ.n[i].sym if isCompileTimeOnly(param.typ): continue if mapType(param.typ) == etyBaseIndex: inc result, 2 @@ -825,67 +1261,87 @@ proc countJsParams(typ: PType): int = const nodeKindsNeedNoCopy = {nkCharLit..nkInt64Lit, nkStrLit..nkTripleStrLit, - nkFloatLit..nkFloat64Lit, nkCurly, nkPar, nkTupleConstr, nkObjConstr, nkStringToCString, + nkFloatLit..nkFloat64Lit, nkPar, nkStringToCString, + nkObjConstr, nkTupleConstr, nkBracket, nkCStringToString, nkCall, nkPrefix, nkPostfix, nkInfix, nkCommand, nkHiddenCallConv, nkCallStrLit} proc needsNoCopy(p: PProc; y: PNode): bool = - result = (y.kind in nodeKindsNeedNoCopy) or - (skipTypes(y.typ, abstractInst).kind in {tyRef, tyPtr, tyLent, tyVar}) + return y.kind in nodeKindsNeedNoCopy or + ((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)) proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = - var a, b: TCompRes + var a, b: TCompRes = default(TCompRes) var xtyp = mapType(p, x.typ) - if x.kind == nkHiddenDeref and x.sons[0].kind == nkCall and xtyp != etyObject: - gen(p, x.sons[0], a) - let tmp = p.getTemp(false) - lineF(p, "var $1 = $2;$n", [tmp, a.rdLoc]) - a.res = "$1[0][$1[1]]" % [tmp] - else: - gen(p, x, a) + # disable `[]=` for cstring + if x.kind == nkBracketExpr and x.len >= 2 and x[0].typ.skipTypes(abstractInst).kind == tyCstring: + localError(p.config, x.info, "cstring doesn't support `[]=` operator") + gen(p, x, a) + genLineDir(p, y) gen(p, y, b) # we don't care if it's an etyBaseIndex (global) of a string, it's # still a string that needs to be copied properly: - if x.typ.skipTypes(abstractInst).kind in {tySequence, tyOpt, tyString}: + if x.typ.skipTypes(abstractInst).kind in {tySequence, tyString}: xtyp = etySeq case xtyp of etySeq: - if (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded: + if x.typ.kind in {tyVar, tyLent} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded: lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc]) else: useMagic(p, "nimCopy") lineF(p, "$1 = nimCopy(null, $2, $3);$n", [a.rdLoc, b.res, genTypeInfo(p, y.typ)]) of etyObject: - if (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded: + if x.typ.kind in {tyVar, tyLent, tyOpenArray, tyVarargs} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded: lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc]) else: useMagic(p, "nimCopy") - lineF(p, "nimCopy($1, $2, $3);$n", - [a.res, b.res, genTypeInfo(p, y.typ)]) + # supports proc getF(): var T + if x.kind in {nkHiddenDeref, nkDerefExpr} and x[0].kind in nkCallKinds: + lineF(p, "nimCopy($1, $2, $3);$n", + [a.res, b.res, genTypeInfo(p, x.typ)]) + else: + lineF(p, "$1 = nimCopy($1, $2, $3);$n", + [a.res, b.res, genTypeInfo(p, x.typ)]) of etyBaseIndex: if a.typ != etyBaseIndex or b.typ != etyBaseIndex: if y.kind == nkCall: let tmp = p.getTemp(false) lineF(p, "var $1 = $4; $2 = $1[0]; $3 = $1[1];$n", [tmp, a.address, a.res, b.rdLoc]) elif b.typ == etyBaseIndex: - lineF(p, "$# = $#;$n", [a.res, b.rdLoc]) + lineF(p, "$# = [$#, $#];$n", [a.res, b.address, b.res]) + elif b.typ == etyNone: + internalAssert p.config, b.address == "" + lineF(p, "$# = [$#, 0];$n", [a.address, b.res]) + elif x.typ.kind == tyVar and y.typ.kind == tyPtr: + lineF(p, "$# = [$#, $#];$n", [a.res, b.address, b.res]) + lineF(p, "$1 = $2;$n", [a.address, b.res]) + lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc]) + elif a.typ == etyBaseIndex: + # array indexing may not map to var type + if b.address != "": + lineF(p, "$1 = $2; $3 = $4;$n", [a.address, b.address, a.res, b.res]) + else: + lineF(p, "$1 = $2;$n", [a.address, b.res]) else: - internalError(p.config, x.info, "genAsgn") - else: + internalError(p.config, x.info, $("genAsgn", b.typ, a.typ)) + elif b.address != "": lineF(p, "$1 = $2; $3 = $4;$n", [a.address, b.address, a.res, b.res]) + else: + lineF(p, "$1 = $2;$n", [a.address, b.res]) else: - lineF(p, "$1 = $2;$n", [a.res, b.res]) + lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc]) proc genAsgn(p: PProc, n: PNode) = - genLineDir(p, n) - genAsgnAux(p, n.sons[0], n.sons[1], noCopyNeeded=false) + genAsgnAux(p, n[0], n[1], noCopyNeeded=false) proc genFastAsgn(p: PProc, n: PNode) = - genLineDir(p, n) # 'shallowCopy' always produced 'noCopyNeeded = true' here but this is wrong # for code like # while j >= pos: @@ -893,184 +1349,270 @@ proc genFastAsgn(p: PProc, n: PNode) = # See bug #5933. So we try to be more compatible with the C backend semantics # here for 'shallowCopy'. This is an educated guess and might require further # changes later: - let noCopy = n[0].typ.skipTypes(abstractInst).kind in {tySequence, tyOpt, tyString} - genAsgnAux(p, n.sons[0], n.sons[1], noCopyNeeded=noCopy) + let noCopy = n[0].typ.skipTypes(abstractInst).kind in {tySequence, tyString} + genAsgnAux(p, n[0], n[1], noCopyNeeded=noCopy) proc genSwap(p: PProc, n: PNode) = - var a, b: TCompRes - gen(p, n.sons[1], a) - gen(p, n.sons[2], b) - var tmp = p.getTemp(false) - if mapType(p, skipTypes(n.sons[1].typ, abstractVar)) == etyBaseIndex: - let tmp2 = p.getTemp(false) - if a.typ != etyBaseIndex or b.typ != etyBaseIndex: - internalError(p.config, n.info, "genSwap") - lineF(p, "var $1 = $2; $2 = $3; $3 = $1;$n", - [tmp, a.address, b.address]) - tmp = tmp2 - lineF(p, "var $1 = $2; $2 = $3; $3 = $1;", - [tmp, a.res, b.res]) + let stmtList = lowerSwap(p.module.graph, n, p.module.idgen, if p.prc != nil: p.prc else: p.module.module) + assert stmtList.kind == nkStmtList + for i in 0..<stmtList.len: + genStmt(p, stmtList[i]) proc getFieldPosition(p: PProc; f: PNode): int = case f.kind of nkIntLit..nkUInt64Lit: result = int(f.intVal) of nkSym: result = f.sym.position - else: internalError(p.config, f.info, "genFieldPosition") + else: + result = 0 + internalError(p.config, f.info, "genFieldPosition") proc genFieldAddr(p: PProc, n: PNode, r: var TCompRes) = - var a: TCompRes + var a: TCompRes = default(TCompRes) r.typ = etyBaseIndex - let b = if n.kind == nkHiddenAddr: n.sons[0] else: n - gen(p, b.sons[0], a) - if skipTypes(b.sons[0].typ, abstractVarRange).kind == tyTuple: - r.res = makeJSString("Field" & $getFieldPosition(p, b.sons[1])) + let b = if n.kind == nkHiddenAddr: n[0] else: n + gen(p, b[0], a) + if skipTypes(b[0].typ, abstractVarRange).kind == tyTuple: + r.res = makeJSString("Field" & $getFieldPosition(p, b[1])) else: - if b.sons[1].kind != nkSym: internalError(p.config, b.sons[1].info, "genFieldAddr") - var f = b.sons[1].sym - if f.loc.r == nil: f.loc.r = mangleName(p.module, f) - r.res = makeJSString($f.loc.r) + if b[1].kind != nkSym: internalError(p.config, b[1].info, "genFieldAddr") + var f = b[1].sym + if f.loc.snippet == "": f.loc.snippet = mangleName(p.module, f) + r.res = makeJSString($f.loc.snippet) internalAssert p.config, a.typ != etyBaseIndex r.address = a.res r.kind = resExpr proc genFieldAccess(p: PProc, n: PNode, r: var TCompRes) = - r.typ = etyNone - gen(p, n.sons[0], r) - let otyp = skipTypes(n.sons[0].typ, abstractVarRange) + gen(p, n[0], r) + r.typ = mapType(n.typ) + let otyp = skipTypes(n[0].typ, abstractVarRange) + + template mkTemp(i: int) = + if r.typ == etyBaseIndex: + if needsTemp(p, n[i]): + let tmp = p.getTemp + r.address = "($1 = $2, $1)[0]" % [tmp, r.res] + r.res = "$1[1]" % [tmp] + r.tmpLoc = tmp + else: + r.address = "$1[0]" % [r.res] + r.res = "$1[1]" % [r.res] if otyp.kind == tyTuple: r.res = ("$1.Field$2") % - [r.res, getFieldPosition(p, n.sons[1]).rope] + [r.res, getFieldPosition(p, n[1]).rope] + mkTemp(0) else: - if n.sons[1].kind != nkSym: internalError(p.config, n.sons[1].info, "genFieldAccess") - var f = n.sons[1].sym - if f.loc.r == nil: f.loc.r = mangleName(p.module, f) - r.res = "$1.$2" % [r.res, f.loc.r] + if n[1].kind != nkSym: internalError(p.config, n[1].info, "genFieldAccess") + var f = n[1].sym + if f.loc.snippet == "": f.loc.snippet = mangleName(p.module, f) + r.res = "$1.$2" % [r.res, f.loc.snippet] + mkTemp(1) r.kind = resExpr proc genAddr(p: PProc, n: PNode, r: var TCompRes) -proc genCheckedFieldAddr(p: PProc, n: PNode, r: var TCompRes) = - let m = if n.kind == nkHiddenAddr: n.sons[0] else: n - internalAssert p.config, m.kind == nkCheckedFieldExpr - genAddr(p, m, r) # XXX - -proc genCheckedFieldAccess(p: PProc, n: PNode, r: var TCompRes) = - genFieldAccess(p, n.sons[0], r) # XXX +proc genCheckedFieldOp(p: PProc, n: PNode, addrTyp: PType, r: var TCompRes) = + internalAssert p.config, n.kind == nkCheckedFieldExpr + # nkDotExpr to access the requested field + let accessExpr = n[0] + # nkCall to check if the discriminant is valid + var checkExpr = n[1] + + let negCheck = checkExpr[0].sym.magic == mNot + if negCheck: + checkExpr = checkExpr[^1] + + # Field symbol + var field = accessExpr[1].sym + internalAssert p.config, field.kind == skField + if field.loc.snippet == "": field.loc.snippet = mangleName(p.module, field) + # Discriminant symbol + let disc = checkExpr[2].sym + internalAssert p.config, disc.kind == skField + if disc.loc.snippet == "": disc.loc.snippet = mangleName(p.module, disc) + + var setx: TCompRes = default(TCompRes) + gen(p, checkExpr[1], setx) + + var obj: TCompRes = default(TCompRes) + gen(p, accessExpr[0], obj) + # Avoid evaluating the LHS twice (one to read the discriminant and one to read + # the field) + let tmp = p.getTemp() + lineF(p, "var $1 = $2;$n", tmp, obj.res) + + useMagic(p, "raiseFieldError2") + useMagic(p, "makeNimstrLit") + useMagic(p, "reprDiscriminant") # no need to offset by firstOrd unlike for cgen + let msg = genFieldDefect(p.config, field.name.s, disc) + lineF(p, "if ($1[$2.$3]$4undefined) { raiseFieldError2(makeNimstrLit($5), reprDiscriminant($2.$3, $6)); }$n", + setx.res, tmp, disc.loc.snippet, if negCheck: "!==" else: "===", + makeJSString(msg), genTypeInfo(p, disc.typ)) + + if addrTyp != nil and mapType(p, addrTyp) == etyBaseIndex: + r.typ = etyBaseIndex + r.res = makeJSString($field.loc.snippet) + r.address = tmp + else: + r.typ = etyNone + r.res = "$1.$2" % [tmp, field.loc.snippet] + r.kind = resExpr proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) = var - a, b: TCompRes - first: BiggestInt + a, b: TCompRes = default(TCompRes) + first: Int128 = Zero r.typ = etyBaseIndex - let m = if n.kind == nkHiddenAddr: n.sons[0] else: n - gen(p, m.sons[0], a) - gen(p, m.sons[1], b) - internalAssert p.config, a.typ != etyBaseIndex and b.typ != etyBaseIndex - r.address = a.res - var typ = skipTypes(m.sons[0].typ, abstractPtrs) - if typ.kind == tyArray: first = firstOrd(p.config, typ.sons[0]) - else: first = 0 + let m = if n.kind == nkHiddenAddr: n[0] else: n + gen(p, m[0], a) + gen(p, m[1], b) + #internalAssert p.config, a.typ != etyBaseIndex and b.typ != etyBaseIndex + let (x, tmp) = maybeMakeTemp(p, m[0], a) + r.address = x + var typ = skipTypes(m[0].typ, abstractPtrs) + if typ.kind == tyArray: + first = firstOrd(p.config, typ.indexType) if optBoundsCheck in p.options: useMagic(p, "chckIndx") - r.res = "chckIndx($1, $2, $3.length+$2-1)-$2" % [b.res, rope(first), a.res] + if first == 0: # save a couple chars + r.res = "chckIndx($1, 0, ($2).length - 1)" % [b.res, tmp] + else: + r.res = "chckIndx($1, $2, ($3).length + ($2) - 1) - ($2)" % [ + b.res, rope(first), tmp] elif first != 0: - r.res = "($1)-$2" % [b.res, rope(first)] + r.res = "($1) - ($2)" % [b.res, rope(first)] else: r.res = b.res r.kind = resExpr proc genArrayAccess(p: PProc, n: PNode, r: var TCompRes) = - var ty = skipTypes(n.sons[0].typ, abstractVarRange) - if ty.kind in {tyRef, tyPtr, tyLent}: ty = skipTypes(ty.lastSon, abstractVarRange) + var ty = skipTypes(n[0].typ, abstractVarRange+tyUserTypeClasses) + if ty.kind in {tyRef, tyPtr, tyLent, tyOwned}: ty = skipTypes(ty.elementType, abstractVarRange) case ty.kind - of tyArray, tyOpenArray, tySequence, tyString, tyCString, tyVarargs: + of tyArray, tyOpenArray, tySequence, tyString, tyCstring, tyVarargs: genArrayAddr(p, n, r) of tyTuple: genFieldAddr(p, n, r) else: internalError(p.config, n.info, "expr(nkBracketExpr, " & $ty.kind & ')') - r.typ = etyNone - if r.res == nil: internalError(p.config, n.info, "genArrayAccess") - if ty.kind == tyCString: + r.typ = mapType(n.typ) + if r.res == "": internalError(p.config, n.info, "genArrayAccess") + if ty.kind == tyCstring: r.res = "$1.charCodeAt($2)" % [r.address, r.res] + elif r.typ == etyBaseIndex: + if needsTemp(p, n[0]): + let tmp = p.getTemp + r.address = "($1 = $2, $1)[0]" % [tmp, r.rdLoc] + r.res = "$1[1]" % [tmp] + r.tmpLoc = tmp + else: + let x = r.rdLoc + r.address = "$1[0]" % [x] + r.res = "$1[1]" % [x] else: r.res = "$1[$2]" % [r.address, r.res] - r.address = nil r.kind = resExpr template isIndirect(x: PSym): bool = let v = x ({sfAddrTaken, sfGlobal} * v.flags != {} and #(mapType(v.typ) != etyObject) and - {sfImportc, sfVolatile, sfExportc} * v.flags == {} and + {sfImportc, sfExportc} * v.flags == {} and v.kind notin {skProc, skFunc, skConverter, skMethod, skIterator, skConst, skTemp, skLet}) +proc genSymAddr(p: PProc, n: PNode, typ: PType, r: var TCompRes) = + let s = n.sym + if s.loc.snippet == "": internalError(p.config, n.info, "genAddr: 3") + case s.kind + of skParam: + r.res = s.loc.snippet + r.address = "" + r.typ = etyNone + of skVar, skLet, skResult: + r.kind = resExpr + let jsType = mapType(p): + if typ.isNil: + n.typ + else: + typ + if jsType == etyObject: + # make addr() a no-op: + r.typ = etyNone + if isIndirect(s): + r.res = s.loc.snippet & "[0]" + else: + r.res = s.loc.snippet + r.address = "" + elif {sfGlobal, sfAddrTaken} * s.flags != {} or jsType == etyBaseIndex: + # for ease of code generation, we do not distinguish between + # sfAddrTaken and sfGlobal. + r.typ = etyBaseIndex + r.address = s.loc.snippet + r.res = rope("0") + else: + # 'var openArray' for instance produces an 'addr' but this is harmless: + gen(p, n, r) + #internalError(p.config, n.info, "genAddr: 4 " & renderTree(n)) + else: internalError(p.config, n.info, $("genAddr: 2", s.kind)) + proc genAddr(p: PProc, n: PNode, r: var TCompRes) = - case n.sons[0].kind - of nkSym: - let s = n.sons[0].sym - if s.loc.r == nil: internalError(p.config, n.info, "genAddr: 3") - case s.kind - of skVar, skLet, skResult: - r.kind = resExpr - let jsType = mapType(p, n.typ) - if jsType == etyObject: - # make addr() a no-op: - r.typ = etyNone - if isIndirect(s): - r.res = s.loc.r & "[0]" - else: - r.res = s.loc.r - r.address = nil - elif {sfGlobal, sfAddrTaken} * s.flags != {} or jsType == etyBaseIndex: - # for ease of code generation, we do not distinguish between - # sfAddrTaken and sfGlobal. - r.typ = etyBaseIndex - r.address = s.loc.r - r.res = rope("0") + if n.kind == nkSym: + genSymAddr(p, n, nil, r) + else: + case n[0].kind + of nkSym: + genSymAddr(p, n[0], n.typ, r) + of nkCheckedFieldExpr: + genCheckedFieldOp(p, n[0], n.typ, r) + of nkDotExpr: + if mapType(p, n.typ) == etyBaseIndex: + genFieldAddr(p, n[0], r) + else: + genFieldAccess(p, n[0], r) + of nkBracketExpr: + var ty = skipTypes(n[0].typ, abstractVarRange) + if ty.kind in MappedToObject: + gen(p, n[0], r) else: + let kindOfIndexedExpr = skipTypes(n[0][0].typ, abstractVarRange+tyUserTypeClasses).kind + case kindOfIndexedExpr + of tyArray, tyOpenArray, tySequence, tyString, tyCstring, tyVarargs: + genArrayAddr(p, n[0], r) + of tyTuple: + genFieldAddr(p, n[0], r) + of tyGenericBody: + genAddr(p, n[^1], r) + else: internalError(p.config, n[0].info, "expr(nkBracketExpr, " & $kindOfIndexedExpr & ')') + of nkObjDownConv: + gen(p, n[0], r) + of nkHiddenDeref: + gen(p, n[0], r) + of nkDerefExpr: + var x = n[0] + if n.kind == nkHiddenAddr: + x = n[0][0] + if n.typ.skipTypes(abstractVar).kind != tyOpenArray: + x.typ = n.typ + gen(p, x, r) + of nkHiddenAddr: + gen(p, n[0], r) + of nkConv: + genAddr(p, n[0], r) + of nkStmtListExpr: + if n.len == 1: gen(p, n[0], r) + else: internalError(p.config, n[0].info, "genAddr for complex nkStmtListExpr") + of nkCallKinds: + if n[0].typ.kind == tyOpenArray: # 'var openArray' for instance produces an 'addr' but this is harmless: - gen(p, n.sons[0], r) - #internalError(p.config, n.info, "genAddr: 4 " & renderTree(n)) - else: internalError(p.config, n.info, "genAddr: 2") - of nkCheckedFieldExpr: - genCheckedFieldAddr(p, n, r) - of nkDotExpr: - if mapType(p, n.typ) == etyBaseIndex: - genFieldAddr(p, n.sons[0], r) - else: - genFieldAccess(p, n.sons[0], r) - of nkBracketExpr: - var ty = skipTypes(n.sons[0].typ, abstractVarRange) - if ty.kind in MappedToObject: - gen(p, n.sons[0], r) - else: - let kindOfIndexedExpr = skipTypes(n.sons[0].sons[0].typ, abstractVarRange).kind - case kindOfIndexedExpr - of tyArray, tyOpenArray, tySequence, tyString, tyCString, tyVarargs: - genArrayAddr(p, n.sons[0], r) - of tyTuple: - genFieldAddr(p, n.sons[0], r) - else: internalError(p.config, n.sons[0].info, "expr(nkBracketExpr, " & $kindOfIndexedExpr & ')') - of nkObjDownConv: - gen(p, n.sons[0], r) - of nkHiddenDeref: - gen(p, n.sons[0].sons[0], r) - else: internalError(p.config, n.sons[0].info, "genAddr: " & $n.sons[0].kind) - -proc thisParam(p: PProc; typ: PType): PType = - discard + # namely toOpenArray(a, 1, 3) + gen(p, n[0], r) + else: + internalError(p.config, n[0].info, "genAddr: " & $n[0].kind) + else: + internalError(p.config, n[0].info, "genAddr: " & $n[0].kind) proc attachProc(p: PProc; content: Rope; s: PSym) = - let otyp = thisParam(p, s.typ) - if otyp != nil: - for i, cls in p.g.classes: - if sameType(cls[0], otyp): - add(p.g.classes[i][1], content) - return - p.g.classes.add((otyp, content)) - else: - add(p.g.code, content) + p.g.code.add(content) proc attachProc(p: PProc; s: PSym) = let newp = genProc(p, s) @@ -1078,44 +1620,75 @@ proc attachProc(p: PProc; s: PSym) = proc genProcForSymIfNeeded(p: PProc, s: PSym) = if not p.g.generatedSyms.containsOrIncl(s.id): - let newp = genProc(p, s) - var owner = p - while owner != nil and owner.prc != s.owner: - owner = owner.up - if owner != nil: add(owner.locals, newp) - else: attachProc(p, newp, 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.snippet, genTypeInfo(p, s.typ)] + owner.locals.add(owner.indentLine(copy)) + return + owner = owner.up + +proc genVarInit(p: PProc, v: PSym, n: PNode) proc genSym(p: PProc, n: PNode, r: var TCompRes) = var s = n.sym case s.kind of skVar, skLet, skParam, skTemp, skResult, skForVar: - if s.loc.r == nil: + if s.loc.snippet == "": 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 if {sfAddrTaken, sfGlobal} * s.flags != {}: - r.address = "$1[0]" % [s.loc.r] - r.res = "$1[1]" % [s.loc.r] + if isIndirect(s): + r.address = "$1[0][0]" % [s.loc.snippet] + r.res = "$1[0][1]" % [s.loc.snippet] + else: + r.address = "$1[0]" % [s.loc.snippet] + r.res = "$1[1]" % [s.loc.snippet] else: - r.address = s.loc.r - r.res = s.loc.r & "_Idx" + r.address = s.loc.snippet + r.res = s.loc.snippet & "_Idx" elif isIndirect(s): - r.res = "$1[0]" % [s.loc.r] + r.res = "$1[0]" % [s.loc.snippet] else: - r.res = s.loc.r + r.res = s.loc.snippet of skConst: genConstant(p, s) - if s.loc.r == nil: + if s.loc.snippet == "": internalError(p.config, n.info, "symbol has no generated name: " & s.name.s) - r.res = s.loc.r - of skProc, skFunc, skConverter, skMethod: + r.res = s.loc.snippet + of skProc, skFunc, skConverter, skMethod, skIterator: + if sfCompileTime in s.flags: + localError(p.config, n.info, "request to generate code for .compileTime proc: " & + s.name.s) discard mangleName(p.module, s) - r.res = s.loc.r - if lfNoDecl in s.loc.flags or s.magic != mNone or + r.res = s.loc.snippet + if lfNoDecl in s.loc.flags or s.magic notin generatedMagics or {sfImportc, sfInfixCall} * s.flags != {}: discard - elif s.kind == skMethod and s.getBody.kind == nkEmpty: + elif s.kind == skMethod and getBody(p.module.graph, s).kind == nkEmpty: # we cannot produce code for the dispatcher yet: discard elif sfForward in s.flags: @@ -1123,84 +1696,92 @@ proc genSym(p: PProc, n: PNode, r: var TCompRes) = else: genProcForSymIfNeeded(p, s) else: - if s.loc.r == nil: + if s.loc.snippet == "": internalError(p.config, n.info, "symbol has no generated name: " & s.name.s) - r.res = s.loc.r + if mapType(p, s.typ) == etyBaseIndex: + r.address = s.loc.snippet + r.res = s.loc.snippet & "_Idx" + else: + r.res = s.loc.snippet r.kind = resVal proc genDeref(p: PProc, n: PNode, r: var TCompRes) = - let it = n.sons[0] + let it = n[0] let t = mapType(p, it.typ) - if t == etyObject: + if t == etyObject or it.typ.kind == tyLent: gen(p, it, r) else: - var a: TCompRes + var a: TCompRes = default(TCompRes) gen(p, it, a) - r.kind = resExpr - if a.typ == etyBaseIndex: - r.res = "$1[$2]" % [a.address, a.res] - elif it.kind == nkCall: + r.kind = a.kind + r.typ = mapType(p, n.typ) + if r.typ == etyBaseIndex: let tmp = p.getTemp - r.res = "($1 = $2, $1[0])[$1[1]]" % [tmp, a.res] - elif t == etyBaseIndex: - r.res = "$1[0]" % [a.res] + r.address = "($1 = $2, $1)[0]" % [tmp, a.rdLoc] + r.res = "$1[1]" % [tmp] + r.tmpLoc = tmp + elif a.typ == etyBaseIndex: + if a.tmpLoc != "": + r.tmpLoc = a.tmpLoc + r.res = a.rdLoc else: internalError(p.config, n.info, "genDeref") proc genArgNoParam(p: PProc, n: PNode, r: var TCompRes) = - var a: TCompRes + var a: TCompRes = default(TCompRes) gen(p, n, a) if a.typ == etyBaseIndex: - add(r.res, a.address) - add(r.res, ", ") - add(r.res, a.res) + r.res.add(a.address) + r.res.add(", ") + r.res.add(a.res) else: - add(r.res, a.res) + r.res.add(a.res) proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes; emitted: ptr int = nil) = - var a: TCompRes + var a: TCompRes = default(TCompRes) gen(p, n, a) if skipTypes(param.typ, abstractVar).kind in {tyOpenArray, tyVarargs} and a.typ == etyBaseIndex: - add(r.res, "$1[$2]" % [a.address, a.res]) + r.res.add("$1[$2]" % [a.address, a.res]) elif a.typ == etyBaseIndex: - add(r.res, a.address) - add(r.res, ", ") - add(r.res, a.res) + r.res.add(a.address) + r.res.add(", ") + r.res.add(a.res) if emitted != nil: inc emitted[] - elif n.typ.kind in {tyVar, tyLent} and n.kind in nkCallKinds and mapType(param.typ) == etyBaseIndex: + elif n.typ.kind in {tyVar, tyPtr, tyRef, tyLent, tyOwned} and + n.kind in nkCallKinds and mapType(param.typ) == etyBaseIndex: # this fixes bug #5608: let tmp = getTemp(p) - add(r.res, "($1 = $2, $1[0]), $1[1]" % [tmp, a.rdLoc]) + r.res.add("($1 = $2, $1[0]), $1[1]" % [tmp, a.rdLoc]) if emitted != nil: inc emitted[] else: - add(r.res, a.res) + r.res.add(a.res) proc genArgs(p: PProc, n: PNode, r: var TCompRes; start=1) = - add(r.res, "(") + r.res.add("(") var hasArgs = false - var typ = skipTypes(n.sons[0].typ, abstractInst) + var typ = skipTypes(n[0].typ, abstractInst) assert(typ.kind == tyProc) - assert(sonsLen(typ) == sonsLen(typ.n)) + assert(typ.len == typ.n.len) var emitted = start-1 - for i in countup(start, sonsLen(n) - 1): - let it = n.sons[i] + for i in start..<n.len: + let it = n[i] var paramType: PNode = nil - if i < sonsLen(typ): - assert(typ.n.sons[i].kind == nkSym) - paramType = typ.n.sons[i] + if i < typ.len: + assert(typ.n[i].kind == nkSym) + paramType = typ.n[i] if paramType.typ.isCompileTimeOnly: continue - if hasArgs: add(r.res, ", ") + if hasArgs: r.res.add(", ") if paramType.isNil: genArgNoParam(p, it, r) else: genArg(p, it, paramType.sym, r, addr emitted) inc emitted hasArgs = true - add(r.res, ")") + r.res.add(")") when false: # XXX look into this: let jsp = countJsParams(typ) @@ -1216,9 +1797,9 @@ proc genOtherArg(p: PProc; n: PNode; i: int; typ: PType; " but got only: " & $(n.len-1)) let it = n[i] var paramType: PNode = nil - if i < sonsLen(typ): - assert(typ.n.sons[i].kind == nkSym) - paramType = typ.n.sons[i] + if i < typ.len: + assert(typ.n[i].kind == nkSym) + paramType = typ.n[i] if paramType.typ.isCompileTimeOnly: return if paramType.isNil: genArgNoParam(p, it, r) @@ -1235,8 +1816,8 @@ proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType; case pat[i] of '@': var generated = 0 - for k in j ..< n.len: - if generated > 0: add(r.res, ", ") + for k in j..<n.len: + if generated > 0: r.res.add(", ") genOtherArg(p, n, k, typ, generated, r) inc i of '#': @@ -1246,11 +1827,11 @@ proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType; inc i of '\31': # unit separator - add(r.res, "#") + r.res.add("#") inc i of '\29': # group separator - add(r.res, "@") + r.res.add("@") inc i else: let start = i @@ -1258,53 +1839,58 @@ proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType; if pat[i] notin {'@', '#', '\31', '\29'}: inc(i) else: break if i - 1 >= start: - add(r.res, substr(pat, start, i - 1)) + r.res.add(substr(pat, start, i - 1)) proc genInfixCall(p: PProc, n: PNode, r: var TCompRes) = # don't call '$' here for efficiency: let f = n[0].sym - if f.loc.r == nil: f.loc.r = mangleName(p.module, f) + if f.loc.snippet == "": f.loc.snippet = mangleName(p.module, f) if sfInfixCall in f.flags: - let pat = n.sons[0].sym.loc.r.data - internalAssert p.config, pat != nil + let pat = $n[0].sym.loc.snippet + internalAssert p.config, pat.len > 0 if pat.contains({'#', '(', '@'}): - var typ = skipTypes(n.sons[0].typ, abstractInst) + var typ = skipTypes(n[0].typ, abstractInst) assert(typ.kind == tyProc) genPatternCall(p, n, pat, typ, r) return if n.len != 1: - gen(p, n.sons[1], r) + gen(p, n[1], r) if r.typ == etyBaseIndex: - if r.address == nil: + if r.address == "": globalError(p.config, n.info, "cannot invoke with infix syntax") r.res = "$1[$2]" % [r.address, r.res] - r.address = nil + r.address = "" r.typ = etyNone - add(r.res, ".") - var op: TCompRes - gen(p, n.sons[0], op) - add(r.res, op.res) + r.res.add(".") + var op: TCompRes = default(TCompRes) + gen(p, n[0], op) + r.res.add(op.res) genArgs(p, n, r, 2) proc genCall(p: PProc, n: PNode, r: var TCompRes) = - if n.sons[0].kind == nkSym and thisParam(p, n.sons[0].typ) != nil: - genInfixCall(p, n, r) - return - gen(p, n.sons[0], r) + gen(p, n[0], r) genArgs(p, n, r) + if n.typ != nil: + let t = mapType(n.typ) + if t == etyBaseIndex: + let tmp = p.getTemp + r.address = "($1 = $2, $1)[0]" % [tmp, r.rdLoc] + r.res = "$1[1]" % [tmp] + r.tmpLoc = tmp + r.typ = t proc genEcho(p: PProc, n: PNode, r: var TCompRes) = let n = n[1].skipConv internalAssert p.config, n.kind == nkBracket useMagic(p, "toJSStr") # Used in rawEcho useMagic(p, "rawEcho") - add(r.res, "rawEcho(") - for i in countup(0, sonsLen(n) - 1): - let it = n.sons[i] + r.res.add("rawEcho(") + for i in 0..<n.len: + let it = n[i] if it.typ.isCompileTimeOnly: continue - if i > 0: add(r.res, ", ") + if i > 0: r.res.add(", ") genArgNoParam(p, it, r) - add(r.res, ")") + r.res.add(")") r.kind = resExpr proc putToSeq(s: string, indirect: bool): Rope = @@ -1315,13 +1901,15 @@ proc createVar(p: PProc, typ: PType, indirect: bool): Rope proc createRecordVarAux(p: PProc, rec: PNode, excludedFieldIDs: IntSet, output: var Rope) = case rec.kind of nkRecList: - for i in countup(0, sonsLen(rec) - 1): - createRecordVarAux(p, rec.sons[i], excludedFieldIDs, output) + for i in 0..<rec.len: + createRecordVarAux(p, rec[i], excludedFieldIDs, output) of nkRecCase: - createRecordVarAux(p, rec.sons[0], excludedFieldIDs, output) - for i in countup(1, sonsLen(rec) - 1): - createRecordVarAux(p, lastSon(rec.sons[i]), excludedFieldIDs, output) + createRecordVarAux(p, rec[0], excludedFieldIDs, output) + for i in 1..<rec.len: + createRecordVarAux(p, lastSon(rec[i]), excludedFieldIDs, output) of nkSym: + # Do not produce code for void types + if isEmptyType(rec.sym.typ): return if rec.sym.id notin excludedFieldIDs: if output.len > 0: output.add(", ") output.addf("$#: ", [mangleName(p.module, rec.sym)]) @@ -1332,43 +1920,71 @@ proc createObjInitList(p: PProc, typ: PType, excludedFieldIDs: IntSet, output: v var t = typ if objHasTypeField(t): if output.len > 0: output.add(", ") - addf(output, "m_type: $1", [genTypeInfo(p, t)]) + output.addf("m_type: $1", [genTypeInfo(p, t)]) while t != nil: t = t.skipTypes(skipPtrs) createRecordVarAux(p, t.n, excludedFieldIDs, output) - t = t.sons[0] + t = t.baseClass -proc arrayTypeForElemType(typ: PType): string = - # XXX This should also support tyEnum and tyBool +proc arrayTypeForElemType(conf: ConfigRef; typ: PType): string = + let typ = typ.skipTypes(abstractRange) case typ.kind of tyInt, tyInt32: "Int32Array" of tyInt16: "Int16Array" of tyInt8: "Int8Array" - of tyUint, tyUint32: "Uint32Array" - of tyUint16: "Uint16Array" - of tyUint8: "Uint8Array" + of tyInt64: + if optJsBigInt64 in conf.globalOptions: + "BigInt64Array" + else: + "" + of tyUInt, tyUInt32: "Uint32Array" + of tyUInt16: "Uint16Array" + of tyUInt8, tyChar, tyBool: "Uint8Array" + of tyUInt64: + if optJsBigInt64 in conf.globalOptions: + "BigUint64Array" + else: + "" of tyFloat32: "Float32Array" of tyFloat64, tyFloat: "Float64Array" - else: nil + of tyEnum: + case typ.size + of 1: "Uint8Array" + of 2: "Uint16Array" + of 4: "Uint32Array" + else: "" + else: "" proc createVar(p: PProc, typ: PType, indirect: bool): Rope = var t = skipTypes(typ, abstractInst) case t.kind - of tyInt..tyInt64, tyUInt..tyUInt64, tyEnum, tyChar: + of tyInt8..tyInt32, tyUInt8..tyUInt32, tyEnum, tyChar: result = putToSeq("0", indirect) + of tyInt, tyUInt: + if $t.sym.loc.snippet == "bigint": + result = putToSeq("0n", indirect) + else: + result = putToSeq("0", indirect) + of tyInt64, tyUInt64: + if optJsBigInt64 in p.config.globalOptions: + result = putToSeq("0n", indirect) + else: + result = putToSeq("0", indirect) of tyFloat..tyFloat128: result = putToSeq("0.0", indirect) - of tyRange, tyGenericInst, tyAlias, tySink: - result = createVar(p, lastSon(typ), indirect) + of tyRange, tyGenericInst, tyAlias, tySink, tyOwned, tyLent: + result = createVar(p, skipModifier(typ), indirect) of tySet: result = putToSeq("{}", indirect) of tyBool: result = putToSeq("false", indirect) + of tyNil: + result = putToSeq("null", indirect) of tyArray: - let length = int(lengthOrd(p.config, t)) + let length = toInt(lengthOrd(p.config, t)) let e = elemType(t) - let jsTyp = arrayTypeForElemType(e) - if not jsTyp.isNil: + let jsTyp = arrayTypeForElemType(p.config, e) + if jsTyp.len > 0: result = "new $1($2)" % [rope(jsTyp), rope(length)] elif length > 32: useMagic(p, "arrayConstr") @@ -1380,72 +1996,84 @@ proc createVar(p: PProc, typ: PType, indirect: bool): Rope = result = rope("[") var i = 0 while i < length: - if i > 0: add(result, ", ") - add(result, createVar(p, e, false)) + if i > 0: result.add(", ") + result.add(createVar(p, e, false)) inc(i) - add(result, "]") + result.add("]") if indirect: result = "[$1]" % [result] of tyTuple: result = rope("{") - for i in 0..<t.sonsLen: - if i > 0: add(result, ", ") - addf(result, "Field$1: $2", [i.rope, - createVar(p, t.sons[i], false)]) - add(result, "}") + for i in 0..<t.len: + if i > 0: result.add(", ") + result.addf("Field$1: $2", [i.rope, + createVar(p, t[i], false)]) + result.add("}") if indirect: result = "[$1]" % [result] of tyObject: - var initList: Rope + var initList: Rope = "" createObjInitList(p, t, initIntSet(), initList) - result = ("{$1}") % [initList] + result = ("({$1})") % [initList] if indirect: result = "[$1]" % [result] - of tyVar, tyPtr, tyLent, tyRef: + of tyVar, tyPtr, tyRef, tyPointer: if mapType(p, t) == etyBaseIndex: result = putToSeq("[null, 0]", indirect) else: result = putToSeq("null", indirect) - of tySequence, tyOpt, tyString, tyCString, tyPointer, tyProc: + of tySequence, tyString: + result = putToSeq("[]", indirect) + of tyCstring, tyProc, tyOpenArray: result = putToSeq("null", indirect) of tyStatic: if t.n != nil: - result = createVar(p, lastSon t, indirect) + result = createVar(p, skipModifier t, indirect) else: internalError(p.config, "createVar: " & $t.kind) - result = nil + result = "" else: internalError(p.config, "createVar: " & $t.kind) - result = nil + result = "" -template returnType: untyped = - ~"" +template returnType: untyped = "" proc genVarInit(p: PProc, v: PSym, n: PNode) = var - a: TCompRes + a: TCompRes = default(TCompRes) s: Rope varCode: string varName = mangleName(p.module, v) - useReloadingGuard = sfGlobal in v.flags and optHotCodeReloading in p.config.options + useReloadingGuard = sfGlobal in v.flags and p.config.hcrOn + useGlobalPragmas = sfGlobal in v.flags and ({sfPure, sfThread} * v.flags != {}) if v.constraint.isNil: if useReloadingGuard: lineF(p, "var $1;$n", varName) lineF(p, "if ($1 === undefined) {$n", varName) varCode = $varName + inc p.extraIndent + elif useGlobalPragmas: + lineF(p, "if (globalThis.$1 === undefined) {$n", varName) + varCode = "globalThis." & $varName + inc p.extraIndent else: varCode = "var $2" else: + # Is this really a thought through feature? this basically unused + # feature makes it impossible for almost all format strings in + # this function to be checked at compile time. varCode = v.constraint.strVal if n.kind == nkEmpty: - lineF(p, varCode & " = $3;$n", - [returnType, varName, createVar(p, v.typ, isIndirect(v))]) - if v.typ.kind in {tyVar, tyPtr, tyLent, tyRef} and mapType(p, v.typ) == etyBaseIndex: + if not isIndirect(v) and + v.typ.kind in {tyVar, tyPtr, tyLent, tyRef, tyOwned} and mapType(p, v.typ) == etyBaseIndex: + lineF(p, "var $1 = null;$n", [varName]) lineF(p, "var $1_Idx = 0;$n", [varName]) + else: + line(p, runtimeFormat(varCode & " = $3;$n", [returnType, varName, createVar(p, v.typ, isIndirect(v))])) else: gen(p, n, a) case mapType(p, v.typ) of etyObject, etySeq: - if needsNoCopy(p, n): + if v.typ.kind in {tyOpenArray, tyVarargs} or needsNoCopy(p, n): s = a.res else: useMagic(p, "nimCopy") @@ -1454,141 +2082,151 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) = let targetBaseIndex = {sfAddrTaken, sfGlobal} * v.flags == {} if a.typ == etyBaseIndex: if targetBaseIndex: - lineF(p, varCode & " = $3, $2_Idx = $4;$n", - [returnType, v.loc.r, a.address, a.res]) + line(p, runtimeFormat(varCode & " = $3, $2_Idx = $4;$n", + [returnType, v.loc.snippet, a.address, a.res])) else: - lineF(p, varCode & " = [$3, $4];$n", - [returnType, v.loc.r, a.address, a.res]) + if isIndirect(v): + line(p, runtimeFormat(varCode & " = [[$3, $4]];$n", + [returnType, v.loc.snippet, a.address, a.res])) + else: + line(p, runtimeFormat(varCode & " = [$3, $4];$n", + [returnType, v.loc.snippet, a.address, a.res])) else: if targetBaseIndex: let tmp = p.getTemp lineF(p, "var $1 = $2, $3 = $1[0], $3_Idx = $1[1];$n", - [tmp, a.res, v.loc.r]) + [tmp, a.res, v.loc.snippet]) else: - lineF(p, varCode & " = $3;$n", [returnType, v.loc.r, a.res]) + line(p, runtimeFormat(varCode & " = $3;$n", [returnType, v.loc.snippet, a.res])) return else: s = a.res if isIndirect(v): - lineF(p, varCode & " = [$3];$n", [returnType, v.loc.r, s]) + line(p, runtimeFormat(varCode & " = [$3];$n", [returnType, v.loc.snippet, s])) else: - lineF(p, varCode & " = $3;$n", [returnType, v.loc.r, s]) + line(p, runtimeFormat(varCode & " = $3;$n", [returnType, v.loc.snippet, s])) - if useReloadingGuard: + if useReloadingGuard or useGlobalPragmas: + dec p.extraIndent lineF(p, "}$n") +proc genClosureVar(p: PProc, n: PNode) = + # assert n[2].kind != nkEmpty + # TODO: fixme transform `var env.x` into `var env.x = default()` after + # the order of transf and lambdalifting is fixed + if n[2].kind != nkEmpty: + genAsgnAux(p, n[0], n[2], false) + else: + var a: TCompRes = default(TCompRes) + gen(p, n[0], a) + line(p, runtimeFormat("$1 = $2;$n", [rdLoc(a), createVar(p, n[0].typ, false)])) + proc genVarStmt(p: PProc, n: PNode) = - for i in countup(0, sonsLen(n) - 1): - var a = n.sons[i] + for i in 0..<n.len: + var a = n[i] if a.kind != nkCommentStmt: if a.kind == nkVarTuple: - let unpacked = lowerTupleUnpacking(p.module.graph, a, p.prc) + let unpacked = lowerTupleUnpacking(p.module.graph, a, p.module.idgen, p.prc) genStmt(p, unpacked) else: assert(a.kind == nkIdentDefs) - assert(a.sons[0].kind == nkSym) - var v = a.sons[0].sym - if lfNoDecl notin v.loc.flags and sfImportc notin v.flags: - genLineDir(p, a) - genVarInit(p, v, a.sons[2]) + if a[0].kind == nkSym: + var v = a[0].sym + if lfNoDecl notin v.loc.flags and sfImportc notin v.flags: + genLineDir(p, a) + if sfCompileTime notin v.flags: + genVarInit(p, v, a[2]) + else: + # lazy emit, done when it's actually used. + if v.ast == nil: v.ast = a[2] + else: # closure + genClosureVar(p, a) proc genConstant(p: PProc, c: PSym) = if lfNoDecl notin c.loc.flags and not p.g.generatedSyms.containsOrIncl(c.id): - let oldBody = p.body - p.body = nil - #genLineDir(p, c.ast) - genVarInit(p, c, c.ast) - add(p.g.constants, p.body) + let oldBody = move p.body + #genLineDir(p, c.astdef) + genVarInit(p, c, c.astdef) + p.g.constants.add(p.body) p.body = oldBody proc genNew(p: PProc, n: PNode) = - var a: TCompRes - gen(p, n.sons[1], a) - var t = skipTypes(n.sons[1].typ, abstractVar).sons[0] - lineF(p, "$1 = $2;$n", [a.res, createVar(p, t, false)]) + var a: TCompRes = default(TCompRes) + gen(p, n[1], a) + var t = skipTypes(n[1].typ, abstractVar)[0] + if mapType(t) == etyObject: + lineF(p, "$1 = $2;$n", [a.rdLoc, createVar(p, t, false)]) + elif a.typ == etyBaseIndex: + lineF(p, "$1 = [$3]; $2 = 0;$n", [a.address, a.res, createVar(p, t, false)]) + else: + lineF(p, "$1 = [[$2], 0];$n", [a.rdLoc, createVar(p, t, false)]) proc genNewSeq(p: PProc, n: PNode) = - var x, y: TCompRes - gen(p, n.sons[1], x) - gen(p, n.sons[2], y) - let t = skipTypes(n.sons[1].typ, abstractVar).sons[0] - lineF(p, "$1 = new Array($2); for (var i=0;i<$2;++i) {$1[i]=$3;}", [ + var x, y: TCompRes = default(TCompRes) + gen(p, n[1], x) + gen(p, n[2], y) + let t = skipTypes(n[1].typ, abstractVar)[0] + lineF(p, "$1 = new Array($2); for (var i = 0 ; i < $2 ; ++i) { $1[i] = $3; }", [ x.rdLoc, y.rdLoc, createVar(p, t, false)]) proc genOrd(p: PProc, n: PNode, r: var TCompRes) = - case skipTypes(n.sons[1].typ, abstractVar).kind - of tyEnum, tyInt..tyUInt64, tyChar: gen(p, n.sons[1], r) - of tyBool: unaryExpr(p, n, r, "", "($1 ? 1:0)") + case skipTypes(n[1].typ, abstractVar + abstractRange).kind + of tyEnum, tyInt..tyInt32, tyUInt..tyUInt32, tyChar: gen(p, n[1], r) + of tyInt64, tyUInt64: + if optJsBigInt64 in p.config.globalOptions: + unaryExpr(p, n, r, "", "Number($1)") + else: gen(p, n[1], r) + of tyBool: unaryExpr(p, n, r, "", "($1 ? 1 : 0)") else: internalError(p.config, n.info, "genOrd") proc genConStrStr(p: PProc, n: PNode, r: var TCompRes) = - var a: TCompRes + var a: TCompRes = default(TCompRes) - gen(p, n.sons[1], a) + gen(p, n[1], a) r.kind = resExpr - if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyChar: + if skipTypes(n[1].typ, abstractVarRange).kind == tyChar: r.res.add("[$1].concat(" % [a.res]) else: - r.res.add("($1.slice(0,-1)).concat(" % [a.res]) + r.res.add("($1).concat(" % [a.res]) - for i in countup(2, sonsLen(n) - 2): - gen(p, n.sons[i], a) - if skipTypes(n.sons[i].typ, abstractVarRange).kind == tyChar: + for i in 2..<n.len - 1: + gen(p, n[i], a) + if skipTypes(n[i].typ, abstractVarRange).kind == tyChar: r.res.add("[$1]," % [a.res]) else: - r.res.add("$1.slice(0,-1)," % [a.res]) + r.res.add("$1," % [a.res]) - gen(p, n.sons[sonsLen(n) - 1], a) - if skipTypes(n.sons[sonsLen(n) - 1].typ, abstractVarRange).kind == tyChar: - r.res.add("[$1, 0])" % [a.res]) + gen(p, n[^1], a) + if skipTypes(n[^1].typ, abstractVarRange).kind == tyChar: + r.res.add("[$1])" % [a.res]) else: r.res.add("$1)" % [a.res]) -proc genToArray(p: PProc; n: PNode; r: var TCompRes) = - # we map mArray to PHP's array constructor, a mild hack: - var a, b: TCompRes - r.kind = resExpr - r.res = rope("array(") - let x = skipConv(n[1]) - if x.kind == nkBracket: - for i in countup(0, x.len - 1): - let it = x[i] - if it.kind in {nkPar, nkTupleConstr} and it.len == 2: - if i > 0: r.res.add(", ") - gen(p, it[0], a) - gen(p, it[1], b) - r.res.add("$# => $#" % [a.rdLoc, b.rdLoc]) - else: - localError(p.config, it.info, "'toArray' needs tuple constructors") - else: - localError(p.config, x.info, "'toArray' needs an array literal") - r.res.add(")") - -proc genReprAux(p: PProc, n: PNode, r: var TCompRes, magic: string, typ: Rope = nil) = +proc genReprAux(p: PProc, n: PNode, r: var TCompRes, magic: string, typ: Rope = "") = useMagic(p, magic) - add(r.res, magic & "(") - var a: TCompRes + r.res.add(magic & "(") + var a: TCompRes = default(TCompRes) - gen(p, n.sons[1], a) + gen(p, n[1], a) if magic == "reprAny": # the pointer argument in reprAny is expandend to # (pointedto, pointer), so we need to fill it - if a.address.isNil: - add(r.res, a.res) - add(r.res, ", null") + if a.address.len == 0: + r.res.add(a.res) + r.res.add(", null") else: - add(r.res, "$1, $2" % [a.address, a.res]) + r.res.add("$1, $2" % [a.address, a.res]) else: - add(r.res, a.res) + r.res.add(a.res) - if not typ.isNil: - add(r.res, ", ") - add(r.res, typ) - add(r.res, ")") + if typ != "": + r.res.add(", ") + r.res.add(typ) + r.res.add(")") proc genRepr(p: PProc, n: PNode, r: var TCompRes) = - let t = skipTypes(n.sons[1].typ, abstractVarRange) - case t.kind: + let t = skipTypes(n[1].typ, abstractVarRange) + case t.kind of tyInt..tyInt64, tyUInt..tyUInt64: genReprAux(p, n, r, "reprInt") of tyChar: @@ -1611,11 +2249,13 @@ proc genRepr(p: PProc, n: PNode, r: var TCompRes) = genReprAux(p, n, r, "reprJSONStringify") else: genReprAux(p, n, r, "reprAny", genTypeInfo(p, t)) + r.kind = resExpr proc genOf(p: PProc, n: PNode, r: var TCompRes) = - var x: TCompRes - let t = skipTypes(n.sons[2].typ, abstractVarRange+{tyRef, tyPtr, tyLent, tyTypeDesc}) - gen(p, n.sons[1], x) + var x: TCompRes = default(TCompRes) + let t = skipTypes(n[2].typ, + abstractVarRange+{tyRef, tyPtr, tyLent, tyTypeDesc, tyOwned}) + gen(p, n[1], x) if tfFinal in t.flags: r.res = "($1.m_type == $2)" % [x.res, genTypeInfo(p, t)] else: @@ -1623,50 +2263,97 @@ proc genOf(p: PProc, n: PNode, r: var TCompRes) = r.res = "isObj($1.m_type, $2)" % [x.res, genTypeInfo(p, t)] r.kind = resExpr -proc genReset(p: PProc, n: PNode) = - var x: TCompRes - useMagic(p, "genericReset") - gen(p, n.sons[1], x) - addf(p.body, "$1 = genericReset($1, $2);$n", [x.res, - genTypeInfo(p, n.sons[1].typ)]) +proc genDefault(p: PProc, n: PNode; r: var TCompRes) = + r.res = createVar(p, n.typ, indirect = false) + r.kind = resExpr + +proc genWasMoved(p: PProc, n: PNode) = + # TODO: it should be done by nir + var x: TCompRes = default(TCompRes) + gen(p, n[1], x) + if x.typ == etyBaseIndex: + lineF(p, "$1 = null, $2 = 0;$n", [x.address, x.res]) + else: + var y: TCompRes = default(TCompRes) + genDefault(p, n[1], y) + let (a, _) = maybeMakeTempAssignable(p, n[1], x) + lineF(p, "$1 = $2;$n", [a, y.rdLoc]) + +proc genMove(p: PProc; n: PNode; r: var TCompRes) = + var a: TCompRes = default(TCompRes) + r.kind = resVal + r.res = p.getTemp() + gen(p, n[1], a) + lineF(p, "$1 = $2;$n", [r.rdLoc, a.rdLoc]) + genWasMoved(p, n) + #lineF(p, "$1 = $2;$n", [dest.rdLoc, src.rdLoc]) + +proc genDup(p: PProc; n: PNode; r: var TCompRes) = + var a: TCompRes = default(TCompRes) + r.kind = resVal + r.res = p.getTemp() + gen(p, n[1], a) + lineF(p, "$1 = $2;$n", [r.rdLoc, a.rdLoc]) + +proc genJSArrayConstr(p: PProc, n: PNode, r: var TCompRes) = + var a: TCompRes = default(TCompRes) + r.res = rope("[") + r.kind = resExpr + for i in 0 ..< n.len: + if i > 0: r.res.add(", ") + gen(p, n[i], a) + if a.typ == etyBaseIndex: + r.res.addf("[$1, $2]", [a.address, a.res]) + else: + if not needsNoCopy(p, n[i]): + let typ = n[i].typ.skipTypes(abstractInst) + useMagic(p, "nimCopy") + a.res = "nimCopy(null, $1, $2)" % [a.rdLoc, genTypeInfo(p, typ)] + r.res.add(a.res) + r.res.add("]") proc genMagic(p: PProc, n: PNode, r: var TCompRes) = var a: TCompRes line, filen: Rope - var op = n.sons[0].sym.magic + var op = n[0].sym.magic case op - of mOr: genOr(p, n.sons[1], n.sons[2], r) - of mAnd: genAnd(p, n.sons[1], n.sons[2], r) + of mOr: genOr(p, n[1], n[2], r) + of mAnd: genAnd(p, n[1], n[2], r) of mAddI..mStrToStr: arith(p, n, r, op) of mRepr: genRepr(p, n, r) of mSwap: genSwap(p, n) - of mUnaryLt: - # XXX: range checking? - if not (optOverflowCheck in p.options): unaryExpr(p, n, r, "", "$1 - 1") - else: unaryExpr(p, n, r, "subInt", "subInt($1, 1)") of mAppendStrCh: binaryExpr(p, n, r, "addChar", - "if ($1 != null) { addChar($1, $2); } else { $1 = [$2, 0]; }") + "addChar($1, $2);") of mAppendStrStr: - if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyCString: - binaryExpr(p, n, r, "", "if ($1 != null) { $1 += $2; } else { $1 = $2; }") + var lhs, rhs: TCompRes = default(TCompRes) + gen(p, n[1], lhs) + gen(p, n[2], rhs) + + if skipTypes(n[1].typ, abstractVarRange).kind == tyCstring: + let (b, tmp) = maybeMakeTemp(p, n[2], rhs) + r.res = "if (null != $1) { if (null == $2) $2 = $3; else $2 += $3; }" % + [b, lhs.rdLoc, tmp] else: - binaryExpr(p, n, r, "", - "if ($1 != null) { $1 = ($1.slice(0, -1)).concat($2); } else { $1 = $2;}") - # XXX: make a copy of $2, because of Javascript's sucking semantics + let (a, tmp) = maybeMakeTemp(p, n[1], lhs) + r.res = "$1.push.apply($3, $2);" % [a, rhs.rdLoc, tmp] + r.kind = resExpr of mAppendSeqElem: - var x, y: TCompRes - gen(p, n.sons[1], x) - gen(p, n.sons[2], y) - if needsNoCopy(p, n[2]): - r.res = "if ($1 != null) { $1.push($2); } else { $1 = [$2]; }" % [x.rdLoc, y.rdLoc] + var x, y: TCompRes = default(TCompRes) + gen(p, n[1], x) + gen(p, n[2], y) + if mapType(n[2].typ) == etyBaseIndex: + let c = "[$1, $2]" % [y.address, y.res] + r.res = "$1.push($2);" % [x.rdLoc, c] + elif needsNoCopy(p, n[2]): + r.res = "$1.push($2);" % [x.rdLoc, y.rdLoc] else: useMagic(p, "nimCopy") let c = getTemp(p, defineInLocals=false) lineF(p, "var $1 = nimCopy(null, $2, $3);$n", [c, y.rdLoc, genTypeInfo(p, n[2].typ)]) - r.res = "if ($1 != null) { $1.push($2); } else { $1 = [$2]; }" % [x.rdLoc, c] + r.res = "$1.push($2);" % [x.rdLoc, c] r.kind = resExpr of mConStrStr: genConStrStr(p, n, r) @@ -1676,48 +2363,93 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = binaryExpr(p, n, r, "cmpStrings", "(cmpStrings($1, $2) <= 0)") of mLtStr: binaryExpr(p, n, r, "cmpStrings", "(cmpStrings($1, $2) < 0)") - of mIsNil: unaryExpr(p, n, r, "", "($1 === null)") + of mIsNil: + # we want to accept undefined, so we == + if mapType(n[1].typ) != etyBaseIndex: + unaryExpr(p, n, r, "", "($1 == null)") + else: + var x: TCompRes = default(TCompRes) + gen(p, n[1], x) + r.res = "($# == null && $# === 0)" % [x.address, x.res] of mEnumToStr: genRepr(p, n, r) of mNew, mNewFinalize: genNew(p, n) - of mSizeOf: r.res = rope(getSize(p.config, n.sons[1].typ)) - of mChr, mArrToSeq: gen(p, n.sons[1], r) # nothing to do + of mChr: gen(p, n[1], r) + of mArrToSeq: + # only array literals doesn't need copy + if n[1].kind == nkBracket: + genJSArrayConstr(p, n[1], r) + else: + var x: TCompRes = default(TCompRes) + gen(p, n[1], x) + useMagic(p, "nimCopy") + r.res = "nimCopy(null, $1, $2)" % [x.rdLoc, genTypeInfo(p, n.typ)] + of mOpenArrayToSeq: + genCall(p, n, r) + of mDestroy, mTrace: discard "ignore calls to the default destructor" of mOrd: genOrd(p, n, r) - of mLengthStr: - if n.sons[1].typ.skipTypes(abstractInst).kind == tyCString: - unaryExpr(p, n, r, "", "($1 != null ? $1.length : 0)") - else: - unaryExpr(p, n, r, "", "($1 != null ? $1.length-1 : 0)") - of mXLenStr: unaryExpr(p, n, r, "", "$1.length-1") - of mLengthSeq, mLengthOpenArray, mLengthArray: - unaryExpr(p, n, r, "", "($1 != null ? $1.length : 0)") - of mXLenSeq: - unaryExpr(p, n, r, "", "$1.length") + of mLengthStr, mLengthSeq, mLengthOpenArray, mLengthArray: + var x: TCompRes = default(TCompRes) + gen(p, n[1], x) + if skipTypes(n[1].typ, abstractInst).kind == tyCstring: + let (a, tmp) = maybeMakeTemp(p, n[1], x) + r.res = "(($1) == null ? 0 : ($2).length)" % [a, tmp] + else: + r.res = "($1).length" % [x.rdLoc] + r.kind = resExpr of mHigh: - if skipTypes(n.sons[1].typ, abstractVar).kind == tyString: - unaryExpr(p, n, r, "", "($1 != null ? ($1.length-2) : -1)") + var x: TCompRes = default(TCompRes) + gen(p, n[1], x) + if skipTypes(n[1].typ, abstractInst).kind == tyCstring: + let (a, tmp) = maybeMakeTemp(p, n[1], x) + r.res = "(($1) == null ? -1 : ($2).length - 1)" % [a, tmp] else: - unaryExpr(p, n, r, "", "($1 != null ? ($1.length-1) : -1)") + r.res = "($1).length - 1" % [x.rdLoc] + r.kind = resExpr of mInc: - if n[1].typ.skipTypes(abstractRange).kind in tyUInt .. tyUInt64: + let typ = n[1].typ.skipTypes(abstractVarRange) + case typ.kind + of tyUInt..tyUInt32: binaryUintExpr(p, n, r, "+", true) + of tyUInt64: + if optJsBigInt64 in p.config.globalOptions: + binaryExpr(p, n, r, "", "$1 = BigInt.asUintN(64, $3 + BigInt($2))", true) + else: binaryUintExpr(p, n, r, "+", true) + elif typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + if optOverflowCheck notin p.options: + binaryExpr(p, n, r, "", "$1 = BigInt.asIntN(64, $3 + BigInt($2))", true) + else: binaryExpr(p, n, r, "addInt64", "$1 = addInt64($3, BigInt($2))", true) else: if optOverflowCheck notin p.options: binaryExpr(p, n, r, "", "$1 += $2") - else: binaryExpr(p, n, r, "addInt", "$1 = addInt($1, $2)") + else: binaryExpr(p, n, r, "addInt", "$1 = addInt($3, $2)", true) of ast.mDec: - if n[1].typ.skipTypes(abstractRange).kind in tyUInt .. tyUInt64: + let typ = n[1].typ.skipTypes(abstractVarRange) + case typ.kind + of tyUInt..tyUInt32: binaryUintExpr(p, n, r, "-", true) + of tyUInt64: + if optJsBigInt64 in p.config.globalOptions: + binaryExpr(p, n, r, "", "$1 = BigInt.asUintN(64, $3 - BigInt($2))", true) + else: binaryUintExpr(p, n, r, "-", true) + elif typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + if optOverflowCheck notin p.options: + binaryExpr(p, n, r, "", "$1 = BigInt.asIntN(64, $3 - BigInt($2))", true) + else: binaryExpr(p, n, r, "subInt64", "$1 = subInt64($3, BigInt($2))", true) else: if optOverflowCheck notin p.options: binaryExpr(p, n, r, "", "$1 -= $2") - else: binaryExpr(p, n, r, "subInt", "$1 = subInt($1, $2)") + else: binaryExpr(p, n, r, "subInt", "$1 = subInt($3, $2)", true) of mSetLengthStr: - binaryExpr(p, n, r, "", "$1.length = $2+1; $1[$1.length-1] = 0") + binaryExpr(p, n, r, "mnewString", + """if ($1.length < $2) { for (var i = $3.length; i < $4; ++i) $3.push(0); } + else {$3.length = $4; }""") of mSetLengthSeq: - var x, y: TCompRes - gen(p, n.sons[1], x) - gen(p, n.sons[2], y) - let t = skipTypes(n.sons[1].typ, abstractVar).sons[0] - r.res = """if ($1.length < $2) { for (var i=$1.length;i<$2;++i) $1.push($3); } - else { $1.length = $2; }""" % [x.rdLoc, y.rdLoc, createVar(p, t, false)] + var x, y: TCompRes = default(TCompRes) + gen(p, n[1], x) + gen(p, n[2], y) + let t = skipTypes(n[1].typ, abstractVar)[0] + let (a, tmp) = maybeMakeTemp(p, n[1], x) + let (b, tmp2) = maybeMakeTemp(p, n[2], y) + r.res = """if ($1.length < $2) { for (var i = $4.length ; i < $5 ; ++i) $4.push($3); } + else { $4.length = $5; }""" % [a, b, createVar(p, t, false), tmp, tmp2] r.kind = resExpr of mCard: unaryExpr(p, n, r, "SetCard", "SetCard($1)") of mLtSet: binaryExpr(p, n, r, "SetLt", "SetLt($1, $2)") @@ -1733,128 +2465,209 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = of mNewSeq: genNewSeq(p, n) of mNewSeqOfCap: unaryExpr(p, n, r, "", "[]") of mOf: genOf(p, n, r) - of mReset: genReset(p, n) + of mDefault, mZeroDefault: genDefault(p, n, r) + of mWasMoved: genWasMoved(p, n) of mEcho: genEcho(p, n, r) of mNLen..mNError, mSlurp, mStaticExec: - localError(p.config, n.info, errXMustBeCompileTime % n.sons[0].sym.name.s) - of mCopyStr: - binaryExpr(p, n, r, "", "($1.slice($2))") - of mCopyStrLast: - ternaryExpr(p, n, r, "", "($1.slice($2, ($3)+1).concat(0))") + localError(p.config, n.info, errXMustBeCompileTime % n[0].sym.name.s) of mNewString: unaryExpr(p, n, r, "mnewString", "mnewString($1)") of mNewStringOfCap: unaryExpr(p, n, r, "mnewString", "mnewString(0)") of mDotDot: - genProcForSymIfNeeded(p, n.sons[0].sym) + genProcForSymIfNeeded(p, n[0].sym) genCall(p, n, r) of mParseBiggestFloat: useMagic(p, "nimParseBiggestFloat") genCall(p, n, r) - of mArray: - genCall(p, n, r) + of mSlice: + # arr.slice([begin[, end]]): 'end' is exclusive + var x, y, z: TCompRes = default(TCompRes) + gen(p, n[1], x) + gen(p, n[2], y) + gen(p, n[3], z) + r.res = "($1.slice($2, $3 + 1))" % [x.rdLoc, y.rdLoc, z.rdLoc] + r.kind = resExpr + of mMove: + genMove(p, n, r) + of mDup: + genDup(p, n, r) + of mEnsureMove: + gen(p, n[1], r) else: genCall(p, n, r) #else internalError(p.config, e.info, 'genMagic: ' + magicToStr[op]); proc genSetConstr(p: PProc, n: PNode, r: var TCompRes) = var - a, b: TCompRes + a, b: TCompRes = default(TCompRes) useMagic(p, "setConstr") r.res = rope("setConstr(") r.kind = resExpr - for i in countup(0, sonsLen(n) - 1): - if i > 0: add(r.res, ", ") - var it = n.sons[i] + for i in 0..<n.len: + if i > 0: r.res.add(", ") + var it = n[i] if it.kind == nkRange: - gen(p, it.sons[0], a) - gen(p, it.sons[1], b) - addf(r.res, "[$1, $2]", [a.res, b.res]) + gen(p, it[0], a) + gen(p, it[1], b) + + if it[0].typ.kind == tyBool: + r.res.addf("$1, $2", [a.res, b.res]) + else: + r.res.addf("[$1, $2]", [a.res, b.res]) else: gen(p, it, a) - add(r.res, a.res) - add(r.res, ")") + r.res.add(a.res) + r.res.add(")") # emit better code for constant sets: if isDeepConstExpr(n): inc(p.g.unique) let tmp = rope("ConstSet") & rope(p.g.unique) - addf(p.g.constants, "var $1 = $2;$n", [tmp, r.res]) + p.g.constants.addf("var $1 = $2;$n", [tmp, r.res]) r.res = tmp proc genArrayConstr(p: PProc, n: PNode, r: var TCompRes) = - var a: TCompRes - r.res = rope("[") - r.kind = resExpr - for i in countup(0, sonsLen(n) - 1): - if i > 0: add(r.res, ", ") - gen(p, n.sons[i], a) - add(r.res, a.res) - add(r.res, "]") + ## Constructs array or sequence. + ## Nim array of uint8..uint32, int8..int32 maps to JS typed arrays. + ## Nim sequence maps to JS array. + var t = skipTypes(n.typ, abstractInst) + let e = elemType(t) + let jsTyp = arrayTypeForElemType(p.config, e) + if skipTypes(n.typ, abstractVarRange).kind != tySequence and jsTyp.len > 0: + # generate typed array + # for example Nim generates `new Uint8Array([1, 2, 3])` for `[byte(1), 2, 3]` + # TODO use `set` or loop to initialize typed array which improves performances in some situations + var a: TCompRes = default(TCompRes) + r.res = "new $1([" % [rope(jsTyp)] + r.kind = resExpr + for i in 0 ..< n.len: + if i > 0: r.res.add(", ") + gen(p, n[i], a) + r.res.add(a.res) + r.res.add("])") + else: + genJSArrayConstr(p, n, r) proc genTupleConstr(p: PProc, n: PNode, r: var TCompRes) = - var a: TCompRes + var a: TCompRes = default(TCompRes) r.res = rope("{") r.kind = resExpr - for i in countup(0, sonsLen(n) - 1): - if i > 0: add(r.res, ", ") - var it = n.sons[i] - if it.kind == nkExprColonExpr: it = it.sons[1] + for i in 0..<n.len: + if i > 0: r.res.add(", ") + var it = n[i] + if it.kind == nkExprColonExpr: it = it[1] gen(p, it, a) - addf(r.res, "Field$#: $#", [i.rope, a.res]) + let typ = it.typ.skipTypes(abstractInst) + if a.typ == etyBaseIndex: + r.res.addf("Field$#: [$#, $#]", [i.rope, a.address, a.res]) + else: + if not needsNoCopy(p, it): + useMagic(p, "nimCopy") + a.res = "nimCopy(null, $1, $2)" % [a.rdLoc, genTypeInfo(p, typ)] + r.res.addf("Field$#: $#", [i.rope, a.res]) r.res.add("}") proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) = - var a: TCompRes + var a: TCompRes = default(TCompRes) r.kind = resExpr - var initList : Rope + var initList : Rope = "" var fieldIDs = initIntSet() - for i in countup(1, sonsLen(n) - 1): - if i > 1: add(initList, ", ") - var it = n.sons[i] + let nTyp = n.typ.skipTypes(abstractInst) + for i in 1..<n.len: + if i > 1: initList.add(", ") + var it = n[i] internalAssert p.config, it.kind == nkExprColonExpr - let val = it.sons[1] + let val = it[1] gen(p, val, a) - var f = it.sons[0].sym - if f.loc.r == nil: f.loc.r = mangleName(p.module, f) - fieldIDs.incl(f.id) + var f = it[0].sym + if f.loc.snippet == "": f.loc.snippet = mangleName(p.module, f) + fieldIDs.incl(lookupFieldAgain(n.typ.skipTypes({tyDistinct}), f).id) let typ = val.typ.skipTypes(abstractInst) - if (typ.kind in IntegralTypes+{tyCstring, tyRef, tyPtr} and - mapType(p, typ) != etyBaseIndex) or needsNoCopy(p, it.sons[1]): - discard + if a.typ == etyBaseIndex: + initList.addf("$#: [$#, $#]", [f.loc.snippet, a.address, a.res]) else: - useMagic(p, "nimCopy") - a.res = "nimCopy(null, $1, $2)" % [a.rdLoc, genTypeInfo(p, typ)] - addf(initList, "$#: $#", [f.loc.r, a.res]) + if not needsNoCopy(p, val): + useMagic(p, "nimCopy") + a.res = "nimCopy(null, $1, $2)" % [a.rdLoc, genTypeInfo(p, typ)] + initList.addf("$#: $#", [f.loc.snippet, a.res]) let t = skipTypes(n.typ, abstractInst + skipPtrs) createObjInitList(p, t, fieldIDs, initList) r.res = ("{$1}") % [initList] proc genConv(p: PProc, n: PNode, r: var TCompRes) = var dest = skipTypes(n.typ, abstractVarRange) - var src = skipTypes(n.sons[1].typ, abstractVarRange) - gen(p, n.sons[1], r) + var src = skipTypes(n[1].typ, abstractVarRange) + gen(p, n[1], r) if dest.kind == src.kind: # no-op conversion return - case dest.kind: - of tyBool: + let toInt = (dest.kind in tyInt..tyInt32) + let fromInt = (src.kind in tyInt..tyInt32) + let toUint = (dest.kind in tyUInt..tyUInt32) + let fromUint = (src.kind in tyUInt..tyUInt32) + if toUint and (fromInt or fromUint): + let trimmer = unsignedTrimmer(dest.size) + r.res = "($1 $2)" % [r.res, trimmer] + elif dest.kind == tyBool: r.res = "(!!($1))" % [r.res] r.kind = resExpr - of tyInt: - r.res = "(($1)|0)" % [r.res] + elif toInt: + if src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions: + r.res = "Number($1)" % [r.res] + else: + r.res = "(($1) | 0)" % [r.res] + elif dest.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + if fromInt or fromUint or src.kind in {tyBool, tyChar, tyEnum}: + r.res = "BigInt($1)" % [r.res] + elif src.kind in {tyFloat..tyFloat64}: + r.res = "BigInt(Math.trunc($1))" % [r.res] + elif src.kind == tyUInt64: + r.res = "BigInt.asIntN(64, $1)" % [r.res] + elif dest.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions: + if fromUint or src.kind in {tyBool, tyChar, tyEnum}: + r.res = "BigInt($1)" % [r.res] + elif fromInt: # could be negative + r.res = "BigInt.asUintN(64, BigInt($1))" % [r.res] + elif src.kind in {tyFloat..tyFloat64}: + r.res = "BigInt.asUintN(64, BigInt(Math.trunc($1)))" % [r.res] + elif src.kind == tyInt64: + r.res = "BigInt.asUintN(64, $1)" % [r.res] + elif toUint or dest.kind in tyFloat..tyFloat64: + if src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions: + r.res = "Number($1)" % [r.res] else: # TODO: What types must we handle here? discard proc upConv(p: PProc, n: PNode, r: var TCompRes) = - gen(p, n.sons[0], r) # XXX + gen(p, n[0], r) # XXX proc genRangeChck(p: PProc, n: PNode, r: var TCompRes, magic: string) = - var a, b: TCompRes - gen(p, n.sons[0], r) - if optRangeCheck in p.options: - gen(p, n.sons[1], a) - gen(p, n.sons[2], b) + var a, b: TCompRes = default(TCompRes) + gen(p, n[0], r) + let src = skipTypes(n[0].typ, abstractVarRange) + let dest = skipTypes(n.typ, abstractVarRange) + if optRangeCheck notin p.options: + if optJsBigInt64 in p.config.globalOptions and + dest.kind in {tyUInt..tyUInt32, tyInt..tyInt32} and + src.kind in {tyInt64, tyUInt64}: + # conversions to Number are kept + r.res = "Number($1)" % [r.res] + else: + discard + elif dest.kind in {tyUInt..tyUInt64} and checkUnsignedConversions notin p.config.legacyFeatures: + if src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions: + r.res = "BigInt.asUintN($1, $2)" % [$(dest.size * 8), r.res] + else: + r.res = "BigInt.asUintN($1, BigInt($2))" % [$(dest.size * 8), r.res] + if not (dest.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions): + r.res = "Number($1)" % [r.res] + else: + if src.kind in {tyInt64, tyUInt64} and dest.kind notin {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions: + # we do a range check anyway, so it's ok if the number gets rounded + r.res = "Number($1)" % [r.res] + gen(p, n[1], a) + gen(p, n[2], b) useMagic(p, "chckRange") r.res = "chckRange($1, $2, $3)" % [r.res, a.res, b.res] r.kind = resExpr @@ -1862,11 +2675,11 @@ proc genRangeChck(p: PProc, n: PNode, r: var TCompRes, magic: string) = proc convStrToCStr(p: PProc, n: PNode, r: var TCompRes) = # we do an optimization here as this is likely to slow down # much of the code otherwise: - if n.sons[0].kind == nkCStringToString: - gen(p, n.sons[0].sons[0], r) + if n[0].kind == nkCStringToString: + gen(p, n[0][0], r) else: - gen(p, n.sons[0], r) - if r.res == nil: internalError(p.config, n.info, "convStrToCStr") + gen(p, n[0], r) + if r.res == "": internalError(p.config, n.info, "convStrToCStr") useMagic(p, "toJSStr") r.res = "toJSStr($1)" % [r.res] r.kind = resExpr @@ -1874,11 +2687,11 @@ proc convStrToCStr(p: PProc, n: PNode, r: var TCompRes) = proc convCStrToStr(p: PProc, n: PNode, r: var TCompRes) = # we do an optimization here as this is likely to slow down # much of the code otherwise: - if n.sons[0].kind == nkStringToCString: - gen(p, n.sons[0].sons[0], r) + if n[0].kind == nkStringToCString: + gen(p, n[0][0], r) else: - gen(p, n.sons[0], r) - if r.res == nil: internalError(p.config, n.info, "convCStrToStr") + gen(p, n[0], r) + if r.res == "": internalError(p.config, n.info, "convCStrToStr") useMagic(p, "cstrToNimstr") r.res = "cstrToNimstr($1)" % [r.res] r.kind = resExpr @@ -1886,15 +2699,15 @@ proc convCStrToStr(p: PProc, n: PNode, r: var TCompRes) = proc genReturnStmt(p: PProc, n: PNode) = if p.procDef == nil: internalError(p.config, n.info, "genReturnStmt") p.beforeRetNeeded = true - if n.sons[0].kind != nkEmpty: - genStmt(p, n.sons[0]) + if n[0].kind != nkEmpty: + genStmt(p, n[0]) else: genLineDir(p, n) lineF(p, "break BeforeRet;$n", []) proc frameCreate(p: PProc; procname, filename: Rope): Rope = - let frameFmt = - "var F={procname:$1,prev:framePtr,filename:$2,line:0};$n" + const frameFmt = + "var F = {procname: $1, prev: framePtr, filename: $2, line: 0};$n" result = p.indentLine(frameFmt % [procname, filename]) result.add p.indentLine(ropes.`%`("framePtr = F;$n", [])) @@ -1906,110 +2719,131 @@ proc genProcBody(p: PProc, prc: PSym): Rope = if hasFrameInfo(p): result = frameCreate(p, makeJSString(prc.owner.name.s & '.' & prc.name.s), - makeJSString(toFilename(p.config, prc.info))) + makeJSString(toFilenameOption(p.config, prc.info.fileIndex, foStacktrace))) else: - result = nil + result = "" if p.beforeRetNeeded: - result.add p.indentLine(~"BeforeRet: do {$n") + result.add p.indentLine("BeforeRet: {\n") result.add p.body - result.add p.indentLine(~"} while (false);$n") + result.add p.indentLine("};\n") else: - add(result, p.body) + result.add(p.body) if prc.typ.callConv == ccSysCall: result = ("try {$n$1} catch (e) {$n" & " alert(\"Unhandled exception:\\n\" + e.message + \"\\n\"$n}") % [result] if hasFrameInfo(p): - add(result, frameDestroy(p)) + result.add(frameDestroy(p)) -proc optionaLine(p: Rope): Rope = - if p == nil: - return nil +proc optionalLine(p: Rope): Rope = + if p == "": + return "" else: return p & "\L" proc genProc(oldProc: PProc, prc: PSym): Rope = + ## Generate a JS procedure ('function'). + result = "" var resultSym: PSym - a: TCompRes + a: TCompRes = default(TCompRes) #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 = nil - var resultAsgn: Rope = nil + var returnStmt: Rope = "" + var resultAsgn: Rope = "" var name = mangleName(p.module, prc) - let header = generateHeader(p, prc.typ) - if prc.typ.sons[0] != nil and sfPure notin prc.flags: - resultSym = prc.ast.sons[resultPos].sym + let header = generateHeader(p, prc) + if prc.typ.returnType != nil and sfPure notin prc.flags: + resultSym = prc.ast[resultPos].sym let mname = mangleName(p.module, resultSym) - let resVar = createVar(p, resultSym.typ, isIndirect(resultSym)) - resultAsgn = p.indentLine(("var $# = $#;$n") % [mname, resVar]) - if resultSym.typ.kind in {tyVar, tyPtr, tyLent, tyRef} and - mapType(p, resultSym.typ) == etyBaseIndex: + # otherwise uses "fat pointers" + let useRawPointer = not isIndirect(resultSym) and + resultSym.typ.kind in {tyVar, tyPtr, tyLent, tyRef, tyOwned} and + mapType(p, resultSym.typ) == etyBaseIndex + if useRawPointer: + resultAsgn = p.indentLine(("var $# = null;$n") % [mname]) resultAsgn.add p.indentLine("var $#_Idx = 0;$n" % [mname]) - gen(p, prc.ast.sons[resultPos], a) + else: + let resVar = createVar(p, resultSym.typ, isIndirect(resultSym)) + resultAsgn = p.indentLine(("var $# = $#;$n") % [mname, resVar]) + gen(p, prc.ast[resultPos], a) if mapType(p, resultSym.typ) == etyBaseIndex: returnStmt = "return [$#, $#];$n" % [a.address, a.res] else: returnStmt = "return $#;$n" % [a.res] - p.nested: genStmt(p, prc.getBody) + var transformedBody = transformBody(p.module.graph, p.module.idgen, prc, {}) + if sfInjectDestructors in prc.flags: + transformedBody = injectDestructorCalls(p.module.graph, p.module.idgen, prc, transformedBody) + + p.nested: genStmt(p, transformedBody) + + + if optLineDir in p.config.options: + result = lineDir(p.config, prc.info, toLinenumber(prc.info)) var def: Rope if not prc.constraint.isNil: - def = (prc.constraint.strVal & " {$n$#$#$#$#$#") % + def = runtimeFormat(prc.constraint.strVal & " {$n$#$#$#$#$#", [ returnType, name, header, - optionaLine(p.globals), - optionaLine(p.locals), - optionaLine(resultAsgn), - optionaLine(genProcBody(p, prc)), - optionaLine(p.indentLine(returnStmt))] + optionalLine(p.globals), + optionalLine(p.locals), + optionalLine(resultAsgn), + optionalLine(genProcBody(p, prc)), + optionalLine(p.indentLine(returnStmt))]) else: - result = ~"\L" + # if optLineDir in p.config.options: + # result.add("\L") - if optHotCodeReloading in p.config.options: + if p.config.hcrOn: # Here, we introduce thunks that create the equivalent of a jump table # for all global functions, because references to them may be stored # in JavaScript variables. The added indirection ensures that such # references will end up calling the reloaded code. var thunkName = name name = name & "IMLP" - result.add("function $#() { return $#.apply(this, arguments); }$n" % + result.add("\Lfunction $#() { return $#.apply(this, arguments); }$n" % [thunkName, name]) - def = "function $#($#) {$n$#$#$#$#$#" % + def = "\Lfunction $#($#) {$n$#$#$#$#$#" % [ name, header, - optionaLine(p.globals), - optionaLine(p.locals), - optionaLine(resultAsgn), - optionaLine(genProcBody(p, prc)), - optionaLine(p.indentLine(returnStmt))] + optionalLine(p.globals), + optionalLine(p.locals), + optionalLine(resultAsgn), + optionalLine(genProcBody(p, prc)), + optionalLine(p.indentLine(returnStmt))] dec p.extraIndent result.add p.indentLine(def) - result.add p.indentLine(~"}$n") + result.add p.indentLine("}\n") #if gVerbosity >= 3: # echo "END generated code for: " & prc.name.s proc genStmt(p: PProc, n: PNode) = - var r: TCompRes + var r: TCompRes = default(TCompRes) gen(p, n, r) - if r.res != nil: lineF(p, "$#;$n", [r.res]) + if r.res != "": lineF(p, "$#;$n", [r.res]) proc genPragma(p: PProc, n: PNode) = - for it in n.sons: + for i in 0..<n.len: + let it = n[i] case whichPragma(it) - of wEmit: genAsmOrEmitStmt(p, it.sons[1]) + of wEmit: genAsmOrEmitStmt(p, it[1]) + of wPush: + processPushBackendOption(p.config, p.optionsStack, p.options, n, i+1) + of wPop: + processPopBackendOption(p.config, p.optionsStack, p.options) else: discard proc genCast(p: PProc, n: PNode, r: var TCompRes) = var dest = skipTypes(n.typ, abstractVarRange) - var src = skipTypes(n.sons[1].typ, abstractVarRange) - gen(p, n.sons[1], r) + var src = skipTypes(n[1].typ, abstractVarRange) + gen(p, n[1], r) if dest.kind == src.kind: # no-op conversion return @@ -2018,39 +2852,72 @@ proc genCast(p: PProc, n: PNode, r: var TCompRes) = let fromInt = (src.kind in tyInt..tyInt32) let fromUint = (src.kind in tyUInt..tyUInt32) - if toUint and (fromInt or fromUint): - let trimmer = unsignedTrimmer(dest.size) - r.res = "($1 $2)" % [r.res, trimmer] + if toUint: + if fromInt or fromUint: + r.res = "Number(BigInt.asUintN($1, BigInt($2)))" % [$(dest.size * 8), r.res] + elif src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions: + r.res = "Number(BigInt.asUintN($1, $2))" % [$(dest.size * 8), r.res] elif toInt: - if fromInt: - let trimmer = unsignedTrimmer(dest.size) - r.res = "($1 $2)" % [r.res, trimmer] - elif fromUint: - if src.size == 4 and dest.size == 4: - # XXX prevent multi evaluations - r.res = "($1|0)" % [r.res] - else: - let trimmer = unsignedTrimmer(dest.size) - let minuend = case dest.size - of 1: "0xfe" - of 2: "0xfffe" - of 4: "0xfffffffe" - else: "" - r.res = "($1 - ($2 $3))" % [rope minuend, r.res, trimmer] + if fromInt or fromUint: + r.res = "Number(BigInt.asIntN($1, BigInt($2)))" % [$(dest.size * 8), r.res] + elif src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions: + r.res = "Number(BigInt.asIntN($1, $2))" % [$(dest.size * 8), r.res] + elif dest.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + if fromInt or fromUint or src.kind in {tyBool, tyChar, tyEnum}: + r.res = "BigInt($1)" % [r.res] + elif src.kind in {tyFloat..tyFloat64}: + r.res = "BigInt(Math.trunc($1))" % [r.res] + elif src.kind == tyUInt64: + r.res = "BigInt.asIntN(64, $1)" % [r.res] + elif dest.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions: + if fromUint or src.kind in {tyBool, tyChar, tyEnum}: + r.res = "BigInt($1)" % [r.res] + elif fromInt: # could be negative + r.res = "BigInt.asUintN(64, BigInt($1))" % [r.res] + elif src.kind in {tyFloat..tyFloat64}: + r.res = "BigInt.asUintN(64, BigInt(Math.trunc($1)))" % [r.res] + elif src.kind == tyInt64: + r.res = "BigInt.asUintN(64, $1)" % [r.res] + elif dest.kind in tyFloat..tyFloat64: + if src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions: + r.res = "Number($1)" % [r.res] + elif (src.kind == tyPtr and mapType(p, src) == etyObject) and dest.kind == tyPointer: + r.address = r.res + r.res = "null" + r.typ = etyBaseIndex + elif (dest.kind == tyPtr and mapType(p, dest) == etyObject) and src.kind == tyPointer: + r.res = r.address + r.typ = etyObject proc gen(p: PProc, n: PNode, r: var TCompRes) = r.typ = etyNone if r.kind != resCallee: r.kind = resNone - #r.address = nil - r.res = nil + #r.address = "" + r.res = "" + case n.kind of nkSym: genSym(p, n, r) of nkCharLit..nkUInt64Lit: - if n.typ.kind == tyBool: + case n.typ.skipTypes(abstractVarRange).kind + of tyBool: r.res = if n.intVal == 0: rope"false" else: rope"true" + of tyUInt64: + r.res = rope($cast[BiggestUInt](n.intVal)) + if optJsBigInt64 in p.config.globalOptions: + r.res.add('n') + of tyInt64: + let wrap = n.intVal < 0 # wrap negative integers with parens + if wrap: r.res.add '(' + r.res.addInt n.intVal + if optJsBigInt64 in p.config.globalOptions: + r.res.add('n') + if wrap: r.res.add ')' else: - r.res = rope(n.intVal) + let wrap = n.intVal < 0 # wrap negative integers with parens + if wrap: r.res.add '(' + r.res.addInt n.intVal + if wrap: r.res.add ')' r.kind = resExpr of nkNilLit: if isEmptyType(n.typ): @@ -2065,16 +2932,22 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) = r.kind = resExpr of nkStrLit..nkTripleStrLit: if skipTypes(n.typ, abstractVarRange).kind == tyString: - useMagic(p, "makeNimstrLit") - r.res = "makeNimstrLit($1)" % [makeJSString(n.strVal)] + if n.strVal.len <= 64: + r.res = makeJsNimStrLit(n.strVal) + else: + useMagic(p, "makeNimstrLit") + r.res = "makeNimstrLit($1)" % [makeJSString(n.strVal)] else: r.res = makeJSString(n.strVal, false) r.kind = resExpr of nkFloatLit..nkFloat64Lit: let f = n.floatVal case classify(f) - of fcNaN: - r.res = rope"NaN" + of fcNan: + if signbit(f): + r.res = rope"-NaN" + else: + r.res = rope"NaN" of fcNegZero: r.res = rope"-0.0" of fcZero: @@ -2083,30 +2956,53 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) = r.res = rope"Infinity" of fcNegInf: r.res = rope"-Infinity" - else: r.res = rope(f.toStrMaxPrecision) + else: + if n.typ.skipTypes(abstractVarRange).kind == tyFloat32: + r.res.addFloatRoundtrip(f.float32) + else: + r.res.addFloatRoundtrip(f) r.kind = resExpr of nkCallKinds: - if isEmptyType(n.typ): genLineDir(p, n) - if (n.sons[0].kind == nkSym) and (n.sons[0].sym.magic != mNone): + if isEmptyType(n.typ): + genLineDir(p, n) + if (n[0].kind == nkSym) and (n[0].sym.magic != mNone): genMagic(p, n, r) - elif n.sons[0].kind == nkSym and sfInfixCall in n.sons[0].sym.flags and + elif n[0].kind == nkSym and sfInfixCall in n[0].sym.flags and n.len >= 1: genInfixCall(p, n, r) else: genCall(p, n, r) - of nkClosure: gen(p, n[0], r) + of nkClosure: + 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) of nkObjConstr: genObjConstr(p, n, r) of nkHiddenStdConv, nkHiddenSubConv, nkConv: genConv(p, n, r) of nkAddr, nkHiddenAddr: - genAddr(p, n, r) - of nkDerefExpr, nkHiddenDeref: genDeref(p, n, r) + if n.typ.kind in {tyLent}: + gen(p, n[0], r) + else: + genAddr(p, n, r) + of nkDerefExpr, nkHiddenDeref: + if n.typ.kind in {tyLent}: + gen(p, n[0], r) + else: + genDeref(p, n, r) of nkBracketExpr: genArrayAccess(p, n, r) of nkDotExpr: genFieldAccess(p, n, r) - of nkCheckedFieldExpr: genCheckedFieldAccess(p, n, r) - of nkObjDownConv: gen(p, n.sons[0], r) + of nkCheckedFieldExpr: genCheckedFieldOp(p, n, nil, r) + of nkObjDownConv: gen(p, n[0], r) of nkObjUpConv: upConv(p, n, r) of nkCast: genCast(p, n, r) of nkChckRangeF: genRangeChck(p, n, r, "chckRangeF") @@ -2116,26 +3012,26 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) = of nkCStringToString: convCStrToStr(p, n, r) of nkEmpty: discard of nkLambdaKinds: - let s = n.sons[namePos].sym + let s = n[namePos].sym discard mangleName(p.module, s) - r.res = s.loc.r - if lfNoDecl in s.loc.flags or s.magic != mNone: discard + r.res = s.loc.snippet + if lfNoDecl in s.loc.flags or s.magic notin generatedMagics: discard elif not p.g.generatedSyms.containsOrIncl(s.id): - add(p.locals, genProc(p, s)) + p.locals.add(genProc(p, s)) of nkType: r.res = genTypeInfo(p, n.typ) of nkStmtList, nkStmtListExpr: # this shows the distinction is nice for backends and should be kept # in the frontend let isExpr = not isEmptyType(n.typ) - for i in countup(0, sonsLen(n) - 1 - isExpr.ord): - genStmt(p, n.sons[i]) + for i in 0..<n.len - isExpr.ord: + genStmt(p, n[i]) if isExpr: gen(p, lastSon(n), r) of nkBlockStmt, nkBlockExpr: genBlock(p, n, r) of nkIfStmt, nkIfExpr: genIf(p, n, r) of nkWhen: # This is "when nimvm" node - gen(p, n.sons[1].sons[0], r) + gen(p, n[1][0], r) of nkWhileStmt: genWhileStmt(p, n) of nkVarSection, nkLetSection: genVarStmt(p, n) of nkConstSection: discard @@ -2145,140 +3041,172 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) = of nkReturnStmt: genReturnStmt(p, n) of nkBreakStmt: genBreakStmt(p, n) of nkAsgn: genAsgn(p, n) - of nkFastAsgn: genFastAsgn(p, n) + of nkFastAsgn, nkSinkAsgn: genFastAsgn(p, n) of nkDiscardStmt: - if n.sons[0].kind != nkEmpty: + if n[0].kind != nkEmpty: genLineDir(p, n) - gen(p, n.sons[0], r) - of nkAsmStmt: genAsmOrEmitStmt(p, n) - of nkTryStmt: genTry(p, n, r) + gen(p, n[0], r) + r.res = "(" & r.res & ")" + of nkAsmStmt: + warningDeprecated(p.config, n.info, "'asm' for the JS target is deprecated, use the 'emit' pragma") + genAsmOrEmitStmt(p, n, true) + of nkTryStmt, nkHiddenTryStmt: genTry(p, n, r) of nkRaiseStmt: genRaiseStmt(p, n) - of nkTypeSection, nkCommentStmt, nkIteratorDef, nkIncludeStmt, + of nkTypeSection, nkCommentStmt, nkIncludeStmt, nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt, - nkFromStmt, nkTemplateDef, nkMacroDef, nkStaticStmt: discard + nkFromStmt, nkTemplateDef, nkMacroDef, nkIteratorDef, nkStaticStmt, + nkMixinStmt, nkBindStmt: discard of nkPragma: genPragma(p, n) of nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef: - var s = n.sons[namePos].sym + var s = n[namePos].sym if {sfExportc, sfCompilerProc} * s.flags == {sfExportc}: - genSym(p, n.sons[namePos], r) - r.res = nil + genSym(p, n[namePos], r) + r.res = "" of nkGotoState, nkState: - internalError(p.config, n.info, "first class iterators not implemented") + globalError(p.config, n.info, "not implemented") + of nkBreakState: + genBreakState(p, n[0], r) of nkPragmaBlock: gen(p, n.lastSon, r) of nkComesFrom: discard "XXX to implement for better stack traces" else: internalError(p.config, n.info, "gen: unknown node type: " & $n.kind) proc newModule(g: ModuleGraph; module: PSym): BModule = - new(result) - result.module = module - result.sigConflicts = initCountTable[SigHash]() + ## Create a new JS backend module node. if g.backend == nil: g.backend = newGlobals() - result.graph = g - result.config = g.config + result = BModule(module: module, sigConflicts: initCountTable[SigHash](), + graph: g, config: g.config + ) + if sfSystemModule in module.flags: + PGlobals(g.backend).inSystem = true proc genHeader(): Rope = - result = ( - "/* Generated by the Nim Compiler v$1 */$n" & - "/* (c) " & copyrightYear & " Andreas Rumpf */$n$n" & - "var framePtr = null;$n" & - "var excHandler = 0;$n" & - "var lastJSError = null;$n" & - "if (typeof Int8Array === 'undefined') Int8Array = Array;$n" & - "if (typeof Int16Array === 'undefined') Int16Array = Array;$n" & - "if (typeof Int32Array === 'undefined') Int32Array = Array;$n" & - "if (typeof Uint8Array === 'undefined') Uint8Array = Array;$n" & - "if (typeof Uint16Array === 'undefined') Uint16Array = Array;$n" & - "if (typeof Uint32Array === 'undefined') Uint32Array = Array;$n" & - "if (typeof Float32Array === 'undefined') Float32Array = Array;$n" & - "if (typeof Float64Array === 'undefined') Float64Array = Array;$n") % - [rope(VersionAsString)] + ## Generate the JS header. + result = rope("""/* Generated by the Nim Compiler v$1 */ + var framePtr = null; + var excHandler = 0; + var lastJSError = null; + """.unindent.format(VersionAsString)) + +proc addHcrInitGuards(p: PProc, n: PNode, + moduleLoadedVar: Rope, inInitGuard: var bool) = + if n.kind == nkStmtList: + for child in n: + addHcrInitGuards(p, child, moduleLoadedVar, inInitGuard) + else: + let stmtShouldExecute = n.kind in { + nkProcDef, nkFuncDef, nkMethodDef,nkConverterDef, + nkVarSection, nkLetSection} or nfExecuteOnReload in n.flags + + if inInitGuard: + if stmtShouldExecute: + dec p.extraIndent + line(p, "}\L") + inInitGuard = false + else: + if not stmtShouldExecute: + lineF(p, "if ($1 == undefined) {$n", [moduleLoadedVar]) + inc p.extraIndent + inInitGuard = true + + genStmt(p, n) proc genModule(p: PProc, n: PNode) = + ## Generate the JS module code. + ## Called for each top level node in a Nim module. if optStackTrace in p.options: - add(p.body, frameCreate(p, + p.body.add(frameCreate(p, makeJSString("module " & p.module.module.name.s), - makeJSString(toFilename(p.config, p.module.module.info)))) - genStmt(p, n) + makeJSString(toFilenameOption(p.config, p.module.module.info.fileIndex, foStacktrace)))) + var transformedN = transformStmt(p.module.graph, p.module.idgen, p.module.module, n) + if sfInjectDestructors in p.module.module.flags: + transformedN = injectDestructorCalls(p.module.graph, p.module.idgen, p.module.module, transformedN) + if p.config.hcrOn and n.kind == nkStmtList: + let moduleSym = p.module.module + var moduleLoadedVar = rope(moduleSym.name.s) & "_loaded" & + idOrSig(moduleSym, moduleSym.name.s, p.module.sigConflicts, p.config) + lineF(p, "var $1;$n", [moduleLoadedVar]) + var inGuardedBlock = false + + addHcrInitGuards(p, transformedN, moduleLoadedVar, inGuardedBlock) + + if inGuardedBlock: + dec p.extraIndent + line(p, "}\L") + + lineF(p, "$1 = true;$n", [moduleLoadedVar]) + else: + genStmt(p, transformedN) + if optStackTrace in p.options: - add(p.body, frameDestroy(p)) + p.body.add(frameDestroy(p)) -proc myProcess(b: PPassContext, n: PNode): PNode = +proc processJSCodeGen*(b: PPassContext, n: PNode): PNode = + ## Generate JS code for a node. result = n let m = BModule(b) - if passes.skipCodegen(m.config, n): return n + if pipelineutils.skipCodegen(m.config, n): return n if m.module == nil: internalError(m.config, n.info, "myProcess") let globals = PGlobals(m.graph.backend) - var p = newProc(globals, m, nil, m.module.options) + var p = newInitProc(globals, m) + m.initProc = p p.unique = globals.unique genModule(p, n) - add(p.g.code, p.locals) - add(p.g.code, p.body) + p.g.code.add(p.locals) + p.g.code.add(p.body) proc wholeCode(graph: ModuleGraph; m: BModule): Rope = + ## Combine source code from all nodes. let globals = PGlobals(graph.backend) for prc in globals.forwarded: if not globals.generatedSyms.containsOrIncl(prc.id): - var p = newProc(globals, m, nil, m.module.options) + var p = newInitProc(globals, m) attachProc(p, prc) - var disp = generateMethodDispatchers(graph) - for i in 0..sonsLen(disp)-1: - let prc = disp.sons[i].sym + generateIfMethodDispatchers(graph, m.idgen) + for prc in getDispatchers(graph): if not globals.generatedSyms.containsOrIncl(prc.id): - var p = newProc(globals, m, nil, m.module.options) + var p = newInitProc(globals, m) attachProc(p, prc) result = globals.typeInfo & globals.constants & globals.code -proc getClassName(t: PType): Rope = - var s = t.sym - if s.isNil or sfAnon in s.flags: - s = skipTypes(t, abstractPtrs).sym - if s.isNil or sfAnon in s.flags: - doAssert(false, "cannot retrieve class name") - if s.loc.r != nil: result = s.loc.r - else: result = rope(s.name.s) - -proc genClass(conf: ConfigRef; obj: PType; content: Rope; ext: string) = - let cls = getClassName(obj) - let t = skipTypes(obj, abstractPtrs) - let extends = if t.kind == tyObject and t.sons[0] != nil: - " extends " & getClassName(t.sons[0]) - else: nil - let result = ("<?php$n" & - "/* Generated by the Nim Compiler v$# */$n" & - "/* (c) " & copyrightYear & " Andreas Rumpf */$n$n" & - "require_once \"nimsystem.php\";$n" & - "class $#$# {$n$#$n}$n") % - [rope(VersionAsString), cls, extends, content] - - let outfile = changeFileExt(completeCFilePath(conf, $cls), ext) - discard writeRopeIfNotEqual(result, outfile) - -proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = - result = myProcess(b, n) +proc finalJSCodeGen*(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = + ## Finalize JS code generation of a Nim module. + ## Param `n` may contain nodes returned from the last module close call. var m = BModule(b) - if passes.skipCodegen(m.config, n): return n if sfMainModule in m.module.flags: - let globals = PGlobals(graph.backend) - let ext = "js" - let f = if globals.classes.len == 0: toFilename(m.config, FileIndex m.module.position) - else: "nimsystem" - let code = wholeCode(graph, m) - let outfile = - if m.config.outFile.len > 0: - if m.config.outFile.isAbsolute: m.config.outFile - else: getCurrentDir() / m.config.outFile - else: - changeFileExt(completeCFilePath(m.config, f), ext) - discard writeRopeIfNotEqual(genHeader() & code, outfile) - for obj, content in items(globals.classes): - genClass(m.config, obj, content, ext) - -proc myOpen(graph: ModuleGraph; s: PSym): PPassContext = + # Add global destructors to the module. + # This must come before the last call to `myProcess`. + for i in countdown(high(graph.globalDestructors), 0): + n.add graph.globalDestructors[i] + # Process any nodes left over from the last call to `myClose`. + result = processJSCodeGen(b, n) + # Some codegen is different (such as no stacktraces; see `initProcOptions`) + # when `std/system` is being processed. + if sfSystemModule in m.module.flags: + PGlobals(graph.backend).inSystem = false + # Check if codegen should continue before any files are generated. + # It may bail early is if too many errors have been raised. + if pipelineutils.skipCodegen(m.config, n): return n + # Nim modules are compiled into a single JS file. + # If this is the main module, then this is the final call to `myClose`. + if sfMainModule in m.module.flags: + var code = genHeader() & wholeCode(graph, m) + let outFile = m.config.prepareToWriteOutput() + # Generate an optional source map. + if optSourcemap in m.config.globalOptions: + var map: SourceMap + map = genSourceMap($code, outFile.string) + code &= "\n//# sourceMappingURL=$#.map" % [outFile.string] + writeFile(outFile.string & ".map", $(%map)) + # Check if the generated JS code matches the output file, or else + # write it to the file. + if not equalsFile(code, outFile): + if not writeRope(code, outFile): + rawMessage(m.config, errCannotOpenFile, outFile.string) + +proc setupJSgen*(graph: ModuleGraph; s: PSym; idgen: IdGenerator): PPassContext = result = newModule(graph, s) - -const JSgenPass* = makePass(myOpen, myProcess, myClose) - + result.idgen = idgen |