Skip to content

Commit 0bd3bde

Browse files
committed
Init
0 parents  commit 0bd3bde

File tree

11 files changed

+571
-0
lines changed

11 files changed

+571
-0
lines changed

.gitignore

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
project/target
2+
project/project
3+
.metals
4+
.bloop
5+
.vscode
6+
*.class
7+
*.sjsr
8+
9+
project/.bloop
10+
11+
.sbt
12+
13+
modules/core/target
14+
15+
root/target
16+
target
17+
18+
.bsp
19+
20+
*.log
21+
22+
project/metals.sbt

README.md

Whitespace-only changes.

build.sbt

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import sbt_jextract.*
2+
import bindgen.interface.*, bindgen.plugin.*
3+
import scalanative.build.BuildTarget
4+
5+
val Versions = new {
6+
val Scala3 = "3.6.3"
7+
}
8+
9+
lazy val root = project.in(file(".")).aggregate(jvmSide, scalaNativeSide)
10+
11+
val jvmSide = project
12+
.in(file("mod/jvm"))
13+
.enablePlugins(JextractPlugin)
14+
.settings(
15+
jextractBindings += JextractBinding(
16+
(ThisBuild / baseDirectory).value / "mod/interface.h",
17+
"myscalalib_bindings"
18+
),
19+
jextractMode := JextractMode.Manual(
20+
sourceDirectory.value / "main/java/generated"
21+
),
22+
Compile / resourceGenerators += Def.task {
23+
val scalaLib = (scalaNativeSide / Compile / nativeLink).value
24+
val dest =
25+
(Compile / resourceManaged).value / (scalaLib.asPath
26+
.getFileName()
27+
.toString)
28+
IO.copyFile(scalaLib, dest)
29+
30+
Seq(dest)
31+
},
32+
run / fork := true,
33+
javaOptions += "--enable-native-access=ALL-UNNAMED"
34+
)
35+
36+
lazy val scalaNativeSide = project
37+
.in(file("mod/scala-native"))
38+
.enablePlugins(ScalaNativePlugin, BindgenPlugin)
39+
.settings(
40+
publish / skip := true,
41+
publishLocal / skip := true,
42+
scalaVersion := Versions.Scala3,
43+
bindgenBindings :=
44+
Seq(
45+
Binding(
46+
(ThisBuild / baseDirectory).value / "mod/interface.h",
47+
"myscalalib"
48+
).withExport(true).withNoLocation(true)
49+
),
50+
bindgenMode := BindgenMode.Manual(
51+
scalaDir = (Compile / sourceDirectory).value / "scala" / "generated",
52+
cDir = (Compile / resourceDirectory).value / "scala-native" / "generated"
53+
),
54+
bindgenBindings := {
55+
bindgenBindings.value.map(_.withNoLocation(true))
56+
},
57+
nativeConfig ~= { _.withBuildTarget(BuildTarget.libraryDynamic) }
58+
)

mod/interface.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
typedef enum { MULTIPLY = 1, ADD = 2 } myscalalib_operation;
2+
3+
typedef struct {
4+
myscalalib_operation op;
5+
char *label;
6+
} myscalalib_config;
7+
8+
extern float myscalalib_run(myscalalib_config *config, float left, float right);

