v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
canonical-types.h
Go to the documentation of this file.
1// Copyright 2022 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef V8_WASM_CANONICAL_TYPES_H_
6#define V8_WASM_CANONICAL_TYPES_H_
7
8#if !V8_ENABLE_WEBASSEMBLY
9#error This header should only be included if WebAssembly is enabled.
10#endif // !V8_ENABLE_WEBASSEMBLY
11
12#include <unordered_map>
13
14#include "src/base/bounds.h"
15#include "src/base/hashing.h"
16#include "src/wasm/value-type.h"
18
19namespace v8::internal::wasm {
20
21class CanonicalTypeNamesProvider;
22
23// We use ValueType instances constructed from canonical type indices, so we
24// can't let them get bigger than what we have storage space for.
25// We could raise this limit using unused bits in the ValueType, but that
26// doesn't seem urgent, as we have no evidence of the current limit being
27// an actual limitation in practice.
28static constexpr size_t kMaxCanonicalTypes = kV8MaxWasmTypes;
29// We don't want any valid modules to fail canonicalization.
30static_assert(kMaxCanonicalTypes >= kV8MaxWasmTypes);
31// We want the invalid index to fail any range checks.
33// Ensure that ValueType can hold all canonical type indexes.
35
36// A singleton class, responsible for isorecursive canonicalization of wasm
37// types.
38// A recursive group is a subsequence of types explicitly marked in the type
39// section of a wasm module. Identical recursive groups have to be canonicalized
40// to a single canonical group. Respective types in two identical groups are
41// considered identical for all purposes.
42// Two groups are considered identical if they have the same shape, and all
43// type indices referenced in the same position in both groups reference:
44// - identical types, if those do not belong to the rec. group,
45// - types in the same relative position in the group, if those belong to the
46// rec. group.
48 public:
51 static constexpr uint32_t kNumberOfPredefinedTypes = 2;
52
54
55 // Singleton class; no copying or moving allowed.
56 TypeCanonicalizer(const TypeCanonicalizer& other) = delete;
60
61 // Register the last {size} types of {module} as a recursive group and
62 // possibly canonicalize it if an identical one has been found.
63 // Modifies {module->isorecursive_canonical_type_ids}.
64 V8_EXPORT_PRIVATE void AddRecursiveGroup(WasmModule* module, uint32_t size);
65
66 // Same as above, but for a group of size 1 (using the last type in the
67 // module).
69
70 // Adds a module-independent signature as a recursive group, and canonicalizes
71 // it if an identical is found. Returns the canonical index of the added
72 // signature.
75
76 // Retrieve back a type from a canonical index later.
78 CanonicalTypeIndex index) const;
80 CanonicalTypeIndex index) const;
82 CanonicalTypeIndex index) const;
83
84 // Returns if {canonical_sub_index} is a canonical subtype of
85 // {canonical_super_index}.
87 CanonicalTypeIndex super_index);
88
89 // Returns if the type at {sub_index} in {sub_module} is a subtype of the
90 // type at {super_index} in {super_module} after canonicalization.
92 ModuleTypeIndex super_index,
93 const WasmModule* sub_module,
94 const WasmModule* super_module);
95
96 // Deletes recursive groups. Used by fuzzers to avoid accumulating memory, and
97 // used by specific tests e.g. for serialization / deserialization.
99
101
103
104 // Prepares wasm for the provided canonical type index. This reserves enough
105 // space in the canonical rtts and the JSToWasm wrappers on the isolate roots.
107 Isolate* isolate, CanonicalTypeIndex id);
108 // Reset the canonical rtts and JSToWasm wrappers on the isolate roots for
109 // testing purposes (in production cases canonical type ids are never freed).
111 Isolate* isolate);
112
116
119 CanonicalTypeIndex super_index) const;
120
122
123#if DEBUG
124 // Check whether a supposedly-canonicalized function signature does indeed
125 // live in this class's storage. Useful for guarding casts of signatures
126 // that are entering the typed world.
127 V8_EXPORT_PRIVATE bool Contains(const CanonicalSig* sig) const;
128#endif
129
130 private:
132 enum Kind : int8_t { kFunction, kStruct, kArray, kCont };
133
134 union {
135 const CanonicalSig* function_sig = nullptr;
139 };
144 bool is_final = false;
145 bool is_shared = false;
146 uint8_t subtyping_depth = 0;
147
156
169
178
179 constexpr CanonicalType(const CanonicalContType* type,
181 bool is_shared)
182 : cont_type(type),
184 kind(kCont),
187
188 constexpr CanonicalType() = default;
189 };
190
191 // Define the range of a recursion group; for use in {CanonicalHashing} and
192 // {CanonicalEquality}.
196
197 bool Contains(CanonicalTypeIndex index) const {
198 return base::IsInRange(index.index, first.index, last.index);
199 }
200 };
201
202 // Support for hashing of recursion groups, where type indexes have to be
203 // hashed relative to the recursion group.
207
210
212 uint32_t is_relative = recgroup.Contains(index) ? 1 : 0;
213 uint32_t relative = index.index - is_relative * recgroup.first.index;
214 return (relative << 1) | is_relative;
215 }
216
217 void Add(CanonicalType type) {
218 // Add several pieces of information in a single number, for faster
219 // hashing.
220 uint32_t supertype_index = MakeGroupRelative(type.supertype);
221 static_assert(kMaxCanonicalTypes <= kMaxUInt32 >> 3);
222 uint32_t metadata =
223 (supertype_index << 2) | (type.is_shared << 1) | (type.is_final << 0);
224 hasher.Add(metadata);
225 switch (type.kind) {
227 Add(*type.function_sig);
228 break;
230 uint32_t descriptor_index = MakeGroupRelative(type.descriptor);
231 uint32_t describes_index = MakeGroupRelative(type.describes);
232 // {MakeGroupRelative} returns a value at most 1 bit bigger than
233 // the max canonical index, which currently fits into 20 bits. So
234 // by shifting left by 11, we're not truncating anything. (And even
235 // if we did, it's just a hash, collisions are OK.)
236 uint32_t desc = (descriptor_index << 11) ^ (describes_index);
237 hasher.Add(desc);
238 Add(*type.struct_type);
239 break;
240 }
242 Add(*type.array_type);
243 break;
245 Add(*type.cont_type);
246 break;
247 }
248 }
249
251 if (value_type.has_index() && recgroup.Contains(value_type.ref_index())) {
252 // For relative indexed types, add their nullability, exactness, and
253 // the relative index to the hash.
254 // Shift the relative index by {kMaxCanonicalTypes} to map it to a
255 // different index space (note that collisions in hashing are OK
256 // though).
257 static_assert(kMaxCanonicalTypes <= kMaxUInt32 / 2);
258 // TODO(403372470): Add the 'exact' bit.
260 hasher.Add((value_type.ref_index().index - recgroup.first.index) +
262 } else {
264 }
265 }
266
267 void Add(const CanonicalSig& sig) {
268 hasher.Add(sig.parameter_count());
269 for (CanonicalValueType type : sig.all()) Add(type);
270 }
271
272 void Add(const CanonicalStructType& struct_type) {
273 hasher.AddRange(struct_type.mutabilities());
274 for (const ValueTypeBase& field : struct_type.fields()) {
275 Add(CanonicalValueType{field});
276 }
277 }
278
279 void Add(const CanonicalArrayType& array_type) {
280 hasher.Add(array_type.mutability());
281 Add(array_type.element_type());
282 }
283
284 void Add(const CanonicalContType& cont_type) {
285 CanonicalTypeIndex cont_index = cont_type.contfun_typeindex();
286
287 if (recgroup.Contains(cont_index)) {
288 hasher.Add(cont_index.index - recgroup.first.index +
290 } else {
291 hasher.Add(cont_index.index); // Not relative to this rec group
292 }
293 }
294
295 size_t hash() const { return hasher.hash(); }
296 };
297
298 // Support for equality checking of recursion groups, where type indexes have
299 // to be compared relative to their respective recursion group.
301 // Recursion group bounds for LHS and RHS.
304
308
310 CanonicalTypeIndex index2) const {
311 const bool relative_index = recgroup1.Contains(index1);
312 if (relative_index != recgroup2.Contains(index2)) return false;
313 if (relative_index) {
314 // Compare relative type indexes within the respective recgroups.
315 uint32_t rel_type1 = index1.index - recgroup1.first.index;
316 uint32_t rel_type2 = index2.index - recgroup2.first.index;
317 if (rel_type1 != rel_type2) return false;
318 } else if (index1 != index2) {
319 return false;
320 }
321 return true;
322 }
323
324 bool EqualType(const CanonicalType& type1,
325 const CanonicalType& type2) const {
326 if (!EqualTypeIndex(type1.supertype, type2.supertype)) return false;
327 if (type1.is_final != type2.is_final) return false;
328 if (type1.is_shared != type2.is_shared) return false;
329 switch (type1.kind) {
331 return type2.kind == CanonicalType::kFunction &&
332 EqualSig(*type1.function_sig, *type2.function_sig);
334 return type2.kind == CanonicalType::kStruct &&
335 EqualTypeIndex(type1.descriptor, type2.descriptor) &&
336 EqualTypeIndex(type1.describes, type2.describes) &&
339 return type2.kind == CanonicalType::kArray &&
340 EqualArrayType(*type1.array_type, *type2.array_type);
342 return type2.kind == CanonicalType::kCont &&
343 EqualContType(*type1.cont_type, *type2.cont_type);
344 }
345 }
346
349 return std::equal(types1.begin(), types1.end(), types2.begin(),
350 types2.end(),
351 std::bind_front(&CanonicalEquality::EqualType, this));
352 }
353
355 CanonicalValueType type2) const {
356 const bool indexed = type1.has_index();
357 if (indexed != type2.has_index()) return false;
358 if (indexed) {
359 return EqualTypeIndex(type1.ref_index(), type2.ref_index());
360 }
361 return type1 == type2;
362 }
363
364 bool EqualSig(const CanonicalSig& sig1, const CanonicalSig& sig2) const {
365 if (sig1.parameter_count() != sig2.parameter_count()) return false;
366 return std::equal(
367 sig1.all().begin(), sig1.all().end(), sig2.all().begin(),
368 sig2.all().end(),
369 std::bind_front(&CanonicalEquality::EqualValueType, this));
370 }
371
373 const CanonicalStructType& type2) const {
374 return
375 // Compare fields, including a check that the size is the same.
376 std::equal(
377 type1.fields().begin(), type1.fields().end(),
378 type2.fields().begin(), type2.fields().end(),
379 std::bind_front(&CanonicalEquality::EqualValueType, this)) &&
380 // Compare mutabilities, skipping the check for the size.
381 std::equal(type1.mutabilities().begin(), type1.mutabilities().end(),
382 type2.mutabilities().begin());
383 }
384
386 const CanonicalArrayType& type2) const {
387 return type1.mutability() == type2.mutability() &&
388 EqualValueType(type1.element_type(), type2.element_type());
389 }
390
392 const CanonicalContType& type2) const {
393 return EqualTypeIndex(type1.contfun_typeindex(),
394 type2.contfun_typeindex());
395 }
396 };
397
399 CanonicalGroup(Zone* zone, size_t size, CanonicalTypeIndex first)
400 : types(zone->AllocateVector<CanonicalType>(size)), first(first) {
401 // size >= 2; otherwise a `CanonicalSingletonGroup` should have been used.
402 DCHECK_LE(2, size);
403 }
404
405 bool operator==(const CanonicalGroup& other) const {
406 CanonicalTypeIndex last{first.index +
407 static_cast<uint32_t>(types.size() - 1)};
408 CanonicalTypeIndex other_last{
409 other.first.index + static_cast<uint32_t>(other.types.size() - 1)};
410 CanonicalEquality equality{{first, last}, {other.first, other_last}};
411 return equality.EqualTypes(types, other.types);
412 }
413
414 size_t hash_value() const {
415 CanonicalTypeIndex last{first.index +
416 static_cast<uint32_t>(types.size()) - 1};
417 CanonicalHashing hasher{{first, last}};
418 for (CanonicalType t : types) {
419 hasher.Add(t);
420 }
421 return hasher.hash();
422 }
423
424 // The storage of this vector is the TypeCanonicalizer's zone_.
427 };
428
430 bool operator==(const CanonicalSingletonGroup& other) const {
431 CanonicalEquality equality{{index, index}, {other.index, other.index}};
432 return equality.EqualType(type, other.type);
433 }
434
435 size_t hash_value() const {
436 CanonicalHashing hasher{{index, index}};
437 hasher.Add(type);
438 return hasher.hash();
439 }
440
443 };
444
445 // Conceptually a vector of CanonicalType. Modification generally requires
446 // synchronization, read-only access can be done without locking.
448 static constexpr uint32_t kSegmentSize = 1024;
449 static constexpr uint32_t kNumSegments =
451 static_assert(kSegmentSize * kNumSegments >= kMaxCanonicalTypes);
452 static_assert(
453 kNumSegments <= 1024,
454 "Reconsider this data structures when increasing kMaxCanonicalTypes");
455
456 public:
458 uint32_t segment_idx = index.index / kSegmentSize;
459 // Only check against the static constant here; uninitialized segments are
460 // {nullptr}, so accessing them crashes.
461 SBXCHECK_GT(kNumSegments, segment_idx);
462 Segment* segment = segments_[segment_idx].load(std::memory_order_relaxed);
463 const CanonicalType* type = (*segment)[index.index % kSegmentSize];
464 // DCHECK is sufficient as returning {nullptr} is safe.
465 DCHECK_NOT_NULL(type);
466 return type;
467 }
468
470 DCHECK(type.has_index());
471 return (*this)[type.ref_index()];
472 }
473
474 void reserve(uint32_t size, Zone* zone) {
476 uint32_t segment_idx = size / kSegmentSize;
477 // Stop on the first segment (iterating backwards) which already exists.
478 while (!segments_[segment_idx].load(std::memory_order_relaxed)) {
479 segments_[segment_idx].store(zone->New<Segment>(),
480 std::memory_order_relaxed);
481 if (segment_idx-- == 0) break;
482 }
483 }
484
485 void set(CanonicalTypeIndex index, const CanonicalType* type) {
486 // No checking needed, this is only executed after CheckMaxCanonicalIndex.
487 uint32_t segment_idx = index.index / kSegmentSize;
488 Segment* segment = segments_[segment_idx].load(std::memory_order_relaxed);
489 segment->set(index.index % kSegmentSize, type);
490 }
491
493 for (uint32_t i = 0; i < kNumSegments; ++i) {
494 if (segments_[i].load(std::memory_order_relaxed) == nullptr) break;
495 segments_[i].store(nullptr, std::memory_order_relaxed);
496 }
497 }
498
500 for (uint32_t i = 0; i < kNumSegments; ++i) {
501 Segment* segment = segments_[i].load(std::memory_order_relaxed);
502 // If callers have a CanonicalSig* to pass into this function, the
503 // type canonicalizer must know about this sig, hence we must find it
504 // before hitting a `nullptr` segment.
505 DCHECK_NOT_NULL(segment);
506 for (uint32_t k = 0; k < kSegmentSize; ++k) {
507 const CanonicalType* type = (*segment)[k];
508 // Again: We expect to find the signature before hitting uninitialized
509 // slots.
510 DCHECK_NOT_NULL(type);
511 if (type->kind == CanonicalType::kFunction &&
512 type->function_sig == sig) {
513 return CanonicalTypeIndex{i * kSegmentSize + k};
514 }
515 }
516 }
517 UNREACHABLE();
518 }
519
520 private:
521 class Segment {
522 public:
523 const CanonicalType* operator[](uint32_t index) const {
524 DCHECK_GT(kSegmentSize, index);
525 return content_[index].load(std::memory_order_relaxed);
526 }
527
528 void set(uint32_t index, const CanonicalType* type) {
529 DCHECK_GT(kSegmentSize, index);
530 DCHECK_NULL(content_[index].load(std::memory_order_relaxed));
531 content_[index].store(type, std::memory_order_relaxed);
532 }
533
534 private:
535 std::atomic<const CanonicalType*> content_[kSegmentSize]{};
536 };
537
538 std::atomic<Segment*> segments_[kNumSegments]{};
539 };
540
542
543 CanonicalTypeIndex FindCanonicalGroup(const CanonicalGroup&) const;
544 CanonicalTypeIndex FindCanonicalGroup(const CanonicalSingletonGroup&) const;
545
546 // Canonicalize the module-specific type at `module_type_idx` within the
547 // recursion group starting at `recursion_group_start`, using
548 // `canonical_recgroup_start` as the start offset of types within the
549 // recursion group.
550 CanonicalType CanonicalizeTypeDef(
551 const WasmModule* module, ModuleTypeIndex module_type_idx,
552 ModuleTypeIndex recgroup_start,
553 CanonicalTypeIndex canonical_recgroup_start);
554
555 void CheckMaxCanonicalIndex() const;
556
557 std::vector<CanonicalTypeIndex> canonical_supertypes_;
558 // Set of all known canonical recgroups of size >=2.
559 std::unordered_set<CanonicalGroup> canonical_groups_;
560 // Set of all known canonical recgroups of size 1.
561 std::unordered_set<CanonicalSingletonGroup> canonical_singleton_groups_;
562 // Maps canonical indices back to the types.
565 Zone zone_{&allocator_, "canonical type zone"};
567};
568
569// Returns a reference to the TypeCanonicalizer shared by the entire process.
571
572} // namespace v8::internal::wasm
573
574#endif // V8_WASM_CANONICAL_TYPES_H_
#define SBXCHECK_GT(lhs, rhs)
Definition check.h:64
Hasher & AddRange(Iterator first, Iterator last)
Definition hashing.h:127
Hasher & Add(const T &t)
Definition hashing.h:121
constexpr size_t hash() const
Definition hashing.h:111
constexpr T * begin() const
Definition vector.h:96
constexpr T * end() const
Definition vector.h:103
size_t parameter_count() const
Definition signature.h:94
base::Vector< const T > all() const
Definition signature.h:117
T * New(Args &&... args)
Definition zone.h:114
CanonicalValueType element_type() const
CanonicalTypeIndex contfun_typeindex() const
base::iterator_range< const CanonicalValueType * > fields() const
constexpr CanonicalTypeIndex ref_index() const
base::iterator_range< const bool * > mutabilities() const
std::atomic< const CanonicalType * > content_[kSegmentSize]
void set(uint32_t index, const CanonicalType *type)
const CanonicalTypeIndex FindIndex_Slow(const CanonicalSig *sig) const
void set(CanonicalTypeIndex index, const CanonicalType *type)
const CanonicalType * operator[](CanonicalValueType type) const
const CanonicalType * operator[](CanonicalTypeIndex index) const
V8_EXPORT_PRIVATE bool IsCanonicalSubtype(CanonicalTypeIndex sub_index, CanonicalTypeIndex super_index)
V8_EXPORT_PRIVATE void AddRecursiveSingletonGroup(WasmModule *module)
V8_EXPORT_PRIVATE const CanonicalStructType * LookupStruct(CanonicalTypeIndex index) const
V8_EXPORT_PRIVATE bool IsFunctionSignature(CanonicalTypeIndex index) const
bool IsCanonicalSubtype_Locked(CanonicalTypeIndex sub_index, CanonicalTypeIndex super_index) const
static constexpr uint32_t kNumberOfPredefinedTypes
CanonicalType CanonicalizeTypeDef(const WasmModule *module, ModuleTypeIndex module_type_idx, ModuleTypeIndex recgroup_start, CanonicalTypeIndex canonical_recgroup_start)
CanonicalTypeIndex FindIndex_Slow(const CanonicalSig *sig) const
TypeCanonicalizer(const TypeCanonicalizer &other)=delete
V8_EXPORT_PRIVATE const CanonicalArrayType * LookupArray(CanonicalTypeIndex index) const
static V8_EXPORT_PRIVATE void ClearWasmCanonicalTypesForTesting(Isolate *isolate)
V8_EXPORT_PRIVATE size_t GetCurrentNumberOfTypes() const
V8_EXPORT_PRIVATE bool IsStruct(CanonicalTypeIndex index) const
static constexpr CanonicalTypeIndex kPredefinedArrayI16Index
CanonicalTypeIndex FindCanonicalGroup(const CanonicalGroup &) const
bool IsHeapSubtype(CanonicalTypeIndex sub, CanonicalTypeIndex super) const
V8_EXPORT_PRIVATE bool IsArray(CanonicalTypeIndex index) const
TypeCanonicalizer & operator=(const TypeCanonicalizer &other)=delete
V8_EXPORT_PRIVATE const CanonicalSig * LookupFunctionSignature(CanonicalTypeIndex index) const
V8_EXPORT_PRIVATE void AddRecursiveGroup(WasmModule *module, uint32_t size)
static V8_EXPORT_PRIVATE void PrepareForCanonicalTypeId(Isolate *isolate, CanonicalTypeIndex id)
std::unordered_set< CanonicalSingletonGroup > canonical_singleton_groups_
TypeCanonicalizer & operator=(TypeCanonicalizer &&other)=delete
std::vector< CanonicalTypeIndex > canonical_supertypes_
static constexpr CanonicalTypeIndex kPredefinedArrayI8Index
std::unordered_set< CanonicalGroup > canonical_groups_
V8_EXPORT_PRIVATE void EmptyStorageForTesting()
TypeCanonicalizer(TypeCanonicalizer &&other)=delete
constexpr bool has_index() const
Definition value-type.h:367
constexpr bool is_nullable() const
Definition value-type.h:393
constexpr bool is_exact() const
Definition value-type.h:402
constexpr ModuleTypeIndex ref_index() const
constexpr bool IsInRange(T value, U lower_limit, U higher_limit)
Definition bounds.h:20
static ValueType value_type()
static constexpr size_t kMaxCanonicalTypes
TypeCanonicalizer * GetTypeCanonicalizer()
constexpr uint32_t kInvalidCanonicalIndex
constexpr ModuleTypeIndex kNoType
constexpr size_t kV8MaxWasmTypes
Definition wasm-limits.h:30
constexpr ModuleTypeIndex kNoSuperType
kWasmInternalFunctionIndirectPointerTag kProtectedInstanceDataOffset sig
constexpr uint32_t kMaxUInt32
Definition globals.h:387
#define DCHECK_LE(v1, v2)
Definition logging.h:490
#define DCHECK_NULL(val)
Definition logging.h:491
#define DCHECK_NOT_NULL(val)
Definition logging.h:492
#define DCHECK_GE(v1, v2)
Definition logging.h:488
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_GT(v1, v2)
Definition logging.h:487
#define V8_EXPORT_PRIVATE
Definition macros.h:460
bool EqualTypeIndex(CanonicalTypeIndex index1, CanonicalTypeIndex index2) const
bool EqualType(const CanonicalType &type1, const CanonicalType &type2) const
bool EqualSig(const CanonicalSig &sig1, const CanonicalSig &sig2) const
bool EqualArrayType(const CanonicalArrayType &type1, const CanonicalArrayType &type2) const
CanonicalEquality(RecursionGroupRange recgroup1, RecursionGroupRange recgroup2)
bool EqualTypes(base::Vector< const CanonicalType > types1, base::Vector< const CanonicalType > types2) const
bool EqualStructType(const CanonicalStructType &type1, const CanonicalStructType &type2) const
bool EqualContType(const CanonicalContType &type1, const CanonicalContType &type2) const
bool EqualValueType(CanonicalValueType type1, CanonicalValueType type2) const
CanonicalGroup(Zone *zone, size_t size, CanonicalTypeIndex first)
bool operator==(const CanonicalGroup &other) const
void Add(const CanonicalStructType &struct_type)
void Add(const CanonicalContType &cont_type)
void Add(const CanonicalArrayType &array_type)
bool operator==(const CanonicalSingletonGroup &other) const
constexpr CanonicalType(const CanonicalSig *sig, CanonicalTypeIndex supertype, bool is_final, bool is_shared)
constexpr CanonicalType(const CanonicalContType *type, CanonicalTypeIndex supertype, bool is_final, bool is_shared)
constexpr CanonicalType(const CanonicalArrayType *type, CanonicalTypeIndex supertype, bool is_final, bool is_shared)
constexpr CanonicalType(const CanonicalStructType *type, CanonicalTypeIndex supertype, CanonicalTypeIndex descriptor, CanonicalTypeIndex describes, bool is_final, bool is_shared)
wasm::ValueType type