diff --git a/benchmarks/src/swam/runtime/internals/interpreter/Config.scala b/benchmarks/src/swam/runtime/internals/interpreter/Config.scala index e82f66e6..f8d591ea 100644 --- a/benchmarks/src/swam/runtime/internals/interpreter/Config.scala +++ b/benchmarks/src/swam/runtime/internals/interpreter/Config.scala @@ -26,6 +26,8 @@ package object interpreter { type Label = Long - val Config = EngineConfiguration(false, StackConfiguration(5.bytes, 1), DataConfiguration(true, 0.bytes)) + val HighConfig = HighLevelStackConfiguration(5.bytes, 1) + + val LowConfig = LowLevelStackConfiguration(5.bytes) } diff --git a/benchmarks/src/swam/runtime/internals/interpreter/StackPerformances.scala b/benchmarks/src/swam/runtime/internals/interpreter/StackPerformances.scala index bc3846ff..fb171673 100644 --- a/benchmarks/src/swam/runtime/internals/interpreter/StackPerformances.scala +++ b/benchmarks/src/swam/runtime/internals/interpreter/StackPerformances.scala @@ -40,7 +40,7 @@ class StackPerformances_01_Push { @Setup(Level.Iteration) def setupFrame(): Unit = { - frame = Frame.makeToplevel[Either[Throwable, ?]](null, Config) + frame = Frame.makeToplevel[Either[Throwable, ?]](null, HighConfig) } @Setup(Level.Invocation) @@ -75,7 +75,7 @@ class StackPerformances_02_Pop { @Setup(Level.Iteration) def setupFrame(): Unit = { - frame = Frame.makeToplevel[Either[Throwable, ?]](null, Config) + frame = Frame.makeToplevel[Either[Throwable, ?]](null, HighConfig) frame.stack.pushInt(Random.nextInt()) frame.stack.pushLong(Random.nextLong()) frame.stack.pushFloat(Random.nextFloat()) diff --git a/runtime/resources/reference.conf b/runtime/resources/reference.conf index 86428a9a..e9c11cb8 100644 --- a/runtime/resources/reference.conf +++ b/runtime/resources/reference.conf @@ -27,14 +27,27 @@ swam { stack { - # The height of the local execution stack - # Each frame gets its own stack, so this can be kept - # quite small. - size = 128 B - - # The depth of the call stack. performing more nested - # call than this limit will result in a stack overflow exception - call-depth = 500 + # stack configuration for the low level interpreter + low { + + # the total stack size + size = 64 KiB + + } + + # stack configuration for the high level interpreter + high { + + # The height of the local execution stack + # Each frame gets its own stack, so this can be kept + # quite small. + size = 128 B + + # The depth of the call stack. performing more nested + # call than this limit will result in a stack overflow exception + call-depth = 500 + + } } diff --git a/runtime/src/swam/runtime/Instance.scala b/runtime/src/swam/runtime/Instance.scala index aaed779e..99fca6e3 100644 --- a/runtime/src/swam/runtime/Instance.scala +++ b/runtime/src/swam/runtime/Instance.scala @@ -144,6 +144,40 @@ class Instance[F[_]] private[runtime] (val module: Module[F], private[runtime] v writer2: ValueWriter[F, P2]): F[e.EFunction2[P1, P2, Unit, F]] = e.EFunction2[P1, P2, F](name, self) + /** Returns a function for given name and type. */ + def function3[P1, P2, P3, Ret](name: String)(implicit F: MonadError[F, Throwable], + writer1: ValueWriter[F, P1], + writer2: ValueWriter[F, P2], + writer3: ValueWriter[F, P3], + reader: ValueReader[F, Ret]): F[e.EFunction3[P1, P2, P3, Ret, F]] = + e.EFunction3(name, self) + + /** Returns a function for given name and type. */ + def procedure3[P1, P2, P3](name: String)(implicit F: MonadError[F, Throwable], + writer1: ValueWriter[F, P1], + writer2: ValueWriter[F, P2], + writer3: ValueWriter[F, P3]): F[e.EFunction3[P1, P2, P3, Unit, F]] = + e.EFunction3[P1, P2, P3, F](name, self) + + /** Returns a function for given name and type. */ + def function4[P1, P2, P3, P4, Ret](name: String)( + implicit F: MonadError[F, Throwable], + writer1: ValueWriter[F, P1], + writer2: ValueWriter[F, P2], + writer3: ValueWriter[F, P3], + writer4: ValueWriter[F, P4], + reader: ValueReader[F, Ret]): F[e.EFunction4[P1, P2, P3, P4, Ret, F]] = + e.EFunction4(name, self) + + /** Returns a function for given name and type. */ + def procedure4[P1, P2, P3, P4](name: String)( + implicit F: MonadError[F, Throwable], + writer1: ValueWriter[F, P1], + writer2: ValueWriter[F, P2], + writer3: ValueWriter[F, P3], + writer4: ValueWriter[F, P4]): F[e.EFunction4[P1, P2, P3, P4, Unit, F]] = + e.EFunction4[P1, P2, P3, P4, F](name, self) + } } @@ -165,7 +199,7 @@ class Instance[F[_]] private[runtime] (val module: Module[F], private[runtime] v module.elems(idx) match { case CompiledElem(coffset, init) => interpreter.interpretInit(ValType.I32, coffset, self).flatMap { roffset => - val offset = (roffset.get & 0x00000000ffffffffl).toInt + val offset = (roffset.get & 0X00000000FFFFFFFFL).toInt if (offset < 0 || init.size + offset > tables(0).size) { F.raiseError(new LinkException("Overflow in table initialization")) } else { @@ -189,7 +223,7 @@ class Instance[F[_]] private[runtime] (val module: Module[F], private[runtime] v module.data(idx) match { case CompiledData(coffset, init) => interpreter.interpretInit(ValType.I32, coffset, self).flatMap { roffset => - val offset = (roffset.get & 0x00000000ffffffffl).toInt + val offset = (roffset.get & 0X00000000FFFFFFFFL).toInt if (offset < 0 || init.capacity + offset > memories(0).size) F.raiseError(new LinkException("Overflow in memory initialization")) else diff --git a/runtime/src/swam/runtime/config/EngineConfiguration.scala b/runtime/src/swam/runtime/config/EngineConfiguration.scala index dd97c270..a10adfbc 100644 --- a/runtime/src/swam/runtime/config/EngineConfiguration.scala +++ b/runtime/src/swam/runtime/config/EngineConfiguration.scala @@ -28,6 +28,10 @@ import squants.information._ */ case class EngineConfiguration(useLowLevelAsm: Boolean, stack: StackConfiguration, data: DataConfiguration) -case class StackConfiguration(size: Information, callDepth: Int) +case class StackConfiguration(high: HighLevelStackConfiguration, low: LowLevelStackConfiguration) case class DataConfiguration(onHeap: Boolean, hardMax: Information) + +case class LowLevelStackConfiguration(size: Information) + +case class HighLevelStackConfiguration(size: Information, callDepth: Int) diff --git a/runtime/src/swam/runtime/exceptions.scala b/runtime/src/swam/runtime/exceptions.scala index 18ac6a12..9ef17b24 100644 --- a/runtime/src/swam/runtime/exceptions.scala +++ b/runtime/src/swam/runtime/exceptions.scala @@ -34,7 +34,8 @@ sealed class RuntimeException(msg: String, inner: Throwable = null) extends Swam final class TrapException(frame: StackFrame, msg: String) extends RuntimeException(msg) /** Raised when call stack overflows. */ -final class StackOverflowException(frame: StackFrame) extends RuntimeException("call stack exhausted") +final class StackOverflowException(frame: StackFrame, inner: Throwable = null) + extends RuntimeException("call stack exhausted", inner) /** Raised when trying to type interface elements with invalid types. */ final class ConversionException(msg: String) extends RuntimeException(msg) diff --git a/runtime/src/swam/runtime/exports/EFunction3.scala b/runtime/src/swam/runtime/exports/EFunction3.scala new file mode 100644 index 00000000..f0d379b0 --- /dev/null +++ b/runtime/src/swam/runtime/exports/EFunction3.scala @@ -0,0 +1,91 @@ +/* + * Copyright 2018 Lucas Satabin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package swam +package runtime +package exports + +import runtime.{imports => i} +import formats._ +import internals.compiler._ + +import cats._ +import cats.implicits._ + +import scala.language.higherKinds + +abstract class EFunction3[P1, P2, P3, Ret, F[_]] private (f: Function[F], m: Option[Memory[F]])( + implicit F: MonadError[F, Throwable], + writer1: ValueWriter[F, P1], + writer2: ValueWriter[F, P2], + writer3: ValueWriter[F, P3]) + extends EFunction[Ret, F] + with Function3[P1, P2, P3, F[Ret]] { + def apply(p1: P1, p2: P2, p3: P3): F[Ret] = + for { + p1 <- writer1.write(p1, m) + p2 <- writer2.write(p2, m) + p3 <- writer3.write(p3, m) + v <- f.invoke(Vector(p1, p2, p3), m) + v <- wrap(v) + } yield v +} + +object EFunction3 { + import EFunction._ + + def apply[P1, P2, P3, F[_]](name: String, self: Instance[F])( + implicit F: MonadError[F, Throwable], + writer1: ValueWriter[F, P1], + writer2: ValueWriter[F, P2], + writer3: ValueWriter[F, P3]): F[EFunction3[P1, P2, P3, Unit, F]] = + self.exps.get(name) match { + case Some(f: Function[F]) => + val expectedt = FuncType(Vector(writer1.swamType, writer2.swamType, writer3.swamType), Vector()) + if (f.tpe == expectedt) + F.pure(new EFunction3[P1, P2, P3, Unit, F](f, self.memories.headOption) { + def wrap(res: Option[Value]): F[Unit] = EFunction.wrapUnit[F](res) + }) + else + F.raiseError(new RuntimeException(s"invalid return type (expected $expectedt but got ${f.tpe}")) + case Some(fld) => + F.raiseError(new RuntimeException(s"cannot get a function for type ${fld.tpe}")) + case None => + F.raiseError(new RuntimeException(s"unknown function named $name")) + } + + def apply[P1, P2, P3, Ret, F[_]](name: String, self: Instance[F])( + implicit F: MonadError[F, Throwable], + writer1: ValueWriter[F, P1], + writer2: ValueWriter[F, P2], + writer3: ValueWriter[F, P3], + reader: ValueReader[F, Ret]): F[EFunction3[P1, P2, P3, Ret, F]] = + self.exps.get(name) match { + case Some(f: Function[F]) => + val expectedt = FuncType(Vector(writer1.swamType, writer2.swamType, writer3.swamType), Vector(reader.swamType)) + if (f.tpe == expectedt) + F.pure(new EFunction3[P1, P2, P3, Ret, F](f, self.memories.headOption) { + def wrap(res: Option[Value]): F[Ret] = EFunction.wrap[F, Ret](res, self.memories.headOption) + }) + else + F.raiseError(new RuntimeException(s"invalid return type (expected $expectedt but got ${f.tpe}")) + case Some(fld) => + F.raiseError(new RuntimeException(s"cannot get a function for type ${fld.tpe}")) + case None => + F.raiseError(new RuntimeException(s"unknown function named $name")) + } + +} diff --git a/runtime/src/swam/runtime/exports/EFunction4.scala b/runtime/src/swam/runtime/exports/EFunction4.scala new file mode 100644 index 00000000..ddf8e258 --- /dev/null +++ b/runtime/src/swam/runtime/exports/EFunction4.scala @@ -0,0 +1,96 @@ +/* + * Copyright 2018 Lucas Satabin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package swam +package runtime +package exports + +import runtime.{imports => i} +import formats._ +import internals.compiler._ + +import cats._ +import cats.implicits._ + +import scala.language.higherKinds + +abstract class EFunction4[P1, P2, P3, P4, Ret, F[_]] private (f: Function[F], m: Option[Memory[F]])( + implicit F: MonadError[F, Throwable], + writer1: ValueWriter[F, P1], + writer2: ValueWriter[F, P2], + writer3: ValueWriter[F, P3], + writer4: ValueWriter[F, P4]) + extends EFunction[Ret, F] + with Function4[P1, P2, P3, P4, F[Ret]] { + def apply(p1: P1, p2: P2, p3: P3, p4: P4): F[Ret] = + for { + p1 <- writer1.write(p1, m) + p2 <- writer2.write(p2, m) + p3 <- writer3.write(p3, m) + p4 <- writer4.write(p4, m) + v <- f.invoke(Vector(p1, p2, p3, p4), m) + v <- wrap(v) + } yield v +} + +object EFunction4 { + import EFunction._ + + def apply[P1, P2, P3, P4, F[_]](name: String, self: Instance[F])( + implicit F: MonadError[F, Throwable], + writer1: ValueWriter[F, P1], + writer2: ValueWriter[F, P2], + writer3: ValueWriter[F, P3], + writer4: ValueWriter[F, P4]): F[EFunction4[P1, P2, P3, P4, Unit, F]] = + self.exps.get(name) match { + case Some(f: Function[F]) => + val expectedt = + FuncType(Vector(writer1.swamType, writer2.swamType, writer3.swamType, writer4.swamType), Vector()) + if (f.tpe == expectedt) + F.pure(new EFunction4[P1, P2, P3, P4, Unit, F](f, self.memories.headOption) { + def wrap(res: Option[Value]): F[Unit] = EFunction.wrapUnit[F](res) + }) + else + F.raiseError(new RuntimeException(s"invalid return type (expected $expectedt but got ${f.tpe}")) + case Some(fld) => + F.raiseError(new RuntimeException(s"cannot get a function for type ${fld.tpe}")) + case None => + F.raiseError(new RuntimeException(s"unknown function named $name")) + } + + def apply[P1, P2, P3, P4, Ret, F[_]](name: String, self: Instance[F])( + implicit F: MonadError[F, Throwable], + writer1: ValueWriter[F, P1], + writer2: ValueWriter[F, P2], + writer3: ValueWriter[F, P3], + writer4: ValueWriter[F, P4], + reader: ValueReader[F, Ret]): F[EFunction4[P1, P2, P3, P4, Ret, F]] = + self.exps.get(name) match { + case Some(f: Function[F]) => + val expectedt = FuncType(Vector(writer1.swamType, writer2.swamType, writer3.swamType), Vector(reader.swamType)) + if (f.tpe == expectedt) + F.pure(new EFunction4[P1, P2, P3, P4, Ret, F](f, self.memories.headOption) { + def wrap(res: Option[Value]): F[Ret] = EFunction.wrap[F, Ret](res, self.memories.headOption) + }) + else + F.raiseError(new RuntimeException(s"invalid return type (expected $expectedt but got ${f.tpe}")) + case Some(fld) => + F.raiseError(new RuntimeException(s"cannot get a function for type ${fld.tpe}")) + case None => + F.raiseError(new RuntimeException(s"unknown function named $name")) + } + +} diff --git a/runtime/src/swam/runtime/internals/compiler/low/Compiler.scala b/runtime/src/swam/runtime/internals/compiler/low/Compiler.scala index 245a2c63..f908dca0 100644 --- a/runtime/src/swam/runtime/internals/compiler/low/Compiler.scala +++ b/runtime/src/swam/runtime/internals/compiler/low/Compiler.scala @@ -155,7 +155,7 @@ class Compiler[F[_]: Effect](engine: Engine[F]) extends compiler.Compiler[F] { val cglobals = globals.map { case Global(tpe, init) => - val ccode = ByteBuffer.wrap(compile(init, FunctionContext(1), ctx.functions, ctx.types)._1) + val ccode = ByteBuffer.wrap(compile(0, init, FunctionContext(1), ctx.functions, ctx.types)._1) Glob.Compiled(CompiledGlobal(tpe, ccode)) } ctx.copy(globals = ctx.globals ++ cglobals) @@ -171,7 +171,12 @@ class Compiler[F[_]: Effect](engine: Engine[F]) extends compiler.Compiler[F] { codes.zipWithIndex.map { case ((FuncBody(locals, code)), idx) => val tpe = ctx.types(ctx.funcs(idx + shift)) - val ccode = ByteBuffer.wrap(compile(code, FunctionContext(tpe.t.size), ctx.functions, ctx.types)._1) + val ccode = ByteBuffer.wrap( + compile(tpe.params.size + locals.map(_.count).sum, + code, + FunctionContext(tpe.t.size), + ctx.functions, + ctx.types)._1) val clocals = locals.flatMap(e => Vector.fill(e.count)(e.tpe)) compiler.Func.Compiled(CompiledFunction(tpe, clocals, ccode)) } @@ -180,7 +185,7 @@ class Compiler[F[_]: Effect](engine: Engine[F]) extends compiler.Compiler[F] { val celems = elems.map { case Elem(_, offset, init) => - val coffset = ByteBuffer.wrap(compile(offset, FunctionContext(1), ctx.functions, ctx.types)._1) + val coffset = ByteBuffer.wrap(compile(0, offset, FunctionContext(1), ctx.functions, ctx.types)._1) CompiledElem(coffset, init) } ctx.copy(elems = celems) @@ -188,7 +193,7 @@ class Compiler[F[_]: Effect](engine: Engine[F]) extends compiler.Compiler[F] { val cdata = data.map { case Data(_, offset, bytes) => - val compiled = compile(offset, FunctionContext(1), ctx.functions, ctx.types)._1 + val compiled = compile(0, offset, FunctionContext(1), ctx.functions, ctx.types)._1 val coffset = if (dataOnHeap) { ByteBuffer.wrap(compiled) @@ -229,7 +234,8 @@ class Compiler[F[_]: Effect](engine: Engine[F]) extends compiler.Compiler[F] { } .handleErrorWith(t => Stream.raiseError[F](new CompileException("An error occurred during compilation", t))) - private def compile(insts: Vector[Inst], + private def compile(nbLocals: Int, + insts: Vector[Inst], ctx: FunctionContext, functions: Vector[FuncType], types: Vector[FuncType]): (Array[Byte], FunctionContext) = { @@ -247,7 +253,7 @@ class Compiler[F[_]: Effect](engine: Engine[F]) extends compiler.Compiler[F] { // arity as the block and a target on break pointing right // after the block body. val label = LabelStack(ctx.labels, ctx.nxt, tpe.arity, 0) - val (compiled, ctx1) = compile(is, ctx.copy(labels = label, nxt = ctx.nxt + 1), functions, types) + val (compiled, ctx1) = compile(nbLocals, is, ctx.copy(labels = label, nxt = ctx.nxt + 1), functions, types) builder ++= compiled // the label target is then the offset right after the body has been compiled loop(instIdx + 1, @@ -257,7 +263,7 @@ class Compiler[F[_]: Effect](engine: Engine[F]) extends compiler.Compiler[F] { // when entering a new loop, push a label with arity 0 // and a target on break pointing at the loop body start val label = LabelStack(ctx.labels, ctx.nxt, 0, 0) - val (compiled, ctx1) = compile(is, ctx.copy(labels = label, nxt = ctx.nxt + 1), functions, types) + val (compiled, ctx1) = compile(nbLocals, is, ctx.copy(labels = label, nxt = ctx.nxt + 1), functions, types) builder ++= compiled loop(instIdx + 1, ctx1.copy(labels = ctx.labels, offsets = ctx1.offsets.updated(ctx.nxt, ctx.offset)).push(tpe.arity), @@ -273,7 +279,8 @@ class Compiler[F[_]: Effect](engine: Engine[F]) extends compiler.Compiler[F] { builder += Asm.JumpIf.toByte storeInt(builder, -1 /* placeholder */ ) val (ecompiled, ctx2) = - compile(eis, + compile(nbLocals, + eis, ctx1.copy(labels = label, nxt = ctx1.nxt + 2, offset = ctx1.offset + 5 /* jumpif + label */ ), functions, types) @@ -281,7 +288,8 @@ class Compiler[F[_]: Effect](engine: Engine[F]) extends compiler.Compiler[F] { // jump right after the then part in the end builder += Asm.Jump.toByte storeInt(builder, -1 /* placeholder */ ) - val (tcompiled, ctx3) = compile(tis, ctx2.copy(offset = ctx2.offset + 5, labels = label), functions, types) + val (tcompiled, ctx3) = + compile(nbLocals, tis, ctx2.copy(offset = ctx2.offset + 5, labels = label), functions, types) builder ++= tcompiled loop( instIdx + 1, @@ -393,15 +401,15 @@ class Compiler[F[_]: Effect](engine: Engine[F]) extends compiler.Compiler[F] { loop(instIdx + 1, ctx.pop(2 /* pop 3 push 1 */ ).copy(offset = ctx.offset + 1), false) case LocalGet(idx) => builder += Asm.LocalGet.toByte - storeInt(builder, idx) + storeInt(builder, nbLocals - idx) loop(instIdx + 1, ctx.push(1).copy(offset = ctx.offset + 5), false) case LocalSet(idx) => builder += Asm.LocalSet.toByte - storeInt(builder, idx) + storeInt(builder, nbLocals - idx) loop(instIdx + 1, ctx.pop(1).copy(offset = ctx.offset + 5), false) case LocalTee(idx) => builder += Asm.LocalTee.toByte - storeInt(builder, idx) + storeInt(builder, nbLocals - idx) loop(instIdx + 1, ctx.copy(offset = ctx.offset + 5), false) case GlobalGet(idx) => builder += Asm.GlobalGet.toByte diff --git a/runtime/src/swam/runtime/internals/instance/FunctionInstance.scala b/runtime/src/swam/runtime/internals/instance/FunctionInstance.scala index be423ac2..27279741 100644 --- a/runtime/src/swam/runtime/internals/instance/FunctionInstance.scala +++ b/runtime/src/swam/runtime/internals/instance/FunctionInstance.scala @@ -34,6 +34,8 @@ private[runtime] case class FunctionInstance[F[_]](tpe: FuncType, instance: Instance[F])(implicit F: MonadError[F, Throwable]) extends Function[F] { + var next: FunctionInstance[F] = _ + def invoke(parameters: Vector[Value], m: Option[Memory[F]]): F[Option[Value]] = instance.interpreter .interpret(this, parameters.map(Value.toRaw(_)), instance) diff --git a/runtime/src/swam/runtime/internals/interpreter/high/Frame.scala b/runtime/src/swam/runtime/internals/interpreter/high/Frame.scala index 1daf569d..2f190929 100644 --- a/runtime/src/swam/runtime/internals/interpreter/high/Frame.scala +++ b/runtime/src/swam/runtime/internals/interpreter/high/Frame.scala @@ -235,8 +235,8 @@ object Frame { private final val VALUE = 0 private final val LABEL = 1 - def makeToplevel[F[_]](instance: Instance[F], conf: EngineConfiguration)( + def makeToplevel[F[_]](instance: Instance[F], conf: HighLevelStackConfiguration)( implicit F: MonadError[F, Throwable]): Frame[F] = - new Frame[F](null, conf.stack.size.toBytes.toInt, conf.stack.callDepth, 0, null, null, 0, instance) + new Frame[F](null, conf.size.toBytes.toInt, conf.callDepth, 0, null, null, 0, instance) } diff --git a/runtime/src/swam/runtime/internals/interpreter/high/Interpreter.scala b/runtime/src/swam/runtime/internals/interpreter/high/Interpreter.scala index 4b4e6bd0..00ddfae4 100644 --- a/runtime/src/swam/runtime/internals/interpreter/high/Interpreter.scala +++ b/runtime/src/swam/runtime/internals/interpreter/high/Interpreter.scala @@ -41,7 +41,7 @@ private[runtime] class Interpreter[F[_]](engine: Engine[F])(implicit F: MonadErr def interpret(funcidx: Int, parameters: Vector[Long], instance: Instance[F]): F[Option[Long]] = { // instantiate the top-level frame - val frame = Frame.makeToplevel[F](instance, conf) + val frame = Frame.makeToplevel[F](instance, conf.stack.high) // push the parameters in the stack frame.stack.pushValues(parameters) // invoke the function @@ -53,7 +53,7 @@ private[runtime] class Interpreter[F[_]](engine: Engine[F])(implicit F: MonadErr def interpret(func: Function[F], parameters: Vector[Long], instance: Instance[F]): F[Option[Long]] = { // instantiate the top-level frame - val frame = Frame.makeToplevel[F](instance, conf) + val frame = Frame.makeToplevel[F](instance, conf.stack.high) // push the parameters in the stack frame.stack.pushValues(parameters) // invoke the function @@ -65,7 +65,7 @@ private[runtime] class Interpreter[F[_]](engine: Engine[F])(implicit F: MonadErr def interpretInit(tpe: ValType, code: ByteBuffer, instance: Instance[F]): F[Option[Long]] = { // instantiate the top-level frame - val frame = Frame.makeToplevel[F](instance, conf) + val frame = Frame.makeToplevel[F](instance, conf.stack.high) // invoke the function invoke(frame, new FunctionInstance(FuncType(Vector(), Vector(tpe)), Vector(), code, instance)).flatMap { case Left(frame) => run(frame) diff --git a/runtime/src/swam/runtime/internals/interpreter/low/Frame.scala b/runtime/src/swam/runtime/internals/interpreter/low/Frame.scala deleted file mode 100644 index d1ba018b..00000000 --- a/runtime/src/swam/runtime/internals/interpreter/low/Frame.scala +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2018 Lucas Satabin - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package swam -package runtime -package internals -package interpreter -package low - -import config._ - -import cats._ - -import scala.annotation.{tailrec, switch} - -import java.nio.ByteBuffer - -import java.lang.{Float => JFloat, Double => JDouble} - -import scala.language.higherKinds - -/** A call frame containing the local stack of operands. - */ -sealed class Frame[F[_]] private (parent: Frame[F], - stackSize: Int, - callDepth: Int, - depth: Int, - code: ByteBuffer, - private[interpreter] val locals: Array[Long], - private[interpreter] val arity: Int, - private[interpreter] val instance: Instance[F])(implicit F: MonadError[F, Throwable]) - extends StackFrame { - self => - - import Frame._ - - private[interpreter] var pc = 0 - - private[interpreter] def readByte(): Byte = { - val b = code.get(pc) - pc += 1 - b - } - - private[interpreter] def readInt(): Int = { - val i = code.getInt(pc) - pc += 4 - i - } - - private[interpreter] def readLong(): Long = { - val l = code.getLong(pc) - pc += 8 - l - } - - private[interpreter] def readFloat(): Float = { - val f = code.getFloat(pc) - pc += 4 - f - } - - private[interpreter] def readDouble(): Double = { - val d = code.getDouble(pc) - pc += 8 - d - } - - private[interpreter] object stack { - - private val vstack = Array.ofDim[Long](stackSize) - - private var vtop = 0 - - def clear(): Unit = { - vtop = 0 - } - - def pushBool(b: Boolean): Unit = - pushInt(if (b) 1 else 0) - - def pushInt(i: Int): Unit = - pushValue(i & 0x00000000ffffffffl) - - def pushLong(l: Long): Unit = - pushValue(l) - - def pushFloat(f: Float): Unit = - pushValue(JFloat.floatToRawIntBits(f) & 0x00000000ffffffffl) - - def pushDouble(d: Double): Unit = - pushValue(JDouble.doubleToRawLongBits(d)) - - def popBool(): Boolean = - popInt() != 0 - - def popInt(): Int = - (popValue() & 0x00000000ffffffffl).toInt - - def peekInt(): Int = - (peekValue() & 0x00000000ffffffffl).toInt - - def popLong(): Long = - popValue() - - def peekLong(): Long = - peekValue() - - def popFloat(): Float = - JFloat.intBitsToFloat(popInt()) - - def peekFloat(): Float = - JFloat.intBitsToFloat(peekInt()) - - def popDouble(): Double = - JDouble.longBitsToDouble(popLong()) - - def peekDouble(): Double = - JDouble.longBitsToDouble(peekLong()) - - def drop(n: Int): Unit = { - vtop -= n - } - - def popValue(): Long = { - vtop -= 1 - vstack(vtop) - } - - def peekValue(): Long = - vstack(vtop - 1) - - def popValues(n: Int): Seq[Long] = { - @tailrec - def loop(n: Int, acc: Seq[Long]): Seq[Long] = - if (n <= 0) - acc - else - loop(n - 1, popValue() +: acc) - loop(n, Seq.empty) - } - - def pushValue(l: Long): Unit = { - vstack(vtop) = l - vtop += 1 - } - - def pushValues(values: Seq[Long]): Unit = - values.foreach(pushValue(_)) - - def pushFrame(arity: Int, code: ByteBuffer, locals: Array[Long], instance: Instance[F]): F[Frame[F]] = - if (depth < callDepth) - F.pure(new Frame[F](self, stackSize, callDepth, depth + 1, code, locals, arity, instance)) - else - F.raiseError(new StackOverflowException(self)) - - def popFrame(): Frame[F] = - parent - } - - private[interpreter] def isToplevel: Boolean = - parent == null - -} - -object Frame { - - def makeToplevel[F[_]](instance: Instance[F], conf: EngineConfiguration)( - implicit F: MonadError[F, Throwable]): Frame[F] = - new Frame[F](null, conf.stack.size.toBytes.toInt, conf.stack.callDepth, 0, null, null, 0, instance) - -} diff --git a/runtime/src/swam/runtime/internals/interpreter/low/Interpreter.scala b/runtime/src/swam/runtime/internals/interpreter/low/Interpreter.scala index d46c6c00..5ef51600 100644 --- a/runtime/src/swam/runtime/internals/interpreter/low/Interpreter.scala +++ b/runtime/src/swam/runtime/internals/interpreter/low/Interpreter.scala @@ -37,1198 +37,1200 @@ import scala.language.higherKinds private[runtime] class Interpreter[F[_]](engine: Engine[F])(implicit F: MonadError[F, Throwable]) extends interpreter.Interpreter[F](engine) { + private type Res = Either[Unit, Option[Long]] + private val conf = engine.conf def interpret(funcidx: Int, parameters: Vector[Long], instance: Instance[F]): F[Option[Long]] = { - // instantiate the top-level frame - val frame = Frame.makeToplevel[F](instance, conf) + // instantiate the top-level thread + val thread = new ThreadFrame[F](conf.stack.low, instance) // push the parameters in the stack - frame.stack.pushValues(parameters) + thread.pushValues(parameters) // invoke the function - invoke(frame, instance.funcs(funcidx)).flatMap { - case Left(frame) => run(frame) - case Right(res) => F.pure(res) + invoke(thread, instance.funcs(funcidx)).flatMap { + case Left(_) => run(thread) + case Right(res) => F.pure(res) } } def interpret(func: Function[F], parameters: Vector[Long], instance: Instance[F]): F[Option[Long]] = { - // instantiate the top-level frame - val frame = Frame.makeToplevel[F](instance, conf) + // instantiate the top-level thread + val thread = new ThreadFrame[F](conf.stack.low, instance) // push the parameters in the stack - frame.stack.pushValues(parameters) + thread.pushValues(parameters) // invoke the function - invoke(frame, func).flatMap { - case Left(frame) => run(frame) - case Right(res) => F.pure(res) + invoke(thread, func).flatMap { + case Left(_) => run(thread) + case Right(res) => F.pure(res) } } def interpretInit(tpe: ValType, code: ByteBuffer, instance: Instance[F]): F[Option[Long]] = { - // instantiate the top-level frame - val frame = Frame.makeToplevel[F](instance, conf) + // instantiate the top-level thread + val thread = new ThreadFrame[F](conf.stack.low, instance) // invoke the function - invoke(frame, new FunctionInstance(FuncType(Vector(), Vector(tpe)), Vector(), code, instance)).flatMap { - case Left(frame) => run(frame) - case Right(res) => F.pure(res) + invoke(thread, new FunctionInstance(FuncType(Vector(), Vector(tpe)), Vector(), code, instance)).flatMap { + case Left(_) => run(thread) + case Right(res) => F.pure(res) } } - private def run(frame: Frame[F]): F[Option[Long]] = - F.tailRecM(frame) { frame => - val opcode = frame.readByte() & 0xff - (opcode: @switch) match { - // === constants === - case Asm.I32Const => - frame.stack.pushInt(frame.readInt()) - F.pure(Left(frame)) - case Asm.I64Const => - frame.stack.pushLong(frame.readLong()) - F.pure(Left(frame)) - case Asm.F32Const => - frame.stack.pushFloat(frame.readFloat()) - F.pure(Left(frame)) - case Asm.F64Const => - frame.stack.pushDouble(frame.readDouble()) - F.pure(Left(frame)) - // === unary operators === - case Asm.I32Clz => - frame.stack.pushInt(JInt.numberOfLeadingZeros(frame.stack.popInt())) - F.pure(Left(frame)) - case Asm.I32Ctz => - frame.stack.pushInt(JInt.numberOfTrailingZeros(frame.stack.popInt())) - F.pure(Left(frame)) - case Asm.I32Popcnt => - frame.stack.pushInt(JInt.bitCount(frame.stack.popInt())) - F.pure(Left(frame)) - case Asm.I64Clz => - frame.stack.pushLong(JLong.numberOfLeadingZeros(frame.stack.popLong())) - F.pure(Left(frame)) - case Asm.I64Ctz => - frame.stack.pushLong(JLong.numberOfTrailingZeros(frame.stack.popLong())) - F.pure(Left(frame)) - case Asm.I64Popcnt => - frame.stack.pushLong(JLong.bitCount(frame.stack.popLong())) - F.pure(Left(frame)) - case Asm.F32Abs => - frame.stack.pushFloat(JFloat.intBitsToFloat(JFloat.floatToRawIntBits(frame.stack.popFloat()) & 0x7fffffff)) - F.pure(Left(frame)) - case Asm.F32Neg => - frame.stack.pushFloat(-frame.stack.popFloat()) - F.pure(Left(frame)) - case Asm.F32Sqrt => - frame.stack.pushFloat(StrictMath.sqrt(frame.stack.popFloat()).toFloat) - F.pure(Left(frame)) - case Asm.F32Ceil => - frame.stack.pushFloat(frame.stack.popFloat().ceil) - F.pure(Left(frame)) - case Asm.F32Floor => - frame.stack.pushFloat(frame.stack.popFloat().floor) - F.pure(Left(frame)) - case Asm.F32Trunc => - val f = frame.stack.popFloat() - frame.stack.pushFloat(F32.trunc(f)) - F.pure(Left(frame)) - case Asm.F32Nearest => - val f = frame.stack.popFloat() - frame.stack.pushFloat(F32.nearest(f)) - F.pure(Left(frame)) - case Asm.F64Abs => - frame.stack.pushDouble( - JDouble.longBitsToDouble(JDouble.doubleToRawLongBits(frame.stack.popDouble()) & 0x7fffffffffffffffl)) - F.pure(Left(frame)) - case Asm.F64Neg => - frame.stack.pushDouble(-frame.stack.popDouble()) - F.pure(Left(frame)) - case Asm.F64Sqrt => - frame.stack.pushDouble(StrictMath.sqrt(frame.stack.popDouble())) - F.pure(Left(frame)) - case Asm.F64Ceil => - frame.stack.pushDouble(frame.stack.popDouble().ceil) - F.pure(Left(frame)) - case Asm.F64Floor => - frame.stack.pushDouble(frame.stack.popDouble().floor) - F.pure(Left(frame)) - case Asm.F64Trunc => - val f = frame.stack.popDouble() - frame.stack.pushDouble(F64.trunc(f)) - F.pure(Left(frame)) - case Asm.F64Nearest => - val d = frame.stack.popDouble() - frame.stack.pushDouble(F64.nearest(d)) - F.pure(Left(frame)) - // === binary operators === - case Asm.I32Add => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - frame.stack.pushInt(i1 + i2) - F.pure(Left(frame)) - case Asm.I32Sub => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - frame.stack.pushInt(i1 - i2) - F.pure(Left(frame)) - case Asm.I32Mul => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - frame.stack.pushInt(i1 * i2) - F.pure(Left(frame)) - case Asm.I32DivU => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - if (i2 == 0) { - F.raiseError(new TrapException(frame, "integer divide by zero")) - } else { - frame.stack.pushInt(JInt.divideUnsigned(i1, i2)) - F.pure(Left(frame)) - } - case Asm.I32DivS => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - if (i2 == 0) { - F.raiseError(new TrapException(frame, "integer divide by zero")) - } else if (i1 == Int.MinValue && i2 == -1) { - F.raiseError(new TrapException(frame, "integer overflow")) - } else { - val res = i1 / i2 - if (i1 >= 0 && i2 > 0 && res < 0) { - F.raiseError(new TrapException(frame, "overflow")) + private val continue: F[Either[Unit, Option[Long]]] = F.pure(Left(())) + + private def run(thread: ThreadFrame[F]): F[Option[Long]] = + F.tailRecM(()) { _ => + val opcode = thread.readByte() & 0xff + (opcode: @switch) match { + // === constants === + case Asm.I32Const => + thread.pushInt(thread.readInt()) + continue + case Asm.I64Const => + thread.pushLong(thread.readLong()) + continue + case Asm.F32Const => + thread.pushFloat(thread.readFloat()) + continue + case Asm.F64Const => + thread.pushDouble(thread.readDouble()) + continue + // === unary operators === + case Asm.I32Clz => + thread.pushInt(JInt.numberOfLeadingZeros(thread.popInt())) + continue + case Asm.I32Ctz => + thread.pushInt(JInt.numberOfTrailingZeros(thread.popInt())) + continue + case Asm.I32Popcnt => + thread.pushInt(JInt.bitCount(thread.popInt())) + continue + case Asm.I64Clz => + thread.pushLong(JLong.numberOfLeadingZeros(thread.popLong())) + continue + case Asm.I64Ctz => + thread.pushLong(JLong.numberOfTrailingZeros(thread.popLong())) + continue + case Asm.I64Popcnt => + thread.pushLong(JLong.bitCount(thread.popLong())) + continue + case Asm.F32Abs => + thread.pushFloat(JFloat.intBitsToFloat(JFloat.floatToRawIntBits(thread.popFloat()) & 0x7fffffff)) + continue + case Asm.F32Neg => + thread.pushFloat(-thread.popFloat()) + continue + case Asm.F32Sqrt => + thread.pushFloat(StrictMath.sqrt(thread.popFloat()).toFloat) + continue + case Asm.F32Ceil => + thread.pushFloat(thread.popFloat().ceil) + continue + case Asm.F32Floor => + thread.pushFloat(thread.popFloat().floor) + continue + case Asm.F32Trunc => + val f = thread.popFloat() + thread.pushFloat(F32.trunc(f)) + continue + case Asm.F32Nearest => + val f = thread.popFloat() + thread.pushFloat(F32.nearest(f)) + continue + case Asm.F64Abs => + thread.pushDouble( + JDouble.longBitsToDouble(JDouble.doubleToRawLongBits(thread.popDouble()) & 0x7fffffffffffffffl)) + continue + case Asm.F64Neg => + thread.pushDouble(-thread.popDouble()) + continue + case Asm.F64Sqrt => + thread.pushDouble(StrictMath.sqrt(thread.popDouble())) + continue + case Asm.F64Ceil => + thread.pushDouble(thread.popDouble().ceil) + continue + case Asm.F64Floor => + thread.pushDouble(thread.popDouble().floor) + continue + case Asm.F64Trunc => + val f = thread.popDouble() + thread.pushDouble(F64.trunc(f)) + continue + case Asm.F64Nearest => + val d = thread.popDouble() + thread.pushDouble(F64.nearest(d)) + continue + // === binary operators === + case Asm.I32Add => + val i2 = thread.popInt() + val i1 = thread.popInt() + thread.pushInt(i1 + i2) + continue + case Asm.I32Sub => + val i2 = thread.popInt() + val i1 = thread.popInt() + thread.pushInt(i1 - i2) + continue + case Asm.I32Mul => + val i2 = thread.popInt() + val i1 = thread.popInt() + thread.pushInt(i1 * i2) + continue + case Asm.I32DivU => + val i2 = thread.popInt() + val i1 = thread.popInt() + if (i2 == 0) { + F.raiseError[Res](new TrapException(thread, "integer divide by zero")) } else { - frame.stack.pushInt(i1 / i2) - F.pure(Left(frame)) + thread.pushInt(JInt.divideUnsigned(i1, i2)) + continue } - } - case Asm.I32RemU => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - if (i2 == 0) { - F.raiseError(new TrapException(frame, "integer divide by zero")) - } else { - frame.stack.pushInt(JInt.remainderUnsigned(i1, i2)) - F.pure(Left(frame)) - } - case Asm.I32RemS => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - if (i2 == 0) { - F.raiseError(new TrapException(frame, "integer divide by zero")) - } else { - frame.stack.pushInt(i1 % i2) - F.pure(Left(frame)) - } - case Asm.I32And => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - frame.stack.pushInt(i1 & i2) - F.pure(Left(frame)) - case Asm.I32Or => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - frame.stack.pushInt(i1 | i2) - F.pure(Left(frame)) - case Asm.I32Xor => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - frame.stack.pushInt(i1 ^ i2) - F.pure(Left(frame)) - case Asm.I32Shl => - val i2 = frame.stack.popInt() % 32 - val i1 = frame.stack.popInt() - frame.stack.pushInt(i1 << i2) - F.pure(Left(frame)) - case Asm.I32ShrU => - val i2 = frame.stack.popInt() % 32 - val i1 = frame.stack.popInt() - frame.stack.pushInt(i1 >>> i2) - F.pure(Left(frame)) - case Asm.I32ShrS => - val i2 = frame.stack.popInt() % 32 - val i1 = frame.stack.popInt() - frame.stack.pushInt(i1 >> i2) - F.pure(Left(frame)) - case Asm.I32Rotl => - val i2 = frame.stack.popInt() % 32 - val i1 = frame.stack.popInt() - frame.stack.pushInt(JInt.rotateLeft(i1, i2)) - F.pure(Left(frame)) - case Asm.I32Rotr => - val i2 = frame.stack.popInt() % 32 - val i1 = frame.stack.popInt() - frame.stack.pushInt(JInt.rotateRight(i1, i2)) - F.pure(Left(frame)) - case Asm.I64Add => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - frame.stack.pushLong(i1 + i2) - F.pure(Left(frame)) - case Asm.I64Sub => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - frame.stack.pushLong(i1 - i2) - F.pure(Left(frame)) - case Asm.I64Mul => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - frame.stack.pushLong(i1 * i2) - F.pure(Left(frame)) - case Asm.I64DivU => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - if (i2 == 0) { - F.raiseError(new TrapException(frame, "integer divide by zero")) - } else { - frame.stack.pushLong(JLong.divideUnsigned(i1, i2)) - F.pure(Left(frame)) - } - case Asm.I64DivS => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - if (i2 == 0) { - F.raiseError(new TrapException(frame, "integer divide by zero")) - } else if (i1 == Long.MinValue && i2 == -1l) { - F.raiseError(new TrapException(frame, "integer overflow")) - } else { - val res = i1 / i2 - if (i1 >= 0 && i2 > 0 && res < 0) { - F.raiseError(new TrapException(frame, "overflow")) + case Asm.I32DivS => + val i2 = thread.popInt() + val i1 = thread.popInt() + if (i2 == 0) { + F.raiseError[Res](new TrapException(thread, "integer divide by zero")) + } else if (i1 == Int.MinValue && i2 == -1) { + F.raiseError[Res](new TrapException(thread, "integer overflow")) } else { - frame.stack.pushLong(i1 / i2) - F.pure(Left(frame)) + val res = i1 / i2 + if (i1 >= 0 && i2 > 0 && res < 0) { + F.raiseError[Res](new TrapException(thread, "overflow")) + } else { + thread.pushInt(i1 / i2) + continue + } } - } - case Asm.I64RemU => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - if (i2 == 0) { - F.raiseError(new TrapException(frame, "integer divide by zero")) - } else { - frame.stack.pushLong(JLong.remainderUnsigned(i1, i2)) - F.pure(Left(frame)) - } - case Asm.I64RemS => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - if (i2 == 0) { - F.raiseError(new TrapException(frame, "integer divide by zero")) - } else { - frame.stack.pushLong(i1 % i2) - F.pure(Left(frame)) - } - case Asm.I64And => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - frame.stack.pushLong(i1 & i2) - F.pure(Left(frame)) - case Asm.I64Or => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - frame.stack.pushLong(i1 | i2) - F.pure(Left(frame)) - case Asm.I64Xor => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - frame.stack.pushLong(i1 ^ i2) - F.pure(Left(frame)) - case Asm.I64Shl => - val i2 = frame.stack.popLong() % 64 - val i1 = frame.stack.popLong() - frame.stack.pushLong(i1 << i2) - F.pure(Left(frame)) - case Asm.I64ShrU => - val i2 = frame.stack.popLong() % 64 - val i1 = frame.stack.popLong() - frame.stack.pushLong(i1 >>> i2) - F.pure(Left(frame)) - case Asm.I64ShrS => - val i2 = frame.stack.popLong() % 64 - val i1 = frame.stack.popLong() - frame.stack.pushLong(i1 >> i2) - F.pure(Left(frame)) - case Asm.I64Rotl => - val i2 = (frame.stack.popLong() % 64).toInt - val i1 = frame.stack.popLong() - frame.stack.pushLong(JLong.rotateLeft(i1, i2)) - F.pure(Left(frame)) - case Asm.I64Rotr => - val i2 = (frame.stack.popLong() % 64).toInt - val i1 = frame.stack.popLong() - frame.stack.pushLong(JLong.rotateRight(i1, i2)) - F.pure(Left(frame)) - case Asm.F32Add => - val f2 = frame.stack.popFloat() - val f1 = frame.stack.popFloat() - frame.stack.pushFloat(f1 + f2) - F.pure(Left(frame)) - case Asm.F32Sub => - val f2 = frame.stack.popFloat() - val f1 = frame.stack.popFloat() - frame.stack.pushFloat(f1 - f2) - F.pure(Left(frame)) - case Asm.F32Mul => - val f2 = frame.stack.popFloat() - val f1 = frame.stack.popFloat() - frame.stack.pushFloat(f1 * f2) - F.pure(Left(frame)) - case Asm.F32Div => - val f2 = frame.stack.popFloat() - val f1 = frame.stack.popFloat() - frame.stack.pushFloat(f1 / f2) - F.pure(Left(frame)) - case Asm.F32Min => - val f2 = frame.stack.popFloat() - val f1 = frame.stack.popFloat() - frame.stack.pushFloat(StrictMath.min(f1, f2)) - F.pure(Left(frame)) - case Asm.F32Max => - val f2 = frame.stack.popFloat() - val f1 = frame.stack.popFloat() - frame.stack.pushFloat(StrictMath.max(f1, f2)) - F.pure(Left(frame)) - case Asm.F32Copysign => - val f2 = frame.stack.popFloat() - val f1 = frame.stack.popFloat() - frame.stack.pushFloat(Math.copySign(f1, f2)) - F.pure(Left(frame)) - case Asm.F64Add => - val f2 = frame.stack.popDouble() - val f1 = frame.stack.popDouble() - frame.stack.pushDouble(f1 + f2) - F.pure(Left(frame)) - case Asm.F64Sub => - val f2 = frame.stack.popDouble() - val f1 = frame.stack.popDouble() - frame.stack.pushDouble(f1 - f2) - F.pure(Left(frame)) - case Asm.F64Mul => - val f2 = frame.stack.popDouble() - val f1 = frame.stack.popDouble() - frame.stack.pushDouble(f1 * f2) - F.pure(Left(frame)) - case Asm.F64Div => - val f2 = frame.stack.popDouble() - val f1 = frame.stack.popDouble() - frame.stack.pushDouble(f1 / f2) - F.pure(Left(frame)) - case Asm.F64Min => - val f2 = frame.stack.popDouble() - val f1 = frame.stack.popDouble() - frame.stack.pushDouble(StrictMath.min(f1, f2)) - F.pure(Left(frame)) - case Asm.F64Max => - val f2 = frame.stack.popDouble() - val f1 = frame.stack.popDouble() - frame.stack.pushDouble(StrictMath.max(f1, f2)) - F.pure(Left(frame)) - case Asm.F64Copysign => - val f2 = frame.stack.popDouble() - val f1 = frame.stack.popDouble() - frame.stack.pushDouble(Math.copySign(f1, f2)) - F.pure(Left(frame)) - // === test operators === - case Asm.I32Eqz => - val i = frame.stack.popInt() - frame.stack.pushBool(i == 0) - F.pure(Left(frame)) - case Asm.I64Eqz => - val i = frame.stack.popLong() - frame.stack.pushBool(i == 0) - F.pure(Left(frame)) - // === relation operators === - case Asm.I32Eq => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - frame.stack.pushBool(i1 == i2) - F.pure(Left(frame)) - case Asm.I32Ne => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - frame.stack.pushBool(i1 != i2) - F.pure(Left(frame)) - case Asm.I32LtU => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - frame.stack.pushBool(JInt.compareUnsigned(i1, i2) < 0) - F.pure(Left(frame)) - case Asm.I32LtS => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - frame.stack.pushBool(i1 < i2) - F.pure(Left(frame)) - case Asm.I32GtU => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - frame.stack.pushBool(JInt.compareUnsigned(i1, i2) > 0) - F.pure(Left(frame)) - case Asm.I32GtS => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - frame.stack.pushBool(i1 > i2) - F.pure(Left(frame)) - case Asm.I32LeU => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - frame.stack.pushBool(JInt.compareUnsigned(i1, i2) <= 0) - F.pure(Left(frame)) - case Asm.I32LeS => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - frame.stack.pushBool(i1 <= i2) - F.pure(Left(frame)) - case Asm.I32GeU => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - frame.stack.pushBool(JInt.compareUnsigned(i1, i2) >= 0) - F.pure(Left(frame)) - case Asm.I32GeS => - val i2 = frame.stack.popInt() - val i1 = frame.stack.popInt() - frame.stack.pushBool(i1 >= i2) - F.pure(Left(frame)) - case Asm.I64Eq => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - frame.stack.pushBool(i1 == i2) - F.pure(Left(frame)) - case Asm.I64Ne => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - frame.stack.pushBool(i1 != i2) - F.pure(Left(frame)) - case Asm.I64LtU => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - frame.stack.pushBool(JLong.compareUnsigned(i1, i2) < 0) - F.pure(Left(frame)) - case Asm.I64LtS => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - frame.stack.pushBool(i1 < i2) - F.pure(Left(frame)) - case Asm.I64GtU => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - frame.stack.pushBool(JLong.compareUnsigned(i1, i2) > 0) - F.pure(Left(frame)) - case Asm.I64GtS => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - frame.stack.pushBool(i1 > i2) - F.pure(Left(frame)) - case Asm.I64LeU => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - frame.stack.pushBool(JLong.compareUnsigned(i1, i2) <= 0) - F.pure(Left(frame)) - case Asm.I64LeS => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - frame.stack.pushBool(i1 <= i2) - F.pure(Left(frame)) - case Asm.I64GeU => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - frame.stack.pushBool(JLong.compareUnsigned(i1, i2) >= 0) - F.pure(Left(frame)) - case Asm.I64GeS => - val i2 = frame.stack.popLong() - val i1 = frame.stack.popLong() - frame.stack.pushBool(i1 >= i2) - F.pure(Left(frame)) - case Asm.F32Eq => - val f2 = frame.stack.popFloat() - val f1 = frame.stack.popFloat() - frame.stack.pushBool(f1 == f2) - F.pure(Left(frame)) - case Asm.F32Ne => - val f2 = frame.stack.popFloat() - val f1 = frame.stack.popFloat() - frame.stack.pushBool(f1 != f2) - F.pure(Left(frame)) - case Asm.F32Lt => - val f2 = frame.stack.popFloat() - val f1 = frame.stack.popFloat() - frame.stack.pushBool(f1 < f2) - F.pure(Left(frame)) - case Asm.F32Gt => - val f2 = frame.stack.popFloat() - val f1 = frame.stack.popFloat() - frame.stack.pushBool(f1 > f2) - F.pure(Left(frame)) - case Asm.F32Le => - val f2 = frame.stack.popFloat() - val f1 = frame.stack.popFloat() - frame.stack.pushBool(f1 <= f2) - F.pure(Left(frame)) - case Asm.F32Ge => - val f2 = frame.stack.popFloat() - val f1 = frame.stack.popFloat() - frame.stack.pushBool(f1 >= f2) - F.pure(Left(frame)) - case Asm.F64Eq => - val f2 = frame.stack.popDouble() - val f1 = frame.stack.popDouble() - frame.stack.pushBool(f1 == f2) - F.pure(Left(frame)) - case Asm.F64Ne => - val f2 = frame.stack.popDouble() - val f1 = frame.stack.popDouble() - frame.stack.pushBool(f1 != f2) - F.pure(Left(frame)) - case Asm.F64Lt => - val f2 = frame.stack.popDouble() - val f1 = frame.stack.popDouble() - frame.stack.pushBool(f1 < f2) - F.pure(Left(frame)) - case Asm.F64Gt => - val f2 = frame.stack.popDouble() - val f1 = frame.stack.popDouble() - frame.stack.pushBool(f1 > f2) - F.pure(Left(frame)) - case Asm.F64Le => - val f2 = frame.stack.popDouble() - val f1 = frame.stack.popDouble() - frame.stack.pushBool(f1 <= f2) - F.pure(Left(frame)) - case Asm.F64Ge => - val f2 = frame.stack.popDouble() - val f1 = frame.stack.popDouble() - frame.stack.pushBool(f1 >= f2) - F.pure(Left(frame)) - // === conversion operators === - case Asm.I32WrapI64 => - val l = frame.stack.popLong() - frame.stack.pushInt(I32.wrap(l)) - F.pure(Left(frame)) - case Asm.I64ExtendUI32 => - val i = frame.stack.popInt() - frame.stack.pushLong(I64.extendUi32(i)) - F.pure(Left(frame)) - case Asm.I64ExtendSI32 => - val i = frame.stack.popInt() - frame.stack.pushLong(I64.extendSi32(i)) - F.pure(Left(frame)) - case Asm.I32TruncUF32 => - val f = frame.stack.popFloat() - I32.truncUf32(f) match { - case Right(i) => - frame.stack.pushInt(i) - F.pure(Left(frame)) - case Left(msg) => - F.raiseError(new TrapException(frame, msg)) - } - case Asm.I32TruncSF32 => - val f = frame.stack.popFloat() - I32.truncSf32(f) match { - case Right(i) => - frame.stack.pushInt(i) - F.pure(Left(frame)) - case Left(msg) => - F.raiseError(new TrapException(frame, msg)) - } - case Asm.I32TruncUF64 => - val f = frame.stack.popDouble() - I32.truncUf64(f) match { - case Right(i) => - frame.stack.pushInt(i) - F.pure(Left(frame)) - case Left(msg) => - F.raiseError(new TrapException(frame, msg)) - } - case Asm.I32TruncSF64 => - val f = frame.stack.popDouble() - I32.truncSf64(f) match { - case Right(i) => - frame.stack.pushInt(i) - F.pure(Left(frame)) - case Left(msg) => - F.raiseError(new TrapException(frame, msg)) - } - case Asm.I64TruncUF32 => - val f = frame.stack.popFloat() - I64.truncUf32(f) match { - case Right(l) => - frame.stack.pushLong(l) - F.pure(Left(frame)) - case Left(msg) => - F.raiseError(new TrapException(frame, msg)) - } - case Asm.I64TruncSF32 => - val f = frame.stack.popFloat() - I64.truncSf32(f) match { - case Right(l) => - frame.stack.pushLong(l) - F.pure(Left(frame)) - case Left(msg) => - F.raiseError(new TrapException(frame, msg)) - } - case Asm.I64TruncUF64 => - val f = frame.stack.popDouble() - I64.truncUf64(f) match { - case Right(l) => - frame.stack.pushLong(l) - F.pure(Left(frame)) - case Left(msg) => - F.raiseError(new TrapException(frame, msg)) - } - case Asm.I64TruncSF64 => - val f = frame.stack.popDouble() - I64.truncSf64(f) match { - case Right(l) => - frame.stack.pushLong(l) - F.pure(Left(frame)) - case Left(msg) => - F.raiseError(new TrapException(frame, msg)) - } - case Asm.F32DemoteF64 => - val f = frame.stack.popDouble() - frame.stack.pushFloat(F32.demote(f)) - F.pure(Left(frame)) - case Asm.F64PromoteF32 => - val f = frame.stack.popFloat() - frame.stack.pushDouble(F64.promote(f)) - F.pure(Left(frame)) - case Asm.F32ConvertUI32 => - val i = frame.stack.popInt() - frame.stack.pushFloat(F32.convertUi32(i)) - F.pure(Left(frame)) - case Asm.F32ConvertSI32 => - val i = frame.stack.popInt() - frame.stack.pushFloat(F32.convertSi32(i)) - F.pure(Left(frame)) - case Asm.F32ConvertUI64 => - val l = frame.stack.popLong() - frame.stack.pushFloat(F32.convertUi64(l)) - F.pure(Left(frame)) - case Asm.F32ConvertSI64 => - val l = frame.stack.popLong() - frame.stack.pushFloat(F32.convertSi64(l)) - F.pure(Left(frame)) - case Asm.F64ConvertUI32 => - val i = frame.stack.popInt() - frame.stack.pushDouble(F64.convertUi32(i)) - F.pure(Left(frame)) - case Asm.F64ConvertSI32 => - val i = frame.stack.popInt() - frame.stack.pushDouble(F64.convertSi32(i)) - F.pure(Left(frame)) - case Asm.F64ConvertUI64 => - val l = frame.stack.popLong() - frame.stack.pushDouble(F64.convertUi64(l)) - F.pure(Left(frame)) - case Asm.F64ConvertSI64 => - val l = frame.stack.popLong() - frame.stack.pushDouble(F64.convertSi64(l)) - F.pure(Left(frame)) - case Asm.I32ReinterpretF32 => - val f = frame.stack.popFloat() - frame.stack.pushInt(I32.reinterpret(f)) - F.pure(Left(frame)) - case Asm.I64ReinterpretF64 => - val f = frame.stack.popDouble() - frame.stack.pushLong(I64.reinterpret(f)) - F.pure(Left(frame)) - case Asm.F32ReinterpretI32 => - val i = frame.stack.popInt() - frame.stack.pushFloat(F32.reinterpret(i)) - F.pure(Left(frame)) - case Asm.F64ReinterpretI64 => - val i = frame.stack.popLong() - frame.stack.pushDouble(F64.reinterpret(i)) - F.pure(Left(frame)) - // === parameteric instructions === - case Asm.Drop => - val n = frame.readInt() - frame.stack.drop(n) - F.pure(Left(frame)) - case Asm.Select => - val b = frame.stack.popBool() - val v2 = frame.stack.popValue() - val v1 = frame.stack.popValue() - if (b) - frame.stack.pushValue(v1) - else - frame.stack.pushValue(v2) - F.pure(Left(frame)) - case Asm.LocalGet => - val idx = frame.readInt() - frame.stack.pushValue(frame.locals(idx)) - F.pure(Left(frame)) - case Asm.LocalSet => - val idx = frame.readInt() - val v = frame.stack.popValue() - frame.locals(idx) = v - F.pure(Left(frame)) - case Asm.LocalTee => - val idx = frame.readInt() - val v = frame.stack.peekValue() - frame.locals(idx) = v - F.pure(Left(frame)) - case Asm.GlobalGet => - val idx = frame.readInt() - frame.instance.globals(idx) match { - case i: GlobalInstance[F] => - frame.stack.pushValue(i.rawget) - case g => - frame.stack.pushValue(Value.toRaw(g.get)) - } - F.pure(Left(frame)) - case Asm.GlobalSet => - val idx = frame.readInt() - val v = frame.stack.popValue() - frame.instance.globals(idx) match { - case i: GlobalInstance[F] => - i.rawset(v) - F.pure(Left(frame)) - case g => - g.set(Value.fromRaw(g.tpe.tpe, v)) >> F.pure(Left(frame)) - } - // === memory instructions === - case Asm.I32Load => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 4 > mem.size) - F.raiseError(new TrapException(frame, "out of bounds memory access")) - else - mem.readInt(ea).map { c => - frame.stack.pushInt(c) - Left(frame) + case Asm.I32RemU => + val i2 = thread.popInt() + val i1 = thread.popInt() + if (i2 == 0) { + F.raiseError[Res](new TrapException(thread, "integer divide by zero")) + } else { + thread.pushInt(JInt.remainderUnsigned(i1, i2)) + continue } - case Asm.I32Load8U => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 1 > mem.size) - F.raiseError(new TrapException(frame, "out of bounds memory access")) - else - mem.readByte(ea).map { c => - frame.stack.pushInt(c & 0xff) - Left(frame) + case Asm.I32RemS => + val i2 = thread.popInt() + val i1 = thread.popInt() + if (i2 == 0) { + F.raiseError[Res](new TrapException(thread, "integer divide by zero")) + } else { + thread.pushInt(i1 % i2) + continue } - case Asm.I32Load8S => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 1 > mem.size) - F.raiseError(new TrapException(frame, "out of bounds memory access")) - else - mem.readByte(ea).map { c => - frame.stack.pushInt(c) - Left(frame) + case Asm.I32And => + val i2 = thread.popInt() + val i1 = thread.popInt() + thread.pushInt(i1 & i2) + continue + case Asm.I32Or => + val i2 = thread.popInt() + val i1 = thread.popInt() + thread.pushInt(i1 | i2) + continue + case Asm.I32Xor => + val i2 = thread.popInt() + val i1 = thread.popInt() + thread.pushInt(i1 ^ i2) + continue + case Asm.I32Shl => + val i2 = thread.popInt() % 32 + val i1 = thread.popInt() + thread.pushInt(i1 << i2) + continue + case Asm.I32ShrU => + val i2 = thread.popInt() % 32 + val i1 = thread.popInt() + thread.pushInt(i1 >>> i2) + continue + case Asm.I32ShrS => + val i2 = thread.popInt() % 32 + val i1 = thread.popInt() + thread.pushInt(i1 >> i2) + continue + case Asm.I32Rotl => + val i2 = thread.popInt() % 32 + val i1 = thread.popInt() + thread.pushInt(JInt.rotateLeft(i1, i2)) + continue + case Asm.I32Rotr => + val i2 = thread.popInt() % 32 + val i1 = thread.popInt() + thread.pushInt(JInt.rotateRight(i1, i2)) + continue + case Asm.I64Add => + val i2 = thread.popLong() + val i1 = thread.popLong() + thread.pushLong(i1 + i2) + continue + case Asm.I64Sub => + val i2 = thread.popLong() + val i1 = thread.popLong() + thread.pushLong(i1 - i2) + continue + case Asm.I64Mul => + val i2 = thread.popLong() + val i1 = thread.popLong() + thread.pushLong(i1 * i2) + continue + case Asm.I64DivU => + val i2 = thread.popLong() + val i1 = thread.popLong() + if (i2 == 0) { + F.raiseError[Res](new TrapException(thread, "integer divide by zero")) + } else { + thread.pushLong(JLong.divideUnsigned(i1, i2)) + continue } - case Asm.I32Load16U => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 2 > mem.size) - F.raiseError(new TrapException(frame, "out of bounds memory access")) - else - mem.readShort(ea).map { c => - frame.stack.pushInt(c & 0xffff) - Left(frame) + case Asm.I64DivS => + val i2 = thread.popLong() + val i1 = thread.popLong() + if (i2 == 0) { + F.raiseError[Res](new TrapException(thread, "integer divide by zero")) + } else if (i1 == Long.MinValue && i2 == -1l) { + F.raiseError[Res](new TrapException(thread, "integer overflow")) + } else { + val res = i1 / i2 + if (i1 >= 0 && i2 > 0 && res < 0) { + F.raiseError[Res](new TrapException(thread, "overflow")) + } else { + thread.pushLong(i1 / i2) + continue + } } - case Asm.I32Load16S => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 2 > mem.size) - F.raiseError(new TrapException(frame, "out of bounds memory access")) - else - mem.readShort(ea).map { c => - frame.stack.pushInt(c) - Left(frame) + case Asm.I64RemU => + val i2 = thread.popLong() + val i1 = thread.popLong() + if (i2 == 0) { + F.raiseError[Res](new TrapException(thread, "integer divide by zero")) + } else { + thread.pushLong(JLong.remainderUnsigned(i1, i2)) + continue } - case Asm.I64Load => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 8 > mem.size) - F.raiseError(new TrapException(frame, "out of bounds memory access")) - else - mem.readLong(ea).map { c => - frame.stack.pushLong(c) - Left(frame) + case Asm.I64RemS => + val i2 = thread.popLong() + val i1 = thread.popLong() + if (i2 == 0) { + F.raiseError[Res](new TrapException(thread, "integer divide by zero")) + } else { + thread.pushLong(i1 % i2) + continue } - case Asm.I64Load8U => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 1 > mem.size) - F.raiseError(new TrapException(frame, "out of bounds memory access")) - else - mem.readByte(ea).map { c => - frame.stack.pushLong(c & 0xffl) - Left(frame) + case Asm.I64And => + val i2 = thread.popLong() + val i1 = thread.popLong() + thread.pushLong(i1 & i2) + continue + case Asm.I64Or => + val i2 = thread.popLong() + val i1 = thread.popLong() + thread.pushLong(i1 | i2) + continue + case Asm.I64Xor => + val i2 = thread.popLong() + val i1 = thread.popLong() + thread.pushLong(i1 ^ i2) + continue + case Asm.I64Shl => + val i2 = thread.popLong() % 64 + val i1 = thread.popLong() + thread.pushLong(i1 << i2) + continue + case Asm.I64ShrU => + val i2 = thread.popLong() % 64 + val i1 = thread.popLong() + thread.pushLong(i1 >>> i2) + continue + case Asm.I64ShrS => + val i2 = thread.popLong() % 64 + val i1 = thread.popLong() + thread.pushLong(i1 >> i2) + continue + case Asm.I64Rotl => + val i2 = (thread.popLong() % 64).toInt + val i1 = thread.popLong() + thread.pushLong(JLong.rotateLeft(i1, i2)) + continue + case Asm.I64Rotr => + val i2 = (thread.popLong() % 64).toInt + val i1 = thread.popLong() + thread.pushLong(JLong.rotateRight(i1, i2)) + continue + case Asm.F32Add => + val f2 = thread.popFloat() + val f1 = thread.popFloat() + thread.pushFloat(f1 + f2) + continue + case Asm.F32Sub => + val f2 = thread.popFloat() + val f1 = thread.popFloat() + thread.pushFloat(f1 - f2) + continue + case Asm.F32Mul => + val f2 = thread.popFloat() + val f1 = thread.popFloat() + thread.pushFloat(f1 * f2) + continue + case Asm.F32Div => + val f2 = thread.popFloat() + val f1 = thread.popFloat() + thread.pushFloat(f1 / f2) + continue + case Asm.F32Min => + val f2 = thread.popFloat() + val f1 = thread.popFloat() + thread.pushFloat(StrictMath.min(f1, f2)) + continue + case Asm.F32Max => + val f2 = thread.popFloat() + val f1 = thread.popFloat() + thread.pushFloat(StrictMath.max(f1, f2)) + continue + case Asm.F32Copysign => + val f2 = thread.popFloat() + val f1 = thread.popFloat() + thread.pushFloat(Math.copySign(f1, f2)) + continue + case Asm.F64Add => + val f2 = thread.popDouble() + val f1 = thread.popDouble() + thread.pushDouble(f1 + f2) + continue + case Asm.F64Sub => + val f2 = thread.popDouble() + val f1 = thread.popDouble() + thread.pushDouble(f1 - f2) + continue + case Asm.F64Mul => + val f2 = thread.popDouble() + val f1 = thread.popDouble() + thread.pushDouble(f1 * f2) + continue + case Asm.F64Div => + val f2 = thread.popDouble() + val f1 = thread.popDouble() + thread.pushDouble(f1 / f2) + continue + case Asm.F64Min => + val f2 = thread.popDouble() + val f1 = thread.popDouble() + thread.pushDouble(StrictMath.min(f1, f2)) + continue + case Asm.F64Max => + val f2 = thread.popDouble() + val f1 = thread.popDouble() + thread.pushDouble(StrictMath.max(f1, f2)) + continue + case Asm.F64Copysign => + val f2 = thread.popDouble() + val f1 = thread.popDouble() + thread.pushDouble(Math.copySign(f1, f2)) + continue + // === test operators === + case Asm.I32Eqz => + val i = thread.popInt() + thread.pushBool(i == 0) + continue + case Asm.I64Eqz => + val i = thread.popLong() + thread.pushBool(i == 0) + continue + // === relation operators === + case Asm.I32Eq => + val i2 = thread.popInt() + val i1 = thread.popInt() + thread.pushBool(i1 == i2) + continue + case Asm.I32Ne => + val i2 = thread.popInt() + val i1 = thread.popInt() + thread.pushBool(i1 != i2) + continue + case Asm.I32LtU => + val i2 = thread.popInt() + val i1 = thread.popInt() + thread.pushBool(JInt.compareUnsigned(i1, i2) < 0) + continue + case Asm.I32LtS => + val i2 = thread.popInt() + val i1 = thread.popInt() + thread.pushBool(i1 < i2) + continue + case Asm.I32GtU => + val i2 = thread.popInt() + val i1 = thread.popInt() + thread.pushBool(JInt.compareUnsigned(i1, i2) > 0) + continue + case Asm.I32GtS => + val i2 = thread.popInt() + val i1 = thread.popInt() + thread.pushBool(i1 > i2) + continue + case Asm.I32LeU => + val i2 = thread.popInt() + val i1 = thread.popInt() + thread.pushBool(JInt.compareUnsigned(i1, i2) <= 0) + continue + case Asm.I32LeS => + val i2 = thread.popInt() + val i1 = thread.popInt() + thread.pushBool(i1 <= i2) + continue + case Asm.I32GeU => + val i2 = thread.popInt() + val i1 = thread.popInt() + thread.pushBool(JInt.compareUnsigned(i1, i2) >= 0) + continue + case Asm.I32GeS => + val i2 = thread.popInt() + val i1 = thread.popInt() + thread.pushBool(i1 >= i2) + continue + case Asm.I64Eq => + val i2 = thread.popLong() + val i1 = thread.popLong() + thread.pushBool(i1 == i2) + continue + case Asm.I64Ne => + val i2 = thread.popLong() + val i1 = thread.popLong() + thread.pushBool(i1 != i2) + continue + case Asm.I64LtU => + val i2 = thread.popLong() + val i1 = thread.popLong() + thread.pushBool(JLong.compareUnsigned(i1, i2) < 0) + continue + case Asm.I64LtS => + val i2 = thread.popLong() + val i1 = thread.popLong() + thread.pushBool(i1 < i2) + continue + case Asm.I64GtU => + val i2 = thread.popLong() + val i1 = thread.popLong() + thread.pushBool(JLong.compareUnsigned(i1, i2) > 0) + continue + case Asm.I64GtS => + val i2 = thread.popLong() + val i1 = thread.popLong() + thread.pushBool(i1 > i2) + continue + case Asm.I64LeU => + val i2 = thread.popLong() + val i1 = thread.popLong() + thread.pushBool(JLong.compareUnsigned(i1, i2) <= 0) + continue + case Asm.I64LeS => + val i2 = thread.popLong() + val i1 = thread.popLong() + thread.pushBool(i1 <= i2) + continue + case Asm.I64GeU => + val i2 = thread.popLong() + val i1 = thread.popLong() + thread.pushBool(JLong.compareUnsigned(i1, i2) >= 0) + continue + case Asm.I64GeS => + val i2 = thread.popLong() + val i1 = thread.popLong() + thread.pushBool(i1 >= i2) + continue + case Asm.F32Eq => + val f2 = thread.popFloat() + val f1 = thread.popFloat() + thread.pushBool(f1 == f2) + continue + case Asm.F32Ne => + val f2 = thread.popFloat() + val f1 = thread.popFloat() + thread.pushBool(f1 != f2) + continue + case Asm.F32Lt => + val f2 = thread.popFloat() + val f1 = thread.popFloat() + thread.pushBool(f1 < f2) + continue + case Asm.F32Gt => + val f2 = thread.popFloat() + val f1 = thread.popFloat() + thread.pushBool(f1 > f2) + continue + case Asm.F32Le => + val f2 = thread.popFloat() + val f1 = thread.popFloat() + thread.pushBool(f1 <= f2) + continue + case Asm.F32Ge => + val f2 = thread.popFloat() + val f1 = thread.popFloat() + thread.pushBool(f1 >= f2) + continue + case Asm.F64Eq => + val f2 = thread.popDouble() + val f1 = thread.popDouble() + thread.pushBool(f1 == f2) + continue + case Asm.F64Ne => + val f2 = thread.popDouble() + val f1 = thread.popDouble() + thread.pushBool(f1 != f2) + continue + case Asm.F64Lt => + val f2 = thread.popDouble() + val f1 = thread.popDouble() + thread.pushBool(f1 < f2) + continue + case Asm.F64Gt => + val f2 = thread.popDouble() + val f1 = thread.popDouble() + thread.pushBool(f1 > f2) + continue + case Asm.F64Le => + val f2 = thread.popDouble() + val f1 = thread.popDouble() + thread.pushBool(f1 <= f2) + continue + case Asm.F64Ge => + val f2 = thread.popDouble() + val f1 = thread.popDouble() + thread.pushBool(f1 >= f2) + continue + // === conversion operators === + case Asm.I32WrapI64 => + val l = thread.popLong() + thread.pushInt(I32.wrap(l)) + continue + case Asm.I64ExtendUI32 => + val i = thread.popInt() + thread.pushLong(I64.extendUi32(i)) + continue + case Asm.I64ExtendSI32 => + val i = thread.popInt() + thread.pushLong(I64.extendSi32(i)) + continue + case Asm.I32TruncUF32 => + val f = thread.popFloat() + I32.truncUf32(f) match { + case Right(i) => + thread.pushInt(i) + continue + case Left(msg) => + F.raiseError[Res](new TrapException(thread, msg)) } - case Asm.I64Load8S => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 1 > mem.size) - F.raiseError(new TrapException(frame, "out of bounds memory access")) - else - mem.readByte(ea).map { c => - frame.stack.pushLong(c) - Left(frame) + case Asm.I32TruncSF32 => + val f = thread.popFloat() + I32.truncSf32(f) match { + case Right(i) => + thread.pushInt(i) + continue + case Left(msg) => + F.raiseError[Res](new TrapException(thread, msg)) } - case Asm.I64Load16U => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 2 > mem.size) - F.raiseError(new TrapException(frame, "out of bounds memory access")) - else - mem.readShort(ea).map { c => - frame.stack.pushLong(c & 0xffffl) - Left(frame) + case Asm.I32TruncUF64 => + val f = thread.popDouble() + I32.truncUf64(f) match { + case Right(i) => + thread.pushInt(i) + continue + case Left(msg) => + F.raiseError[Res](new TrapException(thread, msg)) } - case Asm.I64Load16S => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 2 > mem.size) - F.raiseError(new TrapException(frame, "out of bounds memory access")) - else - mem.readShort(ea).map { c => - frame.stack.pushLong(c) - Left(frame) + case Asm.I32TruncSF64 => + val f = thread.popDouble() + I32.truncSf64(f) match { + case Right(i) => + thread.pushInt(i) + continue + case Left(msg) => + F.raiseError[Res](new TrapException(thread, msg)) } - case Asm.I64Load32U => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 4 > mem.size) - F.raiseError(new TrapException(frame, "out of bounds memory access")) - else - mem.readInt(ea).map { c => - frame.stack.pushLong(c & 0xffffffffl) - Left(frame) + case Asm.I64TruncUF32 => + val f = thread.popFloat() + I64.truncUf32(f) match { + case Right(l) => + thread.pushLong(l) + continue + case Left(msg) => + F.raiseError[Res](new TrapException(thread, msg)) } - case Asm.I64Load32S => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 4 > mem.size) - F.raiseError(new TrapException(frame, "out of bounds memory access")) - else - mem.readInt(ea).map { c => - frame.stack.pushLong(c) - Left(frame) + case Asm.I64TruncSF32 => + val f = thread.popFloat() + I64.truncSf32(f) match { + case Right(l) => + thread.pushLong(l) + continue + case Left(msg) => + F.raiseError[Res](new TrapException(thread, msg)) } - case Asm.F32Load => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 4 > mem.size) - F.raiseError(new TrapException(frame, "out of bounds memory access")) - else - mem.readFloat(ea).map { c => - frame.stack.pushFloat(c) - Left(frame) + case Asm.I64TruncUF64 => + val f = thread.popDouble() + I64.truncUf64(f) match { + case Right(l) => + thread.pushLong(l) + continue + case Left(msg) => + F.raiseError[Res](new TrapException(thread, msg)) } - case Asm.F64Load => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 8 > mem.size) - F.raiseError(new TrapException(frame, "out of bounds memory access")) - else - mem.readDouble(ea).map { c => - frame.stack.pushDouble(c) - Left(frame) + case Asm.I64TruncSF64 => + val f = thread.popDouble() + I64.truncSf64(f) match { + case Right(l) => + thread.pushLong(l) + continue + case Left(msg) => + F.raiseError[Res](new TrapException(thread, msg)) } - case Asm.I32Store => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val c = frame.stack.popInt() - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 4 > mem.size) - F.raiseError(new TrapException(frame, "out of bounds memory access")) - else - mem.writeInt(ea, c).as(Left(frame)) - case Asm.I32Store8 => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val c = frame.stack.popInt() - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 1 > mem.size) { - F.raiseError(new TrapException(frame, "out of bounds memory access")) - } else { - val c1 = (c % (1 << 8)).toByte - mem.writeByte(ea, c1).as(Left(frame)) - } - case Asm.I32Store16 => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val c = frame.stack.popInt() - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 2 > mem.size) { - F.raiseError(new TrapException(frame, "out of bounds memory access")) - } else { - val c1 = (c % (1 << 16)).toShort - mem.writeShort(ea, c1).as(Left(frame)) - } - case Asm.I64Store => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val c = frame.stack.popLong() - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 8 > mem.size) { - F.raiseError(new TrapException(frame, "out of bounds memory access")) - } else { - mem.writeLong(ea, c).as(Left(frame)) - } - case Asm.I64Store8 => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val c = frame.stack.popLong() - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 1 > mem.size) { - F.raiseError(new TrapException(frame, "out of bounds memory access")) - } else { - val c1 = (c % (1l << 8)).toByte - mem.writeByte(ea, c1).as(Left(frame)) - } - case Asm.I64Store16 => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val c = frame.stack.popLong() - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 2 > mem.size) { - F.raiseError(new TrapException(frame, "out of bounds memory access")) - } else { - val c1 = (c % (1l << 16)).toShort - mem.writeShort(ea, c1).as(Left(frame)) - } - case Asm.I64Store32 => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val c = frame.stack.popLong() - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 4 > mem.size) { - F.raiseError(new TrapException(frame, "out of bounds memory access")) - } else { - val c1 = (c % (1l << 32)).toInt - mem.writeInt(ea, c1).as(Left(frame)) - } - case Asm.F32Store => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val c = frame.stack.popFloat() - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 4 > mem.size) { - F.raiseError(new TrapException(frame, "out of bounds memory access")) - } else { - mem.writeFloat(ea, c).as(Left(frame)) - } - case Asm.F64Store => - val align = frame.readInt() - val offset = frame.readInt() - val mem = frame.instance.memories(0) - val c = frame.stack.popDouble() - val i = frame.stack.popInt() - val ea = i + offset - if (offset < 0 || ea < 0 || ea + 8 > mem.size) { - F.raiseError(new TrapException(frame, "out of bounds memory access")) - } else { - mem.writeDouble(ea, c).as(Left(frame)) - } - case Asm.MemorySize => - val mem = frame.instance.memories(0) - val sz = mem.size / pageSize - frame.stack.pushInt(sz) - F.pure(Left(frame)) - case Asm.MemoryGrow => - val mem = frame.instance.memories(0) - val sz = mem.size / pageSize - val n = frame.stack.popInt() - mem - .grow(n) - .map { - case true => frame.stack.pushInt(sz) - case flase => frame.stack.pushInt(-1) + case Asm.F32DemoteF64 => + val f = thread.popDouble() + thread.pushFloat(F32.demote(f)) + continue + case Asm.F64PromoteF32 => + val f = thread.popFloat() + thread.pushDouble(F64.promote(f)) + continue + case Asm.F32ConvertUI32 => + val i = thread.popInt() + thread.pushFloat(F32.convertUi32(i)) + continue + case Asm.F32ConvertSI32 => + val i = thread.popInt() + thread.pushFloat(F32.convertSi32(i)) + continue + case Asm.F32ConvertUI64 => + val l = thread.popLong() + thread.pushFloat(F32.convertUi64(l)) + continue + case Asm.F32ConvertSI64 => + val l = thread.popLong() + thread.pushFloat(F32.convertSi64(l)) + continue + case Asm.F64ConvertUI32 => + val i = thread.popInt() + thread.pushDouble(F64.convertUi32(i)) + continue + case Asm.F64ConvertSI32 => + val i = thread.popInt() + thread.pushDouble(F64.convertSi32(i)) + continue + case Asm.F64ConvertUI64 => + val l = thread.popLong() + thread.pushDouble(F64.convertUi64(l)) + continue + case Asm.F64ConvertSI64 => + val l = thread.popLong() + thread.pushDouble(F64.convertSi64(l)) + continue + case Asm.I32ReinterpretF32 => + val f = thread.popFloat() + thread.pushInt(I32.reinterpret(f)) + continue + case Asm.I64ReinterpretF64 => + val f = thread.popDouble() + thread.pushLong(I64.reinterpret(f)) + continue + case Asm.F32ReinterpretI32 => + val i = thread.popInt() + thread.pushFloat(F32.reinterpret(i)) + continue + case Asm.F64ReinterpretI64 => + val i = thread.popLong() + thread.pushDouble(F64.reinterpret(i)) + continue + // === parameteric instructions === + case Asm.Drop => + val n = thread.readInt() + thread.drop(n) + continue + case Asm.Select => + val b = thread.popBool() + val v2 = thread.popValue() + val v1 = thread.popValue() + if (b) + thread.pushValue(v1) + else + thread.pushValue(v2) + continue + case Asm.LocalGet => + val idx = thread.readInt() + thread.pushValue(thread.local(idx)) + continue + case Asm.LocalSet => + val idx = thread.readInt() + val v = thread.popValue() + thread.setLocal(idx, v) + continue + case Asm.LocalTee => + val idx = thread.readInt() + val v = thread.peekValue() + thread.setLocal(idx, v) + continue + case Asm.GlobalGet => + val idx = thread.readInt() + thread.global(idx) match { + case i: GlobalInstance[F] => + thread.pushValue(i.rawget) + case g => + thread.pushValue(Value.toRaw(g.get)) } - .as(Left(frame)) - // === control instructions === - case Asm.Nop => - F.pure(Left(frame)) - case Asm.Unreachable => - F.raiseError(new TrapException(frame, "unreachable executed")) - case Asm.Jump => - // next comes the jump address - val addr = frame.readInt() - frame.pc = addr - F.pure(Left(frame)) - case Asm.JumpIf => - // read the condition from the stack - val c = frame.stack.popBool() - if (c) { - // only jump if condition is true - // read the address - val addr = frame.readInt() - // and jump - frame.pc = addr - } else { - // fix the pc and continue - frame.pc += 4 - } - F.pure(Left(frame)) - case Asm.Br => - // next comes the label arity - val arity = frame.readInt() - // then the rest to drop - val drop = frame.readInt() - // and finally the jump address - val addr = frame.readInt() - br(frame, arity, drop, addr) - F.pure(Left(frame)) - case Asm.BrIf => - val c = frame.stack.popBool() - if (c) { - // only break if condition is true + continue + case Asm.GlobalSet => + val idx = thread.readInt() + val v = thread.popValue() + thread.global(idx) match { + case i: GlobalInstance[F] => + i.rawset(v) + continue + case g => + g.set(Value.fromRaw(g.tpe.tpe, v)) >> continue + } + // === memory instructions === + case Asm.I32Load => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 4 > mem.size) + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + else + mem.readInt(ea).flatMap { c => + thread.pushInt(c) + continue + } + case Asm.I32Load8U => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 1 > mem.size) + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + else + mem.readByte(ea).flatMap { c => + thread.pushInt(c & 0xff) + continue + } + case Asm.I32Load8S => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 1 > mem.size) + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + else + mem.readByte(ea).flatMap { c => + thread.pushInt(c) + continue + } + case Asm.I32Load16U => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 2 > mem.size) + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + else + mem.readShort(ea).flatMap { c => + thread.pushInt(c & 0xffff) + continue + } + case Asm.I32Load16S => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 2 > mem.size) + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + else + mem.readShort(ea).flatMap { c => + thread.pushInt(c) + continue + } + case Asm.I64Load => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 8 > mem.size) + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + else + mem.readLong(ea).flatMap { c => + thread.pushLong(c) + continue + } + case Asm.I64Load8U => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 1 > mem.size) + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + else + mem.readByte(ea).flatMap { c => + thread.pushLong(c & 0xffl) + continue + } + case Asm.I64Load8S => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 1 > mem.size) + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + else + mem.readByte(ea).flatMap { c => + thread.pushLong(c) + continue + } + case Asm.I64Load16U => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 2 > mem.size) + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + else + mem.readShort(ea).flatMap { c => + thread.pushLong(c & 0xffffl) + continue + } + case Asm.I64Load16S => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 2 > mem.size) + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + else + mem.readShort(ea).flatMap { c => + thread.pushLong(c) + continue + } + case Asm.I64Load32U => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 4 > mem.size) + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + else + mem.readInt(ea).flatMap { c => + thread.pushLong(c & 0xffffffffl) + continue + } + case Asm.I64Load32S => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 4 > mem.size) + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + else + mem.readInt(ea).flatMap { c => + thread.pushLong(c) + continue + } + case Asm.F32Load => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 4 > mem.size) + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + else + mem.readFloat(ea).flatMap { c => + thread.pushFloat(c) + continue + } + case Asm.F64Load => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 8 > mem.size) + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + else + mem.readDouble(ea).flatMap { c => + thread.pushDouble(c) + continue + } + case Asm.I32Store => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val c = thread.popInt() + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 4 > mem.size) + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + else + mem.writeInt(ea, c) >> continue + case Asm.I32Store8 => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val c = thread.popInt() + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 1 > mem.size) { + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + } else { + val c1 = (c % (1 << 8)).toByte + mem.writeByte(ea, c1) >> continue + } + case Asm.I32Store16 => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val c = thread.popInt() + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 2 > mem.size) { + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + } else { + val c1 = (c % (1 << 16)).toShort + mem.writeShort(ea, c1) >> continue + } + case Asm.I64Store => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val c = thread.popLong() + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 8 > mem.size) { + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + } else { + mem.writeLong(ea, c) >> continue + } + case Asm.I64Store8 => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val c = thread.popLong() + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 1 > mem.size) { + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + } else { + val c1 = (c % (1l << 8)).toByte + mem.writeByte(ea, c1) >> continue + } + case Asm.I64Store16 => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val c = thread.popLong() + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 2 > mem.size) { + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + } else { + val c1 = (c % (1l << 16)).toShort + mem.writeShort(ea, c1) >> continue + } + case Asm.I64Store32 => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val c = thread.popLong() + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 4 > mem.size) { + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + } else { + val c1 = (c % (1l << 32)).toInt + mem.writeInt(ea, c1) >> continue + } + case Asm.F32Store => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val c = thread.popFloat() + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 4 > mem.size) { + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + } else { + mem.writeFloat(ea, c) >> continue + } + case Asm.F64Store => + val align = thread.readInt() + val offset = thread.readInt() + val mem = thread.memory(0) + val c = thread.popDouble() + val i = thread.popInt() + val ea = i + offset + if (offset < 0 || ea < 0 || ea + 8 > mem.size) { + F.raiseError[Res](new TrapException(thread, "out of bounds memory access")) + } else { + mem.writeDouble(ea, c) >> continue + } + case Asm.MemorySize => + val mem = thread.memory(0) + val sz = mem.size / pageSize + thread.pushInt(sz) + continue + case Asm.MemoryGrow => + val mem = thread.memory(0) + val sz = mem.size / pageSize + val n = thread.popInt() + mem + .grow(n) + .map { + case true => thread.pushInt(sz) + case flase => thread.pushInt(-1) + } >> continue + // === control instructions === + case Asm.Nop => + continue + case Asm.Unreachable => + F.raiseError[Res](new TrapException(thread, "unreachable executed")) + case Asm.Jump => + // next comes the jump address + val addr = thread.readInt() + thread.pc = addr + continue + case Asm.JumpIf => + // read the condition from the stack + val c = thread.popBool() + if (c) { + // only jump if condition is true + // read the address + val addr = thread.readInt() + // and jump + thread.pc = addr + } else { + // fix the pc and continue + thread.pc += 4 + } + continue + case Asm.Br => // next comes the label arity - val arity = frame.readInt() + val arity = thread.readInt() // then the rest to drop - val drop = frame.readInt() + val drop = thread.readInt() // and finally the jump address - val addr = frame.readInt() - br(frame, arity, drop, addr) - } else { - // otherwise increment program counter and continue - frame.pc += 12 - } - F.pure(Left(frame)) - case Asm.BrTable => - // next int gives the number of labels to come - val nl = frame.readInt() - // get the label index from stack - val i = frame.stack.popInt() - // fix the index to default to the default label - val idx = if (i >= 0 && i < nl) i else nl - // retrieve the correct label information at that index - frame.pc += idx * 12 - val arity = frame.readInt() - val drop = frame.readInt() - val addr = frame.readInt() - br(frame, arity, drop, addr) - F.pure(Left(frame)) - case Asm.Return => - val values = frame.stack.popValues(frame.arity) - // pop the frame to get the parent - val frame1 = frame.stack.popFrame() - if (frame1.isToplevel) { - // this is the top-level call, return, as we are done - F.pure(Right(values.headOption)) - } else { - // push values back to the frame - frame1.stack.pushValues(values) - // continue where we left the frame - F.pure(Left(frame1)) - } - case Asm.Call => - // next integer is the function index - val fidx = frame.readInt() - val f = frame.instance.funcs(fidx) - invoke(frame, f) - case Asm.CallIndirect => - // next integer is the typ index - val tidx = frame.readInt() - val tab = frame.instance.tables(0) - val expectedt = frame.instance.module.types(tidx) - val i = frame.stack.popInt() - if (i < 0 || i >= tab.size) { - F.raiseError(new TrapException(frame, "undefined element")) - } else if (tab(i) == null) { - F.raiseError(new TrapException(frame, s"uninitialized element $i")) - } else { - val f = tab(i) - val actualt = f.tpe - if (expectedt != actualt) { - F.raiseError(new TrapException(frame, "indirect call type mismatch")) + val addr = thread.readInt() + br(thread, arity, drop, addr) + continue + case Asm.BrIf => + val c = thread.popBool() + if (c) { + // only break if condition is true + // next comes the label arity + val arity = thread.readInt() + // then the rest to drop + val drop = thread.readInt() + // and finally the jump address + val addr = thread.readInt() + br(thread, arity, drop, addr) } else { - invoke(frame, f) + // otherwise increment program counter and continue + thread.pc += 12 } - } - case opcode => - F.raiseError(new TrapException(frame, s"unknown opcode 0x${opcode.toHexString}")) + continue + case Asm.BrTable => + // next int gives the number of labels to come + val nl = thread.readInt() + // get the label index from stack + val i = thread.popInt() + // fix the index to default to the default label + val idx = if (i >= 0 && i < nl) i else nl + // retrieve the correct label information at that index + thread.pc += idx * 12 + val arity = thread.readInt() + val drop = thread.readInt() + val addr = thread.readInt() + br(thread, arity, drop, addr) + continue + case Asm.Return => + val values = thread.popValues(thread.arity) + // pop the thread to get the parent + thread.popFrame() + if (thread.isToplevel) { + // this is the top-level call, return, as we are done + F.pure(values.headOption.asRight[Unit]) + } else { + // push values back to the thread + thread.pushValues(values) + // continue where we left the thread + continue + } + case Asm.Call => + // next integer is the function index + val fidx = thread.readInt() + val f = thread.func(fidx) + invoke(thread, f) + case Asm.CallIndirect => + // next integer is the typ index + val tidx = thread.readInt() + val tab = thread.table(0) + val expectedt = thread.module.types(tidx) + val i = thread.popInt() + if (i < 0 || i >= tab.size) { + F.raiseError[Res](new TrapException(thread, "undefined element")) + } else if (tab(i) == null) { + F.raiseError[Res](new TrapException(thread, s"uninitialized element $i")) + } else { + val f = tab(i) + val actualt = f.tpe + if (expectedt != actualt) { + F.raiseError[Res](new TrapException(thread, "indirect call type mismatch")) + } else { + invoke(thread, f) + } + } + case opcode => + F.raiseError[Res](new TrapException(thread, s"unknown opcode 0x${opcode.toHexString}")) + } + } + .adaptError { + case e: ArrayIndexOutOfBoundsException => new StackOverflowException(thread, e) } - } - private def br(frame: Frame[F], arity: Int, drop: Int, addr: Int): Unit = { - val res = frame.stack.popValues(arity) - frame.stack.drop(drop) - frame.stack.pushValues(res) - frame.pc = addr + private def br(thread: ThreadFrame[F], arity: Int, drop: Int, addr: Int): Unit = { + val res = thread.popValues(arity) + thread.drop(drop) + thread.pushValues(res) + thread.pc = addr } - private def invoke(frame: Frame[F], f: Function[F])( - implicit F: MonadError[F, Throwable]): F[Either[Frame[F], Option[Long]]] = + private def invoke(thread: ThreadFrame[F], f: Function[F])( + implicit F: MonadError[F, Throwable]): F[Either[Unit, Option[Long]]] = f match { - case FunctionInstance(tpe, locals, code, inst) => - val ilocals = Array.ofDim[Long](locals.size + tpe.params.size) - val zlocals = Array.fill[Long](locals.size)(0l) - Array.copy(zlocals, 0, ilocals, tpe.params.size, zlocals.length) - // pop the parameters from the stack - val params = frame.stack.popValues(tpe.params.size).toArray - Array.copy(params, 0, ilocals, 0, params.length) - frame.stack.pushFrame(tpe.t.size, code, ilocals, inst).map(Left(_)) + case inst @ FunctionInstance(_, _, _, _) => + // parameters are on top of the stack + thread.pushFrame(inst) + continue case _ => // pop the parameters from the stack - val rawparams = frame.stack.popValues(f.tpe.params.size) + val rawparams = thread.popValues(f.tpe.params.size) // convert parameters according to the type defined for function parameters val params = f.tpe.params.zip(rawparams).map { case (tpe, v) => Value.fromRaw(tpe, v) } // invoke the host function with the parameters - f.invoke(params.toVector, frame.instance.memories.headOption).map { res => - if (frame.isToplevel) { - Right(res.map(Value.toRaw(_))) + f.invoke(params.toVector, thread.memoryOpt(0)).flatMap { res => + if (thread.isToplevel) { + F.pure(Right(res.map(Value.toRaw(_)))) } else { - res.foreach(v => frame.stack.pushValue(Value.toRaw(v))) - Left(frame) + res.foreach(v => thread.pushValue(Value.toRaw(v))) + continue } } } diff --git a/runtime/src/swam/runtime/internals/interpreter/low/Stack.scala b/runtime/src/swam/runtime/internals/interpreter/low/Stack.scala new file mode 100644 index 00000000..bb9f725c --- /dev/null +++ b/runtime/src/swam/runtime/internals/interpreter/low/Stack.scala @@ -0,0 +1,294 @@ +/* + * Copyright 2018 Lucas Satabin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package swam +package runtime +package internals +package interpreter +package low + +import instance._ +import config._ + +import cats._ +import cats.implicits._ + +import scala.annotation.tailrec + +import java.lang.{Float => JFloat, Double => JDouble} + +import java.nio.ByteBuffer + +import scala.language.higherKinds + +/** Low-level structures that represents the stack and registers of + * a thread in the interpreter. + * Each thread gets its own stack. + * When function calls are made, a new frame is pushed on the stack + * with saved registers to allow for returns. + * + * Following registers are maintained: + * - `pc` the program counter that points to the next instruction to execute + * - `fp` the frame poiter that points to the current frame base on the stack + * - `tp` the tp of the stack + * + * The stack structure is as follows: + * {{{ + * . . + * . . + * | | + * +-----------+ + * | local 0 | + * +-----------+ + * | local 1 | + * +-----------+ + * | ... | + * . . + * . . + * +-----------+ + * | local n | + * +-----------+ + * | nb locals | <- fp + * +-----------+ + * | arity | + * +-----------+ + * | return pc | + * +-----------+ + * | return fp | + * +-----------+ + * | ... | + * . . + * . . + * +-----------+ + * | | <- tp + * +-----------+ + * | | + * . . + * . . + * }}} + * + */ +private class ThreadFrame[F[_]](conf: LowLevelStackConfiguration, baseInstance: Instance[F]) extends StackFrame { + + private val stack = Array.ofDim[Long](conf.size.toBytes.toInt / 8) + + private var fp = 0 + + private var tp = 0 + + var pc = 0 + + private var code: ByteBuffer = _ + + private var instances: List[FunctionInstance[F]] = Nil + + def instance: FunctionInstance[F] = instances.head + + def arity: Int = + (stack(fp + 1) & 0xffffffff).toInt + + def nbLocals: Int = + (stack(fp) & 0xffffffff).toInt + + def isToplevel: Boolean = + instances.isEmpty + + def pushFrame(fun: FunctionInstance[F]): Unit = { + val nbLocals = fun.locals.size + if (tp + nbLocals + 4 > stack.size) + throw new StackOverflowException(this) + // push the current function instance on the instance stack + instances = fun :: instances + // set the current code to execute to the new function body + code = fun.code + // make room for locals + tp += nbLocals + pushInt(nbLocals + fun.tpe.params.size) + pushInt(fun.tpe.t.size) + pushInt(pc) + pushInt(fp) + pc = 0 + fp = tp - 4 + } + + def popFrame(): Unit = { + // pop the function instance + instances = instances.tail + //previous.next = null + // restore the registers + tp = fp - nbLocals + pc = (stack(fp + 2) & 0xffffffff).toInt + fp = (stack(fp + 3) & 0xffffffff).toInt + // restore the code + if (instances.isEmpty) { + code = null + } else { + code = instances.head.code + } + } + + def pushBool(b: Boolean): Unit = + pushInt(if (b) 1 else 0) + + def pushInt(i: Int): Unit = + pushValue(i & 0X00000000FFFFFFFFL) + + def pushLong(l: Long): Unit = + pushValue(l) + + def pushFloat(f: Float): Unit = + pushValue(JFloat.floatToRawIntBits(f) & 0X00000000FFFFFFFFL) + + def pushDouble(d: Double): Unit = + pushValue(JDouble.doubleToRawLongBits(d)) + + def popBool(): Boolean = + popInt() != 0 + + def popInt(): Int = + (popValue() & 0X00000000FFFFFFFFL).toInt + + def peekInt(): Int = + (peekValue() & 0X00000000FFFFFFFFL).toInt + + def popLong(): Long = + popValue() + + def peekLong(): Long = + peekValue() + + def popFloat(): Float = + JFloat.intBitsToFloat(popInt()) + + def peekFloat(): Float = + JFloat.intBitsToFloat(peekInt()) + + def popDouble(): Double = + JDouble.longBitsToDouble(popLong()) + + def peekDouble(): Double = + JDouble.longBitsToDouble(peekLong()) + + def drop(n: Int): Unit = { + tp -= n + } + + def popValue(): Long = { + tp -= 1 + stack(tp) + } + + def peekValue(): Long = + stack(tp - 1) + + def popValues(n: Int): Seq[Long] = { + @tailrec + def loop(n: Int, acc: Seq[Long]): Seq[Long] = + if (n <= 0) + acc + else + loop(n - 1, popValue() +: acc) + loop(n, Seq.empty) + } + + def pushValue(l: Long): Unit = { + stack(tp) = l + tp += 1 + } + + def pushValues(values: Seq[Long]): Unit = + values.foreach(pushValue(_)) + + def readByte(): Byte = { + val b = code.get(pc) + pc += 1 + b + } + + def readInt(): Int = { + val i = code.getInt(pc) + pc += 4 + i + } + + def readLong(): Long = { + val l = code.getLong(pc) + pc += 8 + l + } + + def readFloat(): Float = { + val f = code.getFloat(pc) + pc += 4 + f + } + + def readDouble(): Double = { + val d = code.getDouble(pc) + pc += 8 + d + } + + def local(idx: Int): Long = + stack(fp - idx) + + def setLocal(idx: Int, v: Long): Unit = + stack(fp - idx) = v + + def global(idx: Int): Global[F] = + instance.instance.globals(idx) + + def memory(idx: Int): Memory[F] = + instance.instance.memories(idx) + + def func(fidx: Int): Function[F] = + instance.instance.funcs(fidx) + + def table(idx: Int): Table[F] = + instance.instance.tables(idx) + + def module: Module[F] = + instance.instance.module + + def memoryOpt(idx: Int): Option[Memory[F]] = + if (isToplevel) + baseInstance.memories.lift(idx) + else + instance.instance.memories.lift(idx) + +} + +private object ThreadFrame { + + implicit def ThreadFrameShow[F[_]]: Show[ThreadFrame[F]] = new Show[ThreadFrame[F]] { + def show(t: ThreadFrame[F]): String = { + t.stack + .take(t.tp + 1) + .zipWithIndex + .map { + case (v, idx) => + if (idx == t.fp) + s"fp -> | $v" + else if (idx == t.tp) + "tp -> |" + else + s" | $v" + } + .mkString("=== Stack ===\n +---------\n", "\n +---------\n", "") + } + } + +} diff --git a/text/src/swam/decompilation/TextDecompiler.scala b/text/src/swam/decompilation/TextDecompiler.scala index 04b6e520..84ee8f15 100644 --- a/text/src/swam/decompilation/TextDecompiler.scala +++ b/text/src/swam/decompilation/TextDecompiler.scala @@ -179,7 +179,7 @@ class TextDecompiler[F[_]] private (validator: Validator[F])(implicit F: Effect[ val f = FuncType(ps.map(_._2), rs) val acc1 = id.toOption match { case Some(_) => (Right(id) -> f) :: (Left(idx) -> f) :: acc - case None => (Left(idx) -> f) :: acc + case None => (Left(idx) -> f) :: acc } (idx + 1, acc1) case (acc, _) => acc @@ -189,7 +189,7 @@ class TextDecompiler[F[_]] private (validator: Validator[F])(implicit F: Effect[ val f = FuncType(ps.map(_._2), rs) id.toOption match { case Some(_) => List(Right(id) -> f, Left(idx + fidx) -> f) - case None => List(Left(idx + fidx) -> f) + case None => List(Left(idx + fidx) -> f) } } val id = env.moduleName match { @@ -197,8 +197,7 @@ class TextDecompiler[F[_]] private (validator: Validator[F])(implicit F: Effect[ case _ => u.NoId } val functypes = types.toMap - (u.Module(id, imports ++ exports ++ memories ++ data ++ tables ++ elems ++ start ++ functions)(-1), - functypes) + (u.Module(id, imports ++ exports ++ memories ++ data ++ tables ++ elems ++ start ++ functions)(-1), functypes) case None => (u.Module(u.NoId, Seq.empty)(-1), Map.empty) } @@ -273,7 +272,7 @@ class TextDecompiler[F[_]] private (validator: Validator[F])(implicit F: Effect[ functionNames.get(idx) match { case Some(Valid(n)) => Right(u.SomeId(n)) case _ => Left(idx) - }) + }) u.Elem(Left(idx), decompileExpr(offset, -1, functypes, functionNames, localNames), funs)(-1) }