@@ -121,6 +121,119 @@ public void randomizedTupleValueSemanticsStayScalar() throws IOException {
121121 assertFalse (compiled .contains ("tupleEquals" ));
122122 }
123123
124+ @ Test
125+ public void randomizedTupleEvaluationMatchesInterpreterAndLua () throws IOException {
126+ Random random = new Random (0x0D1FF3A7L );
127+ List <String > source = new ArrayList <>();
128+ source .add ("package Test" );
129+ source .add ("native testSuccess()" );
130+ source .add ("tuple pair(int x, int y)" );
131+ source .add ("tuple nested(pair left, pair right)" );
132+ source .add ("int trace" );
133+ source .add ("int calls" );
134+ source .add ("class Holder" );
135+ source .add (" pair value" );
136+ source .add ("Holder current" );
137+ source .add ("Holder replacement" );
138+ source .add ("int currentIndex" );
139+ source .add ("pair array values" );
140+ source .add ("function mark(int value) returns int" );
141+ source .add (" trace = trace * 37 + value" );
142+ source .add (" return value" );
143+ source .add ("@noinline function produce(int seed) returns pair" );
144+ source .add (" calls++" );
145+ source .add (" return pair(mark(seed), mark(seed + 1))" );
146+ source .add ("@noinline function recursive(int seed) returns pair" );
147+ source .add (" if seed == 0" );
148+ source .add (" return pair(mark(7), recursive(1).x)" );
149+ source .add (" return pair(mark(seed), mark(seed + 10))" );
150+ source .add ("function retarget(int x, int y) returns pair" );
151+ source .add (" current = replacement" );
152+ source .add (" currentIndex = 2" );
153+ source .add (" return pair(mark(x), mark(y))" );
154+ source .add ("init" );
155+ source .add (" int checksum = 0" );
156+
157+ int expected = 0 ;
158+ for (int i = 0 ; i < 96 ; i ++) {
159+ int a = random .nextInt (9 ) + 1 ;
160+ int b = random .nextInt (9 ) + 1 ;
161+ int c = random .nextInt (9 ) + 1 ;
162+ int d = random .nextInt (9 ) + 1 ;
163+ switch (random .nextInt (6 )) {
164+ case 0 -> {
165+ boolean selectFirst = random .nextBoolean ();
166+ source .add (" trace = 0" );
167+ source .add (" let selected" + i + " = pair(mark(" + a + "), mark(" + b + "))."
168+ + (selectFirst ? "x" : "y" ));
169+ source .add (" checksum += trace + selected" + i + " * 13" );
170+ expected += a * 37 + b + (selectFirst ? a : b ) * 13 ;
171+ }
172+ case 1 -> {
173+ int selection = random .nextInt (4 );
174+ String [] paths = {"left.x" , "left.y" , "right.x" , "right.y" };
175+ int [] values = {a , b , c , d };
176+ source .add (" trace = 0" );
177+ source .add (" let selected" + i + " = nested(pair(mark(" + a + "), mark(" + b
178+ + ")), pair(mark(" + c + "), mark(" + d + ")))." + paths [selection ]);
179+ source .add (" checksum += trace + selected" + i + " * 17" );
180+ expected += (((a * 37 + b ) * 37 + c ) * 37 + d ) + values [selection ] * 17 ;
181+ }
182+ case 2 -> {
183+ source .add (" trace = 0" );
184+ source .add (" calls = 0" );
185+ source .add (" let selected" + i + " = produce(" + a + ").y" );
186+ source .add (" checksum += trace + selected" + i + " * 19 + calls * 23" );
187+ expected += a * 37 + (a + 1 ) + (a + 1 ) * 19 + 23 ;
188+ }
189+ case 3 -> {
190+ source .add (" trace = 0" );
191+ source .add (" calls = 0" );
192+ source .add (" if produce(" + a + ") != produce(" + b + ")" );
193+ source .add (" checksum += " + (a == b ? 29 : 31 ));
194+ source .add (" else" );
195+ source .add (" checksum += " + (a == b ? 31 : 29 ));
196+ source .add (" checksum += trace + calls * 37" );
197+ expected += 31
198+ + (((a * 37 + (a + 1 )) * 37 + b ) * 37 + (b + 1 )) + 2 * 37 ;
199+ }
200+ case 4 -> {
201+ source .add (" let original" + i + " = new Holder()" );
202+ source .add (" replacement = new Holder()" );
203+ source .add (" current = original" + i );
204+ source .add (" trace = 0" );
205+ source .add (" current.value = retarget(" + a + ", " + b + ")" );
206+ source .add (" checksum += original" + i + ".value.x * 41 + original" + i
207+ + ".value.y * 43 + replacement.value.x + trace" );
208+ expected += a * 41 + b * 43 + a * 37 + b ;
209+ }
210+ case 5 -> {
211+ source .add (" values[1] = pair(0, 0)" );
212+ source .add (" values[2] = pair(0, 0)" );
213+ source .add (" replacement = new Holder()" );
214+ source .add (" currentIndex = 1" );
215+ source .add (" trace = 0" );
216+ source .add (" values[currentIndex] = retarget(" + a + ", " + b + ")" );
217+ source .add (" checksum += values[1].x * 47 + values[1].y * 53 + values[2].x + trace" );
218+ expected += a * 47 + b * 53 + a * 37 + b ;
219+ }
220+ }
221+ }
222+ source .add (" trace = 0" );
223+ source .add (" let recursiveResult = recursive(0)" );
224+ source .add (" checksum += recursiveResult.x * 59 + recursiveResult.y * 61 + trace" );
225+ expected += 7 * 59 + 61 + ((7 * 37 + 1 ) * 37 + 11 );
226+ source .add (" if checksum == " + expected );
227+ source .add (" testSuccess()" );
228+
229+ // executeProg validates the source-level IM interpreter; testLua additionally runs the
230+ // scalarized output in Lua 5.3, making the generated program a deterministic differential test.
231+ test ().testLua (true ).executeProg ().lines (source .toArray (new String [0 ]));
232+ String compiled = compiledLua ("randomizedTupleEvaluationMatchesInterpreterAndLua" );
233+ assertFalse (compiled .contains ("tupleCopy" ));
234+ assertFalse (compiled .contains ("tupleEquals" ));
235+ }
236+
124237 @ Test
125238 public void tupleReturnSlotsAreSharedAcrossMultipleInterfaceRoots () throws IOException {
126239 test ().testLua (true ).executeProg ().lines (
0 commit comments