Commit 95e8d867 authored by Manos Koukoutos's avatar Manos Koukoutos Committed by V8 LUCI CQ

[wasm-gc] Allow struct.new_with_rtt as constant expression

Changes:
- Add struct.new_with_rtt as a new WasmInitExpr. Parse it in
  consume_init_expr(). Add it to
  InstanceBuilder::EvaluateInitExpression().
- Change WasmInitExpr::operand_ to vector operands_.
- In consume_init_expr(), use parsed over hard-coded opcode length.
- Improve WasmStruct::WasmStructPrint slightly.
- Add Factory::NewWasmStruct().
- Add WasmValue::CopyToWithSystemEndianness.
- In wasm-module-builder.js, generalize emit_init_expr for expressions
  with operands. Add missing init. expression types.
- Add tests.

Bug: v8:7748
Change-Id: Ica12378d202730aff1b57c7d4240aa00ef124f8e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2940893
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: 's avatarMichael Lippautz <mlippautz@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75006}
parent cea8c2ca
......@@ -1864,12 +1864,18 @@ void WasmStruct::WasmStructPrint(std::ostream& os) {
os << base::ReadUnalignedValue<double>(field_address);
break;
case wasm::kI8:
os << base::ReadUnalignedValue<int8_t>(field_address);
break;
case wasm::kI16:
case wasm::kS128:
os << base::ReadUnalignedValue<int16_t>(field_address);
break;
case wasm::kRef:
case wasm::kOptRef:
case wasm::kRtt:
case wasm::kRttWithDepth:
os << Brief(base::ReadUnalignedValue<Object>(field_address));
break;
case wasm::kS128:
case wasm::kBottom:
case wasm::kVoid:
os << "UNIMPLEMENTED"; // TODO(7748): Implement.
......
......@@ -67,6 +67,9 @@
#include "src/objects/transitions-inl.h"
#include "src/roots/roots.h"
#include "src/strings/unicode-inl.h"
#if V8_ENABLE_WEBASSEMBLY
#include "src/wasm/wasm-value.h"
#endif
namespace v8 {
namespace internal {
......@@ -1454,6 +1457,25 @@ Handle<WasmCapiFunctionData> Factory::NewWasmCapiFunctionData(
return handle(result, isolate());
}
Handle<WasmStruct> Factory::NewWasmStruct(const wasm::StructType* type,
wasm::WasmValue* args,
Handle<Map> map) {
DCHECK_EQ(WasmStruct::Size(type), map->wasm_type_info().instance_size());
HeapObject raw = AllocateRaw(WasmStruct::Size(type), AllocationType::kYoung);
raw.set_map_after_allocation(*map);
WasmStruct result = WasmStruct::cast(raw);
result.set_raw_properties_or_hash(*empty_fixed_array());
for (uint32_t i = 0; i < type->field_count(); i++) {
Address address = result.RawFieldAddress(type->field_offset(i));
if (type->field(i).is_numeric()) {
args[i].CopyToWithSystemEndianness(reinterpret_cast<byte*>(address));
} else {
base::WriteUnalignedValue<Object>(address, *args[i].to_ref());
}
}
return handle(result, isolate());
}
Handle<SharedFunctionInfo>
Factory::NewSharedFunctionInfoForWasmExportedFunction(
Handle<String> name, Handle<WasmExportedFunctionData> data) {
......
......@@ -69,6 +69,12 @@ class WasmCapiFunctionData;
class WasmExportedFunctionData;
class WasmJSFunctionData;
class WeakCell;
#if V8_ENABLE_WEBASSEMBLY
namespace wasm {
class StructType;
class WasmValue;
} // namespace wasm
#endif
enum class SharedFlag : uint8_t;
enum class InitializedFlag : uint8_t;
......@@ -569,6 +575,8 @@ class V8_EXPORT_PRIVATE Factory : public FactoryBase<Factory> {
Address opt_call_target, Handle<JSReceiver> callable, int return_count,
int parameter_count, Handle<PodArray<wasm::ValueType>> serialized_sig,
Handle<Code> wrapper_code);
Handle<WasmStruct> NewWasmStruct(const wasm::StructType* type,
wasm::WasmValue* args, Handle<Map> map);
Handle<SharedFunctionInfo> NewSharedFunctionInfoForWasmExportedFunction(
Handle<String> name, Handle<WasmExportedFunctionData> data);
......
......@@ -1808,10 +1808,57 @@ class ModuleDecoderImpl : public Decoder {
// the type check or stack height check at the end.
opcode = read_prefixed_opcode<validate>(pc(), &len);
switch (opcode) {
case kExprStructNewWithRtt: {
if (!V8_LIKELY(enabled_features_.has_gc_experiments())) {
error(pc(),
"invalid opcode struct.new_with_rtt in init. expression, "
"enable with --experimental-wasm-gc-experiments");
return {};
}
IndexImmediate<validate> imm(this, pc() + len, "struct index");
if (!V8_LIKELY(module->has_struct(imm.index))) {
errorf(pc() + len, "invalid struct type index #%u", imm.index);
return {};
}
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");
return {};
}
std::vector<WasmInitExpr> arguments(type->field_count() + 1);
WasmInitExpr* stack_args = &stack.back() - type->field_count();
for (uint32_t i = 0; i < type->field_count(); i++) {
WasmInitExpr& argument = stack_args[i];
if (!IsSubtypeOf(TypeOf(argument), type->field(i), module)) {
errorf(pc(), "struct.new[%u]: expected %s, found %s instead",
i, type->field(i).name().c_str(),
TypeOf(argument).name().c_str());
return {};
}
arguments[i] = std::move(argument);
}
WasmInitExpr& rtt = stack.back();
if (!IsSubtypeOf(TypeOf(rtt), ValueType::Rtt(imm.index),
module)) {
errorf(pc(), "struct.new[%u]: expected %s, found %s instead",
type->field_count(),
ValueType::Rtt(imm.index).name().c_str(),
TypeOf(rtt).name().c_str());
return {};
}
arguments[type->field_count()] = std::move(rtt);
for (uint32_t i = 0; i <= type->field_count(); i++) {
stack.pop_back();
}
stack.push_back(WasmInitExpr::StructNewWithRtt(
imm.index, std::move(arguments)));
break;
}
case kExprRttCanon: {
IndexImmediate<validate> imm(this, pc() + 2, "type index");
if (V8_UNLIKELY(imm.index >= module_->types.capacity())) {
errorf(pc() + 2, "type index %u is out of bounds", imm.index);
IndexImmediate<validate> imm(this, pc() + len, "type index");
if (V8_UNLIKELY(!module_->has_type(imm.index))) {
errorf(pc() + len, "type index %u is out of bounds", imm.index);
return {};
}
len += imm.length;
......@@ -1826,9 +1873,9 @@ class ModuleDecoderImpl : public Decoder {
}
V8_FALLTHROUGH;
case kExprRttSub: {
IndexImmediate<validate> imm(this, pc() + 2, "type index");
if (V8_UNLIKELY(imm.index >= module_->types.capacity())) {
errorf(pc() + 2, "type index %u is out of bounds", imm.index);
IndexImmediate<validate> imm(this, pc() + len, "type index");
if (V8_UNLIKELY(!module_->has_type(imm.index))) {
errorf(pc() + len, "type index %u is out of bounds", imm.index);
return {};
}
len += imm.length;
......
......@@ -1669,6 +1669,18 @@ WasmValue InstanceBuilder::EvaluateInitExpression(
UNREACHABLE();
}
}
case WasmInitExpr::kStructNewWithRtt: {
const StructType* type = module_->struct_type(init.immediate().index);
std::vector<WasmValue> fields(type->field_count());
for (uint32_t i = 0; i < type->field_count(); i++) {
fields[i] = EvaluateInitExpression(init.operands()[i], instance);
}
auto rtt = Handle<Map>::cast(
EvaluateInitExpression(init.operands().back(), instance).to_ref());
return WasmValue(
isolate_->factory()->NewWasmStruct(type, fields.data(), rtt),
init.type(module_, enabled_));
}
case WasmInitExpr::kRttCanon: {
int map_index = init.immediate().index;
return WasmValue(
......@@ -1678,7 +1690,7 @@ WasmValue InstanceBuilder::EvaluateInitExpression(
case WasmInitExpr::kRttSub:
case WasmInitExpr::kRttFreshSub: {
uint32_t type = init.immediate().index;
WasmValue parent = EvaluateInitExpression(*init.operand(), instance);
WasmValue parent = EvaluateInitExpression(init.operands()[0], instance);
return WasmValue(AllocateSubRtt(isolate_, instance, type,
Handle<Map>::cast(parent.to_ref()),
init.kind() == WasmInitExpr::kRttSub
......@@ -1698,7 +1710,7 @@ void InstanceBuilder::InitGlobals(Handle<WasmInstanceObject> instance) {
WasmValue value = EvaluateInitExpression(global.init, instance);
if (value.type().is_reference()) {
if (global.type.is_reference()) {
tagged_globals_->set(global.offset, *value.to_ref());
} else {
value.CopyTo(GetRawUntaggedGlobalPtr<byte>(global));
......
......@@ -38,11 +38,13 @@ ValueType WasmInitExpr::type(const WasmModule* module,
}
case kRefNullConst:
return ValueType::Ref(immediate().heap_type, kNullable);
case kStructNewWithRtt:
return ValueType::Ref(immediate().index, kNonNullable);
case kRttCanon:
return ValueType::Rtt(immediate().heap_type, 0);
case kRttSub:
case kRttFreshSub: {
ValueType operand_type = operand()->type(module, enabled_features);
ValueType operand_type = operands()[0].type(module, enabled_features);
if (!operand_type.is_rtt()) return kWasmBottom;
if (operand_type.has_depth()) {
return ValueType::Rtt(immediate().heap_type, operand_type.depth() + 1);
......
......@@ -33,6 +33,7 @@ class WasmInitExpr {
kS128Const,
kRefNullConst,
kRefFuncConst,
kStructNewWithRtt,
kRttCanon,
kRttSub,
kRttFreshSub,
......@@ -88,6 +89,15 @@ class WasmInitExpr {
return expr;
}
static WasmInitExpr StructNewWithRtt(uint32_t index,
std::vector<WasmInitExpr> elements) {
WasmInitExpr expr;
expr.kind_ = kStructNewWithRtt;
expr.immediate_.index = index;
expr.operands_ = std::move(elements);
return expr;
}
static WasmInitExpr RttCanon(uint32_t index) {
WasmInitExpr expr;
expr.kind_ = kRttCanon;
......@@ -99,7 +109,7 @@ class WasmInitExpr {
WasmInitExpr expr;
expr.kind_ = kRttSub;
expr.immediate_.index = index;
expr.operand_ = std::make_unique<WasmInitExpr>(std::move(supertype));
expr.operands_.push_back(std::move(supertype));
return expr;
}
......@@ -107,13 +117,13 @@ class WasmInitExpr {
WasmInitExpr expr;
expr.kind_ = kRttFreshSub;
expr.immediate_.index = index;
expr.operand_ = std::make_unique<WasmInitExpr>(std::move(supertype));
expr.operands_.push_back(std::move(supertype));
return expr;
}
Immediate immediate() const { return immediate_; }
Operator kind() const { return kind_; }
WasmInitExpr* operand() const { return operand_.get(); }
const std::vector<WasmInitExpr>& operands() const { return operands_; }
bool operator==(const WasmInitExpr& other) const {
if (kind() != other.kind()) return false;
......@@ -136,14 +146,21 @@ class WasmInitExpr {
return immediate().s128_const == other.immediate().s128_const;
case kRefNullConst:
return immediate().heap_type == other.immediate().heap_type;
case kStructNewWithRtt:
if (immediate().index != other.immediate().index) return false;
DCHECK_EQ(operands().size(), other.operands().size());
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 &&
*operand() == *other.operand();
operands()[0] == other.operands()[0];
}
}
V8_INLINE bool operator!=(const WasmInitExpr& other) {
V8_INLINE bool operator!=(const WasmInitExpr& other) const {
return !(*this == other);
}
......@@ -153,7 +170,7 @@ class WasmInitExpr {
private:
Immediate immediate_;
Operator kind_;
std::unique_ptr<WasmInitExpr> operand_ = nullptr;
std::vector<WasmInitExpr> operands_;
};
} // namespace wasm
......
......@@ -517,6 +517,15 @@ void WriteInitializerExpression(ZoneBuffer* buffer, const WasmInitExpr& init,
}
break;
}
case WasmInitExpr::kStructNewWithRtt:
STATIC_ASSERT((kExprStructNewWithRtt >> 8) == kGCPrefix);
buffer->write_u8(kGCPrefix);
buffer->write_u8(static_cast<uint8_t>(kExprStructNewWithRtt));
buffer->write_u32v(init.immediate().index);
for (const WasmInitExpr& operand : init.operands()) {
WriteInitializerExpression(buffer, operand, kWasmBottom);
}
break;
case WasmInitExpr::kRttCanon:
STATIC_ASSERT((kExprRttCanon >> 8) == kGCPrefix);
buffer->write_u8(kGCPrefix);
......@@ -526,7 +535,7 @@ void WriteInitializerExpression(ZoneBuffer* buffer, const WasmInitExpr& init,
case WasmInitExpr::kRttSub:
case WasmInitExpr::kRttFreshSub:
// The operand to rtt.sub must be emitted first.
WriteInitializerExpression(buffer, *init.operand(), kWasmBottom);
WriteInitializerExpression(buffer, init.operands()[0], kWasmBottom);
STATIC_ASSERT((kExprRttSub >> 8) == kGCPrefix);
STATIC_ASSERT((kExprRttFreshSub >> 8) == kGCPrefix);
buffer->write_u8(kGCPrefix);
......
......@@ -139,6 +139,44 @@ class WasmValue {
type_.element_size_bytes());
}
void CopyToWithSystemEndianness(byte* to) {
DCHECK(type_.is_numeric());
switch (type_.kind()) {
case kI32: {
int32_t value = to_i32();
base::Memcpy(static_cast<void*>(to), &value, sizeof(value));
break;
}
case kI64: {
int64_t value = to_i64();
base::Memcpy(static_cast<void*>(to), &value, sizeof(value));
break;
}
case kF32: {
float value = to_f32();
base::Memcpy(static_cast<void*>(to), &value, sizeof(value));
break;
}
case kF64: {
double value = to_f64();
base::Memcpy(static_cast<void*>(to), &value, sizeof(value));
break;
}
case kS128:
base::Memcpy(static_cast<void*>(to), to_s128().bytes(), kSimd128Size);
break;
case kRtt:
case kRttWithDepth:
case kRef:
case kOptRef:
case kBottom:
case kVoid:
case kI8:
case kI16:
UNREACHABLE();
}
}
template <typename T>
inline T to() const;
......
......@@ -169,6 +169,7 @@ std::ostream& operator<<(std::ostream& os, const WasmInitExpr& expr) {
case WasmInitExpr::kRttSub:
case WasmInitExpr::kRttFreshSub:
case WasmInitExpr::kRefNullConst:
case WasmInitExpr::kStructNewWithRtt:
// TODO(manoskouk): Implement these.
UNIMPLEMENTED();
case WasmInitExpr::kGlobalGet:
......
......@@ -2,12 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --experimental-wasm-gc
// Flags: --experimental-wasm-gc-experiments
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
(function Test1() {
var exporting_instance = (function () {
(function TestReferenceGlobals() {
var exporting_instance = (function() {
var builder = new WasmModuleBuilder();
var sig_index = builder.addType(kSig_i_ii);
......@@ -103,3 +103,49 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// The correct function reference has been passed.
assertEquals(66, instance.exports.test_import(42, 24));
})();
(function TestStructInitExpr() {
var builder = new WasmModuleBuilder();
var struct_index = builder.addStruct([{type: kWasmI32, mutability: false}]);
var composite_struct_index = builder.addStruct(
[{type: kWasmI32, mutability: false},
{type: wasmRefType(struct_index), mutability: false}]);
let field1_value = 432;
let field2_value = -123;
var global0 = builder.addGlobal(
wasmRefType(struct_index), false,
WasmInitExpr.StructNewWithRtt(
struct_index,
[WasmInitExpr.I32Const(field2_value),
WasmInitExpr.RttCanon(struct_index)]));
var global = builder.addGlobal(
wasmRefType(composite_struct_index), false,
WasmInitExpr.StructNewWithRtt(
composite_struct_index,
[WasmInitExpr.I32Const(field1_value),
WasmInitExpr.GlobalGet(global0.index),
WasmInitExpr.RttCanon(composite_struct_index)]));
builder.addFunction("field_1", kSig_i_v)
.addBody([
kExprGlobalGet, global.index,
kGCPrefix, kExprStructGet, composite_struct_index, 0
])
.exportFunc();
builder.addFunction("field_2", kSig_i_v)
.addBody([
kExprGlobalGet, global.index,
kGCPrefix, kExprStructGet, composite_struct_index, 1,
kGCPrefix, kExprStructGet, struct_index, 0
])
.exportFunc();
var instance = builder.instantiate({});
assertEquals(field1_value, instance.exports.field_1());
assertEquals(field2_value, instance.exports.field_2());
})();
......@@ -945,7 +945,7 @@ class Binary {
}
}
emit_init_expr(expr) {
emit_init_expr_recursive(expr) {
switch (expr.kind) {
case kExprGlobalGet:
this.emit_u8(kExprGlobalGet);
......@@ -974,8 +974,35 @@ class Binary {
this.emit_u8(kExprRefNull);
this.emit_u32v(expr.value);
break;
case kExprStructNewWithRtt:
for (let operand of expr.operands) {
this.emit_init_expr_recursive(operand);
}
this.emit_u8(kGCPrefix);
this.emit_u8(kExprStructNewWithRtt);
this.emit_u32v(expr.value);
break;
case kExprRttCanon:
this.emit_u8(kGCPrefix);
this.emit_u8(kExprRttCanon);
this.emit_u32v(expr.value);
break;
case kExprRttSub:
this.emit_init_expr_recursive(expr.parent);
this.emit_u8(kGcPrefix);
this.emit_u8(kExprRttSub);
this.emit_u32v(expr.value);
case kExprRttFreshSub:
this.emit_init_expr_recursive(expr.parent);
this.emit_u8(kGcPrefix);
this.emit_u8(kExprRttFreshSub);
this.emit_u32v(expr.value);
}
this.emit_u8(kExprEnd); // end of init expression
}
emit_init_expr(expr) {
this.emit_init_expr_recursive(expr);
this.emit_u8(kExprEnd);
}
emit_header() {
......@@ -1099,6 +1126,18 @@ class WasmInitExpr {
static RefNull(type) {
return {kind: kExprRefNull, value: type};
}
static StructNewWithRtt(type, args) {
return {kind: kExprStructNewWithRtt, value: type, operands: args};
}
static RttCanon(type) {
return {kind: kExprRttCanon, value: type};
}
static RttSub(type, parent) {
return {kind: kExprRttSub, value: type, parent: parent};
}
static RttFreshSub(type, parent) {
return {kind: kExprRttFreshSub, value: type, parent: parent};
}
static defaultFor(type) {
switch (type) {
......@@ -1113,6 +1152,9 @@ class WasmInitExpr {
case kWasmS128:
return this.S128Const(new Array(16).fill(0));
default:
if ((typeof type) != 'number' && type.opcode != kWasmOptRef) {
throw new Error("Non-defaultable type");
}
let heap_type = (typeof type) == 'number' ? type : type.index;
return this.RefNull(heap_type);
}
......
......@@ -35,6 +35,9 @@ namespace module_decoder_unittest {
#define WASM_INIT_EXPR_FUNC_REF_NULL WASM_REF_NULL(kFuncRefCode), kExprEnd
#define WASM_INIT_EXPR_REF_FUNC(val) WASM_REF_FUNC(val), kExprEnd
#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_RTT_CANON(index) WASM_RTT_CANON(index), kExprEnd
#define REF_NULL_ELEMENT kExprRefNull, kFuncRefCode, kExprEnd
#define REF_FUNC_ELEMENT(v) kExprRefFunc, U32V_1(v), kExprEnd
......@@ -1078,6 +1081,56 @@ TEST_F(WasmModuleVerifyTest, RttFreshSubGlobalTypeError) {
"(rtt 1 0)");
}
TEST_F(WasmModuleVerifyTest, StructNewInitExpr) {
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_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true))),
SECTION(Global, ENTRY_COUNT(1), // --
kRefCode, 0, 0, // type, mutability
WASM_INIT_EXPR_STRUCT_NEW(0, WASM_I32V(42), WASM_RTT_CANON(0)))};
EXPECT_VERIFIES(basic);
static const byte global_args[] = {
SECTION(Type, ENTRY_COUNT(1), // --
WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true))),
SECTION(Global, ENTRY_COUNT(3), // --
kI32Code, 0, // type, mutability
WASM_INIT_EXPR_I32V_1(10), // --
kRttCode, 0, 0, // type, mutability
WASM_RTT_SUB(0, WASM_RTT_CANON(0)), kExprEnd, // --
kRefCode, 0, 0, // type, mutability
WASM_INIT_EXPR_STRUCT_NEW(0, WASM_GLOBAL_GET(0),
WASM_GLOBAL_GET(1)))};
EXPECT_VERIFIES(global_args);
static const byte type_error[] = {
SECTION(Type, ENTRY_COUNT(2), // --
WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true)),
WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI64Code, true))),
SECTION(Global, ENTRY_COUNT(1), // --
kRefCode, 1, 0, // type, mutability
WASM_INIT_EXPR_STRUCT_NEW(0, 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(2), // --
WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true)),
WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI64Code, true))),
SECTION(Global, ENTRY_COUNT(1), // --
kRefCode, 0, 0, // type, mutability
WASM_INIT_EXPR_STRUCT_NEW(0, WASM_I32V(42), WASM_RTT_CANON(1)))};
EXPECT_FAILURE_WITH_MSG(
subexpr_type_error,
"struct.new[1]: expected (rtt 0), found (rtt 0 1) instead");
}
TEST_F(WasmModuleVerifyTest, EmptyStruct) {
WASM_FEATURE_SCOPE(reftypes);
WASM_FEATURE_SCOPE(typed_funcref);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment