blob: fa4ce47a1aff4293ae106da95a983e1b3dc5bdbb [file] [log] [blame]
// Copyright 2015 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.
#include "src/wasm/module-decoder.h"
#include "src/logging/metrics.h"
#include "src/tracing/trace-event.h"
#include "src/wasm/constant-expression.h"
#include "src/wasm/decoder.h"
#include "src/wasm/module-decoder-impl.h"
#include "src/wasm/struct-types.h"
#include "src/wasm/wasm-constants.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-opcodes-inl.h"
namespace v8 {
namespace internal {
namespace wasm {
const char* SectionName(SectionCode code) {
switch (code) {
case kUnknownSectionCode:
return "Unknown";
case kTypeSectionCode:
return "Type";
case kImportSectionCode:
return "Import";
case kFunctionSectionCode:
return "Function";
case kTableSectionCode:
return "Table";
case kMemorySectionCode:
return "Memory";
case kGlobalSectionCode:
return "Global";
case kExportSectionCode:
return "Export";
case kStartSectionCode:
return "Start";
case kCodeSectionCode:
return "Code";
case kElementSectionCode:
return "Element";
case kDataSectionCode:
return "Data";
case kTagSectionCode:
return "Tag";
case kStringRefSectionCode:
return "StringRef";
case kDataCountSectionCode:
return "DataCount";
case kNameSectionCode:
return kNameString;
case kSourceMappingURLSectionCode:
return kSourceMappingURLString;
case kDebugInfoSectionCode:
return kDebugInfoString;
case kExternalDebugInfoSectionCode:
return kExternalDebugInfoString;
case kInstTraceSectionCode:
return kInstTraceString;
case kCompilationHintsSectionCode:
return kCompilationHintsString;
case kBranchHintsSectionCode:
return kBranchHintsString;
default:
return "<unknown>";
}
}
ModuleResult DecodeWasmModule(
WasmFeatures enabled_features, base::Vector<const uint8_t> wire_bytes,
bool validate_functions, ModuleOrigin origin, Counters* counters,
std::shared_ptr<metrics::Recorder> metrics_recorder,
v8::metrics::Recorder::ContextId context_id,
DecodingMethod decoding_method) {
if (counters) {
auto size_counter =
SELECT_WASM_COUNTER(counters, origin, wasm, module_size_bytes);
static_assert(kV8MaxWasmModuleSize < kMaxInt);
size_counter->AddSample(static_cast<int>(wire_bytes.size()));
}
v8::metrics::WasmModuleDecoded metrics_event;
base::ElapsedTimer timer;
timer.Start();
ModuleResult result = DecodeWasmModule(enabled_features, wire_bytes,
validate_functions, origin);
if (counters && result.ok()) {
auto counter =
SELECT_WASM_COUNTER(counters, origin, wasm_functions_per, module);
counter->AddSample(
static_cast<int>(result.value()->num_declared_functions));
}
// Record event metrics.
metrics_event.wall_clock_duration_in_us = timer.Elapsed().InMicroseconds();
timer.Stop();
metrics_event.success = result.ok();
metrics_event.async = decoding_method == DecodingMethod::kAsync ||
decoding_method == DecodingMethod::kAsyncStream;
metrics_event.streamed = decoding_method == DecodingMethod::kSyncStream ||
decoding_method == DecodingMethod::kAsyncStream;
if (result.ok()) {
metrics_event.function_count = result.value()->num_declared_functions;
}
metrics_event.module_size_in_bytes = wire_bytes.size();
metrics_recorder->DelayMainThreadEvent(metrics_event, context_id);
return result;
}
ModuleResult DecodeWasmModule(WasmFeatures enabled_features,
base::Vector<const uint8_t> wire_bytes,
bool validate_functions, ModuleOrigin origin) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
"wasm.DecodeWasmModule");
ModuleDecoderImpl decoder{enabled_features, wire_bytes, origin};
return decoder.DecodeModule(validate_functions);
}
ModuleResult DecodeWasmModuleForDisassembler(
base::Vector<const uint8_t> wire_bytes, ITracer* tracer) {
constexpr bool kNoValidateFunctions = false;
ModuleDecoderImpl decoder{WasmFeatures::All(), wire_bytes, kWasmOrigin,
tracer};
return decoder.DecodeModule(kNoValidateFunctions);
}
ModuleDecoder::ModuleDecoder(WasmFeatures enabled_features)
: impl_(std::make_unique<ModuleDecoderImpl>(
enabled_features, base::Vector<const uint8_t>{}, kWasmOrigin)) {}
ModuleDecoder::~ModuleDecoder() = default;
const std::shared_ptr<WasmModule>& ModuleDecoder::shared_module() const {
return impl_->shared_module();
}
void ModuleDecoder::DecodeModuleHeader(base::Vector<const uint8_t> bytes) {
impl_->DecodeModuleHeader(bytes);
}
void ModuleDecoder::DecodeSection(SectionCode section_code,
base::Vector<const uint8_t> bytes,
uint32_t offset) {
impl_->DecodeSection(section_code, bytes, offset);
}
void ModuleDecoder::DecodeFunctionBody(uint32_t index, uint32_t length,
uint32_t offset) {
impl_->DecodeFunctionBody(index, length, offset);
}
void ModuleDecoder::StartCodeSection(WireBytesRef section_bytes) {
impl_->StartCodeSection(section_bytes);
}
bool ModuleDecoder::CheckFunctionsCount(uint32_t functions_count,
uint32_t error_offset) {
return impl_->CheckFunctionsCount(functions_count, error_offset);
}
ModuleResult ModuleDecoder::FinishDecoding() { return impl_->FinishDecoding(); }
size_t ModuleDecoder::IdentifyUnknownSection(ModuleDecoder* decoder,
base::Vector<const uint8_t> bytes,
uint32_t offset,
SectionCode* result) {
if (!decoder->ok()) return 0;
decoder->impl_->Reset(bytes, offset);
*result =
IdentifyUnknownSectionInternal(decoder->impl_.get(), ITracer::NoTrace);
return decoder->impl_->pc() - bytes.begin();
}
bool ModuleDecoder::ok() { return impl_->ok(); }
Result<const FunctionSig*> DecodeWasmSignatureForTesting(
WasmFeatures enabled_features, Zone* zone,
base::Vector<const uint8_t> bytes) {
ModuleDecoderImpl decoder{enabled_features, bytes, kWasmOrigin};
return decoder.toResult(decoder.DecodeFunctionSignature(zone, bytes.begin()));
}
ConstantExpression DecodeWasmInitExprForTesting(
WasmFeatures enabled_features, base::Vector<const uint8_t> bytes,
ValueType expected) {
ModuleDecoderImpl decoder{enabled_features, bytes, kWasmOrigin};
return decoder.DecodeInitExprForTesting(expected);
}
FunctionResult DecodeWasmFunctionForTesting(
WasmFeatures enabled_features, Zone* zone, ModuleWireBytes wire_bytes,
const WasmModule* module, base::Vector<const uint8_t> function_bytes) {
if (function_bytes.size() > kV8MaxWasmFunctionSize) {
return FunctionResult{
WasmError{0, "size > maximum function size (%zu): %zu",
kV8MaxWasmFunctionSize, function_bytes.size()}};
}
ModuleDecoderImpl decoder{enabled_features, function_bytes, kWasmOrigin};
return decoder.DecodeSingleFunctionForTesting(zone, wire_bytes, module);
}
AsmJsOffsetsResult DecodeAsmJsOffsets(
base::Vector<const uint8_t> encoded_offsets) {
std::vector<AsmJsOffsetFunctionEntries> functions;
Decoder decoder(encoded_offsets);
uint32_t functions_count = decoder.consume_u32v("functions count");
// Consistency check.
DCHECK_GE(encoded_offsets.size(), functions_count);
functions.reserve(functions_count);
for (uint32_t i = 0; i < functions_count; ++i) {
uint32_t size = decoder.consume_u32v("table size");
if (size == 0) {
functions.emplace_back();
continue;
}
DCHECK(decoder.checkAvailable(size));
const uint8_t* 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;
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.pc() < table_end) {
DCHECK(decoder.ok());
last_byte_offset += decoder.consume_u32v("byte offset delta");
int call_position =
last_asm_position + decoder.consume_i32v("call position delta");
int to_number_position =
call_position + decoder.consume_i32v("to_number position delta");
last_asm_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{
function_start_position, function_end_position,
std::move(func_asm_offsets)});
}
DCHECK(decoder.ok());
DCHECK(!decoder.more());
return decoder.toResult(AsmJsOffsets{std::move(functions)});
}
std::vector<CustomSectionOffset> DecodeCustomSections(
base::Vector<const uint8_t> bytes) {
Decoder decoder(bytes);
decoder.consume_bytes(4, "wasm magic");
decoder.consume_bytes(4, "wasm version");
std::vector<CustomSectionOffset> result;
while (decoder.more()) {
uint8_t section_code = decoder.consume_u8("section code");
uint32_t section_length = decoder.consume_u32v("section length");
uint32_t section_start = decoder.pc_offset();
if (section_code != 0) {
// Skip known sections.
decoder.consume_bytes(section_length, "section bytes");
continue;
}
uint32_t name_length = decoder.consume_u32v("name length");
uint32_t name_offset = decoder.pc_offset();
decoder.consume_bytes(name_length, "section name");
uint32_t payload_offset = decoder.pc_offset();
if (section_length < (payload_offset - section_start)) {
decoder.error("invalid section length");
break;
}
uint32_t payload_length = section_length - (payload_offset - section_start);
decoder.consume_bytes(payload_length);
if (decoder.failed()) break;
result.push_back({{section_start, section_length},
{name_offset, name_length},
{payload_offset, payload_length}});
}
return result;
}
namespace {
bool FindNameSection(Decoder* decoder) {
static constexpr int kModuleHeaderSize = 8;
decoder->consume_bytes(kModuleHeaderSize, "module header");
WasmSectionIterator section_iter(decoder, ITracer::NoTrace);
while (decoder->ok() && section_iter.more() &&
section_iter.section_code() != kNameSectionCode) {
section_iter.advance(true);
}
if (!section_iter.more()) return false;
// Reset the decoder to not read beyond the name section end.
decoder->Reset(section_iter.payload(), decoder->pc_offset());
return true;
}
enum class EmptyNames : bool { kAllow, kSkip };
void DecodeNameMapInternal(NameMap& target, Decoder& decoder,
EmptyNames empty_names = EmptyNames::kSkip) {
uint32_t count = decoder.consume_u32v("names count");
for (uint32_t i = 0; i < count; i++) {
uint32_t index = decoder.consume_u32v("index");
WireBytesRef name =
consume_string(&decoder, unibrow::Utf8Variant::kLossyUtf8, "name");
if (!decoder.ok()) break;
if (index > NameMap::kMaxKey) continue;
if (empty_names == EmptyNames::kSkip && name.is_empty()) continue;
if (!validate_utf8(&decoder, name)) continue;
target.Put(index, name);
}
target.FinishInitialization();
}
void DecodeNameMap(NameMap& target, Decoder& decoder,
uint32_t subsection_payload_length,
EmptyNames empty_names = EmptyNames::kSkip) {
if (target.is_set()) {
decoder.consume_bytes(subsection_payload_length);
return;
}
DecodeNameMapInternal(target, decoder, empty_names);
}
void DecodeIndirectNameMap(IndirectNameMap& target, Decoder& decoder,
uint32_t subsection_payload_length) {
if (target.is_set()) {
decoder.consume_bytes(subsection_payload_length);
return;
}
uint32_t outer_count = decoder.consume_u32v("outer count");
for (uint32_t i = 0; i < outer_count; ++i) {
uint32_t outer_index = decoder.consume_u32v("outer index");
if (outer_index > IndirectNameMap::kMaxKey) continue;
NameMap names;
DecodeNameMapInternal(names, decoder);
target.Put(outer_index, std::move(names));
if (!decoder.ok()) break;
}
target.FinishInitialization();
}
} // namespace
void DecodeFunctionNames(base::Vector<const uint8_t> wire_bytes,
NameMap& names) {
Decoder decoder(wire_bytes);
if (FindNameSection(&decoder)) {
while (decoder.ok() && decoder.more()) {
uint8_t name_type = decoder.consume_u8("name type");
if (name_type & 0x80) break; // no varuint7
uint32_t name_payload_len = decoder.consume_u32v("name payload length");
if (!decoder.checkAvailable(name_payload_len)) break;
if (name_type != NameSectionKindCode::kFunctionCode) {
decoder.consume_bytes(name_payload_len, "name subsection payload");
continue;
}
// We need to allow empty function names for spec-conformant stack traces.
DecodeNameMapInternal(names, decoder, EmptyNames::kAllow);
// The spec allows only one occurrence of each subsection. We could be
// more permissive and allow repeated subsections; in that case we'd
// have to delay calling {target.FinishInitialization()} on the function
// names map until we've seen them all.
// For now, we stop decoding after finding the first function names
// subsection.
return;
}
}
}
namespace {
// A task that validates multiple functions in parallel, storing the earliest
// validation error in {this} decoder.
class ValidateFunctionsTask : public JobTask {
public:
explicit ValidateFunctionsTask(base::Vector<const uint8_t> wire_bytes,
const WasmModule* module,
WasmFeatures enabled_features,
std::function<bool(int)> filter,
WasmError* error_out)
: wire_bytes_(wire_bytes),
module_(module),
enabled_features_(enabled_features),
filter_(std::move(filter)),
next_function_(module->num_imported_functions),
after_last_function_(next_function_ + module->num_declared_functions),
error_out_(error_out) {
DCHECK(!error_out->has_error());
}
void Run(JobDelegate* delegate) override {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
"wasm.ValidateFunctionsTask");
Zone zone(GetWasmEngine()->allocator(), ZONE_NAME);
do {
// Get the index of the next function to validate.
// {fetch_add} might overrun {after_last_function_} by a bit. Since the
// number of functions is limited to a value much smaller than the
// integer range, this is near impossible to happen.
static_assert(kV8MaxWasmFunctions < kMaxInt / 2);
int func_index;
do {
func_index = next_function_.fetch_add(1, std::memory_order_relaxed);
if (V8_UNLIKELY(func_index >= after_last_function_)) return;
DCHECK_LE(0, func_index);
} while ((filter_ && !filter_(func_index)) ||
module_->function_was_validated(func_index));
zone.Reset();
if (!ValidateFunction(func_index, &zone)) {
// No need to validate any more functions.
next_function_.store(after_last_function_, std::memory_order_relaxed);
return;
}
} while (!delegate->ShouldYield());
}
size_t GetMaxConcurrency(size_t /* worker_count */) const override {
int next_func = next_function_.load(std::memory_order_relaxed);
return std::max(0, after_last_function_ - next_func);
}
private:
bool ValidateFunction(int func_index, Zone* zone) {
WasmFeatures unused_detected_features;
const WasmFunction& function = module_->functions[func_index];
DCHECK_LT(0, function.code.offset());
bool is_shared = module_->types[function.sig_index].is_shared;
FunctionBody body{function.sig, function.code.offset(),
wire_bytes_.begin() + function.code.offset(),
wire_bytes_.begin() + function.code.end_offset(),
is_shared};
DecodeResult validation_result = ValidateFunctionBody(
zone, enabled_features_, module_, &unused_detected_features, body);
if (V8_UNLIKELY(validation_result.failed())) {
SetError(func_index, std::move(validation_result).error());
return false;
}
module_->set_function_validated(func_index);
return true;
}
// Set the error from the argument if it's earlier than the error we already
// have (or if we have none yet). Thread-safe.
void SetError(int func_index, WasmError error) {
base::MutexGuard mutex_guard{&set_error_mutex_};
if (error_out_->has_error() && error_out_->offset() <= error.offset()) {
return;
}
*error_out_ = GetWasmErrorWithName(wire_bytes_, func_index, module_, error);
}
const base::Vector<const uint8_t> wire_bytes_;
const WasmModule* const module_;
const WasmFeatures enabled_features_;
const std::function<bool(int)> filter_;
std::atomic<int> next_function_;
const int after_last_function_;
base::Mutex set_error_mutex_;
WasmError* const error_out_;
};
} // namespace
WasmError ValidateFunctions(const WasmModule* module,
WasmFeatures enabled_features,
base::Vector<const uint8_t> wire_bytes,
std::function<bool(int)> filter) {
TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
"wasm.ValidateFunctions", "num_declared_functions",
module->num_declared_functions, "has_filter", filter != nullptr);
DCHECK_EQ(kWasmOrigin, module->origin);
class NeverYieldDelegate final : public JobDelegate {
public:
bool ShouldYield() override { return false; }
bool IsJoiningThread() const override { UNIMPLEMENTED(); }
void NotifyConcurrencyIncrease() override { UNIMPLEMENTED(); }
uint8_t GetTaskId() override { UNIMPLEMENTED(); }
};
// Create a {ValidateFunctionsTask} to validate all functions. The earliest
// error found will be set on this decoder.
WasmError validation_error;
std::unique_ptr<JobTask> validate_job =
std::make_unique<ValidateFunctionsTask>(
wire_bytes, module, enabled_features, std::move(filter),
&validation_error);
if (v8_flags.single_threaded) {
// In single-threaded mode, run the {ValidateFunctionsTask} synchronously.
NeverYieldDelegate delegate;
validate_job->Run(&delegate);
} else {
// Spawn the task and join it.
std::unique_ptr<JobHandle> job_handle = V8::GetCurrentPlatform()->CreateJob(
TaskPriority::kUserVisible, std::move(validate_job));
job_handle->Join();
}
return validation_error;
}
WasmError GetWasmErrorWithName(base::Vector<const uint8_t> wire_bytes,
int func_index, const WasmModule* module,
WasmError error) {
WasmName name = ModuleWireBytes{wire_bytes}.GetNameOrNull(func_index, module);
if (name.begin() == nullptr) {
return WasmError(error.offset(), "Compiling function #%d failed: %s",
func_index, error.message().c_str());
} else {
TruncatedUserString<> truncated_name(name);
return WasmError(error.offset(),
"Compiling function #%d:\"%.*s\" failed: %s", func_index,
truncated_name.length(), truncated_name.start(),
error.message().c_str());
}
}
DecodedNameSection::DecodedNameSection(base::Vector<const uint8_t> wire_bytes,
WireBytesRef name_section) {
if (name_section.is_empty()) return; // No name section.
Decoder decoder(wire_bytes.begin() + name_section.offset(),
wire_bytes.begin() + name_section.end_offset(),
name_section.offset());
while (decoder.ok() && decoder.more()) {
uint8_t name_type = decoder.consume_u8("name type");
if (name_type & 0x80) break; // no varuint7
uint32_t name_payload_len = decoder.consume_u32v("name payload length");
if (!decoder.checkAvailable(name_payload_len)) break;
switch (name_type) {
case kModuleCode:
case kFunctionCode:
// Already handled elsewhere.
decoder.consume_bytes(name_payload_len);
break;
case kLocalCode:
static_assert(kV8MaxWasmFunctions <= IndirectNameMap::kMaxKey);
static_assert(kV8MaxWasmFunctionLocals <= NameMap::kMaxKey);
DecodeIndirectNameMap(local_names_, decoder, name_payload_len);
break;
case kLabelCode:
static_assert(kV8MaxWasmFunctions <= IndirectNameMap::kMaxKey);
static_assert(kV8MaxWasmFunctionSize <= NameMap::kMaxKey);
DecodeIndirectNameMap(label_names_, decoder, name_payload_len);
break;
case kTypeCode:
static_assert(kV8MaxWasmTypes <= NameMap::kMaxKey);
DecodeNameMap(type_names_, decoder, name_payload_len);
break;
case kTableCode:
static_assert(kV8MaxWasmTables <= NameMap::kMaxKey);
DecodeNameMap(table_names_, decoder, name_payload_len);
break;
case kMemoryCode:
static_assert(kV8MaxWasmMemories <= NameMap::kMaxKey);
DecodeNameMap(memory_names_, decoder, name_payload_len);
break;
case kGlobalCode:
static_assert(kV8MaxWasmGlobals <= NameMap::kMaxKey);
DecodeNameMap(global_names_, decoder, name_payload_len);
break;
case kElementSegmentCode:
static_assert(kV8MaxWasmTableInitEntries <= NameMap::kMaxKey);
DecodeNameMap(element_segment_names_, decoder, name_payload_len);
break;
case kDataSegmentCode:
static_assert(kV8MaxWasmDataSegments <= NameMap::kMaxKey);
DecodeNameMap(data_segment_names_, decoder, name_payload_len);
break;
case kFieldCode:
static_assert(kV8MaxWasmTypes <= IndirectNameMap::kMaxKey);
static_assert(kV8MaxWasmStructFields <= NameMap::kMaxKey);
DecodeIndirectNameMap(field_names_, decoder, name_payload_len);
break;
case kTagCode:
static_assert(kV8MaxWasmTags <= NameMap::kMaxKey);
DecodeNameMap(tag_names_, decoder, name_payload_len);
break;
}
}
}
#undef TRACE
} // namespace wasm
} // namespace internal
} // namespace v8