1 | /* |
2 | * Copyright (C) 2016-2019 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 "WasmMachineThreads.h" |
39 | #include "WasmMemory.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/StringConcatenateNumbers.h> |
49 | |
50 | namespace JSC { namespace Wasm { |
51 | |
52 | namespace WasmBBQPlanInternal { |
53 | static constexpr bool verbose = false; |
54 | } |
55 | |
56 | BBQPlan::BBQPlan(Context* context, Ref<ModuleInformation> moduleInformation, uint32_t functionIndex, CodeBlock* codeBlock, CompletionTask&& completionTask) |
57 | : EntryPlan(context, WTFMove(moduleInformation), WTFMove(completionTask)) |
58 | , m_codeBlock(codeBlock) |
59 | , m_functionIndex(functionIndex) |
60 | { |
61 | setMode(m_codeBlock->mode()); |
62 | } |
63 | |
64 | bool BBQPlan::prepareImpl() |
65 | { |
66 | const auto& functions = m_moduleInformation->functions; |
67 | if (!tryReserveCapacity(m_wasmInternalFunctions, functions.size(), " WebAssembly functions" ) |
68 | || !tryReserveCapacity(m_compilationContexts, functions.size(), " compilation contexts" ) |
69 | || !tryReserveCapacity(m_tierUpCounts, functions.size(), " tier-up counts" )) |
70 | return false; |
71 | |
72 | m_wasmInternalFunctions.resize(functions.size()); |
73 | m_compilationContexts.resize(functions.size()); |
74 | m_tierUpCounts.resize(functions.size()); |
75 | |
76 | return true; |
77 | } |
78 | |
79 | void BBQPlan::work(CompilationEffort effort) |
80 | { |
81 | if (!m_codeBlock) { |
82 | Base::work(effort); |
83 | return; |
84 | } |
85 | |
86 | CompilationContext context; |
87 | Vector<UnlinkedWasmToWasmCall> unlinkedWasmToWasmCalls; |
88 | std::unique_ptr<TierUpCount> tierUp = makeUnique<TierUpCount>(); |
89 | std::unique_ptr<InternalFunction> function = compileFunction(m_functionIndex, context, unlinkedWasmToWasmCalls, tierUp.get()); |
90 | |
91 | LinkBuffer linkBuffer(*context.wasmEntrypointJIT, nullptr, JITCompilationCanFail); |
92 | if (UNLIKELY(linkBuffer.didFailToAllocate())) { |
93 | Base::fail(holdLock(m_lock), makeString("Out of executable memory while tiering up function at index " , String::number(m_functionIndex))); |
94 | return; |
95 | } |
96 | |
97 | size_t functionIndexSpace = m_functionIndex + m_moduleInformation->importFunctionCount(); |
98 | SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[m_functionIndex]; |
99 | const Signature& signature = SignatureInformation::get(signatureIndex); |
100 | function->entrypoint.compilation = makeUnique<B3::Compilation>( |
101 | FINALIZE_WASM_CODE_FOR_MODE(CompilationMode::BBQMode, linkBuffer, B3CompilationPtrTag, "WebAssembly BBQ function[%i] %s name %s" , m_functionIndex, signature.toString().ascii().data(), makeString(IndexOrName(functionIndexSpace, m_moduleInformation->nameSection->get(functionIndexSpace))).ascii().data()), |
102 | WTFMove(context.wasmEntrypointByproducts)); |
103 | |
104 | MacroAssemblerCodePtr<WasmEntryPtrTag> entrypoint; |
105 | { |
106 | Ref<BBQCallee> callee = BBQCallee::create(WTFMove(function->entrypoint), functionIndexSpace, m_moduleInformation->nameSection->get(functionIndexSpace), WTFMove(tierUp), WTFMove(unlinkedWasmToWasmCalls)); |
107 | MacroAssembler::repatchPointer(function->calleeMoveLocation, CalleeBits::boxWasm(callee.ptr())); |
108 | ASSERT(!m_codeBlock->m_bbqCallees[m_functionIndex]); |
109 | entrypoint = callee->entrypoint(); |
110 | |
111 | // We want to make sure we publish our callee at the same time as we link our callsites. This enables us to ensure we |
112 | // always call the fastest code. Any function linked after us will see our new code and the new callsites, which they |
113 | // will update. It's also ok if they publish their code before we reset the instruction caches because after we release |
114 | // the lock our code is ready to be published too. |
115 | LockHolder holder(m_codeBlock->m_lock); |
116 | m_codeBlock->m_bbqCallees[m_functionIndex] = callee.copyRef(); |
117 | { |
118 | LLIntCallee& llintCallee = *m_codeBlock->m_llintCallees[m_functionIndex]; |
119 | auto locker = holdLock(llintCallee.tierUpCounter().m_lock); |
120 | llintCallee.setReplacement(callee.copyRef()); |
121 | llintCallee.tierUpCounter().m_compilationStatus = LLIntTierUpCounter::CompilationStatus::Compiled; |
122 | } |
123 | for (auto& call : callee->wasmToWasmCallsites()) { |
124 | MacroAssemblerCodePtr<WasmEntryPtrTag> entrypoint; |
125 | if (call.functionIndexSpace < m_moduleInformation->importFunctionCount()) |
126 | entrypoint = m_codeBlock->m_wasmToWasmExitStubs[call.functionIndexSpace].code(); |
127 | else |
128 | entrypoint = m_codeBlock->wasmEntrypointCalleeFromFunctionIndexSpace(call.functionIndexSpace).entrypoint().retagged<WasmEntryPtrTag>(); |
129 | |
130 | MacroAssembler::repatchNearCall(call.callLocation, CodeLocationLabel<WasmEntryPtrTag>(entrypoint)); |
131 | } |
132 | } |
133 | |
134 | // It's important to make sure we do this before we make any of the code we just compiled visible. If we didn't, we could end up |
135 | // where we are tiering up some function A to A' and we repatch some function B to call A' instead of A. Another CPU could see |
136 | // the updates to B but still not have reset its cache of A', which would lead to all kinds of badness. |
137 | resetInstructionCacheOnAllThreads(); |
138 | WTF::storeStoreFence(); // This probably isn't necessary but it's good to be paranoid. |
139 | |
140 | m_codeBlock->m_wasmIndirectCallEntryPoints[m_functionIndex] = entrypoint; |
141 | { |
142 | LockHolder holder(m_codeBlock->m_lock); |
143 | |
144 | auto repatchCalls = [&] (const Vector<UnlinkedWasmToWasmCall>& callsites) { |
145 | for (auto& call : callsites) { |
146 | dataLogLnIf(WasmBBQPlanInternal::verbose, "Considering repatching call at: " , RawPointer(call.callLocation.dataLocation()), " that targets " , call.functionIndexSpace); |
147 | if (call.functionIndexSpace == functionIndexSpace) { |
148 | dataLogLnIf(WasmBBQPlanInternal::verbose, "Repatching call at: " , RawPointer(call.callLocation.dataLocation()), " to " , RawPointer(entrypoint.executableAddress())); |
149 | MacroAssembler::repatchNearCall(call.callLocation, CodeLocationLabel<WasmEntryPtrTag>(entrypoint)); |
150 | } |
151 | } |
152 | }; |
153 | |
154 | for (unsigned i = 0; i < m_codeBlock->m_wasmToWasmCallsites.size(); ++i) { |
155 | repatchCalls(m_codeBlock->m_wasmToWasmCallsites[i]); |
156 | if (LLIntCallee* llintCallee = m_codeBlock->m_llintCallees[i].get()) { |
157 | if (JITCallee* replacementCallee = llintCallee->replacement()) |
158 | repatchCalls(replacementCallee->wasmToWasmCallsites()); |
159 | if (OMGForOSREntryCallee* osrEntryCallee = llintCallee->osrEntryCallee()) |
160 | repatchCalls(osrEntryCallee->wasmToWasmCallsites()); |
161 | } |
162 | if (BBQCallee* bbqCallee = m_codeBlock->m_bbqCallees[i].get()) { |
163 | if (OMGCallee* replacementCallee = bbqCallee->replacement()) |
164 | repatchCalls(replacementCallee->wasmToWasmCallsites()); |
165 | if (OMGForOSREntryCallee* osrEntryCallee = bbqCallee->osrEntryCallee()) |
166 | repatchCalls(osrEntryCallee->wasmToWasmCallsites()); |
167 | } |
168 | } |
169 | } |
170 | |
171 | dataLogLnIf(WasmBBQPlanInternal::verbose, "Finished BBQ " , m_functionIndex); |
172 | |
173 | |
174 | auto locker = holdLock(m_lock); |
175 | moveToState(State::Completed); |
176 | runCompletionTasks(locker); |
177 | } |
178 | |
179 | void BBQPlan::compileFunction(uint32_t functionIndex) |
180 | { |
181 | m_unlinkedWasmToWasmCalls[functionIndex] = Vector<UnlinkedWasmToWasmCall>(); |
182 | |
183 | if (Options::useBBQTierUpChecks()) |
184 | m_tierUpCounts[functionIndex] = makeUnique<TierUpCount>(); |
185 | else |
186 | m_tierUpCounts[functionIndex] = nullptr; |
187 | |
188 | m_wasmInternalFunctions[functionIndex] = compileFunction(functionIndex, m_compilationContexts[functionIndex], m_unlinkedWasmToWasmCalls[functionIndex], m_tierUpCounts[functionIndex].get()); |
189 | |
190 | if (m_exportedFunctionIndices.contains(functionIndex) || m_moduleInformation->referencedFunctions().contains(functionIndex)) { |
191 | auto locker = holdLock(m_lock); |
192 | SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex]; |
193 | const Signature& signature = SignatureInformation::get(signatureIndex); |
194 | auto result = m_embedderToWasmInternalFunctions.add(functionIndex, m_createEmbedderWrapper(*m_compilationContexts[functionIndex].embedderEntrypointJIT, signature, &m_unlinkedWasmToWasmCalls[functionIndex], m_moduleInformation.get(), m_mode, functionIndex)); |
195 | ASSERT_UNUSED(result, result.isNewEntry); |
196 | } |
197 | } |
198 | |
199 | std::unique_ptr<InternalFunction> BBQPlan::compileFunction(uint32_t functionIndex, CompilationContext& context, Vector<UnlinkedWasmToWasmCall>& unlinkedWasmToWasmCalls, TierUpCount* tierUp) |
200 | { |
201 | const auto& function = m_moduleInformation->functions[functionIndex]; |
202 | SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex]; |
203 | const Signature& signature = SignatureInformation::get(signatureIndex); |
204 | unsigned functionIndexSpace = m_moduleInformation->importFunctionCount() + functionIndex; |
205 | ASSERT_UNUSED(functionIndexSpace, m_moduleInformation->signatureIndexFromFunctionIndexSpace(functionIndexSpace) == signatureIndex); |
206 | ASSERT(validateFunction(function, signature, m_moduleInformation.get())); |
207 | Expected<std::unique_ptr<InternalFunction>, String> parseAndCompileResult; |
208 | unsigned osrEntryScratchBufferSize = 0; |
209 | |
210 | // FIXME: Some webpages use very large Wasm module, and it exhausts all executable memory in ARM64 devices since the size of executable memory region is only limited to 128MB. |
211 | // The long term solution should be to introduce a Wasm interpreter. But as a short term solution, we introduce heuristics to switch back to BBQ B3 at the sacrifice of start-up time, |
212 | // as BBQ Air bloats such lengthy Wasm code and will consume a large amount of executable memory. |
213 | bool forceUsingB3 = false; |
214 | if (Options::webAssemblyBBQAirModeThreshold() && m_moduleInformation->codeSectionSize >= Options::webAssemblyBBQAirModeThreshold()) |
215 | forceUsingB3 = true; |
216 | |
217 | if (!forceUsingB3 && Options::wasmBBQUsesAir()) |
218 | parseAndCompileResult = parseAndCompileAir(context, function, signature, unlinkedWasmToWasmCalls, m_moduleInformation.get(), m_mode, functionIndex, tierUp, m_throwWasmException); |
219 | else |
220 | parseAndCompileResult = parseAndCompile(context, function, signature, unlinkedWasmToWasmCalls, osrEntryScratchBufferSize, m_moduleInformation.get(), m_mode, CompilationMode::BBQMode, functionIndex, UINT32_MAX, tierUp, m_throwWasmException); |
221 | |
222 | if (UNLIKELY(!parseAndCompileResult)) { |
223 | auto locker = holdLock(m_lock); |
224 | if (!m_errorMessage) { |
225 | // Multiple compiles could fail simultaneously. We arbitrarily choose the first. |
226 | fail(locker, makeString(parseAndCompileResult.error(), ", in function at index " , String::number(functionIndex))); // FIXME make this an Expected. |
227 | } |
228 | m_currentIndex = m_moduleInformation->functions.size(); |
229 | return nullptr; |
230 | } |
231 | |
232 | return WTFMove(*parseAndCompileResult); |
233 | } |
234 | |
235 | void BBQPlan::didCompleteCompilation(const AbstractLocker& locker) |
236 | { |
237 | for (uint32_t functionIndex = 0; functionIndex < m_moduleInformation->functions.size(); functionIndex++) { |
238 | CompilationContext& context = m_compilationContexts[functionIndex]; |
239 | SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex]; |
240 | const Signature& signature = SignatureInformation::get(signatureIndex); |
241 | const uint32_t functionIndexSpace = functionIndex + m_moduleInformation->importFunctionCount(); |
242 | ASSERT(functionIndexSpace < m_moduleInformation->functionIndexSpaceSize()); |
243 | { |
244 | LinkBuffer linkBuffer(*context.wasmEntrypointJIT, nullptr, JITCompilationCanFail); |
245 | if (UNLIKELY(linkBuffer.didFailToAllocate())) { |
246 | Base::fail(locker, makeString("Out of executable memory in function at index " , String::number(functionIndex))); |
247 | return; |
248 | } |
249 | |
250 | m_wasmInternalFunctions[functionIndex]->entrypoint.compilation = makeUnique<B3::Compilation>( |
251 | FINALIZE_CODE(linkBuffer, B3CompilationPtrTag, "WebAssembly BBQ function[%i] %s name %s" , functionIndex, signature.toString().ascii().data(), makeString(IndexOrName(functionIndexSpace, m_moduleInformation->nameSection->get(functionIndexSpace))).ascii().data()), |
252 | WTFMove(context.wasmEntrypointByproducts)); |
253 | } |
254 | |
255 | if (const auto& embedderToWasmInternalFunction = m_embedderToWasmInternalFunctions.get(functionIndex)) { |
256 | LinkBuffer linkBuffer(*context.embedderEntrypointJIT, nullptr, JITCompilationCanFail); |
257 | if (UNLIKELY(linkBuffer.didFailToAllocate())) { |
258 | Base::fail(locker, makeString("Out of executable memory in function entrypoint at index " , String::number(functionIndex))); |
259 | return; |
260 | } |
261 | |
262 | embedderToWasmInternalFunction->entrypoint.compilation = makeUnique<B3::Compilation>( |
263 | FINALIZE_CODE(linkBuffer, B3CompilationPtrTag, "Embedder->WebAssembly entrypoint[%i] %s name %s" , functionIndex, signature.toString().ascii().data(), makeString(IndexOrName(functionIndexSpace, m_moduleInformation->nameSection->get(functionIndexSpace))).ascii().data()), |
264 | WTFMove(context.embedderEntrypointByproducts)); |
265 | } |
266 | } |
267 | |
268 | for (auto& unlinked : m_unlinkedWasmToWasmCalls) { |
269 | for (auto& call : unlinked) { |
270 | MacroAssemblerCodePtr<WasmEntryPtrTag> executableAddress; |
271 | if (m_moduleInformation->isImportedFunctionFromFunctionIndexSpace(call.functionIndexSpace)) { |
272 | // 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 |
273 | executableAddress = m_wasmToWasmExitStubs.at(call.functionIndexSpace).code(); |
274 | } else |
275 | executableAddress = m_wasmInternalFunctions.at(call.functionIndexSpace - m_moduleInformation->importFunctionCount())->entrypoint.compilation->code().retagged<WasmEntryPtrTag>(); |
276 | MacroAssembler::repatchNearCall(call.callLocation, CodeLocationLabel<WasmEntryPtrTag>(executableAddress)); |
277 | } |
278 | } |
279 | } |
280 | |
281 | void BBQPlan::initializeCallees(const CalleeInitializer& callback) |
282 | { |
283 | ASSERT(!failed()); |
284 | for (unsigned internalFunctionIndex = 0; internalFunctionIndex < m_wasmInternalFunctions.size(); ++internalFunctionIndex) { |
285 | |
286 | RefPtr<Wasm::Callee> embedderEntrypointCallee; |
287 | if (auto embedderToWasmFunction = m_embedderToWasmInternalFunctions.get(internalFunctionIndex)) { |
288 | embedderEntrypointCallee = Wasm::EmbedderEntrypointCallee::create(WTFMove(embedderToWasmFunction->entrypoint)); |
289 | MacroAssembler::repatchPointer(embedderToWasmFunction->calleeMoveLocation, CalleeBits::boxWasm(embedderEntrypointCallee.get())); |
290 | } |
291 | |
292 | InternalFunction* function = m_wasmInternalFunctions[internalFunctionIndex].get(); |
293 | size_t functionIndexSpace = internalFunctionIndex + m_moduleInformation->importFunctionCount(); |
294 | Ref<Wasm::Callee> wasmEntrypointCallee = Wasm::BBQCallee::create(WTFMove(function->entrypoint), functionIndexSpace, m_moduleInformation->nameSection->get(functionIndexSpace), WTFMove(m_tierUpCounts[internalFunctionIndex]), WTFMove(m_unlinkedWasmToWasmCalls[internalFunctionIndex])); |
295 | MacroAssembler::repatchPointer(function->calleeMoveLocation, CalleeBits::boxWasm(wasmEntrypointCallee.ptr())); |
296 | |
297 | callback(internalFunctionIndex, WTFMove(embedderEntrypointCallee), WTFMove(wasmEntrypointCallee)); |
298 | } |
299 | } |
300 | |
301 | } } // namespace JSC::Wasm |
302 | |
303 | #endif // ENABLE(WEBASSEMBLY) |
304 | |