Skip to content

Commit 367d63a

Browse files
committed
Opt/Wasm: Optimize reference equality as ref.is_null or ref.eq.
Profiling showed that using `jsStrictEquals`/`is` is really prohibitive.
1 parent 1b5b77e commit 367d63a

File tree

2 files changed

+61
-14
lines changed

2 files changed

+61
-14
lines changed

linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,19 +1347,53 @@ private class FunctionEmitter private (
13471347
val BinaryOp(op, lhs, rhs) = tree
13481348
assert(op == === || op == !==)
13491349

1350-
// TODO Optimize this when the operands have a better type than `any`
1351-
1352-
genTree(lhs, AnyType)
1353-
genTree(rhs, AnyType)
1350+
def maybeGenInvert(): Unit = {
1351+
if (op == BinaryOp.!==)
1352+
genBooleanNot(fb)
1353+
}
13541354

1355-
markPosition(tree)
1355+
val lhsType = lhs.tpe
1356+
val rhsType = rhs.tpe
13561357

1357-
fb += wa.Call(genFunctionID.is)
1358+
if (lhsType == NothingType) {
1359+
genTree(lhs, NothingType)
1360+
NothingType
1361+
} else if (rhsType == NothingType) {
1362+
genTree(lhs, NoType)
1363+
genTree(rhs, NothingType)
1364+
NothingType
1365+
} else if (rhsType == NullType) {
1366+
/* Note that the optimizer normalizes Literals on the right of `===`,
1367+
* so testing for the `lhsType == NullType` is not as useful.
1368+
*/
1369+
genTree(lhs, AnyType)
1370+
genTree(rhs, NoType) // no-op if it is actually a Null() literal
1371+
markPosition(tree)
1372+
fb += wa.RefIsNull
1373+
maybeGenInvert()
1374+
BooleanType
1375+
} else {
1376+
val lhsWasmType = transformSingleType(lhsType)
1377+
val rhsWasmType = transformSingleType(rhsType)
1378+
1379+
(lhsWasmType, rhsWasmType) match {
1380+
case (watpe.RefType(_, lhsHeapType), watpe.RefType(_, rhsHeapType))
1381+
if lhsHeapType != watpe.HeapType.Any && rhsHeapType != watpe.HeapType.Any =>
1382+
genTree(lhs, lhsType)
1383+
genTree(rhs, rhsType)
1384+
markPosition(tree)
1385+
fb += wa.RefEq
13581386

1359-
if (op == !==)
1360-
genBooleanNot(fb)
1387+
case _ =>
1388+
genTree(lhs, AnyType)
1389+
genTree(rhs, AnyType)
1390+
markPosition(tree)
1391+
fb += wa.Call(genFunctionID.is)
1392+
}
13611393

1362-
BooleanType
1394+
maybeGenInvert()
1395+
BooleanType
1396+
}
13631397
}
13641398

13651399
private def getElementaryBinaryOpInstr(op: BinaryOp.Code): wa.Instr = {

linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -542,10 +542,14 @@ private[optimizer] abstract class OptimizerCore(
542542
pretransformExpr(expr) { texpr =>
543543
val result = {
544544
if (isSubtype(texpr.tpe.base, testType)) {
545-
if (texpr.tpe.isNullable)
546-
JSBinaryOp(JSBinaryOp.!==, finishTransformExpr(texpr), Null())
547-
else
545+
if (texpr.tpe.isNullable) {
546+
if (isWasm)
547+
BinaryOp(BinaryOp.!==, finishTransformExpr(texpr), Null())
548+
else
549+
JSBinaryOp(JSBinaryOp.!==, finishTransformExpr(texpr), Null())
550+
} else {
548551
Block(finishTransformStat(texpr), BooleanLiteral(true))
552+
}
549553
} else {
550554
if (texpr.tpe.isExact)
551555
Block(finishTransformStat(texpr), BooleanLiteral(false))
@@ -3491,6 +3495,13 @@ private[optimizer] abstract class OptimizerCore(
34913495
if lhsIdent2 == lhsIdent && rhsIdent2 == rhsIdent =>
34923496
elsep
34933497

3498+
// Same, but on Wasm, which does not turn BinaryOp.=== into JSBinaryOp.===
3499+
case (BinaryOp(BinaryOp.===, VarRef(lhsIdent), Null()),
3500+
BinaryOp(BinaryOp.===, VarRef(rhsIdent), Null()),
3501+
BinaryOp(BinaryOp.===, VarRef(lhsIdent2), VarRef(rhsIdent2)))
3502+
if lhsIdent2 == lhsIdent && rhsIdent2 == rhsIdent =>
3503+
elsep
3504+
34943505
// Example: (x > y) || (x == y) -> (x >= y)
34953506
case (BinaryOp(op1 @ (Int_== | Int_!= | Int_< | Int_<= | Int_> | Int_>=), l1, r1),
34963507
BooleanLiteral(true),
@@ -4077,7 +4088,9 @@ private[optimizer] abstract class OptimizerCore(
40774088
case (PreTransLit(l), PreTransLit(r)) =>
40784089
val isSame = literal_===(l, r, isJSStrictEq = false)
40794090
PreTransLit(BooleanLiteral(if (op == ===) isSame else !isSame))
4080-
case _ if canOptimizeAsJSStrictEq(lhs.tpe, rhs.tpe) =>
4091+
case (PreTransLit(_), _) =>
4092+
foldBinaryOp(op, rhs, lhs)
4093+
case _ if !isWasm && canOptimizeAsJSStrictEq(lhs.tpe, rhs.tpe) =>
40814094
foldJSBinaryOp(
40824095
if (op == ===) JSBinaryOp.=== else JSBinaryOp.!==,
40834096
lhs, rhs)
@@ -5027,7 +5040,7 @@ private[optimizer] abstract class OptimizerCore(
50275040
finishTransformStat(lhs),
50285041
BooleanLiteral(!positive)).toPreTransform
50295042

5030-
case (PreTransLit(_), _) => foldBinaryOp(op, rhs, lhs)
5043+
case (PreTransLit(_), _) => foldJSBinaryOp(op, rhs, lhs)
50315044
case _ => default
50325045
}
50335046

0 commit comments

Comments
 (0)