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
44namespace JSC { namespace Wasm {
45
46namespace WasmEntryPlanInternal {
47static const bool verbose = false;
48}
49
50EntryPlan::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
58EntryPlan::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
67EntryPlan::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
75EntryPlan::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
83const 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
95void 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
102bool 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
122bool 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
153void 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.
205class EntryPlan::ThreadCountHolder {
206public:
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
226void 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
241void 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
277void 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