///|
/// A captured execution scope survives suspension. Nested scopes also charge
/// their parent, so a nested call cannot reset its caller's budget.
pub struct ExecutionScope {
  priv controls : FixedArray[@control.InvocationControl]
}

///|
pub fn ExecutionScope::failure(self : ExecutionScope) -> RuntimeError? {
  for i in 0.. Bool {
  for i in 0.. Unit raise RuntimeError {
  if self.poll() && self.failure() is Some(error) {
    raise error
  }
}

///|
pub fn Store::execution_scope(self : Store) -> ExecutionScope? {
  self.execution_scope
}

///|
/// Bound a group of calls and any continuations they publish. Safepoint units
/// count cooperative polls, not Wasm instructions or equivalent engine fuel.
/// Blocking synchronous host callbacks are checked after returning, not interrupted.
pub fn[T] Store::with_execution_limits(
  self : Store,
  run : () -> T raise,
  timeout_ms~ : Int32? = None,
  safepoints~ : Int64? = None,
  cancel~ : (() -> Bool)? = None,
) -> T raise {
  self.ensure_open()
  guard @control.InvocationControl::new(timeout_ms, cancel, safepoints~)
    is Some(control) else {
    raise InvalidExecutionLimit
  }
  let previous = self.execution_scope
  let controls = match previous {
    Some(scope) => scope.controls[:].to_owned()
    None => []
  }
  controls.push(control)
  let scope = ExecutionScope::{
    controls: FixedArray::makei(controls.length(), i => controls[i]),
  }
  self.execution_scope = Some(scope)
  defer {
    self.execution_scope = previous
  }
  for i in 0.. T raise E,
) -> T raise E {
  let previous = self.execution_scope
  let controls = match previous {
    Some(scope) => scope.controls[:].to_owned()
    None => []
  }
  if captured is Some(scope) {
    for control in scope.controls {
      if !controls.any(existing => physical_equal(existing, control)) {
        controls.push(control)
      }
    }
  }
  self.execution_scope = if controls.is_empty() {
    None
  } else {
    Some({ controls: FixedArray::makei(controls.length(), i => controls[i]), })
  }
  defer {
    self.execution_scope = previous
  }
  run()
}