Skip to content

Commit d9aa68e

Browse files
thibaudmichaudV8 LUCI CQ
authored andcommitted
[heap][wasm] Scan wasm inactive stacks
Wasm stack switching breaks the expectations of the unified V8/C++ heap by breaking the stack into multiple segments. To fix this: - Store a list of interesting inactive stacks in the heap's Stack object - When wasm switches stack, update this list, and also update the stack start pointer - Change {Stack::IteratePointers} to also visit pointers in the current list of inactive stacks R=nikolaos@chromium.org,jkummerow@chromium.org CC=​​irezvov@chromium.org Bug: v8:13493 Change-Id: Ieafeb89da31325e542e67403b6dc66c28d3be2fe Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4081126 Commit-Queue: Thibaud Michaud <thibaudm@chromium.org> Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> Reviewed-by: Michael Lippautz <mlippautz@chromium.org> Reviewed-by: Nikolaos Papaspyrou <nikolaos@chromium.org> Cr-Commit-Position: refs/heads/main@{#84731}
1 parent a614ccb commit d9aa68e

6 files changed

Lines changed: 100 additions & 38 deletions

File tree

src/execution/isolate.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3054,6 +3054,30 @@ void Isolate::AddSharedWasmMemory(Handle<WasmMemoryObject> memory_object) {
30543054
this, shared_wasm_memories, MaybeObjectHandle::Weak(memory_object));
30553055
heap()->set_shared_wasm_memories(*shared_wasm_memories);
30563056
}
3057+
3058+
void Isolate::RecordStackSwitchForScanning() {
3059+
Object current = root(RootIndex::kActiveContinuation);
3060+
DCHECK(!current.IsUndefined());
3061+
thread_local_top()->stack_.ClearStackSegments();
3062+
wasm::StackMemory* stack = Managed<wasm::StackMemory>::cast(
3063+
WasmContinuationObject::cast(current).stack())
3064+
.get()
3065+
.get();
3066+
current = WasmContinuationObject::cast(current).parent();
3067+
thread_local_top()->stack_.SetStackStart(
3068+
reinterpret_cast<void*>(stack->base()));
3069+
// We don't need to add all inactive stacks. Only the ones in the active chain
3070+
// may contain cpp heap pointers.
3071+
while (!current.IsUndefined()) {
3072+
auto cont = WasmContinuationObject::cast(current);
3073+
auto* stack = Managed<wasm::StackMemory>::cast(cont.stack()).get().get();
3074+
thread_local_top()->stack_.AddStackSegment(
3075+
reinterpret_cast<const void*>(stack->base()),
3076+
reinterpret_cast<const void*>(stack->jmpbuf()->sp));
3077+
current = cont.parent();
3078+
}
3079+
}
3080+
30573081
#endif // V8_ENABLE_WEBASSEMBLY
30583082

