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/MemoryReporting.h"
23 
24 #include "jit/MacroAssembler.h"
25 #include "threading/ProtectedData.h"
26 #include "vm/HelperThreadTask.h"
27 #include "wasm/WasmCompile.h"
28 #include "wasm/WasmModule.h"
29 #include "wasm/WasmValidate.h"
30 
31 namespace js {
32 namespace wasm {
33 
34 struct CompileTask;
35 using CompileTaskPtrVector = Vector<CompileTask*, 0, SystemAllocPolicy>;
36 
37 // FuncCompileInput contains the input for compiling a single function.
38 
39 struct FuncCompileInput {
40   const uint8_t* begin;
41   const uint8_t* end;
42   uint32_t index;
43   uint32_t lineOrBytecode;
44   Uint32Vector callSiteLineNums;
45 
FuncCompileInputFuncCompileInput46   FuncCompileInput(uint32_t index, uint32_t lineOrBytecode,
47                    const uint8_t* begin, const uint8_t* end,
48                    Uint32Vector&& callSiteLineNums)
49       : begin(begin),
50         end(end),
51         index(index),
52         lineOrBytecode(lineOrBytecode),
53         callSiteLineNums(std::move(callSiteLineNums)) {}
54 };
55 
56 using FuncCompileInputVector = Vector<FuncCompileInput, 8, SystemAllocPolicy>;
57 
58 void CraneliftFreeReusableData(void* ptr);
59 
60 struct CraneliftReusableDataDtor {
operatorCraneliftReusableDataDtor61   void operator()(void* ptr) { CraneliftFreeReusableData(ptr); }
62 };
63 
64 using CraneliftReusableData =
65     mozilla::UniquePtr<void*, CraneliftReusableDataDtor>;
66 
67 // CompiledCode contains the resulting code and metadata for a set of compiled
68 // input functions or stubs.
69 
70 struct CompiledCode {
71   Bytes bytes;
72   CodeRangeVector codeRanges;
73   CallSiteVector callSites;
74   CallSiteTargetVector callSiteTargets;
75   TrapSiteVectorArray trapSites;
76   SymbolicAccessVector symbolicAccesses;
77   jit::CodeLabelVector codeLabels;
78   StackMaps stackMaps;
79   CraneliftReusableData craneliftReusableData;
80 #ifdef ENABLE_WASM_EXCEPTIONS
81   WasmTryNoteVector tryNotes;
82 #endif
83 
84   [[nodiscard]] bool swap(jit::MacroAssembler& masm);
85   [[nodiscard]] bool swapCranelift(jit::MacroAssembler& masm,
86                                    CraneliftReusableData& craneliftData);
87 
clearCompiledCode88   void clear() {
89     bytes.clear();
90     codeRanges.clear();
91     callSites.clear();
92     callSiteTargets.clear();
93     trapSites.clear();
94     symbolicAccesses.clear();
95     codeLabels.clear();
96     stackMaps.clear();
97 #ifdef ENABLE_WASM_EXCEPTIONS
98     tryNotes.clear();
99 #endif
100     // The cranelift reusable data resets itself lazily.
101     MOZ_ASSERT(empty());
102   }
103 
emptyCompiledCode104   bool empty() {
105     return bytes.empty() && codeRanges.empty() && callSites.empty() &&
106            callSiteTargets.empty() && trapSites.empty() &&
107            symbolicAccesses.empty() && codeLabels.empty() &&
108 #ifdef ENABLE_WASM_EXCEPTIONS
109            tryNotes.empty() && stackMaps.empty();
110 #else
111            stackMaps.empty();
112 #endif
113   }
114 
115   size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
116 };
117 
118 // The CompileTaskState of a ModuleGenerator contains the mutable state shared
119 // between helper threads executing CompileTasks. Each CompileTask started on a
120 // helper thread eventually either ends up in the 'finished' list or increments
121 // 'numFailed'.
122 
123 struct CompileTaskState {
124   HelperThreadLockData<CompileTaskPtrVector> finished_;
125   HelperThreadLockData<uint32_t> numFailed_;
126   HelperThreadLockData<UniqueChars> errorMessage_;
127   HelperThreadLockData<ConditionVariable> condVar_;
128 
CompileTaskStateCompileTaskState129   CompileTaskState() : numFailed_(0) {}
~CompileTaskStateCompileTaskState130   ~CompileTaskState() {
131     MOZ_ASSERT(finished_.refNoCheck().empty());
132     MOZ_ASSERT(!numFailed_.refNoCheck());
133   }
134 
finishedCompileTaskState135   CompileTaskPtrVector& finished() { return finished_.ref(); }
numFailedCompileTaskState136   uint32_t& numFailed() { return numFailed_.ref(); }
errorMessageCompileTaskState137   UniqueChars& errorMessage() { return errorMessage_.ref(); }
condVarCompileTaskState138   ConditionVariable& condVar() { return condVar_.ref(); }
139 };
140 
141 // A CompileTask holds a batch of input functions that are to be compiled on a
142 // helper thread as well as, eventually, the results of compilation.
143 
144 struct CompileTask : public HelperThreadTask {
145   const ModuleEnvironment& moduleEnv;
146   const CompilerEnvironment& compilerEnv;
147 
148   CompileTaskState& state;
149   LifoAlloc lifo;
150   FuncCompileInputVector inputs;
151   CompiledCode output;
152 
CompileTaskCompileTask153   CompileTask(const ModuleEnvironment& moduleEnv,
154               const CompilerEnvironment& compilerEnv, CompileTaskState& state,
155               size_t defaultChunkSize)
156       : moduleEnv(moduleEnv),
157         compilerEnv(compilerEnv),
158         state(state),
159         lifo(defaultChunkSize) {}
160 
161   virtual ~CompileTask() = default;
162 
163   size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
164 
165   void runHelperThreadTask(AutoLockHelperThreadState& locked) override;
166   ThreadType threadType() override;
167 };
168 
169 // A ModuleGenerator encapsulates the creation of a wasm module. During the
170 // lifetime of a ModuleGenerator, a sequence of FunctionGenerators are created
171 // and destroyed to compile the individual function bodies. After generating all
172 // functions, ModuleGenerator::finish() must be called to complete the
173 // compilation and extract the resulting wasm module.
174 
175 class MOZ_STACK_CLASS ModuleGenerator {
176   using CompileTaskVector = Vector<CompileTask, 0, SystemAllocPolicy>;
177   using CodeOffsetVector = Vector<jit::CodeOffset, 0, SystemAllocPolicy>;
178   struct CallFarJump {
179     uint32_t funcIndex;
180     jit::CodeOffset jump;
CallFarJumpCallFarJump181     CallFarJump(uint32_t fi, jit::CodeOffset j) : funcIndex(fi), jump(j) {}
182   };
183   using CallFarJumpVector = Vector<CallFarJump, 0, SystemAllocPolicy>;
184 
185   // Constant parameters
186   SharedCompileArgs const compileArgs_;
187   UniqueChars* const error_;
188   const Atomic<bool>* const cancelled_;
189   ModuleEnvironment* const moduleEnv_;
190   CompilerEnvironment* const compilerEnv_;
191 
192   // Data that is moved into the result of finish()
193   UniqueLinkData linkData_;
194   UniqueMetadataTier metadataTier_;
195   MutableMetadata metadata_;
196 
197   // Data scoped to the ModuleGenerator's lifetime
198   CompileTaskState taskState_;
199   LifoAlloc lifo_;
200   jit::JitContext jcx_;
201   jit::TempAllocator masmAlloc_;
202   jit::WasmMacroAssembler masm_;
203   Uint32Vector funcToCodeRange_;
204   uint32_t debugTrapCodeOffset_;
205   CallFarJumpVector callFarJumps_;
206   CallSiteTargetVector callSiteTargets_;
207   uint32_t lastPatchedCallSite_;
208   uint32_t startOfUnpatchedCallsites_;
209   CodeOffsetVector debugTrapFarJumps_;
210 
211   // Parallel compilation
212   bool parallel_;
213   uint32_t outstanding_;
214   CompileTaskVector tasks_;
215   CompileTaskPtrVector freeTasks_;
216   CompileTask* currentTask_;
217   uint32_t batchedBytecode_;
218 
219   // Assertions
220   DebugOnly<bool> finishedFuncDefs_;
221 
222   bool allocateGlobalBytes(uint32_t bytes, uint32_t align,
223                            uint32_t* globalDataOff);
224 
225   bool funcIsCompiled(uint32_t funcIndex) const;
226   const CodeRange& funcCodeRange(uint32_t funcIndex) const;
227   bool linkCallSites();
228   void noteCodeRange(uint32_t codeRangeIndex, const CodeRange& codeRange);
229   bool linkCompiledCode(CompiledCode& code);
230   bool locallyCompileCurrentTask();
231   bool finishTask(CompileTask* task);
232   bool launchBatchCompile();
233   bool finishOutstandingTask();
234   bool finishCodegen();
235   bool finishMetadataTier();
236   UniqueCodeTier finishCodeTier();
237   SharedMetadata finishMetadata(const Bytes& bytecode);
238 
isAsmJS()239   bool isAsmJS() const { return moduleEnv_->isAsmJS(); }
tier()240   Tier tier() const { return compilerEnv_->tier(); }
mode()241   CompileMode mode() const { return compilerEnv_->mode(); }
debugEnabled()242   bool debugEnabled() const { return compilerEnv_->debugEnabled(); }
243 
244  public:
245   ModuleGenerator(const CompileArgs& args, ModuleEnvironment* moduleEnv,
246                   CompilerEnvironment* compilerEnv,
247                   const Atomic<bool>* cancelled, UniqueChars* error);
248   ~ModuleGenerator();
249   [[nodiscard]] bool init(Metadata* maybeAsmJSMetadata = nullptr);
250 
251   // Before finishFuncDefs() is called, compileFuncDef() must be called once
252   // for each funcIndex in the range [0, env->numFuncDefs()).
253 
254   [[nodiscard]] bool compileFuncDef(
255       uint32_t funcIndex, uint32_t lineOrBytecode, const uint8_t* begin,
256       const uint8_t* end, Uint32Vector&& callSiteLineNums = Uint32Vector());
257 
258   // Must be called after the last compileFuncDef() and before finishModule()
259   // or finishTier2().
260 
261   [[nodiscard]] bool finishFuncDefs();
262 
263   // If env->mode is Once or Tier1, finishModule() must be called to generate
264   // a new Module. Otherwise, if env->mode is Tier2, finishTier2() must be
265   // called to augment the given Module with tier 2 code.
266 
267   SharedModule finishModule(
268       const ShareableBytes& bytecode,
269       JS::OptimizedEncodingListener* maybeTier2Listener = nullptr);
270   [[nodiscard]] bool finishTier2(const Module& module);
271 };
272 
273 }  // namespace wasm
274 }  // namespace js
275 
276 #endif  // wasm_generator_h
277