1 | /* |
2 | * Copyright (C) 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 "WasmEntryPlan.h" |
28 | |
29 | #include "WasmBinding.h" |
30 | #include "WasmFaultSignalHandler.h" |
31 | #include "WasmMemory.h" |
32 | #include "WasmSignatureInlines.h" |
33 | #include "WasmValidate.h" |
34 | #include <wtf/CrossThreadCopier.h> |
35 | #include <wtf/DataLog.h> |
36 | #include <wtf/Locker.h> |
37 | #include <wtf/MonotonicTime.h> |
38 | #include <wtf/StdLibExtras.h> |
39 | #include <wtf/SystemTracing.h> |
40 | #include <wtf/text/StringConcatenateNumbers.h> |
41 | |
42 | #if ENABLE(WEBASSEMBLY) |
43 | |
44 | namespace JSC { namespace Wasm { |
45 | |
46 | namespace WasmEntryPlanInternal { |
47 | static const bool verbose = false; |
48 | } |
49 | |
50 | EntryPlan::EntryPlan(Context* context, Ref<ModuleInformation> info, AsyncWork work, CompletionTask&& task, CreateEmbedderWrapper&& createEmbedderWrapper, ThrowWasmException throwWasmException) |
51 | : Base(context, WTFMove(info), WTFMove(task), WTFMove(createEmbedderWrapper), throwWasmException) |
52 | , m_streamingParser(m_moduleInformation.get(), *this) |
53 | , m_state(State::Validated) |
54 | , m_asyncWork(work) |
55 | { |
56 | } |
57 | |
58 | EntryPlan::EntryPlan(Context* context, Vector<uint8_t>&& source, AsyncWork work, CompletionTask&& task, CreateEmbedderWrapper&& createEmbedderWrapper, ThrowWasmException throwWasmException) |
59 | : Base(context, ModuleInformation::create(), WTFMove(task), WTFMove(createEmbedderWrapper), throwWasmException) |
60 | , m_source(WTFMove(source)) |
61 | , m_streamingParser(m_moduleInformation.get(), *this) |
62 | , m_state(State::Initial) |
63 | , m_asyncWork(work) |
64 | { |
65 | } |
66 | |
67 | EntryPlan::EntryPlan(Context* context, Ref<ModuleInformation> moduleInformation, CompletionTask&& task) |
68 | : Base(context, WTFMove(moduleInformation), WTFMove(task)) |
69 | , m_streamingParser(m_moduleInformation.get(), *this) |
70 | , m_state(State::Initial) |
71 | , m_asyncWork(AsyncWork::FullCompile) |
72 | { |
73 | } |
74 | |
75 | EntryPlan::EntryPlan(Context* context, AsyncWork work, CompletionTask&& task) |
76 | : Base(context, WTFMove(task)) |
77 | , m_streamingParser(m_moduleInformation.get(), *this) |
78 | , m_state(State::Initial) |
79 | , m_asyncWork(work) |
80 | { |
81 | } |
82 | |
83 | const char* EntryPlan::stateString(State state) |
84 | { |
85 | switch (state) { |
86 | case State::Initial: return "Initial" ; |
87 | case State::Validated: return "Validated" ; |
88 | case State::Prepared: return "Prepared" ; |
89 | case State::Compiled: return "Compiled" ; |
90 | case State::Completed: return "Completed" ; |
91 | } |
92 | RELEASE_ASSERT_NOT_REACHED(); |
93 | } |
94 | |
95 | void EntryPlan::moveToState(State state) |
96 | { |
97 | ASSERT(state >= m_state); |
98 | dataLogLnIf(WasmEntryPlanInternal::verbose && state != m_state, "moving to state: " , stateString(state), " from state: " , stateString(m_state)); |
99 | m_state = state; |
100 | } |
101 | |
102 | bool EntryPlan::didReceiveFunctionData(unsigned functionIndex, const FunctionData& function) |
103 | { |
104 | dataLogLnIf(WasmEntryPlanInternal::verbose, "Processing function starting at: " , function.start, " and ending at: " , function.end); |
105 | size_t functionLength = function.end - function.start; |
106 | ASSERT(functionLength == function.data.size()); |
107 | SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex]; |
108 | const Signature& signature = SignatureInformation::get(signatureIndex); |
109 | |
110 | auto validationResult = validateFunction(function, signature, m_moduleInformation.get()); |
111 | if (!validationResult) { |
112 | if (WasmEntryPlanInternal::verbose) { |
113 | for (unsigned i = 0; i < functionLength; ++i) |
114 | dataLog(RawPointer(reinterpret_cast<void*>(function.data[i])), ", " ); |
115 | dataLogLn(); |
116 | } |
117 | fail(holdLock(m_lock), makeString(validationResult.error(), ", in function at index " , String::number(functionIndex))); // FIXME make this an Expected. |
118 | } |
119 | return !!validationResult; |
120 | } |
121 | |
122 | bool EntryPlan::parseAndValidateModule(const uint8_t* source, size_t sourceLength) |
123 | { |
124 | if (m_state != State::Initial) |
125 | return true; |
126 | |
127 | dataLogLnIf(WasmEntryPlanInternal::verbose, "starting validation" ); |
128 | MonotonicTime startTime; |
129 | if (WasmEntryPlanInternal::verbose || Options::reportCompileTimes()) |
130 | startTime = MonotonicTime::now(); |
131 | |
132 | m_streamingParser.addBytes(source, sourceLength); |
133 | { |
134 | auto locker = holdLock(m_lock); |
135 | if (failed()) |
136 | return false; |
137 | } |
138 | |
139 | if (m_streamingParser.finalize() != StreamingParser::State::Finished) { |
140 | fail(holdLock(m_lock), m_streamingParser.errorMessage()); |
141 | return false; |
142 | } |
143 | |
144 | if (WasmEntryPlanInternal::verbose || Options::reportCompileTimes()) |
145 | dataLogLn("Took " , (MonotonicTime::now() - startTime).microseconds(), " us to validate module" ); |
146 | |
147 | moveToState(State::Validated); |
148 | if (m_asyncWork == Validation) |
149 | complete(holdLock(m_lock)); |
150 | return true; |
151 | } |
152 | |
153 | void EntryPlan::prepare() |
154 | { |
155 | ASSERT(m_state == State::Validated); |
156 | dataLogLnIf(WasmEntryPlanInternal::verbose, "Starting preparation" ); |
157 | |
158 | const auto& functions = m_moduleInformation->functions; |
159 | if (!tryReserveCapacity(m_wasmToWasmExitStubs, m_moduleInformation->importFunctionSignatureIndices.size(), " WebAssembly to JavaScript stubs" ) |
160 | || !tryReserveCapacity(m_unlinkedWasmToWasmCalls, functions.size(), " unlinked WebAssembly to WebAssembly calls" )) |
161 | return; |
162 | |
163 | m_unlinkedWasmToWasmCalls.resize(functions.size()); |
164 | |
165 | for (unsigned importIndex = 0; importIndex < m_moduleInformation->imports.size(); ++importIndex) { |
166 | Import* import = &m_moduleInformation->imports[importIndex]; |
167 | if (import->kind != ExternalKind::Function) |
168 | continue; |
169 | unsigned importFunctionIndex = m_wasmToWasmExitStubs.size(); |
170 | dataLogLnIf(WasmEntryPlanInternal::verbose, "Processing import function number " , importFunctionIndex, ": " , makeString(import->module), ": " , makeString(import->field)); |
171 | auto binding = wasmToWasm(importFunctionIndex); |
172 | if (UNLIKELY(!binding)) { |
173 | switch (binding.error()) { |
174 | case BindingFailure::OutOfMemory: |
175 | return fail(holdLock(m_lock), makeString("Out of executable memory at import " , String::number(importIndex))); |
176 | } |
177 | RELEASE_ASSERT_NOT_REACHED(); |
178 | } |
179 | m_wasmToWasmExitStubs.uncheckedAppend(binding.value()); |
180 | } |
181 | |
182 | const uint32_t importFunctionCount = m_moduleInformation->importFunctionCount(); |
183 | for (const auto& exp : m_moduleInformation->exports) { |
184 | if (exp.kindIndex >= importFunctionCount) |
185 | m_exportedFunctionIndices.add(exp.kindIndex - importFunctionCount); |
186 | } |
187 | |
188 | for (const auto& element : m_moduleInformation->elements) { |
189 | for (const uint32_t elementIndex : element.functionIndices) { |
190 | if (elementIndex >= importFunctionCount) |
191 | m_exportedFunctionIndices.add(elementIndex - importFunctionCount); |
192 | } |
193 | } |
194 | |
195 | if (m_moduleInformation->startFunctionIndexSpace && m_moduleInformation->startFunctionIndexSpace >= importFunctionCount) |
196 | m_exportedFunctionIndices.add(*m_moduleInformation->startFunctionIndexSpace - importFunctionCount); |
197 | |
198 | if (!prepareImpl()) |
199 | return; |
200 | |
201 | moveToState(State::Prepared); |
202 | } |
203 | |
204 | // We don't have a semaphore class... and this does kinda interesting things. |
205 | class EntryPlan::ThreadCountHolder { |
206 | public: |
207 | ThreadCountHolder(EntryPlan& plan) |
208 | : m_plan(plan) |
209 | { |
210 | LockHolder locker(m_plan.m_lock); |
211 | m_plan.m_numberOfActiveThreads++; |
212 | } |
213 | |
214 | ~ThreadCountHolder() |
215 | { |
216 | LockHolder locker(m_plan.m_lock); |
217 | m_plan.m_numberOfActiveThreads--; |
218 | |
219 | if (!m_plan.m_numberOfActiveThreads && !m_plan.hasWork()) |
220 | m_plan.complete(locker); |
221 | } |
222 | |
223 | EntryPlan& m_plan; |
224 | }; |
225 | |
226 | void EntryPlan::complete(const AbstractLocker& locker) |
227 | { |
228 | ASSERT(m_state != State::Compiled || m_currentIndex >= m_moduleInformation->functions.size()); |
229 | dataLogLnIf(WasmEntryPlanInternal::verbose, "Starting Completion" ); |
230 | |
231 | if (!failed() && m_state == State::Compiled) |
232 | didCompleteCompilation(locker); |
233 | |
234 | if (!isComplete()) { |
235 | moveToState(State::Completed); |
236 | runCompletionTasks(locker); |
237 | } |
238 | } |
239 | |
240 | |
241 | void EntryPlan::compileFunctions(CompilationEffort effort) |
242 | { |
243 | ASSERT(m_state >= State::Prepared); |
244 | dataLogLnIf(WasmEntryPlanInternal::verbose, "Starting compilation" ); |
245 | |
246 | if (!hasWork()) |
247 | return; |
248 | |
249 | Optional<TraceScope> traceScope; |
250 | if (Options::useTracePoints()) |
251 | traceScope.emplace(WebAssemblyCompileStart, WebAssemblyCompileEnd); |
252 | ThreadCountHolder holder(*this); |
253 | |
254 | size_t bytesCompiled = 0; |
255 | const auto& functions = m_moduleInformation->functions; |
256 | while (true) { |
257 | if (effort == Partial && bytesCompiled >= Options::webAssemblyPartialCompileLimit()) |
258 | return; |
259 | |
260 | uint32_t functionIndex; |
261 | { |
262 | auto locker = holdLock(m_lock); |
263 | if (m_currentIndex >= functions.size()) { |
264 | if (hasWork()) |
265 | moveToState(State::Compiled); |
266 | return; |
267 | } |
268 | functionIndex = m_currentIndex; |
269 | ++m_currentIndex; |
270 | } |
271 | |
272 | compileFunction(functionIndex); |
273 | bytesCompiled += functions[functionIndex].data.size(); |
274 | } |
275 | } |
276 | |
277 | void EntryPlan::work(CompilationEffort effort) |
278 | { |
279 | switch (m_state) { |
280 | case State::Initial: |
281 | parseAndValidateModule(m_source.data(), m_source.size()); |
282 | if (!hasWork()) { |
283 | ASSERT(isComplete()); |
284 | break; |
285 | } |
286 | FALLTHROUGH; |
287 | case State::Validated: |
288 | prepare(); |
289 | break; |
290 | case State::Prepared: |
291 | compileFunctions(effort); |
292 | break; |
293 | default: |
294 | break; |
295 | } |
296 | } |
297 | |
298 | } } // namespace JSC::Wasm |
299 | |
300 | #endif // ENABLE(WEBASSEMBLY) |
301 | |