Skip to content
This repository was archived by the owner on Jul 12, 2024. It is now read-only.

[DO NOT MERGE] Initial memory allocation and WASI support #135

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions sample/src/main/scala/Sample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@ package sample

import scala.scalajs.js
import scala.scalajs.js.annotation._
import _root_.scala.scalajs.wasm.io

object Main {
@JSExportTopLevel("field")
var exportedField: Int = 42

@JSExportTopLevel("test")
def test(i: Int): Boolean = {
println("Hello")
exportedField = 53
println(exportedField)
true
}

def main(args: Array[String]): Unit = {
println("hello world")
scala.scalajs.wasm.memory.withAllocator { allocator =>
val segment = allocator.allocate(4)
segment.setInt(0, 100)
val result = segment.getInt(0)
println(result)
}
io.printImpl("Hello from WASI!", newLine = true)
}

// Tested in SampleTest.scala
Expand Down
42 changes: 42 additions & 0 deletions sample/src/main/scala/io.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package scala.scalajs.wasm

object io {
def printImpl(message: String, newLine: Boolean): Unit = {
scala.scalajs.wasm.memory.withAllocator { allocator =>
val bytes = message.toCharArray().map(_.toByte)
wasiPrintImpl(allocator, bytes, newLine)
}
}
def wasiPrintImpl(
allocator: scala.scalajs.wasm.MemoryAllocator,
bytes: Array[Byte],
newLine: Boolean
) = {
val size = bytes.size
val memorySize = size + (if (newLine) 1 else 0)
val segment = allocator.allocate(memorySize)
for ((b, offset) <- bytes.zipWithIndex) {
segment.setByte(offset, b)
}
if (newLine) {
segment.setByte(memorySize - 1, 0x0A)
}

val iovs = allocator.allocate(8)
iovs.setInt(0, segment.start)
iovs.setInt(4, memorySize)

val rp0 = allocator.allocate(4)

val ret = wasi.fdWrite(
descriptor = wasi.STDOUT,
iovs = iovs.start,
iovsLen = 1,
rp = rp0.start
)

if (ret != 0) {
// TODO: check return pointer's error code
}
}
}
52 changes: 52 additions & 0 deletions sample/src/main/scala/memory.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package scala.scalajs.wasm
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests are failing because these code are not available outside the sample project.
However, I'm not sure where to put those library code in our project 🤔


object memory {
def withAllocator(block: MemoryAllocator => Unit): Unit = {
val allocator = new MemoryAllocator()
try {
block(allocator)
} finally {
allocator.free()
}
}

def intrinsic: Nothing = throw new NotImplementedError("This is a stub for wasm intrinsics")
}

class MemorySegment(val start: Int, val size: Int) {
import scala.scalajs.wasm.memory.intrinsic
private def validate(offset: Int, requiredBytes: Int): Unit = {
require(
offset + requiredBytes >= 0 && offset + requiredBytes <= size,
s"MemorySegment.validate($requiredBytes) failed, can't available $requiredBytes bytes"
)
}

// def getByte(offset: Int): Int = {
// validate(offset, 1)
// _loadByte(offset + start)
// }
def getInt(offset: Int): Int = {
validate(offset, 4)
_loadInt(offset + start)
}

def setByte(offset: Int, value: Byte): Unit = {
validate(offset, 1)
_storeByte(offset + start, value)
}
def setInt(offset: Int, value: Int): Unit = {
validate(offset, 4)
_storeInt(offset + start, value)
}

private def _loadInt(offset: Int): Int = intrinsic
private def _storeByte(offset: Int, value: Byte): Unit = intrinsic
private def _storeInt(offset: Int, value: Int): Unit = intrinsic
}

class MemoryAllocator { // 1MB default initial size
import scala.scalajs.wasm.memory.intrinsic
def allocate(size: Int): MemorySegment = intrinsic
def free(): Unit = intrinsic
}
9 changes: 9 additions & 0 deletions sample/src/main/scala/wasi.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package scala.scalajs.wasm

object wasi {
val STDOUT = 1
val STDERR = 2

// "wasi_snapshot_preview1", "fd_write"
def fdWrite(descriptor: Int, iovs: Int, iovsLen: Int, rp: Int): Int = ???
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import SWasmGen._
import VarGen._
import TypeTransformer._
import WasmContext._
import org.scalajs.linker.backend.wasmemitter.FunctionEmitter.SWasmTrees

class ClassEmitter(coreSpec: CoreSpec) {
def genClassDef(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
Expand Down Expand Up @@ -1071,17 +1072,78 @@ class ClassEmitter(coreSpec: CoreSpec) {

val body = method.body.getOrElse(throw new Exception("abstract method cannot be transformed"))

// Emit the function
FunctionEmitter.emitFunction(
functionName,
Some(className),
captureParamDefs = None,
receiverTyp,
method.args,
restParam = None,
body,
method.resultType
)
val maybeIntrinsic: Option[SWasmTrees.SWasmTree] = className match {
case SpecialNames.WasmMemorySegmentClass =>
if (SpecialNames.loadMethodNames.contains(methodName)) {
Comment on lines +1076 to +1077
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, we match the className and methodName with the hardcoded SpecialNames, and generate SWasmTree (kind of fake SJSIR Tree), and FunctionEmitter.emitIntrinsicFucntion to inject an implementation.

val size = methodName match {
case SpecialNames.loadByteMethodName => SWasmTrees.LoadStore.I8
case SpecialNames.loadIntMethodName => SWasmTrees.LoadStore.I32
case _ => ???
}
Some(
SWasmTrees.Load(
size,
VarRef(method.args.head.name)(method.args.head.ptpe),
IntType
)
)
} else if (SpecialNames.storeMethodNames.contains(methodName)) {
val size = methodName match {
case SpecialNames.storeByteMethodName => SWasmTrees.LoadStore.I8
case SpecialNames.storeIntMethodName => SWasmTrees.LoadStore.I32
case _ => ???
}
Some(
SWasmTrees.Store(
size,
VarRef(method.args(0).name)(method.args(0).ptpe),
VarRef(method.args(1).name)(method.args(1).ptpe)
)
)
} else None

case SpecialNames.WasmMemoryAllocatorClass =>
if (methodName == SpecialNames.allocateMethodName)
Some(SWasmTrees.Alloc(VarRef(method.args.head.name)(method.args.head.ptpe)))
else if (methodName == SpecialNames.freeMethodName)
Some(SWasmTrees.Free())
else None
case SpecialNames.WASI if methodName == SpecialNames.wasiFdWrite =>
Some(
SWasmTrees.WasmFunctionCall(
genFunctionName.wasi.fdWrite,
method.args.map(a => VarRef(a.name)(a.ptpe)),
method.resultType
)
)
case _ => None
}
// maybeIntrinsic.foreach(println)

maybeIntrinsic match {
case None =>
// Emit the function
FunctionEmitter.emitFunction(
functionName,
Some(className),
captureParamDefs = None,
receiverTyp,
method.args,
restParam = None,
body,
method.resultType
)
case Some(value) =>
FunctionEmitter.emitIntrinsicFunction(
functionName,
Some(className),
receiverTyp,
method.args,
restParam = None,
value,
method.resultType
)
}

if (namespace == MemberNamespace.Public && !isHijackedClass) {
/* Also generate the bridge that is stored in the table entries. In table
Expand Down
Loading
Loading