diff --git a/compiler/src/main/java/run/endive/compiler/MachineFactoryCompiler.java b/compiler/src/main/java/run/endive/compiler/MachineFactoryCompiler.java index 1fb842dc9..f4a1e543b 100644 --- a/compiler/src/main/java/run/endive/compiler/MachineFactoryCompiler.java +++ b/compiler/src/main/java/run/endive/compiler/MachineFactoryCompiler.java @@ -112,6 +112,15 @@ public Builder withInterpretedFunctions(Set interpretedFunctions) { return this; } + /** + * Sets the {@link MethodPrefixer} used to name the compiled methods. Defaults to + * {@link MethodPrefixer#defaultPrefixer()}. + */ + public Builder withMethodPrefixer(MethodPrefixer methodPrefixer) { + compilerBuilder.withMethodPrefixer(methodPrefixer); + return this; + } + public Builder withCache(Cache cache) { this.cache = cache; return this; diff --git a/compiler/src/main/java/run/endive/compiler/MethodPrefixer.java b/compiler/src/main/java/run/endive/compiler/MethodPrefixer.java new file mode 100644 index 000000000..b45a18246 --- /dev/null +++ b/compiler/src/main/java/run/endive/compiler/MethodPrefixer.java @@ -0,0 +1,60 @@ +package run.endive.compiler; + +import run.endive.wasm.WasmModule; + +/** + * Supplies the human readable prefix used when naming the JVM method compiled for a WASM function. + * + *

The compiler derives every method name as {@code _}. The prefixer + * only controls the prefix; the compiler owns the rest of the name. That split keeps two + * invariants that the rest of the compiler and any external tooling can rely on, regardless of + * what a prefixer returns: + * + *

+ * + *

Characters that are illegal in a JVM method name ({@code . ; [ / < >}, see + * JVM Spec + * ยง4.2.2) are replaced with {@code _}. A prefixer that needs to preserve the original name + * exactly can avoid the substitution by encoding those characters itself, for example by + * percent-encoding them. + * + *

The prefix is a hint for humans reading a thread dump or a profile. Tooling should never + * parse it; it should use the function id instead. + */ +@FunctionalInterface +public interface MethodPrefixer { + + /** + * The prefix used when no prefixer is configured, and the fallback whenever a prefixer returns + * {@code null}, an empty string, or a string that sanitizes to nothing. + */ + String DEFAULT_PREFIX = "func"; + + /** + * Returns the prefix for the method compiled for {@code funcId}, or {@code null} to use + * {@link #DEFAULT_PREFIX}. + * + * @param funcId the WASM function index, covering imported and defined functions + * @param module the module being compiled + */ + String getMethodPrefix(int funcId, WasmModule module); + + /** Returns the default prefixer, naming every method {@value #DEFAULT_PREFIX}. */ + static MethodPrefixer defaultPrefixer() { + return (funcId, module) -> DEFAULT_PREFIX; + } + + /** + * Returns a prefixer that uses the function name from the module's name custom section, falling + * 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); + }; + } +} diff --git a/compiler/src/main/java/run/endive/compiler/internal/Compiler.java b/compiler/src/main/java/run/endive/compiler/internal/Compiler.java index 28bc1f91b..39f8a0b00 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Compiler.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Compiler.java @@ -30,6 +30,7 @@ import static run.endive.compiler.internal.CompilerUtil.emitInvokeVirtual; import static run.endive.compiler.internal.CompilerUtil.emitJvmToLong; import static run.endive.compiler.internal.CompilerUtil.emitLongToJvm; +import static run.endive.compiler.internal.CompilerUtil.extractFuncId; import static run.endive.compiler.internal.CompilerUtil.hasTooManyParameters; import static run.endive.compiler.internal.CompilerUtil.internalClassName; import static run.endive.compiler.internal.CompilerUtil.jvmReturnType; @@ -79,6 +80,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.commons.InstructionAdapter; import run.endive.compiler.InterpreterFallback; +import run.endive.compiler.MethodPrefixer; import run.endive.runtime.CallResult; import run.endive.runtime.Instance; import run.endive.runtime.Machine; @@ -161,6 +163,7 @@ public final class Compiler { private final boolean[] tailCallTypes; private final boolean moduleHasTailCalls; private final boolean moduleHasObjectRefs; + private final String[] methodNames; private boolean useBridgeClasses; private IntFunction callIndirectClassResolver; @@ -170,7 +173,8 @@ private Compiler( int maxFunctionsPerClass, InterpreterFallback interpreterFallback, Set interpretedFunctions, - Supplier classCollectorFactory) { + Supplier classCollectorFactory, + MethodPrefixer methodPrefixer) { this.className = requireNonNull(className, "className"); this.module = requireNonNull(module, "module"); this.analyzer = new WasmAnalyzer(module); @@ -202,6 +206,17 @@ private Compiler( this.functionTypes.stream() .anyMatch(ft -> ft.hasObjectRefParams() || ft.hasObjectRefReturns()); this.maxFunctionsPerClass = maxFunctionsPerClass; + // Resolve every method name up front, so that the prefixer is consulted exactly once per + // function and definitions and call sites cannot disagree. + var prefixer = requireNonNullElse(methodPrefixer, MethodPrefixer.defaultPrefixer()); + this.methodNames = new String[this.functionTypes.size()]; + for (int funcId = 0; funcId < this.methodNames.length; funcId++) { + this.methodNames[funcId] = methodNameForFunc(funcId, prefixer, module); + } + } + + private String methodName(int funcId) { + return methodNames[funcId]; } private Set collectCallRefTypeIds() { @@ -229,6 +244,7 @@ public static final class Builder { private InterpreterFallback interpreterFallback; private Set interpretedFunctions; private Supplier classCollectorFactory; + private MethodPrefixer methodPrefixer; private Builder(WasmModule module) { this.module = module; @@ -259,6 +275,11 @@ public Builder withClassCollectorFactory(Supplier classCollector return this; } + public Builder withMethodPrefixer(MethodPrefixer methodPrefixer) { + this.methodPrefixer = methodPrefixer; + return this; + } + public Compiler build() { var className = this.className; if (className == null) { @@ -280,7 +301,8 @@ public Compiler build() { maxFunctionsPerClass, interpreterFallback, interpretedFunctions, - classCollectorFactory); + classCollectorFactory, + methodPrefixer); } } @@ -351,10 +373,8 @@ private void compileExtraClasses() { break; } catch (MethodTooLargeException e) { String methodName = e.getMethodName(); - if (methodName.startsWith("func_")) { - // Add the method to interpreted function list... and try again. - var funcId = Integer.parseInt(methodName.substring("func_".length())); - + int funcId = extractFuncId(methodName); + if (funcId >= 0) { String functionDescription = "WASM function index: " + funcId; if (module.nameSection() != null) { String name = module.nameSection().nameOfFunction(funcId); @@ -496,7 +516,7 @@ private Consumer emitFunctionGroup(int start, int end, String inte if (i < functionImports) { emitFunction( classWriter, - methodNameForFunc(funcId), + methodName(funcId), methodTypeFor(type), true, asm -> compileHostFunction(funcId, type, asm)); @@ -507,7 +527,7 @@ private Consumer emitFunctionGroup(int start, int end, String inte emitFunction( classWriter, - methodNameForFunc(funcId), + methodName(funcId), methodTypeFor(type), true, asm -> @@ -689,8 +709,8 @@ private boolean isFuncTypeMatch(int expectedTypeId, int funcIdx, FunctionType ex private static RuntimeException handleMethodTooLarge( MethodTooLargeException e, WasmModule module) { String name = e.getMethodName(); - if (name.startsWith("func_") && module.nameSection() != null) { - int funcId = Integer.parseInt(name.split("_", -1)[1]); + int funcId = extractFuncId(name); + if (funcId >= 0 && module.nameSection() != null) { String function = module.nameSection().nameOfFunction(funcId); if (function != null) { name += " (" + function + ")"; @@ -1392,7 +1412,10 @@ private void compileCallFunction(int funcId, FunctionType type, InstructionAdapt asm.load(0, OBJECT_TYPE); emitInvokeFunction( - asm, internalClassName(classNameForFuncGroup(className, funcId)), funcId, type); + asm, + internalClassName(classNameForFuncGroup(className, funcId)), + methodName(funcId), + type); // box the result into long[] Class returnType = jvmReturnType(type); @@ -1482,7 +1505,10 @@ private void compileCallWithRefsFunction( asm.load(0, OBJECT_TYPE); emitInvokeFunction( - asm, internalClassName(classNameForFuncGroup(className, funcId)), funcId, type); + asm, + internalClassName(classNameForFuncGroup(className, funcId)), + methodName(funcId), + type); // Build CallResult from the function's JVM return value Class returnType = jvmReturnType(type); @@ -1681,7 +1707,10 @@ private void compileCallIndirect( // return func_0(a, b, memory, callerInstance); asm.mark(labels[i]); emitInvokeFunction( - asm, classNameForFuncGroup(internalClassName, keys[i]), keys[i], type); + asm, + classNameForFuncGroup(internalClassName, keys[i]), + methodName(keys[i]), + type); asm.areturn(getType(jvmReturnType(type))); } @@ -1829,7 +1858,10 @@ private void compileCallIndirectApply( // return func_0(a, b, memory, callerInstance); asm.mark(labels[i]); emitInvokeFunction( - asm, classNameForFuncGroup(internalClassName, keys[i]), keys[i], type); + asm, + classNameForFuncGroup(internalClassName, keys[i]), + methodName(keys[i]), + type); asm.areturn(getType(jvmReturnType(type))); asm.areturn(OBJECT_TYPE); } @@ -2105,7 +2137,8 @@ private void compileFunction( tailCallFunctions, tailCallTypes, useBridgeClasses ? callIndirectClassResolver : typeId -> internalClassName, - analysis.maxTempSlots()); + analysis.maxTempSlots(), + this::methodName); int localsCount = type.params().size(); if (hasTooManyParameters(type)) { diff --git a/compiler/src/main/java/run/endive/compiler/internal/CompilerUtil.java b/compiler/src/main/java/run/endive/compiler/internal/CompilerUtil.java index d46b7178e..2e8fde3b3 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/CompilerUtil.java +++ b/compiler/src/main/java/run/endive/compiler/internal/CompilerUtil.java @@ -18,6 +18,7 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; +import run.endive.compiler.MethodPrefixer; import run.endive.runtime.Instance; import run.endive.runtime.Memory; import run.endive.wasm.WasmModule; @@ -282,11 +283,14 @@ public static void emitInvokeVirtual(MethodVisitor asm, Method method) { } public static void emitInvokeFunction( - MethodVisitor asm, String internalClassName, int funcId, FunctionType functionType) { + MethodVisitor asm, + String internalClassName, + String methodName, + FunctionType functionType) { asm.visitMethodInsn( Opcodes.INVOKESTATIC, internalClassName, - methodNameForFunc(funcId), + methodName, methodTypeFor(functionType).toMethodDescriptorString(), false); } @@ -298,8 +302,46 @@ public static String valueMethodName(List types) { .collect(joining("_")); } - public static String methodNameForFunc(int funcId) { - return "func_" + funcId; + /** + * Builds the JVM method name for a WASM function as {@code _}. The + * prefixer only supplies the prefix, so the {@code _} suffix always makes the name + * unique and keeps the function id recoverable via {@link #extractFuncId(String)}. + */ + public static String methodNameForFunc(int funcId, MethodPrefixer prefixer, WasmModule module) { + String prefix = prefixer == null ? null : prefixer.getMethodPrefix(funcId, module); + if (prefix != null) { + prefix = sanitizeWasmName(prefix); + } + if (prefix == null || prefix.isEmpty()) { + prefix = MethodPrefixer.DEFAULT_PREFIX; + } + return prefix + "_" + funcId; + } + + static String sanitizeWasmName(String name) { + StringBuilder sb = new StringBuilder(name.length()); + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + // see https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-4.html#jvms-4.2.2 for reference + if (c == '.' || c == ';' || c == '[' || c == '/' || c == '<' || c == '>') { + sb.append('_'); + } else { + sb.append(c); + } + } + return sb.toString(); + } + + static int extractFuncId(String methodName) { + int lastUnderscore = methodName.lastIndexOf('_'); + if (lastUnderscore < 0) { + return -1; + } + try { + return Integer.parseInt(methodName.substring(lastUnderscore + 1)); + } catch (NumberFormatException e) { + return -1; + } } static String callMethodName(int funcId) { diff --git a/compiler/src/main/java/run/endive/compiler/internal/Context.java b/compiler/src/main/java/run/endive/compiler/internal/Context.java index 6cb7a1ef2..bdbd6b09f 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Context.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Context.java @@ -33,6 +33,7 @@ final class Context { private final int tempSlot; private final int trySaveBaseSlot; private final IntFunction callIndirectClassResolver; + private final IntFunction methodNames; public Context( WasmModule module, @@ -46,7 +47,8 @@ public Context( boolean[] tailCallFunctions, boolean[] tailCallTypes, IntFunction callIndirectClassResolver, - int maxTempSlots) { + int maxTempSlots, + IntFunction methodNames) { this.module = module; this.internalClassName = internalClassName; this.maxFunctionsPerClass = maxFunctionsPerClass; @@ -58,6 +60,7 @@ public Context( this.tailCallFunctions = tailCallFunctions; this.tailCallTypes = tailCallTypes; this.callIndirectClassResolver = callIndirectClassResolver; + this.methodNames = methodNames; // compute JVM slot indices for WASM locals List slots = new ArrayList<>(type.params().size() + body.localTypes().size()); @@ -122,6 +125,10 @@ public TypeSection typeSection() { return module.typeSection(); } + public String methodNameForFunc(int funcId) { + return methodNames.apply(funcId); + } + public int getId() { return funcId; } diff --git a/compiler/src/main/java/run/endive/compiler/internal/Emitters.java b/compiler/src/main/java/run/endive/compiler/internal/Emitters.java index 7c10aabe2..5d5ae6481 100644 --- a/compiler/src/main/java/run/endive/compiler/internal/Emitters.java +++ b/compiler/src/main/java/run/endive/compiler/internal/Emitters.java @@ -398,7 +398,7 @@ public static void CALL(Context ctx, CompilerInstruction ins, InstructionAdapter emitInvokeFunction( asm, ctx.classNameForFuncGroup(ctx.internalClassName(), funcId), - funcId, + ctx.methodNameForFunc(funcId), functionType); if (ctx.needsTailCallCheck(funcId)) { diff --git a/compiler/src/test/java/run/endive/compiler/internal/CompilerUtilTest.java b/compiler/src/test/java/run/endive/compiler/internal/CompilerUtilTest.java new file mode 100644 index 000000000..3c20a41ae --- /dev/null +++ b/compiler/src/test/java/run/endive/compiler/internal/CompilerUtilTest.java @@ -0,0 +1,76 @@ +package run.endive.compiler.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static run.endive.compiler.internal.CompilerUtil.extractFuncId; +import static run.endive.compiler.internal.CompilerUtil.methodNameForFunc; +import static run.endive.compiler.internal.CompilerUtil.sanitizeWasmName; + +import org.junit.jupiter.api.Test; +import run.endive.compiler.MethodPrefixer; + +public class CompilerUtilTest { + + @Test + public void methodNameWithoutPrefixer() { + assertEquals("func_0", methodNameForFunc(0, null, null)); + assertEquals("func_42", methodNameForFunc(42, null, null)); + } + + @Test + public void methodNameUsesPrefix() { + MethodPrefixer prefixer = (funcId, module) -> "foo"; + assertEquals("foo_0", methodNameForFunc(0, prefixer, null)); + assertEquals("foo_42", methodNameForFunc(42, prefixer, null)); + } + + @Test + public void methodNameSanitizesPrefix() { + MethodPrefixer prefixer = (funcId, module) -> "a.b/c"; + assertEquals("a_b_c_7", methodNameForFunc(7, prefixer, null)); + } + + @Test + public void methodNameFallsBackToDefaultPrefix() { + assertEquals("func_3", methodNameForFunc(3, (funcId, module) -> null, null)); + assertEquals("func_3", methodNameForFunc(3, (funcId, module) -> "", null)); + } + + @Test + public void percentEncodedPrefixSurvivesSanitization() { + // A prefixer that encodes the illegal characters itself keeps the name reversible. + MethodPrefixer prefixer = (funcId, module) -> "core%2Efmt%2FFormatter"; + assertEquals("core%2Efmt%2FFormatter_9", methodNameForFunc(9, prefixer, null)); + } + + @Test + public void sanitizeReplacesIllegalChars() { + assertEquals("foo", sanitizeWasmName("foo")); + assertEquals("a_b_c", sanitizeWasmName("a.b/c")); + assertEquals("a_b_c_d_e_f", sanitizeWasmName("a.b;c[df")); + } + + @Test + public void sanitizePreservesUnderscoresAndDashes() { + assertEquals("my_func", sanitizeWasmName("my_func")); + assertEquals("my-func", sanitizeWasmName("my-func")); + } + + @Test + public void extractFuncIdFromSimpleName() { + assertEquals(0, extractFuncId("func_0")); + assertEquals(42, extractFuncId("func_42")); + } + + @Test + public void extractFuncIdFromNamedMethod() { + assertEquals(0, extractFuncId("foo_0")); + assertEquals(5, extractFuncId("my_func_5")); + assertEquals(9, extractFuncId("core%2Efmt%2FFormatter_9")); + } + + @Test + public void extractFuncIdReturnsNegativeForInvalid() { + assertEquals(-1, extractFuncId("nounderscore")); + assertEquals(-1, extractFuncId("func_abc")); + } +} diff --git a/compiler/src/test/java/run/endive/compiler/internal/InterruptionTest.java b/compiler/src/test/java/run/endive/compiler/internal/InterruptionTest.java index e1bec61fc..aa85cd10a 100644 --- a/compiler/src/test/java/run/endive/compiler/internal/InterruptionTest.java +++ b/compiler/src/test/java/run/endive/compiler/internal/InterruptionTest.java @@ -83,7 +83,7 @@ private static void waitForWasmExecution(Thread thread, int funcIdx) var className = element.getClassName(); var methodName = element.getMethodName(); if (className.startsWith(Compiler.DEFAULT_CLASS_NAME + "FuncGroup_") - && methodName.equals(methodNameForFunc(funcIdx))) { + && methodName.equals(methodNameForFunc(funcIdx, null, null))) { return; } } diff --git a/compiler/src/test/java/run/endive/compiler/internal/MethodPrefixerTest.java b/compiler/src/test/java/run/endive/compiler/internal/MethodPrefixerTest.java new file mode 100644 index 000000000..4c292ce88 --- /dev/null +++ b/compiler/src/test/java/run/endive/compiler/internal/MethodPrefixerTest.java @@ -0,0 +1,126 @@ +package run.endive.compiler.internal; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import run.endive.compiler.MachineFactoryCompiler; +import run.endive.compiler.MethodPrefixer; +import run.endive.corpus.CorpusResources; +import run.endive.runtime.Instance; +import run.endive.wasm.Parser; + +public class MethodPrefixerTest { + + @Test + public void defaultUsesFuncPrefix() { + var module = Parser.parse(CorpusResources.getResource("compiled/branching.wat.wasm")); + var result = Compiler.builder(module).build().compile(); + var methods = funcGroupMethods(result); + + assertTrue( + methods.stream().anyMatch(n -> n.startsWith("func_")), + "Expected the default \"func\" prefix, got: " + methods); + assertFalse( + methods.stream().anyMatch(n -> n.startsWith("foo_")), + "Default mode should not produce named methods, got: " + methods); + } + + @Test + public void nameSectionPrefixerProducesNamedMethods() { + var module = Parser.parse(CorpusResources.getResource("compiled/branching.wat.wasm")); + var result = + Compiler.builder(module) + .withMethodPrefixer(MethodPrefixer.fromNameSection()) + .build() + .compile(); + var methods = funcGroupMethods(result); + + assertTrue( + methods.stream().anyMatch(n -> n.startsWith("foo_")), + "Expected a method starting with 'foo_', got: " + methods); + } + + @Test + public void customPrefixerIsApplied() { + var module = Parser.parse(CorpusResources.getResource("compiled/branching.wat.wasm")); + var result = + Compiler.builder(module) + .withMethodPrefixer((funcId, m) -> "wasm") + .build() + .compile(); + var methods = funcGroupMethods(result); + + assertTrue( + methods.stream().anyMatch(n -> n.startsWith("wasm_")), + "Expected a method starting with 'wasm_', got: " + methods); + } + + @Test + public void everyMethodNameKeepsTheFuncIdSuffix() { + var module = Parser.parse(CorpusResources.getResource("compiled/branching.wat.wasm")); + var result = + Compiler.builder(module) + // a deliberately hostile prefixer: illegal characters, digits, underscores + .withMethodPrefixer((funcId, m) -> "a.b_1/c<9>") + .build() + .compile(); + + for (var name : funcGroupMethods(result)) { + if (!name.startsWith("a_b_1_c_9_")) { + continue; + } + assertTrue( + CompilerUtil.extractFuncId(name) >= 0, + "Could not recover the func id from: " + name); + } + } + + @Test + public void namedMethodsExecuteCorrectly() { + var module = Parser.parse(CorpusResources.getResource("compiled/branching.wat.wasm")); + var instance = + Instance.builder(module) + .withMachineFactory( + MachineFactoryCompiler.builder(module) + .withMethodPrefixer(MethodPrefixer.fromNameSection()) + .compile()) + .build(); + + var function = instance.export("foo"); + assertArrayEquals(new long[] {42}, function.apply(0)); + assertArrayEquals(new long[] {99}, function.apply(1)); + } + + private static List funcGroupMethods(CompilerResult result) { + var methods = new ArrayList(); + for (var entry : result.classBytes().entrySet()) { + if (!entry.getKey().contains("FuncGroup")) { + continue; + } + var reader = new ClassReader(entry.getValue()); + reader.accept( + new ClassVisitor(Opcodes.ASM9) { + @Override + public MethodVisitor visitMethod( + int access, + String name, + String descriptor, + String signature, + String[] exceptions) { + methods.add(name); + return null; + } + }, + 0); + } + return methods; + } +} diff --git a/docs/docs/execution/runtime-compiler.md b/docs/docs/execution/runtime-compiler.md index 29698bead..85bb1ccf3 100644 --- a/docs/docs/execution/runtime-compiler.md +++ b/docs/docs/execution/runtime-compiler.md @@ -118,6 +118,34 @@ var instance = Instance.builder(module). Typically, you can obtain the list of the functions by running the compiler once with `InterpreterFallback.WARN` +### Method Names + +By default, the compiler names compiled methods `func_0`, `func_1`, etc. A `MethodPrefixer` lets you +replace the `func` prefix with something more recognisable, which improves readability of thread +dumps, profiler output and stack traces. `MethodPrefixer.fromNameSection()` uses the function names +from the module's name section, where present: + +```java +var instance = Instance.builder(module). + withMachineFactory( + MachineFactoryCompiler.builder(module) + .withMethodPrefixer(MethodPrefixer.fromNameSection()) + .compile() + ). + build(); +``` + +A prefixer supplies only the prefix; the compiler always appends `_` to produce the method +name (e.g. `my_func_42`). That keeps method names unique whatever the prefixer returns, and keeps the +WASM function index recoverable from any method name. + +Characters that are illegal in JVM method names (`. ; [ / < >`) are replaced with underscores. A +prefixer that needs to preserve names exactly can avoid the substitution by encoding those +characters itself, for example by percent-encoding them. + +The prefix is a hint for humans inspecting a compiled method name. Tools should not parse it, and +should use the function index instead. + ### Caveats Please note that compiling and executing Wasm modules at runtime requires: