WebAssembly JS API: add Module.sections
authorjfbastien@apple.com <jfbastien@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 4 Jan 2017 19:27:01 +0000 (19:27 +0000)
committerjfbastien@apple.com <jfbastien@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 4 Jan 2017 19:27:01 +0000 (19:27 +0000)
https://bugs.webkit.org/show_bug.cgi?id=165159
<rdar://problem/29760326>

Reviewed by Mark Lam.

JSTests:

As described here: https://github.com/WebAssembly/design/blob/master/JS.md#webassemblymodulecustomsections

* wasm/Builder.js: allow custom sections to be duplicated
* wasm/js-api/Module.customSection.js: Added.
(assert.throws.WebAssembly.Module.prototype.customSections):
(assert.eq):

Source/JavaScriptCore:

As described in: https://github.com/WebAssembly/design/blob/master/JS.md#webassemblymodulecustomsections

This was added for Emscripten, and is likely to be used soon.

* wasm/WasmFormat.h: custom sections are just name + bytes
* wasm/WasmModuleParser.cpp: parse them, instead of skipping over
* wasm/WasmModuleParser.h:
* wasm/js/WebAssemblyModulePrototype.cpp: construct the Array of
ArrayBuffer as described in the spec
(JSC::webAssemblyModuleProtoCustomSections):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@210282 268f45cc-cd09-0410-ab3c-d52691b4dbfc

JSTests/ChangeLog
JSTests/wasm/Builder.js
JSTests/wasm/js-api/Module.customSection.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/wasm/WasmFormat.h
Source/JavaScriptCore/wasm/WasmModuleParser.cpp
Source/JavaScriptCore/wasm/WasmModuleParser.h
Source/JavaScriptCore/wasm/js/WebAssemblyModulePrototype.cpp

index bfae031..01c5593 100644 (file)
@@ -1,3 +1,18 @@
+2017-01-04  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly JS API: add Module.sections
+        https://bugs.webkit.org/show_bug.cgi?id=165159
+        <rdar://problem/29760326>
+
+        Reviewed by Mark Lam.
+
+        As described here: https://github.com/WebAssembly/design/blob/master/JS.md#webassemblymodulecustomsections
+
+        * wasm/Builder.js: allow custom sections to be duplicated
+        * wasm/js-api/Module.customSection.js: Added.
+        (assert.throws.WebAssembly.Module.prototype.customSections):
+        (assert.eq):
+
 2017-01-04  Saam Barati  <sbarati@apple.com>
 
         We don't properly handle exceptions inside the nativeCallTrampoline macro in the LLInt
