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
50namespace JSC { namespace Wasm {
51
52namespace WasmBBQPlanInternal {
53static const bool verbose = false;
54}
55
56BBQPlan::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
63BBQPlan::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
71BBQPlan::BBQPlan(Context* context, AsyncWork work, CompletionTask&& task)
72 : Base(context, WTFMove(task))
73 , m_state(State::Initial)
74 , m_asyncWork(work)
75{
76}
77
78const 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
90void 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
97bool 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
147void 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.
214class BBQPlan::ThreadCountHolder {
215public:
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
235void 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
303void 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
357void 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)
382