Skip to content

Commit ba71afb

Browse files
authoredMar 27, 2025
Merge pull request ruby#3516 from andreaTP/java-wasm-aot
Compile Prism to Bytecode with Chicory
2 parents a4211bb + 7173cf8 commit ba71afb

File tree

5 files changed

+153
-30
lines changed

5 files changed

+153
-30
lines changed
 

‎.github/workflows/java-wasm-bindings.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ jobs:
2424
bundler-cache: true
2525

2626
- name: rake templates
27-
run: PRISM_SERIALIZE_ONLY_SEMANTICS_FIELDS=1 bundle exec rake templates
27+
run: PRISM_EXCLUDE_PRETTYPRINT=1 PRISM_SERIALIZE_ONLY_SEMANTICS_FIELDS=1 bundle exec rake templates
2828

2929
- name: Set up WASI-SDK
3030
run: |
3131
wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz
3232
tar xvf wasi-sdk-25.0-x86_64-linux.tar.gz
3333
3434
- name: Build the project
35-
run: make java-wasm WASI_SDK_PATH=$(pwd)/wasi-sdk-25.0-x86_64-linux PRISM_SERIALIZE_ONLY_SEMANTICS_FIELDS=1
35+
run: make java-wasm WASI_SDK_PATH=$(pwd)/wasi-sdk-25.0-x86_64-linux
3636

3737
- name: Set up Java
3838
uses: actions/setup-java@v4

‎Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ SOEXT ?= $(shell ruby -e 'puts RbConfig::CONFIG["SOEXT"]')
1212

1313
CPPFLAGS := -Iinclude $(CPPFLAGS)
1414
CFLAGS := -g -O2 -std=c99 -Wall -Werror -Wextra -Wpedantic -Wundef -Wconversion -Wno-missing-braces -fPIC -fvisibility=hidden -Wimplicit-fallthrough $(CFLAGS)
15+
JAVA_WASM_CFLAGS := -g -Oz -std=c99 -Wall -Werror -Wextra -Wpedantic -Wundef -Wconversion -Wno-missing-braces -fPIC -fvisibility=hidden -Wimplicit-fallthrough $(JAVA_WASM_CFLAGS)
1516
CC ?= cc
1617
AR ?= ar
1718
WASI_SDK_PATH := /opt/wasi-sdk
@@ -45,7 +46,7 @@ javascript/src/prism.wasm: Makefile $(SOURCES) $(HEADERS)
4546

4647
java-wasm/src/test/resources/prism.wasm: Makefile $(SOURCES) $(HEADERS)
4748
$(ECHO) "building $@"
48-
$(Q) $(WASI_SDK_PATH)/bin/clang $(DEBUG_FLAGS) -DPRISM_EXPORT_SYMBOLS -D_WASI_EMULATED_MMAN -lwasi-emulated-mman $(CPPFLAGS) $(CFLAGS) -Wl,--export-all -Wl,--no-entry -mexec-model=reactor -lc++ -lc++abi -o $@ $(SOURCES)
49+
$(Q) $(WASI_SDK_PATH)/bin/clang $(DEBUG_FLAGS) -DPRISM_EXCLUDE_PRETTYPRINT -DPRISM_EXPORT_SYMBOLS -D_WASI_EMULATED_MMAN -lwasi-emulated-mman $(CPPFLAGS) $(JAVA_WASM_CFLAGS) -Wl,--export-all -Wl,--no-entry -mexec-model=reactor -lc++ -lc++abi -o $@ $(SOURCES)
4950

5051
build/shared/%.o: src/%.c Makefile $(HEADERS)
5152
$(ECHO) "compiling $@"

‎java-wasm/pom.xml

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,47 @@
4848
</dependencies>
4949

