author | Julian Seward <jseward@acm.org> |
Fri, 08 Jun 2018 18:37:42 +0200 | |
changeset 422043 | 710e191b76ff7f9d4afb8b6f47a51ed9b003d4d7 |
parent 422042 | 5a26880a2b24fc5a54595a3432c3484cea5d0e5f |
child 422044 | 37f874064e54635867aa54034cf9cf9bab97d0d7 |
push id | 34114 |
push user | btara@mozilla.com |
push date | Sat, 09 Jun 2018 15:31:58 +0000 |
treeherder | mozilla-central@e02a5155d815 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | lth |
bugs | 1467071 |
milestone | 62.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/src/jit-test/tests/wasm/import-export.js +++ b/js/src/jit-test/tests/wasm/import-export.js @@ -32,18 +32,18 @@ assertErrorMessage(() => new Memory({ini new Memory({initial: 0, maximum: 65536}); assertErrorMessage(() => new Memory({initial: 0, maximum: 65537}), RangeError, /bad Memory maximum size/); // Table size consistency and internal limits. assertErrorMessage(() => new Table({initial:2, maximum:1, element:"anyfunc"}), RangeError, /bad Table maximum size/); new Table({ initial: 10000000, element:"anyfunc" }); assertErrorMessage(() => new Table({initial:10000001, element:"anyfunc"}), RangeError, /bad Table initial size/); -new Table({ initial: 0, maximum: 2**32 - 1, element:"anyfunc" }); -assertErrorMessage(() => new Table({initial:0, maximum: 2**32, element:"anyfunc"}), RangeError, /bad Table maximum size/); +new Table({ initial: 0, maximum: 10000000, element:"anyfunc" }); +assertErrorMessage(() => new Table({initial:0, maximum: 10000001, element:"anyfunc"}), RangeError, /bad Table maximum size/); const m1 = new Module(wasmTextToBinary('(module (import "foo" "bar") (import "baz" "quux"))')); assertErrorMessage(() => new Instance(m1), TypeError, /second argument must be an object/); assertErrorMessage(() => new Instance(m1, {foo:null}), TypeError, /import object field 'foo' is not an Object/); assertErrorMessage(() => new Instance(m1, {foo:{bar:{}}}), LinkError, /import object field 'bar' is not a Function/); assertErrorMessage(() => new Instance(m1, {foo:{bar:()=>{}}, baz:null}), TypeError, /import object field 'baz' is not an Object/); assertErrorMessage(() => new Instance(m1, {foo:{bar:()=>{}}, baz:{}}), LinkError, /import object field 'quux' is not a Function/); assertEq(new Instance(m1, {foo:{bar:()=>{}}, baz:{quux:()=>{}}}) instanceof Instance, true);
--- a/js/src/jit-test/tests/wasm/spec/harness/wasm-module-builder.js +++ b/js/src/jit-test/tests/wasm/spec/harness/wasm-module-builder.js @@ -64,22 +64,24 @@ class Binary extends Array { this.push(kWasmH0, kWasmH1, kWasmH2, kWasmH3, kWasmV0, kWasmV1, kWasmV2, kWasmV3); } emit_section(section_code, content_generator) { // Emit section name. this.emit_u8(section_code); // Emit the section to a temporary buffer: its full length isn't know yet. - let section = new Binary; + const section = new Binary; content_generator(section); // Emit section length. this.emit_u32v(section.length); // Copy the temporary buffer. - this.push(...section); + for (const b of section) { + this.push(b); + } } } class WasmFunctionBuilder { constructor(module, name, type_index) { this.module = module; this.name = name; this.type_index = type_index; @@ -233,21 +235,32 @@ class WasmModuleBuilder { if (length > this.function_table_length) { this.function_table_length = length; } } return this; } appendToTable(array) { + for (let n of array) { + if (typeof n != 'number') + throw new Error('invalid table (entries have to be numbers): ' + array); + } return this.addFunctionTableInit(this.function_table.length, false, array); } + setFunctionTableBounds(min, max) { + this.function_table_length_min = min; + this.function_table_length_max = max; + return this; + } + setFunctionTableLength(length) { - this.function_table_length = length; + this.function_table_length_min = length; + this.function_table_length_max = length; return this; } toArray(debug = false) { let binary = new Binary; let wasm = this; // Add header @@ -315,35 +328,39 @@ class WasmModuleBuilder { has_names = has_names || (func.name != undefined && func.name.length > 0); section.emit_u32v(func.type_index); } }); } // Add function_table. - if (wasm.function_table_length > 0) { + if (wasm.function_table_length_min > 0) { if (debug) print("emitting table @ " + binary.length); binary.emit_section(kTableSectionCode, section => { section.emit_u8(1); // one table entry section.emit_u8(kWasmAnyFunctionTypeForm); - section.emit_u8(1); - section.emit_u32v(wasm.function_table_length); - section.emit_u32v(wasm.function_table_length); + const max = wasm.function_table_length_max; + const has_max = max !== undefined; + section.emit_u8(has_max ? kResizableMaximumFlag : 0); + section.emit_u32v(wasm.function_table_length_min); + if (has_max) section.emit_u32v(max); }); } // Add memory section - if (wasm.memory != undefined) { + if (wasm.memory !== undefined) { if (debug) print("emitting memory @ " + binary.length); binary.emit_section(kMemorySectionCode, section => { section.emit_u8(1); // one memory entry - section.emit_u32v(kResizableMaximumFlag); + const max = wasm.memory.max; + const has_max = max !== undefined; + section.emit_u32v(has_max ? kResizableMaximumFlag : 0); section.emit_u32v(wasm.memory.min); - section.emit_u32v(wasm.memory.max); + if (has_max) section.emit_u32v(max); }); } // Add global section. if (wasm.globals.length > 0) { if (debug) print ("emitting globals @ " + binary.length); binary.emit_section(kGlobalSectionCode, section => { section.emit_u32v(wasm.globals.length); @@ -421,19 +438,19 @@ class WasmModuleBuilder { } // Add table elements. if (wasm.function_table_inits.length > 0) { if (debug) print("emitting table @ " + binary.length); binary.emit_section(kElementSectionCode, section => { var inits = wasm.function_table_inits; section.emit_u32v(inits.length); - section.emit_u8(0); // table index for (let init of inits) { + section.emit_u8(0); // table index if (init.is_global) { section.emit_u8(kExprGetGlobal); } else { section.emit_u8(kExprI32Const); } section.emit_u32v(init.base); section.emit_u8(kExprEnd); section.emit_u32v(init.array.length);
--- a/js/src/jit-test/tests/wasm/spec/jsapi.js +++ b/js/src/jit-test/tests/wasm/spec/jsapi.js @@ -552,17 +552,17 @@ test(() => { assertThrows(() => new Table({initial:{valueOf() { throw new Error("here")}}, element:"anyfunc"}), Error); assertThrows(() => new Table({initial:-1, element:"anyfunc"}), RangeError); assertThrows(() => new Table({initial:Math.pow(2,32), element:"anyfunc"}), RangeError); assertThrows(() => new Table({initial:2, maximum:1, element:"anyfunc"}), RangeError); assertThrows(() => new Table({initial:2, maximum:Math.pow(2,32), element:"anyfunc"}), RangeError); assert_equals(new Table({initial:1, element:"anyfunc"}) instanceof Table, true); assert_equals(new Table({initial:1.5, element:"anyfunc"}) instanceof Table, true); assert_equals(new Table({initial:1, maximum:1.5, element:"anyfunc"}) instanceof Table, true); - assert_equals(new Table({initial:1, maximum:Math.pow(2,32)-1, element:"anyfunc"}) instanceof Table, true); + assertThrows(() => new Table({initial:1, maximum:Math.pow(2,32)-1, element:"anyfunc"}), RangeError); }, "'WebAssembly.Table' constructor function"); test(() => { const tableProtoDesc = Object.getOwnPropertyDescriptor(Table, 'prototype'); assert_equals(typeof tableProtoDesc.value, "object"); assert_equals(tableProtoDesc.writable, false); assert_equals(tableProtoDesc.enumerable, false); assert_equals(tableProtoDesc.configurable, false);
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/wasm/spec/limits.js @@ -0,0 +1,380 @@ +// |jit-test| slow; + +/* + * Copyright 2018 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +let kJSEmbeddingMaxTypes = 1000000; +let kJSEmbeddingMaxFunctions = 1000000; +let kJSEmbeddingMaxImports = 100000; +let kJSEmbeddingMaxExports = 100000; +let kJSEmbeddingMaxGlobals = 1000000; +let kJSEmbeddingMaxDataSegments = 100000; + +let kJSEmbeddingMaxMemoryPages = 65536; +let kJSEmbeddingMaxModuleSize = 1024 * 1024 * 1024; // = 1 GiB +let kJSEmbeddingMaxFunctionSize = 7654321; +let kJSEmbeddingMaxFunctionLocals = 50000; +let kJSEmbeddingMaxFunctionParams = 1000; +let kJSEmbeddingMaxFunctionReturns = 1; +let kJSEmbeddingMaxTableSize = 10000000; +let kJSEmbeddingMaxTableEntries = 10000000; +let kJSEmbeddingMaxTables = 1; +let kJSEmbeddingMaxMemories = 1; + +function verbose(...args) { + if (false) print(...args); +} + +let kTestValidate = true; +let kTestSyncCompile = true; +let kTestAsyncCompile = true; + +//======================================================================= +// HARNESS SNIPPET, DO NOT COMMIT +//======================================================================= +const known_failures = { + // Enter failing tests like follows: + // "'WebAssembly.Instance.prototype.exports' accessor property": + // 'https://bugs.chromium.org/p/v8/issues/detail?id=5507', +}; + +let failures = []; +let unexpected_successes = []; + +let last_promise = new Promise((resolve, reject) => { resolve(); }); + +function test(func, description) { + let maybeErr; + try { func(); } + catch(e) { maybeErr = e; } + if (typeof maybeErr !== 'undefined') { + var known = ""; + if (known_failures[description]) { + known = " (known)"; + } + print(`${description}: FAIL${known}. ${maybeErr}`); + failures.push(description); + } else { + if (known_failures[description]) { + unexpected_successes.push(description); + } + print(`${description}: PASS.`); + } +} + +function promise_test(func, description) { + last_promise = last_promise.then(func) + .then(_ => { + if (known_failures[description]) { + unexpected_successes.push(description); + } + print(`${description}: PASS.`); + }) + .catch(err => { + var known = ""; + if (known_failures[description]) { + known = " (known)"; + } + print(`${description}: FAIL${known}. ${err}`); + failures.push(description); + }); +} +//======================================================================= + +function testLimit(name, min, limit, gen) { + print(""); + print(`==== Test ${name} limit = ${limit} ====`); + function run_validate(count) { + let expected = count <= limit; + verbose(` ${expected ? "(expect ok) " : "(expect fail)"} = ${count}...`); + + // TODO(titzer): builder is slow for large modules; make manual? + let builder = new WasmModuleBuilder(); + gen(builder, count); + let result = WebAssembly.validate(builder.toBuffer()); + + if (result != expected) { + let msg = `UNEXPECTED ${expected ? "FAIL" : "PASS"}: ${name} == ${count}`; + verbose(`=====> ${msg}`); + throw new Error(msg); + } + } + + function run_compile(count) { + let expected = count <= limit; + verbose(` ${expected ? "(expect ok) " : "(expect fail)"} = ${count}...`); + + // TODO(titzer): builder is slow for large modules; make manual? + let builder = new WasmModuleBuilder(); + gen(builder, count); + try { + let result = new WebAssembly.Module(builder.toBuffer()); + } catch (e) { + if (expected) { + let msg = `UNEXPECTED FAIL: ${name} == ${count} (${e})`; + verbose(`=====> ${msg}`); + throw new Error(msg); + } + return; + } + if (!expected) { + let msg = `UNEXPECTED PASS: ${name} == ${count}`; + verbose(`=====> ${msg}`); + throw new Error(msg); + } + } + + function run_async_compile(count) { + let expected = count <= limit; + verbose(` ${expected ? "(expect ok) " : "(expect fail)"} = ${count}...`); + + // TODO(titzer): builder is slow for large modules; make manual? + let builder = new WasmModuleBuilder(); + gen(builder, count); + let buffer = builder.toBuffer(); + WebAssembly.compile(buffer) + .then(result => { + if (!expected) { + let msg = `UNEXPECTED PASS: ${name} == ${count}`; + verbose(`=====> ${msg}`); + throw new Error(msg); + } + }) + .catch(err => { + if (expected) { + let msg = `UNEXPECTED FAIL: ${name} == ${count} (${e})`; + verbose(`=====> ${msg}`); + throw new Error(msg); + } + }) + } + + if (kTestValidate) { + print(""); + test(() => { + run_validate(min); + }, `Validate ${name} mininum`); + print(""); + test(() => { + run_validate(limit); + }, `Validate ${name} limit`); + print(""); + test(() => { + run_validate(limit+1); + }, `Validate ${name} over limit`); + } + + if (kTestSyncCompile) { + print(""); + test(() => { + run_compile(min); + }, `Compile ${name} mininum`); + print(""); + test(() => { + run_compile(limit); + }, `Compile ${name} limit`); + print(""); + test(() => { + run_compile(limit+1); + }, `Compile ${name} over limit`); + } + + if (kTestAsyncCompile) { + print(""); + promise_test(() => { + run_async_compile(min); + }, `Async compile ${name} mininum`); + print(""); + promise_test(() => { + run_async_compile(limit); + }, `Async compile ${name} limit`); + print(""); + promise_test(() => { + run_async_compile(limit+1); + }, `Async compile ${name} over limit`); + } +} + +// A little doodad to disable a test easily +let DISABLED = {testLimit: () => 0}; +let X = DISABLED; + +// passes +testLimit("types", 1, kJSEmbeddingMaxTypes, (builder, count) => { + for (let i = 0; i < count; i++) { + builder.addType(kSig_i_i); + } + }); + +// passes +testLimit("functions", 1, kJSEmbeddingMaxFunctions, (builder, count) => { + let type = builder.addType(kSig_v_v); + let body = [kExprEnd]; + for (let i = 0; i < count; i++) { + builder.addFunction(/*name=*/ undefined, type).addBody(body); + } + }); + +// passes +testLimit("imports", 1, kJSEmbeddingMaxImports, (builder, count) => { + let type = builder.addType(kSig_v_v); + for (let i = 0; i < count; i++) { + builder.addImport("", "", type); + } + }); + +// passes +testLimit("exports", 1, kJSEmbeddingMaxExports, (builder, count) => { + let type = builder.addType(kSig_v_v); + let f = builder.addFunction(/*name=*/ undefined, type); + f.addBody([kExprEnd]); + for (let i = 0; i < count; i++) { + builder.addExport("f" + i, f.index); + } + }); + +// passes +testLimit("globals", 1, kJSEmbeddingMaxGlobals, (builder, count) => { + for (let i = 0; i < count; i++) { + builder.addGlobal(kWasmI32, true); + } + }); + +// passes +testLimit("data segments", 1, kJSEmbeddingMaxDataSegments, (builder, count) => { + let data = []; + builder.addMemory(1, 1, false, false); + for (let i = 0; i < count; i++) { + builder.addDataSegment(0, data); + } + }); + +// fails +// jseward: we expect this to fail, because we support a max initial memory +// page count of 16384, whereas this expects an initial value of 63336 to be +// accepted. +X.testLimit("initial declared memory pages", 1, kJSEmbeddingMaxMemoryPages, + (builder, count) => { + builder.addMemory(count, undefined, false, false); + }); + +// passes +testLimit("maximum declared memory pages", 1, kJSEmbeddingMaxMemoryPages, + (builder, count) => { + builder.addMemory(1, count, false, false); + }); + +// fails +// jseward: we expect this to fail, because we support a max initial memory +// page count of 16384, whereas this expects an initial value of 63336 to be +// accepted. +X.testLimit("initial imported memory pages", 1, kJSEmbeddingMaxMemoryPages, + (builder, count) => { + builder.addImportedMemory("mod", "mem", count, undefined); + }); + +// passes +testLimit("maximum imported memory pages", 1, kJSEmbeddingMaxMemoryPages, + (builder, count) => { + builder.addImportedMemory("mod", "mem", 1, count); + }); + +// disabled +// TODO(titzer): ugh, that's hard to test. +DISABLED.testLimit("module size", 1, kJSEmbeddingMaxModuleSize, + (builder, count) => { + }); + +// passes +testLimit("function size", 2, kJSEmbeddingMaxFunctionSize, (builder, count) => { + let type = builder.addType(kSig_v_v); + let nops = count-2; + let array = new Array(nops); + for (let i = 0; i < nops; i++) array[i] = kExprNop; + array[nops] = kExprEnd; + builder.addFunction(undefined, type).addBody(array); + }); + +// passes +testLimit("function locals", 1, kJSEmbeddingMaxFunctionLocals, (builder, count) => { + let type = builder.addType(kSig_v_v); + builder.addFunction(undefined, type) + .addLocals({i32_count: count}) + .addBody([kExprEnd]); + }); + +// passes +testLimit("function params", 1, kJSEmbeddingMaxFunctionParams, (builder, count) => { + let array = new Array(count); + for (let i = 0; i < count; i++) array[i] = kWasmI32; + let type = builder.addType({params: array, results: []}); + }); + +// passes +testLimit("function params+locals", 1, kJSEmbeddingMaxFunctionLocals - 2, (builder, count) => { + let type = builder.addType(kSig_i_ii); + builder.addFunction(undefined, type) + .addLocals({i32_count: count}) + .addBody([kExprUnreachable, kExprEnd]); + }); + +// passes +testLimit("function returns", 0, kJSEmbeddingMaxFunctionReturns, (builder, count) => { + let array = new Array(count); + for (let i = 0; i < count; i++) array[i] = kWasmI32; + let type = builder.addType({params: [], results: array}); + }); + +// passes +testLimit("initial table size", 1, kJSEmbeddingMaxTableSize, (builder, count) => { + builder.setFunctionTableBounds(count, undefined); + }); + +// passes +testLimit("maximum table size", 1, kJSEmbeddingMaxTableSize, (builder, count) => { + builder.setFunctionTableBounds(1, count); + }); + +// passes +testLimit("table entries", 1, kJSEmbeddingMaxTableEntries, (builder, count) => { + builder.setFunctionTableBounds(1, 1); + let array = []; + for (let i = 0; i < count; i++) { + builder.addFunctionTableInit(0, false, array, false); + } + }); + +// passes +testLimit("tables", 0, kJSEmbeddingMaxTables, (builder, count) => { + for (let i = 0; i < count; i++) { + builder.addImportedTable("", "", 1, 1); + } + }); + +// passed +testLimit("memories", 0, kJSEmbeddingMaxMemories, (builder, count) => { + for (let i = 0; i < count; i++) { + builder.addImportedMemory("", "", 1, 1, false); + } + }); + +//======================================================================= +// HARNESS SNIPPET, DO NOT COMMIT +//======================================================================= +if (false && failures.length > 0) { + throw failures[0]; +} +//=======================================================================
--- a/js/src/wasm/WasmBinaryConstants.h +++ b/js/src/wasm/WasmBinaryConstants.h @@ -595,26 +595,30 @@ enum class NameType static const unsigned MaxTypes = 1000000; static const unsigned MaxFuncs = 1000000; static const unsigned MaxImports = 100000; static const unsigned MaxExports = 100000; static const unsigned MaxGlobals = 1000000; static const unsigned MaxDataSegments = 100000; static const unsigned MaxElemSegments = 10000000; -static const unsigned MaxTableInitialLength = 10000000; -static const unsigned MaxStringBytes = 100000; +static const unsigned MaxTableMaximumLength = 10000000; static const unsigned MaxLocals = 50000; static const unsigned MaxParams = 1000; +static const unsigned MaxMemoryMaximumPages = 65536; +static const unsigned MaxStringBytes = 100000; +static const unsigned MaxModuleBytes = 1024 * 1024 * 1024; +static const unsigned MaxFunctionBytes = 7654321; + +// These limits pertain to our WebAssembly implementation only. + +static const unsigned MaxTableInitialLength = 10000000; static const unsigned MaxBrTableElems = 1000000; -static const unsigned MaxMemoryInitialPages = 16384; -static const unsigned MaxMemoryMaximumPages = 65536; -static const unsigned MaxCodeSectionBytes = 1024 * 1024 * 1024; -static const unsigned MaxModuleBytes = MaxCodeSectionBytes; -static const unsigned MaxFunctionBytes = 7654321; +static const unsigned MaxMemoryInitialPages = 16384; +static const unsigned MaxCodeSectionBytes = MaxModuleBytes; // A magic value of the FramePointer to indicate after a return to the entry // stub that an exception has been caught and that we should throw. static const unsigned FailFP = 0xbad; // Asserted by Decoder::readVarU32.
--- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -1898,17 +1898,17 @@ WasmTableObject::construct(JSContext* cx return false; if (!StringEqualsAscii(elementStr, "anyfunc")) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT); return false; } Limits limits; - if (!GetLimits(cx, obj, MaxTableInitialLength, UINT32_MAX, "Table", &limits, + if (!GetLimits(cx, obj, MaxTableInitialLength, MaxTableMaximumLength, "Table", &limits, Shareable::False)) { return false; } RootedWasmTableObject table(cx, WasmTableObject::create(cx, limits)); if (!table) return false;
--- a/js/src/wasm/WasmValidate.cpp +++ b/js/src/wasm/WasmValidate.cpp @@ -1221,17 +1221,22 @@ DecodeTableLimits(Decoder& d, TableDescV if (elementType != uint8_t(TypeCode::AnyFunc)) return d.fail("expected 'anyfunc' element type"); Limits limits; if (!DecodeLimits(d, &limits)) return false; - if (limits.initial > MaxTableInitialLength) + // If there's a maximum, check it is in range. The check to exclude + // initial > maximum is carried out by the DecodeLimits call above, so + // we don't repeat it here. + if (limits.initial > MaxTableInitialLength || + ((limits.maximum.isSome() && + limits.maximum.value() > MaxTableMaximumLength))) return d.fail("too many table elements"); if (tables->length()) return d.fail("already have default table"); return tables->emplaceBack(TableKind::AnyFunction, limits); }
--- a/testing/web-platform/mozilla/tests/wasm/js/jsapi.js +++ b/testing/web-platform/mozilla/tests/wasm/js/jsapi.js @@ -521,17 +521,17 @@ test(() => { assertThrows(() => new Table({initial:{valueOf() { throw new Error("here")}}, element:"anyfunc"}), Error); assertThrows(() => new Table({initial:-1, element:"anyfunc"}), RangeError); assertThrows(() => new Table({initial:Math.pow(2,32), element:"anyfunc"}), RangeError); assertThrows(() => new Table({initial:2, maximum:1, element:"anyfunc"}), RangeError); assertThrows(() => new Table({initial:2, maximum:Math.pow(2,32), element:"anyfunc"}), RangeError); assert_equals(new Table({initial:1, element:"anyfunc"}) instanceof Table, true); assert_equals(new Table({initial:1.5, element:"anyfunc"}) instanceof Table, true); assert_equals(new Table({initial:1, maximum:1.5, element:"anyfunc"}) instanceof Table, true); - assert_equals(new Table({initial:1, maximum:Math.pow(2,32)-1, element:"anyfunc"}) instanceof Table, true); + assertThrows(() => new Table({initial:1, maximum:Math.pow(2,32)-1, element:"anyfunc"}), RangeError); }, "'WebAssembly.Table' constructor function"); test(() => { const tableProtoDesc = Object.getOwnPropertyDescriptor(Table, 'prototype'); assert_equals(typeof tableProtoDesc.value, "object"); assert_equals(tableProtoDesc.writable, false); assert_equals(tableProtoDesc.enumerable, false); assert_equals(tableProtoDesc.configurable, false);