diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index 4efbcc44c..c90a49c0f 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -288,7 +288,8 @@ func isSyscall(fun *funcScope) bool { if fun.selector == nil || fun.pkg == nil || !isInteropPath(fun.pkg.Path()) { return false } - return fun.pkg.Name() == "neogointernal" && strings.HasPrefix(fun.name, "Syscall") + return fun.pkg.Name() == "neogointernal" && (strings.HasPrefix(fun.name, "Syscall") || + strings.HasPrefix(fun.name, "Opcode")) } const interopPrefix = "github.com/nspcc-dev/neo-go/pkg/interop" diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 062dc185f..1baf52704 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -939,7 +939,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { c.emittedEvents[name] = append(c.emittedEvents[name], params) } } - c.convertSyscall(n) + c.convertSyscall(f, n) default: emit.Call(c.prog.BinWriter, opcode.CALLL, f.label) } @@ -1530,18 +1530,27 @@ func (c *codegen) getByteArray(expr ast.Expr) []byte { } } -func (c *codegen) convertSyscall(expr *ast.CallExpr) { +func (c *codegen) convertSyscall(f *funcScope, expr *ast.CallExpr) { for _, arg := range expr.Args[1:] { ast.Walk(c, arg) } - c.emitReverse(len(expr.Args) - 1) tv := c.typeAndValueOf(expr.Args[0]) - syscall := constant.StringVal(tv.Value) - emit.Syscall(c.prog.BinWriter, syscall) + name := constant.StringVal(tv.Value) + if strings.HasPrefix(f.name, "Syscall") { + c.emitReverse(len(expr.Args) - 1) + emit.Syscall(c.prog.BinWriter, name) - // This NOP instruction is basically not needed, but if we do, we have a - // one to one matching avm file with neo-python which is very nice for debugging. - emit.Opcodes(c.prog.BinWriter, opcode.NOP) + // This NOP instruction is basically not needed, but if we do, we have a + // one to one matching avm file with neo-python which is very nice for debugging. + emit.Opcodes(c.prog.BinWriter, opcode.NOP) + } else { + op, err := opcode.FromString(name) + if err != nil { + c.prog.Err = fmt.Errorf("invalid opcode: %s", op) + return + } + emit.Opcodes(c.prog.BinWriter, op) + } } // emitSliceHelper emits 3 items on stack: slice, its first index, and its size. diff --git a/pkg/compiler/syscall_test.go b/pkg/compiler/syscall_test.go index f5dbd8e35..b6d735f0a 100644 --- a/pkg/compiler/syscall_test.go +++ b/pkg/compiler/syscall_test.go @@ -92,3 +92,29 @@ func TestSyscallInGlobalInit(t *testing.T) { require.NoError(t, v.Run()) require.Equal(t, []byte{1, 2}, v.Estack().Pop().Value()) } + +func TestOpcode(t *testing.T) { + t.Run("1 argument", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" + func abs(a int) int { + return neogointernal.Opcode1("ABS", a).(int) + } + func Main() int { + return abs(-42) + }` + eval(t, src, big.NewInt(42)) + }) + t.Run("2 arguments", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" + func add3(a, b, c int) int { + return neogointernal.Opcode2("SUB", a, + neogointernal.Opcode2("SUB", b, c).(int)).(int) + } + func Main() int { + return add3(53, 12, 1) + }` + eval(t, src, big.NewInt(42)) + }) +} diff --git a/pkg/interop/neogointernal/opcode.go b/pkg/interop/neogointernal/opcode.go new file mode 100644 index 000000000..3a7cb00a4 --- /dev/null +++ b/pkg/interop/neogointernal/opcode.go @@ -0,0 +1,11 @@ +package neogointernal + +// Opcode1 emits opcode with 1 argument. +func Opcode1(op string, arg interface{}) interface{} { + return nil +} + +// Opcode2 emits opcode with 2 arguments. +func Opcode2(op string, arg1, arg2 interface{}) interface{} { + return nil +}