5050
<build>
51-
<sourceDirectory>../java</sourceDirectory>
5251
<plugins>
5352
<plugin>
5453
<groupId>org.apache.maven.plugins</groupId>
5554
<artifactId>maven-surefire-plugin</artifactId>
5655
<version>3.5.2</version>
5756
</plugin>
57+
<plugin>
58+
<groupId>org.codehaus.mojo</groupId>
59+
<artifactId>build-helper-maven-plugin</artifactId>
60+
<version>3.6.0</version>
61+
<executions>
62+
<execution>
63+
<phase>generate-sources</phase>
64+
<goals>
65+
<goal>add-source</goal>
66+
</goals>
67+
<configuration>
68+
<sources>
69+
<source>../java</source>
70+
</sources>
71+
</configuration>
72+
</execution>
73+
</executions>
74+
</plugin>
75+
<plugin>
76+
<groupId>com.dylibso.chicory</groupId>
77+
<artifactId>aot-maven-plugin-experimental</artifactId>
78+
<version>${chicory.version}</version>
79+
<executions>
80+
<execution>
81+
<id>prism</id>
82+
<goals>
83+
<goal>wasm-aot-gen</goal>
84+
</goals>
85+
<configuration>
86+
<name>org.prism.Prism</name>
87+
<wasmFile>src/test/resources/prism.wasm</wasmFile>
88+
</configuration>
89+
</execution>
90+
</executions>
91+
</plugin>
5892
</plugins>
5993
</build>
6094

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package org.prism;
2+
3+
import com.dylibso.chicory.runtime.ByteArrayMemory;
4+
import com.dylibso.chicory.runtime.ExportFunction;
5+
import com.dylibso.chicory.runtime.ImportValues;
6+
import com.dylibso.chicory.runtime.Instance;
7+
import com.dylibso.chicory.wasi.WasiOptions;
8+
import com.dylibso.chicory.wasi.WasiPreview1;
9+
10+
import java.nio.charset.StandardCharsets;
11+
12+
public class Prism implements AutoCloseable {
13+
private final ExportFunction calloc;
14+
private final ExportFunction pmSerializeParse;
15+
private final ExportFunction pmBufferInit;
16+
private final ExportFunction pmBufferSizeof;
17+
private final ExportFunction pmBufferValue;
18+
private final ExportFunction pmBufferLength;
19+
20+
private final WasiPreview1 wasi;
21+
private final Instance instance;
22+
23+
public Prism() {
24+
this(WasiOptions.builder().build());
25+
}
26+
public Prism(WasiOptions wasiOpts) {
27+
wasi = WasiPreview1.builder().withOptions(wasiOpts).build();
28+
instance = Instance.builder(PrismModule.load())
29+
.withMemoryFactory(ByteArrayMemory::new)
30+
.withMachineFactory(PrismModule::create)
31+
.withImportValues(ImportValues.builder().addFunction(wasi.toHostFunctions()).build())
32+
.build();
33+
34+
calloc = instance.exports().function("calloc");
35+
pmSerializeParse = instance.exports().function("pm_serialize_parse");
36+
pmBufferInit = instance.exports().function("pm_buffer_init");
37+
pmBufferSizeof = instance.exports().function("pm_buffer_sizeof");
38+
pmBufferValue = instance.exports().function("pm_buffer_value");
39+
pmBufferLength = instance.exports().function("pm_buffer_length");
40+
}
41+
42+
public ParseResult serializeParse(byte[] packedOptions, String source) {
43+
var sourceBytes = source.getBytes(StandardCharsets.US_ASCII);
44+
45+
var sourcePointer = calloc.apply(1, source.length());
46+
instance.memory().writeString((int) sourcePointer[0], source);
47+
48+
var optionsPointer = calloc.apply(1, packedOptions.length);
49+
instance.memory().write((int) optionsPointer[0], packedOptions);
50+
51+
var bufferPointer = calloc.apply(pmBufferSizeof.apply()[0], 1);
52+
pmBufferInit.apply(bufferPointer);
53+
54+
pmSerializeParse.apply(
55+
bufferPointer[0], sourcePointer[0], source.length(), optionsPointer[0]);
56+
57+
var result = instance.memory().readBytes(
58+
(int) pmBufferValue.apply(bufferPointer[0])[0],
59+
(int) pmBufferLength.apply(bufferPointer[0])[0]);
60+
61+
return Loader.load(result, sourceBytes);
62+
}
63+
64+
@Override
65+
public void close() {
66+
if (wasi != null) {
67+
wasi.close();
68+
}
69+
}
70+
}

