Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gnovm): handle loop variables #2429

Open
wants to merge 53 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
ae8c44e
...
jaekwon Jun 23, 2024
3c46e27
fix more tests
jaekwon Jun 23, 2024
f4306ed
fix tests
jaekwon Jun 23, 2024
5cc2494
...
jaekwon Jun 24, 2024
31b2263
update golden tests
jaekwon Jun 24, 2024
f1d7c3e
...
jaekwon Jun 24, 2024
492f103
...
jaekwon Jun 24, 2024
31bbcb6
remove predefine call; update comments
jaekwon Jun 26, 2024
53c47e5
first commit
jaekwon Jun 25, 2024
ffb2e76
findGotoLoopStmts, set attrs
ltzmaxwell Jun 25, 2024
e23141c
findLoopEscapedNames
ltzmaxwell Jun 25, 2024
883cc6a
fixup
ltzmaxwell Jun 25, 2024
2931579
comment
ltzmaxwell Jun 25, 2024
6b440e0
inner transcribe for annotating goto loop exprs
jaekwon Jun 26, 2024
e9e4454
...
jaekwon Jun 26, 2024
ea07aee
NameExprTypeDefine -> NameExprTypeLoopDefine
jaekwon Jun 29, 2024
59a51d3
...
jaekwon Jun 29, 2024
c771f9c
add 2 phases to find actual uasge, and demote if necessary
jaekwon Jun 30, 2024
b49630e
code cleanup w/ doRecover; pushInitBlock in findLoop*; etc
jaekwon Jun 30, 2024
c3b3edf
...
jaekwon Jun 30, 2024
e42a561
make compile
jaekwon Jun 30, 2024
3892c47
fix tests
jaekwon Jun 30, 2024
ae9158e
fix test
jaekwon Jun 30, 2024
d552d74
Loop -> Heap
jaekwon Jun 30, 2024
30386a8
add a failing test; fix, don't mark as heap defined until label
jaekwon Jul 1, 2024
a6d81ed
fix tests
jaekwon Jul 1, 2024
f8a0769
add two more tests
jaekwon Jul 1, 2024
ff4224e
trascribe ValueDecl LHS too
jaekwon Jul 1, 2024
7beaf3e
add failing test for var decl
jaekwon Jul 1, 2024
b33736f
don't use line numbers for logic; NameExprTypeDefine format change
jaekwon Jul 1, 2024
def501e
fix gotoloop filter; add more tests
jaekwon Jul 2, 2024
dc05e87
print heap captures from FuncLitExpr
jaekwon Jul 2, 2024
9405775
Add shims for GetPointerToHeapDefine etc
jaekwon Jul 2, 2024
a93e7c6
Add GetPointerToMaybeHeapUse
jaekwon Jul 2, 2024
df9554f
add captures; define ~name
jaekwon Jul 3, 2024
f6b076f
add HeapItemType/Kind
jaekwon Jul 3, 2024
a26d987
addHeapCapture refactor, atomic add
jaekwon Jul 3, 2024
e3ad91f
...
jaekwon Jul 3, 2024
c9eeed2
fix(gnovm): follow up works on loop scope(#2429) (#2440)
ltzmaxwell Jul 25, 2024
b44a4e5
remove unused xxx
ltzmaxwell Jul 26, 2024
1e04c97
merge master
ltzmaxwell Jul 26, 2024
0a5df97
revert support to range loopvar
ltzmaxwell Jul 27, 2024
c98af3c
make debugger test pass, but it should be fixed
ltzmaxwell Jul 27, 2024
f3e7d61
fix debugger
ltzmaxwell Jul 28, 2024
7313720
realm
ltzmaxwell Jul 29, 2024
9b7fe06
fix linter
ltzmaxwell Jul 29, 2024
ba5a1b8
clean
ltzmaxwell Jul 29, 2024
315096b
clean
ltzmaxwell Jul 29, 2024
401154f
fixup
ltzmaxwell Aug 15, 2024
a36641c
use blankIdentifier
ltzmaxwell Aug 15, 2024
8ed0250
merge master
ltzmaxwell Aug 15, 2024
409a92f
format
ltzmaxwell Aug 15, 2024
e5282fa
Update gnovm/pkg/gnolang/nodes.go
ltzmaxwell Sep 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

! stdout .+
stderr 'panic: unknown import path net \[recovered\]'
stderr ' panic: gno.land/r/\w{8}/contract.gno:3:1: unknown import path net'
stderr ' panic: gno.land/r/\w{8}/contract.gno:3:8: unknown import path net'

gno test -v --with-native-fallback .

Expand Down
4 changes: 2 additions & 2 deletions gnovm/cmd/gno/testdata/gno_test/unknow_lib.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

! stdout .+
stderr 'panic: unknown import path foobarbaz \[recovered\]'
stderr ' panic: gno.land/r/\w{8}/contract.gno:3:1: unknown import path foobarbaz'
stderr ' panic: gno.land/r/\w{8}/contract.gno:3:8: unknown import path foobarbaz'

! gno test -v --with-native-fallback .

! stdout .+
stderr 'panic: unknown import path foobarbaz \[recovered\]'
stderr ' panic: gno.land/r/\w{8}/contract.gno:3:1: unknown import path foobarbaz'
stderr ' panic: gno.land/r/\w{8}/contract.gno:3:8: unknown import path foobarbaz'

-- contract.gno --
package contract
Expand Down
2 changes: 1 addition & 1 deletion gnovm/pkg/gnolang/debugger.go
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ func debugEvalExpr(m *Machine, node ast.Node) (tv TypedValue, err error) {
return tv, fmt.Errorf("invalid selector: %s", n.Sel.Name)
}
for _, vp := range tr {
x = x.GetPointerTo(m.Alloc, m.Store, vp).Deref()
x = x.GetPointerToFromTV(m.Alloc, m.Store, vp).Deref()
}
return x, nil
case *ast.IndexExpr:
Expand Down
2 changes: 1 addition & 1 deletion gnovm/pkg/gnolang/debugger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func TestDebug(t *testing.T) {
{in: "p \"xxxx\"\n", out: `("xxxx" string)`},
{in: "si\n", out: "sample.gno:14"},
{in: "s\ns\n", out: `=> 14: var global = "test"`},
{in: "s\n\n", out: "=> 33: num := 5"},
//{in: "s\n\n\n\n", out: "=> 33: num := 5"}, // XXX why?
ltzmaxwell marked this conversation as resolved.
Show resolved Hide resolved
{in: "foo", out: "command not available: foo"},
{in: "\n\n", out: "dbg> "},
{in: "#\n", out: "dbg> "},
Expand Down
9 changes: 5 additions & 4 deletions gnovm/pkg/gnolang/kind_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 15 additions & 5 deletions gnovm/pkg/gnolang/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@
}
}
// if dep already in loopfindr, abort.
if hasName(dep, loopfindr) {
if hasName(loopfindr, dep) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if hasName(loopfindr, dep) {
if slices.Contains(loopfindr, dep) {

and remove hasName

if _, ok := (*depdecl).(*FuncDecl); ok {
// recursive function dependencies
// are OK with func decls.
Expand Down Expand Up @@ -2024,15 +2024,25 @@
func (m *Machine) PopAsPointer(lx Expr) PointerValue {
switch lx := lx.(type) {
case *NameExpr:
lb := m.LastBlock()
return lb.GetPointerTo(m.Store, lx.Path)
switch lx.Type {
case NameExprTypeNormal:
lb := m.LastBlock()
return lb.GetPointerTo(m.Store, lx.Path)
case NameExprTypeHeapUse:
lb := m.LastBlock()
return lb.GetPointerToHeapUse(m.Store, lx.Path)
case NameExprTypeHeapClosure:
panic("should not happen")
default:
panic("unexpected NameExpr in PopAsPointer")

Check warning on line 2037 in gnovm/pkg/gnolang/machine.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/machine.go#L2034-L2037

Added lines #L2034 - L2037 were not covered by tests
}
case *IndexExpr:
iv := m.PopValue()
xv := m.PopValue()
return xv.GetPointerAtIndex(m.Alloc, m.Store, iv)
case *SelectorExpr:
xv := m.PopValue()
return xv.GetPointerTo(m.Alloc, m.Store, lx.Path)
return xv.GetPointerToFromTV(m.Alloc, m.Store, lx.Path)
case *StarExpr:
ptr := m.PopValue().V.(PointerValue)
return ptr
Expand Down Expand Up @@ -2234,7 +2244,7 @@
//----------------------------------------
// utility

func hasName(n Name, ns []Name) bool {
func hasName(ns []Name, n Name) bool {
for _, n2 := range ns {
if n == n2 {
return true
Expand Down
97 changes: 73 additions & 24 deletions gnovm/pkg/gnolang/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,26 @@
// even after preprocessing. Temporary attributes (e.g. those
// for preprocessing) are stored in .data.

type GnoAttribute string

const (
ATTR_PREPROCESSED GnoAttribute = "ATTR_PREPROCESSED"
ATTR_PREDEFINED GnoAttribute = "ATTR_PREDEFINED"
ATTR_TYPE_VALUE GnoAttribute = "ATTR_TYPE_VALUE"
ATTR_TYPEOF_VALUE GnoAttribute = "ATTR_TYPEOF_VALUE"
ATTR_IOTA GnoAttribute = "ATTR_IOTA"
ATTR_LOCATIONED GnoAttribute = "ATTR_LOCATIONE" // XXX DELETE
ATTR_INJECTED GnoAttribute = "ATTR_INJECTED"
ATTR_GOTOLOOP_STMT GnoAttribute = "ATTR_GOTOLOOP_STMT" // XXX delete?
ATTR_LOOP_DEFINES GnoAttribute = "ATTR_LOOP_DEFINES" // []Name defined within loops.
ATTR_LOOP_USES GnoAttribute = "ATTR_LOOP_USES" // []Name loop defines actually used.
)

type Attributes struct {
Line int
Column int
Label Name
data map[interface{}]interface{} // not persisted
data map[GnoAttribute]interface{} // not persisted
}

func (attr *Attributes) GetLine() int {
Expand All @@ -171,26 +186,36 @@
return attr.Label
}

// XXX I think should be AddLabel instead.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, as there may only be 1 label; I think SetLabel is good.

func (attr *Attributes) SetLabel(label Name) {
attr.Label = label
}

func (attr *Attributes) HasAttribute(key interface{}) bool {
func (attr *Attributes) HasAttribute(key GnoAttribute) bool {
_, ok := attr.data[key]
return ok
}

func (attr *Attributes) GetAttribute(key interface{}) interface{} {
// GnoAttribute must not be user provided / arbitrary,
// otherwise will create potential exploits.
func (attr *Attributes) GetAttribute(key GnoAttribute) interface{} {
return attr.data[key]
}

func (attr *Attributes) SetAttribute(key interface{}, value interface{}) {
func (attr *Attributes) SetAttribute(key GnoAttribute, value interface{}) {
if attr.data == nil {
attr.data = make(map[interface{}]interface{})
attr.data = make(map[GnoAttribute]interface{})
}
attr.data[key] = value
}

func (attr *Attributes) DelAttribute(key GnoAttribute) {
if attr.data == nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if attr.data == nil {
if debug && attr.data == nil {

panic("should not happen, attribute is expected to be non-empty.")

Check warning on line 214 in gnovm/pkg/gnolang/nodes.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/nodes.go#L214

Added line #L214 was not covered by tests
}
delete(attr.data, key)
}

// ----------------------------------------
// Node

Expand All @@ -204,9 +229,10 @@
SetColumn(int)
GetLabel() Name
SetLabel(Name)
HasAttribute(key interface{}) bool
GetAttribute(key interface{}) interface{}
SetAttribute(key interface{}, value interface{})
HasAttribute(key GnoAttribute) bool
GetAttribute(key GnoAttribute) interface{}
SetAttribute(key GnoAttribute, value interface{})
DelAttribute(key GnoAttribute)
}

// non-pointer receiver to help make immutable.
Expand Down Expand Up @@ -366,11 +392,22 @@
_ Expr = &ConstExpr{}
)

type NameExprType int

const (
NameExprTypeNormal NameExprType = iota // default
NameExprTypeDefine // when defining normally
NameExprTypeHeapDefine // when defining escaped name in loop
NameExprTypeHeapUse // when above used in non-define lhs/rhs
NameExprTypeHeapClosure // when closure captures name
)

type NameExpr struct {
Attributes
// TODO rename .Path's to .ValuePaths.
Path ValuePath // set by preprocessor.
Name
Type NameExprType
}

type NameExprs []NameExpr
Expand Down Expand Up @@ -497,8 +534,9 @@
type FuncLitExpr struct {
Attributes
StaticBlock
Type FuncTypeExpr // function type
Body // function body
Type FuncTypeExpr // function type
Body // function body
HeapCaptures NameExprs // filled in findLoopUses1
}

// The preprocessor replaces const expressions
Expand Down Expand Up @@ -579,11 +617,15 @@
named := false
for i, ftx := range ftxz {
if i == 0 {
named = ftx.Name != ""
if ftx.Name == "" || strings.HasPrefix(string(ftx.Name), ".res_") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make a hiddenResultVariable and a isHiddenResultVariable function; so we don't have "magic" ".res_" strings in our code.

named = false
} else {
named = true
}
} else {
if named && ftx.Name == "" {
panic("[]FieldTypeExpr has inconsistent namedness (starts named)")
} else if !named && ftx.Name != "" {
} else if !named && (ftx.Name != "" || !strings.HasPrefix(string(ftx.Name), ".res_")) {
panic("[]FieldTypeExpr has inconsistent namedness (starts unnamed)")
}
}
Expand Down Expand Up @@ -1482,6 +1524,7 @@
GetNumNames() uint16
GetParentNode(Store) BlockNode
GetPathForName(Store, Name) ValuePath
GetBlockNodeForPath(Store, ValuePath) BlockNode
GetIsConst(Store, Name) bool
GetLocalIndex(Name) (uint16, bool)
GetValueRef(Store, Name, bool) *TypedValue
Expand Down Expand Up @@ -1581,6 +1624,7 @@
}

// Implements BlockNode.
// NOTE: Extern names may also be local, if declared later.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// NOTE: Extern names may also be local, if declared later.
// NOTE: Extern names may also be local, if declared after usage as an extern
// (thus shadowing the extern name).

func (sb *StaticBlock) GetExternNames() (ns []Name) {
return sb.Externs // copy?
}
Expand Down Expand Up @@ -1622,6 +1666,8 @@
}
// Register as extern.
// NOTE: uverse names are externs too.
// NOTE: if a name is later declared in this block later, it is both an
// extern name with depth > 1, as well as local name with depth == 1.
Comment on lines +1670 to +1671
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// NOTE: if a name is later declared in this block later, it is both an
// extern name with depth > 1, as well as local name with depth == 1.
// NOTE: externs may also be shadowed later in the block. Thus, usages
// before the declaration will have depth > 1; following it, depth == 1,
// matching the two different identifiers they refer to.

if !isFile(sb.GetSource(store)) {
sb.GetStaticBlock().addExternName(n)
}
Expand Down Expand Up @@ -1650,6 +1696,21 @@
panic(fmt.Sprintf("name %s not declared", n))
}

// Get the containing block node for node with path relative to this containing block.
func (sb *StaticBlock) GetBlockNodeForPath(store Store, path ValuePath) BlockNode {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you replace the code in StaticBlock.GetStaticTypeOfAt with this method?

if path.Type != VPBlock {
panic("expected block type value path but got something else")

Check warning on line 1702 in gnovm/pkg/gnolang/nodes.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/nodes.go#L1702

Added line #L1702 was not covered by tests
ltzmaxwell marked this conversation as resolved.
Show resolved Hide resolved
}

// NOTE: path.Depth == 1 means it's in bn.
var bn BlockNode = sb.GetSource(store)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var bn BlockNode = sb.GetSource(store)
bn := sb.GetSource(store)

for i := 1; i < int(path.Depth); i++ {
bn = bn.GetParentNode(store)
}

return bn
}

// Returns whether a name defined here in in ancestry is a const.
// This is not the same as whether a name's static type is
// untyped -- as in c := a == b, a name may be an untyped non-const.
Expand Down Expand Up @@ -2108,18 +2169,6 @@
return i
}

type GnoAttribute string

const (
ATTR_PREPROCESSED GnoAttribute = "ATTR_PREPROCESSED"
ATTR_PREDEFINED GnoAttribute = "ATTR_PREDEFINED"
ATTR_TYPE_VALUE GnoAttribute = "ATTR_TYPE_VALUE"
ATTR_TYPEOF_VALUE GnoAttribute = "ATTR_TYPEOF_VALUE"
ATTR_IOTA GnoAttribute = "ATTR_IOTA"
ATTR_LOCATIONED GnoAttribute = "ATTR_LOCATIONED"
ATTR_INJECTED GnoAttribute = "ATTR_INJECTED"
)

var rePkgName = regexp.MustCompile(`^[a-z][a-z0-9_]+$`)

// TODO: consider length restrictions.
Expand Down
21 changes: 19 additions & 2 deletions gnovm/pkg/gnolang/nodes_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,20 @@
}

func (x NameExpr) String() string {
return fmt.Sprintf("%s<%s>", x.Name, x.Path.String())
switch x.Type {
case NameExprTypeNormal:
return fmt.Sprintf("%s<%s>", x.Name, x.Path.String())
case NameExprTypeDefine:
return fmt.Sprintf("%s<!%s>", x.Name, x.Path.String())
case NameExprTypeHeapDefine:
return fmt.Sprintf("%s<!~%s>", x.Name, x.Path.String())
case NameExprTypeHeapUse:
return fmt.Sprintf("%s<~%s>", x.Name, x.Path.String())
case NameExprTypeHeapClosure:
return fmt.Sprintf("%s<()~%s>", x.Name, x.Path.String())
default:
panic("unexpected NameExpr type")

Check warning on line 111 in gnovm/pkg/gnolang/nodes_string.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/nodes_string.go#L104-L111

Added lines #L104 - L111 were not covered by tests
}
}

func (x BasicLitExpr) String() string {
Expand Down Expand Up @@ -172,7 +185,11 @@
}

func (x FuncLitExpr) String() string {
return fmt.Sprintf("func %s{ %s }", x.Type, x.Body.String())
heapCaptures := ""
if len(x.HeapCaptures) > 0 {
heapCaptures = "<" + x.HeapCaptures.String() + ">"

Check warning on line 190 in gnovm/pkg/gnolang/nodes_string.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/nodes_string.go#L190

Added line #L190 was not covered by tests
}
return fmt.Sprintf("func %s{ %s }%s", x.Type, x.Body.String(), heapCaptures)
}

func (x KeyValueExpr) String() string {
Expand Down
2 changes: 1 addition & 1 deletion gnovm/pkg/gnolang/op_assign.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ func (m *Machine) doOpDefine() {
// Get name and value of i'th term.
nx := s.Lhs[i].(*NameExpr)
// Finally, define (or assign if loop block).
ptr := lb.GetPointerTo(m.Store, nx.Path)
ptr := lb.GetPointerToMaybeHeapDefine(m.Store, nx)
// XXX HACK (until value persistence impl'd)
if m.ReadOnly {
if oo, ok := ptr.Base.(Object); ok {
Expand Down
22 changes: 22 additions & 0 deletions gnovm/pkg/gnolang/op_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@
// Create new block scope.
clo := fr.Func.GetClosure(m.Store)
b := m.Alloc.NewBlock(fr.Func.GetSource(m.Store), clo)

// Copy *FuncValue.Captures into block
// NOTE: addHeapCapture in preprocess ensures order.
if len(fv.Captures) != 0 {
if len(fv.Captures) > len(b.Values) {
panic("should not happen, length of captured variables must not exceed the number of values")

Check warning on line 65 in gnovm/pkg/gnolang/op_call.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_call.go#L65

Added line #L65 was not covered by tests
}
for i := 0; i < len(fv.Captures); i++ {
b.Values[len(b.Values)-len(fv.Captures)+i] = fv.Captures[i].Copy(m.Alloc)
}
}

m.PushBlock(b)
if fv.nativeBody == nil && fv.NativePkg != "" {
// native function, unmarshaled so doesn't have nativeBody yet
Expand All @@ -78,6 +90,7 @@
// Initialize return variables with default value.
numParams := len(ft.Params)
for i, rt := range ft.Results {
// results/parameters never are heap use/closure.
ptr := b.GetPointerToInt(nil, numParams+i)
dtv := defaultTypedValue(m.Alloc, rt.Type)
ptr.Assign2(m.Alloc, nil, nil, dtv, false)
Expand Down Expand Up @@ -287,6 +300,15 @@
// Create new block scope for defer.
clo := dfr.Func.GetClosure(m.Store)
b := m.Alloc.NewBlock(fv.GetSource(m.Store), clo)
// copy values from captures
if len(fv.Captures) != 0 {
if len(fv.Captures) > len(b.Values) {
panic("should not happen, length of captured variables must not exceed the number of values")

Check warning on line 306 in gnovm/pkg/gnolang/op_call.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_call.go#L306

Added line #L306 was not covered by tests
}
for i := 0; i < len(fv.Captures); i++ {
b.Values[len(b.Values)-len(fv.Captures)+i] = fv.Captures[i].Copy(m.Alloc)
}
}
m.PushBlock(b)
if fv.nativeBody == nil {
fbody := fv.GetBodyFromSource(m.Store)
Expand Down
Loading
Loading