Skip to content
Draft
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
71 changes: 68 additions & 3 deletions wasm-tools/src/main/java/run/endive/tools/wasm/Validate.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import run.endive.log.Logger;
import run.endive.log.SystemLogger;
Expand All @@ -21,8 +23,6 @@

public final class Validate {

private Validate() {}

private static final Logger logger =
new SystemLogger() {
@Override
Expand All @@ -32,6 +32,16 @@ public boolean isLoggable(Logger.Level level) {
};
private static final WasmModule MODULE = WasmToolsModule.load();

private final List<String> features;

private Validate(List<String> features) {
this.features = features;
}

public static Builder builder() {
return new Builder();
}

public static void validate(File file) {
try (var is = new FileInputStream(file)) {
validate(is);
Expand All @@ -49,16 +59,49 @@ public static void validate(String wat) {
}

public static void validate(InputStream is) {
doValidate(is, Collections.emptyList());
}

public void validateModule(File file) {
try (var is = new FileInputStream(file)) {
validateModule(is);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

public void validateModule(String wat) {
try (var is = new ByteArrayInputStream(wat.getBytes(StandardCharsets.UTF_8))) {
validateModule(is);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

public void validateModule(InputStream is) {
doValidate(is, features);
}

private static void doValidate(InputStream is, List<String> features) {
try (var stdinStream = new ByteArrayInputStream(is.readAllBytes());
var stdoutStream = new ByteArrayOutputStream();
var stderrStream = new ByteArrayOutputStream()) {

List<String> args = new ArrayList<>();
args.add("wasm-tools");
args.add("validate");
if (!features.isEmpty()) {
args.add("--features");
args.add(String.join(",", features));
}
args.add("-");

var options =
WasiOptions.builder()
.withStdin(stdinStream, false)
.withStdout(stdoutStream, false)
.withStderr(stderrStream, false)
.withArguments(List.of("wasm-tools", "validate", "-"))
.withArguments(args)
.build();

logger.info("Running command: " + String.join(" ", options.arguments()));
Expand Down Expand Up @@ -86,4 +129,26 @@ public static void validate(InputStream is) {
throw new UncheckedIOException(e);
}
}

public static final class Builder {
private final List<String> features = new ArrayList<>();

private Builder() {}

public Builder withFeatures(WasmFeature... features) {
for (WasmFeature f : features) {
this.features.add(f.flag());
}
return this;
}

public Builder withoutFeature(WasmFeature feature) {
this.features.add(feature.negatedFlag());
return this;
}

public Validate build() {
return new Validate(Collections.unmodifiableList(new ArrayList<>(features)));
}
}
}
76 changes: 76 additions & 0 deletions wasm-tools/src/main/java/run/endive/tools/wasm/WasmFeature.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package run.endive.tools.wasm;

/**
* WebAssembly features and feature groups for use with {@link Validate}.
*
* <p>Feature groups ({@link #MVP}, {@link #WASM1}, {@link #WASM2}, {@link #WASM3}, {@link #LIME1})
* reset the feature set to a predefined baseline. Individual features can then be enabled or
* disabled on top. Order matters: groups reset, then individual features toggle.
*
* <p>The flag strings correspond to the {@code wasm-tools validate --features} CLI flags from
* wasm-tools v1.240.0.
*/
public enum WasmFeature {

// Feature groups
MVP("mvp"),
WASM1("wasm1"),
WASM2("wasm2"),
WASM3("wasm3"),
LIME1("lime1"),

// Meta-flag
ALL("all"),

// Individual features
MUTABLE_GLOBAL("mutable-global"),
SATURATING_FLOAT_TO_INT("saturating-float-to-int"),
SIGN_EXTENSION("sign-extension"),
REFERENCE_TYPES("reference-types"),
MULTI_VALUE("multi-value"),
BULK_MEMORY("bulk-memory"),
SIMD("simd"),
RELAXED_SIMD("relaxed-simd"),
THREADS("threads"),
SHARED_EVERYTHING_THREADS("shared-everything-threads"),
TAIL_CALL("tail-call"),
FLOATS("floats"),
MULTI_MEMORY("multi-memory"),
EXCEPTIONS("exceptions"),
MEMORY64("memory64"),
EXTENDED_CONST("extended-const"),
COMPONENT_MODEL("component-model"),
FUNCTION_REFERENCES("function-references"),
MEMORY_CONTROL("memory-control"),
GC("gc"),
CUSTOM_PAGE_SIZES("custom-page-sizes"),
LEGACY_EXCEPTIONS("legacy-exceptions"),
GC_TYPES("gc-types"),
STACK_SWITCHING("stack-switching"),
WIDE_ARITHMETIC("wide-arithmetic"),
CM_VALUES("cm-values"),
CM_NESTED_NAMES("cm-nested-names"),
CM_ASYNC("cm-async"),
CM_ASYNC_STACKFUL("cm-async-stackful"),
CM_ASYNC_BUILTINS("cm-async-builtins"),
CM_THREADING("cm-threading"),
CM_ERROR_CONTEXT("cm-error-context"),
CM_FIXED_SIZE_LIST("cm-fixed-size-list"),
CM_GC("cm-gc"),
CALL_INDIRECT_OVERLONG("call-indirect-overlong"),
BULK_MEMORY_OPT("bulk-memory-opt");

private final String flag;

WasmFeature(String flag) {
this.flag = flag;
}

public String flag() {
return flag;
}

public String negatedFlag() {
return "-" + flag;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,47 @@ public void shouldValidateWat() {
exitException.getMessage().contains("failed to validate"),
"found: " + exitException.getMessage() + " doesn't contains the expected result");
}

@Test
public void shouldValidateSimpleModuleWithWasm1() {
Validate.builder()
.withFeatures(WasmFeature.WASM1)
.build()
.validateModule(
"(module (func (export \"add\")"
+ " (param i32) (param i32) (result i32)"
+ " (i32.add (local.get 0) (local.get 1))))");
}

@Test
public void shouldRejectSimdModuleWithWasm1() {
var validator = Validate.builder().withFeatures(WasmFeature.WASM1).build();
assertThrows(
WatParseException.class,
() ->
validator.validateModule(
"(module (func (result v128) (v128.const i32x4 0 0 0 0)))"));
}

@Test
public void shouldAcceptSimdModuleWithWasm2() {
Validate.builder()
.withFeatures(WasmFeature.WASM2)
.build()
.validateModule("(module (func (result v128) (v128.const i32x4 0 0 0 0)))");
}

@Test
public void shouldRejectSimdModuleWhenDisabled() {
var validator =
Validate.builder()
.withFeatures(WasmFeature.WASM2)
.withoutFeature(WasmFeature.SIMD)
.build();
assertThrows(
WatParseException.class,
() ->
validator.validateModule(
"(module (func (result v128) (v128.const i32x4 0 0 0 0)))"));
}
}