Zulip Chat Archive

Stream: lean4

Topic: FFI: lean_io_promise_new


Max Nowak 🐉 (Jun 09 2024 at 09:24):

I am trying to create a promise (context: doing some wgpu bindings).
I have the following code MWE. Oddly enough, lean_io_promise_new does return something, but it doesn't seem to return a valid lean object, since the tag is 0, so I am confused. I expect the tag to be 252, which is a LeanTask. What's the proper way to create a promise in FFI?

There are functions for managing tasks in lean.h, which work and I can spawn a lean task object with the 252 tag.
But lean_io_promise_new is from the runtime, not from lean.h, so maybe that breaks things in some way. Is there a reason why lean.h doesn't have functions for managing promises?

import Alloy.C
open scoped Alloy.C
open IO

alloy c section
  #include <stdio.h>
  #include <stdlib.h>
  #include <lean/lean.h>

  -- hacky. Copied from the compiled output from lean runtime, search `lean_io_promise_new` in the lean4 repo source.
  extern lean_object* lean_io_promise_new(lean_object* seemsNotUsed);
  extern lean_object* lean_io_promise_resolve(lean_object* value, lean_object* promise, lean_object* seemsNotUsed);
end

alloy c extern
def foo : IO (Promise UInt32) := {
  lean_object *promise = lean_io_promise_new(lean_box(1337)); -- call Promise.new
  fprintf(stderr, "promise tag is %d\n", lean_ptr_tag(promise));
  if (lean_ptr_tag((lean_object*) promise) != LeanTask) {
    abort();
  }
  fprintf(stderr, "checkpoint\n");
  return lean_io_result_mk_ok(promise);
}

And then in another file:

import TheAboveFile

def main : IO Unit := do
  let p : Promise UInt32 <- foo
  eprintln s!"got {p.result.get}"

Eric Wieser (Jun 09 2024 at 09:27):

lean_io_promise_new returns an docs#EStateM.Result, I think?

Eric Wieser (Jun 09 2024 at 09:28):

So you need to unpack that to get at the task

Eric Wieser (Jun 09 2024 at 09:28):

You probably want to call get_io_result?

Max Nowak 🐉 (Jun 09 2024 at 09:31):

The signature is the following, which doesn't mention Result. Whatever it returns, shouldn't it still return a valid lean object though? The tag 0 doesn't seem to denote any valid lean object.

@[extern "lean_io_promise_new"]
opaque Promise.new [Nonempty α] : BaseIO (Promise α)

get_io_result is a c++ thing in the runtime and not tagged extern :/

Max Nowak 🐉 (Jun 09 2024 at 09:33):

Ohhh wait I think you are right, it's just called differently in lean.h

Max Nowak 🐉 (Jun 09 2024 at 09:34):

Yep the following works. Awesome. Thanks.

  lean_object *io_res = lean_io_promise_new(lean_box(1337)); -- call Promise.new
  lean_object *promise = lean_io_result_get_value(io_res);

Eric Wieser (Jun 09 2024 at 09:37):

The signature is the following, which doesn't mention Result.

It returns BaseIO, which is

-- fun α => IO.RealWorld → EStateM.Result Empty PUnit.{1} α
#reduce BaseIO

Eric Wieser (Jun 09 2024 at 09:38):

I think this means that lean_io_promise_new is incorrect, and should take an extra (ignored) IO.RealWorld argument?

Max Nowak 🐉 (Jun 09 2024 at 09:39):

Yeah that's what I thought as well, but if you look at the generated C code, it only takes one lean_object. And this way I do get a closure back, so it works.

Max Nowak 🐉 (Jun 09 2024 at 09:52):

Great success!

def triangle : IO Unit := do
  let desc <- WGPUInstanceDescriptor.mk
  let inst <- WGPUInstance.mk desc
  let adapter : Promise WGPUAdapter <- WGPUAdapter.mk inst
  eprintln "created adapter promise!"
  dostuff adapter.result.get

Eric Wieser (Jun 09 2024 at 11:09):

Ah nevermind; what I meant is that you should be using lean_io_promise_new(io_mk_world())

Max Nowak 🐉 (Jun 09 2024 at 11:10):

Ah yeah. But it’s irrelevant because the argument is entirely ignored.

Max Nowak 🐉 (Jun 09 2024 at 11:11):

Thanks for you help!

Henrik Böving (Jun 09 2024 at 12:10):

Eric Wieser said:

Ah nevermind; what I meant is that you should be using lean_io_promise_new(io_mk_world())

Usually we just use the world argument that we get passed in over the FFI boundary

Eric Wieser (Jun 09 2024 at 13:43):

What does that look like in alloy?

Henrik Böving (Jun 09 2024 at 13:46):

Good question actually. @Mac Malone did you think about that?

Mac Malone (Jun 09 2024 at 14:32):

Henrik Böving said:

Usually we just use the world argument that we get passed in over the FFI boundary

While one might think you would do this (and did at first as well), Lean C code usually just creates a new world rather than reusing the old. Part of the reason is that a world is just the scalar 1 (box(0)).

Mac Malone (Jun 09 2024 at 14:36):

Henrik Böving said:

Mac Malone did you think about that?

Unnamed arguments (such as the embedded in BaseIO) are named by their numerical index. In this case, it would be _0.


Last updated: May 02 2025 at 03:31 UTC