diff --git a/.github/workflows/javadoc.yml b/.github/workflows/javadoc.yml
new file mode 100644
index 0000000..1ea6d31
--- /dev/null
+++ b/.github/workflows/javadoc.yml
@@ -0,0 +1,50 @@
+name: Javadoc
+
+on: [ release, workflow_dispatch ]
+
+# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
+# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
+concurrency:
+ group: "pages"
+ cancel-in-progress: false
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup Pages
+ uses: actions/configure-pages@v5
+ - name: Set up JDK
+ uses: actions/setup-java@v4
+ with:
+ java-version: |
+ 23-ea
+ distribution: 'temurin'
+ - name: Grant execute permission for gradlew
+ if: ${{ runner.os != 'Windows' }}
+ run: chmod +x gradlew
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v3
+ - name: Execute Gradle build
+ run: ./gradlew aggregateJavadoc
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: './build/docs/javadoc'
+ deploy:
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ runs-on: ubuntu-latest
+ needs: build
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/README.md b/README.md
index 11d81b3..8539b27 100644
--- a/README.md
+++ b/README.md
@@ -88,7 +88,7 @@ Import as a Gradle dependency:
```groovy
dependencies {
- implementation("io.github.over-run:marshal:0.1.0-alpha.32-jdk23")
+ implementation("io.github.over-run:marshal:0.1.0-alpha.33-jdk23")
}
```
diff --git a/build.gradle.kts b/build.gradle.kts
index 62e87d0..2c2abbd 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -36,6 +36,7 @@ val projDevelopers = arrayOf(
val junitVersion: String by rootProject
val memstackVersion: String by rootProject
+val platformVersion: String by rootProject
data class Organization(
val name: String,
@@ -92,6 +93,7 @@ allprojects {
// add your dependencies
compileOnly("org.jetbrains:annotations:24.1.0")
testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion")
+ testImplementation("io.github.over-run:platform:$platformVersion")
api("io.github.over-run:memstack:$memstackVersion")
}
diff --git a/gradle.properties b/gradle.properties
index ad8e9eb..5affdc9 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -12,7 +12,7 @@ projGroupId=io.github.over-run
projArtifactId=marshal
# The project name should only contain lowercase letters, numbers and hyphen.
projName=marshal
-projVersion=0.1.0-alpha.32-jdk23
+projVersion=0.1.0-alpha.33-jdk23
projDesc=Marshaler of native libraries
# Uncomment them if you want to publish to maven repository.
projUrl=https://github.com/Over-Run/marshal
@@ -36,3 +36,4 @@ jdkEarlyAccessDoc=jdk23
junitVersion=5.11.0
memstackVersion=0.2.0
+platformVersion=1.0.0
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 9355b41..0aaefbc 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java
index c0d402c..fbf7a93 100644
--- a/src/main/java/overrun/marshal/Downcall.java
+++ b/src/main/java/overrun/marshal/Downcall.java
@@ -55,7 +55,9 @@
* which allows {@code Downcall} to define the hidden class.
* You can obtain that lookup object with {@link MethodHandles#lookup()}.
*
- * The generated class implements the target class.
+ * The generated class implements the target class, which is specified with
+ * {@link DowncallOption#targetClass(Class) DowncallOption::targetClass}. If no target class is specified, the caller
+ * class will be used.
*
Methods
* The loader finds method from the target class and its superclasses.
*
diff --git a/src/main/java/overrun/marshal/LayoutBuilder.java b/src/main/java/overrun/marshal/LayoutBuilder.java
index a65f4e4..bd08a90 100644
--- a/src/main/java/overrun/marshal/LayoutBuilder.java
+++ b/src/main/java/overrun/marshal/LayoutBuilder.java
@@ -224,7 +224,7 @@ public Struct getThis() {
@Override
public StructLayout build() {
long size = 0;
- long align = 1;
+ long align = 0;
final List finalLayouts = new ArrayList<>(layouts.size());
for (MemoryLayout layout : layouts) {
final long alignment = layout.byteAlignment();
diff --git a/src/main/java/overrun/marshal/gen/CType.java b/src/main/java/overrun/marshal/gen/CType.java
new file mode 100644
index 0000000..071eb37
--- /dev/null
+++ b/src/main/java/overrun/marshal/gen/CType.java
@@ -0,0 +1,51 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2024 Overrun Organization
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ */
+
+package overrun.marshal.gen;
+
+import overrun.marshal.gen.processor.DescriptorTransformer;
+
+import java.lang.annotation.*;
+import java.lang.foreign.Linker;
+
+/**
+ * This marker annotation is for methods and parameters to mark the native type of them.
+ *
+ * If the target is marked as {@linkplain #canonical() canonical}, then {@link DescriptorTransformer} will use
+ * the {@linkplain Linker#canonicalLayouts() canonical layout} mapped from the linker of the current operating system.
+ *
Example
+ * {@code
+ * @CType(value = "size_t", canonical = true)
+ * long strlen(@CType("const char*") String s);
+ * }
+ *
+ * @author squid233
+ * @since 0.1.0
+ */
+@Documented
+@Target(ElementType.TYPE_USE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CType {
+ /**
+ * {@return the native (C) type of the marked type}
+ */
+ String value();
+
+ /**
+ * {@return {@code true} if {@link DescriptorTransformer} should use the canonical layout}
+ */
+ boolean canonical() default false;
+}
diff --git a/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java b/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java
index 9d92ee2..f88d446 100644
--- a/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java
+++ b/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java
@@ -16,16 +16,19 @@
package overrun.marshal.gen.processor;
+import overrun.marshal.gen.CType;
import overrun.marshal.gen.Sized;
import overrun.marshal.struct.ByValue;
import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.Linker;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.ValueLayout;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* Transforms the method and parameters into a function descriptor.
@@ -34,9 +37,19 @@
* @since 0.1.0
*/
public final class DescriptorTransformer extends TypeTransformer {
+ private final Map canonicalLayouts = Linker.nativeLinker().canonicalLayouts();
+
private DescriptorTransformer() {
}
+ private MemoryLayout findCanonicalLayout(String typename) {
+ MemoryLayout layout = canonicalLayouts.get(typename);
+ if (layout == null) {
+ throw new IllegalArgumentException("Canonical layout not found for type: " + typename);
+ }
+ return layout;
+ }
+
/**
* The context.
*
@@ -54,44 +67,57 @@ public record Context(
@Override
public FunctionDescriptor process(Context context) {
Method method = context.method();
- ProcessorType returnType = ProcessorTypes.fromMethod(method);
+ CType cType = method.getDeclaredAnnotation(CType.class);
List argLayouts = new ArrayList<>();
- Sized sized = method.getDeclaredAnnotation(Sized.class);
- MemoryLayout returnLayout = switch (returnType) {
- case ProcessorType.Allocator allocator -> allocator.downcallLayout();
- case ProcessorType.Array array -> sized != null ?
- ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(sized.value(), array.componentType().downcallLayout())) :
- array.downcallLayout();
- case ProcessorType.BoolConvert boolConvert -> boolConvert.downcallLayout();
- case ProcessorType.Custom custom -> custom.downcallLayout();
- case ProcessorType.Str str -> str.downcallLayout();
- case ProcessorType.Struct struct -> {
- if (method.getDeclaredAnnotation(ByValue.class) != null) {
- yield struct.downcallLayout();
+ MemoryLayout returnLayout;
+ if (cType != null && cType.canonical()) {
+ returnLayout = findCanonicalLayout(cType.value());
+ } else {
+ ProcessorType returnType = ProcessorTypes.fromMethod(method);
+ Sized sized = method.getDeclaredAnnotation(Sized.class);
+ returnLayout = switch (returnType) {
+ case ProcessorType.Allocator allocator -> allocator.downcallLayout();
+ case ProcessorType.Array array -> sized != null ?
+ ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(sized.value(), array.componentType().downcallLayout())) :
+ array.downcallLayout();
+ case ProcessorType.BoolConvert boolConvert -> boolConvert.downcallLayout();
+ case ProcessorType.Custom custom -> custom.downcallLayout();
+ case ProcessorType.Str str -> str.downcallLayout();
+ case ProcessorType.Struct struct -> {
+ if (method.getDeclaredAnnotation(ByValue.class) != null) {
+ yield struct.downcallLayout();
+ }
+ if (sized != null) {
+ yield ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(sized.value(), struct.downcallLayout()));
+ }
+ yield ValueLayout.ADDRESS.withTargetLayout(struct.downcallLayout());
}
- if (sized != null) {
- yield ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(sized.value(), struct.downcallLayout()));
- }
- yield ValueLayout.ADDRESS.withTargetLayout(struct.downcallLayout());
- }
- case ProcessorType.Upcall> upcall -> upcall.downcallLayout();
- case ProcessorType.Value value -> value == ProcessorType.Value.ADDRESS && sized != null ?
- ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(sized.value(), ValueLayout.JAVA_BYTE)) :
- value.downcallLayout();
- case ProcessorType.Void _ -> null;
- };
+ case ProcessorType.Upcall> upcall -> upcall.downcallLayout();
+ case ProcessorType.Value value -> value == ProcessorType.Value.ADDRESS && sized != null ?
+ ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(sized.value(), ValueLayout.JAVA_BYTE)) :
+ value.downcallLayout();
+ case ProcessorType.Void _ -> null;
+ };
+ }
var parameters = context.parameters();
for (int i = context.descriptorSkipFirstParameter() ? 1 : 0, size = parameters.size(); i < size; i++) {
Parameter parameter = parameters.get(i);
- ProcessorType type = ProcessorTypes.fromParameter(parameter);
- argLayouts.add(switch (type) {
- case ProcessorType.Struct struct -> parameter.getDeclaredAnnotation(ByValue.class) != null ?
- struct.downcallLayout() :
- ValueLayout.ADDRESS;
- default -> type.downcallLayout();
- });
+ CType parameterCType = parameter.getDeclaredAnnotation(CType.class);
+ MemoryLayout layout;
+ if (parameterCType != null && parameterCType.canonical()) {
+ layout = findCanonicalLayout(parameterCType.value());
+ } else {
+ ProcessorType type = ProcessorTypes.fromParameter(parameter);
+ layout = switch (type) {
+ case ProcessorType.Struct struct -> parameter.getDeclaredAnnotation(ByValue.class) != null ?
+ struct.downcallLayout() :
+ ValueLayout.ADDRESS;
+ default -> type.downcallLayout();
+ };
+ }
+ argLayouts.add(layout);
}
return returnLayout == null ?
diff --git a/src/test/java/overrun/marshal/test/downcall/CTypeTest.java b/src/test/java/overrun/marshal/test/downcall/CTypeTest.java
new file mode 100644
index 0000000..f89f9e2
--- /dev/null
+++ b/src/test/java/overrun/marshal/test/downcall/CTypeTest.java
@@ -0,0 +1,98 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2024 Overrun Organization
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ */
+
+package overrun.marshal.test.downcall;
+
+import io.github.overrun.platform.Platform;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import overrun.marshal.Downcall;
+import overrun.marshal.DowncallOption;
+import overrun.marshal.gen.CType;
+
+import java.lang.foreign.*;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author squid233
+ * @since 0.1.0
+ */
+public class CTypeTest {
+ private static Functions instance;
+
+ interface Functions {
+ @CType(value = "long", canonical = true)
+ long returnLong();
+
+ long acceptLong(@CType(value = "long", canonical = true) long value);
+ }
+
+ static long returnLong() {
+ return 42L;
+ }
+
+ static int returnInt() {
+ return 41;
+ }
+
+ static long acceptLong(long value) {
+ return value * 2L;
+ }
+
+ static long acceptInt(int value) {
+ return value * 3L;
+ }
+
+ @BeforeAll
+ static void beforeAll() throws NoSuchMethodException, IllegalAccessException {
+ Linker linker = Linker.nativeLinker();
+ MethodHandles.Lookup handles = MethodHandles.lookup();
+ Arena arena = Arena.ofAuto();
+
+ MemorySegment _returnLong = linker.upcallStub(handles.findStatic(CTypeTest.class, "returnLong", MethodType.methodType(long.class)), FunctionDescriptor.of(ValueLayout.JAVA_LONG), arena);
+ MemorySegment _returnInt = linker.upcallStub(handles.findStatic(CTypeTest.class, "returnInt", MethodType.methodType(int.class)), FunctionDescriptor.of(ValueLayout.JAVA_INT), arena);
+ MemorySegment _acceptLong = linker.upcallStub(handles.findStatic(CTypeTest.class, "acceptLong", MethodType.methodType(long.class, long.class)), FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG), arena);
+ MemorySegment _acceptInt = linker.upcallStub(handles.findStatic(CTypeTest.class, "acceptInt", MethodType.methodType(long.class, int.class)), FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT), arena);
+ SymbolLookup lookup = name -> switch (name) {
+ case "returnLong" -> Optional.of(Platform.current() instanceof Platform.Windows ? _returnInt : _returnLong);
+ case "acceptLong" -> Optional.of(Platform.current() instanceof Platform.Windows ? _acceptInt : _acceptLong);
+ default -> Optional.empty();
+ };
+ instance = Downcall.load(handles, lookup, DowncallOption.targetClass(Functions.class));
+ }
+
+ @Test
+ void testReturnLong() {
+ if (Platform.current() instanceof Platform.Windows) {
+ assertEquals(41L, instance.returnLong());
+ } else {
+ assertEquals(42L, instance.returnLong());
+ }
+ }
+
+ @Test
+ void testAcceptLong() {
+ if (Platform.current() instanceof Platform.Windows) {
+ assertEquals(126L, instance.acceptLong(42L));
+ } else {
+ assertEquals(84L, instance.acceptLong(42L));
+ }
+ }
+}