Commit e7dc5177 authored by ahaas's avatar ahaas Committed by Commit bot

[wasm] Add stack checks to loops.

Stack checks in loops allows to interrupt loops.

BUG=cctest/test-run-wasm-module/TestInterruptLoop
R=titzer@chromium.org, bradnelson@chromium.org

Review-Url: https://codereview.chromium.org/2405293002
Cr-Commit-Position: refs/heads/master@{#40251}
parent 58312643
...@@ -407,37 +407,44 @@ Node* WasmGraphBuilder::Int64Constant(int64_t value) { ...@@ -407,37 +407,44 @@ Node* WasmGraphBuilder::Int64Constant(int64_t value) {
return jsgraph()->Int64Constant(value); return jsgraph()->Int64Constant(value);
} }
void WasmGraphBuilder::StackCheck(wasm::WasmCodePosition position) { void WasmGraphBuilder::StackCheck(wasm::WasmCodePosition position,
Node** effect, Node** control) {
if (effect == nullptr) {
effect = effect_;
}
if (control == nullptr) {
control = control_;
}
// We do not generate stack checks for cctests. // We do not generate stack checks for cctests.
if (module_ && !module_->instance->context.is_null()) { if (module_ && !module_->instance->context.is_null()) {
Node* limit = graph()->NewNode( Node* limit = graph()->NewNode(
jsgraph()->machine()->Load(MachineType::Pointer()), jsgraph()->machine()->Load(MachineType::Pointer()),
jsgraph()->ExternalConstant( jsgraph()->ExternalConstant(
ExternalReference::address_of_stack_limit(jsgraph()->isolate())), ExternalReference::address_of_stack_limit(jsgraph()->isolate())),
jsgraph()->IntPtrConstant(0), *effect_, *control_); jsgraph()->IntPtrConstant(0), *effect, *control);
Node* pointer = graph()->NewNode(jsgraph()->machine()->LoadStackPointer()); Node* pointer = graph()->NewNode(jsgraph()->machine()->LoadStackPointer());
Node* check = Node* check =
graph()->NewNode(jsgraph()->machine()->UintLessThan(), limit, pointer); graph()->NewNode(jsgraph()->machine()->UintLessThan(), limit, pointer);
Diamond stack_check(graph(), jsgraph()->common(), check, BranchHint::kTrue); Diamond stack_check(graph(), jsgraph()->common(), check, BranchHint::kTrue);
stack_check.Chain(*control);
Node* effect_true = *effect_; Node* effect_true = *effect;
Node* effect_false; Node* effect_false;
// Generate a call to the runtime if there is a stack check failure. // Generate a call to the runtime if there is a stack check failure.
{ {
Node* node = BuildCallToRuntime(Runtime::kStackGuard, jsgraph(), Node* node = BuildCallToRuntime(Runtime::kStackGuard, jsgraph(),
module_->instance->context, nullptr, 0, module_->instance->context, nullptr, 0,
effect_, stack_check.if_false); effect, stack_check.if_false);
effect_false = node; effect_false = node;
} }
Node* ephi = graph()->NewNode(jsgraph()->common()->EffectPhi(2), Node* ephi = graph()->NewNode(jsgraph()->common()->EffectPhi(2),
effect_true, effect_false, stack_check.merge); effect_true, effect_false, stack_check.merge);
*control_ = stack_check.merge; *control = stack_check.merge;
*effect_ = ephi; *effect = ephi;
} }
} }
......
...@@ -142,7 +142,8 @@ class WasmGraphBuilder { ...@@ -142,7 +142,8 @@ class WasmGraphBuilder {
void AppendToMerge(Node* merge, Node* from); void AppendToMerge(Node* merge, Node* from);
void AppendToPhi(Node* phi, Node* from); void AppendToPhi(Node* phi, Node* from);
void StackCheck(wasm::WasmCodePosition position); void StackCheck(wasm::WasmCodePosition position, Node** effect = nullptr,
Node** control = nullptr);
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
// Operations that read and/or write {control} and {effect}. // Operations that read and/or write {control} and {effect}.
......
...@@ -684,8 +684,8 @@ class WasmFullDecoder : public WasmDecoder { ...@@ -684,8 +684,8 @@ class WasmFullDecoder : public WasmDecoder {
BlockTypeOperand operand(this, pc_); BlockTypeOperand operand(this, pc_);
SsaEnv* finish_try_env = Steal(ssa_env_); SsaEnv* finish_try_env = Steal(ssa_env_);
// The continue environment is the inner environment. // The continue environment is the inner environment.
PrepareForLoop(pc_, finish_try_env); SsaEnv* loop_body_env = PrepareForLoop(pc_, finish_try_env);
SetEnv("loop:start", Split(finish_try_env)); SetEnv("loop:start", loop_body_env);
ssa_env_->SetNotMerged(); ssa_env_->SetNotMerged();
PushLoop(finish_try_env); PushLoop(finish_try_env);
SetBlockType(&control_.back(), operand); SetBlockType(&control_.back(), operand);
...@@ -1616,10 +1616,10 @@ class WasmFullDecoder : public WasmDecoder { ...@@ -1616,10 +1616,10 @@ class WasmFullDecoder : public WasmDecoder {
return tnode; return tnode;
} }
void PrepareForLoop(const byte* pc, SsaEnv* env) { SsaEnv* PrepareForLoop(const byte* pc, SsaEnv* env) {
if (!env->go()) return; if (!builder_) return Split(env);
if (!env->go()) return Split(env);
env->state = SsaEnv::kMerged; env->state = SsaEnv::kMerged;
if (!builder_) return;
env->control = builder_->Loop(env->control); env->control = builder_->Loop(env->control);
env->effect = builder_->EffectPhi(1, &env->effect, env->control); env->effect = builder_->EffectPhi(1, &env->effect, env->control);
...@@ -1633,7 +1633,10 @@ class WasmFullDecoder : public WasmDecoder { ...@@ -1633,7 +1633,10 @@ class WasmFullDecoder : public WasmDecoder {
env->locals[i] = builder_->Phi(local_type_vec_[i], 1, &env->locals[i], env->locals[i] = builder_->Phi(local_type_vec_[i], 1, &env->locals[i],
env->control); env->control);
} }
return; SsaEnv* loop_body_env = Split(env);
builder_->StackCheck(position(), &(loop_body_env->effect),
&(loop_body_env->control));
return loop_body_env;
} }
} }
...@@ -1642,6 +1645,11 @@ class WasmFullDecoder : public WasmDecoder { ...@@ -1642,6 +1645,11 @@ class WasmFullDecoder : public WasmDecoder {
env->locals[i] = env->locals[i] =
builder_->Phi(local_type_vec_[i], 1, &env->locals[i], env->control); builder_->Phi(local_type_vec_[i], 1, &env->locals[i], env->control);
} }
SsaEnv* loop_body_env = Split(env);
builder_->StackCheck(position(), &(loop_body_env->effect),
&(loop_body_env->control));
return loop_body_env;
} }
// Create a complete copy of the {from}. // Create a complete copy of the {from}.
......
...@@ -2200,10 +2200,8 @@ bool wasm::ValidateModuleBytes(Isolate* isolate, const byte* start, ...@@ -2200,10 +2200,8 @@ bool wasm::ValidateModuleBytes(Isolate* isolate, const byte* start,
return false; return false;
} }
namespace { MaybeHandle<JSArrayBuffer> wasm::GetInstanceMemory(Isolate* isolate,
Handle<JSObject> instance) {
MaybeHandle<JSArrayBuffer> GetInstanceMemory(Isolate* isolate,
Handle<JSObject> instance) {
Object* mem = instance->GetInternalField(kWasmMemArrayBuffer); Object* mem = instance->GetInternalField(kWasmMemArrayBuffer);
DCHECK(IsWasmObject(*instance)); DCHECK(IsWasmObject(*instance));
if (mem->IsUndefined(isolate)) return MaybeHandle<JSArrayBuffer>(); if (mem->IsUndefined(isolate)) return MaybeHandle<JSArrayBuffer>();
...@@ -2218,8 +2216,6 @@ void SetInstanceMemory(Handle<JSObject> instance, JSArrayBuffer* buffer) { ...@@ -2218,8 +2216,6 @@ void SetInstanceMemory(Handle<JSObject> instance, JSArrayBuffer* buffer) {
compiled_module->set_ptr_to_heap(buffer); compiled_module->set_ptr_to_heap(buffer);
} }
} // namespace
int32_t wasm::GetInstanceMemorySize(Isolate* isolate, int32_t wasm::GetInstanceMemorySize(Isolate* isolate,
Handle<JSObject> instance) { Handle<JSObject> instance) {
MaybeHandle<JSArrayBuffer> maybe_mem_buffer = MaybeHandle<JSArrayBuffer> maybe_mem_buffer =
......
...@@ -552,6 +552,9 @@ int GetNumImportedFunctions(Handle<JSObject> wasm_object); ...@@ -552,6 +552,9 @@ int GetNumImportedFunctions(Handle<JSObject> wasm_object);
// Returns nullptr on failing to get owning instance. // Returns nullptr on failing to get owning instance.
Object* GetOwningWasmInstance(Code* code); Object* GetOwningWasmInstance(Code* code);
MaybeHandle<JSArrayBuffer> GetInstanceMemory(Isolate* isolate,
Handle<JSObject> instance);
int32_t GetInstanceMemorySize(Isolate* isolate, Handle<JSObject> instance); int32_t GetInstanceMemorySize(Isolate* isolate, Handle<JSObject> instance);
int32_t GrowInstanceMemory(Isolate* isolate, Handle<JSObject> instance, int32_t GrowInstanceMemory(Isolate* isolate, Handle<JSObject> instance,
......
...@@ -310,6 +310,87 @@ TEST(GrowMemoryZero) { ...@@ -310,6 +310,87 @@ TEST(GrowMemoryZero) {
TestModule(&zone, builder, kExpectedValue); TestModule(&zone, builder, kExpectedValue);
} }
class InterruptThread : public v8::base::Thread {
public:
explicit InterruptThread(Isolate* isolate, int32_t* memory)
: Thread(Options("TestInterruptLoop")),
isolate_(isolate),
memory_(memory) {}
static void OnInterrupt(v8::Isolate* isolate, void* data) {
int32_t* m = reinterpret_cast<int32_t*>(data);
// Set the interrupt location to 0 to break the loop in {TestInterruptLoop}.
m[interrupt_location_] = interrupt_value_;
}
virtual void Run() {
// Wait for the main thread to write the signal value.
while (memory_[0] != signal_value_) {
}
isolate_->RequestInterrupt(&OnInterrupt, const_cast<int32_t*>(memory_));
}
Isolate* isolate_;
volatile int32_t* memory_;
static const int32_t interrupt_location_ = 10;
static const int32_t interrupt_value_ = 154;
static const int32_t signal_value_ = 1221;
};
TEST(TestInterruptLoop) {
// This test tests that WebAssembly loops can be interrupted, i.e. that if an
// InterruptCallback is registered by {Isolate::RequestInterrupt}, then the
// InterruptCallback is eventually called even if a loop in WebAssembly code
// is executed.
// Test setup:
// The main thread executes a WebAssembly function with a loop. In the loop
// {signal_value_} is written to memory to signal a helper thread that the
// main thread reached the loop in the WebAssembly program. When the helper
// thread reads {signal_value_} from memory, it registers the
// InterruptCallback. Upon exeution, the InterruptCallback write into the
// WebAssemblyMemory to end the loop in the WebAssembly program.
TestSignatures sigs;
Isolate* isolate = CcTest::InitIsolateOnce();
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator);
WasmModuleBuilder* builder = new (&zone) WasmModuleBuilder(&zone);
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
ExportAsMain(f);
byte code[] = {WASM_LOOP(WASM_IFB(
WASM_NOT(WASM_LOAD_MEM(
MachineType::Int32(),
WASM_I32V(InterruptThread::interrupt_location_ * 4))),
WASM_STORE_MEM(MachineType::Int32(), WASM_ZERO,
WASM_I32V(InterruptThread::signal_value_)),
WASM_BR(1))),
WASM_I32V(121)};
f->EmitCode(code, sizeof(code));
ZoneBuffer buffer(&zone);
builder->WriteTo(buffer);
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "Test");
const Handle<JSObject> instance =
testing::CompileInstantiateWasmModuleForTesting(
isolate, &thrower, &zone, buffer.begin(), buffer.end(),
ModuleOrigin::kWasmOrigin);
CHECK(!instance.is_null());
MaybeHandle<JSArrayBuffer> maybe_memory =
GetInstanceMemory(isolate, instance);
Handle<JSArrayBuffer> memory = maybe_memory.ToHandleChecked();
int32_t* memory_array = reinterpret_cast<int32_t*>(memory->backing_store());
InterruptThread thread(isolate, memory_array);
thread.Start();
testing::RunWasmModuleForTesting(isolate, instance, 0, nullptr,
ModuleOrigin::kWasmOrigin);
CHECK_EQ(InterruptThread::interrupt_value_,
memory_array[InterruptThread::interrupt_location_]);
}
TEST(Run_WasmModule_GrowMemoryInIf) { TEST(Run_WasmModule_GrowMemoryInIf) {
TestSignatures sigs; TestSignatures sigs;
v8::internal::AccountingAllocator allocator; v8::internal::AccountingAllocator allocator;
...@@ -365,8 +446,10 @@ TEST(Run_WasmModule_GrowMemOobFixedIndex) { ...@@ -365,8 +446,10 @@ TEST(Run_WasmModule_GrowMemOobFixedIndex) {
builder->WriteTo(buffer); builder->WriteTo(buffer);
testing::SetupIsolateForWasmModule(isolate); testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "Test");
Handle<JSObject> instance = testing::CompileInstantiateWasmModuleForTesting( Handle<JSObject> instance = testing::CompileInstantiateWasmModuleForTesting(
isolate, &zone, buffer.begin(), buffer.end(), ModuleOrigin::kWasmOrigin); isolate, &thrower, &zone, buffer.begin(), buffer.end(),
ModuleOrigin::kWasmOrigin);
CHECK(!instance.is_null()); CHECK(!instance.is_null());
// Initial memory size is 16 pages, should trap till index > MemSize on // Initial memory size is 16 pages, should trap till index > MemSize on
...@@ -408,8 +491,10 @@ TEST(Run_WasmModule_GrowMemOobVariableIndex) { ...@@ -408,8 +491,10 @@ TEST(Run_WasmModule_GrowMemOobVariableIndex) {
builder->WriteTo(buffer); builder->WriteTo(buffer);
testing::SetupIsolateForWasmModule(isolate); testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "Test");
Handle<JSObject> instance = testing::CompileInstantiateWasmModuleForTesting( Handle<JSObject> instance = testing::CompileInstantiateWasmModuleForTesting(
isolate, &zone, buffer.begin(), buffer.end(), ModuleOrigin::kWasmOrigin); isolate, &thrower, &zone, buffer.begin(), buffer.end(),
ModuleOrigin::kWasmOrigin);
CHECK(!instance.is_null()); CHECK(!instance.is_null());
......
...@@ -78,17 +78,16 @@ const Handle<JSObject> InstantiateModuleForTesting(Isolate* isolate, ...@@ -78,17 +78,16 @@ const Handle<JSObject> InstantiateModuleForTesting(Isolate* isolate,
} }
const Handle<JSObject> CompileInstantiateWasmModuleForTesting( const Handle<JSObject> CompileInstantiateWasmModuleForTesting(
Isolate* isolate, Zone* zone, const byte* module_start, Isolate* isolate, ErrorThrower* thrower, Zone* zone,
const byte* module_end, ModuleOrigin origin) { const byte* module_start, const byte* module_end, ModuleOrigin origin) {
ErrorThrower thrower(isolate, "CompileInstantiateWasmModule");
std::unique_ptr<const WasmModule> module(DecodeWasmModuleForTesting( std::unique_ptr<const WasmModule> module(DecodeWasmModuleForTesting(
isolate, zone, &thrower, module_start, module_end, origin)); isolate, zone, thrower, module_start, module_end, origin));
if (module == nullptr) { if (module == nullptr) {
thrower.Error("Wasm module decode failed"); thrower->Error("Wasm module decode failed");
return Handle<JSObject>::null(); return Handle<JSObject>::null();
} }
return InstantiateModuleForTesting(isolate, &thrower, module.get()); return InstantiateModuleForTesting(isolate, thrower, module.get());
} }
int32_t RunWasmModuleForTesting(Isolate* isolate, Handle<JSObject> instance, int32_t RunWasmModuleForTesting(Isolate* isolate, Handle<JSObject> instance,
...@@ -104,9 +103,9 @@ int32_t CompileAndRunWasmModule(Isolate* isolate, const byte* module_start, ...@@ -104,9 +103,9 @@ int32_t CompileAndRunWasmModule(Isolate* isolate, const byte* module_start,
const byte* module_end, ModuleOrigin origin) { const byte* module_end, ModuleOrigin origin) {
HandleScope scope(isolate); HandleScope scope(isolate);
Zone zone(isolate->allocator()); Zone zone(isolate->allocator());
ErrorThrower thrower(isolate, "CompileAndRunWasmModule");
Handle<JSObject> instance = CompileInstantiateWasmModuleForTesting( Handle<JSObject> instance = CompileInstantiateWasmModuleForTesting(
isolate, &zone, module_start, module_end, origin); isolate, &thrower, &zone, module_start, module_end, origin);
if (instance.is_null()) { if (instance.is_null()) {
return -1; return -1;
} }
...@@ -224,7 +223,6 @@ void SetupIsolateForWasmModule(Isolate* isolate) { ...@@ -224,7 +223,6 @@ void SetupIsolateForWasmModule(Isolate* isolate) {
WasmJs::InstallWasmModuleSymbolIfNeeded(isolate, isolate->global_object(), WasmJs::InstallWasmModuleSymbolIfNeeded(isolate, isolate->global_object(),
isolate->native_context()); isolate->native_context());
} }
} // namespace testing } // namespace testing
} // namespace wasm } // namespace wasm
} // namespace internal } // namespace internal
......
...@@ -49,8 +49,8 @@ int32_t InterpretWasmModule(Isolate* isolate, ErrorThrower* thrower, ...@@ -49,8 +49,8 @@ int32_t InterpretWasmModule(Isolate* isolate, ErrorThrower* thrower,
// Compiles WasmModule bytes and return an instance of the compiled module. // Compiles WasmModule bytes and return an instance of the compiled module.
const Handle<JSObject> CompileInstantiateWasmModuleForTesting( const Handle<JSObject> CompileInstantiateWasmModuleForTesting(
Isolate* isolate, Zone* zone, const byte* module_start, Isolate* isolate, ErrorThrower* thrower, Zone* zone,
const byte* module_end, ModuleOrigin origin); const byte* module_start, const byte* module_end, ModuleOrigin origin);
// Runs the module instance with arguments. // Runs the module instance with arguments.
int32_t RunWasmModuleForTesting(Isolate* isolate, Handle<JSObject> instance, int32_t RunWasmModuleForTesting(Isolate* isolate, Handle<JSObject> instance,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment