diff --git a/examples/gno.land/p/demo/avl/z_0_filetest.gno b/examples/gno.land/p/demo/avl/z_0_filetest.gno index e91788ac8eb..acd3d3d5758 100644 --- a/examples/gno.land/p/demo/avl/z_0_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_0_filetest.gno @@ -280,6 +280,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -316,6 +317,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], diff --git a/examples/gno.land/p/demo/avl/z_1_filetest.gno b/examples/gno.land/p/demo/avl/z_1_filetest.gno index cdd56a5ad89..77726610747 100644 --- a/examples/gno.land/p/demo/avl/z_1_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_1_filetest.gno @@ -304,6 +304,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -340,6 +341,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], diff --git a/gnovm/Makefile b/gnovm/Makefile index cc7154492d8..8c6bac373c0 100644 --- a/gnovm/Makefile +++ b/gnovm/Makefile @@ -60,7 +60,9 @@ _test.gnolang.native:; go test tests/*.go -test.short -run "TestFilesNativ _test.gnolang.stdlibs:; go test tests/*.go -test.short -run 'TestFiles$$/' $(GOTEST_FLAGS) _test.gnolang.native.sync:; go test tests/*.go -test.short -run "TestFilesNative/" --update-golden-tests $(GOTEST_FLAGS) _test.gnolang.stdlibs.sync:; go test tests/*.go -test.short -run 'TestFiles$$/' --update-golden-tests $(GOTEST_FLAGS) +_test.gnolang.debug:; go test tests/*.go -test.short -run 'TestDebug$$/' --update-golden-tests $(GOTEST_FLAGS) +_test.gnolang.sort:; go test tests/*.go -run "TestPackages/(sort)" $(GOTEST_FLAGS) ######################################## # Code gen # TODO: move _dev.stringer to go:generate instructions, simplify generate diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar b/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar index f17f28055f2..7e33972267a 100644 --- a/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar @@ -68,6 +68,7 @@ func main() { // "PkgPath": "gno.land/r/x" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar b/gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar index 38794a3e645..96bb56c4110 100644 --- a/gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar @@ -7,7 +7,7 @@ stderr '=== RUN file/x_filetest.gno' stderr 'panic: fail on x_filetest.gno: diff:' stderr '--- Expected' stderr '\+\+\+ Actual' -stderr '@@ -1 \+1,66 @@' +stderr '@@ -1 \+1,67 @@' stderr '-xxx' stderr '\+switchrealm\["gno.land/r/x"\]' diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar b/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar index e8c643af0ba..ed38562ab09 100644 --- a/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar @@ -83,6 +83,7 @@ func main() { // "PkgPath": "gno.land/r/x" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], diff --git a/gnovm/pkg/gnolang/alloc.go b/gnovm/pkg/gnolang/alloc.go index a83f8102a2b..f19c6b5b8d5 100644 --- a/gnovm/pkg/gnolang/alloc.go +++ b/gnovm/pkg/gnolang/alloc.go @@ -275,6 +275,7 @@ func (alloc *Allocator) NewMap(size int) *MapValue { } func (alloc *Allocator) NewBlock(source BlockNode, parent *Block) *Block { + debug.Printf("---NewBlock, source: %v, parent: %v \n", source, parent) alloc.AllocateBlock(int64(source.GetNumNames())) return NewBlock(source, parent) } diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index a22d335ad3b..91cbc0b0c21 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -718,7 +718,7 @@ func (m *Machine) Eval(x Expr) []TypedValue { // static types and values. func (m *Machine) EvalStatic(last BlockNode, x Expr) TypedValue { if debug { - m.Printf("Machine.EvalStatic(%v, %v)\n", last, x) + //m.Printf("Machine.EvalStatic(%v, %v)\n", last, x) } // X must have been preprocessed. if x.GetAttribute(ATTR_PREPROCESSED) == nil { @@ -1628,7 +1628,7 @@ func (m *Machine) ReapValues(start int) []TypedValue { func (m *Machine) PushBlock(b *Block) { if debug { - m.Println("+B") + m.Printf("+B: %v \n", b) } m.Blocks = append(m.Blocks, b) } diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index b127cd32421..aa683a22cef 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -494,8 +494,25 @@ type KeyValueExprs []KeyValueExpr type FuncLitExpr struct { Attributes StaticBlock - Type FuncTypeExpr // function type - Body // function body + Type FuncTypeExpr // function type + Body // function body + TransientLoopData []*LoopBlockData +} + +func (fx *FuncLitExpr) GetCapturedNxs(store Store) (captures []*NameExpr) { + for _, name := range fx.GetExternNames() { + vp := fx.GetPathForName(store, name) + if vp.Depth == 0 { // skip uverse + continue + } + vp.Depth -= 1 // from the perspective of funcLit block + nx := &NameExpr{ + Name: name, + Path: vp, + } + captures = append(captures, nx) + } + return } // The preprocessor replaces const expressions @@ -870,18 +887,30 @@ type SwitchClauseStmt struct { // ---------------------------------------- // bodyStmt (persistent) +// this assumes that a goto stmt to label ahead +// forms an implicit loop. +// if an funcLitExpr embeded, do capture staff. +type LoopBlockAttr struct { + isLoop bool + start int // line of label start + end int // line of goto stmt +} + // NOTE: embedded in Block. type bodyStmt struct { Attributes - Body // for non-loop stmts - BodyLen int // for for-continue - NextBodyIndex int // init:-2, cond/elem:-1, body:0..., post:n - NumOps int // number of Ops, for goto - NumValues int // number of Values, for goto - NumExprs int // number of Exprs, for goto - NumStmts int // number of Stmts, for goto - Cond Expr // for ForStmt - Post Stmt // for ForStmt + Body // for non-loop stmts + BodyLen int // for for-continue + NextBodyIndex int // init:-2, cond/elem:-1, body:0..., post:n + NumOps int // number of Ops, for goto + NumValues int // number of Values, for goto + NumExprs int // number of Exprs, for goto + NumStmts int // number of Stmts, for goto + Cond Expr // for ForStmt + Post Stmt // for ForStmt + LoopValuesBox *LoopValuesBox // a series of transient values of captured var generated as the iteration goes on + isLoop bool + loopBlockAttr *LoopBlockAttr Active Stmt // for PopStmt() Key Expr // for RangeStmt Value Expr // for RangeStmt @@ -1502,6 +1531,7 @@ func (sb *StaticBlock) revertToOld() { // Implements BlockNode func (sb *StaticBlock) InitStaticBlock(source BlockNode, parent BlockNode) { + debug.Printf("---init static block, source: %v, parent: %v \n", source, parent) if sb.Names != nil || sb.Block.Source != nil { panic("StaticBlock already initialized") } @@ -1512,7 +1542,7 @@ func (sb *StaticBlock) InitStaticBlock(source BlockNode, parent BlockNode) { Parent: nil, } } else { - sb.Block = Block{ + sb.Block = Block{ // build Hierarchical structure in preprocess stage Source: source, Values: nil, Parent: parent.GetStaticBlock().GetBlock(), @@ -1525,6 +1555,22 @@ func (sb *StaticBlock) InitStaticBlock(source BlockNode, parent BlockNode) { return } +func (sb *StaticBlock) dump() { + debug.Println("==============dump staticBlock==============") + for i, n := range sb.Names { + debug.Printf("name[%d] is : %v \n", i, n) + } + for i, c := range sb.Consts { + debug.Printf("const[%d] is : %v \n", i, c) + } + + debug.Printf("block.Source: %v \n", sb.Block.Source) + debug.Printf("block.Parent: %v \n", sb.Block.Parent) + debug.Printf("block: %v \n", sb.Block.String()) + + debug.Println("==============end==============") +} + // Implements BlockNode. func (sb *StaticBlock) IsInitialized() bool { return sb.Block.Source != nil diff --git a/gnovm/pkg/gnolang/op_assign.go b/gnovm/pkg/gnolang/op_assign.go index 0cc30861355..3bdcb8265b4 100644 --- a/gnovm/pkg/gnolang/op_assign.go +++ b/gnovm/pkg/gnolang/op_assign.go @@ -1,10 +1,13 @@ package gnolang func (m *Machine) doOpDefine() { + debug.Println("---doOpDefine") s := m.PopStmt().(*AssignStmt) + debug.Printf("---s: %v \n", s) // Define each value evaluated for Lhs. // NOTE: PopValues() returns a slice in // forward order, not the usual reverse. + // m.PopValue() rvs := m.PopValues(len(s.Lhs)) lb := m.LastBlock() for i := 0; i < len(s.Lhs); i++ { @@ -25,11 +28,14 @@ func (m *Machine) doOpDefine() { } func (m *Machine) doOpAssign() { + debug.Println("---doOpAssign") s := m.PopStmt().(*AssignStmt) + debug.Printf("---s: %v \n", s) // Assign each value evaluated for Lhs. // NOTE: PopValues() returns a slice in // forward order, not the usual reverse. rvs := m.PopValues(len(s.Lhs)) + for i := len(s.Lhs) - 1; 0 <= i; i-- { // Pop lhs value and desired type. lv := m.PopAsPointer(s.Lhs[i]) diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go index eebee978919..fa2a9373874 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -46,6 +46,7 @@ func (m *Machine) doOpPrecall() { var gReturnStmt = &ReturnStmt{} func (m *Machine) doOpCall() { + debug.Println("---doOpCall") // NOTE: Frame won't be popped until the statement is complete, to // discard the correct number of results for func calls in ExprStmts. fr := m.LastFrame() @@ -54,8 +55,59 @@ func (m *Machine) doOpCall() { pts := ft.Params numParams := len(pts) isMethod := 0 // 1 if true - // Create new block scope. - clo := fr.Func.GetClosure(m.Store) + debug.Printf("---fv: %v \n", fv) + + clo := fr.Func.GetClosure(m.Store) // this is "static" + + // update block vars using captured vars + var loopBlock *Block // target loop block with values to be updated + if fv.TransientLoopData != nil { // it captured a bundle of values + debug.Printf("---doOpCall, addr of x.LoopData: %p \n", fv.TransientLoopData) + + if debug { + for i, lbd := range fv.TransientLoopData { + fmt.Printf("========doOpCall, TransientLoopData[%d] is: %s, index: %d \n", i, lbd.loopValuesBox, lbd.index) + } + } + for i, loopData := range fv.TransientLoopData { // each LoopValuesBox is for a certain level of block + box := loopData.loopValuesBox + if box.isSealed { + debug.Println("---isSealed, do update context") + for _, t := range box.transient { // unpack vars belong to a specific block + if debug { + debug.Printf("---len of values: %d \n", len(t.values)) + for k, v := range t.values { + debug.Printf("values[%d] is %v \n", k, v) + } + } + loopBlock = clo.GetBlockWithDepth(m.Store, t.nx.Path) // find target block using depth + // check if exists, should always be? + names := loopBlock.GetSource(m.Store).GetBlockNames() + var index int + var match bool + for l, n := range names { + if n == t.nx.Name { + index = l + match = true + break + } + } + if match { + // update values in context with previously captured. + loopBlock.UpdateValue(index, t.values[loopData.index]) + } + } + } else { // not sealed will be tainted, indicates not sealed with values when loopBlock ends, it's not a closure. + debug.Println("---not sealed, it's tainted") + debug.Printf("---tainted box is: %v \n", fv.TransientLoopData[i].loopValuesBox) + fv.TransientLoopData[i].loopValuesBox.isTainted = true + } + } + fv.TransientLoopData = nil + } + + //debug.Println("---no closure ...") + b := m.Alloc.NewBlock(fr.Func.GetSource(m.Store), clo) m.PushBlock(b) if fv.nativeBody == nil && fv.NativePkg != "" { @@ -67,6 +119,7 @@ func (m *Machine) doOpCall() { } if fv.nativeBody == nil { fbody := fv.GetBodyFromSource(m.Store) + debug.Printf("fbody: %v \n", fbody) if len(ft.Results) == 0 { // Push final empty *ReturnStmt; // TODO: transform in preprocessor instead to return only diff --git a/gnovm/pkg/gnolang/op_eval.go b/gnovm/pkg/gnolang/op_eval.go index 283d035dca2..bbefaedd862 100644 --- a/gnovm/pkg/gnolang/op_eval.go +++ b/gnovm/pkg/gnolang/op_eval.go @@ -18,6 +18,7 @@ func (m *Machine) doOpEval() { debug.Printf("EVAL: (%T) %v\n", x, x) // fmt.Println(m.String()) } + // This case moved out of switch for performance. // TODO: understand this better. if nx, ok := x.(*NameExpr); ok { @@ -307,7 +308,6 @@ func (m *Machine) doOpEval() { m.PushOp(OpEval) case *FuncLitExpr: m.PushOp(OpFuncLit) - // evaluate func type m.PushExpr(&x.Type) m.PushOp(OpEval) case *ConstExpr: diff --git a/gnovm/pkg/gnolang/op_exec.go b/gnovm/pkg/gnolang/op_exec.go index 300303135ad..7ccf9edf798 100644 --- a/gnovm/pkg/gnolang/op_exec.go +++ b/gnovm/pkg/gnolang/op_exec.go @@ -43,6 +43,43 @@ SelectStmt -> */ +func updateCapturedValue(m *Machine, lb *Block) { + debug.Printf("---op_exec, updateCapturedValue, lb: %v \n", lb) + lb.GetBodyStmt().LoopValuesBox = lb.Source.GetStaticBlock().GetBodyStmt().LoopValuesBox + bs := lb.GetBodyStmt() + + debug.Printf("---op_exec, lvBox: %v \n", bs.LoopValuesBox) + + //if bs.LoopValuesBox != nil { + // debug.Printf("---op_exec, lvBox.isFilled: %v \n", bs.LoopValuesBox.isFilled) + // debug.Printf("---op_exec, lvBox.isTainted: %v \n", bs.LoopValuesBox.isTainted) + //} + + if bs.LoopValuesBox != nil && bs.LoopValuesBox.isFilled && !bs.LoopValuesBox.isTainted { + var isSeal bool + for i, tt := range bs.LoopValuesBox.transient { + nvp := lb.Source.GetPathForName(m.Store, tt.nx.Name) + ptr := lb.GetPointerTo(m.Store, nvp) + tv := ptr.Deref() + debug.Printf("---transient value for %s is: %v \n", tt.nx.Name, tv) + // update context use previously recorded value + // inner loops iterates twice while outer loop iterates once. + // it's an alignment for the values of out loop block. + // TODO: hard to understand, more doc or e.g. + expandRatio := bs.LoopValuesBox.transient[i].cursor + 1 - len(bs.LoopValuesBox.transient[i].values) // 2 - 0 + for j := 0; j < expandRatio; j++ { + bs.LoopValuesBox.transient[i].values = append(bs.LoopValuesBox.transient[i].values, tv) + } + isSeal = true + } + if isSeal { + debug.Printf("---seal for: %v \n", bs.LoopValuesBox) + bs.LoopValuesBox.isSealed = true // seal for this LoopValuesBox for closure execution. + } + debug.Printf("---lvBox after updateValue is: %v \n", bs.LoopValuesBox) + } +} + //---------------------------------------- // doOpExec // @@ -51,10 +88,11 @@ SelectStmt -> // operation is that the value of the expression is pushed onto the stack. func (m *Machine) doOpExec(op Op) { + debug.Printf("---doOpExec, op: %v \n", op) s := m.PeekStmt(1) // TODO: PeekStmt1()? if debug { debug.Printf("PEEK STMT: %v\n", s) - debug.Printf("%v\n", m) + //debug.Printf("%v\n", m) } // NOTE this could go in the switch statement, and we could @@ -85,6 +123,7 @@ func (m *Machine) doOpExec(op Op) { return } case OpForLoop: + lb := m.LastBlock() bs := m.LastBlock().GetBodyStmt() // evaluate .Cond. if bs.NextBodyIndex == -2 { // init @@ -114,6 +153,9 @@ func (m *Machine) doOpExec(op Op) { s = next goto EXEC_SWITCH } else if bs.NextBodyIndex == bs.BodyLen { + // exiting loop + debug.Printf("---exiting for loop of bs: %v \n", lb.bodyStmt) + updateCapturedValue(m, lb) // (queue to) go back. if bs.Cond != nil { m.PushExpr(bs.Cond) @@ -206,6 +248,8 @@ func (m *Machine) doOpExec(op Op) { s = next // switch on bs.Active goto EXEC_SWITCH } else if bs.NextBodyIndex == bs.BodyLen { + // update captured values + updateCapturedValue(m, m.LastBlock()) if bs.ListIndex < bs.ListLen-1 { // set up next assign if needed. switch bs.Op { @@ -300,6 +344,7 @@ func (m *Machine) doOpExec(op Op) { s = next // switch on bs.Active goto EXEC_SWITCH } else if bs.NextBodyIndex == bs.BodyLen { + updateCapturedValue(m, m.LastBlock()) if bs.StrIndex < bs.StrLen { // set up next assign if needed. switch bs.Op { @@ -393,6 +438,7 @@ func (m *Machine) doOpExec(op Op) { s = next // switch on bs.Active goto EXEC_SWITCH } else if bs.NextBodyIndex == bs.BodyLen { + updateCapturedValue(m, m.LastBlock()) nnext := bs.NextItem.Next if nnext == nil { // done with range. @@ -493,14 +539,18 @@ EXEC_SWITCH: m.PushExpr(cs.X) m.PushOp(OpEval) case *ForStmt: + debug.Printf("---ForStmt: %v \n", cs) + //debug.Printf("---ForStmt.sb.bs: %v \n", cs.GetStaticBlock().bodyStmt.loopBlockAttr) m.PushFrameBasic(cs) - b := m.Alloc.NewBlock(cs, m.LastBlock()) + // do the copy from preprocess block to runtime block with NewBlock + b := m.Alloc.NewBlock(cs, m.LastBlock()) // runtime block b.bodyStmt = bodyStmt{ Body: cs.Body, BodyLen: len(cs.Body), NextBodyIndex: -2, Cond: cs.Cond, Post: cs.Post, + loopBlockAttr: cs.GetStaticBlock().bodyStmt.loopBlockAttr, // copy from preprocess block } m.PushBlock(b) m.PushOp(OpForLoop) @@ -516,6 +566,7 @@ EXEC_SWITCH: m.PushOp(OpExec) } case *IfStmt: + debug.Printf("---IfStmt: %v \n", cs) b := m.Alloc.NewBlock(cs, m.LastBlock()) m.PushBlock(b) m.PushOp(OpPopBlock) @@ -587,6 +638,7 @@ EXEC_SWITCH: Key: cs.Key, Value: cs.Value, Op: cs.Op, + loopBlockAttr: cs.GetStaticBlock().bodyStmt.loopBlockAttr, } m.PushBlock(b) // TODO: replace with "cs.Op". @@ -620,8 +672,11 @@ EXEC_SWITCH: m.PushExpr(cs.X) m.PushOp(OpEval) case *BranchStmt: + debug.Printf("---BranchStmt, cs: %v \n", cs) switch cs.Op { case BREAK: + last := m.LastBlock() + updateCapturedValue(m, last.GetNearestEnclosingLoopBlock(m.Store)) // Pop frames until for/range // statement (which matches // label, if labeled), and reset. @@ -639,6 +694,7 @@ EXEC_SWITCH: m.PopFrame() } } + case CONTINUE: // TODO document for { @@ -663,18 +719,32 @@ EXEC_SWITCH: } } case GOTO: + debug.Println("---GOTO ", cs.String()) + lb := m.LastBlock() + updateCapturedValue(m, lb.GetNearestEnclosingLoopBlock(m.Store)) + for i := uint8(0); i < cs.Depth; i++ { m.PopBlock() } last := m.LastBlock() + debug.Printf("lb: %v \n", last) + debug.Printf("lb.Source: %v \n", last.Source) + debug.Printf("lb.Source: %T \n", last.Source) bs := last.GetBodyStmt() + debug.Printf("lb.Source isLoop? : %t \n", last.Source.GetStaticBlock().bodyStmt.isLoop) + debug.Printf("bs.isLoop: %v, addr: %p \n", bs.isLoop, &bs) + m.NumOps = bs.NumOps m.NumValues = bs.NumValues m.Exprs = m.Exprs[:bs.NumExprs] m.Stmts = m.Stmts[:bs.NumStmts] bs.NextBodyIndex = cs.BodyIndex + //bs.isLoop = true bs.Active = bs.Body[cs.BodyIndex] // prefill + //updateCapturedValue(m, last) + case FALLTHROUGH: + debug.Println("---FALLTHROUGH") ss, ok := m.LastFrame().Source.(*SwitchStmt) if !ok { // fallthrough is only allowed in a switch statement @@ -769,6 +839,7 @@ EXEC_SWITCH: m.PushStmt(cs.Init) } case *BlockStmt: + debug.Println("---BlockStmt") b := m.Alloc.NewBlock(cs, m.LastBlock()) m.PushBlock(b) m.PushOp(OpPopBlock) diff --git a/gnovm/pkg/gnolang/op_expressions.go b/gnovm/pkg/gnolang/op_expressions.go index b3bf240aea1..aa69cdc7796 100644 --- a/gnovm/pkg/gnolang/op_expressions.go +++ b/gnovm/pkg/gnolang/op_expressions.go @@ -679,21 +679,53 @@ func (m *Machine) doOpStructLit() { func (m *Machine) doOpFuncLit() { x := m.PopExpr().(*FuncLitExpr) - ft := m.PopValue().V.(TypeValue).Type.(*FuncType) + debug.Printf("---doOpFuncLit, x: %v \n", x) + debug.Printf("---doOpFuncLit, get label: %v, getLine: %d \n", x.GetLabel(), x.GetLine()) + lb := m.LastBlock() + + var transientLoopData []*LoopBlockData // for fv to track all transient values + // mutate state of for control + for _, lbd := range x.TransientLoopData { + var cursor int + for _, tst := range lbd.loopValuesBox.transient { + tst.cursor++ // increase cursor of every nameExpr + cursor = tst.cursor + debug.Println("---cursor: ", cursor) + } + // unpack, mutate, pack + lvBox := lbd.loopValuesBox + lvBox.isSealed = false // reset per loop + //lbd.index = lbd.loopValuesBox.transient[0].cursor + debug.Printf("---lvBox: %v \n", lbd.loopValuesBox) + // update + transientLoopData = append(transientLoopData, &LoopBlockData{index: cursor, loopValuesBox: lvBox}) + } + if debug { + for i, lbd := range transientLoopData { + fmt.Printf("========doOpFuncLit, TransientLoopData[%d] is: %s, index: %d \n", i, lbd.loopValuesBox, lbd.index) + } + } + + ft := m.PopValue().V.(TypeValue).Type.(*FuncType) + m.Alloc.AllocateFunc() + + fv := &FuncValue{ + Type: ft, + IsMethod: false, + Source: x, + Name: "", + Closure: lb, + TransientLoopData: transientLoopData, + PkgPath: m.Package.PkgPath, + body: x.Body, + nativeBody: nil, + } + m.PushValue(TypedValue{ T: ft, - V: &FuncValue{ - Type: ft, - IsMethod: false, - Source: x, - Name: "", - Closure: lb, - PkgPath: m.Package.PkgPath, - body: x.Body, - nativeBody: nil, - }, + V: fv, }) } diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index ee943226c85..63792896f2d 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -4,6 +4,7 @@ import ( "fmt" "math/big" "reflect" + "sort" "github.com/gnolang/gno/tm2/pkg/errors" ) @@ -94,12 +95,81 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { } } +func dumpStack(stack []BlockNode) { + if debug { + println("---dumpStack, height of stack is: ", len(stack)) + for i, bn := range stack { + println("=================================") + fmt.Printf("blockNode[%d] type is: %T, value is: %v \n", i, bn, bn) + println("=================================") + } + } +} + +func dumpSnare(snare []BlockNode) { + if debug { + println("---dumpSnare, height of stack is: ", len(snare)) + for i, bn := range snare { + println("=================================") + fmt.Printf("blockNode[%d] type is: %T, value is: %v \n", i, bn, bn) + println("=================================") + } + } +} + +func getBlockNodeAt(stack []BlockNode, n int) BlockNode { + if n > len(stack)-1 { + return nil + } else { + return stack[len(stack)-1-n] + } +} + +// find block node that is loop block which contains name of nx.Name +// the target block node will ref to proper nxs wrapped in a container +// XXX, Note that in preprocess handling, the ref is stored on bn.StaticBlock.Block.BodyStmt +// can bn.StaticBlock.Block be reused in runtime rather than a new one? +func findLoopBlockNodeAndPath(stack []BlockNode, nx *NameExpr) (BlockNode, bool, uint8) { + debug.Printf("---findLoopBlockNodeAndPath, nx: %v \n", nx) + dumpStack(stack) + var gen uint8 = 1 // depth of blockNode + var bn BlockNode + // nav to target level + for i := uint8(0); i < nx.Path.Depth+1; i++ { // find target block at certain depth + bn = getBlockNodeAt(stack, int(i)) + gen++ + if bn == nil { + return nil, false, gen + } + } + + debug.Printf("---got target bn: %v \n", bn) + debug.Printf("---got target bn type: %T \n", bn) + debug.Printf("---got target loopBlockAttr: %v \n", bn.GetStaticBlock().bodyStmt.loopBlockAttr) + + // tmp, if static not nil, means some work done in preprocess, using static + if bn.GetStaticBlock().bodyStmt.loopBlockAttr != nil { + // find name + if bn.GetStaticBlock().bodyStmt.loopBlockAttr.isLoop { + names := bn.GetBlockNames() + for _, name := range names { + if nx.Name == name { // find n in this block + return bn, true, gen + } + } + } + } + return nil, false, gen +} + // This counter ensures (during testing) that certain functions // (like ConvertUntypedTo() for bigints and strings) // are only called during the preprocessing stage. // It is a counter because Preprocess() is recursive. var preprocessing int +//var stack []BlockNode = make([]BlockNode, 0, 32) + // Preprocess n whose parent block node is ctx. If any names // are defined in another file, generally you must call // PredefineFileSet() on the whole fileset first before calling @@ -150,8 +220,11 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { SetNodeLocations(pkgPath, fileName, fn) } - // create stack of BlockNodes. + // for analysis on closure as a special case + var snare []BlockNode = make([]BlockNode, 0, 32) + var stack []BlockNode = make([]BlockNode, 0, 32) + // create stack of BlockNodes. var last BlockNode = ctx lastpn := packageOf(last) stack = append(stack, last) @@ -275,7 +348,11 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_BLOCK ----------------------- case *ForStmt: + debug.Println("---for stmt") pushInitBlock(n, &last, &stack) + n.bodyStmt.loopBlockAttr = &LoopBlockAttr{isLoop: true} + debug.Printf("---sb of ForStmt: %v \n", n.GetStaticBlock()) + n.GetStaticBlock().dump() // TRANS_BLOCK ----------------------- case *IfStmt: @@ -353,6 +430,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } } } + n.bodyStmt.loopBlockAttr = &LoopBlockAttr{isLoop: true} // TRANS_BLOCK ----------------------- case *FuncLitExpr: @@ -360,7 +438,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { ft := evalStaticType(store, last, &n.Type).(*FuncType) // push func body block. pushInitBlock(n, &last, &stack) - // define parameters in new block. for _, p := range ft.Params { last.Define(p.Name, anyValue(p.Type)) } @@ -375,6 +452,8 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { last.Define(Name(rn), anyValue(rf.Type)) } } + // catch funcLit block + snare = append(snare, last) // TRANS_BLOCK ----------------------- case *SelectCaseStmt: @@ -742,6 +821,84 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } } } + case *FuncLitExpr: + debug.Printf("---trans_leave, funcLitExpr: %v \n", n) + // prepare captured named symbols + captures := n.GetCapturedNxs(store) + // sort it so to traverse block in order from inner to outer + // this is for the case of more than one loop block surrounded + sortDepth := func(i, j int) bool { + return int(captures[i].Path.Depth) < int(captures[j].Path.Depth) + } + sort.Slice(captures, sortDepth) + + // loop related vars + var ( + isLoopBlock bool + loopBlockNode BlockNode // e.g. a `for` block that surround funcLit block + lvBox *LoopValuesBox // container per block, to store transient values for captured vars + lastGen, gen uint8 + ) + + // ref captured nx to corresponding loop block, + // it could be a for, range block, or implicit + // loop block with the pattern of goto label + + // when iterating goes on, funcValue will yield several replicas, each of which should capture a slice of + // transient values for a nameExpr. + // e.g. `for i:=0, i++; i<2{ x := i }`, the transient values for x should be [0 ,1], this is recorded and + // used when the funcLit is executed, namely, closure. + // more complex situation like: + // for i:=0, i++; i<2{ x := i for j:=0, j++; j<2{y := j}}, + // in this case, there will be 4 replica of fv, and 3 loopBlock. + // the transient state of x, y is: [0,0], [0,1], [1,0], [1,1]. + // the outer block will hold transient values of [0,0,1,1] for `x`, and inner block 1 will hold [0,1] for `y`, + // another inner block 2 holds [0,1] for y too. + + for _, nx := range captures { + debug.Printf("---loop captures, nx name: %v, nx path: %v \n", nx.Name, nx.Path) + // start search block, in the order of small depth to big depth + loopBlockNode, isLoopBlock, gen = findLoopBlockNodeAndPath(stack, nx) + debug.Printf("isLoopBlock: %v \n", isLoopBlock) + debug.Printf("loopBlockNode: %v \n", loopBlockNode) + debug.Printf("depth: %v \n", gen) + if lastGen == 0 { + lastGen = gen + } else if gen != lastGen { // if enter new level of block, pack last box + lastGen = gen + if lvBox != nil { // closure14.gno + lvBox.isFilled = true + } + } + // prepare lvBox + if isLoopBlock { + lvBox = loopBlockNode.GetStaticBlock().GetBodyStmt().LoopValuesBox + if lvBox == nil { // XXX, closure11_b.gno + lvBox = &LoopValuesBox{} + } + tst := &Transient{ + nx: nx, + cursor: -1, // inc every iteration, and index of transient values, also used as sequence of a fv(s). + } + lvBox.transient = append(lvBox.transient, tst) + lvBox.isFilled = true + + debug.Printf("---fill nx: %v \n", nx) + // ref box from loopBlockNode + loopBlockNode.GetStaticBlock().GetBodyStmt().LoopValuesBox = lvBox + // ref loop block nodes from funcLitExpr, and copy to funcValue later + // so a funcLitExpr will ref to more than one loopBlockNode, and share + // loopValueBox[i] with them, when a loop block switch scope in op_exec, + // the transient state of nx will be recorded to loopBlock(runtime), and + // funcValue, and to replay when doOpCall. + n.TransientLoopData = append(n.TransientLoopData, &LoopBlockData{index: lvBox.transient[0].cursor, loopValuesBox: lvBox}) + } + if debug { + for i, ts := range n.TransientLoopData { + fmt.Printf("========preprocess funcLitExpr, TransientLoopData[%d] is: %s, index: %d \n", i, ts.loopValuesBox, ts.index) + } + } + } // TRANS_LEAVE ----------------------- case *BasicLitExpr: @@ -1636,9 +1793,68 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case BREAK: case CONTINUE: case GOTO: - _, depth, index := findGotoLabel(last, n.Label) + _, depth, index, labelLine := findGotoLabel(last, n.Label) n.Depth = depth n.BodyIndex = index + gotoLine := n.GetLine() + + debug.Printf("---branchStmt, n.Label: %v, n.Line: %d \n", n.GetLabel(), n.GetLine()) + debug.Println("depth:, index:, labelLine:", depth, index, labelLine) + debug.Printf("---BranchStmt, last: %v, %T \n", last, last) + debug.Println("---going to find funcLitExpr blockNode") + dumpSnare(snare) + + // XXX, Hacky way for special case of a goto label forms an implicit loop block + // especially when to goto stmt is after the label, and has + // funcLitExpr embedded in this block. + // XXX, Note that in this case, all logic about capture happens here since + // funcLitExpr is already traversed without knowing if there is + // an outer loopBlock. + // and due to the face blockNode in []stack in popped every time the node + // is done preprocess, so a specified stack call []snare is used to catch + // this pattern. can this be improved? + if labelLine < gotoLine { // only jmp to previous line make it loop? + for i := len(snare) - 1; i >= 0; i-- { + if fx, ok := snare[i].(*FuncLitExpr); ok { + // do staff on fx, copy x here + debug.Printf("---fx: %v, at line of: %d \n", fx, fx.GetLine()) + // do staff with fx + // funcLit in implicit loop block + if labelLine < fx.GetLine() && fx.GetLine() < gotoLine { + debug.Println("---implicit pattern found!") + // prepare captured nxs + captures := fx.GetCapturedNxs(store) + + // find target nx + lvBox := &LoopValuesBox{} + for _, nx := range captures { + names := last.GetBlockNames() + for _, name := range names { + if nx.Name == name { // find nx in this block + tst := &Transient{ + nx: nx, + cursor: -1, // inc every iteration, implies sequence of a fv, and index of transient values. + } + lvBox.transient = append(lvBox.transient, tst) + lvBox.isFilled = true + } + } + } + // ref blockNode to lvBox + // ref fx to blockNodes + // record transient values in lvBox when op_exec + // replay in doOpCall + if lvBox.isFilled { + last.GetStaticBlock().GetBodyStmt().LoopValuesBox = lvBox + fx.TransientLoopData = append(fx.TransientLoopData, &LoopBlockData{index: lvBox.transient[0].cursor, loopValuesBox: lvBox}) + } + } + } + } + + // tag implicit block + snare = snare[:0] + } case FALLTHROUGH: if swchC, ok := last.(*SwitchClauseStmt); ok { // last is a switch clause, find its index in the switch and assign @@ -1937,6 +2153,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // finalization. if _, ok := n.(BlockNode); ok { // Pop block. + debug.Printf("---Pop blockNode: %v \n", stack[len(stack)-1]) stack = stack[:len(stack)-1] last = stack[len(stack)-1] return n, TRANS_CONTINUE @@ -1953,6 +2170,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } func pushInitBlock(bn BlockNode, last *BlockNode, stack *[]BlockNode) { + debug.Printf("---pushInitBlock, source: %v, \n parent: %v \n", bn, *last) if !bn.IsInitialized() { bn.InitStaticBlock(bn, *last) } else { @@ -2221,8 +2439,9 @@ func funcOf(last BlockNode) (BlockNode, *FuncTypeExpr) { } func findGotoLabel(last BlockNode, label Name) ( - bn BlockNode, depth uint8, bodyIdx int, + bn BlockNode, depth uint8, bodyIdx int, line int, ) { + var ls Stmt // labeled stmt for { switch cbn := last.(type) { case *IfStmt, *SwitchStmt: @@ -2234,9 +2453,10 @@ func findGotoLabel(last BlockNode, label Name) ( panic("unexpected package blocknode") case *FuncLitExpr, *FuncDecl: body := cbn.GetBody() - _, bodyIdx = body.GetLabeledStmt(label) + ls, bodyIdx = body.GetLabeledStmt(label) if bodyIdx != -1 { bn = cbn + line = ls.GetLine() return } else { panic(fmt.Sprintf( @@ -2245,9 +2465,10 @@ func findGotoLabel(last BlockNode, label Name) ( } case *BlockStmt, *ForStmt, *IfCaseStmt, *RangeStmt, *SelectCaseStmt, *SwitchClauseStmt: body := cbn.GetBody() - _, bodyIdx = body.GetLabeledStmt(label) + ls, bodyIdx = body.GetLabeledStmt(label) if bodyIdx != -1 { bn = cbn + line = ls.GetLine() return } else { last = cbn.GetParentNode(nil) diff --git a/gnovm/pkg/gnolang/transcribe.go b/gnovm/pkg/gnolang/transcribe.go index c5b72336c83..2d187def6fd 100644 --- a/gnovm/pkg/gnolang/transcribe.go +++ b/gnovm/pkg/gnolang/transcribe.go @@ -260,6 +260,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc cnn.Elts[idx] = KeyValueExpr{Key: k, Value: v} } case *FuncLitExpr: + debug.Printf("---transcribe, funcLitExpr: %v \n", cnn) cnn.Type = *transcribe(t, nns, TRANS_FUNCLIT_TYPE, 0, &cnn.Type, &c).(*FuncTypeExpr) if isStopOrSkip(nc, c) { return @@ -271,6 +272,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } else { cnn = cnn2.(*FuncLitExpr) } + for idx := range cnn.Body { cnn.Body[idx] = transcribe(t, nns, TRANS_FUNCLIT_BODY, idx, cnn.Body[idx], &c).(Stmt) if isBreak(c) { @@ -392,6 +394,13 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } } case *BranchStmt: + //cnn2, c2 := t(ns, ftype, index, cnn, TRANS_BLOCK) + //if isStopOrSkip(nc, c2) { + // nn = cnn2 + // return + //} else { + // cnn = cnn2.(*BranchStmt) + //} case *DeclStmt: for idx := range cnn.Body { cnn.Body[idx] = transcribe(t, nns, TRANS_DECL_BODY, idx, cnn.Body[idx], &c).(SimpleDeclStmt) @@ -594,6 +603,7 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc } else { cnn = cnn2.(*SwitchStmt) } + if cnn.Init != nil { cnn.Init = transcribe(t, nns, TRANS_SWITCH_INIT, 0, cnn.Init, &c).(SimpleStmt) if isStopOrSkip(nc, c) { diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 3607ccc4669..0e62649ae19 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -527,20 +527,74 @@ func (sv *StructValue) Copy(alloc *Allocator) *StructValue { // makes construction TypedValue{T:*FuncType{},V:*FuncValue{}} // faster. type FuncValue struct { - Type Type // includes unbound receiver(s) - IsMethod bool // is an (unbound) method - Source BlockNode // for block mem allocation - Name Name // name of function/method - Closure Value // *Block or RefValue to closure (may be nil for file blocks; lazy) - FileName Name // file name where declared - PkgPath string - NativePkg string // for native bindings through NativeStore - NativeName Name // not redundant with Name; this cannot be changed in userspace + Type Type // includes unbound receiver(s) + IsMethod bool // is an (unbound) method + Source BlockNode // for block mem allocation + Name Name // name of function/method + Closure Value // *Block or RefValue to closure (may be nil for file blocks; lazy) + TransientLoopData []*LoopBlockData + FileName Name // file name where declared + PkgPath string + NativePkg string // for native bindings through NativeStore + NativeName Name // not redundant with Name; this cannot be changed in userspace body []Stmt // function body nativeBody func(*Machine) // alternative to Body } +// LoopBlockData is a wrap of LoopValuesBox, index indicates iteration number of fv. +// there are n replicas of fv generated as the iteration goes on, we have to identify +// which replica is executing thus find proper value in the box to update context(replay). +type LoopBlockData struct { // every fv points to n loopData + index int + loopValuesBox *LoopValuesBox // per loop block +} + +// Transient record all dynamic value for captured variable as iteration goes on. +// e.g. for i:=0;i<2;i++{func(){println(i)}, i will have a [0,1] values. +type Transient struct { + nx *NameExpr + values []TypedValue // one name with multi snapshot + cursor int // cursor per fv to find value in time series +} + +// LoopValuesBox stores a slice of transient values of captured vars +// that generated as the iterations goes on. +// it is used for a closure execution. +type LoopValuesBox struct { + isFilled bool // filled with names, no values + isSealed bool // filled with names and values + isTainted bool // with names but no values. for cases funcLit is called before loop block ends, makes it not a closure. + transient []*Transient +} + +func (tsb *LoopValuesBox) getIndexByName(n Name) int { + debug.Printf("---getIndexByName, target n is: %s, len of transient is: %d \n", n, len(tsb.transient)) + for i, tt := range tsb.transient { + if n == tt.nx.Name { + return i + } + } + return -1 // never reach +} + +func (tsb *LoopValuesBox) String() string { + var s string + s += "\n" + s += "==================LoopValuesBox===================\n" + s += fmt.Sprintf("isFilled: %v \n", tsb.isFilled) + s += fmt.Sprintf("isSealed: %v \n", tsb.isSealed) + for i, t := range tsb.transient { + s += fmt.Sprintf("nx[%d]: %v \n", i, t.nx) + for j, v := range t.values { + s += fmt.Sprintf("values[%d]: %v \n", j, v) + } + s += fmt.Sprintf("cursor: %v \n", t.cursor) + } + s += "====================end======================\n" + return s +} + func (fv *FuncValue) IsNative() bool { if fv.NativePkg == "" && fv.NativeName == "" { return false @@ -2252,6 +2306,17 @@ func NewBlock(source BlockNode, parent *Block) *Block { } } +func (b *Block) UpdateValue(index int, tv TypedValue) { + debug.Printf("---UpdateValue, index: %d \n", index) + debug.Printf("---UpdateValue, tv: %v \n", tv) + debug.Printf("---UpdateValue, block: %v \n", b) + for i := range b.Values { + if i == index { + b.Values[i] = tv + } + } +} + func (b *Block) String() string { return b.StringIndented(" ") } @@ -2263,8 +2328,8 @@ func (b *Block) StringIndented(indent string) string { } lines := []string{} lines = append(lines, - fmt.Sprintf("Block(ID:%v,Addr:%p,Source:%s,Parent:%p)", - b.ObjectInfo.ID, b, source, b.Parent)) // XXX Parent may be RefValue{}. + fmt.Sprintf("Block(ID:%v,HASH:%v,Addr:%p,Source:%s,Parent:%p)", + b.ObjectInfo.ID, b.ObjectInfo.Hash, b, source, b.Parent)) // XXX Parent may be RefValue{}. if b.Source != nil { if _, ok := b.Source.(RefNode); ok { lines = append(lines, @@ -2318,6 +2383,28 @@ func (b *Block) GetPointerToInt(store Store, index int) PointerValue { } } +func (b *Block) GetBlockWithDepth(store Store, path ValuePath) *Block { + for i := uint8(1); i < path.Depth; i++ { + b = b.GetParent(store) + } + return b +} + +func (b *Block) GetNearestEnclosingLoopBlock(store Store) *Block { + debug.Printf("---GetNearestEnclosingLoopBlock, b: %v \n", b) + b = b.GetParent(store) + debug.Printf("--- b: %v \n", b) + debug.Printf("--- b.bodyStmt: %v \n", b.bodyStmt) + debug.Printf("--- b.bodyStmt: %v \n", b.bodyStmt) + for b != nil { + if b.bodyStmt.loopBlockAttr.isLoop { + return b + } + b = b.GetParent(store) + } + return nil +} + func (b *Block) GetPointerTo(store Store, path ValuePath) PointerValue { if path.IsBlockBlankPath() { if debug { diff --git a/gnovm/tests/debug/3_break.gno b/gnovm/tests/debug/3_break.gno new file mode 100644 index 00000000000..0a2a2a5831f --- /dev/null +++ b/gnovm/tests/debug/3_break.gno @@ -0,0 +1,26 @@ +package main + +// this is a fix, intuitive, and same with Go +func main() { + var fns []func() int + + for i := 0; i < 5; i++ { + x := i + f := func() int { + return x + } + fns = append(fns, f) + x += 1 + if i == 2 { + break + } + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 1 +// 2 +// 3 diff --git a/gnovm/tests/debug/3_continue.gno b/gnovm/tests/debug/3_continue.gno new file mode 100644 index 00000000000..d0b8b48e7db --- /dev/null +++ b/gnovm/tests/debug/3_continue.gno @@ -0,0 +1,26 @@ +package main + +// this is a fix, intuitive, and same with Go +func main() { + var fns []func() int + + for i := 0; i < 5; i++ { + x := i + if i <= 2 { + continue + } + + f := func() int { + return x + } + fns = append(fns, f) + x += 1 + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 4 +// 5 diff --git a/gnovm/tests/debug/3_goto.gno b/gnovm/tests/debug/3_goto.gno new file mode 100644 index 00000000000..3f8b29bfd66 --- /dev/null +++ b/gnovm/tests/debug/3_goto.gno @@ -0,0 +1,27 @@ +package main + +// this is a fix, intuitive, and same with Go +func main() { + var fns []func() int + + for i := 0; i < 5; i++ { + x := i + f := func() int { + return x + } + fns = append(fns, f) + x += 1 + if i == 2 { + goto END + } + } +END: + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 1 +// 2 +// 3 diff --git a/gnovm/tests/debug2/1.gno b/gnovm/tests/debug2/1.gno new file mode 100644 index 00000000000..47bf386f606 --- /dev/null +++ b/gnovm/tests/debug2/1.gno @@ -0,0 +1,26 @@ +package main + +func main() { + var y, counter int + var f []func() + defer func() { + for _, ff := range f { + ff() + } + }() + + // this is actually a implicit for loop +LABEL_1: + if counter == 2 { + return + } + x := y + f = append(f, func() { println(x) }) + y++ + counter++ + goto LABEL_1 // this is the edge condition, break? continue? +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/debug2/1a.gnoa b/gnovm/tests/debug2/1a.gnoa new file mode 100644 index 00000000000..eaef398debe --- /dev/null +++ b/gnovm/tests/debug2/1a.gnoa @@ -0,0 +1,23 @@ +package main + +func main() { + var y int + var f []func() + + defer func() { + for _, ff := range f { + ff() + } + }() + + for i := 0; i < 3; i++ { + x := y + f = append(f, func() { println(x) }) + y++ + } +} + +// Output: +// 0 +// 1 +// 2 diff --git a/gnovm/tests/debug2/1a_goto.gno b/gnovm/tests/debug2/1a_goto.gno new file mode 100644 index 00000000000..903e611fc95 --- /dev/null +++ b/gnovm/tests/debug2/1a_goto.gno @@ -0,0 +1,25 @@ +package main + +func main() { + var y, counter int + var f []func() + defer func() { + for _, ff := range f { + ff() + } + }() + + // this is actually a implicit for loop +LABEL_1: + if counter == 2 { + return + } + f = append(f, func() { println(y) }) + y++ + counter++ + goto LABEL_1 // this is the edge condition, break? continue? +} + +// Output: +// 1 +// 2 diff --git a/gnovm/tests/debug2/1b.gno b/gnovm/tests/debug2/1b.gno new file mode 100644 index 00000000000..cc934ed4bf1 --- /dev/null +++ b/gnovm/tests/debug2/1b.gno @@ -0,0 +1,34 @@ +package main + +// invalid goto +func main() { + var y, counter int + var f []func() + defer func() { + for _, ff := range f { + ff() + } + }() + + // this is actually a implicit for loop +LABEL_1: + if counter == 5 { + goto LABEL_2 + } + x := y + f = append(f, func() { println(x) }) + y++ + counter++ + goto LABEL_1 // this is the edge condition, break? continue? + +LABEL_2: + println("end") +} + +// Output: +// end +// 0 +// 1 +// 2 +// 3 +// 4 diff --git a/gnovm/tests/debug2/1b_goto.gno b/gnovm/tests/debug2/1b_goto.gno new file mode 100644 index 00000000000..8e309758aa6 --- /dev/null +++ b/gnovm/tests/debug2/1b_goto.gno @@ -0,0 +1,36 @@ +package main + +func main() { + var y, counter int + + println(&y) + + var f []func() *int + + defer func() { + for _, ff := range f { + println(ff()) + println(*ff()) + } + }() + + // this is actually a implicit for loop +LABEL_1: + if counter == 2 { + return + } + f = append(f, func() *int { + //x := &y + return &y + }) + y++ + counter++ + goto LABEL_1 // this is the edge condition, break? continue? +} + +// Output: +// &0x14000283f80.(*int) +// &0x14000283f80.(*int) +// 1 +// &0x14000283f80.(*int) +// 2 diff --git a/gnovm/tests/debug2/2.gno b/gnovm/tests/debug2/2.gno new file mode 100644 index 00000000000..31d25cf6cb7 --- /dev/null +++ b/gnovm/tests/debug2/2.gno @@ -0,0 +1,34 @@ +package main + +// this is a fix, intuitive, and same with Go +func main() { + var fns []func() int + //defer func() { + // for _, fn := range fns { + // println(fn()) + // } + //}() + + for i := 0; i < 5; i++ { + //defer func() { + // for _, fn := range fns { + // println(fn()) + // } + //}() + x := i + f := func() int { + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 2 +// 3 +// 4 diff --git a/gnovm/tests/debug2/3.gno b/gnovm/tests/debug2/3.gno new file mode 100644 index 00000000000..4fffeb4fea0 --- /dev/null +++ b/gnovm/tests/debug2/3.gno @@ -0,0 +1,25 @@ +package main + +// this is a fix, intuitive, and same with Go +func main() { + var fns []func() int + + for i := 0; i < 5; i++ { + x := i + f := func() int { + return x + } + fns = append(fns, f) + x += 1 + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 1 +// 2 +// 3 +// 4 +// 5 diff --git a/gnovm/tests/debug2/4.gno b/gnovm/tests/debug2/4.gno new file mode 100644 index 00000000000..3835db7d114 --- /dev/null +++ b/gnovm/tests/debug2/4.gno @@ -0,0 +1,26 @@ +package main + +func main() { + var y, counter int + var f []func() + defer func() { + for _, ff := range f { + ff() + } + }() + + // this is actually a implicit for loop +FOO_LABEL: + if counter == 5 { + //for _, ff := range f { + // ff() + //} + return + } + x := y + f = append(f, func() { println(x) }) + x += 1 + y++ + counter++ + goto FOO_LABEL // this is the edge condition, break? continue? +} diff --git a/gnovm/tests/debug2/5.gno b/gnovm/tests/debug2/5.gno new file mode 100644 index 00000000000..d0989412170 --- /dev/null +++ b/gnovm/tests/debug2/5.gno @@ -0,0 +1,33 @@ +package main + +import "fmt" + +func main() { + var counter int + var fns []func() + + defer func() { + for _, f := range fns { + f() + } + }() + +LABEL_1: + if counter >= 2 { + goto LABEL_2 // Jump to LABEL_2 once counter is 2 or more + } + + // capture + fns = append(fns, func() { println(counter) }) + counter++ + goto LABEL_1 // Go back to the beginning of the loop + +LABEL_2: + fmt.Println("Exited loop with counter at:", counter) + // Continue with the rest of the program +} + +// Output: +// Exited loop with counter at: 2 +// 1 +// 2 diff --git a/gnovm/tests/debug2/closure0.gno b/gnovm/tests/debug2/closure0.gno new file mode 100644 index 00000000000..acc1abfd404 --- /dev/null +++ b/gnovm/tests/debug2/closure0.gno @@ -0,0 +1,17 @@ +package main + +type adder func(int, int) int + +func genAdd(k int) adder { + return func(i, j int) int { + return i + j + k + } +} + +func main() { + f := genAdd(5) + println(f(3, 4)) +} + +// Output: +// 12 diff --git a/gnovm/tests/debug2/closure11.gno b/gnovm/tests/debug2/closure11.gno new file mode 100644 index 00000000000..1ea6e013b0d --- /dev/null +++ b/gnovm/tests/debug2/closure11.gno @@ -0,0 +1,25 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + f := func() int { + if true { + if true { + return x + } + } + return 0 + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/debug2/closure11_b.gno b/gnovm/tests/debug2/closure11_b.gno new file mode 100644 index 00000000000..427c6f075f9 --- /dev/null +++ b/gnovm/tests/debug2/closure11_b.gno @@ -0,0 +1,27 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + y := 0 + f := func() int { + if true { + x += y + if true { + return x + } + } + return 0 + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/debug2/closure12_a.gno b/gnovm/tests/debug2/closure12_a.gno new file mode 100644 index 00000000000..1e3eb9fe815 --- /dev/null +++ b/gnovm/tests/debug2/closure12_a.gno @@ -0,0 +1,23 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + f := func() int { + for i := 0; i < 1; i++ { + x++ + } + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 1 +// 2 diff --git a/gnovm/tests/debug2/closure15_a.gno b/gnovm/tests/debug2/closure15_a.gno new file mode 100644 index 00000000000..a30f558350e --- /dev/null +++ b/gnovm/tests/debug2/closure15_a.gno @@ -0,0 +1,50 @@ +package main + +import "fmt" + +// recursive closure does not capture +func main() { + + var fns []func(int) int + var recursiveFunc func(int) int + + for i := 0; i < 3; i++ { + recursiveFunc = func(num int) int { + x := i + println("value of x: ", x) + if num <= 0 { + return 1 + } + return num * recursiveFunc(num-1) + } + + fns = append(fns, recursiveFunc) + } + + for i, r := range fns { + result := r(i) + fmt.Printf("Factorial of %d is: %d \n", i, result) + } +} + +// Go Output: +// value of x: 3 +// Factorial of 0 is: 1 +// value of x: 3 +// value of x: 3 +// Factorial of 1 is: 1 +// value of x: 3 +// value of x: 3 +// value of x: 3 +// Factorial of 2 is: 2 + +// Output: +// value of x: 0 +// Factorial of 0 is: 1 +// value of x: 1 +// value of x: 2 +// Factorial of 1 is: 1 +// value of x: 2 +// value of x: 2 +// value of x: 2 +// Factorial of 2 is: 2 diff --git a/gnovm/tests/debug2/closure16.gno b/gnovm/tests/debug2/closure16.gno new file mode 100644 index 00000000000..8a472ffb410 --- /dev/null +++ b/gnovm/tests/debug2/closure16.gno @@ -0,0 +1,21 @@ +package main + +func main() { + var fns []func() int + s := []int{1, 2, 3} + for i, _ := range s { + x := i + f := func() int { + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 2 diff --git a/gnovm/tests/debug2/closure17_sort_search_efficiency.gno b/gnovm/tests/debug2/closure17_sort_search_efficiency.gno new file mode 100644 index 00000000000..90362944ac5 --- /dev/null +++ b/gnovm/tests/debug2/closure17_sort_search_efficiency.gno @@ -0,0 +1,21 @@ +package main + +func Search(n int, f func(int) bool) int { + f(1) + return 0 +} + +func main() { + for x := 0; x < 2; x++ { + count := 0 + println(" first: count: ", count) + Search(1, func(i int) bool { count++; return i >= x }) + println("second: count: ", count) + } +} + +// Output: +// first: count: 0 +// second: count: 1 +// first: count: 0 +// second: count: 1 diff --git a/gnovm/tests/debug2/closure9.gno b/gnovm/tests/debug2/closure9.gno new file mode 100644 index 00000000000..0de5fc8292e --- /dev/null +++ b/gnovm/tests/debug2/closure9.gno @@ -0,0 +1,26 @@ +package main + +// this is a fix, intuitive, and same with Go +func main() { + var fns []func() int + for i := 0; i < 3; i++ { + x := i + f := func() int { + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Go Output: +// 0 +// 1 +// 2 + +// Output: +// 0 +// 1 +// 2 diff --git a/gnovm/tests/debug2/closure9_g.gno b/gnovm/tests/debug2/closure9_g.gno new file mode 100644 index 00000000000..bd8acbe02a0 --- /dev/null +++ b/gnovm/tests/debug2/closure9_g.gno @@ -0,0 +1,24 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 5; i++ { + x := i + { // another block + f := func() int { + return x + } + fns = append(fns, f) + } + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 2 +// 3 +// 4 diff --git a/gnovm/tests/debug2/closure9_h.gno b/gnovm/tests/debug2/closure9_h.gno new file mode 100644 index 00000000000..23cb5ff9f40 --- /dev/null +++ b/gnovm/tests/debug2/closure9_h.gno @@ -0,0 +1,30 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 2; i++ { + x := i + for j := 0; j < 2; j++ { + y := j + f := func() int { + return x + y + } + fns = append(fns, f) + } + } + for _, fn := range fns { + println(fn()) + } +} + +// Go Output: +// 0 +// 1 +// 1 +// 2 + +// Output: +// 0 +// 1 +// 1 +// 2 diff --git a/gnovm/tests/debug2/closure9_i.gno b/gnovm/tests/debug2/closure9_i.gno new file mode 100644 index 00000000000..42a4d11d862 --- /dev/null +++ b/gnovm/tests/debug2/closure9_i.gno @@ -0,0 +1,31 @@ +package main + +func main() { + var fns []func() int + var x int + for i := 0; i < 2; i++ { + x = i + for j := 0; j < 2; j++ { + y := j + f := func() int { + return x + y + } + fns = append(fns, f) + } + } + for _, fn := range fns { + println(fn()) + } +} + +// Go Output: +// 1 +// 2 +// 1 +// 2 + +// Output: +// 1 +// 2 +// 1 +// 2 diff --git a/gnovm/tests/debug2/declare.gno b/gnovm/tests/debug2/declare.gno new file mode 100644 index 00000000000..761c3029a16 --- /dev/null +++ b/gnovm/tests/debug2/declare.gno @@ -0,0 +1,8 @@ +package main + +func main() { + println(a) + a := 0 +} + +// Output: diff --git a/gnovm/tests/debug2/defer.gno b/gnovm/tests/debug2/defer.gno new file mode 100644 index 00000000000..219888548ac --- /dev/null +++ b/gnovm/tests/debug2/defer.gno @@ -0,0 +1,41 @@ +package main + +// explain: +// in the 1st loop, fns has a fn capture x with value 0, +// and a defer func is placed with refs to fns(it's not captured in face), +// in the 2nd loop, fn captured x with value 1, and fn appended to fns. +// so here, the first defer will print, 0 , 1. +// still in the 2nd loop, a second defer is placed, with fn[0] and fn[1] +// both capture the x in context, whose value is 1, so the second defer +// will print 1, 1 +func main() { + var fns []func() int + + println("start for loop") + for i := 0; i < 2; i++ { + defer func() { // possible resource leak? + println("defer") + for _, fn := range fns { + println(fn()) + } + }() + + x := i + f := func() int { + return x + } + + fns = append(fns, f) + } + println("end for loop") +} + +// Output: +// start for loop +// end for loop +// defer +// 0 +// 1 +// defer +// 1 +// 1 diff --git a/gnovm/tests/debug2/foo1155.gno b/gnovm/tests/debug2/foo1155.gno new file mode 100644 index 00000000000..bd3d5e16a3b --- /dev/null +++ b/gnovm/tests/debug2/foo1155.gno @@ -0,0 +1,25 @@ +package main + +func BalanceOf(i int) int { + return 100 +} + +func main() { + bar := 100 + for i, tc := range []struct { + name string + expected interface{} + fn func() interface{} + }{ + {"BalanceOf(admin, tid1)", 100, func() interface{} { return BalanceOf(bar) }}, + } { + println("hey") + println(i) + println(tc) + } +} + +// Output: +// hey +// 0 +// struct{("BalanceOf(admin, tid1)" string),(100 int),( func()( interface{}))} diff --git a/gnovm/tests/debug2/inc.gno b/gnovm/tests/debug2/inc.gno new file mode 100644 index 00000000000..b45aa0569e3 --- /dev/null +++ b/gnovm/tests/debug2/inc.gno @@ -0,0 +1,10 @@ +package main + +func main() { + x := 1 + x++ + println(x) +} + +// Output: +// 2 diff --git a/gnovm/tests/debug2/return.gno b/gnovm/tests/debug2/return.gno new file mode 100644 index 00000000000..6435588e080 --- /dev/null +++ b/gnovm/tests/debug2/return.gno @@ -0,0 +1,9 @@ +package main + +func main() { + a := 0 + return + println(a) +} + +// Output: diff --git a/gnovm/tests/debug2/shadow.gno b/gnovm/tests/debug2/shadow.gno new file mode 100644 index 00000000000..1b2d40eacdf --- /dev/null +++ b/gnovm/tests/debug2/shadow.gno @@ -0,0 +1,16 @@ +package main + +func main() { + var x int + + if true { + x += 2 + println("inner x: ", x) + } + + println("outer x:", x) +} + +// Output: +// inner x: 2 +// outer x: 2 diff --git a/gnovm/tests/debug2/sort_search_exhausted.gno b/gnovm/tests/debug2/sort_search_exhausted.gno new file mode 100644 index 00000000000..e44a9bf0967 --- /dev/null +++ b/gnovm/tests/debug2/sort_search_exhausted.gno @@ -0,0 +1,20 @@ +package main + +func Search(n int, f func(int) bool) int { + i, j := 0, n + for i := 0; i < j; i++ { + h := int(uint(i+j) >> 1) // avoid overflow when computing h + f(h) + } + return i +} + +func main() { + for size := 0; size < 2; size++ { + for targ := 0; targ <= size; targ++ { + Search(size, func(i int) bool { return i >= targ }) + } + } +} + +// Output: diff --git a/gnovm/tests/debug2/zrealm_avl1.gno b/gnovm/tests/debug2/zrealm_avl1.gno new file mode 100644 index 00000000000..462ff521447 --- /dev/null +++ b/gnovm/tests/debug2/zrealm_avl1.gno @@ -0,0 +1,379 @@ +// PKGPATH: gno.land/r/test +package main + +import ( + "gno.land/p/demo/avl" +) + +var node *avl.Node + +func init() { + node = avl.NewNode("key0", "value0") + node, _ = node.Set("key1", "value1") +} + +func main() { + var updated bool + node, updated = node.Set("key2", "value2") + // println(node, updated) + println(updated, node.Size()) +} + +// Output: +// false 3 + +// Realm: +// switchrealm["gno.land/r/test"] +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key2" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "value2" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key2" +// } +// }, +// {}, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "7a8a63e17a567d7b0891ac89d5cd90072a73787d", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// } +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "ab5a297f4eb033d88bdf1677f4dc151ccb9fde9f", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key1" +// } +// }, +// {}, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AwAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "627e8e517e7ae5db0f3b753e2a32b607989198b6", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// } +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "fe8afd501233fb95375016199f0443b3c6ab1fbc", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// } +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ +// "Blank": {}, +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "IsEscaped": true, +// "ModTime": "6", +// "RefCount": "2" +// }, +// "Parent": null, +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "", +// "Line": "0", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Values": [ +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "10", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "TransientLoopData": null, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "main", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "15", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "TransientLoopData": null, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "c5eefc40ed065461b4a920c1349ed734ffdead8f", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } +// } +// } +// } +// ] +// } +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] diff --git a/gnovm/tests/file_test.go b/gnovm/tests/file_test.go index f070546ab74..10f7c19fef4 100644 --- a/gnovm/tests/file_test.go +++ b/gnovm/tests/file_test.go @@ -32,6 +32,13 @@ func TestFiles(t *testing.T) { runFileTests(t, baseDir, []string{"*_native*"}) } +func TestDebug(t *testing.T) { + // baseDir := filepath.Join(".", "debug2") + baseDir := filepath.Join(".", "debug") + runFileTests(t, baseDir, []string{"*_native*"}) + // runFileTests(t, baseDir, []string{"*_stdlibs*"}, WithNativeLibs()) +} + func TestChallenges(t *testing.T) { baseDir := filepath.Join(".", "challenges") runFileTests(t, baseDir, nil) diff --git a/gnovm/tests/files/closure10.gno b/gnovm/tests/files/closure10.gno new file mode 100644 index 00000000000..1a788d9e0b2 --- /dev/null +++ b/gnovm/tests/files/closure10.gno @@ -0,0 +1,26 @@ +package main + +import "fmt" + +func bar() func() func() int { + x := 1 + + // First level of closure, modifies x + return func() func() int { + //x++ + // Second level of closure, returns x + return func() int { + return x + } + } +} + +func main() { + f := bar() // f is the first-level closure + g := f() // g is the second-level closure, x is incremented here + + fmt.Println(g()) // prints the value of x after being modified by the first-level closure +} + +// Output: +// 1 diff --git a/gnovm/tests/files/closure10_a.gno b/gnovm/tests/files/closure10_a.gno new file mode 100644 index 00000000000..45ad06c6945 --- /dev/null +++ b/gnovm/tests/files/closure10_a.gno @@ -0,0 +1,26 @@ +package main + +import "fmt" + +func bar() func() func() int { + x := 1 + + // First level of closure, modifies x + return func() func() int { + x++ + // Second level of closure, returns x + return func() int { + return x + } + } +} + +func main() { + f := bar() // f is the first-level closure + g := f() // g is the second-level closure, x is incremented here + + fmt.Println(g()) // prints the value of x after being modified by the first-level closure +} + +// Output: +// 2 diff --git a/gnovm/tests/files/closure11.gno b/gnovm/tests/files/closure11.gno new file mode 100644 index 00000000000..1ea6e013b0d --- /dev/null +++ b/gnovm/tests/files/closure11.gno @@ -0,0 +1,25 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + f := func() int { + if true { + if true { + return x + } + } + return 0 + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure11_a.gno b/gnovm/tests/files/closure11_a.gno new file mode 100644 index 00000000000..2ce3df96097 --- /dev/null +++ b/gnovm/tests/files/closure11_a.gno @@ -0,0 +1,23 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + f := func() int { + if true { + return x + } + return 0 + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure11_b.gno b/gnovm/tests/files/closure11_b.gno new file mode 100644 index 00000000000..427c6f075f9 --- /dev/null +++ b/gnovm/tests/files/closure11_b.gno @@ -0,0 +1,27 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + y := 0 + f := func() int { + if true { + x += y + if true { + return x + } + } + return 0 + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure12.gno b/gnovm/tests/files/closure12.gno new file mode 100644 index 00000000000..44717e05cc0 --- /dev/null +++ b/gnovm/tests/files/closure12.gno @@ -0,0 +1,22 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + f := func() int { + { + return x + } + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure12_a.gno b/gnovm/tests/files/closure12_a.gno new file mode 100644 index 00000000000..1e3eb9fe815 --- /dev/null +++ b/gnovm/tests/files/closure12_a.gno @@ -0,0 +1,23 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + f := func() int { + for i := 0; i < 1; i++ { + x++ + } + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 1 +// 2 diff --git a/gnovm/tests/files/closure12_b.gno b/gnovm/tests/files/closure12_b.gno new file mode 100644 index 00000000000..4351ceaaf49 --- /dev/null +++ b/gnovm/tests/files/closure12_b.gno @@ -0,0 +1,24 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + s := []int{1, 2} + f := func() int { + for _, v := range s { + x += v + } + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 3 +// 4 diff --git a/gnovm/tests/files/closure12_c.gno b/gnovm/tests/files/closure12_c.gno new file mode 100644 index 00000000000..3e42e207649 --- /dev/null +++ b/gnovm/tests/files/closure12_c.gno @@ -0,0 +1,27 @@ +package main + +func main() { + var fns []func() int + + for i := 0; i < 2; i++ { + x := i + y := 1 + f := func() int { + switch y { + case 1: + x += 1 + default: + x += 0 + } + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 1 +// 2 diff --git a/gnovm/tests/files/closure12_e.gno b/gnovm/tests/files/closure12_e.gno new file mode 100644 index 00000000000..17490510b26 --- /dev/null +++ b/gnovm/tests/files/closure12_e.gno @@ -0,0 +1,27 @@ +package main + +type queueOnePass struct { + sparse []uint32 + dense []uint32 + size, nextIndex uint32 +} + +func newQueue(size int) (q *queueOnePass) { + return &queueOnePass{ + sparse: make([]uint32, size), + dense: make([]uint32, size), + } +} +func main() { + var ( + visitQueue = newQueue(10) + ) + f := func() { + println(visitQueue.size) + } + + f() +} + +// Output: +// 0 diff --git a/gnovm/tests/files/closure12_f.gno b/gnovm/tests/files/closure12_f.gno new file mode 100644 index 00000000000..60e327bd2b2 --- /dev/null +++ b/gnovm/tests/files/closure12_f.gno @@ -0,0 +1,20 @@ +package main + +func main() { + s := []int{1, 2} + + f := func() { + for i, v := range s { // TODO: exclude s, it's final + println(i) + println(v) + } + } + + f() +} + +// Output: +// 0 +// 1 +// 1 +// 2 diff --git a/gnovm/tests/files/closure12_g.gno b/gnovm/tests/files/closure12_g.gno new file mode 100644 index 00000000000..bf998c163d8 --- /dev/null +++ b/gnovm/tests/files/closure12_g.gno @@ -0,0 +1,14 @@ +package main + +func main() { + f := func(a int) bool { + println(a) + return true + } + + println(f(5)) +} + +// Output: +// 5 +// true diff --git a/gnovm/tests/files/closure12_h.gno b/gnovm/tests/files/closure12_h.gno new file mode 100644 index 00000000000..c9f1f6e97e8 --- /dev/null +++ b/gnovm/tests/files/closure12_h.gno @@ -0,0 +1,25 @@ +package main + +func main() { + s := 1 + f := func() { + i := 0 // no capture for i + var j = s // s should be captured, j not + k := s // s should be captured, k not + m, n := s, 0 + println(i) + println(j) + println(k) + println(m) + println(n) + } + + f() +} + +// Output: +// 0 +// 1 +// 1 +// 1 +// 0 diff --git a/gnovm/tests/files/closure12_i.gno b/gnovm/tests/files/closure12_i.gno new file mode 100644 index 00000000000..acf1b53b32a --- /dev/null +++ b/gnovm/tests/files/closure12_i.gno @@ -0,0 +1,15 @@ +package main + +func main() { + s := []int{1, 2} + + f := func() { + if len(s) == 2 { + println("ok") + } + } + f() +} + +// Output: +// ok diff --git a/gnovm/tests/files/closure13.gno b/gnovm/tests/files/closure13.gno new file mode 100644 index 00000000000..ece19dd87f6 --- /dev/null +++ b/gnovm/tests/files/closure13.gno @@ -0,0 +1,22 @@ +package main + +import "fmt" + +func main() { + // Define a function that returns a closure + var recursiveFunc func(int) int + recursiveFunc = func(num int) int { + if num <= 0 { + return 1 + } + // Closure calling itself recursively + return num * recursiveFunc(num-1) + } + + // Use the recursive closure + result := recursiveFunc(5) + fmt.Println("Factorial of 5 is:", result) // Output: 120 +} + +// Output: +// Factorial of 5 is: 120 diff --git a/gnovm/tests/files/closure13_a.gno b/gnovm/tests/files/closure13_a.gno new file mode 100644 index 00000000000..8345c9ed48d --- /dev/null +++ b/gnovm/tests/files/closure13_a.gno @@ -0,0 +1,24 @@ +package main + +import "fmt" + +func main() { + var recursiveFunc func(int) int + var recursiveFunc2 func(int) int + + recursiveFunc = func(num int) int { + recursiveFunc2 = recursiveFunc + + if num <= 0 { + return 1 + } + + return num * recursiveFunc2(num-1) + } + + result := recursiveFunc(5) + fmt.Println("Factorial of 5 is:", result) // Output: 120 +} + +// Output: +// Factorial of 5 is: 120 diff --git a/gnovm/tests/files/closure13_b0.gno b/gnovm/tests/files/closure13_b0.gno new file mode 100644 index 00000000000..4bfe864cc8e --- /dev/null +++ b/gnovm/tests/files/closure13_b0.gno @@ -0,0 +1,26 @@ +package main + +func main() { + var buf []byte + + // when eval this, buf is still nil, + expectRead := func(size int, expected string, err error) { + b := buf[0:size] + println(b) + println(len(buf)) + println(cap(buf)) + } + + // buf should be captured here, here it's volatile, not where it defined + // namely, the vp should point here, -> so mutate offset + withFooBar := func() { + buf = make([]byte, 20) + expectRead(5, "foo ", nil) + } + withFooBar() +} + +// Output: +// slice[0x0000000000] +// 20 +// 20 diff --git a/gnovm/tests/files/closure13_b1.gno b/gnovm/tests/files/closure13_b1.gno new file mode 100644 index 00000000000..9218ee16fa3 --- /dev/null +++ b/gnovm/tests/files/closure13_b1.gno @@ -0,0 +1,26 @@ +package main + +func main() { + var buf []byte + + // when eval this, buf is still nil, + expectRead := func(size int, expected string, eerr error) { + b := buf[0:size] + println(b) + } + + // buf should be captured here, here it's volatile, not where it defined + // namely, the vp should point here, -> so mutate offset + withFooBar := func() func() { + buf = make([]byte, 20) + return func() { + b := buf[0:4] + println(b) + } + } + withFooBar() + println("ok") +} + +// Output: +// ok diff --git a/gnovm/tests/files/closure13_b_stdlibs.gno b/gnovm/tests/files/closure13_b_stdlibs.gno new file mode 100644 index 00000000000..7d9d935652c --- /dev/null +++ b/gnovm/tests/files/closure13_b_stdlibs.gno @@ -0,0 +1,47 @@ +package main + +import ( + "fmt" + "io" + "strings" +) + +func main() { + var mr io.Reader + var buf []byte + nread := 0 + withFooBar := func(tests func()) { + r1 := strings.NewReader("foo ") + //r2 := strings.NewReader("") + //r3 := strings.NewReader("bar") + //mr = io.MultiReader(r1, r2, r3) + mr = io.MultiReader(r1) + buf = make([]byte, 20) + tests() + } + expectRead := func(size int, expected string, eerr error) { + nread++ + n, gerr := mr.Read(buf[0:size]) + if n != len(expected) { + panic(fmt.Sprintf("#%d, expected %d bytes; got %d", + nread, len(expected), n)) + } + got := string(buf[0:n]) + if got != expected { + panic(fmt.Sprintf("#%d, expected %q; got %q", + nread, expected, got)) + } + if gerr != eerr { + panic(fmt.Sprintf("#%d, expected error %v; got %v", + nread, eerr, gerr)) + } + buf = buf[n:] + } + withFooBar(func() { + expectRead(5, "foo ", nil) + }) + println("ok") +} + +// Output: +// ok diff --git a/gnovm/tests/files/closure14.gno b/gnovm/tests/files/closure14.gno new file mode 100644 index 00000000000..e6b89f9528a --- /dev/null +++ b/gnovm/tests/files/closure14.gno @@ -0,0 +1,26 @@ +package main + +import "fmt" + +func foo() (err error) { + defer func() { + if r := recover(); r != nil { + switch v := r.(type) { + case error: + err = v + default: + err = fmt.Errorf("%s", v) + } + } + }() + + panic("xxx") +} + +func main() { + err := foo() + println(err.Error()) +} + +// Output: +// xxx diff --git a/gnovm/tests/files/closure14_a.gno b/gnovm/tests/files/closure14_a.gno new file mode 100644 index 00000000000..706e2bd7e8b --- /dev/null +++ b/gnovm/tests/files/closure14_a.gno @@ -0,0 +1,27 @@ +package main + +import ( + "errors" +) + +func foo() (err error) { + y := 1 + defer func() { + if r := recover(); r != nil { + switch y { + case 1: + err = errors.New("ok") + default: + err = nil + } + } + }() + panic(y) +} + +func main() { + println(foo()) +} + +// Output: +// ok diff --git a/gnovm/tests/files/closure15.gno b/gnovm/tests/files/closure15.gno new file mode 100644 index 00000000000..86c8fc445f0 --- /dev/null +++ b/gnovm/tests/files/closure15.gno @@ -0,0 +1,32 @@ +package main + +import "fmt" + +func main() { + + var fns []func(int) int + var recursiveFunc func(int) int + + for i := 0; i < 6; i++ { + recursiveFunc = func(num int) int { + if num <= 0 { + return 1 + } + return num * recursiveFunc(num-1) + } + fns = append(fns, recursiveFunc) + } + + for i, r := range fns { + result := r(i) + fmt.Printf("Factorial of %d is: %d \n", i, result) + } +} + +// Output: +// Factorial of 0 is: 1 +// Factorial of 1 is: 1 +// Factorial of 2 is: 2 +// Factorial of 3 is: 6 +// Factorial of 4 is: 24 +// Factorial of 5 is: 120 diff --git a/gnovm/tests/files/closure15_a.gno b/gnovm/tests/files/closure15_a.gno new file mode 100644 index 00000000000..1983845bd81 --- /dev/null +++ b/gnovm/tests/files/closure15_a.gno @@ -0,0 +1,49 @@ +package main + +import "fmt" + +// recursive closure does not capture +func main() { + + var fns []func(int) int + var recursiveFunc func(int) int + + for i := 0; i < 3; i++ { + recursiveFunc = func(num int) int { + x := i + println("value of x: ", x) + if num <= 0 { + return 1 + } + return num * recursiveFunc(num-1) + } + fns = append(fns, recursiveFunc) + } + + for i, r := range fns { + result := r(i) + fmt.Printf("Factorial of %d is: %d \n", i, result) + } +} + +// Go Output: +// value of x: 3 +// Factorial of 0 is: 1 +// value of x: 3 +// value of x: 3 +// Factorial of 1 is: 1 +// value of x: 3 +// value of x: 3 +// value of x: 3 +// Factorial of 2 is: 2 + +// Output: +// value of x: 0 +// Factorial of 0 is: 1 +// value of x: 1 +// value of x: 2 +// Factorial of 1 is: 1 +// value of x: 2 +// value of x: 2 +// value of x: 2 +// Factorial of 2 is: 2 diff --git a/gnovm/tests/files/closure16.gno b/gnovm/tests/files/closure16.gno new file mode 100644 index 00000000000..8a472ffb410 --- /dev/null +++ b/gnovm/tests/files/closure16.gno @@ -0,0 +1,21 @@ +package main + +func main() { + var fns []func() int + s := []int{1, 2, 3} + for i, _ := range s { + x := i + f := func() int { + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 2 diff --git a/gnovm/tests/files/closure16_a.gno b/gnovm/tests/files/closure16_a.gno new file mode 100644 index 00000000000..ee375bc719c --- /dev/null +++ b/gnovm/tests/files/closure16_a.gno @@ -0,0 +1,20 @@ +package main + +func main() { + var fns []func() int + m := map[string]int{"a": 1, "b": 2} + for _, v := range m { + x := v + f := func() int { + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 1 +// 2 diff --git a/gnovm/tests/files/closure16_a1.gno b/gnovm/tests/files/closure16_a1.gno new file mode 100644 index 00000000000..857cc11d7da --- /dev/null +++ b/gnovm/tests/files/closure16_a1.gno @@ -0,0 +1,19 @@ +package main + +func main() { + var fns []func() int + m := map[string]int{"a": 1, "b": 2} + for _, v := range m { + f := func() int { + return v + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 1 +// 2 diff --git a/gnovm/tests/files/closure16_b.gno b/gnovm/tests/files/closure16_b.gno new file mode 100644 index 00000000000..5d26a2c6dc0 --- /dev/null +++ b/gnovm/tests/files/closure16_b.gno @@ -0,0 +1,23 @@ +package main + +func main() { + var fns []func() int + s := "hello" + for i, _ := range s { + x := i + f := func() int { + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 2 +// 3 +// 4 diff --git a/gnovm/tests/files/closure16_b1.gno b/gnovm/tests/files/closure16_b1.gno new file mode 100644 index 00000000000..a7ddb99504f --- /dev/null +++ b/gnovm/tests/files/closure16_b1.gno @@ -0,0 +1,22 @@ +package main + +func main() { + var fns []func() int + s := "hello" + for i, _ := range s { + f := func() int { + return i + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 2 +// 3 +// 4 diff --git a/gnovm/tests/files/closure17_io2.gno b/gnovm/tests/files/closure17_io2.gno new file mode 100644 index 00000000000..24655f5040c --- /dev/null +++ b/gnovm/tests/files/closure17_io2.gno @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + "io" + "log" + "strings" +) + +func main() { + r := strings.NewReader("Go is a general-purpose language designed with systems programming in mind.") + + b, err := io.ReadAll(r) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%s", b) +} + +// Output: +// Go is a general-purpose language designed with systems programming in mind. diff --git a/gnovm/tests/files/closure17_recover4.gno b/gnovm/tests/files/closure17_recover4.gno new file mode 100644 index 00000000000..5a6da4261a2 --- /dev/null +++ b/gnovm/tests/files/closure17_recover4.gno @@ -0,0 +1,25 @@ +package main + +import "fmt" + +func div(a, b int) (result int) { + defer func() { + r := recover() + + fmt.Printf("r = %#v\n", r) + + if r != nil { + result = 0 + } + }() + + return a / b +} + +func main() { + println(div(30, 2)) +} + +// Output: +// r = +// 15 diff --git a/gnovm/tests/files/closure17_recover6.gno b/gnovm/tests/files/closure17_recover6.gno new file mode 100644 index 00000000000..0b304369764 --- /dev/null +++ b/gnovm/tests/files/closure17_recover6.gno @@ -0,0 +1,30 @@ +package main + +import ( + "errors" +) + +func main() { + println(f(false)) + println(f(true)) +} + +func f(dopanic bool) (err error) { + defer func() { + if x := recover(); x != nil { + err = x.(error) + } + }() + q(dopanic) + return +} + +func q(dopanic bool) { + if dopanic { + panic(errors.New("wtf")) + } +} + +// Output: +// undefined +// wtf diff --git a/gnovm/tests/files/closure17_recover6a.gno b/gnovm/tests/files/closure17_recover6a.gno new file mode 100644 index 00000000000..427fc4b74b9 --- /dev/null +++ b/gnovm/tests/files/closure17_recover6a.gno @@ -0,0 +1,40 @@ +package main + +import "errors" + +//func p(s interface{}) { +// fmt.Printf("%T \n", s) +// if v, ok := s.(string); ok { +// println(v) +// panic(v) +// } else { +// println("---") +// } +//} + +type error interface { + Error() string +} + +// New returns an error that formats as the given text. +// Each call to New returns a distinct error value even if the text is identical. +func New(text string) error { + return &errorString{text} +} + +// errorString is a trivial implementation of error. +type errorString struct { + s string +} + +func (e *errorString) Error() string { + return e.s +} + +func main() { + //panic(New("wtf")) + panic(errors.New("wtf")) +} + +// Error: +// wtf diff --git a/gnovm/tests/files/closure17_sort_search_efficiency.gno b/gnovm/tests/files/closure17_sort_search_efficiency.gno new file mode 100644 index 00000000000..90362944ac5 --- /dev/null +++ b/gnovm/tests/files/closure17_sort_search_efficiency.gno @@ -0,0 +1,21 @@ +package main + +func Search(n int, f func(int) bool) int { + f(1) + return 0 +} + +func main() { + for x := 0; x < 2; x++ { + count := 0 + println(" first: count: ", count) + Search(1, func(i int) bool { count++; return i >= x }) + println("second: count: ", count) + } +} + +// Output: +// first: count: 0 +// second: count: 1 +// first: count: 0 +// second: count: 1 diff --git a/gnovm/tests/files/closure17_zregexp_stdlibs.gno b/gnovm/tests/files/closure17_zregexp_stdlibs.gno new file mode 100644 index 00000000000..10bb6f937d3 --- /dev/null +++ b/gnovm/tests/files/closure17_zregexp_stdlibs.gno @@ -0,0 +1,19 @@ +// MAXALLOC: 100000000 +// max total allocation of 100 mb. +package main + +import "regexp" + +var reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]*$`) + +func main() { + for j := 0; j < 100; j++ { + if !(reName.MatchString("thisisatestname")) { + panic("error") + } + } + println(true) +} + +// Output: +// true diff --git a/gnovm/tests/files/closure9.gno b/gnovm/tests/files/closure9.gno new file mode 100644 index 00000000000..e3a4917211f --- /dev/null +++ b/gnovm/tests/files/closure9.gno @@ -0,0 +1,30 @@ +package main + +// this is a fix, intuitive, and same with Go +func main() { + var fns []func() int + for i := 0; i < 5; i++ { + x := i + f := func() int { + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Go Output: +// 0 +// 1 +// 2 +// 3 +// 4 + +// Output: +// 0 +// 1 +// 2 +// 3 +// 4 diff --git a/gnovm/tests/files/closure9_a0.gno b/gnovm/tests/files/closure9_a0.gno new file mode 100644 index 00000000000..0f5ba6d6739 --- /dev/null +++ b/gnovm/tests/files/closure9_a0.gno @@ -0,0 +1,29 @@ +package main + +// this is a fix, intuitive, same with go loopVar experiment feature. +func main() { + var fns []func() int + for i := 0; i < 5; i++ { + f := func() int { + return i + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Go Output: +// 5 +// 5 +// 5 +// 5 +// 5 + +// Output: +// 0 +// 1 +// 2 +// 3 +// 4 diff --git a/gnovm/tests/files/closure9_a02.gno b/gnovm/tests/files/closure9_a02.gno new file mode 100644 index 00000000000..82ca810b13c --- /dev/null +++ b/gnovm/tests/files/closure9_a02.gno @@ -0,0 +1,20 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 5; i++ { + x := i + f := func() int { + return x + } + fns = append(fns, f) + } + println(fns[3]()) + println(fns[2]()) + println(fns[0]()) +} + +// Output: +// 3 +// 2 +// 0 diff --git a/gnovm/tests/files/closure9_a1.gno b/gnovm/tests/files/closure9_a1.gno new file mode 100644 index 00000000000..19951610e7e --- /dev/null +++ b/gnovm/tests/files/closure9_a1.gno @@ -0,0 +1,34 @@ +package main + +// this still keeps consistent with go, that a variable out of loop block is not captured +// this is unintuitive(should capture something) +// TODO: leave it for discuss. + +func main() { + var fns []func() int + var x int + for i := 0; i < 5; i++ { + x = i + f := func() int { + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Go Output: +// 4 +// 4 +// 4 +// 4 +// 4 + +// Output: +// 4 +// 4 +// 4 +// 4 +// 4 diff --git a/gnovm/tests/files/closure9_b.gno b/gnovm/tests/files/closure9_b.gno new file mode 100644 index 00000000000..0822368fe3a --- /dev/null +++ b/gnovm/tests/files/closure9_b.gno @@ -0,0 +1,22 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 2; i++ { + x := i + y := 0 + f := func() int { + x += y + x += 1 + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 1 +// 2 diff --git a/gnovm/tests/files/closure9_c.gno b/gnovm/tests/files/closure9_c.gno new file mode 100644 index 00000000000..e5c61c59089 --- /dev/null +++ b/gnovm/tests/files/closure9_c.gno @@ -0,0 +1,18 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 2; i++ { + f := func() int { + return i + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure9_d.gno b/gnovm/tests/files/closure9_d.gno new file mode 100644 index 00000000000..185961c741f --- /dev/null +++ b/gnovm/tests/files/closure9_d.gno @@ -0,0 +1,29 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 5; i++ { + x := i + f := func() int { + x := 5 + return x + } + println(x) + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 2 +// 3 +// 4 +// 5 +// 5 +// 5 +// 5 +// 5 diff --git a/gnovm/tests/files/closure9_e.gno b/gnovm/tests/files/closure9_e.gno new file mode 100644 index 00000000000..b3b2b2b0385 --- /dev/null +++ b/gnovm/tests/files/closure9_e.gno @@ -0,0 +1,12 @@ +package main + +func main() { + for i := 0; i < 5; i++ { + if i == 1 { + panic("error 1") + } + } +} + +// Error: +// error 1 diff --git a/gnovm/tests/files/closure9_f.gno b/gnovm/tests/files/closure9_f.gno new file mode 100644 index 00000000000..19722ec3ae6 --- /dev/null +++ b/gnovm/tests/files/closure9_f.gno @@ -0,0 +1,53 @@ +package main + +// 1. we can determine target vars by hasClosure pattern and externNames(without uverse). +// it can be a loopvar, or vars derived from loopvar, e.g. x := i; +// 2. now we need to capture every transient state of the target var; firstly we should +// eval the transient state, there are two ways around: +// a) eval funcLit, but the time doing this is hard to determine, namely, you have to eval +// before it leaves its using context. In some sense, you have to cover all cases when a funcValue +// is used, like assign, xxxLit, funcCall as an arg, etc. +// b) another way for this is to generate a `time series` values that the index is the loop index, +// e.g. in a for loop, we define a new var like x := i, we store the transient state of x per loop +// in the time series, which is a slice. This slice is used when the closure fv is called, +// replacing the var in default block.(func value have a slice of name indicates it's captured, when +// eval name in this slice, using the time series instead). +// this solution mainly differs that it eval target x independently with the closure funcLit, so it avoids +// the obsession to determine the eval order. this seems the right way. hmmm. + +//======================================================================================================== +// work flow 1.0, hard to do +// 1. determine loop var, whole logic depends on this, the key word is dynamic, it causes polymorphic. +// this should most be done via define(), // TODO: check it +// 2. in transcribe, find if loopvar is used somewhere, to identify pattern like x := i, what to do with +// other use cases? this brings complexity. This is wrong feel. +// we can generate the ts slice for every target var, e.g. x in this case. if two vars, two slices. so fv should +// reference a slice of slice. + +// work flow 2.0 +// 1. ignore loop var, assume all externNames apart from uverse is target captured var, this also needs to happen +// in transcribe, tag it, and can be checked everywhere it's appears in runtime, x := i, or var x int, etc. +// when eval funcLit, new a slice of slice and push value for further update. + +func main() { + var fns []func() int + for i := 0; i < 5; i++ { + //x := i // check if x is polymorphic, if yes, new a slice and set value by index + var x int // new a slice no init, this would be updated later, so the slice can be mutated after opFuncLit + f := func() int { + return x // set the slice to fv in + } + x = i // check if x is polymorphic, is yes, update slice assigned to fv before this, which is a reference. + fns = append(fns, f) // where should eval funcLit, where it assigned somewhere, or used in another func lit + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 2 +// 3 +// 4 diff --git a/gnovm/tests/files/closure9_g.gno b/gnovm/tests/files/closure9_g.gno new file mode 100644 index 00000000000..e7a6b64f1f3 --- /dev/null +++ b/gnovm/tests/files/closure9_g.gno @@ -0,0 +1,24 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 5; i++ { + x := i + { + f := func() int { + return x + } + fns = append(fns, f) + } + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 +// 2 +// 3 +// 4 diff --git a/gnovm/tests/files/closure9_h.gno b/gnovm/tests/files/closure9_h.gno new file mode 100644 index 00000000000..23cb5ff9f40 --- /dev/null +++ b/gnovm/tests/files/closure9_h.gno @@ -0,0 +1,30 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 2; i++ { + x := i + for j := 0; j < 2; j++ { + y := j + f := func() int { + return x + y + } + fns = append(fns, f) + } + } + for _, fn := range fns { + println(fn()) + } +} + +// Go Output: +// 0 +// 1 +// 1 +// 2 + +// Output: +// 0 +// 1 +// 1 +// 2 diff --git a/gnovm/tests/files/closure9_h_0.gno b/gnovm/tests/files/closure9_h_0.gno new file mode 100644 index 00000000000..d60fa8515a9 --- /dev/null +++ b/gnovm/tests/files/closure9_h_0.gno @@ -0,0 +1,28 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 2; i++ { + for j := 0; j < 2; j++ { + f := func() int { + return i + j + } + fns = append(fns, f) + } + } + for _, fn := range fns { + println(fn()) + } +} + +// Go Output: +// 4 +// 4 +// 4 +// 4 + +// Output: +// 0 +// 1 +// 1 +// 2 diff --git a/gnovm/tests/files/closure9_i.gno b/gnovm/tests/files/closure9_i.gno new file mode 100644 index 00000000000..42a4d11d862 --- /dev/null +++ b/gnovm/tests/files/closure9_i.gno @@ -0,0 +1,31 @@ +package main + +func main() { + var fns []func() int + var x int + for i := 0; i < 2; i++ { + x = i + for j := 0; j < 2; j++ { + y := j + f := func() int { + return x + y + } + fns = append(fns, f) + } + } + for _, fn := range fns { + println(fn()) + } +} + +// Go Output: +// 1 +// 2 +// 1 +// 2 + +// Output: +// 1 +// 2 +// 1 +// 2 diff --git a/gnovm/tests/files/closure9_j.gno b/gnovm/tests/files/closure9_j.gno new file mode 100644 index 00000000000..926caf5090c --- /dev/null +++ b/gnovm/tests/files/closure9_j.gno @@ -0,0 +1,21 @@ +package main + +func main() { + var fns []func() int + for i := 0; i < 2; i++ { + x := i + y := 0 + f := func() int { + x += y + return x + } + fns = append(fns, f) + } + for _, fn := range fns { + println(fn()) + } +} + +// Output: +// 0 +// 1 diff --git a/gnovm/tests/files/closure_9a.gno b/gnovm/tests/files/closure_9a.gno new file mode 100644 index 00000000000..3bfa5f181d0 --- /dev/null +++ b/gnovm/tests/files/closure_9a.gno @@ -0,0 +1,17 @@ +package main + +func main() { + var fns []func() + for _, v := range []int{1, 2, 3} { + x := v*100 + v + fns = append(fns, func() { println(x) }) + } + for _, fn := range fns { + fn() + } +} + +// Output: +// 101 +// 202 +// 303 diff --git a/gnovm/tests/files/zrealm0.gno b/gnovm/tests/files/zrealm0.gno index 7578781e503..487422282b7 100644 --- a/gnovm/tests/files/zrealm0.gno +++ b/gnovm/tests/files/zrealm0.gno @@ -69,6 +69,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], diff --git a/gnovm/tests/files/zrealm1.gno b/gnovm/tests/files/zrealm1.gno index d90c5e8621a..63075a77dcd 100644 --- a/gnovm/tests/files/zrealm1.gno +++ b/gnovm/tests/files/zrealm1.gno @@ -183,6 +183,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], diff --git a/gnovm/tests/files/zrealm2.gno b/gnovm/tests/files/zrealm2.gno index 67ba2f5a768..988d74b436f 100644 --- a/gnovm/tests/files/zrealm2.gno +++ b/gnovm/tests/files/zrealm2.gno @@ -186,6 +186,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -222,6 +223,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], diff --git a/gnovm/tests/files/zrealm3.gno b/gnovm/tests/files/zrealm3.gno index da8a581375c..72bb49f3560 100644 --- a/gnovm/tests/files/zrealm3.gno +++ b/gnovm/tests/files/zrealm3.gno @@ -185,6 +185,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -221,6 +222,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], diff --git a/gnovm/tests/files/zrealm4.gno b/gnovm/tests/files/zrealm4.gno index dc3c48c774b..dd8cc23dd52 100644 --- a/gnovm/tests/files/zrealm4.gno +++ b/gnovm/tests/files/zrealm4.gno @@ -127,6 +127,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -163,6 +164,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], diff --git a/gnovm/tests/files/zrealm5.gno b/gnovm/tests/files/zrealm5.gno index e65b089c18d..954ab2763e6 100644 --- a/gnovm/tests/files/zrealm5.gno +++ b/gnovm/tests/files/zrealm5.gno @@ -198,6 +198,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -234,6 +235,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], diff --git a/gnovm/tests/files/zrealm6.gno b/gnovm/tests/files/zrealm6.gno index 20615fa7d39..e40291dcff7 100644 --- a/gnovm/tests/files/zrealm6.gno +++ b/gnovm/tests/files/zrealm6.gno @@ -270,6 +270,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -306,6 +307,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], diff --git a/gnovm/tests/files/zrealm7.gno b/gnovm/tests/files/zrealm7.gno index 9decb0dae10..20319e4f61b 100644 --- a/gnovm/tests/files/zrealm7.gno +++ b/gnovm/tests/files/zrealm7.gno @@ -342,6 +342,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -378,6 +379,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], diff --git a/gnovm/tests/files/zrealm_avl0.gno b/gnovm/tests/files/zrealm_avl0.gno index e91788ac8eb..acd3d3d5758 100644 --- a/gnovm/tests/files/zrealm_avl0.gno +++ b/gnovm/tests/files/zrealm_avl0.gno @@ -280,6 +280,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -316,6 +317,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], diff --git a/gnovm/tests/files/zrealm_avl1.gno b/gnovm/tests/files/zrealm_avl1.gno index cdd56a5ad89..77726610747 100644 --- a/gnovm/tests/files/zrealm_avl1.gno +++ b/gnovm/tests/files/zrealm_avl1.gno @@ -304,6 +304,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -340,6 +341,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], diff --git a/gnovm/tests/files/zrealm_natbind0.gno b/gnovm/tests/files/zrealm_natbind0.gno index 60e0d448202..8b141fc14f4 100644 --- a/gnovm/tests/files/zrealm_natbind0.gno +++ b/gnovm/tests/files/zrealm_natbind0.gno @@ -74,6 +74,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -110,6 +111,7 @@ func main() { // "PkgPath": "gno.land/r/test" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -166,6 +168,7 @@ func main() { // "PkgPath": "std" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [ diff --git a/gnovm/tests/files/zrealm_tests0.gno b/gnovm/tests/files/zrealm_tests0.gno index 73d07f726eb..40362238489 100644 --- a/gnovm/tests/files/zrealm_tests0.gno +++ b/gnovm/tests/files/zrealm_tests0.gno @@ -252,6 +252,7 @@ func main() { // "PkgPath": "gno.land/r/demo/tests" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [ @@ -358,6 +359,7 @@ func main() { // "PkgPath": "gno.land/r/demo/tests" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [ @@ -424,6 +426,7 @@ func main() { // "PkgPath": "gno.land/r/demo/tests" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [ @@ -480,6 +483,7 @@ func main() { // "PkgPath": "gno.land/r/demo/tests" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -526,6 +530,7 @@ func main() { // "PkgPath": "gno.land/r/demo/tests" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -582,6 +587,7 @@ func main() { // "PkgPath": "gno.land/r/demo/tests" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -628,6 +634,7 @@ func main() { // "PkgPath": "gno.land/r/demo/tests" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -674,6 +681,7 @@ func main() { // "PkgPath": "gno.land/r/demo/tests" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -733,6 +741,7 @@ func main() { // "PkgPath": "gno.land/r/demo/tests" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [ @@ -782,6 +791,7 @@ func main() { // "PkgPath": "gno.land/r/demo/tests" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -818,6 +828,7 @@ func main() { // "PkgPath": "gno.land/r/demo/tests" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -854,6 +865,7 @@ func main() { // "PkgPath": "gno.land/r/demo/tests" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -900,6 +912,7 @@ func main() { // "PkgPath": "gno.land/r/demo/tests" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -956,6 +969,7 @@ func main() { // "PkgPath": "gno.land/r/demo/tests" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [], @@ -1013,6 +1027,7 @@ func main() { // "PkgPath": "gno.land/r/demo/tests" // } // }, +// "TransientLoopData": null, // "Type": { // "@type": "/gno.FuncType", // "Params": [