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

Commit 8e6df1e

Browse files
committed
Use different functions for table entries than for resolved calls.
The real method body goes to a method used for statically resolved calls. That method can take the real `this` type as argument. For table entries, which need to use `(ref any)` as receiver type, we generate a separate function. That bridge function casts down the receiver then performs a tail call to the real function. With this strategy, we do not need to cast down `this` values every time we use them. Since most calls as statically resolved, this also means we don't need any cast at all. For dynamically resolved calls, the overhead should be small thanks to the tail call. This change causes a 30% code size regression for fastLink, but only 6% in fullLink. We should be able to optimize it further by only emitting bridges for methods that actually appear in at least one dispatch table.
1 parent c1252ba commit 8e6df1e

File tree

6 files changed

+65
-38
lines changed

6 files changed

+65
-38
lines changed

Diff for: wasm/src/main/scala/ir2wasm/HelperFunctions.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,7 @@ object HelperFunctions {
549549

550550
val objectClassInfo = ctx.getClassInfo(IRNames.ObjectClass)
551551
instrs ++= objectClassInfo.tableEntries.map { methodName =>
552-
ctx.refFuncWithDeclaration(objectClassInfo.resolvedMethodInfos(methodName).wasmName)
552+
ctx.refFuncWithDeclaration(objectClassInfo.resolvedMethodInfos(methodName).tableEntryName)
553553
}
554554
instrs += STRUCT_NEW(WasmTypeName.WasmStructTypeName.ObjectVTable)
555555
instrs += LOCAL_TEE(arrayTypeDataLocal)

Diff for: wasm/src/main/scala/ir2wasm/WasmBuilder.scala

+57-19
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ class WasmBuilder(coreSpec: CoreSpec) {
337337
val proxyId = ctx.getReflectiveProxyId(proxyInfo.methodName)
338338
List(
339339
I32_CONST(proxyId),
340-
REF_FUNC(proxyInfo.wasmName),
340+
REF_FUNC(proxyInfo.tableEntryName),
341341
STRUCT_NEW(WasmStructTypeName.reflectiveProxy)
342342
)
343343
} :+ ARRAY_NEW_FIXED(WasmArrayTypeName.reflectiveProxies, reflectiveProxies.size)
@@ -417,7 +417,7 @@ class WasmBuilder(coreSpec: CoreSpec) {
417417
classInfo.resolvedMethodInfos.valuesIterator.filter(_.methodName.isReflectiveProxy).toList
418418
val typeDataFieldValues = genTypeDataFieldValues(clazz, reflectiveProxies)
419419
val vtableElems = classInfo.tableEntries.map { methodName =>
420-
REF_FUNC(classInfo.resolvedMethodInfos(methodName).wasmName)
420+
REF_FUNC(classInfo.resolvedMethodInfos(methodName).tableEntryName)
421421
}
422422
val globalVTable =
423423
genTypeDataGlobal(typeRef, vtableTypeName, typeDataFieldValues, vtableElems)
@@ -1068,29 +1068,26 @@ class WasmBuilder(coreSpec: CoreSpec) {
10681068
private def genFunction(
10691069
clazz: LinkedClass,
10701070
method: IRTrees.MethodDef
1071-
)(implicit ctx: WasmContext): WasmFunction = {
1072-
val functionName = Names.WasmFunctionName(
1073-
method.flags.namespace,
1074-
clazz.name.name,
1075-
method.name.name
1076-
)
1071+
)(implicit ctx: WasmContext): Unit = {
1072+
val namespace = method.flags.namespace
1073+
val className = clazz.className
1074+
val methodName = method.methodName
1075+
1076+
val functionName = Names.WasmFunctionName(namespace, className, methodName)
1077+
1078+
val isHijackedClass = ctx.getClassInfo(className).kind == ClassKind.HijackedClass
10771079

1078-
// Receiver type for non-constructor methods needs to be `(ref any)` because params are invariant
1079-
// Otherwise, vtable can't be a subtype of the supertype's subtype
1080-
// Constructor can use the exact type because it won't be registered to vtables.
10811080
val receiverTyp =
1082-
if (method.flags.namespace.isStatic)
1081+
if (namespace.isStatic)
10831082
None
1084-
else if (clazz.kind == ClassKind.HijackedClass)
1085-
Some(transformType(IRTypes.BoxedClassToPrimType(clazz.name.name)))
1086-
else if (method.flags.namespace.isConstructor)
1087-
Some(WasmRefType.nullable(WasmTypeName.WasmStructTypeName.forClass(clazz.name.name)))
1083+
else if (isHijackedClass)
1084+
Some(transformType(IRTypes.BoxedClassToPrimType(className)))
10881085
else
1089-
Some(WasmRefType.any)
1086+
Some(transformType(IRTypes.ClassType(className)))
10901087

10911088
// Prepare for function context, set receiver and parameters
10921089
implicit val fctx = WasmFunctionContext(
1093-
Some(clazz.className),
1090+
Some(className),
10941091
functionName,
10951092
receiverTyp,
10961093
method.args,
@@ -1101,7 +1098,48 @@ class WasmBuilder(coreSpec: CoreSpec) {
11011098
val body = method.body.getOrElse(throw new Exception("abstract method cannot be transformed"))
11021099
WasmExpressionBuilder.generateIRBody(body, method.resultType)
11031100

1104-
fctx.buildAndAddToContext(useFunctionTypeInMainRecType = true)
1101+
fctx.buildAndAddToContext()
1102+
1103+
if (namespace == IRTrees.MemberNamespace.Public && !isHijackedClass) {
1104+
/* Also generate the bridge that is stored in the table entries. In table
1105+
* entries, the receiver type is always `(ref any)`.
1106+
*
1107+
* TODO: generate this only when the method is actually referred to from
1108+
* at least one table.
1109+
*/
1110+
1111+
implicit val fctx = WasmFunctionContext(
1112+
Some(className),
1113+
WasmFunctionName.forTableEntry(className, methodName),
1114+
Some(WasmRefType.any),
1115+
method.args,
1116+
method.resultType
1117+
)
1118+
1119+
import fctx.instrs
1120+
1121+
val receiverLocal :: paramLocals = fctx.paramIndices: @unchecked
1122+
1123+
// Load and cast down the receiver
1124+
instrs += LOCAL_GET(receiverLocal)
1125+
receiverTyp match {
1126+
case Some(Types.WasmRefType(_, WasmHeapType.Any)) =>
1127+
() // no cast necessary
1128+
case Some(receiverTyp: Types.WasmRefType) =>
1129+
instrs += REF_CAST(receiverTyp)
1130+
case _ =>
1131+
throw new AssertionError(s"Unexpected receiver type $receiverTyp")
1132+
}
1133+
1134+
// Load the other parameters
1135+
for (paramLocal <- paramLocals)
1136+
instrs += LOCAL_GET(paramLocal)
1137+
1138+
// Call the statically resolved method
1139+
instrs += RETURN_CALL(functionName)
1140+
1141+
fctx.buildAndAddToContext(useFunctionTypeInMainRecType = true)
1142+
}
11051143
}
11061144

11071145
private def transformField(

Diff for: wasm/src/main/scala/ir2wasm/WasmExpressionBuilder.scala

-15
Original file line numberDiff line numberDiff line change
@@ -1596,21 +1596,6 @@ private class WasmExpressionBuilder private (
15961596
case _ => t.tpe
15971597
}
15981598

1599-
/* If the receiver is a Class/ModuleClass, its wasm type will be declared
1600-
* as `(ref any)`, and therefore we must cast it down.
1601-
*/
1602-
fixedTpe match {
1603-
case IRTypes.ClassType(className) if className != IRNames.ObjectClass =>
1604-
val info = ctx.getClassInfo(className)
1605-
if (info.kind.isClass) {
1606-
instrs += REF_CAST(Types.WasmRefType(WasmStructTypeName.forClass(className)))
1607-
} else if (info.isInterface) {
1608-
instrs += REF_CAST(Types.WasmRefType(Types.WasmHeapType.ObjectType))
1609-
}
1610-
case _ =>
1611-
()
1612-
}
1613-
16141599
fixedTpe
16151600
}
16161601

Diff for: wasm/src/main/scala/wasm4s/Instructions.scala

+1
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ object WasmInstr {
253253
with StackPolymorphicInstr
254254
case object RETURN extends WasmSimpleInstr("return", 0x0F) with StackPolymorphicInstr
255255
case class CALL(i: WasmFunctionName) extends WasmFuncInstr("call", 0x10, i)
256+
case class RETURN_CALL(i: WasmFunctionName) extends WasmFuncInstr("return_call", 0x12, i)
256257
case class THROW(i: WasmTagName) extends WasmTagInstr("throw", 0x08, i) with StackPolymorphicInstr
257258
case object THROW_REF extends WasmSimpleInstr("throw_ref", 0x0A) with StackPolymorphicInstr
258259
case class TRY_TABLE(i: BlockType, cs: List[CatchClause], label: Option[WasmLabelName] = None)

Diff for: wasm/src/main/scala/wasm4s/Names.scala

+3
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ object Names {
118118
}
119119
}
120120

121+
def forTableEntry(clazz: IRNames.ClassName, method: IRNames.MethodName): WasmFunctionName =
122+
new WasmFunctionName("t#" + clazz.nameString, method.nameString)
123+
121124
def forExport(exportedName: String): WasmFunctionName =
122125
new WasmFunctionName("export", exportedName)
123126

Diff for: wasm/src/main/scala/wasm4s/WasmContext.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ class WasmContext(val module: WasmModule) extends TypeDefinableWasmContext {
576576
instrs += WasmInstr.I32_CONST(idx)
577577

578578
for (method <- iface.tableEntries)
579-
instrs += refFuncWithDeclaration(resolvedMethodInfos(method).wasmName)
579+
instrs += refFuncWithDeclaration(resolvedMethodInfos(method).tableEntryName)
580580
instrs += WasmInstr.STRUCT_NEW(WasmTypeName.WasmStructTypeName.forITable(iface.name))
581581
instrs += WasmInstr.ARRAY_SET(WasmTypeName.WasmArrayTypeName.itables)
582582
}
@@ -596,7 +596,7 @@ class WasmContext(val module: WasmModule) extends TypeDefinableWasmContext {
596596
instrs += I32_CONST(getItableIdx(interfaceName))
597597

598598
for (method <- interfaceInfo.tableEntries)
599-
instrs += refFuncWithDeclaration(resolvedMethodInfos(method).wasmName)
599+
instrs += refFuncWithDeclaration(resolvedMethodInfos(method).tableEntryName)
600600
instrs += STRUCT_NEW(WasmStructTypeName.forITable(interfaceName))
601601
instrs += ARRAY_SET(WasmArrayTypeName.itables)
602602
}
@@ -873,7 +873,7 @@ object WasmContext {
873873
val ownerClass: IRNames.ClassName,
874874
val methodName: IRNames.MethodName
875875
) {
876-
val wasmName = WasmFunctionName(IRTrees.MemberNamespace.Public, ownerClass, methodName)
876+
val tableEntryName = WasmFunctionName.forTableEntry(ownerClass, methodName)
877877

878878
private var effectivelyFinal: Boolean = true
879879

0 commit comments

Comments
 (0)