Skip to content
This repository was archived by the owner on Sep 1, 2020. It is now read-only.

Commit 6d0a498

Browse files
committed
Merge pull request scala#4519 from lrytz/opt/nullness-2.11
Nullness Analysis for GenBCode
2 parents 0bc7fa6 + b2a78b3 commit 6d0a498

File tree

13 files changed

+1364
-26
lines changed

13 files changed

+1364
-26
lines changed
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package scala.tools.nsc
2+
package backend.jvm
3+
package analysis
4+
5+
import scala.annotation.switch
6+
import scala.collection.{mutable, immutable}
7+
import scala.tools.asm.Opcodes
8+
import scala.tools.asm.tree._
9+
import scala.tools.asm.tree.analysis.{Analyzer, Value, Frame, Interpreter}
10+
import opt.BytecodeUtils._
11+
12+
object AliasingFrame {
13+
private var _idCounter: Long = 0l
14+
private def nextId = { _idCounter += 1; _idCounter }
15+
}
16+
17+
class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLocals, nStack) {
18+
import Opcodes._
19+
20+
// Auxiliary constructor required for implementing `AliasingAnalyzer.newFrame`
21+
def this(src: Frame[_ <: V]) {
22+
this(src.getLocals, src.getMaxStackSize)
23+
init(src)
24+
}
25+
26+
/**
27+
* For each slot (entry in the `values` array of the frame), an id that uniquely represents
28+
* the object stored in it. If two values have the same id, they are aliases of the same
29+
* object.
30+
*/
31+
private val aliasIds: Array[Long] = Array.fill(nLocals + nStack)(AliasingFrame.nextId)
32+
33+
/**
34+
* The object alias id of for a value index.
35+
*/
36+
def aliasId(entry: Int) = aliasIds(entry)
37+
38+
/**
39+
* Returns the indices of the values array which are aliases of the object `id`.
40+
*/
41+
def valuesWithAliasId(id: Long): Set[Int] = immutable.BitSet.empty ++ aliasIds.indices.iterator.filter(i => aliasId(i) == id)
42+
43+
/**
44+
* The set of aliased values for a given entry in the `values` array.
45+
*/
46+
def aliasesOf(entry: Int): Set[Int] = valuesWithAliasId(aliasIds(entry))
47+
48+
/**
49+
* Define a new alias. For example, given
50+
* var a = this // this, a have the same aliasId
51+
* then an assignment
52+
* b = a
53+
* will set the same the aliasId for `b`.
54+
*/
55+
private def newAlias(assignee: Int, source: Int): Unit = {
56+
aliasIds(assignee) = aliasIds(source)
57+
}
58+
59+
/**
60+
* An assignment
61+
* a = someUnknownValue()
62+
* sets a fresh alias id for `a`.
63+
* A stack value is also removed from its alias set when being consumed.
64+
*/
65+
private def removeAlias(assignee: Int): Unit = {
66+
aliasIds(assignee) = AliasingFrame.nextId
67+
}
68+
69+
override def execute(insn: AbstractInsnNode, interpreter: Interpreter[V]): Unit = {
70+
// Make the extendsion methods easier to use (otherwise we have to repeat `this`.stackTop)
71+
def stackTop: Int = this.stackTop
72+
def peekStack(n: Int): V = this.peekStack(n)
73+
74+
// the val pattern `val (p, c) = f` still allocates a tuple (https://github.com/scala-opt/scala/issues/28)
75+
val prodCons = InstructionStackEffect(insn, this) // needs to be called before super.execute, see its doc
76+
val consumed = prodCons._1
77+
val produced = prodCons._2
78+
79+
super.execute(insn, interpreter)
80+
81+
(insn.getOpcode: @switch) match {
82+
case ALOAD =>
83+
newAlias(assignee = stackTop, source = insn.asInstanceOf[VarInsnNode].`var`)
84+
85+
case DUP =>
86+
val top = stackTop
87+
newAlias(assignee = top, source = top - 1)
88+
89+
case DUP_X1 =>
90+
val top = stackTop
91+
newAlias(assignee = top, source = top - 1)
92+
newAlias(assignee = top - 1, source = top - 2)
93+
newAlias(assignee = top - 2, source = top)
94+
95+
case DUP_X2 =>
96+
// Check if the second element on the stack is size 2
97+
// https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dup_x2
98+
val isSize2 = peekStack(1).getSize == 2
99+
val top = stackTop
100+
newAlias(assignee = top, source = top - 1)
101+
newAlias(assignee = top - 1, source = top - 2)
102+
if (isSize2) {
103+
// Size 2 values on the stack only take one slot in the `values` array
104+
newAlias(assignee = top - 2, source = top)
105+
} else {
106+
newAlias(assignee = top - 2, source = top - 3)
107+
newAlias(assignee = top - 3, source = top)
108+
}
109+
110+
case DUP2 =>
111+
val isSize2 = peekStack(0).getSize == 2
112+
val top = stackTop
113+
if (isSize2) {
114+
newAlias(assignee = top, source = top - 1)
115+
} else {
116+
newAlias(assignee = top - 1, source = top - 3)
117+
newAlias(assignee = top, source = top - 2)
118+
}
119+
120+
case DUP2_X1 =>
121+
val isSize2 = peekStack(0).getSize == 2
122+
val top = stackTop
123+
if (isSize2) {
124+
newAlias(assignee = top, source = top - 1)
125+
newAlias(assignee = top - 1, source = top - 2)
126+
newAlias(assignee = top - 2, source = top)
127+
} else {
128+
newAlias(assignee = top, source = top - 2)
129+
newAlias(assignee = top - 1, source = top - 3)
130+
newAlias(assignee = top - 2, source = top - 4)
131+
newAlias(assignee = top - 4, source = top)
132+
newAlias(assignee = top - 5, source = top - 1)
133+
}
134+
135+
case DUP2_X2 =>
136+
val top = stackTop
137+
// https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dup2_x2
138+
val v1isSize2 = peekStack(0).getSize == 2
139+
if (v1isSize2) {
140+
newAlias(assignee = top, source = top - 1)
141+
newAlias(assignee = top - 1, source = top - 2)
142+
val v2isSize2 = peekStack(1).getSize == 2
143+
if (v2isSize2) {
144+
// Form 4
145+
newAlias(assignee = top - 2, source = top)
146+
} else {
147+
// Form 2
148+
newAlias(assignee = top - 2, source = top - 3)
149+
newAlias(assignee = top - 3, source = top)
150+
}
151+
} else {
152+
newAlias(assignee = top, source = top - 2)
153+
newAlias(assignee = top - 1, source = top - 3)
154+
newAlias(assignee = top - 2, source = top - 4)
155+
val v3isSize2 = peekStack(2).getSize == 2
156+
if (v3isSize2) {
157+
// Form 3
158+
newAlias(assignee = top - 3, source = top)
159+
newAlias(assignee = top - 4, source = top - 1)
160+
} else {
161+
// Form 1
162+
newAlias(assignee = top - 3, source = top - 5)
163+
newAlias(assignee = top - 4, source = top)
164+
newAlias(assignee = top - 5, source = top - 1)
165+
}
166+
}
167+
168+
case SWAP =>
169+
val top = stackTop
170+
val idTop = aliasIds(top)
171+
aliasIds(top) = aliasIds(top - 1)
172+
aliasIds(top - 1) = idTop
173+
174+
case opcode =>
175+
if (opcode == ASTORE) {
176+
// Not a separate case because we need to remove the consumed stack value from alias sets after.
177+
val stackTopBefore = stackTop - produced + consumed
178+
val local = insn.asInstanceOf[VarInsnNode].`var`
179+
newAlias(assignee = local, source = stackTopBefore)
180+
// if the value written is size 2, it overwrites the subsequent slot, which is then no
181+
// longer an alias of anything. see the corresponding case in `Frame.execute`.
182+
if (getLocal(local).getSize == 2)
183+
removeAlias(local + 1)
184+
185+
// if the value at the preceding index is size 2, it is no longer valid, so we remove its
186+
// aliasing. see corresponding case in `Frame.execute`
187+
if (local > 0) {
188+
val precedingValue = getLocal(local - 1)
189+
if (precedingValue != null && precedingValue.getSize == 2)
190+
removeAlias(local - 1)
191+
}
192+
}
193+
194+
// Remove consumed stack values from aliasing sets.
195+
// Example: iadd
196+
// - before: local1, local2, stack1, consumed1, consumed2
197+
// - after: local1, local2, stack1, produced1 // stackTop = 3
198+
val firstConsumed = stackTop - produced + 1 // firstConsumed = 3
199+
for (i <- 0 until consumed)
200+
removeAlias(firstConsumed + i) // remove aliases for 3 and 4
201+
202+
// We don't need to set the aliases ids for the produced values: the aliasIds array already
203+
// contains fresh ids for non-used stack values (ensured by removeAlias).
204+
}
205+
}
206+
207+
/**
208+
* Merge the AliasingFrame `other` into this AliasingFrame.
209+
*
210+
* Aliases that are common in both frames are kept. Example:
211+
*
212+
* var x, y = null
213+
* if (...) {
214+
* x = a
215+
* y = a // (x, y, a) are aliases
216+
* } else {
217+
* x = a
218+
* y = b // (x, a) and (y, b)
219+
* }
220+
* [...] // (x, a)
221+
*/
222+
override def merge(other: Frame[_ <: V], interpreter: Interpreter[V]): Boolean = {
223+
val valuesChanged = super.merge(other, interpreter)
224+
var aliasesChanged = false
225+
val aliasingOther = other.asInstanceOf[AliasingFrame[_]]
226+
for (i <- aliasIds.indices) {
227+
val thisAliases = aliasesOf(i)
228+
val thisNotOther = thisAliases diff (thisAliases intersect aliasingOther.aliasesOf(i))
229+
if (thisNotOther.nonEmpty) {
230+
aliasesChanged = true
231+
thisNotOther foreach removeAlias
232+
}
233+
}
234+
valuesChanged || aliasesChanged
235+
}
236+
237+
override def init(src: Frame[_ <: V]): Frame[V] = {
238+
super.init(src)
239+
compat.Platform.arraycopy(src.asInstanceOf[AliasingFrame[_]].aliasIds, 0, aliasIds, 0, aliasIds.length)
240+
this
241+
}
242+
}
243+
244+
/**
245+
* An analyzer that uses AliasingFrames instead of bare Frames. This can be used when an analysis
246+
* needs to track aliases, but doesn't require a more specific Frame subclass.
247+
*/
248+
class AliasingAnalyzer[V <: Value](interpreter: Interpreter[V]) extends Analyzer[V](interpreter) {
249+
override def newFrame(nLocals: Int, nStack: Int): AliasingFrame[V] = new AliasingFrame(nLocals, nStack)
250+
override def newFrame(src: Frame[_ <: V]): AliasingFrame[V] = new AliasingFrame(src)
251+
}

0 commit comments

Comments
 (0)