jdk-24/test/jdk/java/foreign/CallGeneratorHelper.java
Jorn Vernee ac2f8e5af8 8327994: Update code gen in CallGeneratorHelper
Reviewed-by: mcimadamore
2024-03-21 12:34:38 +00:00

353 lines
14 KiB
Java

/*
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
import java.io.IOException;
import java.io.PrintStream;
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import jdk.internal.foreign.Utils;
import org.testng.annotations.*;
public class CallGeneratorHelper extends NativeTestHelper {
static final List<MemoryLayout> STACK_PREFIX_LAYOUTS = Stream.concat(
Stream.generate(() -> (MemoryLayout) C_LONG_LONG).limit(8),
Stream.generate(() -> (MemoryLayout) C_DOUBLE).limit(8)
).toList();
static SegmentAllocator THROWING_ALLOCATOR = (size, align) -> {
throw new UnsupportedOperationException();
};
static final int SAMPLE_FACTOR = Integer.parseInt((String)System.getProperties().getOrDefault("generator.sample.factor", "-1"));
static final int MAX_FIELDS = 3;
static final int MAX_PARAMS = 3;
static final int CHUNK_SIZE = 600;
enum Ret {
VOID,
NON_VOID
}
enum StructFieldType {
INT("int", C_INT),
FLOAT("float", C_FLOAT),
DOUBLE("double", C_DOUBLE),
POINTER("void*", C_POINTER);
final String typeStr;
final MemoryLayout layout;
StructFieldType(String typeStr, MemoryLayout layout) {
this.typeStr = typeStr;
this.layout = layout;
}
MemoryLayout layout() {
return layout;
}
@SuppressWarnings("unchecked")
static List<List<StructFieldType>>[] perms = new List[10];
static List<List<StructFieldType>> perms(int i) {
if (perms[i] == null) {
perms[i] = generateTest(i, values());
}
return perms[i];
}
}
enum ParamType {
INT("int", C_INT),
FLOAT("float", C_FLOAT),
DOUBLE("double", C_DOUBLE),
POINTER("void*", C_POINTER),
STRUCT("struct S", null);
private final String typeStr;
private final MemoryLayout layout;
ParamType(String typeStr, MemoryLayout layout) {
this.typeStr = typeStr;
this.layout = layout;
}
String type(List<StructFieldType> fields) {
return this == STRUCT ?
typeStr + "_" + sigCode(fields) :
typeStr;
}
MemoryLayout layout(List<StructFieldType> fields) {
if (this == STRUCT) {
return Utils.computePaddedStructLayout(
IntStream.range(0, fields.size())
.mapToObj(i -> fields.get(i).layout().withName("f" + i))
.toArray(MemoryLayout[]::new));
} else {
return layout;
}
}
@SuppressWarnings("unchecked")
static List<List<ParamType>>[] perms = new List[10];
static List<List<ParamType>> perms(int i) {
if (perms[i] == null) {
perms[i] = generateTest(i, values());
}
return perms[i];
}
}
static <Z> List<List<Z>> generateTest(int i, Z[] elems) {
List<List<Z>> res = new ArrayList<>();
generateTest(i, new Stack<>(), elems, res);
return res;
}
static <Z> void generateTest(int i, Stack<Z> combo, Z[] elems, List<List<Z>> results) {
if (i == 0) {
results.add(new ArrayList<>(combo));
} else {
for (Z z : elems) {
combo.push(z);
generateTest(i - 1, combo, elems, results);
combo.pop();
}
}
}
@DataProvider(name = "functions")
public static Object[][] functions() {
int functions = 0;
List<Object[]> downcalls = new ArrayList<>();
for (Ret r : Ret.values()) {
for (int i = 0; i <= MAX_PARAMS; i++) {
if (r != Ret.VOID && i == 0) continue;
for (List<ParamType> ptypes : ParamType.perms(i)) {
String retCode = r == Ret.VOID ? "V" : ptypes.get(0).name().charAt(0) + "";
String sigCode = sigCode(ptypes);
if (ptypes.contains(ParamType.STRUCT)) {
for (int j = 1; j <= MAX_FIELDS; j++) {
for (List<StructFieldType> fields : StructFieldType.perms(j)) {
String structCode = sigCode(fields);
int count = functions;
int fCode = functions++ / CHUNK_SIZE;
String fName = String.format("f%d_%s_%s_%s", fCode, retCode, sigCode, structCode);
if (SAMPLE_FACTOR == -1 || (count % SAMPLE_FACTOR) == 0) {
downcalls.add(new Object[]{count, fName, r, ptypes, fields});
}
}
}
} else {
String structCode = sigCode(List.<StructFieldType>of());
int count = functions;
int fCode = functions++ / CHUNK_SIZE;
String fName = String.format("f%d_%s_%s_%s", fCode, retCode, sigCode, structCode);
if (SAMPLE_FACTOR == -1 || (count % SAMPLE_FACTOR) == 0) {
downcalls.add(new Object[]{count, fName, r, ptypes, List.of()});
}
}
}
}
}
return downcalls.toArray(new Object[0][]);
}
static <Z extends Enum<Z>> String sigCode(List<Z> elems) {
return elems.stream().map(p -> p.name().charAt(0) + "").collect(Collectors.joining());
}
private static void generateStructDecl(PrintStream out, List<StructFieldType> fields) {
String structCode = sigCode(fields);
List<String> fieldDecls = new ArrayList<>();
for (int i = 0 ; i < fields.size() ; i++) {
fieldDecls.add(String.format("%s p%d;", fields.get(i).typeStr, i));
}
String res = String.format("struct S_%s { %s };", structCode,
fieldDecls.stream().collect(Collectors.joining(" ")));
out.println(res);
}
private static PrintStream printStream(String first) throws IOException {
return new PrintStream(Files.newOutputStream(Path.of(first)));
}
// This can be used to generate the test implementation.
// From the test/jdk/java/foreign directory, run this class using:
// java -cp <jtreg_home>\lib\testng-7.3.0.jar --add-exports java.base/jdk.internal.foreign=ALL-UNNAMED ./CallGeneratorHelper.java
// Copyright header has to be added manually, and on Windows line endings have to be changed from \r\n to just \n
public static void main(String[] args) throws IOException {
try (PrintStream shared = printStream("shared.h");
PrintStream libTestDowncall = printStream("libTestDowncall.c");
PrintStream libTestDowncallStack = printStream("libTestDowncallStack.c");
PrintStream libTestUpcall = printStream("libTestUpcall.c");
PrintStream libTestUpcallStack = printStream("libTestUpcallStack.c")) {
generateShared(shared);
generateDowncalls(libTestDowncall, false);
generateDowncalls(libTestDowncallStack, true);
generateUpcalls(libTestUpcall, false);
generateUpcalls(libTestUpcallStack, true);
}
}
private static void generateShared(PrintStream out) {
out.println("""
#include "export.h"
#ifdef __clang__
#pragma clang optimize off
#elif defined __GNUC__
#pragma GCC optimize ("O0")
#elif defined _MSC_BUILD
#pragma optimize( "", off )
#endif
#ifdef _AIX
#pragma align (natural)
#endif
""");
for (int j = 1; j <= MAX_FIELDS; j++) {
for (List<StructFieldType> fields : StructFieldType.perms(j)) {
generateStructDecl(out, fields);
}
}
out.print("""
#ifdef _AIX
#pragma align (reset)
#endif
""");
}
private static void generateDowncalls(PrintStream out, boolean stack) {
out.println("#include \"shared.h\"\n");
for (Object[] downcall : functions()) {
String fName = (String)downcall[1];
Ret r = (Ret)downcall[2];
@SuppressWarnings("unchecked")
List<ParamType> ptypes = (List<ParamType>)downcall[3];
@SuppressWarnings("unchecked")
List<StructFieldType> fields = (List<StructFieldType>)downcall[4];
generateDowncallFunction(out, fName, r, ptypes, fields, stack);
}
}
private static final List<String> stackParamTypes = Stream.concat(Stream.generate(() -> "long long").limit(8),
Stream.generate(() -> "double").limit(8)).toList();
private static final List<String> stackParamNames = IntStream.range(0, 16).mapToObj(i -> "pf" + i).toList();
private static final List<String> stackParamDecls = IntStream.range(0, 16)
.mapToObj(i -> stackParamTypes.get(i) + " " + stackParamNames.get(i)).toList();
private static void generateDowncallFunction(PrintStream out, String fName, Ret ret, List<ParamType> params, List<StructFieldType> fields, boolean stack) {
String retType = ret == Ret.VOID ? "void" : params.get(0).type(fields);
List<String> paramDecls = new ArrayList<>();
if (stack) {
paramDecls.addAll(stackParamDecls);
}
for (int i = 0 ; i < params.size() ; i++) {
paramDecls.add(String.format("%s p%d", params.get(i).type(fields), i));
}
String sig = paramDecls.isEmpty() ?
"void" :
paramDecls.stream().collect(Collectors.joining(", "));
String body = ret == Ret.VOID ? "{ }" : "{ return p0; }";
String res = String.format("EXPORT %s %s%s(%s) %s", retType, stack ? "s" : "", fName,
sig, body);
out.println(res);
}
private static void generateUpcalls(PrintStream out, boolean stack) {
out.println("#include \"shared.h\"\n");
for (Object[] downcall : functions()) {
String fName = (String)downcall[1];
Ret r = (Ret)downcall[2];
@SuppressWarnings("unchecked")
List<ParamType> ptypes = (List<ParamType>)downcall[3];
@SuppressWarnings("unchecked")
List<StructFieldType> fields = (List<StructFieldType>)downcall[4];
generateUpcallFunction(out, fName, r, ptypes, fields, stack);
}
}
private static void generateUpcallFunction(PrintStream out, String fName, Ret ret, List<ParamType> params, List<StructFieldType> fields, boolean stack) {
String retType = ret == Ret.VOID ? "void" : params.get(0).type(fields);
List<String> paramDecls = new ArrayList<>();
if (stack) {
paramDecls.addAll(stackParamDecls);
}
for (int i = 0 ; i < params.size() ; i++) {
paramDecls.add(String.format("%s p%d", params.get(i).type(fields), i));
}
Stream<String> prefixParamNames = stack ? stackParamNames.stream() : Stream.of();
String paramNames = Stream.concat(prefixParamNames, IntStream.range(0, params.size())
.mapToObj(i -> "p" + i))
.collect(Collectors.joining(", "));
String sig = paramDecls.isEmpty() ?
"" :
paramDecls.stream().collect(Collectors.joining(", ")) + ", ";
String body = String.format(ret == Ret.VOID ? "{ cb(%s); }" : "{ return cb(%s); }", paramNames);
List<String> paramTypes = params.stream().map(p -> p.type(fields)).collect(Collectors.toList());
if (stack) {
paramTypes.addAll(0, stackParamTypes);
}
String cbSig = paramTypes.isEmpty() ?
"void" :
paramTypes.stream().collect(Collectors.joining(", "));
String cbParam = String.format("%s (*cb)(%s)",
retType, cbSig);
String res = String.format("EXPORT %s %s%s(%s %s) %s", retType, stack ? "s" : "", fName,
sig, cbParam, body);
out.println(res);
}
//helper methods
MethodHandle downcallHandle(Linker abi, MemorySegment symbol, SegmentAllocator allocator, FunctionDescriptor descriptor) {
MethodHandle mh = abi.downcallHandle(symbol, descriptor);
if (descriptor.returnLayout().isPresent() && descriptor.returnLayout().get() instanceof GroupLayout) {
mh = mh.bindTo(allocator);
}
return mh;
}
}