Zulip Chat Archive

Stream: lean4

Topic: Structure in FFI


Marcus Rossel (Apr 08 2024 at 14:13):

How does one access the fields of a Lean structure in C? For example, I have the following setup:

structure A where
  b : Bool
  s : String
  deriving Inhabited

@[extern "test"]
opaque test : A  A

def main : IO Unit :=
  let a : A := { b := false, s := "" }
  let a' := test a
  IO.println s!"⟨{a'.b}, {a'.s}⟩"
#include <lean/lean.h>
#include <stdio.h>

lean_obj_res test(lean_obj_arg a) {
    if (!lean_is_ctor(a)) {
        printf("a is not a ctor\n");
        return a;
    }

    unsigned n = lean_ctor_num_objs(a);
    printf("num objs: %d\n", n);

    lean_obj_res field_0 = lean_ctor_get(a, 0);
    lean_obj_res field_1 = lean_ctor_get(a, 1); // fails

    return a;
}

Running this fails with the following output:

num objs: 1
LEAN ASSERTION VIOLATION
File: /Users/marcus/.elan/toolchains/stable/include/lean/lean.h
Line: 549
i < lean_ctor_num_objs(o)

So I guess lean_ctor_get isn't the correct function for accessing structures' fields.
How are structures/inductive types represented in the FFI then?

Mario Carneiro (Apr 08 2024 at 14:18):

inductives are represented as two parts:

  • the lean_object* fields
  • the scalar part (a byte buffer)

To offset into the first part, use lean_ctor_get. To get the second part use lean_ctor_set_uint32 or similar functions, depending on the scalar type

Mario Carneiro (Apr 08 2024 at 14:18):

offsets in the second part are represented in bytes, so if there are two uint32s then one will have offset 0 and the next offset 4

Marcus Rossel (Apr 08 2024 at 14:19):

Mario Carneiro said:

inductives are represented as two parts:

  • the lean_object* fields
  • the scalar part (a byte buffer)

Is that somehow related to this?

typedef struct {
    lean_object   m_header;
    lean_object * m_objs[0];
} lean_ctor_object;

Mario Carneiro (Apr 08 2024 at 14:19):

Your structure A has a single pointer field, namely b : String, and one byte of scalar data, namely a : Bool as a uint8 at offset 0

Mario Carneiro (Apr 08 2024 at 14:22):

The struct doesn't actually say too much about this. Both parts I mentioned are actually indexed from m_objs as far as the C code is concerned, but some metadata about the number of fields of each kind is given in m_header so that it can do the necessary indexing

Marcus Rossel (Apr 08 2024 at 14:28):

Hmm, so from your explanation I thought this would work:

lean_obj_res test(lean_obj_arg a) {
    if (!lean_is_ctor(a)) {
        printf("a is not a ctor\n");
        return a;
    }

    unsigned n = lean_ctor_num_objs(a);
    printf("num objs: %d\n", n);

    uint8_t b = lean_ctor_get_uint8(a, 0);
    printf("b: %d\n", b);

    lean_obj_res s = lean_ctor_get(a, 0);
    char* s_cstr = lean_string_cstr(s);
    printf("s: %s\n", s_cstr);

    return a;
}

But that still fails with:

num objs: 1
LEAN ASSERTION VIOLATION
File: /Users/marcus/.elan/toolchains/stable/include/lean/lean.h
Line: 576
offset >= lean_ctor_num_objs(o) * sizeof(void*)

Marcus Rossel (Apr 08 2024 at 14:30):

Ahhh, so the byte buffer starts after the pointer fields, so the offset always has to be desired_offset + lean_ctor_num_objs(a)?

Mario Carneiro (Apr 08 2024 at 14:30):

it does, but lean_ctor_get_uint8 should handle that

Mario Carneiro (Apr 08 2024 at 14:30):

oh no, you are right

Marcus Rossel (Apr 08 2024 at 14:31):

Hmm, can you think of cases where one would use that function without the + lean_ctor_num_objs(_)? Seems like something that could be built in to the function.

Mario Carneiro (Apr 08 2024 at 14:33):

lean code does it like this

lean_ctor_set_uint8(x_3, sizeof(void*)*1, x_2);

Mario Carneiro (Apr 08 2024 at 14:35):

I agree it's a bit messy to be letting this implementation detail leak. The intent is that this will be constant folded by the C compiler, but lean code generating the C is trying to be agnostic about pointer size

Marcus Rossel (Apr 08 2024 at 14:37):

Cool, thanks for the help! Do you think adding some doc comments on some of the lean.h functions would be welcome? I personally found it quite difficult to understand anything non-trivial in there so far.

Mario Carneiro (Apr 08 2024 at 14:37):

Docs on everything is missing and important and welcome

Mario Carneiro (Apr 08 2024 at 14:38):

Note also that USize fields are ordered at the beginning of the scalar section, so you also have to skip them in the sizeof(void*) part of the count

Mario Carneiro (Apr 08 2024 at 14:39):

besides that, I believe scalars are just placed in declaration order (with padding, I think?)

Marcus Rossel (Apr 08 2024 at 14:40):

My real-world application only has bools, so I'll stay blissfully ignorant about that for now :sweat_smile:

Marcus Rossel (Apr 08 2024 at 18:54):

Doc comment PR: https://github.com/leanprover/lean4/pull/3846

Tomas Skrivan (Apr 08 2024 at 22:03):

Just a tip, I usually write a Lean function that does the structure manipulation, like accessing fields, compile it and inspecting the generated C code to see how the access is done exactly.

Maybe you can also add(not tested as I'm on mobile)

attribute [export A_get_s] A.s
attribute [export A_get_b] A.b

Compile it and copy paste the generated functions A_get_s and A_get_b to your C code.

Tomas Skrivan (Apr 08 2024 at 22:04):

(deleted)

Tomas Skrivan (Apr 08 2024 at 22:06):

(deleted)

Eric Wieser (Apr 11 2024 at 15:05):

As far as I can tell, attribute [export foo_bar] Foo.bar doesn't actually do anything on projections

Tomas Skrivan (Apr 11 2024 at 15:07):

I see, then you can just define a new function that calls the projection and export that and inspect.

Eric Wieser (Apr 11 2024 at 15:07):

Probably it should raise an error since it doesn't work?

Mario Carneiro (Apr 11 2024 at 15:55):

export on structures is currently broken (lean4#2292), I don't recommend it

Eric Wieser (Apr 11 2024 at 16:16):

That's only for single-field structures, right?

Leni Aniva (Apr 16 2024 at 16:54):

Marcus Rossel said:

Cool, thanks for the help! Do you think adding some doc comments on some of the lean.h functions would be welcome? I personally found it quite difficult to understand anything non-trivial in there so far.

I think it helps to find the relevant symbol in the Lean source code (.lean) since these tend to be better documented


Last updated: May 02 2025 at 03:31 UTC