mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-22 19:29:39 +00:00
VM and compiler update (#63)
* renamed test folders and fixed bug where wrong jump labels would be exectuted for rewrite. * Added support for Osize (len(string)) and factored out the array tests * Added current instruction number to VM prompt if program is loaded. * added support for unary expressions. * updated README of and sorted the help commands * updated readme of the compiler * bumped version -> 0.39.0
This commit is contained in:
parent
83e467e527
commit
941bd7e728
19 changed files with 475 additions and 292 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
0.38.1
|
0.39.0
|
||||||
|
|
|
@ -52,27 +52,29 @@ More information about standalone installation coming soon.
|
||||||
NEO-GO-VM > help
|
NEO-GO-VM > help
|
||||||
|
|
||||||
COMMAND USAGE
|
COMMAND USAGE
|
||||||
step step (n) instruction in the program (> step 10)
|
|
||||||
ops show the opcodes of the current loaded program
|
|
||||||
ip show the current instruction
|
|
||||||
estack show evaluation stack details
|
|
||||||
astack show alt stack details
|
astack show alt stack details
|
||||||
istack show invocation stack details
|
|
||||||
run execute the current loaded script
|
|
||||||
cont continue execution of the current loaded script
|
|
||||||
help show available commands
|
|
||||||
exit exit the VM prompt
|
|
||||||
break place a breakpoint (> break 1)
|
break place a breakpoint (> break 1)
|
||||||
load load a script into the VM (> load /path/to/script.avm)
|
cont continue execution of the current loaded script
|
||||||
|
estack show evaluation stack details
|
||||||
|
exit exit the VM prompt
|
||||||
|
help show available commands
|
||||||
|
ip show the current instruction
|
||||||
|
istack show invocation stack details
|
||||||
|
loadavm load an avm script into the VM (> load /path/to/script.avm)
|
||||||
|
loadgo compile and load a .go file into the VM (> load /path/to/file.go)
|
||||||
|
loadhex load a hex string into the VM (> loadhex 006166 )
|
||||||
|
ops show the opcodes of the current loaded program
|
||||||
|
run execute the current loaded script
|
||||||
|
step step (n) instruction in the program (> step 10)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Loading in your script
|
### Loading in your script
|
||||||
|
|
||||||
To load a script into the VM:
|
To load an avm script into the VM:
|
||||||
|
|
||||||
```
|
```
|
||||||
NEO-GO-VM > load ../contract.avm
|
NEO-GO-VM > loadavm ../contract.avm
|
||||||
READY
|
READY: loaded 36 instructions
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the script:
|
Run the script:
|
||||||
|
@ -87,6 +89,28 @@ NEO-GO-VM > run
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also directly compile and load `.go` files:
|
||||||
|
|
||||||
|
```
|
||||||
|
NEO-GO-VM > loadgo ../contract.go
|
||||||
|
READY: loaded 36 instructions
|
||||||
|
```
|
||||||
|
|
||||||
|
To make it even more complete, you can directly load hex strings into the VM:
|
||||||
|
|
||||||
|
```
|
||||||
|
NEO-GO-VM > loadhex 54c56b006c766b00527ac46c766b00c391640b006203005a616c756662030000616c7566
|
||||||
|
READY: loaded 36 instructions
|
||||||
|
NEO-GO-VM > run
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"value": 10,
|
||||||
|
"type": "BigInteger"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
### Debugging
|
### Debugging
|
||||||
The `neo-go-vm` provides a debugger to inspect your program in-depth.
|
The `neo-go-vm` provides a debugger to inspect your program in-depth.
|
||||||
|
|
||||||
|
@ -95,6 +119,7 @@ Step 4 instructions.
|
||||||
```
|
```
|
||||||
NEO-GO-VM > step 4
|
NEO-GO-VM > step 4
|
||||||
at breakpoint 4 (Opush4)
|
at breakpoint 4 (Opush4)
|
||||||
|
NEO-GO-VM 4 >
|
||||||
```
|
```
|
||||||
|
|
||||||
Using just `step` will execute 1 instruction at a time.
|
Using just `step` will execute 1 instruction at a time.
|
||||||
|
@ -102,6 +127,7 @@ Using just `step` will execute 1 instruction at a time.
|
||||||
```
|
```
|
||||||
NEO-GO-VM > step
|
NEO-GO-VM > step
|
||||||
instruction pointer at 5 (Odup)
|
instruction pointer at 5 (Odup)
|
||||||
|
NEO-GO-VM 5 >
|
||||||
```
|
```
|
||||||
|
|
||||||
To place breakpoints:
|
To place breakpoints:
|
||||||
|
@ -109,14 +135,15 @@ To place breakpoints:
|
||||||
```
|
```
|
||||||
NEO-GO-VM > break 10
|
NEO-GO-VM > break 10
|
||||||
breakpoint added at instruction 10
|
breakpoint added at instruction 10
|
||||||
NEO-GO-VM > resume
|
NEO-GO-VM > cont
|
||||||
at breakpoint 10 (Osetitem)
|
at breakpoint 10 (Osetitem)
|
||||||
|
NEO-GO-VM 10 > cont
|
||||||
```
|
```
|
||||||
|
|
||||||
Inspecting the stack:
|
Inspecting the evaluation stack:
|
||||||
|
|
||||||
```
|
```
|
||||||
NEO-GO-VM > stack
|
NEO-GO-VM > estack
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"value": [
|
"value": [
|
||||||
|
@ -137,4 +164,7 @@ NEO-GO-VM > stack
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
And a lot more features coming next weeks..
|
There are more stacks that you can inspect.
|
||||||
|
- `astack` alt stack
|
||||||
|
- `istack` invocation stack
|
||||||
|
|
||||||
|
|
|
@ -2,40 +2,45 @@ package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/vm/compiler"
|
||||||
)
|
)
|
||||||
|
|
||||||
// command describes a VM command.
|
// command describes a VM command.
|
||||||
type command struct {
|
type command struct {
|
||||||
// number of minimun arguments the command needs.
|
// number of minimun arguments the command needs.
|
||||||
args int
|
args int
|
||||||
|
|
||||||
// description of the command.
|
// description of the command.
|
||||||
usage string
|
usage string
|
||||||
|
|
||||||
// whether the VM needs to be "ready" to execute this command.
|
// whether the VM needs to be "ready" to execute this command.
|
||||||
ready bool
|
ready bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var commands = map[string]command{
|
var commands = map[string]command{
|
||||||
"help": {0, "show available commands", false},
|
"help": {0, "show available commands", false},
|
||||||
"exit": {0, "exit the VM prompt", false},
|
"exit": {0, "exit the VM prompt", false},
|
||||||
"ip": {0, "show the current instruction", true},
|
"ip": {0, "show the current instruction", true},
|
||||||
"break": {1, "place a breakpoint (> break 1)", true},
|
"break": {1, "place a breakpoint (> break 1)", true},
|
||||||
"estack": {0, "show evaluation stack details", false},
|
"estack": {0, "show evaluation stack details", false},
|
||||||
"astack": {0, "show alt stack details", false},
|
"astack": {0, "show alt stack details", false},
|
||||||
"istack": {0, "show invocation stack details", false},
|
"istack": {0, "show invocation stack details", false},
|
||||||
"load": {1, "load a script into the VM (> load /path/to/script.avm)", false},
|
"loadavm": {1, "load an avm script into the VM (> load /path/to/script.avm)", false},
|
||||||
"run": {0, "execute the current loaded script", true},
|
"loadhex": {1, "load a hex string into the VM (> loadhex 006166 )", false},
|
||||||
"cont": {0, "continue execution of the current loaded script", true},
|
"loadgo": {1, "compile and load a .go file into the VM (> load /path/to/file.go)", false},
|
||||||
"step": {0, "step (n) instruction in the program (> step 10)", true},
|
"run": {0, "execute the current loaded script", true},
|
||||||
"ops": {0, "show the opcodes of the current loaded program", true},
|
"cont": {0, "continue execution of the current loaded script", true},
|
||||||
|
"step": {0, "step (n) instruction in the program (> step 10)", true},
|
||||||
|
"ops": {0, "show the opcodes of the current loaded program", true},
|
||||||
}
|
}
|
||||||
|
|
||||||
// VMCLI object for interacting with the VM.
|
// VMCLI object for interacting with the VM.
|
||||||
|
@ -67,14 +72,7 @@ func (c *VMCLI) handleCommand(cmd string, args ...string) {
|
||||||
|
|
||||||
switch cmd {
|
switch cmd {
|
||||||
case "help":
|
case "help":
|
||||||
fmt.Println()
|
printHelp()
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
|
|
||||||
fmt.Fprintln(w, "COMMAND\tUSAGE")
|
|
||||||
for name, details := range commands {
|
|
||||||
fmt.Fprintf(w, "%s\t%s\n", name, details.usage)
|
|
||||||
}
|
|
||||||
w.Flush()
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
case "exit":
|
case "exit":
|
||||||
fmt.Println("Bye!")
|
fmt.Println("Bye!")
|
||||||
|
@ -97,13 +95,36 @@ func (c *VMCLI) handleCommand(cmd string, args ...string) {
|
||||||
case "estack", "istack", "astack":
|
case "estack", "istack", "astack":
|
||||||
fmt.Println(c.vm.Stack(cmd))
|
fmt.Println(c.vm.Stack(cmd))
|
||||||
|
|
||||||
case "load":
|
case "loadavm":
|
||||||
if err := c.vm.LoadFile(args[0]); err != nil {
|
if err := c.vm.LoadFile(args[0]); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("READY: loaded %d instructions\n", c.vm.Context().LenInstr())
|
fmt.Printf("READY: loaded %d instructions\n", c.vm.Context().LenInstr())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "loadhex":
|
||||||
|
b, err := hex.DecodeString(args[0])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.vm.Load(b)
|
||||||
|
fmt.Printf("READY: loaded %d instructions\n", c.vm.Context().LenInstr())
|
||||||
|
|
||||||
|
case "loadgo":
|
||||||
|
fb, err := ioutil.ReadFile(args[0])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b, err := compiler.Compile(bytes.NewReader(fb), &compiler.Options{})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.vm.Load(b)
|
||||||
|
fmt.Printf("READY: loaded %d instructions\n", c.vm.Context().LenInstr())
|
||||||
|
|
||||||
case "run", "cont":
|
case "run", "cont":
|
||||||
c.vm.Run()
|
c.vm.Run()
|
||||||
|
|
||||||
|
@ -132,7 +153,11 @@ func (c *VMCLI) Run() error {
|
||||||
printLogo()
|
printLogo()
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
for {
|
for {
|
||||||
fmt.Print("NEO-GO-VM > ")
|
if c.vm.Ready() && c.vm.Context().IP()-1 >= 0 {
|
||||||
|
fmt.Printf("NEO-GO-VM %d > ", c.vm.Context().IP()-1)
|
||||||
|
} else {
|
||||||
|
fmt.Print("NEO-GO-VM > ")
|
||||||
|
}
|
||||||
input, _ := reader.ReadString('\n')
|
input, _ := reader.ReadString('\n')
|
||||||
input = strings.Trim(input, "\n")
|
input = strings.Trim(input, "\n")
|
||||||
if len(input) != 0 {
|
if len(input) != 0 {
|
||||||
|
@ -147,6 +172,25 @@ func (c *VMCLI) Run() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printHelp() {
|
||||||
|
names := make([]string, len(commands))
|
||||||
|
i := 0
|
||||||
|
for name, _ := range commands {
|
||||||
|
names[i] = name
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
|
||||||
|
fmt.Fprintln(w, "COMMAND\tUSAGE")
|
||||||
|
for _, name := range names {
|
||||||
|
fmt.Fprintf(w, "%s\t%s\n", name, commands[name].usage)
|
||||||
|
}
|
||||||
|
w.Flush()
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
func printLogo() {
|
func printLogo() {
|
||||||
logo := `
|
logo := `
|
||||||
_ ____________ __________ _ ____ ___
|
_ ____________ __________ _ ____ ___
|
||||||
|
|
|
@ -15,19 +15,19 @@ The neo-go compiler compiles Go programs to bytecode that the NEO virtual machin
|
||||||
- basic if statements
|
- basic if statements
|
||||||
- binary expressions
|
- binary expressions
|
||||||
- return statements
|
- return statements
|
||||||
|
- for loops
|
||||||
- imports
|
- imports
|
||||||
|
|
||||||
### Go builtins
|
### Go builtins
|
||||||
- len
|
- len
|
||||||
|
- append
|
||||||
|
|
||||||
### VM API (interop layer)
|
### VM API (interop layer)
|
||||||
- storage
|
- storage
|
||||||
- runtime
|
- runtime
|
||||||
|
|
||||||
## Not yet implemented
|
## Not yet implemented
|
||||||
- for loops
|
|
||||||
- range
|
- range
|
||||||
- builtin (append)
|
|
||||||
- some parts of the interop layer (VM API)
|
- some parts of the interop layer (VM API)
|
||||||
|
|
||||||
## Not supported
|
## Not supported
|
||||||
|
|
|
@ -192,3 +192,7 @@ func isNoRetSyscall(name string) bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isStringType(t types.Type) bool {
|
||||||
|
return t.String() == "string"
|
||||||
|
}
|
||||||
|
|
|
@ -380,15 +380,16 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
|
|
||||||
case *ast.CallExpr:
|
case *ast.CallExpr:
|
||||||
var (
|
var (
|
||||||
f *funcScope
|
f *funcScope
|
||||||
ok bool
|
ok bool
|
||||||
numArgs = len(n.Args)
|
numArgs = len(n.Args)
|
||||||
|
isBuiltin = isBuiltin(n.Fun)
|
||||||
)
|
)
|
||||||
|
|
||||||
switch fun := n.Fun.(type) {
|
switch fun := n.Fun.(type) {
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
f, ok = c.funcs[fun.Name]
|
f, ok = c.funcs[fun.Name]
|
||||||
if !ok && !isBuiltin(n.Fun) {
|
if !ok && !isBuiltin {
|
||||||
log.Fatalf("could not resolve function %s", fun.Name)
|
log.Fatalf("could not resolve function %s", fun.Name)
|
||||||
}
|
}
|
||||||
case *ast.SelectorExpr:
|
case *ast.SelectorExpr:
|
||||||
|
@ -410,12 +411,15 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
for _, arg := range n.Args {
|
for _, arg := range n.Args {
|
||||||
ast.Walk(c, arg)
|
ast.Walk(c, arg)
|
||||||
}
|
}
|
||||||
if numArgs == 2 {
|
// Do not swap for builtin functions.
|
||||||
emitOpcode(c.prog, vm.Oswap)
|
if !isBuiltin {
|
||||||
}
|
if numArgs == 2 {
|
||||||
if numArgs == 3 {
|
emitOpcode(c.prog, vm.Oswap)
|
||||||
emitInt(c.prog, 2)
|
}
|
||||||
emitOpcode(c.prog, vm.Oxswap)
|
if numArgs == 3 {
|
||||||
|
emitInt(c.prog, 2)
|
||||||
|
emitOpcode(c.prog, vm.Oxswap)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// c# compiler adds a NOP (0x61) before every function call. Dont think its relevant
|
// c# compiler adds a NOP (0x61) before every function call. Dont think its relevant
|
||||||
|
@ -424,7 +428,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
emitOpcode(c.prog, vm.Onop)
|
emitOpcode(c.prog, vm.Onop)
|
||||||
|
|
||||||
// Check builtin first to avoid nil pointer on funcScope!
|
// Check builtin first to avoid nil pointer on funcScope!
|
||||||
if isBuiltin(n.Fun) {
|
if isBuiltin {
|
||||||
// Use the ident to check, builtins are not in func scopes.
|
// Use the ident to check, builtins are not in func scopes.
|
||||||
// We can be sure builtins are of type *ast.Ident.
|
// We can be sure builtins are of type *ast.Ident.
|
||||||
c.convertBuiltin(n.Fun.(*ast.Ident).Name, n)
|
c.convertBuiltin(n.Fun.(*ast.Ident).Name, n)
|
||||||
|
@ -456,7 +460,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case *ast.UnaryExpr:
|
case *ast.UnaryExpr:
|
||||||
// TODO(@anthdm)
|
ast.Walk(c, n.X)
|
||||||
|
c.convertToken(n.Op)
|
||||||
|
return nil
|
||||||
|
|
||||||
case *ast.IncDecStmt:
|
case *ast.IncDecStmt:
|
||||||
ast.Walk(c, n.X)
|
ast.Walk(c, n.X)
|
||||||
|
@ -528,9 +534,15 @@ func (c *codegen) convertSyscall(name string) {
|
||||||
func (c *codegen) convertBuiltin(name string, expr *ast.CallExpr) {
|
func (c *codegen) convertBuiltin(name string, expr *ast.CallExpr) {
|
||||||
switch name {
|
switch name {
|
||||||
case "len":
|
case "len":
|
||||||
emitOpcode(c.prog, vm.Oarraysize)
|
arg := expr.Args[0]
|
||||||
|
typ := c.typeInfo.Types[arg].Type
|
||||||
|
if isStringType(typ) {
|
||||||
|
emitOpcode(c.prog, vm.Osize)
|
||||||
|
} else {
|
||||||
|
emitOpcode(c.prog, vm.Oarraysize)
|
||||||
|
}
|
||||||
case "append":
|
case "append":
|
||||||
log.Fatal("builtin (append) not yet implemented.")
|
emitOpcode(c.prog, vm.Oappend)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -621,6 +633,8 @@ func (c *codegen) convertToken(tok token.Token) {
|
||||||
emitOpcode(c.prog, vm.Odec)
|
emitOpcode(c.prog, vm.Odec)
|
||||||
case token.INC:
|
case token.INC:
|
||||||
emitOpcode(c.prog, vm.Oinc)
|
emitOpcode(c.prog, vm.Oinc)
|
||||||
|
case token.NOT:
|
||||||
|
emitOpcode(c.prog, vm.Onot)
|
||||||
default:
|
default:
|
||||||
log.Fatalf("compiler could not convert token: %s", tok)
|
log.Fatalf("compiler could not convert token: %s", tok)
|
||||||
}
|
}
|
||||||
|
@ -702,7 +716,7 @@ func (c *codegen) writeJumps() {
|
||||||
switch vm.Opcode(op) {
|
switch vm.Opcode(op) {
|
||||||
case vm.Ojmp, vm.Ojmpifnot, vm.Ojmpif, vm.Ocall:
|
case vm.Ojmp, vm.Ojmpifnot, vm.Ojmpif, vm.Ocall:
|
||||||
index := int16(binary.LittleEndian.Uint16(b[j : j+2]))
|
index := int16(binary.LittleEndian.Uint16(b[j : j+2]))
|
||||||
if int(index) > len(c.l) {
|
if int(index) > len(c.l) || int(index) < 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
offset := uint16(c.l[index] - i)
|
offset := uint16(c.l[index] - i)
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
var boolTestCases = []testCase{
|
|
||||||
{
|
|
||||||
"bool assign",
|
|
||||||
`
|
|
||||||
package foo
|
|
||||||
func Main() bool {
|
|
||||||
x := true
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
"52c56b516c766b00527ac46203006c766b00c3616c7566",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bool compare",
|
|
||||||
`
|
|
||||||
package foo
|
|
||||||
func Main() int {
|
|
||||||
x := true
|
|
||||||
if x {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
"54c56b516c766b00527ac46c766b00c3640b006203005a616c756662030000616c7566",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bool compare verbose",
|
|
||||||
`
|
|
||||||
package foo
|
|
||||||
func Main() int {
|
|
||||||
x := true
|
|
||||||
if x == true {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
"54c56b516c766b00527ac46c766b00c3519c640b006203005a616c756662030000616c7566",
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// "bool invert (unary expr)",
|
|
||||||
// `
|
|
||||||
// package foo
|
|
||||||
// func Main() int {
|
|
||||||
// x := true
|
|
||||||
// if !x {
|
|
||||||
// return 10
|
|
||||||
// }
|
|
||||||
// return 0
|
|
||||||
// }
|
|
||||||
// `,
|
|
||||||
// "54c56b516c766b00527ac46c766b00c3630b006203005a616c756662030000616c7566",
|
|
||||||
// },
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
package compiler
|
|
||||||
|
|
||||||
var stringTestCases = []testCase{
|
|
||||||
{
|
|
||||||
"simple string",
|
|
||||||
`
|
|
||||||
package testcase
|
|
||||||
func Main() string {
|
|
||||||
x := "NEO"
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
"52c56b034e454f6c766b00527ac46203006c766b00c3616c7566",
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -28,10 +28,13 @@ func NewContext(b []byte) *Context {
|
||||||
// Next return the next instruction to execute.
|
// Next return the next instruction to execute.
|
||||||
func (c *Context) Next() Opcode {
|
func (c *Context) Next() Opcode {
|
||||||
c.ip++
|
c.ip++
|
||||||
|
if c.ip >= len(c.prog) {
|
||||||
|
return Oret
|
||||||
|
}
|
||||||
return Opcode(c.prog[c.ip])
|
return Opcode(c.prog[c.ip])
|
||||||
}
|
}
|
||||||
|
|
||||||
// IP returns the absosulute instruction without taking 0 into account.
|
// IP returns the absolute instruction without taking 0 into account.
|
||||||
// If that program starts the ip = 0 but IP() will return 1, cause its
|
// If that program starts the ip = 0 but IP() will return 1, cause its
|
||||||
// the first instruction.
|
// the first instruction.
|
||||||
func (c *Context) IP() int {
|
func (c *Context) IP() int {
|
||||||
|
|
|
@ -72,6 +72,9 @@ func (e *Element) BigInt() *big.Int {
|
||||||
// Bool attempts to get the underlying value of the element as a boolean.
|
// Bool attempts to get the underlying value of the element as a boolean.
|
||||||
// Will panic if the assertion failed which will be catched by the VM.
|
// Will panic if the assertion failed which will be catched by the VM.
|
||||||
func (e *Element) Bool() bool {
|
func (e *Element) Bool() bool {
|
||||||
|
if v, ok := e.value.Value().(*big.Int); ok {
|
||||||
|
return v.Int64() == 1
|
||||||
|
}
|
||||||
return e.value.Value().(bool)
|
return e.value.Value().(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,117 +0,0 @@
|
||||||
package vm_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var arrayTestCases = []testCase{
|
|
||||||
{
|
|
||||||
"assign int array",
|
|
||||||
`
|
|
||||||
package foo
|
|
||||||
func Main() []int {
|
|
||||||
x := []int{1, 2, 3}
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
[]vm.StackItem{
|
|
||||||
vm.NewBigIntegerItem(1),
|
|
||||||
vm.NewBigIntegerItem(2),
|
|
||||||
vm.NewBigIntegerItem(3),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"assign string array",
|
|
||||||
`
|
|
||||||
package foo
|
|
||||||
func Main() []string {
|
|
||||||
x := []string{"foo", "bar", "foobar"}
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
[]vm.StackItem{
|
|
||||||
vm.NewByteArrayItem([]byte("foo")),
|
|
||||||
vm.NewByteArrayItem([]byte("bar")),
|
|
||||||
vm.NewByteArrayItem([]byte("foobar")),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"array item assign",
|
|
||||||
`
|
|
||||||
package foo
|
|
||||||
func Main() int {
|
|
||||||
x := []int{0, 1, 2}
|
|
||||||
y := x[0]
|
|
||||||
return y
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
big.NewInt(0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"array item return",
|
|
||||||
`
|
|
||||||
package foo
|
|
||||||
func Main() int {
|
|
||||||
x := []int{0, 1, 2}
|
|
||||||
return x[1]
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
big.NewInt(1),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"array item in bin expr",
|
|
||||||
`
|
|
||||||
package foo
|
|
||||||
func Main() int {
|
|
||||||
x := []int{0, 1, 2}
|
|
||||||
return x[1] + 10
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
big.NewInt(11),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"array item ident",
|
|
||||||
`
|
|
||||||
package foo
|
|
||||||
func Main() int {
|
|
||||||
x := 1
|
|
||||||
y := []int{0, 1, 2}
|
|
||||||
return y[x]
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
big.NewInt(1),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"array item index with binExpr",
|
|
||||||
`
|
|
||||||
package foo
|
|
||||||
func Main() int {
|
|
||||||
x := 1
|
|
||||||
y := []int{0, 1, 2}
|
|
||||||
return y[x + 1]
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
big.NewInt(2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"array item struct",
|
|
||||||
`
|
|
||||||
package foo
|
|
||||||
|
|
||||||
type Bar struct {
|
|
||||||
arr []int
|
|
||||||
}
|
|
||||||
|
|
||||||
func Main() int {
|
|
||||||
b := Bar{
|
|
||||||
arr: []int{0, 1, 2},
|
|
||||||
}
|
|
||||||
x := b.arr[2]
|
|
||||||
return x + 2
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
big.NewInt(4),
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package vm_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestClassicForLoop(t *testing.T) {
|
|
||||||
src := `
|
|
||||||
package foo
|
|
||||||
func Main() int {
|
|
||||||
x := 0
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
x = i
|
|
||||||
}
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
`
|
|
||||||
eval(t, src, big.NewInt(9))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This could be a nasty bug. Output of the VM is 65695.
|
|
||||||
// Only happens above 100000, could be binary read issue.
|
|
||||||
//func TestForLoopBigIter(t *testing.T) {
|
|
||||||
// src := `
|
|
||||||
// package foo
|
|
||||||
// func Main() int {
|
|
||||||
// x := 0
|
|
||||||
// for i := 0; i < 100000; i++ {
|
|
||||||
// x = i
|
|
||||||
// }
|
|
||||||
// return x
|
|
||||||
// }
|
|
||||||
// `
|
|
||||||
// eval(t, src, big.NewInt(99999))
|
|
||||||
//}
|
|
306
pkg/vm/tests/for_test.go
Normal file
306
pkg/vm/tests/for_test.go
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
package vm_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArrayFieldInStruct(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
|
||||||
|
type Bar struct {
|
||||||
|
arr []int
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() int {
|
||||||
|
b := Bar{
|
||||||
|
arr: []int{0, 1, 2},
|
||||||
|
}
|
||||||
|
x := b.arr[2]
|
||||||
|
return x + 2
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, big.NewInt(4))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayItemGetIndexBinaryExpr(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() int {
|
||||||
|
x := 1
|
||||||
|
y := []int{0, 1, 2}
|
||||||
|
return y[x + 1]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, big.NewInt(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayItemGetIndexIdent(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() int {
|
||||||
|
x := 1
|
||||||
|
y := []int{0, 1, 2}
|
||||||
|
return y[x]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, big.NewInt(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayItemBinExpr(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() int {
|
||||||
|
x := []int{0, 1, 2}
|
||||||
|
return x[1] + 10
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, big.NewInt(11))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayItemReturn(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() int {
|
||||||
|
arr := []int{0, 1, 2}
|
||||||
|
return arr[1]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, big.NewInt(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayItemAssign(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() int {
|
||||||
|
arr := []int{1, 2, 3}
|
||||||
|
y := arr[0]
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, big.NewInt(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringArray(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() []string {
|
||||||
|
x := []string{"foo", "bar", "foobar"}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, []vm.StackItem{
|
||||||
|
vm.NewByteArrayItem([]byte("foo")),
|
||||||
|
vm.NewByteArrayItem([]byte("bar")),
|
||||||
|
vm.NewByteArrayItem([]byte("foobar")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntArray(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() []int {
|
||||||
|
arr := []int{1, 2, 3}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, []vm.StackItem{
|
||||||
|
vm.NewBigIntegerItem(1),
|
||||||
|
vm.NewBigIntegerItem(2),
|
||||||
|
vm.NewBigIntegerItem(3),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayLen(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() int {
|
||||||
|
arr := []int{0, 1, 2}
|
||||||
|
return len(arr)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, big.NewInt(3))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringLen(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() int {
|
||||||
|
str := "this is medium sized string"
|
||||||
|
return len(str)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, big.NewInt(27))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimpleString(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() string {
|
||||||
|
x := "NEO"
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, vm.NewByteArrayItem([]byte("NEO")).Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolAssign(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() bool {
|
||||||
|
x := true
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, big.NewInt(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolCompare(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() int {
|
||||||
|
x := true
|
||||||
|
if x {
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, big.NewInt(10))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolCompareVerbose(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() int {
|
||||||
|
x := true
|
||||||
|
if x == true {
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, big.NewInt(10))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnaryExpr(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() bool {
|
||||||
|
x := false
|
||||||
|
return !x
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIfUnaryInvertPass(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() int {
|
||||||
|
x := false
|
||||||
|
if !x {
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, big.NewInt(10))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIfUnaryInvert(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() int {
|
||||||
|
x := true
|
||||||
|
if !x {
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, big.NewInt(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendString(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() string {
|
||||||
|
arr := []string{"a", "b", "c"}
|
||||||
|
arr = append(arr, "d")
|
||||||
|
return arr[3]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, vm.NewByteArrayItem([]byte("d")).Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendInt(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() int {
|
||||||
|
arr := []int{0, 1, 2}
|
||||||
|
arr = append(arr, 3)
|
||||||
|
return arr[3]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, big.NewInt(3))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClassicForLoop(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() int {
|
||||||
|
x := 0
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
x = i
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, big.NewInt(9))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInc(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() int {
|
||||||
|
x := 0
|
||||||
|
x++
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
eval(t, src, big.NewInt(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDec(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package foo
|
||||||
|
func Main() int {
|
||||||
|
x := 2
|
||||||
|
x--
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
eval(t, src, big.NewInt(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This could be a nasty bug. Output of the VM is 65695.
|
||||||
|
// Only happens above 100000, could be binary read issue.
|
||||||
|
//func TestForLoopBigIter(t *testing.T) {
|
||||||
|
// src := `
|
||||||
|
// package foo
|
||||||
|
// func Main() int {
|
||||||
|
// x := 0
|
||||||
|
// for i := 0; i < 100000; i++ {
|
||||||
|
// x = i
|
||||||
|
// }
|
||||||
|
// return x
|
||||||
|
// }
|
||||||
|
// `
|
||||||
|
// eval(t, src, big.NewInt(99999))
|
||||||
|
//}
|
|
@ -33,7 +33,6 @@ func TestVMAndCompilerCases(t *testing.T) {
|
||||||
testCases := []testCase{}
|
testCases := []testCase{}
|
||||||
testCases = append(testCases, numericTestCases...)
|
testCases = append(testCases, numericTestCases...)
|
||||||
testCases = append(testCases, assignTestCases...)
|
testCases = append(testCases, assignTestCases...)
|
||||||
testCases = append(testCases, arrayTestCases...)
|
|
||||||
testCases = append(testCases, binaryExprTestCases...)
|
testCases = append(testCases, binaryExprTestCases...)
|
||||||
testCases = append(testCases, structTestCases...)
|
testCases = append(testCases, structTestCases...)
|
||||||
|
|
17
pkg/vm/vm.go
17
pkg/vm/vm.go
|
@ -186,10 +186,6 @@ func (v *VM) Run() {
|
||||||
func (v *VM) Step() {
|
func (v *VM) Step() {
|
||||||
ctx := v.Context()
|
ctx := v.Context()
|
||||||
op := ctx.Next()
|
op := ctx.Next()
|
||||||
if ctx.ip >= len(ctx.prog) {
|
|
||||||
op = Oret
|
|
||||||
}
|
|
||||||
|
|
||||||
v.execute(ctx, op)
|
v.execute(ctx, op)
|
||||||
|
|
||||||
// re-peek the context as it could been changed during execution.
|
// re-peek the context as it could been changed during execution.
|
||||||
|
@ -460,8 +456,8 @@ func (v *VM) execute(ctx *Context, op Opcode) {
|
||||||
v.estack.PushVal(x.Abs(x))
|
v.estack.PushVal(x.Abs(x))
|
||||||
|
|
||||||
case Onot:
|
case Onot:
|
||||||
x := v.estack.Pop().BigInt()
|
x := v.estack.Pop().Bool()
|
||||||
v.estack.PushVal(x.Not(x))
|
v.estack.PushVal(!x)
|
||||||
|
|
||||||
case Onz:
|
case Onz:
|
||||||
panic("todo NZ")
|
panic("todo NZ")
|
||||||
|
@ -486,6 +482,7 @@ func (v *VM) execute(ctx *Context, op Opcode) {
|
||||||
case *ArrayItem, *StructItem:
|
case *ArrayItem, *StructItem:
|
||||||
arr := t.Value().([]StackItem)
|
arr := t.Value().([]StackItem)
|
||||||
arr = append(arr, itemElem.value)
|
arr = append(arr, itemElem.value)
|
||||||
|
v.estack.PushVal(arr)
|
||||||
default:
|
default:
|
||||||
panic("APPEND: not of underlying type Array")
|
panic("APPEND: not of underlying type Array")
|
||||||
}
|
}
|
||||||
|
@ -558,6 +555,14 @@ func (v *VM) execute(ctx *Context, op Opcode) {
|
||||||
}
|
}
|
||||||
v.estack.PushVal(len(arr))
|
v.estack.PushVal(len(arr))
|
||||||
|
|
||||||
|
case Osize:
|
||||||
|
elem := v.estack.Pop()
|
||||||
|
arr, ok := elem.value.Value().([]uint8)
|
||||||
|
if !ok {
|
||||||
|
panic("SIZE: item not of type []uint8")
|
||||||
|
}
|
||||||
|
v.estack.PushVal(len(arr))
|
||||||
|
|
||||||
case Ojmp, Ojmpif, Ojmpifnot:
|
case Ojmp, Ojmpif, Ojmpifnot:
|
||||||
var (
|
var (
|
||||||
rOffset = int16(ctx.readUint16())
|
rOffset = int16(ctx.readUint16())
|
||||||
|
|
Loading…
Reference in a new issue