mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Bug 1774840 - Implement new GC instructions in baseline (Part 3). r=rhunt.
Adds wasm baseline implementation and test cases for `array.new_fixed`. Differential Revision: https://phabricator.services.mozilla.com/D154230
This commit is contained in:
parent
c5d5216d8e
commit
4ebfb64753
@ -277,3 +277,191 @@ for ([fieldtype, packed] of [
|
||||
wasmValidateText(noExtensionModule);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// array.new_fixed
|
||||
/*
|
||||
validation:
|
||||
array-type imm-operand needs to be "in range"
|
||||
array-type imm-operand must refer to an array type
|
||||
operands (on stack) must all match ("be compatible with") the array elem
|
||||
type
|
||||
number of operands (on stack) must not be less than the num-of-elems
|
||||
imm-operand
|
||||
zero elements doesn't fail compilation
|
||||
reftypes elements doesn't fail compilation
|
||||
trying to create a 1-billion-element array fails gracefully
|
||||
run:
|
||||
resulting 4-element array is as expected
|
||||
resulting zero-element array is as expected
|
||||
resulting 30-element array is as expected
|
||||
*/
|
||||
|
||||
// validation: array-type imm-operand needs to be "in range"
|
||||
assertErrorMessage(() => wasmEvalText(`(module
|
||||
(type $a (array i8))
|
||||
(func (result eqref)
|
||||
i32.const 66
|
||||
i32.const 77
|
||||
array.new_fixed 2 2 ;; type index 2 is the first invalid one
|
||||
)
|
||||
)
|
||||
`), WebAssembly.CompileError, /type index out of range/);
|
||||
|
||||
// validation: array-type imm-operand must refer to an array type
|
||||
assertErrorMessage(() => wasmEvalText(`(module
|
||||
(type $a (func (param f64) (result f64)))
|
||||
(func (result eqref)
|
||||
i32.const 66
|
||||
i32.const 77
|
||||
array.new_fixed $a 2
|
||||
)
|
||||
)
|
||||
`), WebAssembly.CompileError, /not an array type/);
|
||||
|
||||
// validation: operands (on stack) must all match ("be compatible with")
|
||||
// the array elem type
|
||||
assertErrorMessage(() => wasmEvalText(`(module
|
||||
(type $a (array i32))
|
||||
(func (result eqref)
|
||||
f32.const 66.6
|
||||
f64.const 77.7
|
||||
array.new_fixed $a 2
|
||||
)
|
||||
)
|
||||
`), WebAssembly.CompileError, /expression has type f64 but expected i32/);
|
||||
|
||||
// validation: number of operands (on stack) must not be less than the
|
||||
// num-of-elems imm-operand
|
||||
assertNoWarning(() => wasmEvalText(`(module
|
||||
(type $a (array f32))
|
||||
(func
|
||||
f64.const 66.6 ;; we won't put this in the array
|
||||
f32.const 77.7
|
||||
f32.const 88.8
|
||||
array.new_fixed $a 2 ;; use up 88.8 and 77.7 and replace with array
|
||||
drop ;; dump the array
|
||||
f64.const 99.9
|
||||
f64.mul ;; check the 66.6 value is still on the stack
|
||||
drop ;; now should be empty
|
||||
)
|
||||
)
|
||||
`));
|
||||
// (more)
|
||||
assertErrorMessage(() => wasmEvalText(`(module
|
||||
(type $a (array i64))
|
||||
(func (param i64) (result eqref)
|
||||
local.get 0
|
||||
array.new_fixed $a 2
|
||||
)
|
||||
)
|
||||
`), WebAssembly.CompileError, /popping value from empty stack/);
|
||||
|
||||
// validation: zero elements doesn't fail compilation
|
||||
assertNoWarning(() => wasmEvalText(`(module
|
||||
(type $a (array i32))
|
||||
(func (result eqref)
|
||||
array.new_fixed $a 0
|
||||
)
|
||||
)
|
||||
`));
|
||||
|
||||
// validation: reftyped elements doesn't fail compilation
|
||||
assertNoWarning(() => wasmEvalText(`(module
|
||||
(type $a (array eqref))
|
||||
(func (param eqref) (result eqref)
|
||||
local.get 0
|
||||
array.new_fixed $a 1
|
||||
)
|
||||
)
|
||||
`));
|
||||
|
||||
// validation: trying to create a 1-billion-element array fails gracefully
|
||||
assertErrorMessage(() => wasmEvalText(`(module
|
||||
(type $a (array f32))
|
||||
(func (export "newFixed") (result eqref)
|
||||
f32.const 1337.0
|
||||
f32.const 4771.0
|
||||
array.new_fixed $a 1000000000
|
||||
)
|
||||
)
|
||||
`), WebAssembly.CompileError, /popping value from empty stack/);
|
||||
|
||||
// run: resulting 4-element array is as expected
|
||||
{
|
||||
let { newFixed } = wasmEvalText(`(module
|
||||
(type $a (array i8))
|
||||
(func (export "newFixed") (result eqref)
|
||||
(; the spec seems ambiguous about the operand ordering here ;)
|
||||
i32.const 66
|
||||
i32.const 77
|
||||
i32.const 88
|
||||
i32.const 99
|
||||
array.new_fixed $a 4
|
||||
)
|
||||
)`).exports;
|
||||
let a = newFixed();
|
||||
assertEq(a.length, 4);
|
||||
assertEq(a[0], 99);
|
||||
assertEq(a[1], 88);
|
||||
assertEq(a[2], 77);
|
||||
assertEq(a[3], 66);
|
||||
}
|
||||
|
||||
// run: resulting zero-element array is as expected
|
||||
{
|
||||
let { newFixed } = wasmEvalText(`(module
|
||||
(type $a (array i16))
|
||||
(func (export "newFixed") (result eqref)
|
||||
array.new_fixed $a 0
|
||||
)
|
||||
)`).exports;
|
||||
let a = newFixed();
|
||||
assertEq(a.length, 0);
|
||||
}
|
||||
|
||||
// run: resulting 30-element array is as expected
|
||||
{
|
||||
let { newFixed } = wasmEvalText(`(module
|
||||
(type $a (array i16))
|
||||
(func (export "newFixed") (result eqref)
|
||||
i32.const 1
|
||||
i32.const 2
|
||||
i32.const 3
|
||||
i32.const 4
|
||||
i32.const 5
|
||||
i32.const 6
|
||||
i32.const 7
|
||||
i32.const 8
|
||||
i32.const 9
|
||||
i32.const 10
|
||||
i32.const 11
|
||||
i32.const 12
|
||||
i32.const 13
|
||||
i32.const 14
|
||||
i32.const 15
|
||||
i32.const 16
|
||||
i32.const 17
|
||||
i32.const 18
|
||||
i32.const 19
|
||||
i32.const 20
|
||||
i32.const 21
|
||||
i32.const 22
|
||||
i32.const 23
|
||||
i32.const 24
|
||||
i32.const 25
|
||||
i32.const 26
|
||||
i32.const 27
|
||||
i32.const 28
|
||||
i32.const 29
|
||||
i32.const 30
|
||||
array.new_fixed $a 30
|
||||
)
|
||||
)`).exports;
|
||||
let a = newFixed();
|
||||
assertEq(a.length, 30);
|
||||
for (i = 0; i < 30; i++) {
|
||||
assertEq(a[i], 30 - i);
|
||||
}
|
||||
}
|
||||
|
@ -1626,6 +1626,7 @@ struct BaseCompiler final {
|
||||
[[nodiscard]] bool emitStructGet(FieldExtension extension);
|
||||
[[nodiscard]] bool emitStructSet();
|
||||
[[nodiscard]] bool emitArrayNew();
|
||||
[[nodiscard]] bool emitArrayNewFixed();
|
||||
[[nodiscard]] bool emitArrayNewDefault();
|
||||
[[nodiscard]] bool emitArrayGet(FieldExtension extension);
|
||||
[[nodiscard]] bool emitArraySet();
|
||||
@ -1637,6 +1638,7 @@ struct BaseCompiler final {
|
||||
void emitGcCanon(uint32_t typeIndex);
|
||||
void emitGcNullCheck(RegRef rp);
|
||||
RegPtr emitGcArrayGetData(RegRef rp);
|
||||
void emitGcArrayAdjustDataPointer(RegPtr rdata);
|
||||
RegI32 emitGcArrayGetNumElements(RegPtr rdata, bool adjustDataPointer);
|
||||
void emitGcArrayBoundsCheck(RegI32 index, RegI32 length);
|
||||
template <typename T>
|
||||
|
@ -6359,6 +6359,17 @@ RegPtr BaseCompiler::emitGcArrayGetData(RegRef rp) {
|
||||
return rdata;
|
||||
}
|
||||
|
||||
void BaseCompiler::emitGcArrayAdjustDataPointer(RegPtr rdata) {
|
||||
// `rdata` points at the start of an OutlineTypedObject's out of line array
|
||||
// (iow it holds the value of a OutLineTypedObject::data_ field). Move it
|
||||
// forwards so it points at the first byte of the first array element stored
|
||||
// there.
|
||||
STATIC_ASSERT_NUMELEMENTS_IS_U32;
|
||||
masm.addPtr(ImmWord(OutlineTypedObject::offsetOfNumElements() +
|
||||
sizeof(OutlineTypedObject::NumElements)),
|
||||
rdata);
|
||||
}
|
||||
|
||||
RegI32 BaseCompiler::emitGcArrayGetNumElements(RegPtr rdata,
|
||||
bool adjustDataPointer) {
|
||||
STATIC_ASSERT_NUMELEMENTS_IS_U32;
|
||||
@ -6366,9 +6377,7 @@ RegI32 BaseCompiler::emitGcArrayGetNumElements(RegPtr rdata,
|
||||
masm.load32(Address(rdata, OutlineTypedObject::offsetOfNumElements()),
|
||||
length);
|
||||
if (adjustDataPointer) {
|
||||
masm.addPtr(ImmWord(OutlineTypedObject::offsetOfNumElements() +
|
||||
sizeof(OutlineTypedObject::NumElements)),
|
||||
rdata);
|
||||
emitGcArrayAdjustDataPointer(rdata);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
@ -6862,6 +6871,77 @@ bool BaseCompiler::emitArrayNew() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseCompiler::emitArrayNewFixed() {
|
||||
uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
|
||||
|
||||
uint32_t typeIndex, numElements;
|
||||
if (!iter_.readArrayNewFixed(&typeIndex, &numElements)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (deadCode_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const ArrayType& arrayType = (*moduleEnv_.types)[typeIndex].arrayType();
|
||||
|
||||
// At this point, the top section of the value stack contains the values to
|
||||
// be used to initialise the array, with index 0 as the topmost value. Push
|
||||
// the required number of elements and the required type on, since the call
|
||||
// to SASigArrayNew will use them.
|
||||
pushI32(numElements);
|
||||
emitGcCanon(typeIndex);
|
||||
if (!emitInstanceCall(lineOrBytecode, SASigArrayNew)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reserve this register early if we will need it so that it is not taken by
|
||||
// any register used in this function.
|
||||
bool avoidPreBarrierReg = arrayType.elementType_.isRefRepr();
|
||||
if (avoidPreBarrierReg) {
|
||||
needPtr(RegPtr(PreBarrierReg));
|
||||
}
|
||||
|
||||
// Get hold of the pointer to the array, as created by SASigArrayNew.
|
||||
RegRef rp = popRef();
|
||||
|
||||
// Acquire the data pointers from the object
|
||||
RegPtr rdata = emitGcArrayGetData(rp);
|
||||
|
||||
// Free the barrier reg if we previously reserved it.
|
||||
if (avoidPreBarrierReg) {
|
||||
freePtr(RegPtr(PreBarrierReg));
|
||||
}
|
||||
|
||||
// Adjust the data pointer to be immediately after the array length header
|
||||
emitGcArrayAdjustDataPointer(rdata);
|
||||
|
||||
// Generate straight-line initialization code. We could do better here if
|
||||
// there was a version of ::emitGcArraySet that took `index` as a `uint32_t`
|
||||
// rather than a general value-in-a-reg.
|
||||
for (uint32_t i = 0; i < numElements; i++) {
|
||||
if (avoidPreBarrierReg) {
|
||||
needPtr(RegPtr(PreBarrierReg));
|
||||
}
|
||||
AnyReg value = popAny();
|
||||
pushI32(i);
|
||||
RegI32 index = popI32();
|
||||
if (avoidPreBarrierReg) {
|
||||
freePtr(RegPtr(PreBarrierReg));
|
||||
}
|
||||
if (!emitGcArraySet(rp, rdata, index, arrayType, value)) {
|
||||
return false;
|
||||
}
|
||||
freeI32(index);
|
||||
freeAny(value);
|
||||
}
|
||||
|
||||
freePtr(rdata);
|
||||
|
||||
pushRef(rp);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseCompiler::emitArrayNewDefault() {
|
||||
uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
|
||||
|
||||
@ -9232,6 +9312,8 @@ bool BaseCompiler::emitBody() {
|
||||
CHECK_NEXT(emitStructSet());
|
||||
case uint32_t(GcOp::ArrayNew):
|
||||
CHECK_NEXT(emitArrayNew());
|
||||
case uint32_t(GcOp::ArrayNewFixed):
|
||||
CHECK_NEXT(emitArrayNewFixed());
|
||||
case uint32_t(GcOp::ArrayNewDefault):
|
||||
CHECK_NEXT(emitArrayNewDefault());
|
||||
case uint32_t(GcOp::ArrayGet):
|
||||
|
@ -461,6 +461,7 @@ enum class GcOp {
|
||||
|
||||
// Array operations
|
||||
ArrayNew = 0x1b,
|
||||
ArrayNewFixed = 0x1a,
|
||||
ArrayNewDefault = 0x1c,
|
||||
ArrayGet = 0x13,
|
||||
ArrayGetS = 0x14,
|
||||
|
@ -311,6 +311,8 @@ OpKind wasm::Classify(OpBytes op) {
|
||||
WASM_GC_OP(OpKind::StructSet);
|
||||
case GcOp::ArrayNew:
|
||||
WASM_GC_OP(OpKind::ArrayNew);
|
||||
case GcOp::ArrayNewFixed:
|
||||
WASM_GC_OP(OpKind::ArrayNewFixed);
|
||||
case GcOp::ArrayNewDefault:
|
||||
WASM_GC_OP(OpKind::ArrayNewDefault);
|
||||
case GcOp::ArrayGet:
|
||||
|
@ -186,6 +186,7 @@ enum class OpKind {
|
||||
StructGet,
|
||||
StructSet,
|
||||
ArrayNew,
|
||||
ArrayNewFixed,
|
||||
ArrayNewDefault,
|
||||
ArrayGet,
|
||||
ArraySet,
|
||||
@ -688,6 +689,8 @@ class MOZ_STACK_CLASS OpIter : private Policy {
|
||||
Value* ptr, Value* val);
|
||||
[[nodiscard]] bool readArrayNew(uint32_t* typeIndex, Value* numElements,
|
||||
Value* argValue);
|
||||
[[nodiscard]] bool readArrayNewFixed(uint32_t* typeIndex,
|
||||
uint32_t* numElements);
|
||||
[[nodiscard]] bool readArrayNewDefault(uint32_t* typeIndex,
|
||||
Value* numElements);
|
||||
[[nodiscard]] bool readArrayGet(uint32_t* typeIndex, FieldExtension extension,
|
||||
@ -3209,6 +3212,34 @@ inline bool OpIter<Policy>::readArrayNew(uint32_t* typeIndex,
|
||||
return push(RefType::fromTypeIndex(*typeIndex, false));
|
||||
}
|
||||
|
||||
template <typename Policy>
|
||||
inline bool OpIter<Policy>::readArrayNewFixed(uint32_t* typeIndex,
|
||||
uint32_t* numElements) {
|
||||
MOZ_ASSERT(Classify(op_) == OpKind::ArrayNewFixed);
|
||||
|
||||
if (!readArrayTypeIndex(typeIndex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ArrayType& arrayType = env_.types->arrayType(*typeIndex);
|
||||
|
||||
if (!readVarU32(numElements)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For use with Ion, it may be necessary to add a third parameter
|
||||
// of type `Vector<Value>*` into which this loop copies values.
|
||||
ValType widenedElementType = arrayType.elementType_.widenToValType();
|
||||
for (uint32_t i = 0; i < *numElements; i++) {
|
||||
Value v;
|
||||
if (!popWithType(widenedElementType, &v)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return push(RefType::fromTypeIndex(*typeIndex, false));
|
||||
}
|
||||
|
||||
template <typename Policy>
|
||||
inline bool OpIter<Policy>::readArrayNewDefault(uint32_t* typeIndex,
|
||||
Value* numElements) {
|
||||
|
@ -585,6 +585,10 @@ static bool DecodeFunctionBodyExprs(const ModuleEnvironment& env,
|
||||
uint32_t unusedUint;
|
||||
CHECK(iter.readArrayNew(&unusedUint, ¬hing, ¬hing));
|
||||
}
|
||||
case uint32_t(GcOp::ArrayNewFixed): {
|
||||
uint32_t unusedUint1, unusedUint2;
|
||||
CHECK(iter.readArrayNewFixed(&unusedUint1, &unusedUint2));
|
||||
}
|
||||
case uint32_t(GcOp::ArrayNewDefault): {
|
||||
uint32_t unusedUint;
|
||||
CHECK(iter.readArrayNewDefault(&unusedUint, ¬hing));
|
||||
|
Loading…
Reference in New Issue
Block a user