Skip to content

Commit d0cc4f4

Browse files
authored
Allow configuring a method prefixer in the build-time compiler (#178)
1 parent dab3cf8 commit d0cc4f4

10 files changed

Lines changed: 298 additions & 4 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
invoker.goals=test
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project>
3+
4+
<modelVersion>4.0.0</modelVersion>
5+
<groupId>run.endive</groupId>
6+
7+
<artifactId>method-prefixer-endive-it</artifactId>
8+
<version>0.0-SNAPSHOT</version>
9+
<packaging>jar</packaging>
10+
11+
<properties>
12+
<maven.compiler.release>@maven.compiler.release@</maven.compiler.release>
13+
</properties>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>run.endive</groupId>
18+
<artifactId>runtime</artifactId>
19+
<version>@project.version@</version>
20+
</dependency>
21+
22+
<dependency>
23+
<groupId>org.junit.jupiter</groupId>
24+
<artifactId>junit-jupiter-api</artifactId>
25+
<version>@junit.version@</version>
26+
<scope>test</scope>
27+
</dependency>
28+
<dependency>
29+
<groupId>org.junit.jupiter</groupId>
30+
<artifactId>junit-jupiter-engine</artifactId>
31+
<version>@junit.version@</version>
32+
<scope>test</scope>
33+
</dependency>
34+
</dependencies>
35+
36+
<build>
37+
<plugins>
38+
<plugin>
39+
<groupId>org.apache.maven.plugins</groupId>
40+
<artifactId>maven-compiler-plugin</artifactId>
41+
<version>@maven-compiler-plugin.version@</version>
42+
<configuration>
43+
<release>${maven.compiler.release}</release>
44+
</configuration>
45+
</plugin>
46+
<plugin>
47+
<artifactId>maven-resources-plugin</artifactId>
48+
<version>@maven-resources-plugin.version@</version>
49+
<executions>
50+
<execution>
51+
<id>copy-resources</id>
52+
<goals>
53+
<goal>copy-resources</goal>
54+
</goals>
55+
<phase>validate</phase>
56+
<configuration>
57+
<outputDirectory>${basedir}/src/test/resources</outputDirectory>
58+
<resources>
59+
<resource>
60+
<directory>@basedir@/../../wasm-corpus/src/main/resources/compiled</directory>
61+
<includes>
62+
<include>trap.wat.wasm</include>
63+
</includes>
64+
</resource>
65+
</resources>
66+
</configuration>
67+
</execution>
68+
</executions>
69+
</plugin>
70+
<plugin>
71+
<groupId>run.endive</groupId>
72+
<artifactId>endive-compiler-maven-plugin</artifactId>
73+
<version>@project.version@</version>
74+
<executions>
75+
<execution>
76+
<id>compile-named</id>
77+
<goals>
78+
<goal>compile</goal>
79+
</goals>
80+
<configuration>
81+
<name>endive.test.NamedTrapModule</name>
82+
<wasmFile>src/test/resources/trap.wat.wasm</wasmFile>
83+
<methodPrefixer>run.endive.compiler.NameSectionMethodPrefixer</methodPrefixer>
84+
</configuration>
85+
</execution>
86+
<execution>
87+
<id>compile-default</id>
88+
<goals>
89+
<goal>compile</goal>
90+
</goals>
91+
<configuration>
92+
<name>endive.test.DefaultTrapModule</name>
93+
<wasmFile>src/test/resources/trap.wat.wasm</wasmFile>
94+
</configuration>
95+
</execution>
96+
</executions>
97+
</plugin>
98+
</plugins>
99+
</build>
100+
101+
</project>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package endive.test;
2+
3+
import static org.junit.jupiter.api.Assertions.assertThrows;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import java.util.function.Function;
9+
import org.junit.jupiter.api.Test;
10+
import run.endive.runtime.Instance;
11+
import run.endive.runtime.Machine;
12+
import run.endive.wasm.UninstantiableException;
13+
import run.endive.wasm.WasmModule;
14+
15+
/**
16+
* The module traps in its start function, so instantiating it throws from inside the compiled code
17+
* and the stack trace carries the generated method names. The module names its three functions
18+
* "trap", "innerFunc" and "start".
19+
*/
20+
public class MethodPrefixerTest {
21+
22+
@Test
23+
public void nameSectionPrefixerNamesTheGeneratedMethods() {
24+
var methods = methodNamesFromTrap(NamedTrapModule.load(), NamedTrapModule::create);
25+
26+
assertTrue(methods.contains("trap_0"), "Expected trap_0 in: " + methods);
27+
assertTrue(methods.contains("innerFunc_1"), "Expected innerFunc_1 in: " + methods);
28+
assertTrue(methods.contains("start_2"), "Expected start_2 in: " + methods);
29+
}
30+
31+
@Test
32+
public void withoutAPrefixerMethodsKeepTheDefaultNames() {
33+
var methods = methodNamesFromTrap(DefaultTrapModule.load(), DefaultTrapModule::create);
34+
35+
assertTrue(methods.contains("func_0"), "Expected func_0 in: " + methods);
36+
assertTrue(methods.contains("func_1"), "Expected func_1 in: " + methods);
37+
assertTrue(methods.contains("func_2"), "Expected func_2 in: " + methods);
38+
}
39+
40+
/** Instantiates the module and returns every method name on the resulting stack traces. */
41+
private static List<String> methodNamesFromTrap(
42+
WasmModule module, Function<Instance, Machine> machineFactory) {
43+
Throwable thrown =
44+
assertThrows(
45+
UninstantiableException.class,
46+
() -> Instance.builder(module).withMachineFactory(machineFactory).build());
47+
48+
var methods = new ArrayList<String>();
49+
for (Throwable t = thrown; t != null; t = t.getCause()) {
50+
for (var frame : t.getStackTrace()) {
51+
methods.add(frame.getMethodName());
52+
}
53+
}
54+
return methods;
55+
}
56+
}

‎build-time-compiler-cli/src/main/java/run/endive/experimental/compiler/cli/Cli.java‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,15 @@ public String[] getVersion() {
7373

7474
@CommandLine.Option(
7575
order = 7,
76+
names = "--method-prefixer",
77+
description =
78+
"Fully qualified name of a MethodPrefixer implementation used to name compiled"
79+
+ " methods, for example"
80+
+ " run.endive.compiler.NameSectionMethodPrefixer")
81+
String methodPrefixer;
82+
83+
@CommandLine.Option(
84+
order = 8,
7685
names = "--module-interface",
7786
description =
7887
"Fully qualified class name for which to generate _ModuleExports and"
@@ -90,6 +99,7 @@ public void run() {
9099
.withTargetWasmFolder(targetWasmFolder)
91100
.withInterpreterFallback(interpreterFallback)
92101
.withInterpretedFunctions(interpretedFunctions)
102+
.withMethodPrefixer(methodPrefixer)
93103
.withModuleInterface(moduleInterface)
94104
.build();
95105

‎build-time-compiler/src/main/java/run/endive/build/time/compiler/Config.java‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ public final class Config {
4242
*/
4343
private final Set<Integer> interpretedFunctions;
4444

45+
/**
46+
* the fully qualified name of the MethodPrefixer implementation used to name compiled methods
47+
*/
48+
private final String methodPrefixer;
49+
4550
/**
4651
* the fully qualified name of the user's class for which to generate module interface wrappers
4752
*/
@@ -60,6 +65,7 @@ private Config(
6065
Path targetWasmFolder,
6166
InterpreterFallback interpreterFallback,
6267
Set<Integer> interpretedFunctions,
68+
String methodPrefixer,
6369
String moduleInterface,
6470
List<String> redlineTargets) {
6571
this.wasmFile = wasmFile;
@@ -69,6 +75,7 @@ private Config(
6975
this.targetWasmFolder = targetWasmFolder;
7076
this.interpreterFallback = interpreterFallback;
7177
this.interpretedFunctions = interpretedFunctions;
78+
this.methodPrefixer = methodPrefixer;
7279
this.moduleInterface = moduleInterface;
7380
this.redlineTargets = redlineTargets;
7481
}
@@ -101,6 +108,10 @@ public Set<Integer> interpretedFunctions() {
101108
return interpretedFunctions;
102109
}
103110

111+
public String methodPrefixer() {
112+
return methodPrefixer;
113+
}
114+
104115
public String moduleInterface() {
105116
return moduleInterface;
106117
}
@@ -141,6 +152,7 @@ public static final class Builder {
141152
private Path targetWasmFolder;
142153
private InterpreterFallback interpreterFallback = InterpreterFallback.FAIL;
143154
private Set<Integer> interpretedFunctions;
155+
private String methodPrefixer;
144156
private String moduleInterface;
145157
private List<String> redlineTargets = List.of();
146158

@@ -181,6 +193,11 @@ public Builder withInterpretedFunctions(Set<Integer> interpretedFunctions) {
181193
return this;
182194
}
183195

196+
public Builder withMethodPrefixer(String methodPrefixer) {
197+
this.methodPrefixer = methodPrefixer;
198+
return this;
199+
}
200+
184201
public Builder withModuleInterface(String moduleInterface) {
185202
this.moduleInterface = moduleInterface;
186203
return this;
@@ -200,6 +217,7 @@ public Config build() {
200217
targetWasmFolder,
201218
interpreterFallback,
202219
interpretedFunctions,
220+
methodPrefixer,
203221
moduleInterface,
204222
redlineTargets);
205223
}

‎build-time-compiler/src/main/java/run/endive/build/time/compiler/Generator.java‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.util.function.Function;
3636
import run.endive.codegen.CodegenUtils;
3737
import run.endive.codegen.ModuleInterfaceCodegen;
38+
import run.endive.compiler.MethodPrefixer;
3839
import run.endive.compiler.internal.ByteClassCollector;
3940
import run.endive.compiler.internal.Compiler;
4041
import run.endive.runtime.CompiledModule;
@@ -66,6 +67,7 @@ public Set<Integer> generateResources() throws IOException {
6667
.withClassCollectorFactory(ByteClassCollector::new)
6768
.withInterpreterFallback(config.interpreterFallback())
6869
.withInterpretedFunctions(config.interpretedFunctions())
70+
.withMethodPrefixer(loadMethodPrefixer(config.methodPrefixer()))
6971
.build();
7072
var result = compiler.compile();
7173

@@ -82,6 +84,31 @@ public Set<Integer> generateResources() throws IOException {
8284
return result.interpretedFunctions();
8385
}
8486

87+
/**
88+
* Instantiates the named {@link MethodPrefixer}, or returns {@code null} when {@code className}
89+
* is not set. The class must be on the classloader of the build tool driving the generator.
90+
*/
91+
private static MethodPrefixer loadMethodPrefixer(String className) {
92+
if (className == null || className.isEmpty()) {
93+
return null;
94+
}
95+
try {
96+
return Class.forName(className)
97+
.asSubclass(MethodPrefixer.class)
98+
.getDeclaredConstructor()
99+
.newInstance();
100+
} catch (ReflectiveOperationException | ClassCastException e) {
101+
throw new IllegalArgumentException(
102+
"Cannot instantiate the configured MethodPrefixer: "
103+
+ className
104+
+ ". It must implement "
105+
+ MethodPrefixer.class.getName()
106+
+ ", have a public no-argument constructor, and be on the build tool's"
107+
+ " classpath.",
108+
e);
109+
}
110+
}
111+
85112
public void generateSources() throws IOException {
86113
var machineName = config.name() + "Machine";
87114
var split = config.name().split("\\.");

‎compiler-maven-plugin/src/main/java/run/endive/build/time/maven/EndiveCompilerGenMojo.java‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@ public class EndiveCompilerGenMojo extends AbstractMojo {
7171
@Parameter(required = false, defaultValue = "")
7272
Set<Integer> interpretedFunctions;
7373

74+
/**
75+
* Fully qualified name of a MethodPrefixer implementation used to name the compiled methods.
76+
* Defaults to naming them func_0, func_1 and so on. Set it to
77+
* run.endive.compiler.NameSectionMethodPrefixer to name them after the module's name section,
78+
* which makes thread dumps and profiler output readable, or to your own implementation. A
79+
* custom class must be a dependency of this plugin, not of the project.
80+
*/
81+
@Parameter(required = false)
82+
String methodPrefixer;
83+
7484
/**
7585
* Fully qualified name of the user's class that will use the compiled module.
7686
* When set, the plugin generates _ModuleExports and _ModuleImports wrapper classes,
@@ -112,6 +122,7 @@ public void execute() throws MojoExecutionException {
112122
.withTargetWasmFolder(targetWasmFolder.toPath())
113123
.withInterpreterFallback(interpreterFallback)
114124
.withInterpretedFunctions(interpretedFunctions)
125+
.withMethodPrefixer(methodPrefixer)
115126
.withModuleInterface(moduleInterface);
116127
if (redlineTargetsExperimental != null && !redlineTargetsExperimental.isEmpty()) {
117128
configBuilder.withRedlineTargets(redlineTargetsExperimental);

‎compiler/src/main/java/run/endive/compiler/MethodPrefixer.java‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,6 @@ static MethodPrefixer defaultPrefixer() {
5252
* back to {@link #DEFAULT_PREFIX} for functions without one.
5353
*/
5454
static MethodPrefixer fromNameSection() {
55-
return (funcId, module) -> {
56-
var nameSection = module.nameSection();
57-
return nameSection == null ? null : nameSection.nameOfFunction(funcId);
58-
};
55+
return new NameSectionMethodPrefixer();
5956
}
6057
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package run.endive.compiler;
2+
3+
import run.endive.wasm.WasmModule;
4+
5+
/**
6+
* A {@link MethodPrefixer} that takes each prefix from the module's name custom section, falling
7+
* back to {@link MethodPrefixer#DEFAULT_PREFIX} for functions the section does not name.
8+
*/
9+
public final class NameSectionMethodPrefixer implements MethodPrefixer {
10+
11+
@Override
12+
public String getMethodPrefix(int funcId, WasmModule module) {
13+
var nameSection = module.nameSection();
14+
return nameSection == null ? null : nameSection.nameOfFunction(funcId);
15+
}
16+
}

0 commit comments

Comments
 (0)