From 27c1d014c017dfd3ccabf2a29e47d2d6097717d4 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 28 Aug 2024 17:00:30 -0700 Subject: [PATCH] Add experimental annotation framework for functions --- bom/pom.xml | 5 + function-annotations/pom.xml | 16 + .../function/annotations/HostModule.java | 12 + .../function/annotations/WasmExport.java | 12 + function-processor/pom.xml | 77 + .../function/processor/FunctionProcessor.java | 280 ++ .../javax.annotation.processing.Processor | 1 + .../processor/FunctionProcessorTest.java | 58 + .../src/test/resources/BasicMath.java | 28 + .../test/resources/BasicMathGenerated.java | 49 + .../src/test/resources/InvalidParameter.java | 13 + .../src/test/resources/InvalidReturn.java | 13 + .../src/test/resources/Simple.java | 25 + .../src/test/resources/SimpleGenerated.java | 39 + pom.xml | 26 + wasi/pom.xml | 34 + .../dylibso/chicory/wasi/WasiPreview1.java | 2522 +++++++---------- 17 files changed, 1729 insertions(+), 1481 deletions(-) create mode 100644 function-annotations/pom.xml create mode 100644 function-annotations/src/main/java/com/dylibso/chicory/function/annotations/HostModule.java create mode 100644 function-annotations/src/main/java/com/dylibso/chicory/function/annotations/WasmExport.java create mode 100644 function-processor/pom.xml create mode 100644 function-processor/src/main/java/com/dylibso/chicory/function/processor/FunctionProcessor.java create mode 100644 function-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor create mode 100644 function-processor/src/test/java/com/dylibso/chicory/function/processor/FunctionProcessorTest.java create mode 100644 function-processor/src/test/resources/BasicMath.java create mode 100644 function-processor/src/test/resources/BasicMathGenerated.java create mode 100644 function-processor/src/test/resources/InvalidParameter.java create mode 100644 function-processor/src/test/resources/InvalidReturn.java create mode 100644 function-processor/src/test/resources/Simple.java create mode 100644 function-processor/src/test/resources/SimpleGenerated.java diff --git a/bom/pom.xml b/bom/pom.xml index 8b2b0af7e..488885c00 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -14,6 +14,11 @@ + + com.dylibso.chicory + function-annotations + ${project.version} + com.dylibso.chicory log diff --git a/function-annotations/pom.xml b/function-annotations/pom.xml new file mode 100644 index 000000000..aab836388 --- /dev/null +++ b/function-annotations/pom.xml @@ -0,0 +1,16 @@ + + + 4.0.0 + + + com.dylibso.chicory + chicory + 999-SNAPSHOT + ../pom.xml + + function-annotations + jar + Chicory - Function annotations + Experimental annotations for host functions + + diff --git a/function-annotations/src/main/java/com/dylibso/chicory/function/annotations/HostModule.java b/function-annotations/src/main/java/com/dylibso/chicory/function/annotations/HostModule.java new file mode 100644 index 000000000..fc9045448 --- /dev/null +++ b/function-annotations/src/main/java/com/dylibso/chicory/function/annotations/HostModule.java @@ -0,0 +1,12 @@ +package com.dylibso.chicory.function.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface HostModule { + String value(); +} diff --git a/function-annotations/src/main/java/com/dylibso/chicory/function/annotations/WasmExport.java b/function-annotations/src/main/java/com/dylibso/chicory/function/annotations/WasmExport.java new file mode 100644 index 000000000..ec488202a --- /dev/null +++ b/function-annotations/src/main/java/com/dylibso/chicory/function/annotations/WasmExport.java @@ -0,0 +1,12 @@ +package com.dylibso.chicory.function.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface WasmExport { + String value() default ""; +} diff --git a/function-processor/pom.xml b/function-processor/pom.xml new file mode 100644 index 000000000..6276a0f52 --- /dev/null +++ b/function-processor/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + + com.dylibso.chicory + chicory + 999-SNAPSHOT + ../pom.xml + + function-processor + jar + Chicory - Function annotation processor + Experimental annotation processor for host functions + + + + com.dylibso.chicory + function-annotations + + + com.github.javaparser + javaparser-core + ${javaparser.version} + + + com.dylibso.chicory + runtime + test + + + com.dylibso.chicory + wasm + test + + + com.google.testing.compile + compile-testing + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + com.dylibso.chicory:runtime + com.dylibso.chicory:wasm + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + + + + + diff --git a/function-processor/src/main/java/com/dylibso/chicory/function/processor/FunctionProcessor.java b/function-processor/src/main/java/com/dylibso/chicory/function/processor/FunctionProcessor.java new file mode 100644 index 000000000..292ee9a65 --- /dev/null +++ b/function-processor/src/main/java/com/dylibso/chicory/function/processor/FunctionProcessor.java @@ -0,0 +1,280 @@ +package com.dylibso.chicory.function.processor; + +import static com.github.javaparser.StaticJavaParser.parseType; +import static com.github.javaparser.printer.configuration.DefaultPrinterConfiguration.ConfigOption.COLUMN_ALIGN_PARAMETERS; +import static java.lang.String.format; +import static javax.tools.Diagnostic.Kind.ERROR; +import static javax.tools.Diagnostic.Kind.NOTE; + +import com.dylibso.chicory.function.annotations.HostModule; +import com.dylibso.chicory.function.annotations.WasmExport; +import com.github.javaparser.ast.ArrayCreationLevel; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.expr.ArrayAccessExpr; +import com.github.javaparser.ast.expr.ArrayCreationExpr; +import com.github.javaparser.ast.expr.ArrayInitializerExpr; +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.FieldAccessExpr; +import com.github.javaparser.ast.expr.IntegerLiteralExpr; +import com.github.javaparser.ast.expr.LambdaExpr; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.expr.NameExpr; +import com.github.javaparser.ast.expr.NullLiteralExpr; +import com.github.javaparser.ast.expr.ObjectCreationExpr; +import com.github.javaparser.ast.expr.StringLiteralExpr; +import com.github.javaparser.ast.expr.VariableDeclarationExpr; +import com.github.javaparser.ast.stmt.BlockStmt; +import com.github.javaparser.ast.stmt.ExpressionStmt; +import com.github.javaparser.ast.stmt.ReturnStmt; +import com.github.javaparser.printer.DefaultPrettyPrinter; +import com.github.javaparser.printer.configuration.DefaultConfigurationOption; +import com.github.javaparser.printer.configuration.DefaultPrinterConfiguration; +import java.io.IOException; +import java.io.Writer; +import java.lang.annotation.Annotation; +import java.util.Locale; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Filer; +import javax.annotation.processing.Generated; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.tools.Diagnostic; + +public final class FunctionProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public Set getSupportedAnnotationTypes() { + return Set.of(HostModule.class.getName()); + } + + @Override + public boolean process(Set annotations, RoundEnvironment round) { + for (Element element : round.getElementsAnnotatedWith(HostModule.class)) { + log(NOTE, "Generating module factory for " + element, null); + try { + processModule((TypeElement) element); + } catch (AbortProcessingException e) { + // skip type + } + } + + return false; + } + + private void processModule(TypeElement type) { + var moduleName = type.getAnnotation(HostModule.class).value(); + + var functions = new NodeList(); + for (Element member : elements().getAllMembers(type)) { + if (member instanceof ExecutableElement && annotatedWith(member, WasmExport.class)) { + functions.add(processMethod(member, (ExecutableElement) member, moduleName)); + } + } + + var cu = new CompilationUnit(type.getEnclosingElement().toString()); + cu.addImport("com.dylibso.chicory.runtime.HostFunction"); + cu.addImport("com.dylibso.chicory.runtime.Instance"); + cu.addImport("com.dylibso.chicory.wasm.types.Value"); + cu.addImport("com.dylibso.chicory.wasm.types.ValueType"); + cu.addImport("java.util.List"); + + var typeName = type.getSimpleName().toString(); + var classDef = cu.addClass(typeName + "_ModuleFactory").setPublic(true).setFinal(true); + + classDef.addConstructor().setPrivate(true); + + classDef.addSingleMemberAnnotation( + Generated.class, new StringLiteralExpr(FunctionProcessor.class.getName())); + + var newHostFunctions = + new ArrayCreationExpr( + parseType("HostFunction"), + new NodeList<>(new ArrayCreationLevel()), + new ArrayInitializerExpr(functions)); + + classDef.addMethod("toHostFunctions") + .setPublic(true) + .setStatic(true) + .addParameter(typeName, "functions") + .setType("HostFunction[]") + .setBody(new BlockStmt(new NodeList<>(new ReturnStmt(newHostFunctions)))); + + String qualifiedName = type.getQualifiedName() + "_ModuleFactory"; + try (Writer writer = filer().createSourceFile(qualifiedName, type).openWriter()) { + writer.write(cu.printer(printer()).toString()); + } catch (IOException e) { + log(ERROR, format("Failed to create %s file: %s", qualifiedName, e), null); + } + } + + private Expression processMethod( + Element member, ExecutableElement executable, String moduleName) { + // compute function name + var name = executable.getAnnotation(WasmExport.class).value(); + if (name.isEmpty()) { + name = camelCaseToSnakeCase(executable.getSimpleName().toString()); + } + + // compute parameter types and argument conversions + NodeList paramTypes = new NodeList<>(); + NodeList arguments = new NodeList<>(); + for (VariableElement parameter : executable.getParameters()) { + var argExpr = + new ArrayAccessExpr( + new NameExpr("args"), + new IntegerLiteralExpr(String.valueOf(paramTypes.size()))); + switch (parameter.asType().toString()) { + case "int": + paramTypes.add(valueType("I32")); + arguments.add(new MethodCallExpr(argExpr, "asInt")); + break; + case "long": + paramTypes.add(valueType("I64")); + arguments.add(new MethodCallExpr(argExpr, "asLong")); + break; + case "float": + paramTypes.add(valueType("F32")); + arguments.add(new MethodCallExpr(argExpr, "asFloat")); + break; + case "double": + paramTypes.add(valueType("F64")); + arguments.add(new MethodCallExpr(argExpr, "asDouble")); + break; + case "com.dylibso.chicory.runtime.Instance": + arguments.add(new NameExpr("instance")); + break; + default: + log(ERROR, "Unsupported WASM type: " + parameter.asType(), parameter); + throw new AbortProcessingException(); + } + } + + // compute return type and conversion + String returnName = executable.getReturnType().toString(); + NodeList returnType = new NodeList<>(); + String returnExpr = null; + switch (returnName) { + case "void": + break; + case "int": + returnType.add(valueType("I32")); + returnExpr = "i32"; + break; + case "long": + returnType.add(valueType("I64")); + returnExpr = "i64"; + break; + case "float": + returnType.add(valueType("F32")); + returnExpr = "fromFloat"; + break; + case "double": + returnType.add(valueType("F64")); + returnExpr = "fromDouble"; + break; + default: + log(ERROR, "Unsupported WASM type: " + returnName, executable); + throw new AbortProcessingException(); + } + + // function invocation + Expression invocation = + new MethodCallExpr( + new NameExpr("functions"), member.getSimpleName().toString(), arguments); + + // convert return value + BlockStmt handleBody = new BlockStmt(); + if (returnType.isEmpty()) { + handleBody.addStatement(invocation).addStatement(new ReturnStmt(new NullLiteralExpr())); + } else { + var resultType = parseType(returnName); + var result = new VariableDeclarator(resultType, "result", invocation); + var boxed = + new MethodCallExpr( + new NameExpr("Value"), + returnExpr, + new NodeList<>(new NameExpr("result"))); + var wrapped = + new ArrayCreationExpr( + parseType("Value"), + new NodeList<>(new ArrayCreationLevel()), + new ArrayInitializerExpr(new NodeList<>(boxed))); + handleBody + .addStatement(new ExpressionStmt(new VariableDeclarationExpr(result))) + .addStatement(new ReturnStmt(wrapped)); + } + + // lambda for host function + var handle = + new LambdaExpr() + .addParameter("Instance", "instance") + .addParameter(new Parameter(parseType("Value"), "args").setVarArgs(true)) + .setEnclosingParameters(true) + .setBody(handleBody); + + // create host function + var function = + new ObjectCreationExpr() + .setType("HostFunction") + .addArgument(handle) + .addArgument(new StringLiteralExpr(moduleName)) + .addArgument(new StringLiteralExpr(name)) + .addArgument(new MethodCallExpr(new NameExpr("List"), "of", paramTypes)) + .addArgument(new MethodCallExpr(new NameExpr("List"), "of", returnType)); + // TODO: update javaparser and replace with multiline formatting + function.setLineComment(""); + return function; + } + + private Elements elements() { + return processingEnv.getElementUtils(); + } + + private Filer filer() { + return processingEnv.getFiler(); + } + + private void log(Diagnostic.Kind kind, String message, Element element) { + processingEnv.getMessager().printMessage(kind, message, element); + } + + private static boolean annotatedWith(Element element, Class annotation) { + var annotationName = annotation.getName(); + return element.getAnnotationMirrors().stream() + .map(AnnotationMirror::getAnnotationType) + .map(TypeMirror::toString) + .anyMatch(annotationName::equals); + } + + private static Expression valueType(String type) { + return new FieldAccessExpr(new NameExpr("ValueType"), type); + } + + private static String camelCaseToSnakeCase(String name) { + return name.replaceAll("([a-z])([A-Z]+)", "$1_$2").toLowerCase(Locale.ROOT); + } + + private static DefaultPrettyPrinter printer() { + return new DefaultPrettyPrinter( + new DefaultPrinterConfiguration() + .addOption(new DefaultConfigurationOption(COLUMN_ALIGN_PARAMETERS, true))); + } + + private static final class AbortProcessingException extends RuntimeException {} +} diff --git a/function-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/function-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 000000000..21917dfa0 --- /dev/null +++ b/function-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +com.dylibso.chicory.function.processor.FunctionProcessor diff --git a/function-processor/src/test/java/com/dylibso/chicory/function/processor/FunctionProcessorTest.java b/function-processor/src/test/java/com/dylibso/chicory/function/processor/FunctionProcessorTest.java new file mode 100644 index 000000000..3cd7614b4 --- /dev/null +++ b/function-processor/src/test/java/com/dylibso/chicory/function/processor/FunctionProcessorTest.java @@ -0,0 +1,58 @@ +package com.dylibso.chicory.function.processor; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static com.google.testing.compile.Compiler.javac; + +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import org.junit.jupiter.api.Test; + +class FunctionProcessorTest { + + @Test + void generateModules() { + Compilation compilation = + javac().withProcessors(new FunctionProcessor()) + .compile( + JavaFileObjects.forResource("BasicMath.java"), + JavaFileObjects.forResource("Simple.java")); + + assertThat(compilation).succeededWithoutWarnings(); + + assertThat(compilation) + .generatedSourceFile("chicory.testing.BasicMath_ModuleFactory") + .hasSourceEquivalentTo(JavaFileObjects.forResource("BasicMathGenerated.java")); + + assertThat(compilation) + .generatedSourceFile("chicory.testing.Simple_ModuleFactory") + .hasSourceEquivalentTo(JavaFileObjects.forResource("SimpleGenerated.java")); + } + + @Test + void invalidParameterType() { + Compilation compilation = + javac().withProcessors(new FunctionProcessor()) + .compile(JavaFileObjects.forResource("InvalidParameter.java")); + + assertThat(compilation).failed(); + + assertThat(compilation) + .hadErrorContaining("Unsupported WASM type: java.lang.String") + .inFile(JavaFileObjects.forResource("InvalidParameter.java")) + .onLineContaining("public long concat(int a, String s) {"); + } + + @Test + void invalidReturnType() { + Compilation compilation = + javac().withProcessors(new FunctionProcessor()) + .compile(JavaFileObjects.forResource("InvalidReturn.java")); + + assertThat(compilation).failed(); + + assertThat(compilation) + .hadErrorContaining("Unsupported WASM type: java.lang.String") + .inFile(JavaFileObjects.forResource("InvalidReturn.java")) + .onLineContaining("public String toString(int x) {"); + } +} diff --git a/function-processor/src/test/resources/BasicMath.java b/function-processor/src/test/resources/BasicMath.java new file mode 100644 index 000000000..71cd43682 --- /dev/null +++ b/function-processor/src/test/resources/BasicMath.java @@ -0,0 +1,28 @@ +package chicory.testing; + +import com.dylibso.chicory.function.annotations.HostModule; +import com.dylibso.chicory.function.annotations.WasmExport; +import com.dylibso.chicory.runtime.HostFunction; + +@HostModule("math") +public final class BasicMath { + + @WasmExport + public long add(int a, int b) { + return a + b; + } + + @WasmExport("square") + public double pow2(float x) { + return x * x; + } + + @WasmExport + public int floorDiv(int x, int y) { + return Math.floorDiv(x, y); + } + + public HostFunction[] toHostFunctions() { + return BasicMath_ModuleFactory.toHostFunctions(this); + } +} diff --git a/function-processor/src/test/resources/BasicMathGenerated.java b/function-processor/src/test/resources/BasicMathGenerated.java new file mode 100644 index 000000000..86a8cb775 --- /dev/null +++ b/function-processor/src/test/resources/BasicMathGenerated.java @@ -0,0 +1,49 @@ +package chicory.testing; + +import com.dylibso.chicory.runtime.HostFunction; +import com.dylibso.chicory.runtime.Instance; +import com.dylibso.chicory.wasm.types.Value; +import com.dylibso.chicory.wasm.types.ValueType; +import java.util.List; +import javax.annotation.processing.Generated; + +@Generated("com.dylibso.chicory.function.processor.FunctionProcessor") +public final class BasicMath_ModuleFactory { + + private BasicMath_ModuleFactory() {} + + public static HostFunction[] toHostFunctions(BasicMath functions) { + return new HostFunction[] { + new HostFunction( + (Instance instance, Value... args) -> { + long result = functions.add(args[0].asInt(), args[1].asInt()); + return new Value[] { Value.i64(result) }; + }, + "math", + "add", + List.of(ValueType.I32, ValueType.I32), + List.of(ValueType.I64) + ), + new HostFunction( + (Instance instance, Value... args) -> { + double result = functions.pow2(args[0].asFloat()); + return new Value[] { Value.fromDouble(result) }; + }, + "math", + "square", + List.of(ValueType.F32), + List.of(ValueType.F64) + ), + new HostFunction( + (Instance instance, Value... args) -> { + int result = functions.floorDiv(args[0].asInt(), args[1].asInt()) + return new Value[] { Value.i32(result) }; + }, + "math", + "floor_div", + List.of(ValueType.I32, ValueType.I32), + List.of(ValueType.I32) + ), + }; + } +} diff --git a/function-processor/src/test/resources/InvalidParameter.java b/function-processor/src/test/resources/InvalidParameter.java new file mode 100644 index 000000000..0b2ad0508 --- /dev/null +++ b/function-processor/src/test/resources/InvalidParameter.java @@ -0,0 +1,13 @@ +package chicory.testing; + +import com.dylibso.chicory.function.annotations.HostModule; +import com.dylibso.chicory.function.annotations.WasmExport; + +@HostModule("bad_param") +public final class InvalidParameter { + + @WasmExport + public long concat(int a, String s) { + return (s + a).length(); + } +} diff --git a/function-processor/src/test/resources/InvalidReturn.java b/function-processor/src/test/resources/InvalidReturn.java new file mode 100644 index 000000000..539c5b936 --- /dev/null +++ b/function-processor/src/test/resources/InvalidReturn.java @@ -0,0 +1,13 @@ +package chicory.testing; + +import com.dylibso.chicory.function.annotations.HostModule; +import com.dylibso.chicory.function.annotations.WasmExport; + +@HostModule("bad_return") +public final class InvalidReturn { + + @WasmExport + public String toString(int x) { + return String.valueOf(x); + } +} diff --git a/function-processor/src/test/resources/Simple.java b/function-processor/src/test/resources/Simple.java new file mode 100644 index 000000000..ba19d6073 --- /dev/null +++ b/function-processor/src/test/resources/Simple.java @@ -0,0 +1,25 @@ +package chicory.testing; + +import com.dylibso.chicory.function.annotations.HostModule; +import com.dylibso.chicory.function.annotations.WasmExport; +import com.dylibso.chicory.runtime.HostFunction; +import com.dylibso.chicory.runtime.Instance; +import com.dylibso.chicory.wasm.exceptions.ChicoryException; + +@HostModule("simple") +public final class Simple { + + @WasmExport + public void print(Instance instance, int ptr, int len) { + System.out.println(instance.memory().readString(ptr, len)); + } + + @WasmExport + public void exit() { + throw new ChicoryException("exit"); + } + + public HostFunction[] toHostFunctions() { + return Simple_ModuleFactory.toHostFunctions(this); + } +} diff --git a/function-processor/src/test/resources/SimpleGenerated.java b/function-processor/src/test/resources/SimpleGenerated.java new file mode 100644 index 000000000..b2d922e7b --- /dev/null +++ b/function-processor/src/test/resources/SimpleGenerated.java @@ -0,0 +1,39 @@ +package chicory.testing; + +import com.dylibso.chicory.runtime.HostFunction; +import com.dylibso.chicory.runtime.Instance; +import com.dylibso.chicory.wasm.types.Value; +import com.dylibso.chicory.wasm.types.ValueType; +import java.util.List; +import javax.annotation.processing.Generated; + +@Generated("com.dylibso.chicory.function.processor.FunctionProcessor") +public final class Simple_ModuleFactory { + + private Simple_ModuleFactory() {} + + public static HostFunction[] toHostFunctions(Simple functions) { + return new HostFunction[] { + new HostFunction( + (Instance instance, Value... args) -> { + functions.print(instance, args[0].asInt(), args[1].asInt()); + return null; + }, + "simple", + "print", + List.of(ValueType.I32, ValueType.I32), + List.of() + ), + new HostFunction( + (Instance instance, Value... args) -> { + functions.exit(); + return null; + }, + "simple", + "exit", + List.of(), + List.of() + ), + }; + } +} diff --git a/pom.xml b/pom.xml index f6a8ace69..155fdfcf2 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,8 @@ aot bom cli + function-annotations + function-processor fuzz jmh log @@ -138,6 +140,16 @@ cli ${project.version} + + com.dylibso.chicory + function-annotations + ${project.version} + + + com.dylibso.chicory + function-processor + ${project.version} + com.dylibso.chicory log @@ -169,6 +181,13 @@ ${project.version} + + + com.google.guava + guava + 33.3.0-jre + + com.google.jimfs @@ -176,6 +195,13 @@ 1.3.0 + + + com.google.testing.compile + compile-testing + 0.21.0 + + commons-io diff --git a/wasi/pom.xml b/wasi/pom.xml index 3668f126f..32ed827b7 100644 --- a/wasi/pom.xml +++ b/wasi/pom.xml @@ -14,6 +14,10 @@ WASI Preview 1 impolementation for Chicory + + com.dylibso.chicory + function-annotations + com.dylibso.chicory log @@ -26,6 +30,12 @@ com.dylibso.chicory wasm + + + com.dylibso.chicory + function-processor + provided + com.dylibso.chicory wasm-corpus @@ -112,6 +122,30 @@ + + + org.apache.maven.plugins + maven-compiler-plugin + + + + com.dylibso.chicory + function-processor + ${project.version} + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + com.dylibso.chicory:function-processor + + + diff --git a/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java b/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java index bb50d86ce..1f1e88775 100644 --- a/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java +++ b/wasi/src/main/java/com/dylibso/chicory/wasi/WasiPreview1.java @@ -5,8 +5,6 @@ import static com.dylibso.chicory.wasi.Descriptors.Descriptor; import static com.dylibso.chicory.wasi.Descriptors.OpenDirectory; import static com.dylibso.chicory.wasi.Descriptors.OpenFile; -import static com.dylibso.chicory.wasm.types.ValueType.I32; -import static com.dylibso.chicory.wasm.types.ValueType.I64; import static java.lang.Math.min; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; @@ -14,6 +12,8 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.stream.Collectors.toList; +import com.dylibso.chicory.function.annotations.HostModule; +import com.dylibso.chicory.function.annotations.WasmExport; import com.dylibso.chicory.log.Logger; import com.dylibso.chicory.log.SystemLogger; import com.dylibso.chicory.runtime.HostFunction; @@ -24,7 +24,6 @@ import com.dylibso.chicory.wasi.Descriptors.InStream; import com.dylibso.chicory.wasi.Descriptors.OutStream; import com.dylibso.chicory.wasi.Descriptors.PreopenedDirectory; -import com.dylibso.chicory.wasm.types.Value; import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; @@ -55,6 +54,7 @@ import java.util.Set; import java.util.stream.Stream; +@HostModule("wasi_snapshot_preview1") public final class WasiPreview1 implements Closeable { private final Logger logger; private final List arguments; @@ -127,1490 +127,1050 @@ public void close() { descriptors.closeAll(); } - public HostFunction adapterCloseBadfd() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("adapter_close_badfd: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: adapter_close_badfd"); - // return new Value[] { Value.i32(0) }; - }, - "wasi_snapshot_preview1", - "adapter_close_badfd", - List.of(I32), - List.of(I32)); - } - - public HostFunction adapterOpenBadfd() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("adapter_open_badfd: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: adapter_open_badfd"); - // return new Value[] { Value.i32(0) }; - }, - "wasi_snapshot_preview1", - "adapter_open_badfd", - List.of(I32), - List.of(I32)); - } - - public HostFunction argsGet() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("args_get: " + Arrays.toString(args)); - int argv = args[0].asInt(); - int argvBuf = args[1].asInt(); - - Memory memory = instance.memory(); - for (byte[] argument : arguments) { - memory.writeI32(argv, argvBuf); - argv += 4; - memory.write(argvBuf, argument); - argvBuf += argument.length; - memory.writeByte(argvBuf, (byte) 0); - argvBuf++; - } - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "args_get", - List.of(I32, I32), - List.of(I32)); - } - - public HostFunction argsSizesGet() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("args_sizes_get: " + Arrays.toString(args)); - int argc = args[0].asInt(); - int argvBufSize = args[1].asInt(); - - int bufSize = arguments.stream().mapToInt(x -> x.length + 1).sum(); - Memory memory = instance.memory(); - memory.writeI32(argc, arguments.size()); - memory.writeI32(argvBufSize, bufSize); - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "args_sizes_get", - List.of(I32, I32), - List.of(I32)); - } - - public HostFunction clockResGet() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("clock_res_get: " + Arrays.toString(args)); - int clockId = args[0].asInt(); - int resultPtr = args[1].asInt(); - - Memory memory = instance.memory(); - switch (clockId) { - case WasiClockId.REALTIME: - case WasiClockId.MONOTONIC: - memory.writeLong(resultPtr, 1L); - return wasiResult(WasiErrno.ESUCCESS); - case WasiClockId.PROCESS_CPUTIME_ID: - throw new WASMRuntimeException( - "We don't yet support clockid process_cputime_id"); - case WasiClockId.THREAD_CPUTIME_ID: - throw new WASMRuntimeException( - "We don't yet support clockid thread_cputime_id"); - default: - return wasiResult(WasiErrno.EINVAL); - } - }, - "wasi_snapshot_preview1", - "clock_res_get", - List.of(I32, I32), - List.of(I32)); - } - - public HostFunction clockTimeGet() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("clock_time_get: " + Arrays.toString(args)); - int clockId = args[0].asInt(); - // long precision = args[1].asLong(); - int resultPtr = args[2].asInt(); - - Memory memory = instance.memory(); - switch (clockId) { - case WasiClockId.REALTIME: - Instant now = Instant.now(); - long epochNanos = SECONDS.toNanos(now.getEpochSecond()) + now.getNano(); - memory.writeLong(resultPtr, epochNanos); - return wasiResult(WasiErrno.ESUCCESS); - case WasiClockId.MONOTONIC: - memory.writeLong(resultPtr, System.nanoTime()); - return wasiResult(WasiErrno.ESUCCESS); - case WasiClockId.PROCESS_CPUTIME_ID: - throw new WASMRuntimeException( - "We don't yet support clockid process_cputime_id"); - case WasiClockId.THREAD_CPUTIME_ID: - throw new WASMRuntimeException( - "We don't yet support clockid thread_cputime_id"); - default: - return wasiResult(WasiErrno.EINVAL); - } - }, - "wasi_snapshot_preview1", - "clock_time_get", - List.of(I32, I64, I32), - List.of(I32)); - } - - public HostFunction environGet() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("environ_get: " + Arrays.toString(args)); - int environ = args[0].asInt(); - int environBuf = args[1].asInt(); - - Memory memory = instance.memory(); - for (var entry : environment) { - byte[] name = entry.getKey(); - byte[] value = entry.getValue(); - byte[] data = new byte[name.length + value.length + 2]; - System.arraycopy(name, 0, data, 0, name.length); - data[name.length] = '='; - System.arraycopy(value, 0, data, name.length + 1, value.length); - data[data.length - 1] = '\0'; - - memory.writeI32(environ, environBuf); - environ += 4; - memory.write(environBuf, data); - environBuf += data.length; - } - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "environ_get", - List.of(I32, I32), - List.of(I32)); - } - - public HostFunction environSizesGet() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("environ_sizes_get: " + Arrays.toString(args)); - int environCount = args[0].asInt(); - int environBufSize = args[1].asInt(); - - int bufSize = - environment.stream() - .mapToInt(x -> x.getKey().length + x.getValue().length + 2) - .sum(); - Memory memory = instance.memory(); - memory.writeI32(environCount, environment.size()); - memory.writeI32(environBufSize, bufSize); - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "environ_sizes_get", - List.of(I32, I32), - List.of(I32)); - } - - public HostFunction fdAdvise() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_advise: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: fd_advise"); - // return new Value[] { Value.i32(0) }; - }, - "wasi_snapshot_preview1", - "fd_advise", - List.of(I32, I64, I64, I32), - List.of(I32)); - } - - public HostFunction fdAllocate() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_allocate: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: fd_allocate"); - // return new Value[] { Value.i32(0) }; - }, - "wasi_snapshot_preview1", - "fd_allocate", - List.of(I32, I64, I64), - List.of(I32)); - } - - public HostFunction fdClose() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_close: " + Arrays.toString(args)); - int fd = args[0].asInt(); - - Descriptor descriptor = descriptors.get(fd); - if (descriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - - descriptors.free(fd); - - try { - if (descriptor instanceof Closeable) { - ((Closeable) descriptor).close(); - } - } catch (IOException e) { - return wasiResult(WasiErrno.EIO); - } - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "fd_close", - List.of(I32), - List.of(I32)); - } - - public HostFunction fdDatasync() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_datasync: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: fd_datasync"); - // return new Value[] { Value.i32(0) }; - }, - "wasi_snapshot_preview1", - "fd_datasync", - List.of(I32), - List.of(I32)); - } - - public HostFunction fdFdstatGet() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_fdstat_get: " + Arrays.toString(args)); - int fd = args[0].asInt(); - int buf = args[1].asInt(); - int flags = 0; - int rightsBase; - int rightsInheriting = 0; - - var descriptor = descriptors.get(fd); - if (descriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - - WasiFileType fileType; - if (descriptor instanceof InStream) { - fileType = WasiFileType.CHARACTER_DEVICE; - rightsBase = WasiRights.FD_READ; - } else if (descriptor instanceof OutStream) { - fileType = WasiFileType.CHARACTER_DEVICE; - rightsBase = WasiRights.FD_WRITE; - } else if (descriptor instanceof Directory) { - fileType = WasiFileType.DIRECTORY; - rightsBase = WasiRights.DIRECTORY_RIGHTS_BASE; - rightsInheriting = rightsBase | WasiRights.FILE_RIGHTS_BASE; - } else if (descriptor instanceof OpenFile) { - fileType = WasiFileType.REGULAR_FILE; - rightsBase = WasiRights.FILE_RIGHTS_BASE; - flags = ((OpenFile) descriptor).fdFlags(); - } else { - throw unhandledDescriptor(descriptor); - } - - Memory memory = instance.memory(); - memory.write(buf, new byte[8]); - memory.writeByte(buf, (byte) fileType.value()); - memory.writeShort(buf + 2, (short) flags); - memory.writeLong(buf + 8, rightsBase); - memory.writeLong(buf + 16, rightsInheriting); - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "fd_fdstat_get", - List.of(I32, I32), - List.of(I32)); - } - - public HostFunction fdFdstatSetFlags() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_fdstat_set_flags: " + Arrays.toString(args)); - int fd = args[0].asInt(); - int flags = args[1].asInt(); - - var descriptor = descriptors.get(fd); - if (descriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - - if ((descriptor instanceof InStream) || (descriptor instanceof OutStream)) { - return wasiResult(WasiErrno.EINVAL); - } - if ((descriptor instanceof OpenDirectory) - || (descriptor instanceof PreopenedDirectory)) { - return wasiResult(WasiErrno.ESUCCESS); - } - if (!(descriptor instanceof OpenFile)) { - throw unhandledDescriptor(descriptor); - } - - // we don't support changing flags - if (flags != ((OpenFile) descriptor).fdFlags()) { - return wasiResult(WasiErrno.ENOTSUP); - } - - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "fd_fdstat_set_flags", - List.of(I32, I32), - List.of(I32)); - } - - public HostFunction fdFdstatSetRights() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_fdstat_set_rights: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: fd_fdstat_set_rightsn"); - // return new Value[] { Value.i32(0) }; - }, - "wasi_snapshot_preview1", - "fd_fdstat_set_rights", - List.of(I32, I64, I32), - List.of(I32)); - } - - public HostFunction fdFilestatGet() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_filestat_get: " + Arrays.toString(args)); - int fd = args[0].asInt(); - int buf = args[1].asInt(); - - var descriptor = descriptors.get(fd); - if (descriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - - if ((descriptor instanceof InStream) || (descriptor instanceof OutStream)) { - Map attributes = - Map.of( - "dev", 0L, - "ino", 0L, - "nlink", 1L, - "size", 0L, - "lastAccessTime", FileTime.from(Instant.EPOCH), - "lastModifiedTime", FileTime.from(Instant.EPOCH), - "ctime", FileTime.from(Instant.EPOCH)); - writeFileStat( - instance.memory(), buf, attributes, WasiFileType.CHARACTER_DEVICE); - return wasiResult(WasiErrno.ESUCCESS); - } - - Path path; - if (descriptor instanceof OpenFile) { - path = ((OpenFile) descriptor).path(); - } else if (descriptor instanceof OpenDirectory) { - path = ((OpenDirectory) descriptor).path(); - } else { - throw unhandledDescriptor(descriptor); - } - - Map attributes; - try { - attributes = Files.readAttributes(path, "unix:*"); - } catch (UnsupportedOperationException e) { - return wasiResult(WasiErrno.ENOTSUP); - } catch (IOException e) { - return wasiResult(WasiErrno.EIO); - } - - writeFileStat(instance.memory(), buf, attributes, getFileType(attributes)); - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "fd_filestat_get", - List.of(I32, I32), - List.of(I32)); - } - - public HostFunction fdFilestatSetSize() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_filestat_set_size: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: fd_filestat_set_size"); - // return new Value[] { Value.i32(0) }; - }, - "wasi_snapshot_preview1", - "fd_filestat_set_size", - List.of(I32, I64), - List.of(I32)); - } - - public HostFunction fdFilestatSetTimes() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_filestat_set_times: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: fd_filestat_set_times"); - // return new Value[] { Value.i32(0) }; - }, - "wasi_snapshot_preview1", - "fd_filestat_set_times", - List.of(I32, I64, I64, I32), - List.of(I32)); - } - - public HostFunction fdPread() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_pread: " + Arrays.toString(args)); - throw new WASMRuntimeException("We don't yet support this WASI call: fd_pread"); - // return new Value[] { Value.i32(0) }; - }, - "wasi_snapshot_preview1", - "fd_pread", - List.of(I32, I32, I32, I64, I32), - List.of(I32)); - } - - public HostFunction fdPrestatDirName() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_prestat_dir_name: " + Arrays.toString(args)); - int fd = args[0].asInt(); - int path = args[1].asInt(); - int pathLen = args[2].asInt(); - - var descriptor = descriptors.get(fd); - if (descriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - - if (!(descriptor instanceof PreopenedDirectory)) { - return wasiResult(WasiErrno.EBADF); - } - byte[] name = ((PreopenedDirectory) descriptor).name(); - - if (pathLen < name.length) { - return wasiResult(WasiErrno.ENAMETOOLONG); - } - - instance.memory().write(path, name); - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "fd_prestat_dir_name", - List.of(I32, I32, I32), - List.of(I32)); - } - - public HostFunction fdPrestatGet() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_prestat_get: " + Arrays.toString(args)); - int fd = args[0].asInt(); - int buf = args[1].asInt(); - - var descriptor = descriptors.get(fd); - if (descriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - - if (!(descriptor instanceof PreopenedDirectory)) { - return wasiResult(WasiErrno.EBADF); - } - int length = ((PreopenedDirectory) descriptor).name().length; - - Memory memory = instance.memory(); - memory.writeI32(buf, 0); // preopentype::dir - memory.writeI32(buf + 4, length); - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "fd_prestat_get", - List.of(I32, I32), - List.of(I32)); - } - - public HostFunction fdPwrite() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_pwrite: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: fd_pwrite"); - // return new Value[] { Value.i32(0) }; - }, - "wasi_snapshot_preview1", - "fd_pwrite", - List.of(I32, I32, I32, I64, I32), - List.of(I32)); - } - - public HostFunction fdRead() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_read: " + Arrays.toString(args)); - var fd = args[0].asInt(); - var iovs = args[1].asInt(); - var iovsLen = args[2].asInt(); - var nreadPtr = args[3].asInt(); - - var descriptor = descriptors.get(fd); - if (descriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - - if (descriptor instanceof OutStream) { - return wasiResult(WasiErrno.EBADF); - } - if (descriptor instanceof Directory) { - return wasiResult(WasiErrno.EISDIR); - } - if (!(descriptor instanceof DataReader)) { - throw unhandledDescriptor(descriptor); - } - DataReader reader = (DataReader) descriptor; - - int totalRead = 0; - Memory memory = instance.memory(); - for (var i = 0; i < iovsLen; i++) { - int base = iovs + (i * 8); - int iovBase = memory.readI32(base).asInt(); - var iovLen = memory.readI32(base + 4).asInt(); - try { - byte[] data = new byte[iovLen]; - int read = reader.read(data); - if (read < 0) { - break; - } - memory.write(iovBase, data, 0, read); - totalRead += read; - if (read < iovLen) { - break; - } - } catch (IOException e) { - return wasiResult(WasiErrno.EIO); - } - } - - memory.writeI32(nreadPtr, totalRead); - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "fd_read", - List.of(I32, I32, I32, I32), - List.of(I32)); - } - - public HostFunction fdReaddir() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_readdir: " + Arrays.toString(args)); - int dirFd = args[0].asInt(); - int buf = args[1].asInt(); - int bufLen = args[2].asInt(); - long cookie = args[3].asLong(); - int bufUsedPtr = args[4].asInt(); - - if (cookie < 0) { - return wasiResult(WasiErrno.EINVAL); - } - - var descriptor = descriptors.get(dirFd); - if (descriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - - Path directoryPath; - if (descriptor instanceof Directory) { - directoryPath = ((Directory) descriptor).path(); - } else { - return wasiResult(WasiErrno.ENOTDIR); - } - - Memory memory = instance.memory(); - int used = 0; - try (Stream stream = Files.list(directoryPath)) { - Stream special = - Stream.of(directoryPath.resolve("."), directoryPath.resolve("..")); - Iterator iterator = - Stream.concat(special, stream).skip(cookie).iterator(); - while (iterator.hasNext()) { - Path entryPath = iterator.next(); - byte[] name = entryPath.getFileName().toString().getBytes(UTF_8); - cookie++; - - Map attributes; - try { - attributes = Files.readAttributes(entryPath, "unix:*"); - } catch (UnsupportedOperationException e) { - return wasiResult(WasiErrno.ENOTSUP); - } catch (NoSuchFileException e) { - continue; - } - - ByteBuffer entry = - ByteBuffer.allocate(24 + name.length) - .order(ByteOrder.LITTLE_ENDIAN); - entry.putLong(0, cookie); - entry.putLong(8, ((Number) attributes.get("ino")).longValue()); - entry.putInt(16, name.length); - entry.put(20, (byte) getFileType(attributes).value()); - entry.position(24); - entry.put(name); - - int writeSize = min(entry.capacity(), bufLen - used); - memory.write(buf + used, entry.array(), 0, writeSize); - used += writeSize; - - if (used == bufLen) { - break; - } - } - } catch (NotDirectoryException e) { - return wasiResult(WasiErrno.ENOTDIR); - } catch (NoSuchFileException e) { - return wasiResult(WasiErrno.ENOENT); - } catch (IOException e) { - return wasiResult(WasiErrno.EIO); - } - - memory.writeI32(bufUsedPtr, used); - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "fd_readdir", - List.of(I32, I32, I32, I64, I32), - List.of(I32)); - } - - public HostFunction fdRenumber() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_renumber: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: fd_renumber"); - }, - "wasi_snapshot_preview1", - "fd_renumber", - List.of(I32, I32), - List.of(I32)); - } - - public HostFunction fdSeek() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_seek: " + Arrays.toString(args)); - int fd = args[0].asInt(); - long offset = args[1].asLong(); - int whence = args[2].asInt(); - int newOffsetPtr = args[3].asInt(); - - if (whence < 0 || whence > 2) { - return wasiResult(WasiErrno.EINVAL); - } - - var descriptor = descriptors.get(fd); - if (descriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - - if ((descriptor instanceof InStream) || (descriptor instanceof OutStream)) { - return wasiResult(WasiErrno.ESPIPE); - } - if (descriptor instanceof Directory) { - return wasiResult(WasiErrno.EISDIR); - } - if (!(descriptor instanceof OpenFile)) { - throw unhandledDescriptor(descriptor); - } - SeekableByteChannel channel = ((OpenFile) descriptor).channel(); - - long newOffset; - try { - switch (whence) { - case WasiWhence.SET: - channel.position(offset); - break; - case WasiWhence.CUR: - channel.position(channel.position() + offset); - break; - case WasiWhence.END: - channel.position(channel.size() + offset); - break; - } - newOffset = channel.position(); - } catch (IllegalArgumentException e) { - return wasiResult(WasiErrno.EINVAL); - } catch (IOException e) { - return wasiResult(WasiErrno.EIO); - } - - instance.memory().writeLong(newOffsetPtr, newOffset); - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "fd_seek", - List.of(I32, I64, I32, I32), - List.of(I32)); - } - - public HostFunction fdSync() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_sync: " + Arrays.toString(args)); - throw new WASMRuntimeException("We don't yet support this WASI call: fd_sync"); - // return new Value[] {Value.i32(0)}; - }, - "wasi_snapshot_preview1", - "fd_sync", - List.of(I32), - List.of(I32)); - } - - public HostFunction fdTell() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_tell: " + Arrays.toString(args)); - int fd = args[0].asInt(); - int offsetPtr = args[1].asInt(); - - var descriptor = descriptors.get(fd); - if (descriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - - if ((descriptor instanceof InStream) || (descriptor instanceof OutStream)) { - return wasiResult(WasiErrno.ESPIPE); - } - if (descriptor instanceof Directory) { - return wasiResult(WasiErrno.EISDIR); - } - if (!(descriptor instanceof OpenFile)) { - throw unhandledDescriptor(descriptor); - } - SeekableByteChannel channel = ((OpenFile) descriptor).channel(); - - long offset; - try { - offset = channel.position(); - } catch (IOException e) { - return wasiResult(WasiErrno.EIO); - } - - instance.memory().writeLong(offsetPtr, offset); - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "fd_tell", - List.of(I32, I32), - List.of(I32)); - } - - public HostFunction fdWrite() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("fd_write: " + Arrays.toString(args)); - var fd = args[0].asInt(); - var iovs = args[1].asInt(); - var iovsLen = args[2].asInt(); - var nwrittenPtr = args[3].asInt(); - - var descriptor = descriptors.get(fd); - if (descriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - - if (descriptor instanceof InStream) { - return wasiResult(WasiErrno.EBADF); - } - if (descriptor instanceof Directory) { - return wasiResult(WasiErrno.EISDIR); - } - if (!(descriptor instanceof DataWriter)) { - throw unhandledDescriptor(descriptor); - } - DataWriter writer = (DataWriter) descriptor; - - var totalWritten = 0; - Memory memory = instance.memory(); - for (var i = 0; i < iovsLen; i++) { - var base = iovs + (i * 8); - var iovBase = memory.readI32(base).asInt(); - var iovLen = memory.readI32(base + 4).asInt(); - var data = memory.readBytes(iovBase, iovLen); - try { - int written = writer.write(data); - totalWritten += written; - if (written < iovLen) { - break; - } - } catch (IOException e) { - return wasiResult(WasiErrno.EIO); - } - } - - memory.writeI32(nwrittenPtr, totalWritten); - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "fd_write", - List.of(I32, I32, I32, I32), - List.of(I32)); - } - - public HostFunction pathCreateDirectory() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("path_create_directory: " + Arrays.toString(args)); - int dirFd = args[0].asInt(); - int pathPtr = args[1].asInt(); - int pathLen = args[2].asInt(); - - var descriptor = descriptors.get(dirFd); - if (descriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - - if (!(descriptor instanceof Directory)) { - return wasiResult(WasiErrno.ENOTDIR); - } - Path directory = ((Directory) descriptor).path(); - - String rawPath = instance.memory().readString(pathPtr, pathLen); - Path path = resolvePath(directory, rawPath); - if (path == null) { - return wasiResult(WasiErrno.EACCES); - } - - try { - Files.createDirectory(path); - } catch (FileAlreadyExistsException e) { - return wasiResult(WasiErrno.EEXIST); - } catch (NoSuchFileException e) { - return wasiResult(WasiErrno.ENOENT); - } catch (IOException e) { - return wasiResult(WasiErrno.EIO); - } - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "path_create_directory", - List.of(I32, I32, I32), - List.of(I32)); - } - - public HostFunction pathFilestatGet() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("path_filestat_get: " + Arrays.toString(args)); - int dirFd = args[0].asInt(); - int lookupFlags = args[1].asInt(); - int pathPtr = args[2].asInt(); - int pathLen = args[3].asInt(); - int buf = args[4].asInt(); - - var descriptor = descriptors.get(dirFd); - if (descriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - - if (!(descriptor instanceof Directory)) { - return wasiResult(WasiErrno.ENOTDIR); - } - Path directory = ((Directory) descriptor).path(); - - Memory memory = instance.memory(); - String rawPath = memory.readString(pathPtr, pathLen); - Path path = resolvePath(directory, rawPath); - if (path == null) { - return wasiResult(WasiErrno.EACCES); - } - - LinkOption[] linkOptions = toLinkOptions(lookupFlags); - - Map attributes; - try { - attributes = Files.readAttributes(path, "unix:*", linkOptions); - } catch (UnsupportedOperationException e) { - return wasiResult(WasiErrno.ENOTSUP); - } catch (NoSuchFileException e) { - return wasiResult(WasiErrno.ENOENT); - } catch (IOException e) { - return wasiResult(WasiErrno.EIO); - } - - writeFileStat(memory, buf, attributes, getFileType(attributes)); - - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "path_filestat_get", - List.of(I32, I32, I32, I32, I32), - List.of(I32)); - } - - public HostFunction pathFilestatSetTimes() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("path_filestat_set_times: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: path_filestat_set_times"); - }, - "wasi_snapshot_preview1", - "path_filestat_set_times", - List.of(I32, I32, I32, I32, I64, I64, I32), - List.of(I32)); - } - - public HostFunction pathLink() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("path_link: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: path_link"); - }, - "wasi_snapshot_preview1", - "path_link", - List.of(I32, I32, I32, I32, I32, I32, I32), - List.of(I32)); - } - - public HostFunction pathOpen() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("path_open: " + Arrays.toString(args)); - int dirFd = args[0].asInt(); - int lookupFlags = args[1].asInt(); - int pathPtr = args[2].asInt(); - int pathLen = args[3].asInt(); - int openFlags = args[4].asInt(); - // long rightsBase = args[5].asLong(); - // long rightsInheriting = args[6].asLong(); - int fdFlags = args[7].asInt(); - int fdPtr = args[8].asInt(); - - var descriptor = descriptors.get(dirFd); - if (descriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - - if (!(descriptor instanceof Directory)) { - return wasiResult(WasiErrno.ENOTDIR); - } - Path directory = ((Directory) descriptor).path(); - - Memory memory = instance.memory(); - String rawPath = memory.readString(pathPtr, pathLen); - Path path = resolvePath(directory, rawPath); - if (path == null) { - return wasiResult(WasiErrno.EACCES); - } - - LinkOption[] linkOptions = toLinkOptions(lookupFlags); - - if (Files.isDirectory(path, linkOptions)) { - int fd = descriptors.allocate(new OpenDirectory(path)); - memory.writeI32(fdPtr, fd); - return wasiResult(WasiErrno.ESUCCESS); - } - - if (flagSet(openFlags, WasiOpenFlags.DIRECTORY) - && Files.exists(path, linkOptions)) { - return wasiResult(WasiErrno.ENOTDIR); - } - - Set openOptions = new HashSet<>(Arrays.asList(linkOptions)); - - boolean append = flagSet(fdFlags, WasiFdFlags.APPEND); - boolean truncate = flagSet(openFlags, WasiOpenFlags.TRUNC); - - if (append && truncate) { - return wasiResult(WasiErrno.ENOTSUP); - } - if (!append) { - openOptions.add(StandardOpenOption.READ); - } - openOptions.add(StandardOpenOption.WRITE); - - if (flagSet(openFlags, WasiOpenFlags.CREAT)) { - if (flagSet(openFlags, WasiOpenFlags.EXCL)) { - openOptions.add(StandardOpenOption.CREATE_NEW); - } else { - openOptions.add(StandardOpenOption.CREATE); - } - } - if (truncate) { - openOptions.add(StandardOpenOption.TRUNCATE_EXISTING); - } - if (append) { - openOptions.add(StandardOpenOption.APPEND); - } - if (flagSet(fdFlags, WasiFdFlags.SYNC)) { - openOptions.add(StandardOpenOption.SYNC); - } - if (flagSet(fdFlags, WasiFdFlags.DSYNC)) { - openOptions.add(StandardOpenOption.DSYNC); - } - // ignore WasiFdFlags.RSYNC and WasiFdFlags.NONBLOCK - - int fd; - try { - SeekableByteChannel channel = Files.newByteChannel(path, openOptions); - fd = descriptors.allocate(new OpenFile(path, channel, fdFlags)); - } catch (FileAlreadyExistsException e) { - return wasiResult(WasiErrno.EEXIST); - } catch (NoSuchFileException e) { - return wasiResult(WasiErrno.ENOENT); - } catch (IOException e) { - return wasiResult(WasiErrno.EIO); - } - - memory.writeI32(fdPtr, fd); - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "path_open", - List.of(I32, I32, I32, I32, I32, I64, I64, I32, I32), - List.of(I32)); - } - - public HostFunction pathReadlink() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("path_readlink: " + Arrays.toString(args)); - // int dirFd = args[0].asInt(); - // int pathPtr = args[1].asInt(); - // int pathLen = args[2].asInt(); - // int buf = args[3].asInt(); - // int bufLen = args[4].asInt(); - // int bufUsed = args[5].asInt(); - - // Memory memory = instance.memory(); - // String rawPath = memory.readString(pathPtr, pathLen); - - return wasiResult(WasiErrno.EINVAL); - }, - "wasi_snapshot_preview1", - "path_readlink", - List.of(I32, I32, I32, I32, I32, I32), - List.of(I32)); - } - - public HostFunction pathRemoveDirectory() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("path_remove_directory: " + Arrays.toString(args)); - int dirFd = args[0].asInt(); - int pathPtr = args[1].asInt(); - int pathLen = args[2].asInt(); - - var descriptor = descriptors.get(dirFd); - if (descriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - - if (!(descriptor instanceof Directory)) { - return wasiResult(WasiErrno.ENOTDIR); - } - Path directory = ((Directory) descriptor).path(); - - String rawPath = instance.memory().readString(pathPtr, pathLen); - Path path = resolvePath(directory, rawPath); - if (path == null) { - return wasiResult(WasiErrno.EACCES); - } - - try { - var attributes = - Files.readAttributes( - path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); - if (!attributes.isDirectory()) { - return wasiResult(WasiErrno.ENOTDIR); - } - Files.delete(path); - } catch (NoSuchFileException e) { - return wasiResult(WasiErrno.ENOENT); - } catch (DirectoryNotEmptyException e) { - return wasiResult(WasiErrno.ENOTEMPTY); - } catch (IOException e) { - return wasiResult(WasiErrno.EIO); - } - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "path_remove_directory", - List.of(I32, I32, I32), - List.of(I32)); - } - - public HostFunction pathRename() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("path_rename: " + Arrays.toString(args)); - int oldFd = args[0].asInt(); - int oldPathPtr = args[1].asInt(); - int oldPathLen = args[2].asInt(); - int newFd = args[3].asInt(); - int newPathPtr = args[4].asInt(); - int newPathLen = args[5].asInt(); - - var oldDescriptor = descriptors.get(oldFd); - if (oldDescriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - if (!(oldDescriptor instanceof Directory)) { - return wasiResult(WasiErrno.ENOTDIR); - } - Path oldDirectory = ((Directory) oldDescriptor).path(); - - var newDescriptor = descriptors.get(newFd); - if (newDescriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - if (!(newDescriptor instanceof Directory)) { - return wasiResult(WasiErrno.ENOTDIR); - } - Path newDirectory = ((Directory) newDescriptor).path(); - - String oldRawPath = instance.memory().readString(oldPathPtr, oldPathLen); - Path oldPath = resolvePath(oldDirectory, oldRawPath); - if (oldPath == null) { - return wasiResult(WasiErrno.EACCES); - } - - String newRawPath = instance.memory().readString(newPathPtr, newPathLen); - Path newPath = resolvePath(newDirectory, newRawPath); - if (newPath == null) { - return wasiResult(WasiErrno.EACCES); - } - - if (Files.isDirectory(oldPath) - && Files.isRegularFile(newPath, LinkOption.NOFOLLOW_LINKS)) { - return wasiResult(WasiErrno.ENOTDIR); - } - if (Files.isRegularFile(oldPath, LinkOption.NOFOLLOW_LINKS) - && Files.isDirectory(newPath)) { - return wasiResult(WasiErrno.EISDIR); - } - - try { - Files.move( - oldPath, - newPath, - StandardCopyOption.REPLACE_EXISTING, - StandardCopyOption.ATOMIC_MOVE, - StandardCopyOption.COPY_ATTRIBUTES); - } catch (UnsupportedOperationException | AtomicMoveNotSupportedException e) { - return wasiResult(WasiErrno.ENOTSUP); - } catch (NoSuchFileException e) { - return wasiResult(WasiErrno.ENOENT); - } catch (DirectoryNotEmptyException e) { - return wasiResult(WasiErrno.ENOTEMPTY); - } catch (IOException e) { - return wasiResult(WasiErrno.EIO); - } - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "path_rename", - List.of(I32, I32, I32, I32, I32, I32), - List.of(I32)); - } - - public HostFunction pathSymlink() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("path_symlink: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: path_symlink"); - }, - "wasi_snapshot_preview1", - "path_symlink", - List.of(I32, I32, I32, I32, I32), - List.of(I32)); - } - - public HostFunction pathUnlinkFile() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("path_unlink_file: " + Arrays.toString(args)); - int dirFd = args[0].asInt(); - int pathPtr = args[1].asInt(); - int pathLen = args[2].asInt(); - - var descriptor = descriptors.get(dirFd); - if (descriptor == null) { - return wasiResult(WasiErrno.EBADF); - } - - if (!(descriptor instanceof Directory)) { - return wasiResult(WasiErrno.ENOTDIR); - } - Path directory = ((Directory) descriptor).path(); - - String rawPath = instance.memory().readString(pathPtr, pathLen); - Path path = resolvePath(directory, rawPath); - if (path == null) { - return wasiResult(WasiErrno.EACCES); - } - - try { - var attributes = - Files.readAttributes( - path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); - if (attributes.isDirectory()) { - return wasiResult(WasiErrno.EISDIR); - } - if (rawPath.endsWith("/")) { - return wasiResult(WasiErrno.ENOTDIR); - } - Files.delete(path); - } catch (NoSuchFileException e) { - return wasiResult(WasiErrno.ENOENT); - } catch (IOException e) { - return wasiResult(WasiErrno.EIO); - } - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "path_unlink_file", - List.of(I32, I32, I32), - List.of(I32)); - } - - public HostFunction pollOneoff() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("poll_oneoff: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: poll_oneoff"); - }, - "wasi_snapshot_preview1", - "poll_oneoff", - List.of(I32, I32, I32, I32), - List.of(I32)); - } - - public HostFunction procExit() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("proc_exit: " + Arrays.toString(args)); - int code = args[0].asInt(); - throw new WasiExitException(code); - }, - "wasi_snapshot_preview1", - "proc_exit", - List.of(I32), - List.of()); - } - - public HostFunction procRaise() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("proc_raise: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: proc_raise"); - }, - "wasi_snapshot_preview1", - "proc_raise", - List.of(I32), - List.of(I32)); - } - - public HostFunction randomGet() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("random_get: " + Arrays.toString(args)); - int buf = args[0].asInt(); - int bufLen = args[1].asInt(); - - if (bufLen < 0) { - return wasiResult(WasiErrno.EINVAL); - } - if (bufLen >= 100_000) { - throw new WASMRuntimeException("random_get: bufLen must be < 100_000"); - } - - byte[] data = new byte[bufLen]; - new SecureRandom().nextBytes(data); - instance.memory().write(buf, data); - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "random_get", - List.of(I32, I32), - List.of(I32)); - } - - public HostFunction resetAdapterState() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("reset_adapter_state: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: reset_adapter_state"); - }, - "wasi_snapshot_preview1", - "reset_adapter_state", - List.of(), - List.of()); - } - - public HostFunction schedYield() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("sched_yield: " + Arrays.toString(args)); - // do nothing here - return wasiResult(WasiErrno.ESUCCESS); - }, - "wasi_snapshot_preview1", - "sched_yield", - List.of(), - List.of(I32)); - } - - public HostFunction setAllocationState() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("set_allocation_state: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: set_allocation_state"); - }, - "wasi_snapshot_preview1", - "set_allocation_state", - List.of(I32), - List.of()); - } - - public HostFunction setStatePtr() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("set_state_ptr: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: set_state_ptr"); - }, - "wasi_snapshot_preview1", - "set_state_ptr", - List.of(I32), - List.of()); - } - - public HostFunction sockAccept() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("sock_accept: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: sock_accept"); - }, - "wasi_snapshot_preview1", - "sock_accept", - List.of(I32, I32, I32), - List.of(I32)); - } - - public HostFunction sockRecv() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("sock_recv: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: sock_recv"); - }, - "wasi_snapshot_preview1", - "sock_recv", - List.of(I32, I32, I32, I32, I32, I32), - List.of(I32)); - } - - public HostFunction sockSend() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("sock_send: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: sock_send"); - }, - "wasi_snapshot_preview1", - "sock_send", - List.of(I32, I32, I32, I32, I32), - List.of(I32)); - } - - public HostFunction sockShutdown() { - return new HostFunction( - (Instance instance, Value... args) -> { - logger.info("sock_shutdown: " + Arrays.toString(args)); - throw new WASMRuntimeException( - "We don't yet support this WASI call: sock_shutdown"); - }, - "wasi_snapshot_preview1", - "sock_shutdown", - List.of(I32, I32), - List.of(I32)); + @WasmExport + public int adapterCloseBadfd(int fd) { + logger.infof("adapter_close_badfd: [%s]", fd); + throw new WASMRuntimeException("We don't yet support this WASI call: adapter_close_badfd"); + } + + @WasmExport + public int adapterOpenBadfd(int fd) { + logger.infof("adapter_open_badfd: [%s]", fd); + throw new WASMRuntimeException("We don't yet support this WASI call: adapter_open_badfd"); + } + + @WasmExport + public int argsGet(Instance instance, int argv, int argvBuf) { + logger.infof("args_get: [%s, %s]", argv, argvBuf); + Memory memory = instance.memory(); + for (byte[] argument : arguments) { + memory.writeI32(argv, argvBuf); + argv += 4; + memory.write(argvBuf, argument); + argvBuf += argument.length; + memory.writeByte(argvBuf, (byte) 0); + argvBuf++; + } + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int argsSizesGet(Instance instance, int argc, int argvBufSize) { + logger.infof("args_sizes_get: [%s, %s]", argc, argvBufSize); + int bufSize = arguments.stream().mapToInt(x -> x.length + 1).sum(); + Memory memory = instance.memory(); + memory.writeI32(argc, arguments.size()); + memory.writeI32(argvBufSize, bufSize); + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int clockResGet(Instance instance, int clockId, int resultPtr) { + logger.infof("clock_res_get: [%s, %s]", clockId, resultPtr); + Memory memory = instance.memory(); + switch (clockId) { + case WasiClockId.REALTIME: + case WasiClockId.MONOTONIC: + memory.writeLong(resultPtr, 1L); + return wasiResult(WasiErrno.ESUCCESS); + case WasiClockId.PROCESS_CPUTIME_ID: + throw new WASMRuntimeException("We don't yet support clockid process_cputime_id"); + case WasiClockId.THREAD_CPUTIME_ID: + throw new WASMRuntimeException("We don't yet support clockid thread_cputime_id"); + default: + return wasiResult(WasiErrno.EINVAL); + } + } + + @WasmExport + public int clockTimeGet(Instance instance, int clockId, long precision, int resultPtr) { + logger.infof("clock_time_get: [%s, %s, %s]", clockId, precision, resultPtr); + Memory memory = instance.memory(); + switch (clockId) { + case WasiClockId.REALTIME: + Instant now = Instant.now(); + long epochNanos = SECONDS.toNanos(now.getEpochSecond()) + now.getNano(); + memory.writeLong(resultPtr, epochNanos); + return wasiResult(WasiErrno.ESUCCESS); + case WasiClockId.MONOTONIC: + memory.writeLong(resultPtr, System.nanoTime()); + return wasiResult(WasiErrno.ESUCCESS); + case WasiClockId.PROCESS_CPUTIME_ID: + throw new WASMRuntimeException("We don't yet support clockid process_cputime_id"); + case WasiClockId.THREAD_CPUTIME_ID: + throw new WASMRuntimeException("We don't yet support clockid thread_cputime_id"); + default: + return wasiResult(WasiErrno.EINVAL); + } + } + + @WasmExport + public int environGet(Instance instance, int environ, int environBuf) { + logger.infof("environ_get: [%s, %s]", environ, environBuf); + Memory memory = instance.memory(); + for (Entry entry : environment) { + byte[] name = entry.getKey(); + byte[] value = entry.getValue(); + byte[] data = new byte[name.length + value.length + 2]; + System.arraycopy(name, 0, data, 0, name.length); + data[name.length] = '='; + System.arraycopy(value, 0, data, name.length + 1, value.length); + data[data.length - 1] = '\0'; + + memory.writeI32(environ, environBuf); + environ += 4; + memory.write(environBuf, data); + environBuf += data.length; + } + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int environSizesGet(Instance instance, int environCount, int environBufSize) { + logger.infof("environ_sizes_get: [%s, %s]", environCount, environBufSize); + int bufSize = + environment.stream() + .mapToInt(x -> x.getKey().length + x.getValue().length + 2) + .sum(); + Memory memory = instance.memory(); + memory.writeI32(environCount, environment.size()); + memory.writeI32(environBufSize, bufSize); + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int fdAdvise(int fd, long offset, long len, int advice) { + logger.infof("fd_advise: [%s, %s, %s, %s]", fd, offset, len, advice); + throw new WASMRuntimeException("We don't yet support this WASI call: fd_advise"); + } + + @WasmExport + public int fdAllocate(int fd, long offset, long len) { + logger.infof("fd_allocate: [%s, %s, %s]", fd, offset, len); + throw new WASMRuntimeException("We don't yet support this WASI call: fd_allocate"); + } + + @WasmExport + public int fdClose(int fd) { + logger.infof("fd_close: [%s]", fd); + Descriptor descriptor = descriptors.get(fd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + descriptors.free(fd); + try { + if (descriptor instanceof Closeable) { + ((Closeable) descriptor).close(); + } + } catch (IOException e) { + return wasiResult(WasiErrno.EIO); + } + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int fdDatasync(int fd) { + logger.infof("fd_datasync: [%s]", fd); + throw new WASMRuntimeException("We don't yet support this WASI call: fd_datasync"); + } + + @WasmExport + public int fdFdstatGet(Instance instance, int fd, int buf) { + logger.infof("fd_fdstat_get: [%s, %s]", fd, buf); + int flags = 0; + int rightsBase; + int rightsInheriting = 0; + + var descriptor = descriptors.get(fd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + WasiFileType fileType; + if (descriptor instanceof InStream) { + fileType = WasiFileType.CHARACTER_DEVICE; + rightsBase = WasiRights.FD_READ; + } else if (descriptor instanceof OutStream) { + fileType = WasiFileType.CHARACTER_DEVICE; + rightsBase = WasiRights.FD_WRITE; + } else if (descriptor instanceof Directory) { + fileType = WasiFileType.DIRECTORY; + rightsBase = WasiRights.DIRECTORY_RIGHTS_BASE; + rightsInheriting = rightsBase | WasiRights.FILE_RIGHTS_BASE; + } else if (descriptor instanceof OpenFile) { + fileType = WasiFileType.REGULAR_FILE; + rightsBase = WasiRights.FILE_RIGHTS_BASE; + flags = ((OpenFile) descriptor).fdFlags(); + } else { + throw unhandledDescriptor(descriptor); + } + + Memory memory = instance.memory(); + memory.write(buf, new byte[8]); + memory.writeByte(buf, (byte) fileType.value()); + memory.writeShort(buf + 2, (short) flags); + memory.writeLong(buf + 8, rightsBase); + memory.writeLong(buf + 16, rightsInheriting); + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int fdFdstatSetFlags(int fd, int flags) { + logger.infof("fd_fdstat_set_flags: [%s, %s]", fd, flags); + + var descriptor = descriptors.get(fd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + if ((descriptor instanceof InStream) || (descriptor instanceof OutStream)) { + return wasiResult(WasiErrno.EINVAL); + } + if ((descriptor instanceof OpenDirectory) || (descriptor instanceof PreopenedDirectory)) { + return wasiResult(WasiErrno.ESUCCESS); + } + if (!(descriptor instanceof OpenFile)) { + throw unhandledDescriptor(descriptor); + } + + // we don't support changing flags + if (flags != ((OpenFile) descriptor).fdFlags()) { + return wasiResult(WasiErrno.ENOTSUP); + } + + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int fdFdstatSetRights(int fd, long rightsBase, long rightsInheriting) { + logger.infof("fd_fdstat_set_rights: [%s, %s, %s]", fd, rightsBase, rightsInheriting); + throw new WASMRuntimeException("We don't yet support this WASI call: fd_fdstat_set_rights"); + } + + @WasmExport + public int fdFilestatGet(Instance instance, int fd, int buf) { + logger.infof("fd_filestat_get: [%s, %s]", fd, buf); + + var descriptor = descriptors.get(fd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + if ((descriptor instanceof InStream) || (descriptor instanceof OutStream)) { + Map attributes = + Map.of( + "dev", 0L, + "ino", 0L, + "nlink", 1L, + "size", 0L, + "lastAccessTime", FileTime.from(Instant.EPOCH), + "lastModifiedTime", FileTime.from(Instant.EPOCH), + "ctime", FileTime.from(Instant.EPOCH)); + writeFileStat(instance.memory(), buf, attributes, WasiFileType.CHARACTER_DEVICE); + return wasiResult(WasiErrno.ESUCCESS); + } + + Path path; + if (descriptor instanceof OpenFile) { + path = ((OpenFile) descriptor).path(); + } else if (descriptor instanceof OpenDirectory) { + path = ((OpenDirectory) descriptor).path(); + } else { + throw unhandledDescriptor(descriptor); + } + + Map attributes; + try { + attributes = Files.readAttributes(path, "unix:*"); + } catch (UnsupportedOperationException e) { + return wasiResult(WasiErrno.ENOTSUP); + } catch (IOException e) { + return wasiResult(WasiErrno.EIO); + } + + writeFileStat(instance.memory(), buf, attributes, getFileType(attributes)); + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int fdFilestatSetSize(int fd, long size) { + logger.infof("fd_filestat_set_size: [%s, %s]", fd, size); + throw new WASMRuntimeException("We don't yet support this WASI call: fd_filestat_set_size"); + } + + @WasmExport + public int fdFilestatSetTimes(int fd, long accessTime, long modifiedTime, int fstFlags) { + logger.infof( + "fd_filestat_set_times: [%s, %s, %s, %s]", fd, accessTime, modifiedTime, fstFlags); + throw new WASMRuntimeException( + "We don't yet support this WASI call: fd_filestat_set_times"); + } + + @WasmExport + public int fdPread(int fd, int iovs, int iovsLen, long offset, int nreadPtr) { + logger.infof("fd_pread: [%s, %s, %s, %s, %s]", fd, iovs, iovsLen, offset, nreadPtr); + throw new WASMRuntimeException("We don't yet support this WASI call: fd_pread"); + } + + @WasmExport + public int fdPrestatDirName(Instance instance, int fd, int path, int pathLen) { + logger.infof("fd_prestat_dir_name: [%s, %s, %s]", fd, path, pathLen); + var descriptor = descriptors.get(fd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + if (!(descriptor instanceof PreopenedDirectory)) { + return wasiResult(WasiErrno.EBADF); + } + byte[] name = ((PreopenedDirectory) descriptor).name(); + + if (pathLen < name.length) { + return wasiResult(WasiErrno.ENAMETOOLONG); + } + + instance.memory().write(path, name); + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int fdPrestatGet(Instance instance, int fd, int buf) { + logger.infof("fd_prestat_get: [%s, %s]", fd, buf); + var descriptor = descriptors.get(fd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + if (!(descriptor instanceof PreopenedDirectory)) { + return wasiResult(WasiErrno.EBADF); + } + int length = ((PreopenedDirectory) descriptor).name().length; + + Memory memory = instance.memory(); + memory.writeI32(buf, 0); // preopentype::dir + memory.writeI32(buf + 4, length); + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int fdPwrite(int fd, int iovs, int iovsLen, long offset, int nwrittenPtr) { + logger.infof("fd_pwrite: [%s, %s, %s, %s, %s]", fd, iovs, iovsLen, offset, nwrittenPtr); + throw new WASMRuntimeException("We don't yet support this WASI call: fd_pwrite"); + } + + @WasmExport + public int fdRead(Instance instance, int fd, int iovs, int iovsLen, int nreadPtr) { + logger.infof("fd_read: [%s, %s, %s, %s]", fd, iovs, iovsLen, nreadPtr); + var descriptor = descriptors.get(fd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + if (descriptor instanceof OutStream) { + return wasiResult(WasiErrno.EBADF); + } + if (descriptor instanceof Directory) { + return wasiResult(WasiErrno.EISDIR); + } + if (!(descriptor instanceof DataReader)) { + throw unhandledDescriptor(descriptor); + } + DataReader reader = (DataReader) descriptor; + + int totalRead = 0; + Memory memory = instance.memory(); + for (var i = 0; i < iovsLen; i++) { + int base = iovs + (i * 8); + int iovBase = memory.readI32(base).asInt(); + var iovLen = memory.readI32(base + 4).asInt(); + try { + byte[] data = new byte[iovLen]; + int read = reader.read(data); + if (read < 0) { + break; + } + memory.write(iovBase, data, 0, read); + totalRead += read; + if (read < iovLen) { + break; + } + } catch (IOException e) { + return wasiResult(WasiErrno.EIO); + } + } + + memory.writeI32(nreadPtr, totalRead); + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int fdReaddir( + Instance instance, int dirFd, int buf, int bufLen, long cookie, int bufUsedPtr) { + logger.infof("fd_readdir: [%s, %s, %s, %s, %s]", dirFd, buf, bufLen, cookie, bufUsedPtr); + if (cookie < 0) { + return wasiResult(WasiErrno.EINVAL); + } + + var descriptor = descriptors.get(dirFd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + Path directoryPath; + if (descriptor instanceof Directory) { + directoryPath = ((Directory) descriptor).path(); + } else { + return wasiResult(WasiErrno.ENOTDIR); + } + + Memory memory = instance.memory(); + int used = 0; + try (Stream stream = Files.list(directoryPath)) { + Stream special = + Stream.of(directoryPath.resolve("."), directoryPath.resolve("..")); + Iterator iterator = Stream.concat(special, stream).skip(cookie).iterator(); + while (iterator.hasNext()) { + Path entryPath = iterator.next(); + byte[] name = entryPath.getFileName().toString().getBytes(UTF_8); + cookie++; + + Map attributes; + try { + attributes = Files.readAttributes(entryPath, "unix:*"); + } catch (UnsupportedOperationException e) { + return wasiResult(WasiErrno.ENOTSUP); + } catch (NoSuchFileException e) { + continue; + } + + ByteBuffer entry = + ByteBuffer.allocate(24 + name.length).order(ByteOrder.LITTLE_ENDIAN); + entry.putLong(0, cookie); + entry.putLong(8, ((Number) attributes.get("ino")).longValue()); + entry.putInt(16, name.length); + entry.put(20, (byte) getFileType(attributes).value()); + entry.position(24); + entry.put(name); + + int writeSize = min(entry.capacity(), bufLen - used); + memory.write(buf + used, entry.array(), 0, writeSize); + used += writeSize; + + if (used == bufLen) { + break; + } + } + } catch (NotDirectoryException e) { + return wasiResult(WasiErrno.ENOTDIR); + } catch (NoSuchFileException e) { + return wasiResult(WasiErrno.ENOENT); + } catch (IOException e) { + return wasiResult(WasiErrno.EIO); + } + + memory.writeI32(bufUsedPtr, used); + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int fdRenumber(int from, int to) { + logger.infof("fd_renumber: [%s, %s]", from, to); + throw new WASMRuntimeException("We don't yet support this WASI call: fd_renumber"); + } + + @WasmExport + public int fdSeek(Instance instance, int fd, long offset, int whence, int newOffsetPtr) { + logger.infof("fd_seek: [%s, %s, %s, %s]", fd, offset, whence, newOffsetPtr); + if (whence < 0 || whence > 2) { + return wasiResult(WasiErrno.EINVAL); + } + + var descriptor = descriptors.get(fd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + if ((descriptor instanceof InStream) || (descriptor instanceof OutStream)) { + return wasiResult(WasiErrno.ESPIPE); + } + if (descriptor instanceof Directory) { + return wasiResult(WasiErrno.EISDIR); + } + if (!(descriptor instanceof OpenFile)) { + throw unhandledDescriptor(descriptor); + } + SeekableByteChannel channel = ((OpenFile) descriptor).channel(); + + long newOffset; + try { + switch (whence) { + case WasiWhence.SET: + channel.position(offset); + break; + case WasiWhence.CUR: + channel.position(channel.position() + offset); + break; + case WasiWhence.END: + channel.position(channel.size() + offset); + break; + } + newOffset = channel.position(); + } catch (IllegalArgumentException e) { + return wasiResult(WasiErrno.EINVAL); + } catch (IOException e) { + return wasiResult(WasiErrno.EIO); + } + + instance.memory().writeLong(newOffsetPtr, newOffset); + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int fdSync(int fd) { + logger.infof("fd_sync: [%s]", fd); + throw new WASMRuntimeException("We don't yet support this WASI call: fd_sync"); + } + + @WasmExport + public int fdTell(Instance instance, int fd, int offsetPtr) { + logger.infof("fd_tell: [%s, %s]", fd, offsetPtr); + var descriptor = descriptors.get(fd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + if ((descriptor instanceof InStream) || (descriptor instanceof OutStream)) { + return wasiResult(WasiErrno.ESPIPE); + } + if (descriptor instanceof Directory) { + return wasiResult(WasiErrno.EISDIR); + } + if (!(descriptor instanceof OpenFile)) { + throw unhandledDescriptor(descriptor); + } + SeekableByteChannel channel = ((OpenFile) descriptor).channel(); + + long offset; + try { + offset = channel.position(); + } catch (IOException e) { + return wasiResult(WasiErrno.EIO); + } + + instance.memory().writeLong(offsetPtr, offset); + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int fdWrite(Instance instance, int fd, int iovs, int iovsLen, int nwrittenPtr) { + logger.infof("fd_write: [%s, %s, %s, %s]", fd, iovs, iovsLen, nwrittenPtr); + var descriptor = descriptors.get(fd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + if (descriptor instanceof InStream) { + return wasiResult(WasiErrno.EBADF); + } + if (descriptor instanceof Directory) { + return wasiResult(WasiErrno.EISDIR); + } + if (!(descriptor instanceof DataWriter)) { + throw unhandledDescriptor(descriptor); + } + DataWriter writer = (DataWriter) descriptor; + + var totalWritten = 0; + Memory memory = instance.memory(); + for (var i = 0; i < iovsLen; i++) { + var base = iovs + (i * 8); + var iovBase = memory.readI32(base).asInt(); + var iovLen = memory.readI32(base + 4).asInt(); + var data = memory.readBytes(iovBase, iovLen); + try { + int written = writer.write(data); + totalWritten += written; + if (written < iovLen) { + break; + } + } catch (IOException e) { + return wasiResult(WasiErrno.EIO); + } + } + + memory.writeI32(nwrittenPtr, totalWritten); + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int pathCreateDirectory(Instance instance, int dirFd, int pathPtr, int pathLen) { + logger.infof("path_create_directory: [%s, %s, %s]", dirFd, pathPtr, pathLen); + var descriptor = descriptors.get(dirFd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + if (!(descriptor instanceof Directory)) { + return wasiResult(WasiErrno.ENOTDIR); + } + Path directory = ((Directory) descriptor).path(); + + String rawPath = instance.memory().readString(pathPtr, pathLen); + Path path = resolvePath(directory, rawPath); + if (path == null) { + return wasiResult(WasiErrno.EACCES); + } + + try { + Files.createDirectory(path); + } catch (FileAlreadyExistsException e) { + return wasiResult(WasiErrno.EEXIST); + } catch (NoSuchFileException e) { + return wasiResult(WasiErrno.ENOENT); + } catch (IOException e) { + return wasiResult(WasiErrno.EIO); + } + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int pathFilestatGet( + Instance instance, int dirFd, int lookupFlags, int pathPtr, int pathLen, int buf) { + logger.infof( + "path_filestat_get: [%s, %s, %s, %s, %s]", + dirFd, lookupFlags, pathPtr, pathLen, buf); + var descriptor = descriptors.get(dirFd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + if (!(descriptor instanceof Directory)) { + return wasiResult(WasiErrno.ENOTDIR); + } + Path directory = ((Directory) descriptor).path(); + + Memory memory = instance.memory(); + String rawPath = memory.readString(pathPtr, pathLen); + Path path = resolvePath(directory, rawPath); + if (path == null) { + return wasiResult(WasiErrno.EACCES); + } + + LinkOption[] linkOptions = toLinkOptions(lookupFlags); + + Map attributes; + try { + attributes = Files.readAttributes(path, "unix:*", linkOptions); + } catch (UnsupportedOperationException e) { + return wasiResult(WasiErrno.ENOTSUP); + } catch (NoSuchFileException e) { + return wasiResult(WasiErrno.ENOENT); + } catch (IOException e) { + return wasiResult(WasiErrno.EIO); + } + + writeFileStat(memory, buf, attributes, getFileType(attributes)); + + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int pathFilestatSetTimes( + int fd, + int lookupFlags, + int pathPtr, + int pathLen, + long accessTime, + long modifiedTime, + int fstFlags) { + logger.infof( + "path_filestat_set_times: [%s, %s, %s, %s, %s, %s, %s]", + fd, lookupFlags, pathPtr, pathLen, accessTime, modifiedTime, fstFlags); + throw new WASMRuntimeException( + "We don't yet support this WASI call: path_filestat_set_size"); + } + + @WasmExport + public int pathLink( + int oldFd, + int oldFlags, + int oldPathPtr, + int oldPathLen, + int newFd, + int newPathPtr, + int newPathLen) { + logger.infof( + "path_link: [%s, %s, %s, %s, %s, %s, %s]", + oldFd, oldFlags, oldPathPtr, oldPathLen, newFd, newPathPtr, newPathLen); + throw new WASMRuntimeException("We don't yet support this WASI call: path_link"); + } + + @WasmExport + public int pathOpen( + Instance instance, + int dirFd, + int lookupFlags, + int pathPtr, + int pathLen, + int openFlags, + long rightsBase, + long rightsInheriting, + int fdFlags, + int fdPtr) { + logger.infof( + "path_open: [%s, %s, %s, %s, %s, %s, %s, %s, %s]", + dirFd, + lookupFlags, + pathPtr, + pathLen, + openFlags, + rightsBase, + rightsInheriting, + fdFlags, + fdPtr); + var descriptor = descriptors.get(dirFd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + if (!(descriptor instanceof Directory)) { + return wasiResult(WasiErrno.ENOTDIR); + } + Path directory = ((Directory) descriptor).path(); + + Memory memory = instance.memory(); + String rawPath = memory.readString(pathPtr, pathLen); + Path path = resolvePath(directory, rawPath); + if (path == null) { + return wasiResult(WasiErrno.EACCES); + } + + LinkOption[] linkOptions = toLinkOptions(lookupFlags); + + if (Files.isDirectory(path, linkOptions)) { + int fd = descriptors.allocate(new OpenDirectory(path)); + memory.writeI32(fdPtr, fd); + return wasiResult(WasiErrno.ESUCCESS); + } + + if (flagSet(openFlags, WasiOpenFlags.DIRECTORY) && Files.exists(path, linkOptions)) { + return wasiResult(WasiErrno.ENOTDIR); + } + + Set openOptions = new HashSet<>(Arrays.asList(linkOptions)); + + boolean append = flagSet(fdFlags, WasiFdFlags.APPEND); + boolean truncate = flagSet(openFlags, WasiOpenFlags.TRUNC); + + if (append && truncate) { + return wasiResult(WasiErrno.ENOTSUP); + } + if (!append) { + openOptions.add(StandardOpenOption.READ); + } + openOptions.add(StandardOpenOption.WRITE); + + if (flagSet(openFlags, WasiOpenFlags.CREAT)) { + if (flagSet(openFlags, WasiOpenFlags.EXCL)) { + openOptions.add(StandardOpenOption.CREATE_NEW); + } else { + openOptions.add(StandardOpenOption.CREATE); + } + } + if (truncate) { + openOptions.add(StandardOpenOption.TRUNCATE_EXISTING); + } + if (append) { + openOptions.add(StandardOpenOption.APPEND); + } + if (flagSet(fdFlags, WasiFdFlags.SYNC)) { + openOptions.add(StandardOpenOption.SYNC); + } + if (flagSet(fdFlags, WasiFdFlags.DSYNC)) { + openOptions.add(StandardOpenOption.DSYNC); + } + // ignore WasiFdFlags.RSYNC and WasiFdFlags.NONBLOCK + + int fd; + try { + SeekableByteChannel channel = Files.newByteChannel(path, openOptions); + fd = descriptors.allocate(new OpenFile(path, channel, fdFlags)); + } catch (FileAlreadyExistsException e) { + return wasiResult(WasiErrno.EEXIST); + } catch (NoSuchFileException e) { + return wasiResult(WasiErrno.ENOENT); + } catch (IOException e) { + return wasiResult(WasiErrno.EIO); + } + + memory.writeI32(fdPtr, fd); + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int pathReadlink( + Instance instance, + int dirFd, + int pathPtr, + int pathLen, + int buf, + int bufLen, + int bufUsed) { + logger.infof( + "path_readlink: [%s, %s, %s, %s, %s, %s]", + dirFd, pathPtr, pathLen, buf, bufLen, bufUsed); + throw new WASMRuntimeException("We don't yet support this WASI call: path_readlink"); + } + + @WasmExport + public int pathRemoveDirectory(Instance instance, int dirFd, int pathPtr, int pathLen) { + logger.infof("path_remove_directory: [%s, %s, %s]", dirFd, pathPtr, pathLen); + var descriptor = descriptors.get(dirFd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + if (!(descriptor instanceof Directory)) { + return wasiResult(WasiErrno.ENOTDIR); + } + Path directory = ((Directory) descriptor).path(); + + String rawPath = instance.memory().readString(pathPtr, pathLen); + Path path = resolvePath(directory, rawPath); + if (path == null) { + return wasiResult(WasiErrno.EACCES); + } + + try { + var attributes = + Files.readAttributes( + path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + if (!attributes.isDirectory()) { + return wasiResult(WasiErrno.ENOTDIR); + } + Files.delete(path); + } catch (NoSuchFileException e) { + return wasiResult(WasiErrno.ENOENT); + } catch (DirectoryNotEmptyException e) { + return wasiResult(WasiErrno.ENOTEMPTY); + } catch (IOException e) { + return wasiResult(WasiErrno.EIO); + } + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int pathRename( + Instance instance, + int oldFd, + int oldPathPtr, + int oldPathLen, + int newFd, + int newPathPtr, + int newPathLen) { + logger.infof( + "path_rename: [%s, %s, %s, %s, %s, %s]", + oldFd, oldPathPtr, oldPathLen, newFd, newPathPtr, newPathLen); + var oldDescriptor = descriptors.get(oldFd); + if (oldDescriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + if (!(oldDescriptor instanceof Directory)) { + return wasiResult(WasiErrno.ENOTDIR); + } + Path oldDirectory = ((Directory) oldDescriptor).path(); + + var newDescriptor = descriptors.get(newFd); + if (newDescriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + if (!(newDescriptor instanceof Directory)) { + return wasiResult(WasiErrno.ENOTDIR); + } + Path newDirectory = ((Directory) newDescriptor).path(); + + String oldRawPath = instance.memory().readString(oldPathPtr, oldPathLen); + Path oldPath = resolvePath(oldDirectory, oldRawPath); + if (oldPath == null) { + return wasiResult(WasiErrno.EACCES); + } + + String newRawPath = instance.memory().readString(newPathPtr, newPathLen); + Path newPath = resolvePath(newDirectory, newRawPath); + if (newPath == null) { + return wasiResult(WasiErrno.EACCES); + } + + if (Files.isDirectory(oldPath) && Files.isRegularFile(newPath, LinkOption.NOFOLLOW_LINKS)) { + return wasiResult(WasiErrno.ENOTDIR); + } + if (Files.isRegularFile(oldPath, LinkOption.NOFOLLOW_LINKS) && Files.isDirectory(newPath)) { + return wasiResult(WasiErrno.EISDIR); + } + + try { + Files.move( + oldPath, + newPath, + StandardCopyOption.REPLACE_EXISTING, + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.COPY_ATTRIBUTES); + } catch (UnsupportedOperationException | AtomicMoveNotSupportedException e) { + return wasiResult(WasiErrno.ENOTSUP); + } catch (NoSuchFileException e) { + return wasiResult(WasiErrno.ENOENT); + } catch (DirectoryNotEmptyException e) { + return wasiResult(WasiErrno.ENOTEMPTY); + } catch (IOException e) { + return wasiResult(WasiErrno.EIO); + } + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int pathSymlink( + int oldPathPtr, int oldPathLen, int dirFd, int newPathPtr, int newPathLen) { + logger.infof( + "path_symlink: [%s, %s, %s, %s, %s]", + oldPathPtr, oldPathLen, dirFd, newPathPtr, newPathLen); + throw new WASMRuntimeException("We don't yet support this WASI call: path_symlink"); + } + + @WasmExport + public int pathUnlinkFile(Instance instance, int dirFd, int pathPtr, int pathLen) { + logger.infof("path_unlink_file: [%s, %s, %s]", dirFd, pathPtr, pathLen); + var descriptor = descriptors.get(dirFd); + if (descriptor == null) { + return wasiResult(WasiErrno.EBADF); + } + + if (!(descriptor instanceof Directory)) { + return wasiResult(WasiErrno.ENOTDIR); + } + Path directory = ((Directory) descriptor).path(); + + String rawPath = instance.memory().readString(pathPtr, pathLen); + Path path = resolvePath(directory, rawPath); + if (path == null) { + return wasiResult(WasiErrno.EACCES); + } + + try { + var attributes = + Files.readAttributes( + path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + if (attributes.isDirectory()) { + return wasiResult(WasiErrno.EISDIR); + } + if (rawPath.endsWith("/")) { + return wasiResult(WasiErrno.ENOTDIR); + } + Files.delete(path); + } catch (NoSuchFileException e) { + return wasiResult(WasiErrno.ENOENT); + } catch (IOException e) { + return wasiResult(WasiErrno.EIO); + } + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public int pollOneoff(int inPtr, int outPtr, int nsubscriptions, int neventsPtr) { + logger.infof("poll_oneoff: [%s, %s, %s, %s]", inPtr, outPtr, nsubscriptions, neventsPtr); + throw new WASMRuntimeException("We don't yet support this WASI call: poll_oneoff"); + } + + @WasmExport + public void procExit(int code) { + logger.infof("proc_exit: [%s]", code); + throw new WasiExitException(code); + } + + @WasmExport + public int procRaise(int sig) { + logger.infof("proc_raise: [%s]", sig); + throw new WASMRuntimeException("We don't yet support this WASI call: proc_raise"); + } + + @WasmExport + public int randomGet(Instance instance, int buf, int bufLen) { + logger.infof("random_get: [%s, %s]", buf, bufLen); + if (bufLen < 0) { + return wasiResult(WasiErrno.EINVAL); + } + if (bufLen >= 100_000) { + throw new WASMRuntimeException("random_get: bufLen must be < 100_000"); + } + + byte[] data = new byte[bufLen]; + new SecureRandom().nextBytes(data); + instance.memory().write(buf, data); + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public void resetAdapterState() { + logger.info("reset_adapter_state"); + throw new WASMRuntimeException("We don't yet support this WASI call: reset_adapter_state"); + } + + @WasmExport + public int schedYield() { + logger.info("sched_yield"); + // do nothing here + return wasiResult(WasiErrno.ESUCCESS); + } + + @WasmExport + public void setAllocationState(int state) { + logger.infof("set_allocation_state: [%s]", state); + throw new WASMRuntimeException("We don't yet support this WASI call: set_allocation_state"); + } + + @WasmExport + public void setStatePtr(int state) { + logger.infof("set_state_ptr: [%s]", state); + throw new WASMRuntimeException("We don't yet support this WASI call: set_state_ptr"); + } + + @WasmExport + public int sockAccept(int sock, int fdFlags, int roFdPtr) { + logger.infof("sock_accept: [%s, %s, %s]", sock, fdFlags, roFdPtr); + throw new WASMRuntimeException("We don't yet support this WASI call: sock_accept"); + } + + @WasmExport + public int sockRecv( + int sock, int riDataPtr, int riDataLen, int riFlags, int roDataLenPtr, int roFlagsPtr) { + logger.infof( + "sock_recv: [%s, %s, %s, %s, %s, %s]", + sock, riDataPtr, riDataLen, riFlags, roDataLenPtr, roFlagsPtr); + throw new WASMRuntimeException("We don't yet support this WASI call: sock_recv"); + } + + @WasmExport + public int sockSend(int sock, int siDataPtr, int siDataLen, int siFlags, int retDataLenPtr) { + logger.infof( + "sock_send: [%s, %s, %s, %s, %s]", + sock, siDataPtr, siDataLen, siFlags, retDataLenPtr); + throw new WASMRuntimeException("We don't yet support this WASI call: sock_send"); + } + + @WasmExport + public int sockShutdown(int sock, int how) { + logger.infof("sock_shutdown: [%s, %s]", sock, how); + throw new WASMRuntimeException("We don't yet support this WASI call: sock_shutdown"); } public HostFunction[] toHostFunctions() { - return new HostFunction[] { - adapterCloseBadfd(), - adapterOpenBadfd(), - argsGet(), - argsSizesGet(), - clockResGet(), - clockTimeGet(), - environGet(), - environSizesGet(), - fdAdvise(), - fdAllocate(), - fdClose(), - fdDatasync(), - fdFdstatGet(), - fdFdstatSetFlags(), - fdFdstatSetRights(), - fdFilestatGet(), - fdFilestatSetSize(), - fdFilestatSetTimes(), - fdPread(), - fdPrestatDirName(), - fdPrestatGet(), - fdPwrite(), - fdRead(), - fdReaddir(), - fdRenumber(), - fdSeek(), - fdSync(), - fdTell(), - fdWrite(), - pathCreateDirectory(), - pathFilestatGet(), - pathFilestatSetTimes(), - pathLink(), - pathOpen(), - pathReadlink(), - pathRemoveDirectory(), - pathRename(), - pathSymlink(), - pathUnlinkFile(), - pollOneoff(), - procExit(), - procRaise(), - randomGet(), - resetAdapterState(), - schedYield(), - setAllocationState(), - setStatePtr(), - sockAccept(), - sockRecv(), - sockSend(), - sockShutdown() - }; - } - - private Value[] wasiResult(WasiErrno errno) { + return WasiPreview1_ModuleFactory.toHostFunctions(this); + } + + private int wasiResult(WasiErrno errno) { if (errno != WasiErrno.ESUCCESS) { logger.info("result = " + errno.name()); } - return new Value[] {Value.i32(errno.value())}; + return errno.value(); } private static Path resolvePath(Path directory, String rawPathString) {