8000 Merge the itables into the corresponding vtables. · sjrd/scala-js@1b989f2 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1b989f2

Browse files
committed
Merge the itables into the corresponding vtables.
This is a trade-off on memory and code size. The big advantage is that run-time instances of our classes use one fewer word each, since we do not have an `itables` field anymore. There could be some execution time improvements as a side effect, since we have one fewer word to initialize. There are two disadvantages, but IMO they are comparatively minor: * We cannot use the "one empty itables to rule them all" trick anymore. Classes that implement no interfaces need to list their N `null` values. This has both a code size impact and a constant run-time memory cost. * We cannot share the itables of array classes either. This has no impact on code size (there is a single place in the code where we initialize those fields), but it does add run-time footprint to the vtables of each array type that gets created. Interface method calls are not impacted, neither in the code we generate, nor in their run-time performance. Virtual method calls are only impacted insofar as the index of virtual method pointers is bumped up by the number of itable slots. This can add one more byte to some calls, due to the var-length encoding. If we get very unlucky, it could also push some vtable method pointers past a pre-fetch boundary, slowing down the calls at run-time. But I don't really think that's plausible.
1 parent bb4d753 commit 1b989f2

File tree

9 files changed

+97
-183
lines changed

9 files changed

+97
-183
lines changed

linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala

Lines changed: 53 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class ClassEmitter(coreSpec: CoreSpec) {
4949
if (classInfo.hasRuntimeTypeInfo && !(clazz.kind.isClass && clazz.hasDirectInstances)) {
5050
// Gen typeData -- for concrete Scala classes, we do it as part of the vtable generation instead
5151
val typeDataFieldValues = genTypeDataFieldValues(clazz, Nil)
52-
genTypeDataGlobal(clazz.className, genTypeID.typeData, typeDataFieldValues, Nil)
52+
genTypeDataGlobal(clazz.className, genTypeID.typeData, typeDataFieldValues, Nil, Nil)
5353
}
5454

5555
// Declare static fields
@@ -138,15 +138,6 @@ class ClassEmitter(coreSpec: CoreSpec) {
138138
}
139139
}
140140

