Commit 003f622a authored by Clemens Backes's avatar Clemens Backes Committed by Commit Bot

[wasm] Implement toString of exported functions

We currently print asm.js functions converted to wasm as
"function foo() { [native code] }", even though without asm to wasm
translation we get the proper source code. This is an observable
difference that should not be, and also foozzie finds this frequently in
different variations.

This CL makes us remember the start position (position of the "function"
token) and end position (right behind the closing "}") of each function
we transform to wasm. These offsets, together with the Script that
contained the function, allows us to reconstruct the source code of the
function for the {toString()} method.

R=jkummerow@chromium.org

Bug: chromium:667678
Change-Id: If22471cad4cefdfc67f6d1b8fda85aa0eeb411bd
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2016582
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65972}
parent 91c0ef31
......@@ -728,6 +728,9 @@ void AsmJsParser::ValidateFunctionTable() {
// 6.4 ValidateFunction
void AsmJsParser::ValidateFunction() {
// Remember position of the 'function' token as start position.
size_t function_start_position = scanner_.Position();
EXPECT_TOKEN(TOK(function));
if (!scanner_.IsGlobal()) {
FAIL("Expected function name");
......@@ -753,7 +756,8 @@ void AsmJsParser::ValidateFunction() {
return_type_ = nullptr;
// Record start of the function, used as position for the stack check.
current_function_builder_->SetAsmFunctionStartPosition(scanner_.Position());
current_function_builder_->SetAsmFunctionStartPosition(
function_start_position);
CachedVector<AsmType*> params(&cached_asm_type_p_vectors_);
ValidateFunctionParams(&params);
......@@ -778,6 +782,9 @@ void AsmJsParser::ValidateFunction() {
// clang-format on
RECURSE(ValidateStatement());
}
size_t function_end_position = scanner_.Position() + 1;
EXPECT_TOKEN('}');
if (!last_statement_is_return) {
......@@ -809,6 +816,10 @@ void AsmJsParser::ValidateFunction() {
// End function
current_function_builder_->Emit(kExprEnd);
// Emit function end position as the last position for this function.
current_function_builder_->AddAsmWasmOffset(function_end_position,
function_end_position);
if (current_function_builder_->GetPosition() > kV8MaxWasmFunctionSize) {
FAIL("Size of function body exceeds internal limit");
}
......
......@@ -5540,6 +5540,23 @@ Handle<String> JSFunction::ToString(Handle<JSFunction> function) {
return NativeCodeFunctionSourceString(shared_info);
}
// If this function was compiled from asm.js, use the recorded offset
// information.
if (shared_info->HasWasmExportedFunctionData()) {
Handle<WasmExportedFunctionData> function_data(
shared_info->wasm_exported_function_data(), isolate);
const wasm::WasmModule* module = function_data->instance().module();
if (is_asmjs_module(module)) {
std::pair<int, int> offsets =
module->asm_js_offset_information->GetFunctionOffsets(
function_data->function_index());
Handle<String> source(
String::cast(Script::cast(shared_info->script()).source()), isolate);
return isolate->factory()->NewSubString(source, offsets.first,
offsets.second);
}
}
if (shared_info->function_token_position() == kNoSourcePosition) {
// If the function token position isn't valid, return [native code] to
// ensure calling eval on the returned source code throws rather than
......
......@@ -2059,6 +2059,7 @@ AsmJsOffsetsResult DecodeAsmJsOffsets(Vector<const uint8_t> encoded_offsets) {
const byte* table_end = decoder.pc() + size;
uint32_t locals_size = decoder.consume_u32v("locals size");
int function_start_position = decoder.consume_u32v("function start pos");
int function_end_position = function_start_position;
int last_byte_offset = locals_size;
int last_asm_position = function_start_position;
std::vector<AsmJsOffsetEntry> func_asm_offsets;
......@@ -2074,12 +2075,19 @@ AsmJsOffsetsResult DecodeAsmJsOffsets(Vector<const uint8_t> encoded_offsets) {
int to_number_position =
call_position + decoder.consume_i32v("to_number position delta");
last_asm_position = to_number_position;
func_asm_offsets.push_back(
{last_byte_offset, call_position, to_number_position});
if (decoder.pc() == table_end) {
// The last entry is the function end marker.
DCHECK_EQ(call_position, to_number_position);
function_end_position = call_position;
} else {
func_asm_offsets.push_back(
{last_byte_offset, call_position, to_number_position});
}
}
DCHECK_EQ(decoder.pc(), table_end);
functions.emplace_back(
AsmJsOffsetFunctionEntries{std::move(func_asm_offsets)});
functions.emplace_back(AsmJsOffsetFunctionEntries{
function_start_position, function_end_position,
std::move(func_asm_offsets)});
}
DCHECK(decoder.ok());
DCHECK(!decoder.more());
......
......@@ -40,6 +40,8 @@ struct AsmJsOffsetEntry {
int source_position_number_conversion;
};
struct AsmJsOffsetFunctionEntries {
int start_offset;
int end_offset;
std::vector<AsmJsOffsetEntry> entries;
};
struct AsmJsOffsets {
......
......@@ -1456,25 +1456,15 @@ void InstanceBuilder::ProcessExports(Handle<WasmInstanceObject> instance) {
Handle<JSObject> exports_object;
MaybeHandle<String> single_function_name;
bool is_asm_js = false;
switch (module_->origin) {
case kWasmOrigin: {
// Create the "exports" object.
exports_object = isolate_->factory()->NewJSObjectWithNullProto();
break;
}
case kAsmJsSloppyOrigin:
case kAsmJsStrictOrigin: {
Handle<JSFunction> object_function = Handle<JSFunction>(
isolate_->native_context()->object_function(), isolate_);
exports_object = isolate_->factory()->NewJSObject(object_function);
single_function_name = isolate_->factory()->InternalizeUtf8String(
AsmJs::kSingleFunctionName);
is_asm_js = true;
break;
}
default:
UNREACHABLE();
bool is_asm_js = is_asmjs_module(module_);
if (is_asm_js) {
Handle<JSFunction> object_function = Handle<JSFunction>(
isolate_->native_context()->object_function(), isolate_);
exports_object = isolate_->factory()->NewJSObject(object_function);
single_function_name =
isolate_->factory()->InternalizeUtf8String(AsmJs::kSingleFunctionName);
} else {
exports_object = isolate_->factory()->NewJSObjectWithNullProto();
}
instance->set_exports_object(*exports_object);
......
......@@ -119,16 +119,7 @@ AsmJsOffsetInformation::~AsmJsOffsetInformation() = default;
int AsmJsOffsetInformation::GetSourcePosition(int func_index, int byte_offset,
bool is_at_number_conversion) {
base::MutexGuard lock(&mutex_);
DCHECK_EQ(encoded_offsets_ == nullptr, decoded_offsets_ != nullptr);
if (!decoded_offsets_) {
AsmJsOffsetsResult result =
wasm::DecodeAsmJsOffsets(encoded_offsets_.as_vector());
decoded_offsets_ =
std::make_unique<AsmJsOffsets>(std::move(result).value());
encoded_offsets_.ReleaseData();
}
EnsureDecodedOffsets();
DCHECK_LE(0, func_index);
DCHECK_GT(decoded_offsets_->functions.size(), func_index);
......@@ -150,6 +141,28 @@ int AsmJsOffsetInformation::GetSourcePosition(int func_index, int byte_offset,
: it->source_position_call;
}
std::pair<int, int> AsmJsOffsetInformation::GetFunctionOffsets(int func_index) {
EnsureDecodedOffsets();
DCHECK_LE(0, func_index);
DCHECK_GT(decoded_offsets_->functions.size(), func_index);
AsmJsOffsetFunctionEntries& function_info =
decoded_offsets_->functions[func_index];
return {function_info.start_offset, function_info.end_offset};
}
void AsmJsOffsetInformation::EnsureDecodedOffsets() {
base::MutexGuard mutex_guard(&mutex_);
DCHECK_EQ(encoded_offsets_ == nullptr, decoded_offsets_ != nullptr);
if (decoded_offsets_) return;
AsmJsOffsetsResult result =
wasm::DecodeAsmJsOffsets(encoded_offsets_.as_vector());
decoded_offsets_ = std::make_unique<AsmJsOffsets>(std::move(result).value());
encoded_offsets_.ReleaseData();
}
// Get a string stored in the module bytes representing a name.
WasmName ModuleWireBytes::GetNameOrNull(WireBytesRef ref) const {
if (!ref.is_set()) return {nullptr, 0}; // no name.
......
......@@ -203,7 +203,11 @@ class V8_EXPORT_PRIVATE AsmJsOffsetInformation {
int GetSourcePosition(int func_index, int byte_offset,
bool is_at_number_conversion);
std::pair<int, int> GetFunctionOffsets(int func_index);
private:
void EnsureDecodedOffsets();
// The offset information table is decoded lazily, hence needs to be
// protected against concurrent accesses.
// Exactly one of the two fields below will be set at a time.
......
......@@ -1800,6 +1800,7 @@ Handle<WasmExportedFunction> WasmExportedFunction::New(
DCHECK_EQ(is_asm_js_module, js_function->IsConstructor());
js_function->shared().set_length(arity);
js_function->shared().set_internal_formal_parameter_count(arity);
js_function->shared().set_script(instance->module_object().script());
return Handle<WasmExportedFunction>::cast(js_function);
}
......
......@@ -13,3 +13,4 @@ function Module(stdlib, foreign, buffer) {
var func = Module({}, {}, new ArrayBuffer(65536)).bar;
assertEquals("Module", Module.name);
assertEquals("foo", func.name);
assertEquals("function foo() {}", func.toString());
......@@ -148,7 +148,7 @@ function generateOverflowWasmFromAsmJs() {
}
assertInstanceof(e, RangeError, 'RangeError should have been thrown');
checkTopFunctionsOnCallsites(e, [
['f', 133, 13], // --
['f', 133, 3], // --
['f', 135, 12], // --
['f', 135, 12], // --
['f', 135, 12] // --
......
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