Skip to content

Commit

Permalink
rust, js: use bop() helper in VM for binary operations
Browse files Browse the repository at this point in the history
  • Loading branch information
cdump committed Jun 5, 2024
1 parent d6863e4 commit 20bb52e
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 267 deletions.
109 changes: 52 additions & 57 deletions evmole/evm/vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ def step(self) -> tuple[OpCode, int, *tuple[Any, ...]]:
self.stopped = True
return (op, *ret)

def _bop(self, cb):
raws0 = self.stack.pop()
raws1 = self.stack.pop()

s0 = int.from_bytes(raws0.data, 'big', signed=False)
s1 = int.from_bytes(raws1.data, 'big', signed=False)

gas_used, res = cb(raws0, s0, raws1, s1)

self.stack.push_uint(res)
return (gas_used, raws0, raws1)

def _exec_opcode(self, op: OpCode) -> tuple[int, *tuple[Any, ...]]:
match op:
case op if op >= Op.PUSH0 and op <= Op.PUSH32:
Expand Down Expand Up @@ -93,66 +105,49 @@ def _exec_opcode(self, op: OpCode) -> tuple[int, *tuple[Any, ...]]:
self.stopped = True
return (4,)

case op if op in {
Op.EQ,
Op.LT,
Op.GT,
Op.SUB,
Op.ADD,
Op.DIV,
Op.MUL,
Op.EXP,
Op.XOR,
Op.AND,
Op.OR,
Op.SHR,
Op.SHL,
Op.BYTE,
}:
raws0 = self.stack.pop()
raws1 = self.stack.pop()
case Op.EQ:
return self._bop(lambda raws0, s0, raws1, s1: (3, 1 if s0 == s1 else 0))

s0 = int.from_bytes(raws0.data, 'big', signed=False)
s1 = int.from_bytes(raws1.data, 'big', signed=False)
case Op.LT:
return self._bop(lambda raws0, s0, raws1, s1: (3, 1 if s0 < s1 else 0))

