blob: ba4ee4f86308d454dfac43b7e37e0c099faa81ea [file] [log] [blame]
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +00001/*
mark.lam@apple.com03313a92018-02-27 07:50:54 +00002 * Copyright (C) 2016-2018 Apple Inc. All rights reserved.
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +00003 *
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"
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +000032#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
48namespace JSC { namespace Wasm {
49
keith_miller@apple.com504d5852017-09-13 01:31:07 +000050namespace WasmBBQPlanInternal {
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +000051static const bool verbose = false;
keith_miller@apple.com504d5852017-09-13 01:31:07 +000052}
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +000053
jfbastien@apple.comd9f999e2017-10-20 02:23:29 +000054BBQPlan::BBQPlan(Context* context, Ref<ModuleInformation> info, AsyncWork work, CompletionTask&& task, CreateEmbedderWrapper&& createEmbedderWrapper, ThrowWasmException throwWasmException)
55 : Base(context, WTFMove(info), WTFMove(task), WTFMove(createEmbedderWrapper), throwWasmException)
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +000056 , m_state(State::Validated)
57 , m_asyncWork(work)
58{
59}
60
jfbastien@apple.comd9f999e2017-10-20 02:23:29 +000061BBQPlan::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)
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +000063{
64 m_state = State::Initial;
65}
66
jfbastien@apple.comd9f999e2017-10-20 02:23:29 +000067BBQPlan::BBQPlan(Context* context, const uint8_t* source, size_t sourceLength, AsyncWork work, CompletionTask&& task)
68 : Base(context, source, sourceLength, WTFMove(task))
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +000069 , m_state(State::Initial)
70 , m_asyncWork(work)
71{
72}
73
74const 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
86void BBQPlan::moveToState(State state)
87{
88 ASSERT(state >= m_state);
keith_miller@apple.com504d5852017-09-13 01:31:07 +000089 dataLogLnIf(WasmBBQPlanInternal::verbose && state != m_state, "moving to state: ", stateString(state), " from state: ", stateString(m_state));
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +000090 m_state = state;
91}
92
93bool BBQPlan::parseAndValidateModule()
94{
95 if (m_state != State::Initial)
96 return true;
97
keith_miller@apple.com504d5852017-09-13 01:31:07 +000098 dataLogLnIf(WasmBBQPlanInternal::verbose, "starting validation");
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +000099 MonotonicTime startTime;
keith_miller@apple.com504d5852017-09-13 01:31:07 +0000100 if (WasmBBQPlanInternal::verbose || Options::reportCompileTimes())
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000101 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) {
keith_miller@apple.com504d5852017-09-13 01:31:07 +0000114 dataLogLnIf(WasmBBQPlanInternal::verbose, "Processing function starting at: ", functionLocations[functionIndex].start, " and ending at: ", functionLocations[functionIndex].end);
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000115 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) {
keith_miller@apple.com504d5852017-09-13 01:31:07 +0000123 if (WasmBBQPlanInternal::verbose) {
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000124 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
keith_miller@apple.com504d5852017-09-13 01:31:07 +0000133 if (WasmBBQPlanInternal::verbose || Options::reportCompileTimes())
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000134 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
142void BBQPlan::prepare()
143{
144 ASSERT(m_state == State::Validated);
keith_miller@apple.com504d5852017-09-13 01:31:07 +0000145 dataLogLnIf(WasmBBQPlanInternal::verbose, "Starting preparation");
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000146
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();
mark.lam@apple.coma61c6c72018-03-30 16:31:06 +0000177 SignatureIndex signatureIndex = m_moduleInformation->importFunctionSignatureIndices[importFunctionIndex];
178 const Signature& signature = SignatureInformation::get(signatureIndex);
keith_miller@apple.com504d5852017-09-13 01:31:07 +0000179 dataLogLnIf(WasmBBQPlanInternal::verbose, "Processing import function number ", importFunctionIndex, ": ", makeString(import->module), ": ", makeString(import->field));
mark.lam@apple.coma61c6c72018-03-30 16:31:06 +0000180 auto binding = wasmToWasm(signature, importFunctionIndex);
jfbastien@apple.com817d14322017-06-28 06:42:13 +0000181 if (UNLIKELY(!binding)) {
182 switch (binding.error()) {
183 case BindingFailure::OutOfMemory:
184 return fail(holdLock(m_lock), makeString("Out of executable memory at import ", String::number(importIndex)));
185 }
186 RELEASE_ASSERT_NOT_REACHED();
187 }
188 m_wasmToWasmExitStubs.uncheckedAppend(binding.value());
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000189 }
190
keith_miller@apple.come63ccf02017-06-08 19:13:53 +0000191 const uint32_t importFunctionCount = m_moduleInformation->importFunctionCount();
192 for (const auto& exp : m_moduleInformation->exports) {
193 if (exp.kindIndex >= importFunctionCount)
194 m_exportedFunctionIndices.add(exp.kindIndex - importFunctionCount);
195 }
196
197 for (const auto& element : m_moduleInformation->elements) {
198 for (const uint32_t elementIndex : element.functionIndices) {
199 if (elementIndex >= importFunctionCount)
200 m_exportedFunctionIndices.add(elementIndex - importFunctionCount);
201 }
202 }
203
204 if (m_moduleInformation->startFunctionIndexSpace && m_moduleInformation->startFunctionIndexSpace >= importFunctionCount)
205 m_exportedFunctionIndices.add(*m_moduleInformation->startFunctionIndexSpace - importFunctionCount);
206
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000207 moveToState(State::Prepared);
208}
209
210// We don't have a semaphore class... and this does kinda interesting things.
211class BBQPlan::ThreadCountHolder {
212public:
213 ThreadCountHolder(BBQPlan& plan)
214 : m_plan(plan)
215 {
216 LockHolder locker(m_plan.m_lock);
217 m_plan.m_numberOfActiveThreads++;
218 }
219
220 ~ThreadCountHolder()
221 {
222 LockHolder locker(m_plan.m_lock);
223 m_plan.m_numberOfActiveThreads--;
224
225 if (!m_plan.m_numberOfActiveThreads && !m_plan.hasWork())
226 m_plan.complete(locker);
227 }
228
229 BBQPlan& m_plan;
230};
231
232void BBQPlan::compileFunctions(CompilationEffort effort)
233{
234 ASSERT(m_state >= State::Prepared);
keith_miller@apple.com504d5852017-09-13 01:31:07 +0000235 dataLogLnIf(WasmBBQPlanInternal::verbose, "Starting compilation");
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000236
237 if (!hasWork())
238 return;
239
240 TraceScope traceScope(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>();
keith_miller@apple.comea98d8d2017-06-01 18:28:48 +0000271 TierUpCount* tierUp = Options::useBBQTierUpChecks() ? &m_tierUpCounts[functionIndex] : nullptr;
jfbastien@apple.comd9f999e2017-10-20 02:23:29 +0000272 auto parseAndCompileResult = parseAndCompile(m_compilationContexts[functionIndex], functionStart, functionLength, signature, m_unlinkedWasmToWasmCalls[functionIndex], m_moduleInformation.get(), m_mode, CompilationMode::BBQMode, functionIndex, tierUp, m_throwWasmException);
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000273
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);
keith_miller@apple.come63ccf02017-06-08 19:13:53 +0000285
286 if (m_exportedFunctionIndices.contains(functionIndex)) {
287 auto locker = holdLock(m_lock);
jfbastien@apple.comd9f999e2017-10-20 02:23:29 +0000288 auto result = m_embedderToWasmInternalFunctions.add(functionIndex, m_createEmbedderWrapper(m_compilationContexts[functionIndex], signature, &m_unlinkedWasmToWasmCalls[functionIndex], m_moduleInformation.get(), m_mode, functionIndex));
keith_miller@apple.come63ccf02017-06-08 19:13:53 +0000289 ASSERT_UNUSED(result, result.isNewEntry);
290 }
291
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000292 bytesCompiled += functionLength;
293 }
294}
295
296void BBQPlan::complete(const AbstractLocker& locker)
297{
298 ASSERT(m_state != State::Compiled || m_currentIndex >= m_moduleInformation->functionLocationInBinary.size());
keith_miller@apple.com504d5852017-09-13 01:31:07 +0000299 dataLogLnIf(WasmBBQPlanInternal::verbose, "Starting Completion");
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000300
jfbastien@apple.com817d14322017-06-28 06:42:13 +0000301 if (!failed() && m_state == State::Compiled) {
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000302 for (uint32_t functionIndex = 0; functionIndex < m_moduleInformation->functionLocationInBinary.size(); functionIndex++) {
303 CompilationContext& context = m_compilationContexts[functionIndex];
304 SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex];
mark.lam@apple.coma61c6c72018-03-30 16:31:06 +0000305 const Signature& signature = SignatureInformation::get(signatureIndex);
306 PtrTag callTag = ptrTag(WasmCallPtrTag, signature.hash());
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000307 {
jfbastien@apple.com817d14322017-06-28 06:42:13 +0000308 LinkBuffer linkBuffer(*context.wasmEntrypointJIT, nullptr, JITCompilationCanFail);
309 if (UNLIKELY(linkBuffer.didFailToAllocate())) {
310 Base::fail(locker, makeString("Out of executable memory in function at index ", String::number(functionIndex)));
311 return;
312 }
313
keith_miller@apple.come63ccf02017-06-08 19:13:53 +0000314 m_wasmInternalFunctions[functionIndex]->entrypoint.compilation = std::make_unique<B3::Compilation>(
mark.lam@apple.coma61c6c72018-03-30 16:31:06 +0000315 FINALIZE_CODE(linkBuffer, callTag, "WebAssembly function[%i] %s", functionIndex, signature.toString().ascii().data()),
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000316 WTFMove(context.wasmEntrypointByproducts));
317 }
318
jfbastien@apple.comd9f999e2017-10-20 02:23:29 +0000319 if (auto embedderToWasmInternalFunction = m_embedderToWasmInternalFunctions.get(functionIndex)) {
jfbastien@apple.com2aa89f72017-10-24 18:55:52 +0000320 LinkBuffer linkBuffer(*context.embedderEntrypointJIT, nullptr, JITCompilationCanFail);
jfbastien@apple.com817d14322017-06-28 06:42:13 +0000321 if (UNLIKELY(linkBuffer.didFailToAllocate())) {
322 Base::fail(locker, makeString("Out of executable memory in function entrypoint at index ", String::number(functionIndex)));
323 return;
324 }
325
jfbastien@apple.comd9f999e2017-10-20 02:23:29 +0000326 embedderToWasmInternalFunction->entrypoint.compilation = std::make_unique<B3::Compilation>(
mark.lam@apple.com5b1272a2018-04-12 20:14:21 +0000327 FINALIZE_CODE(linkBuffer, CodePtrTag, "Embedder->WebAssembly entrypoint[%i] %s", functionIndex, signature.toString().ascii().data()),
jfbastien@apple.com2aa89f72017-10-24 18:55:52 +0000328 WTFMove(context.embedderEntrypointByproducts));
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000329 }
330 }
331
332 for (auto& unlinked : m_unlinkedWasmToWasmCalls) {
333 for (auto& call : unlinked) {
mark.lam@apple.com2cd487f2017-11-30 23:47:35 +0000334 MacroAssemblerCodePtr executableAddress;
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000335 if (m_moduleInformation->isImportedFunctionFromFunctionIndexSpace(call.functionIndexSpace)) {
336 // 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
mark.lam@apple.com2cd487f2017-11-30 23:47:35 +0000337 executableAddress = m_wasmToWasmExitStubs.at(call.functionIndexSpace).code();
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000338 } else
mark.lam@apple.com2cd487f2017-11-30 23:47:35 +0000339 executableAddress = m_wasmInternalFunctions.at(call.functionIndexSpace - m_moduleInformation->importFunctionCount())->entrypoint.compilation->code();
mark.lam@apple.coma61c6c72018-03-30 16:31:06 +0000340 SignatureIndex signatureIndex = m_moduleInformation->signatureIndexFromFunctionIndexSpace(call.functionIndexSpace);
341 const Signature& signature = SignatureInformation::get(signatureIndex);
342 PtrTag oldTag = ptrTag(WasmCallPtrTag, signature.hash());
mark.lam@apple.com5b1272a2018-04-12 20:14:21 +0000343 MacroAssembler::repatchNearCall(call.callLocation, CodeLocationLabel(executableAddress.retagged(oldTag, NearCodePtrTag)));
keith_miller@apple.com5fe2e702017-04-26 23:55:05 +0000344 }
345 }
346 }
347
348 if (!isComplete()) {
349 moveToState(State::Completed);
350 runCompletionTasks(locker);
351 }
352}
353
354void BBQPlan::work(CompilationEffort effort)
355{
356 switch (m_state) {
357 case State::Initial:
358 parseAndValidateModule();
359 if (!hasWork()) {
360 ASSERT(isComplete());
361 return;
362 }
363 FALLTHROUGH;
364 case State::Validated:
365 prepare();
366 return;
367 case State::Prepared:
368 compileFunctions(effort);
369 return;
370 default:
371 break;
372 }
373 return;
374}
375
376} } // namespace JSC::Wasm
377
378#endif // ENABLE(WEBASSEMBLY)