mod/jvm/src/main/java/Main.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import java.io.*;
2+
import java.lang.foreign.*;
3+
import java.nio.file.Files;
4+
import java.nio.file.Path;
5+
import java.nio.file.Paths;
6+
import myscalalib_bindings.*;
7+
8+
public class Main {
9+
10+
public static void main(String[] args) {
11+
Path dylibPath = null;
12+
try {
13+
dylibPath = Files.createTempDirectory("native-libs").resolve(dylibName);
14+
System.out.println("Loading " + dylibName);
15+
16+
try (InputStream is = Main.class.getClassLoader().getResourceAsStream(dylibName)) {
17+
Files.copy(is, dylibPath);
18+
}
19+
20+
System.load(dylibPath.toAbsolutePath().toString());
21+
22+
// Run exported functions
23+
try (Arena arena = Arena.ofConfined()) {
24+
var config = myscalalib_config.allocate(arena);
25+
26+
myscalalib_config.label(config, arena.allocateFrom("First test"));
27+
myscalalib_config.op(config, interface_h.ADD());
28+
interface_h.myscalalib_run(config, 25.0f, 150.0f);
29+
30+
myscalalib_config.label(config, arena.allocateFrom("Second"));
31+
myscalalib_config.op(config, interface_h.MULTIPLY());
32+
interface_h.myscalalib_run(config, 50.0f, 10.0f);
33+
}
34+
} catch (Exception e) {
35+
System.err.println(e);
36+
e.printStackTrace();
37+
System.exit(-1);
38+
} finally {
39+
if (dylibPath != null) {
40+
try {
41+
Files.delete(dylibPath);
42+
} catch (Exception e) {
43+
System.err.println(e);
44+
e.printStackTrace();
45+
System.exit(-1);
46+
}
47+
}
48+
}
49+
}
50+
51+
// sic! this is sensitive to the name for the project used in the build
52+
private static String dylibName = "libscalanativeside." + dylibExtension();
53+
54+
private static String dylibExtension() {
55+
String os = System.getProperty("os.name", "").toLowerCase();
56+
if (os.startsWith("mac os x") || os.startsWith("darwin"))
57+
return "dylib";
58+
return "so";
59+
}
60+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Generated by jextract
2+
3+
package myscalalib_bindings;
4+
5+
import java.lang.invoke.*;
6+
import java.lang.foreign.*;
7+
import java.nio.ByteOrder;
8+
import java.util.*;
9+
import java.util.function.*;
10+
import java.util.stream.*;
11+
12+
import static java.lang.foreign.ValueLayout.*;
13+
import static java.lang.foreign.MemoryLayout.PathElement.*;
14+
15+
public class interface_h {
16+
17+
interface_h() {
18+
// Should not be called directly
19+
}
20+
21+
static final Arena LIBRARY_ARENA = Arena.ofAuto();
22+
static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls");
23+
24+
static void traceDowncall(String name, Object... args) {
25+
String traceArgs = Arrays.stream(args)
26+
.map(Object::toString)
27+
.collect(Collectors.joining(", "));
28+
System.out.printf("%s(%s)\n", name, traceArgs);
29+
}
30+
31+
static MemorySegment findOrThrow(String symbol) {
32+
return SYMBOL_LOOKUP.find(symbol)
33+
.orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol: " + symbol));
34+
}
35+
36+
static MethodHandle upcallHandle(Class<?> fi, String name, FunctionDescriptor fdesc) {
37+
try {
38+
return MethodHandles.lookup().findVirtual(fi, name, fdesc.toMethodType());
39+
} catch (ReflectiveOperationException ex) {
40+
throw new AssertionError(ex);
41+
}
42+
}
43+
44+
static MemoryLayout align(MemoryLayout layout, long align) {
45+
return switch (layout) {
46+
case PaddingLayout p -> p;
47+
case ValueLayout v -> v.withByteAlignment(align);
48+
case GroupLayout g -> {
49+
MemoryLayout[] alignedMembers = g.memberLayouts().stream()
50+
.map(m -> align(m, align)).toArray(MemoryLayout[]::new);
51+
yield g instanceof StructLayout ?
52+
MemoryLayout.structLayout(alignedMembers) : MemoryLayout.unionLayout(alignedMembers);
53+
}
54+
case SequenceLayout s -> MemoryLayout.sequenceLayout(s.elementCount(), align(s.elementLayout(), align));
55+
};
56+
}
57+
58+
static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup()
59+
.or(Linker.nativeLinker().defaultLookup());
60+
61+
public static final ValueLayout.OfBoolean C_BOOL = ValueLayout.JAVA_BOOLEAN;
62+
public static final ValueLayout.OfByte C_CHAR = ValueLayout.JAVA_BYTE;
63+
public static final ValueLayout.OfShort C_SHORT = ValueLayout.JAVA_SHORT;
64+
public static final ValueLayout.OfInt C_INT = ValueLayout.JAVA_INT;
65+
public static final ValueLayout.OfLong C_LONG_LONG = ValueLayout.JAVA_LONG;
66+
public static final ValueLayout.OfFloat C_FLOAT = ValueLayout.JAVA_FLOAT;
67+
public static final ValueLayout.OfDouble C_DOUBLE = ValueLayout.JAVA_DOUBLE;
68+
public static final AddressLayout C_POINTER = ValueLayout.ADDRESS
69+
.withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, JAVA_BYTE));
70+
public static final ValueLayout.OfLong C_LONG = ValueLayout.JAVA_LONG;
71+
private static final int MULTIPLY = (int)1L;
72+
/**
73+
* {@snippet lang=c :
74+
* enum <anonymous>.MULTIPLY = 1
75+
* }
76+
*/
77+
public static int MULTIPLY() {
78+
return MULTIPLY;
79+
}
80+
private static final int ADD = (int)2L;
81+
/**
82+
* {@snippet lang=c :
83+
* enum <anonymous>.ADD = 2
84+
* }
85+
*/
86+
public static int ADD() {
87+
return ADD;
88+
}
89+
90+
private static class myscalalib_run {
91+
public static final FunctionDescriptor DESC = FunctionDescriptor.of(
92+
interface_h.C_FLOAT,
93+
interface_h.C_POINTER,
94+
interface_h.C_FLOAT,
95+
interface_h.C_FLOAT
96+
);
97+
98+
public static final MemorySegment ADDR = interface_h.findOrThrow("myscalalib_run");
99+
100+
public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
101+
}
102+
103+
/**
104+
* Function descriptor for:
105+
* {@snippet lang=c :
106+
* extern float myscalalib_run(myscalalib_config *config, float left, float right)
107+
* }
108+
*/
109+
public static FunctionDescriptor myscalalib_run$descriptor() {
110+
return myscalalib_run.DESC;
111+
}
112+
113+
/**
114+
* Downcall method handle for:
115+
* {@snippet lang=c :
116+
* extern float myscalalib_run(myscalalib_config *config, float left, float right)
117+
* }
118+
*/
119+
public static MethodHandle myscalalib_run$handle() {
120+
return myscalalib_run.HANDLE;
121+
}
122+
123+
/**
124+
* Address for:
125+
* {@snippet lang=c :
126+
* extern float myscalalib_run(myscalalib_config *config, float left, float right)
127+
* }
128+
*/
129+
public static MemorySegment myscalalib_run$address() {
130+
return myscalalib_run.ADDR;
131+
}
132+
133+
/**
134+
* {@snippet lang=c :
135+
* extern float myscalalib_run(myscalalib_config *config, float left, float right)
136+
* }
137+
*/
138+
public static float myscalalib_run(MemorySegment config, float left, float right) {
139+
var mh$ = myscalalib_run.HANDLE;
140+
try {
141+
if (TRACE_DOWNCALLS) {
142+
traceDowncall("myscalalib_run", config, left, right);
143+
}
144+
return (float)mh$.invokeExact(config, left, right);
145+
} catch (Throwable ex$) {
146+
throw new AssertionError("should not reach here", ex$);
147+
}
148+
}
149+
}
150+

0 commit comments

Comments
 (0)