[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:
clemensh 2017-01-09 01:43:04 -08:00 committed by Commit bot
parent d17558bc49
commit fc327e2308
11 changed files with 156 additions and 41 deletions

View File

@ -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) {

View File

@ -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;

View File

@ -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) { \

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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 =

View File

@ -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());
}

View File

@ -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 {

View 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);
})();

View File

@ -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] // --
]);
})();

View File

@ -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]
]);
}
})();