*
- * Variadic functions (e.g. a C function declared with a trailing ellipses {@code ...} at the end of the formal parameter
- * list or with an empty formal parameter list) are not supported directly by the native linker. However, it is still possible
- * to link a variadic function by using a specialized function descriptor, together with a
- * {@linkplain Linker.Option#firstVariadicArg(int) a linker option} which indicates the position of the first variadic argument
- * in that specialized descriptor.
+ * Variadic functions are C functions which can accept a variable number and type of arguments. They are declared:
+ *
+ *
With a trailing ellipsis ({@code ...}) at the end of the formal parameter list, such as: {@code void foo(int x, ...);}
+ *
With an empty formal parameter list, called a prototype-less function, such as: {@code void foo();}
+ *
+ * The arguments passed in place of the ellipsis, or the arguments passed to a prototype-less function are called
+ * variadic arguments. Variadic functions are, essentially, templates that can be specialized into multiple
+ * non-variadic functions by replacing the {@code ...} or empty formal parameter list with a list of variadic parameters
+ * of a fixed number and type.
+ *
+ * It should be noted that values passed as variadic arguments undergo default argument promotion in C. For instance, the
+ * following argument promotions are applied:
+ *
+ *
{@code _Bool} -> {@code unsigned int}
+ *
{@code [signed] char} -> {@code [signed] int}
+ *
{@code [signed] short} -> {@code [signed] int}
+ *
{@code float} -> {@code double}
+ *
+ * whereby the signed-ness of the source type corresponds to the signed-ness of the promoted type. The complete process
+ * of default argument promotion is described in the C specification. In effect these promotions place limits on the
+ * specialized form of a variadic function, as the variadic parameters of the specialized form will always have a promoted
+ * type.
+ *
+ * The native linker only supports linking the specialized form of a variadic function. A variadic function in its specialized
+ * form can be linked using a function descriptor describing the specialized form. Additionally, the
+ * {@link Linker.Option#firstVariadicArg(int)} linker option must be provided to indicate the first variadic parameter in
+ * the parameter list. The corresponding argument layout, and all following argument layouts in the specialized function
+ * descriptor, are called variadic argument layouts. For a prototype-less function, the index passed to
+ * {@link Linker.Option#firstVariadicArg(int)} should always be {@code 0}.
+ *
+ * The native linker will reject an attempt to link a specialized function descriptor with any variadic argument layouts
+ * corresponding to a C type that would be subject to default argument promotion (as described above). Exactly which layouts
+ * will be rejected is platform specific, but as an example: on Linux/x64 the layouts {@link ValueLayout#JAVA_BOOLEAN},
+ * {@link ValueLayout#JAVA_BYTE}, {@link ValueLayout#JAVA_CHAR}, {@link ValueLayout#JAVA_SHORT}, and
+ * {@link ValueLayout#JAVA_FLOAT} will be rejected.
*
* A well-known variadic function is the {@code printf} function, defined in the C standard library:
*
diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java
index 268fb8def43..b1847791751 100644
--- a/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java
+++ b/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java
@@ -90,6 +90,7 @@ public abstract sealed class AbstractLinker implements Linker permits LinuxAArch
checkLayouts(function);
function = stripNames(function);
LinkerOptions optionSet = LinkerOptions.forDowncall(function, options);
+ validateVariadicLayouts(function, optionSet);
return DOWNCALL_CACHE.get(new LinkRequest(function, optionSet), linkRequest -> {
FunctionDescriptor fd = linkRequest.descriptor();
@@ -134,6 +135,28 @@ public abstract sealed class AbstractLinker implements Linker permits LinuxAArch
/** {@return byte order used by this linker} */
protected abstract ByteOrder linkerByteOrder();
+ // C spec mandates that variadic arguments smaller than int are promoted to int,
+ // and float is promoted to double
+ // See: https://en.cppreference.com/w/c/language/conversion#Default_argument_promotions
+ // We reject the corresponding layouts here, to avoid issues where unsigned values
+ // are sign extended when promoted. (as we don't have a way to unambiguously represent signed-ness atm).
+ private void validateVariadicLayouts(FunctionDescriptor function, LinkerOptions optionSet) {
+ if (optionSet.isVariadicFunction()) {
+ List argumentLayouts = function.argumentLayouts();
+ List variadicLayouts = argumentLayouts.subList(optionSet.firstVariadicArgIndex(), argumentLayouts.size());
+
+ for (MemoryLayout variadicLayout : variadicLayouts) {
+ if (variadicLayout.equals(ValueLayout.JAVA_BOOLEAN)
+ || variadicLayout.equals(ValueLayout.JAVA_BYTE)
+ || variadicLayout.equals(ValueLayout.JAVA_CHAR)
+ || variadicLayout.equals(ValueLayout.JAVA_SHORT)
+ || variadicLayout.equals(ValueLayout.JAVA_FLOAT)) {
+ throw new IllegalArgumentException("Invalid variadic argument layout: " + variadicLayout);
+ }
+ }
+ }
+ }
+
private void checkLayouts(FunctionDescriptor descriptor) {
descriptor.returnLayout().ifPresent(this::checkLayout);
descriptor.argumentLayouts().forEach(this::checkLayout);
diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/LinkerOptions.java b/src/java.base/share/classes/jdk/internal/foreign/abi/LinkerOptions.java
index 28cde93090e..2c2bcbcc163 100644
--- a/src/java.base/share/classes/jdk/internal/foreign/abi/LinkerOptions.java
+++ b/src/java.base/share/classes/jdk/internal/foreign/abi/LinkerOptions.java
@@ -93,6 +93,10 @@ public class LinkerOptions {
return fva != null;
}
+ public int firstVariadicArgIndex() {
+ return getOption(FirstVariadicArg.class).index();
+ }
+
public boolean isTrivial() {
IsTrivial it = getOption(IsTrivial.class);
return it != null;
diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FallbackLinker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FallbackLinker.java
index 72a9399360d..541557b47e7 100644
--- a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FallbackLinker.java
+++ b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FallbackLinker.java
@@ -81,7 +81,7 @@ public final class FallbackLinker extends AbstractLinker {
@Override
protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function, LinkerOptions options) {
- MemorySegment cif = makeCif(inferredMethodType, function, FFIABI.DEFAULT, Arena.ofAuto());
+ MemorySegment cif = makeCif(inferredMethodType, function, options, Arena.ofAuto());
int capturedStateMask = options.capturedCallState()
.mapToInt(CapturableState::mask)
@@ -107,7 +107,7 @@ public final class FallbackLinker extends AbstractLinker {
@Override
protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) {
- MemorySegment cif = makeCif(targetType, function, FFIABI.DEFAULT, Arena.ofAuto());
+ MemorySegment cif = makeCif(targetType, function, options, Arena.ofAuto());
UpcallData invData = new UpcallData(function.returnLayout().orElse(null), function.argumentLayouts(), cif);
MethodHandle doUpcallMH = MethodHandles.insertArguments(MH_DO_UPCALL, 3, invData);
@@ -123,7 +123,9 @@ public final class FallbackLinker extends AbstractLinker {
return ByteOrder.nativeOrder();
}
- private static MemorySegment makeCif(MethodType methodType, FunctionDescriptor function, FFIABI abi, Arena scope) {
+ private static MemorySegment makeCif(MethodType methodType, FunctionDescriptor function, LinkerOptions options, Arena scope) {
+ FFIABI abi = FFIABI.DEFAULT;
+
MemorySegment argTypes = scope.allocate(function.argumentLayouts().size() * ADDRESS.byteSize());
List argLayouts = function.argumentLayouts();
for (int i = 0; i < argLayouts.size(); i++) {
@@ -134,7 +136,14 @@ public final class FallbackLinker extends AbstractLinker {
MemorySegment returnType = methodType.returnType() != void.class
? FFIType.toFFIType(function.returnLayout().orElseThrow(), abi, scope)
: LibFallback.voidType();
- return LibFallback.prepCif(returnType, argLayouts.size(), argTypes, abi, scope);
+
+ if (options.isVariadicFunction()) {
+ int numFixedArgs = options.firstVariadicArgIndex();
+ int numTotalArgs = argLayouts.size();
+ return LibFallback.prepCifVar(returnType, numFixedArgs, numTotalArgs, argTypes, abi, scope);
+ } else {
+ return LibFallback.prepCif(returnType, argLayouts.size(), argTypes, abi, scope);
+ }
}
private record DowncallData(MemorySegment cif, MemoryLayout returnLayout, List argLayouts,
diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/LibFallback.java b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/LibFallback.java
index c434dbcef59..6ba81263d5c 100644
--- a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/LibFallback.java
+++ b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/LibFallback.java
@@ -104,6 +104,27 @@ final class LibFallback {
return cif;
}
+ /**
+ * Wrapper for {@code ffi_prep_cif_var}. The variadic version of prep_cif
+ *
+ * @param returnType a pointer to an @{code ffi_type} describing the return type
+ * @param numFixedArgs the number of fixed arguments
+ * @param numTotalArgs the number of total arguments
+ * @param paramTypes a pointer to an array of pointers, which each point to an {@code ffi_type} describing a
+ * parameter type
+ * @param abi the abi to be used
+ * @param scope the scope into which to allocate the returned {@code ffi_cif} struct
+ * @return a pointer to a prepared {@code ffi_cif} struct
+ *
+ * @throws IllegalStateException if the call to {@code ffi_prep_cif} returns a non-zero status code
+ */
+ static MemorySegment prepCifVar(MemorySegment returnType, int numFixedArgs, int numTotalArgs, MemorySegment paramTypes, FFIABI abi,
+ Arena scope) throws IllegalStateException {
+ MemorySegment cif = scope.allocate(NativeConstants.SIZEOF_CIF);
+ checkStatus(ffi_prep_cif_var(cif.address(), abi.value(), numFixedArgs, numTotalArgs, returnType.address(), paramTypes.address()));
+ return cif;
+ }
+
/**
* Create an upcallStub-style closure. This method wraps the {@code ffi_closure_alloc}
* and {@code ffi_prep_closure_loc} functions.
@@ -177,6 +198,7 @@ final class LibFallback {
private static native void doDowncall(long cif, long fn, long rvalue, long avalues, long capturedState, int capturedStateMask);
private static native int ffi_prep_cif(long cif, int abi, int nargs, long rtype, long atypes);
+ private static native int ffi_prep_cif_var(long cif, int abi, int nfixedargs, int ntotalargs, long rtype, long atypes);
private static native int ffi_get_struct_offsets(int abi, long type, long offsets);
private static native int ffi_default_abi();
diff --git a/src/java.base/share/native/libfallbackLinker/fallbackLinker.c b/src/java.base/share/native/libfallbackLinker/fallbackLinker.c
index d2058ce0899..7f554d49737 100644
--- a/src/java.base/share/native/libfallbackLinker/fallbackLinker.c
+++ b/src/java.base/share/native/libfallbackLinker/fallbackLinker.c
@@ -58,6 +58,10 @@ Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1prep_1cif(JNIEnv* env, j
return ffi_prep_cif(jlong_to_ptr(cif), (ffi_abi) abi, (unsigned int) nargs, jlong_to_ptr(rtype), jlong_to_ptr(atypes));
}
JNIEXPORT jint JNICALL
+Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1prep_1cif_1var(JNIEnv* env, jclass cls, jlong cif, jint abi, jint nfixedargs, jint ntotalargs, jlong rtype, jlong atypes) {
+ return ffi_prep_cif_var(jlong_to_ptr(cif), (ffi_abi) abi, (unsigned int) nfixedargs, (unsigned int) ntotalargs, jlong_to_ptr(rtype), jlong_to_ptr(atypes));
+}
+JNIEXPORT jint JNICALL
Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1get_1struct_1offsets(JNIEnv* env, jclass cls, jint abi, jlong type, jlong offsets) {
return ffi_get_struct_offsets((ffi_abi) abi, jlong_to_ptr(type), jlong_to_ptr(offsets));
}
diff --git a/test/jdk/java/foreign/StdLibTest.java b/test/jdk/java/foreign/StdLibTest.java
index a66dc1e9b9c..360bb501bbb 100644
--- a/test/jdk/java/foreign/StdLibTest.java
+++ b/test/jdk/java/foreign/StdLibTest.java
@@ -378,13 +378,10 @@ public class StdLibTest extends NativeTestHelper {
}
enum PrintfArg {
-
- INTEGRAL(int.class, C_INT, "%d", arena -> 42, 42),
- STRING(MemorySegment.class, C_POINTER, "%s", arena -> {
- return arena.allocateUtf8String("str");
- }, "str"),
- CHAR(byte.class, C_CHAR, "%c", arena -> (byte) 'h', 'h'),
- DOUBLE(double.class, C_DOUBLE, "%.4f", arena ->1.2345d, 1.2345d);
+ INT(int.class, C_INT, "%d", arena -> 42, 42),
+ LONG(long.class, C_LONG_LONG, "%d", arena -> 84L, 84L),
+ DOUBLE(double.class, C_DOUBLE, "%.4f", arena -> 1.2345d, 1.2345d),
+ STRING(MemorySegment.class, C_POINTER, "%s", arena -> arena.allocateUtf8String("str"), "str");
final Class> carrier;
final ValueLayout layout;
diff --git a/test/jdk/java/foreign/TestIllegalLink.java b/test/jdk/java/foreign/TestIllegalLink.java
index ebaf3ab221d..9360e6f8559 100644
--- a/test/jdk/java/foreign/TestIllegalLink.java
+++ b/test/jdk/java/foreign/TestIllegalLink.java
@@ -60,9 +60,9 @@ public class TestIllegalLink extends NativeTestHelper {
private static final Linker ABI = Linker.nativeLinker();
@Test(dataProvider = "types")
- public void testIllegalLayouts(FunctionDescriptor desc, String expectedExceptionMessage) {
+ public void testIllegalLayouts(FunctionDescriptor desc, Linker.Option[] options, String expectedExceptionMessage) {
try {
- ABI.downcallHandle(DUMMY_TARGET, desc);
+ ABI.downcallHandle(DUMMY_TARGET, desc, options);
fail("Expected IllegalArgumentException was not thrown");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains(expectedExceptionMessage),
@@ -108,25 +108,31 @@ public class TestIllegalLink extends NativeTestHelper {
@DataProvider
public static Object[][] types() {
+ Linker.Option[] NO_OPTIONS = new Linker.Option[0];
List