index 1cc8a47..2b00a5d 100644 (file)
@@ -713,7 +713,8 @@ export default class Builder {
         if (this._checked) {
             // Check uniqueness.
             for (const s of this._sections)
-                assert.falsy(s.name === name && s.id === number, `Cannot have two sections with the same name "${name}" and ID ${number}`);
+                if (number !== _unknownSectionId)
+                    assert.falsy(s.name === name && s.id === number, `Cannot have two sections with the same name "${name}" and ID ${number}`);
             // Check ordering.
             if ((number !== _unknownSectionId) && (this._sections.length !== 0)) {
                 for (let i = this._sections.length - 1; i >= 0; --i) {
diff --git a/JSTests/wasm/js-api/Module.customSection.js b/JSTests/wasm/js-api/Module.customSection.js
new file mode 100644 (file)
index 0000000..0a70ce9
--- /dev/null
@@ -0,0 +1,76 @@
+import Builder from '../Builder.js';
+import * as assert from '../assert.js';
+
+assert.throws(() => WebAssembly.Module.prototype.customSections(undefined, ""), TypeError, `WebAssembly.Module.prototype.customSections called with non WebAssembly.Module |this| value`);
+
+{
+    const empty = new WebAssembly.Module((new Builder()).WebAssembly().get());
+    assert.isArray(empty.customSections(""));
+    assert.eq(empty.customSections("").length, 0);
+}
+
+{
+    const single = new WebAssembly.Module((new Builder())
+        .Unknown("hello").Byte(0x00).Byte(0x42).Byte(0xFF).End()
+        .WebAssembly().get());
+    assert.eq(single.customSections("").length, 0);
+    const hello = single.customSections("hello");
+    assert.eq(hello.length, 1);
+    assert.eq(hello[0].byteLength, 3);
+    const helloI8 = new Int8Array(hello[0]);
+    assert.eq(helloI8[0], 0x00);
+    assert.eq(helloI8[1], 0x42);
+    assert.eq(helloI8[2], -1);
+}
+
+{
+    const unicode = new WebAssembly.Module((new Builder())
+        .Unknown("👨‍❤️‍💋‍👨").Byte(42).End()
+        .WebAssembly().get());
+    const family = unicode.customSections("👨‍❤️‍💋‍👨");
+    assert.eq(family.length, 1);
+    assert.eq(family[0].byteLength, 1);
+    const familyI8 = new Int8Array(family[0]);
+    assert.eq(familyI8[0], 42);
+}
+
+{
+    const many = new WebAssembly.Module((new Builder())
+        .Unknown("zero").Byte(0).End()
+        .Unknown("one").Byte(1).Byte(1).End()
+        .Unknown("one").Byte(2).Byte(2).Byte(2).End()
+        .Unknown("two").Byte(3).Byte(3).Byte(3).Byte(3).End()
+        .Unknown("one").Byte(4).Byte(4).Byte(4).Byte(4).Byte(4).End()
+        .WebAssembly().get());
+
+    const zero = many.customSections("zero");
+    assert.eq(zero.length, 1);
+    assert.eq(zero[0].byteLength, 1);
+    const zeroI8 = new Int8Array(zero[0]);
+    assert.eq(zeroI8[0], 0);
+
+    const two = many.customSections("two");
+    assert.eq(two.length, 1);
+    assert.eq(two[0].byteLength, 4);
+    const twoI8 = new Int8Array(two[0]);
+    assert.eq(twoI8[0], 3);
+    assert.eq(twoI8[1], 3);
+    assert.eq(twoI8[2], 3);
+    assert.eq(twoI8[3], 3);
+
+    const one = many.customSections("one");
+    assert.eq(one.length, 3);
+    let seen = 0;
+    const expect = [
+        [1, 1],
+        [2, 2, 2],
+        [4, 4, 4, 4, 4],
+    ];
+    for (const section of one) {
+        assert.eq(section.byteLength, expect[seen].length);
+        const I8 = new Int8Array(section);
+        for (let i = 0; i < expect[seen].length; ++i)
+            assert.eq(I8[i], expect[seen][i]);
+        ++seen;
+    }
+}
index b2f0966..8c8edd8 100644 (file)
@@ -1,3 +1,22 @@
+2017-01-04  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly JS API: add Module.sections
+        https://bugs.webkit.org/show_bug.cgi?id=165159
+        <rdar://problem/29760326>
+
+        Reviewed by Mark Lam.
+
+        As described in: https://github.com/WebAssembly/design/blob/master/JS.md#webassemblymodulecustomsections
+
+        This was added for Emscripten, and is likely to be used soon.
+
+        * wasm/WasmFormat.h: custom sections are just name + bytes
+        * wasm/WasmModuleParser.cpp: parse them, instead of skipping over
+        * wasm/WasmModuleParser.h:
+        * wasm/js/WebAssemblyModulePrototype.cpp: construct the Array of
+        ArrayBuffer as described in the spec
+        (JSC::webAssemblyModuleProtoCustomSections):
+
 2017-01-04  Saam Barati  <sbarati@apple.com>
 
         We don't properly handle exceptions inside the nativeCallTrampoline macro in the LLInt
index 0d7e0a5..dc63474 100644 (file)
@@ -220,6 +220,11 @@ private:
     bool m_isImport { false };
     bool m_isValid { false };
 };
+    
+struct CustomSection {
+    String name;
+    Vector<uint8_t> payload;
+};
 
 struct ModuleInformation {
     Vector<Import> imports;
@@ -235,6 +240,8 @@ struct ModuleInformation {
     TableInformation tableInformation;
     Vector<Global> globals;
     unsigned firstInternalGlobal { 0 };
+    Vector<CustomSection> customSections;
+
     size_t functionIndexSpaceSize() const { return importFunctionSignatureIndices.size() + internalFunctionSignatureIndices.size(); }
     bool isImportedFunctionFromFunctionIndexSpace(size_t functionIndex) const
     {
index 3011c89..00fac16 100644 (file)
@@ -87,8 +87,7 @@ auto ModuleParser::parse() -> Result
 #undef WASM_SECTION_PARSE
 
         case Section::Unknown: {
-            // Ignore section's name LEB and bytes: they're already included in sectionLength.
-            m_offset += sectionLength;
+            WASM_FAIL_IF_HELPER_FAILS(parseCustom(sectionLength));
             break;
         }
         }
@@ -600,6 +599,30 @@ auto ModuleParser::parseData() -> PartialResult
     }
     return { };
 }
+    
+auto ModuleParser::parseCustom(uint32_t sectionLength) -> PartialResult
+{
+    const uint32_t customSectionStartOffset = m_offset;
+
+    CustomSection section;
+    uint32_t customSectionNumber = m_result.module->customSections.size() + 1;
+    uint32_t nameLen;
+    WASM_PARSER_FAIL_IF(!m_result.module->customSections.tryReserveCapacity(customSectionNumber), "can't allocate enough memory for ", customSectionNumber, "th custom section");
+    WASM_PARSER_FAIL_IF(!parseVarUInt32(nameLen), "can't get ", customSectionNumber, "th custom section's name length");
+    WASM_PARSER_FAIL_IF(!consumeUTF8String(section.name, nameLen), "nameLen get ", customSectionNumber, "th custom section's name of length ", nameLen);
+
+    uint32_t payloadBytes = sectionLength - (m_offset - customSectionStartOffset);
+    WASM_PARSER_FAIL_IF(!section.payload.tryReserveCapacity(payloadBytes), "can't allocate enough memory for ", customSectionNumber, "th custom section's ", payloadBytes, " bytes");
+    for (uint32_t byteNumber = 0; byteNumber < payloadBytes; ++byteNumber) {
+        uint8_t byte;
+        WASM_PARSER_FAIL_IF(!parseUInt8(byte), "can't get ", byteNumber, "th data byte from ", customSectionNumber, "th custom section");
+        section.payload.uncheckedAppend(byte);
+    }
+    
+    m_result.module->customSections.uncheckedAppend(WTFMove(section));
+
+    return { };
+}
 
 } } // namespace JSC::Wasm
 
index 7e026c9..41b57fc 100644 (file)
@@ -60,6 +60,7 @@ private:
     FOR_EACH_WASM_SECTION(WASM_SECTION_DECLARE_PARSER)
 #undef WASM_SECTION_DECLARE_PARSER
 
+    PartialResult WARN_UNUSED_RETURN parseCustom(uint32_t);
     PartialResult WARN_UNUSED_RETURN parseGlobalType(Global&);
     PartialResult WARN_UNUSED_RETURN parseMemoryHelper(bool isImport);
     PartialResult WARN_UNUSED_RETURN parseTableHelper(bool isImport);
index 2bb2b5e..b87acab 100644 (file)
 
 #if ENABLE(WEBASSEMBLY)
 
+#include "ArrayBuffer.h"
 #include "FunctionPrototype.h"
+#include "JSArrayBuffer.h"
 #include "JSCInlines.h"
+#include "JSWebAssemblyModule.h"
+
+namespace JSC {
+static EncodedJSValue JSC_HOST_CALL webAssemblyModuleProtoCustomSections(ExecState*);
+}
 
 #include "WebAssemblyModulePrototype.lut.h"
 
@@ -39,9 +46,45 @@ const ClassInfo WebAssemblyModulePrototype::s_info = { "WebAssembly.Module.proto
 
 /* Source for WebAssemblyModulePrototype.lut.h
  @begin prototypeTableWebAssemblyModule
+ customSections webAssemblyModuleProtoCustomSections DontEnum|Function 1
  @end
  */
 
+EncodedJSValue JSC_HOST_CALL webAssemblyModuleProtoCustomSections(ExecState* exec)
+{
+    VM& vm = exec->vm();
+    auto* globalObject = exec->lexicalGlobalObject();
+    auto throwScope = DECLARE_THROW_SCOPE(vm);
+
+    JSWebAssemblyModule* module = jsDynamicCast<JSWebAssemblyModule*>(exec->thisValue());
+    if (!module)
+        throwException(exec, throwScope, createTypeError(exec, ASCIILiteral("WebAssembly.Module.prototype.customSections called with non WebAssembly.Module |this| value")));
+    RETURN_IF_EXCEPTION(throwScope, { });
+
+    const String sectionNameString = exec->argument(0).getString(exec);
+    RETURN_IF_EXCEPTION(throwScope, { });
+
+    JSArray* result = constructEmptyArray(exec, nullptr, globalObject);
+    RETURN_IF_EXCEPTION(throwScope, { });
+
+    const auto& customSections = module->moduleInformation().customSections;
+    for (const Wasm::CustomSection& section : customSections) {
+        if (section.name == sectionNameString) {
+            auto buffer = ArrayBuffer::tryCreate(section.payload.data(), section.payload.size());
+            if (!buffer)
+                throwException(exec, throwScope, createOutOfMemoryError(exec));
+
+            Structure* arrayBufferStructure = InternalFunction::createSubclassStructure(exec, JSValue(), globalObject->arrayBufferStructure(ArrayBufferSharingMode::Default));
+            RETURN_IF_EXCEPTION(throwScope, { });
+
+            result->push(exec, JSArrayBuffer::create(vm, arrayBufferStructure, WTFMove(buffer)));
+            RETURN_IF_EXCEPTION(throwScope, { });
+        }
+    }
+
+    return JSValue::encode(result);
+}
+
 WebAssemblyModulePrototype* WebAssemblyModulePrototype::create(VM& vm, JSGlobalObject*, Structure* structure)
 {
     auto* object = new (NotNull, allocateCell<WebAssemblyModulePrototype>(vm.heap)) WebAssemblyModulePrototype(vm, structure);