1 // Copyright 2018 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/wasm/function-compiler.h"
6 
7 #include "src/codegen/compiler.h"
8 #include "src/codegen/macro-assembler-inl.h"
9 #include "src/codegen/optimized-compilation-info.h"
10 #include "src/compiler/wasm-compiler.h"
11 #include "src/diagnostics/code-tracer.h"
12 #include "src/logging/counters.h"
13 #include "src/logging/log.h"
14 #include "src/utils/ostreams.h"
15 #include "src/wasm/baseline/liftoff-compiler.h"
16 #include "src/wasm/wasm-code-manager.h"
17 
18 namespace v8 {
19 namespace internal {
20 namespace wasm {
21 
22 namespace {
23 
24 class WasmInstructionBufferImpl {
25  public:
26   class View : public AssemblerBuffer {
27    public:
View(Vector<uint8_t> buffer,WasmInstructionBufferImpl * holder)28     View(Vector<uint8_t> buffer, WasmInstructionBufferImpl* holder)
29         : buffer_(buffer), holder_(holder) {}
30 
~View()31     ~View() override {
32       if (buffer_.begin() == holder_->old_buffer_.start()) {
33         DCHECK_EQ(buffer_.size(), holder_->old_buffer_.size());
34         holder_->old_buffer_ = {};
35       }
36     }
37 
start() const38     byte* start() const override { return buffer_.begin(); }
39 
size() const40     int size() const override { return static_cast<int>(buffer_.size()); }
41 
Grow(int new_size)42     std::unique_ptr<AssemblerBuffer> Grow(int new_size) override {
43       // If we grow, we must be the current buffer of {holder_}.
44       DCHECK_EQ(buffer_.begin(), holder_->buffer_.start());
45       DCHECK_EQ(buffer_.size(), holder_->buffer_.size());
46       DCHECK_NULL(holder_->old_buffer_);
47 
48       DCHECK_LT(size(), new_size);
49 
50       holder_->old_buffer_ = std::move(holder_->buffer_);
51       holder_->buffer_ = OwnedVector<uint8_t>::NewForOverwrite(new_size);
52       return std::make_unique<View>(holder_->buffer_.as_vector(), holder_);
53     }
54 
55    private:
56     const Vector<uint8_t> buffer_;
57     WasmInstructionBufferImpl* const holder_;
58   };
59 
WasmInstructionBufferImpl(size_t size)60   explicit WasmInstructionBufferImpl(size_t size)
61       : buffer_(OwnedVector<uint8_t>::NewForOverwrite(size)) {}
62 
CreateView()63   std::unique_ptr<AssemblerBuffer> CreateView() {
64     DCHECK_NOT_NULL(buffer_);
65     return std::make_unique<View>(buffer_.as_vector(), this);
66   }
67 
ReleaseBuffer()68   std::unique_ptr<uint8_t[]> ReleaseBuffer() {
69     DCHECK_NULL(old_buffer_);
70     DCHECK_NOT_NULL(buffer_);
71     return buffer_.ReleaseData();
72   }
73 
released() const74   bool released() const { return buffer_ == nullptr; }
75 
76  private:
77   // The current buffer used to emit code.
78   OwnedVector<uint8_t> buffer_;
79 
80   // While the buffer is grown, we need to temporarily also keep the old buffer
81   // alive.
82   OwnedVector<uint8_t> old_buffer_;
83 };
84 
Impl(WasmInstructionBuffer * buf)85 WasmInstructionBufferImpl* Impl(WasmInstructionBuffer* buf) {
86   return reinterpret_cast<WasmInstructionBufferImpl*>(buf);
87 }
88 
89 }  // namespace
90 
91 // PIMPL interface WasmInstructionBuffer for WasmInstBufferImpl
~WasmInstructionBuffer()92 WasmInstructionBuffer::~WasmInstructionBuffer() {
93   Impl(this)->~WasmInstructionBufferImpl();
94 }
95 
CreateView()96 std::unique_ptr<AssemblerBuffer> WasmInstructionBuffer::CreateView() {
97   return Impl(this)->CreateView();
98 }
99 
ReleaseBuffer()100 std::unique_ptr<uint8_t[]> WasmInstructionBuffer::ReleaseBuffer() {
101   return Impl(this)->ReleaseBuffer();
102 }
103 
104 // static
New(size_t size)105 std::unique_ptr<WasmInstructionBuffer> WasmInstructionBuffer::New(size_t size) {
106   return std::unique_ptr<WasmInstructionBuffer>{
107       reinterpret_cast<WasmInstructionBuffer*>(new WasmInstructionBufferImpl(
108           std::max(size_t{AssemblerBase::kMinimalBufferSize}, size)))};
109 }
110 // End of PIMPL interface WasmInstructionBuffer for WasmInstBufferImpl
111 
112 // static
GetBaselineExecutionTier(const WasmModule * module)113 ExecutionTier WasmCompilationUnit::GetBaselineExecutionTier(
114     const WasmModule* module) {
115   // Liftoff does not support the special asm.js opcodes, thus always compile
116   // asm.js modules with TurboFan.
117   if (is_asmjs_module(module)) return ExecutionTier::kTurbofan;
118   return FLAG_liftoff ? ExecutionTier::kLiftoff : ExecutionTier::kTurbofan;
119 }
120 
ExecuteCompilation(WasmEngine * engine,CompilationEnv * env,const std::shared_ptr<WireBytesStorage> & wire_bytes_storage,Counters * counters,WasmFeatures * detected)121 WasmCompilationResult WasmCompilationUnit::ExecuteCompilation(
122     WasmEngine* engine, CompilationEnv* env,
123     const std::shared_ptr<WireBytesStorage>& wire_bytes_storage,
124     Counters* counters, WasmFeatures* detected) {
125   WasmCompilationResult result;
126   if (func_index_ < static_cast<int>(env->module->num_imported_functions)) {
127     result = ExecuteImportWrapperCompilation(engine, env);
128   } else {
129     result = ExecuteFunctionCompilation(engine, env, wire_bytes_storage,
130                                         counters, detected);
131   }
132 
133   if (result.succeeded() && counters) {
134     counters->wasm_generated_code_size()->Increment(
135         result.code_desc.instr_size);
136     counters->wasm_reloc_size()->Increment(result.code_desc.reloc_size);
137   }
138 
139   result.func_index = func_index_;
140   result.requested_tier = tier_;
141 
142   return result;
143 }
144 
ExecuteImportWrapperCompilation(WasmEngine * engine,CompilationEnv * env)145 WasmCompilationResult WasmCompilationUnit::ExecuteImportWrapperCompilation(
146     WasmEngine* engine, CompilationEnv* env) {
147   const FunctionSig* sig = env->module->functions[func_index_].sig;
148   // Assume the wrapper is going to be a JS function with matching arity at
149   // instantiation time.
150   auto kind = compiler::kDefaultImportCallKind;
151   bool source_positions = is_asmjs_module(env->module);
152   WasmCompilationResult result = compiler::CompileWasmImportCallWrapper(
153       engine, env, kind, sig, source_positions,
154       static_cast<int>(sig->parameter_count()));
155   return result;
156 }
157 
ExecuteFunctionCompilation(WasmEngine * wasm_engine,CompilationEnv * env,const std::shared_ptr<WireBytesStorage> & wire_bytes_storage,Counters * counters,WasmFeatures * detected)158 WasmCompilationResult WasmCompilationUnit::ExecuteFunctionCompilation(
159     WasmEngine* wasm_engine, CompilationEnv* env,
160     const std::shared_ptr<WireBytesStorage>& wire_bytes_storage,
161     Counters* counters, WasmFeatures* detected) {
162   auto* func = &env->module->functions[func_index_];
163   Vector<const uint8_t> code = wire_bytes_storage->GetCode(func->code);
164   wasm::FunctionBody func_body{func->sig, func->code.offset(), code.begin(),
165                                code.end()};
166 
167   base::Optional<TimedHistogramScope> wasm_compile_function_time_scope;
168   if (counters) {
169     auto size_histogram = SELECT_WASM_COUNTER(counters, env->module->origin,
170                                               wasm, function_size_bytes);
171     size_histogram->AddSample(
172         static_cast<int>(func_body.end - func_body.start));
173     auto timed_histogram = SELECT_WASM_COUNTER(counters, env->module->origin,
174                                                wasm_compile, function_time);
175     wasm_compile_function_time_scope.emplace(timed_histogram);
176   }
177 
178   if (FLAG_trace_wasm_compiler) {
179     PrintF("Compiling wasm function %d with %s\n", func_index_,
180            ExecutionTierToString(tier_));
181   }
182 
183   WasmCompilationResult result;
184 
185   switch (tier_) {
186     case ExecutionTier::kNone:
187       UNREACHABLE();
188 
189     case ExecutionTier::kLiftoff:
190       // The --wasm-tier-mask-for-testing flag can force functions to be
191       // compiled with TurboFan, see documentation.
192       if (V8_LIKELY(FLAG_wasm_tier_mask_for_testing == 0) ||
193           func_index_ >= 32 ||
194           ((FLAG_wasm_tier_mask_for_testing & (1 << func_index_)) == 0)) {
195         result = ExecuteLiftoffCompilation(wasm_engine->allocator(), env,
196                                            func_body, func_index_,
197                                            for_debugging_, counters, detected);
198         if (result.succeeded()) break;
199       }
200 
201       // If Liftoff failed, fall back to turbofan.
202       // TODO(wasm): We could actually stop or remove the tiering unit for this
203       // function to avoid compiling it twice with TurboFan.
204       V8_FALLTHROUGH;
205 
206     case ExecutionTier::kTurbofan:
207       result = compiler::ExecuteTurbofanWasmCompilation(
208           wasm_engine, env, func_body, func_index_, counters, detected);
209       result.for_debugging = for_debugging_;
210       break;
211   }
212 
213   return result;
214 }
215 
216 namespace {
must_record_function_compilation(Isolate * isolate)217 bool must_record_function_compilation(Isolate* isolate) {
218   return isolate->logger()->is_listening_to_code_events() ||
219          isolate->is_profiling();
220 }
221 
222 PRINTF_FORMAT(3, 4)
RecordWasmHeapStubCompilation(Isolate * isolate,Handle<Code> code,const char * format,...)223 void RecordWasmHeapStubCompilation(Isolate* isolate, Handle<Code> code,
224                                    const char* format, ...) {
225   DCHECK(must_record_function_compilation(isolate));
226 
227   ScopedVector<char> buffer(128);
228   va_list arguments;
229   va_start(arguments, format);
230   int len = VSNPrintF(buffer, format, arguments);
231   CHECK_LT(0, len);
232   va_end(arguments);
233   Handle<String> name_str =
234       isolate->factory()->NewStringFromAsciiChecked(buffer.begin());
235   PROFILE(isolate, CodeCreateEvent(CodeEventListener::STUB_TAG,
236                                    Handle<AbstractCode>::cast(code), name_str));
237 }
238 }  // namespace
239 
240 // static
CompileWasmFunction(Isolate * isolate,NativeModule * native_module,WasmFeatures * detected,const WasmFunction * function,ExecutionTier tier)241 void WasmCompilationUnit::CompileWasmFunction(Isolate* isolate,
242                                               NativeModule* native_module,
243                                               WasmFeatures* detected,
244                                               const WasmFunction* function,
245                                               ExecutionTier tier) {
246   ModuleWireBytes wire_bytes(native_module->wire_bytes());
247   FunctionBody function_body{function->sig, function->code.offset(),
248                              wire_bytes.start() + function->code.offset(),
249                              wire_bytes.start() + function->code.end_offset()};
250 
251   DCHECK_LE(native_module->num_imported_functions(), function->func_index);
252   DCHECK_LT(function->func_index, native_module->num_functions());
253   WasmCompilationUnit unit(function->func_index, tier, kNoDebugging);
254   CompilationEnv env = native_module->CreateCompilationEnv();
255   WasmCompilationResult result = unit.ExecuteCompilation(
256       isolate->wasm_engine(), &env,
257       native_module->compilation_state()->GetWireBytesStorage(),
258       isolate->counters(), detected);
259   if (result.succeeded()) {
260     WasmCodeRefScope code_ref_scope;
261     native_module->PublishCode(
262         native_module->AddCompiledCode(std::move(result)));
263   } else {
264     native_module->compilation_state()->SetError();
265   }
266 }
267 
268 namespace {
UseGenericWrapper(const FunctionSig * sig)269 bool UseGenericWrapper(const FunctionSig* sig) {
270 #if V8_TARGET_ARCH_X64
271   if (sig->returns().size() > 1) {
272     return false;
273   }
274   if (sig->returns().size() == 1 &&
275       sig->GetReturn(0).kind() != ValueType::kI32 &&
276       sig->GetReturn(0).kind() != ValueType::kI64 &&
277       sig->GetReturn(0).kind() != ValueType::kF32 &&
278       sig->GetReturn(0).kind() != ValueType::kF64) {
279     return false;
280   }
281   for (ValueType type : sig->parameters()) {
282     if (type.kind() != ValueType::kI32 && type.kind() != ValueType::kI64 &&
283         type.kind() != ValueType::kF32 && type.kind() != ValueType::kF64) {
284       return false;
285     }
286   }
287   return FLAG_wasm_generic_wrapper;
288 #else
289   return false;
290 #endif
291 }
292 }  // namespace
293 
JSToWasmWrapperCompilationUnit(Isolate * isolate,WasmEngine * wasm_engine,const FunctionSig * sig,const WasmModule * module,bool is_import,const WasmFeatures & enabled_features,AllowGeneric allow_generic)294 JSToWasmWrapperCompilationUnit::JSToWasmWrapperCompilationUnit(
295     Isolate* isolate, WasmEngine* wasm_engine, const FunctionSig* sig,
296     const WasmModule* module, bool is_import,
297     const WasmFeatures& enabled_features, AllowGeneric allow_generic)
298     : is_import_(is_import),
299       sig_(sig),
300       use_generic_wrapper_(allow_generic && UseGenericWrapper(sig) &&
301                            !is_import),
302       job_(use_generic_wrapper_ ? nullptr
303                                 : compiler::NewJSToWasmCompilationJob(
304                                       isolate, wasm_engine, sig, module,
305                                       is_import, enabled_features)) {}
306 
307 JSToWasmWrapperCompilationUnit::~JSToWasmWrapperCompilationUnit() = default;
308 
Execute()309 void JSToWasmWrapperCompilationUnit::Execute() {
310   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
311                "wasm.CompileJSToWasmWrapper");
312   if (!use_generic_wrapper_) {
313     CompilationJob::Status status = job_->ExecuteJob(nullptr);
314     CHECK_EQ(status, CompilationJob::SUCCEEDED);
315   }
316 }
317 
Finalize(Isolate * isolate)318 Handle<Code> JSToWasmWrapperCompilationUnit::Finalize(Isolate* isolate) {
319   Handle<Code> code;
320   if (use_generic_wrapper_) {
321     code =
322         isolate->builtins()->builtin_handle(Builtins::kGenericJSToWasmWrapper);
323   } else {
324     CompilationJob::Status status = job_->FinalizeJob(isolate);
325     CHECK_EQ(status, CompilationJob::SUCCEEDED);
326     code = job_->compilation_info()->code();
327   }
328   if (!use_generic_wrapper_ && must_record_function_compilation(isolate)) {
329     RecordWasmHeapStubCompilation(
330         isolate, code, "%s", job_->compilation_info()->GetDebugName().get());
331   }
332   return code;
333 }
334 
335 // static
CompileJSToWasmWrapper(Isolate * isolate,const FunctionSig * sig,const WasmModule * module,bool is_import)336 Handle<Code> JSToWasmWrapperCompilationUnit::CompileJSToWasmWrapper(
337     Isolate* isolate, const FunctionSig* sig, const WasmModule* module,
338     bool is_import) {
339   // Run the compilation unit synchronously.
340   WasmFeatures enabled_features = WasmFeatures::FromIsolate(isolate);
341   JSToWasmWrapperCompilationUnit unit(isolate, isolate->wasm_engine(), sig,
342                                       module, is_import, enabled_features,
343                                       kAllowGeneric);
344   unit.Execute();
345   return unit.Finalize(isolate);
346 }
347 
348 // static
CompileSpecificJSToWasmWrapper(Isolate * isolate,const FunctionSig * sig,const WasmModule * module)349 Handle<Code> JSToWasmWrapperCompilationUnit::CompileSpecificJSToWasmWrapper(
350     Isolate* isolate, const FunctionSig* sig, const WasmModule* module) {
351   // Run the compilation unit synchronously.
352   const bool is_import = false;
353   WasmFeatures enabled_features = WasmFeatures::FromIsolate(isolate);
354   JSToWasmWrapperCompilationUnit unit(isolate, isolate->wasm_engine(), sig,
355                                       module, is_import, enabled_features,
356                                       kDontAllowGeneric);
357   unit.Execute();
358   return unit.Finalize(isolate);
359 }
360 
361 }  // namespace wasm
362 }  // namespace internal
363 }  // namespace v8
364