MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

lvl2ix regression test

+111
+111
tests/repro_newt_length.js
··· 1 + // Reproduces a bug where Prelude_length$27 (while-loop length) returns 0 2 + // for a 4-element linked list under ant after GC pressure. 3 + // 4 + // In the full newt bootstrap, lvl2ix calls length(ctx.env) and gets 0 back 5 + // instead of 4, producing a negative de Bruijn index (-3 instead of 1), 6 + // which cascades into "Missing case for Prelude.MkEq". 7 + // 8 + // The bug does not reproduce in isolation without GC pressure. 9 + 10 + const Nil = () => ({ tag: 0 }); 11 + const Cons = (h1, h2) => ({ tag: 1, h1: h1, h2: h2 }); 12 + 13 + // While-loop length (Prelude_length$27 from newt bootstrap) 14 + const length_go = (_, xs, acc) => { 15 + while (1) { 16 + let xs1 = xs; 17 + let acc1 = acc; 18 + if ((xs1.tag) == (1)) { 19 + xs = xs1.h2; 20 + acc = (acc1) + (1); 21 + continue; 22 + } else { 23 + return acc1; 24 + } 25 + } 26 + }; 27 + const length_prime = (xs) => length_go(xs, xs, 0); 28 + 29 + // lvl2ix from newt: l - k - 1 30 + const lvl2ix = (l, k) => l - k - 1; 31 + 32 + // Build a VVar-like object (matches newt's object shape) 33 + const VVar = (h0, h1, h2) => ({ tag: 0, h0: h0, h1: h1, h2: h2 }); 34 + const emptyFC = { tag: 0, h0: "", h1: { tag: 0, h0: 0, h1: 0, h2: 0, h3: 0 } }; 35 + const Lin = () => ({ tag: 0 }); 36 + 37 + // Simulates what quoteAlt_mkenv does: builds an env list with VVar nodes 38 + function mkenv(lvl, env, names) { 39 + while (1) { 40 + if ((names.tag) == (1)) { 41 + let newVar = VVar(emptyFC, lvl, Lin()); 42 + lvl = lvl + 1; 43 + env = Cons(newVar, env); 44 + names = names.h2; 45 + continue; 46 + } else { 47 + return env; 48 + } 49 + } 50 + } 51 + 52 + // Simulates the eval/quote cycle that builds up env lists 53 + function simulateEval(env, depth) { 54 + if (depth <= 0) return env; 55 + 56 + // Build up environment like newt's eval does 57 + let fresh = VVar(emptyFC, length_prime(env), Lin()); 58 + let newEnv = Cons(fresh, env); 59 + 60 + // Allocate throwaway objects to create GC pressure 61 + for (let i = 0; i < 100; i++) { 62 + let junk = { tag: 5, h0: emptyFC, h1: i, h2: { tag: 0 }, h3: "x", h4: { tag: 0, h0: env, h1: { tag: 0 } } }; 63 + } 64 + 65 + return simulateEval(newEnv, depth - 1); 66 + } 67 + 68 + let fail = false; 69 + 70 + // Run many iterations to trigger GC 71 + for (let round = 0; round < 500; round++) { 72 + // Start with a 4-element env (matching the failing case) 73 + let env = Cons(VVar(emptyFC, 3, Lin()), 74 + Cons(VVar(emptyFC, 2, Lin()), 75 + Cons(VVar(emptyFC, 1, Lin()), 76 + Cons(VVar(emptyFC, 0, Lin()), 77 + Nil())))); 78 + 79 + let len = length_prime(env); 80 + if (len !== 4) { 81 + console.log("FAIL round " + round + ": length(env) = " + len + ", expected 4"); 82 + console.log(" lvl2ix would give: " + lvl2ix(len, 1) + " (expected 2, got " + lvl2ix(len, 1) + ")"); 83 + fail = true; 84 + break; 85 + } 86 + 87 + // Simulate eval growing the environment 88 + let bigEnv = simulateEval(env, 20); 89 + let bigLen = length_prime(bigEnv); 90 + if (bigLen !== 24) { 91 + console.log("FAIL round " + round + ": length(bigEnv) = " + bigLen + ", expected 24"); 92 + fail = true; 93 + break; 94 + } 95 + 96 + // mkenv path (like quoteAlt_mkenv) 97 + let names = Cons("a", Cons("b", Cons("c", Nil()))); 98 + let extEnv = mkenv(length_prime(env), env, names); 99 + let extLen = length_prime(extEnv); 100 + if (extLen !== 7) { 101 + console.log("FAIL round " + round + ": length(extEnv) = " + extLen + ", expected 7"); 102 + fail = true; 103 + break; 104 + } 105 + } 106 + 107 + if (!fail) { 108 + console.log("PASS"); 109 + } else { 110 + process.exit(1); 111 + }