From 18bb4b806d9341ecb62ff2786d1ae84f00e9ccd4 Mon Sep 17 00:00:00 2001 From: Sven Sauleau Date: Fri, 1 Feb 2019 19:32:12 +0100 Subject: [PATCH] feat: wip --- packages/ast/src/types/basic.js | 2 + packages/helper-wasm-bytecode/src/index.js | 2 + packages/helper-wasm-bytecode/src/section.js | 3 ++ packages/helper-wasm-section/src/create.js | 25 ++++++--- packages/helper-wasm-section/test/index.js | 23 +++++++++ packages/wasm-edit/src/apply.js | 8 ++- packages/wasm-edit/test/insert-node.js | 37 ++++++++++++-- packages/wasm-gen/src/encoder/index.js | 46 ++++++++++++++++- packages/wasm-gen/src/index.js | 7 +++ packages/wasm-gen/test/index.js | 54 +++++++++++++++++++- packages/wasm-parser/src/decoder.js | 22 ++++++-- 11 files changed, 212 insertions(+), 17 deletions(-) diff --git a/packages/ast/src/types/basic.js b/packages/ast/src/types/basic.js index 8b7a2f92d..3265bd609 100644 --- a/packages/ast/src/types/basic.js +++ b/packages/ast/src/types/basic.js @@ -5,6 +5,8 @@ type Byte = Number; type SectionName = | "custom" + | "custom:name" + | "custom:producers" | "type" | "import" | "func" diff --git a/packages/helper-wasm-bytecode/src/index.js b/packages/helper-wasm-bytecode/src/index.js index 41bb3174d..bbb19f2e1 100644 --- a/packages/helper-wasm-bytecode/src/index.js +++ b/packages/helper-wasm-bytecode/src/index.js @@ -87,6 +87,8 @@ const importTypes = { const sections = { custom: 0, + "custom:name": 0, + "custom:producers": 0, type: 1, import: 2, func: 3, diff --git a/packages/helper-wasm-bytecode/src/section.js b/packages/helper-wasm-bytecode/src/section.js index 3b12b1117..aea1f67cc 100644 --- a/packages/helper-wasm-bytecode/src/section.js +++ b/packages/helper-wasm-bytecode/src/section.js @@ -26,6 +26,9 @@ export function getSectionForNode(n: Node): ?SectionName { case "Global": return "global"; + case "ProducerMetadata": + return "custom:producers"; + // No section default: return; diff --git a/packages/helper-wasm-section/src/create.js b/packages/helper-wasm-section/src/create.js index 1e7fd40ae..12c5f360b 100644 --- a/packages/helper-wasm-section/src/create.js +++ b/packages/helper-wasm-section/src/create.js @@ -1,6 +1,7 @@ // @flow -import { encodeNode } from "@webassemblyjs/wasm-gen"; +import { encodeNode, encodeUTF8Vec } from "@webassemblyjs/wasm-gen"; +import { assert } from "mamacro"; import { overrideBytesInBuffer } from "@webassemblyjs/helper-buffer"; import constants from "@webassemblyjs/helper-wasm-bytecode"; import * as t from "@webassemblyjs/ast"; @@ -61,15 +62,20 @@ export function createEmptySection( end = start; } - // section id - start += 1; + // 1 byte for the empty vector + let size = 1; + + if (section === "custom:producers") { + size += encodeUTF8Vec("producers").length; + } + assert(section !== "custom:name", "not implemented"); + + // the section starts after its header + start += size; const sizeStartLoc = { line: -1, column: start }; const sizeEndLoc = { line: -1, column: start + 1 }; - // 1 byte for the empty vector - const size = t.withLoc(t.numberLiteralFromRaw(1), sizeEndLoc, sizeStartLoc); - const vectorOfSizeStartLoc = { line: -1, column: sizeEndLoc.column }; const vectorOfSizeEndLoc = { line: -1, column: sizeEndLoc.column + 1 }; @@ -79,7 +85,12 @@ export function createEmptySection( vectorOfSizeStartLoc ); - const sectionMetadata = t.sectionMetadata(section, start, size, vectorOfSize); + const sectionMetadata = t.sectionMetadata( + section, + start, + t.withLoc(t.numberLiteralFromRaw(size), sizeEndLoc, sizeStartLoc), + vectorOfSize + ); const sectionBytes = encodeNode(sectionMetadata); diff --git a/packages/helper-wasm-section/test/index.js b/packages/helper-wasm-section/test/index.js index 001f832d8..8f23cd8b0 100644 --- a/packages/helper-wasm-section/test/index.js +++ b/packages/helper-wasm-section/test/index.js @@ -54,6 +54,8 @@ describe("create", () => { assert.equal(res.sectionMetadata.vectorOfSize.value, 0); assert.equal(res.sectionMetadata.vectorOfSize.loc.start.column, 10); assert.equal(res.sectionMetadata.vectorOfSize.loc.end.column, 11); + + assert.isUndefined(res.uint8Buffer[res.startOffset]); }); it("should create an section and preserve section order", () => { @@ -102,6 +104,27 @@ describe("create", () => { assert.equal(12, getSectionMetadata(ast, "global").startOffset); assert.equal(9, getSectionMetadata(ast, "type").startOffset); }); + + it("should create a producers custom section", () => { + const sectionName = "custom:producers"; + + const actual = new Uint8Array(makeBuffer(encodeHeader(), encodeVersion(1))); + const ast = decode(actual); + const res = createEmptySection(ast, actual, sectionName); + + const data = getSectionMetadata(ast, sectionName); + + assert.isNotNull(data); + assert.equal(19, data.startOffset); + assert.equal(0, data.vectorOfSize.value); + assert.equal(11, data.size.value); + + assert.isUndefined(res.uint8Buffer[res.startOffset]); + }); + + it.skip("should create a producers custom section after the name custom section", () => { + // FIXME(sven): implement it + }); }); describe("resize", () => { diff --git a/packages/wasm-edit/src/apply.js b/packages/wasm-edit/src/apply.js index c6b9a4237..3381d5ff0 100644 --- a/packages/wasm-edit/src/apply.js +++ b/packages/wasm-edit/src/apply.js @@ -189,7 +189,7 @@ function applyAdd(ast: Program, uint8Buffer: Uint8Array, node: Node): State { let sectionMetadata = getSectionMetadata(ast, sectionName); // Section doesn't exists, we create an empty one - if (typeof sectionMetadata === "undefined") { + if (sectionMetadata === undefined) { const res = createEmptySection(ast, uint8Buffer, sectionName); uint8Buffer = res.uint8Buffer; @@ -225,6 +225,12 @@ function applyAdd(ast: Program, uint8Buffer: Uint8Array, node: Node): State { */ const deltaBytes = newByteArray.length; + console.log("__________________________________________"); + require("@webassemblyjs/wasm-parser").decode(uint8Buffer, { dump: true }); + console.log("__________________________________________"); + + console.log(start, end, JSON.stringify(newByteArray)); + uint8Buffer = overrideBytesInBuffer(uint8Buffer, start, end, newByteArray); node.loc = { diff --git a/packages/wasm-edit/test/insert-node.js b/packages/wasm-edit/test/insert-node.js index fa49bb1de..a22aff788 100644 --- a/packages/wasm-edit/test/insert-node.js +++ b/packages/wasm-edit/test/insert-node.js @@ -13,6 +13,10 @@ const { fromHexdump } = require("@webassemblyjs/helper-buffer"); const { add } = require("../lib"); +function nameFromStr(str) { + return [str.length].concat(str.split("").map(s => s.charCodeAt(0))); +} + describe("insert a node", () => { describe("ModuleImport", () => { // (module @@ -306,10 +310,8 @@ describe("insert a node", () => { it("should insert type instructions with LEB128 padded type section size", () => { const functype = t.typeInstruction(undefined, t.signature([], [])); - let bin; - // (module) - bin = fromHexdump(` + let bin = fromHexdump(` 00000000 00 61 73 6d 01 00 00 00 01 81 80 80 80 00 00 00000010 06 81 80 80 80 00 `); @@ -331,4 +333,33 @@ describe("insert a node", () => { compareArrayBuffers(bin, expected); }); + + describe("producers section", () => { + it("should insert a new entry and create section", () => { + const producer = t.producerMetadata( + [t.producerMetadataVersionedName("n1", "v1")], // language + [t.producerMetadataVersionedName("n2", "")], // processed-by + [] // sdk + ); + + // (module) + let bin = makeBuffer(encodeHeader(), encodeVersion(1)); + console.log("before"); + require("@webassemblyjs/wasm-parser").decode(bin, { dump: true }); + bin = add(bin, [producer]); + console.log("after"); + require("@webassemblyjs/wasm-parser").decode(bin, { dump: true }); + return; + + // (module) + const expected = makeBuffer( + encodeHeader(), + encodeVersion(1), + [constants.sections.custom, 0x01, ...nameFromStr("producers")], + [0] + ); + + compareArrayBuffers(bin, expected); + }); + }); }); diff --git a/packages/wasm-gen/src/encoder/index.js b/packages/wasm-gen/src/encoder/index.js index 631775d57..5e7e1db12 100644 --- a/packages/wasm-gen/src/encoder/index.js +++ b/packages/wasm-gen/src/encoder/index.js @@ -165,7 +165,21 @@ export function encodeSectionMetadata(n: SectionMetadata): Array { out.push(sectionId); out.push(...encodeU32(n.size.value)); - out.push(...encodeU32(n.vectorOfSize.value)); + + /** + * If it's a custom section emit the name + * If it's a regular section emit the size of the vector of elements + */ + switch (n.section) { + case "custom:name": + out.push(...encodeUTF8Vec("name")); + break; + case "custom:producers": + out.push(...encodeUTF8Vec("producers")); + break; + default: + out.push(...encodeU32(n.vectorOfSize.value)); + } return out; } @@ -366,3 +380,33 @@ export function encodeElem(n: Elem): Array { return out; } + +export function encodeProducerMetadataVersionedName( + n: ProducerMetadataVersionedName +): Array { + return [...encodeUTF8Vec(n.name), ...encodeUTF8Vec(n.version)]; +} + +export function encodeProducerMetadata(n: ProducerMetadata): Array { + const out = []; + + out.push(...encodeUTF8Vec("language")); + out.push(...encodeU32(n.language.length)); + n.language.forEach(l => { + out.push(...encodeProducerMetadataVersionedName(l)); + }); + + out.push(...encodeUTF8Vec("processed-by")); + out.push(...encodeU32(n.processedBy.length)); + n.processedBy.forEach(l => { + out.push(...encodeProducerMetadataVersionedName(l)); + }); + + out.push(...encodeUTF8Vec("sdk")); + out.push(...encodeU32(n.sdk.length)); + n.sdk.forEach(l => { + out.push(...encodeProducerMetadataVersionedName(l)); + }); + + return out; +} diff --git a/packages/wasm-gen/src/index.js b/packages/wasm-gen/src/index.js index bec28efdd..7b77140e6 100644 --- a/packages/wasm-gen/src/index.js +++ b/packages/wasm-gen/src/index.js @@ -47,6 +47,12 @@ export function encodeNode(n: Node): Array { case "Elem": return encoder.encodeElem(n); + case "ProducerMetadataVersionedName": + return encoder.encodeProducerMetadataVersionedName(n); + + case "ProducerMetadata": + return encoder.encodeProducerMetadata(n); + default: throw new Error( "Unsupported encoding for node of type: " + JSON.stringify(n.type) @@ -55,3 +61,4 @@ export function encodeNode(n: Node): Array { } export const encodeU32 = encoder.encodeU32; +export const encodeUTF8Vec = encoder.encodeUTF8Vec; diff --git a/packages/wasm-gen/test/index.js b/packages/wasm-gen/test/index.js index 4d0909802..fd7c3b05c 100644 --- a/packages/wasm-gen/test/index.js +++ b/packages/wasm-gen/test/index.js @@ -4,6 +4,9 @@ const t = require("@webassemblyjs/ast"); const { encodeNode } = require("../lib"); const encoder = require("../lib/encoder"); +function nameFromStr(str) { + return [str.length].concat(str.split("").map(s => s.charCodeAt(0))); +} function callIndirectInstructionIndex(index) { return { type: "CallIndirectInstruction", @@ -80,13 +83,13 @@ const fixtures = [ }, { + name: "an empty ImportSection", node: t.sectionMetadata( "import", 0, t.numberLiteralFromRaw(1), t.numberLiteralFromRaw(0) ), - name: "an empty ImportSection", expected: [0x02, 0x01, 0x00] }, @@ -263,7 +266,7 @@ const fixtures = [ 0x39 ]) ) - } + }, // TODO(sven): utf8 encoder fails here // { @@ -271,6 +274,53 @@ const fixtures = [ // node: t.stringLiteral("🤣见見"), // expected: [10, 0xf0, 0x9f, 0xa4, 0xa3, 0xe8, 0xa7, 0x81, 0xe8, 0xa6, 0x8b] // } + + { + name: "producerMetadata", + node: t.producerMetadata( + [t.producerMetadataVersionedName("n1", "v1")], + [t.producerMetadataVersionedName("n2", "")], + [] + ), + expected: [].concat([ + ...nameFromStr("language"), // field_name + 1, // field_value_count + + // field_values + ...nameFromStr("n1"), // name + ...nameFromStr("v1"), // version + + ...nameFromStr("processed-by"), // field_name + 1, // field_value_count + + // field_values + ...nameFromStr("n2"), // name + ...nameFromStr(""), // version + + ...nameFromStr("sdk"), // field_name + 0 // field_value_count + ]) + }, + + { + name: "producerMetadataVersionedName", + node: t.producerMetadataVersionedName("a", "b"), + expected: [].concat.apply( + [0x01, 97], // name + [0x01, 98] // version + ) + }, + + { + name: "Producers empty section", + node: t.sectionMetadata( + "custom:producers", + 0, // start + t.numberLiteralFromRaw(9), // size + t.numberLiteralFromRaw(0) // vectorOfSize + ), + expected: [0x0, 9, ...nameFromStr("producers")] + } ]; describe("wasm gen", () => { diff --git a/packages/wasm-parser/src/decoder.js b/packages/wasm-parser/src/decoder.js index 47c2fb4aa..2beddbdae 100644 --- a/packages/wasm-parser/src/decoder.js +++ b/packages/wasm-parser/src/decoder.js @@ -1797,9 +1797,7 @@ export function decode(ab: ArrayBuffer, opts: DecoderOpts): Program { dump([sectionId], "section code"); dump([sectionSizeInBytes], "section size"); - const metadata = [ - t.sectionMetadata("custom", startOffset, sectionSizeInBytesNode) - ]; + const metadata = []; const sectionName = readUTF8String(); eatBytes(sectionName.nextIndex); @@ -1810,6 +1808,13 @@ export function decode(ab: ArrayBuffer, opts: DecoderOpts): Program { if (sectionName.value === "name") { try { + metadata.push( + t.sectionMetadata( + "custom:name", + startOffset, + sectionSizeInBytesNode + ) + ); metadata.push(...parseNameSection(remainingBytes)); } catch (e) { console.warn( @@ -1822,6 +1827,13 @@ export function decode(ab: ArrayBuffer, opts: DecoderOpts): Program { } } else if (sectionName.value === "producers") { try { + metadata.push( + t.sectionMetadata( + "custom:producers", + startOffset, + sectionSizeInBytesNode + ) + ); metadata.push(parseProducersSection(remainingBytes)); } catch (e) { console.warn( @@ -1833,6 +1845,10 @@ export function decode(ab: ArrayBuffer, opts: DecoderOpts): Program { eatBytes(remainingBytes); } } else { + metadata.push( + t.sectionMetadata("custom", startOffset, sectionSizeInBytesNode) + ); + // We don't parse the custom section eatBytes(remainingBytes);