Skip to content

Commit 8715766

Browse files
committed
Add label optimization tests
1 parent 1b6f182 commit 8715766

File tree

1 file changed

+273
-0
lines changed

1 file changed

+273
-0
lines changed
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
package dotty.tools.backend.jvm
2+
3+
import scala.language.unsafeNulls
4+
5+
import org.junit.Assert._
6+
import org.junit.Test
7+
8+
import scala.tools.asm
9+
import asm._
10+
import asm.tree._
11+
12+
import scala.tools.asm.Opcodes
13+
import scala.jdk.CollectionConverters._
14+
import Opcodes._
15+
16+
class LabelBytecodeTests extends DottyBytecodeTest {
17+
import ASMConverters._
18+
19+
@Test def labelBreak = {
20+
testLabelBytecode(
21+
"""val local = boundary.Label[Long]()
22+
|try throw boundary.Break[Long](local, 5L)
23+
|catch case ex: boundary.Break[Long] @unchecked =>
24+
| if ex.label eq local then ex.value
25+
| else throw ex
26+
""".stripMargin,
27+
"Long",
28+
Ldc(LDC, 5),
29+
Op(LRETURN)
30+
)
31+
}
32+
33+
@Test def simpleBoundaryBreak = {
34+
testLabelBytecode(
35+
"""boundary: l ?=>
36+
| l.break(2)
37+
""".stripMargin,
38+
"Int",
39+
Op(ICONST_2),
40+
Op(IRETURN)
41+
)
42+
43+
testLabelBytecode(
44+
"""boundary:
45+
| break(3)
46+
""".stripMargin,
47+
"Int",
48+
Op(ICONST_3),
49+
Op(IRETURN)
50+
)
51+
52+
testLabelBytecode(
53+
"""boundary:
54+
| break()
55+
""".stripMargin,
56+
"Unit",
57+
Field(GETSTATIC, "scala/runtime/BoxedUnit", "UNIT", "Lscala/runtime/BoxedUnit;"),
58+
VarOp(ASTORE, 1),
59+
Op(RETURN)
60+
)
61+
}
62+
63+
@Test def labelExtraction = {
64+
// Test extra Inlined around the label
65+
testLabelBytecode(
66+
"""boundary:
67+
| summon[boundary.Label[Int]].break(2)
68+
""".stripMargin,
69+
"Int",
70+
Op(ICONST_2),
71+
Op(IRETURN)
72+
)
73+
74+
// Test extra Block around the label
75+
testLabelBytecode(
76+
"""boundary: l ?=>
77+
| { l }.break(2)
78+
""".stripMargin,
79+
"Int",
80+
Op(ICONST_2),
81+
Op(IRETURN)
82+
)
83+
}
84+
85+
@Test def boundaryLocalBreak = {
86+
testLabelBytecode(
87+
"""val x: Boolean = true
88+
|boundary[Unit, Unit]:
89+
| var i = 0
90+
| while true do
91+
| i += 1
92+
| if i > 10 then break()
93+
""".stripMargin,
94+
"Unit",
95+
Op(ICONST_1),
96+
VarOp(ISTORE, 1),
97+
Op(ICONST_0),
98+
VarOp(ISTORE, 2),
99+
Label(4),
100+
FrameEntry(1, List(1, 1), List()),
101+
Op(ICONST_1),
102+
Jump(IFEQ, Label(18)),
103+
Incr(IINC, 2, 1),
104+
VarOp(ILOAD, 2),
105+
IntOp(BIPUSH, 10),
106+
Jump(IF_ICMPLE, Label(15)),
107+
Field(GETSTATIC, "scala/runtime/BoxedUnit", "UNIT", "Lscala/runtime/BoxedUnit;"),
108+
VarOp(ASTORE, 3),
109+
Op(RETURN), // break()
110+
Label(15),
111+
FrameEntry(3, List(), List()),
112+
Jump(GOTO, Label(4)),
113+
Label(18),
114+
FrameEntry(3, List(), List()),
115+
Op(RETURN)
116+
)
117+
}
118+
119+
@Test def boundaryNonLocalBreak = {
120+
testLabelBytecode(
121+
"""boundary[Unit, Unit]:
122+
| nonLocalBreak()
123+
""".stripMargin,
124+
"Unit",
125+
TypeOp(NEW, "scala/util/boundary$Label"),
126+
Op(DUP),
127+
Invoke(INVOKESPECIAL, "scala/util/boundary$Label", "<init>", "()V", false),
128+
VarOp(ASTORE, 1),
129+
Label(4),
130+
VarOp(ALOAD, 0),
131+
VarOp(ALOAD, 1),
132+
Invoke(INVOKEVIRTUAL, "Test", "nonLocalBreak", "(Lscala/util/boundary$Label;)Lscala/runtime/Nothing$;", false),
133+
Op(ATHROW),
134+
Label(9),
135+
FrameEntry(0, List(), List("java/lang/Throwable")),
136+
Op(NOP),
137+
Op(NOP),
138+
Op(ATHROW),
139+
Label(14),
140+
FrameEntry(0, List("Test", "scala/util/boundary$Label"), List("scala/util/boundary$Break")),
141+
VarOp(ASTORE, 2),
142+
VarOp(ALOAD, 2),
143+
Invoke(INVOKEVIRTUAL, "scala/util/boundary$Break", "label", "()Lscala/util/boundary$Label;", false),
144+
VarOp(ALOAD, 1),
145+
Jump(IF_ACMPNE, Label(22)),
146+
Jump(GOTO, Label(26)),
147+
Label(22),
148+
FrameEntry(1, List("scala/util/boundary$Break"), List()),
149+
VarOp(ALOAD, 2),
150+
Op(ATHROW),
151+
Label(26),
152+
FrameEntry(3, List(), List()),
153+
Jump(GOTO, Label(29)),
154+
Label(29),
155+
FrameEntry(3, List(), List()),
156+
Op(RETURN),
157+
)
158+
159+
testLabelBytecode(
160+
"""boundary[Unit, Unit]:
161+
| def f() = break()
162+
| f()
163+
""".stripMargin,
164+
"Unit",
165+
TypeOp(NEW, "scala/util/boundary$Label"),
166+
Op(DUP),
167+
Invoke(INVOKESPECIAL, "scala/util/boundary$Label", "<init>", "()V", false),
168+
VarOp(ASTORE, 1),
169+
Label(4),
170+
VarOp(ALOAD, 1),
171+
Invoke(INVOKESTATIC, "Test", "f$1", "(Lscala/util/boundary$Label;)Lscala/runtime/Nothing$;", false),
172+
Op(ATHROW),
173+
Label(8),
174+
FrameEntry(0, List(), List("java/lang/Throwable")),
175+
Op(NOP),
176+
Op(NOP),
177+
Op(ATHROW),
178+
Label(13),
179+
FrameEntry(0, List("Test", "scala/util/boundary$Label"), List("scala/util/boundary$Break")),
180+
VarOp(ASTORE, 2),
181+
VarOp(ALOAD, 2),
182+
Invoke(INVOKEVIRTUAL, "scala/util/boundary$Break", "label", "()Lscala/util/boundary$Label;", false),
183+
VarOp(ALOAD, 1),
184+
Jump(IF_ACMPNE, Label(21)),
185+
Jump(GOTO, Label(25)),
186+
Label(21),
187+
FrameEntry(1, List("scala/util/boundary$Break"), List()),
188+
VarOp(ALOAD, 2),
189+
Op(ATHROW),
190+
Label(25),
191+
FrameEntry(3, List(), List()),
192+
Jump(GOTO, Label(28)),
193+
Label(28),
194+
FrameEntry(3, List(), List()),
195+
Op(RETURN),
196+
)
197+
}
198+
199+
@Test def boundaryLocalAndNonLocalBreak = {
200+
testLabelBytecode(
201+
"""boundary[Unit, Unit]: l ?=>
202+
| break()
203+
| nonLocalBreak()
204+
""".stripMargin,
205+
"Unit",
206+
TypeOp(NEW, "scala/util/boundary$Label"),
207+
Op(DUP),
208+
Invoke(INVOKESPECIAL, "scala/util/boundary$Label", "<init>", "()V", false),
209+
VarOp(ASTORE, 1),
210+
Label(4),
211+
Field(GETSTATIC, "scala/runtime/BoxedUnit", "UNIT", "Lscala/runtime/BoxedUnit;"),
212+
VarOp(ASTORE, 2),
213+
Op(RETURN), // break()
214+
Label(8),
215+
FrameEntry(0, List(), List("java/lang/Throwable")),
216+
Op(NOP),
217+
Op(NOP),
218+
Op(NOP),
219+
Op(NOP),
220+
Op(NOP),
221+
Op(ATHROW),
222+
Label(16),
223+
FrameEntry(4, List(), List("java/lang/Throwable")),
224+
Op(NOP),
225+
Op(NOP),
226+
Op(ATHROW),
227+
Label(21),
228+
FrameEntry(0, List("Test", "scala/util/boundary$Label"), List("scala/util/boundary$Break")),
229+
VarOp(ASTORE, 3),
230+
VarOp(ALOAD, 3),
231+
Invoke(INVOKEVIRTUAL, "scala/util/boundary$Break", "label", "()Lscala/util/boundary$Label;", false),
232+
VarOp(ALOAD, 1),
233+
Jump(IF_ACMPNE, Label(29)),
234+
Jump(GOTO, Label(33)),
235+
Label(29),
236+
FrameEntry(1, List(0, "scala/util/boundary$Break"), List()),
237+
VarOp(ALOAD, 3),
238+
Op(ATHROW),
239+
Label(33),
240+
FrameEntry(3, List(), List()),
241+
Jump(GOTO, Label(36)),
242+
Label(36),
243+
FrameEntry(3, List(), List()),
244+
Op(RETURN),
245+
)
246+
}
247+
248+
private def testLabelBytecode(code: String, tpe: String, expected: Instruction*): Unit = {
249+
val source =
250+
s"""import scala.util.*
251+
|class Test:
252+
| def test: $tpe = {
253+
| ${code.lines().toList().asScala.mkString("", "\n ", "")}
254+
| }
255+
| def nonLocalBreak[T](value: T)(using boundary.Label[T]): Nothing = break(value)
256+
| def nonLocalBreak()(using boundary.Label[Unit]): Nothing = break(())
257+
""".stripMargin
258+
259+
checkBCode(source) { dir =>
260+
val clsIn = dir.lookupName("Test.class", directory = false).input
261+
val clsNode = loadClassNode(clsIn)
262+
val method = getMethod(clsNode, "test")
263+
264+
val instructions = instructionsFromMethod(method)
265+
266+
val expectedList = expected.toList
267+
268+
assert(instructions == expectedList,
269+
"`test` was not properly generated\n" + diffInstructions(instructions, expectedList))
270+
}
271+
}
272+
273+
}

0 commit comments

Comments
 (0)