Skip to content

Commit

Permalink
Handle glong/gulong fields on windows (fixes #106)
Browse files Browse the repository at this point in the history
  • Loading branch information
jwharm committed Jul 14, 2024
1 parent a8fd278 commit 74795da
Show file tree
Hide file tree
Showing 19 changed files with 355 additions and 154 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ tasks.named('test', Test) {

// Configure library path for Windows (MSYS2)
else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
jvmArgs += '-Djava.library.path=C:/msys64/mingw64/bin'
jvmArgs += '-Djava.library.path=C:/dev/msys64/mingw64/bin'
}

useJUnitPlatform()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,18 +134,26 @@ private MethodSpec fromTargetType() {
}

private MethodSpec arrayConstructor(Type primitiveType) {
String layout = getValueLayoutPlain(primitiveType);
String layout = getValueLayoutPlain(primitiveType, false);

var spec = MethodSpec.methodBuilder("fromNativeArray")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(ArrayTypeName.of(alias.typeName()))
.addParameter(MemorySegment.class, "address")
.addParameter(long.class, "length")
.addParameter(boolean.class, "free")
.addStatement("$T array = new $T[(int) length]",
ArrayTypeName.of(alias.typeName()), alias.typeName())
.addStatement("long byteSize = $T.$L.byteSize()",
ValueLayout.class, layout)
.addStatement("$T segment = address.reinterpret(byteSize * length)",
ArrayTypeName.of(alias.typeName()), alias.typeName());

if (primitiveType.isLong()) {
spec.addStatement("long byteSize = $1T.longAsInt() ? $2T.JAVA_INT.byteSize() : $2T.JAVA_LONG.byteSize()",
ClassNames.INTEROP, ValueLayout.class);
} else {
spec.addStatement("long byteSize = $T.$L.byteSize()",
ValueLayout.class, layout);
}

spec.addStatement("$T segment = address.reinterpret(byteSize * length)",
MemorySegment.class)
.beginControlFlow("for (int i = 0; i < length; i++)");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import com.squareup.javapoet.*;
import io.github.jwharm.javagi.configuration.ClassNames;
import io.github.jwharm.javagi.gir.*;
import io.github.jwharm.javagi.util.Conversions;
import io.github.jwharm.javagi.util.PartialStatement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand All @@ -32,11 +31,11 @@
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.util.*;
import java.util.stream.Collectors;

import static io.github.jwharm.javagi.util.Conversions.getValueLayout;
import static io.github.jwharm.javagi.util.Conversions.toJavaIdentifier;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;

public class CallableGenerator {

Expand All @@ -55,42 +54,47 @@ CodeBlock generateFunctionDescriptorDeclaration() {
}

CodeBlock generateFunctionDescriptor() {
List<String> valueLayouts = new ArrayList<>();
List<String> layouts = new ArrayList<>();

var returnType = callable.returnValue().anyType();
boolean isVoid = returnType instanceof Type t && t.isVoid();
if (!isVoid)
valueLayouts.add(getValueLayout(returnType));
layouts.add(generateValueLayout(returnType));

if (callable instanceof Signal)
valueLayouts.add("ADDRESS");
layouts.add("$valueLayout:T.ADDRESS");

if (callable.parameters() != null) {
var iParam = callable.parameters().instanceParameter();
if (iParam != null)
valueLayouts.add(getValueLayout(iParam.anyType()));
valueLayouts.addAll(
layouts.add(generateValueLayout(iParam.anyType()));
layouts.addAll(
callable.parameters().parameters().stream()
.filter(not(Parameter::varargs))
.map(Parameter::anyType)
.map(Conversions::getValueLayout)
.map(this::generateValueLayout)
.toList());
}

if (callable.throws_())
valueLayouts.add("ADDRESS");
layouts.add("$valueLayout:T.ADDRESS");

if (valueLayouts.isEmpty()) {
if (layouts.isEmpty())
return CodeBlock.of("$T.ofVoid()", FunctionDescriptor.class);
} else {
String layouts = valueLayouts.stream()
.map(s -> "$2T." + s)
.collect(Collectors.joining(",$W", "(", ")"));
return CodeBlock.of("$1T.$3L" + layouts,
FunctionDescriptor.class,
ValueLayout.class,
isVoid ? "ofVoid" : "of");
}

return PartialStatement.of("$functionDescriptor:T."
+ (isVoid ? "ofVoid" : "of"))
.add(layouts.stream().collect(joining(",$W", "(", ")")),
"functionDescriptor", FunctionDescriptor.class,
"valueLayout", ValueLayout.class,
"interop", ClassNames.INTEROP)
.toCodeBlock();
}

String generateValueLayout(AnyType anyType) {
return anyType instanceof Type type && type.isLong()
? "$interop:T.longAsInt() ? $valueLayout:T.JAVA_INT : $valueLayout:T.JAVA_LONG"
: "$valueLayout:T." + getValueLayout(anyType, false);
}

void generateMethodParameters(MethodSpec.Builder builder,
Expand Down Expand Up @@ -125,7 +129,7 @@ else if (p.notNull())
}
}

PartialStatement marshalParameters() {
PartialStatement marshalParameters(boolean intAsLong) {
var parameters = callable.parameters();
if (parameters == null)
return callable.throws_()
Expand Down Expand Up @@ -155,6 +159,10 @@ PartialStatement marshalParameters() {
if (generator.checkNull())
stmt.add("($memorySegment:T) (" + generator.getName() + " == null ? $memorySegment:T.NULL : ");

// cast int parameter to a long
if (intAsLong && p.anyType() instanceof Type t && t.isLong())
stmt.add("(long) ");

// Callback destroy
if (p.isDestroyNotifyParameter()) {
var notify = parameters.parameters().stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ MethodSpec generateUpcallMethod(String methodName, String name, String methodToI
MethodSpec.Builder upcall = MethodSpec.methodBuilder(name)
.returns(returnsVoid
? TypeName.VOID
: getCarrierTypeName(returnValue.anyType()));
: getCarrierTypeName(returnValue.anyType(), false));

// Javadoc
if (methodToInvoke.equals("run"))
Expand All @@ -127,7 +127,7 @@ MethodSpec generateUpcallMethod(String methodName, String name, String methodToI
// Add parameters (native carrier types)
if (closure.parameters() != null)
for (Parameter p : closure.parameters().parameters())
upcall.addParameter(getCarrierTypeName(p.anyType()),
upcall.addParameter(getCarrierTypeName(p.anyType(), false),
toJavaIdentifier(p.name()));

// GError** parameter
Expand Down Expand Up @@ -182,7 +182,7 @@ MethodSpec generateUpcallMethod(String methodName, String name, String methodToI

// Null-check the return value
if ((!returnsVoid)
&& getCarrierTypeName(returnValue.anyType()).equals(TypeName.get(MemorySegment.class))
&& getCarrierTypeName(returnValue.anyType(), false).equals(TypeName.get(MemorySegment.class))
&& (!returnValue.notNull()))
upcall.addStatement("if (_result == null) return $T.NULL",
MemorySegment.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package io.github.jwharm.javagi.generators;

import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import io.github.jwharm.javagi.configuration.ClassNames;
import io.github.jwharm.javagi.gir.*;
import io.github.jwharm.javagi.gir.Class;
Expand Down Expand Up @@ -66,9 +67,14 @@ private String methodName(String prefix) {
public MethodSpec generateReadMethod() {
// To read from ...** fields, you must provide the length of the array.
boolean isArray = type != null && type.isActuallyAnArray();

// Override the type of long values
boolean isLong = type != null && type.isLong();
TypeName typeName = isLong ? TypeName.INT : getType();

var spec = MethodSpec.methodBuilder(methodName(READ_PREFIX))
.addModifiers(Modifier.PUBLIC)
.returns(getType())
.returns(typeName)
.addJavadoc("Read the value of the field {@code $L}.\n\n", f.name());
if (isArray)
spec.addJavadoc("@param length the number of {@code $L} to read", f.name());
Expand All @@ -94,7 +100,7 @@ public MethodSpec generateReadMethod() {
}

// Read a pointer or primitive value from the struct
var carrierType = getCarrierTypeName(f.anyType());
var carrierType = getCarrierTypeName(f.anyType(), true);
var getResult = "var _result = ($T) getMemoryLayout()$Z.varHandle($T.PathElement.groupElement($S)).get(handle(), 0)";
var returnResult = PartialStatement.of("return ")
.add(marshalNativeToJava("_result", false))
Expand All @@ -114,7 +120,10 @@ public MethodSpec generateWriteMethod() {
@param $2L The new value for the field {@code $1L}
""", f.name(), getName());

spec.addParameter(getType(), getName());
// Override the type of long values
boolean isLong = type != null && type.isLong();
TypeName typeName = isLong ? TypeName.INT : getType();
spec.addParameter(typeName, getName());

if (f.allocatesMemory())
spec.addJavadoc("@param _arena to control the memory allocation scope\n")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package io.github.jwharm.javagi.generators;

import com.squareup.javapoet.MethodSpec;
import io.github.jwharm.javagi.configuration.ClassNames;
import io.github.jwharm.javagi.gir.Class;
import io.github.jwharm.javagi.gir.*;
import io.github.jwharm.javagi.gir.Record;
Expand Down Expand Up @@ -63,23 +64,46 @@ MethodSpec generateMemoryLayout(RegisteredType rt) {

boolean isUnion = rt instanceof Union || !unionList.isEmpty();

// The $> and $< in the statement increase and decrease indentation
var layout = PartialStatement.of("return $memoryLayout:T."
+ (isUnion ? "union" : "struct") + "Layout(\n$>")
.add(generateFieldLayouts(fieldList, isUnion))
.add("$<\n).withName(\"" + rt.cType() + "\");\n");
boolean hasLongFields = rt.deepMatch(
n -> n instanceof Type t && t.isLong(), Callback.class);

return MethodSpec.methodBuilder("getMemoryLayout")
var method = MethodSpec.methodBuilder("getMemoryLayout")
.addJavadoc("The memory layout of the native struct.\n")
.addJavadoc("@return the memory layout\n")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(MemoryLayout.class)
.addNamedCode(layout.format(), layout.arguments())
.build();
.returns(MemoryLayout.class);

// When there are `long` fields, generate 32-bit and 64-bit layout
if (hasLongFields) {
method.beginControlFlow("if ($T.longAsInt())", ClassNames.INTEROP);
var layout = generateMemoryLayout(rt.cType(), fieldList, isUnion, true);
method.addNamedCode(layout.format(), layout.arguments())
.nextControlFlow("else");
layout = generateMemoryLayout(rt.cType(), fieldList, isUnion, false);
method.addNamedCode(layout.format(), layout.arguments())
.endControlFlow();
} else {
var layout = generateMemoryLayout(rt.cType(), fieldList, isUnion, false);
method.addNamedCode(layout.format(), layout.arguments());
}

return method.build();
}

private PartialStatement generateMemoryLayout(String name,
List<Field> fieldList,
boolean isUnion,
boolean longAsInt) {
// The $> and $< in the statement increase and decrease indentation
return PartialStatement.of("return $memoryLayout:T."
+ (isUnion ? "union" : "struct") + "Layout(\n$>")
.add(generateFieldLayouts(fieldList, isUnion, longAsInt))
.add("$<\n).withName(\"" + name + "\");\n");
}

private PartialStatement generateFieldLayouts(List<Field> fieldList,
boolean isUnion) {
boolean isUnion,
boolean longAsInt) {
var stmt = PartialStatement.of(null,
"memoryLayout", MemoryLayout.class,
"valueLayout", ValueLayout.class
Expand All @@ -89,7 +113,7 @@ private PartialStatement generateFieldLayouts(List<Field> fieldList,
if (size > 0) stmt.add(",\n");

// Get the byte size of the field, in bytes
int s = field.getSize();
int s = field.getSize(longAsInt);

// Calculate padding (except for union layouts)
if (!isUnion) {
Expand All @@ -103,22 +127,23 @@ private PartialStatement generateFieldLayouts(List<Field> fieldList,
}

// Write the memory layout declaration
stmt.add(getFieldLayout(field))
stmt.add(getFieldLayout(field, longAsInt))
.add(".withName(\"" + field.name() + "\")");
size += s;
}
return stmt;
}

private PartialStatement getFieldLayout(Field f) {
private PartialStatement getFieldLayout(Field f, boolean longAsInt) {
return switch (f.anyType()) {
case null -> PartialStatement.of("$valueLayout:T.ADDRESS"); // callback
case Type type -> layoutForType(type);
case Type type -> layoutForType(type, longAsInt);
case Array array -> {
if (array.fixedSize() > 0) {
var type = (Type) array.anyType();
yield PartialStatement.of("$memoryLayout:T.sequenceLayout("
+ array.fixedSize() + ", ")
.add(layoutForType((Type) array.anyType()))
.add(layoutForType(type, longAsInt))
.add(")");
} else {
yield PartialStatement.of("$valueLayout:T.ADDRESS");
Expand All @@ -127,22 +152,22 @@ private PartialStatement getFieldLayout(Field f) {
};
}

private PartialStatement layoutForType(Type type) {
private PartialStatement layoutForType(Type type, boolean longAsInt) {
RegisteredType target = type.get();

// Recursive lookup for aliases
if (target instanceof Alias alias)
return layoutForType(alias.type());
return layoutForType(alias.type(), longAsInt);

// Proxy objects with a known memory layout
if (!type.isPointer()
&& new MemoryLayoutGenerator().canGenerate(target)) {
if (!type.isPointer() && canGenerate(target)) {
String tag = type.toTypeTag();
return PartialStatement.of("$" + tag + ":T.getMemoryLayout()",
tag, type.typeName());
}

// Plain value layout
return PartialStatement.of("$valueLayout:T." + getValueLayout(type));
String layout = getValueLayout(type, longAsInt);
return PartialStatement.of("$valueLayout:T." + layout);
}
}
Loading

0 comments on commit 74795da

Please sign in to comment.