8000 Draft: Add support for some "essential" external features in targetPureWasm. by sjrd · Pull Request #38 · scala-wasm/scala-wasm · GitHub
[go: up one dir, main page]

Skip to content

Draft: Add support for some "essential" external features in targetPureWasm. #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
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
8000
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,7 @@ jobs:
run: npm install
- name: testSuiteWASI${{ matrix.suffix }}/fastLinkJS
run: |
sbt "set Seq(Global/enableWasmEverywhere := true, testSuiteWASI.v${{ matrix.suffix }}/scalaJSLinkerConfig ~= (_.withWasmFeatures(_.withExceptionHandling(${{ matrix.eh-support }}))))" testSuiteWASI${{ matrix.suffix }}/fastLinkJS
VERSION=$(echo "${{ matrix.suffix }}" | sed 's/2_/2./')
node ${{ matrix.eh-support && '--experimental-wasm-exnref' || '--no-experimental-wasm-exnref' }} ./index.mjs ./examples/test-suite-wasi/.$VERSION/target/scala-$VERSION/test-suite-wasi-fastopt/main.wasm
echo "test success"
sbt "set Seq(Global/enableWasmEverywhere := true, testSuiteWASI.v${{ matrix.suffix }}/scalaJSLinkerConfig ~= (_.withWasmFeatures(_.withExceptionHandling(${{ matrix.eh-support }}))))" testSuiteWASI${{ matrix.suffix }}/run

