1// Copyright 2019 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#include "src/objects/backing-store.h" 6 7#include <cstring> 8 9#include "src/base/platform/wrappers.h" 10#include "src/execution/isolate.h" 11#include "src/handles/global-handles.h" 12#include "src/logging/counters.h" 13#include "src/sandbox/sandbox.h" 14 15#if V8_ENABLE_WEBASSEMBLY 16#include "src/trap-handler/trap-handler.h" 17#include "src/wasm/wasm-constants.h" 18#include "src/wasm/wasm-engine.h" 19#include "src/wasm/wasm-limits.h" 20#include "src/wasm/wasm-objects-inl.h" 21#endif // V8_ENABLE_WEBASSEMBLY 22 23#define TRACE_BS(...) \ 24 do { \ 25 if (FLAG_trace_backing_store) PrintF(__VA_ARGS__); \ 26 } while (false) 27 28namespace v8 { 29namespace internal { 30 31namespace { 32 33#if V8_ENABLE_WEBASSEMBLY 34constexpr uint64_t kNegativeGuardSize = uint64_t{2} * GB; 35 36#if V8_TARGET_ARCH_64_BIT 37constexpr uint64_t kFullGuardSize = uint64_t{10} * GB; 38#endif 39 40#endif // V8_ENABLE_WEBASSEMBLY 41 42std::atomic<uint32_t> next_backing_store_id_{1}; 43 44// Allocation results are reported to UMA 45// 46// See wasm_memory_allocation_result in counters-definitions.h 47enum class AllocationStatus { 48 kSuccess, // Succeeded on the first try 49 50 kSuccessAfterRetry, // Succeeded after garbage collection 51 52 kAddressSpaceLimitReachedFailure, // Failed because Wasm is at its address 53 // space limit 54 55 kOtherFailure // Failed for an unknown reason 56}; 57 58// Attempts to allocate memory inside the sandbox currently fall back to 59// allocating memory outside of the sandbox if necessary. Once this fallback is 60// no longer allowed/possible, these cases will become allocation failures 61// instead. To track the frequency of such events, the outcome of memory 62// allocation attempts inside the sandbox is reported to UMA. 63// 64// See caged_memory_allocation_outcome in counters-definitions.h 65// This class and the entry in counters-definitions.h use the term "cage" 66// instead of "sandbox" for historical reasons. 67enum class CagedMemoryAllocationOutcome { 68 kSuccess, // Allocation succeeded inside the cage 69 kOutsideCage, // Allocation failed inside the cage but succeeded outside 70 kFailure, // Allocation failed inside and outside of the cage 71}; 72 73base::AddressRegion GetReservedRegion(bool has_guard_regions, 74 void* buffer_start, 75 size_t byte_capacity) { 76#if V8_TARGET_ARCH_64_BIT && V8_ENABLE_WEBASSEMBLY 77 if (has_guard_regions) { 78 // Guard regions always look like this: 79 // |xxx(2GiB)xxx|.......(4GiB)..xxxxx|xxxxxx(4GiB)xxxxxx| 80 // ^ buffer_start 81 // ^ byte_length 82 // ^ negative guard region ^ positive guard region 83 84 Address start = reinterpret_cast<Address>(buffer_start); 85 DCHECK_EQ(8, sizeof(size_t)); // only use on 64-bit 86 DCHECK_EQ(0, start % AllocatePageSize()); 87 return base::AddressRegion(start - kNegativeGuardSize, 88 static_cast<size_t>(kFullGuardSize)); 89 } 90#endif 91 92 DCHECK(!has_guard_regions); 93 return base::AddressRegion(reinterpret_cast<Address>(buffer_start), 94 byte_capacity); 95} 96 97size_t GetReservationSize(bool has_guard_regions, size_t byte_capacity) { 98#if V8_TARGET_ARCH_64_BIT && V8_ENABLE_WEBASSEMBLY 99 if (has_guard_regions) return kFullGuardSize; 100#else 101 DCHECK(!has_guard_regions); 102#endif 103 104 return byte_capacity; 105} 106 107void RecordStatus(Isolate* isolate, AllocationStatus status) { 108 isolate->counters()->wasm_memory_allocation_result()->AddSample( 109 static_cast<int>(status)); 110} 111 112// When the sandbox is active, this function records the outcome of attempts to 113// allocate memory inside the sandbox which fall back to allocating memory 114// outside of the sandbox. Passing a value of nullptr for the result indicates 115// that the memory could not be allocated at all. 116void RecordSandboxMemoryAllocationResult(Isolate* isolate, void* result) { 117 // This metric is only meaningful when the sandbox is active. 118#ifdef V8_SANDBOX 119 if (GetProcessWideSandbox()->is_initialized()) { 120 CagedMemoryAllocationOutcome outcome; 121 if (result) { 122 bool allocation_in_cage = GetProcessWideSandbox()->Contains(result); 123 outcome = allocation_in_cage ? CagedMemoryAllocationOutcome::kSuccess 124 : CagedMemoryAllocationOutcome::kOutsideCage; 125 } else { 126 outcome = CagedMemoryAllocationOutcome::kFailure; 127 } 128 isolate->counters()->caged_memory_allocation_outcome()->AddSample( 129 static_cast<int>(outcome)); 130 } 131#endif 132} 133 134inline void DebugCheckZero(void* start, size_t byte_length) { 135#if DEBUG 136 // Double check memory is zero-initialized. Despite being DEBUG-only, 137 // this function is somewhat optimized for the benefit of test suite 138 // execution times (some tests allocate several gigabytes). 139 const byte* bytes = reinterpret_cast<const byte*>(start); 140 const size_t kBaseCase = 32; 141 for (size_t i = 0; i < kBaseCase && i < byte_length; i++) { 142 DCHECK_EQ(0, bytes[i]); 143 } 144 // Having checked the first kBaseCase bytes to be zero, we can now use 145 // {memcmp} to compare the range against itself shifted by that amount, 146 // thereby inductively checking the remaining bytes. 147 if (byte_length > kBaseCase) { 148 DCHECK_EQ(0, memcmp(bytes, bytes + kBaseCase, byte_length - kBaseCase)); 149 } 150#endif 151} 152} // namespace 153 154// The backing store for a Wasm shared memory remembers all the isolates 155// with which it has been shared. 156struct SharedWasmMemoryData { 157 std::vector<Isolate*> isolates_; 158}; 159 160void BackingStore::Clear() { 161 buffer_start_ = nullptr; 162 byte_length_ = 0; 163 has_guard_regions_ = false; 164 if (holds_shared_ptr_to_allocator_) { 165 type_specific_data_.v8_api_array_buffer_allocator_shared 166 .std::shared_ptr<v8::ArrayBuffer::Allocator>::~shared_ptr(); 167 holds_shared_ptr_to_allocator_ = false; 168 } 169 type_specific_data_.v8_api_array_buffer_allocator = nullptr; 170} 171 172BackingStore::BackingStore(void* buffer_start, size_t byte_length, 173 size_t max_byte_length, size_t byte_capacity, 174 SharedFlag shared, ResizableFlag resizable, 175 bool is_wasm_memory, bool free_on_destruct, 176 bool has_guard_regions, bool custom_deleter, 177 bool empty_deleter) 178 : buffer_start_(buffer_start), 179 byte_length_(byte_length), 180 max_byte_length_(max_byte_length), 181 byte_capacity_(byte_capacity), 182 id_(next_backing_store_id_.fetch_add(1)), 183 is_shared_(shared == SharedFlag::kShared), 184 is_resizable_(resizable == ResizableFlag::kResizable), 185 is_wasm_memory_(is_wasm_memory), 186 holds_shared_ptr_to_allocator_(false), 187 free_on_destruct_(free_on_destruct), 188 has_guard_regions_(has_guard_regions), 189 globally_registered_(false), 190 custom_deleter_(custom_deleter), 191 empty_deleter_(empty_deleter) { 192 // TODO(v8:11111): RAB / GSAB - Wasm integration. 193 DCHECK_IMPLIES(is_wasm_memory_, !is_resizable_); 194 DCHECK_IMPLIES(is_resizable_, !custom_deleter_); 195 DCHECK_IMPLIES(is_resizable_, free_on_destruct_); 196 DCHECK_IMPLIES(!is_wasm_memory && !is_resizable_, 197 byte_length_ == max_byte_length_); 198 DCHECK_GE(max_byte_length_, byte_length_); 199 DCHECK_GE(byte_capacity_, max_byte_length_); 200} 201 202BackingStore::~BackingStore() { 203 GlobalBackingStoreRegistry::Unregister(this); 204 205 if (buffer_start_ == nullptr) { 206 Clear(); 207 return; 208 } 209 210 PageAllocator* page_allocator = GetPlatformPageAllocator(); 211 // TODO(saelo) here and elsewhere in this file, replace with 212 // GetArrayBufferPageAllocator once the fallback to the platform page 213 // allocator is no longer allowed. 214#ifdef V8_SANDBOX 215 if (GetProcessWideSandbox()->Contains(buffer_start_)) { 216 page_allocator = GetSandboxPageAllocator(); 217 } else { 218 DCHECK(kAllowBackingStoresOutsideSandbox); 219 } 220#endif 221 222#if V8_ENABLE_WEBASSEMBLY 223 if (is_wasm_memory_) { 224 // TODO(v8:11111): RAB / GSAB - Wasm integration. 225 DCHECK(!is_resizable_); 226 DCHECK(free_on_destruct_); 227 DCHECK(!custom_deleter_); 228 size_t reservation_size = 229 GetReservationSize(has_guard_regions_, byte_capacity_); 230 TRACE_BS( 231 "BSw:free bs=%p mem=%p (length=%zu, capacity=%zu, reservation=%zu)\n", 232 this, buffer_start_, byte_length(), byte_capacity_, reservation_size); 233 if (is_shared_) { 234 // Deallocate the list of attached memory objects. 235 SharedWasmMemoryData* shared_data = get_shared_wasm_memory_data(); 236 delete shared_data; 237 type_specific_data_.shared_wasm_memory_data = nullptr; 238 } 239 240 // Wasm memories are always allocated through the page allocator. 241 auto region = 242 GetReservedRegion(has_guard_regions_, buffer_start_, byte_capacity_); 243 244 if (!region.is_empty()) { 245 FreePages(page_allocator, reinterpret_cast<void*>(region.begin()), 246 region.size()); 247 } 248 Clear(); 249 return; 250 } 251#endif // V8_ENABLE_WEBASSEMBLY 252 253 if (is_resizable_) { 254 DCHECK(free_on_destruct_); 255 DCHECK(!custom_deleter_); 256 auto region = 257 GetReservedRegion(has_guard_regions_, buffer_start_, byte_capacity_); 258 259 if (!region.is_empty()) { 260 FreePages(page_allocator, reinterpret_cast<void*>(region.begin()), 261 region.size()); 262 } 263 Clear(); 264 return; 265 } 266 if (custom_deleter_) { 267 DCHECK(free_on_destruct_); 268 TRACE_BS("BS:custom deleter bs=%p mem=%p (length=%zu, capacity=%zu)\n", 269 this, buffer_start_, byte_length(), byte_capacity_); 270 type_specific_data_.deleter.callback(buffer_start_, byte_length_, 271 type_specific_data_.deleter.data); 272 Clear(); 273 return; 274 } 275 if (free_on_destruct_) { 276 // JSArrayBuffer backing store. Deallocate through the embedder's allocator. 277 auto allocator = get_v8_api_array_buffer_allocator(); 278 TRACE_BS("BS:free bs=%p mem=%p (length=%zu, capacity=%zu)\n", this, 279 buffer_start_, byte_length(), byte_capacity_); 280 allocator->Free(buffer_start_, byte_length_); 281 } 282 Clear(); 283} 284 285// Allocate a backing store using the array buffer allocator from the embedder. 286std::unique_ptr<BackingStore> BackingStore::Allocate( 287 Isolate* isolate, size_t byte_length, SharedFlag shared, 288 InitializedFlag initialized) { 289 void* buffer_start = nullptr; 290 auto allocator = isolate->array_buffer_allocator(); 291 CHECK_NOT_NULL(allocator); 292 if (byte_length != 0) { 293 auto counters = isolate->counters(); 294 int mb_length = static_cast<int>(byte_length / MB); 295 if (mb_length > 0) { 296 counters->array_buffer_big_allocations()->AddSample(mb_length); 297 } 298 if (shared == SharedFlag::kShared) { 299 counters->shared_array_allocations()->AddSample(mb_length); 300 } 301 auto allocate_buffer = [allocator, initialized](size_t byte_length) { 302 if (initialized == InitializedFlag::kUninitialized) { 303 return allocator->AllocateUninitialized(byte_length); 304 } 305 void* buffer_start = allocator->Allocate(byte_length); 306 if (buffer_start) { 307 // TODO(wasm): node does not implement the zero-initialization API. 308 // Reenable this debug check when node does implement it properly. 309 constexpr bool 310 kDebugCheckZeroDisabledDueToNodeNotImplementingZeroInitAPI = true; 311 if ((!(kDebugCheckZeroDisabledDueToNodeNotImplementingZeroInitAPI)) && 312 !FLAG_mock_arraybuffer_allocator) { 313 DebugCheckZero(buffer_start, byte_length); 314 } 315 } 316 return buffer_start; 317 }; 318 319 buffer_start = isolate->heap()->AllocateExternalBackingStore( 320 allocate_buffer, byte_length); 321 322 if (buffer_start == nullptr) { 323 // Allocation failed. 324 counters->array_buffer_new_size_failures()->AddSample(mb_length); 325 return {}; 326 } 327 } 328 329 auto result = new BackingStore(buffer_start, // start 330 byte_length, // length 331 byte_length, // max length 332 byte_length, // capacity 333 shared, // shared 334 ResizableFlag::kNotResizable, // resizable 335 false, // is_wasm_memory 336 true, // free_on_destruct 337 false, // has_guard_regions 338 false, // custom_deleter 339 false); // empty_deleter 340 341 TRACE_BS("BS:alloc bs=%p mem=%p (length=%zu)\n", result, 342 result->buffer_start(), byte_length); 343 result->SetAllocatorFromIsolate(isolate); 344 return std::unique_ptr<BackingStore>(result); 345} 346 347void BackingStore::SetAllocatorFromIsolate(Isolate* isolate) { 348 if (auto allocator_shared = isolate->array_buffer_allocator_shared()) { 349 holds_shared_ptr_to_allocator_ = true; 350 new (&type_specific_data_.v8_api_array_buffer_allocator_shared) 351 std::shared_ptr<v8::ArrayBuffer::Allocator>( 352 std::move(allocator_shared)); 353 } else { 354 type_specific_data_.v8_api_array_buffer_allocator = 355 isolate->array_buffer_allocator(); 356 } 357} 358 359#if V8_ENABLE_WEBASSEMBLY 360// Allocate a backing store for a Wasm memory. Always use the page allocator 361// and add guard regions. 362std::unique_ptr<BackingStore> BackingStore::TryAllocateWasmMemory( 363 Isolate* isolate, size_t initial_pages, size_t maximum_pages, 364 SharedFlag shared) { 365 // Compute size of reserved memory. 366 size_t engine_max_pages = wasm::max_mem_pages(); 367 maximum_pages = std::min(engine_max_pages, maximum_pages); 368 369 auto result = TryAllocateAndPartiallyCommitMemory( 370 isolate, initial_pages * wasm::kWasmPageSize, 371 maximum_pages * wasm::kWasmPageSize, wasm::kWasmPageSize, initial_pages, 372 maximum_pages, true, shared); 373 // Shared Wasm memories need an anchor for the memory object list. 374 if (result && shared == SharedFlag::kShared) { 375 result->type_specific_data_.shared_wasm_memory_data = 376 new SharedWasmMemoryData(); 377 } 378 return result; 379} 380#endif // V8_ENABLE_WEBASSEMBLY 381 382std::unique_ptr<BackingStore> BackingStore::TryAllocateAndPartiallyCommitMemory( 383 Isolate* isolate, size_t byte_length, size_t max_byte_length, 384 size_t page_size, size_t initial_pages, size_t maximum_pages, 385 bool is_wasm_memory, SharedFlag shared) { 386 // Enforce engine limitation on the maximum number of pages. 387 if (maximum_pages > std::numeric_limits<size_t>::max() / page_size) { 388 return nullptr; 389 } 390 391 // Cannot reserve 0 pages on some OSes. 392 if (maximum_pages == 0) maximum_pages = 1; 393 394 TRACE_BS("BSw:try %zu pages, %zu max\n", initial_pages, maximum_pages); 395 396#if V8_ENABLE_WEBASSEMBLY 397 bool guards = is_wasm_memory && trap_handler::IsTrapHandlerEnabled(); 398#else 399 CHECK(!is_wasm_memory); 400 bool guards = false; 401#endif // V8_ENABLE_WEBASSEMBLY 402 403 // For accounting purposes, whether a GC was necessary. 404 bool did_retry = false; 405 406 // A helper to try running a function up to 3 times, executing a GC 407 // if the first and second attempts failed. 408 auto gc_retry = [&](const std::function<bool()>& fn) { 409 for (int i = 0; i < 3; i++) { 410 if (fn()) return true; 411 // Collect garbage and retry. 412 did_retry = true; 413 // TODO(wasm): try Heap::EagerlyFreeExternalMemory() first? 414 isolate->heap()->MemoryPressureNotification( 415 MemoryPressureLevel::kCritical, true); 416 } 417 return false; 418 }; 419 420 size_t byte_capacity = maximum_pages * page_size; 421 size_t reservation_size = GetReservationSize(guards, byte_capacity); 422 423 //-------------------------------------------------------------------------- 424 // Allocate pages (inaccessible by default). 425 //-------------------------------------------------------------------------- 426 void* allocation_base = nullptr; 427 PageAllocator* page_allocator = GetPlatformPageAllocator(); 428 auto allocate_pages = [&] { 429#ifdef V8_SANDBOX 430 page_allocator = GetSandboxPageAllocator(); 431 allocation_base = AllocatePages(page_allocator, nullptr, reservation_size, 432 page_size, PageAllocator::kNoAccess); 433 if (allocation_base) return true; 434 // We currently still allow falling back to the platform page allocator if 435 // the sandbox page allocator fails. This will eventually be removed. 436 // TODO(chromium:1218005) once we forbid the fallback, we should have a 437 // single API, e.g. GetArrayBufferPageAllocator(), that returns the correct 438 // page allocator to use here depending on whether the sandbox is enabled 439 // or not. 440 if (!kAllowBackingStoresOutsideSandbox) return false; 441 page_allocator = GetPlatformPageAllocator(); 442#endif 443 allocation_base = AllocatePages(page_allocator, nullptr, reservation_size, 444 page_size, PageAllocator::kNoAccess); 445 return allocation_base != nullptr; 446 }; 447 if (!gc_retry(allocate_pages)) { 448 // Page allocator could not reserve enough pages. 449 RecordStatus(isolate, AllocationStatus::kOtherFailure); 450 RecordSandboxMemoryAllocationResult(isolate, nullptr); 451 TRACE_BS("BSw:try failed to allocate pages\n"); 452 return {}; 453 } 454 455 // Get a pointer to the start of the buffer, skipping negative guard region 456 // if necessary. 457#if V8_ENABLE_WEBASSEMBLY 458 byte* buffer_start = reinterpret_cast<byte*>(allocation_base) + 459 (guards ? kNegativeGuardSize : 0); 460#else 461 DCHECK(!guards); 462 byte* buffer_start = reinterpret_cast<byte*>(allocation_base); 463#endif 464 465 //-------------------------------------------------------------------------- 466 // Commit the initial pages (allow read/write). 467 //-------------------------------------------------------------------------- 468 size_t committed_byte_length = initial_pages * page_size; 469 auto commit_memory = [&] { 470 return committed_byte_length == 0 || 471 SetPermissions(page_allocator, buffer_start, committed_byte_length, 472 PageAllocator::kReadWrite); 473 }; 474 if (!gc_retry(commit_memory)) { 475 TRACE_BS("BSw:try failed to set permissions (%p, %zu)\n", buffer_start, 476 committed_byte_length); 477 FreePages(page_allocator, allocation_base, reservation_size); 478 // SetPermissions put us over the process memory limit. 479 // We return an empty result so that the caller can throw an exception. 480 return {}; 481 } 482 483 DebugCheckZero(buffer_start, byte_length); // touch the bytes. 484 485 RecordStatus(isolate, did_retry ? AllocationStatus::kSuccessAfterRetry 486 : AllocationStatus::kSuccess); 487 RecordSandboxMemoryAllocationResult(isolate, allocation_base); 488 489 ResizableFlag resizable = 490 is_wasm_memory ? ResizableFlag::kNotResizable : ResizableFlag::kResizable; 491 492 auto result = new BackingStore(buffer_start, // start 493 byte_length, // length 494 max_byte_length, // max_byte_length 495 byte_capacity, // capacity 496 shared, // shared 497 resizable, // resizable 498 is_wasm_memory, // is_wasm_memory 499 true, // free_on_destruct 500 guards, // has_guard_regions 501 false, // custom_deleter 502 false); // empty_deleter 503 504 TRACE_BS( 505 "BSw:alloc bs=%p mem=%p (length=%zu, capacity=%zu, reservation=%zu)\n", 506 result, result->buffer_start(), byte_length, byte_capacity, 507 reservation_size); 508 509 return std::unique_ptr<BackingStore>(result); 510} 511 512#if V8_ENABLE_WEBASSEMBLY 513// Allocate a backing store for a Wasm memory. Always use the page allocator 514// and add guard regions. 515std::unique_ptr<BackingStore> BackingStore::AllocateWasmMemory( 516 Isolate* isolate, size_t initial_pages, size_t maximum_pages, 517 SharedFlag shared) { 518 // Wasm pages must be a multiple of the allocation page size. 519 DCHECK_EQ(0, wasm::kWasmPageSize % AllocatePageSize()); 520 521 // Enforce engine limitation on the maximum number of pages. 522 if (initial_pages > wasm::max_mem_pages()) return nullptr; 523 524 auto backing_store = 525 TryAllocateWasmMemory(isolate, initial_pages, maximum_pages, shared); 526 if (maximum_pages == initial_pages) { 527 // If initial pages, and maximum are equal, nothing more to do return early. 528 return backing_store; 529 } 530 531 // Retry with smaller maximum pages at each retry. 532 const int kAllocationTries = 3; 533 auto delta = (maximum_pages - initial_pages) / (kAllocationTries + 1); 534 size_t sizes[] = {maximum_pages - delta, maximum_pages - 2 * delta, 535 maximum_pages - 3 * delta, initial_pages}; 536 537 for (size_t i = 0; i < arraysize(sizes) && !backing_store; i++) { 538 backing_store = 539 TryAllocateWasmMemory(isolate, initial_pages, sizes[i], shared); 540 } 541 return backing_store; 542} 543 544std::unique_ptr<BackingStore> BackingStore::CopyWasmMemory(Isolate* isolate, 545 size_t new_pages, 546 size_t max_pages) { 547 // Note that we could allocate uninitialized to save initialization cost here, 548 // but since Wasm memories are allocated by the page allocator, the zeroing 549 // cost is already built-in. 550 auto new_backing_store = BackingStore::AllocateWasmMemory( 551 isolate, new_pages, max_pages, 552 is_shared() ? SharedFlag::kShared : SharedFlag::kNotShared); 553 554 if (!new_backing_store || 555 new_backing_store->has_guard_regions() != has_guard_regions_) { 556 return {}; 557 } 558 559 if (byte_length_ > 0) { 560 // If the allocation was successful, then the new buffer must be at least 561 // as big as the old one. 562 DCHECK_GE(new_pages * wasm::kWasmPageSize, byte_length_); 563 memcpy(new_backing_store->buffer_start(), buffer_start_, byte_length_); 564 } 565 566 return new_backing_store; 567} 568 569// Try to grow the size of a wasm memory in place, without realloc + copy. 570base::Optional<size_t> BackingStore::GrowWasmMemoryInPlace(Isolate* isolate, 571 size_t delta_pages, 572 size_t max_pages) { 573 // This function grows wasm memory by 574 // * changing the permissions of additional {delta_pages} pages to kReadWrite; 575 // * increment {byte_length_}; 576 // 577 // As this code is executed concurrently, the following steps are executed: 578 // 1) Read the current value of {byte_length_}; 579 // 2) Change the permission of all pages from {buffer_start_} to 580 // {byte_length_} + {delta_pages} * {page_size} to kReadWrite; 581 // * This operation may be executed racefully. The OS takes care of 582 // synchronization. 583 // 3) Try to update {byte_length_} with a compare_exchange; 584 // 4) Repeat 1) to 3) until the compare_exchange in 3) succeeds; 585 // 586 // The result of this function is the {byte_length_} before growing in pages. 587 // The result of this function appears like the result of an RMW-update on 588 // {byte_length_}, i.e. two concurrent calls to this function will result in 589 // different return values if {delta_pages} != 0. 590 // 591 // Invariants: 592 // * Permissions are always set incrementally, i.e. for any page {b} with 593 // kReadWrite permission, all pages between the first page {a} and page {b} 594 // also have kReadWrite permission. 595 // * {byte_length_} is always lower or equal than the amount of memory with 596 // permissions set to kReadWrite; 597 // * This is guaranteed by incrementing {byte_length_} with a 598 // compare_exchange after changing the permissions. 599 // * This invariant is the reason why we cannot use a fetch_add. 600 DCHECK(is_wasm_memory_); 601 max_pages = std::min(max_pages, byte_capacity_ / wasm::kWasmPageSize); 602 603 // Do a compare-exchange loop, because we also need to adjust page 604 // permissions. Note that multiple racing grows both try to set page 605 // permissions for the entire range (to be RW), so the operating system 606 // should deal with that raciness. We know we succeeded when we can 607 // compare/swap the old length with the new length. 608 size_t old_length = byte_length_.load(std::memory_order_relaxed); 609 610 if (delta_pages == 0) 611 return {old_length / wasm::kWasmPageSize}; // degenerate grow. 612 if (delta_pages > max_pages) return {}; // would never work. 613 614 size_t new_length = 0; 615 while (true) { 616 size_t current_pages = old_length / wasm::kWasmPageSize; 617 618 // Check if we have exceed the supplied maximum. 619 if (current_pages > (max_pages - delta_pages)) return {}; 620 621 new_length = (current_pages + delta_pages) * wasm::kWasmPageSize; 622 623 // Try to adjust the permissions on the memory. 624 if (!i::SetPermissions(GetPlatformPageAllocator(), buffer_start_, 625 new_length, PageAllocator::kReadWrite)) { 626 return {}; 627 } 628 if (byte_length_.compare_exchange_weak(old_length, new_length, 629 std::memory_order_acq_rel)) { 630 // Successfully updated both the length and permissions. 631 break; 632 } 633 } 634 635 if (!is_shared_ && free_on_destruct_) { 636 // Only do per-isolate accounting for non-shared backing stores. 637 reinterpret_cast<v8::Isolate*>(isolate) 638 ->AdjustAmountOfExternalAllocatedMemory(new_length - old_length); 639 } 640 return {old_length / wasm::kWasmPageSize}; 641} 642 643void BackingStore::AttachSharedWasmMemoryObject( 644 Isolate* isolate, Handle<WasmMemoryObject> memory_object) { 645 DCHECK(is_wasm_memory_); 646 DCHECK(is_shared_); 647 // We need to take the global registry lock for this operation. 648 GlobalBackingStoreRegistry::AddSharedWasmMemoryObject(isolate, this, 649 memory_object); 650} 651 652void BackingStore::BroadcastSharedWasmMemoryGrow( 653 Isolate* isolate, std::shared_ptr<BackingStore> backing_store) { 654 GlobalBackingStoreRegistry::BroadcastSharedWasmMemoryGrow(isolate, 655 backing_store); 656} 657 658void BackingStore::RemoveSharedWasmMemoryObjects(Isolate* isolate) { 659 GlobalBackingStoreRegistry::Purge(isolate); 660} 661 662void BackingStore::UpdateSharedWasmMemoryObjects(Isolate* isolate) { 663 GlobalBackingStoreRegistry::UpdateSharedWasmMemoryObjects(isolate); 664} 665#endif // V8_ENABLE_WEBASSEMBLY 666 667// Commit already reserved memory (for RAB backing stores (not shared)). 668BackingStore::ResizeOrGrowResult BackingStore::ResizeInPlace( 669 Isolate* isolate, size_t new_byte_length, size_t new_committed_length) { 670 DCHECK_LE(new_byte_length, new_committed_length); 671 DCHECK(!is_shared()); 672 673 if (new_byte_length < byte_length_) { 674 // TOOO(v8:11111): Figure out a strategy for shrinking - when do we 675 // un-commit the memory? 676 677 // Zero the memory so that in case the buffer is grown later, we have 678 // zeroed the contents already. 679 memset(reinterpret_cast<byte*>(buffer_start_) + new_byte_length, 0, 680 byte_length_ - new_byte_length); 681 682 // Changing the byte length wouldn't strictly speaking be needed, since 683 // the JSArrayBuffer already stores the updated length. This is to keep 684 // the BackingStore and JSArrayBuffer in sync. 685 byte_length_ = new_byte_length; 686 return kSuccess; 687 } 688 if (new_byte_length == byte_length_) { 689 // i::SetPermissions with size 0 fails on some platforms, so special 690 // handling for the case byte_length_ == new_byte_length == 0 is required. 691 return kSuccess; 692 } 693 694 // Try to adjust the permissions on the memory. 695 if (!i::SetPermissions(GetPlatformPageAllocator(), buffer_start_, 696 new_committed_length, PageAllocator::kReadWrite)) { 697 return kFailure; 698 } 699 700 // Do per-isolate accounting for non-shared backing stores. 701 DCHECK(free_on_destruct_); 702 reinterpret_cast<v8::Isolate*>(isolate) 703 ->AdjustAmountOfExternalAllocatedMemory(new_byte_length - byte_length_); 704 byte_length_ = new_byte_length; 705 return kSuccess; 706} 707 708// Commit already reserved memory (for GSAB backing stores (shared)). 709BackingStore::ResizeOrGrowResult BackingStore::GrowInPlace( 710 Isolate* isolate, size_t new_byte_length, size_t new_committed_length) { 711 DCHECK_LE(new_byte_length, new_committed_length); 712 DCHECK(is_shared()); 713 // See comment in GrowWasmMemoryInPlace. 714 // GrowableSharedArrayBuffer.prototype.grow can be called from several 715 // threads. If two threads try to grow() in a racy way, the spec allows the 716 // larger grow to throw also if the smaller grow succeeds first. The 717 // implementation below doesn't throw in that case - instead, it retries and 718 // succeeds. If the larger grow finishes first though, the smaller grow must 719 // throw. 720 size_t old_byte_length = byte_length_.load(std::memory_order_seq_cst); 721 while (true) { 722 if (new_byte_length < old_byte_length) { 723 // The caller checks for the new_byte_length < old_byte_length_ case. This 724 // can only happen if another thread grew the memory after that. 725 return kRace; 726 } 727 if (new_byte_length == old_byte_length) { 728 // i::SetPermissions with size 0 fails on some platforms, so special 729 // handling for the case old_byte_length == new_byte_length == 0 is 730 // required. 731 return kSuccess; 732 } 733 734 // Try to adjust the permissions on the memory. 735 if (!i::SetPermissions(GetPlatformPageAllocator(), buffer_start_, 736 new_committed_length, PageAllocator::kReadWrite)) { 737 return kFailure; 738 } 739 740 // compare_exchange_weak updates old_byte_length. 741 if (byte_length_.compare_exchange_weak(old_byte_length, new_byte_length, 742 std::memory_order_seq_cst)) { 743 // Successfully updated both the length and permissions. 744 break; 745 } 746 } 747 return kSuccess; 748} 749 750std::unique_ptr<BackingStore> BackingStore::WrapAllocation( 751 Isolate* isolate, void* allocation_base, size_t allocation_length, 752 SharedFlag shared, bool free_on_destruct) { 753 auto result = new BackingStore(allocation_base, // start 754 allocation_length, // length 755 allocation_length, // max length 756 allocation_length, // capacity 757 shared, // shared 758 ResizableFlag::kNotResizable, // resizable 759 false, // is_wasm_memory 760 free_on_destruct, // free_on_destruct 761 false, // has_guard_regions 762 false, // custom_deleter 763 false); // empty_deleter 764 result->SetAllocatorFromIsolate(isolate); 765 TRACE_BS("BS:wrap bs=%p mem=%p (length=%zu)\n", result, 766 result->buffer_start(), result->byte_length()); 767 return std::unique_ptr<BackingStore>(result); 768} 769 770std::unique_ptr<BackingStore> BackingStore::WrapAllocation( 771 void* allocation_base, size_t allocation_length, 772 v8::BackingStore::DeleterCallback deleter, void* deleter_data, 773 SharedFlag shared) { 774 bool is_empty_deleter = (deleter == v8::BackingStore::EmptyDeleter); 775 auto result = new BackingStore(allocation_base, // start 776 allocation_length, // length 777 allocation_length, // max length 778 allocation_length, // capacity 779 shared, // shared 780 ResizableFlag::kNotResizable, // resizable 781 false, // is_wasm_memory 782 true, // free_on_destruct 783 false, // has_guard_regions 784 true, // custom_deleter 785 is_empty_deleter); // empty_deleter 786 result->type_specific_data_.deleter = {deleter, deleter_data}; 787 TRACE_BS("BS:wrap bs=%p mem=%p (length=%zu)\n", result, 788 result->buffer_start(), result->byte_length()); 789 return std::unique_ptr<BackingStore>(result); 790} 791 792std::unique_ptr<BackingStore> BackingStore::EmptyBackingStore( 793 SharedFlag shared) { 794 auto result = new BackingStore(nullptr, // start 795 0, // length 796 0, // max length 797 0, // capacity 798 shared, // shared 799 ResizableFlag::kNotResizable, // resizable 800 false, // is_wasm_memory 801 true, // free_on_destruct 802 false, // has_guard_regions 803 false, // custom_deleter 804 false); // empty_deleter 805 806 return std::unique_ptr<BackingStore>(result); 807} 808 809bool BackingStore::Reallocate(Isolate* isolate, size_t new_byte_length) { 810 CHECK(!is_wasm_memory_ && !custom_deleter_ && !globally_registered_ && 811 free_on_destruct_ && !is_resizable_); 812 auto allocator = get_v8_api_array_buffer_allocator(); 813 CHECK_EQ(isolate->array_buffer_allocator(), allocator); 814 CHECK_EQ(byte_length_, byte_capacity_); 815 void* new_start = 816 allocator->Reallocate(buffer_start_, byte_length_, new_byte_length); 817 if (!new_start) return false; 818 buffer_start_ = new_start; 819 byte_capacity_ = new_byte_length; 820 byte_length_ = new_byte_length; 821 max_byte_length_ = new_byte_length; 822 return true; 823} 824 825v8::ArrayBuffer::Allocator* BackingStore::get_v8_api_array_buffer_allocator() { 826 CHECK(!is_wasm_memory_); 827 auto array_buffer_allocator = 828 holds_shared_ptr_to_allocator_ 829 ? type_specific_data_.v8_api_array_buffer_allocator_shared.get() 830 : type_specific_data_.v8_api_array_buffer_allocator; 831 CHECK_NOT_NULL(array_buffer_allocator); 832 return array_buffer_allocator; 833} 834 835SharedWasmMemoryData* BackingStore::get_shared_wasm_memory_data() { 836 CHECK(is_wasm_memory_ && is_shared_); 837 auto shared_wasm_memory_data = type_specific_data_.shared_wasm_memory_data; 838 CHECK(shared_wasm_memory_data); 839 return shared_wasm_memory_data; 840} 841 842namespace { 843// Implementation details of GlobalBackingStoreRegistry. 844struct GlobalBackingStoreRegistryImpl { 845 GlobalBackingStoreRegistryImpl() = default; 846 base::Mutex mutex_; 847 std::unordered_map<const void*, std::weak_ptr<BackingStore>> map_; 848}; 849base::LazyInstance<GlobalBackingStoreRegistryImpl>::type global_registry_impl_ = 850 LAZY_INSTANCE_INITIALIZER; 851inline GlobalBackingStoreRegistryImpl* impl() { 852 return global_registry_impl_.Pointer(); 853} 854} // namespace 855 856void GlobalBackingStoreRegistry::Register( 857 std::shared_ptr<BackingStore> backing_store) { 858 if (!backing_store || !backing_store->buffer_start()) return; 859 // Only wasm memory backing stores need to be registered globally. 860 CHECK(backing_store->is_wasm_memory()); 861 862 base::MutexGuard scope_lock(&impl()->mutex_); 863 if (backing_store->globally_registered_) return; 864 TRACE_BS("BS:reg bs=%p mem=%p (length=%zu, capacity=%zu)\n", 865 backing_store.get(), backing_store->buffer_start(), 866 backing_store->byte_length(), backing_store->byte_capacity()); 867 std::weak_ptr<BackingStore> weak = backing_store; 868 auto result = impl()->map_.insert({backing_store->buffer_start(), weak}); 869 CHECK(result.second); 870 backing_store->globally_registered_ = true; 871} 872 873void GlobalBackingStoreRegistry::Unregister(BackingStore* backing_store) { 874 if (!backing_store->globally_registered_) return; 875 876 CHECK(backing_store->is_wasm_memory()); 877 878 DCHECK_NOT_NULL(backing_store->buffer_start()); 879 880 base::MutexGuard scope_lock(&impl()->mutex_); 881 const auto& result = impl()->map_.find(backing_store->buffer_start()); 882 if (result != impl()->map_.end()) { 883 DCHECK(!result->second.lock()); 884 impl()->map_.erase(result); 885 } 886 backing_store->globally_registered_ = false; 887} 888 889void GlobalBackingStoreRegistry::Purge(Isolate* isolate) { 890 // We need to keep a reference to all backing stores that are inspected 891 // in the purging loop below. Otherwise, we might get a deadlock 892 // if the temporary backing store reference created in the loop is 893 // the last reference. In that case the destructor of the backing store 894 // may try to take the &impl()->mutex_ in order to unregister itself. 895 std::vector<std::shared_ptr<BackingStore>> prevent_destruction_under_lock; 896 base::MutexGuard scope_lock(&impl()->mutex_); 897 // Purge all entries in the map that refer to the given isolate. 898 for (auto& entry : impl()->map_) { 899 auto backing_store = entry.second.lock(); 900 prevent_destruction_under_lock.emplace_back(backing_store); 901 if (!backing_store) continue; // skip entries where weak ptr is null 902 CHECK(backing_store->is_wasm_memory()); 903 if (!backing_store->is_shared()) continue; // skip non-shared memory 904 SharedWasmMemoryData* shared_data = 905 backing_store->get_shared_wasm_memory_data(); 906 // Remove this isolate from the isolates list. 907 auto& isolates = shared_data->isolates_; 908 for (size_t i = 0; i < isolates.size(); i++) { 909 if (isolates[i] == isolate) isolates[i] = nullptr; 910 } 911 } 912} 913 914#if V8_ENABLE_WEBASSEMBLY 915void GlobalBackingStoreRegistry::AddSharedWasmMemoryObject( 916 Isolate* isolate, BackingStore* backing_store, 917 Handle<WasmMemoryObject> memory_object) { 918 // Add to the weak array list of shared memory objects in the isolate. 919 isolate->AddSharedWasmMemory(memory_object); 920 921 // Add the isolate to the list of isolates sharing this backing store. 922 base::MutexGuard scope_lock(&impl()->mutex_); 923 SharedWasmMemoryData* shared_data = 924 backing_store->get_shared_wasm_memory_data(); 925 auto& isolates = shared_data->isolates_; 926 int free_entry = -1; 927 for (size_t i = 0; i < isolates.size(); i++) { 928 if (isolates[i] == isolate) return; 929 if (isolates[i] == nullptr) free_entry = static_cast<int>(i); 930 } 931 if (free_entry >= 0) 932 isolates[free_entry] = isolate; 933 else 934 isolates.push_back(isolate); 935} 936 937void GlobalBackingStoreRegistry::BroadcastSharedWasmMemoryGrow( 938 Isolate* isolate, std::shared_ptr<BackingStore> backing_store) { 939 { 940 // The global lock protects the list of isolates per backing store. 941 base::MutexGuard scope_lock(&impl()->mutex_); 942 SharedWasmMemoryData* shared_data = 943 backing_store->get_shared_wasm_memory_data(); 944 for (Isolate* other : shared_data->isolates_) { 945 if (other && other != isolate) { 946 other->stack_guard()->RequestGrowSharedMemory(); 947 } 948 } 949 } 950 // Update memory objects in this isolate. 951 UpdateSharedWasmMemoryObjects(isolate); 952} 953 954void GlobalBackingStoreRegistry::UpdateSharedWasmMemoryObjects( 955 Isolate* isolate) { 956 HandleScope scope(isolate); 957 Handle<WeakArrayList> shared_wasm_memories = 958 isolate->factory()->shared_wasm_memories(); 959 960 for (int i = 0; i < shared_wasm_memories->length(); i++) { 961 HeapObject obj; 962 if (!shared_wasm_memories->Get(i).GetHeapObject(&obj)) continue; 963 964 Handle<WasmMemoryObject> memory_object(WasmMemoryObject::cast(obj), 965 isolate); 966 Handle<JSArrayBuffer> old_buffer(memory_object->array_buffer(), isolate); 967 std::shared_ptr<BackingStore> backing_store = old_buffer->GetBackingStore(); 968 969 Handle<JSArrayBuffer> new_buffer = 970 isolate->factory()->NewJSSharedArrayBuffer(std::move(backing_store)); 971 memory_object->update_instances(isolate, new_buffer); 972 } 973} 974#endif // V8_ENABLE_WEBASSEMBLY 975 976} // namespace internal 977} // namespace v8 978 979#undef TRACE_BS 980