gas_used = 3
match op:
case Op.EQ:
res = 1 if s0 == s1 else 0
case Op.LT:
res = 1 if s0 < s1 else 0
case Op.GT:
res = 1 if s0 > s1 else 0
case Op.SUB:
res = (s0 - s1) & E256M1
case Op.ADD:
res = (s0 + s1) & E256M1
case Op.DIV:
res = 0 if s1 == 0 else s0 // s1
gas_used = 5
case Op.MUL:
res = (s0 * s1) & E256M1
gas_used = 5
case Op.EXP:
res = pow(s0, s1, E256)
gas_used = 50 * (1 + (s1.bit_length() // 8)) # ~approx
case Op.XOR:
res = s0 ^ s1
case Op.AND:
res = s0 & s1
case Op.OR:
res = s0 | s1
case Op.SHR:
res = 0 if s0 >= 256 else (s1 >> s0) & E256M1
case Op.SHL:
res = 0 if s0 >= 256 else (s1 << s0) & E256M1
case Op.BYTE:
res = 0 if s0 >= 32 else raws1.data[s0]
case _:
raise Exception(f'BUG: op {op} not handled in match')
case Op.GT:
return self._bop(lambda raws0, s0, raws1, s1: (3, 1 if s0 > s1 else 0))

self.stack.push_uint(res)
return (gas_used, raws0, raws1)
case Op.SUB:
return self._bop(lambda raws0, s0, raws1, s1: (3, (s0 - s1) & E256M1))

case Op.ADD:
return self._bop(lambda raws0, s0, raws1, s1: (3, (s0 + s1) & E256M1))

case Op.DIV:
return self._bop(lambda raws0, s0, raws1, s1: (5, 0 if s1 == 0 else s0 // s1))

case Op.MUL:
return self._bop(lambda raws0, s0, raws1, s1: (5, (s0 * s1) & E256M1))

case Op.EXP:
return self._bop(
lambda raws0, s0, raws1, s1: (50 * (1 + (s1.bit_length() // 8)), pow(s0, s1, E256))
) # ~approx gas

case Op.XOR:
return self._bop(lambda raws0, s0, raws1, s1: (3, s0 ^ s1))

case Op.AND:
return self._bop(lambda raws0, s0, raws1, s1: (3, s0 & s1))

case Op.OR:
return self._bop(lambda raws0, s0, raws1, s1: (3, s0 | s1))

case Op.SHR:
return self._bop(lambda raws0, s0, raws1, s1: (3, 0 if s0 >= 256 else (s1 >> s0) & E256M1))

case Op.SHL:
return self._bop(lambda raws0, s0, raws1, s1: (3, 0 if s0 >= 256 else (s1 << s0) & E256M1))

case Op.BYTE:
return self._bop(lambda raws0, s0, raws1, s1: (3, 0 if s0 >= 32 else raws1.data[s0]))

case op if op in {Op.SLT, Op.SGT}:
raws0 = self.stack.pop()
Expand Down
4 changes: 2 additions & 2 deletions js/src/evm/memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export default class Memory {

size() {
if (this._data.length === 0) {
return 0;
return 0
}
return Math.max(...this._data.map(([off, val]) => off + val.data.length));
return Math.max(...this._data.map(([off, val]) => off + val.data.length))
}

load(offset) {
Expand Down
131 changes: 57 additions & 74 deletions js/src/evm/vm.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,19 @@ export class Vm {
return [op, ...ret]
}

#bop(cb) {
const raws0 = this.stack.pop()
const raws1 = this.stack.pop()

const s0 = toBigInt(raws0.data)
const s1 = toBigInt(raws1.data)

const [gas_used, res] = cb(raws0, s0, raws1, s1)

this.stack.push_uint(res)
return [gas_used, raws0, raws1]
}

#exec_opcode(op) {
if (op >= Op.PUSH0 && op <= Op.PUSH32) {
const n = op - Op.PUSH0
Expand Down Expand Up @@ -111,95 +124,65 @@ export class Vm {
return [4]

case Op.EQ:
return this.#bop((raws0, s0, raws1, s1) => [3, s0 == s1 ? 1n : 0n])

case Op.LT:
return this.#bop((raws0, s0, raws1, s1) => [3, s0 < s1 ? 1n : 0n])

case Op.GT:
return this.#bop((raws0, s0, raws1, s1) => [3, s0 > s1 ? 1n : 0n])

case Op.SUB:
return this.#bop((raws0, s0, raws1, s1) => [3, (s0 - s1) & E256M1])

case Op.ADD:
return this.#bop((raws0, s0, raws1, s1) => [3, (s0 + s1) & E256M1])

case Op.DIV:
return this.#bop((raws0, s0, raws1, s1) => [5, s1 != 0n ? s0 / s1 : 0n])

case Op.MUL:
return this.#bop((raws0, s0, raws1, s1) => [5, (s0 * s1) & E256M1])

case Op.EXP:
return this.#bop((raws0, s0, raws1, s1) => [
50 * (1 + Math.floor(bigIntBitLength(s1) / 8)), // ~approx
modExp(s0, s1, E256),
])

case Op.XOR:
return this.#bop((raws0, s0, raws1, s1) => [3, s0 ^ s1])

case Op.AND:
return this.#bop((raws0, s0, raws1, s1) => [3, s0 & s1])

case Op.OR:
return this.#bop((raws0, s0, raws1, s1) => [3, s0 | s1])

case Op.SHR:
return this.#bop((raws0, s0, raws1, s1) => [3, s0 >= 256n ? 0n : (s1 >> s0) & E256M1])

case Op.SHL:
case Op.BYTE: {
const raws0 = this.stack.pop()
const raws1 = this.stack.pop()
return this.#bop((raws0, s0, raws1, s1) => [3, s0 >= 256n ? 0n : (s1 << s0) & E256M1])

const s0 = toBigInt(raws0.data)
const s1 = toBigInt(raws1.data)
case Op.SLT:
return this.#bop((raws0, s0, raws1, s1) => {
// unsigned to signed
const a = s0 <= E255M1 ? s0 : s0 - E256
const b = s1 <= E255M1 ? s1 : s1 - E256
return [3, a < b ? 1n : 0n]
})

let res
let gas_used = 3
switch (op) {
case Op.EQ:
res = s0 == s1 ? 1n : 0n
break
case Op.LT:
res = s0 < s1 ? 1n : 0n
break
case Op.GT:
res = s0 > s1 ? 1n : 0n
break
case Op.SUB:
res = (s0 - s1) & E256M1
break
case Op.ADD:
res = (s0 + s1) & E256M1
break
case Op.DIV:
res = s1 != 0n ? s0 / s1 : 0n
gas_used = 5
break
case Op.MUL:
res = (s0 * s1) & E256M1
gas_used = 5
break
case Op.EXP:
res = modExp(s0, s1, E256)
gas_used = 50 * (1 + Math.floor(bigIntBitLength(s1) / 8)) // ~approx
break
case Op.XOR:
res = s0 ^ s1
break
case Op.AND:
res = s0 & s1
break
case Op.OR:
res = s0 | s1
break
case Op.SHR:
res = s0 >= 256n ? 0n : (s1 >> s0) & E256M1
break
case Op.SHL:
res = s0 >= 256n ? 0n : (s1 << s0) & E256M1
break
case Op.BYTE:
res = s0 >= 32n ? 0n : BigInt(raws1.data[s0])
break
}
this.stack.push_uint(res)
return [gas_used, raws0, raws1]
}
case Op.SGT:
return this.#bop((raws0, s0, raws1, s1) => {
// unsigned to signed
const a = s0 <= E255M1 ? s0 : s0 - E256
const b = s1 <= E255M1 ? s1 : s1 - E256
return [3, a > b ? 1n : 0n]
})

case Op.SLT:
case Op.SGT: {
let s0 = this.stack.pop_uint()
let s1 = this.stack.pop_uint()

// unsigned to signed
s0 = s0 <= E255M1 ? s0 : s0 - E256
s1 = s1 <= E255M1 ? s1 : s1 - E256
let res
if (op === Op.SLT) {
res = s0 < s1 ? 1n : 0n
} else {
res = s0 > s1 ? 1n : 0n
}
this.stack.push_uint(res)
return [3]
}
case Op.BYTE:
return this.#bop((raws0, s0, raws1) => [3, s0 >= 32n ? 0n : BigInt(raws1.data[s0])])

case Op.ISZERO: {
const raw = this.stack.pop()
Expand Down
Loading

0 comments on commit 20bb52e

Please sign in to comment.