Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions annotations/it/src/it/method-prefixer/invoker.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
invoker.goals=test
101 changes: 101 additions & 0 deletions annotations/it/src/it/method-prefixer/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<project>

<modelVersion>4.0.0</modelVersion>
<groupId>run.endive</groupId>

<artifactId>method-prefixer-endive-it</artifactId>
<version>0.0-SNAPSHOT</version>
<packaging>jar</packaging>

<properties>
<maven.compiler.release>@maven.compiler.release@</maven.compiler.release>
</properties>

<dependencies>
<dependency>
<groupId>run.endive</groupId>
<artifactId>runtime</artifactId>
<version>@project.version@</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>@junit.version@</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>@junit.version@</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>@maven-compiler-plugin.version@</version>
<configuration>
<release>${maven.compiler.release}</release>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>@maven-resources-plugin.version@</version>
<executions>
<execution>
<id>copy-resources</id>
<goals>
<goal>copy-resources</goal>
</goals>
<phase>validate</phase>
<configuration>
<outputDirectory>${basedir}/src/test/resources</outputDirectory>
<resources>
<resource>
<directory>@basedir@/../../wasm-corpus/src/main/resources/compiled</directory>
<includes>
<include>trap.wat.wasm</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>run.endive</groupId>
<artifactId>endive-compiler-maven-plugin</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<id>compile-named</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<name>endive.test.NamedTrapModule</name>
<wasmFile>src/test/resources/trap.wat.wasm</wasmFile>
<methodPrefixer>run.endive.compiler.NameSectionMethodPrefixer</methodPrefixer>
</configuration>
</execution>
<execution>
<id>compile-default</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<name>endive.test.DefaultTrapModule</name>
<wasmFile>src/test/resources/trap.wat.wasm</wasmFile>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package endive.test;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import run.endive.runtime.Instance;
import run.endive.runtime.Machine;
import run.endive.wasm.UninstantiableException;
import run.endive.wasm.WasmModule;

