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:
val
annotated withglobal
will be exported as contanst global. Theval
type must have aValueFormatter
.var
annotated withglobal
will be exported as mutable global. Thevar
type must have aValueFormatter
.def
annotated withpure
oreffectful
will be exported as functions. The parameter types must have aValueReader
and 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