blob: 3c422ccd5214e5a8fb4ddbf0c0a84e1a26446840 [file] [log] [blame]
// Copyright 2024 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_CODE_POINTER_TABLE_H_
#define V8_WASM_WASM_CODE_POINTER_TABLE_H_
#include "include/v8-internal.h"
#include "src/common/segmented-table.h"
#if !V8_ENABLE_WEBASSEMBLY
#error This header should only be included if WebAssembly is enabled.
#endif // !V8_ENABLE_WEBASSEMBLY
namespace v8::internal::wasm {
// Defines the entries in the WasmCodePointerTable and specifies the encoding.
// When entries are in use, they contain the address of a valid Wasm code entry,
// while free entries contain an index to the next element in the freelist.
//
// All reads and writes use relaxed memory ordering and need to be synchronized
// by the caller.
struct WasmCodePointerTableEntry {
// We write-protect the WasmCodePointerTable on platforms that support it for
// forward-edge CFI.
static constexpr bool IsWriteProtected = true;
// Set the entry to point to a given entrypoint.
inline void MakeCodePointerEntry(Address entrypoint, uint64_t signature_hash);
inline void UpdateCodePointerEntry(Address entrypoint,
uint64_t signature_hash);
// Make this entry a freelist entry, containing the index of the next entry
// on the freelist.
inline void MakeFreelistEntry(uint32_t next_entry_index);
// Load code entrypoint pointer stored in this entry.
inline Address GetEntrypoint(uint64_t signature_hash) const;
// Load code entrypoint pointer stored in this entry.
inline Address GetEntrypointWithoutSignatureCheck() const;
// Get the index of the next entry on the freelist.
inline uint32_t GetNextFreelistEntryIndex() const;
private:
friend class WasmCodePointerTable;
std::atomic<Address> entrypoint_;
#ifdef V8_ENABLE_SANDBOX
uint64_t signature_hash_;
#endif
};
// A table for storing valid Wasm code entrypoints. This table allows enforcing
// forward-edge CFI for Wasm calls:
// * The table gets write-protected (e.g. with pkeys) to prevent corruption of
// entries.
// * At write time, we will check that the value is a valid entrypoint as
// tracked in our CFI metadata.
// * Wasm calls can then be replaced with a bounds-checked table lookup + call
// to enforce that only valid entrypoints can be called.
//
// All methods are thread-safe if not specified otherwise.
class V8_EXPORT_PRIVATE WasmCodePointerTable
: public SegmentedTable<WasmCodePointerTableEntry,
kCodePointerTableReservationSize> {
using Base = SegmentedTable<WasmCodePointerTableEntry,
kCodePointerTableReservationSize>;
public:
WasmCodePointerTable() = default;
WasmCodePointerTable(const WasmCodePointerTable&) = delete;
WasmCodePointerTable& operator=(const WasmCodePointerTable&) = delete;
using Handle = uint32_t;
static constexpr Handle kInvalidHandle = -1;
#ifdef V8_ENABLE_SANDBOX
static constexpr int kOffsetOfSignatureHash =
offsetof(WasmCodePointerTableEntry, signature_hash_);
#endif
#ifdef V8_TARGET_ARCH_64_BIT
// 64-bit architectures reserve a large table upfront, hence there's a fixed
// maximum number of Wasm code pointers.
static constexpr size_t kMaxWasmCodePointers = kMaxCapacity;
#endif
using WriteScope = CFIMetadataWriteScope;
// The table should be initialized exactly once before use.
void Initialize();
// Free any resources used by the table.
void TearDown();
// Read the entrypoint at a given index.
inline Address GetEntrypoint(WasmCodePointer index,
uint64_t signature_hash) const;
inline Address GetEntrypointWithoutSignatureCheck(
WasmCodePointer index) const;
// Sets the entrypoint of the entry referenced by the given index.
// The Unlocked version can be used in loops, but you need to hold a
// `WriteScope` while calling it.
inline void UpdateEntrypoint(WasmCodePointer index, Address value,
uint64_t signature_hash);
inline void SetEntrypointAndSignature(WasmCodePointer index, Address value,
uint64_t signature_hash);
inline void SetEntrypointWithWriteScope(WasmCodePointer index, Address value,
uint64_t signature_hash,
WriteScope& write_scope);
// Allocates a new entry in the table and optionally initialize it.
inline WasmCodePointer AllocateAndInitializeEntry(Address entrypoint,
uint64_t signature_hash);
inline WasmCodePointer AllocateUninitializedEntry();
// Free an entry, which will add it to the free list.
inline void FreeEntry(WasmCodePointer index);
// Iterate through the freelist to find and unmap empty segments. Will return
// early if there's less than `threshold` many elements in the freelist.
void SweepSegments(size_t threshold = 2 * kEntriesPerSegment);
// Add an entry for a native function address, used by the C API.
WasmCodePointer GetOrCreateHandleForNativeFunction(Address addr);
// Compare the address of the entry.
bool EntrypointEqualTo(WasmCodePointer index, Address address);
private:
// Allow the ExternalReference to access the table base.
friend class ::v8::internal::ExternalReference;
// This marker is used to temporarily unlink the freelist to get exclusive
// access.
static constexpr FreelistHead kRetryMarker = FreelistHead(0xffffffff, 0);
static bool IsRetryMarker(FreelistHead freelist) {
return freelist.length() == kRetryMarker.length() &&
freelist.next() == kRetryMarker.next();
}
// Access the Freelist head, retrying if the retry marker is seen.
V8_INLINE FreelistHead ReadFreelistHead();
// Allocate an entry either from the freelist or creating a new segment.
uint32_t AllocateEntryImpl();
// Atomically link a freelist into the current freelist head.
V8_INLINE FreelistHead LinkFreelist(FreelistHead new_freelist,
uint32_t last_element);
// Helper functions for converting a freelist to a vector and back.
// Freelist access is not atomic.
std::vector<uint32_t> FreelistToVector(FreelistHead freelist);
FreelistHead VectorToFreelist(std::vector<uint32_t> entries);
// Try to allocate the first entry of the freelist.
//
// This method is mostly a wrapper around an atomic compare-and-swap which
// replaces the current freelist head with the next entry in the freelist,
// thereby allocating the entry at the start of the freelist.
V8_INLINE bool TryAllocateFromFreelist(uint32_t* index);
// Not atomic and should only be used if you have exclusive access to the
// freelist.
V8_INLINE uint32_t
AllocateEntryFromFreelistNonAtomic(FreelistHead* freelist_head);
// Free all handles in the `native_function_map_`.
void FreeNativeFunctionHandles();
std::atomic<FreelistHead> freelist_head_ = FreelistHead();
// The mutex is used to avoid two threads from concurrently allocating
// segments and using more memory than needed.
base::Mutex segment_allocation_mutex_;
base::Mutex native_function_map_mutex_;
std::map<Address, WasmCodePointer> native_function_map_;
friend class WasmCodePointerTableTest;
};
V8_EXPORT_PRIVATE WasmCodePointerTable* GetProcessWideWasmCodePointerTable();
} // namespace v8::internal::wasm
#endif // V8_WASM_WASM_CODE_POINTER_TABLE_H_