Skip to content

Commit fed13a7

Browse files
committed
Introduce non-nullable reference types in the IR.
`ClassType`s and `ArrayType`s now carry a `nullable: Boolean` flag. When `nullable` is false, the type does not admit `null` values. We also introduce `AnyNotNullType` to be the non-nullable variant of `AnyType`. This way, every non-void type `tpe` has a non-nullable variant, which we can get with `tpe.toNonNullable`. There are a few sources of non-nullable values, such as `New` nodes, and, most importantly, `This` nodes. Since non-nullable reference types have no corresponding `TypeRef`, they cannot appear in the signature of methods. They only live locally within method bodies. --- The optimizer already had dedicated tracking of non-nullable `PreTransform`s. We now replace that tracking by the actual `tpe` of the trees. In order to be type-preserving, we must now insert most `Cast`s, to non-nullable types. Before, at the IR level everything became nullable again, so even if the optimizer had determined that a tree could not be null, that did not influence the produced IR. On the plus side, since IR nodes track their nullability, we do not need `AssumeNotNull` anymore. The `Cast`s to non-nullable types achieve the same result in a more consistent way. In fact, the new non-nullable types allow the optimizer to better keep track of nullability, resulting in fewer `$n` calls to check for nulls. This is the main source of code side reduction. --- Since we now have non-nullable reference types, we change `IsInstanceOf` to require non-nullable test types. This makes more sense, since `IsInstanceOf` always answers `false` when the value is `null`. --- We introduce deserialization hacks to: * adapt the type of `This` nodes, and * adapt the test type of `IsInstanceOf` nodes. In this commit, we do not change the compiler nor the hard-coded IR of `jl.Object` yet, in order to test the deserialization hacks.
1 parent 783f520 commit fed13a7

File tree

33 files changed

+763
-642
lines changed

33 files changed

+763
-642
lines changed

compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
154154

