Hello world (WASI)
This tutorial walks through a minimal Hello, world app that uses WASI APIs and runs on Wasmtime. You build a command component that exports the WASI entrypoint wasi:cli/run, then run it with sbt run or wasmtime run.
This example uses wasi4s, which is a pre-generated WASI bindings, to call WASI from Scala. 1
For a complete project example, see component-examples, the helloworld/ subdirectory and the build.sbt.
Project layout
helloworld-wasi/
build.sbt
project/
plugins.sbt
build.properties
build.sbt
src/main/scala/
Main.scala
project/
// project/plugins.sbt
addSbtPlugin("io.github.scala-wasm" % "sbt-scalajs" % "1.22.1-wasm.6")
// project/build.properties
sbt.version=2.0.7
// project/build.sbt
// https://github.com/scala-wasm/scala-js-env-wasmtime
// runs the linked component on wasmtime when you sbt run
libraryDependencies +=
"io.github.scala-wasm" %% "scalajs-env-wasmtime" % "0.1.1"
build.sbt
import org.scalajs.ir.WitScope
import org.scalajs.jsenv.wasmtime.WasmtimeEnv
import org.scalajs.linker.interface.ESVersion
import org.scalajs.linker.interface.WasmComponentModuleInitializerExport
import org.scalajs.linker.interface.WasmComponentModuleInitializerExport._
lazy val root = project
.in(file("."))
.enablePlugins(ScalaJSPlugin)
.settings(
name := "helloworld-wasi",
version := "0.1.0-SNAPSHOT",
scalaVersion := "2.13.18",
libraryDependencies +=
"io.github.scala-wasm" %%% "wasi4s" % "0.1.0+wasi-0.2.12",
scalaJSLinkerConfig ~= {
_.withESFeatures(_.withUseWebAssembly(true).withESVersion(ESVersion.ES2022))
.withModuleKind(ModuleKind.WasmComponent)
.withWasmFeatures(_.withModuleInitializerExport(Some(
WasmComponentModuleInitializerExport(
scope = WitScope.Interface("wasi", "cli", "run", Some("0.2.12")),
functionName = "run",
resultType = ResultType.ResultUnitUnit,
))))
},
scalaJSUseMainModuleInitializer := true,
jsEnv := new WasmtimeEnv(),
)
withUseWebAssemblyandwithModuleKind(ModuleKind.WasmComponent)tell the linker to emit a Wasm component.scalaJSUseMainModuleInitializer := trueplaces yourmainas a Wasm module initializer.withModuleInitializerExportexports that initializer aswasi:cli/run@0.2.12.
src/main/scala/Main.scala
package example
import io.github.scalawasm.wasi4s.wasi.cli.stdout._
object Main {
def main(args: Array[String]): Unit = {
val out = getStdout()
out.blockingWriteAndFlush("Hello, world!\n")
}
}
This example writes to the host using WASI’s low-level stdout API. 2
Run
sbt fastLinkJS
[info] Fast optimizing .../component-examples/target/out/sjs1/scala-2.13.18/helloworld/helloworld-fastopt
main.wasm is generated in that directory. If build.sbt sets jsEnv := new WasmtimeEnv(), then sbt run runs main.wasm using wasmtime from sbt.
sbt run
[info] Running wasi:cli/run.
Hello, world!
To run the artifact yourself using wasmtime:
wasmtime run -W function-references,gc,exceptions path/to/main.wasm
(-W flags might be no longer needed with the latest version of wasmtime).
-
You can instead add a project
wit/directory withworld.witthat imports WASI explicitly and generate bindings withwit-bindgen-scala. We'll coverworld.witin another tutorial. ↩ -
Why not
println? scala-wasm does not ship aprintlnimplementation today; you define stdout yourself via WASI (or a small wrapper). See Limitations. ↩