diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 277d432ffd..ef5d20f120 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -2188,15 +2188,45 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) isJSFunctionDef(currentClassSym)) { val flags = js.MemberFlags.empty.withNamespace(namespace) val body = { + def genAsUnaryOp(op: js.UnaryOp.Code): js.Tree = + js.UnaryOp(op, genThis()) + def genAsBinaryOp(op: js.BinaryOp.Code): js.Tree = + js.BinaryOp(op, genThis(), jsParams.head.ref) + def genAsBinaryOpRhsNotNull(op: js.BinaryOp.Code): js.Tree = + js.BinaryOp(op, genThis(), js.UnaryOp(js.UnaryOp.CheckNotNull, jsParams.head.ref)) + if (currentClassSym.get == HackedStringClass) { /* Hijack the bodies of String.length and String.charAt and replace * them with String_length and String_charAt operations, respectively. */ methodName.name match { - case `lengthMethodName` => - js.UnaryOp(js.UnaryOp.String_length, genThis()) - case `charAtMethodName` => - js.BinaryOp(js.BinaryOp.String_charAt, genThis(), jsParams.head.ref) + case `lengthMethodName` => genAsUnaryOp(js.UnaryOp.String_length) + case `charAtMethodName` => genAsBinaryOp(js.BinaryOp.String_charAt) + case _ => genBody() + } + } else if (currentClassSym.get == ClassClass) { + // Similar, for the Class_x operations + methodName.name match { + case `getNameMethodName` => genAsUnaryOp(js.UnaryOp.Class_name) + case `isPrimitiveMethodName` => genAsUnaryOp(js.UnaryOp.Class_isPrimitive) + case `isInterfaceMethodName` => genAsUnaryOp(js.UnaryOp.Class_isInterface) + case `isArrayMethodName` => genAsUnaryOp(js.UnaryOp.Class_isArray) + case `getComponentTypeMethodName` => genAsUnaryOp(js.UnaryOp.Class_componentType) + case `getSuperclassMethodName` => genAsUnaryOp(js.UnaryOp.Class_superClass) + + case `isInstanceMethodName` => genAsBinaryOp(js.BinaryOp.Class_isInstance) + case `isAssignableFromMethodName` => genAsBinaryOpRhsNotNull(js.BinaryOp.Class_isAssignableFrom) + case `castMethodName` => genAsBinaryOp(js.BinaryOp.Class_cast) + + case _ => genBody() + } + } else if (currentClassSym.get == JavaLangReflectArrayModClass) { + methodName.name match { + case `arrayNewInstanceMethodName` => + val List(jlClassParam, lengthParam) = jsParams + js.BinaryOp(js.BinaryOp.Class_newArray, + js.UnaryOp(js.UnaryOp.CheckNotNull, jlClassParam.ref), + lengthParam.ref) case _ => genBody() } @@ -3644,12 +3674,11 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) */ def genNewArray(arrayTypeRef: jstpe.ArrayTypeRef, arguments: List[js.Tree])( implicit pos: Position): js.Tree = { - assert(arguments.length <= arrayTypeRef.dimensions, - "too many arguments for array constructor: found " + arguments.length + - " but array has only " + arrayTypeRef.dimensions + - " dimension(s)") + assert(arguments.size == 1, + "expected exactly 1 argument for array constructor: found " + + s"${arguments.length} at $pos") - js.NewArray(arrayTypeRef, arguments) + js.NewArray(arrayTypeRef, arguments.head) } /** Gen JS code for an array literal. */ @@ -7084,6 +7113,32 @@ private object GenJSCode { private val charAtMethodName = MethodName("charAt", List(jstpe.IntRef), jstpe.CharRef) + private val getNameMethodName = + MethodName("getName", Nil, jstpe.ClassRef(ir.Names.BoxedStringClass)) + private val isPrimitiveMethodName = + MethodName("isPrimitive", Nil, jstpe.BooleanRef) + private val isInterfaceMethodName = + MethodName("isInterface", Nil, jstpe.BooleanRef) + private val isArrayMethodName = + MethodName("isArray", Nil, jstpe.BooleanRef) + private val getComponentTypeMethodName = + MethodName("getComponentType", Nil, jstpe.ClassRef(ir.Names.ClassClass)) + private val getSuperclassMethodName = + MethodName("getSuperclass", Nil, jstpe.ClassRef(ir.Names.ClassClass)) + + private val isInstanceMethodName = + MethodName("isInstance", List(jstpe.ClassRef(ir.Names.ObjectClass)), jstpe.BooleanRef) + private val isAssignableFromMethodName = + MethodName("isAssignableFrom", List(jstpe.ClassRef(ir.Names.ClassClass)), jstpe.BooleanRef) + private val castMethodName = + MethodName("cast", List(jstpe.ClassRef(ir.Names.ObjectClass)), jstpe.ClassRef(ir.Names.ObjectClass)) + + private val arrayNewInstanceMethodName = { + MethodName("newInstance", + List(jstpe.ClassRef(ir.Names.ClassClass), jstpe.IntRef), + jstpe.ClassRef(ir.Names.ObjectClass)) + } + private val thisOriginalName = OriginalName("this") private object BlockOrAlone { diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala index 43fa33aedd..8214985b6a 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala @@ -37,6 +37,8 @@ trait JSDefinitions { lazy val JavaLangVoidClass = getRequiredClass("java.lang.Void") + lazy val JavaLangReflectArrayModClass = getModuleIfDefined("java.lang.reflect.Array").moduleClass + lazy val BoxedUnitModClass = BoxedUnitModule.moduleClass lazy val ScalaJSJSPackageModule = getPackageObject("scala.scalajs.js") diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala index b1f69595a3..d37bf739e7 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala @@ -321,10 +321,10 @@ object Hashers { mixTree(lhs) mixTree(rhs) - case NewArray(typeRef, lengths) => + case NewArray(typeRef, length) => mixTag(TagNewArray) mixArrayTypeRef(typeRef) - mixTrees(lengths) + mixTrees(length :: Nil) // mixed as a list for historical reasons case ArrayValue(typeRef, elems) => mixTag(TagArrayValue) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index ec0ff8c5f8..34f5743e69 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -371,8 +371,14 @@ object Printers { } else { print(lhs) print((op: @switch) match { - case String_length => ".length" - case CheckNotNull => ".notNull" + case String_length => ".length" + case CheckNotNull => ".notNull" + case Class_name => ".name" + case Class_isPrimitive => ".isPrimitive" + case Class_isInterface => ".isInterface" + case Class_isArray => ".isArray" + case Class_componentType => ".componentType" + case Class_superClass => ".superClass" }) } @@ -413,6 +419,19 @@ object Printers { print(rhs) print(']') + case BinaryOp(op, lhs, rhs) if BinaryOp.isClassOp(op) => + import BinaryOp._ + print((op: @switch) match { + case Class_isInstance => "isInstance(" + case Class_isAssignableFrom => "isAssignableFrom(" + case Class_cast => "cast(" + case Class_newArray => "newArray(" + }) + print(lhs) + print(", ") + print(rhs) + print(')') + case BinaryOp(op, lhs, rhs) => import BinaryOp._ print('(') @@ -492,15 +511,13 @@ object Printers { print(rhs) print(')') - case NewArray(typeRef, lengths) => + case NewArray(typeRef, length) => print("new ") print(typeRef.base) - for (length <- lengths) { - print('[') - print(length) - print(']') - } - for (dim <- lengths.size until typeRef.dimensions) + print('[') + print(length) + print(']') + for (dim <- 1 until typeRef.dimensions) print("[]") case ArrayValue(typeRef, elems) => diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index bad2b82fa5..ae2eec6b1f 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -22,6 +22,7 @@ import scala.collection.mutable import scala.concurrent._ import Names._ +import OriginalName.NoOriginalName import Position._ import Trees._ import Types._ @@ -362,9 +363,10 @@ object Serializers { writeTagAndPos(TagBinaryOp) writeByte(op); writeTree(lhs); writeTree(rhs) - case NewArray(tpe, lengths) => + case NewArray(tpe, length) => writeTagAndPos(TagNewArray) - writeArrayTypeRef(tpe); writeTrees(lengths) + writeArrayTypeRef(tpe) + writeTrees(length :: Nil) // written as a list of historical reasons case ArrayValue(tpe, elems) => writeTagAndPos(TagArrayValue) @@ -1223,13 +1225,45 @@ object Serializers { ApplyDynamicImport(readApplyFlags(), readClassName(), readMethodIdent(), readTrees()) - case TagUnaryOp => UnaryOp(readByte(), readTree()) - case TagBinaryOp => BinaryOp(readByte(), readTree(), readTree()) - case TagNewArray => NewArray(readArrayTypeRef(), readTrees()) - case TagArrayValue => ArrayValue(readArrayTypeRef(), readTrees()) - case TagArrayLength => ArrayLength(readTree()) - case TagArraySelect => ArraySelect(readTree(), readTree())(readType()) - case TagRecordValue => RecordValue(readType().asInstanceOf[RecordType], readTrees()) + case TagUnaryOp => UnaryOp(readByte(), readTree()) + case TagBinaryOp => BinaryOp(readByte(), readTree(), readTree()) + + case TagNewArray => + val arrayTypeRef = readArrayTypeRef() + val lengths = readTrees() + lengths match { + case length :: Nil => + NewArray(arrayTypeRef, length) + + case _ => + if (hacks.use16) { + // Rewrite as a call to j.l.r.Array.newInstance + val ArrayTypeRef(base, origDims) = arrayTypeRef + val newDims = origDims - lengths.size + if (newDims < 0) { + throw new IOException( + s"Illegal legacy NewArray node with ${lengths.size} lengths but dimension $origDims at $pos") + } + val newBase = + if (newDims == 0) base + else ArrayTypeRef(base, newDims) + + ApplyStatic( + ApplyFlags.empty, + HackNames.ReflectArrayClass, + MethodIdent(HackNames.newInstanceMultiName), + List(ClassOf(newBase), ArrayValue(ArrayTypeRef(IntRef, 1), lengths)))( + AnyType) + } else { + throw new IOException( + s"Illegal NewArray node with multiple lengths for IR version 1.17+ at $pos") + } + } + + case TagArrayValue => ArrayValue(readArrayTypeRef(), readTrees()) + case TagArrayLength => ArrayLength(readTree()) + case TagArraySelect => ArraySelect(readTree(), readTree())(readType()) + case TagRecordValue => RecordValue(readType().asInstanceOf[RecordType], readTrees()) case TagIsInstanceOf => val expr = readTree() @@ -1471,6 +1505,10 @@ object Serializers { if (hacks.use4 && kind.isJSClass) { // #4409: Filter out abstract methods in non-native JS classes for version < 1.5 methods0.filter(_.body.isDefined) + } else if (hacks.use16 && cls == ClassClass) { + jlClassMethodsHack16(methods0) + } else if (hacks.use16 && cls == HackNames.ReflectArrayModClass) { + jlReflectArrayMethodsHack16(methods0) } else { methods0 } @@ -1493,6 +1531,246 @@ object Serializers { optimizerHints) } + private def jlClassMethodsHack16(methods: List[MethodDef]): List[MethodDef] = { + for (method <- methods) yield { + implicit val pos = method.pos + + val methodName = method.methodName + val methodSimpleNameString = methodName.simpleName.nameString + + val thisJLClass = This()(ClassType(ClassClass, nullable = false)) + + if (methodName.isConstructor) { + val newName = MethodIdent(NoArgConstructorName)(method.name.pos) + val newBody = ApplyStatically(ApplyFlags.empty.withConstructor(true), + thisJLClass, ObjectClass, newName, Nil)(NoType) + MethodDef(method.flags, newName, method.originalName, + Nil, NoType, Some(newBody))( + method.optimizerHints, method.version) + } else { + def argRef = method.args.head.ref + def argRefNotNull = UnaryOp(UnaryOp.CheckNotNull, argRef) + + var forceInline = true // reset to false in the `case _ =>` + + val newBody: Tree = methodSimpleNameString match { + case "getName" => UnaryOp(UnaryOp.Class_name, thisJLClass) + case "isPrimitive" => UnaryOp(UnaryOp.Class_isPrimitive, thisJLClass) + case "isInterface" => UnaryOp(UnaryOp.Class_isInterface, thisJLClass) + case "isArray" => UnaryOp(UnaryOp.Class_isArray, thisJLClass) + case "getComponentType" => UnaryOp(UnaryOp.Class_componentType, thisJLClass) + case "getSuperclass" => UnaryOp(UnaryOp.Class_superClass, thisJLClass) + + case "isInstance" => BinaryOp(BinaryOp.Class_isInstance, thisJLClass, argRef) + case "isAssignableFrom" => BinaryOp(BinaryOp.Class_isAssignableFrom, thisJLClass, argRefNotNull) + case "cast" => BinaryOp(BinaryOp.Class_cast, thisJLClass, argRef) + + case _ => + forceInline = false + + /* Unfortunately, some of the other methods directly referred to + * `this.data["name"]`, instead of building on `this.getName()`. + * We must replace those occurrences with a `Class_name` as well. + */ + val transformer = new Transformers.Transformer { + override def transform(tree: Tree, isStat: Boolean): Tree = tree match { + case JSSelect(_, StringLiteral("name")) => + implicit val pos = tree.pos + UnaryOp(UnaryOp.Class_name, thisJLClass) + case _ => + super.transform(tree, isStat) + } + } + transformer.transform(method.body.get, isStat = method.resultType == NoType) + } + + val newOptimizerHints = + if (forceInline) method.optimizerHints.withInline(true) + else method.optimizerHints + + MethodDef(method.flags, method.name, method.originalName, + method.args, method.resultType, Some(newBody))( + newOptimizerHints, method.version) + } + } + } + + private def jlReflectArrayMethodsHack16(methods: List[MethodDef]): List[MethodDef] = { + /* Basically this method hard-codes new implementations for the two + * overloads of newInstance. + * It is horrible, but better than pollute everything else in the linker. + */ + + import HackNames._ + + def paramDef(name: String, ptpe: Type)(implicit pos: Position): ParamDef = + ParamDef(LocalIdent(LocalName(name)), NoOriginalName, ptpe, mutable = false) + + def varDef(name: String, vtpe: Type, rhs: Tree, mutable: Boolean = false)( + implicit pos: Position): VarDef = { + VarDef(LocalIdent(LocalName(name)), NoOriginalName, vtpe, mutable, rhs) + } + + val jlClassRef = ClassRef(ClassClass) + val intArrayTypeRef = ArrayTypeRef(IntRef, 1) + val objectRef = ClassRef(ObjectClass) + val objectArrayTypeRef = ArrayTypeRef(objectRef, 1) + + val jlClassType = ClassType(ClassClass, nullable = true) + + val newInstanceRecName = MethodName("newInstanceRec", + List(jlClassRef, intArrayTypeRef, IntRef), objectRef) + + val EAF = ApplyFlags.empty + + val newInstanceRecMethod = { + /* def newInstanceRec(componentType: jl.Class, dimensions: int[], offset: int): any = { + * val length: int = dimensions[offset] + * val result: any = newInstance(componentType, length) + * val innerOffset = offset + 1 + * if (innerOffset < dimensions.length) { + * val result2: Object[] = result.asInstanceOf[Object[]] + * val innerComponentType: jl.Class = componentType.getComponentType() + * var i: Int = 0 + * while (i != length) + * result2[i] = newInstanceRec(innerComponentType, dimensions, innerOffset) + * i = i + 1 + * } + * } + * result + * } + */ + + implicit val pos = Position.NoPosition + + val getComponentTypeName = MethodName("getComponentType", Nil, jlClassRef) + + val ths = This()(ClassType(ReflectArrayModClass, nullable = false)) + + val componentType = paramDef("componentType", jlClassType) + val dimensions = paramDef("dimensions", ArrayType(intArrayTypeRef, nullable = true)) + val offset = paramDef("offset", IntType) + + val length = varDef("length", IntType, ArraySelect(dimensions.ref, offset.ref)(IntType)) + val result = varDef("result", AnyType, + Apply(EAF, ths, MethodIdent(newInstanceSingleName), List(componentType.ref, length.ref))(AnyType)) + val innerOffset = varDef("innerOffset", IntType, + BinaryOp(BinaryOp.Int_+, offset.ref, IntLiteral(1))) + + val result2 = varDef("result2", ArrayType(objectArrayTypeRef, nullable = true), + AsInstanceOf(result.ref, ArrayType(objectArrayTypeRef, nullable = true))) + val innerComponentType = varDef("innerComponentType", jlClassType, + Apply(EAF, componentType.ref, MethodIdent(getComponentTypeName), Nil)(jlClassType)) + val i = varDef("i", IntType, IntLiteral(0), mutable = true) + + val body = { + Block( + length, + result, + innerOffset, + If(BinaryOp(BinaryOp.Int_<, innerOffset.ref, ArrayLength(dimensions.ref)), { + Block( + result2, + innerComponentType, + i, + While(BinaryOp(BinaryOp.Int_!=, i.ref, length.ref), { + Block( + Assign( + ArraySelect(result2.ref, i.ref)(AnyType), + Apply(EAF, ths, MethodIdent(newInstanceRecName), + List(innerComponentType.ref, dimensions.ref, innerOffset.ref))(AnyType) + ), + Assign( + i.ref, + BinaryOp(BinaryOp.Int_+, i.ref, IntLiteral(1)) + ) + ) + }) + ) + }, Skip())(NoType), + result.ref + ) + } + + MethodDef(MemberFlags.empty, MethodIdent(newInstanceRecName), + NoOriginalName, List(componentType, dimensions, offset), AnyType, + Some(body))( + OptimizerHints.empty, Version.fromInt(1)) + } + + val newMethods = for (method <- methods) yield { + method.methodName match { + case `newInstanceSingleName` => + // newInstance(jl.Class, int) --> newArray(jlClass.notNull, length) + + implicit val pos = method.pos + + val List(jlClassParam, lengthParam) = method.args + + val newBody = BinaryOp(BinaryOp.Class_newArray, + UnaryOp(UnaryOp.CheckNotNull, jlClassParam.ref), + lengthParam.ref) + + MethodDef(method.flags, method.name, method.originalName, + method.args, method.resultType, Some(newBody))( + method.optimizerHints.withInline(true), method.version) + + case `newInstanceMultiName` => + /* newInstance(jl.Class, int[]) --> + * var outermostComponentType: jl.Class = jlClassParam + * var i: int = 1 + * while (i != lengths.length) { + * outermostComponentType = getClass(this.newInstance(outermostComponentType, 0)) + * i = i + 1 + * } + * newInstanceRec(outermostComponentType, lengths, 0) + */ + + implicit val pos = method.pos + + val List(jlClassParam, lengthsParam) = method.args + + val newBody = { + val outermostComponentType = varDef("outermostComponentType", + jlClassType, jlClassParam.ref, mutable = true) + val i = varDef("i", IntType, IntLiteral(1), mutable = true) + + Block( + outermostComponentType, + i, + While(BinaryOp(BinaryOp.Int_!=, i.ref, ArrayLength(lengthsParam.ref)), { + Block( + Assign( + outermostComponentType.ref, + GetClass(Apply(EAF, This()(ClassType(ReflectArrayModClass, nullable = false)), + MethodIdent(newInstanceSingleName), + List(outermostComponentType.ref, IntLiteral(0)))(AnyType)) + ), + Assign( + i.ref, + BinaryOp(BinaryOp.Int_+, i.ref, IntLiteral(1)) + ) + ) + }), + Apply(EAF, This()(ClassType(ReflectArrayModClass, nullable = false)), + MethodIdent(newInstanceRecName), + List(outermostComponentType.ref, lengthsParam.ref, IntLiteral(0)))( + AnyType) + ) + } + + MethodDef(method.flags, method.name, method.originalName, + method.args, method.resultType, Some(newBody))( + method.optimizerHints, method.version) + + case _ => + method + } + } + + newInstanceRecMethod :: newMethods + } + private def jsConstructorHack( jsMethodProps: List[JSMethodPropDef]): (Option[JSConstructorDef], List[JSMethodPropDef]) = { val jsConstructorBuilder = new OptionBuilder[JSConstructorDef] @@ -2159,11 +2437,19 @@ object Serializers { ClassName("java.lang.CloneNotSupportedException") val SystemModule: ClassName = ClassName("java.lang.System$") + val ReflectArrayClass = + ClassName("java.lang.reflect.Array") + val ReflectArrayModClass = + ClassName("java.lang.reflect.Array$") val cloneName: MethodName = MethodName("clone", Nil, ClassRef(ObjectClass)) val identityHashCodeName: MethodName = MethodName("identityHashCode", List(ClassRef(ObjectClass)), IntRef) + val newInstanceSingleName: MethodName = + MethodName("newInstance", List(ClassRef(ClassClass), IntRef), ClassRef(ObjectClass)) + val newInstanceMultiName: MethodName = + MethodName("newInstance", List(ClassRef(ClassClass), ArrayTypeRef(IntRef, 1)), ClassRef(ObjectClass)) } private class OptionBuilder[T] { diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala index 6d30327786..09b82b1757 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -114,8 +114,8 @@ object Transformers { case BinaryOp(op, lhs, rhs) => BinaryOp(op, transformExpr(lhs), transformExpr(rhs)) - case NewArray(tpe, lengths) => - NewArray(tpe, lengths map transformExpr) + case NewArray(tpe, length) => + NewArray(tpe, transformExpr(length)) case ArrayValue(tpe, elems) => ArrayValue(tpe, elems map transformExpr) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala index 8a8909cdce..2f68fac81f 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala @@ -101,8 +101,8 @@ object Traversers { traverse(lhs) traverse(rhs) - case NewArray(tpe, lengths) => - lengths foreach traverse + case NewArray(tpe, length) => + traverse(length) case ArrayValue(tpe, elems) => elems foreach traverse diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 34804cc421..51d08ea2e9 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -284,6 +284,9 @@ object Trees { } /** Unary operation. + * + * The `Class_x` operations take a `jl.Class!` argument, i.e., a + * non-nullable `jl.Class`. * * `CheckNotNull` throws NPEs subject to UB. * @@ -327,11 +330,22 @@ object Trees { // String.length, introduced in 1.11 final val String_length = 17 - // New in 1.17 + // Null check, introduced in 1.17 final val CheckNotNull = 18 + // Class operations, introduced in 1.17 + final val Class_name = 19 + final val Class_isPrimitive = 20 + final val Class_isInterface = 21 + final val Class_isArray = 22 + final val Class_componentType = 23 + final val Class_superClass = 24 + + def isClassOp(op: Code): Boolean = + op >= Class_name && op <= Class_superClass + def resultTypeOf(op: Code, argType: Type): Type = (op: @switch) match { - case Boolean_! => + case Boolean_! | Class_isPrimitive | Class_isInterface | Class_isArray => BooleanType case IntToChar => CharType @@ -349,10 +363,42 @@ object Trees { DoubleType case CheckNotNull => argType.toNonNullable + case Class_name => + StringType + case Class_componentType | Class_superClass => + ClassType(ClassClass, nullable = true) } } - /** Binary operation (always preserves pureness). */ + /** Binary operation. + * + * All binary operations follow common evaluation steps: + * + * 1. Let `lhsValue` be the result of evaluating `lhs`. + * 2. Let `rhsValue` be the result of evaluating `rhs`. + * 3. Perform an operation that depends on `op`, `lhsValue` and `rhsValue`. + * + * Unless `lhsValue` throws, `rhsValue` will therefore always be evaluated, + * even if the operation `op` would throw based only on `lhsValue`. + * + * The integer dividing operators (`Int_/`, `Int_%`, `Long_/` and `Long_%`) + * throw an `ArithmeticException` when their right-hand-side is 0. That + * exception is not subject to undefined behavior. + * + * `String_charAt` throws a `StringIndexOutOfBoundsException`. + * + * The `Class_x` operations take a `jl.Class!` as lhs, i.e., a + * non-nullable `jl.Class`. `Class_isAssignableFrom` also takes a + * `jl.Class!` as rhs. + * + * - `Class_isInstance` and `Class_isAssignableFrom` are pure. + * - `Class_cast` throws a CCE if its second argument is non-null and + * not an instance of the class represented by its first argument. + * - `Class_newArray` throws a `NegativeArraySizeException` if its second + * argument is negative. + * + * Otherwise, binary operations preserve pureness. + */ sealed case class BinaryOp(op: BinaryOp.Code, lhs: Tree, rhs: Tree)( implicit val pos: Position) extends Tree { @@ -435,12 +481,22 @@ object Trees { // New in 1.11 final val String_charAt = 58 + // New in 1.17 + final val Class_isInstance = 59 + final val Class_isAssignableFrom = 60 + final val Class_cast = 61 + final val Class_newArray = 62 + + def isClassOp(op: Code): Boolean = + op >= Class_isInstance && op <= Class_newArray + def resultTypeOf(op: Code): Type = (op: @switch) match { case === | !== | Boolean_== | Boolean_!= | Boolean_| | Boolean_& | Int_== | Int_!= | Int_< | Int_<= | Int_> | Int_>= | Long_== | Long_!= | Long_< | Long_<= | Long_> | Long_>= | - Double_== | Double_!= | Double_< | Double_<= | Double_> | Double_>= => + Double_== | Double_!= | Double_< | Double_<= | Double_> | Double_>= | + Class_isInstance | Class_isAssignableFrom => BooleanType case String_+ => StringType @@ -456,10 +512,14 @@ object Trees { DoubleType case String_charAt => CharType + case Class_cast => + AnyType + case Class_newArray => + AnyNotNullType } } - sealed case class NewArray(typeRef: ArrayTypeRef, lengths: List[Tree])( + sealed case class NewArray(typeRef: ArrayTypeRef, length: Tree)( implicit val pos: Position) extends Tree { val tpe = ArrayType(typeRef, nullable = false) } diff --git a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala index f3cef80ffa..57d80c3373 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -394,6 +394,14 @@ class PrintersTest { assertPrintEquals("x.length", UnaryOp(String_length, ref("x", StringType))) assertPrintEquals("x.notNull", UnaryOp(CheckNotNull, ref("x", AnyType))) + + val classVarRef = ref("x", ClassType(ClassClass, nullable = false)) + assertPrintEquals("x.name", UnaryOp(Class_name, classVarRef)) + assertPrintEquals("x.isPrimitive", UnaryOp(Class_isPrimitive, classVarRef)) + assertPrintEquals("x.isInterface", UnaryOp(Class_isInterface, classVarRef)) + assertPrintEquals("x.isArray", UnaryOp(Class_isArray, classVarRef)) + assertPrintEquals("x.componentType", UnaryOp(Class_componentType, classVarRef)) + assertPrintEquals("x.superClass", UnaryOp(Class_superClass, classVarRef)) } @Test def printPseudoUnaryOp(): Unit = { @@ -539,13 +547,20 @@ class PrintersTest { assertPrintEquals("x[y]", BinaryOp(String_charAt, ref("x", StringType), ref("y", IntType))) + + val classVarRef = ref("x", ClassType(ClassClass, nullable = false)) + assertPrintEquals("isInstance(x, y)", BinaryOp(Class_isInstance, classVarRef, ref("y", AnyType))) + assertPrintEquals("isAssignableFrom(x, y)", + BinaryOp(Class_isAssignableFrom, classVarRef, ref("y", ClassType(ClassClass, nullable = false)))) + assertPrintEquals("cast(x, y)", BinaryOp(Class_cast, classVarRef, ref("y", AnyType))) + assertPrintEquals("newArray(x, y)", BinaryOp(Class_newArray, classVarRef, ref("y", IntType))) } @Test def printNewArray(): Unit = { - assertPrintEquals("new int[3]", NewArray(ArrayTypeRef(IntRef, 1), List(i(3)))) - assertPrintEquals("new int[3][]", NewArray(ArrayTypeRef(IntRef, 2), List(i(3)))) - assertPrintEquals("new java.lang.Object[3][4][][]", - NewArray(ArrayTypeRef(ObjectClass, 4), List(i(3), i(4)))) + assertPrintEquals("new int[3]", NewArray(ArrayTypeRef(IntRef, 1), i(3))) + assertPrintEquals("new int[3][]", NewArray(ArrayTypeRef(IntRef, 2), i(3))) + assertPrintEquals("new java.lang.Object[3][][][]", + NewArray(ArrayTypeRef(ObjectClass, 4), i(3))) } @Test def printArrayValue(): Unit = { diff --git a/javalib/src/main/scala/java/lang/Class.scala b/javalib/src/main/scala/java/lang/Class.scala index a39bfd3fa7..b0d80c788b 100644 --- a/javalib/src/main/scala/java/lang/Class.scala +++ b/javalib/src/main/scala/java/lang/Class.scala @@ -15,68 +15,39 @@ package java.lang import java.lang.constant.Constable import java.io.Serializable -import scala.scalajs.js - -@js.native -private trait ScalaJSClassData[A] extends js.Object { - val name: String = js.native - val isPrimitive: scala.Boolean = js.native - val isInterface: scala.Boolean = js.native - val isArrayClass: scala.Boolean = js.native - - def isInstance(obj: Any): scala.Boolean = js.native - def isAssignableFrom(that: ScalaJSClassData[_]): scala.Boolean = js.native - def checkCast(obj: Any): scala.Unit = js.native - - def getSuperclass(): Class[_ >: A] = js.native - def getComponentType(): Class[_] = js.native - - def newArrayOfThisClass(dimensions: js.Array[Int]): AnyRef = js.native -} - -final class Class[A] private (data0: Object) +final class Class[A] private () extends Object with Serializable with Constable { - private[this] val data: ScalaJSClassData[A] = - data0.asInstanceOf[ScalaJSClassData[A]] - private[this] var cachedSimpleName: String = _ - /** Access to `data` for other instances or `@inline` methods. - * - * Directly accessing the `data` field from `@inline` methods will cause - * scalac to make the field public and mangle its name. Since the Emitter - * relies on the field being called exactly `data` in some of its - * optimizations, we must avoid that. - * - * This non-`@noinline` method can be used to access the field without - * triggering scalac's mangling. Since it is a trivial accessor, the - * Scala.js optimizer will inline it anyway. - */ - private def getData(): ScalaJSClassData[A] = data - override def toString(): String = { (if (isInterface()) "interface " else if (isPrimitive()) "" else "class ")+getName() } + @inline def isInstance(obj: Any): scala.Boolean = - data.isInstance(obj) + throw new Error("Stub filled in by the compiler") + @inline def isAssignableFrom(that: Class[_]): scala.Boolean = - this.data.isAssignableFrom(that.getData()) + throw new Error("Stub filled in by the compiler") + @inline def isInterface(): scala.Boolean = - data.isInterface + throw new Error("Stub filled in by the compiler") + @inline def isArray(): scala.Boolean = - data.isArrayClass + throw new Error("Stub filled in by the compiler") + @inline def isPrimitive(): scala.Boolean = - data.isPrimitive + throw new Error("Stub filled in by the compiler") + @inline def getName(): String = - data.name + throw new Error("Stub filled in by the compiler") def getSimpleName(): String = { if (cachedSimpleName == null) @@ -108,7 +79,7 @@ final class Class[A] private (data0: Object) if (isArray()) { getComponentType().getSimpleName() + "[]" } else { - val name = data.name + val name = getName() var idx = name.length - 1 // Include trailing '$'s for module class names @@ -139,20 +110,15 @@ final class Class[A] private (data0: Object) } } + @inline def getSuperclass(): Class[_ >: A] = - data.getSuperclass() + throw new Error("Stub filled in by the compiler") + @inline def getComponentType(): Class[_] = - data.getComponentType() + throw new Error("Stub filled in by the compiler") @inline - def cast(obj: Any): A = { - getData().checkCast(obj) - obj.asInstanceOf[A] - } - - // java.lang.reflect.Array support - - private[lang] def newArrayOfThisClass(dimensions: js.Array[Int]): AnyRef = - data.newArrayOfThisClass(dimensions) + def cast(obj: Any): A = + throw new Error("Stub filled in by the compiler") } diff --git a/javalib/src/main/scala/java/lang/reflect/Array.scala b/javalib/src/main/scala/java/lang/reflect/Array.scala index b2f94b8906..f5f6aaa21f 100644 --- a/javalib/src/main/scala/java/lang/reflect/Array.scala +++ b/javalib/src/main/scala/java/lang/reflect/Array.scala @@ -17,18 +17,34 @@ import scala.scalajs.js import java.lang.Class object Array { + @inline def newInstance(componentType: Class[_], length: Int): AnyRef = - componentType.newArrayOfThisClass(js.Array(length)) + throw new Error("Stub filled in by the compiler") def newInstance(componentType: Class[_], dimensions: scala.Array[Int]): AnyRef = { - val jsDims = js.Array[Int]() - val len = dimensions.length - var i = 0 - while (i != len) { - jsDims.push(dimensions(i)) + def rec(componentType: Class[_], offset: Int): AnyRef = { + val length = dimensions(offset) + val result = newInstance(componentType, length) + val innerOffset = offset + 1 + if (innerOffset < dimensions.length) { + val result2 = result.asInstanceOf[Array[AnyRef]] + val innerComponentType = componentType.getComponentType() + var i = 0 + while (i != length) { + result2(i) = rec(innerComponentType, innerOffset) + i += 1 + } + } + result + } + + var outermostComponentType = componentType + var i = 1 + while (i != dimensions.length) { + outermostComponentType = newInstance(outermostComponentType, 0).getClass() i += 1 } - componentType.newArrayOfThisClass(jsDims) + rec(outermostComponentType, 0) } def getLength(array: AnyRef): Int = array match { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala index 6d0a02e83c..3c1e6f251b 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala @@ -37,6 +37,8 @@ trait Analysis { def classInfos: scala.collection.Map[ClassName, ClassInfo] def topLevelExportInfos: scala.collection.Map[(ModuleID, String), TopLevelExportInfo] + def isClassSuperClassUsed: Boolean + def errors: scala.collection.Seq[Error] } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala index 3931d2ab58..79f1ef432f 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala @@ -118,6 +118,9 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, def classInfos: scala.collection.Map[ClassName, Analysis.ClassInfo] = _classInfos + private val _classSuperClassUsed = new AtomicBoolean(false) + def isClassSuperClassUsed: Boolean = _classSuperClassUsed.get() + private[this] val _errors = new GrowingList[Error] override def errors: List[Error] = _errors.get() @@ -294,20 +297,11 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, /** Reach additional class data based on reflection methods being used. */ private def reachDataThroughReflection(): Unit = { - - val classClassInfo = _classInfos.get(ClassClass) - - /* If Class.getSuperclass() is reachable, we can reach the data of all + /* If Class_superClass is used, we can reach the data of all * superclasses of classes whose data we can already reach. */ - for { - getSuperclassMethodInfo <- - classClassInfo.flatMap(_.publicMethodInfos.get(getSuperclassMethodName)) - if getSuperclassMethodInfo.isReachable - } { - // calledFrom should always be nonEmpty if isReachable, but let's be robust - implicit val from = - getSuperclassMethodInfo.calledFrom.headOption.getOrElse(fromAnalyzer) + if (isClassSuperClassUsed) { + implicit val from = fromAnalyzer for (classInfo <- _classInfos.values.filter(_.isDataAccessed).toList) { @tailrec def loop(classInfo: ClassInfo): Unit = { @@ -1461,7 +1455,7 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, objectClassInfo.addStaticDependency(ClassClass) lookupClass(ClassClass) { clazz => clazz.instantiated() - clazz.callMethodStatically(MemberNamespace.Constructor, ObjectArgConstructorName) + clazz.callMethodStatically(MemberNamespace.Constructor, NoArgConstructorName) } } @@ -1479,6 +1473,10 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, config.coreSpec.esFeatures.esVersion < ESVersion.ES2016) { _errors ::= ExponentOperatorWithoutES2016Support(from) } + + if ((globalFlags & ReachabilityInfo.FlagUsedClassSuperClass) != 0) { + _classSuperClassUsed.set(true) + } } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala index a6ed3f6a6e..8dcb89c784 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala @@ -112,6 +112,7 @@ object Infos { final val FlagAccessedNewTarget = 1 << 1 final val FlagAccessedImportMeta = 1 << 2 final val FlagUsedExponentOperator = 1 << 3 + final val FlagUsedClassSuperClass = 1 << 4 } /** Things from a given class that are reached by one method. */ @@ -380,6 +381,9 @@ object Infos { def addUsedExponentOperator(): this.type = setFlag(ReachabilityInfo.FlagUsedExponentOperator) + def addUsedClassSuperClass(): this.type = + setFlag(ReachabilityInfo.FlagUsedClassSuperClass) + def result(): ReachabilityInfo = new ReachabilityInfo(version, byClass.valuesIterator.map(_.result()).toArray, flags) } @@ -650,6 +654,16 @@ object Infos { builder.maybeAddUsedInstanceTest(tpe) + case UnaryOp(op, _) => + import UnaryOp._ + + op match { + case Class_superClass => + builder.addUsedClassSuperClass() + case _ => + // do nothing + } + case BinaryOp(op, _, rhs) => import BinaryOp._ diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ArrayClassProperty.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ArrayClassProperty.scala deleted file mode 100644 index c33d0a99e7..0000000000 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ArrayClassProperty.scala +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Scala.js (https://www.scala-js.org/) - * - * Copyright EPFL. - * - * Licensed under Apache License 2.0 - * (https://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package org.scalajs.linker.backend.emitter - -import org.scalajs.ir.OriginalName - -/** Represents a property of one of the special `ArrayClass`es. - * - * These properties live in the same namespace as Scala field and method - * names, because the `ArrayClass`es extend `j.l.Object`. Therefore, they - * must take part in the global property minification algorithm. - */ -final class ArrayClassProperty(val nonMinifiedName: String) - extends Comparable[ArrayClassProperty] { - - val originalName: OriginalName = OriginalName(nonMinifiedName) - - def compareTo(that: ArrayClassProperty): Int = - this.nonMinifiedName.compareTo(that.nonMinifiedName) - - override def toString(): String = s"ArrayClassProperty($nonMinifiedName)" -} - -object ArrayClassProperty { - /** `ArrayClass.u`: the underlying array of typed array. */ - val u: ArrayClassProperty = new ArrayClassProperty("u") - - /** `ArrayClass.get()`: gets one element. */ - val get: ArrayClassProperty = new ArrayClassProperty("get") - - /** `ArrayClass.set()`: sets one element. */ - val set: ArrayClassProperty = new ArrayClassProperty("set") - - /** `ArrayClass.copyTo()`: copies from that array to another array. */ - val copyTo: ArrayClassProperty = new ArrayClassProperty("copyTo") -} diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala index 6351e43614..0243d9f7bc 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala @@ -310,6 +310,7 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { } initToInline.fold { + assert(className != ClassClass, s"java.lang.Class did not have an inlineable init") WithGlobals( js.Function(arrow = false, Nil, None, js.Block(superCtorCallAndFieldDefs))) } { initMethodDef => @@ -326,8 +327,20 @@ private[emitter] final class ClassEmitter(sjsGen: SJSGen) { for (generatedInitMethodFun <- generatedInitMethodFunWithGlobals) yield { val js.Function(arrow, args, restParam, initMethodFunBody) = generatedInitMethodFun - js.Function(arrow, args, restParam, - js.Block(superCtorCallAndFieldDefs ::: initMethodFunBody :: Nil)) + + if (className != ClassClass) { + js.Function(arrow, args, restParam, + js.Block(superCtorCallAndFieldDefs ::: initMethodFunBody :: Nil)) + } else { + // Inject the magical `data` argument and field + assert(args.isEmpty, s"Unexpected constructor arguments $args for java.lang.Class") + val dataParam = js.ParamDef(fileLevelVarIdent(VarField.data)) + val createDataField = js.Assign( + js.DotSelect(js.This(), genSyntheticPropertyForDef(SyntheticProperty.data)), + dataParam.ref) + js.Function(arrow, dataParam :: Nil, restParam, + js.Block(superCtorCallAndFieldDefs ::: createDataField :: initMethodFunBody :: Nil)) + } } } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala index 2c27c125b9..e1ff675469 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala @@ -578,7 +578,7 @@ private[emitter] object CoreJSLib { str("char") }, { If(genIsScalaJSObject(value), { - genIdentBracketSelect(value DOT classData, cpn.name) + value DOT classData DOT cpn.name }, { typeof(value) }) @@ -665,33 +665,6 @@ private[emitter] object CoreJSLib { str("Cannot call isInstance() on a Class representing a JS trait/object") :: Nil)) } ::: - defineFunction2(VarField.newArrayObject) { (arrayClassData, lengths) => - Return(genCallHelper(VarField.newArrayObjectInternal, arrayClassData, lengths, int(0))) - } ::: - - defineFunction3(VarField.newArrayObjectInternal) { (arrayClassData, lengths, lengthIndex) => - val result = varRef("result") - val subArrayClassData = varRef("subArrayClassData") - val subLengthIndex = varRef("subLengthIndex") - val underlying = varRef("underlying") - val i = varRef("i") - - Block( - const(result, New(arrayClassData DOT cpn.constr, - BracketSelect(lengths, lengthIndex) :: Nil)), - If(lengthIndex < (lengths.length - 1), Block( - const(subArrayClassData, arrayClassData DOT cpn.componentData), - const(subLengthIndex, lengthIndex + 1), - const(underlying, result.u), - For(let(i, 0), i < underlying.length, i.++, { - BracketSelect(underlying, i) := - genCallHelper(VarField.newArrayObjectInternal, subArrayClassData, lengths, subLengthIndex) - }) - )), - Return(result) - ) - } ::: - defineFunction1(VarField.objectClone) { instance => // return Object.create(Object.getPrototypeOf(instance), $getOwnPropertyDescriptors(instance)); val callGetOwnPropertyDescriptors = genCallPolyfillableBuiltin( @@ -703,7 +676,7 @@ private[emitter] object CoreJSLib { defineFunction1(VarField.objectOrArrayClone) { instance => // return instance.$classData.isArrayClass ? instance.clone__O() : $objectClone(instance); - Return(If(genIdentBracketSelect(instance DOT classData, cpn.isArrayClass), + Return(If(instance DOT classData DOT cpn.isArrayClass, genApply(instance, cloneMethodName, Nil), genCallHelper(VarField.objectClone, instance))) } @@ -797,7 +770,7 @@ private[emitter] object CoreJSLib { StringLiteral(RuntimeClassNameMapperImpl.map( semantics.runtimeClassNameMapper, className.nameString)) }, - instance => genIdentBracketSelect(instance DOT classData, cpn.name), + instance => instance DOT classData DOT cpn.name, { if (nullPointers == CheckedBehavior.Unchecked) genApply(Null(), getNameMethodName, Nil) @@ -1126,13 +1099,13 @@ private[emitter] object CoreJSLib { ) ::: condDefs(esVersion >= ESVersion.ES2015 && nullPointers != CheckedBehavior.Unchecked)( defineFunction5(VarField.systemArraycopy) { (src, srcPos, dest, destPos, length) => - genArrayClassPropApply(src, ArrayClassProperty.copyTo, srcPos, dest, destPos, length) + genSyntheticPropApply(src, SyntheticProperty.copyTo, srcPos, dest, destPos, length) } ) ::: condDefs(arrayStores != CheckedBehavior.Unchecked)( defineFunction5(VarField.systemArraycopyRefs) { (src, srcPos, dest, destPos, length) => - If(Apply(genIdentBracketSelect(dest DOT classData, cpn.isAssignableFrom), List(src DOT classData)), { + If(Apply(dest DOT classData DOT cpn.isAssignableFrom, List(src DOT classData)), { /* Fast-path, no need for array store checks. This always applies * for arrays of the same type, and a fortiori, when `src eq dest`. */ @@ -1150,7 +1123,7 @@ private[emitter] object CoreJSLib { srcPos, dest.u.length, destPos, length) }, For(let(i, 0), i < length, i := ((i + 1) | 0), { - genArrayClassPropApply(dest, ArrayClassProperty.set, + genSyntheticPropApply(dest, SyntheticProperty.set, (destPos + i) | 0, BracketSelect(srcArray, (srcPos + i) | 0)) }) ) @@ -1165,10 +1138,10 @@ private[emitter] object CoreJSLib { const(srcData, src && (src DOT classData)), If(srcData === (dest && (dest DOT classData)), { // Both values have the same "data" (could also be falsy values) - If(srcData && genIdentBracketSelect(srcData, cpn.isArrayClass), { + If(srcData && (srcData DOT cpn.isArrayClass), { // Fast path: the values are array of the same type if (esVersion >= ESVersion.ES2015 && nullPointers == CheckedBehavior.Unchecked) - genArrayClassPropApply(src, ArrayClassProperty.copyTo, srcPos, dest, destPos, length) + genSyntheticPropApply(src, SyntheticProperty.copyTo, srcPos, dest, destPos, length) else genCallHelper(VarField.systemArraycopy, src, srcPos, dest, destPos, length) }, { @@ -1440,8 +1413,8 @@ private[emitter] object CoreJSLib { genCallHelper(VarField.throwArrayIndexOutOfBoundsException, i)) } - val getName = genArrayClassPropertyForDef(ArrayClassProperty.get) - val setName = genArrayClassPropertyForDef(ArrayClassProperty.set) + val getName = genSyntheticPropertyForDef(SyntheticProperty.get) + val setName = genSyntheticPropertyForDef(SyntheticProperty.set) List( MethodDef(static = false, getName, paramList(i), None, { @@ -1464,7 +1437,7 @@ private[emitter] object CoreJSLib { val i = varRef("i") val v = varRef("v") - val setName = genArrayClassPropertyForDef(ArrayClassProperty.set) + val setName = genSyntheticPropertyForDef(SyntheticProperty.set) List( MethodDef(static = false, setName, paramList(i, v), None, { @@ -1481,7 +1454,7 @@ private[emitter] object CoreJSLib { val destPos = varRef("destPos") val length = varRef("length") - val copyToName = genArrayClassPropertyForDef(ArrayClassProperty.copyTo) + val copyToName = genSyntheticPropertyForDef(SyntheticProperty.copyTo) val methodDef = MethodDef(static = false, copyToName, paramList(srcPos, dest, destPos, length), None, { @@ -1574,9 +1547,6 @@ private[emitter] object CoreJSLib { def privateFieldSet(fieldName: String, value: Tree): Tree = This() DOT fieldName := value - def publicFieldSet(fieldName: String, value: Tree): Tree = - genIdentBracketSelect(This(), fieldName) := value - val ctor = { MethodDef(static = false, Ident("constructor"), Nil, None, { Block( @@ -1603,11 +1573,11 @@ private[emitter] object CoreJSLib { privateFieldSet(cpn.wrapArray, Undefined()), privateFieldSet(cpn.isJSType, bool(false)), - publicFieldSet(cpn.name, str("")), - publicFieldSet(cpn.isPrimitive, bool(false)), - publicFieldSet(cpn.isInterface, bool(false)), - publicFieldSet(cpn.isArrayClass, bool(false)), - publicFieldSet(cpn.isInstance, Undefined()) + privateFieldSet(cpn.name, str("")), + privateFieldSet(cpn.isPrimitive, bool(false)), + privateFieldSet(cpn.isInterface, bool(false)), + privateFieldSet(cpn.isArrayClass, bool(false)), + privateFieldSet(cpn.isInstance, Undefined()) ) }) } @@ -1631,9 +1601,9 @@ private[emitter] object CoreJSLib { const(self, This()), // capture `this` for use in arrow fun privateFieldSet(cpn.isAssignableFromFun, genArrowFunction(paramList(that), Return(that === self))), - publicFieldSet(cpn.name, displayName), - publicFieldSet(cpn.isPrimitive, bool(true)), - publicFieldSet(cpn.isInstance, + privateFieldSet(cpn.name, displayName), + privateFieldSet(cpn.isPrimitive, bool(true)), + privateFieldSet(cpn.isInstance, genArrowFunction(paramList(obj), Return(bool(false)))), If(arrayClass !== Undefined(), { // it is undefined for void privateFieldSet(cpn._arrayOf, @@ -1685,9 +1655,9 @@ private[emitter] object CoreJSLib { }) }), privateFieldSet(cpn.isJSType, kindOrCtor === 2), - publicFieldSet(cpn.name, fullName), - publicFieldSet(cpn.isInterface, kindOrCtor === 1), - publicFieldSet(cpn.isInstance, isInstance || { + privateFieldSet(cpn.name, fullName), + privateFieldSet(cpn.isInterface, kindOrCtor === 1), + privateFieldSet(cpn.isInstance, isInstance || { genArrowFunction(paramList(obj), { Return(!(!(obj && (obj DOT classData) && BracketSelect(obj DOT classData DOT cpn.ancestors, internalName)))) @@ -1720,8 +1690,8 @@ private[emitter] object CoreJSLib { privateFieldSet(cpn.arrayBase, arrayBase), privateFieldSet(cpn.arrayDepth, arrayDepth), privateFieldSet(cpn.arrayEncodedName, name), - publicFieldSet(cpn.name, name), - publicFieldSet(cpn.isArrayClass, bool(true)) + privateFieldSet(cpn.name, name), + privateFieldSet(cpn.isArrayClass, bool(true)) ) } @@ -1754,7 +1724,7 @@ private[emitter] object CoreJSLib { }) }) }), - publicFieldSet(cpn.isInstance, + privateFieldSet(cpn.isInstance, genArrowFunction(paramList(obj), Return(obj instanceof arrayClass))), Return(This()) ) @@ -1789,7 +1759,7 @@ private[emitter] object CoreJSLib { val i = varRef("i") val v = varRef("v") - val setName = genArrayClassPropertyForDef(ArrayClassProperty.set) + val setName = genSyntheticPropertyForDef(SyntheticProperty.set) val boundsCheck = condTree(arrayIndexOutOfBounds != CheckedBehavior.Unchecked) { If((i < 0) || (i >= This().u.length), @@ -1798,7 +1768,7 @@ private[emitter] object CoreJSLib { val storeCheck = { If((v !== Null()) && !(componentData DOT cpn.isJSType) && - !Apply(genIdentBracketSelect(componentData, cpn.isInstance), v :: Nil), + !Apply(componentData DOT cpn.isInstance, v :: Nil), genCallHelper(VarField.throwArrayStoreException, v)) } @@ -1821,7 +1791,7 @@ private[emitter] object CoreJSLib { val destPos = varRef("destPos") val length = varRef("length") - val copyToName = genArrayClassPropertyForDef(ArrayClassProperty.copyTo) + val copyToName = genSyntheticPropertyForDef(SyntheticProperty.copyTo) val methodDef = MethodDef(static = false, copyToName, paramList(srcPos, dest, destPos, length), None, { @@ -1882,7 +1852,7 @@ private[emitter] object CoreJSLib { Return(New(ArrayClass, array :: Nil)) })), const(self, This()), // don't rely on the lambda being called with `this` as receiver - publicFieldSet(cpn.isInstance, genArrowFunction(paramList(obj), { + privateFieldSet(cpn.isInstance, genArrowFunction(paramList(obj), { val data = varRef("data") Block( const(data, obj && (obj DOT classData)), @@ -1911,9 +1881,13 @@ private[emitter] object CoreJSLib { def getClassOf = { MethodDef(static = false, Ident(cpn.getClassOf), Nil, None, { + /* We call the NoArgConstructorName with an argument, because it is + * added at the emitter level for java.lang.Class, without being in + * the IR. + */ Block( If(!(This() DOT cpn._classOf), - This() DOT cpn._classOf := genScalaClassNew(ClassClass, ObjectArgConstructorName, This()), + This() DOT cpn._classOf := genScalaClassNew(ClassClass, NoArgConstructorName, This()), Skip()), Return(This() DOT cpn._classOf) ) @@ -1921,7 +1895,7 @@ private[emitter] object CoreJSLib { } def isAssignableFrom = { - /* This is the public method called by j.l.Class.isAssignableFrom. It + /* This is the method called by `Class_isAssignableFrom`. It * first performs a fast-path with `this === that`, and otherwise calls * the internal `isAssignableFromFun` function. * The reason for this decomposition (as opposed to performing the @@ -1931,7 +1905,7 @@ private[emitter] object CoreJSLib { * We only need a polymorphic dispatch in the slow path. */ val that = varRef("that") - MethodDef(static = false, StringLiteral(cpn.isAssignableFrom), + MethodDef(static = false, Ident(cpn.isAssignableFrom), paramList(that), None, { Return( (This() === that) || // fast path @@ -1939,22 +1913,22 @@ private[emitter] object CoreJSLib { }) } - def checkCast = { + def cast = { + assert(asInstanceOfs != CheckedBehavior.Unchecked) val obj = varRef("obj") - MethodDef(static = false, StringLiteral(cpn.checkCast), paramList(obj), None, - if (asInstanceOfs != CheckedBehavior.Unchecked) { + MethodDef(static = false, Ident(cpn.cast), paramList(obj), None, { + Block( If((obj !== Null()) && !(This() DOT cpn.isJSType) && - !Apply(genIdentBracketSelect(This(), cpn.isInstance), obj :: Nil), - genCallHelper(VarField.throwClassCastException, obj, genIdentBracketSelect(This(), cpn.name)), - Skip()) - } else { - Skip() - } - ) + !Apply(This() DOT cpn.isInstance, obj :: Nil), + genCallHelper(VarField.throwClassCastException, obj, This() DOT cpn.name), + Skip()), + Return(obj) + ) + }) } def getSuperclass = { - MethodDef(static = false, StringLiteral(cpn.getSuperclass), Nil, None, { + MethodDef(static = false, Ident(cpn.getSuperclass), Nil, None, { Return(If(This() DOT cpn.parentData, Apply(This() DOT cpn.parentData DOT cpn.getClassOf, Nil), Null())) @@ -1962,26 +1936,18 @@ private[emitter] object CoreJSLib { } def getComponentType = { - MethodDef(static = false, StringLiteral(cpn.getComponentType), Nil, None, { + MethodDef(static = false, Ident(cpn.getComponentType), Nil, None, { Return(If(This() DOT cpn.componentData, Apply(This() DOT cpn.componentData DOT cpn.getClassOf, Nil), Null())) }) } - def newArrayOfThisClass = { - val lengths = varRef("lengths") - val arrayClassData = varRef("arrayClassData") - val i = varRef("i") - MethodDef(static = false, StringLiteral(cpn.newArrayOfThisClass), - paramList(lengths), None, { - Block( - let(arrayClassData, This()), - For(let(i, 0), i < lengths.length, i.++, { - arrayClassData := Apply(arrayClassData DOT cpn.getArrayOf, Nil) - }), - Return(genCallHelper(VarField.newArrayObject, arrayClassData, lengths)) - ) + def newArray = { + val length = varRef("length") + MethodDef(static = false, Ident(cpn.newArray), + paramList(length), None, { + Return(New(Apply(This() DOT cpn.getArrayOf, Nil) DOT cpn.constr, length :: Nil)) }) } @@ -1995,11 +1961,16 @@ private[emitter] object CoreJSLib { if (globalKnowledge.isClassClassInstantiated) { List( getClassOf, - isAssignableFrom, - checkCast, + isAssignableFrom + ) ::: ( + if (asInstanceOfs != CheckedBehavior.Unchecked) + List(cast) + else + Nil + ) ::: List( getSuperclass, getComponentType, - newArrayOfThisClass + newArray ) } else if (arrayStores != CheckedBehavior.Unchecked) { List( @@ -2035,7 +2006,7 @@ private[emitter] object CoreJSLib { Block( const(arrayDepth, data DOT cpn.arrayDepth), Return(If(arrayDepth === depth, { - !genIdentBracketSelect(data DOT cpn.arrayBase, cpn.isPrimitive) + !(data DOT cpn.arrayBase DOT cpn.isPrimitive) }, { arrayDepth > depth })) @@ -2098,9 +2069,6 @@ private[emitter] object CoreJSLib { def privateFieldSet(fieldName: String, value: Tree): Tree = typeDataVar DOT fieldName := value - def publicFieldSet(fieldName: String, value: Tree): Tree = - genIdentBracketSelect(typeDataVar, fieldName) := value - extractWithGlobals( globalVarDef(VarField.d, ObjectClass, New(globalVar(VarField.TypeData, CoreVar), Nil))) ::: List( @@ -2108,11 +2076,11 @@ private[emitter] object CoreJSLib { privateFieldSet(cpn.arrayEncodedName, str("L" + fullName + ";")), privateFieldSet(cpn.isAssignableFromFun, { genArrowFunction(paramList(that), { - Return(!genIdentBracketSelect(that, cpn.isPrimitive)) + Return(!(that DOT cpn.isPrimitive)) }) }), - publicFieldSet(cpn.name, str(fullName)), - publicFieldSet(cpn.isInstance, + privateFieldSet(cpn.name, str(fullName)), + privateFieldSet(cpn.isInstance, genArrowFunction(paramList(obj), Return(obj !== Null()))), privateFieldSet(cpn._arrayOf, { Apply(New(globalVar(VarField.TypeData, CoreVar), Nil) DOT cpn.initSpecializedArray, List( @@ -2124,7 +2092,7 @@ private[emitter] object CoreJSLib { Block( const(thatDepth, that DOT cpn.arrayDepth), Return(If(thatDepth === 1, { - !genIdentBracketSelect(that DOT cpn.arrayBase, cpn.isPrimitive) + !(that DOT cpn.arrayBase DOT cpn.isPrimitive) }, { (thatDepth > 1) })) @@ -2269,7 +2237,7 @@ private[emitter] object CoreJSLib { // cannot extend AnyVal because this is not a static class private implicit class CustomTreeOps(private val self: Tree) { - def u: Tree = genArrayClassPropSelect(self, ArrayClassProperty.u) + def u: Tree = genSyntheticPropSelect(self, SyntheticProperty.u) } } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/EmitterNames.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/EmitterNames.scala index ba31cd7019..a31366670f 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/EmitterNames.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/EmitterNames.scala @@ -26,7 +26,6 @@ private[emitter] object EmitterNames { // Field names - val dataFieldName = FieldName(ClassClass, SimpleFieldName("data")) val exceptionFieldName = FieldName(JavaScriptExceptionClass, SimpleFieldName("exception")) // Method names diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index 4e322050b2..444cca7fb6 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -632,11 +632,11 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { } if (checked) { - genArrayClassPropApply(genArray, ArrayClassProperty.set, genIndex, genRhs) + genSyntheticPropApply(genArray, SyntheticProperty.set, genIndex, genRhs) } else { js.Assign( js.BracketSelect( - genArrayClassPropSelect(genArray, ArrayClassProperty.u)(lhs.pos), + genSyntheticPropSelect(genArray, SyntheticProperty.u)(lhs.pos), genIndex)(lhs.pos), genRhs) } @@ -876,7 +876,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { def genUnchecked(): js.Tree = { if (esFeatures.esVersion >= ESVersion.ES2015 && semantics.nullPointers == CheckedBehavior.Unchecked) - genArrayClassPropApply(jsArgs.head, ArrayClassProperty.copyTo, jsArgs.tail) + genSyntheticPropApply(jsArgs.head, SyntheticProperty.copyTo, jsArgs.tail) else genCallHelper(VarField.systemArraycopy, jsArgs: _*) } @@ -1047,8 +1047,8 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { if noExtractYet || semantics.asInstanceOfs == Unchecked => AsInstanceOf(rec(expr), tpe) - case NewArray(tpe, lengths) => - NewArray(tpe, recs(lengths)) + case NewArray(tpe, length) => + NewArray(tpe, rec(length)) case ArrayValue(tpe, elems) => ArrayValue(tpe, recs(elems)) case JSArrayConstr(items) if !needsToTranslateAnySpread(items) => @@ -1270,6 +1270,12 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case BinaryOp(BinaryOp.String_charAt, lhs, rhs) => allowBehavior(semantics.stringIndexOutOfBounds) && test(lhs) && test(rhs) + // Binary Class_x operations that can have side effects + case BinaryOp(BinaryOp.Class_cast, lhs, rhs) => + allowBehavior(semantics.asInstanceOfs) && test(lhs) && test(rhs) + case BinaryOp(BinaryOp.Class_newArray, lhs, rhs) => + allowBehavior(semantics.negativeArraySizes) && allowUnpure && test(lhs) && test(rhs) + // Expressions preserving pureness (modulo NPE) case Block(trees) => trees forall test case If(cond, thenp, elsep) => test(cond) && test(thenp) && test(elsep) @@ -1338,8 +1344,8 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { allowSideEffects && test(expr) // may TypeError // Array operations with conditional exceptions - case NewArray(tpe, lengths) => - allowBehavior(semantics.negativeArraySizes) && allowUnpure && lengths.forall(test) + case NewArray(tpe, length) => + allowBehavior(semantics.negativeArraySizes) && allowUnpure && test(length) case ArraySelect(array, index) => allowBehavior(semantics.arrayIndexOutOfBounds) && allowUnpure && testNPE(array) && test(index) @@ -1751,9 +1757,9 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { redo(BinaryOp(op, newLhs, newRhs))(env) } - case NewArray(tpe, lengths) => - unnest(lengths) { (newLengths, env) => - redo(NewArray(tpe, newLengths))(env) + case NewArray(tpe, length) => + unnest(length) { (newLength, env) => + redo(NewArray(tpe, newLength))(env) } case ArrayValue(tpe, elems) => @@ -2385,6 +2391,20 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { newLhs else genCallHelper(VarField.n, newLhs) + + // Class operations + case Class_name => + genGetDataOf(newLhs) DOT cpn.name + case Class_isPrimitive => + genGetDataOf(newLhs) DOT cpn.isPrimitive + case Class_isInterface => + genGetDataOf(newLhs) DOT cpn.isInterface + case Class_isArray => + genGetDataOf(newLhs) DOT cpn.isArrayClass + case Class_componentType => + js.Apply(genGetDataOf(newLhs) DOT cpn.getComponentType, Nil) + case Class_superClass => + js.Apply(genGetDataOf(newLhs) DOT cpn.getSuperclass, Nil) } case BinaryOp(op, lhs, rhs) => @@ -2392,6 +2412,11 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { val newLhs = transformExpr(lhs, preserveChar = (op == String_+)) val newRhs = transformExpr(rhs, preserveChar = (op == String_+)) + def extractClassData(origTree: Tree, jsTree: js.Tree): js.Tree = origTree match { + case ClassOf(typeRef) => genClassDataOf(typeRef)(implicitly, implicitly, origTree.pos) + case _ => genGetDataOf(jsTree) + } + (op: @switch) match { case === | !== => /* Semantically, this is an `Object.is` test in JS. However, we @@ -2664,10 +2689,23 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case CheckedBehavior.Unchecked => js.Apply(genIdentBracketSelect(newLhs, "charCodeAt"), List(newRhs)) } + + case Class_isInstance => + js.Apply(extractClassData(lhs, newLhs) DOT cpn.isInstance, newRhs :: Nil) + case Class_isAssignableFrom => + js.Apply(extractClassData(lhs, newLhs) DOT cpn.isAssignableFrom, + extractClassData(rhs, newRhs) :: Nil) + case Class_cast => + if (semantics.asInstanceOfs == CheckedBehavior.Unchecked) + js.Block(newLhs, newRhs) + else + js.Apply(extractClassData(lhs, newLhs) DOT cpn.cast, newRhs :: Nil) + case Class_newArray => + js.Apply(extractClassData(lhs, newLhs) DOT cpn.newArray, newRhs :: Nil) } - case NewArray(typeRef, lengths) => - genNewArray(typeRef, lengths.map(transformExprNoChar)) + case NewArray(typeRef, length) => + js.New(genArrayConstrOf(typeRef), transformExprNoChar(length) :: Nil) case ArrayValue(typeRef, elems) => val preserveChar = typeRef match { @@ -2680,7 +2718,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case ArrayLength(array) => val newArray = transformExprNoChar(checkNotNull(array)) genIdentBracketSelect( - genArrayClassPropSelect(newArray, ArrayClassProperty.u), + genSyntheticPropSelect(newArray, SyntheticProperty.u), "length") case ArraySelect(array, index) => @@ -2688,9 +2726,9 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { val newIndex = transformExprNoChar(index) semantics.arrayIndexOutOfBounds match { case CheckedBehavior.Compliant | CheckedBehavior.Fatal => - genArrayClassPropApply(newArray, ArrayClassProperty.get, newIndex) + genSyntheticPropApply(newArray, SyntheticProperty.get, newIndex) case CheckedBehavior.Unchecked => - js.BracketSelect(genArrayClassPropSelect(newArray, ArrayClassProperty.u), newIndex) + js.BracketSelect(genSyntheticPropSelect(newArray, SyntheticProperty.u), newIndex) } case tree: RecordSelect => @@ -2769,8 +2807,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case Transient(ZeroOf(runtimeClass)) => js.DotSelect( - genSelect(transformExprNoChar(checkNotNull(runtimeClass)), - FieldIdent(dataFieldName)), + genGetDataOf(transformExprNoChar(checkNotNull(runtimeClass))), js.Ident(cpn.zero)) case Transient(NativeArrayWrapper(elemClass, nativeArray)) => @@ -2781,9 +2818,8 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { extractWithGlobals( genNativeArrayWrapper(arrayTypeRef, newNativeArray)) case _ => - val elemClassData = genSelect( - transformExprNoChar(checkNotNull(elemClass)), - FieldIdent(dataFieldName)) + val elemClassData = + genGetDataOf(transformExprNoChar(checkNotNull(elemClass))) val arrayClassData = js.Apply( js.DotSelect(elemClassData, js.Ident(cpn.getArrayOf)), Nil) js.Apply(arrayClassData DOT cpn.wrapArray, newNativeArray :: Nil) @@ -2794,7 +2830,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case Transient(ArrayToTypedArray(expr, primRef)) => val value = transformExprNoChar(checkNotNull(expr)) - val valueUnderlying = genArrayClassPropSelect(value, ArrayClassProperty.u) + val valueUnderlying = genSyntheticPropSelect(value, SyntheticProperty.u) if (es2015) { js.Apply(genIdentBracketSelect(valueUnderlying, "slice"), Nil) @@ -3247,6 +3283,9 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { js.Apply(globalVar(field, (className, method.name)), args) } + private def genGetDataOf(jlClassValue: js.Tree)(implicit pos: Position): js.Tree = + genSyntheticPropSelect(jlClassValue, SyntheticProperty.data) + private def genCallPolyfillableBuiltin( builtin: PolyfillableBuiltin, args: js.Tree*)( implicit pos: Position): js.Tree = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala index 562ad1e519..d5c5d23342 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/KnowledgeGuardian.scala @@ -98,11 +98,12 @@ private[emitter] final class KnowledgeGuardian(config: Emitter.Config) { val invalidateAll = { if (specialInfo == null) { specialInfo = new SpecialInfo(objectClass, classClass, - arithmeticExceptionClass, hijackedClasses.result()) + arithmeticExceptionClass, hijackedClasses.result(), + moduleSet.globalInfo) false } else { specialInfo.update(objectClass, classClass, arithmeticExceptionClass, - hijackedClasses.result()) + hijackedClasses.result(), moduleSet.globalInfo) } } @@ -512,7 +513,8 @@ private[emitter] final class KnowledgeGuardian(config: Emitter.Config) { private class SpecialInfo(initObjectClass: Option[LinkedClass], initClassClass: Option[LinkedClass], initArithmeticExceptionClass: Option[LinkedClass], - initHijackedClasses: Iterable[LinkedClass]) extends Unregisterable { + initHijackedClasses: Iterable[LinkedClass], + initGlobalInfo: LinkedGlobalInfo) extends Unregisterable { import SpecialInfo._ @@ -520,7 +522,7 @@ private[emitter] final class KnowledgeGuardian(config: Emitter.Config) { computeInstantiatedSpecialClassBitSet(initClassClass, initArithmeticExceptionClass) private var isParentDataAccessed = - computeIsParentDataAccessed(initClassClass) + computeIsParentDataAccessed(initGlobalInfo) private var methodsInRepresentativeClasses = computeMethodsInRepresentativeClasses(initObjectClass, initHijackedClasses) @@ -539,7 +541,8 @@ private[emitter] final class KnowledgeGuardian(config: Emitter.Config) { def update(objectClass: Option[LinkedClass], classClass: Option[LinkedClass], arithmeticExceptionClass: Option[LinkedClass], - hijackedClasses: Iterable[LinkedClass]): Boolean = { + hijackedClasses: Iterable[LinkedClass], + globalInfo: LinkedGlobalInfo): Boolean = { var invalidateAll = false val newInstantiatedSpecialClassBitSet = @@ -549,7 +552,7 @@ private[emitter] final class KnowledgeGuardian(config: Emitter.Config) { invalidateAskers(instantiatedSpecialClassAskers) } - val newIsParentDataAccessed = computeIsParentDataAccessed(classClass) + val newIsParentDataAccessed = computeIsParentDataAccessed(globalInfo) if (newIsParentDataAccessed != isParentDataAccessed) { isParentDataAccessed = newIsParentDataAccessed invalidateAll = true @@ -589,16 +592,8 @@ private[emitter] final class KnowledgeGuardian(config: Emitter.Config) { bitSet } - private def computeIsParentDataAccessed(classClass: Option[LinkedClass]): Boolean = { - def methodExists(linkedClass: LinkedClass, methodName: MethodName): Boolean = { - linkedClass.methods.exists { m => - m.flags.namespace == MemberNamespace.Public && - m.methodName == methodName - } - } - - classClass.exists(methodExists(_, getSuperclassMethodName)) - } + private def computeIsParentDataAccessed(globalInfo: LinkedGlobalInfo): Boolean = + globalInfo.isClassSuperClassUsed private def computeMethodsInRepresentativeClasses(objectClass: Option[LinkedClass], hijackedClasses: Iterable[LinkedClass]): List[(MethodName, Set[ClassName])] = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameCompressor.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameCompressor.scala index 0db5ead5bf..e2e8d32d34 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameCompressor.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/NameCompressor.scala @@ -56,8 +56,8 @@ private[emitter] final class NameCompressor(config: Emitter.Config) { def genResolverFor(methodName: MethodName): Resolver = entries.getOrElseUpdate(methodName, new MethodNameEntry(methodName)).genResolver() - def genResolverFor(prop: ArrayClassProperty): Resolver = - entries.getOrElseUpdate(prop, new ArrayClassPropEntry(prop)).genResolver() + def genResolverFor(prop: SyntheticProperty): Resolver = + entries.getOrElseUpdate(prop, new SyntheticPropEntry(prop)).genResolver() def genResolverForAncestor(ancestor: ClassName): Resolver = ancestorEntries.getOrElseUpdate(ancestor, new AncestorNameEntry(ancestor)).genResolver() @@ -168,14 +168,14 @@ private[emitter] object NameCompressor { case (x: MethodNameEntry, y: MethodNameEntry) => x.methodName.compareTo(y.methodName) - case (x: ArrayClassPropEntry, y: ArrayClassPropEntry) => + case (x: SyntheticPropEntry, y: SyntheticPropEntry) => x.property.compareTo(y.property) case _ => def ordinalFor(x: PropertyNameEntry): Int = x match { - case _: FieldNameEntry => 1 - case _: MethodNameEntry => 2 - case _: ArrayClassPropEntry => 3 + case _: FieldNameEntry => 1 + case _: MethodNameEntry => 2 + case _: SyntheticPropEntry => 3 } ordinalFor(this) - ordinalFor(that) } @@ -195,11 +195,11 @@ private[emitter] object NameCompressor { override def toString(): String = s"MethodNameEntry(${methodName.nameString})" } - private final class ArrayClassPropEntry(val property: ArrayClassProperty) + private final class SyntheticPropEntry(val property: SyntheticProperty) extends PropertyNameEntry { protected def debugString: String = property.nonMinifiedName - override def toString(): String = s"ArrayClassPropEntry(${property.nonMinifiedName})" + override def toString(): String = s"SyntheticPropEntry(${property.nonMinifiedName})" } private final class AncestorNameEntry(val ancestor: ClassName) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala index 2a9b3cf93e..44f8708265 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SJSGen.scala @@ -55,7 +55,7 @@ private[emitter] final class SJSGen( /** `Char.c`: the int value of the character. */ val c = "c" - // --- TypeData private fields --- + // --- TypeData fields --- /** `TypeData.constr`: the run-time constructor of the class. */ val constr = if (minify) "C" else "constr" @@ -96,6 +96,18 @@ private[emitter] final class SJSGen( /** `TypeData.isJSType`: whether it is a JS type. */ val isJSType = if (minify) "J" else "isJSType" + /** `TypeData.name`: the user name of the class (the result of `jl.Class.getName()`). */ + val name = if (minify) "N" else "name" + + /** `TypeData.isPrimitive`: whether it is a primitive type. */ + val isPrimitive = if (minify) "X" else "isPrimitive" + + /** `TypeData.isInterface`: whether it is an interface type. */ + val isInterface = if (minify) "Y" else "isInterface" + + /** `TypeData.isArrayClass`: whether it is an array type. */ + val isArrayClass = if (minify) "Z" else "isArrayClass" + // --- TypeData constructors --- val initPrim = if (minify) "p" else "initPrim" @@ -106,7 +118,7 @@ private[emitter] final class SJSGen( val initArray = if (minify) "a" else "initArray" - // --- TypeData private methods --- + // --- TypeData methods --- /** `TypeData.getArrayOf()`: the `Type` instance for that type's array type. */ val getArrayOf = if (minify) "r" else "getArrayOf" @@ -114,35 +126,23 @@ private[emitter] final class SJSGen( /** `TypeData.getClassOf()`: the `jl.Class` instance for that type. */ val getClassOf = if (minify) "l" else "getClassOf" - // --- TypeData public fields --- never minified - - /** `TypeData.name`: public, the user name of the class (the result of `jl.Class.getName()`). */ - val name = "name" - - /** `TypeData.isPrimitive`: public, whether it is a primitive type. */ - val isPrimitive = "isPrimitive" - - /** `TypeData.isInterface`: public, whether it is an interface type. */ - val isInterface = "isInterface" - - /** `TypeData.isArrayClass`: public, whether it is an array type. */ - val isArrayClass = "isArrayClass" + /** `TypeData.getSuperclass()`: implementation of `Class_superClass`. */ + val getSuperclass = if (minify) "S" else "getSuperclass" - /** `TypeData.isInstance()`: public, implementation of `jl.Class.isInstance`. */ - val isInstance = "isInstance" + /** `TypeData.getComponentType()`: implementation of `Class_componentType`. */ + val getComponentType = if (minify) "Q" else "getComponentType" - /** `TypeData.isAssignableFrom()`: public, implementation of `jl.Class.isAssignableFrom`. */ - val isAssignableFrom = "isAssignableFrom" + /** `TypeData.isInstance()`: implementation of `Class_isInstance`. */ + val isInstance = if (minify) "I" else "isInstance" - // --- TypeData public methods --- never minified + /** `TypeData.isAssignableFrom()`: implementation of `Class_isAssignableFrom`. */ + val isAssignableFrom = if (minify) "R" else "isAssignableFrom" - val checkCast = "checkCast" + /** `TypeData.cast()`: implementation of `Class_cast`. */ + val cast = if (minify) "T" else "cast" - val getSuperclass = "getSuperclass" - - val getComponentType = "getComponentType" - - val newArrayOfThisClass = "newArrayOfThisClass" + /** `TypeData.newArray()`: implementation of `Class_newArray`. */ + val newArray = if (minify) "U" else "newArray" } /* This is a `val` because it is used at the top of every file, outside of @@ -356,29 +356,29 @@ private[emitter] final class SJSGen( } } - def genArrayClassPropApply(receiver: Tree, prop: ArrayClassProperty, args: Tree*)( + def genSyntheticPropApply(receiver: Tree, prop: SyntheticProperty, args: Tree*)( implicit pos: Position): Tree = { - genArrayClassPropApply(receiver, prop, args.toList) + genSyntheticPropApply(receiver, prop, args.toList) } - def genArrayClassPropApply(receiver: Tree, prop: ArrayClassProperty, args: List[Tree])( + def genSyntheticPropApply(receiver: Tree, prop: SyntheticProperty, args: List[Tree])( implicit pos: Position): Tree = { - Apply(genArrayClassPropSelect(receiver, prop), args) + Apply(genSyntheticPropSelect(receiver, prop), args) } - def genArrayClassPropSelect(qualifier: Tree, prop: ArrayClassProperty)( + def genSyntheticPropSelect(qualifier: Tree, prop: SyntheticProperty)( implicit pos: Position): Tree = { - DotSelect(qualifier, genArrayClassProperty(prop)) + DotSelect(qualifier, genSyntheticProperty(prop)) } - def genArrayClassProperty(prop: ArrayClassProperty)(implicit pos: Position): MaybeDelayedIdent = { + def genSyntheticProperty(prop: SyntheticProperty)(implicit pos: Position): MaybeDelayedIdent = { nameCompressor match { case None => Ident(prop.nonMinifiedName) case Some(compressor) => DelayedIdent(compressor.genResolverFor(prop)) } } - def genArrayClassPropertyForDef(prop: ArrayClassProperty)(implicit pos: Position): MaybeDelayedIdent = { + def genSyntheticPropertyForDef(prop: SyntheticProperty)(implicit pos: Position): MaybeDelayedIdent = { nameCompressor match { case None => Ident(prop.nonMinifiedName) case Some(compressor) => DelayedIdent(compressor.genResolverFor(prop), prop.originalName) @@ -714,21 +714,6 @@ private[emitter] final class SJSGen( } } - def genNewArray(arrayTypeRef: ArrayTypeRef, lengths: List[Tree])( - implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, - pos: Position): Tree = { - lengths match { - case Nil => - throw new IllegalArgumentException( - "Cannot create a new array with 0 dimensions") - case length :: Nil => - New(genArrayConstrOf(arrayTypeRef), length :: Nil) - case _ => - genCallHelper(VarField.newArrayObject, genClassDataOf(arrayTypeRef), - ArrayConstr(lengths)) - } - } - def genArrayValue(arrayTypeRef: ArrayTypeRef, elems: List[Tree])( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, tracking: GlobalRefTracking, pos: Position): WithGlobals[Tree] = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SyntheticProperty.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SyntheticProperty.scala new file mode 100644 index 0000000000..6e697988de --- /dev/null +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/SyntheticProperty.scala @@ -0,0 +1,56 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.linker.backend.emitter + +import org.scalajs.ir.OriginalName + +/** Represents a synthetic property of some Scala classes. + * + * A synthetic property does not exist as `FieldDef` or `MethodDef` in the IR, + * but is added by the Emitter on some classes that belong to the Scala class + * hierarchy. + * + * Currently, these only include properties of the `ArrayClass`es, as well as + * the magic `jl.Class.data` property. + * + * These properties live in the same namespace as Scala field and method + * names, because the `ArrayClass`es extend `j.l.Object`. Therefore, they + * must take part in the global property minification algorithm. + */ +final class SyntheticProperty(val nonMinifiedName: String) + extends Comparable[SyntheticProperty] { + + val originalName: OriginalName = OriginalName(nonMinifiedName) + + def compareTo(that: SyntheticProperty): Int = + this.nonMinifiedName.compareTo(that.nonMinifiedName) + + override def toString(): String = s"SyntheticProperty($nonMinifiedName)" +} + +object SyntheticProperty { + /** `ArrayClass.u`: the underlying array or typed array. */ + val u: SyntheticProperty = new SyntheticProperty("u") + + /** `ArrayClass.get()`: gets one element. */ + val get: SyntheticProperty = new SyntheticProperty("get") + + /** `ArrayClass.set()`: sets one element. */ + val set: SyntheticProperty = new SyntheticProperty("set") + + /** `ArrayClass.copyTo()`: copies from that array to another array. */ + val copyTo: SyntheticProperty = new SyntheticProperty("copyTo") + + /** `jl.Class.data`: the underlying `TypeData` of the class. */ + val data: SyntheticProperty = new SyntheticProperty("data") +} diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala index e166c997c5..a43a127f61 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarField.scala @@ -132,6 +132,9 @@ private[emitter] object VarField { /** Local field for dynamic imports. */ final val module = mk("$module") + /** Local field for the magic `data` argument to the constructor of `jl.Class`. */ + final val data = mk("$data") + // Core fields: Generated by the CoreJSLib /** The linking info object. */ @@ -231,10 +234,6 @@ private[emitter] object VarField { final val systemArraycopyFull = mk("$systemArraycopyFull") - final val newArrayObject = mk("$newArrayObject") - - final val newArrayObjectInternal = mk("$newArrayObjectInternal") - final val throwArrayCastException = mk("$throwArrayCastException") final val throwArrayIndexOutOfBoundsException = mk("$throwArrayIndexOutOFBoundsException") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala index caa266fd10..a4ff9ceff4 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala @@ -365,9 +365,20 @@ class ClassEmitter(coreSpec: CoreSpec) { isMutable = true // initialized by the constructors, so always mutable at the Wasm level ) } + val jlClassDataField = if (className == ClassClass) { + // Inject the magic `data` field + watpe.StructField( + genFieldID.classData, + OriginalName("data"), + watpe.RefType(genTypeID.typeData), + isMutable = false + ) :: Nil + } else { + Nil + } val structTypeID = genTypeID.forClass(className) val superType = clazz.superClass.map(s => genTypeID.forClass(s.name)) - val structType = watpe.StructType(vtableField :: itablesField :: fields) + val structType = watpe.StructType(vtableField :: itablesField :: fields ::: jlClassDataField) val subType = watpe.SubType( structTypeID, makeDebugName(ns.ClassInstance, className), @@ -598,6 +609,9 @@ class ClassEmitter(coreSpec: CoreSpec) { makeDebugName(ns.NewDefault, className), clazz.pos ) + val dataParamOpt = + if (className == ClassClass) Some(fb.addParam("data", watpe.RefType(genTypeID.typeData))) + else None fb.setResultType(watpe.RefType(structTypeID)) fb += wa.GlobalGet(genGlobalID.forVTable(className)) @@ -610,6 +624,8 @@ class ClassEmitter(coreSpec: CoreSpec) { classInfo.allFieldDefs.foreach { f => fb += genZeroOf(f.ftpe) } + for (dataParam <- dataParamOpt) + fb += wa.LocalGet(dataParam) fb += wa.StructNew(structTypeID) fb.buildAndAddToModule() diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala index 4da2acaff9..90dfbb16ad 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala @@ -94,6 +94,17 @@ final class CoreWasmLib(coreSpec: CoreSpec) { ) } + private val primRefsWithArrayTypes = List( + BooleanRef -> KindBoolean, + CharRef -> KindChar, + ByteRef -> KindByte, + ShortRef -> KindShort, + IntRef -> KindInt, + LongRef -> KindLong, + FloatRef -> KindFloat, + DoubleRef -> KindDouble + ) + /** Generates definitions that must come *before* the code generated for regular classes. * * This notably includes the `typeData` definitions, since the vtable of `jl.Object` is a subtype @@ -421,25 +432,7 @@ final class CoreWasmLib(coreSpec: CoreSpec) { Nil ) - addHelperImport( - genFunctionID.genJSTypeMetaData, - List( - RefType(genTypeID.typeData), // typeData - RefType.extern, // name - Int32, // isPrimitive - Int32, // isArrayClass - Int32, // isInterface, - RefType.func, // isInstanceFun - RefType.func, // isAssignableFromFun - RefType.func, // checkCastFun - RefType.func, // getComponentTypeFun - RefType.func // newArrayOfThisClassFun - ), - List(RefType.any) - ) addHelperImport(genFunctionID.makeTypeError, List(RefType.extern), List(RefType.extern)) - addHelperImport(genFunctionID.jsArrayLength, List(anyref), List(Int32)) - addHelperImport(genFunctionID.jsArrayGetInt, List(anyref, Int32), List(Int32)) addHelperImport(genFunctionID.jsGlobalRefGet, List(RefType.extern), List(anyref)) addHelperImport(genFunctionID.jsGlobalRefSet, List(RefType.extern, anyref), Nil) @@ -699,13 +692,13 @@ final class CoreWasmLib(coreSpec: CoreSpec) { genIsInstance() genIsAssignableFrom() - genCheckCast() + if (semantics.asInstanceOfs != CheckedBehavior.Unchecked) + genCast() genGetComponentType() - genNewArrayOfThisClass() + genNewArray() genAnyGetClass() genAnyGetClassName() genAnyGetTypeData() - genNewArrayObject() genIdentityHashCode() genSearchReflectiveProxy() genArrayCloneFunctions() @@ -1087,51 +1080,18 @@ final class CoreWasmLib(coreSpec: CoreSpec) { val classInstanceLocal = fb.addLocal("classInstance", RefType(genTypeID.ClassStruct)) - // classInstance := newDefault$java.lang.Class() + // classInstance := newDefault$java.lang.Class(typeData) // leave it on the stack for the constructor call + fb += LocalGet(typeDataParam) fb += Call(genFunctionID.newDefault(ClassClass)) fb += LocalTee(classInstanceLocal) - /* The JS object containing metadata to pass as argument to the `jl.Class` constructor. - * Specified by https://lampwww.epfl.ch/~doeraene/sjsir-semantics/#sec-sjsir-createclassdataof - * Leave it on the stack. - */ - - // typeData - fb += LocalGet(typeDataParam) - // name - fb += LocalGet(typeDataParam) - fb += Call(genFunctionID.typeDataName) - // isPrimitive: (typeData.kind <= KindLastPrimitive) - fb += LocalGet(typeDataParam) - fb += StructGet(genTypeID.typeData, genFieldID.typeData.kind) - fb += I32Const(KindLastPrimitive) - fb += I32LeU - // isArrayClass: (typeData.kind == KindArray) - fb += LocalGet(typeDataParam) - fb += StructGet(genTypeID.typeData, genFieldID.typeData.kind) - fb += I32Const(KindArray) - fb += I32Eq - // isInterface: (typeData.kind == KindInterface) - fb += LocalGet(typeDataParam) - fb += StructGet(genTypeID.typeData, genFieldID.typeData.kind) - fb += I32Const(KindInterface) - fb += I32Eq - // the various helper functions - fb += ctx.refFuncWithDeclaration(genFunctionID.isInstance) - fb += ctx.refFuncWithDeclaration(genFunctionID.isAssignableFrom) - fb += ctx.refFuncWithDeclaration(genFunctionID.checkCast) - fb += ctx.refFuncWithDeclaration(genFunctionID.getComponentType) - fb += ctx.refFuncWithDeclaration(genFunctionID.newArrayOfThisClass) - // call the helper to build the metadata object - fb += Call(genFunctionID.genJSTypeMetaData) - - // Call java.lang.Class::(dataObject) + // Call java.lang.Class::() fb += Call( genFunctionID.forMethod( MemberNamespace.Constructor, ClassClass, - SpecialNames.AnyArgConstructorName + NoArgConstructorName ) ) @@ -2335,64 +2295,70 @@ final class CoreWasmLib(coreSpec: CoreSpec) { fb.buildAndAddToModule() } - /** `checkCast: (ref typeData), anyref -> []`. + /** `cast: (ref jlClass), anyref -> anyref`. * * Casts the given value to the given type; subject to undefined behaviors. + * + * This is the underlying func for the `Class_cast` operation. */ - private def genCheckCast()(implicit ctx: WasmContext): Unit = { - val typeDataType = RefType(genTypeID.typeData) + private def genCast()(implicit ctx: WasmContext): Unit = { + assert(semantics.asInstanceOfs != CheckedBehavior.Unchecked) - val fb = newFunctionBuilder(genFunctionID.checkCast) - val typeDataParam = fb.addParam("typeData", typeDataType) + val fb = newFunctionBuilder(genFunctionID.cast) + val jlClassParam = fb.addParam("jlClass", RefType(genTypeID.ClassStruct)) val valueParam = fb.addParam("value", RefType.anyref) + fb.setResultType(RefType.anyref) - if (semantics.asInstanceOfs != CheckedBehavior.Unchecked) { - fb.block() { successLabel => - // If typeData.kind == KindJSType, succeed - fb += LocalGet(typeDataParam) - fb += StructGet(genTypeID.typeData, genFieldID.typeData.kind) - fb += I32Const(KindJSType) - fb += I32Eq - fb += BrIf(successLabel) + val typeDataLocal = fb.addLocal("typeData", RefType(genTypeID.typeData)) - // If value is null, succeed - fb += LocalGet(valueParam) - fb += RefIsNull // consumes `value`, unlike `BrOnNull` which would leave it on the stack - fb += BrIf(successLabel) + fb.block() { successLabel => + // typeData := jlClass.data, leave it on the stack for the kind test + fb += LocalGet(jlClassParam) + fb += StructGet(genTypeID.ClassStruct, genFieldID.classData) + fb += LocalTee(typeDataLocal) - // If isInstance(typeData, value), succeed - fb += LocalGet(typeDataParam) - fb += LocalGet(valueParam) - fb += Call(genFunctionID.isInstance) - fb += BrIf(successLabel) + // If typeData.kind == KindJSType, succeed + fb += StructGet(genTypeID.typeData, genFieldID.typeData.kind) + fb += I32Const(KindJSType) + fb += I32Eq + fb += BrIf(successLabel) - // Otherwise, it is a CCE - fb += LocalGet(valueParam) - fb += LocalGet(typeDataParam) - fb += Call(genFunctionID.classCastException) - fb += Unreachable // for clarity; technically redundant since the stacks align - } + // If value is null, succeed + fb += LocalGet(valueParam) + fb += RefIsNull // consumes `value`, unlike `BrOnNull` which would leave it on the stack + fb += BrIf(successLabel) + + // If isInstance(typeData, value), succeed + fb += LocalGet(typeDataLocal) + fb += LocalGet(valueParam) + fb += Call(genFunctionID.isInstance) + fb += BrIf(successLabel) + + // Otherwise, it is a CCE + fb += LocalGet(valueParam) + fb += LocalGet(typeDataLocal) + fb += Call(genFunctionID.classCastException) + fb += Unreachable // for clarity; technically redundant since the stacks align } + fb += LocalGet(valueParam) + fb.buildAndAddToModule() } - /** `getComponentType: (ref typeData) -> (ref null jlClass)`. + /** `getComponentType: (ref jlClass) -> (ref null jlClass)`. * - * This is the underlying func for the `getComponentType()` closure inside class data objects. + * This is the underlying func for the `Class_componentType` operation. */ private def genGetComponentType()(implicit ctx: WasmContext): Unit = { - val typeDataType = RefType(genTypeID.typeData) - val fb = newFunctionBuilder(genFunctionID.getComponentType) - val typeDataParam = fb.addParam("typeData", typeDataType) + val jlClassParam = fb.addParam("jlClass", RefType(genTypeID.ClassStruct)) fb.setResultType(RefType.nullable(genTypeID.ClassStruct)) - val componentTypeDataLocal = fb.addLocal("componentTypeData", typeDataType) - fb.block() { nullResultLabel => // Try and extract non-null component type data - fb += LocalGet(typeDataParam) + fb += LocalGet(jlClassParam) + fb += StructGet(genTypeID.ClassStruct, genFieldID.classData) fb += StructGet(genTypeID.typeData, genFieldID.typeData.componentType) fb += BrOnNull(nullResultLabel) // Get the corresponding classOf @@ -2404,68 +2370,72 @@ final class CoreWasmLib(coreSpec: CoreSpec) { fb.buildAndAddToModule() } - /** `newArrayOfThisClass: (ref typeData), anyref -> (ref jlObject)`. + /** `newArray: (ref jlClass), i32 -> (ref jlObject)`. * - * This is the underlying func for the `newArrayOfThisClass()` closure inside class data objects. + * This is the underlying func for the `Class_newArray` operation. */ - private def genNewArrayOfThisClass()(implicit ctx: WasmContext): Unit = { + private def genNewArray()(implicit ctx: WasmContext): Unit = { val typeDataType = RefType(genTypeID.typeData) - val i32ArrayType = RefType(genTypeID.i32Array) + val arrayTypeDataType = RefType(genTypeID.ObjectVTable) - val fb = newFunctionBuilder(genFunctionID.newArrayOfThisClass) - val typeDataParam = fb.addParam("typeData", typeDataType) - val lengthsParam = fb.addParam("lengths", RefType.anyref) + val fb = newFunctionBuilder(genFunctionID.newArray) + val jlClassParam = fb.addParam("jlClass", RefType(genTypeID.ClassStruct)) + val lengthParam = fb.addParam("length", Int32) fb.setResultType(RefType(genTypeID.ObjectStruct)) - val lengthsLenLocal = fb.addLocal("lengthsLenLocal", Int32) - val lengthsValuesLocal = fb.addLocal("lengthsValues", i32ArrayType) - val iLocal = fb.addLocal("i", Int32) - - // lengthsLen := lengths.length // as a JS field access, through a helper - fb += LocalGet(lengthsParam) - fb += Call(genFunctionID.jsArrayLength) - fb += LocalTee(lengthsLenLocal) - - // lengthsValues := array.new lengthsLen - fb += ArrayNewDefault(genTypeID.i32Array) - fb += LocalSet(lengthsValuesLocal) - - // i := 0 - fb += I32Const(0) - fb += LocalSet(iLocal) + val componentTypeDataLocal = fb.addLocal("componentTypeData", RefType(genTypeID.typeData)) - // while (i != lengthsLen) - fb.whileLoop() { - fb += LocalGet(iLocal) - fb += LocalGet(lengthsLenLocal) - fb += I32Ne - } { - // lengthsValue[i] := lengths[i] (where the rhs is a JS field access, though a helper) + // Check negative array size + if (semantics.negativeArraySizes != CheckedBehavior.Unchecked) { + fb += LocalGet(lengthParam) + fb += I32Const(0) + fb += I32LtS + fb.ifThen() { + fb += LocalGet(lengthParam) + fb += Call(genFunctionID.throwNegativeArraySizeException) + fb += Unreachable + } + } - fb += LocalGet(lengthsValuesLocal) - fb += LocalGet(iLocal) + // componentTypeData := jlClass.data + fb += LocalGet(jlClassParam) + fb += StructGet(genTypeID.ClassStruct, genFieldID.classData) + fb += LocalTee(componentTypeDataLocal) - fb += LocalGet(lengthsParam) - fb += LocalGet(iLocal) - fb += Call(genFunctionID.jsArrayGetInt) + // Load the vtable and itables of the ArrayClass instance we will create + fb += I32Const(1) + fb += Call(genFunctionID.arrayTypeData) // vtable + fb += GlobalGet(genGlobalID.arrayClassITable) // itables - fb += ArraySet(genTypeID.i32Array) + // Load the length + fb += LocalGet(lengthParam) - // i += 1 - fb += LocalGet(iLocal) - fb += I32Const(1) - fb += I32Add - fb += LocalSet(iLocal) + // switch (componentTypeData.kind) + val switchClauseSig = FunctionType( + List(arrayTypeDataType, RefType(genTypeID.itables), Int32), + List(RefType(genTypeID.ObjectStruct)) + ) + fb.switch(switchClauseSig) { () => + // scrutinee + fb += LocalGet(componentTypeDataLocal) + fb += StructGet(genTypeID.typeData, genFieldID.typeData.kind) + }( + // case KindPrim => array.new_default underlyingPrimArray; struct.new PrimArray + primRefsWithArrayTypes.map { case (primRef, kind) => + List(kind) -> { () => + val arrayTypeRef = ArrayTypeRef(primRef, 1) + fb += ArrayNewDefault(genTypeID.underlyingOf(arrayTypeRef)) + fb += StructNew(genTypeID.forArrayClass(arrayTypeRef)) + () // required for correct type inference + } + }: _* + ) { () => + // case _ => array.new_default anyrefArray; struct.new ObjectArray + val arrayTypeRef = ArrayTypeRef(ClassRef(ObjectClass), 1) + fb += ArrayNewDefault(genTypeID.underlyingOf(arrayTypeRef)) + fb += StructNew(genTypeID.forArrayClass(arrayTypeRef)) } - // return newArrayObject(arrayTypeData(typeData, lengthsLen), lengthsValues, 0) - fb += LocalGet(typeDataParam) - fb += LocalGet(lengthsLenLocal) - fb += Call(genFunctionID.arrayTypeData) - fb += LocalGet(lengthsValuesLocal) - fb += I32Const(0) - fb += Call(genFunctionID.newArrayObject) - fb.buildAndAddToModule() } @@ -2676,187 +2646,6 @@ final class CoreWasmLib(coreSpec: CoreSpec) { fb.buildAndAddToModule() } - /** `newArrayObject`: `(ref typeData), (ref array i32), i32 -> (ref jl.Object)`. - * - * The arguments are `arrayTypeData`, `lengths` and `lengthIndex`. - * - * This recursive function creates a multi-dimensional array. The resulting array has type data - * `arrayTypeData` and length `lengths(lengthIndex)`. If `lengthIndex < `lengths.length - 1`, its - * elements are recursively initialized with `newArrayObject(arrayTypeData.componentType, - * lengths, lengthIndex - 1)`. - */ - private def genNewArrayObject()(implicit ctx: WasmContext): Unit = { - import genFieldID.typeData._ - - val typeDataType = RefType(genTypeID.typeData) - val i32ArrayType = RefType(genTypeID.i32Array) - val objectVTableType = RefType(genTypeID.ObjectVTable) - val arrayTypeDataType = objectVTableType - val itablesType = RefType(genTypeID.itables) - val nonNullObjectType = RefType(genTypeID.ObjectStruct) - val anyArrayType = RefType(genTypeID.anyArray) - - val fb = newFunctionBuilder(genFunctionID.newArrayObject) - val arrayTypeDataParam = fb.addParam("arrayTypeData", arrayTypeDataType) - val lengthsParam = fb.addParam("lengths", i32ArrayType) - val lengthIndexParam = fb.addParam("lengthIndex", Int32) - fb.setResultType(nonNullObjectType) - - val lenLocal = fb.addLocal("len", Int32) - val underlyingLocal = fb.addLocal("underlying", anyArrayType) - val subLengthIndexLocal = fb.addLocal("subLengthIndex", Int32) - val arrayComponentTypeDataLocal = fb.addLocal("arrayComponentTypeData", arrayTypeDataType) - val iLocal = fb.addLocal("i", Int32) - - /* High-level pseudo code of what this function does: - * - * def newArrayObject(arrayTypeData, lengths, lengthIndex) { - * // create an array of the right primitive type - * val len = lengths(lengthIndex) - * // possibly: check negative array size - * switch (arrayTypeData.componentType.kind) { - * // for primitives, return without recursion - * case KindBoolean => new Array[Boolean](len) - * ... - * case KindDouble => new Array[Double](len) - * - * // for reference array types, maybe recursively initialize - * case _ => - * val result = new Array[Object](len) // with arrayTypeData as vtable - * val subLengthIndex = lengthIndex + 1 - * if (subLengthIndex != lengths.length) { - * val arrayComponentTypeData = arrayTypeData.componentType - * for (i <- 0 until len) - * result(i) = newArrayObject(arrayComponentTypeData, lengths, subLengthIndex) - * } - * result - * } - * } - */ - - val primRefsWithArrayTypes = List( - BooleanRef -> KindBoolean, - CharRef -> KindChar, - ByteRef -> KindByte, - ShortRef -> KindShort, - IntRef -> KindInt, - LongRef -> KindLong, - FloatRef -> KindFloat, - DoubleRef -> KindDouble - ) - - // Load the vtable and itable of the resulting array on the stack - fb += LocalGet(arrayTypeDataParam) // vtable - fb += GlobalGet(genGlobalID.arrayClassITable) // itable - - // Load the first length - fb += LocalGet(lengthsParam) - fb += LocalGet(lengthIndexParam) - fb += ArrayGet(genTypeID.i32Array) - - // Check negative array size - if (semantics.negativeArraySizes != CheckedBehavior.Unchecked) { - fb += LocalTee(lenLocal) - fb += I32Const(0) - fb += I32LtS - fb.ifThen() { - fb += LocalGet(lenLocal) - fb += Call(genFunctionID.throwNegativeArraySizeException) - fb += Unreachable - } - fb += LocalGet(lenLocal) - } - - // componentTypeData := ref_as_non_null(arrayTypeData.componentType) - // switch (componentTypeData.kind) - val switchClauseSig = FunctionType( - List(arrayTypeDataType, itablesType, Int32), - List(nonNullObjectType) - ) - fb.switch(switchClauseSig) { () => - // scrutinee - fb += LocalGet(arrayTypeDataParam) - fb += StructGet(genTypeID.typeData, genFieldID.typeData.componentType) - fb += StructGet(genTypeID.typeData, genFieldID.typeData.kind) - }( - // For all the primitive types, by construction, this is the bottom dimension - // case KindPrim => array.new_default underlyingPrimArray; struct.new PrimArray - primRefsWithArrayTypes.map { case (primRef, kind) => - List(kind) -> { () => - val arrayTypeRef = ArrayTypeRef(primRef, 1) - fb += ArrayNewDefault(genTypeID.underlyingOf(arrayTypeRef)) - fb += StructNew(genTypeID.forArrayClass(arrayTypeRef)) - () // required for correct type inference - } - }: _* - ) { () => - // default -- all non-primitive array types - - // len := (which is the first length) - fb += LocalTee(lenLocal) - - // underlying := array.new_default anyArray - val arrayTypeRef = ArrayTypeRef(ClassRef(ObjectClass), 1) - fb += ArrayNewDefault(genTypeID.underlyingOf(arrayTypeRef)) - fb += LocalSet(underlyingLocal) - - // subLengthIndex := lengthIndex + 1 - fb += LocalGet(lengthIndexParam) - fb += I32Const(1) - fb += I32Add - fb += LocalTee(subLengthIndexLocal) - - // if subLengthIndex != lengths.length - fb += LocalGet(lengthsParam) - fb += ArrayLen - fb += I32Ne - fb.ifThen() { - // then, recursively initialize all the elements - - // arrayComponentTypeData := ref_cast arrayTypeData.componentTypeData - fb += LocalGet(arrayTypeDataParam) - fb += StructGet(genTypeID.typeData, genFieldID.typeData.componentType) - fb += RefCast(RefType(arrayTypeDataType.heapType)) - fb += LocalSet(arrayComponentTypeDataLocal) - - // i := 0 - fb += I32Const(0) - fb += LocalSet(iLocal) - - // while (i != len) - fb.whileLoop() { - fb += LocalGet(iLocal) - fb += LocalGet(lenLocal) - fb += I32Ne - } { - // underlying[i] := newArrayObject(arrayComponentType, lengths, subLengthIndex) - - fb += LocalGet(underlyingLocal) - fb += LocalGet(iLocal) - - fb += LocalGet(arrayComponentTypeDataLocal) - fb += LocalGet(lengthsParam) - fb += LocalGet(subLengthIndexLocal) - fb += Call(genFunctionID.newArrayObject) - - fb += ArraySet(genTypeID.anyArray) - - // i += 1 - fb += LocalGet(iLocal) - fb += I32Const(1) - fb += I32Add - fb += LocalSet(iLocal) - } - } - - // load underlying; struct.new ObjectArray - fb += LocalGet(underlyingLocal) - fb += StructNew(genTypeID.forArrayClass(arrayTypeRef)) - } - - fb.buildAndAddToModule() - } - /** `identityHashCode`: `anyref -> i32`. * * This is the implementation of `IdentityHashCode`. It is also used to compute the `hashCode()` diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala index 93b00423de..dca083127a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala @@ -431,7 +431,7 @@ object Emitter { }, // TODO Ideally we should not require these, but rather adapt to their absence - instantiateClass(ClassClass, AnyArgConstructorName), + instantiateClass(ClassClass, NoArgConstructorName), instantiateClass(JSExceptionClass, AnyArgConstructorName), // See genIdentityHashCode in HelperFunctions diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index 9626403260..4be4ac40ac 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -1438,6 +1438,32 @@ private class FunctionEmitter private ( // Null check case CheckNotNull => genAsNonNullOrNPEFor(lhs) + + // Class operations + case Class_name => + fb += wa.StructGet(genTypeID.ClassStruct, genFieldID.classData) + fb += wa.Call(genFunctionID.typeDataName) + case Class_isPrimitive => + fb += wa.StructGet(genTypeID.ClassStruct, genFieldID.classData) + fb += wa.StructGet(genTypeID.typeData, genFieldID.typeData.kind) + fb += wa.I32Const(KindLastPrimitive) + fb += wa.I32LeU + case Class_isInterface => + fb += wa.StructGet(genTypeID.ClassStruct, genFieldID.classData) + fb += wa.StructGet(genTypeID.typeData, genFieldID.typeData.kind) + fb += wa.I32Const(KindInterface) + fb += wa.I32Eq + case Class_isArray => + fb += wa.StructGet(genTypeID.ClassStruct, genFieldID.classData) + fb += wa.StructGet(genTypeID.typeData, genFieldID.typeData.kind) + fb += wa.I32Const(KindArray) + fb += wa.I32Eq + case Class_componentType => + fb += wa.Call(genFunctionID.getComponentType) + case Class_superClass => + // FIXME Implement this + fb += wa.Drop + fb += wa.RefNull(watpe.HeapType.None) } tree.tpe @@ -1457,6 +1483,20 @@ private class FunctionEmitter private ( LongType } + /* Gen the given tree of type `jl.Class!` then extract its `classData`. + * If the arg is a literal `ClassOf`, we directly generate its type data. + */ + def genTreeClassData(arg: Tree): Unit = { + arg match { + case ClassOf(typeRef) => + markPosition(arg) + genLoadTypeData(fb, typeRef) + case _ => + genTreeAuto(arg) + fb += wa.StructGet(genTypeID.ClassStruct, genFieldID.classData) + } + } + (op: @switch) match { case === | !== => genEq(tree) @@ -1535,6 +1575,32 @@ private class FunctionEmitter private ( fb += wa.Call(genFunctionID.checkedStringCharAt) CharType + // Class operations for which genTreeAuto would not do the right thing + case Class_isInstance => + genTreeClassData(lhs) + genTree(rhs, AnyType) + markPosition(tree) + fb += wa.Call(genFunctionID.isInstance) + BooleanType + case Class_isAssignableFrom => + genTreeClassData(lhs) + genTreeClassData(rhs) + markPosition(tree) + fb += wa.Call(genFunctionID.isAssignableFrom) + BooleanType + case Class_cast => + if (semantics.asInstanceOfs != CheckedBehavior.Unchecked) { + genTreeAuto(lhs) + genTree(rhs, AnyType) + markPosition(tree) + fb += wa.Call(genFunctionID.cast) + AnyType + } else { + genTree(lhs, NoType) + genTreeAuto(rhs) + rhs.tpe + } + case _ => genTreeAuto(lhs) genTreeAuto(rhs) @@ -1689,6 +1755,8 @@ private class FunctionEmitter private ( case Double_<= => wa.F64Le case Double_> => wa.F64Gt case Double_>= => wa.F64Ge + + case Class_newArray => wa.Call(genFunctionID.newArray) } } @@ -2873,68 +2941,41 @@ private class FunctionEmitter private ( } private def genNewArray(tree: NewArray): Type = { - val NewArray(arrayTypeRef, lengths) = tree - - if (lengths.isEmpty || lengths.size > arrayTypeRef.dimensions) { - throw new AssertionError( - s"invalid lengths ${tree.lengths} for array type ${arrayTypeRef.displayName}") - } + val NewArray(arrayTypeRef, length) = tree markPosition(tree) - if (lengths.size == 1) { - genLoadVTableAndITableForArray(fb, arrayTypeRef) + genLoadVTableAndITableForArray(fb, arrayTypeRef) - // Create the underlying array - genTree(lengths.head, IntType) - markPosition(tree) + // Create the underlying array + genTree(length, IntType) + markPosition(tree) - if (semantics.negativeArraySizes != CheckedBehavior.Unchecked) { - lengths.head match { - case IntLiteral(lengthValue) if lengthValue >= 0 => - () // always good - case _ => - // if length < 0 - val lengthLocal = addSyntheticLocal(watpe.Int32) - fb += wa.LocalTee(lengthLocal) - fb += wa.I32Const(0) - fb += wa.I32LtS - fb.ifThen() { - // then throw NegativeArraySizeException - fb += wa.LocalGet(lengthLocal) - fb += wa.Call(genFunctionID.throwNegativeArraySizeException) - fb += wa.Unreachable - } + if (semantics.negativeArraySizes != CheckedBehavior.Unchecked) { + length match { + case IntLiteral(lengthValue) if lengthValue >= 0 => + () // always good + case _ => + // if length < 0 + val lengthLocal = addSyntheticLocal(watpe.Int32) + fb += wa.LocalTee(lengthLocal) + fb += wa.I32Const(0) + fb += wa.I32LtS + fb.ifThen() { + // then throw NegativeArraySizeException fb += wa.LocalGet(lengthLocal) - } + fb += wa.Call(genFunctionID.throwNegativeArraySizeException) + fb += wa.Unreachable + } + fb += wa.LocalGet(lengthLocal) } + } - val underlyingArrayType = genTypeID.underlyingOf(arrayTypeRef) - fb += wa.ArrayNewDefault(underlyingArrayType) - - // Create the array object - fb += wa.StructNew(genTypeID.forArrayClass(arrayTypeRef)) - } else { - /* There is no Scala source code that produces `NewArray` with more than - * one specified dimension, so this branch is not tested. - * (The underlying function `newArrayObject` is tested as part of - * reflective array instantiations, though.) - */ - - // First arg to `newArrayObject`: the typeData of the array to create - genLoadArrayTypeData(fb, arrayTypeRef) - - // Second arg: an array of the lengths - for (length <- lengths) - genTree(length, IntType) - markPosition(tree) - fb += wa.ArrayNewFixed(genTypeID.i32Array, lengths.size) - - // Third arg: constant 0 (start index inside the array of lengths) - fb += wa.I32Const(0) + val underlyingArrayType = genTypeID.underlyingOf(arrayTypeRef) + fb += wa.ArrayNewDefault(underlyingArrayType) - fb += wa.Call(genFunctionID.newArrayObject) - } + // Create the array object + fb += wa.StructNew(genTypeID.forArrayClass(arrayTypeRef)) tree.tpe } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala index 90ce8400bf..af81fcd54e 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala @@ -167,22 +167,7 @@ const scalaJSHelpers = { idHashCodeSet: (map, obj, value) => map.set(obj, value), // Some support functions for CoreWasmLib - genJSTypeMetaData: (typeData, name, isPrimitive, isArrayClass, isInterface, isInstanceFun, - isAssignableFromFun, checkCastFun, getComponentTypeFun, newArrayOfThisClassFun) => ({ - __typeData: typeData, // (TODO hide this better? although nobody will notice anyway) - "name": name, - "isPrimitive": isPrimitive !== 0, - "isArrayClass": isArrayClass !== 0, - "isInterface": isInterface !== 0, - "isInstance": (value) => isInstanceFun(typeData, value) !== 0, - "isAssignableFrom": (that) => isAssignableFromFun(typeData, that.__typeData) !== 0, - "checkCast": (value) => checkCastFun(typeData, value), - "getComponentType": () => getComponentTypeFun(typeData), - "newArrayOfThisClass": (lengths) => newArrayOfThisClassFun(typeData, lengths), - }), makeTypeError: (msg) => new TypeError(msg), - jsArrayLength: (array) => array.length, - jsArrayGetInt: (array, index) => array[index], // JS interop jsGlobalRefGet: (globalRefName) => (new Function("return " + globalRefName))(), diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala index 893438b561..18659da4a7 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala @@ -156,10 +156,7 @@ object VarGen { case object idHashCodeGet extends JSHelperFunctionID case object idHashCodeSet extends JSHelperFunctionID - case object genJSTypeMetaData extends JSHelperFunctionID case object makeTypeError extends JSHelperFunctionID - case object jsArrayLength extends JSHelperFunctionID - case object jsArrayGetInt extends JSHelperFunctionID case object jsGlobalRefGet extends JSHelperFunctionID case object jsGlobalRefSet extends JSHelperFunctionID @@ -257,13 +254,12 @@ object VarGen { case object throwModuleInitError extends FunctionID case object isInstance extends FunctionID case object isAssignableFrom extends FunctionID - case object checkCast extends FunctionID + case object cast extends FunctionID case object getComponentType extends FunctionID - case object newArrayOfThisClass extends FunctionID + case object newArray extends FunctionID case object anyGetClass extends FunctionID case object anyGetClassName extends FunctionID case object anyGetTypeData extends FunctionID - case object newArrayObject extends FunctionID case object identityHashCode extends FunctionID case object searchReflectiveProxy extends FunctionID @@ -419,6 +415,9 @@ object VarGen { */ case object reflectiveProxies extends FieldID } + + /** The magic `data` field of type `(ref typeData)`, injected into `jl.Class`. */ + case object classData extends FieldID } object genTypeID { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index 5da8ba0a6f..b569825470 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -663,14 +663,9 @@ private final class ClassDefChecker(classDef: ClassDef, checkTree(lhs, env) checkTree(rhs, env) - case NewArray(typeRef, lengths) => - if (lengths.isEmpty) - reportError("NewArray must have non-0 dimensions") - if (lengths.size > typeRef.dimensions) - reportError("NewArray dimensions may not exceed its type") - + case NewArray(typeRef, length) => checkArrayTypeRef(typeRef) - checkTrees(lengths, env) + checkTree(length, env) case ArrayValue(typeRef, elems) => checkArrayTypeRef(typeRef) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index 7efab4d3c4..5bf5e259c4 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -445,6 +445,9 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { StringType case CheckNotNull => AnyType + case Class_name | Class_isPrimitive | Class_isInterface | + Class_isArray | Class_componentType | Class_superClass => + ClassType(ClassClass, nullable = false) } typecheckExpect(lhs, env, expectedArgType) @@ -471,17 +474,23 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter) { DoubleType case String_charAt => StringType + case Class_isInstance | Class_isAssignableFrom | Class_cast | + Class_newArray => + ClassType(ClassClass, nullable = false) } val expectedRhsType = (op: @switch) match { - case Long_<< | Long_>>> | Long_>> | String_charAt => IntType - case _ => expectedLhsType + case Long_<< | Long_>>> | Long_>> | String_charAt | Class_newArray => + IntType + case Class_isInstance | Class_cast => + AnyType + case _ => + expectedLhsType } typecheckExpect(lhs, env, expectedLhsType) typecheckExpect(rhs, env, expectedRhsType) - case NewArray(typeRef, lengths) => - for (length <- lengths) - typecheckExpect(length, env, IntType) + case NewArray(typeRef, length) => + typecheckExpect(length, env, IntType) case ArrayValue(typeRef, elems) => val elemType = arrayElemType(typeRef) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala index f120dff28a..e9756e38e1 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/BaseLinker.scala @@ -91,9 +91,14 @@ final class BaseLinker(config: CommonPhaseConfig, checkIR: Boolean) { } yield { val (linkedClassDefs, linkedTopLevelExports) = assembled.unzip + val globalInfo = new LinkedGlobalInfo( + analysis.isClassSuperClassUsed + ) + new LinkingUnit(config.coreSpec, linkedClassDefs.toList, linkedTopLevelExports.flatten.toList, - moduleInitializers.toList) + moduleInitializers.toList, + globalInfo) } } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkingUnit.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkingUnit.scala index dd6f61e0c7..836147cba5 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkingUnit.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkingUnit.scala @@ -20,5 +20,6 @@ final class LinkingUnit private[frontend] ( val coreSpec: CoreSpec, val classDefs: List[LinkedClass], val topLevelExports: List[LinkedTopLevelExport], - val moduleInitializers: List[ModuleInitializer] + val moduleInitializers: List[ModuleInitializer], + val globalInfo: LinkedGlobalInfo ) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala index 985023e7ae..df7b262a00 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala @@ -58,8 +58,12 @@ final class Refiner(config: CommonPhaseConfig, checkIR: Boolean) { val (linkedClassDefs, linkedTopLevelExports) = assembled.unzip + val globalInfo = new LinkedGlobalInfo( + analysis.isClassSuperClassUsed + ) + new LinkingUnit(config.coreSpec, linkedClassDefs.toList, - linkedTopLevelExports.flatten.toList, moduleInitializers) + linkedTopLevelExports.flatten.toList, moduleInitializers, globalInfo) } irLoader.cleanAfterRun() diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/modulesplitter/ModuleSplitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/modulesplitter/ModuleSplitter.scala index 579d440265..2518679ca1 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/modulesplitter/ModuleSplitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/modulesplitter/ModuleSplitter.scala @@ -45,7 +45,7 @@ final class ModuleSplitter private (analyzer: ModuleAnalyzer) { * We have to do it here, otherwise we break the assumption, that all * non-abstract classes are reached through a static path. */ - new ModuleSet(unit.coreSpec, Nil, Nil) + new ModuleSet(unit.coreSpec, Nil, Nil, unit.globalInfo) } else { val analysis = logger.time("Module Splitter: Analyze Modules") { analyzer.analyze(dependencyInfo) @@ -124,7 +124,7 @@ final class ModuleSplitter private (analyzer: ModuleAnalyzer) { val modules = builders.values.map(_.result()).toList - new ModuleSet(unit.coreSpec, modules, abstractClasses.result()) + new ModuleSet(unit.coreSpec, modules, abstractClasses.result(), unit.globalInfo) } private def publicModuleDependencies( diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 82be00b249..264d1d8a30 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -530,8 +530,8 @@ private[optimizer] abstract class OptimizerCore( pretransformBinaryOp(tree)(finishTransform(isStat)) } - case NewArray(tpe, lengths) => - NewArray(tpe, lengths map transformExpr) + case NewArray(tpe, length) => + NewArray(tpe, transformExpr(length)) case ArrayValue(tpe, elems) => ArrayValue(tpe, elems map transformExpr) @@ -1553,13 +1553,17 @@ private[optimizer] abstract class OptimizerCore( finishTransformStat(lhs) case PreTransBinaryOp(op, lhs, rhs) => - // Here we need to preserve the side-effects of integer division/modulo and String_charAt import BinaryOp._ + implicit val pos = stat.pos + def newLhs = finishTransformStat(lhs) def finishNoSideEffects: Tree = - Block(newLhs, finishTransformStat(rhs))(stat.pos) + Block(newLhs, finishTransformStat(rhs)) + + def finishWithSideEffects: Tree = + BinaryOp(op, finishTransformExpr(lhs), finishTransformExpr(rhs)) (op: @switch) match { case Int_/ | Int_% => @@ -1567,19 +1571,21 @@ private[optimizer] abstract class OptimizerCore( case PreTransLit(IntLiteral(r)) if r != 0 => finishNoSideEffects case _ => - Block(newLhs, BinaryOp(op, IntLiteral(0)(stat.pos), - finishTransformExpr(rhs))(stat.pos))(stat.pos) + Block(newLhs, BinaryOp(op, IntLiteral(0), finishTransformExpr(rhs))) } case Long_/ | Long_% => rhs match { case PreTransLit(LongLiteral(r)) if r != 0L => finishNoSideEffects case _ => - Block(newLhs, BinaryOp(op, LongLiteral(0L)(stat.pos), - finishTransformExpr(rhs))(stat.pos))(stat.pos) + Block(newLhs, BinaryOp(op, LongLiteral(0L), finishTransformExpr(rhs))) } case String_charAt if semantics.stringIndexOutOfBounds != CheckedBehavior.Unchecked => - finishTransformExpr(stat) + finishWithSideEffects + case Class_cast if semantics.asInstanceOfs != CheckedBehavior.Unchecked => + finishWithSideEffects + case Class_newArray if semantics.negativeArraySizes != CheckedBehavior.Unchecked => + finishWithSideEffects case _ => finishNoSideEffects } @@ -1659,10 +1665,10 @@ private[optimizer] abstract class OptimizerCore( case LoadModule(moduleClassName) => if (hasElidableConstructors(moduleClassName)) Skip()(stat.pos) else stat - case NewArray(_, lengths) if lengths.forall(isNonNegativeIntLiteral(_)) => + case NewArray(_, length) if isNonNegativeIntLiteral(length) => Skip()(stat.pos) - case NewArray(_, lengths) if semantics.negativeArraySizes == CheckedBehavior.Unchecked => - Block(lengths.map(keepOnlySideEffects))(stat.pos) + case NewArray(_, length) if semantics.negativeArraySizes == CheckedBehavior.Unchecked => + keepOnlySideEffects(length) case ArrayValue(_, elems) => Block(elems.map(keepOnlySideEffects(_)))(stat.pos) case ArrayLength(array) => @@ -1682,36 +1688,11 @@ private[optimizer] abstract class OptimizerCore( } case BinaryOp(op, lhs, rhs) => - // Here we need to preserve the side-effects of integer division/modulo and String_charAt - import BinaryOp._ - - implicit val pos = stat.pos - - def newLhs = keepOnlySideEffects(lhs) - - def finishNoSideEffects: Tree = - Block(newLhs, keepOnlySideEffects(rhs)) - - op match { - case Int_/ | Int_% => - rhs match { - case IntLiteral(r) if r != 0 => - finishNoSideEffects - case _ => - Block(newLhs, BinaryOp(op, IntLiteral(0), rhs)) - } - case Long_/ | Long_% => - rhs match { - case LongLiteral(r) if r != 0L => - finishNoSideEffects - case _ => - Block(newLhs, BinaryOp(op, LongLiteral(0L), rhs)) - } - case String_charAt if semantics.stringIndexOutOfBounds != CheckedBehavior.Unchecked => - stat - case _ => - finishNoSideEffects - } + /* The logic here exceeds the complexity threshold for keeping a copy + * compared to `finishTransformStat` at the Tree level. Instead, we + * convert to a PreTransBinaryOp and delegate to the other function. + */ + finishTransformStat(PreTransBinaryOp(op, lhs.toPreTransform, rhs.toPreTransform)(stat.pos)) case RecordValue(_, elems) => Block(elems.map(keepOnlySideEffects))(stat.pos) @@ -1927,7 +1908,8 @@ private[optimizer] abstract class OptimizerCore( case NotFoundPureSoFar => rec(rhs).mapOrKeepGoingIf(BinaryOp(op, lhs, _)) { (op: @switch) match { - case Int_/ | Int_% | Long_/ | Long_% | String_+ | String_charAt => + case Int_/ | Int_% | Long_/ | Long_% | String_+ | String_charAt | + Class_cast | Class_newArray => false case _ => true @@ -1935,8 +1917,8 @@ private[optimizer] abstract class OptimizerCore( } } - case NewArray(typeRef, lengths) => - recs(lengths).mapOrKeepGoing(NewArray(typeRef, _)) + case NewArray(typeRef, length) => + rec(length).mapOrKeepGoing(NewArray(typeRef, _)) case ArrayValue(typeRef, elems) => recs(elems).mapOrKeepGoing(ArrayValue(typeRef, _)) @@ -3192,41 +3174,8 @@ private[optimizer] abstract class OptimizerCore( // java.lang.Class - case ClassGetComponentType => - optTReceiver.get match { - case PreTransLit(ClassOf(ArrayTypeRef(base, depth))) => - contTree(ClassOf( - if (depth == 1) base - else ArrayTypeRef(base, depth - 1))) - case PreTransLit(ClassOf(ClassRef(_))) => - contTree(Null()) - case receiver => - default - } - case ClassGetName => optTReceiver.get match { - case PreTransMaybeBlock(bindingsAndStats, PreTransLit(ClassOf(typeRef))) => - def mappedClassName(className: ClassName): String = { - RuntimeClassNameMapperImpl.map( - config.coreSpec.semantics.runtimeClassNameMapper, - className.nameString) - } - - val nameString = typeRef match { - case primRef: PrimRef => - primRef.displayName - case ClassRef(className) => - mappedClassName(className) - case ArrayTypeRef(primRef: PrimRef, dimensions) => - "[" * dimensions + primRef.charCode - case ArrayTypeRef(ClassRef(className), dimensions) => - "[" * dimensions + "L" + mappedClassName(className) + ";" - } - - contTree(finishTransformBindings( - bindingsAndStats, StringLiteral(nameString))) - case PreTransMaybeBlock(bindingsAndStats, PreTransTree(MaybeCast(GetClass(expr)), _)) => contTree(finishTransformBindings( @@ -3236,18 +3185,6 @@ private[optimizer] abstract class OptimizerCore( default } - // java.lang.reflect.Array - - case ArrayNewInstance => - val List(tcomponentType, tlength) = targs - tcomponentType match { - case PreTransTree(ClassOf(elementTypeRef), _) if elementTypeRef != VoidRef => - val arrayTypeRef = ArrayTypeRef.of(elementTypeRef) - contTree(NewArray(arrayTypeRef, List(finishTransformExpr(tlength)))) - case _ => - default - } - // js.special case ObjectLiteral => @@ -3967,11 +3904,65 @@ private[optimizer] abstract class OptimizerCore( case CheckNotNull => checkNotNull(arg) + // Class operations + + case Class_name => + arg match { + case PreTransLit(ClassOf(typeRef)) => + PreTransLit(StringLiteral(constantClassNameOf(typeRef))) + case _ => + default + } + case Class_isPrimitive => + arg match { + case PreTransLit(ClassOf(typeRef)) => + PreTransLit(BooleanLiteral(typeRef.isInstanceOf[PrimRef])) + case _ => + default + } + case Class_isArray => + arg match { + case PreTransLit(ClassOf(typeRef)) => + PreTransLit(BooleanLiteral(typeRef.isInstanceOf[ArrayTypeRef])) + case _ => + default + } + case Class_componentType => + arg match { + case PreTransLit(ClassOf(ArrayTypeRef(base, depth))) => + PreTransLit(ClassOf( + if (depth == 1) base + else ArrayTypeRef(base, depth - 1))) + case PreTransLit(ClassOf(_)) => + PreTransLit(Null()) + case _ => + default + } + case _ => default } } + private def constantClassNameOf(typeRef: TypeRef): String = { + def mappedClassName(className: ClassName): String = { + RuntimeClassNameMapperImpl.map( + config.coreSpec.semantics.runtimeClassNameMapper, + className.nameString) + } + + typeRef match { + case primRef: PrimRef => + primRef.displayName + case ClassRef(className) => + mappedClassName(className) + case ArrayTypeRef(primRef: PrimRef, dimensions) => + "[" * dimensions + primRef.charCode + case ArrayTypeRef(ClassRef(className), dimensions) => + "[" * dimensions + "L" + mappedClassName(className) + ";" + } + } + /** Performs === for two literals. * The result is always known statically. * @@ -4988,6 +4979,30 @@ private[optimizer] abstract class OptimizerCore( case _ => default } + + case Class_isInstance | Class_isAssignableFrom => + /* We could do something clever here if we added a knowledge query to + * turn a TypeRef into a Type. Barring that, we cannot tell whether a + * ClassRef must become an `AnyType` or a `ClassType`. + */ + default + + case Class_cast => + if (semantics.asInstanceOfs == CheckedBehavior.Unchecked) { + PreTransBlock(finishTransformStat(lhs), rhs) + } else { + // Same comment as in isInstance and isAssignableFrom + default + } + + case Class_newArray => + lhs match { + case PreTransLit(ClassOf(elementTypeRef)) if elementTypeRef != VoidRef => + val arrayTypeRef = ArrayTypeRef.of(elementTypeRef) + NewArray(arrayTypeRef, finishTransformExpr(rhs)).toPreTransform + case _ => + default + } } } @@ -6519,12 +6534,9 @@ private[optimizer] object OptimizerCore { final val ArrayBuilderZeroOf = MathMaxDouble + 1 final val GenericArrayBuilderResult = ArrayBuilderZeroOf + 1 - final val ClassGetComponentType = GenericArrayBuilderResult + 1 - final val ClassGetName = ClassGetComponentType + 1 + final val ClassGetName = GenericArrayBuilderResult + 1 - final val ArrayNewInstance = ClassGetName + 1 - - final val ObjectLiteral = ArrayNewInstance + 1 + final val ObjectLiteral = ClassGetName + 1 final val ByteArrayToInt8Array = ObjectLiteral + 1 final val ShortArrayToInt16Array = ByteArrayToInt8Array + 1 @@ -6576,12 +6588,8 @@ private[optimizer] object OptimizerCore { m("numberOfLeadingZeros", List(I), I) -> IntegerNLZ ), ClassName("java.lang.Class") -> List( - m("getComponentType", Nil, ClassClassRef) -> ClassGetComponentType, m("getName", Nil, StringClassRef) -> ClassGetName ), - ClassName("java.lang.reflect.Array$") -> List( - m("newInstance", List(ClassClassRef, I), O) -> ArrayNewInstance - ), ClassName("scala.scalajs.js.special.package$") -> List( m("objectLiteral", List(SeqClassRef), JSObjectClassRef) -> ObjectLiteral ) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedGlobalInfo.scala b/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedGlobalInfo.scala new file mode 100644 index 0000000000..86253041af --- /dev/null +++ b/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkedGlobalInfo.scala @@ -0,0 +1,18 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.linker.standard + +/** Global information about a linked program. */ +final class LinkedGlobalInfo private[linker] ( + val isClassSuperClassUsed: Boolean +) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/ModuleSet.scala b/linker/shared/src/main/scala/org/scalajs/linker/standard/ModuleSet.scala index 849ed527ed..c03557baa9 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/standard/ModuleSet.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/standard/ModuleSet.scala @@ -42,7 +42,9 @@ final class ModuleSet private[linker] ( * * For example, a native JS class that is needed for its load spec. */ - val abstractClasses: List[LinkedClass] + val abstractClasses: List[LinkedClass], + + val globalInfo: LinkedGlobalInfo ) { require(modules.isEmpty || modules.count(_.isRoot) == 1, "Must have exactly one root module") diff --git a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala index 6dd8041406..9ab95541de 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -70,9 +70,9 @@ class LibrarySizeTest { ) testLinkedSizes( - expectedFastLinkSize = 147707, - expectedFullLinkSizeWithoutClosure = 86729, - expectedFullLinkSizeWithClosure = 21768, + expectedFastLinkSize = 147629, + expectedFullLinkSizeWithoutClosure = 85718, + expectedFullLinkSizeWithClosure = 21625, classDefs, moduleInitializers = MainTestModuleInitializers ) diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 3606a501a3..8c27dca150 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -9,6 +9,12 @@ object BinaryIncompatibilities { ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.ir.Trees#*.tpe"), + ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.scalajs.ir.Trees#NewArray.this"), + ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.scalajs.ir.Trees#NewArray.apply"), + ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.scalajs.ir.Trees#NewArray.copy"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.ir.Trees#NewArray.copy$default$2"), + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Trees#NewArray.lengths"), + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Trees#UnaryOp.resultTypeOf"), ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#ClassType.this"), @@ -31,6 +37,7 @@ object BinaryIncompatibilities { // private[linker], not an issue ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.CoreSpec.apply"), + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.ModuleSet.this"), ) val LinkerInterface = Seq( diff --git a/project/Build.scala b/project/Build.scala index 9cda252d2e..745900effe 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2048,15 +2048,15 @@ object Build { case `default212Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 626000 to 627000, - fullLink = 97000 to 98000, + fastLink = 623000 to 624000, + fullLink = 96000 to 97000, fastLinkGz = 75000 to 79000, fullLinkGz = 25000 to 26000, )) } else { Some(ExpectedSizes( - fastLink = 425000 to 426000, - fullLink = 282000 to 283000, + fastLink = 423000 to 424000, + fullLink = 280000 to 281000, fastLinkGz = 60000 to 61000, fullLinkGz = 43000 to 44000, )) @@ -2065,15 +2065,15 @@ object Build { case `default213Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 451000 to 452000, + fastLink = 449000 to 450000, fullLink = 95000 to 96000, fastLinkGz = 58000 to 59000, - fullLinkGz = 26000 to 27000, + fullLinkGz = 25000 to 26000, )) } else { Some(ExpectedSizes( - fastLink = 306000 to 307000, - fullLink = 262000 to 263000, + fastLink = 304000 to 305000, + fullLink = 261000 to 262000, fastLinkGz = 48000 to 49000, fullLinkGz = 43000 to 44000, )) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ClassTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ClassTest.scala index 5ec3a41777..a447e5c48b 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ClassTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ClassTest.scala @@ -18,7 +18,7 @@ import org.junit.Assume._ import scala.runtime.BoxedUnit -import org.scalajs.testsuite.utils.AssertThrows.assertThrows +import org.scalajs.testsuite.utils.AssertThrows.{assertThrows, _} import org.scalajs.testsuite.utils.Platform._ class ClassTest { @@ -267,6 +267,10 @@ class ClassTest { s"classOf[Comparable[_]].isAssignableFrom($cls) should be true", classOf[Comparable[_]].isAssignableFrom(cls)) } + + // NPE if the rhs is null + + assertThrowsNPEIfCompliant(classOf[Object].isAssignableFrom(null)) } @Test def getComponentType(): Unit = {