Skip to content

Commit cd80489

Browse files
committed
Wasm: Emit fewer null pointer checks.
1 parent 57c7e3e commit cd80489

File tree

3 files changed

+89
-27
lines changed

3 files changed

+89
-27
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ class CoreWasmLib(coreSpec: CoreSpec) {
316316
case DoubleRef => Float64
317317
case _ => Int32
318318
}
319-
addHelperImport(genFunctionID.box(primRef), List(wasmType), List(anyref))
319+
addHelperImport(genFunctionID.box(primRef), List(wasmType), List(RefType.any))
320320
addHelperImport(genFunctionID.unbox(primRef), List(anyref), List(wasmType))
321321
addHelperImport(genFunctionID.typeTest(primRef), List(anyref), List(Int32))
322322
}

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

+87-26
Original file line numberDiff line numberDiff line change
@@ -409,24 +409,78 @@ private class FunctionEmitter private (
409409
}
410410
}
411411

412-
/** Emits a `ref_as_non_null`, or an NPE check if required. */
413-
private def genAsNonNullOrNPE(): Unit = {
414-
if (semantics.nullPointers == CheckedBehavior.Unchecked)
415-
fb += wa.RefAsNonNull
416-
else
417-
fb += wa.BrOnNull(getNPELabel())
412+
/** Emits a `ref_as_non_null` or an NPE check if required for the given `Tree`.
413+
*
414+
* This method does not emit `tree`. It only uses it to determine whether
415+
* a check is required.
416+
*/
417+
private def genAsNonNullOrNPEFor(tree: Tree): Unit = {
418+
val nullabilityLevel = nullabilityLevelOf(tree)
419+
if (nullabilityLevel >= 1) {
420+
if (semantics.nullPointers != CheckedBehavior.Unchecked && nullabilityLevel >= 2)
421+
fb += wa.BrOnNull(getNPELabel())
422+
else
423+
fb += wa.RefAsNonNull
424+
}
418425
}
419426

420-
/** Emits an NPE check if required, otherwise nothing.
427+
/** Emits an NPE check if required for the given `Tree`, otherwise nothing.
428+
*
429+
* This method does not emit `tree`. It only uses it to determine whether
430+
* a check is required.
421431
*
422432
* Unlike `genAsNonNullOrNPE`, after this codegen the value on the stack is
423433
* still statically typed as nullable at the Wasm level.
424434
*/
425-
private def genCheckNonNull(): Unit = {
426-
if (semantics.nullPointers != CheckedBehavior.Unchecked)
435+
private def genCheckNonNullFor(tree: Tree): Unit = {
436+
if (semantics.nullPointers != CheckedBehavior.Unchecked && nullabilityLevelOf(tree) >= 2)
427437
fb += wa.BrOnNull(getNPELabel())
428438
}
429439

440+
/** Analyzes the nullability level of `tree`.
441+
*
442+
* - `0` if `tree` is statically known to generate a non-nullable Wasm value.
443+
* - `1` if `tree` is statically known to generate a non-nullable value,
444+
* but maybe typed as nullable at the Wasm level.
445+
* - `2` if `tree` can be `null`.
446+
*
447+
* See also: `isNotNull` in `emitter.FunctionEmitter`.
448+
*/
449+
private def nullabilityLevelOf(tree: Tree): Int = {
450+
// !!! Similar code in emitter.FunctionEmitter.isNotNull
451+
// !!! Similar code in OptimizerCore.isNotNull
452+
453+
def isNullableType(tpe: Type): Boolean = tpe match {
454+
case NullType => true
455+
case _: PrimType => false
456+
case _ => true
457+
}
458+
459+
def shapeNullabilityLevel(tree: Tree): Int = tree match {
460+
case Transient(Transients.CheckNotNull(_)) =>
461+
0
462+
case Transient(Transients.AssumeNotNull(expr)) =>
463+
Math.min(1, shapeNullabilityLevel(expr))
464+
case Transient(Transients.Cast(expr, _)) =>
465+
shapeNullabilityLevel(expr)
466+
case _: This =>
467+
if (tree.tpe != AnyType) 0
468+
else 2
469+
case _:New | _:NewArray | _:ArrayValue | _:Clone | _:ClassOf =>
470+
0
471+
case _: LoadModule =>
472+
if (semantics.moduleInit == CheckedBehavior.Compliant) 2
473+
else 0
474+
case _ =>
475+
2
476+
}
477+
478+
if (!isNullableType(tree.tpe))
479+
0
480+
else
481+
shapeNullabilityLevel(tree)
482+
}
483+
430484
/** Emits an unconditional NPE. */
431485
private def genNPE(): Unit = {
432486
if (semantics.nullPointers == CheckedBehavior.Unchecked)
@@ -652,7 +706,7 @@ private class FunctionEmitter private (
652706
markPosition(tree)
653707
genNPE()
654708
} else {
655-
genAsNonNullOrNPE()
709+
genAsNonNullOrNPEFor(qualifier)
656710
genTree(rhs, lhs.tpe)
657711
markPosition(tree)
658712
fb += wa.StructSet(
@@ -686,7 +740,7 @@ private class FunctionEmitter private (
686740
case _ => false
687741
}
688742

689-
genCheckNonNull()
743+
genCheckNonNullFor(array)
690744

691745
if (semantics.arrayIndexOutOfBounds == CheckedBehavior.Unchecked &&
692746
(semantics.arrayStores == CheckedBehavior.Unchecked || isPrimArray)) {
@@ -839,7 +893,7 @@ private class FunctionEmitter private (
839893

840894
// Load receiver and arguments
841895
genTree(receiver, AnyType)
842-
genAsNonNullOrNPE()
896+
genAsNonNullOrNPEFor(receiver)
843897
fb += wa.LocalTee(receiverLocalForDispatch)
844898
genArgs(args, methodName)
845899

@@ -901,7 +955,7 @@ private class FunctionEmitter private (
901955
*/
902956
def genReceiverNotNull(): Unit = {
903957
genTreeAuto(receiver)
904-
genAsNonNullOrNPE()
958+
genAsNonNullOrNPEFor(receiver)
905959
}
906960

907961
/* Generates a resolved call to a method of a hijacked class.
@@ -1180,14 +1234,14 @@ private class FunctionEmitter private (
11801234
BoxedClassToPrimType.get(targetClassName) match {
11811235
case None =>
11821236
genTree(receiver, ClassType(targetClassName))
1183-
genAsNonNullOrNPE()
1237+
genAsNonNullOrNPEFor(receiver)
11841238

11851239
case Some(primReceiverType) =>
11861240
if (receiver.tpe == primReceiverType) {
11871241
genTreeAuto(receiver)
11881242
} else {
11891243
genTree(receiver, AnyType)
1190-
genAsNonNullOrNPE()
1244+
genAsNonNullOrNPEFor(receiver)
11911245
genUnbox(primReceiverType)
11921246
}
11931247
}
@@ -1285,7 +1339,7 @@ private class FunctionEmitter private (
12851339
*/
12861340
genNPE()
12871341
} else {
1288-
genCheckNonNull()
1342+
genCheckNonNullFor(qualifier)
12891343
fb += wa.StructGet(
12901344
genTypeID.forClass(className),
12911345
genFieldID.forClassInstanceField(fieldName)
@@ -2005,7 +2059,10 @@ private class FunctionEmitter private (
20052059
case watpe.RefType(true, watpe.HeapType.Any) =>
20062060
() // nothing to do
20072061
case targetWasmType: watpe.RefType =>
2008-
fb += wa.RefCast(targetWasmType)
2062+
if (nullabilityLevelOf(expr) >= 2)
2063+
fb += wa.RefCast(targetWasmType)
2064+
else
2065+
fb += wa.RefCast(targetWasmType.toNonNullable)
20092066
case _ =>
20102067
throw new AssertionError(s"Unexpected type in AsInstanceOf: $targetTpe")
20112068
}
@@ -2029,7 +2086,11 @@ private class FunctionEmitter private (
20292086
fb += wa.GlobalGet(genGlobalID.undef)
20302087

20312088
case StringType =>
2032-
genAsNonNullOrNPE()
2089+
val sig = watpe.FunctionType(List(watpe.RefType.anyref), List(watpe.RefType.any))
2090+
fb.block(sig) { nonNullLabel =>
2091+
fb += wa.BrOnNonNull(nonNullLabel)
2092+
fb += wa.GlobalGet(genGlobalID.emptyString)
2093+
}
20332094

20342095
case CharType | LongType =>
20352096
// Extract the `value` field (the only field) out of the box class.
@@ -2084,13 +2145,13 @@ private class FunctionEmitter private (
20842145

20852146
genTreeAuto(expr)
20862147
markPosition(tree)
2087-
genCheckNonNull()
2148+
genCheckNonNullFor(expr)
20882149
fb += wa.StructGet(genTypeID.ObjectStruct, genFieldID.objStruct.vtable)
20892150
fb += wa.Call(genFunctionID.getClassOf)
20902151
} else {
20912152
genTree(expr, AnyType)
20922153
markPosition(tree)
2093-
genAsNonNullOrNPE()
2154+
genAsNonNullOrNPEFor(expr)
20942155
fb += wa.Call(genFunctionID.anyGetClass)
20952156
}
20962157

@@ -2442,7 +2503,7 @@ private class FunctionEmitter private (
24422503

24432504
markPosition(tree)
24442505

2445-
genAsNonNullOrNPE()
2506+
genAsNonNullOrNPEFor(expr)
24462507

24472508
// if !expr.isInstanceOf[js.JavaScriptException], then br $done
24482509
fb += wa.BrOnCastFail(
@@ -2681,7 +2742,7 @@ private class FunctionEmitter private (
26812742
array.tpe match {
26822743
case ArrayType(arrayTypeRef) =>
26832744
// Get the underlying array
2684-
genCheckNonNull()
2745+
genCheckNonNullFor(array)
26852746
fb += wa.StructGet(
26862747
genTypeID.forArrayClass(arrayTypeRef),
26872748
genFieldID.objStruct.arrayUnderlying
@@ -2776,7 +2837,7 @@ private class FunctionEmitter private (
27762837

27772838
array.tpe match {
27782839
case ArrayType(arrayTypeRef) =>
2779-
genCheckNonNull()
2840+
genCheckNonNullFor(array)
27802841

27812842
if (semantics.arrayIndexOutOfBounds == CheckedBehavior.Unchecked) {
27822843
// Get the underlying array
@@ -2932,7 +2993,7 @@ private class FunctionEmitter private (
29322993

29332994
markPosition(tree)
29342995

2935-
genAsNonNullOrNPE()
2996+
genAsNonNullOrNPEFor(expr)
29362997
fb += wa.LocalTee(exprLocal)
29372998

29382999
fb += wa.LocalGet(exprLocal)
@@ -3134,7 +3195,7 @@ private class FunctionEmitter private (
31343195
case tpe: PrimType =>
31353196
tpe
31363197
case tpe =>
3137-
genCheckNonNull()
3198+
genAsNonNullOrNPEFor(expr)
31383199
tpe
31393200
}
31403201

@@ -3151,7 +3212,7 @@ private class FunctionEmitter private (
31513212
case Transients.ObjectClassName(obj) =>
31523213
genTree(obj, AnyType)
31533214
markPosition(tree)
3154-
genAsNonNullOrNPE()
3215+
genAsNonNullOrNPEFor(obj)
31553216
fb += wa.Call(genFunctionID.anyGetClassName)
31563217
StringType
31573218

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

+1
Original file line numberDiff line numberDiff line change
@@ -5292,6 +5292,7 @@ private[optimizer] abstract class OptimizerCore(
52925292

52935293
private def isNotNull(tree: Tree): Boolean = {
52945294
// !!! Duplicate code with FunctionEmitter.isNotNull
5295+
// !!! Similar code in wasmemitter.FunctionEmitter.nullabilityLevelOf
52955296

52965297
def isShapeNotNull(tree: Tree): Boolean = tree match {
52975298
case Transient(CheckNotNull(_) | AssumeNotNull(_)) =>

0 commit comments

Comments
 (0)