github.com/klaytn/klaytn@v1.12.1/blockchain/asm/compiler.go (about)

     1  // Modifications Copyright 2018 The klaytn Authors
     2  // Copyright 2017 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from core/asm/compiler.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package asm
    22  
    23  import (
    24  	"fmt"
    25  	"math/big"
    26  	"os"
    27  	"strings"
    28  
    29  	"github.com/klaytn/klaytn/blockchain/vm"
    30  	"github.com/klaytn/klaytn/common/math"
    31  )
    32  
    33  // Compiler contains information about the parsed source
    34  // and holds the tokens for the program.
    35  type Compiler struct {
    36  	tokens []token
    37  	binary []interface{}
    38  
    39  	labels map[string]int
    40  
    41  	pc, pos int
    42  
    43  	debug bool
    44  }
    45  
    46  // newCompiler returns a new allocated compiler.
    47  func NewCompiler(debug bool) *Compiler {
    48  	return &Compiler{
    49  		labels: make(map[string]int),
    50  		debug:  debug,
    51  	}
    52  }
    53  
    54  // Feed feeds tokens in to ch and are interpreted by
    55  // the compiler.
    56  //
    57  // feed is the first pass in the compile stage as it
    58  // collects the used labels in the program and keeps a
    59  // program counter which is used to determine the locations
    60  // of the jump dests. The labels can than be used in the
    61  // second stage to push labels and determine the right
    62  // position.
    63  func (c *Compiler) Feed(ch <-chan token) {
    64  	var prev token
    65  	for i := range ch {
    66  		switch i.typ {
    67  		case number:
    68  			num := math.MustParseBig256(i.text).Bytes()
    69  			if len(num) == 0 {
    70  				num = []byte{0}
    71  			}
    72  			c.pc += len(num)
    73  		case stringValue:
    74  			c.pc += len(i.text) - 2
    75  		case element:
    76  			c.pc++
    77  		case labelDef:
    78  			c.labels[i.text] = c.pc
    79  			c.pc++
    80  		case label:
    81  			c.pc += 4
    82  			if prev.typ == element && isJump(prev.text) {
    83  				c.pc++
    84  			}
    85  		}
    86  
    87  		c.tokens = append(c.tokens, i)
    88  		prev = i
    89  	}
    90  	if c.debug {
    91  		fmt.Fprintln(os.Stderr, "found", len(c.labels), "labels")
    92  	}
    93  }
    94  
    95  // Compile compiles the current tokens and returns a
    96  // binary string that can be interpreted by the EVM
    97  // and an error if it failed.
    98  //
    99  // compile is the second stage in the compile phase
   100  // which compiles the tokens to EVM instructions.
   101  func (c *Compiler) Compile() (string, []error) {
   102  	var errors []error
   103  	// continue looping over the tokens until
   104  	// the stack has been exhausted.
   105  	for c.pos < len(c.tokens) {
   106  		if err := c.compileLine(); err != nil {
   107  			errors = append(errors, err)
   108  		}
   109  	}
   110  
   111  	// turn the binary to hex
   112  	var bin string
   113  	for _, v := range c.binary {
   114  		switch v := v.(type) {
   115  		case vm.OpCode:
   116  			bin += fmt.Sprintf("%x", []byte{byte(v)})
   117  		case []byte:
   118  			bin += fmt.Sprintf("%x", v)
   119  		}
   120  	}
   121  	return bin, errors
   122  }
   123  
   124  // next returns the next token and increments the
   125  // position.
   126  func (c *Compiler) next() token {
   127  	token := c.tokens[c.pos]
   128  	c.pos++
   129  	return token
   130  }
   131  
   132  // compileLine compiles a single line instruction e.g.
   133  // "push 1", "jump @label".
   134  func (c *Compiler) compileLine() error {
   135  	n := c.next()
   136  	if n.typ != lineStart {
   137  		return compileErr(n, n.typ.String(), lineStart.String())
   138  	}
   139  
   140  	lvalue := c.next()
   141  	switch lvalue.typ {
   142  	case eof:
   143  		return nil
   144  	case element:
   145  		if err := c.compileElement(lvalue); err != nil {
   146  			return err
   147  		}
   148  	case labelDef:
   149  		c.compileLabel()
   150  	case lineEnd:
   151  		return nil
   152  	default:
   153  		return compileErr(lvalue, lvalue.text, fmt.Sprintf("%v or %v", labelDef, element))
   154  	}
   155  
   156  	if n := c.next(); n.typ != lineEnd {
   157  		return compileErr(n, n.text, lineEnd.String())
   158  	}
   159  
   160  	return nil
   161  }
   162  
   163  // compileNumber compiles the number to bytes
   164  func (c *Compiler) compileNumber(element token) (int, error) {
   165  	num := math.MustParseBig256(element.text).Bytes()
   166  	if len(num) == 0 {
   167  		num = []byte{0}
   168  	}
   169  	c.pushBin(num)
   170  	return len(num), nil
   171  }
   172  
   173  // compileElement compiles the element (push & label or both)
   174  // to a binary representation and may error if incorrect statements
   175  // where fed.
   176  func (c *Compiler) compileElement(element token) error {
   177  	// check for a jump. jumps must be read and compiled
   178  	// from right to left.
   179  	if isJump(element.text) {
   180  		rvalue := c.next()
   181  		switch rvalue.typ {
   182  		case number:
   183  			// TODO figure out how to return the error properly
   184  			c.compileNumber(rvalue)
   185  		case stringValue:
   186  			// strings are quoted, remove them.
   187  			c.pushBin(rvalue.text[1 : len(rvalue.text)-2])
   188  		case label:
   189  			c.pushBin(vm.PUSH4)
   190  			pos := big.NewInt(int64(c.labels[rvalue.text])).Bytes()
   191  			pos = append(make([]byte, 4-len(pos)), pos...)
   192  			c.pushBin(pos)
   193  		case lineEnd:
   194  			c.pos--
   195  		default:
   196  			return compileErr(rvalue, rvalue.text, "number, string or label")
   197  		}
   198  		// push the operation
   199  		c.pushBin(toBinary(element.text))
   200  		return nil
   201  	} else if isPush(element.text) {
   202  		// handle pushes. pushes are read from left to right.
   203  		var value []byte
   204  
   205  		rvalue := c.next()
   206  		switch rvalue.typ {
   207  		case number:
   208  			value = math.MustParseBig256(rvalue.text).Bytes()
   209  			if len(value) == 0 {
   210  				value = []byte{0}
   211  			}
   212  		case stringValue:
   213  			value = []byte(rvalue.text[1 : len(rvalue.text)-1])
   214  		case label:
   215  			value = big.NewInt(int64(c.labels[rvalue.text])).Bytes()
   216  			value = append(make([]byte, 4-len(value)), value...)
   217  		default:
   218  			return compileErr(rvalue, rvalue.text, "number, string or label")
   219  		}
   220  
   221  		if len(value) > 32 {
   222  			return fmt.Errorf("%d type error: unsupported string or number with size > 32", rvalue.lineno)
   223  		}
   224  
   225  		c.pushBin(vm.OpCode(int(vm.PUSH1) - 1 + len(value)))
   226  		c.pushBin(value)
   227  	} else {
   228  		c.pushBin(toBinary(element.text))
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  // compileLabel pushes a jumpdest to the binary slice.
   235  func (c *Compiler) compileLabel() {
   236  	c.pushBin(vm.JUMPDEST)
   237  }
   238  
   239  // pushBin pushes the value v to the binary stack.
   240  func (c *Compiler) pushBin(v interface{}) {
   241  	if c.debug {
   242  		fmt.Printf("%d: %v\n", len(c.binary), v)
   243  	}
   244  	c.binary = append(c.binary, v)
   245  }
   246  
   247  // isPush returns whether the string op is either any of
   248  // push(N).
   249  func isPush(op string) bool {
   250  	return strings.ToUpper(op) == "PUSH"
   251  }
   252  
   253  // isJump returns whether the string op is jump(i)
   254  func isJump(op string) bool {
   255  	return strings.ToUpper(op) == "JUMPI" || strings.ToUpper(op) == "JUMP"
   256  }
   257  
   258  // toBinary converts text to a vm.OpCode
   259  func toBinary(text string) vm.OpCode {
   260  	return vm.StringToOp(strings.ToUpper(text))
   261  }
   262  
   263  type compileError struct {
   264  	got  string
   265  	want string
   266  
   267  	lineno int
   268  }
   269  
   270  func (err compileError) Error() string {
   271  	return fmt.Sprintf("%d syntax error: unexpected %v, expected %v", err.lineno, err.got, err.want)
   272  }
   273  
   274  func compileErr(c token, got, want string) error {
   275  	return compileError{
   276  		got:    got,
   277  		want:   want,
   278  		lineno: c.lineno,
   279  	}
   280  }