30593083
Isolate::PerIsolateThreadData::~PerIsolateThreadData() {

src/execution/isolate.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2023,6 +2023,9 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
20232023

20242024
#ifdef V8_ENABLE_WEBASSEMBLY
20252025
wasm::StackMemory*& wasm_stacks() { return wasm_stacks_; }
2026+
// Update the thread local's Stack object so that it is aware of the new stack
2027+
// start and the inactive stacks.
2028+
void RecordStackSwitchForScanning();
20262029
#endif
20272030

20282031
// Access to the global "locals block list cache". Caches outer-stack

src/execution/thread-local-top.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class ThreadLocalTop {
3030
// TODO(all): This is not particularly beautiful. We should probably
3131
// refactor this to really consist of just Addresses and 32-bit
3232
// integer fields.
33-
static constexpr uint32_t kSizeInBytes = 27 * kSystemPointerSize;
33+
static constexpr uint32_t kSizeInBytes = 30 * kSystemPointerSize;
3434

3535
// Does early low-level initialization that does not depend on the
3636
// isolate being present.

src/heap/base/stack.cc

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ namespace heap::base {
1515
Stack::Stack(const void* stack_start) : stack_start_(stack_start) {}
1616

1717
void Stack::SetStackStart(const void* stack_start) {
18+
DCHECK(!context_);
1819
stack_start_ = stack_start;
1920
}
2021

@@ -61,29 +62,33 @@ void IterateAsanFakeFrameIfNecessary(StackVisitor* visitor,
6162
// native frame. In case |addr| points to a fake frame of the current stack
6263
// iterate the fake frame. Frame layout see
6364
// https://github.com/google/sanitizers/wiki/AddressSanitizerUseAfterReturn
64-
if (asan_fake_stack) {
65-
void* fake_frame_begin;
66-
void* fake_frame_end;
67-
void* real_stack_frame = __asan_addr_is_in_fake_stack(
68-
const_cast<void*>(asan_fake_stack), const_cast<void*>(address),
69-
&fake_frame_begin, &fake_frame_end);
70-
if (real_stack_frame) {
71-
// |address| points to a fake frame. Check that the fake frame is part
72-
// of this stack.
73-
if (stack_start >= real_stack_frame && real_stack_frame >= stack_end) {
74-
// Iterate the fake frame.
75-
for (const void* const* current =
76-
reinterpret_cast<const void* const*>(fake_frame_begin);
77-
current < fake_frame_end; ++current) {
78-
const void* address = *current;
79-
if (address == nullptr) continue;
80-
visitor->VisitPointer(address);
81-
}
65+
if (!asan_fake_stack) return;
66+
void* fake_frame_begin;
67+
void* fake_frame_end;
68+
void* real_stack_frame = __asan_addr_is_in_fake_stack(
69+
const_cast<void*>(asan_fake_stack), const_cast<void*>(address),
70+
&fake_frame_begin, &fake_frame_end);
71+
if (real_stack_frame) {
72+
// |address| points to a fake frame. Check that the fake frame is part
73+
// of this stack.
74+
if (stack_start >= real_stack_frame && real_stack_frame >= stack_end) {
75+
// Iterate the fake frame.
76+
for (const void* const* current =
77+
reinterpret_cast<const void* const*>(fake_frame_begin);
78+
current < fake_frame_end; ++current) {
79+
const void* address = *current;
80+
if (address == nullptr) continue;
81+
visitor->VisitPointer(address);
8282
}
8383
}
8484
}
8585
}
86-
86+
#else
87+
void IterateAsanFakeFrameIfNecessary(StackVisitor* visitor,
88+
const void* asan_fake_stack,
89+
const void* stack_start,
90+
const void* stack_end,
91+
const void* address) {}
8792
#endif // V8_USE_ADDRESS_SANITIZER
8893

8994
void IterateUnsafeStackIfNecessary(StackVisitor* visitor) {
@@ -110,8 +115,6 @@ void IterateUnsafeStackIfNecessary(StackVisitor* visitor) {
110115
#endif // defined(__has_feature)
111116
}
112117

113-
} // namespace
114-
115118
// This method should never be inlined to ensure that a possible redzone cannot
116119
// contain any data that needs to be scanned.
117120
V8_NOINLINE
@@ -121,13 +124,32 @@ DISABLE_ASAN
121124
// thread, e.g., for interrupt handling. Atomic reads are not enough as the
122125
// other thread may use a lock to synchronize the access.
123126
DISABLE_TSAN
127+
void IteratePointersInStack(StackVisitor* visitor, const void* top,
128+
const void* start, const void* asan_fake_stack) {
129+
for (const void* const* current = reinterpret_cast<const void* const*>(top);
130+
current < start; ++current) {
131+
// MSAN: Instead of unpoisoning the whole stack, the slot's value is copied
132+
// into a local which is unpoisoned.
133+
const void* address = *current;
134+
MSAN_MEMORY_IS_INITIALIZED(&address, sizeof(address));
135+
if (address == nullptr) continue;
136+
visitor->VisitPointer(address);
137+
IterateAsanFakeFrameIfNecessary(visitor, asan_fake_stack, start, top,
138+
address);
139+
}
140+
}
141+
142+
} // namespace
143+
124144
void Stack::IteratePointers(StackVisitor* visitor) const {
125145
DCHECK_NOT_NULL(stack_start_);
126146
DCHECK(context_);
127147
DCHECK_NOT_NULL(context_->stack_marker);
128148

129149
#ifdef V8_USE_ADDRESS_SANITIZER
130150
const void* asan_fake_stack = __asan_get_current_fake_stack();
151+
#else
152+
const void* asan_fake_stack = nullptr;
131153
#endif // V8_USE_ADDRESS_SANITIZER
132154

133155
// Iterate through the registers.
@@ -136,10 +158,8 @@ void Stack::IteratePointers(StackVisitor* visitor) const {
136158
MSAN_MEMORY_IS_INITIALIZED(&address, sizeof(address));
137159
if (address == nullptr) continue;
138160
visitor->VisitPointer(address);
139-
#ifdef V8_USE_ADDRESS_SANITIZER
140161
IterateAsanFakeFrameIfNecessary(visitor, asan_fake_stack, stack_start_,
141162
context_->stack_marker, address);
142-
#endif // V8_USE_ADDRESS_SANITIZER
143163
}
144164

145165
// Iterate through the stack.
@@ -148,19 +168,12 @@ void Stack::IteratePointers(StackVisitor* visitor) const {
148168
constexpr size_t kMinStackAlignment = sizeof(void*);
149169
CHECK_EQ(0u, reinterpret_cast<uintptr_t>(context_->stack_marker) &
150170
(kMinStackAlignment - 1));
151-
for (const void* const* current =
152-
reinterpret_cast<const void* const*>(context_->stack_marker);
153-
current < stack_start_; ++current) {
154-
// MSAN: Instead of unpoisoning the whole stack, the slot's value is copied
155-
// into a local which is unpoisoned.
156-
const void* address = *current;
157-
MSAN_MEMORY_IS_INITIALIZED(&address, sizeof(address));
158-
if (address == nullptr) continue;
159-
visitor->VisitPointer(address);
160-
#ifdef V8_USE_ADDRESS_SANITIZER
161-
IterateAsanFakeFrameIfNecessary(visitor, asan_fake_stack, stack_start_,
162-
context_->stack_marker, address);
163-
#endif // V8_USE_ADDRESS_SANITIZER
171+
IteratePointersInStack(
172+
visitor, reinterpret_cast<const void* const*>(context_->stack_marker),
173+
stack_start_, asan_fake_stack);
174+
175+
for (const auto& stack : inactive_stacks_) {
176+
IteratePointersInStack(visitor, stack.top, stack.start, asan_fake_stack);
164177
}
165178

166179
IterateUnsafeStackIfNecessary(visitor);
@@ -219,4 +232,11 @@ void Stack::ClearContext(bool check_invariant) {
219232
context_.reset();
220233
}
221234

235+
void Stack::AddStackSegment(const void* start, const void* top) {
236+
DCHECK_LE(top, start);
237+
inactive_stacks_.push_back({start, top});
238+
}
239+
240+
void Stack::ClearStackSegments() { inactive_stacks_.clear(); }
241+
222242
} // namespace heap::base

src/heap/base/stack.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ class StackVisitor {
2323
// - ASAN/MSAN;
2424
// - SafeStack: https://releases.llvm.org/10.0.0/tools/clang/docs/SafeStack.html
2525
//
26+
// Stacks grow down, so throughout this class "start" refers the highest
27+
// address of the stack, and top/marker the lowest.
28+
//
2629
// TODO(chromium:1056170): Consider adding a component that keeps track
2730
// of relevant GC stack regions where interesting pointers can be found.
2831
class V8_EXPORT_PRIVATE Stack final {
@@ -44,7 +47,7 @@ class V8_EXPORT_PRIVATE Stack final {
4447

4548
// Word-aligned iteration of the stack and the saved registers.
4649
// Slot values are passed on to `visitor`.
47-
V8_NOINLINE void IteratePointers(StackVisitor* visitor) const;
50+
void IteratePointers(StackVisitor* visitor) const;
4851

4952
// Saves and clears the stack context, i.e., it sets the stack marker and
5053
// saves the registers.
@@ -54,6 +57,9 @@ class V8_EXPORT_PRIVATE Stack final {
5457
void SaveContext(bool check_invariant = true);
5558
void ClearContext(bool check_invariant = true);
5659

60+
void AddStackSegment(const void* start, const void* top);
61+
void ClearStackSegments();
62+
5763
private:
5864
struct Context {
5965
// The following constant is architecture-specific.
@@ -111,6 +117,14 @@ class V8_EXPORT_PRIVATE Stack final {
111117

112118
const void* stack_start_;
113119
std::unique_ptr<Context> context_;
120+
121+
// Stack segments that may also contain pointers and should be
122+
// scanned.
123+
struct StackSegments {
124+
const void* start;
125+
const void* top;
126+
};
127+
std::vector<StackSegments> inactive_stacks_;
114128
};
115129

116130
} // namespace heap::base

src/runtime/runtime-wasm.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,7 @@ void SyncStackLimit(Isolate* isolate) {
828828
}
829829
uintptr_t limit = reinterpret_cast<uintptr_t>(stack->jmpbuf()->stack_limit);
830830
isolate->stack_guard()->SetStackLimit(limit);
831+
isolate->RecordStackSwitchForScanning();
831832
}
832833
} // namespace
833834

0 commit comments

Comments
 (0)