/**
* The module traps in its start function, so instantiating it throws from inside the compiled code
* and the stack trace carries the generated method names. The module names its three functions
* "trap", "innerFunc" and "start".
*/
public class MethodPrefixerTest {

@Test
public void nameSectionPrefixerNamesTheGeneratedMethods() {
var methods = methodNamesFromTrap(NamedTrapModule.load(), NamedTrapModule::create);

assertTrue(methods.contains("trap_0"), "Expected trap_0 in: " + methods);
assertTrue(methods.contains("innerFunc_1"), "Expected innerFunc_1 in: " + methods);
assertTrue(methods.contains("start_2"), "Expected start_2 in: " + methods);
}

@Test
public void withoutAPrefixerMethodsKeepTheDefaultNames() {
var methods = methodNamesFromTrap(DefaultTrapModule.load(), DefaultTrapModule::create);

assertTrue(methods.contains("func_0"), "Expected func_0 in: " + methods);
assertTrue(methods.contains("func_1"), "Expected func_1 in: " + methods);
assertTrue(methods.contains("func_2"), "Expected func_2 in: " + methods);
}

/** Instantiates the module and returns every method name on the resulting stack traces. */
private static List<String> methodNamesFromTrap(
WasmModule module, Function<Instance, Machine> machineFactory) {
Throwable thrown =
assertThrows(
UninstantiableException.class,
() -> Instance.builder(module).withMachineFactory(machineFactory).build());

var methods = new ArrayList<String>();
for (Throwable t = thrown; t != null; t = t.getCause()) {
for (var frame : t.getStackTrace()) {
methods.add(frame.getMethodName());
}
}
return methods;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ public String[] getVersion() {

@CommandLine.Option(
order = 7,
names = "--method-prefixer",
description =
"Fully qualified name of a MethodPrefixer implementation used to name compiled"
+ " methods, for example"
+ " run.endive.compiler.NameSectionMethodPrefixer")
String methodPrefixer;

@CommandLine.Option(
order = 8,
names = "--module-interface",
description =
"Fully qualified class name for which to generate _ModuleExports and"
Expand All @@ -90,6 +99,7 @@ public void run() {
.withTargetWasmFolder(targetWasmFolder)
.withInterpreterFallback(interpreterFallback)
.withInterpretedFunctions(interpretedFunctions)
.withMethodPrefixer(methodPrefixer)
.withModuleInterface(moduleInterface)
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ public final class Config {
*/
private final Set<Integer> interpretedFunctions;

/**
* the fully qualified name of the MethodPrefixer implementation used to name compiled methods
*/
private final String methodPrefixer;

/**
* the fully qualified name of the user's class for which to generate module interface wrappers
*/
Expand All @@ -60,6 +65,7 @@ private Config(
Path targetWasmFolder,
InterpreterFallback interpreterFallback,
Set<Integer> interpretedFunctions,
String methodPrefixer,
String moduleInterface,
List<String> redlineTargets) {
this.wasmFile = wasmFile;
Expand All @@ -69,6 +75,7 @@ private Config(
this.targetWasmFolder = targetWasmFolder;
this.interpreterFallback = interpreterFallback;
this.interpretedFunctions = interpretedFunctions;
this.methodPrefixer = methodPrefixer;
this.moduleInterface = moduleInterface;
this.redlineTargets = redlineTargets;
}
Expand Down Expand Up @@ -101,6 +108,10 @@ public Set<Integer> interpretedFunctions() {
return interpretedFunctions;
}

public String methodPrefixer() {
return methodPrefixer;
}

public String moduleInterface() {
return moduleInterface;
}
Expand Down Expand Up @@ -141,6 +152,7 @@ public static final class Builder {
private Path targetWasmFolder;
private InterpreterFallback interpreterFallback = InterpreterFallback.FAIL;
private Set<Integer> interpretedFunctions;
private String methodPrefixer;
private String moduleInterface;
private List<String> redlineTargets = List.of();

Expand Down Expand Up @@ -181,6 +193,11 @@ public Builder withInterpretedFunctions(Set<Integer> interpretedFunctions) {
return this;
}

public Builder withMethodPrefixer(String methodPrefixer) {
this.methodPrefixer = methodPrefixer;
return this;
}

public Builder withModuleInterface(String moduleInterface) {
this.moduleInterface = moduleInterface;
return this;
Expand All @@ -200,6 +217,7 @@ public Config build() {
targetWasmFolder,
interpreterFallback,
interpretedFunctions,
methodPrefixer,
moduleInterface,
redlineTargets);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.function.Function;
import run.endive.codegen.CodegenUtils;
import run.endive.codegen.ModuleInterfaceCodegen;
import run.endive.compiler.MethodPrefixer;
import run.endive.compiler.internal.ByteClassCollector;
import run.endive.compiler.internal.Compiler;
import run.endive.runtime.CompiledModule;
Expand Down Expand Up @@ -66,6 +67,7 @@ public Set<Integer> generateResources() throws IOException {
.withClassCollectorFactory(ByteClassCollector::new)
.withInterpreterFallback(config.interpreterFallback())
.withInterpretedFunctions(config.interpretedFunctions())
.withMethodPrefixer(loadMethodPrefixer(config.methodPrefixer()))
.build();
var result = compiler.compile();

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

/**
* Instantiates the named {@link MethodPrefixer}, or returns {@code null} when {@code className}
* is not set. The class must be on the classloader of the build tool driving the generator.
*/
private static MethodPrefixer loadMethodPrefixer(String className) {
if (className == null || className.isEmpty()) {
return null;
}
try {
return Class.forName(className)
.asSubclass(MethodPrefixer.class)
.getDeclaredConstructor()
.newInstance();
} catch (ReflectiveOperationException | ClassCastException e) {
throw new IllegalArgumentException(
"Cannot instantiate the configured MethodPrefixer: "
+ className
+ ". It must implement "
+ MethodPrefixer.class.getName()
+ ", have a public no-argument constructor, and be on the build tool's"
+ " classpath.",
e);
}
}

public void generateSources() throws IOException {
var machineName = config.name() + "Machine";
var split = config.name().split("\\.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ public class EndiveCompilerGenMojo extends AbstractMojo {
@Parameter(required = false, defaultValue = "")
Set<Integer> interpretedFunctions;

/**
* Fully qualified name of a MethodPrefixer implementation used to name the compiled methods.
* Defaults to naming them func_0, func_1 and so on. Set it to
* run.endive.compiler.NameSectionMethodPrefixer to name them after the module's name section,
* which makes thread dumps and profiler output readable, or to your own implementation. A
* custom class must be a dependency of this plugin, not of the project.
*/
@Parameter(required = false)
String methodPrefixer;

/**
* Fully qualified name of the user's class that will use the compiled module.
* When set, the plugin generates _ModuleExports and _ModuleImports wrapper classes,
Expand Down Expand Up @@ -112,6 +122,7 @@ public void execute() throws MojoExecutionException {
.withTargetWasmFolder(targetWasmFolder.toPath())
.withInterpreterFallback(interpreterFallback)
.withInterpretedFunctions(interpretedFunctions)
.withMethodPrefixer(methodPrefixer)
.withModuleInterface(moduleInterface);
if (redlineTargetsExperimental != null && !redlineTargetsExperimental.isEmpty()) {
configBuilder.withRedlineTargets(redlineTargetsExperimental);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ static MethodPrefixer defaultPrefixer() {
* back to {@link #DEFAULT_PREFIX} for functions without one.
*/
static MethodPrefixer fromNameSection() {
return (funcId, module) -> {
var nameSection = module.nameSection();
return nameSection == null ? null : nameSection.nameOfFunction(funcId);
};
return new NameSectionMethodPrefixer();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package run.endive.compiler;

import run.endive.wasm.WasmModule;

/**
* A {@link MethodPrefixer} that takes each prefix from the module's name custom section, falling
* back to {@link MethodPrefixer#DEFAULT_PREFIX} for functions the section does not name.
*/
public final class NameSectionMethodPrefixer implements MethodPrefixer {

@Override
public String getMethodPrefix(int funcId, WasmModule module) {
var nameSection = module.nameSection();
return nameSection == null ? null : nameSection.nameOfFunction(funcId);
}
}
Loading
Loading