141-
/** Generate common itable global for all array classes. */
142-
def genGlobalArrayClassItable()(implicit ctx: WasmContext): Unit = {
143-
genGlobalClassItable(
144-
genGlobalID.arrayClassITable, ctx.getClassInfo(ObjectClass),
145-
List(SerializableClass, CloneableClass),
146-
OriginalName(genGlobalID.arrayClassITable.toString())
147-
)
148-
}
149-
150141
private def genIsJSClassInstanceFunction(clazz: LinkedClass)(
151142
implicit ctx: WasmContext): Option[wanme.FunctionID] = {
152143
implicit val noPos: Position = Position.NoPosition
@@ -330,10 +321,11 @@ class ClassEmitter(coreSpec: CoreSpec) {
330321
}
331322

332323
private def genTypeDataGlobal(className: ClassName, typeDataTypeID: wanme.TypeID,
333-
typeDataFieldValues: List[wa.Instr], vtableElems: List[wa.RefFunc])(
324+
typeDataFieldValues: List[wa.Instr], itableSlots: List[wa.Instr],
325+
vtableElems: List[wa.RefFunc])(
334326
implicit ctx: WasmContext): Unit = {
335327
val instrs: List[wa.Instr] =
336-
typeDataFieldValues ::: vtableElems ::: wa.StructNew(typeDataTypeID) :: Nil
328+
typeDataFieldValues ::: itableSlots ::: vtableElems ::: wa.StructNew(typeDataTypeID) :: Nil
337329
ctx.addGlobal(
338330
wamod.Global(
339331
genGlobalID.forVTable(className),
@@ -356,19 +348,17 @@ class ClassEmitter(coreSpec: CoreSpec) {
356348

357349
val isAbstractClass = !clazz.hasDirectInstances
358350

359-
// Generate the vtable and itable for concrete classes
351+
// Generate the vtable for concrete classes
360352
if (!isAbstractClass) {
361353
// Generate an actual vtable, which we integrate into the typeData
362354
val reflectiveProxies =
363355
classInfo.resolvedMethodInfos.valuesIterator.filter(_.methodName.isReflectiveProxy).toList
364356
val typeDataFieldValues = genTypeDataFieldValues(clazz, reflectiveProxies)
357+
val itableSlots = genItableSlots(classInfo, clazz.ancestors)
365358
val vtableElems = classInfo.tableEntries.map { methodName =>
366359
wa.RefFunc(classInfo.resolvedMethodInfos(methodName).tableEntryID)
367360
}
368-
genTypeDataGlobal(className, vtableTypeID, typeDataFieldValues, vtableElems)
369-
370-
// Generate the itable
371-
genGlobalClassItable(clazz)
361+
genTypeDataGlobal(className, vtableTypeID, typeDataFieldValues, itableSlots, vtableElems)
372362
}
373363

374364
// Declare the struct type for the class
@@ -378,12 +368,6 @@ class ClassEmitter(coreSpec: CoreSpec) {
378368
watpe.RefType(vtableTypeID),
379369
isMutable = false
380370
)
381-
val itablesField = watpe.StructField(
382-
genFieldID.objStruct.itables,
383-
itablesOriginalName,
384-
watpe.RefType(genTypeID.itables),
385-
isMutable = false
386-
)
387371
val fields = classInfo.allFieldDefs.map { field =>
388372
watpe.StructField(
389373
genFieldID.forClassInstanceField(field.name.name),
@@ -405,7 +389,7 @@ class ClassEmitter(coreSpec: CoreSpec) {
405389
}
406390
val structTypeID = genTypeID.forClass(className)
407391
val superType = clazz.superClass.map(s => genTypeID.forClass(s.name))
408-
val structType = watpe.StructType(vtableField :: itablesField :: fields ::: jlClassDataField)
392+
val structType = watpe.StructType(vtableField :: fields ::: jlClassDataField)
409393
val subType = watpe.SubType(
410394
structTypeID,
411395
makeDebugName(ns.ClassInstance, className),
@@ -461,6 +445,14 @@ class ClassEmitter(coreSpec: CoreSpec) {
461445
implicit ctx: WasmContext): wanme.TypeID = {
462446
val className = classInfo.name
463447
val typeID = genTypeID.forVTable(className)
448+
val itableSlotFields = (0 until ctx.itablesLength).map { i =>
449+
watpe.StructField(
450+
genFieldID.vtableStruct.itableSlot(i),
451+
OriginalName.NoOriginalName,
452+
watpe.RefType.nullable(watpe.HeapType.Struct),
453+
isMutable = false
454+
)
455+
}.toList
464456
val vtableFields =
465457
classInfo.tableEntries.map { methodName =>
466458
watpe.StructField(
@@ -474,7 +466,7 @@ class ClassEmitter(coreSpec: CoreSpec) {
474466
case None => genTypeID.typeData
475467
case Some(s) => genTypeID.forVTable(s.name)
476468
}
477-
val structType = watpe.StructType(ctx.coreLib.typeDataStructFields ::: vtableFields)
469+
val structType = watpe.StructType(ctx.coreLib.typeDataStructFields ::: itableSlotFields ::: vtableFields)
478470
val subType = watpe.SubType(
479471
typeID,
480472
makeDebugName(ns.VTable, className),
@@ -525,10 +517,10 @@ class ClassEmitter(coreSpec: CoreSpec) {
525517
/* Test whether the itable at the target interface's slot is indeed an
526518
* instance of that interface's itable struct type.
527519
*/
528-
fb += wa.StructGet(genTypeID.ObjectStruct, genFieldID.objStruct.itables)
520+
fb += wa.StructGet(genTypeID.ObjectStruct, genFieldID.objStruct.vtable)
529521
fb += wa.StructGet(
530-
genTypeID.itables,
531-
genFieldID.itablesStruct.itableSlot(classInfo.itableIdx)
522+
genTypeID.ObjectVTable,
523+
genFieldID.vtableStruct.itableSlot(classInfo.itableIdx)
532524
)
533525
fb += wa.RefTest(watpe.RefType(genTypeID.forITable(className)))
534526
fb += wa.Return
@@ -642,12 +634,6 @@ class ClassEmitter(coreSpec: CoreSpec) {
642634
fb.setResultType(watpe.RefType(structTypeID))
643635

644636
fb += wa.GlobalGet(genGlobalID.forVTable(className))
645-
646-
if (classInfo.classImplementsAnyInterface)
647-
fb += wa.GlobalGet(genGlobalID.forITable(className))
648-
else
649-
fb += wa.GlobalGet(genGlobalID.emptyITable)
650-
651637
classInfo.allFieldDefs.foreach { f =>
652638
fb += genZeroOf(f.ftpe)
653639
}
@@ -689,9 +675,8 @@ class ClassEmitter(coreSpec: CoreSpec) {
689675
fb += wa.RefCast(structRefType)
690676
fb += wa.LocalSet(fromTypedLocal)
691677

692-
// Push vtable and itables on the stack (there is at least Cloneable in the itables)
678+
// Push the vtable on the stack
693679
fb += wa.GlobalGet(genGlobalID.forVTable(className))
694-
fb += wa.GlobalGet(genGlobalID.forITable(className))
695680

696681
// Push every field of `fromTyped` on the stack
697682
info.allFieldDefs.foreach { field =>
@@ -822,55 +807,6 @@ class ClassEmitter(coreSpec: CoreSpec) {
822807
fb.buildAndAddToModule()
823808
}
824809

825-
/** Generates the global instance of the class itable.
826-
*
827-
* If the class implements no interface at all, we skip this step. Instead,
828-
* we will use the unique `emptyITable` as itable for this class.
829-
*/
830-
private def genGlobalClassItable(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
831-
val className = clazz.className
832-
val classInfo = ctx.getClassInfo(className)
833-
if (classInfo.classImplementsAnyInterface) {
834-
genGlobalClassItable(
835-
genGlobalID.forITable(className),
836-
classInfo,
837-
clazz.ancestors,
838-
makeDebugName(ns.ITable, classInfo.name)
839-
)
840-
}
841-
}
842-
843-
private def genGlobalClassItable(classITableGlobalID: wanme.GlobalID,
844-
classInfoForResolving: WasmContext.ClassInfo, ancestors: List[ClassName],
845-
originalName: OriginalName)(
846-
implicit ctx: WasmContext): Unit = {
847-
val itablesInit = Array.fill[List[wa.Instr]](ctx.itablesLength) {
848-
List(wa.RefNull(watpe.HeapType.Struct))
849-
}
850-
val resolvedMethodInfos = classInfoForResolving.resolvedMethodInfos
851-
852-
for {
853-
ancestor <- ancestors
854-
// Use getClassInfoOption in case the reachability analysis got rid of those interfaces
855-
interfaceInfo <- ctx.getClassInfoOption(ancestor)
856-
if interfaceInfo.isInterface
857-
} {
858-
val init = interfaceInfo.tableEntries.map { method =>
859-
wa.RefFunc(resolvedMethodInfos(method).tableEntryID)
860-
} :+ wa.StructNew(genTypeID.forITable(ancestor))
861-
itablesInit(interfaceInfo.itableIdx) = init
862-
}
863-
864-
val global = wamod.Global(
865-
classITableGlobalID,
866-
originalName,
867-
isMutable = false,
868-
watpe.RefType(genTypeID.itables),
869-
wa.Expr(itablesInit.flatten.toList :+ wa.StructNew(genTypeID.itables))
870-
)
871-
ctx.addGlobal(global)
872-
}
873-
874810
private def genInterface(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
875811
assert(clazz.kind == ClassKind.Interface)
876812
// gen itable type
@@ -1563,5 +1499,36 @@ object ClassEmitter {
15631499

15641500
private val thisOriginalName: OriginalName = OriginalName("this")
15651501
private val vtableOriginalName: OriginalName = OriginalName("vtable")
1566-
private val itablesOriginalName: OriginalName = OriginalName("itables")
1502+
1503+
/** Generates the itable slots of a class.
1504+
*
1505+
* @param classInfoForResolving
1506+
* The `ClassInfo` from which to resolve methods. This is normally the
1507+
* class info of the class for which we are generating the itable slots.
1508+
* For the itable slots of array classes, it must be the info of `jl.Object`.
1509+
* @param ancestors
1510+
* The list of ancestors of the target class.
1511+
*/
1512+
def genItableSlots(classInfoForResolving: WasmContext.ClassInfo,
1513+
ancestors: List[ClassName])(
1514+
implicit ctx: WasmContext): List[wa.Instr] = {
1515+
val itablesInit = Array.fill[List[wa.Instr]](ctx.itablesLength) {
1516+
List(wa.RefNull(watpe.HeapType.Struct))
1517+
}
1518+
val resolvedMethodInfos = classInfoForResolving.resolvedMethodInfos
1519+
1520+
for {
1521+
ancestor <- ancestors
1522+
// Use getClassInfoOption in case the reachability analysis got rid of those interfaces
1523+
interfaceInfo <- ctx.getClassInfoOption(ancestor)
1524+
if interfaceInfo.isInterface
1525+
} {
1526+
val init = interfaceInfo.tableEntries.map { method =>
1527+
wa.RefFunc(resolvedMethodInfos(method).tableEntryID)
1528+
} :+ wa.StructNew(genTypeID.forITable(ancestor))
1529+
itablesInit(interfaceInfo.itableIdx) = init
1530+
}
1531+
1532+
itablesInit.flatten.toList
1533+
}
15671534
}

linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala

Lines changed: 8 additions & 40 deletions
< 10000 td data-grid-cell-id="diff-d46db39061426a9ca80e9cf67b0d20b0d43e85e9b37c03274ccbea7a5ea2a355-181-176-0" data-selected="false" role="gridcell" style="background-color:var(--diffBlob-deletionNum-bgColor, var(--diffBlob-deletion-bgColor-num));text-align:center" tabindex="-1" valign="top" class="focusable-grid-cell diff-line-number position-relative left-side">181
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) {
117117

118118
genImports()
119119

120-
genEmptyITable()
121120
genPrimitiveTypeDataGlobals()
122121

123122
genHelperDefinitions()
@@ -175,20 +174,6 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) {
175174
ArrayType(FieldType(RefType(genTypeID.typeData), isMutable = false))
176175
)
177176

178-
genCoreType(
179-
genTypeID.itables,
180-
StructType(
-
(0 until ctx.itablesLength).map { i =>
182-
StructField(
183-
genFieldID.itablesStruct.itableSlot(i),
184-
OriginalName.NoOriginalName,
185-
RefType.nullable(HeapType.Struct),
186-
isMutable = false
187-
)
188-
}.toList
189-
)
190-
)
191-
192177
genCoreType(
193178
genTypeID.reflectiveProxies,
194179
ArrayType(FieldType(RefType(genTypeID.reflectiveProxy), isMutable = false))
@@ -234,12 +219,6 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) {
234219
RefType(vtableTypeID),
235220
isMutable = false
236221
)
237-
val itablesField = StructField(
238-
genFieldID.objStruct.itables,
239-
OriginalName(genFieldID.objStruct.itables.toString()),
240-
RefType(genTypeID.itables),
241-
isMutable = false
242-
)
243222

244223
val typeRefsWithArrays: List[(TypeID, TypeID)] =
245224
List(
@@ -266,7 +245,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) {
266245

267246
val superType = genTypeID.ObjectStruct
268247
val structType = StructType(
269-
List(vtableField, itablesField, underlyingArrayField)
248+
List(vtableField, underlyingArrayField)
270249
)
271250
val subType = SubType(structTypeID, origName, isFinal = true, Some(superType), structType)
272251
ctx.mainRecType.addSubType(subType)
@@ -431,18 +410,6 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) {
431410

432411
// --- Global definitions ---
433412

434-
private def genEmptyITable()(implicit ctx: WasmContext): Unit = {
435-
ctx.addGlobal(
436-
Global(
437-
genGlobalID.emptyITable,
438-
OriginalName(genGlobalID.emptyITable.toString()),
439-
isMutable = false,
440-
RefType(genTypeID.itables),
441-
Expr(List(StructNewDefault(genTypeID.itables)))
442-
)
443-
)
444-
}
445-
446413
private def genPrimitiveTypeDataGlobals()(implicit ctx: WasmContext): Unit = {
447414
import genFieldID.typeData._
448415

@@ -498,7 +465,6 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) {
498465
val boxStruct = genTypeID.forClass(boxClassName)
499466
val instrs: List[Instr] = List(
500467
GlobalGet(genGlobalID.forVTable(boxClassName)),
501-
GlobalGet(genGlobalID.forITable(boxClassName)),
502468
zeroValueInstr,
503469
StructNew(boxStruct)
504470
)
@@ -1566,7 +1532,11 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) {
15661532
// reflectiveProxies, empty since all methods of array classes exist in jl.Object
15671533
fb += ArrayNewFixed(genTypeID.reflectiveProxies, 0)
15681534

1535+
// itable slots
15691536
val objectClassInfo = ctx.getClassInfo(ObjectClass)
1537+
fb ++= ClassEmitter.genItableSlots(objectClassInfo, List(SerializableClass, CloneableClass))
1538+
1539+
// vtable items
15701540
fb ++= objectClassInfo.tableEntries.map { methodName =>
15711541
ctx.refFuncWithDeclaration(objectClassInfo.resolvedMethodInfos(methodName).tableEntryID)
15721542
}
@@ -2256,17 +2226,16 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) {
22562226
fb += StructGet(genTypeID.ClassStruct, genFieldID.classData)
22572227
fb += LocalTee(componentTypeDataLocal)
22582228

2259-
// Load the vtable and itables of the ArrayClass instance we will create
2229+
// Load the vtable of the ArrayClass instance we will create
22602230
fb += I32Const(1)
2261-
fb += Call(genFunctionID.arrayTypeData) // vtable
2262-
fb += GlobalGet(genGlobalID.arrayClassITable) // itables
2231+
fb += Call(genFunctionID.arrayTypeData)
22632232

22642233
// Load the length
22652234
fb += LocalGet(lengthParam)
22662235

22672236
// switch (componentTypeData.kind)
22682237
val switchClauseSig = FunctionType(
2269-
List(arrayTypeDataType, RefType(genTypeID.itables), Int32),
2238+
List(arrayTypeDataType, Int32),
22702239
List(RefType(genTypeID.ObjectStruct))
22712240
)
22722241
fb.switch(switchClauseSig) { () =>
@@ -2807,7 +2776,6 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) {
28072776
// Build the result arrayStruct
28082777
fb += LocalGet(fromLocal)
28092778
fb += StructGet(arrayStructTypeID, genFieldID.objStruct.vtable) // vtable
2810-
fb += GlobalGet(genGlobalID.arrayClassITable) // itable
28112779
fb += LocalGet(resultUnderlyingLocal)
28122780
fb += StructNew(arrayStructTypeID)
28132781

linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ final class Emitter(config: Emitter.Config) {
9090
coreLib.genPreClasses()
9191
sortedClasses.foreach(classEmitter.genClassDef(_))
9292
topLevelExports.foreach(classEmitter.genTopLevelExport(_))
93-
classEmitter.genGlobalArrayClassItable()
9493
coreLib.genPostClasses()
9594

9695
genStartFunction(sortedClasses, moduleInitializers, topLevelExports)

linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,10 +1190,10 @@ private class FunctionEmitter private (
11901190
// Generates an itable-based dispatch.
11911191
def genITableDispatch(): Unit = {
11921192
fb += wa.LocalGet(receiverLocalForDispatch)
1193-
fb += wa.StructGet(genTypeID.ObjectStruct, genFieldID.objStruct.itables)
1193+
fb += wa.StructGet(genTypeID.ObjectStruct, genFieldID.objStruct.vtable)
11941194
fb += wa.StructGet(
1195-
genTypeID.itables,
1196-
genFieldID.itablesStruct.itableSlot(receiverClassInfo.itableIdx)
1195+
genTypeID.ObjectVTable,
1196+
genFieldID.vtableStruct.itableSlot(receiverClassInfo.itableIdx)
11971197
)
11981198
fb += wa.RefCast(watpe.RefType(genTypeID.forITable(receiverClassInfo.name)))
11991199
fb += wa.StructGet(
@@ -2722,7 +2722,6 @@ private class FunctionEmitter private (
27222722

27232723
fb += wa.LocalSet(primLocal)
27242724
fb += wa.GlobalGet(genGlobalID.forVTable(boxClassName))
2725-
fb += wa.GlobalGet(genGlobalID.forITable(boxClassName))
27262725
fb += wa.LocalGet(primLocal)
27272726
fb += wa.StructNew(genTypeID.forClass(boxClassName))
27282727

@@ -3001,7 +3000,7 @@ private class FunctionEmitter private (
30013000

30023001
markPosition(tree)
30033002

3004-
genLoadVTableAndITableForArray(fb, arrayTypeRef)
3003+
genLoadArrayTypeData(fb, arrayTypeRef) // vtable
30053004

30063005
// Create the underlying array
30073006
genTree(length, IntType)

0 commit comments

Comments
 (0)
0