Zulip Chat Archive
Stream: lean4
Topic: Monads in FFI
Leni Aniva (Mar 10 2024 at 15:32):
How can I tell the native function signature of a function that has generic/type arguments? e.g.
@[export generic_function]
def execCore {α} (context: Lean.Core.Context) (state: Lean.Core.State) (coreM: Lean.CoreM α):
IO (α × Lean.Core.State) :=
coreM.toIO context state
In the FFI documentation, it says that a type like the α
above is either erased or represented as a dummy. How can I determine which case happened for this symbol?
Leni Aniva (Mar 10 2024 at 15:56):
An even simplier example:
abbrev M := ExceptT String IO
@[export exec_monad]
def exec_monad {α} (m: M α): IO α := do
let a ← m
IO.println "exec_monad"
let b ← m
match b with
| .ok v => return v
| .error e => throw <| IO.userError <| e
@[export example_monad]
def example_monad: M Nat := do
IO.println "example_monad"
return 5
@[export another_example_monad]
def another_example_monad (s: String): M Nat := do
IO.println s
return s.length
and calling it with
lean_obj_res exec_monad(lean_obj_arg m, lean_obj_arg world);
lean_obj_res example_monad;
{
...
lean_obj_res result = exec_monad(example_monad, lean_io_mk_world());
}
leads to segmentation fault
Tomas Skrivan (Mar 10 2024 at 17:13):
If you put your file in ffi.lean
and call lean ffi.lean -c ffi.c
the output C file will contain
LEAN_EXPORT lean_object* exec_monad(lean_object*, lean_object*, lean_object*);
...
LEAN_EXPORT lean_object* exec_monad(lean_object* x_1, lean_object* x_2, lean_object* x_3) {
_start:
{
...
}
You can see that exec_monad
has three instead of two arguments and they correspond to {α}
, (m: M α)
and IO.RealWorld
. I think you should call your function as exec_monad(nullptr, example_monad, lean_io_mk_world())
. Type arguments are still present in the function signature but are ignored(as far I'm aware) so you can just pass nullptr
.
Leni Aniva (Mar 10 2024 at 19:42):
Tomas Skrivan said:
If you put your file in
ffi.lean
and calllean ffi.lean -c ffi.c
the output C file will containLEAN_EXPORT lean_object* exec_monad(lean_object*, lean_object*, lean_object*); ... LEAN_EXPORT lean_object* exec_monad(lean_object* x_1, lean_object* x_2, lean_object* x_3) { _start: { ... }
You can see that
exec_monad
has three instead of two arguments and they correspond to{α}
,(m: M α)
andIO.RealWorld
. I think you should call your function asexec_monad(nullptr, example_monad, lean_io_mk_world())
. Type arguments are still present in the function signature but are ignored(as far I'm aware) so you can just passnullptr
.
If I write it as
lean_obj_res result = exec_monad(NULL, example_monad(lean_io_mk_world()), lean_io_mk_world());
it segfaults in lean_apply_1
#0 0x00007fffea23d378 in ?? ()
#1 0x00007ffff79084a6 in lean_apply_1 () from /nix/store/092a3afjqb2kg62iqy7wzs8mi6g10bv9-lean-stage1/lib/lean/libleanshared.so
#2 0x00007ffff7fbe444 in exec_monad () from /nix/store/62bnbaf22qykwa5vxnjj4zvyhhl72kir-libCallee-dynlib/libCallee.so
I suspect this has something to do with the extra argument generated on example_monad
Leni Aniva (Mar 10 2024 at 20:04):
my hypothesis is that the execCore
function is not needed at all, and if there is a function of type Lean.CoreM _
, its signature will be
lean_obj_res some_moand(..., lean_obj_arg context, lean_obj_arg state, lean_obj_arg world);
Leni Aniva (Mar 10 2024 at 20:18):
However, if I have
@[export init_search]
unsafe def initSearch: IO Unit := do
Lean.enableInitializersExecution
Lean.initSearchPath (← Lean.findSysroot)
@[export create_core_context]
def createCoreContext: IO Lean.Core.Context := do
return {
currNamespace := Lean.Name.str .anonymous "Test"
openDecls := [], -- No 'open' directives needed
fileName := "<Filename>",
fileMap := { source := "", positions := #[0], lines := #[1] },
options := Lean.Options.empty,
}
@[export create_core_state]
def createCoreState: IO Lean.Core.State := do
let env ← Lean.importModules
(imports := #["Init".toName])
(opts := {})
(trustLevel := 1)
return { env := env }
@[export exec_core]
def execCore {α} (context: Lean.Core.Context) (state: Lean.Core.State) (coreM: Lean.CoreM α):
IO (α × Lean.Core.State) :=
coreM.toIO context state
@[export core_catalog]
def core_catalog: Lean.CoreM Unit := do
let env ← Lean.MonadEnv.getEnv
let names := env.constants.fold (init := "") (λ acc name _ => acc ++ name.toString)
IO.println names
and I call the functions
lean_obj_res unbox_io(lean_obj_arg obj) {
b_lean_obj_res ok = lean_inc(lean_io_result_get_value(obj));
lean_dec(obj);
return ok;
}
...
lean_obj_res res = unbox_io(init_search(lean_io_mk_world()));
lean_dec(res);
lean_obj_res context = unbox_io(create_core_context(lean_io_mk_world());
lean_obj_res state = unbox_io(create_core_state(lean_io_mk_world());
lean_obj_res res = core_catalog(context, state, lean_io_mk_world());
lean_dec(res);
Leni Aniva (Mar 10 2024 at 20:20):
this again results in a segfault in the fold function inside core_catalog
:
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7fbd8b1 in l_Lean_SMap_fold___at_core__catalog___spec__1___rarg () from /nix/store/ngnmajqx2pnshf8dx8rvqv65xx5ry4wd-libCallee-dynlib/libCallee.so
(gdb) bt
#0 0x00007ffff7fbd8b1 in l_Lean_SMap_fold___at_core__catalog___spec__1___rarg () from /nix/store/ngnmajqx2pnshf8dx8rvqv65xx5ry4wd-libCallee-dynlib/libCallee.so
#1 0x00007ffff7fbdcb4 in core_catalog () from /nix/store/ngnmajqx2pnshf8dx8rvqv65xx5ry4wd-libCallee-dynlib/libCallee.so
Sebastian Ullrich (Mar 10 2024 at 21:00):
You really should avoid using complex types like elaboration monads in FFI because it is way too easy to get wrong. There's a reason Lean itself avoids it.
Leni Aniva (Mar 10 2024 at 21:06):
Sebastian Ullrich said:
You really should avoid using complex types like elaboration monads in FFI because it is way too easy to get wrong. There's a reason Lean itself avoids it.
is it ok to just use CoreM
? I want to invoke tactics via the FFI
Leni Aniva (Mar 10 2024 at 22:14):
I found that the solution is to turn the state
argument into a reference via lean_st_mk_ref
.
Last updated: May 02 2025 at 03:31 UTC