[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:
parent
050a7e05fb
commit
c66f220aaa
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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_) {
|
||||
buffer->write_u8(kWasmFunctionTypeCode);
|
||||
buffer->write_size(sig->parameter_count());
|
||||
for (auto param : sig->parameters()) {
|
||||
buffer->write_u8(param.value_type_code());
|
||||
}
|
||||
buffer->write_size(sig->return_count());
|
||||
for (auto ret : sig->returns()) {
|
||||
buffer->write_u8(ret.value_type_code());
|
||||
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()) {
|
||||
WriteValueType(buffer, param);
|
||||
}
|
||||
buffer->write_size(sig->return_count());
|
||||
for (auto ret : sig->returns()) {
|
||||
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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -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
100
test/cctest/wasm/test-gc.cc
Normal 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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user