@@ -2048,8 +2048,12 @@ public void tinyMonomorphicMethodsInlineOnLua() {
20482048 String body = topLevelFunctionBodyWithPrefix (compiled , "hotLoop" );
20492049 assertFalse ("a tiny method with exactly one implementation must inline in optimized Lua:\n " + body ,
20502050 body .contains ("Accumulator_add(" ));
2051- assertTrue ("the inlined method must retain its field read:\n " + body ,
2052- body .contains ("Accumulator_offset_storage[" ));
2051+ java .util .regex .Matcher offsetAlias = java .util .regex .Pattern
2052+ .compile ("local (\\ w+) = Accumulator_offset_storage" ).matcher (body );
2053+ assertTrue ("the inlined method's field storage must be localized:\n " + body ,
2054+ offsetAlias .find ());
2055+ assertTrue ("the inlined method must retain its field read through the local alias:\n " + body ,
2056+ body .contains (offsetAlias .group (1 ) + "[" ));
20532057 String dynamicBody = topLevelFunctionBodyWithPrefix (compiled , "dynamicCall" );
20542058 assertTrue ("a genuinely virtual method call must retain dispatch:\n " + dynamicBody ,
20552059 dynamicBody .contains ("dispatch_" ));
@@ -2165,16 +2169,31 @@ public void optimizedUnitSpatialIndexInnerLoopUsesRawLuaOperations() {
21652169 // Register-pressure-aware inlining may keep the range helper as the hot-loop owner instead
21662170 // of folding it into query. Inspect whichever function actually retains the loop.
21672171 String body = topLevelFunctionBodyWithPrefix (compiled , "query" );
2168- if (!body .contains ("UnitSpatialIndex_nextInCell[ " )) {
2172+ if (!body .contains ("= UnitSpatialIndex_nextInCell" )) {
21692173 assertTrue ("query must call the retained range helper:\n " + body ,
21702174 body .contains ("addRangeMatches(" ));
21712175 body = topLevelFunctionBodyWithPrefix (compiled , "addRangeMatches" );
21722176 }
2173- assertTrue ("spatial-index hot loop must read the next-link array directly:\n " + body ,
2177+ java .util .regex .Matcher nextAlias = java .util .regex .Pattern
2178+ .compile ("local (\\ w+) = UnitSpatialIndex_nextInCell" ).matcher (body );
2179+ java .util .regex .Matcher xAlias = java .util .regex .Pattern
2180+ .compile ("local (\\ w+) = UnitSpatialIndex_lastX" ).matcher (body );
2181+ java .util .regex .Matcher yAlias = java .util .regex .Pattern
2182+ .compile ("local (\\ w+) = UnitSpatialIndex_lastY" ).matcher (body );
2183+ assertTrue ("spatial-index hot loop must localize the next-link array:\n " + body , nextAlias .find ());
2184+ assertTrue ("spatial-index hot loop must localize cached X:\n " + body , xAlias .find ());
2185+ assertTrue ("spatial-index hot loop must localize cached Y:\n " + body , yAlias .find ());
2186+ assertTrue ("spatial-index hot loop must index the localized next-link array:\n " + body ,
2187+ body .contains (nextAlias .group (1 ) + "[" ));
2188+ assertTrue ("spatial-index hot loop must index localized cached X:\n " + body ,
2189+ body .contains (xAlias .group (1 ) + "[" ));
2190+ assertTrue ("spatial-index hot loop must index localized cached Y:\n " + body ,
2191+ body .contains (yAlias .group (1 ) + "[" ));
2192+ assertFalse ("spatial-index loop must not retain global next-link lookups:\n " + body ,
21742193 body .contains ("UnitSpatialIndex_nextInCell[" ));
2175- assertTrue ("spatial-index hot loop must read cached X directly :\n " + body ,
2194+ assertFalse ("spatial-index loop must not retain global cached-X lookups :\n " + body ,
21762195 body .contains ("UnitSpatialIndex_lastX[" ));
2177- assertTrue ("spatial-index hot loop must read cached Y directly :\n " + body ,
2196+ assertFalse ("spatial-index loop must not retain global cached-Y lookups :\n " + body ,
21782197 body .contains ("UnitSpatialIndex_lastY[" ));
21792198 assertFalse ("typed array reads must not retain assurance calls:\n " + body ,
21802199 body .contains ("__wurst_ensure" ));
@@ -2184,6 +2203,114 @@ public void optimizedUnitSpatialIndexInnerLoopUsesRawLuaOperations() {
21842203 body .contains ("__wurst_intDiv(" ));
21852204 }
21862205
2206+ @ Test
2207+ public void hotLoopStorageTablesAreLocalizedOnLua () {
2208+ String compiled = compileOptimizedLua (
2209+ "hotLoopStorageTablesAreLocalizedOnLua" ,
2210+ "package Test" ,
2211+ "int array values" ,
2212+ "class Counter" ,
2213+ " int total" ,
2214+ "@noinline function hotLoop(Counter counter, int limit)" ,
2215+ " var i = 0" ,
2216+ " while i < limit" ,
2217+ " values[i] = values[i] + counter.total" ,
2218+ " counter.total = values[i + 1] + counter.total" ,
2219+ " i++" ,
2220+ "@noinline function coldPath(Counter counter) returns int" ,
2221+ " return values[0] + counter.total" ,
2222+ "init" ,
2223+ " let counter = new Counter()" ,
2224+ " hotLoop(counter, 16)" ,
2225+ " coldPath(counter)"
2226+ );
2227+
2228+ String hotBody = topLevelFunctionBodyWithPrefix (compiled , "hotLoop" );
2229+ java .util .regex .Matcher arrayAlias = java .util .regex .Pattern
2230+ .compile ("local (\\ w+) = Test_values" ).matcher (hotBody );
2231+ assertTrue ("the loop's generated array table must be cached in a local:\n " + hotBody ,
2232+ arrayAlias .find ());
2233+ String arrayAliasName = arrayAlias .group (1 );
2234+ assertTrue ("the localized array must be used in the loop:\n " + hotBody ,
2235+ hotBody .contains (arrayAliasName + "[" ));
2236+ assertFalse ("loop indexes must not keep resolving the array through a global:\n " + hotBody ,
2237+ hotBody .contains ("Test_values[" ));
2238+
2239+ java .util .regex .Matcher fieldAlias = java .util .regex .Pattern
2240+ .compile ("local (\\ w+) = Counter_total_storage" ).matcher (hotBody );
2241+ assertTrue ("the loop's generated field-storage table must be cached in a local:\n " + hotBody ,
2242+ fieldAlias .find ());
2243+ String fieldAliasName = fieldAlias .group (1 );
2244+ assertTrue ("the localized field storage must be used in the loop:\n " + hotBody ,
2245+ hotBody .contains (fieldAliasName + "[" ));
2246+ assertFalse ("loop field accesses must not keep resolving storage through a global:\n " + hotBody ,
2247+ hotBody .contains ("Counter_total_storage[" ));
2248+
2249+ String coldBody = topLevelFunctionBodyWithPrefix (compiled , "coldPath" );
2250+ assertFalse ("cold array access must not consume a local alias:\n " + coldBody ,
2251+ coldBody .contains ("local " ));
2252+ assertTrue ("cold array access must keep the generated global table:\n " + coldBody ,
2253+ coldBody .contains ("Test_values[" ));
2254+ assertTrue ("cold field access must keep the generated global storage:\n " + coldBody ,
2255+ coldBody .contains ("Counter_total_storage[" ));
2256+ }
2257+
2258+ @ Test
2259+ public void localizedHotStorageTablesPreserveLuaBehavior () throws IOException {
2260+ test ().testLua (true ).executeProg ().lines (
2261+ "package Test" ,
2262+ "native testSuccess()" ,
2263+ "int array values" ,
2264+ "class Counter" ,
2265+ " int total" ,
2266+ "function hotLoop(Counter counter, int limit)" ,
2267+ " var i = 0" ,
2268+ " while i < limit" ,
2269+ " values[i] = values[i] + counter.total" ,
2270+ " counter.total = values[i + 1] + counter.total" ,
2271+ " i++" ,
2272+ "init" ,
2273+ " values[0] = 1" ,
2274+ " let counter = new Counter()" ,
2275+ " counter.total = 2" ,
2276+ " hotLoop(counter, 3)" ,
2277+ " if values[0] == 3 and values[1] == 2 and counter.total == 2" ,
2278+ " testSuccess()"
2279+ );
2280+ }
2281+
2282+ @ Test
2283+ public void hotStorageLocalizationRespectsLuaRegisterHeadroom () {
2284+ List <String > lines = new ArrayList <>();
2285+ lines .add ("package Test" );
2286+ lines .add ("native consume(int value)" );
2287+ lines .add ("int array values" );
2288+ lines .add ("@noinline function crowded(int limit)" );
2289+ lines .add (" var sum = 0" );
2290+ for (int i = 0 ; i < 187 ; i ++) {
2291+ lines .add (" let v" + i + " = " + i );
2292+ lines .add (" sum += v" + i );
2293+ }
2294+ lines .add (" var i = 0" );
2295+ lines .add (" while i < limit" );
2296+ lines .add (" sum += values[i]" );
2297+ lines .add (" i++" );
2298+ lines .add (" consume(sum)" );
2299+ lines .add ("init" );
2300+ lines .add (" crowded(1)" );
2301+
2302+ String compiled = compileLuaWithRunArgs (
2303+ "hotStorageLocalizationRespectsLuaRegisterHeadroom" ,
2304+ new RunArgs ().with ("-lua" ), lines .toArray (new String [0 ]));
2305+ String body = topLevelFunctionBodyWithPrefix (compiled , "crowded" );
2306+ assertFalse ("localization must retain headroom below Lua's hard local limit:\n " + body ,
2307+ body .contains ("= Test_values" ));
2308+ assertTrue ("a skipped alias must leave the generated array access intact:\n " + body ,
2309+ body .contains ("Test_values[i]" ));
2310+ assertFalse ("the optimization must not force the locals-table fallback:\n " + body ,
2311+ body .contains ("__wurst_locals" ));
2312+ }
2313+
21872314 /**
21882315 * On Lua a vararg function used to keep its {@code ...} parameter and pack it into a table on
21892316 * every call, and the inliner refused it. With a static argument count at the call site the
0 commit comments