Skip to content

Commit 0980f17

Browse files
committed
updates
1 parent b9562be commit 0980f17

File tree

9 files changed

+52
-81
lines changed

9 files changed

+52
-81
lines changed

.github/workflows/ci.yml

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: CI
2+
on:
3+
push:
4+
branches: ["main"]
5+
tags: ["v*"]
6+
pull_request:
7+
branches: ["*"]
8+
9+
jobs:
10+
build:
11+
strategy:
12+
fail-fast: false
13+
matrix:
14+
os: [ubuntu-22.04, macos-14, macos-13]
15+
runs-on: ${{ matrix.os }}
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- uses: actions/setup-java@v4
20+
with:
21+
distribution: 'temurin'
22+
java-version: '23'
23+
24+
- uses: coursier/cache-action@v6
25+
26+
- uses: VirtusLab/scala-cli-setup@main
27+
28+
- name: Test
29+
run: make run

README.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## Call Scala Native from Java using FFM
2+
3+
Demonstration project for my [blog post](https://blog.indoorvivants.com/2025-02-16-scala-native-from-java-via-ffm).
4+
5+
Uses Scala CLI.
6+
7+
Run with `make run`.
8+
9+
Other commands will require both [jextract](https://github.com/openjdk/jextract) and [sn-bindgen](https://sn-bindgen.indoorvivants.com/) to be installed.

interface.h

+3-10
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,8 @@
1-
2-
typedef enum {
3-
MULTIPLY = 1,
4-
ADD = 2
5-
} myscalalib_operation;
1+
typedef enum { MULTIPLY = 1, ADD = 2 } myscalalib_operation;
62

73
typedef struct {
84
myscalalib_operation op;
9-
char* label;
5+
char *label;
106
} myscalalib_config;
117

12-
13-
extern float myscalalib_run(myscalalib_config* config, float left, float right);
14-
15-
extern int ScalaNativeInit(void);
8+
extern float myscalalib_run(myscalalib_config *config, float left, float right);

java-side/Main.java

+1-7
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,7 @@ public static void main(String[] args) {
1515
} else {
1616
dylibPath = args[0];
1717
}
18-
var p = Paths.get(dylibPath).toAbsolutePath().toString();
19-
20-
System.load(p);
21-
22-
// Make sure Scala Native GC is initialised correctly!
23-
assert (interface_h.ScalaNativeInit() == 0);
18+
System.load(Paths.get(dylibPath).toAbsolutePath().toString());
2419

2520
// Run exported functions
2621
try (Arena arena = Arena.ofConfined()) {
@@ -33,7 +28,6 @@ public static void main(String[] args) {
3328
myscalalib_config.label(config, arena.allocateFrom("Second"));
3429
myscalalib_config.op(config, interface_h.MULTIPLY());
3530
interface_h.myscalalib_run(config, 50.0f, 10.0f);
36-
3731
}
3832

3933
}

java-side/myscalalib_bindings/interface_h.java

-56
Original file line numberDiff line numberDiff line change
@@ -146,61 +146,5 @@ public static float myscalalib_run(MemorySegment config, float left, float right
146146
throw new AssertionError("should not reach here", ex$);
147147
}
148148
}
149-
150-
private static class ScalaNativeInit {
151-
public static final FunctionDescriptor DESC = FunctionDescriptor.of(
152-
interface_h.C_INT );
153-
154-
public static final MemorySegment ADDR = interface_h.findOrThrow("ScalaNativeInit");
155-
156-
public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
157-
}
158-
159-
/**
160-
* Function descriptor for:
161-
* {@snippet lang=c :
162-
* extern int ScalaNativeInit()
163-
* }
164-
*/
165-
public static FunctionDescriptor ScalaNativeInit$descriptor() {
166-
return ScalaNativeInit.DESC;
167-
}
168-
169-
/**
170-
* Downcall method handle for:
171-
* {@snippet lang=c :
172-
* extern int ScalaNativeInit()
173-
* }
174-
*/
175-
public static MethodHandle ScalaNativeInit$handle() {
176-
return ScalaNativeInit.HANDLE;
177-
}
178-
179-
/**
180-
* Address for:
181-
* {@snippet lang=c :
182-
* extern int ScalaNativeInit()
183-
* }
184-
*/
185-
public static MemorySegment ScalaNativeInit$address() {
186-
return ScalaNativeInit.ADDR;
187-
}
188-
189-
/**
190-
* {@snippet lang=c :
191-
* extern int ScalaNativeInit()
192-
* }
193-
*/
194-
public static int ScalaNativeInit() {
195-
var mh$ = ScalaNativeInit.HANDLE;
196-
try {
197-
if (TRACE_DOWNCALLS) {
198-
traceDowncall("ScalaNativeInit");
199-
}
200-
return (int)mh$.invokeExact();
201-
} catch (Throwable ex$) {
202-
throw new AssertionError("should not reach here", ex$);
203-
}
204-
}
205149
}
206150

java-side/myscalalib_bindings/myscalalib_config.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class myscalalib_config {
3030
interface_h.C_INT.withName("op"),
3131
MemoryLayout.paddingLayout(4),
3232
interface_h.C_POINTER.withName("label")
33-
).withName("$anon$7:9");
33+
).withName("$anon$3:9");
3434

3535
/**
3636
* The layout of this struct

scala-native-side/interface.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,13 @@ object functions extends ExportedFunctions:
7575
* [bindgen] header: interface.h
7676
*/
7777
@exported
78-
override def myscalalib_run(config : Ptr[myscalalib_config], left : Float, right : Float): Float = myscalalib.impl.Implementations.myscalalib_run(config, left, right)
78+
override def myscalalib_run(config : Ptr[myscalalib_config], left : Float, right : Float): Float =
79+
myscalalib.impl.Implementations.myscalalib_run(config, left, right)
7980

8081
object types:
8182
export _root_.myscalalib.structs.*
8283
export _root_.myscalalib.enumerations.*
8384

8485
object all:
8586
export _root_.myscalalib.enumerations.myscalalib_operation
86-
export _root_.myscalalib.structs.myscalalib_config
87+
export _root_.myscalalib.structs.myscalalib_config

scala-native-side/main.scala

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
//> using platform scala-native
2+
//> using scala 3.6.3
3+
//> using nativeVersion 0.5.6
4+
//> using nativeTarget dynamic
5+
//> using dep com.outr::scribe::3.16.0
6+
17
package myscalalib.impl
28

39
import scalanative.unsafe.*

scala-native-side/project.scala

-5
This file was deleted.

0 commit comments

Comments
 (0)