{{ 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.
*/
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();
}
}