summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2023-10-12 23:33:38 +0200
committerGitHub <noreply@github.com>2023-10-12 23:33:38 +0200
commit8990626ca9715a3687b28331aee4ccf242997aa2 (patch)
tree792a4d375eb3ad77cb9764001029b56c62c2416b
parentd790112ea4600c847fed830171333fde308421a3 (diff)
downloadNim-8990626ca9715a3687b28331aee4ccf242997aa2.tar.gz
NIR: progress (#22817)
Done:

- [x] Implement conversions to openArray/varargs.
- [x] Implement index/range checking.
-rw-r--r--compiler/ast.nim2
-rw-r--r--compiler/ccgexprs.nim3
-rw-r--r--compiler/cgen.nim2
-rw-r--r--compiler/jsgen.nim2
-rw-r--r--compiler/lambdalifting.nim8
-rw-r--r--compiler/nir/ast2ir.nim277
-rw-r--r--compiler/nir/nirinsts.nim3
-rw-r--r--compiler/nir/nirtypes.nim5
-rw-r--r--compiler/nir/types2ir.nim6
-rw-r--r--compiler/optimizer.nim8
-rw-r--r--compiler/sempass2.nim4
-rw-r--r--compiler/transf.nim52
-rw-r--r--compiler/trees.nim3
-rw-r--r--compiler/vm.nim2
-rw-r--r--compiler/vmgen.nim2
-rw-r--r--lib/system/strs_v2.nim3
16 files changed, 281 insertions, 101 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 4fe72929f..3017aedcf 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -2157,7 +2157,7 @@ proc toHumanStr*(kind: TTypeKind): string =
   ## strips leading `tk`
   result = toHumanStrImpl(kind, 2)
 
-proc skipAddr*(n: PNode): PNode {.inline.} =
+proc skipHiddenAddr*(n: PNode): PNode {.inline.} =
   (if n.kind == nkHiddenAddr: n[0] else: n)
 
 proc isNewStyleConcept*(n: PNode): bool {.inline.} =
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index 44d04d763..eca6fa995 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -2279,9 +2279,6 @@ proc binaryFloatArith(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
   else:
     binaryArith(p, e, d, m)
 
-proc skipAddr(n: PNode): PNode =
-  result = if n.kind in {nkAddr, nkHiddenAddr}: n[0] else: n
-
 proc genWasMoved(p: BProc; n: PNode) =
   var a: TLoc
   let n1 = n[1].skipAddr
diff --git a/compiler/cgen.nim b/compiler/cgen.nim
index adee8f845..a2b2c429e 100644
--- a/compiler/cgen.nim
+++ b/compiler/cgen.nim
@@ -1162,7 +1162,7 @@ proc genProcAux*(m: BModule, prc: PSym) =
   var returnStmt: Rope = ""
   assert(prc.ast != nil)
 
-  var procBody = transformBody(m.g.graph, m.idgen, prc, dontUseCache)
+  var procBody = transformBody(m.g.graph, m.idgen, prc, {})
   if sfInjectDestructors in prc.flags:
     procBody = injectDestructorCalls(m.g.graph, m.idgen, prc, procBody)
 
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index 1720de17f..6b57c097f 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -2718,7 +2718,7 @@ proc genProc(oldProc: PProc, prc: PSym): Rope =
     else:
       returnStmt = "return $#;$n" % [a.res]
 
-  var transformedBody = transformBody(p.module.graph, p.module.idgen, prc, dontUseCache)
+  var transformedBody = transformBody(p.module.graph, p.module.idgen, prc, {})
   if sfInjectDestructors in prc.flags:
     transformedBody = injectDestructorCalls(p.module.graph, p.module.idgen, prc, transformedBody)
 
diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim
index ee19eec08..fdba7ba3d 100644
--- a/compiler/lambdalifting.nim
+++ b/compiler/lambdalifting.nim
@@ -449,7 +449,7 @@ proc detectCapturedVars(n: PNode; owner: PSym; c: var DetectionPass) =
     if innerProc:
       if s.isIterator: c.somethingToDo = true
       if not c.processed.containsOrIncl(s.id):
-        let body = transformBody(c.graph, c.idgen, s, useCache)
+        let body = transformBody(c.graph, c.idgen, s, {useCache})
         detectCapturedVars(body, s, c)
     let ow = s.skipGenericOwner
     let innerClosure = innerProc and s.typ.callConv == ccClosure and not s.isIterator
@@ -755,7 +755,7 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: var DetectionPass;
         #  echo renderTree(s.getBody, {renderIds})
         let oldInContainer = c.inContainer
         c.inContainer = 0
-        var body = transformBody(d.graph, d.idgen, s, dontUseCache)
+        var body = transformBody(d.graph, d.idgen, s, {})
         body = liftCapturedVars(body, s, d, c)
         if c.envVars.getOrDefault(s.id).isNil:
           s.transformedBody = body
@@ -879,7 +879,7 @@ proc liftIterToProc*(g: ModuleGraph; fn: PSym; body: PNode; ptrType: PType;
   fn.typ.callConv = oldCC
 
 proc liftLambdas*(g: ModuleGraph; fn: PSym, body: PNode; tooEarly: var bool;
-                  idgen: IdGenerator, force: bool): PNode =
+                  idgen: IdGenerator; flags: TransformFlags): PNode =
   # XXX backend == backendJs does not suffice! The compiletime stuff needs
   # the transformation even when compiling to JS ...
 
@@ -888,7 +888,7 @@ proc liftLambdas*(g: ModuleGraph; fn: PSym, body: PNode; tooEarly: var bool;
 
   if body.kind == nkEmpty or (
       g.config.backend == backendJs and not isCompileTime) or
-      (fn.skipGenericOwner.kind != skModule and not force):
+      (fn.skipGenericOwner.kind != skModule and force notin flags):
 
     # ignore forward declaration:
     result = body
diff --git a/compiler/nir/ast2ir.nim b/compiler/nir/ast2ir.nim
index bc7348be3..fcda145ea 100644
--- a/compiler/nir/ast2ir.nim
+++ b/compiler/nir/ast2ir.nim
@@ -45,6 +45,7 @@ type
     locGen: int
     m: ModuleCon
     prc: PSym
+    options: TOptions
 
 proc initModuleCon*(graph: ModuleGraph; config: ConfigRef; idgen: IdGenerator; module: PSym): ModuleCon =
   result = ModuleCon(graph: graph, types: initTypesCon(config), slotGenerator: new(int),
@@ -61,7 +62,9 @@ proc initModuleCon*(graph: ModuleGraph; config: ConfigRef; idgen: IdGenerator; m
     result.nativeUIntId = UInt16Id
 
 proc initProcCon*(m: ModuleCon; prc: PSym; config: ConfigRef): ProcCon =
-  ProcCon(m: m, sm: initSlotManager({}, m.slotGenerator), prc: prc, config: config)
+  ProcCon(m: m, sm: initSlotManager({}, m.slotGenerator), prc: prc, config: config,
+    options: if prc != nil: prc.options
+             else: config.options)
 
 proc toLineInfo(c: var ProcCon; i: TLineInfo): PackedLineInfo =
   var val: LitId
@@ -476,14 +479,21 @@ proc genField(c: var ProcCon; n: PNode; d: var Value) =
   d.addImmediateVal toLineInfo(c, n.info), pos
 
 proc genIndex(c: var ProcCon; n: PNode; arr: PType; d: var Value) =
+  let info = toLineInfo(c, n.info)
   if arr.skipTypes(abstractInst).kind == tyArray and
       (let x = firstOrd(c.config, arr); x != Zero):
-    let info = toLineInfo(c, n.info)
     buildTyped d, info, Sub, c.m.nativeIntId:
       c.gen(n, d)
       d.addImmediateVal toLineInfo(c, n.info), toInt(x)
   else:
     c.gen(n, d)
+  if optBoundsCheck in c.options:
+    let idx = move d
+    build d, info, CheckedIndex:
+      copyTree d.Tree, idx
+      let x = toInt64 lengthOrd(c.config, arr)
+      d.Tree.addIntVal c.m.integers, info, c.m.nativeIntId, x
+      d.Tree.addLabel info, CheckedGoto, c.exitLabel
 
 proc genNew(c: var ProcCon; n: PNode; needsInit: bool) =
   # If in doubt, always follow the blueprint of the C code generator for `mm:orc`.
@@ -586,6 +596,8 @@ proc genBinaryOp(c: var ProcCon; n: PNode; d: var Value; opc: Opcode) =
   let t = typeToIr(c.m.types, n.typ)
   template body(target) =
     buildTyped target, info, opc, t:
+      if optOverflowCheck in c.options and opc in {CheckedAdd, CheckedSub, CheckedMul, CheckedDiv, CheckedMod}:
+        c.code.addLabel info, CheckedGoto, c.exitLabel
       copyTree target, tmp
       copyTree target, tmp2
   intoDest d, info, t, body
@@ -688,10 +700,16 @@ proc genUnaryMinus(c: var ProcCon; n: PNode; d: var Value) =
   c.freeTemp(tmp)
 
 proc genHigh(c: var ProcCon; n: PNode; d: var Value) =
-  let subOpr = createMagic(c.m.graph, c.m.idgen, "-", mSubI)
-  let lenOpr = createMagic(c.m.graph, c.m.idgen, "len", mLengthOpenArray)
-  let asLenExpr = subOpr.buildCall(lenOpr.buildCall(n[1]), nkIntLit.newIntNode(1))
-  c.gen asLenExpr, d
+  let info = toLineInfo(c, n.info)
+  let t = typeToIr(c.m.types, n.typ)
+  var x = default(Value)
+  genArrayLen(c, n, x)
+  template body(target) =
+    buildTyped target, info, Sub, t:
+      copyTree target, x
+      target.addIntVal(c.m.integers, info, t, 1)
+  intoDest d, info, t, body
+  c.freeTemp x
 
 proc genBinaryCp(c: var ProcCon; n: PNode; d: var Value; compilerProc: string) =
   let info = toLineInfo(c, n.info)
@@ -1365,6 +1383,81 @@ proc genDefault(c: var ProcCon; n: PNode; d: var Value) =
   let m = expandDefault(n.typ, n.info)
   gen c, m, d
 
+proc genWasMoved(c: var ProcCon; n: PNode) =
+  let n1 = n[1].skipAddr
+  # XXX We need a way to replicate this logic or better yet a better
+  # solution for injectdestructors.nim:
+  #if c.withinBlockLeaveActions > 0 and notYetAlive(n1):
+  var d = c.genx(n1)
+  assert not isEmpty(d)
+  let m = expandDefault(n1.typ, n1.info)
+  gen c, m, d
+
+proc genMove(c: var ProcCon; n: PNode; d: var Value) =
+  let info = toLineInfo(c, n.info)
+  let n1 = n[1].skipAddr
+  var a = c.genx(n1)
+  if n.len == 4:
+    # generated by liftdestructors:
+    let src = c.genx(n[2])
+    # if ($1.p == $2.p) goto lab1
+    let lab1 = newLabel(c.labelGen)
+
+    let payloadType = seqPayloadPtrType(c.m.types, n1.typ)
+    buildTyped c.code, info, Select, Bool8Id:
+      buildTyped c.code, info, Eq, payloadType:
+        buildTyped c.code, info, FieldAt, payloadType:
+          copyTree c.code, a
+          c.code.addImmediateVal info, 1 # (len, p)-pair
+        buildTyped c.code, info, FieldAt, payloadType:
+          copyTree c.code, src
+          c.code.addImmediateVal info, 1 # (len, p)-pair
+
+      build c.code, info, SelectPair:
+        build c.code, info, SelectValue:
+          c.code.boolVal(info, true)
+        c.code.gotoLabel info, Goto, lab1
+
+    gen(c, n[3])
+    c.patch n, lab1
+
+    buildTyped c.code, info, Asgn, typeToIr(c.m.types, n1.typ):
+      copyTree c.code, a
+      copyTree c.code, src
+
+  else:
+    if isEmpty(d): d = getTemp(c, n)
+    buildTyped c.code, info, Asgn, typeToIr(c.m.types, n1.typ):
+      copyTree c.code, d
+      copyTree c.code, a
+    var op = getAttachedOp(c.m.graph, n.typ, attachedWasMoved)
+    if op == nil or skipTypes(n1.typ, abstractVar+{tyStatic}).kind in {tyOpenArray, tyVarargs}:
+      let m = expandDefault(n1.typ, n1.info)
+      gen c, m, a
+    else:
+      var opB = c.genx(newSymNode(op))
+      buildTyped c.code, info, Call, typeToIr(c.m.types, n.typ):
+        copyTree c.code, opB
+        buildTyped c.code, info, AddrOf, ptrTypeOf(c.m.types.g, typeToIr(c.m.types, n1.typ)):
+          copyTree c.code, a
+
+proc genDestroy(c: var ProcCon; n: PNode) =
+  let t = n[1].typ.skipTypes(abstractInst)
+  case t.kind
+  of tyString:
+    var unused = default(Value)
+    genUnaryCp(c, n, unused, "nimDestroyStrV1")
+  of tySequence:
+    #[
+    var a = initLocExpr(c, arg)
+    linefmt(c, cpsStmts, "if ($1.p && ($1.p->cap & NIM_STRLIT_FLAG) == 0) {$n" &
+      " #alignedDealloc($1.p, NIM_ALIGNOF($2));$n" &
+      "}$n",
+      [rdLoc(a), getTypeDesc(c.module, t.lastSon)])
+    ]#
+    globalError(c.config, n.info, "not implemented: =destroy for seqs")
+  else: discard "nothing to do"
+
 proc genMagic(c: var ProcCon; n: PNode; d: var Value; m: TMagic) =
   case m
   of mAnd: c.genAndOr(n, opcFJmp, d)
@@ -1391,9 +1484,9 @@ proc genMagic(c: var ProcCon; n: PNode; d: var Value; m: TMagic) =
   of mNewString, mNewStringOfCap, mExit: c.genCall(n, d)
   of mLengthOpenArray, mLengthArray, mLengthSeq, mLengthStr:
     genArrayLen(c, n, d)
-  of mMulI: genBinaryOp(c, n, d, Mul)
-  of mDivI: genBinaryOp(c, n, d, Div)
-  of mModI: genBinaryOp(c, n, d, Mod)
+  of mMulI: genBinaryOp(c, n, d, CheckedMul)
+  of mDivI: genBinaryOp(c, n, d, CheckedDiv)
+  of mModI: genBinaryOp(c, n, d, CheckedMod)
   of mAddF64: genBinaryOp(c, n, d, Add)
   of mSubF64: genBinaryOp(c, n, d, Sub)
   of mMulF64: genBinaryOp(c, n, d, Mul)
@@ -1495,7 +1588,6 @@ proc genMagic(c: var ProcCon; n: PNode; d: var Value; m: TMagic) =
     localError(c.config, n.info, sizeOfLikeMsg("offsetof"))
   of mRunnableExamples:
     discard "just ignore any call to runnableExamples"
-  of mDestroy, mTrace: discard "ignore calls to the default destructor"
   of mOf: genOf(c, n, d)
   of mAppendStrStr:
     unused(c, n, d)
@@ -1522,49 +1614,23 @@ proc genMagic(c: var ProcCon; n: PNode; d: var Value; m: TMagic) =
   of mConStrStr: genStrConcat(c, n, d)
   of mDefault, mZeroDefault:
     genDefault c, n, d
+  of mMove: genMove(c, n, d)
+  of mWasMoved, mReset:
+    unused(c, n, d)
+    genWasMoved(c, n)
+  of mDestroy: genDestroy(c, n)
+  #of mAccessEnv: unaryExpr(d, n, d, "$1.ClE_0")
+  #of mAccessTypeField: genAccessTypeField(c, n, d)
+  #of mSlice: genSlice(c, n, d)
+  of mTrace: discard "no code to generate"
   else:
-    # mGCref, mGCunref,
+    # mGCref, mGCunref: unused by ORC
     globalError(c.config, n.info, "cannot generate code for: " & $m)
 
 #[
 
-  of mReset:
-    unused(c, n, d)
-    var d = c.genx(n[1])
-    # XXX use ldNullOpcode() here?
-    c.gABx(n, opcLdNull, d, c.genType(n[1].typ))
-    c.gABC(n, opcNodeToReg, d, d)
-    c.gABx(n, ldNullOpcode(n.typ), d, c.genType(n.typ))
-
-  of mConStrStr: genVarargsABC(c, n, d, opcConcatStr)
-
   of mRepr: genUnaryABC(c, n, d, opcRepr)
 
-  of mSlice:
-    var
-      d = c.genx(n[1])
-      left = c.genIndex(n[2], n[1].typ)
-      right = c.genIndex(n[3], n[1].typ)
-    if isEmpty(d): d = c.getTemp(n)
-    c.gABC(n, opcNodeToReg, d, d)
-    c.gABC(n, opcSlice, d, left, right)
-    c.freeTemp(left)
-    c.freeTemp(right)
-    c.freeTemp(d)
-
-  of mMove:
-    let arg = n[1]
-    let a = c.genx(arg)
-    if isEmpty(d): d = c.getTemp(arg)
-    gABC(c, arg, whichAsgnOpc(arg, requiresCopy=false), d, a)
-    c.freeTemp(a)
-  of mDup:
-    let arg = n[1]
-    let a = c.genx(arg)
-    if isEmpty(d): d = c.getTemp(arg)
-    gABC(c, arg, whichAsgnOpc(arg, requiresCopy=false), d, a)
-    c.freeTemp(a)
-
   of mNodeId:
     c.genUnaryABC(n, d, opcNodeId)
 
@@ -1764,6 +1830,63 @@ proc genDeref(c: var ProcCon; n: PNode; d: var Value; flags: GenFlags) =
   valueIntoDest c, info, d, n.typ, body
   freeTemp c, tmp
 
+proc addAddrOfFirstElem(c: var ProcCon; target: var Tree; info: PackedLineInfo; tmp: Value; typ: PType) =
+  let arrType = typ.skipTypes(abstractVar)
+  let elemType = arrayPtrTypeOf(c.m.types.g, typeToIr(c.m.types, arrType.lastSon))
+  case arrType.kind
+  of tyString:
+    let t = typeToIr(c.m.types, typ.lastSon)
+    target.addImmediateVal info, 0
+    buildTyped target, info, AddrOf, elemType:
+      buildTyped target, info, ArrayAt, t:
+        buildTyped target, info, FieldAt, strPayloadPtrType(c.m.types):
+          copyTree target, tmp
+          target.addImmediateVal info, 1 # (len, p)-pair
+        target.addIntVal c.m.integers, info, c.m.nativeIntId, 0
+    # len:
+    target.addImmediateVal info, 1
+    buildTyped target, info, FieldAt, c.m.nativeIntId:
+      copyTree target, tmp
+      target.addImmediateVal info, 0 # (len, p)-pair so len is at index 0
+
+  of tySequence:
+    let t = typeToIr(c.m.types, typ.lastSon)
+    target.addImmediateVal info, 0
+    buildTyped target, info, AddrOf, elemType:
+      buildTyped target, info, ArrayAt, t:
+        buildTyped target, info, FieldAt, seqPayloadPtrType(c.m.types, typ):
+          copyTree target, tmp
+          target.addImmediateVal info, 1 # (len, p)-pair
+        target.addIntVal c.m.integers, info, c.m.nativeIntId, 0
+    # len:
+    target.addImmediateVal info, 1
+    buildTyped target, info, FieldAt, c.m.nativeIntId:
+      copyTree target, tmp
+      target.addImmediateVal info, 0 # (len, p)-pair so len is at index 0
+
+  of tyArray:
+    let t = typeToIr(c.m.types, typ.lastSon)
+    target.addImmediateVal info, 0
+    buildTyped target, info, AddrOf, elemType:
+      buildTyped target, info, ArrayAt, t:
+        copyTree target, tmp
+        target.addIntVal c.m.integers, info, c.m.nativeIntId, 0
+    target.addImmediateVal info, 1
+    target.addIntVal(c.m.integers, info, c.m.nativeIntId, toInt lengthOrd(c.config, typ))
+  else:
+    raiseAssert "addAddrOfFirstElem: " & typeToString(typ)
+
+proc genToOpenArrayConv(c: var ProcCon; arg: PNode; d: var Value; flags: GenFlags; destType: PType) =
+  let info = toLineInfo(c, arg.info)
+  let tmp = c.genx(arg, flags)
+  let arrType = destType.skipTypes(abstractVar)
+  template body(target) =
+    buildTyped target, info, ObjConstr, typeToIr(c.m.types, arrType):
+      c.addAddrOfFirstElem target, info, tmp, arg.typ
+
+  valueIntoDest c, info, d, arrType, body
+  freeTemp c, tmp
+
 proc genConv(c: var ProcCon; n, arg: PNode; d: var Value; flags: GenFlags; opc: Opcode) =
   let targetType = n.typ.skipTypes({tyDistinct})
   let argType = arg.typ.skipTypes({tyDistinct})
@@ -1774,6 +1897,11 @@ proc genConv(c: var ProcCon; n, arg: PNode; d: var Value; flags: GenFlags; opc:
     gen c, arg, d
     return
 
+  if opc != Cast and targetType.skipTypes({tyVar, tyLent}).kind in {tyOpenArray, tyVarargs} and
+      argType.skipTypes({tyVar, tyLent}).kind notin {tyOpenArray, tyVarargs}:
+    genToOpenArrayConv c, arg, d, flags, n.typ
+    return
+
   let info = toLineInfo(c, n.info)
   let tmp = c.genx(arg, flags)
   template body(target) =
@@ -1850,7 +1978,7 @@ proc genVarSection(c: var ProcCon; n: PNode) =
           genAsgn2(c, vn, a[2])
       else:
         if a[2].kind == nkEmpty:
-          discard "XXX assign default value to location here"
+          genAsgn2(c, vn, expandDefault(vn.typ, vn.info))
         else:
           genAsgn2(c, vn, a[2])
 
@@ -1922,17 +2050,56 @@ proc genNilLit(c: var ProcCon; n: PNode; d: var Value) =
   valueIntoDest c, info, d, n.typ, body
 
 proc genRangeCheck(c: var ProcCon; n: PNode; d: var Value) =
-  # XXX to implement properly
-  gen c, n[0], d
+  if optRangeCheck in c.options:
+    let info = toLineInfo(c, n.info)
+    let tmp = c.genx n[0]
+    let a = c.genx n[1]
+    let b = c.genx n[2]
+    template body(target) =
+      buildTyped target, info, CheckedRange, typeToIr(c.m.types, n.typ):
+        copyTree target, tmp
+        copyTree target, a
+        copyTree target, b
+        target.addLabel info, CheckedGoto, c.exitLabel
+    valueIntoDest c, info, d, n.typ, body
+    freeTemp c, tmp
+    freeTemp c, a
+    freeTemp c, b
+  else:
+    gen c, n[0], d
+
+type
+  IndexFor = enum
+    ForSeq, ForStr, ForOpenArray
+
+proc genIndexCheck(c: var ProcCon; n: PNode; a: Value; kind: IndexFor): Value =
+  if optBoundsCheck in c.options:
+    let info = toLineInfo(c, n.info)
+    result = default(Value)
+    let idx = genx(c, n)
+    build result, info, CheckedIndex:
+      copyTree result.Tree, idx
+      case kind
+      of ForSeq, ForStr:
+        buildTyped result, info, FieldAt, c.m.nativeIntId:
+          copyTree result.Tree, a
+          result.addImmediateVal info, 0 # (len, p)-pair
+      of ForOpenArray:
+        buildTyped result, info, FieldAt, c.m.nativeIntId:
+          copyTree result.Tree, a
+          result.addImmediateVal info, 1 # (p, len)-pair
+      result.Tree.addLabel info, CheckedGoto, c.exitLabel
+    freeTemp c, idx
+  else:
+    result = genx(c, n)
 
 proc genArrAccess(c: var ProcCon; n: PNode; d: var Value; flags: GenFlags) =
   let arrayKind = n[0].typ.skipTypes(abstractVarRange-{tyTypeDesc}).kind
   let info = toLineInfo(c, n.info)
   case arrayKind
   of tyString:
-    # XXX implement range check
     let a = genx(c, n[0], flags)
-    let b = genx(c, n[1])
+    let b = genIndexCheck(c, n[1], a, ForStr)
     let t = typeToIr(c.m.types, n.typ)
     template body(target) =
       buildTyped target, info, ArrayAt, t:
@@ -1966,9 +2133,8 @@ proc genArrAccess(c: var ProcCon; n: PNode; d: var Value; flags: GenFlags) =
 
     freeTemp c, a
   of tyOpenArray, tyVarargs:
-    # XXX implement range check
     let a = genx(c, n[0], flags)
-    let b = genx(c, n[1])
+    let b = genIndexCheck(c, n[1], a, ForOpenArray)
     let t = typeToIr(c.m.types, n.typ)
     template body(target) =
       buildTyped target, info, ArrayAt, t:
@@ -1981,7 +2147,6 @@ proc genArrAccess(c: var ProcCon; n: PNode; d: var Value; flags: GenFlags) =
     freeTemp c, b
     freeTemp c, a
   of tyArray:
-    # XXX implement range check
     let a = genx(c, n[0], flags)
     var b = default(Value)
     genIndex(c, n[1], n[0].typ, b)
@@ -1995,7 +2160,7 @@ proc genArrAccess(c: var ProcCon; n: PNode; d: var Value; flags: GenFlags) =
     freeTemp c, a
   of tySequence:
     let a = genx(c, n[0], flags)
-    let b = genx(c, n[1])
+    let b = genIndexCheck(c, n[1], a, ForSeq)
     let t = typeToIr(c.m.types, n.typ)
     template body(target) =
       buildTyped target, info, ArrayAt, t:
@@ -2047,7 +2212,7 @@ proc genProc(cOuter: var ProcCon; n: PNode) =
   var c = initProcCon(cOuter.m, prc, cOuter.m.graph.config)
   genParams(c, prc.typ.n)
 
-  let body = transformBody(c.m.graph, c.m.idgen, prc, useCache)
+  let body = transformBody(c.m.graph, c.m.idgen, prc, {useCache, keepOpenArrayConversions})
 
   let info = toLineInfo(c, body.info)
   build c.code, info, ProcDecl:
diff --git a/compiler/nir/nirinsts.nim b/compiler/nir/nirinsts.nim
index f037b4f0e..2c0dc3d11 100644
--- a/compiler/nir/nirinsts.nim
+++ b/compiler/nir/nirinsts.nim
@@ -66,6 +66,9 @@ type
     SetExc,
     TestExc,
 
+    CheckedRange,
+    CheckedIndex,
+
     Call,
     IndirectCall,
     CheckedCall, # call that can raise
diff --git a/compiler/nir/nirtypes.nim b/compiler/nir/nirtypes.nim
index d989397a6..a42feab00 100644
--- a/compiler/nir/nirtypes.nim
+++ b/compiler/nir/nirtypes.nim
@@ -239,6 +239,11 @@ proc ptrTypeOf*(g: var TypeGraph; t: TypeId): TypeId =
   g.addType t
   result = sealType(g, f)
 
+proc arrayPtrTypeOf*(g: var TypeGraph; t: TypeId): TypeId =
+  let f = g.openType AArrayPtrTy
+  g.addType t
+  result = sealType(g, f)
+
 proc toString*(dest: var string; g: TypeGraph; i: TypeId) =
   case g[i].kind
   of VoidTy: dest.add "void"
diff --git a/compiler/nir/types2ir.nim b/compiler/nir/types2ir.nim
index 6d163c6c7..835bef03c 100644
--- a/compiler/nir/types2ir.nim
+++ b/compiler/nir/types2ir.nim
@@ -459,7 +459,11 @@ proc typeToIr*(c: var TypesCon; t: PType): TypeId =
       let a = openType(c.g, LastArrayTy)
       c.g.addType(elemType)
       result = sealType(c.g, a)
-  of tyNone, tyEmpty, tyUntyped, tyTyped, tyTypeDesc,
+  of tyUntyped, tyTyped:
+    # this avoids a special case for system.echo which is not a generic but
+    # uses `varargs[typed]`:
+    result = VoidId
+  of tyNone, tyEmpty, tyTypeDesc,
      tyNil, tyGenericInvocation, tyProxy, tyBuiltInTypeClass,
      tyUserTypeClass, tyUserTypeClassInst, tyCompositeTypeClass,
      tyAnd, tyOr, tyNot, tyAnything, tyConcept, tyIterable, tyForward:
diff --git a/compiler/optimizer.nim b/compiler/optimizer.nim
index 56a0039ba..7e46f3d0b 100644
--- a/compiler/optimizer.nim
+++ b/compiler/optimizer.nim
@@ -66,7 +66,7 @@ proc mergeBasicBlockInfo(parent: var BasicBlock; this: BasicBlock) {.inline.} =
 proc wasMovedTarget(matches: var IntSet; branch: seq[PNode]; moveTarget: PNode): bool =
   result = false
   for i in 0..<branch.len:
-    if exprStructuralEquivalent(branch[i][1].skipAddr, moveTarget,
+    if exprStructuralEquivalent(branch[i][1].skipHiddenAddr, moveTarget,
                                 strictSymEquality = true):
       result = true
       matches.incl i
@@ -76,7 +76,7 @@ proc intersect(summary: var seq[PNode]; branch: seq[PNode]) =
   var i = 0
   var matches = initIntSet()
   while i < summary.len:
-    if wasMovedTarget(matches, branch, summary[i][1].skipAddr):
+    if wasMovedTarget(matches, branch, summary[i][1].skipHiddenAddr):
       inc i
     else:
       summary.del i
@@ -87,7 +87,7 @@ proc intersect(summary: var seq[PNode]; branch: seq[PNode]) =
 proc invalidateWasMoved(c: var BasicBlock; x: PNode) =
   var i = 0
   while i < c.wasMovedLocs.len:
-    if exprStructuralEquivalent(c.wasMovedLocs[i][1].skipAddr, x,
+    if exprStructuralEquivalent(c.wasMovedLocs[i][1].skipHiddenAddr, x,
                                 strictSymEquality = true):
       c.wasMovedLocs.del i
     else:
@@ -96,7 +96,7 @@ proc invalidateWasMoved(c: var BasicBlock; x: PNode) =
 proc wasMovedDestroyPair(c: var Con; b: var BasicBlock; d: PNode) =
   var i = 0
   while i < b.wasMovedLocs.len:
-    if exprStructuralEquivalent(b.wasMovedLocs[i][1].skipAddr, d[1].skipAddr,
+    if exprStructuralEquivalent(b.wasMovedLocs[i][1].skipHiddenAddr, d[1].skipHiddenAddr,
                                 strictSymEquality = true):
       b.wasMovedLocs[i].flags.incl nfMarkForDeletion
       c.somethingTodo = true
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim
index f542a1244..423cfbb34 100644
--- a/compiler/sempass2.nim
+++ b/compiler/sempass2.nim
@@ -993,9 +993,9 @@ proc trackCall(tracked: PEffects; n: PNode) =
           # consider this case: p(out x, x); we want to remark that 'x' is not
           # initialized until after the call. Since we do this after we analysed the
           # call, this is fine.
-          initVar(tracked, n[i].skipAddr, false)
+          initVar(tracked, n[i].skipHiddenAddr, false)
         if strictFuncs in tracked.c.features and not tracked.inEnforcedNoSideEffects and
-           isDangerousLocation(n[i].skipAddr, tracked.owner):
+           isDangerousLocation(n[i].skipHiddenAddr, tracked.owner):
           if sfNoSideEffect in tracked.owner.flags:
             localError(tracked.config, n[i].info,
               "cannot pass $1 to `var T` parameter within a strict func" % renderTree(n[i]))
diff --git a/compiler/transf.nim b/compiler/transf.nim
index 6430243d9..65b4c6c3b 100644
--- a/compiler/transf.nim
+++ b/compiler/transf.nim
@@ -28,10 +28,11 @@ when defined(nimPreviewSlimSystem):
   import std/assertions
 
 type
-  TransformBodyFlag* = enum
-    dontUseCache, useCache
+  TransformFlag* = enum
+    useCache, keepOpenArrayConversions, force
+  TransformFlags* = set[TransformFlag]
 
-proc transformBody*(g: ModuleGraph; idgen: IdGenerator, prc: PSym, flag: TransformBodyFlag, force = false): PNode
+proc transformBody*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; flags: TransformFlags): PNode
 
 import closureiters, lambdalifting
 
@@ -50,9 +51,10 @@ type
     module: PSym
     transCon: PTransCon      # top of a TransCon stack
     inlining: int            # > 0 if we are in inlining context (copy vars)
-    isIntroducingNewLocalVars: bool  # true if we are in `introducingNewLocalVars` (don't transform yields)
     contSyms, breakSyms: seq[PSym]  # to transform 'continue' and 'break'
     deferDetected, tooEarly: bool
+    isIntroducingNewLocalVars: bool  # true if we are in `introducingNewLocalVars` (don't transform yields)
+    flags: TransformFlags
     graph: ModuleGraph
     idgen: IdGenerator
 
@@ -116,7 +118,7 @@ proc transformSymAux(c: PTransf, n: PNode): PNode =
   let s = n.sym
   if s.typ != nil and s.typ.callConv == ccClosure:
     if s.kind in routineKinds:
-      discard transformBody(c.graph, c.idgen, s, useCache)
+      discard transformBody(c.graph, c.idgen, s, {useCache}+c.flags)
     if s.kind == skIterator:
       if c.tooEarly: return n
       else: return liftIterSym(c.graph, n, c.idgen, getCurrOwner(c))
@@ -552,11 +554,14 @@ proc transformConv(c: PTransf, n: PNode): PNode =
     else:
       result = transformSons(c, n)
   of tyOpenArray, tyVarargs:
-    result = transform(c, n[1])
-    #result = transformSons(c, n)
-    result.typ = takeType(n.typ, n[1].typ, c.graph, c.idgen)
-    #echo n.info, " came here and produced ", typeToString(result.typ),
-    #   " from ", typeToString(n.typ), " and ", typeToString(n[1].typ)
+    if keepOpenArrayConversions in c.flags:
+      result = transformSons(c, n)
+    else:
+      result = transform(c, n[1])
+      #result = transformSons(c, n)
+      result.typ = takeType(n.typ, n[1].typ, c.graph, c.idgen)
+      #echo n.info, " came here and produced ", typeToString(result.typ),
+      #   " from ", typeToString(n.typ), " and ", typeToString(n[1].typ)
   of tyCstring:
     if source.kind == tyString:
       result = newTransNode(nkStringToCString, n, 1)
@@ -774,7 +779,7 @@ proc transformFor(c: PTransf, n: PNode): PNode =
       stmtList.add(newAsgnStmt(c, nkFastAsgn, temp, arg, true))
       idNodeTablePut(newC.mapping, formal, temp)
 
-  let body = transformBody(c.graph, c.idgen, iter, useCache)
+  let body = transformBody(c.graph, c.idgen, iter, {useCache}+c.flags)
   pushInfoContext(c.graph.config, n.info)
   inc(c.inlining)
   stmtList.add(transform(c, body))
@@ -1137,13 +1142,8 @@ proc processTransf(c: PTransf, n: PNode, owner: PSym): PNode =
   popTransCon(c)
   incl(result.flags, nfTransf)
 
-proc openTransf(g: ModuleGraph; module: PSym, filename: string; idgen: IdGenerator): PTransf =
-  new(result)
-  result.contSyms = @[]
-  result.breakSyms = @[]
-  result.module = module
-  result.graph = g
-  result.idgen = idgen
+proc openTransf(g: ModuleGraph; module: PSym, filename: string; idgen: IdGenerator; flags: TransformFlags): PTransf =
+  result = PTransf(module: module, graph: g, idgen: idgen, flags: flags)
 
 proc flattenStmts(n: PNode) =
   var goOn = true
@@ -1186,7 +1186,7 @@ template liftDefer(c, root) =
   if c.deferDetected:
     liftDeferAux(root)
 
-proc transformBody*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; flag: TransformBodyFlag, force = false): PNode =
+proc transformBody*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; flags: TransformFlags): PNode =
   assert prc.kind in routineKinds
 
   if prc.transformedBody != nil:
@@ -1195,8 +1195,8 @@ proc transformBody*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; flag: Transfo
     result = getBody(g, prc)
   else:
     prc.transformedBody = newNode(nkEmpty) # protects from recursion
-    var c = openTransf(g, prc.getModule, "", idgen)
-    result = liftLambdas(g, prc, getBody(g, prc), c.tooEarly, c.idgen, force)
+    var c = openTransf(g, prc.getModule, "", idgen, flags)
+    result = liftLambdas(g, prc, getBody(g, prc), c.tooEarly, c.idgen, flags)
     result = processTransf(c, result, prc)
     liftDefer(c, result)
     result = liftLocalsIfRequested(prc, result, g.cache, g.config, c.idgen)
@@ -1206,7 +1206,7 @@ proc transformBody*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; flag: Transfo
 
     incl(result.flags, nfTransf)
 
-    if flag == useCache or prc.typ.callConv == ccInline:
+    if useCache in flags or prc.typ.callConv == ccInline:
       # genProc for inline procs will be called multiple times from different modules,
       # it is important to transform exactly once to get sym ids and locations right
       prc.transformedBody = result
@@ -1217,21 +1217,21 @@ proc transformBody*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; flag: Transfo
   #if prc.name.s == "main":
   #  echo "transformed into ", renderTree(result, {renderIds})
 
-proc transformStmt*(g: ModuleGraph; idgen: IdGenerator; module: PSym, n: PNode): PNode =
+proc transformStmt*(g: ModuleGraph; idgen: IdGenerator; module: PSym, n: PNode; flags: TransformFlags = {}): PNode =
   if nfTransf in n.flags:
     result = n
   else:
-    var c = openTransf(g, module, "", idgen)
+    var c = openTransf(g, module, "", idgen, flags)
     result = processTransf(c, n, module)
     liftDefer(c, result)
     #result = liftLambdasForTopLevel(module, result)
     incl(result.flags, nfTransf)
 
-proc transformExpr*(g: ModuleGraph; idgen: IdGenerator; module: PSym, n: PNode): PNode =
+proc transformExpr*(g: ModuleGraph; idgen: IdGenerator; module: PSym, n: PNode; flags: TransformFlags = {}): PNode =
   if nfTransf in n.flags:
     result = n
   else:
-    var c = openTransf(g, module, "", idgen)
+    var c = openTransf(g, module, "", idgen, flags)
     result = processTransf(c, n, module)
     liftDefer(c, result)
     # expressions are not to be injected with destructor calls as that
diff --git a/compiler/trees.nim b/compiler/trees.nim
index f038fbc1e..e39cbafe6 100644
--- a/compiler/trees.nim
+++ b/compiler/trees.nim
@@ -234,3 +234,6 @@ proc isRunnableExamples*(n: PNode): bool =
   # Templates and generics don't perform symbol lookups.
   result = n.kind == nkSym and n.sym.magic == mRunnableExamples or
     n.kind == nkIdent and n.ident.id == ord(wRunnableExamples)
+
+proc skipAddr*(n: PNode): PNode {.inline.} =
+  result = if n.kind in {nkAddr, nkHiddenAddr}: n[0] else: n
diff --git a/compiler/vm.nim b/compiler/vm.nim
index 85c1305e9..fd03c4bae 100644
--- a/compiler/vm.nim
+++ b/compiler/vm.nim
@@ -1295,7 +1295,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
             let ast = a.sym.ast.shallowCopy
             for i in 0..<a.sym.ast.len:
               ast[i] = a.sym.ast[i]
-            ast[bodyPos] = transformBody(c.graph, c.idgen, a.sym, useCache, force=true)
+            ast[bodyPos] = transformBody(c.graph, c.idgen, a.sym, {useCache, force})
             ast.copyTree()
     of opcSymOwner:
       decodeB(rkNode)
diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim
index 42ce09596..efcd0ec35 100644
--- a/compiler/vmgen.nim
+++ b/compiler/vmgen.nim
@@ -2388,7 +2388,7 @@ proc genProc(c: PCtx; s: PSym): int =
     c.procToCodePos[s.id] = result
     # thanks to the jmp we can add top level statements easily and also nest
     # procs easily:
-    let body = transformBody(c.graph, c.idgen, s, if isCompileTimeProc(s): dontUseCache else: useCache)
+    let body = transformBody(c.graph, c.idgen, s, if isCompileTimeProc(s): {} else: {useCache})
     let procStart = c.xjmp(body, opcJmp, 0)
     var p = PProc(blocks: @[], sym: s)
     let oldPrc = c.prc
diff --git a/lib/system/strs_v2.nim b/lib/system/strs_v2.nim
index e79a2b324..5e4cda186 100644
--- a/lib/system/strs_v2.nim
+++ b/lib/system/strs_v2.nim
@@ -206,6 +206,9 @@ proc nimAddStrV1(s: var NimStringV2; src: NimStringV2) {.compilerRtl, inl.} =
   prepareAdd(s, src.len)
   appendString s, src
 
+proc nimDestroyStrV1(s: NimStringV2) {.compilerRtl, inl.} =
+  frees(s)
+
 func capacity*(self: string): int {.inline.} =
   ## Returns the current capacity of the string.
   # See https://github.com/nim-lang/RFCs/issues/460