Skip to content

Commit 45498c8

Browse files
committed
[GR-62089] Fix boolean boxing elimination in combination with short-circuits could lead to deopt loops.
PullRequest: graal/20023
2 parents 7acd5ce + c2e1a20 commit 45498c8

File tree

2 files changed

+344
-2
lines changed

2 files changed

+344
-2
lines changed

truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/BoxingEliminationTest.java

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1867,6 +1867,321 @@ private static BoxingEliminationTestRootNode parse(BytecodeParser<BoxingEliminat
18671867
return nodes.getNode(nodes.count() - 1);
18681868
}
18691869

1870+
@Test
1871+
public void testOrTwice() {
1872+
// return arg0 & arg1 & arg2
1873+
BoxingEliminationTestRootNode node = (BoxingEliminationTestRootNode) parse(b -> {
1874+
b.beginRoot();
1875+
1876+
b.beginConsumer();
1877+
b.beginOr();
1878+
b.emitLoadArgument(0);
1879+
b.emitLoadArgument(1);
1880+
b.endOr();
1881+
b.endConsumer();
1882+
1883+
b.endRoot();
1884+
}).getRootNode();
1885+
1886+
assertInstructions(node,
1887+
"load.argument",
1888+
"c.ToBoolean",
1889+
"sc.Or",
1890+
"load.argument",
1891+
"c.ToBoolean",
1892+
"c.Consumer",
1893+
"return");
1894+
1895+
node.getCallTarget().call(false, true);
1896+
node.getCallTarget().call(true, false);
1897+
1898+
assertInstructions(node,
1899+
"load.argument$Boolean",
1900+
"c.ToBoolean$Boolean",
1901+
"sc.Or",
1902+
"load.argument$Boolean",
1903+
"c.ToBoolean$Boolean",
1904+
"c.Consumer",
1905+
"return");
1906+
1907+
node.getCallTarget().call(0L, 1L);
1908+
node.getCallTarget().call(1L, 0L);
1909+
1910+
assertInstructions(node,
1911+
"load.argument",
1912+
"c.ToBoolean",
1913+
"sc.Or",
1914+
"load.argument",
1915+
"c.ToBoolean",
1916+
"c.Consumer",
1917+
"return");
1918+
1919+
var quickenings = assertQuickenings(node, 12, 5);
1920+
1921+
assertStable(quickenings, node, false, true);
1922+
assertStable(quickenings, node, true, false);
1923+
1924+
assertStable(quickenings, node, 0L, 1L);
1925+
assertStable(quickenings, node, 1L, 0L);
1926+
}
1927+
1928+
@Test
1929+
public void testOrSingle() {
1930+
// return arg0 & arg1 & arg2
1931+
BoxingEliminationTestRootNode node = (BoxingEliminationTestRootNode) parse(b -> {
1932+
b.beginRoot();
1933+
1934+
b.beginConsumer();
1935+
b.beginOr();
1936+
b.emitLoadArgument(0);
1937+
b.endOr();
1938+
b.endConsumer();
1939+
1940+
b.endRoot();
1941+
}).getRootNode();
1942+
1943+
assertInstructions(node,
1944+
"load.argument",
1945+
"c.ToBoolean",
1946+
"c.Consumer",
1947+
"return");
1948+
1949+
node.getCallTarget().call(false);
1950+
node.getCallTarget().call(true);
1951+
1952+
assertInstructions(node,
1953+
"load.argument$Boolean",
1954+
"c.ToBoolean$Boolean$unboxed",
1955+
"c.Consumer$Boolean",
1956+
"return");
1957+
1958+
node.getCallTarget().call(0L);
1959+
node.getCallTarget().call(1L);
1960+
1961+
assertInstructions(node,
1962+
"load.argument",
1963+
"c.ToBoolean$unboxed",
1964+
"c.Consumer$Boolean",
1965+
"return");
1966+
1967+
var quickenings = assertQuickenings(node, 7, 3);
1968+
1969+
assertStable(quickenings, node, false);
1970+
assertStable(quickenings, node, true);
1971+
1972+
assertStable(quickenings, node, 0L);
1973+
assertStable(quickenings, node, 1L);
1974+
}
1975+
1976+
@Test
1977+
public void testAndReturnTwice() {
1978+
// return arg0 & arg1 & arg2
1979+
BoxingEliminationTestRootNode node = (BoxingEliminationTestRootNode) parse(b -> {
1980+
b.beginRoot();
1981+
1982+
b.beginConsumer();
1983+
b.beginAndReturn();
1984+
b.emitLoadArgument(0);
1985+
b.emitLoadArgument(1);
1986+
b.endAndReturn();
1987+
b.endConsumer();
1988+
1989+
b.endRoot();
1990+
}).getRootNode();
1991+
1992+
assertInstructions(node,
1993+
"load.argument",
1994+
"dup",
1995+
"c.ToBoolean",
1996+
"sc.AndReturn",
1997+
"load.argument",
1998+
"c.Consumer",
1999+
"return");
2000+
2001+
node.getCallTarget().call(false, true);
2002+
node.getCallTarget().call(true, false);
2003+
2004+
assertInstructions(node,
2005+
"load.argument",
2006+
"dup",
2007+
"c.ToBoolean",
2008+
"sc.AndReturn",
2009+
"load.argument",
2010+
"c.Consumer",
2011+
"return");
2012+
2013+
node.getCallTarget().call(0L, 1L);
2014+
node.getCallTarget().call(1L, 0L);
2015+
2016+
assertInstructions(node,
2017+
"load.argument",
2018+
"dup",
2019+
"c.ToBoolean",
2020+
"sc.AndReturn",
2021+
"load.argument",
2022+
"c.Consumer",
2023+
"return");
2024+
2025+
var quickenings = assertQuickenings(node, 6, 4);
2026+
2027+
assertStable(quickenings, node, false, true);
2028+
assertStable(quickenings, node, true, false);
2029+
2030+
assertStable(quickenings, node, 0L, 1L);
2031+
assertStable(quickenings, node, 1L, 0L);
2032+
}
2033+
2034+
@Test
2035+
public void testAndReturnSingle() {
2036+
// return arg0 & arg1 & arg2
2037+
BoxingEliminationTestRootNode node = (BoxingEliminationTestRootNode) parse(b -> {
2038+
b.beginRoot();
2039+
2040+
b.beginConsumer();
2041+
b.beginAndReturn();
2042+
b.emitLoadArgument(0);
2043+
b.endAndReturn();
2044+
b.endConsumer();
2045+
2046+
b.endRoot();
2047+
}).getRootNode();
2048+
2049+
assertInstructions(node,
2050+
"load.argument",
2051+
"c.Consumer",
2052+
"return");
2053+
2054+
node.getCallTarget().call(false);
2055+
node.getCallTarget().call(true);
2056+
2057+
assertInstructions(node,
2058+
"load.argument$Boolean",
2059+
"c.Consumer$Boolean",
2060+
"return");
2061+
2062+
node.getCallTarget().call(0L);
2063+
node.getCallTarget().call(1L);
2064+
2065+
assertInstructions(node,
2066+
"load.argument",
2067+
"c.Consumer",
2068+
"return");
2069+
2070+
var quickenings = assertQuickenings(node, 5, 2);
2071+
2072+
assertStable(quickenings, node, false);
2073+
assertStable(quickenings, node, true);
2074+
2075+
assertStable(quickenings, node, 0L);
2076+
assertStable(quickenings, node, 1L);
2077+
}
2078+
2079+
@Test
2080+
public void testAndTwice() {
2081+
// return arg0 & arg1 & arg2
2082+
BoxingEliminationTestRootNode node = (BoxingEliminationTestRootNode) parse(b -> {
2083+
b.beginRoot();
2084+
2085+
b.beginConsumer();
2086+
b.beginAnd();
2087+
b.emitLoadArgument(0);
2088+
b.emitLoadArgument(1);
2089+
b.endAnd();
2090+
b.endConsumer();
2091+
2092+
b.endRoot();
2093+
}).getRootNode();
2094+
2095+
assertInstructions(node,
2096+
"load.argument",
2097+
"c.ToBoolean",
2098+
"sc.And",
2099+
"load.argument",
2100+
"c.ToBoolean",
2101+
"c.Consumer",
2102+
"return");
2103+
2104+
node.getCallTarget().call(false, true);
2105+
node.getCallTarget().call(true, false);
2106+
2107+
assertInstructions(node,
2108+
"load.argument$Boolean",
2109+
"c.ToBoolean$Boolean",
2110+
"sc.And",
2111+
"load.argument$Boolean",
2112+
"c.ToBoolean$Boolean",
2113+
"c.Consumer",
2114+
"return");
2115+
2116+
node.getCallTarget().call(0L, 1L);
2117+
node.getCallTarget().call(1L, 0L);
2118+
2119+
assertInstructions(node,
2120+
"load.argument",
2121+
"c.ToBoolean",
2122+
"sc.And",
2123+
"load.argument",
2124+
"c.ToBoolean",
2125+
"c.Consumer",
2126+
"return");
2127+
2128+
var quickenings = assertQuickenings(node, 12, 5);
2129+
2130+
assertStable(quickenings, node, false, true);
2131+
assertStable(quickenings, node, true, false);
2132+
2133+
assertStable(quickenings, node, 0L, 1L);
2134+
assertStable(quickenings, node, 1L, 0L);
2135+
}
2136+
2137+
@Test
2138+
public void testAndSingle() {
2139+
// return arg0 & arg1 & arg2
2140+
BoxingEliminationTestRootNode node = (BoxingEliminationTestRootNode) parse(b -> {
2141+
b.beginRoot();
2142+
2143+
b.beginConsumer();
2144+
b.beginAnd();
2145+
b.emitLoadArgument(0);
2146+
b.endAnd();
2147+
b.endConsumer();
2148+
2149+
b.endRoot();
2150+
}).getRootNode();
2151+
2152+
assertInstructions(node,
2153+
"load.argument",
2154+
"c.ToBoolean",
2155+
"c.Consumer",
2156+
"return");
2157+
2158+
node.getCallTarget().call(false);
2159+
node.getCallTarget().call(true);
2160+
2161+
assertInstructions(node,
2162+
"load.argument$Boolean",
2163+
"c.ToBoolean$Boolean$unboxed",
2164+
"c.Consumer$Boolean",
2165+
"return");
2166+
2167+
node.getCallTarget().call(0L);
2168+
node.getCallTarget().call(1L);
2169+
2170+
assertInstructions(node,
2171+
"load.argument",
2172+
"c.ToBoolean$unboxed",
2173+
"c.Consumer$Boolean",
2174+
"return");
2175+
2176+
var quickenings = assertQuickenings(node, 7, 3);
2177+
2178+
assertStable(quickenings, node, false);
2179+
assertStable(quickenings, node, true);
2180+
2181+
assertStable(quickenings, node, 0L);
2182+
assertStable(quickenings, node, 1L);
2183+
}
2184+
18702185
@GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, //
18712186
enableYield = true, enableSerialization = true, //
18722187
enableQuickening = true, //
@@ -1893,7 +2208,19 @@ public static boolean doBoolean(boolean v) {
18932208
public static boolean doLong(long v) {
18942209
return v != 0;
18952210
}
2211+
}
2212+
2213+
@Operation
2214+
static final class Consumer {
2215+
@Specialization
2216+
public static boolean doBoolean(boolean v) {
2217+
return v;
2218+
}
18962219

2220+
@Specialization
2221+
public static long doLong(long v) {
2222+
return v;
2223+
}
18972224
}
18982225

