Skip to content

Commit f54d97c

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 f54d97c

File tree

13 files changed

+488
-564
lines changed

13 files changed

+488
-564
lines changed

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

+40-33
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

+55-31
Original file line numberDiff line numberDiff line change
@@ -1144,8 +1144,8 @@ object Serializers {
11441144
* (throw qual.field[null]) = rhs --> qual.field[null] = rhs
11451145
*/
11461146
lhs0 match {
1147-
case Throw(sel: Select) if sel.tpe == NullType => sel
1148-
case _ => lhs0
1147+
case UnaryOp(UnaryOp.Throw, sel: Select) if sel.tpe == NullType => sel
1148+
case _ => lhs0
11491149
}
11501150
} else {
11511151
lhs0
@@ -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())
@@ -1212,7 +1205,7 @@ object Serializers {
12121205
/* Note [Nothing FieldDef rewrite]
12131206
* qual.field[nothing] --> throw qual.field[null]
12141207
*/
1215-
Throw(Select(qualifier, field)(NullType))
1208+
UnaryOp(UnaryOp.Throw, Select(qualifier, field)(NullType))
12161209
} else {
12171210
Select(qualifier, field)(tpe)
12181211
}
@@ -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())
@@ -1464,10 +1478,13 @@ object Serializers {
14641478
* `runtime.package$.unwrapJavaScriptException(x)`.
14651479
*/
14661480
private def throwArgumentHack8(expr: Tree)(implicit pos: Position): Tree = {
1481+
def unwrapFromThrowable(t: Tree): Tree =
1482+
UnaryOp(UnaryOp.UnwrapFromThrowable, t)
1483+
14671484
expr.tpe match {
14681485
case NullType =>
14691486
// Evaluate the expression then definitely run into an NPE UB
1470-
UnwrapFromThrowable(expr)
1487+
unwrapFromThrowable(expr)
14711488

14721489
case ClassType(_, _) =>
14731490
expr match {
@@ -1478,7 +1495,7 @@ object Serializers {
14781495
/* Common case (explicit re-throw of the form `throw th`) where we don't need the IIFE.
14791496
* if (expr === null) unwrapFromThrowable(null) else expr
14801497
*/
1481-
If(BinaryOp(BinaryOp.===, expr, Null()), UnwrapFromThrowable(Null()), expr)(AnyType)
1498+
If(BinaryOp(BinaryOp.===, expr, Null()), unwrapFromThrowable(Null()), expr)(AnyType)
14821499
case _ =>
14831500
/* General case where we need to avoid evaluating `expr` twice.
14841501
* ((x) => if (x === null) unwrapFromThrowable(null) else x)(expr)
@@ -1487,7 +1504,7 @@ object Serializers {
14871504
val xParamDef = ParamDef(x, OriginalName.NoOriginalName, AnyType, mutable = false)
14881505
val xRef = xParamDef.ref
14891506
val closure = Closure(arrow = true, Nil, List(xParamDef), None, {
1490-
If(BinaryOp(BinaryOp.===, xRef, Null()), UnwrapFromThrowable(Null()), xRef)(AnyType)
1507+
If(BinaryOp(BinaryOp.===, xRef, Null()), unwrapFromThrowable(Null()), xRef)(AnyType)
14911508
}, Nil)
14921509
JSFunctionApply(closure, List(expr))
14931510
}
@@ -1683,6 +1700,12 @@ object Serializers {
16831700
VarDef(LocalIdent(LocalName(name)), NoOriginalName, vtpe, mutable, rhs)
16841701
}
16851702

1703+
def arrayLength(t: Tree)(implicit pos: Position): Tree =
1704+
UnaryOp(UnaryOp.Array_length, t)
1705+
1706+
def getClass(t: Tree)(implicit pos: Position): Tree =
1707+
UnaryOp(UnaryOp.GetClass, t)
1708+
16861709
val jlClassRef = ClassRef(ClassClass)
16871710
val intArrayTypeRef = ArrayTypeRef(IntRef, 1)
16881711
val objectRef = ClassRef(ObjectClass)
@@ -1740,7 +1763,7 @@ object Serializers {
17401763
length,
17411764
result,
17421765
innerOffset,
1743-
If(BinaryOp(BinaryOp.Int_<, innerOffset.ref, ArrayLength(dimensions.ref)), {
1766+
If(BinaryOp(BinaryOp.Int_<, innerOffset.ref, arrayLength(dimensions.ref)), {
17441767
Block(
17451768
result2,
17461769
innerComponentType,
@@ -1810,11 +1833,11 @@ object Serializers {
18101833
Block(
18111834
outermostComponentType,
18121835
i,
1813-
While(BinaryOp(BinaryOp.Int_!=, i.ref, ArrayLength(lengthsParam.ref)), {
1836+
While(BinaryOp(BinaryOp.Int_!=, i.ref, arrayLength(lengthsParam.ref)), {
18141837
Block(
18151838
Assign(
18161839
outermostComponentType.ref,
1817-
GetClass(Apply(EAF, This()(ClassType(ReflectArrayModClass, nullable = false)),
1840+
getClass(Apply(EAF, This()(ClassType(ReflectArrayModClass, nullable = false)),
18181841
MethodIdent(newInstanceSingleName),
18191842
List(outermostComponentType.ref, IntLiteral(0)))(AnyType))
18201843
),
@@ -1944,7 +1967,7 @@ object Serializers {
19441967
*/
19451968
assert(args.size == 1)
19461969

1947-
val patchedBody = Some(IdentityHashCode(args(0).ref))
1970+
val patchedBody = Some(UnaryOp(UnaryOp.IdentityHashCode, args(0).ref))
19481971
val patchedOptimizerHints = OptimizerHints.empty.withInline(true)
19491972

19501973
MethodDef(flags, name, originalName, args, resultType, patchedBody)(
@@ -1969,11 +1992,12 @@ object Serializers {
19691992

19701993
val patchedBody = Some {
19711994
If(IsInstanceOf(thisValue, cloneableClassType.toNonNullable),
1972-
Clone(AsInstanceOf(thisValue, cloneableClassType)),
1973-
Throw(New(
1974-
HackNames.CloneNotSupportedExceptionClass,
1975-
MethodIdent(NoArgConstructorName),
1976-
Nil)))(cloneableClassType)
1995+
UnaryOp(UnaryOp.Clone, AsInstanceOf(thisValue, cloneableClassType)),
1996+
UnaryOp(UnaryOp.Throw,
1997+
New(
1998+
HackNames.CloneNotSupportedExceptionClass,
1999+
MethodIdent(NoArgConstructorName),
2000+
Nil)))(cloneableClassType)
19772001
}
19782002
val patchedOptimizerHints = OptimizerHints.empty.withInline(true)
19792003

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

+41-13
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

+8
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 = {

0 commit comments

Comments
 (0)