summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@gmail.com>2015-07-02 20:17:57 +0200
committerLukas Rytz <lukas.rytz@gmail.com>2015-07-03 10:42:52 +0200
commit055a373802a34ee09fc0ed20b2b25c3fa20507d4 (patch)
treea4ae2c969fcea60726796f552e5bbdffe36d191c /src
parent6ae2dd8dc4556e8085710122097c849fdeac6d95 (diff)
downloadscala-055a373802a34ee09fc0ed20b2b25c3fa20507d4.tar.gz
scala-055a373802a34ee09fc0ed20b2b25c3fa20507d4.tar.bz2
scala-055a373802a34ee09fc0ed20b2b25c3fa20507d4.zip
SI-9376 don't crash when inlining a closure body that throws.
If the closure body method has return type Nothing$, add an `ATHROW` instruction after the callsite. This is required for computing stack map frames, as explained in a comment in BCodeBodyBuilder.adapt. Similar for closure bodies with return type Null$.
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala1
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala22
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala4
3 files changed, 25 insertions, 2 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
index c3f71969f6..063fb81d46 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
@@ -843,7 +843,6 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
*
* New (http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1)
* - Requires consistent stack map frames. GenBCode always generates stack frames.
- * or higher.
* - In practice: the ASM library computes stack map frames for us (ClassWriter). Emitting
* correct frames after an ATHROW is probably complex, so ASM uses the following strategy:
* - Every time when generating an ATHROW, a new basic block is started.
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
index 0ec550981a..cd36fd8bba 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
@@ -12,7 +12,7 @@ import scala.collection.mutable
import scala.reflect.internal.util.Collections._
import scala.tools.asm.commons.CodeSizeEvaluator
import scala.tools.asm.tree.analysis._
-import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes}
+import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes, Type}
import scala.tools.asm.tree._
import GenBCode._
import scala.collection.convert.decorateAsScala._
@@ -331,6 +331,26 @@ object BytecodeUtils {
}
/**
+ * This method is used by optimizer components to eliminate phantom values of instruction
+ * that load a value of type `Nothing$` or `Null$`. Such values on the stack don't interact well
+ * with stack map frames.
+ *
+ * For example, `opt.getOrElse(throw e)` is re-written to an invocation of the lambda body, a
+ * method with return type `Nothing$`. Similarly for `opt.getOrElse(null)` and `Null$`.
+ *
+ * During bytecode generation this is handled by BCodeBodyBuilder.adapt. See the comment in that
+ * method which explains the issue with such phantom values.
+ */
+ def fixLoadedNothingOrNullValue(loadedType: Type, loadInstr: AbstractInsnNode, methodNode: MethodNode, bTypes: BTypes): Unit = {
+ if (loadedType == bTypes.coreBTypes.RT_NOTHING.toASMType) {
+ methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ATHROW))
+ } else if (loadedType == bTypes.coreBTypes.RT_NULL.toASMType) {
+ methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ACONST_NULL))
+ methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.POP))
+ }
+ }
+
+ /**
* A wrapper to make ASM's Analyzer a bit easier to use.
*/
class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, interpreter: Interpreter[V] = new BasicInterpreter) {
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
index 1648a53ed8..743a454678 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
@@ -283,6 +283,10 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
val isInterface = bodyOpcode == INVOKEINTERFACE
val bodyInvocation = new MethodInsnNode(bodyOpcode, lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc, isInterface)
methodNode.instructions.insertBefore(invocation, bodyInvocation)
+
+ val returnType = Type.getReturnType(lambdaBodyHandle.getDesc)
+ fixLoadedNothingOrNullValue(returnType, bodyInvocation, methodNode, btypes) // see comment of that method
+
methodNode.instructions.remove(invocation)
// update the call graph