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

Commit dec5985

Browse files
committed
Fix #83: Support clone() for array classes.
Also closes #72.
1 parent d379e39 commit dec5985

9 files changed

+182
-42
lines changed

Diff for: build.sbt

+1-8
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,6 @@ lazy val `scalajs-test-suite` = project
228228

229229
lazy val IgnoredTestNames: Set[String] = {
230230
Set(
231-
// RuntimeError: dereferencing a null pointer
232-
"org.scalajs.testsuite.javalib.util.Base64Test",
233231
// RuntimeError: illegal cast
234232
"org.scalajs.testsuite.compiler.RegressionTest",
235233
"org.scalajs.testsuite.niocharset.UTF8Test",
@@ -267,12 +265,7 @@ lazy val IgnoredTestNames: Set[String] = {
267265
// throwablesAreTrueErrors failed: org.junit.ComparisonFailure: expected:<[object [Error]]> but was:<[object [Object]]>
268266
// throwablesAreJSErrors failed: java.lang.AssertionError: null
269267
"org.scalajs.testsuite.javalib.lang.ThrowableJSTest",
270-
// CloneNotSupportedException
271-
"org.scalajs.testsuite.javalib.lang.ThrowablesTest",
272-
"org.scalajs.testsuite.javalib.math.RoundingModeTest",
273-
"org.scalajs.testsuite.javalib.util.BitSetTest",
274-
"org.scalajs.testsuite.javalib.util.concurrent.ConcurrentHashMapTest",
275-
"org.scalajs.testsuite.javalib.util.concurrent.TimeUnitTest",
268+
// keepBreakToLabelWithinFinallyBlock_Issue2689 failed: java.lang.AssertionError: expected:<2> but was:<1>
276269
"org.scalajs.testsuite.compiler.OptimizerTest",
277270
// nonUnitBoxedPrimitiveValuesAreSerializable failed: java.lang.AssertionError: Boolean
278271
"org.scalajs.testsuite.javalib.io.SerializableTest",

Diff for: wasm/src/main/scala/converters/WasmBinaryWriter.scala

+4
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,10 @@ final class WasmBinaryWriter(module: WasmModule, emitDebugInfo: Boolean) {
441441
writeTypeIdx(buf, typeIdx)
442442
buf.u32(length)
443443

444+
case ARRAY_COPY(destType, srcType) =>
445+
writeTypeIdx(buf, destType)
446+
writeTypeIdx(buf, srcType)
447+
444448
case BR_ON_CAST(labelIdx, from, to) =>
445449
writeBrOnCast(labelIdx, from, to)
446450
case BR_ON_CAST_FAIL(labelIdx, from, to) =>

Diff for: wasm/src/main/scala/converters/WasmTextWriter.scala

+4
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,10 @@ class WasmTextWriter {
396396
b.appendElement(typeIdx.show)
397397
b.appendElement(Integer.toUnsignedString(length))
398398

399+
case ARRAY_COPY(destType, srcType) =>
400+
b.appendElement(destType.show)
401+
b.appendElement(srcType.show)
402+
399403
case BR_ON_CAST(labelIdx, from, to) =>
400404
writeLabelIdx(labelIdx)
401405
writeType(from)

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

+95-3
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,42 @@ object HelperFunctions {
497497
instrs += REF_NULL(WasmHeapType.None) // name
498498
instrs += REF_NULL(WasmHeapType.None) // classOf
499499
instrs += REF_NULL(WasmHeapType.None) // arrayOf
500-
instrs += REF_NULL(WasmHeapType(ctx.cloneFunctionTypeName)) // clone
500+
501+
// clone
502+
fctx.switch(WasmRefType(ctx.cloneFunctionTypeName)) { () =>
503+
instrs += LOCAL_GET(typeDataParam)
504+
instrs += STRUCT_GET(WasmStructTypeName.typeData, WasmFieldIdx.typeData.kindIdx)
505+
}(
506+
List(KindBoolean) -> { () =>
507+
instrs += ctx.refFuncWithDeclaration(WasmFunctionName.clone(IRTypes.BooleanRef))
508+
},
509+
List(KindChar) -> { () =>
510+
instrs += ctx.refFuncWithDeclaration(WasmFunctionName.clone(IRTypes.CharRef))
511+
},
512+
List(KindByte) -> { () =>
513+
instrs += ctx.refFuncWithDeclaration(WasmFunctionName.clone(IRTypes.ByteRef))
514+
},
515+
List(KindShort) -> { () =>
516+
instrs += ctx.refFuncWithDeclaration(WasmFunctionName.clone(IRTypes.ShortRef))
517+
},
518+
List(KindInt) -> { () =>
519+
instrs += ctx.refFuncWithDeclaration(WasmFunctionName.clone(IRTypes.IntRef))
520+
},
521+
List(KindLong) -> { () =>
522+
instrs += ctx.refFuncWithDeclaration(WasmFunctionName.clone(IRTypes.LongRef))
523+
},
524+
List(KindFloat) -> { () =>
525+
instrs += ctx.refFuncWithDeclaration(WasmFunctionName.clone(IRTypes.FloatRef))
526+
},
527+
List(KindDouble) -> { () =>
528+
instrs += ctx.refFuncWithDeclaration(WasmFunctionName.clone(IRTypes.DoubleRef))
529+
}
530+
) { () =>
531+
instrs += ctx.refFuncWithDeclaration(
532+
WasmFunctionName.clone(IRTypes.ClassRef(IRNames.ObjectClass))
533+
)
534+
}
535+
501536
instrs ++= ctx
502537
.calculateGlobalVTable(IRNames.ObjectClass)
503538
.map(method => WasmInstr.REF_FUNC(method.name))
@@ -1242,8 +1277,7 @@ object HelperFunctions {
12421277

12431278
// Load the vtable and itable or the resulting array on the stack
12441279
instrs += LOCAL_GET(arrayTypeDataParam) // vtable
1245-
// TODO: this should not be null because of Serializable and Cloneable
1246-
instrs += REF_NULL(Types.WasmHeapType(WasmArrayType.itables.name)) // itable
1280+
instrs += GLOBAL_GET(WasmGlobalName.arrayClassITable) // itable
12471281

12481282
// Load the first length
12491283
instrs += LOCAL_GET(lengthsParam)
@@ -1456,6 +1490,64 @@ object HelperFunctions {
14561490
}
14571491
}
14581492

1493+
/** Generates the clone function for the given array class. */
1494+
def genArrayCloneFunction(arrayTypeRef: IRTypes.ArrayTypeRef)(implicit ctx: WasmContext): Unit = {
1495+
import WasmTypeName._
1496+
1497+
assert(
1498+
arrayTypeRef.dimensions == 1,
1499+
s"Should not create a specific clone function for the multi-dims array type $arrayTypeRef"
1500+
)
1501+
1502+
val fctx = WasmFunctionContext(
1503+
Names.WasmFunctionName.clone(arrayTypeRef.base),
1504+
List("from" -> WasmRefType(WasmHeapType.ObjectType)),
1505+
List(WasmRefType(WasmHeapType.ObjectType))
1506+
)
1507+
val List(fromParam) = fctx.paramIndices
1508+
import fctx.instrs
1509+
1510+
val arrayStructTypeName = WasmStructTypeName.forArrayClass(arrayTypeRef)
1511+
val arrayClassType = WasmRefType(arrayStructTypeName)
1512+
1513+
val underlyingArrayTypeName = WasmArrayTypeName.underlyingOf(arrayTypeRef)
1514+
val underlyingArrayType = WasmRefType(underlyingArrayTypeName)
1515+
1516+
val fromLocal = fctx.addSyntheticLocal(arrayClassType)
1517+
val fromUnderlyingLocal = fctx.addSyntheticLocal(underlyingArrayType)
1518+
val lengthLocal = fctx.addSyntheticLocal(WasmInt32)
1519+
val resultUnderlyingLocal = fctx.addSyntheticLocal(underlyingArrayType)
1520+
1521+
// Cast down the from argument
1522+
instrs += LOCAL_GET(fromParam)
1523+
instrs += REF_CAST(arrayClassType)
1524+
instrs += LOCAL_TEE(fromLocal)
1525+
1526+
// Load the underlying array
1527+
instrs += STRUCT_GET(arrayStructTypeName, WasmFieldIdx.uniqueRegularField)
1528+
instrs += LOCAL_TEE(fromUnderlyingLocal)
1529+
1530+
// Make a copy of the underlying array
1531+
instrs += ARRAY_LEN
1532+
instrs += LOCAL_TEE(lengthLocal)
1533+
instrs += ARRAY_NEW_DEFAULT(underlyingArrayTypeName)
1534+
instrs += LOCAL_TEE(resultUnderlyingLocal) // also dest for array.copy
1535+
instrs += I32_CONST(0) // destOffset
1536+
instrs += LOCAL_GET(fromUnderlyingLocal) // src
1537+
instrs += I32_CONST(0) // srcOffset
1538+
instrs += LOCAL_GET(lengthLocal) // length
1539+
instrs += ARRAY_COPY(underlyingArrayTypeName, underlyingArrayTypeName)
1540+
1541+
// Build the result arrayStruct
1542+
instrs += LOCAL_GET(fromLocal)
1543+
instrs += STRUCT_GET(arrayStructTypeName, WasmFieldIdx.vtable) // vtable
1544+
instrs += GLOBAL_GET(WasmGlobalName.arrayClassITable) // itable
1545+
instrs += LOCAL_GET(resultUnderlyingLocal)
1546+
instrs += STRUCT_NEW(arrayStructTypeName)
1547+
1548+
fctx.buildAndAddToContext()
1549+
}
1550+
14591551
def genNewDefault(clazz: LinkedClass)(implicit ctx: TypeDefinableWasmContext): Unit = {
14601552
val className = clazz.name.name
14611553
val classInfo = ctx.getClassInfo(className)

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

+40-26
Original file line numberDiff line numberDiff line change
@@ -104,19 +104,22 @@ class WasmBuilder {
104104
isMutable = false
105105
)
106106

107-
val typeRefsWithArrays: List[(WasmStructTypeName, WasmArrayType)] = List(
108-
(WasmStructTypeName.BooleanArray, WasmArrayType.i8Array),
109-
(WasmStructTypeName.CharArray, WasmArrayType.i16Array),
110-
(WasmStructTypeName.ByteArray, WasmArrayType.i8Array),
111-
(WasmStructTypeName.ShortArray, WasmArrayType.i16Array),
112-
(WasmStructTypeName.IntArray, WasmArrayType.i32Array),
113-
(WasmStructTypeName.LongArray, WasmArrayType.i64Array),
114-
(WasmStructTypeName.FloatArray, WasmArrayType.f32Array),
115-
(WasmStructTypeName.DoubleArray, WasmArrayType.f64Array),
116-
(WasmStructTypeName.ObjectArray, WasmArrayType.anyArray)
117-
)
107+
val objectRef = IRTypes.ClassRef(IRNames.ObjectClass)
108+
109+
val typeRefsWithArrays: List[(IRTypes.NonArrayTypeRef, WasmStructTypeName, WasmArrayType)] =
110+
List(
111+
(IRTypes.BooleanRef, WasmStructTypeName.BooleanArray, WasmArrayType.i8Array),
112+
(IRTypes.CharRef, WasmStructTypeName.CharArray, WasmArrayType.i16Array),
113+
(IRTypes.ByteRef, WasmStructTypeName.ByteArray, WasmArrayType.i8Array),
114+
(IRTypes.ShortRef, WasmStructTypeName.ShortArray, WasmArrayType.i16Array),
115+
(IRTypes.IntRef, WasmStructTypeName.IntArray, WasmArrayType.i32Array),
116+
(IRTypes.LongRef, WasmStructTypeName.LongArray, WasmArrayType.i64Array),
117+
(IRTypes.FloatRef, WasmStructTypeName.FloatArray, WasmArrayType.f32Array),
118+
(IRTypes.DoubleRef, WasmStructTypeName.DoubleArray, WasmArrayType.f64Array),
119+
(objectRef, WasmStructTypeName.ObjectArray, WasmArrayType.anyArray)
120+
)
118121

119-
for ((structTypeName, underlyingArrayType) <- typeRefsWithArrays) {
122+
for ((baseRef, structTypeName, underlyingArrayType) <- typeRefsWithArrays) {
120123
val underlyingArrayField = WasmStructField(
121124
WasmFieldName.arrayField,
122125
WasmRefType(underlyingArrayType.name),
@@ -129,7 +132,11 @@ class WasmBuilder {
129132
Some(Names.WasmTypeName.WasmStructTypeName.forClass(IRNames.ObjectClass))
130133
)
131134
ctx.addGCType(structType)
135+
136+
HelperFunctions.genArrayCloneFunction(IRTypes.ArrayTypeRef(baseRef, 1))
132137
}
138+
139+
genArrayClassItable()
133140
}
134141

135142
def transformTopLevelExport(
@@ -437,23 +444,30 @@ class WasmBuilder {
437444
private def genGlobalClassItable(
438445
clazz: LinkedClass
439446
)(implicit ctx: WasmContext): Unit = {
440-
val info = ctx.getClassInfo(clazz.name.name)
441-
val interfaces = info.ancestors.map(ctx.getClassInfo(_)).filter(_.isInterface)
442-
if (!interfaces.isEmpty) {
443-
val itablesInit = List(
444-
I32_CONST(ctx.itablesLength),
445-
ARRAY_NEW_DEFAULT(WasmArrayType.itables.name)
446-
)
447-
val globalITable = WasmGlobal(
448-
WasmGlobalName.forITable(clazz.name.name),
449-
WasmRefType(WasmArrayType.itables.name),
450-
init = WasmExpr(itablesInit),
451-
isMutable = false
452-
)
453-
ctx.addGlobalITable(clazz.name.name, globalITable)
447+
val info = ctx.getClassInfo(clazz.className)
448+
val implementsAnyInterface = info.ancestors.exists(a => ctx.getClassInfo(a).isInterface)
449+
if (implementsAnyInterface) {
450+
val globalName = WasmGlobalName.forITable(clazz.className)
451+
ctx.addGlobalITable(clazz.className, genITableGlobal(globalName))
454452
}
455453
}
456454

455+
private def genArrayClassItable()(implicit ctx: WasmContext): Unit =
456+
ctx.addGlobal(genITableGlobal(WasmGlobalName.arrayClassITable))
457+
458+
private def genITableGlobal(name: WasmGlobalName)(implicit ctx: WasmContext): WasmGlobal = {
459+
val itablesInit = List(
460+
I32_CONST(ctx.itablesLength),
461+
ARRAY_NEW_DEFAULT(WasmArrayType.itables.name)
462+
)
463+
WasmGlobal(
464+
name,
465+
WasmRefType(WasmArrayType.itables.name),
466+
init = WasmExpr(itablesInit),
467+
isMutable = false
468+
)
469+
}
470+
457471
private def transformClass(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
458472
assert(clazz.kind == ClassKind.Class)
459473
transformClassCommon(clazz)

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -2111,8 +2111,7 @@ private class WasmExpressionBuilder private (
21112111
genLoadArrayTypeData(arrayTypeRef)
21122112

21132113
// Load the itables for the array type
2114-
// TODO: this should not be null because of Serializable and Cloneable
2115-
instrs += REF_NULL(Types.WasmHeapType(WasmArrayType.itables.name))
2114+
instrs += GLOBAL_GET(WasmGlobalName.arrayClassITable)
21162115
}
21172116

21182117
/** For getting element from an array, array.set should be generated by transformation of

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,8 @@ object WasmInstr {
333333
case class ARRAY_SET(i: WasmTypeName) extends WasmTypeInstr("array.set", 0xFB0E, i)
334334
case object ARRAY_LEN extends WasmSimpleInstr("array.len", 0xFB0F)
335335
// ARRAY_FILL,
336-
// ARRAY_COPY
336+
case class ARRAY_COPY(destType: WasmTypeName, srcType: WasmTypeName)
337+
extends WasmInstr("array.copy", 0xFB11)
337338
// ARRAY_NEW_DATA
338339
// array_NEW_FIXED
339340

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

+14-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ object Names {
6363
}
6464

6565
def forITable(className: IRNames.ClassName): WasmGlobalName =
66-
new WasmGlobalName(s"itable.${className.nameString}")
66+
new WasmGlobalName(s"itable.L${className.nameString}")
6767

6868
def forStaticField(fieldName: IRNames.FieldName): WasmGlobalName =
6969
new WasmGlobalName(s"static.${fieldName.nameString}")
@@ -72,7 +72,10 @@ object Names {
7272
new WasmGlobalName(s"jspfield.${fieldName.nameString}")
7373

7474
val stringLiteralCache: WasmGlobalName =
75-
new WasmGlobalName(s"string_literal")
75+
new WasmGlobalName("string_literal")
76+
77+
val arrayClassITable: WasmGlobalName =
78+
new WasmGlobalName("itable.A")
7679
}
7780

7881
// final case class WasmGlobalName private (val name: String) extends WasmName(name) {
@@ -121,6 +124,15 @@ object Names {
121124
new WasmFunctionName("instanceTest", clazz.nameString)
122125
def clone(clazz: IRNames.ClassName): WasmFunctionName =
123126
new WasmFunctionName("clone", clazz.nameString)
127+
128+
def clone(arrayBaseRef: IRTypes.NonArrayTypeRef): WasmFunctionName = {
129+
val simpleName = arrayBaseRef match {
130+
case IRTypes.ClassRef(_) => "O"
131+
case IRTypes.PrimRef(tpe) => tpe.primRef.charCode.toString()
132+
}
133+
new WasmFunctionName("cloneArray", simpleName)
134+
}
135+
124136
def loadJSClass(clazz: IRNames.ClassName): WasmFunctionName =
125137
new WasmFunctionName("loadJSClass", clazz.nameString)
126138
def createJSClassOf(clazz: IRNames.ClassName): WasmFunctionName =

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

+21
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,9 @@ class WasmContext(val module: WasmModule) extends TypeDefinableWasmContext {
520520
moduleInitializers: List[ModuleInitializer.Initializer],
521521
classesWithStaticInit: List[IRNames.ClassName]
522522
): Unit = {
523+
import WasmInstr._
524+
import WasmTypeName._
525+
523526
val fctx = WasmFunctionContext(WasmFunctionName.start, Nil, Nil)(this)
524527

525528
import fctx.instrs
@@ -544,6 +547,24 @@ class WasmContext(val module: WasmModule) extends TypeDefinableWasmContext {
544547
}
545548
}
546549

550+
locally {
551+
// For array classes, resolve methods in the vtable of jl.Object
552+
val globalName = WasmGlobalName.arrayClassITable
553+
val objectVTable = calculateVtableType(IRNames.ObjectClass)
554+
555+
for {
556+
interfaceName <- List(IRNames.SerializableClass, IRNames.CloneableClass)
557+
interfaceInfo <- getClassInfoOption(interfaceName)
558+
} {
559+
instrs += GLOBAL_GET(globalName)
560+
instrs += I32_CONST(getItableIdx(interfaceName))
561+
for (method <- interfaceInfo.methods)
562+
instrs += refFuncWithDeclaration(objectVTable.resolve(method.name).name)
563+
instrs += STRUCT_NEW(WasmStructTypeName.forITable(interfaceName))
564+
instrs += ARRAY_SET(WasmArrayTypeName.itables)
565+
}
566+
}
567+
547568
// Initialize the JS private field symbols
548569

549570
for (fieldName <- _jsPrivateFieldNames) {

0 commit comments

Comments
 (0)