Skip to content

Commit 982eee6

Browse files
committed
Fuzz tuple lowering against interpreter
1 parent d672261 commit 982eee6

1 file changed

Lines changed: 113 additions & 0 deletions

File tree

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaBackendAuditTests.java

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)