fe09d3300e4d34523e445c0a91e98044b806fe07
[WebKit.git] / Source / JavaScriptCore / wasm / WasmBBQPlan.cpp
1 /*
2  * Copyright (C) 2016-2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "WasmBBQPlan.h"
28
29 #if ENABLE(WEBASSEMBLY)
30
31 #include "B3Compilation.h"
32 #include "WasmAirIRGenerator.h"
33 #include "WasmB3IRGenerator.h"
34 #include "WasmBinding.h"
35 #include "WasmCallee.h"
36 #include "WasmCallingConvention.h"
37 #include "WasmFaultSignalHandler.h"
38 #include "WasmMemory.h"
39 #include "WasmModuleParser.h"
40 #include "WasmSignatureInlines.h"
41 #include "WasmTierUpCount.h"
42 #include "WasmValidate.h"
43 #include <wtf/DataLog.h>
44 #include <wtf/Locker.h>
45 #include <wtf/MonotonicTime.h>
46 #include <wtf/StdLibExtras.h>
47 #include <wtf/SystemTracing.h>
48 #include <wtf/text/StringBuilder.h>
49
50 namespace JSC { namespace Wasm {
51
52 namespace WasmBBQPlanInternal {
53 static const bool verbose = false;
54 }
55
56 BBQPlan::BBQPlan(Context* context, Ref<ModuleInformation> info, AsyncWork work, CompletionTask&& task, CreateEmbedderWrapper&& createEmbedderWrapper, ThrowWasmException throwWasmException)
57     : Base(context, WTFMove(info), WTFMove(task), WTFMove(createEmbedderWrapper), throwWasmException)
58     , m_state(State::Validated)
59     , m_asyncWork(work)
60 {
61 }
62
63 BBQPlan::BBQPlan(Context* context, Vector<uint8_t>&& source, AsyncWork work, CompletionTask&& task, CreateEmbedderWrapper&& createEmbedderWrapper, ThrowWasmException throwWasmException)
64     : Base(context, ModuleInformation::create(), WTFMove(task), WTFMove(createEmbedderWrapper), throwWasmException)
65     , m_source(WTFMove(source))
66     , m_state(State::Initial)
67     , m_asyncWork(work)
68 {
69 }
70
71 BBQPlan::BBQPlan(Context* context, AsyncWork work, CompletionTask&& task)
72     : Base(context, WTFMove(task))
73     , m_state(State::Initial)
74     , m_asyncWork(work)
75 {
76 }
77
78 const char* BBQPlan::stateString(State state)
79 {
80     switch (state) {
81     case State::Initial: return "Initial";
82     case State::Validated: return "Validated";
83     case State::Prepared: return "Prepared";
84     case State::Compiled: return "Compiled";
85     case State::Completed: return "Completed";
86     }
87     RELEASE_ASSERT_NOT_REACHED();
88 }
89
90 void BBQPlan::moveToState(State state)
91 {
92     ASSERT(state >= m_state);
93     dataLogLnIf(WasmBBQPlanInternal::verbose && state != m_state, "moving to state: ", stateString(state), " from state: ", stateString(m_state));
94     m_state = state;
95 }
96
97 bool BBQPlan::parseAndValidateModule(const uint8_t* source, size_t sourceLength)
98 {
99     if (m_state != State::Initial)
100         return true;
101
102     dataLogLnIf(WasmBBQPlanInternal::verbose, "starting validation");
103     MonotonicTime startTime;
104     if (WasmBBQPlanInternal::verbose || Options::reportCompileTimes())
105         startTime = MonotonicTime::now();
106
107     {
108         ModuleParser moduleParser(source, sourceLength, m_moduleInformation);
109         auto parseResult = moduleParser.parse();
110         if (!parseResult) {
111             Base::fail(holdLock(m_lock), WTFMove(parseResult.error()));
112             return false;
113         }
114     }
115
116     const auto& functions = m_moduleInformation->functions;
117     for (unsigned functionIndex = 0; functionIndex < functions.size(); ++functionIndex) {
118         const auto& function = functions[functionIndex];
119         dataLogLnIf(WasmBBQPlanInternal::verbose, "Processing function starting at: ", function.start, " and ending at: ", function.end);
120         size_t functionLength = function.end - function.start;
121         ASSERT(functionLength <= sourceLength);
122         ASSERT(functionLength == function.data.size());
123         SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex];
124         const Signature& signature = SignatureInformation::get(signatureIndex);
125
126         auto validationResult = validateFunction(function.data.data(), function.data.size(), signature, m_moduleInformation.get());
127         if (!validationResult) {
128             if (WasmBBQPlanInternal::verbose) {
129                 for (unsigned i = 0; i < functionLength; ++i)
130                     dataLog(RawPointer(reinterpret_cast<void*>(function.data[i])), ", ");
131                 dataLogLn();
132             }
133             Base::fail(holdLock(m_lock), makeString(validationResult.error(), ", in function at index ", String::number(functionIndex))); // FIXME make this an Expected.
134             return false;
135         }
136     }
137
138     if (WasmBBQPlanInternal::verbose || Options::reportCompileTimes())
139         dataLogLn("Took ", (MonotonicTime::now() - startTime).microseconds(), " us to validate module");
140
141     moveToState(State::Validated);
142     if (m_asyncWork == Validation)
143         complete(holdLock(m_lock));
144     return true;
145 }
146
147 void BBQPlan::prepare()
148 {
149     ASSERT(m_state == State::Validated);
150     dataLogLnIf(WasmBBQPlanInternal::verbose, "Starting preparation");
151
152     auto tryReserveCapacity = [this] (auto& vector, size_t size, const char* what) {
153         if (UNLIKELY(!vector.tryReserveCapacity(size))) {
154             StringBuilder builder;
155             builder.appendLiteral("Failed allocating enough space for ");
156             builder.appendNumber(size);
157             builder.append(what);
158             fail(holdLock(m_lock), builder.toString());
159             return false;
160         }
161         return true;
162     };
163
164     const auto& functions = m_moduleInformation->functions;
165     if (!tryReserveCapacity(m_wasmToWasmExitStubs, m_moduleInformation->importFunctionSignatureIndices.size(), " WebAssembly to JavaScript stubs")
166         || !tryReserveCapacity(m_unlinkedWasmToWasmCalls, functions.size(), " unlinked WebAssembly to WebAssembly calls")
167         || !tryReserveCapacity(m_wasmInternalFunctions, functions.size(), " WebAssembly functions")
168         || !tryReserveCapacity(m_compilationContexts, functions.size(), " compilation contexts")
169         || !tryReserveCapacity(m_tierUpCounts, functions.size(), " tier-up counts"))
170         return;
171
172     m_unlinkedWasmToWasmCalls.resize(functions.size());
173     m_wasmInternalFunctions.resize(functions.size());
174     m_compilationContexts.resize(functions.size());
175     m_tierUpCounts.resize(functions.size());
176
177     for (unsigned importIndex = 0; importIndex < m_moduleInformation->imports.size(); ++importIndex) {
178         Import* import = &m_moduleInformation->imports[importIndex];
179         if (import->kind != ExternalKind::Function)
180             continue;
181         unsigned importFunctionIndex = m_wasmToWasmExitStubs.size();
182         dataLogLnIf(WasmBBQPlanInternal::verbose, "Processing import function number ", importFunctionIndex, ": ", makeString(import->module), ": ", makeString(import->field));
183         auto binding = wasmToWasm(importFunctionIndex);
184         if (UNLIKELY(!binding)) {
185             switch (binding.error()) {
186             case BindingFailure::OutOfMemory:
187                 return fail(holdLock(m_lock), makeString("Out of executable memory at import ", String::number(importIndex)));
188             }
189             RELEASE_ASSERT_NOT_REACHED();
190         }
191         m_wasmToWasmExitStubs.uncheckedAppend(binding.value());
192     }
193
194     const uint32_t importFunctionCount = m_moduleInformation->importFunctionCount();
195     for (const auto& exp : m_moduleInformation->exports) {
196         if (exp.kindIndex >= importFunctionCount)
197             m_exportedFunctionIndices.add(exp.kindIndex - importFunctionCount);
198     }
199
200     for (const auto& element : m_moduleInformation->elements) {
201         for (const uint32_t elementIndex : element.functionIndices) {
202             if (elementIndex >= importFunctionCount)
203                 m_exportedFunctionIndices.add(elementIndex - importFunctionCount);
204         }
205     }
206
207     if (m_moduleInformation->startFunctionIndexSpace && m_moduleInformation->startFunctionIndexSpace >= importFunctionCount)
208         m_exportedFunctionIndices.add(*m_moduleInformation->startFunctionIndexSpace - importFunctionCount);
209
210     moveToState(State::Prepared);
211 }
212
213 // We don't have a semaphore class... and this does kinda interesting things.
214 class BBQPlan::ThreadCountHolder {
215 public:
216     ThreadCountHolder(BBQPlan& plan)
217         : m_plan(plan)
218     {
219         LockHolder locker(m_plan.m_lock);
220         m_plan.m_numberOfActiveThreads++;
221     }
222
223     ~ThreadCountHolder()
224     {
225         LockHolder locker(m_plan.m_lock);
226         m_plan.m_numberOfActiveThreads--;
227
228         if (!m_plan.m_numberOfActiveThreads && !m_plan.hasWork())
229             m_plan.complete(locker);
230     }
231
232     BBQPlan& m_plan;
233 };
234
235 void BBQPlan::compileFunctions(CompilationEffort effort)
236 {
237     ASSERT(m_state >= State::Prepared);
238     dataLogLnIf(WasmBBQPlanInternal::verbose, "Starting compilation");
239
240     if (!hasWork())
241         return;
242
243     Optional<TraceScope> traceScope;
244     if (Options::useTracePoints())
245         traceScope.emplace(WebAssemblyCompileStart, WebAssemblyCompileEnd);
246     ThreadCountHolder holder(*this);
247
248     size_t bytesCompiled = 0;
249     const auto& functions = m_moduleInformation->functions;
250     while (true) {
251         if (effort == Partial && bytesCompiled >= Options::webAssemblyPartialCompileLimit())
252             return;
253
254         uint32_t functionIndex;
255         {
256             auto locker = holdLock(m_lock);
257             if (m_currentIndex >= functions.size()) {
258                 if (hasWork())
259                     moveToState(State::Compiled);
260                 return;
261             }
262             functionIndex = m_currentIndex;
263             ++m_currentIndex;
264         }
265
266         const auto& function = functions[functionIndex];
267         SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex];
268         const Signature& signature = SignatureInformation::get(signatureIndex);
269         unsigned functionIndexSpace = m_wasmToWasmExitStubs.size() + functionIndex;
270         ASSERT_UNUSED(functionIndexSpace, m_moduleInformation->signatureIndexFromFunctionIndexSpace(functionIndexSpace) == signatureIndex);
271         ASSERT(validateFunction(function.data.data(), function.data.size(), signature, m_moduleInformation.get()));
272
273         m_unlinkedWasmToWasmCalls[functionIndex] = Vector<UnlinkedWasmToWasmCall>();
274         TierUpCount* tierUp = Options::useBBQTierUpChecks() ? &m_tierUpCounts[functionIndex] : nullptr;
275         Expected<std::unique_ptr<InternalFunction>, String> parseAndCompileResult;
276         if (Options::wasmBBQUsesAir())
277             parseAndCompileResult = parseAndCompileAir(m_compilationContexts[functionIndex], function.data.data(), function.data.size(), signature, m_unlinkedWasmToWasmCalls[functionIndex], m_moduleInformation.get(), m_mode, functionIndex, tierUp, m_throwWasmException);
278         else
279             parseAndCompileResult = parseAndCompile(m_compilationContexts[functionIndex], function.data.data(), function.data.size(), signature, m_unlinkedWasmToWasmCalls[functionIndex], m_moduleInformation.get(), m_mode, CompilationMode::BBQMode, functionIndex, tierUp, m_throwWasmException);
280
281         if (UNLIKELY(!parseAndCompileResult)) {
282             auto locker = holdLock(m_lock);
283             if (!m_errorMessage) {
284                 // Multiple compiles could fail simultaneously. We arbitrarily choose the first.
285                 fail(locker, makeString(parseAndCompileResult.error(), ", in function at index ", String::number(functionIndex))); // FIXME make this an Expected.
286             }
287             m_currentIndex = functions.size();
288             return;
289         }
290
291         m_wasmInternalFunctions[functionIndex] = WTFMove(*parseAndCompileResult);
292
293         if (m_exportedFunctionIndices.contains(functionIndex)) {
294             auto locker = holdLock(m_lock);
295             auto result = m_embedderToWasmInternalFunctions.add(functionIndex, m_createEmbedderWrapper(m_compilationContexts[functionIndex], signature, &m_unlinkedWasmToWasmCalls[functionIndex], m_moduleInformation.get(), m_mode, functionIndex));
296             ASSERT_UNUSED(result, result.isNewEntry);
297         }
298
299         bytesCompiled += function.data.size();
300     }
301 }
302
303 void BBQPlan::complete(const AbstractLocker& locker)
304 {
305     ASSERT(m_state != State::Compiled || m_currentIndex >= m_moduleInformation->functions.size());
306     dataLogLnIf(WasmBBQPlanInternal::verbose, "Starting Completion");
307
308     if (!failed() && m_state == State::Compiled) {
309         for (uint32_t functionIndex = 0; functionIndex < m_moduleInformation->functions.size(); functionIndex++) {
310             CompilationContext& context = m_compilationContexts[functionIndex];
311             SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex];
312             const Signature& signature = SignatureInformation::get(signatureIndex);
313             {
314                 LinkBuffer linkBuffer(*context.wasmEntrypointJIT, nullptr, JITCompilationCanFail);
315                 if (UNLIKELY(linkBuffer.didFailToAllocate())) {
316                     Base::fail(locker, makeString("Out of executable memory in function at index ", String::number(functionIndex)));
317                     return;
318                 }
319
320                 m_wasmInternalFunctions[functionIndex]->entrypoint.compilation = std::make_unique<B3::Compilation>(
321                     FINALIZE_CODE(linkBuffer, B3CompilationPtrTag, "WebAssembly BBQ function[%i] %s", functionIndex, signature.toString().ascii().data()),
322                     WTFMove(context.wasmEntrypointByproducts));
323             }
324
325             if (auto embedderToWasmInternalFunction = m_embedderToWasmInternalFunctions.get(functionIndex)) {
326                 LinkBuffer linkBuffer(*context.embedderEntrypointJIT, nullptr, JITCompilationCanFail);
327                 if (UNLIKELY(linkBuffer.didFailToAllocate())) {
328                     Base::fail(locker, makeString("Out of executable memory in function entrypoint at index ", String::number(functionIndex)));
329                     return;
330                 }
331
332                 embedderToWasmInternalFunction->entrypoint.compilation = std::make_unique<B3::Compilation>(
333                     FINALIZE_CODE(linkBuffer, B3CompilationPtrTag, "Embedder->WebAssembly entrypoint[%i] %s", functionIndex, signature.toString().ascii().data()),
334                     WTFMove(context.embedderEntrypointByproducts));
335             }
336         }
337
338         for (auto& unlinked : m_unlinkedWasmToWasmCalls) {
339             for (auto& call : unlinked) {
340                 MacroAssemblerCodePtr<WasmEntryPtrTag> executableAddress;
341                 if (m_moduleInformation->isImportedFunctionFromFunctionIndexSpace(call.functionIndexSpace)) {
342                     // FIXME imports could have been linked in B3, instead of generating a patchpoint. This condition should be replaced by a RELEASE_ASSERT. https://bugs.webkit.org/show_bug.cgi?id=166462
343                     executableAddress = m_wasmToWasmExitStubs.at(call.functionIndexSpace).code();
344                 } else
345                     executableAddress = m_wasmInternalFunctions.at(call.functionIndexSpace - m_moduleInformation->importFunctionCount())->entrypoint.compilation->code().retagged<WasmEntryPtrTag>();
346                 MacroAssembler::repatchNearCall(call.callLocation, CodeLocationLabel<WasmEntryPtrTag>(executableAddress));
347             }
348         }
349     }
350     
351     if (!isComplete()) {
352         moveToState(State::Completed);
353         runCompletionTasks(locker);
354     }
355 }
356
357 void BBQPlan::work(CompilationEffort effort)
358 {
359     switch (m_state) {
360     case State::Initial:
361         parseAndValidateModule(m_source.data(), m_source.size());
362         if (!hasWork()) {
363             ASSERT(isComplete());
364             return;
365         }
366         FALLTHROUGH;
367     case State::Validated:
368         prepare();
369         return;
370     case State::Prepared:
371         compileFunctions(effort);
372         return;
373     default:
374         break;
375     }
376     return;
377 }
378
379 } } // namespace JSC::Wasm
380
381 #endif // ENABLE(WEBASSEMBLY)