‎java-wasm/src/test/java/org/prism/DummyTest.java

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@
1616

1717
public class DummyTest {
1818

19+
byte[] packedOptions = ParsingOptions.serialize(
20+
new byte[] {},
21+
1,
22+
new byte[] {},
23+
false,
24+
EnumSet.noneOf(ParsingOptions.CommandLine.class),
25+
ParsingOptions.SyntaxVersion.LATEST,
26+
false,
27+
false,
28+
false,
29+
new byte[][][] {}
30+
);
31+
1932
@Test
2033
public void test1() {
2134
WasiOptions wasiOpts = WasiOptions.builder().build();
@@ -38,19 +51,6 @@ public void test1() {
3851
var sourcePointer = calloc.apply(1, source.length());
3952
memory.writeString((int) sourcePointer[0], source);
4053

41-
var packedOptions = ParsingOptions.serialize(
42-
new byte[] {},
43-
1,
44-
new byte[] {},
45-
false,
46-
EnumSet.noneOf(ParsingOptions.CommandLine.class),
47-
ParsingOptions.SyntaxVersion.LATEST,
48-
false,
49-
false,
50-
false,
51-
new byte[][][] {}
52-
);
53-
5454
var optionsPointer = calloc.apply(1, packedOptions.length);
5555
memory.write((int) optionsPointer[0], packedOptions);
5656

@@ -74,6 +74,22 @@ public void test1() {
7474
assertTrue(pr.value.childNodes()[0].toString().contains("IntegerNode"));
7575
}
7676

77+
@Test
78+
public void test1Aot() {
79+
// The Ruby source code to be processed
80+
var source = "1 + 1";
81+
82+
ParseResult pr = null;
83+
try (Prism prism = new Prism()) {
84+
pr = prism.serializeParse(packedOptions, source);
85+
}
86+
87+
assertEquals(1, pr.value.childNodes().length);
88+
System.out.println("Nodes:");
89+
System.out.println(pr.value.childNodes()[0]);
90+
assertTrue(pr.value.childNodes()[0].toString().contains("IntegerNode"));
91+
}
92+
7793
@Test
7894
public void test2() {
7995
WasiOptions wasiOpts = WasiOptions.builder().build();
@@ -96,19 +112,6 @@ public void test2() {
96112
var sourcePointer = calloc.apply(1, source.length());
97113
memory.writeString((int) sourcePointer[0], source);
98114

99-
var packedOptions = ParsingOptions.serialize(
100-
new byte[] {},
101-
1,
102-
new byte[] {},
103-
false,
104-
EnumSet.noneOf(ParsingOptions.CommandLine.class),
105-
ParsingOptions.SyntaxVersion.LATEST,
106-
false,
107-
false,
108-
false,
109-
new byte[][][] {}
110-
);
111-
112115
var optionsPointer = calloc.apply(1, packedOptions.length);
113116
memory.write((int) optionsPointer[0], packedOptions);
114117

@@ -133,4 +136,19 @@ public void test2() {
133136
assertTrue(pr.value.childNodes()[0].toString().contains("CallNode"));
134137
}
135138

139+
@Test
140+
public void test2Aot() {
141+
// The Ruby source code to be processed
142+
var source = "puts \"h\ne\nl\nl\no\n\"";
143+
144+
ParseResult pr = null;
145+
try (Prism prism = new Prism()) {
146+
pr = prism.serializeParse(packedOptions, source);
147+
}
148+
149+
assertEquals(1, pr.value.childNodes().length);
150+
System.out.println("Nodes:");
151+
System.out.println(pr.value.childNodes()[0]);
152+
assertTrue(pr.value.childNodes()[0].toString().contains("CallNode"));
153+
}
136154
}

0 commit comments

Comments
 (0)