abstract class LocalOpt extends AnyRef
Optimizations within a single method. Certain optimizations enable others, for example removing
unreachable code can render a try
block empty and enable removeEmptyExceptionHandlers. The
latter in turn enables more unreachable code to be eliminated (the catch
block), so there is
a cyclic dependency. Optimizations that depend on each other are therefore executed in a loop
until reaching a fixpoint.
The optimizations marked UPSTREAM enable optimizations that were already executed, so they cause another iteration in the fixpoint loop.
nullness optimizations: rewrite null-checking branches to GOTO if nullness is known + enables downstream
- unreachable code (null / non-null branch becomes unreachable)
- box-unbox elimination (may render an escaping consumer of a box unreachable)
- stale stores (aload x is replaced by aconst_null if it's known null)
- simplify jumps (replaces conditional jumps by goto, so may enable goto chains)
unreachable code / DCE (removes instructions of basic blocks to which there is no branch) + enables downstream:
- stale stores (loads may be eliminated, removing consumers of a store)
- empty handlers (try blocks may become empty)
- simplify jumps (goto l; [dead code]; l: ..) => remove goto
- stale local variable descriptors
- (not box-unbox, which is implemented using prod-cons, so it doesn't consider dead code)
note that eliminating empty handlers and stale local variable descriptors is required for
correctness, see the comment in the body of methodOptimizations
.
box-unbox elimination (eliminates box-unbox pairs within the same method) + enables UPSTREAM:
- nullness optimizations (a box extraction operation (unknown nullness) may be rewritten to a read of a non-null local. example in doc comment of box-unbox implementation)
- further box-unbox elimination (e.g. an Integer stored in a Tuple; eliminating the tuple may enable eliminating the Integer) + enables downstream:
- copy propagation (new locals are introduced, may be aliases of existing)
- stale stores (multi-value boxes where not all values are used)
- redundant casts (
("a", "b")._1
: the generic_1
method returnsObject
, a cast to String is added. The cast is redundant after eliminating the tuple.) - empty local variable descriptors (local variables that were holding the box may become unused)
- push-pop (due to artifacts of eliminating runtime type tests on primitives)
copy propagation (replaces LOAD n to the LOAD m for the smallest m that is an alias of n) + enables downstream:
- stale stores (a stored value may not be loaded anymore)
- store-load pairs (a load n may now be right after a store n)
stale stores (replace STORE by POP), rewrites ClassTag(x).newArray
, inlines array_apply/update
+ enables UPSTREAM:
- nullness optimizations (newArray rewrite or inlining may turn things non-null) + enables downstream:
- push-pop (the new pop may be the single consumer for an instruction)
- redundant casts (because rewrites
newArray
, the array type gets more precise)
redundant casts and rewrite some intrinsics: eliminates casts that are statically known to succeed (uses type propagation), rewrites instanceof checks, rewrites intrinsics. + enables UPSTREAM:
- box-unbox elimination (a removed checkcast may be a box consumer)
- copy propagation (a removed checkcast may turn an upcasted local variable into an alias) + enables downstream:
- push-pop for closure allocation elimination (every indyLambda is followed by a checkcast, see scala/bug#9540)
- redundant casts (changing an instanceof to true/false removes branches and can make types of other values more precise)
push-pop (when a POP is the only consumer of a value, remove the POP and its producer) + enables UPSTREAM:
- stale stores (if a LOAD is removed, a corresponding STORE may become stale)
- box-unbox elimination (push-pop may eliminate a closure allocation, rendering a captured box non-escaping)
- redundant casts (Int.unbox(x) is replaced by
x.asInstanceOf[Integer]; pop
) - nullness (
x.intValue
is replaced byif (x == null) throw null
) + enables downstream: - store-load pairs (a variable may become non-live)
- stale handlers (push-pop removes code)
- simplify jumps (push-pop removes code)
store-load pairs (remove STORE x; LOAD x
if x is otherwise not used in the method)
+ enables downstream:
- empty handlers (code is removes, a try block may become empty
- simplify jumps (code is removed, a goto may become redundant for example)
- stale local variable descriptors
empty handlers (removes exception handlers whose try block is empty) + enables UPSTREAM:
- unreachable code (catch block becomes unreachable)
- box-unbox (a box may be escape in an operation in a dead handler) + enables downstream:
- simplify jumps
simplify jumps (various, like GOTO l; l: ...
, see doc comments of individual optimizations)
+ enables UPSTREAM
- unreachable code (
GOTO a; a: GOTO b; b: ...
, the first jump is changed toGOTO b
, the second becomes unreachable) - store-load pairs (a
GOTO l; l: ...
is removed between store and load) - push-pop (
IFNULL l; l: ...
is replaced byPOP
)
The following cleanup optimizations don't enable any upstream optimizations, so they can be executed once at the end, when the above optimizations reach a fixpoint.
empty local variable descriptors (removes unused variables from the local variable table)
empty line numbers (eliminates line number nodes that describe no executable instructions)
At this point, we used to filter out redundant label nodes (sequences of labels without any executable instructions in between). However, this operation is relatively expensive, and unnecessary: labels don't exist in the classfile, they are lowered to bytecode offsets, so redundant labels disappear by design.
Note on a method's maxLocals / maxStack: the backend only uses those values for running Analyzers. The values can be conservative approximations: if an optimization removes code and the maximal stack size is now smaller, the larger maxStack value will still work fine for running an Analyzer (just that frames allocate more space than required). The correct max values written to the bytecode are re-computed during classfile serialization. To keep things simpler, we don't update the max values in every optimization:
- we do it in
removeUnreachableCodeImpl
, because it's quite straightforward - maxLocals is updated in
compactLocalVariables
, which runs at the end of method optimizations
Note on updating the call graph: whenever an optimization eliminates a callsite or a closure instantiation, we eliminate the corresponding entry from the call graph.
- Source
- LocalOpt.scala
- Alphabetic
- By Inheritance
- LocalOpt
- AnyRef
- Any
- by any2stringadd
- by StringFormat
- by Ensuring
- by ArrowAssoc
- Hide All
- Show All
- Public
- Protected
Instance Constructors
- new LocalOpt()
Abstract Value Members
- abstract val postProcessor: PostProcessor
Concrete Value Members
- final def !=(arg0: Any): Boolean
- Definition Classes
- AnyRef → Any
- final def ##: Int
- Definition Classes
- AnyRef → Any
- def +(other: String): String
- def ->[B](y: B): (LocalOpt, B)
- final def ==(arg0: Any): Boolean
- Definition Classes
- AnyRef → Any
- final def asInstanceOf[T0]: T0
- Definition Classes
- Any
- val boxUnbox: BoxUnbox { val postProcessor: LocalOpt.this.postProcessor.type }
- def clone(): AnyRef
- Attributes
- protected[lang]
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.CloneNotSupportedException]) @native()
- val copyProp: CopyProp { val postProcessor: LocalOpt.this.postProcessor.type }
- def eliminateRedundantCastsAndRewriteSomeIntrinsics(method: MethodNode, owner: InternalName): (Boolean, Boolean)
Eliminate
CHECKCAST
instructions that are statically known to succeed.Eliminate
CHECKCAST
instructions that are statically known to succeed. This is safe if the tested object is null:null.asInstanceOf
always succeeds.Replace
INSTANCEOF
instructions withICONST_0/1
if the result is statically known.Since this optimization runs a type analysis, we use it to rewrite some intrinsic method calls
java.lang.reflect.Arrays.getLength(x)
whenx
is statically known to be an array: rewrite toARRAYLENGTH
x.getClass
whenx
is statically known to be a primitive array. Rewrite toLDC
.
The type of the tested object is determined using a NonLubbingTypeFlowAnalyzer. Note that this analysis collapses LUBs of non-equal references types to Object for simplicity. Example: given
B <: A <: Object
, the cast in(if (..) new B else new A).asInstanceOf[A]
would not be eliminated.Note: to rewrite
INSTANCEOF
tests, we also run a nullness analyzer. We need to know nullness becausenull.isInstanceOf
is alwaysfalse
.Returns two booleans (typeInsnChanged, intrinsicRewritten)
- def ensuring(cond: (LocalOpt) => Boolean, msg: => Any): LocalOpt
- def ensuring(cond: (LocalOpt) => Boolean): LocalOpt
- def ensuring(cond: Boolean, msg: => Any): LocalOpt
- def ensuring(cond: Boolean): LocalOpt
- final def eq(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef
- def equals(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef → Any
- def finalize(): Unit
- Attributes
- protected[lang]
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.Throwable])
- final def getClass(): Class[_ <: AnyRef]
- Definition Classes
- AnyRef → Any
- Annotations
- @native()
- def hashCode(): Int
- Definition Classes
- AnyRef → Any
- Annotations
- @native()
- final def isInstanceOf[T0]: Boolean
- Definition Classes
- Any
- def methodOptimizations(method: MethodNode, ownerClassName: InternalName): Boolean
Run method-level optimizations, see comment on class LocalOpt.
Run method-level optimizations, see comment on class LocalOpt.
Returns
true
if the bytecode ofmethod
was changed. - def methodOptimizations(clazz: ClassNode): Boolean
Remove unreachable instructions from all (non-abstract) methods and apply various other cleanups to the bytecode.
Remove unreachable instructions from all (non-abstract) methods and apply various other cleanups to the bytecode.
- clazz
The class whose methods are optimized
- returns
true
if unreachable code was eliminated in some method,false
otherwise.
- def minimalRemoveUnreachableCode(method: MethodNode, ownerClassName: InternalName): Boolean
Remove unreachable code from a method.
Remove unreachable code from a method.
This implementation only removes instructions that are unreachable for an ASM analyzer / interpreter. This ensures that future analyses will not produce
null
frames. The inliner depends on this property.- returns
A set containing the eliminated instructions
- final def ne(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef
- final def notify(): Unit
- Definition Classes
- AnyRef
- Annotations
- @native()
- final def notifyAll(): Unit
- Definition Classes
- AnyRef
- Annotations
- @native()
- def nullnessOptimizations(method: MethodNode, ownerClassName: InternalName): Boolean
Apply various optimizations based on nullness analysis information.
Apply various optimizations based on nullness analysis information.
- IFNULL / IFNONNULL are rewritten to GOTO if nullness is known
- IF_ACMPEQ / IF_ACMPNE are rewritten to GOTO if the both references are known null, or if one is known null and the other known not-null
- ALOAD is replaced by ACONST_NULL if the local is known to hold null
- ASTORE of null is removed if the local is known to hold null
- INSTANCEOF of null is replaced by
ICONST_0
- scala.runtime.BoxesRunTime.unboxToX(null) is rewritten to a zero-value load
- def removeUnreachableCodeImpl(method: MethodNode, ownerClassName: InternalName): Boolean
Removes unreachable basic blocks, returns
true
if instructions were removed.Removes unreachable basic blocks, returns
true
if instructions were removed.When this method returns, each
labelNode.getLabel
has a status set whether the label is live or not. This can be queried usingBackendUtils.isLabelReachable
. - final def synchronized[T0](arg0: => T0): T0
- Definition Classes
- AnyRef
- def toString(): String
- Definition Classes
- AnyRef → Any
- final def wait(): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException])
- final def wait(arg0: Long, arg1: Int): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException])
- final def wait(arg0: Long): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException]) @native()
Deprecated Value Members
- def formatted(fmtstr: String): String
- Implicit
- This member is added by an implicit conversion from LocalOpt toStringFormat[LocalOpt] performed by method StringFormat in scala.Predef.
- Definition Classes
- StringFormat
- Annotations
- @deprecated @inline()
- Deprecated
(Since version 2.12.16) Use
formatString.format(value)
instead ofvalue.formatted(formatString)
, or use thef""
string interpolator. In Java 15 and later,formatted
resolves to the new method in String which has reversed parameters.
- def →[B](y: B): (LocalOpt, B)
- Implicit
- This member is added by an implicit conversion from LocalOpt toArrowAssoc[LocalOpt] performed by method ArrowAssoc in scala.Predef.
- Definition Classes
- ArrowAssoc
- Annotations
- @deprecated
- Deprecated
(Since version 2.13.0) Use
->
instead. If you still wish to display it as one character, consider using a font with programming ligatures such as Fira Code.
The Scala compiler and reflection APIs.