///|
// Binary Core guest: store/load the argument, call host.observe, then add one.
const GUEST : Bytes = b"\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x06\x01\x60\x01\x7f\x01\x7f\x02\x10\x01\x04\x68\x6f\x73\x74\x07\x6f\x62\x73\x65\x72\x76\x65\x00\x00\x03\x02\x01\x00\x05\x04\x01\x01\x01\x02\x07\x10\x02\x06\x6d\x65\x6d\x6f\x72\x79\x02\x00\x03\x72\x75\x6e\x00\x01\x0a\x15\x01\x13\x00\x41\x00\x20\x00\x36\x02\x00\x41\x00\x28\x02\x00\x10\x00\x41\x01\x6a\x0b\x00\x11\x04\x6e\x61\x6d\x65\x01\x0a\x01\x00\x07\x6f\x62\x73\x65\x72\x76\x65"

///|
fn run() -> Unit raise {
  let observed = Ref(0)
  let budget = @runtime.ResourceBudget::new(memory_bytes=131072L, instances=2L)
  let store = @runtime.Store(resource_budget=Some(budget))
  defer store.close()
  let linker = @runtime.Linker::with_store(store)
  linker.add_host_func(
    "host",
    "observe",
    args => {
      guard args is [I32(value)] else { raise TypeMismatch }
      observed.val = value
      [I32(value * 2)]
    },
    func_type={ params: [I32], results: [I32], },
  )
  let module_ = store.decode_module(GUEST)
  let validated = @validator.ValidatedModule::new(module_)
  let instance = @executor.instantiate_with_linker(
    linker,
    "guest",
    validated.snapshot(),
  )
  let function = @executor.Function::from_export(store, instance, "run")
  defer function.close()
  let results = function.call([@runtime.RootedValue::i32(21)])
  defer (for value in results {
    value.close()
  })
  assert_eq(results[0].scalar(), I32(43))
  assert_eq(observed.val, 21)
  assert_eq(store.get_mem(instance.mem_addrs[0]).load_i32(0), 21)
  store.close()
  assert_eq(budget.used(MemoryBytes), 0L)
  assert_eq(budget.used(Instances), 0L)
  assert_eq(budget.used(CompilationUnits), 0L)
  println(
    "portable interpreter: result=43, host observed=21, memory=21, budget released",
  )
}

///|
fn main {
  run() catch {
    error => {
      println(error.to_string())
      abort("embedding failed")
    }
  }
}