@@ -43,6 +43,16 @@ private String compiledLua(String testName) throws IOException {
4343 return Files .toString (new File ("test-output/lua/LuaBackendAuditTests_" + testName + ".lua" ), Charsets .UTF_8 );
4444 }
4545
46+ private String luaFunctionBody (String compiled , String functionName ) {
47+ java .util .regex .Matcher matcher = java .util .regex .Pattern .compile (
48+ "function " + java .util .regex .Pattern .quote (functionName )
49+ + "\\ ([^)]*\\ )\\ s*\\ R(.*?)\\ Rend" ,
50+ java .util .regex .Pattern .DOTALL )
51+ .matcher (compiled );
52+ assertTrue ("expected generated Lua function " + functionName , matcher .find ());
53+ return matcher .group (1 );
54+ }
55+
4656 private String compileOptimizedLua (String testName , String ... lines ) {
4757 RunArgs runArgs = new RunArgs ().with ("-lua" , "-inline" , "-localOptimizations" );
4858 return compileLuaWithRunArgs (testName , runArgs , false , lines );
@@ -209,6 +219,69 @@ public void tuplesAreScalarizedWithoutLuaAllocations() throws IOException {
209219 assertFalse ("tuple arrays must be split into scalar arrays" , compiled .contains ("__wurst_arrIndex(" ));
210220 }
211221
222+ @ Test
223+ public void tupleFieldReadsOnlyLoadTheSelectedStorageComponent () throws IOException {
224+ String [] source = {
225+ "package Test" ,
226+ "native testSuccess()" ,
227+ "tuple vec3(real x, real y, real z)" ,
228+ "tuple segment(vec3 start, vec3 finish)" ,
229+ "vec3 array points" ,
230+ "segment array segments" ,
231+ "int indexCalls" ,
232+ "@noinline function nextIndex() returns int" ,
233+ " indexCalls++" ,
234+ " return 2" ,
235+ "@noinline function readAt(int index) returns real" ,
236+ " return points[index].z" ,
237+ "@noinline function readAtNext() returns real" ,
238+ " return points[nextIndex()].z" ,
239+ "@noinline function readNested(int index) returns real" ,
240+ " return segments[index].finish.y" ,
241+ "class Entity" ,
242+ " vec3 pos" ,
243+ "@noinline function readPos(Entity entity) returns real" ,
244+ " return entity.pos.z" ,
245+ "init" ,
246+ " points[2] = vec3(1., 2., 3.)" ,
247+ " segments[2] = segment(vec3(4., 5., 6.), vec3(7., 8., 9.))" ,
248+ " let entity = new Entity()" ,
249+ " entity.pos = vec3(4., 5., 6.)" ,
250+ " if readAt(2) == 3. and readAtNext() == 3. and indexCalls == 1" ,
251+ " and readNested(2) == 8. and readPos(entity) == 6." ,
252+ " testSuccess()"
253+ };
254+ test ().testLua (true ).executeProg ().lines (source );
255+
256+ String compiled = compileOptimizedLua (
257+ "tupleFieldReadsOnlyLoadTheSelectedStorageComponentOptimized" , source );
258+ String plainArrayRead = luaFunctionBody (compiled , "readAt" );
259+ String effectfulArrayRead = luaFunctionBody (compiled , "readAtNext" );
260+ String nestedArrayRead = luaFunctionBody (compiled , "readNested" );
261+ String memberRead = luaFunctionBody (compiled , "readPos" );
262+
263+ assertTrue (plainArrayRead .contains ("points_z[" ));
264+ assertFalse (plainArrayRead .contains ("points_x[" ));
265+ assertFalse (plainArrayRead .contains ("points_y[" ));
266+ assertTrue (effectfulArrayRead .contains ("points_z[" ));
267+ assertFalse (effectfulArrayRead .contains ("points_x[" ));
268+ assertFalse (effectfulArrayRead .contains ("points_y[" ));
269+ assertEquals ("a selected tuple-array field must evaluate its index exactly once" ,
270+ 1 , effectfulArrayRead .split ("nextIndex\\ (" , -1 ).length - 1 );
271+ assertTrue (nestedArrayRead .contains ("segments_finish_y[" ));
272+ assertFalse (nestedArrayRead .contains ("segments_start_" ));
273+ assertFalse (nestedArrayRead .contains ("segments_finish_x[" ));
274+ assertFalse (nestedArrayRead .contains ("segments_finish_z[" ));
275+ assertTrue (memberRead .contains ("Entity_pos_z_storage[" ));
276+ assertFalse (memberRead .contains ("Entity_pos_x_storage[" ));
277+ assertFalse (memberRead .contains ("Entity_pos_y_storage[" ));
278+ assertFalse ("selected tuple storage reads must not need discard helpers" ,
279+ plainArrayRead .contains ("__wurst_tuple_discard_" )
280+ || effectfulArrayRead .contains ("__wurst_tuple_discard_" )
281+ || nestedArrayRead .contains ("__wurst_tuple_discard_" )
282+ || memberRead .contains ("__wurst_tuple_discard_" ));
283+ }
284+
212285 @ Test
213286 public void tupleMemberArrayAssignmentCapturesIndex () throws IOException {
214287 test ().testLua (true ).luaOnly (false ).executeProg ().lines (
0 commit comments