# # # The Nim Compiler # (c) Copyright 2015 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # # this module folds constants; used by semantic checking phase # and evaluation phase import strutils, options, ast, trees, nimsets, platform, math, msgs, idents, renderer, types, commands, magicsys, modulegraphs, strtabs, lineinfos from system/memory import nimCStrLen proc errorType*(g: ModuleGraph): PType = ## creates a type representing an error state result = newType(tyError, nextTypeId(g.idgen), g.owners[^1]) result.flags.incl tfCheckedForDestructor proc getIntLitTypeG(g: ModuleGraph; literal: PNode; idgen: IdGenerator): PType = # we cache some common integer literal types for performance: let ti = getSysType(g, literal.info, tyInt) result = copyType(ti, nextTypeId(idgen), ti.owner) result.n = literal proc newIntNodeT*(intVal: Int128, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode = result = newIntTypeNode(intVal, n.typ) # See bug #6989. 'pred' et al only produce an int literal type if the # original type was 'int', not a distinct int etc. if n.typ.kind == tyInt: # access cache for the int lit type result.typ = getIntLitTypeG(g, result, idgen) result.info = n.info proc newFloatNodeT*(floatVal: BiggestFloat, n: PNode; g: ModuleGraph): PNode = if n.typ.skipTypes(abstractInst).kind == tyFloat32: result = newFloatNode(nkFloat32Lit, floatVal) else: result = newFloatNode(nkFloatLit, floatVal) result.typ = n.typ result.info = n.info proc newStrNodeT*(strVal: string, n: PNode; g: ModuleGraph): PNode = result = newStrNode(nkStrLit, strVal) result.typ = n.typ result.info = n.info proc getConstExpr*(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode # evaluates the constant expression or returns nil if it is no constant # expression proc evalOp*(m: TMagic, n, a, b, c: PNode; idgen: IdGenerator; g: ModuleGraph): PNode proc checkInRange(conf: ConfigRef; n: PNode, res: Int128): bool = res in firstOrd(conf, n.typ)..lastOrd(conf, n.typ) proc foldAdd(a, b: Int128, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode = let res = a + b if checkInRange(g.config, n, res): result = newIntNodeT(res, n, idgen, g) proc foldSub(a, b: Int128, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode = let res = a - b if checkInRange(g.config, n, res): result = newIntNodeT(res, n, idgen, g) proc foldUnarySub(a: Int128, n: PNode; idgen: IdGenerator, g: ModuleGraph): PNode = if a != firstOrd(g.config, n.typ): result = newIntNodeT(-a, n, idgen, g) proc foldAbs(a: Int128, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode = if a != firstOrd(g.config, n.typ): result = newIntNodeT(abs(a), n, idgen, g) proc foldMul(a, b: Int128, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode = let res = a * b if checkInRange(g.config, n, res): return newIntNodeT(res, n, idgen, g) proc ordinalValToString*(a: PNode; g: ModuleGraph): string = # because $ has the param ordinal[T], `a` is not necessarily an enum, but an # ordinal var x = getInt(a) var t = skipTypes(a.typ, abstractRange) case t.kind of tyChar: result = $chr(toInt64(x) and 0xff) of tyEnum: var n = t.n for i in 0.. argB: argA else: argB, n, idgen, g) of mShlI: case skipTypes(n.typ, abstractRange).kind of tyInt8: result = newIntNodeT(toInt128(toInt8(getInt(a)) shl toInt64(getInt(b))), n, idgen, g) of tyInt16: result = newIntNodeT(toInt128(toInt16(getInt(a)) shl toInt64(getInt(b))), n, idgen, g) of tyInt32: result = newIntNodeT(toInt128(toInt32(getInt(a)) shl toInt64(getInt(b))), n, idgen, g) of tyInt64: result = newIntNodeT(toInt128(toInt64(getInt(a)) shl toInt64(getInt(b))), n, idgen, g) of tyInt: if g.config.target.intSize == 4: result = newIntNodeT(toInt128(toInt32(getInt(a)) shl toInt64(getInt(b))), n, idgen, g) else: result = newIntNodeT(toInt128(toInt64(getInt(a)) shl toInt64(getInt(b))), n, idgen, g) of tyUInt8: result = newIntNodeT(toInt128(toUInt8(getInt(a)) shl toInt64(getInt(b))), n, idgen, g) of tyUInt16: result = newIntNodeT(toInt128(toUInt16(getInt(a)) shl toInt64(getInt(b))), n, idgen, g) of tyUInt32: result = newIntNodeT(toInt128(toUInt32(getInt(a)) shl toInt64(getInt(b))), n, idgen, g) of tyUInt64: result = newIntNodeT(toInt128(toUInt64(getInt(a)) shl toInt64(getInt(b))), n, idgen, g) of tyUInt: if g.config.target.intSize == 4: result = newIntNodeT(toInt128(toUInt32(getInt(a)) shl toInt64(getInt(b))), n, idgen, g) else: result = newIntNodeT(toInt128(toUInt64(getInt(a)) shl toInt64(getInt(b))), n, idgen, g) else: internalError(g.config, n.info, "constant folding for shl") of mShrI: var a = cast[uint64](getInt(a)) let b = cast[uint64](getInt(b)) # To support the ``-d:nimOldShiftRight`` flag, we need to mask the # signed integers to cut off the extended sign bit in the internal # representation. if 0'u64 < b: # do not cut off the sign extension, when there is # no bit shifting happening. case skipTypes(n.typ, abstractRange).kind of tyInt8: a = a and 0xff'u64 of tyInt16: a = a and 0xffff'u64 of tyInt32: a = a and 0xffffffff'u64 of tyInt: if g.config.target.intSize == 4: a = a and 0xffffffff'u64 else: # unsigned and 64 bit integers don't need masking discard let c = cast[BiggestInt](a shr b) result = newIntNodeT(toInt128(c), n, idgen, g) of mAshrI: case skipTypes(n.typ, abstractRange).kind of tyInt8: result = newIntNodeT(toInt128(ashr(toInt8(getInt(a)), toInt8(getInt(b)))), n, idgen, g) of tyInt16: result = newIntNodeT(toInt128(ashr(toInt16(getInt(a)), toInt16(getInt(b)))), n, idgen, g) of tyInt32: result = newIntNodeT(toInt128(ashr(toInt32(getInt(a)), toInt32(getInt(b)))), n, idgen, g) of tyInt64, tyInt: result = newIntNodeT(toInt128(ashr(toInt64(getInt(a)), toInt64(getInt(b)))), n, idgen, g) else: internalError(g.config, n.info, "constant folding for ashr") of mDivI: let argA = getInt(a) let argB = getInt(b) if argB != Zero and (argA != firstOrd(g.config, n.typ) or argB != NegOne): result = newIntNodeT(argA div argB, n, idgen, g) of mModI: let argA = getInt(a) let argB = getInt(b) if argB != Zero and (argA != firstOrd(g.config, n.typ) or argB != NegOne): result = newIntNodeT(argA mod argB, n, idgen, g) of mAddF64: result = newFloatNodeT(getFloat(a) + getFloat(b), n, g) of mSubF64: result = newFloatNodeT(getFloat(a) - getFloat(b), n, g) of mMulF64: result = newFloatNodeT(getFloat(a) * getFloat(b), n, g) of mDivF64: result = newFloatNodeT(getFloat(a) / getFloat(b), n, g) of mIsNil: result = newIntNodeT(toInt128(ord(a.kind == nkNilLit)), n, idgen, g) of mLtI, mLtB, mLtEnum, mLtCh: result = newIntNodeT(toInt128(ord(getOrdValue(a) < getOrdValue(b))), n, idgen, g) of mLeI, mLeB, mLeEnum, mLeCh: result = newIntNodeT(toInt128(ord(getOrdValue(a) <= getOrdValue(b))), n, idgen, g) of mEqI, mEqB, mEqEnum, mEqCh: result = newIntNodeT(toInt128(ord(getOrdValue(a) == getOrdValue(b))), n, idgen, g) of mLtF64: result = newIntNodeT(toInt128(ord(getFloat(a) < getFloat(b))), n, idgen, g) of mLeF64: result = newIntNodeT(toInt128(ord(getFloat(a) <= getFloat(b))), n, idgen, g) of mEqF64: result = newIntNodeT(toInt128(ord(getFloat(a) == getFloat(b))), n, idgen, g) of mLtStr: result = newIntNodeT(toInt128(ord(getStr(a) < getStr(b))), n, idgen, g) of mLeStr: result = newIntNodeT(toInt128(ord(getStr(a) <= getStr(b))), n, idgen, g) of mEqStr: result = newIntNodeT(toInt128(ord(getStr(a) == getStr(b))), n, idgen, g) of mLtU: result = newIntNodeT(toInt128(ord(`<%`(toInt64(getOrdValue(a)), toInt64(getOrdValue(b))))), n, idgen, g) of mLeU: result = newIntNodeT(toInt128(ord(`<=%`(toInt64(getOrdValue(a)), toInt64(getOrdValue(b))))), n, idgen, g) of mBitandI, mAnd: result = newIntNodeT(bitand(a.getInt, b.getInt), n, idgen, g) of mBitorI, mOr: result = newIntNodeT(bitor(getInt(a), getInt(b)), n, idgen, g) of mBitxorI, mXor: result = newIntNodeT(bitxor(getInt(a), getInt(b)), n, idgen, g) of mAddU: let val = maskBytes(getInt(a) + getInt(b), int(n.typ.size)) result = newIntNodeT(val, n, idgen, g) of mSubU: let val = maskBytes(getInt(a) - getInt(b), int(n.typ.size)) result = newIntNodeT(val, n, idgen, g) # echo "subU: ", val, " n: ", n, " result: ", val of mMulU: let val = maskBytes(getInt(a) * getInt(b), int(n.typ.size)) result = newIntNodeT(val, n, idgen, g) of mModU: let argA = maskBytes(getInt(a), int(a.typ.size)) let argB = maskBytes(getInt(b), int(a.typ.size)) if argB != Zero: result = newIntNodeT(argA mod argB, n, idgen, g) of mDivU: let argA = maskBytes(getInt(a), int(a.typ.size)) let argB = maskBytes(getInt(b), int(a.typ.size)) if argB != Zero: result = newIntNodeT(argA div argB, n, idgen, g) of mLeSet: result = newIntNodeT(toInt128(ord(containsSets(g.config, a, b))), n, idgen, g) of mEqSet: result = newIntNodeT(toInt128(ord(equalSets(g.config, a, b))), n, idgen, g) of mLtSet: result = newIntNodeT(toInt128(ord( containsSets(g.config, a, b) and not equalSets(g.config, a, b))), n, idgen, g) of mMulSet: result = nimsets.intersectSets(g.config, a, b) result.info = n.info of mPlusSet: result = nimsets.unionSets(g.config, a, b) result.info = n.info of mMinusSet: result = nimsets.diffSets(g.config, a, b) result.info = n.info of mConStrStr: result = newStrNodeT(getStrOrChar(a) & getStrOrChar(b), n, g) of mInSet: result = newIntNodeT(toInt128(ord(inSet(a, b))), n, idgen, g) of mRepr: # BUGFIX: we cannot eval mRepr here for reasons that I forgot. discard of mIntToStr, mInt64ToStr: result = newStrNodeT($(getOrdValue(a)), n, g) of mBoolToStr: if getOrdValue(a) == 0: result = newStrNodeT("false", n, g) else: result = newStrNodeT("true", n, g) of mFloatToStr: result = newStrNodeT($getFloat(a), n, g) of mCStrToStr, mCharToStr: if a.kind == nkBracket: var s = "" for b in a.sons: s.add b.getStrOrChar result = newStrNodeT(s, n, g) else: result = newStrNodeT(getStrOrChar(a), n, g) of mStrToStr: result = newStrNodeT(getStrOrChar(a), n, g) of mEnumToStr: result = newStrNodeT(ordinalValToString(a, g), n, g) of mArrToSeq: result = copyTree(a) result.typ = n.typ of mCompileOption: result = newIntNodeT(toInt128(ord(commands.testCompileOption(g.config, a.getStr, n.info))), n, idgen, g) of mCompileOptionArg: result = newIntNodeT(toInt128(ord( testCompileOptionArg(g.config, getStr(a), getStr(b), n.info))), n, idgen, g) of mEqProc: result = newIntNodeT(toInt128(ord( exprStructuralEquivalent(a, b, strictSymEquality=true))), n, idgen, g) else: discard proc getConstIfExpr(c: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode = result = nil for i in 0.. 2: b = getConstExpr(m, n[2], idgen, g) if b == nil: return if n.len > 3: c = getConstExpr(m, n[3], idgen, g) if c == nil: return result = evalOp(s.magic, n, a, b, c, idgen, g) proc getAppType(n: PNode; g: ModuleGraph): PNode = if g.config.globalOptions.contains(optGenDynLib): result = newStrNodeT("lib", n, g) elif g.config.globalOptions.contains(optGenStaticLib): result = newStrNodeT("staticlib", n, g) elif g.config.globalOptions.contains(optGenGuiApp): result = newStrNodeT("gui", n, g) else: result = newStrNodeT("console", n, g) proc rangeCheck(n: PNode, value: Int128; g: ModuleGraph) = if value < firstOrd(g.config, n.typ) or value > lastOrd(g.config, n.typ): localError(g.config, n.info, "cannot convert " & $value & " to " & typeToString(n.typ)) proc foldConv(n, a: PNode; idgen: IdGenerator; g: ModuleGraph; check = false): PNode = let dstTyp = skipTypes(n.typ, abstractRange - {tyTypeDesc}) let srcTyp = skipTypes(a.typ, abstractRange - {tyTypeDesc}) # if srcTyp.kind == tyUInt64 and "FFFFFF" in $n: # echo "n: ", n, " a: ", a # echo "from: ", srcTyp, " to: ", dstTyp, " check: ", check # echo getInt(a) # echo high(int64) # writeStackTrace() case dstTyp.kind of tyBool: case srcTyp.kind of tyFloat..tyFloat64: result = newIntNodeT(toInt128(getFloat(a) != 0.0), n, idgen, g) of tyChar, tyUInt..tyUInt64, tyInt..tyInt64: result = newIntNodeT(toInt128(a.getOrdValue != 0), n, idgen, g) of tyBool, tyEnum: # xxx shouldn't we disallow `tyEnum`? result = a result.typ = n.typ else: doAssert false, $srcTyp.kind of tyInt..tyInt64, tyUInt..tyUInt64: case srcTyp.kind of tyFloat..tyFloat64: result = newIntNodeT(toInt128(getFloat(a)), n, idgen, g) of tyChar, tyUInt..tyUInt64, tyInt..tyInt64: var val = a.getOrdValue if check: rangeCheck(n, val, g) result = newIntNodeT(val, n, idgen, g) if dstTyp.kind in {tyUInt..tyUInt64}: result.transitionIntKind(nkUIntLit) else: result = a result.typ = n.typ if check and result.kind in {nkCharLit..nkUInt64Lit}: rangeCheck(n, getInt(result), g) of tyFloat..tyFloat64: case srcTyp.kind of tyInt..tyInt64, tyEnum, tyBool, tyChar: result = newFloatNodeT(toFloat64(getOrdValue(a)), n, g) else: result = a result.typ = n.typ of tyOpenArray, tyVarargs, tyProc, tyPointer: discard else: result = a result.typ = n.typ proc getArrayConstr(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode = if n.kind == nkBracket: result = n else: result = getConstExpr(m, n, idgen, g) if result == nil: result = n proc foldArrayAccess(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode = var x = getConstExpr(m, n[0], idgen, g) if x == nil or x.typ.skipTypes({tyGenericInst, tyAlias, tySink}).kind == tyTypeDesc: return var y = getConstExpr(m, n[1], idgen, g) if y == nil: return var idx = toInt64(getOrdValue(y)) case x.kind of nkPar, nkTupleConstr: if idx >= 0 and idx < x.len: result = x.sons[idx] if result.kind == nkExprColonExpr: result = result[1] else: localError(g.config, n.info, formatErrorIndexBound(idx, x.len-1) & $n) of nkBracket: idx -= toInt64(firstOrd(g.config, x.typ)) if idx >= 0 and idx < x.len: result = x[int(idx)] else: localError(g.config, n.info, formatErrorIndexBound(idx, x.len-1) & $n) of nkStrLit..nkTripleStrLit: result = newNodeIT(nkCharLit, x.info, n.typ) if idx >= 0 and idx < x.strVal.len: result.intVal = ord(x.strVal[int(idx)]) else: localError(g.config, n.info, formatErrorIndexBound(idx, x.strVal.len-1) & $n) else: discard proc foldFieldAccess(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode = # a real field access; proc calls have already been transformed var x = getConstExpr(m, n[0], idgen, g) if x == nil or x.kind notin {nkObjConstr, nkPar, nkTupleConstr}: return var field = n[1].sym for i in ord(x.kind == nkObjConstr).. 0) and (n[0].kind == nkExprColonExpr): for i, expr in n.pairs: let exprNew = copyNode(expr) # nkExprColonExpr exprNew.add expr[0] let a = getConstExpr(m, expr[1], idgen, g) if a == nil: return nil exprNew.add a result.add exprNew else: for i, expr in n.pairs: let a = getConstExpr(m, expr, idgen, g) if a == nil: return nil result.add a incl(result.flags, nfAllConst) of nkChckRangeF, nkChckRange64, nkChckRange: var a = getConstExpr(m, n[0], idgen, g) if a == nil: return if leValueConv(n[1], a) and leValueConv(a, n[2]): result = a # a <= x and x <= b result.typ = n.typ else: localError(g.config, n.info, "conversion from $1 to $2 is invalid" % [typeToString(n[0].typ), typeToString(n.typ)]) of nkStringToCString, nkCStringToString: var a = getConstExpr(m, n[0], idgen, g) if a == nil: return result = a result.typ = n.typ of nkHiddenStdConv, nkHiddenSubConv, nkConv: var a = getConstExpr(m, n[1], idgen, g) if a == nil: return result = foldConv(n, a, idgen, g, check=true) of nkDerefExpr, nkHiddenDeref: let a = getConstExpr(m, n[0], idgen, g) if a != nil and a.kind == nkNilLit: result = nil #localError(g.config, n.info, "nil dereference is not allowed") of nkCast: var a = getConstExpr(m, n[1], idgen, g) if a == nil: return if n.typ != nil and n.typ.kind in NilableTypes: # we allow compile-time 'cast' for pointer types: result = a result.typ = n.typ of nkBracketExpr: result = foldArrayAccess(m, n, idgen, g) of nkDotExpr: result = foldFieldAccess(m, n, idgen, g) of nkCheckedFieldExpr: assert n[0].kind == nkDotExpr result = foldFieldAccess(m, n[0], idgen, g) of nkStmtListExpr: var i = 0 while i <= n.len - 2: if n[i].kind in {nkComesFrom, nkCommentStmt, nkEmpty}: i.inc else: break if i == n.len - 1: result = getConstExpr(m, n[i], idgen, g) else: discard