@@ -2016,6 +2016,118 @@ public void tinyPopularLuaHelpersInlineWithoutAnnotations() {
20162016 compiled .contains ("arithmetic(" ));
20172017 }
20182018
2019+ @ Test
2020+ public void tinyMonomorphicMethodsInlineOnLua () {
2021+ String compiled = compileOptimizedLua (
2022+ "tinyMonomorphicMethodsInlineOnLua" ,
2023+ "package Test" ,
2024+ "native consume(int value)" ,
2025+ "class Accumulator" ,
2026+ " int offset" ,
2027+ " construct(int offset)" ,
2028+ " this.offset = offset" ,
2029+ " function add(int value) returns int" ,
2030+ " return value + offset" ,
2031+ "abstract class Operation" ,
2032+ " abstract function apply(int value) returns int" ,
2033+ "class DoubleOperation extends Operation" ,
2034+ " override function apply(int value) returns int" ,
2035+ " return value * 2" ,
2036+ "@noinline function hotLoop(Accumulator accumulator)" ,
2037+ " var i = 0" ,
2038+ " while i < 16" ,
2039+ " consume(accumulator.add(i))" ,
2040+ " i++" ,
2041+ "@noinline function dynamicCall(Operation operation)" ,
2042+ " consume(operation.apply(3))" ,
2043+ "init" ,
2044+ " hotLoop(new Accumulator(2))" ,
2045+ " dynamicCall(new DoubleOperation())"
2046+ );
2047+
2048+ String body = topLevelFunctionBodyWithPrefix (compiled , "hotLoop" );
2049+ assertFalse ("a tiny method with exactly one implementation must inline in optimized Lua:\n " + body ,
2050+ body .contains ("Accumulator_add(" ));
2051+ assertTrue ("the inlined method must retain its field read:\n " + body ,
2052+ body .contains ("Accumulator_offset_storage[" ));
2053+ String dynamicBody = topLevelFunctionBodyWithPrefix (compiled , "dynamicCall" );
2054+ assertTrue ("a genuinely virtual method call must retain dispatch:\n " + dynamicBody ,
2055+ dynamicBody .contains ("dispatch_" ));
2056+ }
2057+
2058+ @ Test
2059+ public void monomorphicMethodInliningEvaluatesReceiverOnce () {
2060+ test ().testLua (true ).luaOnly (false ).optimize ().executeProg ().lines (
2061+ "package Test" ,
2062+ "native testSuccess()" ,
2063+ "int receiverEvaluations = 0" ,
2064+ "class Accumulator" ,
2065+ " int offset" ,
2066+ " construct(int offset)" ,
2067+ " this.offset = offset" ,
2068+ " function add(int value) returns int" ,
2069+ " return value + offset" ,
2070+ "function makeAccumulator() returns Accumulator" ,
2071+ " receiverEvaluations++" ,
2072+ " return new Accumulator(2)" ,
2073+ "function evaluate() returns int" ,
2074+ " var result = 0" ,
2075+ " var i = 0" ,
2076+ " while i < 1" ,
2077+ " result = makeAccumulator().add(5)" ,
2078+ " i++" ,
2079+ " return result" ,
2080+ "init" ,
2081+ " if evaluate() == 7 and receiverEvaluations == 1" ,
2082+ " testSuccess()"
2083+ );
2084+ }
2085+
2086+ @ Test
2087+ public void monomorphicMethodInliningKeepsCallbackBoundary () {
2088+ String compiled = compileOptimizedLua (
2089+ "monomorphicMethodInliningKeepsCallbackBoundary" ,
2090+ "package Test" ,
2091+ "native consume(code callback)" ,
2092+ "function callback()" ,
2093+ "class Registrar" ,
2094+ " function install()" ,
2095+ " consume(function callback)" ,
2096+ "@noinline function hotPath(Registrar registrar)" ,
2097+ " var i = 0" ,
2098+ " while i < 1" ,
2099+ " registrar.install()" ,
2100+ " i++" ,
2101+ "init" ,
2102+ " hotPath(new Registrar())"
2103+ );
2104+
2105+ assertFunctionBodyContains (compiled , "hotPath" , "Registrar_install(" , true );
2106+ }
2107+
2108+ @ Test
2109+ public void monomorphicMethodInliningKeepsLocalPlayerBoundary () {
2110+ String compiled = compileOptimizedLua (
2111+ "monomorphicMethodInliningKeepsLocalPlayerBoundary" ,
2112+ "type player extends handle" ,
2113+ "package Test" ,
2114+ "@extern native GetLocalPlayer() returns player" ,
2115+ "native consume(bool value)" ,
2116+ "class Probe" ,
2117+ " function isLocal() returns bool" ,
2118+ " return GetLocalPlayer() != null" ,
2119+ "@noinline function hotPath(Probe probe)" ,
2120+ " var i = 0" ,
2121+ " while i < 1" ,
2122+ " consume(probe.isLocal())" ,
2123+ " i++" ,
2124+ "init" ,
2125+ " hotPath(new Probe())"
2126+ );
2127+
2128+ assertFunctionBodyContains (compiled , "hotPath" , "Probe_isLocal(" , true );
2129+ }
2130+
20192131 /**
20202132 * Measured after Lua native lowering: unit_getX = 59 IM nodes,
20212133 * unit_getAbilityLevel = 63, real_floor = 35, and __wurst_intDiv = 31. Each helper is called
@@ -2050,14 +2162,19 @@ public void optimizedUnitSpatialIndexInnerLoopUsesRawLuaOperations() {
20502162 " query(vec2(0., 0.))"
20512163 );
20522164
2053- // The spatial-index helpers each have one call site, so the optimizer folds the whole
2054- // query chain into our retained entry point. Inspect that surviving hot- loop owner .
2165+ // Register-pressure-aware inlining may keep the range helper as the hot-loop owner instead
2166+ // of folding it into query. Inspect whichever function actually retains the loop.
20552167 String body = topLevelFunctionBodyWithPrefix (compiled , "query" );
2056- assertTrue ("query loop must read the next-link array directly:\n " + body ,
2168+ if (!body .contains ("UnitSpatialIndex_nextInCell[" )) {
2169+ assertTrue ("query must call the retained range helper:\n " + body ,
2170+ body .contains ("addRangeMatches(" ));
2171+ body = topLevelFunctionBodyWithPrefix (compiled , "addRangeMatches" );
2172+ }
2173+ assertTrue ("spatial-index hot loop must read the next-link array directly:\n " + body ,
20572174 body .contains ("UnitSpatialIndex_nextInCell[" ));
2058- assertTrue ("query loop must read cached X directly:\n " + body ,
2175+ assertTrue ("spatial-index hot loop must read cached X directly:\n " + body ,
20592176 body .contains ("UnitSpatialIndex_lastX[" ));
2060- assertTrue ("query loop must read cached Y directly:\n " + body ,
2177+ assertTrue ("spatial-index hot loop must read cached Y directly:\n " + body ,
20612178 body .contains ("UnitSpatialIndex_lastY[" ));
20622179 assertFalse ("typed array reads must not retain assurance calls:\n " + body ,
20632180 body .contains ("__wurst_ensure" ));
0 commit comments