diff --git a/src/asmjs/asm-wasm-builder.cc b/src/asmjs/asm-wasm-builder.cc index 4996203ce2..8556b13e55 100644 --- a/src/asmjs/asm-wasm-builder.cc +++ b/src/asmjs/asm-wasm-builder.cc @@ -176,6 +176,11 @@ class AsmWasmBuilderImpl final : public AstVisitor { } current_function_builder_ = LookupOrInsertFunction(decl->proxy()->var()); scope_ = kFuncScope; + + // Record start of the function, used as position for the stack check. + current_function_builder_->SetAsmFunctionStartPosition( + decl->fun()->start_position()); + RECURSE(Visit(decl->fun())); decl->set_fun(old_func); if (new_func_scope != nullptr) { diff --git a/src/compiler/wasm-compiler.cc b/src/compiler/wasm-compiler.cc index 13c6622c6b..ed739e1adf 100644 --- a/src/compiler/wasm-compiler.cc +++ b/src/compiler/wasm-compiler.cc @@ -488,17 +488,14 @@ void WasmGraphBuilder::StackCheck(wasm::WasmCodePosition position, 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_false = node; - } + Node* call = BuildCallToRuntime(Runtime::kStackGuard, jsgraph(), + module_->instance->context, nullptr, 0, + effect, stack_check.if_false); + SetSourcePosition(call, position); Node* ephi = graph()->NewNode(jsgraph()->common()->EffectPhi(2), - effect_true, effect_false, stack_check.merge); + effect_true, call, stack_check.merge); *control = stack_check.merge; *effect = ephi; diff --git a/src/runtime/runtime-test.cc b/src/runtime/runtime-test.cc index 23b4948ac4..bf10de842c 100644 --- a/src/runtime/runtime-test.cc +++ b/src/runtime/runtime-test.cc @@ -699,6 +699,13 @@ RUNTIME_FUNCTION(Runtime_IsAsmWasmCode) { return isolate->heap()->true_value(); } +RUNTIME_FUNCTION(Runtime_IsWasmCode) { + SealHandleScope shs(isolate); + DCHECK_EQ(1, args.length()); + CONVERT_ARG_CHECKED(JSFunction, function, 0); + bool is_js_to_wasm = function->code()->kind() == Code::JS_TO_WASM_FUNCTION; + return isolate->heap()->ToBoolean(is_js_to_wasm); +} #define ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(Name) \ RUNTIME_FUNCTION(Runtime_Has##Name) { \ diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index 409ce5a4da..f588210aff 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -906,6 +906,7 @@ namespace internal { F(SerializeWasmModule, 1, 1) \ F(DeserializeWasmModule, 2, 1) \ F(IsAsmWasmCode, 1, 1) \ + F(IsWasmCode, 1, 1) \ F(ValidateWasmInstancesChain, 2, 1) \ F(ValidateWasmModuleState, 1, 1) \ F(ValidateWasmOrphanedInstance, 1, 1) diff --git a/src/wasm/function-body-decoder.cc b/src/wasm/function-body-decoder.cc index a24087aafa..c2110296b5 100644 --- a/src/wasm/function-body-decoder.cc +++ b/src/wasm/function-body-decoder.cc @@ -619,7 +619,9 @@ class WasmFullDecoder : public WasmDecoder { ssa_env->effect = start; SetEnv("initial", ssa_env); if (builder_) { - builder_->StackCheck(position()); + // The function-prologue stack check is associated with position 0, which + // is never a position of any instruction in the function. + builder_->StackCheck(0); } } diff --git a/src/wasm/module-decoder.cc b/src/wasm/module-decoder.cc index 97d1a62da6..506c7355ab 100644 --- a/src/wasm/module-decoder.cc +++ b/src/wasm/module-decoder.cc @@ -1202,11 +1202,15 @@ AsmJsOffsetsResult DecodeAsmJsOffsets(const byte* tables_start, decoder.error("illegal asm function offset table size"); } const byte* table_end = decoder.pc() + size; - uint32_t locals_size = decoder.consume_u32("locals size"); + uint32_t locals_size = decoder.consume_u32v("locals size"); + int function_start_position = decoder.consume_u32v("function start pos"); int last_byte_offset = locals_size; - int last_asm_position = 0; + int last_asm_position = function_start_position; std::vector func_asm_offsets; func_asm_offsets.reserve(size / 4); // conservative estimation + // Add an entry for the stack check, associated with position 0. + func_asm_offsets.push_back( + {0, function_start_position, function_start_position}); while (decoder.ok() && decoder.pc() < table_end) { last_byte_offset += decoder.consume_u32v("byte offset delta"); int call_position = diff --git a/src/wasm/wasm-module-builder.cc b/src/wasm/wasm-module-builder.cc index f5228439ce..33a3464417 100644 --- a/src/wasm/wasm-module-builder.cc +++ b/src/wasm/wasm-module-builder.cc @@ -152,7 +152,7 @@ void WasmFunctionBuilder::SetName(Vector name) { void WasmFunctionBuilder::AddAsmWasmOffset(int call_position, int to_number_position) { - // We only want to emit one mapping per byte offset: + // We only want to emit one mapping per byte offset. DCHECK(asm_offsets_.size() == 0 || body_.size() > last_asm_byte_offset_); DCHECK_LE(body_.size(), kMaxUInt32); @@ -168,6 +168,15 @@ void WasmFunctionBuilder::AddAsmWasmOffset(int call_position, last_asm_source_position_ = to_number_position; } +void WasmFunctionBuilder::SetAsmFunctionStartPosition(int position) { + DCHECK_EQ(0, asm_func_start_source_position_); + DCHECK_LE(0, position); + // Must be called before emitting any asm.js source position. + DCHECK_EQ(0, asm_offsets_.size()); + asm_func_start_source_position_ = position; + last_asm_source_position_ = position; +} + void WasmFunctionBuilder::WriteSignature(ZoneBuffer& buffer) const { buffer.write_u32v(signature_index_); } @@ -201,14 +210,19 @@ void WasmFunctionBuilder::WriteBody(ZoneBuffer& buffer) const { } void WasmFunctionBuilder::WriteAsmWasmOffsetTable(ZoneBuffer& buffer) const { - if (asm_offsets_.size() == 0) { + if (asm_func_start_source_position_ == 0 && asm_offsets_.size() == 0) { buffer.write_size(0); return; } - buffer.write_size(asm_offsets_.size() + kInt32Size); + size_t locals_enc_size = LEBHelper::sizeof_u32v(locals_.Size()); + size_t func_start_size = + LEBHelper::sizeof_u32v(asm_func_start_source_position_); + buffer.write_size(asm_offsets_.size() + locals_enc_size + func_start_size); // Offset of the recorded byte offsets. DCHECK_GE(kMaxUInt32, locals_.Size()); - buffer.write_u32(static_cast(locals_.Size())); + buffer.write_u32v(static_cast(locals_.Size())); + // Start position of the function. + buffer.write_u32v(asm_func_start_source_position_); buffer.write(asm_offsets_.begin(), asm_offsets_.size()); } diff --git a/src/wasm/wasm-module-builder.h b/src/wasm/wasm-module-builder.h index 7b1547e429..3258f78d50 100644 --- a/src/wasm/wasm-module-builder.h +++ b/src/wasm/wasm-module-builder.h @@ -135,6 +135,7 @@ class V8_EXPORT_PRIVATE WasmFunctionBuilder : public ZoneObject { void ExportAs(Vector name); void SetName(Vector name); void AddAsmWasmOffset(int call_position, int to_number_position); + void SetAsmFunctionStartPosition(int position); void WriteSignature(ZoneBuffer& buffer) const; void WriteExports(ZoneBuffer& buffer) const; @@ -171,6 +172,7 @@ class V8_EXPORT_PRIVATE WasmFunctionBuilder : public ZoneObject { ZoneBuffer asm_offsets_; uint32_t last_asm_byte_offset_ = 0; uint32_t last_asm_source_position_ = 0; + uint32_t asm_func_start_source_position_ = 0; }; class WasmTemporary { diff --git a/test/mjsunit/regress/regress-677685.js b/test/mjsunit/regress/regress-677685.js new file mode 100644 index 0000000000..c4ef869b92 --- /dev/null +++ b/test/mjsunit/regress/regress-677685.js @@ -0,0 +1,32 @@ +// Copyright 2017 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. + +// Flags: --ignition-staging --stack-size=100 + +function Module(stdlib) { + "use asm"; + + var fround = stdlib.Math.fround; + + // f: double -> float + function f(a) { + a = +a; + return fround(a); + } + + return { f: f }; +} + +var f = Module({ Math: Math }).f; + +function runNearStackLimit() { + function g() { try { g(); } catch(e) { f(); } }; + g(); +} + +(function () { + function g() {} + + runNearStackLimit(g); +})(); diff --git a/test/mjsunit/wasm/asm-wasm-stack.js b/test/mjsunit/wasm/asm-wasm-stack.js index be728394ea..1e991a2f59 100644 --- a/test/mjsunit/wasm/asm-wasm-stack.js +++ b/test/mjsunit/wasm/asm-wasm-stack.js @@ -16,8 +16,7 @@ function checkPreformattedStack(e, expected_lines) { } } -function checkFunctionsOnCallsites(e, locations) { - var stack = e.stack; +function printCallsites(stack) { print('callsite objects (size ' + stack.length + '):'); for (var i = 0; i < stack.length; ++i) { var s = stack[i]; @@ -25,33 +24,47 @@ function checkFunctionsOnCallsites(e, locations) { ' [' + i + '] ' + s.getFunctionName() + ' (' + s.getFileName() + ':' + s.getLineNumber() + ':' + s.getColumnNumber() + ')'); } - assertEquals(locations.length, stack.length, 'stack size'); - for (var i = 0; i < locations.length; ++i) { +} + +function checkCallsiteArray(stack, expected) { + assertEquals(expected.length, stack.length, 'stack size'); + for (var i = 0; i < expected.length; ++i) { var cs = stack[i]; assertMatches('^' + filename + '$', cs.getFileName(), 'file name at ' + i); - assertEquals( - locations[i][0], cs.getFunctionName(), 'function name at ' + i); - assertEquals(locations[i][1], cs.getLineNumber(), 'line number at ' + i); - assertEquals( - locations[i][2], cs.getColumnNumber(), 'column number at ' + i); + assertEquals(expected[i][0], cs.getFunctionName(), 'function name at ' + i); + assertEquals(expected[i][1], cs.getLineNumber(), 'line number at ' + i); + assertEquals(expected[i][2], cs.getColumnNumber(), 'column number at ' + i); assertNotNull(cs.getThis(), 'receiver should be global'); assertEquals(stack[0].getThis(), cs.getThis(), 'receiver should be global'); } } +function checkFunctionsOnCallsites(e, expected) { + printCallsites(e.stack); + checkCallsiteArray(e.stack, expected); +} + +function checkTopFunctionsOnCallsites(e, expected) { + printCallsites(e.stack); + assertTrue( + e.stack.length >= expected.length, 'expected at least ' + + expected.length + ' callsites, got ' + e.stack.length); + checkCallsiteArray(e.stack.slice(0, expected.length), expected); +} + function throwException() { throw new Error('exception from JS'); } -function generateWasmFromAsmJs(stdlib, foreign, heap) { +function generateWasmFromAsmJs(stdlib, foreign) { 'use asm'; var throwFunc = foreign.throwFunc; function callThrow() { throwFunc(); } function redirectFun(i) { - i = i|0; - switch (i|0) { + i = i | 0; + switch (i | 0) { case 0: callThrow(); break; case 1: redirectFun(0); break; case 2: redirectFun(1); break; @@ -61,7 +74,8 @@ function generateWasmFromAsmJs(stdlib, foreign, heap) { } (function PreformattedStackTraceFromJS() { - var fun = generateWasmFromAsmJs(this, {throwFunc: throwException}, undefined); + var fun = generateWasmFromAsmJs(this, {throwFunc: throwException}); + assertTrue(%IsWasmCode(fun)); var e = null; try { fun(0); @@ -71,11 +85,11 @@ function generateWasmFromAsmJs(stdlib, foreign, heap) { assertInstanceof(e, Error, 'exception should have been thrown'); checkPreformattedStack(e, [ '^Error: exception from JS$', - '^ *at throwException \\(' + filename + ':43:9\\)$', - '^ *at callThrow \\(' + filename + ':50:5\\)$', - '^ *at redirectFun \\(' + filename + ':55:15\\)$', - '^ *at PreformattedStackTraceFromJS \\(' + filename + ':67:5\\)$', - '^ *at ' + filename + ':80:3$' + '^ *at throwException \\(' + filename + ':56:9\\)$', + '^ *at callThrow \\(' + filename + ':63:5\\)$', + '^ *at redirectFun \\(' + filename + ':68:15\\)$', + '^ *at PreformattedStackTraceFromJS \\(' + filename + ':81:5\\)$', + '^ *at ' + filename + ':94:3$' ]); })(); @@ -85,7 +99,8 @@ Error.prepareStackTrace = function(error, frames) { }; (function CallsiteObjectsFromJS() { - var fun = generateWasmFromAsmJs(this, {throwFunc: throwException}, undefined); + var fun = generateWasmFromAsmJs(this, {throwFunc: throwException}); + assertTrue(%IsWasmCode(fun)); var e = null; try { fun(2); @@ -94,12 +109,40 @@ Error.prepareStackTrace = function(error, frames) { } assertInstanceof(e, Error, 'exception should have been thrown'); checkFunctionsOnCallsites(e, [ - ['throwException', 43, 9], // -- - ['callThrow', 50, 5], // -- - ['redirectFun', 55, 15], // -- - ['redirectFun', 56, 15], // -- - ['redirectFun', 57, 15], // -- - ['CallsiteObjectsFromJS', 91, 5], // -- - [null, 105, 3] + ['throwException', 56, 9], // -- + ['callThrow', 63, 5], // -- + ['redirectFun', 68, 15], // -- + ['redirectFun', 69, 15], // -- + ['redirectFun', 70, 15], // -- + ['CallsiteObjectsFromJS', 106, 5], // -- + [null, 120, 3] + ]); +})(); + +function generateOverflowWasmFromAsmJs() { + 'use asm'; + function f(a) { + a = a | 0; + return f(a) | 0; + } + return f; +} + +(function StackOverflowPosition() { + var fun = generateOverflowWasmFromAsmJs(); + assertTrue(%IsWasmCode(fun)); + var e = null; + try { + fun(2); + } catch (ex) { + e = ex; + } + // TODO(wasm): Re-enable the check once 673297 is fixed. + //assertInstanceof(e, RangeError, 'RangeError should have been thrown'); + checkTopFunctionsOnCallsites(e, [ + ['f', 124, 13], // -- + ['f', 126, 12], // -- + ['f', 126, 12], // -- + ['f', 126, 12] // -- ]); })(); diff --git a/test/mjsunit/wasm/stack.js b/test/mjsunit/wasm/stack.js index 538c10e543..650b1436a2 100644 --- a/test/mjsunit/wasm/stack.js +++ b/test/mjsunit/wasm/stack.js @@ -145,5 +145,13 @@ Error.prepareStackTrace = function(error, frames) { fail("expected wasm exception"); } catch (e) { assertEquals("Maximum call stack size exceeded", e.message, "trap reason"); + assertTrue(e.stack.length >= 4, "expected at least 4 stack entries"); + verifyStack(e.stack.splice(0, 4), [ + // isWasm function line pos file + [ true, "recursion", 0, 0, null], + [ true, "recursion", 0, 3, null], + [ true, "recursion", 0, 3, null], + [ true, "recursion", 0, 3, null] + ]); } })();