// Tests stepping through the wasm code with JS PI suspendable stack.

const g = newGlobal({ newCompartment: true });
const dbg = new Debugger(g);

// Estimate internal SP range.
var base = stackPointerInfo();
var estimatedLimit = base - 300000;
var checkFailed = false;
var checksPerformed = {};

// Checks current SP being in the estimated range --
// the debugger callbacks must be executed on the main stack.
function checkStack(s) {
  var sp = stackPointerInfo();
  checksPerformed[s] = true;
  if (sp < estimatedLimit || sp > base) {
    print(`Check failed: ${sp} not in [${estimatedLimit}, ${base}], at ${s}`);
    checkFailed = true;
  }
}
checkStack();
assertEq(checkFailed, false);

dbg.onEnterFrame = function(frame) {
  if (frame.type != "wasmcall") {
    return;
  }
  checkStack("enter");
  frame.onStep = () => {
    checkStack("step");
    frame.offset; // check if frame is valid
  };
  frame.onPop = () => {
    checkStack("pop");
  }
};
dbg.onExceptionUnwind = function (f, e) {
  checkStack("exception");
};

// Run typical JS PI program: create suspendable stack, suspend execution,
// throw on suspendable stack.
g.eval(`
function wasmEvalText(t, imp) {
  var wasm = wasmTextToBinary(t)
  var mod = new WebAssembly.Module(wasm);
  var ins = new WebAssembly.Instance(mod, imp);
  return ins;
}

try{throw"";}catch(_){} // init onExceptionUnwind

var compute_delta = (i) => {
  return Promise.resolve(i/100 || 1);
};

var suspending_compute_delta = new WebAssembly.Suspending(compute_delta);
var ins = wasmEvalText(\`(module
    (import "js" "compute_delta"
      (func $compute_delta (param i32) (result f64)))
    (tag $t)
    (func (export "update_state_export") (param i32) (result f64)
      local.get 0
      call $compute_delta
      i32.const 4
      i32.const 2
      i32.add
      drop
      throw $t
    )
)\`, {
  js: {
    compute_delta: suspending_compute_delta,
  },
});

var update_state = WebAssembly.promising(ins.exports.update_state_export);

var res = update_state(4);
res.then((r) => {
  print("RES:" + r);
}).catch(e => {
  print("ERR: " + e);
});

drainJobQueue();
`);

assertEq(checkFailed, false);
for (let i of ['enter', 'step', 'pop', 'exception']) {
  assertEq(i in checksPerformed, true, `${i} check performed`);
}
