lib/VM/Domain.cpp (281 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "hermes/VM/Domain.h"
#include "hermes/VM/Callable.h"
#include "hermes/VM/GCPointer-inline.h"
#include "hermes/VM/JSLib.h"
#include "hermes/VM/Profiler/SamplingProfiler.h"
namespace hermes {
namespace vm {
const VTable Domain::vt{
CellKind::DomainKind,
cellSize<Domain>(),
_finalizeImpl,
_markWeakImpl,
_mallocSizeImpl,
nullptr,
VTable::HeapSnapshotMetadata{
HeapSnapshot::NodeType::Code,
nullptr,
Domain::_snapshotAddEdgesImpl,
Domain::_snapshotAddNodesImpl,
nullptr}};
void DomainBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
const auto *self = static_cast<const Domain *>(cell);
mb.setVTable(&Domain::vt);
mb.addField("cjsModules", &self->cjsModules_);
mb.addField("throwingRequire", &self->throwingRequire_);
}
PseudoHandle<Domain> Domain::create(Runtime &runtime) {
auto *cell = runtime.makeAFixed<Domain, HasFinalizer::Yes>();
auto self = createPseudoHandle(cell);
return self;
}
void Domain::_finalizeImpl(GCCell *cell, GC *gc) {
auto *self = vmcast<Domain>(cell);
for (RuntimeModule *rm : self->runtimeModules_) {
gc->getIDTracker().untrackNative(rm);
}
self->~Domain();
}
Domain::~Domain() {
for (RuntimeModule *rm : runtimeModules_) {
delete rm;
}
}
PseudoHandle<NativeFunction> Domain::getThrowingRequire(
Runtime &runtime) const {
return createPseudoHandle(throwingRequire_.get(runtime));
}
void Domain::_markWeakImpl(GCCell *cell, WeakRefAcceptor &acceptor) {
auto *self = reinterpret_cast<Domain *>(cell);
self->markWeakRefs(acceptor);
}
void Domain::markWeakRefs(WeakRefAcceptor &acceptor) {
for (RuntimeModule *rm : runtimeModules_) {
rm->markDomainRef(acceptor);
}
}
size_t Domain::_mallocSizeImpl(GCCell *cell) {
auto *self = vmcast<Domain>(cell);
size_t rmSize = 0;
for (auto *rm : self->runtimeModules_)
rmSize += sizeof(RuntimeModule) + rm->additionalMemorySize();
return self->cjsRuntimeModules_.capacity_in_bytes() +
self->cjsModuleTable_.getMemorySize() +
self->runtimeModules_.capacity_in_bytes() + rmSize;
}
void Domain::_snapshotAddEdgesImpl(GCCell *cell, GC *gc, HeapSnapshot &snap) {
auto *const self = vmcast<Domain>(cell);
for (RuntimeModule *rm : self->runtimeModules_)
snap.addNamedEdge(
HeapSnapshot::EdgeType::Internal, "RuntimeModule", gc->getNativeID(rm));
}
void Domain::_snapshotAddNodesImpl(GCCell *cell, GC *gc, HeapSnapshot &snap) {
auto *const self = vmcast<Domain>(cell);
for (RuntimeModule *rm : self->runtimeModules_) {
// Create a native node for each RuntimeModule owned by this domain.
rm->snapshotAddNodes(gc, snap);
snap.beginNode();
rm->snapshotAddEdges(gc, snap);
snap.endNode(
HeapSnapshot::NodeType::Native,
"RuntimeModule",
gc->getNativeID(rm),
sizeof(RuntimeModule) + rm->additionalMemorySize(),
0);
}
}
ExecutionStatus Domain::importCJSModuleTable(
Handle<Domain> self,
Runtime &runtime,
RuntimeModule *runtimeModule) {
if (runtimeModule->getBytecode()->getCJSModuleTable().empty() &&
runtimeModule->getBytecode()->getCJSModuleTableStatic().empty()) {
// Nothing to do, avoid allocating and simply return.
return ExecutionStatus::RETURNED;
}
static_assert(
CJSModuleSize < 10, "CJSModuleSize must be small to avoid overflow");
MutableHandle<ArrayStorage> cjsModules{runtime};
if (!self->cjsModules_) {
// Create the module table on first import.
// If module IDs are contiguous and start from 0, we won't need to resize
// for this RuntimeModule.
const uint64_t firstSegmentModules =
runtimeModule->getBytecode()->getCJSModuleTable().size() +
runtimeModule->getBytecode()->getCJSModuleTableStatic().size();
assert(
firstSegmentModules <= std::numeric_limits<uint32_t>::max() &&
"number of modules is 32 bits due to the bytecode format");
// Use uint64_t to allow us to check for overflow.
const uint64_t requiredSize = firstSegmentModules * CJSModuleSize;
if (requiredSize > std::numeric_limits<uint32_t>::max()) {
return runtime.raiseRangeError("Loaded module count exceeded limit");
}
auto cjsModulesRes = ArrayStorage::create(runtime, requiredSize);
if (LLVM_UNLIKELY(cjsModulesRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
cjsModules = vmcast<ArrayStorage>(*cjsModulesRes);
self->cjsRuntimeModules_.reserve(firstSegmentModules);
for (size_t i = self->cjsRuntimeModules_.size(); i < firstSegmentModules;
i++) {
self->cjsRuntimeModules_.push_back(nullptr, &runtime.getHeap());
}
auto requireFn = NativeFunction::create(
runtime,
Handle<JSObject>::vmcast(&runtime.functionPrototype),
(void *)TypeErrorKind::InvalidDynamicRequire,
throwTypeError,
Predefined::getSymbolID(Predefined::emptyString),
0,
Runtime::makeNullHandle<JSObject>());
auto context = RequireContext::create(
runtime,
self,
runtime.getPredefinedStringHandle(Predefined::emptyString));
// Set the require.context property.
PropertyFlags pf = PropertyFlags::defaultNewNamedPropertyFlags();
pf.writable = 0;
pf.configurable = 0;
if (LLVM_UNLIKELY(
JSObject::defineNewOwnProperty(
requireFn,
runtime,
Predefined::getSymbolID(Predefined::context),
pf,
context) == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
self->throwingRequire_.set(runtime, *requireFn, &runtime.getHeap());
} else {
cjsModules = self->cjsModules_.get(runtime);
}
assert(cjsModules && "cjsModules not set");
// Find the maximum module ID so we can resize cjsModules at most once per
// RuntimeModule.
uint64_t maxModuleID = cjsModules->size() / CJSModuleSize;
// The non-static module table does not store module IDs. They are assigned
// during registration by counting insertions to cjsModuleTable_. Count the
// insertions up front.
for (const auto &pair : runtimeModule->getBytecode()->getCJSModuleTable()) {
SymbolID symbolId =
runtimeModule->getSymbolIDFromStringIDMayAllocate(pair.first);
if (self->cjsModuleTable_.find(symbolId) == self->cjsModuleTable_.end()) {
++maxModuleID;
}
}
// The static module table stores module IDs in an arbitrary order. Scan for
// the maximum ID.
for (const auto &pair :
runtimeModule->getBytecode()->getCJSModuleTableStatic()) {
const auto &moduleID = pair.first;
if (moduleID > maxModuleID) {
maxModuleID = moduleID;
}
}
assert(
maxModuleID <= std::numeric_limits<uint32_t>::max() &&
"number of modules is 32 bits due to the bytecode format");
// Use uint64_t to allow us to check for overflow.
const uint64_t requiredSize = (maxModuleID + 1) * CJSModuleSize;
if (requiredSize > std::numeric_limits<uint32_t>::max()) {
return runtime.raiseRangeError("Loaded module count exceeded limit");
}
// Resize the array to allow for the new modules, if necessary.
if (requiredSize > cjsModules->size()) {
if (LLVM_UNLIKELY(
ArrayStorage::resize(cjsModules, runtime, requiredSize) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
self->cjsRuntimeModules_.reserve(maxModuleID + 1);
for (size_t i = self->cjsRuntimeModules_.size(); i <= maxModuleID; i++) {
self->cjsRuntimeModules_.push_back(nullptr, &runtime.getHeap());
}
}
/// \return Whether the module with ID \param moduleID has been registered in
/// cjsModules. \pre Space has been allocated for this module's record in
/// cjsModules.
const auto isModuleRegistered = [&cjsModules,
maxModuleID](uint32_t moduleID) -> bool {
assert(
moduleID <= maxModuleID &&
"CJS module ID exceeds maximum known module ID");
(void)maxModuleID;
uint32_t index = moduleID * CJSModuleSize;
uint32_t requiredSize = index + CJSModuleSize;
assert(
cjsModules->size() >= requiredSize &&
"CJS module ID exceeds allocated storage");
(void)requiredSize;
return !cjsModules->at(index + FunctionIndexOffset).isEmpty();
};
/// Register CJS module \param moduleID in the runtime module table.
/// \return The index into cjsModules where this module's record begins.
/// \pre Space has been allocated for this module's record in cjsModules.
/// \pre Space has been allocated for this module's RuntimeModule* in
/// cjsRuntimeModules_.
/// \pre There is no module already registered under moduleID.
auto &cjsEntryModuleID = self->cjsEntryModuleID_;
auto &cjsRuntimeModules = self->cjsRuntimeModules_;
const auto registerModule =
[&runtime,
&cjsModules,
&cjsRuntimeModules,
runtimeModule,
&isModuleRegistered,
&cjsEntryModuleID](uint32_t moduleID, uint32_t functionID) -> uint32_t {
assert(!isModuleRegistered(moduleID) && "CJS module ID collision occurred");
(void)isModuleRegistered;
if (LLVM_UNLIKELY(!cjsEntryModuleID.hasValue())) {
cjsEntryModuleID = moduleID;
}
uint32_t index = moduleID * CJSModuleSize;
cjsModules->set(
index + CachedExportsOffset,
HermesValue::encodeEmptyValue(),
&runtime.getHeap());
cjsModules->set(
index + ModuleOffset,
HermesValue::encodeNullValue(),
&runtime.getHeap());
cjsModules->set(
index + FunctionIndexOffset,
HermesValue::encodeNativeUInt32(functionID),
&runtime.getHeap());
cjsRuntimeModules[moduleID] = runtimeModule;
assert(isModuleRegistered(moduleID) && "CJS module was not registered");
return index;
};
// Import full table that allows dynamic requires.
for (const auto &pair : runtimeModule->getBytecode()->getCJSModuleTable()) {
SymbolID symbolId =
runtimeModule->getSymbolIDFromStringIDMayAllocate(pair.first);
auto emplaceRes = self->cjsModuleTable_.try_emplace(symbolId, 0xffffffff);
if (emplaceRes.second) {
// This module has not been registered before.
// Assign it an arbitrary unused module ID, because nothing will be
// referencing that ID from outside Domain.
// Counting insertions to cjsModuleTable_ is a valid source of unique IDs
// since a given Domain uses either dynamic requires or statically
// resolved requires.
uint32_t moduleID = self->cjsModuleTable_.size() - 1;
const auto functionID = pair.second;
auto index = registerModule(moduleID, functionID);
// Update the mapping from symbolId to an index into cjsModules.
emplaceRes.first->second = index;
}
}
// Import table to be used for requireFast.
for (const auto &pair :
runtimeModule->getBytecode()->getCJSModuleTableStatic()) {
const auto &moduleID = pair.first;
const auto &functionID = pair.second;
if (!isModuleRegistered(moduleID)) {
registerModule(moduleID, functionID);
}
}
self->cjsModules_.set(runtime, cjsModules.get(), &runtime.getHeap());
return ExecutionStatus::RETURNED;
}
const ObjectVTable RequireContext::vt{
VTable(CellKind::RequireContextKind, cellSize<RequireContext>()),
RequireContext::_getOwnIndexedRangeImpl,
RequireContext::_haveOwnIndexedImpl,
RequireContext::_getOwnIndexedPropertyFlagsImpl,
RequireContext::_getOwnIndexedImpl,
RequireContext::_setOwnIndexedImpl,
RequireContext::_deleteOwnIndexedImpl,
RequireContext::_checkAllOwnIndexedImpl,
};
void RequireContextBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<RequireContext>());
JSObjectBuildMeta(cell, mb);
const auto *self = static_cast<const RequireContext *>(cell);
mb.setVTable(&RequireContext::vt);
mb.addField(&self->domain_);
mb.addField(&self->dirname_);
}
Handle<RequireContext> RequireContext::create(
Runtime &runtime,
Handle<Domain> domain,
Handle<StringPrimitive> dirname) {
auto objProto = Handle<JSObject>::vmcast(&runtime.objectPrototype);
auto *cell = runtime.makeAFixed<RequireContext>(
runtime,
objProto,
runtime.getHiddenClassForPrototype(
*objProto, numOverlapSlots<RequireContext>()));
auto self = JSObjectInit::initToHandle(runtime, cell);
self->domain_.set(runtime, *domain, &runtime.getHeap());
self->dirname_.set(runtime, *dirname, &runtime.getHeap());
return self;
}
} // namespace vm
} // namespace hermes