[asm.js] [wasm] Store function start position for stack check
We did not associate any position to the stack check in the wasm function prologue, hence a check failed later when trying to map the non-existent position to the asm.js source position. With this CL, we add a mapping to the source position table, mapping the stack check call to byte offset 0 (which is distinct from any valid instruction position). Also, we add another entry to the asm.js source position sidetable, mapping byte offset 0 to the start source position of the function body. R=titzer@chromium.org, ahaas@chromium.org BUG=chromium:677685 Review-Url: https://codereview.chromium.org/2609363004 Cr-Commit-Position: refs/heads/master@{#42130}
This commit is contained in:
parent
d17558bc49
commit
fc327e2308
@ -176,6 +176,11 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
|
||||
}
|
||||
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) {
|
||||
|
@ -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;
|
||||
|
@ -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) { \
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<AsmJsOffsetEntry> 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 =
|
||||
|
@ -152,7 +152,7 @@ void WasmFunctionBuilder::SetName(Vector<const char> 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<uint32_t>(locals_.Size()));
|
||||
buffer.write_u32v(static_cast<uint32_t>(locals_.Size()));
|
||||
// Start position of the function.
|
||||
buffer.write_u32v(asm_func_start_source_position_);
|
||||
buffer.write(asm_offsets_.begin(), asm_offsets_.size());
|
||||
}
|
||||
|
||||
|
@ -135,6 +135,7 @@ class V8_EXPORT_PRIVATE WasmFunctionBuilder : public ZoneObject {
|
||||
void ExportAs(Vector<const char> name);
|
||||
void SetName(Vector<const char> 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 {
|
||||
|
32
test/mjsunit/regress/regress-677685.js
Normal file
32
test/mjsunit/regress/regress-677685.js
Normal file
@ -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);
|
||||
})();
|
@ -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] // --
|
||||
]);
|
||||
})();
|
||||
|
@ -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]
|
||||
]);
|
||||
}
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user