d65f662732c938e3f17a7f7351ccbee41ccefb33
[WebKit-https.git] / Source / JavaScriptCore / wasm / WasmPlan.cpp
1 /*
2  * Copyright (C) 2016 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 "WasmPlan.h"
28
29 #if ENABLE(WEBASSEMBLY)
30
31 #include "B3Compilation.h"
32 #include "JSCInlines.h"
33 #include "JSGlobalObject.h"
34 #include "WasmB3IRGenerator.h"
35 #include "WasmBinding.h"
36 #include "WasmCallee.h"
37 #include "WasmCallingConvention.h"
38 #include "WasmFaultSignalHandler.h"
39 #include "WasmMemory.h"
40 #include "WasmModuleParser.h"
41 #include "WasmValidate.h"
42 #include <wtf/DataLog.h>
43 #include <wtf/Locker.h>
44 #include <wtf/MonotonicTime.h>
45 #include <wtf/StdLibExtras.h>
46 #include <wtf/SystemTracing.h>
47 #include <wtf/text/StringBuilder.h>
48
49 namespace JSC { namespace Wasm {
50
51 static const bool verbose = false;
52
53 Plan::Plan(VM& vm, ArrayBuffer& source, AsyncWork work, CompletionTask&& task)
54     : Plan(vm, reinterpret_cast<uint8_t*>(source.data()), source.byteLength(), work, WTFMove(task))
55 {
56 }
57
58 Plan::Plan(VM& vm, Vector<uint8_t>& source, AsyncWork work, CompletionTask&& task)
59     : Plan(vm, source.data(), source.size(), work, WTFMove(task))
60 {
61 }
62
63 Plan::Plan(VM& vm, const uint8_t* source, size_t sourceLength, AsyncWork work, CompletionTask&& task)
64     : m_vm(vm)
65     , m_completionTask(task)
66     , m_source(source)
67     , m_sourceLength(sourceLength)
68     , m_asyncWork(work)
69 {
70 }
71
72 const char* Plan::stateString(State state)
73 {
74     switch (state) {
75     case State::Initial: return "Initial";
76     case State::Validated: return "Validated";
77     case State::Prepared: return "Prepared";
78     case State::Compiled: return "Compiled";
79     case State::Completed: return "Completed";
80     }
81     RELEASE_ASSERT_NOT_REACHED();
82 }
83
84 void Plan::moveToState(State state)
85 {
86     ASSERT(state >= m_state);
87     dataLogLnIf(verbose && state != m_state, "moving to state: ", stateString(state), " from state: ", stateString(m_state));
88     m_state = state;
89 }
90
91 void Plan::fail(const AbstractLocker& locker, String&& errorMessage)
92 {
93     dataLogLnIf(verbose, "failing with message: ", errorMessage);
94     m_errorMessage = WTFMove(errorMessage);
95     complete(locker);
96 }
97
98 bool Plan::parseAndValidateModule()
99 {
100     ASSERT(m_state == State::Initial);
101     dataLogLnIf(verbose, "starting validation");
102     MonotonicTime startTime;
103     if (verbose || Options::reportCompileTimes())
104         startTime = MonotonicTime::now();
105
106     {
107         ModuleParser moduleParser(m_source, m_sourceLength);
108         auto parseResult = moduleParser.parse();
109         if (!parseResult) {
110             fail(holdLock(m_lock), WTFMove(parseResult.error()));
111             return false;
112         }
113         m_moduleInformation = WTFMove(parseResult->module);
114         m_functionLocationInBinary = WTFMove(parseResult->functionLocationInBinary);
115         m_moduleSignatureIndicesToUniquedSignatureIndices = WTFMove(parseResult->moduleSignatureIndicesToUniquedSignatureIndices);
116     }
117
118     for (unsigned functionIndex = 0; functionIndex < m_functionLocationInBinary.size(); ++functionIndex) {
119         dataLogLnIf(verbose, "Processing function starting at: ", m_functionLocationInBinary[functionIndex].start, " and ending at: ", m_functionLocationInBinary[functionIndex].end);
120         const uint8_t* functionStart = m_source + m_functionLocationInBinary[functionIndex].start;
121         size_t functionLength = m_functionLocationInBinary[functionIndex].end - m_functionLocationInBinary[functionIndex].start;
122         ASSERT(functionLength <= m_sourceLength);
123         SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex];
124         const Signature& signature = SignatureInformation::get(signatureIndex);
125
126         auto validationResult = validateFunction(functionStart, functionLength, signature, *m_moduleInformation, m_moduleSignatureIndicesToUniquedSignatureIndices);
127         if (!validationResult) {
128             if (verbose) {
129                 for (unsigned i = 0; i < functionLength; ++i)
130                     dataLog(RawPointer(reinterpret_cast<void*>(functionStart[i])), ", ");
131                 dataLogLn();
132             }
133             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 (verbose || Options::reportCompileTimes())
139         dataLogLn("Took ", (MonotonicTime::now() - startTime).microseconds(), " us to validate module");
140     if (m_asyncWork == Validation)
141         complete(holdLock(m_lock));
142     else
143         moveToState(State::Validated);
144     return true;
145 }
146
147 void Plan::prepare()
148 {
149     ASSERT(m_state == State::Validated);
150     dataLogLnIf(verbose, "Starting preparation");
151
152     TraceScope traceScope(WebAssemblyCompileStart, WebAssemblyCompileEnd);
153
154     auto tryReserveCapacity = [this] (auto& vector, size_t size, const char* what) {
155         if (UNLIKELY(!vector.tryReserveCapacity(size))) {
156             StringBuilder builder;
157             builder.appendLiteral("Failed allocating enough space for ");
158             builder.appendNumber(size);
159             builder.append(what);
160             fail(holdLock(m_lock), builder.toString());
161             return false;
162         }
163         return true;
164     };
165
166     if (!tryReserveCapacity(m_wasmExitStubs, m_moduleInformation->importFunctionSignatureIndices.size(), " WebAssembly to JavaScript stubs")
167         || !tryReserveCapacity(m_unlinkedWasmToWasmCalls, m_functionLocationInBinary.size(), " unlinked WebAssembly to WebAssembly calls")
168         || !tryReserveCapacity(m_wasmInternalFunctions, m_functionLocationInBinary.size(), " WebAssembly functions")
169         || !tryReserveCapacity(m_compilationContexts, m_functionLocationInBinary.size(), " compilation contexts"))
170         return;
171
172     m_unlinkedWasmToWasmCalls.resize(m_functionLocationInBinary.size());
173     m_wasmInternalFunctions.resize(m_functionLocationInBinary.size());
174     m_compilationContexts.resize(m_functionLocationInBinary.size());
175
176     for (unsigned importIndex = 0; importIndex < m_moduleInformation->imports.size(); ++importIndex) {
177         Import* import = &m_moduleInformation->imports[importIndex];
178         if (import->kind != ExternalKind::Function)
179             continue;
180         unsigned importFunctionIndex = m_wasmExitStubs.size();
181         dataLogLnIf(verbose, "Processing import function number ", importFunctionIndex, ": ", import->module, ": ", import->field);
182         SignatureIndex signatureIndex = m_moduleInformation->importFunctionSignatureIndices.at(import->kindIndex);
183         m_wasmExitStubs.uncheckedAppend(exitStubGenerator(&m_vm, m_callLinkInfos, signatureIndex, importFunctionIndex));
184     }
185
186     moveToState(State::Prepared);
187 }
188
189 // We don't have a semaphore class... and this does kinda interesting things.
190 class Plan::ThreadCountHolder {
191 public:
192     ThreadCountHolder(Plan& plan)
193         : m_plan(plan)
194     {
195         LockHolder locker(m_plan.m_lock);
196         m_plan.m_numberOfActiveThreads++;
197     }
198
199     ~ThreadCountHolder()
200     {
201         LockHolder locker(m_plan.m_lock);
202         m_plan.m_numberOfActiveThreads--;
203
204         if (!m_plan.m_numberOfActiveThreads && !m_plan.hasWork())
205             m_plan.complete(locker);
206     }
207
208     Plan& m_plan;
209 };
210
211 void Plan::compileFunctions(CompilationEffort effort)
212 {
213     ASSERT(m_state >= State::Prepared);
214     dataLogLnIf(verbose, "Starting compilation");
215
216     if (!hasWork())
217         return;
218
219     ThreadCountHolder holder(*this);
220
221     size_t bytesCompiled = 0;
222     while (true) {
223         if (effort == Partial && bytesCompiled >= Options::webAssemblyPartialCompileLimit())
224             return;
225
226         uint32_t functionIndex;
227         {
228             auto locker = holdLock(m_lock);
229             if (m_currentIndex >= m_functionLocationInBinary.size()) {
230                 if (hasWork())
231                     moveToState(State::Compiled);
232                 return;
233             }
234             functionIndex = m_currentIndex;
235             ++m_currentIndex;
236         }
237
238         const uint8_t* functionStart = m_source + m_functionLocationInBinary[functionIndex].start;
239         size_t functionLength = m_functionLocationInBinary[functionIndex].end - m_functionLocationInBinary[functionIndex].start;
240         ASSERT(functionLength <= m_sourceLength);
241         SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex];
242         const Signature& signature = SignatureInformation::get(signatureIndex);
243         unsigned functionIndexSpace = m_wasmExitStubs.size() + functionIndex;
244         ASSERT_UNUSED(functionIndexSpace, m_moduleInformation->signatureIndexFromFunctionIndexSpace(functionIndexSpace) == signatureIndex);
245         ASSERT(validateFunction(functionStart, functionLength, signature, *m_moduleInformation, m_moduleSignatureIndicesToUniquedSignatureIndices));
246
247         m_unlinkedWasmToWasmCalls[functionIndex] = Vector<UnlinkedWasmToWasmCall>();
248         auto parseAndCompileResult = parseAndCompile(m_compilationContexts[functionIndex], functionStart, functionLength, signature, m_unlinkedWasmToWasmCalls[functionIndex], *m_moduleInformation, m_moduleSignatureIndicesToUniquedSignatureIndices, m_mode, Options::webAssemblyB3OptimizationLevel());
249
250         if (UNLIKELY(!parseAndCompileResult)) {
251             auto locker = holdLock(m_lock);
252             if (!m_errorMessage) {
253                 // Multiple compiles could fail simultaneously. We arbitrarily choose the first.
254                 fail(locker, makeString(parseAndCompileResult.error(), ", in function at index ", String::number(functionIndex))); // FIXME make this an Expected.
255             }
256             m_currentIndex = m_functionLocationInBinary.size();
257             return;
258         }
259
260         m_wasmInternalFunctions[functionIndex] = WTFMove(*parseAndCompileResult);
261         bytesCompiled += functionLength;
262     }
263 }
264
265 void Plan::complete(const AbstractLocker&)
266 {
267     ASSERT(m_state != State::Compiled || m_currentIndex >= m_functionLocationInBinary.size());
268     dataLogLnIf(verbose, "Starting Completion");
269
270     if (m_state == State::Compiled) {
271         for (uint32_t functionIndex = 0; functionIndex < m_functionLocationInBinary.size(); functionIndex++) {
272             {
273                 CompilationContext& context = m_compilationContexts[functionIndex];
274                 SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex];
275                 {
276                     LinkBuffer linkBuffer(*context.wasmEntrypointJIT, nullptr);
277                     m_wasmInternalFunctions[functionIndex]->wasmEntrypoint.compilation = std::make_unique<B3::Compilation>(
278                         FINALIZE_CODE(linkBuffer, ("WebAssembly function[%i] %s", functionIndex, SignatureInformation::get(signatureIndex).toString().ascii().data())),
279                         WTFMove(context.wasmEntrypointByproducts));
280                 }
281
282                 {
283                     LinkBuffer linkBuffer(*context.jsEntrypointJIT, nullptr);
284                     linkBuffer.link(context.jsEntrypointToWasmEntrypointCall, FunctionPtr(m_wasmInternalFunctions[functionIndex]->wasmEntrypoint.compilation->code().executableAddress()));
285
286                     m_wasmInternalFunctions[functionIndex]->jsToWasmEntrypoint.compilation = std::make_unique<B3::Compilation>(
287                         FINALIZE_CODE(linkBuffer, ("JavaScript->WebAssembly entrypoint[%i] %s", functionIndex, SignatureInformation::get(signatureIndex).toString().ascii().data())),
288                         WTFMove(context.jsEntrypointByproducts));
289                 }
290             }
291
292         }
293
294         for (auto& unlinked : m_unlinkedWasmToWasmCalls) {
295             for (auto& call : unlinked) {
296                 void* executableAddress;
297                 if (m_moduleInformation->isImportedFunctionFromFunctionIndexSpace(call.functionIndex)) {
298                     // 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
299                     executableAddress = m_wasmExitStubs.at(call.functionIndex).wasmToWasm.code().executableAddress();
300                 } else
301                     executableAddress = m_wasmInternalFunctions.at(call.functionIndex - m_wasmExitStubs.size())->wasmEntrypoint.compilation->code().executableAddress();
302                 MacroAssembler::repatchCall(call.callLocation, CodeLocationLabel(executableAddress));
303             }
304         }
305     }
306
307     if (m_state != State::Completed) {
308         moveToState(State::Completed);
309         m_completionTask(*this);
310         m_completed.notifyAll();
311     }
312 }
313
314 void Plan::waitForCompletion()
315 {
316     LockHolder locker(m_lock);
317     if (m_state != State::Completed) {
318         // FIXME: We should have a wait conditionally so we don't have to hold the lock to complete / fail.
319         m_completed.wait(m_lock);
320     }
321 }
322
323 void Plan::cancel()
324 {
325     LockHolder locker(m_lock);
326     if (m_state != State::Completed) {
327         m_currentIndex = m_functionLocationInBinary.size();
328         fail(locker, ASCIILiteral("WebAssembly Plan was canceled. If you see this error message please file a bug at bugs.webkit.org!"));
329     }
330 }
331
332 Plan::~Plan() { }
333
334 } } // namespace JSC::Wasm
335
336 #endif // ENABLE(WEBASSEMBLY)