Skip to content

Commit 431b9af

Browse files
committed
Merge several IR nodes into UnaryOp.
`ArrayLength`, `GetClass`, `Clone`, `IdentityHashCode`, `WrapAsThrowable`, `UnwrapFromThrowable` and `Throw` are now ops of `UnaryOp`, rather than having their own IR nodes. They all follow the general contract of how a `UnaryOp` must behave: first evaluate the argument, then perform an operation that only depends on the value of the argument (and not, for example, on the scope). It makes the JS `FunctionEmitter` a bit messier. However, everything else gets simpler, as we get rid of dedicated paths for those 7 IR nodes. In order to better fit with the other `UnaryOp`s, notably the ones for `jl.Class` operations, we demand non-nullable arguments. An explicit `CheckNotNull` must be used for arguments that are nullable. --- In this commit, we do not update the compiler yet. It still emits the old IR nodes, so that we can test the deserialiation hacks. That also means the case classes still exist for now.
1 parent d3a5b0a commit 431b9af

File tree

11 files changed

+455
-492
lines changed

11 files changed

+455
-492
lines changed

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

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -348,40 +348,47 @@ object Printers {
348348
case UnaryOp(op, lhs) =>
349349
import UnaryOp._
350350

351-
if (op < String_length) {
352-
print('(')
353-
print((op: @switch) match {
354-
case Boolean_! =>
355-
"!"
356-
case IntToChar =>
357-
"(char)"
358-
case IntToByte =>
359-
"(byte)"
360-
case IntToShort =>
361-
"(short)"
362-
case CharToInt | ByteToInt | ShortToInt | LongToInt | DoubleToInt =>
363-
"(int)"
364-
case IntToLong | DoubleToLong =>
365-
"(long)"
366-
case DoubleToFloat | LongToFloat =>
367-
"(float)"
368-
case IntToDouble | LongToDouble | FloatToDouble =>
369-
"(double)"
370-
})
371-
print(lhs)
372-
print(')')
373-
} else {
351+
def p(prefix: String, suffix: String): Unit = {
352+
print(prefix)
374353
print(lhs)
375-
print((op: @switch) match {
376-
case String_length => ".length"
377-
case CheckNotNull => ".notNull"
378-
case Class_name => ".name"
379-
case Class_isPrimitive => ".isPrimitive"
380-
case Class_isInterface => ".isInterface"
381-
case Class_isArray => ".isArray"
382-
case Class_componentType => ".componentType"
383-
case Class_superClass => ".superClass"
384-
})
354+
print(suffix)
355+
}
356+
357+
(op: @switch) match {
358+
case Boolean_! =>
359+
p("(!", ")")
360+
case IntToChar =>
361+
p("((char)", ")")
362+
case IntToByte =>
363+
p("((byte)", ")")
364+
case IntToShort =>
365+
p("((short)", ")")
366+
case CharToInt | ByteToInt | ShortToInt | LongToInt | DoubleToInt =>
367+
p("((int)", ")")
368+
case IntToLong | DoubleToLong =>
369+
p("((long)", ")")
370+
case DoubleToFloat | LongToFloat =>
371+
p("((float)", ")")
372+
case IntToDouble | LongToDouble | FloatToDouble =>
373+
p("((double)", ")")
374+
375+
case String_length => p("", ".length")
376+
case CheckNotNull => p("", ".notNull")
377+
case Class_name => p("", ".name")
378+
case Class_isPrimitive => p("", ".isPrimitive")
379+
case Class_isInterface => p("", ".isInterface")
380+
case Class_isArray => p("", ".isArray")
381+
case Class_componentType => p("", ".componentType")
382+
case Class_superClass => p("", ".superClass")
383+
case Array_length => p("", ".length")
384+
case GetClass => p("", ".getClass()")
385+
386+
case Clone => p("<clone>(", ")")
387+
case IdentityHashCode => p("<identityHashCode>(", ")")
388+
case WrapAsThrowable => p("<wrapAsThrowable>(", ")")
389+
case UnwrapFromThrowable => p("<unwrapFromThrowable>(", ")")
390+
391+
case Throw => p("throw ", "")
385392
}
386393

387394
case BinaryOp(BinaryOp.Int_-, IntLiteral(0), rhs) =>

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

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,13 +1175,6 @@ object Serializers {
11751175
case TagTryFinally =>
11761176
TryFinally(readTree(), readTree())
11771177

1178-
case TagThrow =>
1179-
val expr = readTree()
1180-
val patchedExpr =
1181-
if (hacks.use8) throwArgumentHack8(expr)
1182-
else expr
1183-
Throw(patchedExpr)
1184-
11851178
case TagMatch =>
11861179
Match(readTree(), List.fill(readInt()) {
11871180
(readTrees().map(_.asInstanceOf[MatchableLiteral]), readTree())
@@ -1251,6 +1244,36 @@ object Serializers {
12511244
case TagUnaryOp => UnaryOp(readByte(), readTree())
12521245
case TagBinaryOp => BinaryOp(readByte(), readTree(), readTree())
12531246

1247+
case TagArrayLength | TagGetClass | TagClone | TagIdentityHashCode |
1248+
TagWrapAsThrowable | TagUnwrapFromThrowable | TagThrow =>
1249+
if (false /*!hacks.use17*/) { // scalastyle:ignore
1250+
throw new IOException(
1251+
s"Illegal legacy node $tag found in class ${enclosingClassName.nameString}")
1252+
}
1253+
1254+
val lhs = readTree()
1255+
def checkNotNullLhs: Tree = UnaryOp(UnaryOp.CheckNotNull, lhs)
1256+
1257+
(tag: @switch) match {
1258+
case TagArrayLength =>
1259+
UnaryOp(UnaryOp.Array_length, checkNotNullLhs)
1260+
case TagGetClass =>
1261+
UnaryOp(UnaryOp.GetClass, checkNotNullLhs)
1262+
case TagClone =>
1263+
UnaryOp(UnaryOp.Clone, checkNotNullLhs)
1264+
case TagIdentityHashCode =>
1265+
UnaryOp(UnaryOp.IdentityHashCode, lhs)
1266+
case TagWrapAsThrowable =>
1267+
UnaryOp(UnaryOp.WrapAsThrowable, lhs)
1268+
case TagUnwrapFromThrowable =>
1269+
UnaryOp(UnaryOp.UnwrapFromThrowable, checkNotNullLhs)
1270+
case TagThrow =>
1271+
val patchedLhs =
1272+
if (hacks.use8) throwArgumentHack8(lhs)
1273+
else lhs
1274+
UnaryOp(UnaryOp.Throw, patchedLhs)
1275+
}
1276+
12541277
case TagNewArray =>
12551278
val arrayTypeRef = readArrayTypeRef()
12561279
val lengths = readTrees()
@@ -1284,7 +1307,6 @@ object Serializers {
12841307
}
12851308

12861309
case TagArrayValue => ArrayValue(readArrayTypeRef(), readTrees())
1287-
case TagArrayLength => ArrayLength(readTree())
12881310
case TagArraySelect => ArraySelect(readTree(), readTree())(readType())
12891311
case TagRecordValue => RecordValue(readType().asInstanceOf[RecordType], readTrees())
12901312

@@ -1304,14 +1326,6 @@ object Serializers {
13041326
IsInstanceOf(expr, testType)
13051327

13061328
case TagAsInstanceOf => AsInstanceOf(readTree(), readType())
1307-
case TagGetClass => GetClass(readTree())
1308-
case TagClone => Clone(readTree())
1309-
case TagIdentityHashCode => IdentityHashCode(readTree())
1310-
1311-
case TagWrapAsThrowable =>
1312-
WrapAsThrowable(readTree())
1313-
case TagUnwrapFromThrowable =>
1314-
UnwrapFromThrowable(readTree())
13151329

13161330
case TagJSNew => JSNew(readTree(), readTreeOrJSSpreads())
13171331
case TagJSPrivateSelect => JSPrivateSelect(readTree(), readFieldIdent())

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

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -283,12 +283,24 @@ object Trees {
283283
}
284284

285285
/** Unary operation.
286+
*
287+
* All unary operations follow common evaluation steps:
288+
*
289+
* 1. Let `lhsValue` be the result of evaluating `lhs`.
290+
* 2. Perform an operation that depends on `op` and `lhsValue`.
286291
*
287292
* The `Class_x` operations take a `jl.Class!` argument, i.e., a
288293
* non-nullable `jl.Class`.
289294
*
295+
* Likewise, `Array_length`, `GetClass`, `Clone` and `UnwrapFromThrowable`
296+
* take arguments of non-nullable types.
297+
*
290298
* `CheckNotNull` throws NPEs subject to UB.
291299
*
300+
* `Throw` always throws, obviously.
301+
*
302+
* `Clone` and `WrapAsThrowable` are side-effect-free but not pure.
303+
*
292304
* Otherwise, unary operations preserve pureness.
293305
*/
294306
sealed case class UnaryOp(op: UnaryOp.Code, lhs: Tree)(
@@ -340,9 +352,26 @@ object Trees {
340352
final val Class_componentType = 23
341353
final val Class_superClass = 24
342354

355+
// Misc ops introduced in 1.18, which used to have dedicated IR nodes
356+
final val Array_length = 25
357+
final val GetClass = 26
358+
final val Clone = 27
359+
final val IdentityHashCode = 28
360+
final val WrapAsThrowable = 29
361+
final val UnwrapFromThrowable = 30
362+
final val Throw = 31
363+
343364
def isClassOp(op: Code): Boolean =
344365
op >= Class_name && op <= Class_superClass
345366

367+
def isPureOp(op: Code): Boolean = (op: @switch) match {
368+
case CheckNotNull | Clone | WrapAsThrowable | Throw => false
369+
case _ => true
370+
}
371+
372+
def isSideEffectFreeOp(op: Code): Boolean =
373+
op != CheckNotNull && op != Throw
374+
346375
def resultTypeOf(op: Code, argType: Type): Type = (op: @switch) match {
347376
case Boolean_! | Class_isPrimitive | Class_isInterface | Class_isArray =>
348377
BooleanType
@@ -352,20 +381,27 @@ object Trees {
352381
ByteType
353382
case IntToShort =>
354383
ShortType
355-
case CharToInt | ByteToInt | ShortToInt | LongToInt | DoubleToInt | String_length =>
384+
case CharToInt | ByteToInt | ShortToInt | LongToInt | DoubleToInt |
385+
String_length | Array_length | IdentityHashCode =>
356386
IntType
357387
case IntToLong | DoubleToLong =>
358388
LongType
359389
case DoubleToFloat | LongToFloat =>
360390
FloatType
361391
case IntToDouble | LongToDouble | FloatToDouble =>
362392
DoubleType
363-
case CheckNotNull =>
393+
case CheckNotNull | Clone =>
364394
argType.toNonNullable
365395
case Class_name =>
366396
StringType
367-
case Class_componentType | Class_superClass =>
397+
case Class_componentType | Class_superClass | GetClass =>
368398
ClassType(ClassClass, nullable = true)
399+
case WrapAsThrowable =>
400+
ClassType(ThrowableClass, nullable = false)
401+
case UnwrapFromThrowable =>
402+
AnyType
403+
case Throw =>
404+
NothingType
369405
}
370406
}
371407

@@ -829,11 +865,7 @@ object Trees {
829865
val tpe = VoidType
830866
}
831867

832-
/** Unary operation (always preserves pureness).
833-
*
834-
* Operations which do not preserve pureness are not allowed in this tree.
835-
* These are notably ++ and --
836-
*/
868+
/** JavaScript unary operation. */
837869
sealed case class JSUnaryOp(op: JSUnaryOp.Code, lhs: Tree)(
838870
implicit val pos: Position) extends Tree {
839871
val tpe = JSUnaryOp.resultTypeOf(op)
@@ -854,11 +886,7 @@ object Trees {
854886
AnyType
855887
}
856888

857-
/** Binary operation (always preserves pureness).
858-
*
859-
* Operations which do not preserve pureness are not allowed in this tree.
860-
* These are notably +=, -=, *=, /= and %=
861-
*/
889+
/** JavaScript binary operation. */
862890
sealed case class JSBinaryOp(op: JSBinaryOp.Code, lhs: Tree, rhs: Tree)(
863891
implicit val pos: Position) extends Tree {
864892
val tpe = JSBinaryOp.resultTypeOf(op)

ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,14 @@ class PrintersTest {
403403
assertPrintEquals("x.isArray", UnaryOp(Class_isArray, classVarRef))
404404
assertPrintEquals("x.componentType", UnaryOp(Class_componentType, classVarRef))
405405
assertPrintEquals("x.superClass", UnaryOp(Class_superClass, classVarRef))
406+
407+
assertPrintEquals("x.length", UnaryOp(Array_length, ref("x", arrayType(IntRef, 1))))
408+
assertPrintEquals("x.getClass()", UnaryOp(GetClass, ref("x", AnyType)))
409+
assertPrintEquals("<clone>(x)", UnaryOp(Clone, ref("x", arrayType(ObjectClass, 1))))
410+
assertPrintEquals("<identityHashCode>(x)", UnaryOp(IdentityHashCode, ref("x", AnyType)))
411+
assertPrintEquals("<wrapAsThrowable>(e)", UnaryOp(WrapAsThrowable, ref("e", AnyType)))
412+
assertPrintEquals("<unwrapFromThrowable>(e)",
413+
UnaryOp(UnwrapFromThrowable, ref("e", ClassType(ThrowableClass, nullable = true))))
406414
}
407415

408416
@Test def printPseudoUnaryOp(): Unit = {

linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -712,23 +712,16 @@ private[emitter] object CoreJSLib {
712712
Return(constantClassResult(BoxedUnitClass))
713713
}
714714
), {
715-
If(instance === Null(), {
716-
if (nullPointers == CheckedBehavior.Unchecked)
717-
Return(genApply(instance, getClassMethodName, Nil))
718-
else
719-
genCallHelper(VarField.throwNullPointerException)
715+
If(genIsInstanceOfHijackedClass(instance, BoxedLongClass), {
716+
Return(constantClassResult(BoxedLongClass))
720717
}, {
721-
If(genIsInstanceOfHijackedClass(instance, BoxedLongClass), {
722-
Return(constantClassResult(BoxedLongClass))
718+
If(genIsInstanceOfHijackedClass(instance, BoxedCharacterClass), {
719+
Return(constantClassResult(BoxedCharacterClass))
723720
}, {
724-
If(genIsInstanceOfHijackedClass(instance, BoxedCharacterClass), {
725-
Return(constantClassResult(BoxedCharacterClass))
721+
If(genIsScalaJSObject(instance), {
722+
Return(scalaObjectResult(instance))
726723
}, {
727-
If(genIsScalaJSObject(instance), {
728-
Return(scalaObjectResult(instance))
729-
}, {
730-
Return(jsObjectResult)
731-
})
724+
Return(jsObjectResult)
732725
})
733726
})
734727
})

0 commit comments

Comments
 (0)