test-suite-component:
runs-on: ubuntu-latest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ object Run {
JavalibLangTest.run()
JavalibUtilTest.run()
LibraryTest.run()

println("Test suite completed")
}
}
4 changes: 3 additions & 1 deletion javalib/src/main/scala/java/lang/Math.scala
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,9 @@ object Math {
@inline def atan(a: scala.Double): scala.Double = js.Math.atan(a)
@inline def atan2(y: scala.Double, x: scala.Double): scala.Double = js.Math.atan2(y, x)

@inline def random(): scala.Double = js.Math.random()
@inline def random(): scala.Double =
if (LinkingInfo.targetPureWasm) WasmSystem.random()
else js.Math.random()

@inline def toDegrees(a: scala.Double): scala.Double = a * 180.0 / PI
@inline def toRadians(a: scala.Double): scala.Double = a / 180.0 * PI
Expand Down
9 changes: 5 additions & 4 deletions javalib/src/main/scala/java/lang/System.scala
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ object System {

@inline
def currentTimeMillis(): scala.Long =
(new js.Date).getTime().toLong
if (LinkingInfo.targetPureWasm) WasmSystem.currentTimeMillis()
else js.Date.now().toLong

private object NanoTime {
val getHighPrecisionTime: js.Function0[scala.Double] = {
Expand All @@ -90,7 +91,8 @@ object System {

@inline
def nanoTime(): scala.Long =
(NanoTime.getHighPrecisionTime() * 1000000).toLong
if (LinkingInfo.targetPureWasm) WasmSystem.nanoTime()
else (NanoTime.getHighPrecisionTime() * 1000000).toLong

// arraycopy ----------------------------------------------------------------

Expand Down Expand Up @@ -384,8 +386,7 @@ private final class JSConsoleBasedPrintStream(isErr: scala.Boolean)

private def doWriteLine(line: String): Unit = {
if (LinkingInfo.targetPureWasm) { // isWASI
// TODO

WasmSystem.print(line)
} else {
import js.DynamicImplicits.truthValue

Expand Down
15 changes: 15 additions & 0 deletions javalib/src/main/scala/java/lang/WasmSystem.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package java.lang

protected[lang] object WasmSystem {
@noinline
def print(s: String): Unit = ()

@noinline
def nanoTime(): scala.Long = 0L

@noinline
def currentTimeMillis(): scala.Long = 0L

@noinline
def random(): scala.Double = 0.0
}
2 changes: 1 addition & 1 deletion javalib/src/main/scala/java/util/Date.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Date(private var millis: Long) extends Object
/* No need to check for overflow. If SJS lives that long (~year 275760),
* it's OK to have a bug ;-)
*/
this(js.Date.now().toLong)
this(System.currentTimeMillis())
}

@Deprecated
Expand Down
2 changes: 1 addition & 1 deletion javalib/src/main/scala/java/util/Random.scala
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,6 @@ object Random {
(randomInt().toLong << 32) | (randomInt().toLong & 0xffffffffL)

private def randomInt(): Int =
(Math.floor(js.Math.random() * 4294967296.0) - 2147483648.0).toInt
(Math.floor(Math.random() * 4294967296.0) - 2147483648.0).toInt

}
Original file line number Diff line number Diff line change
Expand Up @@ -1410,17 +1410,30 @@ class ClassEmitter(coreSpec: CoreSpec) {
val body = method.body.getOrElse(throw new Exception("abstract method cannot be transformed"))

// Emit the function
FunctionEmitter.emitFunction(
functionID,
originalName,
Some(className),
captureParamDefs = None,
receiverType,
method.args,
restParam = None,
body,
method.resultType
)
if (className == SpecialNames.WasmSystemClass &&
namespace == MemberNamespace.Public && !methodName.isReflectiveProxy) {
emitSpecialMethod(
functionID,
originalName,
className,
methodName,
receiverType.get,
method.args,
method.resultType
)
} else {
FunctionEmitter.emitFunction(
functionID,
originalName,
Some(className),
captureParamDefs = None,
receiverType,
method.args,
restParam = None,
body,
method.resultType
)
}

if (namespace == MemberNamespace.Public && !isHijackedClass) {
/* Also generate the bridge that is stored in the table entries. In table
Expand Down Expand Up @@ -1466,6 +1479,48 @@ class ClassEmitter(coreSpec: CoreSpec) {
}
}

private def emitSpecialMethod(
functionID: wanme.FunctionID,
originalName: OriginalName,
enclosingClassName: ClassName,
methodName: MethodName,
receiverType: watpe.Type,
paramDefs: List[ParamDef],
resultType: Type
)(implicit ctx: WasmContext, pos: Position): Unit = {
val fb = new FunctionBuilder(ctx.moduleBuilder, functionID, originalName, pos)
val receiverParam = fb.addParam("this", receiverType)
val paramLocals = paramDefs.map { paramDef =>
fb.addParam(paramDef.originalName.orElse(paramDef.name.name),
transformParamType(paramDef.ptpe))
}
fb.setResultTypes(transformResultType(resultType))

methodName.simpleName.nameString match {
case "print" =>
fb += wa.LocalGet(paramLocals(0))
fb += wa.RefAsNonNull
fb += wa.Call(genFunctionID.wasmString.getWholeChars)
fb += wa.Call(genFunctionID.wasmEssentials.print)

case "nanoTime" =>
fb += wa.Call(genFunctionID.wasmEssentials.nanoTime)
fb += wa.I64TruncSatF64S

case "currentTimeMillis" =>
fb += wa.Call(genFunctionID.wasmEssentials.currentTimeMillis)
fb += wa.I64TruncSatF64S

case "random" =>
fb += wa.Call(genFunctionID.wasmEssentials.random)

case _ =>
throw new AssertionError(s"Unknown WasmSystem method ${methodName.nameString}")
}

fb.buildAndAddToModule()
}

private def makeDebugName(namespace: UTF8String, exportedName: String): OriginalName =
OriginalName(namespace ++ UTF8String(exportedName))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) {

if (!targetPureWasm) genImports()
else {
genWasmEssentialsImports()

if (coreSpec.wasmFeatures.exceptionHandling) {
val exceptionSig = FunctionType(List(RefType.anyref), Nil)
val typeID = ctx.moduleBuilder.functionTypeToTypeID(exceptionSig)
Expand Down Expand Up @@ -232,6 +234,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) {
genUndefinedAndIsUndef()
genNaiveFmod()
genItoa()
genHijackedValueToString()
// genPrintlnInt()
// genPrintMemory()
}
Expand Down Expand Up @@ -716,6 +719,26 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) {
)
}

private def genWasmEssentialsImports()(implicit ctx: WasmContext): Unit = {
def addEssentialImport(id: genFunctionID.JSHelperFunctionID,
params: List[Type], results: List[Type]): Unit = {
val sig = FunctionType(params, results)
val typeID = ctx.moduleBuilder.functionTypeToTypeID(sig)
ctx.moduleBuilder.addImport(
Import(
EssentialExternsModule,
id.toString(), // import name, guaranteed by JSHelperFunctionID
ImportDesc.Func(id, OriginalName(id.toString()), typeID)
)
)
}

addEssentialImport(genFunctionID.wasmEssentials.print, List(RefType(genTypeID.i16Array)), Nil)
addEssentialImport(genFunctionID.wasmEssentials.nanoTime, Nil, List(Float64))
addEssentialImport(genFunctionID.wasmEssentials.currentTimeMillis, Nil, List(Float64))
addEssentialImport(genFunctionID.wasmEssentials.random, Nil, List(Float64))
}

// --- Global definitions ---


Expand Down Expand Up @@ -4894,6 +4917,29 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) {
fb.buildAndAddToModule
}

private def genHijackedValueToString()(implicit ctx: WasmContext): Unit = {
val fb = newFunctionBuilder(genFunctionID.hijackedValueToString)
val value = fb.addParam("value", anyref)
fb.setResultType(stringType)

fb.block(RefType(genTypeID.wasmString)) { labelString =>
fb.block(RefType.i31) { labelI31 =>
fb += LocalGet(value)
fb += BrOnCast(labelI31, anyref, RefType.i31)
fb += BrOnCast(labelString, anyref, RefType(genTypeID.wasmString))

// if none of the above, it must be null
fb ++= ctx.stringPool.getConstantStringInstr("null")
fb += Return
} // end block of labelI31

fb += I31GetS
fb += Call(genFunctionID.itoa)
}

fb.buildAndAddToModule
}

/** Get the type of the given Wasm type of `x` without JS,
* the return value should be compatible with jsValueType.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ object EmbeddedConstants {
/** Imported modules for WTF-16 strings, which cannot use the JS string builtins. */
final val WTF16StringConstantsModule = "wtf16Strings"

/** Module for the essential externs for testing without the component model, defined by hand in `LoaderContent`. */
final val EssentialExternsModule = "essentialExterns"

/* Values returned by the `jsValueType` helper.
*
* 0: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ final class Emitter(config: Emitter.Config) {

def emit(module: ModuleSet.Module, globalInfo: LinkedGlobalInfo, logger: Logger): Result = {
val (wasmModule, jsFileContentInfo) = emitWasmModule(module, globalInfo)
val loaderContent = LoaderContent.bytesContent
val loaderContent =
if (coreSpec.wasmFeatures.targetPureWasm) LoaderContent.pureWasmBytesContent
else LoaderContent.bytesContent
val jsFileContent = buildJSFileContent(module, jsFileContentInfo)

new Result(wasmModule, loaderContent, jsFileContent)
Expand Down Expand Up @@ -223,8 +225,14 @@ final class Emitter(config: Emitter.Config) {
fb += wa.Return
fb += wa.End

// TODO Print *something* useful, somehow.
// For now at least we fail the execution.
// TODO Print the toString() of the exception, if possible
val message = "Uncaught exception"
for (c <- message)
fb += wa.I32Const(c.toInt)
fb += wa.ArrayNewFixed(genTypeID.i16Array, message.length())
fb += wa.Call(genFunctionID.wasmEssentials.print)

// In any case, fail the execution
fb += wa.Unreachable
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1182,23 +1182,10 @@ private class FunctionEmitter private (
if (methodName == toStringMethodName) {
// By spec, toString() is special
assert(argsLocals.isEmpty)
if (targetPureWasm) {
fb.block(Sig(List(watpe.RefType.any), List(watpe.RefType(genTypeID.wasmString)))) { exit =>
fb.block(Sig(List(watpe.RefType.any), List(watpe.RefType(genTypeID.wasmString)))) { labelString =>
fb.block(Sig(List(watpe.RefType.any), List(watpe.RefType.i31))) { labelI31 =>
fb += wa.BrOnCast(labelI31, watpe.RefType.any, watpe.RefType.i31)
fb += wa.BrOnCast(labelString, watpe.RefType.any, watpe.RefType(genTypeID.wasmString))
fb += wa.Unreachable
} // end block of labelI31
fb += wa.I31GetS
fb += wa.Call(genFunctionID.itoa)
fb += wa.Br(exit)
}
fb += wa.Br(exit)
}
} else {
if (targetPureWasm)
fb += wa.Call(genFunctionID.hijackedValueToString)
else
fb += wa.Call(genFunctionID.jsValueToString)
}
} else if (receiverClassName == JLNumberClass) {
// the value must be a `number`, hence we can unbox to `double`
genUnbox(DoubleType)
Expand Down Expand Up @@ -2194,13 +2181,11 @@ private class FunctionEmitter private (
fb += wa.RefNull(watpe.HeapType.Any)
} // end block labelNotOurObject

if (targetPureWasm) {
fb += wa.Drop // there shouldn't be a js value
fb ++= ctx.stringPool.getConstantStringInstr("null")
} else {
// Now we have a value that is not one of our objects; the anyref is still on the stack
// Now we have a value that is not one of our objects; the anyref is still on the stack
if (targetPureWasm)
fb += wa.Call(genFunctionID.hijackedValueToString)
else
fb += wa.Call(genFunctionID.jsValueToStringForConcat)
}
} // end block labelDone
}
}
Expand Down
Loading
Loading
0