Skip to content

Commit

Permalink
track introduced locals in vmgen for eval check (nim-lang#24674)
Browse files Browse the repository at this point in the history
fixes nim-lang#8758, fixes nim-lang#10828, fixes nim-lang#12172, fixes nim-lang#21610, fixes nim-lang#23803,
fixes nim-lang#24633, fixes nim-lang#24634, succeeds nim-lang#24085

We simply track the symbol ID of every traversed `var`/`let` definition
in `vmgen`, then these symbols are always considered evaluable in the
current `vmgen` context. The set of symbols is reset before every
generation, but both tests worked properly without doing this including
the nested `const`, so maybe it's already done in some way I'm not
seeing.
  • Loading branch information
metagn authored Feb 14, 2025
1 parent b211ada commit a5cc33c
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 3 deletions.
5 changes: 4 additions & 1 deletion compiler/vm.nim
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import semmacrosanity
import
std/[strutils, tables, parseutils],
std/[strutils, tables, intsets, parseutils],
msgs, vmdef, vmgen, nimsets, types,
parser, vmdeps, idents, trees, renderer, options, transf,
gorgeimpl, lineinfos, btrees, macrocacheimpl,
Expand Down Expand Up @@ -2425,9 +2425,12 @@ proc evalConstExprAux(module: PSym; idgen: IdGenerator;
setupGlobalCtx(module, g, idgen)
var c = PCtx g.vm
let oldMode = c.mode
let oldLocals = c.locals
c.mode = mode
c.locals = initIntSet()
c.cannotEval = false
let start = genExpr(c, n, requiresValue = mode!=emStaticStmt)
c.locals = oldLocals
if c.cannotEval:
return errorNode(idgen, prc, n)
if c.code[start].opcode == opcEof: return newNodeI(nkEmpty, n.info)
Expand Down
3 changes: 2 additions & 1 deletion compiler/vmdef.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
## This module contains the type definitions for the new evaluation engine.
## An instruction is 1-3 int32s in memory, it is a register based VM.

import std/[tables, strutils]
import std/[tables, strutils, intsets]

import ast, idents, options, modulegraphs, lineinfos

Expand Down Expand Up @@ -272,6 +272,7 @@ type
vmstateDiff*: seq[(PSym, PNode)] # we remember the "diff" to global state here (feature for IC)
procToCodePos*: Table[int, int]
cannotEval*: bool
locals*: IntSet

PStackFrame* = ref TStackFrame
TStackFrame* {.acyclic.} = object
Expand Down
3 changes: 2 additions & 1 deletion compiler/vmgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,7 @@ proc checkCanEval(c: PCtx; n: PNode) =
# are in the right scope:
if sfGenSym in s.flags and c.prc.sym == nil: discard
elif s.kind == skParam and s.typ.kind == tyTypeDesc: discard
elif s.kind in {skVar, skLet} and s.id in c.locals: discard
else: cannotEval(c, n)
elif s.kind in {skProc, skFunc, skConverter, skMethod,
skIterator} and sfForward in s.flags:
Expand Down Expand Up @@ -1975,7 +1976,7 @@ proc genVarSection(c: PCtx; n: PNode) =
c.gen(lowerTupleUnpacking(c.graph, a, c.idgen, c.getOwner))
elif a[0].kind == nkSym:
let s = a[0].sym
checkCanEval(c, a[0])
c.locals.incl(s.id)
if s.isGlobal:
let runtimeAccessToCompileTime = c.mode == emRepl and
sfCompileTime in s.flags and s.position > 0
Expand Down
79 changes: 79 additions & 0 deletions tests/vm/tconststaticvar.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
block: # issue #8758
template baz() =
var i = 0

proc foo() =
static:
var i = 0
baz()

block: # issue #10828
proc test(i: byte): bool =
const SET = block: # No issues when defined outside proc
var s: set[byte]
for i in 0u8 .. 255u8: incl(s, i)
s
return i in SET
doAssert test(0)
doAssert test(127)
doAssert test(255)

block: # issue #12172
const TEST = block:
var test: array[5, string]
for i in low(test)..high(test):
test[i] = $i
test
proc test =
const TEST2 = block:
var test: array[5, string] # Error here
for i in low(test)..high(test):
test[i] = $i
test
doAssert TEST == TEST2
doAssert TEST == @["0", "1", "2", "3", "4"]
doAssert TEST2 == @["0", "1", "2", "3", "4"]
test()

block: # issue #21610
func stuff(): int =
const r = block:
var r = 1 # Error: cannot evaluate at compile time: r
for i in 2..10:
r *= i
r
r
doAssert stuff() == 3628800

block: # issue #23803
func foo1(c: int): int {.inline.} =
const arr = block:
var res: array[0..99, int]
res[42] = 43
res
arr[c]
doAssert foo1(41) == 0
doAssert foo1(42) == 43
doAssert foo1(43) == 0

# works
func foo2(c: int): int {.inline.} =
func initArr(): auto =
var res: array[0..99, int]
res[42] = 43
res
const arr = initArr()
arr[c]
doAssert foo2(41) == 0
doAssert foo2(42) == 43
doAssert foo2(43) == 0

# also works
const globalArr = block:
var res: array[0..99, int]
res[42] = 43
res
func foo3(c: int): int {.inline.} = globalArr[c]
doAssert foo3(41) == 0
doAssert foo3(42) == 43
doAssert foo3(43) == 0
13 changes: 13 additions & 0 deletions tests/vm/tconststaticvar2.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# issue #24634

type J = object

template m(u: J): int =
let v = u
0

proc g() =
const x = J()
const _ = m(x)

g()
9 changes: 9 additions & 0 deletions tests/vm/tconststaticvar3.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# issue #24633

import std/sequtils

proc f(a: static openArray[int]) =
const s1 = a.mapIt(it)
const s2 = a.toSeq()

f([1,2,3])
6 changes: 6 additions & 0 deletions tests/vm/tconststaticvar_wrong.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
proc test =
const TEST = block:
let i = 1
const j = i + 1 #[tt.Error
^ cannot evaluate at compile time: i]#
j

0 comments on commit a5cc33c

Please sign in to comment.