alef 0.24.8

Opinionated polyglot binding generator for Rust libraries
Documentation
{{ header }}package {{ package }};

import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.List;

/**
 * Allocates Panama FFM upcall stubs for a Visitor and assembles
 * the generated C visitor callback table in native memory.
 */
@SuppressWarnings("PMD")
final class VisitorBridge implements AutoCloseable {
    private static final Linker LINKER = Linker.nativeLinker();
    private static final MethodHandles.Lookup LOOKUP =
        MethodHandles.lookup();

    // {{ result_type }} discriminant codes returned to C
{% for constant in result_constants %}
    private static final int VISIT_RESULT_{{ constant.code_name }} = {{ constant.code }};
{% endfor %}

    // Callback table: user_data + {{ num_callbacks }} callbacks
    // = {{ num_fields }} pointer-sized slots
    private static final long CALLBACKS_STRUCT_SIZE =
        (long) ValueLayout.ADDRESS.byteSize() * {{ num_fields }}L;

    // Visitor context field offsets for the FFI callback ABI.
    private static final long CTX_OFFSET_TAG_NAME = 8L;
    private static final long CTX_OFFSET_DEPTH = 16L;
    private static final long CTX_OFFSET_INDEX_IN_PARENT = 24L;
    private static final long CTX_OFFSET_PARENT_TAG = 32L;
    private static final long CTX_OFFSET_IS_INLINE = 40L;

    private static final MemoryLayout CTX_LAYOUT =
        MemoryLayout.structLayout(
            ValueLayout.JAVA_INT.withName("node_type"),
            MemoryLayout.paddingLayout(4),
            ValueLayout.ADDRESS.withName("tag_name"),
            ValueLayout.JAVA_LONG.withName("depth"),
            ValueLayout.JAVA_LONG.withName("index_in_parent"),
            ValueLayout.ADDRESS.withName("parent_tag"),
            ValueLayout.JAVA_INT.withName("is_inline"),
            MemoryLayout.paddingLayout(4)
    );

    private final Arena arena;
    private final MemorySegment struct;
    private final {{ trait_name }} visitor;
    /** Sticky exception captured during a visitor upcall; surfaced after conversion. */
    private volatile Throwable visitorError;

    VisitorBridge({{ trait_name }} visitor) {
        this.visitor = visitor;
        this.arena = Arena.ofConfined();
        this.struct = arena.allocate(CALLBACKS_STRUCT_SIZE);
        // Slot 0: user_data = NULL
        // (visitor captured via lambda closure)
        struct.set(ValueLayout.ADDRESS, 0L, MemorySegment.NULL);
        try {
            long offset = ValueLayout.ADDRESS.byteSize();
{% for stub_call in stub_calls %}
            offset = {{ stub_call }};
{% endfor %}
        } catch (ReflectiveOperationException e) {
            arena.close();
            throw new RuntimeException(
                "Failed to create visitor upcall stubs", e);
        }
    }

{% for stub_method in stub_methods %}
{{ stub_method }}
{% endfor %}

    /** Returns the native visitor callback table pointer. */
    MemorySegment callbacksStruct() {
        return struct;
    }

    void rethrowVisitorError() throws Throwable {
        Throwable err = visitorError;
        if (err != null) {
            throw err;
        }
    }

{% for handle_method in handle_methods %}
{{ handle_method }}
{% endfor %}

    private static {{ context_type }} decodeContext(
            final MemorySegment ctxPtr) {
        var ctx = ctxPtr.reinterpret(CTX_LAYOUT.byteSize());
        int nodeTypeRaw = ctx.get(ValueLayout.JAVA_INT, 0L);
{% if node_type_enum %}        {{ node_type_enum }} nodeType = {{ node_type_enum }}.values()[nodeTypeRaw];
{% else %}        int nodeType = nodeTypeRaw;
{% endif %}        var tagNamePtr = ctx.get(ValueLayout.ADDRESS, CTX_OFFSET_TAG_NAME);
        String tagName = tagNamePtr.reinterpret(Long.MAX_VALUE).getString(0);
        long depth = ctx.get(ValueLayout.JAVA_LONG, CTX_OFFSET_DEPTH);
        long indexInParent = ctx.get(ValueLayout.JAVA_LONG, CTX_OFFSET_INDEX_IN_PARENT);
        var parentTagPtr = ctx.get(ValueLayout.ADDRESS, CTX_OFFSET_PARENT_TAG);
        String parentTag = parentTagPtr.equals(MemorySegment.NULL) ? null
                : parentTagPtr.reinterpret(Long.MAX_VALUE).getString(0);
        int isInlineRaw = ctx.get(ValueLayout.JAVA_INT, CTX_OFFSET_IS_INLINE);
        return new {{ context_type }}(nodeType, tagName, depth, indexInParent, parentTag, isInlineRaw != 0);
    }

    private static List<String> decodeStringList(MemorySegment valuesPtr, long count) {
        var result = new ArrayList<String>((int) count);
        for (long i = 0; i < count; i++) {
            var ptr = valuesPtr.get(ValueLayout.ADDRESS, i * ValueLayout.ADDRESS.byteSize());
            result.add(ptr.reinterpret(Long.MAX_VALUE).getString(0));
        }
        return result;
    }

    private int encodeVisitResult({{ result_type }} result, MemorySegment outCustom, MemorySegment outLen) {
        return switch (result) {
{% for case in result_cases %}
{% if case.payload_field %}
            case {{ case.result_type }}.{{ case.variant_name }} c -> {
                var buf = Arena.global().allocateFrom(c.{{ case.payload_field }}());
                outCustom.reinterpret(ValueLayout.ADDRESS.byteSize()).set(ValueLayout.ADDRESS, 0L, buf);
                long customLen = (long) c.{{ case.payload_field }}().getBytes(java.nio.charset.StandardCharsets.UTF_8).length;
                outLen.reinterpret(ValueLayout.JAVA_LONG.byteSize()).set(ValueLayout.JAVA_LONG, 0L, customLen);
                yield VISIT_RESULT_{{ case.code_name }};
            }
{% else %}
            case {{ case.result_type }}.{{ case.variant_name }} ignored -> VISIT_RESULT_{{ case.code_name }};
{% endif %}
{% endfor %}
        };
    }

    @Override
    public void close() {
        arena.close();
    }
}