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:

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

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