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:
Julian Seward 2022-08-15 10:08:39 +00:00
parent c5d5216d8e
commit 4ebfb64753
7 changed files with 313 additions and 3 deletions

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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):

View File

@ -461,6 +461,7 @@ enum class GcOp {
// Array operations
ArrayNew = 0x1b,
ArrayNewFixed = 0x1a,
ArrayNewDefault = 0x1c,
ArrayGet = 0x13,
ArrayGetS = 0x14,

View File

@ -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:

View File

@ -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) {

View File

@ -585,6 +585,10 @@ static bool DecodeFunctionBodyExprs(const ModuleEnvironment& env,
uint32_t unusedUint;
CHECK(iter.readArrayNew(&unusedUint, &nothing, &nothing));
}
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, &nothing));