#
#
# The Nim Compiler
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# This is the JavaScript code generator.
discard """
The JS code generator contains only 2 tricks:
Trick 1
-------
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.
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``
implements the required case distinction.
"""
import
ast, strutils, trees, magicsys, options,
nversion, msgs, idents, types, tables,
ropes, math, passes, ccgutils, wordrecg, renderer,
intsets, cgmeth, lowerings, sighashes, modulegraphs, lineinfos, rodutils,
transf, injectdestructors, sourcemap, json, sets
from modulegraphs import ModuleGraph, PPassContext
type
TJSGen = object of PPassContext
module: PSym
graph: ModuleGraph
config: ConfigRef
sigConflicts: CountTable[SigHash]
BModule = ref TJSGen
TJSTypeKind = enum # necessary JS "types"
etyNone, # no type
etyNull, # null type
etyProc, # proc type
etyBool, # bool type
etySeq, # Nim seq or string type
etyInt, # JavaScript's int
etyFloat, # JavaScript's float
etyString, # JavaScript's string
etyObject, # JavaScript's reference to an object
etyBaseIndex # base + index needed
TResKind = enum
resNone, # not set
resExpr, # is some complex expression
resVal, # is a temporary/value/l-value
resCallee # expression is callee
TCompRes = object
kind: TResKind
typ: TJSTypeKind
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
# has been used (i.e. the label should be emitted)
isLoop: bool # whether it's a 'block' or 'while'
PGlobals = ref object of RootObj
typeInfo, constants, code: Rope
forwarded: seq[PSym]
generatedSyms: IntSet
typeInfoGenerated: IntSet
unique: int # for temp identifier generation
PProc = ref TProc
TProc = object
procDef: PNode
prc: PSym
globals, locals, body: Rope
options: TOptions
module: BModule
g: PGlobals
generatedParamCopies: IntSet
beforeRetNeeded: bool
unique: int # for temp identifier generation
blocks: seq[TBlock]
extraIndent: int
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 0..
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':
x.add c
else:
x.add("HEX" & toHex(ord(c), 2))
inc i
result = rope(x)
# 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:
result.add(idOrSig(s, m.module.name.s, m.sigConflicts))
else:
result.add("_")
result.add(rope(s.id))
s.loc.r = result
proc escapeJSString(s: string): string =
result = newStringOfCap(s.len + s.len shr 2)
result.add("\"")
for c in items(s):
case c
of '\l': result.add("\\n")
of '\r': result.add("\\r")
of '\t': result.add("\\t")
of '\b': result.add("\\b")
of '\a': result.add("\\a")
of '\e': result.add("\\e")
of '\v': result.add("\\v")
of '\\': result.add("\\\\")
of '\"': result.add("\\\"")
else: result.add(c)
result.add("\"")
proc makeJSString(s: string, escapeNonAscii = true): Rope =
if escapeNonAscii:
result = strutils.escape(s).rope
else:
result = escapeJSString(s).rope
include jstypes
proc gen(p: PProc, n: PNode, r: var TCompRes)
proc genStmt(p: PProc, n: PNode)
proc genProc(oldProc: PProc, prc: PSym): Rope
proc genConstant(p: PProc, c: PSym)
proc useMagic(p: PProc, name: string) =
if name.len == 0: return
var s = magicsys.getCompilerProc(p.module.graph, name)
if s != nil:
internalAssert p.config, s.kind in {skProc, skFunc, skMethod, skConverter}
if not p.g.generatedSyms.containsOrIncl(s.id):
let code = genProc(p, s)
p.g.constants.add(code)
else:
if p.prc != nil:
globalError(p.config, p.prc.info, "system module needs: " & name)
else:
rawMessage(p.config, errGenerated, "system module needs: " & name)
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}):
for c in n:
if not p.isSimpleExpr(c): return false
result = true
elif n.isAtom:
result = true
proc getTemp(p: PProc, defineInLocals: bool = true): Rope =
inc(p.unique)
result = "Tmp$1" % [rope(p.unique)]
if defineInLocals:
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
if p.isSimpleExpr(a) and p.isSimpleExpr(b):
gen(p, a, x)
gen(p, b, y)
r.kind = resExpr
r.res = "($1 && $2)" % [x.rdLoc, y.rdLoc]
else:
r.res = p.getTemp
r.kind = resVal
# while a and b:
# -->
# while true:
# aa
# if not a: tmp = false
# else:
# bb
# tmp = b
# tmp
gen(p, a, x)
lineF(p, "if (!$1) $2 = false; else {", [x.rdLoc, r.rdLoc])
p.nested:
gen(p, b, y)
lineF(p, "$2 = $1;", [y.rdLoc, r.rdLoc])
line(p, "}")
proc genOr(p: PProc, a, b: PNode, r: var TCompRes) =
assert r.kind == resNone
var x, y: TCompRes
if p.isSimpleExpr(a) and p.isSimpleExpr(b):
gen(p, a, x)
gen(p, b, y)
r.kind = resExpr
r.res = "($1 || $2)" % [x.rdLoc, y.rdLoc]
else:
r.res = p.getTemp
r.kind = resVal
gen(p, a, x)
lineF(p, "if ($1) $2 = true; else {", [x.rdLoc, r.rdLoc])
p.nested:
gen(p, b, y)
lineF(p, "$2 = $1;", [y.rdLoc, r.rdLoc])
line(p, "}")
type
TMagicFrmt = array[0..1, string]
TMagicOps = array[mAddI..mStrToStr, TMagicFrmt]
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"],
mIntToStr: ["cstrToNimstr", "cstrToNimstr"],
mInt64ToStr: ["cstrToNimstr", "cstrToNimstr"],
mFloatToStr: ["cstrToNimstr", "cstrToNimstr"],
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
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 != nil 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)
template binaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) =
# $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
useMagic(p, magic)
gen(p, n[1], x)
gen(p, n[2], y)
var
a, tmp = x.rdLoc
b, tmp2 = y.rdLoc
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 =
case size
of 1: rope"& 0xff"
of 2: rope"& 0xffff"
of 4: rope">>> 0"
else: rope""
template unsignedTrimmer(size: BiggestInt): Rope =
size.unsignedTrimmerJS
proc binaryUintExpr(p: PProc, n: PNode, r: var TCompRes, op: string,
reassign = false) =
var x, y: TCompRes
gen(p, n[1], x)
gen(p, n[2], y)
let trimmer = unsignedTrimmer(n[1].typ.skipTypes(abstractRange).size)
if reassign:
let (a, tmp) = maybeMakeTemp(p, n[1], x)
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]
r.kind = resExpr
template ternaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) =
var x, y, z: TCompRes
useMagic(p, magic)
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
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[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 arithAux(p: PProc, n: PNode, r: var TCompRes, op: TMagic) =
var
x, y: TCompRes
xLoc,yLoc: Rope
let i = ord(optOverflowCheck notin p.options)
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[1], r)
xLoc = r.rdLoc
template applyFormat(frmtA, frmtB: string) =
if i == 0:
r.res = frmtA % [xLoc, yLoc]
else:
r.res = frmtB % [xLoc, yLoc]
case op:
of mAddI: applyFormat("addInt($1, $2)", "($1 + $2)")
of mSubI: applyFormat("subInt($1, $2)", "($1 - $2)")
of mMulI: applyFormat("mulInt($1, $2)", "($1 * $2)")
of mDivI: applyFormat("divInt($1, $2)", "Math.trunc($1 / $2)")
of mModI: applyFormat("modInt($1, $2)", "Math.trunc($1 % $2)")
of mSucc: applyFormat("addInt($1, $2)", "($1 + $2)")
of mPred: applyFormat("subInt($1, $2)", "($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: applyFormat("", "")
of mShlI: applyFormat("($1 << $2)", "($1 << $2)")
of mAshrI: applyFormat("($1 >> $2)", "($1 >> $2)")
of mBitandI: applyFormat("($1 & $2)", "($1 & $2)")
of mBitorI: applyFormat("($1 | $2)", "($1 | $2)")
of mBitxorI: applyFormat("($1 ^ $2)", "($1 ^ $2)")
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: applyFormat("absInt($1)", "Math.abs($1)")
of mNot: applyFormat("!($1)", "!($1)")
of mUnaryPlusI: applyFormat("+($1)", "+($1)")
of mBitnotI: applyFormat("~($1)", "~($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 mIntToStr: applyFormat("cstrToNimstr(($1)+\"\")", "cstrToNimstr(($1)+\"\")")
of mInt64ToStr: applyFormat("cstrToNimstr(($1)+\"\")", "cstrToNimstr(($1)+\"\")")
of mFloatToStr: applyFormat("cstrToNimstr(($1)+\"\")", "cstrToNimstr(($1)+\"\")")
of mCStrToStr: applyFormat("cstrToNimstr($1)", "cstrToNimstr($1)")
of mStrToStr, mUnown: 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 mDivI:
arithAux(p, n, r, op)
of mModI:
arithAux(p, n, r, op)
of mShrI:
var x, y: TCompRes
gen(p, n[1], x)
gen(p, n[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:
arithAux(p, n, r, op)
of mEqRef:
if mapType(n[1].typ) != etyBaseIndex:
arithAux(p, n, r, op)
else:
var x, y: TCompRes
gen(p, n[1], x)
gen(p, n[2], y)
r.res = "($# == $# && $# == $#)" % [x.address, y.address, x.res, y.res]
else:
arithAux(p, n, r, op)
r.kind = resExpr
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 =
ropes.`%`("// line $2 \"$1\"$n",
[rope(toFullPath(config, info)), rope(line)])
proc genLineDir(p: PProc, n: PNode) =
let line = toLinenumber(n.info)
if line < 0:
return
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)])
proc genWhileStmt(p: PProc, n: PNode) =
var cond: TCompRes
internalAssert p.config, isEmptyType(n.typ)
genLineDir(p, n)
inc(p.unique)
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[0], cond)
lineF(p, "if (!$1) break L$2;$n",
[cond.res, labl])
p.nested: genStmt(p, n[1])
lineF(p, "}$n", [labl])
setLen(p.blocks, p.blocks.len - 1)
proc moveInto(p: PProc, src: var TCompRes, dest: TCompRes) =
if src.kind != resNone:
if dest.kind != resNone:
lineF(p, "$1 = $2;$n", [dest.rdLoc, src.rdLoc])
else:
lineF(p, "$1;$n", [src.rdLoc])
src.kind = resNone
src.res = nil
proc genTry(p: PProc, n: PNode, r: var TCompRes) =
# code to generate:
#
# ++excHandler;
# var tmpFramePtr = framePtr;
# try {
# stmts;
# --excHandler;
# } catch (EXC) {
# var prevJSError = lastJSError; lastJSError = EXC;
# framePtr = tmpFramePtr;
# --excHandler;
# if (e.typ && e.typ == NTI433 || e.typ == NTI2321) {
# stmts;
# } else if (e.typ && e.typ == NTI32342) {
# stmts;
# } else {
# stmts;
# }
# lastJSError = prevJSError;
# } finally {
# framePtr = tmpFramePtr;
# stmts;
# }
genLineDir(p, n)
if not isEmptyType(n.typ):
r.kind = resVal
r.res = getTemp(p)
inc(p.unique)
var i = 1
var catchBranchesExist = n.len > 1 and n[i].kind == nkExceptBranch
if catchBranchesExist:
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[0], a)
moveInto(p, a, r)
var generalCatchBranchExists = false
if catchBranchesExist:
p.body.addf("--excHandler;$n} catch (EXC) {$n var prevJSError = lastJSError;$n" &
" lastJSError = EXC;$n --excHandler;$n", [])
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[i][0], a)
moveInto(p, a, r)
if i > 1: lineF(p, "}$n", [])
else:
var orExpr: Rope = nil
var excAlias: PNode = nil
useMagic(p, "isObj")
for j in 0.. 1: line(p, "else ")
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.r = mangleName(p.module, excAlias.sym)
lineF(p, "var $1 = lastJSError;$n", excAlias.sym.loc.r)
gen(p, n[i][^1], a)
moveInto(p, a, r)
lineF(p, "}$n", [])
inc(i)
if catchBranchesExist:
if not generalCatchBranchExists:
useMagic(p, "reraiseException")
line(p, "else {\L")
line(p, "\treraiseException();\L")
line(p, "}\L")
lineF(p, "lastJSError = prevJSError;$n")
line(p, "} finally {\L")
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) =
if n[0].kind != nkEmpty:
var a: 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
genLineDir(p, n)
gen(p, n[0], cond)
let stringSwitch = skipTypes(n[0].typ, abstractVar).kind == tyString
if stringSwitch:
useMagic(p, "toJSStr")
lineF(p, "switch (toJSStr($1)) {$n", [cond.rdLoc])
else:
lineF(p, "switch ($1) {$n", [cond.rdLoc])
if not isEmptyType(n.typ):
r.kind = resVal
r.res = getTemp(p)
for i in 1..= 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)])
proc genAsmOrEmitStmt(p: PProc, n: PNode) =
genLineDir(p, n)
p.body.add p.indentLine(nil)
for i in 0.. 0:
lineF(p, "else {$n", [])
inc(toClose)
p.nested: gen(p, it[0], cond)
lineF(p, "if ($1) {$n", [cond.rdLoc])
gen(p, it[1], stmt)
else:
# else part:
lineF(p, "else {$n", [])
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 1..= pos:
# dest[i].shallowCopy(dest[j])
# 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[0], n[1], noCopyNeeded=noCopy)
proc genSwap(p: PProc, n: PNode) =
var a, b: TCompRes
gen(p, n[1], a)
gen(p, n[2], b)
var tmp = p.getTemp(false)
if mapType(p, skipTypes(n[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])
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")
proc genFieldAddr(p: PProc, n: PNode, r: var TCompRes) =
var a: TCompRes
r.typ = etyBaseIndex
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[1].kind != nkSym: internalError(p.config, b[1].info, "genFieldAddr")
var f = b[1].sym
if f.loc.r == nil: f.loc.r = mangleName(p.module, f)
r.res = makeJSString($f.loc.r)
internalAssert p.config, a.typ != etyBaseIndex
r.address = a.res
r.kind = resExpr
proc genFieldAccess(p: PProc, n: PNode, r: var TCompRes) =
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[1]).rope]
mkTemp(0)
else:
if n[1].kind != nkSym: internalError(p.config, n[1].info, "genFieldAccess")
var f = n[1].sym
if f.loc.r == nil: f.loc.r = mangleName(p.module, f)
r.res = "$1.$2" % [r.res, f.loc.r]
mkTemp(1)
r.kind = resExpr
proc genAddr(p: PProc, n: PNode, r: var TCompRes)
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.r == nil: field.loc.r = mangleName(p.module, field)
# Discriminant symbol
let disc = checkExpr[2].sym
internalAssert p.config, disc.kind == skField
if disc.loc.r == nil: disc.loc.r = mangleName(p.module, disc)
var setx: TCompRes
gen(p, checkExpr[1], setx)
var obj: 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, "raiseFieldError")
useMagic(p, "makeNimstrLit")
let msg = genFieldError(field, disc)
lineF(p, "if ($1[$2.$3]$4undefined) { raiseFieldError(makeNimstrLit($5)); }$n",
setx.res, tmp, disc.loc.r, if negCheck: ~"!==" else: ~"===",
makeJSString(msg))
if addrTyp != nil and mapType(p, addrTyp) == etyBaseIndex:
r.typ = etyBaseIndex
r.res = makeJSString($field.loc.r)
r.address = tmp
else:
r.typ = etyNone
r.res = "$1.$2" % [tmp, field.loc.r]
r.kind = resExpr
proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) =
var
a, b: TCompRes
first: Int128
r.typ = etyBaseIndex
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[0])
if optBoundsCheck in p.options:
useMagic(p, "chckIndx")
r.res = "chckIndx($1, $2, ($3 != null ? $3.length : 0)+$2-1)-$2" % [b.res, rope(first), tmp]
elif first != 0:
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[0].typ, abstractVarRange)
if ty.kind in {tyRef, tyPtr, tyLent, tyOwned}: ty = skipTypes(ty.lastSon, abstractVarRange)
case ty.kind
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 = mapType(n.typ)
if r.res == nil: 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.kind = resExpr
template isIndirect(x: PSym): bool =
let v = x
({sfAddrTaken, sfGlobal} * v.flags != {} and
#(mapType(v.typ) != etyObject) and
{sfImportc, sfExportc} * v.flags == {} and
v.kind notin {skProc, skFunc, skConverter, skMethod, skIterator,
skConst, skTemp, skLet})
proc genAddr(p: PProc, n: PNode, r: var TCompRes) =
case n[0].kind
of nkSym:
let s = n[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")
else:
# 'var openArray' for instance produces an 'addr' but this is harmless:
gen(p, n[0], r)
#internalError(p.config, n.info, "genAddr: 4 " & renderTree(n))
else: internalError(p.config, n.info, "genAddr: 2")
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).kind
case kindOfIndexedExpr
of tyArray, tyOpenArray, tySequence, tyString, tyCString, tyVarargs:
genArrayAddr(p, n[0], r)
of tyTuple:
genFieldAddr(p, n[0], r)
else: internalError(p.config, n[0].info, "expr(nkBracketExpr, " & $kindOfIndexedExpr & ')')
of nkObjDownConv:
gen(p, n[0], r)
of nkHiddenDeref:
gen(p, n[0][0], r)
else: internalError(p.config, n[0].info, "genAddr: " & $n[0].kind)
proc attachProc(p: PProc; content: Rope; s: PSym) =
p.g.code.add(content)
proc attachProc(p: PProc; s: PSym) =
let newp = genProc(p, s)
attachProc(p, newp, s)
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: owner.locals.add(newp)
else: attachProc(p, newp, s)
proc genCopyForParamIfNeeded(p: PProc, n: PNode) =
let s = n.sym
if p.prc == s.owner or needsNoCopy(p, n):
return
var owner = p.up
while true:
if owner == nil:
internalError(p.config, n.info, "couldn't find the owner proc of the closed over param: " & s.name.s)
if owner.prc == s.owner:
if not owner.generatedParamCopies.containsOrIncl(s.id):
let copy = "$1 = nimCopy(null, $1, $2);$n" % [s.loc.r, genTypeInfo(p, s.typ)]
owner.locals.add(owner.indentLine(copy))
return
owner = owner.up
proc genVarInit(p: PProc, v: PSym, n: PNode)
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:
internalError(p.config, n.info, "symbol has no generated name: " & s.name.s)
if sfCompileTime in s.flags:
genVarInit(p, s, if s.ast != nil: s.ast else: newNodeI(nkEmpty, s.info))
if s.kind == skParam:
genCopyForParamIfNeeded(p, n)
let k = mapType(p, s.typ)
if k == etyBaseIndex:
r.typ = etyBaseIndex
if {sfAddrTaken, sfGlobal} * s.flags != {}:
if isIndirect(s):
r.address = "$1[0][0]" % [s.loc.r]
r.res = "$1[0][1]" % [s.loc.r]
else:
r.address = "$1[0]" % [s.loc.r]
r.res = "$1[1]" % [s.loc.r]
else:
r.address = s.loc.r
r.res = s.loc.r & "_Idx"
elif isIndirect(s):
r.res = "$1[0]" % [s.loc.r]
else:
r.res = s.loc.r
of skConst:
genConstant(p, s)
if s.loc.r == nil:
internalError(p.config, n.info, "symbol has no generated name: " & s.name.s)
r.res = s.loc.r
of skProc, skFunc, skConverter, skMethod:
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
{sfImportc, sfInfixCall} * s.flags != {}:
discard
elif s.kind == skMethod and s.getBody.kind == nkEmpty:
# we cannot produce code for the dispatcher yet:
discard
elif sfForward in s.flags:
p.g.forwarded.add(s)
else:
genProcForSymIfNeeded(p, s)
else:
if s.loc.r == nil:
internalError(p.config, n.info, "symbol has no generated name: " & s.name.s)
r.res = s.loc.r
r.kind = resVal
proc genDeref(p: PProc, n: PNode, r: var TCompRes) =
let it = n[0]
let t = mapType(p, it.typ)
if t == etyObject:
gen(p, it, r)
else:
var a: TCompRes
gen(p, it, a)
r.kind = a.kind
r.typ = mapType(p, n.typ)
if r.typ == etyBaseIndex:
let tmp = p.getTemp
r.address = "($1 = $2, $1)[0]" % [tmp, a.rdLoc]
r.res = "$1[1]" % [tmp]
r.tmpLoc = tmp
elif a.typ == etyBaseIndex:
if a.tmpLoc != nil:
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
gen(p, n, a)
if a.typ == etyBaseIndex:
r.res.add(a.address)
r.res.add(", ")
r.res.add(a.res)
else:
r.res.add(a.res)
proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes; emitted: ptr int = nil) =
var a: TCompRes
gen(p, n, a)
if skipTypes(param.typ, abstractVar).kind in {tyOpenArray, tyVarargs} and
a.typ == etyBaseIndex:
r.res.add("$1[$2]" % [a.address, a.res])
elif a.typ == etyBaseIndex:
r.res.add(a.address)
r.res.add(", ")
r.res.add(a.res)
if emitted != nil: inc emitted[]
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)
r.res.add("($1 = $2, $1[0]), $1[1]" % [tmp, a.rdLoc])
if emitted != nil: inc emitted[]
else:
r.res.add(a.res)
proc genArgs(p: PProc, n: PNode, r: var TCompRes; start=1) =
r.res.add("(")
var hasArgs = false
var typ = skipTypes(n[0].typ, abstractInst)
assert(typ.kind == tyProc)
assert(typ.len == typ.n.len)
var emitted = start-1
for i in start..= n.len:
globalError(p.config, n.info, "wrong importcpp pattern; expected parameter at position " & $i &
" but got only: " & $(n.len-1))
let it = n[i]
var paramType: PNode = nil
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)
else:
genArg(p, it, paramType.sym, r)
inc generated
proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType;
r: var TCompRes) =
var i = 0
var j = 1
r.kind = resExpr
while i < pat.len:
case pat[i]
of '@':
var generated = 0
for k in j.. 0: r.res.add(", ")
genOtherArg(p, n, k, typ, generated, r)
inc i
of '#':
var generated = 0
genOtherArg(p, n, j, typ, generated, r)
inc j
inc i
of '\31':
# unit separator
r.res.add("#")
inc i
of '\29':
# group separator
r.res.add("@")
inc i
else:
let start = i
while i < pat.len:
if pat[i] notin {'@', '#', '\31', '\29'}: inc(i)
else: break
if i - 1 >= start:
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 sfInfixCall in f.flags:
let pat = n[0].sym.loc.r.data
internalAssert p.config, pat.len > 0
if pat.contains({'#', '(', '@'}):
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[1], r)
if r.typ == etyBaseIndex:
if r.address == nil:
globalError(p.config, n.info, "cannot invoke with infix syntax")
r.res = "$1[$2]" % [r.address, r.res]
r.address = nil
r.typ = etyNone
r.res.add(".")
var op: 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) =
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")
r.res.add("rawEcho(")
for i in 0.. 0: r.res.add(", ")
genArgNoParam(p, it, r)
r.res.add(")")
r.kind = resExpr
proc putToSeq(s: string, indirect: bool): Rope =
result = rope(s)
if indirect: result = "[$1]" % [result]
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 0.. 0: output.add(", ")
output.addf("$#: ", [mangleName(p.module, rec.sym)])
output.add(createVar(p, rec.sym.typ, false))
else: internalError(p.config, rec.info, "createRecordVarAux")
proc createObjInitList(p: PProc, typ: PType, excludedFieldIDs: IntSet, output: var Rope) =
var t = typ
if objHasTypeField(t):
if output.len > 0: output.add(", ")
output.addf("m_type: $1", [genTypeInfo(p, t)])
while t != nil:
t = t.skipTypes(skipPtrs)
createRecordVarAux(p, t.n, excludedFieldIDs, output)
t = t[0]
proc arrayTypeForElemType(typ: PType): string =
# XXX This should also support tyEnum and tyBool
case typ.kind
of tyInt, tyInt32: "Int32Array"
of tyInt16: "Int16Array"
of tyInt8: "Int8Array"
of tyUInt, tyUInt32: "Uint32Array"
of tyUInt16: "Uint16Array"
of tyUInt8: "Uint8Array"
of tyFloat32: "Float32Array"
of tyFloat64, tyFloat: "Float64Array"
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:
result = putToSeq("0", indirect)
of tyFloat..tyFloat128:
result = putToSeq("0.0", indirect)
of tyRange, tyGenericInst, tyAlias, tySink, tyOwned:
result = createVar(p, lastSon(typ), indirect)
of tySet:
result = putToSeq("{}", indirect)
of tyBool:
result = putToSeq("false", indirect)
of tyNil:
result = putToSeq("null", indirect)
of tyArray:
let length = toInt(lengthOrd(p.config, t))
let e = elemType(t)
let jsTyp = arrayTypeForElemType(e)
if jsTyp.len > 0:
result = "new $1($2)" % [rope(jsTyp), rope(length)]
elif length > 32:
useMagic(p, "arrayConstr")
# XXX: arrayConstr depends on nimCopy. This line shouldn't be necessary.
useMagic(p, "nimCopy")
result = "arrayConstr($1, $2, $3)" % [rope(length),
createVar(p, e, false), genTypeInfo(p, e)]
else:
result = rope("[")
var i = 0
while i < length:
if i > 0: result.add(", ")
result.add(createVar(p, e, false))
inc(i)
result.add("]")
if indirect: result = "[$1]" % [result]
of tyTuple:
result = rope("{")
for i in 0.. 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
createObjInitList(p, t, initIntSet(), initList)
result = ("{$1}") % [initList]
if indirect: result = "[$1]" % [result]
of tyVar, tyPtr, tyLent, tyRef, tyPointer:
if mapType(p, t) == etyBaseIndex:
result = putToSeq("[null, 0]", indirect)
else:
result = putToSeq("null", indirect)
of tySequence, tyOpt, tyString, tyCString, tyProc:
result = putToSeq("null", indirect)
of tyStatic:
if t.n != nil:
result = createVar(p, lastSon t, indirect)
else:
internalError(p.config, "createVar: " & $t.kind)
result = nil
else:
internalError(p.config, "createVar: " & $t.kind)
result = nil
template returnType: untyped = ~""
proc genVarInit(p: PProc, v: PSym, n: PNode) =
var
a: TCompRes
s: Rope
varCode: string
varName = mangleName(p.module, v)
useReloadingGuard = sfGlobal in v.flags and p.config.hcrOn
if v.constraint.isNil:
if useReloadingGuard:
lineF(p, "var $1;$n", varName)
lineF(p, "if ($1 === undefined) {$n", varName)
varCode = $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:
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):
s = a.res
else:
useMagic(p, "nimCopy")
s = "nimCopy(null, $1, $2)" % [a.res, genTypeInfo(p, n.typ)]
of etyBaseIndex:
let targetBaseIndex = {sfAddrTaken, sfGlobal} * v.flags == {}
if a.typ == etyBaseIndex:
if targetBaseIndex:
line(p, runtimeFormat(varCode & " = $3, $2_Idx = $4;$n",
[returnType, v.loc.r, a.address, a.res]))
else:
if isIndirect(v):
line(p, runtimeFormat(varCode & " = [[$3, $4]];$n",
[returnType, v.loc.r, a.address, a.res]))
else:
line(p, runtimeFormat(varCode & " = [$3, $4];$n",
[returnType, v.loc.r, 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])
else:
line(p, runtimeFormat(varCode & " = $3;$n", [returnType, v.loc.r, a.res]))
return
else:
s = a.res
if isIndirect(v):
line(p, runtimeFormat(varCode & " = [$3];$n", [returnType, v.loc.r, s]))
else:
line(p, runtimeFormat(varCode & " = $3;$n", [returnType, v.loc.r, s]))
if useReloadingGuard:
dec p.extraIndent
lineF(p, "}$n")
proc genVarStmt(p: PProc, n: PNode) =
for i in 0.. 0: r.res.add(", ")
var it = n[i]
if it.kind == nkRange:
gen(p, it[0], a)
gen(p, it[1], b)
r.res.addf("[$1, $2]", [a.res, b.res])
else:
gen(p, it, a)
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)
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 0.. 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 genTupleConstr(p: PProc, n: PNode, r: var TCompRes) =
var a: TCompRes
r.res = rope("{")
r.kind = resExpr
for i in 0.. 0: r.res.add(", ")
var it = n[i]
if it.kind == nkExprColonExpr: it = it[1]
gen(p, it, a)
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
r.kind = resExpr
var initList : Rope
var fieldIDs = initIntSet()
for i in 1.. 1: initList.add(", ")
var it = n[i]
internalAssert p.config, it.kind == nkExprColonExpr
let val = it[1]
gen(p, val, a)
var f = it[0].sym
if f.loc.r == nil: f.loc.r = mangleName(p.module, f)
fieldIDs.incl(f.id)
let typ = val.typ.skipTypes(abstractInst)
if a.typ == etyBaseIndex:
initList.addf("$#: [$#, $#]", [f.loc.r, a.address, a.res])
else:
if not needsNoCopy(p, val):
useMagic(p, "nimCopy")
a.res = "nimCopy(null, $1, $2)" % [a.rdLoc, genTypeInfo(p, typ)]
initList.addf("$#: $#", [f.loc.r, 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[1].typ, abstractVarRange)
gen(p, n[1], r)
if dest.kind == src.kind:
# no-op conversion
return
case dest.kind:
of tyBool:
r.res = "(!!($1))" % [r.res]
r.kind = resExpr
of tyInt:
r.res = "(($1)|0)" % [r.res]
else:
# TODO: What types must we handle here?
discard
proc upConv(p: PProc, n: PNode, r: var TCompRes) =
gen(p, n[0], r) # XXX
proc genRangeChck(p: PProc, n: PNode, r: var TCompRes, magic: string) =
var a, b: TCompRes
gen(p, n[0], r)
if optRangeCheck notin p.options or (skipTypes(n.typ, abstractVar).kind in {tyUInt..tyUInt64} and
checkUnsignedConversions notin p.config.legacyFeatures):
discard "XXX maybe emit masking instructions here"
else:
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
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[0].kind == nkCStringToString:
gen(p, n[0][0], r)
else:
gen(p, n[0], r)
if r.res == nil: internalError(p.config, n.info, "convStrToCStr")
useMagic(p, "toJSStr")
r.res = "toJSStr($1)" % [r.res]
r.kind = resExpr
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[0].kind == nkStringToCString:
gen(p, n[0][0], r)
else:
gen(p, n[0], r)
if r.res == nil: internalError(p.config, n.info, "convCStrToStr")
useMagic(p, "cstrToNimstr")
r.res = "cstrToNimstr($1)" % [r.res]
r.kind = resExpr
proc genReturnStmt(p: PProc, n: PNode) =
if p.procDef == nil: internalError(p.config, n.info, "genReturnStmt")
p.beforeRetNeeded = true
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 =
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", []))
proc frameDestroy(p: PProc): Rope =
result = p.indentLine rope(("framePtr = F.prev;") & "\L")
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)))
else:
result = nil
if p.beforeRetNeeded:
result.add p.indentLine(~"BeforeRet: do {$n")
result.add p.body
result.add p.indentLine(~"} while (false);$n")
else:
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):
result.add(frameDestroy(p))
proc optionalLine(p: Rope): Rope =
if p == nil:
return nil
else:
return p & "\L"
proc genProc(oldProc: PProc, prc: PSym): Rope =
var
resultSym: PSym
a: 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 name = mangleName(p.module, prc)
let header = generateHeader(p, prc.typ)
if prc.typ[0] != nil and sfPure notin prc.flags:
resultSym = prc.ast[resultPos].sym
let mname = mangleName(p.module, resultSym)
if not isIndirect(resultSym) and
resultSym.typ.kind in {tyVar, tyPtr, tyLent, tyRef, tyOwned} and
mapType(p, resultSym.typ) == etyBaseIndex:
resultAsgn = p.indentLine(("var $# = null;$n") % [mname])
resultAsgn.add p.indentLine("var $#_Idx = 0;$n" % [mname])
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]
var transformedBody = transformBody(oldProc.module.graph, prc, cache = false)
if sfInjectDestructors in prc.flags:
transformedBody = injectDestructorCalls(oldProc.module.graph, 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 = runtimeFormat(prc.constraint.strVal & " {$n$#$#$#$#$#",
[ returnType,
name,
header,
optionalLine(p.globals),
optionalLine(p.locals),
optionalLine(resultAsgn),
optionalLine(genProcBody(p, prc)),
optionalLine(p.indentLine(returnStmt))])
else:
# if optLineDir in p.config.options:
# result.add(~"\L")
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" %
[thunkName, name])
def = "function $#($#) {$n$#$#$#$#$#" %
[ name,
header,
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")
#if gVerbosity >= 3:
# echo "END generated code for: " & prc.name.s
proc genStmt(p: PProc, n: PNode) =
var r: TCompRes
gen(p, n, r)
if r.res != nil: lineF(p, "$#;$n", [r.res])
proc genPragma(p: PProc, n: PNode) =
for it in n.sons:
case whichPragma(it)
of wEmit: genAsmOrEmitStmt(p, it[1])
else: discard
proc genCast(p: PProc, n: PNode, r: var TCompRes) =
var dest = skipTypes(n.typ, abstractVarRange)
var src = skipTypes(n[1].typ, abstractVarRange)
gen(p, n[1], r)
if dest.kind == src.kind:
# no-op conversion
return
let toInt = (dest.kind in tyInt..tyInt32)
let toUint = (dest.kind in tyUInt..tyUInt32)
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]
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]
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
case n.kind
of nkSym:
genSym(p, n, r)
of nkCharLit..nkUInt64Lit:
if n.typ.kind == tyBool:
r.res = if n.intVal == 0: rope"false" else: rope"true"
else:
r.res = rope(n.intVal)
r.kind = resExpr
of nkNilLit:
if isEmptyType(n.typ):
discard
elif mapType(p, n.typ) == etyBaseIndex:
r.typ = etyBaseIndex
r.address = rope"null"
r.res = rope"0"
r.kind = resExpr
else:
r.res = rope"null"
r.kind = resExpr
of nkStrLit..nkTripleStrLit:
if skipTypes(n.typ, abstractVarRange).kind == tyString:
if n.strVal.len != 0:
useMagic(p, "makeNimstrLit")
r.res = "makeNimstrLit($1)" % [makeJSString(n.strVal)]
else:
r.res = rope"[]"
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 fcNegZero:
r.res = rope"-0.0"
of fcZero:
r.res = rope"0.0"
of fcInf:
r.res = rope"Infinity"
of fcNegInf:
r.res = rope"-Infinity"
else: r.res = rope(f.toStrMaxPrecision)
r.kind = resExpr
of nkCallKinds:
if isEmptyType(n.typ):
genLineDir(p, n)
if (n[0].kind == nkSym) and (n[0].sym.magic != mNone):
genMagic(p, n, r)
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 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)
of nkBracketExpr: genArrayAccess(p, n, r)
of nkDotExpr: genFieldAccess(p, n, 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")
of nkChckRange64: genRangeChck(p, n, r, "chckRange64")
of nkChckRange: genRangeChck(p, n, r, "chckRange")
of nkStringToCString: convStrToCStr(p, n, r)
of nkCStringToString: convCStrToStr(p, n, r)
of nkEmpty: discard
of nkLambdaKinds:
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
elif not p.g.generatedSyms.containsOrIncl(s.id):
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 0..