Skip to content
This repository has been archived by the owner on Apr 4, 2023. It is now read-only.

Commit

Permalink
Implement low level stack more efficiently
Browse files Browse the repository at this point in the history
The high level interpreter creates a lot of objects whenever a new frame
is pushed, or even at every interpretation step. Whereas this is ok for
simple scripts, it is quite costly and adds a lot of overhead at
runtime. The idea is to make pushing frames object creation free in the
end. This lower level representation of a thread frame, is one step
towards that result.
  • Loading branch information
satabin committed May 19, 2019
1 parent fb2586d commit 0b17098
Show file tree
Hide file tree
Showing 16 changed files with 1,719 additions and 1,358 deletions.
Expand Up @@ -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)

}
Expand Up @@ -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)
Expand Down Expand Up @@ -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())
Expand Down
29 changes: 21 additions & 8 deletions runtime/resources/reference.conf
Expand Up @@ -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

}

}

Expand Down
38 changes: 36 additions & 2 deletions runtime/src/swam/runtime/Instance.scala
Expand Up @@ -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)

}

}
Expand All @@ -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 {
Expand All @@ -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
Expand Down
6 changes: 5 additions & 1 deletion runtime/src/swam/runtime/config/EngineConfiguration.scala
Expand Up @@ -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)
3 changes: 2 additions & 1 deletion runtime/src/swam/runtime/exceptions.scala
Expand Up @@ -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)
91 changes: 91 additions & 0 deletions 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"))
}

}
96 changes: 96 additions & 0 deletions 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"))
}

}

0 comments on commit 0b17098

Please sign in to comment.