155155
def currentThisType: jstpe.Type = {
156156
encodeClassType(currentClassSym) match {
157-
case tpe @ jstpe.ClassType(cls) =>
157+
case tpe @ jstpe.ClassType(cls, _) =>
158158
jstpe.BoxedClassToPrimType.getOrElse(cls, tpe)
159159
case tpe =>
160160
tpe
@@ -4562,8 +4562,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
45624562
def genAnyEquality(eqeq: Boolean, not: Boolean): js.Tree = {
45634563
// Arrays, Null, Nothing never have a custom equals() method
45644564
def canHaveCustomEquals(tpe: jstpe.Type): Boolean = tpe match {
4565-
case jstpe.AnyType | jstpe.ClassType(_) => true
4566-
case _ => false
4565+
case jstpe.AnyType | _:jstpe.ClassType => true
4566+
case _ => false
45674567
}
45684568
if (eqeq &&
45694569
// don't call equals if we have a literal null at either side

compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -973,7 +973,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
973973
import org.scalajs.ir.Names
974974

975975
(toIRType(tpe): @unchecked) match {
976-
case jstpe.AnyType => NoTypeTest
976+
case jstpe.AnyType | jstpe.AnyNotNullType => NoTypeTest
977977

978978
case jstpe.NoType => PrimitiveTypeTest(jstpe.UndefType, 0)
979979
case jstpe.BooleanType => PrimitiveTypeTest(jstpe.BooleanType, 1)
@@ -985,11 +985,11 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
985985
case jstpe.FloatType => PrimitiveTypeTest(jstpe.FloatType, 7)
986986
case jstpe.DoubleType => PrimitiveTypeTest(jstpe.DoubleType, 8)
987987

988-
case jstpe.ClassType(Names.BoxedUnitClass) => PrimitiveTypeTest(jstpe.UndefType, 0)
989-
case jstpe.ClassType(Names.BoxedStringClass) => PrimitiveTypeTest(jstpe.StringType, 9)
990-
case jstpe.ClassType(_) => InstanceOfTypeTest(tpe)
988+
case jstpe.ClassType(Names.BoxedUnitClass, _) => PrimitiveTypeTest(jstpe.UndefType, 0)
989+
case jstpe.ClassType(Names.BoxedStringClass, _) => PrimitiveTypeTest(jstpe.StringType, 9)
990+
case jstpe.ClassType(_, _) => InstanceOfTypeTest(tpe)
991991

992-
case jstpe.ArrayType(_) => InstanceOfTypeTest(tpe)
992+
case jstpe.ArrayType(_, _) => InstanceOfTypeTest(tpe)
993993
}
994994
}
995995
}

ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -605,27 +605,28 @@ object Hashers {
605605
}
606606

607607
def mixType(tpe: Type): Unit = tpe match {
608-
case AnyType => mixTag(TagAnyType)
609-
case NothingType => mixTag(TagNothingType)
610-
case UndefType => mixTag(TagUndefType)
611-
case BooleanType => mixTag(TagBooleanType)
612-
case CharType => mixTag(TagCharType)
613-
case ByteType => mixTag(TagByteType)
614-
case ShortType => mixTag(TagShortType)
615-
case IntType => mixTag(TagIntType)
616-
case LongType => mixTag(TagLongType)
617-
case FloatType => mixTag(TagFloatType)
618-
case DoubleType => mixTag(TagDoubleType)
619-
case StringType => mixTag(TagStringType)
620-
case NullType => mixTag(TagNullType)
621-
case NoType => mixTag(TagNoType)
622-
623-
case ClassType(className) =>
624-
mixTag(TagClassType)
608+
case AnyType => mixTag(TagAnyType)
609+
case AnyNotNullType => mixTag(TagAnyNotNullType)
610+
case NothingType => mixTag(TagNothingType)
611+
case UndefType => mixTag(TagUndefType)
612+
case BooleanType => mixTag(TagBooleanType)
613+
case CharType => mixTag(TagCharType)
614+
case ByteType => mixTag(TagByteType)
615+
case ShortType => mixTag(TagShortType)
616+
case IntType => mixTag(TagIntType)
617+
case LongType => mixTag(TagLongType)
618+
case FloatType => mixTag(TagFloatType)
619+
case DoubleType => mixTag(TagDoubleType)
620+
case StringType => mixTag(TagStringType)
621+
case NullType => mixTag(TagNullType)
622+
case NoType => mixTag(TagNoType)
623+
624+
case ClassType(className, nullable) =>
625+
mixTag(if (nullable) TagClassType else TagNonNullClassType)
625626
mixName(className)
626627

627-
case ArrayType(arrayTypeRef) =>
628-
mixTag(TagArrayType)
628+
case ArrayType(arrayTypeRef, nullable) =>
629+
mixTag(if (nullable) TagArrayType else TagNonNullArrayType)
629630
mixArrayTypeRef(arrayTypeRef)
630631

631632
case RecordType(fields) =>

ir/shared/src/main/scala/org/scalajs/ir/Printers.scala

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,24 +1066,31 @@ object Printers {
10661066
}
10671067

10681068
def print(tpe: Type): Unit = tpe match {
1069-
case AnyType => print("any")
1070-
case NothingType => print("nothing")
1071-
case UndefType => print("void")
1072-
case BooleanType => print("boolean")
1073-
case CharType => print("char")
1074-
case ByteType => print("byte")
1075-
case ShortType => print("short")
1076-
case IntType => print("int")
1077-
case LongType => print("long")
1078-
case FloatType => print("float")
1079-
case DoubleType => print("double")
1080-
case StringType => print("string")
1081-
case NullType => print("null")
1082-
case ClassType(className) => print(className)
1083-
case NoType => print("<notype>")
1084-
1085-
case ArrayType(arrayTypeRef) =>
1069+
case AnyType => print("any")
1070+
case AnyNotNullType => print("any!")
1071+
case NothingType => print("nothing")
1072+
case UndefType => print("void")
1073+
case BooleanType => print("boolean")
1074+
case CharType => print("char")
1075+
case ByteType => print("byte")
1076+
case ShortType => print("short")
1077+
case IntType => print("int")
1078+
case LongType => print("long")
1079+
case FloatType => print("float")
1080+
case DoubleType => print("double")
1081+
case StringType => print("string")
1082+
case NullType => print("null")
1083+
case NoType => print("<notype>")
1084+
1085+
case ClassType(className, nullable) =>
1086+
print(className)
1087+
if (!nullable)
1088+
print("!")
1089+
1090+
case ArrayType(arrayTypeRef, nullable) =>
10861091
print(arrayTypeRef)
1092+
if (!nullable)
1093+
print("!")
10871094

10881095
case RecordType(fields) =>
10891096
print('(')

ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import scala.util.matching.Regex
1818

1919
object ScalaJSVersions extends VersionChecks(
2020
current = "1.17.0-SNAPSHOT",
21-
binaryEmitted = "1.16"
21+
binaryEmitted = "1.17-SNAPSHOT"
2222
)
2323

2424
/** Helper class to allow for testing of logic. */

ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala

Lines changed: 73 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -851,27 +851,28 @@ object Serializers {
851851

852852
def writeType(tpe: Type): Unit = {
853853
tpe match {
854-
case AnyType => buffer.write(TagAnyType)
855-
case NothingType => buffer.write(TagNothingType)
856-
case UndefType => buffer.write(TagUndefType)
857-
case BooleanType => buffer.write(TagBooleanType)
858-
case CharType => buffer.write(TagCharType)
859-
case ByteType => buffer.write(TagByteType)
860-
case ShortType => buffer.write(TagShortType)
861-
case IntType => buffer.write(TagIntType)
862-
case LongType => buffer.write(TagLongType)
863-
case FloatType => buffer.write(TagFloatType)
864-
case DoubleType => buffer.write(TagDoubleType)
865-
case StringType => buffer.write(TagStringType)
866-
case NullType => buffer.write(TagNullType)
867-
case NoType => buffer.write(TagNoType)
868-
869-
case ClassType(className) =>
870-
buffer.write(TagClassType)
854+
case AnyType => buffer.write(TagAnyType)
855+
case AnyNotNullType => buffer.write(TagAnyNotNullType)
856+
case NothingType => buffer.write(TagNothingType)
857+
case UndefType => buffer.write(TagUndefType)
858+
case BooleanType => buffer.write(TagBooleanType)
859+
case CharType => buffer.write(TagCharType)
860+
case ByteType => buffer.write(TagByteType)
861+
case ShortType => buffer.write(TagShortType)
862+
case IntType => buffer.write(TagIntType)
863+
case LongType => buffer.write(TagLongType)
864+
case FloatType => buffer.write(TagFloatType)
865+
case DoubleType => buffer.write(TagDoubleType)
866+
case StringType => buffer.write(TagStringType)
867+
case NullType => buffer.write(TagNullType)
868+
case NoType => buffer.write(TagNoType)
869+
870+
case ClassType(className, nullable) =>
871+
buffer.write(if (nullable) TagClassType else TagNonNullClassType)
871872
writeName(className)
872873

873-
case ArrayType(arrayTypeRef) =>
874-
buffer.write(TagArrayType)
874+
case ArrayType(arrayTypeRef, nullable) =>
875+
buffer.write(if (nullable) TagArrayType else TagNonNullArrayType)
875876
writeArrayTypeRef(arrayTypeRef)
876877

877878
case RecordType(fields) =>
@@ -1035,7 +1036,7 @@ object Serializers {
10351036
private[this] var lastPosition: Position = Position.NoPosition
10361037

10371038
private[this] var enclosingClassName: ClassName = _
1038-
private[this] var thisTypeForHack8: Type = NoType
1039+
private[this] var thisTypeForHack: Type = NoType
10391040

10401041
def deserializeEntryPointsInfo(): EntryPointsInfo = {
10411042
hacks = new Hacks(sourceVersion = readHeader())
@@ -1229,7 +1230,22 @@ object Serializers {
12291230
case TagArrayLength => ArrayLength(readTree())
12301231
case TagArraySelect => ArraySelect(readTree(), readTree())(readType())
12311232
case TagRecordValue => RecordValue(readType().asInstanceOf[RecordType], readTrees())
1232-
case TagIsInstanceOf => IsInstanceOf(readTree(), readType())
1233+
1234+
case TagIsInstanceOf =>
1235+
val expr = readTree()
1236+
val testType0 = readType()
1237+
val testType = if (true /* hacks.use16 */) { // scalastyle:ignore
1238+
testType0 match {
1239+
case ClassType(className, true) => ClassType(className, nullable = false)
1240+
case ArrayType(arrayTypeRef, true) => ArrayType(arrayTypeRef, nullable = false)
1241+
case AnyType => AnyNotNullType
1242+
case _ => testType0
1243+
}
1244+
} else {
1245+
testType0
1246+
}
1247+
IsInstanceOf(expr, testType)
1248+
12331249
case TagAsInstanceOf => AsInstanceOf(readTree(), readType())
12341250
case TagGetClass => GetClass(readTree())
12351251
case TagClone => Clone(readTree())
@@ -1282,24 +1298,24 @@ object Serializers {
12821298

12831299
case TagThis =>
12841300
val tpe = readType()
1285-
if (hacks.use8)
1286-
This()(thisTypeForHack8)
1301+
if (true /* hacks.use16 */) // scalastyle:ignore
1302+
This()(thisTypeForHack)
12871303
else
12881304
This()(tpe)
12891305

12901306
case TagClosure =>
12911307
val arrow = readBoolean()
12921308
val captureParams = readParamDefs()
12931309
val (params, restParam) = readParamDefsWithRest()
1294-
val body = if (!hacks.use8) {
1310+
val body = if (false /* !hacks.use16 */) { // scalastyle:ignore
12951311
readTree()
12961312
} else {
1297-
val prevThisTypeForHack8 = thisTypeForHack8
1298-
thisTypeForHack8 = if (arrow) NoType else AnyType
1313+
val prevThisTypeForHack = thisTypeForHack
1314+
thisTypeForHack = if (arrow) NoType else AnyType
12991315
try {
13001316
readTree()
13011317
} finally {
1302-
thisTypeForHack8 = prevThisTypeForHack8
1318+
thisTypeForHack = prevThisTypeForHack
13031319
}
13041320
}
13051321
val captureValues = readTrees()
@@ -1358,7 +1374,7 @@ object Serializers {
13581374
// Evaluate the expression then definitely run into an NPE UB
13591375
UnwrapFromThrowable(expr)
13601376

1361-
case ClassType(_) =>
1377+
case ClassType(_, _) =>
13621378
expr match {
13631379
case New(_, _, _) =>
13641380
// Common case (`throw new SomeException(...)`) that is known not to be `null`
@@ -1400,14 +1416,14 @@ object Serializers {
14001416
val originalName = readOriginalName()
14011417
val kind = ClassKind.fromByte(readByte())
14021418

1403-
if (hacks.use8) {
1404-
thisTypeForHack8 = {
1419+
if (true /* hacks.use16 */) { // scalastyle:ignore
1420+
thisTypeForHack = {
14051421
if (kind.isJSType)
14061422
AnyType
14071423
else if (kind == ClassKind.HijackedClass)
1408-
BoxedClassToPrimType.getOrElse(cls, ClassType(cls)) // getOrElse as safety guard
1424+
BoxedClassToPrimType.getOrElse(cls, ClassType(cls, nullable = false)) // getOrElse as safety guard
14091425
else
1410-
ClassType(cls)
1426+
ClassType(cls, nullable = false)
14111427
}
14121428
}
14131429

@@ -1599,11 +1615,11 @@ object Serializers {
15991615
*/
16001616
assert(args.isEmpty)
16011617

1602-
val thisValue = This()(ClassType(ObjectClass))
1618+
val thisValue = This()(ClassType(ObjectClass, nullable = false))
16031619
val cloneableClassType = ClassType(CloneableClass)
16041620

16051621
val patchedBody = Some {
1606-
If(IsInstanceOf(thisValue, cloneableClassType),
1622+
If(IsInstanceOf(thisValue, cloneableClassType.toNonNullable),
16071623
Clone(AsInstanceOf(thisValue, cloneableClassType)),
16081624
Throw(New(
16091625
HackNames.CloneNotSupportedExceptionClass,
@@ -1844,24 +1860,28 @@ object Serializers {
18441860
def readType(): Type = {
18451861
val tag = readByte()
18461862
(tag: @switch) match {
1847-
case TagAnyType => AnyType
1848-
case TagNothingType => NothingType
1849-
case TagUndefType => UndefType
1850-
case TagBooleanType => BooleanType
1851-
case TagCharType => CharType
1852-
case TagByteType => ByteType
1853-
case TagShortType => ShortType
1854-
case TagIntType => IntType
1855-
case TagLongType => LongType
1856-
case TagFloatType => FloatType
1857-
case TagDoubleType => DoubleType
1858-
case TagStringType => StringType
1859-
case TagNullType => NullType
1860-
case TagNoType => NoType
1863+
case TagAnyType => AnyType
1864+
case TagAnyNotNullType => AnyNotNullType
1865+
case TagNothingType => NothingType
1866+
case TagUndefType => UndefType
1867+
case TagBooleanType => BooleanType
1868+
case TagCharType => CharType
1869+
case TagByteType => ByteType
1870+
case TagShortType => ShortType
1871+
case TagIntType => IntType
1872+
case TagLongType => LongType
1873+
case TagFloatType => FloatType
1874+
case TagDoubleType => DoubleType
1875+
case TagStringType => StringType
1876+
case TagNullType => NullType
1877+
case TagNoType => NoType
18611878

18621879
case TagClassType => ClassType(readClassName())
18631880
case TagArrayType => ArrayType(readArrayTypeRef())
18641881

1882+
case TagNonNullClassType => ClassType(readClassName(), nullable = false)
1883+
case TagNonNullArrayType => ArrayType(readArrayTypeRef(), nullable = false)
1884+
18651885
case TagRecordType =>
18661886
RecordType(List.fill(readInt()) {
18671887
val name = readSimpleFieldName()
@@ -2127,6 +2147,11 @@ object Serializers {
21272147
val use12: Boolean = use11 || sourceVersion == "1.12"
21282148

21292149
val use13: Boolean = use12 || sourceVersion == "1.13"
2150+
2151+
assert(sourceVersion != "1.14", "source version 1.14 does not exist")
2152+
assert(sourceVersion != "1.15", "source version 1.15 does not exist")
2153+
2154+
val use16: Boolean = use13 || sourceVersion == "1.16"
21302155
}
21312156

21322157
/** Names needed for hacks. */

ir/shared/src/main/scala/org/scalajs/ir/Tags.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ private[ir] object Tags {
170170
final val TagRecordType = TagArrayType + 1
171171
final val TagNoType = TagRecordType + 1
172172

173+
// New in 1.17
174+
175+
final val TagAnyNotNullType = TagNoType + 1
176+
final val TagNonNullClassType = TagAnyNotNullType + 1
177+
final val TagNonNullArrayType = TagNonNullClassType + 1
178+
173179
// Tags for TypeRefs
174180

175181
final val TagVoidRef = 1

0 commit comments

Comments
 (0)