[wasm-gc] Add a basic test case for structs

Bug: v8:7748
Change-Id: I80265c7070dc7ec421bf53aa717a727c144b0699
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2152844
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67288}
This commit is contained in:
Jakob Kummerow 2020-04-21 18:45:53 +02:00 committed by Commit Bot
parent 050a7e05fb
commit c66f220aaa
10 changed files with 189 additions and 25 deletions

View File

@ -5382,8 +5382,9 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
case wasm::ValueType::kRef:
case wasm::ValueType::kOptRef:
case wasm::ValueType::kEqRef:
// TODO(7748): Implement
UNIMPLEMENTED();
// TODO(7748): Implement properly. For now, we just expose the raw
// object for testing.
return node;
case wasm::ValueType::kStmt:
case wasm::ValueType::kBottom:
UNREACHABLE();

View File

@ -2887,7 +2887,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
len += imm.length;
if (!this->Validate(this->pc_, imm)) break;
auto args = PopArgs(imm.struct_type);
auto* value = Push(ValueType(ValueType::kEqRef, imm.index));
auto* value = Push(ValueType(ValueType::kRef, imm.index));
CALL_INTERFACE_IF_REACHABLE(StructNew, imm, args.begin(), value);
break;
}
@ -2895,7 +2895,9 @@ class WasmFullDecoder : public WasmDecoder<validate> {
FieldIndexImmediate<validate> field(this, this->pc_ + len);
if (!this->Validate(this->pc_ + len, field)) break;
len += field.length;
auto struct_obj = Pop(0, kWasmEqRef);
// TODO(7748): This should take an optref, and perform a null-check.
auto struct_obj =
Pop(0, ValueType(ValueType::kRef, field.struct_index.index));
auto* value = Push(field.struct_index.struct_type->field(field.index));
CALL_INTERFACE_IF_REACHABLE(StructGet, struct_obj, field, value);
break;

View File

@ -1744,11 +1744,15 @@ class ModuleDecoderImpl : public Decoder {
if (enabled_features_.has_eh()) return kWasmExnRef;
break;
case kLocalRef:
if (enabled_features_.has_gc()) return ValueType(ValueType::kRef);
if (enabled_features_.has_gc()) {
uint32_t type_index = consume_u32v("type index");
return ValueType(ValueType::kRef, type_index);
}
break;
case kLocalOptRef:
if (enabled_features_.has_gc()) {
return ValueType(ValueType::kOptRef);
uint32_t type_index = consume_u32v("type index");
return ValueType(ValueType::kOptRef, type_index);
}
break;
case kLocalEqRef:

View File

@ -239,7 +239,7 @@ void WasmFunctionBuilder::WriteAsmWasmOffsetTable(ZoneBuffer* buffer) const {
WasmModuleBuilder::WasmModuleBuilder(Zone* zone)
: zone_(zone),
signatures_(zone),
types_(zone),
function_imports_(zone),
global_imports_(zone),
exports_(zone),
@ -274,9 +274,15 @@ void WasmModuleBuilder::AddDataSegment(const byte* data, uint32_t size,
uint32_t WasmModuleBuilder::AddSignature(FunctionSig* sig) {
auto sig_entry = signature_map_.find(*sig);
if (sig_entry != signature_map_.end()) return sig_entry->second;
uint32_t index = static_cast<uint32_t>(signatures_.size());
uint32_t index = static_cast<uint32_t>(types_.size());
signature_map_.emplace(*sig, index);
signatures_.push_back(sig);
types_.push_back(Type(sig));
return index;
}
uint32_t WasmModuleBuilder::AddStructType(StructType* type) {
uint32_t index = static_cast<uint32_t>(types_.size());
types_.push_back(Type(type));
return index;
}
@ -399,25 +405,50 @@ void WasmModuleBuilder::SetMaxMemorySize(uint32_t value) {
void WasmModuleBuilder::SetHasSharedMemory() { has_shared_memory_ = true; }
namespace {
void WriteValueType(ZoneBuffer* buffer, const ValueType& type) {
buffer->write_u8(type.value_type_code());
if (type.kind() == ValueType::kRef || type.kind() == ValueType::kOptRef) {
buffer->write_u32v(type.ref_index());
}
}
} // namespace
void WasmModuleBuilder::WriteTo(ZoneBuffer* buffer) const {
// == Emit magic =============================================================
buffer->write_u32(kWasmMagic);
buffer->write_u32(kWasmVersion);
// == Emit signatures ========================================================
if (signatures_.size() > 0) {
// == Emit types =============================================================
if (types_.size() > 0) {
size_t start = EmitSection(kTypeSectionCode, buffer);
buffer->write_size(signatures_.size());
buffer->write_size(types_.size());
for (FunctionSig* sig : signatures_) {
for (const Type& type : types_) {
switch (type.kind) {
case Type::kFunctionSig: {
FunctionSig* sig = type.sig;
buffer->write_u8(kWasmFunctionTypeCode);
buffer->write_size(sig->parameter_count());
for (auto param : sig->parameters()) {
buffer->write_u8(param.value_type_code());
WriteValueType(buffer, param);
}
buffer->write_size(sig->return_count());
for (auto ret : sig->returns()) {
buffer->write_u8(ret.value_type_code());
WriteValueType(buffer, ret);
}
break;
}
case Type::kStructType: {
StructType* struct_type = type.type;
buffer->write_u8(kWasmStructTypeCode);
buffer->write_size(struct_type->field_count());
for (auto field : struct_type->fields()) {
WriteValueType(buffer, field);
}
break;
}
}
}
FixupSection(buffer, start);

View File

@ -241,6 +241,7 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
bool mutability, Vector<const char> module = {});
void AddDataSegment(const byte* data, uint32_t size, uint32_t dest);
uint32_t AddSignature(FunctionSig* sig);
uint32_t AddStructType(StructType* type);
// In the current implementation, it's supported to have uninitialized slots
// at the beginning and/or end of the indirect function table, as long as
// the filled slots form a contiguous block in the middle.
@ -268,9 +269,25 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
Zone* zone() { return zone_; }
FunctionSig* GetSignature(uint32_t index) { return signatures_[index]; }
FunctionSig* GetSignature(uint32_t index) {
DCHECK(types_[index].kind == Type::kFunctionSig);
return types_[index].sig;
}
private:
struct Type {
enum Kind { kFunctionSig, kStructType };
explicit Type(FunctionSig* signature)
: kind(kFunctionSig), sig(signature) {}
explicit Type(StructType* struct_type)
: kind(kStructType), type(struct_type) {}
Kind kind;
union {
FunctionSig* sig;
StructType* type;
};
};
struct WasmFunctionImport {
Vector<const char> module;
Vector<const char> name;
@ -310,7 +327,7 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
friend class WasmFunctionBuilder;
Zone* zone_;
ZoneVector<FunctionSig*> signatures_;
ZoneVector<Type> types_;
ZoneVector<WasmFunctionImport> function_imports_;
ZoneVector<WasmGlobalImport> global_imports_;
ZoneVector<WasmExport> exports_;
@ -335,7 +352,7 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
};
inline FunctionSig* WasmFunctionBuilder::signature() {
return builder_->signatures_[signature_index_];
return builder_->types_[signature_index_].sig;
}
} // namespace wasm

View File

@ -1421,7 +1421,7 @@ Handle<Map> WasmInstanceObject::GetOrCreateStructMap(
const wasm::StructType* type = module->struct_type(struct_index);
int inobject_properties = 0;
DCHECK_LE(kMaxInt - WasmStruct::kHeaderSize, type->total_fields_size());
DCHECK_LE(type->total_fields_size(), kMaxInt - WasmStruct::kHeaderSize);
int instance_size =
WasmStruct::kHeaderSize + static_cast<int>(type->total_fields_size());
InstanceType instance_type = WASM_STRUCT_TYPE;

View File

@ -275,6 +275,7 @@ v8_source_set("cctest_sources") {
"unicode-helpers.h",
"wasm/test-c-wasm-entry.cc",
"wasm/test-compilation-cache.cc",
"wasm/test-gc.cc",
"wasm/test-grow-memory.cc",
"wasm/test-jump-table-assembler.cc",
"wasm/test-liftoff-inspection.cc",

View File

@ -467,6 +467,7 @@
'test-c-wasm-entry/*': [SKIP],
'test-compilation-cache/*': [SKIP],
'test-jump-table-assembler/*': [SKIP],
'test-gc/*': [SKIP],
'test-grow-memory/*': [SKIP],
'test-run-wasm-64/*': [SKIP],
'test-run-wasm-asmjs/*': [SKIP],

100
test/cctest/wasm/test-gc.cc Normal file
View File

@ -0,0 +1,100 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdint.h>
#include "src/utils/utils.h"
#include "src/utils/vector.h"
#include "src/wasm/module-decoder.h"
#include "src/wasm/struct-types.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-opcodes.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/value-helper.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
#include "test/common/wasm/wasm-module-runner.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace test_gc {
WASM_EXEC_TEST(BasicStruct) {
// TODO(7748): Implement support in other tiers.
if (execution_tier == ExecutionTier::kLiftoff) return;
if (execution_tier == ExecutionTier::kInterpreter) return;
TestSignatures sigs;
EXPERIMENTAL_FLAG_SCOPE(gc);
EXPERIMENTAL_FLAG_SCOPE(anyref);
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
WasmModuleBuilder* builder = new (&zone) WasmModuleBuilder(&zone);
StructType::Builder type_builder(&zone, 2);
type_builder.AddField(kWasmI32);
type_builder.AddField(kWasmI32);
int32_t type_index = builder->AddStructType(type_builder.Build());
ValueType kRefTypes[] = {ValueType(ValueType::kRef, type_index)};
FunctionSig sig_q_v(1, 0, kRefTypes);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
f->builder()->AddExport(CStrVector("f"), f);
byte f_code[] = {WASM_STRUCT_GET(type_index, 0,
WASM_STRUCT_NEW(type_index, WASM_I32V(42),
WASM_I32V(64))),
kExprEnd};
f->EmitCode(f_code, sizeof(f_code));
WasmFunctionBuilder* g = builder->AddFunction(sigs.i_v());
g->builder()->AddExport(CStrVector("g"), g);
byte g_code[] = {WASM_STRUCT_GET(type_index, 1,
WASM_STRUCT_NEW(type_index, WASM_I32V(42),
WASM_I32V(64))),
kExprEnd};
g->EmitCode(g_code, sizeof(g_code));
WasmFunctionBuilder* h = builder->AddFunction(&sig_q_v);
h->builder()->AddExport(CStrVector("h"), h);
byte h_code[] = {WASM_STRUCT_NEW(type_index, WASM_I32V(42), WASM_I32V(64)),
kExprEnd};
h->EmitCode(h_code, sizeof(h_code));
ZoneBuffer buffer(&zone);
builder->WriteTo(&buffer);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "Test");
Handle<WasmInstanceObject> instance =
testing::CompileAndInstantiateForTesting(
isolate, &thrower, ModuleWireBytes(buffer.begin(), buffer.end()))
.ToHandleChecked();
CHECK_EQ(42, testing::CallWasmFunctionForTesting(isolate, instance, &thrower,
"f", 0, nullptr));
CHECK_EQ(64, testing::CallWasmFunctionForTesting(isolate, instance, &thrower,
"g", 0, nullptr));
// TODO(7748): This uses the JavaScript interface to retrieve the plain
// WasmStruct. Once the JS interaction story is settled, this may well
// need to be changed.
Handle<WasmExportedFunction> h_export =
testing::GetExportedFunction(isolate, instance, "h").ToHandleChecked();
Handle<Object> undefined = isolate->factory()->undefined_value();
Handle<Object> ref_result =
Execution::Call(isolate, h_export, undefined, 0, nullptr)
.ToHandleChecked();
CHECK(ref_result->IsWasmStruct());
}
} // namespace test_gc
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -409,6 +409,13 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) {
#define TABLE_ZERO 0
#define WASM_GC_OP(op) kGCPrefix, static_cast<byte>(op)
#define WASM_STRUCT_NEW(index, ...) \
__VA_ARGS__, WASM_GC_OP(kExprStructNew), static_cast<byte>(index)
#define WASM_STRUCT_GET(typeidx, fieldidx, ...) \
__VA_ARGS__, WASM_GC_OP(kExprStructGet), static_cast<byte>(typeidx), \
static_cast<byte>(fieldidx)
// Pass: sig_index, ...args, func_index
#define WASM_CALL_INDIRECT(sig_index, ...) \
__VA_ARGS__, kExprCallIndirect, static_cast<byte>(sig_index), TABLE_ZERO