blob: 7ab794c25041dea9dc2863a05bcd9b8eca98839c [file] [log] [blame]
// Copyright 2018 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/function-compiler.h"
#include "src/codegen/compiler.h"
#include "src/codegen/macro-assembler-inl.h"
#include "src/codegen/optimized-compilation-info.h"
#include "src/compiler/wasm-compiler.h"
#include "src/diagnostics/code-tracer.h"
#include "src/logging/counters.h"
#include "src/logging/log.h"
#include "src/utils/ostreams.h"
#include "src/wasm/baseline/liftoff-compiler.h"
#include "src/wasm/wasm-code-manager.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace {
class WasmInstructionBufferImpl {
public:
class View : public AssemblerBuffer {
public:
View(Vector<uint8_t> buffer, WasmInstructionBufferImpl* holder)
: buffer_(buffer), holder_(holder) {}
~View() override {
if (buffer_.begin() == holder_->old_buffer_.start()) {
DCHECK_EQ(buffer_.size(), holder_->old_buffer_.size());
holder_->old_buffer_ = {};
}
}
byte* start() const override { return buffer_.begin(); }
int size() const override { return static_cast<int>(buffer_.size()); }
std::unique_ptr<AssemblerBuffer> Grow(int new_size) override {
// If we grow, we must be the current buffer of {holder_}.
DCHECK_EQ(buffer_.begin(), holder_->buffer_.start());
DCHECK_EQ(buffer_.size(), holder_->buffer_.size());
DCHECK_NULL(holder_->old_buffer_);
DCHECK_LT(size(), new_size);
holder_->old_buffer_ = std::move(holder_->buffer_);
holder_->buffer_ = OwnedVector<uint8_t>::New(new_size);
return base::make_unique<View>(holder_->buffer_.as_vector(), holder_);
}
private:
const Vector<uint8_t> buffer_;
WasmInstructionBufferImpl* const holder_;
};
std::unique_ptr<AssemblerBuffer> CreateView() {
DCHECK_NOT_NULL(buffer_);
return base::make_unique<View>(buffer_.as_vector(), this);
}
std::unique_ptr<uint8_t[]> ReleaseBuffer() {
DCHECK_NULL(old_buffer_);
DCHECK_NOT_NULL(buffer_);
return buffer_.ReleaseData();
}
bool released() const { return buffer_ == nullptr; }
private:
// The current buffer used to emit code.
OwnedVector<uint8_t> buffer_ =
OwnedVector<uint8_t>::New(AssemblerBase::kMinimalBufferSize);
// While the buffer is grown, we need to temporarily also keep the old buffer
// alive.
OwnedVector<uint8_t> old_buffer_;
};
WasmInstructionBufferImpl* Impl(WasmInstructionBuffer* buf) {
return reinterpret_cast<WasmInstructionBufferImpl*>(buf);
}
} // namespace
// PIMPL interface WasmInstructionBuffer for WasmInstBufferImpl
WasmInstructionBuffer::~WasmInstructionBuffer() {
Impl(this)->~WasmInstructionBufferImpl();
}
std::unique_ptr<AssemblerBuffer> WasmInstructionBuffer::CreateView() {
return Impl(this)->CreateView();
}
std::unique_ptr<uint8_t[]> WasmInstructionBuffer::ReleaseBuffer() {
return Impl(this)->ReleaseBuffer();
}
// static
std::unique_ptr<WasmInstructionBuffer> WasmInstructionBuffer::New() {
return std::unique_ptr<WasmInstructionBuffer>{
reinterpret_cast<WasmInstructionBuffer*>(
new WasmInstructionBufferImpl())};
}
// End of PIMPL interface WasmInstructionBuffer for WasmInstBufferImpl
// static
ExecutionTier WasmCompilationUnit::GetDefaultExecutionTier(
const WasmModule* module) {
// Liftoff does not support the special asm.js opcodes, thus always compile
// asm.js modules with TurboFan.
if (is_asmjs_module(module)) return ExecutionTier::kTurbofan;
if (FLAG_wasm_interpret_all) return ExecutionTier::kInterpreter;
return FLAG_liftoff ? ExecutionTier::kLiftoff : ExecutionTier::kTurbofan;
}
WasmCompilationResult WasmCompilationUnit::ExecuteCompilation(
WasmEngine* engine, CompilationEnv* env,
const std::shared_ptr<WireBytesStorage>& wire_bytes_storage,
Counters* counters, WasmFeatures* detected) {
WasmCompilationResult result;
if (func_index_ < static_cast<int>(env->module->num_imported_functions)) {
result = ExecuteImportWrapperCompilation(engine, env);
} else {
result = ExecuteFunctionCompilation(engine, env, wire_bytes_storage,
counters, detected);
}
if (result.succeeded()) {
counters->wasm_generated_code_size()->Increment(
result.code_desc.instr_size);
counters->wasm_reloc_size()->Increment(result.code_desc.reloc_size);
}
result.func_index = func_index_;
result.requested_tier = tier_;
return result;
}
WasmCompilationResult WasmCompilationUnit::ExecuteImportWrapperCompilation(
WasmEngine* engine, CompilationEnv* env) {
FunctionSig* sig = env->module->functions[func_index_].sig;
// Assume the wrapper is going to be a JS function with matching arity at
// instantiation time.
auto kind = compiler::kDefaultImportCallKind;
bool source_positions = is_asmjs_module(env->module);
WasmCompilationResult result = compiler::CompileWasmImportCallWrapper(
engine, env, kind, sig, source_positions);
return result;
}
WasmCompilationResult WasmCompilationUnit::ExecuteFunctionCompilation(
WasmEngine* wasm_engine, CompilationEnv* env,
const std::shared_ptr<WireBytesStorage>& wire_bytes_storage,
Counters* counters, WasmFeatures* detected) {
auto* func = &env->module->functions[func_index_];
Vector<const uint8_t> code = wire_bytes_storage->GetCode(func->code);
wasm::FunctionBody func_body{func->sig, func->code.offset(), code.begin(),
code.end()};
auto size_histogram = SELECT_WASM_COUNTER(counters, env->module->origin, wasm,
function_size_bytes);
size_histogram->AddSample(static_cast<int>(func_body.end - func_body.start));
auto timed_histogram = SELECT_WASM_COUNTER(counters, env->module->origin,
wasm_compile, function_time);
TimedHistogramScope wasm_compile_function_time_scope(timed_histogram);
if (FLAG_trace_wasm_compiler) {
PrintF("Compiling wasm function %d with %s\n\n", func_index_,
ExecutionTierToString(tier_));
}
WasmCompilationResult result;
// Cobalt does not support WASM.
#if !defined(DISABLE_WASM_STARBOARD)
switch (tier_) {
case ExecutionTier::kNone:
UNREACHABLE();
case ExecutionTier::kLiftoff:
// The --wasm-tier-mask-for-testing flag can force functions to be
// compiled with TurboFan, see documentation.
if (V8_LIKELY(FLAG_wasm_tier_mask_for_testing == 0) ||
func_index_ >= 32 ||
((FLAG_wasm_tier_mask_for_testing & (1 << func_index_)) == 0)) {
result =
ExecuteLiftoffCompilation(wasm_engine->allocator(), env, func_body,
func_index_, counters, detected);
if (result.succeeded()) break;
}
// If Liftoff failed, fall back to turbofan.
// TODO(wasm): We could actually stop or remove the tiering unit for this
// function to avoid compiling it twice with TurboFan.
V8_FALLTHROUGH;
case ExecutionTier::kTurbofan:
result = compiler::ExecuteTurbofanWasmCompilation(
wasm_engine, env, func_body, func_index_, counters, detected);
break;
case ExecutionTier::kInterpreter:
result = compiler::ExecuteInterpreterEntryCompilation(
wasm_engine, env, func_body, func_index_, counters, detected);
break;
}
#endif
return result;
}
namespace {
bool must_record_function_compilation(Isolate* isolate) {
return isolate->logger()->is_listening_to_code_events() ||
isolate->is_profiling();
}
PRINTF_FORMAT(3, 4)
void RecordWasmHeapStubCompilation(Isolate* isolate, Handle<Code> code,
const char* format, ...) {
DCHECK(must_record_function_compilation(isolate));
ScopedVector<char> buffer(128);
va_list arguments;
va_start(arguments, format);
int len = VSNPrintF(buffer, format, arguments);
CHECK_LT(0, len);
va_end(arguments);
Handle<String> name_str =
isolate->factory()->NewStringFromAsciiChecked(buffer.begin());
PROFILE(isolate, CodeCreateEvent(CodeEventListener::STUB_TAG,
AbstractCode::cast(*code), *name_str));
}
} // namespace
// static
void WasmCompilationUnit::CompileWasmFunction(Isolate* isolate,
NativeModule* native_module,
WasmFeatures* detected,
const WasmFunction* function,
ExecutionTier tier) {
ModuleWireBytes wire_bytes(native_module->wire_bytes());
FunctionBody function_body{function->sig, function->code.offset(),
wire_bytes.start() + function->code.offset(),
wire_bytes.start() + function->code.end_offset()};
DCHECK_LE(native_module->num_imported_functions(), function->func_index);
DCHECK_LT(function->func_index, native_module->num_functions());
WasmCompilationUnit unit(function->func_index, tier);
CompilationEnv env = native_module->CreateCompilationEnv();
WasmCompilationResult result = unit.ExecuteCompilation(
isolate->wasm_engine(), &env,
native_module->compilation_state()->GetWireBytesStorage(),
isolate->counters(), detected);
if (result.succeeded()) {
WasmCodeRefScope code_ref_scope;
native_module->AddCompiledCode(std::move(result));
} else {
native_module->compilation_state()->SetError();
}
}
JSToWasmWrapperCompilationUnit::JSToWasmWrapperCompilationUnit(Isolate* isolate,
FunctionSig* sig,
bool is_import)
: job_(compiler::NewJSToWasmCompilationJob(isolate, sig, is_import)) {}
JSToWasmWrapperCompilationUnit::~JSToWasmWrapperCompilationUnit() = default;
void JSToWasmWrapperCompilationUnit::Prepare(Isolate* isolate) {
CompilationJob::Status status = job_->PrepareJob(isolate);
CHECK_EQ(status, CompilationJob::SUCCEEDED);
}
void JSToWasmWrapperCompilationUnit::Execute() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"), "CompileJSToWasmWrapper");
DCHECK_EQ(job_->state(), CompilationJob::State::kReadyToExecute);
CompilationJob::Status status = job_->ExecuteJob();
CHECK_EQ(status, CompilationJob::SUCCEEDED);
}
Handle<Code> JSToWasmWrapperCompilationUnit::Finalize(Isolate* isolate) {
CompilationJob::Status status = job_->FinalizeJob(isolate);
CHECK_EQ(status, CompilationJob::SUCCEEDED);
Handle<Code> code = job_->compilation_info()->code();
if (must_record_function_compilation(isolate)) {
RecordWasmHeapStubCompilation(
isolate, code, "%s", job_->compilation_info()->GetDebugName().get());
}
return code;
}
// static
Handle<Code> JSToWasmWrapperCompilationUnit::CompileJSToWasmWrapper(
Isolate* isolate, FunctionSig* sig, bool is_import) {
// Run the compilation unit synchronously.
JSToWasmWrapperCompilationUnit unit(isolate, sig, is_import);
unit.Prepare(isolate);
unit.Execute();
return unit.Finalize(isolate);
}
} // namespace wasm
} // namespace internal
} // namespace v8