Commit 41aa8a60 authored by Clemens Hammacher's avatar Clemens Hammacher Committed by Commit Bot

[wasm] Speed up WasmModuleBuilder

Emitting bytes to the Uint8Array directly speeds up generation of
binaries enormously.
On the limits-any.js spec test (which creates huge modules), the
execution time of an optdebug build reduces from 286 seconds to 61
seconds.

R=titzer@chromium.org
CC=​ahaas@chromium.org, ssauleau@igalia.com

Change-Id: I5b473b7dc7b0853e54d2406f3db3658bb2abed40
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1508352Reviewed-by: 's avatarBen Titzer <titzer@chromium.org>
Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Commit-Queue: Clemens Hammacher <clemensh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60096}
parent e762b7db
......@@ -9,11 +9,11 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
(function TestAsyncCompileMultipleCodeSections() {
let binary = new Binary();
binary.emit_header();
binary.push(kTypeSectionCode, 4, 1, kWasmFunctionTypeForm, 0, 0);
binary.push(kFunctionSectionCode, 2, 1, 0);
binary.push(kCodeSectionCode, 6, 1, 4, 0, kExprGetLocal, 0, kExprEnd);
binary.push(kCodeSectionCode, 6, 1, 4, 0, kExprGetLocal, 0, kExprEnd);
let buffer = Uint8Array.from(binary).buffer;
binary.emit_bytes([kTypeSectionCode, 4, 1, kWasmFunctionTypeForm, 0, 0]);
binary.emit_bytes([kFunctionSectionCode, 2, 1, 0]);
binary.emit_bytes([kCodeSectionCode, 6, 1, 4, 0, kExprGetLocal, 0, kExprEnd]);
binary.emit_bytes([kCodeSectionCode, 6, 1, 4, 0, kExprGetLocal, 0, kExprEnd]);
let buffer = binary.trunc_buffer();
assertPromiseResult(WebAssembly.compile(buffer), assertUnreachable,
e => assertInstanceof(e, WebAssembly.CompileError));
})();
......@@ -20,37 +20,26 @@ function module(bytes) {
return new WebAssembly.Module(buffer);
}
function toBuffer(binary) {
let buffer = new ArrayBuffer(binary.length);
let view = new Uint8Array(buffer);
for (let i = 0; i < binary.length; i++) {
let val = binary[i];
if ((typeof val) == 'string') val = val.charCodeAt(0);
view[i] = val | 0;
}
return buffer;
}
function testErrorPosition(bytes, pos, test_name) {
assertThrowsAsync(
WebAssembly.compile(toBuffer(bytes)), WebAssembly.CompileError,
WebAssembly.compile(bytes.trunc_buffer()), WebAssembly.CompileError,
new RegExp('@\\+' + pos));
}
(function testInvalidMagic() {
let bytes = new Binary;
bytes.push(
kWasmH0, kWasmH1 + 1, kWasmH2, kWasmH3, kWasmV0, kWasmV1, kWasmV2,
kWasmV3);
bytes.emit_bytes([
kWasmH0, kWasmH1 + 1, kWasmH2, kWasmH3, kWasmV0, kWasmV1, kWasmV2, kWasmV3
]);
// Error at pos==0 because that's where the magic word is.
testErrorPosition(bytes, 0, 'testInvalidMagic');
})();
(function testInvalidVersion() {
let bytes = new Binary;
bytes.push(
kWasmH0, kWasmH1, kWasmH2, kWasmH3, kWasmV0, kWasmV1 + 1, kWasmV2,
kWasmV3);
bytes.emit_bytes([
kWasmH0, kWasmH1, kWasmH2, kWasmH3, kWasmV0, kWasmV1 + 1, kWasmV2, kWasmV3
]);
// Error at pos==4 because that's where the version word is.
testErrorPosition(bytes, 4, 'testInvalidVersion');
})();
......@@ -59,7 +48,7 @@ function testErrorPosition(bytes, pos, test_name) {
let bytes = new Binary;
bytes.emit_header();
bytes.emit_u8(kTypeSectionCode);
bytes.push(0x80, 0x80, 0x80, 0x80, 0x80, 0x00);
bytes.emit_bytes([0x80, 0x80, 0x80, 0x80, 0x80, 0x00]);
let pos = bytes.length - 1 - 1;
testErrorPosition(bytes, pos, 'testSectionLengthInvalidVarint');
})();
......@@ -76,22 +65,22 @@ function testErrorPosition(bytes, pos, test_name) {
(function testFunctionsCountInvalidVarint() {
let bytes = new Binary;
bytes.emit_header();
bytes.push(
bytes.emit_bytes([
kTypeSectionCode, // section id
1, // section length
0 // number of types
);
bytes.push(
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
1, // section length
0 // number of functions
);
bytes.push(
]);
bytes.emit_bytes([
kCodeSectionCode, // section id
20, // section length (arbitrary value > 6)
);
]);
// Functions count
bytes.push(0x80, 0x80, 0x80, 0x80, 0x80, 0x00);
bytes.emit_bytes([0x80, 0x80, 0x80, 0x80, 0x80, 0x00]);
let pos = bytes.length - 1 - 1;
testErrorPosition(bytes, pos, 'testFunctionsCountInvalidVarint');
......@@ -100,20 +89,20 @@ function testErrorPosition(bytes, pos, test_name) {
(function testFunctionsCountTooBig() {
let bytes = new Binary;
bytes.emit_header();
bytes.push(
bytes.emit_bytes([
kTypeSectionCode, // section id
1, // section length
0 // number of types
);
bytes.push(
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
1, // section length
0 // number of functions
);
bytes.push(
]);
bytes.emit_bytes([
kCodeSectionCode, // section id
20, // section length (arbitrary value > 6)
);
]);
// Functions count
bytes.emit_u32v(0xffffff23);
......@@ -124,20 +113,20 @@ function testErrorPosition(bytes, pos, test_name) {
(function testFunctionsCountDoesNotMatch() {
let bytes = new Binary;
bytes.emit_header();
bytes.push(
bytes.emit_bytes([
kTypeSectionCode, // section id
1, // section length
0 // number of types
);
bytes.push(
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
1, // section length
0 // number of functions
);
bytes.push(
]);
bytes.emit_bytes([
kCodeSectionCode, // section id
20, // section length (arbitrary value > 6)
);
]);
// Functions count (different than the count in the functions section.
bytes.emit_u32v(5);
......@@ -148,27 +137,27 @@ function testErrorPosition(bytes, pos, test_name) {
(function testBodySizeInvalidVarint() {
let bytes = new Binary;
bytes.emit_header();
bytes.push(
bytes.emit_bytes([
kTypeSectionCode, // section id
4, // section length
1, // number of types
kWasmFunctionTypeForm, // type
0, // number of parameter
0 // number of returns
);
bytes.push(
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
2, // section length
1, // number of functions
0 // signature index
);
bytes.push(
]);
bytes.emit_bytes([
kCodeSectionCode, // section id
20, // section length (arbitrary value > 6)
1 // functions count
);
]);
// Invalid function body size.
bytes.push(0x80, 0x80, 0x80, 0x80, 0x80, 0x00);
bytes.emit_bytes([0x80, 0x80, 0x80, 0x80, 0x80, 0x00]);
let pos = bytes.length - 1 - 1;
testErrorPosition(bytes, pos, 'testBodySizeInvalidVarint');
......@@ -177,25 +166,25 @@ function testErrorPosition(bytes, pos, test_name) {
(function testBodySizeTooBig() {
let bytes = new Binary;
bytes.emit_header();
bytes.push(
bytes.emit_bytes([
kTypeSectionCode, // section id
4, // section length
1, // number of types
kWasmFunctionTypeForm, // type
0, // number of parameter
0 // number of returns
);
bytes.push(
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
2, // section length
1, // number of functions
0 // signature index
);
bytes.push(
]);
bytes.emit_bytes([
kCodeSectionCode, // section id
20, // section length (arbitrary value > 6)
1 // functions count
);
]);
// Invalid function body size.
bytes.emit_u32v(0xffffff23);
......@@ -206,25 +195,25 @@ function testErrorPosition(bytes, pos, test_name) {
(function testBodySizeDoesNotFit() {
let bytes = new Binary;
bytes.emit_header();
bytes.push(
bytes.emit_bytes([
kTypeSectionCode, // section id
4, // section length
1, // number of types
kWasmFunctionTypeForm, // type
0, // number of parameter
0 // number of returns
);
bytes.push(
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
2, // section length
1, // number of functions
0 // signature index
);
bytes.push(
]);
bytes.emit_bytes([
kCodeSectionCode, // section id
20, // section length (arbitrary value > 6)
1 // functions count
);
]);
// Invalid function body size (does not fit into the code section).
bytes.emit_u32v(20);
......@@ -235,25 +224,25 @@ function testErrorPosition(bytes, pos, test_name) {
(function testBodySizeIsZero() {
let bytes = new Binary;
bytes.emit_header();
bytes.push(
bytes.emit_bytes([
kTypeSectionCode, // section id
4, // section length
1, // number of types
kWasmFunctionTypeForm, // type
0, // number of parameter
0 // number of returns
);
bytes.push(
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
2, // section length
1, // number of functions
0 // signature index
);
bytes.push(
]);
bytes.emit_bytes([
kCodeSectionCode, // section id
20, // section length (arbitrary value > 6)
1 // functions count
);
]);
// Invalid function body size (body size of 0 is invalid).
bytes.emit_u32v(0);
......@@ -264,28 +253,28 @@ function testErrorPosition(bytes, pos, test_name) {
(function testStaleCodeSectionBytes() {
let bytes = new Binary;
bytes.emit_header();
bytes.push(
bytes.emit_bytes([
kTypeSectionCode, // section id
4, // section length
1, // number of types
kWasmFunctionTypeForm, // type
0, // number of parameter
0 // number of returns
);
bytes.push(
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
2, // section length
1, // number of functions
0 // signature index
);
bytes.push(
]);
bytes.emit_bytes([
kCodeSectionCode, // section id
20, // section length (too big)
1, // functions count
2, // body size
0, // locals count
kExprEnd // body
);
]);
let pos = bytes.length - 1;
testErrorPosition(bytes, pos, 'testStaleCodeSectionBytes');
......@@ -294,21 +283,21 @@ function testErrorPosition(bytes, pos, test_name) {
(function testInvalidCode() {
let bytes = new Binary;
bytes.emit_header();
bytes.push(
bytes.emit_bytes([
kTypeSectionCode, // section id
4, // section length
1, // number of types
kWasmFunctionTypeForm, // type
0, // number of parameter
0 // number of returns
);
bytes.push(
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
2, // section length
1, // number of functions
0 // signature index
);
bytes.push(
]);
bytes.emit_bytes([
kCodeSectionCode, // section id
6, // section length (too big)
1, // functions count
......@@ -316,7 +305,7 @@ function testErrorPosition(bytes, pos, test_name) {
0, // locals count
kExprGetLocal, 0, // Access a non-existing local
kExprEnd // --
);
]);
// Find error at the index of kExprGetLocal.
let pos = bytes.length - 1 - 1;
......@@ -326,24 +315,24 @@ function testErrorPosition(bytes, pos, test_name) {
(function testCodeSectionSizeZero() {
let bytes = new Binary;
bytes.emit_header();
bytes.push(
bytes.emit_bytes([
kTypeSectionCode, // section id
4, // section length
1, // number of types
kWasmFunctionTypeForm, // type
0, // number of parameter
0 // number of returns
);
bytes.push(
]);
bytes.emit_bytes([
kFunctionSectionCode, // section id
2, // section length
1, // number of functions
0 // signature index
);
bytes.push(
]);
bytes.emit_bytes([
kCodeSectionCode, // section id
0, // section length (too big)
);
]);
// Find error at the index of kExprGetLocal.
let pos = bytes.length - 1;
......@@ -353,7 +342,7 @@ function testErrorPosition(bytes, pos, test_name) {
(function testInvalidSection() {
let bytes = new Binary;
bytes.emit_header();
bytes.push(
bytes.emit_bytes([
kTypeSectionCode, // section id
5, // section length
1, // number of types
......@@ -361,7 +350,7 @@ function testErrorPosition(bytes, pos, test_name) {
1, // number of parameter
0x7b, // invalid type
0 // number of returns
);
]);
let pos = bytes.length - 1 - 1;
testErrorPosition(bytes, pos, 'testInvalidSection');
......
......@@ -496,51 +496,74 @@ function assertTraps(trap, code) {
assertThrows(code, WebAssembly.RuntimeError, kTrapMsgs[trap]);
}
class Binary extends Array {
class Binary {
constructor() {
this.length = 0;
this.buffer = new Uint8Array(8192);
}
ensure_space(needed) {
if (this.buffer.length - this.length >= needed) return;
let new_capacity = this.buffer.length * 2;
while (new_capacity - this.length < needed) new_capacity *= 2;
let new_buffer = new Uint8Array(new_capacity);
new_buffer.set(this.buffer);
this.buffer = new_buffer;
}
trunc_buffer() {
return this.buffer = this.buffer.slice(0, this.length);
}
emit_u8(val) {
this.push(val);
this.ensure_space(1);
this.buffer[this.length++] = val;
}
emit_u16(val) {
this.push(val & 0xff);
this.push((val >> 8) & 0xff);
this.ensure_space(2);
this.buffer[this.length++] = val;
this.buffer[this.length++] = val >> 8;
}
emit_u32(val) {
this.push(val & 0xff);
this.push((val >> 8) & 0xff);
this.push((val >> 16) & 0xff);
this.push((val >> 24) & 0xff);
this.ensure_space(4);
this.buffer[this.length++] = val;
this.buffer[this.length++] = val >> 8;
this.buffer[this.length++] = val >> 16;
this.buffer[this.length++] = val >> 24;
}
emit_u32v(val) {
this.ensure_space(5);
while (true) {
let v = val & 0xff;
val = val >>> 7;
if (val == 0) {
this.push(v);
this.buffer[this.length++] = v;
break;
}
this.push(v | 0x80);
this.buffer[this.length++] = v | 0x80;
}
}
emit_u64v(val) {
this.ensure_space(10);
while (true) {
let v = val & 0xff;
val = val >>> 7;
if (val == 0) {
this.push(v);
this.buffer[this.length++] = v;
break;
}
this.push(v | 0x80);
this.buffer[this.length++] = v | 0x80;
}
}
emit_bytes(data) {
for (let i = 0; i < data.length; i++) {
this.push(data[i] & 0xff);
}
this.ensure_space(data.length);
this.buffer.set(data, this.length);
this.length += data.length;
}
emit_string(string) {
......@@ -561,8 +584,9 @@ class Binary extends Array {
}
emit_header() {
this.push(kWasmH0, kWasmH1, kWasmH2, kWasmH3,
kWasmV0, kWasmV1, kWasmV2, kWasmV3);
this.emit_bytes([
kWasmH0, kWasmH1, kWasmH2, kWasmH3, kWasmV0, kWasmV1, kWasmV2, kWasmV3
]);
}
emit_section(section_code, content_generator) {
......@@ -575,7 +599,7 @@ class Binary extends Array {
this.emit_u32v(section.length);
// Copy the temporary buffer.
// Avoid spread because {section} can be huge.
for (let b of section) this.push(b);
this.emit_bytes(section.trunc_buffer());
}
}
......@@ -719,17 +743,18 @@ class WasmModuleBuilder {
for (var i = 0; i < name.length; i++) {
result.emit_u8(name.charCodeAt(i));
}
return result;
return result.trunc_buffer()
}
addCustomSection(name, bytes) {
name = this.stringToBytes(name);
var length = new Binary();
length.emit_u32v(name.length + bytes.length);
var section = [0, ...length, ...name];
// Avoid spread because {bytes} can be huge.
for (var b of bytes) section.push(b);
this.explicit.push(section);
var section = new Binary();
section.ensure_space(1 + 5 + 5 + name.length + bytes.length);
section.emit_u8(0);
section.emit_u32v(name.length + bytes.length);
section.emit_bytes(name);
section.emit_bytes(bytes);
this.explicit.push(section.trunc_buffer());
}
addType(type) {
......@@ -891,7 +916,7 @@ class WasmModuleBuilder {
return this;
}
toArray(debug = false) {
toUint8Array(debug = false) {
let binary = new Binary;
let wasm = this;
......@@ -1185,8 +1210,9 @@ class WasmModuleBuilder {
header.emit_u8(decl.type);
}
section.ensure_space(5 + header.length + func.body.length);
section.emit_u32v(header.length + func.body.length);
section.emit_bytes(header);
section.emit_bytes(header.trunc_buffer());
section.emit_bytes(func.body);
}
});
......@@ -1273,23 +1299,15 @@ class WasmModuleBuilder {
});
}
return binary;
return binary.trunc_buffer();
}
toBuffer(debug = false) {
let bytes = this.toArray(debug);
let buffer = new ArrayBuffer(bytes.length);
let view = new Uint8Array(buffer);
for (let i = 0; i < bytes.length; i++) {
let val = bytes[i];
if ((typeof val) == "string") val = val.charCodeAt(0);
view[i] = val | 0;
}
return buffer;
return this.toUint8Array(debug).buffer;
}
toUint8Array(debug = false) {
return new Uint8Array(this.toBuffer(debug));
toArray(debug = false) {
return Array.from(this.toUint8Array(debug));
}
instantiate(ffi) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment