-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added list based stack, tests, and benchmarks
- Loading branch information
1 parent
167e22f
commit 498949c
Showing
3 changed files
with
471 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package io.iohk.ethereum.vm | ||
|
||
import io.iohk.ethereum.domain.UInt256 | ||
|
||
object StackL { | ||
/** | ||
* Stack max size as defined in the YP (9.1) | ||
*/ | ||
val DefaultMaxSize = 1024 | ||
|
||
def empty(maxSize: Int = DefaultMaxSize): StackL = | ||
new StackL(List(), maxSize) | ||
} | ||
|
||
//TODO: consider a List with head being top of the stack (DUP,SWAP go at most the depth of 16) [EC-251] | ||
/** | ||
* Stack for the EVM. Instruction pop their arguments from it and push their results to it. | ||
* The Stack doesn't handle overflow and underflow errors. Any operations that trascend given stack bounds will | ||
* return the stack unchanged. Pop will always return zeroes in such case. | ||
*/ | ||
class StackL private(private val underlying: List[UInt256], val maxSize: Int) { | ||
|
||
def pop: (UInt256, StackL) = underlying.headOption match { | ||
case Some(word) => | ||
val updated = underlying.tail | ||
(word, copy(updated)) | ||
|
||
case None => | ||
(UInt256.Zero, this) | ||
} | ||
|
||
/** | ||
* Pop n elements from the stack. The first element in the resulting sequence will be the top-most element | ||
* in the current stack | ||
*/ | ||
def pop(n: Int): (Seq[UInt256], StackL) = { | ||
val (popped, updated) = underlying.splitAt(n) | ||
if (popped.length == n) | ||
(popped, copy(updated)) | ||
else | ||
(Seq.fill(n)(UInt256.Zero), this) | ||
} | ||
|
||
def push(word: UInt256): StackL = { | ||
val updated = word :: underlying | ||
if (updated.length <= maxSize) | ||
copy(updated) | ||
else | ||
this | ||
} | ||
|
||
/** | ||
* Push a sequence of elements to the stack. That last element of the sequence will be the top-most element | ||
* in the resulting stack | ||
*/ | ||
def push(words: Seq[UInt256]): StackL = { | ||
val updated = underlying.reverse_:::(words.toList) | ||
if (updated.length > maxSize) | ||
this | ||
else | ||
copy(updated) | ||
} | ||
|
||
/** | ||
* Duplicate i-th element of the stack, pushing it to the top. i=0 is the top-most element. | ||
*/ | ||
def dup(i: Int): StackL = { | ||
if (i < 0 || i >= underlying.length || underlying.length >= maxSize) | ||
this | ||
else | ||
copy(underlying(i) :: underlying) | ||
} | ||
|
||
/** | ||
* Swap i-th and the top-most elements of the stack. i=0 is the top-most element (and that would be a no-op) | ||
*/ | ||
def swap(i: Int): StackL = { | ||
if (i <= 0 || i >= underlying.length) | ||
this | ||
else { | ||
val a = underlying.head | ||
val b = underlying(i) | ||
val updated = b :: underlying.updated(i, a).tail | ||
copy(updated) | ||
} | ||
} | ||
|
||
def size: Int = underlying.size | ||
|
||
/** | ||
* @return the elements of the stack as a sequence, with the top-most element of the stack | ||
* as the first element in the sequence | ||
*/ | ||
def toSeq: Seq[UInt256] = underlying | ||
|
||
override def equals(that: Any): Boolean = that match { | ||
case that: StackL => this.underlying == that.underlying | ||
case _ => false | ||
} | ||
|
||
override def hashCode(): Int = underlying.hashCode | ||
|
||
override def toString: String = | ||
underlying.reverse.mkString("Stack(", ",", ")") | ||
|
||
private def copy(updated: List[UInt256]): StackL = | ||
new StackL(updated, maxSize) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package io.iohk.ethereum.vm | ||
|
||
import io.iohk.ethereum.domain.UInt256 | ||
import org.scalacheck.Gen | ||
import org.scalatest.prop.PropertyChecks | ||
import org.scalatest.{FunSuite, Matchers} | ||
|
||
class StackLSpec extends FunSuite with Matchers with PropertyChecks { | ||
|
||
val maxStackSize = 32 | ||
val stackGen = Generators.getStackLGen(maxSize = maxStackSize) | ||
val intGen = Gen.choose(0, maxStackSize).filter(_ >= 0) | ||
val uint256Gen = Generators.getUInt256Gen() | ||
val uint256ListGen = Generators.getListGen(0, 16, uint256Gen) | ||
|
||
test("pop single element") { | ||
forAll(stackGen) { stack => | ||
val (v, stack1) = stack.pop | ||
if (stack.size > 0) { | ||
v shouldEqual stack.toSeq.head | ||
stack1.toSeq shouldEqual stack.toSeq.tail | ||
} else { | ||
v shouldEqual 0 | ||
stack1 shouldEqual stack | ||
} | ||
} | ||
} | ||
|
||
test("pop multiple elements") { | ||
forAll(stackGen, intGen) { (stack, i) => | ||
val (vs, stack1) = stack.pop(i) | ||
if (stack.size >= i) { | ||
vs shouldEqual stack.toSeq.take(i) | ||
stack1.toSeq shouldEqual stack.toSeq.drop(i) | ||
} else { | ||
vs shouldEqual Seq.fill(i)(UInt256.Zero) | ||
stack1 shouldEqual stack | ||
} | ||
} | ||
} | ||
|
||
test("push single element") { | ||
forAll(stackGen, uint256Gen) { (stack, v) => | ||
val stack1 = stack.push(v) | ||
|
||
if (stack.size < stack.maxSize) { | ||
stack1.toSeq shouldEqual (v +: stack.toSeq) | ||
} else { | ||
stack1 shouldEqual stack | ||
} | ||
} | ||
} | ||
|
||
test("push multiple elements") { | ||
forAll(stackGen, uint256ListGen) { (stack, vs) => | ||
val stack1 = stack.push(vs) | ||
|
||
if (stack.size + vs.size <= stack.maxSize) { | ||
stack1.toSeq shouldEqual (vs.reverse ++ stack.toSeq) | ||
} else { | ||
stack1 shouldEqual stack | ||
} | ||
} | ||
} | ||
|
||
test("duplicate element") { | ||
forAll(stackGen, intGen) { (stack, i) => | ||
val stack1 = stack.dup(i) | ||
|
||
if (i < stack.size && stack.size < stack.maxSize) { | ||
val x = stack.toSeq(i) | ||
stack1.toSeq shouldEqual (x +: stack.toSeq) | ||
} else { | ||
stack1 shouldEqual stack | ||
} | ||
} | ||
} | ||
|
||
test("swap elements") { | ||
forAll(stackGen, intGen) { (stack, i) => | ||
val stack1 = stack.swap(i) | ||
|
||
if (i < stack.size) { | ||
val x = stack.toSeq.head | ||
val y = stack.toSeq(i) | ||
stack1.toSeq shouldEqual stack.toSeq.updated(0, y).updated(i, x) | ||
} else { | ||
stack1 shouldEqual stack | ||
} | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.