diff options
author | rec <44084068+recloser@users.noreply.github.com> | 2018-12-04 12:04:27 +0100 |
---|---|---|
committer | Andreas Rumpf <rumpf_a@web.de> | 2018-12-04 12:04:27 +0100 |
commit | e3e5ae287f9a10ab9ee350e64982f21c44238594 (patch) | |
tree | 7ae04f1401c62ef4b31cbbce5134dfb3555a20e9 | |
parent | 07744b6e47ef1e83630d628c7d1eae4f3aa5bee5 (diff) | |
download | Nim-e3e5ae287f9a10ab9ee350e64982f21c44238594.tar.gz |
Fix fat pointers, object copying, magic double evals on JS (#9411) [backport]
* Add a test for issue #9389 * Fixes #9389. * Make object contructors copy objects properly by checking whether the expressions passed to them don't need to be copied. * Make mArrToSeq implementation actually check if a copy needs to be made. * Avoid unnecessary copy in mChr impl * Assume set constructor elements need no copy * Add a test for issue #9410 * Add a test * fix passing fat pointers (#9410) * Enhance tests * More tests and fixes * Add more (failing) tests [ci skip] * Added equality operator for fat pointers, more tests and fixes * Fix printing uninitialized strings * Fix mInc, mDec double eval, add more tests * Tests * Refactored, fixed multiple evals, revamped the tests, added missing ops * Fix ups * Fix #9643 and #9644 * add pointer normalization
-rw-r--r-- | compiler/jsgen.nim | 317 | ||||
-rw-r--r-- | lib/system.nim | 11 | ||||
-rw-r--r-- | lib/system/jssys.nim | 7 | ||||
-rw-r--r-- | lib/system/reprjs.nim | 4 | ||||
-rw-r--r-- | tests/js/t9410.nim | 454 | ||||
-rw-r--r-- | tests/js/tcopying.nim | 35 |
6 files changed, 745 insertions, 83 deletions
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 3af34c03b..a9813f5c5 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -66,6 +66,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 @@ -131,16 +137,15 @@ proc newGlobals(): PGlobals = proc initCompRes(r: var TCompRes) = r.address = nil r.res = nil + r.tmpLoc = nil r.typ = etyNone r.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 = @@ -447,12 +452,48 @@ const # magic checked op; magic unchecked op; checked op; unchecked op ["cstrToNimstr", "cstrToNimstr", "cstrToNimstr($1)", "cstrToNimstr($1)"], ["", "", "$1", "$1"]] +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) + proc 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.sons[1], x) gen(p, n.sons[2], y) - r.res = frmt % [x.rdLoc, y.rdLoc] + + var + a, tmp = x.rdLoc + b, tmp2 = y.rdLoc + if "$3" in frmt: (a, tmp) = maybeMakeTemp(p, n[1], x) + if "$4" in frmt: (a, tmp) = maybeMakeTemp(p, n[1], x) + + r.res = frmt % [a, b, tmp, tmp2] r.kind = resExpr proc unsignedTrimmerJS(size: BiggestInt): Rope = @@ -473,7 +514,8 @@ proc binaryUintExpr(p: PProc, n: PNode, r: var TCompRes, op: string, 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] + 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] @@ -487,9 +529,12 @@ proc ternaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) = r.kind = resExpr proc 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] + 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) = @@ -524,6 +569,14 @@ proc arith(p: PProc, n: PNode, r: var TCompRes, op: TMagic) = of mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, mFloatToStr, mCStrToStr, mStrToStr, mEnumToStr: arithAux(p, n, r, op) + of mEqRef, mEqUntracedRef: + 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 @@ -801,6 +854,7 @@ proc genAsmOrEmitStmt(p: PProc, n: PNode) = # A fat pointer is disguised as an array r.res = r.address r.address = nil + r.typ = etyNone elif r.typ == etyBaseIndex: # Deference first r.res = "$1[$2]" % [r.address, r.res] @@ -863,26 +917,42 @@ proc countJsParams(typ: PType): int = const nodeKindsNeedNoCopy = {nkCharLit..nkInt64Lit, nkStrLit..nkTripleStrLit, - nkFloatLit..nkFloat64Lit, nkCurly, nkPar, nkTupleConstr, nkObjConstr, nkStringToCString, + nkFloatLit..nkFloat64Lit, nkCurly, nkPar, nkStringToCString, 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}) + # if the node is a literal object constructor we have to recursively + # check the expressions passed into it + case y.kind + of nkObjConstr: + for arg in y.sons[1..^1]: + if not needsNoCopy(p, arg[1]): + return false + of nkTupleConstr: + for arg in y.sons: + var arg = arg + if arg.kind == nkExprColonExpr: + arg = arg[1] + if not needsNoCopy(p, arg): + return false + of nkBracket: + for arg in y.sons: + if not needsNoCopy(p, arg): + return false + of nodeKindsNeedNoCopy: + return true + else: + return (mapType(y.typ) != etyBaseIndex and + (skipTypes(y.typ, abstractInst).kind in + {tyRef, tyPtr, tyLent, tyVar, tyCString} + IntegralTypes)) + return true proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = var a, b: 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) - + gen(p, x, a) genLineDir(p, y) gen(p, y, b) @@ -911,13 +981,13 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = 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]) else: internalError(p.config, x.info, "genAsgn") else: lineF(p, "$1 = $2; $3 = $4;$n", [a.address, b.address, a.res, 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) = genAsgnAux(p, n.sons[0], n.sons[1], noCopyNeeded=false) @@ -971,17 +1041,30 @@ proc genFieldAddr(p: PProc, n: PNode, r: var TCompRes) = r.kind = resExpr proc genFieldAccess(p: PProc, n: PNode, r: var TCompRes) = - r.typ = etyNone gen(p, n.sons[0], r) + r.typ = mapType(n.typ) let otyp = skipTypes(n.sons[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] + 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] + mkTemp(1) r.kind = resExpr proc genAddr(p: PProc, n: PNode, r: var TCompRes) @@ -1039,14 +1122,15 @@ proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) = 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 + #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.sons[0].typ, abstractPtrs) if typ.kind == tyArray: first = firstOrd(p.config, typ.sons[0]) else: first = 0 if optBoundsCheck in p.options: useMagic(p, "chckIndx") - r.res = "chckIndx($1, $2, $3.length+$2-1)-$2" % [b.res, rope(first), a.res] + 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)] else: @@ -1062,13 +1146,22 @@ proc genArrayAccess(p: PProc, n: PNode, r: var TCompRes) = of tyTuple: genFieldAddr(p, n, r) else: internalError(p.config, n.info, "expr(nkBracketExpr, " & $ty.kind & ')') - r.typ = etyNone + 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.address = nil r.kind = resExpr template isIndirect(x: PSym): bool = @@ -1169,8 +1262,12 @@ proc genSym(p: PProc, n: PNode, r: var TCompRes) = 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.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" @@ -1210,14 +1307,17 @@ proc genDeref(p: PProc, n: PNode, r: var TCompRes) = else: var a: 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 != nil: + r.tmpLoc = a.tmpLoc + r.res = a.rdLoc else: internalError(p.config, n.info, "genDeref") @@ -1242,7 +1342,7 @@ proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes; emitted: ptr int = add(r.res, ", ") add(r.res, 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} 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]) @@ -1366,6 +1466,14 @@ proc genCall(p: PProc, n: PNode, r: var TCompRes) = return gen(p, n.sons[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 @@ -1472,12 +1580,12 @@ proc createVar(p: PProc, typ: PType, indirect: bool): Rope = createObjInitList(p, t, initIntSet(), initList) result = ("{$1}") % [initList] if indirect: result = "[$1]" % [result] - of tyVar, tyPtr, tyLent, tyRef: + 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, tyPointer, tyProc: + of tySequence, tyOpt, tyString, tyCString, tyProc: result = putToSeq("null", indirect) of tyStatic: if t.n != nil: @@ -1511,10 +1619,13 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) = 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} and mapType(p, v.typ) == etyBaseIndex: + lineF(p, "var $1 = null;$n", [varName]) lineF(p, "var $1_Idx = 0;$n", [varName]) + else: + lineF(p, varCode & " = $3;$n", + [returnType, varName, createVar(p, v.typ, isIndirect(v))]) else: gen(p, n, a) case mapType(p, v.typ) @@ -1531,8 +1642,12 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) = lineF(p, varCode & " = $3, $2_Idx = $4;$n", [returnType, v.loc.r, a.address, a.res]) else: - lineF(p, varCode & " = [$3, $4];$n", - [returnType, v.loc.r, a.address, a.res]) + if isIndirect(v): + lineF(p, varCode & " = [[$3, $4]];$n", + [returnType, v.loc.r, a.address, a.res]) + else: + lineF(p, varCode & " = [$3, $4];$n", + [returnType, v.loc.r, a.address, a.res]) else: if targetBaseIndex: let tmp = p.getTemp @@ -1579,7 +1694,12 @@ 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)]) + 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 @@ -1603,20 +1723,20 @@ proc genConStrStr(p: PProc, n: PNode, r: var TCompRes) = if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyChar: r.res.add("[$1].concat(" % [a.res]) else: - r.res.add("($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: r.res.add("[$1]," % [a.res]) else: - r.res.add("$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])" % [a.res]) else: - r.res.add("$1)" % [a.res]) + 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: @@ -1701,8 +1821,12 @@ 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)]) + if x.typ == etyBaseIndex: + lineF(p, "$1 = null, $2 = 0;$n", [x.address, x.res]) + else: + let (a, tmp) = maybeMakeTemp(p, n[1], x) + lineF(p, "$1 = genericReset($3, $2);$n", [a, + genTypeInfo(p, n.sons[1].typ), tmp]) proc genMagic(p: PProc, n: PNode, r: var TCompRes) = var @@ -1721,32 +1845,37 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = 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]; }") + "if ($1 != null) { addChar($3, $2); } else { $3 = [$2]; }") of mAppendStrStr: var lhs, rhs: TCompRes gen(p, n[1], lhs) gen(p, n[2], rhs) let rhsIsLit = n[2].kind in nkStrKinds + let (a, tmp) = maybeMakeTemp(p, n[1], lhs) if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyCString: - r.res = "if ($1 != null) { $1 += $2; } else { $1 = $2$3; }" % [ - lhs.rdLoc, rhs.rdLoc, if rhsIsLit: nil else: ~".slice()"] + r.res = "if ($1 != null) { $4 += $2; } else { $4 = $2$3; }" % [ + a, rhs.rdLoc, if rhsIsLit: nil else: ~".slice()", tmp] else: - r.res = "if ($1 != null) { $1 = ($1).concat($2); } else { $1 = $2$3; }" % [ - lhs.rdLoc, rhs.rdLoc, if rhsIsLit: nil else: ~".slice()"] + r.res = "if ($1 != null) { $4 = ($4).concat($2); } else { $4 = $2$3; }" % [ + lhs.rdLoc, rhs.rdLoc, if rhsIsLit: nil else: ~".slice()", 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] + let (a, tmp) = maybeMakeTemp(p, n[1], x) + if mapType(n[2].typ) == etyBaseIndex: + let c = "[$1, $2]" % [y.address, y.res] + r.res = "if ($1 != null) { $3.push($2); } else { $3 = [$2]; }" % [a, c, tmp] + elif needsNoCopy(p, n[2]): + r.res = "if ($1 != null) { $3.push($2); } else { $3 = [$2]; }" % [a, y.rdLoc, tmp] 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 = "if ($1 != null) { $3.push($2); } else { $3 = [$2]; }" % [a, c, tmp] r.kind = resExpr of mConStrStr: genConStrStr(p, n, r) @@ -1756,39 +1885,56 @@ 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: + if mapType(n[1].typ) != etyBaseIndex: + unaryExpr(p, n, r, "", "($1 === null)") + else: + var x: 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 mChr, mArrToSeq: gen(p, n.sons[1], r) # nothing to do + of mChr: gen(p, n.sons[1], r) + of mArrToSeq: + if needsNoCopy(p, n.sons[1]): + gen(p, n.sons[1], r) + else: + var x: TCompRes + gen(p, n.sons[1], x) + useMagic(p, "nimCopy") + r.res = "nimCopy(null, $1, $2)" % [x.rdLoc, genTypeInfo(p, n.typ)] of mDestroy: discard "ignore calls to the default destructor" of mOrd: genOrd(p, n, r) of mLengthStr, mLengthSeq, mLengthOpenArray, mLengthArray: - unaryExpr(p, n, r, "", "($1 != null ? $1.length : 0)") + unaryExpr(p, n, r, "", "($1 != null ? $2.length : 0)") of mXLenStr, mXLenSeq: unaryExpr(p, n, r, "", "$1.length") of mHigh: - unaryExpr(p, n, r, "", "($1 != null ? ($1.length-1) : -1)") + unaryExpr(p, n, r, "", "($1 != null ? ($2.length-1) : -1)") of mInc: if n[1].typ.skipTypes(abstractRange).kind in tyUInt .. tyUInt64: binaryUintExpr(p, n, r, "+", 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)") of ast.mDec: if n[1].typ.skipTypes(abstractRange).kind in tyUInt .. tyUInt64: binaryUintExpr(p, n, r, "-", 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)") of mSetLengthStr: - binaryExpr(p, n, r, "", "$1.length = $2") + binaryExpr(p, n, r, "mnewString", "($1 === null ? $3 = mnewString($2) : $3.length = $2)") 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)] + let (a, tmp) = maybeMakeTemp(p, n[1], x) + let (b, tmp2) = maybeMakeTemp(p, n[2], y) + r.res = """if ($1 === null) $4 = []; + if ($4.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)") @@ -1856,7 +2002,10 @@ proc genArrayConstr(p: PProc, n: PNode, r: var TCompRes) = 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) + if a.typ == etyBaseIndex: + addf(r.res, "[$1, $2]", [a.address, a.res]) + else: + add(r.res, a.res) add(r.res, "]") proc genTupleConstr(p: PProc, n: PNode, r: var TCompRes) = @@ -1868,7 +2017,10 @@ proc genTupleConstr(p: PProc, n: PNode, r: var TCompRes) = var it = n.sons[i] if it.kind == nkExprColonExpr: it = it.sons[1] gen(p, it, a) - addf(r.res, "Field$#: $#", [i.rope, a.res]) + if a.typ == etyBaseIndex: + addf(r.res, "Field$#: [$#, $#]", [i.rope, a.address, a.res]) + else: + addf(r.res, "Field$#: $#", [i.rope, a.res]) r.res.add("}") proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) = @@ -1888,12 +2040,17 @@ proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) = 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]): + mapType(p, typ) != etyBaseIndex) or + a.typ == etyBaseIndex or + needsNoCopy(p, it.sons[1]): discard else: useMagic(p, "nimCopy") a.res = "nimCopy(null, $1, $2)" % [a.rdLoc, genTypeInfo(p, typ)] - addf(initList, "$#: $#", [f.loc.r, a.res]) + if a.typ == etyBaseIndex: + addf(initList, "$#: [$#, $#]", [f.loc.r, a.address, a.res]) + else: + addf(initList, "$#: $#", [f.loc.r, a.res]) let t = skipTypes(n.typ, abstractInst + skipPtrs) createObjInitList(p, t, fieldIDs, initList) r.res = ("{$1}") % [initList] @@ -2011,11 +2168,14 @@ proc genProc(oldProc: PProc, prc: PSym): Rope = if prc.typ.sons[0] != nil and sfPure notin prc.flags: resultSym = prc.ast.sons[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 + if not isindirect(resultSym) and + resultSym.typ.kind in {tyVar, tyPtr, tyLent, tyRef} 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.sons[resultPos], a) if mapType(p, resultSym.typ) == etyBaseIndex: returnStmt = "return [$#, $#];$n" % [a.address, a.res] @@ -2107,6 +2267,13 @@ proc genCast(p: PProc, n: PNode, r: var TCompRes) = 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 diff --git a/lib/system.nim b/lib/system.nim index 9111ddd86..0bb53d9df 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2522,12 +2522,13 @@ proc `==`*[T](x, y: seq[T]): bool {.noSideEffect.} = when not defined(JS): proc seqToPtr[T](x: seq[T]): pointer {.inline, nosideeffect.} = result = cast[pointer](x) - else: - proc seqToPtr[T](x: seq[T]): pointer {.asmNoStackFrame, nosideeffect.} = - asm """return `x`""" - if seqToPtr(x) == seqToPtr(y): - return true + if seqToPtr(x) == seqToPtr(y): + return true + else: + var sameObject = false + asm """`sameObject` = `x` === `y`""" + if sameObject: return true when not defined(nimNoNil): if x.isNil or y.isNil: diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index 5ac0ca8b2..8be19e5b8 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -519,8 +519,11 @@ proc nimCopyAux(dest, src: JSRef, n: ptr TNimNode) {.compilerproc.} = `dest`[`n`.offset] = nimCopy(`dest`[`n`.offset], `src`[`n`.offset], `n`.typ); """ of nkList: - for i in 0..n.len-1: - nimCopyAux(dest, src, n.sons[i]) + asm """ + for (var i = 0; i < `n`.sons.length; i++) { + nimCopyAux(`dest`, `src`, `n`.sons[i]); + } + """ of nkCase: asm """ `dest`[`n`.offset] = nimCopy(`dest`[`n`.offset], `src`[`n`.offset], `n`.typ); diff --git a/lib/system/reprjs.nim b/lib/system/reprjs.nim index 7cb25a252..fb231bbed 100644 --- a/lib/system/reprjs.nim +++ b/lib/system/reprjs.nim @@ -64,7 +64,9 @@ proc reprStrAux(result: var string, s: cstring, len: int) = proc reprStr(s: string): string {.compilerRtl.} = result = "" - if cast[pointer](s).isNil: + var sIsNil = false + asm """`sIsNil` = `s` === null""" + if sIsNil: # cast[pointer](s).isNil: # Handle nil strings here because they don't have a length field in js # TODO: check for null/undefined before generating call to length in js? # Also: c backend repr of a nil string is <pointer>"", but repr of an diff --git a/tests/js/t9410.nim b/tests/js/t9410.nim new file mode 100644 index 000000000..9aca6d45b --- /dev/null +++ b/tests/js/t9410.nim @@ -0,0 +1,454 @@ +template doAssert(exp: untyped) = + when defined(echot9410): + let r = exp + echo $(instantiationInfo().line) & ":\n " & astToStr(exp) & "\n was " & repr(r) + when not defined(noassertt9410): + system.doAssert r + else: + when not defined(noassertt9410): + system.doAssert exp + +template tests = + block: + var i = 0 + i = 2 + + var y: ptr int + doAssert y == nil + doAssert isNil(y) + y = i.addr + y[] = 3 + doAssert i == 3 + doAssert i == y[] + + let z = i.addr + z[] = 4 + doAssert i == 4 + doAssert i == y[] and y[] == z[] + + var hmm = (a: (b: z)) + var hmmptr = hmm.a.b.addr + hmmptr[][] = 5 + + doAssert i == 5 + doAssert y == z + doAssert z == hmmptr[] + doAssert 5 == y[] and 5 == z[] and 5 == hmmptr[][] + + block: + var someint = 500 + + let p: ptr int = someint.addr + let tup = (f: p) + let tcopy = tup + var vtcopy = tcopy + p[] = 654 + doAssert p[] == 654 + doAssert tup.f[] == 654 + doAssert tcopy.f[] == 654 + doAssert vtcopy.f[] == 654 + + block: + var someint = 500 + + var p: ptr int = someint.addr + let arr = [p] + let arrc = arr + p[] = 256 + doAssert someint == 256 + doAssert p[] == 256 + doAssert arr[0][] == 256 + doAssert arrc[0][] == 256 + + block: + var someref: ref int + new(someref) + var someref2 = someref + + var tup1 = (f: someref) + tup1.f = someref + let tup2 = tup1 + + someref[] = 543 + + proc passref(r: var ref int): var ref int = r + new(passref(someref)) + + doAssert someref[] == 0 + doAssert tup1.f[] == 543 + doAssert tup2.f[] == 543 + doAssert someref2[] == 543 + + block: + type Whatever = object + i: ref int + + var someref: ref int + new(someref) + someref[] = 10 + + let w = Whatever(i: someref) + var wcopy = w + + someref[] = 20 + + doAssert w.i[] == 20 + doAssert someref[] == 20 + doAssert wcopy.i[] == 20 + doAssert w.i == wcopy.i + #echo w.i[], " ", someref[], " ", wcopy.i[] + + block: + var oneseq: ref seq[ref int] + new(oneseq) + var aref: ref int + new(aref) + aref[] = 123 + let arefs = [aref] + oneseq[] &= arefs[0] + oneseq[] &= aref + aref[] = 222 + new(aref) + doAssert oneseq[0] == oneseq[1] + doAssert oneseq[0][] == 222 + doAssert oneseq[1][] == 222 + doAssert aref[] == 0 + + block: + var seqs: ref seq[ref seq[ref int]] + new(seqs) + seqs[] = newSeq[ref seq[ref int]](1) + new(seqs[0]) + seqs[0][] = newSeq[ref int](0) + + var aref: ref int + new aref + aref[] = 654 + + let arefs = [aref] + doAssert arefs[0] == aref + seqs[0][] &= arefs[0] + seqs[0][] &= aref + seqs[0][1][] = 456 + let seqs2 = seqs + let same = seqs2[0][0] == seqs2[0][1] + doAssert arefs[0] == aref + doAssert aref[] == 456 + doAssert seqs[].len == 1 + doAssert seqs[0][].len == 2 + doAssert seqs[0][0][] == 456 + doAssert seqs[0][1][] == 456 + doAssert same + + block: + type Obj = object + x, y: int + + var objrefs: seq[ref Obj] = @[(ref Obj)(nil), nil, nil] + objrefs[2].new + objrefs[2][] = Obj(x: 123, y: 321) + objrefs[1] = objrefs[2] + doAssert objrefs[0] == nil + doAssert objrefs[1].y == 321 + doAssert objrefs[2].y == 321 + doAssert objrefs[1] == objrefs[2] + + block: + var refs: seq[ref string] = @[(ref string)(nil), nil, nil] + refs[1].new + refs[1][] = "it's a ref!" + refs[0] = refs[1] + refs[2] = refs[1] + new(refs[0]) + doAssert refs[0][] == "" + doAssert refs[1][] == "it's a ref!" + doAssert refs[2][] == "it's a ref!" + doAssert refs[1] == refs[2] + + block: + var retaddr_calls = 0 + proc retaddr(p: var int): var int = + retaddr_calls += 1 + p + + var tfoo_calls = 0 + proc tfoo(x: var int) = + tfoo_calls += 1 + x += 10 + var y = x.addr + y[] += 20 + retaddr(x) += 30 + let z = retaddr(x).addr + z[] += 40 + + var ints = @[1, 2, 3] + tfoo(ints[1]) + doAssert retaddr_calls == 2 + doAssert tfoo_calls == 1 + doAssert ints[1] == 102 + + var tbar_calls = 0 + proc tbar(x: var int): var int = + tbar_calls += 1 + x + + tbar(ints[2]) += 10 + tbar(ints[2]) *= 2 + doAssert tbar_calls == 2 + + var tqux_calls = 0 + proc tqux(x: var int): ptr int = + tqux_calls += 1 + x.addr + + discard tqux(ints[2]) == tqux(ints[2]) + doAssert tqux_calls == 2 + doAssert isNil(tqux(ints[2])) == false + doAssert tqux_calls == 3 + + var tseq_calls = 0 + proc tseq(x: var seq[int]): var seq[int] = + tseq_calls += 1 + x + + tseq(ints) &= 999 + doAssert tseq_calls == 1 + doAssert ints == @[1, 102, 26, 999] + + var rawints = @[555] + rawints &= 666 + doAssert rawints == @[555, 666] + + var resetints_calls = 0 + proc resetInts(): int = + resetints_calls += 1 + ints = @[0, 0, 0] + 1 + + proc incr(x: var int; b: int): var int = + x = x + b + x + + var q = 0 + var qp = q.addr + qp[] += 123 + doAssert q == 123 + # check order of evaluation + doAssert (resetInts() + incr(q, tqux(ints[2])[])) == 124 + + block: # reset + var calls = 0 + proc passsomething(x: var int): var int = + calls += 1 + x + + var + a = 123 + b = 500 + c = a.addr + reset(passsomething(a)) + doAssert calls == 1 + reset(b) + doAssert a == b + reset(c) + doAssert c == nil + + block: # strings + var calls = 0 + proc stringtest(s: var string): var string = + calls += 1 + s + + var somestr: string + + stringtest(somestr) &= 'a' + stringtest(somestr) &= 'b' + doAssert calls == 2 + doAssert somestr == "ab" + stringtest(somestr) &= "woot!" + doAssert somestr == "abwoot!" + doAssert calls == 3 + + doAssert stringtest(somestr).len == 7 + doAssert calls == 4 + doAssert high(stringtest(somestr)) == 6 + doAssert calls == 5 + + var somestr2: string + stringtest(somestr2).setLen(stringtest(somestr).len) + doAssert calls == 7 + doAssert somestr2.len == somestr.len + + var somestr3: string + doAssert (somestr3 & "foo") == "foo" + + block: + var a, b, c, d: string + d = a & b & c + doAssert d == "" + d = stringtest(a) & stringtest(b) & stringtest(c) + doAssert calls == 10 + doAssert d == "" + + block: # seqs + var calls = 0 + proc seqtest(s: var seq[int]): var seq[int] = + calls += 1 + s + + var someseq: seq[int] + + seqtest(someseq) &= 1 + seqtest(someseq) &= 2 + doAssert calls == 2 + doAssert someseq == @[1, 2] + seqtest(someseq) &= @[3, 4, 5] + doAssert someseq == @[1, 2, 3, 4, 5] + doAssert calls == 3 + + doAssert seqtest(someseq).len == 5 + doAssert calls == 4 + doAssert high(seqtest(someseq)) == 4 + doAssert calls == 5 + + # genArrayAddr + doAssert seqtest(someseq)[2] == 3 + doAssert calls == 6 + + seqtest(someseq).setLen(seqtest(someseq).len) + doAssert calls == 8 + + var somenilseq: seq[int] + seqtest(somenilseq).setLen(3) + doAssert calls == 9 + doAssert somenilseq[1] == 0 + + someseq = @[1, 2, 3] + doAssert (seqtest(someseq) & seqtest(someseq)) == @[1, 2, 3, 1, 2, 3] + + + block: # mInc, mDec + var calls = 0 + proc someint(x: var int): var int = + calls += 1 + x + + var x = 10 + + inc(someint(x)) + doAssert x == 11 + doAssert calls == 1 + + dec(someint(x)) + doAssert x == 10 + doAssert calls == 2 + + block: # uints + var calls = 0 + proc passuint(x: var uint32): var uint32 = + calls += 1 + x + + var u: uint32 = 5 + passuint(u) += 1 + doAssert u == 6 + doAssert calls == 1 + + passuint(u) -= 1 + doAssert u == 5 + doAssert calls == 2 + + passuint(u) *= 2 + doAssert u == 10 + doAssert calls == 3 + + block: # objs + type Thing = ref object + x, y: int + + var a, b: Thing + a = Thing() + b = a + + doAssert a == b + + var calls = 0 + proc passobj(o: var Thing): var Thing = + calls += 1 + o + + passobj(b) = Thing(x: 123) + doAssert calls == 1 + doAssert a != b + doAssert b.x == 123 + + var passobjptr_calls = 0 + proc passobjptr(o: var Thing): ptr Thing = + passobjptr_calls += 1 + o.addr + + passobjptr(b)[] = Thing(x: 234) + doAssert passobjptr_calls == 1 + doAssert a != b + doAssert b.x == 234 + passobjptr(b)[].x = 500 + doAssert b.x == 500 + + var pptr = passobjptr(b) + pptr.x += 100 + doAssert b.x == 600 + + proc getuninitptr(): ptr int = + return + + doAssert getuninitptr() == nil + + block: # pointer casting + var obj = (x: 321, y: 543) + var x = 500 + + var objptr = obj.addr + var xptr = x.addr + + var p1, p2: pointer + p1 = cast[pointer](objptr) + p2 = cast[pointer](xptr) + doAssert p1 != p2 + + p1 = cast[pointer](objptr) + p2 = cast[pointer](objptr) + doAssert p1 == p2 + + let objptr2 = cast[type(objptr)](p2) + doAssert objptr == objptr2 + + p1 = cast[pointer](xptr) + p2 = cast[pointer](xptr) + doAssert p1 == p2 + + let xptr2 = cast[type(xptr)](p2) + doAssert xptr == xptr2 + + when false: + block: # openarray + # Error: internal error: genAddr: nkStmtListExpr + var calls = 0 + proc getvarint(x: var openarray[int]): var int = + calls += 1 + if true: + x[1] + else: + x[0] + + var arr = [1, 2, 3] + getvarint(arr) += 5 + doAssert calls == 1 + doAssert arr[1] == 7 + +proc tests_in_proc = + tests + +# since pointers are handled differently in global/local contexts +# let's just run all of them twice +tests_in_proc() +tests diff --git a/tests/js/tcopying.nim b/tests/js/tcopying.nim index 387df9cd3..c58a080e9 100644 --- a/tests/js/tcopying.nim +++ b/tests/js/tcopying.nim @@ -2,6 +2,10 @@ discard """ output: '''123 2 9 2 9 +1 124 +true false +100 300 100 +1 ''' """ @@ -35,3 +39,34 @@ block: obj.ary2[1] = 9 echo ary1[1], " ", obj.ary2[1] + +block: + type TestObj = object + x, y: int + + let obj = TestObj(x: 1, y: 2) + var s = @[obj] + s[0].x += 123 + echo obj.x, " ", s[0].x + +block: + var nums = {1, 2, 3, 4} + let obj = (n: nums) + nums.incl 5 + echo (5 in nums), " ", (5 in obj.n) + +block: + let tup1 = (a: 100) + var tup2 = (t: (t2: tup1)) + var tup3 = tup1 + tup2.t.t2.a = 300 + echo tup1.a, " ", tup2.t.t2.a, " ", tup3.a + +block: + proc foo(arr: array[2, int]) = + var s = @arr + s[0] = 500 + + var nums = [1, 2] + foo(nums) + echo nums[0] \ No newline at end of file |