/src/hermes/lib/VM/Domain.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) Meta Platforms, Inc. and affiliates. |
3 | | * |
4 | | * This source code is licensed under the MIT license found in the |
5 | | * LICENSE file in the root directory of this source tree. |
6 | | */ |
7 | | |
8 | | #include "hermes/VM/Domain.h" |
9 | | |
10 | | #include "hermes/VM/Callable.h" |
11 | | #include "hermes/VM/GCPointer-inline.h" |
12 | | #include "hermes/VM/JSLib.h" |
13 | | #include "hermes/VM/Profiler/SamplingProfiler.h" |
14 | | #pragma GCC diagnostic push |
15 | | |
16 | | #ifdef HERMES_COMPILER_SUPPORTS_WSHORTEN_64_TO_32 |
17 | | #pragma GCC diagnostic ignored "-Wshorten-64-to-32" |
18 | | #endif |
19 | | namespace hermes { |
20 | | namespace vm { |
21 | | |
22 | | const VTable Domain::vt{ |
23 | | CellKind::DomainKind, |
24 | | cellSize<Domain>(), |
25 | | _finalizeImpl, |
26 | | _mallocSizeImpl, |
27 | | nullptr |
28 | | #ifdef HERMES_MEMORY_INSTRUMENTATION |
29 | | , |
30 | | VTable::HeapSnapshotMetadata{ |
31 | | HeapSnapshot::NodeType::Code, |
32 | | nullptr, |
33 | | Domain::_snapshotAddEdgesImpl, |
34 | | Domain::_snapshotAddNodesImpl, |
35 | | nullptr} |
36 | | #endif |
37 | | }; |
38 | | |
39 | 1 | void DomainBuildMeta(const GCCell *cell, Metadata::Builder &mb) { |
40 | 1 | const auto *self = static_cast<const Domain *>(cell); |
41 | 1 | mb.setVTable(&Domain::vt); |
42 | 1 | mb.addField("cjsModules", &self->cjsModules_); |
43 | 1 | mb.addField("throwingRequire", &self->throwingRequire_); |
44 | 1 | } |
45 | | |
46 | 373 | PseudoHandle<Domain> Domain::create(Runtime &runtime) { |
47 | 373 | auto *cell = runtime.makeAFixed<Domain, HasFinalizer::Yes>(); |
48 | 373 | auto self = createPseudoHandle(cell); |
49 | 373 | return self; |
50 | 373 | } |
51 | | |
52 | 373 | void Domain::_finalizeImpl(GCCell *cell, GC &gc) { |
53 | 373 | auto *self = vmcast<Domain>(cell); |
54 | 453 | for (RuntimeModule *rm : self->runtimeModules_) { |
55 | 453 | gc.getIDTracker().untrackNative(rm); |
56 | 453 | } |
57 | 373 | self->~Domain(); |
58 | 373 | } |
59 | | |
60 | | PseudoHandle<NativeFunction> Domain::getThrowingRequire( |
61 | 0 | Runtime &runtime) const { |
62 | 0 | return createPseudoHandle(throwingRequire_.get(runtime)); |
63 | 0 | } |
64 | | |
65 | 0 | size_t Domain::_mallocSizeImpl(GCCell *cell) { |
66 | 0 | auto *self = vmcast<Domain>(cell); |
67 | 0 | size_t rmSize = 0; |
68 | 0 | for (auto *rm : self->runtimeModules_) |
69 | 0 | rmSize += sizeof(RuntimeModule) + rm->additionalMemorySize(); |
70 | |
|
71 | 0 | return self->cjsRuntimeModules_.capacity_in_bytes() + |
72 | 0 | self->cjsModuleTable_.getMemorySize() + |
73 | 0 | self->runtimeModules_.capacity_in_bytes() + rmSize; |
74 | 0 | } |
75 | | |
76 | | #ifdef HERMES_MEMORY_INSTRUMENTATION |
77 | 0 | void Domain::_snapshotAddEdgesImpl(GCCell *cell, GC &gc, HeapSnapshot &snap) { |
78 | 0 | auto *const self = vmcast<Domain>(cell); |
79 | 0 | for (RuntimeModule *rm : self->runtimeModules_) |
80 | 0 | snap.addNamedEdge( |
81 | 0 | HeapSnapshot::EdgeType::Internal, "RuntimeModule", gc.getNativeID(rm)); |
82 | 0 | } |
83 | | |
84 | 0 | void Domain::_snapshotAddNodesImpl(GCCell *cell, GC &gc, HeapSnapshot &snap) { |
85 | 0 | auto *const self = vmcast<Domain>(cell); |
86 | 0 | for (RuntimeModule *rm : self->runtimeModules_) { |
87 | | // Create a native node for each RuntimeModule owned by this domain. |
88 | 0 | rm->snapshotAddNodes(gc, snap); |
89 | 0 | snap.beginNode(); |
90 | 0 | rm->snapshotAddEdges(gc, snap); |
91 | 0 | snap.endNode( |
92 | 0 | HeapSnapshot::NodeType::Native, |
93 | 0 | "RuntimeModule", |
94 | 0 | gc.getNativeID(rm), |
95 | 0 | sizeof(RuntimeModule) + rm->additionalMemorySize(), |
96 | 0 | 0); |
97 | 0 | } |
98 | 0 | } |
99 | | #endif |
100 | | |
101 | | ExecutionStatus Domain::importCJSModuleTable( |
102 | | Handle<Domain> self, |
103 | | Runtime &runtime, |
104 | 260 | RuntimeModule *runtimeModule) { |
105 | 260 | if (runtimeModule->getBytecode()->getCJSModuleTable().empty() && |
106 | 260 | runtimeModule->getBytecode()->getCJSModuleTableStatic().empty()) { |
107 | | // Nothing to do, avoid allocating and simply return. |
108 | 260 | return ExecutionStatus::RETURNED; |
109 | 260 | } |
110 | | |
111 | 0 | static_assert( |
112 | 0 | CJSModuleSize < 10, "CJSModuleSize must be small to avoid overflow"); |
113 | |
|
114 | 0 | MutableHandle<ArrayStorage> cjsModules{runtime}; |
115 | |
|
116 | 0 | if (!self->cjsModules_) { |
117 | | // Create the module table on first import. |
118 | | // If module IDs are contiguous and start from 0, we won't need to resize |
119 | | // for this RuntimeModule. |
120 | |
|
121 | 0 | const uint64_t firstSegmentModules = |
122 | 0 | runtimeModule->getBytecode()->getCJSModuleTable().size() + |
123 | 0 | runtimeModule->getBytecode()->getCJSModuleTableStatic().size(); |
124 | |
|
125 | 0 | assert( |
126 | 0 | firstSegmentModules <= std::numeric_limits<uint32_t>::max() && |
127 | 0 | "number of modules is 32 bits due to the bytecode format"); |
128 | | |
129 | | // Use uint64_t to allow us to check for overflow. |
130 | 0 | const uint64_t requiredSize = firstSegmentModules * CJSModuleSize; |
131 | 0 | if (requiredSize > std::numeric_limits<uint32_t>::max()) { |
132 | 0 | return runtime.raiseRangeError("Loaded module count exceeded limit"); |
133 | 0 | } |
134 | | |
135 | 0 | auto cjsModulesRes = ArrayStorage::create(runtime, requiredSize); |
136 | 0 | if (LLVM_UNLIKELY(cjsModulesRes == ExecutionStatus::EXCEPTION)) { |
137 | 0 | return ExecutionStatus::EXCEPTION; |
138 | 0 | } |
139 | 0 | cjsModules = vmcast<ArrayStorage>(*cjsModulesRes); |
140 | 0 | self->cjsRuntimeModules_.reserve(firstSegmentModules); |
141 | 0 | for (size_t i = self->cjsRuntimeModules_.size(); i < firstSegmentModules; |
142 | 0 | i++) { |
143 | 0 | self->cjsRuntimeModules_.push_back(nullptr, runtime.getHeap()); |
144 | 0 | } |
145 | |
|
146 | 0 | auto requireFn = NativeFunction::create( |
147 | 0 | runtime, |
148 | 0 | Handle<JSObject>::vmcast(&runtime.functionPrototype), |
149 | 0 | (void *)TypeErrorKind::InvalidDynamicRequire, |
150 | 0 | throwTypeError, |
151 | 0 | Predefined::getSymbolID(Predefined::emptyString), |
152 | 0 | 0, |
153 | 0 | Runtime::makeNullHandle<JSObject>()); |
154 | |
|
155 | 0 | auto context = RequireContext::create( |
156 | 0 | runtime, |
157 | 0 | self, |
158 | 0 | runtime.getPredefinedStringHandle(Predefined::emptyString)); |
159 | | |
160 | | // Set the require.context property. |
161 | 0 | PropertyFlags pf = PropertyFlags::defaultNewNamedPropertyFlags(); |
162 | 0 | pf.writable = 0; |
163 | 0 | pf.configurable = 0; |
164 | 0 | if (LLVM_UNLIKELY( |
165 | 0 | JSObject::defineNewOwnProperty( |
166 | 0 | requireFn, |
167 | 0 | runtime, |
168 | 0 | Predefined::getSymbolID(Predefined::context), |
169 | 0 | pf, |
170 | 0 | context) == ExecutionStatus::EXCEPTION)) { |
171 | 0 | return ExecutionStatus::EXCEPTION; |
172 | 0 | } |
173 | | |
174 | 0 | self->throwingRequire_.set(runtime, *requireFn, runtime.getHeap()); |
175 | 0 | } else { |
176 | 0 | cjsModules = self->cjsModules_.get(runtime); |
177 | 0 | } |
178 | | |
179 | 0 | assert(cjsModules && "cjsModules not set"); |
180 | | |
181 | | // Find the maximum module ID so we can resize cjsModules at most once per |
182 | | // RuntimeModule. |
183 | 0 | uint64_t maxModuleID = cjsModules->size() / CJSModuleSize; |
184 | | |
185 | | // The non-static module table does not store module IDs. They are assigned |
186 | | // during registration by counting insertions to cjsModuleTable_. Count the |
187 | | // insertions up front. |
188 | 0 | for (const auto &pair : runtimeModule->getBytecode()->getCJSModuleTable()) { |
189 | 0 | SymbolID symbolId = |
190 | 0 | runtimeModule->getSymbolIDFromStringIDMayAllocate(pair.first); |
191 | 0 | if (self->cjsModuleTable_.find(symbolId) == self->cjsModuleTable_.end()) { |
192 | 0 | ++maxModuleID; |
193 | 0 | } |
194 | 0 | } |
195 | | |
196 | | // The static module table stores module IDs in an arbitrary order. Scan for |
197 | | // the maximum ID. |
198 | 0 | for (const auto &pair : |
199 | 0 | runtimeModule->getBytecode()->getCJSModuleTableStatic()) { |
200 | 0 | const auto &moduleID = pair.first; |
201 | 0 | if (moduleID > maxModuleID) { |
202 | 0 | maxModuleID = moduleID; |
203 | 0 | } |
204 | 0 | } |
205 | |
|
206 | 0 | assert( |
207 | 0 | maxModuleID <= std::numeric_limits<uint32_t>::max() && |
208 | 0 | "number of modules is 32 bits due to the bytecode format"); |
209 | | |
210 | | // Use uint64_t to allow us to check for overflow. |
211 | 0 | const uint64_t requiredSize = (maxModuleID + 1) * CJSModuleSize; |
212 | 0 | if (requiredSize > std::numeric_limits<uint32_t>::max()) { |
213 | 0 | return runtime.raiseRangeError("Loaded module count exceeded limit"); |
214 | 0 | } |
215 | | |
216 | | // Resize the array to allow for the new modules, if necessary. |
217 | 0 | if (requiredSize > cjsModules->size()) { |
218 | 0 | if (LLVM_UNLIKELY( |
219 | 0 | ArrayStorage::resize(cjsModules, runtime, requiredSize) == |
220 | 0 | ExecutionStatus::EXCEPTION)) { |
221 | 0 | return ExecutionStatus::EXCEPTION; |
222 | 0 | } |
223 | 0 | self->cjsRuntimeModules_.reserve(maxModuleID + 1); |
224 | 0 | for (size_t i = self->cjsRuntimeModules_.size(); i <= maxModuleID; i++) { |
225 | 0 | self->cjsRuntimeModules_.push_back(nullptr, runtime.getHeap()); |
226 | 0 | } |
227 | 0 | } |
228 | | |
229 | | /// \return Whether the module with ID \param moduleID has been registered in |
230 | | /// cjsModules. \pre Space has been allocated for this module's record in |
231 | | /// cjsModules. |
232 | 0 | const auto isModuleRegistered = [&cjsModules, |
233 | 0 | maxModuleID](uint32_t moduleID) -> bool { |
234 | 0 | assert( |
235 | 0 | moduleID <= maxModuleID && |
236 | 0 | "CJS module ID exceeds maximum known module ID"); |
237 | 0 | (void)maxModuleID; |
238 | 0 | uint32_t index = moduleID * CJSModuleSize; |
239 | 0 | uint32_t requiredSize = index + CJSModuleSize; |
240 | 0 | assert( |
241 | 0 | cjsModules->size() >= requiredSize && |
242 | 0 | "CJS module ID exceeds allocated storage"); |
243 | 0 | (void)requiredSize; |
244 | 0 | return !cjsModules->at(index + FunctionIndexOffset).isEmpty(); |
245 | 0 | }; |
246 | | |
247 | | /// Register CJS module \param moduleID in the runtime module table. |
248 | | /// \return The index into cjsModules where this module's record begins. |
249 | | /// \pre Space has been allocated for this module's record in cjsModules. |
250 | | /// \pre Space has been allocated for this module's RuntimeModule* in |
251 | | /// cjsRuntimeModules_. |
252 | | /// \pre There is no module already registered under moduleID. |
253 | 0 | auto &cjsEntryModuleID = self->cjsEntryModuleID_; |
254 | 0 | auto &cjsRuntimeModules = self->cjsRuntimeModules_; |
255 | 0 | const auto registerModule = |
256 | 0 | [&runtime, |
257 | 0 | &cjsModules, |
258 | 0 | &cjsRuntimeModules, |
259 | 0 | runtimeModule, |
260 | 0 | &isModuleRegistered, |
261 | 0 | &cjsEntryModuleID](uint32_t moduleID, uint32_t functionID) -> uint32_t { |
262 | 0 | assert(!isModuleRegistered(moduleID) && "CJS module ID collision occurred"); |
263 | 0 | (void)isModuleRegistered; |
264 | 0 | if (LLVM_UNLIKELY(!cjsEntryModuleID.hasValue())) { |
265 | 0 | cjsEntryModuleID = moduleID; |
266 | 0 | } |
267 | 0 | uint32_t index = moduleID * CJSModuleSize; |
268 | 0 | cjsModules->set( |
269 | 0 | index + CachedExportsOffset, |
270 | 0 | HermesValue::encodeEmptyValue(), |
271 | 0 | runtime.getHeap()); |
272 | 0 | cjsModules->set( |
273 | 0 | index + ModuleOffset, |
274 | 0 | HermesValue::encodeNullValue(), |
275 | 0 | runtime.getHeap()); |
276 | 0 | cjsModules->set( |
277 | 0 | index + FunctionIndexOffset, |
278 | 0 | HermesValue::encodeNativeUInt32(functionID), |
279 | 0 | runtime.getHeap()); |
280 | 0 | cjsRuntimeModules[moduleID] = runtimeModule; |
281 | 0 | assert(isModuleRegistered(moduleID) && "CJS module was not registered"); |
282 | 0 | return index; |
283 | 0 | }; |
284 | | |
285 | | // Import full table that allows dynamic requires. |
286 | 0 | for (const auto &pair : runtimeModule->getBytecode()->getCJSModuleTable()) { |
287 | 0 | SymbolID symbolId = |
288 | 0 | runtimeModule->getSymbolIDFromStringIDMayAllocate(pair.first); |
289 | 0 | auto emplaceRes = self->cjsModuleTable_.try_emplace(symbolId, 0xffffffff); |
290 | 0 | if (emplaceRes.second) { |
291 | | // This module has not been registered before. |
292 | | // Assign it an arbitrary unused module ID, because nothing will be |
293 | | // referencing that ID from outside Domain. |
294 | | // Counting insertions to cjsModuleTable_ is a valid source of unique IDs |
295 | | // since a given Domain uses either dynamic requires or statically |
296 | | // resolved requires. |
297 | 0 | uint32_t moduleID = self->cjsModuleTable_.size() - 1; |
298 | 0 | const auto functionID = pair.second; |
299 | 0 | auto index = registerModule(moduleID, functionID); |
300 | | // Update the mapping from symbolId to an index into cjsModules. |
301 | 0 | emplaceRes.first->second = index; |
302 | 0 | } |
303 | 0 | } |
304 | | |
305 | | // Import table to be used for requireFast. |
306 | 0 | for (const auto &pair : |
307 | 0 | runtimeModule->getBytecode()->getCJSModuleTableStatic()) { |
308 | 0 | const auto &moduleID = pair.first; |
309 | 0 | const auto &functionID = pair.second; |
310 | 0 | if (!isModuleRegistered(moduleID)) { |
311 | 0 | registerModule(moduleID, functionID); |
312 | 0 | } |
313 | 0 | } |
314 | |
|
315 | 0 | self->cjsModules_.set(runtime, cjsModules.get(), runtime.getHeap()); |
316 | 0 | return ExecutionStatus::RETURNED; |
317 | 0 | } |
318 | | |
319 | | const ObjectVTable RequireContext::vt{ |
320 | | VTable(CellKind::RequireContextKind, cellSize<RequireContext>()), |
321 | | RequireContext::_getOwnIndexedRangeImpl, |
322 | | RequireContext::_haveOwnIndexedImpl, |
323 | | RequireContext::_getOwnIndexedPropertyFlagsImpl, |
324 | | RequireContext::_getOwnIndexedImpl, |
325 | | RequireContext::_setOwnIndexedImpl, |
326 | | RequireContext::_deleteOwnIndexedImpl, |
327 | | RequireContext::_checkAllOwnIndexedImpl, |
328 | | }; |
329 | | |
330 | 1 | void RequireContextBuildMeta(const GCCell *cell, Metadata::Builder &mb) { |
331 | 1 | mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<RequireContext>()); |
332 | 1 | JSObjectBuildMeta(cell, mb); |
333 | 1 | const auto *self = static_cast<const RequireContext *>(cell); |
334 | 1 | mb.setVTable(&RequireContext::vt); |
335 | 1 | mb.addField(&self->domain_); |
336 | 1 | mb.addField(&self->dirname_); |
337 | 1 | } |
338 | | |
339 | | Handle<RequireContext> RequireContext::create( |
340 | | Runtime &runtime, |
341 | | Handle<Domain> domain, |
342 | 0 | Handle<StringPrimitive> dirname) { |
343 | 0 | auto objProto = Handle<JSObject>::vmcast(&runtime.objectPrototype); |
344 | 0 | auto *cell = runtime.makeAFixed<RequireContext>( |
345 | 0 | runtime, |
346 | 0 | objProto, |
347 | 0 | runtime.getHiddenClassForPrototype( |
348 | 0 | *objProto, numOverlapSlots<RequireContext>())); |
349 | 0 | auto self = JSObjectInit::initToHandle(runtime, cell); |
350 | 0 | self->domain_.set(runtime, *domain, runtime.getHeap()); |
351 | 0 | self->dirname_.set(runtime, *dirname, runtime.getHeap()); |
352 | 0 | return self; |
353 | 0 | } |
354 | | |
355 | | } // namespace vm |
356 | | } // namespace hermes |