Skip to content

Commit

Permalink
feat(gnovm): support constant evaluation of len and cap on arrays (#3600
Browse files Browse the repository at this point in the history
)

Closes: #3201 

This update introduces the ability to evaluate len and cap for arrays at
preprocess-time, allowing these values to be treated as constants. While
the array itself is not constant, the values of len and cap can be
determined during the preprocess. This change eliminates the need for
machine.EvalStatic that causes a vm crash.

---------

Co-authored-by: Lee ByeongJun <[email protected]>
Co-authored-by: Petar Dambovaliev <[email protected]>
  • Loading branch information
3 people authored Feb 4, 2025
1 parent 85a8740 commit 479b314
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 11 deletions.
42 changes: 33 additions & 9 deletions gnovm/pkg/gnolang/preprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -3366,18 +3366,42 @@ func getResultTypedValues(cx *CallExpr) []TypedValue {
// NOTE: Generally, conversion happens in a separate step while leaving
// composite exprs/nodes that contain constant expression nodes (e.g. const
// exprs in the rhs of AssignStmts).
//
// Array-related expressions like `len` and `cap` are manually evaluated
// as constants, even if the array itself is not a constant. This evaluation
// is handled independently of the rest of the constant evaluation process,
// bypassing machine.EvalStatic.
func evalConst(store Store, last BlockNode, x Expr) *ConstExpr {
// TODO: some check or verification for ensuring x
// is constant? From the machine?
m := NewMachine(".dontcare", store)
m.PreprocessorMode = true
var cx *ConstExpr
if clx, ok := x.(*CallExpr); ok {
t := evalStaticTypeOf(store, last, clx.Args[0])
if ar, ok := unwrapPointerType(baseOf(t)).(*ArrayType); ok {
fv := clx.Func.(*ConstExpr).V.(*FuncValue)
switch fv.Name {
case "cap", "len":
tv := TypedValue{T: IntType}
tv.SetInt(ar.Len)
cx = &ConstExpr{
Source: x,
TypedValue: tv,
}
default:
panic(fmt.Sprintf("unexpected const func %s", fv.Name))
}
}
}

cv := m.EvalStatic(last, x)
m.PreprocessorMode = false
m.Release()
cx := &ConstExpr{
Source: x,
TypedValue: cv,
if cx == nil {
// is constant? From the machine?
m := NewMachine(".dontcare", store)
cv := m.EvalStatic(last, x)
m.PreprocessorMode = false
m.Release()
cx = &ConstExpr{
Source: x,
TypedValue: cv,
}
}
cx.SetLine(x.GetLine())
cx.SetAttribute(ATTR_PREPROCESSED, true)
Expand Down
4 changes: 2 additions & 2 deletions gnovm/pkg/gnolang/type_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,15 +270,15 @@ Main:
switch {
case fv.Name == "len":
at := evalStaticTypeOf(store, last, currExpr.Args[0])
if _, ok := baseOf(at).(*ArrayType); ok {
if _, ok := unwrapPointerType(baseOf(at)).(*ArrayType); ok {
// ok
break Main
}
assertValidConstValue(store, last, currExpr.Args[0])
break Main
case fv.Name == "cap":
at := evalStaticTypeOf(store, last, currExpr.Args[0])
if _, ok := baseOf(at).(*ArrayType); ok {
if _, ok := unwrapPointerType(baseOf(at)).(*ArrayType); ok {
// ok
break Main
}
Expand Down
7 changes: 7 additions & 0 deletions gnovm/pkg/gnolang/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1469,6 +1469,13 @@ func baseOf(t Type) Type {
}
}

func unwrapPointerType(t Type) Type {
if pt, ok := t.(*PointerType); ok {
return pt.Elem()
}
return t
}

// NOTE: it may be faster to switch on baseOf().
func (dt *DeclaredType) Kind() Kind {
return dt.Base.Kind()
Expand Down
20 changes: 20 additions & 0 deletions gnovm/tests/files/const51.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

type T1 struct {
x [2]string
}

type T2 struct {
x *[2]string
}

func main() {
t1 := T1{x: [2]string{"a", "b"}}
t2 := T2{x: &[2]string{"a", "b"}}
const c1 = len(t1.x)
const c2 = len(t2.x)
println(c1, c2)
}

// Output:
// 2 2
11 changes: 11 additions & 0 deletions gnovm/tests/files/const52.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

func main() {
s := make([][2]string, 1) // Slice with length 1
s[0] = [2]string{"a", "b"} // Assign value to s[0]
const r = len(s[0])
println(r) // Prints: 2
}

// Output:
// 2

0 comments on commit 479b314

Please sign in to comment.