From c66f220aaaed14342cd97dcdb61cc5cc1e06e64a Mon Sep 17 00:00:00 2001 From: Jakob Kummerow Date: Tue, 21 Apr 2020 18:45:53 +0200 Subject: [PATCH] [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 Reviewed-by: Andreas Haas Cr-Commit-Position: refs/heads/master@{#67288} --- src/compiler/wasm-compiler.cc | 5 +- src/wasm/function-body-decoder-impl.h | 6 +- src/wasm/module-decoder.cc | 8 ++- src/wasm/wasm-module-builder.cc | 61 ++++++++++++---- src/wasm/wasm-module-builder.h | 23 +++++- src/wasm/wasm-objects.cc | 2 +- test/cctest/BUILD.gn | 1 + test/cctest/cctest.status | 1 + test/cctest/wasm/test-gc.cc | 100 ++++++++++++++++++++++++++ test/common/wasm/wasm-macro-gen.h | 7 ++ 10 files changed, 189 insertions(+), 25 deletions(-) create mode 100644 test/cctest/wasm/test-gc.cc diff --git a/src/compiler/wasm-compiler.cc b/src/compiler/wasm-compiler.cc index e289984eba..8827b66a29 100644 --- a/src/compiler/wasm-compiler.cc +++ b/src/compiler/wasm-compiler.cc @@ -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(); diff --git a/src/wasm/function-body-decoder-impl.h b/src/wasm/function-body-decoder-impl.h index 0f20880f68..1c43a46e5b 100644 --- a/src/wasm/function-body-decoder-impl.h +++ b/src/wasm/function-body-decoder-impl.h @@ -2887,7 +2887,7 @@ class WasmFullDecoder : public WasmDecoder { 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 { FieldIndexImmediate 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; diff --git a/src/wasm/module-decoder.cc b/src/wasm/module-decoder.cc index 1fb4563098..0b91807a17 100644 --- a/src/wasm/module-decoder.cc +++ b/src/wasm/module-decoder.cc @@ -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: diff --git a/src/wasm/wasm-module-builder.cc b/src/wasm/wasm-module-builder.cc index c6523906a6..7704614c66 100644 --- a/src/wasm/wasm-module-builder.cc +++ b/src/wasm/wasm-module-builder.cc @@ -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(signatures_.size()); + uint32_t index = static_cast(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(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); diff --git a/src/wasm/wasm-module-builder.h b/src/wasm/wasm-module-builder.h index 404e7c63fe..eaa8e3683d 100644 --- a/src/wasm/wasm-module-builder.h +++ b/src/wasm/wasm-module-builder.h @@ -241,6 +241,7 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject { bool mutability, Vector 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 module; Vector name; @@ -310,7 +327,7 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject { friend class WasmFunctionBuilder; Zone* zone_; - ZoneVector signatures_; + ZoneVector types_; ZoneVector function_imports_; ZoneVector global_imports_; ZoneVector 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 diff --git a/src/wasm/wasm-objects.cc b/src/wasm/wasm-objects.cc index 1a6831e318..eb48424a6a 100644 --- a/src/wasm/wasm-objects.cc +++ b/src/wasm/wasm-objects.cc @@ -1421,7 +1421,7 @@ Handle 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(type->total_fields_size()); InstanceType instance_type = WASM_STRUCT_TYPE; diff --git a/test/cctest/BUILD.gn b/test/cctest/BUILD.gn index c923ff4b55..39eadfbc26 100644 --- a/test/cctest/BUILD.gn +++ b/test/cctest/BUILD.gn @@ -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", diff --git a/test/cctest/cctest.status b/test/cctest/cctest.status index 4d69b2613d..eb3077f302 100644 --- a/test/cctest/cctest.status +++ b/test/cctest/cctest.status @@ -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], diff --git a/test/cctest/wasm/test-gc.cc b/test/cctest/wasm/test-gc.cc new file mode 100644 index 0000000000..e46a8f00f1 --- /dev/null +++ b/test/cctest/wasm/test-gc.cc @@ -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 + +#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 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 h_export = + testing::GetExportedFunction(isolate, instance, "h").ToHandleChecked(); + Handle undefined = isolate->factory()->undefined_value(); + Handle ref_result = + Execution::Call(isolate, h_export, undefined, 0, nullptr) + .ToHandleChecked(); + CHECK(ref_result->IsWasmStruct()); +} + +} // namespace test_gc +} // namespace wasm +} // namespace internal +} // namespace v8 diff --git a/test/common/wasm/wasm-macro-gen.h b/test/common/wasm/wasm-macro-gen.h index 1da88a406a..173136dc05 100644 --- a/test/common/wasm/wasm-macro-gen.h +++ b/test/common/wasm/wasm-macro-gen.h @@ -409,6 +409,13 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) { #define TABLE_ZERO 0 +#define WASM_GC_OP(op) kGCPrefix, static_cast(op) +#define WASM_STRUCT_NEW(index, ...) \ + __VA_ARGS__, WASM_GC_OP(kExprStructNew), static_cast(index) +#define WASM_STRUCT_GET(typeidx, fieldidx, ...) \ + __VA_ARGS__, WASM_GC_OP(kExprStructGet), static_cast(typeidx), \ + static_cast(fieldidx) + // Pass: sig_index, ...args, func_index #define WASM_CALL_INDIRECT(sig_index, ...) \ __VA_ARGS__, kExprCallIndirect, static_cast(sig_index), TABLE_ZERO