From 24d6d56cfb68eb45f2336bde3133bfefb40d8356 Mon Sep 17 00:00:00 2001 From: Tim Chevalier Date: Wed, 4 Jan 2023 17:17:42 -0800 Subject: [PATCH] Re-land [Wasm-GC] Implement packed types in arrays https://bugs.webkit.org/show_bug.cgi?id=247576 Reviewed by Justin Michaud. This patch implements support for packed types (i8, i16) in Wasm GC arrays. Packed types are represented with new entries in wasm.json and are only allowed for use in StorageTypes, which are a new kind of type used for struct and array type definition fields. Packed structs are not yet allowed with this patch. Relanded patch fixes an invalid move in the Air generator for non-x86 platforms. It also refactors StorageType use slightly and also eliminates redundant LLInt opcodes. * JSTests/wasm/gc/arrays.js: (testArrayDeclaration): * JSTests/wasm/gc/packed-arrays.js: Added. (module): (check): (testArrayGetPacked): (testArrayGetUWithNewCanonPacked): (testArrayGetSWithNewCanonPacked): (testTypeMismatch64): (testTypeMismatchArrayGet): (testPackedTypeOutOfContext): (testSetGetTruncate): (testArraySet): (testArrayGetUnreachable): * JSTests/wasm/wasm.json: * Source/JavaScriptCore/bytecode/BytecodeList.rb: * Source/JavaScriptCore/wasm/WasmAirIRGeneratorBase.h: (JSC::Wasm::ExpressionType>::addArrayNewDefault): (JSC::Wasm::ExpressionType>::addArrayGet): (JSC::Wasm::ExpressionType>::addStructGet): (JSC::Wasm::ExpressionType>::addStructSet): * Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp: (JSC::Wasm::B3IRGenerator::emitStructSet): (JSC::Wasm::B3IRGenerator::pushArrayNew): (JSC::Wasm::B3IRGenerator::addArrayNew): (JSC::Wasm::B3IRGenerator::addArrayNewDefault): (JSC::Wasm::B3IRGenerator::addArrayGet): (JSC::Wasm::B3IRGenerator::addStructGet): * Source/JavaScriptCore/wasm/WasmFormat.h: (JSC::Wasm::isRefType): (JSC::Wasm::isRefWithRecursiveReference): (JSC::Wasm::isSubtype): (JSC::Wasm::isDefaultableType): * Source/JavaScriptCore/wasm/WasmFunctionParser.h: (JSC::Wasm::FunctionParser::parseExpression): (JSC::Wasm::FunctionParser::parseUnreachableExpression): * Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp: (JSC::Wasm::LLIntGenerator::addArrayGet): * Source/JavaScriptCore/wasm/WasmOperations.cpp: (JSC::Wasm::JSC_DEFINE_JIT_OPERATION): * Source/JavaScriptCore/wasm/WasmSectionParser.cpp: (JSC::Wasm::SectionParser::parsePackedType): (JSC::Wasm::SectionParser::parseStorageType): (JSC::Wasm::SectionParser::parseStructType): (JSC::Wasm::SectionParser::parseArrayType): * Source/JavaScriptCore/wasm/WasmSectionParser.h: * Source/JavaScriptCore/wasm/WasmSlowPaths.cpp: (JSC::LLInt::WASM_SLOW_PATH_DECL): * Source/JavaScriptCore/wasm/WasmTypeDefinition.cpp: (JSC::Wasm::StructType::dump const): (JSC::Wasm::StructType::StructType): (JSC::Wasm::ArrayType::dump const): (JSC::Wasm::StorageType::dump const): (JSC::Wasm::computeStructTypeHash): (JSC::Wasm::computeArrayTypeHash): (JSC::Wasm::TypeDefinition::replacePlaceholders const): * Source/JavaScriptCore/wasm/WasmTypeDefinition.h: (JSC::Wasm::StorageType::is const): (JSC::Wasm::StorageType::as const): (JSC::Wasm::StorageType::StorageType): (JSC::Wasm::StorageType::unpacked const): (JSC::Wasm::StorageType::elementSize const): (JSC::Wasm::StorageType::operator== const): (JSC::Wasm::StorageType::operator!= const): (JSC::Wasm::StorageType::typeCode const): (JSC::Wasm::StorageType::index const): (JSC::Wasm::makeString): (JSC::Wasm::typeSizeInBytes): * Source/JavaScriptCore/wasm/generateWasm.py: (Wasm.__init__): * Source/JavaScriptCore/wasm/generateWasmOpsHeader.py: (cppMacro): (cppMacroPacked): (packedTypeMacroizer): * Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.cpp: (JSC::JSWebAssemblyArray::JSWebAssemblyArray): (JSC::JSWebAssemblyArray::~JSWebAssemblyArray): (JSC::JSWebAssemblyArray::visitChildrenImpl): * Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h: * Source/JavaScriptCore/wasm/js/JSWebAssemblyStruct.cpp: (JSC::JSWebAssemblyStruct::get const): (JSC::JSWebAssemblyStruct::set): * Source/JavaScriptCore/wasm/wasm.json: Canonical link: https://commits.webkit.org/258463@main --- JSTests/wasm/gc/arrays.js | 2 +- JSTests/wasm/gc/packed-arrays.js | 474 ++++++++++++++++++ JSTests/wasm/wasm.json | 6 + .../JavaScriptCore/bytecode/BytecodeList.rb | 1 + .../wasm/WasmAirIRGeneratorBase.h | 43 +- .../JavaScriptCore/wasm/WasmB3IRGenerator.cpp | 76 ++- Source/JavaScriptCore/wasm/WasmFormat.h | 35 +- .../JavaScriptCore/wasm/WasmFunctionParser.h | 67 ++- .../wasm/WasmLLIntGenerator.cpp | 6 +- Source/JavaScriptCore/wasm/WasmOperations.cpp | 48 +- .../JavaScriptCore/wasm/WasmSectionParser.cpp | 43 +- .../JavaScriptCore/wasm/WasmSectionParser.h | 2 + Source/JavaScriptCore/wasm/WasmSlowPaths.cpp | 17 +- .../wasm/WasmTypeDefinition.cpp | 30 +- .../JavaScriptCore/wasm/WasmTypeDefinition.h | 104 +++- Source/JavaScriptCore/wasm/generateWasm.py | 1 + .../wasm/generateWasmOpsHeader.py | 42 ++ .../wasm/js/JSWebAssemblyArray.cpp | 37 +- .../wasm/js/JSWebAssemblyArray.h | 52 +- .../wasm/js/JSWebAssemblyStruct.cpp | 14 +- Source/JavaScriptCore/wasm/wasm.json | 6 + 21 files changed, 998 insertions(+), 108 deletions(-) create mode 100644 JSTests/wasm/gc/packed-arrays.js diff --git a/JSTests/wasm/gc/arrays.js b/JSTests/wasm/gc/arrays.js index 051b4d9383ab..2644c44d9646 100644 --- a/JSTests/wasm/gc/arrays.js +++ b/JSTests/wasm/gc/arrays.js @@ -34,7 +34,7 @@ function testArrayDeclaration() { assert.throws( () => new WebAssembly.Instance(module("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x84\x80\x80\x80\x00\x01\x5e\xff\x02")), WebAssembly.CompileError, - "Module doesn't parse at byte 17: can't get array's element Type" + "Module doesn't parse at byte 16: can't get array's element Type" ) /* diff --git a/JSTests/wasm/gc/packed-arrays.js b/JSTests/wasm/gc/packed-arrays.js new file mode 100644 index 000000000000..81eda1baa61b --- /dev/null +++ b/JSTests/wasm/gc/packed-arrays.js @@ -0,0 +1,474 @@ +//@ runWebAssemblySuite("--useWebAssemblyTypedFunctionReferences=true", "--useWebAssemblyGC=true") + +import * as assert from "../assert.js"; +import { compile, instantiate } from "./wast-wrapper.js"; + +function module(bytes, valid = true) { + let buffer = new ArrayBuffer(bytes.length); + let view = new Uint8Array(buffer); + for (let i = 0; i < bytes.length; ++i) { + view[i] = bytes.charCodeAt(i); + } + return new WebAssembly.Module(buffer); +} + +function check(createModuleString, type, expectedVal) { + let m = instantiate(createModuleString(type)); + assert.eq(m.exports.f(), expectedVal); +} + +function testArrayGetPacked() { + let f = (type) => ` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (array.new_canon_default 0 (i32.const 5)) + (i32.const 2) + (array.get_u 0))) + `; + check(f, "i8", 0); + check(f, "i16", 0); +} + +function testArrayGetUWithNewCanonPacked() { + /* + * maxint32 => truncate to 0xFF => zero-extend to 0x000000FF (i8) or 0x0000FFFF (i16) + */ + { + let f = (type) => ` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (array.new_canon 0 (i32.const 0x7FFF_FFFF) (i32.const 5)) + (i32.const 2) + (array.get_u 0))) + `; + check(f, "i8", 255); + check(f, "i16", 0xFFFF); + } + + /* + * -1 => truncate to 0xFF => zero-extend to 0x000000FF (i8) or 0x0000FFFF (i16) + */ + { + let f = (type) => ` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (array.new_canon 0 (i32.const 0xFFFF_FFFF) (i32.const 5)) + (i32.const 2) + (array.get_u 0))) + `; + check(f, "i8", 255); + check(f, "i16", 0xFFFF); + } + + /* + * 0x4000_0000 => truncate to 0 => zero-extend to 0 + */ + { + let f = (type) => ` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (array.new_canon 0 (i32.const 0x4000_0000) (i32.const 5)) + (i32.const 2) + (array.get_u 0))) + `; + check(f, "i8", 0); + check(f, "i16", 0); + } + + /* + * 0x00000080 => truncate to 0x80 => zero-extend to 128 + */ + { + let f = (type) => ` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (array.new_canon 0 (i32.const 0x80) (i32.const 5)) + (i32.const 2) + (array.get_u 0))) + `; + check(f, "i8", 128); + check(f, "i16", 128); + } + + /* + * 0xaaaa_aaaa => truncate to 0xaa => zero-extend to 0xaa (i8) or 0xaaaa (i16) + */ + { + let f = (type) => ` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (array.new_canon 0 (i32.const 0xaaaa_aaaa) (i32.const 5)) + (i32.const 2) + (array.get_u 0))) + `; + check(f, "i8", 0xaa); + check(f, "i16", 0xaaaa); + } + +} + +// These tests test two different things at once (that array.get_s sign-extends properly, +// and that array.new_canon truncates properly) +function testArrayGetSWithNewCanonPacked() { + /* + * maxint32 => truncate to 0xFF (or 0xFFFF) => sign-extend to 0xFFFFFFFF + */ + + { + let f = (type) => ` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (array.new_canon 0 (i32.const 0x7FFF_FFFF) (i32.const 5)) + (i32.const 2) + (array.get_s 0))) + `; + check(f, "i8", -1); + check(f, "i16", -1); + } + + /* + * -1 => truncate to 0xFF (or 0xFFFF) => sign-extend to 0xFFFFFFFF + */ + + { + let f = (type) => ` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (array.new_canon 0 (i32.const 0xFFFF_FFFF) (i32.const 5)) + (i32.const 2) + (array.get_s 0))) + `; + check(f, "i8", -1); + check(f, "i16", -1); + } + + /* + * 0x4000_0000 => truncate to 0 => sign-extend to 0 + */ + + { + let f = (type) => ` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (array.new_canon 0 (i32.const 0x4000_0000) (i32.const 5)) + (i32.const 2) + (array.get_s 0))) + `; + check(f, "i8", 0); + check(f, "i16", 0); + } + + /* + * 0x0000_0080 => truncate to 0x80 => sign-extend to -128 (i8) or 128 (i16) + */ + + { + let f = (type) => ` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (array.new_canon 0 (i32.const 0x0000_0080) (i32.const 5)) + (i32.const 2) + (array.get_s 0))) + `; + check(f, "i8", -128); + check(f, "i16", 128); + } + + /* + * 0x0000_8000 => truncate to 0 (i8) 0x8000 (i16) => sign-extend to 0 (i8) or 0xffff8000 (-32768) (i16) + */ + + { + let f = (type) => ` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (array.new_canon 0 (i32.const 0x0000_8000) (i32.const 5)) + (i32.const 2) + (array.get_s 0))) + `; + check(f, "i8", 0); + check(f, "i16", -32768); + } + + + /* + * 0xaaaa_aaaa => truncate to 0xaa (i8) or 0xaaaa (i16) => sign-extend to 0xffff_ffaa (-86) (i8) or 0xffff_aaaa (-21846) (i16) + */ + + { + let f = (type) => ` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (array.new_canon 0 (i32.const 0xaaaa_aaaa) (i32.const 5)) + (i32.const 2) + (array.get_s 0))) + `; + check(f, "i8", -86); + check(f, "i16", -21846); + } + +} + +// Should be an error to initialize a packed array with an i64 value +function testTypeMismatch64() { + let check = (type) => assert.throws( + () => compile(` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (array.new_canon 0 (i64.const 0) (i32.const 5)) + (i32.const 2) + (array.get_u 0))) + `), + WebAssembly.CompileError, + "array.new value to type I64 expected I32" + ); + check("i8"); + check("i16"); +} + + +// Should be an error to use array.get on a packed array -- need get_s or get_u +function testTypeMismatchArrayGet() { + let f = (type) => () => compile(` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (array.new_canon 0 (i32.const 0) (i32.const 5)) + (i32.const 2) + (array.get 0))) + `); + assert.throws(f("i8"), + WebAssembly.CompileError, + "array.get applied to packed array of I8 -- use array.get_s or array.get_u"); + assert.throws(f("i16"), + WebAssembly.CompileError, + "array.get applied to packed array of I16 -- use array.get_s or array.get_u"); +} + +/* +Should be an error to use i8 or i16 outside an array context +*/ +function testPackedTypeOutOfContext() { +/* +(module + (func (export "f") (result i8) + (i32.const 2))) +*/ + assert.throws(() => new WebAssembly.Instance(module("\x00\x61\x73\x6D\x01\x00\x00\x00\x01\x85\x80\x80\x80\x00\x01\x60\x00\x01\x7A\x03\x82\x80\x80\x80\x00\x01\x00\x07\x85\x80\x80\x80\x00\x01\x01\x66\x00\x00\x0A\x8A\x80\x80\x80\x00\x01\x84\x80\x80\x80\x00\x00\x41\x02\x0B")), + WebAssembly.CompileError, + "WebAssembly.Module doesn't parse at byte 19: can't get 0th Type's return value"); +/* +(module + (func (export "f") (param i16) (result i32) + (i32.const 2))) +*/ + assert.throws(() => new WebAssembly.Instance(module("\x00\x61\x73\x6D\x01\x00\x00\x00\x01\x86\x80\x80\x80\x00\x01\x60\x01\x79\x01\x7F\x03\x82\x80\x80\x80\x00\x01\x00\x07\x85\x80\x80\x80\x00\x01\x01\x66\x00\x00\x0A\x8A\x80\x80\x80\x00\x01\x84\x80\x80\x80\x00\x00\x41\x02\x0B")), + WebAssembly.CompileError, + "WebAssembly.Module doesn't parse at byte 18: can't get 0th argument Type"); +/* +(module + (type (func (param i8) (result i32)))) +*/ + assert.throws(() => new WebAssembly.Instance(module("\x00\x61\x73\x6D\x01\x00\x00\x00\x01\x86\x80\x80\x80\x00\x01\x60\x01\x7A\x01\x7F")), + WebAssembly.CompileError, + "WebAssembly.Module doesn't parse at byte 18: can't get 0th argument Type"); +/* +(module + (type (func (param i32) (result i16)))) +*/ + assert.throws(() => new WebAssembly.Instance(module("\x00\x61\x73\x6D\x01\x00\x00\x00\x01\x86\x80\x80\x80\x00\x01\x60\x01\x7F\x01\x79")), + WebAssembly.CompileError, + "WebAssembly.Module doesn't parse at byte 20: can't get 0th Type's return value"); +/* +(module + (global (mut i8) (i32.const 0))) +*/ + assert.throws(() => new WebAssembly.Instance(module("\x00\x61\x73\x6D\x01\x00\x00\x00\x06\x86\x80\x80\x80\x00\x01\x7A\x01\x41\x00\x0B")), + WebAssembly.CompileError, + "WebAssembly.Module doesn't parse at byte 16: can't get Global's value type"); +/* +(module + (global (mut i16) (i32.const 0))) +*/ + assert.throws(() => new WebAssembly.Instance(module("\x00\x61\x73\x6D\x01\x00\x00\x00\x06\x86\x80\x80\x80\x00\x01\x79\x01\x41\x00\x0B")), + WebAssembly.CompileError, + "WebAssembly.Module doesn't parse at byte 16: can't get Global's value type"); +} + +// Tests that setting `element` in an array of `type` is a type error; also takes `elementType` +// so the right error message can be tested for +function testTypeError(type, element, elementType) { + assert.throws(() => compile(` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (array.new_canon_default 0 (i32.const 5)) + (i32.const 2)` + + element + + `\n(array.set 0) + (array.get_u 0)))`), + WebAssembly.CompileError, + "array.set value to type " + elementType + " expected I32"); +} + +// Tests that setting an element in a null array of type `type` is a runtime error +function testTrapsNull(type) { + let m = instantiate(` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (ref.null 0) + (i32.const 2) + (i32.const 17) + (array.set 0) + (i32.const 0)))`); + assert.throws(() => { m.exports.f(); }, + WebAssembly.RuntimeError, + "array.set to a null reference"); +} + +// Tests that setting `index` in a `type` array of length `len` is a runtime error +function testTrapsOutOfBounds(type, len, index) { + let m = instantiate(` + (module + (type (array (mut ` + type + `))) + (func (export "f") (result i32) + (array.new_canon_default 0 (i32.const ` + len + `)) + (i32.const ` + index + `) + (i32.const 17) + (array.set 0) + (i32.const 0)))`); + assert.throws(() => { m.exports.f(); }, + WebAssembly.RuntimeError, + "Out of bounds array.set"); +} + +// Tests that if we set a[`index`] to (i32.const `value`), where a is a `type` array of length `len`, the following get operation returns (i32.const `value`) +function testSetGetRoundtrip(type, len, index, value) { + let m = instantiate(` + (module + (type (array (mut ` + type + `))) + (global (mut (ref null 0)) (ref.null 0)) + (func (export "init") + (global.set 0 (array.new_canon_default 0 (i32.const ` + len + `)))) + (func (export "f") (result i32) + (array.set 0 (global.get 0) (i32.const ` + index + `) (i32.const ` + value + `)) + (array.get_u 0 (global.get 0) (i32.const ` + index + `))))`); + m.exports.init(); + assert.eq(m.exports.f(), value); +} + +// Creates an array a of type `type`, sets a[i] (for some in-bounds i) to `inValue`, and gets `a[i]` using `signMode`; compares the result to `outValue` +function testSetGetTruncate(type, signMode, inValue, outValue) { + let m = instantiate(` + (module + (type (array (mut ` + type + `))) + (global (mut (ref null 0)) (ref.null 0)) + (func (export "init") + (global.set 0 (array.new_canon_default 0 (i32.const 5)))) + (func (export "f") (result i32) + (array.set 0 (global.get 0) (i32.const 3) (i32.const ` + inValue + `)) + (array.get_` + signMode + ` 0 (global.get 0) (i32.const 3))))`); + m.exports.init(); + assert.eq(m.exports.f(), outValue); +} + +/* + array.set + for both i8 and i16: + + test that it's a type error to pass in a non-i32 value when setting a packed array element + test that it traps correctly for null + and if the index is out of bounds + test array lengths 0, 1, 5 + test indices 0, 1, and (for length 5) 3 and 4 + + test that set/get roundtrips for within-bounds values + test that set/get returns the properly truncated value for each combination of type and sign-extension + */ + +function testArraySet() { + for (const type of ["i8", "i16"]) { + testTypeError(type, "(i64.const 3)", "I64"); + testTrapsNull(type); + testTrapsOutOfBounds(type, 5, 5); + testTrapsOutOfBounds(type, 5, 7); + testTrapsOutOfBounds(type, 0, 1); + for (const length of [0, 1, 5]) { + for (const index of [0, 1, 3, 4]) { + if (index < length) { + testSetGetRoundtrip(type, length, index, 17); + } else { + testTrapsOutOfBounds(type, length, index); + } + } + } + } + // testSetGetTruncate(type, signMode, inValue, outValue) + testSetGetTruncate("i8", "u", 0xFFFF_FFFF, 255); + testSetGetTruncate("i8", "u", 0x4000_0000, 0); + testSetGetTruncate("i8", "u", 0x80, 128); + testSetGetTruncate("i8", "s", 0xFFFF_FFFF, -1); + testSetGetTruncate("i8", "s", 0x4000_0000, 0); + testSetGetTruncate("i8", "s", 0x80, -128); + testSetGetTruncate("i16", "u", 0xFFFF_FFFF, 0xFFFF); + testSetGetTruncate("i16", "u", 0x4000_0000, 0); + testSetGetTruncate("i16", "u", 0x80, 128); + testSetGetTruncate("i16", "s", 0xFFFF_FFFF, -1); + testSetGetTruncate("i16", "s", 0x4000_0000, 0); + testSetGetTruncate("i16", "s", 0x80, 128); + testSetGetTruncate("i16", "s", 0x7FFF_FFFF, -1); + testSetGetTruncate("i16", "s", 0x4000_0000, 0); + testSetGetTruncate("i16", "s", 0x80, 128); + testSetGetTruncate("i16", "s", 0x8000, -32768); + testSetGetTruncate("i16", "s", 0xaaaa_aaaa, -21846); +} + +function testArrayGetUnreachable() { +/* +(module + (type (array (mut i8))) + (func (export "f") (result i32) + (return) + (array.new_canon_default 0 (i32.const 5)) + (i32.const 2) + (array.get_s -1))) +*/ + assert.throws(() => new WebAssembly.Instance(module("\x00\x61\x73\x6D\x01\x00\x00\x00\x01\x88\x80\x80\x80\x00\x02\x5E\x7A\x01\x60\x00\x01\x7F\x03\x82\x80\x80\x80\x00\x01\x01\x07\x85\x80\x80\x80\x00\x01\x01\x66\x00\x00\x0A\x95\x80\x80\x80\x00\x01\x8F\x80\x80\x80\x00\x00\x41\x2A\x0F\x41\x05\xFB\x12\x00\x41\x02\xFB\x14\xFF\xFF")), + WebAssembly.CompileError, + "WebAssembly.Module doesn't parse at byte 15: can't get type index immediate for array.get_s in unreachable context"); +/* +(module + (type (array (mut i8))) + (func (export "f") (result i32) + (return) + (array.new_canon_default 0 (i32.const 5)) + (i32.const 2) + (array.get_u -1))) +*/ + assert.throws(() => new WebAssembly.Instance(module("\x00\x61\x73\x6D\x01\x00\x00\x00\x01\x88\x80\x80\x80\x00\x02\x5E\x7A\x01\x60\x00\x01\x7F\x03\x82\x80\x80\x80\x00\x01\x01\x07\x85\x80\x80\x80\x00\x01\x01\x66\x00\x00\x0A\x95\x80\x80\x80\x00\x01\x8F\x80\x80\x80\x00\x00\x41\x2A\x0F\x41\x05\xFB\x12\x00\x41\x02\xFB\x15\xFF\xFF")), + WebAssembly.CompileError, + "WebAssembly.Module doesn't parse at byte 15: can't get type index immediate for array.get_u in unreachable context"); +} + +testArrayGetPacked(); +testArrayGetUWithNewCanonPacked(); +testArrayGetSWithNewCanonPacked(); +testTypeMismatch64(); +testTypeMismatchArrayGet(); +testPackedTypeOutOfContext(); +testArraySet(); +testArrayGetUnreachable(); diff --git a/JSTests/wasm/wasm.json b/JSTests/wasm/wasm.json index b5c6395aa49f..b5c5d043deb7 100644 --- a/JSTests/wasm/wasm.json +++ b/JSTests/wasm/wasm.json @@ -25,6 +25,10 @@ "rec": { "type": "varint7", "value": -49, "b3type": "B3::Void", "width": 0 }, "void": { "type": "varint7", "value": -64, "b3type": "B3::Void", "width": 0 } }, + "packed_type": { + "i8": { "type": "varint7", "value": -6}, + "i16": { "type": "varint7", "value": -7} + }, "value_type": ["i32", "i64", "f32", "f64", "externref", "funcref", "v128"], "block_type": ["i32", "i64", "f32", "f64", "void", "externref", "funcref", "v128"], "ref_type": ["funcref", "externref", "ref", "ref_null"], @@ -266,6 +270,8 @@ "array.new": { "category": "gc", "value": 251, "return": ["arrayref"], "parameter": ["any", "i32", "rtt"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 17 }, "array.new_default": { "category": "gc", "value": 251, "return": ["arrayref"], "parameter": ["i32", "rtt"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 18 }, "array.get": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["arrayref", "i32"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 19 }, + "array.get_s": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["arrayref", "i32"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 20 }, + "array.get_u": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["arrayref", "i32"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 21 }, "array.set": { "category": "gc", "value": 251, "return": [], "parameter": ["arrayref", "i32", "any"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 22 }, "array.len": { "category": "gc", "value": 251, "return": ["i32"], "parameter": ["arrayref"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 23 }, "i31.new": { "category": "gc", "value": 251, "return": ["i31ref"], "parameter": ["i32"], "immediate": [], "extendedOp": 32 }, diff --git a/Source/JavaScriptCore/bytecode/BytecodeList.rb b/Source/JavaScriptCore/bytecode/BytecodeList.rb index 7290db5fe2fe..a7b2cfa3d221 100644 --- a/Source/JavaScriptCore/bytecode/BytecodeList.rb +++ b/Source/JavaScriptCore/bytecode/BytecodeList.rb @@ -1878,6 +1878,7 @@ arrayref: VirtualRegister, index: VirtualRegister, typeIndex: unsigned, + arrayGetKind: unsigned, } op :array_set, diff --git a/Source/JavaScriptCore/wasm/WasmAirIRGeneratorBase.h b/Source/JavaScriptCore/wasm/WasmAirIRGeneratorBase.h index 124ea0f92208..f2639626b72d 100644 --- a/Source/JavaScriptCore/wasm/WasmAirIRGeneratorBase.h +++ b/Source/JavaScriptCore/wasm/WasmAirIRGeneratorBase.h @@ -404,7 +404,7 @@ struct AirIRGeneratorBase { // GC (in derived classes) PartialResult WARN_UNUSED_RETURN addArrayNew(uint32_t typeIndex, ExpressionType size, ExpressionType value, ExpressionType& result); PartialResult WARN_UNUSED_RETURN addArrayNewDefault(uint32_t index, ExpressionType size, ExpressionType& result); - PartialResult WARN_UNUSED_RETURN addArrayGet(uint32_t typeIndex, ExpressionType arrayref, ExpressionType index, ExpressionType& result); + PartialResult WARN_UNUSED_RETURN addArrayGet(GCOpType arrayGetKind, uint32_t typeIndex, ExpressionType arrayref, ExpressionType index, ExpressionType& result); PartialResult WARN_UNUSED_RETURN addArraySet(uint32_t typeIndex, ExpressionType arrayref, ExpressionType index, ExpressionType value); PartialResult WARN_UNUSED_RETURN addArrayLen(ExpressionType arrayref, ExpressionType& result); PartialResult WARN_UNUSED_RETURN addStructNew(uint32_t index, Vector& args, ExpressionType& result); @@ -2491,11 +2491,11 @@ auto AirIRGeneratorBase::addArrayNewDefault(uint32_t ty { Wasm::TypeDefinition& arraySignature = m_info.typeSignatures[typeIndex]; ASSERT(arraySignature.is()); - Wasm::Type elementType = arraySignature.as()->elementType().type; + const StorageType& elementType = arraySignature.as()->elementType().type; ExpressionType tmpForValue; if (Wasm::isRefType(elementType)) - tmpForValue = self().addConstant(elementType, JSValue::encode(jsNull())); + tmpForValue = self().addConstant(elementType.as(), JSValue::encode(jsNull())); else { tmpForValue = self().g64(); self().emitZeroInitialize(tmpForValue); @@ -2510,11 +2510,14 @@ auto AirIRGeneratorBase::addArrayNewDefault(uint32_t ty } template -auto AirIRGeneratorBase::addArrayGet(uint32_t typeIndex, ExpressionType arrayref, ExpressionType index, ExpressionType& result) -> PartialResult +auto AirIRGeneratorBase::addArrayGet(GCOpType arrayGetKind, uint32_t typeIndex, ExpressionType arrayref, ExpressionType index, ExpressionType& result) -> PartialResult { + ASSERT(arrayGetKind == GCOpType::ArrayGet || arrayGetKind == GCOpType::ArrayGetS || arrayGetKind == GCOpType::ArrayGetU); + Wasm::TypeDefinition& arraySignature = m_info.typeSignatures[typeIndex]; ASSERT(arraySignature.is()); - Wasm::Type elementType = arraySignature.as()->elementType().type; + Wasm::StorageType elementType = arraySignature.as()->elementType().type; + Wasm::Type resultType = elementType.unpacked(); // Ensure arrayref is non-null. emitThrowOnNullReference(arrayref, ExceptionType::NullArrayGet); @@ -2533,7 +2536,27 @@ auto AirIRGeneratorBase::addArrayGet(uint32_t typeIndex // https://bugs.webkit.org/show_bug.cgi?id=245405 emitCCall(&operationWasmArrayGet, getValue, instanceValue(), self().addConstant(Types::I32, typeIndex), arrayref, index); - self().emitCoerceFromI64(elementType, getValue, result); + self().emitCoerceFromI64(resultType, getValue, result); + + if (elementType.is()) { + switch (arrayGetKind) { + case GCOpType::ArrayGetU: + break; + case GCOpType::ArrayGetS: { + size_t elementSize = elementType.as() == PackedType::I8 ? sizeof(uint8_t) : sizeof(uint16_t); + uint8_t bitShift = (sizeof(uint32_t) - elementSize) * 8; + auto tmpForShift = self().g32(); + append(Move, Arg::imm(bitShift), tmpForShift); + self().addShift(Types::I32, Lshift32, result, tmpForShift, result); + self().addShift(Types::I32, Rshift32, result, tmpForShift, result); + break; + } + default: + RELEASE_ASSERT_NOT_REACHED(); + return { }; + } + } + return { }; } @@ -2597,7 +2620,9 @@ auto AirIRGeneratorBase::addStructGet(ExpressionType st self().emitLoad(structBase, JSWebAssemblyStruct::offsetOfPayload(), payload); uint32_t fieldOffset = fixupPointerPlusOffset(payload, *structType.getFieldOffset(fieldIndex)); - const auto& fieldType = structType.field(fieldIndex).type; + // FIXME: https://bugs.webkit.org/show_bug.cgi?id=246981 + ASSERT(structType.field(fieldIndex).type.is()); + Type fieldType = structType.field(fieldIndex).type.as(); result = tmpForType(fieldType); self().emitLoad(payload, fieldOffset, result); return { }; @@ -2611,7 +2636,9 @@ auto AirIRGeneratorBase::addStructSet(ExpressionType st self().emitLoad(structBase, JSWebAssemblyStruct::offsetOfPayload(), payload); uint32_t fieldOffset = fixupPointerPlusOffset(payload, *structType.getFieldOffset(fieldIndex)); - const auto& fieldType = structType.field(fieldIndex).type; + // FIXME: https://bugs.webkit.org/show_bug.cgi?id=246981 + ASSERT(structType.field(fieldIndex).type.is()); + Type fieldType = structType.field(fieldIndex).type.as(); if (isRefType(fieldType)) { auto instanceCell = self().gPtr(); diff --git a/Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp b/Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp index 7deae8a3952e..236ff93d48ca 100644 --- a/Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp +++ b/Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp @@ -555,7 +555,7 @@ class B3IRGenerator { PartialResult WARN_UNUSED_RETURN addI31GetU(ExpressionType ref, ExpressionType& result); PartialResult WARN_UNUSED_RETURN addArrayNew(uint32_t index, ExpressionType size, ExpressionType value, ExpressionType& result); PartialResult WARN_UNUSED_RETURN addArrayNewDefault(uint32_t index, ExpressionType size, ExpressionType& result); - PartialResult WARN_UNUSED_RETURN addArrayGet(uint32_t typeIndex, ExpressionType arrayref, ExpressionType index, ExpressionType& result); + PartialResult WARN_UNUSED_RETURN addArrayGet(GCOpType arrayGetKind, uint32_t typeIndex, ExpressionType arrayref, ExpressionType index, ExpressionType& result); PartialResult WARN_UNUSED_RETURN addArraySet(uint32_t typeIndex, ExpressionType arrayref, ExpressionType index, ExpressionType value); PartialResult WARN_UNUSED_RETURN addArrayLen(ExpressionType arrayref, ExpressionType& result); PartialResult WARN_UNUSED_RETURN addStructNew(uint32_t typeIndex, Vector& args, ExpressionType& result); @@ -658,6 +658,7 @@ class B3IRGenerator { Value* emitAtomicCompareExchange(ExtAtomicOpType, Type, Value* pointer, Value* expected, Value*, uint32_t offset); void emitStructSet(Value*, uint32_t, const StructType&, ExpressionType&); + ExpressionType WARN_UNUSED_RETURN pushArrayNew(uint32_t typeIndex, Value* initValue, ExpressionType size); void unify(Value* phi, const ExpressionType source); void unifyValuesWithBlock(const Stack& resultStack, const ControlData& block); @@ -2300,7 +2301,11 @@ void B3IRGenerator::emitStructSet(Value* structValue, uint32_t fieldIndex, const { Value* payloadBase = m_currentBlock->appendNew(m_proc, memoryKind(Load), Int64, origin(), structValue, JSWebAssemblyStruct::offsetOfPayload()); int32_t fieldOffset = fixupPointerPlusOffset(payloadBase, *structType.getFieldOffset(fieldIndex)); - const auto& fieldType = structType.field(fieldIndex).type; + + // FIXME: https://bugs.webkit.org/show_bug.cgi?id=246981 + ASSERT(structType.field(fieldIndex).type.is()); + + Type fieldType = structType.field(fieldIndex).type.as(); switch (fieldType.kind) { case TypeKind::I32: case TypeKind::Funcref: @@ -2609,13 +2614,23 @@ auto B3IRGenerator::addI31GetU(ExpressionType ref, ExpressionType& result) -> Pa return { }; } +Variable* B3IRGenerator::pushArrayNew(uint32_t typeIndex, Value* initValue, ExpressionType size) +{ + // FIXME: Emit this inline. + // https://bugs.webkit.org/show_bug.cgi?id=245405 + return push(m_currentBlock->appendNew(m_proc, toB3Type(Types::Arrayref), origin(), + m_currentBlock->appendNew(m_proc, origin(), tagCFunction(operationWasmArrayNew)), + instanceValue(), m_currentBlock->appendNew(m_proc, origin(), typeIndex), + get(size), initValue)); +} + auto B3IRGenerator::addArrayNew(uint32_t typeIndex, ExpressionType size, ExpressionType value, ExpressionType& result) -> PartialResult { #if ASSERT_ENABLED Wasm::TypeDefinition& arraySignature = m_info.typeSignatures[typeIndex]; ASSERT(arraySignature.is()); - Wasm::Type elementType = arraySignature.as()->elementType().type; - ASSERT(toB3Type(elementType) == value->type()); + const StorageType& elementType = arraySignature.as()->elementType().type; + ASSERT(toB3Type(elementType.unpacked()) == value->type()); #endif Value* initValue = get(value); @@ -2628,44 +2643,33 @@ auto B3IRGenerator::addArrayNew(uint32_t typeIndex, ExpressionType size, Express initValue = patchpoint; } - // FIXME: Emit this inline. - // https://bugs.webkit.org/show_bug.cgi?id=245405 - result = push(m_currentBlock->appendNew(m_proc, toB3Type(Types::Arrayref), origin(), - m_currentBlock->appendNew(m_proc, origin(), tagCFunction(operationWasmArrayNew)), - instanceValue(), m_currentBlock->appendNew(m_proc, origin(), typeIndex), - get(size), initValue)); + result = pushArrayNew(typeIndex, initValue, size); return { }; } auto B3IRGenerator::addArrayNewDefault(uint32_t typeIndex, ExpressionType size, ExpressionType& result) -> PartialResult { - // FIXME: Abstract and generalize with addArrayNew. Wasm::TypeDefinition& arraySignature = m_info.typeSignatures[typeIndex]; ASSERT(arraySignature.is()); - Wasm::Type elementType = arraySignature.as()->elementType().type; Value* initValue; - if (Wasm::isRefType(elementType)) + if (Wasm::isRefType(arraySignature.as()->elementType().type)) initValue = m_currentBlock->appendNew(m_proc, origin(), JSValue::encode(jsNull())); else initValue = m_currentBlock->appendNew(m_proc, origin(), 0); - // FIXME: Emit this inline. - // https://bugs.webkit.org/show_bug.cgi?id=245405 - result = push(m_currentBlock->appendNew(m_proc, toB3Type(Types::Arrayref), origin(), - m_currentBlock->appendNew(m_proc, origin(), tagCFunction(operationWasmArrayNew)), - instanceValue(), m_currentBlock->appendNew(m_proc, origin(), typeIndex), - get(size), initValue)); + result = pushArrayNew(typeIndex, initValue, size); return { }; } -auto B3IRGenerator::addArrayGet(uint32_t typeIndex, ExpressionType arrayref, ExpressionType index, ExpressionType& result) -> PartialResult +auto B3IRGenerator::addArrayGet(GCOpType arrayGetKind, uint32_t typeIndex, ExpressionType arrayref, ExpressionType index, ExpressionType& result) -> PartialResult { Wasm::TypeDefinition& arraySignature = m_info.typeSignatures[typeIndex]; ASSERT(arraySignature.is()); - Wasm::Type elementType = arraySignature.as()->elementType().type; + Wasm::StorageType elementType = arraySignature.as()->elementType().type; + Wasm::Type resultType = elementType.unpacked(); // Ensure arrayref is non-null. { @@ -2694,10 +2698,10 @@ auto B3IRGenerator::addArrayGet(uint32_t typeIndex, ExpressionType arrayref, Exp instanceValue(), m_currentBlock->appendNew(m_proc, origin(), typeIndex), get(arrayref), get(index)); - switch (toB3Type(elementType).kind()) { + switch (toB3Type(resultType).kind()) { case B3::Float: case B3::Double: { - PatchpointValue* patchpoint = m_currentBlock->appendNew(m_proc, toB3Type(elementType), origin()); + PatchpointValue* patchpoint = m_currentBlock->appendNew(m_proc, toB3Type(resultType), origin()); patchpoint->appendSomeRegister(arrayResult); patchpoint->setGenerator([=] (CCallHelpers& jit, const StackmapGenerationParams& params) { jit.move64ToDouble(params[1].gpr(), params[0].fpr()); @@ -2706,12 +2710,28 @@ auto B3IRGenerator::addArrayGet(uint32_t typeIndex, ExpressionType arrayref, Exp break; } case B3::Int32: { - PatchpointValue* patchpoint = m_currentBlock->appendNew(m_proc, toB3Type(elementType), origin()); + PatchpointValue* patchpoint = m_currentBlock->appendNew(m_proc, toB3Type(resultType), origin()); patchpoint->appendSomeRegister(arrayResult); patchpoint->setGenerator([=] (CCallHelpers& jit, const StackmapGenerationParams& params) { jit.move(params[1].gpr(), params[0].gpr()); }); - result = push(patchpoint); + Value* postProcess = patchpoint; + switch (arrayGetKind) { + case GCOpType::ArrayGet: + case GCOpType::ArrayGetU: + break; + case GCOpType::ArrayGetS: { + size_t elementSize = elementType.as() == PackedType::I8 ? sizeof(uint8_t) : sizeof(uint16_t); + uint8_t bitShift = (sizeof(uint32_t) - elementSize) * 8; + Value* shiftLeft = m_currentBlock->appendNew(m_proc, B3::Shl, origin(), patchpoint, m_currentBlock->appendNew(m_proc, origin(), bitShift)); + postProcess = m_currentBlock->appendNew(m_proc, B3::SShr, origin(), shiftLeft, m_currentBlock->appendNew(m_proc, origin(), bitShift)); + break; + } + default: + RELEASE_ASSERT_NOT_REACHED(); + return { }; + } + result = push(postProcess); break; } case B3::Int64: @@ -2812,7 +2832,11 @@ auto B3IRGenerator::addStructGet(ExpressionType structReference, const StructTyp { Value* payloadBase = m_currentBlock->appendNew(m_proc, memoryKind(Load), pointerType(), origin(), get(structReference), JSWebAssemblyStruct::offsetOfPayload()); int32_t fieldOffset = fixupPointerPlusOffset(payloadBase, *structType.getFieldOffset(fieldIndex)); - switch (structType.field(fieldIndex).type.kind) { + + // FIXME: https://bugs.webkit.org/show_bug.cgi?id=246981 + ASSERT(structType.field(fieldIndex).type.is()); + + switch (structType.field(fieldIndex).type.as().kind) { case TypeKind::I32: result = push(m_currentBlock->appendNew(m_proc, memoryKind(Load), Int32, origin(), payloadBase, fieldOffset)); break; diff --git a/Source/JavaScriptCore/wasm/WasmFormat.h b/Source/JavaScriptCore/wasm/WasmFormat.h index 5c03c93863ce..7dfdb7566ebc 100644 --- a/Source/JavaScriptCore/wasm/WasmFormat.h +++ b/Source/JavaScriptCore/wasm/WasmFormat.h @@ -104,6 +104,14 @@ inline bool isRefType(Type type) return type.isFuncref() || type.isExternref(); } +// If this is a type, returns true iff it's a ref type; if it's a packed type, returns false +inline bool isRefType(StorageType type) +{ + if (type.is()) + return isRefType(type.as()); + return false; +} + inline bool isExternref(Type type) { if (Options::useWebAssemblyTypedFunctionReferences()) @@ -155,7 +163,7 @@ inline bool isRefWithTypeIndex(Type type) } // Determine if the ref type has a placeholder type index that is used -// for an unresoled recursive reference in a recursion group. +// for an unresolved recursive reference in a recursion group. inline bool isRefWithRecursiveReference(Type type) { if (!Options::useWebAssemblyGC()) @@ -170,6 +178,14 @@ inline bool isRefWithRecursiveReference(Type type) return false; } +inline bool isRefWithRecursiveReference(StorageType storageType) +{ + if (storageType.is()) + return false; + + return isRefWithRecursiveReference(storageType.as()); +} + inline bool isTypeIndexHeapType(int32_t heapType) { if (!Options::useWebAssemblyTypedFunctionReferences()) @@ -212,6 +228,15 @@ inline bool isSubtype(Type sub, Type parent) return sub == parent; } +inline bool isSubtype(StorageType sub, StorageType parent) +{ + if (sub.is() || parent.is()) + return sub == parent; + + ASSERT(sub.is() && parent.is()); + return isSubtype(sub.as(), parent.as()); +} + inline bool isValidHeapTypeKind(TypeKind kind) { switch (kind) { @@ -232,6 +257,14 @@ inline bool isDefaultableType(Type type) return !type.isRef(); } +inline bool isDefaultableType(StorageType type) +{ + if (type.is()) + return !type.as().isRef(); + // All packed types are defaultable. + return true; +} + enum class ExternalKind : uint8_t { // FIXME auto-generate this. https://bugs.webkit.org/show_bug.cgi?id=165231 Function = 0, diff --git a/Source/JavaScriptCore/wasm/WasmFunctionParser.h b/Source/JavaScriptCore/wasm/WasmFunctionParser.h index e08c01024859..35e0185bb2df 100644 --- a/Source/JavaScriptCore/wasm/WasmFunctionParser.h +++ b/Source/JavaScriptCore/wasm/WasmFunctionParser.h @@ -143,7 +143,7 @@ class FunctionParser : public Parser, public FunctionParserTypes(extOp)) { + GCOpType extOp = static_cast(extOpInt); + switch (extOp) { case GCOpType::I31New: { TypedExpression value; WASM_TRY_POP_EXPRESSION_STACK_INTO(value, "i31.new"); @@ -1777,12 +1778,13 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) const TypeDefinition& typeDefinition = m_info.typeSignatures[typeIndex].get(); WASM_VALIDATOR_FAIL_IF(!typeDefinition.is(), "array.new index ", typeIndex, " does not reference an array definition"); - const Type elementType = typeDefinition.as()->elementType().type; + // If this is a packed array, then the value has to have type i32 + const Type unpackedElementType = typeDefinition.as()->elementType().type.unpacked(); TypedExpression value, size; WASM_TRY_POP_EXPRESSION_STACK_INTO(size, "array.new"); WASM_TRY_POP_EXPRESSION_STACK_INTO(value, "array.new"); - WASM_VALIDATOR_FAIL_IF(!isSubtype(value.type(), elementType), "array.new value to type ", value.type(), " expected ", elementType); + WASM_VALIDATOR_FAIL_IF(!isSubtype(value.type(), unpackedElementType), "array.new value to type ", value.type(), " expected ", unpackedElementType); WASM_VALIDATOR_FAIL_IF(TypeKind::I32 != size.type().kind, "array.new index to type ", size.type(), " expected ", TypeKind::I32); ExpressionType result; @@ -1798,7 +1800,7 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) const TypeDefinition& typeDefinition = m_info.typeSignatures[typeIndex].get(); WASM_VALIDATOR_FAIL_IF(!typeDefinition.is(), "array.new_default index ", typeIndex, " does not reference an array definition"); - const Type elementType = typeDefinition.as()->elementType().type; + const StorageType elementType = typeDefinition.as()->elementType().type; WASM_VALIDATOR_FAIL_IF(!isDefaultableType(elementType), "array.new_default index ", typeIndex, " does not reference an array definition with a defaultable type"); TypedExpression size; @@ -1811,25 +1813,38 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) m_expressionStack.constructAndAppend(Type { TypeKind::Ref, TypeInformation::get(typeDefinition) }, result); return { }; } - case GCOpType::ArrayGet: { + case GCOpType::ArrayGet: + case GCOpType::ArrayGetS: + case GCOpType::ArrayGetU: { uint32_t typeIndex; - WASM_PARSER_FAIL_IF(!parseVarUInt32(typeIndex), "can't get type index for array.get"); - WASM_VALIDATOR_FAIL_IF(typeIndex >= m_info.typeCount(), "array.get index ", typeIndex, " is out of bounds"); + const char* opName = extOp == GCOpType::ArrayGet ? "array.get" : extOp == GCOpType::ArrayGetS ? "array.get_s" : "array.get_u"; + WASM_PARSER_FAIL_IF(!parseVarUInt32(typeIndex), "can't get type index for ", opName); + WASM_VALIDATOR_FAIL_IF(typeIndex >= m_info.typeCount(), opName, " index ", typeIndex, " is out of bounds"); const TypeDefinition& typeDefinition = m_info.typeSignatures[typeIndex].get(); - WASM_VALIDATOR_FAIL_IF(!typeDefinition.is(), "array.get index ", typeIndex, " does not reference an array definition"); - const Type elementType = typeDefinition.as()->elementType().type; + WASM_VALIDATOR_FAIL_IF(!typeDefinition.is(), opName, " index ", typeIndex, " does not reference an array definition"); + const StorageType elementType = typeDefinition.as()->elementType().type; + // The type of the result will be unpacked if the array is packed. + const Type resultType = elementType.unpacked(); + + // array.get_s and array.get_u are only valid for packed arrays + if (extOp == GCOpType::ArrayGetS || extOp == GCOpType::ArrayGetU) + WASM_PARSER_FAIL_IF(!elementType.is(), opName, " applied to wrong type of array -- expected: i8 or i16, found ", elementType.as().kind); + + // array.get is not valid for packed arrays + if (extOp == GCOpType::ArrayGet) + WASM_PARSER_FAIL_IF(elementType.is(), opName, " applied to packed array of ", elementType.as(), " -- use array.get_s or array.get_u"); TypedExpression arrayref, index; WASM_TRY_POP_EXPRESSION_STACK_INTO(index, "array.get"); WASM_TRY_POP_EXPRESSION_STACK_INTO(arrayref, "array.get"); - WASM_VALIDATOR_FAIL_IF(!isSubtype(arrayref.type(), Type { TypeKind::RefNull, TypeInformation::get(typeDefinition) }), "array.get arrayref to type ", arrayref.type().kind, " expected arrayref"); + WASM_VALIDATOR_FAIL_IF(!isSubtype(arrayref.type(), Type { TypeKind::RefNull, TypeInformation::get(typeDefinition) }), opName, " arrayref to type ", arrayref.type().kind, " expected arrayref"); WASM_VALIDATOR_FAIL_IF(TypeKind::I32 != index.type().kind, "array.get index to type ", index.type(), " expected ", TypeKind::I32); ExpressionType result; - WASM_TRY_ADD_TO_CONTEXT(addArrayGet(typeIndex, arrayref, index, result)); + WASM_TRY_ADD_TO_CONTEXT(addArrayGet(extOp, typeIndex, arrayref, index, result)); - m_expressionStack.constructAndAppend(elementType, result); + m_expressionStack.constructAndAppend(resultType, result); return { }; } case GCOpType::ArraySet: { @@ -1841,6 +1856,8 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) WASM_VALIDATOR_FAIL_IF(!typeDefinition.is(), "array.set index ", typeIndex, " does not reference an array definition"); WASM_VALIDATOR_FAIL_IF(!typeDefinition.is(), "array.set index ", typeIndex, " does not reference an array definition"); const FieldType elementType = typeDefinition.as()->elementType(); + const Type unpackedElementType = elementType.type.unpacked(); + WASM_VALIDATOR_FAIL_IF(elementType.mutability != Mutability::Mutable, "array.set index ", typeIndex, " does not reference a mutable array definition"); TypedExpression arrayref, index, value; @@ -1849,7 +1866,7 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) WASM_TRY_POP_EXPRESSION_STACK_INTO(arrayref, "array.set"); WASM_VALIDATOR_FAIL_IF(!isSubtype(arrayref.type(), Type { TypeKind::RefNull, TypeInformation::get(typeDefinition) }), "array.set arrayref to type ", arrayref.type(), " expected arrayref"); WASM_VALIDATOR_FAIL_IF(TypeKind::I32 != index.type().kind, "array.set index to type ", index.type(), " expected ", TypeKind::I32); - WASM_VALIDATOR_FAIL_IF(!isSubtype(value.type(), elementType.type), "array.set value to type ", value.type(), " expected ", elementType.type); + WASM_VALIDATOR_FAIL_IF(!isSubtype(value.type(), unpackedElementType), "array.set value to type ", value.type(), " expected ", unpackedElementType); WASM_TRY_ADD_TO_CONTEXT(addArraySet(typeIndex, arrayref, index, value)); @@ -1883,7 +1900,7 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) for (size_t i = firstArgumentIndex; i < m_expressionStack.size(); ++i) { TypedExpression arg = m_expressionStack.at(i); - const auto& fieldType = structType->field(StructFieldCount(i - firstArgumentIndex)).type; + const auto& fieldType = structType->field(StructFieldCount(i - firstArgumentIndex)).type.unpacked(); WASM_VALIDATOR_FAIL_IF(!isSubtype(arg.type(), fieldType), "argument type mismatch in struct.new, got ", arg.type(), ", expected ", fieldType); args.uncheckedAppend(arg); m_context.didPopValueFromStack(); @@ -1904,7 +1921,7 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) const auto& structType = *m_info.typeSignatures[structGetInput.indices.structTypeIndex]->template as(); WASM_TRY_ADD_TO_CONTEXT(addStructGet(structGetInput.structReference, structType, structGetInput.indices.fieldIndex, result)); - m_expressionStack.constructAndAppend(structGetInput.field.type, result); + m_expressionStack.constructAndAppend(structGetInput.field.type.unpacked(), result); return { }; } case GCOpType::StructSet: { @@ -1916,14 +1933,14 @@ FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) const auto& field = structSetInput.field; WASM_PARSER_FAIL_IF(field.mutability != Mutability::Mutable, "the field ", structSetInput.indices.fieldIndex, " can't be set because it is immutable"); - WASM_PARSER_FAIL_IF(!isSubtype(value.type(), field.type), "type mismatch in struct.set"); + WASM_PARSER_FAIL_IF(!isSubtype(value.type(), field.type.unpacked()), "type mismatch in struct.set"); const auto& structType = *m_info.typeSignatures[structSetInput.indices.structTypeIndex]->template as(); WASM_TRY_ADD_TO_CONTEXT(addStructSet(structSetInput.structReference, structType, structSetInput.indices.fieldIndex, value)); return { }; } default: - WASM_PARSER_FAIL_IF(true, "invalid extended GC op ", extOp); + WASM_PARSER_FAIL_IF(true, "invalid extended GC op ", extOpInt); break; } return { }; @@ -2926,6 +2943,16 @@ auto FunctionParser::parseUnreachableExpression() -> PartialResult WASM_PARSER_FAIL_IF(!parseVarUInt32(unused), "can't get type index immediate for array.get in unreachable context"); return { }; } + case GCOpType::ArrayGetS: { + uint32_t unused; + WASM_PARSER_FAIL_IF(!parseVarUInt32(unused), "can't get type index immediate for array.get_s in unreachable context"); + return { }; + } + case GCOpType::ArrayGetU: { + uint32_t unused; + WASM_PARSER_FAIL_IF(!parseVarUInt32(unused), "can't get type index immediate for array.get_u in unreachable context"); + return { }; + } case GCOpType::ArraySet: { uint32_t unused; WASM_PARSER_FAIL_IF(!parseVarUInt32(unused), "can't get type index immediate for array.set in unreachable context"); diff --git a/Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp b/Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp index e03860276bdb..72e6c9d96bdc 100644 --- a/Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp +++ b/Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp @@ -314,7 +314,7 @@ class LLIntGenerator : public BytecodeGeneratorBase { PartialResult WARN_UNUSED_RETURN addI31GetU(ExpressionType ref, ExpressionType& result); PartialResult WARN_UNUSED_RETURN addArrayNew(uint32_t index, ExpressionType size, ExpressionType value, ExpressionType& result); PartialResult WARN_UNUSED_RETURN addArrayNewDefault(uint32_t index, ExpressionType size, ExpressionType& result); - PartialResult WARN_UNUSED_RETURN addArrayGet(uint32_t typeIndex, ExpressionType arrayref, ExpressionType index, ExpressionType& result); + PartialResult WARN_UNUSED_RETURN addArrayGet(GCOpType arrayGetKind, uint32_t typeIndex, ExpressionType arrayref, ExpressionType index, ExpressionType& result); PartialResult WARN_UNUSED_RETURN addArraySet(uint32_t typeIndex, ExpressionType arrayref, ExpressionType index, ExpressionType value); PartialResult WARN_UNUSED_RETURN addArrayLen(ExpressionType arrayref, ExpressionType& result); PartialResult WARN_UNUSED_RETURN addStructNew(uint32_t index, Vector& args, ExpressionType& result); @@ -1952,10 +1952,10 @@ auto LLIntGenerator::addArrayNewDefault(uint32_t index, ExpressionType size, Exp return { }; } -auto LLIntGenerator::addArrayGet(uint32_t typeIndex, ExpressionType arrayref, ExpressionType index, ExpressionType& result) -> PartialResult +auto LLIntGenerator::addArrayGet(GCOpType arrayGetKind, uint32_t typeIndex, ExpressionType arrayref, ExpressionType index, ExpressionType& result) -> PartialResult { result = push(); - WasmArrayGet::emit(this, result, arrayref, index, typeIndex); + WasmArrayGet::emit(this, result, arrayref, index, typeIndex, static_cast(arrayGetKind)); return { }; } diff --git a/Source/JavaScriptCore/wasm/WasmOperations.cpp b/Source/JavaScriptCore/wasm/WasmOperations.cpp index 8bbbebc7f4a2..398203cf1ef6 100644 --- a/Source/JavaScriptCore/wasm/WasmOperations.cpp +++ b/Source/JavaScriptCore/wasm/WasmOperations.cpp @@ -880,8 +880,11 @@ JSC_DEFINE_JIT_OPERATION(operationWasmStructNew, EncodedJSValue, (Instance* inst const StructType& structType = *structTypeDefinition->as(); JSWebAssemblyStruct* structValue = JSWebAssemblyStruct::tryCreate(globalObject, globalObject->webAssemblyStructStructure(), jsInstance, typeIndex); - for (unsigned i = 0; i < structType.fieldCount(); ++i) - structValue->set(globalObject, i, toJSValue(globalObject, structType.field(i).type, arguments[i])); + for (unsigned i = 0; i < structType.fieldCount(); ++i) { + // FIXME: https://bugs.webkit.org/show_bug.cgi?id=246981 + ASSERT(structType.field(i).type.is()); + structValue->set(globalObject, i, toJSValue(globalObject, structType.field(i).type.as(), arguments[i])); + } return JSValue::encode(structValue); } @@ -910,7 +913,11 @@ JSC_DEFINE_JIT_OPERATION(operationWasmStructSet, void, (Instance* instance, Enco JSObject* structureAsObject = jsCast(structReference); ASSERT(structureAsObject->inherits()); JSWebAssemblyStruct* structPointer = jsCast(structureAsObject); - const auto fieldType = structPointer->structType()->field(fieldIndex).type; + + // FIXME: https://bugs.webkit.org/show_bug.cgi?id=246981 + ASSERT(structPointer->structType()->field(fieldIndex).type.is()); + + const auto fieldType = structPointer->structType()->field(fieldIndex).type.as(); return structPointer->set(jsInstance->globalObject(), fieldIndex, toJSValue(jsInstance->globalObject(), fieldType, argument)); } @@ -1154,25 +1161,46 @@ JSC_DEFINE_JIT_OPERATION(operationWasmArrayNew, EncodedJSValue, (Instance* insta ASSERT(arraySignature.is()); Wasm::FieldType fieldType = arraySignature.as()->elementType(); + size_t elementSize = fieldType.type.elementSize(); + JSWebAssemblyArray* array = nullptr; - switch (fieldType.type.kind) { - case Wasm::TypeKind::I32: - case Wasm::TypeKind::F32: { + + switch (elementSize) { + case sizeof(uint8_t): { + // `encValue` must be an unboxed int32 (since the typechecker guarantees that its type is i32); so it's safe to truncate it in the cases below. + ASSERT(encValue <= UINT32_MAX); + FixedVector values(size); + for (unsigned i = 0; i < size; i++) + values[i] = static_cast(encValue); + array = JSWebAssemblyArray::create(vm, globalObject->webAssemblyArrayStructure(), fieldType, size, WTFMove(values)); + break; + } + case sizeof(uint16_t): { + ASSERT(encValue <= UINT32_MAX); + FixedVector values(size); + for (unsigned i = 0; i < size; i++) + values[i] = static_cast(encValue); + array = JSWebAssemblyArray::create(vm, globalObject->webAssemblyArrayStructure(), fieldType, size, WTFMove(values)); + break; + } + case sizeof(uint32_t): { + ASSERT(encValue <= UINT32_MAX); FixedVector values(size); for (unsigned i = 0; i < size; i++) values[i] = static_cast(encValue); - array = JSWebAssemblyArray::create(vm, globalObject->webAssemblyArrayStructure(), fieldType.type, size, WTFMove(values)); + array = JSWebAssemblyArray::create(vm, globalObject->webAssemblyArrayStructure(), fieldType, size, WTFMove(values)); break; } - default: { + case sizeof(uint64_t): { FixedVector values(size); for (unsigned i = 0; i < size; i++) values[i] = static_cast(encValue); - array = JSWebAssemblyArray::create(vm, globalObject->webAssemblyArrayStructure(), fieldType.type, size, WTFMove(values)); + array = JSWebAssemblyArray::create(vm, globalObject->webAssemblyArrayStructure(), fieldType, size, WTFMove(values)); break; } + default: + RELEASE_ASSERT_NOT_REACHED(); } - return JSValue::encode(JSValue(array)); } diff --git a/Source/JavaScriptCore/wasm/WasmSectionParser.cpp b/Source/JavaScriptCore/wasm/WasmSectionParser.cpp index 375f8fceeee5..6b0cee0b8e79 100644 --- a/Source/JavaScriptCore/wasm/WasmSectionParser.cpp +++ b/Source/JavaScriptCore/wasm/WasmSectionParser.cpp @@ -780,6 +780,36 @@ auto SectionParser::parseFunctionType(uint32_t position, RefPtr& return { }; } +auto SectionParser::parsePackedType(PackedType& packedType) -> PartialResult +{ + int8_t kind; + WASM_PARSER_FAIL_IF(!parseInt7(kind), "invalid type in struct field or array element"); + if (isValidPackedType(kind)) { + packedType = static_cast(kind); + return { }; + } + return fail("expected a packed type but got ", kind); +} + +auto SectionParser::parseStorageType(StorageType& storageType) -> PartialResult +{ + ASSERT(Options::useWebAssemblyGC()); + + int8_t kind; + WASM_PARSER_FAIL_IF(!peekInt7(kind), "invalid type in struct field or array element"); + if (isValidTypeKind(kind)) { + Type elementType; + WASM_PARSER_FAIL_IF(!parseValueType(m_info, elementType), "invalid type in struct field or array element"); + storageType = StorageType { elementType }; + return { }; + } + + PackedType elementType; + WASM_PARSER_FAIL_IF(!parsePackedType(elementType), "invalid type in struct field or array element"); + storageType = StorageType { elementType }; + return { }; +} + auto SectionParser::parseStructType(uint32_t position, RefPtr& structType) -> PartialResult { ASSERT(Options::useWebAssemblyGC()); @@ -792,15 +822,18 @@ auto SectionParser::parseStructType(uint32_t position, RefPtr& s Checked structInstancePayloadSize { 0 }; for (uint32_t fieldIndex = 0; fieldIndex < fieldCount; ++fieldIndex) { - Type fieldType; - WASM_PARSER_FAIL_IF(!parseValueType(m_info, fieldType), "can't get ", fieldIndex, "th field Type"); + StorageType fieldType; + WASM_PARSER_FAIL_IF(!parseStorageType(fieldType), "can't get ", fieldIndex, "th field Type"); + + // FIXME: https://bugs.webkit.org/show_bug.cgi?id=246981 + WASM_PARSER_FAIL_IF(fieldType.is(), "packed types in structs are not supported yet"); uint8_t mutability; WASM_PARSER_FAIL_IF(!parseUInt8(mutability), position, "can't get ", fieldIndex, "th field mutability"); WASM_PARSER_FAIL_IF(mutability != 0x0 && mutability != 0x1, "invalid Field's mutability: 0x", hex(mutability, 2, Lowercase)); fields.uncheckedAppend(FieldType { fieldType, static_cast(mutability) }); - structInstancePayloadSize += typeKindSizeInBytes(fieldType.kind); + structInstancePayloadSize += typeSizeInBytes(fieldType); WASM_PARSER_FAIL_IF(structInstancePayloadSize.hasOverflowed(), "struct layout is too big"); } @@ -812,8 +845,8 @@ auto SectionParser::parseArrayType(uint32_t position, RefPtr& ar { ASSERT(Options::useWebAssemblyGC()); - Type elementType; - WASM_PARSER_FAIL_IF(!parseValueType(m_info, elementType), "can't get array's element Type"); + StorageType elementType; + WASM_PARSER_FAIL_IF(!parseStorageType(elementType), "can't get array's element Type"); uint8_t mutability; WASM_PARSER_FAIL_IF(!parseUInt8(mutability), position, "can't get array's mutability"); diff --git a/Source/JavaScriptCore/wasm/WasmSectionParser.h b/Source/JavaScriptCore/wasm/WasmSectionParser.h index acce864b2f34..e2f354977778 100644 --- a/Source/JavaScriptCore/wasm/WasmSectionParser.h +++ b/Source/JavaScriptCore/wasm/WasmSectionParser.h @@ -70,6 +70,8 @@ class SectionParser final : public Parser { PartialResult WARN_UNUSED_RETURN parseI32InitExpr(std::optional&, ASCIILiteral failMessage); PartialResult WARN_UNUSED_RETURN parseFunctionType(uint32_t position, RefPtr&); + PartialResult WARN_UNUSED_RETURN parsePackedType(PackedType&); + PartialResult WARN_UNUSED_RETURN parseStorageType(StorageType&); PartialResult WARN_UNUSED_RETURN parseStructType(uint32_t position, RefPtr&); PartialResult WARN_UNUSED_RETURN parseArrayType(uint32_t position, RefPtr&); PartialResult WARN_UNUSED_RETURN parseRecursionGroup(uint32_t position, RefPtr&); diff --git a/Source/JavaScriptCore/wasm/WasmSlowPaths.cpp b/Source/JavaScriptCore/wasm/WasmSlowPaths.cpp index 6bccb704c2b3..3cc11eadfd0a 100644 --- a/Source/JavaScriptCore/wasm/WasmSlowPaths.cpp +++ b/Source/JavaScriptCore/wasm/WasmSlowPaths.cpp @@ -401,7 +401,7 @@ WASM_SLOW_PATH_DECL(array_new) Wasm::TypeDefinition& arraySignature = instance->module().moduleInformation().typeSignatures[instruction.m_typeIndex]; ASSERT(arraySignature.is()); - Wasm::Type elementType = arraySignature.as()->elementType().type; + Wasm::StorageType elementType = arraySignature.as()->elementType().type; EncodedJSValue value = 0; if (useDefault) { @@ -420,14 +420,23 @@ WASM_SLOW_PATH_DECL(array_get) if (JSValue::decode(arrayref).isNull()) WASM_THROW(Wasm::ExceptionType::NullArrayGet); uint32_t index = READ(instruction.m_index).unboxedUInt32(); - JSValue arrayValue = JSValue::decode(arrayref); ASSERT(arrayValue.isObject()); JSWebAssemblyArray* arrayObject = jsCast(arrayValue.getObject()); if (index >= arrayObject->size()) WASM_THROW(Wasm::ExceptionType::OutOfBoundsArrayGet); - - WASM_RETURN(Wasm::operationWasmArrayGet(instance, instruction.m_typeIndex, arrayref, index)); + Wasm::GCOpType arrayGetKind = static_cast(instruction.m_arrayGetKind); + if (arrayGetKind == Wasm::GCOpType::ArrayGetS) { + EncodedJSValue value = Wasm::operationWasmArrayGet(instance, instruction.m_typeIndex, arrayref, index); + Wasm::StorageType type = arrayObject->elementType().type; + ASSERT(type.is()); + size_t elementSize = type.as() == Wasm::PackedType::I8 ? sizeof(uint8_t) : sizeof(uint16_t); + uint8_t bitShift = (sizeof(uint32_t) - elementSize) * 8; + int32_t result = static_cast(value); + result = result << bitShift; + WASM_RETURN(static_cast(result >> bitShift)); + } else + WASM_RETURN(Wasm::operationWasmArrayGet(instance, instruction.m_typeIndex, arrayref, index)); } WASM_SLOW_PATH_DECL(array_set) diff --git a/Source/JavaScriptCore/wasm/WasmTypeDefinition.cpp b/Source/JavaScriptCore/wasm/WasmTypeDefinition.cpp index 24eb21ab6875..f878b8fe9afe 100644 --- a/Source/JavaScriptCore/wasm/WasmTypeDefinition.cpp +++ b/Source/JavaScriptCore/wasm/WasmTypeDefinition.cpp @@ -96,7 +96,7 @@ void StructType::dump(PrintStream& out) const out.print("("); CommaPrinter comma; for (StructFieldCount fieldIndex = 0; fieldIndex < fieldCount(); ++fieldIndex) { - out.print(comma, makeString(field(fieldIndex).type.kind)); + out.print(comma, makeString(field(fieldIndex).type)); out.print(comma, field(fieldIndex).mutability ? "immutable" : "mutable"); } out.print(")"); @@ -114,7 +114,7 @@ StructType::StructType(FieldType* payload, StructFieldCount fieldCount, const Fi hasRecursiveReference |= isRefWithRecursiveReference(fieldType.type); getField(fieldIndex) = fieldType; *getFieldOffset(fieldIndex) = currentFieldOffset; - currentFieldOffset += typeKindSizeInBytes(field(fieldIndex).type.kind); + currentFieldOffset += typeSizeInBytes(field(fieldIndex).type); } m_instancePayloadSize = WTF::roundUpToMultipleOf(currentFieldOffset); @@ -130,7 +130,7 @@ void ArrayType::dump(PrintStream& out) const { out.print("("); CommaPrinter comma; - out.print(comma, makeString(elementType().type.kind)); + out.print(comma, makeString(elementType().type)); out.print(comma, elementType().mutability ? "immutable" : "mutable"); out.print(")"); } @@ -183,6 +183,16 @@ void Subtype::dump(PrintStream& out) const out.print(")"); } +void StorageType::dump(PrintStream& out) const +{ + if (is()) + out.print(makeString(as().kind)); + else { + ASSERT(is()); + out.print(makeString(as())); + } +} + void TypeDefinition::cleanup() { // Only compound type definitions need to be cleaned up, not, e.g., function types. @@ -230,8 +240,8 @@ static unsigned computeStructTypeHash(size_t fieldCount, const FieldType* fields { unsigned accumulator = 0x15d2546; for (uint32_t i = 0; i < fieldCount; ++i) { - accumulator = WTF::pairIntHash(accumulator, WTF::IntHash::hash(static_cast(fields[i].type.kind))); - accumulator = WTF::pairIntHash(accumulator, WTF::IntHash::hash(static_cast(fields[i].type.index))); + accumulator = WTF::pairIntHash(accumulator, WTF::IntHash::hash(static_cast(fields[i].type.typeCode()))); + accumulator = WTF::pairIntHash(accumulator, WTF::IntHash::hash(static_cast(fields[i].type.index()))); accumulator = WTF::pairIntHash(accumulator, WTF::IntHash::hash(static_cast(fields[i].mutability))); } return accumulator; @@ -240,8 +250,8 @@ static unsigned computeStructTypeHash(size_t fieldCount, const FieldType* fields static unsigned computeArrayTypeHash(FieldType elementType) { unsigned accumulator = 0x7835ab; - accumulator = WTF::pairIntHash(accumulator, WTF::IntHash::hash(static_cast(elementType.type.kind))); - accumulator = WTF::pairIntHash(accumulator, WTF::IntHash::hash(elementType.type.index)); + accumulator = WTF::pairIntHash(accumulator, WTF::IntHash::hash(static_cast(elementType.type.typeCode()))); + accumulator = WTF::pairIntHash(accumulator, WTF::IntHash::hash(elementType.type.index())); accumulator = WTF::pairIntHash(accumulator, WTF::IntHash::hash(static_cast(elementType.mutability))); return accumulator; } @@ -427,7 +437,8 @@ const TypeDefinition& TypeDefinition::replacePlaceholders(TypeIndex projectee) c newFields.tryReserveCapacity(structType->fieldCount()); for (unsigned i = 0; i < structType->fieldCount(); i++) { FieldType field = structType->field(i); - newFields.uncheckedAppend(FieldType { substitute(field.type, projectee), field.mutability }); + StorageType substituted = field.type.is() ? field.type : StorageType(substitute(field.type.as(), projectee)); + newFields.uncheckedAppend(FieldType { substituted, field.mutability }); } RefPtr def = TypeInformation::typeDefinitionForStruct(newFields); @@ -437,7 +448,8 @@ const TypeDefinition& TypeDefinition::replacePlaceholders(TypeIndex projectee) c if (is()) { const ArrayType* arrayType = as(); FieldType field = arrayType->elementType(); - RefPtr def = TypeInformation::typeDefinitionForArray(FieldType { substitute(field.type, projectee), field.mutability }); + StorageType substituted = field.type.is() ? field.type : StorageType(substitute(field.type.as(), projectee)); + RefPtr def = TypeInformation::typeDefinitionForArray(FieldType { substituted, field.mutability }); return *def; } diff --git a/Source/JavaScriptCore/wasm/WasmTypeDefinition.h b/Source/JavaScriptCore/wasm/WasmTypeDefinition.h index f6ecca6d8076..cb50c1289599 100644 --- a/Source/JavaScriptCore/wasm/WasmTypeDefinition.h +++ b/Source/JavaScriptCore/wasm/WasmTypeDefinition.h @@ -217,8 +217,108 @@ enum Mutability : uint8_t { Immutable = 0 }; -struct FieldType { - Type type; +struct StorageType { +public: + template + bool is() const { return std::holds_alternative(m_storageType); } + + template + const T as() const { ASSERT(is()); return *std::get_if(&m_storageType); } + + StorageType() = default; + + explicit StorageType(Type t) + { + m_storageType = std::variant(t); + } + + explicit StorageType(PackedType t) + { + m_storageType = std::variant(t); + } + + // Return a value type suitable for validating instruction arguments. Packed types cannot show up as value types and need to be unpacked to I32. + Type unpacked() const + { + if (is()) + return as(); + return Types::I32; + } + + size_t elementSize() const + { + if (is()) { + switch (as().kind) { + case Wasm::TypeKind::I32: + case Wasm::TypeKind::F32: + return sizeof(uint32_t); + default: + return sizeof(uint64_t); + } + } + switch (as()) { + case PackedType::I8: + return sizeof(uint8_t); + case PackedType::I16: + return sizeof(uint16_t); + } + RELEASE_ASSERT_NOT_REACHED(); + } + + bool operator==(const StorageType& rhs) const + { + if (rhs.is()) + return (is() && as() == rhs.as()); + if (!is()) + return false; + return(as() == rhs.as()); + } + bool operator!=(const StorageType& rhs) const { return !(*this == rhs); }; + + int8_t typeCode() const + { + if (is()) + return static_cast(as().kind); + return static_cast(as()); + } + + TypeIndex index() const + { + if (is()) + return as().index; + return 0; + } + void dump(WTF::PrintStream& out) const; + +private: + std::variant m_storageType; + +}; + +inline const char* makeString(const StorageType& storageType) +{ + return(storageType.is() ? makeString(storageType.as().kind) : + makeString(storageType.as())); +} + +inline size_t typeSizeInBytes(const StorageType& storageType) +{ + if (storageType.is()) { + switch (storageType.as()) { + case PackedType::I8: { + return 1; + } + case PackedType::I16: { + return 2; + } + } + } + return typeKindSizeInBytes(storageType.as().kind); +} + +class FieldType { +public: + StorageType type; Mutability mutability; bool operator==(const FieldType& rhs) const { return type == rhs.type && mutability == rhs.mutability; } diff --git a/Source/JavaScriptCore/wasm/generateWasm.py b/Source/JavaScriptCore/wasm/generateWasm.py index d4e610e1f217..434223d346a0 100755 --- a/Source/JavaScriptCore/wasm/generateWasm.py +++ b/Source/JavaScriptCore/wasm/generateWasm.py @@ -39,6 +39,7 @@ def __init__(self, scriptName, jsonPath): self.expectedVersionNumber = str(pre["value"]) self.preamble = wasm["preamble"] self.types = wasm["type"] + self.packed_types = wasm["packed_type"] self.opcodes = wasm["opcode"] self.header = """/* * Copyright (C) 2016-2017 Apple Inc. All rights reserved. diff --git a/Source/JavaScriptCore/wasm/generateWasmOpsHeader.py b/Source/JavaScriptCore/wasm/generateWasmOpsHeader.py index 3ebc90802d19..b1f764fd06b1 100755 --- a/Source/JavaScriptCore/wasm/generateWasmOpsHeader.py +++ b/Source/JavaScriptCore/wasm/generateWasmOpsHeader.py @@ -50,6 +50,11 @@ def cppMacro(wasmOpcode, value, b3, inc, *extraArgs): extraArgsStr = ", " + ", ".join(extraArgs) if len(extraArgs) else "" return " \\\n macro(" + wasm.toCpp(wasmOpcode) + ", " + hex(int(value)) + ", " + b3 + ", " + str(inc) + extraArgsStr + ")" + +def cppMacroPacked(wasmOpcode, value): + return " \\\n macro(" + wasm.toCpp(wasmOpcode) + ", " + hex(int(value)) + ")" + + def typeMacroizer(): inc = 0 for ty in wasm.types: @@ -57,6 +62,11 @@ def typeMacroizer(): inc += 1 +def packedTypeMacroizer(): + for ty in wasm.packed_types: + yield cppMacroPacked(ty, wasm.packed_types[ty]["value"]) + + def typeMacroizerFiltered(filter): for t in typeMacroizer(): if not filter(t): @@ -64,6 +74,8 @@ def typeMacroizerFiltered(filter): type_definitions = ["#define FOR_EACH_WASM_TYPE(macro)"] type_definitions.extend([t for t in typeMacroizer()]) +type_definitions.extend(["\n\n#define FOR_EACH_WASM_PACKED_TYPE(macro)"]) +type_definitions.extend([t for t in packedTypeMacroizer()]) type_definitions = "".join(type_definitions) type_definitions_except_funcref_externref = ["#define FOR_EACH_WASM_TYPE_EXCEPT_FUNCREF_AND_EXTERNREF(macro)"] @@ -229,6 +241,12 @@ def atomicMemoryLog2AlignmentGenerator(filter): }; #undef CREATE_ENUM_VALUE +#define CREATE_ENUM_VALUE(name, id) name = id, +enum class PackedType: int8_t { + FOR_EACH_WASM_PACKED_TYPE(CREATE_ENUM_VALUE) +}; +#undef CREATE_ENUM_VALUE + using TypeIndex = uintptr_t; inline bool typeIndexIsType(TypeIndex index) @@ -305,6 +323,19 @@ def atomicMemoryLog2AlignmentGenerator(filter): } #undef CREATE_CASE +#define CREATE_CASE(name, id, ...) case id: return true; +template +inline bool isValidPackedType(Int i) +{ + switch (i) { + default: return false; + FOR_EACH_WASM_PACKED_TYPE(CREATE_CASE) + } + RELEASE_ASSERT_NOT_REACHED(); + return false; +} +#undef CREATE_CASE + #define CREATE_CASE(name, ...) case TypeKind::name: return #name; inline const char* makeString(TypeKind kind) { @@ -316,6 +347,17 @@ def atomicMemoryLog2AlignmentGenerator(filter): } #undef CREATE_CASE +#define CREATE_CASE(name, ...) case PackedType::name: return #name; +inline const char* makeString(PackedType packedType) +{ + switch (packedType) { + FOR_EACH_WASM_PACKED_TYPE(CREATE_CASE) + } + RELEASE_ASSERT_NOT_REACHED(); + return nullptr; +} +#undef CREATE_CASE + #define CREATE_CASE(name, id, b3type, inc, ...) case TypeKind::name: return inc; inline int linearizeType(TypeKind kind) { diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.cpp b/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.cpp index a00eba423636..99b719b372c3 100644 --- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.cpp +++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.cpp @@ -30,12 +30,29 @@ #include "JSCInlines.h" #include "WasmFormat.h" +#include "WasmTypeDefinition.h" namespace JSC { const ClassInfo JSWebAssemblyArray::s_info = { "WebAssembly.Array"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWebAssemblyArray) }; -JSWebAssemblyArray::JSWebAssemblyArray(VM& vm, Structure* structure, Wasm::Type elementType, size_t size, FixedVector&& payload) +JSWebAssemblyArray::JSWebAssemblyArray(VM& vm, Structure* structure, Wasm::FieldType elementType, size_t size, FixedVector&& payload) + : Base(vm, structure) + , m_elementType(elementType) + , m_size(size) + , m_payload8(WTFMove(payload)) +{ +} + +JSWebAssemblyArray::JSWebAssemblyArray(VM& vm, Structure* structure, Wasm::FieldType elementType, size_t size, FixedVector&& payload) + : Base(vm, structure) + , m_elementType(elementType) + , m_size(size) + , m_payload16(WTFMove(payload)) +{ +} + +JSWebAssemblyArray::JSWebAssemblyArray(VM& vm, Structure* structure, Wasm::FieldType elementType, size_t size, FixedVector&& payload) : Base(vm, structure) , m_elementType(elementType) , m_size(size) @@ -43,7 +60,7 @@ JSWebAssemblyArray::JSWebAssemblyArray(VM& vm, Structure* structure, Wasm::Type { } -JSWebAssemblyArray::JSWebAssemblyArray(VM& vm, Structure* structure, Wasm::Type elementType, size_t size, FixedVector&& payload) +JSWebAssemblyArray::JSWebAssemblyArray(VM& vm, Structure* structure, Wasm::FieldType elementType, size_t size, FixedVector&& payload) : Base(vm, structure) , m_elementType(elementType) , m_size(size) @@ -53,7 +70,19 @@ JSWebAssemblyArray::JSWebAssemblyArray(VM& vm, Structure* structure, Wasm::Type JSWebAssemblyArray::~JSWebAssemblyArray() { - switch (m_elementType.kind) { + if (m_elementType.type.is()) { + switch (m_elementType.type.as()) { + case Wasm::PackedType::I8: + m_payload8.~FixedVector(); + break; + case Wasm::PackedType::I16: + m_payload16.~FixedVector(); + break; + } + return; + } + + switch (m_elementType.type.as().kind) { case Wasm::TypeKind::I32: case Wasm::TypeKind::F32: m_payload32.~FixedVector(); @@ -88,7 +117,7 @@ void JSWebAssemblyArray::visitChildrenImpl(JSCell* cell, Visitor& visitor) Base::visitChildren(thisObject, visitor); - if (isRefType(thisObject->elementType())) { + if (isRefType(thisObject->elementType().type)) { for (unsigned i = 0; i < thisObject->size(); ++i) visitor.append(bitwise_cast>(thisObject->get(i))); } diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h b/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h index 8b1e8e1c6ae1..21ccb8c6b2ad 100644 --- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h +++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyArray.h @@ -29,6 +29,7 @@ #include "JSObject.h" #include "WasmOps.h" +#include "WasmTypeDefinition.h" namespace JSC { @@ -54,28 +55,33 @@ class JSWebAssemblyArray final : public JSNonFinalObject { return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); } - static JSWebAssemblyArray* create(VM& vm, Structure* structure, Wasm::Type elementType, size_t size, FixedVector&& payload) + template + static JSWebAssemblyArray* create(VM& vm, Structure* structure, Wasm::FieldType elementType, size_t size, FixedVector&& payload) { JSWebAssemblyArray* array = new (NotNull, allocateCell(vm)) JSWebAssemblyArray(vm, structure, elementType, size, WTFMove(payload)); array->finishCreation(vm); return array; - } - static JSWebAssemblyArray* create(VM& vm, Structure* structure, Wasm::Type elementType, size_t size, FixedVector&& payload) - { - JSWebAssemblyArray* array = new (NotNull, allocateCell(vm)) JSWebAssemblyArray(vm, structure, elementType, size, WTFMove(payload)); - array->finishCreation(vm); - return array; } DECLARE_VISIT_CHILDREN; - Wasm::Type elementType() const { return m_elementType; } + Wasm::FieldType elementType() const { return m_elementType; } size_t size() const { return m_size; } EncodedJSValue get(uint32_t index) { - switch (m_elementType.kind) { + if (m_elementType.type.is()) { + switch (m_elementType.type.as()) { + case Wasm::PackedType::I8: + return static_cast(m_payload8[index]); + case Wasm::PackedType::I16: + return static_cast(m_payload16[index]); + } + } + // m_element_type must be a type, so we can get its kind + ASSERT(m_elementType.type.is()); + switch (m_elementType.type.as().kind) { case Wasm::TypeKind::I32: case Wasm::TypeKind::F32: return static_cast(m_payload32[index]); @@ -86,7 +92,23 @@ class JSWebAssemblyArray final : public JSNonFinalObject { void set(VM& vm, uint32_t index, EncodedJSValue value) { - switch (m_elementType.kind) { + if (m_elementType.type.is()) { + // `value` is assumed to be an unboxed int32; truncate it to either 8 or 16 bits + ASSERT(value <= UINT32_MAX); + switch (m_elementType.type.as()) { + case Wasm::PackedType::I8: + m_payload8[index] = static_cast(value); + break; + case Wasm::PackedType::I16: + m_payload16[index] = static_cast(value); + break; + } + return; + } + + ASSERT(m_elementType.type.is()); + + switch (m_elementType.type.as().kind) { case Wasm::TypeKind::I32: case Wasm::TypeKind::F32: m_payload32[index] = static_cast(value); @@ -118,18 +140,22 @@ class JSWebAssemblyArray final : public JSNonFinalObject { } protected: - JSWebAssemblyArray(VM&, Structure*, Wasm::Type, size_t, FixedVector&&); - JSWebAssemblyArray(VM&, Structure*, Wasm::Type, size_t, FixedVector&&); + JSWebAssemblyArray(VM&, Structure*, Wasm::FieldType, size_t, FixedVector&&); + JSWebAssemblyArray(VM&, Structure*, Wasm::FieldType, size_t, FixedVector&&); + JSWebAssemblyArray(VM&, Structure*, Wasm::FieldType, size_t, FixedVector&&); + JSWebAssemblyArray(VM&, Structure*, Wasm::FieldType, size_t, FixedVector&&); ~JSWebAssemblyArray(); void finishCreation(VM&); - Wasm::Type m_elementType; + Wasm::FieldType m_elementType; size_t m_size; // A union is used here to ensure the underlying storage is aligned correctly. // The payload member used entirely depends on m_elementType, so no tag is required. union { + FixedVector m_payload8; + FixedVector m_payload16; FixedVector m_payload32; FixedVector m_payload64; }; diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyStruct.cpp b/Source/JavaScriptCore/wasm/js/JSWebAssemblyStruct.cpp index 9ae3f8e4cadd..97a5376a97b3 100644 --- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyStruct.cpp +++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyStruct.cpp @@ -83,7 +83,12 @@ uint64_t JSWebAssemblyStruct::get(uint32_t fieldIndex) const using Wasm::TypeKind; const uint8_t* targetPointer = fieldPointer(fieldIndex); - switch (fieldType(fieldIndex).type.kind) { + + // FIXME: packed types in structs not supported yet: + // https://bugs.webkit.org/show_bug.cgi?id=246981 + ASSERT(fieldType(fieldIndex).type.is()); + + switch (fieldType(fieldIndex).type.as().kind) { case TypeKind::I32: case TypeKind::F32: return *bitwise_cast(targetPointer); @@ -106,7 +111,12 @@ void JSWebAssemblyStruct::set(JSGlobalObject* globalObject, uint32_t fieldIndex, using Wasm::TypeKind; uint8_t* targetPointer = fieldPointer(fieldIndex); - switch (fieldType(fieldIndex).type.kind) { + + // FIXME: packed types in structs not supported yet: + // https://bugs.webkit.org/show_bug.cgi?id=246981 + ASSERT(fieldType(fieldIndex).type.is()); + + switch (fieldType(fieldIndex).type.as().kind) { case TypeKind::I32: { *bitwise_cast(targetPointer) = argument.toInt32(globalObject); return; diff --git a/Source/JavaScriptCore/wasm/wasm.json b/Source/JavaScriptCore/wasm/wasm.json index c934de2b8fb2..34ab4872839b 100644 --- a/Source/JavaScriptCore/wasm/wasm.json +++ b/Source/JavaScriptCore/wasm/wasm.json @@ -25,6 +25,10 @@ "rec": { "type": "varint7", "value": -49, "b3type": "B3::Void", "width": 0 }, "void": { "type": "varint7", "value": -64, "b3type": "B3::Void", "width": 0 } }, + "packed_type": { + "i8": { "type": "varint7", "value": -6}, + "i16": { "type": "varint7", "value": -7} + }, "value_type": ["i32", "i64", "f32", "f64", "externref", "funcref", "v128"], "block_type": ["i32", "i64", "f32", "f64", "void", "externref", "funcref", "v128"], "ref_type": ["funcref", "externref", "ref", "ref_null"], @@ -266,6 +270,8 @@ "array.new": { "category": "gc", "value": 251, "return": ["arrayref"], "parameter": ["any", "i32", "rtt"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 17 }, "array.new_default": { "category": "gc", "value": 251, "return": ["arrayref"], "parameter": ["i32", "rtt"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 18 }, "array.get": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["arrayref", "i32"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 19 }, + "array.get_s": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["arrayref", "i32"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 20 }, + "array.get_u": { "category": "gc", "value": 251, "return": ["any"], "parameter": ["arrayref", "i32"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 21 }, "array.set": { "category": "gc", "value": 251, "return": [], "parameter": ["arrayref", "i32", "any"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 22 }, "array.len": { "category": "gc", "value": 251, "return": ["i32"], "parameter": ["arrayref"], "immediate": [{"name": "type_index", "type": "varuint32"}], "extendedOp": 23 }, "i31.new": { "category": "gc", "value": 251, "return": ["i31ref"], "parameter": ["i32"], "immediate": [], "extendedOp": 32 },