// |jit-test| skip-if: !wasmReftypesEnabled()

// Generates a bunch of numbers-on-the-heap, and tries to ensure that they are
// held live -- at least for a short while -- only by references from the wasm
// evaluation stack.  Then assembles them in a list and checks that the list
// is as expected (and we don't segfault).  While all this is running we also
// have an regular interrupt whose handler does a bunch of allocation, so as
// to cause as much disruption as possible.

// Note this makes an assumption about how the wasm compiler works.  There's
// no particular reason that the wasm compiler needs to keep the results of
// the $mkBoxedInt calls on the machine stack.  It could equally cache them in
// registers or even reorder the call sequences so as to interleave
// construction of the list elements with construction of the list itself.  It
// just happens that our baseline compiler will behave as described.  That
// said, however, it's hard to imagine how an implementation could complete
// the list construction without having at least one root in a register or on
// the stack, so the test still has value regardless of how the underlying
// implementation works.

const {Module,Instance} = WebAssembly;

const DEBUG = false;

let t =
  `(module
     (import $mkCons "" "mkCons" (func (result anyref)
                                       (param anyref) (param anyref)))
     (import $mkBoxedInt "" "mkBoxedInt" (func (result anyref)))

     (func $mkNil (result anyref)
       ref.null
     )

     (func $mkConsIgnoringScalar (result anyref)
              (param $hd anyref) (param i32) (param $tl anyref)
        (local.get $hd)
        (local.get $tl)
        call $mkCons
     )

     (func $mkList (export "mkList") (result anyref)
        call $mkList20
     )

     (func $mkList20 (result anyref)
       ;; create 20 pointers to boxed ints on the stack, plus a few
       ;; scalars for added confusion
       (local $scalar99 i32)
       (local $scalar97 i32)
       (local.set $scalar99 (i32.const 99))
       (local.set $scalar97 (i32.const 97))

       call $mkBoxedInt
       local.get $scalar99
       call $mkBoxedInt
       call $mkBoxedInt
       local.get $scalar97
       call $mkBoxedInt
       call $mkBoxedInt
       call $mkBoxedInt
       call $mkBoxedInt
       call $mkBoxedInt
       call $mkBoxedInt
       call $mkBoxedInt
       call $mkBoxedInt
       call $mkBoxedInt
       call $mkBoxedInt
       call $mkBoxedInt
       call $mkBoxedInt
       call $mkBoxedInt
       call $mkBoxedInt
       call $mkBoxedInt
       call $mkBoxedInt
       call $mkBoxedInt
       call $mkNil
       ;; Now we have (pointers to) 20 boxed ints and a NIL on the stack, and
       ;; nothing else holding them live.  Build a list from the elements.
       call $mkCons
       call $mkCons
       call $mkCons
       call $mkCons
       call $mkCons
       call $mkCons
       call $mkCons
       call $mkCons
       call $mkCons
       call $mkCons
       call $mkCons
       call $mkCons
       call $mkCons
       call $mkCons
       call $mkCons
       call $mkCons
       call $mkCons
       call $mkConsIgnoringScalar
       call $mkCons
       call $mkConsIgnoringScalar
     )
   )`;

let boxedIntCounter = 0;

function BoxedInt() {
    this.theInt = boxedIntCounter;
    boxedIntCounter++;
}

function mkBoxedInt() {
    return new BoxedInt();
}

function printBoxedInt(bi) {
    print(bi.theInt);
}

function Cons(hd, tl) {
    this.hd = hd;
    this.tl = tl;
}

function mkCons(hd, tl) {
    return new Cons(hd, tl);
}

function showList(list) {
    print("[");
    while (list) {
        printBoxedInt(list.hd);
        print(",");
        list = list.tl;
    }
    print("]");
}

function checkList(list, expectedHdValue, expectedLength) {
    while (list) {
        if (expectedLength <= 0)
            return false;
        if (list.hd.theInt !== expectedHdValue) {
            return false;
        }
        list = list.tl;
        expectedHdValue++;
        expectedLength--;
    }
    if (expectedLength == 0) {
        return true;
    } else {
        return false;
    }
}

let i = wasmEvalText(t, {"":{mkCons: mkCons, mkBoxedInt: mkBoxedInt}});


function Croissant(chocolate) {
    this.chocolate = chocolate;
}

function allocates() {
    return new Croissant(true);
}

function handler() {
    if (DEBUG) {
        print('XXXXXXXX icallback: START');
    }
    let q = allocates();
    let sum = 0;
    for (let i = 0; i < 15000; i++) {
        let x = allocates();
        // Without this hoop jumping to create an apparent use of |x|, Ion
        // will remove the allocation call and make the test pointless.
        if (x == q) { sum++; }
    }
    // Artificial use of |sum|.  See comment above.
    if (sum == 133713371337) { print("unlikely!"); }
    timeout(1, handler);
    if (DEBUG) {
        print('XXXXXXXX icallback: END');
    }
    return true;
}

timeout(1, handler);

for (let n = 0; n < 10000; n++) {
    let listLowest = boxedIntCounter;

    // Create the list in wasm land, possibly inducing GC on the way
    let aList = i.exports.mkList();

    // Check it is as we expect
    let ok = checkList(aList, listLowest, 20/*expected length*/);
    if (!ok) {
        print("Failed on list: ");
        showList(aList);
    }
    assertEq(ok, true);
}

// If we get here, the test finished successfully.
