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