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

Reland "[wasm-gc] Implement isorecursive canonicalization"

This is a reland of commit e76ad5c6

Changes compared to original:
- Move invocation of LAZY_INSTANCE_INITIALIZER to a static global
  variable, as some builds were failing with a function-level static.
- Drive-by: Improve documentation a bit.

Original change's description:
> [wasm-gc] Implement isorecursive canonicalization
>
> This implements isorecursive canonicalization for static types.
>
> Not implemented in this CL:
> - Runtime type canonicalization.
> - Cross-module signature canonicalization for purposes of call_indirect.
>
> Bug: v8:7748
> Change-Id: I6214f947444eea8d7b15a29b35c94c3d07ddb525
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3541925
> Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
> Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#79665}

Bug: v8:7748
Change-Id: I493fba1906491762f7d8bae50108e3e4a743391d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3560480Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79692}
parent 54469edf
...@@ -2434,6 +2434,8 @@ filegroup( ...@@ -2434,6 +2434,8 @@ filegroup(
"src/wasm/baseline/liftoff-compiler.h", "src/wasm/baseline/liftoff-compiler.h",
"src/wasm/baseline/liftoff-register.h", "src/wasm/baseline/liftoff-register.h",
"src/wasm/branch-hint-map.h", "src/wasm/branch-hint-map.h",
"src/wasm/canonical-types.cc",
"src/wasm/canonical-types.h",
"src/wasm/code-space-access.cc", "src/wasm/code-space-access.cc",
"src/wasm/code-space-access.h", "src/wasm/code-space-access.h",
"src/wasm/compilation-environment.h", "src/wasm/compilation-environment.h",
......
...@@ -3513,6 +3513,7 @@ v8_header_set("v8_internal_headers") { ...@@ -3513,6 +3513,7 @@ v8_header_set("v8_internal_headers") {
"src/wasm/baseline/liftoff-assembler.h", "src/wasm/baseline/liftoff-assembler.h",
"src/wasm/baseline/liftoff-compiler.h", "src/wasm/baseline/liftoff-compiler.h",
"src/wasm/baseline/liftoff-register.h", "src/wasm/baseline/liftoff-register.h",
"src/wasm/canonical-types.h",
"src/wasm/code-space-access.h", "src/wasm/code-space-access.h",
"src/wasm/compilation-environment.h", "src/wasm/compilation-environment.h",
"src/wasm/decoder.h", "src/wasm/decoder.h",
...@@ -4540,6 +4541,7 @@ v8_source_set("v8_base_without_compiler") { ...@@ -4540,6 +4541,7 @@ v8_source_set("v8_base_without_compiler") {
"src/trap-handler/handler-shared.cc", "src/trap-handler/handler-shared.cc",
"src/wasm/baseline/liftoff-assembler.cc", "src/wasm/baseline/liftoff-assembler.cc",
"src/wasm/baseline/liftoff-compiler.cc", "src/wasm/baseline/liftoff-compiler.cc",
"src/wasm/canonical-types.cc",
"src/wasm/code-space-access.cc", "src/wasm/code-space-access.cc",
"src/wasm/function-body-decoder.cc", "src/wasm/function-body-decoder.cc",
"src/wasm/function-compiler.cc", "src/wasm/function-compiler.cc",
......
...@@ -1096,10 +1096,14 @@ DEFINE_BOOL(wasm_speculative_inlining, false, ...@@ -1096,10 +1096,14 @@ DEFINE_BOOL(wasm_speculative_inlining, false,
DEFINE_BOOL(trace_wasm_inlining, false, "trace wasm inlining") DEFINE_BOOL(trace_wasm_inlining, false, "trace wasm inlining")
DEFINE_BOOL(trace_wasm_speculative_inlining, false, DEFINE_BOOL(trace_wasm_speculative_inlining, false,
"trace wasm speculative inlining") "trace wasm speculative inlining")
DEFINE_BOOL(wasm_type_canonicalization, false,
"apply isorecursive canonicalization on wasm types")
DEFINE_IMPLICATION(wasm_speculative_inlining, experimental_wasm_typed_funcref) DEFINE_IMPLICATION(wasm_speculative_inlining, experimental_wasm_typed_funcref)
DEFINE_IMPLICATION(wasm_speculative_inlining, wasm_dynamic_tiering) DEFINE_IMPLICATION(wasm_speculative_inlining, wasm_dynamic_tiering)
DEFINE_IMPLICATION(wasm_speculative_inlining, wasm_inlining) DEFINE_IMPLICATION(wasm_speculative_inlining, wasm_inlining)
DEFINE_WEAK_IMPLICATION(experimental_wasm_gc, wasm_speculative_inlining) DEFINE_WEAK_IMPLICATION(experimental_wasm_gc, wasm_speculative_inlining)
DEFINE_WEAK_IMPLICATION(experimental_wasm_typed_funcref,
wasm_type_canonicalization)
// Speculative inlining needs type feedback from Liftoff and compilation in // Speculative inlining needs type feedback from Liftoff and compilation in
// Turbofan. // Turbofan.
DEFINE_NEG_NEG_IMPLICATION(liftoff, wasm_speculative_inlining) DEFINE_NEG_NEG_IMPLICATION(liftoff, wasm_speculative_inlining)
......
// Copyright 2022 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 "src/wasm/canonical-types.h"
#include "src/wasm/wasm-engine.h"
namespace v8 {
namespace internal {
namespace wasm {
V8_EXPORT_PRIVATE TypeCanonicalizer* GetTypeCanonicalizer() {
return GetWasmEngine()->type_canonicalizer();
}
void TypeCanonicalizer::AddRecursiveGroup(WasmModule* module, uint32_t size) {
// Multiple threads could try to register recursive groups concurrently.
// TODO(manoskouk): Investigate if we can fine-grain the synchronization.
base::MutexGuard mutex_guard(&mutex_);
DCHECK_GE(module->types.size(), size);
uint32_t start_index = static_cast<uint32_t>(module->types.size()) - size;
CanonicalGroup group;
group.types.resize(size);
for (uint32_t i = 0; i < size; i++) {
group.types[i] = CanonicalizeTypeDef(module, module->types[start_index + i],
start_index);
}
int canonical_index = FindCanonicalGroup(group);
if (canonical_index >= 0) {
// Identical group found. Map new types to the old types's canonical
// representatives.
for (uint32_t i = 0; i < size; i++) {
module->isorecursive_canonical_type_ids[start_index + i] =
canonical_index + i;
}
} else {
// Identical group not found. Add new canonical representatives for the new
// types.
uint32_t first_canonical_index =
static_cast<uint32_t>(canonical_supertypes_.size());
canonical_supertypes_.resize(first_canonical_index + size);
for (uint32_t i = 0; i < size; i++) {
CanonicalType& canonical_type = group.types[i];
// Compute the canonical index of the supertype: If it is relative, we
// need to add {first_canonical_index}.
canonical_supertypes_[first_canonical_index + i] =
canonical_type.is_relative_supertype
? canonical_type.type_def.supertype + first_canonical_index
: canonical_type.type_def.supertype;
module->isorecursive_canonical_type_ids[start_index + i] =
first_canonical_index + i;
}
canonical_groups_.emplace(group, first_canonical_index);
}
}
// An index in a type gets mapped to a relative index if it is inside the new
// canonical group, or the canonical representative if it is not.
ValueType TypeCanonicalizer::CanonicalizeValueType(
const WasmModule* module, ValueType type,
uint32_t recursive_group_start) const {
if (!type.has_index()) return type;
return type.ref_index() >= recursive_group_start
? ValueType::CanonicalWithRelativeIndex(
type.kind(), type.ref_index() - recursive_group_start)
: ValueType::FromIndex(
type.kind(),
module->isorecursive_canonical_type_ids[type.ref_index()]);
}
bool TypeCanonicalizer::IsCanonicalSubtype(uint32_t sub_index,
uint32_t super_index,
const WasmModule* sub_module,
const WasmModule* super_module) {
// Multiple threads could try to register and access recursive groups
// concurrently.
// TODO(manoskouk): Investigate if we can improve this synchronization.
base::MutexGuard mutex_guard(&mutex_);
uint32_t canonical_super =
super_module->isorecursive_canonical_type_ids[super_index];
uint32_t canonical_sub =
sub_module->isorecursive_canonical_type_ids[sub_index];
while (canonical_sub != kNoSuperType) {
if (canonical_sub == canonical_super) return true;
canonical_sub = canonical_supertypes_[canonical_sub];
}
return false;
}
// Map all type indices (including supertype) inside {type} to indices relative
// to {recursive_group_start}.
TypeCanonicalizer::CanonicalType TypeCanonicalizer::CanonicalizeTypeDef(
const WasmModule* module, TypeDefinition type,
uint32_t recursive_group_start) {
uint32_t canonical_supertype = kNoSuperType;
bool is_relative_supertype = false;
if (type.supertype < recursive_group_start) {
canonical_supertype =
module->isorecursive_canonical_type_ids[type.supertype];
} else if (type.supertype != kNoSuperType) {
canonical_supertype = type.supertype - recursive_group_start;
is_relative_supertype = true;
}
TypeDefinition result;
switch (type.kind) {
case TypeDefinition::kFunction: {
const FunctionSig* original_sig = type.function_sig;
FunctionSig::Builder builder(&zone_, original_sig->return_count(),
original_sig->parameter_count());
for (ValueType ret : original_sig->returns()) {
builder.AddReturn(
CanonicalizeValueType(module, ret, recursive_group_start));
}
for (ValueType param : original_sig->parameters()) {
builder.AddParam(
CanonicalizeValueType(module, param, recursive_group_start));
}
result = TypeDefinition(builder.Build(), canonical_supertype);
break;
}
case TypeDefinition::kStruct: {
const StructType* original_type = type.struct_type;
StructType::Builder builder(&zone_, original_type->field_count());
for (uint32_t i = 0; i < original_type->field_count(); i++) {
builder.AddField(CanonicalizeValueType(module, original_type->field(i),
recursive_group_start),
original_type->mutability(i));
}
result = TypeDefinition(builder.Build(), canonical_supertype);
break;
}
case TypeDefinition::kArray: {
ValueType element_type = CanonicalizeValueType(
module, type.array_type->element_type(), recursive_group_start);
result = TypeDefinition(
zone_.New<ArrayType>(element_type, type.array_type->mutability()),
canonical_supertype);
break;
}
}
return {result, is_relative_supertype};
}
// Returns the index of the canonical representative of the first type in this
// group, or -1 if an identical group does not exist.
int TypeCanonicalizer::FindCanonicalGroup(CanonicalGroup& group) const {
auto element = canonical_groups_.find(group);
return element == canonical_groups_.end() ? -1 : element->second;
}
} // namespace wasm
} // namespace internal
} // namespace v8
// Copyright 2022 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.
#if !V8_ENABLE_WEBASSEMBLY
#error This header should only be included if WebAssembly is enabled.
#endif // !V8_ENABLE_WEBASSEMBLY
#ifndef V8_WASM_CANONICAL_TYPES_H_
#define V8_WASM_CANONICAL_TYPES_H_
#include <unordered_map>
#include "src/base/lazy-instance.h"
#include "src/wasm/wasm-module.h"
namespace v8 {
namespace internal {
namespace wasm {
// A singleton class, responsible for isorecursive canonicalization of wasm
// types.
// A recursive group is a subsequence of types explicitly marked in the type
// section of a wasm module. Identical recursive groups have to be canonicalized
// to a single canonical group and are are considered identical. Respective
// types in two identical groups are considered identical for all purposes.
// Two groups are considered identical if they have the same shape, and all
// type indices referenced in the same position in both groups reference:
// - identical types, if those do not belong to the rec. group,
// - types in the same relative position in the group, if those belong to the
// rec. group.
class TypeCanonicalizer {
public:
TypeCanonicalizer() = default;
// Singleton class; no copying or moving allowed.
TypeCanonicalizer(const TypeCanonicalizer& other) = delete;
TypeCanonicalizer& operator=(const TypeCanonicalizer& other) = delete;
TypeCanonicalizer(TypeCanonicalizer&& other) = delete;
TypeCanonicalizer& operator=(TypeCanonicalizer&& other) = delete;
// Registers the last {size} types of {module} as a recursive group, and
// possibly canonicalizes it if an identical one has been found.
// Modifies {module->isorecursive_canonical_type_ids}.
V8_EXPORT_PRIVATE void AddRecursiveGroup(WasmModule* module, uint32_t size);
// Returns if the type at {sub_index} in {sub_module} is a subtype of the
// type at {super_index} in {super_module} after canonicalization.
V8_EXPORT_PRIVATE bool IsCanonicalSubtype(uint32_t sub_index,
uint32_t super_index,
const WasmModule* sub_module,
const WasmModule* super_module);
private:
using TypeInModule = std::pair<const WasmModule*, uint32_t>;
struct CanonicalType {
TypeDefinition type_def;
bool is_relative_supertype;
bool operator==(const CanonicalType& other) const {
return type_def == other.type_def &&
is_relative_supertype == other.is_relative_supertype;
}
bool operator!=(const CanonicalType& other) const {
return type_def != other.type_def ||
is_relative_supertype != other.is_relative_supertype;
}
size_t hash_value() const {
return base::hash_combine(type_def.kind,
base::hash_value(is_relative_supertype));
}
};
struct CanonicalGroup {
struct hash {
size_t operator()(const CanonicalGroup& group) const {
return group.hash_value();
}
};
bool operator==(const CanonicalGroup& other) const {
return types == other.types;
}
bool operator!=(const CanonicalGroup& other) const {
return types != other.types;
}
size_t hash_value() const {
size_t result = 0;
for (const CanonicalType& type : types) {
result = base::hash_combine(result, type.hash_value());
}
return result;
}
std::vector<CanonicalType> types;
};
int FindCanonicalGroup(CanonicalGroup&) const;
CanonicalType CanonicalizeTypeDef(const WasmModule* module,
TypeDefinition type,
uint32_t recursive_group_start);
ValueType CanonicalizeValueType(const WasmModule* module, ValueType type,
uint32_t recursive_group_start) const;
std::vector<uint32_t> canonical_supertypes_;
// group -> canonical id of first type
std::unordered_map<CanonicalGroup, uint32_t, CanonicalGroup::hash>
canonical_groups_;
AccountingAllocator allocator_;
Zone zone_{&allocator_, "canonical type zone"};
base::Mutex mutex_;
};
// Returns a reference to the TypeCanonicalizer shared by the entire process.
V8_EXPORT_PRIVATE TypeCanonicalizer* GetTypeCanonicalizer();
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_CANONICAL_TYPES_H_
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "src/logging/metrics.h" #include "src/logging/metrics.h"
#include "src/objects/objects-inl.h" #include "src/objects/objects-inl.h"
#include "src/utils/ostreams.h" #include "src/utils/ostreams.h"
#include "src/wasm/canonical-types.h"
#include "src/wasm/decoder.h" #include "src/wasm/decoder.h"
#include "src/wasm/function-body-decoder-impl.h" #include "src/wasm/function-body-decoder-impl.h"
#include "src/wasm/init-expr-interface.h" #include "src/wasm/init-expr-interface.h"
...@@ -669,17 +670,21 @@ class ModuleDecoderImpl : public Decoder { ...@@ -669,17 +670,21 @@ class ModuleDecoderImpl : public Decoder {
} }
void DecodeTypeSection() { void DecodeTypeSection() {
TypeCanonicalizer* type_canon = GetTypeCanonicalizer();
uint32_t types_count = consume_count("types count", kV8MaxWasmTypes); uint32_t types_count = consume_count("types count", kV8MaxWasmTypes);
// Non wasm-gc type section decoding. // Non wasm-gc type section decoding.
if (!enabled_features_.has_gc()) { if (!enabled_features_.has_gc()) {
for (uint32_t i = 0; ok() && i < types_count; ++i) { for (uint32_t i = 0; i < types_count; ++i) {
TRACE("DecodeSignature[%d] module+%d\n", i, TRACE("DecodeSignature[%d] module+%d\n", i,
static_cast<int>(pc_ - start_)); static_cast<int>(pc_ - start_));
expect_u8("signature definition", kWasmFunctionTypeCode); expect_u8("signature definition", kWasmFunctionTypeCode);
const FunctionSig* sig = consume_sig(module_->signature_zone.get()); const FunctionSig* sig = consume_sig(module_->signature_zone.get());
if (!ok()) break; if (!ok()) break;
module_->add_signature(sig, kNoSuperType); module_->add_signature(sig, kNoSuperType);
if (FLAG_wasm_type_canonicalization) {
type_canon->AddRecursiveGroup(module_.get(), 1);
}
} }
return; return;
} }
...@@ -700,6 +705,9 @@ class ModuleDecoderImpl : public Decoder { ...@@ -700,6 +705,9 @@ class ModuleDecoderImpl : public Decoder {
TypeDefinition type = consume_nominal_type_definition(); TypeDefinition type = consume_nominal_type_definition();
if (ok()) module_->add_type(type); if (ok()) module_->add_type(type);
} }
if (ok() && FLAG_wasm_type_canonicalization) {
type_canon->AddRecursiveGroup(module_.get(), types_count);
}
} else { } else {
// wasm-gc isorecursive type section decoding. // wasm-gc isorecursive type section decoding.
for (uint32_t i = 0; ok() && i < types_count; ++i) { for (uint32_t i = 0; ok() && i < types_count; ++i) {
...@@ -722,9 +730,17 @@ class ModuleDecoderImpl : public Decoder { ...@@ -722,9 +730,17 @@ class ModuleDecoderImpl : public Decoder {
TypeDefinition type = consume_subtype_definition(); TypeDefinition type = consume_subtype_definition();
if (ok()) module_->add_type(type); if (ok()) module_->add_type(type);
} }
if (ok() && FLAG_wasm_type_canonicalization) {
type_canon->AddRecursiveGroup(module_.get(), group_size);
}
} else { } else {
TypeDefinition type = consume_subtype_definition(); TypeDefinition type = consume_subtype_definition();
if (ok()) module_->add_type(type); if (ok()) {
module_->add_type(type);
if (FLAG_wasm_type_canonicalization) {
type_canon->AddRecursiveGroup(module_.get(), 1);
}
}
} }
} }
} }
......
...@@ -132,8 +132,12 @@ class ArrayType : public ZoneObject { ...@@ -132,8 +132,12 @@ class ArrayType : public ZoneObject {
ValueType element_type() const { return rep_; } ValueType element_type() const { return rep_; }
bool mutability() const { return mutability_; } bool mutability() const { return mutability_; }
bool operator==(const ArrayType& other) const { return rep_ == other.rep_; } bool operator==(const ArrayType& other) const {
bool operator!=(const ArrayType& other) const { return rep_ != other.rep_; } return rep_ == other.rep_ && mutability_ == other.mutability_;
}
bool operator!=(const ArrayType& other) const {
return rep_ != other.rep_ || mutability_ != other.mutability_;
}
private: private:
const ValueType rep_; const ValueType rep_;
......
...@@ -279,12 +279,14 @@ constexpr bool is_defaultable(ValueKind kind) { ...@@ -279,12 +279,14 @@ constexpr bool is_defaultable(ValueKind kind) {
return kind != kRef && !is_rtt(kind); return kind != kRef && !is_rtt(kind);
} }
// A ValueType is encoded by three components: A ValueKind, a heap // A ValueType is encoded by two components: a ValueKind and a heap
// representation (for reference types), and an inheritance depth (for rtts // representation (for reference types/rtts). Those are encoded into 32 bits
// only). Those are encoded into 32 bits using base::BitField. The underlying // using base::BitField. The underlying ValueKind enumeration includes four
// ValueKind enumeration includes four elements which do not strictly correspond // elements which do not strictly correspond to value types: the two packed
// to value types: the two packed types i8 and i16, the void type (for control // types i8 and i16, the void type (for control structures), and a bottom value
// structures), and a bottom value (for internal use). // (for internal use).
// ValueType encoding includes an additional bit marking the index of a type as
// relative. This should only be used during type canonicalization.
class ValueType { class ValueType {
public: public:
/******************************* Constructors *******************************/ /******************************* Constructors *******************************/
...@@ -309,6 +311,11 @@ class ValueType { ...@@ -309,6 +311,11 @@ class ValueType {
HeapTypeField::encode(type_index)); HeapTypeField::encode(type_index));
} }
static constexpr ValueType FromIndex(ValueKind kind, uint32_t index) {
DCHECK(kind == kOptRef || kind == kRef || kind == kRtt);
return ValueType(KindField::encode(kind) | HeapTypeField::encode(index));
}
// Useful when deserializing a type stored in a runtime object. // Useful when deserializing a type stored in a runtime object.
static constexpr ValueType FromRawBitField(uint32_t bit_field) { static constexpr ValueType FromRawBitField(uint32_t bit_field) {
return ValueType(bit_field); return ValueType(bit_field);
...@@ -491,8 +498,6 @@ class ValueType { ...@@ -491,8 +498,6 @@ class ValueType {
} }
} }
static constexpr int kLastUsedBit = 24;
/****************************** Pretty-printing *****************************/ /****************************** Pretty-printing *****************************/
constexpr char short_name() const { return wasm::short_name(kind()); } constexpr char short_name() const { return wasm::short_name(kind()); }
...@@ -517,23 +522,40 @@ class ValueType { ...@@ -517,23 +522,40 @@ class ValueType {
return buf.str(); return buf.str();
} }
// We only use 31 bits so ValueType fits in a Smi. This can be changed if /********************** Type canonicalization utilities *********************/
// needed. static constexpr ValueType CanonicalWithRelativeIndex(ValueKind kind,
uint32_t index) {
return ValueType(KindField::encode(kind) | HeapTypeField::encode(index) |
CanonicalRelativeField::encode(true));
}
constexpr bool is_canonical_relative() const {
return has_index() && CanonicalRelativeField::decode(bit_field_);
}
/**************************** Static constants ******************************/
static constexpr int kLastUsedBit = 25;
static constexpr int kKindBits = 5; static constexpr int kKindBits = 5;
static constexpr int kHeapTypeBits = 20; static constexpr int kHeapTypeBits = 20;
private: private:
STATIC_ASSERT(kV8MaxWasmTypes < (1u << kHeapTypeBits));
// {hash_value} directly reads {bit_field_}. // {hash_value} directly reads {bit_field_}.
friend size_t hash_value(ValueType type); friend size_t hash_value(ValueType type);
using KindField = base::BitField<ValueKind, 0, kKindBits>; using KindField = base::BitField<ValueKind, 0, kKindBits>;
using HeapTypeField = KindField::Next<uint32_t, kHeapTypeBits>; using HeapTypeField = KindField::Next<uint32_t, kHeapTypeBits>;
// Marks a type as a canonical type which uses an index relative to its
// recursive group start. Used only during type canonicalization.
using CanonicalRelativeField = HeapTypeField::Next<bool, 1>;
static_assert(kV8MaxWasmTypes < (1u << kHeapTypeBits),
"Type indices fit in kHeapTypeBits");
// This is implemented defensively against field order changes. // This is implemented defensively against field order changes.
STATIC_ASSERT(kLastUsedBit == static_assert(kLastUsedBit ==
std::max(KindField::kLastUsedBit, HeapTypeField::kLastUsedBit)); std::max(KindField::kLastUsedBit,
std::max(HeapTypeField::kLastUsedBit,
CanonicalRelativeField::kLastUsedBit)),
"kLastUsedBit is consistent");
constexpr explicit ValueType(uint32_t bit_field) : bit_field_(bit_field) {} constexpr explicit ValueType(uint32_t bit_field) : bit_field_(bit_field) {}
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "src/base/platform/mutex.h" #include "src/base/platform/mutex.h"
#include "src/tasks/cancelable-task.h" #include "src/tasks/cancelable-task.h"
#include "src/tasks/operations-barrier.h" #include "src/tasks/operations-barrier.h"
#include "src/wasm/canonical-types.h"
#include "src/wasm/wasm-code-manager.h" #include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-tier.h" #include "src/wasm/wasm-tier.h"
#include "src/zone/accounting-allocator.h" #include "src/zone/accounting-allocator.h"
...@@ -357,6 +358,8 @@ class V8_EXPORT_PRIVATE WasmEngine { ...@@ -357,6 +358,8 @@ class V8_EXPORT_PRIVATE WasmEngine {
void SampleRethrowEvent(Isolate*); void SampleRethrowEvent(Isolate*);
void SampleCatchEvent(Isolate*); void SampleCatchEvent(Isolate*);
TypeCanonicalizer* type_canonicalizer() { return &type_canonicalizer_; }
// Call on process start and exit. // Call on process start and exit.
static void InitializeOncePerProcess(); static void InitializeOncePerProcess();
static void GlobalTearDown(); static void GlobalTearDown();
...@@ -392,6 +395,8 @@ class V8_EXPORT_PRIVATE WasmEngine { ...@@ -392,6 +395,8 @@ class V8_EXPORT_PRIVATE WasmEngine {
std::atomic<int> next_compilation_id_{0}; std::atomic<int> next_compilation_id_{0};
TypeCanonicalizer type_canonicalizer_;
// This mutex protects all information which is mutated concurrently or // This mutex protects all information which is mutated concurrently or
// fields that are initialized lazily on the first access. // fields that are initialized lazily on the first access.
base::Mutex mutex_; base::Mutex mutex_;
......
...@@ -274,6 +274,8 @@ WasmModuleBuilder::WasmModuleBuilder(Zone* zone) ...@@ -274,6 +274,8 @@ WasmModuleBuilder::WasmModuleBuilder(Zone* zone)
globals_(zone), globals_(zone),
exceptions_(zone), exceptions_(zone),
signature_map_(zone), signature_map_(zone),
current_recursive_group_start_(-1),
recursive_groups_(zone),
start_function_index_(-1), start_function_index_(-1),
min_memory_size_(16), min_memory_size_(16),
max_memory_size_(0), max_memory_size_(0),
...@@ -593,10 +595,24 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer* buffer) const { ...@@ -593,10 +595,24 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer* buffer) const {
// == Emit types ============================================================= // == Emit types =============================================================
if (types_.size() > 0) { if (types_.size() > 0) {
size_t start = EmitSection(kTypeSectionCode, buffer); size_t start = EmitSection(kTypeSectionCode, buffer);
buffer->write_size(types_.size()); size_t type_count = types_.size();
for (auto pair : recursive_groups_) {
// Every rec. group counts as one type entry.
type_count -= pair.second - 1;
}
buffer->write_size(type_count);
for (uint32_t i = 0; i < types_.size(); i++) {
auto recursive_group = recursive_groups_.find(i);
if (recursive_group != recursive_groups_.end()) {
buffer->write_u8(kWasmRecursiveTypeGroupCode);
buffer->write_u32v(recursive_group->second);
}
const TypeDefinition& type = types_[i];
// TODO(7748): Add support for recursive groups.
for (const TypeDefinition& type : types_) {
if (type.supertype != kNoSuperType) { if (type.supertype != kNoSuperType) {
buffer->write_u8(kWasmSubtypeCode); buffer->write_u8(kWasmSubtypeCode);
buffer->write_u8(1); // The supertype count is always 1. buffer->write_u8(1); // The supertype count is always 1.
......
...@@ -359,6 +359,22 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject { ...@@ -359,6 +359,22 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
void SetMaxMemorySize(uint32_t value); void SetMaxMemorySize(uint32_t value);
void SetHasSharedMemory(); void SetHasSharedMemory();
void StartRecursiveTypeGroup() {
DCHECK_EQ(current_recursive_group_start_, -1);
current_recursive_group_start_ = static_cast<int>(types_.size());
}
void EndRecursiveTypeGroup() {
// Make sure we are in a recursive group.
DCHECK_NE(current_recursive_group_start_, -1);
// Make sure the current recursive group has at least one element.
DCHECK_GT(static_cast<int>(types_.size()), current_recursive_group_start_);
recursive_groups_.emplace(
current_recursive_group_start_,
static_cast<uint32_t>(types_.size()) - current_recursive_group_start_);
current_recursive_group_start_ = -1;
}
// Writing methods. // Writing methods.
void WriteTo(ZoneBuffer* buffer) const; void WriteTo(ZoneBuffer* buffer) const;
void WriteAsmJsOffsetTable(ZoneBuffer* buffer) const; void WriteAsmJsOffsetTable(ZoneBuffer* buffer) const;
...@@ -455,6 +471,9 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject { ...@@ -455,6 +471,9 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
ZoneVector<WasmGlobal> globals_; ZoneVector<WasmGlobal> globals_;
ZoneVector<int> exceptions_; ZoneVector<int> exceptions_;
ZoneUnorderedMap<FunctionSig, uint32_t> signature_map_; ZoneUnorderedMap<FunctionSig, uint32_t> signature_map_;
int current_recursive_group_start_;
// first index -> size
ZoneUnorderedMap<uint32_t, uint32_t> recursive_groups_;
int start_function_index_; int start_function_index_;
uint32_t min_memory_size_; uint32_t min_memory_size_;
uint32_t max_memory_size_; uint32_t max_memory_size_;
......
...@@ -364,6 +364,25 @@ struct TypeDefinition { ...@@ -364,6 +364,25 @@ struct TypeDefinition {
const StructType* struct_type; const StructType* struct_type;
const ArrayType* array_type; const ArrayType* array_type;
}; };
bool operator==(const TypeDefinition& other) const {
if (supertype != other.supertype || kind != other.kind) {
return false;
}
switch (kind) {
case kFunction:
return *function_sig == *other.function_sig;
case kStruct:
return *struct_type == *other.struct_type;
case kArray:
return *array_type == *other.array_type;
}
}
bool operator!=(const TypeDefinition& other) const {
return !(*this == other);
}
uint32_t supertype; uint32_t supertype;
Kind kind; Kind kind;
}; };
...@@ -424,15 +443,14 @@ struct V8_EXPORT_PRIVATE WasmModule { ...@@ -424,15 +443,14 @@ struct V8_EXPORT_PRIVATE WasmModule {
? signature_map.FindOrInsert(*type.function_sig) ? signature_map.FindOrInsert(*type.function_sig)
: 0; : 0;
canonicalized_type_ids.push_back(canonical_id); canonicalized_type_ids.push_back(canonical_id);
isorecursive_canonical_type_ids.push_back(-1); // Will be computed later.
} }
bool has_type(uint32_t index) const { return index < types.size(); } bool has_type(uint32_t index) const { return index < types.size(); }
void add_signature(const FunctionSig* sig, uint32_t supertype) { void add_signature(const FunctionSig* sig, uint32_t supertype) {
types.push_back(TypeDefinition(sig, supertype));
DCHECK_NOT_NULL(sig); DCHECK_NOT_NULL(sig);
uint32_t canonical_id = signature_map.FindOrInsert(*sig); add_type(TypeDefinition(sig, supertype));
canonicalized_type_ids.push_back(canonical_id);
} }
bool has_signature(uint32_t index) const { bool has_signature(uint32_t index) const {
return index < types.size() && return index < types.size() &&
...@@ -444,9 +462,8 @@ struct V8_EXPORT_PRIVATE WasmModule { ...@@ -444,9 +462,8 @@ struct V8_EXPORT_PRIVATE WasmModule {
} }
void add_struct_type(const StructType* type, uint32_t supertype) { void add_struct_type(const StructType* type, uint32_t supertype) {
types.push_back(TypeDefinition(type, supertype)); DCHECK_NOT_NULL(type);
// No canonicalization for structs. add_type(TypeDefinition(type, supertype));
canonicalized_type_ids.push_back(0);
} }
bool has_struct(uint32_t index) const { bool has_struct(uint32_t index) const {
return index < types.size() && types[index].kind == TypeDefinition::kStruct; return index < types.size() && types[index].kind == TypeDefinition::kStruct;
...@@ -457,9 +474,8 @@ struct V8_EXPORT_PRIVATE WasmModule { ...@@ -457,9 +474,8 @@ struct V8_EXPORT_PRIVATE WasmModule {
} }
void add_array_type(const ArrayType* type, uint32_t supertype) { void add_array_type(const ArrayType* type, uint32_t supertype) {
types.push_back(TypeDefinition(type, supertype)); DCHECK_NOT_NULL(type);
// No canonicalization for arrays. add_type(TypeDefinition(type, supertype));
canonicalized_type_ids.push_back(0);
} }
bool has_array(uint32_t index) const { bool has_array(uint32_t index) const {
return index < types.size() && types[index].kind == TypeDefinition::kArray; return index < types.size() && types[index].kind == TypeDefinition::kArray;
...@@ -478,11 +494,12 @@ struct V8_EXPORT_PRIVATE WasmModule { ...@@ -478,11 +494,12 @@ struct V8_EXPORT_PRIVATE WasmModule {
} }
std::vector<TypeDefinition> types; // by type index std::vector<TypeDefinition> types; // by type index
// Map from each type index to the index of its corresponding canonical index. // TODO(7748): Unify the following two arrays.
// Canonical indices do not correspond to types. // Maps each type index to a canonical index for purposes of call_indirect.
// Note: right now, only functions are canonicalized, and arrays and structs
// map to 0.
std::vector<uint32_t> canonicalized_type_ids; std::vector<uint32_t> canonicalized_type_ids;
// Maps each type index to its global (cross-module) canonical index as per
// isorecursive type canonicalization.
std::vector<uint32_t> isorecursive_canonical_type_ids;
// Canonicalizing map for signature indexes. // Canonicalizing map for signature indexes.
SignatureMap signature_map; SignatureMap signature_map;
std::vector<WasmFunction> functions; std::vector<WasmFunction> functions;
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "src/wasm/wasm-subtyping.h" #include "src/wasm/wasm-subtyping.h"
#include "src/base/platform/mutex.h" #include "src/base/platform/mutex.h"
#include "src/wasm/canonical-types.h"
#include "src/wasm/wasm-module.h" #include "src/wasm/wasm-module.h"
#include "src/zone/zone-containers.h" #include "src/zone/zone-containers.h"
...@@ -18,17 +19,15 @@ V8_INLINE bool EquivalentIndices(uint32_t index1, uint32_t index2, ...@@ -18,17 +19,15 @@ V8_INLINE bool EquivalentIndices(uint32_t index1, uint32_t index2,
const WasmModule* module1, const WasmModule* module1,
const WasmModule* module2) { const WasmModule* module2) {
DCHECK(index1 != index2 || module1 != module2); DCHECK(index1 != index2 || module1 != module2);
// TODO(7748): Canonicalize types. if (!FLAG_wasm_type_canonicalization) return false;
return false; return module1->isorecursive_canonical_type_ids[index1] ==
module2->isorecursive_canonical_type_ids[index2];
} }
bool ValidStructSubtypeDefinition(uint32_t subtype_index, bool ValidStructSubtypeDefinition(uint32_t subtype_index,
uint32_t supertype_index, uint32_t supertype_index,
const WasmModule* sub_module, const WasmModule* sub_module,
const WasmModule* super_module) { const WasmModule* super_module) {
// TODO(7748): Figure out the cross-module story.
if (sub_module != super_module) return false;
const StructType* sub_struct = sub_module->types[subtype_index].struct_type; const StructType* sub_struct = sub_module->types[subtype_index].struct_type;
const StructType* super_struct = const StructType* super_struct =
super_module->types[supertype_index].struct_type; super_module->types[supertype_index].struct_type;
...@@ -56,9 +55,6 @@ bool ValidArraySubtypeDefinition(uint32_t subtype_index, ...@@ -56,9 +55,6 @@ bool ValidArraySubtypeDefinition(uint32_t subtype_index,
uint32_t supertype_index, uint32_t supertype_index,
const WasmModule* sub_module, const WasmModule* sub_module,
const WasmModule* super_module) { const WasmModule* super_module) {
// TODO(7748): Figure out the cross-module story.
if (sub_module != super_module) return false;
const ArrayType* sub_array = sub_module->types[subtype_index].array_type; const ArrayType* sub_array = sub_module->types[subtype_index].array_type;
const ArrayType* super_array = const ArrayType* super_array =
super_module->types[supertype_index].array_type; super_module->types[supertype_index].array_type;
...@@ -78,9 +74,6 @@ bool ValidFunctionSubtypeDefinition(uint32_t subtype_index, ...@@ -78,9 +74,6 @@ bool ValidFunctionSubtypeDefinition(uint32_t subtype_index,
uint32_t supertype_index, uint32_t supertype_index,
const WasmModule* sub_module, const WasmModule* sub_module,
const WasmModule* super_module) { const WasmModule* super_module) {
// TODO(7748): Figure out the cross-module story.
if (sub_module != super_module) return false;
const FunctionSig* sub_func = sub_module->types[subtype_index].function_sig; const FunctionSig* sub_func = sub_module->types[subtype_index].function_sig;
const FunctionSig* super_func = const FunctionSig* super_func =
super_module->types[supertype_index].function_sig; super_module->types[supertype_index].function_sig;
...@@ -219,15 +212,17 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl( ...@@ -219,15 +212,17 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(
// equality; here we catch (ref $x) being a subtype of (ref null $x). // equality; here we catch (ref $x) being a subtype of (ref null $x).
if (sub_module == super_module && sub_index == super_index) return true; if (sub_module == super_module && sub_index == super_index) return true;
// TODO(7748): Figure out cross-module story. if (FLAG_wasm_type_canonicalization) {
if (sub_module != super_module) return false; return GetTypeCanonicalizer()->IsCanonicalSubtype(sub_index, super_index,
sub_module, super_module);
uint32_t explicit_super = sub_module->supertype(sub_index); } else {
while (true) { uint32_t explicit_super = sub_module->supertype(sub_index);
if (explicit_super == super_index) return true; while (true) {
// Reached the end of the explicitly defined inheritance chain. if (explicit_super == super_index) return true;
if (explicit_super == kNoSuperType) return false; // Reached the end of the explicitly defined inheritance chain.
explicit_super = sub_module->supertype(explicit_super); if (explicit_super == kNoSuperType) return false;
explicit_super = sub_module->supertype(explicit_super);
}
} }
} }
......
...@@ -27,14 +27,16 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl( ...@@ -27,14 +27,16 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(
// - Two numeric types are equivalent iff they are equal. // - Two numeric types are equivalent iff they are equal.
// - T(ht1) ~ T(ht2) iff ht1 ~ ht2 for T in {ref, optref, rtt}. // - T(ht1) ~ T(ht2) iff ht1 ~ ht2 for T in {ref, optref, rtt}.
// Equivalence of heap types ht1 ~ ht2 is defined as follows: // Equivalence of heap types ht1 ~ ht2 is defined as follows:
// - Two heap types are equivalent iff they are equal. // - Two non-index heap types are equivalent iff they are equal.
// - TODO(7748): Implement iso-recursive canonicalization. // - Two indexed heap types are equivalent iff they are iso-recursive
V8_NOINLINE bool EquivalentTypes(ValueType type1, ValueType type2, // equivalent.
const WasmModule* module1, V8_NOINLINE V8_EXPORT_PRIVATE bool EquivalentTypes(ValueType type1,
const WasmModule* module2); ValueType type2,
const WasmModule* module1,
const WasmModule* module2);
// Checks if subtype, defined in module1, is a subtype of supertype, defined in // Checks if {subtype}, defined in {module1}, is a subtype of {supertype},
// module2. // defined in {module2}.
// Subtyping between value types is described by the following rules // Subtyping between value types is described by the following rules
// (structural subtyping): // (structural subtyping):
// - numeric types are subtype-related iff they are equal. // - numeric types are subtype-related iff they are equal.
...@@ -54,7 +56,7 @@ V8_NOINLINE bool EquivalentTypes(ValueType type1, ValueType type2, ...@@ -54,7 +56,7 @@ V8_NOINLINE bool EquivalentTypes(ValueType type1, ValueType type2,
// - All structs are subtypes of data. // - All structs are subtypes of data.
// - All arrays are subtypes of array. // - All arrays are subtypes of array.
// - An indexed heap type h1 is a subtype of indexed heap type h2 if h2 is // - An indexed heap type h1 is a subtype of indexed heap type h2 if h2 is
// transitively an explicit supertype of h1. // transitively an explicit canonical supertype of h1.
V8_INLINE bool IsSubtypeOf(ValueType subtype, ValueType supertype, V8_INLINE bool IsSubtypeOf(ValueType subtype, ValueType supertype,
const WasmModule* sub_module, const WasmModule* sub_module,
const WasmModule* super_module) { const WasmModule* super_module) {
...@@ -62,7 +64,7 @@ V8_INLINE bool IsSubtypeOf(ValueType subtype, ValueType supertype, ...@@ -62,7 +64,7 @@ V8_INLINE bool IsSubtypeOf(ValueType subtype, ValueType supertype,
return IsSubtypeOfImpl(subtype, supertype, sub_module, super_module); return IsSubtypeOfImpl(subtype, supertype, sub_module, super_module);
} }
// Checks if 'subtype' is a subtype of 'supertype' (both defined in module). // Checks if {subtype} is a subtype of {supertype} (both defined in {module}).
V8_INLINE bool IsSubtypeOf(ValueType subtype, ValueType supertype, V8_INLINE bool IsSubtypeOf(ValueType subtype, ValueType supertype,
const WasmModule* module) { const WasmModule* module) {
// If the types are trivially identical, exit early. // If the types are trivially identical, exit early.
......
...@@ -1304,11 +1304,14 @@ WASM_COMPILED_EXEC_TEST(WasmArrayCopy) { ...@@ -1304,11 +1304,14 @@ WASM_COMPILED_EXEC_TEST(WasmArrayCopy) {
tester.CheckResult(kZeroLength, 0); // Does not throw. tester.CheckResult(kZeroLength, 0); // Does not throw.
} }
/* TODO(7748): This test requires for recursive groups.
WASM_COMPILED_EXEC_TEST(NewDefault) { WASM_COMPILED_EXEC_TEST(NewDefault) {
WasmGCTester tester(execution_tier); WasmGCTester tester(execution_tier);
tester.builder()->StartRecursiveTypeGroup();
const byte struct_type = tester.DefineStruct( const byte struct_type = tester.DefineStruct(
{F(wasm::kWasmI32, true), F(wasm::kWasmF64, true), F(optref(0), true)}); {F(wasm::kWasmI32, true), F(wasm::kWasmF64, true), F(optref(0), true)});
tester.builder()->EndRecursiveTypeGroup();
const byte array_type = tester.DefineArray(wasm::kWasmI32, true); const byte array_type = tester.DefineArray(wasm::kWasmI32, true);
// Returns: struct[0] + f64_to_i32(struct[1]) + (struct[2].is_null ^ 1) == 0. // Returns: struct[0] + f64_to_i32(struct[1]) + (struct[2].is_null ^ 1) == 0.
const byte allocate_struct = tester.DefineFunction( const byte allocate_struct = tester.DefineFunction(
...@@ -1338,7 +1341,6 @@ WASM_COMPILED_EXEC_TEST(NewDefault) { ...@@ -1338,7 +1341,6 @@ WASM_COMPILED_EXEC_TEST(NewDefault) {
tester.CheckResult(allocate_struct, 0); tester.CheckResult(allocate_struct, 0);
tester.CheckResult(allocate_array, 0); tester.CheckResult(allocate_array, 0);
} }
*/
WASM_COMPILED_EXEC_TEST(BasicRtt) { WASM_COMPILED_EXEC_TEST(BasicRtt) {
WasmGCTester tester(execution_tier); WasmGCTester tester(execution_tier);
......
...@@ -96,11 +96,10 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js"); ...@@ -96,11 +96,10 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
print("--imported function from another module--"); print("--imported function from another module--");
assertEquals(57, instance.exports.test_wasm_import()); assertEquals(57, instance.exports.test_wasm_import());
/* TODO(7748): Implement cross-module type canonicalization.
print("--not imported function defined in another module--"); print("--not imported function defined in another module--");
assertEquals(19, instance.exports.main( assertEquals(19, instance.exports.main(
exporting_instance.exports.addition, 12, 7)); exporting_instance.exports.addition, 12, 7));
*/
print("--imported WebAssembly.Function--") print("--imported WebAssembly.Function--")
assertEquals(21, instance.exports.test_js_api_import()); assertEquals(21, instance.exports.test_js_api_import());
print("--not imported WebAssembly.Function--") print("--not imported WebAssembly.Function--")
......
...@@ -35,9 +35,8 @@ var importing_module = function(imported_function) { ...@@ -35,9 +35,8 @@ var importing_module = function(imported_function) {
return builder.instantiate({other: {func: imported_function}}); return builder.instantiate({other: {func: imported_function}});
}; };
// TODO(7748): Implement cross-module subtyping.
// Same form/different index should be fine. // Same form/different index should be fine.
// importing_module(exporting_module.exports.func2); importing_module(exporting_module.exports.func2);
// Same index/different form should throw. // Same index/different form should throw.
assertThrows( assertThrows(
() => importing_module(exporting_module.exports.func1), () => importing_module(exporting_module.exports.func1),
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js"); d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
/* TODO(7748): Implement cross-module subtyping.
(function TestReferenceGlobals() { (function TestReferenceGlobals() {
print(arguments.callee.name); print(arguments.callee.name);
...@@ -106,7 +105,6 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js"); ...@@ -106,7 +105,6 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// The correct function reference has been passed. // The correct function reference has been passed.
assertEquals(66, instance.exports.test_import(42, 24)); assertEquals(66, instance.exports.test_import(42, 24));
})(); })();
*/
(function TestStructInitExpr() { (function TestStructInitExpr() {
print(arguments.callee.name); print(arguments.callee.name);
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
// Flags: --experimental-wasm-gc // Flags: --experimental-wasm-gc
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
/* TODO(7748): Implement cross-module subtyping.
(function TestTables() { (function TestTables() {
print(arguments.callee.name); print(arguments.callee.name);
var exporting_instance = (function() { var exporting_instance = (function() {
...@@ -100,9 +99,8 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); ...@@ -100,9 +99,8 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
assertThrows( assertThrows(
() => instance.exports.table.set(0, exporting_instance.exports.addition), () => instance.exports.table.set(0, exporting_instance.exports.addition),
TypeError, TypeError,
/Argument 1 must be null or a WebAssembly function of type compatible to/); /Argument 1 is invalid for table of type \(ref null 0\)/);
})(); })();
*/
(function TestNonNullableTables() { (function TestNonNullableTables() {
print(arguments.callee.name); print(arguments.callee.name);
......
...@@ -155,7 +155,6 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js"); ...@@ -155,7 +155,6 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
assertEquals(8, instance.exports.main(10, 0)); assertEquals(8, instance.exports.main(10, 0));
})(); })();
/* TODO(7748): Implement cross-module subtyping.
(function CallRefImportedFunction() { (function CallRefImportedFunction() {
print(arguments.callee.name); print(arguments.callee.name);
...@@ -196,7 +195,6 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js"); ...@@ -196,7 +195,6 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// The function f1 defined in another module should not be inlined. // The function f1 defined in another module should not be inlined.
assertEquals(1, instance2.exports.main(0, instance1.exports.f1)); assertEquals(1, instance2.exports.main(0, instance1.exports.f1));
})(); })();
*/
// Check that we handle WasmJSFunctions properly and do not inline them, both // Check that we handle WasmJSFunctions properly and do not inline them, both
// in the monomorphic and polymorphic case. // in the monomorphic and polymorphic case.
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "src/objects/objects-inl.h" #include "src/objects/objects-inl.h"
#include "src/objects/objects.h" #include "src/objects/objects.h"
#include "src/utils/ostreams.h" #include "src/utils/ostreams.h"
#include "src/wasm/canonical-types.h"
#include "src/wasm/function-body-decoder-impl.h" #include "src/wasm/function-body-decoder-impl.h"
#include "src/wasm/leb-helper.h" #include "src/wasm/leb-helper.h"
#include "src/wasm/local-decl-encoder.h" #include "src/wasm/local-decl-encoder.h"
...@@ -89,6 +90,7 @@ class TestModuleBuilder { ...@@ -89,6 +90,7 @@ class TestModuleBuilder {
byte AddSignature(const FunctionSig* sig, uint32_t supertype = kNoSuperType) { byte AddSignature(const FunctionSig* sig, uint32_t supertype = kNoSuperType) {
mod.add_signature(sig, supertype); mod.add_signature(sig, supertype);
CHECK_LE(mod.types.size(), kMaxByteSizedLeb128); CHECK_LE(mod.types.size(), kMaxByteSizedLeb128);
GetTypeCanonicalizer()->AddRecursiveGroup(module(), 1);
return static_cast<byte>(mod.types.size() - 1); return static_cast<byte>(mod.types.size() - 1);
} }
byte AddFunction(const FunctionSig* sig, bool declared = true) { byte AddFunction(const FunctionSig* sig, bool declared = true) {
...@@ -131,12 +133,14 @@ class TestModuleBuilder { ...@@ -131,12 +133,14 @@ class TestModuleBuilder {
type_builder.AddField(field.first, field.second); type_builder.AddField(field.first, field.second);
} }
mod.add_struct_type(type_builder.Build(), supertype); mod.add_struct_type(type_builder.Build(), supertype);
GetTypeCanonicalizer()->AddRecursiveGroup(module(), 1);
return static_cast<byte>(mod.types.size() - 1); return static_cast<byte>(mod.types.size() - 1);
} }
byte AddArray(ValueType type, bool mutability) { byte AddArray(ValueType type, bool mutability) {
ArrayType* array = mod.signature_zone->New<ArrayType>(type, mutability); ArrayType* array = mod.signature_zone->New<ArrayType>(type, mutability);
mod.add_array_type(array, kNoSuperType); mod.add_array_type(array, kNoSuperType);
GetTypeCanonicalizer()->AddRecursiveGroup(module(), 1);
return static_cast<byte>(mod.types.size() - 1); return static_cast<byte>(mod.types.size() - 1);
} }
...@@ -3639,32 +3643,6 @@ TEST_F(FunctionBodyDecoderTest, StructNewDefaultWithRtt) { ...@@ -3639,32 +3643,6 @@ TEST_F(FunctionBodyDecoderTest, StructNewDefaultWithRtt) {
} }
} }
TEST_F(FunctionBodyDecoderTest, NominalStructSubtyping) {
WASM_FEATURE_SCOPE(typed_funcref);
WASM_FEATURE_SCOPE(gc);
byte structural_type = builder.AddStruct({F(kWasmI32, true)});
byte nominal_type = builder.AddStruct({F(kWasmI32, true)});
AddLocals(optref(structural_type), 1);
AddLocals(optref(nominal_type), 1);
// Try to assign a nominally-typed value to a structurally-typed local.
ExpectFailure(sigs.v_v(),
{WASM_LOCAL_SET(0, WASM_STRUCT_NEW_DEFAULT(nominal_type))},
kAppendEnd, "expected type (ref null 0)");
// Try to assign a structurally-typed value to a nominally-typed local.
ExpectFailure(sigs.v_v(),
{WASM_LOCAL_SET(
1, WASM_STRUCT_NEW_DEFAULT_WITH_RTT(
structural_type, WASM_RTT_CANON(structural_type)))},
kAppendEnd, "expected type (ref null 1)");
// But assigning to the correctly typed local works.
ExpectValidates(sigs.v_v(),
{WASM_LOCAL_SET(1, WASM_STRUCT_NEW_DEFAULT(nominal_type))});
ExpectValidates(sigs.v_v(),
{WASM_LOCAL_SET(0, WASM_STRUCT_NEW_DEFAULT_WITH_RTT(
structural_type,
WASM_RTT_CANON(structural_type)))});
}
TEST_F(FunctionBodyDecoderTest, DefaultableLocal) { TEST_F(FunctionBodyDecoderTest, DefaultableLocal) {
WASM_FEATURE_SCOPE(typed_funcref); WASM_FEATURE_SCOPE(typed_funcref);
AddLocals(kWasmAnyRef, 1); AddLocals(kWasmAnyRef, 1);
......
...@@ -166,10 +166,12 @@ namespace module_decoder_unittest { ...@@ -166,10 +166,12 @@ namespace module_decoder_unittest {
} \ } \
} while (false) } while (false)
#define EXPECT_NOT_OK(result, msg) \ #define EXPECT_NOT_OK(result, msg) \
do { \ do { \
EXPECT_FALSE(result.ok()); \ EXPECT_FALSE(result.ok()); \
EXPECT_THAT(result.error().message(), HasSubstr(msg)); \ if (!result.ok()) { \
EXPECT_THAT(result.error().message(), HasSubstr(msg)); \
} \
} while (false) } while (false)
static size_t SizeOfVarInt(size_t value) { static size_t SizeOfVarInt(size_t value) {
...@@ -803,7 +805,7 @@ TEST_F(WasmModuleVerifyTest, RttCanonGlobalTypeError) { ...@@ -803,7 +805,7 @@ TEST_F(WasmModuleVerifyTest, RttCanonGlobalTypeError) {
static const byte data[] = { static const byte data[] = {
SECTION(Type, ENTRY_COUNT(2), SECTION(Type, ENTRY_COUNT(2),
WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true)), WASM_STRUCT_DEF(FIELD_COUNT(1), STRUCT_FIELD(kI32Code, true)),
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), WASM_RTT(0), 1, WASM_RTT_CANON(1), SECTION(Global, ENTRY_COUNT(1), WASM_RTT(0), 1, WASM_RTT_CANON(1),
kExprEnd)}; kExprEnd)};
ModuleResult result = DecodeModule(data, data + sizeof(data)); ModuleResult result = DecodeModule(data, data + sizeof(data));
...@@ -1189,6 +1191,46 @@ TEST_F(WasmModuleVerifyTest, InvalidArrayTypeDef) { ...@@ -1189,6 +1191,46 @@ TEST_F(WasmModuleVerifyTest, InvalidArrayTypeDef) {
EXPECT_VERIFIES(immutable); EXPECT_VERIFIES(immutable);
} }
TEST_F(WasmModuleVerifyTest, TypeCanonicalization) {
WASM_FEATURE_SCOPE(typed_funcref);
WASM_FEATURE_SCOPE(gc);
FLAG_SCOPE(wasm_type_canonicalization);
static const byte identical_group[] = {
SECTION(Type, // --
ENTRY_COUNT(2), // two identical rec. groups
kWasmRecursiveTypeGroupCode, ENTRY_COUNT(1), // --
kWasmArrayTypeCode, kI32Code, 0, // --
kWasmRecursiveTypeGroupCode, ENTRY_COUNT(1), // --
kWasmArrayTypeCode, kI32Code, 0),
SECTION(Global, // --
ENTRY_COUNT(1), kRefCode, 0, 0, // Type, mutability
WASM_ARRAY_INIT_STATIC(1, 1, WASM_I32V(10)),
kExprEnd) // Init. expression
};
// Global initializer should verify as identical type in other group
EXPECT_VERIFIES(identical_group);
static const byte non_identical_group[] = {
SECTION(Type, // --
ENTRY_COUNT(2), // two distrinct rec. groups
kWasmRecursiveTypeGroupCode, ENTRY_COUNT(1), // --
kWasmArrayTypeCode, kI32Code, 0, // --
kWasmRecursiveTypeGroupCode, ENTRY_COUNT(2), // --
kWasmArrayTypeCode, kI32Code, 0, // --
kWasmStructTypeCode, ENTRY_COUNT(0)),
SECTION(Global, // --
ENTRY_COUNT(1), kRefCode, 0, 0, // Type, mutability
WASM_ARRAY_INIT_STATIC(1, 1, WASM_I32V(10)),
kExprEnd) // Init. expression
};
// Global initializer should not verify as type in distinct rec. group.
EXPECT_FAILURE_WITH_MSG(
non_identical_group,
"type error in init. expression[0] (expected (ref 0), got (ref 1))");
}
TEST_F(WasmModuleVerifyTest, ZeroExceptions) { TEST_F(WasmModuleVerifyTest, ZeroExceptions) {
static const byte data[] = {SECTION(Tag, ENTRY_COUNT(0))}; static const byte data[] = {SECTION(Tag, ENTRY_COUNT(0))};
FAIL_IF_NO_EXPERIMENTAL_EH(data); FAIL_IF_NO_EXPERIMENTAL_EH(data);
...@@ -3389,13 +3431,13 @@ TEST_F(WasmModuleVerifyTest, DataCountSegmentCount_omitted) { ...@@ -3389,13 +3431,13 @@ TEST_F(WasmModuleVerifyTest, DataCountSegmentCount_omitted) {
EXPECT_NOT_OK(result, "data segments count 0 mismatch (1 expected)"); EXPECT_NOT_OK(result, "data segments count 0 mismatch (1 expected)");
} }
/* TODO(7748): Add support for rec. groups.
TEST_F(WasmModuleVerifyTest, GcStructIdsPass) { TEST_F(WasmModuleVerifyTest, GcStructIdsPass) {
WASM_FEATURE_SCOPE(gc); WASM_FEATURE_SCOPE(gc);
WASM_FEATURE_SCOPE(typed_funcref); WASM_FEATURE_SCOPE(typed_funcref);
static const byte data[] = {SECTION( static const byte data[] = {SECTION(
Type, ENTRY_COUNT(3), Type, ENTRY_COUNT(1), // One recursive group...
kWasmRecursiveTypeGroupCode, ENTRY_COUNT(3), // with three entries.
WASM_STRUCT_DEF(FIELD_COUNT(3), STRUCT_FIELD(kI32Code, true), WASM_STRUCT_DEF(FIELD_COUNT(3), STRUCT_FIELD(kI32Code, true),
STRUCT_FIELD(WASM_OPT_REF(0), true), STRUCT_FIELD(WASM_OPT_REF(0), true),
STRUCT_FIELD(WASM_OPT_REF(1), true)), STRUCT_FIELD(WASM_OPT_REF(1), true)),
...@@ -3404,7 +3446,7 @@ TEST_F(WasmModuleVerifyTest, GcStructIdsPass) { ...@@ -3404,7 +3446,7 @@ TEST_F(WasmModuleVerifyTest, GcStructIdsPass) {
WASM_ARRAY_DEF(WASM_OPT_REF(0), true))}; WASM_ARRAY_DEF(WASM_OPT_REF(0), true))};
ModuleResult result = DecodeModule(data, data + sizeof(data)); ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_OK(result); EXPECT_OK(result);
}*/ }
TEST_F(WasmModuleVerifyTest, OutOfBoundsTypeInGlobal) { TEST_F(WasmModuleVerifyTest, OutOfBoundsTypeInGlobal) {
WASM_FEATURE_SCOPE(typed_funcref); WASM_FEATURE_SCOPE(typed_funcref);
...@@ -3424,7 +3466,6 @@ TEST_F(WasmModuleVerifyTest, OutOfBoundsTypeInType) { ...@@ -3424,7 +3466,6 @@ TEST_F(WasmModuleVerifyTest, OutOfBoundsTypeInType) {
EXPECT_NOT_OK(result, "Type index 1 is out of bounds"); EXPECT_NOT_OK(result, "Type index 1 is out of bounds");
} }
// TODO(7748): Add support for rec. groups.
TEST_F(WasmModuleVerifyTest, ForwardSupertype) { TEST_F(WasmModuleVerifyTest, ForwardSupertype) {
WASM_FEATURE_SCOPE(typed_funcref); WASM_FEATURE_SCOPE(typed_funcref);
WASM_FEATURE_SCOPE(gc); WASM_FEATURE_SCOPE(gc);
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "src/wasm/canonical-types.h"
#include "src/wasm/wasm-subtyping.h" #include "src/wasm/wasm-subtyping.h"
#include "test/common/flag-utils.h"
#include "test/common/wasm/flag-utils.h" #include "test/common/wasm/flag-utils.h"
#include "test/unittests/test-utils.h" #include "test/unittests/test-utils.h"
...@@ -25,29 +27,41 @@ FieldInit mut(ValueType type) { return FieldInit(type, true); } ...@@ -25,29 +27,41 @@ FieldInit mut(ValueType type) { return FieldInit(type, true); }
FieldInit immut(ValueType type) { return FieldInit(type, false); } FieldInit immut(ValueType type) { return FieldInit(type, false); }
void DefineStruct(WasmModule* module, std::initializer_list<FieldInit> fields, void DefineStruct(WasmModule* module, std::initializer_list<FieldInit> fields,
uint32_t supertype = kNoSuperType) { uint32_t supertype = kNoSuperType,
bool in_singleton_rec_group = true) {
StructType::Builder builder(module->signature_zone.get(), StructType::Builder builder(module->signature_zone.get(),
static_cast<uint32_t>(fields.size())); static_cast<uint32_t>(fields.size()));
for (FieldInit field : fields) { for (FieldInit field : fields) {
builder.AddField(field.first, field.second); builder.AddField(field.first, field.second);
} }
return module->add_struct_type(builder.Build(), supertype); module->add_struct_type(builder.Build(), supertype);
if (in_singleton_rec_group) {
GetTypeCanonicalizer()->AddRecursiveGroup(module, 1);
}
} }
void DefineArray(WasmModule* module, FieldInit element_type, void DefineArray(WasmModule* module, FieldInit element_type,
uint32_t supertype = kNoSuperType) { uint32_t supertype = kNoSuperType,
bool in_singleton_rec_group = true) {
module->add_array_type(module->signature_zone->New<ArrayType>( module->add_array_type(module->signature_zone->New<ArrayType>(
element_type.first, element_type.second), element_type.first, element_type.second),
supertype); supertype);
if (in_singleton_rec_group) {
GetTypeCanonicalizer()->AddRecursiveGroup(module, 1);
}
} }
void DefineSignature(WasmModule* module, void DefineSignature(WasmModule* module,
std::initializer_list<ValueType> params, std::initializer_list<ValueType> params,
std::initializer_list<ValueType> returns, std::initializer_list<ValueType> returns,
uint32_t supertype = kNoSuperType) { uint32_t supertype = kNoSuperType,
bool in_singleton_rec_group = true) {
module->add_signature( module->add_signature(
FunctionSig::Build(module->signature_zone.get(), returns, params), FunctionSig::Build(module->signature_zone.get(), returns, params),
supertype); supertype);
if (in_singleton_rec_group) {
GetTypeCanonicalizer()->AddRecursiveGroup(module, 1);
}
} }
TEST_F(WasmSubtypingTest, Subtyping) { TEST_F(WasmSubtypingTest, Subtyping) {
...@@ -79,6 +93,37 @@ TEST_F(WasmSubtypingTest, Subtyping) { ...@@ -79,6 +93,37 @@ TEST_F(WasmSubtypingTest, Subtyping) {
/* 14 */ DefineSignature(module, {ref(0)}, {kWasmI32}, 13); /* 14 */ DefineSignature(module, {ref(0)}, {kWasmI32}, 13);
/* 15 */ DefineSignature(module, {ref(0)}, {ref(4)}, 16); /* 15 */ DefineSignature(module, {ref(0)}, {ref(4)}, 16);
/* 16 */ DefineSignature(module, {ref(0)}, {ref(0)}); /* 16 */ DefineSignature(module, {ref(0)}, {ref(0)});
/* 17 */ DefineStruct(module, {mut(kWasmI32), immut(optRef(17))});
// Rec. group.
/* 18 */ DefineStruct(module, {mut(kWasmI32), immut(optRef(17))}, 17,
false);
/* 19 */ DefineArray(module, {mut(optRef(21))}, kNoSuperType, false);
/* 20 */ DefineSignature(module, {kWasmI32}, {kWasmI32}, kNoSuperType,
false);
/* 21 */ DefineSignature(module, {kWasmI32}, {kWasmI32}, 20, false);
GetTypeCanonicalizer()->AddRecursiveGroup(module, 4);
// Identical rec. group.
/* 22 */ DefineStruct(module, {mut(kWasmI32), immut(optRef(17))}, 17,
false);
/* 23 */ DefineArray(module, {mut(optRef(25))}, kNoSuperType, false);
/* 24 */ DefineSignature(module, {kWasmI32}, {kWasmI32}, kNoSuperType,
false);
/* 25 */ DefineSignature(module, {kWasmI32}, {kWasmI32}, 24, false);
GetTypeCanonicalizer()->AddRecursiveGroup(module, 4);
// Nonidentical rec. group: the last function extends a type outside the
// recursive group.
/* 26 */ DefineStruct(module, {mut(kWasmI32), immut(optRef(17))}, 17,
false);
/* 27 */ DefineArray(module, {mut(optRef(29))}, kNoSuperType, false);
/* 28 */ DefineSignature(module, {kWasmI32}, {kWasmI32}, kNoSuperType,
false);
/* 29 */ DefineSignature(module, {kWasmI32}, {kWasmI32}, 20, false);
GetTypeCanonicalizer()->AddRecursiveGroup(module, 4);
/* 30 */ DefineStruct(module, {mut(kWasmI32), immut(optRef(18))}, 18);
} }
constexpr ValueType numeric_types[] = {kWasmI32, kWasmI64, kWasmF32, kWasmF64, constexpr ValueType numeric_types[] = {kWasmI32, kWasmI64, kWasmF32, kWasmF64,
...@@ -88,6 +133,7 @@ TEST_F(WasmSubtypingTest, Subtyping) { ...@@ -88,6 +133,7 @@ TEST_F(WasmSubtypingTest, Subtyping) {
optRef(0), ref(0), optRef(2), optRef(0), ref(0), optRef(2),
ref(2), optRef(11), ref(11)}; ref(2), optRef(11), ref(11)};
// Some macros to help managing types and modules.
#define SUBTYPE(type1, type2) \ #define SUBTYPE(type1, type2) \
EXPECT_TRUE(IsSubtypeOf(type1, type2, module1, module)) EXPECT_TRUE(IsSubtypeOf(type1, type2, module1, module))
#define SUBTYPE_IFF(type1, type2, condition) \ #define SUBTYPE_IFF(type1, type2, condition) \
...@@ -102,10 +148,20 @@ TEST_F(WasmSubtypingTest, Subtyping) { ...@@ -102,10 +148,20 @@ TEST_F(WasmSubtypingTest, Subtyping) {
#define NOT_VALID_SUBTYPE(type1, type2) \ #define NOT_VALID_SUBTYPE(type1, type2) \
EXPECT_FALSE(ValidSubtypeDefinition(type1.ref_index(), type2.ref_index(), \ EXPECT_FALSE(ValidSubtypeDefinition(type1.ref_index(), type2.ref_index(), \
module1, module)); module1, module));
#define IDENTICAL(index1, index2) \
EXPECT_TRUE(EquivalentTypes(ValueType::Ref(index1, kNullable), \
ValueType::Ref(index2, kNullable), module1, \
module));
#define DISTINCT(index1, index2) \
EXPECT_FALSE(EquivalentTypes(ValueType::Ref(index1, kNullable), \
ValueType::Ref(index2, kNullable), module1, \
module));
for (WasmModule* module : {module1, module2}) {
// For cross module subtyping, we need to enable type canonicalization.
// Type judgements across modules should work the same as within one module.
FLAG_VALUE_SCOPE(wasm_type_canonicalization, module == module2);
// Type judgements across modules should work the same as within one module.
// TODO(7748): add module2 once we have a cross-module story.
for (WasmModule* module : {module1 /* , module2 */}) {
// Value types are unrelated, except if they are equal. // Value types are unrelated, except if they are equal.
for (ValueType subtype : numeric_types) { for (ValueType subtype : numeric_types) {
for (ValueType supertype : numeric_types) { for (ValueType supertype : numeric_types) {
...@@ -183,9 +239,6 @@ TEST_F(WasmSubtypingTest, Subtyping) { ...@@ -183,9 +239,6 @@ TEST_F(WasmSubtypingTest, Subtyping) {
SUBTYPE(ValueType::Rtt(5), ValueType::Rtt(5)); SUBTYPE(ValueType::Rtt(5), ValueType::Rtt(5));
// Rtts of unrelated types are unrelated. // Rtts of unrelated types are unrelated.
NOT_SUBTYPE(ValueType::Rtt(1), ValueType::Rtt(2)); NOT_SUBTYPE(ValueType::Rtt(1), ValueType::Rtt(2));
// Rtts of identical types are subtype-related.
// TODO(7748): Implement type canonicalization.
// SUBTYPE(ValueType::Rtt(8), ValueType::Rtt(9));
// Rtts of subtypes are not related. // Rtts of subtypes are not related.
NOT_SUBTYPE(ValueType::Rtt(1), ValueType::Rtt(0)); NOT_SUBTYPE(ValueType::Rtt(1), ValueType::Rtt(0));
...@@ -201,10 +254,42 @@ TEST_F(WasmSubtypingTest, Subtyping) { ...@@ -201,10 +254,42 @@ TEST_F(WasmSubtypingTest, Subtyping) {
// Identical types are subtype-related. // Identical types are subtype-related.
VALID_SUBTYPE(ref(10), ref(10)); VALID_SUBTYPE(ref(10), ref(10));
VALID_SUBTYPE(ref(11), ref(11)); VALID_SUBTYPE(ref(11), ref(11));
{
// Canonicalization tests.
FLAG_SCOPE(wasm_type_canonicalization);
// Groups should only be canonicalized to identical groups.
IDENTICAL(18, 22);
IDENTICAL(19, 23);
IDENTICAL(20, 24);
IDENTICAL(21, 25);
DISTINCT(18, 26);
DISTINCT(19, 27);
DISTINCT(20, 28);
DISTINCT(21, 29);
// A type should not be canonicalized to an identical one with a different
// group structure.
DISTINCT(18, 17);
// A subtype should also be subtype of an equivalent type.
VALID_SUBTYPE(ref(30), ref(18));
VALID_SUBTYPE(ref(30), ref(22));
NOT_SUBTYPE(ref(30), ref(26));
// Rtts of identical types are subtype-related.
SUBTYPE(ValueType::Rtt(8), ValueType::Rtt(17));
}
} }
#undef SUBTYPE #undef SUBTYPE
#undef NOT_SUBTYPE #undef NOT_SUBTYPE
#undef SUBTYPE_IFF #undef SUBTYPE_IFF
#undef VALID_SUBTYPE
#undef NOT_VALID_SUBTYPE
#undef IDENTICAL
#undef DISTINCT
} }
} // namespace subtyping_unittest } // namespace subtyping_unittest
......
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