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

[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/+/3541925Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79665}
parent 0ca7f580
......@@ -2434,6 +2434,8 @@ filegroup(
"src/wasm/baseline/liftoff-compiler.h",
"src/wasm/baseline/liftoff-register.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.h",
"src/wasm/compilation-environment.h",
......
......@@ -3513,6 +3513,7 @@ v8_header_set("v8_internal_headers") {
"src/wasm/baseline/liftoff-assembler.h",
"src/wasm/baseline/liftoff-compiler.h",
"src/wasm/baseline/liftoff-register.h",
"src/wasm/canonical-types.h",
"src/wasm/code-space-access.h",
"src/wasm/compilation-environment.h",
"src/wasm/decoder.h",
......@@ -4540,6 +4541,7 @@ v8_source_set("v8_base_without_compiler") {
"src/trap-handler/handler-shared.cc",
"src/wasm/baseline/liftoff-assembler.cc",
"src/wasm/baseline/liftoff-compiler.cc",
"src/wasm/canonical-types.cc",
"src/wasm/code-space-access.cc",
"src/wasm/function-body-decoder.cc",
"src/wasm/function-compiler.cc",
......
......@@ -1095,10 +1095,14 @@ DEFINE_BOOL(wasm_speculative_inlining, false,
DEFINE_BOOL(trace_wasm_inlining, false, "trace wasm inlining")
DEFINE_BOOL(trace_wasm_speculative_inlining, false,
"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, wasm_dynamic_tiering)
DEFINE_IMPLICATION(wasm_speculative_inlining, wasm_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
// Turbofan.
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"
namespace v8 {
namespace internal {
namespace wasm {
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;
static TypeCanonicalizer* instance() {
static base::LazyInstance<TypeCanonicalizer>::type instance_ =
LAZY_INSTANCE_INITIALIZER;
return instance_.Pointer();
}
// 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_;
};
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_CANONICAL_TYPES_H_
......@@ -13,6 +13,7 @@
#include "src/logging/metrics.h"
#include "src/objects/objects-inl.h"
#include "src/utils/ostreams.h"
#include "src/wasm/canonical-types.h"
#include "src/wasm/decoder.h"
#include "src/wasm/function-body-decoder-impl.h"
#include "src/wasm/init-expr-interface.h"
......@@ -669,17 +670,21 @@ class ModuleDecoderImpl : public Decoder {
}
void DecodeTypeSection() {
TypeCanonicalizer* type_canon = TypeCanonicalizer::instance();
uint32_t types_count = consume_count("types count", kV8MaxWasmTypes);
// Non wasm-gc type section decoding.
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,
static_cast<int>(pc_ - start_));
expect_u8("signature definition", kWasmFunctionTypeCode);
const FunctionSig* sig = consume_sig(module_->signature_zone.get());
if (!ok()) break;
module_->add_signature(sig, kNoSuperType);
if (FLAG_wasm_type_canonicalization) {
type_canon->AddRecursiveGroup(module_.get(), 1);
}
}
return;
}
......@@ -700,6 +705,9 @@ class ModuleDecoderImpl : public Decoder {
TypeDefinition type = consume_nominal_type_definition();
if (ok()) module_->add_type(type);
}
if (ok() && FLAG_wasm_type_canonicalization) {
type_canon->AddRecursiveGroup(module_.get(), types_count);
}
} else {
// wasm-gc isorecursive type section decoding.
for (uint32_t i = 0; ok() && i < types_count; ++i) {
......@@ -722,9 +730,17 @@ class ModuleDecoderImpl : public Decoder {
TypeDefinition type = consume_subtype_definition();
if (ok()) module_->add_type(type);
}
if (ok() && FLAG_wasm_type_canonicalization) {
type_canon->AddRecursiveGroup(module_.get(), group_size);
}
} else {
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 {
ValueType element_type() const { return rep_; }
bool mutability() const { return mutability_; }
bool operator==(const ArrayType& other) const { return rep_ == other.rep_; }
bool operator!=(const ArrayType& other) const { return rep_ != other.rep_; }
bool operator==(const ArrayType& other) const {
return rep_ == other.rep_ && mutability_ == other.mutability_;
}
bool operator!=(const ArrayType& other) const {
return rep_ != other.rep_ || mutability_ != other.mutability_;
}
private:
const ValueType rep_;
......
......@@ -279,12 +279,14 @@ constexpr bool is_defaultable(ValueKind kind) {
return kind != kRef && !is_rtt(kind);
}
// A ValueType is encoded by three components: A ValueKind, a heap
// representation (for reference types), and an inheritance depth (for rtts
// only). Those are encoded into 32 bits using base::BitField. The underlying
// ValueKind enumeration includes four elements which do not strictly correspond
// to value types: the two packed types i8 and i16, the void type (for control
// structures), and a bottom value (for internal use).
// A ValueType is encoded by two components: a ValueKind and a heap
// representation (for reference types/rtts). Those are encoded into 32 bits
// using base::BitField. The underlying ValueKind enumeration includes four
// elements which do not strictly correspond to value types: the two packed
// types i8 and i16, the void type (for control structures), and a bottom value
// (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 {
public:
/******************************* Constructors *******************************/
......@@ -309,6 +311,11 @@ class ValueType {
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.
static constexpr ValueType FromRawBitField(uint32_t bit_field) {
return ValueType(bit_field);
......@@ -491,8 +498,6 @@ class ValueType {
}
}
static constexpr int kLastUsedBit = 24;
/****************************** Pretty-printing *****************************/
constexpr char short_name() const { return wasm::short_name(kind()); }
......@@ -517,23 +522,40 @@ class ValueType {
return buf.str();
}
// We only use 31 bits so ValueType fits in a Smi. This can be changed if
// needed.
/********************** Type canonicalization utilities *********************/
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 kHeapTypeBits = 20;
private:
STATIC_ASSERT(kV8MaxWasmTypes < (1u << kHeapTypeBits));
// {hash_value} directly reads {bit_field_}.
friend size_t hash_value(ValueType type);
using KindField = base::BitField<ValueKind, 0, kKindBits>;
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.
STATIC_ASSERT(kLastUsedBit ==
std::max(KindField::kLastUsedBit, HeapTypeField::kLastUsedBit));
static_assert(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) {}
......
......@@ -274,6 +274,8 @@ WasmModuleBuilder::WasmModuleBuilder(Zone* zone)
globals_(zone),
exceptions_(zone),
signature_map_(zone),
current_recursive_group_start_(-1),
recursive_groups_(zone),
start_function_index_(-1),
min_memory_size_(16),
max_memory_size_(0),
......@@ -593,10 +595,24 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer* buffer) const {
// == Emit types =============================================================
if (types_.size() > 0) {
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) {
buffer->write_u8(kWasmSubtypeCode);
buffer->write_u8(1); // The supertype count is always 1.
......
......@@ -359,6 +359,22 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
void SetMaxMemorySize(uint32_t value);
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.
void WriteTo(ZoneBuffer* buffer) const;
void WriteAsmJsOffsetTable(ZoneBuffer* buffer) const;
......@@ -455,6 +471,9 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
ZoneVector<WasmGlobal> globals_;
ZoneVector<int> exceptions_;
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_;
uint32_t min_memory_size_;
uint32_t max_memory_size_;
......
......@@ -364,6 +364,25 @@ struct TypeDefinition {
const StructType* struct_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;
Kind kind;
};
......@@ -424,15 +443,14 @@ struct V8_EXPORT_PRIVATE WasmModule {
? signature_map.FindOrInsert(*type.function_sig)
: 0;
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(); }
void add_signature(const FunctionSig* sig, uint32_t supertype) {
types.push_back(TypeDefinition(sig, supertype));
DCHECK_NOT_NULL(sig);
uint32_t canonical_id = signature_map.FindOrInsert(*sig);
canonicalized_type_ids.push_back(canonical_id);
add_type(TypeDefinition(sig, supertype));
}
bool has_signature(uint32_t index) const {
return index < types.size() &&
......@@ -444,9 +462,8 @@ struct V8_EXPORT_PRIVATE WasmModule {
}
void add_struct_type(const StructType* type, uint32_t supertype) {
types.push_back(TypeDefinition(type, supertype));
// No canonicalization for structs.
canonicalized_type_ids.push_back(0);
DCHECK_NOT_NULL(type);
add_type(TypeDefinition(type, supertype));
}
bool has_struct(uint32_t index) const {
return index < types.size() && types[index].kind == TypeDefinition::kStruct;
......@@ -457,9 +474,8 @@ struct V8_EXPORT_PRIVATE WasmModule {
}
void add_array_type(const ArrayType* type, uint32_t supertype) {
types.push_back(TypeDefinition(type, supertype));
// No canonicalization for arrays.
canonicalized_type_ids.push_back(0);
DCHECK_NOT_NULL(type);
add_type(TypeDefinition(type, supertype));
}
bool has_array(uint32_t index) const {
return index < types.size() && types[index].kind == TypeDefinition::kArray;
......@@ -478,11 +494,12 @@ struct V8_EXPORT_PRIVATE WasmModule {
}
std::vector<TypeDefinition> types; // by type index
// Map from each type index to the index of its corresponding canonical index.
// Canonical indices do not correspond to types.
// Note: right now, only functions are canonicalized, and arrays and structs
// map to 0.
// TODO(7748): Unify the following two arrays.
// Maps each type index to a canonical index for purposes of call_indirect.
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.
SignatureMap signature_map;
std::vector<WasmFunction> functions;
......
......@@ -5,6 +5,7 @@
#include "src/wasm/wasm-subtyping.h"
#include "src/base/platform/mutex.h"
#include "src/wasm/canonical-types.h"
#include "src/wasm/wasm-module.h"
#include "src/zone/zone-containers.h"
......@@ -18,17 +19,15 @@ V8_INLINE bool EquivalentIndices(uint32_t index1, uint32_t index2,
const WasmModule* module1,
const WasmModule* module2) {
DCHECK(index1 != index2 || module1 != module2);
// TODO(7748): Canonicalize types.
return false;
if (!FLAG_wasm_type_canonicalization) return false;
return module1->isorecursive_canonical_type_ids[index1] ==
module2->isorecursive_canonical_type_ids[index2];
}
bool ValidStructSubtypeDefinition(uint32_t subtype_index,
uint32_t supertype_index,
const WasmModule* sub_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* super_struct =
super_module->types[supertype_index].struct_type;
......@@ -56,9 +55,6 @@ bool ValidArraySubtypeDefinition(uint32_t subtype_index,
uint32_t supertype_index,
const WasmModule* sub_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* super_array =
super_module->types[supertype_index].array_type;
......@@ -78,9 +74,6 @@ bool ValidFunctionSubtypeDefinition(uint32_t subtype_index,
uint32_t supertype_index,
const WasmModule* sub_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* super_func =
super_module->types[supertype_index].function_sig;
......@@ -219,15 +212,17 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(
// equality; here we catch (ref $x) being a subtype of (ref null $x).
if (sub_module == super_module && sub_index == super_index) return true;
// TODO(7748): Figure out cross-module story.
if (sub_module != super_module) return false;
uint32_t explicit_super = sub_module->supertype(sub_index);
while (true) {
if (explicit_super == super_index) return true;
// Reached the end of the explicitly defined inheritance chain.
if (explicit_super == kNoSuperType) return false;
explicit_super = sub_module->supertype(explicit_super);
if (FLAG_wasm_type_canonicalization) {
return TypeCanonicalizer::instance()->IsCanonicalSubtype(
sub_index, super_index, sub_module, super_module);
} else {
uint32_t explicit_super = sub_module->supertype(sub_index);
while (true) {
if (explicit_super == super_index) return true;
// Reached the end of the explicitly defined inheritance chain.
if (explicit_super == kNoSuperType) return false;
explicit_super = sub_module->supertype(explicit_super);
}
}
}
......
......@@ -27,14 +27,16 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(
// - Two numeric types are equivalent iff they are equal.
// - T(ht1) ~ T(ht2) iff ht1 ~ ht2 for T in {ref, optref, rtt}.
// Equivalence of heap types ht1 ~ ht2 is defined as follows:
// - Two heap types are equivalent iff they are equal.
// - TODO(7748): Implement iso-recursive canonicalization.
V8_NOINLINE bool EquivalentTypes(ValueType type1, ValueType type2,
const WasmModule* module1,
const WasmModule* module2);
// - Two non-index heap types are equivalent iff they are equal.
// - Two indexed heap types are equivalent iff they are iso-recursive
// equivalent.
V8_NOINLINE V8_EXPORT_PRIVATE bool EquivalentTypes(ValueType type1,
ValueType type2,
const WasmModule* module1,
const WasmModule* module2);
// Checks if subtype, defined in module1, is a subtype of supertype, defined in
// module2.
// Checks if {subtype}, defined in {module1}, is a subtype of {supertype},
// defined in {module2}.
// Subtyping between value types is described by the following rules
// (structural subtyping):
// - numeric types are subtype-related iff they are equal.
......@@ -54,7 +56,7 @@ V8_NOINLINE bool EquivalentTypes(ValueType type1, ValueType type2,
// - All structs are subtypes of data.
// - All arrays are subtypes of array.
// - 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,
const WasmModule* sub_module,
const WasmModule* super_module) {
......@@ -62,7 +64,7 @@ V8_INLINE bool IsSubtypeOf(ValueType subtype, ValueType supertype,
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,
const WasmModule* module) {
// If the types are trivially identical, exit early.
......
......@@ -1304,11 +1304,14 @@ WASM_COMPILED_EXEC_TEST(WasmArrayCopy) {
tester.CheckResult(kZeroLength, 0); // Does not throw.
}
/* TODO(7748): This test requires for recursive groups.
WASM_COMPILED_EXEC_TEST(NewDefault) {
WasmGCTester tester(execution_tier);
tester.builder()->StartRecursiveTypeGroup();
const byte struct_type = tester.DefineStruct(
{F(wasm::kWasmI32, true), F(wasm::kWasmF64, true), F(optref(0), true)});
tester.builder()->EndRecursiveTypeGroup();
const byte array_type = tester.DefineArray(wasm::kWasmI32, true);
// Returns: struct[0] + f64_to_i32(struct[1]) + (struct[2].is_null ^ 1) == 0.
const byte allocate_struct = tester.DefineFunction(
......@@ -1338,7 +1341,6 @@ WASM_COMPILED_EXEC_TEST(NewDefault) {
tester.CheckResult(allocate_struct, 0);
tester.CheckResult(allocate_array, 0);
}
*/
WASM_COMPILED_EXEC_TEST(BasicRtt) {
WasmGCTester tester(execution_tier);
......
......@@ -96,11 +96,10 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
print("--imported function from another module--");
assertEquals(57, instance.exports.test_wasm_import());
/* TODO(7748): Implement cross-module type canonicalization.
print("--not imported function defined in another module--");
assertEquals(19, instance.exports.main(
exporting_instance.exports.addition, 12, 7));
*/
print("--imported WebAssembly.Function--")
assertEquals(21, instance.exports.test_js_api_import());
print("--not imported WebAssembly.Function--")
......
......@@ -35,9 +35,8 @@ var importing_module = function(imported_function) {
return builder.instantiate({other: {func: imported_function}});
};
// TODO(7748): Implement cross-module subtyping.
// 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.
assertThrows(
() => importing_module(exporting_module.exports.func1),
......
......@@ -6,7 +6,6 @@
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
/* TODO(7748): Implement cross-module subtyping.
(function TestReferenceGlobals() {
print(arguments.callee.name);
......@@ -106,7 +105,6 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// The correct function reference has been passed.
assertEquals(66, instance.exports.test_import(42, 24));
})();
*/
(function TestStructInitExpr() {
print(arguments.callee.name);
......
......@@ -5,7 +5,6 @@
// Flags: --experimental-wasm-gc
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
/* TODO(7748): Implement cross-module subtyping.
(function TestTables() {
print(arguments.callee.name);
var exporting_instance = (function() {
......@@ -100,9 +99,8 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
assertThrows(
() => instance.exports.table.set(0, exporting_instance.exports.addition),
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() {
print(arguments.callee.name);
......
......@@ -155,7 +155,6 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
assertEquals(8, instance.exports.main(10, 0));
})();
/* TODO(7748): Implement cross-module subtyping.
(function CallRefImportedFunction() {
print(arguments.callee.name);
......@@ -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.
assertEquals(1, instance2.exports.main(0, instance1.exports.f1));
})();
*/
// Check that we handle WasmJSFunctions properly and do not inline them, both
// in the monomorphic and polymorphic case.
......
......@@ -8,6 +8,7 @@
#include "src/objects/objects-inl.h"
#include "src/objects/objects.h"
#include "src/utils/ostreams.h"
#include "src/wasm/canonical-types.h"
#include "src/wasm/function-body-decoder-impl.h"
#include "src/wasm/leb-helper.h"
#include "src/wasm/local-decl-encoder.h"
......@@ -89,6 +90,7 @@ class TestModuleBuilder {
byte AddSignature(const FunctionSig* sig, uint32_t supertype = kNoSuperType) {
mod.add_signature(sig, supertype);
CHECK_LE(mod.types.size(), kMaxByteSizedLeb128);
TypeCanonicalizer::instance()->AddRecursiveGroup(module(), 1);
return static_cast<byte>(mod.types.size() - 1);
}
byte AddFunction(const FunctionSig* sig, bool declared = true) {
......@@ -131,12 +133,14 @@ class TestModuleBuilder {
type_builder.AddField(field.first, field.second);
}
mod.add_struct_type(type_builder.Build(), supertype);
TypeCanonicalizer::instance()->AddRecursiveGroup(module(), 1);
return static_cast<byte>(mod.types.size() - 1);
}
byte AddArray(ValueType type, bool mutability) {
ArrayType* array = mod.signature_zone->New<ArrayType>(type, mutability);
mod.add_array_type(array, kNoSuperType);
TypeCanonicalizer::instance()->AddRecursiveGroup(module(), 1);
return static_cast<byte>(mod.types.size() - 1);
}
......@@ -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) {
WASM_FEATURE_SCOPE(typed_funcref);
AddLocals(kWasmAnyRef, 1);
......
......@@ -166,10 +166,12 @@ namespace module_decoder_unittest {
} \
} while (false)
#define EXPECT_NOT_OK(result, msg) \
do { \
EXPECT_FALSE(result.ok()); \
EXPECT_THAT(result.error().message(), HasSubstr(msg)); \
#define EXPECT_NOT_OK(result, msg) \
do { \
EXPECT_FALSE(result.ok()); \
if (!result.ok()) { \
EXPECT_THAT(result.error().message(), HasSubstr(msg)); \
} \
} while (false)
static size_t SizeOfVarInt(size_t value) {
......@@ -803,7 +805,7 @@ TEST_F(WasmModuleVerifyTest, RttCanonGlobalTypeError) {
static const byte data[] = {
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(kI64Code, true))),
SECTION(Global, ENTRY_COUNT(1), WASM_RTT(0), 1, WASM_RTT_CANON(1),
kExprEnd)};
ModuleResult result = DecodeModule(data, data + sizeof(data));
......@@ -1189,6 +1191,46 @@ TEST_F(WasmModuleVerifyTest, InvalidArrayTypeDef) {
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) {
static const byte data[] = {SECTION(Tag, ENTRY_COUNT(0))};
FAIL_IF_NO_EXPERIMENTAL_EH(data);
......@@ -3389,13 +3431,13 @@ TEST_F(WasmModuleVerifyTest, DataCountSegmentCount_omitted) {
EXPECT_NOT_OK(result, "data segments count 0 mismatch (1 expected)");
}
/* TODO(7748): Add support for rec. groups.
TEST_F(WasmModuleVerifyTest, GcStructIdsPass) {
WASM_FEATURE_SCOPE(gc);
WASM_FEATURE_SCOPE(typed_funcref);
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),
STRUCT_FIELD(WASM_OPT_REF(0), true),
STRUCT_FIELD(WASM_OPT_REF(1), true)),
......@@ -3404,7 +3446,7 @@ TEST_F(WasmModuleVerifyTest, GcStructIdsPass) {
WASM_ARRAY_DEF(WASM_OPT_REF(0), true))};
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_OK(result);
}*/
}
TEST_F(WasmModuleVerifyTest, OutOfBoundsTypeInGlobal) {
WASM_FEATURE_SCOPE(typed_funcref);
......@@ -3424,7 +3466,6 @@ TEST_F(WasmModuleVerifyTest, OutOfBoundsTypeInType) {
EXPECT_NOT_OK(result, "Type index 1 is out of bounds");
}
// TODO(7748): Add support for rec. groups.
TEST_F(WasmModuleVerifyTest, ForwardSupertype) {
WASM_FEATURE_SCOPE(typed_funcref);
WASM_FEATURE_SCOPE(gc);
......
......@@ -2,7 +2,9 @@
// 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-subtyping.h"
#include "test/common/flag-utils.h"
#include "test/common/wasm/flag-utils.h"
#include "test/unittests/test-utils.h"
......@@ -25,29 +27,41 @@ FieldInit mut(ValueType type) { return FieldInit(type, true); }
FieldInit immut(ValueType type) { return FieldInit(type, false); }
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(),
static_cast<uint32_t>(fields.size()));
for (FieldInit field : fields) {
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) {
TypeCanonicalizer::instance()->AddRecursiveGroup(module, 1);
}
}
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>(
element_type.first, element_type.second),
supertype);
if (in_singleton_rec_group) {
TypeCanonicalizer::instance()->AddRecursiveGroup(module, 1);
}
}
void DefineSignature(WasmModule* module,
std::initializer_list<ValueType> params,
std::initializer_list<ValueType> returns,
uint32_t supertype = kNoSuperType) {
uint32_t supertype = kNoSuperType,
bool in_singleton_rec_group = true) {
module->add_signature(
FunctionSig::Build(module->signature_zone.get(), returns, params),
supertype);
if (in_singleton_rec_group) {
TypeCanonicalizer::instance()->AddRecursiveGroup(module, 1);
}
}
TEST_F(WasmSubtypingTest, Subtyping) {
......@@ -79,6 +93,37 @@ TEST_F(WasmSubtypingTest, Subtyping) {
/* 14 */ DefineSignature(module, {ref(0)}, {kWasmI32}, 13);
/* 15 */ DefineSignature(module, {ref(0)}, {ref(4)}, 16);
/* 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);
TypeCanonicalizer::instance()->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);
TypeCanonicalizer::instance()->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);
TypeCanonicalizer::instance()->AddRecursiveGroup(module, 4);
/* 30 */ DefineStruct(module, {mut(kWasmI32), immut(optRef(18))}, 18);
}
constexpr ValueType numeric_types[] = {kWasmI32, kWasmI64, kWasmF32, kWasmF64,
......@@ -88,6 +133,7 @@ TEST_F(WasmSubtypingTest, Subtyping) {
optRef(0), ref(0), optRef(2),
ref(2), optRef(11), ref(11)};
// Some macros to help managing types and modules.
#define SUBTYPE(type1, type2) \
EXPECT_TRUE(IsSubtypeOf(type1, type2, module1, module))
#define SUBTYPE_IFF(type1, type2, condition) \
......@@ -102,10 +148,20 @@ TEST_F(WasmSubtypingTest, Subtyping) {
#define NOT_VALID_SUBTYPE(type1, type2) \
EXPECT_FALSE(ValidSubtypeDefinition(type1.ref_index(), type2.ref_index(), \
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.
for (ValueType subtype : numeric_types) {
for (ValueType supertype : numeric_types) {
......@@ -183,9 +239,6 @@ TEST_F(WasmSubtypingTest, Subtyping) {
SUBTYPE(ValueType::Rtt(5), ValueType::Rtt(5));
// Rtts of unrelated types are unrelated.
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.
NOT_SUBTYPE(ValueType::Rtt(1), ValueType::Rtt(0));
......@@ -201,10 +254,42 @@ TEST_F(WasmSubtypingTest, Subtyping) {
// Identical types are subtype-related.
VALID_SUBTYPE(ref(10), ref(10));
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 NOT_SUBTYPE
#undef SUBTYPE_IFF
#undef VALID_SUBTYPE
#undef NOT_VALID_SUBTYPE
#undef IDENTICAL
#undef DISTINCT
}
} // 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