From b17343dc19f7646efdd2ef376cf4059ee971a373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 21 Oct 2022 15:03:31 -0700 Subject: [PATCH 01/89] PoC: Build Bytecode Quickly --- runtime/bbq/compiler/compiler.go | 547 +++++++++++++++++++++++ runtime/bbq/compiler/compiler_test.go | 331 ++++++++++++++ runtime/bbq/compiler/constant.go | 27 ++ runtime/bbq/compiler/function.go | 66 +++ runtime/bbq/compiler/global.go | 23 + runtime/bbq/compiler/local.go | 23 + runtime/bbq/compiler/loop.go | 24 + runtime/bbq/constant.go | 26 ++ runtime/bbq/constantkind/constantkind.go | 135 ++++++ runtime/bbq/function.go | 26 ++ runtime/bbq/leb128/leb128.go | 220 +++++++++ runtime/bbq/leb128/leb128_test.go | 292 ++++++++++++ runtime/bbq/opcode/opcode.go | 54 +++ runtime/bbq/opcode/opcode_string.go | 45 ++ runtime/bbq/program.go | 24 + runtime/bbq/vm/callframe.go | 37 ++ runtime/bbq/vm/value.go | 74 +++ runtime/bbq/vm/vm.go | 314 +++++++++++++ runtime/bbq/vm/vm_test.go | 199 +++++++++ runtime/tests/interpreter/fib_test.go | 71 +++ 20 files changed, 2558 insertions(+) create mode 100644 runtime/bbq/compiler/compiler.go create mode 100644 runtime/bbq/compiler/compiler_test.go create mode 100644 runtime/bbq/compiler/constant.go create mode 100644 runtime/bbq/compiler/function.go create mode 100644 runtime/bbq/compiler/global.go create mode 100644 runtime/bbq/compiler/local.go create mode 100644 runtime/bbq/compiler/loop.go create mode 100644 runtime/bbq/constant.go create mode 100644 runtime/bbq/constantkind/constantkind.go create mode 100644 runtime/bbq/function.go create mode 100644 runtime/bbq/leb128/leb128.go create mode 100644 runtime/bbq/leb128/leb128_test.go create mode 100644 runtime/bbq/opcode/opcode.go create mode 100644 runtime/bbq/opcode/opcode_string.go create mode 100644 runtime/bbq/program.go create mode 100644 runtime/bbq/vm/callframe.go create mode 100644 runtime/bbq/vm/value.go create mode 100644 runtime/bbq/vm/vm.go create mode 100644 runtime/bbq/vm/vm_test.go create mode 100644 runtime/tests/interpreter/fib_test.go diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go new file mode 100644 index 0000000000..2ede219c78 --- /dev/null +++ b/runtime/bbq/compiler/compiler.go @@ -0,0 +1,547 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package compiler + +import ( + "math" + + "github.com/onflow/cadence/runtime/ast" + "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/bbq/constantkind" + "github.com/onflow/cadence/runtime/bbq/leb128" + "github.com/onflow/cadence/runtime/bbq/opcode" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/sema" +) + +type Compiler struct { + Program *ast.Program + Elaboration *sema.Elaboration + + currentFunction *function + functions []*function + constants []*constant + globals map[string]*global + loops []*loop + currentLoop *loop +} + +var _ ast.DeclarationVisitor[struct{}] = &Compiler{} +var _ ast.StatementVisitor[struct{}] = &Compiler{} +var _ ast.ExpressionVisitor[struct{}] = &Compiler{} + +func NewCompiler( + program *ast.Program, + elaboration *sema.Elaboration, +) *Compiler { + return &Compiler{ + Program: program, + Elaboration: elaboration, + globals: map[string]*global{}, + } +} + +func (c *Compiler) findGlobal(name string) *global { + return c.globals[name] +} + +func (c *Compiler) addGlobal(name string) *global { + count := len(c.globals) + if count >= math.MaxUint16 { + panic(errors.NewDefaultUserError("invalid global declaration")) + } + global := &global{ + index: uint16(count), + } + c.globals[name] = global + return global +} + +func (c *Compiler) addFunction(name string, parameterCount uint16) *function { + c.addGlobal(name) + function := newFunction(name, parameterCount) + c.functions = append(c.functions, function) + c.currentFunction = function + return function +} + +func (c *Compiler) addConstant(kind constantkind.Constant, data []byte) *constant { + count := len(c.constants) + if count >= math.MaxUint16 { + panic(errors.NewDefaultUserError("invalid constant declaration")) + } + constant := &constant{ + index: uint16(count), + kind: kind, + data: data[:], + } + c.constants = append(c.constants, constant) + return constant +} + +func (c *Compiler) emit(opcode opcode.Opcode, args ...byte) int { + return c.currentFunction.emit(opcode, args...) +} + +func (c *Compiler) emitUndefinedJump(opcode opcode.Opcode) int { + return c.emit(opcode, 0xff, 0xff) +} + +func (c *Compiler) emitJump(opcode opcode.Opcode, target int) int { + if target >= math.MaxUint16 { + panic(errors.NewDefaultUserError("invalid jump")) + } + first, second := encodeUint16(uint16(target)) + return c.emit(opcode, first, second) +} + +func (c *Compiler) patchJump(opcodeOffset int) { + code := c.currentFunction.code + count := len(code) + if count == 0 { + panic(errors.NewUnreachableError()) + } + if count >= math.MaxUint16 { + panic(errors.NewDefaultUserError("invalid jump")) + } + target := uint16(count) + first, second := encodeUint16(target) + code[opcodeOffset+1] = first + code[opcodeOffset+2] = second +} + +// encodeUint16 encodes the given uint16 in big-endian representation +func encodeUint16(jump uint16) (byte, byte) { + return byte((jump >> 8) & 0xff), + byte(jump & 0xff) +} + +func (c *Compiler) pushLoop(start int) { + loop := &loop{ + start: start, + } + c.loops = append(c.loops, loop) + c.currentLoop = loop +} + +func (c *Compiler) popLoop() { + lastIndex := len(c.loops) - 1 + l := c.loops[lastIndex] + c.loops[lastIndex] = nil + c.loops = c.loops[:lastIndex] + + c.patchLoop(l) + + var previousLoop *loop + if lastIndex > 0 { + previousLoop = c.loops[lastIndex] + } + c.currentLoop = previousLoop +} + +func (c *Compiler) Compile() *bbq.Program { + for _, declaration := range c.Program.Declarations() { + c.compileDeclaration(declaration) + } + + functions := c.exportFunctions() + constants := c.exportConstants() + + return &bbq.Program{ + Functions: functions, + Constants: constants, + } +} + +func (c *Compiler) exportConstants() []*bbq.Constant { + constants := make([]*bbq.Constant, 0, len(c.constants)) + for _, constant := range c.constants { + constants = append( + constants, + &bbq.Constant{ + Data: constant.data, + Kind: constant.kind, + }, + ) + } + return constants +} + +func (c *Compiler) exportFunctions() []*bbq.Function { + functions := make([]*bbq.Function, 0, len(c.functions)) + for _, function := range c.functions { + functions = append( + functions, + &bbq.Function{ + Name: function.name, + Code: function.code, + LocalCount: function.localCount, + ParameterCount: function.parameterCount, + }, + ) + } + return functions +} + +func (c *Compiler) compileDeclaration(declaration ast.Declaration) { + ast.AcceptDeclaration[struct{}](declaration, c) +} + +func (c *Compiler) compileBlock(block *ast.Block) { + // TODO: scope + for _, statement := range block.Statements { + c.compileStatement(statement) + } +} + +func (c *Compiler) compileFunctionBlock(functionBlock *ast.FunctionBlock) { + // TODO: pre and post conditions, incl. interfaces + c.compileBlock(functionBlock.Block) +} + +func (c *Compiler) compileStatement(statement ast.Statement) { + ast.AcceptStatement[struct{}](statement, c) +} + +func (c *Compiler) compileExpression(expression ast.Expression) { + ast.AcceptExpression[struct{}](expression, c) +} + +func (c *Compiler) VisitReturnStatement(statement *ast.ReturnStatement) (_ struct{}) { + expression := statement.Expression + if expression != nil { + // TODO: copy + c.compileExpression(expression) + c.emit(opcode.ReturnValue) + } else { + c.emit(opcode.Return) + } + return +} + +func (c *Compiler) VisitBreakStatement(_ *ast.BreakStatement) (_ struct{}) { + offset := len(c.currentFunction.code) + c.currentLoop.breaks = append(c.currentLoop.breaks, offset) + c.emitUndefinedJump(opcode.Jump) + return +} + +func (c *Compiler) VisitContinueStatement(_ *ast.ContinueStatement) (_ struct{}) { + c.emitJump(opcode.Jump, c.currentLoop.start) + return +} + +func (c *Compiler) VisitIfStatement(statement *ast.IfStatement) (_ struct{}) { + // TODO: scope + switch test := statement.Test.(type) { + case ast.Expression: + c.compileExpression(test) + default: + // TODO: + panic(errors.NewUnreachableError()) + } + elseJump := c.emitUndefinedJump(opcode.JumpIfFalse) + c.compileBlock(statement.Then) + elseBlock := statement.Else + if elseBlock != nil { + thenJump := c.emit(opcode.Jump) + c.patchJump(elseJump) + c.compileBlock(elseBlock) + c.patchJump(thenJump) + } else { + c.patchJump(elseJump) + } + return +} + +func (c *Compiler) VisitWhileStatement(statement *ast.WhileStatement) (_ struct{}) { + testOffset := len(c.currentFunction.code) + c.pushLoop(testOffset) + c.compileExpression(statement.Test) + endJump := c.emitUndefinedJump(opcode.JumpIfFalse) + c.compileBlock(statement.Block) + c.emitJump(opcode.Jump, testOffset) + c.patchJump(endJump) + c.popLoop() + return +} + +func (c *Compiler) VisitForStatement(_ *ast.ForStatement) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitEmitStatement(_ *ast.EmitStatement) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitSwitchStatement(_ *ast.SwitchStatement) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitVariableDeclaration(declaration *ast.VariableDeclaration) (_ struct{}) { + // TODO: second value + c.compileExpression(declaration.Value) + local := c.currentFunction.declareLocal(declaration.Identifier.Identifier) + first, second := encodeUint16(local.index) + c.emit(opcode.SetLocal, first, second) + return +} + +func (c *Compiler) VisitAssignmentStatement(statement *ast.AssignmentStatement) (_ struct{}) { + c.compileExpression(statement.Value) + switch target := statement.Target.(type) { + case *ast.IdentifierExpression: + local := c.currentFunction.findLocal(target.Identifier.Identifier) + first, second := encodeUint16(local.index) + c.emit(opcode.SetLocal, first, second) + default: + // TODO: + panic(errors.NewUnreachableError()) + } + return +} + +func (c *Compiler) VisitSwapStatement(_ *ast.SwapStatement) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitExpressionStatement(_ *ast.ExpressionStatement) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitVoidExpression(_ *ast.VoidExpression) (_ struct{}) { + //TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitBoolExpression(expression *ast.BoolExpression) (_ struct{}) { + if expression.Value { + c.emit(opcode.True) + } else { + c.emit(opcode.False) + } + return +} + +func (c *Compiler) VisitNilExpression(_ *ast.NilExpression) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitIntegerExpression(expression *ast.IntegerExpression) (_ struct{}) { + integerType := c.Elaboration.IntegerExpressionType[expression] + constantKind := constantkind.FromSemaType(integerType) + + // TODO: + var data []byte + data = leb128.AppendInt64(data, expression.Value.Int64()) + + constant := c.addConstant(constantKind, data) + first, second := encodeUint16(constant.index) + c.emit(opcode.GetConstant, first, second) + return +} + +func (c *Compiler) VisitFixedPointExpression(_ *ast.FixedPointExpression) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitArrayExpression(_ *ast.ArrayExpression) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitDictionaryExpression(_ *ast.DictionaryExpression) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitIdentifierExpression(expression *ast.IdentifierExpression) (_ struct{}) { + name := expression.Identifier.Identifier + local := c.currentFunction.findLocal(name) + if local != nil { + first, second := encodeUint16(local.index) + c.emit(opcode.GetLocal, first, second) + return + } + global := c.findGlobal(name) + first, second := encodeUint16(global.index) + c.emit(opcode.GetGlobal, first, second) + return +} + +func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpression) (_ struct{}) { + // TODO: copy + for _, argument := range expression.Arguments { + c.compileExpression(argument.Expression) + } + c.compileExpression(expression.InvokedExpression) + c.emit(opcode.Call) + return +} + +func (c *Compiler) VisitMemberExpression(_ *ast.MemberExpression) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitIndexExpression(_ *ast.IndexExpression) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitConditionalExpression(_ *ast.ConditionalExpression) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitUnaryExpression(_ *ast.UnaryExpression) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitBinaryExpression(expression *ast.BinaryExpression) (_ struct{}) { + c.compileExpression(expression.Left) + c.compileExpression(expression.Right) + // TODO: add support for other types + c.emit(intBinaryOpcodes[expression.Operation]) + return +} + +var intBinaryOpcodes = [...]opcode.Opcode{ + ast.OperationPlus: opcode.IntAdd, + ast.OperationMinus: opcode.IntSubtract, + ast.OperationMul: opcode.IntMultiply, + ast.OperationDiv: opcode.IntDivide, + ast.OperationMod: opcode.IntMod, + ast.OperationEqual: opcode.IntEqual, + ast.OperationNotEqual: opcode.IntNotEqual, + ast.OperationLess: opcode.IntLess, + ast.OperationLessEqual: opcode.IntLessOrEqual, + ast.OperationGreater: opcode.IntGreater, + ast.OperationGreaterEqual: opcode.IntGreaterOrEqual, +} + +func (c *Compiler) VisitFunctionExpression(_ *ast.FunctionExpression) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitStringExpression(_ *ast.StringExpression) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitCastingExpression(_ *ast.CastingExpression) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitCreateExpression(_ *ast.CreateExpression) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitDestroyExpression(_ *ast.DestroyExpression) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitReferenceExpression(_ *ast.ReferenceExpression) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitForceExpression(_ *ast.ForceExpression) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitPathExpression(_ *ast.PathExpression) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunctionDeclaration) (_ struct{}) { + return c.VisitFunctionDeclaration(declaration.FunctionDeclaration) +} + +func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration) (_ struct{}) { + // TODO: handle nested functions + functionName := declaration.Identifier.Identifier + functionType := c.Elaboration.FunctionDeclarationFunctionTypes[declaration] + parameterCount := len(functionType.Parameters) + if parameterCount > math.MaxUint16 { + panic(errors.NewDefaultUserError("invalid parameter count")) + } + function := c.addFunction(functionName, uint16(parameterCount)) + for _, parameter := range declaration.ParameterList.Parameters { + parameterName := parameter.Identifier.Identifier + function.declareLocal(parameterName) + } + c.compileFunctionBlock(declaration.FunctionBlock) + return +} + +func (c *Compiler) VisitCompositeDeclaration(_ *ast.CompositeDeclaration) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitInterfaceDeclaration(_ *ast.InterfaceDeclaration) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitFieldDeclaration(_ *ast.FieldDeclaration) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitPragmaDeclaration(_ *ast.PragmaDeclaration) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitImportDeclaration(_ *ast.ImportDeclaration) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitTransactionDeclaration(_ *ast.TransactionDeclaration) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) VisitEnumCaseDeclaration(_ *ast.EnumCaseDeclaration) (_ struct{}) { + // TODO + panic(errors.NewUnreachableError()) +} + +func (c *Compiler) patchLoop(l *loop) { + for _, breakOffset := range l.breaks { + c.patchJump(breakOffset) + } +} diff --git a/runtime/bbq/compiler/compiler_test.go b/runtime/bbq/compiler/compiler_test.go new file mode 100644 index 0000000000..92db712d92 --- /dev/null +++ b/runtime/bbq/compiler/compiler_test.go @@ -0,0 +1,331 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package compiler + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/bbq/constantkind" + "github.com/onflow/cadence/runtime/bbq/opcode" + . "github.com/onflow/cadence/runtime/tests/checker" +) + +func TestCompileRecursionFib(t *testing.T) { + + t.Parallel() + + checker, err := ParseAndCheck(t, ` + fun fib(_ n: Int): Int { + if n < 2 { + return n + } + return fib(n - 1) + fib(n - 2) + } + `) + require.NoError(t, err) + + compiler := NewCompiler(checker.Program, checker.Elaboration) + program := compiler.Compile() + + require.Len(t, program.Functions, 1) + require.Equal(t, + []byte{ + // if n < 2 + byte(opcode.GetLocal), 0, 0, + byte(opcode.GetConstant), 0, 0, + byte(opcode.IntLess), + byte(opcode.JumpIfFalse), 0, 14, + // then return n + byte(opcode.GetLocal), 0, 0, + byte(opcode.ReturnValue), + // fib(n - 1) + byte(opcode.GetLocal), 0, 0, + byte(opcode.GetConstant), 0, 1, + byte(opcode.IntSubtract), + byte(opcode.GetGlobal), 0, 0, + byte(opcode.Call), + // fib(n - 2) + byte(opcode.GetLocal), 0, 0, + byte(opcode.GetConstant), 0, 2, + byte(opcode.IntSubtract), + byte(opcode.GetGlobal), 0, 0, + byte(opcode.Call), + // return sum + byte(opcode.IntAdd), + byte(opcode.ReturnValue), + }, + compiler.functions[0].code, + ) + + require.Equal(t, + []*bbq.Constant{ + { + Data: []byte{0x2}, + Kind: constantkind.Int, + }, + { + Data: []byte{0x1}, + Kind: constantkind.Int, + }, + { + Data: []byte{0x2}, + Kind: constantkind.Int, + }, + }, + program.Constants, + ) +} + +func TestCompileImperativeFib(t *testing.T) { + + t.Parallel() + + checker, err := ParseAndCheck(t, ` + fun fib(_ n: Int): Int { + var fib1 = 1 + var fib2 = 1 + var fibonacci = fib1 + var i = 2 + while i < n { + fibonacci = fib1 + fib2 + fib1 = fib2 + fib2 = fibonacci + i = i + 1 + } + return fibonacci + } + `) + require.NoError(t, err) + + compiler := NewCompiler(checker.Program, checker.Elaboration) + program := compiler.Compile() + + require.Len(t, program.Functions, 1) + require.Equal(t, + []byte{ + // var fib1 = 1 + byte(opcode.GetConstant), 0, 0, + byte(opcode.SetLocal), 0, 1, + // var fib2 = 1 + byte(opcode.GetConstant), 0, 1, + byte(opcode.SetLocal), 0, 2, + // var fibonacci = fib1 + byte(opcode.GetLocal), 0, 1, + byte(opcode.SetLocal), 0, 3, + // var i = 2 + byte(opcode.GetConstant), 0, 2, + byte(opcode.SetLocal), 0, 4, + // while i < n + byte(opcode.GetLocal), 0, 4, + byte(opcode.GetLocal), 0, 0, + byte(opcode.IntLess), + byte(opcode.JumpIfFalse), 0, 69, + // fibonacci = fib1 + fib2 + byte(opcode.GetLocal), 0, 1, + byte(opcode.GetLocal), 0, 2, + byte(opcode.IntAdd), + byte(opcode.SetLocal), 0, 3, + // fib1 = fib2 + byte(opcode.GetLocal), 0, 2, + byte(opcode.SetLocal), 0, 1, + // fib2 = fibonacci + byte(opcode.GetLocal), 0, 3, + byte(opcode.SetLocal), 0, 2, + // i = i + 1 + byte(opcode.GetLocal), 0, 4, + byte(opcode.GetConstant), 0, 3, + byte(opcode.IntAdd), + byte(opcode.SetLocal), 0, 4, + // continue loop + byte(opcode.Jump), 0, 24, + // return fibonacci + byte(opcode.GetLocal), 0, 3, + byte(opcode.ReturnValue), + }, + compiler.functions[0].code, + ) + + require.Equal(t, + []*bbq.Constant{ + { + Data: []byte{0x1}, + Kind: constantkind.Int, + }, + { + Data: []byte{0x1}, + Kind: constantkind.Int, + }, + { + Data: []byte{0x2}, + Kind: constantkind.Int, + }, + { + Data: []byte{0x1}, + Kind: constantkind.Int, + }, + }, + program.Constants, + ) +} + +func TestCompileBreak(t *testing.T) { + + t.Parallel() + + checker, err := ParseAndCheck(t, ` + fun test(): Int { + var i = 0 + while true { + if i > 3 { + break + } + i = i + 1 + } + return i + } + `) + require.NoError(t, err) + + compiler := NewCompiler(checker.Program, checker.Elaboration) + program := compiler.Compile() + + require.Len(t, program.Functions, 1) + require.Equal(t, + []byte{ + // var i = 0 + byte(opcode.GetConstant), 0, 0, + byte(opcode.SetLocal), 0, 0, + // while true + byte(opcode.True), + byte(opcode.JumpIfFalse), 0, 36, + // if i > 3 + byte(opcode.GetLocal), 0, 0, + byte(opcode.GetConstant), 0, 1, + byte(opcode.IntGreater), + byte(opcode.JumpIfFalse), 0, 23, + // break + byte(opcode.Jump), 0, 36, + // i = i + 1 + byte(opcode.GetLocal), 0, 0, + byte(opcode.GetConstant), 0, 2, + byte(opcode.IntAdd), + byte(opcode.SetLocal), 0, 0, + // repeat + byte(opcode.Jump), 0, 6, + // return i + byte(opcode.GetLocal), 0, 0, + byte(opcode.ReturnValue), + }, + compiler.functions[0].code, + ) + + require.Equal(t, + []*bbq.Constant{ + { + Data: []byte{0x0}, + Kind: constantkind.Int, + }, + { + Data: []byte{0x3}, + Kind: constantkind.Int, + }, + { + Data: []byte{0x1}, + Kind: constantkind.Int, + }, + }, + program.Constants, + ) +} + +func TestCompileContinue(t *testing.T) { + + t.Parallel() + + checker, err := ParseAndCheck(t, ` + fun test(): Int { + var i = 0 + while true { + i = i + 1 + if i < 3 { + continue + } + break + } + return i + } + `) + require.NoError(t, err) + + compiler := NewCompiler(checker.Program, checker.Elaboration) + program := compiler.Compile() + + require.Len(t, program.Functions, 1) + require.Equal(t, + []byte{ + // var i = 0 + byte(opcode.GetConstant), 0, 0, + byte(opcode.SetLocal), 0, 0, + // while true + byte(opcode.True), + byte(opcode.JumpIfFalse), 0, 39, + // i = i + 1 + byte(opcode.GetLocal), 0, 0, + byte(opcode.GetConstant), 0, 1, + byte(opcode.IntAdd), + byte(opcode.SetLocal), 0, 0, + // if i < 3 + byte(opcode.GetLocal), 0, 0, + byte(opcode.GetConstant), 0, 2, + byte(opcode.IntLess), + byte(opcode.JumpIfFalse), 0, 33, + // continue + byte(opcode.Jump), 0, 6, + // break + byte(opcode.Jump), 0, 39, + // repeat + byte(opcode.Jump), 0, 6, + // return i + byte(opcode.GetLocal), 0, 0, + byte(opcode.ReturnValue), + }, + compiler.functions[0].code, + ) + + require.Equal(t, + []*bbq.Constant{ + { + Data: []byte{0x0}, + Kind: constantkind.Int, + }, + { + Data: []byte{0x1}, + Kind: constantkind.Int, + }, + { + Data: []byte{0x3}, + Kind: constantkind.Int, + }, + }, + program.Constants, + ) +} diff --git a/runtime/bbq/compiler/constant.go b/runtime/bbq/compiler/constant.go new file mode 100644 index 0000000000..63984ccd1b --- /dev/null +++ b/runtime/bbq/compiler/constant.go @@ -0,0 +1,27 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package compiler + +import "github.com/onflow/cadence/runtime/bbq/constantkind" + +type constant struct { + index uint16 + data []byte + kind constantkind.Constant +} diff --git a/runtime/bbq/compiler/function.go b/runtime/bbq/compiler/function.go new file mode 100644 index 0000000000..329cd32b2e --- /dev/null +++ b/runtime/bbq/compiler/function.go @@ -0,0 +1,66 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package compiler + +import ( + "math" + + "github.com/onflow/cadence/runtime/activations" + "github.com/onflow/cadence/runtime/bbq/opcode" + "github.com/onflow/cadence/runtime/errors" +) + +type function struct { + name string + localCount uint16 + // TODO: use byte.Buffer? + code []byte + locals *activations.Activations[*local] + parameterCount uint16 +} + +func newFunction(name string, parameterCount uint16) *function { + return &function{ + name: name, + parameterCount: parameterCount, + locals: activations.NewActivations[*local](nil), + } +} + +func (f *function) emit(opcode opcode.Opcode, args ...byte) int { + offset := len(f.code) + f.code = append(f.code, byte(opcode)) + f.code = append(f.code, args...) + return offset +} + +func (f *function) declareLocal(name string) *local { + if f.localCount >= math.MaxUint16 { + panic(errors.NewDefaultUserError("invalid local declaration")) + } + index := f.localCount + f.localCount++ + local := &local{index: index} + f.locals.Set(name, local) + return local +} + +func (f *function) findLocal(name string) *local { + return f.locals.Find(name) +} diff --git a/runtime/bbq/compiler/global.go b/runtime/bbq/compiler/global.go new file mode 100644 index 0000000000..1fd2591ecc --- /dev/null +++ b/runtime/bbq/compiler/global.go @@ -0,0 +1,23 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package compiler + +type global struct { + index uint16 +} diff --git a/runtime/bbq/compiler/local.go b/runtime/bbq/compiler/local.go new file mode 100644 index 0000000000..30b467fbd5 --- /dev/null +++ b/runtime/bbq/compiler/local.go @@ -0,0 +1,23 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package compiler + +type local struct { + index uint16 +} diff --git a/runtime/bbq/compiler/loop.go b/runtime/bbq/compiler/loop.go new file mode 100644 index 0000000000..3fd4197f44 --- /dev/null +++ b/runtime/bbq/compiler/loop.go @@ -0,0 +1,24 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package compiler + +type loop struct { + breaks []int + start int +} diff --git a/runtime/bbq/constant.go b/runtime/bbq/constant.go new file mode 100644 index 0000000000..f90600b099 --- /dev/null +++ b/runtime/bbq/constant.go @@ -0,0 +1,26 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package bbq + +import "github.com/onflow/cadence/runtime/bbq/constantkind" + +type Constant struct { + Data []byte + Kind constantkind.Constant +} diff --git a/runtime/bbq/constantkind/constantkind.go b/runtime/bbq/constantkind/constantkind.go new file mode 100644 index 0000000000..a5b6afd261 --- /dev/null +++ b/runtime/bbq/constantkind/constantkind.go @@ -0,0 +1,135 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package constantkind + +import ( + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/sema" +) + +type Constant uint8 + +const ( + Unknown Constant = iota + String + + // Int* + Int + Int8 + Int16 + Int32 + Int64 + Int128 + Int256 + _ + + // UInt* + UInt + UInt8 + UInt16 + UInt32 + UInt64 + UInt128 + UInt256 + _ + + // Word* + _ + Word8 + Word16 + Word32 + Word64 + _ // future: Word128 + _ // future: Word256 + _ + + // Fix* + _ + _ // future: Fix8 + _ // future: Fix16 + _ // future: Fix32 + Fix64 + _ // future: Fix128 + _ // future: Fix256 + _ + + // UFix* + _ + _ // future: UFix8 + _ // future: UFix16 + _ // future: UFix32 + UFix64 + _ // future: UFix128 + _ // future: UFix256 +) + +func FromSemaType(ty sema.Type) Constant { + switch ty { + // Int* + case sema.IntType: + return Int + case sema.Int8Type: + return Int8 + case sema.Int16Type: + return Int16 + case sema.Int32Type: + return Int32 + case sema.Int64Type: + return Int64 + case sema.Int128Type: + return Int128 + case sema.Int256Type: + return Int256 + + // UInt* + case sema.UIntType: + return UInt + case sema.UInt8Type: + return UInt8 + case sema.UInt16Type: + return UInt16 + case sema.UInt32Type: + return UInt32 + case sema.UInt64Type: + return UInt64 + case sema.UInt128Type: + return UInt128 + case sema.UInt256Type: + return UInt256 + + // Word* + case sema.Word8Type: + return Word8 + case sema.Word16Type: + return Word16 + case sema.Word32Type: + return Word32 + case sema.Word64Type: + return Word64 + + // Fix* + case sema.Fix64Type: + return Fix64 + case sema.UFix64Type: + return UFix64 + + default: + panic(errors.NewUnreachableError()) + } +} diff --git a/runtime/bbq/function.go b/runtime/bbq/function.go new file mode 100644 index 0000000000..9d7686104e --- /dev/null +++ b/runtime/bbq/function.go @@ -0,0 +1,26 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package bbq + +type Function struct { + Name string + Code []byte + ParameterCount uint16 + LocalCount uint16 +} diff --git a/runtime/bbq/leb128/leb128.go b/runtime/bbq/leb128/leb128.go new file mode 100644 index 0000000000..735146a766 --- /dev/null +++ b/runtime/bbq/leb128/leb128.go @@ -0,0 +1,220 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package leb128 + +import ( + "fmt" +) + +// max32bitByteCount is the maximum number of bytes a 32-bit integer +// (signed or unsigned) may be encoded as. From +// https://webassembly.github.io/spec/core/binary/values.html#binary-int: +// +// "the total number of bytes encoding a value of type uN must not exceed ceil(N/7) bytes" +// "the total number of bytes encoding a value of type sN must not exceed ceil(N/7) bytes" +const max32bitByteCount = 5 + +// max64bitByteCount is the maximum number of bytes a 64-bit integer +// (signed or unsigned) may be encoded as. From +// https://webassembly.github.io/spec/core/binary/values.html#binary-int: +// +// "the total number of bytes encoding a value of type uN must not exceed ceil(N/7) bytes" +// "the total number of bytes encoding a value of type sN must not exceed ceil(N/7) bytes" +const max64bitByteCount = 10 + +// AppendUint32 encodes and writes the given unsigned 32-bit integer +// in canonical (with the fewest bytes possible) unsigned little-endian base-128 format +func AppendUint32(data []byte, v uint32) []byte { + if v < 128 { + data = append(data, uint8(v)) + return data + } + + more := true + for more { + // low order 7 bits of value + c := uint8(v & 0x7f) + v >>= 7 + // more bits to come? + more = v != 0 + if more { + // set high order bit of byte + c |= 0x80 + } + data = append(data, c) + } + return data +} + +// AppendUint64 encodes and writes the given unsigned 64-bit integer +// in canonical (with the fewest bytes possible) unsigned little-endian base-128 format +func AppendUint64(data []byte, v uint64) []byte { + if v < 128 { + data = append(data, uint8(v)) + return data + } + + more := true + for more { + // low order 7 bits of value + c := uint8(v & 0x7f) + v >>= 7 + // more bits to come? + more = v != 0 + if more { + // set high order bit of byte + c |= 0x80 + } + // emit byte + data = append(data, c) + } + return data +} + +// AppendUint32FixedLength encodes and writes the given unsigned 32-bit integer +// in non-canonical (fixed-size, instead of with the fewest bytes possible) +// unsigned little-endian base-128 format +func AppendUint32FixedLength(data []byte, v uint32, length int) ([]byte, error) { + for i := 0; i < length; i++ { + c := uint8(v & 0x7f) + v >>= 7 + if i < length-1 { + c |= 0x80 + } + data = append(data, c) + } + if v != 0 { + return nil, fmt.Errorf("length too small: %d", length) + } + return data, nil +} + +// ReadUint32 reads and decodes an unsigned 32-bit integer +func ReadUint32(data []byte) (result uint32, count int, err error) { + var shift uint + // only read up to maximum number of bytes + for i := 0; i < max32bitByteCount; i++ { + if i >= len(data) { + return 0, 0, fmt.Errorf("data too short: %d", len(data)) + } + b := data[i] + count++ + result |= (uint32(b & 0x7F)) << shift + // check high order bit of byte + if b&0x80 == 0 { + break + } + shift += 7 + } + return result, count, nil +} + +// ReadUint64 reads and decodes an unsigned 64-bit integer +func ReadUint64(data []byte) (result uint64, count int, err error) { + var shift uint + // only read up to maximum number of bytes + for i := 0; i < max64bitByteCount; i++ { + if i >= len(data) { + return 0, 0, fmt.Errorf("data too short: %d", len(data)) + } + b := data[i] + count++ + result |= (uint64(b & 0x7F)) << shift + // check high order bit of byte + if b&0x80 == 0 { + break + } + shift += 7 + } + return result, count, nil +} + +// AppendInt32 encodes and writes the given signed 32-bit integer +// in canonical (with the fewest bytes possible) signed little-endian base-128 format +func AppendInt32(data []byte, v int32) []byte { + more := true + for more { + // low order 7 bits of value + c := uint8(v & 0x7f) + sign := uint8(v & 0x40) + v >>= 7 + more = !((v == 0 && sign == 0) || (v == -1 && sign != 0)) + if more { + c |= 0x80 + } + data = append(data, c) + } + return data +} + +// AppendInt64 encodes and writes the given signed 64-bit integer +// in canonical (with the fewest bytes possible) signed little-endian base-128 format +func AppendInt64(data []byte, v int64) []byte { + more := true + for more { + // low order 7 bits of value + c := uint8(v & 0x7f) + sign := uint8(v & 0x40) + v >>= 7 + more = !((v == 0 && sign == 0) || (v == -1 && sign != 0)) + if more { + c |= 0x80 + } + data = append(data, c) + } + return data +} + +// ReadInt32 reads and decodes a signed 32-bit integer +func ReadInt32(data []byte) (result int32, count int, err error) { + var b byte = 0x80 + var signBits int32 = -1 + for i := 0; (b&0x80 == 0x80) && i < max32bitByteCount; i++ { + if i >= len(data) { + return 0, 0, fmt.Errorf("data too short: %d", len(data)) + } + b = data[i] + count++ + result += int32(b&0x7f) << (i * 7) + signBits <<= 7 + } + if ((signBits >> 1) & result) != 0 { + result += signBits + } + return result, count, nil +} + +// ReadInt64 reads and decodes a signed 64-bit integer +func ReadInt64(data []byte) (result int64, count int, err error) { + var b byte = 0x80 + var signBits int64 = -1 + for i := 0; (b&0x80 == 0x80) && i < max64bitByteCount; i++ { + if i >= len(data) { + return 0, 0, fmt.Errorf("data too short: %d", len(data)) + } + b = data[i] + count++ + result += int64(b&0x7f) << (i * 7) + signBits <<= 7 + } + if ((signBits >> 1) & result) != 0 { + result += signBits + } + return result, count, nil +} diff --git a/runtime/bbq/leb128/leb128_test.go b/runtime/bbq/leb128/leb128_test.go new file mode 100644 index 0000000000..b5a68dc29f --- /dev/null +++ b/runtime/bbq/leb128/leb128_test.go @@ -0,0 +1,292 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package leb128 + +import ( + "math" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUint32(t *testing.T) { + + t.Parallel() + + t.Run("DWARF spec + more", func(t *testing.T) { + + t.Parallel() + + // DWARF Debugging Information Format, Version 3, page 140 + + for v, expected := range map[uint32][]byte{ + 0: {0x00}, + 1: {0x01}, + 2: {2}, + 63: {0x3f}, + 64: {0x40}, + 127: {127}, + 128: {0 + 0x80, 1}, + 129: {1 + 0x80, 1}, + 130: {2 + 0x80, 1}, + 0x90: {0x90, 0x01}, + 0x100: {0x80, 0x02}, + 0x101: {0x81, 0x02}, + 0xff: {0xff, 0x01}, + 12857: {57 + 0x80, 100}, + } { + var b []byte + b = AppendUint32(b, v) + require.Equal(t, expected, b) + + actual, n, err := ReadUint32(b) + require.NoError(t, err) + require.Equal(t, v, actual) + require.Equal(t, len(b), n) + } + }) + + t.Run("write: max byte count", func(t *testing.T) { + + t.Parallel() + + // This test ensures that only up to the maximum number of bytes are written + // when writing a LEB128-encoded 32-bit number (see max32bitByteCount), + // i.e. test that only up to 5 bytes are written. + + var b []byte + AppendUint32(b, math.MaxUint32) + require.GreaterOrEqual(t, max32bitByteCount, len(b)) + }) + + t.Run("read: max byte count", func(t *testing.T) { + + t.Parallel() + + // This test ensures that only up to the maximum number of bytes are read + // when reading a LEB128-encoded 32-bit number (see max32bitByteCount), + // i.e. test that only 5 of the 8 given bytes are read, + // to ensure the LEB128 parser doesn't keep reading infinitely. + + b := []byte{0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88} + _, n, err := ReadUint32(b) + require.NoError(t, err) + require.Equal(t, max32bitByteCount, n) + }) +} + +func TestBuf_Uint64LEB128(t *testing.T) { + + t.Parallel() + + t.Run("DWARF spec + more", func(t *testing.T) { + + t.Parallel() + + // DWARF Debugging Information Format, Version 3, page 140 + + for v, expected := range map[uint64][]byte{ + 0: {0x00}, + 1: {0x01}, + 2: {2}, + 63: {0x3f}, + 64: {0x40}, + 127: {127}, + 128: {0 + 0x80, 1}, + 129: {1 + 0x80, 1}, + 130: {2 + 0x80, 1}, + 0x90: {0x90, 0x01}, + 0x100: {0x80, 0x02}, + 0x101: {0x81, 0x02}, + 0xff: {0xff, 0x01}, + 12857: {57 + 0x80, 100}, + } { + var b []byte + b = AppendUint64(b, v) + require.Equal(t, expected, b) + + actual, n, err := ReadUint64(b) + require.NoError(t, err) + require.Equal(t, v, actual) + require.Equal(t, len(b), n) + } + }) + + t.Run("write: max byte count", func(t *testing.T) { + + t.Parallel() + + var b []byte + b = AppendUint64(b, math.MaxUint64) + require.GreaterOrEqual(t, max64bitByteCount, len(b)) + }) + + t.Run("read: max byte count", func(t *testing.T) { + + t.Parallel() + + b := []byte{ + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, + } + _, n, err := ReadUint64(b) + require.NoError(t, err) + require.Equal(t, max64bitByteCount, n) + }) +} + +func TestBuf_Int32(t *testing.T) { + + t.Parallel() + + t.Run("DWARF spec + more", func(t *testing.T) { + + t.Parallel() + + // DWARF Debugging Information Format, Version 3, page 141 + + for v, expected := range map[int32][]byte{ + 0: {0x00}, + 1: {0x01}, + -1: {0x7f}, + 2: {2}, + -2: {0x7e}, + 63: {0x3f}, + -63: {0x41}, + 64: {0xc0, 0x00}, + -64: {0x40}, + -65: {0xbf, 0x7f}, + 127: {127 + 0x80, 0}, + -127: {1 + 0x80, 0x7f}, + 128: {0 + 0x80, 1}, + -128: {0 + 0x80, 0x7f}, + 129: {1 + 0x80, 1}, + -129: {0x7f + 0x80, 0x7e}, + -12345: {0xc7, 0x9f, 0x7f}, + } { + var b []byte + b = AppendInt32(b, v) + require.Equal(t, expected, b) + + actual, n, err := ReadInt32(b) + require.NoError(t, err) + require.Equal(t, v, actual) + require.Equal(t, len(b), n) + } + }) + + t.Run("write: max byte count", func(t *testing.T) { + + t.Parallel() + + // This test ensures that only up to the maximum number of bytes are written + // when writing a LEB128-encoded 32-bit number (see max32bitByteCount), + // i.e. test that only up to 5 bytes are written. + + var b []byte + b = AppendInt32(b, math.MaxInt32) + require.GreaterOrEqual(t, max32bitByteCount, len(b)) + + var b2 []byte + b2 = AppendInt32(b2, math.MinInt32) + require.GreaterOrEqual(t, max32bitByteCount, len(b2)) + }) + + t.Run("read: max byte count", func(t *testing.T) { + + t.Parallel() + + // This test ensures that only up to the maximum number of bytes are read + // when reading a LEB128-encoded 32-bit number (see max32bitByteCount), + // i.e. test that only 5 of the 8 given bytes are read, + // to ensure the LEB128 parser doesn't keep reading infinitely. + + b := []byte{0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88} + _, n, err := ReadInt32(b) + require.NoError(t, err) + require.Equal(t, max32bitByteCount, n) + }) +} + +func TestBuf_Int64LEB128(t *testing.T) { + + t.Parallel() + + t.Run("DWARF spec + more", func(t *testing.T) { + + t.Parallel() + + // DWARF Debugging Information Format, Version 3, page 141 + + for v, expected := range map[int64][]byte{ + 0: {0x00}, + 1: {0x01}, + -1: {0x7f}, + 2: {2}, + -2: {0x7e}, + 63: {0x3f}, + -63: {0x41}, + 64: {0xc0, 0x00}, + -64: {0x40}, + -65: {0xbf, 0x7f}, + 127: {127 + 0x80, 0}, + -127: {1 + 0x80, 0x7f}, + 128: {0 + 0x80, 1}, + -128: {0 + 0x80, 0x7f}, + 129: {1 + 0x80, 1}, + -129: {0x7f + 0x80, 0x7e}, + -12345: {0xc7, 0x9f, 0x7f}, + } { + var b []byte + b = AppendInt64(b, v) + require.Equal(t, expected, b) + + actual, n, err := ReadInt64(b) + require.NoError(t, err) + require.Equal(t, v, actual) + require.Equal(t, len(b), n) + } + }) + + t.Run("write: max byte count", func(t *testing.T) { + + t.Parallel() + + var b []byte + b = AppendInt64(b, math.MaxInt64) + require.GreaterOrEqual(t, max64bitByteCount, len(b)) + + var b2 []byte + b2 = AppendInt64(b2, math.MinInt64) + require.GreaterOrEqual(t, max64bitByteCount, len(b2)) + }) + + t.Run("read: max byte count", func(t *testing.T) { + + t.Parallel() + + b := []byte{ + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, + } + _, n, err := ReadInt64(b) + require.NoError(t, err) + require.Equal(t, max64bitByteCount, n) + }) +} diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go new file mode 100644 index 0000000000..cba7d2aedc --- /dev/null +++ b/runtime/bbq/opcode/opcode.go @@ -0,0 +1,54 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package opcode + +//go:generate go run golang.org/x/tools/cmd/stringer -type=Opcode + +type Opcode byte + +const ( + Unknown Opcode = iota + + Return + ReturnValue + Jump + JumpIfFalse + + IntAdd + IntSubtract + IntMultiply + IntDivide + IntMod + IntEqual + IntNotEqual + IntLess + IntGreater + IntLessOrEqual + IntGreaterOrEqual + + GetConstant + True + False + + GetLocal + SetLocal + GetGlobal + + Call +) diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go new file mode 100644 index 0000000000..d64bc27039 --- /dev/null +++ b/runtime/bbq/opcode/opcode_string.go @@ -0,0 +1,45 @@ +// Code generated by "stringer -type=Opcode"; DO NOT EDIT. + +package opcode + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Unknown-0] + _ = x[Return-1] + _ = x[ReturnValue-2] + _ = x[Jump-3] + _ = x[JumpIfFalse-4] + _ = x[IntAdd-5] + _ = x[IntSubtract-6] + _ = x[IntMultiply-7] + _ = x[IntDivide-8] + _ = x[IntMod-9] + _ = x[IntEqual-10] + _ = x[IntNotEqual-11] + _ = x[IntLess-12] + _ = x[IntGreater-13] + _ = x[IntLessOrEqual-14] + _ = x[IntGreaterOrEqual-15] + _ = x[GetConstant-16] + _ = x[True-17] + _ = x[False-18] + _ = x[GetLocal-19] + _ = x[SetLocal-20] + _ = x[GetGlobal-21] + _ = x[Call-22] +} + +const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalCall" + +var _Opcode_index = [...]uint8{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 198} + +func (i Opcode) String() string { + if i >= Opcode(len(_Opcode_index)-1) { + return "Opcode(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Opcode_name[_Opcode_index[i]:_Opcode_index[i+1]] +} diff --git a/runtime/bbq/program.go b/runtime/bbq/program.go new file mode 100644 index 0000000000..90964f68ed --- /dev/null +++ b/runtime/bbq/program.go @@ -0,0 +1,24 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package bbq + +type Program struct { + Functions []*Function + Constants []*Constant +} diff --git a/runtime/bbq/vm/callframe.go b/runtime/bbq/vm/callframe.go new file mode 100644 index 0000000000..6bf88c405a --- /dev/null +++ b/runtime/bbq/vm/callframe.go @@ -0,0 +1,37 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/cadence/runtime/bbq" +) + +type callFrame struct { + parent *callFrame + locals []Value + function *bbq.Function + ip uint16 +} + +func (f *callFrame) getUint16() uint16 { + first := f.function.Code[f.ip] + last := f.function.Code[f.ip+1] + f.ip += 2 + return uint16(first)<<8 | uint16(last) +} diff --git a/runtime/bbq/vm/value.go b/runtime/bbq/vm/value.go new file mode 100644 index 0000000000..1821643b1e --- /dev/null +++ b/runtime/bbq/vm/value.go @@ -0,0 +1,74 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/cadence/runtime/bbq" +) + +type Value interface { + isValue() +} + +var trueValue Value = BoolValue(true) +var falseValue Value = BoolValue(false) + +type BoolValue bool + +var _ Value = BoolValue(true) + +func (BoolValue) isValue() {} + +type IntValue struct { + smallInt int64 +} + +var _ Value = IntValue{} + +func (IntValue) isValue() {} + +func (v IntValue) Add(other IntValue) Value { + return IntValue{v.smallInt + other.smallInt} +} + +func (v IntValue) Subtract(other IntValue) Value { + return IntValue{v.smallInt - other.smallInt} +} + +func (v IntValue) Less(other IntValue) Value { + if v.smallInt < other.smallInt { + return trueValue + } + return falseValue +} + +func (v IntValue) Greater(other IntValue) Value { + if v.smallInt > other.smallInt { + return trueValue + } + return falseValue +} + +type FunctionValue struct { + Function *bbq.Function +} + +var _ Value = FunctionValue{} + +func (FunctionValue) isValue() {} diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go new file mode 100644 index 0000000000..2be39c0509 --- /dev/null +++ b/runtime/bbq/vm/vm.go @@ -0,0 +1,314 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/bbq/constantkind" + "github.com/onflow/cadence/runtime/bbq/leb128" + "github.com/onflow/cadence/runtime/bbq/opcode" + "github.com/onflow/cadence/runtime/errors" +) + +type VM struct { + Program *bbq.Program + globals []Value + constants []Value + functions map[string]*bbq.Function + callFrame *callFrame + stack []Value +} + +func NewVM(program *bbq.Program) *VM { + functions := indexFunctions(program.Functions) + + // TODO: include non-function globals + globals := make([]Value, 0, len(functions)) + for _, function := range functions { + // TODO: + globals = append(globals, FunctionValue{Function: function}) + } + + return &VM{ + Program: program, + globals: globals, + functions: functions, + constants: make([]Value, len(program.Constants)), + } +} + +func indexFunctions(functions []*bbq.Function) map[string]*bbq.Function { + indexedFunctions := make(map[string]*bbq.Function, len(functions)) + for _, function := range functions { + indexedFunctions[function.Name] = function + } + return indexedFunctions +} + +func (vm *VM) push(value Value) { + vm.stack = append(vm.stack, value) +} + +func (vm *VM) pop() Value { + lastIndex := len(vm.stack) - 1 + value := vm.stack[lastIndex] + vm.stack[lastIndex] = nil + vm.stack = vm.stack[:lastIndex] + return value +} + +func (vm *VM) dropN(count int) { + stackHeight := len(vm.stack) + for i := 1; i <= count; i++ { + vm.stack[stackHeight-i] = nil + } + vm.stack = vm.stack[:stackHeight-count] +} + +func (vm *VM) peekPop() (Value, Value) { + lastIndex := len(vm.stack) - 1 + return vm.stack[lastIndex-1], vm.pop() +} + +func (vm *VM) replaceTop(value Value) { + lastIndex := len(vm.stack) - 1 + vm.stack[lastIndex] = value +} + +func (vm *VM) pushCallFrame(function *bbq.Function, arguments []Value) { + + locals := make([]Value, function.LocalCount) + for i, argument := range arguments { + locals[i] = argument + } + + callFrame := &callFrame{ + parent: vm.callFrame, + locals: locals, + function: function, + } + vm.callFrame = callFrame +} + +func (vm *VM) popCallFrame() { + vm.callFrame = vm.callFrame.parent +} + +func (vm *VM) Invoke(name string, arguments ...Value) (Value, error) { + function, ok := vm.functions[name] + if !ok { + return nil, errors.NewDefaultUserError("unknown function") + } + + if len(arguments) != int(function.ParameterCount) { + return nil, errors.NewDefaultUserError("wrong number of arguments") + } + + vm.pushCallFrame(function, arguments) + + vm.run() + + if len(vm.stack) == 0 { + return nil, nil + } + + return vm.pop(), nil +} + +type vmOp func(*VM) + +var vmOps = [...]vmOp{ + opcode.ReturnValue: opReturnValue, + opcode.Jump: opJump, + opcode.JumpIfFalse: opJumpIfFalse, + opcode.IntAdd: opBinaryIntAdd, + opcode.IntSubtract: opBinaryIntSubtract, + opcode.IntLess: opBinaryIntLess, + opcode.IntGreater: opBinaryIntGreater, + opcode.True: opTrue, + opcode.False: opFalse, + opcode.GetConstant: opGetConstant, + opcode.GetLocal: opGetLocal, + opcode.SetLocal: opSetLocal, + opcode.GetGlobal: opGetGlobal, + opcode.Call: opCall, +} + +func opReturnValue(vm *VM) { + value := vm.pop() + vm.popCallFrame() + vm.push(value) +} + +func opJump(vm *VM) { + callFrame := vm.callFrame + target := callFrame.getUint16() + callFrame.ip = target +} + +func opJumpIfFalse(vm *VM) { + callFrame := vm.callFrame + target := callFrame.getUint16() + value := vm.pop().(BoolValue) + if !value { + callFrame.ip = target + } +} + +func opBinaryIntAdd(vm *VM) { + left, right := vm.peekPop() + leftNumber := left.(IntValue) + rightNumber := right.(IntValue) + vm.replaceTop(leftNumber.Add(rightNumber)) +} + +func opBinaryIntSubtract(vm *VM) { + left, right := vm.peekPop() + leftNumber := left.(IntValue) + rightNumber := right.(IntValue) + vm.replaceTop(leftNumber.Subtract(rightNumber)) +} + +func opBinaryIntLess(vm *VM) { + left, right := vm.peekPop() + leftNumber := left.(IntValue) + rightNumber := right.(IntValue) + vm.replaceTop(leftNumber.Less(rightNumber)) +} + +func opBinaryIntGreater(vm *VM) { + left, right := vm.peekPop() + leftNumber := left.(IntValue) + rightNumber := right.(IntValue) + vm.replaceTop(leftNumber.Greater(rightNumber)) +} + +func opTrue(vm *VM) { + vm.push(trueValue) +} + +func opFalse(vm *VM) { + vm.push(falseValue) +} + +func opGetConstant(vm *VM) { + callFrame := vm.callFrame + index := callFrame.getUint16() + constant := vm.constants[index] + if constant == nil { + constant = vm.initializeConstant(index) + } + vm.push(constant) +} + +func opGetLocal(vm *VM) { + callFrame := vm.callFrame + index := callFrame.getUint16() + local := callFrame.locals[index] + vm.push(local) +} + +func opSetLocal(vm *VM) { + callFrame := vm.callFrame + index := callFrame.getUint16() + callFrame.locals[index] = vm.pop() +} + +func opGetGlobal(vm *VM) { + callFrame := vm.callFrame + index := callFrame.getUint16() + vm.push(vm.globals[index]) +} + +func opCall(vm *VM) { + // TODO: support any function value + value := vm.pop().(FunctionValue) + stackHeight := len(vm.stack) + parameterCount := int(value.Function.ParameterCount) + arguments := vm.stack[stackHeight-parameterCount:] + vm.pushCallFrame(value.Function, arguments) + vm.dropN(parameterCount) +} + +func (vm *VM) run() { + for { + + callFrame := vm.callFrame + + if callFrame == nil || + int(callFrame.ip) >= len(callFrame.function.Code) { + + return + } + + op := opcode.Opcode(callFrame.function.Code[callFrame.ip]) + callFrame.ip++ + + switch op { + case opcode.ReturnValue: + opReturnValue(vm) + case opcode.Jump: + opJump(vm) + case opcode.JumpIfFalse: + opJumpIfFalse(vm) + case opcode.IntAdd: + opBinaryIntAdd(vm) + case opcode.IntSubtract: + opBinaryIntSubtract(vm) + case opcode.IntLess: + opBinaryIntLess(vm) + case opcode.IntGreater: + opBinaryIntGreater(vm) + case opcode.True: + opTrue(vm) + case opcode.False: + opFalse(vm) + case opcode.GetConstant: + opGetConstant(vm) + case opcode.GetLocal: + opGetLocal(vm) + case opcode.SetLocal: + opSetLocal(vm) + case opcode.GetGlobal: + opGetGlobal(vm) + case opcode.Call: + opCall(vm) + default: + panic(errors.NewUnreachableError()) + } + + // Faster in Go <1.19: + // vmOps[op](vm) + } +} + +func (vm *VM) initializeConstant(index uint16) (value Value) { + constant := vm.Program.Constants[index] + switch constant.Kind { + case constantkind.Int: + // TODO: + smallInt, _, _ := leb128.ReadInt64(constant.Data) + value = IntValue{smallInt} + default: + // TODO: + panic(errors.NewUnreachableError()) + } + vm.constants[index] = value + return value +} diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go new file mode 100644 index 0000000000..4d165d5d95 --- /dev/null +++ b/runtime/bbq/vm/vm_test.go @@ -0,0 +1,199 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence/runtime/bbq/compiler" + . "github.com/onflow/cadence/runtime/tests/checker" +) + +const recursiveFib = ` + fun fib(_ n: Int): Int { + if n < 2 { + return n + } + return fib(n - 1) + fib(n - 2) + } +` + +func TestRecursionFib(t *testing.T) { + + t.Parallel() + + checker, err := ParseAndCheck(t, recursiveFib) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vm := NewVM(program) + + result, err := vm.Invoke( + "fib", + IntValue{7}, + ) + require.NoError(t, err) + require.Equal(t, IntValue{13}, result) +} + +func BenchmarkRecursionFib(b *testing.B) { + + checker, err := ParseAndCheck(b, recursiveFib) + require.NoError(b, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vm := NewVM(program) + + b.ReportAllocs() + b.ResetTimer() + + expected := IntValue{377} + + for i := 0; i < b.N; i++ { + + result, err := vm.Invoke( + "fib", + IntValue{14}, + ) + require.NoError(b, err) + require.Equal(b, expected, result) + } +} + +const imperativeFib = ` + fun fib(_ n: Int): Int { + var fib1 = 1 + var fib2 = 1 + var fibonacci = fib1 + var i = 2 + while i < n { + fibonacci = fib1 + fib2 + fib1 = fib2 + fib2 = fibonacci + i = i + 1 + } + return fibonacci + } +` + +func TestImperativeFib(t *testing.T) { + + t.Parallel() + + checker, err := ParseAndCheck(t, imperativeFib) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vm := NewVM(program) + + result, err := vm.Invoke( + "fib", + IntValue{7}, + ) + require.NoError(t, err) + require.Equal(t, IntValue{13}, result) +} + +func BenchmarkImperativeFib(b *testing.B) { + + checker, err := ParseAndCheck(b, imperativeFib) + require.NoError(b, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vm := NewVM(program) + + b.ReportAllocs() + b.ResetTimer() + + var value Value = IntValue{14} + + for i := 0; i < b.N; i++ { + _, err := vm.Invoke("fib", value) + require.NoError(b, err) + } +} + +func TestBreak(t *testing.T) { + + t.Parallel() + + checker, err := ParseAndCheck(t, ` + fun test(): Int { + var i = 0 + while true { + if i > 3 { + break + } + i = i + 1 + } + return i + } + `) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vm := NewVM(program) + + result, err := vm.Invoke("test") + require.NoError(t, err) + + require.Equal(t, IntValue{4}, result) +} + +func TestContinue(t *testing.T) { + + t.Parallel() + + checker, err := ParseAndCheck(t, ` + fun test(): Int { + var i = 0 + while true { + i = i + 1 + if i < 3 { + continue + } + break + } + return i + } + `) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vm := NewVM(program) + + result, err := vm.Invoke("test") + require.NoError(t, err) + + require.Equal(t, IntValue{3}, result) +} diff --git a/runtime/tests/interpreter/fib_test.go b/runtime/tests/interpreter/fib_test.go new file mode 100644 index 0000000000..009b7b584a --- /dev/null +++ b/runtime/tests/interpreter/fib_test.go @@ -0,0 +1,71 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package interpreter_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence/runtime/interpreter" +) + +const imperativeFib = ` + fun fib(_ n: Int): Int { + var fib1 = 1 + var fib2 = 1 + var fibonacci = fib1 + var i = 2 + while i < n { + fibonacci = fib1 + fib2 + fib1 = fib2 + fib2 = fibonacci + i = i + 1 + } + return fibonacci + } +` + +func TestImperativeFib(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, imperativeFib) + + var value interpreter.Value = interpreter.NewUnmeteredIntValueFromInt64(7) + + result, err := inter.Invoke("fib", value) + require.NoError(t, err) + require.Equal(t, interpreter.NewUnmeteredIntValueFromInt64(13), result) +} + +func BenchmarkImperativeFib(b *testing.B) { + + inter := parseCheckAndInterpret(b, imperativeFib) + + var value interpreter.Value = interpreter.NewUnmeteredIntValueFromInt64(14) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _, err := inter.Invoke("fib", value) + require.NoError(b, err) + } +} From 4afa28b3389df14a5cfba8fb5d865b034bd068e6 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Mon, 27 Feb 2023 12:09:51 -0800 Subject: [PATCH 02/89] Add struct init --- runtime/bbq/bytecode_printer.go | 109 ++++++++++++++++++ runtime/bbq/compiler/compiler.go | 87 ++++++++++++-- runtime/bbq/compiler/constant.go | 2 +- runtime/bbq/constant.go | 2 +- runtime/bbq/constantkind/constantkind.go | 8 +- .../bbq/constantkind/constantkind_string.go | 66 +++++++++++ runtime/bbq/opcode/opcode.go | 3 + runtime/bbq/opcode/opcode_string.go | 6 +- runtime/bbq/vm/value.go | 16 +++ runtime/bbq/vm/vm.go | 22 ++++ runtime/bbq/vm/vm_test.go | 65 +++++++++++ 11 files changed, 368 insertions(+), 18 deletions(-) create mode 100644 runtime/bbq/bytecode_printer.go create mode 100644 runtime/bbq/constantkind/constantkind_string.go diff --git a/runtime/bbq/bytecode_printer.go b/runtime/bbq/bytecode_printer.go new file mode 100644 index 0000000000..c570d32a0e --- /dev/null +++ b/runtime/bbq/bytecode_printer.go @@ -0,0 +1,109 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package bbq + +import ( + "fmt" + "strings" + + "github.com/onflow/cadence/runtime/bbq/constantkind" + "github.com/onflow/cadence/runtime/bbq/leb128" + "github.com/onflow/cadence/runtime/bbq/opcode" + "github.com/onflow/cadence/runtime/errors" +) + +type BytecodePrinter struct { + stringBuilder strings.Builder +} + +func (p *BytecodePrinter) PrintProgram(program *Program) string { + p.printConstantPool(program.Constants) + for _, function := range program.Functions { + p.printFunction(function) + p.stringBuilder.WriteRune('\n') + } + + return p.stringBuilder.String() +} + +func (p *BytecodePrinter) printFunction(function *Function) { + p.stringBuilder.WriteString("-- " + function.Name + " --\n") + p.printCode(function.Code) +} + +func (p *BytecodePrinter) printCode(codes []byte) { + for i := 0; i < len(codes); i++ { + code := codes[i] + opcodeString := opcode.Opcode(code).String() + + p.stringBuilder.WriteString(opcodeString) + + switch opcode.Opcode(code) { + + // opcodes with one operand + case opcode.GetConstant, + opcode.GetLocal, + opcode.SetLocal, + opcode.GetGlobal, + opcode.Jump, + opcode.JumpIfFalse: + + first := codes[i+1] + last := codes[i+2] + i += 2 + + operand := int(uint16(first)<<8 | uint16(last)) + p.stringBuilder.WriteString(" " + fmt.Sprint(operand)) + + // opcodes with no operands + default: + // do nothing + } + + p.stringBuilder.WriteRune('\n') + } +} + +func (p *BytecodePrinter) printConstantPool(constants []*Constant) { + p.stringBuilder.WriteString("-- Constant Pool --\n") + + for index, constant := range constants { + var constantStr string + + // TODO: duplicate of `VM.initializeConstant()` + switch constant.Kind { + case constantkind.Int: + smallInt, _, _ := leb128.ReadInt64(constant.Data) + constantStr = fmt.Sprint(smallInt) + case constantkind.String: + constantStr = string(constant.Data) + default: + panic(errors.NewUnreachableError()) + } + + p.stringBuilder.WriteString(fmt.Sprint(index)) + p.stringBuilder.WriteString(" | ") + p.stringBuilder.WriteString(constant.Kind.String()) + p.stringBuilder.WriteString(" | ") + p.stringBuilder.WriteString(constantStr) + p.stringBuilder.WriteRune('\n') + } + + p.stringBuilder.WriteRune('\n') +} diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index 2ede219c78..e32af99040 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -26,6 +26,7 @@ import ( "github.com/onflow/cadence/runtime/bbq/constantkind" "github.com/onflow/cadence/runtime/bbq/leb128" "github.com/onflow/cadence/runtime/bbq/opcode" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/sema" ) @@ -40,6 +41,8 @@ type Compiler struct { globals map[string]*global loops []*loop currentLoop *loop + + currentCompositeType *sema.CompositeType } var _ ast.DeclarationVisitor[struct{}] = &Compiler{} @@ -81,7 +84,7 @@ func (c *Compiler) addFunction(name string, parameterCount uint16) *function { return function } -func (c *Compiler) addConstant(kind constantkind.Constant, data []byte) *constant { +func (c *Compiler) addConstant(kind constantkind.ConstantKind, data []byte) *constant { count := len(c.constants) if count >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid constant declaration")) @@ -95,6 +98,12 @@ func (c *Compiler) addConstant(kind constantkind.Constant, data []byte) *constan return constant } +func (c *Compiler) stringConstLoad(str string) { + constant := c.addConstant(constantkind.String, []byte(str)) + first, second := encodeUint16(constant.index) + c.emit(opcode.GetConstant, first, second) +} + func (c *Compiler) emit(opcode opcode.Opcode, args ...byte) int { return c.currentFunction.emit(opcode, args...) } @@ -325,9 +334,10 @@ func (c *Compiler) VisitSwapStatement(_ *ast.SwapStatement) (_ struct{}) { panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitExpressionStatement(_ *ast.ExpressionStatement) (_ struct{}) { - // TODO - panic(errors.NewUnreachableError()) +func (c *Compiler) VisitExpressionStatement(statement *ast.ExpressionStatement) (_ struct{}) { + c.compileExpression(statement.Expression) + c.emit(opcode.Pop) + return } func (c *Compiler) VisitVoidExpression(_ *ast.VoidExpression) (_ struct{}) { @@ -449,9 +459,9 @@ func (c *Compiler) VisitFunctionExpression(_ *ast.FunctionExpression) (_ struct{ panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitStringExpression(_ *ast.StringExpression) (_ struct{}) { - // TODO - panic(errors.NewUnreachableError()) +func (c *Compiler) VisitStringExpression(expression *ast.StringExpression) (_ struct{}) { + c.stringConstLoad(expression.Value) + return } func (c *Compiler) VisitCastingExpression(_ *ast.CastingExpression) (_ struct{}) { @@ -485,7 +495,51 @@ func (c *Compiler) VisitPathExpression(_ *ast.PathExpression) (_ struct{}) { } func (c *Compiler) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunctionDeclaration) (_ struct{}) { - return c.VisitFunctionDeclaration(declaration.FunctionDeclaration) + enclosingCompositeTypeName := c.currentCompositeType.Identifier + + var functionName string + kind := declaration.DeclarationKind() + switch kind { + case common.DeclarationKindInitializer: + functionName = enclosingCompositeTypeName + default: + // TODO: support other special functions + panic(errors.NewUnreachableError()) + } + + parameter := declaration.FunctionDeclaration.ParameterList.Parameters + parameterCount := len(parameter) + if parameterCount > math.MaxUint16 { + panic(errors.NewDefaultUserError("invalid parameter count")) + } + + function := c.addFunction(functionName, uint16(parameterCount)) + + // TODO: pass location + c.stringConstLoad(enclosingCompositeTypeName) + + // Declare `self` + self := c.currentFunction.declareLocal(sema.SelfIdentifier) + selfFirst, selfSecond := encodeUint16(self.index) + + for _, parameter := range parameter { + parameterName := parameter.Identifier.Identifier + function.declareLocal(parameterName) + } + + // Initialize an empty struct and assign to `self`. + // i.e: `self = New()` + c.emit(opcode.New) + c.emit(opcode.SetLocal, selfFirst, selfSecond) + + // Emit for the statements in `init()` body. + c.compileFunctionBlock(declaration.FunctionDeclaration.FunctionBlock) + + // Constructor should return the created the struct. i.e: return `self` + c.emit(opcode.GetLocal, selfFirst, selfSecond) + c.emit(opcode.ReturnValue) + + return } func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration) (_ struct{}) { @@ -505,9 +559,20 @@ func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration return } -func (c *Compiler) VisitCompositeDeclaration(_ *ast.CompositeDeclaration) (_ struct{}) { - // TODO - panic(errors.NewUnreachableError()) +func (c *Compiler) VisitCompositeDeclaration(declaration *ast.CompositeDeclaration) (_ struct{}) { + prevCompositeType := c.currentCompositeType + c.currentCompositeType = c.Elaboration.CompositeDeclarationTypes[declaration] + defer func() { + c.currentCompositeType = prevCompositeType + }() + + for _, specialFunc := range declaration.Members.SpecialFunctions() { + c.compileDeclaration(specialFunc) + } + + // TODO: + + return } func (c *Compiler) VisitInterfaceDeclaration(_ *ast.InterfaceDeclaration) (_ struct{}) { diff --git a/runtime/bbq/compiler/constant.go b/runtime/bbq/compiler/constant.go index 63984ccd1b..3f2986246e 100644 --- a/runtime/bbq/compiler/constant.go +++ b/runtime/bbq/compiler/constant.go @@ -23,5 +23,5 @@ import "github.com/onflow/cadence/runtime/bbq/constantkind" type constant struct { index uint16 data []byte - kind constantkind.Constant + kind constantkind.ConstantKind } diff --git a/runtime/bbq/constant.go b/runtime/bbq/constant.go index f90600b099..5e2ff5f1e6 100644 --- a/runtime/bbq/constant.go +++ b/runtime/bbq/constant.go @@ -22,5 +22,5 @@ import "github.com/onflow/cadence/runtime/bbq/constantkind" type Constant struct { Data []byte - Kind constantkind.Constant + Kind constantkind.ConstantKind } diff --git a/runtime/bbq/constantkind/constantkind.go b/runtime/bbq/constantkind/constantkind.go index a5b6afd261..c7b2ea0c41 100644 --- a/runtime/bbq/constantkind/constantkind.go +++ b/runtime/bbq/constantkind/constantkind.go @@ -23,10 +23,12 @@ import ( "github.com/onflow/cadence/runtime/sema" ) -type Constant uint8 +//go:generate go run golang.org/x/tools/cmd/stringer -type=ConstantKind + +type ConstantKind uint8 const ( - Unknown Constant = iota + Unknown ConstantKind = iota String // Int* @@ -79,7 +81,7 @@ const ( _ // future: UFix256 ) -func FromSemaType(ty sema.Type) Constant { +func FromSemaType(ty sema.Type) ConstantKind { switch ty { // Int* case sema.IntType: diff --git a/runtime/bbq/constantkind/constantkind_string.go b/runtime/bbq/constantkind/constantkind_string.go new file mode 100644 index 0000000000..c1b31f2c13 --- /dev/null +++ b/runtime/bbq/constantkind/constantkind_string.go @@ -0,0 +1,66 @@ +// Code generated by "stringer -type=ConstantKind"; DO NOT EDIT. + +package constantkind + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Unknown-0] + _ = x[String-1] + _ = x[Int-2] + _ = x[Int8-3] + _ = x[Int16-4] + _ = x[Int32-5] + _ = x[Int64-6] + _ = x[Int128-7] + _ = x[Int256-8] + _ = x[UInt-10] + _ = x[UInt8-11] + _ = x[UInt16-12] + _ = x[UInt32-13] + _ = x[UInt64-14] + _ = x[UInt128-15] + _ = x[UInt256-16] + _ = x[Word8-19] + _ = x[Word16-20] + _ = x[Word32-21] + _ = x[Word64-22] + _ = x[Fix64-30] + _ = x[UFix64-38] +} + +const ( + _ConstantKind_name_0 = "UnknownStringIntInt8Int16Int32Int64Int128Int256" + _ConstantKind_name_1 = "UIntUInt8UInt16UInt32UInt64UInt128UInt256" + _ConstantKind_name_2 = "Word8Word16Word32Word64" + _ConstantKind_name_3 = "Fix64" + _ConstantKind_name_4 = "UFix64" +) + +var ( + _ConstantKind_index_0 = [...]uint8{0, 7, 13, 16, 20, 25, 30, 35, 41, 47} + _ConstantKind_index_1 = [...]uint8{0, 4, 9, 15, 21, 27, 34, 41} + _ConstantKind_index_2 = [...]uint8{0, 5, 11, 17, 23} +) + +func (i ConstantKind) String() string { + switch { + case i <= 8: + return _ConstantKind_name_0[_ConstantKind_index_0[i]:_ConstantKind_index_0[i+1]] + case 10 <= i && i <= 16: + i -= 10 + return _ConstantKind_name_1[_ConstantKind_index_1[i]:_ConstantKind_index_1[i+1]] + case 19 <= i && i <= 22: + i -= 19 + return _ConstantKind_name_2[_ConstantKind_index_2[i]:_ConstantKind_index_2[i+1]] + case i == 30: + return _ConstantKind_name_3 + case i == 38: + return _ConstantKind_name_4 + default: + return "ConstantKind(" + strconv.FormatInt(int64(i), 10) + ")" + } +} diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index cba7d2aedc..503c2c27d6 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -51,4 +51,7 @@ const ( GetGlobal Call + + New + Pop ) diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index d64bc27039..00fbed334c 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -31,11 +31,13 @@ func _() { _ = x[SetLocal-20] _ = x[GetGlobal-21] _ = x[Call-22] + _ = x[New-23] + _ = x[Pop-24] } -const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalCall" +const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalCallNewPop" -var _Opcode_index = [...]uint8{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 198} +var _Opcode_index = [...]uint8{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 198, 201, 204} func (i Opcode) String() string { if i >= Opcode(len(_Opcode_index)-1) { diff --git a/runtime/bbq/vm/value.go b/runtime/bbq/vm/value.go index 1821643b1e..ae774389ea 100644 --- a/runtime/bbq/vm/value.go +++ b/runtime/bbq/vm/value.go @@ -72,3 +72,19 @@ type FunctionValue struct { var _ Value = FunctionValue{} func (FunctionValue) isValue() {} + +type StringValue struct { + string []byte +} + +var _ Value = StringValue{} + +func (StringValue) isValue() {} + +type StructValue struct { + Name string +} + +var _ Value = StructValue{} + +func (StructValue) isValue() {} diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 2be39c0509..40393befd6 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -246,6 +246,22 @@ func opCall(vm *VM) { vm.dropN(parameterCount) } +func opPop(vm *VM) { + _ = vm.pop() +} + +func opNew(vm *VM) { + stackHeight := len(vm.stack) + const parameterCount = 1 + arguments := vm.stack[stackHeight-parameterCount:] + + // TODO: get location + name := arguments[0].(StringValue) + + value := StructValue{Name: string(name.string)} + vm.push(value) +} + func (vm *VM) run() { for { @@ -289,6 +305,10 @@ func (vm *VM) run() { opGetGlobal(vm) case opcode.Call: opCall(vm) + case opcode.Pop: + opPop(vm) + case opcode.New: + opNew(vm) default: panic(errors.NewUnreachableError()) } @@ -305,6 +325,8 @@ func (vm *VM) initializeConstant(index uint16) (value Value) { // TODO: smallInt, _, _ := leb128.ReadInt64(constant.Data) value = IntValue{smallInt} + case constantkind.String: + value = StringValue{constant.Data} default: // TODO: panic(errors.NewUnreachableError()) diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index 4d165d5d95..00eeaffd69 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -197,3 +197,68 @@ func TestContinue(t *testing.T) { require.Equal(t, IntValue{3}, result) } + +func TestNewStruct(t *testing.T) { + + t.Parallel() + + checker, err := ParseAndCheck(t, ` + struct Foo { + init() {} + } + + fun test(count: Int): Int { + var i = 0 + while i < count { + i = i + 1 + Foo() + } + return i + } + `) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vm := NewVM(program) + + result, err := vm.Invoke("test", IntValue{10}) + require.NoError(t, err) + + require.Equal(t, IntValue{10}, result) +} + +func BenchmarkNewStruct(b *testing.B) { + + checker, err := ParseAndCheck(b, ` + struct Foo { + init() {} + } + + fun test(count: Int): Int { + var i = 0 + while i < count { + i = i + 1 + Foo() + } + return i + } + `) + require.NoError(b, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vm := NewVM(program) + + b.ReportAllocs() + b.ResetTimer() + + var value Value = IntValue{7} + + for i := 0; i < b.N; i++ { + _, err := vm.Invoke("test", value) + require.NoError(b, err) + } +} From 43808d5881ede8563f46cbb973fc5833274ee3d0 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 28 Feb 2023 15:33:48 -0800 Subject: [PATCH 03/89] Add struct field access --- runtime/bbq/compiler/compiler.go | 26 +++++++++++------ runtime/bbq/compiler/function.go | 16 ++++++----- runtime/bbq/function.go | 2 ++ runtime/bbq/opcode/opcode.go | 2 ++ runtime/bbq/opcode/opcode_string.go | 12 ++++---- runtime/bbq/vm/value.go | 10 ++++++- runtime/bbq/vm/vm.go | 44 +++++++++++++++++++++++------ runtime/bbq/vm/vm_test.go | 38 +++++++++++++++++++------ 8 files changed, 112 insertions(+), 38 deletions(-) diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index e32af99040..8339e4eedd 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -78,7 +78,10 @@ func (c *Compiler) addGlobal(name string) *global { func (c *Compiler) addFunction(name string, parameterCount uint16) *function { c.addGlobal(name) - function := newFunction(name, parameterCount) + + isCompositeFunction := c.currentCompositeType != nil + + function := newFunction(name, parameterCount, isCompositeFunction) c.functions = append(c.functions, function) c.currentFunction = function return function @@ -198,10 +201,11 @@ func (c *Compiler) exportFunctions() []*bbq.Function { functions = append( functions, &bbq.Function{ - Name: function.name, - Code: function.code, - LocalCount: function.localCount, - ParameterCount: function.parameterCount, + Name: function.name, + Code: function.code, + LocalCount: function.localCount, + ParameterCount: function.parameterCount, + IsCompositeFunction: function.isCompositeFunction, }, ) } @@ -322,6 +326,10 @@ func (c *Compiler) VisitAssignmentStatement(statement *ast.AssignmentStatement) local := c.currentFunction.findLocal(target.Identifier.Identifier) first, second := encodeUint16(local.index) c.emit(opcode.SetLocal, first, second) + case *ast.MemberExpression: + c.compileExpression(target.Expression) + c.stringConstLoad(target.Identifier.Identifier) + c.emit(opcode.SetField) default: // TODO: panic(errors.NewUnreachableError()) @@ -412,9 +420,11 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio return } -func (c *Compiler) VisitMemberExpression(_ *ast.MemberExpression) (_ struct{}) { - // TODO - panic(errors.NewUnreachableError()) +func (c *Compiler) VisitMemberExpression(expression *ast.MemberExpression) (_ struct{}) { + c.compileExpression(expression.Expression) + c.stringConstLoad(expression.Identifier.Identifier) + c.emit(opcode.GetField) + return } func (c *Compiler) VisitIndexExpression(_ *ast.IndexExpression) (_ struct{}) { diff --git a/runtime/bbq/compiler/function.go b/runtime/bbq/compiler/function.go index 329cd32b2e..943db95f76 100644 --- a/runtime/bbq/compiler/function.go +++ b/runtime/bbq/compiler/function.go @@ -30,16 +30,18 @@ type function struct { name string localCount uint16 // TODO: use byte.Buffer? - code []byte - locals *activations.Activations[*local] - parameterCount uint16 + code []byte + locals *activations.Activations[*local] + parameterCount uint16 + isCompositeFunction bool } -func newFunction(name string, parameterCount uint16) *function { +func newFunction(name string, parameterCount uint16, isCompositeFunction bool) *function { return &function{ - name: name, - parameterCount: parameterCount, - locals: activations.NewActivations[*local](nil), + name: name, + parameterCount: parameterCount, + locals: activations.NewActivations[*local](nil), + isCompositeFunction: isCompositeFunction, } } diff --git a/runtime/bbq/function.go b/runtime/bbq/function.go index 9d7686104e..9c863a9fe2 100644 --- a/runtime/bbq/function.go +++ b/runtime/bbq/function.go @@ -23,4 +23,6 @@ type Function struct { Code []byte ParameterCount uint16 LocalCount uint16 + + IsCompositeFunction bool } diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index 503c2c27d6..8ed7fb73a1 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -49,6 +49,8 @@ const ( GetLocal SetLocal GetGlobal + GetField + SetField Call diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index 00fbed334c..edb18824cd 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -30,14 +30,16 @@ func _() { _ = x[GetLocal-19] _ = x[SetLocal-20] _ = x[GetGlobal-21] - _ = x[Call-22] - _ = x[New-23] - _ = x[Pop-24] + _ = x[GetField-22] + _ = x[SetField-23] + _ = x[Call-24] + _ = x[New-25] + _ = x[Pop-26] } -const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalCallNewPop" +const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalGetFieldSetFieldCallNewPop" -var _Opcode_index = [...]uint8{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 198, 201, 204} +var _Opcode_index = [...]uint8{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 202, 210, 214, 217, 220} func (i Opcode) String() string { if i >= Opcode(len(_Opcode_index)-1) { diff --git a/runtime/bbq/vm/value.go b/runtime/bbq/vm/value.go index ae774389ea..9882d8ad64 100644 --- a/runtime/bbq/vm/value.go +++ b/runtime/bbq/vm/value.go @@ -82,7 +82,15 @@ var _ Value = StringValue{} func (StringValue) isValue() {} type StructValue struct { - Name string + Name string + Fields map[string]Value +} + +func NewStructValue(name string) StructValue { + return StructValue{ + Name: name, + Fields: map[string]Value{}, + } } var _ Value = StructValue{} diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 40393befd6..500ba054b7 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -93,9 +93,15 @@ func (vm *VM) replaceTop(value Value) { func (vm *VM) pushCallFrame(function *bbq.Function, arguments []Value) { + // Preserve local index zero for `self`. + localOffset := 0 + if function.IsCompositeFunction { + localOffset = 1 + } + locals := make([]Value, function.LocalCount) for i, argument := range arguments { - locals[i] = argument + locals[i+localOffset] = argument } callFrame := &callFrame{ @@ -251,17 +257,35 @@ func opPop(vm *VM) { } func opNew(vm *VM) { - stackHeight := len(vm.stack) - const parameterCount = 1 - arguments := vm.stack[stackHeight-parameterCount:] - // TODO: get location - name := arguments[0].(StringValue) - - value := StructValue{Name: string(name.string)} + name := vm.pop().(StringValue) + value := NewStructValue(string(name.string)) vm.push(value) } +func opSetField(vm *VM) { + fieldName := vm.pop().(StringValue) + fieldNameStr := string(fieldName.string) + + // TODO: support all container types + structValue := vm.pop().(StructValue) + + fieldValue := vm.pop() + + structValue.Fields[fieldNameStr] = fieldValue +} + +func opGetField(vm *VM) { + fieldName := vm.pop().(StringValue) + fieldNameStr := string(fieldName.string) + + // TODO: support all container types + structValue := vm.pop().(StructValue) + + fieldValue := structValue.Fields[fieldNameStr] + vm.push(fieldValue) +} + func (vm *VM) run() { for { @@ -309,6 +333,10 @@ func (vm *VM) run() { opPop(vm) case opcode.New: opNew(vm) + case opcode.SetField: + opSetField(vm) + case opcode.GetField: + opGetField(vm) default: panic(errors.NewUnreachableError()) } diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index 00eeaffd69..f0c139b9b5 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -204,16 +204,22 @@ func TestNewStruct(t *testing.T) { checker, err := ParseAndCheck(t, ` struct Foo { - init() {} + var id : Int + + init(_ id: Int) { + self.id = id + } } - fun test(count: Int): Int { + fun test(count: Int): Foo { var i = 0 + var r = Foo(0) while i < count { i = i + 1 - Foo() + r = Foo(i) + r.id = r.id + 2 } - return i + return r } `) require.NoError(t, err) @@ -226,23 +232,37 @@ func TestNewStruct(t *testing.T) { result, err := vm.Invoke("test", IntValue{10}) require.NoError(t, err) - require.Equal(t, IntValue{10}, result) + require.Equal( + t, + StructValue{ + Name: "Foo", + Fields: map[string]Value{ + "id": IntValue{12}, + }, + }, + result, + ) } func BenchmarkNewStruct(b *testing.B) { checker, err := ParseAndCheck(b, ` struct Foo { - init() {} + var id : Int + + init(_ id: Int) { + self.id = id + } } - fun test(count: Int): Int { + fun test(count: Int): Foo { var i = 0 + var r = Foo(0) while i < count { i = i + 1 - Foo() + r = Foo(i) } - return i + return r } `) require.NoError(b, err) From 6c6519b3e443963972b62d7225d354ea002bd2e9 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 1 Mar 2023 09:02:53 -0800 Subject: [PATCH 04/89] Use atree for struct values --- runtime/bbq/vm/callframe.go | 3 +- runtime/bbq/vm/context/context.go | 46 +++++++ runtime/bbq/vm/values/composite_value.go | 129 ++++++++++++++++++ runtime/bbq/vm/values/convertions.go | 45 ++++++ .../vm/{value.go => values/simple_values.go} | 44 ++---- runtime/bbq/vm/vm.go | 97 +++++++------ runtime/bbq/vm/vm_test.go | 36 ++--- runtime/interpreter/encode.go | 8 +- 8 files changed, 314 insertions(+), 94 deletions(-) create mode 100644 runtime/bbq/vm/context/context.go create mode 100644 runtime/bbq/vm/values/composite_value.go create mode 100644 runtime/bbq/vm/values/convertions.go rename runtime/bbq/vm/{value.go => values/simple_values.go} (67%) diff --git a/runtime/bbq/vm/callframe.go b/runtime/bbq/vm/callframe.go index 6bf88c405a..8e186e8da8 100644 --- a/runtime/bbq/vm/callframe.go +++ b/runtime/bbq/vm/callframe.go @@ -20,11 +20,12 @@ package vm import ( "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/bbq/vm/values" ) type callFrame struct { parent *callFrame - locals []Value + locals []values.Value function *bbq.Function ip uint16 } diff --git a/runtime/bbq/vm/context/context.go b/runtime/bbq/vm/context/context.go new file mode 100644 index 0000000000..30f4cc4bd6 --- /dev/null +++ b/runtime/bbq/vm/context/context.go @@ -0,0 +1,46 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package context + +import ( + "github.com/onflow/atree" + + "github.com/onflow/cadence/runtime/errors" +) + +type Context struct { + Storage Storage +} + +type Storage interface { + atree.SlabStorage +} + +func RemoveReferencedSlab(storage Storage, storable atree.Storable) { + storageIDStorable, ok := storable.(atree.StorageIDStorable) + if !ok { + return + } + + storageID := atree.StorageID(storageIDStorable) + err := storage.Remove(storageID) + if err != nil { + panic(errors.NewExternalError(err)) + } +} diff --git a/runtime/bbq/vm/values/composite_value.go b/runtime/bbq/vm/values/composite_value.go new file mode 100644 index 0000000000..5e242b99a4 --- /dev/null +++ b/runtime/bbq/vm/values/composite_value.go @@ -0,0 +1,129 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package values + +import ( + "github.com/onflow/atree" + + "github.com/onflow/cadence/runtime/bbq/vm/context" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" +) + +type StructValue struct { + dictionary *atree.OrderedMap + Location common.Location + QualifiedIdentifier string +} + +var _ Value = StructValue{} + +func NewStructValue( + location common.Location, + qualifiedIdentifier string, + address common.Address, + storage atree.SlabStorage, +) StructValue { + + const kind = common.CompositeKindStructure + + dictionary, err := atree.NewMap( + storage, + atree.Address(address), + atree.NewDefaultDigesterBuilder(), + interpreter.NewCompositeTypeInfo( + nil, + location, + qualifiedIdentifier, + kind, + ), + ) + + if err != nil { + panic(errors.NewExternalError(err)) + } + + return StructValue{ + QualifiedIdentifier: qualifiedIdentifier, + Location: location, + dictionary: dictionary, + } +} + +func (StructValue) isValue() {} + +func (v StructValue) GetMember(context context.Context, name string) Value { + storable, err := v.dictionary.Get( + interpreter.StringAtreeComparator, + interpreter.StringAtreeHashInput, + interpreter.StringAtreeValue(name), + ) + if err != nil { + if _, ok := err.(*atree.KeyNotFoundError); !ok { + panic(errors.NewExternalError(err)) + } + } + + if storable != nil { + interpreterValue := interpreter.StoredValue(nil, storable, context.Storage) + // TODO: Temp conversion + return InterpreterValueToVMValue(interpreterValue) + } + + return nil +} + +func (v StructValue) SetMember(ctx context.Context, name string, value Value) { + + // TODO: + //address := v.StorageID().Address + //value = value.Transfer( + // interpreter, + // locationRange, + // address, + // true, + // nil, + //) + + interpreterValue := VMValueToInterpreterValue(value) + + existingStorable, err := v.dictionary.Set( + interpreter.StringAtreeComparator, + interpreter.StringAtreeHashInput, + interpreter.NewStringAtreeValue(nil, name), + interpreterValue, + ) + + if err != nil { + panic(errors.NewExternalError(err)) + } + + if existingStorable != nil { + // TODO: + //existingValue := interpreter.StoredValue(nil, existingStorable, context.Storage) + //existingValue.DeepRemove(interpreter) + + context.RemoveReferencedSlab(ctx.Storage, existingStorable) + } +} + +func (v StructValue) StorageID() atree.StorageID { + return v.dictionary.StorageID() +} diff --git a/runtime/bbq/vm/values/convertions.go b/runtime/bbq/vm/values/convertions.go new file mode 100644 index 0000000000..daf40ecd19 --- /dev/null +++ b/runtime/bbq/vm/values/convertions.go @@ -0,0 +1,45 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package values + +import ( + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" +) + +// Utility methods to convert between old and new values. +// These are temporary until all parts of the interpreter are migrated to the vm. + +func InterpreterValueToVMValue(value interpreter.Value) Value { + switch value := value.(type) { + case interpreter.IntValue: + return IntValue{value.BigInt.Int64()} + default: + panic(errors.NewUnreachableError()) + } +} + +func VMValueToInterpreterValue(value Value) interpreter.Value { + switch value := value.(type) { + case IntValue: + return interpreter.NewIntValueFromInt64(nil, value.SmallInt) + default: + panic(errors.NewUnreachableError()) + } +} diff --git a/runtime/bbq/vm/value.go b/runtime/bbq/vm/values/simple_values.go similarity index 67% rename from runtime/bbq/vm/value.go rename to runtime/bbq/vm/values/simple_values.go index 9882d8ad64..8ff2886278 100644 --- a/runtime/bbq/vm/value.go +++ b/runtime/bbq/vm/values/simple_values.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Dapper Labs, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ * limitations under the License. */ -package vm +package values import ( "github.com/onflow/cadence/runtime/bbq" @@ -26,8 +26,8 @@ type Value interface { isValue() } -var trueValue Value = BoolValue(true) -var falseValue Value = BoolValue(false) +var TrueValue Value = BoolValue(true) +var FalseValue Value = BoolValue(false) type BoolValue bool @@ -36,7 +36,7 @@ var _ Value = BoolValue(true) func (BoolValue) isValue() {} type IntValue struct { - smallInt int64 + SmallInt int64 } var _ Value = IntValue{} @@ -44,25 +44,25 @@ var _ Value = IntValue{} func (IntValue) isValue() {} func (v IntValue) Add(other IntValue) Value { - return IntValue{v.smallInt + other.smallInt} + return IntValue{v.SmallInt + other.SmallInt} } func (v IntValue) Subtract(other IntValue) Value { - return IntValue{v.smallInt - other.smallInt} + return IntValue{v.SmallInt - other.SmallInt} } func (v IntValue) Less(other IntValue) Value { - if v.smallInt < other.smallInt { - return trueValue + if v.SmallInt < other.SmallInt { + return TrueValue } - return falseValue + return FalseValue } func (v IntValue) Greater(other IntValue) Value { - if v.smallInt > other.smallInt { - return trueValue + if v.SmallInt > other.SmallInt { + return TrueValue } - return falseValue + return FalseValue } type FunctionValue struct { @@ -74,25 +74,9 @@ var _ Value = FunctionValue{} func (FunctionValue) isValue() {} type StringValue struct { - string []byte + String []byte } var _ Value = StringValue{} func (StringValue) isValue() {} - -type StructValue struct { - Name string - Fields map[string]Value -} - -func NewStructValue(name string) StructValue { - return StructValue{ - Name: name, - Fields: map[string]Value{}, - } -} - -var _ Value = StructValue{} - -func (StructValue) isValue() {} diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 500ba054b7..611109feba 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -19,37 +19,48 @@ package vm import ( + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/bbq" "github.com/onflow/cadence/runtime/bbq/constantkind" "github.com/onflow/cadence/runtime/bbq/leb128" "github.com/onflow/cadence/runtime/bbq/opcode" - "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/bbq/vm/context" + "github.com/onflow/cadence/runtime/bbq/vm/values" ) type VM struct { Program *bbq.Program - globals []Value - constants []Value + globals []values.Value + constants []values.Value functions map[string]*bbq.Function callFrame *callFrame - stack []Value + stack []values.Value + context context.Context } func NewVM(program *bbq.Program) *VM { functions := indexFunctions(program.Functions) // TODO: include non-function globals - globals := make([]Value, 0, len(functions)) + globals := make([]values.Value, 0, len(functions)) for _, function := range functions { // TODO: - globals = append(globals, FunctionValue{Function: function}) + globals = append(globals, values.FunctionValue{Function: function}) } + storage := interpreter.NewInMemoryStorage(nil) + return &VM{ Program: program, globals: globals, functions: functions, - constants: make([]Value, len(program.Constants)), + constants: make([]values.Value, len(program.Constants)), + context: context.Context{ + Storage: storage, + }, } } @@ -61,11 +72,11 @@ func indexFunctions(functions []*bbq.Function) map[string]*bbq.Function { return indexedFunctions } -func (vm *VM) push(value Value) { +func (vm *VM) push(value values.Value) { vm.stack = append(vm.stack, value) } -func (vm *VM) pop() Value { +func (vm *VM) pop() values.Value { lastIndex := len(vm.stack) - 1 value := vm.stack[lastIndex] vm.stack[lastIndex] = nil @@ -81,25 +92,24 @@ func (vm *VM) dropN(count int) { vm.stack = vm.stack[:stackHeight-count] } -func (vm *VM) peekPop() (Value, Value) { +func (vm *VM) peekPop() (values.Value, values.Value) { lastIndex := len(vm.stack) - 1 return vm.stack[lastIndex-1], vm.pop() } -func (vm *VM) replaceTop(value Value) { +func (vm *VM) replaceTop(value values.Value) { lastIndex := len(vm.stack) - 1 vm.stack[lastIndex] = value } -func (vm *VM) pushCallFrame(function *bbq.Function, arguments []Value) { - +func (vm *VM) pushCallFrame(function *bbq.Function, arguments []values.Value) { // Preserve local index zero for `self`. localOffset := 0 if function.IsCompositeFunction { localOffset = 1 } - locals := make([]Value, function.LocalCount) + locals := make([]values.Value, function.LocalCount) for i, argument := range arguments { locals[i+localOffset] = argument } @@ -116,7 +126,7 @@ func (vm *VM) popCallFrame() { vm.callFrame = vm.callFrame.parent } -func (vm *VM) Invoke(name string, arguments ...Value) (Value, error) { +func (vm *VM) Invoke(name string, arguments ...values.Value) (values.Value, error) { function, ok := vm.functions[name] if !ok { return nil, errors.NewDefaultUserError("unknown function") @@ -171,7 +181,7 @@ func opJump(vm *VM) { func opJumpIfFalse(vm *VM) { callFrame := vm.callFrame target := callFrame.getUint16() - value := vm.pop().(BoolValue) + value := vm.pop().(values.BoolValue) if !value { callFrame.ip = target } @@ -179,38 +189,38 @@ func opJumpIfFalse(vm *VM) { func opBinaryIntAdd(vm *VM) { left, right := vm.peekPop() - leftNumber := left.(IntValue) - rightNumber := right.(IntValue) + leftNumber := left.(values.IntValue) + rightNumber := right.(values.IntValue) vm.replaceTop(leftNumber.Add(rightNumber)) } func opBinaryIntSubtract(vm *VM) { left, right := vm.peekPop() - leftNumber := left.(IntValue) - rightNumber := right.(IntValue) + leftNumber := left.(values.IntValue) + rightNumber := right.(values.IntValue) vm.replaceTop(leftNumber.Subtract(rightNumber)) } func opBinaryIntLess(vm *VM) { left, right := vm.peekPop() - leftNumber := left.(IntValue) - rightNumber := right.(IntValue) + leftNumber := left.(values.IntValue) + rightNumber := right.(values.IntValue) vm.replaceTop(leftNumber.Less(rightNumber)) } func opBinaryIntGreater(vm *VM) { left, right := vm.peekPop() - leftNumber := left.(IntValue) - rightNumber := right.(IntValue) + leftNumber := left.(values.IntValue) + rightNumber := right.(values.IntValue) vm.replaceTop(leftNumber.Greater(rightNumber)) } func opTrue(vm *VM) { - vm.push(trueValue) + vm.push(values.TrueValue) } func opFalse(vm *VM) { - vm.push(falseValue) + vm.push(values.FalseValue) } func opGetConstant(vm *VM) { @@ -244,7 +254,7 @@ func opGetGlobal(vm *VM) { func opCall(vm *VM) { // TODO: support any function value - value := vm.pop().(FunctionValue) + value := vm.pop().(values.FunctionValue) stackHeight := len(vm.stack) parameterCount := int(value.Function.ParameterCount) arguments := vm.stack[stackHeight-parameterCount:] @@ -257,32 +267,37 @@ func opPop(vm *VM) { } func opNew(vm *VM) { - // TODO: get location - name := vm.pop().(StringValue) - value := NewStructValue(string(name.string)) + name := vm.pop().(values.StringValue) + value := values.NewStructValue( + // TODO: get location + nil, + string(name.String), + common.Address{}, + vm.context.Storage, + ) vm.push(value) } func opSetField(vm *VM) { - fieldName := vm.pop().(StringValue) - fieldNameStr := string(fieldName.string) + fieldName := vm.pop().(values.StringValue) + fieldNameStr := string(fieldName.String) // TODO: support all container types - structValue := vm.pop().(StructValue) + structValue := vm.pop().(values.StructValue) fieldValue := vm.pop() - structValue.Fields[fieldNameStr] = fieldValue + structValue.SetMember(vm.context, fieldNameStr, fieldValue) } func opGetField(vm *VM) { - fieldName := vm.pop().(StringValue) - fieldNameStr := string(fieldName.string) + fieldName := vm.pop().(values.StringValue) + fieldNameStr := string(fieldName.String) // TODO: support all container types - structValue := vm.pop().(StructValue) + structValue := vm.pop().(values.StructValue) - fieldValue := structValue.Fields[fieldNameStr] + fieldValue := structValue.GetMember(vm.context, fieldNameStr) vm.push(fieldValue) } @@ -346,15 +361,15 @@ func (vm *VM) run() { } } -func (vm *VM) initializeConstant(index uint16) (value Value) { +func (vm *VM) initializeConstant(index uint16) (value values.Value) { constant := vm.Program.Constants[index] switch constant.Kind { case constantkind.Int: // TODO: smallInt, _, _ := leb128.ReadInt64(constant.Data) - value = IntValue{smallInt} + value = values.IntValue{SmallInt: smallInt} case constantkind.String: - value = StringValue{constant.Data} + value = values.StringValue{String: constant.Data} default: // TODO: panic(errors.NewUnreachableError()) diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index f0c139b9b5..5f76edcf6c 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/cadence/runtime/bbq/compiler" + "github.com/onflow/cadence/runtime/bbq/vm/values" . "github.com/onflow/cadence/runtime/tests/checker" ) @@ -50,10 +51,10 @@ func TestRecursionFib(t *testing.T) { result, err := vm.Invoke( "fib", - IntValue{7}, + values.IntValue{SmallInt: 7}, ) require.NoError(t, err) - require.Equal(t, IntValue{13}, result) + require.Equal(t, values.IntValue{SmallInt: 13}, result) } func BenchmarkRecursionFib(b *testing.B) { @@ -69,13 +70,13 @@ func BenchmarkRecursionFib(b *testing.B) { b.ReportAllocs() b.ResetTimer() - expected := IntValue{377} + expected := values.IntValue{SmallInt: 377} for i := 0; i < b.N; i++ { result, err := vm.Invoke( "fib", - IntValue{14}, + values.IntValue{SmallInt: 14}, ) require.NoError(b, err) require.Equal(b, expected, result) @@ -112,10 +113,10 @@ func TestImperativeFib(t *testing.T) { result, err := vm.Invoke( "fib", - IntValue{7}, + values.IntValue{SmallInt: 7}, ) require.NoError(t, err) - require.Equal(t, IntValue{13}, result) + require.Equal(t, values.IntValue{SmallInt: 13}, result) } func BenchmarkImperativeFib(b *testing.B) { @@ -131,7 +132,7 @@ func BenchmarkImperativeFib(b *testing.B) { b.ReportAllocs() b.ResetTimer() - var value Value = IntValue{14} + var value values.Value = values.IntValue{SmallInt: 14} for i := 0; i < b.N; i++ { _, err := vm.Invoke("fib", value) @@ -165,7 +166,7 @@ func TestBreak(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) - require.Equal(t, IntValue{4}, result) + require.Equal(t, values.IntValue{SmallInt: 4}, result) } func TestContinue(t *testing.T) { @@ -195,7 +196,7 @@ func TestContinue(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) - require.Equal(t, IntValue{3}, result) + require.Equal(t, values.IntValue{SmallInt: 3}, result) } func TestNewStruct(t *testing.T) { @@ -229,18 +230,17 @@ func TestNewStruct(t *testing.T) { vm := NewVM(program) - result, err := vm.Invoke("test", IntValue{10}) + result, err := vm.Invoke("test", values.IntValue{SmallInt: 10}) require.NoError(t, err) + require.IsType(t, values.StructValue{}, result) + structValue := result.(values.StructValue) + + require.Equal(t, "Foo", structValue.QualifiedIdentifier) require.Equal( t, - StructValue{ - Name: "Foo", - Fields: map[string]Value{ - "id": IntValue{12}, - }, - }, - result, + values.IntValue{SmallInt: 12}, + structValue.GetMember(vm.context, "id"), ) } @@ -275,7 +275,7 @@ func BenchmarkNewStruct(b *testing.B) { b.ReportAllocs() b.ResetTimer() - var value Value = IntValue{7} + var value values.Value = values.IntValue{SmallInt: 7} for i := 0; i < b.N; i++ { _, err := vm.Invoke("test", value) diff --git a/runtime/interpreter/encode.go b/runtime/interpreter/encode.go index 75383a6d56..d4f0f72d63 100644 --- a/runtime/interpreter/encode.go +++ b/runtime/interpreter/encode.go @@ -783,7 +783,7 @@ const ( encodedAddressLocationLength = 2 ) -func encodeLocation(e *cbor.StreamEncoder, l common.Location) error { +func EncodeLocation(e *cbor.StreamEncoder, l common.Location) error { if l == nil { return e.EncodeNil() } @@ -1100,7 +1100,7 @@ func (t CompositeStaticType) Encode(e *cbor.StreamEncoder) error { } // Encode location at array index encodedCompositeStaticTypeLocationFieldKey - err = encodeLocation(e, t.Location) + err = EncodeLocation(e, t.Location) if err != nil { return err } @@ -1143,7 +1143,7 @@ func (t InterfaceStaticType) Encode(e *cbor.StreamEncoder) error { } // Encode location at array index encodedInterfaceStaticTypeLocationFieldKey - err = encodeLocation(e, t.Location) + err = EncodeLocation(e, t.Location) if err != nil { return err } @@ -1404,7 +1404,7 @@ func (c compositeTypeInfo) Encode(e *cbor.StreamEncoder) error { return err } - err = encodeLocation(e, c.location) + err = EncodeLocation(e, c.location) if err != nil { return err } From 1c3d8e760f315fff57822ce700cbdaed8e7bb4b6 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 1 Mar 2023 15:16:28 -0800 Subject: [PATCH 05/89] Add type check during value transfer --- runtime/bbq/bytecode_printer.go | 3 +- runtime/bbq/compiler/compiler.go | 54 ++++++++++++++++- runtime/bbq/opcode/opcode.go | 1 + runtime/bbq/opcode/opcode_string.go | 5 +- runtime/bbq/program.go | 1 + runtime/bbq/vm/context/context.go | 4 +- runtime/bbq/vm/types/types.go | 38 ++++++++++++ runtime/bbq/vm/values/composite_value.go | 52 ++++++++++++---- runtime/bbq/vm/values/simple_values.go | 21 +++++++ runtime/bbq/vm/vm.go | 75 ++++++++++++++++++------ runtime/bbq/vm/vm_test.go | 38 ++++++++++-- 11 files changed, 255 insertions(+), 37 deletions(-) create mode 100644 runtime/bbq/vm/types/types.go diff --git a/runtime/bbq/bytecode_printer.go b/runtime/bbq/bytecode_printer.go index c570d32a0e..0a4faa0cdd 100644 --- a/runtime/bbq/bytecode_printer.go +++ b/runtime/bbq/bytecode_printer.go @@ -62,7 +62,8 @@ func (p *BytecodePrinter) printCode(codes []byte) { opcode.SetLocal, opcode.GetGlobal, opcode.Jump, - opcode.JumpIfFalse: + opcode.JumpIfFalse, + opcode.CheckType: first := codes[i+1] last := codes[i+2] diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index 8339e4eedd..b38aa471c1 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -28,6 +28,7 @@ import ( "github.com/onflow/cadence/runtime/bbq/opcode" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" ) @@ -42,7 +43,13 @@ type Compiler struct { loops []*loop currentLoop *loop + staticTypes [][]byte + typesInPool map[sema.Type]uint16 + currentCompositeType *sema.CompositeType + + // TODO: initialize + memoryGauge common.MemoryGauge } var _ ast.DeclarationVisitor[struct{}] = &Compiler{} @@ -57,6 +64,7 @@ func NewCompiler( Program: program, Elaboration: elaboration, globals: map[string]*global{}, + typesInPool: map[sema.Type]uint16{}, } } @@ -174,10 +182,12 @@ func (c *Compiler) Compile() *bbq.Program { functions := c.exportFunctions() constants := c.exportConstants() + types := c.exportTypes() return &bbq.Program{ Functions: functions, Constants: constants, + Types: types, } } @@ -195,6 +205,14 @@ func (c *Compiler) exportConstants() []*bbq.Constant { return constants } +func (c *Compiler) exportTypes() [][]byte { + types := make([][]byte, len(c.staticTypes)) + for index, typeBytes := range c.staticTypes { + types[index] = typeBytes + } + return types +} + func (c *Compiler) exportFunctions() []*bbq.Function { functions := make([]*bbq.Function, 0, len(c.functions)) for _, function := range c.functions { @@ -321,6 +339,10 @@ func (c *Compiler) VisitVariableDeclaration(declaration *ast.VariableDeclaration func (c *Compiler) VisitAssignmentStatement(statement *ast.AssignmentStatement) (_ struct{}) { c.compileExpression(statement.Value) + + assignmentTypes := c.Elaboration.AssignmentStatementTypes[statement] + c.emitCheckType(assignmentTypes.TargetType) + switch target := statement.Target.(type) { case *ast.IdentifierExpression: local := c.currentFunction.findLocal(target.Identifier.Identifier) @@ -412,8 +434,11 @@ func (c *Compiler) VisitIdentifierExpression(expression *ast.IdentifierExpressio func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpression) (_ struct{}) { // TODO: copy - for _, argument := range expression.Arguments { + + invocationTypes := c.Elaboration.InvocationExpressionTypes[expression] + for index, argument := range expression.Arguments { c.compileExpression(argument.Expression) + c.emitCheckType(invocationTypes.ArgumentTypes[index]) } c.compileExpression(expression.InvokedExpression) c.emit(opcode.Call) @@ -620,3 +645,30 @@ func (c *Compiler) patchLoop(l *loop) { c.patchJump(breakOffset) } } + +func (c *Compiler) emitCheckType(targetType sema.Type) { + // Optimization: Re-use types in the pool. + index, ok := c.typesInPool[targetType] + if !ok { + staticType := interpreter.ConvertSemaToStaticType(c.memoryGauge, targetType) + bytes, err := interpreter.StaticTypeToBytes(staticType) + if err != nil { + panic(err) + } + index = c.addType(bytes) + c.typesInPool[targetType] = index + } + + first, second := encodeUint16(index) + c.emit(opcode.CheckType, first, second) +} + +func (c *Compiler) addType(data []byte) uint16 { + count := len(c.staticTypes) + if count >= math.MaxUint16 { + panic(errors.NewDefaultUserError("invalid type declaration")) + } + + c.staticTypes = append(c.staticTypes, data) + return uint16(count) +} diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index 8ed7fb73a1..9459be3065 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -56,4 +56,5 @@ const ( New Pop + CheckType ) diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index edb18824cd..09890e0f19 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -35,11 +35,12 @@ func _() { _ = x[Call-24] _ = x[New-25] _ = x[Pop-26] + _ = x[CheckType-27] } -const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalGetFieldSetFieldCallNewPop" +const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalGetFieldSetFieldCallNewPopCheckType" -var _Opcode_index = [...]uint8{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 202, 210, 214, 217, 220} +var _Opcode_index = [...]uint8{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 202, 210, 214, 217, 220, 229} func (i Opcode) String() string { if i >= Opcode(len(_Opcode_index)-1) { diff --git a/runtime/bbq/program.go b/runtime/bbq/program.go index 90964f68ed..d2f362affa 100644 --- a/runtime/bbq/program.go +++ b/runtime/bbq/program.go @@ -21,4 +21,5 @@ package bbq type Program struct { Functions []*Function Constants []*Constant + Types [][]byte } diff --git a/runtime/bbq/vm/context/context.go b/runtime/bbq/vm/context/context.go index 30f4cc4bd6..c1a3ad2a29 100644 --- a/runtime/bbq/vm/context/context.go +++ b/runtime/bbq/vm/context/context.go @@ -21,11 +21,13 @@ package context import ( "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" ) type Context struct { - Storage Storage + Storage + common.MemoryGauge } type Storage interface { diff --git a/runtime/bbq/vm/types/types.go b/runtime/bbq/vm/types/types.go new file mode 100644 index 0000000000..266bd032dc --- /dev/null +++ b/runtime/bbq/vm/types/types.go @@ -0,0 +1,38 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package types + +import "github.com/onflow/cadence/runtime/interpreter" + +type StaticType = interpreter.StaticType + +func IsSubType(sourceType, targetType StaticType) bool { + if targetType == interpreter.PrimitiveStaticTypeAny { + return true + } + + // Optimization: If the static types are equal, then no need to check further. + if sourceType.Equal(targetType) { + return true + } + + // TODO: Add the remaining subType rules + + return false +} diff --git a/runtime/bbq/vm/values/composite_value.go b/runtime/bbq/vm/values/composite_value.go index 5e242b99a4..187c542da9 100644 --- a/runtime/bbq/vm/values/composite_value.go +++ b/runtime/bbq/vm/values/composite_value.go @@ -21,26 +21,30 @@ package values import ( "github.com/onflow/atree" - "github.com/onflow/cadence/runtime/bbq/vm/context" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" + + "github.com/onflow/cadence/runtime/bbq/vm/context" + "github.com/onflow/cadence/runtime/bbq/vm/types" ) type StructValue struct { dictionary *atree.OrderedMap Location common.Location QualifiedIdentifier string + typeID common.TypeID + staticType types.StaticType } -var _ Value = StructValue{} +var _ Value = &StructValue{} func NewStructValue( location common.Location, qualifiedIdentifier string, address common.Address, storage atree.SlabStorage, -) StructValue { +) *StructValue { const kind = common.CompositeKindStructure @@ -60,16 +64,30 @@ func NewStructValue( panic(errors.NewExternalError(err)) } - return StructValue{ + return &StructValue{ QualifiedIdentifier: qualifiedIdentifier, Location: location, dictionary: dictionary, } } -func (StructValue) isValue() {} +func (*StructValue) isValue() {} + +func (v *StructValue) StaticType(memoryGauge common.MemoryGauge) types.StaticType { + if v.staticType == nil { + // NOTE: Instead of using NewCompositeStaticType, which always generates the type ID, + // use the TypeID accessor, which may return an already computed type ID + v.staticType = interpreter.NewCompositeStaticType( + memoryGauge, + v.Location, + v.QualifiedIdentifier, + v.TypeID(), + ) + } + return v.staticType +} -func (v StructValue) GetMember(context context.Context, name string) Value { +func (v *StructValue) GetMember(ctx context.Context, name string) Value { storable, err := v.dictionary.Get( interpreter.StringAtreeComparator, interpreter.StringAtreeHashInput, @@ -82,7 +100,7 @@ func (v StructValue) GetMember(context context.Context, name string) Value { } if storable != nil { - interpreterValue := interpreter.StoredValue(nil, storable, context.Storage) + interpreterValue := interpreter.StoredValue(ctx.MemoryGauge, storable, ctx.Storage) // TODO: Temp conversion return InterpreterValueToVMValue(interpreterValue) } @@ -90,7 +108,7 @@ func (v StructValue) GetMember(context context.Context, name string) Value { return nil } -func (v StructValue) SetMember(ctx context.Context, name string, value Value) { +func (v *StructValue) SetMember(ctx context.Context, name string, value Value) { // TODO: //address := v.StorageID().Address @@ -107,7 +125,7 @@ func (v StructValue) SetMember(ctx context.Context, name string, value Value) { existingStorable, err := v.dictionary.Set( interpreter.StringAtreeComparator, interpreter.StringAtreeHashInput, - interpreter.NewStringAtreeValue(nil, name), + interpreter.NewStringAtreeValue(ctx.MemoryGauge, name), interpreterValue, ) @@ -124,6 +142,20 @@ func (v StructValue) SetMember(ctx context.Context, name string, value Value) { } } -func (v StructValue) StorageID() atree.StorageID { +func (v *StructValue) StorageID() atree.StorageID { return v.dictionary.StorageID() } + +func (v *StructValue) TypeID() common.TypeID { + if v.typeID == "" { + location := v.Location + qualifiedIdentifier := v.QualifiedIdentifier + if location == nil { + return common.TypeID(qualifiedIdentifier) + } + + // TODO: TypeID metering + v.typeID = location.TypeID(nil, qualifiedIdentifier) + } + return v.typeID +} diff --git a/runtime/bbq/vm/values/simple_values.go b/runtime/bbq/vm/values/simple_values.go index 8ff2886278..5d1705b9eb 100644 --- a/runtime/bbq/vm/values/simple_values.go +++ b/runtime/bbq/vm/values/simple_values.go @@ -20,10 +20,15 @@ package values import ( "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/bbq/vm/types" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" ) type Value interface { isValue() + StaticType(common.MemoryGauge) types.StaticType } var TrueValue Value = BoolValue(true) @@ -35,6 +40,10 @@ var _ Value = BoolValue(true) func (BoolValue) isValue() {} +func (BoolValue) StaticType(common.MemoryGauge) types.StaticType { + return interpreter.PrimitiveStaticTypeBool +} + type IntValue struct { SmallInt int64 } @@ -43,6 +52,10 @@ var _ Value = IntValue{} func (IntValue) isValue() {} +func (IntValue) StaticType(common.MemoryGauge) types.StaticType { + return interpreter.PrimitiveStaticTypeInt +} + func (v IntValue) Add(other IntValue) Value { return IntValue{v.SmallInt + other.SmallInt} } @@ -73,6 +86,10 @@ var _ Value = FunctionValue{} func (FunctionValue) isValue() {} +func (FunctionValue) StaticType(common.MemoryGauge) types.StaticType { + panic(errors.NewUnreachableError()) +} + type StringValue struct { String []byte } @@ -80,3 +97,7 @@ type StringValue struct { var _ Value = StringValue{} func (StringValue) isValue() {} + +func (StringValue) StaticType(common.MemoryGauge) types.StaticType { + return interpreter.PrimitiveStaticTypeString +} diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 611109feba..fb09cef59f 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -19,26 +19,27 @@ package vm import ( - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/errors" - "github.com/onflow/cadence/runtime/interpreter" - "github.com/onflow/cadence/runtime/bbq" "github.com/onflow/cadence/runtime/bbq/constantkind" "github.com/onflow/cadence/runtime/bbq/leb128" "github.com/onflow/cadence/runtime/bbq/opcode" "github.com/onflow/cadence/runtime/bbq/vm/context" + "github.com/onflow/cadence/runtime/bbq/vm/types" "github.com/onflow/cadence/runtime/bbq/vm/values" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" ) type VM struct { - Program *bbq.Program - globals []values.Value - constants []values.Value - functions map[string]*bbq.Function - callFrame *callFrame - stack []values.Value - context context.Context + Program *bbq.Program + globals []values.Value + constants []values.Value + staticTypes []types.StaticType + functions map[string]*bbq.Function + callFrame *callFrame + stack []values.Value + context context.Context } func NewVM(program *bbq.Program) *VM { @@ -54,10 +55,11 @@ func NewVM(program *bbq.Program) *VM { storage := interpreter.NewInMemoryStorage(nil) return &VM{ - Program: program, - globals: globals, - functions: functions, - constants: make([]values.Value, len(program.Constants)), + Program: program, + globals: globals, + functions: functions, + constants: make([]values.Value, len(program.Constants)), + staticTypes: make([]types.StaticType, len(program.Types)), context: context.Context{ Storage: storage, }, @@ -84,6 +86,11 @@ func (vm *VM) pop() values.Value { return value } +func (vm *VM) peek() values.Value { + lastIndex := len(vm.stack) - 1 + return vm.stack[lastIndex] +} + func (vm *VM) dropN(count int) { stackHeight := len(vm.stack) for i := 1; i <= count; i++ { @@ -270,7 +277,7 @@ func opNew(vm *VM) { name := vm.pop().(values.StringValue) value := values.NewStructValue( // TODO: get location - nil, + common.StringLocation("test"), string(name.String), common.Address{}, vm.context.Storage, @@ -283,7 +290,7 @@ func opSetField(vm *VM) { fieldNameStr := string(fieldName.String) // TODO: support all container types - structValue := vm.pop().(values.StructValue) + structValue := vm.pop().(*values.StructValue) fieldValue := vm.pop() @@ -295,12 +302,20 @@ func opGetField(vm *VM) { fieldNameStr := string(fieldName.String) // TODO: support all container types - structValue := vm.pop().(values.StructValue) + structValue := vm.pop().(*values.StructValue) fieldValue := structValue.GetMember(vm.context, fieldNameStr) vm.push(fieldValue) } +func opCheckType(vm *VM) { + targetType := vm.loadType() + valueType := vm.peek().StaticType(vm.context.MemoryGauge) + if !types.IsSubType(valueType, targetType) { + panic("invalid transfer") + } +} + func (vm *VM) run() { for { @@ -352,6 +367,8 @@ func (vm *VM) run() { opSetField(vm) case opcode.GetField: opGetField(vm) + case opcode.CheckType: + opCheckType(vm) default: panic(errors.NewUnreachableError()) } @@ -377,3 +394,25 @@ func (vm *VM) initializeConstant(index uint16) (value values.Value) { vm.constants[index] = value return value } + +func (vm *VM) loadType() types.StaticType { + index := vm.callFrame.getUint16() + staticType := vm.staticTypes[index] + if staticType == nil { + staticType = vm.initializeType(index) + } + + return staticType +} + +func (vm *VM) initializeType(index uint16) interpreter.StaticType { + typeBytes := vm.Program.Types[index] + dec := interpreter.CBORDecMode.NewByteStreamDecoder(typeBytes) + staticType, err := interpreter.NewTypeDecoder(dec, nil).DecodeStaticType() + if err != nil { + panic(err) + } + + vm.staticTypes[index] = staticType + return staticType +} diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index 5f76edcf6c..daa9b4f7ca 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -19,13 +19,19 @@ package vm import ( + "fmt" "testing" "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + . "github.com/onflow/cadence/runtime/tests/checker" + + "github.com/onflow/cadence/runtime/bbq" "github.com/onflow/cadence/runtime/bbq/compiler" + "github.com/onflow/cadence/runtime/bbq/vm/context" "github.com/onflow/cadence/runtime/bbq/vm/values" - . "github.com/onflow/cadence/runtime/tests/checker" ) const recursiveFib = ` @@ -233,8 +239,8 @@ func TestNewStruct(t *testing.T) { result, err := vm.Invoke("test", values.IntValue{SmallInt: 10}) require.NoError(t, err) - require.IsType(t, values.StructValue{}, result) - structValue := result.(values.StructValue) + require.IsType(t, &values.StructValue{}, result) + structValue := result.(*values.StructValue) require.Equal(t, "Foo", structValue.QualifiedIdentifier) require.Equal( @@ -275,10 +281,34 @@ func BenchmarkNewStruct(b *testing.B) { b.ReportAllocs() b.ResetTimer() - var value values.Value = values.IntValue{SmallInt: 7} + value := values.IntValue{SmallInt: 7} for i := 0; i < b.N; i++ { _, err := vm.Invoke("test", value) require.NoError(b, err) } } + +func BenchmarkNewStructRaw(b *testing.B) { + + storage := interpreter.NewInMemoryStorage(nil) + ctx := context.Context{ + Storage: storage, + } + + fieldValue := values.IntValue{SmallInt: 7} + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < 7; j++ { + structValue := values.NewStructValue(nil, "Foo", common.Address{}, storage.BasicSlabStorage) + structValue.SetMember(ctx, "id", fieldValue) + } + } +} + +func printProgram(program *bbq.Program) { + byteCodePrinter := &bbq.BytecodePrinter{} + fmt.Println(byteCodePrinter.PrintProgram(program)) +} From 1608f15d37420421aeee30af2e4d832e282c7129 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 2 Mar 2023 13:22:47 -0800 Subject: [PATCH 06/89] Fix non-determinism in global variables --- runtime/bbq/vm/vm.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 611109feba..28cb999c36 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -42,15 +42,17 @@ type VM struct { } func NewVM(program *bbq.Program) *VM { - functions := indexFunctions(program.Functions) - // TODO: include non-function globals - globals := make([]values.Value, 0, len(functions)) - for _, function := range functions { + // Iterate through `program.Functions` to be deterministic. + // Order of globals must be same as index set at `Compiler.addGlobal()`. + globals := make([]values.Value, 0, len(program.Functions)) + for _, function := range program.Functions { // TODO: globals = append(globals, values.FunctionValue{Function: function}) } + functions := indexFunctions(program.Functions) + storage := interpreter.NewInMemoryStorage(nil) return &VM{ From 15bb002c9d544145369abe0cd472ae624eda76b9 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 1 Mar 2023 15:16:28 -0800 Subject: [PATCH 07/89] Add type check during value transfer --- runtime/bbq/bytecode_printer.go | 3 +- runtime/bbq/compiler/compiler.go | 54 ++++++++++++++++- runtime/bbq/opcode/opcode.go | 1 + runtime/bbq/opcode/opcode_string.go | 5 +- runtime/bbq/program.go | 1 + runtime/bbq/vm/context/context.go | 4 +- runtime/bbq/vm/types/types.go | 38 ++++++++++++ runtime/bbq/vm/values/composite_value.go | 52 ++++++++++++---- runtime/bbq/vm/values/simple_values.go | 21 +++++++ runtime/bbq/vm/vm.go | 75 ++++++++++++++++++------ runtime/bbq/vm/vm_test.go | 38 ++++++++++-- 11 files changed, 255 insertions(+), 37 deletions(-) create mode 100644 runtime/bbq/vm/types/types.go diff --git a/runtime/bbq/bytecode_printer.go b/runtime/bbq/bytecode_printer.go index c570d32a0e..0a4faa0cdd 100644 --- a/runtime/bbq/bytecode_printer.go +++ b/runtime/bbq/bytecode_printer.go @@ -62,7 +62,8 @@ func (p *BytecodePrinter) printCode(codes []byte) { opcode.SetLocal, opcode.GetGlobal, opcode.Jump, - opcode.JumpIfFalse: + opcode.JumpIfFalse, + opcode.CheckType: first := codes[i+1] last := codes[i+2] diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index 8339e4eedd..b38aa471c1 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -28,6 +28,7 @@ import ( "github.com/onflow/cadence/runtime/bbq/opcode" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" ) @@ -42,7 +43,13 @@ type Compiler struct { loops []*loop currentLoop *loop + staticTypes [][]byte + typesInPool map[sema.Type]uint16 + currentCompositeType *sema.CompositeType + + // TODO: initialize + memoryGauge common.MemoryGauge } var _ ast.DeclarationVisitor[struct{}] = &Compiler{} @@ -57,6 +64,7 @@ func NewCompiler( Program: program, Elaboration: elaboration, globals: map[string]*global{}, + typesInPool: map[sema.Type]uint16{}, } } @@ -174,10 +182,12 @@ func (c *Compiler) Compile() *bbq.Program { functions := c.exportFunctions() constants := c.exportConstants() + types := c.exportTypes() return &bbq.Program{ Functions: functions, Constants: constants, + Types: types, } } @@ -195,6 +205,14 @@ func (c *Compiler) exportConstants() []*bbq.Constant { return constants } +func (c *Compiler) exportTypes() [][]byte { + types := make([][]byte, len(c.staticTypes)) + for index, typeBytes := range c.staticTypes { + types[index] = typeBytes + } + return types +} + func (c *Compiler) exportFunctions() []*bbq.Function { functions := make([]*bbq.Function, 0, len(c.functions)) for _, function := range c.functions { @@ -321,6 +339,10 @@ func (c *Compiler) VisitVariableDeclaration(declaration *ast.VariableDeclaration func (c *Compiler) VisitAssignmentStatement(statement *ast.AssignmentStatement) (_ struct{}) { c.compileExpression(statement.Value) + + assignmentTypes := c.Elaboration.AssignmentStatementTypes[statement] + c.emitCheckType(assignmentTypes.TargetType) + switch target := statement.Target.(type) { case *ast.IdentifierExpression: local := c.currentFunction.findLocal(target.Identifier.Identifier) @@ -412,8 +434,11 @@ func (c *Compiler) VisitIdentifierExpression(expression *ast.IdentifierExpressio func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpression) (_ struct{}) { // TODO: copy - for _, argument := range expression.Arguments { + + invocationTypes := c.Elaboration.InvocationExpressionTypes[expression] + for index, argument := range expression.Arguments { c.compileExpression(argument.Expression) + c.emitCheckType(invocationTypes.ArgumentTypes[index]) } c.compileExpression(expression.InvokedExpression) c.emit(opcode.Call) @@ -620,3 +645,30 @@ func (c *Compiler) patchLoop(l *loop) { c.patchJump(breakOffset) } } + +func (c *Compiler) emitCheckType(targetType sema.Type) { + // Optimization: Re-use types in the pool. + index, ok := c.typesInPool[targetType] + if !ok { + staticType := interpreter.ConvertSemaToStaticType(c.memoryGauge, targetType) + bytes, err := interpreter.StaticTypeToBytes(staticType) + if err != nil { + panic(err) + } + index = c.addType(bytes) + c.typesInPool[targetType] = index + } + + first, second := encodeUint16(index) + c.emit(opcode.CheckType, first, second) +} + +func (c *Compiler) addType(data []byte) uint16 { + count := len(c.staticTypes) + if count >= math.MaxUint16 { + panic(errors.NewDefaultUserError("invalid type declaration")) + } + + c.staticTypes = append(c.staticTypes, data) + return uint16(count) +} diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index 8ed7fb73a1..9459be3065 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -56,4 +56,5 @@ const ( New Pop + CheckType ) diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index edb18824cd..09890e0f19 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -35,11 +35,12 @@ func _() { _ = x[Call-24] _ = x[New-25] _ = x[Pop-26] + _ = x[CheckType-27] } -const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalGetFieldSetFieldCallNewPop" +const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalGetFieldSetFieldCallNewPopCheckType" -var _Opcode_index = [...]uint8{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 202, 210, 214, 217, 220} +var _Opcode_index = [...]uint8{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 202, 210, 214, 217, 220, 229} func (i Opcode) String() string { if i >= Opcode(len(_Opcode_index)-1) { diff --git a/runtime/bbq/program.go b/runtime/bbq/program.go index 90964f68ed..d2f362affa 100644 --- a/runtime/bbq/program.go +++ b/runtime/bbq/program.go @@ -21,4 +21,5 @@ package bbq type Program struct { Functions []*Function Constants []*Constant + Types [][]byte } diff --git a/runtime/bbq/vm/context/context.go b/runtime/bbq/vm/context/context.go index 30f4cc4bd6..c1a3ad2a29 100644 --- a/runtime/bbq/vm/context/context.go +++ b/runtime/bbq/vm/context/context.go @@ -21,11 +21,13 @@ package context import ( "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" ) type Context struct { - Storage Storage + Storage + common.MemoryGauge } type Storage interface { diff --git a/runtime/bbq/vm/types/types.go b/runtime/bbq/vm/types/types.go new file mode 100644 index 0000000000..266bd032dc --- /dev/null +++ b/runtime/bbq/vm/types/types.go @@ -0,0 +1,38 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package types + +import "github.com/onflow/cadence/runtime/interpreter" + +type StaticType = interpreter.StaticType + +func IsSubType(sourceType, targetType StaticType) bool { + if targetType == interpreter.PrimitiveStaticTypeAny { + return true + } + + // Optimization: If the static types are equal, then no need to check further. + if sourceType.Equal(targetType) { + return true + } + + // TODO: Add the remaining subType rules + + return false +} diff --git a/runtime/bbq/vm/values/composite_value.go b/runtime/bbq/vm/values/composite_value.go index 5e242b99a4..187c542da9 100644 --- a/runtime/bbq/vm/values/composite_value.go +++ b/runtime/bbq/vm/values/composite_value.go @@ -21,26 +21,30 @@ package values import ( "github.com/onflow/atree" - "github.com/onflow/cadence/runtime/bbq/vm/context" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" + + "github.com/onflow/cadence/runtime/bbq/vm/context" + "github.com/onflow/cadence/runtime/bbq/vm/types" ) type StructValue struct { dictionary *atree.OrderedMap Location common.Location QualifiedIdentifier string + typeID common.TypeID + staticType types.StaticType } -var _ Value = StructValue{} +var _ Value = &StructValue{} func NewStructValue( location common.Location, qualifiedIdentifier string, address common.Address, storage atree.SlabStorage, -) StructValue { +) *StructValue { const kind = common.CompositeKindStructure @@ -60,16 +64,30 @@ func NewStructValue( panic(errors.NewExternalError(err)) } - return StructValue{ + return &StructValue{ QualifiedIdentifier: qualifiedIdentifier, Location: location, dictionary: dictionary, } } -func (StructValue) isValue() {} +func (*StructValue) isValue() {} + +func (v *StructValue) StaticType(memoryGauge common.MemoryGauge) types.StaticType { + if v.staticType == nil { + // NOTE: Instead of using NewCompositeStaticType, which always generates the type ID, + // use the TypeID accessor, which may return an already computed type ID + v.staticType = interpreter.NewCompositeStaticType( + memoryGauge, + v.Location, + v.QualifiedIdentifier, + v.TypeID(), + ) + } + return v.staticType +} -func (v StructValue) GetMember(context context.Context, name string) Value { +func (v *StructValue) GetMember(ctx context.Context, name string) Value { storable, err := v.dictionary.Get( interpreter.StringAtreeComparator, interpreter.StringAtreeHashInput, @@ -82,7 +100,7 @@ func (v StructValue) GetMember(context context.Context, name string) Value { } if storable != nil { - interpreterValue := interpreter.StoredValue(nil, storable, context.Storage) + interpreterValue := interpreter.StoredValue(ctx.MemoryGauge, storable, ctx.Storage) // TODO: Temp conversion return InterpreterValueToVMValue(interpreterValue) } @@ -90,7 +108,7 @@ func (v StructValue) GetMember(context context.Context, name string) Value { return nil } -func (v StructValue) SetMember(ctx context.Context, name string, value Value) { +func (v *StructValue) SetMember(ctx context.Context, name string, value Value) { // TODO: //address := v.StorageID().Address @@ -107,7 +125,7 @@ func (v StructValue) SetMember(ctx context.Context, name string, value Value) { existingStorable, err := v.dictionary.Set( interpreter.StringAtreeComparator, interpreter.StringAtreeHashInput, - interpreter.NewStringAtreeValue(nil, name), + interpreter.NewStringAtreeValue(ctx.MemoryGauge, name), interpreterValue, ) @@ -124,6 +142,20 @@ func (v StructValue) SetMember(ctx context.Context, name string, value Value) { } } -func (v StructValue) StorageID() atree.StorageID { +func (v *StructValue) StorageID() atree.StorageID { return v.dictionary.StorageID() } + +func (v *StructValue) TypeID() common.TypeID { + if v.typeID == "" { + location := v.Location + qualifiedIdentifier := v.QualifiedIdentifier + if location == nil { + return common.TypeID(qualifiedIdentifier) + } + + // TODO: TypeID metering + v.typeID = location.TypeID(nil, qualifiedIdentifier) + } + return v.typeID +} diff --git a/runtime/bbq/vm/values/simple_values.go b/runtime/bbq/vm/values/simple_values.go index 8ff2886278..5d1705b9eb 100644 --- a/runtime/bbq/vm/values/simple_values.go +++ b/runtime/bbq/vm/values/simple_values.go @@ -20,10 +20,15 @@ package values import ( "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/bbq/vm/types" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" ) type Value interface { isValue() + StaticType(common.MemoryGauge) types.StaticType } var TrueValue Value = BoolValue(true) @@ -35,6 +40,10 @@ var _ Value = BoolValue(true) func (BoolValue) isValue() {} +func (BoolValue) StaticType(common.MemoryGauge) types.StaticType { + return interpreter.PrimitiveStaticTypeBool +} + type IntValue struct { SmallInt int64 } @@ -43,6 +52,10 @@ var _ Value = IntValue{} func (IntValue) isValue() {} +func (IntValue) StaticType(common.MemoryGauge) types.StaticType { + return interpreter.PrimitiveStaticTypeInt +} + func (v IntValue) Add(other IntValue) Value { return IntValue{v.SmallInt + other.SmallInt} } @@ -73,6 +86,10 @@ var _ Value = FunctionValue{} func (FunctionValue) isValue() {} +func (FunctionValue) StaticType(common.MemoryGauge) types.StaticType { + panic(errors.NewUnreachableError()) +} + type StringValue struct { String []byte } @@ -80,3 +97,7 @@ type StringValue struct { var _ Value = StringValue{} func (StringValue) isValue() {} + +func (StringValue) StaticType(common.MemoryGauge) types.StaticType { + return interpreter.PrimitiveStaticTypeString +} diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 28cb999c36..303a3a6cfe 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -19,26 +19,27 @@ package vm import ( - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/errors" - "github.com/onflow/cadence/runtime/interpreter" - "github.com/onflow/cadence/runtime/bbq" "github.com/onflow/cadence/runtime/bbq/constantkind" "github.com/onflow/cadence/runtime/bbq/leb128" "github.com/onflow/cadence/runtime/bbq/opcode" "github.com/onflow/cadence/runtime/bbq/vm/context" + "github.com/onflow/cadence/runtime/bbq/vm/types" "github.com/onflow/cadence/runtime/bbq/vm/values" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" ) type VM struct { - Program *bbq.Program - globals []values.Value - constants []values.Value - functions map[string]*bbq.Function - callFrame *callFrame - stack []values.Value - context context.Context + Program *bbq.Program + globals []values.Value + constants []values.Value + staticTypes []types.StaticType + functions map[string]*bbq.Function + callFrame *callFrame + stack []values.Value + context context.Context } func NewVM(program *bbq.Program) *VM { @@ -56,10 +57,11 @@ func NewVM(program *bbq.Program) *VM { storage := interpreter.NewInMemoryStorage(nil) return &VM{ - Program: program, - globals: globals, - functions: functions, - constants: make([]values.Value, len(program.Constants)), + Program: program, + globals: globals, + functions: functions, + constants: make([]values.Value, len(program.Constants)), + staticTypes: make([]types.StaticType, len(program.Types)), context: context.Context{ Storage: storage, }, @@ -86,6 +88,11 @@ func (vm *VM) pop() values.Value { return value } +func (vm *VM) peek() values.Value { + lastIndex := len(vm.stack) - 1 + return vm.stack[lastIndex] +} + func (vm *VM) dropN(count int) { stackHeight := len(vm.stack) for i := 1; i <= count; i++ { @@ -272,7 +279,7 @@ func opNew(vm *VM) { name := vm.pop().(values.StringValue) value := values.NewStructValue( // TODO: get location - nil, + common.StringLocation("test"), string(name.String), common.Address{}, vm.context.Storage, @@ -285,7 +292,7 @@ func opSetField(vm *VM) { fieldNameStr := string(fieldName.String) // TODO: support all container types - structValue := vm.pop().(values.StructValue) + structValue := vm.pop().(*values.StructValue) fieldValue := vm.pop() @@ -297,12 +304,20 @@ func opGetField(vm *VM) { fieldNameStr := string(fieldName.String) // TODO: support all container types - structValue := vm.pop().(values.StructValue) + structValue := vm.pop().(*values.StructValue) fieldValue := structValue.GetMember(vm.context, fieldNameStr) vm.push(fieldValue) } +func opCheckType(vm *VM) { + targetType := vm.loadType() + valueType := vm.peek().StaticType(vm.context.MemoryGauge) + if !types.IsSubType(valueType, targetType) { + panic("invalid transfer") + } +} + func (vm *VM) run() { for { @@ -354,6 +369,8 @@ func (vm *VM) run() { opSetField(vm) case opcode.GetField: opGetField(vm) + case opcode.CheckType: + opCheckType(vm) default: panic(errors.NewUnreachableError()) } @@ -379,3 +396,25 @@ func (vm *VM) initializeConstant(index uint16) (value values.Value) { vm.constants[index] = value return value } + +func (vm *VM) loadType() types.StaticType { + index := vm.callFrame.getUint16() + staticType := vm.staticTypes[index] + if staticType == nil { + staticType = vm.initializeType(index) + } + + return staticType +} + +func (vm *VM) initializeType(index uint16) interpreter.StaticType { + typeBytes := vm.Program.Types[index] + dec := interpreter.CBORDecMode.NewByteStreamDecoder(typeBytes) + staticType, err := interpreter.NewTypeDecoder(dec, nil).DecodeStaticType() + if err != nil { + panic(err) + } + + vm.staticTypes[index] = staticType + return staticType +} diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index 5f76edcf6c..daa9b4f7ca 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -19,13 +19,19 @@ package vm import ( + "fmt" "testing" "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + . "github.com/onflow/cadence/runtime/tests/checker" + + "github.com/onflow/cadence/runtime/bbq" "github.com/onflow/cadence/runtime/bbq/compiler" + "github.com/onflow/cadence/runtime/bbq/vm/context" "github.com/onflow/cadence/runtime/bbq/vm/values" - . "github.com/onflow/cadence/runtime/tests/checker" ) const recursiveFib = ` @@ -233,8 +239,8 @@ func TestNewStruct(t *testing.T) { result, err := vm.Invoke("test", values.IntValue{SmallInt: 10}) require.NoError(t, err) - require.IsType(t, values.StructValue{}, result) - structValue := result.(values.StructValue) + require.IsType(t, &values.StructValue{}, result) + structValue := result.(*values.StructValue) require.Equal(t, "Foo", structValue.QualifiedIdentifier) require.Equal( @@ -275,10 +281,34 @@ func BenchmarkNewStruct(b *testing.B) { b.ReportAllocs() b.ResetTimer() - var value values.Value = values.IntValue{SmallInt: 7} + value := values.IntValue{SmallInt: 7} for i := 0; i < b.N; i++ { _, err := vm.Invoke("test", value) require.NoError(b, err) } } + +func BenchmarkNewStructRaw(b *testing.B) { + + storage := interpreter.NewInMemoryStorage(nil) + ctx := context.Context{ + Storage: storage, + } + + fieldValue := values.IntValue{SmallInt: 7} + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < 7; j++ { + structValue := values.NewStructValue(nil, "Foo", common.Address{}, storage.BasicSlabStorage) + structValue.SetMember(ctx, "id", fieldValue) + } + } +} + +func printProgram(program *bbq.Program) { + byteCodePrinter := &bbq.BytecodePrinter{} + fmt.Println(byteCodePrinter.PrintProgram(program)) +} From 84892d8f03ff5928373d41711f503e267a64e51e Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 3 Mar 2023 12:37:19 -0800 Subject: [PATCH 08/89] Add resource types --- runtime/bbq/bytecode_printer.go | 28 ++- runtime/bbq/compiler/compiler.go | 41 +++- runtime/bbq/opcode/opcode.go | 1 + runtime/bbq/opcode/opcode_string.go | 9 +- runtime/bbq/vm/callframe.go | 7 + runtime/bbq/vm/values/composite_value.go | 288 ++++++++++++++++++++++- runtime/bbq/vm/values/simple_values.go | 25 ++ runtime/bbq/vm/vm.go | 46 +++- runtime/bbq/vm/vm_test.go | 28 ++- runtime/interpreter/encode.go | 24 +- runtime/interpreter/value.go | 8 +- 11 files changed, 433 insertions(+), 72 deletions(-) diff --git a/runtime/bbq/bytecode_printer.go b/runtime/bbq/bytecode_printer.go index 0a4faa0cdd..5a54b35ec4 100644 --- a/runtime/bbq/bytecode_printer.go +++ b/runtime/bbq/bytecode_printer.go @@ -64,13 +64,18 @@ func (p *BytecodePrinter) printCode(codes []byte) { opcode.Jump, opcode.JumpIfFalse, opcode.CheckType: + var operand int + operand, i = p.getIntOperand(codes, i) + p.stringBuilder.WriteString(" " + fmt.Sprint(operand)) - first := codes[i+1] - last := codes[i+2] - i += 2 + case opcode.New: + var kind int + kind, i = p.getIntOperand(codes, i) - operand := int(uint16(first)<<8 | uint16(last)) - p.stringBuilder.WriteString(" " + fmt.Sprint(operand)) + var typeName string + typeName, i = p.getStringOperand(codes, i) + + p.stringBuilder.WriteString(" " + fmt.Sprint(kind) + " " + typeName) // opcodes with no operands default: @@ -81,6 +86,19 @@ func (p *BytecodePrinter) printCode(codes []byte) { } } +func (*BytecodePrinter) getIntOperand(codes []byte, i int) (operand int, endIndex int) { + first := codes[i+1] + last := codes[i+2] + operand = int(uint16(first)<<8 | uint16(last)) + return operand, i + 2 +} + +func (p *BytecodePrinter) getStringOperand(codes []byte, i int) (operand string, endIndex int) { + strLen, i := p.getIntOperand(codes, i) + operand = string(codes[i+1 : i+1+strLen]) + return operand, i + strLen +} + func (p *BytecodePrinter) printConstantPool(constants []*Constant) { p.stringBuilder.WriteString("-- Constant Pool --\n") diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index b38aa471c1..bf300dfcb0 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -462,9 +462,16 @@ func (c *Compiler) VisitConditionalExpression(_ *ast.ConditionalExpression) (_ s panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitUnaryExpression(_ *ast.UnaryExpression) (_ struct{}) { - // TODO - panic(errors.NewUnreachableError()) +func (c *Compiler) VisitUnaryExpression(expression *ast.UnaryExpression) (_ struct{}) { + switch expression.Operation { + case ast.OperationMove: + c.compileExpression(expression.Expression) + default: + // TODO + panic(errors.NewUnreachableError()) + } + + return } func (c *Compiler) VisitBinaryExpression(expression *ast.BinaryExpression) (_ struct{}) { @@ -504,14 +511,15 @@ func (c *Compiler) VisitCastingExpression(_ *ast.CastingExpression) (_ struct{}) panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitCreateExpression(_ *ast.CreateExpression) (_ struct{}) { - // TODO - panic(errors.NewUnreachableError()) +func (c *Compiler) VisitCreateExpression(expression *ast.CreateExpression) (_ struct{}) { + c.compileExpression(expression.InvocationExpression) + return } -func (c *Compiler) VisitDestroyExpression(_ *ast.DestroyExpression) (_ struct{}) { - // TODO - panic(errors.NewUnreachableError()) +func (c *Compiler) VisitDestroyExpression(expression *ast.DestroyExpression) (_ struct{}) { + c.compileExpression(expression.Expression) + c.emit(opcode.Destroy) + return } func (c *Compiler) VisitReferenceExpression(_ *ast.ReferenceExpression) (_ struct{}) { @@ -550,9 +558,6 @@ func (c *Compiler) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunct function := c.addFunction(functionName, uint16(parameterCount)) - // TODO: pass location - c.stringConstLoad(enclosingCompositeTypeName) - // Declare `self` self := c.currentFunction.declareLocal(sema.SelfIdentifier) selfFirst, selfSecond := encodeUint16(self.index) @@ -564,7 +569,17 @@ func (c *Compiler) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunct // Initialize an empty struct and assign to `self`. // i.e: `self = New()` - c.emit(opcode.New) + + // TODO: pass location + + // TODO: unsafe conversion + kindFirst, kindSecond := encodeUint16(uint16(c.currentCompositeType.Kind)) + nameLen := len(enclosingCompositeTypeName) + // TODO: unsafe conversion + lenFirst, lenSecond := encodeUint16(uint16(nameLen)) + args := []byte{kindFirst, kindSecond, lenFirst, lenSecond} + args = append(args, enclosingCompositeTypeName...) + c.emit(opcode.New, args...) c.emit(opcode.SetLocal, selfFirst, selfSecond) // Emit for the statements in `init()` body. diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index 9459be3065..9adef82469 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -55,6 +55,7 @@ const ( Call New + Destroy Pop CheckType ) diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index 09890e0f19..06df4c5f9e 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -34,13 +34,14 @@ func _() { _ = x[SetField-23] _ = x[Call-24] _ = x[New-25] - _ = x[Pop-26] - _ = x[CheckType-27] + _ = x[Destroy-26] + _ = x[Pop-27] + _ = x[CheckType-28] } -const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalGetFieldSetFieldCallNewPopCheckType" +const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalGetFieldSetFieldCallNewDestroyPopCheckType" -var _Opcode_index = [...]uint8{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 202, 210, 214, 217, 220, 229} +var _Opcode_index = [...]uint8{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 202, 210, 214, 217, 224, 227, 236} func (i Opcode) String() string { if i >= Opcode(len(_Opcode_index)-1) { diff --git a/runtime/bbq/vm/callframe.go b/runtime/bbq/vm/callframe.go index 8e186e8da8..1ec945d85f 100644 --- a/runtime/bbq/vm/callframe.go +++ b/runtime/bbq/vm/callframe.go @@ -36,3 +36,10 @@ func (f *callFrame) getUint16() uint16 { f.ip += 2 return uint16(first)<<8 | uint16(last) } + +func (f *callFrame) getString() string { + strLen := f.getUint16() + str := string(f.function.Code[f.ip : f.ip+strLen]) + f.ip = f.ip + strLen + return str +} diff --git a/runtime/bbq/vm/values/composite_value.go b/runtime/bbq/vm/values/composite_value.go index 187c542da9..72a1222889 100644 --- a/runtime/bbq/vm/values/composite_value.go +++ b/runtime/bbq/vm/values/composite_value.go @@ -29,24 +29,25 @@ import ( "github.com/onflow/cadence/runtime/bbq/vm/types" ) -type StructValue struct { +type CompositeValue struct { dictionary *atree.OrderedMap Location common.Location QualifiedIdentifier string typeID common.TypeID staticType types.StaticType + Kind common.CompositeKind } -var _ Value = &StructValue{} -func NewStructValue( +var _ Value = &CompositeValue{} + +func NewCompositeValue( location common.Location, qualifiedIdentifier string, + kind common.CompositeKind, address common.Address, storage atree.SlabStorage, -) *StructValue { - - const kind = common.CompositeKindStructure +) *CompositeValue { dictionary, err := atree.NewMap( storage, @@ -64,16 +65,17 @@ func NewStructValue( panic(errors.NewExternalError(err)) } - return &StructValue{ + return &CompositeValue{ QualifiedIdentifier: qualifiedIdentifier, Location: location, dictionary: dictionary, + Kind: kind, } } -func (*StructValue) isValue() {} +func (*CompositeValue) isValue() {} -func (v *StructValue) StaticType(memoryGauge common.MemoryGauge) types.StaticType { +func (v *CompositeValue) StaticType(memoryGauge common.MemoryGauge) types.StaticType { if v.staticType == nil { // NOTE: Instead of using NewCompositeStaticType, which always generates the type ID, // use the TypeID accessor, which may return an already computed type ID @@ -87,7 +89,7 @@ func (v *StructValue) StaticType(memoryGauge common.MemoryGauge) types.StaticTyp return v.staticType } -func (v *StructValue) GetMember(ctx context.Context, name string) Value { +func (v *CompositeValue) GetMember(ctx *context.Context, name string) Value { storable, err := v.dictionary.Get( interpreter.StringAtreeComparator, interpreter.StringAtreeHashInput, @@ -108,7 +110,7 @@ func (v *StructValue) GetMember(ctx context.Context, name string) Value { return nil } -func (v *StructValue) SetMember(ctx context.Context, name string, value Value) { +func (v *CompositeValue) SetMember(ctx *context.Context, name string, value Value) { // TODO: //address := v.StorageID().Address @@ -142,11 +144,11 @@ func (v *StructValue) SetMember(ctx context.Context, name string, value Value) { } } -func (v *StructValue) StorageID() atree.StorageID { +func (v *CompositeValue) StorageID() atree.StorageID { return v.dictionary.StorageID() } -func (v *StructValue) TypeID() common.TypeID { +func (v *CompositeValue) TypeID() common.TypeID { if v.typeID == "" { location := v.Location qualifiedIdentifier := v.QualifiedIdentifier @@ -159,3 +161,263 @@ func (v *StructValue) TypeID() common.TypeID { } return v.typeID } + +func (v *CompositeValue) IsResourceKinded() bool { + return v.Kind == common.CompositeKindResource +} + +func (v *CompositeValue) Transfer( + ctx *context.Context, + address atree.Address, + remove bool, + storable atree.Storable, +) Value { + + //baseUse, elementOverhead, dataUse, metaDataUse := common.NewCompositeMemoryUsages(v.dictionary.Count(), 0) + //common.UseMemory(interpreter, baseUse) + //common.UseMemory(interpreter, elementOverhead) + //common.UseMemory(interpreter, dataUse) + //common.UseMemory(interpreter, metaDataUse) + // + //interpreter.ReportComputation(common.ComputationKindTransferCompositeValue, 1) + + //if interpreter.Config.InvalidatedResourceValidationEnabled { + // v.checkInvalidatedResourceUse(locationRange) + //} + + //if interpreter.Config.TracingEnabled { + // startTime := time.Now() + // + // owner := v.GetOwner().String() + // typeID := string(v.TypeID()) + // kind := v.Kind.String() + // + // defer func() { + // interpreter.reportCompositeValueTransferTrace( + // owner, + // typeID, + // kind, + // time.Since(startTime), + // ) + // }() + //} + + currentStorageID := v.StorageID() + currentAddress := currentStorageID.Address + + dictionary := v.dictionary + + needsStoreTo := address != currentAddress + isResourceKinded := v.IsResourceKinded() + + if needsStoreTo && v.Kind == common.CompositeKindContract { + panic(interpreter.NonTransferableValueError{ + Value: VMValueToInterpreterValue(v), + }) + } + + if needsStoreTo || !isResourceKinded { + iterator, err := v.dictionary.Iterator() + if err != nil { + panic(errors.NewExternalError(err)) + } + + elementMemoryUse := common.NewAtreeMapPreAllocatedElementsMemoryUsage(v.dictionary.Count(), 0) + common.UseMemory(ctx.MemoryGauge, elementMemoryUse) + + dictionary, err = atree.NewMapFromBatchData( + ctx.Storage, + address, + atree.NewDefaultDigesterBuilder(), + v.dictionary.Type(), + interpreter.StringAtreeComparator, + interpreter.StringAtreeHashInput, + v.dictionary.Seed(), + func() (atree.Value, atree.Value, error) { + + atreeKey, atreeValue, err := iterator.Next() + if err != nil { + return nil, nil, err + } + if atreeKey == nil || atreeValue == nil { + return nil, nil, nil + } + + // NOTE: key is stringAtreeValue + // and does not need to be converted or copied + + value := interpreter.MustConvertStoredValue(ctx.MemoryGauge, atreeValue) + + vmValue := InterpreterValueToVMValue(value) + vmValue.Transfer(ctx, address, remove, nil) + + return atreeKey, value, nil + }, + ) + if err != nil { + panic(errors.NewExternalError(err)) + } + + if remove { + err = v.dictionary.PopIterate(func(nameStorable atree.Storable, valueStorable atree.Storable) { + context.RemoveReferencedSlab(ctx.Storage, nameStorable) + context.RemoveReferencedSlab(ctx.Storage, valueStorable) + }) + if err != nil { + panic(errors.NewExternalError(err)) + } + //interpreter.maybeValidateAtreeValue(v.dictionary) + + context.RemoveReferencedSlab(ctx.Storage, storable) + } + } + + var res *CompositeValue + + if isResourceKinded { + // Update the resource in-place, + // and also update all values that are referencing the same value + // (but currently point to an outdated Go instance of the value) + + // If checking of transfers of invalidated resource is enabled, + // then mark the resource as invalidated, by unsetting the backing dictionary. + // This allows raising an error when the resource is attempted + // to be transferred/moved again (see beginning of this function) + + //if interpreter.Config.InvalidatedResourceValidationEnabled { + // v.dictionary = nil + //} else { + // v.dictionary = dictionary + // res = v + //} + + //newStorageID := dictionary.StorageID() + // + //interpreter.updateReferencedResource( + // currentStorageID, + // newStorageID, + // func(value ReferenceTrackedResourceKindedValue) { + // compositeValue, ok := value.(*CompositeValue) + // if !ok { + // panic(errors.NewUnreachableError()) + // } + // compositeValue.dictionary = dictionary + // }, + //) + } + + if res == nil { + typeInfo := interpreter.NewCompositeTypeInfo( + ctx.MemoryGauge, + v.Location, + v.QualifiedIdentifier, + v.Kind, + ) + res = &CompositeValue{ + dictionary: dictionary, + Location: typeInfo.Location, + QualifiedIdentifier: typeInfo.QualifiedIdentifier, + Kind: typeInfo.Kind, + typeID: v.typeID, + staticType: v.staticType, + } + + //res.InjectedFields = v.InjectedFields + //res.ComputedFields = v.ComputedFields + //res.NestedVariables = v.NestedVariables + //res.Functions = v.Functions + //res.Destructor = v.Destructor + //res.Stringer = v.Stringer + //res.isDestroyed = v.isDestroyed + } + + //onResourceOwnerChange := interpreter.Config.OnResourceOwnerChange + // + //if needsStoreTo && + // res.Kind == common.CompositeKindResource && + // onResourceOwnerChange != nil { + // + // onResourceOwnerChange( + // interpreter, + // res, + // common.Address(currentAddress), + // common.Address(address), + // ) + //} + + return res +} + +func (v *CompositeValue) Destroy(*context.Context) { + + //interpreter.ReportComputation(common.ComputationKindDestroyCompositeValue, 1) + // + //if interpreter.Config.InvalidatedResourceValidationEnabled { + // v.checkInvalidatedResourceUse(locationRange) + //} + // + //storageID := v.StorageID() + // + //if interpreter.Config.TracingEnabled { + // startTime := time.Now() + // + // owner := v.GetOwner().String() + // typeID := string(v.TypeID()) + // kind := v.Kind.String() + // + // defer func() { + // + // interpreter.reportCompositeValueDestroyTrace( + // owner, + // typeID, + // kind, + // time.Since(startTime), + // ) + // }() + //} + + //interpreter = v.getInterpreter(interpreter) + + //// if composite was deserialized, dynamically link in the destructor + //if v.Destructor == nil { + // v.Destructor = interpreter.sharedState.typeCodes.CompositeCodes[v.TypeID()].DestructorFunction + //} + // + //destructor := v.Destructor + // + //if destructor != nil { + // invocation := NewInvocation( + // interpreter, + // v, + // nil, + // nil, + // nil, + // locationRange, + // ) + // + // destructor.invoke(invocation) + //} + + //v.isDestroyed = true + + //if interpreter.Config.InvalidatedResourceValidationEnabled { + // v.dictionary = nil + //} + + //interpreter.updateReferencedResource( + // storageID, + // storageID, + // func(value ReferenceTrackedResourceKindedValue) { + // compositeValue, ok := value.(*CompositeValue) + // if !ok { + // panic(errors.NewUnreachableError()) + // } + // + // compositeValue.isDestroyed = true + // + // if interpreter.Config.InvalidatedResourceValidationEnabled { + // compositeValue.dictionary = nil + // } + // }, + //) +} diff --git a/runtime/bbq/vm/values/simple_values.go b/runtime/bbq/vm/values/simple_values.go index 5d1705b9eb..dd42dd8056 100644 --- a/runtime/bbq/vm/values/simple_values.go +++ b/runtime/bbq/vm/values/simple_values.go @@ -19,7 +19,10 @@ package values import ( + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/bbq/vm/context" "github.com/onflow/cadence/runtime/bbq/vm/types" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" @@ -29,6 +32,12 @@ import ( type Value interface { isValue() StaticType(common.MemoryGauge) types.StaticType + Transfer( + ctx *context.Context, + address atree.Address, + remove bool, + storable atree.Storable, + ) Value } var TrueValue Value = BoolValue(true) @@ -44,6 +53,10 @@ func (BoolValue) StaticType(common.MemoryGauge) types.StaticType { return interpreter.PrimitiveStaticTypeBool } +func (v BoolValue) Transfer(*context.Context, atree.Address, bool, atree.Storable) Value { + return v +} + type IntValue struct { SmallInt int64 } @@ -56,6 +69,10 @@ func (IntValue) StaticType(common.MemoryGauge) types.StaticType { return interpreter.PrimitiveStaticTypeInt } +func (v IntValue) Transfer(*context.Context, atree.Address, bool, atree.Storable) Value { + return v +} + func (v IntValue) Add(other IntValue) Value { return IntValue{v.SmallInt + other.SmallInt} } @@ -90,6 +107,10 @@ func (FunctionValue) StaticType(common.MemoryGauge) types.StaticType { panic(errors.NewUnreachableError()) } +func (v FunctionValue) Transfer(*context.Context, atree.Address, bool, atree.Storable) Value { + return v +} + type StringValue struct { String []byte } @@ -101,3 +122,7 @@ func (StringValue) isValue() {} func (StringValue) StaticType(common.MemoryGauge) types.StaticType { return interpreter.PrimitiveStaticTypeString } + +func (v StringValue) Transfer(*context.Context, atree.Address, bool, atree.Storable) Value { + return v +} diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 303a3a6cfe..dbf652d52a 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -19,6 +19,7 @@ package vm import ( + "github.com/onflow/atree" "github.com/onflow/cadence/runtime/bbq" "github.com/onflow/cadence/runtime/bbq/constantkind" "github.com/onflow/cadence/runtime/bbq/leb128" @@ -39,7 +40,7 @@ type VM struct { functions map[string]*bbq.Function callFrame *callFrame stack []values.Value - context context.Context + context *context.Context } func NewVM(program *bbq.Program) *VM { @@ -54,7 +55,9 @@ func NewVM(program *bbq.Program) *VM { functions := indexFunctions(program.Functions) - storage := interpreter.NewInMemoryStorage(nil) + ctx := &context.Context{ + Storage: interpreter.NewInMemoryStorage(nil), + } return &VM{ Program: program, @@ -62,9 +65,7 @@ func NewVM(program *bbq.Program) *VM { functions: functions, constants: make([]values.Value, len(program.Constants)), staticTypes: make([]types.StaticType, len(program.Types)), - context: context.Context{ - Storage: storage, - }, + context: ctx, } } @@ -276,11 +277,16 @@ func opPop(vm *VM) { } func opNew(vm *VM) { - name := vm.pop().(values.StringValue) - value := values.NewStructValue( + kind := vm.callFrame.getUint16() + compositeKind := common.CompositeKind(kind) + + typeName := vm.callFrame.getString() + + value := values.NewCompositeValue( // TODO: get location common.StringLocation("test"), - string(name.String), + typeName, + compositeKind, common.Address{}, vm.context.Storage, ) @@ -292,7 +298,7 @@ func opSetField(vm *VM) { fieldNameStr := string(fieldName.String) // TODO: support all container types - structValue := vm.pop().(*values.StructValue) + structValue := vm.pop().(*values.CompositeValue) fieldValue := vm.pop() @@ -304,7 +310,7 @@ func opGetField(vm *VM) { fieldNameStr := string(fieldName.String) // TODO: support all container types - structValue := vm.pop().(*values.StructValue) + structValue := vm.pop().(*values.CompositeValue) fieldValue := structValue.GetMember(vm.context, fieldNameStr) vm.push(fieldValue) @@ -312,10 +318,26 @@ func opGetField(vm *VM) { func opCheckType(vm *VM) { targetType := vm.loadType() - valueType := vm.peek().StaticType(vm.context.MemoryGauge) + + value := vm.peek() + + transferredValue := value.Transfer( + vm.context, + atree.Address{}, + false, nil, + ) + + valueType := transferredValue.StaticType(vm.context.MemoryGauge) if !types.IsSubType(valueType, targetType) { panic("invalid transfer") } + + vm.replaceTop(transferredValue) +} + +func opDestroy(vm *VM) { + value := vm.peek().(*values.CompositeValue) + value.Destroy(vm.context) } func (vm *VM) run() { @@ -371,6 +393,8 @@ func (vm *VM) run() { opGetField(vm) case opcode.CheckType: opCheckType(vm) + case opcode.Destroy: + opDestroy(vm) default: panic(errors.NewUnreachableError()) } diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index daa9b4f7ca..ad1e0930b0 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -234,13 +234,15 @@ func TestNewStruct(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() + printProgram(program) + vm := NewVM(program) result, err := vm.Invoke("test", values.IntValue{SmallInt: 10}) require.NoError(t, err) - require.IsType(t, &values.StructValue{}, result) - structValue := result.(*values.StructValue) + require.IsType(t, &values.CompositeValue{}, result) + structValue := result.(*values.CompositeValue) require.Equal(t, "Foo", structValue.QualifiedIdentifier) require.Equal( @@ -253,7 +255,7 @@ func TestNewStruct(t *testing.T) { func BenchmarkNewStruct(b *testing.B) { checker, err := ParseAndCheck(b, ` - struct Foo { + resource Foo { var id : Int init(_ id: Int) { @@ -261,14 +263,14 @@ func BenchmarkNewStruct(b *testing.B) { } } - fun test(count: Int): Foo { + fun test(count: Int): @Foo { var i = 0 - var r = Foo(0) + var r <- create Foo(0) while i < count { i = i + 1 - r = Foo(i) + destroy create Foo(i) } - return r + return <- r } `) require.NoError(b, err) @@ -292,7 +294,7 @@ func BenchmarkNewStruct(b *testing.B) { func BenchmarkNewStructRaw(b *testing.B) { storage := interpreter.NewInMemoryStorage(nil) - ctx := context.Context{ + ctx := &context.Context{ Storage: storage, } @@ -301,8 +303,14 @@ func BenchmarkNewStructRaw(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - for j := 0; j < 7; j++ { - structValue := values.NewStructValue(nil, "Foo", common.Address{}, storage.BasicSlabStorage) + for j := 0; j < 8; j++ { + structValue := values.NewCompositeValue( + nil, + "Foo", + common.CompositeKindStructure, + common.Address{}, + storage.BasicSlabStorage, + ) structValue.SetMember(ctx, "id", fieldValue) } } diff --git a/runtime/interpreter/encode.go b/runtime/interpreter/encode.go index d4f0f72d63..c8ec4e5032 100644 --- a/runtime/interpreter/encode.go +++ b/runtime/interpreter/encode.go @@ -1369,9 +1369,9 @@ func (t FunctionStaticType) Encode(_ *cbor.StreamEncoder) error { // compositeTypeInfo type compositeTypeInfo struct { - location common.Location - qualifiedIdentifier string - kind common.CompositeKind + Location common.Location + QualifiedIdentifier string + Kind common.CompositeKind } func NewCompositeTypeInfo( @@ -1383,9 +1383,9 @@ func NewCompositeTypeInfo( common.UseMemory(memoryGauge, common.CompositeTypeInfoMemoryUsage) return compositeTypeInfo{ - location: location, - qualifiedIdentifier: qualifiedIdentifier, - kind: kind, + Location: location, + QualifiedIdentifier: qualifiedIdentifier, + Kind: kind, } } @@ -1404,17 +1404,17 @@ func (c compositeTypeInfo) Encode(e *cbor.StreamEncoder) error { return err } - err = EncodeLocation(e, c.location) + err = EncodeLocation(e, c.Location) if err != nil { return err } - err = e.EncodeString(c.qualifiedIdentifier) + err = e.EncodeString(c.QualifiedIdentifier) if err != nil { return err } - err = e.EncodeUint64(uint64(c.kind)) + err = e.EncodeUint64(uint64(c.Kind)) if err != nil { return err } @@ -1425,9 +1425,9 @@ func (c compositeTypeInfo) Encode(e *cbor.StreamEncoder) error { func (c compositeTypeInfo) Equal(o atree.TypeInfo) bool { other, ok := o.(compositeTypeInfo) return ok && - c.location == other.location && - c.qualifiedIdentifier == other.qualifiedIdentifier && - c.kind == other.kind + c.Location == other.Location && + c.QualifiedIdentifier == other.QualifiedIdentifier && + c.Kind == other.Kind } // EmptyTypeInfo diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index e12bd934f9..29c6196eef 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -13913,9 +13913,9 @@ func newCompositeValueFromOrderedMap( ) *CompositeValue { return &CompositeValue{ dictionary: dict, - Location: typeInfo.location, - QualifiedIdentifier: typeInfo.qualifiedIdentifier, - Kind: typeInfo.kind, + Location: typeInfo.Location, + QualifiedIdentifier: typeInfo.QualifiedIdentifier, + Kind: typeInfo.Kind, } } @@ -14664,7 +14664,7 @@ func (v *CompositeValue) Transfer( }) } - if needsStoreTo || !isResourceKinded { + if needsStoreTo { iterator, err := v.dictionary.Iterator() if err != nil { panic(errors.NewExternalError(err)) From 737fe2490211de9ba7394aafa214d01e49a32880 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 3 Mar 2023 13:40:07 -0800 Subject: [PATCH 09/89] Refactor tests --- runtime/bbq/vm/vm_test.go | 39 ++++++++++++++++++++++++++++++++++++ runtime/interpreter/value.go | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index ad1e0930b0..b193df309d 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -254,6 +254,45 @@ func TestNewStruct(t *testing.T) { func BenchmarkNewStruct(b *testing.B) { + checker, err := ParseAndCheck(b, ` + struct Foo { + var id : Int + + init(_ id: Int) { + self.id = id + } + } + + fun test(count: Int): Foo { + var i = 0 + var r = Foo(0) + while i < count { + i = i + 1 + r = Foo(i) + } + return r + } + `) + require.NoError(b, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vm := NewVM(program) + + b.ReportAllocs() + b.ResetTimer() + + value := values.IntValue{SmallInt: 7} + + for i := 0; i < b.N; i++ { + _, err := vm.Invoke("test", value) + require.NoError(b, err) + } +} + +func BenchmarkNewResource(b *testing.B) { + checker, err := ParseAndCheck(b, ` resource Foo { var id : Int diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 29c6196eef..911ba1d833 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -14664,7 +14664,7 @@ func (v *CompositeValue) Transfer( }) } - if needsStoreTo { + if needsStoreTo || !isResourceKinded { iterator, err := v.dictionary.Iterator() if err != nil { panic(errors.NewExternalError(err)) From 22de01ed0619ca5cafc89697c2be40f67bb1317f Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Mon, 6 Mar 2023 12:44:14 -0800 Subject: [PATCH 10/89] Add composite functions. Pass location to constructor --- runtime/bbq/bytecode_printer.go | 21 +++- runtime/bbq/compiler/compiler.go | 155 ++++++++++++++++++++------ runtime/bbq/compiler/compiler_test.go | 4 +- runtime/bbq/opcode/opcode.go | 3 +- runtime/bbq/opcode/opcode_string.go | 15 +-- runtime/bbq/vm/values/convertions.go | 4 + runtime/bbq/vm/vm.go | 83 +++++++++----- runtime/bbq/vm/vm_test.go | 37 ++++++ 8 files changed, 250 insertions(+), 72 deletions(-) diff --git a/runtime/bbq/bytecode_printer.go b/runtime/bbq/bytecode_printer.go index 5a54b35ec4..3085345940 100644 --- a/runtime/bbq/bytecode_printer.go +++ b/runtime/bbq/bytecode_printer.go @@ -25,7 +25,9 @@ import ( "github.com/onflow/cadence/runtime/bbq/constantkind" "github.com/onflow/cadence/runtime/bbq/leb128" "github.com/onflow/cadence/runtime/bbq/opcode" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" ) type BytecodePrinter struct { @@ -72,10 +74,13 @@ func (p *BytecodePrinter) printCode(codes []byte) { var kind int kind, i = p.getIntOperand(codes, i) + var location common.Location + location, i = p.getLocation(codes, i) + var typeName string typeName, i = p.getStringOperand(codes, i) - p.stringBuilder.WriteString(" " + fmt.Sprint(kind) + " " + typeName) + p.stringBuilder.WriteString(" " + fmt.Sprint(kind) + " " + string(location.TypeID(nil, typeName))) // opcodes with no operands default: @@ -126,3 +131,17 @@ func (p *BytecodePrinter) printConstantPool(constants []*Constant) { p.stringBuilder.WriteRune('\n') } + +func (p *BytecodePrinter) getLocation(codes []byte, i int) (location common.Location, endIndex int) { + locationLen, i := p.getIntOperand(codes, i) + locationBytes := codes[i+1 : i+1+locationLen] + + dec := interpreter.CBORDecMode.NewByteStreamDecoder(locationBytes) + locationDecoder := interpreter.NewLocationDecoder(dec, nil) + location, err := locationDecoder.DecodeLocation() + if err != nil { + panic(err) + } + + return location, i + locationLen +} diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index bf300dfcb0..63438dcc78 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -19,6 +19,7 @@ package compiler import ( + "bytes" "math" "github.com/onflow/cadence/runtime/ast" @@ -36,18 +37,17 @@ type Compiler struct { Program *ast.Program Elaboration *sema.Elaboration - currentFunction *function - functions []*function - constants []*constant - globals map[string]*global - loops []*loop - currentLoop *loop + currentFunction *function + currentCompositeType *sema.CompositeType + functions []*function + constants []*constant + globals map[string]*global + loops []*loop + currentLoop *loop staticTypes [][]byte typesInPool map[sema.Type]uint16 - currentCompositeType *sema.CompositeType - // TODO: initialize memoryGauge common.MemoryGauge } @@ -419,30 +419,58 @@ func (c *Compiler) VisitDictionaryExpression(_ *ast.DictionaryExpression) (_ str } func (c *Compiler) VisitIdentifierExpression(expression *ast.IdentifierExpression) (_ struct{}) { - name := expression.Identifier.Identifier + c.emitVariableLoad(expression.Identifier.Identifier) + return +} + +func (c *Compiler) emitVariableLoad(name string) { local := c.currentFunction.findLocal(name) if local != nil { first, second := encodeUint16(local.index) c.emit(opcode.GetLocal, first, second) return } + global := c.findGlobal(name) first, second := encodeUint16(global.index) c.emit(opcode.GetGlobal, first, second) - return } func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpression) (_ struct{}) { // TODO: copy + switch invokedExpr := expression.InvokedExpression.(type) { + case *ast.IdentifierExpression: + // Load arguments + c.loadArguments(expression) + // Load function value + c.emitVariableLoad(invokedExpr.Identifier.Identifier) + c.emit(opcode.InvokeStatic) + case *ast.MemberExpression: + // Receiver is loaded first. So 'self' is always the zero-th argument. + // This must be in sync with `compileCompositeFunction`. + c.compileExpression(invokedExpr.Expression) + // Load arguments + c.loadArguments(expression) + // Load function value + memberInfo := c.Elaboration.MemberExpressionMemberInfos[invokedExpr] + typeName := memberInfo.AccessedType.QualifiedString() + funcName := compositeFunctionQualifiedName(typeName, invokedExpr.Identifier.Identifier) + c.emitVariableLoad(funcName) + c.emit(opcode.Invoke) + default: + panic(errors.NewUnreachableError()) + } + + return +} + +func (c *Compiler) loadArguments(expression *ast.InvocationExpression) { invocationTypes := c.Elaboration.InvocationExpressionTypes[expression] for index, argument := range expression.Arguments { c.compileExpression(argument.Expression) c.emitCheckType(invocationTypes.ArgumentTypes[index]) } - c.compileExpression(expression.InvokedExpression) - c.emit(opcode.Call) - return } func (c *Compiler) VisitMemberExpression(expression *ast.MemberExpression) (_ struct{}) { @@ -550,35 +578,50 @@ func (c *Compiler) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunct panic(errors.NewUnreachableError()) } - parameter := declaration.FunctionDeclaration.ParameterList.Parameters - parameterCount := len(parameter) + parameters := declaration.FunctionDeclaration.ParameterList.Parameters + parameterCount := len(parameters) if parameterCount > math.MaxUint16 { panic(errors.NewDefaultUserError("invalid parameter count")) } function := c.addFunction(functionName, uint16(parameterCount)) + declareParameters(function, parameters) // Declare `self` self := c.currentFunction.declareLocal(sema.SelfIdentifier) selfFirst, selfSecond := encodeUint16(self.index) - for _, parameter := range parameter { - parameterName := parameter.Identifier.Identifier - function.declareLocal(parameterName) - } - // Initialize an empty struct and assign to `self`. // i.e: `self = New()` - // TODO: pass location + location := c.currentCompositeType.Location + locationBytes, err := locationToBytes(location) + if err != nil { + panic(err) + } - // TODO: unsafe conversion + byteSize := 2 + // two bytes for composite kind + 2 + // 2 bytes for location size + len(locationBytes) + // location + 2 + // 2 bytes for type name size + len(enclosingCompositeTypeName) // type name + + args := make([]byte, 0, byteSize) + + // Write composite kind kindFirst, kindSecond := encodeUint16(uint16(c.currentCompositeType.Kind)) - nameLen := len(enclosingCompositeTypeName) - // TODO: unsafe conversion - lenFirst, lenSecond := encodeUint16(uint16(nameLen)) - args := []byte{kindFirst, kindSecond, lenFirst, lenSecond} + args = append(args, kindFirst, kindSecond) + + // Write location + locationSizeFirst, locationSizeSecond := encodeUint16(uint16(len(locationBytes))) + args = append(args, locationSizeFirst, locationSizeSecond) + args = append(args, locationBytes...) + + // Write composite name + typeNameSizeFirst, typeNameSizeSecond := encodeUint16(uint16(len(enclosingCompositeTypeName))) + args = append(args, typeNameSizeFirst, typeNameSizeSecond) args = append(args, enclosingCompositeTypeName...) + c.emit(opcode.New, args...) c.emit(opcode.SetLocal, selfFirst, selfSecond) @@ -594,19 +637,25 @@ func (c *Compiler) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunct func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration) (_ struct{}) { // TODO: handle nested functions + function := c.declareFunction(declaration) + declareParameters(function, declaration.ParameterList.Parameters) + c.compileFunctionBlock(declaration.FunctionBlock) + return +} + +func (c *Compiler) declareFunction(declaration *ast.FunctionDeclaration) *function { functionName := declaration.Identifier.Identifier + if c.currentCompositeType != nil { + functionName = compositeFunctionQualifiedName(c.currentCompositeType.Identifier, functionName) + } + functionType := c.Elaboration.FunctionDeclarationFunctionTypes[declaration] parameterCount := len(functionType.Parameters) if parameterCount > math.MaxUint16 { panic(errors.NewDefaultUserError("invalid parameter count")) } - function := c.addFunction(functionName, uint16(parameterCount)) - for _, parameter := range declaration.ParameterList.Parameters { - parameterName := parameter.Identifier.Identifier - function.declareLocal(parameterName) - } - c.compileFunctionBlock(declaration.FunctionBlock) - return + + return c.addFunction(functionName, uint16(parameterCount)) } func (c *Compiler) VisitCompositeDeclaration(declaration *ast.CompositeDeclaration) (_ struct{}) { @@ -620,11 +669,23 @@ func (c *Compiler) VisitCompositeDeclaration(declaration *ast.CompositeDeclarati c.compileDeclaration(specialFunc) } + for _, function := range declaration.Members.Functions() { + c.compileCompositeFunction(function) + } + // TODO: return } +func (c *Compiler) compileCompositeFunction(declaration *ast.FunctionDeclaration) { + function := c.declareFunction(declaration) + // Declare `self`. Receiver is always at the zero-th index of params. + function.declareLocal(sema.SelfIdentifier) + declareParameters(function, declaration.ParameterList.Parameters) + c.compileFunctionBlock(declaration.FunctionBlock) +} + func (c *Compiler) VisitInterfaceDeclaration(_ *ast.InterfaceDeclaration) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) @@ -687,3 +748,31 @@ func (c *Compiler) addType(data []byte) uint16 { c.staticTypes = append(c.staticTypes, data) return uint16(count) } + +func compositeFunctionQualifiedName(typeName, functionName string) string { + return typeName + "." + functionName +} + +func declareParameters(function *function, parameters []*ast.Parameter) { + for _, parameter := range parameters { + parameterName := parameter.Identifier.Identifier + function.declareLocal(parameterName) + } +} + +func locationToBytes(location common.Location) ([]byte, error) { + var buf bytes.Buffer + enc := interpreter.CBOREncMode.NewStreamEncoder(&buf) + + err := interpreter.EncodeLocation(enc, location) + if err != nil { + return nil, err + } + + err = enc.Flush() + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/runtime/bbq/compiler/compiler_test.go b/runtime/bbq/compiler/compiler_test.go index 92db712d92..d5f8ec2415 100644 --- a/runtime/bbq/compiler/compiler_test.go +++ b/runtime/bbq/compiler/compiler_test.go @@ -62,13 +62,13 @@ func TestCompileRecursionFib(t *testing.T) { byte(opcode.GetConstant), 0, 1, byte(opcode.IntSubtract), byte(opcode.GetGlobal), 0, 0, - byte(opcode.Call), + byte(opcode.InvokeStatic), // fib(n - 2) byte(opcode.GetLocal), 0, 0, byte(opcode.GetConstant), 0, 2, byte(opcode.IntSubtract), byte(opcode.GetGlobal), 0, 0, - byte(opcode.Call), + byte(opcode.InvokeStatic), // return sum byte(opcode.IntAdd), byte(opcode.ReturnValue), diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index 9adef82469..581e064c1d 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -52,7 +52,8 @@ const ( GetField SetField - Call + InvokeStatic + Invoke New Destroy diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index 06df4c5f9e..ff42e1d6ab 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -32,16 +32,17 @@ func _() { _ = x[GetGlobal-21] _ = x[GetField-22] _ = x[SetField-23] - _ = x[Call-24] - _ = x[New-25] - _ = x[Destroy-26] - _ = x[Pop-27] - _ = x[CheckType-28] + _ = x[InvokeStatic-24] + _ = x[Invoke-25] + _ = x[New-26] + _ = x[Destroy-27] + _ = x[Pop-28] + _ = x[CheckType-29] } -const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalGetFieldSetFieldCallNewDestroyPopCheckType" +const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalGetFieldSetFieldInvokeStaticInvokeNewDestroyPopCheckType" -var _Opcode_index = [...]uint8{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 202, 210, 214, 217, 224, 227, 236} +var _Opcode_index = [...]uint8{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 202, 210, 222, 228, 231, 238, 241, 250} func (i Opcode) String() string { if i >= Opcode(len(_Opcode_index)-1) { diff --git a/runtime/bbq/vm/values/convertions.go b/runtime/bbq/vm/values/convertions.go index daf40ecd19..9489c3ef46 100644 --- a/runtime/bbq/vm/values/convertions.go +++ b/runtime/bbq/vm/values/convertions.go @@ -30,6 +30,8 @@ func InterpreterValueToVMValue(value interpreter.Value) Value { switch value := value.(type) { case interpreter.IntValue: return IntValue{value.BigInt.Int64()} + case *interpreter.StringValue: + return StringValue{String: []byte(value.Str)} default: panic(errors.NewUnreachableError()) } @@ -39,6 +41,8 @@ func VMValueToInterpreterValue(value Value) interpreter.Value { switch value := value.(type) { case IntValue: return interpreter.NewIntValueFromInt64(nil, value.SmallInt) + case StringValue: + return interpreter.NewUnmeteredStringValue(string(value.String)) default: panic(errors.NewUnreachableError()) } diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index dbf652d52a..7f280b787b 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -113,15 +113,9 @@ func (vm *VM) replaceTop(value values.Value) { } func (vm *VM) pushCallFrame(function *bbq.Function, arguments []values.Value) { - // Preserve local index zero for `self`. - localOffset := 0 - if function.IsCompositeFunction { - localOffset = 1 - } - locals := make([]values.Value, function.LocalCount) for i, argument := range arguments { - locals[i+localOffset] = argument + locals[i] = argument } callFrame := &callFrame{ @@ -160,20 +154,20 @@ func (vm *VM) Invoke(name string, arguments ...values.Value) (values.Value, erro type vmOp func(*VM) var vmOps = [...]vmOp{ - opcode.ReturnValue: opReturnValue, - opcode.Jump: opJump, - opcode.JumpIfFalse: opJumpIfFalse, - opcode.IntAdd: opBinaryIntAdd, - opcode.IntSubtract: opBinaryIntSubtract, - opcode.IntLess: opBinaryIntLess, - opcode.IntGreater: opBinaryIntGreater, - opcode.True: opTrue, - opcode.False: opFalse, - opcode.GetConstant: opGetConstant, - opcode.GetLocal: opGetLocal, - opcode.SetLocal: opSetLocal, - opcode.GetGlobal: opGetGlobal, - opcode.Call: opCall, + opcode.ReturnValue: opReturnValue, + opcode.Jump: opJump, + opcode.JumpIfFalse: opJumpIfFalse, + opcode.IntAdd: opBinaryIntAdd, + opcode.IntSubtract: opBinaryIntSubtract, + opcode.IntLess: opBinaryIntLess, + opcode.IntGreater: opBinaryIntGreater, + opcode.True: opTrue, + opcode.False: opFalse, + opcode.GetConstant: opGetConstant, + opcode.GetLocal: opGetLocal, + opcode.SetLocal: opSetLocal, + opcode.GetGlobal: opGetGlobal, + opcode.InvokeStatic: opInvokeStatic, } func opReturnValue(vm *VM) { @@ -262,7 +256,7 @@ func opGetGlobal(vm *VM) { vm.push(vm.globals[index]) } -func opCall(vm *VM) { +func opInvokeStatic(vm *VM) { // TODO: support any function value value := vm.pop().(values.FunctionValue) stackHeight := len(vm.stack) @@ -272,19 +266,39 @@ func opCall(vm *VM) { vm.dropN(parameterCount) } +func opInvoke(vm *VM) { + // TODO: support any function value + value := vm.pop().(values.FunctionValue) + stackHeight := len(vm.stack) + + // Add one to account for `self` + parameterCount := int(value.Function.ParameterCount) + 1 + + arguments := vm.stack[stackHeight-parameterCount:] + vm.pushCallFrame(value.Function, arguments) + vm.dropN(parameterCount) +} + func opPop(vm *VM) { _ = vm.pop() } func opNew(vm *VM) { - kind := vm.callFrame.getUint16() + callframe := vm.callFrame + + kind := callframe.getUint16() compositeKind := common.CompositeKind(kind) - typeName := vm.callFrame.getString() + // decode location + locationLen := callframe.getUint16() + locationBytes := callframe.function.Code[callframe.ip : callframe.ip+locationLen] + callframe.ip = callframe.ip + locationLen + location := decodeLocation(locationBytes) + + typeName := callframe.getString() value := values.NewCompositeValue( - // TODO: get location - common.StringLocation("test"), + location, typeName, compositeKind, common.Address{}, @@ -381,8 +395,10 @@ func (vm *VM) run() { opSetLocal(vm) case opcode.GetGlobal: opGetGlobal(vm) - case opcode.Call: - opCall(vm) + case opcode.InvokeStatic: + opInvokeStatic(vm) + case opcode.Invoke: + opInvoke(vm) case opcode.Pop: opPop(vm) case opcode.New: @@ -442,3 +458,14 @@ func (vm *VM) initializeType(index uint16) interpreter.StaticType { vm.staticTypes[index] = staticType return staticType } + +func decodeLocation(locationBytes []byte) common.Location { + // TODO: is it possible to re-use decoders? + dec := interpreter.CBORDecMode.NewByteStreamDecoder(locationBytes) + locationDecoder := interpreter.NewLocationDecoder(dec, nil) + location, err := locationDecoder.DecodeLocation() + if err != nil { + panic(err) + } + return location +} diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index b193df309d..102c3a3dd6 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -252,6 +252,43 @@ func TestNewStruct(t *testing.T) { ) } +func TestStructMethodCall(t *testing.T) { + + t.Parallel() + + checker, err := ParseAndCheck(t, ` + struct Foo { + var id : String + + init(_ id: String) { + self.id = id + } + + fun sayHello(_ id: Int): String { + return self.id + } + } + + fun test(): String { + var r = Foo("Hello from Foo!") + return r.sayHello(1) + } + `) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + printProgram(program) + + vm := NewVM(program) + + result, err := vm.Invoke("test") + require.NoError(t, err) + + require.Equal(t, values.StringValue{String: []byte("Hello from Foo!")}, result) +} + func BenchmarkNewStruct(b *testing.B) { checker, err := ParseAndCheck(b, ` From 4fcbe79d925aa237c5bae64e30fde488ef8e53ea Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Mon, 6 Mar 2023 17:47:33 -0800 Subject: [PATCH 11/89] Support imports --- runtime/bbq/compiler/compiler.go | 25 +++- .../context/context.go => compiler/config.go} | 28 +--- runtime/bbq/program.go | 3 + runtime/bbq/vm/callframe.go | 1 + runtime/bbq/vm/config/config.go | 31 +++++ runtime/bbq/vm/values/composite_value.go | 33 +++-- runtime/bbq/vm/values/context.go | 40 ++++++ runtime/bbq/vm/values/simple_values.go | 13 +- runtime/bbq/vm/vm.go | 129 +++++++++++------- runtime/bbq/vm/vm_test.go | 107 +++++++++++++-- 10 files changed, 299 insertions(+), 111 deletions(-) rename runtime/bbq/{vm/context/context.go => compiler/config.go} (60%) create mode 100644 runtime/bbq/vm/config/config.go create mode 100644 runtime/bbq/vm/values/context.go diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index 63438dcc78..f72ff421ca 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -36,6 +36,7 @@ import ( type Compiler struct { Program *ast.Program Elaboration *sema.Elaboration + Config *Config currentFunction *function currentCompositeType *sema.CompositeType @@ -63,6 +64,7 @@ func NewCompiler( return &Compiler{ Program: program, Elaboration: elaboration, + Config: &Config{}, globals: map[string]*global{}, typesInPool: map[sema.Type]uint16{}, } @@ -183,11 +185,13 @@ func (c *Compiler) Compile() *bbq.Program { functions := c.exportFunctions() constants := c.exportConstants() types := c.exportTypes() + imports := c.exportImports() return &bbq.Program{ Functions: functions, Constants: constants, Types: types, + Imports: imports, } } @@ -213,6 +217,16 @@ func (c *Compiler) exportTypes() [][]byte { return types } +func (c *Compiler) exportImports() []common.Location { + imports := c.Program.ImportDeclarations() + exportedImports := make([]common.Location, len(imports)) + for index, importDecl := range imports { + exportedImports[index] = importDecl.Location + } + + return exportedImports +} + func (c *Compiler) exportFunctions() []*bbq.Function { functions := make([]*bbq.Function, 0, len(c.functions)) for _, function := range c.functions { @@ -701,9 +715,14 @@ func (c *Compiler) VisitPragmaDeclaration(_ *ast.PragmaDeclaration) (_ struct{}) panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitImportDeclaration(_ *ast.ImportDeclaration) (_ struct{}) { - // TODO - panic(errors.NewUnreachableError()) +func (c *Compiler) VisitImportDeclaration(declaration *ast.ImportDeclaration) (_ struct{}) { + importedProgram := c.Config.ImportHandler(declaration.Location) + for _, function := range importedProgram.Functions { + // TODO: Filter-in only public functions + c.addGlobal(function.Name) + } + + return } func (c *Compiler) VisitTransactionDeclaration(_ *ast.TransactionDeclaration) (_ struct{}) { diff --git a/runtime/bbq/vm/context/context.go b/runtime/bbq/compiler/config.go similarity index 60% rename from runtime/bbq/vm/context/context.go rename to runtime/bbq/compiler/config.go index c1a3ad2a29..c9dae46707 100644 --- a/runtime/bbq/vm/context/context.go +++ b/runtime/bbq/compiler/config.go @@ -16,33 +16,15 @@ * limitations under the License. */ -package context +package compiler import ( - "github.com/onflow/atree" - + "github.com/onflow/cadence/runtime/bbq" "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/errors" ) -type Context struct { - Storage - common.MemoryGauge -} - -type Storage interface { - atree.SlabStorage +type Config struct { + ImportHandler ImportHandler } -func RemoveReferencedSlab(storage Storage, storable atree.Storable) { - storageIDStorable, ok := storable.(atree.StorageIDStorable) - if !ok { - return - } - - storageID := atree.StorageID(storageIDStorable) - err := storage.Remove(storageID) - if err != nil { - panic(errors.NewExternalError(err)) - } -} +type ImportHandler func(location common.Location) *bbq.Program diff --git a/runtime/bbq/program.go b/runtime/bbq/program.go index d2f362affa..24588f4390 100644 --- a/runtime/bbq/program.go +++ b/runtime/bbq/program.go @@ -18,7 +18,10 @@ package bbq +import "github.com/onflow/cadence/runtime/common" + type Program struct { + Imports []common.Location Functions []*Function Constants []*Constant Types [][]byte diff --git a/runtime/bbq/vm/callframe.go b/runtime/bbq/vm/callframe.go index 1ec945d85f..aa464ef503 100644 --- a/runtime/bbq/vm/callframe.go +++ b/runtime/bbq/vm/callframe.go @@ -25,6 +25,7 @@ import ( type callFrame struct { parent *callFrame + context *values.Context locals []values.Value function *bbq.Function ip uint16 diff --git a/runtime/bbq/vm/config/config.go b/runtime/bbq/vm/config/config.go new file mode 100644 index 0000000000..c14e5c8b96 --- /dev/null +++ b/runtime/bbq/vm/config/config.go @@ -0,0 +1,31 @@ +package config + +import ( + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/bbq/compiler" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" +) + +type Config struct { + Storage + common.MemoryGauge + ImportHandler compiler.ImportHandler +} + +type Storage interface { + atree.SlabStorage +} + +func RemoveReferencedSlab(storage Storage, storable atree.Storable) { + storageIDStorable, ok := storable.(atree.StorageIDStorable) + if !ok { + return + } + + storageID := atree.StorageID(storageIDStorable) + err := storage.Remove(storageID) + if err != nil { + panic(errors.NewExternalError(err)) + } +} diff --git a/runtime/bbq/vm/values/composite_value.go b/runtime/bbq/vm/values/composite_value.go index 72a1222889..801ea2be6c 100644 --- a/runtime/bbq/vm/values/composite_value.go +++ b/runtime/bbq/vm/values/composite_value.go @@ -25,7 +25,7 @@ import ( "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" - "github.com/onflow/cadence/runtime/bbq/vm/context" + "github.com/onflow/cadence/runtime/bbq/vm/config" "github.com/onflow/cadence/runtime/bbq/vm/types" ) @@ -38,7 +38,6 @@ type CompositeValue struct { Kind common.CompositeKind } - var _ Value = &CompositeValue{} func NewCompositeValue( @@ -89,7 +88,7 @@ func (v *CompositeValue) StaticType(memoryGauge common.MemoryGauge) types.Static return v.staticType } -func (v *CompositeValue) GetMember(ctx *context.Context, name string) Value { +func (v *CompositeValue) GetMember(config *config.Config, name string) Value { storable, err := v.dictionary.Get( interpreter.StringAtreeComparator, interpreter.StringAtreeHashInput, @@ -102,7 +101,7 @@ func (v *CompositeValue) GetMember(ctx *context.Context, name string) Value { } if storable != nil { - interpreterValue := interpreter.StoredValue(ctx.MemoryGauge, storable, ctx.Storage) + interpreterValue := interpreter.StoredValue(config.MemoryGauge, storable, config.Storage) // TODO: Temp conversion return InterpreterValueToVMValue(interpreterValue) } @@ -110,7 +109,7 @@ func (v *CompositeValue) GetMember(ctx *context.Context, name string) Value { return nil } -func (v *CompositeValue) SetMember(ctx *context.Context, name string, value Value) { +func (v *CompositeValue) SetMember(conf *config.Config, name string, value Value) { // TODO: //address := v.StorageID().Address @@ -127,7 +126,7 @@ func (v *CompositeValue) SetMember(ctx *context.Context, name string, value Valu existingStorable, err := v.dictionary.Set( interpreter.StringAtreeComparator, interpreter.StringAtreeHashInput, - interpreter.NewStringAtreeValue(ctx.MemoryGauge, name), + interpreter.NewStringAtreeValue(conf.MemoryGauge, name), interpreterValue, ) @@ -140,7 +139,7 @@ func (v *CompositeValue) SetMember(ctx *context.Context, name string, value Valu //existingValue := interpreter.StoredValue(nil, existingStorable, context.Storage) //existingValue.DeepRemove(interpreter) - context.RemoveReferencedSlab(ctx.Storage, existingStorable) + config.RemoveReferencedSlab(conf.Storage, existingStorable) } } @@ -167,7 +166,7 @@ func (v *CompositeValue) IsResourceKinded() bool { } func (v *CompositeValue) Transfer( - ctx *context.Context, + conf *config.Config, address atree.Address, remove bool, storable atree.Storable, @@ -223,10 +222,10 @@ func (v *CompositeValue) Transfer( } elementMemoryUse := common.NewAtreeMapPreAllocatedElementsMemoryUsage(v.dictionary.Count(), 0) - common.UseMemory(ctx.MemoryGauge, elementMemoryUse) + common.UseMemory(conf.MemoryGauge, elementMemoryUse) dictionary, err = atree.NewMapFromBatchData( - ctx.Storage, + conf.Storage, address, atree.NewDefaultDigesterBuilder(), v.dictionary.Type(), @@ -246,10 +245,10 @@ func (v *CompositeValue) Transfer( // NOTE: key is stringAtreeValue // and does not need to be converted or copied - value := interpreter.MustConvertStoredValue(ctx.MemoryGauge, atreeValue) + value := interpreter.MustConvertStoredValue(conf.MemoryGauge, atreeValue) vmValue := InterpreterValueToVMValue(value) - vmValue.Transfer(ctx, address, remove, nil) + vmValue.Transfer(conf, address, remove, nil) return atreeKey, value, nil }, @@ -260,15 +259,15 @@ func (v *CompositeValue) Transfer( if remove { err = v.dictionary.PopIterate(func(nameStorable atree.Storable, valueStorable atree.Storable) { - context.RemoveReferencedSlab(ctx.Storage, nameStorable) - context.RemoveReferencedSlab(ctx.Storage, valueStorable) + config.RemoveReferencedSlab(conf.Storage, nameStorable) + config.RemoveReferencedSlab(conf.Storage, valueStorable) }) if err != nil { panic(errors.NewExternalError(err)) } //interpreter.maybeValidateAtreeValue(v.dictionary) - context.RemoveReferencedSlab(ctx.Storage, storable) + config.RemoveReferencedSlab(conf.Storage, storable) } } @@ -308,7 +307,7 @@ func (v *CompositeValue) Transfer( if res == nil { typeInfo := interpreter.NewCompositeTypeInfo( - ctx.MemoryGauge, + conf.MemoryGauge, v.Location, v.QualifiedIdentifier, v.Kind, @@ -348,7 +347,7 @@ func (v *CompositeValue) Transfer( return res } -func (v *CompositeValue) Destroy(*context.Context) { +func (v *CompositeValue) Destroy(*config.Config) { //interpreter.ReportComputation(common.ComputationKindDestroyCompositeValue, 1) // diff --git a/runtime/bbq/vm/values/context.go b/runtime/bbq/vm/values/context.go new file mode 100644 index 0000000000..a3e6e50704 --- /dev/null +++ b/runtime/bbq/vm/values/context.go @@ -0,0 +1,40 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package values + +import ( + "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/bbq/vm/types" +) + +type Context struct { + Program *bbq.Program + Globals []Value + Constants []Value + StaticTypes []types.StaticType +} + +func NewContext(program *bbq.Program, globals []Value) *Context { + return &Context{ + Program: program, + Globals: globals, + Constants: make([]Value, len(program.Constants)), + StaticTypes: make([]types.StaticType, len(program.Types)), + } +} diff --git a/runtime/bbq/vm/values/simple_values.go b/runtime/bbq/vm/values/simple_values.go index dd42dd8056..8aa6bbd927 100644 --- a/runtime/bbq/vm/values/simple_values.go +++ b/runtime/bbq/vm/values/simple_values.go @@ -22,7 +22,7 @@ import ( "github.com/onflow/atree" "github.com/onflow/cadence/runtime/bbq" - "github.com/onflow/cadence/runtime/bbq/vm/context" + "github.com/onflow/cadence/runtime/bbq/vm/config" "github.com/onflow/cadence/runtime/bbq/vm/types" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" @@ -33,7 +33,7 @@ type Value interface { isValue() StaticType(common.MemoryGauge) types.StaticType Transfer( - ctx *context.Context, + config *config.Config, address atree.Address, remove bool, storable atree.Storable, @@ -53,7 +53,7 @@ func (BoolValue) StaticType(common.MemoryGauge) types.StaticType { return interpreter.PrimitiveStaticTypeBool } -func (v BoolValue) Transfer(*context.Context, atree.Address, bool, atree.Storable) Value { +func (v BoolValue) Transfer(*config.Config, atree.Address, bool, atree.Storable) Value { return v } @@ -69,7 +69,7 @@ func (IntValue) StaticType(common.MemoryGauge) types.StaticType { return interpreter.PrimitiveStaticTypeInt } -func (v IntValue) Transfer(*context.Context, atree.Address, bool, atree.Storable) Value { +func (v IntValue) Transfer(*config.Config, atree.Address, bool, atree.Storable) Value { return v } @@ -97,6 +97,7 @@ func (v IntValue) Greater(other IntValue) Value { type FunctionValue struct { Function *bbq.Function + Context *Context } var _ Value = FunctionValue{} @@ -107,7 +108,7 @@ func (FunctionValue) StaticType(common.MemoryGauge) types.StaticType { panic(errors.NewUnreachableError()) } -func (v FunctionValue) Transfer(*context.Context, atree.Address, bool, atree.Storable) Value { +func (v FunctionValue) Transfer(*config.Config, atree.Address, bool, atree.Storable) Value { return v } @@ -123,6 +124,6 @@ func (StringValue) StaticType(common.MemoryGauge) types.StaticType { return interpreter.PrimitiveStaticTypeString } -func (v StringValue) Transfer(*context.Context, atree.Address, bool, atree.Storable) Value { +func (v StringValue) Transfer(*config.Config, atree.Address, bool, atree.Storable) Value { return v } diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 7f280b787b..d125950476 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -20,11 +20,12 @@ package vm import ( "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/bbq" "github.com/onflow/cadence/runtime/bbq/constantkind" "github.com/onflow/cadence/runtime/bbq/leb128" "github.com/onflow/cadence/runtime/bbq/opcode" - "github.com/onflow/cadence/runtime/bbq/vm/context" + "github.com/onflow/cadence/runtime/bbq/vm/config" "github.com/onflow/cadence/runtime/bbq/vm/types" "github.com/onflow/cadence/runtime/bbq/vm/values" "github.com/onflow/cadence/runtime/common" @@ -33,47 +34,72 @@ import ( ) type VM struct { - Program *bbq.Program - globals []values.Value - constants []values.Value - staticTypes []types.StaticType - functions map[string]*bbq.Function - callFrame *callFrame - stack []values.Value - context *context.Context + functions map[string]values.FunctionValue + callFrame *callFrame + stack []values.Value + config *config.Config } -func NewVM(program *bbq.Program) *VM { - // TODO: include non-function globals +func NewVM(program *bbq.Program, conf *config.Config) *VM { + if conf == nil { + conf = &config.Config{} + } + conf.Storage = interpreter.NewInMemoryStorage(nil) + + globals := initializeGlobals(program, conf) + functions := indexFunctions(program.Functions, globals) + + return &VM{ + functions: functions, + config: conf, + } +} + +func initializeGlobals(program *bbq.Program, conf *config.Config) []values.Value { + // TODO: global variable lookup relies too much on the order. + // Figure out a better way. + + var importedGlobals []values.Value + for _, location := range program.Imports { + importedProgram := conf.ImportHandler(location) + // TODO: cache imported globals for imported programs. + importedProgramGlobals := initializeGlobals(importedProgram, conf) + importedGlobals = append(importedGlobals, importedProgramGlobals...) + } + + ctx := values.NewContext(program, nil) + // Iterate through `program.Functions` to be deterministic. // Order of globals must be same as index set at `Compiler.addGlobal()`. + // TODO: include non-function globals + globals := make([]values.Value, 0, len(program.Functions)) for _, function := range program.Functions { // TODO: - globals = append(globals, values.FunctionValue{Function: function}) + globals = append(globals, values.FunctionValue{ + Function: function, + Context: ctx, + }) } - functions := indexFunctions(program.Functions) + // Imported globals are added first. + // This is the same order as they are added in the compiler. + ctx.Globals = importedGlobals + ctx.Globals = append(ctx.Globals, globals...) - ctx := &context.Context{ - Storage: interpreter.NewInMemoryStorage(nil), - } - - return &VM{ - Program: program, - globals: globals, - functions: functions, - constants: make([]values.Value, len(program.Constants)), - staticTypes: make([]types.StaticType, len(program.Types)), - context: ctx, - } + // Return only the globals defined in the current program. + // Because the importer/caller doesn't need to know globals of nested imports. + return globals } -func indexFunctions(functions []*bbq.Function) map[string]*bbq.Function { - indexedFunctions := make(map[string]*bbq.Function, len(functions)) - for _, function := range functions { - indexedFunctions[function.Name] = function +func indexFunctions(functions []*bbq.Function, globals []values.Value) map[string]values.FunctionValue { + // TODO: Filter out non-functions + indexedFunctions := make(map[string]values.FunctionValue, len(functions)) + for _, globalValue := range globals { + function := globalValue.(values.FunctionValue) + indexedFunctions[function.Function.Name] = function } + return indexedFunctions } @@ -112,7 +138,7 @@ func (vm *VM) replaceTop(value values.Value) { vm.stack[lastIndex] = value } -func (vm *VM) pushCallFrame(function *bbq.Function, arguments []values.Value) { +func (vm *VM) pushCallFrame(ctx *values.Context, function *bbq.Function, arguments []values.Value) { locals := make([]values.Value, function.LocalCount) for i, argument := range arguments { locals[i] = argument @@ -122,6 +148,7 @@ func (vm *VM) pushCallFrame(function *bbq.Function, arguments []values.Value) { parent: vm.callFrame, locals: locals, function: function, + context: ctx, } vm.callFrame = callFrame } @@ -136,11 +163,11 @@ func (vm *VM) Invoke(name string, arguments ...values.Value) (values.Value, erro return nil, errors.NewDefaultUserError("unknown function") } - if len(arguments) != int(function.ParameterCount) { + if len(arguments) != int(function.Function.ParameterCount) { return nil, errors.NewDefaultUserError("wrong number of arguments") } - vm.pushCallFrame(function, arguments) + vm.pushCallFrame(function.Context, function.Function, arguments) vm.run() @@ -230,7 +257,7 @@ func opFalse(vm *VM) { func opGetConstant(vm *VM) { callFrame := vm.callFrame index := callFrame.getUint16() - constant := vm.constants[index] + constant := callFrame.context.Constants[index] if constant == nil { constant = vm.initializeConstant(index) } @@ -253,7 +280,7 @@ func opSetLocal(vm *VM) { func opGetGlobal(vm *VM) { callFrame := vm.callFrame index := callFrame.getUint16() - vm.push(vm.globals[index]) + vm.push(callFrame.context.Globals[index]) } func opInvokeStatic(vm *VM) { @@ -262,7 +289,8 @@ func opInvokeStatic(vm *VM) { stackHeight := len(vm.stack) parameterCount := int(value.Function.ParameterCount) arguments := vm.stack[stackHeight-parameterCount:] - vm.pushCallFrame(value.Function, arguments) + + vm.pushCallFrame(value.Context, value.Function, arguments) vm.dropN(parameterCount) } @@ -275,7 +303,7 @@ func opInvoke(vm *VM) { parameterCount := int(value.Function.ParameterCount) + 1 arguments := vm.stack[stackHeight-parameterCount:] - vm.pushCallFrame(value.Function, arguments) + vm.pushCallFrame(value.Context, value.Function, arguments) vm.dropN(parameterCount) } @@ -302,7 +330,7 @@ func opNew(vm *VM) { typeName, compositeKind, common.Address{}, - vm.context.Storage, + vm.config.Storage, ) vm.push(value) } @@ -316,7 +344,7 @@ func opSetField(vm *VM) { fieldValue := vm.pop() - structValue.SetMember(vm.context, fieldNameStr, fieldValue) + structValue.SetMember(vm.config, fieldNameStr, fieldValue) } func opGetField(vm *VM) { @@ -326,7 +354,7 @@ func opGetField(vm *VM) { // TODO: support all container types structValue := vm.pop().(*values.CompositeValue) - fieldValue := structValue.GetMember(vm.context, fieldNameStr) + fieldValue := structValue.GetMember(vm.config, fieldNameStr) vm.push(fieldValue) } @@ -336,12 +364,12 @@ func opCheckType(vm *VM) { value := vm.peek() transferredValue := value.Transfer( - vm.context, + vm.config, atree.Address{}, false, nil, ) - valueType := transferredValue.StaticType(vm.context.MemoryGauge) + valueType := transferredValue.StaticType(vm.config.MemoryGauge) if !types.IsSubType(valueType, targetType) { panic("invalid transfer") } @@ -351,7 +379,7 @@ func opCheckType(vm *VM) { func opDestroy(vm *VM) { value := vm.peek().(*values.CompositeValue) - value.Destroy(vm.context) + value.Destroy(vm.config) } func (vm *VM) run() { @@ -421,7 +449,9 @@ func (vm *VM) run() { } func (vm *VM) initializeConstant(index uint16) (value values.Value) { - constant := vm.Program.Constants[index] + ctx := vm.callFrame.context + + constant := ctx.Program.Constants[index] switch constant.Kind { case constantkind.Int: // TODO: @@ -433,13 +463,15 @@ func (vm *VM) initializeConstant(index uint16) (value values.Value) { // TODO: panic(errors.NewUnreachableError()) } - vm.constants[index] = value + + ctx.Constants[index] = value return value } func (vm *VM) loadType() types.StaticType { - index := vm.callFrame.getUint16() - staticType := vm.staticTypes[index] + callframe := vm.callFrame + index := callframe.getUint16() + staticType := callframe.context.StaticTypes[index] if staticType == nil { staticType = vm.initializeType(index) } @@ -448,14 +480,15 @@ func (vm *VM) loadType() types.StaticType { } func (vm *VM) initializeType(index uint16) interpreter.StaticType { - typeBytes := vm.Program.Types[index] + ctx := vm.callFrame.context + typeBytes := ctx.Program.Types[index] dec := interpreter.CBORDecMode.NewByteStreamDecoder(typeBytes) staticType, err := interpreter.NewTypeDecoder(dec, nil).DecodeStaticType() if err != nil { panic(err) } - vm.staticTypes[index] = staticType + ctx.StaticTypes[index] = staticType return staticType } diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index 102c3a3dd6..8e419748c8 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -24,13 +24,16 @@ import ( "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime/ast" + "github.com/onflow/cadence/runtime/bbq/vm/config" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" . "github.com/onflow/cadence/runtime/tests/checker" + "github.com/onflow/cadence/runtime/tests/utils" "github.com/onflow/cadence/runtime/bbq" "github.com/onflow/cadence/runtime/bbq/compiler" - "github.com/onflow/cadence/runtime/bbq/vm/context" "github.com/onflow/cadence/runtime/bbq/vm/values" ) @@ -53,7 +56,7 @@ func TestRecursionFib(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program) + vm := NewVM(program, nil) result, err := vm.Invoke( "fib", @@ -71,7 +74,7 @@ func BenchmarkRecursionFib(b *testing.B) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program) + vm := NewVM(program, nil) b.ReportAllocs() b.ResetTimer() @@ -115,7 +118,7 @@ func TestImperativeFib(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program) + vm := NewVM(program, nil) result, err := vm.Invoke( "fib", @@ -133,7 +136,7 @@ func BenchmarkImperativeFib(b *testing.B) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program) + vm := NewVM(program, nil) b.ReportAllocs() b.ResetTimer() @@ -167,7 +170,7 @@ func TestBreak(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program) + vm := NewVM(program, nil) result, err := vm.Invoke("test") require.NoError(t, err) @@ -197,7 +200,7 @@ func TestContinue(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program) + vm := NewVM(program, nil) result, err := vm.Invoke("test") require.NoError(t, err) @@ -236,7 +239,7 @@ func TestNewStruct(t *testing.T) { printProgram(program) - vm := NewVM(program) + vm := NewVM(program, nil) result, err := vm.Invoke("test", values.IntValue{SmallInt: 10}) require.NoError(t, err) @@ -248,7 +251,7 @@ func TestNewStruct(t *testing.T) { require.Equal( t, values.IntValue{SmallInt: 12}, - structValue.GetMember(vm.context, "id"), + structValue.GetMember(vm.config, "id"), ) } @@ -281,7 +284,7 @@ func TestStructMethodCall(t *testing.T) { printProgram(program) - vm := NewVM(program) + vm := NewVM(program, nil) result, err := vm.Invoke("test") require.NoError(t, err) @@ -315,7 +318,7 @@ func BenchmarkNewStruct(b *testing.B) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program) + vm := NewVM(program, nil) b.ReportAllocs() b.ResetTimer() @@ -354,7 +357,7 @@ func BenchmarkNewResource(b *testing.B) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program) + vm := NewVM(program, nil) b.ReportAllocs() b.ResetTimer() @@ -370,7 +373,7 @@ func BenchmarkNewResource(b *testing.B) { func BenchmarkNewStructRaw(b *testing.B) { storage := interpreter.NewInMemoryStorage(nil) - ctx := &context.Context{ + conf := &config.Config{ Storage: storage, } @@ -387,7 +390,7 @@ func BenchmarkNewStructRaw(b *testing.B) { common.Address{}, storage.BasicSlabStorage, ) - structValue.SetMember(ctx, "id", fieldValue) + structValue.SetMember(conf, "id", fieldValue) } } } @@ -396,3 +399,79 @@ func printProgram(program *bbq.Program) { byteCodePrinter := &bbq.BytecodePrinter{} fmt.Println(byteCodePrinter.PrintProgram(program)) } + +func TestImport(t *testing.T) { + + t.Parallel() + + importedChecker, err := ParseAndCheckWithOptions(t, + ` + fun helloText(): String { + return "global function of the imported program" + } + + struct Foo { + var id : String + + init(_ id: String) { + self.id = id + } + + fun sayHello(_ id: Int): String { + self.id + return helloText() + } + } + + `, + ParseAndCheckOptions{ + Location: utils.ImportedLocation, + }, + ) + require.NoError(t, err) + + subComp := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importedProgram := subComp.Compile() + printProgram(importedProgram) + + checker, err := ParseAndCheckWithOptions(t, ` + import Foo from 0x01 + + fun test(): String { + var r = Foo("Hello from Foo!") + return r.sayHello(1) + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + return importedProgram + } + + program := comp.Compile() + printProgram(program) + + vmConfig := &config.Config{ + ImportHandler: func(location common.Location) *bbq.Program { + return importedProgram + }, + } + + vm := NewVM(program, vmConfig) + + result, err := vm.Invoke("test") + require.NoError(t, err) + + require.Equal(t, values.StringValue{String: []byte("global function of the imported program")}, result) +} From 6ce24a74a6ba4b73744918ee129e54487bb7d03d Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 7 Mar 2023 12:31:36 -0800 Subject: [PATCH 12/89] Support importing contracts --- runtime/bbq/bytecode_printer.go | 4 +- runtime/bbq/compiler/compiler.go | 153 +++++++++--- runtime/bbq/compiler/stack.go | 43 ++++ runtime/bbq/contract.go | 24 ++ runtime/bbq/opcode/opcode.go | 8 +- runtime/bbq/opcode/opcode_string.go | 21 +- runtime/bbq/program.go | 1 + runtime/bbq/vm/callframe.go | 5 +- runtime/bbq/vm/{config => }/config.go | 8 +- runtime/bbq/vm/{values => }/context.go | 7 +- runtime/bbq/vm/{types => }/types.go | 2 +- ...composite_value.go => values_composite.go} | 30 +-- .../convertions.go => values_conversions.go} | 40 ++- .../simple_values.go => values_simple.go} | 24 +- runtime/bbq/vm/vm.go | 133 +++++----- runtime/bbq/vm/vm_test.go | 229 ++++++++++++++++-- 16 files changed, 571 insertions(+), 161 deletions(-) create mode 100644 runtime/bbq/compiler/stack.go create mode 100644 runtime/bbq/contract.go rename runtime/bbq/vm/{config => }/config.go (79%) rename runtime/bbq/vm/{values => }/context.go (86%) rename runtime/bbq/vm/{types => }/types.go (98%) rename runtime/bbq/vm/{values/composite_value.go => values_composite.go} (92%) rename runtime/bbq/vm/{values/convertions.go => values_conversions.go} (64%) rename runtime/bbq/vm/{values/simple_values.go => values_simple.go} (72%) diff --git a/runtime/bbq/bytecode_printer.go b/runtime/bbq/bytecode_printer.go index 3085345940..5269e6614e 100644 --- a/runtime/bbq/bytecode_printer.go +++ b/runtime/bbq/bytecode_printer.go @@ -63,6 +63,7 @@ func (p *BytecodePrinter) printCode(codes []byte) { opcode.GetLocal, opcode.SetLocal, opcode.GetGlobal, + opcode.SetGlobal, opcode.Jump, opcode.JumpIfFalse, opcode.CheckType: @@ -80,7 +81,8 @@ func (p *BytecodePrinter) printCode(codes []byte) { var typeName string typeName, i = p.getStringOperand(codes, i) - p.stringBuilder.WriteString(" " + fmt.Sprint(kind) + " " + string(location.TypeID(nil, typeName))) + p.stringBuilder.WriteString(" " + fmt.Sprint(kind) + + " " + string(location.TypeID(nil, typeName))) // opcodes with no operands default: diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index f72ff421ca..d322fa296b 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -21,6 +21,7 @@ package compiler import ( "bytes" "math" + "strings" "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/bbq" @@ -38,8 +39,8 @@ type Compiler struct { Elaboration *sema.Elaboration Config *Config - currentFunction *function - currentCompositeType *sema.CompositeType + currentFunction *function + compositeTypeStack *Stack[*sema.CompositeType] functions []*function constants []*constant @@ -67,6 +68,9 @@ func NewCompiler( Config: &Config{}, globals: map[string]*global{}, typesInPool: map[sema.Type]uint16{}, + compositeTypeStack: &Stack[*sema.CompositeType]{ + elements: make([]*sema.CompositeType, 0), + }, } } @@ -89,7 +93,7 @@ func (c *Compiler) addGlobal(name string) *global { func (c *Compiler) addFunction(name string, parameterCount uint16) *function { c.addGlobal(name) - isCompositeFunction := c.currentCompositeType != nil + isCompositeFunction := !c.compositeTypeStack.isEmpty() function := newFunction(name, parameterCount, isCompositeFunction) c.functions = append(c.functions, function) @@ -186,12 +190,14 @@ func (c *Compiler) Compile() *bbq.Program { constants := c.exportConstants() types := c.exportTypes() imports := c.exportImports() + contract := c.exportContract() return &bbq.Program{ Functions: functions, Constants: constants, Types: types, Imports: imports, + Contract: contract, } } @@ -244,6 +250,20 @@ func (c *Compiler) exportFunctions() []*bbq.Function { return functions } +func (c *Compiler) exportContract() *bbq.Contract { + contractDecl := c.Program.SoleContractDeclaration() + if contractDecl == nil { + return nil + } + + contractType := c.Elaboration.CompositeDeclarationTypes[contractDecl] + addressLocation := contractType.Location.(common.AddressLocation) + return &bbq.Contract{ + Name: addressLocation.Name, + Address: addressLocation.Address[:], + } +} + func (c *Compiler) compileDeclaration(declaration ast.Declaration) { ast.AcceptDeclaration[struct{}](declaration, c) } @@ -380,7 +400,8 @@ func (c *Compiler) VisitSwapStatement(_ *ast.SwapStatement) (_ struct{}) { func (c *Compiler) VisitExpressionStatement(statement *ast.ExpressionStatement) (_ struct{}) { c.compileExpression(statement.Expression) - c.emit(opcode.Pop) + // Drop the expression evaluation result + c.emit(opcode.Drop) return } @@ -461,17 +482,28 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio c.emitVariableLoad(invokedExpr.Identifier.Identifier) c.emit(opcode.InvokeStatic) case *ast.MemberExpression: - // Receiver is loaded first. So 'self' is always the zero-th argument. - // This must be in sync with `compileCompositeFunction`. - c.compileExpression(invokedExpr.Expression) - // Load arguments - c.loadArguments(expression) - // Load function value memberInfo := c.Elaboration.MemberExpressionMemberInfos[invokedExpr] typeName := memberInfo.AccessedType.QualifiedString() funcName := compositeFunctionQualifiedName(typeName, invokedExpr.Identifier.Identifier) - c.emitVariableLoad(funcName) - c.emit(opcode.Invoke) + + invocationType := memberInfo.Member.TypeAnnotation.Type.(*sema.FunctionType) + if invocationType.IsConstructor { + // Calling a type constructor must be invoked statically. e.g: `SomeContract.Foo()`. + // Load arguments + c.loadArguments(expression) + // Load function value + c.emitVariableLoad(funcName) + c.emit(opcode.InvokeStatic) + } else { + // Receiver is loaded first. So 'self' is always the zero-th argument. + // This must be in sync with `compileCompositeFunction`. + c.compileExpression(invokedExpr.Expression) + // Load arguments + c.loadArguments(expression) + // Load function value + c.emitVariableLoad(funcName) + c.emit(opcode.Invoke) + } default: panic(errors.NewUnreachableError()) } @@ -580,17 +612,35 @@ func (c *Compiler) VisitPathExpression(_ *ast.PathExpression) (_ struct{}) { } func (c *Compiler) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunctionDeclaration) (_ struct{}) { - enclosingCompositeTypeName := c.currentCompositeType.Identifier - - var functionName string kind := declaration.DeclarationKind() switch kind { case common.DeclarationKindInitializer: - functionName = enclosingCompositeTypeName + c.compileInitializer(declaration) default: // TODO: support other special functions panic(errors.NewUnreachableError()) } + return +} + +func (c *Compiler) compileInitializer(declaration *ast.SpecialFunctionDeclaration) { + enclosingCompositeTypeName := c.enclosingCompositeTypeFullyQualifiedName() + enclosingType := c.compositeTypeStack.top() + + var functionName string + if enclosingType.Kind == common.CompositeKindContract { + // For contracts, add the initializer as `init()`. + // A global variable with the same name as contract is separately added. + // The VM will load the contract and assign to that global variable during imports resolution. + functionName = compositeFunctionQualifiedName( + enclosingType.Identifier, + declaration.DeclarationIdentifier().Identifier, + ) + } else { + // Use the type name as the function name for initializer. + // So `x = Foo()` would directly call the init method. + functionName = enclosingCompositeTypeName + } parameters := declaration.FunctionDeclaration.ParameterList.Parameters parameterCount := len(parameters) @@ -608,7 +658,8 @@ func (c *Compiler) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunct // Initialize an empty struct and assign to `self`. // i.e: `self = New()` - location := c.currentCompositeType.Location + enclosingCompositeType := c.compositeTypeStack.top() + location := enclosingCompositeType.Location locationBytes, err := locationToBytes(location) if err != nil { panic(err) @@ -623,7 +674,7 @@ func (c *Compiler) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunct args := make([]byte, 0, byteSize) // Write composite kind - kindFirst, kindSecond := encodeUint16(uint16(c.currentCompositeType.Kind)) + kindFirst, kindSecond := encodeUint16(uint16(enclosingCompositeType.Kind)) args = append(args, kindFirst, kindSecond) // Write location @@ -637,6 +688,24 @@ func (c *Compiler) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunct args = append(args, enclosingCompositeTypeName...) c.emit(opcode.New, args...) + + if enclosingType.Kind == common.CompositeKindContract { + // During contract init, update the global variable with the newly initialized contract value. + // So accessing the contract through the global variable while initializing itself, would work. + // i.e: + // contract Foo { + // init() { + // Foo.something() // <-- accessing `Foo` while initializing `Foo` + // } + // } + + // Duplicate the top of stack and store it in both global variable and in `self` + c.emit(opcode.Dup) + global := c.findGlobal(enclosingCompositeTypeName) + first, second := encodeUint16(global.index) + c.emit(opcode.SetGlobal, first, second) + } + c.emit(opcode.SetLocal, selfFirst, selfSecond) // Emit for the statements in `init()` body. @@ -645,8 +714,6 @@ func (c *Compiler) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunct // Constructor should return the created the struct. i.e: return `self` c.emit(opcode.GetLocal, selfFirst, selfSecond) c.emit(opcode.ReturnValue) - - return } func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration) (_ struct{}) { @@ -658,10 +725,8 @@ func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration } func (c *Compiler) declareFunction(declaration *ast.FunctionDeclaration) *function { - functionName := declaration.Identifier.Identifier - if c.currentCompositeType != nil { - functionName = compositeFunctionQualifiedName(c.currentCompositeType.Identifier, functionName) - } + enclosingCompositeTypeName := c.enclosingCompositeTypeFullyQualifiedName() + functionName := compositeFunctionQualifiedName(enclosingCompositeTypeName, declaration.Identifier.Identifier) functionType := c.Elaboration.FunctionDeclarationFunctionTypes[declaration] parameterCount := len(functionType.Parameters) @@ -673,10 +738,13 @@ func (c *Compiler) declareFunction(declaration *ast.FunctionDeclaration) *functi } func (c *Compiler) VisitCompositeDeclaration(declaration *ast.CompositeDeclaration) (_ struct{}) { - prevCompositeType := c.currentCompositeType - c.currentCompositeType = c.Elaboration.CompositeDeclarationTypes[declaration] + if declaration.DeclarationKind() == common.DeclarationKindContract { + c.addGlobal(declaration.Identifier.Identifier) + } + + c.compositeTypeStack.push(c.Elaboration.CompositeDeclarationTypes[declaration]) defer func() { - c.currentCompositeType = prevCompositeType + c.compositeTypeStack.pop() }() for _, specialFunc := range declaration.Members.SpecialFunctions() { @@ -687,6 +755,10 @@ func (c *Compiler) VisitCompositeDeclaration(declaration *ast.CompositeDeclarati c.compileCompositeFunction(function) } + for _, nestedTypes := range declaration.Members.Composites() { + c.compileDeclaration(nestedTypes) + } + // TODO: return @@ -717,6 +789,13 @@ func (c *Compiler) VisitPragmaDeclaration(_ *ast.PragmaDeclaration) (_ struct{}) func (c *Compiler) VisitImportDeclaration(declaration *ast.ImportDeclaration) (_ struct{}) { importedProgram := c.Config.ImportHandler(declaration.Location) + + // Add a global variable for the imported contract value. + contractDecl := importedProgram.Contract + if contractDecl != nil { + c.addGlobal(contractDecl.Name) + } + for _, function := range importedProgram.Functions { // TODO: Filter-in only public functions c.addGlobal(function.Name) @@ -768,7 +847,27 @@ func (c *Compiler) addType(data []byte) uint16 { return uint16(count) } +func (c *Compiler) enclosingCompositeTypeFullyQualifiedName() string { + if c.compositeTypeStack.isEmpty() { + return "" + } + + var sb strings.Builder + for i, typ := range c.compositeTypeStack.elements { + if i > 0 { + sb.WriteRune('.') + } + sb.WriteString(typ.Identifier) + } + + return sb.String() +} + func compositeFunctionQualifiedName(typeName, functionName string) string { + if typeName == "" { + return functionName + } + return typeName + "." + functionName } diff --git a/runtime/bbq/compiler/stack.go b/runtime/bbq/compiler/stack.go new file mode 100644 index 0000000000..1d1ca28ece --- /dev/null +++ b/runtime/bbq/compiler/stack.go @@ -0,0 +1,43 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package compiler + +type Stack[T any] struct { + elements []T +} + +func (s *Stack[T]) push(typ T) { + s.elements = append(s.elements, typ) +} + +func (s *Stack[T]) pop() T { + lastIndex := len(s.elements) - 1 + top := s.elements[lastIndex] + s.elements = s.elements[:lastIndex] + return top +} + +func (s *Stack[T]) top() T { + lastIndex := len(s.elements) - 1 + return s.elements[lastIndex] +} + +func (s *Stack[T]) isEmpty() bool { + return len(s.elements) == 0 +} diff --git a/runtime/bbq/contract.go b/runtime/bbq/contract.go new file mode 100644 index 0000000000..882b595520 --- /dev/null +++ b/runtime/bbq/contract.go @@ -0,0 +1,24 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package bbq + +type Contract struct { + Name string + Address []byte +} diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index 581e064c1d..c649a76eaf 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -49,14 +49,18 @@ const ( GetLocal SetLocal GetGlobal + SetGlobal GetField SetField - InvokeStatic Invoke + InvokeStatic + InvokeNative New Destroy - Pop CheckType + + Drop + Dup ) diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index ff42e1d6ab..3cb2d246f0 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -30,19 +30,22 @@ func _() { _ = x[GetLocal-19] _ = x[SetLocal-20] _ = x[GetGlobal-21] - _ = x[GetField-22] - _ = x[SetField-23] - _ = x[InvokeStatic-24] + _ = x[SetGlobal-22] + _ = x[GetField-23] + _ = x[SetField-24] _ = x[Invoke-25] - _ = x[New-26] - _ = x[Destroy-27] - _ = x[Pop-28] - _ = x[CheckType-29] + _ = x[InvokeStatic-26] + _ = x[InvokeNative-27] + _ = x[New-28] + _ = x[Destroy-29] + _ = x[CheckType-30] + _ = x[Drop-31] + _ = x[Dup-32] } -const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalGetFieldSetFieldInvokeStaticInvokeNewDestroyPopCheckType" +const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeInvokeStaticInvokeNativeNewDestroyCheckTypeDropDup" -var _Opcode_index = [...]uint8{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 202, 210, 222, 228, 231, 238, 241, 250} +var _Opcode_index = [...]uint16{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 203, 211, 219, 225, 237, 249, 252, 259, 268, 272, 275} func (i Opcode) String() string { if i >= Opcode(len(_Opcode_index)-1) { diff --git a/runtime/bbq/program.go b/runtime/bbq/program.go index 24588f4390..6e00688d19 100644 --- a/runtime/bbq/program.go +++ b/runtime/bbq/program.go @@ -21,6 +21,7 @@ package bbq import "github.com/onflow/cadence/runtime/common" type Program struct { + Contract *Contract Imports []common.Location Functions []*Function Constants []*Constant diff --git a/runtime/bbq/vm/callframe.go b/runtime/bbq/vm/callframe.go index aa464ef503..140bcc6e90 100644 --- a/runtime/bbq/vm/callframe.go +++ b/runtime/bbq/vm/callframe.go @@ -20,13 +20,12 @@ package vm import ( "github.com/onflow/cadence/runtime/bbq" - "github.com/onflow/cadence/runtime/bbq/vm/values" ) type callFrame struct { parent *callFrame - context *values.Context - locals []values.Value + context *Context + locals []Value function *bbq.Function ip uint16 } diff --git a/runtime/bbq/vm/config/config.go b/runtime/bbq/vm/config.go similarity index 79% rename from runtime/bbq/vm/config/config.go rename to runtime/bbq/vm/config.go index c14e5c8b96..1fd2836101 100644 --- a/runtime/bbq/vm/config/config.go +++ b/runtime/bbq/vm/config.go @@ -1,7 +1,8 @@ -package config +package vm import ( "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/bbq/compiler" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" @@ -10,9 +11,12 @@ import ( type Config struct { Storage common.MemoryGauge - ImportHandler compiler.ImportHandler + compiler.ImportHandler + ContractValueHandler } +type ContractValueHandler func(conf *Config, location common.Location) *CompositeValue + type Storage interface { atree.SlabStorage } diff --git a/runtime/bbq/vm/values/context.go b/runtime/bbq/vm/context.go similarity index 86% rename from runtime/bbq/vm/values/context.go rename to runtime/bbq/vm/context.go index a3e6e50704..36ca70bbe5 100644 --- a/runtime/bbq/vm/values/context.go +++ b/runtime/bbq/vm/context.go @@ -16,18 +16,17 @@ * limitations under the License. */ -package values +package vm import ( "github.com/onflow/cadence/runtime/bbq" - "github.com/onflow/cadence/runtime/bbq/vm/types" ) type Context struct { Program *bbq.Program Globals []Value Constants []Value - StaticTypes []types.StaticType + StaticTypes []StaticType } func NewContext(program *bbq.Program, globals []Value) *Context { @@ -35,6 +34,6 @@ func NewContext(program *bbq.Program, globals []Value) *Context { Program: program, Globals: globals, Constants: make([]Value, len(program.Constants)), - StaticTypes: make([]types.StaticType, len(program.Types)), + StaticTypes: make([]StaticType, len(program.Types)), } } diff --git a/runtime/bbq/vm/types/types.go b/runtime/bbq/vm/types.go similarity index 98% rename from runtime/bbq/vm/types/types.go rename to runtime/bbq/vm/types.go index 266bd032dc..ede2085c38 100644 --- a/runtime/bbq/vm/types/types.go +++ b/runtime/bbq/vm/types.go @@ -16,7 +16,7 @@ * limitations under the License. */ -package types +package vm import "github.com/onflow/cadence/runtime/interpreter" diff --git a/runtime/bbq/vm/values/composite_value.go b/runtime/bbq/vm/values_composite.go similarity index 92% rename from runtime/bbq/vm/values/composite_value.go rename to runtime/bbq/vm/values_composite.go index 801ea2be6c..af7ae101f5 100644 --- a/runtime/bbq/vm/values/composite_value.go +++ b/runtime/bbq/vm/values_composite.go @@ -16,17 +16,13 @@ * limitations under the License. */ -package values +package vm import ( "github.com/onflow/atree" - "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" - - "github.com/onflow/cadence/runtime/bbq/vm/config" - "github.com/onflow/cadence/runtime/bbq/vm/types" ) type CompositeValue struct { @@ -34,7 +30,7 @@ type CompositeValue struct { Location common.Location QualifiedIdentifier string typeID common.TypeID - staticType types.StaticType + staticType StaticType Kind common.CompositeKind } @@ -74,7 +70,7 @@ func NewCompositeValue( func (*CompositeValue) isValue() {} -func (v *CompositeValue) StaticType(memoryGauge common.MemoryGauge) types.StaticType { +func (v *CompositeValue) StaticType(memoryGauge common.MemoryGauge) StaticType { if v.staticType == nil { // NOTE: Instead of using NewCompositeStaticType, which always generates the type ID, // use the TypeID accessor, which may return an already computed type ID @@ -88,7 +84,7 @@ func (v *CompositeValue) StaticType(memoryGauge common.MemoryGauge) types.Static return v.staticType } -func (v *CompositeValue) GetMember(config *config.Config, name string) Value { +func (v *CompositeValue) GetMember(config *Config, name string) Value { storable, err := v.dictionary.Get( interpreter.StringAtreeComparator, interpreter.StringAtreeHashInput, @@ -103,13 +99,13 @@ func (v *CompositeValue) GetMember(config *config.Config, name string) Value { if storable != nil { interpreterValue := interpreter.StoredValue(config.MemoryGauge, storable, config.Storage) // TODO: Temp conversion - return InterpreterValueToVMValue(interpreterValue) + return InterpreterValueToVMValue(config, interpreterValue) } return nil } -func (v *CompositeValue) SetMember(conf *config.Config, name string, value Value) { +func (v *CompositeValue) SetMember(conf *Config, name string, value Value) { // TODO: //address := v.StorageID().Address @@ -139,7 +135,7 @@ func (v *CompositeValue) SetMember(conf *config.Config, name string, value Value //existingValue := interpreter.StoredValue(nil, existingStorable, context.Storage) //existingValue.DeepRemove(interpreter) - config.RemoveReferencedSlab(conf.Storage, existingStorable) + RemoveReferencedSlab(conf.Storage, existingStorable) } } @@ -166,7 +162,7 @@ func (v *CompositeValue) IsResourceKinded() bool { } func (v *CompositeValue) Transfer( - conf *config.Config, + conf *Config, address atree.Address, remove bool, storable atree.Storable, @@ -247,7 +243,7 @@ func (v *CompositeValue) Transfer( value := interpreter.MustConvertStoredValue(conf.MemoryGauge, atreeValue) - vmValue := InterpreterValueToVMValue(value) + vmValue := InterpreterValueToVMValue(nil, value) vmValue.Transfer(conf, address, remove, nil) return atreeKey, value, nil @@ -259,15 +255,15 @@ func (v *CompositeValue) Transfer( if remove { err = v.dictionary.PopIterate(func(nameStorable atree.Storable, valueStorable atree.Storable) { - config.RemoveReferencedSlab(conf.Storage, nameStorable) - config.RemoveReferencedSlab(conf.Storage, valueStorable) + RemoveReferencedSlab(conf.Storage, nameStorable) + RemoveReferencedSlab(conf.Storage, valueStorable) }) if err != nil { panic(errors.NewExternalError(err)) } //interpreter.maybeValidateAtreeValue(v.dictionary) - config.RemoveReferencedSlab(conf.Storage, storable) + RemoveReferencedSlab(conf.Storage, storable) } } @@ -347,7 +343,7 @@ func (v *CompositeValue) Transfer( return res } -func (v *CompositeValue) Destroy(*config.Config) { +func (v *CompositeValue) Destroy(*Config) { //interpreter.ReportComputation(common.ComputationKindDestroyCompositeValue, 1) // diff --git a/runtime/bbq/vm/values/convertions.go b/runtime/bbq/vm/values_conversions.go similarity index 64% rename from runtime/bbq/vm/values/convertions.go rename to runtime/bbq/vm/values_conversions.go index 9489c3ef46..ff74d86195 100644 --- a/runtime/bbq/vm/values/convertions.go +++ b/runtime/bbq/vm/values_conversions.go @@ -16,33 +16,69 @@ * limitations under the License. */ -package values +package vm import ( + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/tests/utils" ) // Utility methods to convert between old and new values. // These are temporary until all parts of the interpreter are migrated to the vm. -func InterpreterValueToVMValue(value interpreter.Value) Value { +func InterpreterValueToVMValue(config *Config, value interpreter.Value) Value { switch value := value.(type) { case interpreter.IntValue: return IntValue{value.BigInt.Int64()} case *interpreter.StringValue: return StringValue{String: []byte(value.Str)} + case *interpreter.CompositeValue: + return NewCompositeValue( + value.Location, + value.QualifiedIdentifier, + value.Kind, + common.Address{}, + config.Storage, + ) default: panic(errors.NewUnreachableError()) } } +var inter = func() *interpreter.Interpreter { + inter, err := interpreter.NewInterpreter( + nil, + utils.TestLocation, + &interpreter.Config{ + Storage: interpreter.NewInMemoryStorage(nil), + }, + ) + + if err != nil { + panic(err) + } + + return inter +}() + func VMValueToInterpreterValue(value Value) interpreter.Value { switch value := value.(type) { case IntValue: return interpreter.NewIntValueFromInt64(nil, value.SmallInt) case StringValue: return interpreter.NewUnmeteredStringValue(string(value.String)) + case *CompositeValue: + return interpreter.NewCompositeValue( + inter, + interpreter.EmptyLocationRange, + value.Location, + value.QualifiedIdentifier, + value.Kind, + nil, + common.Address{}, + ) default: panic(errors.NewUnreachableError()) } diff --git a/runtime/bbq/vm/values/simple_values.go b/runtime/bbq/vm/values_simple.go similarity index 72% rename from runtime/bbq/vm/values/simple_values.go rename to runtime/bbq/vm/values_simple.go index 8aa6bbd927..3876d58020 100644 --- a/runtime/bbq/vm/values/simple_values.go +++ b/runtime/bbq/vm/values_simple.go @@ -16,14 +16,12 @@ * limitations under the License. */ -package values +package vm import ( "github.com/onflow/atree" "github.com/onflow/cadence/runtime/bbq" - "github.com/onflow/cadence/runtime/bbq/vm/config" - "github.com/onflow/cadence/runtime/bbq/vm/types" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" @@ -31,9 +29,9 @@ import ( type Value interface { isValue() - StaticType(common.MemoryGauge) types.StaticType + StaticType(common.MemoryGauge) StaticType Transfer( - config *config.Config, + config *Config, address atree.Address, remove bool, storable atree.Storable, @@ -49,11 +47,11 @@ var _ Value = BoolValue(true) func (BoolValue) isValue() {} -func (BoolValue) StaticType(common.MemoryGauge) types.StaticType { +func (BoolValue) StaticType(common.MemoryGauge) StaticType { return interpreter.PrimitiveStaticTypeBool } -func (v BoolValue) Transfer(*config.Config, atree.Address, bool, atree.Storable) Value { +func (v BoolValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { return v } @@ -65,11 +63,11 @@ var _ Value = IntValue{} func (IntValue) isValue() {} -func (IntValue) StaticType(common.MemoryGauge) types.StaticType { +func (IntValue) StaticType(common.MemoryGauge) StaticType { return interpreter.PrimitiveStaticTypeInt } -func (v IntValue) Transfer(*config.Config, atree.Address, bool, atree.Storable) Value { +func (v IntValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { return v } @@ -104,11 +102,11 @@ var _ Value = FunctionValue{} func (FunctionValue) isValue() {} -func (FunctionValue) StaticType(common.MemoryGauge) types.StaticType { +func (FunctionValue) StaticType(common.MemoryGauge) StaticType { panic(errors.NewUnreachableError()) } -func (v FunctionValue) Transfer(*config.Config, atree.Address, bool, atree.Storable) Value { +func (v FunctionValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { return v } @@ -120,10 +118,10 @@ var _ Value = StringValue{} func (StringValue) isValue() {} -func (StringValue) StaticType(common.MemoryGauge) types.StaticType { +func (StringValue) StaticType(common.MemoryGauge) StaticType { return interpreter.PrimitiveStaticTypeString } -func (v StringValue) Transfer(*config.Config, atree.Address, bool, atree.Storable) Value { +func (v StringValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { return v } diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index d125950476..e08dc6837e 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -25,26 +25,26 @@ import ( "github.com/onflow/cadence/runtime/bbq/constantkind" "github.com/onflow/cadence/runtime/bbq/leb128" "github.com/onflow/cadence/runtime/bbq/opcode" - "github.com/onflow/cadence/runtime/bbq/vm/config" - "github.com/onflow/cadence/runtime/bbq/vm/types" - "github.com/onflow/cadence/runtime/bbq/vm/values" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" ) type VM struct { - functions map[string]values.FunctionValue + functions map[string]FunctionValue callFrame *callFrame - stack []values.Value - config *config.Config + stack []Value + config *Config } -func NewVM(program *bbq.Program, conf *config.Config) *VM { +func NewVM(program *bbq.Program, conf *Config) *VM { + // TODO: Remove initializing config. Following is for testing purpose only. if conf == nil { - conf = &config.Config{} + conf = &Config{} + } + if conf.Storage == nil { + conf.Storage = interpreter.NewInMemoryStorage(nil) } - conf.Storage = interpreter.NewInMemoryStorage(nil) globals := initializeGlobals(program, conf) functions := indexFunctions(program.Functions, globals) @@ -55,28 +55,43 @@ func NewVM(program *bbq.Program, conf *config.Config) *VM { } } -func initializeGlobals(program *bbq.Program, conf *config.Config) []values.Value { +func initializeGlobals(program *bbq.Program, conf *Config) []Value { // TODO: global variable lookup relies too much on the order. // Figure out a better way. - var importedGlobals []values.Value + var importedGlobals []Value for _, location := range program.Imports { importedProgram := conf.ImportHandler(location) - // TODO: cache imported globals for imported programs. + + // TODO: cache globals for imported programs. + importedProgramGlobals := initializeGlobals(importedProgram, conf) importedGlobals = append(importedGlobals, importedProgramGlobals...) } - ctx := values.NewContext(program, nil) + ctx := NewContext(program, nil) + + globalsCount := len(program.Functions) + globals := make([]Value, 0, globalsCount) + + // Load contract value + if program.Contract != nil { + contract := program.Contract + contractLocation := common.NewAddressLocation( + conf.MemoryGauge, + common.MustBytesToAddress(contract.Address), + contract.Name, + ) + contractValue := conf.ContractValueHandler(conf, contractLocation) + globals = append(globals, contractValue) + } // Iterate through `program.Functions` to be deterministic. // Order of globals must be same as index set at `Compiler.addGlobal()`. // TODO: include non-function globals - - globals := make([]values.Value, 0, len(program.Functions)) for _, function := range program.Functions { // TODO: - globals = append(globals, values.FunctionValue{ + globals = append(globals, FunctionValue{ Function: function, Context: ctx, }) @@ -92,22 +107,22 @@ func initializeGlobals(program *bbq.Program, conf *config.Config) []values.Value return globals } -func indexFunctions(functions []*bbq.Function, globals []values.Value) map[string]values.FunctionValue { +func indexFunctions(functions []*bbq.Function, globals []Value) map[string]FunctionValue { // TODO: Filter out non-functions - indexedFunctions := make(map[string]values.FunctionValue, len(functions)) + indexedFunctions := make(map[string]FunctionValue, len(functions)) for _, globalValue := range globals { - function := globalValue.(values.FunctionValue) + function := globalValue.(FunctionValue) indexedFunctions[function.Function.Name] = function } return indexedFunctions } -func (vm *VM) push(value values.Value) { +func (vm *VM) push(value Value) { vm.stack = append(vm.stack, value) } -func (vm *VM) pop() values.Value { +func (vm *VM) pop() Value { lastIndex := len(vm.stack) - 1 value := vm.stack[lastIndex] vm.stack[lastIndex] = nil @@ -115,7 +130,7 @@ func (vm *VM) pop() values.Value { return value } -func (vm *VM) peek() values.Value { +func (vm *VM) peek() Value { lastIndex := len(vm.stack) - 1 return vm.stack[lastIndex] } @@ -128,18 +143,18 @@ func (vm *VM) dropN(count int) { vm.stack = vm.stack[:stackHeight-count] } -func (vm *VM) peekPop() (values.Value, values.Value) { +func (vm *VM) peekPop() (Value, Value) { lastIndex := len(vm.stack) - 1 return vm.stack[lastIndex-1], vm.pop() } -func (vm *VM) replaceTop(value values.Value) { +func (vm *VM) replaceTop(value Value) { lastIndex := len(vm.stack) - 1 vm.stack[lastIndex] = value } -func (vm *VM) pushCallFrame(ctx *values.Context, function *bbq.Function, arguments []values.Value) { - locals := make([]values.Value, function.LocalCount) +func (vm *VM) pushCallFrame(ctx *Context, function *bbq.Function, arguments []Value) { + locals := make([]Value, function.LocalCount) for i, argument := range arguments { locals[i] = argument } @@ -157,7 +172,7 @@ func (vm *VM) popCallFrame() { vm.callFrame = vm.callFrame.parent } -func (vm *VM) Invoke(name string, arguments ...values.Value) (values.Value, error) { +func (vm *VM) Invoke(name string, arguments ...Value) (Value, error) { function, ok := vm.functions[name] if !ok { return nil, errors.NewDefaultUserError("unknown function") @@ -212,7 +227,7 @@ func opJump(vm *VM) { func opJumpIfFalse(vm *VM) { callFrame := vm.callFrame target := callFrame.getUint16() - value := vm.pop().(values.BoolValue) + value := vm.pop().(BoolValue) if !value { callFrame.ip = target } @@ -220,38 +235,38 @@ func opJumpIfFalse(vm *VM) { func opBinaryIntAdd(vm *VM) { left, right := vm.peekPop() - leftNumber := left.(values.IntValue) - rightNumber := right.(values.IntValue) + leftNumber := left.(IntValue) + rightNumber := right.(IntValue) vm.replaceTop(leftNumber.Add(rightNumber)) } func opBinaryIntSubtract(vm *VM) { left, right := vm.peekPop() - leftNumber := left.(values.IntValue) - rightNumber := right.(values.IntValue) + leftNumber := left.(IntValue) + rightNumber := right.(IntValue) vm.replaceTop(leftNumber.Subtract(rightNumber)) } func opBinaryIntLess(vm *VM) { left, right := vm.peekPop() - leftNumber := left.(values.IntValue) - rightNumber := right.(values.IntValue) + leftNumber := left.(IntValue) + rightNumber := right.(IntValue) vm.replaceTop(leftNumber.Less(rightNumber)) } func opBinaryIntGreater(vm *VM) { left, right := vm.peekPop() - leftNumber := left.(values.IntValue) - rightNumber := right.(values.IntValue) + leftNumber := left.(IntValue) + rightNumber := right.(IntValue) vm.replaceTop(leftNumber.Greater(rightNumber)) } func opTrue(vm *VM) { - vm.push(values.TrueValue) + vm.push(TrueValue) } func opFalse(vm *VM) { - vm.push(values.FalseValue) + vm.push(FalseValue) } func opGetConstant(vm *VM) { @@ -284,8 +299,7 @@ func opGetGlobal(vm *VM) { } func opInvokeStatic(vm *VM) { - // TODO: support any function value - value := vm.pop().(values.FunctionValue) + value := vm.pop().(FunctionValue) stackHeight := len(vm.stack) parameterCount := int(value.Function.ParameterCount) arguments := vm.stack[stackHeight-parameterCount:] @@ -295,8 +309,7 @@ func opInvokeStatic(vm *VM) { } func opInvoke(vm *VM) { - // TODO: support any function value - value := vm.pop().(values.FunctionValue) + value := vm.pop().(FunctionValue) stackHeight := len(vm.stack) // Add one to account for `self` @@ -307,10 +320,15 @@ func opInvoke(vm *VM) { vm.dropN(parameterCount) } -func opPop(vm *VM) { +func opDrop(vm *VM) { _ = vm.pop() } +func opDup(vm *VM) { + top := vm.peek() + vm.push(top) +} + func opNew(vm *VM) { callframe := vm.callFrame @@ -325,7 +343,7 @@ func opNew(vm *VM) { typeName := callframe.getString() - value := values.NewCompositeValue( + value := NewCompositeValue( location, typeName, compositeKind, @@ -336,11 +354,11 @@ func opNew(vm *VM) { } func opSetField(vm *VM) { - fieldName := vm.pop().(values.StringValue) + fieldName := vm.pop().(StringValue) fieldNameStr := string(fieldName.String) // TODO: support all container types - structValue := vm.pop().(*values.CompositeValue) + structValue := vm.pop().(*CompositeValue) fieldValue := vm.pop() @@ -348,11 +366,11 @@ func opSetField(vm *VM) { } func opGetField(vm *VM) { - fieldName := vm.pop().(values.StringValue) + fieldName := vm.pop().(StringValue) fieldNameStr := string(fieldName.String) // TODO: support all container types - structValue := vm.pop().(*values.CompositeValue) + structValue := vm.pop().(*CompositeValue) fieldValue := structValue.GetMember(vm.config, fieldNameStr) vm.push(fieldValue) @@ -360,7 +378,6 @@ func opGetField(vm *VM) { func opCheckType(vm *VM) { targetType := vm.loadType() - value := vm.peek() transferredValue := value.Transfer( @@ -370,7 +387,7 @@ func opCheckType(vm *VM) { ) valueType := transferredValue.StaticType(vm.config.MemoryGauge) - if !types.IsSubType(valueType, targetType) { + if !IsSubType(valueType, targetType) { panic("invalid transfer") } @@ -378,7 +395,7 @@ func opCheckType(vm *VM) { } func opDestroy(vm *VM) { - value := vm.peek().(*values.CompositeValue) + value := vm.peek().(*CompositeValue) value.Destroy(vm.config) } @@ -427,8 +444,10 @@ func (vm *VM) run() { opInvokeStatic(vm) case opcode.Invoke: opInvoke(vm) - case opcode.Pop: - opPop(vm) + case opcode.Drop: + opDrop(vm) + case opcode.Dup: + opDup(vm) case opcode.New: opNew(vm) case opcode.SetField: @@ -448,7 +467,7 @@ func (vm *VM) run() { } } -func (vm *VM) initializeConstant(index uint16) (value values.Value) { +func (vm *VM) initializeConstant(index uint16) (value Value) { ctx := vm.callFrame.context constant := ctx.Program.Constants[index] @@ -456,9 +475,9 @@ func (vm *VM) initializeConstant(index uint16) (value values.Value) { case constantkind.Int: // TODO: smallInt, _, _ := leb128.ReadInt64(constant.Data) - value = values.IntValue{SmallInt: smallInt} + value = IntValue{SmallInt: smallInt} case constantkind.String: - value = values.StringValue{String: constant.Data} + value = StringValue{String: constant.Data} default: // TODO: panic(errors.NewUnreachableError()) @@ -468,7 +487,7 @@ func (vm *VM) initializeConstant(index uint16) (value values.Value) { return value } -func (vm *VM) loadType() types.StaticType { +func (vm *VM) loadType() StaticType { callframe := vm.callFrame index := callframe.getUint16() staticType := callframe.context.StaticTypes[index] diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index 8e419748c8..528c79a9be 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -25,7 +25,6 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/cadence/runtime/ast" - "github.com/onflow/cadence/runtime/bbq/vm/config" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" @@ -34,7 +33,6 @@ import ( "github.com/onflow/cadence/runtime/bbq" "github.com/onflow/cadence/runtime/bbq/compiler" - "github.com/onflow/cadence/runtime/bbq/vm/values" ) const recursiveFib = ` @@ -60,10 +58,10 @@ func TestRecursionFib(t *testing.T) { result, err := vm.Invoke( "fib", - values.IntValue{SmallInt: 7}, + IntValue{SmallInt: 7}, ) require.NoError(t, err) - require.Equal(t, values.IntValue{SmallInt: 13}, result) + require.Equal(t, IntValue{SmallInt: 13}, result) } func BenchmarkRecursionFib(b *testing.B) { @@ -79,13 +77,13 @@ func BenchmarkRecursionFib(b *testing.B) { b.ReportAllocs() b.ResetTimer() - expected := values.IntValue{SmallInt: 377} + expected := IntValue{SmallInt: 377} for i := 0; i < b.N; i++ { result, err := vm.Invoke( "fib", - values.IntValue{SmallInt: 14}, + IntValue{SmallInt: 14}, ) require.NoError(b, err) require.Equal(b, expected, result) @@ -122,10 +120,10 @@ func TestImperativeFib(t *testing.T) { result, err := vm.Invoke( "fib", - values.IntValue{SmallInt: 7}, + IntValue{SmallInt: 7}, ) require.NoError(t, err) - require.Equal(t, values.IntValue{SmallInt: 13}, result) + require.Equal(t, IntValue{SmallInt: 13}, result) } func BenchmarkImperativeFib(b *testing.B) { @@ -141,7 +139,7 @@ func BenchmarkImperativeFib(b *testing.B) { b.ReportAllocs() b.ResetTimer() - var value values.Value = values.IntValue{SmallInt: 14} + var value Value = IntValue{SmallInt: 14} for i := 0; i < b.N; i++ { _, err := vm.Invoke("fib", value) @@ -175,7 +173,7 @@ func TestBreak(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) - require.Equal(t, values.IntValue{SmallInt: 4}, result) + require.Equal(t, IntValue{SmallInt: 4}, result) } func TestContinue(t *testing.T) { @@ -205,7 +203,7 @@ func TestContinue(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) - require.Equal(t, values.IntValue{SmallInt: 3}, result) + require.Equal(t, IntValue{SmallInt: 3}, result) } func TestNewStruct(t *testing.T) { @@ -241,16 +239,16 @@ func TestNewStruct(t *testing.T) { vm := NewVM(program, nil) - result, err := vm.Invoke("test", values.IntValue{SmallInt: 10}) + result, err := vm.Invoke("test", IntValue{SmallInt: 10}) require.NoError(t, err) - require.IsType(t, &values.CompositeValue{}, result) - structValue := result.(*values.CompositeValue) + require.IsType(t, &CompositeValue{}, result) + structValue := result.(*CompositeValue) require.Equal(t, "Foo", structValue.QualifiedIdentifier) require.Equal( t, - values.IntValue{SmallInt: 12}, + IntValue{SmallInt: 12}, structValue.GetMember(vm.config, "id"), ) } @@ -289,7 +287,7 @@ func TestStructMethodCall(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) - require.Equal(t, values.StringValue{String: []byte("Hello from Foo!")}, result) + require.Equal(t, StringValue{String: []byte("Hello from Foo!")}, result) } func BenchmarkNewStruct(b *testing.B) { @@ -323,7 +321,7 @@ func BenchmarkNewStruct(b *testing.B) { b.ReportAllocs() b.ResetTimer() - value := values.IntValue{SmallInt: 7} + value := IntValue{SmallInt: 7} for i := 0; i < b.N; i++ { _, err := vm.Invoke("test", value) @@ -362,7 +360,7 @@ func BenchmarkNewResource(b *testing.B) { b.ReportAllocs() b.ResetTimer() - value := values.IntValue{SmallInt: 7} + value := IntValue{SmallInt: 7} for i := 0; i < b.N; i++ { _, err := vm.Invoke("test", value) @@ -373,17 +371,17 @@ func BenchmarkNewResource(b *testing.B) { func BenchmarkNewStructRaw(b *testing.B) { storage := interpreter.NewInMemoryStorage(nil) - conf := &config.Config{ + conf := &Config{ Storage: storage, } - fieldValue := values.IntValue{SmallInt: 7} + fieldValue := IntValue{SmallInt: 7} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { for j := 0; j < 8; j++ { - structValue := values.NewCompositeValue( + structValue := NewCompositeValue( nil, "Foo", common.CompositeKindStructure, @@ -454,6 +452,88 @@ func TestImport(t *testing.T) { ) require.NoError(t, err) + importCompiler := compiler.NewCompiler(checker.Program, checker.Elaboration) + importCompiler.Config.ImportHandler = func(location common.Location) *bbq.Program { + return importedProgram + } + + program := importCompiler.Compile() + printProgram(program) + + vmConfig := &Config{ + ImportHandler: func(location common.Location) *bbq.Program { + return importedProgram + }, + } + + vm := NewVM(program, vmConfig) + + result, err := vm.Invoke("test") + require.NoError(t, err) + + require.Equal(t, StringValue{String: []byte("global function of the imported program")}, result) +} + +func TestContractImport(t *testing.T) { + + t.Parallel() + + importedChecker, err := ParseAndCheckWithOptions(t, + ` + + contract MyContract { + + fun helloText(): String { + return "global function of the imported program" + } + + init() {} + + struct Foo { + var id : String + + init(_ id: String) { + self.id = id + } + + fun sayHello(_ id: Int): String { + self.id + return MyContract.helloText() + } + } + } + + `, + ParseAndCheckOptions{ + Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), + }, + ) + require.NoError(t, err) + + importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importedProgram := importCompiler.Compile() + printProgram(importedProgram) + + checker, err := ParseAndCheckWithOptions(t, ` + import MyContract from 0x01 + + fun test(): String { + var r = MyContract.Foo("Hello from Foo!") + return r.sayHello(1) + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + require.NoError(t, err) + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) comp.Config.ImportHandler = func(location common.Location) *bbq.Program { return importedProgram @@ -462,16 +542,119 @@ func TestImport(t *testing.T) { program := comp.Compile() printProgram(program) - vmConfig := &config.Config{ + vmConfig := &Config{ ImportHandler: func(location common.Location) *bbq.Program { return importedProgram }, + ContractValueHandler: func(conf *Config, location common.Location) *CompositeValue { + addressLocation := location.(common.AddressLocation) + return NewCompositeValue( + location, + addressLocation.Name, + common.CompositeKindContract, + addressLocation.Address, + conf.Storage, + ) + }, } vm := NewVM(program, vmConfig) result, err := vm.Invoke("test") require.NoError(t, err) + require.Equal(t, StringValue{String: []byte("global function of the imported program")}, result) +} + +func BenchmarkContractImport(b *testing.B) { + + importedChecker, err := ParseAndCheckWithOptions(b, + ` + contract MyContract { + fun helloText(): String { + return "global function of the imported program" + } + + init() {} + + struct Foo { + var id : String + + init(_ id: String) { + self.id = id + } + + fun sayHello(_ id: Int): String { + // return self.id + return MyContract.helloText() + } + } + } + `, + ParseAndCheckOptions{ + Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), + }, + ) + require.NoError(b, err) + + importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importedProgram := importCompiler.Compile() - require.Equal(t, values.StringValue{String: []byte("global function of the imported program")}, result) + vmConfig := &Config{ + ImportHandler: func(location common.Location) *bbq.Program { + return importedProgram + }, + ContractValueHandler: func(conf *Config, location common.Location) *CompositeValue { + addressLocation := location.(common.AddressLocation) + return NewCompositeValue( + location, + addressLocation.Name, + common.CompositeKindContract, + addressLocation.Address, + conf.Storage, + ) + }, + } + + b.ResetTimer() + b.ReportAllocs() + + value := IntValue{SmallInt: 7} + + for i := 0; i < b.N; i++ { + checker, err := ParseAndCheckWithOptions(b, ` + import MyContract from 0x01 + + fun test(count: Int): String { + var i = 0 + var r = MyContract.Foo("Hello from Foo!") + while i < count { + i = i + 1 + r = MyContract.Foo("Hello from Foo!") + r.sayHello(1) + } + return r.sayHello(1) + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + require.NoError(b, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + return importedProgram + } + program := comp.Compile() + + vm := NewVM(program, vmConfig) + _, err = vm.Invoke("test", value) + require.NoError(b, err) + } } From ece09b456de15078ff39c53ea8a89e640b920f08 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 7 Mar 2023 18:36:35 -0800 Subject: [PATCH 13/89] Add contract initialization --- runtime/bbq/compiler/compiler.go | 73 +++++++++--- runtime/bbq/vm/constants.go | 23 ++++ runtime/bbq/vm/vm.go | 42 ++++++- runtime/bbq/vm/vm_test.go | 189 ++++++++++++++++++++++++++++++- 4 files changed, 307 insertions(+), 20 deletions(-) create mode 100644 runtime/bbq/vm/constants.go diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index d322fa296b..e1eb7bc32c 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -91,8 +91,6 @@ func (c *Compiler) addGlobal(name string) *global { } func (c *Compiler) addFunction(name string, parameterCount uint16) *function { - c.addGlobal(name) - isCompositeFunction := !c.compositeTypeStack.isEmpty() function := newFunction(name, parameterCount, isCompositeFunction) @@ -182,7 +180,23 @@ func (c *Compiler) popLoop() { } func (c *Compiler) Compile() *bbq.Program { - for _, declaration := range c.Program.Declarations() { + + for _, declaration := range c.Program.ImportDeclarations() { + c.compileDeclaration(declaration) + } + + // Reserve globals for functions/types before visiting their implementations. + c.reserveGlobalVars( + "", + c.Program.FunctionDeclarations(), + c.Program.CompositeDeclarations(), + ) + + // Compile declarations + for _, declaration := range c.Program.FunctionDeclarations() { + c.compileDeclaration(declaration) + } + for _, declaration := range c.Program.CompositeDeclarations() { c.compileDeclaration(declaration) } @@ -201,6 +215,40 @@ func (c *Compiler) Compile() *bbq.Program { } } +func (c *Compiler) reserveGlobalVars( + compositeTypeName string, + funcDecls []*ast.FunctionDeclaration, + compositeDecls []*ast.CompositeDeclaration, +) { + for _, declaration := range funcDecls { + funcName := typeQualifiedName(compositeTypeName, declaration.Identifier.Identifier) + c.addGlobal(funcName) + } + + for _, declaration := range compositeDecls { + // TODO: Handle nested composite types. Those name should be `Foo.Bar`. + qualifiedTypeName := typeQualifiedName(compositeTypeName, declaration.Identifier.Identifier) + + c.addGlobal(qualifiedTypeName) + + // For composite type other than contracts, globals variable + // reserved by the type-name will be used for the init method. + // For contracts, globals variable reserved by the type-name + // will be used for the contract value. + // Hence, reserve a separate global var for contract inits. + if declaration.CompositeKind == common.CompositeKindContract { + c.addGlobal("init") + } + + // Define globals for functions before visiting function bodies + c.reserveGlobalVars( + qualifiedTypeName, + declaration.Members.Functions(), + declaration.Members.Composites(), + ) + } +} + func (c *Compiler) exportConstants() []*bbq.Constant { constants := make([]*bbq.Constant, 0, len(c.constants)) for _, constant := range c.constants { @@ -484,7 +532,7 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio case *ast.MemberExpression: memberInfo := c.Elaboration.MemberExpressionMemberInfos[invokedExpr] typeName := memberInfo.AccessedType.QualifiedString() - funcName := compositeFunctionQualifiedName(typeName, invokedExpr.Identifier.Identifier) + funcName := typeQualifiedName(typeName, invokedExpr.Identifier.Identifier) invocationType := memberInfo.Member.TypeAnnotation.Type.(*sema.FunctionType) if invocationType.IsConstructor { @@ -632,10 +680,7 @@ func (c *Compiler) compileInitializer(declaration *ast.SpecialFunctionDeclaratio // For contracts, add the initializer as `init()`. // A global variable with the same name as contract is separately added. // The VM will load the contract and assign to that global variable during imports resolution. - functionName = compositeFunctionQualifiedName( - enclosingType.Identifier, - declaration.DeclarationIdentifier().Identifier, - ) + functionName = declaration.DeclarationIdentifier().Identifier } else { // Use the type name as the function name for initializer. // So `x = Foo()` would directly call the init method. @@ -726,7 +771,7 @@ func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration func (c *Compiler) declareFunction(declaration *ast.FunctionDeclaration) *function { enclosingCompositeTypeName := c.enclosingCompositeTypeFullyQualifiedName() - functionName := compositeFunctionQualifiedName(enclosingCompositeTypeName, declaration.Identifier.Identifier) + functionName := typeQualifiedName(enclosingCompositeTypeName, declaration.Identifier.Identifier) functionType := c.Elaboration.FunctionDeclarationFunctionTypes[declaration] parameterCount := len(functionType.Parameters) @@ -738,15 +783,13 @@ func (c *Compiler) declareFunction(declaration *ast.FunctionDeclaration) *functi } func (c *Compiler) VisitCompositeDeclaration(declaration *ast.CompositeDeclaration) (_ struct{}) { - if declaration.DeclarationKind() == common.DeclarationKindContract { - c.addGlobal(declaration.Identifier.Identifier) - } - - c.compositeTypeStack.push(c.Elaboration.CompositeDeclarationTypes[declaration]) + enclosingCompositeType := c.Elaboration.CompositeDeclarationTypes[declaration] + c.compositeTypeStack.push(enclosingCompositeType) defer func() { c.compositeTypeStack.pop() }() + // Compile members for _, specialFunc := range declaration.Members.SpecialFunctions() { c.compileDeclaration(specialFunc) } @@ -863,7 +906,7 @@ func (c *Compiler) enclosingCompositeTypeFullyQualifiedName() string { return sb.String() } -func compositeFunctionQualifiedName(typeName, functionName string) string { +func typeQualifiedName(typeName, functionName string) string { if typeName == "" { return functionName } diff --git a/runtime/bbq/vm/constants.go b/runtime/bbq/vm/constants.go new file mode 100644 index 0000000000..f4d610e245 --- /dev/null +++ b/runtime/bbq/vm/constants.go @@ -0,0 +1,23 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +const ( + InitFunctionName = "init" +) diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index e08dc6837e..da44fe4d6c 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -82,7 +82,13 @@ func initializeGlobals(program *bbq.Program, conf *Config) []Value { common.MustBytesToAddress(contract.Address), contract.Name, ) - contractValue := conf.ContractValueHandler(conf, contractLocation) + + var contractValue Value + // TODO: remove this check. This shouldn't be nil ideally. + if conf.ContractValueHandler != nil { + contractValue = conf.ContractValueHandler(conf, contractLocation) + } + globals = append(globals, contractValue) } @@ -108,10 +114,12 @@ func initializeGlobals(program *bbq.Program, conf *Config) []Value { } func indexFunctions(functions []*bbq.Function, globals []Value) map[string]FunctionValue { - // TODO: Filter out non-functions indexedFunctions := make(map[string]FunctionValue, len(functions)) for _, globalValue := range globals { - function := globalValue.(FunctionValue) + function, isFunction := globalValue.(FunctionValue) + if !isFunction { + continue + } indexedFunctions[function.Function.Name] = function } @@ -193,6 +201,20 @@ func (vm *VM) Invoke(name string, arguments ...Value) (Value, error) { return vm.pop(), nil } +func (vm *VM) InitializeContract(arguments ...Value) (*CompositeValue, error) { + value, err := vm.Invoke(InitFunctionName, arguments...) + if err != nil { + return nil, err + } + + contractValue, ok := value.(*CompositeValue) + if !ok { + return nil, errors.NewUnexpectedError("invalid contract value") + } + + return contractValue, nil +} + type vmOp func(*VM) var vmOps = [...]vmOp{ @@ -298,6 +320,12 @@ func opGetGlobal(vm *VM) { vm.push(callFrame.context.Globals[index]) } +func opSetGlobal(vm *VM) { + callFrame := vm.callFrame + index := callFrame.getUint16() + callFrame.context.Globals[index] = vm.pop() +} + func opInvokeStatic(vm *VM) { value := vm.pop().(FunctionValue) stackHeight := len(vm.stack) @@ -373,6 +401,12 @@ func opGetField(vm *VM) { structValue := vm.pop().(*CompositeValue) fieldValue := structValue.GetMember(vm.config, fieldNameStr) + if fieldValue == nil { + panic(interpreter.MissingMemberValueError{ + Name: fieldNameStr, + }) + } + vm.push(fieldValue) } @@ -440,6 +474,8 @@ func (vm *VM) run() { opSetLocal(vm) case opcode.GetGlobal: opGetGlobal(vm) + case opcode.SetGlobal: + opSetGlobal(vm) case opcode.InvokeStatic: opInvokeStatic(vm) case opcode.Invoke: diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index 528c79a9be..7e162e7607 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -22,6 +22,7 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/onflow/cadence/runtime/ast" @@ -480,7 +481,6 @@ func TestContractImport(t *testing.T) { importedChecker, err := ParseAndCheckWithOptions(t, ` - contract MyContract { fun helloText(): String { @@ -502,7 +502,6 @@ func TestContractImport(t *testing.T) { } } } - `, ParseAndCheckOptions{ Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), @@ -658,3 +657,189 @@ func BenchmarkContractImport(b *testing.B) { require.NoError(b, err) } } + +func TestInitializeContract(t *testing.T) { + + checker, err := ParseAndCheckWithOptions(t, + ` + contract MyContract { + var status : String + init() { + self.status = "PENDING" + } + } + `, + ParseAndCheckOptions{ + Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), + }, + ) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vm := NewVM(program, nil) + contractValue, err := vm.InitializeContract() + require.NoError(t, err) + + fieldValue := contractValue.GetMember(vm.config, "status") + assert.Equal(t, StringValue{String: []byte("PENDING")}, fieldValue) +} + +func TestContractAccessDuringInit(t *testing.T) { + + t.Parallel() + + t.Run("using contract name", func(t *testing.T) { + t.Parallel() + + checker, err := ParseAndCheckWithOptions(t, ` + contract MyContract { + var status : String + + pub fun getInitialStatus(): String { + return "PENDING" + } + + init() { + self.status = MyContract.getInitialStatus() + } + }`, + ParseAndCheckOptions{ + Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), + }, + ) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + printProgram(program) + + vm := NewVM(program, nil) + contractValue, err := vm.InitializeContract() + require.NoError(t, err) + + fieldValue := contractValue.GetMember(vm.config, "status") + assert.Equal(t, StringValue{String: []byte("PENDING")}, fieldValue) + }) + + t.Run("using self", func(t *testing.T) { + t.Parallel() + + checker, err := ParseAndCheckWithOptions(t, ` + contract MyContract { + var status : String + + pub fun getInitialStatus(): String { + return "PENDING" + } + + init() { + self.status = self.getInitialStatus() + } + }`, + ParseAndCheckOptions{ + Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), + }, + ) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + printProgram(program) + + vm := NewVM(program, nil) + contractValue, err := vm.InitializeContract() + require.NoError(t, err) + + fieldValue := contractValue.GetMember(vm.config, "status") + assert.Equal(t, StringValue{String: []byte("PENDING")}, fieldValue) + }) +} + +func TestFunctionOrder(t *testing.T) { + + t.Parallel() + + t.Run("top level", func(t *testing.T) { + t.Parallel() + + checker, err := ParseAndCheck(t, ` + fun foo(): Int { + return 2 + } + + fun test(): Int { + return foo() + bar() + } + + fun bar(): Int { + return 3 + }`) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vm := NewVM(program, nil) + + result, err := vm.Invoke("test") + require.NoError(t, err) + + require.Equal(t, IntValue{SmallInt: 5}, result) + }) + + t.Run("nested", func(t *testing.T) { + t.Parallel() + + code := ` + contract MyContract { + + fun helloText(): String { + return "global function of the imported program" + } + + init() {} + + fun initializeFoo() { + MyContract.Foo("one") + } + + struct Foo { + var id : String + + init(_ id: String) { + self.id = id + } + + fun sayHello(_ id: Int): String { + self.id + return MyContract.helloText() + } + } + + fun initializeFooAgain() { + MyContract.Foo("two") + } + }` + + checker, err := ParseAndCheckWithOptions( + t, + code, + ParseAndCheckOptions{ + Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), + }, + ) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vm := NewVM(program, nil) + + result, err := vm.Invoke("init") + require.NoError(t, err) + + require.IsType(t, &CompositeValue{}, result) + }) +} From 2c94c758ba724dd9fbb097fc73f80e0c4dc78ee2 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 8 Mar 2023 15:18:05 -0800 Subject: [PATCH 14/89] Fix contract loading --- runtime/bbq/vm/vm.go | 42 ++++++---- runtime/bbq/vm/vm_test.go | 172 +++++++++++++++++++++++++++++++++++--- runtime/runtime_test.go | 107 ++++++++++++++++++++++++ 3 files changed, 292 insertions(+), 29 deletions(-) diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index da44fe4d6c..9a08f95648 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -66,30 +66,38 @@ func initializeGlobals(program *bbq.Program, conf *Config) []Value { // TODO: cache globals for imported programs. importedProgramGlobals := initializeGlobals(importedProgram, conf) + + // Load contract value + if importedProgram.Contract != nil { + // If the imported program is a contract, + // load the contract value and populate the global variable. + contract := importedProgram.Contract + contractLocation := common.NewAddressLocation( + conf.MemoryGauge, + common.MustBytesToAddress(contract.Address), + contract.Name, + ) + + // TODO: remove this check. This shouldn't be nil ideally. + if conf.ContractValueHandler != nil { + // Contract value is always at the zero-th index. + importedProgramGlobals[0] = conf.ContractValueHandler(conf, contractLocation) + } + } + importedGlobals = append(importedGlobals, importedProgramGlobals...) } ctx := NewContext(program, nil) - globalsCount := len(program.Functions) - globals := make([]Value, 0, globalsCount) + globals := make([]Value, 0) - // Load contract value + // If the current program is a contract, reserve a global variable for the contract value. + // The reserved position is always the zero-th index. + // This value will be populated either by the `init` method invocation of the contract, + // Or when this program is imported by another (loads the value from storage). if program.Contract != nil { - contract := program.Contract - contractLocation := common.NewAddressLocation( - conf.MemoryGauge, - common.MustBytesToAddress(contract.Address), - contract.Name, - ) - - var contractValue Value - // TODO: remove this check. This shouldn't be nil ideally. - if conf.ContractValueHandler != nil { - contractValue = conf.ContractValueHandler(conf, contractLocation) - } - - globals = append(globals, contractValue) + globals = append(globals, nil) } // Iterate through `program.Functions` to be deterministic. diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index 7e162e7607..f89a849870 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -25,6 +25,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" @@ -314,15 +316,15 @@ func BenchmarkNewStruct(b *testing.B) { `) require.NoError(b, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - program := comp.Compile() - - vm := NewVM(program, nil) + value := IntValue{SmallInt: 1} b.ReportAllocs() b.ResetTimer() - value := IntValue{SmallInt: 7} + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vm := NewVM(program, nil) for i := 0; i < b.N; i++ { _, err := vm.Invoke("test", value) @@ -353,17 +355,16 @@ func BenchmarkNewResource(b *testing.B) { `) require.NoError(b, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - program := comp.Compile() - - vm := NewVM(program, nil) - b.ReportAllocs() b.ResetTimer() - value := IntValue{SmallInt: 7} + value := IntValue{SmallInt: 9} for i := 0; i < b.N; i++ { + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vm := NewVM(program, nil) _, err := vm.Invoke("test", value) require.NoError(b, err) } @@ -381,7 +382,7 @@ func BenchmarkNewStructRaw(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - for j := 0; j < 8; j++ { + for j := 0; j < 1; j++ { structValue := NewCompositeValue( nil, "Foo", @@ -390,6 +391,7 @@ func BenchmarkNewStructRaw(b *testing.B) { storage.BasicSlabStorage, ) structValue.SetMember(conf, "id", fieldValue) + structValue.Transfer(conf, atree.Address{}, false, nil) } } } @@ -843,3 +845,149 @@ func TestFunctionOrder(t *testing.T) { require.IsType(t, &CompositeValue{}, result) }) } + +func TestContractField(t *testing.T) { + + t.Parallel() + + t.Run("get", func(t *testing.T) { + + importedChecker, err := ParseAndCheckWithOptions(t, + ` + contract MyContract { + var status : String + + init() { + self.status = "PENDING" + } + } + `, + ParseAndCheckOptions{ + Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), + }, + ) + require.NoError(t, err) + + importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importedProgram := importCompiler.Compile() + printProgram(importedProgram) + + vm := NewVM(importedProgram, nil) + importedContractValue, err := vm.InitializeContract() + require.NoError(t, err) + + checker, err := ParseAndCheckWithOptions(t, ` + import MyContract from 0x01 + + fun test(): String { + return MyContract.status + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + return importedProgram + } + + program := comp.Compile() + printProgram(program) + + vmConfig := &Config{ + ImportHandler: func(location common.Location) *bbq.Program { + return importedProgram + }, + ContractValueHandler: func(conf *Config, location common.Location) *CompositeValue { + return importedContractValue + }, + } + + vm = NewVM(program, vmConfig) + result, err := vm.Invoke("test") + require.NoError(t, err) + require.Equal(t, StringValue{String: []byte("PENDING")}, result) + }) + + t.Run("set", func(t *testing.T) { + + importedChecker, err := ParseAndCheckWithOptions(t, + ` + contract MyContract { + var status : String + + init() { + self.status = "PENDING" + } + } + `, + ParseAndCheckOptions{ + Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), + }, + ) + require.NoError(t, err) + + importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importedProgram := importCompiler.Compile() + printProgram(importedProgram) + + vm := NewVM(importedProgram, nil) + importedContractValue, err := vm.InitializeContract() + require.NoError(t, err) + + checker, err := ParseAndCheckWithOptions(t, ` + import MyContract from 0x01 + + fun test(): String { + MyContract.status = "UPDATED" + return MyContract.status + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + return importedProgram + } + + program := comp.Compile() + printProgram(program) + + vmConfig := &Config{ + ImportHandler: func(location common.Location) *bbq.Program { + return importedProgram + }, + ContractValueHandler: func(conf *Config, location common.Location) *CompositeValue { + return importedContractValue + }, + } + + vm = NewVM(program, vmConfig) + + result, err := vm.Invoke("test") + require.NoError(t, err) + require.Equal(t, StringValue{String: []byte("UPDATED")}, result) + + fieldValue := importedContractValue.GetMember(vm.config, "status") + assert.Equal(t, StringValue{String: []byte("UPDATED")}, fieldValue) + }) +} diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 00071fb2ad..8cb9e25972 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -7545,3 +7545,110 @@ func TestImportingTestStdlib(t *testing.T) { require.ErrorAs(t, errs[0], ¬DeclaredErr) assert.Equal(t, "Test", notDeclaredErr.Name) } + +func BenchmarkContractFunctionInvocation(b *testing.B) { + + runtime := newTestInterpreterRuntime() + + addressValue := cadence.BytesToAddress([]byte{0x1}) + + contract := []byte(` + pub contract Test { + pub fun helloText(): String { + return "global function of the imported program" + } + + init() {} + + pub struct Foo { + pub var id : String + + init(_ id: String) { + self.id = id + } + + pub fun sayHello(_ id: Int): String { + // return self.id + return Test.helloText() + } + } + } + `) + + deploy := DeploymentTransaction("Test", contract) + + var accountCode []byte + var events []cadence.Event + + runtimeInterface := &testRuntimeInterface{ + getCode: func(_ Location) (bytes []byte, err error) { + return accountCode, nil + }, + storage: newTestLedger(nil, nil), + getSigningAccounts: func() ([]Address, error) { + return []Address{Address(addressValue)}, nil + }, + resolveLocation: singleIdentifierLocationResolver(b), + getAccountContractCode: func(_ Address, _ string) (code []byte, err error) { + return accountCode, nil + }, + updateAccountContractCode: func(_ Address, _ string, code []byte) error { + accountCode = code + return nil + }, + emitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + decodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + nextTransactionLocation := newTransactionLocationGenerator() + + err := runtime.ExecuteTransaction( + Script{ + Source: deploy, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(b, err) + assert.NotNil(b, accountCode) + + script := ` + import Test from 0x01 + + pub fun main(count: Int): String { + var i = 0 + var r = Test.Foo("Hello from Foo!") + while i < count { + i = i + 1 + r = Test.Foo("Hello from Foo!") + r.sayHello(1) + } + return r.sayHello(1) + }` + + args := encodeArgs([]cadence.Value{cadence.NewInt(7)}) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + _, err := runtime.ExecuteScript( + Script{ + Source: []byte(script), + Arguments: args, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(b, err) + } +} From a83a915051d4d64f22a685de0e5984982eb99d11 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 10 Mar 2023 16:52:39 -0800 Subject: [PATCH 15/89] Improve linker --- runtime/bbq/bytecode_printer.go | 12 + runtime/bbq/{vm => commons}/constants.go | 2 +- runtime/bbq/commons/handlers.go | 55 +++ runtime/bbq/compiler/compiler.go | 137 ++++++-- runtime/bbq/compiler/config.go | 8 +- runtime/bbq/compiler/global.go | 6 +- runtime/bbq/import.go | 26 ++ runtime/bbq/program.go | 4 +- runtime/bbq/vm/config.go | 23 +- runtime/bbq/vm/linker.go | 117 +++++++ runtime/bbq/vm/vm.go | 140 ++------ runtime/bbq/vm/vm_test.go | 406 +++++++++++++++++++---- 12 files changed, 713 insertions(+), 223 deletions(-) rename runtime/bbq/{vm => commons}/constants.go (97%) create mode 100644 runtime/bbq/commons/handlers.go create mode 100644 runtime/bbq/import.go create mode 100644 runtime/bbq/vm/linker.go diff --git a/runtime/bbq/bytecode_printer.go b/runtime/bbq/bytecode_printer.go index 5269e6614e..e3e0f8cbb4 100644 --- a/runtime/bbq/bytecode_printer.go +++ b/runtime/bbq/bytecode_printer.go @@ -35,6 +35,7 @@ type BytecodePrinter struct { } func (p *BytecodePrinter) PrintProgram(program *Program) string { + p.printImports(program.Imports) p.printConstantPool(program.Constants) for _, function := range program.Functions { p.printFunction(function) @@ -147,3 +148,14 @@ func (p *BytecodePrinter) getLocation(codes []byte, i int) (location common.Loca return location, i + locationLen } + +func (p *BytecodePrinter) printImports(imports []*Import) { + p.stringBuilder.WriteString("-- Imports --\n") + for _, impt := range imports { + p.stringBuilder.WriteString(impt.Location.String()) + p.stringBuilder.WriteRune('.') + p.stringBuilder.WriteString(impt.Name) + p.stringBuilder.WriteRune('\n') + } + p.stringBuilder.WriteRune('\n') +} diff --git a/runtime/bbq/vm/constants.go b/runtime/bbq/commons/constants.go similarity index 97% rename from runtime/bbq/vm/constants.go rename to runtime/bbq/commons/constants.go index f4d610e245..72b3e1bd75 100644 --- a/runtime/bbq/vm/constants.go +++ b/runtime/bbq/commons/constants.go @@ -16,7 +16,7 @@ * limitations under the License. */ -package vm +package commons const ( InitFunctionName = "init" diff --git a/runtime/bbq/commons/handlers.go b/runtime/bbq/commons/handlers.go new file mode 100644 index 0000000000..5b8861944d --- /dev/null +++ b/runtime/bbq/commons/handlers.go @@ -0,0 +1,55 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package commons + +import ( + "github.com/onflow/cadence/runtime/ast" + "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/sema" +) + +type ImportHandler func(location common.Location) *bbq.Program + +type LocationHandler func(identifiers []ast.Identifier, location common.Location) ([]ResolvedLocation, error) + +type ResolvedLocation = sema.ResolvedLocation + +func ResolveLocation( + locationHandler LocationHandler, + identifiers []ast.Identifier, + location common.Location, +) ([]ResolvedLocation, error) { + + // If no location handler is available, + // default to resolving to a single location that declares all identifiers + + if locationHandler == nil { + return []ResolvedLocation{ + { + Location: location, + Identifiers: identifiers, + }, + }, nil + } + + // A location handler is available, + // use it to resolve the location / identifiers + return locationHandler(identifiers, location) +} diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index e1eb7bc32c..d56673b7cb 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -24,14 +24,16 @@ import ( "strings" "github.com/onflow/cadence/runtime/ast" - "github.com/onflow/cadence/runtime/bbq" - "github.com/onflow/cadence/runtime/bbq/constantkind" - "github.com/onflow/cadence/runtime/bbq/leb128" - "github.com/onflow/cadence/runtime/bbq/opcode" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" + + "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/bbq/commons" + "github.com/onflow/cadence/runtime/bbq/constantkind" + "github.com/onflow/cadence/runtime/bbq/leb128" + "github.com/onflow/cadence/runtime/bbq/opcode" ) type Compiler struct { @@ -42,13 +44,16 @@ type Compiler struct { currentFunction *function compositeTypeStack *Stack[*sema.CompositeType] - functions []*function - constants []*constant - globals map[string]*global - loops []*loop - currentLoop *loop - staticTypes [][]byte - typesInPool map[sema.Type]uint16 + functions []*function + constants []*constant + globals map[string]*global + indexedImportedGlobals map[string]*global + importedGlobals []*global + loops []*loop + currentLoop *loop + staticTypes [][]byte + typesInPool map[sema.Type]uint16 + exportedImports []*bbq.Import // TODO: initialize memoryGauge common.MemoryGauge @@ -63,11 +68,13 @@ func NewCompiler( elaboration *sema.Elaboration, ) *Compiler { return &Compiler{ - Program: program, - Elaboration: elaboration, - Config: &Config{}, - globals: map[string]*global{}, - typesInPool: map[sema.Type]uint16{}, + Program: program, + Elaboration: elaboration, + Config: &Config{}, + globals: map[string]*global{}, + indexedImportedGlobals: map[string]*global{}, + exportedImports: make([]*bbq.Import, 0), + typesInPool: map[sema.Type]uint16{}, compositeTypeStack: &Stack[*sema.CompositeType]{ elements: make([]*sema.CompositeType, 0), }, @@ -75,7 +82,28 @@ func NewCompiler( } func (c *Compiler) findGlobal(name string) *global { - return c.globals[name] + global, ok := c.globals[name] + if ok { + return global + } + + importedGlobal, ok := c.indexedImportedGlobals[name] + if !ok { + panic(errors.NewUnexpectedError("cannot find global declaration '%s'", name)) + } + + // Add the 'importedGlobal' to 'globals' when they are used for the first time. + // This way, the 'globals' would eventually have only the used imports. + // + // If a global is found in imported globals, that means the index is not set. + // So set an index and add it to the 'globals'. + count := len(c.globals) + if count >= math.MaxUint16 { + panic(errors.NewUnexpectedError("invalid global declaration '%s'", name)) + } + importedGlobal.index = uint16(count) + c.globals[name] = importedGlobal + return importedGlobal } func (c *Compiler) addGlobal(name string) *global { @@ -90,6 +118,17 @@ func (c *Compiler) addGlobal(name string) *global { return global } +func (c *Compiler) addImportedGlobal(location common.Location, name string) *global { + // Index is not set here. It is set only if this imported global is used. + global := &global{ + location: location, + name: name, + } + c.importedGlobals = append(c.importedGlobals, global) + c.indexedImportedGlobals[name] = global + return global +} + func (c *Compiler) addFunction(name string, parameterCount uint16) *function { isCompositeFunction := !c.compositeTypeStack.isEmpty() @@ -237,7 +276,7 @@ func (c *Compiler) reserveGlobalVars( // will be used for the contract value. // Hence, reserve a separate global var for contract inits. if declaration.CompositeKind == common.CompositeKindContract { - c.addGlobal("init") + c.addGlobal(commons.InitFunctionName) } // Define globals for functions before visiting function bodies @@ -271,11 +310,23 @@ func (c *Compiler) exportTypes() [][]byte { return types } -func (c *Compiler) exportImports() []common.Location { - imports := c.Program.ImportDeclarations() - exportedImports := make([]common.Location, len(imports)) - for index, importDecl := range imports { - exportedImports[index] = importDecl.Location +func (c *Compiler) exportImports() []*bbq.Import { + exportedImports := make([]*bbq.Import, 0) + + for _, importedGlobal := range c.importedGlobals { + name := importedGlobal.name + + // Export only used imports + if _, ok := c.globals[name]; !ok { + continue + } + + bbqImport := &bbq.Import{ + Location: importedGlobal.location, + Name: name, + } + + exportedImports = append(exportedImports, bbqImport) } return exportedImports @@ -307,7 +358,7 @@ func (c *Compiler) exportContract() *bbq.Contract { contractType := c.Elaboration.CompositeDeclarationTypes[contractDecl] addressLocation := contractType.Location.(common.AddressLocation) return &bbq.Contract{ - Name: addressLocation.Name, + Name: contractType.Identifier, Address: addressLocation.Address[:], } } @@ -831,17 +882,37 @@ func (c *Compiler) VisitPragmaDeclaration(_ *ast.PragmaDeclaration) (_ struct{}) } func (c *Compiler) VisitImportDeclaration(declaration *ast.ImportDeclaration) (_ struct{}) { - importedProgram := c.Config.ImportHandler(declaration.Location) - - // Add a global variable for the imported contract value. - contractDecl := importedProgram.Contract - if contractDecl != nil { - c.addGlobal(contractDecl.Name) + resolvedLocation, err := commons.ResolveLocation( + c.Config.LocationHandler, + declaration.Identifiers, + declaration.Location, + ) + if err != nil { + panic(err) } - for _, function := range importedProgram.Functions { - // TODO: Filter-in only public functions - c.addGlobal(function.Name) + for _, location := range resolvedLocation { + importedProgram := c.Config.ImportHandler(location.Location) + + // Add a global variable for the imported contract value. + contractDecl := importedProgram.Contract + isContract := contractDecl != nil + if isContract { + c.addImportedGlobal(location.Location, contractDecl.Name) + } + + for _, function := range importedProgram.Functions { + name := function.Name + + // Skip the contract initializer. + // It should never be able to invoked within the code. + if isContract && name == commons.InitFunctionName { + continue + } + + // TODO: Filter-in only public functions + c.addImportedGlobal(location.Location, function.Name) + } } return diff --git a/runtime/bbq/compiler/config.go b/runtime/bbq/compiler/config.go index c9dae46707..4ee60350c4 100644 --- a/runtime/bbq/compiler/config.go +++ b/runtime/bbq/compiler/config.go @@ -19,12 +19,10 @@ package compiler import ( - "github.com/onflow/cadence/runtime/bbq" - "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/bbq/commons" ) type Config struct { - ImportHandler ImportHandler + ImportHandler commons.ImportHandler + LocationHandler commons.LocationHandler } - -type ImportHandler func(location common.Location) *bbq.Program diff --git a/runtime/bbq/compiler/global.go b/runtime/bbq/compiler/global.go index 1fd2591ecc..e7d78aa56f 100644 --- a/runtime/bbq/compiler/global.go +++ b/runtime/bbq/compiler/global.go @@ -18,6 +18,10 @@ package compiler +import "github.com/onflow/cadence/runtime/common" + type global struct { - index uint16 + name string + location common.Location + index uint16 } diff --git a/runtime/bbq/import.go b/runtime/bbq/import.go new file mode 100644 index 0000000000..e138241b2d --- /dev/null +++ b/runtime/bbq/import.go @@ -0,0 +1,26 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package bbq + +import "github.com/onflow/cadence/runtime/common" + +type Import struct { + Location common.Location + Name string +} diff --git a/runtime/bbq/program.go b/runtime/bbq/program.go index 6e00688d19..80efbdc16f 100644 --- a/runtime/bbq/program.go +++ b/runtime/bbq/program.go @@ -18,11 +18,9 @@ package bbq -import "github.com/onflow/cadence/runtime/common" - type Program struct { Contract *Contract - Imports []common.Location + Imports []*Import Functions []*Function Constants []*Constant Types [][]byte diff --git a/runtime/bbq/vm/config.go b/runtime/bbq/vm/config.go index 1fd2836101..341b19f89d 100644 --- a/runtime/bbq/vm/config.go +++ b/runtime/bbq/vm/config.go @@ -1,17 +1,36 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package vm import ( "github.com/onflow/atree" - "github.com/onflow/cadence/runtime/bbq/compiler" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" + + "github.com/onflow/cadence/runtime/bbq/commons" ) type Config struct { Storage common.MemoryGauge - compiler.ImportHandler + commons.ImportHandler ContractValueHandler } diff --git a/runtime/bbq/vm/linker.go b/runtime/bbq/vm/linker.go new file mode 100644 index 0000000000..dffd49445e --- /dev/null +++ b/runtime/bbq/vm/linker.go @@ -0,0 +1,117 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/common" +) + +type LinkedGlobals struct { + // context shared by the globals in the program. + context *Context + + // globals defined in the program, indexed by name. + indexedGlobals map[string]Value +} + +// LinkGlobals performs the linking of global functions and variables for a given program. +func LinkGlobals( + program *bbq.Program, + conf *Config, + linkedGlobalsCache map[common.Location]LinkedGlobals, +) LinkedGlobals { + + var importedGlobals []Value + + for _, programImport := range program.Imports { + importLocation := programImport.Location + linkedGlobals, ok := linkedGlobalsCache[importLocation] + + if !ok { + importedProgram := conf.ImportHandler(importLocation) + + // Link and get all globals at the import location. + linkedGlobals = LinkGlobals(importedProgram, conf, linkedGlobalsCache) + + // If the imported program is a contract, + // load the contract value and populate the global variable. + if importedProgram.Contract != nil { + contract := importedProgram.Contract + location := common.NewAddressLocation( + conf.MemoryGauge, + common.MustBytesToAddress(contract.Address), + contract.Name, + ) + + // TODO: remove this check. This shouldn't be nil ideally. + if conf.ContractValueHandler != nil { + contractValue := conf.ContractValueHandler(conf, location) + // Update the globals - both the context and the mapping. + // Contract value is always at the zero-th index. + linkedGlobals.context.Globals[0] = contractValue + linkedGlobals.indexedGlobals[contract.Name] = contractValue + } + } + + linkedGlobalsCache[importLocation] = linkedGlobals + } + + importedGlobal := linkedGlobals.indexedGlobals[programImport.Name] + importedGlobals = append(importedGlobals, importedGlobal) + } + + ctx := NewContext(program, nil) + + globals := make([]Value, 0) + indexedGlobals := make(map[string]Value, 0) + + // If the current program is a contract, reserve a global variable for the contract value. + // The reserved position is always the zero-th index. + // This value will be populated either by the `init` method invocation of the contract, + // Or when this program is imported by another (loads the value from storage). + if program.Contract != nil { + globals = append(globals, nil) + } + + // Iterate through `program.Functions` to be deterministic. + // Order of globals must be same as index set at `Compiler.addGlobal()`. + // TODO: include non-function globals + for _, function := range program.Functions { + value := FunctionValue{ + Function: function, + Context: ctx, + } + + globals = append(globals, value) + indexedGlobals[function.Name] = value + } + + // Globals of the current program are added first. + // This is the same order as they are added in the compiler. + ctx.Globals = globals + ctx.Globals = append(ctx.Globals, importedGlobals...) + + // Return only the globals defined in the current program. + // Because the importer/caller doesn't need to know globals of nested imports. + return LinkedGlobals{ + context: ctx, + indexedGlobals: indexedGlobals, + } +} diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 9a08f95648..e4fb28d20b 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -21,17 +21,19 @@ package vm import ( "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/bbq/commons" "github.com/onflow/cadence/runtime/bbq/constantkind" "github.com/onflow/cadence/runtime/bbq/leb128" "github.com/onflow/cadence/runtime/bbq/opcode" - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/errors" - "github.com/onflow/cadence/runtime/interpreter" ) type VM struct { - functions map[string]FunctionValue + globals map[string]Value callFrame *callFrame stack []Value config *Config @@ -46,92 +48,16 @@ func NewVM(program *bbq.Program, conf *Config) *VM { conf.Storage = interpreter.NewInMemoryStorage(nil) } - globals := initializeGlobals(program, conf) - functions := indexFunctions(program.Functions, globals) - - return &VM{ - functions: functions, - config: conf, - } -} - -func initializeGlobals(program *bbq.Program, conf *Config) []Value { - // TODO: global variable lookup relies too much on the order. - // Figure out a better way. - - var importedGlobals []Value - for _, location := range program.Imports { - importedProgram := conf.ImportHandler(location) - - // TODO: cache globals for imported programs. - - importedProgramGlobals := initializeGlobals(importedProgram, conf) - - // Load contract value - if importedProgram.Contract != nil { - // If the imported program is a contract, - // load the contract value and populate the global variable. - contract := importedProgram.Contract - contractLocation := common.NewAddressLocation( - conf.MemoryGauge, - common.MustBytesToAddress(contract.Address), - contract.Name, - ) - - // TODO: remove this check. This shouldn't be nil ideally. - if conf.ContractValueHandler != nil { - // Contract value is always at the zero-th index. - importedProgramGlobals[0] = conf.ContractValueHandler(conf, contractLocation) - } - } - - importedGlobals = append(importedGlobals, importedProgramGlobals...) - } - - ctx := NewContext(program, nil) - - globals := make([]Value, 0) - - // If the current program is a contract, reserve a global variable for the contract value. - // The reserved position is always the zero-th index. - // This value will be populated either by the `init` method invocation of the contract, - // Or when this program is imported by another (loads the value from storage). - if program.Contract != nil { - globals = append(globals, nil) - } - - // Iterate through `program.Functions` to be deterministic. - // Order of globals must be same as index set at `Compiler.addGlobal()`. - // TODO: include non-function globals - for _, function := range program.Functions { - // TODO: - globals = append(globals, FunctionValue{ - Function: function, - Context: ctx, - }) - } + // linkedGlobalsCache is a local cache-alike that is being used to hold already linked imports. + linkedGlobalsCache := map[common.Location]LinkedGlobals{} - // Imported globals are added first. - // This is the same order as they are added in the compiler. - ctx.Globals = importedGlobals - ctx.Globals = append(ctx.Globals, globals...) + // Link global variables and functions. + linkedGlobals := LinkGlobals(program, conf, linkedGlobalsCache) - // Return only the globals defined in the current program. - // Because the importer/caller doesn't need to know globals of nested imports. - return globals -} - -func indexFunctions(functions []*bbq.Function, globals []Value) map[string]FunctionValue { - indexedFunctions := make(map[string]FunctionValue, len(functions)) - for _, globalValue := range globals { - function, isFunction := globalValue.(FunctionValue) - if !isFunction { - continue - } - indexedFunctions[function.Function.Name] = function + return &VM{ + globals: linkedGlobals.indexedGlobals, + config: conf, } - - return indexedFunctions } func (vm *VM) push(value Value) { @@ -189,16 +115,25 @@ func (vm *VM) popCallFrame() { } func (vm *VM) Invoke(name string, arguments ...Value) (Value, error) { - function, ok := vm.functions[name] + function, ok := vm.globals[name] if !ok { - return nil, errors.NewDefaultUserError("unknown function") + return nil, errors.NewDefaultUserError("unknown function '%s'", name) } - if len(arguments) != int(function.Function.ParameterCount) { - return nil, errors.NewDefaultUserError("wrong number of arguments") + functionValue, ok := function.(FunctionValue) + if !ok { + return nil, errors.NewDefaultUserError("not invocable: %s", name) } - vm.pushCallFrame(function.Context, function.Function, arguments) + if len(arguments) != int(functionValue.Function.ParameterCount) { + return nil, errors.NewDefaultUserError( + "wrong number of arguments: expected %d, found %d", + functionValue.Function.ParameterCount, + len(arguments), + ) + } + + vm.pushCallFrame(functionValue.Context, functionValue.Function, arguments) vm.run() @@ -210,7 +145,7 @@ func (vm *VM) Invoke(name string, arguments ...Value) (Value, error) { } func (vm *VM) InitializeContract(arguments ...Value) (*CompositeValue, error) { - value, err := vm.Invoke(InitFunctionName, arguments...) + value, err := vm.Invoke(commons.InitFunctionName, arguments...) if err != nil { return nil, err } @@ -223,25 +158,6 @@ func (vm *VM) InitializeContract(arguments ...Value) (*CompositeValue, error) { return contractValue, nil } -type vmOp func(*VM) - -var vmOps = [...]vmOp{ - opcode.ReturnValue: opReturnValue, - opcode.Jump: opJump, - opcode.JumpIfFalse: opJumpIfFalse, - opcode.IntAdd: opBinaryIntAdd, - opcode.IntSubtract: opBinaryIntSubtract, - opcode.IntLess: opBinaryIntLess, - opcode.IntGreater: opBinaryIntGreater, - opcode.True: opTrue, - opcode.False: opFalse, - opcode.GetConstant: opGetConstant, - opcode.GetLocal: opGetLocal, - opcode.SetLocal: opSetLocal, - opcode.GetGlobal: opGetGlobal, - opcode.InvokeStatic: opInvokeStatic, -} - func opReturnValue(vm *VM) { value := vm.pop() vm.popCallFrame() diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index f89a849870..766518059e 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -35,6 +35,7 @@ import ( "github.com/onflow/cadence/runtime/tests/utils" "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/bbq/commons" "github.com/onflow/cadence/runtime/bbq/compiler" ) @@ -238,8 +239,6 @@ func TestNewStruct(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - printProgram(program) - vm := NewVM(program, nil) result, err := vm.Invoke("test", IntValue{SmallInt: 10}) @@ -283,8 +282,6 @@ func TestStructMethodCall(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - printProgram(program) - vm := NewVM(program, nil) result, err := vm.Invoke("test") @@ -433,7 +430,6 @@ func TestImport(t *testing.T) { subComp := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := subComp.Compile() - printProgram(importedProgram) checker, err := ParseAndCheckWithOptions(t, ` import Foo from 0x01 @@ -461,7 +457,6 @@ func TestImport(t *testing.T) { } program := importCompiler.Compile() - printProgram(program) vmConfig := &Config{ ImportHandler: func(location common.Location) *bbq.Program { @@ -481,8 +476,10 @@ func TestContractImport(t *testing.T) { t.Parallel() - importedChecker, err := ParseAndCheckWithOptions(t, - ` + t.Run("nested type def", func(t *testing.T) { + + importedChecker, err := ParseAndCheckWithOptions(t, + ` contract MyContract { fun helloText(): String { @@ -505,17 +502,20 @@ func TestContractImport(t *testing.T) { } } `, - ParseAndCheckOptions{ - Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), - }, - ) - require.NoError(t, err) + ParseAndCheckOptions{ + Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), + }, + ) + require.NoError(t, err) - importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) - importedProgram := importCompiler.Compile() - printProgram(importedProgram) + importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importedProgram := importCompiler.Compile() - checker, err := ParseAndCheckWithOptions(t, ` + vm := NewVM(importedProgram, nil) + importedContractValue, err := vm.InitializeContract() + require.NoError(t, err) + + checker, err := ParseAndCheckWithOptions(t, ` import MyContract from 0x01 fun test(): String { @@ -523,47 +523,283 @@ func TestContractImport(t *testing.T) { return r.sayHello(1) } `, - ParseAndCheckOptions{ - Config: &sema.Config{ - ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { - return sema.ElaborationImport{ - Elaboration: importedChecker.Elaboration, - }, nil + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, }, }, - }, - ) - require.NoError(t, err) + ) + require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { - return importedProgram - } + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + return importedProgram + } - program := comp.Compile() - printProgram(program) + program := comp.Compile() - vmConfig := &Config{ - ImportHandler: func(location common.Location) *bbq.Program { + vmConfig := &Config{ + ImportHandler: func(location common.Location) *bbq.Program { + return importedProgram + }, + ContractValueHandler: func(*Config, common.Location) *CompositeValue { + return importedContractValue + }, + } + + vm = NewVM(program, vmConfig) + + result, err := vm.Invoke("test") + require.NoError(t, err) + require.Equal(t, StringValue{String: []byte("global function of the imported program")}, result) + }) + + t.Run("contract function", func(t *testing.T) { + + importedChecker, err := ParseAndCheckWithOptions(t, + ` + contract MyContract { + + var s: String + + fun helloText(): String { + return self.s + } + + init() { + self.s = "contract function of the imported program" + } + } + `, + ParseAndCheckOptions{ + Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), + }, + ) + require.NoError(t, err) + + importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importedProgram := importCompiler.Compile() + + vm := NewVM(importedProgram, nil) + importedContractValue, err := vm.InitializeContract() + require.NoError(t, err) + + checker, err := ParseAndCheckWithOptions(t, ` + import MyContract from 0x01 + + fun test(): String { + return MyContract.helloText() + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { return importedProgram - }, - ContractValueHandler: func(conf *Config, location common.Location) *CompositeValue { - addressLocation := location.(common.AddressLocation) - return NewCompositeValue( - location, - addressLocation.Name, - common.CompositeKindContract, - addressLocation.Address, - conf.Storage, - ) - }, - } + } - vm := NewVM(program, vmConfig) + program := comp.Compile() - result, err := vm.Invoke("test") - require.NoError(t, err) - require.Equal(t, StringValue{String: []byte("global function of the imported program")}, result) + vmConfig := &Config{ + ImportHandler: func(location common.Location) *bbq.Program { + return importedProgram + }, + ContractValueHandler: func(*Config, common.Location) *CompositeValue { + return importedContractValue + }, + } + + vm = NewVM(program, vmConfig) + + result, err := vm.Invoke("test") + require.NoError(t, err) + require.Equal(t, StringValue{String: []byte("contract function of the imported program")}, result) + }) + + t.Run("nested imports", func(t *testing.T) { + + // Initialize Foo + + fooLocation := common.NewAddressLocation( + nil, + common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + "Foo", + ) + + fooChecker, err := ParseAndCheckWithOptions(t, + ` + contract Foo { + var s: String + init() { + self.s = "Hello from Foo!" + } + fun sayHello(): String { + return self.s + } + }`, + ParseAndCheckOptions{ + Location: fooLocation, + }, + ) + require.NoError(t, err) + + fooCompiler := compiler.NewCompiler(fooChecker.Program, fooChecker.Elaboration) + fooProgram := fooCompiler.Compile() + + vm := NewVM(fooProgram, nil) + fooContractValue, err := vm.InitializeContract() + require.NoError(t, err) + + // Initialize Bar + + barLocation := common.NewAddressLocation( + nil, + common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2}, + "Bar", + ) + + barChecker, err := ParseAndCheckWithOptions(t, ` + import Foo from 0x01 + + contract Bar { + init() {} + fun sayHello(): String { + return Foo.sayHello() + } + }`, + ParseAndCheckOptions{ + Location: barLocation, + Config: &sema.Config{ + ImportHandler: func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { + require.Equal(t, fooLocation, location) + return sema.ElaborationImport{ + Elaboration: fooChecker.Elaboration, + }, nil + }, + LocationHandler: singleIdentifierLocationResolver(t), + }, + }, + ) + require.NoError(t, err) + + barCompiler := compiler.NewCompiler(barChecker.Program, barChecker.Elaboration) + barCompiler.Config.LocationHandler = singleIdentifierLocationResolver(t) + barCompiler.Config.ImportHandler = func(location common.Location) *bbq.Program { + require.Equal(t, fooLocation, location) + return fooProgram + } + + barProgram := barCompiler.Compile() + + vmConfig := &Config{ + ImportHandler: func(location common.Location) *bbq.Program { + require.Equal(t, fooLocation, location) + return fooProgram + }, + ContractValueHandler: func(_ *Config, location common.Location) *CompositeValue { + require.Equal(t, fooLocation, location) + return fooContractValue + }, + } + + vm = NewVM(barProgram, vmConfig) + barContractValue, err := vm.InitializeContract() + require.NoError(t, err) + + // Compile and run main program + + checker, err := ParseAndCheckWithOptions(t, ` + import Bar from 0x02 + + fun test(): String { + return Bar.sayHello() + }`, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { + require.IsType(t, common.AddressLocation{}, location) + addressLocation := location.(common.AddressLocation) + var elaboration *sema.Elaboration + switch addressLocation.Address { + case fooLocation.Address: + elaboration = fooChecker.Elaboration + case barLocation.Address: + elaboration = barChecker.Elaboration + default: + assert.FailNow(t, "invalid location") + } + + return sema.ElaborationImport{ + Elaboration: elaboration, + }, nil + }, + LocationHandler: singleIdentifierLocationResolver(t), + }, + }, + ) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.LocationHandler = singleIdentifierLocationResolver(t) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + switch location { + case fooLocation: + return fooProgram + case barLocation: + return barProgram + default: + assert.FailNow(t, "invalid location") + return nil + } + } + + program := comp.Compile() + + vmConfig = &Config{ + ImportHandler: func(location common.Location) *bbq.Program { + switch location { + case fooLocation: + return fooProgram + case barLocation: + return barProgram + default: + assert.FailNow(t, "invalid location") + return nil + } + }, + ContractValueHandler: func(_ *Config, location common.Location) *CompositeValue { + switch location { + case fooLocation: + return fooContractValue + case barLocation: + return barContractValue + default: + assert.FailNow(t, "invalid location") + return nil + } + }, + } + + vm = NewVM(program, vmConfig) + + result, err := vm.Invoke("test") + require.NoError(t, err) + require.Equal(t, StringValue{String: []byte("Hello from Foo!")}, result) + }) } func BenchmarkContractImport(b *testing.B) { @@ -571,11 +807,15 @@ func BenchmarkContractImport(b *testing.B) { importedChecker, err := ParseAndCheckWithOptions(b, ` contract MyContract { + var s: String + fun helloText(): String { - return "global function of the imported program" + return self.s } - init() {} + init() { + self.s = "contract function of the imported program" + } struct Foo { var id : String @@ -600,19 +840,16 @@ func BenchmarkContractImport(b *testing.B) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() + vm := NewVM(importedProgram, nil) + importedContractValue, err := vm.InitializeContract() + require.NoError(b, err) + vmConfig := &Config{ ImportHandler: func(location common.Location) *bbq.Program { return importedProgram }, ContractValueHandler: func(conf *Config, location common.Location) *CompositeValue { - addressLocation := location.(common.AddressLocation) - return NewCompositeValue( - location, - addressLocation.Name, - common.CompositeKindContract, - addressLocation.Address, - conf.Storage, - ) + return importedContractValue }, } @@ -715,7 +952,6 @@ func TestContractAccessDuringInit(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - printProgram(program) vm := NewVM(program, nil) contractValue, err := vm.InitializeContract() @@ -748,7 +984,6 @@ func TestContractAccessDuringInit(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - printProgram(program) vm := NewVM(program, nil) contractValue, err := vm.InitializeContract() @@ -870,7 +1105,6 @@ func TestContractField(t *testing.T) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() - printProgram(importedProgram) vm := NewVM(importedProgram, nil) importedContractValue, err := vm.InitializeContract() @@ -901,7 +1135,6 @@ func TestContractField(t *testing.T) { } program := comp.Compile() - printProgram(program) vmConfig := &Config{ ImportHandler: func(location common.Location) *bbq.Program { @@ -938,7 +1171,6 @@ func TestContractField(t *testing.T) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() - printProgram(importedProgram) vm := NewVM(importedProgram, nil) importedContractValue, err := vm.InitializeContract() @@ -970,7 +1202,6 @@ func TestContractField(t *testing.T) { } program := comp.Compile() - printProgram(program) vmConfig := &Config{ ImportHandler: func(location common.Location) *bbq.Program { @@ -991,3 +1222,46 @@ func TestContractField(t *testing.T) { assert.Equal(t, StringValue{String: []byte("UPDATED")}, fieldValue) }) } + +func TestEvaluationOrder(t *testing.T) { + f := Foo{"pending"} + f.GetFoo().printArgument(getArg()) +} + +type Foo struct { + id string +} + +func (f Foo) GetFoo() Foo { + fmt.Println("evaluating receiver") + return f +} + +func (f Foo) printArgument(s string) { + fmt.Println(s) +} + +func getArg() string { + fmt.Println("evaluating argument") + return "argument" +} + +func singleIdentifierLocationResolver(t testing.TB) func( + identifiers []ast.Identifier, + location common.Location, +) ([]commons.ResolvedLocation, error) { + return func(identifiers []ast.Identifier, location common.Location) ([]commons.ResolvedLocation, error) { + require.Len(t, identifiers, 1) + require.IsType(t, common.AddressLocation{}, location) + + return []commons.ResolvedLocation{ + { + Location: common.AddressLocation{ + Address: location.(common.AddressLocation).Address, + Name: identifiers[0].Identifier, + }, + Identifiers: identifiers, + }, + }, nil + } +} From aa19efc74465d34d867bf76751420df3b842e0c0 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Mon, 13 Mar 2023 10:01:31 -0700 Subject: [PATCH 16/89] Unify instance-method invocation and static-method invocation --- runtime/bbq/compiler/compiler.go | 38 +++++++++++++++------------ runtime/bbq/compiler/compiler_test.go | 4 +-- runtime/bbq/opcode/opcode.go | 1 - runtime/bbq/opcode/opcode_string.go | 17 ++++++------ runtime/bbq/vm/vm.go | 16 +---------- runtime/bbq/vm/vm_test.go | 23 ---------------- 6 files changed, 32 insertions(+), 67 deletions(-) diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index d56673b7cb..f2102f6145 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -579,7 +579,7 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio c.loadArguments(expression) // Load function value c.emitVariableLoad(invokedExpr.Identifier.Identifier) - c.emit(opcode.InvokeStatic) + c.emit(opcode.Invoke) case *ast.MemberExpression: memberInfo := c.Elaboration.MemberExpressionMemberInfos[invokedExpr] typeName := memberInfo.AccessedType.QualifiedString() @@ -592,7 +592,7 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio c.loadArguments(expression) // Load function value c.emitVariableLoad(funcName) - c.emit(opcode.InvokeStatic) + c.emit(opcode.Invoke) } else { // Receiver is loaded first. So 'self' is always the zero-th argument. // This must be in sync with `compileCompositeFunction`. @@ -745,7 +745,7 @@ func (c *Compiler) compileInitializer(declaration *ast.SpecialFunctionDeclaratio } function := c.addFunction(functionName, uint16(parameterCount)) - declareParameters(function, parameters) + c.declareParameters(function, parameters, false) // Declare `self` self := c.currentFunction.declareLocal(sema.SelfIdentifier) @@ -814,22 +814,28 @@ func (c *Compiler) compileInitializer(declaration *ast.SpecialFunctionDeclaratio func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration) (_ struct{}) { // TODO: handle nested functions - function := c.declareFunction(declaration) - declareParameters(function, declaration.ParameterList.Parameters) + declareReceiver := !c.compositeTypeStack.isEmpty() + function := c.declareFunction(declaration, declareReceiver) + + c.declareParameters(function, declaration.ParameterList.Parameters, declareReceiver) c.compileFunctionBlock(declaration.FunctionBlock) return } -func (c *Compiler) declareFunction(declaration *ast.FunctionDeclaration) *function { +func (c *Compiler) declareFunction(declaration *ast.FunctionDeclaration, declareReceiver bool) *function { enclosingCompositeTypeName := c.enclosingCompositeTypeFullyQualifiedName() functionName := typeQualifiedName(enclosingCompositeTypeName, declaration.Identifier.Identifier) functionType := c.Elaboration.FunctionDeclarationFunctionTypes[declaration] parameterCount := len(functionType.Parameters) - if parameterCount > math.MaxUint16 { + if parameterCount >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid parameter count")) } + if declareReceiver { + parameterCount++ + } + return c.addFunction(functionName, uint16(parameterCount)) } @@ -846,7 +852,7 @@ func (c *Compiler) VisitCompositeDeclaration(declaration *ast.CompositeDeclarati } for _, function := range declaration.Members.Functions() { - c.compileCompositeFunction(function) + c.compileDeclaration(function) } for _, nestedTypes := range declaration.Members.Composites() { @@ -858,14 +864,6 @@ func (c *Compiler) VisitCompositeDeclaration(declaration *ast.CompositeDeclarati return } -func (c *Compiler) compileCompositeFunction(declaration *ast.FunctionDeclaration) { - function := c.declareFunction(declaration) - // Declare `self`. Receiver is always at the zero-th index of params. - function.declareLocal(sema.SelfIdentifier) - declareParameters(function, declaration.ParameterList.Parameters) - c.compileFunctionBlock(declaration.FunctionBlock) -} - func (c *Compiler) VisitInterfaceDeclaration(_ *ast.InterfaceDeclaration) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) @@ -985,7 +983,13 @@ func typeQualifiedName(typeName, functionName string) string { return typeName + "." + functionName } -func declareParameters(function *function, parameters []*ast.Parameter) { +func (c *Compiler) declareParameters(function *function, parameters []*ast.Parameter, declareReceiver bool) { + if declareReceiver { + // Declare receiver as `self`. + // Receiver is always at the zero-th index of params. + function.declareLocal(sema.SelfIdentifier) + } + for _, parameter := range parameters { parameterName := parameter.Identifier.Identifier function.declareLocal(parameterName) diff --git a/runtime/bbq/compiler/compiler_test.go b/runtime/bbq/compiler/compiler_test.go index d5f8ec2415..726db2aec9 100644 --- a/runtime/bbq/compiler/compiler_test.go +++ b/runtime/bbq/compiler/compiler_test.go @@ -62,13 +62,13 @@ func TestCompileRecursionFib(t *testing.T) { byte(opcode.GetConstant), 0, 1, byte(opcode.IntSubtract), byte(opcode.GetGlobal), 0, 0, - byte(opcode.InvokeStatic), + byte(opcode.Invoke), // fib(n - 2) byte(opcode.GetLocal), 0, 0, byte(opcode.GetConstant), 0, 2, byte(opcode.IntSubtract), byte(opcode.GetGlobal), 0, 0, - byte(opcode.InvokeStatic), + byte(opcode.Invoke), // return sum byte(opcode.IntAdd), byte(opcode.ReturnValue), diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index c649a76eaf..a879159c4f 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -54,7 +54,6 @@ const ( SetField Invoke - InvokeStatic InvokeNative New diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index 3cb2d246f0..bdaa2a2d51 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -34,18 +34,17 @@ func _() { _ = x[GetField-23] _ = x[SetField-24] _ = x[Invoke-25] - _ = x[InvokeStatic-26] - _ = x[InvokeNative-27] - _ = x[New-28] - _ = x[Destroy-29] - _ = x[CheckType-30] - _ = x[Drop-31] - _ = x[Dup-32] + _ = x[InvokeNative-26] + _ = x[New-27] + _ = x[Destroy-28] + _ = x[CheckType-29] + _ = x[Drop-30] + _ = x[Dup-31] } -const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeInvokeStaticInvokeNativeNewDestroyCheckTypeDropDup" +const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeInvokeNativeNewDestroyCheckTypeDropDup" -var _Opcode_index = [...]uint16{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 203, 211, 219, 225, 237, 249, 252, 259, 268, 272, 275} +var _Opcode_index = [...]uint16{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 203, 211, 219, 225, 237, 240, 247, 256, 260, 263} func (i Opcode) String() string { if i >= Opcode(len(_Opcode_index)-1) { diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index e4fb28d20b..5ca10f79d2 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -250,7 +250,7 @@ func opSetGlobal(vm *VM) { callFrame.context.Globals[index] = vm.pop() } -func opInvokeStatic(vm *VM) { +func opInvoke(vm *VM) { value := vm.pop().(FunctionValue) stackHeight := len(vm.stack) parameterCount := int(value.Function.ParameterCount) @@ -260,18 +260,6 @@ func opInvokeStatic(vm *VM) { vm.dropN(parameterCount) } -func opInvoke(vm *VM) { - value := vm.pop().(FunctionValue) - stackHeight := len(vm.stack) - - // Add one to account for `self` - parameterCount := int(value.Function.ParameterCount) + 1 - - arguments := vm.stack[stackHeight-parameterCount:] - vm.pushCallFrame(value.Context, value.Function, arguments) - vm.dropN(parameterCount) -} - func opDrop(vm *VM) { _ = vm.pop() } @@ -400,8 +388,6 @@ func (vm *VM) run() { opGetGlobal(vm) case opcode.SetGlobal: opSetGlobal(vm) - case opcode.InvokeStatic: - opInvokeStatic(vm) case opcode.Invoke: opInvoke(vm) case opcode.Drop: diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index 766518059e..dc6ca57ac3 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -1223,29 +1223,6 @@ func TestContractField(t *testing.T) { }) } -func TestEvaluationOrder(t *testing.T) { - f := Foo{"pending"} - f.GetFoo().printArgument(getArg()) -} - -type Foo struct { - id string -} - -func (f Foo) GetFoo() Foo { - fmt.Println("evaluating receiver") - return f -} - -func (f Foo) printArgument(s string) { - fmt.Println(s) -} - -func getArg() string { - fmt.Println("evaluating argument") - return "argument" -} - func singleIdentifierLocationResolver(t testing.TB) func( identifiers []ast.Identifier, location common.Location, From 3455f0811f65948bba0cbdbc8db1a3a3e4e89dd1 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Mon, 13 Mar 2023 19:41:15 -0700 Subject: [PATCH 17/89] Add native function invocation support --- runtime/bbq/bytecode_printer.go | 9 +- runtime/bbq/commons/constants.go | 1 + runtime/bbq/commons/utils.go | 27 ++++++ runtime/bbq/compiler/compiler.go | 22 ++--- runtime/bbq/compiler/native_functions.go | 64 +++++++++++++ runtime/bbq/opcode/opcode.go | 3 +- runtime/bbq/opcode/opcode_string.go | 15 ++- runtime/bbq/vm/errors.go | 35 +++++++ runtime/bbq/vm/linker.go | 10 +- runtime/bbq/vm/native_functions.go | 56 +++++++++++ runtime/bbq/vm/value.go | 37 ++++++++ runtime/bbq/vm/value_bool.go | 48 ++++++++++ ...values_composite.go => value_composite.go} | 5 + ...es_conversions.go => value_conversions.go} | 4 +- runtime/bbq/vm/value_function.go | 71 ++++++++++++++ .../bbq/vm/{values_simple.go => value_int.go} | 69 ++------------ runtime/bbq/vm/value_string.go | 73 +++++++++++++++ runtime/bbq/vm/value_utils.go | 35 +++++++ runtime/bbq/vm/value_void.go | 45 +++++++++ runtime/bbq/vm/vm.go | 40 +++++--- runtime/bbq/vm/vm_test.go | 93 ++++++++++++++++--- 21 files changed, 645 insertions(+), 117 deletions(-) create mode 100644 runtime/bbq/commons/utils.go create mode 100644 runtime/bbq/compiler/native_functions.go create mode 100644 runtime/bbq/vm/errors.go create mode 100644 runtime/bbq/vm/native_functions.go create mode 100644 runtime/bbq/vm/value.go create mode 100644 runtime/bbq/vm/value_bool.go rename runtime/bbq/vm/{values_composite.go => value_composite.go} (99%) rename runtime/bbq/vm/{values_conversions.go => value_conversions.go} (94%) create mode 100644 runtime/bbq/vm/value_function.go rename runtime/bbq/vm/{values_simple.go => value_int.go} (54%) create mode 100644 runtime/bbq/vm/value_string.go create mode 100644 runtime/bbq/vm/value_utils.go create mode 100644 runtime/bbq/vm/value_void.go diff --git a/runtime/bbq/bytecode_printer.go b/runtime/bbq/bytecode_printer.go index e3e0f8cbb4..31712c80e1 100644 --- a/runtime/bbq/bytecode_printer.go +++ b/runtime/bbq/bytecode_printer.go @@ -67,7 +67,7 @@ func (p *BytecodePrinter) printCode(codes []byte) { opcode.SetGlobal, opcode.Jump, opcode.JumpIfFalse, - opcode.CheckType: + opcode.Transfer: var operand int operand, i = p.getIntOperand(codes, i) p.stringBuilder.WriteString(" " + fmt.Sprint(operand)) @@ -152,8 +152,11 @@ func (p *BytecodePrinter) getLocation(codes []byte, i int) (location common.Loca func (p *BytecodePrinter) printImports(imports []*Import) { p.stringBuilder.WriteString("-- Imports --\n") for _, impt := range imports { - p.stringBuilder.WriteString(impt.Location.String()) - p.stringBuilder.WriteRune('.') + location := impt.Location + if location != nil { + p.stringBuilder.WriteString(location.String()) + p.stringBuilder.WriteRune('.') + } p.stringBuilder.WriteString(impt.Name) p.stringBuilder.WriteRune('\n') } diff --git a/runtime/bbq/commons/constants.go b/runtime/bbq/commons/constants.go index 72b3e1bd75..22dfe1ab80 100644 --- a/runtime/bbq/commons/constants.go +++ b/runtime/bbq/commons/constants.go @@ -20,4 +20,5 @@ package commons const ( InitFunctionName = "init" + LogFunctionName = "log" ) diff --git a/runtime/bbq/commons/utils.go b/runtime/bbq/commons/utils.go new file mode 100644 index 0000000000..52ae19b2f8 --- /dev/null +++ b/runtime/bbq/commons/utils.go @@ -0,0 +1,27 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package commons + +func TypeQualifiedName(typeName, functionName string) string { + if typeName == "" { + return functionName + } + + return typeName + "." + functionName +} diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index f2102f6145..c66a9cee3c 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -72,7 +72,8 @@ func NewCompiler( Elaboration: elaboration, Config: &Config{}, globals: map[string]*global{}, - indexedImportedGlobals: map[string]*global{}, + indexedImportedGlobals: indexedNativeFunctions, + importedGlobals: nativeFunctions, exportedImports: make([]*bbq.Import, 0), typesInPool: map[sema.Type]uint16{}, compositeTypeStack: &Stack[*sema.CompositeType]{ @@ -260,13 +261,13 @@ func (c *Compiler) reserveGlobalVars( compositeDecls []*ast.CompositeDeclaration, ) { for _, declaration := range funcDecls { - funcName := typeQualifiedName(compositeTypeName, declaration.Identifier.Identifier) + funcName := commons.TypeQualifiedName(compositeTypeName, declaration.Identifier.Identifier) c.addGlobal(funcName) } for _, declaration := range compositeDecls { // TODO: Handle nested composite types. Those name should be `Foo.Bar`. - qualifiedTypeName := typeQualifiedName(compositeTypeName, declaration.Identifier.Identifier) + qualifiedTypeName := commons.TypeQualifiedName(compositeTypeName, declaration.Identifier.Identifier) c.addGlobal(qualifiedTypeName) @@ -583,7 +584,7 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio case *ast.MemberExpression: memberInfo := c.Elaboration.MemberExpressionMemberInfos[invokedExpr] typeName := memberInfo.AccessedType.QualifiedString() - funcName := typeQualifiedName(typeName, invokedExpr.Identifier.Identifier) + funcName := commons.TypeQualifiedName(typeName, invokedExpr.Identifier.Identifier) invocationType := memberInfo.Member.TypeAnnotation.Type.(*sema.FunctionType) if invocationType.IsConstructor { @@ -595,7 +596,6 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio c.emit(opcode.Invoke) } else { // Receiver is loaded first. So 'self' is always the zero-th argument. - // This must be in sync with `compileCompositeFunction`. c.compileExpression(invokedExpr.Expression) // Load arguments c.loadArguments(expression) @@ -824,7 +824,7 @@ func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration func (c *Compiler) declareFunction(declaration *ast.FunctionDeclaration, declareReceiver bool) *function { enclosingCompositeTypeName := c.enclosingCompositeTypeFullyQualifiedName() - functionName := typeQualifiedName(enclosingCompositeTypeName, declaration.Identifier.Identifier) + functionName := commons.TypeQualifiedName(enclosingCompositeTypeName, declaration.Identifier.Identifier) functionType := c.Elaboration.FunctionDeclarationFunctionTypes[declaration] parameterCount := len(functionType.Parameters) @@ -946,7 +946,7 @@ func (c *Compiler) emitCheckType(targetType sema.Type) { } first, second := encodeUint16(index) - c.emit(opcode.CheckType, first, second) + c.emit(opcode.Transfer, first, second) } func (c *Compiler) addType(data []byte) uint16 { @@ -975,14 +975,6 @@ func (c *Compiler) enclosingCompositeTypeFullyQualifiedName() string { return sb.String() } -func typeQualifiedName(typeName, functionName string) string { - if typeName == "" { - return functionName - } - - return typeName + "." + functionName -} - func (c *Compiler) declareParameters(function *function, parameters []*ast.Parameter, declareReceiver bool) { if declareReceiver { // Declare receiver as `self`. diff --git a/runtime/bbq/compiler/native_functions.go b/runtime/bbq/compiler/native_functions.go new file mode 100644 index 0000000000..aa8e8290d1 --- /dev/null +++ b/runtime/bbq/compiler/native_functions.go @@ -0,0 +1,64 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package compiler + +import ( + "github.com/onflow/cadence/runtime/sema" + + "github.com/onflow/cadence/runtime/bbq/commons" +) + +var indexedNativeFunctions map[string]*global +var nativeFunctions []*global + +var builtinTypes = []sema.Type{ + sema.StringType, +} + +// TODO: Maybe +var stdlibFunctions = []string{ + commons.LogFunctionName, +} + +func init() { + indexedNativeFunctions = make(map[string]*global) + + // Here the order isn't really important. + // Because the native functions used by a program are also + // added to the imports section of the compiled program. + // Then the VM will link the imports (native functions) by the name. + for _, builtinType := range builtinTypes { + for name, _ := range builtinType.GetMembers() { + funcName := commons.TypeQualifiedName(builtinType.QualifiedString(), name) + addNativeFunction(funcName) + } + } + + for _, funcName := range stdlibFunctions { + addNativeFunction(funcName) + } +} + +func addNativeFunction(name string) { + global := &global{ + name: name, + } + nativeFunctions = append(nativeFunctions, global) + indexedNativeFunctions[name] = global +} diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index a879159c4f..972f5ce34d 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -54,11 +54,10 @@ const ( SetField Invoke - InvokeNative New Destroy - CheckType + Transfer Drop Dup diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index bdaa2a2d51..e540632d04 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -34,17 +34,16 @@ func _() { _ = x[GetField-23] _ = x[SetField-24] _ = x[Invoke-25] - _ = x[InvokeNative-26] - _ = x[New-27] - _ = x[Destroy-28] - _ = x[CheckType-29] - _ = x[Drop-30] - _ = x[Dup-31] + _ = x[New-26] + _ = x[Destroy-27] + _ = x[Transfer-28] + _ = x[Drop-29] + _ = x[Dup-30] } -const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeInvokeNativeNewDestroyCheckTypeDropDup" +const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeNewDestroyTransferDropDup" -var _Opcode_index = [...]uint16{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 203, 211, 219, 225, 237, 240, 247, 256, 260, 263} +var _Opcode_index = [...]uint8{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 203, 211, 219, 225, 228, 235, 243, 247, 250} func (i Opcode) String() string { if i >= Opcode(len(_Opcode_index)-1) { diff --git a/runtime/bbq/vm/errors.go b/runtime/bbq/vm/errors.go new file mode 100644 index 0000000000..617d9b400e --- /dev/null +++ b/runtime/bbq/vm/errors.go @@ -0,0 +1,35 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import "github.com/onflow/cadence/runtime/errors" + +type LinkerError struct { + Message string +} + +var _ error = LinkerError{} +var _ errors.InternalError = LinkerError{} + +func (l LinkerError) IsInternalError() { +} + +func (l LinkerError) Error() string { + return l.Message +} diff --git a/runtime/bbq/vm/linker.go b/runtime/bbq/vm/linker.go index dffd49445e..6b5edef52e 100644 --- a/runtime/bbq/vm/linker.go +++ b/runtime/bbq/vm/linker.go @@ -19,7 +19,10 @@ package vm import ( + "fmt" + "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/common" ) @@ -73,7 +76,12 @@ func LinkGlobals( linkedGlobalsCache[importLocation] = linkedGlobals } - importedGlobal := linkedGlobals.indexedGlobals[programImport.Name] + importedGlobal, ok := linkedGlobals.indexedGlobals[programImport.Name] + if !ok { + panic(LinkerError{ + Message: fmt.Sprintf("cannot find import '%s'", programImport.Name), + }) + } importedGlobals = append(importedGlobals, importedGlobal) } diff --git a/runtime/bbq/vm/native_functions.go b/runtime/bbq/vm/native_functions.go new file mode 100644 index 0000000000..e784fcee4d --- /dev/null +++ b/runtime/bbq/vm/native_functions.go @@ -0,0 +1,56 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "fmt" + + "github.com/onflow/cadence/runtime/bbq/commons" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/stdlib" +) + +var NativeFunctions = map[string]Value{} + +// BuiltInLocation is the location of built-in constructs. +// It's always nil. +var BuiltInLocation common.Location = nil + +func RegisterFunction(functionName string, functionValue NativeFunctionValue) { + NativeFunctions[functionName] = functionValue +} + +func RegisterTypeBoundFunction(typeName, functionName string, functionValue NativeFunctionValue) { + // +1 is for the receiver + functionValue.ParameterCount++ + qualifiedName := commons.TypeQualifiedName(typeName, functionName) + RegisterFunction(qualifiedName, functionValue) +} + +func init() { + RegisterFunction(commons.LogFunctionName, NativeFunctionValue{ + ParameterCount: len(stdlib.LogFunctionType.Parameters), + Function: func(arguments ...Value) Value { + // TODO: Properly implement + fmt.Println(arguments[0].String()) + return VoidValue{} + }, + }) +} diff --git a/runtime/bbq/vm/value.go b/runtime/bbq/vm/value.go new file mode 100644 index 0000000000..786a9dc477 --- /dev/null +++ b/runtime/bbq/vm/value.go @@ -0,0 +1,37 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/atree" + + "github.com/onflow/cadence/runtime/common" +) + +type Value interface { + isValue() + StaticType(common.MemoryGauge) StaticType + Transfer( + config *Config, + address atree.Address, + remove bool, + storable atree.Storable, + ) Value + String() string +} diff --git a/runtime/bbq/vm/value_bool.go b/runtime/bbq/vm/value_bool.go new file mode 100644 index 0000000000..761e6160db --- /dev/null +++ b/runtime/bbq/vm/value_bool.go @@ -0,0 +1,48 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/atree" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/format" + "github.com/onflow/cadence/runtime/interpreter" +) + +var TrueValue Value = BoolValue(true) +var FalseValue Value = BoolValue(false) + +type BoolValue bool + +var _ Value = BoolValue(true) + +func (BoolValue) isValue() {} + +func (BoolValue) StaticType(common.MemoryGauge) StaticType { + return interpreter.PrimitiveStaticTypeBool +} + +func (v BoolValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { + return v +} + +func (v BoolValue) String() string { + return format.Bool(bool(v)) +} diff --git a/runtime/bbq/vm/values_composite.go b/runtime/bbq/vm/value_composite.go similarity index 99% rename from runtime/bbq/vm/values_composite.go rename to runtime/bbq/vm/value_composite.go index af7ae101f5..2d824ffc40 100644 --- a/runtime/bbq/vm/values_composite.go +++ b/runtime/bbq/vm/value_composite.go @@ -161,6 +161,11 @@ func (v *CompositeValue) IsResourceKinded() bool { return v.Kind == common.CompositeKindResource } +func (v *CompositeValue) String() string { + //TODO implement me + panic("implement me") +} + func (v *CompositeValue) Transfer( conf *Config, address atree.Address, diff --git a/runtime/bbq/vm/values_conversions.go b/runtime/bbq/vm/value_conversions.go similarity index 94% rename from runtime/bbq/vm/values_conversions.go rename to runtime/bbq/vm/value_conversions.go index ff74d86195..6e842c4aca 100644 --- a/runtime/bbq/vm/values_conversions.go +++ b/runtime/bbq/vm/value_conversions.go @@ -33,7 +33,7 @@ func InterpreterValueToVMValue(config *Config, value interpreter.Value) Value { case interpreter.IntValue: return IntValue{value.BigInt.Int64()} case *interpreter.StringValue: - return StringValue{String: []byte(value.Str)} + return StringValue{Str: []byte(value.Str)} case *interpreter.CompositeValue: return NewCompositeValue( value.Location, @@ -68,7 +68,7 @@ func VMValueToInterpreterValue(value Value) interpreter.Value { case IntValue: return interpreter.NewIntValueFromInt64(nil, value.SmallInt) case StringValue: - return interpreter.NewUnmeteredStringValue(string(value.String)) + return interpreter.NewUnmeteredStringValue(string(value.Str)) case *CompositeValue: return interpreter.NewCompositeValue( inter, diff --git a/runtime/bbq/vm/value_function.go b/runtime/bbq/vm/value_function.go new file mode 100644 index 0000000000..26052ba9ae --- /dev/null +++ b/runtime/bbq/vm/value_function.go @@ -0,0 +1,71 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/atree" + + "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" +) + +type FunctionValue struct { + Function *bbq.Function + Context *Context +} + +var _ Value = FunctionValue{} + +func (FunctionValue) isValue() {} + +func (FunctionValue) StaticType(common.MemoryGauge) StaticType { + panic(errors.NewUnreachableError()) +} + +func (v FunctionValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { + return v +} + +func (v FunctionValue) String() string { + //TODO implement me + panic("implement me") +} + +type NativeFunctionValue struct { + ParameterCount int + Function func(arguments ...Value) Value +} + +var _ Value = NativeFunctionValue{} + +func (NativeFunctionValue) isValue() {} + +func (NativeFunctionValue) StaticType(common.MemoryGauge) StaticType { + panic(errors.NewUnreachableError()) +} + +func (v NativeFunctionValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { + return v +} + +func (v NativeFunctionValue) String() string { + //TODO implement me + panic("implement me") +} diff --git a/runtime/bbq/vm/values_simple.go b/runtime/bbq/vm/value_int.go similarity index 54% rename from runtime/bbq/vm/values_simple.go rename to runtime/bbq/vm/value_int.go index 3876d58020..100d2f558d 100644 --- a/runtime/bbq/vm/values_simple.go +++ b/runtime/bbq/vm/value_int.go @@ -19,46 +19,22 @@ package vm import ( + "strconv" + "github.com/onflow/atree" - "github.com/onflow/cadence/runtime/bbq" "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" ) -type Value interface { - isValue() - StaticType(common.MemoryGauge) StaticType - Transfer( - config *Config, - address atree.Address, - remove bool, - storable atree.Storable, - ) Value -} - -var TrueValue Value = BoolValue(true) -var FalseValue Value = BoolValue(false) - -type BoolValue bool - -var _ Value = BoolValue(true) - -func (BoolValue) isValue() {} - -func (BoolValue) StaticType(common.MemoryGauge) StaticType { - return interpreter.PrimitiveStaticTypeBool -} - -func (v BoolValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { - return v -} - type IntValue struct { SmallInt int64 } +func (v IntValue) String() string { + return strconv.FormatInt(v.SmallInt, 10) +} + var _ Value = IntValue{} func (IntValue) isValue() {} @@ -92,36 +68,3 @@ func (v IntValue) Greater(other IntValue) Value { } return FalseValue } - -type FunctionValue struct { - Function *bbq.Function - Context *Context -} - -var _ Value = FunctionValue{} - -func (FunctionValue) isValue() {} - -func (FunctionValue) StaticType(common.MemoryGauge) StaticType { - panic(errors.NewUnreachableError()) -} - -func (v FunctionValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { - return v -} - -type StringValue struct { - String []byte -} - -var _ Value = StringValue{} - -func (StringValue) isValue() {} - -func (StringValue) StaticType(common.MemoryGauge) StaticType { - return interpreter.PrimitiveStaticTypeString -} - -func (v StringValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { - return v -} diff --git a/runtime/bbq/vm/value_string.go b/runtime/bbq/vm/value_string.go new file mode 100644 index 0000000000..b403ecce1b --- /dev/null +++ b/runtime/bbq/vm/value_string.go @@ -0,0 +1,73 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "strings" + + "github.com/onflow/atree" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" +) + +type StringValue struct { + Str []byte +} + +var _ Value = StringValue{} + +func (StringValue) isValue() {} + +func (StringValue) StaticType(common.MemoryGauge) StaticType { + return interpreter.PrimitiveStaticTypeString +} + +func (v StringValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { + return v +} + +func (v StringValue) String() string { + return string(v.Str) +} + +// members + +const ( + StringConcatFunctionName = "concat" +) + +func init() { + typeName := interpreter.PrimitiveStaticTypeString.String() + + RegisterTypeBoundFunction(typeName, StringConcatFunctionName, NativeFunctionValue{ + ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), + Function: func(value ...Value) Value { + first := value[0].(StringValue) + second := value[1].(StringValue) + var sb strings.Builder + sb.Write(first.Str) + sb.Write(second.Str) + return StringValue{ + []byte(sb.String()), + } + }, + }) +} diff --git a/runtime/bbq/vm/value_utils.go b/runtime/bbq/vm/value_utils.go new file mode 100644 index 0000000000..1175337a42 --- /dev/null +++ b/runtime/bbq/vm/value_utils.go @@ -0,0 +1,35 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import "github.com/onflow/cadence/runtime/interpreter" + +const goIntSize = 32 << (^uint(0) >> 63) // 32 or 64 +const goMaxInt = 1<<(goIntSize-1) - 1 +const goMinInt = -1 << (goIntSize - 1) + +func safeAdd(a, b int) int { + // INT32-C + if (b > 0) && (a > (goMaxInt - b)) { + panic(interpreter.OverflowError{}) + } else if (b < 0) && (a < (goMinInt - b)) { + panic(interpreter.UnderflowError{}) + } + return a + b +} diff --git a/runtime/bbq/vm/value_void.go b/runtime/bbq/vm/value_void.go new file mode 100644 index 0000000000..36cb19ce45 --- /dev/null +++ b/runtime/bbq/vm/value_void.go @@ -0,0 +1,45 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/format" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" +) + +type VoidValue struct{} + +var _ Value = VoidValue{} + +func (VoidValue) isValue() {} + +func (VoidValue) StaticType(common.MemoryGauge) StaticType { + return interpreter.PrimitiveStaticTypeVoid +} + +func (v VoidValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { + return v +} + +func (v VoidValue) String() string { + return format.Void +} diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 5ca10f79d2..c9791a1e6b 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -49,7 +49,13 @@ func NewVM(program *bbq.Program, conf *Config) *VM { } // linkedGlobalsCache is a local cache-alike that is being used to hold already linked imports. - linkedGlobalsCache := map[common.Location]LinkedGlobals{} + linkedGlobalsCache := map[common.Location]LinkedGlobals{ + BuiltInLocation: { + // It is safe to re-use native functions map here because, + // once put into the cache, it will only be used for read-only operations. + indexedGlobals: NativeFunctions, + }, + } // Link global variables and functions. linkedGlobals := LinkGlobals(program, conf, linkedGlobalsCache) @@ -251,13 +257,23 @@ func opSetGlobal(vm *VM) { } func opInvoke(vm *VM) { - value := vm.pop().(FunctionValue) + value := vm.pop() stackHeight := len(vm.stack) - parameterCount := int(value.Function.ParameterCount) - arguments := vm.stack[stackHeight-parameterCount:] - vm.pushCallFrame(value.Context, value.Function, arguments) - vm.dropN(parameterCount) + switch value := value.(type) { + case FunctionValue: + parameterCount := int(value.Function.ParameterCount) + arguments := vm.stack[stackHeight-parameterCount:] + vm.pushCallFrame(value.Context, value.Function, arguments) + vm.dropN(parameterCount) + case NativeFunctionValue: + parameterCount := value.ParameterCount + arguments := vm.stack[stackHeight-parameterCount:] + result := value.Function(arguments...) + vm.push(result) + default: + panic(errors.NewUnreachableError()) + } } func opDrop(vm *VM) { @@ -295,7 +311,7 @@ func opNew(vm *VM) { func opSetField(vm *VM) { fieldName := vm.pop().(StringValue) - fieldNameStr := string(fieldName.String) + fieldNameStr := string(fieldName.Str) // TODO: support all container types structValue := vm.pop().(*CompositeValue) @@ -307,7 +323,7 @@ func opSetField(vm *VM) { func opGetField(vm *VM) { fieldName := vm.pop().(StringValue) - fieldNameStr := string(fieldName.String) + fieldNameStr := string(fieldName.Str) // TODO: support all container types structValue := vm.pop().(*CompositeValue) @@ -322,7 +338,7 @@ func opGetField(vm *VM) { vm.push(fieldValue) } -func opCheckType(vm *VM) { +func opTransfer(vm *VM) { targetType := vm.loadType() value := vm.peek() @@ -400,8 +416,8 @@ func (vm *VM) run() { opSetField(vm) case opcode.GetField: opGetField(vm) - case opcode.CheckType: - opCheckType(vm) + case opcode.Transfer: + opTransfer(vm) case opcode.Destroy: opDestroy(vm) default: @@ -423,7 +439,7 @@ func (vm *VM) initializeConstant(index uint16) (value Value) { smallInt, _, _ := leb128.ReadInt64(constant.Data) value = IntValue{SmallInt: smallInt} case constantkind.String: - value = StringValue{String: constant.Data} + value = StringValue{Str: constant.Data} default: // TODO: panic(errors.NewUnreachableError()) diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index dc6ca57ac3..9fac80a4e8 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -31,6 +31,7 @@ import ( "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" . "github.com/onflow/cadence/runtime/tests/checker" "github.com/onflow/cadence/runtime/tests/utils" @@ -287,7 +288,7 @@ func TestStructMethodCall(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) - require.Equal(t, StringValue{String: []byte("Hello from Foo!")}, result) + require.Equal(t, StringValue{Str: []byte("Hello from Foo!")}, result) } func BenchmarkNewStruct(b *testing.B) { @@ -469,7 +470,7 @@ func TestImport(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) - require.Equal(t, StringValue{String: []byte("global function of the imported program")}, result) + require.Equal(t, StringValue{Str: []byte("global function of the imported program")}, result) } func TestContractImport(t *testing.T) { @@ -555,7 +556,7 @@ func TestContractImport(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) - require.Equal(t, StringValue{String: []byte("global function of the imported program")}, result) + require.Equal(t, StringValue{Str: []byte("global function of the imported program")}, result) }) t.Run("contract function", func(t *testing.T) { @@ -627,7 +628,7 @@ func TestContractImport(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) - require.Equal(t, StringValue{String: []byte("contract function of the imported program")}, result) + require.Equal(t, StringValue{Str: []byte("contract function of the imported program")}, result) }) t.Run("nested imports", func(t *testing.T) { @@ -798,7 +799,7 @@ func TestContractImport(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) - require.Equal(t, StringValue{String: []byte("Hello from Foo!")}, result) + require.Equal(t, StringValue{Str: []byte("Hello from Foo!")}, result) }) } @@ -922,7 +923,7 @@ func TestInitializeContract(t *testing.T) { require.NoError(t, err) fieldValue := contractValue.GetMember(vm.config, "status") - assert.Equal(t, StringValue{String: []byte("PENDING")}, fieldValue) + assert.Equal(t, StringValue{Str: []byte("PENDING")}, fieldValue) } func TestContractAccessDuringInit(t *testing.T) { @@ -958,7 +959,7 @@ func TestContractAccessDuringInit(t *testing.T) { require.NoError(t, err) fieldValue := contractValue.GetMember(vm.config, "status") - assert.Equal(t, StringValue{String: []byte("PENDING")}, fieldValue) + assert.Equal(t, StringValue{Str: []byte("PENDING")}, fieldValue) }) t.Run("using self", func(t *testing.T) { @@ -990,7 +991,7 @@ func TestContractAccessDuringInit(t *testing.T) { require.NoError(t, err) fieldValue := contractValue.GetMember(vm.config, "status") - assert.Equal(t, StringValue{String: []byte("PENDING")}, fieldValue) + assert.Equal(t, StringValue{Str: []byte("PENDING")}, fieldValue) }) } @@ -1148,7 +1149,7 @@ func TestContractField(t *testing.T) { vm = NewVM(program, vmConfig) result, err := vm.Invoke("test") require.NoError(t, err) - require.Equal(t, StringValue{String: []byte("PENDING")}, result) + require.Equal(t, StringValue{Str: []byte("PENDING")}, result) }) t.Run("set", func(t *testing.T) { @@ -1216,10 +1217,10 @@ func TestContractField(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) - require.Equal(t, StringValue{String: []byte("UPDATED")}, result) + require.Equal(t, StringValue{Str: []byte("UPDATED")}, result) fieldValue := importedContractValue.GetMember(vm.config, "status") - assert.Equal(t, StringValue{String: []byte("UPDATED")}, fieldValue) + assert.Equal(t, StringValue{Str: []byte("UPDATED")}, fieldValue) }) } @@ -1242,3 +1243,73 @@ func singleIdentifierLocationResolver(t testing.TB) func( }, nil } } + +func TestNativeFunctions(t *testing.T) { + + t.Parallel() + + t.Run("static function", func(t *testing.T) { + + logFunction := stdlib.NewStandardLibraryFunction( + "log", + &sema.FunctionType{ + Parameters: []*sema.Parameter{ + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "value", + TypeAnnotation: sema.NewTypeAnnotation(sema.AnyStructType), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation( + sema.VoidType, + ), + }, + ``, + nil, + ) + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(logFunction) + + checker, err := ParseAndCheckWithOptions(t, ` + fun test() { + log("Hello, World!") + }`, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + }, + ) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + printProgram(program) + + vm := NewVM(program, nil) + + _, err = vm.Invoke("test") + require.NoError(t, err) + }) + + t.Run("bound function", func(t *testing.T) { + checker, err := ParseAndCheck(t, ` + fun test(): String { + return "Hello".concat(", World!") + }`, + ) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + printProgram(program) + + vm := NewVM(program, nil) + + result, err := vm.Invoke("test") + require.NoError(t, err) + + require.Equal(t, StringValue{Str: []byte("Hello, World!")}, result) + }) +} From 26ae063a324918cd6918727de3cb8b2aa9dbb3cc Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 28 Mar 2023 14:46:48 -0700 Subject: [PATCH 18/89] Start basics needed for FT transfer --- runtime/bbq/commons/constants.go | 26 ++ runtime/bbq/compiler/compiler.go | 102 ++++- runtime/bbq/compiler/native_functions.go | 1 + runtime/bbq/compiler/stack.go | 4 + runtime/bbq/contract.go | 5 +- runtime/bbq/opcode/opcode.go | 6 +- runtime/bbq/opcode/opcode_string.go | 16 +- runtime/bbq/vm/callframe.go | 6 + runtime/bbq/vm/ft_test.go | 509 +++++++++++++++++++++++ runtime/bbq/vm/value_authaccount.go | 48 +++ runtime/bbq/vm/value_nil.go | 47 +++ runtime/bbq/vm/value_path.go | 61 +++ runtime/bbq/vm/vm.go | 15 +- runtime/bbq/vm/vm_test.go | 169 ++++++++ 14 files changed, 988 insertions(+), 27 deletions(-) create mode 100644 runtime/bbq/vm/ft_test.go create mode 100644 runtime/bbq/vm/value_authaccount.go create mode 100644 runtime/bbq/vm/value_nil.go create mode 100644 runtime/bbq/vm/value_path.go diff --git a/runtime/bbq/commons/constants.go b/runtime/bbq/commons/constants.go index 22dfe1ab80..ce82e61360 100644 --- a/runtime/bbq/commons/constants.go +++ b/runtime/bbq/commons/constants.go @@ -18,7 +18,33 @@ package commons +import ( + "github.com/onflow/cadence/runtime/ast" + "github.com/onflow/cadence/runtime/errors" +) + const ( InitFunctionName = "init" LogFunctionName = "log" ) + +type CastType byte + +const ( + SimpleCast CastType = iota + FailableCast + ForceCast +) + +func CastTypeFrom(operation ast.Operation) CastType { + switch operation { + case ast.OperationCast: + return SimpleCast + case ast.OperationFailableCast: + return FailableCast + case ast.OperationForceCast: + return ForceCast + default: + panic(errors.NewUnreachableError()) + } +} diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index c66a9cee3c..a0005ba227 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -88,6 +88,18 @@ func (c *Compiler) findGlobal(name string) *global { return global } + // If failed to find, then try with type-qualified name. + // This is because contract functions/type-constructors can be accessed without the contract name. + // e.g: SomeContract.Foo() == Foo(), within `SomeContract`. + if !c.compositeTypeStack.isEmpty() { + enclosingContract := c.compositeTypeStack.bottom() + typeQualifiedName := commons.TypeQualifiedName(enclosingContract.Identifier, name) + global, ok = c.globals[typeQualifiedName] + if ok { + return global + } + } + importedGlobal, ok := c.indexedImportedGlobals[name] if !ok { panic(errors.NewUnexpectedError("cannot find global declaration '%s'", name)) @@ -225,6 +237,12 @@ func (c *Compiler) Compile() *bbq.Program { c.compileDeclaration(declaration) } + if c.Program.SoleContractInterfaceDeclaration() != nil { + return &bbq.Program{ + Contract: c.exportContract(), + } + } + // Reserve globals for functions/types before visiting their implementations. c.reserveGlobalVars( "", @@ -351,16 +369,30 @@ func (c *Compiler) exportFunctions() []*bbq.Function { } func (c *Compiler) exportContract() *bbq.Contract { + var location common.Location + var name string + contractDecl := c.Program.SoleContractDeclaration() - if contractDecl == nil { - return nil + if contractDecl != nil { + contractType := c.Elaboration.CompositeDeclarationTypes[contractDecl] + location = contractType.Location + name = contractType.Identifier + } else { + interfaceDecl := c.Program.SoleContractInterfaceDeclaration() + if interfaceDecl == nil { + return nil + } + + interfaceType := c.Elaboration.InterfaceDeclarationTypes[interfaceDecl] + location = interfaceType.Location + name = interfaceType.Identifier } - contractType := c.Elaboration.CompositeDeclarationTypes[contractDecl] - addressLocation := contractType.Location.(common.AddressLocation) + addressLocation := location.(common.AddressLocation) return &bbq.Contract{ - Name: contractType.Identifier, - Address: addressLocation.Address[:], + Name: name, + Address: addressLocation.Address[:], + IsInterface: contractDecl == nil, } } @@ -377,6 +409,10 @@ func (c *Compiler) compileBlock(block *ast.Block) { func (c *Compiler) compileFunctionBlock(functionBlock *ast.FunctionBlock) { // TODO: pre and post conditions, incl. interfaces + if functionBlock == nil { + return + } + c.compileBlock(functionBlock.Block) } @@ -576,6 +612,12 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio switch invokedExpr := expression.InvokedExpression.(type) { case *ast.IdentifierExpression: + typ := c.Elaboration.IdentifierInInvocationTypes[invokedExpr] + invocationType := typ.(*sema.FunctionType) + if invocationType.IsConstructor { + + } + // Load arguments c.loadArguments(expression) // Load function value @@ -679,9 +721,17 @@ func (c *Compiler) VisitStringExpression(expression *ast.StringExpression) (_ st return } -func (c *Compiler) VisitCastingExpression(_ *ast.CastingExpression) (_ struct{}) { - // TODO - panic(errors.NewUnreachableError()) +func (c *Compiler) VisitCastingExpression(expression *ast.CastingExpression) (_ struct{}) { + c.compileExpression(expression.Expression) + + targetType := c.Elaboration.CastingTargetTypes[expression] + index := c.getOrAddType(targetType) + first, second := encodeUint16(index) + + castType := commons.CastTypeFrom(expression.Operation) + + c.emit(opcode.Cast, first, second, byte(castType)) + return } func (c *Compiler) VisitCreateExpression(expression *ast.CreateExpression) (_ struct{}) { @@ -705,9 +755,25 @@ func (c *Compiler) VisitForceExpression(_ *ast.ForceExpression) (_ struct{}) { panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitPathExpression(_ *ast.PathExpression) (_ struct{}) { - // TODO - panic(errors.NewUnreachableError()) +func (c *Compiler) VisitPathExpression(expression *ast.PathExpression) (_ struct{}) { + identifier := expression.Identifier.Identifier + + byteSize := 1 + // one byte for path domain + 2 + // 2 bytes for identifier size + len(identifier) // identifier + + args := make([]byte, 0, byteSize) + + domainByte := byte(common.PathDomainFromIdentifier(expression.Domain.Identifier)) + args = append(args, domainByte) + + identifierSizeFirst, identifierSizeSecond := encodeUint16(uint16(len(identifier))) + args = append(args, identifierSizeFirst, identifierSizeSecond) + args = append(args, identifier...) + + c.emit(opcode.Path, args...) + + return } func (c *Compiler) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunctionDeclaration) (_ struct{}) { @@ -715,6 +781,8 @@ func (c *Compiler) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunct switch kind { case common.DeclarationKindInitializer: c.compileInitializer(declaration) + case common.DeclarationKindDestructor: + // TODO: default: // TODO: support other special functions panic(errors.NewUnreachableError()) @@ -933,6 +1001,12 @@ func (c *Compiler) patchLoop(l *loop) { } func (c *Compiler) emitCheckType(targetType sema.Type) { + index := c.getOrAddType(targetType) + first, second := encodeUint16(index) + c.emit(opcode.Transfer, first, second) +} + +func (c *Compiler) getOrAddType(targetType sema.Type) uint16 { // Optimization: Re-use types in the pool. index, ok := c.typesInPool[targetType] if !ok { @@ -944,9 +1018,7 @@ func (c *Compiler) emitCheckType(targetType sema.Type) { index = c.addType(bytes) c.typesInPool[targetType] = index } - - first, second := encodeUint16(index) - c.emit(opcode.Transfer, first, second) + return index } func (c *Compiler) addType(data []byte) uint16 { diff --git a/runtime/bbq/compiler/native_functions.go b/runtime/bbq/compiler/native_functions.go index aa8e8290d1..ef51fd6803 100644 --- a/runtime/bbq/compiler/native_functions.go +++ b/runtime/bbq/compiler/native_functions.go @@ -29,6 +29,7 @@ var nativeFunctions []*global var builtinTypes = []sema.Type{ sema.StringType, + sema.AuthAccountType, } // TODO: Maybe diff --git a/runtime/bbq/compiler/stack.go b/runtime/bbq/compiler/stack.go index 1d1ca28ece..43ec3425e8 100644 --- a/runtime/bbq/compiler/stack.go +++ b/runtime/bbq/compiler/stack.go @@ -38,6 +38,10 @@ func (s *Stack[T]) top() T { return s.elements[lastIndex] } +func (s *Stack[T]) bottom() T { + return s.elements[0] +} + func (s *Stack[T]) isEmpty() bool { return len(s.elements) == 0 } diff --git a/runtime/bbq/contract.go b/runtime/bbq/contract.go index 882b595520..a5ab6bb892 100644 --- a/runtime/bbq/contract.go +++ b/runtime/bbq/contract.go @@ -19,6 +19,7 @@ package bbq type Contract struct { - Name string - Address []byte + Name string + Address []byte + IsInterface bool } diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index 972f5ce34d..939f788b9d 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -54,10 +54,12 @@ const ( SetField Invoke - - New Destroy Transfer + Cast + + New + Path Drop Dup diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index e540632d04..c9413a5b46 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -34,16 +34,18 @@ func _() { _ = x[GetField-23] _ = x[SetField-24] _ = x[Invoke-25] - _ = x[New-26] - _ = x[Destroy-27] - _ = x[Transfer-28] - _ = x[Drop-29] - _ = x[Dup-30] + _ = x[Destroy-26] + _ = x[Transfer-27] + _ = x[Cast-28] + _ = x[New-29] + _ = x[Path-30] + _ = x[Drop-31] + _ = x[Dup-32] } -const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeNewDestroyTransferDropDup" +const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeDestroyTransferCastNewPathDropDup" -var _Opcode_index = [...]uint8{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 203, 211, 219, 225, 228, 235, 243, 247, 250} +var _Opcode_index = [...]uint16{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 203, 211, 219, 225, 232, 240, 244, 247, 251, 255, 258} func (i Opcode) String() string { if i >= Opcode(len(_Opcode_index)-1) { diff --git a/runtime/bbq/vm/callframe.go b/runtime/bbq/vm/callframe.go index 140bcc6e90..5ed4bb89e3 100644 --- a/runtime/bbq/vm/callframe.go +++ b/runtime/bbq/vm/callframe.go @@ -37,6 +37,12 @@ func (f *callFrame) getUint16() uint16 { return uint16(first)<<8 | uint16(last) } +func (f *callFrame) getByte() byte { + byt := f.function.Code[f.ip] + f.ip++ + return byt +} + func (f *callFrame) getString() string { strLen := f.getUint16() str := string(f.function.Code[f.ip : f.ip+strLen]) diff --git a/runtime/bbq/vm/ft_test.go b/runtime/bbq/vm/ft_test.go new file mode 100644 index 0000000000..96c0b61143 --- /dev/null +++ b/runtime/bbq/vm/ft_test.go @@ -0,0 +1,509 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "fmt" + "github.com/onflow/cadence/runtime/ast" + "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/common" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence/runtime/bbq/compiler" + "github.com/onflow/cadence/runtime/sema" + . "github.com/onflow/cadence/runtime/tests/checker" +) + +func TestFTTransfer(t *testing.T) { + + // Deploy FT Contract + ftLocation := common.NewAddressLocation(nil, common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, "FungibleToken") + ftChecker, err := ParseAndCheckWithOptions(t, realFungibleTokenContractInterface, + ParseAndCheckOptions{Location: ftLocation}, + ) + require.NoError(t, err) + + ftCompiler := compiler.NewCompiler(ftChecker.Program, ftChecker.Elaboration) + ftProgram := ftCompiler.Compile() + + //vm := NewVM(ftProgram, nil) + //importedContractValue, err := vm.InitializeContract() + //require.NoError(t, err) + + // Deploy FlowToken Contract + flowTokenLocation := common.NewAddressLocation(nil, common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, "FlowToken") + flowTokenChecker, err := ParseAndCheckWithOptions(t, realFlowContract, + ParseAndCheckOptions{ + Location: flowTokenLocation, + Config: &sema.Config{ + ImportHandler: func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { + switch location { + case ftLocation: + return sema.ElaborationImport{ + Elaboration: ftChecker.Elaboration, + }, nil + default: + return nil, fmt.Errorf("cannot find contract in location %s", location) + } + }, + LocationHandler: singleIdentifierLocationResolver(t), + }, + }, + ) + require.NoError(t, err) + + flowTokenCompiler := compiler.NewCompiler(flowTokenChecker.Program, flowTokenChecker.Elaboration) + flowTokenCompiler.Config.ImportHandler = func(location common.Location) *bbq.Program { + return ftProgram + } + + flowTokenProgram := flowTokenCompiler.Compile() + + vm := NewVM(flowTokenProgram, nil) + + authAcount := NewCompositeValue( + nil, + "AuthAccount", + common.CompositeKindStructure, + common.Address{}, + vm.config.Storage, + ) + + printProgram(flowTokenProgram) + + flowTokenContractValue, err := vm.InitializeContract(authAcount) + require.NoError(t, err) + + // Run script + + checker, err := ParseAndCheckWithOptions(t, ` + import FungibleToken from 0x01 + + fun test(): String { + return "hello" + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { + switch location { + case flowTokenLocation: + return sema.ElaborationImport{ + Elaboration: flowTokenChecker.Elaboration, + }, nil + default: + return nil, fmt.Errorf("cannot find contract in location %s", location) + } + }, + LocationHandler: singleIdentifierLocationResolver(t), + }, + }, + ) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + return flowTokenProgram + } + + program := comp.Compile() + + vmConfig := &Config{ + ImportHandler: func(location common.Location) *bbq.Program { + return ftProgram + }, + ContractValueHandler: func(*Config, common.Location) *CompositeValue { + return flowTokenContractValue + }, + } + + vm = NewVM(program, vmConfig) + + result, err := vm.Invoke("test") + require.NoError(t, err) + require.Equal(t, StringValue{Str: []byte("global function of the imported program")}, result) +} + +const realFungibleTokenContractInterface = ` +/// FungibleToken +/// +/// The interface that fungible token contracts implement. +/// +pub contract interface FungibleToken { + + /// The total number of tokens in existence. + /// It is up to the implementer to ensure that the total supply + /// stays accurate and up to date + /// + pub var totalSupply: Int + + ///// TokensInitialized + ///// + ///// The event that is emitted when the contract is created + ///// + //pub event TokensInitialized(initialSupply: Int) + // + ///// TokensWithdrawn + ///// + ///// The event that is emitted when tokens are withdrawn from a Vault + ///// + //pub event TokensWithdrawn(amount: Int, from: Address?) + // + ///// TokensDeposited + ///// + ///// The event that is emitted when tokens are deposited into a Vault + ///// + //pub event TokensDeposited(amount: Int, to: Address?) + + /// Provider + /// + /// The interface that enforces the requirements for withdrawing + /// tokens from the implementing type. + /// + /// It does not enforce requirements on 'balance' here, + /// because it leaves open the possibility of creating custom providers + /// that do not necessarily need their own balance. + /// + pub resource interface Provider { + + /// withdraw subtracts tokens from the owner's Vault + /// and returns a Vault with the removed tokens. + /// + /// The function's access level is public, but this is not a problem + /// because only the owner storing the resource in their account + /// can initially call this function. + /// + /// The owner may grant other accounts access by creating a private + /// capability that allows specific other users to access + /// the provider resource through a reference. + /// + /// The owner may also grant all accounts access by creating a public + /// capability that allows all users to access the provider + /// resource through a reference. + /// + pub fun withdraw(amount: Int): @Vault { + post { + // 'result' refers to the return value + result.balance == amount: + "Withdrawal amount must be the same as the balance of the withdrawn Vault" + } + } + } + + /// Receiver + /// + /// The interface that enforces the requirements for depositing + /// tokens into the implementing type. + /// + /// We do not include a condition that checks the balance because + /// we want to give users the ability to make custom receivers that + /// can do custom things with the tokens, like split them up and + /// send them to different places. + /// + pub resource interface Receiver { + + /// deposit takes a Vault and deposits it into the implementing resource type + /// + pub fun deposit(from: @Vault) + } + + /// Balance + /// + /// The interface that contains the 'balance' field of the Vault + /// and enforces that when new Vaults are created, the balance + /// is initialized correctly. + /// + pub resource interface Balance { + + /// The total balance of a vault + /// + pub var balance: Int + + init(balance: Int) { + post { + self.balance == balance: + "Balance must be initialized to the initial balance" + } + } + } + + /// Vault + /// + /// The resource that contains the functions to send and receive tokens. + /// + pub resource Vault: Provider, Receiver, Balance { + + // The declaration of a concrete type in a contract interface means that + // every Fungible Token contract that implements the FungibleToken interface + // must define a concrete 'Vault' resource that conforms to the 'Provider', 'Receiver', + // and 'Balance' interfaces, and declares their required fields and functions + + /// The total balance of the vault + /// + pub var balance: Int + + // The conforming type must declare an initializer + // that allows prioviding the initial balance of the Vault + // + init(balance: Int) + + /// withdraw subtracts 'amount' from the Vault's balance + /// and returns a new Vault with the subtracted balance + /// + pub fun withdraw(amount: Int): @Vault { + pre { + self.balance >= amount: + "Amount withdrawn must be less than or equal than the balance of the Vault" + } + post { + // use the special function 'before' to get the value of the 'balance' field + // at the beginning of the function execution + // + self.balance == before(self.balance) - amount: + "New Vault balance must be the difference of the previous balance and the withdrawn Vault" + } + } + + /// deposit takes a Vault and adds its balance to the balance of this Vault + /// + pub fun deposit(from: @Vault) { + post { + self.balance == before(self.balance) + before(from.balance): + "New Vault balance must be the sum of the previous balance and the deposited Vault" + } + } + } + + /// createEmptyVault allows any user to create a new Vault that has a zero balance + /// + pub fun createEmptyVault(): @Vault { + post { + result.balance == 0: "The newly created Vault must have zero balance" + } + } +} +` + +const realFlowContract = ` +import FungibleToken from 0x1 + +pub contract FlowToken: FungibleToken { + + // Total supply of Flow tokens in existence + pub var totalSupply: Int + + //// Event that is emitted when the contract is created + //pub event TokensInitialized(initialSupply: Int) + // + //// Event that is emitted when tokens are withdrawn from a Vault + //pub event TokensWithdrawn(amount: Int, from: Address?) + // + //// Event that is emitted when tokens are deposited to a Vault + //pub event TokensDeposited(amount: Int, to: Address?) + // + //// Event that is emitted when new tokens are minted + //pub event TokensMinted(amount: Int) + // + //// Event that is emitted when tokens are destroyed + //pub event TokensBurned(amount: Int) + // + //// Event that is emitted when a new minter resource is created + //pub event MinterCreated(allowedAmount: Int) + // + //// Event that is emitted when a new burner resource is created + //pub event BurnerCreated() + + // Vault + // + // Each user stores an instance of only the Vault in their storage + // The functions in the Vault and governed by the pre and post conditions + // in FungibleToken when they are called. + // The checks happen at runtime whenever a function is called. + // + // Resources can only be created in the context of the contract that they + // are defined in, so there is no way for a malicious user to create Vaults + // out of thin air. A special Minter resource needs to be defined to mint + // new tokens. + // + pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance { + + // holds the balance of a users tokens + pub var balance: Int + + // initialize the balance at resource creation time + init(balance: Int) { + self.balance = balance + } + + // withdraw + // + // Function that takes an integer amount as an argument + // and withdraws that amount from the Vault. + // It creates a new temporary Vault that is used to hold + // the money that is being transferred. It returns the newly + // created Vault to the context that called so it can be deposited + // elsewhere. + // + pub fun withdraw(amount: Int): @FungibleToken.Vault { + self.balance = self.balance - amount + // emit TokensWithdrawn(amount: amount, from: self.owner?.address) + return <-create Vault(balance: amount) + } + + // deposit + // + // Function that takes a Vault object as an argument and adds + // its balance to the balance of the owners Vault. + // It is allowed to destroy the sent Vault because the Vault + // was a temporary holder of the tokens. The Vault's balance has + // been consumed and therefore can be destroyed. + pub fun deposit(from: @FungibleToken.Vault) { + let vault <- from as! @FlowToken.Vault + self.balance = self.balance + vault.balance + // emit TokensDeposited(amount: vault.balance, to: self.owner?.address) + vault.balance = 0 + destroy vault + } + + destroy() { + FlowToken.totalSupply = FlowToken.totalSupply - self.balance + } + } + + // createEmptyVault + // + // Function that creates a new Vault with a balance of zero + // and returns it to the calling context. A user must call this function + // and store the returned Vault in their storage in order to allow their + // account to be able to receive deposits of this token type. + // + pub fun createEmptyVault(): @FungibleToken.Vault { + return <-create Vault(balance: 0) + } + + pub resource Administrator { + + init() {} + + // createNewMinter + // + // Function that creates and returns a new minter resource + // + pub fun createNewMinter(allowedAmount: Int): @Minter { + // emit MinterCreated(allowedAmount: allowedAmount) + return <-create Minter(allowedAmount: allowedAmount) + } + + // createNewBurner + // + // Function that creates and returns a new burner resource + // + pub fun createNewBurner(): @Burner { + // emit BurnerCreated() + return <-create Burner() + } + } + + // Minter + // + // Resource object that token admin accounts can hold to mint new tokens. + // + pub resource Minter { + + // the amount of tokens that the minter is allowed to mint + pub var allowedAmount: Int + + // mintTokens + // + // Function that mints new tokens, adds them to the total supply, + // and returns them to the calling context. + // + pub fun mintTokens(amount: Int): @FlowToken.Vault { + pre { + amount > Int(0): "Amount minted must be greater than zero" + amount <= self.allowedAmount: "Amount minted must be less than the allowed amount" + } + FlowToken.totalSupply = FlowToken.totalSupply + amount + self.allowedAmount = self.allowedAmount - amount + // emit TokensMinted(amount: amount) + return <-create Vault(balance: amount) + } + + init(allowedAmount: Int) { + self.allowedAmount = allowedAmount + } + } + + // Burner + // + // Resource object that token admin accounts can hold to burn tokens. + // + pub resource Burner { + + init() {} + + // burnTokens + // + // Function that destroys a Vault instance, effectively burning the tokens. + // + // Note: the burned tokens are automatically subtracted from the + // total supply in the Vault destructor. + // + pub fun burnTokens(from: @FungibleToken.Vault) { + let vault <- from as! @FlowToken.Vault + let amount = vault.balance + destroy vault + // emit TokensBurned(amount: amount) + } + } + + init(adminAccount: AuthAccount) { + self.totalSupply = 0 + + // Create the Vault with the total supply of tokens and save it in storage + // + let vault <- create Vault(balance: self.totalSupply) + adminAccount.save(<-vault, to: /storage/flowTokenVault) + + // Create a public capability to the stored Vault that only exposes + // the 'deposit' method through the 'Receiver' interface + // + adminAccount.link<&FlowToken.Vault{FungibleToken.Receiver}>( + /public/flowTokenReceiver, + target: /storage/flowTokenVault + ) + + // Create a public capability to the stored Vault that only exposes + // the 'balance' field through the 'Balance' interface + // + adminAccount.link<&FlowToken.Vault{FungibleToken.Balance}>( + /public/flowTokenBalance, + target: /storage/flowTokenVault + ) + + let admin <- create Administrator() + adminAccount.save(<-admin, to: /storage/flowTokenAdmin) + + // Emit an event that shows that the contract was initialized + // emit TokensInitialized(initialSupply: self.totalSupply) + } +} +` diff --git a/runtime/bbq/vm/value_authaccount.go b/runtime/bbq/vm/value_authaccount.go new file mode 100644 index 0000000000..ab6ae68f51 --- /dev/null +++ b/runtime/bbq/vm/value_authaccount.go @@ -0,0 +1,48 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" +) + +// members + +func init() { + typeName := interpreter.PrimitiveStaticTypeAuthAccount.String() + + // AuthAccount.link + RegisterTypeBoundFunction(typeName, sema.AuthAccountLinkField, NativeFunctionValue{ + ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), + Function: func(value ...Value) Value { + // TODO: + return NilValue{} + }, + }) + + // AuthAccount.save + RegisterTypeBoundFunction(typeName, sema.AuthAccountSaveField, NativeFunctionValue{ + ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), + Function: func(value ...Value) Value { + // TODO: + return NilValue{} + }, + }) +} diff --git a/runtime/bbq/vm/value_nil.go b/runtime/bbq/vm/value_nil.go new file mode 100644 index 0000000000..0bcc4a7ec4 --- /dev/null +++ b/runtime/bbq/vm/value_nil.go @@ -0,0 +1,47 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/format" + "github.com/onflow/cadence/runtime/interpreter" +) + +type NilValue struct{} + +var _ Value = NilValue{} + +func (NilValue) isValue() {} + +func (NilValue) StaticType(common.MemoryGauge) StaticType { + return interpreter.NewOptionalStaticType( + nil, + interpreter.PrimitiveStaticTypeNever, + ) +} + +func (v NilValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { + return v +} + +func (v NilValue) String() string { + return format.Nil +} diff --git a/runtime/bbq/vm/value_path.go b/runtime/bbq/vm/value_path.go new file mode 100644 index 0000000000..4b114ac3c4 --- /dev/null +++ b/runtime/bbq/vm/value_path.go @@ -0,0 +1,61 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/atree" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/format" + "github.com/onflow/cadence/runtime/interpreter" +) + +type PathValue struct { + Domain common.PathDomain + Identifier string +} + +var _ Value = PathValue{} + +func (PathValue) isValue() {} + +func (v PathValue) StaticType(common.MemoryGauge) StaticType { + switch v.Domain { + case common.PathDomainStorage: + return interpreter.PrimitiveStaticTypeStoragePath + case common.PathDomainPublic: + return interpreter.PrimitiveStaticTypePublicPath + case common.PathDomainPrivate: + return interpreter.PrimitiveStaticTypePrivatePath + default: + panic(errors.NewUnreachableError()) + } +} + +func (v PathValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { + return v +} + +func (v PathValue) String() string { + return format.Path( + v.Domain.Identifier(), + v.Identifier, + ) +} diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index c9791a1e6b..83f24ae28e 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -361,6 +361,17 @@ func opDestroy(vm *VM) { value.Destroy(vm.config) } +func opPath(vm *VM) { + callframe := vm.callFrame + domain := common.PathDomain(callframe.getByte()) + identifier := callframe.getString() + value := PathValue{ + Domain: domain, + Identifier: identifier, + } + vm.push(value) +} + func (vm *VM) run() { for { @@ -420,6 +431,8 @@ func (vm *VM) run() { opTransfer(vm) case opcode.Destroy: opDestroy(vm) + case opcode.Path: + opPath(vm) default: panic(errors.NewUnreachableError()) } @@ -442,7 +455,7 @@ func (vm *VM) initializeConstant(index uint16) (value Value) { value = StringValue{Str: constant.Data} default: // TODO: - panic(errors.NewUnreachableError()) + panic(errors.NewUnexpectedError("unsupported constant kind '%s'", constant.Kind.String())) } ctx.Constants[index] = value diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index 9fac80a4e8..7d8f806629 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -801,6 +801,175 @@ func TestContractImport(t *testing.T) { require.NoError(t, err) require.Equal(t, StringValue{Str: []byte("Hello from Foo!")}, result) }) + + t.Run("contract interface", func(t *testing.T) { + + // Initialize Foo + + fooLocation := common.NewAddressLocation( + nil, + common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + "Foo", + ) + + fooChecker, err := ParseAndCheckWithOptions(t, + ` + contract interface Foo { + fun withdraw(_ amount: Int): String { + pre { + amount < 100: "Withdraw limit exceeds" + } + } + }`, + ParseAndCheckOptions{ + Location: fooLocation, + }, + ) + require.NoError(t, err) + + fooCompiler := compiler.NewCompiler(fooChecker.Program, fooChecker.Elaboration) + fooProgram := fooCompiler.Compile() + + //vm := NewVM(fooProgram, nil) + //fooContractValue, err := vm.InitializeContract() + //require.NoError(t, err) + + // Initialize Bar + + barLocation := common.NewAddressLocation( + nil, + common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2}, + "Bar", + ) + + barChecker, err := ParseAndCheckWithOptions(t, ` + import Foo from 0x01 + + contract Bar: Foo { + init() {} + fun withdraw(_ amount: Int): String { + return "Successfully withdrew" + } + }`, + ParseAndCheckOptions{ + Location: barLocation, + Config: &sema.Config{ + ImportHandler: func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { + require.Equal(t, fooLocation, location) + return sema.ElaborationImport{ + Elaboration: fooChecker.Elaboration, + }, nil + }, + LocationHandler: singleIdentifierLocationResolver(t), + }, + }, + ) + require.NoError(t, err) + + barCompiler := compiler.NewCompiler(barChecker.Program, barChecker.Elaboration) + barCompiler.Config.LocationHandler = singleIdentifierLocationResolver(t) + barCompiler.Config.ImportHandler = func(location common.Location) *bbq.Program { + require.Equal(t, fooLocation, location) + return fooProgram + } + + barProgram := barCompiler.Compile() + + vmConfig := &Config{ + ImportHandler: func(location common.Location) *bbq.Program { + require.Equal(t, fooLocation, location) + return fooProgram + }, + //ContractValueHandler: func(_ *Config, location common.Location) *CompositeValue { + // require.Equal(t, fooLocation, location) + // return fooContractValue + //}, + } + + vm := NewVM(barProgram, vmConfig) + barContractValue, err := vm.InitializeContract() + require.NoError(t, err) + + // Compile and run main program + + checker, err := ParseAndCheckWithOptions(t, ` + import Bar from 0x02 + + fun test(): String { + return Bar.withdraw(150) + }`, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { + require.IsType(t, common.AddressLocation{}, location) + addressLocation := location.(common.AddressLocation) + var elaboration *sema.Elaboration + switch addressLocation.Address { + case fooLocation.Address: + elaboration = fooChecker.Elaboration + case barLocation.Address: + elaboration = barChecker.Elaboration + default: + assert.FailNow(t, "invalid location") + } + + return sema.ElaborationImport{ + Elaboration: elaboration, + }, nil + }, + LocationHandler: singleIdentifierLocationResolver(t), + }, + }, + ) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.LocationHandler = singleIdentifierLocationResolver(t) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + switch location { + case fooLocation: + return fooProgram + case barLocation: + return barProgram + default: + assert.FailNow(t, "invalid location") + return nil + } + } + + program := comp.Compile() + + vmConfig = &Config{ + ImportHandler: func(location common.Location) *bbq.Program { + switch location { + case fooLocation: + return fooProgram + case barLocation: + return barProgram + default: + assert.FailNow(t, "invalid location") + return nil + } + }, + ContractValueHandler: func(_ *Config, location common.Location) *CompositeValue { + switch location { + //case fooLocation: + // return fooContractValue + case barLocation: + return barContractValue + default: + assert.FailNow(t, fmt.Sprintf("invalid location %s", location)) + return nil + } + }, + } + + vm = NewVM(program, vmConfig) + + result, err := vm.Invoke("test") + require.NoError(t, err) + require.Equal(t, StringValue{Str: []byte("Successfully withdrew")}, result) + }) } func BenchmarkContractImport(b *testing.B) { From 0a776cca65a5fb9ea32c8816b483e50a185904a5 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 29 Mar 2023 10:52:55 -0700 Subject: [PATCH 19/89] Add transaction execution support --- runtime/bbq/bytecode_printer.go | 26 +++++- runtime/bbq/commons/constants.go | 7 +- runtime/bbq/commons/utils.go | 24 ++++++ runtime/bbq/compiler/compiler.go | 139 +++++++++++++++++++++++++------ runtime/bbq/vm/ft_test.go | 4 - runtime/bbq/vm/value_nil.go | 4 +- runtime/bbq/vm/vm.go | 23 ++++- runtime/bbq/vm/vm_test.go | 55 ++++++++++++ 8 files changed, 244 insertions(+), 38 deletions(-) diff --git a/runtime/bbq/bytecode_printer.go b/runtime/bbq/bytecode_printer.go index 31712c80e1..d01e5c3017 100644 --- a/runtime/bbq/bytecode_printer.go +++ b/runtime/bbq/bytecode_printer.go @@ -82,8 +82,25 @@ func (p *BytecodePrinter) printCode(codes []byte) { var typeName string typeName, i = p.getStringOperand(codes, i) - p.stringBuilder.WriteString(" " + fmt.Sprint(kind) + - " " + string(location.TypeID(nil, typeName))) + if location != nil { + typeName = string(location.TypeID(nil, typeName)) + } + + p.stringBuilder.WriteString(" " + fmt.Sprint(kind) + " " + typeName) + + case opcode.Cast: + var typeIndex int + var castType byte + typeIndex, i = p.getIntOperand(codes, i) + castType, i = p.getByteOperand(codes, i) + p.stringBuilder.WriteString(" " + fmt.Sprint(typeIndex) + " " + fmt.Sprint(int8(castType))) + + case opcode.Path: + var identifier string + var domain byte + domain, i = p.getByteOperand(codes, i) + identifier, i = p.getStringOperand(codes, i) + p.stringBuilder.WriteString(" " + fmt.Sprint(int8(domain)) + " " + identifier) // opcodes with no operands default: @@ -107,6 +124,11 @@ func (p *BytecodePrinter) getStringOperand(codes []byte, i int) (operand string, return operand, i + strLen } +func (*BytecodePrinter) getByteOperand(codes []byte, i int) (operand byte, endIndex int) { + byt := codes[i+1] + return byt, i + 1 +} + func (p *BytecodePrinter) printConstantPool(constants []*Constant) { p.stringBuilder.WriteString("-- Constant Pool --\n") diff --git a/runtime/bbq/commons/constants.go b/runtime/bbq/commons/constants.go index ce82e61360..70937c8174 100644 --- a/runtime/bbq/commons/constants.go +++ b/runtime/bbq/commons/constants.go @@ -24,8 +24,11 @@ import ( ) const ( - InitFunctionName = "init" - LogFunctionName = "log" + InitFunctionName = "init" + TransactionWrapperCompositeName = "transaction" + TransactionExecuteFunctionName = "transaction.execute" + TransactionPrepareFunctionName = "transaction.prepare" + LogFunctionName = "log" ) type CastType byte diff --git a/runtime/bbq/commons/utils.go b/runtime/bbq/commons/utils.go index 52ae19b2f8..1f54c67fa2 100644 --- a/runtime/bbq/commons/utils.go +++ b/runtime/bbq/commons/utils.go @@ -18,6 +18,13 @@ package commons +import ( + "bytes" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" +) + func TypeQualifiedName(typeName, functionName string) string { if typeName == "" { return functionName @@ -25,3 +32,20 @@ func TypeQualifiedName(typeName, functionName string) string { return typeName + "." + functionName } + +func LocationToBytes(location common.Location) ([]byte, error) { + var buf bytes.Buffer + enc := interpreter.CBOREncMode.NewStreamEncoder(&buf) + + err := interpreter.EncodeLocation(enc, location) + if err != nil { + return nil, err + } + + err = enc.Flush() + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index a0005ba227..7607c58651 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -19,7 +19,6 @@ package compiler import ( - "bytes" "math" "strings" @@ -243,18 +242,26 @@ func (c *Compiler) Compile() *bbq.Program { } } + compositeDeclarations := c.Program.CompositeDeclarations() + + transaction := c.Program.SoleTransactionDeclaration() + if transaction != nil { + desugaredTransaction := c.desugarTransaction(transaction) + compositeDeclarations = append(compositeDeclarations, desugaredTransaction) + } + // Reserve globals for functions/types before visiting their implementations. c.reserveGlobalVars( "", c.Program.FunctionDeclarations(), - c.Program.CompositeDeclarations(), + compositeDeclarations, ) // Compile declarations for _, declaration := range c.Program.FunctionDeclarations() { c.compileDeclaration(declaration) } - for _, declaration := range c.Program.CompositeDeclarations() { + for _, declaration := range compositeDeclarations { c.compileDeclaration(declaration) } @@ -781,8 +788,8 @@ func (c *Compiler) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunct switch kind { case common.DeclarationKindInitializer: c.compileInitializer(declaration) - case common.DeclarationKindDestructor: - // TODO: + case common.DeclarationKindDestructor, common.DeclarationKindPrepare: + c.compileDeclaration(declaration.FunctionDeclaration) default: // TODO: support other special functions panic(errors.NewUnreachableError()) @@ -806,14 +813,18 @@ func (c *Compiler) compileInitializer(declaration *ast.SpecialFunctionDeclaratio functionName = enclosingCompositeTypeName } - parameters := declaration.FunctionDeclaration.ParameterList.Parameters - parameterCount := len(parameters) + parameterCount := 0 + parameterList := declaration.FunctionDeclaration.ParameterList + if parameterList != nil { + parameterCount = len(parameterList.Parameters) + } + if parameterCount > math.MaxUint16 { panic(errors.NewDefaultUserError("invalid parameter count")) } function := c.addFunction(functionName, uint16(parameterCount)) - c.declareParameters(function, parameters, false) + c.declareParameters(function, parameterList, false) // Declare `self` self := c.currentFunction.declareLocal(sema.SelfIdentifier) @@ -823,8 +834,7 @@ func (c *Compiler) compileInitializer(declaration *ast.SpecialFunctionDeclaratio // i.e: `self = New()` enclosingCompositeType := c.compositeTypeStack.top() - location := enclosingCompositeType.Location - locationBytes, err := locationToBytes(location) + locationBytes, err := commons.LocationToBytes(enclosingCompositeType.Location) if err != nil { panic(err) } @@ -885,7 +895,7 @@ func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration declareReceiver := !c.compositeTypeStack.isEmpty() function := c.declareFunction(declaration, declareReceiver) - c.declareParameters(function, declaration.ParameterList.Parameters, declareReceiver) + c.declareParameters(function, declaration.ParameterList, declareReceiver) c.compileFunctionBlock(declaration.FunctionBlock) return } @@ -894,8 +904,13 @@ func (c *Compiler) declareFunction(declaration *ast.FunctionDeclaration, declare enclosingCompositeTypeName := c.enclosingCompositeTypeFullyQualifiedName() functionName := commons.TypeQualifiedName(enclosingCompositeTypeName, declaration.Identifier.Identifier) - functionType := c.Elaboration.FunctionDeclarationFunctionTypes[declaration] - parameterCount := len(functionType.Parameters) + parameterCount := 0 + + paramList := declaration.ParameterList + if paramList != nil { + parameterCount = len(paramList.Parameters) + } + if parameterCount >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid parameter count")) } @@ -915,10 +930,19 @@ func (c *Compiler) VisitCompositeDeclaration(declaration *ast.CompositeDeclarati }() // Compile members + hasInit := false for _, specialFunc := range declaration.Members.SpecialFunctions() { + if specialFunc.Kind == common.DeclarationKindInitializer { + hasInit = true + } c.compileDeclaration(specialFunc) } + // If the initializer is not declared, generate an empty initializer. + if !hasInit { + c.generateEmptyInit() + } + for _, function := range declaration.Members.Functions() { c.compileDeclaration(function) } @@ -1047,32 +1071,93 @@ func (c *Compiler) enclosingCompositeTypeFullyQualifiedName() string { return sb.String() } -func (c *Compiler) declareParameters(function *function, parameters []*ast.Parameter, declareReceiver bool) { +func (c *Compiler) declareParameters(function *function, paramList *ast.ParameterList, declareReceiver bool) { if declareReceiver { // Declare receiver as `self`. // Receiver is always at the zero-th index of params. function.declareLocal(sema.SelfIdentifier) } - for _, parameter := range parameters { - parameterName := parameter.Identifier.Identifier - function.declareLocal(parameterName) + if paramList != nil { + for _, parameter := range paramList.Parameters { + parameterName := parameter.Identifier.Identifier + function.declareLocal(parameterName) + } } } -func locationToBytes(location common.Location) ([]byte, error) { - var buf bytes.Buffer - enc := interpreter.CBOREncMode.NewStreamEncoder(&buf) +// desugarTransaction Convert a transaction into a composite type declaration, +// so the code-gen would seamlessly work without having special-case anything in compiler/vm. +func (c *Compiler) desugarTransaction(transaction *ast.TransactionDeclaration) *ast.CompositeDeclaration { - err := interpreter.EncodeLocation(enc, location) - if err != nil { - return nil, err + // TODO: This assumes the transaction program/elaboration is not cached. + // i.e: Modifies the elaboration. + // Handle this properly for cached transactions. + + members := []ast.Declaration{ + transaction.Execute.FunctionDeclaration, } - err = enc.Flush() - if err != nil { - return nil, err + if transaction.Prepare != nil { + members = append(members, transaction.Prepare) } - return buf.Bytes(), nil + compositeType := &sema.CompositeType{ + Location: nil, + Identifier: commons.TransactionWrapperCompositeName, + Kind: common.CompositeKindStructure, + } + + compositeDecl := ast.NewCompositeDeclaration( + c.memoryGauge, + ast.AccessNotSpecified, + common.CompositeKindStructure, + ast.NewIdentifier( + c.memoryGauge, + commons.TransactionWrapperCompositeName, + ast.EmptyPosition, + ), + nil, + ast.NewMembers(c.memoryGauge, members), + "", + ast.EmptyRange, + ) + + c.Elaboration.CompositeDeclarationTypes[compositeDecl] = compositeType + + return compositeDecl +} + +var emptyInitializer = func() *ast.SpecialFunctionDeclaration { + // This is created only once per compilation. So no need to meter memory. + + initializer := ast.NewFunctionDeclaration( + nil, + ast.AccessNotSpecified, + ast.NewIdentifier( + nil, + commons.InitFunctionName, + ast.EmptyPosition, + ), + nil, + nil, + ast.NewFunctionBlock( + nil, + ast.NewBlock(nil, nil, ast.EmptyRange), + nil, + nil, + ), + ast.Position{}, + "", + ) + + return ast.NewSpecialFunctionDeclaration( + nil, + common.DeclarationKindInitializer, + initializer, + ) +}() + +func (c *Compiler) generateEmptyInit() { + c.VisitSpecialFunctionDeclaration(emptyInitializer) } diff --git a/runtime/bbq/vm/ft_test.go b/runtime/bbq/vm/ft_test.go index 96c0b61143..63b0294d11 100644 --- a/runtime/bbq/vm/ft_test.go +++ b/runtime/bbq/vm/ft_test.go @@ -401,8 +401,6 @@ pub contract FlowToken: FungibleToken { pub resource Administrator { - init() {} - // createNewMinter // // Function that creates and returns a new minter resource @@ -458,8 +456,6 @@ pub contract FlowToken: FungibleToken { // pub resource Burner { - init() {} - // burnTokens // // Function that destroys a Vault instance, effectively burning the tokens. diff --git a/runtime/bbq/vm/value_nil.go b/runtime/bbq/vm/value_nil.go index 0bcc4a7ec4..2e5237910d 100644 --- a/runtime/bbq/vm/value_nil.go +++ b/runtime/bbq/vm/value_nil.go @@ -31,9 +31,9 @@ var _ Value = NilValue{} func (NilValue) isValue() {} -func (NilValue) StaticType(common.MemoryGauge) StaticType { +func (NilValue) StaticType(gauge common.MemoryGauge) StaticType { return interpreter.NewOptionalStaticType( - nil, + gauge, interpreter.PrimitiveStaticTypeNever, ) } diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 83f24ae28e..e51283bab0 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -20,7 +20,6 @@ package vm import ( "github.com/onflow/atree" - "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" @@ -164,6 +163,28 @@ func (vm *VM) InitializeContract(arguments ...Value) (*CompositeValue, error) { return contractValue, nil } +func (vm *VM) ExecuteTransaction() error { + // Create transaction value + transaction, err := vm.Invoke(commons.TransactionWrapperCompositeName) + if err != nil { + return err + } + + // Invoke 'prepare' + _, err = vm.Invoke(commons.TransactionPrepareFunctionName, transaction) + if err != nil { + return err + } + + // TODO: Invoke pre/post conditions + + // Invoke 'execute' + // TODO: pass auth accounts + _, err = vm.Invoke(commons.TransactionExecuteFunctionName, transaction) + + return err +} + func opReturnValue(vm *VM) { value := vm.pop() vm.popCallFrame() diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index 7d8f806629..59bf88394a 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -1482,3 +1482,58 @@ func TestNativeFunctions(t *testing.T) { require.Equal(t, StringValue{Str: []byte("Hello, World!")}, result) }) } + +func TestTransaction(t *testing.T) { + + t.Parallel() + + t.Run("simple", func(t *testing.T) { + + checker, err := ParseAndCheck(t, ` + transaction { + var a: String + prepare() { + self.a = "Hello!" + } + execute { + self.a = "Hello again!" + } + }`, + ) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + printProgram(program) + + vm := NewVM(program, nil) + + err = vm.ExecuteTransaction() + require.NoError(t, err) + + // Rerun the same again using internal functions, to get the access to the transaction value. + + transaction, err := vm.Invoke(commons.TransactionWrapperCompositeName) + require.NoError(t, err) + + require.IsType(t, &CompositeValue{}, transaction) + compositeValue := transaction.(*CompositeValue) + + // At the beginning, 'a' is uninitialized + assert.Nil(t, compositeValue.GetMember(vm.config, "a")) + + // Invoke 'prepare' + _, err = vm.Invoke(commons.TransactionPrepareFunctionName, transaction) + require.NoError(t, err) + + // Once 'prepare' is called, 'a' is initialized to "Hello!" + assert.Equal(t, StringValue{Str: []byte("Hello!")}, compositeValue.GetMember(vm.config, "a")) + + // Invoke 'execute' + _, err = vm.Invoke(commons.TransactionExecuteFunctionName, transaction) + require.NoError(t, err) + + // Once 'execute' is called, 'a' is initialized to "Hello, again!" + assert.Equal(t, StringValue{Str: []byte("Hello again!")}, compositeValue.GetMember(vm.config, "a")) + }) +} From 8dc619977a94638e20e4b1d62706272d5d40b096 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 11 Apr 2023 11:26:18 -0700 Subject: [PATCH 20/89] Add interface method invocation --- runtime/bbq/bytecode_printer.go | 5 + runtime/bbq/compiler/compiler.go | 33 +++- runtime/bbq/opcode/opcode.go | 1 + runtime/bbq/opcode/opcode_string.go | 19 +- runtime/bbq/vm/vm.go | 81 +++++++- runtime/bbq/vm/vm_test.go | 280 ++++++++++++++++++++++++++++ 6 files changed, 399 insertions(+), 20 deletions(-) diff --git a/runtime/bbq/bytecode_printer.go b/runtime/bbq/bytecode_printer.go index d01e5c3017..d0dc878011 100644 --- a/runtime/bbq/bytecode_printer.go +++ b/runtime/bbq/bytecode_printer.go @@ -102,6 +102,11 @@ func (p *BytecodePrinter) printCode(codes []byte) { identifier, i = p.getStringOperand(codes, i) p.stringBuilder.WriteString(" " + fmt.Sprint(int8(domain)) + " " + identifier) + case opcode.InvokeDynamic: + var funcName string + funcName, i = p.getStringOperand(codes, i) + p.stringBuilder.WriteString(" " + " " + funcName) + // opcodes with no operands default: // do nothing diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index 7607c58651..6a21b51655 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -633,22 +633,40 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio case *ast.MemberExpression: memberInfo := c.Elaboration.MemberExpressionMemberInfos[invokedExpr] typeName := memberInfo.AccessedType.QualifiedString() - funcName := commons.TypeQualifiedName(typeName, invokedExpr.Identifier.Identifier) + var funcName string invocationType := memberInfo.Member.TypeAnnotation.Type.(*sema.FunctionType) if invocationType.IsConstructor { + funcName = commons.TypeQualifiedName(typeName, invokedExpr.Identifier.Identifier) + // Calling a type constructor must be invoked statically. e.g: `SomeContract.Foo()`. // Load arguments c.loadArguments(expression) // Load function value c.emitVariableLoad(funcName) c.emit(opcode.Invoke) + return + } + + // Receiver is loaded first. So 'self' is always the zero-th argument. + c.compileExpression(invokedExpr.Expression) + // Load arguments + c.loadArguments(expression) + + if _, ok := memberInfo.AccessedType.(*sema.RestrictedType); ok { + funcName = invokedExpr.Identifier.Identifier + funcNameSizeFirst, funcNameSizeSecond := encodeUint16(uint16(len(funcName))) + + argsCountFirst, argsCountSecond := encodeUint16(uint16(len(expression.Arguments))) + + args := []byte{funcNameSizeFirst, funcNameSizeSecond} + args = append(args, []byte(funcName)...) + args = append(args, argsCountFirst, argsCountSecond) + + c.emit(opcode.InvokeDynamic, args...) } else { - // Receiver is loaded first. So 'self' is always the zero-th argument. - c.compileExpression(invokedExpr.Expression) - // Load arguments - c.loadArguments(expression) // Load function value + funcName = commons.TypeQualifiedName(typeName, invokedExpr.Identifier.Identifier) c.emitVariableLoad(funcName) c.emit(opcode.Invoke) } @@ -897,6 +915,11 @@ func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration c.declareParameters(function, declaration.ParameterList, declareReceiver) c.compileFunctionBlock(declaration.FunctionBlock) + + if !declaration.FunctionBlock.HasStatements() { + c.emit(opcode.Return) + } + return } diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index 939f788b9d..a0638bf0c0 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -54,6 +54,7 @@ const ( SetField Invoke + InvokeDynamic Destroy Transfer Cast diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index c9413a5b46..5f42c2c19e 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -34,18 +34,19 @@ func _() { _ = x[GetField-23] _ = x[SetField-24] _ = x[Invoke-25] - _ = x[Destroy-26] - _ = x[Transfer-27] - _ = x[Cast-28] - _ = x[New-29] - _ = x[Path-30] - _ = x[Drop-31] - _ = x[Dup-32] + _ = x[InvokeDynamic-26] + _ = x[Destroy-27] + _ = x[Transfer-28] + _ = x[Cast-29] + _ = x[New-30] + _ = x[Path-31] + _ = x[Drop-32] + _ = x[Dup-33] } -const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeDestroyTransferCastNewPathDropDup" +const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeInvokeDynamicDestroyTransferCastNewPathDropDup" -var _Opcode_index = [...]uint16{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 203, 211, 219, 225, 232, 240, 244, 247, 251, 255, 258} +var _Opcode_index = [...]uint16{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 203, 211, 219, 225, 238, 245, 253, 257, 260, 264, 268, 271} func (i Opcode) String() string { if i >= Opcode(len(_Opcode_index)-1) { diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index e51283bab0..470d9a1222 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -32,10 +32,11 @@ import ( ) type VM struct { - globals map[string]Value - callFrame *callFrame - stack []Value - config *Config + globals map[string]Value + callFrame *callFrame + stack []Value + config *Config + linkedGlobalsCache map[common.Location]LinkedGlobals } func NewVM(program *bbq.Program, conf *Config) *VM { @@ -60,8 +61,9 @@ func NewVM(program *bbq.Program, conf *Config) *VM { linkedGlobals := LinkGlobals(program, conf, linkedGlobalsCache) return &VM{ - globals: linkedGlobals.indexedGlobals, - config: conf, + globals: linkedGlobals.indexedGlobals, + linkedGlobalsCache: linkedGlobalsCache, + config: conf, } } @@ -191,6 +193,13 @@ func opReturnValue(vm *VM) { vm.push(value) } +var voidValue = VoidValue{} + +func opReturn(vm *VM) { + vm.popCallFrame() + vm.push(voidValue) +} + func opJump(vm *VM) { callFrame := vm.callFrame target := callFrame.getUint16() @@ -297,6 +306,24 @@ func opInvoke(vm *VM) { } } +func opInvokeDynamic(vm *VM) { + callframe := vm.callFrame + funcName := callframe.getString() + argsCount := callframe.getUint16() + + stackHeight := len(vm.stack) + receiver := vm.stack[stackHeight-int(argsCount)-1] + + compositeValue := receiver.(*CompositeValue) + qualifiedFuncName := commons.TypeQualifiedName(compositeValue.QualifiedIdentifier, funcName) + var functionValue = vm.lookupFunction(compositeValue.Location, qualifiedFuncName) + + parameterCount := int(functionValue.Function.ParameterCount) + arguments := vm.stack[stackHeight-parameterCount:] + vm.pushCallFrame(functionValue.Context, functionValue.Function, arguments) + vm.dropN(parameterCount) +} + func opDrop(vm *VM) { _ = vm.pop() } @@ -410,6 +437,8 @@ func (vm *VM) run() { switch op { case opcode.ReturnValue: opReturnValue(vm) + case opcode.Return: + opReturn(vm) case opcode.Jump: opJump(vm) case opcode.JumpIfFalse: @@ -438,6 +467,8 @@ func (vm *VM) run() { opSetGlobal(vm) case opcode.Invoke: opInvoke(vm) + case opcode.InvokeDynamic: + opInvokeDynamic(vm) case opcode.Drop: opDrop(vm) case opcode.Dup: @@ -507,6 +538,44 @@ func (vm *VM) initializeType(index uint16) interpreter.StaticType { return staticType } +func (vm *VM) lookupFunction(location common.Location, name string) FunctionValue { + // First check in current program. + value, ok := vm.globals[name] + if ok { + return value.(FunctionValue) + } + + // If not found, check in already linked imported functions. + linkedGlobals, ok := vm.linkedGlobalsCache[location] + if ok { + value, ok := linkedGlobals.indexedGlobals[name] + if ok { + return value.(FunctionValue) + } + } + + // If not found, link the function now, dynamically. + + // TODO: This currently link all functions in program, unnecessarily. Link only yhe requested function. + program := vm.config.ImportHandler(location) + ctx := NewContext(program, nil) + + indexedGlobals := make(map[string]Value, len(program.Functions)) + for _, function := range program.Functions { + indexedGlobals[function.Name] = FunctionValue{ + Function: function, + Context: ctx, + } + } + + vm.linkedGlobalsCache[location] = LinkedGlobals{ + context: ctx, + indexedGlobals: indexedGlobals, + } + + return indexedGlobals[name].(FunctionValue) +} + func decodeLocation(locationBytes []byte) common.Location { // TODO: is it possible to re-use decoders? dec := interpreter.CBORDecMode.NewByteStreamDecoder(locationBytes) diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index 59bf88394a..3303d24683 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -1537,3 +1537,283 @@ func TestTransaction(t *testing.T) { assert.Equal(t, StringValue{Str: []byte("Hello again!")}, compositeValue.GetMember(vm.config, "a")) }) } + +func TestInterfaceMethodCall(t *testing.T) { + + t.Parallel() + + location := common.NewAddressLocation( + nil, + common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + "MyContract", + ) + + importedChecker, err := ParseAndCheckWithOptions(t, + ` + contract MyContract { + struct Foo: Greetings { + var id : String + + init(_ id: String) { + self.id = id + } + + fun sayHello(_ id: Int): String { + return self.id + } + } + + struct interface Greetings { + fun sayHello(_ id: Int): String + } + + struct interface SomethingElse { + } + } + `, + ParseAndCheckOptions{ + Location: location, + }, + ) + require.NoError(t, err) + + importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importedProgram := importCompiler.Compile() + + vm := NewVM(importedProgram, nil) + importedContractValue, err := vm.InitializeContract() + require.NoError(t, err) + + checker, err := ParseAndCheckWithOptions(t, ` + import MyContract from 0x01 + + fun test(): String { + var r: {MyContract.Greetings} = MyContract.Foo("Hello from Foo!") + // first call must link + r.sayHello(1) + + // second call should pick from the cache + return r.sayHello(1) + }`, + + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + LocationHandler: singleIdentifierLocationResolver(t), + }, + }, + ) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.LocationHandler = singleIdentifierLocationResolver(t) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + return importedProgram + } + + program := comp.Compile() + + vmConfig := &Config{ + ImportHandler: func(location common.Location) *bbq.Program { + return importedProgram + }, + ContractValueHandler: func(conf *Config, location common.Location) *CompositeValue { + return importedContractValue + }, + } + + vm = NewVM(program, vmConfig) + result, err := vm.Invoke("test") + require.NoError(t, err) + + require.Equal(t, StringValue{Str: []byte("Hello from Foo!")}, result) +} + +func BenchmarkMethodCall(b *testing.B) { + + b.Run("interface method call", func(b *testing.B) { + + importedChecker, err := ParseAndCheckWithOptions(b, + ` + contract MyContract { + struct Foo: Greetings { + var id : String + + init(_ id: String) { + self.id = id + } + + fun sayHello(_ id: Int): String { + return self.id + } + } + + struct interface Greetings { + fun sayHello(_ id: Int): String + } + + struct interface SomethingElse { + } + } + `, + ParseAndCheckOptions{ + Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), + }, + ) + require.NoError(b, err) + + importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importedProgram := importCompiler.Compile() + + vm := NewVM(importedProgram, nil) + importedContractValue, err := vm.InitializeContract() + require.NoError(b, err) + + checker, err := ParseAndCheckWithOptions(b, ` + import MyContract from 0x01 + + fun test(count: Int) { + var r: {MyContract.Greetings} = MyContract.Foo("Hello from Foo!") + var i = 0 + while i < count { + i = i + 1 + r.sayHello(1) + } + }`, + + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + require.NoError(b, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + return importedProgram + } + + program := comp.Compile() + + vmConfig := &Config{ + ImportHandler: func(location common.Location) *bbq.Program { + return importedProgram + }, + ContractValueHandler: func(conf *Config, location common.Location) *CompositeValue { + return importedContractValue + }, + } + + vm = NewVM(program, vmConfig) + + value := IntValue{SmallInt: 10} + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + _, err := vm.Invoke("test", value) + require.NoError(b, err) + } + }) + + b.Run("concrete type method call", func(b *testing.B) { + + importedChecker, err := ParseAndCheckWithOptions(b, + ` + contract MyContract { + struct Foo: Greetings { + var id : String + + init(_ id: String) { + self.id = id + } + + fun sayHello(_ id: Int): String { + return self.id + } + } + + struct interface Greetings { + fun sayHello(_ id: Int): String + } + + struct interface SomethingElse { + } + } + `, + ParseAndCheckOptions{ + Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), + }, + ) + require.NoError(b, err) + + importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importedProgram := importCompiler.Compile() + + vm := NewVM(importedProgram, nil) + importedContractValue, err := vm.InitializeContract() + require.NoError(b, err) + + checker, err := ParseAndCheckWithOptions(b, ` + import MyContract from 0x01 + + fun test(count: Int) { + var r: MyContract.Foo = MyContract.Foo("Hello from Foo!") + var i = 0 + while i < count { + i = i + 1 + r.sayHello(1) + } + }`, + + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + require.NoError(b, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + return importedProgram + } + + program := comp.Compile() + + vmConfig := &Config{ + ImportHandler: func(location common.Location) *bbq.Program { + return importedProgram + }, + ContractValueHandler: func(conf *Config, location common.Location) *CompositeValue { + return importedContractValue + }, + } + + vm = NewVM(program, vmConfig) + + value := IntValue{SmallInt: 10} + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + _, err := vm.Invoke("test", value) + require.NoError(b, err) + } + }) +} From 804e4c441114d487c3db0a9cd87ddbfb10d9c7a5 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 11 Apr 2023 16:05:36 -0700 Subject: [PATCH 21/89] Run FT transfer account setup transaction --- runtime/bbq/compiler/compiler.go | 95 +++++++++++++++----------- runtime/bbq/opcode/opcode.go | 3 + runtime/bbq/opcode/opcode_string.go | 42 ++++++------ runtime/bbq/vm/ft_test.go | 101 ++++++++++++++++++++++------ runtime/bbq/vm/linker.go | 1 + runtime/bbq/vm/value_authaccount.go | 20 ++++++ runtime/bbq/vm/vm.go | 61 ++++++++++++++--- 7 files changed, 233 insertions(+), 90 deletions(-) diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index 6a21b51655..a9aefa36f1 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -43,16 +43,16 @@ type Compiler struct { currentFunction *function compositeTypeStack *Stack[*sema.CompositeType] - functions []*function - constants []*constant - globals map[string]*global - indexedImportedGlobals map[string]*global - importedGlobals []*global - loops []*loop - currentLoop *loop - staticTypes [][]byte - typesInPool map[sema.Type]uint16 - exportedImports []*bbq.Import + functions []*function + constants []*constant + globals map[string]*global + importedGlobals map[string]*global + usedImportedGlobals []*global + loops []*loop + currentLoop *loop + staticTypes [][]byte + typesInPool map[sema.Type]uint16 + exportedImports []*bbq.Import // TODO: initialize memoryGauge common.MemoryGauge @@ -67,14 +67,13 @@ func NewCompiler( elaboration *sema.Elaboration, ) *Compiler { return &Compiler{ - Program: program, - Elaboration: elaboration, - Config: &Config{}, - globals: map[string]*global{}, - indexedImportedGlobals: indexedNativeFunctions, - importedGlobals: nativeFunctions, - exportedImports: make([]*bbq.Import, 0), - typesInPool: map[sema.Type]uint16{}, + Program: program, + Elaboration: elaboration, + Config: &Config{}, + globals: map[string]*global{}, + importedGlobals: indexedNativeFunctions, + exportedImports: make([]*bbq.Import, 0), + typesInPool: map[sema.Type]uint16{}, compositeTypeStack: &Stack[*sema.CompositeType]{ elements: make([]*sema.CompositeType, 0), }, @@ -99,13 +98,14 @@ func (c *Compiler) findGlobal(name string) *global { } } - importedGlobal, ok := c.indexedImportedGlobals[name] + importedGlobal, ok := c.importedGlobals[name] if !ok { panic(errors.NewUnexpectedError("cannot find global declaration '%s'", name)) } // Add the 'importedGlobal' to 'globals' when they are used for the first time. // This way, the 'globals' would eventually have only the used imports. + // This is important since global indexes rely on this. // // If a global is found in imported globals, that means the index is not set. // So set an index and add it to the 'globals'. @@ -115,6 +115,17 @@ func (c *Compiler) findGlobal(name string) *global { } importedGlobal.index = uint16(count) c.globals[name] = importedGlobal + + // Also add it to the usedImportedGlobals. + // This is later used to export the imports, which is eventually used by the linker. + // Linker will link the imports in the same order as they are added here. + // i.e: same order as their indexes (preceded by globals defined in the current program). + // e.g: [global1, global2, ... [importedGlobal1, importedGlobal2, ...]]. + // Earlier we already reserved the indexes for the globals defined in the current program. + // (`reserveGlobalVars`) + + c.usedImportedGlobals = append(c.usedImportedGlobals, importedGlobal) + return importedGlobal } @@ -136,8 +147,7 @@ func (c *Compiler) addImportedGlobal(location common.Location, name string) *glo location: location, name: name, } - c.importedGlobals = append(c.importedGlobals, global) - c.indexedImportedGlobals[name] = global + c.importedGlobals[name] = global return global } @@ -253,6 +263,7 @@ func (c *Compiler) Compile() *bbq.Program { // Reserve globals for functions/types before visiting their implementations. c.reserveGlobalVars( "", + nil, c.Program.FunctionDeclarations(), compositeDeclarations, ) @@ -282,10 +293,22 @@ func (c *Compiler) Compile() *bbq.Program { func (c *Compiler) reserveGlobalVars( compositeTypeName string, - funcDecls []*ast.FunctionDeclaration, + specialFunctionDecls []*ast.SpecialFunctionDeclaration, + functionDecls []*ast.FunctionDeclaration, compositeDecls []*ast.CompositeDeclaration, ) { - for _, declaration := range funcDecls { + for _, declaration := range specialFunctionDecls { + switch declaration.Kind { + case common.DeclarationKindDestructor, + common.DeclarationKindPrepare: + // Important: All special functions visited within `VisitSpecialFunctionDeclaration` + // must be also visited here. And must be visited only them. e.g: Don't visit inits. + funcName := commons.TypeQualifiedName(compositeTypeName, declaration.FunctionDeclaration.Identifier.Identifier) + c.addGlobal(funcName) + } + } + + for _, declaration := range functionDecls { funcName := commons.TypeQualifiedName(compositeTypeName, declaration.Identifier.Identifier) c.addGlobal(funcName) } @@ -308,6 +331,7 @@ func (c *Compiler) reserveGlobalVars( // Define globals for functions before visiting function bodies c.reserveGlobalVars( qualifiedTypeName, + declaration.Members.SpecialFunctions(), declaration.Members.Functions(), declaration.Members.Composites(), ) @@ -338,20 +362,11 @@ func (c *Compiler) exportTypes() [][]byte { func (c *Compiler) exportImports() []*bbq.Import { exportedImports := make([]*bbq.Import, 0) - - for _, importedGlobal := range c.importedGlobals { - name := importedGlobal.name - - // Export only used imports - if _, ok := c.globals[name]; !ok { - continue - } - + for _, importedGlobal := range c.usedImportedGlobals { bbqImport := &bbq.Import{ Location: importedGlobal.location, - Name: name, + Name: importedGlobal.name, } - exportedImports = append(exportedImports, bbqImport) } @@ -563,8 +578,8 @@ func (c *Compiler) VisitBoolExpression(expression *ast.BoolExpression) (_ struct } func (c *Compiler) VisitNilExpression(_ *ast.NilExpression) (_ struct{}) { - // TODO - panic(errors.NewUnreachableError()) + c.emit(opcode.Nil) + return } func (c *Compiler) VisitIntegerExpression(expression *ast.IntegerExpression) (_ struct{}) { @@ -728,7 +743,7 @@ var intBinaryOpcodes = [...]opcode.Opcode{ ast.OperationMul: opcode.IntMultiply, ast.OperationDiv: opcode.IntDivide, ast.OperationMod: opcode.IntMod, - ast.OperationEqual: opcode.IntEqual, + ast.OperationEqual: opcode.Equal, ast.OperationNotEqual: opcode.IntNotEqual, ast.OperationLess: opcode.IntLess, ast.OperationLessEqual: opcode.IntLessOrEqual, @@ -1117,8 +1132,10 @@ func (c *Compiler) desugarTransaction(transaction *ast.TransactionDeclaration) * // i.e: Modifies the elaboration. // Handle this properly for cached transactions. - members := []ast.Declaration{ - transaction.Execute.FunctionDeclaration, + var members []ast.Declaration + + if transaction.Execute != nil { + members = append(members, transaction.Execute.FunctionDeclaration) } if transaction.Prepare != nil { diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index a0638bf0c0..22b3fadb07 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -42,6 +42,8 @@ const ( IntLessOrEqual IntGreaterOrEqual + Equal + GetConstant True False @@ -61,6 +63,7 @@ const ( New Path + Nil Drop Dup diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index 5f42c2c19e..81f5ddcd6f 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -24,29 +24,31 @@ func _() { _ = x[IntGreater-13] _ = x[IntLessOrEqual-14] _ = x[IntGreaterOrEqual-15] - _ = x[GetConstant-16] - _ = x[True-17] - _ = x[False-18] - _ = x[GetLocal-19] - _ = x[SetLocal-20] - _ = x[GetGlobal-21] - _ = x[SetGlobal-22] - _ = x[GetField-23] - _ = x[SetField-24] - _ = x[Invoke-25] - _ = x[InvokeDynamic-26] - _ = x[Destroy-27] - _ = x[Transfer-28] - _ = x[Cast-29] - _ = x[New-30] - _ = x[Path-31] - _ = x[Drop-32] - _ = x[Dup-33] + _ = x[Equal-16] + _ = x[GetConstant-17] + _ = x[True-18] + _ = x[False-19] + _ = x[GetLocal-20] + _ = x[SetLocal-21] + _ = x[GetGlobal-22] + _ = x[SetGlobal-23] + _ = x[GetField-24] + _ = x[SetField-25] + _ = x[Invoke-26] + _ = x[InvokeDynamic-27] + _ = x[Destroy-28] + _ = x[Transfer-29] + _ = x[Cast-30] + _ = x[New-31] + _ = x[Path-32] + _ = x[Nil-33] + _ = x[Drop-34] + _ = x[Dup-35] } -const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeInvokeDynamicDestroyTransferCastNewPathDropDup" +const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeInvokeDynamicDestroyTransferCastNewPathNilDropDup" -var _Opcode_index = [...]uint16{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 160, 164, 169, 177, 185, 194, 203, 211, 219, 225, 238, 245, 253, 257, 260, 264, 268, 271} +var _Opcode_index = [...]uint16{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 154, 165, 169, 174, 182, 190, 199, 208, 216, 224, 230, 243, 250, 258, 262, 265, 269, 272, 276, 279} func (i Opcode) String() string { if i >= Opcode(len(_Opcode_index)-1) { diff --git a/runtime/bbq/vm/ft_test.go b/runtime/bbq/vm/ft_test.go index 63b0294d11..8db3644be2 100644 --- a/runtime/bbq/vm/ft_test.go +++ b/runtime/bbq/vm/ft_test.go @@ -23,6 +23,7 @@ import ( "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/bbq" "github.com/onflow/cadence/runtime/common" + "github.com/stretchr/testify/assert" "testing" "github.com/stretchr/testify/require" @@ -49,6 +50,7 @@ func TestFTTransfer(t *testing.T) { //require.NoError(t, err) // Deploy FlowToken Contract + flowTokenLocation := common.NewAddressLocation(nil, common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, "FlowToken") flowTokenChecker, err := ParseAndCheckWithOptions(t, realFlowContract, ParseAndCheckOptions{ @@ -92,26 +94,28 @@ func TestFTTransfer(t *testing.T) { flowTokenContractValue, err := vm.InitializeContract(authAcount) require.NoError(t, err) - // Run script - - checker, err := ParseAndCheckWithOptions(t, ` - import FungibleToken from 0x01 + // Run setup account transaction - fun test(): String { - return "hello" - } - `, + checker, err := ParseAndCheckWithOptions(t, realSetupFlowTokenAccountTransaction, ParseAndCheckOptions{ Config: &sema.Config{ ImportHandler: func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { - switch location { + require.IsType(t, common.AddressLocation{}, location) + addressLocation := location.(common.AddressLocation) + var elaboration *sema.Elaboration + + switch addressLocation { + case ftLocation: + elaboration = ftChecker.Elaboration case flowTokenLocation: - return sema.ElaborationImport{ - Elaboration: flowTokenChecker.Elaboration, - }, nil + elaboration = flowTokenChecker.Elaboration default: - return nil, fmt.Errorf("cannot find contract in location %s", location) + assert.FailNow(t, "invalid location") } + + return sema.ElaborationImport{ + Elaboration: elaboration, + }, nil }, LocationHandler: singleIdentifierLocationResolver(t), }, @@ -120,26 +124,53 @@ func TestFTTransfer(t *testing.T) { require.NoError(t, err) comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.LocationHandler = singleIdentifierLocationResolver(t) comp.Config.ImportHandler = func(location common.Location) *bbq.Program { - return flowTokenProgram + switch location { + case ftLocation: + return ftProgram + case flowTokenLocation: + return flowTokenProgram + default: + assert.FailNow(t, "invalid location") + return nil + } } program := comp.Compile() + printProgram(program) vmConfig := &Config{ ImportHandler: func(location common.Location) *bbq.Program { - return ftProgram + switch location { + case ftLocation: + return ftProgram + case flowTokenLocation: + return flowTokenProgram + default: + assert.FailNow(t, "invalid location") + return nil + } }, - ContractValueHandler: func(*Config, common.Location) *CompositeValue { - return flowTokenContractValue + ContractValueHandler: func(_ *Config, location common.Location) *CompositeValue { + switch location { + case ftLocation: + // interface + return nil + case flowTokenLocation: + return flowTokenContractValue + default: + assert.FailNow(t, "invalid location") + return nil + } }, } vm = NewVM(program, vmConfig) - result, err := vm.Invoke("test") + authorizer := NewAuthAccountValue() + err = vm.ExecuteTransaction(authorizer) require.NoError(t, err) - require.Equal(t, StringValue{Str: []byte("global function of the imported program")}, result) } const realFungibleTokenContractInterface = ` @@ -395,7 +426,7 @@ pub contract FlowToken: FungibleToken { // and store the returned Vault in their storage in order to allow their // account to be able to receive deposits of this token type. // - pub fun createEmptyVault(): @FungibleToken.Vault { + pub fun createEmptyVault(): @Vault { return <-create Vault(balance: 0) } @@ -503,3 +534,33 @@ pub contract FlowToken: FungibleToken { } } ` + +const realSetupFlowTokenAccountTransaction = ` +import FungibleToken from 0x1 +import FlowToken from 0x1 + +transaction { + + prepare(signer: AuthAccount) { + + if signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) == nil { + // Create a new flowToken Vault and put it in storage + signer.save(<-FlowToken.createEmptyVault(), to: /storage/flowTokenVault) + + // Create a public capability to the Vault that only exposes + // the deposit function through the Receiver interface + signer.link<&FlowToken.Vault{FungibleToken.Receiver}>( + /public/flowTokenReceiver, + target: /storage/flowTokenVault + ) + + // Create a public capability to the Vault that only exposes + // the balance field through the Balance interface + signer.link<&FlowToken.Vault{FungibleToken.Balance}>( + /public/flowTokenBalance, + target: /storage/flowTokenVault + ) + } + } +} +` diff --git a/runtime/bbq/vm/linker.go b/runtime/bbq/vm/linker.go index 6b5edef52e..78110304a6 100644 --- a/runtime/bbq/vm/linker.go +++ b/runtime/bbq/vm/linker.go @@ -113,6 +113,7 @@ func LinkGlobals( // Globals of the current program are added first. // This is the same order as they are added in the compiler. + // e.g: [global1, global2, ... [importedGlobal1, importedGlobal2, ...]] ctx.Globals = globals ctx.Globals = append(ctx.Globals, importedGlobals...) diff --git a/runtime/bbq/vm/value_authaccount.go b/runtime/bbq/vm/value_authaccount.go index ab6ae68f51..cdc1b7ad51 100644 --- a/runtime/bbq/vm/value_authaccount.go +++ b/runtime/bbq/vm/value_authaccount.go @@ -19,10 +19,21 @@ package vm import ( + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" ) +func NewAuthAccountValue() *CompositeValue { + return &CompositeValue{ + Location: nil, + QualifiedIdentifier: sema.AuthAccountType.QualifiedIdentifier(), + typeID: sema.AuthAccountType.ID(), + staticType: interpreter.PrimitiveStaticTypeAuthAccount, + Kind: common.CompositeKindStructure, + } +} + // members func init() { @@ -45,4 +56,13 @@ func init() { return NilValue{} }, }) + + // AuthAccount.borrow + RegisterTypeBoundFunction(typeName, sema.AuthAccountBorrowField, NativeFunctionValue{ + ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), + Function: func(value ...Value) Value { + // TODO: + return NilValue{} + }, + }) } diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 470d9a1222..611742f9a9 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -127,9 +127,13 @@ func (vm *VM) Invoke(name string, arguments ...Value) (Value, error) { return nil, errors.NewDefaultUserError("unknown function '%s'", name) } + return vm.invoke(function, arguments) +} + +func (vm *VM) invoke(function Value, arguments []Value) (Value, error) { functionValue, ok := function.(FunctionValue) if !ok { - return nil, errors.NewDefaultUserError("not invocable: %s", name) + return nil, errors.NewDefaultUserError("not invocable") } if len(arguments) != int(functionValue.Function.ParameterCount) { @@ -165,26 +169,33 @@ func (vm *VM) InitializeContract(arguments ...Value) (*CompositeValue, error) { return contractValue, nil } -func (vm *VM) ExecuteTransaction() error { +func (vm *VM) ExecuteTransaction(signers ...Value) error { // Create transaction value transaction, err := vm.Invoke(commons.TransactionWrapperCompositeName) if err != nil { return err } - // Invoke 'prepare' - _, err = vm.Invoke(commons.TransactionPrepareFunctionName, transaction) - if err != nil { - return err + args := []Value{transaction} + args = append(args, signers...) + + // Invoke 'prepare', if exists. + if prepare, ok := vm.globals[commons.TransactionPrepareFunctionName]; ok { + _, err = vm.invoke(prepare, args) + if err != nil { + return err + } } // TODO: Invoke pre/post conditions - // Invoke 'execute' - // TODO: pass auth accounts - _, err = vm.Invoke(commons.TransactionExecuteFunctionName, transaction) + // Invoke 'execute', if exists. + if execute, ok := vm.globals[commons.TransactionExecuteFunctionName]; ok { + _, err = vm.invoke(execute, args) + return err + } - return err + return nil } func opReturnValue(vm *VM) { @@ -420,6 +431,28 @@ func opPath(vm *VM) { vm.push(value) } +func opCast(vm *VM) { + callframe := vm.callFrame + value := vm.pop() + targetType := vm.loadType() + castType := commons.CastType(callframe.getByte()) + + // TODO: + _ = castType + _ = targetType + + vm.push(value) +} + +func opNil(vm *VM) { + vm.push(NilValue{}) +} + +func opEqual(vm *VM) { + left, right := vm.peekPop() + vm.replaceTop(BoolValue(left == right)) +} + func (vm *VM) run() { for { @@ -485,8 +518,14 @@ func (vm *VM) run() { opDestroy(vm) case opcode.Path: opPath(vm) + case opcode.Cast: + opCast(vm) + case opcode.Nil: + opNil(vm) + case opcode.Equal: + opEqual(vm) default: - panic(errors.NewUnreachableError()) + panic(errors.NewUnexpectedError("cannot execute opcode '%s'", op.String())) } // Faster in Go <1.19: From 8611b11acace20481ba61408ce841c714a0d7edc Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 11 Apr 2023 17:03:23 -0700 Subject: [PATCH 22/89] Run FT transfer transaction --- runtime/bbq/commons/constants.go | 1 + runtime/bbq/compiler/compiler.go | 23 +++- runtime/bbq/compiler/native_functions.go | 2 +- runtime/bbq/vm/ft_test.go | 159 +++++++++++++++++------ runtime/bbq/vm/native_functions.go | 15 ++- runtime/stdlib/account.go | 4 +- runtime/stdlib/panic.go | 4 +- 7 files changed, 157 insertions(+), 51 deletions(-) diff --git a/runtime/bbq/commons/constants.go b/runtime/bbq/commons/constants.go index 70937c8174..8dc15fd217 100644 --- a/runtime/bbq/commons/constants.go +++ b/runtime/bbq/commons/constants.go @@ -29,6 +29,7 @@ const ( TransactionExecuteFunctionName = "transaction.execute" TransactionPrepareFunctionName = "transaction.prepare" LogFunctionName = "log" + PanicFunctionName = "panic" ) type CastType byte diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index a9aefa36f1..8181c93750 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -731,9 +731,20 @@ func (c *Compiler) VisitUnaryExpression(expression *ast.UnaryExpression) (_ stru func (c *Compiler) VisitBinaryExpression(expression *ast.BinaryExpression) (_ struct{}) { c.compileExpression(expression.Left) - c.compileExpression(expression.Right) // TODO: add support for other types - c.emit(intBinaryOpcodes[expression.Operation]) + + switch expression.Operation { + case ast.OperationNilCoalesce: + c.emit(opcode.Nil) + c.emit(opcode.Equal) + jumpToEnd := c.emitUndefinedJump(opcode.JumpIfFalse) + c.compileExpression(expression.Right) + c.patchJump(jumpToEnd) + default: + c.compileExpression(expression.Right) + c.emit(intBinaryOpcodes[expression.Operation]) + } + return } @@ -1126,11 +1137,13 @@ func (c *Compiler) declareParameters(function *function, paramList *ast.Paramete // desugarTransaction Convert a transaction into a composite type declaration, // so the code-gen would seamlessly work without having special-case anything in compiler/vm. -func (c *Compiler) desugarTransaction(transaction *ast.TransactionDeclaration) *ast.CompositeDeclaration { +func (c *Compiler) desugarTransaction(transaction *ast.TransactionDeclaration) (*ast.CompositeDeclaration, []*ast.VariableDeclaration) { // TODO: This assumes the transaction program/elaboration is not cached. - // i.e: Modifies the elaboration. - // Handle this properly for cached transactions. + // i.e: Modifies the elaboration. + // Handle this properly for cached transactions. + + // TODO: add pre/post conditions var members []ast.Declaration diff --git a/runtime/bbq/compiler/native_functions.go b/runtime/bbq/compiler/native_functions.go index ef51fd6803..98a105cd8b 100644 --- a/runtime/bbq/compiler/native_functions.go +++ b/runtime/bbq/compiler/native_functions.go @@ -32,9 +32,9 @@ var builtinTypes = []sema.Type{ sema.AuthAccountType, } -// TODO: Maybe var stdlibFunctions = []string{ commons.LogFunctionName, + commons.PanicFunctionName, } func init() { diff --git a/runtime/bbq/vm/ft_test.go b/runtime/bbq/vm/ft_test.go index 8db3644be2..415e48cd2d 100644 --- a/runtime/bbq/vm/ft_test.go +++ b/runtime/bbq/vm/ft_test.go @@ -23,6 +23,8 @@ import ( "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/bbq" "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/stdlib" "github.com/stretchr/testify/assert" "testing" @@ -35,7 +37,8 @@ import ( func TestFTTransfer(t *testing.T) { - // Deploy FT Contract + // ---- Deploy FT Contract ----- + ftLocation := common.NewAddressLocation(nil, common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, "FungibleToken") ftChecker, err := ParseAndCheckWithOptions(t, realFungibleTokenContractInterface, ParseAndCheckOptions{Location: ftLocation}, @@ -49,7 +52,7 @@ func TestFTTransfer(t *testing.T) { //importedContractValue, err := vm.InitializeContract() //require.NoError(t, err) - // Deploy FlowToken Contract + // ----- Deploy FlowToken Contract ----- flowTokenLocation := common.NewAddressLocation(nil, common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, "FlowToken") flowTokenChecker, err := ParseAndCheckWithOptions(t, realFlowContract, @@ -78,54 +81,43 @@ func TestFTTransfer(t *testing.T) { } flowTokenProgram := flowTokenCompiler.Compile() + printProgram(flowTokenProgram) - vm := NewVM(flowTokenProgram, nil) + flowTokenVM := NewVM(flowTokenProgram, nil) authAcount := NewCompositeValue( nil, "AuthAccount", common.CompositeKindStructure, common.Address{}, - vm.config.Storage, + flowTokenVM.config.Storage, ) - printProgram(flowTokenProgram) - - flowTokenContractValue, err := vm.InitializeContract(authAcount) + flowTokenContractValue, err := flowTokenVM.InitializeContract(authAcount) require.NoError(t, err) - // Run setup account transaction + // ----- Run setup account transaction ----- - checker, err := ParseAndCheckWithOptions(t, realSetupFlowTokenAccountTransaction, - ParseAndCheckOptions{ - Config: &sema.Config{ - ImportHandler: func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { - require.IsType(t, common.AddressLocation{}, location) - addressLocation := location.(common.AddressLocation) - var elaboration *sema.Elaboration + checkerImportHandler := func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { + require.IsType(t, common.AddressLocation{}, location) + addressLocation := location.(common.AddressLocation) + var elaboration *sema.Elaboration - switch addressLocation { - case ftLocation: - elaboration = ftChecker.Elaboration - case flowTokenLocation: - elaboration = flowTokenChecker.Elaboration - default: - assert.FailNow(t, "invalid location") - } + switch addressLocation { + case ftLocation: + elaboration = ftChecker.Elaboration + case flowTokenLocation: + elaboration = flowTokenChecker.Elaboration + default: + assert.FailNow(t, "invalid location") + } - return sema.ElaborationImport{ - Elaboration: elaboration, - }, nil - }, - LocationHandler: singleIdentifierLocationResolver(t), - }, - }, - ) - require.NoError(t, err) + return sema.ElaborationImport{ + Elaboration: elaboration, + }, nil + } - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - comp.Config.LocationHandler = singleIdentifierLocationResolver(t) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + compilerImportHandler := func(location common.Location) *bbq.Program { switch location { case ftLocation: return ftProgram @@ -137,9 +129,6 @@ func TestFTTransfer(t *testing.T) { } } - program := comp.Compile() - printProgram(program) - vmConfig := &Config{ ImportHandler: func(location common.Location) *bbq.Program { switch location { @@ -166,10 +155,67 @@ func TestFTTransfer(t *testing.T) { }, } - vm = NewVM(program, vmConfig) + setupTxChecker, err := ParseAndCheckWithOptions( + t, + realSetupFlowTokenAccountTransaction, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: checkerImportHandler, + LocationHandler: singleIdentifierLocationResolver(t), + }, + }, + ) + require.NoError(t, err) + + setupTxCompiler := compiler.NewCompiler(setupTxChecker.Program, setupTxChecker.Elaboration) + setupTxCompiler.Config.LocationHandler = singleIdentifierLocationResolver(t) + setupTxCompiler.Config.ImportHandler = compilerImportHandler + + program := setupTxCompiler.Compile() + printProgram(program) + + setupTxVM := NewVM(program, vmConfig) authorizer := NewAuthAccountValue() - err = vm.ExecuteTransaction(authorizer) + err = setupTxVM.ExecuteTransaction(authorizer) + require.NoError(t, err) + + // ----- Run token transfer transaction ----- + + // Only need for to make the checker happy + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.PanicFunction) + baseValueActivation.DeclareValue(stdlib.NewStandardLibraryFunction( + "getAccount", + stdlib.GetAccountFunctionType, + "", + func(invocation interpreter.Invocation) interpreter.Value { + return nil + }, + )) + + tokenTransferTxChecker, err := ParseAndCheckWithOptions( + t, + realFlowTokenTransferTransaction, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: checkerImportHandler, + BaseValueActivation: baseValueActivation, + LocationHandler: singleIdentifierLocationResolver(t), + }, + }, + ) + require.NoError(t, err) + + tokenTransferTxCompiler := compiler.NewCompiler(tokenTransferTxChecker.Program, tokenTransferTxChecker.Elaboration) + tokenTransferTxCompiler.Config.LocationHandler = singleIdentifierLocationResolver(t) + tokenTransferTxCompiler.Config.ImportHandler = compilerImportHandler + + tokenTransferTxProgram := tokenTransferTxCompiler.Compile() + printProgram(tokenTransferTxProgram) + + tokenTransferTxVM := NewVM(tokenTransferTxProgram, vmConfig) + err = tokenTransferTxVM.ExecuteTransaction(authorizer) require.NoError(t, err) } @@ -564,3 +610,36 @@ transaction { } } ` + +const realFlowTokenTransferTransaction = ` +import FungibleToken from 0x1 +import FlowToken from 0x1 + +transaction(amount: Int, to: Address) { + + // The Vault resource that holds the tokens that are being transferred + let sentVault: @FungibleToken.Vault + + prepare(signer: AuthAccount) { + + // Get a reference to the signer's stored vault + let vaultRef = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) + ?? panic("Could not borrow reference to the owner's Vault!") + + // Withdraw tokens from the signer's stored vault + self.sentVault <- vaultRef.withdraw(amount: amount) + } + + execute { + + // Get a reference to the recipient's Receiver + let receiverRef = getAccount(to) + .getCapability(/public/flowTokenReceiver) + .borrow<&{FungibleToken.Receiver}>() + ?? panic("Could not borrow receiver reference to the recipient's Vault") + + // Deposit the withdrawn tokens in the recipient's receiver + receiverRef.deposit(from: <-self.sentVault) + } +} +` diff --git a/runtime/bbq/vm/native_functions.go b/runtime/bbq/vm/native_functions.go index e784fcee4d..6ca8bdc27f 100644 --- a/runtime/bbq/vm/native_functions.go +++ b/runtime/bbq/vm/native_functions.go @@ -20,8 +20,8 @@ package vm import ( "fmt" - "github.com/onflow/cadence/runtime/bbq/commons" + "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/stdlib" @@ -53,4 +53,17 @@ func init() { return VoidValue{} }, }) + + RegisterFunction(commons.PanicFunctionName, NativeFunctionValue{ + ParameterCount: len(stdlib.PanicFunctionType.Parameters), + Function: func(arguments ...Value) Value { + // TODO: Properly implement + messageValue, ok := arguments[0].(StringValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + panic(string(messageValue.Str)) + }, + }) } diff --git a/runtime/stdlib/account.go b/runtime/stdlib/account.go index d23b4f4e6d..aa7e087dd4 100644 --- a/runtime/stdlib/account.go +++ b/runtime/stdlib/account.go @@ -1872,7 +1872,7 @@ const getAccountFunctionDocString = ` Returns the public account for the given address ` -var getAccountFunctionType = &sema.FunctionType{ +var GetAccountFunctionType = &sema.FunctionType{ Parameters: []*sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, @@ -1899,7 +1899,7 @@ type PublicAccountHandler interface { func NewGetAccountFunction(handler PublicAccountHandler) StandardLibraryValue { return NewStandardLibraryFunction( "getAccount", - getAccountFunctionType, + GetAccountFunctionType, getAccountFunctionDocString, func(invocation interpreter.Invocation) interpreter.Value { accountAddress, ok := invocation.Arguments[0].(interpreter.AddressValue) diff --git a/runtime/stdlib/panic.go b/runtime/stdlib/panic.go index 30fcb1ae22..1ae3f09db9 100644 --- a/runtime/stdlib/panic.go +++ b/runtime/stdlib/panic.go @@ -43,7 +43,7 @@ const panicFunctionDocString = ` Terminates the program unconditionally and reports a message which explains why the unrecoverable error occurred. ` -var panicFunctionType = &sema.FunctionType{ +var PanicFunctionType = &sema.FunctionType{ Parameters: []*sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, @@ -58,7 +58,7 @@ var panicFunctionType = &sema.FunctionType{ var PanicFunction = NewStandardLibraryFunction( "panic", - panicFunctionType, + PanicFunctionType, panicFunctionDocString, func(invocation interpreter.Invocation) interpreter.Value { messageValue, ok := invocation.Arguments[0].(*interpreter.StringValue) From 2144c4346738cf623fef050167552309d22ed22f Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 12 Apr 2023 16:23:31 -0700 Subject: [PATCH 23/89] Support transaction parameters --- runtime/bbq/commons/constants.go | 6 + runtime/bbq/compiler/compiler.go | 171 +++++++++++++++++++++-- runtime/bbq/compiler/native_functions.go | 3 + runtime/bbq/program.go | 1 + runtime/bbq/variable.go | 23 +++ runtime/bbq/vm/ft_test.go | 10 +- runtime/bbq/vm/linker.go | 4 + runtime/bbq/vm/native_functions.go | 9 ++ runtime/bbq/vm/value_address.go | 45 ++++++ runtime/bbq/vm/value_capability.go | 39 ++++++ runtime/bbq/vm/value_function.go | 1 + runtime/bbq/vm/value_publicaccount.go | 39 ++++++ runtime/bbq/vm/vm.go | 14 +- runtime/bbq/vm/vm_test.go | 57 +++++++- 14 files changed, 402 insertions(+), 20 deletions(-) create mode 100644 runtime/bbq/variable.go create mode 100644 runtime/bbq/vm/value_address.go create mode 100644 runtime/bbq/vm/value_capability.go create mode 100644 runtime/bbq/vm/value_publicaccount.go diff --git a/runtime/bbq/commons/constants.go b/runtime/bbq/commons/constants.go index 8dc15fd217..b9b87db60a 100644 --- a/runtime/bbq/commons/constants.go +++ b/runtime/bbq/commons/constants.go @@ -30,6 +30,12 @@ const ( TransactionPrepareFunctionName = "transaction.prepare" LogFunctionName = "log" PanicFunctionName = "panic" + GetAccountFunctionName = "getAccount" + + // Names used by generated constructs + + ProgramInitFunctionName = "$_init_" + TransactionGeneratedParamPrefix = "$_param_" ) type CastType byte diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index 8181c93750..ae2208f124 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -253,23 +253,30 @@ func (c *Compiler) Compile() *bbq.Program { } compositeDeclarations := c.Program.CompositeDeclarations() + variableDeclarations := c.Program.VariableDeclarations() + functionDeclarations := c.Program.FunctionDeclarations() transaction := c.Program.SoleTransactionDeclaration() if transaction != nil { - desugaredTransaction := c.desugarTransaction(transaction) + desugaredTransaction, desugaredTransactionParams, initFunction := c.desugarTransaction(transaction) compositeDeclarations = append(compositeDeclarations, desugaredTransaction) + variableDeclarations = append(variableDeclarations, desugaredTransactionParams...) + if initFunction != nil { + functionDeclarations = append(functionDeclarations, initFunction) + } } // Reserve globals for functions/types before visiting their implementations. c.reserveGlobalVars( "", + variableDeclarations, nil, - c.Program.FunctionDeclarations(), + functionDeclarations, compositeDeclarations, ) // Compile declarations - for _, declaration := range c.Program.FunctionDeclarations() { + for _, declaration := range functionDeclarations { c.compileDeclaration(declaration) } for _, declaration := range compositeDeclarations { @@ -281,6 +288,7 @@ func (c *Compiler) Compile() *bbq.Program { types := c.exportTypes() imports := c.exportImports() contract := c.exportContract() + variables := c.exportVariables(variableDeclarations) return &bbq.Program{ Functions: functions, @@ -288,15 +296,21 @@ func (c *Compiler) Compile() *bbq.Program { Types: types, Imports: imports, Contract: contract, + Variables: variables, } } func (c *Compiler) reserveGlobalVars( compositeTypeName string, + variableDecls []*ast.VariableDeclaration, specialFunctionDecls []*ast.SpecialFunctionDeclaration, functionDecls []*ast.FunctionDeclaration, compositeDecls []*ast.CompositeDeclaration, ) { + for _, declaration := range variableDecls { + c.addGlobal(declaration.Identifier.Identifier) + } + for _, declaration := range specialFunctionDecls { switch declaration.Kind { case common.DeclarationKindDestructor, @@ -331,6 +345,7 @@ func (c *Compiler) reserveGlobalVars( // Define globals for functions before visiting function bodies c.reserveGlobalVars( qualifiedTypeName, + nil, declaration.Members.SpecialFunctions(), declaration.Members.Functions(), declaration.Members.Composites(), @@ -390,6 +405,19 @@ func (c *Compiler) exportFunctions() []*bbq.Function { return functions } +func (c *Compiler) exportVariables(variableDecls []*ast.VariableDeclaration) []*bbq.Variable { + variables := make([]*bbq.Variable, 0, len(c.functions)) + for _, varDecl := range variableDecls { + variables = append( + variables, + &bbq.Variable{ + Name: varDecl.Identifier.Identifier, + }, + ) + } + return variables +} + func (c *Compiler) exportContract() *bbq.Contract { var location common.Location var name string @@ -537,9 +565,17 @@ func (c *Compiler) VisitAssignmentStatement(statement *ast.AssignmentStatement) switch target := statement.Target.(type) { case *ast.IdentifierExpression: - local := c.currentFunction.findLocal(target.Identifier.Identifier) - first, second := encodeUint16(local.index) - c.emit(opcode.SetLocal, first, second) + varName := target.Identifier.Identifier + local := c.currentFunction.findLocal(varName) + if local != nil { + first, second := encodeUint16(local.index) + c.emit(opcode.SetLocal, first, second) + return + } + + global := c.findGlobal(varName) + first, second := encodeUint16(global.index) + c.emit(opcode.SetGlobal, first, second) case *ast.MemberExpression: c.compileExpression(target.Expression) c.stringConstLoad(target.Identifier.Identifier) @@ -637,7 +673,7 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio typ := c.Elaboration.IdentifierInInvocationTypes[invokedExpr] invocationType := typ.(*sema.FunctionType) if invocationType.IsConstructor { - + // TODO: } // Load arguments @@ -647,7 +683,7 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio c.emit(opcode.Invoke) case *ast.MemberExpression: memberInfo := c.Elaboration.MemberExpressionMemberInfos[invokedExpr] - typeName := memberInfo.AccessedType.QualifiedString() + typeName := TypeName(memberInfo.AccessedType) var funcName string invocationType := memberInfo.Member.TypeAnnotation.Type.(*sema.FunctionType) @@ -668,7 +704,7 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio // Load arguments c.loadArguments(expression) - if _, ok := memberInfo.AccessedType.(*sema.RestrictedType); ok { + if isInterfaceMethodInvocation(memberInfo.AccessedType) { funcName = invokedExpr.Identifier.Identifier funcNameSizeFirst, funcNameSizeSecond := encodeUint16(uint16(len(funcName))) @@ -692,6 +728,31 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio return } +func isInterfaceMethodInvocation(accessedType sema.Type) bool { + switch typ := accessedType.(type) { + case *sema.ReferenceType: + return isInterfaceMethodInvocation(typ.Type) + case *sema.RestrictedType: + // TODO: If the concrete type is known (other than AnyStruct/AnyResource), can + // this be treated as a concrete type method invocation (instead of the restriction interface)? + return true + default: + return false + } +} + +func TypeName(typ sema.Type) string { + switch typ := typ.(type) { + case *sema.ReferenceType: + return TypeName(typ.Type) + case *sema.RestrictedType: + // TODO: Revisit. Probably this is not needed here? + return TypeName(typ.Restrictions[0]) + default: + return typ.QualifiedString() + } +} + func (c *Compiler) loadArguments(expression *ast.InvocationExpression) { invocationTypes := c.Elaboration.InvocationExpressionTypes[expression] for index, argument := range expression.Arguments { @@ -1137,20 +1198,102 @@ func (c *Compiler) declareParameters(function *function, paramList *ast.Paramete // desugarTransaction Convert a transaction into a composite type declaration, // so the code-gen would seamlessly work without having special-case anything in compiler/vm. -func (c *Compiler) desugarTransaction(transaction *ast.TransactionDeclaration) (*ast.CompositeDeclaration, []*ast.VariableDeclaration) { - +// Transaction parameters are converted into global variables. +// An initializer is generated to set parameters to above generated global variables. +func (c *Compiler) desugarTransaction(transaction *ast.TransactionDeclaration) ( + *ast.CompositeDeclaration, + []*ast.VariableDeclaration, + *ast.FunctionDeclaration, +) { // TODO: This assumes the transaction program/elaboration is not cached. // i.e: Modifies the elaboration. // Handle this properly for cached transactions. // TODO: add pre/post conditions - var members []ast.Declaration + var varDeclarations []*ast.VariableDeclaration + var initFunction *ast.FunctionDeclaration + + if transaction.ParameterList != nil { + varDeclarations = make([]*ast.VariableDeclaration, 0, len(transaction.ParameterList.Parameters)) + statements := make([]ast.Statement, 0, len(transaction.ParameterList.Parameters)) + parameters := make([]*ast.Parameter, 0, len(transaction.ParameterList.Parameters)) + + for index, parameter := range transaction.ParameterList.Parameters { + // Create global variables + // i.e: `var a: Type` + field := &ast.VariableDeclaration{ + Access: ast.AccessPrivate, + IsConstant: false, + Identifier: parameter.Identifier, + TypeAnnotation: parameter.TypeAnnotation, + } + varDeclarations = append(varDeclarations, field) + + // Create assignment from param to global var. + // i.e: `a = $param_a` + modifiedParamName := commons.TransactionGeneratedParamPrefix + parameter.Identifier.Identifier + modifiedParameter := &ast.Parameter{ + Label: "", + Identifier: ast.Identifier{ + Identifier: modifiedParamName, + }, + TypeAnnotation: parameter.TypeAnnotation, + } + parameters = append(parameters, modifiedParameter) + + assignment := &ast.AssignmentStatement{ + Target: &ast.IdentifierExpression{ + Identifier: parameter.Identifier, + }, + Value: &ast.IdentifierExpression{ + Identifier: ast.Identifier{ + Identifier: modifiedParamName, + }, + }, + Transfer: &ast.Transfer{ + Operation: ast.TransferOperationCopy, + }, + } + statements = append(statements, assignment) + transactionTypes := c.Elaboration.TransactionDeclarationTypes[transaction] + paramType := transactionTypes.Parameters[index].TypeAnnotation.Type + assignmentTypes := sema.AssignmentStatementTypes{ + ValueType: paramType, + TargetType: paramType, + } + + c.Elaboration.AssignmentStatementTypes[assignment] = assignmentTypes + } + + // Create an init function. + // func $init($param_a: Type, $param_b: Type, ...) { + // a = $param_a + // b = $param_b + // ... + // } + initFunction = &ast.FunctionDeclaration{ + Access: 0, + Identifier: ast.Identifier{ + Identifier: commons.ProgramInitFunctionName, + }, + ParameterList: &ast.ParameterList{ + Parameters: parameters, + }, + ReturnTypeAnnotation: nil, + FunctionBlock: &ast.FunctionBlock{ + Block: &ast.Block{ + Statements: statements, + }, + }, + } + } + + var members []ast.Declaration if transaction.Execute != nil { members = append(members, transaction.Execute.FunctionDeclaration) } - if transaction.Prepare != nil { members = append(members, transaction.Prepare) } @@ -1178,7 +1321,7 @@ func (c *Compiler) desugarTransaction(transaction *ast.TransactionDeclaration) ( c.Elaboration.CompositeDeclarationTypes[compositeDecl] = compositeType - return compositeDecl + return compositeDecl, varDeclarations, initFunction } var emptyInitializer = func() *ast.SpecialFunctionDeclaration { diff --git a/runtime/bbq/compiler/native_functions.go b/runtime/bbq/compiler/native_functions.go index 98a105cd8b..e1cd355826 100644 --- a/runtime/bbq/compiler/native_functions.go +++ b/runtime/bbq/compiler/native_functions.go @@ -30,11 +30,14 @@ var nativeFunctions []*global var builtinTypes = []sema.Type{ sema.StringType, sema.AuthAccountType, + sema.PublicAccountType, + &sema.CapabilityType{}, } var stdlibFunctions = []string{ commons.LogFunctionName, commons.PanicFunctionName, + commons.GetAccountFunctionName, } func init() { diff --git a/runtime/bbq/program.go b/runtime/bbq/program.go index 80efbdc16f..a6709e0ee5 100644 --- a/runtime/bbq/program.go +++ b/runtime/bbq/program.go @@ -23,5 +23,6 @@ type Program struct { Imports []*Import Functions []*Function Constants []*Constant + Variables []*Variable Types [][]byte } diff --git a/runtime/bbq/variable.go b/runtime/bbq/variable.go new file mode 100644 index 0000000000..e642c01d3d --- /dev/null +++ b/runtime/bbq/variable.go @@ -0,0 +1,23 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package bbq + +type Variable struct { + Name string +} diff --git a/runtime/bbq/vm/ft_test.go b/runtime/bbq/vm/ft_test.go index 415e48cd2d..c95f6e090b 100644 --- a/runtime/bbq/vm/ft_test.go +++ b/runtime/bbq/vm/ft_test.go @@ -177,7 +177,7 @@ func TestFTTransfer(t *testing.T) { setupTxVM := NewVM(program, vmConfig) authorizer := NewAuthAccountValue() - err = setupTxVM.ExecuteTransaction(authorizer) + err = setupTxVM.ExecuteTransaction(nil, authorizer) require.NoError(t, err) // ----- Run token transfer transaction ----- @@ -215,7 +215,13 @@ func TestFTTransfer(t *testing.T) { printProgram(tokenTransferTxProgram) tokenTransferTxVM := NewVM(tokenTransferTxProgram, vmConfig) - err = tokenTransferTxVM.ExecuteTransaction(authorizer) + + args := []Value{ + IntValue{1}, + AddressValue(common.Address{0x01}), + } + + err = tokenTransferTxVM.ExecuteTransaction(args, authorizer) require.NoError(t, err) } diff --git a/runtime/bbq/vm/linker.go b/runtime/bbq/vm/linker.go index 78110304a6..c82be51765 100644 --- a/runtime/bbq/vm/linker.go +++ b/runtime/bbq/vm/linker.go @@ -98,6 +98,10 @@ func LinkGlobals( globals = append(globals, nil) } + for range program.Variables { + globals = append(globals, nil) + } + // Iterate through `program.Functions` to be deterministic. // Order of globals must be same as index set at `Compiler.addGlobal()`. // TODO: include non-function globals diff --git a/runtime/bbq/vm/native_functions.go b/runtime/bbq/vm/native_functions.go index 6ca8bdc27f..d35cd27905 100644 --- a/runtime/bbq/vm/native_functions.go +++ b/runtime/bbq/vm/native_functions.go @@ -34,6 +34,7 @@ var NativeFunctions = map[string]Value{} var BuiltInLocation common.Location = nil func RegisterFunction(functionName string, functionValue NativeFunctionValue) { + functionValue.Name = functionName NativeFunctions[functionName] = functionValue } @@ -66,4 +67,12 @@ func init() { panic(string(messageValue.Str)) }, }) + + RegisterFunction(commons.GetAccountFunctionName, NativeFunctionValue{ + ParameterCount: len(stdlib.PanicFunctionType.Parameters), + Function: func(arguments ...Value) Value { + // TODO: Properly implement + return VoidValue{} + }, + }) } diff --git a/runtime/bbq/vm/value_address.go b/runtime/bbq/vm/value_address.go new file mode 100644 index 0000000000..8a6a953f66 --- /dev/null +++ b/runtime/bbq/vm/value_address.go @@ -0,0 +1,45 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/atree" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/format" + "github.com/onflow/cadence/runtime/interpreter" +) + +type AddressValue common.Address + +var _ Value = AddressValue{} + +func (AddressValue) isValue() {} + +func (AddressValue) StaticType(common.MemoryGauge) StaticType { + return interpreter.PrimitiveStaticTypeAddress +} + +func (v AddressValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { + return v +} + +func (v AddressValue) String() string { + return format.Address(common.Address(v)) +} diff --git a/runtime/bbq/vm/value_capability.go b/runtime/bbq/vm/value_capability.go new file mode 100644 index 0000000000..7d2d556c82 --- /dev/null +++ b/runtime/bbq/vm/value_capability.go @@ -0,0 +1,39 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" +) + +// members + +func init() { + typeName := interpreter.PrimitiveStaticTypeCapability.String() + + // Capability.borrow + RegisterTypeBoundFunction(typeName, sema.CapabilityTypeBorrowField, NativeFunctionValue{ + ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), + Function: func(value ...Value) Value { + // TODO: + return NilValue{} + }, + }) +} diff --git a/runtime/bbq/vm/value_function.go b/runtime/bbq/vm/value_function.go index 26052ba9ae..8cef889ff2 100644 --- a/runtime/bbq/vm/value_function.go +++ b/runtime/bbq/vm/value_function.go @@ -49,6 +49,7 @@ func (v FunctionValue) String() string { } type NativeFunctionValue struct { + Name string ParameterCount int Function func(arguments ...Value) Value } diff --git a/runtime/bbq/vm/value_publicaccount.go b/runtime/bbq/vm/value_publicaccount.go new file mode 100644 index 0000000000..970388cf8f --- /dev/null +++ b/runtime/bbq/vm/value_publicaccount.go @@ -0,0 +1,39 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" +) + +// members + +func init() { + typeName := interpreter.PrimitiveStaticTypePublicAccount.String() + + // PublicAccount.getCapability + RegisterTypeBoundFunction(typeName, sema.PublicAccountGetCapabilityField, NativeFunctionValue{ + ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), + Function: func(value ...Value) Value { + // TODO: + return NilValue{} + }, + }) +} diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 611742f9a9..adba62182d 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -169,14 +169,22 @@ func (vm *VM) InitializeContract(arguments ...Value) (*CompositeValue, error) { return contractValue, nil } -func (vm *VM) ExecuteTransaction(signers ...Value) error { +func (vm *VM) ExecuteTransaction(transactionArgs []Value, signers ...Value) error { // Create transaction value transaction, err := vm.Invoke(commons.TransactionWrapperCompositeName) if err != nil { return err } - args := []Value{transaction} + if initializer, ok := vm.globals[commons.ProgramInitFunctionName]; ok { + _, err = vm.invoke(initializer, transactionArgs) + if err != nil { + return err + } + } + + args := make([]Value, 0, len(signers)+1) + args = append(args, transaction) args = append(args, signers...) // Invoke 'prepare', if exists. @@ -409,7 +417,7 @@ func opTransfer(vm *VM) { valueType := transferredValue.StaticType(vm.config.MemoryGauge) if !IsSubType(valueType, targetType) { - panic("invalid transfer") + panic(errors.NewUnexpectedError("invalid transfer: expected '%s', found '%s'", targetType, valueType)) } vm.replaceTop(transferredValue) diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index 3303d24683..6b3c1cfe06 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -1508,7 +1508,62 @@ func TestTransaction(t *testing.T) { vm := NewVM(program, nil) - err = vm.ExecuteTransaction() + err = vm.ExecuteTransaction(nil) + require.NoError(t, err) + + // Rerun the same again using internal functions, to get the access to the transaction value. + + transaction, err := vm.Invoke(commons.TransactionWrapperCompositeName) + require.NoError(t, err) + + require.IsType(t, &CompositeValue{}, transaction) + compositeValue := transaction.(*CompositeValue) + + // At the beginning, 'a' is uninitialized + assert.Nil(t, compositeValue.GetMember(vm.config, "a")) + + // Invoke 'prepare' + _, err = vm.Invoke(commons.TransactionPrepareFunctionName, transaction) + require.NoError(t, err) + + // Once 'prepare' is called, 'a' is initialized to "Hello!" + assert.Equal(t, StringValue{Str: []byte("Hello!")}, compositeValue.GetMember(vm.config, "a")) + + // Invoke 'execute' + _, err = vm.Invoke(commons.TransactionExecuteFunctionName, transaction) + require.NoError(t, err) + + // Once 'execute' is called, 'a' is initialized to "Hello, again!" + assert.Equal(t, StringValue{Str: []byte("Hello again!")}, compositeValue.GetMember(vm.config, "a")) + }) + + t.Run("with params", func(t *testing.T) { + + checker, err := ParseAndCheck(t, ` + transaction(param1: String, param2: String) { + var a: String + prepare() { + self.a = param1 + } + execute { + self.a = param2 + } + }`, + ) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + printProgram(program) + + vm := NewVM(program, nil) + + args := []Value{ + StringValue{[]byte("Hello!")}, + StringValue{[]byte("Hello again!")}, + } + + err = vm.ExecuteTransaction(args) require.NoError(t, err) // Rerun the same again using internal functions, to get the access to the transaction value. From 61d7a01a50867d6860faebe1966aafe06cd4b1d8 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 14 May 2024 15:23:08 -0700 Subject: [PATCH 24/89] Add auth account borrow --- runtime/bbq/vm/config.go | 10 +- runtime/bbq/vm/native_functions.go | 6 +- runtime/bbq/vm/storage.go | 31 +++++++ runtime/bbq/vm/value_authaccount.go | 54 ++++++++++- runtime/bbq/vm/value_capability.go | 2 +- runtime/bbq/vm/value_composite.go | 16 +++- runtime/bbq/vm/value_conversions.go | 4 +- runtime/bbq/vm/value_function.go | 2 +- runtime/bbq/vm/value_publicaccount.go | 2 +- runtime/bbq/vm/value_some.go | 59 ++++++++++++ runtime/bbq/vm/value_storage_reference.go | 108 ++++++++++++++++++++++ runtime/bbq/vm/value_string.go | 2 +- runtime/bbq/vm/vm.go | 2 +- runtime/bbq/vm/vm_test.go | 2 +- runtime/interpreter/encode.go | 16 ++-- runtime/interpreter/interpreter.go | 2 +- runtime/interpreter/storage.go | 2 +- runtime/interpreter/value.go | 4 +- 18 files changed, 286 insertions(+), 38 deletions(-) create mode 100644 runtime/bbq/vm/storage.go create mode 100644 runtime/bbq/vm/value_some.go create mode 100644 runtime/bbq/vm/value_storage_reference.go diff --git a/runtime/bbq/vm/config.go b/runtime/bbq/vm/config.go index 341b19f89d..ef91d5f1be 100644 --- a/runtime/bbq/vm/config.go +++ b/runtime/bbq/vm/config.go @@ -20,15 +20,15 @@ package vm import ( "github.com/onflow/atree" - "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/bbq/commons" ) type Config struct { - Storage + interpreter.Storage common.MemoryGauge commons.ImportHandler ContractValueHandler @@ -36,11 +36,7 @@ type Config struct { type ContractValueHandler func(conf *Config, location common.Location) *CompositeValue -type Storage interface { - atree.SlabStorage -} - -func RemoveReferencedSlab(storage Storage, storable atree.Storable) { +func RemoveReferencedSlab(storage interpreter.Storage, storable atree.Storable) { storageIDStorable, ok := storable.(atree.StorageIDStorable) if !ok { return diff --git a/runtime/bbq/vm/native_functions.go b/runtime/bbq/vm/native_functions.go index d35cd27905..0309b10d52 100644 --- a/runtime/bbq/vm/native_functions.go +++ b/runtime/bbq/vm/native_functions.go @@ -48,7 +48,7 @@ func RegisterTypeBoundFunction(typeName, functionName string, functionValue Nati func init() { RegisterFunction(commons.LogFunctionName, NativeFunctionValue{ ParameterCount: len(stdlib.LogFunctionType.Parameters), - Function: func(arguments ...Value) Value { + Function: func(config *Config, arguments ...Value) Value { // TODO: Properly implement fmt.Println(arguments[0].String()) return VoidValue{} @@ -57,7 +57,7 @@ func init() { RegisterFunction(commons.PanicFunctionName, NativeFunctionValue{ ParameterCount: len(stdlib.PanicFunctionType.Parameters), - Function: func(arguments ...Value) Value { + Function: func(config *Config, arguments ...Value) Value { // TODO: Properly implement messageValue, ok := arguments[0].(StringValue) if !ok { @@ -70,7 +70,7 @@ func init() { RegisterFunction(commons.GetAccountFunctionName, NativeFunctionValue{ ParameterCount: len(stdlib.PanicFunctionType.Parameters), - Function: func(arguments ...Value) Value { + Function: func(config *Config, arguments ...Value) Value { // TODO: Properly implement return VoidValue{} }, diff --git a/runtime/bbq/vm/storage.go b/runtime/bbq/vm/storage.go new file mode 100644 index 0000000000..bf50c81c9a --- /dev/null +++ b/runtime/bbq/vm/storage.go @@ -0,0 +1,31 @@ +/* +* Cadence - The resource-oriented smart contract programming language +* +* Copyright 2019-2022 Dapper Labs, Inc. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" +) + +func StoredValue(gauge common.MemoryGauge, storable atree.Storable, storage interpreter.Storage) Value { + // Delegate + value := interpreter.StoredValue(gauge, storable, storage) + return InterpreterValueToVMValue(storage, value) +} diff --git a/runtime/bbq/vm/value_authaccount.go b/runtime/bbq/vm/value_authaccount.go index cdc1b7ad51..66fba94e53 100644 --- a/runtime/bbq/vm/value_authaccount.go +++ b/runtime/bbq/vm/value_authaccount.go @@ -20,6 +20,7 @@ package vm import ( "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" ) @@ -42,7 +43,7 @@ func init() { // AuthAccount.link RegisterTypeBoundFunction(typeName, sema.AuthAccountLinkField, NativeFunctionValue{ ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), - Function: func(value ...Value) Value { + Function: func(config *Config, value ...Value) Value { // TODO: return NilValue{} }, @@ -51,7 +52,7 @@ func init() { // AuthAccount.save RegisterTypeBoundFunction(typeName, sema.AuthAccountSaveField, NativeFunctionValue{ ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), - Function: func(value ...Value) Value { + Function: func(config *Config, value ...Value) Value { // TODO: return NilValue{} }, @@ -60,9 +61,52 @@ func init() { // AuthAccount.borrow RegisterTypeBoundFunction(typeName, sema.AuthAccountBorrowField, NativeFunctionValue{ ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), - Function: func(value ...Value) Value { - // TODO: - return NilValue{} + Function: func(config *Config, args ...Value) Value { + authAccount, ok := args[0].(*CompositeValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + path, ok := args[1].(PathValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + // TODO: pass type parameter + var typeParameter StaticType + + referenceType, ok := typeParameter.(*interpreter.ReferenceStaticType) + if !ok { + panic(errors.NewUnreachableError()) + } + + address := authAccount.GetMember(config, sema.AuthAccountAddressField) + addressValue, ok := address.(AddressValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + reference := NewStorageReferenceValue( + nil, + referenceType.Authorized, + common.Address(addressValue), + path, + typeParameter, + ) + + // Attempt to dereference, + // which reads the stored value + // and performs a dynamic type check + + referenced, err := reference.dereference(config.MemoryGauge) + if err != nil { + panic(err) + } + if referenced == nil { + return NilValue{} + } + + return NewSomeValueNonCopying(reference) }, }) } diff --git a/runtime/bbq/vm/value_capability.go b/runtime/bbq/vm/value_capability.go index 7d2d556c82..8765408cf2 100644 --- a/runtime/bbq/vm/value_capability.go +++ b/runtime/bbq/vm/value_capability.go @@ -31,7 +31,7 @@ func init() { // Capability.borrow RegisterTypeBoundFunction(typeName, sema.CapabilityTypeBorrowField, NativeFunctionValue{ ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), - Function: func(value ...Value) Value { + Function: func(config *Config, value ...Value) Value { // TODO: return NilValue{} }, diff --git a/runtime/bbq/vm/value_composite.go b/runtime/bbq/vm/value_composite.go index 2d824ffc40..fe3025aa28 100644 --- a/runtime/bbq/vm/value_composite.go +++ b/runtime/bbq/vm/value_composite.go @@ -68,6 +68,18 @@ func NewCompositeValue( } } +func newCompositeValueFromOrderedMap( + dict *atree.OrderedMap, + typeInfo interpreter.CompositeTypeInfo, +) *CompositeValue { + return &CompositeValue{ + dictionary: dict, + Location: typeInfo.Location, + QualifiedIdentifier: typeInfo.QualifiedIdentifier, + Kind: typeInfo.Kind, + } +} + func (*CompositeValue) isValue() {} func (v *CompositeValue) StaticType(memoryGauge common.MemoryGauge) StaticType { @@ -97,9 +109,7 @@ func (v *CompositeValue) GetMember(config *Config, name string) Value { } if storable != nil { - interpreterValue := interpreter.StoredValue(config.MemoryGauge, storable, config.Storage) - // TODO: Temp conversion - return InterpreterValueToVMValue(config, interpreterValue) + return StoredValue(config.MemoryGauge, storable, config.Storage) } return nil diff --git a/runtime/bbq/vm/value_conversions.go b/runtime/bbq/vm/value_conversions.go index 6e842c4aca..6042aa6a1b 100644 --- a/runtime/bbq/vm/value_conversions.go +++ b/runtime/bbq/vm/value_conversions.go @@ -28,7 +28,7 @@ import ( // Utility methods to convert between old and new values. // These are temporary until all parts of the interpreter are migrated to the vm. -func InterpreterValueToVMValue(config *Config, value interpreter.Value) Value { +func InterpreterValueToVMValue(storage interpreter.Storage, value interpreter.Value) Value { switch value := value.(type) { case interpreter.IntValue: return IntValue{value.BigInt.Int64()} @@ -40,7 +40,7 @@ func InterpreterValueToVMValue(config *Config, value interpreter.Value) Value { value.QualifiedIdentifier, value.Kind, common.Address{}, - config.Storage, + storage, ) default: panic(errors.NewUnreachableError()) diff --git a/runtime/bbq/vm/value_function.go b/runtime/bbq/vm/value_function.go index 8cef889ff2..a341f83de6 100644 --- a/runtime/bbq/vm/value_function.go +++ b/runtime/bbq/vm/value_function.go @@ -51,7 +51,7 @@ func (v FunctionValue) String() string { type NativeFunctionValue struct { Name string ParameterCount int - Function func(arguments ...Value) Value + Function func(config *Config, arguments ...Value) Value } var _ Value = NativeFunctionValue{} diff --git a/runtime/bbq/vm/value_publicaccount.go b/runtime/bbq/vm/value_publicaccount.go index 970388cf8f..b8daf58b04 100644 --- a/runtime/bbq/vm/value_publicaccount.go +++ b/runtime/bbq/vm/value_publicaccount.go @@ -31,7 +31,7 @@ func init() { // PublicAccount.getCapability RegisterTypeBoundFunction(typeName, sema.PublicAccountGetCapabilityField, NativeFunctionValue{ ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), - Function: func(value ...Value) Value { + Function: func(config *Config, value ...Value) Value { // TODO: return NilValue{} }, diff --git a/runtime/bbq/vm/value_some.go b/runtime/bbq/vm/value_some.go new file mode 100644 index 0000000000..1edef0a12f --- /dev/null +++ b/runtime/bbq/vm/value_some.go @@ -0,0 +1,59 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/atree" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" +) + +type SomeValue struct { + value Value +} + +var _ Value = &SomeValue{} + +func NewSomeValueNonCopying(value Value) *SomeValue { + return &SomeValue{ + value: value, + } +} + +func (*SomeValue) isValue() {} + +func (v *SomeValue) StaticType(common.MemoryGauge) StaticType { + innerType := v.value.StaticType(inter) + if innerType == nil { + return nil + } + return interpreter.NewOptionalStaticType( + inter, + innerType, + ) +} + +func (v *SomeValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { + return v +} + +func (v *SomeValue) String() string { + return v.value.String() +} diff --git a/runtime/bbq/vm/value_storage_reference.go b/runtime/bbq/vm/value_storage_reference.go new file mode 100644 index 0000000000..4c2678165f --- /dev/null +++ b/runtime/bbq/vm/value_storage_reference.go @@ -0,0 +1,108 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "fmt" + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/format" + "github.com/onflow/cadence/runtime/interpreter" +) + +type StorageReferenceValue struct { + Authorized bool + TargetStorageAddress common.Address + TargetPath PathValue + BorrowedType interpreter.StaticType + storage interpreter.Storage +} + +func NewStorageReferenceValue( + storage interpreter.Storage, + authorized bool, + targetStorageAddress common.Address, + targetPath PathValue, + borrowedType interpreter.StaticType, +) *StorageReferenceValue { + return &StorageReferenceValue{ + Authorized: authorized, + TargetStorageAddress: targetStorageAddress, + TargetPath: targetPath, + BorrowedType: borrowedType, + storage: storage, + } +} + +var _ Value = &StorageReferenceValue{} + +func (*StorageReferenceValue) isValue() {} + +func (v *StorageReferenceValue) StaticType(gauge common.MemoryGauge) StaticType { + referencedValue, err := v.dereference(gauge) + if err != nil { + panic(err) + } + + return interpreter.NewReferenceStaticType( + inter, + v.Authorized, + v.BorrowedType, + (*referencedValue).StaticType(inter), + ) +} + +func (v *StorageReferenceValue) dereference(gauge common.MemoryGauge) (*Value, error) { + address := v.TargetStorageAddress + domain := v.TargetPath.Domain.Identifier() + identifier := v.TargetPath.Identifier + + accountStorage := v.storage.GetStorageMap(address, domain, false) + if accountStorage == nil { + return nil, nil + } + + referenced := accountStorage.ReadValue(gauge, identifier) + vmReferencedValue := InterpreterValueToVMValue(v.storage, referenced) + + if v.BorrowedType != nil { + staticType := vmReferencedValue.StaticType(gauge) + + if !IsSubType(staticType, v.BorrowedType) { + panic(fmt.Errorf("type mismatch: expected %s, found %s", v.BorrowedType, staticType)) + //semaType := interpreter.MustConvertStaticToSemaType(staticType) + // + //return nil, ForceCastTypeMismatchError{ + // ExpectedType: v.BorrowedType, + // ActualType: semaType, + // LocationRange: locationRange, + //} + } + } + + return &vmReferencedValue, nil +} + +func (v *StorageReferenceValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { + return v +} + +func (v *StorageReferenceValue) String() string { + return format.StorageReference +} diff --git a/runtime/bbq/vm/value_string.go b/runtime/bbq/vm/value_string.go index b403ecce1b..500b56aea8 100644 --- a/runtime/bbq/vm/value_string.go +++ b/runtime/bbq/vm/value_string.go @@ -59,7 +59,7 @@ func init() { RegisterTypeBoundFunction(typeName, StringConcatFunctionName, NativeFunctionValue{ ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), - Function: func(value ...Value) Value { + Function: func(config *Config, value ...Value) Value { first := value[0].(StringValue) second := value[1].(StringValue) var sb strings.Builder diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index adba62182d..163e819fb6 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -318,7 +318,7 @@ func opInvoke(vm *VM) { case NativeFunctionValue: parameterCount := value.ParameterCount arguments := vm.stack[stackHeight-parameterCount:] - result := value.Function(arguments...) + result := value.Function(vm.config, arguments...) vm.push(result) default: panic(errors.NewUnreachableError()) diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index 6b3c1cfe06..ed7f50ae0d 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -20,6 +20,7 @@ package vm import ( "fmt" + "github.com/onflow/cadence/runtime/interpreter" "testing" "github.com/stretchr/testify/assert" @@ -29,7 +30,6 @@ import ( "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" . "github.com/onflow/cadence/runtime/tests/checker" diff --git a/runtime/interpreter/encode.go b/runtime/interpreter/encode.go index c8ec4e5032..12c15b1ce7 100644 --- a/runtime/interpreter/encode.go +++ b/runtime/interpreter/encode.go @@ -1367,8 +1367,8 @@ func (t FunctionStaticType) Encode(_ *cbor.StreamEncoder) error { } } -// compositeTypeInfo -type compositeTypeInfo struct { +// CompositeTypeInfo +type CompositeTypeInfo struct { Location common.Location QualifiedIdentifier string Kind common.CompositeKind @@ -1379,21 +1379,21 @@ func NewCompositeTypeInfo( location common.Location, qualifiedIdentifier string, kind common.CompositeKind, -) compositeTypeInfo { +) CompositeTypeInfo { common.UseMemory(memoryGauge, common.CompositeTypeInfoMemoryUsage) - return compositeTypeInfo{ + return CompositeTypeInfo{ Location: location, QualifiedIdentifier: qualifiedIdentifier, Kind: kind, } } -var _ atree.TypeInfo = compositeTypeInfo{} +var _ atree.TypeInfo = CompositeTypeInfo{} const encodedCompositeTypeInfoLength = 3 -func (c compositeTypeInfo) Encode(e *cbor.StreamEncoder) error { +func (c CompositeTypeInfo) Encode(e *cbor.StreamEncoder) error { err := e.EncodeRawBytes([]byte{ // tag number 0xd8, CBORTagCompositeValue, @@ -1422,8 +1422,8 @@ func (c compositeTypeInfo) Encode(e *cbor.StreamEncoder) error { return nil } -func (c compositeTypeInfo) Equal(o atree.TypeInfo) bool { - other, ok := o.(compositeTypeInfo) +func (c CompositeTypeInfo) Equal(o atree.TypeInfo) bool { + other, ok := o.(CompositeTypeInfo) return ok && c.Location == other.Location && c.QualifiedIdentifier == other.QualifiedIdentifier && diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 138d71e854..18c57d7427 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -4194,7 +4194,7 @@ func (interpreter *Interpreter) ValidateAtreeValue(value atree.Value) { return info.Equal(other.(StaticType)) case DictionaryStaticType: return info.Equal(other.(StaticType)) - case compositeTypeInfo: + case CompositeTypeInfo: return info.Equal(other) case EmptyTypeInfo: _, ok := other.(EmptyTypeInfo) diff --git a/runtime/interpreter/storage.go b/runtime/interpreter/storage.go index c763fa091a..8f096be56b 100644 --- a/runtime/interpreter/storage.go +++ b/runtime/interpreter/storage.go @@ -70,7 +70,7 @@ func ConvertStoredValue(gauge common.MemoryGauge, value atree.Value) (Value, err switch typeInfo := typeInfo.(type) { case DictionaryStaticType: return newDictionaryValueFromConstructor(gauge, typeInfo, value.Count(), func() *atree.OrderedMap { return value }), nil - case compositeTypeInfo: + case CompositeTypeInfo: return newCompositeValueFromConstructor(gauge, value.Count(), typeInfo, func() *atree.OrderedMap { return value }), nil default: return nil, errors.NewUnexpectedError("invalid ordered map type info: %T", typeInfo) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 911ba1d833..35f0a157e2 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -13909,7 +13909,7 @@ func NewCompositeValue( func newCompositeValueFromOrderedMap( dict *atree.OrderedMap, - typeInfo compositeTypeInfo, + typeInfo CompositeTypeInfo, ) *CompositeValue { return &CompositeValue{ dictionary: dict, @@ -13922,7 +13922,7 @@ func newCompositeValueFromOrderedMap( func newCompositeValueFromConstructor( gauge common.MemoryGauge, count uint64, - typeInfo compositeTypeInfo, + typeInfo CompositeTypeInfo, constructor func() *atree.OrderedMap, ) *CompositeValue { baseUse, elementOverhead, dataUse, metaDataUse := common.NewCompositeMemoryUsages(count, 0) From 2970b2c32ad71dca904355c44af42fd641aa3a1f Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 15 May 2024 11:17:06 -0700 Subject: [PATCH 25/89] Support type arguments --- runtime/bbq/bytecode_printer.go | 5 ++++ runtime/bbq/compiler/compiler.go | 41 ++++++++++++++++++++++++--- runtime/bbq/function.go | 9 +++--- runtime/bbq/vm/native_functions.go | 6 ++-- runtime/bbq/vm/value_authaccount.go | 19 ++++++------- runtime/bbq/vm/value_capability.go | 2 +- runtime/bbq/vm/value_function.go | 2 +- runtime/bbq/vm/value_publicaccount.go | 2 +- runtime/bbq/vm/value_string.go | 2 +- runtime/bbq/vm/vm.go | 12 +++++++- 10 files changed, 73 insertions(+), 27 deletions(-) diff --git a/runtime/bbq/bytecode_printer.go b/runtime/bbq/bytecode_printer.go index d0dc878011..c9e320943b 100644 --- a/runtime/bbq/bytecode_printer.go +++ b/runtime/bbq/bytecode_printer.go @@ -107,6 +107,11 @@ func (p *BytecodePrinter) printCode(codes []byte) { funcName, i = p.getStringOperand(codes, i) p.stringBuilder.WriteString(" " + " " + funcName) + case opcode.Invoke: + //var typeIndex int + //typeIndex, i = p.getIntOperand(codes, i) + //p.stringBuilder.WriteString(" " + fmt.Sprint(typeIndex)) + // opcodes with no operands default: // do nothing diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index ae2208f124..c580afaec5 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -680,7 +680,9 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio c.loadArguments(expression) // Load function value c.emitVariableLoad(invokedExpr.Identifier.Identifier) - c.emit(opcode.Invoke) + + typeArgs := c.loadTypeArguments(expression) + c.emit(opcode.Invoke, typeArgs...) case *ast.MemberExpression: memberInfo := c.Elaboration.MemberExpressionMemberInfos[invokedExpr] typeName := TypeName(memberInfo.AccessedType) @@ -695,7 +697,9 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio c.loadArguments(expression) // Load function value c.emitVariableLoad(funcName) - c.emit(opcode.Invoke) + + typeArgs := c.loadTypeArguments(expression) + c.emit(opcode.Invoke, typeArgs...) return } @@ -710,7 +714,8 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio argsCountFirst, argsCountSecond := encodeUint16(uint16(len(expression.Arguments))) - args := []byte{funcNameSizeFirst, funcNameSizeSecond} + args := c.loadTypeArguments(expression) + args = append(args, funcNameSizeFirst, funcNameSizeSecond) args = append(args, []byte(funcName)...) args = append(args, argsCountFirst, argsCountSecond) @@ -719,7 +724,9 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio // Load function value funcName = commons.TypeQualifiedName(typeName, invokedExpr.Identifier.Identifier) c.emitVariableLoad(funcName) - c.emit(opcode.Invoke) + + typeArgs := c.loadTypeArguments(expression) + c.emit(opcode.Invoke, typeArgs...) } default: panic(errors.NewUnreachableError()) @@ -761,6 +768,32 @@ func (c *Compiler) loadArguments(expression *ast.InvocationExpression) { } } +func (c *Compiler) loadTypeArguments(expression *ast.InvocationExpression) []byte { + invocationTypes := c.Elaboration.InvocationExpressionTypes[expression] + + if len(expression.TypeArguments) == 0 { + return nil + } + + var typeArgs []byte + + typeArgsCount := invocationTypes.TypeArguments.Len() + if typeArgsCount >= math.MaxUint16 { + panic(errors.NewDefaultUserError("invalid number of type arguments: %d", typeArgsCount)) + } + + first, second := encodeUint16(uint16(typeArgsCount)) + typeArgs = append(typeArgs, first, second) + + invocationTypes.TypeArguments.Foreach(func(key *sema.TypeParameter, typeParam sema.Type) { + index := c.getOrAddType(typeParam) + first, second := encodeUint16(index) + typeArgs = append(typeArgs, first, second) + }) + + return typeArgs +} + func (c *Compiler) VisitMemberExpression(expression *ast.MemberExpression) (_ struct{}) { c.compileExpression(expression.Expression) c.stringConstLoad(expression.Identifier.Identifier) diff --git a/runtime/bbq/function.go b/runtime/bbq/function.go index 9c863a9fe2..477fe96265 100644 --- a/runtime/bbq/function.go +++ b/runtime/bbq/function.go @@ -19,10 +19,11 @@ package bbq type Function struct { - Name string - Code []byte - ParameterCount uint16 - LocalCount uint16 + Name string + Code []byte + ParameterCount uint16 + TypeParameterCount uint16 + LocalCount uint16 IsCompositeFunction bool } diff --git a/runtime/bbq/vm/native_functions.go b/runtime/bbq/vm/native_functions.go index 0309b10d52..324abaa285 100644 --- a/runtime/bbq/vm/native_functions.go +++ b/runtime/bbq/vm/native_functions.go @@ -48,7 +48,7 @@ func RegisterTypeBoundFunction(typeName, functionName string, functionValue Nati func init() { RegisterFunction(commons.LogFunctionName, NativeFunctionValue{ ParameterCount: len(stdlib.LogFunctionType.Parameters), - Function: func(config *Config, arguments ...Value) Value { + Function: func(config *Config, typeArguments []StaticType, arguments ...Value) Value { // TODO: Properly implement fmt.Println(arguments[0].String()) return VoidValue{} @@ -57,7 +57,7 @@ func init() { RegisterFunction(commons.PanicFunctionName, NativeFunctionValue{ ParameterCount: len(stdlib.PanicFunctionType.Parameters), - Function: func(config *Config, arguments ...Value) Value { + Function: func(config *Config, typeArguments []StaticType, arguments ...Value) Value { // TODO: Properly implement messageValue, ok := arguments[0].(StringValue) if !ok { @@ -70,7 +70,7 @@ func init() { RegisterFunction(commons.GetAccountFunctionName, NativeFunctionValue{ ParameterCount: len(stdlib.PanicFunctionType.Parameters), - Function: func(config *Config, arguments ...Value) Value { + Function: func(config *Config, typeArguments []StaticType, arguments ...Value) Value { // TODO: Properly implement return VoidValue{} }, diff --git a/runtime/bbq/vm/value_authaccount.go b/runtime/bbq/vm/value_authaccount.go index 66fba94e53..7421699f86 100644 --- a/runtime/bbq/vm/value_authaccount.go +++ b/runtime/bbq/vm/value_authaccount.go @@ -42,8 +42,8 @@ func init() { // AuthAccount.link RegisterTypeBoundFunction(typeName, sema.AuthAccountLinkField, NativeFunctionValue{ - ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), - Function: func(config *Config, value ...Value) Value { + ParameterCount: len(sema.AuthAccountTypeLinkFunctionType.Parameters), + Function: func(config *Config, typeArguments []StaticType, value ...Value) Value { // TODO: return NilValue{} }, @@ -51,8 +51,8 @@ func init() { // AuthAccount.save RegisterTypeBoundFunction(typeName, sema.AuthAccountSaveField, NativeFunctionValue{ - ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), - Function: func(config *Config, value ...Value) Value { + ParameterCount: len(sema.AuthAccountTypeSaveFunctionType.Parameters), + Function: func(config *Config, typeArguments []StaticType, value ...Value) Value { // TODO: return NilValue{} }, @@ -60,8 +60,8 @@ func init() { // AuthAccount.borrow RegisterTypeBoundFunction(typeName, sema.AuthAccountBorrowField, NativeFunctionValue{ - ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), - Function: func(config *Config, args ...Value) Value { + ParameterCount: len(sema.AuthAccountTypeBorrowFunctionType.Parameters), + Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { authAccount, ok := args[0].(*CompositeValue) if !ok { panic(errors.NewUnreachableError()) @@ -72,10 +72,7 @@ func init() { panic(errors.NewUnreachableError()) } - // TODO: pass type parameter - var typeParameter StaticType - - referenceType, ok := typeParameter.(*interpreter.ReferenceStaticType) + referenceType, ok := typeArguments[0].(interpreter.ReferenceStaticType) if !ok { panic(errors.NewUnreachableError()) } @@ -91,7 +88,7 @@ func init() { referenceType.Authorized, common.Address(addressValue), path, - typeParameter, + referenceType, ) // Attempt to dereference, diff --git a/runtime/bbq/vm/value_capability.go b/runtime/bbq/vm/value_capability.go index 8765408cf2..1236342a95 100644 --- a/runtime/bbq/vm/value_capability.go +++ b/runtime/bbq/vm/value_capability.go @@ -31,7 +31,7 @@ func init() { // Capability.borrow RegisterTypeBoundFunction(typeName, sema.CapabilityTypeBorrowField, NativeFunctionValue{ ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), - Function: func(config *Config, value ...Value) Value { + Function: func(config *Config, typeArguments []StaticType, value ...Value) Value { // TODO: return NilValue{} }, diff --git a/runtime/bbq/vm/value_function.go b/runtime/bbq/vm/value_function.go index a341f83de6..f57d8dc036 100644 --- a/runtime/bbq/vm/value_function.go +++ b/runtime/bbq/vm/value_function.go @@ -51,7 +51,7 @@ func (v FunctionValue) String() string { type NativeFunctionValue struct { Name string ParameterCount int - Function func(config *Config, arguments ...Value) Value + Function func(config *Config, typeArguments []StaticType, arguments ...Value) Value } var _ Value = NativeFunctionValue{} diff --git a/runtime/bbq/vm/value_publicaccount.go b/runtime/bbq/vm/value_publicaccount.go index b8daf58b04..8abd35fe50 100644 --- a/runtime/bbq/vm/value_publicaccount.go +++ b/runtime/bbq/vm/value_publicaccount.go @@ -31,7 +31,7 @@ func init() { // PublicAccount.getCapability RegisterTypeBoundFunction(typeName, sema.PublicAccountGetCapabilityField, NativeFunctionValue{ ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), - Function: func(config *Config, value ...Value) Value { + Function: func(config *Config, typeArguments []StaticType, value ...Value) Value { // TODO: return NilValue{} }, diff --git a/runtime/bbq/vm/value_string.go b/runtime/bbq/vm/value_string.go index 500b56aea8..6d1fc5ad3f 100644 --- a/runtime/bbq/vm/value_string.go +++ b/runtime/bbq/vm/value_string.go @@ -59,7 +59,7 @@ func init() { RegisterTypeBoundFunction(typeName, StringConcatFunctionName, NativeFunctionValue{ ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), - Function: func(config *Config, value ...Value) Value { + Function: func(config *Config, typeArguments []StaticType, value ...Value) Value { first := value[0].(StringValue) second := value[1].(StringValue) var sb strings.Builder diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 163e819fb6..3bc4643a2f 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -309,6 +309,9 @@ func opInvoke(vm *VM) { value := vm.pop() stackHeight := len(vm.stack) + callFrame := vm.callFrame + typeArgCount := callFrame.getUint16() + switch value := value.(type) { case FunctionValue: parameterCount := int(value.Function.ParameterCount) @@ -317,8 +320,15 @@ func opInvoke(vm *VM) { vm.dropN(parameterCount) case NativeFunctionValue: parameterCount := value.ParameterCount + + var typeArguments []StaticType + for i := 0; i < int(typeArgCount); i++ { + typeArg := vm.loadType() + typeArguments = append(typeArguments, typeArg) + } + arguments := vm.stack[stackHeight-parameterCount:] - result := value.Function(vm.config, arguments...) + result := value.Function(vm.config, typeArguments, arguments...) vm.push(result) default: panic(errors.NewUnreachableError()) From 0fe0ca7c3f1e369e94df3690999dfc7451700180 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 16 May 2024 08:54:10 -0700 Subject: [PATCH 26/89] Use single storage across execution --- runtime/bbq/bytecode_printer.go | 31 ++- runtime/bbq/compiler/compiler.go | 16 +- runtime/bbq/vm/config.go | 3 +- runtime/bbq/vm/ft_test.go | 26 ++- runtime/bbq/vm/storage.go | 76 ++++++- runtime/bbq/vm/storage_map.go | 240 ++++++++++++++++++++++ runtime/bbq/vm/value_authaccount.go | 75 ++++++- runtime/bbq/vm/value_composite.go | 11 +- runtime/bbq/vm/value_conversions.go | 13 +- runtime/bbq/vm/value_simple_composite.go | 153 ++++++++++++++ runtime/bbq/vm/value_some.go | 6 +- runtime/bbq/vm/value_storage_reference.go | 4 +- runtime/bbq/vm/vm.go | 8 + 13 files changed, 609 insertions(+), 53 deletions(-) create mode 100644 runtime/bbq/vm/storage_map.go create mode 100644 runtime/bbq/vm/value_simple_composite.go diff --git a/runtime/bbq/bytecode_printer.go b/runtime/bbq/bytecode_printer.go index c9e320943b..fda62596b9 100644 --- a/runtime/bbq/bytecode_printer.go +++ b/runtime/bbq/bytecode_printer.go @@ -105,12 +105,35 @@ func (p *BytecodePrinter) printCode(codes []byte) { case opcode.InvokeDynamic: var funcName string funcName, i = p.getStringOperand(codes, i) - p.stringBuilder.WriteString(" " + " " + funcName) + p.stringBuilder.WriteString(" " + funcName) + + // Type parameters + var typeParamCount int + typeParamCount, i = p.getIntOperand(codes, i) + p.stringBuilder.WriteString(" " + fmt.Sprint(typeParamCount)) + + var typeIndex int + for j := 0; j < typeParamCount; j++ { + typeIndex, i = p.getIntOperand(codes, i) + p.stringBuilder.WriteString(" " + fmt.Sprint(typeIndex)) + } + + // Argument count parameters + var argsCount int + argsCount, i = p.getIntOperand(codes, i) + p.stringBuilder.WriteString(" " + fmt.Sprint(argsCount)) case opcode.Invoke: - //var typeIndex int - //typeIndex, i = p.getIntOperand(codes, i) - //p.stringBuilder.WriteString(" " + fmt.Sprint(typeIndex)) + // Type parameters + var typeParamCount int + typeParamCount, i = p.getIntOperand(codes, i) + p.stringBuilder.WriteString(" " + fmt.Sprint(typeParamCount)) + + var typeIndex int + for j := 0; j < typeParamCount; j++ { + typeIndex, i = p.getIntOperand(codes, i) + p.stringBuilder.WriteString(" " + fmt.Sprint(typeIndex)) + } // opcodes with no operands default: diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index c580afaec5..c50c0813d8 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -714,9 +714,9 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio argsCountFirst, argsCountSecond := encodeUint16(uint16(len(expression.Arguments))) - args := c.loadTypeArguments(expression) - args = append(args, funcNameSizeFirst, funcNameSizeSecond) + args := []byte{funcNameSizeFirst, funcNameSizeSecond} args = append(args, []byte(funcName)...) + args = append(args, c.loadTypeArguments(expression)...) args = append(args, argsCountFirst, argsCountSecond) c.emit(opcode.InvokeDynamic, args...) @@ -771,17 +771,19 @@ func (c *Compiler) loadArguments(expression *ast.InvocationExpression) { func (c *Compiler) loadTypeArguments(expression *ast.InvocationExpression) []byte { invocationTypes := c.Elaboration.InvocationExpressionTypes[expression] - if len(expression.TypeArguments) == 0 { - return nil - } - - var typeArgs []byte + //if len(expression.TypeArguments) == 0 { + // first, second := encodeUint16(0) + // typeArgs = append(typeArgs, first, second) + // return typeArgs + //} typeArgsCount := invocationTypes.TypeArguments.Len() if typeArgsCount >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid number of type arguments: %d", typeArgsCount)) } + var typeArgs []byte + first, second := encodeUint16(uint16(typeArgsCount)) typeArgs = append(typeArgs, first, second) diff --git a/runtime/bbq/vm/config.go b/runtime/bbq/vm/config.go index ef91d5f1be..858433823a 100644 --- a/runtime/bbq/vm/config.go +++ b/runtime/bbq/vm/config.go @@ -20,11 +20,10 @@ package vm import ( "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/bbq/commons" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" - - "github.com/onflow/cadence/runtime/bbq/commons" ) type Config struct { diff --git a/runtime/bbq/vm/ft_test.go b/runtime/bbq/vm/ft_test.go index c95f6e090b..6837493342 100644 --- a/runtime/bbq/vm/ft_test.go +++ b/runtime/bbq/vm/ft_test.go @@ -39,7 +39,11 @@ func TestFTTransfer(t *testing.T) { // ---- Deploy FT Contract ----- - ftLocation := common.NewAddressLocation(nil, common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, "FungibleToken") + storage := interpreter.NewInMemoryStorage(nil) + + address := common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1} + + ftLocation := common.NewAddressLocation(nil, address, "FungibleToken") ftChecker, err := ParseAndCheckWithOptions(t, realFungibleTokenContractInterface, ParseAndCheckOptions{Location: ftLocation}, ) @@ -54,7 +58,7 @@ func TestFTTransfer(t *testing.T) { // ----- Deploy FlowToken Contract ----- - flowTokenLocation := common.NewAddressLocation(nil, common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, "FlowToken") + flowTokenLocation := common.NewAddressLocation(nil, address, "FlowToken") flowTokenChecker, err := ParseAndCheckWithOptions(t, realFlowContract, ParseAndCheckOptions{ Location: flowTokenLocation, @@ -83,16 +87,15 @@ func TestFTTransfer(t *testing.T) { flowTokenProgram := flowTokenCompiler.Compile() printProgram(flowTokenProgram) - flowTokenVM := NewVM(flowTokenProgram, nil) - - authAcount := NewCompositeValue( - nil, - "AuthAccount", - common.CompositeKindStructure, - common.Address{}, - flowTokenVM.config.Storage, + flowTokenVM := NewVM( + flowTokenProgram, + &Config{ + Storage: storage, + }, ) + authAcount := NewAuthAccountValue(address) + flowTokenContractValue, err := flowTokenVM.InitializeContract(authAcount) require.NoError(t, err) @@ -130,6 +133,7 @@ func TestFTTransfer(t *testing.T) { } vmConfig := &Config{ + Storage: storage, ImportHandler: func(location common.Location) *bbq.Program { switch location { case ftLocation: @@ -176,7 +180,7 @@ func TestFTTransfer(t *testing.T) { setupTxVM := NewVM(program, vmConfig) - authorizer := NewAuthAccountValue() + authorizer := NewAuthAccountValue(address) err = setupTxVM.ExecuteTransaction(nil, authorizer) require.NoError(t, err) diff --git a/runtime/bbq/vm/storage.go b/runtime/bbq/vm/storage.go index bf50c81c9a..b5230188a6 100644 --- a/runtime/bbq/vm/storage.go +++ b/runtime/bbq/vm/storage.go @@ -20,12 +20,86 @@ package vm import ( "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" ) -func StoredValue(gauge common.MemoryGauge, storable atree.Storable, storage interpreter.Storage) Value { +// type Storage interface { +// atree.SlabStorage +// GetStorageMap(address common.Address, domain string, createIfNotExists bool) *StorageMap +// CheckHealth() error +// } +func StoredValue(gauge common.MemoryGauge, storable atree.Storable, storage atree.SlabStorage) Value { // Delegate value := interpreter.StoredValue(gauge, storable, storage) return InterpreterValueToVMValue(storage, value) } + +func WriteStored( + gauge common.MemoryGauge, + storage interpreter.Storage, + storageAddress common.Address, + domain string, + identifier string, + value Value, +) { + accountStorage := storage.GetStorageMap(storageAddress, domain, true) + interValue := VMValueToInterpreterValue(storage, value) + accountStorage.WriteValue(inter(storage), identifier, interValue) + //interpreter.recordStorageMutation() +} + +// +//// InMemoryStorage +//type InMemoryStorage struct { +// *atree.BasicSlabStorage +// StorageMaps map[interpreter.StorageKey]*StorageMap +// memoryGauge common.MemoryGauge +//} +// +//var _ Storage = InMemoryStorage{} +// +//func NewInMemoryStorage(memoryGauge common.MemoryGauge) InMemoryStorage { +// decodeStorable := func(decoder *cbor.StreamDecoder, storableSlabStorageID atree.StorageID) (atree.Storable, error) { +// return interpreter.DecodeStorable(decoder, storableSlabStorageID, memoryGauge) +// } +// +// decodeTypeInfo := func(decoder *cbor.StreamDecoder) (atree.TypeInfo, error) { +// return interpreter.DecodeTypeInfo(decoder, memoryGauge) +// } +// +// slabStorage := atree.NewBasicSlabStorage( +// interpreter.CBOREncMode, +// interpreter.CBORDecMode, +// decodeStorable, +// decodeTypeInfo, +// ) +// +// return InMemoryStorage{ +// BasicSlabStorage: slabStorage, +// StorageMaps: make(map[interpreter.StorageKey]*StorageMap), +// memoryGauge: memoryGauge, +// } +//} +// +//func (i InMemoryStorage) GetStorageMap( +// address common.Address, +// domain string, +// createIfNotExists bool, +//) ( +// storageMap *StorageMap, +//) { +// key := interpreter.NewStorageKey(i.memoryGauge, address, domain) +// storageMap = i.StorageMaps[key] +// if storageMap == nil && createIfNotExists { +// storageMap = NewStorageMap(i.memoryGauge, i, atree.Address(address)) +// i.StorageMaps[key] = storageMap +// } +// return storageMap +//} +// +//func (i InMemoryStorage) CheckHealth() error { +// _, err := atree.CheckStorageHealth(i, -1) +// return err +//} diff --git a/runtime/bbq/vm/storage_map.go b/runtime/bbq/vm/storage_map.go new file mode 100644 index 0000000000..a6040c1e96 --- /dev/null +++ b/runtime/bbq/vm/storage_map.go @@ -0,0 +1,240 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +//// StorageMap is an ordered map which stores values in an account. +//type StorageMap struct { +// orderedMap *atree.OrderedMap +//} +// +//func NewStorageMap(memoryGauge common.MemoryGauge, storage atree.SlabStorage, address atree.Address) *StorageMap { +// common.UseMemory(memoryGauge, common.StorageMapMemoryUsage) +// +// orderedMap, err := atree.NewMap( +// storage, +// address, +// atree.NewDefaultDigesterBuilder(), +// interpreter.EmptyTypeInfo{}, +// ) +// if err != nil { +// panic(errors.NewExternalError(err)) +// } +// +// return &StorageMap{ +// orderedMap: orderedMap, +// } +//} +// +//func NewStorageMapWithRootID(storage atree.SlabStorage, storageID atree.StorageID) *StorageMap { +// orderedMap, err := atree.NewMapWithRootID( +// storage, +// storageID, +// atree.NewDefaultDigesterBuilder(), +// ) +// if err != nil { +// panic(errors.NewExternalError(err)) +// } +// +// return &StorageMap{ +// orderedMap: orderedMap, +// } +//} +// +//// ValueExists returns true if the given key exists in the storage map. +//func (s StorageMap) ValueExists(key string) bool { +// _, err := s.orderedMap.Get( +// interpreter.StringAtreeComparator, +// interpreter.StringAtreeHashInput, +// interpreter.StringAtreeValue(key), +// ) +// if err != nil { +// if _, ok := err.(*atree.KeyNotFoundError); ok { +// return false +// } +// panic(errors.NewExternalError(err)) +// } +// +// return true +//} +// +//// ReadValue returns the value for the given key. +//// Returns nil if the key does not exist. +//func (s StorageMap) ReadValue(gauge common.MemoryGauge, key string) Value { +// storable, err := s.orderedMap.Get( +// interpreter.StringAtreeComparator, +// interpreter.StringAtreeHashInput, +// interpreter.StringAtreeValue(key), +// ) +// if err != nil { +// if _, ok := err.(*atree.KeyNotFoundError); ok { +// return nil +// } +// panic(errors.NewExternalError(err)) +// } +// +// return StoredValue(gauge, storable, s.orderedMap.Storage) +//} +// +//// WriteValue sets or removes a value in the storage map. +//// If the given value is nil, the key is removed. +//// If the given value is non-nil, the key is added/updated. +//func (s StorageMap) WriteValue(gauge common.MemoryGauge, key string, value atree.Value) { +// if value == nil { +// s.RemoveValue(gauge, key) +// } else { +// s.SetValue(gauge, key, value) +// } +//} +// +//// SetValue sets a value in the storage map. +//// If the given key already stores a value, it is overwritten. +//func (s StorageMap) SetValue(gauge common.MemoryGauge, key string, value atree.Value) { +// existingStorable, err := s.orderedMap.Set( +// interpreter.StringAtreeComparator, +// interpreter.StringAtreeHashInput, +// interpreter.StringAtreeValue(key), +// value, +// ) +// if err != nil { +// panic(errors.NewExternalError(err)) +// } +// +// // TODO: +// //interpreter.maybeValidateAtreeValue(s.orderedMap) +// +// if existingStorable != nil { +// existingValue := interpreter.StoredValue(gauge, existingStorable, s.orderedMap.Storage) +// existingValue.DeepRemove(inter) +// //RemoveReferencedSlab(s.orderedMap.Storage, existingStorable) +// } +//} +// +//// RemoveValue removes a value in the storage map, if it exists. +//func (s StorageMap) RemoveValue(gauge common.MemoryGauge, key string) { +// _ /*existingKeyStorable*/, existingValueStorable, err := s.orderedMap.Remove( +// interpreter.StringAtreeComparator, +// interpreter.StringAtreeHashInput, +// interpreter.StringAtreeValue(key), +// ) +// if err != nil { +// if _, ok := err.(*atree.KeyNotFoundError); ok { +// return +// } +// panic(errors.NewExternalError(err)) +// } +// +// // TODO +// //interpreter.maybeValidateAtreeValue(s.orderedMap) +// +// // Key +// +// // NOTE: key / field name is stringAtreeValue, +// // and not a Value, so no need to deep remove +// // TODO +// //interpreter.RemoveReferencedSlab(existingKeyStorable) +// +// // Value +// +// if existingValueStorable != nil { +// _ = StoredValue(gauge, existingValueStorable, s.orderedMap.Storage) +// //existingValue.DeepRemove(interpreter) +// //interpreter.RemoveReferencedSlab(existingValueStorable) +// } +//} +// +//// Iterator returns an iterator (StorageMapIterator), +//// which allows iterating over the keys and values of the storage map +//func (s StorageMap) Iterator(gauge common.MemoryGauge) StorageMapIterator { +// mapIterator, err := s.orderedMap.Iterator() +// if err != nil { +// panic(errors.NewExternalError(err)) +// } +// +// return StorageMapIterator{ +// gauge: gauge, +// mapIterator: mapIterator, +// storage: s.orderedMap.Storage, +// } +//} +// +//func (s StorageMap) StorageID() atree.StorageID { +// return s.orderedMap.StorageID() +//} +// +//func (s StorageMap) Count() uint64 { +// return s.orderedMap.Count() +//} +// +//// StorageMapIterator is an iterator over StorageMap +//type StorageMapIterator struct { +// gauge common.MemoryGauge +// mapIterator *atree.MapIterator +// storage atree.SlabStorage +//} +// +//// Next returns the next key and value of the storage map iterator. +//// If there is no further key-value pair, ("", nil) is returned. +//func (i StorageMapIterator) Next() (string, Value) { +// k, v, err := i.mapIterator.Next() +// if err != nil { +// panic(errors.NewExternalError(err)) +// } +// +// if k == nil || v == nil { +// return "", nil +// } +// +// key := string(k.(interpreter.StringAtreeValue)) +// value := interpreter.MustConvertStoredValue(i.gauge, v) +// +// vmValue := InterpreterValueToVMValue(i.storage, value) +// return key, vmValue +//} +// +//// NextKey returns the next key of the storage map iterator. +//// If there is no further key, "" is returned. +//func (i StorageMapIterator) NextKey() string { +// k, err := i.mapIterator.NextKey() +// if err != nil { +// panic(errors.NewExternalError(err)) +// } +// +// if k == nil { +// return "" +// } +// +// return string(k.(interpreter.StringAtreeValue)) +//} +// +//// NextValue returns the next value in the storage map iterator. +//// If there is nop further value, nil is returned. +//func (i StorageMapIterator) NextValue() Value { +// v, err := i.mapIterator.NextValue() +// if err != nil { +// panic(errors.NewExternalError(err)) +// } +// +// if v == nil { +// return nil +// } +// +// value := interpreter.MustConvertStoredValue(i.gauge, v) +// +// return InterpreterValueToVMValue(i.storage, value) +//} diff --git a/runtime/bbq/vm/value_authaccount.go b/runtime/bbq/vm/value_authaccount.go index 7421699f86..7f83e51f9b 100644 --- a/runtime/bbq/vm/value_authaccount.go +++ b/runtime/bbq/vm/value_authaccount.go @@ -19,19 +19,25 @@ package vm import ( + "github.com/onflow/atree" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" ) -func NewAuthAccountValue() *CompositeValue { - return &CompositeValue{ - Location: nil, +func NewAuthAccountValue( + address common.Address, +) *SimpleCompositeValue { + return &SimpleCompositeValue{ QualifiedIdentifier: sema.AuthAccountType.QualifiedIdentifier(), typeID: sema.AuthAccountType.ID(), staticType: interpreter.PrimitiveStaticTypeAuthAccount, Kind: common.CompositeKindStructure, + fields: map[string]Value{ + sema.AuthAccountAddressField: AddressValue(address), + // TODO: add the remaining fields + }, } } @@ -52,17 +58,64 @@ func init() { // AuthAccount.save RegisterTypeBoundFunction(typeName, sema.AuthAccountSaveField, NativeFunctionValue{ ParameterCount: len(sema.AuthAccountTypeSaveFunctionType.Parameters), - Function: func(config *Config, typeArguments []StaticType, value ...Value) Value { - // TODO: - return NilValue{} + Function: func(config *Config, typeArs []StaticType, args ...Value) Value { + authAccount, ok := args[0].(*SimpleCompositeValue) + if !ok { + panic(errors.NewUnreachableError()) + } + address := authAccount.GetMember(config, sema.AuthAccountAddressField) + addressValue, ok := address.(AddressValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + value := args[1] + + path, ok := args[2].(PathValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + domain := path.Domain.Identifier() + identifier := path.Identifier + + // Prevent an overwrite + + //if interpreter.storedValueExists( + // address, + // domain, + // identifier, + //) { + // panic("overwrite error") + //} + + value = value.Transfer( + config, + atree.Address(addressValue), + true, + nil, + ) + + // Write new value + + WriteStored( + config.MemoryGauge, + config.Storage, + common.Address(addressValue), + domain, + identifier, + value, + ) + + return VoidValue{} }, }) // AuthAccount.borrow RegisterTypeBoundFunction(typeName, sema.AuthAccountBorrowField, NativeFunctionValue{ ParameterCount: len(sema.AuthAccountTypeBorrowFunctionType.Parameters), - Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { - authAccount, ok := args[0].(*CompositeValue) + Function: func(config *Config, typeArgs []StaticType, args ...Value) Value { + authAccount, ok := args[0].(*SimpleCompositeValue) if !ok { panic(errors.NewUnreachableError()) } @@ -72,7 +125,7 @@ func init() { panic(errors.NewUnreachableError()) } - referenceType, ok := typeArguments[0].(interpreter.ReferenceStaticType) + referenceType, ok := typeArgs[0].(interpreter.ReferenceStaticType) if !ok { panic(errors.NewUnreachableError()) } @@ -84,11 +137,11 @@ func init() { } reference := NewStorageReferenceValue( - nil, + config.Storage, referenceType.Authorized, common.Address(addressValue), path, - referenceType, + referenceType.BorrowedType, ) // Attempt to dereference, diff --git a/runtime/bbq/vm/value_composite.go b/runtime/bbq/vm/value_composite.go index fe3025aa28..28d59c9154 100644 --- a/runtime/bbq/vm/value_composite.go +++ b/runtime/bbq/vm/value_composite.go @@ -127,7 +127,7 @@ func (v *CompositeValue) SetMember(conf *Config, name string, value Value) { // nil, //) - interpreterValue := VMValueToInterpreterValue(value) + interpreterValue := VMValueToInterpreterValue(conf.Storage, value) existingStorable, err := v.dictionary.Set( interpreter.StringAtreeComparator, @@ -141,10 +141,9 @@ func (v *CompositeValue) SetMember(conf *Config, name string, value Value) { } if existingStorable != nil { - // TODO: - //existingValue := interpreter.StoredValue(nil, existingStorable, context.Storage) - //existingValue.DeepRemove(interpreter) - + inter := inter(conf.Storage) + existingValue := interpreter.StoredValue(nil, existingStorable, conf.Storage) + existingValue.DeepRemove(inter) RemoveReferencedSlab(conf.Storage, existingStorable) } } @@ -222,7 +221,7 @@ func (v *CompositeValue) Transfer( if needsStoreTo && v.Kind == common.CompositeKindContract { panic(interpreter.NonTransferableValueError{ - Value: VMValueToInterpreterValue(v), + Value: VMValueToInterpreterValue(conf.Storage, v), }) } diff --git a/runtime/bbq/vm/value_conversions.go b/runtime/bbq/vm/value_conversions.go index 6042aa6a1b..c13c32c517 100644 --- a/runtime/bbq/vm/value_conversions.go +++ b/runtime/bbq/vm/value_conversions.go @@ -19,6 +19,7 @@ package vm import ( + "github.com/onflow/atree" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" @@ -28,7 +29,7 @@ import ( // Utility methods to convert between old and new values. // These are temporary until all parts of the interpreter are migrated to the vm. -func InterpreterValueToVMValue(storage interpreter.Storage, value interpreter.Value) Value { +func InterpreterValueToVMValue(storage atree.SlabStorage, value interpreter.Value) Value { switch value := value.(type) { case interpreter.IntValue: return IntValue{value.BigInt.Int64()} @@ -47,12 +48,12 @@ func InterpreterValueToVMValue(storage interpreter.Storage, value interpreter.Va } } -var inter = func() *interpreter.Interpreter { +var inter = func(storage interpreter.Storage) *interpreter.Interpreter { inter, err := interpreter.NewInterpreter( nil, utils.TestLocation, &interpreter.Config{ - Storage: interpreter.NewInMemoryStorage(nil), + Storage: storage, }, ) @@ -61,9 +62,9 @@ var inter = func() *interpreter.Interpreter { } return inter -}() +} -func VMValueToInterpreterValue(value Value) interpreter.Value { +func VMValueToInterpreterValue(storage interpreter.Storage, value Value) interpreter.Value { switch value := value.(type) { case IntValue: return interpreter.NewIntValueFromInt64(nil, value.SmallInt) @@ -71,7 +72,7 @@ func VMValueToInterpreterValue(value Value) interpreter.Value { return interpreter.NewUnmeteredStringValue(string(value.Str)) case *CompositeValue: return interpreter.NewCompositeValue( - inter, + inter(storage), interpreter.EmptyLocationRange, value.Location, value.QualifiedIdentifier, diff --git a/runtime/bbq/vm/value_simple_composite.go b/runtime/bbq/vm/value_simple_composite.go new file mode 100644 index 0000000000..9de62904ec --- /dev/null +++ b/runtime/bbq/vm/value_simple_composite.go @@ -0,0 +1,153 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/common" +) + +type SimpleCompositeValue struct { + fields map[string]Value + QualifiedIdentifier string + typeID common.TypeID + staticType StaticType + Kind common.CompositeKind +} + +var _ Value = &CompositeValue{} + +func NewSimpleCompositeValue( + qualifiedIdentifier string, + kind common.CompositeKind, + typeID common.TypeID, +) *SimpleCompositeValue { + + return &SimpleCompositeValue{ + QualifiedIdentifier: qualifiedIdentifier, + Kind: kind, + typeID: typeID, + } +} + +func (*SimpleCompositeValue) isValue() {} + +func (v *SimpleCompositeValue) StaticType(memoryGauge common.MemoryGauge) StaticType { + return v.staticType +} + +func (v *SimpleCompositeValue) GetMember(_ *Config, name string) Value { + return v.fields[name] +} + +func (v *SimpleCompositeValue) SetMember(_ *Config, name string, value Value) { + v.fields[name] = value +} + +func (v *SimpleCompositeValue) IsResourceKinded() bool { + return v.Kind == common.CompositeKindResource +} + +func (v *SimpleCompositeValue) String() string { + //TODO implement me + panic("implement me") +} + +func (v *SimpleCompositeValue) Transfer( + conf *Config, + address atree.Address, + remove bool, + storable atree.Storable, +) Value { + return v +} + +func (v *SimpleCompositeValue) Destroy(*Config) { + + //interpreter.ReportComputation(common.ComputationKindDestroyCompositeValue, 1) + // + //if interpreter.Config.InvalidatedResourceValidationEnabled { + // v.checkInvalidatedResourceUse(locationRange) + //} + // + //storageID := v.StorageID() + // + //if interpreter.Config.TracingEnabled { + // startTime := time.Now() + // + // owner := v.GetOwner().String() + // typeID := string(v.TypeID()) + // kind := v.Kind.String() + // + // defer func() { + // + // interpreter.reportCompositeValueDestroyTrace( + // owner, + // typeID, + // kind, + // time.Since(startTime), + // ) + // }() + //} + + //interpreter = v.getInterpreter(interpreter) + + //// if composite was deserialized, dynamically link in the destructor + //if v.Destructor == nil { + // v.Destructor = interpreter.sharedState.typeCodes.CompositeCodes[v.TypeID()].DestructorFunction + //} + // + //destructor := v.Destructor + // + //if destructor != nil { + // invocation := NewInvocation( + // interpreter, + // v, + // nil, + // nil, + // nil, + // locationRange, + // ) + // + // destructor.invoke(invocation) + //} + + //v.isDestroyed = true + + //if interpreter.Config.InvalidatedResourceValidationEnabled { + // v.dictionary = nil + //} + + //interpreter.updateReferencedResource( + // storageID, + // storageID, + // func(value ReferenceTrackedResourceKindedValue) { + // compositeValue, ok := value.(*CompositeValue) + // if !ok { + // panic(errors.NewUnreachableError()) + // } + // + // compositeValue.isDestroyed = true + // + // if interpreter.Config.InvalidatedResourceValidationEnabled { + // compositeValue.dictionary = nil + // } + // }, + //) +} diff --git a/runtime/bbq/vm/value_some.go b/runtime/bbq/vm/value_some.go index 1edef0a12f..3425f0b4c7 100644 --- a/runtime/bbq/vm/value_some.go +++ b/runtime/bbq/vm/value_some.go @@ -39,13 +39,13 @@ func NewSomeValueNonCopying(value Value) *SomeValue { func (*SomeValue) isValue() {} -func (v *SomeValue) StaticType(common.MemoryGauge) StaticType { - innerType := v.value.StaticType(inter) +func (v *SomeValue) StaticType(gauge common.MemoryGauge) StaticType { + innerType := v.value.StaticType(gauge) if innerType == nil { return nil } return interpreter.NewOptionalStaticType( - inter, + gauge, innerType, ) } diff --git a/runtime/bbq/vm/value_storage_reference.go b/runtime/bbq/vm/value_storage_reference.go index 4c2678165f..7497b5ee6b 100644 --- a/runtime/bbq/vm/value_storage_reference.go +++ b/runtime/bbq/vm/value_storage_reference.go @@ -61,10 +61,10 @@ func (v *StorageReferenceValue) StaticType(gauge common.MemoryGauge) StaticType } return interpreter.NewReferenceStaticType( - inter, + gauge, v.Authorized, v.BorrowedType, - (*referencedValue).StaticType(inter), + (*referencedValue).StaticType(gauge), ) } diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 3bc4643a2f..9e283a0f97 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -338,11 +338,19 @@ func opInvoke(vm *VM) { func opInvokeDynamic(vm *VM) { callframe := vm.callFrame funcName := callframe.getString() + typeArgCount := callframe.getUint16() argsCount := callframe.getUint16() stackHeight := len(vm.stack) receiver := vm.stack[stackHeight-int(argsCount)-1] + // TODO: + var typeArguments []StaticType + for i := 0; i < int(typeArgCount); i++ { + typeArg := vm.loadType() + typeArguments = append(typeArguments, typeArg) + } + compositeValue := receiver.(*CompositeValue) qualifiedFuncName := commons.TypeQualifiedName(compositeValue.QualifiedIdentifier, funcName) var functionValue = vm.lookupFunction(compositeValue.Location, qualifiedFuncName) From 5acd4ec72c10e6341ba70da18d2a89f4978a3eac Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 16 May 2024 10:22:04 -0700 Subject: [PATCH 27/89] Fix member get/set --- runtime/bbq/compiler/compiler.go | 4 ++ runtime/bbq/vm/value.go | 5 ++ runtime/bbq/vm/value_composite.go | 11 +-- runtime/bbq/vm/value_conversions.go | 21 +++--- runtime/bbq/vm/value_some.go | 10 +++ runtime/bbq/vm/value_storage_reference.go | 25 ++++++- runtime/bbq/vm/vm.go | 11 +-- runtime/interpreter/value.go | 84 +++++++++++------------ 8 files changed, 107 insertions(+), 64 deletions(-) diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index c50c0813d8..4a51f81571 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -831,6 +831,10 @@ func (c *Compiler) VisitBinaryExpression(expression *ast.BinaryExpression) (_ st switch expression.Operation { case ast.OperationNilCoalesce: + // create a duplicate to perform the equal check. + // So if the condition succeeds, then the result will be at the top of the stack. + c.emit(opcode.Dup) + c.emit(opcode.Nil) c.emit(opcode.Equal) jumpToEnd := c.emitUndefinedJump(opcode.JumpIfFalse) diff --git a/runtime/bbq/vm/value.go b/runtime/bbq/vm/value.go index 786a9dc477..3c3ea9bb32 100644 --- a/runtime/bbq/vm/value.go +++ b/runtime/bbq/vm/value.go @@ -35,3 +35,8 @@ type Value interface { ) Value String() string } + +type MemberAccessibleValue interface { + GetMember(config *Config, name string) Value + SetMember(conf *Config, name string, value Value) +} diff --git a/runtime/bbq/vm/value_composite.go b/runtime/bbq/vm/value_composite.go index 28d59c9154..0d4907a06d 100644 --- a/runtime/bbq/vm/value_composite.go +++ b/runtime/bbq/vm/value_composite.go @@ -35,6 +35,7 @@ type CompositeValue struct { } var _ Value = &CompositeValue{} +var _ MemberAccessibleValue = &CompositeValue{} func NewCompositeValue( location common.Location, @@ -70,13 +71,15 @@ func NewCompositeValue( func newCompositeValueFromOrderedMap( dict *atree.OrderedMap, - typeInfo interpreter.CompositeTypeInfo, + location common.Location, + qualifiedIdentifier string, + kind common.CompositeKind, ) *CompositeValue { return &CompositeValue{ dictionary: dict, - Location: typeInfo.Location, - QualifiedIdentifier: typeInfo.QualifiedIdentifier, - Kind: typeInfo.Kind, + Location: location, + QualifiedIdentifier: qualifiedIdentifier, + Kind: kind, } } diff --git a/runtime/bbq/vm/value_conversions.go b/runtime/bbq/vm/value_conversions.go index c13c32c517..4c1edc9a2a 100644 --- a/runtime/bbq/vm/value_conversions.go +++ b/runtime/bbq/vm/value_conversions.go @@ -20,7 +20,6 @@ package vm import ( "github.com/onflow/atree" - "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/tests/utils" @@ -36,12 +35,11 @@ func InterpreterValueToVMValue(storage atree.SlabStorage, value interpreter.Valu case *interpreter.StringValue: return StringValue{Str: []byte(value.Str)} case *interpreter.CompositeValue: - return NewCompositeValue( + return newCompositeValueFromOrderedMap( + value.Dictionary, value.Location, value.QualifiedIdentifier, value.Kind, - common.Address{}, - storage, ) default: panic(errors.NewUnreachableError()) @@ -71,14 +69,13 @@ func VMValueToInterpreterValue(storage interpreter.Storage, value Value) interpr case StringValue: return interpreter.NewUnmeteredStringValue(string(value.Str)) case *CompositeValue: - return interpreter.NewCompositeValue( - inter(storage), - interpreter.EmptyLocationRange, - value.Location, - value.QualifiedIdentifier, - value.Kind, - nil, - common.Address{}, + return interpreter.NewCompositeValueFromOrderedMap( + value.dictionary, + interpreter.CompositeTypeInfo{ + Location: value.Location, + QualifiedIdentifier: value.QualifiedIdentifier, + Kind: value.Kind, + }, ) default: panic(errors.NewUnreachableError()) diff --git a/runtime/bbq/vm/value_some.go b/runtime/bbq/vm/value_some.go index 3425f0b4c7..83a06609cb 100644 --- a/runtime/bbq/vm/value_some.go +++ b/runtime/bbq/vm/value_some.go @@ -30,6 +30,7 @@ type SomeValue struct { } var _ Value = &SomeValue{} +var _ MemberAccessibleValue = &SomeValue{} func NewSomeValueNonCopying(value Value) *SomeValue { return &SomeValue{ @@ -57,3 +58,12 @@ func (v *SomeValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value func (v *SomeValue) String() string { return v.value.String() } +func (v *SomeValue) GetMember(config *Config, name string) Value { + memberAccessibleValue := (v.value).(MemberAccessibleValue) + return memberAccessibleValue.GetMember(config, name) +} + +func (v *SomeValue) SetMember(config *Config, name string, value Value) { + memberAccessibleValue := (v.value).(MemberAccessibleValue) + memberAccessibleValue.SetMember(config, name, value) +} diff --git a/runtime/bbq/vm/value_storage_reference.go b/runtime/bbq/vm/value_storage_reference.go index 7497b5ee6b..15575a2762 100644 --- a/runtime/bbq/vm/value_storage_reference.go +++ b/runtime/bbq/vm/value_storage_reference.go @@ -34,6 +34,9 @@ type StorageReferenceValue struct { storage interpreter.Storage } +var _ Value = &StorageReferenceValue{} +var _ MemberAccessibleValue = &StorageReferenceValue{} + func NewStorageReferenceValue( storage interpreter.Storage, authorized bool, @@ -50,8 +53,6 @@ func NewStorageReferenceValue( } } -var _ Value = &StorageReferenceValue{} - func (*StorageReferenceValue) isValue() {} func (v *StorageReferenceValue) StaticType(gauge common.MemoryGauge) StaticType { @@ -106,3 +107,23 @@ func (v *StorageReferenceValue) Transfer(*Config, atree.Address, bool, atree.Sto func (v *StorageReferenceValue) String() string { return format.StorageReference } + +func (v *StorageReferenceValue) GetMember(config *Config, name string) Value { + referencedValue, err := v.dereference(config.MemoryGauge) + if err != nil { + panic(err) + } + + memberAccessibleValue := (*referencedValue).(MemberAccessibleValue) + return memberAccessibleValue.GetMember(config, name) +} + +func (v *StorageReferenceValue) SetMember(config *Config, name string, value Value) { + referencedValue, err := v.dereference(config.MemoryGauge) + if err != nil { + panic(err) + } + + memberAccessibleValue := (*referencedValue).(MemberAccessibleValue) + memberAccessibleValue.SetMember(config, name, value) +} diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 9e283a0f97..cd1bd661a8 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -328,6 +328,10 @@ func opInvoke(vm *VM) { } arguments := vm.stack[stackHeight-parameterCount:] + + // TODO: + //vm.dropN(parameterCount) + result := value.Function(vm.config, typeArguments, arguments...) vm.push(result) default: @@ -399,7 +403,7 @@ func opSetField(vm *VM) { fieldNameStr := string(fieldName.Str) // TODO: support all container types - structValue := vm.pop().(*CompositeValue) + structValue := vm.pop().(MemberAccessibleValue) fieldValue := vm.pop() @@ -410,10 +414,9 @@ func opGetField(vm *VM) { fieldName := vm.pop().(StringValue) fieldNameStr := string(fieldName.Str) - // TODO: support all container types - structValue := vm.pop().(*CompositeValue) + memberAccessibleValue := vm.pop().(MemberAccessibleValue) - fieldValue := structValue.GetMember(vm.config, fieldNameStr) + fieldValue := memberAccessibleValue.GetMember(vm.config, fieldNameStr) if fieldValue == nil { panic(interpreter.MissingMemberValueError{ Name: fieldNameStr, diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 35f0a157e2..c12e7349eb 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -13798,7 +13798,7 @@ func (UFix64Value) Scale() int { // CompositeValue type CompositeValue struct { - dictionary *atree.OrderedMap + Dictionary *atree.OrderedMap Location common.Location QualifiedIdentifier string Kind common.CompositeKind @@ -13907,12 +13907,12 @@ func NewCompositeValue( return v } -func newCompositeValueFromOrderedMap( +func NewCompositeValueFromOrderedMap( dict *atree.OrderedMap, typeInfo CompositeTypeInfo, ) *CompositeValue { return &CompositeValue{ - dictionary: dict, + Dictionary: dict, Location: typeInfo.Location, QualifiedIdentifier: typeInfo.QualifiedIdentifier, Kind: typeInfo.Kind, @@ -13930,7 +13930,7 @@ func newCompositeValueFromConstructor( common.UseMemory(gauge, elementOverhead) common.UseMemory(gauge, dataUse) common.UseMemory(gauge, metaDataUse) - return newCompositeValueFromOrderedMap(constructor(), typeInfo) + return NewCompositeValueFromOrderedMap(constructor(), typeInfo) } var _ Value = &CompositeValue{} @@ -14038,7 +14038,7 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio v.isDestroyed = true if interpreter.Config.InvalidatedResourceValidationEnabled { - v.dictionary = nil + v.Dictionary = nil } interpreter.updateReferencedResource( @@ -14053,7 +14053,7 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio compositeValue.isDestroyed = true if interpreter.Config.InvalidatedResourceValidationEnabled { - compositeValue.dictionary = nil + compositeValue.Dictionary = nil } }, ) @@ -14089,7 +14089,7 @@ func (v *CompositeValue) GetMember(interpreter *Interpreter, locationRange Locat return v.OwnerValue(interpreter, locationRange) } - storable, err := v.dictionary.Get( + storable, err := v.Dictionary.Get( StringAtreeComparator, StringAtreeHashInput, StringAtreeValue(name), @@ -14149,7 +14149,7 @@ func (v *CompositeValue) GetMember(interpreter *Interpreter, locationRange Locat } func (v *CompositeValue) checkInvalidatedResourceUse(locationRange LocationRange) { - if v.isDestroyed || (v.dictionary == nil && v.Kind == common.CompositeKindResource) { + if v.isDestroyed || (v.Dictionary == nil && v.Kind == common.CompositeKindResource) { panic(InvalidatedResourceError{ LocationRange: locationRange, }) @@ -14223,7 +14223,7 @@ func (v *CompositeValue) RemoveMember( // No need to clean up storable for passed-in key value, // as atree never calls Storable() - existingKeyStorable, existingValueStorable, err := v.dictionary.Remove( + existingKeyStorable, existingValueStorable, err := v.Dictionary.Remove( StringAtreeComparator, StringAtreeHashInput, StringAtreeValue(name), @@ -14234,7 +14234,7 @@ func (v *CompositeValue) RemoveMember( } panic(errors.NewExternalError(err)) } - interpreter.maybeValidateAtreeValue(v.dictionary) + interpreter.maybeValidateAtreeValue(v.Dictionary) storage := interpreter.Config.Storage @@ -14292,7 +14292,7 @@ func (v *CompositeValue) SetMember( nil, ) - existingStorable, err := v.dictionary.Set( + existingStorable, err := v.Dictionary.Set( StringAtreeComparator, StringAtreeHashInput, NewStringAtreeValue(interpreter, name), @@ -14301,7 +14301,7 @@ func (v *CompositeValue) SetMember( if err != nil { panic(errors.NewExternalError(err)) } - interpreter.maybeValidateAtreeValue(v.dictionary) + interpreter.maybeValidateAtreeValue(v.Dictionary) if existingStorable != nil { existingValue := StoredValue(interpreter, existingStorable, interpreter.Config.Storage) @@ -14331,7 +14331,7 @@ func (v *CompositeValue) MeteredString(memoryGauge common.MemoryGauge, seenRefer strLen := emptyCompositeStringLen var fields []CompositeField - _ = v.dictionary.Iterate(func(key atree.Value, value atree.Value) (resume bool, err error) { + _ = v.Dictionary.Iterate(func(key atree.Value, value atree.Value) (resume bool, err error) { field := NewCompositeField( memoryGauge, string(key.(StringAtreeValue)), @@ -14393,7 +14393,7 @@ func (v *CompositeValue) GetField(interpreter *Interpreter, locationRange Locati v.checkInvalidatedResourceUse(locationRange) } - storable, err := v.dictionary.Get( + storable, err := v.Dictionary.Get( StringAtreeComparator, StringAtreeHashInput, StringAtreeValue(name), @@ -14405,7 +14405,7 @@ func (v *CompositeValue) GetField(interpreter *Interpreter, locationRange Locati panic(errors.NewExternalError(err)) } - return StoredValue(interpreter, storable, v.dictionary.Storage) + return StoredValue(interpreter, storable, v.Dictionary.Storage) } func (v *CompositeValue) Equal(interpreter *Interpreter, locationRange LocationRange, other Value) bool { @@ -14416,12 +14416,12 @@ func (v *CompositeValue) Equal(interpreter *Interpreter, locationRange LocationR if !v.StaticType(interpreter).Equal(otherComposite.StaticType(interpreter)) || v.Kind != otherComposite.Kind || - v.dictionary.Count() != otherComposite.dictionary.Count() { + v.Dictionary.Count() != otherComposite.Dictionary.Count() { return false } - iterator, err := v.dictionary.Iterator() + iterator, err := v.Dictionary.Iterator() if err != nil { panic(errors.NewExternalError(err)) } @@ -14528,7 +14528,7 @@ func (v *CompositeValue) ConformsToStaticType( return false } - fieldsLen := int(v.dictionary.Count()) + fieldsLen := int(v.Dictionary.Count()) if v.ComputedFields != nil { fieldsLen += len(v.ComputedFields) } @@ -14621,7 +14621,7 @@ func (v *CompositeValue) Transfer( storable atree.Storable, ) Value { - baseUse, elementOverhead, dataUse, metaDataUse := common.NewCompositeMemoryUsages(v.dictionary.Count(), 0) + baseUse, elementOverhead, dataUse, metaDataUse := common.NewCompositeMemoryUsages(v.Dictionary.Count(), 0) common.UseMemory(interpreter, baseUse) common.UseMemory(interpreter, elementOverhead) common.UseMemory(interpreter, dataUse) @@ -14653,7 +14653,7 @@ func (v *CompositeValue) Transfer( currentStorageID := v.StorageID() currentAddress := currentStorageID.Address - dictionary := v.dictionary + dictionary := v.Dictionary needsStoreTo := address != currentAddress isResourceKinded := v.IsResourceKinded(interpreter) @@ -14665,22 +14665,22 @@ func (v *CompositeValue) Transfer( } if needsStoreTo || !isResourceKinded { - iterator, err := v.dictionary.Iterator() + iterator, err := v.Dictionary.Iterator() if err != nil { panic(errors.NewExternalError(err)) } - elementMemoryUse := common.NewAtreeMapPreAllocatedElementsMemoryUsage(v.dictionary.Count(), 0) + elementMemoryUse := common.NewAtreeMapPreAllocatedElementsMemoryUsage(v.Dictionary.Count(), 0) common.UseMemory(interpreter.Config.MemoryGauge, elementMemoryUse) dictionary, err = atree.NewMapFromBatchData( interpreter.Config.Storage, address, atree.NewDefaultDigesterBuilder(), - v.dictionary.Type(), + v.Dictionary.Type(), StringAtreeComparator, StringAtreeHashInput, - v.dictionary.Seed(), + v.Dictionary.Seed(), func() (atree.Value, atree.Value, error) { atreeKey, atreeValue, err := iterator.Next() @@ -14705,14 +14705,14 @@ func (v *CompositeValue) Transfer( } if remove { - err = v.dictionary.PopIterate(func(nameStorable atree.Storable, valueStorable atree.Storable) { + err = v.Dictionary.PopIterate(func(nameStorable atree.Storable, valueStorable atree.Storable) { interpreter.RemoveReferencedSlab(nameStorable) interpreter.RemoveReferencedSlab(valueStorable) }) if err != nil { panic(errors.NewExternalError(err)) } - interpreter.maybeValidateAtreeValue(v.dictionary) + interpreter.maybeValidateAtreeValue(v.Dictionary) interpreter.RemoveReferencedSlab(storable) } @@ -14731,9 +14731,9 @@ func (v *CompositeValue) Transfer( // to be transferred/moved again (see beginning of this function) if interpreter.Config.InvalidatedResourceValidationEnabled { - v.dictionary = nil + v.Dictionary = nil } else { - v.dictionary = dictionary + v.Dictionary = dictionary res = v } @@ -14747,7 +14747,7 @@ func (v *CompositeValue) Transfer( if !ok { panic(errors.NewUnreachableError()) } - compositeValue.dictionary = dictionary + compositeValue.Dictionary = dictionary }, ) } @@ -14759,7 +14759,7 @@ func (v *CompositeValue) Transfer( v.QualifiedIdentifier, v.Kind, ) - res = newCompositeValueFromOrderedMap(dictionary, info) + res = NewCompositeValueFromOrderedMap(dictionary, info) res.InjectedFields = v.InjectedFields res.ComputedFields = v.ComputedFields res.NestedVariables = v.NestedVariables @@ -14799,22 +14799,22 @@ func (v *CompositeValue) ResourceUUID(interpreter *Interpreter, locationRange Lo func (v *CompositeValue) Clone(interpreter *Interpreter) Value { - iterator, err := v.dictionary.Iterator() + iterator, err := v.Dictionary.Iterator() if err != nil { panic(errors.NewExternalError(err)) } - elementMemoryUse := common.NewAtreeMapPreAllocatedElementsMemoryUsage(v.dictionary.Count(), 0) + elementMemoryUse := common.NewAtreeMapPreAllocatedElementsMemoryUsage(v.Dictionary.Count(), 0) common.UseMemory(interpreter.Config.MemoryGauge, elementMemoryUse) dictionary, err := atree.NewMapFromBatchData( interpreter.Config.Storage, v.StorageID().Address, atree.NewDefaultDigesterBuilder(), - v.dictionary.Type(), + v.Dictionary.Type(), StringAtreeComparator, StringAtreeHashInput, - v.dictionary.Seed(), + v.Dictionary.Seed(), func() (atree.Value, atree.Value, error) { atreeKey, atreeValue, err := iterator.Next() @@ -14836,7 +14836,7 @@ func (v *CompositeValue) Clone(interpreter *Interpreter) Value { } return &CompositeValue{ - dictionary: dictionary, + Dictionary: dictionary, Location: v.Location, QualifiedIdentifier: v.QualifiedIdentifier, Kind: v.Kind, @@ -14873,9 +14873,9 @@ func (v *CompositeValue) DeepRemove(interpreter *Interpreter) { // Remove nested values and storables - storage := v.dictionary.Storage + storage := v.Dictionary.Storage - err := v.dictionary.PopIterate(func(nameStorable atree.Storable, valueStorable atree.Storable) { + err := v.Dictionary.PopIterate(func(nameStorable atree.Storable, valueStorable atree.Storable) { // NOTE: key / field name is stringAtreeValue, // and not a Value, so no need to deep remove interpreter.RemoveReferencedSlab(nameStorable) @@ -14887,7 +14887,7 @@ func (v *CompositeValue) DeepRemove(interpreter *Interpreter) { if err != nil { panic(errors.NewExternalError(err)) } - interpreter.maybeValidateAtreeValue(v.dictionary) + interpreter.maybeValidateAtreeValue(v.Dictionary) } func (v *CompositeValue) GetOwner() common.Address { @@ -14898,7 +14898,7 @@ func (v *CompositeValue) GetOwner() common.Address { // It does NOT iterate over computed fields and functions! func (v *CompositeValue) ForEachField(gauge common.MemoryGauge, f func(fieldName string, fieldValue Value)) { - err := v.dictionary.Iterate(func(key atree.Value, value atree.Value) (resume bool, err error) { + err := v.Dictionary.Iterate(func(key atree.Value, value atree.Value) (resume bool, err error) { f( string(key.(StringAtreeValue)), MustConvertStoredValue(gauge, value), @@ -14911,7 +14911,7 @@ func (v *CompositeValue) ForEachField(gauge common.MemoryGauge, f func(fieldName } func (v *CompositeValue) StorageID() atree.StorageID { - return v.dictionary.StorageID() + return v.Dictionary.StorageID() } func (v *CompositeValue) RemoveField( @@ -14920,7 +14920,7 @@ func (v *CompositeValue) RemoveField( name string, ) { - existingKeyStorable, existingValueStorable, err := v.dictionary.Remove( + existingKeyStorable, existingValueStorable, err := v.Dictionary.Remove( StringAtreeComparator, StringAtreeHashInput, StringAtreeValue(name), @@ -14931,7 +14931,7 @@ func (v *CompositeValue) RemoveField( } panic(errors.NewExternalError(err)) } - interpreter.maybeValidateAtreeValue(v.dictionary) + interpreter.maybeValidateAtreeValue(v.Dictionary) storage := interpreter.Config.Storage From 6a0e25f513c5043fabd895edfd32d73295bbba99 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 16 May 2024 11:02:27 -0700 Subject: [PATCH 28/89] Fix transaction execute block argument passing --- runtime/bbq/vm/types.go | 2 +- runtime/bbq/vm/vm.go | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/runtime/bbq/vm/types.go b/runtime/bbq/vm/types.go index ede2085c38..78d64a6f86 100644 --- a/runtime/bbq/vm/types.go +++ b/runtime/bbq/vm/types.go @@ -34,5 +34,5 @@ func IsSubType(sourceType, targetType StaticType) bool { // TODO: Add the remaining subType rules - return false + return true } diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index cd1bd661a8..539e7f44d3 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -183,13 +183,13 @@ func (vm *VM) ExecuteTransaction(transactionArgs []Value, signers ...Value) erro } } - args := make([]Value, 0, len(signers)+1) - args = append(args, transaction) - args = append(args, signers...) + prepareArgs := make([]Value, 0, len(signers)+1) + prepareArgs = append(prepareArgs, transaction) + prepareArgs = append(prepareArgs, signers...) // Invoke 'prepare', if exists. if prepare, ok := vm.globals[commons.TransactionPrepareFunctionName]; ok { - _, err = vm.invoke(prepare, args) + _, err = vm.invoke(prepare, prepareArgs) if err != nil { return err } @@ -198,8 +198,9 @@ func (vm *VM) ExecuteTransaction(transactionArgs []Value, signers ...Value) erro // TODO: Invoke pre/post conditions // Invoke 'execute', if exists. + executeArgs := []Value{transaction} if execute, ok := vm.globals[commons.TransactionExecuteFunctionName]; ok { - _, err = vm.invoke(execute, args) + _, err = vm.invoke(execute, executeArgs) return err } From 06c68e901a33e4ae2d917d04711c4a06abe08295 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 17 May 2024 07:13:34 -0700 Subject: [PATCH 29/89] Add account link function --- runtime/bbq/vm/storage.go | 2 +- runtime/bbq/vm/value_authaccount.go | 61 +++++++++++++++++++++-- runtime/bbq/vm/value_capability.go | 41 +++++++++++++++ runtime/bbq/vm/value_composite.go | 2 +- runtime/bbq/vm/value_conversions.go | 22 +++++++- runtime/bbq/vm/value_link.go | 57 +++++++++++++++++++++ runtime/bbq/vm/value_storage_reference.go | 2 +- 7 files changed, 179 insertions(+), 8 deletions(-) create mode 100644 runtime/bbq/vm/value_link.go diff --git a/runtime/bbq/vm/storage.go b/runtime/bbq/vm/storage.go index b5230188a6..2610bc213f 100644 --- a/runtime/bbq/vm/storage.go +++ b/runtime/bbq/vm/storage.go @@ -33,7 +33,7 @@ import ( func StoredValue(gauge common.MemoryGauge, storable atree.Storable, storage atree.SlabStorage) Value { // Delegate value := interpreter.StoredValue(gauge, storable, storage) - return InterpreterValueToVMValue(storage, value) + return InterpreterValueToVMValue(value) } func WriteStored( diff --git a/runtime/bbq/vm/value_authaccount.go b/runtime/bbq/vm/value_authaccount.go index 7f83e51f9b..4ce71c86e4 100644 --- a/runtime/bbq/vm/value_authaccount.go +++ b/runtime/bbq/vm/value_authaccount.go @@ -49,9 +49,64 @@ func init() { // AuthAccount.link RegisterTypeBoundFunction(typeName, sema.AuthAccountLinkField, NativeFunctionValue{ ParameterCount: len(sema.AuthAccountTypeLinkFunctionType.Parameters), - Function: func(config *Config, typeArguments []StaticType, value ...Value) Value { - // TODO: - return NilValue{} + Function: func(config *Config, typeArgs []StaticType, args ...Value) Value { + borrowType, ok := typeArgs[0].(interpreter.ReferenceStaticType) + if !ok { + panic(errors.NewUnreachableError()) + } + + authAccount, ok := args[0].(*SimpleCompositeValue) + if !ok { + panic(errors.NewUnreachableError()) + } + address := authAccount.GetMember(config, sema.AuthAccountAddressField) + addressValue, ok := address.(AddressValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + newCapabilityPath, ok := args[1].(PathValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + targetPath, ok := args[2].(PathValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + newCapabilityDomain := newCapabilityPath.Domain.Identifier() + newCapabilityIdentifier := newCapabilityPath.Identifier + + //if interpreter.storedValueExists( + // address, + // newCapabilityDomain, + // newCapabilityIdentifier, + //) { + // return Nil + //} + + // Write new value + + // Note that this will be metered twice if Atree validation is enabled. + linkValue := NewLinkValue(targetPath, borrowType) + + WriteStored( + config.MemoryGauge, + config.Storage, + common.Address(addressValue), + newCapabilityDomain, + newCapabilityIdentifier, + linkValue, + ) + + return NewSomeValueNonCopying( + NewCapabilityValue( + addressValue, + newCapabilityPath, + borrowType, + ), + ) }, }) diff --git a/runtime/bbq/vm/value_capability.go b/runtime/bbq/vm/value_capability.go index 1236342a95..8fab944c21 100644 --- a/runtime/bbq/vm/value_capability.go +++ b/runtime/bbq/vm/value_capability.go @@ -19,12 +19,53 @@ package vm import ( + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/format" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" ) // members +type CapabilityValue struct { + Address AddressValue + Path PathValue + BorrowType StaticType +} + +var _ Value = CapabilityValue{} + +func NewCapabilityValue(address AddressValue, path PathValue, borrowType StaticType) CapabilityValue { + return CapabilityValue{ + Address: address, + Path: path, + BorrowType: borrowType, + } +} + +func (CapabilityValue) isValue() {} + +func (v CapabilityValue) StaticType(gauge common.MemoryGauge) StaticType { + return interpreter.NewCapabilityStaticType(gauge, v.BorrowType) +} + +func (v CapabilityValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { + return v +} + +func (v CapabilityValue) String() string { + var borrowType string + if v.BorrowType != nil { + borrowType = v.BorrowType.String() + } + return format.Capability( + borrowType, + v.Address.String(), + v.Path.String(), + ) +} + func init() { typeName := interpreter.PrimitiveStaticTypeCapability.String() diff --git a/runtime/bbq/vm/value_composite.go b/runtime/bbq/vm/value_composite.go index 0d4907a06d..46d92eb25a 100644 --- a/runtime/bbq/vm/value_composite.go +++ b/runtime/bbq/vm/value_composite.go @@ -260,7 +260,7 @@ func (v *CompositeValue) Transfer( value := interpreter.MustConvertStoredValue(conf.MemoryGauge, atreeValue) - vmValue := InterpreterValueToVMValue(nil, value) + vmValue := InterpreterValueToVMValue(value) vmValue.Transfer(conf, address, remove, nil) return atreeKey, value, nil diff --git a/runtime/bbq/vm/value_conversions.go b/runtime/bbq/vm/value_conversions.go index 4c1edc9a2a..cfafa595f1 100644 --- a/runtime/bbq/vm/value_conversions.go +++ b/runtime/bbq/vm/value_conversions.go @@ -19,7 +19,6 @@ package vm import ( - "github.com/onflow/atree" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/tests/utils" @@ -28,7 +27,7 @@ import ( // Utility methods to convert between old and new values. // These are temporary until all parts of the interpreter are migrated to the vm. -func InterpreterValueToVMValue(storage atree.SlabStorage, value interpreter.Value) Value { +func InterpreterValueToVMValue(value interpreter.Value) Value { switch value := value.(type) { case interpreter.IntValue: return IntValue{value.BigInt.Int64()} @@ -77,6 +76,25 @@ func VMValueToInterpreterValue(storage interpreter.Storage, value Value) interpr Kind: value.Kind, }, ) + case *CapabilityValue: + return interpreter.NewCapabilityValue( + nil, + VMValueToInterpreterValue(storage, value.Address).(interpreter.AddressValue), + VMValueToInterpreterValue(storage, value.Path).(interpreter.PathValue), + value.BorrowType, + ) + case LinkValue: + return interpreter.LinkValue{ + TargetPath: VMValueToInterpreterValue(storage, value.TargetPath).(interpreter.PathValue), + Type: value.StaticType(nil), + } + case AddressValue: + return interpreter.AddressValue(value) + case PathValue: + return interpreter.PathValue{ + Domain: value.Domain, + Identifier: value.Identifier, + } default: panic(errors.NewUnreachableError()) } diff --git a/runtime/bbq/vm/value_link.go b/runtime/bbq/vm/value_link.go new file mode 100644 index 0000000000..0d58684fe4 --- /dev/null +++ b/runtime/bbq/vm/value_link.go @@ -0,0 +1,57 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/format" + "github.com/onflow/cadence/runtime/interpreter" +) + +type LinkValue struct { + TargetPath PathValue + Type StaticType +} + +var _ Value = LinkValue{} + +func NewLinkValue(targetPath PathValue, staticType StaticType) LinkValue { + return LinkValue{ + TargetPath: targetPath, + Type: staticType, + } +} + +func (LinkValue) isValue() {} + +func (v LinkValue) StaticType(gauge common.MemoryGauge) StaticType { + return interpreter.NewCapabilityStaticType(gauge, v.Type) +} + +func (v LinkValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { + return v +} + +func (v LinkValue) String() string { + return format.Link( + v.Type.String(), + v.TargetPath.String(), + ) +} diff --git a/runtime/bbq/vm/value_storage_reference.go b/runtime/bbq/vm/value_storage_reference.go index 15575a2762..50806e1962 100644 --- a/runtime/bbq/vm/value_storage_reference.go +++ b/runtime/bbq/vm/value_storage_reference.go @@ -80,7 +80,7 @@ func (v *StorageReferenceValue) dereference(gauge common.MemoryGauge) (*Value, e } referenced := accountStorage.ReadValue(gauge, identifier) - vmReferencedValue := InterpreterValueToVMValue(v.storage, referenced) + vmReferencedValue := InterpreterValueToVMValue(referenced) if v.BorrowedType != nil { staticType := vmReferencedValue.StaticType(gauge) From 02c3d3dd6fedc693cae4d802407ba70900c88d0a Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 22 May 2024 14:03:11 -0700 Subject: [PATCH 30/89] Add public account functions --- runtime/bbq/compiler/compiler.go | 14 ++- runtime/bbq/opcode/opcode.go | 2 + runtime/bbq/opcode/opcode_string.go | 44 ++++---- runtime/bbq/vm/ft_test.go | 2 +- runtime/bbq/vm/native_functions.go | 4 +- runtime/bbq/vm/storage.go | 17 ++++ runtime/bbq/vm/value_capability.go | 105 +++++++++++++++++++- runtime/bbq/vm/value_conversions.go | 10 ++ runtime/bbq/vm/value_path.go | 2 + runtime/bbq/vm/value_publicaccount.go | 56 ++++++++++- runtime/bbq/vm/vm.go | 33 ++++++ runtime/sema/check_invocation_expression.go | 2 + runtime/sema/elaboration.go | 1 + 13 files changed, 258 insertions(+), 34 deletions(-) diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index 4a51f81571..efa5b88334 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -766,6 +766,12 @@ func (c *Compiler) loadArguments(expression *ast.InvocationExpression) { c.compileExpression(argument.Expression) c.emitCheckType(invocationTypes.ArgumentTypes[index]) } + + // TODO: Is this needed? + // Load empty values for optional parameters, if they are not provided. + for i := len(expression.Arguments); i < invocationTypes.ParamCount; i++ { + c.emit(opcode.Empty) + } } func (c *Compiler) loadTypeArguments(expression *ast.InvocationExpression) []byte { @@ -837,9 +843,13 @@ func (c *Compiler) VisitBinaryExpression(expression *ast.BinaryExpression) (_ st c.emit(opcode.Nil) c.emit(opcode.Equal) - jumpToEnd := c.emitUndefinedJump(opcode.JumpIfFalse) + elseJump := c.emitUndefinedJump(opcode.JumpIfFalse) c.compileExpression(expression.Right) - c.patchJump(jumpToEnd) + + thenJump := c.emitUndefinedJump(opcode.Jump) + c.patchJump(elseJump) + c.emit(opcode.Unwrap) + c.patchJump(thenJump) default: c.compileExpression(expression.Right) c.emit(intBinaryOpcodes[expression.Operation]) diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index 22b3fadb07..0750ec33df 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -43,6 +43,7 @@ const ( IntGreaterOrEqual Equal + Unwrap GetConstant True @@ -67,4 +68,5 @@ const ( Drop Dup + Empty ) diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index 81f5ddcd6f..41a1700764 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -25,30 +25,32 @@ func _() { _ = x[IntLessOrEqual-14] _ = x[IntGreaterOrEqual-15] _ = x[Equal-16] - _ = x[GetConstant-17] - _ = x[True-18] - _ = x[False-19] - _ = x[GetLocal-20] - _ = x[SetLocal-21] - _ = x[GetGlobal-22] - _ = x[SetGlobal-23] - _ = x[GetField-24] - _ = x[SetField-25] - _ = x[Invoke-26] - _ = x[InvokeDynamic-27] - _ = x[Destroy-28] - _ = x[Transfer-29] - _ = x[Cast-30] - _ = x[New-31] - _ = x[Path-32] - _ = x[Nil-33] - _ = x[Drop-34] - _ = x[Dup-35] + _ = x[Unwrap-17] + _ = x[GetConstant-18] + _ = x[True-19] + _ = x[False-20] + _ = x[GetLocal-21] + _ = x[SetLocal-22] + _ = x[GetGlobal-23] + _ = x[SetGlobal-24] + _ = x[GetField-25] + _ = x[SetField-26] + _ = x[Invoke-27] + _ = x[InvokeDynamic-28] + _ = x[Destroy-29] + _ = x[Transfer-30] + _ = x[Cast-31] + _ = x[New-32] + _ = x[Path-33] + _ = x[Nil-34] + _ = x[Drop-35] + _ = x[Dup-36] + _ = x[Empty-37] } -const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualEqualGetConstantTrueFalseGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeInvokeDynamicDestroyTransferCastNewPathNilDropDup" +const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualEqualUnwrapGetConstantTrueFalseGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeInvokeDynamicDestroyTransferCastNewPathNilDropDupEmpty" -var _Opcode_index = [...]uint16{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 154, 165, 169, 174, 182, 190, 199, 208, 216, 224, 230, 243, 250, 258, 262, 265, 269, 272, 276, 279} +var _Opcode_index = [...]uint16{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 154, 160, 171, 175, 180, 188, 196, 205, 214, 222, 230, 236, 249, 256, 264, 268, 271, 275, 278, 282, 285, 290} func (i Opcode) String() string { if i >= Opcode(len(_Opcode_index)-1) { diff --git a/runtime/bbq/vm/ft_test.go b/runtime/bbq/vm/ft_test.go index 6837493342..63f4aaad8c 100644 --- a/runtime/bbq/vm/ft_test.go +++ b/runtime/bbq/vm/ft_test.go @@ -222,7 +222,7 @@ func TestFTTransfer(t *testing.T) { args := []Value{ IntValue{1}, - AddressValue(common.Address{0x01}), + AddressValue(address), } err = tokenTransferTxVM.ExecuteTransaction(args, authorizer) diff --git a/runtime/bbq/vm/native_functions.go b/runtime/bbq/vm/native_functions.go index 324abaa285..d5a64196ab 100644 --- a/runtime/bbq/vm/native_functions.go +++ b/runtime/bbq/vm/native_functions.go @@ -71,8 +71,8 @@ func init() { RegisterFunction(commons.GetAccountFunctionName, NativeFunctionValue{ ParameterCount: len(stdlib.PanicFunctionType.Parameters), Function: func(config *Config, typeArguments []StaticType, arguments ...Value) Value { - // TODO: Properly implement - return VoidValue{} + address := arguments[0].(AddressValue) + return NewPublicAccountValue(common.Address(address)) }, }) } diff --git a/runtime/bbq/vm/storage.go b/runtime/bbq/vm/storage.go index 2610bc213f..90d82ed889 100644 --- a/runtime/bbq/vm/storage.go +++ b/runtime/bbq/vm/storage.go @@ -30,12 +30,29 @@ import ( // GetStorageMap(address common.Address, domain string, createIfNotExists bool) *StorageMap // CheckHealth() error // } + func StoredValue(gauge common.MemoryGauge, storable atree.Storable, storage atree.SlabStorage) Value { // Delegate value := interpreter.StoredValue(gauge, storable, storage) return InterpreterValueToVMValue(value) } +func ReadStored( + gauge common.MemoryGauge, + storage interpreter.Storage, + address common.Address, + domain string, + identifier string, +) Value { + accountStorage := storage.GetStorageMap(address, domain, false) + if accountStorage == nil { + return nil + } + + referenced := accountStorage.ReadValue(gauge, identifier) + return InterpreterValueToVMValue(referenced) +} + func WriteStored( gauge common.MemoryGauge, storage interpreter.Storage, diff --git a/runtime/bbq/vm/value_capability.go b/runtime/bbq/vm/value_capability.go index 8fab944c21..50d873cd4f 100644 --- a/runtime/bbq/vm/value_capability.go +++ b/runtime/bbq/vm/value_capability.go @@ -19,6 +19,7 @@ package vm import ( + "fmt" "github.com/onflow/atree" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/format" @@ -71,10 +72,106 @@ func init() { // Capability.borrow RegisterTypeBoundFunction(typeName, sema.CapabilityTypeBorrowField, NativeFunctionValue{ - ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), - Function: func(config *Config, typeArguments []StaticType, value ...Value) Value { - // TODO: - return NilValue{} + ParameterCount: 0, + Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { + capabilityValue := args[0].(CapabilityValue) + + // NOTE: if a type argument is provided for the function, + // use it *instead* of the type of the value (if any) + var borrowType interpreter.ReferenceStaticType + if len(typeArguments) > 0 { + borrowType = typeArguments[0].(interpreter.ReferenceStaticType) + } else { + borrowType = capabilityValue.BorrowType.(interpreter.ReferenceStaticType) + } + + address := capabilityValue.Address + + targetPath, authorized, err := getCapabilityFinalTargetPath( + config.Storage, + common.Address(address), + capabilityValue.Path, + borrowType, + ) + if err != nil { + panic(err) + } + + reference := NewStorageReferenceValue( + config.Storage, + authorized, + common.Address(address), + targetPath, + borrowType, + ) + + // Attempt to dereference, + // which reads the stored value + // and performs a dynamic type check + + value, err := reference.dereference(config.MemoryGauge) + if err != nil { + panic(err) + } + if value == nil { + return NilValue{} + } + + return NewSomeValueNonCopying(reference) }, }) } + +func getCapabilityFinalTargetPath( + storage interpreter.Storage, + address common.Address, + path PathValue, + wantedBorrowType interpreter.ReferenceStaticType, +) ( + finalPath PathValue, + authorized bool, + err error, +) { + wantedReferenceType := wantedBorrowType + + seenPaths := map[PathValue]struct{}{} + paths := []PathValue{path} + + for { + // Detect cyclic links + + if _, ok := seenPaths[path]; ok { + return EmptyPathValue, false, fmt.Errorf("cyclic link error") + } else { + seenPaths[path] = struct{}{} + } + + value := ReadStored( + nil, + storage, + address, + path.Domain.Identifier(), + path.Identifier, + ) + + if value == nil { + return EmptyPathValue, false, nil + } + + if link, ok := value.(LinkValue); ok { + + //allowedType := interpreter.MustConvertStaticToSemaType(link.Type) + + //if !sema.IsSubType(allowedType, wantedBorrowType) { + // return EmptyPathValue, false, nil + //} + + targetPath := link.TargetPath + paths = append(paths, targetPath) + path = targetPath + + } else { + return path, wantedReferenceType.Authorized, nil + } + } +} diff --git a/runtime/bbq/vm/value_conversions.go b/runtime/bbq/vm/value_conversions.go index cfafa595f1..8e9cde6158 100644 --- a/runtime/bbq/vm/value_conversions.go +++ b/runtime/bbq/vm/value_conversions.go @@ -40,6 +40,16 @@ func InterpreterValueToVMValue(value interpreter.Value) Value { value.QualifiedIdentifier, value.Kind, ) + case interpreter.LinkValue: + return NewLinkValue( + InterpreterValueToVMValue(value.TargetPath).(PathValue), + value.Type, + ) + case interpreter.PathValue: + return PathValue{ + Domain: value.Domain, + Identifier: value.Identifier, + } default: panic(errors.NewUnreachableError()) } diff --git a/runtime/bbq/vm/value_path.go b/runtime/bbq/vm/value_path.go index 4b114ac3c4..04729ade18 100644 --- a/runtime/bbq/vm/value_path.go +++ b/runtime/bbq/vm/value_path.go @@ -32,6 +32,8 @@ type PathValue struct { Identifier string } +var EmptyPathValue = PathValue{} + var _ Value = PathValue{} func (PathValue) isValue() {} diff --git a/runtime/bbq/vm/value_publicaccount.go b/runtime/bbq/vm/value_publicaccount.go index 8abd35fe50..27ba9da498 100644 --- a/runtime/bbq/vm/value_publicaccount.go +++ b/runtime/bbq/vm/value_publicaccount.go @@ -19,10 +19,27 @@ package vm import ( + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" ) +func NewPublicAccountValue( + address common.Address, +) *SimpleCompositeValue { + return &SimpleCompositeValue{ + QualifiedIdentifier: sema.PublicAccountType.QualifiedIdentifier(), + typeID: sema.PublicAccountType.ID(), + staticType: interpreter.PrimitiveStaticTypePublicAccount, + Kind: common.CompositeKindStructure, + fields: map[string]Value{ + sema.PublicAccountAddressField: AddressValue(address), + // TODO: add the remaining fields + }, + } +} + // members func init() { @@ -30,10 +47,41 @@ func init() { // PublicAccount.getCapability RegisterTypeBoundFunction(typeName, sema.PublicAccountGetCapabilityField, NativeFunctionValue{ - ParameterCount: len(sema.StringTypeConcatFunctionType.Parameters), - Function: func(config *Config, typeArguments []StaticType, value ...Value) Value { - // TODO: - return NilValue{} + ParameterCount: len(sema.PublicAccountTypeGetCapabilityFunctionType.Parameters), + Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { + // Get address field from the receiver (PublicAccount) + authAccount, ok := args[0].(*SimpleCompositeValue) + if !ok { + panic(errors.NewUnreachableError()) + } + address := authAccount.GetMember(config, sema.PublicAccountAddressField) + addressValue, ok := address.(AddressValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + // Path argument + path, ok := args[1].(PathValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + //pathStaticType := path.StaticType(config.MemoryGauge) + // + //if !IsSubType(pathStaticType, pathType) { + // panic(fmt.Errorf("type mismatch")) + //} + + // NOTE: the type parameter is optional, for backwards compatibility + + var borrowType *interpreter.ReferenceStaticType + if len(typeArguments) > 0 { + ty := typeArguments[1] + // we handle the nil case for this below + borrowType, _ = ty.(*interpreter.ReferenceStaticType) + } + + return NewCapabilityValue(addressValue, path, borrowType) }, }) } diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 539e7f44d3..b6eeeed87d 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -356,7 +356,20 @@ func opInvokeDynamic(vm *VM) { typeArguments = append(typeArguments, typeArg) } + switch typedReceiver := receiver.(type) { + case *StorageReferenceValue: + referenced, err := typedReceiver.dereference(vm.config.MemoryGauge) + if err != nil { + panic(err) + } + receiver = *referenced + + // TODO: + //case ReferenceValue + } + compositeValue := receiver.(*CompositeValue) + qualifiedFuncName := commons.TypeQualifiedName(compositeValue.QualifiedIdentifier, funcName) var functionValue = vm.lookupFunction(compositeValue.Location, qualifiedFuncName) @@ -375,6 +388,10 @@ func opDup(vm *VM) { vm.push(top) } +func opEmpty(vm *VM) { + vm.push(nil) +} + func opNew(vm *VM) { callframe := vm.callFrame @@ -483,6 +500,18 @@ func opEqual(vm *VM) { vm.replaceTop(BoolValue(left == right)) } +func opUnwrap(vm *VM) { + value := vm.peek() + someValue, ok := value.(*SomeValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + value = someValue.value + + vm.replaceTop(value) +} + func (vm *VM) run() { for { @@ -536,6 +565,8 @@ func (vm *VM) run() { opDrop(vm) case opcode.Dup: opDup(vm) + case opcode.Empty: + opEmpty(vm) case opcode.New: opNew(vm) case opcode.SetField: @@ -554,6 +585,8 @@ func (vm *VM) run() { opNil(vm) case opcode.Equal: opEqual(vm) + case opcode.Unwrap: + opUnwrap(vm) default: panic(errors.NewUnexpectedError("cannot execute opcode '%s'", op.String())) } diff --git a/runtime/sema/check_invocation_expression.go b/runtime/sema/check_invocation_expression.go index cccaf7445d..1853e4afad 100644 --- a/runtime/sema/check_invocation_expression.go +++ b/runtime/sema/check_invocation_expression.go @@ -103,6 +103,7 @@ func (checker *Checker) checkInvocationExpression(invocationExpression *ast.Invo InvocationExpressionTypes{ ArgumentTypes: argumentTypes, ReturnType: checker.expectedType, + ParamCount: len(functionType.Parameters), } return InvalidType @@ -475,6 +476,7 @@ func (checker *Checker) checkInvocation( TypeParameterTypes: parameterTypes, ReturnType: returnType, ArgumentTypes: argumentTypes, + ParamCount: parameterCount, } return argumentTypes, returnType diff --git a/runtime/sema/elaboration.go b/runtime/sema/elaboration.go index 4e10595ddb..797b18c3dc 100644 --- a/runtime/sema/elaboration.go +++ b/runtime/sema/elaboration.go @@ -69,6 +69,7 @@ type InvocationExpressionTypes struct { TypeParameterTypes []Type ReturnType Type TypeArguments *TypeParameterTypeOrderedMap + ParamCount int } type ArrayExpressionTypes struct { From f15e9836e32a2421960062de9f0df45f641f1bed Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 22 May 2024 15:03:23 -0700 Subject: [PATCH 31/89] Improve test --- runtime/bbq/compiler/compiler.go | 8 +- runtime/bbq/vm/ft_test.go | 205 ++++++++++++++++---- runtime/bbq/vm/value_authaccount.go | 7 +- runtime/bbq/vm/value_conversions.go | 44 +++++ runtime/bbq/vm/value_publicaccount.go | 7 +- runtime/bbq/vm/value_simple_composite.go | 17 +- runtime/sema/check_invocation_expression.go | 2 - runtime/sema/elaboration.go | 1 - 8 files changed, 229 insertions(+), 62 deletions(-) diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index efa5b88334..fb4747be35 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -768,10 +768,10 @@ func (c *Compiler) loadArguments(expression *ast.InvocationExpression) { } // TODO: Is this needed? - // Load empty values for optional parameters, if they are not provided. - for i := len(expression.Arguments); i < invocationTypes.ParamCount; i++ { - c.emit(opcode.Empty) - } + //// Load empty values for optional parameters, if they are not provided. + //for i := len(expression.Arguments); i < invocationTypes.ParamCount; i++ { + // c.emit(opcode.Empty) + //} } func (c *Compiler) loadTypeArguments(expression *ast.InvocationExpression) []byte { diff --git a/runtime/bbq/vm/ft_test.go b/runtime/bbq/vm/ft_test.go index 63f4aaad8c..8cac93f22f 100644 --- a/runtime/bbq/vm/ft_test.go +++ b/runtime/bbq/vm/ft_test.go @@ -41,24 +41,24 @@ func TestFTTransfer(t *testing.T) { storage := interpreter.NewInMemoryStorage(nil) - address := common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1} + contractsAddress := common.MustBytesToAddress([]byte{0x1}) + senderAddress := common.MustBytesToAddress([]byte{0x2}) + receiverAddress := common.MustBytesToAddress([]byte{0x3}) - ftLocation := common.NewAddressLocation(nil, address, "FungibleToken") + ftLocation := common.NewAddressLocation(nil, contractsAddress, "FungibleToken") ftChecker, err := ParseAndCheckWithOptions(t, realFungibleTokenContractInterface, - ParseAndCheckOptions{Location: ftLocation}, + ParseAndCheckOptions{ + Location: ftLocation, + }, ) require.NoError(t, err) ftCompiler := compiler.NewCompiler(ftChecker.Program, ftChecker.Elaboration) ftProgram := ftCompiler.Compile() - //vm := NewVM(ftProgram, nil) - //importedContractValue, err := vm.InitializeContract() - //require.NoError(t, err) - // ----- Deploy FlowToken Contract ----- - flowTokenLocation := common.NewAddressLocation(nil, address, "FlowToken") + flowTokenLocation := common.NewAddressLocation(nil, contractsAddress, "FlowToken") flowTokenChecker, err := ParseAndCheckWithOptions(t, realFlowContract, ParseAndCheckOptions{ Location: flowTokenLocation, @@ -94,9 +94,9 @@ func TestFTTransfer(t *testing.T) { }, ) - authAcount := NewAuthAccountValue(address) + authAccount := NewAuthAccountValue(contractsAddress) - flowTokenContractValue, err := flowTokenVM.InitializeContract(authAcount) + flowTokenContractValue, err := flowTokenVM.InitializeContract(authAccount) require.NoError(t, err) // ----- Run setup account transaction ----- @@ -159,52 +159,80 @@ func TestFTTransfer(t *testing.T) { }, } - setupTxChecker, err := ParseAndCheckWithOptions( + for _, address := range []common.Address{ + senderAddress, + receiverAddress, + } { + setupTxChecker, err := ParseAndCheckWithOptions( + t, + realSetupFlowTokenAccountTransaction, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: checkerImportHandler, + LocationHandler: singleIdentifierLocationResolver(t), + }, + }, + ) + require.NoError(t, err) + + setupTxCompiler := compiler.NewCompiler(setupTxChecker.Program, setupTxChecker.Elaboration) + setupTxCompiler.Config.LocationHandler = singleIdentifierLocationResolver(t) + setupTxCompiler.Config.ImportHandler = compilerImportHandler + + program := setupTxCompiler.Compile() + printProgram(program) + + setupTxVM := NewVM(program, vmConfig) + + authorizer := NewAuthAccountValue(address) + err = setupTxVM.ExecuteTransaction(nil, authorizer) + require.NoError(t, err) + } + + // Mint FLOW to sender + + mintTxChecker, err := ParseAndCheckWithOptions( t, - realSetupFlowTokenAccountTransaction, + realMintFlowTokenTransaction, ParseAndCheckOptions{ Config: &sema.Config{ - ImportHandler: checkerImportHandler, - LocationHandler: singleIdentifierLocationResolver(t), + ImportHandler: checkerImportHandler, + BaseValueActivation: baseActivation(), + LocationHandler: singleIdentifierLocationResolver(t), }, }, ) require.NoError(t, err) - setupTxCompiler := compiler.NewCompiler(setupTxChecker.Program, setupTxChecker.Elaboration) - setupTxCompiler.Config.LocationHandler = singleIdentifierLocationResolver(t) - setupTxCompiler.Config.ImportHandler = compilerImportHandler + mintTxCompiler := compiler.NewCompiler(mintTxChecker.Program, mintTxChecker.Elaboration) + mintTxCompiler.Config.LocationHandler = singleIdentifierLocationResolver(t) + mintTxCompiler.Config.ImportHandler = compilerImportHandler - program := setupTxCompiler.Compile() + program := mintTxCompiler.Compile() printProgram(program) - setupTxVM := NewVM(program, vmConfig) + mintTxVM := NewVM(program, vmConfig) - authorizer := NewAuthAccountValue(address) - err = setupTxVM.ExecuteTransaction(nil, authorizer) + total := int64(1000000) + + mintTxArgs := []Value{ + AddressValue(senderAddress), + IntValue{total}, + } + + mintTxAuthorizer := NewAuthAccountValue(contractsAddress) + err = mintTxVM.ExecuteTransaction(mintTxArgs, mintTxAuthorizer) require.NoError(t, err) // ----- Run token transfer transaction ----- - // Only need for to make the checker happy - baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - baseValueActivation.DeclareValue(stdlib.PanicFunction) - baseValueActivation.DeclareValue(stdlib.NewStandardLibraryFunction( - "getAccount", - stdlib.GetAccountFunctionType, - "", - func(invocation interpreter.Invocation) interpreter.Value { - return nil - }, - )) - tokenTransferTxChecker, err := ParseAndCheckWithOptions( t, realFlowTokenTransferTransaction, ParseAndCheckOptions{ Config: &sema.Config{ ImportHandler: checkerImportHandler, - BaseValueActivation: baseValueActivation, + BaseValueActivation: baseActivation(), LocationHandler: singleIdentifierLocationResolver(t), }, }, @@ -220,13 +248,68 @@ func TestFTTransfer(t *testing.T) { tokenTransferTxVM := NewVM(tokenTransferTxProgram, vmConfig) - args := []Value{ - IntValue{1}, - AddressValue(address), + transferAmount := int64(1) + + tokenTransferTxArgs := []Value{ + IntValue{transferAmount}, + AddressValue(receiverAddress), } - err = tokenTransferTxVM.ExecuteTransaction(args, authorizer) + tokenTransferTxAuthorizer := NewAuthAccountValue(senderAddress) + err = tokenTransferTxVM.ExecuteTransaction(tokenTransferTxArgs, tokenTransferTxAuthorizer) require.NoError(t, err) + + // Run validation scripts + + for _, address := range []common.Address{ + senderAddress, + receiverAddress, + } { + validationScriptChecker, err := ParseAndCheckWithOptions( + t, + realFlowTokenBalanceScript, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: checkerImportHandler, + BaseValueActivation: baseActivation(), + LocationHandler: singleIdentifierLocationResolver(t), + }, + }, + ) + require.NoError(t, err) + + validationScriptCompiler := compiler.NewCompiler(validationScriptChecker.Program, validationScriptChecker.Elaboration) + validationScriptCompiler.Config.LocationHandler = singleIdentifierLocationResolver(t) + validationScriptCompiler.Config.ImportHandler = compilerImportHandler + + program := validationScriptCompiler.Compile() + printProgram(program) + + validationScriptVM := NewVM(program, vmConfig) + + addressValue := AddressValue(address) + result, err := validationScriptVM.Invoke("main", addressValue) + require.NoError(t, err) + + if address == senderAddress { + assert.Equal(t, IntValue{total - transferAmount}, result) + } else { + assert.Equal(t, IntValue{transferAmount}, result) + } + } +} + +func baseActivation() *sema.VariableActivation { + // Only need to make the checker happy + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.PanicFunction) + baseValueActivation.DeclareValue(stdlib.NewStandardLibraryFunction( + "getAccount", + stdlib.GetAccountFunctionType, + "", + nil, + )) + return baseValueActivation } const realFungibleTokenContractInterface = ` @@ -653,3 +736,49 @@ transaction(amount: Int, to: Address) { } } ` + +const realMintFlowTokenTransaction = ` +import FungibleToken from 0x1 +import FlowToken from 0x1 + +transaction(recipient: Address, amount: Int) { + let signer: AuthAccount + + prepare(signer: AuthAccount) { + self.signer = signer + } + + execute { + var tokenAdmin = self.signer + .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + ?? panic("Signer is not the token admin") + + var tokenReceiver = getAccount(recipient) + .getCapability(/public/flowTokenReceiver) + .borrow<&{FungibleToken.Receiver}>() + ?? panic("Unable to borrow receiver reference") + + let minter <- tokenAdmin.createNewMinter(allowedAmount: amount) + let mintedVault <- minter.mintTokens(amount: amount) + + tokenReceiver.deposit(from: <-mintedVault) + + destroy minter + } +} +` + +const realFlowTokenBalanceScript = ` +import FungibleToken from 0x1 +import FlowToken from 0x1 + +pub fun main(account: Address): Int { + + let vaultRef = getAccount(account) + .getCapability(/public/flowTokenBalance) + .borrow<&FlowToken.Vault{FungibleToken.Balance}>() + ?? panic("Could not borrow Balance reference to the Vault") + + return vaultRef.balance +} +` diff --git a/runtime/bbq/vm/value_authaccount.go b/runtime/bbq/vm/value_authaccount.go index 4ce71c86e4..742795e0f7 100644 --- a/runtime/bbq/vm/value_authaccount.go +++ b/runtime/bbq/vm/value_authaccount.go @@ -30,10 +30,9 @@ func NewAuthAccountValue( address common.Address, ) *SimpleCompositeValue { return &SimpleCompositeValue{ - QualifiedIdentifier: sema.AuthAccountType.QualifiedIdentifier(), - typeID: sema.AuthAccountType.ID(), - staticType: interpreter.PrimitiveStaticTypeAuthAccount, - Kind: common.CompositeKindStructure, + typeID: sema.AuthAccountType.ID(), + staticType: interpreter.PrimitiveStaticTypeAuthAccount, + Kind: common.CompositeKindStructure, fields: map[string]Value{ sema.AuthAccountAddressField: AddressValue(address), // TODO: add the remaining fields diff --git a/runtime/bbq/vm/value_conversions.go b/runtime/bbq/vm/value_conversions.go index 8e9cde6158..3d5def1df4 100644 --- a/runtime/bbq/vm/value_conversions.go +++ b/runtime/bbq/vm/value_conversions.go @@ -19,6 +19,7 @@ package vm import ( + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/tests/utils" @@ -50,6 +51,22 @@ func InterpreterValueToVMValue(value interpreter.Value) Value { Domain: value.Domain, Identifier: value.Identifier, } + case interpreter.AddressValue: + return AddressValue(value) + case *interpreter.SimpleCompositeValue: + fields := make(map[string]Value) + var fieldNames []string + + for name, field := range value.Fields { + fields[name] = InterpreterValueToVMValue(field) + fieldNames = append(fieldNames, name) + } + + return NewSimpleCompositeValue( + common.CompositeKindStructure, + value.TypeID, + fields, + ) default: panic(errors.NewUnreachableError()) } @@ -105,6 +122,33 @@ func VMValueToInterpreterValue(storage interpreter.Storage, value Value) interpr Domain: value.Domain, Identifier: value.Identifier, } + case *SimpleCompositeValue: + fields := make(map[string]interpreter.Value) + var fieldNames []string + + for name, field := range value.fields { + fields[name] = VMValueToInterpreterValue(storage, field) + fieldNames = append(fieldNames, name) + } + + return interpreter.NewSimpleCompositeValue( + nil, + value.typeID, + nil, + fieldNames, + fields, + nil, + nil, + nil, + ) + //case *StorageReferenceValue: + // return interpreter.NewStorageReferenceValue( + // nil, + // value.Authorized, + // value.TargetStorageAddress, + // value.TargetPath, + // value.BorrowedType, + // ) default: panic(errors.NewUnreachableError()) } diff --git a/runtime/bbq/vm/value_publicaccount.go b/runtime/bbq/vm/value_publicaccount.go index 27ba9da498..4b56ab7afd 100644 --- a/runtime/bbq/vm/value_publicaccount.go +++ b/runtime/bbq/vm/value_publicaccount.go @@ -29,10 +29,9 @@ func NewPublicAccountValue( address common.Address, ) *SimpleCompositeValue { return &SimpleCompositeValue{ - QualifiedIdentifier: sema.PublicAccountType.QualifiedIdentifier(), - typeID: sema.PublicAccountType.ID(), - staticType: interpreter.PrimitiveStaticTypePublicAccount, - Kind: common.CompositeKindStructure, + typeID: sema.PublicAccountType.ID(), + staticType: interpreter.PrimitiveStaticTypePublicAccount, + Kind: common.CompositeKindStructure, fields: map[string]Value{ sema.PublicAccountAddressField: AddressValue(address), // TODO: add the remaining fields diff --git a/runtime/bbq/vm/value_simple_composite.go b/runtime/bbq/vm/value_simple_composite.go index 9de62904ec..aa6d638e76 100644 --- a/runtime/bbq/vm/value_simple_composite.go +++ b/runtime/bbq/vm/value_simple_composite.go @@ -24,25 +24,24 @@ import ( ) type SimpleCompositeValue struct { - fields map[string]Value - QualifiedIdentifier string - typeID common.TypeID - staticType StaticType - Kind common.CompositeKind + fields map[string]Value + typeID common.TypeID + staticType StaticType + Kind common.CompositeKind } var _ Value = &CompositeValue{} func NewSimpleCompositeValue( - qualifiedIdentifier string, kind common.CompositeKind, typeID common.TypeID, + fields map[string]Value, ) *SimpleCompositeValue { return &SimpleCompositeValue{ - QualifiedIdentifier: qualifiedIdentifier, - Kind: kind, - typeID: typeID, + Kind: kind, + typeID: typeID, + fields: fields, } } diff --git a/runtime/sema/check_invocation_expression.go b/runtime/sema/check_invocation_expression.go index 1853e4afad..cccaf7445d 100644 --- a/runtime/sema/check_invocation_expression.go +++ b/runtime/sema/check_invocation_expression.go @@ -103,7 +103,6 @@ func (checker *Checker) checkInvocationExpression(invocationExpression *ast.Invo InvocationExpressionTypes{ ArgumentTypes: argumentTypes, ReturnType: checker.expectedType, - ParamCount: len(functionType.Parameters), } return InvalidType @@ -476,7 +475,6 @@ func (checker *Checker) checkInvocation( TypeParameterTypes: parameterTypes, ReturnType: returnType, ArgumentTypes: argumentTypes, - ParamCount: parameterCount, } return argumentTypes, returnType diff --git a/runtime/sema/elaboration.go b/runtime/sema/elaboration.go index 797b18c3dc..4e10595ddb 100644 --- a/runtime/sema/elaboration.go +++ b/runtime/sema/elaboration.go @@ -69,7 +69,6 @@ type InvocationExpressionTypes struct { TypeParameterTypes []Type ReturnType Type TypeArguments *TypeParameterTypeOrderedMap - ParamCount int } type ArrayExpressionTypes struct { From 5bf8ca05de2e5870ba4ba73cbd3b81f13ca7aebe Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 23 May 2024 08:28:42 -0700 Subject: [PATCH 32/89] Add FT transfer benchmark --- runtime/bbq/vm/ft_test.go | 229 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) diff --git a/runtime/bbq/vm/ft_test.go b/runtime/bbq/vm/ft_test.go index 8cac93f22f..f7d2b28614 100644 --- a/runtime/bbq/vm/ft_test.go +++ b/runtime/bbq/vm/ft_test.go @@ -782,3 +782,232 @@ pub fun main(account: Address): Int { return vaultRef.balance } ` + +func BenchmarkFTTransfer(b *testing.B) { + + // ---- Deploy FT Contract ----- + + storage := interpreter.NewInMemoryStorage(nil) + + contractsAddress := common.MustBytesToAddress([]byte{0x1}) + senderAddress := common.MustBytesToAddress([]byte{0x2}) + receiverAddress := common.MustBytesToAddress([]byte{0x3}) + + ftLocation := common.NewAddressLocation(nil, contractsAddress, "FungibleToken") + ftChecker, err := ParseAndCheckWithOptions(b, realFungibleTokenContractInterface, + ParseAndCheckOptions{ + Location: ftLocation, + }, + ) + require.NoError(b, err) + + ftCompiler := compiler.NewCompiler(ftChecker.Program, ftChecker.Elaboration) + ftProgram := ftCompiler.Compile() + + // ----- Deploy FlowToken Contract ----- + + flowTokenLocation := common.NewAddressLocation(nil, contractsAddress, "FlowToken") + flowTokenChecker, err := ParseAndCheckWithOptions(b, realFlowContract, + ParseAndCheckOptions{ + Location: flowTokenLocation, + Config: &sema.Config{ + ImportHandler: func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { + switch location { + case ftLocation: + return sema.ElaborationImport{ + Elaboration: ftChecker.Elaboration, + }, nil + default: + return nil, fmt.Errorf("cannot find contract in location %s", location) + } + }, + LocationHandler: singleIdentifierLocationResolver(b), + }, + }, + ) + require.NoError(b, err) + + flowTokenCompiler := compiler.NewCompiler(flowTokenChecker.Program, flowTokenChecker.Elaboration) + flowTokenCompiler.Config.ImportHandler = func(location common.Location) *bbq.Program { + return ftProgram + } + + flowTokenProgram := flowTokenCompiler.Compile() + + flowTokenVM := NewVM( + flowTokenProgram, + &Config{ + Storage: storage, + }, + ) + + authAccount := NewAuthAccountValue(contractsAddress) + + flowTokenContractValue, err := flowTokenVM.InitializeContract(authAccount) + require.NoError(b, err) + + // ----- Run setup account transaction ----- + + checkerImportHandler := func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { + require.IsType(b, common.AddressLocation{}, location) + addressLocation := location.(common.AddressLocation) + var elaboration *sema.Elaboration + + switch addressLocation { + case ftLocation: + elaboration = ftChecker.Elaboration + case flowTokenLocation: + elaboration = flowTokenChecker.Elaboration + default: + assert.FailNow(b, "invalid location") + } + + return sema.ElaborationImport{ + Elaboration: elaboration, + }, nil + } + + compilerImportHandler := func(location common.Location) *bbq.Program { + switch location { + case ftLocation: + return ftProgram + case flowTokenLocation: + return flowTokenProgram + default: + assert.FailNow(b, "invalid location") + return nil + } + } + + vmConfig := &Config{ + Storage: storage, + ImportHandler: func(location common.Location) *bbq.Program { + switch location { + case ftLocation: + return ftProgram + case flowTokenLocation: + return flowTokenProgram + default: + assert.FailNow(b, "invalid location") + return nil + } + }, + ContractValueHandler: func(_ *Config, location common.Location) *CompositeValue { + switch location { + case ftLocation: + // interface + return nil + case flowTokenLocation: + return flowTokenContractValue + default: + assert.FailNow(b, "invalid location") + return nil + } + }, + } + + for _, address := range []common.Address{ + senderAddress, + receiverAddress, + } { + setupTxChecker, err := ParseAndCheckWithOptions( + b, + realSetupFlowTokenAccountTransaction, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: checkerImportHandler, + LocationHandler: singleIdentifierLocationResolver(b), + }, + }, + ) + require.NoError(b, err) + + setupTxCompiler := compiler.NewCompiler(setupTxChecker.Program, setupTxChecker.Elaboration) + setupTxCompiler.Config.LocationHandler = singleIdentifierLocationResolver(b) + setupTxCompiler.Config.ImportHandler = compilerImportHandler + + program := setupTxCompiler.Compile() + + setupTxVM := NewVM(program, vmConfig) + + authorizer := NewAuthAccountValue(address) + err = setupTxVM.ExecuteTransaction(nil, authorizer) + require.NoError(b, err) + } + + // Mint FLOW to sender + + mintTxChecker, err := ParseAndCheckWithOptions( + b, + realMintFlowTokenTransaction, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: checkerImportHandler, + BaseValueActivation: baseActivation(), + LocationHandler: singleIdentifierLocationResolver(b), + }, + }, + ) + require.NoError(b, err) + + mintTxCompiler := compiler.NewCompiler(mintTxChecker.Program, mintTxChecker.Elaboration) + mintTxCompiler.Config.LocationHandler = singleIdentifierLocationResolver(b) + mintTxCompiler.Config.ImportHandler = compilerImportHandler + + program := mintTxCompiler.Compile() + + mintTxVM := NewVM(program, vmConfig) + + total := int64(1000000) + + mintTxArgs := []Value{ + AddressValue(senderAddress), + IntValue{total}, + } + + mintTxAuthorizer := NewAuthAccountValue(contractsAddress) + err = mintTxVM.ExecuteTransaction(mintTxArgs, mintTxAuthorizer) + require.NoError(b, err) + + // ----- Run token transfer transaction ----- + + tokenTransferTxChecker, err := ParseAndCheckWithOptions( + b, + realFlowTokenTransferTransaction, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: checkerImportHandler, + BaseValueActivation: baseActivation(), + LocationHandler: singleIdentifierLocationResolver(b), + }, + }, + ) + require.NoError(b, err) + + tokenTransferTxCompiler := compiler.NewCompiler(tokenTransferTxChecker.Program, tokenTransferTxChecker.Elaboration) + tokenTransferTxCompiler.Config.LocationHandler = singleIdentifierLocationResolver(b) + tokenTransferTxCompiler.Config.ImportHandler = compilerImportHandler + + tokenTransferTxProgram := tokenTransferTxCompiler.Compile() + + tokenTransferTxVM := NewVM(tokenTransferTxProgram, vmConfig) + + transferAmount := int64(1) + + tokenTransferTxArgs := []Value{ + IntValue{transferAmount}, + AddressValue(receiverAddress), + } + + tokenTransferTxAuthorizer := NewAuthAccountValue(senderAddress) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + err = tokenTransferTxVM.ExecuteTransaction(tokenTransferTxArgs, tokenTransferTxAuthorizer) + require.NoError(b, err) + } + + b.StopTimer() +} From 21719e9a1c1fc0c95b87733cb86867b48532d8c5 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 23 May 2024 09:41:40 -0700 Subject: [PATCH 33/89] Fix parameter dropping in invocation --- runtime/bbq/compiler/compiler.go | 9 +++- runtime/bbq/opcode/opcode.go | 26 +++++++--- runtime/bbq/opcode/opcode_string.go | 39 +++++++------- runtime/bbq/vm/ft_test.go | 4 ++ runtime/bbq/vm/vm.go | 20 ++------ runtime/bbq/vm/vm_test.go | 80 +++++++++++++++++++++++++++++ 6 files changed, 132 insertions(+), 46 deletions(-) diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index fb4747be35..f7fdcef745 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -511,7 +511,7 @@ func (c *Compiler) VisitIfStatement(statement *ast.IfStatement) (_ struct{}) { c.compileBlock(statement.Then) elseBlock := statement.Else if elseBlock != nil { - thenJump := c.emit(opcode.Jump) + thenJump := c.emitUndefinedJump(opcode.Jump) c.patchJump(elseJump) c.compileBlock(elseBlock) c.patchJump(thenJump) @@ -838,12 +838,17 @@ func (c *Compiler) VisitBinaryExpression(expression *ast.BinaryExpression) (_ st switch expression.Operation { case ast.OperationNilCoalesce: // create a duplicate to perform the equal check. - // So if the condition succeeds, then the result will be at the top of the stack. + // So if the condition succeeds, then the condition's result will be at the top of the stack. c.emit(opcode.Dup) c.emit(opcode.Nil) c.emit(opcode.Equal) elseJump := c.emitUndefinedJump(opcode.JumpIfFalse) + + // Drop the duplicated condition result. + // It is not needed for the 'then' path. + c.emit(opcode.Drop) + c.compileExpression(expression.Right) thenJump := c.emitUndefinedJump(opcode.Jump) diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index 0750ec33df..7fc78700b8 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -25,11 +25,15 @@ type Opcode byte const ( Unknown Opcode = iota + // Control flow + Return ReturnValue Jump JumpIfFalse + // Int operations + IntAdd IntSubtract IntMultiply @@ -42,13 +46,23 @@ const ( IntLessOrEqual IntGreaterOrEqual + // Unary/Binary operators + Equal Unwrap + Destroy + Transfer + Cast + + // Value/Constant loading - GetConstant True False + New + Path + Nil + GetConstant GetLocal SetLocal GetGlobal @@ -56,17 +70,13 @@ const ( GetField SetField + // Invocations + Invoke InvokeDynamic - Destroy - Transfer - Cast - New - Path - Nil + // Stack operations Drop Dup - Empty ) diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index 41a1700764..9cb47aeb27 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -26,31 +26,30 @@ func _() { _ = x[IntGreaterOrEqual-15] _ = x[Equal-16] _ = x[Unwrap-17] - _ = x[GetConstant-18] - _ = x[True-19] - _ = x[False-20] - _ = x[GetLocal-21] - _ = x[SetLocal-22] - _ = x[GetGlobal-23] - _ = x[SetGlobal-24] - _ = x[GetField-25] - _ = x[SetField-26] - _ = x[Invoke-27] - _ = x[InvokeDynamic-28] - _ = x[Destroy-29] - _ = x[Transfer-30] - _ = x[Cast-31] - _ = x[New-32] - _ = x[Path-33] - _ = x[Nil-34] + _ = x[Destroy-18] + _ = x[Transfer-19] + _ = x[Cast-20] + _ = x[True-21] + _ = x[False-22] + _ = x[New-23] + _ = x[Path-24] + _ = x[Nil-25] + _ = x[GetConstant-26] + _ = x[GetLocal-27] + _ = x[SetLocal-28] + _ = x[GetGlobal-29] + _ = x[SetGlobal-30] + _ = x[GetField-31] + _ = x[SetField-32] + _ = x[Invoke-33] + _ = x[InvokeDynamic-34] _ = x[Drop-35] _ = x[Dup-36] - _ = x[Empty-37] } -const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualEqualUnwrapGetConstantTrueFalseGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeInvokeDynamicDestroyTransferCastNewPathNilDropDupEmpty" +const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualEqualUnwrapDestroyTransferCastTrueFalseNewPathNilGetConstantGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeInvokeDynamicDropDup" -var _Opcode_index = [...]uint16{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 154, 160, 171, 175, 180, 188, 196, 205, 214, 222, 230, 236, 249, 256, 264, 268, 271, 275, 278, 282, 285, 290} +var _Opcode_index = [...]uint16{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 154, 160, 167, 175, 179, 183, 188, 191, 195, 198, 209, 217, 225, 234, 243, 251, 259, 265, 278, 282, 285} func (i Opcode) String() string { if i >= Opcode(len(_Opcode_index)-1) { diff --git a/runtime/bbq/vm/ft_test.go b/runtime/bbq/vm/ft_test.go index f7d2b28614..4e5e1cb89f 100644 --- a/runtime/bbq/vm/ft_test.go +++ b/runtime/bbq/vm/ft_test.go @@ -187,6 +187,7 @@ func TestFTTransfer(t *testing.T) { authorizer := NewAuthAccountValue(address) err = setupTxVM.ExecuteTransaction(nil, authorizer) require.NoError(t, err) + require.Empty(t, setupTxVM.stack) } // Mint FLOW to sender @@ -223,6 +224,7 @@ func TestFTTransfer(t *testing.T) { mintTxAuthorizer := NewAuthAccountValue(contractsAddress) err = mintTxVM.ExecuteTransaction(mintTxArgs, mintTxAuthorizer) require.NoError(t, err) + require.Empty(t, mintTxVM.stack) // ----- Run token transfer transaction ----- @@ -258,6 +260,7 @@ func TestFTTransfer(t *testing.T) { tokenTransferTxAuthorizer := NewAuthAccountValue(senderAddress) err = tokenTransferTxVM.ExecuteTransaction(tokenTransferTxArgs, tokenTransferTxAuthorizer) require.NoError(t, err) + require.Empty(t, tokenTransferTxVM.stack) // Run validation scripts @@ -290,6 +293,7 @@ func TestFTTransfer(t *testing.T) { addressValue := AddressValue(address) result, err := validationScriptVM.Invoke("main", addressValue) require.NoError(t, err) + require.Empty(t, validationScriptVM.stack) if address == senderAddress { assert.Equal(t, IntValue{total - transferAmount}, result) diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index b6eeeed87d..7fd9a131e8 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -330,10 +330,8 @@ func opInvoke(vm *VM) { arguments := vm.stack[stackHeight-parameterCount:] - // TODO: - //vm.dropN(parameterCount) - result := value.Function(vm.config, typeArguments, arguments...) + vm.dropN(parameterCount) vm.push(result) default: panic(errors.NewUnreachableError()) @@ -388,10 +386,6 @@ func opDup(vm *VM) { vm.push(top) } -func opEmpty(vm *VM) { - vm.push(nil) -} - func opNew(vm *VM) { callframe := vm.callFrame @@ -502,14 +496,10 @@ func opEqual(vm *VM) { func opUnwrap(vm *VM) { value := vm.peek() - someValue, ok := value.(*SomeValue) - if !ok { - panic(errors.NewUnreachableError()) + if someValue, ok := value.(*SomeValue); ok { + value = someValue.value + vm.replaceTop(value) } - - value = someValue.value - - vm.replaceTop(value) } func (vm *VM) run() { @@ -565,8 +555,6 @@ func (vm *VM) run() { opDrop(vm) case opcode.Dup: opDup(vm) - case opcode.Empty: - opEmpty(vm) case opcode.New: opNew(vm) case opcode.SetField: diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index ed7f50ae0d..97b1a92dee 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -67,6 +67,7 @@ func TestRecursionFib(t *testing.T) { ) require.NoError(t, err) require.Equal(t, IntValue{SmallInt: 13}, result) + require.Empty(t, vm.stack) } func BenchmarkRecursionFib(b *testing.B) { @@ -129,6 +130,7 @@ func TestImperativeFib(t *testing.T) { ) require.NoError(t, err) require.Equal(t, IntValue{SmallInt: 13}, result) + require.Empty(t, vm.stack) } func BenchmarkImperativeFib(b *testing.B) { @@ -179,6 +181,7 @@ func TestBreak(t *testing.T) { require.NoError(t, err) require.Equal(t, IntValue{SmallInt: 4}, result) + require.Empty(t, vm.stack) } func TestContinue(t *testing.T) { @@ -209,6 +212,61 @@ func TestContinue(t *testing.T) { require.NoError(t, err) require.Equal(t, IntValue{SmallInt: 3}, result) + require.Empty(t, vm.stack) +} + +func TestNilCoalesce(t *testing.T) { + + t.Parallel() + + t.Run("true", func(t *testing.T) { + t.Parallel() + + checker, err := ParseAndCheck(t, ` + fun test(): Int { + var i: Int? = 2 + var j = i ?? 3 + return j + } + `) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vm := NewVM(program, nil) + + result, err := vm.Invoke("test") + require.NoError(t, err) + + require.Equal(t, IntValue{SmallInt: 2}, result) + require.Empty(t, vm.stack) + }) + + t.Run("false", func(t *testing.T) { + t.Parallel() + + checker, err := ParseAndCheck(t, ` + fun test(): Int { + var i: Int? = nil + var j = i ?? 3 + return j + } + `) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + printProgram(program) + + vm := NewVM(program, nil) + + result, err := vm.Invoke("test") + require.NoError(t, err) + + require.Equal(t, IntValue{SmallInt: 3}, result) + require.Empty(t, vm.stack) + }) } func TestNewStruct(t *testing.T) { @@ -244,6 +302,7 @@ func TestNewStruct(t *testing.T) { result, err := vm.Invoke("test", IntValue{SmallInt: 10}) require.NoError(t, err) + require.Empty(t, vm.stack) require.IsType(t, &CompositeValue{}, result) structValue := result.(*CompositeValue) @@ -287,6 +346,7 @@ func TestStructMethodCall(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) + require.Empty(t, vm.stack) require.Equal(t, StringValue{Str: []byte("Hello from Foo!")}, result) } @@ -469,6 +529,7 @@ func TestImport(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) + require.Empty(t, vm.stack) require.Equal(t, StringValue{Str: []byte("global function of the imported program")}, result) } @@ -556,6 +617,8 @@ func TestContractImport(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) + require.Empty(t, vm.stack) + require.Equal(t, StringValue{Str: []byte("global function of the imported program")}, result) }) @@ -628,6 +691,8 @@ func TestContractImport(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) + require.Empty(t, vm.stack) + require.Equal(t, StringValue{Str: []byte("contract function of the imported program")}, result) }) @@ -799,6 +864,8 @@ func TestContractImport(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) + require.Empty(t, vm.stack) + require.Equal(t, StringValue{Str: []byte("Hello from Foo!")}, result) }) @@ -968,6 +1035,8 @@ func TestContractImport(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) + require.Empty(t, vm.stack) + require.Equal(t, StringValue{Str: []byte("Successfully withdrew")}, result) }) } @@ -1192,6 +1261,7 @@ func TestFunctionOrder(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) + require.Empty(t, vm.stack) require.Equal(t, IntValue{SmallInt: 5}, result) }) @@ -1246,6 +1316,7 @@ func TestFunctionOrder(t *testing.T) { result, err := vm.Invoke("init") require.NoError(t, err) + require.Empty(t, vm.stack) require.IsType(t, &CompositeValue{}, result) }) @@ -1318,6 +1389,8 @@ func TestContractField(t *testing.T) { vm = NewVM(program, vmConfig) result, err := vm.Invoke("test") require.NoError(t, err) + require.Empty(t, vm.stack) + require.Equal(t, StringValue{Str: []byte("PENDING")}, result) }) @@ -1386,6 +1459,8 @@ func TestContractField(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) + require.Empty(t, vm.stack) + require.Equal(t, StringValue{Str: []byte("UPDATED")}, result) fieldValue := importedContractValue.GetMember(vm.config, "status") @@ -1460,6 +1535,7 @@ func TestNativeFunctions(t *testing.T) { _, err = vm.Invoke("test") require.NoError(t, err) + require.Empty(t, vm.stack) }) t.Run("bound function", func(t *testing.T) { @@ -1478,6 +1554,7 @@ func TestNativeFunctions(t *testing.T) { result, err := vm.Invoke("test") require.NoError(t, err) + require.Empty(t, vm.stack) require.Equal(t, StringValue{Str: []byte("Hello, World!")}, result) }) @@ -1510,6 +1587,7 @@ func TestTransaction(t *testing.T) { err = vm.ExecuteTransaction(nil) require.NoError(t, err) + require.Empty(t, vm.stack) // Rerun the same again using internal functions, to get the access to the transaction value. @@ -1565,6 +1643,7 @@ func TestTransaction(t *testing.T) { err = vm.ExecuteTransaction(args) require.NoError(t, err) + require.Empty(t, vm.stack) // Rerun the same again using internal functions, to get the access to the transaction value. @@ -1684,6 +1763,7 @@ func TestInterfaceMethodCall(t *testing.T) { vm = NewVM(program, vmConfig) result, err := vm.Invoke("test") require.NoError(t, err) + require.Empty(t, vm.stack) require.Equal(t, StringValue{Str: []byte("Hello from Foo!")}, result) } From a637bed3f33f7469024789eb45a282a014f03161 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 23 May 2024 10:45:48 -0700 Subject: [PATCH 34/89] Reuse constants in the pool --- runtime/bbq/compiler/compiler.go | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index f7fdcef745..444cadc685 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -51,13 +51,21 @@ type Compiler struct { loops []*loop currentLoop *loop staticTypes [][]byte - typesInPool map[sema.Type]uint16 exportedImports []*bbq.Import + // Cache alike for staticTypes and constants in the pool. + typesInPool map[sema.Type]uint16 + constantsInPool map[constantsCacheKey]*constant + // TODO: initialize memoryGauge common.MemoryGauge } +type constantsCacheKey struct { + data string + kind constantkind.ConstantKind +} + var _ ast.DeclarationVisitor[struct{}] = &Compiler{} var _ ast.StatementVisitor[struct{}] = &Compiler{} var _ ast.ExpressionVisitor[struct{}] = &Compiler{} @@ -70,10 +78,11 @@ func NewCompiler( Program: program, Elaboration: elaboration, Config: &Config{}, - globals: map[string]*global{}, + globals: make(map[string]*global), importedGlobals: indexedNativeFunctions, exportedImports: make([]*bbq.Import, 0), - typesInPool: map[sema.Type]uint16{}, + typesInPool: make(map[sema.Type]uint16), + constantsInPool: make(map[constantsCacheKey]*constant), compositeTypeStack: &Stack[*sema.CompositeType]{ elements: make([]*sema.CompositeType, 0), }, @@ -165,12 +174,23 @@ func (c *Compiler) addConstant(kind constantkind.ConstantKind, data []byte) *con if count >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid constant declaration")) } + + // Optimization: Reuse the constant if it is already added to the constant pool. + cacheKey := constantsCacheKey{ + data: string(data), + kind: kind, + } + if constant, ok := c.constantsInPool[cacheKey]; ok { + return constant + } + constant := &constant{ index: uint16(count), kind: kind, data: data[:], } c.constants = append(c.constants, constant) + c.constantsInPool[cacheKey] = constant return constant } From f90703a93313a00aa9456352bfbf3f913e3f7750 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 23 May 2024 16:09:02 -0700 Subject: [PATCH 35/89] Optimize --- runtime/bbq/vm/config.go | 33 ++++++++++++++++++----------- runtime/bbq/vm/ft_test.go | 15 ++++++------- runtime/bbq/vm/storage.go | 21 ++++++++++++++---- runtime/bbq/vm/value_authaccount.go | 6 ++---- runtime/bbq/vm/value_composite.go | 2 +- runtime/bbq/vm/value_conversions.go | 17 --------------- 6 files changed, 48 insertions(+), 46 deletions(-) diff --git a/runtime/bbq/vm/config.go b/runtime/bbq/vm/config.go index 858433823a..ab6a0fbd2b 100644 --- a/runtime/bbq/vm/config.go +++ b/runtime/bbq/vm/config.go @@ -19,11 +19,10 @@ package vm import ( - "github.com/onflow/atree" "github.com/onflow/cadence/runtime/bbq/commons" "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/tests/utils" ) type Config struct { @@ -31,19 +30,29 @@ type Config struct { common.MemoryGauge commons.ImportHandler ContractValueHandler + + inter *interpreter.Interpreter } -type ContractValueHandler func(conf *Config, location common.Location) *CompositeValue +// TODO: This is temporary. Remove once storing/reading is supported for VM values. +func (c *Config) interpreter() *interpreter.Interpreter { + if c.inter == nil { + inter, err := interpreter.NewInterpreter( + nil, + utils.TestLocation, + &interpreter.Config{ + Storage: c.Storage, + }, + ) -func RemoveReferencedSlab(storage interpreter.Storage, storable atree.Storable) { - storageIDStorable, ok := storable.(atree.StorageIDStorable) - if !ok { - return - } + if err != nil { + panic(err) + } - storageID := atree.StorageID(storageIDStorable) - err := storage.Remove(storageID) - if err != nil { - panic(errors.NewExternalError(err)) + c.inter = inter } + + return c.inter } + +type ContractValueHandler func(conf *Config, location common.Location) *CompositeValue diff --git a/runtime/bbq/vm/ft_test.go b/runtime/bbq/vm/ft_test.go index 4e5e1cb89f..0ec5cf89dd 100644 --- a/runtime/bbq/vm/ft_test.go +++ b/runtime/bbq/vm/ft_test.go @@ -988,14 +988,6 @@ func BenchmarkFTTransfer(b *testing.B) { ) require.NoError(b, err) - tokenTransferTxCompiler := compiler.NewCompiler(tokenTransferTxChecker.Program, tokenTransferTxChecker.Elaboration) - tokenTransferTxCompiler.Config.LocationHandler = singleIdentifierLocationResolver(b) - tokenTransferTxCompiler.Config.ImportHandler = compilerImportHandler - - tokenTransferTxProgram := tokenTransferTxCompiler.Compile() - - tokenTransferTxVM := NewVM(tokenTransferTxProgram, vmConfig) - transferAmount := int64(1) tokenTransferTxArgs := []Value{ @@ -1009,6 +1001,13 @@ func BenchmarkFTTransfer(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { + tokenTransferTxCompiler := compiler.NewCompiler(tokenTransferTxChecker.Program, tokenTransferTxChecker.Elaboration) + tokenTransferTxCompiler.Config.LocationHandler = singleIdentifierLocationResolver(b) + tokenTransferTxCompiler.Config.ImportHandler = compilerImportHandler + + tokenTransferTxProgram := tokenTransferTxCompiler.Compile() + + tokenTransferTxVM := NewVM(tokenTransferTxProgram, vmConfig) err = tokenTransferTxVM.ExecuteTransaction(tokenTransferTxArgs, tokenTransferTxAuthorizer) require.NoError(b, err) } diff --git a/runtime/bbq/vm/storage.go b/runtime/bbq/vm/storage.go index 90d82ed889..03020989f5 100644 --- a/runtime/bbq/vm/storage.go +++ b/runtime/bbq/vm/storage.go @@ -20,6 +20,7 @@ package vm import ( "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" @@ -32,7 +33,6 @@ import ( // } func StoredValue(gauge common.MemoryGauge, storable atree.Storable, storage atree.SlabStorage) Value { - // Delegate value := interpreter.StoredValue(gauge, storable, storage) return InterpreterValueToVMValue(value) } @@ -54,19 +54,32 @@ func ReadStored( } func WriteStored( - gauge common.MemoryGauge, - storage interpreter.Storage, + config *Config, storageAddress common.Address, domain string, identifier string, value Value, ) { + storage := config.Storage accountStorage := storage.GetStorageMap(storageAddress, domain, true) interValue := VMValueToInterpreterValue(storage, value) - accountStorage.WriteValue(inter(storage), identifier, interValue) + accountStorage.WriteValue(config.interpreter(), identifier, interValue) //interpreter.recordStorageMutation() } +func RemoveReferencedSlab(storage interpreter.Storage, storable atree.Storable) { + storageIDStorable, ok := storable.(atree.StorageIDStorable) + if !ok { + return + } + + storageID := atree.StorageID(storageIDStorable) + err := storage.Remove(storageID) + if err != nil { + panic(errors.NewExternalError(err)) + } +} + // //// InMemoryStorage //type InMemoryStorage struct { diff --git a/runtime/bbq/vm/value_authaccount.go b/runtime/bbq/vm/value_authaccount.go index 742795e0f7..5b37204b2c 100644 --- a/runtime/bbq/vm/value_authaccount.go +++ b/runtime/bbq/vm/value_authaccount.go @@ -91,8 +91,7 @@ func init() { linkValue := NewLinkValue(targetPath, borrowType) WriteStored( - config.MemoryGauge, - config.Storage, + config, common.Address(addressValue), newCapabilityDomain, newCapabilityIdentifier, @@ -153,8 +152,7 @@ func init() { // Write new value WriteStored( - config.MemoryGauge, - config.Storage, + config, common.Address(addressValue), domain, identifier, diff --git a/runtime/bbq/vm/value_composite.go b/runtime/bbq/vm/value_composite.go index 46d92eb25a..449c31cb98 100644 --- a/runtime/bbq/vm/value_composite.go +++ b/runtime/bbq/vm/value_composite.go @@ -144,7 +144,7 @@ func (v *CompositeValue) SetMember(conf *Config, name string, value Value) { } if existingStorable != nil { - inter := inter(conf.Storage) + inter := conf.interpreter() existingValue := interpreter.StoredValue(nil, existingStorable, conf.Storage) existingValue.DeepRemove(inter) RemoveReferencedSlab(conf.Storage, existingStorable) diff --git a/runtime/bbq/vm/value_conversions.go b/runtime/bbq/vm/value_conversions.go index 3d5def1df4..78ed23d73d 100644 --- a/runtime/bbq/vm/value_conversions.go +++ b/runtime/bbq/vm/value_conversions.go @@ -22,7 +22,6 @@ import ( "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" - "github.com/onflow/cadence/runtime/tests/utils" ) // Utility methods to convert between old and new values. @@ -72,22 +71,6 @@ func InterpreterValueToVMValue(value interpreter.Value) Value { } } -var inter = func(storage interpreter.Storage) *interpreter.Interpreter { - inter, err := interpreter.NewInterpreter( - nil, - utils.TestLocation, - &interpreter.Config{ - Storage: storage, - }, - ) - - if err != nil { - panic(err) - } - - return inter -} - func VMValueToInterpreterValue(storage interpreter.Storage, value Value) interpreter.Value { switch value := value.(type) { case IntValue: From 11e88c8172f2b3979298d33adf57eb98f46d1757 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 9 Jul 2024 14:41:47 -0700 Subject: [PATCH 36/89] Fix key not found error handling --- runtime/bbq/vm/value_composite.go | 8 ++++++-- runtime/bbq/vm/vm_test.go | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/runtime/bbq/vm/value_composite.go b/runtime/bbq/vm/value_composite.go index e563b3fcd8..ee382d9db8 100644 --- a/runtime/bbq/vm/value_composite.go +++ b/runtime/bbq/vm/value_composite.go @@ -19,6 +19,8 @@ package vm import ( + goerrors "errors" + "github.com/onflow/atree" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" @@ -106,9 +108,11 @@ func (v *CompositeValue) GetMember(config *Config, name string) Value { interpreter.StringAtreeValue(name), ) if err != nil { - if _, ok := err.(*atree.KeyNotFoundError); !ok { - panic(errors.NewExternalError(err)) + var keyNotFoundError *atree.KeyNotFoundError + if goerrors.As(err, &keyNotFoundError) { + return nil } + panic(errors.NewExternalError(err)) } if storable != nil { diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/vm_test.go index 0999768271..e88af08b28 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/vm_test.go @@ -1175,7 +1175,7 @@ func TestContractAccessDuringInit(t *testing.T) { contract MyContract { var status : String - pub fun getInitialStatus(): String { + access(all) fun getInitialStatus(): String { return "PENDING" } @@ -1207,7 +1207,7 @@ func TestContractAccessDuringInit(t *testing.T) { contract MyContract { var status : String - pub fun getInitialStatus(): String { + access(all) fun getInitialStatus(): String { return "PENDING" } From 1e66eb5a4b34034b809187b6f43153c757bacbcf Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 10 Jul 2024 11:49:47 -0700 Subject: [PATCH 37/89] Add more account type functions --- runtime/bbq/compiler/native_functions.go | 21 +- runtime/bbq/vm/config.go | 13 + runtime/bbq/vm/ft_test.go | 228 +++++------- runtime/bbq/vm/storage.go | 22 +- runtime/bbq/vm/value_account.go | 304 +++++++++++++++- runtime/bbq/vm/value_capability_controller.go | 7 +- runtime/bbq/vm/value_composite.go | 1 + runtime/bbq/vm/value_dictionary.go | 334 ++++++++++++++++++ runtime/bbq/vm/value_function.go | 4 +- runtime/bbq/vm/value_void.go | 2 +- 10 files changed, 783 insertions(+), 153 deletions(-) create mode 100644 runtime/bbq/vm/value_dictionary.go diff --git a/runtime/bbq/compiler/native_functions.go b/runtime/bbq/compiler/native_functions.go index 6b0401cd21..a564da829f 100644 --- a/runtime/bbq/compiler/native_functions.go +++ b/runtime/bbq/compiler/native_functions.go @@ -46,11 +46,8 @@ func init() { // Because the native functions used by a program are also // added to the imports section of the compiled program. // Then the VM will link the imports (native functions) by the name. - for _, builtinType := range builtinTypes { - for name, _ := range builtinType.GetMembers() { - funcName := commons.TypeQualifiedName(builtinType.QualifiedString(), name) - addNativeFunction(funcName) - } + for _, typ := range builtinTypes { + registerBoundFunctions(typ) } for _, funcName := range stdlibFunctions { @@ -58,6 +55,20 @@ func init() { } } +func registerBoundFunctions(typ sema.Type) { + for name, _ := range typ.GetMembers() { + funcName := commons.TypeQualifiedName(typ.QualifiedString(), name) + addNativeFunction(funcName) + } + + compositeType, ok := typ.(*sema.CompositeType) + if ok && compositeType.NestedTypes != nil { + compositeType.NestedTypes.Foreach(func(_ string, nestedType sema.Type) { + registerBoundFunctions(nestedType) + }) + } +} + func addNativeFunction(name string) { global := &global{ name: name, diff --git a/runtime/bbq/vm/config.go b/runtime/bbq/vm/config.go index ab6a0fbd2b..943c8959b7 100644 --- a/runtime/bbq/vm/config.go +++ b/runtime/bbq/vm/config.go @@ -22,6 +22,7 @@ import ( "github.com/onflow/cadence/runtime/bbq/commons" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/stdlib" "github.com/onflow/cadence/runtime/tests/utils" ) @@ -30,7 +31,14 @@ type Config struct { common.MemoryGauge commons.ImportHandler ContractValueHandler + stdlib.AccountHandler + // TODO: Move these to `Context`. + // Context is the shared state across a single execution + CapabilityControllerIterations map[AddressPath]int + MutationDuringCapabilityControllerIteration bool + + // TODO: temp inter *interpreter.Interpreter } @@ -56,3 +64,8 @@ func (c *Config) interpreter() *interpreter.Interpreter { } type ContractValueHandler func(conf *Config, location common.Location) *CompositeValue + +type AddressPath struct { + Address common.Address + Path PathValue +} diff --git a/runtime/bbq/vm/ft_test.go b/runtime/bbq/vm/ft_test.go index 8996167819..47905dd72d 100644 --- a/runtime/bbq/vm/ft_test.go +++ b/runtime/bbq/vm/ft_test.go @@ -321,31 +321,31 @@ const realFungibleTokenContractInterface = ` /// /// The interface that fungible token contracts implement. /// -pub contract interface FungibleToken { +access(all) contract interface FungibleToken { /// The total number of tokens in existence. /// It is up to the implementer to ensure that the total supply /// stays accurate and up to date /// - pub var totalSupply: Int - - ///// TokensInitialized - ///// - ///// The event that is emitted when the contract is created - ///// - //pub event TokensInitialized(initialSupply: Int) - // - ///// TokensWithdrawn - ///// - ///// The event that is emitted when tokens are withdrawn from a Vault - ///// - //pub event TokensWithdrawn(amount: Int, from: Address?) - // - ///// TokensDeposited - ///// - ///// The event that is emitted when tokens are deposited into a Vault - ///// - //pub event TokensDeposited(amount: Int, to: Address?) + access(all) var totalSupply: Int + + /// TokensInitialized + /// + /// The event that is emitted when the contract is created + /// + access(all) event TokensInitialized(initialSupply: Int) + + /// TokensWithdrawn + /// + /// The event that is emitted when tokens are withdrawn from a Vault + /// + access(all) event TokensWithdrawn(amount: Int, from: Address?) + + /// TokensDeposited + /// + /// The event that is emitted when tokens are deposited into a Vault + /// + access(all) event TokensDeposited(amount: Int, to: Address?) /// Provider /// @@ -356,7 +356,7 @@ pub contract interface FungibleToken { /// because it leaves open the possibility of creating custom providers /// that do not necessarily need their own balance. /// - pub resource interface Provider { + access(all) resource interface Provider { /// withdraw subtracts tokens from the owner's Vault /// and returns a Vault with the removed tokens. @@ -373,7 +373,7 @@ pub contract interface FungibleToken { /// capability that allows all users to access the provider /// resource through a reference. /// - pub fun withdraw(amount: Int): @Vault { + access(all) fun withdraw(amount: Int): @{Vault} { post { // 'result' refers to the return value result.balance == amount: @@ -392,11 +392,11 @@ pub contract interface FungibleToken { /// can do custom things with the tokens, like split them up and /// send them to different places. /// - pub resource interface Receiver { + access(all) resource interface Receiver { /// deposit takes a Vault and deposits it into the implementing resource type /// - pub fun deposit(from: @Vault) + access(all) fun deposit(from: @{Vault}) } /// Balance @@ -405,11 +405,11 @@ pub contract interface FungibleToken { /// and enforces that when new Vaults are created, the balance /// is initialized correctly. /// - pub resource interface Balance { + access(all) resource interface Balance { /// The total balance of a vault /// - pub var balance: Int + access(all) var balance: Int init(balance: Int) { post { @@ -423,7 +423,7 @@ pub contract interface FungibleToken { /// /// The resource that contains the functions to send and receive tokens. /// - pub resource Vault: Provider, Receiver, Balance { + access(all) resource interface Vault: Provider, Receiver, Balance { // The declaration of a concrete type in a contract interface means that // every Fungible Token contract that implements the FungibleToken interface @@ -432,7 +432,7 @@ pub contract interface FungibleToken { /// The total balance of the vault /// - pub var balance: Int + access(all) var balance: Int // The conforming type must declare an initializer // that allows prioviding the initial balance of the Vault @@ -442,7 +442,7 @@ pub contract interface FungibleToken { /// withdraw subtracts 'amount' from the Vault's balance /// and returns a new Vault with the subtracted balance /// - pub fun withdraw(amount: Int): @Vault { + access(all) fun withdraw(amount: Int): @{Vault} { pre { self.balance >= amount: "Amount withdrawn must be less than or equal than the balance of the Vault" @@ -458,7 +458,7 @@ pub contract interface FungibleToken { /// deposit takes a Vault and adds its balance to the balance of this Vault /// - pub fun deposit(from: @Vault) { + access(all) fun deposit(from: @{Vault}) { post { self.balance == before(self.balance) + before(from.balance): "New Vault balance must be the sum of the previous balance and the deposited Vault" @@ -468,7 +468,7 @@ pub contract interface FungibleToken { /// createEmptyVault allows any user to create a new Vault that has a zero balance /// - pub fun createEmptyVault(): @Vault { + access(all) fun createEmptyVault(): @{Vault} { post { result.balance == 0: "The newly created Vault must have zero balance" } @@ -479,31 +479,10 @@ pub contract interface FungibleToken { const realFlowContract = ` import FungibleToken from 0x1 -pub contract FlowToken: FungibleToken { +access(all) contract FlowToken: FungibleToken { // Total supply of Flow tokens in existence - pub var totalSupply: Int - - //// Event that is emitted when the contract is created - //pub event TokensInitialized(initialSupply: Int) - // - //// Event that is emitted when tokens are withdrawn from a Vault - //pub event TokensWithdrawn(amount: Int, from: Address?) - // - //// Event that is emitted when tokens are deposited to a Vault - //pub event TokensDeposited(amount: Int, to: Address?) - // - //// Event that is emitted when new tokens are minted - //pub event TokensMinted(amount: Int) - // - //// Event that is emitted when tokens are destroyed - //pub event TokensBurned(amount: Int) - // - //// Event that is emitted when a new minter resource is created - //pub event MinterCreated(allowedAmount: Int) - // - //// Event that is emitted when a new burner resource is created - //pub event BurnerCreated() + access(all) var totalSupply: Int // Vault // @@ -517,10 +496,10 @@ pub contract FlowToken: FungibleToken { // out of thin air. A special Minter resource needs to be defined to mint // new tokens. // - pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance { + access(all) resource Vault: FungibleToken.Vault { // holds the balance of a users tokens - pub var balance: Int + access(all) var balance: Int // initialize the balance at resource creation time init(balance: Int) { @@ -536,9 +515,8 @@ pub contract FlowToken: FungibleToken { // created Vault to the context that called so it can be deposited // elsewhere. // - pub fun withdraw(amount: Int): @FungibleToken.Vault { + access(all) fun withdraw(amount: Int): @{FungibleToken.Vault} { self.balance = self.balance - amount - // emit TokensWithdrawn(amount: amount, from: self.owner?.address) return <-create Vault(balance: amount) } @@ -549,17 +527,12 @@ pub contract FlowToken: FungibleToken { // It is allowed to destroy the sent Vault because the Vault // was a temporary holder of the tokens. The Vault's balance has // been consumed and therefore can be destroyed. - pub fun deposit(from: @FungibleToken.Vault) { + access(all) fun deposit(from: @{FungibleToken.Vault}) { let vault <- from as! @FlowToken.Vault self.balance = self.balance + vault.balance - // emit TokensDeposited(amount: vault.balance, to: self.owner?.address) vault.balance = 0 destroy vault } - - destroy() { - FlowToken.totalSupply = FlowToken.totalSupply - self.balance - } } // createEmptyVault @@ -569,18 +542,16 @@ pub contract FlowToken: FungibleToken { // and store the returned Vault in their storage in order to allow their // account to be able to receive deposits of this token type. // - pub fun createEmptyVault(): @Vault { + access(all) fun createEmptyVault(): @{FungibleToken.Vault} { return <-create Vault(balance: 0) } - pub resource Administrator { - + access(all) resource Administrator { // createNewMinter // // Function that creates and returns a new minter resource // - pub fun createNewMinter(allowedAmount: Int): @Minter { - // emit MinterCreated(allowedAmount: allowedAmount) + access(all) fun createNewMinter(allowedAmount: Int): @Minter { return <-create Minter(allowedAmount: allowedAmount) } @@ -588,8 +559,7 @@ pub contract FlowToken: FungibleToken { // // Function that creates and returns a new burner resource // - pub fun createNewBurner(): @Burner { - // emit BurnerCreated() + access(all) fun createNewBurner(): @Burner { return <-create Burner() } } @@ -598,24 +568,23 @@ pub contract FlowToken: FungibleToken { // // Resource object that token admin accounts can hold to mint new tokens. // - pub resource Minter { + access(all) resource Minter { // the amount of tokens that the minter is allowed to mint - pub var allowedAmount: Int + access(all) var allowedAmount: Int // mintTokens // // Function that mints new tokens, adds them to the total supply, // and returns them to the calling context. // - pub fun mintTokens(amount: Int): @FlowToken.Vault { + access(all) fun mintTokens(amount: Int): @FlowToken.Vault { pre { - amount > Int(0): "Amount minted must be greater than zero" + amount > 0: "Amount minted must be greater than zero" amount <= self.allowedAmount: "Amount minted must be less than the allowed amount" } FlowToken.totalSupply = FlowToken.totalSupply + amount self.allowedAmount = self.allowedAmount - amount - // emit TokensMinted(amount: amount) return <-create Vault(balance: amount) } @@ -628,7 +597,7 @@ pub contract FlowToken: FungibleToken { // // Resource object that token admin accounts can hold to burn tokens. // - pub resource Burner { + access(all) resource Burner { // burnTokens // @@ -637,43 +606,37 @@ pub contract FlowToken: FungibleToken { // Note: the burned tokens are automatically subtracted from the // total supply in the Vault destructor. // - pub fun burnTokens(from: @FungibleToken.Vault) { + access(all) fun burnTokens(from: @{FungibleToken.Vault}) { let vault <- from as! @FlowToken.Vault let amount = vault.balance destroy vault - // emit TokensBurned(amount: amount) } } - init(adminAccount: AuthAccount) { + init(adminAccount: auth(Storage, Capabilities) &Account) { self.totalSupply = 0 // Create the Vault with the total supply of tokens and save it in storage // let vault <- create Vault(balance: self.totalSupply) - adminAccount.save(<-vault, to: /storage/flowTokenVault) + adminAccount.storage.save(<-vault, to: /storage/flowTokenVault) // Create a public capability to the stored Vault that only exposes // the 'deposit' method through the 'Receiver' interface // - adminAccount.link<&FlowToken.Vault{FungibleToken.Receiver}>( - /public/flowTokenReceiver, - target: /storage/flowTokenVault - ) + let receiverCap = adminAccount.capabilities.storage + .issue<&FlowToken.Vault>(/storage/flowTokenVault) + adminAccount.capabilities.publish(receiverCap, at: /public/flowTokenReceiver) // Create a public capability to the stored Vault that only exposes // the 'balance' field through the 'Balance' interface // - adminAccount.link<&FlowToken.Vault{FungibleToken.Balance}>( - /public/flowTokenBalance, - target: /storage/flowTokenVault - ) + let balanceCap = adminAccount.capabilities.storage + .issue<&FlowToken.Vault>(/storage/flowTokenVault) + adminAccount.capabilities.publish(balanceCap, at: /public/flowTokenBalance) let admin <- create Administrator() - adminAccount.save(<-admin, to: /storage/flowTokenAdmin) - - // Emit an event that shows that the contract was initialized - // emit TokensInitialized(initialSupply: self.totalSupply) + adminAccount.storage.save(<-admin, to: /storage/flowTokenAdmin) } } ` @@ -684,22 +647,22 @@ import FlowToken from 0x1 transaction { - prepare(signer: AuthAccount) { + prepare(signer: &Account) { - if signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) == nil { + if signer.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) == nil { // Create a new flowToken Vault and put it in storage - signer.save(<-FlowToken.createEmptyVault(), to: /storage/flowTokenVault) + signer.storage.save(<-FlowToken.createEmptyVault(), to: /storage/flowTokenVault) // Create a public capability to the Vault that only exposes // the deposit function through the Receiver interface - signer.link<&FlowToken.Vault{FungibleToken.Receiver}>( + signer.link<&FlowToken.Vault>( /public/flowTokenReceiver, target: /storage/flowTokenVault ) // Create a public capability to the Vault that only exposes // the balance field through the Balance interface - signer.link<&FlowToken.Vault{FungibleToken.Balance}>( + signer.link<&FlowToken.Vault>( /public/flowTokenBalance, target: /storage/flowTokenVault ) @@ -712,31 +675,28 @@ const realFlowTokenTransferTransaction = ` import FungibleToken from 0x1 import FlowToken from 0x1 -transaction(amount: Int, to: Address) { - - // The Vault resource that holds the tokens that are being transferred - let sentVault: @FungibleToken.Vault - - prepare(signer: AuthAccount) { +transaction(recipient: Address, amount: Int) { + let tokenAdmin: &FlowToken.Administrator + let tokenReceiver: &{FungibleToken.Receiver} - // Get a reference to the signer's stored vault - let vaultRef = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) - ?? panic("Could not borrow reference to the owner's Vault!") + prepare(signer: &Account) { + self.tokenAdmin = signer + .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + ?? panic("Signer is not the token admin") - // Withdraw tokens from the signer's stored vault - self.sentVault <- vaultRef.withdraw(amount: amount) + self.tokenReceiver = getAccount(recipient) + .getCapability(/public/flowTokenReceiver) + .borrow<&{FungibleToken.Receiver}>() + ?? panic("Unable to borrow receiver reference") } execute { + let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) + let mintedVault <- minter.mintTokens(amount: amount) - // Get a reference to the recipient's Receiver - let receiverRef = getAccount(to) - .getCapability(/public/flowTokenReceiver) - .borrow<&{FungibleToken.Receiver}>() - ?? panic("Could not borrow receiver reference to the recipient's Vault") + self.tokenReceiver.deposit(from: <-mintedVault) - // Deposit the withdrawn tokens in the recipient's receiver - receiverRef.deposit(from: <-self.sentVault) + destroy minter } } ` @@ -745,29 +705,31 @@ const realMintFlowTokenTransaction = ` import FungibleToken from 0x1 import FlowToken from 0x1 -transaction(recipient: Address, amount: Int) { - let signer: AuthAccount +transaction(amount: Int, to: Address) { + + // The Vault resource that holds the tokens that are being transferred + let sentVault: @{FungibleToken.Vault} + + prepare(signer: &Account) { + + // Get a reference to the signer's stored vault + let vaultRef = signer.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) + ?? panic("Could not borrow reference to the owner's Vault!") - prepare(signer: AuthAccount) { - self.signer = signer + // Withdraw tokens from the signer's stored vault + self.sentVault <- vaultRef.withdraw(amount: amount) } execute { - var tokenAdmin = self.signer - .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) - ?? panic("Signer is not the token admin") - var tokenReceiver = getAccount(recipient) + // Get a reference to the recipient's Receiver + let receiverRef = getAccount(to) .getCapability(/public/flowTokenReceiver) .borrow<&{FungibleToken.Receiver}>() - ?? panic("Unable to borrow receiver reference") - - let minter <- tokenAdmin.createNewMinter(allowedAmount: amount) - let mintedVault <- minter.mintTokens(amount: amount) - - tokenReceiver.deposit(from: <-mintedVault) + ?? panic("Could not borrow receiver reference to the recipient's Vault") - destroy minter + // Deposit the withdrawn tokens in the recipient's receiver + receiverRef.deposit(from: <-self.sentVault) } } ` @@ -776,11 +738,11 @@ const realFlowTokenBalanceScript = ` import FungibleToken from 0x1 import FlowToken from 0x1 -pub fun main(account: Address): Int { +access(all) fun main(account: Address): Int { let vaultRef = getAccount(account) .getCapability(/public/flowTokenBalance) - .borrow<&FlowToken.Vault{FungibleToken.Balance}>() + .borrow<&FlowToken.Vault>() ?? panic("Could not borrow Balance reference to the Vault") return vaultRef.balance diff --git a/runtime/bbq/vm/storage.go b/runtime/bbq/vm/storage.go index 690477b59d..a614b15518 100644 --- a/runtime/bbq/vm/storage.go +++ b/runtime/bbq/vm/storage.go @@ -57,15 +57,16 @@ func WriteStored( config *Config, storageAddress common.Address, domain string, - identifier string, + key interpreter.StorageMapKey, value Value, -) { +) (existed bool) { storage := config.Storage accountStorage := storage.GetStorageMap(storageAddress, domain, true) interValue := VMValueToInterpreterValue(storage, value) - accountStorage.WriteValue( + + return accountStorage.WriteValue( config.interpreter(), - interpreter.StringStorageMapKey(identifier), + key, interValue, ) //interpreter.recordStorageMutation() @@ -84,6 +85,19 @@ func RemoveReferencedSlab(storage interpreter.Storage, storable atree.Storable) } } +func StoredValueExists( + storage interpreter.Storage, + storageAddress common.Address, + domain string, + identifier interpreter.StorageMapKey, +) bool { + accountStorage := storage.GetStorageMap(storageAddress, domain, false) + if accountStorage == nil { + return false + } + return accountStorage.ValueExists(identifier) +} + // //// InMemoryStorage //type InMemoryStorage struct { diff --git a/runtime/bbq/vm/value_account.go b/runtime/bbq/vm/value_account.go index c4055496bd..65588606ef 100644 --- a/runtime/bbq/vm/value_account.go +++ b/runtime/bbq/vm/value_account.go @@ -24,8 +24,14 @@ import ( "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" ) +type AccountIDGenerator interface { + // GenerateAccountID generates a new, *non-zero*, unique ID for the given account. + GenerateAccountID(address common.Address) (uint64, error) +} + func NewAuthAccountReferenceValue( address common.Address, ) *EphemeralReferenceValue { @@ -135,8 +141,9 @@ func init() { // }, //}) - accountStorageTypeName := interpreter.PrimitiveStaticTypeAccount_Storage.String() - accountCapabilitiesTypeName := interpreter.PrimitiveStaticTypeAccount_Capabilities.String() + accountStorageTypeName := sema.Account_StorageType.QualifiedIdentifier() + accountCapabilitiesTypeName := sema.Account_CapabilitiesType.QualifiedIdentifier() + accountStorageCapabilitiesTypeName := sema.Account_StorageCapabilitiesType.QualifiedIdentifier() // Account.Storage.save RegisterTypeBoundFunction( @@ -188,7 +195,7 @@ func init() { config, common.Address(addressValue), domain, - identifier, + interpreter.StringStorageMapKey(identifier), value, ) @@ -282,7 +289,7 @@ func init() { var borrowType *interpreter.ReferenceStaticType if len(typeArguments) > 0 { - ty := typeArguments[1] + ty := typeArguments[0] // we handle the nil case for this below borrowType, _ = ty.(*interpreter.ReferenceStaticType) } @@ -296,6 +303,139 @@ func init() { ) }, }) + + // Account.Capabilities.publish + RegisterTypeBoundFunction( + accountCapabilitiesTypeName, + sema.Account_CapabilitiesTypePublishFunctionName, + NativeFunctionValue{ + ParameterCount: len(sema.Account_CapabilitiesTypeGetFunctionType.Parameters), + Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { + // Get address field from the receiver (Account) + account, ok := args[0].(*SimpleCompositeValue) + if !ok { + panic(errors.NewUnreachableError()) + } + accountAddressValue, ok := account.GetMember(config, sema.AccountTypeAddressFieldName).(AddressValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + // Get capability argument + + var capabilityValue *CapabilityValue + switch firstValue := args[1].(type) { + case *CapabilityValue: + capabilityValue = firstValue + default: + panic(errors.NewUnreachableError()) + } + + capabilityAddressValue := capabilityValue.Address + if capabilityAddressValue != accountAddressValue { + panic(interpreter.CapabilityAddressPublishingError{ + CapabilityAddress: interpreter.AddressValue(capabilityAddressValue), + AccountAddress: interpreter.AddressValue(accountAddressValue), + }) + } + + // Get path argument + + path, ok := args[2].(PathValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + if !ok || path.Domain != common.PathDomainPublic { + panic(errors.NewUnreachableError()) + } + + domain := path.Domain.Identifier() + identifier := path.Identifier + + // Prevent an overwrite + + accountAddress := common.Address(accountAddressValue) + + storageMapKey := interpreter.StringStorageMapKey(identifier) + if StoredValueExists( + config.Storage, + common.Address(accountAddress), + domain, + storageMapKey, + ) { + panic(interpreter.OverwriteError{ + Address: interpreter.AddressValue(accountAddress), + Path: VMValueToInterpreterValue(config.Storage, path).(interpreter.PathValue), + }) + } + + capabilityValue, ok = capabilityValue.Transfer( + config, + atree.Address(accountAddress), + true, + nil, + ).(*CapabilityValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + // Write new value + + WriteStored( + config, + accountAddress, + domain, + storageMapKey, + capabilityValue, + ) + + return Void + }, + }) + + // Account.StorageCapabilities.issue + RegisterTypeBoundFunction( + accountStorageCapabilitiesTypeName, + sema.Account_StorageCapabilitiesTypeIssueFunctionName, + NativeFunctionValue{ + ParameterCount: len(sema.Account_CapabilitiesTypeGetFunctionType.Parameters), + Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { + // Get address field from the receiver (PublicAccount) + authAccount, ok := args[0].(*SimpleCompositeValue) + if !ok { + panic(errors.NewUnreachableError()) + } + address := authAccount.GetMember(config, sema.AccountTypeAddressFieldName) + addressValue, ok := address.(AddressValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + // Path argument + targetPathValue, ok := args[1].(PathValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + if !ok || targetPathValue.Domain != common.PathDomainStorage { + panic(errors.NewUnreachableError()) + } + + // Get borrow type type-argument + ty := typeArguments[0] + + // Issue capability controller and return capability + + return checkAndIssueStorageCapabilityControllerWithType( + config, + config.AccountHandler, + common.Address(addressValue), + targetPathValue, + ty, + ) + }, + }) } func getCapability( @@ -430,3 +570,159 @@ func BorrowCapabilityController( return referenceValue } + +func checkAndIssueStorageCapabilityControllerWithType( + config *Config, + idGenerator AccountIDGenerator, + address common.Address, + targetPathValue PathValue, + ty StaticType, +) CapabilityValue { + + borrowType, ok := ty.(*interpreter.ReferenceStaticType) + if !ok { + // TODO: remove conversion. se static type in error + semaType, err := config.interpreter().ConvertStaticToSemaType(ty) + if err != nil { + panic(err) + } + panic(interpreter.InvalidCapabilityIssueTypeError{ + ExpectedTypeDescription: "reference type", + ActualType: semaType, + }) + } + + // Issue capability controller + + capabilityIDValue := IssueStorageCapabilityController( + config, + idGenerator, + address, + borrowType, + targetPathValue, + ) + + if capabilityIDValue == InvalidCapabilityID { + panic(interpreter.InvalidCapabilityIDError{}) + } + + // Return controller's capability + + return NewCapabilityValue( + AddressValue(address), + capabilityIDValue, + borrowType, + ) +} + +func IssueStorageCapabilityController( + config *Config, + idGenerator AccountIDGenerator, + address common.Address, + borrowType *interpreter.ReferenceStaticType, + targetPathValue PathValue, +) IntValue { + // Create and write StorageCapabilityController + + var capabilityID uint64 + var err error + errors.WrapPanic(func() { + capabilityID, err = idGenerator.GenerateAccountID(address) + }) + if err != nil { + panic(interpreter.WrappedExternalError(err)) + } + if capabilityID == 0 { + panic(errors.NewUnexpectedError("invalid zero account ID")) + } + + capabilityIDValue := IntValue{ + SmallInt: int64(capabilityID), + } + + controller := NewStorageCapabilityControllerValue( + borrowType, + capabilityIDValue, + targetPathValue, + ) + + storeCapabilityController(config, address, capabilityIDValue, controller) + recordStorageCapabilityController(config, address, targetPathValue, capabilityIDValue) + + return capabilityIDValue +} + +func storeCapabilityController( + config *Config, + address common.Address, + capabilityIDValue IntValue, + controller CapabilityControllerValue, +) { + storageMapKey := interpreter.Uint64StorageMapKey(capabilityIDValue.SmallInt) + + existed := WriteStored( + config, + address, + stdlib.CapabilityControllerStorageDomain, + storageMapKey, + controller, + ) + + if existed { + panic(errors.NewUnreachableError()) + } +} + +var capabilityIDSetStaticType = &interpreter.DictionaryStaticType{ + KeyType: interpreter.PrimitiveStaticTypeUInt64, + ValueType: interpreter.NilStaticType, +} + +func recordStorageCapabilityController( + config *Config, + address common.Address, + targetPathValue PathValue, + capabilityIDValue IntValue, +) { + if targetPathValue.Domain != common.PathDomainStorage { + panic(errors.NewUnreachableError()) + } + + addressPath := AddressPath{ + Address: address, + Path: targetPathValue, + } + if config.CapabilityControllerIterations[addressPath] > 0 { + config.MutationDuringCapabilityControllerIteration = true + } + + storage := config.Storage + identifier := targetPathValue.Identifier + + storageMapKey := interpreter.StringStorageMapKey(identifier) + + accountStorage := storage.GetStorageMap(address, stdlib.PathCapabilityStorageDomain, false) + + referenced := accountStorage.ReadValue(config.MemoryGauge, interpreter.StringStorageMapKey(identifier)) + readValue := InterpreterValueToVMValue(referenced) + + setKey := capabilityIDValue + setValue := Nil + + if readValue == nil { + capabilityIDSet := NewDictionaryValue( + config, + capabilityIDSetStaticType, + setKey, + setValue, + ) + capabilityIDSetInterValue := VMValueToInterpreterValue(storage, capabilityIDSet) + accountStorage.SetValue(config.interpreter(), storageMapKey, capabilityIDSetInterValue) + } else { + capabilityIDSet := readValue.(*DictionaryValue) + existing := capabilityIDSet.Insert(config, setKey, setValue) + if existing != Nil { + panic(errors.NewUnreachableError()) + } + } +} diff --git a/runtime/bbq/vm/value_capability_controller.go b/runtime/bbq/vm/value_capability_controller.go index c5979bf76c..1aff466401 100644 --- a/runtime/bbq/vm/value_capability_controller.go +++ b/runtime/bbq/vm/value_capability_controller.go @@ -80,13 +80,10 @@ func NewUnmeteredStorageCapabilityControllerValue( } func NewStorageCapabilityControllerValue( - memoryGauge common.MemoryGauge, borrowType *interpreter.ReferenceStaticType, capabilityID IntValue, targetPath PathValue, ) *StorageCapabilityControllerValue { - // Constant because its constituents are already metered. - common.UseMemory(memoryGauge, common.StorageCapabilityControllerValueMemoryUsage) return NewUnmeteredStorageCapabilityControllerValue( borrowType, capabilityID, @@ -165,7 +162,7 @@ func (v *StorageCapabilityControllerValue) GetMember(config *Config, name string } func init() { - typeName := interpreter.PrimitiveStaticTypeStorageCapabilityController.String() + typeName := sema.StorageCapabilityControllerType.QualifiedName // Capability.borrow RegisterTypeBoundFunction( @@ -176,7 +173,7 @@ func init() { Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { capabilityValue := args[0].(*StorageCapabilityControllerValue) - //stdlib.SetCapabilityControllerTag(config.inter) + //stdlib.SetCapabilityControllerTag(config.interpreter()) capabilityValue.checkDeleted() diff --git a/runtime/bbq/vm/value_composite.go b/runtime/bbq/vm/value_composite.go index ee382d9db8..8c84901fbf 100644 --- a/runtime/bbq/vm/value_composite.go +++ b/runtime/bbq/vm/value_composite.go @@ -264,6 +264,7 @@ func (v *CompositeValue) Transfer( value := interpreter.MustConvertStoredValue(conf.MemoryGauge, atreeValue) + // TODO: vmValue := InterpreterValueToVMValue(value) vmValue.Transfer(conf, address, remove, nil) diff --git a/runtime/bbq/vm/value_dictionary.go b/runtime/bbq/vm/value_dictionary.go new file mode 100644 index 0000000000..9d331dd8e5 --- /dev/null +++ b/runtime/bbq/vm/value_dictionary.go @@ -0,0 +1,334 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" +) + +type DictionaryValue struct { + dictionary *atree.OrderedMap + Type *interpreter.DictionaryStaticType + elementSize uint +} + +var _ Value = &DictionaryValue{} +var _ MemberAccessibleValue = &DictionaryValue{} + +func NewDictionaryValue( + conf *Config, + dictionaryType *interpreter.DictionaryStaticType, + keysAndValues ...Value, +) *DictionaryValue { + + address := common.ZeroAddress + + keysAndValuesCount := len(keysAndValues) + if keysAndValuesCount%2 != 0 { + panic("uneven number of keys and values") + } + + constructor := func() *atree.OrderedMap { + dictionary, err := atree.NewMap( + conf.Storage, + atree.Address(address), + atree.NewDefaultDigesterBuilder(), + dictionaryType, + ) + if err != nil { + panic(errors.NewExternalError(err)) + } + return dictionary + } + + // values are added to the dictionary after creation, not here + v := newDictionaryValueFromConstructor(dictionaryType, constructor) + + for i := 0; i < keysAndValuesCount; i += 2 { + key := keysAndValues[i] + value := keysAndValues[i+1] + existingValue := v.Insert(conf, key, value) + // If the dictionary already contained a value for the key, + // and the dictionary is resource-typed, + // then we need to prevent a resource loss + if _, ok := existingValue.(*SomeValue); ok { + if v.IsResourceKinded() { + panic(interpreter.DuplicateKeyInResourceDictionaryError{}) + } + } + } + + return v +} + +func newDictionaryValueFromConstructor( + staticType *interpreter.DictionaryStaticType, + constructor func() *atree.OrderedMap, +) *DictionaryValue { + + elementSize := interpreter.DictionaryElementSize(staticType) + return newDictionaryValueFromAtreeMap( + staticType, + elementSize, + constructor(), + ) +} + +func newDictionaryValueFromAtreeMap( + staticType *interpreter.DictionaryStaticType, + elementSize uint, + atreeOrderedMap *atree.OrderedMap, +) *DictionaryValue { + + return &DictionaryValue{ + Type: staticType, + dictionary: atreeOrderedMap, + elementSize: elementSize, + } +} + +func (*DictionaryValue) isValue() {} + +func (v *DictionaryValue) StaticType(memoryGauge common.MemoryGauge) StaticType { + return v.Type +} + +func (v *DictionaryValue) GetMember(config *Config, name string) Value { + // TODO: + return nil +} + +func (v *DictionaryValue) SetMember(conf *Config, name string, value Value) { + // Dictionaries have no settable members (fields / functions) + panic(errors.NewUnreachableError()) +} + +func (v *DictionaryValue) Insert( + conf *Config, + keyValue, value Value, +) Value /* TODO: OptionalValue*/ { + + address := v.dictionary.Address() + + keyValue = keyValue.Transfer( + conf, + address, + true, + nil, + ) + + value = value.Transfer( + conf, + address, + true, + nil, + ) + + //interpreter.checkContainerMutation(v.Type.KeyType, keyValue, locationRange) + //interpreter.checkContainerMutation(v.Type.ValueType, value, locationRange) + + existingValueStorable := v.InsertWithoutTransfer(conf, keyValue, value) + + if existingValueStorable == nil { + return Nil // TODO: NilOptionalValue + } + + existingValue := StoredValue( + conf, + existingValueStorable, + conf.Storage, + ).Transfer( + conf, + atree.Address{}, + true, + existingValueStorable, + ) + + return NewSomeValueNonCopying(existingValue) +} + +func (v *DictionaryValue) InsertWithoutTransfer(conf *Config, key, value Value) (existingValueStorable atree.Storable) { + + //interpreter.validateMutation(v.StorageID(), locationRange) + + valueComparator := newValueComparator(conf) + hashInputProvider := newHashInputProvider(conf) + + keyInterpreterValue := VMValueToInterpreterValue(conf.Storage, key) + valueInterpreterValue := VMValueToInterpreterValue(conf.Storage, value) + + // atree only calls Storable() on keyValue if needed, + // i.e., if the key is a new key + var err error + existingValueStorable, err = v.dictionary.Set( + valueComparator, + hashInputProvider, + keyInterpreterValue, + valueInterpreterValue, + ) + if err != nil { + panic(errors.NewExternalError(err)) + } + + //interpreter.maybeValidateAtreeValue(v.dictionary) + + return existingValueStorable +} + +func (v *DictionaryValue) StorageID() atree.StorageID { + return v.dictionary.StorageID() +} + +func (v *DictionaryValue) IsResourceKinded() bool { + // TODO: + return false +} + +func (v *DictionaryValue) String() string { + //TODO implement me + panic("implement me") +} + +func (v *DictionaryValue) Transfer( + conf *Config, + address atree.Address, + remove bool, + storable atree.Storable, +) Value { + currentStorageID := v.StorageID() + currentAddress := currentStorageID.Address + + dictionary := v.dictionary + + needsStoreTo := address != currentAddress + isResourceKinded := v.IsResourceKinded() + + if needsStoreTo || !isResourceKinded { + valueComparator := newValueComparator(conf) + hashInputProvider := newHashInputProvider(conf) + + iterator, err := v.dictionary.Iterator() + if err != nil { + panic(errors.NewExternalError(err)) + } + + dictionary, err = atree.NewMapFromBatchData( + conf.Storage, + address, + atree.NewDefaultDigesterBuilder(), + v.dictionary.Type(), + valueComparator, + hashInputProvider, + v.dictionary.Seed(), + func() (atree.Value, atree.Value, error) { + + atreeKey, atreeValue, err := iterator.Next() + if err != nil { + return nil, nil, err + } + if atreeKey == nil || atreeValue == nil { + return nil, nil, nil + } + + key := interpreter.MustConvertStoredValue(conf.MemoryGauge, atreeValue) + // TODO: converted value is unused + vmKey := InterpreterValueToVMValue(key) + vmKey = vmKey.Transfer(conf, address, remove, nil) + + value := interpreter.MustConvertStoredValue(conf.MemoryGauge, atreeValue) + // TODO: converted value is unused + vmValue := InterpreterValueToVMValue(value) + vmValue = vmValue.Transfer(conf, address, remove, nil) + + return key, value, nil + }, + ) + if err != nil { + panic(errors.NewExternalError(err)) + } + + if remove { + err = v.dictionary.PopIterate(func(keyStorable atree.Storable, valueStorable atree.Storable) { + RemoveReferencedSlab(conf.Storage, keyStorable) + RemoveReferencedSlab(conf.Storage, valueStorable) + }) + if err != nil { + panic(errors.NewExternalError(err)) + } + //interpreter.maybeValidateAtreeValue(v.dictionary) + + RemoveReferencedSlab(conf.Storage, storable) + } + } + + if isResourceKinded { + // Update the resource in-place, + // and also update all values that are referencing the same value + // (but currently point to an outdated Go instance of the value) + + // If checking of transfers of invalidated resource is enabled, + // then mark the resource array as invalidated, by unsetting the backing array. + // This allows raising an error when the resource array is attempted + // to be transferred/moved again (see beginning of this function) + + // TODO: + //interpreter.invalidateReferencedResources(v, locationRange) + + v.dictionary = nil + } + + res := newDictionaryValueFromAtreeMap( + v.Type, + v.elementSize, + dictionary, + ) + + //res.semaType = v.semaType + //res.isResourceKinded = v.isResourceKinded + //res.isDestroyed = v.isDestroyed + + return res +} + +func (v *DictionaryValue) Destroy(*Config) { + v.dictionary = nil +} + +func newValueComparator(conf *Config) atree.ValueComparator { + return func(storage atree.SlabStorage, atreeValue atree.Value, otherStorable atree.Storable) (bool, error) { + inter := conf.interpreter() + locationRange := interpreter.EmptyLocationRange + value := interpreter.MustConvertStoredValue(inter, atreeValue) + otherValue := interpreter.StoredValue(inter, otherStorable, storage) + return value.(interpreter.EquatableValue).Equal(inter, locationRange, otherValue), nil + } +} + +func newHashInputProvider(conf *Config) atree.HashInputProvider { + return func(value atree.Value, scratch []byte) ([]byte, error) { + inter := conf.interpreter() + locationRange := interpreter.EmptyLocationRange + hashInput := interpreter.MustConvertStoredValue(inter, value).(interpreter.HashableValue). + HashInput(inter, locationRange, scratch) + return hashInput, nil + } +} diff --git a/runtime/bbq/vm/value_function.go b/runtime/bbq/vm/value_function.go index f57d8dc036..1fadf311e5 100644 --- a/runtime/bbq/vm/value_function.go +++ b/runtime/bbq/vm/value_function.go @@ -48,10 +48,12 @@ func (v FunctionValue) String() string { panic("implement me") } +type NativeFunction func(config *Config, typeArguments []StaticType, arguments ...Value) Value + type NativeFunctionValue struct { Name string ParameterCount int - Function func(config *Config, typeArguments []StaticType, arguments ...Value) Value + Function NativeFunction } var _ Value = NativeFunctionValue{} diff --git a/runtime/bbq/vm/value_void.go b/runtime/bbq/vm/value_void.go index 36cb19ce45..38ff0a2c46 100644 --- a/runtime/bbq/vm/value_void.go +++ b/runtime/bbq/vm/value_void.go @@ -28,7 +28,7 @@ import ( type VoidValue struct{} -var _ Value = VoidValue{} +var Void Value = VoidValue{} func (VoidValue) isValue() {} From b90fb6f6b6081bafd7fad6e367a93d139adae0e5 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 10 Jul 2024 14:25:30 -0700 Subject: [PATCH 38/89] Add meta info to SimpleCompositeValue --- runtime/bbq/vm/value_account.go | 100 +++++++------------ runtime/bbq/vm/value_account_capabilities.go | 60 +++++++++++ runtime/bbq/vm/value_account_storage.go | 45 +++++++++ runtime/bbq/vm/value_capability.go | 5 +- runtime/bbq/vm/value_simple_composite.go | 1 + 5 files changed, 145 insertions(+), 66 deletions(-) create mode 100644 runtime/bbq/vm/value_account_capabilities.go create mode 100644 runtime/bbq/vm/value_account_storage.go diff --git a/runtime/bbq/vm/value_account.go b/runtime/bbq/vm/value_account.go index 65588606ef..27018f3d83 100644 --- a/runtime/bbq/vm/value_account.go +++ b/runtime/bbq/vm/value_account.go @@ -69,7 +69,9 @@ func newAccountValue( staticType: interpreter.PrimitiveStaticTypeAccount, Kind: common.CompositeKindStructure, fields: map[string]Value{ - sema.AccountTypeAddressFieldName: AddressValue(address), + sema.AccountTypeAddressFieldName: AddressValue(address), + sema.AccountTypeStorageFieldName: NewAccountStorageValue(address), + sema.AccountTypeCapabilitiesFieldName: NewAccountCapabilitiesValue(address), // TODO: add the remaining fields }, } @@ -152,15 +154,7 @@ func init() { NativeFunctionValue{ ParameterCount: len(sema.Account_StorageTypeSaveFunctionType.Parameters), Function: func(config *Config, typeArs []StaticType, args ...Value) Value { - authAccount, ok := args[0].(*SimpleCompositeValue) - if !ok { - panic(errors.NewUnreachableError()) - } - address := authAccount.GetMember(config, sema.AccountTypeAddressFieldName) - addressValue, ok := address.(AddressValue) - if !ok { - panic(errors.NewUnreachableError()) - } + address := getAddressMetaInfoFromValue(args[0]) value := args[1] @@ -184,7 +178,7 @@ func init() { value = value.Transfer( config, - atree.Address(addressValue), + atree.Address(address), true, nil, ) @@ -193,7 +187,7 @@ func init() { WriteStored( config, - common.Address(addressValue), + address, domain, interpreter.StringStorageMapKey(identifier), value, @@ -210,10 +204,7 @@ func init() { NativeFunctionValue{ ParameterCount: len(sema.Account_StorageTypeBorrowFunctionType.Parameters), Function: func(config *Config, typeArgs []StaticType, args ...Value) Value { - authAccount, ok := args[0].(*SimpleCompositeValue) - if !ok { - panic(errors.NewUnreachableError()) - } + address := getAddressMetaInfoFromValue(args[0]) path, ok := args[1].(PathValue) if !ok { @@ -225,16 +216,10 @@ func init() { panic(errors.NewUnreachableError()) } - address := authAccount.GetMember(config, sema.AccountTypeAddressFieldName) - addressValue, ok := address.(AddressValue) - if !ok { - panic(errors.NewUnreachableError()) - } - reference := NewStorageReferenceValue( config.Storage, referenceType.Authorization, - common.Address(addressValue), + address, path, referenceType, ) @@ -262,16 +247,8 @@ func init() { NativeFunctionValue{ ParameterCount: len(sema.Account_CapabilitiesTypeGetFunctionType.Parameters), Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { - // Get address field from the receiver (PublicAccount) - authAccount, ok := args[0].(*SimpleCompositeValue) - if !ok { - panic(errors.NewUnreachableError()) - } - address := authAccount.GetMember(config, sema.AccountTypeAddressFieldName) - addressValue, ok := address.(AddressValue) - if !ok { - panic(errors.NewUnreachableError()) - } + // Get address field from the receiver (Account.Capabilities) + address := getAddressMetaInfoFromValue(args[0]) // Path argument path, ok := args[1].(PathValue) @@ -296,7 +273,7 @@ func init() { return getCapability( config, - addressValue, + address, path, borrowType, true, @@ -311,15 +288,8 @@ func init() { NativeFunctionValue{ ParameterCount: len(sema.Account_CapabilitiesTypeGetFunctionType.Parameters), Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { - // Get address field from the receiver (Account) - account, ok := args[0].(*SimpleCompositeValue) - if !ok { - panic(errors.NewUnreachableError()) - } - accountAddressValue, ok := account.GetMember(config, sema.AccountTypeAddressFieldName).(AddressValue) - if !ok { - panic(errors.NewUnreachableError()) - } + // Get address field from the receiver (Account.Capabilities) + accountAddress := getAddressMetaInfoFromValue(args[0]) // Get capability argument @@ -331,11 +301,11 @@ func init() { panic(errors.NewUnreachableError()) } - capabilityAddressValue := capabilityValue.Address - if capabilityAddressValue != accountAddressValue { + capabilityAddressValue := common.Address(capabilityValue.Address) + if capabilityAddressValue != accountAddress { panic(interpreter.CapabilityAddressPublishingError{ CapabilityAddress: interpreter.AddressValue(capabilityAddressValue), - AccountAddress: interpreter.AddressValue(accountAddressValue), + AccountAddress: interpreter.AddressValue(accountAddress), }) } @@ -355,12 +325,10 @@ func init() { // Prevent an overwrite - accountAddress := common.Address(accountAddressValue) - storageMapKey := interpreter.StringStorageMapKey(identifier) if StoredValueExists( config.Storage, - common.Address(accountAddress), + accountAddress, domain, storageMapKey, ) { @@ -401,16 +369,8 @@ func init() { NativeFunctionValue{ ParameterCount: len(sema.Account_CapabilitiesTypeGetFunctionType.Parameters), Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { - // Get address field from the receiver (PublicAccount) - authAccount, ok := args[0].(*SimpleCompositeValue) - if !ok { - panic(errors.NewUnreachableError()) - } - address := authAccount.GetMember(config, sema.AccountTypeAddressFieldName) - addressValue, ok := address.(AddressValue) - if !ok { - panic(errors.NewUnreachableError()) - } + // Get address field from the receiver (Account.StorageCapabilities) + accountAddress := getAddressMetaInfoFromValue(args[0]) // Path argument targetPathValue, ok := args[1].(PathValue) @@ -430,7 +390,7 @@ func init() { return checkAndIssueStorageCapabilityControllerWithType( config, config.AccountHandler, - common.Address(addressValue), + accountAddress, targetPathValue, ty, ) @@ -438,9 +398,24 @@ func init() { }) } +func getAddressMetaInfoFromValue(value Value) common.Address { + simpleCompositeValue, ok := value.(*SimpleCompositeValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + addressMetaInfo := simpleCompositeValue.metaInfo[sema.AccountTypeAddressFieldName] + address, ok := addressMetaInfo.(common.Address) + if !ok { + panic(errors.NewUnreachableError()) + } + + return address +} + func getCapability( config *Config, - address AddressValue, + address common.Address, path PathValue, wantedBorrowType *interpreter.ReferenceStaticType, borrow bool, @@ -451,7 +426,6 @@ func getCapability( } else { failValue = NewInvalidCapabilityValue( - config.MemoryGauge, address, wantedBorrowType, ) @@ -465,7 +439,7 @@ func getCapability( readValue := ReadStored( config.MemoryGauge, config.Storage, - common.Address(address), + address, domain, identifier, ) diff --git a/runtime/bbq/vm/value_account_capabilities.go b/runtime/bbq/vm/value_account_capabilities.go new file mode 100644 index 0000000000..92ae03ec35 --- /dev/null +++ b/runtime/bbq/vm/value_account_capabilities.go @@ -0,0 +1,60 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" +) + +func NewAccountCapabilitiesValue(accountAddress common.Address) *SimpleCompositeValue { + return &SimpleCompositeValue{ + typeID: sema.Account_StorageType.ID(), + staticType: interpreter.PrimitiveStaticTypeAccount_Capabilities, + Kind: common.CompositeKindStructure, + fields: map[string]Value{ + sema.Account_CapabilitiesTypeStorageFieldName: NewAccountStorageCapabilitiesValue(accountAddress), + // TODO: add the remaining fields + }, + metaInfo: map[string]any{ + sema.AccountTypeAddressFieldName: accountAddress, + }, + } +} + +func NewAccountStorageCapabilitiesValue(accountAddress common.Address) *SimpleCompositeValue { + return &SimpleCompositeValue{ + typeID: sema.Account_StorageCapabilitiesType.ID(), + staticType: interpreter.PrimitiveStaticTypeAccount_StorageCapabilities, + Kind: common.CompositeKindStructure, + fields: map[string]Value{ + // TODO: add the remaining fields + }, + metaInfo: map[string]any{ + sema.AccountTypeAddressFieldName: accountAddress, + }, + } +} + +// members + +func init() { + // TODO +} diff --git a/runtime/bbq/vm/value_account_storage.go b/runtime/bbq/vm/value_account_storage.go new file mode 100644 index 0000000000..1f630697c9 --- /dev/null +++ b/runtime/bbq/vm/value_account_storage.go @@ -0,0 +1,45 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" +) + +func NewAccountStorageValue(accountAddress common.Address) *SimpleCompositeValue { + return &SimpleCompositeValue{ + typeID: sema.Account_StorageType.ID(), + staticType: interpreter.PrimitiveStaticTypeAccount_Storage, + Kind: common.CompositeKindStructure, + fields: map[string]Value{ + // TODO: add the remaining fields + }, + metaInfo: map[string]any{ + sema.AccountTypeAddressFieldName: accountAddress, + }, + } +} + +// members + +func init() { + // TODO +} diff --git a/runtime/bbq/vm/value_capability.go b/runtime/bbq/vm/value_capability.go index ce22f2318b..a61275d03e 100644 --- a/runtime/bbq/vm/value_capability.go +++ b/runtime/bbq/vm/value_capability.go @@ -47,13 +47,12 @@ func NewCapabilityValue(address AddressValue, id IntValue, borrowType StaticType } func NewInvalidCapabilityValue( - memoryGauge common.MemoryGauge, - address AddressValue, + address common.Address, borrowType StaticType, ) *CapabilityValue { return &CapabilityValue{ ID: InvalidCapabilityID, - Address: address, + Address: AddressValue(address), BorrowType: borrowType, } } diff --git a/runtime/bbq/vm/value_simple_composite.go b/runtime/bbq/vm/value_simple_composite.go index aa6d638e76..d3dc031b32 100644 --- a/runtime/bbq/vm/value_simple_composite.go +++ b/runtime/bbq/vm/value_simple_composite.go @@ -28,6 +28,7 @@ type SimpleCompositeValue struct { typeID common.TypeID staticType StaticType Kind common.CompositeKind + metaInfo map[string]any } var _ Value = &CompositeValue{} From 5c3bd9a4686ce4e347faf5850477c79d49c5bfea Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 11 Jul 2024 10:41:52 -0700 Subject: [PATCH 39/89] Fix FT transfer transaction --- runtime/bbq/compiler/compiler.go | 4 +- runtime/bbq/opcode/opcode.go | 4 +- runtime/bbq/opcode/opcode_string.go | 57 ++- runtime/bbq/vm/ft_test.go | 62 ++-- runtime/bbq/vm/storage.go | 5 +- runtime/bbq/vm/test_utils.go | 330 ++++++++++++++++++ runtime/bbq/vm/value_account.go | 170 +-------- runtime/bbq/vm/value_account_capabilities.go | 162 ++++++++- runtime/bbq/vm/value_capability.go | 6 +- runtime/bbq/vm/value_capability_controller.go | 7 - runtime/bbq/vm/value_composite.go | 4 +- runtime/bbq/vm/value_conversions.go | 50 ++- runtime/bbq/vm/value_dictionary.go | 10 +- runtime/bbq/vm/value_int.go | 6 + runtime/bbq/vm/vm.go | 24 ++ runtime/interpreter/storage.go | 2 +- runtime/interpreter/value.go | 16 +- runtime/runtime_test.go | 22 +- 18 files changed, 673 insertions(+), 268 deletions(-) create mode 100644 runtime/bbq/vm/test_utils.go diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index 3b84b1e283..2b713ab16d 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -803,6 +803,8 @@ func TypeName(typ sema.Type) string { case *sema.IntersectionType: // TODO: Revisit. Probably this is not needed here? return TypeName(typ.Types[0]) + case *sema.CapabilityType: + return interpreter.PrimitiveStaticTypeCapability.String() default: return typ.QualifiedString() } @@ -918,7 +920,7 @@ var intBinaryOpcodes = [...]opcode.Opcode{ ast.OperationDiv: opcode.IntDivide, ast.OperationMod: opcode.IntMod, ast.OperationEqual: opcode.Equal, - ast.OperationNotEqual: opcode.IntNotEqual, + ast.OperationNotEqual: opcode.NotEqual, ast.OperationLess: opcode.IntLess, ast.OperationLessEqual: opcode.IntLessOrEqual, ast.OperationGreater: opcode.IntGreater, diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index 7fc78700b8..b409201e4b 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -39,9 +39,8 @@ const ( IntMultiply IntDivide IntMod - IntEqual - IntNotEqual IntLess + IntGreater IntLessOrEqual IntGreaterOrEqual @@ -49,6 +48,7 @@ const ( // Unary/Binary operators Equal + NotEqual Unwrap Destroy Transfer diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index 9cb47aeb27..891317dbcf 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -18,38 +18,37 @@ func _() { _ = x[IntMultiply-7] _ = x[IntDivide-8] _ = x[IntMod-9] - _ = x[IntEqual-10] - _ = x[IntNotEqual-11] - _ = x[IntLess-12] - _ = x[IntGreater-13] - _ = x[IntLessOrEqual-14] - _ = x[IntGreaterOrEqual-15] - _ = x[Equal-16] - _ = x[Unwrap-17] - _ = x[Destroy-18] - _ = x[Transfer-19] - _ = x[Cast-20] - _ = x[True-21] - _ = x[False-22] - _ = x[New-23] - _ = x[Path-24] - _ = x[Nil-25] - _ = x[GetConstant-26] - _ = x[GetLocal-27] - _ = x[SetLocal-28] - _ = x[GetGlobal-29] - _ = x[SetGlobal-30] - _ = x[GetField-31] - _ = x[SetField-32] - _ = x[Invoke-33] - _ = x[InvokeDynamic-34] - _ = x[Drop-35] - _ = x[Dup-36] + _ = x[IntLess-10] + _ = x[IntGreater-11] + _ = x[IntLessOrEqual-12] + _ = x[IntGreaterOrEqual-13] + _ = x[Equal-14] + _ = x[NotEqual-15] + _ = x[Unwrap-16] + _ = x[Destroy-17] + _ = x[Transfer-18] + _ = x[Cast-19] + _ = x[True-20] + _ = x[False-21] + _ = x[New-22] + _ = x[Path-23] + _ = x[Nil-24] + _ = x[GetConstant-25] + _ = x[GetLocal-26] + _ = x[SetLocal-27] + _ = x[GetGlobal-28] + _ = x[SetGlobal-29] + _ = x[GetField-30] + _ = x[SetField-31] + _ = x[Invoke-32] + _ = x[InvokeDynamic-33] + _ = x[Drop-34] + _ = x[Dup-35] } -const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntEqualIntNotEqualIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualEqualUnwrapDestroyTransferCastTrueFalseNewPathNilGetConstantGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeInvokeDynamicDropDup" +const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualEqualNotEqualUnwrapDestroyTransferCastTrueFalseNewPathNilGetConstantGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeInvokeDynamicDropDup" -var _Opcode_index = [...]uint16{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 90, 101, 108, 118, 132, 149, 154, 160, 167, 175, 179, 183, 188, 191, 195, 198, 209, 217, 225, 234, 243, 251, 259, 265, 278, 282, 285} +var _Opcode_index = [...]uint16{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 89, 99, 113, 130, 135, 143, 149, 156, 164, 168, 172, 177, 180, 184, 187, 198, 206, 214, 223, 232, 240, 248, 254, 267, 271, 274} func (i Opcode) String() string { if i >= Opcode(len(_Opcode_index)-1) { diff --git a/runtime/bbq/vm/ft_test.go b/runtime/bbq/vm/ft_test.go index 47905dd72d..853f4fd6bc 100644 --- a/runtime/bbq/vm/ft_test.go +++ b/runtime/bbq/vm/ft_test.go @@ -90,7 +90,8 @@ func TestFTTransfer(t *testing.T) { flowTokenVM := NewVM( flowTokenProgram, &Config{ - Storage: storage, + Storage: storage, + AccountHandler: &testAccountHandler{}, }, ) @@ -157,6 +158,8 @@ func TestFTTransfer(t *testing.T) { return nil } }, + + AccountHandler: &testAccountHandler{}, } for _, address := range []common.Address{ @@ -217,8 +220,8 @@ func TestFTTransfer(t *testing.T) { total := int64(1000000) mintTxArgs := []Value{ - AddressValue(senderAddress), IntValue{total}, + AddressValue(senderAddress), } mintTxAuthorizer := NewAuthAccountReferenceValue(contractsAddress) @@ -647,26 +650,28 @@ import FlowToken from 0x1 transaction { - prepare(signer: &Account) { - - if signer.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) == nil { - // Create a new flowToken Vault and put it in storage - signer.storage.save(<-FlowToken.createEmptyVault(), to: /storage/flowTokenVault) - - // Create a public capability to the Vault that only exposes - // the deposit function through the Receiver interface - signer.link<&FlowToken.Vault>( - /public/flowTokenReceiver, - target: /storage/flowTokenVault - ) - - // Create a public capability to the Vault that only exposes - // the balance field through the Balance interface - signer.link<&FlowToken.Vault>( - /public/flowTokenBalance, - target: /storage/flowTokenVault - ) + prepare(signer: auth(BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { + + if signer.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) != nil { + return } + + var storagePath = /storage/flowTokenVault + + // Create a new flowToken Vault and put it in storage + signer.storage.save(<-FlowToken.createEmptyVault(), to: storagePath) + + // Create a public capability to the Vault that exposes the Vault interfaces + let vaultCap = signer.capabilities.storage.issue<&FlowToken.Vault>( + storagePath + ) + signer.capabilities.publish(vaultCap, at: /public/flowTokenVault) + + // Create a public Capability to the Vault's Receiver functionality + let receiverCap = signer.capabilities.storage.issue<&FlowToken.Vault>( + storagePath + ) + signer.capabilities.publish(receiverCap, at: /public/flowTokenReceiver) } } ` @@ -679,14 +684,13 @@ transaction(recipient: Address, amount: Int) { let tokenAdmin: &FlowToken.Administrator let tokenReceiver: &{FungibleToken.Receiver} - prepare(signer: &Account) { - self.tokenAdmin = signer - .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + prepare(signer: auth(BorrowValue) &Account) { + self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") self.tokenReceiver = getAccount(recipient) - .getCapability(/public/flowTokenReceiver) - .borrow<&{FungibleToken.Receiver}>() + .capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + .borrow() ?? panic("Unable to borrow receiver reference") } @@ -710,7 +714,7 @@ transaction(amount: Int, to: Address) { // The Vault resource that holds the tokens that are being transferred let sentVault: @{FungibleToken.Vault} - prepare(signer: &Account) { + prepare(signer: auth(BorrowValue) &Account) { // Get a reference to the signer's stored vault let vaultRef = signer.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) @@ -724,8 +728,8 @@ transaction(amount: Int, to: Address) { // Get a reference to the recipient's Receiver let receiverRef = getAccount(to) - .getCapability(/public/flowTokenReceiver) - .borrow<&{FungibleToken.Receiver}>() + .capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + .borrow() ?? panic("Could not borrow receiver reference to the recipient's Vault") // Deposit the withdrawn tokens in the recipient's receiver diff --git a/runtime/bbq/vm/storage.go b/runtime/bbq/vm/storage.go index a614b15518..49e002ba7e 100644 --- a/runtime/bbq/vm/storage.go +++ b/runtime/bbq/vm/storage.go @@ -60,9 +60,8 @@ func WriteStored( key interpreter.StorageMapKey, value Value, ) (existed bool) { - storage := config.Storage - accountStorage := storage.GetStorageMap(storageAddress, domain, true) - interValue := VMValueToInterpreterValue(storage, value) + accountStorage := config.Storage.GetStorageMap(storageAddress, domain, true) + interValue := VMValueToInterpreterValue(value) return accountStorage.WriteValue( config.interpreter(), diff --git a/runtime/bbq/vm/test_utils.go b/runtime/bbq/vm/test_utils.go new file mode 100644 index 0000000000..81fb3362cc --- /dev/null +++ b/runtime/bbq/vm/test_utils.go @@ -0,0 +1,330 @@ +package vm + +import ( + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" +) + +type testAccountHandler struct { + accountIDs map[common.Address]uint64 + generateAccountID func(address common.Address) (uint64, error) + getAccountBalance func(address common.Address) (uint64, error) + getAccountAvailableBalance func(address common.Address) (uint64, error) + commitStorageTemporarily func(inter *interpreter.Interpreter) error + getStorageUsed func(address common.Address) (uint64, error) + getStorageCapacity func(address common.Address) (uint64, error) + validatePublicKey func(key *stdlib.PublicKey) error + verifySignature func( + signature []byte, + tag string, + signedData []byte, + publicKey []byte, + signatureAlgorithm sema.SignatureAlgorithm, + hashAlgorithm sema.HashAlgorithm, + ) ( + bool, + error, + ) + blsVerifyPOP func(publicKey *stdlib.PublicKey, signature []byte) (bool, error) + hash func(data []byte, tag string, algorithm sema.HashAlgorithm) ([]byte, error) + getAccountKey func(address common.Address, index int) (*stdlib.AccountKey, error) + accountKeysCount func(address common.Address) (uint64, error) + emitEvent func( + inter *interpreter.Interpreter, + eventType *sema.CompositeType, + values []interpreter.Value, + locationRange interpreter.LocationRange, + ) + addAccountKey func( + address common.Address, + key *stdlib.PublicKey, + algo sema.HashAlgorithm, + weight int, + ) ( + *stdlib.AccountKey, + error, + ) + revokeAccountKey func(address common.Address, index int) (*stdlib.AccountKey, error) + getAccountContractCode func(location common.AddressLocation) ([]byte, error) + parseAndCheckProgram func( + code []byte, + location common.Location, + getAndSetProgram bool, + ) ( + *interpreter.Program, + error, + ) + updateAccountContractCode func(location common.AddressLocation, code []byte) error + recordContractUpdate func(location common.AddressLocation, value *interpreter.CompositeValue) + contractUpdateRecorded func(location common.AddressLocation) bool + interpretContract func( + location common.AddressLocation, + program *interpreter.Program, + name string, + invocation stdlib.DeployedContractConstructorInvocation, + ) ( + *interpreter.CompositeValue, + error, + ) + temporarilyRecordCode func(location common.AddressLocation, code []byte) + removeAccountContractCode func(location common.AddressLocation) error + recordContractRemoval func(location common.AddressLocation) + getAccountContractNames func(address common.Address) ([]string, error) +} + +var _ stdlib.AccountHandler = &testAccountHandler{} + +func (t *testAccountHandler) GenerateAccountID(address common.Address) (uint64, error) { + if t.generateAccountID == nil { + if t.accountIDs == nil { + t.accountIDs = map[common.Address]uint64{} + } + t.accountIDs[address]++ + return t.accountIDs[address], nil + } + return t.generateAccountID(address) +} + +func (t *testAccountHandler) GetAccountBalance(address common.Address) (uint64, error) { + if t.getAccountBalance == nil { + panic(errors.NewUnexpectedError("unexpected call to GetAccountBalance")) + } + return t.getAccountBalance(address) +} + +func (t *testAccountHandler) GetAccountAvailableBalance(address common.Address) (uint64, error) { + if t.getAccountAvailableBalance == nil { + panic(errors.NewUnexpectedError("unexpected call to GetAccountAvailableBalance")) + } + return t.getAccountAvailableBalance(address) +} + +func (t *testAccountHandler) CommitStorageTemporarily(inter *interpreter.Interpreter) error { + if t.commitStorageTemporarily == nil { + panic(errors.NewUnexpectedError("unexpected call to CommitStorageTemporarily")) + } + return t.commitStorageTemporarily(inter) +} + +func (t *testAccountHandler) GetStorageUsed(address common.Address) (uint64, error) { + if t.getStorageUsed == nil { + panic(errors.NewUnexpectedError("unexpected call to GetStorageUsed")) + } + return t.getStorageUsed(address) +} + +func (t *testAccountHandler) GetStorageCapacity(address common.Address) (uint64, error) { + if t.getStorageCapacity == nil { + panic(errors.NewUnexpectedError("unexpected call to GetStorageCapacity")) + } + return t.getStorageCapacity(address) +} + +func (t *testAccountHandler) ValidatePublicKey(key *stdlib.PublicKey) error { + if t.validatePublicKey == nil { + panic(errors.NewUnexpectedError("unexpected call to ValidatePublicKey")) + } + return t.validatePublicKey(key) +} + +func (t *testAccountHandler) VerifySignature( + signature []byte, + tag string, + signedData []byte, + publicKey []byte, + signatureAlgorithm sema.SignatureAlgorithm, + hashAlgorithm sema.HashAlgorithm, +) ( + bool, + error, +) { + if t.verifySignature == nil { + panic(errors.NewUnexpectedError("unexpected call to VerifySignature")) + } + return t.verifySignature( + signature, + tag, + signedData, + publicKey, + signatureAlgorithm, + hashAlgorithm, + ) +} + +func (t *testAccountHandler) BLSVerifyPOP(publicKey *stdlib.PublicKey, signature []byte) (bool, error) { + if t.blsVerifyPOP == nil { + panic(errors.NewUnexpectedError("unexpected call to BLSVerifyPOP")) + } + return t.blsVerifyPOP(publicKey, signature) +} + +func (t *testAccountHandler) Hash(data []byte, tag string, algorithm sema.HashAlgorithm) ([]byte, error) { + if t.hash == nil { + panic(errors.NewUnexpectedError("unexpected call to Hash")) + } + return t.hash(data, tag, algorithm) +} + +func (t *testAccountHandler) GetAccountKey(address common.Address, index int) (*stdlib.AccountKey, error) { + if t.getAccountKey == nil { + panic(errors.NewUnexpectedError("unexpected call to GetAccountKey")) + } + return t.getAccountKey(address, index) +} + +func (t *testAccountHandler) AccountKeysCount(address common.Address) (uint64, error) { + if t.accountKeysCount == nil { + panic(errors.NewUnexpectedError("unexpected call to AccountKeysCount")) + } + return t.accountKeysCount(address) +} + +func (t *testAccountHandler) EmitEvent( + inter *interpreter.Interpreter, + eventType *sema.CompositeType, + values []interpreter.Value, + locationRange interpreter.LocationRange, +) { + if t.emitEvent == nil { + panic(errors.NewUnexpectedError("unexpected call to EmitEvent")) + } + t.emitEvent( + inter, + eventType, + values, + locationRange, + ) +} + +func (t *testAccountHandler) AddAccountKey( + address common.Address, + key *stdlib.PublicKey, + algo sema.HashAlgorithm, + weight int, +) ( + *stdlib.AccountKey, + error, +) { + if t.addAccountKey == nil { + panic(errors.NewUnexpectedError("unexpected call to AddAccountKey")) + } + return t.addAccountKey( + address, + key, + algo, + weight, + ) +} + +func (t *testAccountHandler) RevokeAccountKey(address common.Address, index int) (*stdlib.AccountKey, error) { + if t.revokeAccountKey == nil { + panic(errors.NewUnexpectedError("unexpected call to RevokeAccountKey")) + } + return t.revokeAccountKey(address, index) +} + +func (t *testAccountHandler) GetAccountContractCode(location common.AddressLocation) ([]byte, error) { + if t.getAccountContractCode == nil { + panic(errors.NewUnexpectedError("unexpected call to GetAccountContractCode")) + } + return t.getAccountContractCode(location) +} + +func (t *testAccountHandler) ParseAndCheckProgram( + code []byte, + location common.Location, + getAndSetProgram bool, +) ( + *interpreter.Program, + error, +) { + if t.parseAndCheckProgram == nil { + panic(errors.NewUnexpectedError("unexpected call to ParseAndCheckProgram")) + } + return t.parseAndCheckProgram(code, location, getAndSetProgram) +} + +func (t *testAccountHandler) UpdateAccountContractCode(location common.AddressLocation, code []byte) error { + if t.updateAccountContractCode == nil { + panic(errors.NewUnexpectedError("unexpected call to UpdateAccountContractCode")) + } + return t.updateAccountContractCode(location, code) +} + +func (t *testAccountHandler) RecordContractUpdate(location common.AddressLocation, value *interpreter.CompositeValue) { + if t.recordContractUpdate == nil { + panic(errors.NewUnexpectedError("unexpected call to RecordContractUpdate")) + } + t.recordContractUpdate(location, value) +} + +func (t *testAccountHandler) ContractUpdateRecorded(location common.AddressLocation) bool { + if t.contractUpdateRecorded == nil { + panic(errors.NewUnexpectedError("unexpected call to ContractUpdateRecorded")) + } + return t.contractUpdateRecorded(location) +} + +func (t *testAccountHandler) InterpretContract( + location common.AddressLocation, + program *interpreter.Program, + name string, + invocation stdlib.DeployedContractConstructorInvocation, +) ( + *interpreter.CompositeValue, + error, +) { + if t.interpretContract == nil { + panic(errors.NewUnexpectedError("unexpected call to InterpretContract")) + } + return t.interpretContract( + location, + program, + name, + invocation, + ) +} + +func (t *testAccountHandler) TemporarilyRecordCode(location common.AddressLocation, code []byte) { + if t.temporarilyRecordCode == nil { + panic(errors.NewUnexpectedError("unexpected call to TemporarilyRecordCode")) + } + t.temporarilyRecordCode(location, code) +} + +func (t *testAccountHandler) RemoveAccountContractCode(location common.AddressLocation) error { + if t.removeAccountContractCode == nil { + panic(errors.NewUnexpectedError("unexpected call to RemoveAccountContractCode")) + } + return t.removeAccountContractCode(location) +} + +func (t *testAccountHandler) RecordContractRemoval(location common.AddressLocation) { + if t.recordContractRemoval == nil { + panic(errors.NewUnexpectedError("unexpected call to RecordContractRemoval")) + } + t.recordContractRemoval(location) +} + +func (t *testAccountHandler) GetAccountContractNames(address common.Address) ([]string, error) { + if t.getAccountContractNames == nil { + panic(errors.NewUnexpectedError("unexpected call to GetAccountContractNames")) + } + return t.getAccountContractNames(address) +} + +func (t *testAccountHandler) StartContractAddition(common.AddressLocation) { + // NO-OP +} + +func (t *testAccountHandler) EndContractAddition(common.AddressLocation) { + // NO-OP +} + +func (t *testAccountHandler) IsContractBeingAdded(common.AddressLocation) bool { + // NO-OP + return false +} diff --git a/runtime/bbq/vm/value_account.go b/runtime/bbq/vm/value_account.go index 27018f3d83..763b8e209c 100644 --- a/runtime/bbq/vm/value_account.go +++ b/runtime/bbq/vm/value_account.go @@ -144,8 +144,6 @@ func init() { //}) accountStorageTypeName := sema.Account_StorageType.QualifiedIdentifier() - accountCapabilitiesTypeName := sema.Account_CapabilitiesType.QualifiedIdentifier() - accountStorageCapabilitiesTypeName := sema.Account_StorageCapabilitiesType.QualifiedIdentifier() // Account.Storage.save RegisterTypeBoundFunction( @@ -239,163 +237,6 @@ func init() { return NewSomeValueNonCopying(reference) }, }) - - // Account.Capabilities.get - RegisterTypeBoundFunction( - accountCapabilitiesTypeName, - sema.Account_CapabilitiesTypeGetFunctionName, - NativeFunctionValue{ - ParameterCount: len(sema.Account_CapabilitiesTypeGetFunctionType.Parameters), - Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { - // Get address field from the receiver (Account.Capabilities) - address := getAddressMetaInfoFromValue(args[0]) - - // Path argument - path, ok := args[1].(PathValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - //pathStaticType := path.StaticType(config.MemoryGauge) - // - //if !IsSubType(pathStaticType, pathType) { - // panic(fmt.Errorf("type mismatch")) - //} - - // NOTE: the type parameter is optional, for backwards compatibility - - var borrowType *interpreter.ReferenceStaticType - if len(typeArguments) > 0 { - ty := typeArguments[0] - // we handle the nil case for this below - borrowType, _ = ty.(*interpreter.ReferenceStaticType) - } - - return getCapability( - config, - address, - path, - borrowType, - true, - ) - }, - }) - - // Account.Capabilities.publish - RegisterTypeBoundFunction( - accountCapabilitiesTypeName, - sema.Account_CapabilitiesTypePublishFunctionName, - NativeFunctionValue{ - ParameterCount: len(sema.Account_CapabilitiesTypeGetFunctionType.Parameters), - Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { - // Get address field from the receiver (Account.Capabilities) - accountAddress := getAddressMetaInfoFromValue(args[0]) - - // Get capability argument - - var capabilityValue *CapabilityValue - switch firstValue := args[1].(type) { - case *CapabilityValue: - capabilityValue = firstValue - default: - panic(errors.NewUnreachableError()) - } - - capabilityAddressValue := common.Address(capabilityValue.Address) - if capabilityAddressValue != accountAddress { - panic(interpreter.CapabilityAddressPublishingError{ - CapabilityAddress: interpreter.AddressValue(capabilityAddressValue), - AccountAddress: interpreter.AddressValue(accountAddress), - }) - } - - // Get path argument - - path, ok := args[2].(PathValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - if !ok || path.Domain != common.PathDomainPublic { - panic(errors.NewUnreachableError()) - } - - domain := path.Domain.Identifier() - identifier := path.Identifier - - // Prevent an overwrite - - storageMapKey := interpreter.StringStorageMapKey(identifier) - if StoredValueExists( - config.Storage, - accountAddress, - domain, - storageMapKey, - ) { - panic(interpreter.OverwriteError{ - Address: interpreter.AddressValue(accountAddress), - Path: VMValueToInterpreterValue(config.Storage, path).(interpreter.PathValue), - }) - } - - capabilityValue, ok = capabilityValue.Transfer( - config, - atree.Address(accountAddress), - true, - nil, - ).(*CapabilityValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - // Write new value - - WriteStored( - config, - accountAddress, - domain, - storageMapKey, - capabilityValue, - ) - - return Void - }, - }) - - // Account.StorageCapabilities.issue - RegisterTypeBoundFunction( - accountStorageCapabilitiesTypeName, - sema.Account_StorageCapabilitiesTypeIssueFunctionName, - NativeFunctionValue{ - ParameterCount: len(sema.Account_CapabilitiesTypeGetFunctionType.Parameters), - Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { - // Get address field from the receiver (Account.StorageCapabilities) - accountAddress := getAddressMetaInfoFromValue(args[0]) - - // Path argument - targetPathValue, ok := args[1].(PathValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - if !ok || targetPathValue.Domain != common.PathDomainStorage { - panic(errors.NewUnreachableError()) - } - - // Get borrow type type-argument - ty := typeArguments[0] - - // Issue capability controller and return capability - - return checkAndIssueStorageCapabilityControllerWithType( - config, - config.AccountHandler, - accountAddress, - targetPathValue, - ty, - ) - }, - }) } func getAddressMetaInfoFromValue(value Value) common.Address { @@ -447,12 +288,10 @@ func getCapability( return failValue } - var readCapabilityValue *CapabilityValue - + var readCapabilityValue CapabilityValue switch readValue := readValue.(type) { - case *CapabilityValue: + case CapabilityValue: readCapabilityValue = readValue - default: panic(errors.NewUnreachableError()) } @@ -670,12 +509,11 @@ func recordStorageCapabilityController( config.MutationDuringCapabilityControllerIteration = true } - storage := config.Storage identifier := targetPathValue.Identifier storageMapKey := interpreter.StringStorageMapKey(identifier) - accountStorage := storage.GetStorageMap(address, stdlib.PathCapabilityStorageDomain, false) + accountStorage := config.Storage.GetStorageMap(address, stdlib.PathCapabilityStorageDomain, true) referenced := accountStorage.ReadValue(config.MemoryGauge, interpreter.StringStorageMapKey(identifier)) readValue := InterpreterValueToVMValue(referenced) @@ -690,7 +528,7 @@ func recordStorageCapabilityController( setKey, setValue, ) - capabilityIDSetInterValue := VMValueToInterpreterValue(storage, capabilityIDSet) + capabilityIDSetInterValue := VMValueToInterpreterValue(capabilityIDSet) accountStorage.SetValue(config.interpreter(), storageMapKey, capabilityIDSetInterValue) } else { capabilityIDSet := readValue.(*DictionaryValue) diff --git a/runtime/bbq/vm/value_account_capabilities.go b/runtime/bbq/vm/value_account_capabilities.go index 92ae03ec35..369f007b3b 100644 --- a/runtime/bbq/vm/value_account_capabilities.go +++ b/runtime/bbq/vm/value_account_capabilities.go @@ -19,7 +19,9 @@ package vm import ( + "github.com/onflow/atree" "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" ) @@ -56,5 +58,163 @@ func NewAccountStorageCapabilitiesValue(accountAddress common.Address) *SimpleCo // members func init() { - // TODO + accountCapabilitiesTypeName := sema.Account_CapabilitiesType.QualifiedIdentifier() + accountStorageCapabilitiesTypeName := sema.Account_StorageCapabilitiesType.QualifiedIdentifier() + + // Account.Capabilities.get + RegisterTypeBoundFunction( + accountCapabilitiesTypeName, + sema.Account_CapabilitiesTypeGetFunctionName, + NativeFunctionValue{ + ParameterCount: len(sema.Account_CapabilitiesTypeGetFunctionType.Parameters), + Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { + // Get address field from the receiver (Account.Capabilities) + address := getAddressMetaInfoFromValue(args[0]) + + // Path argument + path, ok := args[1].(PathValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + //pathStaticType := path.StaticType(config.MemoryGauge) + // + //if !IsSubType(pathStaticType, pathType) { + // panic(fmt.Errorf("type mismatch")) + //} + + // NOTE: the type parameter is optional, for backwards compatibility + + var borrowType *interpreter.ReferenceStaticType + if len(typeArguments) > 0 { + ty := typeArguments[0] + // we handle the nil case for this below + borrowType, _ = ty.(*interpreter.ReferenceStaticType) + } + + return getCapability( + config, + address, + path, + borrowType, + false, + ) + }, + }) + + // Account.Capabilities.publish + RegisterTypeBoundFunction( + accountCapabilitiesTypeName, + sema.Account_CapabilitiesTypePublishFunctionName, + NativeFunctionValue{ + ParameterCount: len(sema.Account_CapabilitiesTypePublishFunctionType.Parameters), + Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { + // Get address field from the receiver (Account.Capabilities) + accountAddress := getAddressMetaInfoFromValue(args[0]) + + // Get capability argument + + var capabilityValue CapabilityValue + switch firstValue := args[1].(type) { + case CapabilityValue: + capabilityValue = firstValue + default: + panic(errors.NewUnreachableError()) + } + + capabilityAddressValue := common.Address(capabilityValue.Address) + if capabilityAddressValue != accountAddress { + panic(interpreter.CapabilityAddressPublishingError{ + CapabilityAddress: interpreter.AddressValue(capabilityAddressValue), + AccountAddress: interpreter.AddressValue(accountAddress), + }) + } + + // Get path argument + + path, ok := args[2].(PathValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + if !ok || path.Domain != common.PathDomainPublic { + panic(errors.NewUnreachableError()) + } + + domain := path.Domain.Identifier() + identifier := path.Identifier + + // Prevent an overwrite + + storageMapKey := interpreter.StringStorageMapKey(identifier) + if StoredValueExists( + config.Storage, + accountAddress, + domain, + storageMapKey, + ) { + panic(interpreter.OverwriteError{ + Address: interpreter.AddressValue(accountAddress), + Path: VMValueToInterpreterValue(path).(interpreter.PathValue), + }) + } + + capabilityValue, ok = capabilityValue.Transfer( + config, + atree.Address(accountAddress), + true, + nil, + ).(CapabilityValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + // Write new value + + WriteStored( + config, + accountAddress, + domain, + storageMapKey, + capabilityValue, + ) + + return Void + }, + }) + + // Account.StorageCapabilities.issue + RegisterTypeBoundFunction( + accountStorageCapabilitiesTypeName, + sema.Account_StorageCapabilitiesTypeIssueFunctionName, + NativeFunctionValue{ + ParameterCount: len(sema.Account_StorageCapabilitiesTypeIssueFunctionType.Parameters), + Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { + // Get address field from the receiver (Account.StorageCapabilities) + accountAddress := getAddressMetaInfoFromValue(args[0]) + + // Path argument + targetPathValue, ok := args[1].(PathValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + if !ok || targetPathValue.Domain != common.PathDomainStorage { + panic(errors.NewUnreachableError()) + } + + // Get borrow type type-argument + ty := typeArguments[0] + + // Issue capability controller and return capability + + return checkAndIssueStorageCapabilityControllerWithType( + config, + config.AccountHandler, + accountAddress, + targetPathValue, + ty, + ) + }, + }) } diff --git a/runtime/bbq/vm/value_capability.go b/runtime/bbq/vm/value_capability.go index a61275d03e..1c15785c8b 100644 --- a/runtime/bbq/vm/value_capability.go +++ b/runtime/bbq/vm/value_capability.go @@ -49,8 +49,8 @@ func NewCapabilityValue(address AddressValue, id IntValue, borrowType StaticType func NewInvalidCapabilityValue( address common.Address, borrowType StaticType, -) *CapabilityValue { - return &CapabilityValue{ +) CapabilityValue { + return CapabilityValue{ ID: InvalidCapabilityID, Address: AddressValue(address), BorrowType: borrowType, @@ -91,7 +91,7 @@ func init() { NativeFunctionValue{ ParameterCount: 0, Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { - capabilityValue := args[0].(CapabilityValue) + capabilityValue := getReceiver[CapabilityValue](args[0]) capabilityID := capabilityValue.ID if capabilityID == InvalidCapabilityID { diff --git a/runtime/bbq/vm/value_capability_controller.go b/runtime/bbq/vm/value_capability_controller.go index 1aff466401..63b678cb61 100644 --- a/runtime/bbq/vm/value_capability_controller.go +++ b/runtime/bbq/vm/value_capability_controller.go @@ -49,13 +49,6 @@ type StorageCapabilityControllerValue struct { // deleted indicates if the controller got deleted. Not stored deleted bool - // Lazily initialized function values. - // Host functions based on injected functions (see below). - deleteFunction *FunctionValue - targetFunction *FunctionValue - retargetFunction *FunctionValue - setTagFunction *FunctionValue - // Injected functions. // Tags are not stored directly inside the controller // to avoid unnecessary storage reads diff --git a/runtime/bbq/vm/value_composite.go b/runtime/bbq/vm/value_composite.go index 8c84901fbf..ddbc5c12bb 100644 --- a/runtime/bbq/vm/value_composite.go +++ b/runtime/bbq/vm/value_composite.go @@ -134,7 +134,7 @@ func (v *CompositeValue) SetMember(conf *Config, name string, value Value) { // nil, //) - interpreterValue := VMValueToInterpreterValue(conf.Storage, value) + interpreterValue := VMValueToInterpreterValue(value) existingStorable, err := v.dictionary.Set( interpreter.StringAtreeValueComparator, @@ -228,7 +228,7 @@ func (v *CompositeValue) Transfer( if needsStoreTo && v.Kind == common.CompositeKindContract { panic(interpreter.NonTransferableValueError{ - Value: VMValueToInterpreterValue(conf.Storage, v), + Value: VMValueToInterpreterValue(v), }) } diff --git a/runtime/bbq/vm/value_conversions.go b/runtime/bbq/vm/value_conversions.go index 824175f24a..c165b27633 100644 --- a/runtime/bbq/vm/value_conversions.go +++ b/runtime/bbq/vm/value_conversions.go @@ -29,6 +29,10 @@ import ( func InterpreterValueToVMValue(value interpreter.Value) Value { switch value := value.(type) { + case nil: + return nil + case interpreter.NilValue: + return Nil case interpreter.IntValue: return IntValue{value.BigInt.Int64()} case *interpreter.StringValue: @@ -66,13 +70,35 @@ func InterpreterValueToVMValue(value interpreter.Value) Value { value.TypeID, fields, ) + case *interpreter.DictionaryValue: + return newDictionaryValueFromAtreeMap( + value.Type, + value.ElementSize(), + value.AtreeMap(), + ) + case *interpreter.IDCapabilityValue: + return NewCapabilityValue( + AddressValue(value.Address), + NewIntValue(int64(value.ID.ToInt(interpreter.EmptyLocationRange))), + value.BorrowType, + ) + case *interpreter.StorageCapabilityControllerValue: + return NewStorageCapabilityControllerValue( + value.BorrowType, + NewIntValue(int64(value.CapabilityID.ToInt(interpreter.EmptyLocationRange))), + InterpreterValueToVMValue(value.TargetPath).(PathValue), + ) default: panic(errors.NewUnreachableError()) } } -func VMValueToInterpreterValue(storage interpreter.Storage, value Value) interpreter.Value { +func VMValueToInterpreterValue(value Value) interpreter.Value { switch value := value.(type) { + case nil: + return nil + case NilValue: + return interpreter.Nil case IntValue: return interpreter.NewIntValueFromInt64(nil, value.SmallInt) case StringValue: @@ -87,11 +113,20 @@ func VMValueToInterpreterValue(storage interpreter.Storage, value Value) interpr }, value.dictionary, ) - case *CapabilityValue: + case *DictionaryValue: + staticType := value.Type + elementSize := interpreter.DictionaryElementSize(staticType) + return interpreter.NewDictionaryValueFromAtreeMap( + nil, + staticType, + elementSize, + value.dictionary, + ) + case CapabilityValue: return interpreter.NewCapabilityValue( nil, interpreter.NewUnmeteredUInt64Value(uint64(value.ID.SmallInt)), // TODO: properly convert - VMValueToInterpreterValue(storage, value.Address).(interpreter.AddressValue), + VMValueToInterpreterValue(value.Address).(interpreter.AddressValue), value.BorrowType, ) //case LinkValue: @@ -111,7 +146,7 @@ func VMValueToInterpreterValue(storage interpreter.Storage, value Value) interpr var fieldNames []string for name, field := range value.fields { - fields[name] = VMValueToInterpreterValue(storage, field) + fields[name] = VMValueToInterpreterValue(field) fieldNames = append(fieldNames, name) } @@ -133,6 +168,13 @@ func VMValueToInterpreterValue(storage interpreter.Storage, value Value) interpr // value.TargetPath, // value.BorrowedType, // ) + case *StorageCapabilityControllerValue: + return interpreter.NewStorageCapabilityControllerValue( + nil, + value.BorrowType, + interpreter.NewUnmeteredUInt64Value(uint64(value.CapabilityID.SmallInt)), + VMValueToInterpreterValue(value.TargetPath).(interpreter.PathValue), + ) default: panic(errors.NewUnreachableError()) } diff --git a/runtime/bbq/vm/value_dictionary.go b/runtime/bbq/vm/value_dictionary.go index 9d331dd8e5..a6e3faf38c 100644 --- a/runtime/bbq/vm/value_dictionary.go +++ b/runtime/bbq/vm/value_dictionary.go @@ -35,7 +35,7 @@ var _ Value = &DictionaryValue{} var _ MemberAccessibleValue = &DictionaryValue{} func NewDictionaryValue( - conf *Config, + config *Config, dictionaryType *interpreter.DictionaryStaticType, keysAndValues ...Value, ) *DictionaryValue { @@ -49,7 +49,7 @@ func NewDictionaryValue( constructor := func() *atree.OrderedMap { dictionary, err := atree.NewMap( - conf.Storage, + config.Storage, atree.Address(address), atree.NewDefaultDigesterBuilder(), dictionaryType, @@ -66,7 +66,7 @@ func NewDictionaryValue( for i := 0; i < keysAndValuesCount; i += 2 { key := keysAndValues[i] value := keysAndValues[i+1] - existingValue := v.Insert(conf, key, value) + existingValue := v.Insert(config, key, value) // If the dictionary already contained a value for the key, // and the dictionary is resource-typed, // then we need to prevent a resource loss @@ -173,8 +173,8 @@ func (v *DictionaryValue) InsertWithoutTransfer(conf *Config, key, value Value) valueComparator := newValueComparator(conf) hashInputProvider := newHashInputProvider(conf) - keyInterpreterValue := VMValueToInterpreterValue(conf.Storage, key) - valueInterpreterValue := VMValueToInterpreterValue(conf.Storage, value) + keyInterpreterValue := VMValueToInterpreterValue(key) + valueInterpreterValue := VMValueToInterpreterValue(value) // atree only calls Storable() on keyValue if needed, // i.e., if the key is a new key diff --git a/runtime/bbq/vm/value_int.go b/runtime/bbq/vm/value_int.go index 100d2f558d..5278330c83 100644 --- a/runtime/bbq/vm/value_int.go +++ b/runtime/bbq/vm/value_int.go @@ -31,6 +31,12 @@ type IntValue struct { SmallInt int64 } +func NewIntValue(smallInt int64) IntValue { + return IntValue{ + SmallInt: smallInt, + } +} + func (v IntValue) String() string { return strconv.FormatInt(v.SmallInt, 10) } diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index a0aaf0accf..805c4471b7 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -494,6 +494,11 @@ func opEqual(vm *VM) { vm.replaceTop(BoolValue(left == right)) } +func opNotEqual(vm *VM) { + left, right := vm.peekPop() + vm.replaceTop(BoolValue(left != right)) +} + func opUnwrap(vm *VM) { value := vm.peek() if someValue, ok := value.(*SomeValue); ok { @@ -573,6 +578,8 @@ func (vm *VM) run() { opNil(vm) case opcode.Equal: opEqual(vm) + case opcode.NotEqual: + opNotEqual(vm) case opcode.Unwrap: opUnwrap(vm) default: @@ -676,3 +683,20 @@ func decodeLocation(locationBytes []byte) common.Location { } return location } + +func getReceiver[T any](receiver Value) T { + switch receiver := receiver.(type) { + case *SomeValue: + return getReceiver[T](receiver.value) + case *EphemeralReferenceValue: + return getReceiver[T](receiver.Value) + case *StorageReferenceValue: + referencedValue, err := receiver.dereference(nil) + if err != nil { + panic(err) + } + return getReceiver[T](*referencedValue) + default: + return receiver.(T) + } +} diff --git a/runtime/interpreter/storage.go b/runtime/interpreter/storage.go index 5af2ce0eb3..84c6242242 100644 --- a/runtime/interpreter/storage.go +++ b/runtime/interpreter/storage.go @@ -75,7 +75,7 @@ func ConvertStoredValue(gauge common.MemoryGauge, value atree.Value) (Value, err typeInfo := value.Type() switch staticType := typeInfo.(type) { case *DictionaryStaticType: - return newDictionaryValueFromAtreeMap( + return NewDictionaryValueFromAtreeMap( gauge, staticType, DictionaryElementSize(staticType), diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index b585bf2551..91527a7266 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -18753,7 +18753,7 @@ func newDictionaryValueFromConstructor( common.UseMemory(gauge, dataSlabs) common.UseMemory(gauge, metaDataSlabs) - return newDictionaryValueFromAtreeMap( + return NewDictionaryValueFromAtreeMap( gauge, staticType, elementSize, @@ -18761,7 +18761,7 @@ func newDictionaryValueFromConstructor( ) } -func newDictionaryValueFromAtreeMap( +func NewDictionaryValueFromAtreeMap( gauge common.MemoryGauge, staticType *DictionaryStaticType, elementSize uint, @@ -19834,7 +19834,7 @@ func (v *DictionaryValue) Transfer( v.dictionary = nil } - res := newDictionaryValueFromAtreeMap( + res := NewDictionaryValueFromAtreeMap( interpreter, v.Type, v.elementSize, @@ -19890,7 +19890,7 @@ func (v *DictionaryValue) Clone(interpreter *Interpreter) Value { panic(errors.NewExternalError(err)) } - dictionary := newDictionaryValueFromAtreeMap( + dictionary := NewDictionaryValueFromAtreeMap( interpreter, v.Type, v.elementSize, @@ -19983,6 +19983,14 @@ func (v *DictionaryValue) SetType(staticType *DictionaryStaticType) { } } +func (v *DictionaryValue) AtreeMap() *atree.OrderedMap { + return v.dictionary +} + +func (v *DictionaryValue) ElementSize() uint { + return v.elementSize +} + // OptionalValue type OptionalValue interface { diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 1a435d5d1e..0373db57c7 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -10945,7 +10945,7 @@ func TestRuntimeAccountStorageBorrowEphemeralReferenceValue(t *testing.T) { func BenchmarkContractFunctionInvocation(b *testing.B) { - runtime := newTestInterpreterRuntime() + runtime := NewTestInterpreterRuntime() addressValue := cadence.BytesToAddress([]byte{0x1}) @@ -10977,32 +10977,32 @@ func BenchmarkContractFunctionInvocation(b *testing.B) { var accountCode []byte var events []cadence.Event - runtimeInterface := &testRuntimeInterface{ - getCode: func(_ Location) (bytes []byte, err error) { + runtimeInterface := &TestRuntimeInterface{ + OnGetCode: func(_ Location) (bytes []byte, err error) { return accountCode, nil }, - storage: newTestLedger(nil, nil), - getSigningAccounts: func() ([]Address, error) { + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]Address, error) { return []Address{Address(addressValue)}, nil }, - resolveLocation: singleIdentifierLocationResolver(b), - getAccountContractCode: func(_ Address, _ string) (code []byte, err error) { + OnResolveLocation: NewSingleIdentifierLocationResolver(b), + OnGetAccountContractCode: func(_ common.AddressLocation) (code []byte, err error) { return accountCode, nil }, - updateAccountContractCode: func(_ Address, _ string, code []byte) error { + OnUpdateAccountContractCode: func(_ common.AddressLocation, code []byte) error { accountCode = code return nil }, - emitEvent: func(event cadence.Event) error { + OnEmitEvent: func(event cadence.Event) error { events = append(events, event) return nil }, - decodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { return json.Decode(nil, b) }, } - nextTransactionLocation := newTransactionLocationGenerator() + nextTransactionLocation := NewTransactionLocationGenerator() err := runtime.ExecuteTransaction( Script{ From 50323dbd07dced8413c30048dc358b6f0a864001 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 11 Jul 2024 11:59:04 -0700 Subject: [PATCH 40/89] Refactor --- runtime/bbq/vm/value_account.go | 159 +----------------- runtime/bbq/vm/value_account_capabilities.go | 51 +----- runtime/bbq/vm/value_account_storage.go | 101 ++++++++++- .../vm/value_account_storagecapabilities.go | 81 +++++++++ 4 files changed, 182 insertions(+), 210 deletions(-) create mode 100644 runtime/bbq/vm/value_account_storagecapabilities.go diff --git a/runtime/bbq/vm/value_account.go b/runtime/bbq/vm/value_account.go index 763b8e209c..c6236d3ffa 100644 --- a/runtime/bbq/vm/value_account.go +++ b/runtime/bbq/vm/value_account.go @@ -19,7 +19,6 @@ package vm import ( - "github.com/onflow/atree" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" @@ -80,163 +79,7 @@ func newAccountValue( // members func init() { - // AuthAccount.link - //RegisterTypeBoundFunction(typeName, sema.AccountLinkField, NativeFunctionValue{ - // ParameterCount: len(sema.AuthAccountTypeLinkFunctionType.Parameters), - // Function: func(config *Config, typeArgs []StaticType, args ...Value) Value { - // borrowType, ok := typeArgs[0].(interpreter.ReferenceStaticType) - // if !ok { - // panic(errors.NewUnreachableError()) - // } - // - // authAccount, ok := args[0].(*SimpleCompositeValue) - // if !ok { - // panic(errors.NewUnreachableError()) - // } - // address := authAccount.GetMember(config, sema.AuthAccountAddressField) - // addressValue, ok := address.(AddressValue) - // if !ok { - // panic(errors.NewUnreachableError()) - // } - // - // newCapabilityPath, ok := args[1].(PathValue) - // if !ok { - // panic(errors.NewUnreachableError()) - // } - // - // targetPath, ok := args[2].(PathValue) - // if !ok { - // panic(errors.NewUnreachableError()) - // } - // - // newCapabilityDomain := newCapabilityPath.Domain.Identifier() - // newCapabilityIdentifier := newCapabilityPath.Identifier - // - // //if interpreter.storedValueExists( - // // address, - // // newCapabilityDomain, - // // newCapabilityIdentifier, - // //) { - // // return Nil - // //} - // - // // Write new value - // - // // Note that this will be metered twice if Atree validation is enabled. - // linkValue := NewLinkValue(targetPath, borrowType) - // - // WriteStored( - // config, - // common.Address(addressValue), - // newCapabilityDomain, - // newCapabilityIdentifier, - // linkValue, - // ) - // - // return NewSomeValueNonCopying( - // NewCapabilityValue( - // addressValue, - // newCapabilityPath, - // borrowType, - // ), - // ) - // }, - //}) - - accountStorageTypeName := sema.Account_StorageType.QualifiedIdentifier() - - // Account.Storage.save - RegisterTypeBoundFunction( - accountStorageTypeName, - sema.Account_StorageTypeSaveFunctionName, - NativeFunctionValue{ - ParameterCount: len(sema.Account_StorageTypeSaveFunctionType.Parameters), - Function: func(config *Config, typeArs []StaticType, args ...Value) Value { - address := getAddressMetaInfoFromValue(args[0]) - - value := args[1] - - path, ok := args[2].(PathValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - domain := path.Domain.Identifier() - identifier := path.Identifier - - // Prevent an overwrite - - //if interpreter.storedValueExists( - // address, - // domain, - // identifier, - //) { - // panic("overwrite error") - //} - - value = value.Transfer( - config, - atree.Address(address), - true, - nil, - ) - - // Write new value - - WriteStored( - config, - address, - domain, - interpreter.StringStorageMapKey(identifier), - value, - ) - - return VoidValue{} - }, - }) - - // Account.Storage.borrow - RegisterTypeBoundFunction( - accountStorageTypeName, - sema.Account_StorageTypeBorrowFunctionName, - NativeFunctionValue{ - ParameterCount: len(sema.Account_StorageTypeBorrowFunctionType.Parameters), - Function: func(config *Config, typeArgs []StaticType, args ...Value) Value { - address := getAddressMetaInfoFromValue(args[0]) - - path, ok := args[1].(PathValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - referenceType, ok := typeArgs[0].(*interpreter.ReferenceStaticType) - if !ok { - panic(errors.NewUnreachableError()) - } - - reference := NewStorageReferenceValue( - config.Storage, - referenceType.Authorization, - address, - path, - referenceType, - ) - - // Attempt to dereference, - // which reads the stored value - // and performs a dynamic type check - - referenced, err := reference.dereference(config.MemoryGauge) - if err != nil { - panic(err) - } - if referenced == nil { - return NilValue{} - } - - return NewSomeValueNonCopying(reference) - }, - }) + // TODO } func getAddressMetaInfoFromValue(value Value) common.Address { diff --git a/runtime/bbq/vm/value_account_capabilities.go b/runtime/bbq/vm/value_account_capabilities.go index 369f007b3b..f0cd4d2aa6 100644 --- a/runtime/bbq/vm/value_account_capabilities.go +++ b/runtime/bbq/vm/value_account_capabilities.go @@ -20,6 +20,7 @@ package vm import ( "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" @@ -41,25 +42,10 @@ func NewAccountCapabilitiesValue(accountAddress common.Address) *SimpleComposite } } -func NewAccountStorageCapabilitiesValue(accountAddress common.Address) *SimpleCompositeValue { - return &SimpleCompositeValue{ - typeID: sema.Account_StorageCapabilitiesType.ID(), - staticType: interpreter.PrimitiveStaticTypeAccount_StorageCapabilities, - Kind: common.CompositeKindStructure, - fields: map[string]Value{ - // TODO: add the remaining fields - }, - metaInfo: map[string]any{ - sema.AccountTypeAddressFieldName: accountAddress, - }, - } -} - // members func init() { accountCapabilitiesTypeName := sema.Account_CapabilitiesType.QualifiedIdentifier() - accountStorageCapabilitiesTypeName := sema.Account_StorageCapabilitiesType.QualifiedIdentifier() // Account.Capabilities.get RegisterTypeBoundFunction( @@ -182,39 +168,4 @@ func init() { return Void }, }) - - // Account.StorageCapabilities.issue - RegisterTypeBoundFunction( - accountStorageCapabilitiesTypeName, - sema.Account_StorageCapabilitiesTypeIssueFunctionName, - NativeFunctionValue{ - ParameterCount: len(sema.Account_StorageCapabilitiesTypeIssueFunctionType.Parameters), - Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { - // Get address field from the receiver (Account.StorageCapabilities) - accountAddress := getAddressMetaInfoFromValue(args[0]) - - // Path argument - targetPathValue, ok := args[1].(PathValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - if !ok || targetPathValue.Domain != common.PathDomainStorage { - panic(errors.NewUnreachableError()) - } - - // Get borrow type type-argument - ty := typeArguments[0] - - // Issue capability controller and return capability - - return checkAndIssueStorageCapabilityControllerWithType( - config, - config.AccountHandler, - accountAddress, - targetPathValue, - ty, - ) - }, - }) } diff --git a/runtime/bbq/vm/value_account_storage.go b/runtime/bbq/vm/value_account_storage.go index 1f630697c9..3cb836c066 100644 --- a/runtime/bbq/vm/value_account_storage.go +++ b/runtime/bbq/vm/value_account_storage.go @@ -19,7 +19,10 @@ package vm import ( + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" ) @@ -29,7 +32,7 @@ func NewAccountStorageValue(accountAddress common.Address) *SimpleCompositeValue typeID: sema.Account_StorageType.ID(), staticType: interpreter.PrimitiveStaticTypeAccount_Storage, Kind: common.CompositeKindStructure, - fields: map[string]Value{ + fields: map[string]Value{ // TODO: add the remaining fields }, metaInfo: map[string]any{ @@ -41,5 +44,99 @@ func NewAccountStorageValue(accountAddress common.Address) *SimpleCompositeValue // members func init() { - // TODO + + accountStorageTypeName := sema.Account_StorageType.QualifiedIdentifier() + + // Account.Storage.save + RegisterTypeBoundFunction( + accountStorageTypeName, + sema.Account_StorageTypeSaveFunctionName, + NativeFunctionValue{ + ParameterCount: len(sema.Account_StorageTypeSaveFunctionType.Parameters), + Function: func(config *Config, typeArs []StaticType, args ...Value) Value { + address := getAddressMetaInfoFromValue(args[0]) + + value := args[1] + + path, ok := args[2].(PathValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + domain := path.Domain.Identifier() + identifier := path.Identifier + + // Prevent an overwrite + + //if interpreter.storedValueExists( + // address, + // domain, + // identifier, + //) { + // panic("overwrite error") + //} + + value = value.Transfer( + config, + atree.Address(address), + true, + nil, + ) + + // Write new value + + WriteStored( + config, + address, + domain, + interpreter.StringStorageMapKey(identifier), + value, + ) + + return VoidValue{} + }, + }) + + // Account.Storage.borrow + RegisterTypeBoundFunction( + accountStorageTypeName, + sema.Account_StorageTypeBorrowFunctionName, + NativeFunctionValue{ + ParameterCount: len(sema.Account_StorageTypeBorrowFunctionType.Parameters), + Function: func(config *Config, typeArgs []StaticType, args ...Value) Value { + address := getAddressMetaInfoFromValue(args[0]) + + path, ok := args[1].(PathValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + referenceType, ok := typeArgs[0].(*interpreter.ReferenceStaticType) + if !ok { + panic(errors.NewUnreachableError()) + } + + reference := NewStorageReferenceValue( + config.Storage, + referenceType.Authorization, + address, + path, + referenceType, + ) + + // Attempt to dereference, + // which reads the stored value + // and performs a dynamic type check + + referenced, err := reference.dereference(config.MemoryGauge) + if err != nil { + panic(err) + } + if referenced == nil { + return NilValue{} + } + + return NewSomeValueNonCopying(reference) + }, + }) } diff --git a/runtime/bbq/vm/value_account_storagecapabilities.go b/runtime/bbq/vm/value_account_storagecapabilities.go new file mode 100644 index 0000000000..ffd9fc215d --- /dev/null +++ b/runtime/bbq/vm/value_account_storagecapabilities.go @@ -0,0 +1,81 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" +) + +func NewAccountStorageCapabilitiesValue(accountAddress common.Address) *SimpleCompositeValue { + return &SimpleCompositeValue{ + typeID: sema.Account_StorageCapabilitiesType.ID(), + staticType: interpreter.PrimitiveStaticTypeAccount_StorageCapabilities, + Kind: common.CompositeKindStructure, + fields: map[string]Value{ + // TODO: add the remaining fields + }, + metaInfo: map[string]any{ + sema.AccountTypeAddressFieldName: accountAddress, + }, + } +} + +// members + +func init() { + accountStorageCapabilitiesTypeName := sema.Account_StorageCapabilitiesType.QualifiedIdentifier() + + // Account.StorageCapabilities.issue + RegisterTypeBoundFunction( + accountStorageCapabilitiesTypeName, + sema.Account_StorageCapabilitiesTypeIssueFunctionName, + NativeFunctionValue{ + ParameterCount: len(sema.Account_StorageCapabilitiesTypeIssueFunctionType.Parameters), + Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { + // Get address field from the receiver (Account.StorageCapabilities) + accountAddress := getAddressMetaInfoFromValue(args[0]) + + // Path argument + targetPathValue, ok := args[1].(PathValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + if !ok || targetPathValue.Domain != common.PathDomainStorage { + panic(errors.NewUnreachableError()) + } + + // Get borrow type type-argument + ty := typeArguments[0] + + // Issue capability controller and return capability + + return checkAndIssueStorageCapabilityControllerWithType( + config, + config.AccountHandler, + accountAddress, + targetPathValue, + ty, + ) + }, + }) +} From 6a7bdad95f8fe2d1165a3a3cbed1657a1b226ee1 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 11 Jul 2024 15:53:42 -0700 Subject: [PATCH 41/89] Fix FT token tranfer --- runtime/bbq/vm/config.go | 13 +- runtime/bbq/vm/errors.go | 5 +- runtime/bbq/vm/ft_test.go | 145 ++++++++++++------- runtime/bbq/vm/storage.go | 8 +- runtime/bbq/vm/value_account.go | 4 +- runtime/bbq/vm/value_account_capabilities.go | 2 +- runtime/bbq/vm/value_capability.go | 2 +- runtime/bbq/vm/value_composite.go | 34 ++--- runtime/bbq/vm/value_conversions.go | 43 ++++-- runtime/bbq/vm/value_dictionary.go | 37 ++--- runtime/bbq/vm/vm.go | 3 +- 11 files changed, 182 insertions(+), 114 deletions(-) diff --git a/runtime/bbq/vm/config.go b/runtime/bbq/vm/config.go index 943c8959b7..a43d0e25f7 100644 --- a/runtime/bbq/vm/config.go +++ b/runtime/bbq/vm/config.go @@ -22,6 +22,7 @@ import ( "github.com/onflow/cadence/runtime/bbq/commons" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" "github.com/onflow/cadence/runtime/tests/utils" ) @@ -39,7 +40,8 @@ type Config struct { MutationDuringCapabilityControllerIteration bool // TODO: temp - inter *interpreter.Interpreter + inter *interpreter.Interpreter + TypeLoader func(location common.Location, typeID interpreter.TypeID) sema.CompositeKindedType } // TODO: This is temporary. Remove once storing/reading is supported for VM values. @@ -49,7 +51,14 @@ func (c *Config) interpreter() *interpreter.Interpreter { nil, utils.TestLocation, &interpreter.Config{ - Storage: c.Storage, + Storage: c.Storage, + ImportLocationHandler: nil, + CompositeTypeHandler: func(location common.Location, typeID interpreter.TypeID) *sema.CompositeType { + return c.TypeLoader(location, typeID).(*sema.CompositeType) + }, + InterfaceTypeHandler: func(location common.Location, typeID interpreter.TypeID) *sema.InterfaceType { + return c.TypeLoader(location, typeID).(*sema.InterfaceType) + }, }, ) diff --git a/runtime/bbq/vm/errors.go b/runtime/bbq/vm/errors.go index 398155139c..8c7e814cac 100644 --- a/runtime/bbq/vm/errors.go +++ b/runtime/bbq/vm/errors.go @@ -38,7 +38,8 @@ func (l LinkerError) Error() string { } type MissingMemberValueError struct { - Name string + Parent MemberAccessibleValue + Name string } var _ error = MissingMemberValueError{} @@ -48,6 +49,6 @@ func (l MissingMemberValueError) IsInternalError() { } func (l MissingMemberValueError) Error() string { - return fmt.Sprintf("cannot find member: %s", l.Name) + return fmt.Sprintf("cannot find member: `%s` in `%T`", l.Name, l.Parent) } diff --git a/runtime/bbq/vm/ft_test.go b/runtime/bbq/vm/ft_test.go index 853f4fd6bc..c263d727d6 100644 --- a/runtime/bbq/vm/ft_test.go +++ b/runtime/bbq/vm/ft_test.go @@ -160,6 +160,22 @@ func TestFTTransfer(t *testing.T) { }, AccountHandler: &testAccountHandler{}, + + TypeLoader: func(location common.Location, typeID interpreter.TypeID) sema.CompositeKindedType { + semaImport, err := checkerImportHandler(nil, location, ast.EmptyRange) + if err != nil { + panic(err) + } + + elaborationImport := semaImport.(sema.ElaborationImport) + + compositeType := elaborationImport.Elaboration.CompositeType(typeID) + if compositeType != nil { + return compositeType + } + + return elaborationImport.Elaboration.InterfaceType(typeID) + }, } for _, address := range []common.Address{ @@ -220,8 +236,8 @@ func TestFTTransfer(t *testing.T) { total := int64(1000000) mintTxArgs := []Value{ - IntValue{total}, AddressValue(senderAddress), + IntValue{total}, } mintTxAuthorizer := NewAuthAccountReferenceValue(contractsAddress) @@ -622,21 +638,20 @@ access(all) contract FlowToken: FungibleToken { // Create the Vault with the total supply of tokens and save it in storage // let vault <- create Vault(balance: self.totalSupply) + adminAccount.storage.save(<-vault, to: /storage/flowTokenVault) // Create a public capability to the stored Vault that only exposes // the 'deposit' method through the 'Receiver' interface // - let receiverCap = adminAccount.capabilities.storage - .issue<&FlowToken.Vault>(/storage/flowTokenVault) - adminAccount.capabilities.publish(receiverCap, at: /public/flowTokenReceiver) + let receiverCapability = adminAccount.capabilities.storage.issue<&FlowToken.Vault>(/storage/flowTokenVault) + adminAccount.capabilities.publish(receiverCapability, at: /public/flowTokenReceiver) // Create a public capability to the stored Vault that only exposes // the 'balance' field through the 'Balance' interface // - let balanceCap = adminAccount.capabilities.storage - .issue<&FlowToken.Vault>(/storage/flowTokenVault) - adminAccount.capabilities.publish(balanceCap, at: /public/flowTokenBalance) + let balanceCapability = adminAccount.capabilities.storage.issue<&FlowToken.Vault>(/storage/flowTokenVault) + adminAccount.capabilities.publish(balanceCapability, at: /public/flowTokenBalance) let admin <- create Administrator() adminAccount.storage.save(<-admin, to: /storage/flowTokenAdmin) @@ -652,26 +667,32 @@ transaction { prepare(signer: auth(BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { - if signer.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) != nil { + var storagePath = /storage/flowTokenVault + + if signer.storage.borrow<&FlowToken.Vault>(from: storagePath) != nil { return } - var storagePath = /storage/flowTokenVault - // Create a new flowToken Vault and put it in storage signer.storage.save(<-FlowToken.createEmptyVault(), to: storagePath) - // Create a public capability to the Vault that exposes the Vault interfaces - let vaultCap = signer.capabilities.storage.issue<&FlowToken.Vault>( - storagePath + // Create a public capability to the Vault that only exposes + // the deposit function through the Receiver interface + let vaultCap = signer.capabilities.storage.issue<&FlowToken.Vault>(storagePath) + + signer.capabilities.publish( + vaultCap, + at: /public/flowTokenReceiver ) - signer.capabilities.publish(vaultCap, at: /public/flowTokenVault) - // Create a public Capability to the Vault's Receiver functionality - let receiverCap = signer.capabilities.storage.issue<&FlowToken.Vault>( - storagePath + // Create a public capability to the Vault that only exposes + // the balance field through the Balance interface + let balanceCap = signer.capabilities.storage.issue<&FlowToken.Vault>(storagePath) + + signer.capabilities.publish( + balanceCap, + at: /public/flowTokenBalance ) - signer.capabilities.publish(receiverCap, at: /public/flowTokenReceiver) } } ` @@ -680,27 +701,27 @@ const realFlowTokenTransferTransaction = ` import FungibleToken from 0x1 import FlowToken from 0x1 -transaction(recipient: Address, amount: Int) { - let tokenAdmin: &FlowToken.Administrator - let tokenReceiver: &{FungibleToken.Receiver} +transaction(amount: Int, to: Address) { + let sentVault: @{FungibleToken.Vault} prepare(signer: auth(BorrowValue) &Account) { - self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) - ?? panic("Signer is not the token admin") + // Get a reference to the signer's stored vault + let vaultRef = signer.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) + ?? panic("Could not borrow reference to the owner's Vault!") - self.tokenReceiver = getAccount(recipient) - .capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) - .borrow() - ?? panic("Unable to borrow receiver reference") + // Withdraw tokens from the signer's stored vault + self.sentVault <- vaultRef.withdraw(amount: amount) } execute { - let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) - let mintedVault <- minter.mintTokens(amount: amount) - - self.tokenReceiver.deposit(from: <-mintedVault) + // Get a reference to the recipient's Receiver + let receiverRef = getAccount(to) + .capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + .borrow() + ?? panic("Could not borrow receiver reference to the recipient's Vault") - destroy minter + // Deposit the withdrawn tokens in the recipient's receiver + receiverRef.deposit(from: <-self.sentVault) } } ` @@ -709,31 +730,32 @@ const realMintFlowTokenTransaction = ` import FungibleToken from 0x1 import FlowToken from 0x1 -transaction(amount: Int, to: Address) { +transaction(recipient: Address, amount: Int) { - // The Vault resource that holds the tokens that are being transferred - let sentVault: @{FungibleToken.Vault} + /// Reference to the FlowToken Minter Resource object + let tokenAdmin: &FlowToken.Administrator - prepare(signer: auth(BorrowValue) &Account) { + /// Reference to the Fungible Token Receiver of the recipient + let tokenReceiver: &{FungibleToken.Receiver} - // Get a reference to the signer's stored vault - let vaultRef = signer.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) - ?? panic("Could not borrow reference to the owner's Vault!") + prepare(signer: auth(BorrowValue) &Account) { + self.tokenAdmin = signer.storage + .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + ?? panic("Signer is not the token admin") - // Withdraw tokens from the signer's stored vault - self.sentVault <- vaultRef.withdraw(amount: amount) + self.tokenReceiver = getAccount(recipient) + .capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + .borrow() + ?? panic("Unable to borrow receiver reference") } execute { + let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) + let mintedVault <- minter.mintTokens(amount: amount) - // Get a reference to the recipient's Receiver - let receiverRef = getAccount(to) - .capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) - .borrow() - ?? panic("Could not borrow receiver reference to the recipient's Vault") + self.tokenReceiver.deposit(from: <-mintedVault) - // Deposit the withdrawn tokens in the recipient's receiver - receiverRef.deposit(from: <-self.sentVault) + destroy minter } } ` @@ -745,8 +767,8 @@ import FlowToken from 0x1 access(all) fun main(account: Address): Int { let vaultRef = getAccount(account) - .getCapability(/public/flowTokenBalance) - .borrow<&FlowToken.Vault>() + .capabilities.get<&FlowToken.Vault>(/public/flowTokenBalance) + .borrow() ?? panic("Could not borrow Balance reference to the Vault") return vaultRef.balance @@ -807,7 +829,8 @@ func BenchmarkFTTransfer(b *testing.B) { flowTokenVM := NewVM( flowTokenProgram, &Config{ - Storage: storage, + Storage: storage, + AccountHandler: &testAccountHandler{}, }, ) @@ -874,6 +897,24 @@ func BenchmarkFTTransfer(b *testing.B) { return nil } }, + + AccountHandler: &testAccountHandler{}, + + TypeLoader: func(location common.Location, typeID interpreter.TypeID) sema.CompositeKindedType { + semaImport, err := checkerImportHandler(nil, location, ast.EmptyRange) + if err != nil { + panic(err) + } + + elaborationImport := semaImport.(sema.ElaborationImport) + + compositeType := elaborationImport.Elaboration.CompositeType(typeID) + if compositeType != nil { + return compositeType + } + + return elaborationImport.Elaboration.InterfaceType(typeID) + }, } for _, address := range []common.Address{ @@ -903,6 +944,7 @@ func BenchmarkFTTransfer(b *testing.B) { authorizer := NewAuthAccountReferenceValue(address) err = setupTxVM.ExecuteTransaction(nil, authorizer) require.NoError(b, err) + require.Empty(b, setupTxVM.stack) } // Mint FLOW to sender @@ -938,6 +980,7 @@ func BenchmarkFTTransfer(b *testing.B) { mintTxAuthorizer := NewAuthAccountReferenceValue(contractsAddress) err = mintTxVM.ExecuteTransaction(mintTxArgs, mintTxAuthorizer) require.NoError(b, err) + require.Empty(b, mintTxVM.stack) // ----- Run token transfer transaction ----- diff --git a/runtime/bbq/vm/storage.go b/runtime/bbq/vm/storage.go index 49e002ba7e..18851eba35 100644 --- a/runtime/bbq/vm/storage.go +++ b/runtime/bbq/vm/storage.go @@ -32,9 +32,9 @@ import ( // CheckHealth() error // } -func StoredValue(gauge common.MemoryGauge, storable atree.Storable, storage atree.SlabStorage) Value { +func StoredValue(gauge common.MemoryGauge, storable atree.Storable, storage interpreter.Storage) Value { value := interpreter.StoredValue(gauge, storable, storage) - return InterpreterValueToVMValue(value) + return InterpreterValueToVMValue(storage, value) } func ReadStored( @@ -50,7 +50,7 @@ func ReadStored( } referenced := accountStorage.ReadValue(gauge, interpreter.StringStorageMapKey(identifier)) - return InterpreterValueToVMValue(referenced) + return InterpreterValueToVMValue(storage, referenced) } func WriteStored( @@ -61,7 +61,7 @@ func WriteStored( value Value, ) (existed bool) { accountStorage := config.Storage.GetStorageMap(storageAddress, domain, true) - interValue := VMValueToInterpreterValue(value) + interValue := VMValueToInterpreterValue(config, value) return accountStorage.WriteValue( config.interpreter(), diff --git a/runtime/bbq/vm/value_account.go b/runtime/bbq/vm/value_account.go index c6236d3ffa..dc8c30389c 100644 --- a/runtime/bbq/vm/value_account.go +++ b/runtime/bbq/vm/value_account.go @@ -359,7 +359,7 @@ func recordStorageCapabilityController( accountStorage := config.Storage.GetStorageMap(address, stdlib.PathCapabilityStorageDomain, true) referenced := accountStorage.ReadValue(config.MemoryGauge, interpreter.StringStorageMapKey(identifier)) - readValue := InterpreterValueToVMValue(referenced) + readValue := InterpreterValueToVMValue(config.Storage, referenced) setKey := capabilityIDValue setValue := Nil @@ -371,7 +371,7 @@ func recordStorageCapabilityController( setKey, setValue, ) - capabilityIDSetInterValue := VMValueToInterpreterValue(capabilityIDSet) + capabilityIDSetInterValue := VMValueToInterpreterValue(config, capabilityIDSet) accountStorage.SetValue(config.interpreter(), storageMapKey, capabilityIDSetInterValue) } else { capabilityIDSet := readValue.(*DictionaryValue) diff --git a/runtime/bbq/vm/value_account_capabilities.go b/runtime/bbq/vm/value_account_capabilities.go index f0cd4d2aa6..bc8f5c92ff 100644 --- a/runtime/bbq/vm/value_account_capabilities.go +++ b/runtime/bbq/vm/value_account_capabilities.go @@ -141,7 +141,7 @@ func init() { ) { panic(interpreter.OverwriteError{ Address: interpreter.AddressValue(accountAddress), - Path: VMValueToInterpreterValue(path).(interpreter.PathValue), + Path: VMValueToInterpreterValue(config, path).(interpreter.PathValue), }) } diff --git a/runtime/bbq/vm/value_capability.go b/runtime/bbq/vm/value_capability.go index 1c15785c8b..4d3e6959bf 100644 --- a/runtime/bbq/vm/value_capability.go +++ b/runtime/bbq/vm/value_capability.go @@ -213,7 +213,7 @@ func getCapabilityController( } referenced := accountStorage.ReadValue(config.MemoryGauge, storageMapKey) - vmReferencedValue := InterpreterValueToVMValue(referenced) + vmReferencedValue := InterpreterValueToVMValue(config.Storage, referenced) controller, ok := vmReferencedValue.(CapabilityControllerValue) if !ok { diff --git a/runtime/bbq/vm/value_composite.go b/runtime/bbq/vm/value_composite.go index ddbc5c12bb..1221e990ea 100644 --- a/runtime/bbq/vm/value_composite.go +++ b/runtime/bbq/vm/value_composite.go @@ -122,7 +122,7 @@ func (v *CompositeValue) GetMember(config *Config, name string) Value { return nil } -func (v *CompositeValue) SetMember(conf *Config, name string, value Value) { +func (v *CompositeValue) SetMember(config *Config, name string, value Value) { // TODO: //address := v.StorageID().Address @@ -134,12 +134,12 @@ func (v *CompositeValue) SetMember(conf *Config, name string, value Value) { // nil, //) - interpreterValue := VMValueToInterpreterValue(value) + interpreterValue := VMValueToInterpreterValue(config, value) existingStorable, err := v.dictionary.Set( interpreter.StringAtreeValueComparator, interpreter.StringAtreeValueHashInput, - interpreter.NewStringAtreeValue(conf.MemoryGauge, name), + interpreter.NewStringAtreeValue(config.MemoryGauge, name), interpreterValue, ) @@ -148,10 +148,10 @@ func (v *CompositeValue) SetMember(conf *Config, name string, value Value) { } if existingStorable != nil { - inter := conf.interpreter() - existingValue := interpreter.StoredValue(nil, existingStorable, conf.Storage) + inter := config.interpreter() + existingValue := interpreter.StoredValue(nil, existingStorable, config.Storage) existingValue.DeepRemove(inter) - RemoveReferencedSlab(conf.Storage, existingStorable) + RemoveReferencedSlab(config.Storage, existingStorable) } } @@ -183,7 +183,7 @@ func (v *CompositeValue) String() string { } func (v *CompositeValue) Transfer( - conf *Config, + config *Config, address atree.Address, remove bool, storable atree.Storable, @@ -228,7 +228,7 @@ func (v *CompositeValue) Transfer( if needsStoreTo && v.Kind == common.CompositeKindContract { panic(interpreter.NonTransferableValueError{ - Value: VMValueToInterpreterValue(v), + Value: VMValueToInterpreterValue(config, v), }) } @@ -239,10 +239,10 @@ func (v *CompositeValue) Transfer( } elementMemoryUse := common.NewAtreeMapPreAllocatedElementsMemoryUsage(v.dictionary.Count(), 0) - common.UseMemory(conf.MemoryGauge, elementMemoryUse) + common.UseMemory(config.MemoryGauge, elementMemoryUse) dictionary, err = atree.NewMapFromBatchData( - conf.Storage, + config.Storage, address, atree.NewDefaultDigesterBuilder(), v.dictionary.Type(), @@ -262,11 +262,11 @@ func (v *CompositeValue) Transfer( // NOTE: key is stringAtreeValue // and does not need to be converted or copied - value := interpreter.MustConvertStoredValue(conf.MemoryGauge, atreeValue) + value := interpreter.MustConvertStoredValue(config.MemoryGauge, atreeValue) // TODO: - vmValue := InterpreterValueToVMValue(value) - vmValue.Transfer(conf, address, remove, nil) + vmValue := InterpreterValueToVMValue(config.Storage, value) + vmValue.Transfer(config, address, remove, nil) return atreeKey, value, nil }, @@ -277,15 +277,15 @@ func (v *CompositeValue) Transfer( if remove { err = v.dictionary.PopIterate(func(nameStorable atree.Storable, valueStorable atree.Storable) { - RemoveReferencedSlab(conf.Storage, nameStorable) - RemoveReferencedSlab(conf.Storage, valueStorable) + RemoveReferencedSlab(config.Storage, nameStorable) + RemoveReferencedSlab(config.Storage, valueStorable) }) if err != nil { panic(errors.NewExternalError(err)) } //interpreter.maybeValidateAtreeValue(v.dictionary) - RemoveReferencedSlab(conf.Storage, storable) + RemoveReferencedSlab(config.Storage, storable) } } @@ -325,7 +325,7 @@ func (v *CompositeValue) Transfer( if res == nil { typeInfo := interpreter.NewCompositeTypeInfo( - conf.MemoryGauge, + config.MemoryGauge, v.Location, v.QualifiedIdentifier, v.Kind, diff --git a/runtime/bbq/vm/value_conversions.go b/runtime/bbq/vm/value_conversions.go index c165b27633..3afc0a94ea 100644 --- a/runtime/bbq/vm/value_conversions.go +++ b/runtime/bbq/vm/value_conversions.go @@ -27,7 +27,7 @@ import ( // Utility methods to convert between old and new values. // These are temporary until all parts of the interpreter are migrated to the vm. -func InterpreterValueToVMValue(value interpreter.Value) Value { +func InterpreterValueToVMValue(storage interpreter.Storage, value interpreter.Value) Value { switch value := value.(type) { case nil: return nil @@ -61,7 +61,7 @@ func InterpreterValueToVMValue(value interpreter.Value) Value { var fieldNames []string for name, field := range value.Fields { - fields[name] = InterpreterValueToVMValue(field) + fields[name] = InterpreterValueToVMValue(storage, field) fieldNames = append(fieldNames, name) } @@ -86,14 +86,22 @@ func InterpreterValueToVMValue(value interpreter.Value) Value { return NewStorageCapabilityControllerValue( value.BorrowType, NewIntValue(int64(value.CapabilityID.ToInt(interpreter.EmptyLocationRange))), - InterpreterValueToVMValue(value.TargetPath).(PathValue), + InterpreterValueToVMValue(storage, value.TargetPath).(PathValue), + ) + case *interpreter.StorageReferenceValue: + return NewStorageReferenceValue( + storage, + value.Authorization, + value.TargetStorageAddress, + InterpreterValueToVMValue(storage, value.TargetPath).(PathValue), + interpreter.ConvertSemaToStaticType(nil, value.BorrowedType), ) default: panic(errors.NewUnreachableError()) } } -func VMValueToInterpreterValue(value Value) interpreter.Value { +func VMValueToInterpreterValue(config *Config, value Value) interpreter.Value { switch value := value.(type) { case nil: return nil @@ -126,7 +134,7 @@ func VMValueToInterpreterValue(value Value) interpreter.Value { return interpreter.NewCapabilityValue( nil, interpreter.NewUnmeteredUInt64Value(uint64(value.ID.SmallInt)), // TODO: properly convert - VMValueToInterpreterValue(value.Address).(interpreter.AddressValue), + VMValueToInterpreterValue(config, value.Address).(interpreter.AddressValue), value.BorrowType, ) //case LinkValue: @@ -146,7 +154,7 @@ func VMValueToInterpreterValue(value Value) interpreter.Value { var fieldNames []string for name, field := range value.fields { - fields[name] = VMValueToInterpreterValue(field) + fields[name] = VMValueToInterpreterValue(config, field) fieldNames = append(fieldNames, name) } @@ -160,20 +168,25 @@ func VMValueToInterpreterValue(value Value) interpreter.Value { nil, nil, ) - //case *StorageReferenceValue: - // return interpreter.NewStorageReferenceValue( - // nil, - // value.Authorized, - // value.TargetStorageAddress, - // value.TargetPath, - // value.BorrowedType, - // ) + case *StorageReferenceValue: + inter := config.interpreter() + semaBorrowType, err := inter.ConvertStaticToSemaType(value.BorrowedType) + if err != nil { + panic(err) + } + return interpreter.NewStorageReferenceValue( + nil, + value.Authorization, + value.TargetStorageAddress, + VMValueToInterpreterValue(config, value.TargetPath).(interpreter.PathValue), + semaBorrowType, + ) case *StorageCapabilityControllerValue: return interpreter.NewStorageCapabilityControllerValue( nil, value.BorrowType, interpreter.NewUnmeteredUInt64Value(uint64(value.CapabilityID.SmallInt)), - VMValueToInterpreterValue(value.TargetPath).(interpreter.PathValue), + VMValueToInterpreterValue(config, value.TargetPath).(interpreter.PathValue), ) default: panic(errors.NewUnreachableError()) diff --git a/runtime/bbq/vm/value_dictionary.go b/runtime/bbq/vm/value_dictionary.go index a6e3faf38c..e88b96abab 100644 --- a/runtime/bbq/vm/value_dictionary.go +++ b/runtime/bbq/vm/value_dictionary.go @@ -166,15 +166,15 @@ func (v *DictionaryValue) Insert( return NewSomeValueNonCopying(existingValue) } -func (v *DictionaryValue) InsertWithoutTransfer(conf *Config, key, value Value) (existingValueStorable atree.Storable) { +func (v *DictionaryValue) InsertWithoutTransfer(config *Config, key, value Value) (existingValueStorable atree.Storable) { //interpreter.validateMutation(v.StorageID(), locationRange) - valueComparator := newValueComparator(conf) - hashInputProvider := newHashInputProvider(conf) + valueComparator := newValueComparator(config) + hashInputProvider := newHashInputProvider(config) - keyInterpreterValue := VMValueToInterpreterValue(key) - valueInterpreterValue := VMValueToInterpreterValue(value) + keyInterpreterValue := VMValueToInterpreterValue(config, key) + valueInterpreterValue := VMValueToInterpreterValue(config, value) // atree only calls Storable() on keyValue if needed, // i.e., if the key is a new key @@ -209,13 +209,14 @@ func (v *DictionaryValue) String() string { } func (v *DictionaryValue) Transfer( - conf *Config, + config *Config, address atree.Address, remove bool, storable atree.Storable, ) Value { currentStorageID := v.StorageID() currentAddress := currentStorageID.Address + storage := config.Storage dictionary := v.dictionary @@ -223,8 +224,8 @@ func (v *DictionaryValue) Transfer( isResourceKinded := v.IsResourceKinded() if needsStoreTo || !isResourceKinded { - valueComparator := newValueComparator(conf) - hashInputProvider := newHashInputProvider(conf) + valueComparator := newValueComparator(config) + hashInputProvider := newHashInputProvider(config) iterator, err := v.dictionary.Iterator() if err != nil { @@ -232,7 +233,7 @@ func (v *DictionaryValue) Transfer( } dictionary, err = atree.NewMapFromBatchData( - conf.Storage, + storage, address, atree.NewDefaultDigesterBuilder(), v.dictionary.Type(), @@ -249,15 +250,15 @@ func (v *DictionaryValue) Transfer( return nil, nil, nil } - key := interpreter.MustConvertStoredValue(conf.MemoryGauge, atreeValue) + key := interpreter.MustConvertStoredValue(config.MemoryGauge, atreeValue) // TODO: converted value is unused - vmKey := InterpreterValueToVMValue(key) - vmKey = vmKey.Transfer(conf, address, remove, nil) + vmKey := InterpreterValueToVMValue(config.Storage, key) + vmKey = vmKey.Transfer(config, address, remove, nil) - value := interpreter.MustConvertStoredValue(conf.MemoryGauge, atreeValue) + value := interpreter.MustConvertStoredValue(config.MemoryGauge, atreeValue) // TODO: converted value is unused - vmValue := InterpreterValueToVMValue(value) - vmValue = vmValue.Transfer(conf, address, remove, nil) + vmValue := InterpreterValueToVMValue(config.Storage, value) + vmValue = vmValue.Transfer(config, address, remove, nil) return key, value, nil }, @@ -268,15 +269,15 @@ func (v *DictionaryValue) Transfer( if remove { err = v.dictionary.PopIterate(func(keyStorable atree.Storable, valueStorable atree.Storable) { - RemoveReferencedSlab(conf.Storage, keyStorable) - RemoveReferencedSlab(conf.Storage, valueStorable) + RemoveReferencedSlab(storage, keyStorable) + RemoveReferencedSlab(storage, valueStorable) }) if err != nil { panic(errors.NewExternalError(err)) } //interpreter.maybeValidateAtreeValue(v.dictionary) - RemoveReferencedSlab(conf.Storage, storable) + RemoveReferencedSlab(storage, storable) } } diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 805c4471b7..3268cbd91c 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -431,7 +431,8 @@ func opGetField(vm *VM) { fieldValue := memberAccessibleValue.GetMember(vm.config, fieldNameStr) if fieldValue == nil { panic(MissingMemberValueError{ - Name: fieldNameStr, + Parent: memberAccessibleValue, + Name: fieldNameStr, }) } From 2a1b1e543dd29c1ab10167182bcd875f99513f8b Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 12 Jul 2024 10:37:23 -0700 Subject: [PATCH 42/89] Update interpreter FT tranfer benchmarck --- runtime/ft_test.go | 78 ++++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/runtime/ft_test.go b/runtime/ft_test.go index d9c47330c5..34e0e5c0f2 100644 --- a/runtime/ft_test.go +++ b/runtime/ft_test.go @@ -395,26 +395,34 @@ import FlowToken from 0x1 transaction { - prepare(signer: &Account) { - - if signer.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) == nil { - // Create a new flowToken Vault and put it in storage - signer.storage.save(<-FlowToken.createEmptyVault(), to: /storage/flowTokenVault) - - // Create a public capability to the Vault that only exposes - // the deposit function through the Receiver interface - signer.link<&FlowToken.Vault>( - /public/flowTokenReceiver, - target: /storage/flowTokenVault - ) - - // Create a public capability to the Vault that only exposes - // the balance field through the Balance interface - signer.link<&FlowToken.Vault>( - /public/flowTokenBalance, - target: /storage/flowTokenVault - ) + prepare(signer: auth(BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { + + var storagePath = /storage/flowTokenVault + + if signer.storage.borrow<&FlowToken.Vault>(from: storagePath) != nil { + return } + + // Create a new flowToken Vault and put it in storage + signer.storage.save(<-FlowToken.createEmptyVault(), to: storagePath) + + // Create a public capability to the Vault that only exposes + // the deposit function through the Receiver interface + let vaultCap = signer.capabilities.storage.issue<&FlowToken.Vault>(storagePath) + + signer.capabilities.publish( + vaultCap, + at: /public/flowTokenReceiver + ) + + // Create a public capability to the Vault that only exposes + // the balance field through the Balance interface + let balanceCap = signer.capabilities.storage.issue<&FlowToken.Vault>(storagePath) + + signer.capabilities.publish( + balanceCap, + at: /public/flowTokenBalance + ) } } ` @@ -424,17 +432,21 @@ import FungibleToken from 0x1 import FlowToken from 0x1 transaction(recipient: Address, amount: UFix64) { + + /// Reference to the FlowToken Minter Resource object let tokenAdmin: &FlowToken.Administrator + + /// Reference to the Fungible Token Receiver of the recipient let tokenReceiver: &{FungibleToken.Receiver} - prepare(signer: &Account) { - self.tokenAdmin = signer + prepare(signer: auth(BorrowValue) &Account) { + self.tokenAdmin = signer.storage .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) ?? panic("Signer is not the token admin") self.tokenReceiver = getAccount(recipient) - .getCapability(/public/flowTokenReceiver) - .borrow<&{FungibleToken.Receiver}>() + .capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + .borrow() ?? panic("Unable to borrow receiver reference") } @@ -454,12 +466,9 @@ import FungibleToken from 0x1 import FlowToken from 0x1 transaction(amount: UFix64, to: Address) { - - // The Vault resource that holds the tokens that are being transferred let sentVault: @{FungibleToken.Vault} - prepare(signer: &Account) { - + prepare(signer: auth(BorrowValue) &Account) { // Get a reference to the signer's stored vault let vaultRef = signer.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) ?? panic("Could not borrow reference to the owner's Vault!") @@ -469,11 +478,10 @@ transaction(amount: UFix64, to: Address) { } execute { - // Get a reference to the recipient's Receiver let receiverRef = getAccount(to) - .getCapability(/public/flowTokenReceiver) - .borrow<&{FungibleToken.Receiver}>() + .capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + .borrow() ?? panic("Could not borrow receiver reference to the recipient's Vault") // Deposit the withdrawn tokens in the recipient's receiver @@ -489,8 +497,8 @@ import FlowToken from 0x1 access(all) fun main(account: Address): UFix64 { let vaultRef = getAccount(account) - .getCapability(/public/flowTokenBalance) - .borrow<&FlowToken.Vault>() + .capabilities.get<&FlowToken.Vault>(/public/flowTokenBalance) + .borrow() ?? panic("Could not borrow Balance reference to the Vault") return vaultRef.balance @@ -565,7 +573,7 @@ func BenchmarkRuntimeFungibleTokenTransfer(b *testing.B) { ` transaction { - prepare(signer: &Account) { + prepare(signer: auth(Storage, Capabilities, Contracts) &Account) { signer.contracts.add(name: "FlowToken", code: "%s".decodeHex(), signer) } } @@ -665,6 +673,8 @@ func BenchmarkRuntimeFungibleTokenTransfer(b *testing.B) { inter := NewTestInterpreter(b) + nextScriptLocation := NewScriptLocationGenerator() + for _, address := range []common.Address{ senderAddress, receiverAddress, @@ -679,7 +689,7 @@ func BenchmarkRuntimeFungibleTokenTransfer(b *testing.B) { }, Context{ Interface: runtimeInterface, - Location: nextTransactionLocation(), + Location: nextScriptLocation(), Environment: environment, }, ) From cbcdf7b5e4b412e8912c3d66e0b745eda318e4c6 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 16 Jul 2024 12:10:01 -0700 Subject: [PATCH 43/89] Cleanup code --- runtime/bbq/compiler/compiler.go | 8 +- runtime/bbq/compiler/native_functions.go | 4 +- runtime/bbq/opcode/opcode.go | 1 - runtime/bbq/vm/config.go | 5 +- runtime/bbq/vm/context.go | 5 ++ runtime/bbq/vm/value_account.go | 4 +- runtime/bbq/vm/value_account_capabilities.go | 2 +- runtime/bbq/vm/value_account_storage.go | 4 +- .../vm/value_account_storagecapabilities.go | 4 +- runtime/bbq/vm/value_simple_composite.go | 80 ++----------------- runtime/bbq/vm/vm.go | 3 +- 11 files changed, 26 insertions(+), 94 deletions(-) diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index 2b713ab16d..ab7cd668e8 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -51,7 +51,6 @@ type Compiler struct { loops []*loop currentLoop *loop staticTypes [][]byte - exportedImports []*bbq.Import // Cache alike for staticTypes and constants in the pool. typesInPool map[sema.Type]uint16 @@ -105,7 +104,6 @@ func NewCompiler( Config: &Config{}, globals: make(map[string]*global), importedGlobals: indexedNativeFunctions, - exportedImports: make([]*bbq.Import, 0), typesInPool: make(map[sema.Type]uint16), constantsInPool: make(map[constantsCacheKey]*constant), compositeTypeStack: &Stack[*sema.CompositeType]{ @@ -378,16 +376,16 @@ func (c *Compiler) reserveGlobalVars( c.addGlobal(qualifiedTypeName) - // For composite type other than contracts, globals variable + // For composite types other than contracts, global variables // reserved by the type-name will be used for the init method. - // For contracts, globals variable reserved by the type-name + // For contracts, global variables reserved by the type-name // will be used for the contract value. // Hence, reserve a separate global var for contract inits. if declaration.CompositeKind == common.CompositeKindContract { c.addGlobal(commons.InitFunctionName) } - // Define globals for functions before visiting function bodies + // Define globals for functions before visiting function bodies. c.reserveGlobalVars( qualifiedTypeName, nil, diff --git a/runtime/bbq/compiler/native_functions.go b/runtime/bbq/compiler/native_functions.go index a564da829f..f130b8f8ef 100644 --- a/runtime/bbq/compiler/native_functions.go +++ b/runtime/bbq/compiler/native_functions.go @@ -24,7 +24,7 @@ import ( "github.com/onflow/cadence/runtime/bbq/commons" ) -var indexedNativeFunctions map[string]*global +var indexedNativeFunctions = make(map[string]*global) var nativeFunctions []*global var builtinTypes = []sema.Type{ @@ -40,8 +40,6 @@ var stdlibFunctions = []string{ } func init() { - indexedNativeFunctions = make(map[string]*global) - // Here the order isn't really important. // Because the native functions used by a program are also // added to the imports section of the compiled program. diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index b409201e4b..d1406edcb1 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -40,7 +40,6 @@ const ( IntDivide IntMod IntLess - IntGreater IntLessOrEqual IntGreaterOrEqual diff --git a/runtime/bbq/vm/config.go b/runtime/bbq/vm/config.go index a43d0e25f7..529b44caa0 100644 --- a/runtime/bbq/vm/config.go +++ b/runtime/bbq/vm/config.go @@ -34,12 +34,11 @@ type Config struct { ContractValueHandler stdlib.AccountHandler - // TODO: Move these to `Context`. - // Context is the shared state across a single execution + // TODO: Move these to a 'shared state'? CapabilityControllerIterations map[AddressPath]int MutationDuringCapabilityControllerIteration bool - // TODO: temp + // TODO: These are temporary. Remove once storing/reading is supported for VM values. inter *interpreter.Interpreter TypeLoader func(location common.Location, typeID interpreter.TypeID) sema.CompositeKindedType } diff --git a/runtime/bbq/vm/context.go b/runtime/bbq/vm/context.go index 36ca70bbe5..703630ca3c 100644 --- a/runtime/bbq/vm/context.go +++ b/runtime/bbq/vm/context.go @@ -22,6 +22,11 @@ import ( "github.com/onflow/cadence/runtime/bbq" ) +// Context is the context of a program. +// It holds information that are accessible to a given program, +// such as constants, static-types, and global variables. +// These info are accessed by the opcodes of the program. +// i.e: indexes used in opcodes refer to the indexes of its context. type Context struct { Program *bbq.Program Globals []Value diff --git a/runtime/bbq/vm/value_account.go b/runtime/bbq/vm/value_account.go index dc8c30389c..eea4acdd96 100644 --- a/runtime/bbq/vm/value_account.go +++ b/runtime/bbq/vm/value_account.go @@ -79,7 +79,7 @@ func newAccountValue( // members func init() { - // TODO + // Any member methods goes here } func getAddressMetaInfoFromValue(value Value) common.Address { @@ -88,7 +88,7 @@ func getAddressMetaInfoFromValue(value Value) common.Address { panic(errors.NewUnreachableError()) } - addressMetaInfo := simpleCompositeValue.metaInfo[sema.AccountTypeAddressFieldName] + addressMetaInfo := simpleCompositeValue.metadata[sema.AccountTypeAddressFieldName] address, ok := addressMetaInfo.(common.Address) if !ok { panic(errors.NewUnreachableError()) diff --git a/runtime/bbq/vm/value_account_capabilities.go b/runtime/bbq/vm/value_account_capabilities.go index bc8f5c92ff..d139148b90 100644 --- a/runtime/bbq/vm/value_account_capabilities.go +++ b/runtime/bbq/vm/value_account_capabilities.go @@ -36,7 +36,7 @@ func NewAccountCapabilitiesValue(accountAddress common.Address) *SimpleComposite sema.Account_CapabilitiesTypeStorageFieldName: NewAccountStorageCapabilitiesValue(accountAddress), // TODO: add the remaining fields }, - metaInfo: map[string]any{ + metadata: map[string]any{ sema.AccountTypeAddressFieldName: accountAddress, }, } diff --git a/runtime/bbq/vm/value_account_storage.go b/runtime/bbq/vm/value_account_storage.go index 3cb836c066..0d609be441 100644 --- a/runtime/bbq/vm/value_account_storage.go +++ b/runtime/bbq/vm/value_account_storage.go @@ -32,10 +32,10 @@ func NewAccountStorageValue(accountAddress common.Address) *SimpleCompositeValue typeID: sema.Account_StorageType.ID(), staticType: interpreter.PrimitiveStaticTypeAccount_Storage, Kind: common.CompositeKindStructure, - fields: map[string]Value{ + fields: map[string]Value{ // TODO: add the remaining fields }, - metaInfo: map[string]any{ + metadata: map[string]any{ sema.AccountTypeAddressFieldName: accountAddress, }, } diff --git a/runtime/bbq/vm/value_account_storagecapabilities.go b/runtime/bbq/vm/value_account_storagecapabilities.go index ffd9fc215d..f237ddb710 100644 --- a/runtime/bbq/vm/value_account_storagecapabilities.go +++ b/runtime/bbq/vm/value_account_storagecapabilities.go @@ -30,10 +30,10 @@ func NewAccountStorageCapabilitiesValue(accountAddress common.Address) *SimpleCo typeID: sema.Account_StorageCapabilitiesType.ID(), staticType: interpreter.PrimitiveStaticTypeAccount_StorageCapabilities, Kind: common.CompositeKindStructure, - fields: map[string]Value{ + fields: map[string]Value{ // TODO: add the remaining fields }, - metaInfo: map[string]any{ + metadata: map[string]any{ sema.AccountTypeAddressFieldName: accountAddress, }, } diff --git a/runtime/bbq/vm/value_simple_composite.go b/runtime/bbq/vm/value_simple_composite.go index d3dc031b32..044b15d467 100644 --- a/runtime/bbq/vm/value_simple_composite.go +++ b/runtime/bbq/vm/value_simple_composite.go @@ -28,7 +28,11 @@ type SimpleCompositeValue struct { typeID common.TypeID staticType StaticType Kind common.CompositeKind - metaInfo map[string]any + + // metadata is a property bag to carry internal data + // that are not visible to cadence users. + // TODO: any better way to pass down information? + metadata map[string]any } var _ Value = &CompositeValue{} @@ -78,76 +82,4 @@ func (v *SimpleCompositeValue) Transfer( return v } -func (v *SimpleCompositeValue) Destroy(*Config) { - - //interpreter.ReportComputation(common.ComputationKindDestroyCompositeValue, 1) - // - //if interpreter.Config.InvalidatedResourceValidationEnabled { - // v.checkInvalidatedResourceUse(locationRange) - //} - // - //storageID := v.StorageID() - // - //if interpreter.Config.TracingEnabled { - // startTime := time.Now() - // - // owner := v.GetOwner().String() - // typeID := string(v.TypeID()) - // kind := v.Kind.String() - // - // defer func() { - // - // interpreter.reportCompositeValueDestroyTrace( - // owner, - // typeID, - // kind, - // time.Since(startTime), - // ) - // }() - //} - - //interpreter = v.getInterpreter(interpreter) - - //// if composite was deserialized, dynamically link in the destructor - //if v.Destructor == nil { - // v.Destructor = interpreter.sharedState.typeCodes.CompositeCodes[v.TypeID()].DestructorFunction - //} - // - //destructor := v.Destructor - // - //if destructor != nil { - // invocation := NewInvocation( - // interpreter, - // v, - // nil, - // nil, - // nil, - // locationRange, - // ) - // - // destructor.invoke(invocation) - //} - - //v.isDestroyed = true - - //if interpreter.Config.InvalidatedResourceValidationEnabled { - // v.dictionary = nil - //} - - //interpreter.updateReferencedResource( - // storageID, - // storageID, - // func(value ReferenceTrackedResourceKindedValue) { - // compositeValue, ok := value.(*CompositeValue) - // if !ok { - // panic(errors.NewUnreachableError()) - // } - // - // compositeValue.isDestroyed = true - // - // if interpreter.Config.InvalidatedResourceValidationEnabled { - // compositeValue.dictionary = nil - // } - // }, - //) -} +func (v *SimpleCompositeValue) Destroy(*Config) {} diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 3268cbd91c..4e07cec749 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -654,7 +654,8 @@ func (vm *VM) lookupFunction(location common.Location, name string) FunctionValu // If not found, link the function now, dynamically. - // TODO: This currently link all functions in program, unnecessarily. Link only yhe requested function. + // TODO: This currently link all functions in program, unnecessarily. + // Link only the requested function. program := vm.config.ImportHandler(location) ctx := NewContext(program, nil) From 4ee489a80d5987c6cbd8d7006a7324f97d6c6299 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 2 Aug 2024 14:40:34 -0700 Subject: [PATCH 44/89] Update to match latest changes in master --- runtime/bbq/vm/storage.go | 11 ++++++--- runtime/bbq/vm/test_utils.go | 18 +++++++------- runtime/bbq/vm/value_composite.go | 38 +++++++++++++++++++----------- runtime/bbq/vm/value_dictionary.go | 21 +++++++++++------ 4 files changed, 55 insertions(+), 33 deletions(-) diff --git a/runtime/bbq/vm/storage.go b/runtime/bbq/vm/storage.go index 18851eba35..3b837d7dd3 100644 --- a/runtime/bbq/vm/storage.go +++ b/runtime/bbq/vm/storage.go @@ -37,6 +37,11 @@ func StoredValue(gauge common.MemoryGauge, storable atree.Storable, storage inte return InterpreterValueToVMValue(storage, value) } +func MustConvertStoredValue(gauge common.MemoryGauge, storage interpreter.Storage, storedValue atree.Value) Value { + value := interpreter.MustConvertStoredValue(gauge, storedValue) + return InterpreterValueToVMValue(storage, value) +} + func ReadStored( gauge common.MemoryGauge, storage interpreter.Storage, @@ -72,13 +77,13 @@ func WriteStored( } func RemoveReferencedSlab(storage interpreter.Storage, storable atree.Storable) { - storageIDStorable, ok := storable.(atree.StorageIDStorable) + slabIDStorable, ok := storable.(atree.SlabIDStorable) if !ok { return } - storageID := atree.StorageID(storageIDStorable) - err := storage.Remove(storageID) + slabID := atree.SlabID(slabIDStorable) + err := storage.Remove(slabID) if err != nil { panic(errors.NewExternalError(err)) } diff --git a/runtime/bbq/vm/test_utils.go b/runtime/bbq/vm/test_utils.go index 81fb3362cc..9edcf13cab 100644 --- a/runtime/bbq/vm/test_utils.go +++ b/runtime/bbq/vm/test_utils.go @@ -30,13 +30,13 @@ type testAccountHandler struct { ) blsVerifyPOP func(publicKey *stdlib.PublicKey, signature []byte) (bool, error) hash func(data []byte, tag string, algorithm sema.HashAlgorithm) ([]byte, error) - getAccountKey func(address common.Address, index int) (*stdlib.AccountKey, error) - accountKeysCount func(address common.Address) (uint64, error) + getAccountKey func(address common.Address, index uint32) (*stdlib.AccountKey, error) + accountKeysCount func(address common.Address) (uint32, error) emitEvent func( inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, eventType *sema.CompositeType, values []interpreter.Value, - locationRange interpreter.LocationRange, ) addAccountKey func( address common.Address, @@ -47,7 +47,7 @@ type testAccountHandler struct { *stdlib.AccountKey, error, ) - revokeAccountKey func(address common.Address, index int) (*stdlib.AccountKey, error) + revokeAccountKey func(address common.Address, index uint32) (*stdlib.AccountKey, error) getAccountContractCode func(location common.AddressLocation) ([]byte, error) parseAndCheckProgram func( code []byte, @@ -168,14 +168,14 @@ func (t *testAccountHandler) Hash(data []byte, tag string, algorithm sema.HashAl return t.hash(data, tag, algorithm) } -func (t *testAccountHandler) GetAccountKey(address common.Address, index int) (*stdlib.AccountKey, error) { +func (t *testAccountHandler) GetAccountKey(address common.Address, index uint32) (*stdlib.AccountKey, error) { if t.getAccountKey == nil { panic(errors.NewUnexpectedError("unexpected call to GetAccountKey")) } return t.getAccountKey(address, index) } -func (t *testAccountHandler) AccountKeysCount(address common.Address) (uint64, error) { +func (t *testAccountHandler) AccountKeysCount(address common.Address) (uint32, error) { if t.accountKeysCount == nil { panic(errors.NewUnexpectedError("unexpected call to AccountKeysCount")) } @@ -184,18 +184,18 @@ func (t *testAccountHandler) AccountKeysCount(address common.Address) (uint64, e func (t *testAccountHandler) EmitEvent( inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, eventType *sema.CompositeType, values []interpreter.Value, - locationRange interpreter.LocationRange, ) { if t.emitEvent == nil { panic(errors.NewUnexpectedError("unexpected call to EmitEvent")) } t.emitEvent( inter, + locationRange, eventType, values, - locationRange, ) } @@ -219,7 +219,7 @@ func (t *testAccountHandler) AddAccountKey( ) } -func (t *testAccountHandler) RevokeAccountKey(address common.Address, index int) (*stdlib.AccountKey, error) { +func (t *testAccountHandler) RevokeAccountKey(address common.Address, index uint32) (*stdlib.AccountKey, error) { if t.revokeAccountKey == nil { panic(errors.NewUnexpectedError("unexpected call to RevokeAccountKey")) } diff --git a/runtime/bbq/vm/value_composite.go b/runtime/bbq/vm/value_composite.go index 1221e990ea..77a3f4bd28 100644 --- a/runtime/bbq/vm/value_composite.go +++ b/runtime/bbq/vm/value_composite.go @@ -102,7 +102,11 @@ func (v *CompositeValue) StaticType(memoryGauge common.MemoryGauge) StaticType { } func (v *CompositeValue) GetMember(config *Config, name string) Value { - storable, err := v.dictionary.Get( + return v.GetField(config, name) +} + +func (v *CompositeValue) GetField(config *Config, name string) Value { + storedValue, err := v.dictionary.Get( interpreter.StringAtreeValueComparator, interpreter.StringAtreeValueHashInput, interpreter.StringAtreeValue(name), @@ -115,11 +119,7 @@ func (v *CompositeValue) GetMember(config *Config, name string) Value { panic(errors.NewExternalError(err)) } - if storable != nil { - return StoredValue(config.MemoryGauge, storable, config.Storage) - } - - return nil + return MustConvertStoredValue(config.MemoryGauge, config.Storage, storedValue) } func (v *CompositeValue) SetMember(config *Config, name string, value Value) { @@ -150,13 +150,15 @@ func (v *CompositeValue) SetMember(config *Config, name string, value Value) { if existingStorable != nil { inter := config.interpreter() existingValue := interpreter.StoredValue(nil, existingStorable, config.Storage) - existingValue.DeepRemove(inter) + + existingValue.DeepRemove(inter, true) // existingValue is standalone because it was overwritten in parent container. + RemoveReferencedSlab(config.Storage, existingStorable) } } -func (v *CompositeValue) StorageID() atree.StorageID { - return v.dictionary.StorageID() +func (v *CompositeValue) SlabID() atree.SlabID { + return v.dictionary.SlabID() } func (v *CompositeValue) TypeID() common.TypeID { @@ -218,12 +220,9 @@ func (v *CompositeValue) Transfer( // }() //} - currentStorageID := v.StorageID() - currentAddress := currentStorageID.Address - dictionary := v.dictionary - needsStoreTo := address != currentAddress + needsStoreTo := v.NeedsStoreTo(address) isResourceKinded := v.IsResourceKinded() if needsStoreTo && v.Kind == common.CompositeKindContract { @@ -233,7 +232,10 @@ func (v *CompositeValue) Transfer( } if needsStoreTo || !isResourceKinded { - iterator, err := v.dictionary.Iterator() + iterator, err := v.dictionary.Iterator( + interpreter.StringAtreeValueComparator, + interpreter.StringAtreeValueHashInput, + ) if err != nil { panic(errors.NewExternalError(err)) } @@ -438,3 +440,11 @@ func (v *CompositeValue) Destroy(*Config) { // }, //) } + +func (v *CompositeValue) NeedsStoreTo(address atree.Address) bool { + return address != v.StorageAddress() +} + +func (v *CompositeValue) StorageAddress() atree.Address { + return v.dictionary.Address() +} diff --git a/runtime/bbq/vm/value_dictionary.go b/runtime/bbq/vm/value_dictionary.go index e88b96abab..c13afd6864 100644 --- a/runtime/bbq/vm/value_dictionary.go +++ b/runtime/bbq/vm/value_dictionary.go @@ -194,8 +194,8 @@ func (v *DictionaryValue) InsertWithoutTransfer(config *Config, key, value Value return existingValueStorable } -func (v *DictionaryValue) StorageID() atree.StorageID { - return v.dictionary.StorageID() +func (v *DictionaryValue) SlabID() atree.SlabID { + return v.dictionary.SlabID() } func (v *DictionaryValue) IsResourceKinded() bool { @@ -214,20 +214,19 @@ func (v *DictionaryValue) Transfer( remove bool, storable atree.Storable, ) Value { - currentStorageID := v.StorageID() - currentAddress := currentStorageID.Address storage := config.Storage - dictionary := v.dictionary - needsStoreTo := address != currentAddress + needsStoreTo := v.NeedsStoreTo(address) isResourceKinded := v.IsResourceKinded() if needsStoreTo || !isResourceKinded { valueComparator := newValueComparator(config) hashInputProvider := newHashInputProvider(config) - iterator, err := v.dictionary.Iterator() + // Use non-readonly iterator here because iterated + // value can be removed if remove parameter is true. + iterator, err := v.dictionary.Iterator(valueComparator, hashInputProvider) if err != nil { panic(errors.NewExternalError(err)) } @@ -314,6 +313,14 @@ func (v *DictionaryValue) Destroy(*Config) { v.dictionary = nil } +func (v *DictionaryValue) NeedsStoreTo(address atree.Address) bool { + return address != v.StorageAddress() +} + +func (v *DictionaryValue) StorageAddress() atree.Address { + return v.dictionary.Address() +} + func newValueComparator(conf *Config) atree.ValueComparator { return func(storage atree.SlabStorage, atreeValue atree.Value, otherStorable atree.Storable) (bool, error) { inter := conf.interpreter() From 359435d8613ed8f588f117a7b3831cf81980188c Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 2 Aug 2024 15:00:34 -0700 Subject: [PATCH 45/89] Move vm tests to a dedicated directory --- runtime/bbq/vm/{ => test}/ft_test.go | 91 ++-- .../bbq/vm/{test_utils.go => test/utils.go} | 34 +- runtime/bbq/vm/{ => test}/vm_test.go | 447 +++++++++--------- runtime/bbq/vm/vm.go | 4 + 4 files changed, 305 insertions(+), 271 deletions(-) rename runtime/bbq/vm/{ => test}/ft_test.go (93%) rename runtime/bbq/vm/{test_utils.go => test/utils.go} (91%) rename runtime/bbq/vm/{ => test}/vm_test.go (77%) diff --git a/runtime/bbq/vm/ft_test.go b/runtime/bbq/vm/test/ft_test.go similarity index 93% rename from runtime/bbq/vm/ft_test.go rename to runtime/bbq/vm/test/ft_test.go index c263d727d6..163cd47146 100644 --- a/runtime/bbq/vm/ft_test.go +++ b/runtime/bbq/vm/test/ft_test.go @@ -16,12 +16,13 @@ * limitations under the License. */ -package vm +package test import ( "fmt" "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/bbq/vm" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/stdlib" @@ -87,15 +88,15 @@ func TestFTTransfer(t *testing.T) { flowTokenProgram := flowTokenCompiler.Compile() printProgram(flowTokenProgram) - flowTokenVM := NewVM( + flowTokenVM := vm.NewVM( flowTokenProgram, - &Config{ + &vm.Config{ Storage: storage, AccountHandler: &testAccountHandler{}, }, ) - authAccount := NewAuthAccountReferenceValue(contractsAddress) + authAccount := vm.NewAuthAccountReferenceValue(contractsAddress) flowTokenContractValue, err := flowTokenVM.InitializeContract(authAccount) require.NoError(t, err) @@ -133,7 +134,7 @@ func TestFTTransfer(t *testing.T) { } } - vmConfig := &Config{ + vmConfig := &vm.Config{ Storage: storage, ImportHandler: func(location common.Location) *bbq.Program { switch location { @@ -146,7 +147,7 @@ func TestFTTransfer(t *testing.T) { return nil } }, - ContractValueHandler: func(_ *Config, location common.Location) *CompositeValue { + ContractValueHandler: func(_ *vm.Config, location common.Location) *vm.CompositeValue { switch location { case ftLocation: // interface @@ -201,12 +202,12 @@ func TestFTTransfer(t *testing.T) { program := setupTxCompiler.Compile() printProgram(program) - setupTxVM := NewVM(program, vmConfig) + setupTxVM := vm.NewVM(program, vmConfig) - authorizer := NewAuthAccountReferenceValue(address) + authorizer := vm.NewAuthAccountReferenceValue(address) err = setupTxVM.ExecuteTransaction(nil, authorizer) require.NoError(t, err) - require.Empty(t, setupTxVM.stack) + require.Equal(t, 0, setupTxVM.StackSize()) } // Mint FLOW to sender @@ -231,19 +232,19 @@ func TestFTTransfer(t *testing.T) { program := mintTxCompiler.Compile() printProgram(program) - mintTxVM := NewVM(program, vmConfig) + mintTxVM := vm.NewVM(program, vmConfig) total := int64(1000000) - mintTxArgs := []Value{ - AddressValue(senderAddress), - IntValue{total}, + mintTxArgs := []vm.Value{ + vm.AddressValue(senderAddress), + vm.IntValue{total}, } - mintTxAuthorizer := NewAuthAccountReferenceValue(contractsAddress) + mintTxAuthorizer := vm.NewAuthAccountReferenceValue(contractsAddress) err = mintTxVM.ExecuteTransaction(mintTxArgs, mintTxAuthorizer) require.NoError(t, err) - require.Empty(t, mintTxVM.stack) + require.Equal(t, 0, mintTxVM.StackSize()) // ----- Run token transfer transaction ----- @@ -267,19 +268,19 @@ func TestFTTransfer(t *testing.T) { tokenTransferTxProgram := tokenTransferTxCompiler.Compile() printProgram(tokenTransferTxProgram) - tokenTransferTxVM := NewVM(tokenTransferTxProgram, vmConfig) + tokenTransferTxVM := vm.NewVM(tokenTransferTxProgram, vmConfig) transferAmount := int64(1) - tokenTransferTxArgs := []Value{ - IntValue{transferAmount}, - AddressValue(receiverAddress), + tokenTransferTxArgs := []vm.Value{ + vm.IntValue{transferAmount}, + vm.AddressValue(receiverAddress), } - tokenTransferTxAuthorizer := NewAuthAccountReferenceValue(senderAddress) + tokenTransferTxAuthorizer := vm.NewAuthAccountReferenceValue(senderAddress) err = tokenTransferTxVM.ExecuteTransaction(tokenTransferTxArgs, tokenTransferTxAuthorizer) require.NoError(t, err) - require.Empty(t, tokenTransferTxVM.stack) + require.Equal(t, 0, tokenTransferTxVM.StackSize()) // Run validation scripts @@ -307,17 +308,17 @@ func TestFTTransfer(t *testing.T) { program := validationScriptCompiler.Compile() printProgram(program) - validationScriptVM := NewVM(program, vmConfig) + validationScriptVM := vm.NewVM(program, vmConfig) - addressValue := AddressValue(address) + addressValue := vm.AddressValue(address) result, err := validationScriptVM.Invoke("main", addressValue) require.NoError(t, err) - require.Empty(t, validationScriptVM.stack) + require.Equal(t, 0, validationScriptVM.StackSize()) if address == senderAddress { - assert.Equal(t, IntValue{total - transferAmount}, result) + assert.Equal(t, vm.IntValue{total - transferAmount}, result) } else { - assert.Equal(t, IntValue{transferAmount}, result) + assert.Equal(t, vm.IntValue{transferAmount}, result) } } } @@ -826,15 +827,15 @@ func BenchmarkFTTransfer(b *testing.B) { flowTokenProgram := flowTokenCompiler.Compile() - flowTokenVM := NewVM( + flowTokenVM := vm.NewVM( flowTokenProgram, - &Config{ + &vm.Config{ Storage: storage, AccountHandler: &testAccountHandler{}, }, ) - authAccount := NewAuthAccountReferenceValue(contractsAddress) + authAccount := vm.NewAuthAccountReferenceValue(contractsAddress) flowTokenContractValue, err := flowTokenVM.InitializeContract(authAccount) require.NoError(b, err) @@ -872,7 +873,7 @@ func BenchmarkFTTransfer(b *testing.B) { } } - vmConfig := &Config{ + vmConfig := &vm.Config{ Storage: storage, ImportHandler: func(location common.Location) *bbq.Program { switch location { @@ -885,7 +886,7 @@ func BenchmarkFTTransfer(b *testing.B) { return nil } }, - ContractValueHandler: func(_ *Config, location common.Location) *CompositeValue { + ContractValueHandler: func(_ *vm.Config, location common.Location) *vm.CompositeValue { switch location { case ftLocation: // interface @@ -939,12 +940,12 @@ func BenchmarkFTTransfer(b *testing.B) { program := setupTxCompiler.Compile() - setupTxVM := NewVM(program, vmConfig) + setupTxVM := vm.NewVM(program, vmConfig) - authorizer := NewAuthAccountReferenceValue(address) + authorizer := vm.NewAuthAccountReferenceValue(address) err = setupTxVM.ExecuteTransaction(nil, authorizer) require.NoError(b, err) - require.Empty(b, setupTxVM.stack) + require.Equal(b, 0, setupTxVM.StackSize()) } // Mint FLOW to sender @@ -968,19 +969,19 @@ func BenchmarkFTTransfer(b *testing.B) { program := mintTxCompiler.Compile() - mintTxVM := NewVM(program, vmConfig) + mintTxVM := vm.NewVM(program, vmConfig) total := int64(1000000) - mintTxArgs := []Value{ - AddressValue(senderAddress), - IntValue{total}, + mintTxArgs := []vm.Value{ + vm.AddressValue(senderAddress), + vm.IntValue{total}, } - mintTxAuthorizer := NewAuthAccountReferenceValue(contractsAddress) + mintTxAuthorizer := vm.NewAuthAccountReferenceValue(contractsAddress) err = mintTxVM.ExecuteTransaction(mintTxArgs, mintTxAuthorizer) require.NoError(b, err) - require.Empty(b, mintTxVM.stack) + require.Equal(b, 0, mintTxVM.StackSize()) // ----- Run token transfer transaction ----- @@ -999,12 +1000,12 @@ func BenchmarkFTTransfer(b *testing.B) { transferAmount := int64(1) - tokenTransferTxArgs := []Value{ - IntValue{transferAmount}, - AddressValue(receiverAddress), + tokenTransferTxArgs := []vm.Value{ + vm.IntValue{transferAmount}, + vm.AddressValue(receiverAddress), } - tokenTransferTxAuthorizer := NewAuthAccountReferenceValue(senderAddress) + tokenTransferTxAuthorizer := vm.NewAuthAccountReferenceValue(senderAddress) b.ReportAllocs() b.ResetTimer() @@ -1016,7 +1017,7 @@ func BenchmarkFTTransfer(b *testing.B) { tokenTransferTxProgram := tokenTransferTxCompiler.Compile() - tokenTransferTxVM := NewVM(tokenTransferTxProgram, vmConfig) + tokenTransferTxVM := vm.NewVM(tokenTransferTxProgram, vmConfig) err = tokenTransferTxVM.ExecuteTransaction(tokenTransferTxArgs, tokenTransferTxAuthorizer) require.NoError(b, err) } diff --git a/runtime/bbq/vm/test_utils.go b/runtime/bbq/vm/test/utils.go similarity index 91% rename from runtime/bbq/vm/test_utils.go rename to runtime/bbq/vm/test/utils.go index 9edcf13cab..2a9e3db980 100644 --- a/runtime/bbq/vm/test_utils.go +++ b/runtime/bbq/vm/test/utils.go @@ -1,11 +1,18 @@ -package vm +package test import ( + "fmt" + "testing" + + "github.com/onflow/cadence/runtime/ast" + "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/bbq/commons" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" + "github.com/stretchr/testify/require" ) type testAccountHandler struct { @@ -328,3 +335,28 @@ func (t *testAccountHandler) IsContractBeingAdded(common.AddressLocation) bool { // NO-OP return false } + +func singleIdentifierLocationResolver(t testing.TB) func( + identifiers []ast.Identifier, + location common.Location, +) ([]commons.ResolvedLocation, error) { + return func(identifiers []ast.Identifier, location common.Location) ([]commons.ResolvedLocation, error) { + require.Len(t, identifiers, 1) + require.IsType(t, common.AddressLocation{}, location) + + return []commons.ResolvedLocation{ + { + Location: common.AddressLocation{ + Address: location.(common.AddressLocation).Address, + Name: identifiers[0].Identifier, + }, + Identifiers: identifiers, + }, + }, nil + } +} + +func printProgram(program *bbq.Program) { + byteCodePrinter := &bbq.BytecodePrinter{} + fmt.Println(byteCodePrinter.PrintProgram(program)) +} diff --git a/runtime/bbq/vm/vm_test.go b/runtime/bbq/vm/test/vm_test.go similarity index 77% rename from runtime/bbq/vm/vm_test.go rename to runtime/bbq/vm/test/vm_test.go index e88af08b28..0130ea7ea9 100644 --- a/runtime/bbq/vm/vm_test.go +++ b/runtime/bbq/vm/test/vm_test.go @@ -16,11 +16,10 @@ * limitations under the License. */ -package vm +package test import ( "fmt" - "github.com/onflow/cadence/runtime/interpreter" "testing" "github.com/stretchr/testify/assert" @@ -29,7 +28,9 @@ import ( "github.com/onflow/atree" "github.com/onflow/cadence/runtime/ast" + "github.com/onflow/cadence/runtime/bbq/vm" "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" . "github.com/onflow/cadence/runtime/tests/checker" @@ -59,15 +60,16 @@ func TestRecursionFib(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) - result, err := vm.Invoke( + result, err := vmInstance.Invoke( "fib", - IntValue{SmallInt: 7}, + vm.IntValue{SmallInt: 7}, ) require.NoError(t, err) - require.Equal(t, IntValue{SmallInt: 13}, result) - require.Empty(t, vm.stack) + require.Equal(t, vm.IntValue{SmallInt: 13}, result) + require.Equal(t, 0, vmInstance.StackSize()) } func BenchmarkRecursionFib(b *testing.B) { @@ -78,18 +80,19 @@ func BenchmarkRecursionFib(b *testing.B) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) b.ReportAllocs() b.ResetTimer() - expected := IntValue{SmallInt: 377} + expected := vm.IntValue{SmallInt: 377} for i := 0; i < b.N; i++ { - result, err := vm.Invoke( + result, err := vmInstance.Invoke( "fib", - IntValue{SmallInt: 14}, + vm.IntValue{SmallInt: 14}, ) require.NoError(b, err) require.Equal(b, expected, result) @@ -122,15 +125,16 @@ func TestImperativeFib(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) - result, err := vm.Invoke( + result, err := vmInstance.Invoke( "fib", - IntValue{SmallInt: 7}, + vm.IntValue{SmallInt: 7}, ) require.NoError(t, err) - require.Equal(t, IntValue{SmallInt: 13}, result) - require.Empty(t, vm.stack) + require.Equal(t, vm.IntValue{SmallInt: 13}, result) + require.Equal(t, 0, vmInstance.StackSize()) } func BenchmarkImperativeFib(b *testing.B) { @@ -141,15 +145,16 @@ func BenchmarkImperativeFib(b *testing.B) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) b.ReportAllocs() b.ResetTimer() - var value Value = IntValue{SmallInt: 14} + var value vm.Value = vm.IntValue{SmallInt: 14} for i := 0; i < b.N; i++ { - _, err := vm.Invoke("fib", value) + _, err := vmInstance.Invoke("fib", value) require.NoError(b, err) } } @@ -175,13 +180,14 @@ func TestBreak(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) - result, err := vm.Invoke("test") + result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Equal(t, IntValue{SmallInt: 4}, result) - require.Empty(t, vm.stack) + require.Equal(t, vm.IntValue{SmallInt: 4}, result) + require.Equal(t, 0, vmInstance.StackSize()) } func TestContinue(t *testing.T) { @@ -206,13 +212,14 @@ func TestContinue(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) - result, err := vm.Invoke("test") + result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Equal(t, IntValue{SmallInt: 3}, result) - require.Empty(t, vm.stack) + require.Equal(t, vm.IntValue{SmallInt: 3}, result) + require.Equal(t, 0, vmInstance.StackSize()) } func TestNilCoalesce(t *testing.T) { @@ -234,13 +241,14 @@ func TestNilCoalesce(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) - result, err := vm.Invoke("test") + result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Equal(t, IntValue{SmallInt: 2}, result) - require.Empty(t, vm.stack) + require.Equal(t, vm.IntValue{SmallInt: 2}, result) + require.Equal(t, 0, vmInstance.StackSize()) }) t.Run("false", func(t *testing.T) { @@ -259,13 +267,14 @@ func TestNilCoalesce(t *testing.T) { program := comp.Compile() printProgram(program) - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) - result, err := vm.Invoke("test") + result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Equal(t, IntValue{SmallInt: 3}, result) - require.Empty(t, vm.stack) + require.Equal(t, vm.IntValue{SmallInt: 3}, result) + require.Equal(t, 0, vmInstance.StackSize()) }) } @@ -298,20 +307,21 @@ func TestNewStruct(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) - result, err := vm.Invoke("test", IntValue{SmallInt: 10}) + result, err := vmInstance.Invoke("test", vm.IntValue{SmallInt: 10}) require.NoError(t, err) - require.Empty(t, vm.stack) + require.Equal(t, 0, vmInstance.StackSize()) - require.IsType(t, &CompositeValue{}, result) - structValue := result.(*CompositeValue) + require.IsType(t, &vm.CompositeValue{}, result) + structValue := result.(*vm.CompositeValue) require.Equal(t, "Foo", structValue.QualifiedIdentifier) require.Equal( t, - IntValue{SmallInt: 12}, - structValue.GetMember(vm.config, "id"), + vm.IntValue{SmallInt: 12}, + structValue.GetMember(vmConfig, "id"), ) } @@ -342,13 +352,14 @@ func TestStructMethodCall(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) - result, err := vm.Invoke("test") + result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Empty(t, vm.stack) + require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, StringValue{Str: []byte("Hello from Foo!")}, result) + require.Equal(t, vm.StringValue{Str: []byte("Hello from Foo!")}, result) } func BenchmarkNewStruct(b *testing.B) { @@ -374,7 +385,7 @@ func BenchmarkNewStruct(b *testing.B) { `) require.NoError(b, err) - value := IntValue{SmallInt: 1} + value := vm.IntValue{SmallInt: 1} b.ReportAllocs() b.ResetTimer() @@ -382,10 +393,11 @@ func BenchmarkNewStruct(b *testing.B) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) for i := 0; i < b.N; i++ { - _, err := vm.Invoke("test", value) + _, err := vmInstance.Invoke("test", value) require.NoError(b, err) } } @@ -416,14 +428,15 @@ func BenchmarkNewResource(b *testing.B) { b.ReportAllocs() b.ResetTimer() - value := IntValue{SmallInt: 9} + value := vm.IntValue{SmallInt: 9} for i := 0; i < b.N; i++ { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program, nil) - _, err := vm.Invoke("test", value) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) + _, err := vmInstance.Invoke("test", value) require.NoError(b, err) } } @@ -431,34 +444,29 @@ func BenchmarkNewResource(b *testing.B) { func BenchmarkNewStructRaw(b *testing.B) { storage := interpreter.NewInMemoryStorage(nil) - conf := &Config{ + vmConfig := &vm.Config{ Storage: storage, } - fieldValue := IntValue{SmallInt: 7} + fieldValue := vm.IntValue{SmallInt: 7} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { for j := 0; j < 1; j++ { - structValue := NewCompositeValue( + structValue := vm.NewCompositeValue( nil, "Foo", common.CompositeKindStructure, common.Address{}, storage.BasicSlabStorage, ) - structValue.SetMember(conf, "id", fieldValue) - structValue.Transfer(conf, atree.Address{}, false, nil) + structValue.SetMember(vmConfig, "id", fieldValue) + structValue.Transfer(vmConfig, atree.Address{}, false, nil) } } } -func printProgram(program *bbq.Program) { - byteCodePrinter := &bbq.BytecodePrinter{} - fmt.Println(byteCodePrinter.PrintProgram(program)) -} - func TestImport(t *testing.T) { t.Parallel() @@ -519,19 +527,19 @@ func TestImport(t *testing.T) { program := importCompiler.Compile() - vmConfig := &Config{ + vmConfig := &vm.Config{ ImportHandler: func(location common.Location) *bbq.Program { return importedProgram }, } - vm := NewVM(program, vmConfig) + vmInstance := vm.NewVM(program, vmConfig) - result, err := vm.Invoke("test") + result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Empty(t, vm.stack) + require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, StringValue{Str: []byte("global function of the imported program")}, result) + require.Equal(t, vm.StringValue{Str: []byte("global function of the imported program")}, result) } func TestContractImport(t *testing.T) { @@ -573,8 +581,8 @@ func TestContractImport(t *testing.T) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() - vm := NewVM(importedProgram, nil) - importedContractValue, err := vm.InitializeContract() + vmInstance := vm.NewVM(importedProgram, nil) + importedContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) checker, err := ParseAndCheckWithOptions(t, ` @@ -604,22 +612,22 @@ func TestContractImport(t *testing.T) { program := comp.Compile() - vmConfig := &Config{ + vmConfig := &vm.Config{ ImportHandler: func(location common.Location) *bbq.Program { return importedProgram }, - ContractValueHandler: func(*Config, common.Location) *CompositeValue { + ContractValueHandler: func(*vm.Config, common.Location) *vm.CompositeValue { return importedContractValue }, } - vm = NewVM(program, vmConfig) + vmInstance = vm.NewVM(program, vmConfig) - result, err := vm.Invoke("test") + result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Empty(t, vm.stack) + require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, StringValue{Str: []byte("global function of the imported program")}, result) + require.Equal(t, vm.StringValue{Str: []byte("global function of the imported program")}, result) }) t.Run("contract function", func(t *testing.T) { @@ -648,8 +656,8 @@ func TestContractImport(t *testing.T) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() - vm := NewVM(importedProgram, nil) - importedContractValue, err := vm.InitializeContract() + vmInstance := vm.NewVM(importedProgram, nil) + importedContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) checker, err := ParseAndCheckWithOptions(t, ` @@ -678,22 +686,22 @@ func TestContractImport(t *testing.T) { program := comp.Compile() - vmConfig := &Config{ + vmConfig := &vm.Config{ ImportHandler: func(location common.Location) *bbq.Program { return importedProgram }, - ContractValueHandler: func(*Config, common.Location) *CompositeValue { + ContractValueHandler: func(*vm.Config, common.Location) *vm.CompositeValue { return importedContractValue }, } - vm = NewVM(program, vmConfig) + vmInstance = vm.NewVM(program, vmConfig) - result, err := vm.Invoke("test") + result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Empty(t, vm.stack) + require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, StringValue{Str: []byte("contract function of the imported program")}, result) + require.Equal(t, vm.StringValue{Str: []byte("contract function of the imported program")}, result) }) t.Run("nested imports", func(t *testing.T) { @@ -726,8 +734,8 @@ func TestContractImport(t *testing.T) { fooCompiler := compiler.NewCompiler(fooChecker.Program, fooChecker.Elaboration) fooProgram := fooCompiler.Compile() - vm := NewVM(fooProgram, nil) - fooContractValue, err := vm.InitializeContract() + vmInstance := vm.NewVM(fooProgram, nil) + fooContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) // Initialize Bar @@ -771,19 +779,19 @@ func TestContractImport(t *testing.T) { barProgram := barCompiler.Compile() - vmConfig := &Config{ + vmConfig := &vm.Config{ ImportHandler: func(location common.Location) *bbq.Program { require.Equal(t, fooLocation, location) return fooProgram }, - ContractValueHandler: func(_ *Config, location common.Location) *CompositeValue { + ContractValueHandler: func(_ *vm.Config, location common.Location) *vm.CompositeValue { require.Equal(t, fooLocation, location) return fooContractValue }, } - vm = NewVM(barProgram, vmConfig) - barContractValue, err := vm.InitializeContract() + vmInstance = vm.NewVM(barProgram, vmConfig) + barContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) // Compile and run main program @@ -835,7 +843,7 @@ func TestContractImport(t *testing.T) { program := comp.Compile() - vmConfig = &Config{ + vmConfig = &vm.Config{ ImportHandler: func(location common.Location) *bbq.Program { switch location { case fooLocation: @@ -847,7 +855,7 @@ func TestContractImport(t *testing.T) { return nil } }, - ContractValueHandler: func(_ *Config, location common.Location) *CompositeValue { + ContractValueHandler: func(_ *vm.Config, location common.Location) *vm.CompositeValue { switch location { case fooLocation: return fooContractValue @@ -860,13 +868,13 @@ func TestContractImport(t *testing.T) { }, } - vm = NewVM(program, vmConfig) + vmInstance = vm.NewVM(program, vmConfig) - result, err := vm.Invoke("test") + result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Empty(t, vm.stack) + require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, StringValue{Str: []byte("Hello from Foo!")}, result) + require.Equal(t, vm.StringValue{Str: []byte("Hello from Foo!")}, result) }) t.Run("contract interface", func(t *testing.T) { @@ -897,8 +905,8 @@ func TestContractImport(t *testing.T) { fooCompiler := compiler.NewCompiler(fooChecker.Program, fooChecker.Elaboration) fooProgram := fooCompiler.Compile() - //vm := NewVM(fooProgram, nil) - //fooContractValue, err := vm.InitializeContract() + //vmInstance := NewVM(fooProgram, nil) + //fooContractValue, err := vmInstance.InitializeContract() //require.NoError(t, err) // Initialize Bar @@ -942,7 +950,7 @@ func TestContractImport(t *testing.T) { barProgram := barCompiler.Compile() - vmConfig := &Config{ + vmConfig := &vm.Config{ ImportHandler: func(location common.Location) *bbq.Program { require.Equal(t, fooLocation, location) return fooProgram @@ -953,8 +961,8 @@ func TestContractImport(t *testing.T) { //}, } - vm := NewVM(barProgram, vmConfig) - barContractValue, err := vm.InitializeContract() + vmInstance := vm.NewVM(barProgram, vmConfig) + barContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) // Compile and run main program @@ -1006,7 +1014,7 @@ func TestContractImport(t *testing.T) { program := comp.Compile() - vmConfig = &Config{ + vmConfig = &vm.Config{ ImportHandler: func(location common.Location) *bbq.Program { switch location { case fooLocation: @@ -1018,7 +1026,7 @@ func TestContractImport(t *testing.T) { return nil } }, - ContractValueHandler: func(_ *Config, location common.Location) *CompositeValue { + ContractValueHandler: func(_ *vm.Config, location common.Location) *vm.CompositeValue { switch location { //case fooLocation: // return fooContractValue @@ -1031,13 +1039,13 @@ func TestContractImport(t *testing.T) { }, } - vm = NewVM(program, vmConfig) + vmInstance = vm.NewVM(program, vmConfig) - result, err := vm.Invoke("test") + result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Empty(t, vm.stack) + require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, StringValue{Str: []byte("Successfully withdrew")}, result) + require.Equal(t, vm.StringValue{Str: []byte("Successfully withdrew")}, result) }) } @@ -1079,15 +1087,15 @@ func BenchmarkContractImport(b *testing.B) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() - vm := NewVM(importedProgram, nil) - importedContractValue, err := vm.InitializeContract() + vmInstance := vm.NewVM(importedProgram, nil) + importedContractValue, err := vmInstance.InitializeContract() require.NoError(b, err) - vmConfig := &Config{ + vmConfig := &vm.Config{ ImportHandler: func(location common.Location) *bbq.Program { return importedProgram }, - ContractValueHandler: func(conf *Config, location common.Location) *CompositeValue { + ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { return importedContractValue }, } @@ -1095,7 +1103,7 @@ func BenchmarkContractImport(b *testing.B) { b.ResetTimer() b.ReportAllocs() - value := IntValue{SmallInt: 7} + value := vm.IntValue{SmallInt: 7} for i := 0; i < b.N; i++ { checker, err := ParseAndCheckWithOptions(b, ` @@ -1130,8 +1138,8 @@ func BenchmarkContractImport(b *testing.B) { } program := comp.Compile() - vm := NewVM(program, vmConfig) - _, err = vm.Invoke("test", value) + vmInstance := vm.NewVM(program, vmConfig) + _, err = vmInstance.Invoke("test", value) require.NoError(b, err) } } @@ -1156,12 +1164,13 @@ func TestInitializeContract(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program, nil) - contractValue, err := vm.InitializeContract() + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) + contractValue, err := vmInstance.InitializeContract() require.NoError(t, err) - fieldValue := contractValue.GetMember(vm.config, "status") - assert.Equal(t, StringValue{Str: []byte("PENDING")}, fieldValue) + fieldValue := contractValue.GetMember(vmConfig, "status") + assert.Equal(t, vm.StringValue{Str: []byte("PENDING")}, fieldValue) } func TestContractAccessDuringInit(t *testing.T) { @@ -1192,12 +1201,13 @@ func TestContractAccessDuringInit(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program, nil) - contractValue, err := vm.InitializeContract() + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) + contractValue, err := vmInstance.InitializeContract() require.NoError(t, err) - fieldValue := contractValue.GetMember(vm.config, "status") - assert.Equal(t, StringValue{Str: []byte("PENDING")}, fieldValue) + fieldValue := contractValue.GetMember(vmConfig, "status") + assert.Equal(t, vm.StringValue{Str: []byte("PENDING")}, fieldValue) }) t.Run("using self", func(t *testing.T) { @@ -1224,12 +1234,13 @@ func TestContractAccessDuringInit(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program, nil) - contractValue, err := vm.InitializeContract() + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) + contractValue, err := vmInstance.InitializeContract() require.NoError(t, err) - fieldValue := contractValue.GetMember(vm.config, "status") - assert.Equal(t, StringValue{Str: []byte("PENDING")}, fieldValue) + fieldValue := contractValue.GetMember(vmConfig, "status") + assert.Equal(t, vm.StringValue{Str: []byte("PENDING")}, fieldValue) }) } @@ -1257,13 +1268,14 @@ func TestFunctionOrder(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) - result, err := vm.Invoke("test") + result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Empty(t, vm.stack) + require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, IntValue{SmallInt: 5}, result) + require.Equal(t, vm.IntValue{SmallInt: 5}, result) }) t.Run("nested", func(t *testing.T) { @@ -1312,13 +1324,14 @@ func TestFunctionOrder(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) - result, err := vm.Invoke("init") + result, err := vmInstance.Invoke("init") require.NoError(t, err) - require.Empty(t, vm.stack) + require.Equal(t, 0, vmInstance.StackSize()) - require.IsType(t, &CompositeValue{}, result) + require.IsType(t, &vm.CompositeValue{}, result) }) } @@ -1347,8 +1360,8 @@ func TestContractField(t *testing.T) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() - vm := NewVM(importedProgram, nil) - importedContractValue, err := vm.InitializeContract() + vmInstance := vm.NewVM(importedProgram, nil) + importedContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) checker, err := ParseAndCheckWithOptions(t, ` @@ -1377,21 +1390,21 @@ func TestContractField(t *testing.T) { program := comp.Compile() - vmConfig := &Config{ + vmConfig := &vm.Config{ ImportHandler: func(location common.Location) *bbq.Program { return importedProgram }, - ContractValueHandler: func(conf *Config, location common.Location) *CompositeValue { + ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { return importedContractValue }, } - vm = NewVM(program, vmConfig) - result, err := vm.Invoke("test") + vmInstance = vm.NewVM(program, vmConfig) + result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Empty(t, vm.stack) + require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, StringValue{Str: []byte("PENDING")}, result) + require.Equal(t, vm.StringValue{Str: []byte("PENDING")}, result) }) t.Run("set", func(t *testing.T) { @@ -1415,8 +1428,8 @@ func TestContractField(t *testing.T) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() - vm := NewVM(importedProgram, nil) - importedContractValue, err := vm.InitializeContract() + vmInstance := vm.NewVM(importedProgram, nil) + importedContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) checker, err := ParseAndCheckWithOptions(t, ` @@ -1446,48 +1459,28 @@ func TestContractField(t *testing.T) { program := comp.Compile() - vmConfig := &Config{ + vmConfig := &vm.Config{ ImportHandler: func(location common.Location) *bbq.Program { return importedProgram }, - ContractValueHandler: func(conf *Config, location common.Location) *CompositeValue { + ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { return importedContractValue }, } - vm = NewVM(program, vmConfig) + vmInstance = vm.NewVM(program, vmConfig) - result, err := vm.Invoke("test") + result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Empty(t, vm.stack) + require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, StringValue{Str: []byte("UPDATED")}, result) + require.Equal(t, vm.StringValue{Str: []byte("UPDATED")}, result) - fieldValue := importedContractValue.GetMember(vm.config, "status") - assert.Equal(t, StringValue{Str: []byte("UPDATED")}, fieldValue) + fieldValue := importedContractValue.GetMember(vmConfig, "status") + assert.Equal(t, vm.StringValue{Str: []byte("UPDATED")}, fieldValue) }) } -func singleIdentifierLocationResolver(t testing.TB) func( - identifiers []ast.Identifier, - location common.Location, -) ([]commons.ResolvedLocation, error) { - return func(identifiers []ast.Identifier, location common.Location) ([]commons.ResolvedLocation, error) { - require.Len(t, identifiers, 1) - require.IsType(t, common.AddressLocation{}, location) - - return []commons.ResolvedLocation{ - { - Location: common.AddressLocation{ - Address: location.(common.AddressLocation).Address, - Name: identifiers[0].Identifier, - }, - Identifiers: identifiers, - }, - }, nil - } -} - func TestNativeFunctions(t *testing.T) { t.Parallel() @@ -1533,11 +1526,12 @@ func TestNativeFunctions(t *testing.T) { program := comp.Compile() printProgram(program) - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) - _, err = vm.Invoke("test") + _, err = vmInstance.Invoke("test") require.NoError(t, err) - require.Empty(t, vm.stack) + require.Equal(t, 0, vmInstance.StackSize()) }) t.Run("bound function", func(t *testing.T) { @@ -1552,13 +1546,14 @@ func TestNativeFunctions(t *testing.T) { program := comp.Compile() printProgram(program) - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) - result, err := vm.Invoke("test") + result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Empty(t, vm.stack) + require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, StringValue{Str: []byte("Hello, World!")}, result) + require.Equal(t, vm.StringValue{Str: []byte("Hello, World!")}, result) }) } @@ -1585,36 +1580,37 @@ func TestTransaction(t *testing.T) { program := comp.Compile() printProgram(program) - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) - err = vm.ExecuteTransaction(nil) + err = vmInstance.ExecuteTransaction(nil) require.NoError(t, err) - require.Empty(t, vm.stack) + require.Equal(t, 0, vmInstance.StackSize()) // Rerun the same again using internal functions, to get the access to the transaction value. - transaction, err := vm.Invoke(commons.TransactionWrapperCompositeName) + transaction, err := vmInstance.Invoke(commons.TransactionWrapperCompositeName) require.NoError(t, err) - require.IsType(t, &CompositeValue{}, transaction) - compositeValue := transaction.(*CompositeValue) + require.IsType(t, &vm.CompositeValue{}, transaction) + compositeValue := transaction.(*vm.CompositeValue) // At the beginning, 'a' is uninitialized - assert.Nil(t, compositeValue.GetMember(vm.config, "a")) + assert.Nil(t, compositeValue.GetMember(vmConfig, "a")) // Invoke 'prepare' - _, err = vm.Invoke(commons.TransactionPrepareFunctionName, transaction) + _, err = vmInstance.Invoke(commons.TransactionPrepareFunctionName, transaction) require.NoError(t, err) // Once 'prepare' is called, 'a' is initialized to "Hello!" - assert.Equal(t, StringValue{Str: []byte("Hello!")}, compositeValue.GetMember(vm.config, "a")) + assert.Equal(t, vm.StringValue{Str: []byte("Hello!")}, compositeValue.GetMember(vmConfig, "a")) // Invoke 'execute' - _, err = vm.Invoke(commons.TransactionExecuteFunctionName, transaction) + _, err = vmInstance.Invoke(commons.TransactionExecuteFunctionName, transaction) require.NoError(t, err) // Once 'execute' is called, 'a' is initialized to "Hello, again!" - assert.Equal(t, StringValue{Str: []byte("Hello again!")}, compositeValue.GetMember(vm.config, "a")) + assert.Equal(t, vm.StringValue{Str: []byte("Hello again!")}, compositeValue.GetMember(vmConfig, "a")) }) t.Run("with params", func(t *testing.T) { @@ -1636,41 +1632,42 @@ func TestTransaction(t *testing.T) { program := comp.Compile() printProgram(program) - vm := NewVM(program, nil) + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) - args := []Value{ - StringValue{[]byte("Hello!")}, - StringValue{[]byte("Hello again!")}, + args := []vm.Value{ + vm.StringValue{[]byte("Hello!")}, + vm.StringValue{[]byte("Hello again!")}, } - err = vm.ExecuteTransaction(args) + err = vmInstance.ExecuteTransaction(args) require.NoError(t, err) - require.Empty(t, vm.stack) + require.Equal(t, 0, vmInstance.StackSize()) // Rerun the same again using internal functions, to get the access to the transaction value. - transaction, err := vm.Invoke(commons.TransactionWrapperCompositeName) + transaction, err := vmInstance.Invoke(commons.TransactionWrapperCompositeName) require.NoError(t, err) - require.IsType(t, &CompositeValue{}, transaction) - compositeValue := transaction.(*CompositeValue) + require.IsType(t, &vm.CompositeValue{}, transaction) + compositeValue := transaction.(*vm.CompositeValue) // At the beginning, 'a' is uninitialized - assert.Nil(t, compositeValue.GetMember(vm.config, "a")) + assert.Nil(t, compositeValue.GetMember(vmConfig, "a")) // Invoke 'prepare' - _, err = vm.Invoke(commons.TransactionPrepareFunctionName, transaction) + _, err = vmInstance.Invoke(commons.TransactionPrepareFunctionName, transaction) require.NoError(t, err) // Once 'prepare' is called, 'a' is initialized to "Hello!" - assert.Equal(t, StringValue{Str: []byte("Hello!")}, compositeValue.GetMember(vm.config, "a")) + assert.Equal(t, vm.StringValue{Str: []byte("Hello!")}, compositeValue.GetMember(vmConfig, "a")) // Invoke 'execute' - _, err = vm.Invoke(commons.TransactionExecuteFunctionName, transaction) + _, err = vmInstance.Invoke(commons.TransactionExecuteFunctionName, transaction) require.NoError(t, err) // Once 'execute' is called, 'a' is initialized to "Hello, again!" - assert.Equal(t, StringValue{Str: []byte("Hello again!")}, compositeValue.GetMember(vm.config, "a")) + assert.Equal(t, vm.StringValue{Str: []byte("Hello again!")}, compositeValue.GetMember(vmConfig, "a")) }) } @@ -1716,8 +1713,8 @@ func TestInterfaceMethodCall(t *testing.T) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() - vm := NewVM(importedProgram, nil) - importedContractValue, err := vm.InitializeContract() + vmInstance := vm.NewVM(importedProgram, nil) + importedContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) checker, err := ParseAndCheckWithOptions(t, ` @@ -1753,21 +1750,21 @@ func TestInterfaceMethodCall(t *testing.T) { program := comp.Compile() - vmConfig := &Config{ + vmConfig := &vm.Config{ ImportHandler: func(location common.Location) *bbq.Program { return importedProgram }, - ContractValueHandler: func(conf *Config, location common.Location) *CompositeValue { + ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { return importedContractValue }, } - vm = NewVM(program, vmConfig) - result, err := vm.Invoke("test") + vmInstance = vm.NewVM(program, vmConfig) + result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Empty(t, vm.stack) + require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, StringValue{Str: []byte("Hello from Foo!")}, result) + require.Equal(t, vm.StringValue{Str: []byte("Hello from Foo!")}, result) } func BenchmarkMethodCall(b *testing.B) { @@ -1806,8 +1803,8 @@ func BenchmarkMethodCall(b *testing.B) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() - vm := NewVM(importedProgram, nil) - importedContractValue, err := vm.InitializeContract() + vmInstance := vm.NewVM(importedProgram, nil) + importedContractValue, err := vmInstance.InitializeContract() require.NoError(b, err) checker, err := ParseAndCheckWithOptions(b, ` @@ -1841,24 +1838,24 @@ func BenchmarkMethodCall(b *testing.B) { program := comp.Compile() - vmConfig := &Config{ + vmConfig := &vm.Config{ ImportHandler: func(location common.Location) *bbq.Program { return importedProgram }, - ContractValueHandler: func(conf *Config, location common.Location) *CompositeValue { + ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { return importedContractValue }, } - vm = NewVM(program, vmConfig) + vmInstance = vm.NewVM(program, vmConfig) - value := IntValue{SmallInt: 10} + value := vm.IntValue{SmallInt: 10} b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - _, err := vm.Invoke("test", value) + _, err := vmInstance.Invoke("test", value) require.NoError(b, err) } }) @@ -1897,8 +1894,8 @@ func BenchmarkMethodCall(b *testing.B) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() - vm := NewVM(importedProgram, nil) - importedContractValue, err := vm.InitializeContract() + vmInstance := vm.NewVM(importedProgram, nil) + importedContractValue, err := vmInstance.InitializeContract() require.NoError(b, err) checker, err := ParseAndCheckWithOptions(b, ` @@ -1932,24 +1929,24 @@ func BenchmarkMethodCall(b *testing.B) { program := comp.Compile() - vmConfig := &Config{ + vmConfig := &vm.Config{ ImportHandler: func(location common.Location) *bbq.Program { return importedProgram }, - ContractValueHandler: func(conf *Config, location common.Location) *CompositeValue { + ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { return importedContractValue }, } - vm = NewVM(program, vmConfig) + vmInstance = vm.NewVM(program, vmConfig) - value := IntValue{SmallInt: 10} + value := vm.IntValue{SmallInt: 10} b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - _, err := vm.Invoke("test", value) + _, err := vmInstance.Invoke("test", value) require.NoError(b, err) } }) diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 4e07cec749..256a132684 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -686,6 +686,10 @@ func decodeLocation(locationBytes []byte) common.Location { return location } +func (vm *VM) StackSize() int { + return len(vm.stack) +} + func getReceiver[T any](receiver Value) T { switch receiver := receiver.(type) { case *SomeValue: From 8f781d11fe8c30810278dee65ef7a8e2d1c381ae Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 2 Aug 2024 16:31:29 -0700 Subject: [PATCH 46/89] Refactor tests --- runtime/bbq/vm/test/ft_test.go | 344 ++++----------------------------- runtime/bbq/vm/test/utils.go | 87 ++++++++- 2 files changed, 121 insertions(+), 310 deletions(-) diff --git a/runtime/bbq/vm/test/ft_test.go b/runtime/bbq/vm/test/ft_test.go index 163cd47146..c63fbdd480 100644 --- a/runtime/bbq/vm/test/ft_test.go +++ b/runtime/bbq/vm/test/ft_test.go @@ -20,20 +20,16 @@ package test import ( "fmt" - "github.com/onflow/cadence/runtime/ast" - "github.com/onflow/cadence/runtime/bbq" - "github.com/onflow/cadence/runtime/bbq/vm" - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/interpreter" - "github.com/onflow/cadence/runtime/stdlib" - "github.com/stretchr/testify/assert" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/cadence/runtime/bbq/compiler" + "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/runtime/bbq/vm" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" - . "github.com/onflow/cadence/runtime/tests/checker" ) func TestFTTransfer(t *testing.T) { @@ -41,52 +37,21 @@ func TestFTTransfer(t *testing.T) { // ---- Deploy FT Contract ----- storage := interpreter.NewInMemoryStorage(nil) + programs := map[common.Location]compiledProgram{} contractsAddress := common.MustBytesToAddress([]byte{0x1}) senderAddress := common.MustBytesToAddress([]byte{0x2}) receiverAddress := common.MustBytesToAddress([]byte{0x3}) ftLocation := common.NewAddressLocation(nil, contractsAddress, "FungibleToken") - ftChecker, err := ParseAndCheckWithOptions(t, realFungibleTokenContractInterface, - ParseAndCheckOptions{ - Location: ftLocation, - }, - ) - require.NoError(t, err) - ftCompiler := compiler.NewCompiler(ftChecker.Program, ftChecker.Elaboration) - ftProgram := ftCompiler.Compile() + _ = compileCode(t, realFungibleTokenContractInterface, ftLocation, programs) // ----- Deploy FlowToken Contract ----- flowTokenLocation := common.NewAddressLocation(nil, contractsAddress, "FlowToken") - flowTokenChecker, err := ParseAndCheckWithOptions(t, realFlowContract, - ParseAndCheckOptions{ - Location: flowTokenLocation, - Config: &sema.Config{ - ImportHandler: func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { - switch location { - case ftLocation: - return sema.ElaborationImport{ - Elaboration: ftChecker.Elaboration, - }, nil - default: - return nil, fmt.Errorf("cannot find contract in location %s", location) - } - }, - LocationHandler: singleIdentifierLocationResolver(t), - }, - }, - ) - require.NoError(t, err) - flowTokenCompiler := compiler.NewCompiler(flowTokenChecker.Program, flowTokenChecker.Elaboration) - flowTokenCompiler.Config.ImportHandler = func(location common.Location) *bbq.Program { - return ftProgram - } - - flowTokenProgram := flowTokenCompiler.Compile() - printProgram(flowTokenProgram) + flowTokenProgram := compileCode(t, realFlowContract, flowTokenLocation, programs) flowTokenVM := vm.NewVM( flowTokenProgram, @@ -97,55 +62,19 @@ func TestFTTransfer(t *testing.T) { ) authAccount := vm.NewAuthAccountReferenceValue(contractsAddress) - flowTokenContractValue, err := flowTokenVM.InitializeContract(authAccount) require.NoError(t, err) // ----- Run setup account transaction ----- - checkerImportHandler := func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { - require.IsType(t, common.AddressLocation{}, location) - addressLocation := location.(common.AddressLocation) - var elaboration *sema.Elaboration - - switch addressLocation { - case ftLocation: - elaboration = ftChecker.Elaboration - case flowTokenLocation: - elaboration = flowTokenChecker.Elaboration - default: - assert.FailNow(t, "invalid location") - } - - return sema.ElaborationImport{ - Elaboration: elaboration, - }, nil - } - - compilerImportHandler := func(location common.Location) *bbq.Program { - switch location { - case ftLocation: - return ftProgram - case flowTokenLocation: - return flowTokenProgram - default: - assert.FailNow(t, "invalid location") - return nil - } - } - vmConfig := &vm.Config{ Storage: storage, ImportHandler: func(location common.Location) *bbq.Program { - switch location { - case ftLocation: - return ftProgram - case flowTokenLocation: - return flowTokenProgram - default: - assert.FailNow(t, "invalid location") + imported, ok := programs[location] + if !ok { return nil } + return imported.Program }, ContractValueHandler: func(_ *vm.Config, location common.Location) *vm.CompositeValue { switch location { @@ -163,19 +92,17 @@ func TestFTTransfer(t *testing.T) { AccountHandler: &testAccountHandler{}, TypeLoader: func(location common.Location, typeID interpreter.TypeID) sema.CompositeKindedType { - semaImport, err := checkerImportHandler(nil, location, ast.EmptyRange) - if err != nil { - panic(err) + imported, ok := programs[location] + if !ok { + panic(fmt.Errorf("cannot find contract in location %s", location)) } - elaborationImport := semaImport.(sema.ElaborationImport) - - compositeType := elaborationImport.Elaboration.CompositeType(typeID) + compositeType := imported.Elaboration.CompositeType(typeID) if compositeType != nil { return compositeType } - return elaborationImport.Elaboration.InterfaceType(typeID) + return imported.Elaboration.InterfaceType(typeID) }, } @@ -183,24 +110,7 @@ func TestFTTransfer(t *testing.T) { senderAddress, receiverAddress, } { - setupTxChecker, err := ParseAndCheckWithOptions( - t, - realSetupFlowTokenAccountTransaction, - ParseAndCheckOptions{ - Config: &sema.Config{ - ImportHandler: checkerImportHandler, - LocationHandler: singleIdentifierLocationResolver(t), - }, - }, - ) - require.NoError(t, err) - - setupTxCompiler := compiler.NewCompiler(setupTxChecker.Program, setupTxChecker.Elaboration) - setupTxCompiler.Config.LocationHandler = singleIdentifierLocationResolver(t) - setupTxCompiler.Config.ImportHandler = compilerImportHandler - - program := setupTxCompiler.Compile() - printProgram(program) + program := compileCode(t, realSetupFlowTokenAccountTransaction, nil, programs) setupTxVM := vm.NewVM(program, vmConfig) @@ -212,25 +122,7 @@ func TestFTTransfer(t *testing.T) { // Mint FLOW to sender - mintTxChecker, err := ParseAndCheckWithOptions( - t, - realMintFlowTokenTransaction, - ParseAndCheckOptions{ - Config: &sema.Config{ - ImportHandler: checkerImportHandler, - BaseValueActivationHandler: baseActivation, - LocationHandler: singleIdentifierLocationResolver(t), - }, - }, - ) - require.NoError(t, err) - - mintTxCompiler := compiler.NewCompiler(mintTxChecker.Program, mintTxChecker.Elaboration) - mintTxCompiler.Config.LocationHandler = singleIdentifierLocationResolver(t) - mintTxCompiler.Config.ImportHandler = compilerImportHandler - - program := mintTxCompiler.Compile() - printProgram(program) + program := compileCode(t, realMintFlowTokenTransaction, nil, programs) mintTxVM := vm.NewVM(program, vmConfig) @@ -248,25 +140,7 @@ func TestFTTransfer(t *testing.T) { // ----- Run token transfer transaction ----- - tokenTransferTxChecker, err := ParseAndCheckWithOptions( - t, - realFlowTokenTransferTransaction, - ParseAndCheckOptions{ - Config: &sema.Config{ - ImportHandler: checkerImportHandler, - BaseValueActivationHandler: baseActivation, - LocationHandler: singleIdentifierLocationResolver(t), - }, - }, - ) - require.NoError(t, err) - - tokenTransferTxCompiler := compiler.NewCompiler(tokenTransferTxChecker.Program, tokenTransferTxChecker.Elaboration) - tokenTransferTxCompiler.Config.LocationHandler = singleIdentifierLocationResolver(t) - tokenTransferTxCompiler.Config.ImportHandler = compilerImportHandler - - tokenTransferTxProgram := tokenTransferTxCompiler.Compile() - printProgram(tokenTransferTxProgram) + tokenTransferTxProgram := compileCode(t, realFlowTokenTransferTransaction, nil, programs) tokenTransferTxVM := vm.NewVM(tokenTransferTxProgram, vmConfig) @@ -288,25 +162,7 @@ func TestFTTransfer(t *testing.T) { senderAddress, receiverAddress, } { - validationScriptChecker, err := ParseAndCheckWithOptions( - t, - realFlowTokenBalanceScript, - ParseAndCheckOptions{ - Config: &sema.Config{ - ImportHandler: checkerImportHandler, - BaseValueActivationHandler: baseActivation, - LocationHandler: singleIdentifierLocationResolver(t), - }, - }, - ) - require.NoError(t, err) - - validationScriptCompiler := compiler.NewCompiler(validationScriptChecker.Program, validationScriptChecker.Elaboration) - validationScriptCompiler.Config.LocationHandler = singleIdentifierLocationResolver(t) - validationScriptCompiler.Config.ImportHandler = compilerImportHandler - - program := validationScriptCompiler.Compile() - printProgram(program) + program := compileCode(t, realFlowTokenBalanceScript, nil, programs) validationScriptVM := vm.NewVM(program, vmConfig) @@ -323,19 +179,6 @@ func TestFTTransfer(t *testing.T) { } } -func baseActivation(common.Location) *sema.VariableActivation { - // Only need to make the checker happy - baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - baseValueActivation.DeclareValue(stdlib.PanicFunction) - baseValueActivation.DeclareValue(stdlib.NewStandardLibraryStaticFunction( - "getAccount", - stdlib.GetAccountFunctionType, - "", - nil, - )) - return baseValueActivation -} - const realFungibleTokenContractInterface = ` /// FungibleToken /// @@ -781,51 +624,19 @@ func BenchmarkFTTransfer(b *testing.B) { // ---- Deploy FT Contract ----- storage := interpreter.NewInMemoryStorage(nil) + programs := map[common.Location]compiledProgram{} contractsAddress := common.MustBytesToAddress([]byte{0x1}) senderAddress := common.MustBytesToAddress([]byte{0x2}) receiverAddress := common.MustBytesToAddress([]byte{0x3}) ftLocation := common.NewAddressLocation(nil, contractsAddress, "FungibleToken") - ftChecker, err := ParseAndCheckWithOptions(b, realFungibleTokenContractInterface, - ParseAndCheckOptions{ - Location: ftLocation, - }, - ) - require.NoError(b, err) - - ftCompiler := compiler.NewCompiler(ftChecker.Program, ftChecker.Elaboration) - ftProgram := ftCompiler.Compile() + _ = compileCode(b, realFungibleTokenContractInterface, ftLocation, programs) // ----- Deploy FlowToken Contract ----- flowTokenLocation := common.NewAddressLocation(nil, contractsAddress, "FlowToken") - flowTokenChecker, err := ParseAndCheckWithOptions(b, realFlowContract, - ParseAndCheckOptions{ - Location: flowTokenLocation, - Config: &sema.Config{ - ImportHandler: func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { - switch location { - case ftLocation: - return sema.ElaborationImport{ - Elaboration: ftChecker.Elaboration, - }, nil - default: - return nil, fmt.Errorf("cannot find contract in location %s", location) - } - }, - LocationHandler: singleIdentifierLocationResolver(b), - }, - }, - ) - require.NoError(b, err) - - flowTokenCompiler := compiler.NewCompiler(flowTokenChecker.Program, flowTokenChecker.Elaboration) - flowTokenCompiler.Config.ImportHandler = func(location common.Location) *bbq.Program { - return ftProgram - } - - flowTokenProgram := flowTokenCompiler.Compile() + flowTokenProgram := compileCode(b, realFlowContract, flowTokenLocation, programs) flowTokenVM := vm.NewVM( flowTokenProgram, @@ -842,49 +653,14 @@ func BenchmarkFTTransfer(b *testing.B) { // ----- Run setup account transaction ----- - checkerImportHandler := func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { - require.IsType(b, common.AddressLocation{}, location) - addressLocation := location.(common.AddressLocation) - var elaboration *sema.Elaboration - - switch addressLocation { - case ftLocation: - elaboration = ftChecker.Elaboration - case flowTokenLocation: - elaboration = flowTokenChecker.Elaboration - default: - assert.FailNow(b, "invalid location") - } - - return sema.ElaborationImport{ - Elaboration: elaboration, - }, nil - } - - compilerImportHandler := func(location common.Location) *bbq.Program { - switch location { - case ftLocation: - return ftProgram - case flowTokenLocation: - return flowTokenProgram - default: - assert.FailNow(b, "invalid location") - return nil - } - } - vmConfig := &vm.Config{ Storage: storage, ImportHandler: func(location common.Location) *bbq.Program { - switch location { - case ftLocation: - return ftProgram - case flowTokenLocation: - return flowTokenProgram - default: - assert.FailNow(b, "invalid location") + imported, ok := programs[location] + if !ok { return nil } + return imported.Program }, ContractValueHandler: func(_ *vm.Config, location common.Location) *vm.CompositeValue { switch location { @@ -902,19 +678,17 @@ func BenchmarkFTTransfer(b *testing.B) { AccountHandler: &testAccountHandler{}, TypeLoader: func(location common.Location, typeID interpreter.TypeID) sema.CompositeKindedType { - semaImport, err := checkerImportHandler(nil, location, ast.EmptyRange) - if err != nil { - panic(err) + imported, ok := programs[location] + if !ok { + panic(fmt.Errorf("cannot find contract in location %s", location)) } - elaborationImport := semaImport.(sema.ElaborationImport) - - compositeType := elaborationImport.Elaboration.CompositeType(typeID) + compositeType := imported.Elaboration.CompositeType(typeID) if compositeType != nil { return compositeType } - return elaborationImport.Elaboration.InterfaceType(typeID) + return imported.Elaboration.InterfaceType(typeID) }, } @@ -922,23 +696,7 @@ func BenchmarkFTTransfer(b *testing.B) { senderAddress, receiverAddress, } { - setupTxChecker, err := ParseAndCheckWithOptions( - b, - realSetupFlowTokenAccountTransaction, - ParseAndCheckOptions{ - Config: &sema.Config{ - ImportHandler: checkerImportHandler, - LocationHandler: singleIdentifierLocationResolver(b), - }, - }, - ) - require.NoError(b, err) - - setupTxCompiler := compiler.NewCompiler(setupTxChecker.Program, setupTxChecker.Elaboration) - setupTxCompiler.Config.LocationHandler = singleIdentifierLocationResolver(b) - setupTxCompiler.Config.ImportHandler = compilerImportHandler - - program := setupTxCompiler.Compile() + program := compileCode(b, realSetupFlowTokenAccountTransaction, nil, programs) setupTxVM := vm.NewVM(program, vmConfig) @@ -950,24 +708,7 @@ func BenchmarkFTTransfer(b *testing.B) { // Mint FLOW to sender - mintTxChecker, err := ParseAndCheckWithOptions( - b, - realMintFlowTokenTransaction, - ParseAndCheckOptions{ - Config: &sema.Config{ - ImportHandler: checkerImportHandler, - BaseValueActivationHandler: baseActivation, - LocationHandler: singleIdentifierLocationResolver(b), - }, - }, - ) - require.NoError(b, err) - - mintTxCompiler := compiler.NewCompiler(mintTxChecker.Program, mintTxChecker.Elaboration) - mintTxCompiler.Config.LocationHandler = singleIdentifierLocationResolver(b) - mintTxCompiler.Config.ImportHandler = compilerImportHandler - - program := mintTxCompiler.Compile() + program := compileCode(b, realMintFlowTokenTransaction, nil, programs) mintTxVM := vm.NewVM(program, vmConfig) @@ -985,18 +726,7 @@ func BenchmarkFTTransfer(b *testing.B) { // ----- Run token transfer transaction ----- - tokenTransferTxChecker, err := ParseAndCheckWithOptions( - b, - realFlowTokenTransferTransaction, - ParseAndCheckOptions{ - Config: &sema.Config{ - ImportHandler: checkerImportHandler, - BaseValueActivationHandler: baseActivation, - LocationHandler: singleIdentifierLocationResolver(b), - }, - }, - ) - require.NoError(b, err) + tokenTransferTxChecker := parseAndCheck(b, realFlowTokenTransferTransaction, nil, programs) transferAmount := int64(1) @@ -1011,11 +741,7 @@ func BenchmarkFTTransfer(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - tokenTransferTxCompiler := compiler.NewCompiler(tokenTransferTxChecker.Program, tokenTransferTxChecker.Elaboration) - tokenTransferTxCompiler.Config.LocationHandler = singleIdentifierLocationResolver(b) - tokenTransferTxCompiler.Config.ImportHandler = compilerImportHandler - - tokenTransferTxProgram := tokenTransferTxCompiler.Compile() + tokenTransferTxProgram := compile(b, tokenTransferTxChecker, programs) tokenTransferTxVM := vm.NewVM(tokenTransferTxProgram, vmConfig) err = tokenTransferTxVM.ExecuteTransaction(tokenTransferTxArgs, tokenTransferTxAuthorizer) diff --git a/runtime/bbq/vm/test/utils.go b/runtime/bbq/vm/test/utils.go index 2a9e3db980..7b767ae537 100644 --- a/runtime/bbq/vm/test/utils.go +++ b/runtime/bbq/vm/test/utils.go @@ -4,15 +4,18 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/bbq" "github.com/onflow/cadence/runtime/bbq/commons" + "github.com/onflow/cadence/runtime/bbq/compiler" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" - "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime/tests/checker" ) type testAccountHandler struct { @@ -360,3 +363,85 @@ func printProgram(program *bbq.Program) { byteCodePrinter := &bbq.BytecodePrinter{} fmt.Println(byteCodePrinter.PrintProgram(program)) } + +func baseActivation(common.Location) *sema.VariableActivation { + // Only need to make the checker happy + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.PanicFunction) + baseValueActivation.DeclareValue(stdlib.NewStandardLibraryStaticFunction( + "getAccount", + stdlib.GetAccountFunctionType, + "", + nil, + )) + return baseValueActivation +} + +type compiledProgram struct { + *bbq.Program + *sema.Elaboration +} + +func compileCode( + t testing.TB, + code string, + location common.Location, + programs map[common.Location]compiledProgram, +) *bbq.Program { + checker := parseAndCheck(t, code, location, programs) + + program := compile(t, checker, programs) + + programs[location] = compiledProgram{ + Program: program, + Elaboration: checker.Elaboration, + } + return program +} + +func parseAndCheck( + t testing.TB, + code string, + location common.Location, + programs map[common.Location]compiledProgram, +) *sema.Checker { + checker, err := checker.ParseAndCheckWithOptions( + t, + code, + checker.ParseAndCheckOptions{ + Location: location, + Config: &sema.Config{ + ImportHandler: func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { + imported, ok := programs[location] + if !ok { + return nil, fmt.Errorf("cannot find contract in location %s", location) + } + + return sema.ElaborationImport{ + Elaboration: imported.Elaboration, + }, nil + }, + LocationHandler: singleIdentifierLocationResolver(t), + BaseValueActivationHandler: baseActivation, + }, + }, + ) + require.NoError(t, err) + return checker +} + +func compile(t testing.TB, checker *sema.Checker, programs map[common.Location]compiledProgram) *bbq.Program { + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.LocationHandler = singleIdentifierLocationResolver(t) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + imported, ok := programs[location] + if !ok { + return nil + } + return imported.Program + } + + program := comp.Compile() + printProgram(program) + return program +} From 799cda2cd246423588ea4ac3d899639b2459b060 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 6 Aug 2024 08:50:06 -0700 Subject: [PATCH 47/89] Add resource invalidation test --- runtime/bbq/vm/test/runtime_test.go | 158 ++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 runtime/bbq/vm/test/runtime_test.go diff --git a/runtime/bbq/vm/test/runtime_test.go b/runtime/bbq/vm/test/runtime_test.go new file mode 100644 index 0000000000..2ad7cad45d --- /dev/null +++ b/runtime/bbq/vm/test/runtime_test.go @@ -0,0 +1,158 @@ +package test + +import ( + "fmt" + "github.com/onflow/cadence/runtime/bbq" + "github.com/stretchr/testify/assert" + "testing" + + "github.com/onflow/cadence/runtime/bbq/vm" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/stretchr/testify/require" +) + +func TestResourceLossViaSelfRugPull(t *testing.T) { + + // ---- Deploy FT Contract ----- + + storage := interpreter.NewInMemoryStorage(nil) + + programs := map[common.Location]compiledProgram{} + + contractsAddress := common.MustBytesToAddress([]byte{0x1}) + authorizerAddress := common.MustBytesToAddress([]byte{0x2}) + + // ----- Deploy Contract ----- + + contractCode := ` + access(all) contract Bar { + + access(all) resource Vault { + + // Balance of a user's Vault + // we use unsigned fixed point numbers for balances + // because they can represent decimals and do not allow negative values + access(all) var balance: Int + + init(balance: Int) { + self.balance = balance + } + + access(all) fun withdraw(amount: Int): @Vault { + self.balance = self.balance - amount + return <-create Vault(balance: amount) + } + + access(all) fun deposit(from: @Vault) { + self.balance = self.balance + from.balance + destroy from + } + } + + access(all) fun createEmptyVault(): @Bar.Vault { + return <- create Bar.Vault(balance: 0) + } + + access(all) fun createVault(balance: Int): @Bar.Vault { + return <- create Bar.Vault(balance: balance) + } + } + ` + barLocation := common.NewAddressLocation(nil, contractsAddress, "Bar") + + barProgram := compileCode(t, contractCode, barLocation, programs) + + barVM := vm.NewVM( + barProgram, + &vm.Config{ + Storage: storage, + AccountHandler: &testAccountHandler{}, + }, + ) + + barContractValue, err := barVM.InitializeContract() + require.NoError(t, err) + + // --------- Execute Transaction ------------ + + tx := fmt.Sprintf(` + import Bar from %[1]s + + access(all) contract Foo { + access(all) var rCopy1: @R? + init() { + self.rCopy1 <- nil + var r <- Bar.createVault(balance: 1337) + self.loser(<- r) + } + access(all) resource R { + access(all) var optional: @[Bar.Vault]? + + init() { + self.optional <- [] + } + + access(all) fun rugpullAndAssign(_ callback: fun(): Void, _ victim: @Bar.Vault) { + callback() + // "self" has now been invalidated and accessing "a" for reading would + // trigger a "not initialized" error. However, force-assigning to it succeeds + // and leaves the victim object hanging from an invalidated resource + self.optional <-! [<- victim] + } + } + + access(all) fun loser(_ victim: @Bar.Vault): Void{ + var array: @[R] <- [<- create R()] + let arrRef = &array as auth(Remove) &[R] + fun rugPullCallback(): Void{ + // Here we move the R resource from the array to a contract field + // invalidating the "self" during the execution of rugpullAndAssign + Foo.rCopy1 <-! arrRef.removeLast() + } + array[0].rugpullAndAssign(rugPullCallback, <- victim) + destroy array + + var y: @R? <- nil + self.rCopy1 <-> y + destroy y + } + + }`, + contractsAddress.HexWithPrefix(), + ) + + importHandler := func(location common.Location) *bbq.Program { + switch location { + case barLocation: + return barProgram + default: + assert.FailNow(t, "invalid location") + return nil + } + } + + program := compileCode(t, tx, nil, programs) + printProgram(program) + + vmConfig := &vm.Config{ + Storage: storage, + ImportHandler: importHandler, + ContractValueHandler: func(_ *vm.Config, location common.Location) *vm.CompositeValue { + switch location { + case barLocation: + return barContractValue + default: + assert.FailNow(t, "invalid location") + return nil + } + }, + } + + txVM := vm.NewVM(program, vmConfig) + + authorizer := vm.NewAuthAccountReferenceValue(authorizerAddress) + err = txVM.ExecuteTransaction(nil, authorizer) + require.NoError(t, err) + require.Equal(t, 0, txVM.StackSize()) +} From 540c09d1e7103260d9b5acc103f91e65f58308c7 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 7 Aug 2024 11:02:11 -0700 Subject: [PATCH 48/89] Add support for arrays --- runtime/bbq/compiler/compiler.go | 45 ++- runtime/bbq/constantkind/constantkind.go | 2 +- runtime/bbq/opcode/opcode.go | 60 ++++ runtime/bbq/opcode/opcode_string.go | 109 ++++--- runtime/bbq/vm/callframe.go | 6 + runtime/bbq/vm/test/vm_test.go | 85 ++++++ runtime/bbq/vm/value_array.go | 360 +++++++++++++++++++++++ runtime/bbq/vm/vm.go | 58 ++++ 8 files changed, 683 insertions(+), 42 deletions(-) create mode 100644 runtime/bbq/vm/value_array.go diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index ab7cd668e8..f46dd54300 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -623,6 +623,10 @@ func (c *Compiler) VisitAssignmentStatement(statement *ast.AssignmentStatement) c.compileExpression(target.Expression) c.stringConstLoad(target.Identifier.Identifier) c.emit(opcode.SetField) + case *ast.IndexExpression: + c.compileExpression(target.TargetExpression) + c.compileExpression(target.IndexingExpression) + c.emit(opcode.SetIndex) default: // TODO: panic(errors.NewUnreachableError()) @@ -680,9 +684,36 @@ func (c *Compiler) VisitFixedPointExpression(_ *ast.FixedPointExpression) (_ str panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitArrayExpression(_ *ast.ArrayExpression) (_ struct{}) { - // TODO - panic(errors.NewUnreachableError()) +func (c *Compiler) VisitArrayExpression(array *ast.ArrayExpression) (_ struct{}) { + arrayTypes := c.Elaboration.ArrayExpressionTypes(array) + + var isResource byte + if arrayTypes.ArrayType.IsResourceType() { + isResource = 1 + } + + typeIndex := c.getOrAddType(arrayTypes.ArrayType) + typeIndexFirst, typeIndexSecond := encodeUint16(typeIndex) + + sizeFirst, sizeSecond := encodeUint16(uint16(len(array.Values))) + + for _, expression := range array.Values { + //c.emit(opcode.Dup) + c.compileExpression(expression) + //first, second := encodeUint16(uint16(index)) + //c.emit(opcode.SetIndex, first, second) + } + + c.emit( + opcode.NewArray, + typeIndexFirst, + typeIndexSecond, + sizeFirst, + sizeSecond, + isResource, + ) + + return } func (c *Compiler) VisitDictionaryExpression(_ *ast.DictionaryExpression) (_ struct{}) { @@ -857,9 +888,11 @@ func (c *Compiler) VisitMemberExpression(expression *ast.MemberExpression) (_ st return } -func (c *Compiler) VisitIndexExpression(_ *ast.IndexExpression) (_ struct{}) { - // TODO - panic(errors.NewUnreachableError()) +func (c *Compiler) VisitIndexExpression(expression *ast.IndexExpression) (_ struct{}) { + c.compileExpression(expression.TargetExpression) + c.compileExpression(expression.IndexingExpression) + c.emit(opcode.GetIndex) + return } func (c *Compiler) VisitConditionalExpression(_ *ast.ConditionalExpression) (_ struct{}) { diff --git a/runtime/bbq/constantkind/constantkind.go b/runtime/bbq/constantkind/constantkind.go index c7b2ea0c41..0ad928fddf 100644 --- a/runtime/bbq/constantkind/constantkind.go +++ b/runtime/bbq/constantkind/constantkind.go @@ -84,7 +84,7 @@ const ( func FromSemaType(ty sema.Type) ConstantKind { switch ty { // Int* - case sema.IntType: + case sema.IntType, sema.IntegerType: return Int case sema.Int8Type: return Int8 diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index d1406edcb1..caf58fe1a0 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -31,6 +31,12 @@ const ( ReturnValue Jump JumpIfFalse + _ + _ + _ + _ + _ + _ // Int operations @@ -43,6 +49,17 @@ const ( IntGreater IntLessOrEqual IntGreaterOrEqual + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ // Unary/Binary operators @@ -52,6 +69,10 @@ const ( Destroy Transfer Cast + _ + _ + _ + _ // Value/Constant loading @@ -60,6 +81,21 @@ const ( New Path Nil + NewArray + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ GetConstant GetLocal @@ -68,14 +104,38 @@ const ( SetGlobal GetField SetField + SetIndex + GetIndex + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ + _ // Invocations Invoke InvokeDynamic + _ + _ + _ + _ + _ + _ + _ + _ // Stack operations Drop Dup + _ + _ + _ ) diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index 891317dbcf..9b1ee3a3ed 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -13,46 +13,85 @@ func _() { _ = x[ReturnValue-2] _ = x[Jump-3] _ = x[JumpIfFalse-4] - _ = x[IntAdd-5] - _ = x[IntSubtract-6] - _ = x[IntMultiply-7] - _ = x[IntDivide-8] - _ = x[IntMod-9] - _ = x[IntLess-10] - _ = x[IntGreater-11] - _ = x[IntLessOrEqual-12] - _ = x[IntGreaterOrEqual-13] - _ = x[Equal-14] - _ = x[NotEqual-15] - _ = x[Unwrap-16] - _ = x[Destroy-17] - _ = x[Transfer-18] - _ = x[Cast-19] - _ = x[True-20] - _ = x[False-21] - _ = x[New-22] - _ = x[Path-23] - _ = x[Nil-24] - _ = x[GetConstant-25] - _ = x[GetLocal-26] - _ = x[SetLocal-27] - _ = x[GetGlobal-28] - _ = x[SetGlobal-29] - _ = x[GetField-30] - _ = x[SetField-31] - _ = x[Invoke-32] - _ = x[InvokeDynamic-33] - _ = x[Drop-34] - _ = x[Dup-35] + _ = x[IntAdd-11] + _ = x[IntSubtract-12] + _ = x[IntMultiply-13] + _ = x[IntDivide-14] + _ = x[IntMod-15] + _ = x[IntLess-16] + _ = x[IntGreater-17] + _ = x[IntLessOrEqual-18] + _ = x[IntGreaterOrEqual-19] + _ = x[Equal-31] + _ = x[NotEqual-32] + _ = x[Unwrap-33] + _ = x[Destroy-34] + _ = x[Transfer-35] + _ = x[Cast-36] + _ = x[True-41] + _ = x[False-42] + _ = x[New-43] + _ = x[Path-44] + _ = x[Nil-45] + _ = x[NewArray-46] + _ = x[GetConstant-61] + _ = x[GetLocal-62] + _ = x[SetLocal-63] + _ = x[GetGlobal-64] + _ = x[SetGlobal-65] + _ = x[GetField-66] + _ = x[SetField-67] + _ = x[SetIndex-68] + _ = x[GetIndex-69] + _ = x[Invoke-81] + _ = x[InvokeDynamic-82] + _ = x[Drop-91] + _ = x[Dup-92] } -const _Opcode_name = "UnknownReturnReturnValueJumpJumpIfFalseIntAddIntSubtractIntMultiplyIntDivideIntModIntLessIntGreaterIntLessOrEqualIntGreaterOrEqualEqualNotEqualUnwrapDestroyTransferCastTrueFalseNewPathNilGetConstantGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldInvokeInvokeDynamicDropDup" +const ( + _Opcode_name_0 = "UnknownReturnReturnValueJumpJumpIfFalse" + _Opcode_name_1 = "IntAddIntSubtractIntMultiplyIntDivideIntModIntLessIntGreaterIntLessOrEqualIntGreaterOrEqual" + _Opcode_name_2 = "EqualNotEqualUnwrapDestroyTransferCast" + _Opcode_name_3 = "TrueFalseNewPathNilNewArray" + _Opcode_name_4 = "GetConstantGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldSetIndexGetIndex" + _Opcode_name_5 = "InvokeInvokeDynamic" + _Opcode_name_6 = "DropDup" +) -var _Opcode_index = [...]uint16{0, 7, 13, 24, 28, 39, 45, 56, 67, 76, 82, 89, 99, 113, 130, 135, 143, 149, 156, 164, 168, 172, 177, 180, 184, 187, 198, 206, 214, 223, 232, 240, 248, 254, 267, 271, 274} +var ( + _Opcode_index_0 = [...]uint8{0, 7, 13, 24, 28, 39} + _Opcode_index_1 = [...]uint8{0, 6, 17, 28, 37, 43, 50, 60, 74, 91} + _Opcode_index_2 = [...]uint8{0, 5, 13, 19, 26, 34, 38} + _Opcode_index_3 = [...]uint8{0, 4, 9, 12, 16, 19, 27} + _Opcode_index_4 = [...]uint8{0, 11, 19, 27, 36, 45, 53, 61, 69, 77} + _Opcode_index_5 = [...]uint8{0, 6, 19} + _Opcode_index_6 = [...]uint8{0, 4, 7} +) func (i Opcode) String() string { - if i >= Opcode(len(_Opcode_index)-1) { + switch { + case i <= 4: + return _Opcode_name_0[_Opcode_index_0[i]:_Opcode_index_0[i+1]] + case 11 <= i && i <= 19: + i -= 11 + return _Opcode_name_1[_Opcode_index_1[i]:_Opcode_index_1[i+1]] + case 31 <= i && i <= 36: + i -= 31 + return _Opcode_name_2[_Opcode_index_2[i]:_Opcode_index_2[i+1]] + case 41 <= i && i <= 46: + i -= 41 + return _Opcode_name_3[_Opcode_index_3[i]:_Opcode_index_3[i+1]] + case 61 <= i && i <= 69: + i -= 61 + return _Opcode_name_4[_Opcode_index_4[i]:_Opcode_index_4[i+1]] + case 81 <= i && i <= 82: + i -= 81 + return _Opcode_name_5[_Opcode_index_5[i]:_Opcode_index_5[i+1]] + case 91 <= i && i <= 92: + i -= 91 + return _Opcode_name_6[_Opcode_index_6[i]:_Opcode_index_6[i+1]] + default: return "Opcode(" + strconv.FormatInt(int64(i), 10) + ")" } - return _Opcode_name[_Opcode_index[i]:_Opcode_index[i+1]] } diff --git a/runtime/bbq/vm/callframe.go b/runtime/bbq/vm/callframe.go index 5ed4bb89e3..7ac48ff49b 100644 --- a/runtime/bbq/vm/callframe.go +++ b/runtime/bbq/vm/callframe.go @@ -43,6 +43,12 @@ func (f *callFrame) getByte() byte { return byt } +func (f *callFrame) getBool() bool { + byt := f.function.Code[f.ip] + f.ip++ + return byt == 1 +} + func (f *callFrame) getString() string { strLen := f.getUint16() str := string(f.function.Code[f.ip : f.ip+strLen]) diff --git a/runtime/bbq/vm/test/vm_test.go b/runtime/bbq/vm/test/vm_test.go index 0130ea7ea9..17abebacb1 100644 --- a/runtime/bbq/vm/test/vm_test.go +++ b/runtime/bbq/vm/test/vm_test.go @@ -1951,3 +1951,88 @@ func BenchmarkMethodCall(b *testing.B) { } }) } + +func TestArrayLiteral(t *testing.T) { + + t.Parallel() + + t.Run("array literal", func(t *testing.T) { + t.Parallel() + + checker, err := ParseAndCheck(t, ` + fun test(): [Int] { + return [2, 5] + } + `) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) + + result, err := vmInstance.Invoke("test") + require.NoError(t, err) + require.Equal(t, 0, vmInstance.StackSize()) + + require.IsType(t, &vm.ArrayValue{}, result) + array := result.(*vm.ArrayValue) + assert.Equal(t, 2, array.Count()) + assert.Equal(t, vm.IntValue{SmallInt: 2}, array.Get(vmConfig, 0)) + assert.Equal(t, vm.IntValue{SmallInt: 5}, array.Get(vmConfig, 1)) + }) + + t.Run("array get", func(t *testing.T) { + t.Parallel() + + checker, err := ParseAndCheck(t, ` + fun test(): Int { + var a = [2, 5, 7, 3] + return a[1] + } + `) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) + + result, err := vmInstance.Invoke("test") + require.NoError(t, err) + require.Equal(t, 0, vmInstance.StackSize()) + assert.Equal(t, vm.IntValue{SmallInt: 5}, result) + }) + + t.Run("array set", func(t *testing.T) { + t.Parallel() + + checker, err := ParseAndCheck(t, ` + fun test(): [Int] { + var a = [2, 5, 4] + a[2] = 8 + return a + } + `) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(program, vmConfig) + + result, err := vmInstance.Invoke("test") + require.NoError(t, err) + require.Equal(t, 0, vmInstance.StackSize()) + + require.IsType(t, &vm.ArrayValue{}, result) + array := result.(*vm.ArrayValue) + assert.Equal(t, 3, array.Count()) + assert.Equal(t, vm.IntValue{SmallInt: 2}, array.Get(vmConfig, 0)) + assert.Equal(t, vm.IntValue{SmallInt: 5}, array.Get(vmConfig, 1)) + assert.Equal(t, vm.IntValue{SmallInt: 8}, array.Get(vmConfig, 2)) + }) +} diff --git a/runtime/bbq/vm/value_array.go b/runtime/bbq/vm/value_array.go new file mode 100644 index 0000000000..da19bf8a8d --- /dev/null +++ b/runtime/bbq/vm/value_array.go @@ -0,0 +1,360 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vm + +import ( + goerrors "errors" + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" +) + +type ArrayValue struct { + Type interpreter.ArrayStaticType + semaType sema.ArrayType + array *atree.Array + isResourceKinded bool + elementSize uint + isDestroyed bool +} + +var _ Value = &ArrayValue{} + +func NewArrayValue( + config *Config, + arrayType interpreter.ArrayStaticType, + isResourceKinded bool, + values ...Value, +) *ArrayValue { + + address := common.ZeroAddress + + var index int + count := len(values) + + return NewArrayValueWithIterator( + config, + arrayType, + isResourceKinded, + address, + func() Value { + if index >= count { + return nil + } + + value := values[index] + + index++ + + value = value.Transfer( + config, + atree.Address(address), + true, + nil, + ) + + return value + }, + ) +} + +func NewArrayValueWithIterator( + config *Config, + arrayType interpreter.ArrayStaticType, + isResourceKinded bool, + address common.Address, + values func() Value, +) *ArrayValue { + constructor := func() *atree.Array { + array, err := atree.NewArrayFromBatchData( + config.Storage, + atree.Address(address), + arrayType, + func() (atree.Value, error) { + vmValue := values() + value := VMValueToInterpreterValue(config, vmValue) + return value, nil + }, + ) + if err != nil { + panic(errors.NewExternalError(err)) + } + return array + } + + return newArrayValueFromConstructor(arrayType, isResourceKinded, constructor) +} + +func newArrayValueFromConstructor( + staticType interpreter.ArrayStaticType, + isResourceKinded bool, + constructor func() *atree.Array, +) *ArrayValue { + + elementSize := interpreter.ArrayElementSize(staticType) + + return newArrayValueFromAtreeArray( + staticType, + isResourceKinded, + elementSize, + constructor(), + ) +} + +func newArrayValueFromAtreeArray( + staticType interpreter.ArrayStaticType, + isResourceKinded bool, + elementSize uint, + atreeArray *atree.Array, +) *ArrayValue { + return &ArrayValue{ + Type: staticType, + array: atreeArray, + elementSize: elementSize, + isResourceKinded: isResourceKinded, + } +} + +func (v *ArrayValue) isValue() { + panic("implement me") +} + +func (v *ArrayValue) StaticType(common.MemoryGauge) StaticType { + return v.Type +} + +func (v *ArrayValue) Transfer(config *Config, address atree.Address, remove bool, storable atree.Storable) Value { + + storage := config.Storage + + array := v.array + + //currentValueID := v.ValueID() + + //if preventTransfer == nil { + // preventTransfer = map[atree.ValueID]struct{}{} + //} else if _, ok := preventTransfer[currentValueID]; ok { + // panic(RecursiveTransferError{ + // LocationRange: locationRange, + // }) + //} + //preventTransfer[currentValueID] = struct{}{} + //defer delete(preventTransfer, currentValueID) + + needsStoreTo := v.NeedsStoreTo(address) + isResourceKinded := v.IsResourceKinded() + + if needsStoreTo || !isResourceKinded { + + // Use non-readonly iterator here because iterated + // value can be removed if remove parameter is true. + iterator, err := v.array.Iterator() + if err != nil { + panic(errors.NewExternalError(err)) + } + + array, err = atree.NewArrayFromBatchData( + config.Storage, + address, + v.array.Type(), + func() (atree.Value, error) { + value, err := iterator.Next() + if err != nil { + return nil, err + } + if value == nil { + return nil, nil + } + + element := interpreter.MustConvertStoredValue(config.MemoryGauge, value) + + // TODO: converted value is unused + vmElement := InterpreterValueToVMValue(config.Storage, element) + vmElement = vmElement.Transfer( + config, + address, + remove, + nil, + ) + + return element, nil + }, + ) + if err != nil { + panic(errors.NewExternalError(err)) + } + + if remove { + err = v.array.PopIterate(func(valueStorable atree.Storable) { + RemoveReferencedSlab(storage, valueStorable) + }) + if err != nil { + panic(errors.NewExternalError(err)) + } + + //interpreter.maybeValidateAtreeValue(v.array) + //if hasNoParentContainer { + // interpreter.maybeValidateAtreeStorage() + //} + + RemoveReferencedSlab(storage, storable) + } + } + + if isResourceKinded { + // Update the resource in-place, + // and also update all values that are referencing the same value + // (but currently point to an outdated Go instance of the value) + + // If checking of transfers of invalidated resource is enabled, + // then mark the resource array as invalidated, by unsetting the backing array. + // This allows raising an error when the resource array is attempted + // to be transferred/moved again (see beginning of this function) + + // TODO: + //interpreter.invalidateReferencedResources(v, locationRange) + + v.array = nil + } + + res := newArrayValueFromAtreeArray( + v.Type, + isResourceKinded, + v.elementSize, + array, + ) + + res.semaType = v.semaType + res.isResourceKinded = v.isResourceKinded + res.isDestroyed = v.isDestroyed + + return res +} + +func (v *ArrayValue) String() string { + panic("implement me") +} + +func (v *ArrayValue) ValueID() atree.ValueID { + return v.array.ValueID() +} + +func (v *ArrayValue) SlabID() atree.SlabID { + return v.array.SlabID() +} + +func (v *ArrayValue) StorageAddress() atree.Address { + return v.array.Address() +} + +func (v *ArrayValue) NeedsStoreTo(address atree.Address) bool { + return address != v.StorageAddress() +} + +func (v *ArrayValue) IsResourceKinded() bool { + return v.isResourceKinded +} + +func (v *ArrayValue) Count() int { + return int(v.array.Count()) +} + +func (v *ArrayValue) Get(config *Config, index int) Value { + + // We only need to check the lower bound before converting from `int` (signed) to `uint64` (unsigned). + // atree's Array.Get function will check the upper bound and report an atree.IndexOutOfBoundsError + + if index < 0 { + panic(interpreter.ArrayIndexOutOfBoundsError{ + Index: index, + Size: v.Count(), + }) + } + + storedValue, err := v.array.Get(uint64(index)) + if err != nil { + v.handleIndexOutOfBoundsError(err, index) + panic(errors.NewExternalError(err)) + } + + return MustConvertStoredValue( + config.MemoryGauge, + config.Storage, + storedValue, + ) +} + +func (v *ArrayValue) handleIndexOutOfBoundsError(err error, index int) { + var indexOutOfBoundsError *atree.IndexOutOfBoundsError + if goerrors.As(err, &indexOutOfBoundsError) { + panic(interpreter.ArrayIndexOutOfBoundsError{ + Index: index, + Size: v.Count(), + }) + } +} + +func (v *ArrayValue) Set(config *Config, index int, element Value) { + + // TODO: + //interpreter.validateMutation(v.ValueID(), locationRange) + + // We only need to check the lower bound before converting from `int` (signed) to `uint64` (unsigned). + // atree's Array.Set function will check the upper bound and report an atree.IndexOutOfBoundsError + + if index < 0 { + panic(interpreter.ArrayIndexOutOfBoundsError{ + Index: index, + Size: v.Count(), + }) + } + + // TODO: + //interpreter.checkContainerMutation(v.Type.ElementType(), element, locationRange) + + element = element.Transfer( + config, + v.array.Address(), + true, + nil, + ) + + storableElement := VMValueToInterpreterValue(config, element) + + existingStorable, err := v.array.Set(uint64(index), storableElement) + if err != nil { + v.handleIndexOutOfBoundsError(err, index) + + panic(errors.NewExternalError(err)) + } + + //interpreter.maybeValidateAtreeValue(v.array) + //interpreter.maybeValidateAtreeStorage() + + existingValue := StoredValue(config.MemoryGauge, existingStorable, config.Storage) + _ = existingValue + + //interpreter.checkResourceLoss(existingValue, locationRange) + + //existingValue.DeepRemove(interpreter, true) // existingValue is standalone because it was overwritten in parent container. + + RemoveReferencedSlab(config.Storage, existingStorable) +} diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 256a132684..a015a3a6fa 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -79,6 +79,26 @@ func (vm *VM) pop() Value { return value } +// pop2 removes and returns the top two value of the stack. +// It is efficient than calling `pop` twice. +func (vm *VM) pop2() (Value, Value) { + lastIndex := len(vm.stack) - 1 + value1, value2 := vm.stack[lastIndex], vm.stack[lastIndex-1] + vm.stack[lastIndex], vm.stack[lastIndex-1] = nil, nil + vm.stack = vm.stack[:lastIndex-1] + return value1, value2 +} + +// pop3 removes and returns the top three value of the stack. +// It is efficient than calling `pop` thrice. +func (vm *VM) pop3() (Value, Value, Value) { + lastIndex := len(vm.stack) - 1 + value1, value2, value3 := vm.stack[lastIndex], vm.stack[lastIndex-1], vm.stack[lastIndex-2] + vm.stack[lastIndex], vm.stack[lastIndex-1], vm.stack[lastIndex-2] = nil, nil, nil + vm.stack = vm.stack[:lastIndex-2] + return value1, value2, value3 +} + func (vm *VM) peek() Value { lastIndex := len(vm.stack) - 1 return vm.stack[lastIndex] @@ -306,6 +326,21 @@ func opSetGlobal(vm *VM) { callFrame.context.Globals[index] = vm.pop() } +func opSetIndex(vm *VM) { + index, array, element := vm.pop3() + indexValue := index.(IntValue) + arrayValue := array.(*ArrayValue) + arrayValue.Set(vm.config, int(indexValue.SmallInt), element) +} + +func opGetIndex(vm *VM) { + index, array := vm.pop2() + indexValue := index.(IntValue) + arrayValue := array.(*ArrayValue) + element := arrayValue.Get(vm.config, int(indexValue.SmallInt)) + vm.push(element) +} + func opInvoke(vm *VM) { value := vm.pop() stackHeight := len(vm.stack) @@ -508,6 +543,23 @@ func opUnwrap(vm *VM) { } } +func opNewArray(vm *VM) { + typ := vm.loadType().(interpreter.ArrayStaticType) + size := int(vm.callFrame.getUint16()) + isResourceKinded := vm.callFrame.getBool() + + elements := make([]Value, size) + + // Must be inserted in the reverse, + //since the stack if FILO. + for i := size - 1; i >= 0; i-- { + elements[i] = vm.pop() + } + + array := NewArrayValue(vm.config, typ, isResourceKinded, elements...) + vm.push(array) +} + func (vm *VM) run() { for { @@ -553,6 +605,10 @@ func (vm *VM) run() { opGetGlobal(vm) case opcode.SetGlobal: opSetGlobal(vm) + case opcode.SetIndex: + opSetIndex(vm) + case opcode.GetIndex: + opGetIndex(vm) case opcode.Invoke: opInvoke(vm) case opcode.InvokeDynamic: @@ -563,6 +619,8 @@ func (vm *VM) run() { opDup(vm) case opcode.New: opNew(vm) + case opcode.NewArray: + opNewArray(vm) case opcode.SetField: opSetField(vm) case opcode.GetField: From d13f04ccc01498b222f594eeb3cf8638a5d80ecb Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 8 Aug 2024 10:16:05 -0700 Subject: [PATCH 49/89] Add support for references --- runtime/bbq/compiler/compiler.go | 10 +- runtime/bbq/opcode/opcode.go | 4 +- runtime/bbq/opcode/opcode_string.go | 8 +- runtime/bbq/vm/test/runtime_test.go | 272 ++++++++++++++++++++++++++++ runtime/bbq/vm/test/utils.go | 16 ++ runtime/bbq/vm/vm.go | 14 ++ 6 files changed, 316 insertions(+), 8 deletions(-) diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index f46dd54300..d09954ef89 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -992,9 +992,13 @@ func (c *Compiler) VisitDestroyExpression(expression *ast.DestroyExpression) (_ return } -func (c *Compiler) VisitReferenceExpression(_ *ast.ReferenceExpression) (_ struct{}) { - // TODO - panic(errors.NewUnreachableError()) +func (c *Compiler) VisitReferenceExpression(expression *ast.ReferenceExpression) (_ struct{}) { + c.compileExpression(expression.Expression) + borrowType := c.Elaboration.ReferenceExpressionBorrowType(expression) + index := c.getOrAddType(borrowType) + typeIndexFirst, typeIndexSecond := encodeUint16(index) + c.emit(opcode.NewRef, typeIndexFirst, typeIndexSecond) + return } func (c *Compiler) VisitForceExpression(_ *ast.ForceExpression) (_ struct{}) { diff --git a/runtime/bbq/opcode/opcode.go b/runtime/bbq/opcode/opcode.go index caf58fe1a0..0c8680f201 100644 --- a/runtime/bbq/opcode/opcode.go +++ b/runtime/bbq/opcode/opcode.go @@ -82,8 +82,8 @@ const ( Path Nil NewArray - _ - _ + NewDictionary + NewRef _ _ _ diff --git a/runtime/bbq/opcode/opcode_string.go b/runtime/bbq/opcode/opcode_string.go index 9b1ee3a3ed..45b821b0c6 100644 --- a/runtime/bbq/opcode/opcode_string.go +++ b/runtime/bbq/opcode/opcode_string.go @@ -34,6 +34,8 @@ func _() { _ = x[Path-44] _ = x[Nil-45] _ = x[NewArray-46] + _ = x[NewDictionary-47] + _ = x[NewRef-48] _ = x[GetConstant-61] _ = x[GetLocal-62] _ = x[SetLocal-63] @@ -53,7 +55,7 @@ const ( _Opcode_name_0 = "UnknownReturnReturnValueJumpJumpIfFalse" _Opcode_name_1 = "IntAddIntSubtractIntMultiplyIntDivideIntModIntLessIntGreaterIntLessOrEqualIntGreaterOrEqual" _Opcode_name_2 = "EqualNotEqualUnwrapDestroyTransferCast" - _Opcode_name_3 = "TrueFalseNewPathNilNewArray" + _Opcode_name_3 = "TrueFalseNewPathNilNewArrayNewDictionaryNewRef" _Opcode_name_4 = "GetConstantGetLocalSetLocalGetGlobalSetGlobalGetFieldSetFieldSetIndexGetIndex" _Opcode_name_5 = "InvokeInvokeDynamic" _Opcode_name_6 = "DropDup" @@ -63,7 +65,7 @@ var ( _Opcode_index_0 = [...]uint8{0, 7, 13, 24, 28, 39} _Opcode_index_1 = [...]uint8{0, 6, 17, 28, 37, 43, 50, 60, 74, 91} _Opcode_index_2 = [...]uint8{0, 5, 13, 19, 26, 34, 38} - _Opcode_index_3 = [...]uint8{0, 4, 9, 12, 16, 19, 27} + _Opcode_index_3 = [...]uint8{0, 4, 9, 12, 16, 19, 27, 40, 46} _Opcode_index_4 = [...]uint8{0, 11, 19, 27, 36, 45, 53, 61, 69, 77} _Opcode_index_5 = [...]uint8{0, 6, 19} _Opcode_index_6 = [...]uint8{0, 4, 7} @@ -79,7 +81,7 @@ func (i Opcode) String() string { case 31 <= i && i <= 36: i -= 31 return _Opcode_name_2[_Opcode_index_2[i]:_Opcode_index_2[i+1]] - case 41 <= i && i <= 46: + case 41 <= i && i <= 48: i -= 41 return _Opcode_name_3[_Opcode_index_3[i]:_Opcode_index_3[i+1]] case 61 <= i && i <= 69: diff --git a/runtime/bbq/vm/test/runtime_test.go b/runtime/bbq/vm/test/runtime_test.go index 2ad7cad45d..57e2f86cf6 100644 --- a/runtime/bbq/vm/test/runtime_test.go +++ b/runtime/bbq/vm/test/runtime_test.go @@ -156,3 +156,275 @@ func TestResourceLossViaSelfRugPull(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, txVM.StackSize()) } + +func TestInterpretResourceReferenceInvalidationOnMove(t *testing.T) { + + t.Parallel() + + t.Run("stack to account", func(t *testing.T) { + + t.Parallel() + + code := ` + resource R { + access(all) var id: Int + + access(all) fun setID(_ id: Int) { + self.id = id + } + + init() { + self.id = 1 + } + } + + fun getRef(_ ref: &R): &R { + return ref + } + + fun test() { + let r <-create R() + let ref = getRef(&r as &R) + + // Move the resource into the account + account.storage.save(<-r, to: /storage/r) + + // Update the reference + ref.setID(2) + }` + + _, err := compileAndInvoke(t, code, "test") + require.Error(t, err) + require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) + }) + // + //t.Run("stack to account readonly", func(t *testing.T) { + // + // t.Parallel() + // + // address := interpreter.NewUnmeteredAddressValueFromBytes([]byte{42}) + // + // inter, _ := testAccountWithErrorHandler(t, address, true, nil, ` + // resource R { + // access(all) var id: Int + // + // init() { + // self.id = 1 + // } + // } + // + // fun test() { + // let r <-create R() + // let ref = &r as &R + // + // // Move the resource into the account + // account.storage.save(<-r, to: /storage/r) + // + // // 'Read' a field from the reference + // let id = ref.id + // }`, sema.Config{}, errorHandler(t)) + // + // _, err := inter.Invoke("test") + // RequireError(t, err) + // require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) + //}) + // + //t.Run("account to stack", func(t *testing.T) { + // + // t.Parallel() + // + // inter := parseCheckAndInterpret(t, ` + // resource R { + // access(all) var id: Int + // + // access(all) fun setID(_ id: Int) { + // self.id = id + // } + // + // init() { + // self.id = 1 + // } + // } + // + // fun test(target: auth(Mutate) &[R]) { + // target.append(<- create R()) + // + // // Take reference while in the account + // let ref = target[0] + // + // // Move the resource out of the account onto the stack + // let movedR <- target.remove(at: 0) + // + // // Update the reference + // ref.setID(2) + // + // destroy movedR + // } + // `) + // + // address := common.Address{0x1} + // + // rType := checker.RequireGlobalType(t, inter.Program.Elaboration, "R").(*sema.CompositeType) + // + // array := interpreter.NewArrayValue( + // inter, + // interpreter.EmptyLocationRange, + // &interpreter.VariableSizedStaticType{ + // Type: interpreter.ConvertSemaToStaticType(nil, rType), + // }, + // address, + // ) + // + // arrayRef := interpreter.NewUnmeteredEphemeralReferenceValue( + // inter, + // interpreter.NewEntitlementSetAuthorization( + // nil, + // func() []common.TypeID { return []common.TypeID{"Mutate"} }, + // 1, + // sema.Conjunction, + // ), + // array, + // &sema.VariableSizedType{ + // Type: rType, + // }, + // interpreter.EmptyLocationRange, + // ) + // + // _, err := inter.Invoke("test", arrayRef) + // RequireError(t, err) + // require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) + //}) + // + //t.Run("stack to stack", func(t *testing.T) { + // + // t.Parallel() + // + // inter, err := parseCheckAndInterpretWithOptions( + // t, + // ` + // resource R { + // access(all) var id: Int + // + // access(all) fun setID(_ id: Int) { + // self.id = id + // } + // + // init() { + // self.id = 1 + // } + // } + // + // fun test() { + // let r1 <-create R() + // let ref = &r1 as &R + // + // // Move the resource onto the same stack + // let r2 <- r1 + // + // // Update the reference + // ref.setID(2) + // + // destroy r2 + // }`, + // + // ParseCheckAndInterpretOptions{ + // HandleCheckerError: errorHandler(t), + // }, + // ) + // require.NoError(t, err) + // + // _, err = inter.Invoke("test") + // RequireError(t, err) + // require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) + //}) + // + //t.Run("one account to another account", func(t *testing.T) { + // + // t.Parallel() + // + // inter := parseCheckAndInterpret(t, ` + // resource R { + // access(all) var id: Int + // + // access(all) fun setID(_ id: Int) { + // self.id = id + // } + // + // init() { + // self.id = 1 + // } + // } + // + // fun test(target1: auth(Mutate) &[R], target2: auth(Mutate) &[R]) { + // target1.append(<- create R()) + // + // // Take reference while in the account_1 + // let ref = target1[0] + // + // // Move the resource out of the account_1 into the account_2 + // target2.append(<- target1.remove(at: 0)) + // + // // Update the reference + // ref.setID(2) + // } + // `) + // + // rType := checker.RequireGlobalType(t, inter.Program.Elaboration, "R").(*sema.CompositeType) + // + // // Resource array in account 0x01 + // + // array1 := interpreter.NewArrayValue( + // inter, + // interpreter.EmptyLocationRange, + // &interpreter.VariableSizedStaticType{ + // Type: interpreter.ConvertSemaToStaticType(nil, rType), + // }, + // common.Address{0x1}, + // ) + // + // arrayRef1 := interpreter.NewUnmeteredEphemeralReferenceValue( + // inter, + // interpreter.NewEntitlementSetAuthorization( + // nil, + // func() []common.TypeID { return []common.TypeID{"Mutate"} }, + // 1, + // sema.Conjunction, + // ), + // array1, + // &sema.VariableSizedType{ + // Type: rType, + // }, + // interpreter.EmptyLocationRange, + // ) + // + // // Resource array in account 0x02 + // + // array2 := interpreter.NewArrayValue( + // inter, + // interpreter.EmptyLocationRange, + // &interpreter.VariableSizedStaticType{ + // Type: interpreter.ConvertSemaToStaticType(nil, rType), + // }, + // common.Address{0x2}, + // ) + // + // arrayRef2 := interpreter.NewUnmeteredEphemeralReferenceValue( + // inter, + // interpreter.NewEntitlementSetAuthorization( + // nil, + // func() []common.TypeID { return []common.TypeID{"Mutate"} }, + // 1, + // sema.Conjunction, + // ), + // array2, + // &sema.VariableSizedType{ + // Type: rType, + // }, + // interpreter.EmptyLocationRange, + // ) + // + // _, err := inter.Invoke("test", arrayRef1, arrayRef2) + // RequireError(t, err) + // require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) + //}) +} diff --git a/runtime/bbq/vm/test/utils.go b/runtime/bbq/vm/test/utils.go index 7b767ae537..546ec63e8c 100644 --- a/runtime/bbq/vm/test/utils.go +++ b/runtime/bbq/vm/test/utils.go @@ -2,6 +2,7 @@ package test import ( "fmt" + "github.com/onflow/cadence/runtime/bbq/vm" "testing" "github.com/stretchr/testify/require" @@ -445,3 +446,18 @@ func compile(t testing.TB, checker *sema.Checker, programs map[common.Location]c printProgram(program) return program } + +func compileAndInvoke(t testing.TB, code string, funcName string) (vm.Value, error) { + location := common.ScriptLocation{0x1} + program := compileCode(t, code, location, map[common.Location]compiledProgram{}) + storage := interpreter.NewInMemoryStorage(nil) + barVM := vm.NewVM( + program, + &vm.Config{ + Storage: storage, + AccountHandler: &testAccountHandler{}, + }, + ) + + return barVM.Invoke("test") +} diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index a015a3a6fa..748c60db15 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -560,6 +560,18 @@ func opNewArray(vm *VM) { vm.push(array) } +func opNewRef(vm *VM) { + borrowedType := vm.loadType().(*interpreter.ReferenceStaticType) + value := vm.pop() + + ref := NewEphemeralReferenceValue( + value, + borrowedType.Authorization, + borrowedType.ReferencedType, + ) + vm.push(ref) +} + func (vm *VM) run() { for { @@ -621,6 +633,8 @@ func (vm *VM) run() { opNew(vm) case opcode.NewArray: opNewArray(vm) + case opcode.NewRef: + opNewRef(vm) case opcode.SetField: opSetField(vm) case opcode.GetField: From cb2a8be520380725c54966c9d122f2ca1f510b25 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 23 Oct 2024 15:59:16 -0700 Subject: [PATCH 50/89] Add resource reference tracking --- runtime/bbq/compiler/compiler.go | 4 + runtime/bbq/vm/config.go | 20 +++ runtime/bbq/vm/native_functions.go | 2 +- runtime/bbq/vm/reference_tracking.go | 147 ++++++++++++++++++ runtime/bbq/vm/test/ft_test.go | 36 +++-- runtime/bbq/vm/test/runtime_test.go | 160 ++++++++++---------- runtime/bbq/vm/test/utils.go | 12 +- runtime/bbq/vm/test/vm_test.go | 42 +++++ runtime/bbq/vm/value.go | 17 +++ runtime/bbq/vm/value_account.go | 6 + runtime/bbq/vm/value_array.go | 75 ++++++++- runtime/bbq/vm/value_composite.go | 94 +++++++++--- runtime/bbq/vm/value_dictionary.go | 76 +++++++++- runtime/bbq/vm/value_ephemeral_reference.go | 7 +- runtime/bbq/vm/value_some.go | 6 + runtime/bbq/vm/vm.go | 18 ++- runtime/ft_test.go | 6 +- runtime/runtime.go | 4 +- 18 files changed, 595 insertions(+), 137 deletions(-) create mode 100644 runtime/bbq/vm/reference_tracking.go diff --git a/runtime/bbq/compiler/compiler.go b/runtime/bbq/compiler/compiler.go index d09954ef89..edbd58570e 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/runtime/bbq/compiler/compiler.go @@ -594,6 +594,10 @@ func (c *Compiler) VisitSwitchStatement(_ *ast.SwitchStatement) (_ struct{}) { func (c *Compiler) VisitVariableDeclaration(declaration *ast.VariableDeclaration) (_ struct{}) { // TODO: second value c.compileExpression(declaration.Value) + + varDeclTypes := c.Elaboration.VariableDeclarationTypes(declaration) + c.emitCheckType(varDeclTypes.TargetType) + local := c.currentFunction.declareLocal(declaration.Identifier.Identifier) first, second := encodeUint16(local.index) c.emit(opcode.SetLocal, first, second) diff --git a/runtime/bbq/vm/config.go b/runtime/bbq/vm/config.go index 529b44caa0..2ce9097dca 100644 --- a/runtime/bbq/vm/config.go +++ b/runtime/bbq/vm/config.go @@ -37,12 +37,32 @@ type Config struct { // TODO: Move these to a 'shared state'? CapabilityControllerIterations map[AddressPath]int MutationDuringCapabilityControllerIteration bool + referencedResourceKindedValues ReferencedResourceKindedValues // TODO: These are temporary. Remove once storing/reading is supported for VM values. inter *interpreter.Interpreter TypeLoader func(location common.Location, typeID interpreter.TypeID) sema.CompositeKindedType } +func NewConfig(storage interpreter.Storage) *Config { + return &Config{ + Storage: storage, + MemoryGauge: nil, + ImportHandler: nil, + ContractValueHandler: nil, + AccountHandler: nil, + + CapabilityControllerIterations: make(map[AddressPath]int), + MutationDuringCapabilityControllerIteration: false, + referencedResourceKindedValues: ReferencedResourceKindedValues{}, + } +} + +func (c *Config) WithAccountHandler(handler stdlib.AccountHandler) *Config { + c.AccountHandler = handler + return c +} + // TODO: This is temporary. Remove once storing/reading is supported for VM values. func (c *Config) interpreter() *interpreter.Interpreter { if c.inter == nil { diff --git a/runtime/bbq/vm/native_functions.go b/runtime/bbq/vm/native_functions.go index e4676c26bf..cf099401ed 100644 --- a/runtime/bbq/vm/native_functions.go +++ b/runtime/bbq/vm/native_functions.go @@ -72,7 +72,7 @@ func init() { ParameterCount: len(stdlib.PanicFunctionType.Parameters), Function: func(config *Config, typeArguments []StaticType, arguments ...Value) Value { address := arguments[0].(AddressValue) - return NewAccountReferenceValue(common.Address(address)) + return NewAccountReferenceValue(config, common.Address(address)) }, }) } diff --git a/runtime/bbq/vm/reference_tracking.go b/runtime/bbq/vm/reference_tracking.go new file mode 100644 index 0000000000..3e5ac03927 --- /dev/null +++ b/runtime/bbq/vm/reference_tracking.go @@ -0,0 +1,147 @@ +package vm + +import ( + "github.com/onflow/atree" + + "github.com/onflow/cadence/runtime/interpreter" +) + +type ReferencedResourceKindedValues map[atree.ValueID]map[*EphemeralReferenceValue]struct{} + +func maybeTrackReferencedResourceKindedValue(conf *Config, value Value) { + referenceValue, ok := value.(*EphemeralReferenceValue) + if !ok { + return + } + + referenceTRackedValue, ok := referenceValue.Value.(ReferenceTrackedResourceKindedValue) + if !ok { + return + } + + trackReferencedResourceKindedValue( + conf, + referenceTRackedValue.ValueID(), + referenceValue, + ) +} + +func trackReferencedResourceKindedValue( + conf *Config, + id atree.ValueID, + value *EphemeralReferenceValue, +) { + values := conf.referencedResourceKindedValues[id] + if values == nil { + values = map[*EphemeralReferenceValue]struct{}{} + conf.referencedResourceKindedValues[id] = values + } + values[value] = struct{}{} +} + +func checkInvalidatedResourceOrResourceReference(value Value) { + + // Unwrap SomeValue, to access references wrapped inside optionals. + someValue, isSomeValue := value.(*SomeValue) + for isSomeValue && someValue.value != nil { + value = someValue.value + someValue, isSomeValue = value.(*SomeValue) + } + + switch value := value.(type) { + // TODO: + //case ResourceKindedValue: + // if value.isInvalidatedResource(interpreter) { + // panic(InvalidatedResourceError{ + // LocationRange: LocationRange{ + // Location: interpreter.Location, + // HasPosition: hasPosition, + // }, + // }) + // } + case *EphemeralReferenceValue: + if value.Value == nil { + panic(interpreter.InvalidatedResourceReferenceError{ + //LocationRange: interpreter.LocationRange{ + // Location: interpreter.Location, + // HasPosition: hasPosition, + //}, + }) + } else { + // If the value is there, check whether the referenced value is an invalidated one. + // This step is not really needed, since reference tracking is supposed to clear the + // `value.Value` if the referenced-value was moved/deleted. + // However, have this as a second layer of defensive. + checkInvalidatedResourceOrResourceReference(value.Value) + } + } +} + +func invalidateReferencedResources( + conf *Config, + value Value, +) { + // skip non-resource typed values + resourceKinded, ok := value.(ResourceKindedValue) + if !ok || !resourceKinded.IsResourceKinded() { + return + } + + var valueID atree.ValueID + + switch value := resourceKinded.(type) { + case *CompositeValue: + value.ForEachReadOnlyLoadedField( + conf, + func(_ string, fieldValue Value) (resume bool) { + invalidateReferencedResources(conf, fieldValue) + // continue iteration + return true + }, + ) + valueID = value.ValueID() + + case *DictionaryValue: + value.IterateReadOnlyLoaded( + conf, + func(_, value Value) (resume bool) { + invalidateReferencedResources(conf, value) + return true + }, + ) + valueID = value.ValueID() + + case *ArrayValue: + value.IterateReadOnlyLoaded( + conf, + func(element Value) (resume bool) { + invalidateReferencedResources(conf, element) + return true + }, + ) + valueID = value.ValueID() + + case *SomeValue: + invalidateReferencedResources(conf, value.value) + return + + default: + // skip non-container typed values. + return + } + + values := conf.referencedResourceKindedValues[valueID] + if values == nil { + return + } + + for value := range values { //nolint:maprange + value.Value = nil + } + + // The old resource instances are already cleared/invalidated above. + // So no need to track those stale resources anymore. We will not need to update/clear them again. + // Therefore, remove them from the mapping. + // This is only to allow GC. No impact to the behavior. + delete(conf.referencedResourceKindedValues, valueID) +} diff --git a/runtime/bbq/vm/test/ft_test.go b/runtime/bbq/vm/test/ft_test.go index c63fbdd480..37b7f94bb4 100644 --- a/runtime/bbq/vm/test/ft_test.go +++ b/runtime/bbq/vm/test/ft_test.go @@ -53,15 +53,17 @@ func TestFTTransfer(t *testing.T) { flowTokenProgram := compileCode(t, realFlowContract, flowTokenLocation, programs) + config := &vm.Config{ + Storage: storage, + AccountHandler: &testAccountHandler{}, + } + flowTokenVM := vm.NewVM( flowTokenProgram, - &vm.Config{ - Storage: storage, - AccountHandler: &testAccountHandler{}, - }, + config, ) - authAccount := vm.NewAuthAccountReferenceValue(contractsAddress) + authAccount := vm.NewAuthAccountReferenceValue(config, contractsAddress) flowTokenContractValue, err := flowTokenVM.InitializeContract(authAccount) require.NoError(t, err) @@ -114,7 +116,7 @@ func TestFTTransfer(t *testing.T) { setupTxVM := vm.NewVM(program, vmConfig) - authorizer := vm.NewAuthAccountReferenceValue(address) + authorizer := vm.NewAuthAccountReferenceValue(vmConfig, address) err = setupTxVM.ExecuteTransaction(nil, authorizer) require.NoError(t, err) require.Equal(t, 0, setupTxVM.StackSize()) @@ -133,7 +135,7 @@ func TestFTTransfer(t *testing.T) { vm.IntValue{total}, } - mintTxAuthorizer := vm.NewAuthAccountReferenceValue(contractsAddress) + mintTxAuthorizer := vm.NewAuthAccountReferenceValue(vmConfig, contractsAddress) err = mintTxVM.ExecuteTransaction(mintTxArgs, mintTxAuthorizer) require.NoError(t, err) require.Equal(t, 0, mintTxVM.StackSize()) @@ -151,7 +153,7 @@ func TestFTTransfer(t *testing.T) { vm.AddressValue(receiverAddress), } - tokenTransferTxAuthorizer := vm.NewAuthAccountReferenceValue(senderAddress) + tokenTransferTxAuthorizer := vm.NewAuthAccountReferenceValue(vmConfig, senderAddress) err = tokenTransferTxVM.ExecuteTransaction(tokenTransferTxArgs, tokenTransferTxAuthorizer) require.NoError(t, err) require.Equal(t, 0, tokenTransferTxVM.StackSize()) @@ -638,15 +640,17 @@ func BenchmarkFTTransfer(b *testing.B) { flowTokenLocation := common.NewAddressLocation(nil, contractsAddress, "FlowToken") flowTokenProgram := compileCode(b, realFlowContract, flowTokenLocation, programs) + config := &vm.Config{ + Storage: storage, + AccountHandler: &testAccountHandler{}, + } + flowTokenVM := vm.NewVM( flowTokenProgram, - &vm.Config{ - Storage: storage, - AccountHandler: &testAccountHandler{}, - }, + config, ) - authAccount := vm.NewAuthAccountReferenceValue(contractsAddress) + authAccount := vm.NewAuthAccountReferenceValue(config, contractsAddress) flowTokenContractValue, err := flowTokenVM.InitializeContract(authAccount) require.NoError(b, err) @@ -700,7 +704,7 @@ func BenchmarkFTTransfer(b *testing.B) { setupTxVM := vm.NewVM(program, vmConfig) - authorizer := vm.NewAuthAccountReferenceValue(address) + authorizer := vm.NewAuthAccountReferenceValue(vmConfig, address) err = setupTxVM.ExecuteTransaction(nil, authorizer) require.NoError(b, err) require.Equal(b, 0, setupTxVM.StackSize()) @@ -719,7 +723,7 @@ func BenchmarkFTTransfer(b *testing.B) { vm.IntValue{total}, } - mintTxAuthorizer := vm.NewAuthAccountReferenceValue(contractsAddress) + mintTxAuthorizer := vm.NewAuthAccountReferenceValue(vmConfig, contractsAddress) err = mintTxVM.ExecuteTransaction(mintTxArgs, mintTxAuthorizer) require.NoError(b, err) require.Equal(b, 0, mintTxVM.StackSize()) @@ -735,7 +739,7 @@ func BenchmarkFTTransfer(b *testing.B) { vm.AddressValue(receiverAddress), } - tokenTransferTxAuthorizer := vm.NewAuthAccountReferenceValue(senderAddress) + tokenTransferTxAuthorizer := vm.NewAuthAccountReferenceValue(vmConfig, senderAddress) b.ReportAllocs() b.ResetTimer() diff --git a/runtime/bbq/vm/test/runtime_test.go b/runtime/bbq/vm/test/runtime_test.go index 57e2f86cf6..73d5ff3ccd 100644 --- a/runtime/bbq/vm/test/runtime_test.go +++ b/runtime/bbq/vm/test/runtime_test.go @@ -105,7 +105,7 @@ func TestResourceLossViaSelfRugPull(t *testing.T) { access(all) fun loser(_ victim: @Bar.Vault): Void{ var array: @[R] <- [<- create R()] let arrRef = &array as auth(Remove) &[R] - fun rugPullCallback(): Void{ + fun rugPullCallback(): Void { // Here we move the R resource from the array to a contract field // invalidating the "self" during the execution of rugpullAndAssign Foo.rCopy1 <-! arrRef.removeLast() @@ -151,7 +151,7 @@ func TestResourceLossViaSelfRugPull(t *testing.T) { txVM := vm.NewVM(program, vmConfig) - authorizer := vm.NewAuthAccountReferenceValue(authorizerAddress) + authorizer := vm.NewAuthAccountReferenceValue(vmConfig, authorizerAddress) err = txVM.ExecuteTransaction(nil, authorizer) require.NoError(t, err) require.Equal(t, 0, txVM.StackSize()) @@ -161,42 +161,42 @@ func TestInterpretResourceReferenceInvalidationOnMove(t *testing.T) { t.Parallel() - t.Run("stack to account", func(t *testing.T) { - - t.Parallel() - - code := ` - resource R { - access(all) var id: Int - - access(all) fun setID(_ id: Int) { - self.id = id - } - - init() { - self.id = 1 - } - } - - fun getRef(_ ref: &R): &R { - return ref - } - - fun test() { - let r <-create R() - let ref = getRef(&r as &R) - - // Move the resource into the account - account.storage.save(<-r, to: /storage/r) - - // Update the reference - ref.setID(2) - }` - - _, err := compileAndInvoke(t, code, "test") - require.Error(t, err) - require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) - }) + //t.Run("stack to account", func(t *testing.T) { + // + // t.Parallel() + // + // code := ` + // resource R { + // access(all) var id: Int + // + // access(all) fun setID(_ id: Int) { + // self.id = id + // } + // + // init() { + // self.id = 1 + // } + // } + // + // fun getRef(_ ref: &R): &R { + // return ref + // } + // + // fun test() { + // let r <-create R() + // let ref = getRef(&r as &R) + // + // // Move the resource into the account + // account.storage.save(<-r, to: /storage/r) + // + // // Update the reference + // ref.setID(2) + // }` + // + // _, err := compileAndInvoke(t, code, "test") + // require.Error(t, err) + // require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) + //}) // //t.Run("stack to account readonly", func(t *testing.T) { // @@ -294,50 +294,46 @@ func TestInterpretResourceReferenceInvalidationOnMove(t *testing.T) { // RequireError(t, err) // require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) //}) - // - //t.Run("stack to stack", func(t *testing.T) { - // - // t.Parallel() - // - // inter, err := parseCheckAndInterpretWithOptions( - // t, - // ` - // resource R { - // access(all) var id: Int - // - // access(all) fun setID(_ id: Int) { - // self.id = id - // } - // - // init() { - // self.id = 1 - // } - // } - // - // fun test() { - // let r1 <-create R() - // let ref = &r1 as &R - // - // // Move the resource onto the same stack - // let r2 <- r1 - // - // // Update the reference - // ref.setID(2) - // - // destroy r2 - // }`, - // - // ParseCheckAndInterpretOptions{ - // HandleCheckerError: errorHandler(t), - // }, - // ) - // require.NoError(t, err) - // - // _, err = inter.Invoke("test") - // RequireError(t, err) - // require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) - //}) - // + + t.Run("stack to stack", func(t *testing.T) { + + t.Parallel() + + code := ` + resource R { + access(all) var id: Int + + access(all) fun setID(_ id: Int) { + self.id = id + } + + init() { + self.id = 1 + } + } + + fun test() { + let r1 <- create R() + let ref = reference(&r1 as &R) + + // Move the resource onto the same stack + let r2 <- r1 + + // Update the reference + ref.setID(2) + + destroy r2 + } + + fun reference(_ ref: &R): &R { + return ref + }` + + _, err := compileAndInvoke(t, code, "test") + require.Error(t, err) + require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) + }) + //t.Run("one account to another account", func(t *testing.T) { // // t.Parallel() diff --git a/runtime/bbq/vm/test/utils.go b/runtime/bbq/vm/test/utils.go index 546ec63e8c..8a0bbc2c7e 100644 --- a/runtime/bbq/vm/test/utils.go +++ b/runtime/bbq/vm/test/utils.go @@ -443,7 +443,6 @@ func compile(t testing.TB, checker *sema.Checker, programs map[common.Location]c } program := comp.Compile() - printProgram(program) return program } @@ -451,13 +450,12 @@ func compileAndInvoke(t testing.TB, code string, funcName string) (vm.Value, err location := common.ScriptLocation{0x1} program := compileCode(t, code, location, map[common.Location]compiledProgram{}) storage := interpreter.NewInMemoryStorage(nil) - barVM := vm.NewVM( + + programVM := vm.NewVM( program, - &vm.Config{ - Storage: storage, - AccountHandler: &testAccountHandler{}, - }, + vm.NewConfig(storage). + WithAccountHandler(&testAccountHandler{}), ) - return barVM.Invoke("test") + return programVM.Invoke(funcName) } diff --git a/runtime/bbq/vm/test/vm_test.go b/runtime/bbq/vm/test/vm_test.go index 17abebacb1..01e6e3b1e7 100644 --- a/runtime/bbq/vm/test/vm_test.go +++ b/runtime/bbq/vm/test/vm_test.go @@ -2036,3 +2036,45 @@ func TestArrayLiteral(t *testing.T) { assert.Equal(t, vm.IntValue{SmallInt: 8}, array.Get(vmConfig, 2)) }) } + +func TestReference(t *testing.T) { + + t.Parallel() + + t.Run("method call", func(t *testing.T) { + t.Parallel() + + checker, err := ParseAndCheck(t, ` + struct Foo { + var id : String + + init(_ id: String) { + self.id = id + } + + fun sayHello(_ id: Int): String { + return self.id + } + } + + fun test(): String { + var foo = Foo("Hello from Foo!") + var ref = &foo as &Foo + return ref.sayHello(1) + } + `) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vmConfig := vm.NewConfig(nil) + vmInstance := vm.NewVM(program, vmConfig) + + result, err := vmInstance.Invoke("test") + require.NoError(t, err) + require.Equal(t, 0, vmInstance.StackSize()) + + require.Equal(t, vm.StringValue{Str: []byte("Hello from Foo!")}, result) + }) +} diff --git a/runtime/bbq/vm/value.go b/runtime/bbq/vm/value.go index 3c3ea9bb32..3c618abbda 100644 --- a/runtime/bbq/vm/value.go +++ b/runtime/bbq/vm/value.go @@ -40,3 +40,20 @@ type MemberAccessibleValue interface { GetMember(config *Config, name string) Value SetMember(conf *Config, name string, value Value) } + +type ResourceKindedValue interface { + Value + //Destroy(interpreter *Interpreter, locationRange LocationRange) + //IsDestroyed() bool + //isInvalidatedResource(*Interpreter) bool + IsResourceKinded() bool +} + +// ReferenceTrackedResourceKindedValue is a resource-kinded value +// that must be tracked when a reference of it is taken. +type ReferenceTrackedResourceKindedValue interface { + ResourceKindedValue + IsReferenceTrackedResourceKindedValue() + ValueID() atree.ValueID + IsStaleResource() bool +} diff --git a/runtime/bbq/vm/value_account.go b/runtime/bbq/vm/value_account.go index eea4acdd96..4d37cc64a7 100644 --- a/runtime/bbq/vm/value_account.go +++ b/runtime/bbq/vm/value_account.go @@ -32,28 +32,34 @@ type AccountIDGenerator interface { } func NewAuthAccountReferenceValue( + conf *Config, address common.Address, ) *EphemeralReferenceValue { return newAccountReferenceValue( + conf, address, interpreter.FullyEntitledAccountAccess, ) } func NewAccountReferenceValue( + conf *Config, address common.Address, ) *EphemeralReferenceValue { return newAccountReferenceValue( + conf, address, interpreter.UnauthorizedAccess, ) } func newAccountReferenceValue( + conf *Config, address common.Address, authorization interpreter.Authorization, ) *EphemeralReferenceValue { return NewEphemeralReferenceValue( + conf, newAccountValue(address), authorization, interpreter.PrimitiveStaticTypeAccount, diff --git a/runtime/bbq/vm/value_array.go b/runtime/bbq/vm/value_array.go index da19bf8a8d..b5648f722f 100644 --- a/runtime/bbq/vm/value_array.go +++ b/runtime/bbq/vm/value_array.go @@ -37,6 +37,7 @@ type ArrayValue struct { } var _ Value = &ArrayValue{} +var _ ReferenceTrackedResourceKindedValue = &ArrayValue{} func NewArrayValue( config *Config, @@ -229,8 +230,7 @@ func (v *ArrayValue) Transfer(config *Config, address atree.Address, remove bool // This allows raising an error when the resource array is attempted // to be transferred/moved again (see beginning of this function) - // TODO: - //interpreter.invalidateReferencedResources(v, locationRange) + invalidateReferencedResources(config, v) v.array = nil } @@ -273,6 +273,12 @@ func (v *ArrayValue) IsResourceKinded() bool { return v.isResourceKinded } +func (v *ArrayValue) IsReferenceTrackedResourceKindedValue() {} + +func (v *ArrayValue) IsStaleResource() bool { + return v.array == nil && v.IsResourceKinded() +} + func (v *ArrayValue) Count() int { return int(v.array.Count()) } @@ -358,3 +364,68 @@ func (v *ArrayValue) Set(config *Config, index int, element Value) { RemoveReferencedSlab(config.Storage, existingStorable) } + +func (v *ArrayValue) Iterate( + config *Config, + f func(element Value) (resume bool), + transferElements bool, +) { + v.iterate( + config, + v.array.Iterate, + f, + transferElements, + ) +} + +// IterateReadOnlyLoaded iterates over all LOADED elements of the array. +// DO NOT perform storage mutations in the callback! +func (v *ArrayValue) IterateReadOnlyLoaded( + config *Config, + f func(element Value) (resume bool), +) { + const transferElements = false + + v.iterate( + config, + v.array.IterateReadOnlyLoadedValues, + f, + transferElements, + ) +} + +func (v *ArrayValue) iterate( + config *Config, + atreeIterate func(fn atree.ArrayIterationFunc) error, + f func(element Value) (resume bool), + transferElements bool, +) { + iterate := func() { + err := atreeIterate(func(element atree.Value) (resume bool, err error) { + // atree.Array iteration provides low-level atree.Value, + // convert to high-level interpreter.Value + elementValue := MustConvertStoredValue(config, config, element) + checkInvalidatedResourceOrResourceReference(elementValue) + + if transferElements { + // Each element must be transferred before passing onto the function. + elementValue = elementValue.Transfer( + config, + atree.Address{}, + false, + nil, + ) + } + + resume = f(elementValue) + + return resume, nil + }) + if err != nil { + panic(errors.NewExternalError(err)) + } + } + + iterate() + //interpreter.withMutationPrevention(v.ValueID(), iterate) +} diff --git a/runtime/bbq/vm/value_composite.go b/runtime/bbq/vm/value_composite.go index 77a3f4bd28..28f1907edd 100644 --- a/runtime/bbq/vm/value_composite.go +++ b/runtime/bbq/vm/value_composite.go @@ -38,6 +38,7 @@ type CompositeValue struct { var _ Value = &CompositeValue{} var _ MemberAccessibleValue = &CompositeValue{} +var _ ReferenceTrackedResourceKindedValue = &CompositeValue{} func NewCompositeValue( location common.Location, @@ -303,26 +304,9 @@ func (v *CompositeValue) Transfer( // This allows raising an error when the resource is attempted // to be transferred/moved again (see beginning of this function) - //if interpreter.Config.InvalidatedResourceValidationEnabled { - // v.dictionary = nil - //} else { - // v.dictionary = dictionary - // res = v - //} - - //newStorageID := dictionary.StorageID() - // - //interpreter.updateReferencedResource( - // currentStorageID, - // newStorageID, - // func(value ReferenceTrackedResourceKindedValue) { - // compositeValue, ok := value.(*CompositeValue) - // if !ok { - // panic(errors.NewUnreachableError()) - // } - // compositeValue.dictionary = dictionary - // }, - //) + invalidateReferencedResources(config, v) + + v.dictionary = nil } if res == nil { @@ -448,3 +432,73 @@ func (v *CompositeValue) NeedsStoreTo(address atree.Address) bool { func (v *CompositeValue) StorageAddress() atree.Address { return v.dictionary.Address() } + +func (v *CompositeValue) IsReferenceTrackedResourceKindedValue() {} + +func (v *CompositeValue) ValueID() atree.ValueID { + return v.dictionary.ValueID() +} + +func (v *CompositeValue) IsStaleResource() bool { + return v.dictionary == nil && v.IsResourceKinded() +} + +// ForEachField iterates over all field-name field-value pairs of the composite value. +// It does NOT iterate over computed fields and functions! +func (v *CompositeValue) ForEachField( + config *Config, + f func(fieldName string, fieldValue Value) (resume bool), +) { + iterate := func(fn atree.MapEntryIterationFunc) error { + return v.dictionary.Iterate( + interpreter.StringAtreeValueComparator, + interpreter.StringAtreeValueHashInput, + fn, + ) + } + v.forEachField( + config, + iterate, + f, + ) +} + +// ForEachReadOnlyLoadedField iterates over all LOADED field-name field-value pairs of the composite value. +// It does NOT iterate over computed fields and functions! +// DO NOT perform storage mutations in the callback! +func (v *CompositeValue) ForEachReadOnlyLoadedField( + config *Config, + f func(fieldName string, fieldValue Value) (resume bool), +) { + v.forEachField( + config, + v.dictionary.IterateReadOnlyLoadedValues, + f, + ) +} + +func (v *CompositeValue) forEachField( + config *Config, + atreeIterate func(fn atree.MapEntryIterationFunc) error, + f func(fieldName string, fieldValue Value) (resume bool), +) { + err := atreeIterate(func(key atree.Value, atreeValue atree.Value) (resume bool, err error) { + value := MustConvertStoredValue( + config.MemoryGauge, + config.Storage, + atreeValue, + ) + + checkInvalidatedResourceOrResourceReference(value) + + resume = f( + string(key.(interpreter.StringAtreeValue)), + value, + ) + return + }) + + if err != nil { + panic(errors.NewExternalError(err)) + } +} diff --git a/runtime/bbq/vm/value_dictionary.go b/runtime/bbq/vm/value_dictionary.go index c13afd6864..d0b2ff2cb8 100644 --- a/runtime/bbq/vm/value_dictionary.go +++ b/runtime/bbq/vm/value_dictionary.go @@ -33,6 +33,7 @@ type DictionaryValue struct { var _ Value = &DictionaryValue{} var _ MemberAccessibleValue = &DictionaryValue{} +var _ ReferenceTrackedResourceKindedValue = &DictionaryValue{} func NewDictionaryValue( config *Config, @@ -290,8 +291,7 @@ func (v *DictionaryValue) Transfer( // This allows raising an error when the resource array is attempted // to be transferred/moved again (see beginning of this function) - // TODO: - //interpreter.invalidateReferencedResources(v, locationRange) + invalidateReferencedResources(config, v) v.dictionary = nil } @@ -321,6 +321,78 @@ func (v *DictionaryValue) StorageAddress() atree.Address { return v.dictionary.Address() } +func (v *DictionaryValue) IsReferenceTrackedResourceKindedValue() {} + +func (v *DictionaryValue) ValueID() atree.ValueID { + return v.dictionary.ValueID() +} + +func (v *DictionaryValue) IsStaleResource() bool { + return v.dictionary == nil && v.IsResourceKinded() +} + +func (v *DictionaryValue) Iterate( + config *Config, + f func(key, value Value) (resume bool), +) { + valueComparator := newValueComparator(config) + hashInputProvider := newHashInputProvider(config) + iterate := func(fn atree.MapEntryIterationFunc) error { + return v.dictionary.Iterate( + valueComparator, + hashInputProvider, + fn, + ) + } + v.iterate(config, iterate, f) +} + +// IterateReadOnlyLoaded iterates over all LOADED key-valye pairs of the array. +// DO NOT perform storage mutations in the callback! +func (v *DictionaryValue) IterateReadOnlyLoaded( + config *Config, + f func(key, value Value) (resume bool), +) { + v.iterate( + config, + v.dictionary.IterateReadOnlyLoadedValues, + f, + ) +} + +func (v *DictionaryValue) iterate( + config *Config, + atreeIterate func(fn atree.MapEntryIterationFunc) error, + f func(key Value, value Value) (resume bool), +) { + iterate := func() { + err := atreeIterate(func(key, value atree.Value) (resume bool, err error) { + // atree.OrderedMap iteration provides low-level atree.Value, + // convert to high-level interpreter.Value + + keyValue := MustConvertStoredValue(config.MemoryGauge, config.Storage, key) + valueValue := MustConvertStoredValue(config.MemoryGauge, config.Storage, value) + + checkInvalidatedResourceOrResourceReference(keyValue) + checkInvalidatedResourceOrResourceReference(valueValue) + + resume = f( + keyValue, + valueValue, + ) + + return resume, nil + }) + if err != nil { + panic(errors.NewExternalError(err)) + } + } + + // TODO: + //interpreter.withMutationPrevention(v.ValueID(), iterate) + iterate() +} + func newValueComparator(conf *Config) atree.ValueComparator { return func(storage atree.SlabStorage, atreeValue atree.Value, otherStorable atree.Storable) (bool, error) { inter := conf.interpreter() diff --git a/runtime/bbq/vm/value_ephemeral_reference.go b/runtime/bbq/vm/value_ephemeral_reference.go index 1ede98054e..2784172a13 100644 --- a/runtime/bbq/vm/value_ephemeral_reference.go +++ b/runtime/bbq/vm/value_ephemeral_reference.go @@ -45,15 +45,20 @@ var _ MemberAccessibleValue = &EphemeralReferenceValue{} var _ ReferenceValue = &EphemeralReferenceValue{} func NewEphemeralReferenceValue( + conf *Config, value Value, authorization interpreter.Authorization, borrowedType interpreter.StaticType, ) *EphemeralReferenceValue { - return &EphemeralReferenceValue{ + ref := &EphemeralReferenceValue{ Value: value, Authorization: authorization, BorrowedType: borrowedType, } + + maybeTrackReferencedResourceKindedValue(conf, ref) + + return ref } func (*EphemeralReferenceValue) isValue() {} diff --git a/runtime/bbq/vm/value_some.go b/runtime/bbq/vm/value_some.go index 83a06609cb..f9cfe8aa85 100644 --- a/runtime/bbq/vm/value_some.go +++ b/runtime/bbq/vm/value_some.go @@ -31,6 +31,7 @@ type SomeValue struct { var _ Value = &SomeValue{} var _ MemberAccessibleValue = &SomeValue{} +var _ ResourceKindedValue = &SomeValue{} func NewSomeValueNonCopying(value Value) *SomeValue { return &SomeValue{ @@ -67,3 +68,8 @@ func (v *SomeValue) SetMember(config *Config, name string, value Value) { memberAccessibleValue := (v.value).(MemberAccessibleValue) memberAccessibleValue.SetMember(config, name, value) } + +func (v *SomeValue) IsResourceKinded() bool { + resourceKinded, ok := v.value.(ResourceKindedValue) + return ok && resourceKinded.IsResourceKinded() +} diff --git a/runtime/bbq/vm/vm.go b/runtime/bbq/vm/vm.go index 748c60db15..36e440cc4a 100644 --- a/runtime/bbq/vm/vm.go +++ b/runtime/bbq/vm/vm.go @@ -20,6 +20,7 @@ package vm import ( "github.com/onflow/atree" + "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" @@ -76,6 +77,9 @@ func (vm *VM) pop() Value { value := vm.stack[lastIndex] vm.stack[lastIndex] = nil vm.stack = vm.stack[:lastIndex] + + checkInvalidatedResourceOrResourceReference(value) + return value } @@ -141,12 +145,23 @@ func (vm *VM) popCallFrame() { vm.callFrame = vm.callFrame.parent } -func (vm *VM) Invoke(name string, arguments ...Value) (Value, error) { +func (vm *VM) Invoke(name string, arguments ...Value) (v Value, err error) { function, ok := vm.globals[name] if !ok { return nil, errors.NewDefaultUserError("unknown function '%s'", name) } + defer func() { + recovered := recover() + if recovered == nil { + return + } + + // TODO: pass proper location + codesAndPrograms := runtime.NewCodesAndPrograms() + err = runtime.GetWrappedError(recovered, nil, codesAndPrograms) + }() + return vm.invoke(function, arguments) } @@ -565,6 +580,7 @@ func opNewRef(vm *VM) { value := vm.pop() ref := NewEphemeralReferenceValue( + vm.config, value, borrowedType.Authorization, borrowedType.ReferencedType, diff --git a/runtime/ft_test.go b/runtime/ft_test.go index 6088328408..eff4ffa403 100644 --- a/runtime/ft_test.go +++ b/runtime/ft_test.go @@ -234,9 +234,9 @@ access(all) contract interface FungibleToken { /// createEmptyVault allows any user to create a new Vault that has a zero balance /// - access(all) fun createEmptyVault(vaultType: Type): @{FungibleToken.Vault} { + access(all) fun createEmptyVault(): @{FungibleToken.Vault} { post { - result.getType() == vaultType: "The returned vault does not match the desired type" + // result.getType() == vaultType: "The returned vault does not match the desired type" result.balance == 0.0: "The newly created Vault must have zero balance" } } @@ -388,7 +388,7 @@ access(all) contract FlowToken: FungibleToken { // and store the returned Vault in their storage in order to allow their // account to be able to receive deposits of this token type. // - access(all) fun createEmptyVault(vaultType: Type): @FlowToken.Vault { + access(all) fun createEmptyVault(): @FlowToken.Vault { return <-create Vault(balance: 0.0) } diff --git a/runtime/runtime.go b/runtime/runtime.go index 25dc1704e9..2272903d8f 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -227,11 +227,11 @@ func (r *interpreterRuntime) Recover(onError func(Error), location Location, cod return } - err := getWrappedError(recovered, location, codesAndPrograms) + err := GetWrappedError(recovered, location, codesAndPrograms) onError(err) } -func getWrappedError(recovered any, location Location, codesAndPrograms CodesAndPrograms) Error { +func GetWrappedError(recovered any, location Location, codesAndPrograms CodesAndPrograms) Error { switch recovered := recovered.(type) { // If the error is already a `runtime.Error`, then avoid redundant wrapping. From e240da78ab1cb5d388422ef17998124a0bfef196 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 23 Oct 2024 16:35:03 -0700 Subject: [PATCH 51/89] Move contarcts and transactions used in tests to a separate file --- runtime/bbq/vm/test/ft_contracts_and_txs.go | 459 ++++++++++++++++++++ runtime/bbq/vm/test/ft_test.go | 442 +------------------ runtime/bbq/vm/test/runtime_test.go | 18 + runtime/bbq/vm/test/utils.go | 18 + runtime/bbq/vm/test/vm_test.go | 2 +- 5 files changed, 497 insertions(+), 442 deletions(-) create mode 100644 runtime/bbq/vm/test/ft_contracts_and_txs.go diff --git a/runtime/bbq/vm/test/ft_contracts_and_txs.go b/runtime/bbq/vm/test/ft_contracts_and_txs.go new file mode 100644 index 0000000000..bcd8b51fb8 --- /dev/null +++ b/runtime/bbq/vm/test/ft_contracts_and_txs.go @@ -0,0 +1,459 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package test + +const realFungibleTokenContractInterface = ` +/// FungibleToken +/// +/// The interface that fungible token contracts implement. +/// +access(all) contract interface FungibleToken { + + /// The total number of tokens in existence. + /// It is up to the implementer to ensure that the total supply + /// stays accurate and up to date + /// + access(all) var totalSupply: Int + + /// TokensInitialized + /// + /// The event that is emitted when the contract is created + /// + access(all) event TokensInitialized(initialSupply: Int) + + /// TokensWithdrawn + /// + /// The event that is emitted when tokens are withdrawn from a Vault + /// + access(all) event TokensWithdrawn(amount: Int, from: Address?) + + /// TokensDeposited + /// + /// The event that is emitted when tokens are deposited into a Vault + /// + access(all) event TokensDeposited(amount: Int, to: Address?) + + /// Provider + /// + /// The interface that enforces the requirements for withdrawing + /// tokens from the implementing type. + /// + /// It does not enforce requirements on 'balance' here, + /// because it leaves open the possibility of creating custom providers + /// that do not necessarily need their own balance. + /// + access(all) resource interface Provider { + + /// withdraw subtracts tokens from the owner's Vault + /// and returns a Vault with the removed tokens. + /// + /// The function's access level is public, but this is not a problem + /// because only the owner storing the resource in their account + /// can initially call this function. + /// + /// The owner may grant other accounts access by creating a private + /// capability that allows specific other users to access + /// the provider resource through a reference. + /// + /// The owner may also grant all accounts access by creating a public + /// capability that allows all users to access the provider + /// resource through a reference. + /// + access(all) fun withdraw(amount: Int): @{Vault} { + post { + // 'result' refers to the return value + result.balance == amount: + "Withdrawal amount must be the same as the balance of the withdrawn Vault" + } + } + } + + /// Receiver + /// + /// The interface that enforces the requirements for depositing + /// tokens into the implementing type. + /// + /// We do not include a condition that checks the balance because + /// we want to give users the ability to make custom receivers that + /// can do custom things with the tokens, like split them up and + /// send them to different places. + /// + access(all) resource interface Receiver { + + /// deposit takes a Vault and deposits it into the implementing resource type + /// + access(all) fun deposit(from: @{Vault}) + } + + /// Balance + /// + /// The interface that contains the 'balance' field of the Vault + /// and enforces that when new Vaults are created, the balance + /// is initialized correctly. + /// + access(all) resource interface Balance { + + /// The total balance of a vault + /// + access(all) var balance: Int + + init(balance: Int) { + post { + self.balance == balance: + "Balance must be initialized to the initial balance" + } + } + } + + /// Vault + /// + /// The resource that contains the functions to send and receive tokens. + /// + access(all) resource interface Vault: Provider, Receiver, Balance { + + // The declaration of a concrete type in a contract interface means that + // every Fungible Token contract that implements the FungibleToken interface + // must define a concrete 'Vault' resource that conforms to the 'Provider', 'Receiver', + // and 'Balance' interfaces, and declares their required fields and functions + + /// The total balance of the vault + /// + access(all) var balance: Int + + // The conforming type must declare an initializer + // that allows prioviding the initial balance of the Vault + // + init(balance: Int) + + /// withdraw subtracts 'amount' from the Vault's balance + /// and returns a new Vault with the subtracted balance + /// + access(all) fun withdraw(amount: Int): @{Vault} { + pre { + self.balance >= amount: + "Amount withdrawn must be less than or equal than the balance of the Vault" + } + post { + // use the special function 'before' to get the value of the 'balance' field + // at the beginning of the function execution + // + self.balance == before(self.balance) - amount: + "New Vault balance must be the difference of the previous balance and the withdrawn Vault" + } + } + + /// deposit takes a Vault and adds its balance to the balance of this Vault + /// + access(all) fun deposit(from: @{Vault}) { + post { + self.balance == before(self.balance) + before(from.balance): + "New Vault balance must be the sum of the previous balance and the deposited Vault" + } + } + } + + /// createEmptyVault allows any user to create a new Vault that has a zero balance + /// + access(all) fun createEmptyVault(): @{Vault} { + post { + result.balance == 0: "The newly created Vault must have zero balance" + } + } +} +` + +const realFlowContract = ` +import FungibleToken from 0x1 + +access(all) contract FlowToken: FungibleToken { + + // Total supply of Flow tokens in existence + access(all) var totalSupply: Int + + // Vault + // + // Each user stores an instance of only the Vault in their storage + // The functions in the Vault and governed by the pre and post conditions + // in FungibleToken when they are called. + // The checks happen at runtime whenever a function is called. + // + // Resources can only be created in the context of the contract that they + // are defined in, so there is no way for a malicious user to create Vaults + // out of thin air. A special Minter resource needs to be defined to mint + // new tokens. + // + access(all) resource Vault: FungibleToken.Vault { + + // holds the balance of a users tokens + access(all) var balance: Int + + // initialize the balance at resource creation time + init(balance: Int) { + self.balance = balance + } + + // withdraw + // + // Function that takes an integer amount as an argument + // and withdraws that amount from the Vault. + // It creates a new temporary Vault that is used to hold + // the money that is being transferred. It returns the newly + // created Vault to the context that called so it can be deposited + // elsewhere. + // + access(all) fun withdraw(amount: Int): @{FungibleToken.Vault} { + self.balance = self.balance - amount + return <-create Vault(balance: amount) + } + + // deposit + // + // Function that takes a Vault object as an argument and adds + // its balance to the balance of the owners Vault. + // It is allowed to destroy the sent Vault because the Vault + // was a temporary holder of the tokens. The Vault's balance has + // been consumed and therefore can be destroyed. + access(all) fun deposit(from: @{FungibleToken.Vault}) { + let vault <- from as! @FlowToken.Vault + self.balance = self.balance + vault.balance + vault.balance = 0 + destroy vault + } + } + + // createEmptyVault + // + // Function that creates a new Vault with a balance of zero + // and returns it to the calling context. A user must call this function + // and store the returned Vault in their storage in order to allow their + // account to be able to receive deposits of this token type. + // + access(all) fun createEmptyVault(): @{FungibleToken.Vault} { + return <-create Vault(balance: 0) + } + + access(all) resource Administrator { + // createNewMinter + // + // Function that creates and returns a new minter resource + // + access(all) fun createNewMinter(allowedAmount: Int): @Minter { + return <-create Minter(allowedAmount: allowedAmount) + } + + // createNewBurner + // + // Function that creates and returns a new burner resource + // + access(all) fun createNewBurner(): @Burner { + return <-create Burner() + } + } + + // Minter + // + // Resource object that token admin accounts can hold to mint new tokens. + // + access(all) resource Minter { + + // the amount of tokens that the minter is allowed to mint + access(all) var allowedAmount: Int + + // mintTokens + // + // Function that mints new tokens, adds them to the total supply, + // and returns them to the calling context. + // + access(all) fun mintTokens(amount: Int): @FlowToken.Vault { + pre { + amount > 0: "Amount minted must be greater than zero" + amount <= self.allowedAmount: "Amount minted must be less than the allowed amount" + } + FlowToken.totalSupply = FlowToken.totalSupply + amount + self.allowedAmount = self.allowedAmount - amount + return <-create Vault(balance: amount) + } + + init(allowedAmount: Int) { + self.allowedAmount = allowedAmount + } + } + + // Burner + // + // Resource object that token admin accounts can hold to burn tokens. + // + access(all) resource Burner { + + // burnTokens + // + // Function that destroys a Vault instance, effectively burning the tokens. + // + // Note: the burned tokens are automatically subtracted from the + // total supply in the Vault destructor. + // + access(all) fun burnTokens(from: @{FungibleToken.Vault}) { + let vault <- from as! @FlowToken.Vault + let amount = vault.balance + destroy vault + } + } + + init(adminAccount: auth(Storage, Capabilities) &Account) { + self.totalSupply = 0 + + // Create the Vault with the total supply of tokens and save it in storage + // + let vault <- create Vault(balance: self.totalSupply) + + adminAccount.storage.save(<-vault, to: /storage/flowTokenVault) + + // Create a public capability to the stored Vault that only exposes + // the 'deposit' method through the 'Receiver' interface + // + let receiverCapability = adminAccount.capabilities.storage.issue<&FlowToken.Vault>(/storage/flowTokenVault) + adminAccount.capabilities.publish(receiverCapability, at: /public/flowTokenReceiver) + + // Create a public capability to the stored Vault that only exposes + // the 'balance' field through the 'Balance' interface + // + let balanceCapability = adminAccount.capabilities.storage.issue<&FlowToken.Vault>(/storage/flowTokenVault) + adminAccount.capabilities.publish(balanceCapability, at: /public/flowTokenBalance) + + let admin <- create Administrator() + adminAccount.storage.save(<-admin, to: /storage/flowTokenAdmin) + } +} +` + +const realSetupFlowTokenAccountTransaction = ` +import FungibleToken from 0x1 +import FlowToken from 0x1 + +transaction { + + prepare(signer: auth(BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { + + var storagePath = /storage/flowTokenVault + + if signer.storage.borrow<&FlowToken.Vault>(from: storagePath) != nil { + return + } + + // Create a new flowToken Vault and put it in storage + signer.storage.save(<-FlowToken.createEmptyVault(), to: storagePath) + + // Create a public capability to the Vault that only exposes + // the deposit function through the Receiver interface + let vaultCap = signer.capabilities.storage.issue<&FlowToken.Vault>(storagePath) + + signer.capabilities.publish( + vaultCap, + at: /public/flowTokenReceiver + ) + + // Create a public capability to the Vault that only exposes + // the balance field through the Balance interface + let balanceCap = signer.capabilities.storage.issue<&FlowToken.Vault>(storagePath) + + signer.capabilities.publish( + balanceCap, + at: /public/flowTokenBalance + ) + } +} +` + +const realFlowTokenTransferTransaction = ` +import FungibleToken from 0x1 +import FlowToken from 0x1 + +transaction(amount: Int, to: Address) { + let sentVault: @{FungibleToken.Vault} + + prepare(signer: auth(BorrowValue) &Account) { + // Get a reference to the signer's stored vault + let vaultRef = signer.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) + ?? panic("Could not borrow reference to the owner's Vault!") + + // Withdraw tokens from the signer's stored vault + self.sentVault <- vaultRef.withdraw(amount: amount) + } + + execute { + // Get a reference to the recipient's Receiver + let receiverRef = getAccount(to) + .capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + .borrow() + ?? panic("Could not borrow receiver reference to the recipient's Vault") + + // Deposit the withdrawn tokens in the recipient's receiver + receiverRef.deposit(from: <-self.sentVault) + } +} +` + +const realMintFlowTokenTransaction = ` +import FungibleToken from 0x1 +import FlowToken from 0x1 + +transaction(recipient: Address, amount: Int) { + + /// Reference to the FlowToken Minter Resource object + let tokenAdmin: &FlowToken.Administrator + + /// Reference to the Fungible Token Receiver of the recipient + let tokenReceiver: &{FungibleToken.Receiver} + + prepare(signer: auth(BorrowValue) &Account) { + self.tokenAdmin = signer.storage + .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) + ?? panic("Signer is not the token admin") + + self.tokenReceiver = getAccount(recipient) + .capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) + .borrow() + ?? panic("Unable to borrow receiver reference") + } + + execute { + let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) + let mintedVault <- minter.mintTokens(amount: amount) + + self.tokenReceiver.deposit(from: <-mintedVault) + + destroy minter + } +} +` + +const realFlowTokenBalanceScript = ` +import FungibleToken from 0x1 +import FlowToken from 0x1 + +access(all) fun main(account: Address): Int { + + let vaultRef = getAccount(account) + .capabilities.get<&FlowToken.Vault>(/public/flowTokenBalance) + .borrow() + ?? panic("Could not borrow Balance reference to the Vault") + + return vaultRef.balance +} +` diff --git a/runtime/bbq/vm/test/ft_test.go b/runtime/bbq/vm/test/ft_test.go index 37b7f94bb4..1b26ba47a7 100644 --- a/runtime/bbq/vm/test/ft_test.go +++ b/runtime/bbq/vm/test/ft_test.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -181,446 +181,6 @@ func TestFTTransfer(t *testing.T) { } } -const realFungibleTokenContractInterface = ` -/// FungibleToken -/// -/// The interface that fungible token contracts implement. -/// -access(all) contract interface FungibleToken { - - /// The total number of tokens in existence. - /// It is up to the implementer to ensure that the total supply - /// stays accurate and up to date - /// - access(all) var totalSupply: Int - - /// TokensInitialized - /// - /// The event that is emitted when the contract is created - /// - access(all) event TokensInitialized(initialSupply: Int) - - /// TokensWithdrawn - /// - /// The event that is emitted when tokens are withdrawn from a Vault - /// - access(all) event TokensWithdrawn(amount: Int, from: Address?) - - /// TokensDeposited - /// - /// The event that is emitted when tokens are deposited into a Vault - /// - access(all) event TokensDeposited(amount: Int, to: Address?) - - /// Provider - /// - /// The interface that enforces the requirements for withdrawing - /// tokens from the implementing type. - /// - /// It does not enforce requirements on 'balance' here, - /// because it leaves open the possibility of creating custom providers - /// that do not necessarily need their own balance. - /// - access(all) resource interface Provider { - - /// withdraw subtracts tokens from the owner's Vault - /// and returns a Vault with the removed tokens. - /// - /// The function's access level is public, but this is not a problem - /// because only the owner storing the resource in their account - /// can initially call this function. - /// - /// The owner may grant other accounts access by creating a private - /// capability that allows specific other users to access - /// the provider resource through a reference. - /// - /// The owner may also grant all accounts access by creating a public - /// capability that allows all users to access the provider - /// resource through a reference. - /// - access(all) fun withdraw(amount: Int): @{Vault} { - post { - // 'result' refers to the return value - result.balance == amount: - "Withdrawal amount must be the same as the balance of the withdrawn Vault" - } - } - } - - /// Receiver - /// - /// The interface that enforces the requirements for depositing - /// tokens into the implementing type. - /// - /// We do not include a condition that checks the balance because - /// we want to give users the ability to make custom receivers that - /// can do custom things with the tokens, like split them up and - /// send them to different places. - /// - access(all) resource interface Receiver { - - /// deposit takes a Vault and deposits it into the implementing resource type - /// - access(all) fun deposit(from: @{Vault}) - } - - /// Balance - /// - /// The interface that contains the 'balance' field of the Vault - /// and enforces that when new Vaults are created, the balance - /// is initialized correctly. - /// - access(all) resource interface Balance { - - /// The total balance of a vault - /// - access(all) var balance: Int - - init(balance: Int) { - post { - self.balance == balance: - "Balance must be initialized to the initial balance" - } - } - } - - /// Vault - /// - /// The resource that contains the functions to send and receive tokens. - /// - access(all) resource interface Vault: Provider, Receiver, Balance { - - // The declaration of a concrete type in a contract interface means that - // every Fungible Token contract that implements the FungibleToken interface - // must define a concrete 'Vault' resource that conforms to the 'Provider', 'Receiver', - // and 'Balance' interfaces, and declares their required fields and functions - - /// The total balance of the vault - /// - access(all) var balance: Int - - // The conforming type must declare an initializer - // that allows prioviding the initial balance of the Vault - // - init(balance: Int) - - /// withdraw subtracts 'amount' from the Vault's balance - /// and returns a new Vault with the subtracted balance - /// - access(all) fun withdraw(amount: Int): @{Vault} { - pre { - self.balance >= amount: - "Amount withdrawn must be less than or equal than the balance of the Vault" - } - post { - // use the special function 'before' to get the value of the 'balance' field - // at the beginning of the function execution - // - self.balance == before(self.balance) - amount: - "New Vault balance must be the difference of the previous balance and the withdrawn Vault" - } - } - - /// deposit takes a Vault and adds its balance to the balance of this Vault - /// - access(all) fun deposit(from: @{Vault}) { - post { - self.balance == before(self.balance) + before(from.balance): - "New Vault balance must be the sum of the previous balance and the deposited Vault" - } - } - } - - /// createEmptyVault allows any user to create a new Vault that has a zero balance - /// - access(all) fun createEmptyVault(): @{Vault} { - post { - result.balance == 0: "The newly created Vault must have zero balance" - } - } -} -` - -const realFlowContract = ` -import FungibleToken from 0x1 - -access(all) contract FlowToken: FungibleToken { - - // Total supply of Flow tokens in existence - access(all) var totalSupply: Int - - // Vault - // - // Each user stores an instance of only the Vault in their storage - // The functions in the Vault and governed by the pre and post conditions - // in FungibleToken when they are called. - // The checks happen at runtime whenever a function is called. - // - // Resources can only be created in the context of the contract that they - // are defined in, so there is no way for a malicious user to create Vaults - // out of thin air. A special Minter resource needs to be defined to mint - // new tokens. - // - access(all) resource Vault: FungibleToken.Vault { - - // holds the balance of a users tokens - access(all) var balance: Int - - // initialize the balance at resource creation time - init(balance: Int) { - self.balance = balance - } - - // withdraw - // - // Function that takes an integer amount as an argument - // and withdraws that amount from the Vault. - // It creates a new temporary Vault that is used to hold - // the money that is being transferred. It returns the newly - // created Vault to the context that called so it can be deposited - // elsewhere. - // - access(all) fun withdraw(amount: Int): @{FungibleToken.Vault} { - self.balance = self.balance - amount - return <-create Vault(balance: amount) - } - - // deposit - // - // Function that takes a Vault object as an argument and adds - // its balance to the balance of the owners Vault. - // It is allowed to destroy the sent Vault because the Vault - // was a temporary holder of the tokens. The Vault's balance has - // been consumed and therefore can be destroyed. - access(all) fun deposit(from: @{FungibleToken.Vault}) { - let vault <- from as! @FlowToken.Vault - self.balance = self.balance + vault.balance - vault.balance = 0 - destroy vault - } - } - - // createEmptyVault - // - // Function that creates a new Vault with a balance of zero - // and returns it to the calling context. A user must call this function - // and store the returned Vault in their storage in order to allow their - // account to be able to receive deposits of this token type. - // - access(all) fun createEmptyVault(): @{FungibleToken.Vault} { - return <-create Vault(balance: 0) - } - - access(all) resource Administrator { - // createNewMinter - // - // Function that creates and returns a new minter resource - // - access(all) fun createNewMinter(allowedAmount: Int): @Minter { - return <-create Minter(allowedAmount: allowedAmount) - } - - // createNewBurner - // - // Function that creates and returns a new burner resource - // - access(all) fun createNewBurner(): @Burner { - return <-create Burner() - } - } - - // Minter - // - // Resource object that token admin accounts can hold to mint new tokens. - // - access(all) resource Minter { - - // the amount of tokens that the minter is allowed to mint - access(all) var allowedAmount: Int - - // mintTokens - // - // Function that mints new tokens, adds them to the total supply, - // and returns them to the calling context. - // - access(all) fun mintTokens(amount: Int): @FlowToken.Vault { - pre { - amount > 0: "Amount minted must be greater than zero" - amount <= self.allowedAmount: "Amount minted must be less than the allowed amount" - } - FlowToken.totalSupply = FlowToken.totalSupply + amount - self.allowedAmount = self.allowedAmount - amount - return <-create Vault(balance: amount) - } - - init(allowedAmount: Int) { - self.allowedAmount = allowedAmount - } - } - - // Burner - // - // Resource object that token admin accounts can hold to burn tokens. - // - access(all) resource Burner { - - // burnTokens - // - // Function that destroys a Vault instance, effectively burning the tokens. - // - // Note: the burned tokens are automatically subtracted from the - // total supply in the Vault destructor. - // - access(all) fun burnTokens(from: @{FungibleToken.Vault}) { - let vault <- from as! @FlowToken.Vault - let amount = vault.balance - destroy vault - } - } - - init(adminAccount: auth(Storage, Capabilities) &Account) { - self.totalSupply = 0 - - // Create the Vault with the total supply of tokens and save it in storage - // - let vault <- create Vault(balance: self.totalSupply) - - adminAccount.storage.save(<-vault, to: /storage/flowTokenVault) - - // Create a public capability to the stored Vault that only exposes - // the 'deposit' method through the 'Receiver' interface - // - let receiverCapability = adminAccount.capabilities.storage.issue<&FlowToken.Vault>(/storage/flowTokenVault) - adminAccount.capabilities.publish(receiverCapability, at: /public/flowTokenReceiver) - - // Create a public capability to the stored Vault that only exposes - // the 'balance' field through the 'Balance' interface - // - let balanceCapability = adminAccount.capabilities.storage.issue<&FlowToken.Vault>(/storage/flowTokenVault) - adminAccount.capabilities.publish(balanceCapability, at: /public/flowTokenBalance) - - let admin <- create Administrator() - adminAccount.storage.save(<-admin, to: /storage/flowTokenAdmin) - } -} -` - -const realSetupFlowTokenAccountTransaction = ` -import FungibleToken from 0x1 -import FlowToken from 0x1 - -transaction { - - prepare(signer: auth(BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { - - var storagePath = /storage/flowTokenVault - - if signer.storage.borrow<&FlowToken.Vault>(from: storagePath) != nil { - return - } - - // Create a new flowToken Vault and put it in storage - signer.storage.save(<-FlowToken.createEmptyVault(), to: storagePath) - - // Create a public capability to the Vault that only exposes - // the deposit function through the Receiver interface - let vaultCap = signer.capabilities.storage.issue<&FlowToken.Vault>(storagePath) - - signer.capabilities.publish( - vaultCap, - at: /public/flowTokenReceiver - ) - - // Create a public capability to the Vault that only exposes - // the balance field through the Balance interface - let balanceCap = signer.capabilities.storage.issue<&FlowToken.Vault>(storagePath) - - signer.capabilities.publish( - balanceCap, - at: /public/flowTokenBalance - ) - } -} -` - -const realFlowTokenTransferTransaction = ` -import FungibleToken from 0x1 -import FlowToken from 0x1 - -transaction(amount: Int, to: Address) { - let sentVault: @{FungibleToken.Vault} - - prepare(signer: auth(BorrowValue) &Account) { - // Get a reference to the signer's stored vault - let vaultRef = signer.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) - ?? panic("Could not borrow reference to the owner's Vault!") - - // Withdraw tokens from the signer's stored vault - self.sentVault <- vaultRef.withdraw(amount: amount) - } - - execute { - // Get a reference to the recipient's Receiver - let receiverRef = getAccount(to) - .capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) - .borrow() - ?? panic("Could not borrow receiver reference to the recipient's Vault") - - // Deposit the withdrawn tokens in the recipient's receiver - receiverRef.deposit(from: <-self.sentVault) - } -} -` - -const realMintFlowTokenTransaction = ` -import FungibleToken from 0x1 -import FlowToken from 0x1 - -transaction(recipient: Address, amount: Int) { - - /// Reference to the FlowToken Minter Resource object - let tokenAdmin: &FlowToken.Administrator - - /// Reference to the Fungible Token Receiver of the recipient - let tokenReceiver: &{FungibleToken.Receiver} - - prepare(signer: auth(BorrowValue) &Account) { - self.tokenAdmin = signer.storage - .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) - ?? panic("Signer is not the token admin") - - self.tokenReceiver = getAccount(recipient) - .capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) - .borrow() - ?? panic("Unable to borrow receiver reference") - } - - execute { - let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) - let mintedVault <- minter.mintTokens(amount: amount) - - self.tokenReceiver.deposit(from: <-mintedVault) - - destroy minter - } -} -` - -const realFlowTokenBalanceScript = ` -import FungibleToken from 0x1 -import FlowToken from 0x1 - -access(all) fun main(account: Address): Int { - - let vaultRef = getAccount(account) - .capabilities.get<&FlowToken.Vault>(/public/flowTokenBalance) - .borrow() - ?? panic("Could not borrow Balance reference to the Vault") - - return vaultRef.balance -} -` - func BenchmarkFTTransfer(b *testing.B) { // ---- Deploy FT Contract ----- diff --git a/runtime/bbq/vm/test/runtime_test.go b/runtime/bbq/vm/test/runtime_test.go index 73d5ff3ccd..95374a0afa 100644 --- a/runtime/bbq/vm/test/runtime_test.go +++ b/runtime/bbq/vm/test/runtime_test.go @@ -1,3 +1,21 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package test import ( diff --git a/runtime/bbq/vm/test/utils.go b/runtime/bbq/vm/test/utils.go index 8a0bbc2c7e..614f9b193d 100644 --- a/runtime/bbq/vm/test/utils.go +++ b/runtime/bbq/vm/test/utils.go @@ -1,3 +1,21 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package test import ( diff --git a/runtime/bbq/vm/test/vm_test.go b/runtime/bbq/vm/test/vm_test.go index 01e6e3b1e7..dfe138a83a 100644 --- a/runtime/bbq/vm/test/vm_test.go +++ b/runtime/bbq/vm/test/vm_test.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From de92828801f817cb8496b7a7e39460c43cdcb6ee Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 24 Oct 2024 12:09:47 -0700 Subject: [PATCH 52/89] Move compiler and vm to top level --- {runtime/bbq => bbq}/bytecode_printer.go | 6 +++--- {runtime/bbq => bbq}/commons/constants.go | 0 {runtime/bbq => bbq}/commons/handlers.go | 2 +- {runtime/bbq => bbq}/commons/utils.go | 0 {runtime/bbq => bbq}/compiler/compiler.go | 10 +++++----- {runtime/bbq => bbq}/compiler/compiler_test.go | 6 +++--- {runtime/bbq => bbq}/compiler/config.go | 2 +- {runtime/bbq => bbq}/compiler/constant.go | 2 +- {runtime/bbq => bbq}/compiler/function.go | 2 +- {runtime/bbq => bbq}/compiler/global.go | 0 {runtime/bbq => bbq}/compiler/local.go | 0 {runtime/bbq => bbq}/compiler/loop.go | 0 {runtime/bbq => bbq}/compiler/native_functions.go | 2 +- {runtime/bbq => bbq}/compiler/stack.go | 0 {runtime/bbq => bbq}/constant.go | 2 +- {runtime/bbq => bbq}/constantkind/constantkind.go | 0 .../bbq => bbq}/constantkind/constantkind_string.go | 0 {runtime/bbq => bbq}/contract.go | 0 {runtime/bbq => bbq}/function.go | 0 {runtime/bbq => bbq}/import.go | 0 {runtime/bbq => bbq}/leb128/leb128.go | 0 {runtime/bbq => bbq}/leb128/leb128_test.go | 0 {runtime/bbq => bbq}/opcode/opcode.go | 0 {runtime/bbq => bbq}/opcode/opcode_string.go | 0 {runtime/bbq => bbq}/program.go | 0 {runtime/bbq => bbq}/variable.go | 0 {runtime/bbq => bbq}/vm/callframe.go | 2 +- {runtime/bbq => bbq}/vm/config.go | 2 +- {runtime/bbq => bbq}/vm/context.go | 2 +- {runtime/bbq => bbq}/vm/errors.go | 0 {runtime/bbq => bbq}/vm/linker.go | 2 +- {runtime/bbq => bbq}/vm/native_functions.go | 2 +- {runtime/bbq => bbq}/vm/reference_tracking.go | 0 {runtime/bbq => bbq}/vm/storage.go | 0 {runtime/bbq => bbq}/vm/storage_map.go | 0 {runtime/bbq => bbq}/vm/test/ft_contracts_and_txs.go | 0 {runtime/bbq => bbq}/vm/test/ft_test.go | 4 ++-- {runtime/bbq => bbq}/vm/test/runtime_test.go | 4 ++-- {runtime/bbq => bbq}/vm/test/utils.go | 8 ++++---- {runtime/bbq => bbq}/vm/test/vm_test.go | 8 ++++---- {runtime/bbq => bbq}/vm/types.go | 0 {runtime/bbq => bbq}/vm/value.go | 0 {runtime/bbq => bbq}/vm/value_account.go | 0 {runtime/bbq => bbq}/vm/value_account_capabilities.go | 0 {runtime/bbq => bbq}/vm/value_account_storage.go | 0 .../vm/value_account_storagecapabilities.go | 0 {runtime/bbq => bbq}/vm/value_address.go | 0 {runtime/bbq => bbq}/vm/value_array.go | 0 {runtime/bbq => bbq}/vm/value_bool.go | 0 {runtime/bbq => bbq}/vm/value_capability.go | 0 {runtime/bbq => bbq}/vm/value_capability_controller.go | 0 {runtime/bbq => bbq}/vm/value_composite.go | 1 + {runtime/bbq => bbq}/vm/value_conversions.go | 0 {runtime/bbq => bbq}/vm/value_dictionary.go | 1 + {runtime/bbq => bbq}/vm/value_ephemeral_reference.go | 0 {runtime/bbq => bbq}/vm/value_function.go | 2 +- {runtime/bbq => bbq}/vm/value_int.go | 0 {runtime/bbq => bbq}/vm/value_nil.go | 0 {runtime/bbq => bbq}/vm/value_path.go | 0 {runtime/bbq => bbq}/vm/value_simple_composite.go | 0 {runtime/bbq => bbq}/vm/value_some.go | 0 {runtime/bbq => bbq}/vm/value_storage_reference.go | 0 {runtime/bbq => bbq}/vm/value_string.go | 0 {runtime/bbq => bbq}/vm/value_utils.go | 0 {runtime/bbq => bbq}/vm/value_void.go | 0 {runtime/bbq => bbq}/vm/vm.go | 10 +++++----- 66 files changed, 42 insertions(+), 40 deletions(-) rename {runtime/bbq => bbq}/bytecode_printer.go (97%) rename {runtime/bbq => bbq}/commons/constants.go (100%) rename {runtime/bbq => bbq}/commons/handlers.go (97%) rename {runtime/bbq => bbq}/commons/utils.go (100%) rename {runtime/bbq => bbq}/compiler/compiler.go (99%) rename {runtime/bbq => bbq}/compiler/compiler_test.go (98%) rename {runtime/bbq => bbq}/compiler/config.go (94%) rename {runtime/bbq => bbq}/compiler/constant.go (92%) rename {runtime/bbq => bbq}/compiler/function.go (97%) rename {runtime/bbq => bbq}/compiler/global.go (100%) rename {runtime/bbq => bbq}/compiler/local.go (100%) rename {runtime/bbq => bbq}/compiler/loop.go (100%) rename {runtime/bbq => bbq}/compiler/native_functions.go (97%) rename {runtime/bbq => bbq}/compiler/stack.go (100%) rename {runtime/bbq => bbq}/constant.go (92%) rename {runtime/bbq => bbq}/constantkind/constantkind.go (100%) rename {runtime/bbq => bbq}/constantkind/constantkind_string.go (100%) rename {runtime/bbq => bbq}/contract.go (100%) rename {runtime/bbq => bbq}/function.go (100%) rename {runtime/bbq => bbq}/import.go (100%) rename {runtime/bbq => bbq}/leb128/leb128.go (100%) rename {runtime/bbq => bbq}/leb128/leb128_test.go (100%) rename {runtime/bbq => bbq}/opcode/opcode.go (100%) rename {runtime/bbq => bbq}/opcode/opcode_string.go (100%) rename {runtime/bbq => bbq}/program.go (100%) rename {runtime/bbq => bbq}/variable.go (100%) rename {runtime/bbq => bbq}/vm/callframe.go (97%) rename {runtime/bbq => bbq}/vm/config.go (98%) rename {runtime/bbq => bbq}/vm/context.go (97%) rename {runtime/bbq => bbq}/vm/errors.go (100%) rename {runtime/bbq => bbq}/vm/linker.go (98%) rename {runtime/bbq => bbq}/vm/native_functions.go (98%) rename {runtime/bbq => bbq}/vm/reference_tracking.go (100%) rename {runtime/bbq => bbq}/vm/storage.go (100%) rename {runtime/bbq => bbq}/vm/storage_map.go (100%) rename {runtime/bbq => bbq}/vm/test/ft_contracts_and_txs.go (100%) rename {runtime/bbq => bbq}/vm/test/ft_test.go (99%) rename {runtime/bbq => bbq}/vm/test/runtime_test.go (99%) rename {runtime/bbq => bbq}/vm/test/utils.go (98%) rename {runtime/bbq => bbq}/vm/test/vm_test.go (99%) rename {runtime/bbq => bbq}/vm/types.go (100%) rename {runtime/bbq => bbq}/vm/value.go (100%) rename {runtime/bbq => bbq}/vm/value_account.go (100%) rename {runtime/bbq => bbq}/vm/value_account_capabilities.go (100%) rename {runtime/bbq => bbq}/vm/value_account_storage.go (100%) rename {runtime/bbq => bbq}/vm/value_account_storagecapabilities.go (100%) rename {runtime/bbq => bbq}/vm/value_address.go (100%) rename {runtime/bbq => bbq}/vm/value_array.go (100%) rename {runtime/bbq => bbq}/vm/value_bool.go (100%) rename {runtime/bbq => bbq}/vm/value_capability.go (100%) rename {runtime/bbq => bbq}/vm/value_capability_controller.go (100%) rename {runtime/bbq => bbq}/vm/value_composite.go (99%) rename {runtime/bbq => bbq}/vm/value_conversions.go (100%) rename {runtime/bbq => bbq}/vm/value_dictionary.go (99%) rename {runtime/bbq => bbq}/vm/value_ephemeral_reference.go (100%) rename {runtime/bbq => bbq}/vm/value_function.go (97%) rename {runtime/bbq => bbq}/vm/value_int.go (100%) rename {runtime/bbq => bbq}/vm/value_nil.go (100%) rename {runtime/bbq => bbq}/vm/value_path.go (100%) rename {runtime/bbq => bbq}/vm/value_simple_composite.go (100%) rename {runtime/bbq => bbq}/vm/value_some.go (100%) rename {runtime/bbq => bbq}/vm/value_storage_reference.go (100%) rename {runtime/bbq => bbq}/vm/value_string.go (100%) rename {runtime/bbq => bbq}/vm/value_utils.go (100%) rename {runtime/bbq => bbq}/vm/value_void.go (100%) rename {runtime/bbq => bbq}/vm/vm.go (98%) diff --git a/runtime/bbq/bytecode_printer.go b/bbq/bytecode_printer.go similarity index 97% rename from runtime/bbq/bytecode_printer.go rename to bbq/bytecode_printer.go index 0e2abb49e3..2eca09f9c1 100644 --- a/runtime/bbq/bytecode_printer.go +++ b/bbq/bytecode_printer.go @@ -22,12 +22,12 @@ import ( "fmt" "strings" + "github.com/onflow/cadence/bbq/constantkind" + "github.com/onflow/cadence/bbq/leb128" + "github.com/onflow/cadence/bbq/opcode" "github.com/onflow/cadence/common" "github.com/onflow/cadence/errors" "github.com/onflow/cadence/interpreter" - "github.com/onflow/cadence/runtime/bbq/constantkind" - "github.com/onflow/cadence/runtime/bbq/leb128" - "github.com/onflow/cadence/runtime/bbq/opcode" ) type BytecodePrinter struct { diff --git a/runtime/bbq/commons/constants.go b/bbq/commons/constants.go similarity index 100% rename from runtime/bbq/commons/constants.go rename to bbq/commons/constants.go diff --git a/runtime/bbq/commons/handlers.go b/bbq/commons/handlers.go similarity index 97% rename from runtime/bbq/commons/handlers.go rename to bbq/commons/handlers.go index 1cb29c96ef..23d0a307eb 100644 --- a/runtime/bbq/commons/handlers.go +++ b/bbq/commons/handlers.go @@ -20,8 +20,8 @@ package commons import ( "github.com/onflow/cadence/ast" + "github.com/onflow/cadence/bbq" "github.com/onflow/cadence/common" - "github.com/onflow/cadence/runtime/bbq" "github.com/onflow/cadence/sema" ) diff --git a/runtime/bbq/commons/utils.go b/bbq/commons/utils.go similarity index 100% rename from runtime/bbq/commons/utils.go rename to bbq/commons/utils.go diff --git a/runtime/bbq/compiler/compiler.go b/bbq/compiler/compiler.go similarity index 99% rename from runtime/bbq/compiler/compiler.go rename to bbq/compiler/compiler.go index 516e5c4b8b..06efeddc9b 100644 --- a/runtime/bbq/compiler/compiler.go +++ b/bbq/compiler/compiler.go @@ -28,11 +28,11 @@ import ( "github.com/onflow/cadence/interpreter" "github.com/onflow/cadence/sema" - "github.com/onflow/cadence/runtime/bbq" - "github.com/onflow/cadence/runtime/bbq/commons" - "github.com/onflow/cadence/runtime/bbq/constantkind" - "github.com/onflow/cadence/runtime/bbq/leb128" - "github.com/onflow/cadence/runtime/bbq/opcode" + "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/bbq/commons" + "github.com/onflow/cadence/bbq/constantkind" + "github.com/onflow/cadence/bbq/leb128" + "github.com/onflow/cadence/bbq/opcode" ) type Compiler struct { diff --git a/runtime/bbq/compiler/compiler_test.go b/bbq/compiler/compiler_test.go similarity index 98% rename from runtime/bbq/compiler/compiler_test.go rename to bbq/compiler/compiler_test.go index 7dadb450c3..8a63548615 100644 --- a/runtime/bbq/compiler/compiler_test.go +++ b/bbq/compiler/compiler_test.go @@ -23,9 +23,9 @@ import ( "github.com/stretchr/testify/require" - "github.com/onflow/cadence/runtime/bbq" - "github.com/onflow/cadence/runtime/bbq/constantkind" - "github.com/onflow/cadence/runtime/bbq/opcode" + "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/bbq/constantkind" + "github.com/onflow/cadence/bbq/opcode" . "github.com/onflow/cadence/tests/checker" ) diff --git a/runtime/bbq/compiler/config.go b/bbq/compiler/config.go similarity index 94% rename from runtime/bbq/compiler/config.go rename to bbq/compiler/config.go index 4ee60350c4..fdb5c47ac2 100644 --- a/runtime/bbq/compiler/config.go +++ b/bbq/compiler/config.go @@ -19,7 +19,7 @@ package compiler import ( - "github.com/onflow/cadence/runtime/bbq/commons" + "github.com/onflow/cadence/bbq/commons" ) type Config struct { diff --git a/runtime/bbq/compiler/constant.go b/bbq/compiler/constant.go similarity index 92% rename from runtime/bbq/compiler/constant.go rename to bbq/compiler/constant.go index 3f2986246e..1f6f66c23f 100644 --- a/runtime/bbq/compiler/constant.go +++ b/bbq/compiler/constant.go @@ -18,7 +18,7 @@ package compiler -import "github.com/onflow/cadence/runtime/bbq/constantkind" +import "github.com/onflow/cadence/bbq/constantkind" type constant struct { index uint16 diff --git a/runtime/bbq/compiler/function.go b/bbq/compiler/function.go similarity index 97% rename from runtime/bbq/compiler/function.go rename to bbq/compiler/function.go index 2affe8127c..4465e2277b 100644 --- a/runtime/bbq/compiler/function.go +++ b/bbq/compiler/function.go @@ -22,8 +22,8 @@ import ( "math" "github.com/onflow/cadence/activations" + "github.com/onflow/cadence/bbq/opcode" "github.com/onflow/cadence/errors" - "github.com/onflow/cadence/runtime/bbq/opcode" ) type function struct { diff --git a/runtime/bbq/compiler/global.go b/bbq/compiler/global.go similarity index 100% rename from runtime/bbq/compiler/global.go rename to bbq/compiler/global.go diff --git a/runtime/bbq/compiler/local.go b/bbq/compiler/local.go similarity index 100% rename from runtime/bbq/compiler/local.go rename to bbq/compiler/local.go diff --git a/runtime/bbq/compiler/loop.go b/bbq/compiler/loop.go similarity index 100% rename from runtime/bbq/compiler/loop.go rename to bbq/compiler/loop.go diff --git a/runtime/bbq/compiler/native_functions.go b/bbq/compiler/native_functions.go similarity index 97% rename from runtime/bbq/compiler/native_functions.go rename to bbq/compiler/native_functions.go index d7a6c502ef..ff418f889d 100644 --- a/runtime/bbq/compiler/native_functions.go +++ b/bbq/compiler/native_functions.go @@ -21,7 +21,7 @@ package compiler import ( "github.com/onflow/cadence/sema" - "github.com/onflow/cadence/runtime/bbq/commons" + "github.com/onflow/cadence/bbq/commons" ) var indexedNativeFunctions = make(map[string]*global) diff --git a/runtime/bbq/compiler/stack.go b/bbq/compiler/stack.go similarity index 100% rename from runtime/bbq/compiler/stack.go rename to bbq/compiler/stack.go diff --git a/runtime/bbq/constant.go b/bbq/constant.go similarity index 92% rename from runtime/bbq/constant.go rename to bbq/constant.go index 5e2ff5f1e6..cf925e37f0 100644 --- a/runtime/bbq/constant.go +++ b/bbq/constant.go @@ -18,7 +18,7 @@ package bbq -import "github.com/onflow/cadence/runtime/bbq/constantkind" +import "github.com/onflow/cadence/bbq/constantkind" type Constant struct { Data []byte diff --git a/runtime/bbq/constantkind/constantkind.go b/bbq/constantkind/constantkind.go similarity index 100% rename from runtime/bbq/constantkind/constantkind.go rename to bbq/constantkind/constantkind.go diff --git a/runtime/bbq/constantkind/constantkind_string.go b/bbq/constantkind/constantkind_string.go similarity index 100% rename from runtime/bbq/constantkind/constantkind_string.go rename to bbq/constantkind/constantkind_string.go diff --git a/runtime/bbq/contract.go b/bbq/contract.go similarity index 100% rename from runtime/bbq/contract.go rename to bbq/contract.go diff --git a/runtime/bbq/function.go b/bbq/function.go similarity index 100% rename from runtime/bbq/function.go rename to bbq/function.go diff --git a/runtime/bbq/import.go b/bbq/import.go similarity index 100% rename from runtime/bbq/import.go rename to bbq/import.go diff --git a/runtime/bbq/leb128/leb128.go b/bbq/leb128/leb128.go similarity index 100% rename from runtime/bbq/leb128/leb128.go rename to bbq/leb128/leb128.go diff --git a/runtime/bbq/leb128/leb128_test.go b/bbq/leb128/leb128_test.go similarity index 100% rename from runtime/bbq/leb128/leb128_test.go rename to bbq/leb128/leb128_test.go diff --git a/runtime/bbq/opcode/opcode.go b/bbq/opcode/opcode.go similarity index 100% rename from runtime/bbq/opcode/opcode.go rename to bbq/opcode/opcode.go diff --git a/runtime/bbq/opcode/opcode_string.go b/bbq/opcode/opcode_string.go similarity index 100% rename from runtime/bbq/opcode/opcode_string.go rename to bbq/opcode/opcode_string.go diff --git a/runtime/bbq/program.go b/bbq/program.go similarity index 100% rename from runtime/bbq/program.go rename to bbq/program.go diff --git a/runtime/bbq/variable.go b/bbq/variable.go similarity index 100% rename from runtime/bbq/variable.go rename to bbq/variable.go diff --git a/runtime/bbq/vm/callframe.go b/bbq/vm/callframe.go similarity index 97% rename from runtime/bbq/vm/callframe.go rename to bbq/vm/callframe.go index 7ac48ff49b..a0ca9dbb44 100644 --- a/runtime/bbq/vm/callframe.go +++ b/bbq/vm/callframe.go @@ -19,7 +19,7 @@ package vm import ( - "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/bbq" ) type callFrame struct { diff --git a/runtime/bbq/vm/config.go b/bbq/vm/config.go similarity index 98% rename from runtime/bbq/vm/config.go rename to bbq/vm/config.go index 7cc280767e..e77a15ef1e 100644 --- a/runtime/bbq/vm/config.go +++ b/bbq/vm/config.go @@ -19,9 +19,9 @@ package vm import ( + "github.com/onflow/cadence/bbq/commons" "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" - "github.com/onflow/cadence/runtime/bbq/commons" "github.com/onflow/cadence/sema" "github.com/onflow/cadence/stdlib" "github.com/onflow/cadence/tests/utils" diff --git a/runtime/bbq/vm/context.go b/bbq/vm/context.go similarity index 97% rename from runtime/bbq/vm/context.go rename to bbq/vm/context.go index 703630ca3c..fe8f2a9c3b 100644 --- a/runtime/bbq/vm/context.go +++ b/bbq/vm/context.go @@ -19,7 +19,7 @@ package vm import ( - "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/bbq" ) // Context is the context of a program. diff --git a/runtime/bbq/vm/errors.go b/bbq/vm/errors.go similarity index 100% rename from runtime/bbq/vm/errors.go rename to bbq/vm/errors.go diff --git a/runtime/bbq/vm/linker.go b/bbq/vm/linker.go similarity index 98% rename from runtime/bbq/vm/linker.go rename to bbq/vm/linker.go index f93b37c29f..532194661e 100644 --- a/runtime/bbq/vm/linker.go +++ b/bbq/vm/linker.go @@ -21,7 +21,7 @@ package vm import ( "fmt" - "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/bbq" "github.com/onflow/cadence/common" ) diff --git a/runtime/bbq/vm/native_functions.go b/bbq/vm/native_functions.go similarity index 98% rename from runtime/bbq/vm/native_functions.go rename to bbq/vm/native_functions.go index dfb1c2c55f..e547111722 100644 --- a/runtime/bbq/vm/native_functions.go +++ b/bbq/vm/native_functions.go @@ -20,8 +20,8 @@ package vm import ( "fmt" + "github.com/onflow/cadence/bbq/commons" "github.com/onflow/cadence/errors" - "github.com/onflow/cadence/runtime/bbq/commons" "github.com/onflow/cadence/common" "github.com/onflow/cadence/stdlib" diff --git a/runtime/bbq/vm/reference_tracking.go b/bbq/vm/reference_tracking.go similarity index 100% rename from runtime/bbq/vm/reference_tracking.go rename to bbq/vm/reference_tracking.go diff --git a/runtime/bbq/vm/storage.go b/bbq/vm/storage.go similarity index 100% rename from runtime/bbq/vm/storage.go rename to bbq/vm/storage.go diff --git a/runtime/bbq/vm/storage_map.go b/bbq/vm/storage_map.go similarity index 100% rename from runtime/bbq/vm/storage_map.go rename to bbq/vm/storage_map.go diff --git a/runtime/bbq/vm/test/ft_contracts_and_txs.go b/bbq/vm/test/ft_contracts_and_txs.go similarity index 100% rename from runtime/bbq/vm/test/ft_contracts_and_txs.go rename to bbq/vm/test/ft_contracts_and_txs.go diff --git a/runtime/bbq/vm/test/ft_test.go b/bbq/vm/test/ft_test.go similarity index 99% rename from runtime/bbq/vm/test/ft_test.go rename to bbq/vm/test/ft_test.go index e7bf0e56f0..1bfa6ef9a8 100644 --- a/runtime/bbq/vm/test/ft_test.go +++ b/bbq/vm/test/ft_test.go @@ -25,10 +25,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/bbq/vm" "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" - "github.com/onflow/cadence/runtime/bbq" - "github.com/onflow/cadence/runtime/bbq/vm" "github.com/onflow/cadence/sema" ) diff --git a/runtime/bbq/vm/test/runtime_test.go b/bbq/vm/test/runtime_test.go similarity index 99% rename from runtime/bbq/vm/test/runtime_test.go rename to bbq/vm/test/runtime_test.go index 63cfbab399..5f2150ebb4 100644 --- a/runtime/bbq/vm/test/runtime_test.go +++ b/bbq/vm/test/runtime_test.go @@ -20,13 +20,13 @@ package test import ( "fmt" - "github.com/onflow/cadence/runtime/bbq" + "github.com/onflow/cadence/bbq" "github.com/stretchr/testify/assert" "testing" + "github.com/onflow/cadence/bbq/vm" "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" - "github.com/onflow/cadence/runtime/bbq/vm" "github.com/stretchr/testify/require" ) diff --git a/runtime/bbq/vm/test/utils.go b/bbq/vm/test/utils.go similarity index 98% rename from runtime/bbq/vm/test/utils.go rename to bbq/vm/test/utils.go index 31a5a91619..8a329f7e7f 100644 --- a/runtime/bbq/vm/test/utils.go +++ b/bbq/vm/test/utils.go @@ -20,18 +20,18 @@ package test import ( "fmt" - "github.com/onflow/cadence/runtime/bbq/vm" + "github.com/onflow/cadence/bbq/vm" "testing" "github.com/stretchr/testify/require" "github.com/onflow/cadence/ast" + "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/bbq/commons" + "github.com/onflow/cadence/bbq/compiler" "github.com/onflow/cadence/common" "github.com/onflow/cadence/errors" "github.com/onflow/cadence/interpreter" - "github.com/onflow/cadence/runtime/bbq" - "github.com/onflow/cadence/runtime/bbq/commons" - "github.com/onflow/cadence/runtime/bbq/compiler" "github.com/onflow/cadence/sema" "github.com/onflow/cadence/stdlib" "github.com/onflow/cadence/tests/checker" diff --git a/runtime/bbq/vm/test/vm_test.go b/bbq/vm/test/vm_test.go similarity index 99% rename from runtime/bbq/vm/test/vm_test.go rename to bbq/vm/test/vm_test.go index db02f2cf0d..ace5e6c94b 100644 --- a/runtime/bbq/vm/test/vm_test.go +++ b/bbq/vm/test/vm_test.go @@ -28,17 +28,17 @@ import ( "github.com/onflow/atree" "github.com/onflow/cadence/ast" + "github.com/onflow/cadence/bbq/vm" "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" - "github.com/onflow/cadence/runtime/bbq/vm" "github.com/onflow/cadence/sema" "github.com/onflow/cadence/stdlib" . "github.com/onflow/cadence/tests/checker" "github.com/onflow/cadence/tests/utils" - "github.com/onflow/cadence/runtime/bbq" - "github.com/onflow/cadence/runtime/bbq/commons" - "github.com/onflow/cadence/runtime/bbq/compiler" + "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/bbq/commons" + "github.com/onflow/cadence/bbq/compiler" ) const recursiveFib = ` diff --git a/runtime/bbq/vm/types.go b/bbq/vm/types.go similarity index 100% rename from runtime/bbq/vm/types.go rename to bbq/vm/types.go diff --git a/runtime/bbq/vm/value.go b/bbq/vm/value.go similarity index 100% rename from runtime/bbq/vm/value.go rename to bbq/vm/value.go diff --git a/runtime/bbq/vm/value_account.go b/bbq/vm/value_account.go similarity index 100% rename from runtime/bbq/vm/value_account.go rename to bbq/vm/value_account.go diff --git a/runtime/bbq/vm/value_account_capabilities.go b/bbq/vm/value_account_capabilities.go similarity index 100% rename from runtime/bbq/vm/value_account_capabilities.go rename to bbq/vm/value_account_capabilities.go diff --git a/runtime/bbq/vm/value_account_storage.go b/bbq/vm/value_account_storage.go similarity index 100% rename from runtime/bbq/vm/value_account_storage.go rename to bbq/vm/value_account_storage.go diff --git a/runtime/bbq/vm/value_account_storagecapabilities.go b/bbq/vm/value_account_storagecapabilities.go similarity index 100% rename from runtime/bbq/vm/value_account_storagecapabilities.go rename to bbq/vm/value_account_storagecapabilities.go diff --git a/runtime/bbq/vm/value_address.go b/bbq/vm/value_address.go similarity index 100% rename from runtime/bbq/vm/value_address.go rename to bbq/vm/value_address.go diff --git a/runtime/bbq/vm/value_array.go b/bbq/vm/value_array.go similarity index 100% rename from runtime/bbq/vm/value_array.go rename to bbq/vm/value_array.go diff --git a/runtime/bbq/vm/value_bool.go b/bbq/vm/value_bool.go similarity index 100% rename from runtime/bbq/vm/value_bool.go rename to bbq/vm/value_bool.go diff --git a/runtime/bbq/vm/value_capability.go b/bbq/vm/value_capability.go similarity index 100% rename from runtime/bbq/vm/value_capability.go rename to bbq/vm/value_capability.go diff --git a/runtime/bbq/vm/value_capability_controller.go b/bbq/vm/value_capability_controller.go similarity index 100% rename from runtime/bbq/vm/value_capability_controller.go rename to bbq/vm/value_capability_controller.go diff --git a/runtime/bbq/vm/value_composite.go b/bbq/vm/value_composite.go similarity index 99% rename from runtime/bbq/vm/value_composite.go rename to bbq/vm/value_composite.go index ff4bc0e8b4..1973f1cd29 100644 --- a/runtime/bbq/vm/value_composite.go +++ b/bbq/vm/value_composite.go @@ -22,6 +22,7 @@ import ( goerrors "errors" "github.com/onflow/atree" + "github.com/onflow/cadence/common" "github.com/onflow/cadence/errors" "github.com/onflow/cadence/interpreter" diff --git a/runtime/bbq/vm/value_conversions.go b/bbq/vm/value_conversions.go similarity index 100% rename from runtime/bbq/vm/value_conversions.go rename to bbq/vm/value_conversions.go diff --git a/runtime/bbq/vm/value_dictionary.go b/bbq/vm/value_dictionary.go similarity index 99% rename from runtime/bbq/vm/value_dictionary.go rename to bbq/vm/value_dictionary.go index fc122a4d42..ade5804f00 100644 --- a/runtime/bbq/vm/value_dictionary.go +++ b/bbq/vm/value_dictionary.go @@ -20,6 +20,7 @@ package vm import ( "github.com/onflow/atree" + "github.com/onflow/cadence/common" "github.com/onflow/cadence/errors" "github.com/onflow/cadence/interpreter" diff --git a/runtime/bbq/vm/value_ephemeral_reference.go b/bbq/vm/value_ephemeral_reference.go similarity index 100% rename from runtime/bbq/vm/value_ephemeral_reference.go rename to bbq/vm/value_ephemeral_reference.go diff --git a/runtime/bbq/vm/value_function.go b/bbq/vm/value_function.go similarity index 97% rename from runtime/bbq/vm/value_function.go rename to bbq/vm/value_function.go index a6c06015d1..90e17966cd 100644 --- a/runtime/bbq/vm/value_function.go +++ b/bbq/vm/value_function.go @@ -21,9 +21,9 @@ package vm import ( "github.com/onflow/atree" + "github.com/onflow/cadence/bbq" "github.com/onflow/cadence/common" "github.com/onflow/cadence/errors" - "github.com/onflow/cadence/runtime/bbq" ) type FunctionValue struct { diff --git a/runtime/bbq/vm/value_int.go b/bbq/vm/value_int.go similarity index 100% rename from runtime/bbq/vm/value_int.go rename to bbq/vm/value_int.go diff --git a/runtime/bbq/vm/value_nil.go b/bbq/vm/value_nil.go similarity index 100% rename from runtime/bbq/vm/value_nil.go rename to bbq/vm/value_nil.go diff --git a/runtime/bbq/vm/value_path.go b/bbq/vm/value_path.go similarity index 100% rename from runtime/bbq/vm/value_path.go rename to bbq/vm/value_path.go diff --git a/runtime/bbq/vm/value_simple_composite.go b/bbq/vm/value_simple_composite.go similarity index 100% rename from runtime/bbq/vm/value_simple_composite.go rename to bbq/vm/value_simple_composite.go diff --git a/runtime/bbq/vm/value_some.go b/bbq/vm/value_some.go similarity index 100% rename from runtime/bbq/vm/value_some.go rename to bbq/vm/value_some.go diff --git a/runtime/bbq/vm/value_storage_reference.go b/bbq/vm/value_storage_reference.go similarity index 100% rename from runtime/bbq/vm/value_storage_reference.go rename to bbq/vm/value_storage_reference.go diff --git a/runtime/bbq/vm/value_string.go b/bbq/vm/value_string.go similarity index 100% rename from runtime/bbq/vm/value_string.go rename to bbq/vm/value_string.go diff --git a/runtime/bbq/vm/value_utils.go b/bbq/vm/value_utils.go similarity index 100% rename from runtime/bbq/vm/value_utils.go rename to bbq/vm/value_utils.go diff --git a/runtime/bbq/vm/value_void.go b/bbq/vm/value_void.go similarity index 100% rename from runtime/bbq/vm/value_void.go rename to bbq/vm/value_void.go diff --git a/runtime/bbq/vm/vm.go b/bbq/vm/vm.go similarity index 98% rename from runtime/bbq/vm/vm.go rename to bbq/vm/vm.go index 52bf172a97..258eaaa1ba 100644 --- a/runtime/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -25,11 +25,11 @@ import ( "github.com/onflow/cadence/interpreter" "github.com/onflow/cadence/runtime" - "github.com/onflow/cadence/runtime/bbq" - "github.com/onflow/cadence/runtime/bbq/commons" - "github.com/onflow/cadence/runtime/bbq/constantkind" - "github.com/onflow/cadence/runtime/bbq/leb128" - "github.com/onflow/cadence/runtime/bbq/opcode" + "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/bbq/commons" + "github.com/onflow/cadence/bbq/constantkind" + "github.com/onflow/cadence/bbq/leb128" + "github.com/onflow/cadence/bbq/opcode" ) type VM struct { From f6d578bb37d24ae86c97b019982aff566cc8d0fb Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 24 Oct 2024 17:21:38 -0700 Subject: [PATCH 53/89] Add interpreter FT transfer test --- bbq/vm/test/interpreter_test.go | 517 ++++++++++++++++++++++++++++++++ bbq/vm/test/utils.go | 12 +- 2 files changed, 523 insertions(+), 6 deletions(-) create mode 100644 bbq/vm/test/interpreter_test.go diff --git a/bbq/vm/test/interpreter_test.go b/bbq/vm/test/interpreter_test.go new file mode 100644 index 0000000000..a07c2036cf --- /dev/null +++ b/bbq/vm/test/interpreter_test.go @@ -0,0 +1,517 @@ +package test + +import ( + "encoding/hex" + "fmt" + "github.com/onflow/cadence/activations" + "github.com/onflow/cadence/ast" + "github.com/onflow/cadence/pretty" + "github.com/onflow/cadence/stdlib" + "github.com/onflow/cadence/tests/checker" + "github.com/onflow/cadence/tests/runtime_utils" + "strings" + "testing" + + "github.com/onflow/cadence/common" + "github.com/onflow/cadence/interpreter" + "github.com/onflow/cadence/sema" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type ParseCheckAndInterpretOptions struct { + Config *interpreter.Config + CheckerConfig *sema.Config + HandleCheckerError func(error) +} + +func parseCheckAndInterpretWithOptions( + t testing.TB, + code string, + location common.Location, + options ParseCheckAndInterpretOptions, +) ( + inter *interpreter.Interpreter, + err error, +) { + return parseCheckAndInterpretWithOptionsAndMemoryMetering(t, code, location, options, nil) +} + +func parseCheckAndInterpretWithOptionsAndMemoryMetering( + t testing.TB, + code string, + location common.Location, + options ParseCheckAndInterpretOptions, + memoryGauge common.MemoryGauge, +) ( + inter *interpreter.Interpreter, + err error, +) { + + checker, err := checker.ParseAndCheckWithOptionsAndMemoryMetering(t, + code, + checker.ParseAndCheckOptions{ + Location: location, + Config: options.CheckerConfig, + }, + memoryGauge, + ) + + if options.HandleCheckerError != nil { + options.HandleCheckerError(err) + } else if !assert.NoError(t, err) { + var sb strings.Builder + location := checker.Location + printErr := pretty.NewErrorPrettyPrinter(&sb, true). + PrettyPrintError(err, location, map[common.Location][]byte{location: []byte(code)}) + if printErr != nil { + panic(printErr) + } + assert.Fail(t, sb.String()) + return nil, err + } + + var uuid uint64 = 0 + + var config interpreter.Config + if options.Config != nil { + config = *options.Config + } + if memoryGauge == nil { + config.AtreeValueValidationEnabled = true + config.AtreeStorageValidationEnabled = true + } + if config.UUIDHandler == nil { + config.UUIDHandler = func() (uint64, error) { + uuid++ + return uuid, nil + } + } + if config.Storage == nil { + config.Storage = interpreter.NewInMemoryStorage(memoryGauge) + } + + if memoryGauge != nil && config.MemoryGauge == nil { + config.MemoryGauge = memoryGauge + } + + inter, err = interpreter.NewInterpreter( + interpreter.ProgramFromChecker(checker), + checker.Location, + &config, + ) + + require.NoError(t, err) + + err = inter.Interpret() + + if err == nil { + + // recover internal panics and return them as an error + defer inter.RecoverErrors(func(internalErr error) { + err = internalErr + }) + + // Contract declarations are evaluated lazily, + // so force the contract value handler to be called + + for _, compositeDeclaration := range checker.Program.CompositeDeclarations() { + if compositeDeclaration.CompositeKind != common.CompositeKindContract { + continue + } + + contractVariable := inter.Globals.Get(compositeDeclaration.Identifier.Identifier) + + _ = contractVariable.GetValue(inter) + } + } + + return inter, err +} + +func newContractDeployTransaction(name, code string) string { + return fmt.Sprintf( + ` + transaction { + prepare(signer: auth(Contracts) &Account) { + signer.contracts.%s(name: "%s", code: "%s".decodeHex()) + } + } + `, + sema.Account_ContractsTypeAddFunctionName, + name, + hex.EncodeToString([]byte(code)), + ) +} + +func makeContractValueHandler( + arguments []interpreter.Value, + argumentTypes []sema.Type, + parameterTypes []sema.Type, +) interpreter.ContractValueHandlerFunc { + return func( + inter *interpreter.Interpreter, + compositeType *sema.CompositeType, + constructorGenerator func(common.Address) *interpreter.HostFunctionValue, + invocationRange ast.Range, + ) interpreter.ContractValue { + + constructor := constructorGenerator(common.ZeroAddress) + + value, err := inter.InvokeFunctionValue( + constructor, + arguments, + argumentTypes, + parameterTypes, + compositeType, + ast.Range{}, + ) + if err != nil { + panic(err) + } + + return value.(*interpreter.CompositeValue) + } +} + +func TestInterpreterFTTransfer(t *testing.T) { + + // ---- Deploy FT Contract ----- + + storage := interpreter.NewInMemoryStorage(nil) + + contractsAddress := common.MustBytesToAddress([]byte{0x1}) + senderAddress := common.MustBytesToAddress([]byte{0x2}) + receiverAddress := common.MustBytesToAddress([]byte{0x3}) + + flowTokenLocation := common.NewAddressLocation(nil, contractsAddress, "FlowToken") + ftLocation := common.NewAddressLocation(nil, contractsAddress, "FungibleToken") + + programs := map[common.Location]*interpreter.Interpreter{} + codes := map[common.Location][]byte{ + ftLocation: []byte(realFungibleTokenContractInterface), + } + + txLocation := runtime_utils.NewTransactionLocationGenerator() + scriptLocation := runtime_utils.NewScriptLocationGenerator() + + var signer interpreter.Value + var flowTokenContractValue *interpreter.CompositeValue + + accountHandler := &testAccountHandler{ + getAccountContractCode: func(location common.AddressLocation) ([]byte, error) { + code, ok := codes[location] + if !ok { + return nil, nil + // return nil, fmt.Errorf("cannot find code for %s", location) + } + + return code, nil + }, + updateAccountContractCode: func(location common.AddressLocation, code []byte) error { + codes[location] = code + return nil + }, + contractUpdateRecorded: func(location common.AddressLocation) bool { + return false + }, + interpretContract: func( + location common.AddressLocation, + program *interpreter.Program, + name string, + invocation stdlib.DeployedContractConstructorInvocation, + ) (*interpreter.CompositeValue, error) { + if location == flowTokenLocation { + return flowTokenContractValue, nil + } + return nil, fmt.Errorf("cannot interpret contract %s", location) + }, + temporarilyRecordCode: func(location common.AddressLocation, code []byte) { + // do nothing + }, + emitEvent: func(*interpreter.Interpreter, interpreter.LocationRange, *sema.CompositeType, []interpreter.Value) { + // do nothing + }, + recordContractUpdate: func(location common.AddressLocation, value *interpreter.CompositeValue) { + // do nothing + }, + } + + baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) + interpreter.Declare(baseActivation, stdlib.PanicFunction) + interpreter.Declare(baseActivation, stdlib.NewGetAccountFunction(accountHandler)) + + checkerConfig := &sema.Config{ + ImportHandler: func(checker *sema.Checker, location common.Location, importRange ast.Range) (sema.Import, error) { + imported, ok := programs[location] + if !ok { + return nil, fmt.Errorf("cannot find contract in location %s", location) + } + + return sema.ElaborationImport{ + Elaboration: imported.Program.Elaboration, + }, nil + }, + BaseValueActivationHandler: baseValueActivation, + LocationHandler: singleIdentifierLocationResolver(t), + } + + interConfig := &interpreter.Config{ + Storage: storage, + BaseActivationHandler: func(_ common.Location) *interpreter.VariableActivation { + return baseActivation + }, + ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { + imported, ok := programs[location] + if !ok { + panic(fmt.Errorf("cannot find contract in location %s", location)) + } + + return interpreter.InterpreterImport{ + Interpreter: imported, + } + }, + ContractValueHandler: func( + inter *interpreter.Interpreter, + compositeType *sema.CompositeType, + constructorGenerator func(common.Address) *interpreter.HostFunctionValue, + invocationRange ast.Range, + ) interpreter.ContractValue { + + constructor := constructorGenerator(common.ZeroAddress) + + value, err := inter.InvokeFunctionValue( + constructor, + []interpreter.Value{signer}, + []sema.Type{ + sema.FullyEntitledAccountReferenceType, + }, + []sema.Type{ + sema.FullyEntitledAccountReferenceType, + }, + compositeType, + ast.Range{}, + ) + if err != nil { + panic(err) + } + + flowTokenContractValue = value.(*interpreter.CompositeValue) + return flowTokenContractValue + }, + CapabilityBorrowHandler: func( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + address interpreter.AddressValue, + capabilityID interpreter.UInt64Value, + wantedBorrowType *sema.ReferenceType, + capabilityBorrowType *sema.ReferenceType, + ) interpreter.ReferenceValue { + return stdlib.BorrowCapabilityController( + inter, + locationRange, + address, + capabilityID, + wantedBorrowType, + capabilityBorrowType, + accountHandler, + ) + }, + } + + accountHandler.parseAndCheckProgram = + func(code []byte, location common.Location, getAndSetProgram bool) (*interpreter.Program, error) { + inter, err := parseCheckAndInterpretWithOptions( + t, + string(code), + location, + ParseCheckAndInterpretOptions{ + Config: interConfig, + CheckerConfig: checkerConfig, + }, + ) + + if err != nil { + return nil, err + } + + programs[location] = inter + + return inter.Program, err + } + + // ----- Parse and Check FungibleToken Contract interface ----- + + inter, err := parseCheckAndInterpretWithOptions( + t, + realFungibleTokenContractInterface, + ftLocation, + ParseCheckAndInterpretOptions{ + Config: interConfig, + CheckerConfig: checkerConfig, + }, + ) + require.NoError(t, err) + programs[ftLocation] = inter + + // ----- Deploy FlowToken Contract ----- + + tx := fmt.Sprintf(` + transaction { + prepare(signer: auth(Storage, Capabilities, Contracts) &Account) { + signer.contracts.add(name: "FlowToken", code: "%s".decodeHex(), signer) + } + }`, + hex.EncodeToString([]byte(realFlowContract)), + ) + + inter, err = parseCheckAndInterpretWithOptions( + t, + tx, + txLocation(), + ParseCheckAndInterpretOptions{ + Config: interConfig, + CheckerConfig: checkerConfig, + }, + ) + require.NoError(t, err) + + signer = stdlib.NewAccountReferenceValue( + inter, + accountHandler, + interpreter.AddressValue(contractsAddress), + interpreter.FullyEntitledAccountAccess, + interpreter.EmptyLocationRange, + ) + + err = inter.InvokeTransaction(0, signer) + require.NoError(t, err) + + // ----- Run setup account transaction ----- + + authorization := sema.NewEntitlementSetAccess( + []*sema.EntitlementType{ + sema.BorrowValueType, + sema.IssueStorageCapabilityControllerType, + sema.PublishCapabilityType, + sema.SaveValueType, + }, + sema.Conjunction, + ) + + for _, address := range []common.Address{ + senderAddress, + receiverAddress, + } { + inter, err := parseCheckAndInterpretWithOptions( + t, + realSetupFlowTokenAccountTransaction, + txLocation(), + ParseCheckAndInterpretOptions{ + Config: interConfig, + CheckerConfig: checkerConfig, + }, + ) + + signer = stdlib.NewAccountReferenceValue( + inter, + accountHandler, + interpreter.AddressValue(address), + interpreter.ConvertSemaAccessToStaticAuthorization(nil, authorization), + interpreter.EmptyLocationRange, + ) + + err = inter.InvokeTransaction(0, signer) + require.NoError(t, err) + } + + // Mint FLOW to sender + + total := int64(1000000) + + inter, err = parseCheckAndInterpretWithOptions( + t, + realMintFlowTokenTransaction, + txLocation(), + ParseCheckAndInterpretOptions{ + Config: interConfig, + CheckerConfig: checkerConfig, + }, + ) + + signer = stdlib.NewAccountReferenceValue( + inter, + accountHandler, + interpreter.AddressValue(contractsAddress), + interpreter.ConvertSemaAccessToStaticAuthorization(nil, authorization), + interpreter.EmptyLocationRange, + ) + + err = inter.InvokeTransaction( + 0, + interpreter.AddressValue(senderAddress), + interpreter.NewUnmeteredIntValueFromInt64(total), + signer, + ) + require.NoError(t, err) + + // ----- Run token transfer transaction ----- + + transferAmount := int64(1) + + inter, err = parseCheckAndInterpretWithOptions( + t, + realFlowTokenTransferTransaction, + txLocation(), + ParseCheckAndInterpretOptions{ + Config: interConfig, + CheckerConfig: checkerConfig, + }, + ) + + signer = stdlib.NewAccountReferenceValue( + inter, + accountHandler, + interpreter.AddressValue(senderAddress), + interpreter.ConvertSemaAccessToStaticAuthorization(nil, authorization), + interpreter.EmptyLocationRange, + ) + + err = inter.InvokeTransaction( + 0, + interpreter.NewUnmeteredIntValueFromInt64(transferAmount), + interpreter.AddressValue(receiverAddress), + signer, + ) + require.NoError(t, err) + + // Run validation scripts + + for _, address := range []common.Address{ + senderAddress, + receiverAddress, + } { + inter, err = parseCheckAndInterpretWithOptions( + t, + realFlowTokenBalanceScript, + scriptLocation(), + ParseCheckAndInterpretOptions{ + Config: interConfig, + CheckerConfig: checkerConfig, + }, + ) + require.NoError(t, err) + + result, err := inter.Invoke( + "main", + interpreter.AddressValue(address), + ) + require.NoError(t, err) + + if address == senderAddress { + assert.Equal(t, interpreter.NewUnmeteredIntValueFromInt64(total-transferAmount), result) + } else { + assert.Equal(t, interpreter.NewUnmeteredIntValueFromInt64(transferAmount), result) + } + } +} diff --git a/bbq/vm/test/utils.go b/bbq/vm/test/utils.go index 8a329f7e7f..3ae510400d 100644 --- a/bbq/vm/test/utils.go +++ b/bbq/vm/test/utils.go @@ -383,17 +383,17 @@ func printProgram(program *bbq.Program) { fmt.Println(byteCodePrinter.PrintProgram(program)) } -func baseActivation(common.Location) *sema.VariableActivation { +func baseValueActivation(common.Location) *sema.VariableActivation { // Only need to make the checker happy - baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - baseValueActivation.DeclareValue(stdlib.PanicFunction) - baseValueActivation.DeclareValue(stdlib.NewStandardLibraryStaticFunction( + activation := sema.NewVariableActivation(sema.BaseValueActivation) + activation.DeclareValue(stdlib.PanicFunction) + activation.DeclareValue(stdlib.NewStandardLibraryStaticFunction( "getAccount", stdlib.GetAccountFunctionType, "", nil, )) - return baseValueActivation + return activation } type compiledProgram struct { @@ -441,7 +441,7 @@ func parseAndCheck( }, nil }, LocationHandler: singleIdentifierLocationResolver(t), - BaseValueActivationHandler: baseActivation, + BaseValueActivationHandler: baseValueActivation, }, }, ) From c9871e2c4c8706fc97523e2f2aa993b4ae0198f1 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 24 Oct 2024 18:01:49 -0700 Subject: [PATCH 54/89] Add FT token benchmark for interpreter as a runtime test --- bbq/vm/test/interpreter_test.go | 614 +++++++++++++++++++++++++++++--- 1 file changed, 557 insertions(+), 57 deletions(-) diff --git a/bbq/vm/test/interpreter_test.go b/bbq/vm/test/interpreter_test.go index a07c2036cf..e1f214f5f2 100644 --- a/bbq/vm/test/interpreter_test.go +++ b/bbq/vm/test/interpreter_test.go @@ -3,18 +3,22 @@ package test import ( "encoding/hex" "fmt" - "github.com/onflow/cadence/activations" - "github.com/onflow/cadence/ast" - "github.com/onflow/cadence/pretty" - "github.com/onflow/cadence/stdlib" - "github.com/onflow/cadence/tests/checker" - "github.com/onflow/cadence/tests/runtime_utils" "strings" "testing" + "github.com/onflow/cadence" + "github.com/onflow/cadence/activations" + "github.com/onflow/cadence/ast" "github.com/onflow/cadence/common" + "github.com/onflow/cadence/encoding/json" "github.com/onflow/cadence/interpreter" + "github.com/onflow/cadence/pretty" + "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/sema" + "github.com/onflow/cadence/stdlib" + "github.com/onflow/cadence/tests/checker" + "github.com/onflow/cadence/tests/runtime_utils" + "github.com/onflow/cadence/tests/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -100,7 +104,6 @@ func parseCheckAndInterpretWithOptionsAndMemoryMetering( checker.Location, &config, ) - require.NoError(t, err) err = inter.Interpret() @@ -129,51 +132,6 @@ func parseCheckAndInterpretWithOptionsAndMemoryMetering( return inter, err } -func newContractDeployTransaction(name, code string) string { - return fmt.Sprintf( - ` - transaction { - prepare(signer: auth(Contracts) &Account) { - signer.contracts.%s(name: "%s", code: "%s".decodeHex()) - } - } - `, - sema.Account_ContractsTypeAddFunctionName, - name, - hex.EncodeToString([]byte(code)), - ) -} - -func makeContractValueHandler( - arguments []interpreter.Value, - argumentTypes []sema.Type, - parameterTypes []sema.Type, -) interpreter.ContractValueHandlerFunc { - return func( - inter *interpreter.Interpreter, - compositeType *sema.CompositeType, - constructorGenerator func(common.Address) *interpreter.HostFunctionValue, - invocationRange ast.Range, - ) interpreter.ContractValue { - - constructor := constructorGenerator(common.ZeroAddress) - - value, err := inter.InvokeFunctionValue( - constructor, - arguments, - argumentTypes, - parameterTypes, - compositeType, - ast.Range{}, - ) - if err != nil { - panic(err) - } - - return value.(*interpreter.CompositeValue) - } -} - func TestInterpreterFTTransfer(t *testing.T) { // ---- Deploy FT Contract ----- @@ -187,7 +145,7 @@ func TestInterpreterFTTransfer(t *testing.T) { flowTokenLocation := common.NewAddressLocation(nil, contractsAddress, "FlowToken") ftLocation := common.NewAddressLocation(nil, contractsAddress, "FungibleToken") - programs := map[common.Location]*interpreter.Interpreter{} + subInterpreters := map[common.Location]*interpreter.Interpreter{} codes := map[common.Location][]byte{ ftLocation: []byte(realFungibleTokenContractInterface), } @@ -243,7 +201,7 @@ func TestInterpreterFTTransfer(t *testing.T) { checkerConfig := &sema.Config{ ImportHandler: func(checker *sema.Checker, location common.Location, importRange ast.Range) (sema.Import, error) { - imported, ok := programs[location] + imported, ok := subInterpreters[location] if !ok { return nil, fmt.Errorf("cannot find contract in location %s", location) } @@ -262,7 +220,7 @@ func TestInterpreterFTTransfer(t *testing.T) { return baseActivation }, ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { - imported, ok := programs[location] + imported, ok := subInterpreters[location] if !ok { panic(fmt.Errorf("cannot find contract in location %s", location)) } @@ -321,6 +279,10 @@ func TestInterpreterFTTransfer(t *testing.T) { accountHandler.parseAndCheckProgram = func(code []byte, location common.Location, getAndSetProgram bool) (*interpreter.Program, error) { + if subInterpreter, ok := subInterpreters[location]; ok { + return subInterpreter.Program, nil + } + inter, err := parseCheckAndInterpretWithOptions( t, string(code), @@ -335,7 +297,7 @@ func TestInterpreterFTTransfer(t *testing.T) { return nil, err } - programs[location] = inter + subInterpreters[location] = inter return inter.Program, err } @@ -352,7 +314,7 @@ func TestInterpreterFTTransfer(t *testing.T) { }, ) require.NoError(t, err) - programs[ftLocation] = inter + subInterpreters[ftLocation] = inter // ----- Deploy FlowToken Contract ----- @@ -412,6 +374,7 @@ func TestInterpreterFTTransfer(t *testing.T) { CheckerConfig: checkerConfig, }, ) + require.NoError(t, err) signer = stdlib.NewAccountReferenceValue( inter, @@ -438,6 +401,7 @@ func TestInterpreterFTTransfer(t *testing.T) { CheckerConfig: checkerConfig, }, ) + require.NoError(t, err) signer = stdlib.NewAccountReferenceValue( inter, @@ -468,6 +432,7 @@ func TestInterpreterFTTransfer(t *testing.T) { CheckerConfig: checkerConfig, }, ) + require.NoError(t, err) signer = stdlib.NewAccountReferenceValue( inter, @@ -515,3 +480,538 @@ func TestInterpreterFTTransfer(t *testing.T) { } } } + +func BenchmarkInterpreterFTTransfer(b *testing.B) { + + // ---- Deploy FT Contract ----- + + storage := interpreter.NewInMemoryStorage(nil) + + contractsAddress := common.MustBytesToAddress([]byte{0x1}) + senderAddress := common.MustBytesToAddress([]byte{0x2}) + receiverAddress := common.MustBytesToAddress([]byte{0x3}) + + flowTokenLocation := common.NewAddressLocation(nil, contractsAddress, "FlowToken") + ftLocation := common.NewAddressLocation(nil, contractsAddress, "FungibleToken") + + subInterpreters := map[common.Location]*interpreter.Interpreter{} + codes := map[common.Location][]byte{ + ftLocation: []byte(realFungibleTokenContractInterface), + } + + txLocation := runtime_utils.NewTransactionLocationGenerator() + + var signer interpreter.Value + var flowTokenContractValue *interpreter.CompositeValue + + accountHandler := &testAccountHandler{ + getAccountContractCode: func(location common.AddressLocation) ([]byte, error) { + code, ok := codes[location] + if !ok { + return nil, nil + // return nil, fmt.Errorf("cannot find code for %s", location) + } + + return code, nil + }, + updateAccountContractCode: func(location common.AddressLocation, code []byte) error { + codes[location] = code + return nil + }, + contractUpdateRecorded: func(location common.AddressLocation) bool { + return false + }, + interpretContract: func( + location common.AddressLocation, + program *interpreter.Program, + name string, + invocation stdlib.DeployedContractConstructorInvocation, + ) (*interpreter.CompositeValue, error) { + if location == flowTokenLocation { + return flowTokenContractValue, nil + } + return nil, fmt.Errorf("cannot interpret contract %s", location) + }, + temporarilyRecordCode: func(location common.AddressLocation, code []byte) { + // do nothing + }, + emitEvent: func(*interpreter.Interpreter, interpreter.LocationRange, *sema.CompositeType, []interpreter.Value) { + // do nothing + }, + recordContractUpdate: func(location common.AddressLocation, value *interpreter.CompositeValue) { + // do nothing + }, + } + + baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) + interpreter.Declare(baseActivation, stdlib.PanicFunction) + interpreter.Declare(baseActivation, stdlib.NewGetAccountFunction(accountHandler)) + + checkerConfig := &sema.Config{ + ImportHandler: func(checker *sema.Checker, location common.Location, importRange ast.Range) (sema.Import, error) { + imported, ok := subInterpreters[location] + if !ok { + return nil, fmt.Errorf("cannot find contract in location %s", location) + } + + return sema.ElaborationImport{ + Elaboration: imported.Program.Elaboration, + }, nil + }, + BaseValueActivationHandler: baseValueActivation, + LocationHandler: singleIdentifierLocationResolver(b), + } + + interConfig := &interpreter.Config{ + Storage: storage, + BaseActivationHandler: func(_ common.Location) *interpreter.VariableActivation { + return baseActivation + }, + ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { + imported, ok := subInterpreters[location] + if !ok { + panic(fmt.Errorf("cannot find contract in location %s", location)) + } + + return interpreter.InterpreterImport{ + Interpreter: imported, + } + }, + ContractValueHandler: func( + inter *interpreter.Interpreter, + compositeType *sema.CompositeType, + constructorGenerator func(common.Address) *interpreter.HostFunctionValue, + invocationRange ast.Range, + ) interpreter.ContractValue { + + constructor := constructorGenerator(common.ZeroAddress) + + value, err := inter.InvokeFunctionValue( + constructor, + []interpreter.Value{signer}, + []sema.Type{ + sema.FullyEntitledAccountReferenceType, + }, + []sema.Type{ + sema.FullyEntitledAccountReferenceType, + }, + compositeType, + ast.Range{}, + ) + if err != nil { + panic(err) + } + + flowTokenContractValue = value.(*interpreter.CompositeValue) + return flowTokenContractValue + }, + CapabilityBorrowHandler: func( + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + address interpreter.AddressValue, + capabilityID interpreter.UInt64Value, + wantedBorrowType *sema.ReferenceType, + capabilityBorrowType *sema.ReferenceType, + ) interpreter.ReferenceValue { + return stdlib.BorrowCapabilityController( + inter, + locationRange, + address, + capabilityID, + wantedBorrowType, + capabilityBorrowType, + accountHandler, + ) + }, + } + + accountHandler.parseAndCheckProgram = + func(code []byte, location common.Location, getAndSetProgram bool) (*interpreter.Program, error) { + if subInterpreter, ok := subInterpreters[location]; ok { + return subInterpreter.Program, nil + } + + inter, err := parseCheckAndInterpretWithOptions( + b, + string(code), + location, + ParseCheckAndInterpretOptions{ + Config: interConfig, + CheckerConfig: checkerConfig, + }, + ) + + if err != nil { + return nil, err + } + + subInterpreters[location] = inter + + return inter.Program, err + } + + // ----- Parse and Check FungibleToken Contract interface ----- + + inter, err := parseCheckAndInterpretWithOptions( + b, + realFungibleTokenContractInterface, + ftLocation, + ParseCheckAndInterpretOptions{ + Config: interConfig, + CheckerConfig: checkerConfig, + }, + ) + require.NoError(b, err) + subInterpreters[ftLocation] = inter + + // ----- Deploy FlowToken Contract ----- + + tx := fmt.Sprintf(` + transaction { + prepare(signer: auth(Storage, Capabilities, Contracts) &Account) { + signer.contracts.add(name: "FlowToken", code: "%s".decodeHex(), signer) + } + }`, + hex.EncodeToString([]byte(realFlowContract)), + ) + + inter, err = parseCheckAndInterpretWithOptions( + b, + tx, + txLocation(), + ParseCheckAndInterpretOptions{ + Config: interConfig, + CheckerConfig: checkerConfig, + }, + ) + require.NoError(b, err) + + signer = stdlib.NewAccountReferenceValue( + inter, + accountHandler, + interpreter.AddressValue(contractsAddress), + interpreter.FullyEntitledAccountAccess, + interpreter.EmptyLocationRange, + ) + + err = inter.InvokeTransaction(0, signer) + require.NoError(b, err) + + // ----- Run setup account transaction ----- + + authorization := sema.NewEntitlementSetAccess( + []*sema.EntitlementType{ + sema.BorrowValueType, + sema.IssueStorageCapabilityControllerType, + sema.PublishCapabilityType, + sema.SaveValueType, + }, + sema.Conjunction, + ) + + for _, address := range []common.Address{ + senderAddress, + receiverAddress, + } { + inter, err := parseCheckAndInterpretWithOptions( + b, + realSetupFlowTokenAccountTransaction, + txLocation(), + ParseCheckAndInterpretOptions{ + Config: interConfig, + CheckerConfig: checkerConfig, + }, + ) + require.NoError(b, err) + + signer = stdlib.NewAccountReferenceValue( + inter, + accountHandler, + interpreter.AddressValue(address), + interpreter.ConvertSemaAccessToStaticAuthorization(nil, authorization), + interpreter.EmptyLocationRange, + ) + + err = inter.InvokeTransaction(0, signer) + require.NoError(b, err) + } + + // Mint FLOW to sender + + total := int64(1000000) + + inter, err = parseCheckAndInterpretWithOptions( + b, + realMintFlowTokenTransaction, + txLocation(), + ParseCheckAndInterpretOptions{ + Config: interConfig, + CheckerConfig: checkerConfig, + }, + ) + require.NoError(b, err) + + signer = stdlib.NewAccountReferenceValue( + inter, + accountHandler, + interpreter.AddressValue(contractsAddress), + interpreter.ConvertSemaAccessToStaticAuthorization(nil, authorization), + interpreter.EmptyLocationRange, + ) + + err = inter.InvokeTransaction( + 0, + interpreter.AddressValue(senderAddress), + interpreter.NewUnmeteredIntValueFromInt64(total), + signer, + ) + require.NoError(b, err) + + // ----- Run token transfer transaction ----- + + signer = stdlib.NewAccountReferenceValue( + inter, + accountHandler, + interpreter.AddressValue(senderAddress), + interpreter.ConvertSemaAccessToStaticAuthorization(nil, authorization), + interpreter.EmptyLocationRange, + ) + + inter, err = parseCheckAndInterpretWithOptions( + b, + realFlowTokenTransferTransaction, + txLocation(), + ParseCheckAndInterpretOptions{ + Config: interConfig, + CheckerConfig: checkerConfig, + }, + ) + require.NoError(b, err) + + transferAmount := int64(1) + + amount := interpreter.NewUnmeteredIntValueFromInt64(transferAmount) + receiver := interpreter.AddressValue(receiverAddress) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + err = inter.InvokeTransaction( + 0, + amount, + receiver, + signer, + ) + require.NoError(b, err) + } + + b.StopTimer() +} + +func BenchmarkRuntimeFungibleTokenTransfer(b *testing.B) { + + interpreterRuntime := runtime_utils.NewTestInterpreterRuntime() + + contractsAddress := common.MustBytesToAddress([]byte{0x1}) + senderAddress := common.MustBytesToAddress([]byte{0x2}) + receiverAddress := common.MustBytesToAddress([]byte{0x3}) + + accountCodes := map[common.Location][]byte{} + + var events []cadence.Event + + signerAccount := contractsAddress + + runtimeInterface := &runtime_utils.TestRuntimeInterface{ + OnGetCode: func(location common.Location) (bytes []byte, err error) { + return accountCodes[location], nil + }, + Storage: runtime_utils.NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]common.Address, error) { + return []common.Address{signerAccount}, nil + }, + OnResolveLocation: runtime_utils.NewSingleIdentifierLocationResolver(b), + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + return accountCodes[location], nil + }, + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (value cadence.Value, err error) { + return json.Decode(nil, b) + }, + } + + environment := runtime.NewBaseInterpreterEnvironment(runtime.Config{}) + + nextTransactionLocation := runtime_utils.NewTransactionLocationGenerator() + + // Deploy Fungible Token contract + + err := interpreterRuntime.ExecuteTransaction( + runtime.Script{ + Source: utils.DeploymentTransaction( + "FungibleToken", + []byte(realFungibleTokenContractInterface), + ), + }, + runtime.Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + Environment: environment, + }, + ) + require.NoError(b, err) + + // Deploy Flow Token contract + + err = interpreterRuntime.ExecuteTransaction( + runtime.Script{ + Source: []byte(fmt.Sprintf(` + transaction { + prepare(signer: auth(Storage, Capabilities, Contracts) &Account) { + signer.contracts.add(name: "FlowToken", code: "%s".decodeHex(), signer) + } + }`, + hex.EncodeToString([]byte(realFlowContract)), + )), + }, + runtime.Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + Environment: environment, + }, + ) + require.NoError(b, err) + + // Setup both user accounts for Flow Token + + for _, address := range []common.Address{ + senderAddress, + receiverAddress, + } { + + signerAccount = address + + err = interpreterRuntime.ExecuteTransaction( + runtime.Script{ + Source: []byte(realSetupFlowTokenAccountTransaction), + }, + runtime.Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + Environment: environment, + }, + ) + require.NoError(b, err) + } + + // Mint 1000 FLOW to sender + + amount := 100000000000 + mintAmount := cadence.NewInt(amount) + mintAmountValue := interpreter.NewUnmeteredIntValueFromInt64(int64(amount)) + + signerAccount = contractsAddress + + err = interpreterRuntime.ExecuteTransaction( + runtime.Script{ + Source: []byte(realMintFlowTokenTransaction), + Arguments: encodeArgs([]cadence.Value{ + cadence.Address(senderAddress), + mintAmount, + }), + }, + runtime.Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + Environment: environment, + }, + ) + require.NoError(b, err) + + // Benchmark sending tokens from sender to receiver + + sendAmount := cadence.NewInt(1) + + signerAccount = senderAddress + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + + err = interpreterRuntime.ExecuteTransaction( + runtime.Script{ + Source: []byte(realFlowTokenTransferTransaction), + Arguments: encodeArgs([]cadence.Value{ + sendAmount, + cadence.Address(receiverAddress), + }), + }, + runtime.Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + Environment: environment, + }, + ) + require.NoError(b, err) + } + + b.StopTimer() + + // Run validation scripts + + sum := interpreter.NewUnmeteredIntValueFromInt64(0) + + inter := runtime_utils.NewTestInterpreter(b) + + nextScriptLocation := runtime_utils.NewScriptLocationGenerator() + + for _, address := range []common.Address{ + senderAddress, + receiverAddress, + } { + + result, err := interpreterRuntime.ExecuteScript( + runtime.Script{ + Source: []byte(realFlowTokenBalanceScript), + Arguments: encodeArgs([]cadence.Value{ + cadence.Address(address), + }), + }, + runtime.Context{ + Interface: runtimeInterface, + Location: nextScriptLocation(), + Environment: environment, + }, + ) + require.NoError(b, err) + + value := interpreter.NewUnmeteredIntValueFromBigInt(result.(cadence.Int).Big()) + + require.True(b, bool(value.Less(inter, mintAmountValue, interpreter.EmptyLocationRange))) + + sum = sum.Plus(inter, value, interpreter.EmptyLocationRange).(interpreter.IntValue) + } + + utils.RequireValuesEqual(b, nil, mintAmountValue, sum) +} + +func encodeArgs(argValues []cadence.Value) [][]byte { + args := make([][]byte, len(argValues)) + for i, arg := range argValues { + var err error + args[i], err = json.Encode(arg) + if err != nil { + panic(fmt.Errorf("broken test: invalid argument: %w", err)) + } + } + return args +} From 966036c55e854b2e7aed33c257dff79b8711f17d Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 25 Oct 2024 09:58:28 -0700 Subject: [PATCH 55/89] Refactor and cleanup --- bbq/vm/linker.go | 12 +- bbq/vm/test/ft_test.go | 29 +- bbq/vm/test/interpreter_test.go | 20 +- bbq/vm/test/runtime_test.go | 16 +- bbq/vm/test/utils.go | 13 +- bbq/vm/test/vm_bench_test.go | 472 ++++++++++++++++++++++++++++ bbq/vm/test/vm_test.go | 529 ++++---------------------------- bbq/vm/vm.go | 39 ++- 8 files changed, 612 insertions(+), 518 deletions(-) create mode 100644 bbq/vm/test/vm_bench_test.go diff --git a/bbq/vm/linker.go b/bbq/vm/linker.go index 532194661e..1b781509e7 100644 --- a/bbq/vm/linker.go +++ b/bbq/vm/linker.go @@ -35,7 +35,8 @@ type LinkedGlobals struct { } // LinkGlobals performs the linking of global functions and variables for a given program. -func LinkGlobals( +func (vm *VM) LinkGlobals( + location common.Location, program *bbq.Program, conf *Config, linkedGlobalsCache map[common.Location]LinkedGlobals, @@ -51,7 +52,12 @@ func LinkGlobals( importedProgram := conf.ImportHandler(importLocation) // Link and get all globals at the import location. - linkedGlobals = LinkGlobals(importedProgram, conf, linkedGlobalsCache) + linkedGlobals = vm.LinkGlobals( + importLocation, + importedProgram, + conf, + linkedGlobalsCache, + ) // If the imported program is a contract, // load the contract value and populate the global variable. @@ -85,7 +91,7 @@ func LinkGlobals( importedGlobals = append(importedGlobals, importedGlobal) } - ctx := NewContext(program, nil) + ctx := vm.NewProgramContext(location, program) globals := make([]Value, 0) indexedGlobals := make(map[string]Value, 0) diff --git a/bbq/vm/test/ft_test.go b/bbq/vm/test/ft_test.go index 1bfa6ef9a8..2e7ec328eb 100644 --- a/bbq/vm/test/ft_test.go +++ b/bbq/vm/test/ft_test.go @@ -30,6 +30,7 @@ import ( "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" "github.com/onflow/cadence/sema" + "github.com/onflow/cadence/tests/runtime_utils" ) func TestFTTransfer(t *testing.T) { @@ -43,6 +44,9 @@ func TestFTTransfer(t *testing.T) { senderAddress := common.MustBytesToAddress([]byte{0x2}) receiverAddress := common.MustBytesToAddress([]byte{0x3}) + txLocation := runtime_utils.NewTransactionLocationGenerator() + scriptLocation := runtime_utils.NewScriptLocationGenerator() + ftLocation := common.NewAddressLocation(nil, contractsAddress, "FungibleToken") _ = compileCode(t, realFungibleTokenContractInterface, ftLocation, programs) @@ -59,6 +63,7 @@ func TestFTTransfer(t *testing.T) { } flowTokenVM := vm.NewVM( + flowTokenLocation, flowTokenProgram, config, ) @@ -114,7 +119,7 @@ func TestFTTransfer(t *testing.T) { } { program := compileCode(t, realSetupFlowTokenAccountTransaction, nil, programs) - setupTxVM := vm.NewVM(program, vmConfig) + setupTxVM := vm.NewVM(txLocation(), program, vmConfig) authorizer := vm.NewAuthAccountReferenceValue(vmConfig, address) err = setupTxVM.ExecuteTransaction(nil, authorizer) @@ -126,7 +131,7 @@ func TestFTTransfer(t *testing.T) { program := compileCode(t, realMintFlowTokenTransaction, nil, programs) - mintTxVM := vm.NewVM(program, vmConfig) + mintTxVM := vm.NewVM(txLocation(), program, vmConfig) total := int64(1000000) @@ -144,7 +149,7 @@ func TestFTTransfer(t *testing.T) { tokenTransferTxProgram := compileCode(t, realFlowTokenTransferTransaction, nil, programs) - tokenTransferTxVM := vm.NewVM(tokenTransferTxProgram, vmConfig) + tokenTransferTxVM := vm.NewVM(txLocation(), tokenTransferTxProgram, vmConfig) transferAmount := int64(1) @@ -166,7 +171,7 @@ func TestFTTransfer(t *testing.T) { } { program := compileCode(t, realFlowTokenBalanceScript, nil, programs) - validationScriptVM := vm.NewVM(program, vmConfig) + validationScriptVM := vm.NewVM(scriptLocation(), program, vmConfig) addressValue := vm.AddressValue(address) result, err := validationScriptVM.Invoke("main", addressValue) @@ -192,6 +197,8 @@ func BenchmarkFTTransfer(b *testing.B) { senderAddress := common.MustBytesToAddress([]byte{0x2}) receiverAddress := common.MustBytesToAddress([]byte{0x3}) + txLocation := runtime_utils.NewTransactionLocationGenerator() + ftLocation := common.NewAddressLocation(nil, contractsAddress, "FungibleToken") _ = compileCode(b, realFungibleTokenContractInterface, ftLocation, programs) @@ -206,6 +213,7 @@ func BenchmarkFTTransfer(b *testing.B) { } flowTokenVM := vm.NewVM( + flowTokenLocation, flowTokenProgram, config, ) @@ -262,7 +270,7 @@ func BenchmarkFTTransfer(b *testing.B) { } { program := compileCode(b, realSetupFlowTokenAccountTransaction, nil, programs) - setupTxVM := vm.NewVM(program, vmConfig) + setupTxVM := vm.NewVM(txLocation(), program, vmConfig) authorizer := vm.NewAuthAccountReferenceValue(vmConfig, address) err = setupTxVM.ExecuteTransaction(nil, authorizer) @@ -274,7 +282,7 @@ func BenchmarkFTTransfer(b *testing.B) { program := compileCode(b, realMintFlowTokenTransaction, nil, programs) - mintTxVM := vm.NewVM(program, vmConfig) + mintTxVM := vm.NewVM(txLocation(), program, vmConfig) total := int64(1000000) @@ -290,8 +298,6 @@ func BenchmarkFTTransfer(b *testing.B) { // ----- Run token transfer transaction ----- - tokenTransferTxChecker := parseAndCheck(b, realFlowTokenTransferTransaction, nil, programs) - transferAmount := int64(1) tokenTransferTxArgs := []vm.Value{ @@ -301,13 +307,14 @@ func BenchmarkFTTransfer(b *testing.B) { tokenTransferTxAuthorizer := vm.NewAuthAccountReferenceValue(vmConfig, senderAddress) + tokenTransferTxChecker := parseAndCheck(b, realFlowTokenTransferTransaction, nil, programs) + tokenTransferTxProgram := compile(b, tokenTransferTxChecker, programs) + tokenTransferTxVM := vm.NewVM(txLocation(), tokenTransferTxProgram, vmConfig) + b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - tokenTransferTxProgram := compile(b, tokenTransferTxChecker, programs) - - tokenTransferTxVM := vm.NewVM(tokenTransferTxProgram, vmConfig) err = tokenTransferTxVM.ExecuteTransaction(tokenTransferTxArgs, tokenTransferTxAuthorizer) require.NoError(b, err) } diff --git a/bbq/vm/test/interpreter_test.go b/bbq/vm/test/interpreter_test.go index e1f214f5f2..ae723ebc46 100644 --- a/bbq/vm/test/interpreter_test.go +++ b/bbq/vm/test/interpreter_test.go @@ -6,6 +6,9 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/onflow/cadence" "github.com/onflow/cadence/activations" "github.com/onflow/cadence/ast" @@ -19,8 +22,6 @@ import ( "github.com/onflow/cadence/tests/checker" "github.com/onflow/cadence/tests/runtime_utils" "github.com/onflow/cadence/tests/utils" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) type ParseCheckAndInterpretOptions struct { @@ -81,10 +82,7 @@ func parseCheckAndInterpretWithOptionsAndMemoryMetering( if options.Config != nil { config = *options.Config } - if memoryGauge == nil { - config.AtreeValueValidationEnabled = true - config.AtreeStorageValidationEnabled = true - } + if config.UUIDHandler == nil { config.UUIDHandler = func() (uint64, error) { uuid++ @@ -777,6 +775,11 @@ func BenchmarkInterpreterFTTransfer(b *testing.B) { interpreter.EmptyLocationRange, ) + transferAmount := int64(1) + + amount := interpreter.NewUnmeteredIntValueFromInt64(transferAmount) + receiver := interpreter.AddressValue(receiverAddress) + inter, err = parseCheckAndInterpretWithOptions( b, realFlowTokenTransferTransaction, @@ -788,11 +791,6 @@ func BenchmarkInterpreterFTTransfer(b *testing.B) { ) require.NoError(b, err) - transferAmount := int64(1) - - amount := interpreter.NewUnmeteredIntValueFromInt64(transferAmount) - receiver := interpreter.AddressValue(receiverAddress) - b.ReportAllocs() b.ResetTimer() diff --git a/bbq/vm/test/runtime_test.go b/bbq/vm/test/runtime_test.go index 5f2150ebb4..533294a486 100644 --- a/bbq/vm/test/runtime_test.go +++ b/bbq/vm/test/runtime_test.go @@ -20,14 +20,17 @@ package test import ( "fmt" - "github.com/onflow/cadence/bbq" - "github.com/stretchr/testify/assert" "testing" - "github.com/onflow/cadence/bbq/vm" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" - "github.com/stretchr/testify/require" + "github.com/onflow/cadence/tests/runtime_utils" + + "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/bbq/vm" ) func TestResourceLossViaSelfRugPull(t *testing.T) { @@ -82,6 +85,7 @@ func TestResourceLossViaSelfRugPull(t *testing.T) { barProgram := compileCode(t, contractCode, barLocation, programs) barVM := vm.NewVM( + barLocation, barProgram, &vm.Config{ Storage: storage, @@ -167,7 +171,9 @@ func TestResourceLossViaSelfRugPull(t *testing.T) { }, } - txVM := vm.NewVM(program, vmConfig) + txLocation := runtime_utils.NewTransactionLocationGenerator() + + txVM := vm.NewVM(txLocation(), program, vmConfig) authorizer := vm.NewAuthAccountReferenceValue(vmConfig, authorizerAddress) err = txVM.ExecuteTransaction(nil, authorizer) diff --git a/bbq/vm/test/utils.go b/bbq/vm/test/utils.go index 3ae510400d..955a88cc7f 100644 --- a/bbq/vm/test/utils.go +++ b/bbq/vm/test/utils.go @@ -20,21 +20,23 @@ package test import ( "fmt" - "github.com/onflow/cadence/bbq/vm" "testing" "github.com/stretchr/testify/require" "github.com/onflow/cadence/ast" - "github.com/onflow/cadence/bbq" - "github.com/onflow/cadence/bbq/commons" - "github.com/onflow/cadence/bbq/compiler" "github.com/onflow/cadence/common" "github.com/onflow/cadence/errors" "github.com/onflow/cadence/interpreter" "github.com/onflow/cadence/sema" "github.com/onflow/cadence/stdlib" "github.com/onflow/cadence/tests/checker" + "github.com/onflow/cadence/tests/runtime_utils" + + "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/bbq/commons" + "github.com/onflow/cadence/bbq/compiler" + "github.com/onflow/cadence/bbq/vm" ) type testAccountHandler struct { @@ -469,7 +471,10 @@ func compileAndInvoke(t testing.TB, code string, funcName string) (vm.Value, err program := compileCode(t, code, location, map[common.Location]compiledProgram{}) storage := interpreter.NewInMemoryStorage(nil) + scriptLocation := runtime_utils.NewScriptLocationGenerator() + programVM := vm.NewVM( + scriptLocation(), program, vm.NewConfig(storage). WithAccountHandler(&testAccountHandler{}), diff --git a/bbq/vm/test/vm_bench_test.go b/bbq/vm/test/vm_bench_test.go new file mode 100644 index 0000000000..116e594f22 --- /dev/null +++ b/bbq/vm/test/vm_bench_test.go @@ -0,0 +1,472 @@ +package test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/atree" + + "github.com/onflow/cadence/ast" + "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/common" + "github.com/onflow/cadence/interpreter" + "github.com/onflow/cadence/sema" + . "github.com/onflow/cadence/tests/checker" + "github.com/onflow/cadence/tests/runtime_utils" + + "github.com/onflow/cadence/bbq/compiler" + "github.com/onflow/cadence/bbq/vm" +) + +func BenchmarkRecursionFib(b *testing.B) { + + scriptLocation := runtime_utils.NewScriptLocationGenerator() + + checker, err := ParseAndCheck(b, recursiveFib) + require.NoError(b, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) + + b.ReportAllocs() + b.ResetTimer() + + expected := vm.IntValue{SmallInt: 377} + + for i := 0; i < b.N; i++ { + + result, err := vmInstance.Invoke( + "fib", + vm.IntValue{SmallInt: 14}, + ) + require.NoError(b, err) + require.Equal(b, expected, result) + } +} + +func BenchmarkImperativeFib(b *testing.B) { + + scriptLocation := runtime_utils.NewScriptLocationGenerator() + + checker, err := ParseAndCheck(b, imperativeFib) + require.NoError(b, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) + + b.ReportAllocs() + b.ResetTimer() + + var value vm.Value = vm.IntValue{SmallInt: 14} + + for i := 0; i < b.N; i++ { + _, err := vmInstance.Invoke("fib", value) + require.NoError(b, err) + } +} + +func BenchmarkNewStruct(b *testing.B) { + + scriptLocation := runtime_utils.NewScriptLocationGenerator() + + checker, err := ParseAndCheck(b, ` + struct Foo { + var id : Int + + init(_ id: Int) { + self.id = id + } + } + + fun test(count: Int): Foo { + var i = 0 + var r = Foo(0) + while i < count { + i = i + 1 + r = Foo(i) + } + return r + } + `) + require.NoError(b, err) + + value := vm.IntValue{SmallInt: 1} + + b.ReportAllocs() + b.ResetTimer() + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) + + for i := 0; i < b.N; i++ { + _, err := vmInstance.Invoke("test", value) + require.NoError(b, err) + } +} + +func BenchmarkNewResource(b *testing.B) { + + checker, err := ParseAndCheck(b, ` + resource Foo { + var id : Int + + init(_ id: Int) { + self.id = id + } + } + + fun test(count: Int): @Foo { + var i = 0 + var r <- create Foo(0) + while i < count { + i = i + 1 + destroy create Foo(i) + } + return <- r + } + `) + require.NoError(b, err) + + b.ReportAllocs() + b.ResetTimer() + + value := vm.IntValue{SmallInt: 9} + + scriptLocation := runtime_utils.NewScriptLocationGenerator() + + for i := 0; i < b.N; i++ { + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) + _, err := vmInstance.Invoke("test", value) + require.NoError(b, err) + } +} + +func BenchmarkNewStructRaw(b *testing.B) { + + storage := interpreter.NewInMemoryStorage(nil) + vmConfig := &vm.Config{ + Storage: storage, + } + + fieldValue := vm.IntValue{SmallInt: 7} + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < 1; j++ { + structValue := vm.NewCompositeValue( + nil, + "Foo", + common.CompositeKindStructure, + common.Address{}, + storage.BasicSlabStorage, + ) + structValue.SetMember(vmConfig, "id", fieldValue) + structValue.Transfer(vmConfig, atree.Address{}, false, nil) + } + } +} + +func BenchmarkContractImport(b *testing.B) { + + location := common.NewAddressLocation(nil, common.Address{0x1}, "MyContract") + + importedChecker, err := ParseAndCheckWithOptions(b, + ` + contract MyContract { + var s: String + + fun helloText(): String { + return self.s + } + + init() { + self.s = "contract function of the imported program" + } + + struct Foo { + var id : String + + init(_ id: String) { + self.id = id + } + + fun sayHello(_ id: Int): String { + // return self.id + return MyContract.helloText() + } + } + } + `, + ParseAndCheckOptions{ + Location: location, + }, + ) + require.NoError(b, err) + + importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importedProgram := importCompiler.Compile() + + vmInstance := vm.NewVM(location, importedProgram, nil) + importedContractValue, err := vmInstance.InitializeContract() + require.NoError(b, err) + + vmConfig := &vm.Config{ + ImportHandler: func(location common.Location) *bbq.Program { + return importedProgram + }, + ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { + return importedContractValue + }, + } + + b.ResetTimer() + b.ReportAllocs() + + value := vm.IntValue{SmallInt: 7} + + for i := 0; i < b.N; i++ { + checker, err := ParseAndCheckWithOptions(b, ` + import MyContract from 0x01 + + fun test(count: Int): String { + var i = 0 + var r = MyContract.Foo("Hello from Foo!") + while i < count { + i = i + 1 + r = MyContract.Foo("Hello from Foo!") + r.sayHello(1) + } + return r.sayHello(1) + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + require.NoError(b, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + return importedProgram + } + program := comp.Compile() + + scriptLocation := runtime_utils.NewScriptLocationGenerator() + + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) + _, err = vmInstance.Invoke("test", value) + require.NoError(b, err) + } +} + +func BenchmarkMethodCall(b *testing.B) { + + b.Run("interface method call", func(b *testing.B) { + location := common.NewAddressLocation(nil, common.Address{0x1}, "MyContract") + + importedChecker, err := ParseAndCheckWithOptions(b, + ` + contract MyContract { + struct Foo: Greetings { + var id : String + + init(_ id: String) { + self.id = id + } + + fun sayHello(_ id: Int): String { + return self.id + } + } + + struct interface Greetings { + fun sayHello(_ id: Int): String + } + + struct interface SomethingElse { + } + } + `, + ParseAndCheckOptions{ + Location: location, + }, + ) + require.NoError(b, err) + + importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importedProgram := importCompiler.Compile() + + vmInstance := vm.NewVM(location, importedProgram, nil) + importedContractValue, err := vmInstance.InitializeContract() + require.NoError(b, err) + + checker, err := ParseAndCheckWithOptions(b, ` + import MyContract from 0x01 + + fun test(count: Int) { + var r: {MyContract.Greetings} = MyContract.Foo("Hello from Foo!") + var i = 0 + while i < count { + i = i + 1 + r.sayHello(1) + } + }`, + + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + require.NoError(b, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + return importedProgram + } + + program := comp.Compile() + + vmConfig := &vm.Config{ + ImportHandler: func(location common.Location) *bbq.Program { + return importedProgram + }, + ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { + return importedContractValue + }, + } + + scriptLocation := runtime_utils.NewScriptLocationGenerator() + + vmInstance = vm.NewVM(scriptLocation(), program, vmConfig) + + value := vm.IntValue{SmallInt: 10} + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + _, err := vmInstance.Invoke("test", value) + require.NoError(b, err) + } + }) + + b.Run("concrete type method call", func(b *testing.B) { + location := common.NewAddressLocation(nil, common.Address{0x1}, "MyContract") + + importedChecker, err := ParseAndCheckWithOptions(b, + ` + contract MyContract { + struct Foo: Greetings { + var id : String + + init(_ id: String) { + self.id = id + } + + fun sayHello(_ id: Int): String { + return self.id + } + } + + struct interface Greetings { + fun sayHello(_ id: Int): String + } + + struct interface SomethingElse { + } + } + `, + ParseAndCheckOptions{ + Location: location, + }, + ) + require.NoError(b, err) + + importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importedProgram := importCompiler.Compile() + + vmInstance := vm.NewVM(location, importedProgram, nil) + importedContractValue, err := vmInstance.InitializeContract() + require.NoError(b, err) + + checker, err := ParseAndCheckWithOptions(b, ` + import MyContract from 0x01 + + fun test(count: Int) { + var r: MyContract.Foo = MyContract.Foo("Hello from Foo!") + var i = 0 + while i < count { + i = i + 1 + r.sayHello(1) + } + }`, + + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + require.NoError(b, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + return importedProgram + } + + program := comp.Compile() + + vmConfig := &vm.Config{ + ImportHandler: func(location common.Location) *bbq.Program { + return importedProgram + }, + ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { + return importedContractValue + }, + } + + scriptLocation := runtime_utils.NewScriptLocationGenerator() + + vmInstance = vm.NewVM(scriptLocation(), program, vmConfig) + + value := vm.IntValue{SmallInt: 10} + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + _, err := vmInstance.Invoke("test", value) + require.NoError(b, err) + } + }) +} diff --git a/bbq/vm/test/vm_test.go b/bbq/vm/test/vm_test.go index ace5e6c94b..dbf9f40431 100644 --- a/bbq/vm/test/vm_test.go +++ b/bbq/vm/test/vm_test.go @@ -25,20 +25,18 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/atree" - "github.com/onflow/cadence/ast" - "github.com/onflow/cadence/bbq/vm" "github.com/onflow/cadence/common" - "github.com/onflow/cadence/interpreter" "github.com/onflow/cadence/sema" "github.com/onflow/cadence/stdlib" . "github.com/onflow/cadence/tests/checker" + "github.com/onflow/cadence/tests/runtime_utils" "github.com/onflow/cadence/tests/utils" "github.com/onflow/cadence/bbq" "github.com/onflow/cadence/bbq/commons" "github.com/onflow/cadence/bbq/compiler" + "github.com/onflow/cadence/bbq/vm" ) const recursiveFib = ` @@ -50,6 +48,11 @@ const recursiveFib = ` } ` +func scriptLocation() common.Location { + scriptLocation := runtime_utils.NewScriptLocationGenerator() + return scriptLocation() +} + func TestRecursionFib(t *testing.T) { t.Parallel() @@ -61,7 +64,7 @@ func TestRecursionFib(t *testing.T) { program := comp.Compile() vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke( "fib", @@ -72,33 +75,6 @@ func TestRecursionFib(t *testing.T) { require.Equal(t, 0, vmInstance.StackSize()) } -func BenchmarkRecursionFib(b *testing.B) { - - checker, err := ParseAndCheck(b, recursiveFib) - require.NoError(b, err) - - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - program := comp.Compile() - - vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) - - b.ReportAllocs() - b.ResetTimer() - - expected := vm.IntValue{SmallInt: 377} - - for i := 0; i < b.N; i++ { - - result, err := vmInstance.Invoke( - "fib", - vm.IntValue{SmallInt: 14}, - ) - require.NoError(b, err) - require.Equal(b, expected, result) - } -} - const imperativeFib = ` fun fib(_ n: Int): Int { var fib1 = 1 @@ -126,7 +102,7 @@ func TestImperativeFib(t *testing.T) { program := comp.Compile() vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke( "fib", @@ -137,28 +113,6 @@ func TestImperativeFib(t *testing.T) { require.Equal(t, 0, vmInstance.StackSize()) } -func BenchmarkImperativeFib(b *testing.B) { - - checker, err := ParseAndCheck(b, imperativeFib) - require.NoError(b, err) - - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - program := comp.Compile() - - vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) - - b.ReportAllocs() - b.ResetTimer() - - var value vm.Value = vm.IntValue{SmallInt: 14} - - for i := 0; i < b.N; i++ { - _, err := vmInstance.Invoke("fib", value) - require.NoError(b, err) - } -} - func TestBreak(t *testing.T) { t.Parallel() @@ -181,7 +135,7 @@ func TestBreak(t *testing.T) { program := comp.Compile() vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) @@ -213,7 +167,7 @@ func TestContinue(t *testing.T) { program := comp.Compile() vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) @@ -242,7 +196,7 @@ func TestNilCoalesce(t *testing.T) { program := comp.Compile() vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) @@ -268,7 +222,7 @@ func TestNilCoalesce(t *testing.T) { printProgram(program) vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) @@ -308,7 +262,7 @@ func TestNewStruct(t *testing.T) { program := comp.Compile() vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test", vm.IntValue{SmallInt: 10}) require.NoError(t, err) @@ -353,7 +307,7 @@ func TestStructMethodCall(t *testing.T) { program := comp.Compile() vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) @@ -362,111 +316,6 @@ func TestStructMethodCall(t *testing.T) { require.Equal(t, vm.StringValue{Str: []byte("Hello from Foo!")}, result) } -func BenchmarkNewStruct(b *testing.B) { - - checker, err := ParseAndCheck(b, ` - struct Foo { - var id : Int - - init(_ id: Int) { - self.id = id - } - } - - fun test(count: Int): Foo { - var i = 0 - var r = Foo(0) - while i < count { - i = i + 1 - r = Foo(i) - } - return r - } - `) - require.NoError(b, err) - - value := vm.IntValue{SmallInt: 1} - - b.ReportAllocs() - b.ResetTimer() - - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - program := comp.Compile() - - vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) - - for i := 0; i < b.N; i++ { - _, err := vmInstance.Invoke("test", value) - require.NoError(b, err) - } -} - -func BenchmarkNewResource(b *testing.B) { - - checker, err := ParseAndCheck(b, ` - resource Foo { - var id : Int - - init(_ id: Int) { - self.id = id - } - } - - fun test(count: Int): @Foo { - var i = 0 - var r <- create Foo(0) - while i < count { - i = i + 1 - destroy create Foo(i) - } - return <- r - } - `) - require.NoError(b, err) - - b.ReportAllocs() - b.ResetTimer() - - value := vm.IntValue{SmallInt: 9} - - for i := 0; i < b.N; i++ { - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - program := comp.Compile() - - vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) - _, err := vmInstance.Invoke("test", value) - require.NoError(b, err) - } -} - -func BenchmarkNewStructRaw(b *testing.B) { - - storage := interpreter.NewInMemoryStorage(nil) - vmConfig := &vm.Config{ - Storage: storage, - } - - fieldValue := vm.IntValue{SmallInt: 7} - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - for j := 0; j < 1; j++ { - structValue := vm.NewCompositeValue( - nil, - "Foo", - common.CompositeKindStructure, - common.Address{}, - storage.BasicSlabStorage, - ) - structValue.SetMember(vmConfig, "id", fieldValue) - structValue.Transfer(vmConfig, atree.Address{}, false, nil) - } - } -} - func TestImport(t *testing.T) { t.Parallel() @@ -533,7 +382,7 @@ func TestImport(t *testing.T) { }, } - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) @@ -548,6 +397,8 @@ func TestContractImport(t *testing.T) { t.Run("nested type def", func(t *testing.T) { + importLocation := common.NewAddressLocation(nil, common.Address{0x1}, "MyContract") + importedChecker, err := ParseAndCheckWithOptions(t, ` contract MyContract { @@ -573,7 +424,7 @@ func TestContractImport(t *testing.T) { } `, ParseAndCheckOptions{ - Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), + Location: importLocation, }, ) require.NoError(t, err) @@ -581,7 +432,7 @@ func TestContractImport(t *testing.T) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() - vmInstance := vm.NewVM(importedProgram, nil) + vmInstance := vm.NewVM(importLocation, importedProgram, nil) importedContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) @@ -621,7 +472,7 @@ func TestContractImport(t *testing.T) { }, } - vmInstance = vm.NewVM(program, vmConfig) + vmInstance = vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) @@ -631,6 +482,7 @@ func TestContractImport(t *testing.T) { }) t.Run("contract function", func(t *testing.T) { + importLocation := common.NewAddressLocation(nil, common.Address{0x1}, "MyContract") importedChecker, err := ParseAndCheckWithOptions(t, ` @@ -648,7 +500,7 @@ func TestContractImport(t *testing.T) { } `, ParseAndCheckOptions{ - Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), + Location: importLocation, }, ) require.NoError(t, err) @@ -656,7 +508,7 @@ func TestContractImport(t *testing.T) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() - vmInstance := vm.NewVM(importedProgram, nil) + vmInstance := vm.NewVM(importLocation, importedProgram, nil) importedContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) @@ -695,7 +547,7 @@ func TestContractImport(t *testing.T) { }, } - vmInstance = vm.NewVM(program, vmConfig) + vmInstance = vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) @@ -734,7 +586,7 @@ func TestContractImport(t *testing.T) { fooCompiler := compiler.NewCompiler(fooChecker.Program, fooChecker.Elaboration) fooProgram := fooCompiler.Compile() - vmInstance := vm.NewVM(fooProgram, nil) + vmInstance := vm.NewVM(fooLocation, fooProgram, nil) fooContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) @@ -790,7 +642,7 @@ func TestContractImport(t *testing.T) { }, } - vmInstance = vm.NewVM(barProgram, vmConfig) + vmInstance = vm.NewVM(barLocation, barProgram, vmConfig) barContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) @@ -868,7 +720,7 @@ func TestContractImport(t *testing.T) { }, } - vmInstance = vm.NewVM(program, vmConfig) + vmInstance = vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) @@ -961,7 +813,7 @@ func TestContractImport(t *testing.T) { //}, } - vmInstance := vm.NewVM(barProgram, vmConfig) + vmInstance := vm.NewVM(barLocation, barProgram, vmConfig) barContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) @@ -1039,7 +891,7 @@ func TestContractImport(t *testing.T) { }, } - vmInstance = vm.NewVM(program, vmConfig) + vmInstance = vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) @@ -1049,101 +901,6 @@ func TestContractImport(t *testing.T) { }) } -func BenchmarkContractImport(b *testing.B) { - - importedChecker, err := ParseAndCheckWithOptions(b, - ` - contract MyContract { - var s: String - - fun helloText(): String { - return self.s - } - - init() { - self.s = "contract function of the imported program" - } - - struct Foo { - var id : String - - init(_ id: String) { - self.id = id - } - - fun sayHello(_ id: Int): String { - // return self.id - return MyContract.helloText() - } - } - } - `, - ParseAndCheckOptions{ - Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), - }, - ) - require.NoError(b, err) - - importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) - importedProgram := importCompiler.Compile() - - vmInstance := vm.NewVM(importedProgram, nil) - importedContractValue, err := vmInstance.InitializeContract() - require.NoError(b, err) - - vmConfig := &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { - return importedProgram - }, - ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { - return importedContractValue - }, - } - - b.ResetTimer() - b.ReportAllocs() - - value := vm.IntValue{SmallInt: 7} - - for i := 0; i < b.N; i++ { - checker, err := ParseAndCheckWithOptions(b, ` - import MyContract from 0x01 - - fun test(count: Int): String { - var i = 0 - var r = MyContract.Foo("Hello from Foo!") - while i < count { - i = i + 1 - r = MyContract.Foo("Hello from Foo!") - r.sayHello(1) - } - return r.sayHello(1) - } - `, - ParseAndCheckOptions{ - Config: &sema.Config{ - ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { - return sema.ElaborationImport{ - Elaboration: importedChecker.Elaboration, - }, nil - }, - }, - }, - ) - require.NoError(b, err) - - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { - return importedProgram - } - program := comp.Compile() - - vmInstance := vm.NewVM(program, vmConfig) - _, err = vmInstance.Invoke("test", value) - require.NoError(b, err) - } -} - func TestInitializeContract(t *testing.T) { checker, err := ParseAndCheckWithOptions(t, @@ -1165,7 +922,7 @@ func TestInitializeContract(t *testing.T) { program := comp.Compile() vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) contractValue, err := vmInstance.InitializeContract() require.NoError(t, err) @@ -1202,7 +959,7 @@ func TestContractAccessDuringInit(t *testing.T) { program := comp.Compile() vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) contractValue, err := vmInstance.InitializeContract() require.NoError(t, err) @@ -1235,7 +992,7 @@ func TestContractAccessDuringInit(t *testing.T) { program := comp.Compile() vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) contractValue, err := vmInstance.InitializeContract() require.NoError(t, err) @@ -1269,7 +1026,7 @@ func TestFunctionOrder(t *testing.T) { program := comp.Compile() vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) @@ -1325,7 +1082,7 @@ func TestFunctionOrder(t *testing.T) { program := comp.Compile() vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("init") require.NoError(t, err) @@ -1340,6 +1097,7 @@ func TestContractField(t *testing.T) { t.Parallel() t.Run("get", func(t *testing.T) { + importLocation := common.NewAddressLocation(nil, common.Address{0x1}, "MyContract") importedChecker, err := ParseAndCheckWithOptions(t, ` @@ -1360,7 +1118,7 @@ func TestContractField(t *testing.T) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() - vmInstance := vm.NewVM(importedProgram, nil) + vmInstance := vm.NewVM(importLocation, importedProgram, nil) importedContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) @@ -1399,7 +1157,7 @@ func TestContractField(t *testing.T) { }, } - vmInstance = vm.NewVM(program, vmConfig) + vmInstance = vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) @@ -1408,6 +1166,7 @@ func TestContractField(t *testing.T) { }) t.Run("set", func(t *testing.T) { + importLocation := common.NewAddressLocation(nil, common.Address{0x1}, "MyContract") importedChecker, err := ParseAndCheckWithOptions(t, ` @@ -1428,7 +1187,7 @@ func TestContractField(t *testing.T) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() - vmInstance := vm.NewVM(importedProgram, nil) + vmInstance := vm.NewVM(importLocation, importedProgram, nil) importedContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) @@ -1468,7 +1227,7 @@ func TestContractField(t *testing.T) { }, } - vmInstance = vm.NewVM(program, vmConfig) + vmInstance = vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) @@ -1527,7 +1286,7 @@ func TestNativeFunctions(t *testing.T) { printProgram(program) vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) _, err = vmInstance.Invoke("test") require.NoError(t, err) @@ -1547,7 +1306,7 @@ func TestNativeFunctions(t *testing.T) { printProgram(program) vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) @@ -1581,7 +1340,7 @@ func TestTransaction(t *testing.T) { printProgram(program) vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) err = vmInstance.ExecuteTransaction(nil) require.NoError(t, err) @@ -1633,7 +1392,7 @@ func TestTransaction(t *testing.T) { printProgram(program) vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) args := []vm.Value{ vm.StringValue{[]byte("Hello!")}, @@ -1713,7 +1472,7 @@ func TestInterfaceMethodCall(t *testing.T) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() - vmInstance := vm.NewVM(importedProgram, nil) + vmInstance := vm.NewVM(location, importedProgram, nil) importedContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) @@ -1759,7 +1518,7 @@ func TestInterfaceMethodCall(t *testing.T) { }, } - vmInstance = vm.NewVM(program, vmConfig) + vmInstance = vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) @@ -1767,191 +1526,6 @@ func TestInterfaceMethodCall(t *testing.T) { require.Equal(t, vm.StringValue{Str: []byte("Hello from Foo!")}, result) } -func BenchmarkMethodCall(b *testing.B) { - - b.Run("interface method call", func(b *testing.B) { - - importedChecker, err := ParseAndCheckWithOptions(b, - ` - contract MyContract { - struct Foo: Greetings { - var id : String - - init(_ id: String) { - self.id = id - } - - fun sayHello(_ id: Int): String { - return self.id - } - } - - struct interface Greetings { - fun sayHello(_ id: Int): String - } - - struct interface SomethingElse { - } - } - `, - ParseAndCheckOptions{ - Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), - }, - ) - require.NoError(b, err) - - importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) - importedProgram := importCompiler.Compile() - - vmInstance := vm.NewVM(importedProgram, nil) - importedContractValue, err := vmInstance.InitializeContract() - require.NoError(b, err) - - checker, err := ParseAndCheckWithOptions(b, ` - import MyContract from 0x01 - - fun test(count: Int) { - var r: {MyContract.Greetings} = MyContract.Foo("Hello from Foo!") - var i = 0 - while i < count { - i = i + 1 - r.sayHello(1) - } - }`, - - ParseAndCheckOptions{ - Config: &sema.Config{ - ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { - return sema.ElaborationImport{ - Elaboration: importedChecker.Elaboration, - }, nil - }, - }, - }, - ) - require.NoError(b, err) - - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { - return importedProgram - } - - program := comp.Compile() - - vmConfig := &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { - return importedProgram - }, - ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { - return importedContractValue - }, - } - - vmInstance = vm.NewVM(program, vmConfig) - - value := vm.IntValue{SmallInt: 10} - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - _, err := vmInstance.Invoke("test", value) - require.NoError(b, err) - } - }) - - b.Run("concrete type method call", func(b *testing.B) { - - importedChecker, err := ParseAndCheckWithOptions(b, - ` - contract MyContract { - struct Foo: Greetings { - var id : String - - init(_ id: String) { - self.id = id - } - - fun sayHello(_ id: Int): String { - return self.id - } - } - - struct interface Greetings { - fun sayHello(_ id: Int): String - } - - struct interface SomethingElse { - } - } - `, - ParseAndCheckOptions{ - Location: common.NewAddressLocation(nil, common.Address{0x1}, "MyContract"), - }, - ) - require.NoError(b, err) - - importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) - importedProgram := importCompiler.Compile() - - vmInstance := vm.NewVM(importedProgram, nil) - importedContractValue, err := vmInstance.InitializeContract() - require.NoError(b, err) - - checker, err := ParseAndCheckWithOptions(b, ` - import MyContract from 0x01 - - fun test(count: Int) { - var r: MyContract.Foo = MyContract.Foo("Hello from Foo!") - var i = 0 - while i < count { - i = i + 1 - r.sayHello(1) - } - }`, - - ParseAndCheckOptions{ - Config: &sema.Config{ - ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { - return sema.ElaborationImport{ - Elaboration: importedChecker.Elaboration, - }, nil - }, - }, - }, - ) - require.NoError(b, err) - - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { - return importedProgram - } - - program := comp.Compile() - - vmConfig := &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { - return importedProgram - }, - ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { - return importedContractValue - }, - } - - vmInstance = vm.NewVM(program, vmConfig) - - value := vm.IntValue{SmallInt: 10} - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - _, err := vmInstance.Invoke("test", value) - require.NoError(b, err) - } - }) -} - func TestArrayLiteral(t *testing.T) { t.Parallel() @@ -1970,7 +1544,7 @@ func TestArrayLiteral(t *testing.T) { program := comp.Compile() vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) @@ -1998,7 +1572,7 @@ func TestArrayLiteral(t *testing.T) { program := comp.Compile() vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) @@ -2022,7 +1596,7 @@ func TestArrayLiteral(t *testing.T) { program := comp.Compile() vmConfig := &vm.Config{} - vmInstance := vm.NewVM(program, vmConfig) + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) @@ -2069,7 +1643,8 @@ func TestReference(t *testing.T) { program := comp.Compile() vmConfig := vm.NewConfig(nil) - vmInstance := vm.NewVM(program, vmConfig) + + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) result, err := vmInstance.Invoke("test") require.NoError(t, err) diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index 258eaaa1ba..eecfe3f5da 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -38,9 +38,14 @@ type VM struct { stack []Value config *Config linkedGlobalsCache map[common.Location]LinkedGlobals + contextCache map[common.Location]*Context } -func NewVM(program *bbq.Program, conf *Config) *VM { +func NewVM( + location common.Location, + program *bbq.Program, + conf *Config, +) *VM { // TODO: Remove initializing config. Following is for testing purpose only. if conf == nil { conf = &Config{} @@ -58,14 +63,23 @@ func NewVM(program *bbq.Program, conf *Config) *VM { }, } - // Link global variables and functions. - linkedGlobals := LinkGlobals(program, conf, linkedGlobalsCache) - - return &VM{ - globals: linkedGlobals.indexedGlobals, + vm := &VM{ linkedGlobalsCache: linkedGlobalsCache, config: conf, + contextCache: make(map[common.Location]*Context), } + + // Link global variables and functions. + linkedGlobals := vm.LinkGlobals( + location, + program, + conf, + linkedGlobalsCache, + ) + + vm.globals = linkedGlobals.indexedGlobals + + return vm } func (vm *VM) push(value Value) { @@ -745,7 +759,7 @@ func (vm *VM) lookupFunction(location common.Location, name string) FunctionValu // TODO: This currently link all functions in program, unnecessarily. // Link only the requested function. program := vm.config.ImportHandler(location) - ctx := NewContext(program, nil) + ctx := vm.NewProgramContext(location, program) indexedGlobals := make(map[string]Value, len(program.Functions)) for _, function := range program.Functions { @@ -763,6 +777,17 @@ func (vm *VM) lookupFunction(location common.Location, name string) FunctionValu return indexedGlobals[name].(FunctionValue) } +func (vm *VM) NewProgramContext(location common.Location, program *bbq.Program) *Context { + // Only one context needs to be created per Program. + ctx, ok := vm.contextCache[location] + if !ok { + ctx = NewContext(program, nil) + vm.contextCache[location] = ctx + } + + return ctx +} + func decodeLocation(locationBytes []byte) common.Location { // TODO: is it possible to re-use decoders? dec := interpreter.CBORDecMode.NewByteStreamDecoder(locationBytes) From b7004d65d3a548fa8ab0054134e718f923e42460 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 25 Oct 2024 11:28:54 -0700 Subject: [PATCH 56/89] Rename Context to ExecutableProgram --- bbq/vm/callframe.go | 10 +-- bbq/vm/{context.go => executable_program.go} | 29 ++++++-- bbq/vm/linker.go | 16 ++--- bbq/vm/value_function.go | 4 +- bbq/vm/vm.go | 70 +++++++++----------- 5 files changed, 72 insertions(+), 57 deletions(-) rename bbq/vm/{context.go => executable_program.go} (60%) diff --git a/bbq/vm/callframe.go b/bbq/vm/callframe.go index a0ca9dbb44..8aaa7e70c3 100644 --- a/bbq/vm/callframe.go +++ b/bbq/vm/callframe.go @@ -23,11 +23,11 @@ import ( ) type callFrame struct { - parent *callFrame - context *Context - locals []Value - function *bbq.Function - ip uint16 + parent *callFrame + executable *ExecutableProgram + locals []Value + function *bbq.Function + ip uint16 } func (f *callFrame) getUint16() uint16 { diff --git a/bbq/vm/context.go b/bbq/vm/executable_program.go similarity index 60% rename from bbq/vm/context.go rename to bbq/vm/executable_program.go index fe8f2a9c3b..d7ad6b08e3 100644 --- a/bbq/vm/context.go +++ b/bbq/vm/executable_program.go @@ -20,25 +20,44 @@ package vm import ( "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/common" ) -// Context is the context of a program. +// ExecutableProgram is the 'executable' version of a `bbq.Program`. // It holds information that are accessible to a given program, // such as constants, static-types, and global variables. // These info are accessed by the opcodes of the program. -// i.e: indexes used in opcodes refer to the indexes of its context. -type Context struct { +// i.e: indexes used in opcodes refer to the indexes of its ExecutableProgram. +type ExecutableProgram struct { + Location common.Location Program *bbq.Program Globals []Value Constants []Value StaticTypes []StaticType } -func NewContext(program *bbq.Program, globals []Value) *Context { - return &Context{ +func NewExecutableProgram( + location common.Location, + program *bbq.Program, + globals []Value, +) *ExecutableProgram { + return &ExecutableProgram{ + Location: location, Program: program, Globals: globals, Constants: make([]Value, len(program.Constants)), StaticTypes: make([]StaticType, len(program.Types)), } } + +func NewLoadedExecutableProgram(location common.Location, program *bbq.Program) *ExecutableProgram { + executable := NewExecutableProgram(location, program, nil) + + // Optimization: Pre load/decode types + for index, bytes := range program.Types { + staticType := decodeType(bytes) + executable.StaticTypes[index] = staticType + } + + return executable +} diff --git a/bbq/vm/linker.go b/bbq/vm/linker.go index 1b781509e7..ffac3ee43e 100644 --- a/bbq/vm/linker.go +++ b/bbq/vm/linker.go @@ -28,7 +28,7 @@ import ( type LinkedGlobals struct { // context shared by the globals in the program. - context *Context + executable *ExecutableProgram // globals defined in the program, indexed by name. indexedGlobals map[string]Value @@ -74,7 +74,7 @@ func (vm *VM) LinkGlobals( contractValue := conf.ContractValueHandler(conf, location) // Update the globals - both the context and the mapping. // Contract value is always at the zero-th index. - linkedGlobals.context.Globals[0] = contractValue + linkedGlobals.executable.Globals[0] = contractValue linkedGlobals.indexedGlobals[contract.Name] = contractValue } } @@ -91,7 +91,7 @@ func (vm *VM) LinkGlobals( importedGlobals = append(importedGlobals, importedGlobal) } - ctx := vm.NewProgramContext(location, program) + executable := NewLoadedExecutableProgram(location, program) globals := make([]Value, 0) indexedGlobals := make(map[string]Value, 0) @@ -113,8 +113,8 @@ func (vm *VM) LinkGlobals( // TODO: include non-function globals for _, function := range program.Functions { value := FunctionValue{ - Function: function, - Context: ctx, + Function: function, + Executable: executable, } globals = append(globals, value) @@ -124,13 +124,13 @@ func (vm *VM) LinkGlobals( // Globals of the current program are added first. // This is the same order as they are added in the compiler. // e.g: [global1, global2, ... [importedGlobal1, importedGlobal2, ...]] - ctx.Globals = globals - ctx.Globals = append(ctx.Globals, importedGlobals...) + executable.Globals = globals + executable.Globals = append(executable.Globals, importedGlobals...) // Return only the globals defined in the current program. // Because the importer/caller doesn't need to know globals of nested imports. return LinkedGlobals{ - context: ctx, + executable: executable, indexedGlobals: indexedGlobals, } } diff --git a/bbq/vm/value_function.go b/bbq/vm/value_function.go index 90e17966cd..0b9fdc06e1 100644 --- a/bbq/vm/value_function.go +++ b/bbq/vm/value_function.go @@ -27,8 +27,8 @@ import ( ) type FunctionValue struct { - Function *bbq.Function - Context *Context + Function *bbq.Function + Executable *ExecutableProgram } var _ Value = FunctionValue{} diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index eecfe3f5da..1d8367ff9c 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -38,7 +38,6 @@ type VM struct { stack []Value config *Config linkedGlobalsCache map[common.Location]LinkedGlobals - contextCache map[common.Location]*Context } func NewVM( @@ -66,7 +65,6 @@ func NewVM( vm := &VM{ linkedGlobalsCache: linkedGlobalsCache, config: conf, - contextCache: make(map[common.Location]*Context), } // Link global variables and functions. @@ -140,17 +138,17 @@ func (vm *VM) replaceTop(value Value) { vm.stack[lastIndex] = value } -func (vm *VM) pushCallFrame(ctx *Context, function *bbq.Function, arguments []Value) { - locals := make([]Value, function.LocalCount) +func (vm *VM) pushCallFrame(functionValue FunctionValue, arguments []Value) { + locals := make([]Value, functionValue.Function.LocalCount) for i, argument := range arguments { locals[i] = argument } callFrame := &callFrame{ - parent: vm.callFrame, - locals: locals, - function: function, - context: ctx, + parent: vm.callFrame, + locals: locals, + function: functionValue.Function, + executable: functionValue.Executable, } vm.callFrame = callFrame } @@ -193,7 +191,7 @@ func (vm *VM) invoke(function Value, arguments []Value) (Value, error) { ) } - vm.pushCallFrame(functionValue.Context, functionValue.Function, arguments) + vm.pushCallFrame(functionValue, arguments) vm.run() @@ -323,7 +321,7 @@ func opFalse(vm *VM) { func opGetConstant(vm *VM) { callFrame := vm.callFrame index := callFrame.getUint16() - constant := callFrame.context.Constants[index] + constant := callFrame.executable.Constants[index] if constant == nil { constant = vm.initializeConstant(index) } @@ -346,13 +344,13 @@ func opSetLocal(vm *VM) { func opGetGlobal(vm *VM) { callFrame := vm.callFrame index := callFrame.getUint16() - vm.push(callFrame.context.Globals[index]) + vm.push(callFrame.executable.Globals[index]) } func opSetGlobal(vm *VM) { callFrame := vm.callFrame index := callFrame.getUint16() - callFrame.context.Globals[index] = vm.pop() + callFrame.executable.Globals[index] = vm.pop() } func opSetIndex(vm *VM) { @@ -381,7 +379,7 @@ func opInvoke(vm *VM) { case FunctionValue: parameterCount := int(value.Function.ParameterCount) arguments := vm.stack[stackHeight-parameterCount:] - vm.pushCallFrame(value.Context, value.Function, arguments) + vm.pushCallFrame(value, arguments) vm.dropN(parameterCount) case NativeFunctionValue: parameterCount := value.ParameterCount @@ -437,7 +435,7 @@ func opInvokeDynamic(vm *VM) { parameterCount := int(functionValue.Function.ParameterCount) arguments := vm.stack[stackHeight-parameterCount:] - vm.pushCallFrame(functionValue.Context, functionValue.Function, arguments) + vm.pushCallFrame(functionValue, arguments) vm.dropN(parameterCount) } @@ -695,9 +693,9 @@ func (vm *VM) run() { } func (vm *VM) initializeConstant(index uint16) (value Value) { - ctx := vm.callFrame.context + executable := vm.callFrame.executable - constant := ctx.Program.Constants[index] + constant := executable.Program.Constants[index] switch constant.Kind { case constantkind.Int: // TODO: @@ -710,15 +708,17 @@ func (vm *VM) initializeConstant(index uint16) (value Value) { panic(errors.NewUnexpectedError("unsupported constant kind '%s'", constant.Kind.String())) } - ctx.Constants[index] = value + executable.Constants[index] = value return value } func (vm *VM) loadType() StaticType { callframe := vm.callFrame index := callframe.getUint16() - staticType := callframe.context.StaticTypes[index] + staticType := callframe.executable.StaticTypes[index] if staticType == nil { + // TODO: Remove. Should never reach because of the + // pre loading-decoding of types. staticType = vm.initializeType(index) } @@ -726,15 +726,19 @@ func (vm *VM) loadType() StaticType { } func (vm *VM) initializeType(index uint16) interpreter.StaticType { - ctx := vm.callFrame.context - typeBytes := ctx.Program.Types[index] + executable := vm.callFrame.executable + typeBytes := executable.Program.Types[index] + staticType := decodeType(typeBytes) + executable.StaticTypes[index] = staticType + return staticType +} + +func decodeType(typeBytes []byte) interpreter.StaticType { dec := interpreter.CBORDecMode.NewByteStreamDecoder(typeBytes) staticType, err := interpreter.NewTypeDecoder(dec, nil).DecodeStaticType() if err != nil { panic(err) } - - ctx.StaticTypes[index] = staticType return staticType } @@ -759,35 +763,27 @@ func (vm *VM) lookupFunction(location common.Location, name string) FunctionValu // TODO: This currently link all functions in program, unnecessarily. // Link only the requested function. program := vm.config.ImportHandler(location) - ctx := vm.NewProgramContext(location, program) + + // TODO: Instead of creating the executable here, maybe cache it, + // and the `ImportHandler` could return the executable itself. + executable := NewLoadedExecutableProgram(location, program) indexedGlobals := make(map[string]Value, len(program.Functions)) for _, function := range program.Functions { indexedGlobals[function.Name] = FunctionValue{ - Function: function, - Context: ctx, + Function: function, + Executable: executable, } } vm.linkedGlobalsCache[location] = LinkedGlobals{ - context: ctx, + executable: executable, indexedGlobals: indexedGlobals, } return indexedGlobals[name].(FunctionValue) } -func (vm *VM) NewProgramContext(location common.Location, program *bbq.Program) *Context { - // Only one context needs to be created per Program. - ctx, ok := vm.contextCache[location] - if !ok { - ctx = NewContext(program, nil) - vm.contextCache[location] = ctx - } - - return ctx -} - func decodeLocation(locationBytes []byte) common.Location { // TODO: is it possible to re-use decoders? dec := interpreter.CBORDecMode.NewByteStreamDecoder(locationBytes) From 4c937a563dd322b537ccf6cc3e5156a5d2632dc2 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 25 Oct 2024 13:14:29 -0700 Subject: [PATCH 57/89] Update licence headers --- bbq/bytecode_printer.go | 2 +- bbq/commons/constants.go | 2 +- bbq/commons/handlers.go | 2 +- bbq/commons/utils.go | 2 +- bbq/compiler/compiler.go | 2 +- bbq/compiler/compiler_test.go | 2 +- bbq/compiler/config.go | 2 +- bbq/compiler/constant.go | 2 +- bbq/compiler/function.go | 2 +- bbq/compiler/global.go | 2 +- bbq/compiler/local.go | 2 +- bbq/compiler/loop.go | 2 +- bbq/compiler/native_functions.go | 2 +- bbq/compiler/stack.go | 2 +- bbq/constant.go | 2 +- bbq/constantkind/constantkind.go | 2 +- bbq/contract.go | 2 +- bbq/function.go | 2 +- bbq/import.go | 2 +- bbq/leb128/leb128.go | 2 +- bbq/leb128/leb128_test.go | 2 +- bbq/opcode/opcode.go | 2 +- bbq/program.go | 2 +- bbq/variable.go | 2 +- bbq/vm/callframe.go | 2 +- bbq/vm/config.go | 2 +- bbq/vm/errors.go | 2 +- bbq/vm/executable_program.go | 2 +- bbq/vm/linker.go | 2 +- bbq/vm/native_functions.go | 2 +- bbq/vm/reference_tracking.go | 18 +++++++++++++ bbq/vm/storage.go | 30 ++++++++++----------- bbq/vm/storage_map.go | 2 +- bbq/vm/types.go | 2 +- bbq/vm/value.go | 2 +- bbq/vm/value_account.go | 2 +- bbq/vm/value_account_capabilities.go | 2 +- bbq/vm/value_account_storage.go | 2 +- bbq/vm/value_account_storagecapabilities.go | 2 +- bbq/vm/value_address.go | 2 +- bbq/vm/value_array.go | 2 +- bbq/vm/value_bool.go | 2 +- bbq/vm/value_capability.go | 2 +- bbq/vm/value_capability_controller.go | 2 +- bbq/vm/value_composite.go | 2 +- bbq/vm/value_conversions.go | 2 +- bbq/vm/value_dictionary.go | 2 +- bbq/vm/value_ephemeral_reference.go | 2 +- bbq/vm/value_function.go | 2 +- bbq/vm/value_int.go | 2 +- bbq/vm/value_nil.go | 2 +- bbq/vm/value_path.go | 2 +- bbq/vm/value_simple_composite.go | 2 +- bbq/vm/value_some.go | 2 +- bbq/vm/value_storage_reference.go | 2 +- bbq/vm/value_string.go | 2 +- bbq/vm/value_utils.go | 2 +- bbq/vm/value_void.go | 2 +- bbq/vm/vm.go | 2 +- tests/interpreter/fib_test.go | 2 +- 60 files changed, 91 insertions(+), 73 deletions(-) diff --git a/bbq/bytecode_printer.go b/bbq/bytecode_printer.go index 2eca09f9c1..2a547f0abf 100644 --- a/bbq/bytecode_printer.go +++ b/bbq/bytecode_printer.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/commons/constants.go b/bbq/commons/constants.go index d475273f5a..b5ad6766b2 100644 --- a/bbq/commons/constants.go +++ b/bbq/commons/constants.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/commons/handlers.go b/bbq/commons/handlers.go index 23d0a307eb..8c69e99e79 100644 --- a/bbq/commons/handlers.go +++ b/bbq/commons/handlers.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/commons/utils.go b/bbq/commons/utils.go index 9892debd6a..006ac25baf 100644 --- a/bbq/commons/utils.go +++ b/bbq/commons/utils.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/compiler/compiler.go b/bbq/compiler/compiler.go index 06efeddc9b..1bbd5647b4 100644 --- a/bbq/compiler/compiler.go +++ b/bbq/compiler/compiler.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/compiler/compiler_test.go b/bbq/compiler/compiler_test.go index 8a63548615..6938023812 100644 --- a/bbq/compiler/compiler_test.go +++ b/bbq/compiler/compiler_test.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/compiler/config.go b/bbq/compiler/config.go index fdb5c47ac2..84b0e57442 100644 --- a/bbq/compiler/config.go +++ b/bbq/compiler/config.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/compiler/constant.go b/bbq/compiler/constant.go index 1f6f66c23f..643eab08d3 100644 --- a/bbq/compiler/constant.go +++ b/bbq/compiler/constant.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/compiler/function.go b/bbq/compiler/function.go index 4465e2277b..75038f7ae8 100644 --- a/bbq/compiler/function.go +++ b/bbq/compiler/function.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/compiler/global.go b/bbq/compiler/global.go index 1354d63e8b..8ace0c0475 100644 --- a/bbq/compiler/global.go +++ b/bbq/compiler/global.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/compiler/local.go b/bbq/compiler/local.go index 30b467fbd5..53620359bc 100644 --- a/bbq/compiler/local.go +++ b/bbq/compiler/local.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/compiler/loop.go b/bbq/compiler/loop.go index 3fd4197f44..f91c5ebc96 100644 --- a/bbq/compiler/loop.go +++ b/bbq/compiler/loop.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/compiler/native_functions.go b/bbq/compiler/native_functions.go index ff418f889d..2aa6f1624a 100644 --- a/bbq/compiler/native_functions.go +++ b/bbq/compiler/native_functions.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/compiler/stack.go b/bbq/compiler/stack.go index 43ec3425e8..2251070ed6 100644 --- a/bbq/compiler/stack.go +++ b/bbq/compiler/stack.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/constant.go b/bbq/constant.go index cf925e37f0..57889cedb6 100644 --- a/bbq/constant.go +++ b/bbq/constant.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/constantkind/constantkind.go b/bbq/constantkind/constantkind.go index 691b836857..5d8caa584c 100644 --- a/bbq/constantkind/constantkind.go +++ b/bbq/constantkind/constantkind.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/contract.go b/bbq/contract.go index a5ab6bb892..4c1aa7ed9a 100644 --- a/bbq/contract.go +++ b/bbq/contract.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/function.go b/bbq/function.go index 477fe96265..4b7589e198 100644 --- a/bbq/function.go +++ b/bbq/function.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/import.go b/bbq/import.go index 65b6c9b55a..4e54052967 100644 --- a/bbq/import.go +++ b/bbq/import.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/leb128/leb128.go b/bbq/leb128/leb128.go index 735146a766..80469c96bc 100644 --- a/bbq/leb128/leb128.go +++ b/bbq/leb128/leb128.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/leb128/leb128_test.go b/bbq/leb128/leb128_test.go index b5a68dc29f..ac0fa0be15 100644 --- a/bbq/leb128/leb128_test.go +++ b/bbq/leb128/leb128_test.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/opcode/opcode.go b/bbq/opcode/opcode.go index 0c8680f201..98df78012a 100644 --- a/bbq/opcode/opcode.go +++ b/bbq/opcode/opcode.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/program.go b/bbq/program.go index a6709e0ee5..dba0bfe84f 100644 --- a/bbq/program.go +++ b/bbq/program.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/variable.go b/bbq/variable.go index e642c01d3d..521e22a1b1 100644 --- a/bbq/variable.go +++ b/bbq/variable.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/callframe.go b/bbq/vm/callframe.go index 8aaa7e70c3..1ce06a6551 100644 --- a/bbq/vm/callframe.go +++ b/bbq/vm/callframe.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/config.go b/bbq/vm/config.go index e77a15ef1e..0d8acb77c9 100644 --- a/bbq/vm/config.go +++ b/bbq/vm/config.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/errors.go b/bbq/vm/errors.go index 029b3ab6d5..661aed9b30 100644 --- a/bbq/vm/errors.go +++ b/bbq/vm/errors.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/executable_program.go b/bbq/vm/executable_program.go index d7ad6b08e3..6e8ad217e7 100644 --- a/bbq/vm/executable_program.go +++ b/bbq/vm/executable_program.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/linker.go b/bbq/vm/linker.go index ffac3ee43e..3212c643db 100644 --- a/bbq/vm/linker.go +++ b/bbq/vm/linker.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/native_functions.go b/bbq/vm/native_functions.go index e547111722..71d5bf9ee2 100644 --- a/bbq/vm/native_functions.go +++ b/bbq/vm/native_functions.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/reference_tracking.go b/bbq/vm/reference_tracking.go index eafaddb79d..5ac08f0cce 100644 --- a/bbq/vm/reference_tracking.go +++ b/bbq/vm/reference_tracking.go @@ -1,3 +1,21 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package vm import ( diff --git a/bbq/vm/storage.go b/bbq/vm/storage.go index 31b554c0a9..da0171e9d2 100644 --- a/bbq/vm/storage.go +++ b/bbq/vm/storage.go @@ -1,19 +1,19 @@ /* -* Cadence - The resource-oriented smart contract programming language -* -* Copyright 2019-2022 Dapper Labs, Inc. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package vm diff --git a/bbq/vm/storage_map.go b/bbq/vm/storage_map.go index a6040c1e96..2c25cd97d9 100644 --- a/bbq/vm/storage_map.go +++ b/bbq/vm/storage_map.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/types.go b/bbq/vm/types.go index 1e771767c3..7ffbfbe308 100644 --- a/bbq/vm/types.go +++ b/bbq/vm/types.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value.go b/bbq/vm/value.go index 6cc3c2dba5..f4a77fd086 100644 --- a/bbq/vm/value.go +++ b/bbq/vm/value.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_account.go b/bbq/vm/value_account.go index 9ebdf78aff..c637c10f45 100644 --- a/bbq/vm/value_account.go +++ b/bbq/vm/value_account.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_account_capabilities.go b/bbq/vm/value_account_capabilities.go index fd8c718022..dda4c6305b 100644 --- a/bbq/vm/value_account_capabilities.go +++ b/bbq/vm/value_account_capabilities.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_account_storage.go b/bbq/vm/value_account_storage.go index dc3dfeb0bb..0cbee855e8 100644 --- a/bbq/vm/value_account_storage.go +++ b/bbq/vm/value_account_storage.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_account_storagecapabilities.go b/bbq/vm/value_account_storagecapabilities.go index 0768e3bfb5..05d02b213b 100644 --- a/bbq/vm/value_account_storagecapabilities.go +++ b/bbq/vm/value_account_storagecapabilities.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_address.go b/bbq/vm/value_address.go index 1faa3adb19..402bdd602d 100644 --- a/bbq/vm/value_address.go +++ b/bbq/vm/value_address.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_array.go b/bbq/vm/value_array.go index 8a09f8a6d6..29dfb82422 100644 --- a/bbq/vm/value_array.go +++ b/bbq/vm/value_array.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_bool.go b/bbq/vm/value_bool.go index ce8db1588f..029594202b 100644 --- a/bbq/vm/value_bool.go +++ b/bbq/vm/value_bool.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_capability.go b/bbq/vm/value_capability.go index 0795478aaf..0931fe4eb1 100644 --- a/bbq/vm/value_capability.go +++ b/bbq/vm/value_capability.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_capability_controller.go b/bbq/vm/value_capability_controller.go index 3428ac8074..bc10d4f662 100644 --- a/bbq/vm/value_capability_controller.go +++ b/bbq/vm/value_capability_controller.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_composite.go b/bbq/vm/value_composite.go index 1973f1cd29..a7e4ae88f9 100644 --- a/bbq/vm/value_composite.go +++ b/bbq/vm/value_composite.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_conversions.go b/bbq/vm/value_conversions.go index 01469ce2e0..9351533dca 100644 --- a/bbq/vm/value_conversions.go +++ b/bbq/vm/value_conversions.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_dictionary.go b/bbq/vm/value_dictionary.go index ade5804f00..c03308c3bd 100644 --- a/bbq/vm/value_dictionary.go +++ b/bbq/vm/value_dictionary.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_ephemeral_reference.go b/bbq/vm/value_ephemeral_reference.go index 3faf2c3ec4..360c931f70 100644 --- a/bbq/vm/value_ephemeral_reference.go +++ b/bbq/vm/value_ephemeral_reference.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_function.go b/bbq/vm/value_function.go index 0b9fdc06e1..0e3c86a587 100644 --- a/bbq/vm/value_function.go +++ b/bbq/vm/value_function.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_int.go b/bbq/vm/value_int.go index 8aa289bc1b..2dc9974dc7 100644 --- a/bbq/vm/value_int.go +++ b/bbq/vm/value_int.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_nil.go b/bbq/vm/value_nil.go index 2e3043b187..acdbe2c9a2 100644 --- a/bbq/vm/value_nil.go +++ b/bbq/vm/value_nil.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_path.go b/bbq/vm/value_path.go index fac4c89f2e..f38e94c80a 100644 --- a/bbq/vm/value_path.go +++ b/bbq/vm/value_path.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_simple_composite.go b/bbq/vm/value_simple_composite.go index ca97085de2..14d251d1c4 100644 --- a/bbq/vm/value_simple_composite.go +++ b/bbq/vm/value_simple_composite.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_some.go b/bbq/vm/value_some.go index 448e8395a3..2ef54d1c20 100644 --- a/bbq/vm/value_some.go +++ b/bbq/vm/value_some.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_storage_reference.go b/bbq/vm/value_storage_reference.go index 915b73d6e2..345ff7f3fa 100644 --- a/bbq/vm/value_storage_reference.go +++ b/bbq/vm/value_storage_reference.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_string.go b/bbq/vm/value_string.go index 88735fd5d1..34617cbf4b 100644 --- a/bbq/vm/value_string.go +++ b/bbq/vm/value_string.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_utils.go b/bbq/vm/value_utils.go index 2289b78acb..89b74a57a4 100644 --- a/bbq/vm/value_utils.go +++ b/bbq/vm/value_utils.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/value_void.go b/bbq/vm/value_void.go index f14f1c8cd7..b20bd1d7c0 100644 --- a/bbq/vm/value_void.go +++ b/bbq/vm/value_void.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index 1d8367ff9c..fbe9ff46bc 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/interpreter/fib_test.go b/tests/interpreter/fib_test.go index 4b46081d8e..d6581959f5 100644 --- a/tests/interpreter/fib_test.go +++ b/tests/interpreter/fib_test.go @@ -1,7 +1,7 @@ /* * Cadence - The resource-oriented smart contract programming language * - * Copyright 2019-2022 Dapper Labs, Inc. + * Copyright Flow Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 7ee292c36cbad06bd3c62226c4bcdf5ce8412210 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Mon, 28 Oct 2024 14:54:18 -0700 Subject: [PATCH 58/89] Print FT transfer compiled codes --- bbq/vm/test/ft_test.go | 6 +++++- bbq/vm/test/runtime_test.go | 1 - bbq/vm/test/utils.go | 3 ++- bbq/vm/test/vm_test.go | 5 ----- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/bbq/vm/test/ft_test.go b/bbq/vm/test/ft_test.go index 2e7ec328eb..3aff054238 100644 --- a/bbq/vm/test/ft_test.go +++ b/bbq/vm/test/ft_test.go @@ -49,13 +49,15 @@ func TestFTTransfer(t *testing.T) { ftLocation := common.NewAddressLocation(nil, contractsAddress, "FungibleToken") - _ = compileCode(t, realFungibleTokenContractInterface, ftLocation, programs) + ftContractProgram := compileCode(t, realFungibleTokenContractInterface, ftLocation, programs) + printProgram("FungibleToken", ftContractProgram) // ----- Deploy FlowToken Contract ----- flowTokenLocation := common.NewAddressLocation(nil, contractsAddress, "FlowToken") flowTokenProgram := compileCode(t, realFlowContract, flowTokenLocation, programs) + printProgram("FlowToken", flowTokenProgram) config := &vm.Config{ Storage: storage, @@ -130,6 +132,7 @@ func TestFTTransfer(t *testing.T) { // Mint FLOW to sender program := compileCode(t, realMintFlowTokenTransaction, nil, programs) + printProgram("Setup FlowToken Tx", program) mintTxVM := vm.NewVM(txLocation(), program, vmConfig) @@ -148,6 +151,7 @@ func TestFTTransfer(t *testing.T) { // ----- Run token transfer transaction ----- tokenTransferTxProgram := compileCode(t, realFlowTokenTransferTransaction, nil, programs) + printProgram("FT Transfer Tx", tokenTransferTxProgram) tokenTransferTxVM := vm.NewVM(txLocation(), tokenTransferTxProgram, vmConfig) diff --git a/bbq/vm/test/runtime_test.go b/bbq/vm/test/runtime_test.go index 533294a486..5d639a5363 100644 --- a/bbq/vm/test/runtime_test.go +++ b/bbq/vm/test/runtime_test.go @@ -155,7 +155,6 @@ func TestResourceLossViaSelfRugPull(t *testing.T) { } program := compileCode(t, tx, nil, programs) - printProgram(program) vmConfig := &vm.Config{ Storage: storage, diff --git a/bbq/vm/test/utils.go b/bbq/vm/test/utils.go index 955a88cc7f..6a0ac428b9 100644 --- a/bbq/vm/test/utils.go +++ b/bbq/vm/test/utils.go @@ -380,8 +380,9 @@ func singleIdentifierLocationResolver(t testing.TB) func( } } -func printProgram(program *bbq.Program) { +func printProgram(name string, program *bbq.Program) { byteCodePrinter := &bbq.BytecodePrinter{} + fmt.Println("===================", name, "===================") fmt.Println(byteCodePrinter.PrintProgram(program)) } diff --git a/bbq/vm/test/vm_test.go b/bbq/vm/test/vm_test.go index dbf9f40431..64893423a7 100644 --- a/bbq/vm/test/vm_test.go +++ b/bbq/vm/test/vm_test.go @@ -219,7 +219,6 @@ func TestNilCoalesce(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - printProgram(program) vmConfig := &vm.Config{} vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) @@ -1283,7 +1282,6 @@ func TestNativeFunctions(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - printProgram(program) vmConfig := &vm.Config{} vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) @@ -1303,7 +1301,6 @@ func TestNativeFunctions(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - printProgram(program) vmConfig := &vm.Config{} vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) @@ -1337,7 +1334,6 @@ func TestTransaction(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - printProgram(program) vmConfig := &vm.Config{} vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) @@ -1389,7 +1385,6 @@ func TestTransaction(t *testing.T) { comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() - printProgram(program) vmConfig := &vm.Config{} vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) From d2a9fccb57f25d4055cc411b718a029047b192f3 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Mon, 28 Oct 2024 15:04:52 -0700 Subject: [PATCH 59/89] Skip TestResourceLossViaSelfRugPull test --- bbq/vm/test/runtime_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bbq/vm/test/runtime_test.go b/bbq/vm/test/runtime_test.go index 5d639a5363..7585d3d342 100644 --- a/bbq/vm/test/runtime_test.go +++ b/bbq/vm/test/runtime_test.go @@ -35,6 +35,9 @@ import ( func TestResourceLossViaSelfRugPull(t *testing.T) { + // TODO: + t.SkipNow() + // ---- Deploy FT Contract ----- storage := interpreter.NewInMemoryStorage(nil) From 78780d547e640ca4c982d4078843ef7030c54bcc Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Mon, 28 Oct 2024 15:45:59 -0700 Subject: [PATCH 60/89] Fix tests --- bbq/compiler/compiler_test.go | 60 ++++++++++++++++--------------- bbq/vm/value_array.go | 3 -- tests/ft_test.go | 66 ++++++++++++++++++----------------- 3 files changed, 65 insertions(+), 64 deletions(-) diff --git a/bbq/compiler/compiler_test.go b/bbq/compiler/compiler_test.go index 6938023812..ce4d20c2a4 100644 --- a/bbq/compiler/compiler_test.go +++ b/bbq/compiler/compiler_test.go @@ -61,14 +61,16 @@ func TestCompileRecursionFib(t *testing.T) { byte(opcode.GetLocal), 0, 0, byte(opcode.GetConstant), 0, 1, byte(opcode.IntSubtract), + byte(opcode.Transfer), 0, 0, byte(opcode.GetGlobal), 0, 0, - byte(opcode.Invoke), + byte(opcode.Invoke), 0, 0, // fib(n - 2) byte(opcode.GetLocal), 0, 0, - byte(opcode.GetConstant), 0, 2, + byte(opcode.GetConstant), 0, 0, byte(opcode.IntSubtract), + byte(opcode.Transfer), 0, 0, byte(opcode.GetGlobal), 0, 0, - byte(opcode.Invoke), + byte(opcode.Invoke), 0, 0, // return sum byte(opcode.IntAdd), byte(opcode.ReturnValue), @@ -86,10 +88,6 @@ func TestCompileRecursionFib(t *testing.T) { Data: []byte{0x1}, Kind: constantkind.Int, }, - { - Data: []byte{0x2}, - Kind: constantkind.Int, - }, }, program.Constants, ) @@ -124,39 +122,47 @@ func TestCompileImperativeFib(t *testing.T) { []byte{ // var fib1 = 1 byte(opcode.GetConstant), 0, 0, + byte(opcode.Transfer), 0, 0, byte(opcode.SetLocal), 0, 1, // var fib2 = 1 - byte(opcode.GetConstant), 0, 1, + byte(opcode.GetConstant), 0, 0, + byte(opcode.Transfer), 0, 0, byte(opcode.SetLocal), 0, 2, // var fibonacci = fib1 byte(opcode.GetLocal), 0, 1, + byte(opcode.Transfer), 0, 0, byte(opcode.SetLocal), 0, 3, // var i = 2 - byte(opcode.GetConstant), 0, 2, + byte(opcode.GetConstant), 0, 1, + byte(opcode.Transfer), 0, 0, byte(opcode.SetLocal), 0, 4, // while i < n byte(opcode.GetLocal), 0, 4, byte(opcode.GetLocal), 0, 0, byte(opcode.IntLess), - byte(opcode.JumpIfFalse), 0, 69, + byte(opcode.JumpIfFalse), 0, 93, // fibonacci = fib1 + fib2 byte(opcode.GetLocal), 0, 1, byte(opcode.GetLocal), 0, 2, byte(opcode.IntAdd), + byte(opcode.Transfer), 0, 0, byte(opcode.SetLocal), 0, 3, // fib1 = fib2 byte(opcode.GetLocal), 0, 2, + byte(opcode.Transfer), 0, 0, byte(opcode.SetLocal), 0, 1, // fib2 = fibonacci byte(opcode.GetLocal), 0, 3, + byte(opcode.Transfer), 0, 0, byte(opcode.SetLocal), 0, 2, // i = i + 1 byte(opcode.GetLocal), 0, 4, - byte(opcode.GetConstant), 0, 3, + byte(opcode.GetConstant), 0, 0, byte(opcode.IntAdd), + byte(opcode.Transfer), 0, 0, byte(opcode.SetLocal), 0, 4, // continue loop - byte(opcode.Jump), 0, 24, + byte(opcode.Jump), 0, 36, // return fibonacci byte(opcode.GetLocal), 0, 3, byte(opcode.ReturnValue), @@ -166,10 +172,6 @@ func TestCompileImperativeFib(t *testing.T) { require.Equal(t, []*bbq.Constant{ - { - Data: []byte{0x1}, - Kind: constantkind.Int, - }, { Data: []byte{0x1}, Kind: constantkind.Int, @@ -178,10 +180,6 @@ func TestCompileImperativeFib(t *testing.T) { Data: []byte{0x2}, Kind: constantkind.Int, }, - { - Data: []byte{0x1}, - Kind: constantkind.Int, - }, }, program.Constants, ) @@ -213,24 +211,26 @@ func TestCompileBreak(t *testing.T) { []byte{ // var i = 0 byte(opcode.GetConstant), 0, 0, + byte(opcode.Transfer), 0, 0, byte(opcode.SetLocal), 0, 0, // while true byte(opcode.True), - byte(opcode.JumpIfFalse), 0, 36, + byte(opcode.JumpIfFalse), 0, 42, // if i > 3 byte(opcode.GetLocal), 0, 0, byte(opcode.GetConstant), 0, 1, byte(opcode.IntGreater), - byte(opcode.JumpIfFalse), 0, 23, + byte(opcode.JumpIfFalse), 0, 26, // break - byte(opcode.Jump), 0, 36, + byte(opcode.Jump), 0, 42, // i = i + 1 byte(opcode.GetLocal), 0, 0, byte(opcode.GetConstant), 0, 2, byte(opcode.IntAdd), + byte(opcode.Transfer), 0, 0, byte(opcode.SetLocal), 0, 0, // repeat - byte(opcode.Jump), 0, 6, + byte(opcode.Jump), 0, 9, // return i byte(opcode.GetLocal), 0, 0, byte(opcode.ReturnValue), @@ -284,26 +284,28 @@ func TestCompileContinue(t *testing.T) { []byte{ // var i = 0 byte(opcode.GetConstant), 0, 0, + byte(opcode.Transfer), 0, 0, byte(opcode.SetLocal), 0, 0, // while true byte(opcode.True), - byte(opcode.JumpIfFalse), 0, 39, + byte(opcode.JumpIfFalse), 0, 45, // i = i + 1 byte(opcode.GetLocal), 0, 0, byte(opcode.GetConstant), 0, 1, byte(opcode.IntAdd), + byte(opcode.Transfer), 0, 0, byte(opcode.SetLocal), 0, 0, // if i < 3 byte(opcode.GetLocal), 0, 0, byte(opcode.GetConstant), 0, 2, byte(opcode.IntLess), - byte(opcode.JumpIfFalse), 0, 33, + byte(opcode.JumpIfFalse), 0, 39, // continue - byte(opcode.Jump), 0, 6, + byte(opcode.Jump), 0, 9, // break - byte(opcode.Jump), 0, 39, + byte(opcode.Jump), 0, 45, // repeat - byte(opcode.Jump), 0, 6, + byte(opcode.Jump), 0, 9, // return i byte(opcode.GetLocal), 0, 0, byte(opcode.ReturnValue), diff --git a/bbq/vm/value_array.go b/bbq/vm/value_array.go index 29dfb82422..708ea33171 100644 --- a/bbq/vm/value_array.go +++ b/bbq/vm/value_array.go @@ -24,12 +24,10 @@ import ( "github.com/onflow/cadence/common" "github.com/onflow/cadence/errors" "github.com/onflow/cadence/interpreter" - "github.com/onflow/cadence/sema" ) type ArrayValue struct { Type interpreter.ArrayStaticType - semaType sema.ArrayType array *atree.Array isResourceKinded bool elementSize uint @@ -242,7 +240,6 @@ func (v *ArrayValue) Transfer(config *Config, address atree.Address, remove bool array, ) - res.semaType = v.semaType res.isResourceKinded = v.isResourceKinded res.isDestroyed = v.isDestroyed diff --git a/tests/ft_test.go b/tests/ft_test.go index 4e5c0422ce..2efce854de 100644 --- a/tests/ft_test.go +++ b/tests/ft_test.go @@ -233,9 +233,9 @@ access(all) contract interface FungibleToken { /// createEmptyVault allows any user to create a new Vault that has a zero balance /// - access(all) fun createEmptyVault(): @{FungibleToken.Vault} { + access(all) fun createEmptyVault(vaultType: Type): @{FungibleToken.Vault} { post { - // result.getType() == vaultType: "The returned vault does not match the desired type" + result.getType() == vaultType: "The returned vault does not match the desired type" result.balance == 0.0: "The newly created Vault must have zero balance" } } @@ -387,7 +387,7 @@ access(all) contract FlowToken: FungibleToken { // and store the returned Vault in their storage in order to allow their // account to be able to receive deposits of this token type. // - access(all) fun createEmptyVault(): @FlowToken.Vault { + access(all) fun createEmptyVault(vaultType: Type): @FlowToken.Vault { return <-create Vault(balance: 0.0) } @@ -470,34 +470,35 @@ import FlowToken from 0x1 transaction { - prepare(signer: auth(BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { - - var storagePath = /storage/flowTokenVault - - if signer.storage.borrow<&FlowToken.Vault>(from: storagePath) != nil { - return + prepare(signer: auth(Storage, Capabilities) &Account) { + + if signer.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) == nil { + // Create a new flowToken Vault and put it in storage + var vault <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>()) + signer.storage.save(<- vault, to: /storage/flowTokenVault) + + // Create a public capability to the Vault that only exposes + // the deposit function through the Receiver interface + let vaultCap = signer.capabilities.storage.issue<&FlowToken.Vault>( + /storage/flowTokenVault + ) + + signer.capabilities.publish( + vaultCap, + at: /public/flowTokenReceiver + ) + + // Create a public capability to the Vault that only exposes + // the balance field through the Balance interface + let balanceCap = signer.capabilities.storage.issue<&FlowToken.Vault>( + /storage/flowTokenVault + ) + + signer.capabilities.publish( + balanceCap, + at: /public/flowTokenBalance + ) } - - // Create a new flowToken Vault and put it in storage - signer.storage.save(<-FlowToken.createEmptyVault(), to: storagePath) - - // Create a public capability to the Vault that only exposes - // the deposit function through the Receiver interface - let vaultCap = signer.capabilities.storage.issue<&FlowToken.Vault>(storagePath) - - signer.capabilities.publish( - vaultCap, - at: /public/flowTokenReceiver - ) - - // Create a public capability to the Vault that only exposes - // the balance field through the Balance interface - let balanceCap = signer.capabilities.storage.issue<&FlowToken.Vault>(storagePath) - - signer.capabilities.publish( - balanceCap, - at: /public/flowTokenBalance - ) } } ` @@ -509,8 +510,6 @@ import FlowToken from 0x1 transaction(recipient: Address, amount: UFix64) { let tokenAdmin: &FlowToken.Administrator - - /// Reference to the Fungible Token Receiver of the recipient let tokenReceiver: &{FungibleToken.Receiver} prepare(signer: auth(BorrowValue) &Account) { @@ -540,6 +539,8 @@ import FungibleToken from 0x1 import FlowToken from 0x1 transaction(amount: UFix64, to: Address) { + + // The Vault resource that holds the tokens that are being transferred let sentVault: @{FungibleToken.Vault} prepare(signer: auth(BorrowValue) &Account) { @@ -553,6 +554,7 @@ transaction(amount: UFix64, to: Address) { } execute { + // Get a reference to the recipient's Receiver let receiverRef = getAccount(to) .capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) From c61f9b2039011423e587e7d85d17b3b8dc0d2590 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 6 Nov 2024 12:47:07 -0800 Subject: [PATCH 61/89] Use type-index in the 'New' instruction --- bbq/bytecode_printer.go | 38 +++++++++++------ bbq/compiler/compiler.go | 39 ++++++----------- bbq/vm/test/vm_bench_test.go | 8 ++-- bbq/vm/test/vm_test.go | 3 +- bbq/vm/value_composite.go | 81 +++++++++++------------------------- bbq/vm/value_conversions.go | 8 ++-- bbq/vm/vm.go | 17 ++++---- 7 files changed, 80 insertions(+), 114 deletions(-) diff --git a/bbq/bytecode_printer.go b/bbq/bytecode_printer.go index 2a547f0abf..6cc2a63a1e 100644 --- a/bbq/bytecode_printer.go +++ b/bbq/bytecode_printer.go @@ -37,6 +37,8 @@ type BytecodePrinter struct { func (p *BytecodePrinter) PrintProgram(program *Program) string { p.printImports(program.Imports) p.printConstantPool(program.Constants) + p.printTypePool(program.Types) + for _, function := range program.Functions { p.printFunction(function) p.stringBuilder.WriteRune('\n') @@ -73,20 +75,10 @@ func (p *BytecodePrinter) printCode(codes []byte) { p.stringBuilder.WriteString(" " + fmt.Sprint(operand)) case opcode.New: - var kind int + var kind, typeIndex int kind, i = p.getIntOperand(codes, i) - - var location common.Location - location, i = p.getLocation(codes, i) - - var typeName string - typeName, i = p.getStringOperand(codes, i) - - if location != nil { - typeName = string(location.TypeID(nil, typeName)) - } - - p.stringBuilder.WriteString(" " + fmt.Sprint(kind) + " " + typeName) + typeIndex, i = p.getIntOperand(codes, i) + p.stringBuilder.WriteString(" " + fmt.Sprint(kind) + " " + fmt.Sprint(typeIndex)) case opcode.Cast: var typeIndex int @@ -190,6 +182,26 @@ func (p *BytecodePrinter) printConstantPool(constants []*Constant) { p.stringBuilder.WriteRune('\n') } +func (p *BytecodePrinter) printTypePool(types [][]byte) { + p.stringBuilder.WriteString("-- Type Pool --\n") + + for index, typeBytes := range types { + dec := interpreter.CBORDecMode.NewByteStreamDecoder(typeBytes) + typeDecoder := interpreter.NewTypeDecoder(dec, nil) + staticType, err := typeDecoder.DecodeStaticType() + if err != nil { + panic(err) + } + + p.stringBuilder.WriteString(fmt.Sprint(index)) + p.stringBuilder.WriteString(" | ") + p.stringBuilder.WriteString(string(staticType.ID())) + p.stringBuilder.WriteRune('\n') + } + + p.stringBuilder.WriteRune('\n') +} + func (p *BytecodePrinter) getLocation(codes []byte, i int) (location common.Location, endIndex int) { locationLen, i := p.getIntOperand(codes, i) locationBytes := codes[i+1 : i+1+locationLen] diff --git a/bbq/compiler/compiler.go b/bbq/compiler/compiler.go index e9bc6581b9..98092dbe3d 100644 --- a/bbq/compiler/compiler.go +++ b/bbq/compiler/compiler.go @@ -53,7 +53,7 @@ type Compiler struct { staticTypes [][]byte // Cache alike for staticTypes and constants in the pool. - typesInPool map[sema.Type]uint16 + typesInPool map[sema.TypeID]uint16 constantsInPool map[constantsCacheKey]*constant // TODO: initialize @@ -104,7 +104,7 @@ func NewCompiler( Config: &Config{}, globals: make(map[string]*global), importedGlobals: indexedNativeFunctions, - typesInPool: make(map[sema.Type]uint16), + typesInPool: make(map[sema.TypeID]uint16), constantsInPool: make(map[constantsCacheKey]*constant), compositeTypeStack: &Stack[*sema.CompositeType]{ elements: make([]*sema.CompositeType, 0), @@ -1087,34 +1087,19 @@ func (c *Compiler) compileInitializer(declaration *ast.SpecialFunctionDeclaratio // i.e: `self = New()` enclosingCompositeType := c.compositeTypeStack.top() - locationBytes, err := commons.LocationToBytes(enclosingCompositeType.Location) - if err != nil { - panic(err) - } - - byteSize := 2 + // two bytes for composite kind - 2 + // 2 bytes for location size - len(locationBytes) + // location - 2 + // 2 bytes for type name size - len(enclosingCompositeTypeName) // type name - - args := make([]byte, 0, byteSize) // Write composite kind + // TODO: Maybe get/include this from static-type. Then no need to provide separately. kindFirst, kindSecond := encodeUint16(uint16(enclosingCompositeType.Kind)) - args = append(args, kindFirst, kindSecond) - - // Write location - locationSizeFirst, locationSizeSecond := encodeUint16(uint16(len(locationBytes))) - args = append(args, locationSizeFirst, locationSizeSecond) - args = append(args, locationBytes...) - // Write composite name - typeNameSizeFirst, typeNameSizeSecond := encodeUint16(uint16(len(enclosingCompositeTypeName))) - args = append(args, typeNameSizeFirst, typeNameSizeSecond) - args = append(args, enclosingCompositeTypeName...) + index := c.getOrAddType(enclosingCompositeType) + typeFirst, typeSecond := encodeUint16(index) - c.emit(opcode.New, args...) + c.emit( + opcode.New, + kindFirst, kindSecond, + typeFirst, typeSecond, + ) if enclosingType.Kind == common.CompositeKindContract { // During contract init, update the global variable with the newly initialized contract value. @@ -1290,7 +1275,7 @@ func (c *Compiler) emitCheckType(targetType sema.Type) { func (c *Compiler) getOrAddType(targetType sema.Type) uint16 { // Optimization: Re-use types in the pool. - index, ok := c.typesInPool[targetType] + index, ok := c.typesInPool[targetType.ID()] if !ok { staticType := interpreter.ConvertSemaToStaticType(c.memoryGauge, targetType) bytes, err := interpreter.StaticTypeToBytes(staticType) @@ -1298,7 +1283,7 @@ func (c *Compiler) getOrAddType(targetType sema.Type) uint16 { panic(err) } index = c.addType(bytes) - c.typesInPool[targetType] = index + c.typesInPool[targetType.ID()] = index } return index } diff --git a/bbq/vm/test/vm_bench_test.go b/bbq/vm/test/vm_bench_test.go index 97adb3b1f4..0cdcdf6b66 100644 --- a/bbq/vm/test/vm_bench_test.go +++ b/bbq/vm/test/vm_bench_test.go @@ -169,10 +169,12 @@ func BenchmarkNewStructRaw(b *testing.B) { for i := 0; i < b.N; i++ { for j := 0; j < 1; j++ { structValue := vm.NewCompositeValue( - nil, - "Foo", common.CompositeKindStructure, - common.Address{}, + interpreter.NewCompositeStaticTypeComputeTypeID( + nil, + common.NewAddressLocation(nil, common.ZeroAddress, "Foo"), + "Foo", + ), storage.BasicSlabStorage, ) structValue.SetMember(vmConfig, "id", fieldValue) diff --git a/bbq/vm/test/vm_test.go b/bbq/vm/test/vm_test.go index 41601f527d..45adbee04b 100644 --- a/bbq/vm/test/vm_test.go +++ b/bbq/vm/test/vm_test.go @@ -269,8 +269,9 @@ func TestNewStruct(t *testing.T) { require.IsType(t, &vm.CompositeValue{}, result) structValue := result.(*vm.CompositeValue) + compositeType := structValue.CompositeType - require.Equal(t, "Foo", structValue.QualifiedIdentifier) + require.Equal(t, "Foo", compositeType.QualifiedIdentifier) require.Equal( t, vm.IntValue{SmallInt: 12}, diff --git a/bbq/vm/value_composite.go b/bbq/vm/value_composite.go index a7e4ae88f9..eb9ae3180d 100644 --- a/bbq/vm/value_composite.go +++ b/bbq/vm/value_composite.go @@ -29,12 +29,9 @@ import ( ) type CompositeValue struct { - dictionary *atree.OrderedMap - Location common.Location - QualifiedIdentifier string - typeID common.TypeID - staticType StaticType - Kind common.CompositeKind + dictionary *atree.OrderedMap + CompositeType *interpreter.CompositeStaticType + Kind common.CompositeKind } var _ Value = &CompositeValue{} @@ -42,21 +39,25 @@ var _ MemberAccessibleValue = &CompositeValue{} var _ ReferenceTrackedResourceKindedValue = &CompositeValue{} func NewCompositeValue( - location common.Location, - qualifiedIdentifier string, kind common.CompositeKind, - address common.Address, + staticType *interpreter.CompositeStaticType, storage atree.SlabStorage, ) *CompositeValue { + // Newly created values are always on stack. + // Need to 'Transfer' if needed to be stored in an account. + address := common.ZeroAddress + dictionary, err := atree.NewMap( storage, + atree.Address(address), + atree.NewDefaultDigesterBuilder(), interpreter.NewCompositeTypeInfo( nil, - location, - qualifiedIdentifier, + staticType.Location, + staticType.QualifiedIdentifier, kind, ), ) @@ -66,41 +67,28 @@ func NewCompositeValue( } return &CompositeValue{ - QualifiedIdentifier: qualifiedIdentifier, - Location: location, - dictionary: dictionary, - Kind: kind, + CompositeType: staticType, + dictionary: dictionary, + Kind: kind, } } func newCompositeValueFromOrderedMap( dict *atree.OrderedMap, - location common.Location, - qualifiedIdentifier string, + staticType *interpreter.CompositeStaticType, kind common.CompositeKind, ) *CompositeValue { return &CompositeValue{ - dictionary: dict, - Location: location, - QualifiedIdentifier: qualifiedIdentifier, - Kind: kind, + dictionary: dict, + CompositeType: staticType, + Kind: kind, } } func (*CompositeValue) isValue() {} -func (v *CompositeValue) StaticType(memoryGauge common.MemoryGauge) StaticType { - if v.staticType == nil { - // NOTE: Instead of using NewCompositeStaticType, which always generates the type ID, - // use the TypeID accessor, which may return an already computed type ID - v.staticType = interpreter.NewCompositeStaticType( - memoryGauge, - v.Location, - v.QualifiedIdentifier, - v.TypeID(), - ) - } - return v.staticType +func (v *CompositeValue) StaticType(common.MemoryGauge) StaticType { + return v.CompositeType } func (v *CompositeValue) GetMember(config *Config, name string) Value { @@ -164,17 +152,7 @@ func (v *CompositeValue) SlabID() atree.SlabID { } func (v *CompositeValue) TypeID() common.TypeID { - if v.typeID == "" { - location := v.Location - qualifiedIdentifier := v.QualifiedIdentifier - if location == nil { - return common.TypeID(qualifiedIdentifier) - } - - // TODO: TypeID metering - v.typeID = location.TypeID(nil, qualifiedIdentifier) - } - return v.typeID + return v.CompositeType.TypeID } func (v *CompositeValue) IsResourceKinded() bool { @@ -311,20 +289,11 @@ func (v *CompositeValue) Transfer( } if res == nil { - typeInfo := interpreter.NewCompositeTypeInfo( - config.MemoryGauge, - v.Location, - v.QualifiedIdentifier, + res = newCompositeValueFromOrderedMap( + dictionary, + v.CompositeType, v.Kind, ) - res = &CompositeValue{ - dictionary: dictionary, - Location: typeInfo.Location, - QualifiedIdentifier: typeInfo.QualifiedIdentifier, - Kind: typeInfo.Kind, - typeID: v.typeID, - staticType: v.staticType, - } //res.InjectedFields = v.InjectedFields //res.ComputedFields = v.ComputedFields diff --git a/bbq/vm/value_conversions.go b/bbq/vm/value_conversions.go index 9351533dca..69123b727a 100644 --- a/bbq/vm/value_conversions.go +++ b/bbq/vm/value_conversions.go @@ -40,8 +40,7 @@ func InterpreterValueToVMValue(storage interpreter.Storage, value interpreter.Va case *interpreter.CompositeValue: return newCompositeValueFromOrderedMap( value.AtreeMap(), - value.Location, - value.QualifiedIdentifier, + value.StaticType(nil).(*interpreter.CompositeStaticType), value.Kind, ) //case interpreter.LinkValue: @@ -112,11 +111,12 @@ func VMValueToInterpreterValue(config *Config, value Value) interpreter.Value { case StringValue: return interpreter.NewUnmeteredStringValue(string(value.Str)) case *CompositeValue: + compositeType := value.CompositeType return interpreter.NewCompositeValueFromAtreeMap( nil, interpreter.CompositeTypeInfo{ - Location: value.Location, - QualifiedIdentifier: value.QualifiedIdentifier, + Location: compositeType.Location, + QualifiedIdentifier: compositeType.QualifiedIdentifier, Kind: value.Kind, }, value.dictionary, diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index fbe9ff46bc..6666b6c8f8 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -429,9 +429,10 @@ func opInvokeDynamic(vm *VM) { } compositeValue := receiver.(*CompositeValue) + compositeType := compositeValue.CompositeType - qualifiedFuncName := commons.TypeQualifiedName(compositeValue.QualifiedIdentifier, funcName) - var functionValue = vm.lookupFunction(compositeValue.Location, qualifiedFuncName) + qualifiedFuncName := commons.TypeQualifiedName(compositeType.QualifiedIdentifier, funcName) + var functionValue = vm.lookupFunction(compositeType.Location, qualifiedFuncName) parameterCount := int(functionValue.Function.ParameterCount) arguments := vm.stack[stackHeight-parameterCount:] @@ -455,18 +456,14 @@ func opNew(vm *VM) { compositeKind := common.CompositeKind(kind) // decode location - locationLen := callframe.getUint16() - locationBytes := callframe.function.Code[callframe.ip : callframe.ip+locationLen] - callframe.ip = callframe.ip + locationLen - location := decodeLocation(locationBytes) + staticType := vm.loadType() - typeName := callframe.getString() + // TODO: Support inclusive-range type + compositeStaticType := staticType.(*interpreter.CompositeStaticType) value := NewCompositeValue( - location, - typeName, compositeKind, - common.Address{}, + compositeStaticType, vm.config.Storage, ) vm.push(value) From ad3620ec6e1cca73f9ba5349626a5c666a728941 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 6 Nov 2024 13:55:31 -0800 Subject: [PATCH 62/89] Rename 'CastType' to 'CastKind' --- bbq/bytecode_printer.go | 6 +++--- bbq/commons/constants.go | 6 +++--- bbq/compiler/compiler.go | 4 ++-- bbq/vm/vm.go | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bbq/bytecode_printer.go b/bbq/bytecode_printer.go index 6cc2a63a1e..f529665b31 100644 --- a/bbq/bytecode_printer.go +++ b/bbq/bytecode_printer.go @@ -82,10 +82,10 @@ func (p *BytecodePrinter) printCode(codes []byte) { case opcode.Cast: var typeIndex int - var castType byte + var castKind byte typeIndex, i = p.getIntOperand(codes, i) - castType, i = p.getByteOperand(codes, i) - p.stringBuilder.WriteString(" " + fmt.Sprint(typeIndex) + " " + fmt.Sprint(int8(castType))) + castKind, i = p.getByteOperand(codes, i) + p.stringBuilder.WriteString(" " + fmt.Sprint(typeIndex) + " " + fmt.Sprint(int8(castKind))) case opcode.Path: var identifier string diff --git a/bbq/commons/constants.go b/bbq/commons/constants.go index b5ad6766b2..1891cc54c2 100644 --- a/bbq/commons/constants.go +++ b/bbq/commons/constants.go @@ -38,15 +38,15 @@ const ( TransactionGeneratedParamPrefix = "$_param_" ) -type CastType byte +type CastKind byte const ( - SimpleCast CastType = iota + SimpleCast CastKind = iota FailableCast ForceCast ) -func CastTypeFrom(operation ast.Operation) CastType { +func CastKindFrom(operation ast.Operation) CastKind { switch operation { case ast.OperationCast: return SimpleCast diff --git a/bbq/compiler/compiler.go b/bbq/compiler/compiler.go index 98092dbe3d..ea35ffc268 100644 --- a/bbq/compiler/compiler.go +++ b/bbq/compiler/compiler.go @@ -984,9 +984,9 @@ func (c *Compiler) VisitCastingExpression(expression *ast.CastingExpression) (_ index := c.getOrAddType(castingTypes.TargetType) first, second := encodeUint16(index) - castType := commons.CastTypeFrom(expression.Operation) + castKind := commons.CastKindFrom(expression.Operation) - c.emit(opcode.Cast, first, second, byte(castType)) + c.emit(opcode.Cast, first, second, byte(castKind)) return } diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index 6666b6c8f8..bf6b3fa44c 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -536,10 +536,10 @@ func opCast(vm *VM) { callframe := vm.callFrame value := vm.pop() targetType := vm.loadType() - castType := commons.CastType(callframe.getByte()) + castKind := commons.CastKind(callframe.getByte()) // TODO: - _ = castType + _ = castKind _ = targetType vm.push(value) From 907fe6e7d0b8260caa61421630f719125861be66 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 7 Nov 2024 10:13:54 -0800 Subject: [PATCH 63/89] Refactor and fix lint --- bbq/compiler/compiler.go | 27 +++--- bbq/vm/callframe.go | 2 +- bbq/vm/native_functions.go | 3 +- bbq/vm/test/ft_test.go | 12 +-- bbq/vm/test/vm_bench_test.go | 18 ++-- bbq/vm/test/vm_test.go | 157 ++++++++++++++++++++++++++--------- bbq/vm/value_account.go | 4 +- bbq/vm/value_array.go | 10 +-- bbq/vm/value_capability.go | 2 + bbq/vm/value_conversions.go | 15 ++-- bbq/vm/value_dictionary.go | 8 +- bbq/vm/value_int.go | 4 +- bbq/vm/value_string.go | 16 +++- bbq/vm/vm.go | 24 ++---- 14 files changed, 185 insertions(+), 117 deletions(-) diff --git a/bbq/compiler/compiler.go b/bbq/compiler/compiler.go index ea35ffc268..9360f4d46d 100644 --- a/bbq/compiler/compiler.go +++ b/bbq/compiler/compiler.go @@ -411,11 +411,7 @@ func (c *Compiler) exportConstants() []*bbq.Constant { } func (c *Compiler) exportTypes() [][]byte { - types := make([][]byte, len(c.staticTypes)) - for index, typeBytes := range c.staticTypes { - types[index] = typeBytes - } - return types + return c.staticTypes } func (c *Compiler) exportImports() []*bbq.Import { @@ -645,8 +641,15 @@ func (c *Compiler) VisitSwapStatement(_ *ast.SwapStatement) (_ struct{}) { func (c *Compiler) VisitExpressionStatement(statement *ast.ExpressionStatement) (_ struct{}) { c.compileExpression(statement.Expression) - // Drop the expression evaluation result - c.emit(opcode.Drop) + + switch statement.Expression.(type) { + case *ast.DestroyExpression: + // Do nothing. Destroy operation will not produce any result. + default: + // Otherwise, drop the expression evaluation result. + c.emit(opcode.Drop) + } + return } @@ -748,11 +751,11 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio switch invokedExpr := expression.InvokedExpression.(type) { case *ast.IdentifierExpression: - typ := c.Elaboration.IdentifierInInvocationType(invokedExpr) - invocationType := typ.(*sema.FunctionType) - if invocationType.IsConstructor { - // TODO: - } + // TODO: Does constructors need any special handling? + //typ := c.Elaboration.IdentifierInInvocationType(invokedExpr) + //invocationType := typ.(*sema.FunctionType) + //if invocationType.IsConstructor { + //} // Load arguments c.loadArguments(expression) diff --git a/bbq/vm/callframe.go b/bbq/vm/callframe.go index 1ce06a6551..830a66c97e 100644 --- a/bbq/vm/callframe.go +++ b/bbq/vm/callframe.go @@ -52,6 +52,6 @@ func (f *callFrame) getBool() bool { func (f *callFrame) getString() string { strLen := f.getUint16() str := string(f.function.Code[f.ip : f.ip+strLen]) - f.ip = f.ip + strLen + f.ip += strLen return str } diff --git a/bbq/vm/native_functions.go b/bbq/vm/native_functions.go index 71d5bf9ee2..5871a61e0b 100644 --- a/bbq/vm/native_functions.go +++ b/bbq/vm/native_functions.go @@ -20,10 +20,11 @@ package vm import ( "fmt" + "github.com/onflow/cadence/bbq/commons" - "github.com/onflow/cadence/errors" "github.com/onflow/cadence/common" + "github.com/onflow/cadence/errors" "github.com/onflow/cadence/stdlib" ) diff --git a/bbq/vm/test/ft_test.go b/bbq/vm/test/ft_test.go index 9598bba5ef..920d925d09 100644 --- a/bbq/vm/test/ft_test.go +++ b/bbq/vm/test/ft_test.go @@ -140,7 +140,7 @@ func TestFTTransfer(t *testing.T) { mintTxArgs := []vm.Value{ vm.AddressValue(senderAddress), - vm.IntValue{total}, + vm.NewIntValue(total), } mintTxAuthorizer := vm.NewAuthAccountReferenceValue(vmConfig, contractsAddress) @@ -158,7 +158,7 @@ func TestFTTransfer(t *testing.T) { transferAmount := int64(1) tokenTransferTxArgs := []vm.Value{ - vm.IntValue{transferAmount}, + vm.NewIntValue(transferAmount), vm.AddressValue(receiverAddress), } @@ -183,9 +183,9 @@ func TestFTTransfer(t *testing.T) { require.Equal(t, 0, validationScriptVM.StackSize()) if address == senderAddress { - assert.Equal(t, vm.IntValue{total - transferAmount}, result) + assert.Equal(t, vm.NewIntValue(total-transferAmount), result) } else { - assert.Equal(t, vm.IntValue{transferAmount}, result) + assert.Equal(t, vm.NewIntValue(transferAmount), result) } } } @@ -292,7 +292,7 @@ func BenchmarkFTTransfer(b *testing.B) { mintTxArgs := []vm.Value{ vm.AddressValue(senderAddress), - vm.IntValue{total}, + vm.NewIntValue(total), } mintTxAuthorizer := vm.NewAuthAccountReferenceValue(vmConfig, contractsAddress) @@ -305,7 +305,7 @@ func BenchmarkFTTransfer(b *testing.B) { transferAmount := int64(1) tokenTransferTxArgs := []vm.Value{ - vm.IntValue{transferAmount}, + vm.NewIntValue(transferAmount), vm.AddressValue(receiverAddress), } diff --git a/bbq/vm/test/vm_bench_test.go b/bbq/vm/test/vm_bench_test.go index 0cdcdf6b66..7711d4121e 100644 --- a/bbq/vm/test/vm_bench_test.go +++ b/bbq/vm/test/vm_bench_test.go @@ -35,13 +35,13 @@ func BenchmarkRecursionFib(b *testing.B) { b.ReportAllocs() b.ResetTimer() - expected := vm.IntValue{SmallInt: 377} + expected := vm.NewIntValue(377) for i := 0; i < b.N; i++ { result, err := vmInstance.Invoke( "fib", - vm.IntValue{SmallInt: 14}, + vm.NewIntValue(14), ) require.NoError(b, err) require.Equal(b, expected, result) @@ -64,7 +64,7 @@ func BenchmarkImperativeFib(b *testing.B) { b.ReportAllocs() b.ResetTimer() - var value vm.Value = vm.IntValue{SmallInt: 14} + var value vm.Value = vm.NewIntValue(14) for i := 0; i < b.N; i++ { _, err := vmInstance.Invoke("fib", value) @@ -97,7 +97,7 @@ func BenchmarkNewStruct(b *testing.B) { `) require.NoError(b, err) - value := vm.IntValue{SmallInt: 1} + value := vm.NewIntValue(1) b.ReportAllocs() b.ResetTimer() @@ -140,7 +140,7 @@ func BenchmarkNewResource(b *testing.B) { b.ReportAllocs() b.ResetTimer() - value := vm.IntValue{SmallInt: 9} + value := vm.NewIntValue(9) scriptLocation := runtime_utils.NewScriptLocationGenerator() @@ -162,7 +162,7 @@ func BenchmarkNewStructRaw(b *testing.B) { Storage: storage, } - fieldValue := vm.IntValue{SmallInt: 7} + fieldValue := vm.NewIntValue(7) b.ReportAllocs() b.ResetTimer() @@ -239,7 +239,7 @@ func BenchmarkContractImport(b *testing.B) { b.ResetTimer() b.ReportAllocs() - value := vm.IntValue{SmallInt: 7} + value := vm.NewIntValue(7) for i := 0; i < b.N; i++ { checker, err := ParseAndCheckWithOptions(b, ` @@ -367,7 +367,7 @@ func BenchmarkMethodCall(b *testing.B) { vmInstance = vm.NewVM(scriptLocation(), program, vmConfig) - value := vm.IntValue{SmallInt: 10} + value := vm.NewIntValue(10) b.ResetTimer() b.ReportAllocs() @@ -461,7 +461,7 @@ func BenchmarkMethodCall(b *testing.B) { vmInstance = vm.NewVM(scriptLocation(), program, vmConfig) - value := vm.IntValue{SmallInt: 10} + value := vm.NewIntValue(10) b.ResetTimer() b.ReportAllocs() diff --git a/bbq/vm/test/vm_test.go b/bbq/vm/test/vm_test.go index 45adbee04b..ea168c4ba4 100644 --- a/bbq/vm/test/vm_test.go +++ b/bbq/vm/test/vm_test.go @@ -68,10 +68,10 @@ func TestRecursionFib(t *testing.T) { result, err := vmInstance.Invoke( "fib", - vm.IntValue{SmallInt: 7}, + vm.NewIntValue(7), ) require.NoError(t, err) - require.Equal(t, vm.IntValue{SmallInt: 13}, result) + require.Equal(t, vm.NewIntValue(13), result) require.Equal(t, 0, vmInstance.StackSize()) } @@ -106,10 +106,10 @@ func TestImperativeFib(t *testing.T) { result, err := vmInstance.Invoke( "fib", - vm.IntValue{SmallInt: 7}, + vm.NewIntValue(7), ) require.NoError(t, err) - require.Equal(t, vm.IntValue{SmallInt: 13}, result) + require.Equal(t, vm.NewIntValue(13), result) require.Equal(t, 0, vmInstance.StackSize()) } @@ -140,7 +140,7 @@ func TestBreak(t *testing.T) { result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Equal(t, vm.IntValue{SmallInt: 4}, result) + require.Equal(t, vm.NewIntValue(4), result) require.Equal(t, 0, vmInstance.StackSize()) } @@ -172,7 +172,7 @@ func TestContinue(t *testing.T) { result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Equal(t, vm.IntValue{SmallInt: 3}, result) + require.Equal(t, vm.NewIntValue(3), result) require.Equal(t, 0, vmInstance.StackSize()) } @@ -201,7 +201,7 @@ func TestNilCoalesce(t *testing.T) { result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Equal(t, vm.IntValue{SmallInt: 2}, result) + require.Equal(t, vm.NewIntValue(2), result) require.Equal(t, 0, vmInstance.StackSize()) }) @@ -226,7 +226,7 @@ func TestNilCoalesce(t *testing.T) { result, err := vmInstance.Invoke("test") require.NoError(t, err) - require.Equal(t, vm.IntValue{SmallInt: 3}, result) + require.Equal(t, vm.NewIntValue(3), result) require.Equal(t, 0, vmInstance.StackSize()) }) } @@ -263,7 +263,7 @@ func TestNewStruct(t *testing.T) { vmConfig := &vm.Config{} vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) - result, err := vmInstance.Invoke("test", vm.IntValue{SmallInt: 10}) + result, err := vmInstance.Invoke("test", vm.NewIntValue(10)) require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) @@ -274,7 +274,7 @@ func TestNewStruct(t *testing.T) { require.Equal(t, "Foo", compositeType.QualifiedIdentifier) require.Equal( t, - vm.IntValue{SmallInt: 12}, + vm.NewIntValue(12), structValue.GetMember(vmConfig, "id"), ) } @@ -313,7 +313,7 @@ func TestStructMethodCall(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, vm.StringValue{Str: []byte("Hello from Foo!")}, result) + require.Equal(t, vm.NewStringValue("Hello from Foo!"), result) } func TestImport(t *testing.T) { @@ -388,7 +388,7 @@ func TestImport(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, vm.StringValue{Str: []byte("global function of the imported program")}, result) + require.Equal(t, vm.NewStringValue("global function of the imported program"), result) } func TestContractImport(t *testing.T) { @@ -478,7 +478,7 @@ func TestContractImport(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, vm.StringValue{Str: []byte("global function of the imported program")}, result) + require.Equal(t, vm.NewStringValue("global function of the imported program"), result) }) t.Run("contract function", func(t *testing.T) { @@ -553,7 +553,7 @@ func TestContractImport(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, vm.StringValue{Str: []byte("contract function of the imported program")}, result) + require.Equal(t, vm.NewStringValue("contract function of the imported program"), result) }) t.Run("nested imports", func(t *testing.T) { @@ -726,7 +726,7 @@ func TestContractImport(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, vm.StringValue{Str: []byte("Hello from Foo!")}, result) + require.Equal(t, vm.NewStringValue("Hello from Foo!"), result) }) t.Run("contract interface", func(t *testing.T) { @@ -897,7 +897,7 @@ func TestContractImport(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, vm.StringValue{Str: []byte("Successfully withdrew")}, result) + require.Equal(t, vm.NewStringValue("Successfully withdrew"), result) }) } @@ -927,7 +927,7 @@ func TestInitializeContract(t *testing.T) { require.NoError(t, err) fieldValue := contractValue.GetMember(vmConfig, "status") - assert.Equal(t, vm.StringValue{Str: []byte("PENDING")}, fieldValue) + assert.Equal(t, vm.NewStringValue("PENDING"), fieldValue) } func TestContractAccessDuringInit(t *testing.T) { @@ -964,7 +964,7 @@ func TestContractAccessDuringInit(t *testing.T) { require.NoError(t, err) fieldValue := contractValue.GetMember(vmConfig, "status") - assert.Equal(t, vm.StringValue{Str: []byte("PENDING")}, fieldValue) + assert.Equal(t, vm.NewStringValue("PENDING"), fieldValue) }) t.Run("using self", func(t *testing.T) { @@ -997,7 +997,7 @@ func TestContractAccessDuringInit(t *testing.T) { require.NoError(t, err) fieldValue := contractValue.GetMember(vmConfig, "status") - assert.Equal(t, vm.StringValue{Str: []byte("PENDING")}, fieldValue) + assert.Equal(t, vm.NewStringValue("PENDING"), fieldValue) }) } @@ -1032,7 +1032,7 @@ func TestFunctionOrder(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, vm.IntValue{SmallInt: 5}, result) + require.Equal(t, vm.NewIntValue(5), result) }) t.Run("nested", func(t *testing.T) { @@ -1162,7 +1162,7 @@ func TestContractField(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, vm.StringValue{Str: []byte("PENDING")}, result) + require.Equal(t, vm.NewStringValue("PENDING"), result) }) t.Run("set", func(t *testing.T) { @@ -1233,10 +1233,10 @@ func TestContractField(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, vm.StringValue{Str: []byte("UPDATED")}, result) + require.Equal(t, vm.NewStringValue("UPDATED"), result) fieldValue := importedContractValue.GetMember(vmConfig, "status") - assert.Equal(t, vm.StringValue{Str: []byte("UPDATED")}, fieldValue) + assert.Equal(t, vm.NewStringValue("UPDATED"), fieldValue) }) } @@ -1310,7 +1310,7 @@ func TestNativeFunctions(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, vm.StringValue{Str: []byte("Hello, World!")}, result) + require.Equal(t, vm.NewStringValue("Hello, World!"), result) }) } @@ -1359,14 +1359,14 @@ func TestTransaction(t *testing.T) { require.NoError(t, err) // Once 'prepare' is called, 'a' is initialized to "Hello!" - assert.Equal(t, vm.StringValue{Str: []byte("Hello!")}, compositeValue.GetMember(vmConfig, "a")) + assert.Equal(t, vm.NewStringValue("Hello!"), compositeValue.GetMember(vmConfig, "a")) // Invoke 'execute' _, err = vmInstance.Invoke(commons.TransactionExecuteFunctionName, transaction) require.NoError(t, err) // Once 'execute' is called, 'a' is initialized to "Hello, again!" - assert.Equal(t, vm.StringValue{Str: []byte("Hello again!")}, compositeValue.GetMember(vmConfig, "a")) + assert.Equal(t, vm.NewStringValue("Hello again!"), compositeValue.GetMember(vmConfig, "a")) }) t.Run("with params", func(t *testing.T) { @@ -1391,8 +1391,8 @@ func TestTransaction(t *testing.T) { vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) args := []vm.Value{ - vm.StringValue{[]byte("Hello!")}, - vm.StringValue{[]byte("Hello again!")}, + vm.NewStringValue("Hello!"), + vm.NewStringValue("Hello again!"), } err = vmInstance.ExecuteTransaction(args) @@ -1415,14 +1415,14 @@ func TestTransaction(t *testing.T) { require.NoError(t, err) // Once 'prepare' is called, 'a' is initialized to "Hello!" - assert.Equal(t, vm.StringValue{Str: []byte("Hello!")}, compositeValue.GetMember(vmConfig, "a")) + assert.Equal(t, vm.NewStringValue("Hello!"), compositeValue.GetMember(vmConfig, "a")) // Invoke 'execute' _, err = vmInstance.Invoke(commons.TransactionExecuteFunctionName, transaction) require.NoError(t, err) // Once 'execute' is called, 'a' is initialized to "Hello, again!" - assert.Equal(t, vm.StringValue{Str: []byte("Hello again!")}, compositeValue.GetMember(vmConfig, "a")) + assert.Equal(t, vm.NewStringValue("Hello again!"), compositeValue.GetMember(vmConfig, "a")) }) } @@ -1519,7 +1519,7 @@ func TestInterfaceMethodCall(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, vm.StringValue{Str: []byte("Hello from Foo!")}, result) + require.Equal(t, vm.NewStringValue("Hello from Foo!"), result) } func TestArrayLiteral(t *testing.T) { @@ -1549,8 +1549,8 @@ func TestArrayLiteral(t *testing.T) { require.IsType(t, &vm.ArrayValue{}, result) array := result.(*vm.ArrayValue) assert.Equal(t, 2, array.Count()) - assert.Equal(t, vm.IntValue{SmallInt: 2}, array.Get(vmConfig, 0)) - assert.Equal(t, vm.IntValue{SmallInt: 5}, array.Get(vmConfig, 1)) + assert.Equal(t, vm.NewIntValue(2), array.Get(vmConfig, 0)) + assert.Equal(t, vm.NewIntValue(5), array.Get(vmConfig, 1)) }) t.Run("array get", func(t *testing.T) { @@ -1573,7 +1573,7 @@ func TestArrayLiteral(t *testing.T) { result, err := vmInstance.Invoke("test") require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) - assert.Equal(t, vm.IntValue{SmallInt: 5}, result) + assert.Equal(t, vm.NewIntValue(5), result) }) t.Run("array set", func(t *testing.T) { @@ -1601,9 +1601,9 @@ func TestArrayLiteral(t *testing.T) { require.IsType(t, &vm.ArrayValue{}, result) array := result.(*vm.ArrayValue) assert.Equal(t, 3, array.Count()) - assert.Equal(t, vm.IntValue{SmallInt: 2}, array.Get(vmConfig, 0)) - assert.Equal(t, vm.IntValue{SmallInt: 5}, array.Get(vmConfig, 1)) - assert.Equal(t, vm.IntValue{SmallInt: 8}, array.Get(vmConfig, 2)) + assert.Equal(t, vm.NewIntValue(2), array.Get(vmConfig, 0)) + assert.Equal(t, vm.NewIntValue(5), array.Get(vmConfig, 1)) + assert.Equal(t, vm.NewIntValue(8), array.Get(vmConfig, 2)) }) } @@ -1646,6 +1646,87 @@ func TestReference(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, vm.StringValue{Str: []byte("Hello from Foo!")}, result) + require.Equal(t, vm.NewStringValue("Hello from Foo!"), result) }) } + +func TestResource(t *testing.T) { + + t.Parallel() + + t.Run("new", func(t *testing.T) { + t.Parallel() + + checker, err := ParseAndCheck(t, ` + resource Foo { + var id : Int + + init(_ id: Int) { + self.id = id + } + } + + fun test(): @Foo { + var i = 0 + var r <- create Foo(5) + return <- r + } + `) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) + + result, err := vmInstance.Invoke("test") + require.NoError(t, err) + require.Equal(t, 0, vmInstance.StackSize()) + + require.IsType(t, &vm.CompositeValue{}, result) + structValue := result.(*vm.CompositeValue) + compositeType := structValue.CompositeType + + require.Equal(t, "Foo", compositeType.QualifiedIdentifier) + require.Equal( + t, + vm.NewIntValue(5), + structValue.GetMember(vmConfig, "id"), + ) + }) + + t.Run("destroy", func(t *testing.T) { + t.Parallel() + + checker, err := ParseAndCheck(t, ` + resource Foo { + var id : Int + + init(_ id: Int) { + self.id = id + } + } + + fun test() { + var i = 0 + var r <- create Foo(5) + destroy r + } + `) + require.NoError(t, err) + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + program := comp.Compile() + + printProgram("", program) + + vmConfig := &vm.Config{} + vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) + + _, err = vmInstance.Invoke("test") + require.NoError(t, err) + require.Equal(t, 0, vmInstance.StackSize()) + }) + +} diff --git a/bbq/vm/value_account.go b/bbq/vm/value_account.go index c637c10f45..7ab3cd8136 100644 --- a/bbq/vm/value_account.go +++ b/bbq/vm/value_account.go @@ -298,9 +298,7 @@ func IssueStorageCapabilityController( panic(errors.NewUnexpectedError("invalid zero account ID")) } - capabilityIDValue := IntValue{ - SmallInt: int64(capabilityID), - } + capabilityIDValue := NewIntValue(int64(capabilityID)) controller := NewStorageCapabilityControllerValue( borrowType, diff --git a/bbq/vm/value_array.go b/bbq/vm/value_array.go index 708ea33171..b2e777e0df 100644 --- a/bbq/vm/value_array.go +++ b/bbq/vm/value_array.go @@ -185,15 +185,7 @@ func (v *ArrayValue) Transfer(config *Config, address atree.Address, remove bool element := interpreter.MustConvertStoredValue(config.MemoryGauge, value) - // TODO: converted value is unused - vmElement := InterpreterValueToVMValue(config.Storage, element) - vmElement = vmElement.Transfer( - config, - address, - remove, - nil, - ) - + // TODO: Transfer before returning. return element, nil }, ) diff --git a/bbq/vm/value_capability.go b/bbq/vm/value_capability.go index 0931fe4eb1..485a9f6725 100644 --- a/bbq/vm/value_capability.go +++ b/bbq/vm/value_capability.go @@ -177,6 +177,8 @@ func getCheckedCapabilityController( if wantedBorrowType == nil { wantedBorrowType = capabilityBorrowType } else { + //wantedBorrowType = inter.SubstituteMappedEntitlements(wantedBorrowType).(*sema.ReferenceType) + if !canBorrow(wantedBorrowType, capabilityBorrowType) { return nil, nil } diff --git a/bbq/vm/value_conversions.go b/bbq/vm/value_conversions.go index 69123b727a..adee75bc57 100644 --- a/bbq/vm/value_conversions.go +++ b/bbq/vm/value_conversions.go @@ -34,9 +34,9 @@ func InterpreterValueToVMValue(storage interpreter.Storage, value interpreter.Va case interpreter.NilValue: return Nil case interpreter.IntValue: - return IntValue{value.BigInt.Int64()} + return NewIntValue(value.BigInt.Int64()) case *interpreter.StringValue: - return StringValue{Str: []byte(value.Str)} + return NewStringValue(value.Str) case *interpreter.CompositeValue: return newCompositeValueFromOrderedMap( value.AtreeMap(), @@ -57,13 +57,9 @@ func InterpreterValueToVMValue(storage interpreter.Storage, value interpreter.Va return AddressValue(value) case *interpreter.SimpleCompositeValue: fields := make(map[string]Value) - var fieldNames []string - - for name, field := range value.Fields { + for name, field := range value.Fields { //nolint:maprange fields[name] = InterpreterValueToVMValue(storage, field) - fieldNames = append(fieldNames, name) } - return NewSimpleCompositeValue( common.CompositeKindStructure, value.TypeID, @@ -153,7 +149,8 @@ func VMValueToInterpreterValue(config *Config, value Value) interpreter.Value { fields := make(map[string]interpreter.Value) var fieldNames []string - for name, field := range value.fields { + // TODO: Fields names order matters. However, this is temporary. So ignore for now. + for name, field := range value.fields { //nolint:maprange fields[name] = VMValueToInterpreterValue(config, field) fieldNames = append(fieldNames, name) } @@ -161,7 +158,7 @@ func VMValueToInterpreterValue(config *Config, value Value) interpreter.Value { return interpreter.NewSimpleCompositeValue( nil, value.typeID, - nil, + value.staticType, fieldNames, fields, nil, diff --git a/bbq/vm/value_dictionary.go b/bbq/vm/value_dictionary.go index c03308c3bd..86f9739e01 100644 --- a/bbq/vm/value_dictionary.go +++ b/bbq/vm/value_dictionary.go @@ -252,15 +252,9 @@ func (v *DictionaryValue) Transfer( } key := interpreter.MustConvertStoredValue(config.MemoryGauge, atreeValue) - // TODO: converted value is unused - vmKey := InterpreterValueToVMValue(config.Storage, key) - vmKey = vmKey.Transfer(config, address, remove, nil) - value := interpreter.MustConvertStoredValue(config.MemoryGauge, atreeValue) - // TODO: converted value is unused - vmValue := InterpreterValueToVMValue(config.Storage, value) - vmValue = vmValue.Transfer(config, address, remove, nil) + // TODO: Transfer both key and value before returning. return key, value, nil }, ) diff --git a/bbq/vm/value_int.go b/bbq/vm/value_int.go index 2dc9974dc7..20417911b2 100644 --- a/bbq/vm/value_int.go +++ b/bbq/vm/value_int.go @@ -54,11 +54,11 @@ func (v IntValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { } func (v IntValue) Add(other IntValue) Value { - return IntValue{v.SmallInt + other.SmallInt} + return NewIntValue(v.SmallInt + other.SmallInt) } func (v IntValue) Subtract(other IntValue) Value { - return IntValue{v.SmallInt - other.SmallInt} + return NewIntValue(v.SmallInt - other.SmallInt) } func (v IntValue) Less(other IntValue) Value { diff --git a/bbq/vm/value_string.go b/bbq/vm/value_string.go index 34617cbf4b..e5211915b0 100644 --- a/bbq/vm/value_string.go +++ b/bbq/vm/value_string.go @@ -34,6 +34,18 @@ type StringValue struct { var _ Value = StringValue{} +func NewStringValue(str string) StringValue { + return StringValue{ + Str: []byte(str), + } +} + +func NewStringValueFromBytes(bytes []byte) StringValue { + return StringValue{ + Str: bytes, + } +} + func (StringValue) isValue() {} func (StringValue) StaticType(common.MemoryGauge) StaticType { @@ -65,9 +77,7 @@ func init() { var sb strings.Builder sb.Write(first.Str) sb.Write(second.Str) - return StringValue{ - []byte(sb.String()), - } + return NewStringValue(sb.String()) }, }) } diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index bf6b3fa44c..ae1da01600 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -140,9 +140,8 @@ func (vm *VM) replaceTop(value Value) { func (vm *VM) pushCallFrame(functionValue FunctionValue, arguments []Value) { locals := make([]Value, functionValue.Function.LocalCount) - for i, argument := range arguments { - locals[i] = argument - } + + copy(locals, arguments) callFrame := &callFrame{ parent: vm.callFrame, @@ -415,6 +414,8 @@ func opInvokeDynamic(vm *VM) { typeArg := vm.loadType() typeArguments = append(typeArguments, typeArg) } + // TODO: Just to make the linter happy + _ = typeArguments switch typedReceiver := receiver.(type) { case *StorageReferenceValue: @@ -517,7 +518,7 @@ func opTransfer(vm *VM) { } func opDestroy(vm *VM) { - value := vm.peek().(*CompositeValue) + value := vm.pop().(*CompositeValue) value.Destroy(vm.config) } @@ -697,9 +698,9 @@ func (vm *VM) initializeConstant(index uint16) (value Value) { case constantkind.Int: // TODO: smallInt, _, _ := leb128.ReadInt64(constant.Data) - value = IntValue{SmallInt: smallInt} + value = NewIntValue(smallInt) case constantkind.String: - value = StringValue{Str: constant.Data} + value = NewStringValueFromBytes(constant.Data) default: // TODO: panic(errors.NewUnexpectedError("unsupported constant kind '%s'", constant.Kind.String())) @@ -781,17 +782,6 @@ func (vm *VM) lookupFunction(location common.Location, name string) FunctionValu return indexedGlobals[name].(FunctionValue) } -func decodeLocation(locationBytes []byte) common.Location { - // TODO: is it possible to re-use decoders? - dec := interpreter.CBORDecMode.NewByteStreamDecoder(locationBytes) - locationDecoder := interpreter.NewLocationDecoder(dec, nil) - location, err := locationDecoder.DecodeLocation() - if err != nil { - panic(err) - } - return location -} - func (vm *VM) StackSize() int { return len(vm.stack) } From b8e44e7aa395237eebb8dc66450924ae99730772 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 8 Nov 2024 13:40:49 -0800 Subject: [PATCH 64/89] Recursively link dynamically imported programs --- bbq/vm/executable_program.go | 2 + bbq/vm/linker.go | 4 +- bbq/vm/test/vm_test.go | 397 ++++++++++++++++++++++++++++++----- bbq/vm/vm.go | 42 ++-- 4 files changed, 370 insertions(+), 75 deletions(-) diff --git a/bbq/vm/executable_program.go b/bbq/vm/executable_program.go index 6e8ad217e7..5e895de879 100644 --- a/bbq/vm/executable_program.go +++ b/bbq/vm/executable_program.go @@ -50,6 +50,8 @@ func NewExecutableProgram( } } +// NewLoadedExecutableProgram returns an ExecutableProgram with types decoded. +// Note that the returned program **doesn't** have the globals linked. func NewLoadedExecutableProgram(location common.Location, program *bbq.Program) *ExecutableProgram { executable := NewExecutableProgram(location, program, nil) diff --git a/bbq/vm/linker.go b/bbq/vm/linker.go index 3212c643db..98d97a2312 100644 --- a/bbq/vm/linker.go +++ b/bbq/vm/linker.go @@ -35,7 +35,7 @@ type LinkedGlobals struct { } // LinkGlobals performs the linking of global functions and variables for a given program. -func (vm *VM) LinkGlobals( +func LinkGlobals( location common.Location, program *bbq.Program, conf *Config, @@ -52,7 +52,7 @@ func (vm *VM) LinkGlobals( importedProgram := conf.ImportHandler(importLocation) // Link and get all globals at the import location. - linkedGlobals = vm.LinkGlobals( + linkedGlobals = LinkGlobals( importLocation, importedProgram, conf, diff --git a/bbq/vm/test/vm_test.go b/bbq/vm/test/vm_test.go index ea168c4ba4..868ff51a4a 100644 --- a/bbq/vm/test/vm_test.go +++ b/bbq/vm/test/vm_test.go @@ -1430,14 +1430,18 @@ func TestInterfaceMethodCall(t *testing.T) { t.Parallel() - location := common.NewAddressLocation( - nil, - common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, - "MyContract", - ) + t.Run("impl in same program", func(t *testing.T) { - importedChecker, err := ParseAndCheckWithOptions(t, - ` + t.Parallel() + + location := common.NewAddressLocation( + nil, + common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + "MyContract", + ) + + importedChecker, err := ParseAndCheckWithOptions(t, + ` contract MyContract { struct Foo: Greetings { var id : String @@ -1459,20 +1463,20 @@ func TestInterfaceMethodCall(t *testing.T) { } } `, - ParseAndCheckOptions{ - Location: location, - }, - ) - require.NoError(t, err) + ParseAndCheckOptions{ + Location: location, + }, + ) + require.NoError(t, err) - importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) - importedProgram := importCompiler.Compile() + importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importedProgram := importCompiler.Compile() - vmInstance := vm.NewVM(location, importedProgram, nil) - importedContractValue, err := vmInstance.InitializeContract() - require.NoError(t, err) + vmInstance := vm.NewVM(location, importedProgram, nil) + importedContractValue, err := vmInstance.InitializeContract() + require.NoError(t, err) - checker, err := ParseAndCheckWithOptions(t, ` + checker, err := ParseAndCheckWithOptions(t, ` import MyContract from 0x01 fun test(): String { @@ -1484,42 +1488,341 @@ func TestInterfaceMethodCall(t *testing.T) { return r.sayHello(1) }`, - ParseAndCheckOptions{ - Config: &sema.Config{ - ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { - return sema.ElaborationImport{ - Elaboration: importedChecker.Elaboration, - }, nil + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(*sema.Checker, common.Location, ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + LocationHandler: singleIdentifierLocationResolver(t), }, - LocationHandler: singleIdentifierLocationResolver(t), }, - }, - ) - require.NoError(t, err) + ) + require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - comp.Config.LocationHandler = singleIdentifierLocationResolver(t) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { - return importedProgram - } + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.LocationHandler = singleIdentifierLocationResolver(t) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + return importedProgram + } - program := comp.Compile() + program := comp.Compile() - vmConfig := &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { - return importedProgram - }, - ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { - return importedContractValue - }, - } + vmConfig := &vm.Config{ + ImportHandler: func(location common.Location) *bbq.Program { + return importedProgram + }, + ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { + return importedContractValue + }, + } - vmInstance = vm.NewVM(scriptLocation(), program, vmConfig) - result, err := vmInstance.Invoke("test") - require.NoError(t, err) - require.Equal(t, 0, vmInstance.StackSize()) + vmInstance = vm.NewVM(scriptLocation(), program, vmConfig) + result, err := vmInstance.Invoke("test") + require.NoError(t, err) + require.Equal(t, 0, vmInstance.StackSize()) - require.Equal(t, vm.NewStringValue("Hello from Foo!"), result) + require.Equal(t, vm.NewStringValue("Hello from Foo!"), result) + }) + + t.Run("impl in different program", func(t *testing.T) { + + t.Parallel() + + // Define the interface in `Foo` + + fooLocation := common.NewAddressLocation( + nil, + common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + "Foo", + ) + + fooChecker, err := ParseAndCheckWithOptions(t, + ` + contract Foo { + struct interface Greetings { + fun sayHello(): String + } + }`, + ParseAndCheckOptions{ + Location: fooLocation, + }, + ) + require.NoError(t, err) + + interfaceCompiler := compiler.NewCompiler(fooChecker.Program, fooChecker.Elaboration) + fooProgram := interfaceCompiler.Compile() + + interfaceVM := vm.NewVM(fooLocation, fooProgram, nil) + fooContractValue, err := interfaceVM.InitializeContract() + require.NoError(t, err) + + // Deploy the imported `Bar` program + + barLocation := common.NewAddressLocation( + nil, + common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2}, + "Bar", + ) + + barChecker, err := ParseAndCheckWithOptions(t, + ` + contract Bar { + fun sayHello(): String { + return "Hello from Bar!" + } + }`, + ParseAndCheckOptions{ + Config: &sema.Config{ + LocationHandler: singleIdentifierLocationResolver(t), + }, + Location: barLocation, + }, + ) + require.NoError(t, err) + + barCompiler := compiler.NewCompiler(barChecker.Program, barChecker.Elaboration) + barProgram := barCompiler.Compile() + + barVM := vm.NewVM(barLocation, barProgram, nil) + barContractValue, err := barVM.InitializeContract() + require.NoError(t, err) + + // Define the implementation + + bazLocation := common.NewAddressLocation( + nil, + common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3}, + "Baz", + ) + + bazChecker, err := ParseAndCheckWithOptions(t, + ` + import Foo from 0x01 + import Bar from 0x02 + + contract Baz { + struct GreetingImpl: Foo.Greetings { + fun sayHello(): String { + return Bar.sayHello() + } + } + }`, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { + var elaboration *sema.Elaboration + switch location { + case fooLocation: + elaboration = fooChecker.Elaboration + case barLocation: + elaboration = barChecker.Elaboration + default: + return nil, fmt.Errorf("cannot find import for: %s", location) + } + + return sema.ElaborationImport{ + Elaboration: elaboration, + }, nil + }, + LocationHandler: singleIdentifierLocationResolver(t), + }, + Location: bazLocation, + }, + ) + require.NoError(t, err) + + bazImportHandler := func(location common.Location) *bbq.Program { + switch location { + case fooLocation: + return fooProgram + case barLocation: + return barProgram + default: + panic(fmt.Errorf("cannot find import for: %s", location)) + } + } + + bazCompiler := compiler.NewCompiler(bazChecker.Program, bazChecker.Elaboration) + bazCompiler.Config.LocationHandler = singleIdentifierLocationResolver(t) + bazCompiler.Config.ImportHandler = bazImportHandler + bazProgram := bazCompiler.Compile() + + implProgramVMConfig := &vm.Config{ + ImportHandler: bazImportHandler, + ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { + switch location { + case fooLocation: + return fooContractValue + case barLocation: + return barContractValue + default: + panic(fmt.Errorf("cannot find contract: %s", location)) + } + }, + } + + bazVM := vm.NewVM(bazLocation, bazProgram, implProgramVMConfig) + bazContractValue, err := bazVM.InitializeContract() + require.NoError(t, err) + + // Get `Bar.GreetingsImpl` value + + checker, err := ParseAndCheckWithOptions(t, ` + import Baz from 0x03 + + fun test(): Baz.GreetingImpl { + return Baz.GreetingImpl() + }`, + + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { + var elaboration *sema.Elaboration + switch location { + case bazLocation: + elaboration = bazChecker.Elaboration + default: + return nil, fmt.Errorf("cannot find import for: %s", location) + } + + return sema.ElaborationImport{ + Elaboration: elaboration, + }, nil + }, + LocationHandler: singleIdentifierLocationResolver(t), + }, + }, + ) + require.NoError(t, err) + + scriptImportHandler := func(location common.Location) *bbq.Program { + switch location { + case barLocation: + return barProgram + case bazLocation: + return bazProgram + default: + panic(fmt.Errorf("cannot find import for: %s", location)) + } + } + + comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.LocationHandler = singleIdentifierLocationResolver(t) + comp.Config.ImportHandler = scriptImportHandler + + program := comp.Compile() + + vmConfig := &vm.Config{ + ImportHandler: scriptImportHandler, + ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { + switch location { + case barLocation: + return barContractValue + case bazLocation: + return bazContractValue + default: + panic(fmt.Errorf("cannot find contract: %s", location)) + } + }, + } + + scriptVM := vm.NewVM(scriptLocation(), program, vmConfig) + implValue, err := scriptVM.Invoke("test") + require.NoError(t, err) + require.Equal(t, 0, scriptVM.StackSize()) + + require.IsType(t, &vm.CompositeValue{}, implValue) + compositeValue := implValue.(*vm.CompositeValue) + require.Equal( + t, + common.TypeID("A.0000000000000003.Baz.GreetingImpl"), + compositeValue.TypeID(), + ) + + // Test Script. This program only imports `Foo` statically. + // But the argument passed into the script is of type `Baz.GreetingImpl`. + // So the linking of `Baz` happens dynamically at runtime. + // However, `Baz` also has an import to `Bar`. So when the + // `Baz` is linked and imported at runtime, its imports also + // should get linked at runtime (similar to how static linking works). + + checker, err = ParseAndCheckWithOptions(t, ` + import Foo from 0x01 + + fun test(v: {Foo.Greetings}): String { + return v.sayHello() + }`, + + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(_ *sema.Checker, location common.Location, _ ast.Range) (sema.Import, error) { + var elaboration *sema.Elaboration + switch location { + case fooLocation: + elaboration = fooChecker.Elaboration + case bazLocation: + elaboration = bazChecker.Elaboration + default: + return nil, fmt.Errorf("cannot find import for: %s", location) + } + + return sema.ElaborationImport{ + Elaboration: elaboration, + }, nil + }, + LocationHandler: singleIdentifierLocationResolver(t), + }, + }, + ) + require.NoError(t, err) + + scriptImportHandler = func(location common.Location) *bbq.Program { + switch location { + case fooLocation: + return fooProgram + case barLocation: + return barProgram + case bazLocation: + return bazProgram + default: + panic(fmt.Errorf("cannot find import for: %s", location)) + } + + return fooProgram + } + + comp = compiler.NewCompiler(checker.Program, checker.Elaboration) + comp.Config.LocationHandler = singleIdentifierLocationResolver(t) + comp.Config.ImportHandler = scriptImportHandler + + program = comp.Compile() + + vmConfig = &vm.Config{ + ImportHandler: scriptImportHandler, + ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { + switch location { + case fooLocation: + return fooContractValue + case barLocation: + return barContractValue + case bazLocation: + return bazContractValue + default: + panic(fmt.Errorf("cannot find contract: %s", location)) + } + }, + } + + scriptVM = vm.NewVM(scriptLocation(), program, vmConfig) + + result, err := scriptVM.Invoke("test", implValue) + require.NoError(t, err) + require.Equal(t, 0, scriptVM.StackSize()) + + require.Equal(t, vm.NewStringValue("Hello from Bar!"), result) + }) } func TestArrayLiteral(t *testing.T) { diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index ae1da01600..4c8bdd413b 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -68,7 +68,7 @@ func NewVM( } // Link global variables and functions. - linkedGlobals := vm.LinkGlobals( + linkedGlobals := LinkGlobals( location, program, conf, @@ -749,37 +749,27 @@ func (vm *VM) lookupFunction(location common.Location, name string) FunctionValu // If not found, check in already linked imported functions. linkedGlobals, ok := vm.linkedGlobalsCache[location] - if ok { - value, ok := linkedGlobals.indexedGlobals[name] - if ok { - return value.(FunctionValue) - } - } // If not found, link the function now, dynamically. - - // TODO: This currently link all functions in program, unnecessarily. - // Link only the requested function. - program := vm.config.ImportHandler(location) - - // TODO: Instead of creating the executable here, maybe cache it, - // and the `ImportHandler` could return the executable itself. - executable := NewLoadedExecutableProgram(location, program) - - indexedGlobals := make(map[string]Value, len(program.Functions)) - for _, function := range program.Functions { - indexedGlobals[function.Name] = FunctionValue{ - Function: function, - Executable: executable, - } + if !ok { + // TODO: This currently link all functions in program, unnecessarily. + // Link only the requested function. + program := vm.config.ImportHandler(location) + + linkedGlobals = LinkGlobals( + location, + program, + vm.config, + vm.linkedGlobalsCache, + ) } - vm.linkedGlobalsCache[location] = LinkedGlobals{ - executable: executable, - indexedGlobals: indexedGlobals, + value, ok = linkedGlobals.indexedGlobals[name] + if !ok { + panic(errors.NewUnexpectedError("cannot link global: %s", name)) } - return indexedGlobals[name].(FunctionValue) + return value.(FunctionValue) } func (vm *VM) StackSize() int { From 50606ffac9fb00c91f2ac76f64b2638962cb1a85 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 8 Nov 2024 13:57:11 -0800 Subject: [PATCH 65/89] Fix concurrent map access --- bbq/compiler/compiler.go | 2 +- bbq/compiler/native_functions.go | 10 ++++++++-- bbq/vm/native_functions.go | 12 ++++++++++-- bbq/vm/vm.go | 7 ++++--- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/bbq/compiler/compiler.go b/bbq/compiler/compiler.go index 9360f4d46d..b5ce159524 100644 --- a/bbq/compiler/compiler.go +++ b/bbq/compiler/compiler.go @@ -103,7 +103,7 @@ func NewCompiler( Elaboration: elaboration, Config: &Config{}, globals: make(map[string]*global), - importedGlobals: indexedNativeFunctions, + importedGlobals: NativeFunctions(), typesInPool: make(map[sema.TypeID]uint16), constantsInPool: make(map[constantsCacheKey]*constant), compositeTypeStack: &Stack[*sema.CompositeType]{ diff --git a/bbq/compiler/native_functions.go b/bbq/compiler/native_functions.go index 2aa6f1624a..80e31d7ea1 100644 --- a/bbq/compiler/native_functions.go +++ b/bbq/compiler/native_functions.go @@ -24,9 +24,16 @@ import ( "github.com/onflow/cadence/bbq/commons" ) -var indexedNativeFunctions = make(map[string]*global) var nativeFunctions []*global +func NativeFunctions() map[string]*global { + funcs := make(map[string]*global, len(nativeFunctions)) + for _, value := range nativeFunctions { + funcs[value.name] = value + } + return funcs +} + var builtinTypes = []sema.Type{ sema.StringType, sema.AccountType, @@ -72,5 +79,4 @@ func addNativeFunction(name string) { name: name, } nativeFunctions = append(nativeFunctions, global) - indexedNativeFunctions[name] = global } diff --git a/bbq/vm/native_functions.go b/bbq/vm/native_functions.go index 5871a61e0b..7a85a3ee8d 100644 --- a/bbq/vm/native_functions.go +++ b/bbq/vm/native_functions.go @@ -28,15 +28,23 @@ import ( "github.com/onflow/cadence/stdlib" ) -var NativeFunctions = map[string]Value{} +var nativeFunctions = map[string]Value{} // BuiltInLocation is the location of built-in constructs. // It's always nil. var BuiltInLocation common.Location = nil +func NativeFunctions() map[string]Value { + funcs := make(map[string]Value, len(nativeFunctions)) + for name, value := range nativeFunctions { + funcs[name] = value + } + return funcs +} + func RegisterFunction(functionName string, functionValue NativeFunctionValue) { functionValue.Name = functionName - NativeFunctions[functionName] = functionValue + nativeFunctions[functionName] = functionValue } func RegisterTypeBoundFunction(typeName, functionName string, functionValue NativeFunctionValue) { diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index 4c8bdd413b..930d92913a 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -56,9 +56,10 @@ func NewVM( // linkedGlobalsCache is a local cache-alike that is being used to hold already linked imports. linkedGlobalsCache := map[common.Location]LinkedGlobals{ BuiltInLocation: { - // It is safe to re-use native functions map here because, - // once put into the cache, it will only be used for read-only operations. - indexedGlobals: NativeFunctions, + // It is NOT safe to re-use native functions map here because, + // once put into the cache, it will be updated by adding the + // globals of the current program. + indexedGlobals: NativeFunctions(), }, } From e04c0be594b28ebe772013bd7d9978c92032dd84 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 14 Nov 2024 15:20:17 -0800 Subject: [PATCH 66/89] Add runtime sub-type checking --- bbq/vm/test/ft_test.go | 58 +++++++++++++++------------ bbq/vm/test/vm_test.go | 16 ++++++-- bbq/vm/types.go | 17 ++------ bbq/vm/value.go | 4 +- bbq/vm/value_account.go | 2 +- bbq/vm/value_account_storage.go | 4 +- bbq/vm/value_address.go | 2 +- bbq/vm/value_array.go | 2 +- bbq/vm/value_bool.go | 3 +- bbq/vm/value_capability.go | 23 +++++++---- bbq/vm/value_capability_controller.go | 2 +- bbq/vm/value_composite.go | 2 +- bbq/vm/value_dictionary.go | 2 +- bbq/vm/value_ephemeral_reference.go | 11 +++-- bbq/vm/value_function.go | 5 +-- bbq/vm/value_int.go | 3 +- bbq/vm/value_nil.go | 5 +-- bbq/vm/value_path.go | 2 +- bbq/vm/value_simple_composite.go | 2 +- bbq/vm/value_some.go | 7 ++-- bbq/vm/value_storage_reference.go | 31 ++++++++------ bbq/vm/value_string.go | 3 +- bbq/vm/value_void.go | 3 +- bbq/vm/vm.go | 18 +++++---- 24 files changed, 121 insertions(+), 106 deletions(-) diff --git a/bbq/vm/test/ft_test.go b/bbq/vm/test/ft_test.go index 920d925d09..a51333f34d 100644 --- a/bbq/vm/test/ft_test.go +++ b/bbq/vm/test/ft_test.go @@ -40,6 +40,20 @@ func TestFTTransfer(t *testing.T) { storage := interpreter.NewInMemoryStorage(nil) programs := map[common.Location]compiledProgram{} + typeLoader := func(location common.Location, typeID interpreter.TypeID) sema.CompositeKindedType { + program, ok := programs[location] + if !ok { + panic(fmt.Errorf("cannot find elaboration for: %s", location)) + } + elaboration := program.Elaboration + compositeType := elaboration.CompositeType(typeID) + if compositeType != nil { + return compositeType + } + + return elaboration.InterfaceType(typeID) + } + contractsAddress := common.MustBytesToAddress([]byte{0x1}) senderAddress := common.MustBytesToAddress([]byte{0x2}) receiverAddress := common.MustBytesToAddress([]byte{0x3}) @@ -62,6 +76,7 @@ func TestFTTransfer(t *testing.T) { config := &vm.Config{ Storage: storage, AccountHandler: &testAccountHandler{}, + TypeLoader: typeLoader, } flowTokenVM := vm.NewVM( @@ -100,19 +115,7 @@ func TestFTTransfer(t *testing.T) { AccountHandler: &testAccountHandler{}, - TypeLoader: func(location common.Location, typeID interpreter.TypeID) sema.CompositeKindedType { - imported, ok := programs[location] - if !ok { - panic(fmt.Errorf("cannot find contract in location %s", location)) - } - - compositeType := imported.Elaboration.CompositeType(typeID) - if compositeType != nil { - return compositeType - } - - return imported.Elaboration.InterfaceType(typeID) - }, + TypeLoader: typeLoader, } for _, address := range []common.Address{ @@ -197,6 +200,20 @@ func BenchmarkFTTransfer(b *testing.B) { storage := interpreter.NewInMemoryStorage(nil) programs := map[common.Location]compiledProgram{} + typeLoader := func(location common.Location, typeID interpreter.TypeID) sema.CompositeKindedType { + program, ok := programs[location] + if !ok { + panic(fmt.Errorf("cannot find elaboration for: %s", location)) + } + elaboration := program.Elaboration + compositeType := elaboration.CompositeType(typeID) + if compositeType != nil { + return compositeType + } + + return elaboration.InterfaceType(typeID) + } + contractsAddress := common.MustBytesToAddress([]byte{0x1}) senderAddress := common.MustBytesToAddress([]byte{0x2}) receiverAddress := common.MustBytesToAddress([]byte{0x3}) @@ -214,6 +231,7 @@ func BenchmarkFTTransfer(b *testing.B) { config := &vm.Config{ Storage: storage, AccountHandler: &testAccountHandler{}, + TypeLoader: typeLoader, } flowTokenVM := vm.NewVM( @@ -253,19 +271,7 @@ func BenchmarkFTTransfer(b *testing.B) { AccountHandler: &testAccountHandler{}, - TypeLoader: func(location common.Location, typeID interpreter.TypeID) sema.CompositeKindedType { - imported, ok := programs[location] - if !ok { - panic(fmt.Errorf("cannot find contract in location %s", location)) - } - - compositeType := imported.Elaboration.CompositeType(typeID) - if compositeType != nil { - return compositeType - } - - return imported.Elaboration.InterfaceType(typeID) - }, + TypeLoader: typeLoader, } for _, address := range []common.Address{ diff --git a/bbq/vm/test/vm_test.go b/bbq/vm/test/vm_test.go index 868ff51a4a..cbd303244a 100644 --- a/bbq/vm/test/vm_test.go +++ b/bbq/vm/test/vm_test.go @@ -20,6 +20,7 @@ package test import ( "fmt" + "github.com/onflow/cadence/interpreter" "testing" "github.com/stretchr/testify/assert" @@ -1434,7 +1435,7 @@ func TestInterfaceMethodCall(t *testing.T) { t.Parallel() - location := common.NewAddressLocation( + contractLocation := common.NewAddressLocation( nil, common.Address{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, "MyContract", @@ -1464,7 +1465,7 @@ func TestInterfaceMethodCall(t *testing.T) { } `, ParseAndCheckOptions{ - Location: location, + Location: contractLocation, }, ) require.NoError(t, err) @@ -1472,7 +1473,7 @@ func TestInterfaceMethodCall(t *testing.T) { importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() - vmInstance := vm.NewVM(location, importedProgram, nil) + vmInstance := vm.NewVM(contractLocation, importedProgram, nil) importedContractValue, err := vmInstance.InitializeContract() require.NoError(t, err) @@ -1516,6 +1517,15 @@ func TestInterfaceMethodCall(t *testing.T) { ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { return importedContractValue }, + TypeLoader: func(location common.Location, typeID interpreter.TypeID) sema.CompositeKindedType { + elaboration := importedChecker.Elaboration + compositeType := elaboration.CompositeType(typeID) + if compositeType != nil { + return compositeType + } + + return elaboration.InterfaceType(typeID) + }, } vmInstance = vm.NewVM(scriptLocation(), program, vmConfig) diff --git a/bbq/vm/types.go b/bbq/vm/types.go index 7ffbfbe308..3af1f23766 100644 --- a/bbq/vm/types.go +++ b/bbq/vm/types.go @@ -22,17 +22,8 @@ import "github.com/onflow/cadence/interpreter" type StaticType = interpreter.StaticType -func IsSubType(sourceType, targetType StaticType) bool { - if targetType == interpreter.PrimitiveStaticTypeAny { - return true - } - - // Optimization: If the static types are equal, then no need to check further. - if sourceType.Equal(targetType) { - return true - } - - // TODO: Add the remaining subType rules - - return true +func IsSubType(config *Config, sourceType, targetType StaticType) bool { + // TODO: Avoid conversion to sema types. + inter := config.interpreter() + return inter.IsSubType(sourceType, targetType) } diff --git a/bbq/vm/value.go b/bbq/vm/value.go index f4a77fd086..ba3cc89a24 100644 --- a/bbq/vm/value.go +++ b/bbq/vm/value.go @@ -20,13 +20,11 @@ package vm import ( "github.com/onflow/atree" - - "github.com/onflow/cadence/common" ) type Value interface { isValue() - StaticType(common.MemoryGauge) StaticType + StaticType(*Config) StaticType Transfer( config *Config, address atree.Address, diff --git a/bbq/vm/value_account.go b/bbq/vm/value_account.go index 7ab3cd8136..b68b20afbf 100644 --- a/bbq/vm/value_account.go +++ b/bbq/vm/value_account.go @@ -223,7 +223,7 @@ func BorrowCapabilityController( // and performs a dynamic type check referencedValue := referenceValue.ReferencedValue( - config.MemoryGauge, + config, false, ) if referencedValue == nil { diff --git a/bbq/vm/value_account_storage.go b/bbq/vm/value_account_storage.go index 0cbee855e8..19879f74c3 100644 --- a/bbq/vm/value_account_storage.go +++ b/bbq/vm/value_account_storage.go @@ -121,14 +121,14 @@ func init() { referenceType.Authorization, address, path, - referenceType, + referenceType.ReferencedType, ) // Attempt to dereference, // which reads the stored value // and performs a dynamic type check - referenced, err := reference.dereference(config.MemoryGauge) + referenced, err := reference.dereference(config) if err != nil { panic(err) } diff --git a/bbq/vm/value_address.go b/bbq/vm/value_address.go index 402bdd602d..7510eddb9d 100644 --- a/bbq/vm/value_address.go +++ b/bbq/vm/value_address.go @@ -32,7 +32,7 @@ var _ Value = AddressValue{} func (AddressValue) isValue() {} -func (AddressValue) StaticType(common.MemoryGauge) StaticType { +func (AddressValue) StaticType(*Config) StaticType { return interpreter.PrimitiveStaticTypeAddress } diff --git a/bbq/vm/value_array.go b/bbq/vm/value_array.go index b2e777e0df..cd71d04ab5 100644 --- a/bbq/vm/value_array.go +++ b/bbq/vm/value_array.go @@ -136,7 +136,7 @@ func (v *ArrayValue) isValue() { panic("implement me") } -func (v *ArrayValue) StaticType(common.MemoryGauge) StaticType { +func (v *ArrayValue) StaticType(*Config) StaticType { return v.Type } diff --git a/bbq/vm/value_bool.go b/bbq/vm/value_bool.go index 029594202b..1c4997d2ab 100644 --- a/bbq/vm/value_bool.go +++ b/bbq/vm/value_bool.go @@ -21,7 +21,6 @@ package vm import ( "github.com/onflow/atree" - "github.com/onflow/cadence/common" "github.com/onflow/cadence/format" "github.com/onflow/cadence/interpreter" ) @@ -35,7 +34,7 @@ var _ Value = BoolValue(true) func (BoolValue) isValue() {} -func (BoolValue) StaticType(common.MemoryGauge) StaticType { +func (BoolValue) StaticType(*Config) StaticType { return interpreter.PrimitiveStaticTypeBool } diff --git a/bbq/vm/value_capability.go b/bbq/vm/value_capability.go index 485a9f6725..3af4056e97 100644 --- a/bbq/vm/value_capability.go +++ b/bbq/vm/value_capability.go @@ -59,8 +59,8 @@ func NewInvalidCapabilityValue( func (CapabilityValue) isValue() {} -func (v CapabilityValue) StaticType(gauge common.MemoryGauge) StaticType { - return interpreter.NewCapabilityStaticType(gauge, v.BorrowType) +func (v CapabilityValue) StaticType(config *Config) StaticType { + return interpreter.NewCapabilityStaticType(config.MemoryGauge, v.BorrowType) } func (v CapabilityValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { @@ -91,7 +91,7 @@ func init() { NativeFunctionValue{ ParameterCount: 0, Function: func(config *Config, typeArguments []StaticType, args ...Value) Value { - capabilityValue := getReceiver[CapabilityValue](args[0]) + capabilityValue := getReceiver[CapabilityValue](config, args[0]) capabilityID := capabilityValue.ID if capabilityID == InvalidCapabilityID { @@ -179,7 +179,7 @@ func getCheckedCapabilityController( } else { //wantedBorrowType = inter.SubstituteMappedEntitlements(wantedBorrowType).(*sema.ReferenceType) - if !canBorrow(wantedBorrowType, capabilityBorrowType) { + if !canBorrow(config, wantedBorrowType, capabilityBorrowType) { return nil, nil } } @@ -193,7 +193,7 @@ func getCheckedCapabilityController( } controllerBorrowType := controller.CapabilityControllerBorrowType() - if !canBorrow(wantedBorrowType, controllerBorrowType) { + if !canBorrow(config, wantedBorrowType, controllerBorrowType) { return nil, nil } @@ -226,6 +226,7 @@ func getCapabilityController( } func canBorrow( + config *Config, wantedBorrowType *interpreter.ReferenceStaticType, capabilityBorrowType *interpreter.ReferenceStaticType, ) bool { @@ -240,6 +241,14 @@ func canBorrow( // Ensure the wanted borrow type is a subtype or supertype of the capability borrow type - return IsSubType(wantedBorrowType.ReferencedType, capabilityBorrowType.ReferencedType) || - IsSubType(capabilityBorrowType.ReferencedType, wantedBorrowType.ReferencedType) + return IsSubType( + config, + wantedBorrowType.ReferencedType, + capabilityBorrowType.ReferencedType, + ) || + IsSubType( + config, + capabilityBorrowType.ReferencedType, + wantedBorrowType.ReferencedType, + ) } diff --git a/bbq/vm/value_capability_controller.go b/bbq/vm/value_capability_controller.go index bc10d4f662..db324a027e 100644 --- a/bbq/vm/value_capability_controller.go +++ b/bbq/vm/value_capability_controller.go @@ -96,7 +96,7 @@ func (v *StorageCapabilityControllerValue) CapabilityControllerBorrowType() *int return v.BorrowType } -func (v *StorageCapabilityControllerValue) StaticType(_ common.MemoryGauge) StaticType { +func (v *StorageCapabilityControllerValue) StaticType(*Config) StaticType { return interpreter.PrimitiveStaticTypeStorageCapabilityController } diff --git a/bbq/vm/value_composite.go b/bbq/vm/value_composite.go index eb9ae3180d..5ceeb7d104 100644 --- a/bbq/vm/value_composite.go +++ b/bbq/vm/value_composite.go @@ -87,7 +87,7 @@ func newCompositeValueFromOrderedMap( func (*CompositeValue) isValue() {} -func (v *CompositeValue) StaticType(common.MemoryGauge) StaticType { +func (v *CompositeValue) StaticType(*Config) StaticType { return v.CompositeType } diff --git a/bbq/vm/value_dictionary.go b/bbq/vm/value_dictionary.go index 86f9739e01..83dc1924ef 100644 --- a/bbq/vm/value_dictionary.go +++ b/bbq/vm/value_dictionary.go @@ -110,7 +110,7 @@ func newDictionaryValueFromAtreeMap( func (*DictionaryValue) isValue() {} -func (v *DictionaryValue) StaticType(memoryGauge common.MemoryGauge) StaticType { +func (v *DictionaryValue) StaticType(*Config) StaticType { return v.Type } diff --git a/bbq/vm/value_ephemeral_reference.go b/bbq/vm/value_ephemeral_reference.go index 360c931f70..d7bd542554 100644 --- a/bbq/vm/value_ephemeral_reference.go +++ b/bbq/vm/value_ephemeral_reference.go @@ -20,7 +20,6 @@ package vm import ( "github.com/onflow/atree" - "github.com/onflow/cadence/common" "github.com/onflow/cadence/format" "github.com/onflow/cadence/interpreter" ) @@ -29,7 +28,7 @@ type ReferenceValue interface { Value //AuthorizedValue isReference() - ReferencedValue(gauge common.MemoryGauge, errorOnFailedDereference bool) *Value + ReferencedValue(config *Config, errorOnFailedDereference bool) *Value BorrowType() interpreter.StaticType } @@ -65,7 +64,7 @@ func (*EphemeralReferenceValue) isValue() {} func (v *EphemeralReferenceValue) isReference() {} -func (v *EphemeralReferenceValue) ReferencedValue(_ common.MemoryGauge, _ bool) *Value { +func (v *EphemeralReferenceValue) ReferencedValue(*Config, bool) *Value { return &v.Value } @@ -73,11 +72,11 @@ func (v *EphemeralReferenceValue) BorrowType() interpreter.StaticType { return v.BorrowedType } -func (v *EphemeralReferenceValue) StaticType(gauge common.MemoryGauge) StaticType { +func (v *EphemeralReferenceValue) StaticType(config *Config) StaticType { return interpreter.NewReferenceStaticType( - gauge, + config.MemoryGauge, v.Authorization, - v.Value.StaticType(gauge), + v.Value.StaticType(config), ) } diff --git a/bbq/vm/value_function.go b/bbq/vm/value_function.go index 0e3c86a587..bbecc758cd 100644 --- a/bbq/vm/value_function.go +++ b/bbq/vm/value_function.go @@ -22,7 +22,6 @@ import ( "github.com/onflow/atree" "github.com/onflow/cadence/bbq" - "github.com/onflow/cadence/common" "github.com/onflow/cadence/errors" ) @@ -35,7 +34,7 @@ var _ Value = FunctionValue{} func (FunctionValue) isValue() {} -func (FunctionValue) StaticType(common.MemoryGauge) StaticType { +func (FunctionValue) StaticType(*Config) StaticType { panic(errors.NewUnreachableError()) } @@ -60,7 +59,7 @@ var _ Value = NativeFunctionValue{} func (NativeFunctionValue) isValue() {} -func (NativeFunctionValue) StaticType(common.MemoryGauge) StaticType { +func (NativeFunctionValue) StaticType(*Config) StaticType { panic(errors.NewUnreachableError()) } diff --git a/bbq/vm/value_int.go b/bbq/vm/value_int.go index 20417911b2..00211d5d8d 100644 --- a/bbq/vm/value_int.go +++ b/bbq/vm/value_int.go @@ -23,7 +23,6 @@ import ( "github.com/onflow/atree" - "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" ) @@ -45,7 +44,7 @@ var _ Value = IntValue{} func (IntValue) isValue() {} -func (IntValue) StaticType(common.MemoryGauge) StaticType { +func (IntValue) StaticType(*Config) StaticType { return interpreter.PrimitiveStaticTypeInt } diff --git a/bbq/vm/value_nil.go b/bbq/vm/value_nil.go index acdbe2c9a2..f4d87d30c9 100644 --- a/bbq/vm/value_nil.go +++ b/bbq/vm/value_nil.go @@ -20,7 +20,6 @@ package vm import ( "github.com/onflow/atree" - "github.com/onflow/cadence/common" "github.com/onflow/cadence/format" "github.com/onflow/cadence/interpreter" ) @@ -31,9 +30,9 @@ var Nil Value = NilValue{} func (NilValue) isValue() {} -func (NilValue) StaticType(gauge common.MemoryGauge) StaticType { +func (NilValue) StaticType(config *Config) StaticType { return interpreter.NewOptionalStaticType( - gauge, + config.MemoryGauge, interpreter.PrimitiveStaticTypeNever, ) } diff --git a/bbq/vm/value_path.go b/bbq/vm/value_path.go index f38e94c80a..42f22ca3f9 100644 --- a/bbq/vm/value_path.go +++ b/bbq/vm/value_path.go @@ -38,7 +38,7 @@ var _ Value = PathValue{} func (PathValue) isValue() {} -func (v PathValue) StaticType(common.MemoryGauge) StaticType { +func (v PathValue) StaticType(*Config) StaticType { switch v.Domain { case common.PathDomainStorage: return interpreter.PrimitiveStaticTypeStoragePath diff --git a/bbq/vm/value_simple_composite.go b/bbq/vm/value_simple_composite.go index 14d251d1c4..a4aa06cadd 100644 --- a/bbq/vm/value_simple_composite.go +++ b/bbq/vm/value_simple_composite.go @@ -52,7 +52,7 @@ func NewSimpleCompositeValue( func (*SimpleCompositeValue) isValue() {} -func (v *SimpleCompositeValue) StaticType(memoryGauge common.MemoryGauge) StaticType { +func (v *SimpleCompositeValue) StaticType(*Config) StaticType { return v.staticType } diff --git a/bbq/vm/value_some.go b/bbq/vm/value_some.go index 2ef54d1c20..041d3bc80b 100644 --- a/bbq/vm/value_some.go +++ b/bbq/vm/value_some.go @@ -21,7 +21,6 @@ package vm import ( "github.com/onflow/atree" - "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" ) @@ -41,13 +40,13 @@ func NewSomeValueNonCopying(value Value) *SomeValue { func (*SomeValue) isValue() {} -func (v *SomeValue) StaticType(gauge common.MemoryGauge) StaticType { - innerType := v.value.StaticType(gauge) +func (v *SomeValue) StaticType(config *Config) StaticType { + innerType := v.value.StaticType(config) if innerType == nil { return nil } return interpreter.NewOptionalStaticType( - gauge, + config, innerType, ) } diff --git a/bbq/vm/value_storage_reference.go b/bbq/vm/value_storage_reference.go index 345ff7f3fa..8f4e72f127 100644 --- a/bbq/vm/value_storage_reference.go +++ b/bbq/vm/value_storage_reference.go @@ -58,8 +58,8 @@ func (*StorageReferenceValue) isValue() {} func (v *StorageReferenceValue) isReference() {} -func (v *StorageReferenceValue) ReferencedValue(gauge common.MemoryGauge, errorOnFailedDereference bool) *Value { - referenced, err := v.dereference(gauge) +func (v *StorageReferenceValue) ReferencedValue(config *Config, errorOnFailedDereference bool) *Value { + referenced, err := v.dereference(config) if err != nil && errorOnFailedDereference { panic(err) } @@ -71,33 +71,40 @@ func (v *StorageReferenceValue) BorrowType() interpreter.StaticType { return v.BorrowedType } -func (v *StorageReferenceValue) StaticType(gauge common.MemoryGauge) StaticType { - referencedValue, err := v.dereference(gauge) +func (v *StorageReferenceValue) StaticType(config *Config) StaticType { + referencedValue, err := v.dereference(config) if err != nil { panic(err) } + memoryGauge := config.MemoryGauge + return interpreter.NewReferenceStaticType( - gauge, + memoryGauge, v.Authorization, - (*referencedValue).StaticType(gauge), + (*referencedValue).StaticType(config), ) } -func (v *StorageReferenceValue) dereference(gauge common.MemoryGauge) (*Value, error) { +func (v *StorageReferenceValue) dereference(config *Config) (*Value, error) { + memoryGauge := config.MemoryGauge address := v.TargetStorageAddress domain := v.TargetPath.Domain.Identifier() identifier := v.TargetPath.Identifier - vmReferencedValue := ReadStored(gauge, v.storage, address, domain, identifier) + vmReferencedValue := ReadStored(memoryGauge, v.storage, address, domain, identifier) if vmReferencedValue == nil { return nil, nil } + if _, isReference := vmReferencedValue.(ReferenceValue); isReference { + panic(interpreter.NestedReferenceError{}) + } + if v.BorrowedType != nil { - staticType := vmReferencedValue.StaticType(gauge) + staticType := vmReferencedValue.StaticType(config) - if !IsSubType(staticType, v.BorrowedType) { + if !IsSubType(config, staticType, v.BorrowedType) { panic(fmt.Errorf("type mismatch: expected %s, found %s", v.BorrowedType, staticType)) //semaType := interpreter.MustConvertStaticToSemaType(staticType) // @@ -121,7 +128,7 @@ func (v *StorageReferenceValue) String() string { } func (v *StorageReferenceValue) GetMember(config *Config, name string) Value { - referencedValue, err := v.dereference(config.MemoryGauge) + referencedValue, err := v.dereference(config) if err != nil { panic(err) } @@ -131,7 +138,7 @@ func (v *StorageReferenceValue) GetMember(config *Config, name string) Value { } func (v *StorageReferenceValue) SetMember(config *Config, name string, value Value) { - referencedValue, err := v.dereference(config.MemoryGauge) + referencedValue, err := v.dereference(config) if err != nil { panic(err) } diff --git a/bbq/vm/value_string.go b/bbq/vm/value_string.go index e5211915b0..6029d4ec8c 100644 --- a/bbq/vm/value_string.go +++ b/bbq/vm/value_string.go @@ -23,7 +23,6 @@ import ( "github.com/onflow/atree" - "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" "github.com/onflow/cadence/sema" ) @@ -48,7 +47,7 @@ func NewStringValueFromBytes(bytes []byte) StringValue { func (StringValue) isValue() {} -func (StringValue) StaticType(common.MemoryGauge) StaticType { +func (StringValue) StaticType(*Config) StaticType { return interpreter.PrimitiveStaticTypeString } diff --git a/bbq/vm/value_void.go b/bbq/vm/value_void.go index b20bd1d7c0..a845ca3515 100644 --- a/bbq/vm/value_void.go +++ b/bbq/vm/value_void.go @@ -22,7 +22,6 @@ import ( "github.com/onflow/atree" "github.com/onflow/cadence/format" - "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" ) @@ -32,7 +31,7 @@ var Void Value = VoidValue{} func (VoidValue) isValue() {} -func (VoidValue) StaticType(common.MemoryGauge) StaticType { +func (VoidValue) StaticType(*Config) StaticType { return interpreter.PrimitiveStaticTypeVoid } diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index 930d92913a..005b02bf51 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -420,7 +420,7 @@ func opInvokeDynamic(vm *VM) { switch typedReceiver := receiver.(type) { case *StorageReferenceValue: - referenced, err := typedReceiver.dereference(vm.config.MemoryGauge) + referenced, err := typedReceiver.dereference(vm.config) if err != nil { panic(err) } @@ -504,14 +504,16 @@ func opTransfer(vm *VM) { targetType := vm.loadType() value := vm.peek() + config := vm.config + transferredValue := value.Transfer( - vm.config, + config, atree.Address{}, false, nil, ) - valueType := transferredValue.StaticType(vm.config.MemoryGauge) - if !IsSubType(valueType, targetType) { + valueType := transferredValue.StaticType(config) + if !IsSubType(config, valueType, targetType) { panic(errors.NewUnexpectedError("invalid transfer: expected '%s', found '%s'", targetType, valueType)) } @@ -777,18 +779,18 @@ func (vm *VM) StackSize() int { return len(vm.stack) } -func getReceiver[T any](receiver Value) T { +func getReceiver[T any](config *Config, receiver Value) T { switch receiver := receiver.(type) { case *SomeValue: - return getReceiver[T](receiver.value) + return getReceiver[T](config, receiver.value) case *EphemeralReferenceValue: - return getReceiver[T](receiver.Value) + return getReceiver[T](config, receiver.Value) case *StorageReferenceValue: referencedValue, err := receiver.dereference(nil) if err != nil { panic(err) } - return getReceiver[T](*referencedValue) + return getReceiver[T](config, *referencedValue) default: return receiver.(T) } From e5c0dbe0408683907cddc0f840e86398d0fcede7 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 20 Nov 2024 13:32:43 -0800 Subject: [PATCH 67/89] Improve tests --- bbq/vm/test/interpreter_test.go | 38 +++++++++++++++++++++++++++++++++ bbq/vm/test/vm_bench_test.go | 14 ++++++------ bbq/vm/value_int.go | 3 ++- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/bbq/vm/test/interpreter_test.go b/bbq/vm/test/interpreter_test.go index abf497211d..920b0ddfbd 100644 --- a/bbq/vm/test/interpreter_test.go +++ b/bbq/vm/test/interpreter_test.go @@ -1057,3 +1057,41 @@ func BenchmarkInterpreterImperativeFib(b *testing.B) { require.NoError(b, err) } } + +func BenchmarkInterpreterNewStruct(b *testing.B) { + + scriptLocation := runtime_utils.NewScriptLocationGenerator() + + inter, err := parseCheckAndInterpretWithOptions( + b, + ` + struct Foo { + var id : Int + + init(_ id: Int) { + self.id = id + } + } + + fun test(count: Int) { + var i = 0 + while i < count { + Foo(i) + i = i + 1 + } + }`, + scriptLocation(), + ParseCheckAndInterpretOptions{}, + ) + require.NoError(b, err) + + value := interpreter.NewIntValueFromInt64(nil, 10) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _, err := inter.Invoke("test", value) + require.NoError(b, err) + } +} diff --git a/bbq/vm/test/vm_bench_test.go b/bbq/vm/test/vm_bench_test.go index 7711d4121e..5717768401 100644 --- a/bbq/vm/test/vm_bench_test.go +++ b/bbq/vm/test/vm_bench_test.go @@ -85,22 +85,17 @@ func BenchmarkNewStruct(b *testing.B) { } } - fun test(count: Int): Foo { + fun test(count: Int) { var i = 0 - var r = Foo(0) while i < count { + Foo(i) i = i + 1 - r = Foo(i) } - return r } `) require.NoError(b, err) - value := vm.NewIntValue(1) - - b.ReportAllocs() - b.ResetTimer() + value := vm.NewIntValue(10) comp := compiler.NewCompiler(checker.Program, checker.Elaboration) program := comp.Compile() @@ -108,6 +103,9 @@ func BenchmarkNewStruct(b *testing.B) { vmConfig := &vm.Config{} vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { _, err := vmInstance.Invoke("test", value) require.NoError(b, err) diff --git a/bbq/vm/value_int.go b/bbq/vm/value_int.go index 00211d5d8d..42065feff4 100644 --- a/bbq/vm/value_int.go +++ b/bbq/vm/value_int.go @@ -53,7 +53,8 @@ func (v IntValue) Transfer(*Config, atree.Address, bool, atree.Storable) Value { } func (v IntValue) Add(other IntValue) Value { - return NewIntValue(v.SmallInt + other.SmallInt) + sum := safeAdd(int(v.SmallInt), int(other.SmallInt)) + return NewIntValue(int64(sum)) } func (v IntValue) Subtract(other IntValue) Value { From 405516ea9531edb07dad117990df1f55fa8d1d6d Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Mon, 25 Nov 2024 14:29:41 -0800 Subject: [PATCH 68/89] Move locals out of stackframe --- bbq/vm/callframe.go | 11 +++++---- bbq/vm/reference_tracking.go | 10 +++----- bbq/vm/test/ft_test.go | 2 +- bbq/vm/test/vm_bench_test.go | 12 ++++++++-- bbq/vm/test/vm_test.go | 4 ++-- bbq/vm/vm.go | 46 ++++++++++++++++++++++++++---------- 6 files changed, 55 insertions(+), 30 deletions(-) diff --git a/bbq/vm/callframe.go b/bbq/vm/callframe.go index 830a66c97e..529e54c041 100644 --- a/bbq/vm/callframe.go +++ b/bbq/vm/callframe.go @@ -23,11 +23,12 @@ import ( ) type callFrame struct { - parent *callFrame - executable *ExecutableProgram - locals []Value - function *bbq.Function - ip uint16 + parent *callFrame + executable *ExecutableProgram + localsOffset uint16 + localsCount uint16 + function *bbq.Function + ip uint16 } func (f *callFrame) getUint16() uint16 { diff --git a/bbq/vm/reference_tracking.go b/bbq/vm/reference_tracking.go index 5ac08f0cce..e468a761d0 100644 --- a/bbq/vm/reference_tracking.go +++ b/bbq/vm/reference_tracking.go @@ -59,14 +59,10 @@ func trackReferencedResourceKindedValue( func checkInvalidatedResourceOrResourceReference(value Value) { - // Unwrap SomeValue, to access references wrapped inside optionals. - someValue, isSomeValue := value.(*SomeValue) - for isSomeValue && someValue.value != nil { - value = someValue.value - someValue, isSomeValue = value.(*SomeValue) - } - switch value := value.(type) { + case *SomeValue: + checkInvalidatedResourceOrResourceReference(value.value) + // TODO: //case ResourceKindedValue: // if value.isInvalidatedResource(interpreter) { diff --git a/bbq/vm/test/ft_test.go b/bbq/vm/test/ft_test.go index a51333f34d..57b5ae9b5d 100644 --- a/bbq/vm/test/ft_test.go +++ b/bbq/vm/test/ft_test.go @@ -319,12 +319,12 @@ func BenchmarkFTTransfer(b *testing.B) { tokenTransferTxChecker := parseAndCheck(b, realFlowTokenTransferTransaction, nil, programs) tokenTransferTxProgram := compile(b, tokenTransferTxChecker, programs) - tokenTransferTxVM := vm.NewVM(txLocation(), tokenTransferTxProgram, vmConfig) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { + tokenTransferTxVM := vm.NewVM(txLocation(), tokenTransferTxProgram, vmConfig) err = tokenTransferTxVM.ExecuteTransaction(tokenTransferTxArgs, tokenTransferTxAuthorizer) require.NoError(b, err) } diff --git a/bbq/vm/test/vm_bench_test.go b/bbq/vm/test/vm_bench_test.go index 5717768401..c558dc1fe8 100644 --- a/bbq/vm/test/vm_bench_test.go +++ b/bbq/vm/test/vm_bench_test.go @@ -359,18 +359,26 @@ func BenchmarkMethodCall(b *testing.B) { ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { return importedContractValue }, + TypeLoader: func(location common.Location, typeID interpreter.TypeID) sema.CompositeKindedType { + elaboration := importedChecker.Elaboration + compositeType := elaboration.CompositeType(typeID) + if compositeType != nil { + return compositeType + } + + return elaboration.InterfaceType(typeID) + }, } scriptLocation := runtime_utils.NewScriptLocationGenerator() - vmInstance = vm.NewVM(scriptLocation(), program, vmConfig) - value := vm.NewIntValue(10) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { + vmInstance = vm.NewVM(scriptLocation(), program, vmConfig) _, err := vmInstance.Invoke("test", value) require.NoError(b, err) } diff --git a/bbq/vm/test/vm_test.go b/bbq/vm/test/vm_test.go index cbd303244a..ea0e562363 100644 --- a/bbq/vm/test/vm_test.go +++ b/bbq/vm/test/vm_test.go @@ -69,10 +69,10 @@ func TestRecursionFib(t *testing.T) { result, err := vmInstance.Invoke( "fib", - vm.NewIntValue(7), + vm.NewIntValue(35), ) require.NoError(t, err) - require.Equal(t, vm.NewIntValue(13), result) + require.Equal(t, vm.NewIntValue(9227465), result) require.Equal(t, 0, vmInstance.StackSize()) } diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index 005b02bf51..3de9891012 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -20,22 +20,22 @@ package vm import ( "github.com/onflow/atree" - "github.com/onflow/cadence/common" - "github.com/onflow/cadence/errors" - "github.com/onflow/cadence/interpreter" - "github.com/onflow/cadence/runtime" - "github.com/onflow/cadence/bbq" "github.com/onflow/cadence/bbq/commons" "github.com/onflow/cadence/bbq/constantkind" "github.com/onflow/cadence/bbq/leb128" "github.com/onflow/cadence/bbq/opcode" + "github.com/onflow/cadence/common" + "github.com/onflow/cadence/errors" + "github.com/onflow/cadence/interpreter" + "github.com/onflow/cadence/runtime" ) type VM struct { globals map[string]Value callFrame *callFrame stack []Value + locals []Value config *Config linkedGlobalsCache map[common.Location]LinkedGlobals } @@ -139,21 +139,38 @@ func (vm *VM) replaceTop(value Value) { vm.stack[lastIndex] = value } +func fill(slice []Value, n int) []Value { + for i := 0; i < n; i++ { + slice = append(slice, nil) + } + return slice +} + func (vm *VM) pushCallFrame(functionValue FunctionValue, arguments []Value) { - locals := make([]Value, functionValue.Function.LocalCount) + localsCount := functionValue.Function.LocalCount - copy(locals, arguments) + vm.locals = append(vm.locals, arguments...) + vm.locals = fill(vm.locals, int(localsCount)-len(arguments)) + + // Calculate the offset for local variable for the new callframe. + // This is equal to: (local var offset + local var count) of previous callframe. + var offset uint16 + if vm.callFrame != nil { + offset = vm.callFrame.localsOffset + vm.callFrame.localsCount + } callFrame := &callFrame{ - parent: vm.callFrame, - locals: locals, - function: functionValue.Function, - executable: functionValue.Executable, + parent: vm.callFrame, + localsCount: localsCount, + localsOffset: offset, + function: functionValue.Function, + executable: functionValue.Executable, } vm.callFrame = callFrame } func (vm *VM) popCallFrame() { + vm.locals = vm.locals[:vm.callFrame.localsOffset] vm.callFrame = vm.callFrame.parent } @@ -331,14 +348,17 @@ func opGetConstant(vm *VM) { func opGetLocal(vm *VM) { callFrame := vm.callFrame index := callFrame.getUint16() - local := callFrame.locals[index] + absoluteIndex := callFrame.localsOffset + index + local := vm.locals[absoluteIndex] vm.push(local) } func opSetLocal(vm *VM) { callFrame := vm.callFrame index := callFrame.getUint16() - callFrame.locals[index] = vm.pop() + + absoluteIndex := callFrame.localsOffset + index + vm.locals[absoluteIndex] = vm.pop() } func opGetGlobal(vm *VM) { From 22ae521871a9c15a7ef4570757bfe650c21cdaeb Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 26 Nov 2024 14:09:13 -0800 Subject: [PATCH 69/89] Fix missing returns --- bbq/compiler/compiler.go | 7 +++++++ bbq/vm/linker.go | 7 +++++-- bbq/vm/test/ft_test.go | 27 ++++++++++++++++++++++++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/bbq/compiler/compiler.go b/bbq/compiler/compiler.go index b5ce159524..366862ec50 100644 --- a/bbq/compiler/compiler.go +++ b/bbq/compiler/compiler.go @@ -1139,8 +1139,15 @@ func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration c.declareParameters(function, declaration.ParameterList, declareReceiver) c.compileFunctionBlock(declaration.FunctionBlock) + // Manually emit a return, if there are no explicit return statements. if !declaration.FunctionBlock.HasStatements() { c.emit(opcode.Return) + } else { + statements := declaration.FunctionBlock.Block.Statements + lastStmt := statements[len(statements)-1] + if _, isReturn := lastStmt.(*ast.ReturnStatement); !isReturn { + c.emit(opcode.Return) + } } return diff --git a/bbq/vm/linker.go b/bbq/vm/linker.go index 98d97a2312..eaa4b27c5a 100644 --- a/bbq/vm/linker.go +++ b/bbq/vm/linker.go @@ -93,8 +93,11 @@ func LinkGlobals( executable := NewLoadedExecutableProgram(location, program) - globals := make([]Value, 0) - indexedGlobals := make(map[string]Value, 0) + globalsLen := len(program.Variables) + len(program.Functions) + len(importedGlobals) + 1 + indexedGlobalsLen := len(program.Functions) + + globals := make([]Value, 0, globalsLen) + indexedGlobals := make(map[string]Value, indexedGlobalsLen) // If the current program is a contract, reserve a global variable for the contract value. // The reserved position is always the zero-th index. diff --git a/bbq/vm/test/ft_test.go b/bbq/vm/test/ft_test.go index 57b5ae9b5d..a2e53276c2 100644 --- a/bbq/vm/test/ft_test.go +++ b/bbq/vm/test/ft_test.go @@ -319,15 +319,40 @@ func BenchmarkFTTransfer(b *testing.B) { tokenTransferTxChecker := parseAndCheck(b, realFlowTokenTransferTransaction, nil, programs) tokenTransferTxProgram := compile(b, tokenTransferTxChecker, programs) + tokenTransferTxVM := vm.NewVM(txLocation(), tokenTransferTxProgram, vmConfig) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - tokenTransferTxVM := vm.NewVM(txLocation(), tokenTransferTxProgram, vmConfig) err = tokenTransferTxVM.ExecuteTransaction(tokenTransferTxArgs, tokenTransferTxAuthorizer) require.NoError(b, err) } b.StopTimer() + + // Run validation scripts + + // actual transfer amount = (transfer amount in one tx) * (number of time the tx/benchmark runs) + actualTransferAmount := transferAmount * int64(b.N) + + for _, address := range []common.Address{ + senderAddress, + receiverAddress, + } { + program := compileCode(b, realFlowTokenBalanceScript, nil, programs) + + validationScriptVM := vm.NewVM(scriptLocation(), program, vmConfig) + + addressValue := vm.AddressValue(address) + result, err := validationScriptVM.Invoke("main", addressValue) + require.NoError(b, err) + require.Equal(b, 0, validationScriptVM.StackSize()) + + if address == senderAddress { + assert.Equal(b, vm.NewIntValue(total-actualTransferAmount), result) + } else { + assert.Equal(b, vm.NewIntValue(actualTransferAmount), result) + } + } } From 2d9b4b770ae31862378c5f814b9e717c807dbe87 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 27 Nov 2024 07:29:28 -0800 Subject: [PATCH 70/89] optimization: make the callframe a non-pointer --- bbq/vm/callframe.go | 53 ++++++++------- bbq/vm/test/vm_bench_test.go | 4 +- bbq/vm/test/vm_test.go | 15 +++++ bbq/vm/vm.go | 123 +++++++++++++++++++++++------------ 4 files changed, 126 insertions(+), 69 deletions(-) diff --git a/bbq/vm/callframe.go b/bbq/vm/callframe.go index 529e54c041..0c3adfebb3 100644 --- a/bbq/vm/callframe.go +++ b/bbq/vm/callframe.go @@ -23,36 +23,35 @@ import ( ) type callFrame struct { - parent *callFrame executable *ExecutableProgram localsOffset uint16 localsCount uint16 function *bbq.Function - ip uint16 } -func (f *callFrame) getUint16() uint16 { - first := f.function.Code[f.ip] - last := f.function.Code[f.ip+1] - f.ip += 2 - return uint16(first)<<8 | uint16(last) -} - -func (f *callFrame) getByte() byte { - byt := f.function.Code[f.ip] - f.ip++ - return byt -} - -func (f *callFrame) getBool() bool { - byt := f.function.Code[f.ip] - f.ip++ - return byt == 1 -} - -func (f *callFrame) getString() string { - strLen := f.getUint16() - str := string(f.function.Code[f.ip : f.ip+strLen]) - f.ip += strLen - return str -} +// +//func (f *callFrame) getUint16() uint16 { +// first := f.function.Code[f.ip] +// last := f.function.Code[f.ip+1] +// f.ip += 2 +// return uint16(first)<<8 | uint16(last) +//} +// +//func (f *callFrame) getByte() byte { +// byt := f.function.Code[f.ip] +// f.ip++ +// return byt +//} +// +//func (f *callFrame) getBool() bool { +// byt := f.function.Code[f.ip] +// f.ip++ +// return byt == 1 +//} +// +//func (f *callFrame) getString() string { +// strLen := f.getUint16() +// str := string(f.function.Code[f.ip : f.ip+strLen]) +// f.ip += strLen +// return str +//} diff --git a/bbq/vm/test/vm_bench_test.go b/bbq/vm/test/vm_bench_test.go index c558dc1fe8..7f795fb3d2 100644 --- a/bbq/vm/test/vm_bench_test.go +++ b/bbq/vm/test/vm_bench_test.go @@ -32,11 +32,11 @@ func BenchmarkRecursionFib(b *testing.B) { vmConfig := &vm.Config{} vmInstance := vm.NewVM(scriptLocation(), program, vmConfig) + expected := vm.NewIntValue(377) + b.ReportAllocs() b.ResetTimer() - expected := vm.NewIntValue(377) - for i := 0; i < b.N; i++ { result, err := vmInstance.Invoke( diff --git a/bbq/vm/test/vm_test.go b/bbq/vm/test/vm_test.go index ea0e562363..07059eed89 100644 --- a/bbq/vm/test/vm_test.go +++ b/bbq/vm/test/vm_test.go @@ -2041,5 +2041,20 @@ func TestResource(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, vmInstance.StackSize()) }) +} + +func fib(n int) int { + if n < 2 { + return n + } + return fib(n-1) + fib(n-2) +} + +func BenchmarkGoFib(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + fib(46) + } } diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index 3de9891012..c8db589751 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -32,11 +32,17 @@ import ( ) type VM struct { - globals map[string]Value - callFrame *callFrame - stack []Value - locals []Value + stack []Value + locals []Value + + callstack []callFrame + callFrame callFrame + + ipStack []uint16 + ip uint16 + config *Config + globals map[string]Value linkedGlobalsCache map[common.Location]LinkedGlobals } @@ -155,23 +161,42 @@ func (vm *VM) pushCallFrame(functionValue FunctionValue, arguments []Value) { // Calculate the offset for local variable for the new callframe. // This is equal to: (local var offset + local var count) of previous callframe. var offset uint16 - if vm.callFrame != nil { + if len(vm.callstack) > 0 { offset = vm.callFrame.localsOffset + vm.callFrame.localsCount + + // store/update the current ip, so it can be resumed. + vm.ipStack[len(vm.ipStack)-1] = vm.ip } - callFrame := &callFrame{ - parent: vm.callFrame, + callFrame := callFrame{ localsCount: localsCount, localsOffset: offset, function: functionValue.Function, executable: functionValue.Executable, } + + vm.ipStack = append(vm.ipStack, 0) + vm.ip = 0 + + vm.callstack = append(vm.callstack, callFrame) vm.callFrame = callFrame } func (vm *VM) popCallFrame() { vm.locals = vm.locals[:vm.callFrame.localsOffset] - vm.callFrame = vm.callFrame.parent + + newIpStackDepth := len(vm.ipStack) - 1 + vm.ipStack = vm.ipStack[:newIpStackDepth] + + newStackDepth := len(vm.callstack) - 1 + vm.callstack = vm.callstack[:newStackDepth] + + if newStackDepth == 0 { + vm.ip = 0 + } else { + vm.ip = vm.ipStack[newIpStackDepth-1] + vm.callFrame = vm.callstack[newStackDepth-1] + } } func (vm *VM) Invoke(name string, arguments ...Value) (v Value, err error) { @@ -285,17 +310,15 @@ func opReturn(vm *VM) { } func opJump(vm *VM) { - callFrame := vm.callFrame - target := callFrame.getUint16() - callFrame.ip = target + target := vm.getUint16() + vm.ip = target } func opJumpIfFalse(vm *VM) { - callFrame := vm.callFrame - target := callFrame.getUint16() + target := vm.getUint16() value := vm.pop().(BoolValue) if !value { - callFrame.ip = target + vm.ip = target } } @@ -337,7 +360,7 @@ func opFalse(vm *VM) { func opGetConstant(vm *VM) { callFrame := vm.callFrame - index := callFrame.getUint16() + index := vm.getUint16() constant := callFrame.executable.Constants[index] if constant == nil { constant = vm.initializeConstant(index) @@ -347,7 +370,7 @@ func opGetConstant(vm *VM) { func opGetLocal(vm *VM) { callFrame := vm.callFrame - index := callFrame.getUint16() + index := vm.getUint16() absoluteIndex := callFrame.localsOffset + index local := vm.locals[absoluteIndex] vm.push(local) @@ -355,7 +378,7 @@ func opGetLocal(vm *VM) { func opSetLocal(vm *VM) { callFrame := vm.callFrame - index := callFrame.getUint16() + index := vm.getUint16() absoluteIndex := callFrame.localsOffset + index vm.locals[absoluteIndex] = vm.pop() @@ -363,13 +386,13 @@ func opSetLocal(vm *VM) { func opGetGlobal(vm *VM) { callFrame := vm.callFrame - index := callFrame.getUint16() + index := vm.getUint16() vm.push(callFrame.executable.Globals[index]) } func opSetGlobal(vm *VM) { callFrame := vm.callFrame - index := callFrame.getUint16() + index := vm.getUint16() callFrame.executable.Globals[index] = vm.pop() } @@ -392,8 +415,7 @@ func opInvoke(vm *VM) { value := vm.pop() stackHeight := len(vm.stack) - callFrame := vm.callFrame - typeArgCount := callFrame.getUint16() + typeArgCount := vm.getUint16() switch value := value.(type) { case FunctionValue: @@ -421,10 +443,9 @@ func opInvoke(vm *VM) { } func opInvokeDynamic(vm *VM) { - callframe := vm.callFrame - funcName := callframe.getString() - typeArgCount := callframe.getUint16() - argsCount := callframe.getUint16() + funcName := vm.getString() + typeArgCount := vm.getUint16() + argsCount := vm.getUint16() stackHeight := len(vm.stack) receiver := vm.stack[stackHeight-int(argsCount)-1] @@ -472,9 +493,7 @@ func opDup(vm *VM) { } func opNew(vm *VM) { - callframe := vm.callFrame - - kind := callframe.getUint16() + kind := vm.getUint16() compositeKind := common.CompositeKind(kind) // decode location @@ -546,9 +565,8 @@ func opDestroy(vm *VM) { } func opPath(vm *VM) { - callframe := vm.callFrame - domain := common.PathDomain(callframe.getByte()) - identifier := callframe.getString() + domain := common.PathDomain(vm.getByte()) + identifier := vm.getString() value := PathValue{ Domain: domain, Identifier: identifier, @@ -557,10 +575,9 @@ func opPath(vm *VM) { } func opCast(vm *VM) { - callframe := vm.callFrame value := vm.pop() targetType := vm.loadType() - castKind := commons.CastKind(callframe.getByte()) + castKind := commons.CastKind(vm.getByte()) // TODO: _ = castKind @@ -593,8 +610,8 @@ func opUnwrap(vm *VM) { func opNewArray(vm *VM) { typ := vm.loadType().(interpreter.ArrayStaticType) - size := int(vm.callFrame.getUint16()) - isResourceKinded := vm.callFrame.getBool() + size := int(vm.getUint16()) + isResourceKinded := vm.getBool() elements := make([]Value, size) @@ -626,14 +643,14 @@ func (vm *VM) run() { callFrame := vm.callFrame - if callFrame == nil || - int(callFrame.ip) >= len(callFrame.function.Code) { + if len(vm.callstack) == 0 || + int(vm.ip) >= len(callFrame.function.Code) { return } - op := opcode.Opcode(callFrame.function.Code[callFrame.ip]) - callFrame.ip++ + op := opcode.Opcode(callFrame.function.Code[vm.ip]) + vm.ip++ switch op { case opcode.ReturnValue: @@ -735,7 +752,7 @@ func (vm *VM) initializeConstant(index uint16) (value Value) { func (vm *VM) loadType() StaticType { callframe := vm.callFrame - index := callframe.getUint16() + index := vm.getUint16() staticType := callframe.executable.StaticTypes[index] if staticType == nil { // TODO: Remove. Should never reach because of the @@ -799,6 +816,32 @@ func (vm *VM) StackSize() int { return len(vm.stack) } +func (vm *VM) getUint16() uint16 { + first := vm.callFrame.function.Code[vm.ip] + last := vm.callFrame.function.Code[vm.ip+1] + vm.ip += 2 + return uint16(first)<<8 | uint16(last) +} + +func (vm *VM) getByte() byte { + byt := vm.callFrame.function.Code[vm.ip] + vm.ip++ + return byt +} + +func (vm *VM) getBool() bool { + byt := vm.callFrame.function.Code[vm.ip] + vm.ip++ + return byt == 1 +} + +func (vm *VM) getString() string { + strLen := vm.getUint16() + str := string(vm.callFrame.function.Code[vm.ip : vm.ip+strLen]) + vm.ip += strLen + return str +} + func getReceiver[T any](config *Config, receiver Value) T { switch receiver := receiver.(type) { case *SomeValue: From e8627af524d3d095712167336aa97d7d1b5db6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 29 Nov 2024 11:55:08 -0800 Subject: [PATCH 71/89] improve and test instruction printer --- bbq/bytecode_printer.go | 114 ++------------------- bbq/opcode/print.go | 211 +++++++++++++++++++++++++++++++++++++++ bbq/opcode/print_test.go | 141 ++++++++++++++++++++++++++ 3 files changed, 359 insertions(+), 107 deletions(-) create mode 100644 bbq/opcode/print.go create mode 100644 bbq/opcode/print_test.go diff --git a/bbq/bytecode_printer.go b/bbq/bytecode_printer.go index f529665b31..9c56c649a0 100644 --- a/bbq/bytecode_printer.go +++ b/bbq/bytecode_printer.go @@ -19,13 +19,13 @@ package bbq import ( + "bytes" "fmt" "strings" "github.com/onflow/cadence/bbq/constantkind" "github.com/onflow/cadence/bbq/leb128" "github.com/onflow/cadence/bbq/opcode" - "github.com/onflow/cadence/common" "github.com/onflow/cadence/errors" "github.com/onflow/cadence/interpreter" ) @@ -52,87 +52,12 @@ func (p *BytecodePrinter) printFunction(function *Function) { p.printCode(function.Code) } -func (p *BytecodePrinter) printCode(codes []byte) { - for i := 0; i < len(codes); i++ { - code := codes[i] - opcodeString := opcode.Opcode(code).String() - - p.stringBuilder.WriteString(opcodeString) - - switch opcode.Opcode(code) { - - // opcodes with one operand - case opcode.GetConstant, - opcode.GetLocal, - opcode.SetLocal, - opcode.GetGlobal, - opcode.SetGlobal, - opcode.Jump, - opcode.JumpIfFalse, - opcode.Transfer: - var operand int - operand, i = p.getIntOperand(codes, i) - p.stringBuilder.WriteString(" " + fmt.Sprint(operand)) - - case opcode.New: - var kind, typeIndex int - kind, i = p.getIntOperand(codes, i) - typeIndex, i = p.getIntOperand(codes, i) - p.stringBuilder.WriteString(" " + fmt.Sprint(kind) + " " + fmt.Sprint(typeIndex)) - - case opcode.Cast: - var typeIndex int - var castKind byte - typeIndex, i = p.getIntOperand(codes, i) - castKind, i = p.getByteOperand(codes, i) - p.stringBuilder.WriteString(" " + fmt.Sprint(typeIndex) + " " + fmt.Sprint(int8(castKind))) - - case opcode.Path: - var identifier string - var domain byte - domain, i = p.getByteOperand(codes, i) - identifier, i = p.getStringOperand(codes, i) - p.stringBuilder.WriteString(" " + fmt.Sprint(int8(domain)) + " " + identifier) - - case opcode.InvokeDynamic: - var funcName string - funcName, i = p.getStringOperand(codes, i) - p.stringBuilder.WriteString(" " + funcName) - - // Type parameters - var typeParamCount int - typeParamCount, i = p.getIntOperand(codes, i) - p.stringBuilder.WriteString(" " + fmt.Sprint(typeParamCount)) - - var typeIndex int - for j := 0; j < typeParamCount; j++ { - typeIndex, i = p.getIntOperand(codes, i) - p.stringBuilder.WriteString(" " + fmt.Sprint(typeIndex)) - } - - // Argument count parameters - var argsCount int - argsCount, i = p.getIntOperand(codes, i) - p.stringBuilder.WriteString(" " + fmt.Sprint(argsCount)) - - case opcode.Invoke: - // Type parameters - var typeParamCount int - typeParamCount, i = p.getIntOperand(codes, i) - p.stringBuilder.WriteString(" " + fmt.Sprint(typeParamCount)) - - var typeIndex int - for j := 0; j < typeParamCount; j++ { - typeIndex, i = p.getIntOperand(codes, i) - p.stringBuilder.WriteString(" " + fmt.Sprint(typeIndex)) - } - - // opcodes with no operands - default: - // do nothing - } - - p.stringBuilder.WriteRune('\n') +func (p *BytecodePrinter) printCode(code []byte) { + reader := bytes.NewReader(code) + err := opcode.PrintInstructions(&p.stringBuilder, reader) + if err != nil { + // TODO: propagate error + panic(err) } } @@ -143,17 +68,6 @@ func (*BytecodePrinter) getIntOperand(codes []byte, i int) (operand int, endInde return operand, i + 2 } -func (p *BytecodePrinter) getStringOperand(codes []byte, i int) (operand string, endIndex int) { - strLen, i := p.getIntOperand(codes, i) - operand = string(codes[i+1 : i+1+strLen]) - return operand, i + strLen -} - -func (*BytecodePrinter) getByteOperand(codes []byte, i int) (operand byte, endIndex int) { - byt := codes[i+1] - return byt, i + 1 -} - func (p *BytecodePrinter) printConstantPool(constants []*Constant) { p.stringBuilder.WriteString("-- Constant Pool --\n") @@ -202,20 +116,6 @@ func (p *BytecodePrinter) printTypePool(types [][]byte) { p.stringBuilder.WriteRune('\n') } -func (p *BytecodePrinter) getLocation(codes []byte, i int) (location common.Location, endIndex int) { - locationLen, i := p.getIntOperand(codes, i) - locationBytes := codes[i+1 : i+1+locationLen] - - dec := interpreter.CBORDecMode.NewByteStreamDecoder(locationBytes) - locationDecoder := interpreter.NewLocationDecoder(dec, nil) - location, err := locationDecoder.DecodeLocation() - if err != nil { - panic(err) - } - - return location, i + locationLen -} - func (p *BytecodePrinter) printImports(imports []*Import) { p.stringBuilder.WriteString("-- Imports --\n") for _, impt := range imports { diff --git a/bbq/opcode/print.go b/bbq/opcode/print.go new file mode 100644 index 0000000000..7c46ceb5ea --- /dev/null +++ b/bbq/opcode/print.go @@ -0,0 +1,211 @@ +package opcode + +import ( + "bytes" + "fmt" + "strings" +) + +func PrintInstructions(builder *strings.Builder, reader *bytes.Reader) error { + for reader.Len() > 0 { + offset := reader.Size() - int64(reader.Len()) + err := PrintInstruction(builder, reader) + if err != nil { + return fmt.Errorf("failed to print instruction at offset %d: %w", offset, err) + } + builder.WriteByte('\n') + } + return nil +} + +func PrintInstruction(builder *strings.Builder, reader *bytes.Reader) error { + + rawOpcode, err := reader.ReadByte() + if err != nil { + return fmt.Errorf("failed to read opcode: %w", err) + } + opcode := Opcode(rawOpcode) + + builder.WriteString(opcode.String()) + + switch opcode { + + // opcodes with one operand + case GetConstant, + GetLocal, + SetLocal, + GetGlobal, + SetGlobal, + Jump, + JumpIfFalse, + Transfer: + + operand, err := readIntOperand(reader) + if err != nil { + return fmt.Errorf("failed to read operand: %w", err) + } + + builder.WriteByte(' ') + _, _ = fmt.Fprint(builder, operand) + + case New: + kind, err := readIntOperand(reader) + if err != nil { + return fmt.Errorf("failed to read kind operand: %w", err) + } + + typeIndex, err := readIntOperand(reader) + if err != nil { + return fmt.Errorf("failed to read type index operand: %w", err) + } + + _, _ = fmt.Fprintf(builder, " kind:%d typeIndex:%d", kind, typeIndex) + + case Cast: + typeIndex, err := readIntOperand(reader) + if err != nil { + return fmt.Errorf("failed to read type index operand: %w", err) + } + + castKind, err := reader.ReadByte() + if err != nil { + return fmt.Errorf("failed to read cast kind operand: %w", err) + } + + _, _ = fmt.Fprintf(builder, " typeIndex:%d castKind:%d", typeIndex, castKind) + + case Path: + domain, err := reader.ReadByte() + if err != nil { + return fmt.Errorf("failed to read domain operand: %w", err) + } + + identifier, err := readStringOperand(reader) + if err != nil { + return fmt.Errorf("failed to read identifier operand: %w", err) + } + + _, _ = fmt.Fprintf(builder, " domain:%d identifier:%q", domain, identifier) + + case InvokeDynamic: + // Function name + funcName, err := readStringOperand(reader) + if err != nil { + return fmt.Errorf("failed to read function name operand: %w", err) + } + _, _ = fmt.Fprintf(builder, " funcName:%q", funcName) + + // Type parameters + err = printTypeParameters(builder, reader) + if err != nil { + return fmt.Errorf("failed to read type parameters: %w", err) + } + + // Argument count + argsCount, err := readIntOperand(reader) + if err != nil { + return fmt.Errorf("failed to read argument count operand: %w", err) + } + _, _ = fmt.Fprintf(builder, " argsCount:%d", argsCount) + + case Invoke: + err := printTypeParameters(builder, reader) + if err != nil { + return fmt.Errorf("failed to read type parameters: %w", err) + } + + // opcodes with no operands + case Unknown, + Return, + ReturnValue, + IntAdd, + IntSubtract, + IntMultiply, + IntDivide, + IntMod, + IntLess, + IntGreater, + IntLessOrEqual, + IntGreaterOrEqual, + Equal, + NotEqual, + Unwrap, + Destroy, + True, + False, + Nil, + NewArray, + NewDictionary, + NewRef, + GetField, + SetField, + SetIndex, + GetIndex, + Drop, + Dup: + // no operands + } + + return nil +} + +func readIntOperand(reader *bytes.Reader) (operand int, err error) { + first, err := reader.ReadByte() + if err != nil { + return 0, fmt.Errorf("failed to read first byte of int operand: %w", err) + } + + second, err := reader.ReadByte() + if err != nil { + return 0, fmt.Errorf("failed to read second byte of int operand: %w", err) + } + + operand = int(uint16(first)<<8 | uint16(second)) + return operand, nil +} + +func readStringOperand(reader *bytes.Reader) (operand string, err error) { + stringLength, err := readIntOperand(reader) + if err != nil { + return "", fmt.Errorf("failed to read string length of string operand: %w", err) + } + + stringBytes := make([]byte, stringLength) + readLength, err := reader.Read(stringBytes) + if err != nil { + return "", fmt.Errorf("failed to read string bytes of string operand: %w", err) + } + if readLength != stringLength { + return "", fmt.Errorf( + "failed to read all bytes of string operand: expected %d, got %d", + stringLength, + readLength, + ) + } + + return string(stringBytes), nil +} + +func printTypeParameters(builder *strings.Builder, reader *bytes.Reader) error { + typeParamCount, err := readIntOperand(reader) + if err != nil { + return fmt.Errorf("failed to read type parameter count operand: %w", err) + } + _, _ = fmt.Fprintf(builder, " typeParamCount:%d typeParams:[", typeParamCount) + + for i := 0; i < typeParamCount; i++ { + if i > 0 { + builder.WriteString(", ") + } + + typeIndex, err := readIntOperand(reader) + if err != nil { + return fmt.Errorf("failed to read type index operand: %w", err) + } + + _, _ = fmt.Fprint(builder, typeIndex) + } + builder.WriteByte(']') + + return nil +} diff --git a/bbq/opcode/print_test.go b/bbq/opcode/print_test.go new file mode 100644 index 0000000000..d06270e098 --- /dev/null +++ b/bbq/opcode/print_test.go @@ -0,0 +1,141 @@ +package opcode + +import ( + "bytes" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPrintRecursionFib(t *testing.T) { + t.Parallel() + + code := []byte{ + // if n < 2 + byte(GetLocal), 0, 0, + byte(GetConstant), 0, 0, + byte(IntLess), + byte(JumpIfFalse), 0, 14, + // then return n + byte(GetLocal), 0, 0, + byte(ReturnValue), + // fib(n - 1) + byte(GetLocal), 0, 0, + byte(GetConstant), 0, 1, + byte(IntSubtract), + byte(Transfer), 0, 0, + byte(GetGlobal), 0, 0, + byte(Invoke), 0, 0, + // fib(n - 2) + byte(GetLocal), 0, 0, + byte(GetConstant), 0, 0, + byte(IntSubtract), + byte(Transfer), 0, 0, + byte(GetGlobal), 0, 0, + byte(Invoke), 0, 0, + // return sum + byte(IntAdd), + byte(ReturnValue), + } + + const expected = `GetLocal 0 +GetConstant 0 +IntLess +JumpIfFalse 14 +GetLocal 0 +ReturnValue +GetLocal 0 +GetConstant 1 +IntSubtract +Transfer 0 +GetGlobal 0 +Invoke typeParamCount:0 typeParams:[] +GetLocal 0 +GetConstant 0 +IntSubtract +Transfer 0 +GetGlobal 0 +Invoke typeParamCount:0 typeParams:[] +IntAdd +ReturnValue +` + + var builder strings.Builder + reader := bytes.NewReader(code) + err := PrintInstructions(&builder, reader) + require.NoError(t, err) + + assert.Equal(t, expected, builder.String()) +} + +func TestPrintInstruction(t *testing.T) { + t.Parallel() + + instructions := map[string][]byte{ + "GetConstant 258": {byte(GetConstant), 1, 2}, + "GetLocal 258": {byte(GetLocal), 1, 2}, + "SetLocal 258": {byte(SetLocal), 1, 2}, + "GetGlobal 258": {byte(GetGlobal), 1, 2}, + "SetGlobal 258": {byte(SetGlobal), 1, 2}, + "Jump 258": {byte(Jump), 1, 2}, + "JumpIfFalse 258": {byte(JumpIfFalse), 1, 2}, + "Transfer 258": {byte(Transfer), 1, 2}, + + "New kind:258 typeIndex:772": {byte(New), 1, 2, 3, 4}, + + "Cast typeIndex:258 castKind:3": {byte(Cast), 1, 2, 3}, + + `Path domain:1 identifier:"hello"`: {byte(Path), 1, 0, 5, 'h', 'e', 'l', 'l', 'o'}, + + `InvokeDynamic funcName:"abc" typeParamCount:2 typeParams:[772, 1286] argsCount:1800`: { + byte(InvokeDynamic), 0, 3, 'a', 'b', 'c', 0, 2, 3, 4, 5, 6, 7, 8, + }, + + "Invoke typeParamCount:2 typeParams:[772, 1286]": { + byte(Invoke), 0, 2, 3, 4, 5, 6, + }, + + "Unknown": {byte(Unknown)}, + "Return": {byte(Return)}, + "ReturnValue": {byte(ReturnValue)}, + "IntAdd": {byte(IntAdd)}, + "IntSubtract": {byte(IntSubtract)}, + "IntMultiply": {byte(IntMultiply)}, + "IntDivide": {byte(IntDivide)}, + "IntMod": {byte(IntMod)}, + "IntLess": {byte(IntLess)}, + "IntGreater": {byte(IntGreater)}, + "IntLessOrEqual": {byte(IntLessOrEqual)}, + "IntGreaterOrEqual": {byte(IntGreaterOrEqual)}, + "Equal": {byte(Equal)}, + "NotEqual": {byte(NotEqual)}, + "Unwrap": {byte(Unwrap)}, + "Destroy": {byte(Destroy)}, + "True": {byte(True)}, + "False": {byte(False)}, + "Nil": {byte(Nil)}, + "NewArray": {byte(NewArray)}, + "NewDictionary": {byte(NewDictionary)}, + "NewRef": {byte(NewRef)}, + "GetField": {byte(GetField)}, + "SetField": {byte(SetField)}, + "SetIndex": {byte(SetIndex)}, + "GetIndex": {byte(GetIndex)}, + "Drop": {byte(Drop)}, + "Dup": {byte(Dup)}, + } + + for expected, instruction := range instructions { + t.Run(expected, func(t *testing.T) { + + var builder strings.Builder + reader := bytes.NewReader(instruction) + err := PrintInstruction(&builder, reader) + require.NoError(t, err) + assert.Equal(t, 0, reader.Len()) + assert.Equal(t, expected, builder.String()) + }) + } +} From c01300eba7858dfeebcee49cf7911e6ad991f5b4 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 6 Dec 2024 12:22:10 -0800 Subject: [PATCH 72/89] Lazily load fields for simple composite values --- bbq/vm/value_account.go | 26 +++++++++++++++++++++----- bbq/vm/value_account_capabilities.go | 20 +++++++++++++++++--- bbq/vm/value_account_storage.go | 1 - bbq/vm/value_capability.go | 1 - bbq/vm/value_capability_controller.go | 3 --- bbq/vm/value_conversions.go | 1 - bbq/vm/value_simple_composite.go | 11 +++++++++-- bbq/vm/value_storage_reference.go | 5 +---- 8 files changed, 48 insertions(+), 20 deletions(-) diff --git a/bbq/vm/value_account.go b/bbq/vm/value_account.go index b68b20afbf..781892bb9b 100644 --- a/bbq/vm/value_account.go +++ b/bbq/vm/value_account.go @@ -69,17 +69,33 @@ func newAccountReferenceValue( func newAccountValue( address common.Address, ) *SimpleCompositeValue { - return &SimpleCompositeValue{ + value := &SimpleCompositeValue{ typeID: sema.AccountType.ID(), staticType: interpreter.PrimitiveStaticTypeAccount, Kind: common.CompositeKindStructure, fields: map[string]Value{ - sema.AccountTypeAddressFieldName: AddressValue(address), - sema.AccountTypeStorageFieldName: NewAccountStorageValue(address), - sema.AccountTypeCapabilitiesFieldName: NewAccountCapabilitiesValue(address), - // TODO: add the remaining fields + sema.AccountTypeAddressFieldName: AddressValue(address), }, } + + value.computeField = func(name string) Value { + var field Value + switch name { + case sema.AccountTypeStorageFieldName: + field = NewAccountStorageValue(address) + case sema.AccountTypeCapabilitiesFieldName: + field = NewAccountCapabilitiesValue(address) + default: + return nil + } + + value.fields[name] = field + return field + } + + // TODO: add the remaining fields + + return value } // members diff --git a/bbq/vm/value_account_capabilities.go b/bbq/vm/value_account_capabilities.go index dda4c6305b..7cac4796ea 100644 --- a/bbq/vm/value_account_capabilities.go +++ b/bbq/vm/value_account_capabilities.go @@ -28,18 +28,32 @@ import ( ) func NewAccountCapabilitiesValue(accountAddress common.Address) *SimpleCompositeValue { - return &SimpleCompositeValue{ + value := &SimpleCompositeValue{ typeID: sema.Account_StorageType.ID(), staticType: interpreter.PrimitiveStaticTypeAccount_Capabilities, Kind: common.CompositeKindStructure, - fields: map[string]Value{ - sema.Account_CapabilitiesTypeStorageFieldName: NewAccountStorageCapabilitiesValue(accountAddress), + fields: map[string]Value{ // TODO: add the remaining fields }, metadata: map[string]any{ sema.AccountTypeAddressFieldName: accountAddress, }, } + + value.computeField = func(name string) Value { + var field Value + switch name { + case sema.Account_CapabilitiesTypeStorageFieldName: + field = NewAccountStorageCapabilitiesValue(accountAddress) + default: + return nil + } + + value.fields[name] = field + return field + } + + return value } // members diff --git a/bbq/vm/value_account_storage.go b/bbq/vm/value_account_storage.go index 19879f74c3..18f29180d2 100644 --- a/bbq/vm/value_account_storage.go +++ b/bbq/vm/value_account_storage.go @@ -117,7 +117,6 @@ func init() { } reference := NewStorageReferenceValue( - config.Storage, referenceType.Authorization, address, path, diff --git a/bbq/vm/value_capability.go b/bbq/vm/value_capability.go index 3af4056e97..a7db209383 100644 --- a/bbq/vm/value_capability.go +++ b/bbq/vm/value_capability.go @@ -158,7 +158,6 @@ func GetCheckedCapabilityControllerReference( capabilityAddress := common.Address(capabilityAddressValue) return controller.ReferenceValue( - config, capabilityAddress, resultBorrowType, ) diff --git a/bbq/vm/value_capability_controller.go b/bbq/vm/value_capability_controller.go index db324a027e..deda75f747 100644 --- a/bbq/vm/value_capability_controller.go +++ b/bbq/vm/value_capability_controller.go @@ -31,7 +31,6 @@ type CapabilityControllerValue interface { Value isCapabilityControllerValue() ReferenceValue( - conf *Config, capabilityAddress common.Address, resultBorrowType *interpreter.ReferenceStaticType, ) ReferenceValue @@ -200,12 +199,10 @@ func (v *StorageCapabilityControllerValue) ControllerCapabilityID() IntValue { } func (v *StorageCapabilityControllerValue) ReferenceValue( - conf *Config, capabilityAddress common.Address, resultBorrowType *interpreter.ReferenceStaticType, ) ReferenceValue { return NewStorageReferenceValue( - conf.Storage, resultBorrowType.Authorization, capabilityAddress, v.TargetPath, diff --git a/bbq/vm/value_conversions.go b/bbq/vm/value_conversions.go index adee75bc57..175afb6d41 100644 --- a/bbq/vm/value_conversions.go +++ b/bbq/vm/value_conversions.go @@ -85,7 +85,6 @@ func InterpreterValueToVMValue(storage interpreter.Storage, value interpreter.Va ) case *interpreter.StorageReferenceValue: return NewStorageReferenceValue( - storage, value.Authorization, value.TargetStorageAddress, InterpreterValueToVMValue(storage, value.TargetPath).(PathValue), diff --git a/bbq/vm/value_simple_composite.go b/bbq/vm/value_simple_composite.go index a4aa06cadd..ad1803546a 100644 --- a/bbq/vm/value_simple_composite.go +++ b/bbq/vm/value_simple_composite.go @@ -24,11 +24,13 @@ import ( ) type SimpleCompositeValue struct { - fields map[string]Value typeID common.TypeID staticType StaticType Kind common.CompositeKind + fields map[string]Value + computeField func(name string) Value + // metadata is a property bag to carry internal data // that are not visible to cadence users. // TODO: any better way to pass down information? @@ -57,7 +59,12 @@ func (v *SimpleCompositeValue) StaticType(*Config) StaticType { } func (v *SimpleCompositeValue) GetMember(_ *Config, name string) Value { - return v.fields[name] + value, ok := v.fields[name] + if ok { + return value + } + + return v.computeField(name) } func (v *SimpleCompositeValue) SetMember(_ *Config, name string, value Value) { diff --git a/bbq/vm/value_storage_reference.go b/bbq/vm/value_storage_reference.go index 8f4e72f127..38158f2345 100644 --- a/bbq/vm/value_storage_reference.go +++ b/bbq/vm/value_storage_reference.go @@ -31,7 +31,6 @@ type StorageReferenceValue struct { TargetStorageAddress common.Address TargetPath PathValue BorrowedType interpreter.StaticType - storage interpreter.Storage } var _ Value = &StorageReferenceValue{} @@ -39,7 +38,6 @@ var _ MemberAccessibleValue = &StorageReferenceValue{} var _ ReferenceValue = &StorageReferenceValue{} func NewStorageReferenceValue( - storage interpreter.Storage, authorization interpreter.Authorization, targetStorageAddress common.Address, targetPath PathValue, @@ -50,7 +48,6 @@ func NewStorageReferenceValue( TargetStorageAddress: targetStorageAddress, TargetPath: targetPath, BorrowedType: borrowedType, - storage: storage, } } @@ -92,7 +89,7 @@ func (v *StorageReferenceValue) dereference(config *Config) (*Value, error) { domain := v.TargetPath.Domain.Identifier() identifier := v.TargetPath.Identifier - vmReferencedValue := ReadStored(memoryGauge, v.storage, address, domain, identifier) + vmReferencedValue := ReadStored(memoryGauge, config.Storage, address, domain, identifier) if vmReferencedValue == nil { return nil, nil } From 75bd3cfb516ad4662c9fc4ab59440c75463d3369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 11 Dec 2024 09:11:00 -0800 Subject: [PATCH 73/89] refactor instruction encoding --- bbq/commons/constants.go | 26 --- bbq/compiler/codegen.go | 242 +++++++++++++++++++++++++++ bbq/compiler/compiler.go | 300 ++++++++++++++++------------------ bbq/compiler/compiler_test.go | 8 +- bbq/compiler/function.go | 16 +- bbq/opcode/castkind.go | 27 +++ bbq/opcode/emit.go | 255 +++++++++++++++++++++++++++++ bbq/vm/vm.go | 3 +- 8 files changed, 673 insertions(+), 204 deletions(-) create mode 100644 bbq/compiler/codegen.go create mode 100644 bbq/opcode/castkind.go create mode 100644 bbq/opcode/emit.go diff --git a/bbq/commons/constants.go b/bbq/commons/constants.go index 1891cc54c2..3569377da1 100644 --- a/bbq/commons/constants.go +++ b/bbq/commons/constants.go @@ -18,11 +18,6 @@ package commons -import ( - "github.com/onflow/cadence/ast" - "github.com/onflow/cadence/errors" -) - const ( InitFunctionName = "init" TransactionWrapperCompositeName = "transaction" @@ -37,24 +32,3 @@ const ( ProgramInitFunctionName = "$_init_" TransactionGeneratedParamPrefix = "$_param_" ) - -type CastKind byte - -const ( - SimpleCast CastKind = iota - FailableCast - ForceCast -) - -func CastKindFrom(operation ast.Operation) CastKind { - switch operation { - case ast.OperationCast: - return SimpleCast - case ast.OperationFailableCast: - return FailableCast - case ast.OperationForceCast: - return ForceCast - default: - panic(errors.NewUnreachableError()) - } -} diff --git a/bbq/compiler/codegen.go b/bbq/compiler/codegen.go new file mode 100644 index 0000000000..960a54ae8e --- /dev/null +++ b/bbq/compiler/codegen.go @@ -0,0 +1,242 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package compiler + +import ( + "github.com/onflow/cadence/bbq/opcode" + "github.com/onflow/cadence/common" +) + +type CodeGen interface { + Offset() int + Code() interface{} + EmitNil() + EmitTrue() + EmitFalse() + EmitDup() + EmitDrop() + EmitGetConstant(index uint16) + EmitJump(target uint16) int + EmitJumpIfFalse(target uint16) int + PatchJump(offset int, newTarget uint16) + EmitReturnValue() + EmitReturn() + EmitGetLocal(index uint16) + EmitSetLocal(index uint16) + EmitGetGlobal(index uint16) + EmitSetGlobal(index uint16) + EmitGetField() + EmitSetField() + EmitGetIndex() + EmitSetIndex() + EmitNewArray(index uint16, size uint16, isResource bool) + EmitIntAdd() + EmitIntSubtract() + EmitIntMultiply() + EmitIntDivide() + EmitIntMod() + EmitEqual() + EmitNotEqual() + EmitIntLess() + EmitIntLessOrEqual() + EmitIntGreater() + EmitIntGreaterOrEqual() + EmitUnwrap() + EmitCast(index uint16, kind opcode.CastKind) + EmitDestroy() + EmitTransfer(index uint16) + EmitNewRef(index uint16) + EmitPath(domain common.PathDomain, identifier string) + EmitNew(kind uint16, index uint16) + EmitInvoke(typeArgs []uint16) + EmitInvokeDynamic(name string, typeArgs []uint16, argCount uint16) +} + +type BytecodeGen struct { + code []byte +} + +var _ CodeGen = &BytecodeGen{} + +func (g *BytecodeGen) Offset() int { + return len(g.code) +} + +func (g *BytecodeGen) Code() interface{} { + return g.code +} + +func (g *BytecodeGen) EmitNil() { + opcode.EmitNil(&g.code) +} + +func (g *BytecodeGen) EmitTrue() { + opcode.EmitTrue(&g.code) +} + +func (g *BytecodeGen) EmitFalse() { + opcode.EmitFalse(&g.code) +} + +func (g *BytecodeGen) EmitDup() { + opcode.EmitDup(&g.code) +} + +func (g *BytecodeGen) EmitDrop() { + opcode.EmitDrop(&g.code) +} + +func (g *BytecodeGen) EmitGetConstant(index uint16) { + opcode.EmitGetConstant(&g.code, index) +} + +func (g *BytecodeGen) EmitJump(target uint16) int { + return opcode.EmitJump(&g.code, target) +} + +func (g *BytecodeGen) EmitJumpIfFalse(target uint16) int { + return opcode.EmitJumpIfFalse(&g.code, target) +} + +func (g *BytecodeGen) PatchJump(offset int, newTarget uint16) { + opcode.PatchJump(&g.code, offset, newTarget) +} + +func (g *BytecodeGen) EmitReturnValue() { + opcode.EmitReturnValue(&g.code) +} + +func (g *BytecodeGen) EmitReturn() { + opcode.EmitReturn(&g.code) +} + +func (g *BytecodeGen) EmitGetLocal(index uint16) { + opcode.EmitGetLocal(&g.code, index) +} +func (g *BytecodeGen) EmitSetLocal(index uint16) { + opcode.EmitSetLocal(&g.code, index) +} + +func (g *BytecodeGen) EmitGetGlobal(index uint16) { + opcode.EmitGetGlobal(&g.code, index) +} + +func (g *BytecodeGen) EmitSetGlobal(index uint16) { + opcode.EmitSetGlobal(&g.code, index) +} + +func (g *BytecodeGen) EmitGetField() { + opcode.EmitGetField(&g.code) +} + +func (g *BytecodeGen) EmitSetField() { + opcode.EmitSetField(&g.code) +} + +func (g *BytecodeGen) EmitGetIndex() { + opcode.EmitGetIndex(&g.code) +} + +func (g *BytecodeGen) EmitSetIndex() { + opcode.EmitSetIndex(&g.code) +} + +func (g *BytecodeGen) EmitNewArray(index uint16, size uint16, isResource bool) { + opcode.EmitNewArray(&g.code, index, size, isResource) +} + +func (g *BytecodeGen) EmitIntAdd() { + opcode.EmitIntAdd(&g.code) +} + +func (g *BytecodeGen) EmitIntSubtract() { + opcode.EmitIntSubtract(&g.code) +} + +func (g *BytecodeGen) EmitIntMultiply() { + opcode.EmitIntMultiply(&g.code) +} + +func (g *BytecodeGen) EmitIntDivide() { + opcode.EmitIntDivide(&g.code) +} + +func (g *BytecodeGen) EmitIntMod() { + opcode.EmitIntMod(&g.code) +} + +func (g *BytecodeGen) EmitEqual() { + opcode.EmitEqual(&g.code) +} + +func (g *BytecodeGen) EmitNotEqual() { + opcode.EmitNotEqual(&g.code) +} + +func (g *BytecodeGen) EmitIntLess() { + opcode.EmitIntLess(&g.code) +} + +func (g *BytecodeGen) EmitIntLessOrEqual() { + opcode.EmitIntLessOrEqual(&g.code) +} + +func (g *BytecodeGen) EmitIntGreater() { + opcode.EmitIntGreater(&g.code) +} + +func (g *BytecodeGen) EmitIntGreaterOrEqual() { + opcode.EmitIntGreaterOrEqual(&g.code) +} + +func (g *BytecodeGen) EmitUnwrap() { + opcode.EmitUnwrap(&g.code) +} + +func (g *BytecodeGen) EmitCast(index uint16, kind opcode.CastKind) { + opcode.EmitCast(&g.code, index, kind) +} + +func (g *BytecodeGen) EmitDestroy() { + opcode.EmitDestroy(&g.code) +} + +func (g *BytecodeGen) EmitTransfer(index uint16) { + opcode.EmitTransfer(&g.code, index) +} + +func (g *BytecodeGen) EmitNewRef(index uint16) { + opcode.EmitNewRef(&g.code, index) +} + +func (g *BytecodeGen) EmitPath(domain common.PathDomain, identifier string) { + opcode.EmitPath(&g.code, domain, identifier) +} + +func (g *BytecodeGen) EmitNew(kind uint16, index uint16) { + opcode.EmitNew(&g.code, kind, index) +} + +func (g *BytecodeGen) EmitInvoke(typeArgs []uint16) { + opcode.EmitInvoke(&g.code, typeArgs) +} + +func (g *BytecodeGen) EmitInvokeDynamic(name string, typeArgs []uint16, argCount uint16) { + opcode.EmitInvokeDynamic(&g.code, name, typeArgs, argCount) +} diff --git a/bbq/compiler/compiler.go b/bbq/compiler/compiler.go index 366862ec50..7ff7f2dc9b 100644 --- a/bbq/compiler/compiler.go +++ b/bbq/compiler/compiler.go @@ -23,16 +23,15 @@ import ( "strings" "github.com/onflow/cadence/ast" - "github.com/onflow/cadence/common" - "github.com/onflow/cadence/errors" - "github.com/onflow/cadence/interpreter" - "github.com/onflow/cadence/sema" - "github.com/onflow/cadence/bbq" "github.com/onflow/cadence/bbq/commons" "github.com/onflow/cadence/bbq/constantkind" "github.com/onflow/cadence/bbq/leb128" "github.com/onflow/cadence/bbq/opcode" + "github.com/onflow/cadence/common" + "github.com/onflow/cadence/errors" + "github.com/onflow/cadence/interpreter" + "github.com/onflow/cadence/sema" ) type Compiler struct { @@ -219,45 +218,40 @@ func (c *Compiler) addConstant(kind constantkind.ConstantKind, data []byte) *con func (c *Compiler) stringConstLoad(str string) { constant := c.addConstant(constantkind.String, []byte(str)) - first, second := encodeUint16(constant.index) - c.emit(opcode.GetConstant, first, second) + c.currentFunction.codeGen.EmitGetConstant(constant.index) } -func (c *Compiler) emit(opcode opcode.Opcode, args ...byte) int { - return c.currentFunction.emit(opcode, args...) +func (c *Compiler) emitJump(target int) int { + if target >= math.MaxUint16 { + panic(errors.NewDefaultUserError("invalid jump")) + } + return c.currentFunction.codeGen.EmitJump(uint16(target)) } -func (c *Compiler) emitUndefinedJump(opcode opcode.Opcode) int { - return c.emit(opcode, 0xff, 0xff) +func (c *Compiler) emitUndefinedJump() int { + return c.currentFunction.codeGen.EmitJump(math.MaxUint16) } -func (c *Compiler) emitJump(opcode opcode.Opcode, target int) int { +func (c *Compiler) emitJumpIfFalse(target uint16) int { if target >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid jump")) } - first, second := encodeUint16(uint16(target)) - return c.emit(opcode, first, second) + return c.currentFunction.codeGen.EmitJumpIfFalse(target) +} + +func (c *Compiler) emitUndefinedJumpIfFalse() int { + return c.currentFunction.codeGen.EmitJumpIfFalse(math.MaxUint16) } func (c *Compiler) patchJump(opcodeOffset int) { - code := c.currentFunction.code - count := len(code) + count := c.currentFunction.codeGen.Offset() if count == 0 { panic(errors.NewUnreachableError()) } if count >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid jump")) } - target := uint16(count) - first, second := encodeUint16(target) - code[opcodeOffset+1] = first - code[opcodeOffset+2] = second -} - -// encodeUint16 encodes the given uint16 in big-endian representation -func encodeUint16(jump uint16) (byte, byte) { - return byte((jump >> 8) & 0xff), - byte(jump & 0xff) + c.currentFunction.codeGen.PatchJump(opcodeOffset, uint16(count)) } func (c *Compiler) pushLoop(start int) { @@ -434,7 +428,7 @@ func (c *Compiler) exportFunctions() []*bbq.Function { functions, &bbq.Function{ Name: function.name, - Code: function.code, + Code: function.codeGen.Code().([]byte), LocalCount: function.localCount, ParameterCount: function.parameterCount, IsCompositeFunction: function.isCompositeFunction, @@ -518,22 +512,22 @@ func (c *Compiler) VisitReturnStatement(statement *ast.ReturnStatement) (_ struc if expression != nil { // TODO: copy c.compileExpression(expression) - c.emit(opcode.ReturnValue) + c.currentFunction.codeGen.EmitReturnValue() } else { - c.emit(opcode.Return) + c.currentFunction.codeGen.EmitReturn() } return } func (c *Compiler) VisitBreakStatement(_ *ast.BreakStatement) (_ struct{}) { - offset := len(c.currentFunction.code) + offset := c.currentFunction.codeGen.Offset() c.currentLoop.breaks = append(c.currentLoop.breaks, offset) - c.emitUndefinedJump(opcode.Jump) + c.emitUndefinedJump() return } func (c *Compiler) VisitContinueStatement(_ *ast.ContinueStatement) (_ struct{}) { - c.emitJump(opcode.Jump, c.currentLoop.start) + c.emitJump(c.currentLoop.start) return } @@ -546,11 +540,11 @@ func (c *Compiler) VisitIfStatement(statement *ast.IfStatement) (_ struct{}) { // TODO: panic(errors.NewUnreachableError()) } - elseJump := c.emitUndefinedJump(opcode.JumpIfFalse) + elseJump := c.emitUndefinedJumpIfFalse() c.compileBlock(statement.Then) elseBlock := statement.Else if elseBlock != nil { - thenJump := c.emitUndefinedJump(opcode.Jump) + thenJump := c.emitUndefinedJump() c.patchJump(elseJump) c.compileBlock(elseBlock) c.patchJump(thenJump) @@ -561,12 +555,12 @@ func (c *Compiler) VisitIfStatement(statement *ast.IfStatement) (_ struct{}) { } func (c *Compiler) VisitWhileStatement(statement *ast.WhileStatement) (_ struct{}) { - testOffset := len(c.currentFunction.code) + testOffset := c.currentFunction.codeGen.Offset() c.pushLoop(testOffset) c.compileExpression(statement.Test) - endJump := c.emitUndefinedJump(opcode.JumpIfFalse) + endJump := c.emitUndefinedJumpIfFalse() c.compileBlock(statement.Block) - c.emitJump(opcode.Jump, testOffset) + c.emitJump(testOffset) c.patchJump(endJump) c.popLoop() return @@ -595,8 +589,7 @@ func (c *Compiler) VisitVariableDeclaration(declaration *ast.VariableDeclaration c.emitCheckType(varDeclTypes.TargetType) local := c.currentFunction.declareLocal(declaration.Identifier.Identifier) - first, second := encodeUint16(local.index) - c.emit(opcode.SetLocal, first, second) + c.currentFunction.codeGen.EmitSetLocal(local.index) return } @@ -611,22 +604,23 @@ func (c *Compiler) VisitAssignmentStatement(statement *ast.AssignmentStatement) varName := target.Identifier.Identifier local := c.currentFunction.findLocal(varName) if local != nil { - first, second := encodeUint16(local.index) - c.emit(opcode.SetLocal, first, second) + c.currentFunction.codeGen.EmitSetLocal(local.index) return } global := c.findGlobal(varName) - first, second := encodeUint16(global.index) - c.emit(opcode.SetGlobal, first, second) + c.currentFunction.codeGen.EmitSetGlobal(global.index) + case *ast.MemberExpression: c.compileExpression(target.Expression) c.stringConstLoad(target.Identifier.Identifier) - c.emit(opcode.SetField) + c.currentFunction.codeGen.EmitSetField() + case *ast.IndexExpression: c.compileExpression(target.TargetExpression) c.compileExpression(target.IndexingExpression) - c.emit(opcode.SetIndex) + c.currentFunction.codeGen.EmitSetIndex() + default: // TODO: panic(errors.NewUnreachableError()) @@ -647,7 +641,7 @@ func (c *Compiler) VisitExpressionStatement(statement *ast.ExpressionStatement) // Do nothing. Destroy operation will not produce any result. default: // Otherwise, drop the expression evaluation result. - c.emit(opcode.Drop) + c.currentFunction.codeGen.EmitDrop() } return @@ -660,15 +654,15 @@ func (c *Compiler) VisitVoidExpression(_ *ast.VoidExpression) (_ struct{}) { func (c *Compiler) VisitBoolExpression(expression *ast.BoolExpression) (_ struct{}) { if expression.Value { - c.emit(opcode.True) + c.currentFunction.codeGen.EmitTrue() } else { - c.emit(opcode.False) + c.currentFunction.codeGen.EmitFalse() } return } func (c *Compiler) VisitNilExpression(_ *ast.NilExpression) (_ struct{}) { - c.emit(opcode.Nil) + c.currentFunction.codeGen.EmitNil() return } @@ -681,8 +675,7 @@ func (c *Compiler) VisitIntegerExpression(expression *ast.IntegerExpression) (_ data = leb128.AppendInt64(data, expression.Value.Int64()) constant := c.addConstant(constantKind, data) - first, second := encodeUint16(constant.index) - c.emit(opcode.GetConstant, first, second) + c.currentFunction.codeGen.EmitGetConstant(constant.index) return } @@ -694,30 +687,23 @@ func (c *Compiler) VisitFixedPointExpression(_ *ast.FixedPointExpression) (_ str func (c *Compiler) VisitArrayExpression(array *ast.ArrayExpression) (_ struct{}) { arrayTypes := c.Elaboration.ArrayExpressionTypes(array) - var isResource byte - if arrayTypes.ArrayType.IsResourceType() { - isResource = 1 - } - typeIndex := c.getOrAddType(arrayTypes.ArrayType) - typeIndexFirst, typeIndexSecond := encodeUint16(typeIndex) - sizeFirst, sizeSecond := encodeUint16(uint16(len(array.Values))) + size := len(array.Values) + if size >= math.MaxUint16 { + panic(errors.NewDefaultUserError("invalid array expression")) + } for _, expression := range array.Values { - //c.emit(opcode.Dup) + //EmitDup() c.compileExpression(expression) - //first, second := encodeUint16(uint16(index)) - //c.emit(opcode.SetIndex, first, second) + //EmitSetIndex(index) } - c.emit( - opcode.NewArray, - typeIndexFirst, - typeIndexSecond, - sizeFirst, - sizeSecond, - isResource, + c.currentFunction.codeGen.EmitNewArray( + typeIndex, + uint16(size), + arrayTypes.ArrayType.IsResourceType(), ) return @@ -736,14 +722,12 @@ func (c *Compiler) VisitIdentifierExpression(expression *ast.IdentifierExpressio func (c *Compiler) emitVariableLoad(name string) { local := c.currentFunction.findLocal(name) if local != nil { - first, second := encodeUint16(local.index) - c.emit(opcode.GetLocal, first, second) + c.currentFunction.codeGen.EmitGetLocal(local.index) return } global := c.findGlobal(name) - first, second := encodeUint16(global.index) - c.emit(opcode.GetGlobal, first, second) + c.currentFunction.codeGen.EmitGetGlobal(global.index) } func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpression) (_ struct{}) { @@ -763,7 +747,8 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio c.emitVariableLoad(invokedExpr.Identifier.Identifier) typeArgs := c.loadTypeArguments(expression) - c.emit(opcode.Invoke, typeArgs...) + c.currentFunction.codeGen.EmitInvoke(typeArgs) + case *ast.MemberExpression: memberInfo, ok := c.Elaboration.MemberExpressionMemberAccessInfo(invokedExpr) if !ok { @@ -785,7 +770,7 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio c.emitVariableLoad(funcName) typeArgs := c.loadTypeArguments(expression) - c.emit(opcode.Invoke, typeArgs...) + c.currentFunction.codeGen.EmitInvoke(typeArgs) return } @@ -796,23 +781,26 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio if isInterfaceMethodInvocation(memberInfo.AccessedType) { funcName = invokedExpr.Identifier.Identifier - funcNameSizeFirst, funcNameSizeSecond := encodeUint16(uint16(len(funcName))) + if len(funcName) >= math.MaxUint16 { + panic(errors.NewDefaultUserError("invalid function name")) + } - argsCountFirst, argsCountSecond := encodeUint16(uint16(len(expression.Arguments))) + typeArgs := c.loadTypeArguments(expression) + + argumentCount := len(expression.Arguments) + if argumentCount >= math.MaxUint16 { + panic(errors.NewDefaultUserError("invalid number of arguments")) + } - args := []byte{funcNameSizeFirst, funcNameSizeSecond} - args = append(args, []byte(funcName)...) - args = append(args, c.loadTypeArguments(expression)...) - args = append(args, argsCountFirst, argsCountSecond) + c.currentFunction.codeGen.EmitInvokeDynamic(funcName, typeArgs, uint16(argumentCount)) - c.emit(opcode.InvokeDynamic, args...) } else { // Load function value funcName = commons.TypeQualifiedName(typeName, invokedExpr.Identifier.Identifier) c.emitVariableLoad(funcName) typeArgs := c.loadTypeArguments(expression) - c.emit(opcode.Invoke, typeArgs...) + c.currentFunction.codeGen.EmitInvoke(typeArgs) } default: panic(errors.NewUnreachableError()) @@ -860,29 +848,22 @@ func (c *Compiler) loadArguments(expression *ast.InvocationExpression) { //} } -func (c *Compiler) loadTypeArguments(expression *ast.InvocationExpression) []byte { +func (c *Compiler) loadTypeArguments(expression *ast.InvocationExpression) []uint16 { invocationTypes := c.Elaboration.InvocationExpressionTypes(expression) - //if len(expression.TypeArguments) == 0 { - // first, second := encodeUint16(0) - // typeArgs = append(typeArgs, first, second) - // return typeArgs - //} - typeArgsCount := invocationTypes.TypeArguments.Len() if typeArgsCount >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid number of type arguments: %d", typeArgsCount)) } - var typeArgs []byte + if typeArgsCount == 0 { + return nil + } - first, second := encodeUint16(uint16(typeArgsCount)) - typeArgs = append(typeArgs, first, second) + typeArgs := make([]uint16, 0, typeArgsCount) invocationTypes.TypeArguments.Foreach(func(key *sema.TypeParameter, typeParam sema.Type) { - index := c.getOrAddType(typeParam) - first, second := encodeUint16(index) - typeArgs = append(typeArgs, first, second) + typeArgs = append(typeArgs, c.getOrAddType(typeParam)) }) return typeArgs @@ -891,14 +872,14 @@ func (c *Compiler) loadTypeArguments(expression *ast.InvocationExpression) []byt func (c *Compiler) VisitMemberExpression(expression *ast.MemberExpression) (_ struct{}) { c.compileExpression(expression.Expression) c.stringConstLoad(expression.Identifier.Identifier) - c.emit(opcode.GetField) + c.currentFunction.codeGen.EmitGetField() return } func (c *Compiler) VisitIndexExpression(expression *ast.IndexExpression) (_ struct{}) { c.compileExpression(expression.TargetExpression) c.compileExpression(expression.IndexingExpression) - c.emit(opcode.GetIndex) + c.currentFunction.codeGen.EmitGetIndex() return } @@ -923,48 +904,62 @@ func (c *Compiler) VisitBinaryExpression(expression *ast.BinaryExpression) (_ st c.compileExpression(expression.Left) // TODO: add support for other types + codeGen := c.currentFunction.codeGen + switch expression.Operation { case ast.OperationNilCoalesce: // create a duplicate to perform the equal check. // So if the condition succeeds, then the condition's result will be at the top of the stack. - c.emit(opcode.Dup) + codeGen.EmitDup() - c.emit(opcode.Nil) - c.emit(opcode.Equal) - elseJump := c.emitUndefinedJump(opcode.JumpIfFalse) + codeGen.EmitNil() + codeGen.EmitEqual() + elseJump := c.emitUndefinedJumpIfFalse() // Drop the duplicated condition result. // It is not needed for the 'then' path. - c.emit(opcode.Drop) + codeGen.EmitDrop() c.compileExpression(expression.Right) - thenJump := c.emitUndefinedJump(opcode.Jump) + thenJump := c.emitUndefinedJump() c.patchJump(elseJump) - c.emit(opcode.Unwrap) + codeGen.EmitUnwrap() c.patchJump(thenJump) default: c.compileExpression(expression.Right) - c.emit(intBinaryOpcodes[expression.Operation]) + + switch expression.Operation { + case ast.OperationPlus: + codeGen.EmitIntAdd() + case ast.OperationMinus: + codeGen.EmitIntSubtract() + case ast.OperationMul: + codeGen.EmitIntMultiply() + case ast.OperationDiv: + codeGen.EmitIntDivide() + case ast.OperationMod: + codeGen.EmitIntMod() + case ast.OperationEqual: + codeGen.EmitEqual() + case ast.OperationNotEqual: + codeGen.EmitNotEqual() + case ast.OperationLess: + codeGen.EmitIntLess() + case ast.OperationLessEqual: + codeGen.EmitIntLessOrEqual() + case ast.OperationGreater: + codeGen.EmitIntGreater() + case ast.OperationGreaterEqual: + codeGen.EmitIntGreaterOrEqual() + default: + panic(errors.NewUnreachableError()) + } } return } -var intBinaryOpcodes = [...]opcode.Opcode{ - ast.OperationPlus: opcode.IntAdd, - ast.OperationMinus: opcode.IntSubtract, - ast.OperationMul: opcode.IntMultiply, - ast.OperationDiv: opcode.IntDivide, - ast.OperationMod: opcode.IntMod, - ast.OperationEqual: opcode.Equal, - ast.OperationNotEqual: opcode.NotEqual, - ast.OperationLess: opcode.IntLess, - ast.OperationLessEqual: opcode.IntLessOrEqual, - ast.OperationGreater: opcode.IntGreater, - ast.OperationGreaterEqual: opcode.IntGreaterOrEqual, -} - func (c *Compiler) VisitFunctionExpression(_ *ast.FunctionExpression) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) @@ -985,11 +980,10 @@ func (c *Compiler) VisitCastingExpression(expression *ast.CastingExpression) (_ castingTypes := c.Elaboration.CastingExpressionTypes(expression) index := c.getOrAddType(castingTypes.TargetType) - first, second := encodeUint16(index) - castKind := commons.CastKindFrom(expression.Operation) + castKind := opcode.CastKindFrom(expression.Operation) - c.emit(opcode.Cast, first, second, byte(castKind)) + c.currentFunction.codeGen.EmitCast(index, castKind) return } @@ -1000,7 +994,7 @@ func (c *Compiler) VisitCreateExpression(expression *ast.CreateExpression) (_ st func (c *Compiler) VisitDestroyExpression(expression *ast.DestroyExpression) (_ struct{}) { c.compileExpression(expression.Expression) - c.emit(opcode.Destroy) + c.currentFunction.codeGen.EmitDestroy() return } @@ -1008,8 +1002,7 @@ func (c *Compiler) VisitReferenceExpression(expression *ast.ReferenceExpression) c.compileExpression(expression.Expression) borrowType := c.Elaboration.ReferenceExpressionBorrowType(expression) index := c.getOrAddType(borrowType) - typeIndexFirst, typeIndexSecond := encodeUint16(index) - c.emit(opcode.NewRef, typeIndexFirst, typeIndexSecond) + c.currentFunction.codeGen.EmitNewRef(index) return } @@ -1019,23 +1012,12 @@ func (c *Compiler) VisitForceExpression(_ *ast.ForceExpression) (_ struct{}) { } func (c *Compiler) VisitPathExpression(expression *ast.PathExpression) (_ struct{}) { + domain := common.PathDomainFromIdentifier(expression.Domain.Identifier) identifier := expression.Identifier.Identifier - - byteSize := 1 + // one byte for path domain - 2 + // 2 bytes for identifier size - len(identifier) // identifier - - args := make([]byte, 0, byteSize) - - domainByte := byte(common.PathDomainFromIdentifier(expression.Domain.Identifier)) - args = append(args, domainByte) - - identifierSizeFirst, identifierSizeSecond := encodeUint16(uint16(len(identifier))) - args = append(args, identifierSizeFirst, identifierSizeSecond) - args = append(args, identifier...) - - c.emit(opcode.Path, args...) - + if len(identifier) >= math.MaxUint16 { + panic(errors.NewDefaultUserError("invalid identifier")) + } + c.currentFunction.codeGen.EmitPath(domain, identifier) return } @@ -1084,25 +1066,21 @@ func (c *Compiler) compileInitializer(declaration *ast.SpecialFunctionDeclaratio // Declare `self` self := c.currentFunction.declareLocal(sema.SelfIdentifier) - selfFirst, selfSecond := encodeUint16(self.index) // Initialize an empty struct and assign to `self`. // i.e: `self = New()` enclosingCompositeType := c.compositeTypeStack.top() + codeGen := c.currentFunction.codeGen + // Write composite kind // TODO: Maybe get/include this from static-type. Then no need to provide separately. - kindFirst, kindSecond := encodeUint16(uint16(enclosingCompositeType.Kind)) + kind := uint16(enclosingCompositeType.Kind) - index := c.getOrAddType(enclosingCompositeType) - typeFirst, typeSecond := encodeUint16(index) + typeIndex := c.getOrAddType(enclosingCompositeType) - c.emit( - opcode.New, - kindFirst, kindSecond, - typeFirst, typeSecond, - ) + c.currentFunction.codeGen.EmitNew(kind, typeIndex) if enclosingType.Kind == common.CompositeKindContract { // During contract init, update the global variable with the newly initialized contract value. @@ -1115,20 +1093,20 @@ func (c *Compiler) compileInitializer(declaration *ast.SpecialFunctionDeclaratio // } // Duplicate the top of stack and store it in both global variable and in `self` - c.emit(opcode.Dup) + codeGen.EmitDup() global := c.findGlobal(enclosingCompositeTypeName) - first, second := encodeUint16(global.index) - c.emit(opcode.SetGlobal, first, second) + + codeGen.EmitSetGlobal(global.index) } - c.emit(opcode.SetLocal, selfFirst, selfSecond) + codeGen.EmitSetLocal(self.index) - // Emit for the statements in `init()` body. + // emit for the statements in `init()` body. c.compileFunctionBlock(declaration.FunctionDeclaration.FunctionBlock) // Constructor should return the created the struct. i.e: return `self` - c.emit(opcode.GetLocal, selfFirst, selfSecond) - c.emit(opcode.ReturnValue) + codeGen.EmitGetLocal(self.index) + codeGen.EmitReturnValue() } func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration) (_ struct{}) { @@ -1141,12 +1119,12 @@ func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration // Manually emit a return, if there are no explicit return statements. if !declaration.FunctionBlock.HasStatements() { - c.emit(opcode.Return) + c.currentFunction.codeGen.EmitReturn() } else { statements := declaration.FunctionBlock.Block.Statements lastStmt := statements[len(statements)-1] if _, isReturn := lastStmt.(*ast.ReturnStatement); !isReturn { - c.emit(opcode.Return) + c.currentFunction.codeGen.EmitReturn() } } @@ -1279,8 +1257,8 @@ func (c *Compiler) patchLoop(l *loop) { func (c *Compiler) emitCheckType(targetType sema.Type) { index := c.getOrAddType(targetType) - first, second := encodeUint16(index) - c.emit(opcode.Transfer, first, second) + + c.currentFunction.codeGen.EmitTransfer(index) } func (c *Compiler) getOrAddType(targetType sema.Type) uint16 { diff --git a/bbq/compiler/compiler_test.go b/bbq/compiler/compiler_test.go index 1b03948467..3f29cc75ef 100644 --- a/bbq/compiler/compiler_test.go +++ b/bbq/compiler/compiler_test.go @@ -75,7 +75,7 @@ func TestCompileRecursionFib(t *testing.T) { byte(opcode.IntAdd), byte(opcode.ReturnValue), }, - compiler.functions[0].code, + compiler.functions[0].codeGen.Code(), ) require.Equal(t, @@ -167,7 +167,7 @@ func TestCompileImperativeFib(t *testing.T) { byte(opcode.GetLocal), 0, 3, byte(opcode.ReturnValue), }, - compiler.functions[0].code, + compiler.functions[0].codeGen.Code(), ) require.Equal(t, @@ -235,7 +235,7 @@ func TestCompileBreak(t *testing.T) { byte(opcode.GetLocal), 0, 0, byte(opcode.ReturnValue), }, - compiler.functions[0].code, + compiler.functions[0].codeGen.Code(), ) require.Equal(t, @@ -310,7 +310,7 @@ func TestCompileContinue(t *testing.T) { byte(opcode.GetLocal), 0, 0, byte(opcode.ReturnValue), }, - compiler.functions[0].code, + compiler.functions[0].codeGen.Code(), ) require.Equal(t, diff --git a/bbq/compiler/function.go b/bbq/compiler/function.go index 75038f7ae8..1d43731c7c 100644 --- a/bbq/compiler/function.go +++ b/bbq/compiler/function.go @@ -22,15 +22,13 @@ import ( "math" "github.com/onflow/cadence/activations" - "github.com/onflow/cadence/bbq/opcode" "github.com/onflow/cadence/errors" ) type function struct { - name string - localCount uint16 - // TODO: use byte.Buffer? - code []byte + name string + localCount uint16 + codeGen CodeGen locals *activations.Activations[*local] parameterCount uint16 isCompositeFunction bool @@ -40,18 +38,12 @@ func newFunction(name string, parameterCount uint16, isCompositeFunction bool) * return &function{ name: name, parameterCount: parameterCount, + codeGen: &BytecodeGen{}, locals: activations.NewActivations[*local](nil), isCompositeFunction: isCompositeFunction, } } -func (f *function) emit(opcode opcode.Opcode, args ...byte) int { - offset := len(f.code) - f.code = append(f.code, byte(opcode)) - f.code = append(f.code, args...) - return offset -} - func (f *function) declareLocal(name string) *local { if f.localCount >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid local declaration")) diff --git a/bbq/opcode/castkind.go b/bbq/opcode/castkind.go new file mode 100644 index 0000000000..7faa312534 --- /dev/null +++ b/bbq/opcode/castkind.go @@ -0,0 +1,27 @@ +package opcode + +import ( + "github.com/onflow/cadence/ast" + "github.com/onflow/cadence/errors" +) + +type CastKind byte + +const ( + SimpleCast CastKind = iota + FailableCast + ForceCast +) + +func CastKindFrom(operation ast.Operation) CastKind { + switch operation { + case ast.OperationCast: + return SimpleCast + case ast.OperationFailableCast: + return FailableCast + case ast.OperationForceCast: + return ForceCast + default: + panic(errors.NewUnreachableError()) + } +} diff --git a/bbq/opcode/emit.go b/bbq/opcode/emit.go new file mode 100644 index 0000000000..2ec7bb54e9 --- /dev/null +++ b/bbq/opcode/emit.go @@ -0,0 +1,255 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package opcode + +import ( + "github.com/onflow/cadence/common" +) + +func emit(code *[]byte, opcode Opcode, args ...byte) int { + offset := len(*code) + *code = append(*code, byte(opcode)) + *code = append(*code, args...) + return offset +} + +// encodeUint16 encodes the given uint16 in big-endian representation +func encodeUint16(v uint16) (byte, byte) { + return byte((v >> 8) & 0xff), + byte(v & 0xff) +} + +func EmitTrue(code *[]byte) { + emit(code, True) +} + +func EmitFalse(code *[]byte) { + emit(code, False) +} + +func EmitNil(code *[]byte) { + emit(code, Nil) +} + +func EmitDup(code *[]byte) { + emit(code, Dup) +} + +func EmitDrop(code *[]byte) { + emit(code, Drop) +} + +func EmitGetConstant(code *[]byte, index uint16) int { + first, second := encodeUint16(index) + return emit(code, GetConstant, first, second) +} + +func EmitJump(code *[]byte, target uint16) int { + first, second := encodeUint16(target) + return emit(code, Jump, first, second) +} + +func EmitJumpIfFalse(code *[]byte, target uint16) int { + first, second := encodeUint16(target) + return emit(code, JumpIfFalse, first, second) +} + +func PatchJump(code *[]byte, opcodeOffset int, target uint16) { + first, second := encodeUint16(target) + (*code)[opcodeOffset+1] = first + (*code)[opcodeOffset+2] = second +} + +func EmitReturn(code *[]byte) int { + return emit(code, Return) +} + +func EmitReturnValue(code *[]byte) int { + return emit(code, ReturnValue) +} + +func EmitGetLocal(code *[]byte, index uint16) { + first, second := encodeUint16(index) + emit(code, GetLocal, first, second) +} + +func EmitSetLocal(code *[]byte, index uint16) { + first, second := encodeUint16(index) + emit(code, SetLocal, first, second) +} + +func EmitGetGlobal(code *[]byte, index uint16) { + first, second := encodeUint16(index) + emit(code, GetGlobal, first, second) +} +func EmitSetGlobal(code *[]byte, index uint16) { + first, second := encodeUint16(index) + emit(code, SetGlobal, first, second) +} + +func EmitGetField(code *[]byte) { + emit(code, GetField) +} + +func EmitSetField(code *[]byte) { + emit(code, SetField) +} + +func EmitGetIndex(code *[]byte) { + emit(code, GetIndex) +} + +func EmitSetIndex(code *[]byte) { + emit(code, SetIndex) +} + +func EmitNewArray(code *[]byte, index uint16, size uint16, isResource bool) { + indexFirst, indexSecond := encodeUint16(index) + sizeFirst, sizeSecond := encodeUint16(size) + var isResourceFlag byte + if isResource { + isResourceFlag = 1 + } + emit( + code, + NewArray, + indexFirst, indexSecond, + sizeFirst, sizeSecond, + isResourceFlag, + ) +} + +func EmitIntAdd(code *[]byte) { + emit(code, IntAdd) +} + +func EmitIntSubtract(code *[]byte) { + emit(code, IntSubtract) +} + +func EmitIntMultiply(code *[]byte) { + emit(code, IntMultiply) +} + +func EmitIntDivide(code *[]byte) { + emit(code, IntDivide) +} + +func EmitIntMod(code *[]byte) { + emit(code, IntMod) +} + +func EmitEqual(code *[]byte) { + emit(code, Equal) +} + +func EmitNotEqual(code *[]byte) { + emit(code, NotEqual) +} + +func EmitIntLess(code *[]byte) { + emit(code, IntLess) +} + +func EmitIntLessOrEqual(code *[]byte) { + emit(code, IntLessOrEqual) +} + +func EmitIntGreater(code *[]byte) { + emit(code, IntGreater) +} + +func EmitIntGreaterOrEqual(code *[]byte) { + emit(code, IntGreaterOrEqual) +} + +func EmitUnwrap(code *[]byte) { + emit(code, Unwrap) +} + +func EmitCast(code *[]byte, index uint16, kind CastKind) { + first, second := encodeUint16(index) + emit(code, Cast, first, second, byte(kind)) +} + +func EmitDestroy(code *[]byte) { + emit(code, Destroy) +} + +func EmitTransfer(code *[]byte, index uint16) { + first, second := encodeUint16(index) + emit(code, Transfer, first, second) +} + +func EmitNewRef(code *[]byte, index uint16) { + first, second := encodeUint16(index) + emit(code, NewRef, first, second) +} + +func EmitPath(code *[]byte, domain common.PathDomain, identifier string) { + emit(code, Path) + + *code = append(*code, byte(domain)) + + identifierLength := len(identifier) + + identifierSizeFirst, identifierSizeSecond := encodeUint16(uint16(identifierLength)) + *code = append(*code, identifierSizeFirst, identifierSizeSecond) + + *code = append(*code, identifier...) +} + +func emitTypeArgs(code *[]byte, typeArgs []uint16) { + first, second := encodeUint16(uint16(len(typeArgs))) + *code = append(*code, first, second) + for _, typeArg := range typeArgs { + first, second := encodeUint16(typeArg) + *code = append(*code, first, second) + } +} + +func EmitInvoke(code *[]byte, typeArgs []uint16) { + emit(code, Invoke) + emitTypeArgs(code, typeArgs) +} + +func EmitNew(code *[]byte, kind uint16, index uint16) { + firstKind, secondKind := encodeUint16(kind) + firstIndex, secondIndex := encodeUint16(index) + emit( + code, + New, + firstKind, secondKind, + firstIndex, secondIndex, + ) +} + +func EmitInvokeDynamic(code *[]byte, name string, typeArgs []uint16, argCount uint16) { + emit(code, InvokeDynamic) + + funcNameSizeFirst, funcNameSizeSecond := encodeUint16(uint16(len(name))) + *code = append(*code, funcNameSizeFirst, funcNameSizeSecond) + + *code = append(*code, []byte(name)...) + + emitTypeArgs(code, typeArgs) + + argsCountFirst, argsCountSecond := encodeUint16(argCount) + *code = append(*code, argsCountFirst, argsCountSecond) +} diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index c8db589751..63c16c2018 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -20,6 +20,7 @@ package vm import ( "github.com/onflow/atree" + "github.com/onflow/cadence/bbq" "github.com/onflow/cadence/bbq/commons" "github.com/onflow/cadence/bbq/constantkind" @@ -577,7 +578,7 @@ func opPath(vm *VM) { func opCast(vm *VM) { value := vm.pop() targetType := vm.loadType() - castKind := commons.CastKind(vm.getByte()) + castKind := opcode.CastKind(vm.getByte()) // TODO: _ = castKind From e26c88fd260b875a3b125500ffe1bf1dad2e8153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 11 Dec 2024 09:52:32 -0800 Subject: [PATCH 74/89] add license headers --- bbq/opcode/castkind.go | 18 ++++++++++++++++++ bbq/opcode/print.go | 18 ++++++++++++++++++ bbq/opcode/print_test.go | 18 ++++++++++++++++++ bbq/vm/test/interpreter_test.go | 23 +++++++++++++++++++++-- bbq/vm/test/vm_bench_test.go | 18 ++++++++++++++++++ 5 files changed, 93 insertions(+), 2 deletions(-) diff --git a/bbq/opcode/castkind.go b/bbq/opcode/castkind.go index 7faa312534..a2ec015457 100644 --- a/bbq/opcode/castkind.go +++ b/bbq/opcode/castkind.go @@ -1,3 +1,21 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package opcode import ( diff --git a/bbq/opcode/print.go b/bbq/opcode/print.go index 7c46ceb5ea..1740bc913c 100644 --- a/bbq/opcode/print.go +++ b/bbq/opcode/print.go @@ -1,3 +1,21 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package opcode import ( diff --git a/bbq/opcode/print_test.go b/bbq/opcode/print_test.go index d06270e098..4b63c9575e 100644 --- a/bbq/opcode/print_test.go +++ b/bbq/opcode/print_test.go @@ -1,3 +1,21 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package opcode import ( diff --git a/bbq/vm/test/interpreter_test.go b/bbq/vm/test/interpreter_test.go index 920b0ddfbd..9de6f0c0b2 100644 --- a/bbq/vm/test/interpreter_test.go +++ b/bbq/vm/test/interpreter_test.go @@ -1,13 +1,32 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package test import ( "encoding/hex" "fmt" + "strings" + "testing" + "github.com/onflow/cadence/test_utils/interpreter_utils" "github.com/onflow/cadence/test_utils/runtime_utils" "github.com/onflow/cadence/test_utils/sema_utils" - "strings" - "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/bbq/vm/test/vm_bench_test.go b/bbq/vm/test/vm_bench_test.go index 7f795fb3d2..4c77f61451 100644 --- a/bbq/vm/test/vm_bench_test.go +++ b/bbq/vm/test/vm_bench_test.go @@ -1,3 +1,21 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package test import ( From d02289b09a0f92570eabead6c3751b3b1cfa05ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 11 Dec 2024 12:43:12 -0800 Subject: [PATCH 75/89] improve names of indices --- bbq/compiler/codegen.go | 60 ++++++++++++++++++++--------------------- bbq/opcode/emit.go | 44 +++++++++++++++--------------- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/bbq/compiler/codegen.go b/bbq/compiler/codegen.go index 960a54ae8e..d0a6b18d8d 100644 --- a/bbq/compiler/codegen.go +++ b/bbq/compiler/codegen.go @@ -31,21 +31,21 @@ type CodeGen interface { EmitFalse() EmitDup() EmitDrop() - EmitGetConstant(index uint16) + EmitGetConstant(constantIndex uint16) EmitJump(target uint16) int EmitJumpIfFalse(target uint16) int PatchJump(offset int, newTarget uint16) EmitReturnValue() EmitReturn() - EmitGetLocal(index uint16) - EmitSetLocal(index uint16) - EmitGetGlobal(index uint16) - EmitSetGlobal(index uint16) + EmitGetLocal(localIndex uint16) + EmitSetLocal(localIndex uint16) + EmitGetGlobal(globalIndex uint16) + EmitSetGlobal(globalIndex uint16) EmitGetField() EmitSetField() EmitGetIndex() EmitSetIndex() - EmitNewArray(index uint16, size uint16, isResource bool) + EmitNewArray(typeIndex uint16, size uint16, isResource bool) EmitIntAdd() EmitIntSubtract() EmitIntMultiply() @@ -58,12 +58,12 @@ type CodeGen interface { EmitIntGreater() EmitIntGreaterOrEqual() EmitUnwrap() - EmitCast(index uint16, kind opcode.CastKind) + EmitCast(typeIndex uint16, kind opcode.CastKind) EmitDestroy() - EmitTransfer(index uint16) - EmitNewRef(index uint16) + EmitTransfer(typeIndex uint16) + EmitNewRef(typeIndex uint16) EmitPath(domain common.PathDomain, identifier string) - EmitNew(kind uint16, index uint16) + EmitNew(kind uint16, typeIndex uint16) EmitInvoke(typeArgs []uint16) EmitInvokeDynamic(name string, typeArgs []uint16, argCount uint16) } @@ -102,8 +102,8 @@ func (g *BytecodeGen) EmitDrop() { opcode.EmitDrop(&g.code) } -func (g *BytecodeGen) EmitGetConstant(index uint16) { - opcode.EmitGetConstant(&g.code, index) +func (g *BytecodeGen) EmitGetConstant(constantIndex uint16) { + opcode.EmitGetConstant(&g.code, constantIndex) } func (g *BytecodeGen) EmitJump(target uint16) int { @@ -126,19 +126,19 @@ func (g *BytecodeGen) EmitReturn() { opcode.EmitReturn(&g.code) } -func (g *BytecodeGen) EmitGetLocal(index uint16) { - opcode.EmitGetLocal(&g.code, index) +func (g *BytecodeGen) EmitGetLocal(localIndex uint16) { + opcode.EmitGetLocal(&g.code, localIndex) } -func (g *BytecodeGen) EmitSetLocal(index uint16) { - opcode.EmitSetLocal(&g.code, index) +func (g *BytecodeGen) EmitSetLocal(localIndex uint16) { + opcode.EmitSetLocal(&g.code, localIndex) } -func (g *BytecodeGen) EmitGetGlobal(index uint16) { - opcode.EmitGetGlobal(&g.code, index) +func (g *BytecodeGen) EmitGetGlobal(globalIndex uint16) { + opcode.EmitGetGlobal(&g.code, globalIndex) } -func (g *BytecodeGen) EmitSetGlobal(index uint16) { - opcode.EmitSetGlobal(&g.code, index) +func (g *BytecodeGen) EmitSetGlobal(globalIndex uint16) { + opcode.EmitSetGlobal(&g.code, globalIndex) } func (g *BytecodeGen) EmitGetField() { @@ -157,8 +157,8 @@ func (g *BytecodeGen) EmitSetIndex() { opcode.EmitSetIndex(&g.code) } -func (g *BytecodeGen) EmitNewArray(index uint16, size uint16, isResource bool) { - opcode.EmitNewArray(&g.code, index, size, isResource) +func (g *BytecodeGen) EmitNewArray(typeIndex uint16, size uint16, isResource bool) { + opcode.EmitNewArray(&g.code, typeIndex, size, isResource) } func (g *BytecodeGen) EmitIntAdd() { @@ -209,28 +209,28 @@ func (g *BytecodeGen) EmitUnwrap() { opcode.EmitUnwrap(&g.code) } -func (g *BytecodeGen) EmitCast(index uint16, kind opcode.CastKind) { - opcode.EmitCast(&g.code, index, kind) +func (g *BytecodeGen) EmitCast(typeIndex uint16, kind opcode.CastKind) { + opcode.EmitCast(&g.code, typeIndex, kind) } func (g *BytecodeGen) EmitDestroy() { opcode.EmitDestroy(&g.code) } -func (g *BytecodeGen) EmitTransfer(index uint16) { - opcode.EmitTransfer(&g.code, index) +func (g *BytecodeGen) EmitTransfer(typeIndex uint16) { + opcode.EmitTransfer(&g.code, typeIndex) } -func (g *BytecodeGen) EmitNewRef(index uint16) { - opcode.EmitNewRef(&g.code, index) +func (g *BytecodeGen) EmitNewRef(typeIndex uint16) { + opcode.EmitNewRef(&g.code, typeIndex) } func (g *BytecodeGen) EmitPath(domain common.PathDomain, identifier string) { opcode.EmitPath(&g.code, domain, identifier) } -func (g *BytecodeGen) EmitNew(kind uint16, index uint16) { - opcode.EmitNew(&g.code, kind, index) +func (g *BytecodeGen) EmitNew(kind uint16, typeIndex uint16) { + opcode.EmitNew(&g.code, kind, typeIndex) } func (g *BytecodeGen) EmitInvoke(typeArgs []uint16) { diff --git a/bbq/opcode/emit.go b/bbq/opcode/emit.go index 2ec7bb54e9..73d60116c4 100644 --- a/bbq/opcode/emit.go +++ b/bbq/opcode/emit.go @@ -55,8 +55,8 @@ func EmitDrop(code *[]byte) { emit(code, Drop) } -func EmitGetConstant(code *[]byte, index uint16) int { - first, second := encodeUint16(index) +func EmitGetConstant(code *[]byte, constantIndex uint16) int { + first, second := encodeUint16(constantIndex) return emit(code, GetConstant, first, second) } @@ -84,22 +84,22 @@ func EmitReturnValue(code *[]byte) int { return emit(code, ReturnValue) } -func EmitGetLocal(code *[]byte, index uint16) { - first, second := encodeUint16(index) +func EmitGetLocal(code *[]byte, localIndex uint16) { + first, second := encodeUint16(localIndex) emit(code, GetLocal, first, second) } -func EmitSetLocal(code *[]byte, index uint16) { - first, second := encodeUint16(index) +func EmitSetLocal(code *[]byte, localIndex uint16) { + first, second := encodeUint16(localIndex) emit(code, SetLocal, first, second) } -func EmitGetGlobal(code *[]byte, index uint16) { - first, second := encodeUint16(index) +func EmitGetGlobal(code *[]byte, globalIndex uint16) { + first, second := encodeUint16(globalIndex) emit(code, GetGlobal, first, second) } -func EmitSetGlobal(code *[]byte, index uint16) { - first, second := encodeUint16(index) +func EmitSetGlobal(code *[]byte, globalIndex uint16) { + first, second := encodeUint16(globalIndex) emit(code, SetGlobal, first, second) } @@ -119,8 +119,8 @@ func EmitSetIndex(code *[]byte) { emit(code, SetIndex) } -func EmitNewArray(code *[]byte, index uint16, size uint16, isResource bool) { - indexFirst, indexSecond := encodeUint16(index) +func EmitNewArray(code *[]byte, typeIndex uint16, size uint16, isResource bool) { + typeIndexFirst, typeIndexSecond := encodeUint16(typeIndex) sizeFirst, sizeSecond := encodeUint16(size) var isResourceFlag byte if isResource { @@ -129,7 +129,7 @@ func EmitNewArray(code *[]byte, index uint16, size uint16, isResource bool) { emit( code, NewArray, - indexFirst, indexSecond, + typeIndexFirst, typeIndexSecond, sizeFirst, sizeSecond, isResourceFlag, ) @@ -183,8 +183,8 @@ func EmitUnwrap(code *[]byte) { emit(code, Unwrap) } -func EmitCast(code *[]byte, index uint16, kind CastKind) { - first, second := encodeUint16(index) +func EmitCast(code *[]byte, typeIndex uint16, kind CastKind) { + first, second := encodeUint16(typeIndex) emit(code, Cast, first, second, byte(kind)) } @@ -192,13 +192,13 @@ func EmitDestroy(code *[]byte) { emit(code, Destroy) } -func EmitTransfer(code *[]byte, index uint16) { - first, second := encodeUint16(index) +func EmitTransfer(code *[]byte, typeIndex uint16) { + first, second := encodeUint16(typeIndex) emit(code, Transfer, first, second) } -func EmitNewRef(code *[]byte, index uint16) { - first, second := encodeUint16(index) +func EmitNewRef(code *[]byte, typeIndex uint16) { + first, second := encodeUint16(typeIndex) emit(code, NewRef, first, second) } @@ -229,14 +229,14 @@ func EmitInvoke(code *[]byte, typeArgs []uint16) { emitTypeArgs(code, typeArgs) } -func EmitNew(code *[]byte, kind uint16, index uint16) { +func EmitNew(code *[]byte, kind uint16, typeIndex uint16) { firstKind, secondKind := encodeUint16(kind) - firstIndex, secondIndex := encodeUint16(index) + firstTypeIndex, secondTypeIndex := encodeUint16(typeIndex) emit( code, New, firstKind, secondKind, - firstIndex, secondIndex, + firstTypeIndex, secondTypeIndex, ) } From 140113ccf3a5581f7f2499d13be4143755d25a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 11 Dec 2024 12:48:56 -0800 Subject: [PATCH 76/89] refactor instruction decoding --- bbq/opcode/emit.go | 214 ++++++++++++++++++++++++++++++++++++++++++--- bbq/vm/vm.go | 102 +++++++++------------ 2 files changed, 243 insertions(+), 73 deletions(-) diff --git a/bbq/opcode/emit.go b/bbq/opcode/emit.go index 73d60116c4..c1182d19b9 100644 --- a/bbq/opcode/emit.go +++ b/bbq/opcode/emit.go @@ -29,96 +29,202 @@ func emit(code *[]byte, opcode Opcode, args ...byte) int { return offset } +// uint16 + // encodeUint16 encodes the given uint16 in big-endian representation func encodeUint16(v uint16) (byte, byte) { return byte((v >> 8) & 0xff), byte(v & 0xff) } +func decodeUint16(ip *uint16, code []byte) uint16 { + first := code[*ip] + last := code[*ip+1] + *ip += 2 + return uint16(first)<<8 | uint16(last) +} + +// Byte + +func decodeByte(ip *uint16, code []byte) byte { + byt := code[*ip] + *ip += 1 + return byt +} + +// Bool + +func decodeBool(ip *uint16, code []byte) bool { + return decodeByte(ip, code) == 1 +} + +// String + +func decodeString(ip *uint16, code []byte) string { + strLen := decodeUint16(ip, code) + start := *ip + *ip += strLen + end := *ip + return string(code[start:end]) +} + +func emitString(code *[]byte, str string) { + sizeFirst, sizeSecond := encodeUint16(uint16(len(str))) + *code = append(*code, sizeFirst, sizeSecond) + *code = append(*code, []byte(str)...) +} + +// True + func EmitTrue(code *[]byte) { emit(code, True) } +// False + func EmitFalse(code *[]byte) { emit(code, False) } +// Nil + func EmitNil(code *[]byte) { emit(code, Nil) } +// Dup + func EmitDup(code *[]byte) { emit(code, Dup) } +// Drop + func EmitDrop(code *[]byte) { emit(code, Drop) } +// GetConstant + func EmitGetConstant(code *[]byte, constantIndex uint16) int { first, second := encodeUint16(constantIndex) return emit(code, GetConstant, first, second) } +func DecodeGetConstant(ip *uint16, code []byte) (constantIndex uint16) { + return decodeUint16(ip, code) +} + +// Jump + func EmitJump(code *[]byte, target uint16) int { first, second := encodeUint16(target) return emit(code, Jump, first, second) } +func DecodeJump(ip *uint16, code []byte) (target uint16) { + return decodeUint16(ip, code) +} + +// JumpIfFalse + func EmitJumpIfFalse(code *[]byte, target uint16) int { first, second := encodeUint16(target) return emit(code, JumpIfFalse, first, second) } +func DecodeJumpIfFalse(ip *uint16, code []byte) (target uint16) { + return decodeUint16(ip, code) +} + func PatchJump(code *[]byte, opcodeOffset int, target uint16) { first, second := encodeUint16(target) (*code)[opcodeOffset+1] = first (*code)[opcodeOffset+2] = second } +// Return + func EmitReturn(code *[]byte) int { return emit(code, Return) } +// ReturnValue + func EmitReturnValue(code *[]byte) int { return emit(code, ReturnValue) } +// GetLocal + func EmitGetLocal(code *[]byte, localIndex uint16) { first, second := encodeUint16(localIndex) emit(code, GetLocal, first, second) } +func DecodeGetLocal(ip *uint16, code []byte) (localIndex uint16) { + return decodeUint16(ip, code) +} + +// SetLocal + func EmitSetLocal(code *[]byte, localIndex uint16) { first, second := encodeUint16(localIndex) emit(code, SetLocal, first, second) } +func DecodeSetLocal(ip *uint16, code []byte) (localIndex uint16) { + return decodeUint16(ip, code) +} + +// GetGlobal + func EmitGetGlobal(code *[]byte, globalIndex uint16) { first, second := encodeUint16(globalIndex) emit(code, GetGlobal, first, second) } + +func DecodeGetGlobal(ip *uint16, code []byte) (globalIndex uint16) { + return decodeUint16(ip, code) +} + +// SetGlobal + func EmitSetGlobal(code *[]byte, globalIndex uint16) { first, second := encodeUint16(globalIndex) emit(code, SetGlobal, first, second) } +func DecodeSetGlobal(ip *uint16, code []byte) (globalIndex uint16) { + return decodeUint16(ip, code) +} + +// GetField + func EmitGetField(code *[]byte) { emit(code, GetField) } +// SetField + func EmitSetField(code *[]byte) { emit(code, SetField) } +// GetIndex + func EmitGetIndex(code *[]byte) { emit(code, GetIndex) } +// SetIndex + func EmitSetIndex(code *[]byte) { emit(code, SetIndex) } +// NewArray + func EmitNewArray(code *[]byte, typeIndex uint16, size uint16, isResource bool) { typeIndexFirst, typeIndexSecond := encodeUint16(typeIndex) sizeFirst, sizeSecond := encodeUint16(size) @@ -135,86 +241,144 @@ func EmitNewArray(code *[]byte, typeIndex uint16, size uint16, isResource bool) ) } +func DecodeNewArray(ip *uint16, code []byte) (typeIndex uint16, size uint16, isResource bool) { + typeIndex = decodeUint16(ip, code) + size = decodeUint16(ip, code) + isResource = decodeBool(ip, code) + return typeIndex, size, isResource +} + +// IntAdd + func EmitIntAdd(code *[]byte) { emit(code, IntAdd) } +// IntSubtract + func EmitIntSubtract(code *[]byte) { emit(code, IntSubtract) } +// IntMultiply + func EmitIntMultiply(code *[]byte) { emit(code, IntMultiply) } +// IntDivide + func EmitIntDivide(code *[]byte) { emit(code, IntDivide) } +// IntMod + func EmitIntMod(code *[]byte) { emit(code, IntMod) } +// IntEqual + func EmitEqual(code *[]byte) { emit(code, Equal) } +// IntNotEqual + func EmitNotEqual(code *[]byte) { emit(code, NotEqual) } +// IntLess + func EmitIntLess(code *[]byte) { emit(code, IntLess) } +// IntLessOrEqual + func EmitIntLessOrEqual(code *[]byte) { emit(code, IntLessOrEqual) } +// IntGreater + func EmitIntGreater(code *[]byte) { emit(code, IntGreater) } +// IntGreaterOrEqual + func EmitIntGreaterOrEqual(code *[]byte) { emit(code, IntGreaterOrEqual) } +// Unwrap + func EmitUnwrap(code *[]byte) { emit(code, Unwrap) } +// Cast + func EmitCast(code *[]byte, typeIndex uint16, kind CastKind) { first, second := encodeUint16(typeIndex) emit(code, Cast, first, second, byte(kind)) } +func DecodeCast(ip *uint16, code []byte) (typeIndex uint16, kind byte) { + typeIndex = decodeUint16(ip, code) + kind = decodeByte(ip, code) + return typeIndex, kind +} + +// Destroy + func EmitDestroy(code *[]byte) { emit(code, Destroy) } +// Transfer + func EmitTransfer(code *[]byte, typeIndex uint16) { first, second := encodeUint16(typeIndex) emit(code, Transfer, first, second) } +func DecodeTransfer(ip *uint16, code []byte) (typeIndex uint16) { + return decodeUint16(ip, code) +} + +// NewRef + func EmitNewRef(code *[]byte, typeIndex uint16) { first, second := encodeUint16(typeIndex) emit(code, NewRef, first, second) } +func DecodeNewRef(ip *uint16, code []byte) (typeIndex uint16) { + return decodeUint16(ip, code) +} + +// Path + func EmitPath(code *[]byte, domain common.PathDomain, identifier string) { emit(code, Path) *code = append(*code, byte(domain)) - identifierLength := len(identifier) - - identifierSizeFirst, identifierSizeSecond := encodeUint16(uint16(identifierLength)) - *code = append(*code, identifierSizeFirst, identifierSizeSecond) + emitString(code, identifier) +} - *code = append(*code, identifier...) +func DecodePath(ip *uint16, code []byte) (domain byte, identifier string) { + domain = decodeByte(ip, code) + identifier = decodeString(ip, code) + return domain, identifier } +// Type arguments + func emitTypeArgs(code *[]byte, typeArgs []uint16) { first, second := encodeUint16(uint16(len(typeArgs))) *code = append(*code, first, second) @@ -224,11 +388,28 @@ func emitTypeArgs(code *[]byte, typeArgs []uint16) { } } +func decodeTypeArgs(ip *uint16, code []byte) (typeArgs []uint16) { + typeArgCount := decodeUint16(ip, code) + for i := 0; i < int(typeArgCount); i++ { + typeIndex := decodeUint16(ip, code) + typeArgs = append(typeArgs, typeIndex) + } + return typeArgs +} + +// Invoke + func EmitInvoke(code *[]byte, typeArgs []uint16) { emit(code, Invoke) emitTypeArgs(code, typeArgs) } +func DecodeInvoke(ip *uint16, code []byte) (typeArgs []uint16) { + return decodeTypeArgs(ip, code) +} + +// New + func EmitNew(code *[]byte, kind uint16, typeIndex uint16) { firstKind, secondKind := encodeUint16(kind) firstTypeIndex, secondTypeIndex := encodeUint16(typeIndex) @@ -240,16 +421,25 @@ func EmitNew(code *[]byte, kind uint16, typeIndex uint16) { ) } -func EmitInvokeDynamic(code *[]byte, name string, typeArgs []uint16, argCount uint16) { - emit(code, InvokeDynamic) - - funcNameSizeFirst, funcNameSizeSecond := encodeUint16(uint16(len(name))) - *code = append(*code, funcNameSizeFirst, funcNameSizeSecond) +func DecodeNew(ip *uint16, code []byte) (kind uint16, typeIndex uint16) { + kind = decodeUint16(ip, code) + typeIndex = decodeUint16(ip, code) + return kind, typeIndex +} - *code = append(*code, []byte(name)...) +// InvokeDynamic +func EmitInvokeDynamic(code *[]byte, name string, typeArgs []uint16, argCount uint16) { + emit(code, InvokeDynamic) + emitString(code, name) emitTypeArgs(code, typeArgs) - argsCountFirst, argsCountSecond := encodeUint16(argCount) *code = append(*code, argsCountFirst, argsCountSecond) } + +func DecodeInvokeDynamic(ip *uint16, code []byte) (name string, typeArgs []uint16, argCount uint16) { + name = decodeString(ip, code) + typeArgs = decodeTypeArgs(ip, code) + argCount = decodeUint16(ip, code) + return name, typeArgs, argCount +} diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index 63c16c2018..e25289b35c 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -311,12 +311,12 @@ func opReturn(vm *VM) { } func opJump(vm *VM) { - target := vm.getUint16() + target := opcode.DecodeJump(&vm.ip, vm.callFrame.function.Code) vm.ip = target } func opJumpIfFalse(vm *VM) { - target := vm.getUint16() + target := opcode.DecodeJumpIfFalse(&vm.ip, vm.callFrame.function.Code) value := vm.pop().(BoolValue) if !value { vm.ip = target @@ -361,7 +361,7 @@ func opFalse(vm *VM) { func opGetConstant(vm *VM) { callFrame := vm.callFrame - index := vm.getUint16() + index := opcode.DecodeGetConstant(&vm.ip, callFrame.function.Code) constant := callFrame.executable.Constants[index] if constant == nil { constant = vm.initializeConstant(index) @@ -371,7 +371,7 @@ func opGetConstant(vm *VM) { func opGetLocal(vm *VM) { callFrame := vm.callFrame - index := vm.getUint16() + index := opcode.DecodeGetLocal(&vm.ip, callFrame.function.Code) absoluteIndex := callFrame.localsOffset + index local := vm.locals[absoluteIndex] vm.push(local) @@ -379,21 +379,20 @@ func opGetLocal(vm *VM) { func opSetLocal(vm *VM) { callFrame := vm.callFrame - index := vm.getUint16() - + index := opcode.DecodeSetLocal(&vm.ip, callFrame.function.Code) absoluteIndex := callFrame.localsOffset + index vm.locals[absoluteIndex] = vm.pop() } func opGetGlobal(vm *VM) { callFrame := vm.callFrame - index := vm.getUint16() + index := opcode.DecodeGetGlobal(&vm.ip, callFrame.function.Code) vm.push(callFrame.executable.Globals[index]) } func opSetGlobal(vm *VM) { callFrame := vm.callFrame - index := vm.getUint16() + index := opcode.DecodeSetGlobal(&vm.ip, callFrame.function.Code) callFrame.executable.Globals[index] = vm.pop() } @@ -416,7 +415,8 @@ func opInvoke(vm *VM) { value := vm.pop() stackHeight := len(vm.stack) - typeArgCount := vm.getUint16() + callFrame := vm.callFrame + typeArgs := opcode.DecodeInvoke(&vm.ip, callFrame.function.Code) switch value := value.(type) { case FunctionValue: @@ -424,12 +424,13 @@ func opInvoke(vm *VM) { arguments := vm.stack[stackHeight-parameterCount:] vm.pushCallFrame(value, arguments) vm.dropN(parameterCount) + case NativeFunctionValue: parameterCount := value.ParameterCount var typeArguments []StaticType - for i := 0; i < int(typeArgCount); i++ { - typeArg := vm.loadType() + for _, index := range typeArgs { + typeArg := vm.loadType(index) typeArguments = append(typeArguments, typeArg) } @@ -438,23 +439,24 @@ func opInvoke(vm *VM) { result := value.Function(vm.config, typeArguments, arguments...) vm.dropN(parameterCount) vm.push(result) + default: panic(errors.NewUnreachableError()) } } func opInvokeDynamic(vm *VM) { - funcName := vm.getString() - typeArgCount := vm.getUint16() - argsCount := vm.getUint16() + callFrame := vm.callFrame + + funcName, typeArgs, argsCount := opcode.DecodeInvokeDynamic(&vm.ip, callFrame.function.Code) stackHeight := len(vm.stack) receiver := vm.stack[stackHeight-int(argsCount)-1] // TODO: var typeArguments []StaticType - for i := 0; i < int(typeArgCount); i++ { - typeArg := vm.loadType() + for _, index := range typeArgs { + typeArg := vm.loadType(index) typeArguments = append(typeArguments, typeArg) } // TODO: Just to make the linter happy @@ -494,11 +496,11 @@ func opDup(vm *VM) { } func opNew(vm *VM) { - kind := vm.getUint16() + kind, staticTypeIndex := opcode.DecodeNew(&vm.ip, vm.callFrame.function.Code) compositeKind := common.CompositeKind(kind) // decode location - staticType := vm.loadType() + staticType := vm.loadType(staticTypeIndex) // TODO: Support inclusive-range type compositeStaticType := staticType.(*interpreter.CompositeStaticType) @@ -541,7 +543,9 @@ func opGetField(vm *VM) { } func opTransfer(vm *VM) { - targetType := vm.loadType() + typeIndex := opcode.DecodeTransfer(&vm.ip, vm.callFrame.function.Code) + + targetType := vm.loadType(typeIndex) value := vm.peek() config := vm.config @@ -566,10 +570,9 @@ func opDestroy(vm *VM) { } func opPath(vm *VM) { - domain := common.PathDomain(vm.getByte()) - identifier := vm.getString() + domain, identifier := opcode.DecodePath(&vm.ip, vm.callFrame.function.Code) value := PathValue{ - Domain: domain, + Domain: common.PathDomain(domain), Identifier: identifier, } vm.push(value) @@ -577,8 +580,11 @@ func opPath(vm *VM) { func opCast(vm *VM) { value := vm.pop() - targetType := vm.loadType() - castKind := opcode.CastKind(vm.getByte()) + + typeIndex, kind := opcode.DecodeCast(&vm.ip, vm.callFrame.function.Code) + + targetType := vm.loadType(typeIndex) + castKind := opcode.CastKind(kind) // TODO: _ = castKind @@ -610,24 +616,26 @@ func opUnwrap(vm *VM) { } func opNewArray(vm *VM) { - typ := vm.loadType().(interpreter.ArrayStaticType) - size := int(vm.getUint16()) - isResourceKinded := vm.getBool() + typeIndex, size, isResource := opcode.DecodeNewArray(&vm.ip, vm.callFrame.function.Code) + + typ := vm.loadType(typeIndex).(interpreter.ArrayStaticType) elements := make([]Value, size) // Must be inserted in the reverse, - //since the stack if FILO. - for i := size - 1; i >= 0; i-- { + // since the stack if FILO. + for i := int(size) - 1; i >= 0; i-- { elements[i] = vm.pop() } - array := NewArrayValue(vm.config, typ, isResourceKinded, elements...) + array := NewArrayValue(vm.config, typ, isResource, elements...) vm.push(array) } func opNewRef(vm *VM) { - borrowedType := vm.loadType().(*interpreter.ReferenceStaticType) + index := opcode.DecodeNewRef(&vm.ip, vm.callFrame.function.Code) + + borrowedType := vm.loadType(index).(*interpreter.ReferenceStaticType) value := vm.pop() ref := NewEphemeralReferenceValue( @@ -751,10 +759,8 @@ func (vm *VM) initializeConstant(index uint16) (value Value) { return value } -func (vm *VM) loadType() StaticType { - callframe := vm.callFrame - index := vm.getUint16() - staticType := callframe.executable.StaticTypes[index] +func (vm *VM) loadType(index uint16) StaticType { + staticType := vm.callFrame.executable.StaticTypes[index] if staticType == nil { // TODO: Remove. Should never reach because of the // pre loading-decoding of types. @@ -817,32 +823,6 @@ func (vm *VM) StackSize() int { return len(vm.stack) } -func (vm *VM) getUint16() uint16 { - first := vm.callFrame.function.Code[vm.ip] - last := vm.callFrame.function.Code[vm.ip+1] - vm.ip += 2 - return uint16(first)<<8 | uint16(last) -} - -func (vm *VM) getByte() byte { - byt := vm.callFrame.function.Code[vm.ip] - vm.ip++ - return byt -} - -func (vm *VM) getBool() bool { - byt := vm.callFrame.function.Code[vm.ip] - vm.ip++ - return byt == 1 -} - -func (vm *VM) getString() string { - strLen := vm.getUint16() - str := string(vm.callFrame.function.Code[vm.ip : vm.ip+strLen]) - vm.ip += strLen - return str -} - func getReceiver[T any](config *Config, receiver Value) T { switch receiver := receiver.(type) { case *SomeValue: From 8c6338ae0b5dac1a42aef20f78f4353caf8fc08d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 11 Dec 2024 13:06:05 -0800 Subject: [PATCH 77/89] improve encoding of uint16 values --- bbq/opcode/emit.go | 169 +++++++++++++++++++++++---------------------- bbq/vm/vm.go | 3 +- 2 files changed, 87 insertions(+), 85 deletions(-) diff --git a/bbq/opcode/emit.go b/bbq/opcode/emit.go index c1182d19b9..39ed9d8df5 100644 --- a/bbq/opcode/emit.go +++ b/bbq/opcode/emit.go @@ -22,10 +22,9 @@ import ( "github.com/onflow/cadence/common" ) -func emit(code *[]byte, opcode Opcode, args ...byte) int { +func emitOpcode(code *[]byte, opcode Opcode) int { offset := len(*code) *code = append(*code, byte(opcode)) - *code = append(*code, args...) return offset } @@ -37,6 +36,12 @@ func encodeUint16(v uint16) (byte, byte) { byte(v & 0xff) } +// emitUint16 encodes the given uint16 in big-endian representation +func emitUint16(code *[]byte, v uint16) { + first, last := encodeUint16(v) + *code = append(*code, first, last) +} + func decodeUint16(ip *uint16, code []byte) uint16 { first := code[*ip] last := code[*ip+1] @@ -52,12 +57,24 @@ func decodeByte(ip *uint16, code []byte) byte { return byt } +func emitByte(code *[]byte, b byte) { + *code = append(*code, b) +} + // Bool func decodeBool(ip *uint16, code []byte) bool { return decodeByte(ip, code) == 1 } +func emitBool(code *[]byte, v bool) { + var b byte + if v { + b = 1 + } + *code = append(*code, b) +} + // String func decodeString(ip *uint16, code []byte) string { @@ -69,46 +86,46 @@ func decodeString(ip *uint16, code []byte) string { } func emitString(code *[]byte, str string) { - sizeFirst, sizeSecond := encodeUint16(uint16(len(str))) - *code = append(*code, sizeFirst, sizeSecond) + emitUint16(code, uint16(len(str))) *code = append(*code, []byte(str)...) } // True func EmitTrue(code *[]byte) { - emit(code, True) + emitOpcode(code, True) } // False func EmitFalse(code *[]byte) { - emit(code, False) + emitOpcode(code, False) } // Nil func EmitNil(code *[]byte) { - emit(code, Nil) + emitOpcode(code, Nil) } // Dup func EmitDup(code *[]byte) { - emit(code, Dup) + emitOpcode(code, Dup) } // Drop func EmitDrop(code *[]byte) { - emit(code, Drop) + emitOpcode(code, Drop) } // GetConstant -func EmitGetConstant(code *[]byte, constantIndex uint16) int { - first, second := encodeUint16(constantIndex) - return emit(code, GetConstant, first, second) +func EmitGetConstant(code *[]byte, constantIndex uint16) (offset int) { + offset = emitOpcode(code, GetConstant) + emitUint16(code, constantIndex) + return offset } func DecodeGetConstant(ip *uint16, code []byte) (constantIndex uint16) { @@ -117,9 +134,10 @@ func DecodeGetConstant(ip *uint16, code []byte) (constantIndex uint16) { // Jump -func EmitJump(code *[]byte, target uint16) int { - first, second := encodeUint16(target) - return emit(code, Jump, first, second) +func EmitJump(code *[]byte, target uint16) (offset int) { + offset = emitOpcode(code, Jump) + emitUint16(code, target) + return offset } func DecodeJump(ip *uint16, code []byte) (target uint16) { @@ -128,9 +146,10 @@ func DecodeJump(ip *uint16, code []byte) (target uint16) { // JumpIfFalse -func EmitJumpIfFalse(code *[]byte, target uint16) int { - first, second := encodeUint16(target) - return emit(code, JumpIfFalse, first, second) +func EmitJumpIfFalse(code *[]byte, target uint16) (offset int) { + offset = emitOpcode(code, JumpIfFalse) + emitUint16(code, target) + return offset } func DecodeJumpIfFalse(ip *uint16, code []byte) (target uint16) { @@ -146,20 +165,20 @@ func PatchJump(code *[]byte, opcodeOffset int, target uint16) { // Return func EmitReturn(code *[]byte) int { - return emit(code, Return) + return emitOpcode(code, Return) } // ReturnValue func EmitReturnValue(code *[]byte) int { - return emit(code, ReturnValue) + return emitOpcode(code, ReturnValue) } // GetLocal func EmitGetLocal(code *[]byte, localIndex uint16) { - first, second := encodeUint16(localIndex) - emit(code, GetLocal, first, second) + emitOpcode(code, GetLocal) + emitUint16(code, localIndex) } func DecodeGetLocal(ip *uint16, code []byte) (localIndex uint16) { @@ -169,8 +188,8 @@ func DecodeGetLocal(ip *uint16, code []byte) (localIndex uint16) { // SetLocal func EmitSetLocal(code *[]byte, localIndex uint16) { - first, second := encodeUint16(localIndex) - emit(code, SetLocal, first, second) + emitOpcode(code, SetLocal) + emitUint16(code, localIndex) } func DecodeSetLocal(ip *uint16, code []byte) (localIndex uint16) { @@ -180,8 +199,8 @@ func DecodeSetLocal(ip *uint16, code []byte) (localIndex uint16) { // GetGlobal func EmitGetGlobal(code *[]byte, globalIndex uint16) { - first, second := encodeUint16(globalIndex) - emit(code, GetGlobal, first, second) + emitOpcode(code, GetGlobal) + emitUint16(code, globalIndex) } func DecodeGetGlobal(ip *uint16, code []byte) (globalIndex uint16) { @@ -191,8 +210,8 @@ func DecodeGetGlobal(ip *uint16, code []byte) (globalIndex uint16) { // SetGlobal func EmitSetGlobal(code *[]byte, globalIndex uint16) { - first, second := encodeUint16(globalIndex) - emit(code, SetGlobal, first, second) + emitOpcode(code, SetGlobal) + emitUint16(code, globalIndex) } func DecodeSetGlobal(ip *uint16, code []byte) (globalIndex uint16) { @@ -202,43 +221,34 @@ func DecodeSetGlobal(ip *uint16, code []byte) (globalIndex uint16) { // GetField func EmitGetField(code *[]byte) { - emit(code, GetField) + emitOpcode(code, GetField) } // SetField func EmitSetField(code *[]byte) { - emit(code, SetField) + emitOpcode(code, SetField) } // GetIndex func EmitGetIndex(code *[]byte) { - emit(code, GetIndex) + emitOpcode(code, GetIndex) } // SetIndex func EmitSetIndex(code *[]byte) { - emit(code, SetIndex) + emitOpcode(code, SetIndex) } // NewArray func EmitNewArray(code *[]byte, typeIndex uint16, size uint16, isResource bool) { - typeIndexFirst, typeIndexSecond := encodeUint16(typeIndex) - sizeFirst, sizeSecond := encodeUint16(size) - var isResourceFlag byte - if isResource { - isResourceFlag = 1 - } - emit( - code, - NewArray, - typeIndexFirst, typeIndexSecond, - sizeFirst, sizeSecond, - isResourceFlag, - ) + emitOpcode(code, NewArray) + emitUint16(code, typeIndex) + emitUint16(code, size) + emitBool(code, isResource) } func DecodeNewArray(ip *uint16, code []byte) (typeIndex uint16, size uint16, isResource bool) { @@ -251,99 +261,100 @@ func DecodeNewArray(ip *uint16, code []byte) (typeIndex uint16, size uint16, isR // IntAdd func EmitIntAdd(code *[]byte) { - emit(code, IntAdd) + emitOpcode(code, IntAdd) } // IntSubtract func EmitIntSubtract(code *[]byte) { - emit(code, IntSubtract) + emitOpcode(code, IntSubtract) } // IntMultiply func EmitIntMultiply(code *[]byte) { - emit(code, IntMultiply) + emitOpcode(code, IntMultiply) } // IntDivide func EmitIntDivide(code *[]byte) { - emit(code, IntDivide) + emitOpcode(code, IntDivide) } // IntMod func EmitIntMod(code *[]byte) { - emit(code, IntMod) + emitOpcode(code, IntMod) } // IntEqual func EmitEqual(code *[]byte) { - emit(code, Equal) + emitOpcode(code, Equal) } // IntNotEqual func EmitNotEqual(code *[]byte) { - emit(code, NotEqual) + emitOpcode(code, NotEqual) } // IntLess func EmitIntLess(code *[]byte) { - emit(code, IntLess) + emitOpcode(code, IntLess) } // IntLessOrEqual func EmitIntLessOrEqual(code *[]byte) { - emit(code, IntLessOrEqual) + emitOpcode(code, IntLessOrEqual) } // IntGreater func EmitIntGreater(code *[]byte) { - emit(code, IntGreater) + emitOpcode(code, IntGreater) } // IntGreaterOrEqual func EmitIntGreaterOrEqual(code *[]byte) { - emit(code, IntGreaterOrEqual) + emitOpcode(code, IntGreaterOrEqual) } // Unwrap func EmitUnwrap(code *[]byte) { - emit(code, Unwrap) + emitOpcode(code, Unwrap) } // Cast func EmitCast(code *[]byte, typeIndex uint16, kind CastKind) { - first, second := encodeUint16(typeIndex) - emit(code, Cast, first, second, byte(kind)) + emitOpcode(code, Cast) + emitUint16(code, typeIndex) + emitByte(code, byte(kind)) } -func DecodeCast(ip *uint16, code []byte) (typeIndex uint16, kind byte) { +func DecodeCast(ip *uint16, code []byte) (typeIndex uint16, kind CastKind) { typeIndex = decodeUint16(ip, code) - kind = decodeByte(ip, code) + kind = CastKind(decodeByte(ip, code)) return typeIndex, kind } // Destroy func EmitDestroy(code *[]byte) { - emit(code, Destroy) + emitOpcode(code, Destroy) } // Transfer func EmitTransfer(code *[]byte, typeIndex uint16) { - first, second := encodeUint16(typeIndex) - emit(code, Transfer, first, second) + emitOpcode(code, Transfer) + emitUint16(code, typeIndex) } func DecodeTransfer(ip *uint16, code []byte) (typeIndex uint16) { @@ -353,8 +364,8 @@ func DecodeTransfer(ip *uint16, code []byte) (typeIndex uint16) { // NewRef func EmitNewRef(code *[]byte, typeIndex uint16) { - first, second := encodeUint16(typeIndex) - emit(code, NewRef, first, second) + emitOpcode(code, NewRef) + emitUint16(code, typeIndex) } func DecodeNewRef(ip *uint16, code []byte) (typeIndex uint16) { @@ -364,7 +375,7 @@ func DecodeNewRef(ip *uint16, code []byte) (typeIndex uint16) { // Path func EmitPath(code *[]byte, domain common.PathDomain, identifier string) { - emit(code, Path) + emitOpcode(code, Path) *code = append(*code, byte(domain)) @@ -380,11 +391,9 @@ func DecodePath(ip *uint16, code []byte) (domain byte, identifier string) { // Type arguments func emitTypeArgs(code *[]byte, typeArgs []uint16) { - first, second := encodeUint16(uint16(len(typeArgs))) - *code = append(*code, first, second) + emitUint16(code, uint16(len(typeArgs))) for _, typeArg := range typeArgs { - first, second := encodeUint16(typeArg) - *code = append(*code, first, second) + emitUint16(code, typeArg) } } @@ -400,7 +409,7 @@ func decodeTypeArgs(ip *uint16, code []byte) (typeArgs []uint16) { // Invoke func EmitInvoke(code *[]byte, typeArgs []uint16) { - emit(code, Invoke) + emitOpcode(code, Invoke) emitTypeArgs(code, typeArgs) } @@ -411,14 +420,9 @@ func DecodeInvoke(ip *uint16, code []byte) (typeArgs []uint16) { // New func EmitNew(code *[]byte, kind uint16, typeIndex uint16) { - firstKind, secondKind := encodeUint16(kind) - firstTypeIndex, secondTypeIndex := encodeUint16(typeIndex) - emit( - code, - New, - firstKind, secondKind, - firstTypeIndex, secondTypeIndex, - ) + emitOpcode(code, New) + emitUint16(code, kind) + emitUint16(code, typeIndex) } func DecodeNew(ip *uint16, code []byte) (kind uint16, typeIndex uint16) { @@ -430,11 +434,10 @@ func DecodeNew(ip *uint16, code []byte) (kind uint16, typeIndex uint16) { // InvokeDynamic func EmitInvokeDynamic(code *[]byte, name string, typeArgs []uint16, argCount uint16) { - emit(code, InvokeDynamic) + emitOpcode(code, InvokeDynamic) emitString(code, name) emitTypeArgs(code, typeArgs) - argsCountFirst, argsCountSecond := encodeUint16(argCount) - *code = append(*code, argsCountFirst, argsCountSecond) + emitUint16(code, argCount) } func DecodeInvokeDynamic(ip *uint16, code []byte) (name string, typeArgs []uint16, argCount uint16) { diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index e25289b35c..79b275f43b 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -581,10 +581,9 @@ func opPath(vm *VM) { func opCast(vm *VM) { value := vm.pop() - typeIndex, kind := opcode.DecodeCast(&vm.ip, vm.callFrame.function.Code) + typeIndex, castKind := opcode.DecodeCast(&vm.ip, vm.callFrame.function.Code) targetType := vm.loadType(typeIndex) - castKind := opcode.CastKind(kind) // TODO: _ = castKind From fa12063ecbdd6c4811725c54b5ea8010c47141ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 11 Dec 2024 14:06:30 -0800 Subject: [PATCH 78/89] add instruction types, refactor encoding and decoding to use them --- bbq/compiler/codegen.go | 196 +-------------------- bbq/compiler/compiler.go | 151 +++++++++------- bbq/opcode/emit.go | 362 ++++++++++++++++++++++++++------------- bbq/vm/vm.go | 78 ++++----- 4 files changed, 377 insertions(+), 410 deletions(-) diff --git a/bbq/compiler/codegen.go b/bbq/compiler/codegen.go index d0a6b18d8d..e815efd8c0 100644 --- a/bbq/compiler/codegen.go +++ b/bbq/compiler/codegen.go @@ -20,52 +20,13 @@ package compiler import ( "github.com/onflow/cadence/bbq/opcode" - "github.com/onflow/cadence/common" ) type CodeGen interface { Offset() int Code() interface{} - EmitNil() - EmitTrue() - EmitFalse() - EmitDup() - EmitDrop() - EmitGetConstant(constantIndex uint16) - EmitJump(target uint16) int - EmitJumpIfFalse(target uint16) int + Emit(instruction opcode.Instruction) PatchJump(offset int, newTarget uint16) - EmitReturnValue() - EmitReturn() - EmitGetLocal(localIndex uint16) - EmitSetLocal(localIndex uint16) - EmitGetGlobal(globalIndex uint16) - EmitSetGlobal(globalIndex uint16) - EmitGetField() - EmitSetField() - EmitGetIndex() - EmitSetIndex() - EmitNewArray(typeIndex uint16, size uint16, isResource bool) - EmitIntAdd() - EmitIntSubtract() - EmitIntMultiply() - EmitIntDivide() - EmitIntMod() - EmitEqual() - EmitNotEqual() - EmitIntLess() - EmitIntLessOrEqual() - EmitIntGreater() - EmitIntGreaterOrEqual() - EmitUnwrap() - EmitCast(typeIndex uint16, kind opcode.CastKind) - EmitDestroy() - EmitTransfer(typeIndex uint16) - EmitNewRef(typeIndex uint16) - EmitPath(domain common.PathDomain, identifier string) - EmitNew(kind uint16, typeIndex uint16) - EmitInvoke(typeArgs []uint16) - EmitInvokeDynamic(name string, typeArgs []uint16, argCount uint16) } type BytecodeGen struct { @@ -82,161 +43,10 @@ func (g *BytecodeGen) Code() interface{} { return g.code } -func (g *BytecodeGen) EmitNil() { - opcode.EmitNil(&g.code) -} - -func (g *BytecodeGen) EmitTrue() { - opcode.EmitTrue(&g.code) -} - -func (g *BytecodeGen) EmitFalse() { - opcode.EmitFalse(&g.code) -} - -func (g *BytecodeGen) EmitDup() { - opcode.EmitDup(&g.code) -} - -func (g *BytecodeGen) EmitDrop() { - opcode.EmitDrop(&g.code) -} - -func (g *BytecodeGen) EmitGetConstant(constantIndex uint16) { - opcode.EmitGetConstant(&g.code, constantIndex) -} - -func (g *BytecodeGen) EmitJump(target uint16) int { - return opcode.EmitJump(&g.code, target) -} - -func (g *BytecodeGen) EmitJumpIfFalse(target uint16) int { - return opcode.EmitJumpIfFalse(&g.code, target) +func (g *BytecodeGen) Emit(instruction opcode.Instruction) { + instruction.Encode(&g.code) } func (g *BytecodeGen) PatchJump(offset int, newTarget uint16) { opcode.PatchJump(&g.code, offset, newTarget) } - -func (g *BytecodeGen) EmitReturnValue() { - opcode.EmitReturnValue(&g.code) -} - -func (g *BytecodeGen) EmitReturn() { - opcode.EmitReturn(&g.code) -} - -func (g *BytecodeGen) EmitGetLocal(localIndex uint16) { - opcode.EmitGetLocal(&g.code, localIndex) -} -func (g *BytecodeGen) EmitSetLocal(localIndex uint16) { - opcode.EmitSetLocal(&g.code, localIndex) -} - -func (g *BytecodeGen) EmitGetGlobal(globalIndex uint16) { - opcode.EmitGetGlobal(&g.code, globalIndex) -} - -func (g *BytecodeGen) EmitSetGlobal(globalIndex uint16) { - opcode.EmitSetGlobal(&g.code, globalIndex) -} - -func (g *BytecodeGen) EmitGetField() { - opcode.EmitGetField(&g.code) -} - -func (g *BytecodeGen) EmitSetField() { - opcode.EmitSetField(&g.code) -} - -func (g *BytecodeGen) EmitGetIndex() { - opcode.EmitGetIndex(&g.code) -} - -func (g *BytecodeGen) EmitSetIndex() { - opcode.EmitSetIndex(&g.code) -} - -func (g *BytecodeGen) EmitNewArray(typeIndex uint16, size uint16, isResource bool) { - opcode.EmitNewArray(&g.code, typeIndex, size, isResource) -} - -func (g *BytecodeGen) EmitIntAdd() { - opcode.EmitIntAdd(&g.code) -} - -func (g *BytecodeGen) EmitIntSubtract() { - opcode.EmitIntSubtract(&g.code) -} - -func (g *BytecodeGen) EmitIntMultiply() { - opcode.EmitIntMultiply(&g.code) -} - -func (g *BytecodeGen) EmitIntDivide() { - opcode.EmitIntDivide(&g.code) -} - -func (g *BytecodeGen) EmitIntMod() { - opcode.EmitIntMod(&g.code) -} - -func (g *BytecodeGen) EmitEqual() { - opcode.EmitEqual(&g.code) -} - -func (g *BytecodeGen) EmitNotEqual() { - opcode.EmitNotEqual(&g.code) -} - -func (g *BytecodeGen) EmitIntLess() { - opcode.EmitIntLess(&g.code) -} - -func (g *BytecodeGen) EmitIntLessOrEqual() { - opcode.EmitIntLessOrEqual(&g.code) -} - -func (g *BytecodeGen) EmitIntGreater() { - opcode.EmitIntGreater(&g.code) -} - -func (g *BytecodeGen) EmitIntGreaterOrEqual() { - opcode.EmitIntGreaterOrEqual(&g.code) -} - -func (g *BytecodeGen) EmitUnwrap() { - opcode.EmitUnwrap(&g.code) -} - -func (g *BytecodeGen) EmitCast(typeIndex uint16, kind opcode.CastKind) { - opcode.EmitCast(&g.code, typeIndex, kind) -} - -func (g *BytecodeGen) EmitDestroy() { - opcode.EmitDestroy(&g.code) -} - -func (g *BytecodeGen) EmitTransfer(typeIndex uint16) { - opcode.EmitTransfer(&g.code, typeIndex) -} - -func (g *BytecodeGen) EmitNewRef(typeIndex uint16) { - opcode.EmitNewRef(&g.code, typeIndex) -} - -func (g *BytecodeGen) EmitPath(domain common.PathDomain, identifier string) { - opcode.EmitPath(&g.code, domain, identifier) -} - -func (g *BytecodeGen) EmitNew(kind uint16, typeIndex uint16) { - opcode.EmitNew(&g.code, kind, typeIndex) -} - -func (g *BytecodeGen) EmitInvoke(typeArgs []uint16) { - opcode.EmitInvoke(&g.code, typeArgs) -} - -func (g *BytecodeGen) EmitInvokeDynamic(name string, typeArgs []uint16, argCount uint16) { - opcode.EmitInvokeDynamic(&g.code, name, typeArgs, argCount) -} diff --git a/bbq/compiler/compiler.go b/bbq/compiler/compiler.go index 7ff7f2dc9b..c932c78b2e 100644 --- a/bbq/compiler/compiler.go +++ b/bbq/compiler/compiler.go @@ -218,29 +218,41 @@ func (c *Compiler) addConstant(kind constantkind.ConstantKind, data []byte) *con func (c *Compiler) stringConstLoad(str string) { constant := c.addConstant(constantkind.String, []byte(str)) - c.currentFunction.codeGen.EmitGetConstant(constant.index) + c.currentFunction.codeGen.Emit(opcode.InstructionGetConstant{ConstantIndex: constant.index}) } func (c *Compiler) emitJump(target int) int { if target >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid jump")) } - return c.currentFunction.codeGen.EmitJump(uint16(target)) + codeGen := c.currentFunction.codeGen + offset := codeGen.Offset() + codeGen.Emit(opcode.InstructionJump{Target: uint16(target)}) + return offset } func (c *Compiler) emitUndefinedJump() int { - return c.currentFunction.codeGen.EmitJump(math.MaxUint16) + codeGen := c.currentFunction.codeGen + offset := codeGen.Offset() + codeGen.Emit(opcode.InstructionJump{Target: math.MaxUint16}) + return offset } func (c *Compiler) emitJumpIfFalse(target uint16) int { if target >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid jump")) } - return c.currentFunction.codeGen.EmitJumpIfFalse(target) + codeGen := c.currentFunction.codeGen + offset := codeGen.Offset() + codeGen.Emit(opcode.InstructionJumpIfFalse{Target: target}) + return offset } func (c *Compiler) emitUndefinedJumpIfFalse() int { - return c.currentFunction.codeGen.EmitJumpIfFalse(math.MaxUint16) + codeGen := c.currentFunction.codeGen + offset := codeGen.Offset() + codeGen.Emit(opcode.InstructionJumpIfFalse{Target: math.MaxUint16}) + return offset } func (c *Compiler) patchJump(opcodeOffset int) { @@ -512,9 +524,9 @@ func (c *Compiler) VisitReturnStatement(statement *ast.ReturnStatement) (_ struc if expression != nil { // TODO: copy c.compileExpression(expression) - c.currentFunction.codeGen.EmitReturnValue() + c.currentFunction.codeGen.Emit(opcode.InstructionReturnValue{}) } else { - c.currentFunction.codeGen.EmitReturn() + c.currentFunction.codeGen.Emit(opcode.InstructionReturn{}) } return } @@ -589,7 +601,7 @@ func (c *Compiler) VisitVariableDeclaration(declaration *ast.VariableDeclaration c.emitCheckType(varDeclTypes.TargetType) local := c.currentFunction.declareLocal(declaration.Identifier.Identifier) - c.currentFunction.codeGen.EmitSetLocal(local.index) + c.currentFunction.codeGen.Emit(opcode.InstructionSetLocal{LocalIndex: local.index}) return } @@ -604,22 +616,22 @@ func (c *Compiler) VisitAssignmentStatement(statement *ast.AssignmentStatement) varName := target.Identifier.Identifier local := c.currentFunction.findLocal(varName) if local != nil { - c.currentFunction.codeGen.EmitSetLocal(local.index) + c.currentFunction.codeGen.Emit(opcode.InstructionSetLocal{LocalIndex: local.index}) return } global := c.findGlobal(varName) - c.currentFunction.codeGen.EmitSetGlobal(global.index) + c.currentFunction.codeGen.Emit(opcode.InstructionSetGlobal{GlobalIndex: global.index}) case *ast.MemberExpression: c.compileExpression(target.Expression) c.stringConstLoad(target.Identifier.Identifier) - c.currentFunction.codeGen.EmitSetField() + c.currentFunction.codeGen.Emit(opcode.InstructionSetField{}) case *ast.IndexExpression: c.compileExpression(target.TargetExpression) c.compileExpression(target.IndexingExpression) - c.currentFunction.codeGen.EmitSetIndex() + c.currentFunction.codeGen.Emit(opcode.InstructionSetIndex{}) default: // TODO: @@ -641,7 +653,7 @@ func (c *Compiler) VisitExpressionStatement(statement *ast.ExpressionStatement) // Do nothing. Destroy operation will not produce any result. default: // Otherwise, drop the expression evaluation result. - c.currentFunction.codeGen.EmitDrop() + c.currentFunction.codeGen.Emit(opcode.InstructionDrop{}) } return @@ -654,15 +666,15 @@ func (c *Compiler) VisitVoidExpression(_ *ast.VoidExpression) (_ struct{}) { func (c *Compiler) VisitBoolExpression(expression *ast.BoolExpression) (_ struct{}) { if expression.Value { - c.currentFunction.codeGen.EmitTrue() + c.currentFunction.codeGen.Emit(opcode.InstructionTrue{}) } else { - c.currentFunction.codeGen.EmitFalse() + c.currentFunction.codeGen.Emit(opcode.InstructionFalse{}) } return } func (c *Compiler) VisitNilExpression(_ *ast.NilExpression) (_ struct{}) { - c.currentFunction.codeGen.EmitNil() + c.currentFunction.codeGen.Emit(opcode.InstructionNil{}) return } @@ -675,7 +687,7 @@ func (c *Compiler) VisitIntegerExpression(expression *ast.IntegerExpression) (_ data = leb128.AppendInt64(data, expression.Value.Int64()) constant := c.addConstant(constantKind, data) - c.currentFunction.codeGen.EmitGetConstant(constant.index) + c.currentFunction.codeGen.Emit(opcode.InstructionGetConstant{ConstantIndex: constant.index}) return } @@ -700,10 +712,12 @@ func (c *Compiler) VisitArrayExpression(array *ast.ArrayExpression) (_ struct{}) //EmitSetIndex(index) } - c.currentFunction.codeGen.EmitNewArray( - typeIndex, - uint16(size), - arrayTypes.ArrayType.IsResourceType(), + c.currentFunction.codeGen.Emit( + opcode.InstructionNewArray{ + TypeIndex: typeIndex, + Size: uint16(size), + IsResource: arrayTypes.ArrayType.IsResourceType(), + }, ) return @@ -722,12 +736,12 @@ func (c *Compiler) VisitIdentifierExpression(expression *ast.IdentifierExpressio func (c *Compiler) emitVariableLoad(name string) { local := c.currentFunction.findLocal(name) if local != nil { - c.currentFunction.codeGen.EmitGetLocal(local.index) + c.currentFunction.codeGen.Emit(opcode.InstructionGetLocal{LocalIndex: local.index}) return } global := c.findGlobal(name) - c.currentFunction.codeGen.EmitGetGlobal(global.index) + c.currentFunction.codeGen.Emit(opcode.InstructionGetGlobal{GlobalIndex: global.index}) } func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpression) (_ struct{}) { @@ -747,7 +761,7 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio c.emitVariableLoad(invokedExpr.Identifier.Identifier) typeArgs := c.loadTypeArguments(expression) - c.currentFunction.codeGen.EmitInvoke(typeArgs) + c.currentFunction.codeGen.Emit(opcode.InstructionInvoke{TypeArgs: typeArgs}) case *ast.MemberExpression: memberInfo, ok := c.Elaboration.MemberExpressionMemberAccessInfo(invokedExpr) @@ -770,7 +784,7 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio c.emitVariableLoad(funcName) typeArgs := c.loadTypeArguments(expression) - c.currentFunction.codeGen.EmitInvoke(typeArgs) + c.currentFunction.codeGen.Emit(opcode.InstructionInvoke{TypeArgs: typeArgs}) return } @@ -792,7 +806,13 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio panic(errors.NewDefaultUserError("invalid number of arguments")) } - c.currentFunction.codeGen.EmitInvokeDynamic(funcName, typeArgs, uint16(argumentCount)) + c.currentFunction.codeGen.Emit( + opcode.InstructionInvokeDynamic{ + Name: funcName, + TypeArgs: typeArgs, + ArgCount: uint16(argumentCount), + }, + ) } else { // Load function value @@ -800,7 +820,7 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio c.emitVariableLoad(funcName) typeArgs := c.loadTypeArguments(expression) - c.currentFunction.codeGen.EmitInvoke(typeArgs) + c.currentFunction.codeGen.Emit(opcode.InstructionInvoke{TypeArgs: typeArgs}) } default: panic(errors.NewUnreachableError()) @@ -872,14 +892,14 @@ func (c *Compiler) loadTypeArguments(expression *ast.InvocationExpression) []uin func (c *Compiler) VisitMemberExpression(expression *ast.MemberExpression) (_ struct{}) { c.compileExpression(expression.Expression) c.stringConstLoad(expression.Identifier.Identifier) - c.currentFunction.codeGen.EmitGetField() + c.currentFunction.codeGen.Emit(opcode.InstructionGetField{}) return } func (c *Compiler) VisitIndexExpression(expression *ast.IndexExpression) (_ struct{}) { c.compileExpression(expression.TargetExpression) c.compileExpression(expression.IndexingExpression) - c.currentFunction.codeGen.EmitGetIndex() + c.currentFunction.codeGen.Emit(opcode.InstructionGetIndex{}) return } @@ -910,48 +930,48 @@ func (c *Compiler) VisitBinaryExpression(expression *ast.BinaryExpression) (_ st case ast.OperationNilCoalesce: // create a duplicate to perform the equal check. // So if the condition succeeds, then the condition's result will be at the top of the stack. - codeGen.EmitDup() + codeGen.Emit(opcode.InstructionDup{}) - codeGen.EmitNil() - codeGen.EmitEqual() + codeGen.Emit(opcode.InstructionNil{}) + codeGen.Emit(opcode.InstructionEqual{}) elseJump := c.emitUndefinedJumpIfFalse() // Drop the duplicated condition result. // It is not needed for the 'then' path. - codeGen.EmitDrop() + codeGen.Emit(opcode.InstructionDrop{}) c.compileExpression(expression.Right) thenJump := c.emitUndefinedJump() c.patchJump(elseJump) - codeGen.EmitUnwrap() + codeGen.Emit(opcode.InstructionUnwrap{}) c.patchJump(thenJump) default: c.compileExpression(expression.Right) switch expression.Operation { case ast.OperationPlus: - codeGen.EmitIntAdd() + codeGen.Emit(opcode.InstructionIntAdd{}) case ast.OperationMinus: - codeGen.EmitIntSubtract() + codeGen.Emit(opcode.InstructionIntSubtract{}) case ast.OperationMul: - codeGen.EmitIntMultiply() + codeGen.Emit(opcode.InstructionIntMultiply{}) case ast.OperationDiv: - codeGen.EmitIntDivide() + codeGen.Emit(opcode.InstructionIntDivide{}) case ast.OperationMod: - codeGen.EmitIntMod() + codeGen.Emit(opcode.InstructionIntMod{}) case ast.OperationEqual: - codeGen.EmitEqual() + codeGen.Emit(opcode.InstructionEqual{}) case ast.OperationNotEqual: - codeGen.EmitNotEqual() + codeGen.Emit(opcode.InstructionNotEqual{}) case ast.OperationLess: - codeGen.EmitIntLess() + codeGen.Emit(opcode.InstructionIntLess{}) case ast.OperationLessEqual: - codeGen.EmitIntLessOrEqual() + codeGen.Emit(opcode.InstructionIntLessOrEqual{}) case ast.OperationGreater: - codeGen.EmitIntGreater() + codeGen.Emit(opcode.InstructionIntGreater{}) case ast.OperationGreaterEqual: - codeGen.EmitIntGreaterOrEqual() + codeGen.Emit(opcode.InstructionIntGreaterOrEqual{}) default: panic(errors.NewUnreachableError()) } @@ -983,7 +1003,12 @@ func (c *Compiler) VisitCastingExpression(expression *ast.CastingExpression) (_ castKind := opcode.CastKindFrom(expression.Operation) - c.currentFunction.codeGen.EmitCast(index, castKind) + c.currentFunction.codeGen.Emit( + opcode.InstructionCast{ + TypeIndex: index, + Kind: castKind, + }, + ) return } @@ -994,7 +1019,7 @@ func (c *Compiler) VisitCreateExpression(expression *ast.CreateExpression) (_ st func (c *Compiler) VisitDestroyExpression(expression *ast.DestroyExpression) (_ struct{}) { c.compileExpression(expression.Expression) - c.currentFunction.codeGen.EmitDestroy() + c.currentFunction.codeGen.Emit(opcode.InstructionDestroy{}) return } @@ -1002,7 +1027,7 @@ func (c *Compiler) VisitReferenceExpression(expression *ast.ReferenceExpression) c.compileExpression(expression.Expression) borrowType := c.Elaboration.ReferenceExpressionBorrowType(expression) index := c.getOrAddType(borrowType) - c.currentFunction.codeGen.EmitNewRef(index) + c.currentFunction.codeGen.Emit(opcode.InstructionNewRef{TypeIndex: index}) return } @@ -1017,7 +1042,12 @@ func (c *Compiler) VisitPathExpression(expression *ast.PathExpression) (_ struct if len(identifier) >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid identifier")) } - c.currentFunction.codeGen.EmitPath(domain, identifier) + c.currentFunction.codeGen.Emit( + opcode.InstructionPath{ + Domain: domain, + Identifier: identifier, + }, + ) return } @@ -1080,7 +1110,12 @@ func (c *Compiler) compileInitializer(declaration *ast.SpecialFunctionDeclaratio typeIndex := c.getOrAddType(enclosingCompositeType) - c.currentFunction.codeGen.EmitNew(kind, typeIndex) + c.currentFunction.codeGen.Emit( + opcode.InstructionNew{ + Kind: kind, + TypeIndex: typeIndex, + }, + ) if enclosingType.Kind == common.CompositeKindContract { // During contract init, update the global variable with the newly initialized contract value. @@ -1093,20 +1128,20 @@ func (c *Compiler) compileInitializer(declaration *ast.SpecialFunctionDeclaratio // } // Duplicate the top of stack and store it in both global variable and in `self` - codeGen.EmitDup() + codeGen.Emit(opcode.InstructionDup{}) global := c.findGlobal(enclosingCompositeTypeName) - codeGen.EmitSetGlobal(global.index) + codeGen.Emit(opcode.InstructionSetGlobal{GlobalIndex: global.index}) } - codeGen.EmitSetLocal(self.index) + codeGen.Emit(opcode.InstructionSetLocal{LocalIndex: self.index}) // emit for the statements in `init()` body. c.compileFunctionBlock(declaration.FunctionDeclaration.FunctionBlock) // Constructor should return the created the struct. i.e: return `self` - codeGen.EmitGetLocal(self.index) - codeGen.EmitReturnValue() + codeGen.Emit(opcode.InstructionGetLocal{LocalIndex: self.index}) + codeGen.Emit(opcode.InstructionReturnValue{}) } func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration) (_ struct{}) { @@ -1119,12 +1154,12 @@ func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration // Manually emit a return, if there are no explicit return statements. if !declaration.FunctionBlock.HasStatements() { - c.currentFunction.codeGen.EmitReturn() + c.currentFunction.codeGen.Emit(opcode.InstructionReturn{}) } else { statements := declaration.FunctionBlock.Block.Statements lastStmt := statements[len(statements)-1] if _, isReturn := lastStmt.(*ast.ReturnStatement); !isReturn { - c.currentFunction.codeGen.EmitReturn() + c.currentFunction.codeGen.Emit(opcode.InstructionReturn{}) } } @@ -1258,7 +1293,7 @@ func (c *Compiler) patchLoop(l *loop) { func (c *Compiler) emitCheckType(targetType sema.Type) { index := c.getOrAddType(targetType) - c.currentFunction.codeGen.EmitTransfer(index) + c.currentFunction.codeGen.Emit(opcode.InstructionTransfer{TypeIndex: index}) } func (c *Compiler) getOrAddType(targetType sema.Type) uint16 { diff --git a/bbq/opcode/emit.go b/bbq/opcode/emit.go index 39ed9d8df5..0dc7e4878c 100644 --- a/bbq/opcode/emit.go +++ b/bbq/opcode/emit.go @@ -22,10 +22,12 @@ import ( "github.com/onflow/cadence/common" ) -func emitOpcode(code *[]byte, opcode Opcode) int { - offset := len(*code) +type Instruction interface { + Encode(code *[]byte) +} + +func emitOpcode(code *[]byte, opcode Opcode) { *code = append(*code, byte(opcode)) - return offset } // uint16 @@ -92,300 +94,404 @@ func emitString(code *[]byte, str string) { // True -func EmitTrue(code *[]byte) { +type InstructionTrue struct{} + +func (InstructionTrue) Encode(code *[]byte) { emitOpcode(code, True) } // False -func EmitFalse(code *[]byte) { +type InstructionFalse struct{} + +func (InstructionFalse) Encode(code *[]byte) { emitOpcode(code, False) } // Nil -func EmitNil(code *[]byte) { +type InstructionNil struct{} + +func (InstructionNil) Encode(code *[]byte) { emitOpcode(code, Nil) } // Dup -func EmitDup(code *[]byte) { +type InstructionDup struct{} + +func (InstructionDup) Encode(code *[]byte) { emitOpcode(code, Dup) } // Drop -func EmitDrop(code *[]byte) { +type InstructionDrop struct{} + +func (InstructionDrop) Encode(code *[]byte) { emitOpcode(code, Drop) } // GetConstant -func EmitGetConstant(code *[]byte, constantIndex uint16) (offset int) { - offset = emitOpcode(code, GetConstant) - emitUint16(code, constantIndex) - return offset +type InstructionGetConstant struct { + ConstantIndex uint16 } -func DecodeGetConstant(ip *uint16, code []byte) (constantIndex uint16) { - return decodeUint16(ip, code) +func (ins InstructionGetConstant) Encode(code *[]byte) { + emitOpcode(code, GetConstant) + emitUint16(code, ins.ConstantIndex) +} + +func DecodeGetConstant(ip *uint16, code []byte) (ins InstructionGetConstant) { + ins.ConstantIndex = decodeUint16(ip, code) + return ins } // Jump -func EmitJump(code *[]byte, target uint16) (offset int) { - offset = emitOpcode(code, Jump) - emitUint16(code, target) - return offset +type InstructionJump struct { + Target uint16 +} + +func (ins InstructionJump) Encode(code *[]byte) { + emitOpcode(code, Jump) + emitUint16(code, ins.Target) } -func DecodeJump(ip *uint16, code []byte) (target uint16) { - return decodeUint16(ip, code) +func DecodeJump(ip *uint16, code []byte) (ins InstructionJump) { + ins.Target = decodeUint16(ip, code) + return ins } // JumpIfFalse -func EmitJumpIfFalse(code *[]byte, target uint16) (offset int) { - offset = emitOpcode(code, JumpIfFalse) - emitUint16(code, target) - return offset +type InstructionJumpIfFalse struct { + Target uint16 } -func DecodeJumpIfFalse(ip *uint16, code []byte) (target uint16) { - return decodeUint16(ip, code) +func (ins InstructionJumpIfFalse) Encode(code *[]byte) { + emitOpcode(code, JumpIfFalse) + emitUint16(code, ins.Target) } -func PatchJump(code *[]byte, opcodeOffset int, target uint16) { - first, second := encodeUint16(target) +func DecodeJumpIfFalse(ip *uint16, code []byte) (ins InstructionJumpIfFalse) { + ins.Target = decodeUint16(ip, code) + return ins +} + +func PatchJump(code *[]byte, opcodeOffset int, newTarget uint16) { + first, second := encodeUint16(newTarget) (*code)[opcodeOffset+1] = first (*code)[opcodeOffset+2] = second } // Return -func EmitReturn(code *[]byte) int { - return emitOpcode(code, Return) +type InstructionReturn struct{} + +func (InstructionReturn) Encode(code *[]byte) { + emitOpcode(code, Return) } // ReturnValue -func EmitReturnValue(code *[]byte) int { - return emitOpcode(code, ReturnValue) +type InstructionReturnValue struct{} + +func (InstructionReturnValue) Encode(code *[]byte) { + emitOpcode(code, ReturnValue) } // GetLocal -func EmitGetLocal(code *[]byte, localIndex uint16) { +type InstructionGetLocal struct { + LocalIndex uint16 +} + +func (ins InstructionGetLocal) Encode(code *[]byte) { emitOpcode(code, GetLocal) - emitUint16(code, localIndex) + emitUint16(code, ins.LocalIndex) } -func DecodeGetLocal(ip *uint16, code []byte) (localIndex uint16) { - return decodeUint16(ip, code) +func DecodeGetLocal(ip *uint16, code []byte) (ins InstructionGetLocal) { + ins.LocalIndex = decodeUint16(ip, code) + return ins } // SetLocal -func EmitSetLocal(code *[]byte, localIndex uint16) { +type InstructionSetLocal struct { + LocalIndex uint16 +} + +func (ins InstructionSetLocal) Encode(code *[]byte) { emitOpcode(code, SetLocal) - emitUint16(code, localIndex) + emitUint16(code, ins.LocalIndex) } -func DecodeSetLocal(ip *uint16, code []byte) (localIndex uint16) { - return decodeUint16(ip, code) +func DecodeSetLocal(ip *uint16, code []byte) (ins InstructionSetLocal) { + ins.LocalIndex = decodeUint16(ip, code) + return ins } // GetGlobal -func EmitGetGlobal(code *[]byte, globalIndex uint16) { +type InstructionGetGlobal struct { + GlobalIndex uint16 +} + +func (ins InstructionGetGlobal) Encode(code *[]byte) { emitOpcode(code, GetGlobal) - emitUint16(code, globalIndex) + emitUint16(code, ins.GlobalIndex) } -func DecodeGetGlobal(ip *uint16, code []byte) (globalIndex uint16) { - return decodeUint16(ip, code) +func DecodeGetGlobal(ip *uint16, code []byte) (ins InstructionGetGlobal) { + ins.GlobalIndex = decodeUint16(ip, code) + return ins } // SetGlobal -func EmitSetGlobal(code *[]byte, globalIndex uint16) { +type InstructionSetGlobal struct { + GlobalIndex uint16 +} + +func (ins InstructionSetGlobal) Encode(code *[]byte) { emitOpcode(code, SetGlobal) - emitUint16(code, globalIndex) + emitUint16(code, ins.GlobalIndex) } -func DecodeSetGlobal(ip *uint16, code []byte) (globalIndex uint16) { - return decodeUint16(ip, code) +func DecodeSetGlobal(ip *uint16, code []byte) (ins InstructionSetGlobal) { + ins.GlobalIndex = decodeUint16(ip, code) + return ins } // GetField -func EmitGetField(code *[]byte) { +type InstructionGetField struct{} + +func (InstructionGetField) Encode(code *[]byte) { emitOpcode(code, GetField) } // SetField -func EmitSetField(code *[]byte) { +type InstructionSetField struct{} + +func (InstructionSetField) Encode(code *[]byte) { emitOpcode(code, SetField) } // GetIndex -func EmitGetIndex(code *[]byte) { +type InstructionGetIndex struct{} + +func (InstructionGetIndex) Encode(code *[]byte) { emitOpcode(code, GetIndex) } // SetIndex -func EmitSetIndex(code *[]byte) { +type InstructionSetIndex struct{} + +func (InstructionSetIndex) Encode(code *[]byte) { emitOpcode(code, SetIndex) } // NewArray -func EmitNewArray(code *[]byte, typeIndex uint16, size uint16, isResource bool) { +type InstructionNewArray struct { + TypeIndex uint16 + Size uint16 + IsResource bool +} + +func (ins InstructionNewArray) Encode(code *[]byte) { emitOpcode(code, NewArray) - emitUint16(code, typeIndex) - emitUint16(code, size) - emitBool(code, isResource) + emitUint16(code, ins.TypeIndex) + emitUint16(code, ins.Size) + emitBool(code, ins.IsResource) } -func DecodeNewArray(ip *uint16, code []byte) (typeIndex uint16, size uint16, isResource bool) { - typeIndex = decodeUint16(ip, code) - size = decodeUint16(ip, code) - isResource = decodeBool(ip, code) - return typeIndex, size, isResource +func DecodeNewArray(ip *uint16, code []byte) (ins InstructionNewArray) { + ins.TypeIndex = decodeUint16(ip, code) + ins.Size = decodeUint16(ip, code) + ins.IsResource = decodeBool(ip, code) + return ins } // IntAdd -func EmitIntAdd(code *[]byte) { +type InstructionIntAdd struct{} + +func (InstructionIntAdd) Encode(code *[]byte) { emitOpcode(code, IntAdd) } // IntSubtract -func EmitIntSubtract(code *[]byte) { +type InstructionIntSubtract struct{} + +func (InstructionIntSubtract) Encode(code *[]byte) { emitOpcode(code, IntSubtract) } // IntMultiply -func EmitIntMultiply(code *[]byte) { +type InstructionIntMultiply struct{} + +func (InstructionIntMultiply) Encode(code *[]byte) { emitOpcode(code, IntMultiply) } // IntDivide -func EmitIntDivide(code *[]byte) { +type InstructionIntDivide struct{} + +func (InstructionIntDivide) Encode(code *[]byte) { emitOpcode(code, IntDivide) } // IntMod -func EmitIntMod(code *[]byte) { +type InstructionIntMod struct{} + +func (InstructionIntMod) Encode(code *[]byte) { emitOpcode(code, IntMod) } -// IntEqual +// Equal + +type InstructionEqual struct{} -func EmitEqual(code *[]byte) { +func (InstructionEqual) Encode(code *[]byte) { emitOpcode(code, Equal) } -// IntNotEqual +// NotEqual -func EmitNotEqual(code *[]byte) { +type InstructionNotEqual struct{} + +func (InstructionNotEqual) Encode(code *[]byte) { emitOpcode(code, NotEqual) } // IntLess -func EmitIntLess(code *[]byte) { +type InstructionIntLess struct{} + +func (InstructionIntLess) Encode(code *[]byte) { emitOpcode(code, IntLess) } // IntLessOrEqual -func EmitIntLessOrEqual(code *[]byte) { +type InstructionIntLessOrEqual struct{} + +func (InstructionIntLessOrEqual) Encode(code *[]byte) { emitOpcode(code, IntLessOrEqual) } // IntGreater -func EmitIntGreater(code *[]byte) { +type InstructionIntGreater struct{} + +func (InstructionIntGreater) Encode(code *[]byte) { emitOpcode(code, IntGreater) } // IntGreaterOrEqual -func EmitIntGreaterOrEqual(code *[]byte) { +type InstructionIntGreaterOrEqual struct{} + +func (InstructionIntGreaterOrEqual) Encode(code *[]byte) { emitOpcode(code, IntGreaterOrEqual) } // Unwrap -func EmitUnwrap(code *[]byte) { +type InstructionUnwrap struct{} + +func (InstructionUnwrap) Encode(code *[]byte) { emitOpcode(code, Unwrap) } // Cast -func EmitCast(code *[]byte, typeIndex uint16, kind CastKind) { +type InstructionCast struct { + TypeIndex uint16 + Kind CastKind +} + +func (ins InstructionCast) Encode(code *[]byte) { emitOpcode(code, Cast) - emitUint16(code, typeIndex) - emitByte(code, byte(kind)) + emitUint16(code, ins.TypeIndex) + emitByte(code, byte(ins.Kind)) } -func DecodeCast(ip *uint16, code []byte) (typeIndex uint16, kind CastKind) { - typeIndex = decodeUint16(ip, code) - kind = CastKind(decodeByte(ip, code)) - return typeIndex, kind +func DecodeCast(ip *uint16, code []byte) (ins InstructionCast) { + ins.TypeIndex = decodeUint16(ip, code) + ins.Kind = CastKind(decodeByte(ip, code)) + return ins } // Destroy -func EmitDestroy(code *[]byte) { +type InstructionDestroy struct{} + +func (InstructionDestroy) Encode(code *[]byte) { emitOpcode(code, Destroy) } // Transfer -func EmitTransfer(code *[]byte, typeIndex uint16) { +type InstructionTransfer struct { + TypeIndex uint16 +} + +func (ins InstructionTransfer) Encode(code *[]byte) { emitOpcode(code, Transfer) - emitUint16(code, typeIndex) + emitUint16(code, ins.TypeIndex) } -func DecodeTransfer(ip *uint16, code []byte) (typeIndex uint16) { - return decodeUint16(ip, code) +func DecodeTransfer(ip *uint16, code []byte) (ins InstructionTransfer) { + ins.TypeIndex = decodeUint16(ip, code) + return ins } // NewRef -func EmitNewRef(code *[]byte, typeIndex uint16) { +type InstructionNewRef struct { + TypeIndex uint16 +} + +func (ins InstructionNewRef) Encode(code *[]byte) { emitOpcode(code, NewRef) - emitUint16(code, typeIndex) + emitUint16(code, ins.TypeIndex) } -func DecodeNewRef(ip *uint16, code []byte) (typeIndex uint16) { - return decodeUint16(ip, code) +func DecodeNewRef(ip *uint16, code []byte) (ins InstructionNewRef) { + ins.TypeIndex = decodeUint16(ip, code) + return ins } // Path -func EmitPath(code *[]byte, domain common.PathDomain, identifier string) { - emitOpcode(code, Path) - - *code = append(*code, byte(domain)) +type InstructionPath struct { + Domain common.PathDomain + Identifier string +} - emitString(code, identifier) +func (ins InstructionPath) Encode(code *[]byte) { + emitOpcode(code, Path) + emitByte(code, byte(ins.Domain)) + emitString(code, ins.Identifier) } -func DecodePath(ip *uint16, code []byte) (domain byte, identifier string) { - domain = decodeByte(ip, code) - identifier = decodeString(ip, code) - return domain, identifier +func DecodePath(ip *uint16, code []byte) (ins InstructionPath) { + ins.Domain = common.PathDomain(decodeByte(ip, code)) + ins.Identifier = decodeString(ip, code) + return ins } // Type arguments @@ -408,41 +514,57 @@ func decodeTypeArgs(ip *uint16, code []byte) (typeArgs []uint16) { // Invoke -func EmitInvoke(code *[]byte, typeArgs []uint16) { +type InstructionInvoke struct { + TypeArgs []uint16 +} + +func (ins InstructionInvoke) Encode(code *[]byte) { emitOpcode(code, Invoke) - emitTypeArgs(code, typeArgs) + emitTypeArgs(code, ins.TypeArgs) } -func DecodeInvoke(ip *uint16, code []byte) (typeArgs []uint16) { - return decodeTypeArgs(ip, code) +func DecodeInvoke(ip *uint16, code []byte) (ins InstructionInvoke) { + ins.TypeArgs = decodeTypeArgs(ip, code) + return ins } // New -func EmitNew(code *[]byte, kind uint16, typeIndex uint16) { +type InstructionNew struct { + Kind uint16 + TypeIndex uint16 +} + +func (ins InstructionNew) Encode(code *[]byte) { emitOpcode(code, New) - emitUint16(code, kind) - emitUint16(code, typeIndex) + emitUint16(code, ins.Kind) + emitUint16(code, ins.TypeIndex) } -func DecodeNew(ip *uint16, code []byte) (kind uint16, typeIndex uint16) { - kind = decodeUint16(ip, code) - typeIndex = decodeUint16(ip, code) - return kind, typeIndex +func DecodeNew(ip *uint16, code []byte) (ins InstructionNew) { + ins.Kind = decodeUint16(ip, code) + ins.TypeIndex = decodeUint16(ip, code) + return ins } // InvokeDynamic -func EmitInvokeDynamic(code *[]byte, name string, typeArgs []uint16, argCount uint16) { +type InstructionInvokeDynamic struct { + Name string + TypeArgs []uint16 + ArgCount uint16 +} + +func (ins InstructionInvokeDynamic) Encode(code *[]byte) { emitOpcode(code, InvokeDynamic) - emitString(code, name) - emitTypeArgs(code, typeArgs) - emitUint16(code, argCount) + emitString(code, ins.Name) + emitTypeArgs(code, ins.TypeArgs) + emitUint16(code, ins.ArgCount) } -func DecodeInvokeDynamic(ip *uint16, code []byte) (name string, typeArgs []uint16, argCount uint16) { - name = decodeString(ip, code) - typeArgs = decodeTypeArgs(ip, code) - argCount = decodeUint16(ip, code) - return name, typeArgs, argCount +func DecodeInvokeDynamic(ip *uint16, code []byte) (ins InstructionInvokeDynamic) { + ins.Name = decodeString(ip, code) + ins.TypeArgs = decodeTypeArgs(ip, code) + ins.ArgCount = decodeUint16(ip, code) + return } diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index 79b275f43b..7b79c304a5 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -311,15 +311,15 @@ func opReturn(vm *VM) { } func opJump(vm *VM) { - target := opcode.DecodeJump(&vm.ip, vm.callFrame.function.Code) - vm.ip = target + ins := opcode.DecodeJump(&vm.ip, vm.callFrame.function.Code) + vm.ip = ins.Target } func opJumpIfFalse(vm *VM) { - target := opcode.DecodeJumpIfFalse(&vm.ip, vm.callFrame.function.Code) + ins := opcode.DecodeJumpIfFalse(&vm.ip, vm.callFrame.function.Code) value := vm.pop().(BoolValue) if !value { - vm.ip = target + vm.ip = ins.Target } } @@ -361,39 +361,39 @@ func opFalse(vm *VM) { func opGetConstant(vm *VM) { callFrame := vm.callFrame - index := opcode.DecodeGetConstant(&vm.ip, callFrame.function.Code) - constant := callFrame.executable.Constants[index] + ins := opcode.DecodeGetConstant(&vm.ip, callFrame.function.Code) + constant := callFrame.executable.Constants[ins.ConstantIndex] if constant == nil { - constant = vm.initializeConstant(index) + constant = vm.initializeConstant(ins.ConstantIndex) } vm.push(constant) } func opGetLocal(vm *VM) { callFrame := vm.callFrame - index := opcode.DecodeGetLocal(&vm.ip, callFrame.function.Code) - absoluteIndex := callFrame.localsOffset + index + ins := opcode.DecodeGetLocal(&vm.ip, callFrame.function.Code) + absoluteIndex := callFrame.localsOffset + ins.LocalIndex local := vm.locals[absoluteIndex] vm.push(local) } func opSetLocal(vm *VM) { callFrame := vm.callFrame - index := opcode.DecodeSetLocal(&vm.ip, callFrame.function.Code) - absoluteIndex := callFrame.localsOffset + index + ins := opcode.DecodeSetLocal(&vm.ip, callFrame.function.Code) + absoluteIndex := callFrame.localsOffset + ins.LocalIndex vm.locals[absoluteIndex] = vm.pop() } func opGetGlobal(vm *VM) { callFrame := vm.callFrame - index := opcode.DecodeGetGlobal(&vm.ip, callFrame.function.Code) - vm.push(callFrame.executable.Globals[index]) + ins := opcode.DecodeGetGlobal(&vm.ip, callFrame.function.Code) + vm.push(callFrame.executable.Globals[ins.GlobalIndex]) } func opSetGlobal(vm *VM) { callFrame := vm.callFrame - index := opcode.DecodeSetGlobal(&vm.ip, callFrame.function.Code) - callFrame.executable.Globals[index] = vm.pop() + ins := opcode.DecodeSetGlobal(&vm.ip, callFrame.function.Code) + callFrame.executable.Globals[ins.GlobalIndex] = vm.pop() } func opSetIndex(vm *VM) { @@ -416,7 +416,7 @@ func opInvoke(vm *VM) { stackHeight := len(vm.stack) callFrame := vm.callFrame - typeArgs := opcode.DecodeInvoke(&vm.ip, callFrame.function.Code) + ins := opcode.DecodeInvoke(&vm.ip, callFrame.function.Code) switch value := value.(type) { case FunctionValue: @@ -429,7 +429,7 @@ func opInvoke(vm *VM) { parameterCount := value.ParameterCount var typeArguments []StaticType - for _, index := range typeArgs { + for _, index := range ins.TypeArgs { typeArg := vm.loadType(index) typeArguments = append(typeArguments, typeArg) } @@ -448,14 +448,14 @@ func opInvoke(vm *VM) { func opInvokeDynamic(vm *VM) { callFrame := vm.callFrame - funcName, typeArgs, argsCount := opcode.DecodeInvokeDynamic(&vm.ip, callFrame.function.Code) + ins := opcode.DecodeInvokeDynamic(&vm.ip, callFrame.function.Code) stackHeight := len(vm.stack) - receiver := vm.stack[stackHeight-int(argsCount)-1] + receiver := vm.stack[stackHeight-int(ins.ArgCount)-1] // TODO: var typeArguments []StaticType - for _, index := range typeArgs { + for _, index := range ins.TypeArgs { typeArg := vm.loadType(index) typeArguments = append(typeArguments, typeArg) } @@ -477,7 +477,7 @@ func opInvokeDynamic(vm *VM) { compositeValue := receiver.(*CompositeValue) compositeType := compositeValue.CompositeType - qualifiedFuncName := commons.TypeQualifiedName(compositeType.QualifiedIdentifier, funcName) + qualifiedFuncName := commons.TypeQualifiedName(compositeType.QualifiedIdentifier, ins.Name) var functionValue = vm.lookupFunction(compositeType.Location, qualifiedFuncName) parameterCount := int(functionValue.Function.ParameterCount) @@ -496,11 +496,11 @@ func opDup(vm *VM) { } func opNew(vm *VM) { - kind, staticTypeIndex := opcode.DecodeNew(&vm.ip, vm.callFrame.function.Code) - compositeKind := common.CompositeKind(kind) + ins := opcode.DecodeNew(&vm.ip, vm.callFrame.function.Code) + compositeKind := common.CompositeKind(ins.Kind) // decode location - staticType := vm.loadType(staticTypeIndex) + staticType := vm.loadType(ins.TypeIndex) // TODO: Support inclusive-range type compositeStaticType := staticType.(*interpreter.CompositeStaticType) @@ -543,9 +543,9 @@ func opGetField(vm *VM) { } func opTransfer(vm *VM) { - typeIndex := opcode.DecodeTransfer(&vm.ip, vm.callFrame.function.Code) + ins := opcode.DecodeTransfer(&vm.ip, vm.callFrame.function.Code) - targetType := vm.loadType(typeIndex) + targetType := vm.loadType(ins.TypeIndex) value := vm.peek() config := vm.config @@ -570,10 +570,10 @@ func opDestroy(vm *VM) { } func opPath(vm *VM) { - domain, identifier := opcode.DecodePath(&vm.ip, vm.callFrame.function.Code) + ins := opcode.DecodePath(&vm.ip, vm.callFrame.function.Code) value := PathValue{ - Domain: common.PathDomain(domain), - Identifier: identifier, + Domain: ins.Domain, + Identifier: ins.Identifier, } vm.push(value) } @@ -581,12 +581,12 @@ func opPath(vm *VM) { func opCast(vm *VM) { value := vm.pop() - typeIndex, castKind := opcode.DecodeCast(&vm.ip, vm.callFrame.function.Code) + ins := opcode.DecodeCast(&vm.ip, vm.callFrame.function.Code) - targetType := vm.loadType(typeIndex) + targetType := vm.loadType(ins.TypeIndex) // TODO: - _ = castKind + _ = ins.Kind _ = targetType vm.push(value) @@ -615,26 +615,26 @@ func opUnwrap(vm *VM) { } func opNewArray(vm *VM) { - typeIndex, size, isResource := opcode.DecodeNewArray(&vm.ip, vm.callFrame.function.Code) + ins := opcode.DecodeNewArray(&vm.ip, vm.callFrame.function.Code) - typ := vm.loadType(typeIndex).(interpreter.ArrayStaticType) + typ := vm.loadType(ins.TypeIndex).(interpreter.ArrayStaticType) - elements := make([]Value, size) + elements := make([]Value, ins.Size) // Must be inserted in the reverse, // since the stack if FILO. - for i := int(size) - 1; i >= 0; i-- { + for i := int(ins.Size) - 1; i >= 0; i-- { elements[i] = vm.pop() } - array := NewArrayValue(vm.config, typ, isResource, elements...) + array := NewArrayValue(vm.config, typ, ins.IsResource, elements...) vm.push(array) } func opNewRef(vm *VM) { - index := opcode.DecodeNewRef(&vm.ip, vm.callFrame.function.Code) + ins := opcode.DecodeNewRef(&vm.ip, vm.callFrame.function.Code) - borrowedType := vm.loadType(index).(*interpreter.ReferenceStaticType) + borrowedType := vm.loadType(ins.TypeIndex).(*interpreter.ReferenceStaticType) value := vm.pop() ref := NewEphemeralReferenceValue( From b7e9a770358947d37be3c558151790c6fbcb3cc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 11 Dec 2024 15:08:18 -0800 Subject: [PATCH 79/89] refactor instruction printing --- bbq/bytecode_printer.go | 4 +- bbq/compiler/codegen.go | 47 ++- bbq/compiler/function.go | 2 +- bbq/opcode/emit.go | 630 +++++++++++++++++++++++++++++++++++---- bbq/opcode/print.go | 203 +------------ bbq/opcode/print_test.go | 72 +++-- 6 files changed, 649 insertions(+), 309 deletions(-) diff --git a/bbq/bytecode_printer.go b/bbq/bytecode_printer.go index 9c56c649a0..356a90be47 100644 --- a/bbq/bytecode_printer.go +++ b/bbq/bytecode_printer.go @@ -19,7 +19,6 @@ package bbq import ( - "bytes" "fmt" "strings" @@ -53,8 +52,7 @@ func (p *BytecodePrinter) printFunction(function *Function) { } func (p *BytecodePrinter) printCode(code []byte) { - reader := bytes.NewReader(code) - err := opcode.PrintInstructions(&p.stringBuilder, reader) + err := opcode.PrintInstructions(&p.stringBuilder, code) if err != nil { // TODO: propagate error panic(err) diff --git a/bbq/compiler/codegen.go b/bbq/compiler/codegen.go index e815efd8c0..101505253a 100644 --- a/bbq/compiler/codegen.go +++ b/bbq/compiler/codegen.go @@ -20,6 +20,7 @@ package compiler import ( "github.com/onflow/cadence/bbq/opcode" + "github.com/onflow/cadence/errors" ) type CodeGen interface { @@ -29,24 +30,58 @@ type CodeGen interface { PatchJump(offset int, newTarget uint16) } -type BytecodeGen struct { +// ByteCodeGen is a CodeGen implementation that emits bytecode +type ByteCodeGen struct { code []byte } -var _ CodeGen = &BytecodeGen{} +var _ CodeGen = &ByteCodeGen{} -func (g *BytecodeGen) Offset() int { +func (g *ByteCodeGen) Offset() int { return len(g.code) } -func (g *BytecodeGen) Code() interface{} { +func (g *ByteCodeGen) Code() interface{} { return g.code } -func (g *BytecodeGen) Emit(instruction opcode.Instruction) { +func (g *ByteCodeGen) Emit(instruction opcode.Instruction) { instruction.Encode(&g.code) } -func (g *BytecodeGen) PatchJump(offset int, newTarget uint16) { +func (g *ByteCodeGen) PatchJump(offset int, newTarget uint16) { opcode.PatchJump(&g.code, offset, newTarget) } + +// InstructionCodeGen is a CodeGen implementation that emits opcode.Instruction +type InstructionCodeGen struct { + code []opcode.Instruction +} + +var _ CodeGen = &InstructionCodeGen{} + +func (g *InstructionCodeGen) Offset() int { + return len(g.code) +} + +func (g *InstructionCodeGen) Code() interface{} { + return g.code +} + +func (g *InstructionCodeGen) Emit(instruction opcode.Instruction) { + g.code = append(g.code, instruction) +} + +func (g *InstructionCodeGen) PatchJump(offset int, newTarget uint16) { + switch ins := g.code[offset].(type) { + case opcode.InstructionJump: + ins.Target = newTarget + g.code[offset] = ins + + case opcode.InstructionJumpIfFalse: + ins.Target = newTarget + g.code[offset] = ins + } + + panic(errors.NewUnreachableError()) +} diff --git a/bbq/compiler/function.go b/bbq/compiler/function.go index 1d43731c7c..9f8a43905b 100644 --- a/bbq/compiler/function.go +++ b/bbq/compiler/function.go @@ -38,7 +38,7 @@ func newFunction(name string, parameterCount uint16, isCompositeFunction bool) * return &function{ name: name, parameterCount: parameterCount, - codeGen: &BytecodeGen{}, + codeGen: &ByteCodeGen{}, locals: activations.NewActivations[*local](nil), isCompositeFunction: isCompositeFunction, } diff --git a/bbq/opcode/emit.go b/bbq/opcode/emit.go index 0dc7e4878c..4768682c37 100644 --- a/bbq/opcode/emit.go +++ b/bbq/opcode/emit.go @@ -19,11 +19,16 @@ package opcode import ( + "fmt" + "github.com/onflow/cadence/common" + "github.com/onflow/cadence/errors" ) type Instruction interface { Encode(code *[]byte) + String() string + Opcode() Opcode } func emitOpcode(code *[]byte, opcode Opcode) { @@ -96,40 +101,80 @@ func emitString(code *[]byte, str string) { type InstructionTrue struct{} -func (InstructionTrue) Encode(code *[]byte) { - emitOpcode(code, True) +func (InstructionTrue) Opcode() Opcode { + return True +} + +func (ins InstructionTrue) String() string { + return ins.Opcode().String() +} + +func (ins InstructionTrue) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // False type InstructionFalse struct{} -func (InstructionFalse) Encode(code *[]byte) { - emitOpcode(code, False) +func (InstructionFalse) Opcode() Opcode { + return False +} + +func (ins InstructionFalse) String() string { + return ins.Opcode().String() +} + +func (ins InstructionFalse) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // Nil type InstructionNil struct{} -func (InstructionNil) Encode(code *[]byte) { - emitOpcode(code, Nil) +func (InstructionNil) Opcode() Opcode { + return Nil +} + +func (ins InstructionNil) String() string { + return ins.Opcode().String() +} + +func (ins InstructionNil) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // Dup type InstructionDup struct{} -func (InstructionDup) Encode(code *[]byte) { - emitOpcode(code, Dup) +func (InstructionDup) Opcode() Opcode { + return Dup +} + +func (ins InstructionDup) String() string { + return ins.Opcode().String() +} + +func (ins InstructionDup) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // Drop type InstructionDrop struct{} -func (InstructionDrop) Encode(code *[]byte) { - emitOpcode(code, Drop) +func (InstructionDrop) Opcode() Opcode { + return Drop +} + +func (ins InstructionDrop) String() string { + return ins.Opcode().String() +} + +func (ins InstructionDrop) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // GetConstant @@ -138,8 +183,20 @@ type InstructionGetConstant struct { ConstantIndex uint16 } +func (InstructionGetConstant) Opcode() Opcode { + return GetConstant +} + +func (ins InstructionGetConstant) String() string { + return fmt.Sprintf( + "%s constantIndex:%d", + ins.Opcode(), + ins.ConstantIndex, + ) +} + func (ins InstructionGetConstant) Encode(code *[]byte) { - emitOpcode(code, GetConstant) + emitOpcode(code, ins.Opcode()) emitUint16(code, ins.ConstantIndex) } @@ -154,8 +211,20 @@ type InstructionJump struct { Target uint16 } +func (InstructionJump) Opcode() Opcode { + return Jump +} + +func (ins InstructionJump) String() string { + return fmt.Sprintf( + "%s target:%d", + ins.Opcode(), + ins.Target, + ) +} + func (ins InstructionJump) Encode(code *[]byte) { - emitOpcode(code, Jump) + emitOpcode(code, ins.Opcode()) emitUint16(code, ins.Target) } @@ -170,8 +239,20 @@ type InstructionJumpIfFalse struct { Target uint16 } +func (InstructionJumpIfFalse) Opcode() Opcode { + return JumpIfFalse +} + +func (ins InstructionJumpIfFalse) String() string { + return fmt.Sprintf( + "%s target:%d", + ins.Opcode(), + ins.Target, + ) +} + func (ins InstructionJumpIfFalse) Encode(code *[]byte) { - emitOpcode(code, JumpIfFalse) + emitOpcode(code, ins.Opcode()) emitUint16(code, ins.Target) } @@ -190,16 +271,32 @@ func PatchJump(code *[]byte, opcodeOffset int, newTarget uint16) { type InstructionReturn struct{} -func (InstructionReturn) Encode(code *[]byte) { - emitOpcode(code, Return) +func (InstructionReturn) Opcode() Opcode { + return Return +} + +func (ins InstructionReturn) String() string { + return ins.Opcode().String() +} + +func (ins InstructionReturn) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // ReturnValue type InstructionReturnValue struct{} -func (InstructionReturnValue) Encode(code *[]byte) { - emitOpcode(code, ReturnValue) +func (InstructionReturnValue) Opcode() Opcode { + return ReturnValue +} + +func (ins InstructionReturnValue) String() string { + return ins.Opcode().String() +} + +func (ins InstructionReturnValue) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // GetLocal @@ -208,8 +305,20 @@ type InstructionGetLocal struct { LocalIndex uint16 } +func (InstructionGetLocal) Opcode() Opcode { + return GetLocal +} + +func (ins InstructionGetLocal) String() string { + return fmt.Sprintf( + "%s localIndex:%d", + ins.Opcode(), + ins.LocalIndex, + ) +} + func (ins InstructionGetLocal) Encode(code *[]byte) { - emitOpcode(code, GetLocal) + emitOpcode(code, ins.Opcode()) emitUint16(code, ins.LocalIndex) } @@ -224,8 +333,20 @@ type InstructionSetLocal struct { LocalIndex uint16 } +func (InstructionSetLocal) Opcode() Opcode { + return SetLocal +} + +func (ins InstructionSetLocal) String() string { + return fmt.Sprintf( + "%s localIndex:%d", + ins.Opcode(), + ins.LocalIndex, + ) +} + func (ins InstructionSetLocal) Encode(code *[]byte) { - emitOpcode(code, SetLocal) + emitOpcode(code, ins.Opcode()) emitUint16(code, ins.LocalIndex) } @@ -240,8 +361,20 @@ type InstructionGetGlobal struct { GlobalIndex uint16 } +func (InstructionGetGlobal) Opcode() Opcode { + return GetGlobal +} + +func (ins InstructionGetGlobal) String() string { + return fmt.Sprintf( + "%s globalIndex:%d", + ins.Opcode(), + ins.GlobalIndex, + ) +} + func (ins InstructionGetGlobal) Encode(code *[]byte) { - emitOpcode(code, GetGlobal) + emitOpcode(code, ins.Opcode()) emitUint16(code, ins.GlobalIndex) } @@ -256,8 +389,20 @@ type InstructionSetGlobal struct { GlobalIndex uint16 } +func (InstructionSetGlobal) Opcode() Opcode { + return SetGlobal +} + +func (ins InstructionSetGlobal) String() string { + return fmt.Sprintf( + "%s globalIndex:%d", + ins.Opcode(), + ins.GlobalIndex, + ) +} + func (ins InstructionSetGlobal) Encode(code *[]byte) { - emitOpcode(code, SetGlobal) + emitOpcode(code, ins.Opcode()) emitUint16(code, ins.GlobalIndex) } @@ -270,32 +415,64 @@ func DecodeSetGlobal(ip *uint16, code []byte) (ins InstructionSetGlobal) { type InstructionGetField struct{} -func (InstructionGetField) Encode(code *[]byte) { - emitOpcode(code, GetField) +func (InstructionGetField) Opcode() Opcode { + return GetField +} + +func (ins InstructionGetField) String() string { + return ins.Opcode().String() +} + +func (ins InstructionGetField) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // SetField type InstructionSetField struct{} -func (InstructionSetField) Encode(code *[]byte) { - emitOpcode(code, SetField) +func (InstructionSetField) Opcode() Opcode { + return SetField +} + +func (ins InstructionSetField) String() string { + return ins.Opcode().String() +} + +func (ins InstructionSetField) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // GetIndex type InstructionGetIndex struct{} -func (InstructionGetIndex) Encode(code *[]byte) { - emitOpcode(code, GetIndex) +func (InstructionGetIndex) Opcode() Opcode { + return GetIndex +} + +func (ins InstructionGetIndex) String() string { + return ins.Opcode().String() +} + +func (ins InstructionGetIndex) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // SetIndex type InstructionSetIndex struct{} -func (InstructionSetIndex) Encode(code *[]byte) { - emitOpcode(code, SetIndex) +func (InstructionSetIndex) Opcode() Opcode { + return SetIndex +} + +func (ins InstructionSetIndex) String() string { + return ins.Opcode().String() +} + +func (ins InstructionSetIndex) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // NewArray @@ -306,8 +483,22 @@ type InstructionNewArray struct { IsResource bool } +func (InstructionNewArray) Opcode() Opcode { + return NewArray +} + +func (ins InstructionNewArray) String() string { + return fmt.Sprintf( + "%s typeIndex:%d size:%d isResource:%t", + ins.Opcode(), + ins.TypeIndex, + ins.Size, + ins.IsResource, + ) +} + func (ins InstructionNewArray) Encode(code *[]byte) { - emitOpcode(code, NewArray) + emitOpcode(code, ins.Opcode()) emitUint16(code, ins.TypeIndex) emitUint16(code, ins.Size) emitBool(code, ins.IsResource) @@ -324,96 +515,192 @@ func DecodeNewArray(ip *uint16, code []byte) (ins InstructionNewArray) { type InstructionIntAdd struct{} -func (InstructionIntAdd) Encode(code *[]byte) { - emitOpcode(code, IntAdd) +func (InstructionIntAdd) Opcode() Opcode { + return IntAdd +} + +func (ins InstructionIntAdd) String() string { + return ins.Opcode().String() +} + +func (ins InstructionIntAdd) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // IntSubtract type InstructionIntSubtract struct{} -func (InstructionIntSubtract) Encode(code *[]byte) { - emitOpcode(code, IntSubtract) +func (InstructionIntSubtract) Opcode() Opcode { + return IntSubtract +} + +func (ins InstructionIntSubtract) String() string { + return ins.Opcode().String() +} + +func (ins InstructionIntSubtract) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // IntMultiply type InstructionIntMultiply struct{} -func (InstructionIntMultiply) Encode(code *[]byte) { - emitOpcode(code, IntMultiply) +func (InstructionIntMultiply) Opcode() Opcode { + return IntMultiply +} + +func (ins InstructionIntMultiply) String() string { + return ins.Opcode().String() +} + +func (ins InstructionIntMultiply) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // IntDivide type InstructionIntDivide struct{} -func (InstructionIntDivide) Encode(code *[]byte) { - emitOpcode(code, IntDivide) +func (InstructionIntDivide) Opcode() Opcode { + return IntDivide +} + +func (ins InstructionIntDivide) String() string { + return ins.Opcode().String() +} + +func (ins InstructionIntDivide) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // IntMod type InstructionIntMod struct{} -func (InstructionIntMod) Encode(code *[]byte) { - emitOpcode(code, IntMod) +func (InstructionIntMod) Opcode() Opcode { + return IntMod +} + +func (ins InstructionIntMod) String() string { + return ins.Opcode().String() +} + +func (ins InstructionIntMod) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // Equal type InstructionEqual struct{} -func (InstructionEqual) Encode(code *[]byte) { - emitOpcode(code, Equal) +func (ins InstructionEqual) Opcode() Opcode { + return Equal +} + +func (ins InstructionEqual) String() string { + return ins.Opcode().String() +} + +func (ins InstructionEqual) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // NotEqual type InstructionNotEqual struct{} -func (InstructionNotEqual) Encode(code *[]byte) { - emitOpcode(code, NotEqual) +func (InstructionNotEqual) Opcode() Opcode { + return NotEqual +} + +func (ins InstructionNotEqual) String() string { + return ins.Opcode().String() +} + +func (ins InstructionNotEqual) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // IntLess type InstructionIntLess struct{} -func (InstructionIntLess) Encode(code *[]byte) { - emitOpcode(code, IntLess) +func (InstructionIntLess) Opcode() Opcode { + return IntLess +} + +func (ins InstructionIntLess) String() string { + return ins.Opcode().String() +} + +func (ins InstructionIntLess) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // IntLessOrEqual type InstructionIntLessOrEqual struct{} -func (InstructionIntLessOrEqual) Encode(code *[]byte) { - emitOpcode(code, IntLessOrEqual) +func (ins InstructionIntLessOrEqual) Opcode() Opcode { + return IntLessOrEqual +} + +func (ins InstructionIntLessOrEqual) String() string { + return ins.Opcode().String() +} + +func (ins InstructionIntLessOrEqual) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // IntGreater type InstructionIntGreater struct{} -func (InstructionIntGreater) Encode(code *[]byte) { - emitOpcode(code, IntGreater) +func (InstructionIntGreater) Opcode() Opcode { + return IntGreater +} + +func (ins InstructionIntGreater) String() string { + return ins.Opcode().String() +} + +func (ins InstructionIntGreater) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // IntGreaterOrEqual type InstructionIntGreaterOrEqual struct{} -func (InstructionIntGreaterOrEqual) Encode(code *[]byte) { - emitOpcode(code, IntGreaterOrEqual) +func (ins InstructionIntGreaterOrEqual) Opcode() Opcode { + return IntGreaterOrEqual +} + +func (ins InstructionIntGreaterOrEqual) String() string { + return ins.Opcode().String() +} + +func (ins InstructionIntGreaterOrEqual) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // Unwrap type InstructionUnwrap struct{} -func (InstructionUnwrap) Encode(code *[]byte) { - emitOpcode(code, Unwrap) +func (InstructionUnwrap) Opcode() Opcode { + return Unwrap +} + +func (ins InstructionUnwrap) String() string { + return ins.Opcode().String() +} + +func (ins InstructionUnwrap) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // Cast @@ -423,8 +710,21 @@ type InstructionCast struct { Kind CastKind } +func (InstructionCast) Opcode() Opcode { + return Cast +} + +func (ins InstructionCast) String() string { + return fmt.Sprintf( + "%s typeIndex:%d kind:%d", + ins.Opcode(), + ins.TypeIndex, + ins.Kind, + ) +} + func (ins InstructionCast) Encode(code *[]byte) { - emitOpcode(code, Cast) + emitOpcode(code, ins.Opcode()) emitUint16(code, ins.TypeIndex) emitByte(code, byte(ins.Kind)) } @@ -439,8 +739,16 @@ func DecodeCast(ip *uint16, code []byte) (ins InstructionCast) { type InstructionDestroy struct{} -func (InstructionDestroy) Encode(code *[]byte) { - emitOpcode(code, Destroy) +func (InstructionDestroy) Opcode() Opcode { + return Destroy +} + +func (ins InstructionDestroy) String() string { + return ins.Opcode().String() +} + +func (ins InstructionDestroy) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) } // Transfer @@ -449,8 +757,20 @@ type InstructionTransfer struct { TypeIndex uint16 } +func (InstructionTransfer) Opcode() Opcode { + return Transfer +} + +func (ins InstructionTransfer) String() string { + return fmt.Sprintf( + "%s typeIndex:%d", + ins.Opcode(), + ins.TypeIndex, + ) +} + func (ins InstructionTransfer) Encode(code *[]byte) { - emitOpcode(code, Transfer) + emitOpcode(code, ins.Opcode()) emitUint16(code, ins.TypeIndex) } @@ -465,8 +785,20 @@ type InstructionNewRef struct { TypeIndex uint16 } +func (InstructionNewRef) Opcode() Opcode { + return NewRef +} + +func (ins InstructionNewRef) String() string { + return fmt.Sprintf( + "%s typeIndex:%d", + ins.Opcode(), + ins.TypeIndex, + ) +} + func (ins InstructionNewRef) Encode(code *[]byte) { - emitOpcode(code, NewRef) + emitOpcode(code, ins.Opcode()) emitUint16(code, ins.TypeIndex) } @@ -482,8 +814,21 @@ type InstructionPath struct { Identifier string } +func (InstructionPath) Opcode() Opcode { + return Path +} + +func (ins InstructionPath) String() string { + return fmt.Sprintf( + "%s domain:%d identifier:%q", + ins.Opcode(), + ins.Domain, + ins.Identifier, + ) +} + func (ins InstructionPath) Encode(code *[]byte) { - emitOpcode(code, Path) + emitOpcode(code, ins.Opcode()) emitByte(code, byte(ins.Domain)) emitString(code, ins.Identifier) } @@ -518,8 +863,20 @@ type InstructionInvoke struct { TypeArgs []uint16 } +func (InstructionInvoke) Opcode() Opcode { + return Invoke +} + +func (ins InstructionInvoke) String() string { + return fmt.Sprintf( + "%s typeArgs:%v", + ins.Opcode(), + ins.TypeArgs, + ) +} + func (ins InstructionInvoke) Encode(code *[]byte) { - emitOpcode(code, Invoke) + emitOpcode(code, ins.Opcode()) emitTypeArgs(code, ins.TypeArgs) } @@ -535,8 +892,21 @@ type InstructionNew struct { TypeIndex uint16 } +func (InstructionNew) Opcode() Opcode { + return New +} + +func (ins InstructionNew) String() string { + return fmt.Sprintf( + "%s kind:%d typeIndex:%d", + ins.Opcode(), + ins.Kind, + ins.TypeIndex, + ) +} + func (ins InstructionNew) Encode(code *[]byte) { - emitOpcode(code, New) + emitOpcode(code, ins.Opcode()) emitUint16(code, ins.Kind) emitUint16(code, ins.TypeIndex) } @@ -555,8 +925,22 @@ type InstructionInvokeDynamic struct { ArgCount uint16 } +func (InstructionInvokeDynamic) Opcode() Opcode { + return InvokeDynamic +} + +func (ins InstructionInvokeDynamic) String() string { + return fmt.Sprintf( + "%s name:%q typeArgs:%v argCount:%d", + ins.Opcode(), + ins.Name, + ins.TypeArgs, + ins.ArgCount, + ) +} + func (ins InstructionInvokeDynamic) Encode(code *[]byte) { - emitOpcode(code, InvokeDynamic) + emitOpcode(code, ins.Opcode()) emitString(code, ins.Name) emitTypeArgs(code, ins.TypeArgs) emitUint16(code, ins.ArgCount) @@ -568,3 +952,123 @@ func DecodeInvokeDynamic(ip *uint16, code []byte) (ins InstructionInvokeDynamic) ins.ArgCount = decodeUint16(ip, code) return } + +// Unknown + +type InstructionUnknown struct{} + +func (InstructionUnknown) Opcode() Opcode { + return Unknown +} + +func (ins InstructionUnknown) String() string { + return ins.Opcode().String() +} + +func (ins InstructionUnknown) Encode(code *[]byte) { + emitOpcode(code, ins.Opcode()) +} + +// DecodeInstruction + +func DecodeInstruction(ip *uint16, code []byte) Instruction { + + switch Opcode(decodeByte(ip, code)) { + + case Return: + return InstructionReturn{} + case ReturnValue: + return InstructionReturnValue{} + case Jump: + return DecodeJump(ip, code) + case JumpIfFalse: + return DecodeJumpIfFalse(ip, code) + case IntAdd: + return InstructionIntAdd{} + case IntSubtract: + return InstructionIntSubtract{} + case IntMultiply: + return InstructionIntMultiply{} + case IntDivide: + return InstructionIntDivide{} + case IntMod: + return InstructionIntMod{} + case IntLess: + return InstructionIntLess{} + case IntGreater: + return InstructionIntGreater{} + case IntLessOrEqual: + return InstructionIntLessOrEqual{} + case IntGreaterOrEqual: + return InstructionIntGreaterOrEqual{} + case Equal: + return InstructionEqual{} + case NotEqual: + return InstructionNotEqual{} + case Unwrap: + return InstructionUnwrap{} + case Destroy: + return InstructionDestroy{} + case Transfer: + return DecodeTransfer(ip, code) + case Cast: + return DecodeCast(ip, code) + case True: + return InstructionTrue{} + case False: + return InstructionFalse{} + case New: + return DecodeNew(ip, code) + case Path: + return DecodePath(ip, code) + case Nil: + return InstructionNil{} + case NewArray: + return DecodeNewArray(ip, code) + case NewDictionary: + // TODO: + return nil + case NewRef: + return DecodeNewRef(ip, code) + case GetConstant: + return DecodeGetConstant(ip, code) + case GetLocal: + return DecodeGetLocal(ip, code) + case SetLocal: + return DecodeSetLocal(ip, code) + case GetGlobal: + return DecodeGetGlobal(ip, code) + case SetGlobal: + return DecodeSetGlobal(ip, code) + case GetField: + return InstructionGetField{} + case SetField: + return InstructionSetField{} + case SetIndex: + return InstructionSetIndex{} + case GetIndex: + return InstructionGetIndex{} + case Invoke: + return DecodeInvoke(ip, code) + case InvokeDynamic: + return DecodeInvokeDynamic(ip, code) + case Drop: + return InstructionDrop{} + case Dup: + return InstructionDup{} + case Unknown: + return InstructionUnknown{} + } + + panic(errors.NewUnreachableError()) +} + +func DecodeInstructions(code []byte) []Instruction { + var instructions []Instruction + var ip uint16 + for ip < uint16(len(code)) { + instruction := DecodeInstruction(&ip, code) + instructions = append(instructions, instruction) + } + return instructions +} diff --git a/bbq/opcode/print.go b/bbq/opcode/print.go index 1740bc913c..485e74b641 100644 --- a/bbq/opcode/print.go +++ b/bbq/opcode/print.go @@ -19,211 +19,18 @@ package opcode import ( - "bytes" "fmt" "strings" ) -func PrintInstructions(builder *strings.Builder, reader *bytes.Reader) error { - for reader.Len() > 0 { - offset := reader.Size() - int64(reader.Len()) - err := PrintInstruction(builder, reader) +func PrintInstructions(builder *strings.Builder, code []byte) error { + instructions := DecodeInstructions(code) + for _, instruction := range instructions { + _, err := fmt.Fprint(builder, instruction) if err != nil { - return fmt.Errorf("failed to print instruction at offset %d: %w", offset, err) + return err } builder.WriteByte('\n') } return nil } - -func PrintInstruction(builder *strings.Builder, reader *bytes.Reader) error { - - rawOpcode, err := reader.ReadByte() - if err != nil { - return fmt.Errorf("failed to read opcode: %w", err) - } - opcode := Opcode(rawOpcode) - - builder.WriteString(opcode.String()) - - switch opcode { - - // opcodes with one operand - case GetConstant, - GetLocal, - SetLocal, - GetGlobal, - SetGlobal, - Jump, - JumpIfFalse, - Transfer: - - operand, err := readIntOperand(reader) - if err != nil { - return fmt.Errorf("failed to read operand: %w", err) - } - - builder.WriteByte(' ') - _, _ = fmt.Fprint(builder, operand) - - case New: - kind, err := readIntOperand(reader) - if err != nil { - return fmt.Errorf("failed to read kind operand: %w", err) - } - - typeIndex, err := readIntOperand(reader) - if err != nil { - return fmt.Errorf("failed to read type index operand: %w", err) - } - - _, _ = fmt.Fprintf(builder, " kind:%d typeIndex:%d", kind, typeIndex) - - case Cast: - typeIndex, err := readIntOperand(reader) - if err != nil { - return fmt.Errorf("failed to read type index operand: %w", err) - } - - castKind, err := reader.ReadByte() - if err != nil { - return fmt.Errorf("failed to read cast kind operand: %w", err) - } - - _, _ = fmt.Fprintf(builder, " typeIndex:%d castKind:%d", typeIndex, castKind) - - case Path: - domain, err := reader.ReadByte() - if err != nil { - return fmt.Errorf("failed to read domain operand: %w", err) - } - - identifier, err := readStringOperand(reader) - if err != nil { - return fmt.Errorf("failed to read identifier operand: %w", err) - } - - _, _ = fmt.Fprintf(builder, " domain:%d identifier:%q", domain, identifier) - - case InvokeDynamic: - // Function name - funcName, err := readStringOperand(reader) - if err != nil { - return fmt.Errorf("failed to read function name operand: %w", err) - } - _, _ = fmt.Fprintf(builder, " funcName:%q", funcName) - - // Type parameters - err = printTypeParameters(builder, reader) - if err != nil { - return fmt.Errorf("failed to read type parameters: %w", err) - } - - // Argument count - argsCount, err := readIntOperand(reader) - if err != nil { - return fmt.Errorf("failed to read argument count operand: %w", err) - } - _, _ = fmt.Fprintf(builder, " argsCount:%d", argsCount) - - case Invoke: - err := printTypeParameters(builder, reader) - if err != nil { - return fmt.Errorf("failed to read type parameters: %w", err) - } - - // opcodes with no operands - case Unknown, - Return, - ReturnValue, - IntAdd, - IntSubtract, - IntMultiply, - IntDivide, - IntMod, - IntLess, - IntGreater, - IntLessOrEqual, - IntGreaterOrEqual, - Equal, - NotEqual, - Unwrap, - Destroy, - True, - False, - Nil, - NewArray, - NewDictionary, - NewRef, - GetField, - SetField, - SetIndex, - GetIndex, - Drop, - Dup: - // no operands - } - - return nil -} - -func readIntOperand(reader *bytes.Reader) (operand int, err error) { - first, err := reader.ReadByte() - if err != nil { - return 0, fmt.Errorf("failed to read first byte of int operand: %w", err) - } - - second, err := reader.ReadByte() - if err != nil { - return 0, fmt.Errorf("failed to read second byte of int operand: %w", err) - } - - operand = int(uint16(first)<<8 | uint16(second)) - return operand, nil -} - -func readStringOperand(reader *bytes.Reader) (operand string, err error) { - stringLength, err := readIntOperand(reader) - if err != nil { - return "", fmt.Errorf("failed to read string length of string operand: %w", err) - } - - stringBytes := make([]byte, stringLength) - readLength, err := reader.Read(stringBytes) - if err != nil { - return "", fmt.Errorf("failed to read string bytes of string operand: %w", err) - } - if readLength != stringLength { - return "", fmt.Errorf( - "failed to read all bytes of string operand: expected %d, got %d", - stringLength, - readLength, - ) - } - - return string(stringBytes), nil -} - -func printTypeParameters(builder *strings.Builder, reader *bytes.Reader) error { - typeParamCount, err := readIntOperand(reader) - if err != nil { - return fmt.Errorf("failed to read type parameter count operand: %w", err) - } - _, _ = fmt.Fprintf(builder, " typeParamCount:%d typeParams:[", typeParamCount) - - for i := 0; i < typeParamCount; i++ { - if i > 0 { - builder.WriteString(", ") - } - - typeIndex, err := readIntOperand(reader) - if err != nil { - return fmt.Errorf("failed to read type index operand: %w", err) - } - - _, _ = fmt.Fprint(builder, typeIndex) - } - builder.WriteByte(']') - - return nil -} diff --git a/bbq/opcode/print_test.go b/bbq/opcode/print_test.go index 4b63c9575e..95b862b760 100644 --- a/bbq/opcode/print_test.go +++ b/bbq/opcode/print_test.go @@ -19,7 +19,6 @@ package opcode import ( - "bytes" "strings" "testing" @@ -58,31 +57,30 @@ func TestPrintRecursionFib(t *testing.T) { byte(ReturnValue), } - const expected = `GetLocal 0 -GetConstant 0 + const expected = `GetLocal localIndex:0 +GetConstant constantIndex:0 IntLess -JumpIfFalse 14 -GetLocal 0 +JumpIfFalse target:14 +GetLocal localIndex:0 ReturnValue -GetLocal 0 -GetConstant 1 +GetLocal localIndex:0 +GetConstant constantIndex:1 IntSubtract -Transfer 0 -GetGlobal 0 -Invoke typeParamCount:0 typeParams:[] -GetLocal 0 -GetConstant 0 +Transfer typeIndex:0 +GetGlobal globalIndex:0 +Invoke typeArgs:[] +GetLocal localIndex:0 +GetConstant constantIndex:0 IntSubtract -Transfer 0 -GetGlobal 0 -Invoke typeParamCount:0 typeParams:[] +Transfer typeIndex:0 +GetGlobal globalIndex:0 +Invoke typeArgs:[] IntAdd ReturnValue ` var builder strings.Builder - reader := bytes.NewReader(code) - err := PrintInstructions(&builder, reader) + err := PrintInstructions(&builder, code) require.NoError(t, err) assert.Equal(t, expected, builder.String()) @@ -92,29 +90,33 @@ func TestPrintInstruction(t *testing.T) { t.Parallel() instructions := map[string][]byte{ - "GetConstant 258": {byte(GetConstant), 1, 2}, - "GetLocal 258": {byte(GetLocal), 1, 2}, - "SetLocal 258": {byte(SetLocal), 1, 2}, - "GetGlobal 258": {byte(GetGlobal), 1, 2}, - "SetGlobal 258": {byte(SetGlobal), 1, 2}, - "Jump 258": {byte(Jump), 1, 2}, - "JumpIfFalse 258": {byte(JumpIfFalse), 1, 2}, - "Transfer 258": {byte(Transfer), 1, 2}, + "GetConstant constantIndex:258": {byte(GetConstant), 1, 2}, + "GetLocal localIndex:258": {byte(GetLocal), 1, 2}, + "SetLocal localIndex:258": {byte(SetLocal), 1, 2}, + "GetGlobal globalIndex:258": {byte(GetGlobal), 1, 2}, + "SetGlobal globalIndex:258": {byte(SetGlobal), 1, 2}, + "Jump target:258": {byte(Jump), 1, 2}, + "JumpIfFalse target:258": {byte(JumpIfFalse), 1, 2}, + "Transfer typeIndex:258": {byte(Transfer), 1, 2}, "New kind:258 typeIndex:772": {byte(New), 1, 2, 3, 4}, - "Cast typeIndex:258 castKind:3": {byte(Cast), 1, 2, 3}, + "Cast typeIndex:258 kind:3": {byte(Cast), 1, 2, 3}, `Path domain:1 identifier:"hello"`: {byte(Path), 1, 0, 5, 'h', 'e', 'l', 'l', 'o'}, - `InvokeDynamic funcName:"abc" typeParamCount:2 typeParams:[772, 1286] argsCount:1800`: { + `InvokeDynamic name:"abc" typeArgs:[772 1286] argCount:1800`: { byte(InvokeDynamic), 0, 3, 'a', 'b', 'c', 0, 2, 3, 4, 5, 6, 7, 8, }, - "Invoke typeParamCount:2 typeParams:[772, 1286]": { + "Invoke typeArgs:[772 1286]": { byte(Invoke), 0, 2, 3, 4, 5, 6, }, + "NewRef typeIndex:258": {byte(NewRef), 1, 2}, + + "NewArray typeIndex:258 size:772 isResource:true": {byte(NewArray), 1, 2, 3, 4, 1}, + "Unknown": {byte(Unknown)}, "Return": {byte(Return)}, "ReturnValue": {byte(ReturnValue)}, @@ -134,9 +136,6 @@ func TestPrintInstruction(t *testing.T) { "True": {byte(True)}, "False": {byte(False)}, "Nil": {byte(Nil)}, - "NewArray": {byte(NewArray)}, - "NewDictionary": {byte(NewDictionary)}, - "NewRef": {byte(NewRef)}, "GetField": {byte(GetField)}, "SetField": {byte(SetField)}, "SetIndex": {byte(SetIndex)}, @@ -145,15 +144,12 @@ func TestPrintInstruction(t *testing.T) { "Dup": {byte(Dup)}, } - for expected, instruction := range instructions { + for expected, code := range instructions { t.Run(expected, func(t *testing.T) { - var builder strings.Builder - reader := bytes.NewReader(instruction) - err := PrintInstruction(&builder, reader) - require.NoError(t, err) - assert.Equal(t, 0, reader.Len()) - assert.Equal(t, expected, builder.String()) + var ip uint16 + instruction := DecodeInstruction(&ip, code) + assert.Equal(t, expected, instruction.String()) }) } } From a0c6fafeed54150b762f19a7f169ca895e49c957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 11 Dec 2024 16:48:58 -0800 Subject: [PATCH 80/89] improve file name --- bbq/opcode/{emit.go => instruction.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename bbq/opcode/{emit.go => instruction.go} (100%) diff --git a/bbq/opcode/emit.go b/bbq/opcode/instruction.go similarity index 100% rename from bbq/opcode/emit.go rename to bbq/opcode/instruction.go From a8603ec69e38b2d08debaed0f440ac674bed2bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 12 Dec 2024 11:31:59 -0800 Subject: [PATCH 81/89] use one codegen and retarget it to the current function --- bbq/compiler/codegen.go | 50 +++-- bbq/compiler/compiler.go | 336 +++++++++++++++++----------------- bbq/compiler/compiler_test.go | 8 +- bbq/compiler/function.go | 13 +- 4 files changed, 208 insertions(+), 199 deletions(-) diff --git a/bbq/compiler/codegen.go b/bbq/compiler/codegen.go index 101505253a..924a882d4c 100644 --- a/bbq/compiler/codegen.go +++ b/bbq/compiler/codegen.go @@ -23,65 +23,79 @@ import ( "github.com/onflow/cadence/errors" ) -type CodeGen interface { +type CodeGen[E any] interface { Offset() int - Code() interface{} + SetTarget(code *[]E) Emit(instruction opcode.Instruction) PatchJump(offset int, newTarget uint16) + // TODO: remove, by makeing bbq.Function generic + Assemble(code []E) []byte } // ByteCodeGen is a CodeGen implementation that emits bytecode type ByteCodeGen struct { - code []byte + target *[]byte } -var _ CodeGen = &ByteCodeGen{} +var _ CodeGen[byte] = &ByteCodeGen{} func (g *ByteCodeGen) Offset() int { - return len(g.code) + return len(*g.target) } -func (g *ByteCodeGen) Code() interface{} { - return g.code +func (g *ByteCodeGen) SetTarget(target *[]byte) { + g.target = target } func (g *ByteCodeGen) Emit(instruction opcode.Instruction) { - instruction.Encode(&g.code) + instruction.Encode(g.target) } func (g *ByteCodeGen) PatchJump(offset int, newTarget uint16) { - opcode.PatchJump(&g.code, offset, newTarget) + opcode.PatchJump(g.target, offset, newTarget) +} + +func (g *ByteCodeGen) Assemble(code []byte) []byte { + return code } // InstructionCodeGen is a CodeGen implementation that emits opcode.Instruction type InstructionCodeGen struct { - code []opcode.Instruction + target *[]opcode.Instruction } -var _ CodeGen = &InstructionCodeGen{} +var _ CodeGen[opcode.Instruction] = &InstructionCodeGen{} func (g *InstructionCodeGen) Offset() int { - return len(g.code) + return len(*g.target) } -func (g *InstructionCodeGen) Code() interface{} { - return g.code +func (g *InstructionCodeGen) SetTarget(target *[]opcode.Instruction) { + g.target = target } func (g *InstructionCodeGen) Emit(instruction opcode.Instruction) { - g.code = append(g.code, instruction) + *g.target = append(*g.target, instruction) } func (g *InstructionCodeGen) PatchJump(offset int, newTarget uint16) { - switch ins := g.code[offset].(type) { + switch ins := (*g.target)[offset].(type) { case opcode.InstructionJump: ins.Target = newTarget - g.code[offset] = ins + (*g.target)[offset] = ins case opcode.InstructionJumpIfFalse: ins.Target = newTarget - g.code[offset] = ins + (*g.target)[offset] = ins } panic(errors.NewUnreachableError()) } + +func (g *InstructionCodeGen) Assemble(code []opcode.Instruction) []byte { + var result []byte + for _, ins := range code { + ins.Encode(&result) + } + return result +} diff --git a/bbq/compiler/compiler.go b/bbq/compiler/compiler.go index c932c78b2e..34c5cd341d 100644 --- a/bbq/compiler/compiler.go +++ b/bbq/compiler/compiler.go @@ -34,15 +34,15 @@ import ( "github.com/onflow/cadence/sema" ) -type Compiler struct { +type Compiler[E any] struct { Program *ast.Program Elaboration *sema.Elaboration Config *Config - currentFunction *function + currentFunction *function[E] compositeTypeStack *Stack[*sema.CompositeType] - functions []*function + functions []*function[E] constants []*constant globals map[string]*global importedGlobals map[string]*global @@ -57,29 +57,31 @@ type Compiler struct { // TODO: initialize memoryGauge common.MemoryGauge + + codeGen CodeGen[E] } -func (c *Compiler) VisitAttachmentDeclaration(_ *ast.AttachmentDeclaration) (_ struct{}) { +func (c *Compiler[_]) VisitAttachmentDeclaration(_ *ast.AttachmentDeclaration) (_ struct{}) { //TODO implement me panic("implement me") } -func (c *Compiler) VisitEntitlementDeclaration(_ *ast.EntitlementDeclaration) (_ struct{}) { +func (c *Compiler[_]) VisitEntitlementDeclaration(_ *ast.EntitlementDeclaration) (_ struct{}) { //TODO implement me panic("implement me") } -func (c *Compiler) VisitEntitlementMappingDeclaration(_ *ast.EntitlementMappingDeclaration) (_ struct{}) { +func (c *Compiler[_]) VisitEntitlementMappingDeclaration(_ *ast.EntitlementMappingDeclaration) (_ struct{}) { //TODO implement me panic("implement me") } -func (c *Compiler) VisitRemoveStatement(_ *ast.RemoveStatement) (_ struct{}) { +func (c *Compiler[_]) VisitRemoveStatement(_ *ast.RemoveStatement) (_ struct{}) { //TODO implement me panic("implement me") } -func (c *Compiler) VisitAttachExpression(_ *ast.AttachExpression) (_ struct{}) { +func (c *Compiler[_]) VisitAttachExpression(_ *ast.AttachExpression) (_ struct{}) { //TODO implement me panic("implement me") } @@ -89,15 +91,15 @@ type constantsCacheKey struct { kind constantkind.ConstantKind } -var _ ast.DeclarationVisitor[struct{}] = &Compiler{} -var _ ast.StatementVisitor[struct{}] = &Compiler{} -var _ ast.ExpressionVisitor[struct{}] = &Compiler{} +var _ ast.DeclarationVisitor[struct{}] = &Compiler[any]{} +var _ ast.StatementVisitor[struct{}] = &Compiler[any]{} +var _ ast.ExpressionVisitor[struct{}] = &Compiler[any]{} func NewCompiler( program *ast.Program, elaboration *sema.Elaboration, -) *Compiler { - return &Compiler{ +) *Compiler[byte] { + return &Compiler[byte]{ Program: program, Elaboration: elaboration, Config: &Config{}, @@ -108,10 +110,11 @@ func NewCompiler( compositeTypeStack: &Stack[*sema.CompositeType]{ elements: make([]*sema.CompositeType, 0), }, + codeGen: &ByteCodeGen{}, } } -func (c *Compiler) findGlobal(name string) *global { +func (c *Compiler[_]) findGlobal(name string) *global { global, ok := c.globals[name] if ok { return global @@ -160,7 +163,7 @@ func (c *Compiler) findGlobal(name string) *global { return importedGlobal } -func (c *Compiler) addGlobal(name string) *global { +func (c *Compiler[_]) addGlobal(name string) *global { count := len(c.globals) if count >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid global declaration")) @@ -172,7 +175,7 @@ func (c *Compiler) addGlobal(name string) *global { return global } -func (c *Compiler) addImportedGlobal(location common.Location, name string) *global { +func (c *Compiler[_]) addImportedGlobal(location common.Location, name string) *global { // Index is not set here. It is set only if this imported global is used. global := &global{ location: location, @@ -182,16 +185,17 @@ func (c *Compiler) addImportedGlobal(location common.Location, name string) *glo return global } -func (c *Compiler) addFunction(name string, parameterCount uint16) *function { +func (c *Compiler[E]) addFunction(name string, parameterCount uint16) *function[E] { isCompositeFunction := !c.compositeTypeStack.isEmpty() - function := newFunction(name, parameterCount, isCompositeFunction) + function := newFunction[E](name, parameterCount, isCompositeFunction) c.functions = append(c.functions, function) c.currentFunction = function + c.codeGen.SetTarget(&function.code) return function } -func (c *Compiler) addConstant(kind constantkind.ConstantKind, data []byte) *constant { +func (c *Compiler[_]) addConstant(kind constantkind.ConstantKind, data []byte) *constant { count := len(c.constants) if count >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid constant declaration")) @@ -216,57 +220,53 @@ func (c *Compiler) addConstant(kind constantkind.ConstantKind, data []byte) *con return constant } -func (c *Compiler) stringConstLoad(str string) { +func (c *Compiler[_]) stringConstLoad(str string) { constant := c.addConstant(constantkind.String, []byte(str)) - c.currentFunction.codeGen.Emit(opcode.InstructionGetConstant{ConstantIndex: constant.index}) + c.codeGen.Emit(opcode.InstructionGetConstant{ConstantIndex: constant.index}) } -func (c *Compiler) emitJump(target int) int { +func (c *Compiler[_]) emitJump(target int) int { if target >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid jump")) } - codeGen := c.currentFunction.codeGen - offset := codeGen.Offset() - codeGen.Emit(opcode.InstructionJump{Target: uint16(target)}) + offset := c.codeGen.Offset() + c.codeGen.Emit(opcode.InstructionJump{Target: uint16(target)}) return offset } -func (c *Compiler) emitUndefinedJump() int { - codeGen := c.currentFunction.codeGen - offset := codeGen.Offset() - codeGen.Emit(opcode.InstructionJump{Target: math.MaxUint16}) +func (c *Compiler[_]) emitUndefinedJump() int { + offset := c.codeGen.Offset() + c.codeGen.Emit(opcode.InstructionJump{Target: math.MaxUint16}) return offset } -func (c *Compiler) emitJumpIfFalse(target uint16) int { +func (c *Compiler[_]) emitJumpIfFalse(target uint16) int { if target >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid jump")) } - codeGen := c.currentFunction.codeGen - offset := codeGen.Offset() - codeGen.Emit(opcode.InstructionJumpIfFalse{Target: target}) + offset := c.codeGen.Offset() + c.codeGen.Emit(opcode.InstructionJumpIfFalse{Target: target}) return offset } -func (c *Compiler) emitUndefinedJumpIfFalse() int { - codeGen := c.currentFunction.codeGen - offset := codeGen.Offset() - codeGen.Emit(opcode.InstructionJumpIfFalse{Target: math.MaxUint16}) +func (c *Compiler[_]) emitUndefinedJumpIfFalse() int { + offset := c.codeGen.Offset() + c.codeGen.Emit(opcode.InstructionJumpIfFalse{Target: math.MaxUint16}) return offset } -func (c *Compiler) patchJump(opcodeOffset int) { - count := c.currentFunction.codeGen.Offset() +func (c *Compiler[_]) patchJump(opcodeOffset int) { + count := c.codeGen.Offset() if count == 0 { panic(errors.NewUnreachableError()) } if count >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid jump")) } - c.currentFunction.codeGen.PatchJump(opcodeOffset, uint16(count)) + c.codeGen.PatchJump(opcodeOffset, uint16(count)) } -func (c *Compiler) pushLoop(start int) { +func (c *Compiler[_]) pushLoop(start int) { loop := &loop{ start: start, } @@ -274,7 +274,7 @@ func (c *Compiler) pushLoop(start int) { c.currentLoop = loop } -func (c *Compiler) popLoop() { +func (c *Compiler[_]) popLoop() { lastIndex := len(c.loops) - 1 l := c.loops[lastIndex] c.loops[lastIndex] = nil @@ -289,7 +289,7 @@ func (c *Compiler) popLoop() { c.currentLoop = previousLoop } -func (c *Compiler) Compile() *bbq.Program { +func (c *Compiler[_]) Compile() *bbq.Program { for _, declaration := range c.Program.ImportDeclarations() { c.compileDeclaration(declaration) @@ -332,7 +332,7 @@ func (c *Compiler) Compile() *bbq.Program { c.compileDeclaration(declaration) } - functions := c.exportFunctions() + functions := c.ExportFunctions() constants := c.exportConstants() types := c.exportTypes() imports := c.exportImports() @@ -349,7 +349,7 @@ func (c *Compiler) Compile() *bbq.Program { } } -func (c *Compiler) reserveGlobalVars( +func (c *Compiler[_]) reserveGlobalVars( compositeTypeName string, variableDecls []*ast.VariableDeclaration, specialFunctionDecls []*ast.SpecialFunctionDeclaration, @@ -402,7 +402,7 @@ func (c *Compiler) reserveGlobalVars( } } -func (c *Compiler) exportConstants() []*bbq.Constant { +func (c *Compiler[_]) exportConstants() []*bbq.Constant { constants := make([]*bbq.Constant, 0, len(c.constants)) for _, constant := range c.constants { constants = append( @@ -416,11 +416,11 @@ func (c *Compiler) exportConstants() []*bbq.Constant { return constants } -func (c *Compiler) exportTypes() [][]byte { +func (c *Compiler[_]) exportTypes() [][]byte { return c.staticTypes } -func (c *Compiler) exportImports() []*bbq.Import { +func (c *Compiler[_]) exportImports() []*bbq.Import { exportedImports := make([]*bbq.Import, 0) for _, importedGlobal := range c.usedImportedGlobals { bbqImport := &bbq.Import{ @@ -433,14 +433,14 @@ func (c *Compiler) exportImports() []*bbq.Import { return exportedImports } -func (c *Compiler) exportFunctions() []*bbq.Function { +func (c *Compiler[E]) ExportFunctions() []*bbq.Function { functions := make([]*bbq.Function, 0, len(c.functions)) for _, function := range c.functions { functions = append( functions, &bbq.Function{ Name: function.name, - Code: function.codeGen.Code().([]byte), + Code: c.codeGen.Assemble(function.code), LocalCount: function.localCount, ParameterCount: function.parameterCount, IsCompositeFunction: function.isCompositeFunction, @@ -450,7 +450,7 @@ func (c *Compiler) exportFunctions() []*bbq.Function { return functions } -func (c *Compiler) exportVariables(variableDecls []*ast.VariableDeclaration) []*bbq.Variable { +func (c *Compiler[_]) exportVariables(variableDecls []*ast.VariableDeclaration) []*bbq.Variable { variables := make([]*bbq.Variable, 0, len(c.functions)) for _, varDecl := range variableDecls { variables = append( @@ -463,7 +463,7 @@ func (c *Compiler) exportVariables(variableDecls []*ast.VariableDeclaration) []* return variables } -func (c *Compiler) exportContract() *bbq.Contract { +func (c *Compiler[_]) exportContract() *bbq.Contract { var location common.Location var name string @@ -491,18 +491,18 @@ func (c *Compiler) exportContract() *bbq.Contract { } } -func (c *Compiler) compileDeclaration(declaration ast.Declaration) { +func (c *Compiler[_]) compileDeclaration(declaration ast.Declaration) { ast.AcceptDeclaration[struct{}](declaration, c) } -func (c *Compiler) compileBlock(block *ast.Block) { +func (c *Compiler[_]) compileBlock(block *ast.Block) { // TODO: scope for _, statement := range block.Statements { c.compileStatement(statement) } } -func (c *Compiler) compileFunctionBlock(functionBlock *ast.FunctionBlock) { +func (c *Compiler[_]) compileFunctionBlock(functionBlock *ast.FunctionBlock) { // TODO: pre and post conditions, incl. interfaces if functionBlock == nil { return @@ -511,39 +511,39 @@ func (c *Compiler) compileFunctionBlock(functionBlock *ast.FunctionBlock) { c.compileBlock(functionBlock.Block) } -func (c *Compiler) compileStatement(statement ast.Statement) { +func (c *Compiler[_]) compileStatement(statement ast.Statement) { ast.AcceptStatement[struct{}](statement, c) } -func (c *Compiler) compileExpression(expression ast.Expression) { +func (c *Compiler[_]) compileExpression(expression ast.Expression) { ast.AcceptExpression[struct{}](expression, c) } -func (c *Compiler) VisitReturnStatement(statement *ast.ReturnStatement) (_ struct{}) { +func (c *Compiler[_]) VisitReturnStatement(statement *ast.ReturnStatement) (_ struct{}) { expression := statement.Expression if expression != nil { // TODO: copy c.compileExpression(expression) - c.currentFunction.codeGen.Emit(opcode.InstructionReturnValue{}) + c.codeGen.Emit(opcode.InstructionReturnValue{}) } else { - c.currentFunction.codeGen.Emit(opcode.InstructionReturn{}) + c.codeGen.Emit(opcode.InstructionReturn{}) } return } -func (c *Compiler) VisitBreakStatement(_ *ast.BreakStatement) (_ struct{}) { - offset := c.currentFunction.codeGen.Offset() +func (c *Compiler[_]) VisitBreakStatement(_ *ast.BreakStatement) (_ struct{}) { + offset := c.codeGen.Offset() c.currentLoop.breaks = append(c.currentLoop.breaks, offset) c.emitUndefinedJump() return } -func (c *Compiler) VisitContinueStatement(_ *ast.ContinueStatement) (_ struct{}) { +func (c *Compiler[_]) VisitContinueStatement(_ *ast.ContinueStatement) (_ struct{}) { c.emitJump(c.currentLoop.start) return } -func (c *Compiler) VisitIfStatement(statement *ast.IfStatement) (_ struct{}) { +func (c *Compiler[_]) VisitIfStatement(statement *ast.IfStatement) (_ struct{}) { // TODO: scope switch test := statement.Test.(type) { case ast.Expression: @@ -566,8 +566,8 @@ func (c *Compiler) VisitIfStatement(statement *ast.IfStatement) (_ struct{}) { return } -func (c *Compiler) VisitWhileStatement(statement *ast.WhileStatement) (_ struct{}) { - testOffset := c.currentFunction.codeGen.Offset() +func (c *Compiler[_]) VisitWhileStatement(statement *ast.WhileStatement) (_ struct{}) { + testOffset := c.codeGen.Offset() c.pushLoop(testOffset) c.compileExpression(statement.Test) endJump := c.emitUndefinedJumpIfFalse() @@ -578,22 +578,22 @@ func (c *Compiler) VisitWhileStatement(statement *ast.WhileStatement) (_ struct{ return } -func (c *Compiler) VisitForStatement(_ *ast.ForStatement) (_ struct{}) { +func (c *Compiler[_]) VisitForStatement(_ *ast.ForStatement) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitEmitStatement(_ *ast.EmitStatement) (_ struct{}) { +func (c *Compiler[_]) VisitEmitStatement(_ *ast.EmitStatement) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitSwitchStatement(_ *ast.SwitchStatement) (_ struct{}) { +func (c *Compiler[_]) VisitSwitchStatement(_ *ast.SwitchStatement) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitVariableDeclaration(declaration *ast.VariableDeclaration) (_ struct{}) { +func (c *Compiler[_]) VisitVariableDeclaration(declaration *ast.VariableDeclaration) (_ struct{}) { // TODO: second value c.compileExpression(declaration.Value) @@ -601,11 +601,11 @@ func (c *Compiler) VisitVariableDeclaration(declaration *ast.VariableDeclaration c.emitCheckType(varDeclTypes.TargetType) local := c.currentFunction.declareLocal(declaration.Identifier.Identifier) - c.currentFunction.codeGen.Emit(opcode.InstructionSetLocal{LocalIndex: local.index}) + c.codeGen.Emit(opcode.InstructionSetLocal{LocalIndex: local.index}) return } -func (c *Compiler) VisitAssignmentStatement(statement *ast.AssignmentStatement) (_ struct{}) { +func (c *Compiler[_]) VisitAssignmentStatement(statement *ast.AssignmentStatement) (_ struct{}) { c.compileExpression(statement.Value) assignmentTypes := c.Elaboration.AssignmentStatementTypes(statement) @@ -616,22 +616,22 @@ func (c *Compiler) VisitAssignmentStatement(statement *ast.AssignmentStatement) varName := target.Identifier.Identifier local := c.currentFunction.findLocal(varName) if local != nil { - c.currentFunction.codeGen.Emit(opcode.InstructionSetLocal{LocalIndex: local.index}) + c.codeGen.Emit(opcode.InstructionSetLocal{LocalIndex: local.index}) return } global := c.findGlobal(varName) - c.currentFunction.codeGen.Emit(opcode.InstructionSetGlobal{GlobalIndex: global.index}) + c.codeGen.Emit(opcode.InstructionSetGlobal{GlobalIndex: global.index}) case *ast.MemberExpression: c.compileExpression(target.Expression) c.stringConstLoad(target.Identifier.Identifier) - c.currentFunction.codeGen.Emit(opcode.InstructionSetField{}) + c.codeGen.Emit(opcode.InstructionSetField{}) case *ast.IndexExpression: c.compileExpression(target.TargetExpression) c.compileExpression(target.IndexingExpression) - c.currentFunction.codeGen.Emit(opcode.InstructionSetIndex{}) + c.codeGen.Emit(opcode.InstructionSetIndex{}) default: // TODO: @@ -640,12 +640,12 @@ func (c *Compiler) VisitAssignmentStatement(statement *ast.AssignmentStatement) return } -func (c *Compiler) VisitSwapStatement(_ *ast.SwapStatement) (_ struct{}) { +func (c *Compiler[_]) VisitSwapStatement(_ *ast.SwapStatement) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitExpressionStatement(statement *ast.ExpressionStatement) (_ struct{}) { +func (c *Compiler[_]) VisitExpressionStatement(statement *ast.ExpressionStatement) (_ struct{}) { c.compileExpression(statement.Expression) switch statement.Expression.(type) { @@ -653,32 +653,32 @@ func (c *Compiler) VisitExpressionStatement(statement *ast.ExpressionStatement) // Do nothing. Destroy operation will not produce any result. default: // Otherwise, drop the expression evaluation result. - c.currentFunction.codeGen.Emit(opcode.InstructionDrop{}) + c.codeGen.Emit(opcode.InstructionDrop{}) } return } -func (c *Compiler) VisitVoidExpression(_ *ast.VoidExpression) (_ struct{}) { +func (c *Compiler[_]) VisitVoidExpression(_ *ast.VoidExpression) (_ struct{}) { //TODO panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitBoolExpression(expression *ast.BoolExpression) (_ struct{}) { +func (c *Compiler[_]) VisitBoolExpression(expression *ast.BoolExpression) (_ struct{}) { if expression.Value { - c.currentFunction.codeGen.Emit(opcode.InstructionTrue{}) + c.codeGen.Emit(opcode.InstructionTrue{}) } else { - c.currentFunction.codeGen.Emit(opcode.InstructionFalse{}) + c.codeGen.Emit(opcode.InstructionFalse{}) } return } -func (c *Compiler) VisitNilExpression(_ *ast.NilExpression) (_ struct{}) { - c.currentFunction.codeGen.Emit(opcode.InstructionNil{}) +func (c *Compiler[_]) VisitNilExpression(_ *ast.NilExpression) (_ struct{}) { + c.codeGen.Emit(opcode.InstructionNil{}) return } -func (c *Compiler) VisitIntegerExpression(expression *ast.IntegerExpression) (_ struct{}) { +func (c *Compiler[_]) VisitIntegerExpression(expression *ast.IntegerExpression) (_ struct{}) { integerType := c.Elaboration.IntegerExpressionType(expression) constantKind := constantkind.FromSemaType(integerType) @@ -687,16 +687,16 @@ func (c *Compiler) VisitIntegerExpression(expression *ast.IntegerExpression) (_ data = leb128.AppendInt64(data, expression.Value.Int64()) constant := c.addConstant(constantKind, data) - c.currentFunction.codeGen.Emit(opcode.InstructionGetConstant{ConstantIndex: constant.index}) + c.codeGen.Emit(opcode.InstructionGetConstant{ConstantIndex: constant.index}) return } -func (c *Compiler) VisitFixedPointExpression(_ *ast.FixedPointExpression) (_ struct{}) { +func (c *Compiler[_]) VisitFixedPointExpression(_ *ast.FixedPointExpression) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitArrayExpression(array *ast.ArrayExpression) (_ struct{}) { +func (c *Compiler[_]) VisitArrayExpression(array *ast.ArrayExpression) (_ struct{}) { arrayTypes := c.Elaboration.ArrayExpressionTypes(array) typeIndex := c.getOrAddType(arrayTypes.ArrayType) @@ -712,7 +712,7 @@ func (c *Compiler) VisitArrayExpression(array *ast.ArrayExpression) (_ struct{}) //EmitSetIndex(index) } - c.currentFunction.codeGen.Emit( + c.codeGen.Emit( opcode.InstructionNewArray{ TypeIndex: typeIndex, Size: uint16(size), @@ -723,28 +723,28 @@ func (c *Compiler) VisitArrayExpression(array *ast.ArrayExpression) (_ struct{}) return } -func (c *Compiler) VisitDictionaryExpression(_ *ast.DictionaryExpression) (_ struct{}) { +func (c *Compiler[_]) VisitDictionaryExpression(_ *ast.DictionaryExpression) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitIdentifierExpression(expression *ast.IdentifierExpression) (_ struct{}) { +func (c *Compiler[_]) VisitIdentifierExpression(expression *ast.IdentifierExpression) (_ struct{}) { c.emitVariableLoad(expression.Identifier.Identifier) return } -func (c *Compiler) emitVariableLoad(name string) { +func (c *Compiler[_]) emitVariableLoad(name string) { local := c.currentFunction.findLocal(name) if local != nil { - c.currentFunction.codeGen.Emit(opcode.InstructionGetLocal{LocalIndex: local.index}) + c.codeGen.Emit(opcode.InstructionGetLocal{LocalIndex: local.index}) return } global := c.findGlobal(name) - c.currentFunction.codeGen.Emit(opcode.InstructionGetGlobal{GlobalIndex: global.index}) + c.codeGen.Emit(opcode.InstructionGetGlobal{GlobalIndex: global.index}) } -func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpression) (_ struct{}) { +func (c *Compiler[_]) VisitInvocationExpression(expression *ast.InvocationExpression) (_ struct{}) { // TODO: copy switch invokedExpr := expression.InvokedExpression.(type) { @@ -761,7 +761,7 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio c.emitVariableLoad(invokedExpr.Identifier.Identifier) typeArgs := c.loadTypeArguments(expression) - c.currentFunction.codeGen.Emit(opcode.InstructionInvoke{TypeArgs: typeArgs}) + c.codeGen.Emit(opcode.InstructionInvoke{TypeArgs: typeArgs}) case *ast.MemberExpression: memberInfo, ok := c.Elaboration.MemberExpressionMemberAccessInfo(invokedExpr) @@ -784,7 +784,7 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio c.emitVariableLoad(funcName) typeArgs := c.loadTypeArguments(expression) - c.currentFunction.codeGen.Emit(opcode.InstructionInvoke{TypeArgs: typeArgs}) + c.codeGen.Emit(opcode.InstructionInvoke{TypeArgs: typeArgs}) return } @@ -806,7 +806,7 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio panic(errors.NewDefaultUserError("invalid number of arguments")) } - c.currentFunction.codeGen.Emit( + c.codeGen.Emit( opcode.InstructionInvokeDynamic{ Name: funcName, TypeArgs: typeArgs, @@ -820,7 +820,7 @@ func (c *Compiler) VisitInvocationExpression(expression *ast.InvocationExpressio c.emitVariableLoad(funcName) typeArgs := c.loadTypeArguments(expression) - c.currentFunction.codeGen.Emit(opcode.InstructionInvoke{TypeArgs: typeArgs}) + c.codeGen.Emit(opcode.InstructionInvoke{TypeArgs: typeArgs}) } default: panic(errors.NewUnreachableError()) @@ -854,7 +854,7 @@ func TypeName(typ sema.Type) string { } } -func (c *Compiler) loadArguments(expression *ast.InvocationExpression) { +func (c *Compiler[_]) loadArguments(expression *ast.InvocationExpression) { invocationTypes := c.Elaboration.InvocationExpressionTypes(expression) for index, argument := range expression.Arguments { c.compileExpression(argument.Expression) @@ -868,7 +868,7 @@ func (c *Compiler) loadArguments(expression *ast.InvocationExpression) { //} } -func (c *Compiler) loadTypeArguments(expression *ast.InvocationExpression) []uint16 { +func (c *Compiler[_]) loadTypeArguments(expression *ast.InvocationExpression) []uint16 { invocationTypes := c.Elaboration.InvocationExpressionTypes(expression) typeArgsCount := invocationTypes.TypeArguments.Len() @@ -889,26 +889,26 @@ func (c *Compiler) loadTypeArguments(expression *ast.InvocationExpression) []uin return typeArgs } -func (c *Compiler) VisitMemberExpression(expression *ast.MemberExpression) (_ struct{}) { +func (c *Compiler[_]) VisitMemberExpression(expression *ast.MemberExpression) (_ struct{}) { c.compileExpression(expression.Expression) c.stringConstLoad(expression.Identifier.Identifier) - c.currentFunction.codeGen.Emit(opcode.InstructionGetField{}) + c.codeGen.Emit(opcode.InstructionGetField{}) return } -func (c *Compiler) VisitIndexExpression(expression *ast.IndexExpression) (_ struct{}) { +func (c *Compiler[_]) VisitIndexExpression(expression *ast.IndexExpression) (_ struct{}) { c.compileExpression(expression.TargetExpression) c.compileExpression(expression.IndexingExpression) - c.currentFunction.codeGen.Emit(opcode.InstructionGetIndex{}) + c.codeGen.Emit(opcode.InstructionGetIndex{}) return } -func (c *Compiler) VisitConditionalExpression(_ *ast.ConditionalExpression) (_ struct{}) { +func (c *Compiler[_]) VisitConditionalExpression(_ *ast.ConditionalExpression) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitUnaryExpression(expression *ast.UnaryExpression) (_ struct{}) { +func (c *Compiler[_]) VisitUnaryExpression(expression *ast.UnaryExpression) (_ struct{}) { switch expression.Operation { case ast.OperationMove: c.compileExpression(expression.Expression) @@ -920,58 +920,56 @@ func (c *Compiler) VisitUnaryExpression(expression *ast.UnaryExpression) (_ stru return } -func (c *Compiler) VisitBinaryExpression(expression *ast.BinaryExpression) (_ struct{}) { +func (c *Compiler[_]) VisitBinaryExpression(expression *ast.BinaryExpression) (_ struct{}) { c.compileExpression(expression.Left) // TODO: add support for other types - codeGen := c.currentFunction.codeGen - switch expression.Operation { case ast.OperationNilCoalesce: // create a duplicate to perform the equal check. // So if the condition succeeds, then the condition's result will be at the top of the stack. - codeGen.Emit(opcode.InstructionDup{}) + c.codeGen.Emit(opcode.InstructionDup{}) - codeGen.Emit(opcode.InstructionNil{}) - codeGen.Emit(opcode.InstructionEqual{}) + c.codeGen.Emit(opcode.InstructionNil{}) + c.codeGen.Emit(opcode.InstructionEqual{}) elseJump := c.emitUndefinedJumpIfFalse() // Drop the duplicated condition result. // It is not needed for the 'then' path. - codeGen.Emit(opcode.InstructionDrop{}) + c.codeGen.Emit(opcode.InstructionDrop{}) c.compileExpression(expression.Right) thenJump := c.emitUndefinedJump() c.patchJump(elseJump) - codeGen.Emit(opcode.InstructionUnwrap{}) + c.codeGen.Emit(opcode.InstructionUnwrap{}) c.patchJump(thenJump) default: c.compileExpression(expression.Right) switch expression.Operation { case ast.OperationPlus: - codeGen.Emit(opcode.InstructionIntAdd{}) + c.codeGen.Emit(opcode.InstructionIntAdd{}) case ast.OperationMinus: - codeGen.Emit(opcode.InstructionIntSubtract{}) + c.codeGen.Emit(opcode.InstructionIntSubtract{}) case ast.OperationMul: - codeGen.Emit(opcode.InstructionIntMultiply{}) + c.codeGen.Emit(opcode.InstructionIntMultiply{}) case ast.OperationDiv: - codeGen.Emit(opcode.InstructionIntDivide{}) + c.codeGen.Emit(opcode.InstructionIntDivide{}) case ast.OperationMod: - codeGen.Emit(opcode.InstructionIntMod{}) + c.codeGen.Emit(opcode.InstructionIntMod{}) case ast.OperationEqual: - codeGen.Emit(opcode.InstructionEqual{}) + c.codeGen.Emit(opcode.InstructionEqual{}) case ast.OperationNotEqual: - codeGen.Emit(opcode.InstructionNotEqual{}) + c.codeGen.Emit(opcode.InstructionNotEqual{}) case ast.OperationLess: - codeGen.Emit(opcode.InstructionIntLess{}) + c.codeGen.Emit(opcode.InstructionIntLess{}) case ast.OperationLessEqual: - codeGen.Emit(opcode.InstructionIntLessOrEqual{}) + c.codeGen.Emit(opcode.InstructionIntLessOrEqual{}) case ast.OperationGreater: - codeGen.Emit(opcode.InstructionIntGreater{}) + c.codeGen.Emit(opcode.InstructionIntGreater{}) case ast.OperationGreaterEqual: - codeGen.Emit(opcode.InstructionIntGreaterOrEqual{}) + c.codeGen.Emit(opcode.InstructionIntGreaterOrEqual{}) default: panic(errors.NewUnreachableError()) } @@ -980,22 +978,22 @@ func (c *Compiler) VisitBinaryExpression(expression *ast.BinaryExpression) (_ st return } -func (c *Compiler) VisitFunctionExpression(_ *ast.FunctionExpression) (_ struct{}) { +func (c *Compiler[_]) VisitFunctionExpression(_ *ast.FunctionExpression) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitStringExpression(expression *ast.StringExpression) (_ struct{}) { +func (c *Compiler[_]) VisitStringExpression(expression *ast.StringExpression) (_ struct{}) { c.stringConstLoad(expression.Value) return } -func (c *Compiler) VisitStringTemplateExpression(_ *ast.StringTemplateExpression) (_ struct{}) { +func (c *Compiler[_]) VisitStringTemplateExpression(_ *ast.StringTemplateExpression) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitCastingExpression(expression *ast.CastingExpression) (_ struct{}) { +func (c *Compiler[_]) VisitCastingExpression(expression *ast.CastingExpression) (_ struct{}) { c.compileExpression(expression.Expression) castingTypes := c.Elaboration.CastingExpressionTypes(expression) @@ -1003,7 +1001,7 @@ func (c *Compiler) VisitCastingExpression(expression *ast.CastingExpression) (_ castKind := opcode.CastKindFrom(expression.Operation) - c.currentFunction.codeGen.Emit( + c.codeGen.Emit( opcode.InstructionCast{ TypeIndex: index, Kind: castKind, @@ -1012,37 +1010,37 @@ func (c *Compiler) VisitCastingExpression(expression *ast.CastingExpression) (_ return } -func (c *Compiler) VisitCreateExpression(expression *ast.CreateExpression) (_ struct{}) { +func (c *Compiler[_]) VisitCreateExpression(expression *ast.CreateExpression) (_ struct{}) { c.compileExpression(expression.InvocationExpression) return } -func (c *Compiler) VisitDestroyExpression(expression *ast.DestroyExpression) (_ struct{}) { +func (c *Compiler[_]) VisitDestroyExpression(expression *ast.DestroyExpression) (_ struct{}) { c.compileExpression(expression.Expression) - c.currentFunction.codeGen.Emit(opcode.InstructionDestroy{}) + c.codeGen.Emit(opcode.InstructionDestroy{}) return } -func (c *Compiler) VisitReferenceExpression(expression *ast.ReferenceExpression) (_ struct{}) { +func (c *Compiler[_]) VisitReferenceExpression(expression *ast.ReferenceExpression) (_ struct{}) { c.compileExpression(expression.Expression) borrowType := c.Elaboration.ReferenceExpressionBorrowType(expression) index := c.getOrAddType(borrowType) - c.currentFunction.codeGen.Emit(opcode.InstructionNewRef{TypeIndex: index}) + c.codeGen.Emit(opcode.InstructionNewRef{TypeIndex: index}) return } -func (c *Compiler) VisitForceExpression(_ *ast.ForceExpression) (_ struct{}) { +func (c *Compiler[_]) VisitForceExpression(_ *ast.ForceExpression) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitPathExpression(expression *ast.PathExpression) (_ struct{}) { +func (c *Compiler[_]) VisitPathExpression(expression *ast.PathExpression) (_ struct{}) { domain := common.PathDomainFromIdentifier(expression.Domain.Identifier) identifier := expression.Identifier.Identifier if len(identifier) >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid identifier")) } - c.currentFunction.codeGen.Emit( + c.codeGen.Emit( opcode.InstructionPath{ Domain: domain, Identifier: identifier, @@ -1051,7 +1049,7 @@ func (c *Compiler) VisitPathExpression(expression *ast.PathExpression) (_ struct return } -func (c *Compiler) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunctionDeclaration) (_ struct{}) { +func (c *Compiler[_]) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunctionDeclaration) (_ struct{}) { kind := declaration.DeclarationKind() switch kind { case common.DeclarationKindInitializer: @@ -1065,7 +1063,7 @@ func (c *Compiler) VisitSpecialFunctionDeclaration(declaration *ast.SpecialFunct return } -func (c *Compiler) compileInitializer(declaration *ast.SpecialFunctionDeclaration) { +func (c *Compiler[_]) compileInitializer(declaration *ast.SpecialFunctionDeclaration) { enclosingCompositeTypeName := c.enclosingCompositeTypeFullyQualifiedName() enclosingType := c.compositeTypeStack.top() @@ -1102,15 +1100,13 @@ func (c *Compiler) compileInitializer(declaration *ast.SpecialFunctionDeclaratio enclosingCompositeType := c.compositeTypeStack.top() - codeGen := c.currentFunction.codeGen - // Write composite kind // TODO: Maybe get/include this from static-type. Then no need to provide separately. kind := uint16(enclosingCompositeType.Kind) typeIndex := c.getOrAddType(enclosingCompositeType) - c.currentFunction.codeGen.Emit( + c.codeGen.Emit( opcode.InstructionNew{ Kind: kind, TypeIndex: typeIndex, @@ -1128,23 +1124,23 @@ func (c *Compiler) compileInitializer(declaration *ast.SpecialFunctionDeclaratio // } // Duplicate the top of stack and store it in both global variable and in `self` - codeGen.Emit(opcode.InstructionDup{}) + c.codeGen.Emit(opcode.InstructionDup{}) global := c.findGlobal(enclosingCompositeTypeName) - codeGen.Emit(opcode.InstructionSetGlobal{GlobalIndex: global.index}) + c.codeGen.Emit(opcode.InstructionSetGlobal{GlobalIndex: global.index}) } - codeGen.Emit(opcode.InstructionSetLocal{LocalIndex: self.index}) + c.codeGen.Emit(opcode.InstructionSetLocal{LocalIndex: self.index}) // emit for the statements in `init()` body. c.compileFunctionBlock(declaration.FunctionDeclaration.FunctionBlock) // Constructor should return the created the struct. i.e: return `self` - codeGen.Emit(opcode.InstructionGetLocal{LocalIndex: self.index}) - codeGen.Emit(opcode.InstructionReturnValue{}) + c.codeGen.Emit(opcode.InstructionGetLocal{LocalIndex: self.index}) + c.codeGen.Emit(opcode.InstructionReturnValue{}) } -func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration) (_ struct{}) { +func (c *Compiler[_]) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration) (_ struct{}) { // TODO: handle nested functions declareReceiver := !c.compositeTypeStack.isEmpty() function := c.declareFunction(declaration, declareReceiver) @@ -1154,19 +1150,19 @@ func (c *Compiler) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration // Manually emit a return, if there are no explicit return statements. if !declaration.FunctionBlock.HasStatements() { - c.currentFunction.codeGen.Emit(opcode.InstructionReturn{}) + c.codeGen.Emit(opcode.InstructionReturn{}) } else { statements := declaration.FunctionBlock.Block.Statements lastStmt := statements[len(statements)-1] if _, isReturn := lastStmt.(*ast.ReturnStatement); !isReturn { - c.currentFunction.codeGen.Emit(opcode.InstructionReturn{}) + c.codeGen.Emit(opcode.InstructionReturn{}) } } return } -func (c *Compiler) declareFunction(declaration *ast.FunctionDeclaration, declareReceiver bool) *function { +func (c *Compiler[E]) declareFunction(declaration *ast.FunctionDeclaration, declareReceiver bool) *function[E] { enclosingCompositeTypeName := c.enclosingCompositeTypeFullyQualifiedName() functionName := commons.TypeQualifiedName(enclosingCompositeTypeName, declaration.Identifier.Identifier) @@ -1188,7 +1184,7 @@ func (c *Compiler) declareFunction(declaration *ast.FunctionDeclaration, declare return c.addFunction(functionName, uint16(parameterCount)) } -func (c *Compiler) VisitCompositeDeclaration(declaration *ast.CompositeDeclaration) (_ struct{}) { +func (c *Compiler[_]) VisitCompositeDeclaration(declaration *ast.CompositeDeclaration) (_ struct{}) { enclosingCompositeType := c.Elaboration.CompositeDeclarationType(declaration) c.compositeTypeStack.push(enclosingCompositeType) defer func() { @@ -1222,22 +1218,22 @@ func (c *Compiler) VisitCompositeDeclaration(declaration *ast.CompositeDeclarati return } -func (c *Compiler) VisitInterfaceDeclaration(_ *ast.InterfaceDeclaration) (_ struct{}) { +func (c *Compiler[_]) VisitInterfaceDeclaration(_ *ast.InterfaceDeclaration) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitFieldDeclaration(_ *ast.FieldDeclaration) (_ struct{}) { +func (c *Compiler[_]) VisitFieldDeclaration(_ *ast.FieldDeclaration) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitPragmaDeclaration(_ *ast.PragmaDeclaration) (_ struct{}) { +func (c *Compiler[_]) VisitPragmaDeclaration(_ *ast.PragmaDeclaration) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitImportDeclaration(declaration *ast.ImportDeclaration) (_ struct{}) { +func (c *Compiler[_]) VisitImportDeclaration(declaration *ast.ImportDeclaration) (_ struct{}) { resolvedLocation, err := commons.ResolveLocation( c.Config.LocationHandler, declaration.Identifiers, @@ -1274,29 +1270,29 @@ func (c *Compiler) VisitImportDeclaration(declaration *ast.ImportDeclaration) (_ return } -func (c *Compiler) VisitTransactionDeclaration(_ *ast.TransactionDeclaration) (_ struct{}) { +func (c *Compiler[_]) VisitTransactionDeclaration(_ *ast.TransactionDeclaration) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) } -func (c *Compiler) VisitEnumCaseDeclaration(_ *ast.EnumCaseDeclaration) (_ struct{}) { +func (c *Compiler[_]) VisitEnumCaseDeclaration(_ *ast.EnumCaseDeclaration) (_ struct{}) { // TODO panic(errors.NewUnreachableError()) } -func (c *Compiler) patchLoop(l *loop) { +func (c *Compiler[_]) patchLoop(l *loop) { for _, breakOffset := range l.breaks { c.patchJump(breakOffset) } } -func (c *Compiler) emitCheckType(targetType sema.Type) { +func (c *Compiler[_]) emitCheckType(targetType sema.Type) { index := c.getOrAddType(targetType) - c.currentFunction.codeGen.Emit(opcode.InstructionTransfer{TypeIndex: index}) + c.codeGen.Emit(opcode.InstructionTransfer{TypeIndex: index}) } -func (c *Compiler) getOrAddType(targetType sema.Type) uint16 { +func (c *Compiler[_]) getOrAddType(targetType sema.Type) uint16 { // Optimization: Re-use types in the pool. index, ok := c.typesInPool[targetType.ID()] if !ok { @@ -1311,7 +1307,7 @@ func (c *Compiler) getOrAddType(targetType sema.Type) uint16 { return index } -func (c *Compiler) addType(data []byte) uint16 { +func (c *Compiler[_]) addType(data []byte) uint16 { count := len(c.staticTypes) if count >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid type declaration")) @@ -1321,7 +1317,7 @@ func (c *Compiler) addType(data []byte) uint16 { return uint16(count) } -func (c *Compiler) enclosingCompositeTypeFullyQualifiedName() string { +func (c *Compiler[_]) enclosingCompositeTypeFullyQualifiedName() string { if c.compositeTypeStack.isEmpty() { return "" } @@ -1337,7 +1333,7 @@ func (c *Compiler) enclosingCompositeTypeFullyQualifiedName() string { return sb.String() } -func (c *Compiler) declareParameters(function *function, paramList *ast.ParameterList, declareReceiver bool) { +func (c *Compiler[E]) declareParameters(function *function[E], paramList *ast.ParameterList, declareReceiver bool) { if declareReceiver { // Declare receiver as `self`. // Receiver is always at the zero-th index of params. @@ -1356,7 +1352,7 @@ func (c *Compiler) declareParameters(function *function, paramList *ast.Paramete // so the code-gen would seamlessly work without having special-case anything in compiler/vm. // Transaction parameters are converted into global variables. // An initializer is generated to set parameters to above generated global variables. -func (c *Compiler) desugarTransaction(transaction *ast.TransactionDeclaration) ( +func (c *Compiler[_]) desugarTransaction(transaction *ast.TransactionDeclaration) ( *ast.CompositeDeclaration, []*ast.VariableDeclaration, *ast.FunctionDeclaration, @@ -1514,6 +1510,6 @@ var emptyInitializer = func() *ast.SpecialFunctionDeclaration { ) }() -func (c *Compiler) generateEmptyInit() { +func (c *Compiler[_]) generateEmptyInit() { c.VisitSpecialFunctionDeclaration(emptyInitializer) } diff --git a/bbq/compiler/compiler_test.go b/bbq/compiler/compiler_test.go index 3f29cc75ef..0d94cfa730 100644 --- a/bbq/compiler/compiler_test.go +++ b/bbq/compiler/compiler_test.go @@ -75,7 +75,7 @@ func TestCompileRecursionFib(t *testing.T) { byte(opcode.IntAdd), byte(opcode.ReturnValue), }, - compiler.functions[0].codeGen.Code(), + compiler.ExportFunctions()[0].Code, ) require.Equal(t, @@ -167,7 +167,7 @@ func TestCompileImperativeFib(t *testing.T) { byte(opcode.GetLocal), 0, 3, byte(opcode.ReturnValue), }, - compiler.functions[0].codeGen.Code(), + compiler.ExportFunctions()[0].Code, ) require.Equal(t, @@ -235,7 +235,7 @@ func TestCompileBreak(t *testing.T) { byte(opcode.GetLocal), 0, 0, byte(opcode.ReturnValue), }, - compiler.functions[0].codeGen.Code(), + compiler.ExportFunctions()[0].Code, ) require.Equal(t, @@ -310,7 +310,7 @@ func TestCompileContinue(t *testing.T) { byte(opcode.GetLocal), 0, 0, byte(opcode.ReturnValue), }, - compiler.functions[0].codeGen.Code(), + compiler.ExportFunctions()[0].Code, ) require.Equal(t, diff --git a/bbq/compiler/function.go b/bbq/compiler/function.go index 9f8a43905b..132b3f20af 100644 --- a/bbq/compiler/function.go +++ b/bbq/compiler/function.go @@ -25,26 +25,25 @@ import ( "github.com/onflow/cadence/errors" ) -type function struct { +type function[E any] struct { name string + code []E localCount uint16 - codeGen CodeGen locals *activations.Activations[*local] parameterCount uint16 isCompositeFunction bool } -func newFunction(name string, parameterCount uint16, isCompositeFunction bool) *function { - return &function{ +func newFunction[E any](name string, parameterCount uint16, isCompositeFunction bool) *function[E] { + return &function[E]{ name: name, parameterCount: parameterCount, - codeGen: &ByteCodeGen{}, locals: activations.NewActivations[*local](nil), isCompositeFunction: isCompositeFunction, } } -func (f *function) declareLocal(name string) *local { +func (f *function[E]) declareLocal(name string) *local { if f.localCount >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid local declaration")) } @@ -55,6 +54,6 @@ func (f *function) declareLocal(name string) *local { return local } -func (f *function) findLocal(name string) *local { +func (f *function[E]) findLocal(name string) *local { return f.locals.Find(name) } From 9e560c17dd8eaefe407822f114cb5915cd748202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 12 Dec 2024 13:34:23 -0800 Subject: [PATCH 82/89] retarget VM to use instructions, avoid bytecode encoding/decoding --- bbq/commons/handlers.go | 3 +- bbq/compiler/codegen.go | 17 +- bbq/compiler/compiler.go | 47 +++-- bbq/compiler/compiler_test.go | 8 +- bbq/compiler/config.go | 2 +- bbq/function.go | 4 +- bbq/opcode/print.go | 6 +- bbq/opcode/print_test.go | 2 +- bbq/program.go | 4 +- ...bytecode_printer.go => program_printer.go} | 33 ++-- bbq/vm/callframe.go | 30 +-- bbq/vm/executable_program.go | 10 +- bbq/vm/linker.go | 3 +- bbq/vm/test/ft_test.go | 5 +- bbq/vm/test/runtime_test.go | 3 +- bbq/vm/test/utils.go | 21 ++- bbq/vm/test/vm_bench_test.go | 33 ++-- bbq/vm/test/vm_test.go | 142 +++++++------- bbq/vm/value_function.go | 3 +- bbq/vm/vm.go | 174 ++++++++---------- 20 files changed, 269 insertions(+), 281 deletions(-) rename bbq/{bytecode_printer.go => program_printer.go} (77%) diff --git a/bbq/commons/handlers.go b/bbq/commons/handlers.go index 8c69e99e79..b39e36352c 100644 --- a/bbq/commons/handlers.go +++ b/bbq/commons/handlers.go @@ -21,11 +21,12 @@ package commons import ( "github.com/onflow/cadence/ast" "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/bbq/opcode" "github.com/onflow/cadence/common" "github.com/onflow/cadence/sema" ) -type ImportHandler func(location common.Location) *bbq.Program +type ImportHandler func(location common.Location) *bbq.Program[opcode.Instruction] type LocationHandler func(identifiers []ast.Identifier, location common.Location) ([]ResolvedLocation, error) diff --git a/bbq/compiler/codegen.go b/bbq/compiler/codegen.go index 924a882d4c..24bf7c357a 100644 --- a/bbq/compiler/codegen.go +++ b/bbq/compiler/codegen.go @@ -28,8 +28,6 @@ type CodeGen[E any] interface { SetTarget(code *[]E) Emit(instruction opcode.Instruction) PatchJump(offset int, newTarget uint16) - // TODO: remove, by makeing bbq.Function generic - Assemble(code []E) []byte } // ByteCodeGen is a CodeGen implementation that emits bytecode @@ -55,10 +53,6 @@ func (g *ByteCodeGen) PatchJump(offset int, newTarget uint16) { opcode.PatchJump(g.target, offset, newTarget) } -func (g *ByteCodeGen) Assemble(code []byte) []byte { - return code -} - // InstructionCodeGen is a CodeGen implementation that emits opcode.Instruction type InstructionCodeGen struct { target *[]opcode.Instruction @@ -87,15 +81,8 @@ func (g *InstructionCodeGen) PatchJump(offset int, newTarget uint16) { case opcode.InstructionJumpIfFalse: ins.Target = newTarget (*g.target)[offset] = ins - } - - panic(errors.NewUnreachableError()) -} -func (g *InstructionCodeGen) Assemble(code []opcode.Instruction) []byte { - var result []byte - for _, ins := range code { - ins.Encode(&result) + default: + panic(errors.NewUnreachableError()) } - return result } diff --git a/bbq/compiler/compiler.go b/bbq/compiler/compiler.go index 34c5cd341d..7a2c84b56a 100644 --- a/bbq/compiler/compiler.go +++ b/bbq/compiler/compiler.go @@ -37,7 +37,7 @@ import ( type Compiler[E any] struct { Program *ast.Program Elaboration *sema.Elaboration - Config *Config + Config *Config[E] currentFunction *function[E] compositeTypeStack *Stack[*sema.CompositeType] @@ -95,14 +95,37 @@ var _ ast.DeclarationVisitor[struct{}] = &Compiler[any]{} var _ ast.StatementVisitor[struct{}] = &Compiler[any]{} var _ ast.ExpressionVisitor[struct{}] = &Compiler[any]{} -func NewCompiler( +func NewBytecodeCompiler( program *ast.Program, elaboration *sema.Elaboration, ) *Compiler[byte] { - return &Compiler[byte]{ + return newCompiler( + program, + elaboration, + &ByteCodeGen{}, + ) +} + +func NewInstructionCompiler( + program *ast.Program, + elaboration *sema.Elaboration, +) *Compiler[opcode.Instruction] { + return newCompiler( + program, + elaboration, + &InstructionCodeGen{}, + ) +} + +func newCompiler[E any]( + program *ast.Program, + elaboration *sema.Elaboration, + codeGen CodeGen[E], +) *Compiler[E] { + return &Compiler[E]{ Program: program, Elaboration: elaboration, - Config: &Config{}, + Config: &Config[E]{}, globals: make(map[string]*global), importedGlobals: NativeFunctions(), typesInPool: make(map[sema.TypeID]uint16), @@ -110,7 +133,7 @@ func NewCompiler( compositeTypeStack: &Stack[*sema.CompositeType]{ elements: make([]*sema.CompositeType, 0), }, - codeGen: &ByteCodeGen{}, + codeGen: codeGen, } } @@ -289,14 +312,14 @@ func (c *Compiler[_]) popLoop() { c.currentLoop = previousLoop } -func (c *Compiler[_]) Compile() *bbq.Program { +func (c *Compiler[E]) Compile() *bbq.Program[E] { for _, declaration := range c.Program.ImportDeclarations() { c.compileDeclaration(declaration) } if c.Program.SoleContractInterfaceDeclaration() != nil { - return &bbq.Program{ + return &bbq.Program[E]{ Contract: c.exportContract(), } } @@ -339,7 +362,7 @@ func (c *Compiler[_]) Compile() *bbq.Program { contract := c.exportContract() variables := c.exportVariables(variableDeclarations) - return &bbq.Program{ + return &bbq.Program[E]{ Functions: functions, Constants: constants, Types: types, @@ -433,14 +456,14 @@ func (c *Compiler[_]) exportImports() []*bbq.Import { return exportedImports } -func (c *Compiler[E]) ExportFunctions() []*bbq.Function { - functions := make([]*bbq.Function, 0, len(c.functions)) +func (c *Compiler[E]) ExportFunctions() []*bbq.Function[E] { + functions := make([]*bbq.Function[E], 0, len(c.functions)) for _, function := range c.functions { functions = append( functions, - &bbq.Function{ + &bbq.Function[E]{ Name: function.name, - Code: c.codeGen.Assemble(function.code), + Code: function.code, LocalCount: function.localCount, ParameterCount: function.parameterCount, IsCompositeFunction: function.isCompositeFunction, diff --git a/bbq/compiler/compiler_test.go b/bbq/compiler/compiler_test.go index 0d94cfa730..acdf4211d6 100644 --- a/bbq/compiler/compiler_test.go +++ b/bbq/compiler/compiler_test.go @@ -43,7 +43,7 @@ func TestCompileRecursionFib(t *testing.T) { `) require.NoError(t, err) - compiler := NewCompiler(checker.Program, checker.Elaboration) + compiler := NewBytecodeCompiler(checker.Program, checker.Elaboration) program := compiler.Compile() require.Len(t, program.Functions, 1) @@ -114,7 +114,7 @@ func TestCompileImperativeFib(t *testing.T) { `) require.NoError(t, err) - compiler := NewCompiler(checker.Program, checker.Elaboration) + compiler := NewBytecodeCompiler(checker.Program, checker.Elaboration) program := compiler.Compile() require.Len(t, program.Functions, 1) @@ -203,7 +203,7 @@ func TestCompileBreak(t *testing.T) { `) require.NoError(t, err) - compiler := NewCompiler(checker.Program, checker.Elaboration) + compiler := NewBytecodeCompiler(checker.Program, checker.Elaboration) program := compiler.Compile() require.Len(t, program.Functions, 1) @@ -276,7 +276,7 @@ func TestCompileContinue(t *testing.T) { `) require.NoError(t, err) - compiler := NewCompiler(checker.Program, checker.Elaboration) + compiler := NewBytecodeCompiler(checker.Program, checker.Elaboration) program := compiler.Compile() require.Len(t, program.Functions, 1) diff --git a/bbq/compiler/config.go b/bbq/compiler/config.go index 84b0e57442..3f55fff4c9 100644 --- a/bbq/compiler/config.go +++ b/bbq/compiler/config.go @@ -22,7 +22,7 @@ import ( "github.com/onflow/cadence/bbq/commons" ) -type Config struct { +type Config[E any] struct { ImportHandler commons.ImportHandler LocationHandler commons.LocationHandler } diff --git a/bbq/function.go b/bbq/function.go index 4b7589e198..cc58cffaae 100644 --- a/bbq/function.go +++ b/bbq/function.go @@ -18,9 +18,9 @@ package bbq -type Function struct { +type Function[E any] struct { Name string - Code []byte + Code []E ParameterCount uint16 TypeParameterCount uint16 LocalCount uint16 diff --git a/bbq/opcode/print.go b/bbq/opcode/print.go index 485e74b641..5085c0bc41 100644 --- a/bbq/opcode/print.go +++ b/bbq/opcode/print.go @@ -23,8 +23,12 @@ import ( "strings" ) -func PrintInstructions(builder *strings.Builder, code []byte) error { +func PrintBytecode(builder *strings.Builder, code []byte) error { instructions := DecodeInstructions(code) + return PrintInstructions(builder, instructions) +} + +func PrintInstructions(builder *strings.Builder, instructions []Instruction) error { for _, instruction := range instructions { _, err := fmt.Fprint(builder, instruction) if err != nil { diff --git a/bbq/opcode/print_test.go b/bbq/opcode/print_test.go index 95b862b760..3b443a1a08 100644 --- a/bbq/opcode/print_test.go +++ b/bbq/opcode/print_test.go @@ -80,7 +80,7 @@ ReturnValue ` var builder strings.Builder - err := PrintInstructions(&builder, code) + err := PrintBytecode(&builder, code) require.NoError(t, err) assert.Equal(t, expected, builder.String()) diff --git a/bbq/program.go b/bbq/program.go index dba0bfe84f..1dc1aa0c09 100644 --- a/bbq/program.go +++ b/bbq/program.go @@ -18,10 +18,10 @@ package bbq -type Program struct { +type Program[E any] struct { Contract *Contract Imports []*Import - Functions []*Function + Functions []*Function[E] Constants []*Constant Variables []*Variable Types [][]byte diff --git a/bbq/bytecode_printer.go b/bbq/program_printer.go similarity index 77% rename from bbq/bytecode_printer.go rename to bbq/program_printer.go index 356a90be47..641e8a6e9c 100644 --- a/bbq/bytecode_printer.go +++ b/bbq/program_printer.go @@ -29,11 +29,24 @@ import ( "github.com/onflow/cadence/interpreter" ) -type BytecodePrinter struct { +type ProgramPrinter[E any] struct { stringBuilder strings.Builder + codePrinter func(builder *strings.Builder, code []E) error } -func (p *BytecodePrinter) PrintProgram(program *Program) string { +func NewBytecodeProgramPrinter() *ProgramPrinter[byte] { + return &ProgramPrinter[byte]{ + codePrinter: opcode.PrintBytecode, + } +} + +func NewInstructionsProgramPrinter() *ProgramPrinter[opcode.Instruction] { + return &ProgramPrinter[opcode.Instruction]{ + codePrinter: opcode.PrintInstructions, + } +} + +func (p *ProgramPrinter[E]) PrintProgram(program *Program[E]) string { p.printImports(program.Imports) p.printConstantPool(program.Constants) p.printTypePool(program.Types) @@ -46,27 +59,23 @@ func (p *BytecodePrinter) PrintProgram(program *Program) string { return p.stringBuilder.String() } -func (p *BytecodePrinter) printFunction(function *Function) { +func (p *ProgramPrinter[E]) printFunction(function *Function[E]) { p.stringBuilder.WriteString("-- " + function.Name + " --\n") - p.printCode(function.Code) -} - -func (p *BytecodePrinter) printCode(code []byte) { - err := opcode.PrintInstructions(&p.stringBuilder, code) + err := p.codePrinter(&p.stringBuilder, function.Code) if err != nil { // TODO: propagate error panic(err) } } -func (*BytecodePrinter) getIntOperand(codes []byte, i int) (operand int, endIndex int) { +func (*ProgramPrinter[_]) getIntOperand(codes []byte, i int) (operand int, endIndex int) { first := codes[i+1] last := codes[i+2] operand = int(uint16(first)<<8 | uint16(last)) return operand, i + 2 } -func (p *BytecodePrinter) printConstantPool(constants []*Constant) { +func (p *ProgramPrinter[_]) printConstantPool(constants []*Constant) { p.stringBuilder.WriteString("-- Constant Pool --\n") for index, constant := range constants { @@ -94,7 +103,7 @@ func (p *BytecodePrinter) printConstantPool(constants []*Constant) { p.stringBuilder.WriteRune('\n') } -func (p *BytecodePrinter) printTypePool(types [][]byte) { +func (p *ProgramPrinter[_]) printTypePool(types [][]byte) { p.stringBuilder.WriteString("-- Type Pool --\n") for index, typeBytes := range types { @@ -114,7 +123,7 @@ func (p *BytecodePrinter) printTypePool(types [][]byte) { p.stringBuilder.WriteRune('\n') } -func (p *BytecodePrinter) printImports(imports []*Import) { +func (p *ProgramPrinter[_]) printImports(imports []*Import) { p.stringBuilder.WriteString("-- Imports --\n") for _, impt := range imports { location := impt.Location diff --git a/bbq/vm/callframe.go b/bbq/vm/callframe.go index 0c3adfebb3..25c57d6dc1 100644 --- a/bbq/vm/callframe.go +++ b/bbq/vm/callframe.go @@ -20,38 +20,12 @@ package vm import ( "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/bbq/opcode" ) type callFrame struct { executable *ExecutableProgram localsOffset uint16 localsCount uint16 - function *bbq.Function + function *bbq.Function[opcode.Instruction] } - -// -//func (f *callFrame) getUint16() uint16 { -// first := f.function.Code[f.ip] -// last := f.function.Code[f.ip+1] -// f.ip += 2 -// return uint16(first)<<8 | uint16(last) -//} -// -//func (f *callFrame) getByte() byte { -// byt := f.function.Code[f.ip] -// f.ip++ -// return byt -//} -// -//func (f *callFrame) getBool() bool { -// byt := f.function.Code[f.ip] -// f.ip++ -// return byt == 1 -//} -// -//func (f *callFrame) getString() string { -// strLen := f.getUint16() -// str := string(f.function.Code[f.ip : f.ip+strLen]) -// f.ip += strLen -// return str -//} diff --git a/bbq/vm/executable_program.go b/bbq/vm/executable_program.go index 5e895de879..f01ed2940c 100644 --- a/bbq/vm/executable_program.go +++ b/bbq/vm/executable_program.go @@ -20,6 +20,7 @@ package vm import ( "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/bbq/opcode" "github.com/onflow/cadence/common" ) @@ -30,7 +31,7 @@ import ( // i.e: indexes used in opcodes refer to the indexes of its ExecutableProgram. type ExecutableProgram struct { Location common.Location - Program *bbq.Program + Program *bbq.Program[opcode.Instruction] Globals []Value Constants []Value StaticTypes []StaticType @@ -38,7 +39,7 @@ type ExecutableProgram struct { func NewExecutableProgram( location common.Location, - program *bbq.Program, + program *bbq.Program[opcode.Instruction], globals []Value, ) *ExecutableProgram { return &ExecutableProgram{ @@ -52,7 +53,10 @@ func NewExecutableProgram( // NewLoadedExecutableProgram returns an ExecutableProgram with types decoded. // Note that the returned program **doesn't** have the globals linked. -func NewLoadedExecutableProgram(location common.Location, program *bbq.Program) *ExecutableProgram { +func NewLoadedExecutableProgram( + location common.Location, + program *bbq.Program[opcode.Instruction], +) *ExecutableProgram { executable := NewExecutableProgram(location, program, nil) // Optimization: Pre load/decode types diff --git a/bbq/vm/linker.go b/bbq/vm/linker.go index eaa4b27c5a..b218d95549 100644 --- a/bbq/vm/linker.go +++ b/bbq/vm/linker.go @@ -22,6 +22,7 @@ import ( "fmt" "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/bbq/opcode" "github.com/onflow/cadence/common" ) @@ -37,7 +38,7 @@ type LinkedGlobals struct { // LinkGlobals performs the linking of global functions and variables for a given program. func LinkGlobals( location common.Location, - program *bbq.Program, + program *bbq.Program[opcode.Instruction], conf *Config, linkedGlobalsCache map[common.Location]LinkedGlobals, ) LinkedGlobals { diff --git a/bbq/vm/test/ft_test.go b/bbq/vm/test/ft_test.go index a2e53276c2..dc70abc385 100644 --- a/bbq/vm/test/ft_test.go +++ b/bbq/vm/test/ft_test.go @@ -26,6 +26,7 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/bbq/opcode" "github.com/onflow/cadence/bbq/vm" "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" @@ -93,7 +94,7 @@ func TestFTTransfer(t *testing.T) { vmConfig := &vm.Config{ Storage: storage, - ImportHandler: func(location common.Location) *bbq.Program { + ImportHandler: func(location common.Location) *bbq.Program[opcode.Instruction] { imported, ok := programs[location] if !ok { return nil @@ -249,7 +250,7 @@ func BenchmarkFTTransfer(b *testing.B) { vmConfig := &vm.Config{ Storage: storage, - ImportHandler: func(location common.Location) *bbq.Program { + ImportHandler: func(location common.Location) *bbq.Program[opcode.Instruction] { imported, ok := programs[location] if !ok { return nil diff --git a/bbq/vm/test/runtime_test.go b/bbq/vm/test/runtime_test.go index 5fb73400b4..49975acf9f 100644 --- a/bbq/vm/test/runtime_test.go +++ b/bbq/vm/test/runtime_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/cadence/bbq/opcode" "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" "github.com/onflow/cadence/test_utils/runtime_utils" @@ -147,7 +148,7 @@ func TestResourceLossViaSelfRugPull(t *testing.T) { contractsAddress.HexWithPrefix(), ) - importHandler := func(location common.Location) *bbq.Program { + importHandler := func(location common.Location) *bbq.Program[opcode.Instruction] { switch location { case barLocation: return barProgram diff --git a/bbq/vm/test/utils.go b/bbq/vm/test/utils.go index e9cf42e391..861e116ba6 100644 --- a/bbq/vm/test/utils.go +++ b/bbq/vm/test/utils.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/cadence/ast" + "github.com/onflow/cadence/bbq/opcode" "github.com/onflow/cadence/common" "github.com/onflow/cadence/errors" "github.com/onflow/cadence/interpreter" @@ -380,10 +381,10 @@ func singleIdentifierLocationResolver(t testing.TB) func( } } -func printProgram(name string, program *bbq.Program) { - byteCodePrinter := &bbq.BytecodePrinter{} +func printProgram(name string, program *bbq.Program[opcode.Instruction]) { + printer := bbq.NewInstructionsProgramPrinter() fmt.Println("===================", name, "===================") - fmt.Println(byteCodePrinter.PrintProgram(program)) + fmt.Println(printer.PrintProgram(program)) } func baseValueActivation(common.Location) *sema.VariableActivation { @@ -400,7 +401,7 @@ func baseValueActivation(common.Location) *sema.VariableActivation { } type compiledProgram struct { - *bbq.Program + *bbq.Program[opcode.Instruction] *sema.Elaboration } @@ -409,7 +410,7 @@ func compileCode( code string, location common.Location, programs map[common.Location]compiledProgram, -) *bbq.Program { +) *bbq.Program[opcode.Instruction] { checker := parseAndCheck(t, code, location, programs) program := compile(t, checker, programs) @@ -452,10 +453,14 @@ func parseAndCheck( return checker } -func compile(t testing.TB, checker *sema.Checker, programs map[common.Location]compiledProgram) *bbq.Program { - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) +func compile( + t testing.TB, + checker *sema.Checker, + programs map[common.Location]compiledProgram, +) *bbq.Program[opcode.Instruction] { + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) comp.Config.LocationHandler = singleIdentifierLocationResolver(t) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + comp.Config.ImportHandler = func(location common.Location) *bbq.Program[opcode.Instruction] { imported, ok := programs[location] if !ok { return nil diff --git a/bbq/vm/test/vm_bench_test.go b/bbq/vm/test/vm_bench_test.go index 4c77f61451..a83bbee2cd 100644 --- a/bbq/vm/test/vm_bench_test.go +++ b/bbq/vm/test/vm_bench_test.go @@ -27,6 +27,7 @@ import ( "github.com/onflow/cadence/ast" "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/bbq/opcode" "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" "github.com/onflow/cadence/sema" @@ -44,7 +45,7 @@ func BenchmarkRecursionFib(b *testing.B) { checker, err := ParseAndCheck(b, recursiveFib) require.NoError(b, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -73,7 +74,7 @@ func BenchmarkImperativeFib(b *testing.B) { checker, err := ParseAndCheck(b, imperativeFib) require.NoError(b, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -115,7 +116,7 @@ func BenchmarkNewStruct(b *testing.B) { value := vm.NewIntValue(10) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -161,7 +162,7 @@ func BenchmarkNewResource(b *testing.B) { scriptLocation := runtime_utils.NewScriptLocationGenerator() for i := 0; i < b.N; i++ { - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -236,7 +237,7 @@ func BenchmarkContractImport(b *testing.B) { ) require.NoError(b, err) - importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importCompiler := compiler.NewInstructionCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() vmInstance := vm.NewVM(location, importedProgram, nil) @@ -244,7 +245,7 @@ func BenchmarkContractImport(b *testing.B) { require.NoError(b, err) vmConfig := &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { + ImportHandler: func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram }, ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { @@ -284,8 +285,8 @@ func BenchmarkContractImport(b *testing.B) { ) require.NoError(b, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram } program := comp.Compile() @@ -332,7 +333,7 @@ func BenchmarkMethodCall(b *testing.B) { ) require.NoError(b, err) - importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importCompiler := compiler.NewInstructionCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() vmInstance := vm.NewVM(location, importedProgram, nil) @@ -363,15 +364,15 @@ func BenchmarkMethodCall(b *testing.B) { ) require.NoError(b, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram } program := comp.Compile() vmConfig := &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { + ImportHandler: func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram }, ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { @@ -434,7 +435,7 @@ func BenchmarkMethodCall(b *testing.B) { ) require.NoError(b, err) - importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importCompiler := compiler.NewInstructionCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() vmInstance := vm.NewVM(location, importedProgram, nil) @@ -465,15 +466,15 @@ func BenchmarkMethodCall(b *testing.B) { ) require.NoError(b, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram } program := comp.Compile() vmConfig := &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { + ImportHandler: func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram }, ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { diff --git a/bbq/vm/test/vm_test.go b/bbq/vm/test/vm_test.go index 07059eed89..ab58a8f687 100644 --- a/bbq/vm/test/vm_test.go +++ b/bbq/vm/test/vm_test.go @@ -20,9 +20,11 @@ package test import ( "fmt" - "github.com/onflow/cadence/interpreter" "testing" + "github.com/onflow/cadence/bbq/opcode" + "github.com/onflow/cadence/interpreter" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -61,7 +63,7 @@ func TestRecursionFib(t *testing.T) { checker, err := ParseAndCheck(t, recursiveFib) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -99,7 +101,7 @@ func TestImperativeFib(t *testing.T) { checker, err := ParseAndCheck(t, imperativeFib) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -132,7 +134,7 @@ func TestBreak(t *testing.T) { `) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -164,7 +166,7 @@ func TestContinue(t *testing.T) { `) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -193,7 +195,7 @@ func TestNilCoalesce(t *testing.T) { `) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -218,7 +220,7 @@ func TestNilCoalesce(t *testing.T) { `) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -258,7 +260,7 @@ func TestNewStruct(t *testing.T) { `) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -304,7 +306,7 @@ func TestStructMethodCall(t *testing.T) { `) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -347,7 +349,7 @@ func TestImport(t *testing.T) { ) require.NoError(t, err) - subComp := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + subComp := compiler.NewInstructionCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := subComp.Compile() checker, err := ParseAndCheckWithOptions(t, ` @@ -370,15 +372,15 @@ func TestImport(t *testing.T) { ) require.NoError(t, err) - importCompiler := compiler.NewCompiler(checker.Program, checker.Elaboration) - importCompiler.Config.ImportHandler = func(location common.Location) *bbq.Program { + importCompiler := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) + importCompiler.Config.ImportHandler = func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram } program := importCompiler.Compile() vmConfig := &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { + ImportHandler: func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram }, } @@ -430,7 +432,7 @@ func TestContractImport(t *testing.T) { ) require.NoError(t, err) - importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importCompiler := compiler.NewInstructionCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() vmInstance := vm.NewVM(importLocation, importedProgram, nil) @@ -457,15 +459,15 @@ func TestContractImport(t *testing.T) { ) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram } program := comp.Compile() vmConfig := &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { + ImportHandler: func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram }, ContractValueHandler: func(*vm.Config, common.Location) *vm.CompositeValue { @@ -506,7 +508,7 @@ func TestContractImport(t *testing.T) { ) require.NoError(t, err) - importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importCompiler := compiler.NewInstructionCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() vmInstance := vm.NewVM(importLocation, importedProgram, nil) @@ -532,15 +534,15 @@ func TestContractImport(t *testing.T) { ) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram } program := comp.Compile() vmConfig := &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { + ImportHandler: func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram }, ContractValueHandler: func(*vm.Config, common.Location) *vm.CompositeValue { @@ -584,7 +586,7 @@ func TestContractImport(t *testing.T) { ) require.NoError(t, err) - fooCompiler := compiler.NewCompiler(fooChecker.Program, fooChecker.Elaboration) + fooCompiler := compiler.NewInstructionCompiler(fooChecker.Program, fooChecker.Elaboration) fooProgram := fooCompiler.Compile() vmInstance := vm.NewVM(fooLocation, fooProgram, nil) @@ -623,9 +625,9 @@ func TestContractImport(t *testing.T) { ) require.NoError(t, err) - barCompiler := compiler.NewCompiler(barChecker.Program, barChecker.Elaboration) + barCompiler := compiler.NewInstructionCompiler(barChecker.Program, barChecker.Elaboration) barCompiler.Config.LocationHandler = singleIdentifierLocationResolver(t) - barCompiler.Config.ImportHandler = func(location common.Location) *bbq.Program { + barCompiler.Config.ImportHandler = func(location common.Location) *bbq.Program[opcode.Instruction] { require.Equal(t, fooLocation, location) return fooProgram } @@ -633,7 +635,7 @@ func TestContractImport(t *testing.T) { barProgram := barCompiler.Compile() vmConfig := &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { + ImportHandler: func(location common.Location) *bbq.Program[opcode.Instruction] { require.Equal(t, fooLocation, location) return fooProgram }, @@ -680,9 +682,9 @@ func TestContractImport(t *testing.T) { ) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) comp.Config.LocationHandler = singleIdentifierLocationResolver(t) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + comp.Config.ImportHandler = func(location common.Location) *bbq.Program[opcode.Instruction] { switch location { case fooLocation: return fooProgram @@ -697,7 +699,7 @@ func TestContractImport(t *testing.T) { program := comp.Compile() vmConfig = &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { + ImportHandler: func(location common.Location) *bbq.Program[opcode.Instruction] { switch location { case fooLocation: return fooProgram @@ -755,7 +757,7 @@ func TestContractImport(t *testing.T) { ) require.NoError(t, err) - fooCompiler := compiler.NewCompiler(fooChecker.Program, fooChecker.Elaboration) + fooCompiler := compiler.NewInstructionCompiler(fooChecker.Program, fooChecker.Elaboration) fooProgram := fooCompiler.Compile() //vmInstance := NewVM(fooProgram, nil) @@ -794,9 +796,9 @@ func TestContractImport(t *testing.T) { ) require.NoError(t, err) - barCompiler := compiler.NewCompiler(barChecker.Program, barChecker.Elaboration) + barCompiler := compiler.NewInstructionCompiler(barChecker.Program, barChecker.Elaboration) barCompiler.Config.LocationHandler = singleIdentifierLocationResolver(t) - barCompiler.Config.ImportHandler = func(location common.Location) *bbq.Program { + barCompiler.Config.ImportHandler = func(location common.Location) *bbq.Program[opcode.Instruction] { require.Equal(t, fooLocation, location) return fooProgram } @@ -804,7 +806,7 @@ func TestContractImport(t *testing.T) { barProgram := barCompiler.Compile() vmConfig := &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { + ImportHandler: func(location common.Location) *bbq.Program[opcode.Instruction] { require.Equal(t, fooLocation, location) return fooProgram }, @@ -851,9 +853,9 @@ func TestContractImport(t *testing.T) { ) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) comp.Config.LocationHandler = singleIdentifierLocationResolver(t) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + comp.Config.ImportHandler = func(location common.Location) *bbq.Program[opcode.Instruction] { switch location { case fooLocation: return fooProgram @@ -868,7 +870,7 @@ func TestContractImport(t *testing.T) { program := comp.Compile() vmConfig = &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { + ImportHandler: func(location common.Location) *bbq.Program[opcode.Instruction] { switch location { case fooLocation: return fooProgram @@ -919,7 +921,7 @@ func TestInitializeContract(t *testing.T) { ) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -956,7 +958,7 @@ func TestContractAccessDuringInit(t *testing.T) { ) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -989,7 +991,7 @@ func TestContractAccessDuringInit(t *testing.T) { ) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -1023,7 +1025,7 @@ func TestFunctionOrder(t *testing.T) { }`) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -1079,7 +1081,7 @@ func TestFunctionOrder(t *testing.T) { ) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -1116,7 +1118,7 @@ func TestContractField(t *testing.T) { ) require.NoError(t, err) - importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importCompiler := compiler.NewInstructionCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() vmInstance := vm.NewVM(importLocation, importedProgram, nil) @@ -1142,15 +1144,15 @@ func TestContractField(t *testing.T) { ) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram } program := comp.Compile() vmConfig := &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { + ImportHandler: func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram }, ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { @@ -1185,7 +1187,7 @@ func TestContractField(t *testing.T) { ) require.NoError(t, err) - importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importCompiler := compiler.NewInstructionCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() vmInstance := vm.NewVM(importLocation, importedProgram, nil) @@ -1212,15 +1214,15 @@ func TestContractField(t *testing.T) { ) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) + comp.Config.ImportHandler = func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram } program := comp.Compile() vmConfig := &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { + ImportHandler: func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram }, ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { @@ -1282,7 +1284,7 @@ func TestNativeFunctions(t *testing.T) { ) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -1301,7 +1303,7 @@ func TestNativeFunctions(t *testing.T) { ) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -1334,7 +1336,7 @@ func TestTransaction(t *testing.T) { ) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -1385,7 +1387,7 @@ func TestTransaction(t *testing.T) { ) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -1470,7 +1472,7 @@ func TestInterfaceMethodCall(t *testing.T) { ) require.NoError(t, err) - importCompiler := compiler.NewCompiler(importedChecker.Program, importedChecker.Elaboration) + importCompiler := compiler.NewInstructionCompiler(importedChecker.Program, importedChecker.Elaboration) importedProgram := importCompiler.Compile() vmInstance := vm.NewVM(contractLocation, importedProgram, nil) @@ -1502,16 +1504,16 @@ func TestInterfaceMethodCall(t *testing.T) { ) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) comp.Config.LocationHandler = singleIdentifierLocationResolver(t) - comp.Config.ImportHandler = func(location common.Location) *bbq.Program { + comp.Config.ImportHandler = func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram } program := comp.Compile() vmConfig := &vm.Config{ - ImportHandler: func(location common.Location) *bbq.Program { + ImportHandler: func(location common.Location) *bbq.Program[opcode.Instruction] { return importedProgram }, ContractValueHandler: func(vmConfig *vm.Config, location common.Location) *vm.CompositeValue { @@ -1561,7 +1563,7 @@ func TestInterfaceMethodCall(t *testing.T) { ) require.NoError(t, err) - interfaceCompiler := compiler.NewCompiler(fooChecker.Program, fooChecker.Elaboration) + interfaceCompiler := compiler.NewInstructionCompiler(fooChecker.Program, fooChecker.Elaboration) fooProgram := interfaceCompiler.Compile() interfaceVM := vm.NewVM(fooLocation, fooProgram, nil) @@ -1592,7 +1594,7 @@ func TestInterfaceMethodCall(t *testing.T) { ) require.NoError(t, err) - barCompiler := compiler.NewCompiler(barChecker.Program, barChecker.Elaboration) + barCompiler := compiler.NewInstructionCompiler(barChecker.Program, barChecker.Elaboration) barProgram := barCompiler.Compile() barVM := vm.NewVM(barLocation, barProgram, nil) @@ -1643,7 +1645,7 @@ func TestInterfaceMethodCall(t *testing.T) { ) require.NoError(t, err) - bazImportHandler := func(location common.Location) *bbq.Program { + bazImportHandler := func(location common.Location) *bbq.Program[opcode.Instruction] { switch location { case fooLocation: return fooProgram @@ -1654,7 +1656,7 @@ func TestInterfaceMethodCall(t *testing.T) { } } - bazCompiler := compiler.NewCompiler(bazChecker.Program, bazChecker.Elaboration) + bazCompiler := compiler.NewInstructionCompiler(bazChecker.Program, bazChecker.Elaboration) bazCompiler.Config.LocationHandler = singleIdentifierLocationResolver(t) bazCompiler.Config.ImportHandler = bazImportHandler bazProgram := bazCompiler.Compile() @@ -1707,7 +1709,7 @@ func TestInterfaceMethodCall(t *testing.T) { ) require.NoError(t, err) - scriptImportHandler := func(location common.Location) *bbq.Program { + scriptImportHandler := func(location common.Location) *bbq.Program[opcode.Instruction] { switch location { case barLocation: return barProgram @@ -1718,7 +1720,7 @@ func TestInterfaceMethodCall(t *testing.T) { } } - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) comp.Config.LocationHandler = singleIdentifierLocationResolver(t) comp.Config.ImportHandler = scriptImportHandler @@ -1788,7 +1790,7 @@ func TestInterfaceMethodCall(t *testing.T) { ) require.NoError(t, err) - scriptImportHandler = func(location common.Location) *bbq.Program { + scriptImportHandler = func(location common.Location) *bbq.Program[opcode.Instruction] { switch location { case fooLocation: return fooProgram @@ -1803,7 +1805,7 @@ func TestInterfaceMethodCall(t *testing.T) { return fooProgram } - comp = compiler.NewCompiler(checker.Program, checker.Elaboration) + comp = compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) comp.Config.LocationHandler = singleIdentifierLocationResolver(t) comp.Config.ImportHandler = scriptImportHandler @@ -1849,7 +1851,7 @@ func TestArrayLiteral(t *testing.T) { `) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -1877,7 +1879,7 @@ func TestArrayLiteral(t *testing.T) { `) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -1901,7 +1903,7 @@ func TestArrayLiteral(t *testing.T) { `) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -1948,7 +1950,7 @@ func TestReference(t *testing.T) { `) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := vm.NewConfig(nil) @@ -1987,7 +1989,7 @@ func TestResource(t *testing.T) { `) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() vmConfig := &vm.Config{} @@ -2029,7 +2031,7 @@ func TestResource(t *testing.T) { `) require.NoError(t, err) - comp := compiler.NewCompiler(checker.Program, checker.Elaboration) + comp := compiler.NewInstructionCompiler(checker.Program, checker.Elaboration) program := comp.Compile() printProgram("", program) diff --git a/bbq/vm/value_function.go b/bbq/vm/value_function.go index bbecc758cd..1a41cd09db 100644 --- a/bbq/vm/value_function.go +++ b/bbq/vm/value_function.go @@ -22,11 +22,12 @@ import ( "github.com/onflow/atree" "github.com/onflow/cadence/bbq" + "github.com/onflow/cadence/bbq/opcode" "github.com/onflow/cadence/errors" ) type FunctionValue struct { - Function *bbq.Function + Function *bbq.Function[opcode.Instruction] Executable *ExecutableProgram } diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index 7b79c304a5..db383d3358 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -49,7 +49,7 @@ type VM struct { func NewVM( location common.Location, - program *bbq.Program, + program *bbq.Program[opcode.Instruction], conf *Config, ) *VM { // TODO: Remove initializing config. Following is for testing purpose only. @@ -310,13 +310,11 @@ func opReturn(vm *VM) { vm.push(voidValue) } -func opJump(vm *VM) { - ins := opcode.DecodeJump(&vm.ip, vm.callFrame.function.Code) +func opJump(vm *VM, ins opcode.InstructionJump) { vm.ip = ins.Target } -func opJumpIfFalse(vm *VM) { - ins := opcode.DecodeJumpIfFalse(&vm.ip, vm.callFrame.function.Code) +func opJumpIfFalse(vm *VM, ins opcode.InstructionJumpIfFalse) { value := vm.pop().(BoolValue) if !value { vm.ip = ins.Target @@ -359,41 +357,32 @@ func opFalse(vm *VM) { vm.push(FalseValue) } -func opGetConstant(vm *VM) { - callFrame := vm.callFrame - ins := opcode.DecodeGetConstant(&vm.ip, callFrame.function.Code) - constant := callFrame.executable.Constants[ins.ConstantIndex] +func opGetConstant(vm *VM, ins opcode.InstructionGetConstant) { + constant := vm.callFrame.executable.Constants[ins.ConstantIndex] if constant == nil { constant = vm.initializeConstant(ins.ConstantIndex) } vm.push(constant) } -func opGetLocal(vm *VM) { - callFrame := vm.callFrame - ins := opcode.DecodeGetLocal(&vm.ip, callFrame.function.Code) - absoluteIndex := callFrame.localsOffset + ins.LocalIndex +func opGetLocal(vm *VM, ins opcode.InstructionGetLocal) { + absoluteIndex := vm.callFrame.localsOffset + ins.LocalIndex local := vm.locals[absoluteIndex] vm.push(local) } -func opSetLocal(vm *VM) { - callFrame := vm.callFrame - ins := opcode.DecodeSetLocal(&vm.ip, callFrame.function.Code) - absoluteIndex := callFrame.localsOffset + ins.LocalIndex +func opSetLocal(vm *VM, ins opcode.InstructionSetLocal) { + absoluteIndex := vm.callFrame.localsOffset + ins.LocalIndex vm.locals[absoluteIndex] = vm.pop() } -func opGetGlobal(vm *VM) { - callFrame := vm.callFrame - ins := opcode.DecodeGetGlobal(&vm.ip, callFrame.function.Code) - vm.push(callFrame.executable.Globals[ins.GlobalIndex]) +func opGetGlobal(vm *VM, ins opcode.InstructionGetGlobal) { + value := vm.callFrame.executable.Globals[ins.GlobalIndex] + vm.push(value) } -func opSetGlobal(vm *VM) { - callFrame := vm.callFrame - ins := opcode.DecodeSetGlobal(&vm.ip, callFrame.function.Code) - callFrame.executable.Globals[ins.GlobalIndex] = vm.pop() +func opSetGlobal(vm *VM, ins opcode.InstructionSetGlobal) { + vm.callFrame.executable.Globals[ins.GlobalIndex] = vm.pop() } func opSetIndex(vm *VM) { @@ -411,13 +400,10 @@ func opGetIndex(vm *VM) { vm.push(element) } -func opInvoke(vm *VM) { +func opInvoke(vm *VM, ins opcode.InstructionInvoke) { value := vm.pop() stackHeight := len(vm.stack) - callFrame := vm.callFrame - ins := opcode.DecodeInvoke(&vm.ip, callFrame.function.Code) - switch value := value.(type) { case FunctionValue: parameterCount := int(value.Function.ParameterCount) @@ -445,11 +431,7 @@ func opInvoke(vm *VM) { } } -func opInvokeDynamic(vm *VM) { - callFrame := vm.callFrame - - ins := opcode.DecodeInvokeDynamic(&vm.ip, callFrame.function.Code) - +func opInvokeDynamic(vm *VM, ins opcode.InstructionInvokeDynamic) { stackHeight := len(vm.stack) receiver := vm.stack[stackHeight-int(ins.ArgCount)-1] @@ -495,8 +477,7 @@ func opDup(vm *VM) { vm.push(top) } -func opNew(vm *VM) { - ins := opcode.DecodeNew(&vm.ip, vm.callFrame.function.Code) +func opNew(vm *VM, ins opcode.InstructionNew) { compositeKind := common.CompositeKind(ins.Kind) // decode location @@ -542,9 +523,7 @@ func opGetField(vm *VM) { vm.push(fieldValue) } -func opTransfer(vm *VM) { - ins := opcode.DecodeTransfer(&vm.ip, vm.callFrame.function.Code) - +func opTransfer(vm *VM, ins opcode.InstructionTransfer) { targetType := vm.loadType(ins.TypeIndex) value := vm.peek() @@ -569,8 +548,7 @@ func opDestroy(vm *VM) { value.Destroy(vm.config) } -func opPath(vm *VM) { - ins := opcode.DecodePath(&vm.ip, vm.callFrame.function.Code) +func opPath(vm *VM, ins opcode.InstructionPath) { value := PathValue{ Domain: ins.Domain, Identifier: ins.Identifier, @@ -578,11 +556,9 @@ func opPath(vm *VM) { vm.push(value) } -func opCast(vm *VM) { +func opCast(vm *VM, ins opcode.InstructionCast) { value := vm.pop() - ins := opcode.DecodeCast(&vm.ip, vm.callFrame.function.Code) - targetType := vm.loadType(ins.TypeIndex) // TODO: @@ -614,8 +590,7 @@ func opUnwrap(vm *VM) { } } -func opNewArray(vm *VM) { - ins := opcode.DecodeNewArray(&vm.ip, vm.callFrame.function.Code) +func opNewArray(vm *VM, ins opcode.InstructionNewArray) { typ := vm.loadType(ins.TypeIndex).(interpreter.ArrayStaticType) @@ -631,8 +606,7 @@ func opNewArray(vm *VM) { vm.push(array) } -func opNewRef(vm *VM) { - ins := opcode.DecodeNewRef(&vm.ip, vm.callFrame.function.Code) +func opNewRef(vm *VM, ins opcode.InstructionNewRef) { borrowedType := vm.loadType(ins.TypeIndex).(*interpreter.ReferenceStaticType) value := vm.pop() @@ -657,80 +631,80 @@ func (vm *VM) run() { return } - op := opcode.Opcode(callFrame.function.Code[vm.ip]) + ins := callFrame.function.Code[vm.ip] vm.ip++ - switch op { - case opcode.ReturnValue: + switch ins := ins.(type) { + case opcode.InstructionReturnValue: opReturnValue(vm) - case opcode.Return: + case opcode.InstructionReturn: opReturn(vm) - case opcode.Jump: - opJump(vm) - case opcode.JumpIfFalse: - opJumpIfFalse(vm) - case opcode.IntAdd: + case opcode.InstructionJump: + opJump(vm, ins) + case opcode.InstructionJumpIfFalse: + opJumpIfFalse(vm, ins) + case opcode.InstructionIntAdd: opBinaryIntAdd(vm) - case opcode.IntSubtract: + case opcode.InstructionIntSubtract: opBinaryIntSubtract(vm) - case opcode.IntLess: + case opcode.InstructionIntLess: opBinaryIntLess(vm) - case opcode.IntGreater: + case opcode.InstructionIntGreater: opBinaryIntGreater(vm) - case opcode.True: + case opcode.InstructionTrue: opTrue(vm) - case opcode.False: + case opcode.InstructionFalse: opFalse(vm) - case opcode.GetConstant: - opGetConstant(vm) - case opcode.GetLocal: - opGetLocal(vm) - case opcode.SetLocal: - opSetLocal(vm) - case opcode.GetGlobal: - opGetGlobal(vm) - case opcode.SetGlobal: - opSetGlobal(vm) - case opcode.SetIndex: + case opcode.InstructionGetConstant: + opGetConstant(vm, ins) + case opcode.InstructionGetLocal: + opGetLocal(vm, ins) + case opcode.InstructionSetLocal: + opSetLocal(vm, ins) + case opcode.InstructionGetGlobal: + opGetGlobal(vm, ins) + case opcode.InstructionSetGlobal: + opSetGlobal(vm, ins) + case opcode.InstructionSetIndex: opSetIndex(vm) - case opcode.GetIndex: + case opcode.InstructionGetIndex: opGetIndex(vm) - case opcode.Invoke: - opInvoke(vm) - case opcode.InvokeDynamic: - opInvokeDynamic(vm) - case opcode.Drop: + case opcode.InstructionInvoke: + opInvoke(vm, ins) + case opcode.InstructionInvokeDynamic: + opInvokeDynamic(vm, ins) + case opcode.InstructionDrop: opDrop(vm) - case opcode.Dup: + case opcode.InstructionDup: opDup(vm) - case opcode.New: - opNew(vm) - case opcode.NewArray: - opNewArray(vm) - case opcode.NewRef: - opNewRef(vm) - case opcode.SetField: + case opcode.InstructionNew: + opNew(vm, ins) + case opcode.InstructionNewArray: + opNewArray(vm, ins) + case opcode.InstructionNewRef: + opNewRef(vm, ins) + case opcode.InstructionSetField: opSetField(vm) - case opcode.GetField: + case opcode.InstructionGetField: opGetField(vm) - case opcode.Transfer: - opTransfer(vm) - case opcode.Destroy: + case opcode.InstructionTransfer: + opTransfer(vm, ins) + case opcode.InstructionDestroy: opDestroy(vm) - case opcode.Path: - opPath(vm) - case opcode.Cast: - opCast(vm) - case opcode.Nil: + case opcode.InstructionPath: + opPath(vm, ins) + case opcode.InstructionCast: + opCast(vm, ins) + case opcode.InstructionNil: opNil(vm) - case opcode.Equal: + case opcode.InstructionEqual: opEqual(vm) - case opcode.NotEqual: + case opcode.InstructionNotEqual: opNotEqual(vm) - case opcode.Unwrap: + case opcode.InstructionUnwrap: opUnwrap(vm) default: - panic(errors.NewUnexpectedError("cannot execute opcode '%s'", op.String())) + panic(errors.NewUnexpectedError("cannot execute instruction of type %T", ins)) } // Faster in Go <1.19: From d9e61fb94736fe354515dafe9732bc9db720a94b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 13 Dec 2024 16:57:52 -0800 Subject: [PATCH 83/89] generate the code for the instructions from a file that defines all instructions --- bbq/compiler/compiler.go | 2 +- bbq/opcode/gen/instructions.schema.json | 41 + bbq/opcode/gen/main.go | 826 ++++++++++++++++++++ bbq/opcode/instruction.go | 971 +----------------------- bbq/opcode/instructions.yml | 178 +++++ bbq/vm/vm.go | 2 +- go.mod | 1 + go.sum | 2 + 8 files changed, 1080 insertions(+), 943 deletions(-) create mode 100644 bbq/opcode/gen/instructions.schema.json create mode 100644 bbq/opcode/gen/main.go create mode 100644 bbq/opcode/instructions.yml diff --git a/bbq/compiler/compiler.go b/bbq/compiler/compiler.go index 7a2c84b56a..41e35f1a2d 100644 --- a/bbq/compiler/compiler.go +++ b/bbq/compiler/compiler.go @@ -1125,7 +1125,7 @@ func (c *Compiler[_]) compileInitializer(declaration *ast.SpecialFunctionDeclara // Write composite kind // TODO: Maybe get/include this from static-type. Then no need to provide separately. - kind := uint16(enclosingCompositeType.Kind) + kind := enclosingCompositeType.Kind typeIndex := c.getOrAddType(enclosingCompositeType) diff --git a/bbq/opcode/gen/instructions.schema.json b/bbq/opcode/gen/instructions.schema.json new file mode 100644 index 0000000000..e0f6bda38b --- /dev/null +++ b/bbq/opcode/gen/instructions.schema.json @@ -0,0 +1,41 @@ +{ + "type": "array", + "items": { "$ref": "#/$defs/instruction" }, + "$defs": { + "instruction": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "description": { "type": "string" }, + "operands": { + "type": "array", + "items": { "$ref": "#/$defs/operand" } + } + }, + "required": ["name", "description"], + "additionalProperties": false + }, + "operand": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "type": { + "type": "string", + "enum": [ + "bool", + "index", + "indices", + "size", + "string", + "castKind", + "pathDomain", + "compositeKind" + ] + }, + "description": { "type": "string" } + }, + "required": ["name", "type"], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/bbq/opcode/gen/main.go b/bbq/opcode/gen/main.go new file mode 100644 index 0000000000..70782b5ca8 --- /dev/null +++ b/bbq/opcode/gen/main.go @@ -0,0 +1,826 @@ +package main + +import ( + "bytes" + "fmt" + "go/token" + "io" + "os" + "unicode" + "unicode/utf8" + + "github.com/dave/dst" + "github.com/dave/dst/decorator" + "github.com/dave/dst/decorator/resolver/guess" + "github.com/goccy/go-yaml" +) + +const ( + opcodePackagePath = "github.com/onflow/cadence/bbq/opcode" + commonPackagePath = "github.com/onflow/cadence/common" + errorsPackagePath = "github.com/onflow/cadence/errors" +) + +const headerFormat = `// Code generated by gen/main.go from %s. DO NOT EDIT. + +` + +const typeCommentFormat = `// %s +// +// %s +` + +type operandType string + +const ( + operandTypeBool = "bool" + operandTypeIndex = "index" + operandTypeIndices = "indices" + operandTypeSize = "size" + operandTypeString = "string" + operandTypeCastKind = "castKind" + operandTypePathDomain = "pathDomain" + operandTypeCompositeKind = "compositeKind" +) + +type instruction struct { + Name string + Description string + Operands []operand +} + +type operand struct { + Name string + Description string + Type operandType +} + +func main() { + if len(os.Args) != 3 { + panic("usage: gen instructions.yml instructions.go") + } + + yamlPath := os.Args[1] + goPath := os.Args[2] + + yamlContents, err := os.ReadFile(yamlPath) + if err != nil { + panic(err) + } + + var instructions []instruction + + err = yaml.Unmarshal(yamlContents, &instructions) + if err != nil { + panic(err) + } + + var buffer bytes.Buffer + _, err = fmt.Fprintf(&buffer, headerFormat, yamlPath) + if err != nil { + panic(err) + } + + var decls []dst.Decl + for _, instruction := range instructions { + decls = append( + decls, + instructionDecls(instruction)..., + ) + } + decls = append(decls, decodeInstructionFuncDecl(instructions)) + + writeGoFile(&buffer, decls, opcodePackagePath) + + err = os.WriteFile(goPath, buffer.Bytes(), 0644) + if err != nil { + panic(err) + } +} + +func decodeInstructionFuncDecl(instructions []instruction) *dst.FuncDecl { + + var caseStmts []dst.Stmt + + for _, ins := range instructions { + + var resultExpr dst.Expr + if len(ins.Operands) == 0 { + resultExpr = &dst.CompositeLit{ + Type: instructionIdent(ins), + } + } else { + resultExpr = &dst.CallExpr{ + Fun: &dst.Ident{ + Name: "Decode" + firstUpper(ins.Name), + }, + Args: []dst.Expr{ + dst.NewIdent("ip"), + dst.NewIdent("code"), + }, + } + } + + caseStmts = append( + caseStmts, + &dst.CaseClause{ + List: []dst.Expr{ + dst.NewIdent(firstUpper(ins.Name)), + }, + Body: []dst.Stmt{ + &dst.ReturnStmt{ + Results: []dst.Expr{ + resultExpr, + }, + }, + }, + }, + ) + } + + switchStmt := &dst.SwitchStmt{ + Tag: &dst.CallExpr{ + Fun: &dst.Ident{ + Name: "Opcode", + Path: opcodePackagePath, + }, + Args: []dst.Expr{ + &dst.CallExpr{ + + Fun: &dst.Ident{ + Name: "decodeByte", + Path: opcodePackagePath, + }, + Args: []dst.Expr{ + dst.NewIdent("ip"), + dst.NewIdent("code"), + }, + }, + }, + }, + Body: &dst.BlockStmt{ + List: caseStmts, + }, + Decs: dst.SwitchStmtDecorations{ + NodeDecs: dst.NodeDecs{ + Before: dst.NewLine, + After: dst.NewLine, + }, + }, + } + + stmts := []dst.Stmt{ + switchStmt, + &dst.ExprStmt{ + X: &dst.CallExpr{ + Fun: dst.NewIdent("panic"), + Args: []dst.Expr{ + &dst.CallExpr{ + Fun: &dst.Ident{ + Name: "NewUnreachableError", + Path: errorsPackagePath, + }, + }, + }, + }, + Decs: dst.ExprStmtDecorations{ + NodeDecs: dst.NodeDecs{ + Before: dst.EmptyLine, + After: dst.NewLine, + }, + }, + }, + } + + return &dst.FuncDecl{ + Name: dst.NewIdent("DecodeInstruction"), + Type: &dst.FuncType{ + Params: &dst.FieldList{ + List: []*dst.Field{ + { + Names: []*dst.Ident{ + dst.NewIdent("ip"), + }, + Type: &dst.StarExpr{ + X: dst.NewIdent("uint16"), + }, + }, + { + Names: []*dst.Ident{ + dst.NewIdent("code"), + }, + Type: &dst.ArrayType{ + Elt: dst.NewIdent("byte"), + }, + }, + }, + }, + Results: &dst.FieldList{ + List: []*dst.Field{ + { + Type: &dst.Ident{ + Name: "Instruction", + Path: opcodePackagePath, + }, + }, + }, + }, + }, + Body: &dst.BlockStmt{ + List: stmts, + }, + } +} + +func instructionDecls(instruction instruction) []dst.Decl { + decls := []dst.Decl{ + instructionTypeDecl(instruction), + instructionConformanceDecl(instruction), + instructionOpcodeFuncDecl(instruction), + instructionStringFuncDecl(instruction), + instructionEncodeFuncDecl(instruction), + } + if len(instruction.Operands) > 0 { + decls = append(decls, instructionDecodeFuncDecl(instruction)) + } + return decls +} + +func instructionIdent(ins instruction) *dst.Ident { + return dst.NewIdent("Instruction" + firstUpper(ins.Name)) +} + +func operandIdent(o operand) *dst.Ident { + return dst.NewIdent(firstUpper(o.Name)) +} + +func instructionTypeDecl(ins instruction) dst.Decl { + comment := fmt.Sprintf( + typeCommentFormat, + instructionIdent(ins).String(), + ins.Description, + ) + + return &dst.GenDecl{ + Tok: token.TYPE, + Specs: []dst.Spec{ + &dst.TypeSpec{ + Name: instructionIdent(ins), + Type: &dst.StructType{ + Fields: instructionOperandsFields(ins), + }, + }, + }, + Decs: dst.GenDeclDecorations{ + NodeDecs: dst.NodeDecs{ + Start: []string{comment}, + }, + }, + } +} + +func instructionOperandsFields(ins instruction) *dst.FieldList { + fields := make([]*dst.Field, len(ins.Operands)) + for i, operand := range ins.Operands { + + var typeExpr dst.Expr + + switch operand.Type { + case operandTypeBool: + typeExpr = dst.NewIdent("bool") + + case operandTypeIndex, + operandTypeSize: + + typeExpr = dst.NewIdent("uint16") + + case operandTypeIndices: + typeExpr = &dst.ArrayType{ + Elt: dst.NewIdent("uint16"), + } + + case operandTypeString: + typeExpr = dst.NewIdent("string") + + case operandTypeCastKind: + typeExpr = &dst.Ident{ + Name: "CastKind", + Path: opcodePackagePath, + } + + case operandTypePathDomain: + typeExpr = &dst.Ident{ + Name: "PathDomain", + Path: commonPackagePath, + } + + case operandTypeCompositeKind: + typeExpr = &dst.Ident{ + Name: "CompositeKind", + Path: commonPackagePath, + } + + default: + panic(fmt.Sprintf("unsupported operand type: %s", operand.Type)) + } + + fields[i] = &dst.Field{ + Names: []*dst.Ident{ + operandIdent(operand), + }, + Type: typeExpr, + } + } + return &dst.FieldList{ + List: fields, + } +} + +func instructionConformanceDecl(ins instruction) *dst.GenDecl { + return &dst.GenDecl{ + Tok: token.VAR, + Specs: []dst.Spec{ + &dst.ValueSpec{ + Names: []*dst.Ident{ + dst.NewIdent("_"), + }, + Type: &dst.Ident{ + Name: "Instruction", + Path: opcodePackagePath, + }, + Values: []dst.Expr{ + &dst.CompositeLit{ + Type: instructionIdent(ins), + }, + }, + }, + }, + } +} + +func instructionOpcodeFuncDecl(ins instruction) *dst.FuncDecl { + stmt := &dst.ReturnStmt{ + Results: []dst.Expr{ + dst.NewIdent(firstUpper(ins.Name)), + }, + Decs: dst.ReturnStmtDecorations{ + NodeDecs: dst.NodeDecs{ + Before: dst.NewLine, + After: dst.NewLine, + }, + }, + } + return &dst.FuncDecl{ + Recv: &dst.FieldList{ + List: []*dst.Field{ + { + Type: instructionIdent(ins), + }, + }, + }, + Name: dst.NewIdent("Opcode"), + Type: &dst.FuncType{ + Params: &dst.FieldList{}, + Results: &dst.FieldList{ + List: []*dst.Field{ + { + Type: &dst.Ident{ + Name: "Opcode", + Path: opcodePackagePath, + }, + }, + }, + }, + }, + Body: &dst.BlockStmt{ + List: []dst.Stmt{ + stmt, + }, + }, + } +} + +func instructionStringFuncDecl(ins instruction) *dst.FuncDecl { + + var stmts []dst.Stmt + + opcodeStringExpr := &dst.CallExpr{ + Fun: &dst.SelectorExpr{ + X: &dst.CallExpr{ + Fun: &dst.SelectorExpr{ + X: dst.NewIdent("i"), + Sel: dst.NewIdent("Opcode"), + }, + }, + Sel: dst.NewIdent("String"), + }, + } + + if len(ins.Operands) == 0 { + stmt := &dst.ReturnStmt{ + Results: []dst.Expr{ + opcodeStringExpr, + }, + Decs: dst.ReturnStmtDecorations{ + NodeDecs: dst.NodeDecs{ + Before: dst.NewLine, + After: dst.NewLine, + }, + }, + } + stmts = append(stmts, stmt) + } else { + + stmts = append( + stmts, + &dst.DeclStmt{ + Decl: &dst.GenDecl{ + Tok: token.VAR, + Specs: []dst.Spec{ + &dst.ValueSpec{ + Names: []*dst.Ident{ + dst.NewIdent("sb"), + }, + Type: &dst.Ident{ + Name: "Builder", + Path: "strings", + }, + }, + }, + }, + Decs: dst.DeclStmtDecorations{ + NodeDecs: dst.NodeDecs{ + Before: dst.NewLine, + After: dst.NewLine, + }, + }, + }, + ) + + stmts = append( + stmts, + &dst.ExprStmt{ + X: &dst.CallExpr{ + Fun: &dst.SelectorExpr{ + X: dst.NewIdent("sb"), + Sel: dst.NewIdent("WriteString"), + }, + Args: []dst.Expr{ + opcodeStringExpr, + }, + }, + }, + ) + + for _, operand := range ins.Operands { + stmts = append( + stmts, + &dst.ExprStmt{ + X: &dst.CallExpr{ + Fun: &dst.Ident{ + Name: "Fprintf", + Path: "fmt", + }, + Args: []dst.Expr{ + &dst.UnaryExpr{ + Op: token.AND, + X: dst.NewIdent("sb"), + }, + &dst.BasicLit{ + Kind: token.STRING, + Value: fmt.Sprintf(`" %s:%%s"`, operand.Name), + }, + &dst.SelectorExpr{ + X: dst.NewIdent("i"), + Sel: operandIdent(operand), + }, + }, + }, + }, + ) + } + + stmts = append( + stmts, + &dst.ReturnStmt{ + Results: []dst.Expr{ + &dst.CallExpr{ + Fun: &dst.SelectorExpr{ + X: dst.NewIdent("sb"), + Sel: dst.NewIdent("String"), + }, + }, + }, + Decs: dst.ReturnStmtDecorations{ + NodeDecs: dst.NodeDecs{ + Before: dst.NewLine, + After: dst.NewLine, + }, + }, + }, + ) + } + + return &dst.FuncDecl{ + Recv: &dst.FieldList{ + List: []*dst.Field{ + { + Names: []*dst.Ident{ + dst.NewIdent("i"), + }, + Type: instructionIdent(ins), + }, + }, + }, + Name: dst.NewIdent("String"), + Type: &dst.FuncType{ + Params: &dst.FieldList{}, + Results: &dst.FieldList{ + List: []*dst.Field{ + { + Type: dst.NewIdent("string"), + }, + }, + }, + }, + Body: &dst.BlockStmt{ + List: stmts, + }, + } +} + +func instructionEncodeFuncDecl(ins instruction) *dst.FuncDecl { + stmts := []dst.Stmt{ + &dst.ExprStmt{ + X: &dst.CallExpr{ + Fun: &dst.Ident{ + Name: "emitOpcode", + Path: opcodePackagePath, + }, + Args: []dst.Expr{ + dst.NewIdent("code"), + &dst.CallExpr{ + Fun: &dst.SelectorExpr{ + X: dst.NewIdent("i"), + Sel: dst.NewIdent("Opcode"), + }, + }, + }, + }, + Decs: dst.ExprStmtDecorations{ + NodeDecs: dst.NodeDecs{ + Before: dst.NewLine, + After: dst.NewLine, + }, + }, + }, + } + + for _, operand := range ins.Operands { + + var funcName string + + switch operand.Type { + case operandTypeBool: + funcName = "emitBool" + + case operandTypeIndex: + funcName = "emitUint16" + + case operandTypeIndices: + funcName = "emitUint16Array" + + case operandTypeSize: + funcName = "emitUint16" + + case operandTypeString: + funcName = "emitString" + + case operandTypeCastKind: + funcName = "emitCastKind" + + case operandTypePathDomain: + funcName = "emitPathDomain" + + case operandTypeCompositeKind: + funcName = "emitCompositeKind" + + default: + panic(fmt.Sprintf("unsupported operand type: %s", operand.Type)) + } + + stmts = append(stmts, + &dst.ExprStmt{ + X: &dst.CallExpr{ + Fun: &dst.Ident{ + Name: funcName, + Path: opcodePackagePath, + }, + Args: []dst.Expr{ + dst.NewIdent("code"), + &dst.SelectorExpr{ + X: dst.NewIdent("i"), + Sel: operandIdent(operand), + }, + }, + }, + Decs: dst.ExprStmtDecorations{ + NodeDecs: dst.NodeDecs{ + Before: dst.NewLine, + After: dst.NewLine, + }, + }, + }, + ) + } + + return &dst.FuncDecl{ + Recv: &dst.FieldList{ + List: []*dst.Field{ + { + Names: []*dst.Ident{ + dst.NewIdent("i"), + }, + Type: instructionIdent(ins), + }, + }, + }, + Name: dst.NewIdent("Encode"), + Type: &dst.FuncType{ + Params: &dst.FieldList{ + List: []*dst.Field{ + { + Names: []*dst.Ident{ + dst.NewIdent("code"), + }, + Type: &dst.StarExpr{ + X: &dst.ArrayType{ + Elt: dst.NewIdent("byte"), + }, + }, + }, + }, + }, + Results: &dst.FieldList{}, + }, + Body: &dst.BlockStmt{ + List: stmts, + }, + } +} + +func instructionDecodeFuncDecl(ins instruction) *dst.FuncDecl { + var stmts []dst.Stmt + + for _, operand := range ins.Operands { + + var funcName string + + switch operand.Type { + case operandTypeBool: + funcName = "decodeBool" + + case operandTypeIndex: + funcName = "decodeUint16" + + case operandTypeIndices: + funcName = "decodeUint16Array" + + case operandTypeSize: + funcName = "decodeUint16" + + case operandTypeString: + funcName = "decodeString" + + case operandTypeCastKind: + funcName = "decodeCastKind" + + case operandTypePathDomain: + funcName = "decodePathDomain" + + case operandTypeCompositeKind: + funcName = "decodeCompositeKind" + + default: + panic(fmt.Sprintf("unsupported operand type: %s", operand.Type)) + } + + stmts = append(stmts, + &dst.AssignStmt{ + Lhs: []dst.Expr{ + &dst.SelectorExpr{ + X: dst.NewIdent("i"), + Sel: operandIdent(operand), + }, + }, + Tok: token.ASSIGN, + Rhs: []dst.Expr{ + &dst.CallExpr{ + Fun: &dst.Ident{ + Name: funcName, + Path: opcodePackagePath, + }, + Args: []dst.Expr{ + dst.NewIdent("ip"), + dst.NewIdent("code"), + }, + }, + }, + Decs: dst.AssignStmtDecorations{ + NodeDecs: dst.NodeDecs{ + Before: dst.NewLine, + After: dst.NewLine, + }, + }, + }, + ) + } + + stmts = append( + stmts, + &dst.ReturnStmt{ + Results: []dst.Expr{ + dst.NewIdent("i"), + }, + Decs: dst.ReturnStmtDecorations{ + NodeDecs: dst.NodeDecs{ + Before: dst.NewLine, + After: dst.NewLine, + }, + }, + }, + ) + + return &dst.FuncDecl{ + Name: dst.NewIdent("Decode" + firstUpper(ins.Name)), + Type: &dst.FuncType{ + Params: &dst.FieldList{ + List: []*dst.Field{ + { + Names: []*dst.Ident{ + dst.NewIdent("ip"), + }, + Type: &dst.StarExpr{ + X: dst.NewIdent("uint16"), + }, + }, + { + Names: []*dst.Ident{ + dst.NewIdent("code"), + }, + Type: &dst.ArrayType{ + Elt: dst.NewIdent("byte"), + }, + }, + }, + }, + Results: &dst.FieldList{ + List: []*dst.Field{ + { + Names: []*dst.Ident{ + dst.NewIdent("i"), + }, + Type: instructionIdent(ins), + }, + }, + }, + }, + Body: &dst.BlockStmt{ + List: stmts, + }, + } +} + +func writeGoFile(writer io.Writer, decls []dst.Decl, packagePath string) { + resolver := guess.New() + restorer := decorator.NewRestorerWithImports(packagePath, resolver) + + packageName, err := resolver.ResolvePackage(packagePath) + if err != nil { + panic(err) + } + + for _, decl := range decls { + decl.Decorations().Before = dst.NewLine + decl.Decorations().After = dst.EmptyLine + } + + err = restorer.Fprint( + writer, + &dst.File{ + Name: dst.NewIdent(packageName), + Decls: decls, + }, + ) + if err != nil { + panic(err) + } +} + +func firstUpper(s string) string { + if s == "" { + return "" + } + r, n := utf8.DecodeRuneInString(s) + return string(unicode.ToUpper(r)) + s[n:] +} diff --git a/bbq/opcode/instruction.go b/bbq/opcode/instruction.go index 4768682c37..dd8f129401 100644 --- a/bbq/opcode/instruction.go +++ b/bbq/opcode/instruction.go @@ -16,13 +16,12 @@ * limitations under the License. */ +//go:generate go run ./gen/main.go instructions.yml instructions.go + package opcode import ( - "fmt" - "github.com/onflow/cadence/common" - "github.com/onflow/cadence/errors" ) type Instruction interface { @@ -97,972 +96,62 @@ func emitString(code *[]byte, str string) { *code = append(*code, []byte(str)...) } -// True - -type InstructionTrue struct{} - -func (InstructionTrue) Opcode() Opcode { - return True -} - -func (ins InstructionTrue) String() string { - return ins.Opcode().String() -} - -func (ins InstructionTrue) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// False - -type InstructionFalse struct{} - -func (InstructionFalse) Opcode() Opcode { - return False -} - -func (ins InstructionFalse) String() string { - return ins.Opcode().String() -} - -func (ins InstructionFalse) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// Nil - -type InstructionNil struct{} - -func (InstructionNil) Opcode() Opcode { - return Nil -} - -func (ins InstructionNil) String() string { - return ins.Opcode().String() -} - -func (ins InstructionNil) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// Dup - -type InstructionDup struct{} - -func (InstructionDup) Opcode() Opcode { - return Dup -} +// PathDomain -func (ins InstructionDup) String() string { - return ins.Opcode().String() +func decodePathDomain(ip *uint16, code []byte) common.PathDomain { + return common.PathDomain(decodeByte(ip, code)) } -func (ins InstructionDup) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) +func emitPathDomain(code *[]byte, domain common.PathDomain) { + emitByte(code, byte(domain)) } -// Drop +// CastKind -type InstructionDrop struct{} - -func (InstructionDrop) Opcode() Opcode { - return Drop -} - -func (ins InstructionDrop) String() string { - return ins.Opcode().String() +func decodeCastKind(ip *uint16, code []byte) CastKind { + return CastKind(decodeByte(ip, code)) } -func (ins InstructionDrop) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) +func emitCastKind(code *[]byte, kind CastKind) { + emitByte(code, byte(kind)) } -// GetConstant +// CompositeKind -type InstructionGetConstant struct { - ConstantIndex uint16 +func decodeCompositeKind(ip *uint16, code []byte) common.CompositeKind { + return common.CompositeKind(decodeUint16(ip, code)) } -func (InstructionGetConstant) Opcode() Opcode { - return GetConstant +func emitCompositeKind(code *[]byte, kind common.CompositeKind) { + emitUint16(code, uint16(kind)) } -func (ins InstructionGetConstant) String() string { - return fmt.Sprintf( - "%s constantIndex:%d", - ins.Opcode(), - ins.ConstantIndex, - ) -} +// Uint16Array -func (ins InstructionGetConstant) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) - emitUint16(code, ins.ConstantIndex) +func emitUint16Array(code *[]byte, values []uint16) { + emitUint16(code, uint16(len(values))) + for _, value := range values { + emitUint16(code, value) + } } -func DecodeGetConstant(ip *uint16, code []byte) (ins InstructionGetConstant) { - ins.ConstantIndex = decodeUint16(ip, code) - return ins +func decodeUint16Array(ip *uint16, code []byte) (values []uint16) { + typeArgCount := decodeUint16(ip, code) + for i := 0; i < int(typeArgCount); i++ { + value := decodeUint16(ip, code) + values = append(values, value) + } + return values } // Jump -type InstructionJump struct { - Target uint16 -} - -func (InstructionJump) Opcode() Opcode { - return Jump -} - -func (ins InstructionJump) String() string { - return fmt.Sprintf( - "%s target:%d", - ins.Opcode(), - ins.Target, - ) -} - -func (ins InstructionJump) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) - emitUint16(code, ins.Target) -} - -func DecodeJump(ip *uint16, code []byte) (ins InstructionJump) { - ins.Target = decodeUint16(ip, code) - return ins -} - -// JumpIfFalse - -type InstructionJumpIfFalse struct { - Target uint16 -} - -func (InstructionJumpIfFalse) Opcode() Opcode { - return JumpIfFalse -} - -func (ins InstructionJumpIfFalse) String() string { - return fmt.Sprintf( - "%s target:%d", - ins.Opcode(), - ins.Target, - ) -} - -func (ins InstructionJumpIfFalse) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) - emitUint16(code, ins.Target) -} - -func DecodeJumpIfFalse(ip *uint16, code []byte) (ins InstructionJumpIfFalse) { - ins.Target = decodeUint16(ip, code) - return ins -} - func PatchJump(code *[]byte, opcodeOffset int, newTarget uint16) { first, second := encodeUint16(newTarget) (*code)[opcodeOffset+1] = first (*code)[opcodeOffset+2] = second } -// Return - -type InstructionReturn struct{} - -func (InstructionReturn) Opcode() Opcode { - return Return -} - -func (ins InstructionReturn) String() string { - return ins.Opcode().String() -} - -func (ins InstructionReturn) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// ReturnValue - -type InstructionReturnValue struct{} - -func (InstructionReturnValue) Opcode() Opcode { - return ReturnValue -} - -func (ins InstructionReturnValue) String() string { - return ins.Opcode().String() -} - -func (ins InstructionReturnValue) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// GetLocal - -type InstructionGetLocal struct { - LocalIndex uint16 -} - -func (InstructionGetLocal) Opcode() Opcode { - return GetLocal -} - -func (ins InstructionGetLocal) String() string { - return fmt.Sprintf( - "%s localIndex:%d", - ins.Opcode(), - ins.LocalIndex, - ) -} - -func (ins InstructionGetLocal) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) - emitUint16(code, ins.LocalIndex) -} - -func DecodeGetLocal(ip *uint16, code []byte) (ins InstructionGetLocal) { - ins.LocalIndex = decodeUint16(ip, code) - return ins -} - -// SetLocal - -type InstructionSetLocal struct { - LocalIndex uint16 -} - -func (InstructionSetLocal) Opcode() Opcode { - return SetLocal -} - -func (ins InstructionSetLocal) String() string { - return fmt.Sprintf( - "%s localIndex:%d", - ins.Opcode(), - ins.LocalIndex, - ) -} - -func (ins InstructionSetLocal) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) - emitUint16(code, ins.LocalIndex) -} - -func DecodeSetLocal(ip *uint16, code []byte) (ins InstructionSetLocal) { - ins.LocalIndex = decodeUint16(ip, code) - return ins -} - -// GetGlobal - -type InstructionGetGlobal struct { - GlobalIndex uint16 -} - -func (InstructionGetGlobal) Opcode() Opcode { - return GetGlobal -} - -func (ins InstructionGetGlobal) String() string { - return fmt.Sprintf( - "%s globalIndex:%d", - ins.Opcode(), - ins.GlobalIndex, - ) -} - -func (ins InstructionGetGlobal) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) - emitUint16(code, ins.GlobalIndex) -} - -func DecodeGetGlobal(ip *uint16, code []byte) (ins InstructionGetGlobal) { - ins.GlobalIndex = decodeUint16(ip, code) - return ins -} - -// SetGlobal - -type InstructionSetGlobal struct { - GlobalIndex uint16 -} - -func (InstructionSetGlobal) Opcode() Opcode { - return SetGlobal -} - -func (ins InstructionSetGlobal) String() string { - return fmt.Sprintf( - "%s globalIndex:%d", - ins.Opcode(), - ins.GlobalIndex, - ) -} - -func (ins InstructionSetGlobal) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) - emitUint16(code, ins.GlobalIndex) -} - -func DecodeSetGlobal(ip *uint16, code []byte) (ins InstructionSetGlobal) { - ins.GlobalIndex = decodeUint16(ip, code) - return ins -} - -// GetField - -type InstructionGetField struct{} - -func (InstructionGetField) Opcode() Opcode { - return GetField -} - -func (ins InstructionGetField) String() string { - return ins.Opcode().String() -} - -func (ins InstructionGetField) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// SetField - -type InstructionSetField struct{} - -func (InstructionSetField) Opcode() Opcode { - return SetField -} - -func (ins InstructionSetField) String() string { - return ins.Opcode().String() -} - -func (ins InstructionSetField) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// GetIndex - -type InstructionGetIndex struct{} - -func (InstructionGetIndex) Opcode() Opcode { - return GetIndex -} - -func (ins InstructionGetIndex) String() string { - return ins.Opcode().String() -} - -func (ins InstructionGetIndex) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// SetIndex - -type InstructionSetIndex struct{} - -func (InstructionSetIndex) Opcode() Opcode { - return SetIndex -} - -func (ins InstructionSetIndex) String() string { - return ins.Opcode().String() -} - -func (ins InstructionSetIndex) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// NewArray - -type InstructionNewArray struct { - TypeIndex uint16 - Size uint16 - IsResource bool -} - -func (InstructionNewArray) Opcode() Opcode { - return NewArray -} - -func (ins InstructionNewArray) String() string { - return fmt.Sprintf( - "%s typeIndex:%d size:%d isResource:%t", - ins.Opcode(), - ins.TypeIndex, - ins.Size, - ins.IsResource, - ) -} - -func (ins InstructionNewArray) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) - emitUint16(code, ins.TypeIndex) - emitUint16(code, ins.Size) - emitBool(code, ins.IsResource) -} - -func DecodeNewArray(ip *uint16, code []byte) (ins InstructionNewArray) { - ins.TypeIndex = decodeUint16(ip, code) - ins.Size = decodeUint16(ip, code) - ins.IsResource = decodeBool(ip, code) - return ins -} - -// IntAdd - -type InstructionIntAdd struct{} - -func (InstructionIntAdd) Opcode() Opcode { - return IntAdd -} - -func (ins InstructionIntAdd) String() string { - return ins.Opcode().String() -} - -func (ins InstructionIntAdd) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// IntSubtract - -type InstructionIntSubtract struct{} - -func (InstructionIntSubtract) Opcode() Opcode { - return IntSubtract -} - -func (ins InstructionIntSubtract) String() string { - return ins.Opcode().String() -} - -func (ins InstructionIntSubtract) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// IntMultiply - -type InstructionIntMultiply struct{} - -func (InstructionIntMultiply) Opcode() Opcode { - return IntMultiply -} - -func (ins InstructionIntMultiply) String() string { - return ins.Opcode().String() -} - -func (ins InstructionIntMultiply) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// IntDivide - -type InstructionIntDivide struct{} - -func (InstructionIntDivide) Opcode() Opcode { - return IntDivide -} - -func (ins InstructionIntDivide) String() string { - return ins.Opcode().String() -} - -func (ins InstructionIntDivide) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// IntMod - -type InstructionIntMod struct{} - -func (InstructionIntMod) Opcode() Opcode { - return IntMod -} - -func (ins InstructionIntMod) String() string { - return ins.Opcode().String() -} - -func (ins InstructionIntMod) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// Equal - -type InstructionEqual struct{} - -func (ins InstructionEqual) Opcode() Opcode { - return Equal -} - -func (ins InstructionEqual) String() string { - return ins.Opcode().String() -} - -func (ins InstructionEqual) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// NotEqual - -type InstructionNotEqual struct{} - -func (InstructionNotEqual) Opcode() Opcode { - return NotEqual -} - -func (ins InstructionNotEqual) String() string { - return ins.Opcode().String() -} - -func (ins InstructionNotEqual) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// IntLess - -type InstructionIntLess struct{} - -func (InstructionIntLess) Opcode() Opcode { - return IntLess -} - -func (ins InstructionIntLess) String() string { - return ins.Opcode().String() -} - -func (ins InstructionIntLess) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// IntLessOrEqual - -type InstructionIntLessOrEqual struct{} - -func (ins InstructionIntLessOrEqual) Opcode() Opcode { - return IntLessOrEqual -} - -func (ins InstructionIntLessOrEqual) String() string { - return ins.Opcode().String() -} - -func (ins InstructionIntLessOrEqual) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// IntGreater - -type InstructionIntGreater struct{} - -func (InstructionIntGreater) Opcode() Opcode { - return IntGreater -} - -func (ins InstructionIntGreater) String() string { - return ins.Opcode().String() -} - -func (ins InstructionIntGreater) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// IntGreaterOrEqual - -type InstructionIntGreaterOrEqual struct{} - -func (ins InstructionIntGreaterOrEqual) Opcode() Opcode { - return IntGreaterOrEqual -} - -func (ins InstructionIntGreaterOrEqual) String() string { - return ins.Opcode().String() -} - -func (ins InstructionIntGreaterOrEqual) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// Unwrap - -type InstructionUnwrap struct{} - -func (InstructionUnwrap) Opcode() Opcode { - return Unwrap -} - -func (ins InstructionUnwrap) String() string { - return ins.Opcode().String() -} - -func (ins InstructionUnwrap) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// Cast - -type InstructionCast struct { - TypeIndex uint16 - Kind CastKind -} - -func (InstructionCast) Opcode() Opcode { - return Cast -} - -func (ins InstructionCast) String() string { - return fmt.Sprintf( - "%s typeIndex:%d kind:%d", - ins.Opcode(), - ins.TypeIndex, - ins.Kind, - ) -} - -func (ins InstructionCast) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) - emitUint16(code, ins.TypeIndex) - emitByte(code, byte(ins.Kind)) -} - -func DecodeCast(ip *uint16, code []byte) (ins InstructionCast) { - ins.TypeIndex = decodeUint16(ip, code) - ins.Kind = CastKind(decodeByte(ip, code)) - return ins -} - -// Destroy - -type InstructionDestroy struct{} - -func (InstructionDestroy) Opcode() Opcode { - return Destroy -} - -func (ins InstructionDestroy) String() string { - return ins.Opcode().String() -} - -func (ins InstructionDestroy) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// Transfer - -type InstructionTransfer struct { - TypeIndex uint16 -} - -func (InstructionTransfer) Opcode() Opcode { - return Transfer -} - -func (ins InstructionTransfer) String() string { - return fmt.Sprintf( - "%s typeIndex:%d", - ins.Opcode(), - ins.TypeIndex, - ) -} - -func (ins InstructionTransfer) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) - emitUint16(code, ins.TypeIndex) -} - -func DecodeTransfer(ip *uint16, code []byte) (ins InstructionTransfer) { - ins.TypeIndex = decodeUint16(ip, code) - return ins -} - -// NewRef - -type InstructionNewRef struct { - TypeIndex uint16 -} - -func (InstructionNewRef) Opcode() Opcode { - return NewRef -} - -func (ins InstructionNewRef) String() string { - return fmt.Sprintf( - "%s typeIndex:%d", - ins.Opcode(), - ins.TypeIndex, - ) -} - -func (ins InstructionNewRef) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) - emitUint16(code, ins.TypeIndex) -} - -func DecodeNewRef(ip *uint16, code []byte) (ins InstructionNewRef) { - ins.TypeIndex = decodeUint16(ip, code) - return ins -} - -// Path - -type InstructionPath struct { - Domain common.PathDomain - Identifier string -} - -func (InstructionPath) Opcode() Opcode { - return Path -} - -func (ins InstructionPath) String() string { - return fmt.Sprintf( - "%s domain:%d identifier:%q", - ins.Opcode(), - ins.Domain, - ins.Identifier, - ) -} - -func (ins InstructionPath) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) - emitByte(code, byte(ins.Domain)) - emitString(code, ins.Identifier) -} - -func DecodePath(ip *uint16, code []byte) (ins InstructionPath) { - ins.Domain = common.PathDomain(decodeByte(ip, code)) - ins.Identifier = decodeString(ip, code) - return ins -} - -// Type arguments - -func emitTypeArgs(code *[]byte, typeArgs []uint16) { - emitUint16(code, uint16(len(typeArgs))) - for _, typeArg := range typeArgs { - emitUint16(code, typeArg) - } -} - -func decodeTypeArgs(ip *uint16, code []byte) (typeArgs []uint16) { - typeArgCount := decodeUint16(ip, code) - for i := 0; i < int(typeArgCount); i++ { - typeIndex := decodeUint16(ip, code) - typeArgs = append(typeArgs, typeIndex) - } - return typeArgs -} - -// Invoke - -type InstructionInvoke struct { - TypeArgs []uint16 -} - -func (InstructionInvoke) Opcode() Opcode { - return Invoke -} - -func (ins InstructionInvoke) String() string { - return fmt.Sprintf( - "%s typeArgs:%v", - ins.Opcode(), - ins.TypeArgs, - ) -} - -func (ins InstructionInvoke) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) - emitTypeArgs(code, ins.TypeArgs) -} - -func DecodeInvoke(ip *uint16, code []byte) (ins InstructionInvoke) { - ins.TypeArgs = decodeTypeArgs(ip, code) - return ins -} - -// New - -type InstructionNew struct { - Kind uint16 - TypeIndex uint16 -} - -func (InstructionNew) Opcode() Opcode { - return New -} - -func (ins InstructionNew) String() string { - return fmt.Sprintf( - "%s kind:%d typeIndex:%d", - ins.Opcode(), - ins.Kind, - ins.TypeIndex, - ) -} - -func (ins InstructionNew) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) - emitUint16(code, ins.Kind) - emitUint16(code, ins.TypeIndex) -} - -func DecodeNew(ip *uint16, code []byte) (ins InstructionNew) { - ins.Kind = decodeUint16(ip, code) - ins.TypeIndex = decodeUint16(ip, code) - return ins -} - -// InvokeDynamic - -type InstructionInvokeDynamic struct { - Name string - TypeArgs []uint16 - ArgCount uint16 -} - -func (InstructionInvokeDynamic) Opcode() Opcode { - return InvokeDynamic -} - -func (ins InstructionInvokeDynamic) String() string { - return fmt.Sprintf( - "%s name:%q typeArgs:%v argCount:%d", - ins.Opcode(), - ins.Name, - ins.TypeArgs, - ins.ArgCount, - ) -} - -func (ins InstructionInvokeDynamic) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) - emitString(code, ins.Name) - emitTypeArgs(code, ins.TypeArgs) - emitUint16(code, ins.ArgCount) -} - -func DecodeInvokeDynamic(ip *uint16, code []byte) (ins InstructionInvokeDynamic) { - ins.Name = decodeString(ip, code) - ins.TypeArgs = decodeTypeArgs(ip, code) - ins.ArgCount = decodeUint16(ip, code) - return -} - -// Unknown - -type InstructionUnknown struct{} - -func (InstructionUnknown) Opcode() Opcode { - return Unknown -} - -func (ins InstructionUnknown) String() string { - return ins.Opcode().String() -} - -func (ins InstructionUnknown) Encode(code *[]byte) { - emitOpcode(code, ins.Opcode()) -} - -// DecodeInstruction - -func DecodeInstruction(ip *uint16, code []byte) Instruction { - - switch Opcode(decodeByte(ip, code)) { - - case Return: - return InstructionReturn{} - case ReturnValue: - return InstructionReturnValue{} - case Jump: - return DecodeJump(ip, code) - case JumpIfFalse: - return DecodeJumpIfFalse(ip, code) - case IntAdd: - return InstructionIntAdd{} - case IntSubtract: - return InstructionIntSubtract{} - case IntMultiply: - return InstructionIntMultiply{} - case IntDivide: - return InstructionIntDivide{} - case IntMod: - return InstructionIntMod{} - case IntLess: - return InstructionIntLess{} - case IntGreater: - return InstructionIntGreater{} - case IntLessOrEqual: - return InstructionIntLessOrEqual{} - case IntGreaterOrEqual: - return InstructionIntGreaterOrEqual{} - case Equal: - return InstructionEqual{} - case NotEqual: - return InstructionNotEqual{} - case Unwrap: - return InstructionUnwrap{} - case Destroy: - return InstructionDestroy{} - case Transfer: - return DecodeTransfer(ip, code) - case Cast: - return DecodeCast(ip, code) - case True: - return InstructionTrue{} - case False: - return InstructionFalse{} - case New: - return DecodeNew(ip, code) - case Path: - return DecodePath(ip, code) - case Nil: - return InstructionNil{} - case NewArray: - return DecodeNewArray(ip, code) - case NewDictionary: - // TODO: - return nil - case NewRef: - return DecodeNewRef(ip, code) - case GetConstant: - return DecodeGetConstant(ip, code) - case GetLocal: - return DecodeGetLocal(ip, code) - case SetLocal: - return DecodeSetLocal(ip, code) - case GetGlobal: - return DecodeGetGlobal(ip, code) - case SetGlobal: - return DecodeSetGlobal(ip, code) - case GetField: - return InstructionGetField{} - case SetField: - return InstructionSetField{} - case SetIndex: - return InstructionSetIndex{} - case GetIndex: - return InstructionGetIndex{} - case Invoke: - return DecodeInvoke(ip, code) - case InvokeDynamic: - return DecodeInvokeDynamic(ip, code) - case Drop: - return InstructionDrop{} - case Dup: - return InstructionDup{} - case Unknown: - return InstructionUnknown{} - } - - panic(errors.NewUnreachableError()) -} - func DecodeInstructions(code []byte) []Instruction { var instructions []Instruction var ip uint16 diff --git a/bbq/opcode/instructions.yml b/bbq/opcode/instructions.yml new file mode 100644 index 0000000000..cc71667c9a --- /dev/null +++ b/bbq/opcode/instructions.yml @@ -0,0 +1,178 @@ +- name: "true" + description: Pushes the boolean value `true` onto the stack. + +- name: "false" + description: Pushes the boolean value `false` onto the stack. + +- name: "nil" + description: Pushes the value `nil` onto the stack. + +- name: "dup" + description: Duplicates the top value on the stack. + +- name: "drop" + description: Removes the top value from the stack. + +- name: "getConstant" + description: Pushes the constant at the given index onto the stack. + operands: + - name: "constantIndex" + type: "index" + +- name: "jump" + description: Jumps to the given instruction. + operands: + - name: "target" + type: "index" + +- name: "jumpIfFalse" + description: Jumps to the given instruction, if the top value on the stack is `false`. + operands: + - name: "target" + type: "index" + +- name: "return" + description: Returns from the current function, without a value. + +- name: "returnValue" + description: Returns from the current function, with the top value on the stack. + +- name: "getLocal" + description: Pushes the value of the local at the given index onto the stack. + operands: + - name: "localIndex" + type: "index" + +- name: "setLocal" + description: Sets the value of the local at the given index to the top value on the stack. + operands: + - name: "localIndex" + type: "index" + +- name: "getGlobal" + description: Pushes the value of the global at the given index onto the stack. + operands: + - name: "globalIndex" + type: "index" + +- name: "setGlobal" + description: Sets the value of the global at the given index to the top value on the stack. + operands: + - name: "globalIndex" + type: "index" + +- name: "getField" + description: Pushes the value of the field at the given index onto the stack. + +- name: "setField" + description: Sets the value of the field at the given index to the top value on the stack. + +- name: "getIndex" + description: Pushes the value at the given index onto the stack. + +- name: "setIndex" + description: Sets the value at the given index to the top value on the stack. + +- name: "newArray" + description: Creates a new array with the given type and size. + operands: + - name: "typeIndex" + type: "index" + - name: "size" + type: "size" + - name: "isResource" + type: "bool" + +- name: "intAdd" + description: Adds the top two values on the stack. + +- name: "intSubtract" + description: Subtracts the top two values on the stack. + +- name: "intMultiply" + description: Multiplies the top two values on the stack. + +- name: "intDivide" + description: Divides the top two values on the stack. + +- name: "intMod" + description: Calculates the modulo of the top two values on the stack. + +- name: "equal" + description: Compares the top two values on the stack for equality. + +- name: "notEqual" + description: Compares the top two values on the stack for inequality. + +- name: "intLess" + description: Compares the top two values on the stack for less than. + +- name: "intLessOrEqual" + description: Compares the top two values on the stack for less than or equal. + +- name: "intGreater" + description: Compares the top two values on the stack for greater than. + +- name: "intGreaterOrEqual" + description: Compares the top two values on the stack for greater than or equal. + +- name: "unwrap" + description: Unwraps the top value on the stack. + +- name: "cast" + description: Casts the top value on the stack to the given type. + operands: + - name: "typeIndex" + type: "index" + - name: "kind" + type: "castKind" + +- name: "destroy" + description: Destroys the top value on the stack. + +- name: "transfer" + description: Transfers the top value on the stack. + operands: + - name: "typeIndex" + type: "index" + +- name: "newRef" + description: Creates a new reference with the given type. + operands: + - name: "typeIndex" + type: "index" + +- name: "path" + description: Pushes the path with the given domain and identifier onto the stack. + operands: + - name: "domain" + type: "pathDomain" + - name: "identifier" + type: "string" + +- name: "invoke" + description: Invokes the function with the given type arguments. + operands: + - name: "typeArgs" + type: "indices" + +- name: "new" + description: Creates a new instance of the given type. + operands: + - name: "kind" + type: "compositeKind" + - name: "typeIndex" + type: "index" + +- name: "invokeDynamic" + description: Invokes the dynamic function with the given name, type arguments, and argument count. + operands: + - name: "name" + type: "string" + - name: "typeArgs" + type: "indices" + - name: "argCount" + type: "size" + +- name: "unknown" + description: An unknown instruction. diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index db383d3358..4ef2b46e86 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -478,7 +478,7 @@ func opDup(vm *VM) { } func opNew(vm *VM, ins opcode.InstructionNew) { - compositeKind := common.CompositeKind(ins.Kind) + compositeKind := ins.Kind // decode location staticType := vm.loadType(ins.TypeIndex) diff --git a/go.mod b/go.mod index 6193bce06e..7db9c01457 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( require ( github.com/SaveTheRbtz/mph v0.1.1-0.20240117162131-4166ec7869bc + github.com/goccy/go-yaml v1.15.9 github.com/k0kubun/pp v3.0.1+incompatible github.com/kodova/html-to-markdown v1.0.1 github.com/onflow/crypto v0.25.0 diff --git a/go.sum b/go.sum index cc2c3058d0..b7f332a032 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/fxamacker/cbor/v2 v2.4.1-0.20230228173756-c0c9f774e40c h1:5tm/Wbs9d9r github.com/fxamacker/cbor/v2 v2.4.1-0.20230228173756-c0c9f774e40c/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/circlehash v0.3.0 h1:XKdvTtIJV9t7DDUtsf0RIpC1OcxZtPbmgIH7ekx28WA= github.com/fxamacker/circlehash v0.3.0/go.mod h1:3aq3OfVvsWtkWMb6A1owjOQFA+TLsD5FgJflnaQwtMM= +github.com/goccy/go-yaml v1.15.9 h1:500CYajdgpK4Smqrf86u7VMZuj/bFt2ghdff9D/nQYc= +github.com/goccy/go-yaml v1.15.9/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= From c6756ffd5feda9217666ced4c31993a189d27b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 13 Dec 2024 17:03:53 -0800 Subject: [PATCH 84/89] add the generated instructions --- bbq/opcode/instructions.go | 1076 ++++++++++++++++++++++++++++++++++++ 1 file changed, 1076 insertions(+) create mode 100644 bbq/opcode/instructions.go diff --git a/bbq/opcode/instructions.go b/bbq/opcode/instructions.go new file mode 100644 index 0000000000..04cb0445d9 --- /dev/null +++ b/bbq/opcode/instructions.go @@ -0,0 +1,1076 @@ +// Code generated by gen/main.go from instructions.yml. DO NOT EDIT. + +package opcode + +import ( + "fmt" + "strings" + + "github.com/onflow/cadence/common" + "github.com/onflow/cadence/errors" +) + +// InstructionTrue +// +// Pushes the boolean value `true` onto the stack. +type InstructionTrue struct { +} + +var _ Instruction = InstructionTrue{} + +func (InstructionTrue) Opcode() Opcode { + return True +} + +func (i InstructionTrue) String() string { + return i.Opcode().String() +} + +func (i InstructionTrue) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionFalse +// +// Pushes the boolean value `false` onto the stack. +type InstructionFalse struct { +} + +var _ Instruction = InstructionFalse{} + +func (InstructionFalse) Opcode() Opcode { + return False +} + +func (i InstructionFalse) String() string { + return i.Opcode().String() +} + +func (i InstructionFalse) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionNil +// +// Pushes the value `nil` onto the stack. +type InstructionNil struct { +} + +var _ Instruction = InstructionNil{} + +func (InstructionNil) Opcode() Opcode { + return Nil +} + +func (i InstructionNil) String() string { + return i.Opcode().String() +} + +func (i InstructionNil) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionDup +// +// Duplicates the top value on the stack. +type InstructionDup struct { +} + +var _ Instruction = InstructionDup{} + +func (InstructionDup) Opcode() Opcode { + return Dup +} + +func (i InstructionDup) String() string { + return i.Opcode().String() +} + +func (i InstructionDup) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionDrop +// +// Removes the top value from the stack. +type InstructionDrop struct { +} + +var _ Instruction = InstructionDrop{} + +func (InstructionDrop) Opcode() Opcode { + return Drop +} + +func (i InstructionDrop) String() string { + return i.Opcode().String() +} + +func (i InstructionDrop) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionGetConstant +// +// Pushes the constant at the given index onto the stack. +type InstructionGetConstant struct { + ConstantIndex uint16 +} + +var _ Instruction = InstructionGetConstant{} + +func (InstructionGetConstant) Opcode() Opcode { + return GetConstant +} + +func (i InstructionGetConstant) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " constantIndex:%s", i.ConstantIndex) + return sb.String() +} + +func (i InstructionGetConstant) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitUint16(code, i.ConstantIndex) +} + +func DecodeGetConstant(ip *uint16, code []byte) (i InstructionGetConstant) { + i.ConstantIndex = decodeUint16(ip, code) + return i +} + +// InstructionJump +// +// Jumps to the given instruction. +type InstructionJump struct { + Target uint16 +} + +var _ Instruction = InstructionJump{} + +func (InstructionJump) Opcode() Opcode { + return Jump +} + +func (i InstructionJump) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " target:%s", i.Target) + return sb.String() +} + +func (i InstructionJump) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitUint16(code, i.Target) +} + +func DecodeJump(ip *uint16, code []byte) (i InstructionJump) { + i.Target = decodeUint16(ip, code) + return i +} + +// InstructionJumpIfFalse +// +// Jumps to the given instruction, if the top value on the stack is `false`. +type InstructionJumpIfFalse struct { + Target uint16 +} + +var _ Instruction = InstructionJumpIfFalse{} + +func (InstructionJumpIfFalse) Opcode() Opcode { + return JumpIfFalse +} + +func (i InstructionJumpIfFalse) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " target:%s", i.Target) + return sb.String() +} + +func (i InstructionJumpIfFalse) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitUint16(code, i.Target) +} + +func DecodeJumpIfFalse(ip *uint16, code []byte) (i InstructionJumpIfFalse) { + i.Target = decodeUint16(ip, code) + return i +} + +// InstructionReturn +// +// Returns from the current function, without a value. +type InstructionReturn struct { +} + +var _ Instruction = InstructionReturn{} + +func (InstructionReturn) Opcode() Opcode { + return Return +} + +func (i InstructionReturn) String() string { + return i.Opcode().String() +} + +func (i InstructionReturn) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionReturnValue +// +// Returns from the current function, with the top value on the stack. +type InstructionReturnValue struct { +} + +var _ Instruction = InstructionReturnValue{} + +func (InstructionReturnValue) Opcode() Opcode { + return ReturnValue +} + +func (i InstructionReturnValue) String() string { + return i.Opcode().String() +} + +func (i InstructionReturnValue) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionGetLocal +// +// Pushes the value of the local at the given index onto the stack. +type InstructionGetLocal struct { + LocalIndex uint16 +} + +var _ Instruction = InstructionGetLocal{} + +func (InstructionGetLocal) Opcode() Opcode { + return GetLocal +} + +func (i InstructionGetLocal) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " localIndex:%s", i.LocalIndex) + return sb.String() +} + +func (i InstructionGetLocal) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitUint16(code, i.LocalIndex) +} + +func DecodeGetLocal(ip *uint16, code []byte) (i InstructionGetLocal) { + i.LocalIndex = decodeUint16(ip, code) + return i +} + +// InstructionSetLocal +// +// Sets the value of the local at the given index to the top value on the stack. +type InstructionSetLocal struct { + LocalIndex uint16 +} + +var _ Instruction = InstructionSetLocal{} + +func (InstructionSetLocal) Opcode() Opcode { + return SetLocal +} + +func (i InstructionSetLocal) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " localIndex:%s", i.LocalIndex) + return sb.String() +} + +func (i InstructionSetLocal) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitUint16(code, i.LocalIndex) +} + +func DecodeSetLocal(ip *uint16, code []byte) (i InstructionSetLocal) { + i.LocalIndex = decodeUint16(ip, code) + return i +} + +// InstructionGetGlobal +// +// Pushes the value of the global at the given index onto the stack. +type InstructionGetGlobal struct { + GlobalIndex uint16 +} + +var _ Instruction = InstructionGetGlobal{} + +func (InstructionGetGlobal) Opcode() Opcode { + return GetGlobal +} + +func (i InstructionGetGlobal) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " globalIndex:%s", i.GlobalIndex) + return sb.String() +} + +func (i InstructionGetGlobal) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitUint16(code, i.GlobalIndex) +} + +func DecodeGetGlobal(ip *uint16, code []byte) (i InstructionGetGlobal) { + i.GlobalIndex = decodeUint16(ip, code) + return i +} + +// InstructionSetGlobal +// +// Sets the value of the global at the given index to the top value on the stack. +type InstructionSetGlobal struct { + GlobalIndex uint16 +} + +var _ Instruction = InstructionSetGlobal{} + +func (InstructionSetGlobal) Opcode() Opcode { + return SetGlobal +} + +func (i InstructionSetGlobal) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " globalIndex:%s", i.GlobalIndex) + return sb.String() +} + +func (i InstructionSetGlobal) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitUint16(code, i.GlobalIndex) +} + +func DecodeSetGlobal(ip *uint16, code []byte) (i InstructionSetGlobal) { + i.GlobalIndex = decodeUint16(ip, code) + return i +} + +// InstructionGetField +// +// Pushes the value of the field at the given index onto the stack. +type InstructionGetField struct { +} + +var _ Instruction = InstructionGetField{} + +func (InstructionGetField) Opcode() Opcode { + return GetField +} + +func (i InstructionGetField) String() string { + return i.Opcode().String() +} + +func (i InstructionGetField) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionSetField +// +// Sets the value of the field at the given index to the top value on the stack. +type InstructionSetField struct { +} + +var _ Instruction = InstructionSetField{} + +func (InstructionSetField) Opcode() Opcode { + return SetField +} + +func (i InstructionSetField) String() string { + return i.Opcode().String() +} + +func (i InstructionSetField) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionGetIndex +// +// Pushes the value at the given index onto the stack. +type InstructionGetIndex struct { +} + +var _ Instruction = InstructionGetIndex{} + +func (InstructionGetIndex) Opcode() Opcode { + return GetIndex +} + +func (i InstructionGetIndex) String() string { + return i.Opcode().String() +} + +func (i InstructionGetIndex) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionSetIndex +// +// Sets the value at the given index to the top value on the stack. +type InstructionSetIndex struct { +} + +var _ Instruction = InstructionSetIndex{} + +func (InstructionSetIndex) Opcode() Opcode { + return SetIndex +} + +func (i InstructionSetIndex) String() string { + return i.Opcode().String() +} + +func (i InstructionSetIndex) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionNewArray +// +// Creates a new array with the given type and size. +type InstructionNewArray struct { + TypeIndex uint16 + Size uint16 + IsResource bool +} + +var _ Instruction = InstructionNewArray{} + +func (InstructionNewArray) Opcode() Opcode { + return NewArray +} + +func (i InstructionNewArray) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) + fmt.Fprintf(&sb, " size:%s", i.Size) + fmt.Fprintf(&sb, " isResource:%s", i.IsResource) + return sb.String() +} + +func (i InstructionNewArray) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitUint16(code, i.TypeIndex) + emitUint16(code, i.Size) + emitBool(code, i.IsResource) +} + +func DecodeNewArray(ip *uint16, code []byte) (i InstructionNewArray) { + i.TypeIndex = decodeUint16(ip, code) + i.Size = decodeUint16(ip, code) + i.IsResource = decodeBool(ip, code) + return i +} + +// InstructionIntAdd +// +// Adds the top two values on the stack. +type InstructionIntAdd struct { +} + +var _ Instruction = InstructionIntAdd{} + +func (InstructionIntAdd) Opcode() Opcode { + return IntAdd +} + +func (i InstructionIntAdd) String() string { + return i.Opcode().String() +} + +func (i InstructionIntAdd) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionIntSubtract +// +// Subtracts the top two values on the stack. +type InstructionIntSubtract struct { +} + +var _ Instruction = InstructionIntSubtract{} + +func (InstructionIntSubtract) Opcode() Opcode { + return IntSubtract +} + +func (i InstructionIntSubtract) String() string { + return i.Opcode().String() +} + +func (i InstructionIntSubtract) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionIntMultiply +// +// Multiplies the top two values on the stack. +type InstructionIntMultiply struct { +} + +var _ Instruction = InstructionIntMultiply{} + +func (InstructionIntMultiply) Opcode() Opcode { + return IntMultiply +} + +func (i InstructionIntMultiply) String() string { + return i.Opcode().String() +} + +func (i InstructionIntMultiply) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionIntDivide +// +// Divides the top two values on the stack. +type InstructionIntDivide struct { +} + +var _ Instruction = InstructionIntDivide{} + +func (InstructionIntDivide) Opcode() Opcode { + return IntDivide +} + +func (i InstructionIntDivide) String() string { + return i.Opcode().String() +} + +func (i InstructionIntDivide) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionIntMod +// +// Calculates the modulo of the top two values on the stack. +type InstructionIntMod struct { +} + +var _ Instruction = InstructionIntMod{} + +func (InstructionIntMod) Opcode() Opcode { + return IntMod +} + +func (i InstructionIntMod) String() string { + return i.Opcode().String() +} + +func (i InstructionIntMod) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionEqual +// +// Compares the top two values on the stack for equality. +type InstructionEqual struct { +} + +var _ Instruction = InstructionEqual{} + +func (InstructionEqual) Opcode() Opcode { + return Equal +} + +func (i InstructionEqual) String() string { + return i.Opcode().String() +} + +func (i InstructionEqual) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionNotEqual +// +// Compares the top two values on the stack for inequality. +type InstructionNotEqual struct { +} + +var _ Instruction = InstructionNotEqual{} + +func (InstructionNotEqual) Opcode() Opcode { + return NotEqual +} + +func (i InstructionNotEqual) String() string { + return i.Opcode().String() +} + +func (i InstructionNotEqual) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionIntLess +// +// Compares the top two values on the stack for less than. +type InstructionIntLess struct { +} + +var _ Instruction = InstructionIntLess{} + +func (InstructionIntLess) Opcode() Opcode { + return IntLess +} + +func (i InstructionIntLess) String() string { + return i.Opcode().String() +} + +func (i InstructionIntLess) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionIntLessOrEqual +// +// Compares the top two values on the stack for less than or equal. +type InstructionIntLessOrEqual struct { +} + +var _ Instruction = InstructionIntLessOrEqual{} + +func (InstructionIntLessOrEqual) Opcode() Opcode { + return IntLessOrEqual +} + +func (i InstructionIntLessOrEqual) String() string { + return i.Opcode().String() +} + +func (i InstructionIntLessOrEqual) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionIntGreater +// +// Compares the top two values on the stack for greater than. +type InstructionIntGreater struct { +} + +var _ Instruction = InstructionIntGreater{} + +func (InstructionIntGreater) Opcode() Opcode { + return IntGreater +} + +func (i InstructionIntGreater) String() string { + return i.Opcode().String() +} + +func (i InstructionIntGreater) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionIntGreaterOrEqual +// +// Compares the top two values on the stack for greater than or equal. +type InstructionIntGreaterOrEqual struct { +} + +var _ Instruction = InstructionIntGreaterOrEqual{} + +func (InstructionIntGreaterOrEqual) Opcode() Opcode { + return IntGreaterOrEqual +} + +func (i InstructionIntGreaterOrEqual) String() string { + return i.Opcode().String() +} + +func (i InstructionIntGreaterOrEqual) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionUnwrap +// +// Unwraps the top value on the stack. +type InstructionUnwrap struct { +} + +var _ Instruction = InstructionUnwrap{} + +func (InstructionUnwrap) Opcode() Opcode { + return Unwrap +} + +func (i InstructionUnwrap) String() string { + return i.Opcode().String() +} + +func (i InstructionUnwrap) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionCast +// +// Casts the top value on the stack to the given type. +type InstructionCast struct { + TypeIndex uint16 + Kind CastKind +} + +var _ Instruction = InstructionCast{} + +func (InstructionCast) Opcode() Opcode { + return Cast +} + +func (i InstructionCast) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) + fmt.Fprintf(&sb, " kind:%s", i.Kind) + return sb.String() +} + +func (i InstructionCast) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitUint16(code, i.TypeIndex) + emitCastKind(code, i.Kind) +} + +func DecodeCast(ip *uint16, code []byte) (i InstructionCast) { + i.TypeIndex = decodeUint16(ip, code) + i.Kind = decodeCastKind(ip, code) + return i +} + +// InstructionDestroy +// +// Destroys the top value on the stack. +type InstructionDestroy struct { +} + +var _ Instruction = InstructionDestroy{} + +func (InstructionDestroy) Opcode() Opcode { + return Destroy +} + +func (i InstructionDestroy) String() string { + return i.Opcode().String() +} + +func (i InstructionDestroy) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionTransfer +// +// Transfers the top value on the stack. +type InstructionTransfer struct { + TypeIndex uint16 +} + +var _ Instruction = InstructionTransfer{} + +func (InstructionTransfer) Opcode() Opcode { + return Transfer +} + +func (i InstructionTransfer) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) + return sb.String() +} + +func (i InstructionTransfer) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitUint16(code, i.TypeIndex) +} + +func DecodeTransfer(ip *uint16, code []byte) (i InstructionTransfer) { + i.TypeIndex = decodeUint16(ip, code) + return i +} + +// InstructionNewRef +// +// Creates a new reference with the given type. +type InstructionNewRef struct { + TypeIndex uint16 +} + +var _ Instruction = InstructionNewRef{} + +func (InstructionNewRef) Opcode() Opcode { + return NewRef +} + +func (i InstructionNewRef) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) + return sb.String() +} + +func (i InstructionNewRef) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitUint16(code, i.TypeIndex) +} + +func DecodeNewRef(ip *uint16, code []byte) (i InstructionNewRef) { + i.TypeIndex = decodeUint16(ip, code) + return i +} + +// InstructionPath +// +// Pushes the path with the given domain and identifier onto the stack. +type InstructionPath struct { + Domain common.PathDomain + Identifier string +} + +var _ Instruction = InstructionPath{} + +func (InstructionPath) Opcode() Opcode { + return Path +} + +func (i InstructionPath) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " domain:%s", i.Domain) + fmt.Fprintf(&sb, " identifier:%s", i.Identifier) + return sb.String() +} + +func (i InstructionPath) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitPathDomain(code, i.Domain) + emitString(code, i.Identifier) +} + +func DecodePath(ip *uint16, code []byte) (i InstructionPath) { + i.Domain = decodePathDomain(ip, code) + i.Identifier = decodeString(ip, code) + return i +} + +// InstructionInvoke +// +// Invokes the function with the given type arguments. +type InstructionInvoke struct { + TypeArgs []uint16 +} + +var _ Instruction = InstructionInvoke{} + +func (InstructionInvoke) Opcode() Opcode { + return Invoke +} + +func (i InstructionInvoke) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " typeArgs:%s", i.TypeArgs) + return sb.String() +} + +func (i InstructionInvoke) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitUint16Array(code, i.TypeArgs) +} + +func DecodeInvoke(ip *uint16, code []byte) (i InstructionInvoke) { + i.TypeArgs = decodeUint16Array(ip, code) + return i +} + +// InstructionNew +// +// Creates a new instance of the given type. +type InstructionNew struct { + Kind common.CompositeKind + TypeIndex uint16 +} + +var _ Instruction = InstructionNew{} + +func (InstructionNew) Opcode() Opcode { + return New +} + +func (i InstructionNew) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " kind:%s", i.Kind) + fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) + return sb.String() +} + +func (i InstructionNew) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitCompositeKind(code, i.Kind) + emitUint16(code, i.TypeIndex) +} + +func DecodeNew(ip *uint16, code []byte) (i InstructionNew) { + i.Kind = decodeCompositeKind(ip, code) + i.TypeIndex = decodeUint16(ip, code) + return i +} + +// InstructionInvokeDynamic +// +// Invokes the dynamic function with the given name, type arguments, and argument count. +type InstructionInvokeDynamic struct { + Name string + TypeArgs []uint16 + ArgCount uint16 +} + +var _ Instruction = InstructionInvokeDynamic{} + +func (InstructionInvokeDynamic) Opcode() Opcode { + return InvokeDynamic +} + +func (i InstructionInvokeDynamic) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " name:%s", i.Name) + fmt.Fprintf(&sb, " typeArgs:%s", i.TypeArgs) + fmt.Fprintf(&sb, " argCount:%s", i.ArgCount) + return sb.String() +} + +func (i InstructionInvokeDynamic) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitString(code, i.Name) + emitUint16Array(code, i.TypeArgs) + emitUint16(code, i.ArgCount) +} + +func DecodeInvokeDynamic(ip *uint16, code []byte) (i InstructionInvokeDynamic) { + i.Name = decodeString(ip, code) + i.TypeArgs = decodeUint16Array(ip, code) + i.ArgCount = decodeUint16(ip, code) + return i +} + +// InstructionUnknown +// +// An unknown instruction. +type InstructionUnknown struct { +} + +var _ Instruction = InstructionUnknown{} + +func (InstructionUnknown) Opcode() Opcode { + return Unknown +} + +func (i InstructionUnknown) String() string { + return i.Opcode().String() +} + +func (i InstructionUnknown) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +func DecodeInstruction(ip *uint16, code []byte) Instruction { + switch Opcode(decodeByte(ip, code)) { + case True: + return InstructionTrue{} + case False: + return InstructionFalse{} + case Nil: + return InstructionNil{} + case Dup: + return InstructionDup{} + case Drop: + return InstructionDrop{} + case GetConstant: + return DecodeGetConstant(ip, code) + case Jump: + return DecodeJump(ip, code) + case JumpIfFalse: + return DecodeJumpIfFalse(ip, code) + case Return: + return InstructionReturn{} + case ReturnValue: + return InstructionReturnValue{} + case GetLocal: + return DecodeGetLocal(ip, code) + case SetLocal: + return DecodeSetLocal(ip, code) + case GetGlobal: + return DecodeGetGlobal(ip, code) + case SetGlobal: + return DecodeSetGlobal(ip, code) + case GetField: + return InstructionGetField{} + case SetField: + return InstructionSetField{} + case GetIndex: + return InstructionGetIndex{} + case SetIndex: + return InstructionSetIndex{} + case NewArray: + return DecodeNewArray(ip, code) + case IntAdd: + return InstructionIntAdd{} + case IntSubtract: + return InstructionIntSubtract{} + case IntMultiply: + return InstructionIntMultiply{} + case IntDivide: + return InstructionIntDivide{} + case IntMod: + return InstructionIntMod{} + case Equal: + return InstructionEqual{} + case NotEqual: + return InstructionNotEqual{} + case IntLess: + return InstructionIntLess{} + case IntLessOrEqual: + return InstructionIntLessOrEqual{} + case IntGreater: + return InstructionIntGreater{} + case IntGreaterOrEqual: + return InstructionIntGreaterOrEqual{} + case Unwrap: + return InstructionUnwrap{} + case Cast: + return DecodeCast(ip, code) + case Destroy: + return InstructionDestroy{} + case Transfer: + return DecodeTransfer(ip, code) + case NewRef: + return DecodeNewRef(ip, code) + case Path: + return DecodePath(ip, code) + case Invoke: + return DecodeInvoke(ip, code) + case New: + return DecodeNew(ip, code) + case InvokeDynamic: + return DecodeInvokeDynamic(ip, code) + case Unknown: + return InstructionUnknown{} + } + + panic(errors.NewUnreachableError()) +} From 22370f72313075e673e696b265a6e54ccb8ecc81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 16 Dec 2024 10:20:25 -0800 Subject: [PATCH 85/89] group instructions into categories --- bbq/opcode/instructions.go | 1106 +++++++++++++++++------------------ bbq/opcode/instructions.yml | 222 ++++--- 2 files changed, 679 insertions(+), 649 deletions(-) diff --git a/bbq/opcode/instructions.go b/bbq/opcode/instructions.go index 04cb0445d9..2797c8943c 100644 --- a/bbq/opcode/instructions.go +++ b/bbq/opcode/instructions.go @@ -10,233 +10,23 @@ import ( "github.com/onflow/cadence/errors" ) -// InstructionTrue -// -// Pushes the boolean value `true` onto the stack. -type InstructionTrue struct { -} - -var _ Instruction = InstructionTrue{} - -func (InstructionTrue) Opcode() Opcode { - return True -} - -func (i InstructionTrue) String() string { - return i.Opcode().String() -} - -func (i InstructionTrue) Encode(code *[]byte) { - emitOpcode(code, i.Opcode()) -} - -// InstructionFalse -// -// Pushes the boolean value `false` onto the stack. -type InstructionFalse struct { -} - -var _ Instruction = InstructionFalse{} - -func (InstructionFalse) Opcode() Opcode { - return False -} - -func (i InstructionFalse) String() string { - return i.Opcode().String() -} - -func (i InstructionFalse) Encode(code *[]byte) { - emitOpcode(code, i.Opcode()) -} - -// InstructionNil -// -// Pushes the value `nil` onto the stack. -type InstructionNil struct { -} - -var _ Instruction = InstructionNil{} - -func (InstructionNil) Opcode() Opcode { - return Nil -} - -func (i InstructionNil) String() string { - return i.Opcode().String() -} - -func (i InstructionNil) Encode(code *[]byte) { - emitOpcode(code, i.Opcode()) -} - -// InstructionDup -// -// Duplicates the top value on the stack. -type InstructionDup struct { -} - -var _ Instruction = InstructionDup{} - -func (InstructionDup) Opcode() Opcode { - return Dup -} - -func (i InstructionDup) String() string { - return i.Opcode().String() -} - -func (i InstructionDup) Encode(code *[]byte) { - emitOpcode(code, i.Opcode()) -} - -// InstructionDrop -// -// Removes the top value from the stack. -type InstructionDrop struct { -} - -var _ Instruction = InstructionDrop{} - -func (InstructionDrop) Opcode() Opcode { - return Drop -} - -func (i InstructionDrop) String() string { - return i.Opcode().String() -} - -func (i InstructionDrop) Encode(code *[]byte) { - emitOpcode(code, i.Opcode()) -} - -// InstructionGetConstant -// -// Pushes the constant at the given index onto the stack. -type InstructionGetConstant struct { - ConstantIndex uint16 -} - -var _ Instruction = InstructionGetConstant{} - -func (InstructionGetConstant) Opcode() Opcode { - return GetConstant -} - -func (i InstructionGetConstant) String() string { - var sb strings.Builder - sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " constantIndex:%s", i.ConstantIndex) - return sb.String() -} - -func (i InstructionGetConstant) Encode(code *[]byte) { - emitOpcode(code, i.Opcode()) - emitUint16(code, i.ConstantIndex) -} - -func DecodeGetConstant(ip *uint16, code []byte) (i InstructionGetConstant) { - i.ConstantIndex = decodeUint16(ip, code) - return i -} - -// InstructionJump -// -// Jumps to the given instruction. -type InstructionJump struct { - Target uint16 -} - -var _ Instruction = InstructionJump{} - -func (InstructionJump) Opcode() Opcode { - return Jump -} - -func (i InstructionJump) String() string { - var sb strings.Builder - sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " target:%s", i.Target) - return sb.String() -} - -func (i InstructionJump) Encode(code *[]byte) { - emitOpcode(code, i.Opcode()) - emitUint16(code, i.Target) -} - -func DecodeJump(ip *uint16, code []byte) (i InstructionJump) { - i.Target = decodeUint16(ip, code) - return i -} - -// InstructionJumpIfFalse -// -// Jumps to the given instruction, if the top value on the stack is `false`. -type InstructionJumpIfFalse struct { - Target uint16 -} - -var _ Instruction = InstructionJumpIfFalse{} - -func (InstructionJumpIfFalse) Opcode() Opcode { - return JumpIfFalse -} - -func (i InstructionJumpIfFalse) String() string { - var sb strings.Builder - sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " target:%s", i.Target) - return sb.String() -} - -func (i InstructionJumpIfFalse) Encode(code *[]byte) { - emitOpcode(code, i.Opcode()) - emitUint16(code, i.Target) -} - -func DecodeJumpIfFalse(ip *uint16, code []byte) (i InstructionJumpIfFalse) { - i.Target = decodeUint16(ip, code) - return i -} - -// InstructionReturn -// -// Returns from the current function, without a value. -type InstructionReturn struct { -} - -var _ Instruction = InstructionReturn{} - -func (InstructionReturn) Opcode() Opcode { - return Return -} - -func (i InstructionReturn) String() string { - return i.Opcode().String() -} - -func (i InstructionReturn) Encode(code *[]byte) { - emitOpcode(code, i.Opcode()) -} - -// InstructionReturnValue +// InstructionUnknown // -// Returns from the current function, with the top value on the stack. -type InstructionReturnValue struct { +// An unknown instruction. +type InstructionUnknown struct { } -var _ Instruction = InstructionReturnValue{} +var _ Instruction = InstructionUnknown{} -func (InstructionReturnValue) Opcode() Opcode { - return ReturnValue +func (InstructionUnknown) Opcode() Opcode { + return Unknown } -func (i InstructionReturnValue) String() string { +func (i InstructionUnknown) String() string { return i.Opcode().String() } -func (i InstructionReturnValue) Encode(code *[]byte) { +func (i InstructionUnknown) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) } @@ -440,576 +230,768 @@ func (i InstructionSetIndex) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) } -// InstructionNewArray +// InstructionTrue // -// Creates a new array with the given type and size. -type InstructionNewArray struct { - TypeIndex uint16 - Size uint16 - IsResource bool +// Pushes the boolean value `true` onto the stack. +type InstructionTrue struct { } -var _ Instruction = InstructionNewArray{} +var _ Instruction = InstructionTrue{} -func (InstructionNewArray) Opcode() Opcode { - return NewArray +func (InstructionTrue) Opcode() Opcode { + return True } -func (i InstructionNewArray) String() string { - var sb strings.Builder - sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) - fmt.Fprintf(&sb, " size:%s", i.Size) - fmt.Fprintf(&sb, " isResource:%s", i.IsResource) - return sb.String() +func (i InstructionTrue) String() string { + return i.Opcode().String() } -func (i InstructionNewArray) Encode(code *[]byte) { +func (i InstructionTrue) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) - emitUint16(code, i.TypeIndex) - emitUint16(code, i.Size) - emitBool(code, i.IsResource) } -func DecodeNewArray(ip *uint16, code []byte) (i InstructionNewArray) { +// InstructionFalse +// +// Pushes the boolean value `false` onto the stack. +type InstructionFalse struct { +} + +var _ Instruction = InstructionFalse{} + +func (InstructionFalse) Opcode() Opcode { + return False +} + +func (i InstructionFalse) String() string { + return i.Opcode().String() +} + +func (i InstructionFalse) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionNil +// +// Pushes the value `nil` onto the stack. +type InstructionNil struct { +} + +var _ Instruction = InstructionNil{} + +func (InstructionNil) Opcode() Opcode { + return Nil +} + +func (i InstructionNil) String() string { + return i.Opcode().String() +} + +func (i InstructionNil) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) +} + +// InstructionPath +// +// Pushes the path with the given domain and identifier onto the stack. +type InstructionPath struct { + Domain common.PathDomain + Identifier string +} + +var _ Instruction = InstructionPath{} + +func (InstructionPath) Opcode() Opcode { + return Path +} + +func (i InstructionPath) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " domain:%s", i.Domain) + fmt.Fprintf(&sb, " identifier:%s", i.Identifier) + return sb.String() +} + +func (i InstructionPath) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitPathDomain(code, i.Domain) + emitString(code, i.Identifier) +} + +func DecodePath(ip *uint16, code []byte) (i InstructionPath) { + i.Domain = decodePathDomain(ip, code) + i.Identifier = decodeString(ip, code) + return i +} + +// InstructionNew +// +// Creates a new instance of the given type. +type InstructionNew struct { + Kind common.CompositeKind + TypeIndex uint16 +} + +var _ Instruction = InstructionNew{} + +func (InstructionNew) Opcode() Opcode { + return New +} + +func (i InstructionNew) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " kind:%s", i.Kind) + fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) + return sb.String() +} + +func (i InstructionNew) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitCompositeKind(code, i.Kind) + emitUint16(code, i.TypeIndex) +} + +func DecodeNew(ip *uint16, code []byte) (i InstructionNew) { + i.Kind = decodeCompositeKind(ip, code) + i.TypeIndex = decodeUint16(ip, code) + return i +} + +// InstructionNewArray +// +// Creates a new array with the given type and size. +type InstructionNewArray struct { + TypeIndex uint16 + Size uint16 + IsResource bool +} + +var _ Instruction = InstructionNewArray{} + +func (InstructionNewArray) Opcode() Opcode { + return NewArray +} + +func (i InstructionNewArray) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) + fmt.Fprintf(&sb, " size:%s", i.Size) + fmt.Fprintf(&sb, " isResource:%s", i.IsResource) + return sb.String() +} + +func (i InstructionNewArray) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitUint16(code, i.TypeIndex) + emitUint16(code, i.Size) + emitBool(code, i.IsResource) +} + +func DecodeNewArray(ip *uint16, code []byte) (i InstructionNewArray) { i.TypeIndex = decodeUint16(ip, code) i.Size = decodeUint16(ip, code) i.IsResource = decodeBool(ip, code) return i } -// InstructionIntAdd +// InstructionNewRef // -// Adds the top two values on the stack. -type InstructionIntAdd struct { +// Creates a new reference with the given type. +type InstructionNewRef struct { + TypeIndex uint16 } -var _ Instruction = InstructionIntAdd{} +var _ Instruction = InstructionNewRef{} -func (InstructionIntAdd) Opcode() Opcode { - return IntAdd +func (InstructionNewRef) Opcode() Opcode { + return NewRef } -func (i InstructionIntAdd) String() string { +func (i InstructionNewRef) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) + return sb.String() +} + +func (i InstructionNewRef) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitUint16(code, i.TypeIndex) +} + +func DecodeNewRef(ip *uint16, code []byte) (i InstructionNewRef) { + i.TypeIndex = decodeUint16(ip, code) + return i +} + +// InstructionGetConstant +// +// Pushes the constant at the given index onto the stack. +type InstructionGetConstant struct { + ConstantIndex uint16 +} + +var _ Instruction = InstructionGetConstant{} + +func (InstructionGetConstant) Opcode() Opcode { + return GetConstant +} + +func (i InstructionGetConstant) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " constantIndex:%s", i.ConstantIndex) + return sb.String() +} + +func (i InstructionGetConstant) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitUint16(code, i.ConstantIndex) +} + +func DecodeGetConstant(ip *uint16, code []byte) (i InstructionGetConstant) { + i.ConstantIndex = decodeUint16(ip, code) + return i +} + +// InstructionInvoke +// +// Invokes the function with the given type arguments. +type InstructionInvoke struct { + TypeArgs []uint16 +} + +var _ Instruction = InstructionInvoke{} + +func (InstructionInvoke) Opcode() Opcode { + return Invoke +} + +func (i InstructionInvoke) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " typeArgs:%s", i.TypeArgs) + return sb.String() +} + +func (i InstructionInvoke) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitUint16Array(code, i.TypeArgs) +} + +func DecodeInvoke(ip *uint16, code []byte) (i InstructionInvoke) { + i.TypeArgs = decodeUint16Array(ip, code) + return i +} + +// InstructionInvokeDynamic +// +// Invokes the dynamic function with the given name, type arguments, and argument count. +type InstructionInvokeDynamic struct { + Name string + TypeArgs []uint16 + ArgCount uint16 +} + +var _ Instruction = InstructionInvokeDynamic{} + +func (InstructionInvokeDynamic) Opcode() Opcode { + return InvokeDynamic +} + +func (i InstructionInvokeDynamic) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " name:%s", i.Name) + fmt.Fprintf(&sb, " typeArgs:%s", i.TypeArgs) + fmt.Fprintf(&sb, " argCount:%s", i.ArgCount) + return sb.String() +} + +func (i InstructionInvokeDynamic) Encode(code *[]byte) { + emitOpcode(code, i.Opcode()) + emitString(code, i.Name) + emitUint16Array(code, i.TypeArgs) + emitUint16(code, i.ArgCount) +} + +func DecodeInvokeDynamic(ip *uint16, code []byte) (i InstructionInvokeDynamic) { + i.Name = decodeString(ip, code) + i.TypeArgs = decodeUint16Array(ip, code) + i.ArgCount = decodeUint16(ip, code) + return i +} + +// InstructionDup +// +// Duplicates the top value on the stack. +type InstructionDup struct { +} + +var _ Instruction = InstructionDup{} + +func (InstructionDup) Opcode() Opcode { + return Dup +} + +func (i InstructionDup) String() string { return i.Opcode().String() } -func (i InstructionIntAdd) Encode(code *[]byte) { +func (i InstructionDup) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) } -// InstructionIntSubtract +// InstructionDrop // -// Subtracts the top two values on the stack. -type InstructionIntSubtract struct { +// Removes the top value from the stack. +type InstructionDrop struct { } -var _ Instruction = InstructionIntSubtract{} +var _ Instruction = InstructionDrop{} -func (InstructionIntSubtract) Opcode() Opcode { - return IntSubtract +func (InstructionDrop) Opcode() Opcode { + return Drop } -func (i InstructionIntSubtract) String() string { +func (i InstructionDrop) String() string { return i.Opcode().String() } -func (i InstructionIntSubtract) Encode(code *[]byte) { +func (i InstructionDrop) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) } -// InstructionIntMultiply +// InstructionDestroy // -// Multiplies the top two values on the stack. -type InstructionIntMultiply struct { +// Destroys the top value on the stack. +type InstructionDestroy struct { } -var _ Instruction = InstructionIntMultiply{} +var _ Instruction = InstructionDestroy{} -func (InstructionIntMultiply) Opcode() Opcode { - return IntMultiply +func (InstructionDestroy) Opcode() Opcode { + return Destroy } -func (i InstructionIntMultiply) String() string { +func (i InstructionDestroy) String() string { return i.Opcode().String() } -func (i InstructionIntMultiply) Encode(code *[]byte) { +func (i InstructionDestroy) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) } -// InstructionIntDivide +// InstructionUnwrap // -// Divides the top two values on the stack. -type InstructionIntDivide struct { +// Unwraps the top value on the stack. +type InstructionUnwrap struct { } -var _ Instruction = InstructionIntDivide{} +var _ Instruction = InstructionUnwrap{} -func (InstructionIntDivide) Opcode() Opcode { - return IntDivide +func (InstructionUnwrap) Opcode() Opcode { + return Unwrap } -func (i InstructionIntDivide) String() string { +func (i InstructionUnwrap) String() string { return i.Opcode().String() } -func (i InstructionIntDivide) Encode(code *[]byte) { +func (i InstructionUnwrap) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) } -// InstructionIntMod +// InstructionTransfer // -// Calculates the modulo of the top two values on the stack. -type InstructionIntMod struct { +// Transfers the top value on the stack. +type InstructionTransfer struct { + TypeIndex uint16 } -var _ Instruction = InstructionIntMod{} +var _ Instruction = InstructionTransfer{} -func (InstructionIntMod) Opcode() Opcode { - return IntMod +func (InstructionTransfer) Opcode() Opcode { + return Transfer } -func (i InstructionIntMod) String() string { - return i.Opcode().String() +func (i InstructionTransfer) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) + return sb.String() } -func (i InstructionIntMod) Encode(code *[]byte) { +func (i InstructionTransfer) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) + emitUint16(code, i.TypeIndex) } -// InstructionEqual +func DecodeTransfer(ip *uint16, code []byte) (i InstructionTransfer) { + i.TypeIndex = decodeUint16(ip, code) + return i +} + +// InstructionCast // -// Compares the top two values on the stack for equality. -type InstructionEqual struct { +// Casts the top value on the stack to the given type. +type InstructionCast struct { + TypeIndex uint16 + Kind CastKind } -var _ Instruction = InstructionEqual{} +var _ Instruction = InstructionCast{} -func (InstructionEqual) Opcode() Opcode { - return Equal +func (InstructionCast) Opcode() Opcode { + return Cast } -func (i InstructionEqual) String() string { - return i.Opcode().String() +func (i InstructionCast) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) + fmt.Fprintf(&sb, " kind:%s", i.Kind) + return sb.String() } -func (i InstructionEqual) Encode(code *[]byte) { +func (i InstructionCast) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) + emitUint16(code, i.TypeIndex) + emitCastKind(code, i.Kind) } -// InstructionNotEqual +func DecodeCast(ip *uint16, code []byte) (i InstructionCast) { + i.TypeIndex = decodeUint16(ip, code) + i.Kind = decodeCastKind(ip, code) + return i +} + +// InstructionJump // -// Compares the top two values on the stack for inequality. -type InstructionNotEqual struct { +// Jumps to the given instruction. +type InstructionJump struct { + Target uint16 } -var _ Instruction = InstructionNotEqual{} +var _ Instruction = InstructionJump{} -func (InstructionNotEqual) Opcode() Opcode { - return NotEqual +func (InstructionJump) Opcode() Opcode { + return Jump } -func (i InstructionNotEqual) String() string { - return i.Opcode().String() +func (i InstructionJump) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " target:%s", i.Target) + return sb.String() } -func (i InstructionNotEqual) Encode(code *[]byte) { +func (i InstructionJump) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) + emitUint16(code, i.Target) } -// InstructionIntLess +func DecodeJump(ip *uint16, code []byte) (i InstructionJump) { + i.Target = decodeUint16(ip, code) + return i +} + +// InstructionJumpIfFalse // -// Compares the top two values on the stack for less than. -type InstructionIntLess struct { +// Jumps to the given instruction, if the top value on the stack is `false`. +type InstructionJumpIfFalse struct { + Target uint16 } -var _ Instruction = InstructionIntLess{} +var _ Instruction = InstructionJumpIfFalse{} -func (InstructionIntLess) Opcode() Opcode { - return IntLess +func (InstructionJumpIfFalse) Opcode() Opcode { + return JumpIfFalse } -func (i InstructionIntLess) String() string { - return i.Opcode().String() +func (i InstructionJumpIfFalse) String() string { + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " target:%s", i.Target) + return sb.String() } -func (i InstructionIntLess) Encode(code *[]byte) { +func (i InstructionJumpIfFalse) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) + emitUint16(code, i.Target) } -// InstructionIntLessOrEqual +func DecodeJumpIfFalse(ip *uint16, code []byte) (i InstructionJumpIfFalse) { + i.Target = decodeUint16(ip, code) + return i +} + +// InstructionReturn // -// Compares the top two values on the stack for less than or equal. -type InstructionIntLessOrEqual struct { +// Returns from the current function, without a value. +type InstructionReturn struct { } -var _ Instruction = InstructionIntLessOrEqual{} +var _ Instruction = InstructionReturn{} -func (InstructionIntLessOrEqual) Opcode() Opcode { - return IntLessOrEqual +func (InstructionReturn) Opcode() Opcode { + return Return } -func (i InstructionIntLessOrEqual) String() string { +func (i InstructionReturn) String() string { return i.Opcode().String() } -func (i InstructionIntLessOrEqual) Encode(code *[]byte) { +func (i InstructionReturn) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) } -// InstructionIntGreater +// InstructionReturnValue // -// Compares the top two values on the stack for greater than. -type InstructionIntGreater struct { +// Returns from the current function, with the top value on the stack. +type InstructionReturnValue struct { } -var _ Instruction = InstructionIntGreater{} +var _ Instruction = InstructionReturnValue{} -func (InstructionIntGreater) Opcode() Opcode { - return IntGreater +func (InstructionReturnValue) Opcode() Opcode { + return ReturnValue } -func (i InstructionIntGreater) String() string { +func (i InstructionReturnValue) String() string { return i.Opcode().String() } -func (i InstructionIntGreater) Encode(code *[]byte) { +func (i InstructionReturnValue) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) } -// InstructionIntGreaterOrEqual +// InstructionEqual // -// Compares the top two values on the stack for greater than or equal. -type InstructionIntGreaterOrEqual struct { +// Compares the top two values on the stack for equality. +type InstructionEqual struct { } -var _ Instruction = InstructionIntGreaterOrEqual{} +var _ Instruction = InstructionEqual{} -func (InstructionIntGreaterOrEqual) Opcode() Opcode { - return IntGreaterOrEqual +func (InstructionEqual) Opcode() Opcode { + return Equal } -func (i InstructionIntGreaterOrEqual) String() string { +func (i InstructionEqual) String() string { return i.Opcode().String() } -func (i InstructionIntGreaterOrEqual) Encode(code *[]byte) { +func (i InstructionEqual) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) } -// InstructionUnwrap +// InstructionNotEqual // -// Unwraps the top value on the stack. -type InstructionUnwrap struct { +// Compares the top two values on the stack for inequality. +type InstructionNotEqual struct { } -var _ Instruction = InstructionUnwrap{} +var _ Instruction = InstructionNotEqual{} -func (InstructionUnwrap) Opcode() Opcode { - return Unwrap +func (InstructionNotEqual) Opcode() Opcode { + return NotEqual } -func (i InstructionUnwrap) String() string { +func (i InstructionNotEqual) String() string { return i.Opcode().String() } -func (i InstructionUnwrap) Encode(code *[]byte) { +func (i InstructionNotEqual) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) } -// InstructionCast +// InstructionIntAdd // -// Casts the top value on the stack to the given type. -type InstructionCast struct { - TypeIndex uint16 - Kind CastKind +// Adds the top two values on the stack. +type InstructionIntAdd struct { } -var _ Instruction = InstructionCast{} +var _ Instruction = InstructionIntAdd{} -func (InstructionCast) Opcode() Opcode { - return Cast +func (InstructionIntAdd) Opcode() Opcode { + return IntAdd } -func (i InstructionCast) String() string { - var sb strings.Builder - sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) - fmt.Fprintf(&sb, " kind:%s", i.Kind) - return sb.String() +func (i InstructionIntAdd) String() string { + return i.Opcode().String() } -func (i InstructionCast) Encode(code *[]byte) { +func (i InstructionIntAdd) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) - emitUint16(code, i.TypeIndex) - emitCastKind(code, i.Kind) } -func DecodeCast(ip *uint16, code []byte) (i InstructionCast) { - i.TypeIndex = decodeUint16(ip, code) - i.Kind = decodeCastKind(ip, code) - return i -} - -// InstructionDestroy +// InstructionIntSubtract // -// Destroys the top value on the stack. -type InstructionDestroy struct { +// Subtracts the top two values on the stack. +type InstructionIntSubtract struct { } -var _ Instruction = InstructionDestroy{} +var _ Instruction = InstructionIntSubtract{} -func (InstructionDestroy) Opcode() Opcode { - return Destroy +func (InstructionIntSubtract) Opcode() Opcode { + return IntSubtract } -func (i InstructionDestroy) String() string { +func (i InstructionIntSubtract) String() string { return i.Opcode().String() } -func (i InstructionDestroy) Encode(code *[]byte) { +func (i InstructionIntSubtract) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) } -// InstructionTransfer +// InstructionIntMultiply // -// Transfers the top value on the stack. -type InstructionTransfer struct { - TypeIndex uint16 +// Multiplies the top two values on the stack. +type InstructionIntMultiply struct { } -var _ Instruction = InstructionTransfer{} +var _ Instruction = InstructionIntMultiply{} -func (InstructionTransfer) Opcode() Opcode { - return Transfer +func (InstructionIntMultiply) Opcode() Opcode { + return IntMultiply } -func (i InstructionTransfer) String() string { - var sb strings.Builder - sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) - return sb.String() +func (i InstructionIntMultiply) String() string { + return i.Opcode().String() } -func (i InstructionTransfer) Encode(code *[]byte) { +func (i InstructionIntMultiply) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) - emitUint16(code, i.TypeIndex) -} - -func DecodeTransfer(ip *uint16, code []byte) (i InstructionTransfer) { - i.TypeIndex = decodeUint16(ip, code) - return i } -// InstructionNewRef -// -// Creates a new reference with the given type. -type InstructionNewRef struct { - TypeIndex uint16 +// InstructionIntDivide +// +// Divides the top two values on the stack. +type InstructionIntDivide struct { } -var _ Instruction = InstructionNewRef{} +var _ Instruction = InstructionIntDivide{} -func (InstructionNewRef) Opcode() Opcode { - return NewRef +func (InstructionIntDivide) Opcode() Opcode { + return IntDivide } -func (i InstructionNewRef) String() string { - var sb strings.Builder - sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) - return sb.String() +func (i InstructionIntDivide) String() string { + return i.Opcode().String() } -func (i InstructionNewRef) Encode(code *[]byte) { +func (i InstructionIntDivide) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) - emitUint16(code, i.TypeIndex) -} - -func DecodeNewRef(ip *uint16, code []byte) (i InstructionNewRef) { - i.TypeIndex = decodeUint16(ip, code) - return i } -// InstructionPath +// InstructionIntMod // -// Pushes the path with the given domain and identifier onto the stack. -type InstructionPath struct { - Domain common.PathDomain - Identifier string +// Calculates the modulo of the top two values on the stack. +type InstructionIntMod struct { } -var _ Instruction = InstructionPath{} +var _ Instruction = InstructionIntMod{} -func (InstructionPath) Opcode() Opcode { - return Path +func (InstructionIntMod) Opcode() Opcode { + return IntMod } -func (i InstructionPath) String() string { - var sb strings.Builder - sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " domain:%s", i.Domain) - fmt.Fprintf(&sb, " identifier:%s", i.Identifier) - return sb.String() +func (i InstructionIntMod) String() string { + return i.Opcode().String() } -func (i InstructionPath) Encode(code *[]byte) { +func (i InstructionIntMod) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) - emitPathDomain(code, i.Domain) - emitString(code, i.Identifier) -} - -func DecodePath(ip *uint16, code []byte) (i InstructionPath) { - i.Domain = decodePathDomain(ip, code) - i.Identifier = decodeString(ip, code) - return i } -// InstructionInvoke +// InstructionIntLess // -// Invokes the function with the given type arguments. -type InstructionInvoke struct { - TypeArgs []uint16 +// Compares the top two values on the stack for less than. +type InstructionIntLess struct { } -var _ Instruction = InstructionInvoke{} +var _ Instruction = InstructionIntLess{} -func (InstructionInvoke) Opcode() Opcode { - return Invoke +func (InstructionIntLess) Opcode() Opcode { + return IntLess } -func (i InstructionInvoke) String() string { - var sb strings.Builder - sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " typeArgs:%s", i.TypeArgs) - return sb.String() +func (i InstructionIntLess) String() string { + return i.Opcode().String() } -func (i InstructionInvoke) Encode(code *[]byte) { +func (i InstructionIntLess) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) - emitUint16Array(code, i.TypeArgs) -} - -func DecodeInvoke(ip *uint16, code []byte) (i InstructionInvoke) { - i.TypeArgs = decodeUint16Array(ip, code) - return i } -// InstructionNew +// InstructionIntLessOrEqual // -// Creates a new instance of the given type. -type InstructionNew struct { - Kind common.CompositeKind - TypeIndex uint16 +// Compares the top two values on the stack for less than or equal. +type InstructionIntLessOrEqual struct { } -var _ Instruction = InstructionNew{} +var _ Instruction = InstructionIntLessOrEqual{} -func (InstructionNew) Opcode() Opcode { - return New +func (InstructionIntLessOrEqual) Opcode() Opcode { + return IntLessOrEqual } -func (i InstructionNew) String() string { - var sb strings.Builder - sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " kind:%s", i.Kind) - fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) - return sb.String() +func (i InstructionIntLessOrEqual) String() string { + return i.Opcode().String() } -func (i InstructionNew) Encode(code *[]byte) { +func (i InstructionIntLessOrEqual) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) - emitCompositeKind(code, i.Kind) - emitUint16(code, i.TypeIndex) -} - -func DecodeNew(ip *uint16, code []byte) (i InstructionNew) { - i.Kind = decodeCompositeKind(ip, code) - i.TypeIndex = decodeUint16(ip, code) - return i } -// InstructionInvokeDynamic +// InstructionIntGreater // -// Invokes the dynamic function with the given name, type arguments, and argument count. -type InstructionInvokeDynamic struct { - Name string - TypeArgs []uint16 - ArgCount uint16 +// Compares the top two values on the stack for greater than. +type InstructionIntGreater struct { } -var _ Instruction = InstructionInvokeDynamic{} +var _ Instruction = InstructionIntGreater{} -func (InstructionInvokeDynamic) Opcode() Opcode { - return InvokeDynamic +func (InstructionIntGreater) Opcode() Opcode { + return IntGreater } -func (i InstructionInvokeDynamic) String() string { - var sb strings.Builder - sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " name:%s", i.Name) - fmt.Fprintf(&sb, " typeArgs:%s", i.TypeArgs) - fmt.Fprintf(&sb, " argCount:%s", i.ArgCount) - return sb.String() +func (i InstructionIntGreater) String() string { + return i.Opcode().String() } -func (i InstructionInvokeDynamic) Encode(code *[]byte) { +func (i InstructionIntGreater) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) - emitString(code, i.Name) - emitUint16Array(code, i.TypeArgs) - emitUint16(code, i.ArgCount) -} - -func DecodeInvokeDynamic(ip *uint16, code []byte) (i InstructionInvokeDynamic) { - i.Name = decodeString(ip, code) - i.TypeArgs = decodeUint16Array(ip, code) - i.ArgCount = decodeUint16(ip, code) - return i } -// InstructionUnknown +// InstructionIntGreaterOrEqual // -// An unknown instruction. -type InstructionUnknown struct { +// Compares the top two values on the stack for greater than or equal. +type InstructionIntGreaterOrEqual struct { } -var _ Instruction = InstructionUnknown{} +var _ Instruction = InstructionIntGreaterOrEqual{} -func (InstructionUnknown) Opcode() Opcode { - return Unknown +func (InstructionIntGreaterOrEqual) Opcode() Opcode { + return IntGreaterOrEqual } -func (i InstructionUnknown) String() string { +func (i InstructionIntGreaterOrEqual) String() string { return i.Opcode().String() } -func (i InstructionUnknown) Encode(code *[]byte) { +func (i InstructionIntGreaterOrEqual) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) } func DecodeInstruction(ip *uint16, code []byte) Instruction { switch Opcode(decodeByte(ip, code)) { - case True: - return InstructionTrue{} - case False: - return InstructionFalse{} - case Nil: - return InstructionNil{} - case Dup: - return InstructionDup{} - case Drop: - return InstructionDrop{} - case GetConstant: - return DecodeGetConstant(ip, code) - case Jump: - return DecodeJump(ip, code) - case JumpIfFalse: - return DecodeJumpIfFalse(ip, code) - case Return: - return InstructionReturn{} - case ReturnValue: - return InstructionReturnValue{} + case Unknown: + return InstructionUnknown{} case GetLocal: return DecodeGetLocal(ip, code) case SetLocal: @@ -1026,8 +1008,50 @@ func DecodeInstruction(ip *uint16, code []byte) Instruction { return InstructionGetIndex{} case SetIndex: return InstructionSetIndex{} + case True: + return InstructionTrue{} + case False: + return InstructionFalse{} + case Nil: + return InstructionNil{} + case Path: + return DecodePath(ip, code) + case New: + return DecodeNew(ip, code) case NewArray: return DecodeNewArray(ip, code) + case NewRef: + return DecodeNewRef(ip, code) + case GetConstant: + return DecodeGetConstant(ip, code) + case Invoke: + return DecodeInvoke(ip, code) + case InvokeDynamic: + return DecodeInvokeDynamic(ip, code) + case Dup: + return InstructionDup{} + case Drop: + return InstructionDrop{} + case Destroy: + return InstructionDestroy{} + case Unwrap: + return InstructionUnwrap{} + case Transfer: + return DecodeTransfer(ip, code) + case Cast: + return DecodeCast(ip, code) + case Jump: + return DecodeJump(ip, code) + case JumpIfFalse: + return DecodeJumpIfFalse(ip, code) + case Return: + return InstructionReturn{} + case ReturnValue: + return InstructionReturnValue{} + case Equal: + return InstructionEqual{} + case NotEqual: + return InstructionNotEqual{} case IntAdd: return InstructionIntAdd{} case IntSubtract: @@ -1038,10 +1062,6 @@ func DecodeInstruction(ip *uint16, code []byte) Instruction { return InstructionIntDivide{} case IntMod: return InstructionIntMod{} - case Equal: - return InstructionEqual{} - case NotEqual: - return InstructionNotEqual{} case IntLess: return InstructionIntLess{} case IntLessOrEqual: @@ -1050,26 +1070,6 @@ func DecodeInstruction(ip *uint16, code []byte) Instruction { return InstructionIntGreater{} case IntGreaterOrEqual: return InstructionIntGreaterOrEqual{} - case Unwrap: - return InstructionUnwrap{} - case Cast: - return DecodeCast(ip, code) - case Destroy: - return InstructionDestroy{} - case Transfer: - return DecodeTransfer(ip, code) - case NewRef: - return DecodeNewRef(ip, code) - case Path: - return DecodePath(ip, code) - case Invoke: - return DecodeInvoke(ip, code) - case New: - return DecodeNew(ip, code) - case InvokeDynamic: - return DecodeInvokeDynamic(ip, code) - case Unknown: - return InstructionUnknown{} } panic(errors.NewUnreachableError()) diff --git a/bbq/opcode/instructions.yml b/bbq/opcode/instructions.yml index cc71667c9a..2852f79b04 100644 --- a/bbq/opcode/instructions.yml +++ b/bbq/opcode/instructions.yml @@ -1,41 +1,8 @@ -- name: "true" - description: Pushes the boolean value `true` onto the stack. - -- name: "false" - description: Pushes the boolean value `false` onto the stack. - -- name: "nil" - description: Pushes the value `nil` onto the stack. - -- name: "dup" - description: Duplicates the top value on the stack. - -- name: "drop" - description: Removes the top value from the stack. - -- name: "getConstant" - description: Pushes the constant at the given index onto the stack. - operands: - - name: "constantIndex" - type: "index" - -- name: "jump" - description: Jumps to the given instruction. - operands: - - name: "target" - type: "index" -- name: "jumpIfFalse" - description: Jumps to the given instruction, if the top value on the stack is `false`. - operands: - - name: "target" - type: "index" - -- name: "return" - description: Returns from the current function, without a value. +- name: "unknown" + description: An unknown instruction. -- name: "returnValue" - description: Returns from the current function, with the top value on the stack. +# Local instructions - name: "getLocal" description: Pushes the value of the local at the given index onto the stack. @@ -49,6 +16,8 @@ - name: "localIndex" type: "index" +# Global instructions + - name: "getGlobal" description: Pushes the value of the global at the given index onto the stack. operands: @@ -61,18 +30,49 @@ - name: "globalIndex" type: "index" +# Field instructions + - name: "getField" description: Pushes the value of the field at the given index onto the stack. - name: "setField" description: Sets the value of the field at the given index to the top value on the stack. +# Index instructions + - name: "getIndex" description: Pushes the value at the given index onto the stack. - name: "setIndex" description: Sets the value at the given index to the top value on the stack. +# Value instantiation instructions + +- name: "true" + description: Pushes the boolean value `true` onto the stack. + +- name: "false" + description: Pushes the boolean value `false` onto the stack. + +- name: "nil" + description: Pushes the value `nil` onto the stack. + +- name: "path" + description: Pushes the path with the given domain and identifier onto the stack. + operands: + - name: "domain" + type: "pathDomain" + - name: "identifier" + type: "string" + +- name: "new" + description: Creates a new instance of the given type. + operands: + - name: "kind" + type: "compositeKind" + - name: "typeIndex" + type: "index" + - name: "newArray" description: Creates a new array with the given type and size. operands: @@ -83,42 +83,63 @@ - name: "isResource" type: "bool" -- name: "intAdd" - description: Adds the top two values on the stack. +- name: "newRef" + description: Creates a new reference with the given type. + operands: + - name: "typeIndex" + type: "index" -- name: "intSubtract" - description: Subtracts the top two values on the stack. +- name: "getConstant" + description: Pushes the constant at the given index onto the stack. + operands: + - name: "constantIndex" + type: "index" -- name: "intMultiply" - description: Multiplies the top two values on the stack. +# Invocation instructions -- name: "intDivide" - description: Divides the top two values on the stack. +- name: "invoke" + description: Invokes the function with the given type arguments. + operands: + - name: "typeArgs" + type: "indices" -- name: "intMod" - description: Calculates the modulo of the top two values on the stack. -- name: "equal" - description: Compares the top two values on the stack for equality. +- name: "invokeDynamic" + description: Invokes the dynamic function with the given name, type arguments, and argument count. + operands: + - name: "name" + type: "string" + - name: "typeArgs" + type: "indices" + - name: "argCount" + type: "size" -- name: "notEqual" - description: Compares the top two values on the stack for inequality. +# Value stack instructions -- name: "intLess" - description: Compares the top two values on the stack for less than. +- name: "dup" + description: Duplicates the top value on the stack. -- name: "intLessOrEqual" - description: Compares the top two values on the stack for less than or equal. +- name: "drop" + description: Removes the top value from the stack. -- name: "intGreater" - description: Compares the top two values on the stack for greater than. +# Resource stack instructions -- name: "intGreaterOrEqual" - description: Compares the top two values on the stack for greater than or equal. +- name: "destroy" + description: Destroys the top value on the stack. + +# Optional instructions - name: "unwrap" description: Unwraps the top value on the stack. +# Conversion instructions + +- name: "transfer" + description: Transfers the top value on the stack. + operands: + - name: "typeIndex" + type: "index" + - name: "cast" description: Casts the top value on the stack to the given type. operands: @@ -127,52 +148,61 @@ - name: "kind" type: "castKind" -- name: "destroy" - description: Destroys the top value on the stack. +# Control flow instructions -- name: "transfer" - description: Transfers the top value on the stack. +- name: "jump" + description: Jumps to the given instruction. operands: - - name: "typeIndex" + - name: "target" type: "index" -- name: "newRef" - description: Creates a new reference with the given type. +- name: "jumpIfFalse" + description: Jumps to the given instruction, if the top value on the stack is `false`. operands: - - name: "typeIndex" + - name: "target" type: "index" -- name: "path" - description: Pushes the path with the given domain and identifier onto the stack. - operands: - - name: "domain" - type: "pathDomain" - - name: "identifier" - type: "string" +- name: "return" + description: Returns from the current function, without a value. -- name: "invoke" - description: Invokes the function with the given type arguments. - operands: - - name: "typeArgs" - type: "indices" +- name: "returnValue" + description: Returns from the current function, with the top value on the stack. -- name: "new" - description: Creates a new instance of the given type. - operands: - - name: "kind" - type: "compositeKind" - - name: "typeIndex" - type: "index" +# Comparison instructions -- name: "invokeDynamic" - description: Invokes the dynamic function with the given name, type arguments, and argument count. - operands: - - name: "name" - type: "string" - - name: "typeArgs" - type: "indices" - - name: "argCount" - type: "size" +- name: "equal" + description: Compares the top two values on the stack for equality. -- name: "unknown" - description: An unknown instruction. +- name: "notEqual" + description: Compares the top two values on the stack for inequality. + +# Integer arithmetic instructions + +- name: "intAdd" + description: Adds the top two values on the stack. + +- name: "intSubtract" + description: Subtracts the top two values on the stack. + +- name: "intMultiply" + description: Multiplies the top two values on the stack. + +- name: "intDivide" + description: Divides the top two values on the stack. + +- name: "intMod" + description: Calculates the modulo of the top two values on the stack. + +# Integer comparison instructions + +- name: "intLess" + description: Compares the top two values on the stack for less than. + +- name: "intLessOrEqual" + description: Compares the top two values on the stack for less than or equal. + +- name: "intGreater" + description: Compares the top two values on the stack for greater than. + +- name: "intGreaterOrEqual" + description: Compares the top two values on the stack for greater than or equal. From 03cce846a2cbb437ed5deeee978f0d8002a5c728 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Mon, 16 Dec 2024 12:46:40 -0800 Subject: [PATCH 86/89] Make field name a part of the get/set field instruction --- bbq/compiler/compiler.go | 14 +++++++++----- bbq/opcode/instructions.go | 28 ++++++++++++++++++++++++---- bbq/opcode/instructions.yml | 6 ++++++ bbq/vm/vm.go | 28 +++++++++++++++------------- 4 files changed, 54 insertions(+), 22 deletions(-) diff --git a/bbq/compiler/compiler.go b/bbq/compiler/compiler.go index 41e35f1a2d..b052abec13 100644 --- a/bbq/compiler/compiler.go +++ b/bbq/compiler/compiler.go @@ -244,10 +244,14 @@ func (c *Compiler[_]) addConstant(kind constantkind.ConstantKind, data []byte) * } func (c *Compiler[_]) stringConstLoad(str string) { - constant := c.addConstant(constantkind.String, []byte(str)) + constant := c.addStringConst(str) c.codeGen.Emit(opcode.InstructionGetConstant{ConstantIndex: constant.index}) } +func (c *Compiler[_]) addStringConst(str string) *constant { + return c.addConstant(constantkind.String, []byte(str)) +} + func (c *Compiler[_]) emitJump(target int) int { if target >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid jump")) @@ -648,8 +652,8 @@ func (c *Compiler[_]) VisitAssignmentStatement(statement *ast.AssignmentStatemen case *ast.MemberExpression: c.compileExpression(target.Expression) - c.stringConstLoad(target.Identifier.Identifier) - c.codeGen.Emit(opcode.InstructionSetField{}) + constant := c.addStringConst(target.Identifier.Identifier) + c.codeGen.Emit(opcode.InstructionSetField{FieldNameIndex: constant.index}) case *ast.IndexExpression: c.compileExpression(target.TargetExpression) @@ -914,8 +918,8 @@ func (c *Compiler[_]) loadTypeArguments(expression *ast.InvocationExpression) [] func (c *Compiler[_]) VisitMemberExpression(expression *ast.MemberExpression) (_ struct{}) { c.compileExpression(expression.Expression) - c.stringConstLoad(expression.Identifier.Identifier) - c.codeGen.Emit(opcode.InstructionGetField{}) + constant := c.addStringConst(expression.Identifier.Identifier) + c.codeGen.Emit(opcode.InstructionGetField{FieldNameIndex: constant.index}) return } diff --git a/bbq/opcode/instructions.go b/bbq/opcode/instructions.go index 2797c8943c..98939eec12 100644 --- a/bbq/opcode/instructions.go +++ b/bbq/opcode/instructions.go @@ -154,6 +154,7 @@ func DecodeSetGlobal(ip *uint16, code []byte) (i InstructionSetGlobal) { // // Pushes the value of the field at the given index onto the stack. type InstructionGetField struct { + FieldNameIndex uint16 } var _ Instruction = InstructionGetField{} @@ -163,17 +164,27 @@ func (InstructionGetField) Opcode() Opcode { } func (i InstructionGetField) String() string { - return i.Opcode().String() + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " fieldNameIndex:%s", i.FieldNameIndex) + return sb.String() } func (i InstructionGetField) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) + emitUint16(code, i.FieldNameIndex) +} + +func DecodeGetField(ip *uint16, code []byte) (i InstructionGetField) { + i.FieldNameIndex = decodeUint16(ip, code) + return i } // InstructionSetField // // Sets the value of the field at the given index to the top value on the stack. type InstructionSetField struct { + FieldNameIndex uint16 } var _ Instruction = InstructionSetField{} @@ -183,11 +194,20 @@ func (InstructionSetField) Opcode() Opcode { } func (i InstructionSetField) String() string { - return i.Opcode().String() + var sb strings.Builder + sb.WriteString(i.Opcode().String()) + fmt.Fprintf(&sb, " fieldNameIndex:%s", i.FieldNameIndex) + return sb.String() } func (i InstructionSetField) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) + emitUint16(code, i.FieldNameIndex) +} + +func DecodeSetField(ip *uint16, code []byte) (i InstructionSetField) { + i.FieldNameIndex = decodeUint16(ip, code) + return i } // InstructionGetIndex @@ -1001,9 +1021,9 @@ func DecodeInstruction(ip *uint16, code []byte) Instruction { case SetGlobal: return DecodeSetGlobal(ip, code) case GetField: - return InstructionGetField{} + return DecodeGetField(ip, code) case SetField: - return InstructionSetField{} + return DecodeSetField(ip, code) case GetIndex: return InstructionGetIndex{} case SetIndex: diff --git a/bbq/opcode/instructions.yml b/bbq/opcode/instructions.yml index 2852f79b04..8afbd4cc7f 100644 --- a/bbq/opcode/instructions.yml +++ b/bbq/opcode/instructions.yml @@ -34,9 +34,15 @@ - name: "getField" description: Pushes the value of the field at the given index onto the stack. + operands: + - name: "fieldNameIndex" + type: "index" - name: "setField" description: Sets the value of the field at the given index to the top value on the stack. + operands: + - name: "fieldNameIndex" + type: "index" # Index instructions diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index 4ef2b46e86..c96421a8ca 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -494,29 +494,31 @@ func opNew(vm *VM, ins opcode.InstructionNew) { vm.push(value) } -func opSetField(vm *VM) { - fieldName := vm.pop().(StringValue) - fieldNameStr := string(fieldName.Str) - +func opSetField(vm *VM, ins opcode.InstructionSetField) { // TODO: support all container types structValue := vm.pop().(MemberAccessibleValue) fieldValue := vm.pop() - structValue.SetMember(vm.config, fieldNameStr, fieldValue) -} + // VM assumes the field name is always a string. + constant := vm.callFrame.executable.Program.Constants[ins.FieldNameIndex] + fieldName := string(constant.Data) -func opGetField(vm *VM) { - fieldName := vm.pop().(StringValue) - fieldNameStr := string(fieldName.Str) + structValue.SetMember(vm.config, fieldName, fieldValue) +} +func opGetField(vm *VM, ins opcode.InstructionGetField) { memberAccessibleValue := vm.pop().(MemberAccessibleValue) - fieldValue := memberAccessibleValue.GetMember(vm.config, fieldNameStr) + // VM assumes the field name is always a string. + constant := vm.callFrame.executable.Program.Constants[ins.FieldNameIndex] + fieldName := string(constant.Data) + + fieldValue := memberAccessibleValue.GetMember(vm.config, fieldName) if fieldValue == nil { panic(MissingMemberValueError{ Parent: memberAccessibleValue, - Name: fieldNameStr, + Name: fieldName, }) } @@ -684,9 +686,9 @@ func (vm *VM) run() { case opcode.InstructionNewRef: opNewRef(vm, ins) case opcode.InstructionSetField: - opSetField(vm) + opSetField(vm, ins) case opcode.InstructionGetField: - opGetField(vm) + opGetField(vm, ins) case opcode.InstructionTransfer: opTransfer(vm, ins) case opcode.InstructionDestroy: From 850294899d06bedba4dfa4ff49e9dad09d42180e Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Mon, 16 Dec 2024 12:53:22 -0800 Subject: [PATCH 87/89] Move string arguments of instructions to the constant pool --- bbq/compiler/compiler.go | 14 +++++++++----- bbq/opcode/instructions.go | 22 +++++++++++----------- bbq/opcode/instructions.yml | 8 ++++---- bbq/vm/vm.go | 18 ++++++++++++------ 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/bbq/compiler/compiler.go b/bbq/compiler/compiler.go index b052abec13..57ad16baf0 100644 --- a/bbq/compiler/compiler.go +++ b/bbq/compiler/compiler.go @@ -833,11 +833,12 @@ func (c *Compiler[_]) VisitInvocationExpression(expression *ast.InvocationExpres panic(errors.NewDefaultUserError("invalid number of arguments")) } + funcNameConst := c.addStringConst(funcName) c.codeGen.Emit( opcode.InstructionInvokeDynamic{ - Name: funcName, - TypeArgs: typeArgs, - ArgCount: uint16(argumentCount), + NameIndex: funcNameConst.index, + TypeArgs: typeArgs, + ArgCount: uint16(argumentCount), }, ) @@ -1067,10 +1068,13 @@ func (c *Compiler[_]) VisitPathExpression(expression *ast.PathExpression) (_ str if len(identifier) >= math.MaxUint16 { panic(errors.NewDefaultUserError("invalid identifier")) } + + identifierConst := c.addStringConst(identifier) + c.codeGen.Emit( opcode.InstructionPath{ - Domain: domain, - Identifier: identifier, + Domain: domain, + IdentifierIndex: identifierConst.index, }, ) return diff --git a/bbq/opcode/instructions.go b/bbq/opcode/instructions.go index 98939eec12..c7dabc2ab1 100644 --- a/bbq/opcode/instructions.go +++ b/bbq/opcode/instructions.go @@ -314,8 +314,8 @@ func (i InstructionNil) Encode(code *[]byte) { // // Pushes the path with the given domain and identifier onto the stack. type InstructionPath struct { - Domain common.PathDomain - Identifier string + Domain common.PathDomain + IdentifierIndex uint16 } var _ Instruction = InstructionPath{} @@ -328,19 +328,19 @@ func (i InstructionPath) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) fmt.Fprintf(&sb, " domain:%s", i.Domain) - fmt.Fprintf(&sb, " identifier:%s", i.Identifier) + fmt.Fprintf(&sb, " identifierIndex:%s", i.IdentifierIndex) return sb.String() } func (i InstructionPath) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) emitPathDomain(code, i.Domain) - emitString(code, i.Identifier) + emitUint16(code, i.IdentifierIndex) } func DecodePath(ip *uint16, code []byte) (i InstructionPath) { i.Domain = decodePathDomain(ip, code) - i.Identifier = decodeString(ip, code) + i.IdentifierIndex = decodeUint16(ip, code) return i } @@ -510,9 +510,9 @@ func DecodeInvoke(ip *uint16, code []byte) (i InstructionInvoke) { // // Invokes the dynamic function with the given name, type arguments, and argument count. type InstructionInvokeDynamic struct { - Name string - TypeArgs []uint16 - ArgCount uint16 + NameIndex uint16 + TypeArgs []uint16 + ArgCount uint16 } var _ Instruction = InstructionInvokeDynamic{} @@ -524,7 +524,7 @@ func (InstructionInvokeDynamic) Opcode() Opcode { func (i InstructionInvokeDynamic) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " name:%s", i.Name) + fmt.Fprintf(&sb, " nameIndex:%s", i.NameIndex) fmt.Fprintf(&sb, " typeArgs:%s", i.TypeArgs) fmt.Fprintf(&sb, " argCount:%s", i.ArgCount) return sb.String() @@ -532,13 +532,13 @@ func (i InstructionInvokeDynamic) String() string { func (i InstructionInvokeDynamic) Encode(code *[]byte) { emitOpcode(code, i.Opcode()) - emitString(code, i.Name) + emitUint16(code, i.NameIndex) emitUint16Array(code, i.TypeArgs) emitUint16(code, i.ArgCount) } func DecodeInvokeDynamic(ip *uint16, code []byte) (i InstructionInvokeDynamic) { - i.Name = decodeString(ip, code) + i.NameIndex = decodeUint16(ip, code) i.TypeArgs = decodeUint16Array(ip, code) i.ArgCount = decodeUint16(ip, code) return i diff --git a/bbq/opcode/instructions.yml b/bbq/opcode/instructions.yml index 8afbd4cc7f..4b12cebce1 100644 --- a/bbq/opcode/instructions.yml +++ b/bbq/opcode/instructions.yml @@ -68,8 +68,8 @@ operands: - name: "domain" type: "pathDomain" - - name: "identifier" - type: "string" + - name: "identifierIndex" + type: "index" - name: "new" description: Creates a new instance of the given type. @@ -113,8 +113,8 @@ - name: "invokeDynamic" description: Invokes the dynamic function with the given name, type arguments, and argument count. operands: - - name: "name" - type: "string" + - name: "nameIndex" + type: "index" - name: "typeArgs" type: "indices" - name: "argCount" diff --git a/bbq/vm/vm.go b/bbq/vm/vm.go index c96421a8ca..39ca5ce124 100644 --- a/bbq/vm/vm.go +++ b/bbq/vm/vm.go @@ -459,7 +459,9 @@ func opInvokeDynamic(vm *VM, ins opcode.InstructionInvokeDynamic) { compositeValue := receiver.(*CompositeValue) compositeType := compositeValue.CompositeType - qualifiedFuncName := commons.TypeQualifiedName(compositeType.QualifiedIdentifier, ins.Name) + funcName := getStringConstant(vm, ins.NameIndex) + + qualifiedFuncName := commons.TypeQualifiedName(compositeType.QualifiedIdentifier, funcName) var functionValue = vm.lookupFunction(compositeType.Location, qualifiedFuncName) parameterCount := int(functionValue.Function.ParameterCount) @@ -501,8 +503,7 @@ func opSetField(vm *VM, ins opcode.InstructionSetField) { fieldValue := vm.pop() // VM assumes the field name is always a string. - constant := vm.callFrame.executable.Program.Constants[ins.FieldNameIndex] - fieldName := string(constant.Data) + fieldName := getStringConstant(vm, ins.FieldNameIndex) structValue.SetMember(vm.config, fieldName, fieldValue) } @@ -511,8 +512,7 @@ func opGetField(vm *VM, ins opcode.InstructionGetField) { memberAccessibleValue := vm.pop().(MemberAccessibleValue) // VM assumes the field name is always a string. - constant := vm.callFrame.executable.Program.Constants[ins.FieldNameIndex] - fieldName := string(constant.Data) + fieldName := getStringConstant(vm, ins.FieldNameIndex) fieldValue := memberAccessibleValue.GetMember(vm.config, fieldName) if fieldValue == nil { @@ -525,6 +525,11 @@ func opGetField(vm *VM, ins opcode.InstructionGetField) { vm.push(fieldValue) } +func getStringConstant(vm *VM, index uint16) string { + constant := vm.callFrame.executable.Program.Constants[index] + return string(constant.Data) +} + func opTransfer(vm *VM, ins opcode.InstructionTransfer) { targetType := vm.loadType(ins.TypeIndex) value := vm.peek() @@ -551,9 +556,10 @@ func opDestroy(vm *VM) { } func opPath(vm *VM, ins opcode.InstructionPath) { + identifier := getStringConstant(vm, ins.IdentifierIndex) value := PathValue{ Domain: ins.Domain, - Identifier: ins.Identifier, + Identifier: identifier, } vm.push(value) } From 4ac394b607ef25c28cb37cb6b145b276030238ce Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Mon, 16 Dec 2024 13:49:06 -0800 Subject: [PATCH 88/89] Fix instruction pretty printing --- bbq/opcode/gen/main.go | 12 +++++++--- bbq/opcode/instruction.go | 21 ++++++++++++++++ bbq/opcode/instructions.go | 49 +++++++++++++++++++------------------- 3 files changed, 54 insertions(+), 28 deletions(-) diff --git a/bbq/opcode/gen/main.go b/bbq/opcode/gen/main.go index 70782b5ca8..2a3d1f2ec4 100644 --- a/bbq/opcode/gen/main.go +++ b/bbq/opcode/gen/main.go @@ -473,13 +473,19 @@ func instructionStringFuncDecl(ins instruction) *dst.FuncDecl { ) for _, operand := range ins.Operands { + var funcName string + switch operand.Type { + case operandTypeIndices: + funcName = "printfUInt16ArrayArgument" + default: + funcName = "printfArgument" + } stmts = append( stmts, &dst.ExprStmt{ X: &dst.CallExpr{ Fun: &dst.Ident{ - Name: "Fprintf", - Path: "fmt", + Name: funcName, }, Args: []dst.Expr{ &dst.UnaryExpr{ @@ -488,7 +494,7 @@ func instructionStringFuncDecl(ins instruction) *dst.FuncDecl { }, &dst.BasicLit{ Kind: token.STRING, - Value: fmt.Sprintf(`" %s:%%s"`, operand.Name), + Value: fmt.Sprintf(`"%s"`, operand.Name), }, &dst.SelectorExpr{ X: dst.NewIdent("i"), diff --git a/bbq/opcode/instruction.go b/bbq/opcode/instruction.go index dd8f129401..9c69db2de6 100644 --- a/bbq/opcode/instruction.go +++ b/bbq/opcode/instruction.go @@ -21,6 +21,9 @@ package opcode import ( + "fmt" + "strings" + "github.com/onflow/cadence/common" ) @@ -161,3 +164,21 @@ func DecodeInstructions(code []byte) []Instruction { } return instructions } + +// Instruction pretty print + +func printfUInt16ArrayArgument(sb *strings.Builder, argName string, values []uint16) { + _, _ = fmt.Fprintf(sb, " %s:[", argName) + for i, value := range values { + if i > 0 { + _, _ = fmt.Fprint(sb, ", ") + } + _, _ = fmt.Fprintf(sb, "%d", value) + } + + sb.WriteString("]") +} + +func printfArgument(sb *strings.Builder, fieldName string, v any) { + _, _ = fmt.Fprintf(sb, " %s:%v", fieldName, v) +} diff --git a/bbq/opcode/instructions.go b/bbq/opcode/instructions.go index c7dabc2ab1..19ed6de1d5 100644 --- a/bbq/opcode/instructions.go +++ b/bbq/opcode/instructions.go @@ -3,7 +3,6 @@ package opcode import ( - "fmt" "strings" "github.com/onflow/cadence/common" @@ -46,7 +45,7 @@ func (InstructionGetLocal) Opcode() Opcode { func (i InstructionGetLocal) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " localIndex:%s", i.LocalIndex) + printfArgument(&sb, "localIndex", i.LocalIndex) return sb.String() } @@ -76,7 +75,7 @@ func (InstructionSetLocal) Opcode() Opcode { func (i InstructionSetLocal) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " localIndex:%s", i.LocalIndex) + printfArgument(&sb, "localIndex", i.LocalIndex) return sb.String() } @@ -106,7 +105,7 @@ func (InstructionGetGlobal) Opcode() Opcode { func (i InstructionGetGlobal) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " globalIndex:%s", i.GlobalIndex) + printfArgument(&sb, "globalIndex", i.GlobalIndex) return sb.String() } @@ -136,7 +135,7 @@ func (InstructionSetGlobal) Opcode() Opcode { func (i InstructionSetGlobal) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " globalIndex:%s", i.GlobalIndex) + printfArgument(&sb, "globalIndex", i.GlobalIndex) return sb.String() } @@ -166,7 +165,7 @@ func (InstructionGetField) Opcode() Opcode { func (i InstructionGetField) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " fieldNameIndex:%s", i.FieldNameIndex) + printfArgument(&sb, "fieldNameIndex", i.FieldNameIndex) return sb.String() } @@ -196,7 +195,7 @@ func (InstructionSetField) Opcode() Opcode { func (i InstructionSetField) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " fieldNameIndex:%s", i.FieldNameIndex) + printfArgument(&sb, "fieldNameIndex", i.FieldNameIndex) return sb.String() } @@ -327,8 +326,8 @@ func (InstructionPath) Opcode() Opcode { func (i InstructionPath) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " domain:%s", i.Domain) - fmt.Fprintf(&sb, " identifierIndex:%s", i.IdentifierIndex) + printfArgument(&sb, "domain", i.Domain) + printfArgument(&sb, "identifierIndex", i.IdentifierIndex) return sb.String() } @@ -361,8 +360,8 @@ func (InstructionNew) Opcode() Opcode { func (i InstructionNew) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " kind:%s", i.Kind) - fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) + printfArgument(&sb, "kind", i.Kind) + printfArgument(&sb, "typeIndex", i.TypeIndex) return sb.String() } @@ -396,9 +395,9 @@ func (InstructionNewArray) Opcode() Opcode { func (i InstructionNewArray) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) - fmt.Fprintf(&sb, " size:%s", i.Size) - fmt.Fprintf(&sb, " isResource:%s", i.IsResource) + printfArgument(&sb, "typeIndex", i.TypeIndex) + printfArgument(&sb, "size", i.Size) + printfArgument(&sb, "isResource", i.IsResource) return sb.String() } @@ -432,7 +431,7 @@ func (InstructionNewRef) Opcode() Opcode { func (i InstructionNewRef) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) + printfArgument(&sb, "typeIndex", i.TypeIndex) return sb.String() } @@ -462,7 +461,7 @@ func (InstructionGetConstant) Opcode() Opcode { func (i InstructionGetConstant) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " constantIndex:%s", i.ConstantIndex) + printfArgument(&sb, "constantIndex", i.ConstantIndex) return sb.String() } @@ -492,7 +491,7 @@ func (InstructionInvoke) Opcode() Opcode { func (i InstructionInvoke) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " typeArgs:%s", i.TypeArgs) + printfUInt16ArrayArgument(&sb, "typeArgs", i.TypeArgs) return sb.String() } @@ -524,9 +523,9 @@ func (InstructionInvokeDynamic) Opcode() Opcode { func (i InstructionInvokeDynamic) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " nameIndex:%s", i.NameIndex) - fmt.Fprintf(&sb, " typeArgs:%s", i.TypeArgs) - fmt.Fprintf(&sb, " argCount:%s", i.ArgCount) + printfArgument(&sb, "nameIndex", i.NameIndex) + printfUInt16ArrayArgument(&sb, "typeArgs", i.TypeArgs) + printfArgument(&sb, "argCount", i.ArgCount) return sb.String() } @@ -640,7 +639,7 @@ func (InstructionTransfer) Opcode() Opcode { func (i InstructionTransfer) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) + printfArgument(&sb, "typeIndex", i.TypeIndex) return sb.String() } @@ -671,8 +670,8 @@ func (InstructionCast) Opcode() Opcode { func (i InstructionCast) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " typeIndex:%s", i.TypeIndex) - fmt.Fprintf(&sb, " kind:%s", i.Kind) + printfArgument(&sb, "typeIndex", i.TypeIndex) + printfArgument(&sb, "kind", i.Kind) return sb.String() } @@ -704,7 +703,7 @@ func (InstructionJump) Opcode() Opcode { func (i InstructionJump) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " target:%s", i.Target) + printfArgument(&sb, "target", i.Target) return sb.String() } @@ -734,7 +733,7 @@ func (InstructionJumpIfFalse) Opcode() Opcode { func (i InstructionJumpIfFalse) String() string { var sb strings.Builder sb.WriteString(i.Opcode().String()) - fmt.Fprintf(&sb, " target:%s", i.Target) + printfArgument(&sb, "target", i.Target) return sb.String() } From a47483608739de1aded0b1e4215d258b0174a80b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 16 Dec 2024 14:57:24 -0800 Subject: [PATCH 89/89] document effects on value stack and control stack --- bbq/opcode/gen/instructions.schema.json | 100 ++++++++- bbq/opcode/instructions.yml | 256 +++++++++++++++++++++++- 2 files changed, 349 insertions(+), 7 deletions(-) diff --git a/bbq/opcode/gen/instructions.schema.json b/bbq/opcode/gen/instructions.schema.json index e0f6bda38b..62c8bd9683 100644 --- a/bbq/opcode/gen/instructions.schema.json +++ b/bbq/opcode/gen/instructions.schema.json @@ -5,12 +5,14 @@ "instruction": { "type": "object", "properties": { - "name": { "type": "string" }, - "description": { "type": "string" }, - "operands": { - "type": "array", - "items": { "$ref": "#/$defs/operand" } - } + "name": { "type": "string" }, + "description": { "type": "string" }, + "operands": { + "type": "array", + "items": { "$ref": "#/$defs/operand" } + }, + "valueEffects": {"$ref": "#/$defs/valueEffects"}, + "controlEffects": {"$ref": "#/$defs/controlEffects"} }, "required": ["name", "description"], "additionalProperties": false @@ -36,6 +38,92 @@ }, "required": ["name", "type"], "additionalProperties": false + }, + "valueEffects": { + "type": "object", + "properties": { + "push": { + "type": "array", + "items": { "$ref": "#/$defs/valueStackOp" } + }, + "pop": { + "type": "array", + "items": { + "$ref": "#/$defs/valueStackOp" + } + } + }, + "additionalProperties": false + }, + "valueStackOp": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "type": { + "type": "string", + "enum": [ + "value", + "string", + "array", + "integer", + "bool", + "path", + "int", + "reference", + "function", + "resource", + "optional" + ] + }, + "count": { + "oneOf": [ + { "type": "integer" }, + { "type": "string" } + ] + } + }, + "required": ["name", "type"], + "additionalProperties": false + }, + "controlEffects": { + "type": "array", + "items": { "$ref": "#/$defs/controlEffect" } + }, + "controlEffect": { + "oneOf": [ + { "$ref": "#/$defs/jump" }, + { "$ref": "#/$defs/call" }, + { "$ref": "#/$defs/return" } + ] + }, + "jump": { + "type": "object", + "properties": { + "jump": { "type": "string" } + }, + "required": ["jump"], + "additionalProperties": false + }, + "call": { + "type": "object", + "properties": { + "call": { "type": "null" } + }, + "required": ["call"], + "additionalProperties": false + }, + "return": { + "type": "object", + "properties": { + "return": { + "oneOf": [ + {"type": "string"}, + {"type": "null"} + ] + } + }, + "required": ["return"], + "additionalProperties": false } } } \ No newline at end of file diff --git a/bbq/opcode/instructions.yml b/bbq/opcode/instructions.yml index 4b12cebce1..b9f466bfb3 100644 --- a/bbq/opcode/instructions.yml +++ b/bbq/opcode/instructions.yml @@ -9,12 +9,20 @@ operands: - name: "localIndex" type: "index" + valueEffects: + push: + - name: "value" + type: "value" - name: "setLocal" description: Sets the value of the local at the given index to the top value on the stack. operands: - name: "localIndex" type: "index" + valueEffects: + pop: + - name: "value" + type: "value" # Global instructions @@ -23,12 +31,20 @@ operands: - name: "globalIndex" type: "index" + valueEffects: + push: + - name: "value" + type: "value" - name: "setGlobal" description: Sets the value of the global at the given index to the top value on the stack. operands: - name: "globalIndex" type: "index" + valueEffects: + pop: + - name: "value" + type: "value" # Field instructions @@ -37,31 +53,73 @@ operands: - name: "fieldNameIndex" type: "index" + valueEffects: + pop: + - name: "container" + type: "value" + push: + - name: "value" + type: "value" - name: "setField" description: Sets the value of the field at the given index to the top value on the stack. operands: - name: "fieldNameIndex" type: "index" + valueEffects: + pop: + - name: "container" + type: "value" + - name: "value" + type: "value" # Index instructions - name: "getIndex" description: Pushes the value at the given index onto the stack. + valueEffects: + pop: + - name: "array" + type: "array" + - name: "index" + type: "integer" + push: + - name: "value" + type: "value" - name: "setIndex" description: Sets the value at the given index to the top value on the stack. + valueEffects: + pop: + - name: "array" + type: "array" + - name: "index" + type: "integer" + - name: "value" + type: "value" # Value instantiation instructions - name: "true" description: Pushes the boolean value `true` onto the stack. + valueEffects: + push: + - name: "value" + type: "bool" - name: "false" description: Pushes the boolean value `false` onto the stack. + valueEffects: + push: + - name: "value" + type: "bool" - name: "nil" description: Pushes the value `nil` onto the stack. + valueEffects: + push: + - name: "value" + type: "value" - name: "path" description: Pushes the path with the given domain and identifier onto the stack. @@ -70,6 +128,10 @@ type: "pathDomain" - name: "identifierIndex" type: "index" + valueEffects: + push: + - name: "value" + type: "path" - name: "new" description: Creates a new instance of the given type. @@ -78,6 +140,10 @@ type: "compositeKind" - name: "typeIndex" type: "index" + valueEffects: + push: + - name: "value" + type: "value" - name: "newArray" description: Creates a new array with the given type and size. @@ -88,18 +154,38 @@ type: "size" - name: "isResource" type: "bool" + valueEffects: + pop: + - name: "elements" + type: "value" + # The number of elements taken from the stack is equal to the size operand of the opcode. + count: "size" + push: + - name: "array" + type: "array" - name: "newRef" description: Creates a new reference with the given type. operands: - name: "typeIndex" type: "index" + valueEffects: + pop: + - name: "value" + type: "value" + push: + - name: "reference" + type: "reference" - name: "getConstant" description: Pushes the constant at the given index onto the stack. operands: - name: "constantIndex" type: "index" + valueEffects: + push: + - name: "value" + type: "value" # Invocation instructions @@ -108,7 +194,17 @@ operands: - name: "typeArgs" type: "indices" - + valueEffects: + pop: + - name: "arguments" + # TODO: count + - name: "function" + type: "function" + push: + - name: "result" + type: "value" + controlEffects: + - call: - name: "invokeDynamic" description: Invokes the dynamic function with the given name, type arguments, and argument count. @@ -119,24 +215,57 @@ type: "indices" - name: "argCount" type: "size" + valueEffects: + pop: + - name: "arguments" + # TODO: count + push: + - name: "result" + type: "value" + controlEffects: + - call: # Value stack instructions - name: "dup" description: Duplicates the top value on the stack. + valueEffects: + pop: + - name: "value" + type: "value" + push: + - name: "original" + type: "value" + - name: "duplicate" + type: "value" - name: "drop" description: Removes the top value from the stack. + valueEffects: + pop: + - name: "value" + type: "value" # Resource stack instructions - name: "destroy" description: Destroys the top value on the stack. + valueEffects: + pop: + - name: "resource" + type: "resource" # Optional instructions - name: "unwrap" description: Unwraps the top value on the stack. + valueEffects: + pop: + - name: "optional" + type: "optional" + push: + - name: "value" + type: "value" # Conversion instructions @@ -145,6 +274,13 @@ operands: - name: "typeIndex" type: "index" + valueEffects: + pop: + - name: "value" + type: "value" + push: + - name: "value" + type: "value" - name: "cast" description: Casts the top value on the stack to the given type. @@ -153,6 +289,13 @@ type: "index" - name: "kind" type: "castKind" + valueEffects: + pop: + - name: "value" + type: "value" + push: + - name: "value" + type: "value" # Control flow instructions @@ -161,54 +304,165 @@ operands: - name: "target" type: "index" + controlEffects: + - jump: "target" - name: "jumpIfFalse" description: Jumps to the given instruction, if the top value on the stack is `false`. operands: - name: "target" type: "index" + controlEffects: + - jump: "target" - name: "return" description: Returns from the current function, without a value. + controlEffects: + - return: - name: "returnValue" description: Returns from the current function, with the top value on the stack. + valueEffects: + pop: + - name: "value" + type: "value" + controlEffects: + - return: "value" # Comparison instructions - name: "equal" description: Compares the top two values on the stack for equality. + valueEffects: + pop: + - name: "left" + type: "value" + - name: "right" + type: "value" + push: + - name: "result" + type: "bool" - name: "notEqual" description: Compares the top two values on the stack for inequality. + valueEffects: + pop: + - name: "left" + type: "value" + - name: "right" + type: "value" + push: + - name: "result" + type: "bool" # Integer arithmetic instructions - name: "intAdd" description: Adds the top two values on the stack. + valueEffects: + pop: + - name: "left" + type: "int" + - name: "right" + type: "int" + push: + - name: "result" + type: "int" - name: "intSubtract" description: Subtracts the top two values on the stack. + valueEffects: + pop: + - name: "left" + type: "int" + - name: "right" + type: "int" + push: + - name: "result" + type: "int" - name: "intMultiply" description: Multiplies the top two values on the stack. + valueEffects: + pop: + - name: "left" + type: "int" + - name: "right" + type: "int" + push: + - name: "result" + type: "int" - name: "intDivide" description: Divides the top two values on the stack. + valueEffects: + pop: + - name: "left" + type: "int" + - name: "right" + type: "int" + push: + - name: "result" + type: "int" - name: "intMod" description: Calculates the modulo of the top two values on the stack. + valueEffects: + pop: + - name: "left" + type: "int" + - name: "right" + type: "int" + push: + - name: "result" + type: "int" # Integer comparison instructions - name: "intLess" description: Compares the top two values on the stack for less than. + valueEffects: + pop: + - name: "left" + type: "int" + - name: "right" + type: "int" + push: + - name: "result" + type: "bool" - name: "intLessOrEqual" description: Compares the top two values on the stack for less than or equal. + valueEffects: + pop: + - name: "left" + type: "int" + - name: "right" + type: "int" + push: + - name: "result" + type: "bool" - name: "intGreater" description: Compares the top two values on the stack for greater than. + valueEffects: + pop: + - name: "left" + type: "int" + - name: "right" + type: "int" + push: + - name: "result" + type: "bool" - name: "intGreaterOrEqual" description: Compares the top two values on the stack for greater than or equal. + valueEffects: + pop: + - name: "left" + type: "int" + - name: "right" + type: "int" + push: + - name: "result" + type: "bool"