f672686d3f99a0d0d2e9c321f27bc0a1cbf5b2dd
[WebKit-https.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 "WasmB3IRGenerator.h"
33 #include "WasmBinding.h"
34 #include "WasmCallee.h"
35 #include "WasmCallingConvention.h"
36 #include "WasmFaultSignalHandler.h"
37 #include "WasmMemory.h"
38 #include "WasmModuleParser.h"
39 #include "WasmTierUpCount.h"
40 #include "WasmValidate.h"
41 #include <wtf/DataLog.h>
42 #include <wtf/Locker.h>
43 #include <wtf/MonotonicTime.h>
44 #include <wtf/StdLibExtras.h>
45 #include <wtf/SystemTracing.h>
46 #include <wtf/text/StringBuilder.h>
47
48 namespace JSC { namespace Wasm {
49
50 namespace WasmBBQPlanInternal {
51 static const bool verbose = false;
52 }
53
54 BBQPlan::BBQPlan(Context* context, Ref<ModuleInformation> info, AsyncWork work, CompletionTask&& task, CreateEmbedderWrapper&& createEmbedderWrapper, ThrowWasmException throwWasmException)
55     : Base(context, WTFMove(info), WTFMove(task), WTFMove(createEmbedderWrapper), throwWasmException)
56     , m_state(State::Validated)
57     , m_asyncWork(work)
58 {
59 }
60
61 BBQPlan::BBQPlan(Context* context, Vector<uint8_t>&& source, AsyncWork work, CompletionTask&& task, CreateEmbedderWrapper&& createEmbedderWrapper, ThrowWasmException throwWasmException)
62     : BBQPlan(context, adoptRef(*new ModuleInformation(WTFMove(source))), work, WTFMove(task), WTFMove(createEmbedderWrapper), throwWasmException)
63 {
64     m_state = State::Initial;
65 }
66
67 BBQPlan::BBQPlan(Context* context, const uint8_t* source, size_t sourceLength, AsyncWork work, CompletionTask&& task)
68     : Base(context, source, sourceLength, WTFMove(task))
69     , m_state(State::Initial)
70     , m_asyncWork(work)
71 {
72 }
73
74 const char* BBQPlan::stateString(State state)
75 {
76     switch (state) {
77     case State::Initial: return "Initial";
78     case State::Validated: return "Validated";
79     case State::Prepared: return "Prepared";
80     case State::Compiled: return "Compiled";
81     case State::Completed: return "Completed";
82     }
83     RELEASE_ASSERT_NOT_REACHED();
84 }
85
86 void BBQPlan::moveToState(State state)
87 {
88     ASSERT(state >= m_state);
89     dataLogLnIf(WasmBBQPlanInternal::verbose && state != m_state, "moving to state: ", stateString(state), " from state: ", stateString(m_state));
90     m_state = state;
91 }
92
93 bool BBQPlan::parseAndValidateModule()
94 {
95     if (m_state != State::Initial)
96         return true;
97
98     dataLogLnIf(WasmBBQPlanInternal::verbose, "starting validation");
99     MonotonicTime startTime;
100     if (WasmBBQPlanInternal::verbose || Options::reportCompileTimes())
101         startTime = MonotonicTime::now();
102
103     {
104         ModuleParser moduleParser(m_source, m_sourceLength, m_moduleInformation);
105         auto parseResult = moduleParser.parse();
106         if (!parseResult) {
107             Base::fail(holdLock(m_lock), WTFMove(parseResult.error()));
108             return false;
109         }
110     }
111
112     const auto& functionLocations = m_moduleInformation->functionLocationInBinary;
113     for (unsigned functionIndex = 0; functionIndex < functionLocations.size(); ++functionIndex) {
114         dataLogLnIf(WasmBBQPlanInternal::verbose, "Processing function starting at: ", functionLocations[functionIndex].start, " and ending at: ", functionLocations[functionIndex].end);
115         const uint8_t* functionStart = m_source + functionLocations[functionIndex].start;
116         size_t functionLength = functionLocations[functionIndex].end - functionLocations[functionIndex].start;
117         ASSERT(functionLength <= m_sourceLength);
118         SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex];
119         const Signature& signature = SignatureInformation::get(signatureIndex);
120
121         auto validationResult = validateFunction(functionStart, functionLength, signature, m_moduleInformation.get());
122         if (!validationResult) {
123             if (WasmBBQPlanInternal::verbose) {
124                 for (unsigned i = 0; i < functionLength; ++i)
125                     dataLog(RawPointer(reinterpret_cast<void*>(functionStart[i])), ", ");
126                 dataLogLn();
127             }
128             Base::fail(holdLock(m_lock), makeString(validationResult.error(), ", in function at index ", String::number(functionIndex))); // FIXME make this an Expected.
129             return false;
130         }
131     }
132
133     if (WasmBBQPlanInternal::verbose || Options::reportCompileTimes())
134         dataLogLn("Took ", (MonotonicTime::now() - startTime).microseconds(), " us to validate module");
135
136     moveToState(State::Validated);
137     if (m_asyncWork == Validation)
138         complete(holdLock(m_lock));
139     return true;
140 }
141
142 void BBQPlan::prepare()
143 {
144     ASSERT(m_state == State::Validated);
145     dataLogLnIf(WasmBBQPlanInternal::verbose, "Starting preparation");
146
147     auto tryReserveCapacity = [this] (auto& vector, size_t size, const char* what) {
148         if (UNLIKELY(!vector.tryReserveCapacity(size))) {
149             StringBuilder builder;
150             builder.appendLiteral("Failed allocating enough space for ");
151             builder.appendNumber(size);
152             builder.append(what);
153             fail(holdLock(m_lock), builder.toString());
154             return false;
155         }
156         return true;
157     };
158
159     const auto& functionLocations = m_moduleInformation->functionLocationInBinary;
160     if (!tryReserveCapacity(m_wasmToWasmExitStubs, m_moduleInformation->importFunctionSignatureIndices.size(), " WebAssembly to JavaScript stubs")
161         || !tryReserveCapacity(m_unlinkedWasmToWasmCalls, functionLocations.size(), " unlinked WebAssembly to WebAssembly calls")
162         || !tryReserveCapacity(m_wasmInternalFunctions, functionLocations.size(), " WebAssembly functions")
163         || !tryReserveCapacity(m_compilationContexts, functionLocations.size(), " compilation contexts")
164         || !tryReserveCapacity(m_tierUpCounts, functionLocations.size(), " tier-up counts"))
165         return;
166
167     m_unlinkedWasmToWasmCalls.resize(functionLocations.size());
168     m_wasmInternalFunctions.resize(functionLocations.size());
169     m_compilationContexts.resize(functionLocations.size());
170     m_tierUpCounts.resize(functionLocations.size());
171
172     for (unsigned importIndex = 0; importIndex < m_moduleInformation->imports.size(); ++importIndex) {
173         Import* import = &m_moduleInformation->imports[importIndex];
174         if (import->kind != ExternalKind::Function)
175             continue;
176         unsigned importFunctionIndex = m_wasmToWasmExitStubs.size();
177         dataLogLnIf(WasmBBQPlanInternal::verbose, "Processing import function number ", importFunctionIndex, ": ", makeString(import->module), ": ", makeString(import->field));
178         auto binding = wasmToWasm(importFunctionIndex);
179         if (UNLIKELY(!binding)) {
180             switch (binding.error()) {
181             case BindingFailure::OutOfMemory:
182                 return fail(holdLock(m_lock), makeString("Out of executable memory at import ", String::number(importIndex)));
183             }
184             RELEASE_ASSERT_NOT_REACHED();
185         }
186         m_wasmToWasmExitStubs.uncheckedAppend(binding.value());
187     }
188
189     const uint32_t importFunctionCount = m_moduleInformation->importFunctionCount();
190     for (const auto& exp : m_moduleInformation->exports) {
191         if (exp.kindIndex >= importFunctionCount)
192             m_exportedFunctionIndices.add(exp.kindIndex - importFunctionCount);
193     }
194
195     for (const auto& element : m_moduleInformation->elements) {
196         for (const uint32_t elementIndex : element.functionIndices) {
197             if (elementIndex >= importFunctionCount)
198                 m_exportedFunctionIndices.add(elementIndex - importFunctionCount);
199         }
200     }
201
202     if (m_moduleInformation->startFunctionIndexSpace && m_moduleInformation->startFunctionIndexSpace >= importFunctionCount)
203         m_exportedFunctionIndices.add(*m_moduleInformation->startFunctionIndexSpace - importFunctionCount);
204
205     moveToState(State::Prepared);
206 }
207
208 // We don't have a semaphore class... and this does kinda interesting things.
209 class BBQPlan::ThreadCountHolder {
210 public:
211     ThreadCountHolder(BBQPlan& plan)
212         : m_plan(plan)
213     {
214         LockHolder locker(m_plan.m_lock);
215         m_plan.m_numberOfActiveThreads++;
216     }
217
218     ~ThreadCountHolder()
219     {
220         LockHolder locker(m_plan.m_lock);
221         m_plan.m_numberOfActiveThreads--;
222
223         if (!m_plan.m_numberOfActiveThreads && !m_plan.hasWork())
224             m_plan.complete(locker);
225     }
226
227     BBQPlan& m_plan;
228 };
229
230 void BBQPlan::compileFunctions(CompilationEffort effort)
231 {
232     ASSERT(m_state >= State::Prepared);
233     dataLogLnIf(WasmBBQPlanInternal::verbose, "Starting compilation");
234
235     if (!hasWork())
236         return;
237
238     std::optional<TraceScope> traceScope;
239     if (Options::useTracePoints())
240         traceScope.emplace(WebAssemblyCompileStart, WebAssemblyCompileEnd);
241     ThreadCountHolder holder(*this);
242
243     size_t bytesCompiled = 0;
244     const auto& functionLocations = m_moduleInformation->functionLocationInBinary;
245     while (true) {
246         if (effort == Partial && bytesCompiled >= Options::webAssemblyPartialCompileLimit())
247             return;
248
249         uint32_t functionIndex;
250         {
251             auto locker = holdLock(m_lock);
252             if (m_currentIndex >= functionLocations.size()) {
253                 if (hasWork())
254                     moveToState(State::Compiled);
255                 return;
256             }
257             functionIndex = m_currentIndex;
258             ++m_currentIndex;
259         }
260
261         const uint8_t* functionStart = m_source + functionLocations[functionIndex].start;
262         size_t functionLength = functionLocations[functionIndex].end - functionLocations[functionIndex].start;
263         ASSERT(functionLength <= m_sourceLength);
264         SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex];
265         const Signature& signature = SignatureInformation::get(signatureIndex);
266         unsigned functionIndexSpace = m_wasmToWasmExitStubs.size() + functionIndex;
267         ASSERT_UNUSED(functionIndexSpace, m_moduleInformation->signatureIndexFromFunctionIndexSpace(functionIndexSpace) == signatureIndex);
268         ASSERT(validateFunction(functionStart, functionLength, signature, m_moduleInformation.get()));
269
270         m_unlinkedWasmToWasmCalls[functionIndex] = Vector<UnlinkedWasmToWasmCall>();
271         TierUpCount* tierUp = Options::useBBQTierUpChecks() ? &m_tierUpCounts[functionIndex] : nullptr;
272         auto parseAndCompileResult = parseAndCompile(m_compilationContexts[functionIndex], functionStart, functionLength, signature, m_unlinkedWasmToWasmCalls[functionIndex], m_moduleInformation.get(), m_mode, CompilationMode::BBQMode, functionIndex, tierUp, m_throwWasmException);
273
274         if (UNLIKELY(!parseAndCompileResult)) {
275             auto locker = holdLock(m_lock);
276             if (!m_errorMessage) {
277                 // Multiple compiles could fail simultaneously. We arbitrarily choose the first.
278                 fail(locker, makeString(parseAndCompileResult.error(), ", in function at index ", String::number(functionIndex))); // FIXME make this an Expected.
279             }
280             m_currentIndex = functionLocations.size();
281             return;
282         }
283
284         m_wasmInternalFunctions[functionIndex] = WTFMove(*parseAndCompileResult);
285
286         if (m_exportedFunctionIndices.contains(functionIndex)) {
287             auto locker = holdLock(m_lock);
288             auto result = m_embedderToWasmInternalFunctions.add(functionIndex, m_createEmbedderWrapper(m_compilationContexts[functionIndex], signature, &m_unlinkedWasmToWasmCalls[functionIndex], m_moduleInformation.get(), m_mode, functionIndex));
289             ASSERT_UNUSED(result, result.isNewEntry);
290         }
291
292         bytesCompiled += functionLength;
293     }
294 }
295
296 void BBQPlan::complete(const AbstractLocker& locker)
297 {
298     ASSERT(m_state != State::Compiled || m_currentIndex >= m_moduleInformation->functionLocationInBinary.size());
299     dataLogLnIf(WasmBBQPlanInternal::verbose, "Starting Completion");
300
301     if (!failed() && m_state == State::Compiled) {
302         for (uint32_t functionIndex = 0; functionIndex < m_moduleInformation->functionLocationInBinary.size(); functionIndex++) {
303             CompilationContext& context = m_compilationContexts[functionIndex];
304             SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex];
305             const Signature& signature = SignatureInformation::get(signatureIndex);
306             {
307                 LinkBuffer linkBuffer(*context.wasmEntrypointJIT, nullptr, JITCompilationCanFail);
308                 if (UNLIKELY(linkBuffer.didFailToAllocate())) {
309                     Base::fail(locker, makeString("Out of executable memory in function at index ", String::number(functionIndex)));
310                     return;
311                 }
312
313                 m_wasmInternalFunctions[functionIndex]->entrypoint.compilation = std::make_unique<B3::Compilation>(
314                     FINALIZE_CODE(linkBuffer, B3CompilationPtrTag, "WebAssembly function[%i] %s", functionIndex, signature.toString().ascii().data()),
315                     WTFMove(context.wasmEntrypointByproducts));
316             }
317
318             if (auto embedderToWasmInternalFunction = m_embedderToWasmInternalFunctions.get(functionIndex)) {
319                 LinkBuffer linkBuffer(*context.embedderEntrypointJIT, nullptr, JITCompilationCanFail);
320                 if (UNLIKELY(linkBuffer.didFailToAllocate())) {
321                     Base::fail(locker, makeString("Out of executable memory in function entrypoint at index ", String::number(functionIndex)));
322                     return;
323                 }
324
325                 embedderToWasmInternalFunction->entrypoint.compilation = std::make_unique<B3::Compilation>(
326                     FINALIZE_CODE(linkBuffer, B3CompilationPtrTag, "Embedder->WebAssembly entrypoint[%i] %s", functionIndex, signature.toString().ascii().data()),
327                     WTFMove(context.embedderEntrypointByproducts));
328             }
329         }
330
331         for (auto& unlinked : m_unlinkedWasmToWasmCalls) {
332             for (auto& call : unlinked) {
333                 MacroAssemblerCodePtr<WasmEntryPtrTag> executableAddress;
334                 if (m_moduleInformation->isImportedFunctionFromFunctionIndexSpace(call.functionIndexSpace)) {
335                     // 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
336                     executableAddress = m_wasmToWasmExitStubs.at(call.functionIndexSpace).code();
337                 } else
338                     executableAddress = m_wasmInternalFunctions.at(call.functionIndexSpace - m_moduleInformation->importFunctionCount())->entrypoint.compilation->code().retagged<WasmEntryPtrTag>();
339                 MacroAssembler::repatchNearCall(call.callLocation, CodeLocationLabel<WasmEntryPtrTag>(executableAddress));
340             }
341         }
342     }
343     
344     if (!isComplete()) {
345         moveToState(State::Completed);
346         runCompletionTasks(locker);
347     }
348 }
349
350 void BBQPlan::work(CompilationEffort effort)
351 {
352     switch (m_state) {
353     case State::Initial:
354         parseAndValidateModule();
355         if (!hasWork()) {
356             ASSERT(isComplete());
357             return;
358         }
359         FALLTHROUGH;
360     case State::Validated:
361         prepare();
362         return;
363     case State::Prepared:
364         compileFunctions(effort);
365         return;
366     default:
367         break;
368     }
369     return;
370 }
371
372 } } // namespace JSC::Wasm
373
374 #endif // ENABLE(WEBASSEMBLY)