Using the module annotation, it is possible to mark an entire class or trait as some module that can be imported by Swam modules. The annotated type will have an implicit instance of AsInstance with annotated exported fields. This comes in handy to expose Scala instances to WebAssembly modules.
Following class members can be exported:
valannotated withglobalwill be exported as contanst global. Thevaltype must have aValueFormatter.varannotated withglobalwill be exported as mutable global. Thevartype must have aValueFormatter.defannotated withpureoreffectfulwill be exported as functions. The parameter types must have aValueReaderand the return type aValueWriter.
By default the exported field will have the member name, but this can be changed by providing the name parameter.
Exported global variables
For instance, let’s define a simple class with three exported members.
import swam.runtime.imports.annotations._
@module
class MyModule {
@global
val v1: Int = 23
@global(name = "test")
val v2: Double = 2.0d
@global
var v3: Int = 45
}
val m = new MyModule
Instance m can be used as an import and v3 can be mutated from a WebAssembly module.
import swam.text._
import swam.runtime._
import cats.effect._
import java.nio.file.Paths
implicit val cs = IO.contextShift(scala.concurrent.ExecutionContext.global)
val f =
Blocker[IO].use { blocker =>
for {
engine <- Engine[IO](blocker)
tcompiler <- Compiler[IO](blocker)
mod <- engine.compile(tcompiler.stream(Paths.get("annotations.wat"), true, blocker))
inst <- mod.importing("m", m).instantiate
f <- inst.exports.typed.function[Unit, Unit]("mutate")
} yield f
}.unsafeRunSync()
m.v3
// res0: Int = 45
f().unsafeRunSync()
m.v3
// res2: Int = 45
Exported functions
More interestingly, functions can also be exported. They come in two flavors:
- pure functions, annotated with
pure; - and effectful functions, annotated with
effectful.
Pure functions
Let’s assume we have a simple add42 function, written in scala:
@module
class PureModule {
@pure
def add42(i: Int): Int = i + 42
}
val pm = new PureModule
WebAssembly modules can now import it, and call it:
val add42 =
Blocker[IO].use { blocker =>
for {
engine <- Engine[IO](blocker)
tcompiler <- Compiler[IO](blocker)
mod <- engine.compile(tcompiler.stream(Paths.get("pure-annotations.wat"), true, blocker))
inst <- mod.importing("m", pm).instantiate
f <- inst.exports.typed.function[Unit, Int]("f")
} yield f
}.unsafeRunSync()
Executing the imported function, returns the desired result:
add42().unsafeRunSync()
// res3: Int = 55
Effectful functions
Functions that may raise exceptions or perform side-effects, should return their result wrapped in an effect type F[_]. Modules with such functions must have the effect type as type parameter, annotated with effect.
This type must be unique, and all effectful functions must use it.
We can define a logging function in scala as follows, that simply prints to stdout:
import cats._
@module
class EffectfulModule[@effect F[_]](implicit F: Applicative[F]) {
@effectful
def log(i: Int): F[Unit] = F.pure(println(s"got: $i"))
}
val mIO = new EffectfulModule[IO]
Now, WebAssembly modules can import that module and use the effectful function:
val logged =
Blocker[IO].use { blocker =>
for {
engine <- Engine[IO](blocker)
tcompiler <- Compiler[IO](blocker)
mod <- engine.compile(tcompiler.stream(Paths.get("effectful-annotations.wat"), true, blocker))
inst <- mod.importing("console", mIO).instantiate
f <- inst.exports.typed.function[Int, Int]("add42")
} yield f
}.unsafeRunSync()
We can now run the logged function and the parameter is logged to stdout as expected
logged(43).unsafeRunSync()
// got: 43
// res4: Int = 85