1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
3  *
4  * Copyright 2015 Mozilla Foundation
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 #ifndef wasm_generator_h
20 #define wasm_generator_h
21 
22 #include "mozilla/Attributes.h"
23 #include "mozilla/MemoryReporting.h"
24 
25 #include "jit/MacroAssembler.h"
26 #include "threading/ProtectedData.h"
27 #include "vm/HelperThreadTask.h"
28 #include "wasm/WasmCompile.h"
29 #include "wasm/WasmModule.h"
30 #include "wasm/WasmValidate.h"
31 
32 namespace JS {
33 class OptimizedEncodingListener;
34 }
35 
36 namespace js {
37 namespace wasm {
38 
39 struct CompileTask;
40 using CompileTaskPtrVector = Vector<CompileTask*, 0, SystemAllocPolicy>;
41 
42 // FuncCompileInput contains the input for compiling a single function.
43 
44 struct FuncCompileInput {
45   const uint8_t* begin;
46   const uint8_t* end;
47   uint32_t index;
48   uint32_t lineOrBytecode;
49   Uint32Vector callSiteLineNums;
50 
FuncCompileInputFuncCompileInput51   FuncCompileInput(uint32_t index, uint32_t lineOrBytecode,
52                    const uint8_t* begin, const uint8_t* end,
53                    Uint32Vector&& callSiteLineNums)
54       : begin(begin),
55         end(end),
56         index(index),
57         lineOrBytecode(lineOrBytecode),
58         callSiteLineNums(std::move(callSiteLineNums)) {}
59 };
60 
61 using FuncCompileInputVector = Vector<FuncCompileInput, 8, SystemAllocPolicy>;
62 
63 void CraneliftFreeReusableData(void* ptr);
64 
65 struct CraneliftReusableDataDtor {
operatorCraneliftReusableDataDtor66   void operator()(void* ptr) { CraneliftFreeReusableData(ptr); }
67 };
68 
69 using CraneliftReusableData =
70     mozilla::UniquePtr<void*, CraneliftReusableDataDtor>;
71 
72 // CompiledCode contains the resulting code and metadata for a set of compiled
73 // input functions or stubs.
74 
75 struct CompiledCode {
76   Bytes bytes;
77   CodeRangeVector codeRanges;
78   CallSiteVector callSites;
79   CallSiteTargetVector callSiteTargets;
80   TrapSiteVectorArray trapSites;
81   SymbolicAccessVector symbolicAccesses;
82   jit::CodeLabelVector codeLabels;
83   StackMaps stackMaps;
84   CraneliftReusableData craneliftReusableData;
85 #ifdef ENABLE_WASM_EXCEPTIONS
86   WasmTryNoteVector tryNotes;
87 #endif
88 
89   [[nodiscard]] bool swap(jit::MacroAssembler& masm);
90   [[nodiscard]] bool swapCranelift(jit::MacroAssembler& masm,
91                                    CraneliftReusableData& craneliftData);
92 
clearCompiledCode93   void clear() {
94     bytes.clear();
95     codeRanges.clear();
96     callSites.clear();
97     callSiteTargets.clear();
98     trapSites.clear();
99     symbolicAccesses.clear();
100     codeLabels.clear();
101     stackMaps.clear();
102 #ifdef ENABLE_WASM_EXCEPTIONS
103     tryNotes.clear();
104 #endif
105     // The cranelift reusable data resets itself lazily.
106     MOZ_ASSERT(empty());
107   }
108 
emptyCompiledCode109   bool empty() {
110     return bytes.empty() && codeRanges.empty() && callSites.empty() &&
111            callSiteTargets.empty() && trapSites.empty() &&
112            symbolicAccesses.empty() && codeLabels.empty() &&
113 #ifdef ENABLE_WASM_EXCEPTIONS
114            tryNotes.empty() && stackMaps.empty();
115 #else
116            stackMaps.empty();
117 #endif
118   }
119 
120   size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
121 };
122 
123 // The CompileTaskState of a ModuleGenerator contains the mutable state shared
124 // between helper threads executing CompileTasks. Each CompileTask started on a
125 // helper thread eventually either ends up in the 'finished' list or increments
126 // 'numFailed'.
127 
128 struct CompileTaskState {
129   HelperThreadLockData<CompileTaskPtrVector> finished_;
130   HelperThreadLockData<uint32_t> numFailed_;
131   HelperThreadLockData<UniqueChars> errorMessage_;
132   HelperThreadLockData<ConditionVariable> condVar_;
133 
CompileTaskStateCompileTaskState134   CompileTaskState() : numFailed_(0) {}
~CompileTaskStateCompileTaskState135   ~CompileTaskState() {
136     MOZ_ASSERT(finished_.refNoCheck().empty());
137     MOZ_ASSERT(!numFailed_.refNoCheck());
138   }
139 
finishedCompileTaskState140   CompileTaskPtrVector& finished() { return finished_.ref(); }
numFailedCompileTaskState141   uint32_t& numFailed() { return numFailed_.ref(); }
errorMessageCompileTaskState142   UniqueChars& errorMessage() { return errorMessage_.ref(); }
condVarCompileTaskState143   ConditionVariable& condVar() { return condVar_.ref(); }
144 };
145 
146 // A CompileTask holds a batch of input functions that are to be compiled on a
147 // helper thread as well as, eventually, the results of compilation.
148 
149 struct CompileTask : public HelperThreadTask {
150   const ModuleEnvironment& moduleEnv;
151   const CompilerEnvironment& compilerEnv;
152 
153   CompileTaskState& state;
154   LifoAlloc lifo;
155   FuncCompileInputVector inputs;
156   CompiledCode output;
157 
CompileTaskCompileTask158   CompileTask(const ModuleEnvironment& moduleEnv,
159               const CompilerEnvironment& compilerEnv, CompileTaskState& state,
160               size_t defaultChunkSize)
161       : moduleEnv(moduleEnv),
162         compilerEnv(compilerEnv),
163         state(state),
164         lifo(defaultChunkSize) {}
165 
166   virtual ~CompileTask() = default;
167 
168   size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
169 
170   void runHelperThreadTask(AutoLockHelperThreadState& locked) override;
171   ThreadType threadType() override;
172 };
173 
174 // A ModuleGenerator encapsulates the creation of a wasm module. During the
175 // lifetime of a ModuleGenerator, a sequence of FunctionGenerators are created
176 // and destroyed to compile the individual function bodies. After generating all
177 // functions, ModuleGenerator::finish() must be called to complete the
178 // compilation and extract the resulting wasm module.
179 
180 class MOZ_STACK_CLASS ModuleGenerator {
181   using CompileTaskVector = Vector<CompileTask, 0, SystemAllocPolicy>;
182   using CodeOffsetVector = Vector<jit::CodeOffset, 0, SystemAllocPolicy>;
183   struct CallFarJump {
184     uint32_t funcIndex;
185     jit::CodeOffset jump;
CallFarJumpCallFarJump186     CallFarJump(uint32_t fi, jit::CodeOffset j) : funcIndex(fi), jump(j) {}
187   };
188   using CallFarJumpVector = Vector<CallFarJump, 0, SystemAllocPolicy>;
189 
190   // Constant parameters
191   SharedCompileArgs const compileArgs_;
192   UniqueChars* const error_;
193   UniqueCharsVector* const warnings_;
194   const Atomic<bool>* const cancelled_;
195   ModuleEnvironment* const moduleEnv_;
196   CompilerEnvironment* const compilerEnv_;
197 
198   // Data that is moved into the result of finish()
199   UniqueLinkData linkData_;
200   UniqueMetadataTier metadataTier_;
201   MutableMetadata metadata_;
202 
203   // Data scoped to the ModuleGenerator's lifetime
204   CompileTaskState taskState_;
205   LifoAlloc lifo_;
206   jit::JitContext jcx_;
207   jit::TempAllocator masmAlloc_;
208   jit::WasmMacroAssembler masm_;
209   Uint32Vector funcToCodeRange_;
210   uint32_t debugTrapCodeOffset_;
211   CallFarJumpVector callFarJumps_;
212   CallSiteTargetVector callSiteTargets_;
213   uint32_t lastPatchedCallSite_;
214   uint32_t startOfUnpatchedCallsites_;
215   CodeOffsetVector debugTrapFarJumps_;
216 
217   // Parallel compilation
218   bool parallel_;
219   uint32_t outstanding_;
220   CompileTaskVector tasks_;
221   CompileTaskPtrVector freeTasks_;
222   CompileTask* currentTask_;
223   uint32_t batchedBytecode_;
224 
225   // Assertions
226   DebugOnly<bool> finishedFuncDefs_;
227 
228   bool allocateGlobalBytes(uint32_t bytes, uint32_t align,
229                            uint32_t* globalDataOff);
230 
231   bool funcIsCompiled(uint32_t funcIndex) const;
232   const CodeRange& funcCodeRange(uint32_t funcIndex) const;
233   bool linkCallSites();
234   void noteCodeRange(uint32_t codeRangeIndex, const CodeRange& codeRange);
235   bool linkCompiledCode(CompiledCode& code);
236   bool locallyCompileCurrentTask();
237   bool finishTask(CompileTask* task);
238   bool launchBatchCompile();
239   bool finishOutstandingTask();
240   bool finishCodegen();
241   bool finishMetadataTier();
242   UniqueCodeTier finishCodeTier();
243   SharedMetadata finishMetadata(const Bytes& bytecode);
244 
isAsmJS()245   bool isAsmJS() const { return moduleEnv_->isAsmJS(); }
tier()246   Tier tier() const { return compilerEnv_->tier(); }
mode()247   CompileMode mode() const { return compilerEnv_->mode(); }
debugEnabled()248   bool debugEnabled() const { return compilerEnv_->debugEnabled(); }
249 
250   void warnf(const char* msg, ...) MOZ_FORMAT_PRINTF(2, 3);
251 
252  public:
253   ModuleGenerator(const CompileArgs& args, ModuleEnvironment* moduleEnv,
254                   CompilerEnvironment* compilerEnv,
255                   const Atomic<bool>* cancelled, UniqueChars* error,
256                   UniqueCharsVector* warnings);
257   ~ModuleGenerator();
258   [[nodiscard]] bool init(Metadata* maybeAsmJSMetadata = nullptr);
259 
260   // Before finishFuncDefs() is called, compileFuncDef() must be called once
261   // for each funcIndex in the range [0, env->numFuncDefs()).
262 
263   [[nodiscard]] bool compileFuncDef(
264       uint32_t funcIndex, uint32_t lineOrBytecode, const uint8_t* begin,
265       const uint8_t* end, Uint32Vector&& callSiteLineNums = Uint32Vector());
266 
267   // Must be called after the last compileFuncDef() and before finishModule()
268   // or finishTier2().
269 
270   [[nodiscard]] bool finishFuncDefs();
271 
272   // If env->mode is Once or Tier1, finishModule() must be called to generate
273   // a new Module. Otherwise, if env->mode is Tier2, finishTier2() must be
274   // called to augment the given Module with tier 2 code.
275 
276   SharedModule finishModule(
277       const ShareableBytes& bytecode,
278       JS::OptimizedEncodingListener* maybeTier2Listener = nullptr);
279   [[nodiscard]] bool finishTier2(const Module& module);
280 };
281 
282 }  // namespace wasm
283 }  // namespace js
284 
285 #endif  // wasm_generator_h
286