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

Commit 460e10c

Browse files
committed
Address review feedback
Address feedback in scala#4516 / 57b8da4. Save allocations of NullnessValue - there's only 4 possible instances. Also save tuple allocations in InstructionStackEffect.
1 parent 53a274e commit 460e10c

File tree

4 files changed

+109
-69
lines changed

4 files changed

+109
-69
lines changed

src/compiler/scala/tools/nsc/backend/jvm/analysis/AliasingFrame.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc
3838
/**
3939
* Returns the indices of the values array which are aliases of the object `id`.
4040
*/
41-
def valuesWithAliasId(id: Long): Set[Int] = immutable.BitSet.empty ++ aliasIds.indices.filter(i => aliasId(i) == id)
41+
def valuesWithAliasId(id: Long): Set[Int] = immutable.BitSet.empty ++ aliasIds.indices.iterator.filter(i => aliasId(i) == id)
4242

4343
/**
4444
* The set of aliased values for a given entry in the `values` array.
@@ -71,7 +71,11 @@ class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLoc
7171
def stackTop: Int = this.stackTop
7272
def peekStack(n: Int): V = this.peekStack(n)
7373

74-
val (consumed, produced) = InstructionStackEffect(insn, this) // needs to be called before super.execute, see its doc
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+
7579
super.execute(insn, interpreter)
7680

7781
(insn.getOpcode: @switch) match {

src/compiler/scala/tools/nsc/backend/jvm/analysis/InstructionStackEffect.scala

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,26 @@ import scala.tools.asm.Type
88
import scala.tools.asm.tree.{MultiANewArrayInsnNode, InvokeDynamicInsnNode, MethodInsnNode, AbstractInsnNode}
99
import scala.tools.asm.tree.analysis.{Frame, Value}
1010
import opt.BytecodeUtils._
11+
import collection.immutable
1112

1213
object InstructionStackEffect {
14+
private var cache: immutable.IntMap[(Int, Int)] = immutable.IntMap.empty
15+
private def t(x: Int, y: Int): (Int, Int) = {
16+
// x can go up to 255 (number of parameters of a method, dimensions in multianewarray) we cache
17+
// x up to 10, which covers most cases and limits the cache. y doesn't go above 6 (see cases).
18+
if (x > 10 || y > 6) (x, y)
19+
else {
20+
val key = (x << 8) + y // this would work for any x < 256
21+
if (cache contains key) {
22+
cache(key)
23+
} else {
24+
val r = (x, y)
25+
cache += key -> r
26+
r
27+
}
28+
}
29+
}
30+
1331
/**
1432
* Returns a pair with the number of stack values consumed and produced by `insn`.
1533
* This method requires the `frame` to be in the state **before** executing / interpreting
@@ -20,7 +38,7 @@ object InstructionStackEffect {
2038

2139
(insn.getOpcode: @switch) match {
2240
// The order of opcodes is the same as in Frame.execute.
23-
case NOP => (0, 0)
41+
case NOP => t(0, 0)
2442

2543
case ACONST_NULL |
2644
ICONST_M1 |
@@ -44,7 +62,7 @@ object InstructionStackEffect {
4462
LLOAD |
4563
FLOAD |
4664
DLOAD |
47-
ALOAD => (0, 1)
65+
ALOAD => t(0, 1)
4866

4967
case IALOAD |
5068
LALOAD |
@@ -53,13 +71,13 @@ object InstructionStackEffect {
5371
AALOAD |
5472
BALOAD |
5573
CALOAD |
56-
SALOAD => (2, 1)
74+
SALOAD => t(2, 1)
5775

5876
case ISTORE |
5977
LSTORE |
6078
FSTORE |
6179
DSTORE |
62-
ASTORE => (1, 0)
80+
ASTORE => t(1, 0)
6381

6482
case IASTORE |
6583
LASTORE |
@@ -68,41 +86,41 @@ object InstructionStackEffect {
6886
AASTORE |
6987
BASTORE |
7088
CASTORE |
71-
SASTORE => (3, 0)
89+
SASTORE => t(3, 0)
7290

73-
case POP => (1, 0)
91+
case POP => t(1, 0)
7492

7593
case POP2 =>
7694
val isSize2 = peekStack(0).getSize == 2
77-
if (isSize2) (1, 0) else (2, 0)
95+
if (isSize2) t(1, 0) else t(2, 0)
7896

79-
case DUP => (0, 1)
97+
case DUP => t(0, 1)
8098

81-
case DUP_X1 => (2, 3)
99+
case DUP_X1 => t(2, 3)
82100

83101
case DUP_X2 =>
84102
val isSize2 = peekStack(1).getSize == 2
85-
if (isSize2) (2, 3) else (3, 4)
103+
if (isSize2) t(2, 3) else t(3, 4)
86104

87105
case DUP2 =>
88106
val isSize2 = peekStack(0).getSize == 2
89-
if (isSize2) (0, 1) else (0, 2)
107+
if (isSize2) t(0, 1) else t(0, 2)
90108

91109
case DUP2_X1 =>
92110
val isSize2 = peekStack(0).getSize == 2
93-
if (isSize2) (2, 3) else (3, 4)
111+
if (isSize2) t(2, 3) else t(3, 4)
94112

95113
case DUP2_X2 =>
96114
val v1isSize2 = peekStack(0).getSize == 2
97115
if (v1isSize2) {
98116
val v2isSize2 = peekStack(1).getSize == 2
99-
if (v2isSize2) (2, 3) else (3, 4)
117+
if (v2isSize2) t(2, 3) else t(3, 4)
100118
} else {
101119
val v3isSize2 = peekStack(2).getSize == 2
102-
if (v3isSize2) (3, 5) else (4, 6)
120+
if (v3isSize2) t(3, 5) else t(4, 6)
103121
}
104122

105-
case SWAP => (2, 2)
123+
case SWAP => t(2, 2)
106124

107125
case IADD |
108126
LADD |
@@ -123,12 +141,12 @@ object InstructionStackEffect {
123141
IREM |
124142
LREM |
125143
FREM |
126-
DREM => (2, 1)
144+
DREM => t(2, 1)
127145

128146
case INEG |
129147
LNEG |
130148
FNEG |
131-
DNEG => (1, 1)
149+
DNEG => t(1, 1)
132150

133151
case ISHL |
134152
LSHL |
@@ -141,9 +159,9 @@ object InstructionStackEffect {
141159
IOR |
142160
LOR |
143161
IXOR |
144-
LXOR => (2, 1)
162+
LXOR => t(2, 1)
145163

146-
case IINC => (0, 0)
164+
case IINC => t(0, 0)
147165

148166
case I2L |
149167
I2F |
@@ -159,20 +177,20 @@ object InstructionStackEffect {
159177
D2F |
160178
I2B |
161179
I2C |
162-
I2S => (1, 1)
180+
I2S => t(1, 1)
163181

164182
case LCMP |
165183
FCMPL |
166184
FCMPG |
167185
DCMPL |
168-
DCMPG => (2, 1)
186+
DCMPG => t(2, 1)
169187

170188
case IFEQ |
171189
IFNE |
172190
IFLT |
173191
IFGE |
174192
IFGT |
175-
IFLE => (1, 0)
193+
IFLE => t(1, 0)
176194

177195
case IF_ICMPEQ |
178196
IF_ICMPNE |
@@ -181,32 +199,32 @@ object InstructionStackEffect {
181199
IF_ICMPGT |
182200
IF_ICMPLE |
183201
IF_ACMPEQ |
184-
IF_ACMPNE => (2, 0)
202+
IF_ACMPNE => t(2, 0)
185203

186-
case GOTO => (0, 0)
204+
case GOTO => t(0, 0)
187205

188-
case JSR => (0, 1)
206+
case JSR => t(0, 1)
189207

190-
case RET => (0, 0)
208+
case RET => t(0, 0)
191209

192210
case TABLESWITCH |
193-
LOOKUPSWITCH => (1, 0)
211+
LOOKUPSWITCH => t(1, 0)
194212

195213
case IRETURN |
196214
LRETURN |
197215
FRETURN |
198216
DRETURN |
199-
ARETURN => (1, 0) // Frame.execute consumes one stack value
217+
ARETURN => t(1, 0) // Frame.execute consumes one stack value
200218

201-
case RETURN => (0, 0) // Frame.execute does not change the stack
219+
case RETURN => t(0, 0) // Frame.execute does not change the stack
202220

203-
case GETSTATIC => (0, 1)
221+
case GETSTATIC => t(0, 1)
204222

205-
case PUTSTATIC => (1, 0)
223+
case PUTSTATIC => t(1, 0)
206224

207-
case GETFIELD => (1, 1)
225+
case GETFIELD => t(1, 1)
208226

209-
case PUTFIELD => (2, 0)
227+
case PUTFIELD => t(2, 0)
210228

211229
case INVOKEVIRTUAL |
212230
INVOKESPECIAL |
@@ -215,33 +233,33 @@ object InstructionStackEffect {
215233
val desc = insn.asInstanceOf[MethodInsnNode].desc
216234
val cons = Type.getArgumentTypes(desc).length + (if (insn.getOpcode == INVOKESTATIC) 0 else 1)
217235
val prod = if (Type.getReturnType(desc) == Type.VOID_TYPE) 0 else 1
218-
(cons, prod)
236+
t(cons, prod)
219237

220238
case INVOKEDYNAMIC =>
221239
val desc = insn.asInstanceOf[InvokeDynamicInsnNode].desc
222240
val cons = Type.getArgumentTypes(desc).length
223241
val prod = if (Type.getReturnType(desc) == Type.VOID_TYPE) 0 else 1
224-
(cons, prod)
242+
t(cons, prod)
225243

226-
case NEW => (0, 1)
244+
case NEW => t(0, 1)
227245

228246
case NEWARRAY |
229247
ANEWARRAY |
230-
ARRAYLENGTH => (1, 1)
248+
ARRAYLENGTH => t(1, 1)
231249

232-
case ATHROW => (1, 0) // Frame.execute consumes one stack value
250+
case ATHROW => t(1, 0) // Frame.execute consumes one stack value
233251

234-
case CHECKCAST => (0, 0)
252+
case CHECKCAST => t(0, 0)
235253

236-
case INSTANCEOF => (1, 1)
254+
case INSTANCEOF => t(1, 1)
237255

238256
case MONITORENTER |
239-
MONITOREXIT => (1, 0)
257+
MONITOREXIT => t(1, 0)
240258

241-
case MULTIANEWARRAY => (insn.asInstanceOf[MultiANewArrayInsnNode].dims, 1)
259+
case MULTIANEWARRAY => t(insn.asInstanceOf[MultiANewArrayInsnNode].dims, 1)
242260

243261
case IFNULL |
244-
IFNONNULL => (1, 0)
262+
IFNONNULL => t(1, 0)
245263
}
246264
}
247265

src/compiler/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzer.scala

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -100,25 +100,43 @@ case object Null extends Nullness
100100
* Represents the nullness state for a local variable or stack value.
101101
*
102102
* Note that nullness of primitive values is not tracked, it will be always [[Unknown]].
103-
*
104-
* @param nullness The nullness of this value.
105-
* @param longOrDouble True if this value is a long or double. The Analyzer framework needs to know
106-
* the size of each value when interpreting instructions, see `Frame.execute`.
107103
*/
108-
final case class NullnessValue(nullness: Nullness, longOrDouble: Boolean) extends Value {
109-
def this(nullness: Nullness, insn: AbstractInsnNode) = this(nullness, longOrDouble = BytecodeUtils.instructionResultSize(insn) == 2)
104+
sealed trait NullnessValue extends Value {
105+
/**
106+
* The nullness of this value.
107+
*/
108+
def nullness: Nullness
110109

110+
/**
111+
* True if this value is a long or double. The Analyzer framework needs to know
112+
* the size of each value when interpreting instructions, see `Frame.execute`.
113+
*/
114+
def isSize2: Boolean
111115
/**
112116
* The size of the slot described by this value. Cannot be 0 because no values are allocated
113117
* for void-typed slots, see NullnessInterpreter.newValue.
114118
**/
115-
def getSize: Int = if (longOrDouble) 2 else 1
119+
def getSize: Int = if (isSize2) 2 else 1
116120

117-
def merge(other: NullnessValue) = NullnessValue(nullness merge other.nullness, longOrDouble)
121+
def merge(other: NullnessValue) = NullnessValue(nullness merge other.nullness, isSize2)
118122
}
119123

124+
object NullValue extends NullnessValue { def nullness = Null; def isSize2 = false; override def toString = "Null" }
125+
object UnknownValue1 extends NullnessValue { def nullness = Unknown; def isSize2 = false; override def toString = "Unknown1" }
126+
object UnknownValue2 extends NullnessValue { def nullness = Unknown; def isSize2 = true; override def toString = "Unknown2" }
127+
object NotNullValue extends NullnessValue { def nullness = NotNull; def isSize2 = false; override def toString = "NotNull" }
128+
120129
object NullnessValue {
121-
def apply(nullness: Nullness, insn: AbstractInsnNode) = new NullnessValue(nullness, insn)
130+
def apply(nullness: Nullness, isSize2: Boolean): NullnessValue = {
131+
if (nullness == Null) NullValue
132+
else if (nullness == NotNull) NotNullValue
133+
else if (isSize2) UnknownValue2
134+
else UnknownValue1
135+
}
136+
137+
def apply(nullness: Nullness, insn: AbstractInsnNode): NullnessValue = {
138+
apply(nullness, isSize2 = BytecodeUtils.instructionResultSize(insn) == 2)
139+
}
122140
}
123141

124142
final class NullnessInterpreter extends Interpreter[NullnessValue](Opcodes.ASM5) {
@@ -133,12 +151,12 @@ final class NullnessInterpreter extends Interpreter[NullnessValue](Opcodes.ASM5)
133151
// (2) `tp` may also be `null`. When creating the initial frame, the analyzer invokes
134152
// `newValue(null)` for each local variable. We have to return a value of size 1.
135153
if (tp == Type.VOID_TYPE) null // (1)
136-
else NullnessValue(Unknown, longOrDouble = tp != null /*(2)*/ && tp.getSize == 2 )
154+
else NullnessValue(Unknown, isSize2 = tp != null /*(2)*/ && tp.getSize == 2 )
137155
}
138156

139157
override def newParameterValue(isInstanceMethod: Boolean, local: Int, tp: Type): NullnessValue = {
140158
// For instance methods, the `this` parameter is known to be not null.
141-
if (isInstanceMethod && local == 0) NullnessValue(NotNull, longOrDouble = false)
159+
if (isInstanceMethod && local == 0) NullnessValue(NotNull, isSize2 = false)
142160
else super.newParameterValue(isInstanceMethod, local, tp)
143161
}
144162

@@ -162,7 +180,7 @@ final class NullnessInterpreter extends Interpreter[NullnessValue](Opcodes.ASM5)
162180

163181
def unaryOperation(insn: AbstractInsnNode, value: NullnessValue): NullnessValue = (insn.getOpcode: @switch) match {
164182
case Opcodes.NEWARRAY |
165-
Opcodes.ANEWARRAY => NullnessValue(NotNull, longOrDouble = false)
183+
Opcodes.ANEWARRAY => NullnessValue(NotNull, isSize2 = false)
166184

167185
case _ => NullnessValue(Unknown, insn)
168186
}
@@ -172,12 +190,12 @@ final class NullnessInterpreter extends Interpreter[NullnessValue](Opcodes.ASM5)
172190
}
173191

174192
def ternaryOperation(insn: AbstractInsnNode, value1: NullnessValue, value2: NullnessValue, value3: NullnessValue): NullnessValue = {
175-
NullnessValue(Unknown, longOrDouble = false)
193+
NullnessValue(Unknown, isSize2 = false)
176194
}
177195

178196
def naryOperation(insn: AbstractInsnNode, values: util.List[_ <: NullnessValue]): NullnessValue = (insn.getOpcode: @switch) match {
179197
case Opcodes.MULTIANEWARRAY =>
180-
NullnessValue(NotNull, longOrDouble = false)
198+
NullnessValue(NotNull, isSize2 = false)
181199

182200
case _ =>
183201
// TODO: use a list of methods that are known to return non-null values
@@ -247,7 +265,7 @@ class NullnessFrame(nLocals: Int, nStack: Int) extends AliasingFrame[NullnessVal
247265

248266
if (nullCheckedAliasId != -1) {
249267
for (i <- valuesWithAliasId(nullCheckedAliasId))
250-
this.setValue(i, this.getValue(i).copy(nullness = NotNull))
268+
this.setValue(i, NotNullValue)
251269
}
252270
}
253271
}

0 commit comments

Comments
 (0)