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) {
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.
if (module_ && !module_->instance->context.is_null()) {
Node* limit = graph()->NewNode(
jsgraph()->machine()->Load(MachineType::Pointer()),
jsgraph()->ExternalConstant(
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* check =
graph()->NewNode(jsgraph()->machine()->UintLessThan(), limit, pointer);
Diamond stack_check(graph(), jsgraph()->common(), check, BranchHint::kTrue);
Node* effect_true = *effect_;
stack_check.Chain(*control);
Node* effect_true = *effect;
Node* effect_false;
// Generate a call to the runtime if there is a stack check failure.
{
Node* node = BuildCallToRuntime(Runtime::kStackGuard, jsgraph(),
module_->instance->context, nullptr, 0,
effect_, stack_check.if_false);
effect, stack_check.if_false);
effect_false = node;
}
Node* ephi = graph()->NewNode(jsgraph()->common()->EffectPhi(2),
effect_true, effect_false, stack_check.merge);
*control_ = stack_check.merge;
*effect_ = ephi;
*control = stack_check.merge;
*effect = ephi;
}
}
......
......@@ -142,7 +142,8 @@ class WasmGraphBuilder {
void AppendToMerge(Node* merge, 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}.
......
......@@ -684,8 +684,8 @@ class WasmFullDecoder : public WasmDecoder {
BlockTypeOperand operand(this, pc_);
SsaEnv* finish_try_env = Steal(ssa_env_);
// The continue environment is the inner environment.
PrepareForLoop(pc_, finish_try_env);
SetEnv("loop:start", Split(finish_try_env));
SsaEnv* loop_body_env = PrepareForLoop(pc_, finish_try_env);
SetEnv("loop:start", loop_body_env);
ssa_env_->SetNotMerged();
PushLoop(finish_try_env);
SetBlockType(&control_.back(), operand);
......@@ -1616,10 +1616,10 @@ class WasmFullDecoder : public WasmDecoder {
return tnode;
}
void PrepareForLoop(const byte* pc, SsaEnv* env) {
if (!env->go()) return;
SsaEnv* PrepareForLoop(const byte* pc, SsaEnv* env) {
if (!builder_) return Split(env);
if (!env->go()) return Split(env);
env->state = SsaEnv::kMerged;
if (!builder_) return;
env->control = builder_->Loop(env->control);
env->effect = builder_->EffectPhi(1, &env->effect, env->control);
......@@ -1633,7 +1633,10 @@ class WasmFullDecoder : public WasmDecoder {
env->locals[i] = builder_->Phi(local_type_vec_[i], 1, &env->locals[i],
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 {
env->locals[i] =
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}.
......
......@@ -2200,10 +2200,8 @@ bool wasm::ValidateModuleBytes(Isolate* isolate, const byte* start,
return false;
}
namespace {
MaybeHandle<JSArrayBuffer> GetInstanceMemory(Isolate* isolate,
Handle<JSObject> instance) {
MaybeHandle<JSArrayBuffer> wasm::GetInstanceMemory(Isolate* isolate,
Handle<JSObject> instance) {
Object* mem = instance->GetInternalField(kWasmMemArrayBuffer);
DCHECK(IsWasmObject(*instance));
if (mem->IsUndefined(isolate)) return MaybeHandle<JSArrayBuffer>();
......@@ -2218,8 +2216,6 @@ void SetInstanceMemory(Handle<JSObject> instance, JSArrayBuffer* buffer) {
compiled_module->set_ptr_to_heap(buffer);
}
} // namespace
int32_t wasm::GetInstanceMemorySize(Isolate* isolate,
Handle<JSObject> instance) {
MaybeHandle<JSArrayBuffer> maybe_mem_buffer =
......
......@@ -552,6 +552,9 @@ int GetNumImportedFunctions(Handle<JSObject> wasm_object);
// Returns nullptr on failing to get owning instance.
Object* GetOwningWasmInstance(Code* code);
MaybeHandle<JSArrayBuffer> GetInstanceMemory(Isolate* isolate,
Handle<JSObject> instance);
int32_t GetInstanceMemorySize(Isolate* isolate, Handle<JSObject> instance);
int32_t GrowInstanceMemory(Isolate* isolate, Handle<JSObject> instance,
......
......@@ -310,6 +310,87 @@ TEST(GrowMemoryZero) {
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) {
TestSignatures sigs;
v8::internal::AccountingAllocator allocator;
......@@ -365,8 +446,10 @@ TEST(Run_WasmModule_GrowMemOobFixedIndex) {
builder->WriteTo(buffer);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "Test");
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());
// Initial memory size is 16 pages, should trap till index > MemSize on
......@@ -408,8 +491,10 @@ TEST(Run_WasmModule_GrowMemOobVariableIndex) {
builder->WriteTo(buffer);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "Test");
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());
......
......@@ -78,17 +78,16 @@ const Handle<JSObject> InstantiateModuleForTesting(Isolate* isolate,
}
const Handle<JSObject> CompileInstantiateWasmModuleForTesting(
Isolate* isolate, Zone* zone, const byte* module_start,
const byte* module_end, ModuleOrigin origin) {
ErrorThrower thrower(isolate, "CompileInstantiateWasmModule");
Isolate* isolate, ErrorThrower* thrower, Zone* zone,
const byte* module_start, const byte* module_end, ModuleOrigin origin) {
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) {
thrower.Error("Wasm module decode failed");
thrower->Error("Wasm module decode failed");
return Handle<JSObject>::null();
}
return InstantiateModuleForTesting(isolate, &thrower, module.get());
return InstantiateModuleForTesting(isolate, thrower, module.get());
}
int32_t RunWasmModuleForTesting(Isolate* isolate, Handle<JSObject> instance,
......@@ -104,9 +103,9 @@ int32_t CompileAndRunWasmModule(Isolate* isolate, const byte* module_start,
const byte* module_end, ModuleOrigin origin) {
HandleScope scope(isolate);
Zone zone(isolate->allocator());
ErrorThrower thrower(isolate, "CompileAndRunWasmModule");
Handle<JSObject> instance = CompileInstantiateWasmModuleForTesting(
isolate, &zone, module_start, module_end, origin);
isolate, &thrower, &zone, module_start, module_end, origin);
if (instance.is_null()) {
return -1;
}
......@@ -224,7 +223,6 @@ void SetupIsolateForWasmModule(Isolate* isolate) {
WasmJs::InstallWasmModuleSymbolIfNeeded(isolate, isolate->global_object(),
isolate->native_context());
}
} // namespace testing
} // namespace wasm
} // namespace internal
......
......@@ -49,8 +49,8 @@ int32_t InterpretWasmModule(Isolate* isolate, ErrorThrower* thrower,
// Compiles WasmModule bytes and return an instance of the compiled module.
const Handle<JSObject> CompileInstantiateWasmModuleForTesting(
Isolate* isolate, Zone* zone, const byte* module_start,
const byte* module_end, ModuleOrigin origin);
Isolate* isolate, ErrorThrower* thrower, Zone* zone,
const byte* module_start, const byte* module_end, ModuleOrigin origin);
// Runs the module instance with arguments.
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