18992226
/*

truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeRootNodeElement.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4752,8 +4752,10 @@ private CodeExecutableElement createEnd(OperationModel operation) {
47524752
b.string("operationData.childBci");
47534753
b.end(2);
47544754
} else if (operation.kind == OperationKind.CUSTOM_SHORT_CIRCUIT) {
4755-
b.startStatement().startCall("afterChild");
4756-
b.string("true");
4755+
b.declaration(type(int.class), "nextBci");
4756+
b.startIf().string("operation.childCount <= 1").end().startBlock();
4757+
b.lineComment("Single child -> boxing elimination possible");
4758+
b.startStatement().string("nextBci = ");
47574759
ShortCircuitInstructionModel shortCircuitModel = operation.instruction.shortCircuitModel;
47584760
if (shortCircuitModel.returnConvertedBoolean()) {
47594761
// We emit a boolean converter instruction above. Compute its bci.
@@ -4762,6 +4764,16 @@ private CodeExecutableElement createEnd(OperationModel operation) {
47624764
// The child bci points to the instruction producing this last value.
47634765
b.string("operationData.childBci");
47644766
}
4767+
b.end(); // statement
4768+
b.end(); // if block
4769+
4770+
b.startElseBlock();
4771+
b.lineComment("Multi child -> boxing elimination not possible use short-circuit bci to disable it.");
4772+
b.statement("nextBci = operationData.shortCircuitBci");
4773+
b.end();
4774+
4775+
b.startStatement().startCall("afterChild");
4776+
b.string("true").string("nextBci");
47654777
b.end(2);
47664778
} else {
47674779
b.startStatement().startCall("afterChild");
@@ -5897,6 +5909,8 @@ enum BeforeChildKind {
58975909
b.statement("operationData.branchFixupBcis.add(bci + " + op.instruction.getImmediate("branch_target").offset() + ")");
58985910
b.end();
58995911

5912+
b.statement("operationData.shortCircuitBci = bci");
5913+
59005914
// Emit the boolean check.
59015915
buildEmitInstruction(b, op.instruction, emitShortCircuitArguments(op.instruction));
59025916

@@ -7621,6 +7635,7 @@ The index of the finally operation (TryFinally/TryCatchOtherwise) on the operati
76217635
name = "CustomShortCircuitOperationData";
76227636
fields = List.of(//
76237637
field(type(int.class), "childBci").withInitializer(UNINIT),
7638+
field(type(int.class), "shortCircuitBci").withInitializer(UNINIT),
76247639
field(generic(List.class, Integer.class), "branchFixupBcis").withInitializer("new ArrayList<>(4)"));
76257640
break;
76267641
default:

0 commit comments

Comments
 (0)