blob: 091fff758525696207b1440f92539cec39b7c0ab [file] [log] [blame] [edit]
// Copyright 2025 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.
#ifndef V8_WASM_WASM_EXPORT_WRAPPER_CACHE_H_
#define V8_WASM_WASM_EXPORT_WRAPPER_CACHE_H_
#if !V8_ENABLE_WEBASSEMBLY
#error This header should only be included if WebAssembly is enabled.
#endif // !V8_ENABLE_WEBASSEMBLY
#include "src/objects/code.h"
#include "src/objects/fixed-array.h"
#include "src/objects/internal-index.h"
#include "src/wasm/canonical-types.h"
#include "src/wasm/value-type.h"
namespace v8::internal::wasm {
// Provides functionality for caching JS-to-Wasm wrappers in an {Isolate}.
// Since these wrappers are JS-compatible {Code} objects living in managed
// code space, the cache is also Isolate-specific. It uses a WeakFixedArray
// as backing store in order to not leak unused wrappers, and a HashTable-
// inspired indexing scheme rather than a dense array layout because we expect
// only a minority of canonical type indices to have a corresponding wrapper.
// For sandbox purposes, the cache itself is untrusted, but calls through the
// wrappers are protected by our regular signature verification scheme.
class WasmExportWrapperCache : public AllStatic {
public:
// DCHECK-fails if a caller attempts to overwrite an existing wrapper.
static void Put(Isolate* isolate, CanonicalTypeIndex sig_index,
bool receiver_is_first_param, DirectHandle<Code> code);
// Returns an empty Tagged if no cache entry was found.
static Tagged<CodeWrapper> Get(Isolate* isolate, CanonicalTypeIndex sig_index,
bool receiver_is_first_param);
#ifdef VERIFY_HEAP
static void Verify(Heap* heap);
#endif // VERIFY_HEAP
// Defined in cctest/test-run-wasm-wrappers.cc to not bloat shipping binaries.
static int CountWrappersForTesting(Isolate* isolate);
private:
static constexpr int kUnused = -1;
static constexpr int kNumUsedElementsIndex = 0;
static constexpr int kReservedSlots = 1;
// Key (Smi), value (Weak<CodeWrapper>).
static constexpr int kSlotsPerEntry = 2;
static DirectHandle<WeakFixedArray> New(Isolate* isolate, int capacity);
static uint32_t Capacity(Tagged<WeakFixedArray> cache);
static uint32_t Hash(CanonicalTypeIndex sig_index,
bool receiver_is_first_param) {
// We expect most signatures to get only one wrapper, so putting the
// {receiver_is_first_param} bit at the top should give good distribution
// of hash values.
// Note: "hash" is a bit of a misnomer here; we require this value to
// fully represent the identity of the cached value. See {FirstProbe} for
// the step where collisions can and do happen.
static_assert(kMaxCanonicalTypes < (1u << 20));
return ((receiver_is_first_param ? 1 : 0) << 20) | sig_index.index;
}
// Maps the n-th entry to an index in the underlying WeakFixedArray.
static int ToArraySlot(InternalIndex entry) {
return kReservedSlots + entry.as_int() * kSlotsPerEntry;
}
static InternalIndex FirstProbe(uint32_t hash, uint32_t capacity) {
#if V8_HASHES_COLLIDE
if (v8_flags.hashes_collide) hash = base::kCollidingHash;
#endif // V8_HASHES_COLLIDE
// {New()} ensures that {capacity} is always a power of two.
return InternalIndex(hash & (capacity - 1));
}
static InternalIndex NextProbe(InternalIndex last, uint32_t number,
uint32_t capacity) {
// {New()} ensures that {capacity} is always a power of two.
return InternalIndex((last.as_uint32() + number) & (capacity - 1));
}
V8_WARN_UNUSED_RESULT static Tagged<WeakFixedArray> EnsureCapacity(
Isolate* isolate);
template <bool entry_may_exist>
static void PutInternal(Tagged<WeakFixedArray> cache, uint32_t hash,
Tagged<MaybeWeak<Object>> value);
};
} // namespace v8::internal::wasm
#endif // V8_WASM_WASM_EXPORT_WRAPPER_CACHE_H_