diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmJsApiSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmJsApiSuite.java index 0b96d73515be..14634f78c219 100644 --- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmJsApiSuite.java +++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/WasmJsApiSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -835,13 +835,11 @@ public void testExportCountsLimit() throws IOException { context.readModule(binaryWithMixedExports, limits); final int noLimit = Integer.MAX_VALUE; - limits = new ModuleLimits(noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, 6, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, - noLimit); + limits = new ModuleLimits(noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, 6, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit); context.readModule(binaryWithMixedExports, limits); try { - limits = new ModuleLimits(noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, 5, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, - noLimit); + limits = new ModuleLimits(noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, 5, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit, noLimit); context.readModule(binaryWithMixedExports, limits); Assert.fail("Should have failed - export count exceeds the limit"); } catch (WasmException ex) { diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/WasmImplementationLimitationsSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/WasmImplementationLimitationsSuite.java index 6928f69af351..bb6ce1f2a577 100644 --- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/WasmImplementationLimitationsSuite.java +++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/WasmImplementationLimitationsSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -65,7 +65,7 @@ public class WasmImplementationLimitationsSuite { public static Collection data() { return Arrays.asList( stringCase("Table instance - initial size out of bounds", - "table instance size exceeds limit: 2147483648 should be <= 2147483647", + "table instance size exceeds limit: 2147483648 should be <= 10000000", "(table $table1 2147483648 funcref)", Failure.Type.TRAP), stringCase("Memory instance - initial size out of bounds", "memory instance size exceeds limit: 32768 should be <= 32767", diff --git a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/validation/ValidationSuite.java b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/validation/ValidationSuite.java index 346b542d81f1..89f1b6d2f237 100644 --- a/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/validation/ValidationSuite.java +++ b/wasm/src/org.graalvm.wasm.test/src/org/graalvm/wasm/test/suites/validation/ValidationSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -134,20 +134,23 @@ public static Collection data() { // checked individually on memories and tables // ### Function Types - // Return arity + // Return arity limit (implementation-defined) + // Always <= 1 if wasm.MultiValue=false, else constrained by JS API limits + // "maximum number of return values for any function or block is 1,000". binaryCase( "Function - cannot return more than one value", - "A function can return at most one result.", + "invalid result arity: 2 should be <= 1", // (func $f (result i32) i32.const 42 i32.const 42) "0061 736d 0100 0000 0105 0160 0002 7f03 0201 000a 0801 0600 412a 412a 0b", Failure.Type.INVALID), // ### Table types - // Limits - // Limitation only applies to GraalWasm (max array length) + // Table size limit (implementation-defined) + // Constrained by JS API limits, "maximum size of a table is 10,000,000" + // and in GraalWasm, max array length (near 2**31-1). stringCase( "Table - initial size out of bounds", - "table instance size exceeds limit: 2147483648 should be <= 2147483647", + "table instance size exceeds limit: 2147483648 should be <= 10000000", "(table $table1 2147483648 funcref)", Failure.Type.TRAP), stringCase( @@ -359,12 +362,12 @@ public static Collection data() { // Validated in: BinaryParser#readMemorySection stringCase( "Module - two memories (2 locals)", - "A memory has already been declared in the module.", + "multiple memories: 2 should be <= 1", "(memory $mem1 1) (memory $mem2 1)", Failure.Type.INVALID), stringCase( "Module - two memories (1 local and 1 import)", - "A memory has already been imported in the module.", + "multiple memories: 2 should be <= 1", "(memory $mem1 (import \"some\" \"memory\") 1) (memory $mem2 1)", Failure.Type.INVALID), // Validated in: SymbolTable#validateSingleMemory diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/ModuleLimits.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/ModuleLimits.java index e18184330084..1c4aab15b47c 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/ModuleLimits.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/ModuleLimits.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -52,11 +52,14 @@ * Limits on various aspects of a module. */ public final class ModuleLimits { + + private static final int SINGLE_MEMORY_COUNT_LIMIT = 1; + private static final int SINGLE_RESULT_COUNT_LIMIT = 1; + private final int moduleSizeLimit; private final int typeCountLimit; private final int functionCountLimit; private final int tableCountLimit; - private final int memoryCountLimit; private final int multiMemoryCountLimit; private final int importCountLimit; private final int exportCountLimit; @@ -65,23 +68,21 @@ public final class ModuleLimits { private final int elementSegmentCountLimit; private final int functionSizeLimit; private final int paramCountLimit; - private final int resultCountLimit; private final int multiValueResultCountLimit; private final int localCountLimit; private final int tableInstanceSizeLimit; private final int memoryInstanceSizeLimit; private final long memory64InstanceSizeLimit; - public ModuleLimits(int moduleSizeLimit, int typeCountLimit, int functionCountLimit, int tableCountLimit, int memoryCountLimit, int multiMemoryCountLimit, int importCountLimit, + public ModuleLimits(int moduleSizeLimit, int typeCountLimit, int functionCountLimit, int tableCountLimit, int memoryCountLimit, int importCountLimit, int exportCountLimit, int globalCountLimit, - int dataSegmentCountLimit, int elementSegmentCountLimit, int functionSizeLimit, int paramCountLimit, int resultCountLimit, int multiValueResultCountLimit, int localCountLimit, + int dataSegmentCountLimit, int elementSegmentCountLimit, int functionSizeLimit, int paramCountLimit, int resultCountLimit, int localCountLimit, int tableInstanceSizeLimit, int memoryInstanceSizeLimit, long memory64InstanceSizeLimit) { this.moduleSizeLimit = minUnsigned(moduleSizeLimit, Integer.MAX_VALUE); this.typeCountLimit = minUnsigned(typeCountLimit, Integer.MAX_VALUE); this.functionCountLimit = minUnsigned(functionCountLimit, Integer.MAX_VALUE); this.tableCountLimit = minUnsigned(tableCountLimit, Integer.MAX_VALUE); - this.memoryCountLimit = minUnsigned(memoryCountLimit, Integer.MAX_VALUE); - this.multiMemoryCountLimit = minUnsigned(multiMemoryCountLimit, Integer.MAX_VALUE); + this.multiMemoryCountLimit = minUnsigned(memoryCountLimit, Integer.MAX_VALUE); this.importCountLimit = minUnsigned(importCountLimit, Integer.MAX_VALUE); this.exportCountLimit = minUnsigned(exportCountLimit, Integer.MAX_VALUE); this.globalCountLimit = minUnsigned(globalCountLimit, Integer.MAX_VALUE); @@ -89,8 +90,7 @@ public ModuleLimits(int moduleSizeLimit, int typeCountLimit, int functionCountLi this.elementSegmentCountLimit = minUnsigned(elementSegmentCountLimit, Integer.MAX_VALUE); this.functionSizeLimit = minUnsigned(functionSizeLimit, Integer.MAX_VALUE); this.paramCountLimit = minUnsigned(paramCountLimit, Integer.MAX_VALUE); - this.resultCountLimit = minUnsigned(resultCountLimit, Integer.MAX_VALUE); - this.multiValueResultCountLimit = minUnsigned(multiValueResultCountLimit, Integer.MAX_VALUE); + this.multiValueResultCountLimit = minUnsigned(resultCountLimit, Integer.MAX_VALUE); this.localCountLimit = minUnsigned(localCountLimit, Integer.MAX_VALUE); this.tableInstanceSizeLimit = minUnsigned(tableInstanceSizeLimit, MAX_TABLE_INSTANCE_SIZE); this.memoryInstanceSizeLimit = minUnsigned(memoryInstanceSizeLimit, MAX_MEMORY_INSTANCE_SIZE); @@ -120,8 +120,6 @@ private static long minUnsigned(long a, long b) { Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, - Integer.MAX_VALUE, - Integer.MAX_VALUE, MAX_TABLE_INSTANCE_SIZE, MAX_MEMORY_INSTANCE_SIZE, MAX_MEMORY_64_INSTANCE_SIZE); @@ -143,11 +141,10 @@ public void checkTableCount(int count) { } public void checkMemoryCount(int count, boolean multiMemory) { - if (multiMemory) { - assertUnsignedIntLessOrEqual(count, multiMemoryCountLimit, Failure.MEMORY_COUNT_LIMIT_EXCEEDED); - } else { - assertUnsignedIntLessOrEqual(count, memoryCountLimit, Failure.MEMORY_COUNT_LIMIT_EXCEEDED); + if (!multiMemory) { + assertUnsignedIntLessOrEqual(count, SINGLE_MEMORY_COUNT_LIMIT, Failure.MULTIPLE_MEMORIES); } + assertUnsignedIntLessOrEqual(count, multiMemoryCountLimit, Failure.MEMORY_COUNT_LIMIT_EXCEEDED); } public void checkImportCount(int count) { @@ -179,11 +176,10 @@ public void checkParamCount(int count) { } public void checkResultCount(int count, boolean multiValue) { - if (multiValue) { - assertUnsignedIntLessOrEqual(count, multiValueResultCountLimit, Failure.RESULT_COUNT_LIMIT_EXCEEDED); - } else { - assertUnsignedIntLessOrEqual(count, resultCountLimit, Failure.RESULT_COUNT_LIMIT_EXCEEDED); + if (!multiValue) { + assertUnsignedIntLessOrEqual(count, SINGLE_RESULT_COUNT_LIMIT, Failure.INVALID_RESULT_ARITY); } + assertUnsignedIntLessOrEqual(count, multiValueResultCountLimit, Failure.RESULT_COUNT_LIMIT_EXCEEDED); } public void checkLocalCount(int count) { diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmLanguage.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmLanguage.java index 861675d21129..0b7e846df38c 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmLanguage.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/WasmLanguage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -49,6 +49,7 @@ import org.graalvm.options.OptionValues; import org.graalvm.wasm.api.JsConstants; import org.graalvm.wasm.api.WebAssembly; +import org.graalvm.wasm.exception.WasmJsApiException; import org.graalvm.wasm.memory.WasmMemory; import org.graalvm.wasm.predefined.BuiltinModule; @@ -69,19 +70,18 @@ @Registration(id = WasmLanguage.ID, // name = WasmLanguage.NAME, // defaultMimeType = WasmLanguage.WASM_MIME_TYPE, // - byteMimeTypes = WasmLanguage.WASM_MIME_TYPE, // + byteMimeTypes = {WasmLanguage.WASM_MIME_TYPE}, // contextPolicy = TruffleLanguage.ContextPolicy.SHARED, // fileTypeDetectors = WasmFileDetector.class, // interactive = false, // - website = "https://www.graalvm.org/") + website = "https://www.graalvm.org/webassembly/") @ProvidedTags({StandardTags.RootTag.class, StandardTags.RootBodyTag.class, StandardTags.StatementTag.class}) public final class WasmLanguage extends TruffleLanguage { public static final String ID = "wasm"; public static final String NAME = "WebAssembly"; public static final String WASM_MIME_TYPE = "application/wasm"; public static final String WASM_SOURCE_NAME_SUFFIX = ".wasm"; - public static final String PARSE_JS_MODULE_MARKER = "js_module_decode"; - public static final String[] PARSE_JS_MODULE_ARGS = {PARSE_JS_MODULE_MARKER}; + public static final String MODULE_DECODE = "module_decode"; private static final LanguageReference REFERENCE = LanguageReference.create(WasmLanguage.class); @@ -124,10 +124,7 @@ protected CallTarget parse(ParsingRequest request) { final Source source = request.getSource(); final String moduleName = source.getName(); final byte[] data = source.getBytes().toByteArray(); - ModuleLimits moduleLimits = null; - if (!request.getArgumentNames().isEmpty() && PARSE_JS_MODULE_MARKER.equals(request.getArgumentNames().get(0))) { - moduleLimits = JsConstants.JS_LIMITS; - } + ModuleLimits moduleLimits = JsConstants.JS_LIMITS; final WasmModule module = context.readModule(moduleName, data, moduleLimits); return new ParsedWasmModuleRootNode(this, module, source).getCallTarget(); } @@ -142,14 +139,36 @@ private ParsedWasmModuleRootNode(WasmLanguage language, WasmModule module, Sourc this.source = source; } + /** + * The CallTarget returned by {@code parse} supports two calling conventions: + * + *
    + *
  1. (default) zero arguments provided: on the first call, instantiates the decoded module + * and puts it in the context's module instance map; then returns the {@link WasmInstance}. + *
  2. first argument is {@code "module_decode"}: returns the decoded {@link WasmModule} + * (i.e. behaves like {@link WebAssembly#moduleDecode module_decode}). Used by the JS API. + *
+ */ @Override - public WasmInstance execute(VirtualFrame frame) { - final WasmContext context = WasmContext.get(this); - WasmInstance instance = context.lookupModuleInstance(module); - if (instance == null) { - instance = context.readInstance(module); + public Object execute(VirtualFrame frame) { + if (frame.getArguments().length == 0) { + final WasmContext context = WasmContext.get(this); + WasmInstance instance = context.lookupModuleInstance(module); + if (instance == null) { + instance = context.readInstance(module); + } + return instance; + } else { + if (frame.getArguments()[0] instanceof String mode) { + if (mode.equals(MODULE_DECODE)) { + return module; + } else { + throw WasmJsApiException.format(WasmJsApiException.Kind.TypeError, "Unsupported first argument: '%s'", mode); + } + } else { + throw WasmJsApiException.format(WasmJsApiException.Kind.TypeError, "First argument must be a string"); + } } - return instance; } @Override diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/JsConstants.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/JsConstants.java index b9a421b63bcc..e5b07bd2fca0 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/JsConstants.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/JsConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -56,12 +56,10 @@ private JsConstants() { private static final int GLOBAL_COUNT_LIMIT = 1000000; private static final int DATA_SEGMENT_LIMIT = 100000; private static final int TABLE_COUNT_LIMIT = 100000; - private static final int MEMORY_COUNT_LIMIT = 1; private static final int MULTI_MEMORY_COUNT_LIMIT = 100; private static final int ELEMENT_SEGMENT_LIMIT = 10000000; private static final int FUNCTION_SIZE_LIMIT = 7654321; private static final int PARAM_COUNT_LIMIT = 1000; - private static final int RESULT_COUNT_LIMIT = 1; private static final int MULTI_VALUE_RESULT_COUNT_LIMIT = 1000; private static final int LOCAL_COUNT_LIMIT = 50000; private static final int TABLE_SIZE_LIMIT = 10000000; @@ -72,7 +70,6 @@ private JsConstants() { TYPE_COUNT_LIMIT, FUNCTION_COUNT_LIMIT, TABLE_COUNT_LIMIT, - MEMORY_COUNT_LIMIT, MULTI_MEMORY_COUNT_LIMIT, IMPORT_COUNT_LIMIT, EXPORT_COUNT_LIMIT, @@ -81,7 +78,6 @@ private JsConstants() { ELEMENT_SEGMENT_LIMIT, FUNCTION_SIZE_LIMIT, PARAM_COUNT_LIMIT, - RESULT_COUNT_LIMIT, MULTI_VALUE_RESULT_COUNT_LIMIT, LOCAL_COUNT_LIMIT, TABLE_SIZE_LIMIT, diff --git a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/WebAssembly.java b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/WebAssembly.java index 8ea8fd84fe20..2c36798d2216 100644 --- a/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/WebAssembly.java +++ b/wasm/src/org.graalvm.wasm/src/org/graalvm/wasm/api/WebAssembly.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -259,8 +259,8 @@ private static String makeModuleName(byte[] data) { private WasmModuleWithSource moduleDecodeImpl(byte[] data) { String moduleName = makeModuleName(data); - Source source = Source.newBuilder(WasmLanguage.ID, ByteSequence.create(data), moduleName).build(); - CallTarget parseResult = currentContext.environment().parsePublic(source, WasmLanguage.PARSE_JS_MODULE_ARGS); + Source source = Source.newBuilder(WasmLanguage.ID, ByteSequence.create(data), moduleName).mimeType(WasmLanguage.WASM_MIME_TYPE).build(); + CallTarget parseResult = currentContext.environment().parsePublic(source); WasmModule module = WasmLanguage.getParsedModule(parseResult); assert module.limits().equals(JsConstants.JS_LIMITS); return new WasmModuleWithSource(module, source); @@ -332,9 +332,17 @@ private static WasmJsApiException cannotConvertToBytesError(Throwable cause) { return (cause == null) ? new WasmJsApiException(kind, message) : new WasmJsApiException(kind, message, cause); } + /** + * Extract a {@link WasmModule} from argument 0. The argument may be a {@link WasmModule} or a + * {@link WasmModuleWithSource} as produced by the CallTarget returned from Env.parse or + * {@link #moduleDecode}, respectively. + */ private static WasmModule toModule(Object[] args) { checkArgumentCount(args, 1); - if (args[0] instanceof WasmModuleWithSource moduleObject) { + Object arg0 = args[0]; + if (arg0 instanceof WasmModule moduleObject) { + return moduleObject; + } else if (arg0 instanceof WasmModuleWithSource moduleObject) { return moduleObject.module(); } else { throw new WasmJsApiException(WasmJsApiException.Kind.TypeError, "First argument must be wasm module");