[wasm-gc] Add array.init constant expression

Bug: v8:7748
Change-Id: I3fa510b4dc35d3f58532ecbbeecd79d2826ff667
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2951722
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75115}
This commit is contained in:
Manos Koukoutos 2021-06-11 14:17:07 +00:00 committed by V8 LUCI CQ
parent e4921a2ae4
commit 546929280e
18 changed files with 320 additions and 7 deletions

View File

@ -1457,6 +1457,29 @@ Handle<WasmCapiFunctionData> Factory::NewWasmCapiFunctionData(
return handle(result, isolate());
}
Handle<WasmArray> Factory::NewWasmArray(
const wasm::ArrayType* type, const std::vector<wasm::WasmValue>& elements,
Handle<Map> map) {
uint32_t length = static_cast<uint32_t>(elements.size());
HeapObject raw =
AllocateRaw(WasmArray::SizeFor(*map, length), AllocationType::kYoung);
raw.set_map_after_allocation(*map);
WasmArray result = WasmArray::cast(raw);
result.set_raw_properties_or_hash(*empty_fixed_array());
result.set_length(length);
for (uint32_t i = 0; i < length; i++) {
Address address = result.ElementAddress(i);
if (type->element_type().is_numeric()) {
elements[i]
.Packed(type->element_type())
.CopyTo(reinterpret_cast<byte*>(address));
} else {
base::WriteUnalignedValue<Object>(address, *elements[i].to_ref());
}
}
return handle(result, isolate());
}
Handle<WasmStruct> Factory::NewWasmStruct(const wasm::StructType* type,
wasm::WasmValue* args,
Handle<Map> map) {

View File

@ -71,6 +71,7 @@ class WasmJSFunctionData;
class WeakCell;
#if V8_ENABLE_WEBASSEMBLY
namespace wasm {
class ArrayType;
class StructType;
class WasmValue;
} // namespace wasm
@ -578,6 +579,9 @@ class V8_EXPORT_PRIVATE Factory : public FactoryBase<Factory> {
Handle<Code> wrapper_code);
Handle<WasmStruct> NewWasmStruct(const wasm::StructType* type,
wasm::WasmValue* args, Handle<Map> map);
Handle<WasmArray> NewWasmArray(const wasm::ArrayType* type,
const std::vector<wasm::WasmValue>& elements,
Handle<Map> map);
Handle<SharedFunctionInfo> NewSharedFunctionInfoForWasmExportedFunction(
Handle<String> name, Handle<WasmExportedFunctionData> data);

View File

@ -1823,7 +1823,10 @@ class ModuleDecoderImpl : public Decoder {
len += imm.length;
const StructType* type = module->struct_type(imm.index);
if (!V8_LIKELY(stack.size() >= type->field_count() + 1)) {
error(pc(), "not enough arguments for struct.new");
errorf(pc(),
"not enough arguments on the stack for struct.new: "
"expected %u, found %zu",
type->field_count() + 1, stack.size());
return {};
}
std::vector<WasmInitExpr> arguments(type->field_count() + 1);
@ -1856,6 +1859,71 @@ class ModuleDecoderImpl : public Decoder {
imm.index, std::move(arguments)));
break;
}
case kExprArrayInit: {
if (!V8_LIKELY(enabled_features_.has_gc_experiments())) {
error(pc(),
"invalid opcode array.init in init. expression, enable "
"with --experimental-wasm-gc-experiments");
return {};
}
IndexImmediate<validate> array_imm(this, pc() + len,
"array index");
if (!V8_LIKELY(module->has_array(array_imm.index))) {
errorf(pc() + len, "invalid array type index #%u",
array_imm.index);
return {};
}
IndexImmediate<validate> length_imm(
this, pc() + len + array_imm.length, "array.init length");
uint32_t elem_count = length_imm.index;
if (elem_count > kV8MaxWasmArrayInitLength) {
errorf(pc() + len + array_imm.length,
"Requested length %u for array.init too large, maximum "
"is %zu",
length_imm.index, kV8MaxWasmArrayInitLength);
return {};
}
len += array_imm.length + length_imm.length;
const ArrayType* array_type =
module_->array_type(array_imm.index);
if (stack.size() < elem_count + 1) {
errorf(pc(),
"not enough arguments on the stack for array.init: "
"expected %u, found %zu",
elem_count + 1, stack.size());
return {};
}
std::vector<WasmInitExpr> arguments(elem_count + 1);
WasmInitExpr* stack_args = &stack.back() - elem_count;
for (uint32_t i = 0; i < elem_count; i++) {
WasmInitExpr& argument = stack_args[i];
if (!IsSubtypeOf(TypeOf(argument),
array_type->element_type().Unpacked(),
module)) {
errorf(pc(), "array.init[%u]: expected %s, found %s instead",
i, array_type->element_type().name().c_str(),
TypeOf(argument).name().c_str());
return {};
}
arguments[i] = std::move(argument);
}
WasmInitExpr& rtt = stack.back();
if (!IsSubtypeOf(TypeOf(rtt), ValueType::Rtt(array_imm.index),
module)) {
errorf(pc(), "array.init[%u]: expected %s, found %s instead",
elem_count,
ValueType::Rtt(array_imm.index).name().c_str(),
TypeOf(rtt).name().c_str());
return {};
}
arguments[elem_count] = std::move(rtt);
for (uint32_t i = 0; i <= elem_count; i++) {
stack.pop_back();
}
stack.push_back(WasmInitExpr::ArrayInit(array_imm.index,
std::move(arguments)));
break;
}
case kExprRttCanon: {
IndexImmediate<validate> imm(this, pc() + len, "type index");
if (V8_UNLIKELY(!module_->has_type(imm.index))) {
@ -2049,7 +2117,7 @@ class ModuleDecoderImpl : public Decoder {
ValueType field = consume_storage_type();
if (failed()) return nullptr;
bool mutability = consume_mutability();
if (!mutability) {
if (!V8_LIKELY(mutability)) {
error(this->pc() - 1, "immutable arrays are not supported yet");
}
return zone->New<ArrayType>(field, mutability);

View File

@ -1576,6 +1576,17 @@ WasmValue InstanceBuilder::EvaluateInitExpression(
isolate_->factory()->NewWasmStruct(type, fields.data(), rtt),
init.type(module_, enabled_));
}
case WasmInitExpr::kArrayInit: {
const ArrayType* type = module_->array_type(init.immediate().index);
std::vector<WasmValue> elements(init.operands().size() - 1);
for (uint32_t i = 0; i < elements.size(); i++) {
elements[i] = EvaluateInitExpression(init.operands()[i], instance);
}
auto rtt = Handle<Map>::cast(
EvaluateInitExpression(init.operands().back(), instance).to_ref());
return WasmValue(isolate_->factory()->NewWasmArray(type, elements, rtt),
init.type(module_, enabled_));
}
case WasmInitExpr::kRttCanon: {
int map_index = init.immediate().index;
return WasmValue(

View File

@ -39,6 +39,7 @@ ValueType WasmInitExpr::type(const WasmModule* module,
case kRefNullConst:
return ValueType::Ref(immediate().heap_type, kNullable);
case kStructNewWithRtt:
case kArrayInit:
return ValueType::Ref(immediate().index, kNonNullable);
case kRttCanon:
return ValueType::Rtt(immediate().heap_type, 0);

View File

@ -34,6 +34,7 @@ class WasmInitExpr {
kRefNullConst,
kRefFuncConst,
kStructNewWithRtt,
kArrayInit,
kRttCanon,
kRttSub,
kRttFreshSub,
@ -98,6 +99,15 @@ class WasmInitExpr {
return expr;
}
static WasmInitExpr ArrayInit(uint32_t index,
std::vector<WasmInitExpr> elements) {
WasmInitExpr expr;
expr.kind_ = kArrayInit;
expr.immediate_.index = index;
expr.operands_ = std::move(elements);
return expr;
}
static WasmInitExpr RttCanon(uint32_t index) {
WasmInitExpr expr;
expr.kind_ = kRttCanon;
@ -153,6 +163,13 @@ class WasmInitExpr {
if (operands()[i] != other.operands()[i]) return false;
}
return true;
case kArrayInit:
if (immediate().index != other.immediate().index) return false;
if (operands().size() != other.operands().size()) return false;
for (uint32_t i = 0; i < operands().size(); i++) {
if (operands()[i] != other.operands()[i]) return false;
}
return true;
case kRttSub:
case kRttFreshSub:
return immediate().index == other.immediate().index &&

View File

@ -61,6 +61,7 @@ constexpr uint32_t kV8MaxRttSubtypingDepth = 31;
// Maximum supported by implementation: ((1<<27)-3).
// Reason: total object size in bytes must fit into a Smi, for filler objects.
constexpr size_t kV8MaxWasmArrayLength = 1u << 26;
constexpr size_t kV8MaxWasmArrayInitLength = 999;
static_assert(kV8MaxWasmTableSize <= 4294967295, // 2^32 - 1
"v8 should not exceed WebAssembly's non-web embedding limits");

View File

@ -526,6 +526,16 @@ void WriteInitializerExpression(ZoneBuffer* buffer, const WasmInitExpr& init,
buffer->write_u8(static_cast<uint8_t>(kExprStructNewWithRtt));
buffer->write_u32v(init.immediate().index);
break;
case WasmInitExpr::kArrayInit:
STATIC_ASSERT((kExprArrayInit >> 8) == kGCPrefix);
for (const WasmInitExpr& operand : init.operands()) {
WriteInitializerExpression(buffer, operand, kWasmBottom);
}
buffer->write_u8(kGCPrefix);
buffer->write_u8(static_cast<uint8_t>(kExprArrayInit));
buffer->write_u32v(init.immediate().index);
buffer->write_u32v(static_cast<uint32_t>(init.operands().size() - 1));
break;
case WasmInitExpr::kRttCanon:
STATIC_ASSERT((kExprRttCanon >> 8) == kGCPrefix);
buffer->write_u8(kGCPrefix);

View File

@ -1685,6 +1685,12 @@ ObjectSlot WasmArray::ElementSlot(uint32_t index) {
return RawField(kHeaderSize + kTaggedSize * index);
}
Address WasmArray::ElementAddress(uint32_t index) {
DCHECK_LE(index, length());
return ptr() + WasmArray::kHeaderSize +
index * type()->element_type().element_size_bytes() - kHeapObjectTag;
}
// static
Handle<WasmExceptionObject> WasmExceptionObject::New(
Isolate* isolate, const wasm::FunctionSig* sig,

View File

@ -1000,6 +1000,9 @@ class WasmArray : public TorqueGeneratedWasmArray<WasmArray, WasmObject> {
Handle<WasmArray> array,
uint32_t index);
// Returns the Address of the element at {index}.
Address ElementAddress(uint32_t index);
DECL_CAST(WasmArray)
DECL_PRINTER(WasmArray)

View File

@ -396,6 +396,7 @@ constexpr const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
CASE_OP(ArraySet, "array.set")
CASE_OP(ArrayLen, "array.len")
CASE_OP(ArrayCopy, "array.copy")
CASE_OP(ArrayInit, "array.init")
CASE_OP(I31New, "i31.new")
CASE_OP(I31GetS, "i31.get_s")
CASE_OP(I31GetU, "i31.get_u")

View File

@ -664,6 +664,7 @@ bool V8_EXPORT_PRIVATE IsJSCompatibleSignature(const FunctionSig* sig,
V(ArraySet, 0xfb16, _) \
V(ArrayLen, 0xfb17, _) \
V(ArrayCopy, 0xfb18, _) /* not standardized - V8 experimental */ \
V(ArrayInit, 0xfb19, _) /* not standardized - V8 experimental */ \
V(I31New, 0xfb20, _) \
V(I31GetS, 0xfb21, _) \
V(I31GetU, 0xfb22, _) \

View File

@ -560,6 +560,9 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) {
dst_array, dst_index, src_array, src_index, length, \
WASM_GC_OP(kExprArrayCopy), static_cast<byte>(dst_idx), \
static_cast<byte>(src_idx)
#define WASM_ARRAY_INIT(index, length, ...) \
__VA_ARGS__, WASM_GC_OP(kExprArrayInit), static_cast<byte>(index), \
static_cast<byte>(length)
#define WASM_RTT_WITH_DEPTH(depth, typeidx) \
kRttWithDepthCode, U32V_1(depth), U32V_1(typeidx)

View File

@ -170,6 +170,7 @@ std::ostream& operator<<(std::ostream& os, const WasmInitExpr& expr) {
case WasmInitExpr::kRttFreshSub:
case WasmInitExpr::kRefNullConst:
case WasmInitExpr::kStructNewWithRtt:
case WasmInitExpr::kArrayInit:
// TODO(manoskouk): Implement these.
UNIMPLEMENTED();
case WasmInitExpr::kGlobalGet:

View File

@ -7,6 +7,8 @@
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
(function TestReferenceGlobals() {
print(arguments.callee.name);
var exporting_instance = (function() {
var builder = new WasmModuleBuilder();
@ -105,6 +107,8 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
})();
(function TestStructInitExpr() {
print(arguments.callee.name);
var builder = new WasmModuleBuilder();
var struct_index = builder.addStruct([{type: kWasmI32, mutability: false}]);
var composite_struct_index = builder.addStruct(
@ -158,3 +162,96 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
assertEquals(field2_value, instance.exports.field_2());
assertEquals((field3_value << 24) >> 24, instance.exports.field_3());
})();
(function TestArrayInitExprNumeric() {
print(arguments.callee.name);
var builder = new WasmModuleBuilder();
var array_index = builder.addArray(kWasmI16, true);
let element0_value = -44;
let element1_value = 55;
var global0 = builder.addGlobal(
kWasmI32, false,
WasmInitExpr.I32Const(element0_value));
var global = builder.addGlobal(
wasmRefType(array_index), false,
WasmInitExpr.ArrayInit(
array_index,
[WasmInitExpr.GlobalGet(global0.index),
WasmInitExpr.I32Const(element1_value),
WasmInitExpr.RttCanon(array_index)]));
builder.addFunction("get_element", kSig_i_i)
.addBody([
kExprGlobalGet, global.index,
kExprLocalGet, 0,
kGCPrefix, kExprArrayGetS, array_index])
.exportFunc();
var instance = builder.instantiate({});
assertEquals(element0_value, instance.exports.get_element(0));
assertEquals(element1_value, instance.exports.get_element(1));
})();
(function TestArrayInitExprRef() {
print(arguments.callee.name);
var builder = new WasmModuleBuilder();
var struct_index = builder.addStruct([{type: kWasmI32, mutability: false}]);
var array_index = builder.addArray(wasmOptRefType(struct_index), true);
let element0_value = 44;
let element2_value = 55;
var global0 = builder.addGlobal(
wasmRefType(struct_index), false,
WasmInitExpr.StructNewWithRtt(
struct_index,
[WasmInitExpr.I32Const(element0_value),
WasmInitExpr.RttCanon(struct_index)]));
var global = builder.addGlobal(
wasmRefType(array_index), false,
WasmInitExpr.ArrayInit(
array_index,
[WasmInitExpr.GlobalGet(global0.index),
WasmInitExpr.RefNull(struct_index),
WasmInitExpr.StructNewWithRtt(
struct_index,
[WasmInitExpr.I32Const(element2_value),
WasmInitExpr.RttCanon(struct_index)]),
WasmInitExpr.RttCanon(array_index)]));
builder.addFunction("element0", kSig_i_v)
.addBody([
kExprGlobalGet, global.index,
kExprI32Const, 0,
kGCPrefix, kExprArrayGet, array_index,
kGCPrefix, kExprStructGet, struct_index, 0])
.exportFunc();
builder.addFunction("element1", makeSig([], [kWasmAnyRef]))
.addBody([
kExprGlobalGet, global.index,
kExprI32Const, 1,
kGCPrefix, kExprArrayGet, array_index])
.exportFunc();
builder.addFunction("element2", kSig_i_v)
.addBody([
kExprGlobalGet, global.index,
kExprI32Const, 2,
kGCPrefix, kExprArrayGet, array_index,
kGCPrefix, kExprStructGet, struct_index, 0])
.exportFunc();
var instance = builder.instantiate({});
assertEquals(element0_value, instance.exports.element0());
assertEquals(null, instance.exports.element1());
assertEquals(element2_value, instance.exports.element2());
})();

View File

@ -15,7 +15,7 @@ let instances = [];
function createArray_i() {
let builder = new WasmModuleBuilder();
const type_index = builder.addArray(kWasmI32);
const type_index = builder.addArray(kWasmI32, true);
let sig_a_i = makeSig_r_x(kWasmDataRef, kWasmI32);
let sig_i_ai = makeSig([kWasmDataRef, kWasmI32], [kWasmI32]);

View File

@ -458,6 +458,8 @@ let kExprArrayGetS = 0x14;
let kExprArrayGetU = 0x15;
let kExprArraySet = 0x16;
let kExprArrayLen = 0x17;
let kExprArrayCopy = 0x18;
let kExprArrayInit = 0x19;
let kExprI31New = 0x20;
let kExprI31GetS = 0x21;
let kExprI31GetU = 0x22;
@ -982,6 +984,15 @@ class Binary {
this.emit_u8(kExprStructNewWithRtt);
this.emit_u32v(expr.value);
break;
case kExprArrayInit:
for (let operand of expr.operands) {
this.emit_init_expr_recursive(operand);
}
this.emit_u8(kGCPrefix);
this.emit_u8(kExprArrayInit);
this.emit_u32v(expr.value);
this.emit_u32v(expr.operands.length - 1);
break;
case kExprRttCanon:
this.emit_u8(kGCPrefix);
this.emit_u8(kExprRttCanon);
@ -992,11 +1003,13 @@ class Binary {
this.emit_u8(kGcPrefix);
this.emit_u8(kExprRttSub);
this.emit_u32v(expr.value);
break;
case kExprRttFreshSub:
this.emit_init_expr_recursive(expr.parent);
this.emit_u8(kGcPrefix);
this.emit_u8(kExprRttFreshSub);
this.emit_u32v(expr.value);
break;
}
}
@ -1129,6 +1142,9 @@ class WasmInitExpr {
static StructNewWithRtt(type, args) {
return {kind: kExprStructNewWithRtt, value: type, operands: args};
}
static ArrayInit(type, args) {
return {kind: kExprArrayInit, value: type, operands: args};
}
static RttCanon(type) {
return {kind: kExprRttCanon, value: type};
}
@ -1212,8 +1228,10 @@ class WasmStruct {
}
class WasmArray {
constructor(type) {
constructor(type, mutability) {
this.type = type;
if (!mutability) throw new Error("Immutable arrays are not supported yet");
this.mutability = mutability;
}
}
@ -1339,8 +1357,8 @@ class WasmModuleBuilder {
return this.types.length - 1;
}
addArray(type) {
this.types.push(new WasmArray(type));
addArray(type, mutability) {
this.types.push(new WasmArray(type, mutability));
return this.types.length - 1;
}
@ -1581,7 +1599,7 @@ class WasmModuleBuilder {
} else if (type instanceof WasmArray) {
section.emit_u8(kWasmArrayTypeForm);
section.emit_type(type.type);
section.emit_u8(1); // Only mutable arrays supported currently.
section.emit_u8(type.mutability ? 1 : 0);
} else {
section.emit_u8(kWasmFunctionTypeForm);
section.emit_u32v(type.params.length);

View File

@ -37,6 +37,8 @@ namespace module_decoder_unittest {
#define WASM_INIT_EXPR_GLOBAL(index) WASM_GLOBAL_GET(index), kExprEnd
#define WASM_INIT_EXPR_STRUCT_NEW(index, ...) \
WASM_STRUCT_NEW_WITH_RTT(index, __VA_ARGS__), kExprEnd
#define WASM_INIT_EXPR_ARRAY_INIT(index, length, ...) \
WASM_ARRAY_INIT(index, length, __VA_ARGS__), kExprEnd
#define WASM_INIT_EXPR_RTT_CANON(index) WASM_RTT_CANON(index), kExprEnd
#define REF_NULL_ELEMENT kExprRefNull, kFuncRefCode, kExprEnd
@ -1131,6 +1133,52 @@ TEST_F(WasmModuleVerifyTest, StructNewInitExpr) {
"struct.new[1]: expected (rtt 0), found (rtt 0 1) instead");
}
TEST_F(WasmModuleVerifyTest, ArrayInitInitExpr) {
WASM_FEATURE_SCOPE(reftypes);
WASM_FEATURE_SCOPE(typed_funcref);
WASM_FEATURE_SCOPE(gc);
WASM_FEATURE_SCOPE(gc_experiments);
static const byte basic[] = {
SECTION(Type, ENTRY_COUNT(1), WASM_ARRAY_DEF(kI16Code, true)),
SECTION(Global, ENTRY_COUNT(1), // --
kRefCode, 0, 0, // type, mutability
WASM_INIT_EXPR_ARRAY_INIT(0, 3, WASM_I32V(10), WASM_I32V(20),
WASM_I32V(30), WASM_RTT_CANON(0)))};
EXPECT_VERIFIES(basic);
static const byte type_error[] = {
SECTION(Type, ENTRY_COUNT(2), // --
WASM_ARRAY_DEF(kI32Code, true),
WASM_ARRAY_DEF(WASM_SEQ(kRefCode, 0), true)),
SECTION(
Global, ENTRY_COUNT(1), // --
kRefCode, 1, 0, // type, mutability
WASM_INIT_EXPR_ARRAY_INIT(0, 1, WASM_I32V(42), WASM_RTT_CANON(0)))};
EXPECT_FAILURE_WITH_MSG(
type_error,
"type error in init expression, expected (ref 1), got (ref 0)");
static const byte subexpr_type_error[] = {
SECTION(Type, ENTRY_COUNT(1), WASM_ARRAY_DEF(kI64Code, true)),
SECTION(Global, ENTRY_COUNT(1), // --
kRefCode, 0, 0, // type, mutability
WASM_INIT_EXPR_ARRAY_INIT(0, 2, WASM_I64V(42), WASM_I32V(142),
WASM_RTT_CANON(0)))};
EXPECT_FAILURE_WITH_MSG(subexpr_type_error,
"array.init[1]: expected i64, found i32 instead");
static const byte length_error[] = {
SECTION(Type, ENTRY_COUNT(1), WASM_ARRAY_DEF(kI16Code, true)),
SECTION(Global, ENTRY_COUNT(1), // --
kRefCode, 0, 0, // type, mutability
WASM_INIT_EXPR_ARRAY_INIT(0, 10, WASM_I32V(10), WASM_I32V(20),
WASM_I32V(30), WASM_RTT_CANON(0)))};
EXPECT_FAILURE_WITH_MSG(
length_error,
"not enough arguments on the stack for array.init: expected 11, found 4");
}
TEST_F(WasmModuleVerifyTest, EmptyStruct) {
WASM_FEATURE_SCOPE(reftypes);
WASM_FEATURE_SCOPE(typed_funcref);