-
Notifications
You must be signed in to change notification settings - Fork 396
Wasm: Implement checked behaviors. #5002
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
Changes from all commits
506b976
89dddcd
833f778
41b259b
cff558f
e22e40e
58af468
906276c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ import org.scalajs.ir.OriginalName.NoOriginalName | |
import org.scalajs.ir.Trees._ | ||
import org.scalajs.ir.Types._ | ||
|
||
import org.scalajs.linker.interface.CheckedBehavior | ||
import org.scalajs.linker.interface.unstable.RuntimeClassNameMapperImpl | ||
import org.scalajs.linker.standard.{CoreSpec, LinkedClass, LinkedTopLevelExport} | ||
|
||
|
@@ -37,6 +38,7 @@ import WasmContext._ | |
|
||
class ClassEmitter(coreSpec: CoreSpec) { | ||
import ClassEmitter._ | ||
import coreSpec.semantics | ||
|
||
def genClassDef(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { | ||
val classInfo = ctx.getClassInfo(clazz.className) | ||
|
@@ -373,6 +375,12 @@ class ClassEmitter(coreSpec: CoreSpec) { | |
genCloneFunction(clazz) | ||
} | ||
|
||
// Generate cast functions | ||
if (clazz.hasInstanceTests && semantics.asInstanceOfs != CheckedBehavior.Unchecked) { | ||
if (className != ObjectClass) | ||
genClassCastFunction(clazz) | ||
} | ||
|
||
// Generate the module accessor | ||
if (clazz.kind == ClassKind.ModuleClass && clazz.hasInstances) { | ||
val heapType = watpe.HeapType(genTypeID.forClass(clazz.className)) | ||
|
@@ -387,6 +395,17 @@ class ClassEmitter(coreSpec: CoreSpec) { | |
) | ||
ctx.addGlobal(global) | ||
|
||
if (semantics.moduleInit != CheckedBehavior.Unchecked) { | ||
val initFlagGlobal = wamod.Global( | ||
genGlobalID.forModuleInitFlag(className), | ||
makeDebugName(ns.ModuleInitFlag, className), | ||
isMutable = true, | ||
watpe.Int32, | ||
wa.Expr(List(wa.I32Const(0))) | ||
) | ||
ctx.addGlobal(initFlagGlobal) | ||
} | ||
|
||
genModuleAccessor(clazz) | ||
} | ||
} | ||
|
@@ -408,7 +427,7 @@ class ClassEmitter(coreSpec: CoreSpec) { | |
case None => genTypeID.typeData | ||
case Some(s) => genTypeID.forVTable(s.name) | ||
} | ||
val structType = watpe.StructType(CoreWasmLib.typeDataStructFields ::: vtableFields) | ||
val structType = watpe.StructType(ctx.coreLib.typeDataStructFields ::: vtableFields) | ||
val subType = watpe.SubType( | ||
typeID, | ||
makeDebugName(ns.VTable, className), | ||
|
@@ -522,6 +541,50 @@ class ClassEmitter(coreSpec: CoreSpec) { | |
fb.buildAndAddToModule() | ||
} | ||
|
||
/** Generate the cast function for an interface. | ||
* | ||
* When `asInstanceOfs` are checked, the expression `asInstanceOf[<interface>]` | ||
* will be compiled to a CALL to the function generated by this method. | ||
*/ | ||
private def genInterfaceCastFunction(clazz: LinkedClass)( | ||
implicit ctx: WasmContext): Unit = { | ||
assert(clazz.kind == ClassKind.Interface) | ||
|
||
val className = clazz.className | ||
val resultType = TypeTransformer.transformClassType(className, nullable = true) | ||
|
||
val fb = new FunctionBuilder( | ||
ctx.moduleBuilder, | ||
genFunctionID.asInstance(ClassType(className, nullable = true)), | ||
makeDebugName(ns.AsInstance, className), | ||
clazz.pos | ||
) | ||
val objParam = fb.addParam("obj", watpe.RefType.anyref) | ||
fb.setResultType(resultType) | ||
|
||
fb.block() { successLabel => | ||
// Succeed if null | ||
fb += wa.LocalGet(objParam) | ||
fb += wa.BrOnNull(successLabel) | ||
|
||
// Succeed if the instance test succeeds | ||
fb += wa.Call(genFunctionID.instanceTest(className)) | ||
fb += wa.BrIf(successLabel) | ||
|
||
// If we get here, it's a CCE | ||
fb += wa.LocalGet(objParam) | ||
fb += wa.GlobalGet(genGlobalID.forVTable(className)) | ||
fb += wa.Call(genFunctionID.classCastException) | ||
fb += wa.Unreachable | ||
} | ||
|
||
fb += wa.LocalGet(objParam) | ||
if (resultType != watpe.RefType.anyref) | ||
fb += wa.RefCast(resultType) | ||
|
||
fb.buildAndAddToModule() | ||
} | ||
|
||
private def genNewDefaultFunc(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { | ||
val className = clazz.name.name | ||
val classInfo = ctx.getClassInfo(className) | ||
|
@@ -598,6 +661,56 @@ class ClassEmitter(coreSpec: CoreSpec) { | |
fb.buildAndAddToModule() | ||
} | ||
|
||
/** Generate the cast function for a class. | ||
* | ||
* When `asInstanceOfs` are checked, the expression `asInstanceOf[<class>]` | ||
* will be compiled to a CALL to the function generated by this method. | ||
*/ | ||
private def genClassCastFunction(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { | ||
val className = clazz.className | ||
|
||
val resultType = TypeTransformer.transformClassType(className, nullable = true) | ||
|
||
val fb = new FunctionBuilder( | ||
ctx.moduleBuilder, | ||
genFunctionID.asInstance(ClassType(clazz.className, nullable = true)), | ||
makeDebugName(ns.AsInstance, className), | ||
clazz.pos | ||
) | ||
val objParam = fb.addParam("obj", watpe.RefType.anyref) | ||
fb.setResultType(resultType) | ||
|
||
fb.block(resultType) { successLabel => | ||
fb += wa.LocalGet(objParam) | ||
|
||
if (className == SpecialNames.JLNumberClass) { | ||
/* jl.Number is special, because it is the only non-Object *class* | ||
* that is an ancestor of a hijacked class. | ||
*/ | ||
fb += wa.BrOnCast(successLabel, watpe.RefType.anyref, | ||
watpe.RefType.nullable(genTypeID.forClass(SpecialNames.JLNumberClass))) | ||
|
||
/* The `obj` still on the stack will be used for: | ||
* a) the result in the true case | ||
* b) consistency with non-Number in the false case | ||
*/ | ||
|
||
fb += wa.LocalGet(objParam) | ||
fb += wa.Call(genFunctionID.typeTest(DoubleRef)) | ||
fb += wa.BrIf(successLabel) | ||
} else { | ||
fb += wa.BrOnCast(successLabel, watpe.RefType.anyref, resultType) | ||
} | ||
|
||
// If we get here, it's a CCE -- `obj` is still on the stack | ||
fb += wa.GlobalGet(genGlobalID.forVTable(className)) | ||
fb += wa.Call(genFunctionID.classCastException) | ||
fb += wa.Unreachable | ||
} | ||
|
||
fb.buildAndAddToModule() | ||
} | ||
|
||
private def genModuleAccessor(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { | ||
assert(clazz.kind == ClassKind.ModuleClass) | ||
|
||
|
@@ -613,7 +726,10 @@ class ClassEmitter(coreSpec: CoreSpec) { | |
makeDebugName(ns.ModuleAccessor, className), | ||
clazz.pos | ||
) | ||
fb.setResultType(resultType) | ||
if (semantics.moduleInit == CheckedBehavior.Compliant) | ||
fb.setResultType(resultType.toNullable) | ||
else | ||
fb.setResultType(resultType) | ||
|
||
val instanceLocal = fb.addLocal("instance", resultType) | ||
|
||
|
@@ -622,6 +738,30 @@ class ClassEmitter(coreSpec: CoreSpec) { | |
fb += wa.GlobalGet(globalInstanceID) | ||
fb += wa.BrOnNonNull(nonNullLabel) | ||
|
||
// check ongoing initialization | ||
if (semantics.moduleInit != CheckedBehavior.Unchecked) { | ||
val initFlagID = genGlobalID.forModuleInitFlag(className) | ||
|
||
// if being initialized | ||
fb += wa.GlobalGet(initFlagID) | ||
fb.ifThen() { | ||
if (semantics.moduleInit == CheckedBehavior.Compliant) { | ||
Comment on lines
+741
to
+748
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [note] If |
||
// then, return null | ||
fb += wa.RefNull(watpe.HeapType.None) | ||
fb += wa.Return | ||
} else { | ||
// then, throw | ||
fb += wa.GlobalGet(genGlobalID.forVTable(className)) | ||
fb += wa.Call(genFunctionID.throwModuleInitError) | ||
fb += wa.Unreachable // for clarity; technically redundant since the stacks align | ||
} | ||
} | ||
|
||
// mark as being initialized | ||
fb += wa.I32Const(1) | ||
fb += wa.GlobalSet(initFlagID) | ||
} | ||
|
||
// create an instance and call its constructor | ||
fb += wa.Call(genFunctionID.newDefault(className)) | ||
fb += wa.LocalTee(instanceLocal) | ||
|
@@ -685,8 +825,11 @@ class ClassEmitter(coreSpec: CoreSpec) { | |
itableType | ||
) | ||
|
||
if (clazz.hasInstanceTests) | ||
if (clazz.hasInstanceTests) { | ||
genInterfaceInstanceTest(clazz) | ||
if (semantics.asInstanceOfs != CheckedBehavior.Unchecked) | ||
genInterfaceCastFunction(clazz) | ||
} | ||
} | ||
|
||
private def genJSClass(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { | ||
|
@@ -1258,10 +1401,12 @@ object ClassEmitter { | |
// Shared with JS backend -- className | ||
val ModuleAccessor = UTF8String("m.") | ||
val ModuleInstance = UTF8String("n.") | ||
val ModuleInitFlag = UTF8String("ni.") | ||
val JSClassAccessor = UTF8String("a.") | ||
val JSClassValueCache = UTF8String("b.") | ||
val TypeData = UTF8String("d.") | ||
val IsInstance = UTF8String("is.") | ||
val AsInstance = UTF8String("as.") | ||
|
||
// Shared with JS backend -- string | ||
val TopLevelExport = UTF8String("e.") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[note]
We reach here if
objParam
is JS numberobjParam
is neither JS number nor an instance ofjava.lang.Number
typeTest(DoubleRef)
will be true for (1) because it calls thetD: (x) => typeof x === 'number'
.