in API/hermes/TraceInterpreter.cpp [983:1577]
Value TraceInterpreter::execFunction(
const TraceInterpreter::Call &call,
const Value &thisVal,
const Value *args,
uint64_t count,
const PropNameID *nativePropNameToConsumeAsDef) {
llvh::SaveAndRestore<uint64_t> depthGuard(depth_, depth_ + 1);
// A mapping from an ObjectID to the Object for local variables.
// Invariant: value is Object or String;
std::unordered_map<ObjectID, Value> locals;
std::unordered_map<ObjectID, PropNameID> pniLocals;
// Save a value so that Call can set it, and Return can access it.
Value retval;
// Carry the return value from BeginJSExec to EndJSExec.
Value overallRetval;
#ifndef NDEBUG
if (depth_ != 1) {
RecordType firstRecType = call.pieces.front().records.front()->getType();
assert(
(firstRecType == RecordType::CallToNative ||
firstRecType == RecordType::GetNativePropertyNames ||
firstRecType == RecordType::GetPropertyNative ||
firstRecType == RecordType::SetPropertyNative) &&
"Illegal starting record");
}
#endif
#ifndef NDEBUG
// We'll want the first record, to verify that it's the one that consumes
// nativePropNameToConsumeAsDef if that is non-null.
const SynthTrace::Record *firstRec = call.pieces.at(0).records.at(0);
#endif
for (const TraceInterpreter::Call::Piece &piece : call.pieces) {
uint64_t globalRecordNum = piece.start;
const auto getJSIValueForUseOpt =
[this, &call, &locals, &globalRecordNum](
ObjectID obj) -> llvh::Optional<Value> {
// Check locals, then globals.
auto it = locals.find(obj);
if (it != locals.end()) {
// Satisfiable locally
Value val{rt_, it->second};
assert(val.isObject() || val.isString() || val.isSymbol());
// If it was the last local use, delete that object id from locals.
auto defAndUse = call.locals.find(obj);
if (defAndUse != call.locals.end() &&
defAndUse->second.lastUse == globalRecordNum) {
assert(
defAndUse->second.lastDefBeforeFirstUse != DefAndUse::kUnused &&
"All uses must be preceded by a def");
locals.erase(it);
}
return val;
}
auto defAndUse = globalDefsAndUses_.find(obj);
// Since the use might not be a jsi::Value, it might be found in the
// locals table for non-Values, and therefore might not be in the global
// use/def table.
if (defAndUse == globalDefsAndUses_.end()) {
return llvh::None;
}
it = gom_.find(obj);
if (it != gom_.end()) {
Value val{rt_, it->second};
assert(val.isObject() || val.isString() || val.isSymbol());
// If it was the last global use, delete that object id from globals.
if (defAndUse->second.lastUse == globalRecordNum) {
gom_.erase(it);
}
return val;
}
return llvh::None;
};
const auto getJSIValueForUse =
[&getJSIValueForUseOpt](ObjectID obj) -> Value {
auto valOpt = getJSIValueForUseOpt(obj);
assert(valOpt && "There must be a definition for all uses.");
return std::move(*valOpt);
};
const auto getObjForUse = [this,
getJSIValueForUse](ObjectID obj) -> Object {
return getJSIValueForUse(obj).getObject(rt_);
};
const auto getPropNameIDForUse =
[this, &call, &pniLocals, &globalRecordNum](
ObjectID obj) -> PropNameID {
// Check locals, then globals.
auto it = pniLocals.find(obj);
if (it != pniLocals.end()) {
// Satisfiable locally
PropNameID propNameID{rt_, it->second};
// If it was the last local use, delete that object id from locals.
auto defAndUse = call.locals.find(obj);
if (defAndUse != call.locals.end() &&
defAndUse->second.lastUse == globalRecordNum) {
assert(
defAndUse->second.lastDefBeforeFirstUse != DefAndUse::kUnused &&
"All uses must be preceded by a def");
pniLocals.erase(it);
}
return propNameID;
}
auto defAndUse = globalDefsAndUses_.find(obj);
assert(
defAndUse != globalDefsAndUses_.end() &&
"All global uses must have a global definition");
it = gpnm_.find(obj);
assert(
it != gpnm_.end() &&
"If there is a global definition, it must exist in one of the maps");
PropNameID propNameID{rt_, it->second};
// If it was the last global use, delete that object id from globals.
if (defAndUse->second.lastUse == globalRecordNum) {
gpnm_.erase(it);
}
return propNameID;
};
for (const SynthTrace::Record *rec : piece.records) {
try {
switch (rec->getType()) {
case RecordType::BeginExecJS: {
const auto &bejsr =
dynamic_cast<const SynthTrace::BeginExecJSRecord &>(*rec);
auto it = bundles_.find(bejsr.sourceHash());
if (it == bundles_.end()) {
if ((options_.disableSourceHashCheck ||
isAllZeroSourceHash(bejsr.sourceHash())) &&
bundles_.size() == 1) {
// Normally, if a bundle's source hash doesn't match, it would
// be an error. However, for convenience and backwards
// compatibility, allow an all-zero hash to automatically assume
// a bundle if that was the only bundle supplied.
it = bundles_.begin();
} else {
throw std::invalid_argument(
"Trace expected the source hash " +
::hermes::hashAsString(bejsr.sourceHash()) +
", that wasn't provided as a file");
}
}
// Copy the shared pointer to the buffer in case this file is
// executed multiple times.
auto bundle = it->second;
if (!HermesRuntime::isHermesBytecode(
bundle->data(), bundle->size())) {
llvh::errs()
<< "Note: You are running from source code, not HBC bytecode.\n"
<< " This run will reflect dev performance, not production.\n";
}
// overallRetval is to be consumed when we get an EndExecJS record.
overallRetval =
rt_.evaluateJavaScript(std::move(bundle), bejsr.sourceURL());
break;
}
case RecordType::EndExecJS: {
const auto &eejsr =
dynamic_cast<const SynthTrace::EndExecJSRecord &>(*rec);
ifObjectAddToDefs(
eejsr.retVal_,
std::move(overallRetval),
call,
globalRecordNum,
locals);
LLVM_FALLTHROUGH;
}
case RecordType::Marker: {
const auto &mr =
dynamic_cast<const SynthTrace::MarkerRecord &>(*rec);
// If the tag is the requested tag, and the stats have not already
// been collected, collect them.
checkMarker(mr.tag_);
if (auto *tracingRT = dynamic_cast<TracingRuntime *>(&rt_)) {
if (rec->getType() != RecordType::EndExecJS) {
// If tracing is on, re-emit the marker into the result stream.
// This way, the trace emitted from replay will be the same as
// the trace input. Don't do this for EndExecJSRecord, since
// that falls through to here, and it already emits a marker
// automatically.
tracingRT->addMarker(mr.tag_);
}
}
break;
}
case RecordType::CreateObject: {
const auto &cor =
static_cast<const SynthTrace::CreateObjectRecord &>(*rec);
// Make an empty object to be used.
addJSIValueToDefs(
call, cor.objID_, globalRecordNum, Object(rt_), locals);
break;
}
case RecordType::CreateString: {
const auto &csr =
static_cast<const SynthTrace::CreateStringRecord &>(*rec);
Value str;
if (csr.ascii_) {
str = String::createFromAscii(
rt_, csr.chars_.data(), csr.chars_.size());
} else {
str = String::createFromUtf8(
rt_,
reinterpret_cast<const uint8_t *>(csr.chars_.data()),
csr.chars_.size());
}
assert(str.asString(rt_).utf8(rt_) == csr.chars_);
addJSIValueToDefs(
call, csr.objID_, globalRecordNum, std::move(str), locals);
break;
}
case RecordType::CreatePropNameID: {
const auto &cpnr =
static_cast<const SynthTrace::CreatePropNameIDRecord &>(*rec);
// We perform the calls below for their side effects (for example,
auto propNameID = [&] {
switch (cpnr.valueType_) {
case SynthTrace::CreatePropNameIDRecord::ASCII:
return PropNameID::forAscii(
rt_, cpnr.chars_.data(), cpnr.chars_.size());
case SynthTrace::CreatePropNameIDRecord::UTF8:
return PropNameID::forUtf8(
rt_,
reinterpret_cast<const uint8_t *>(cpnr.chars_.data()),
cpnr.chars_.size());
case SynthTrace::CreatePropNameIDRecord::TRACEVALUE: {
auto val = traceValueToJSIValue(
rt_, trace_, getJSIValueForUse, cpnr.traceValue_);
if (val.isSymbol())
return PropNameID::forSymbol(rt_, val.getSymbol(rt_));
return PropNameID::forString(rt_, val.getString(rt_));
}
}
llvm_unreachable("No other way to construct PropNameID");
}();
addPropNameIDToDefs(
call,
cpnr.propNameID_,
globalRecordNum,
std::move(propNameID),
pniLocals);
break;
}
case RecordType::CreateHostObject: {
const auto &chor =
static_cast<const SynthTrace::CreateHostObjectRecord &>(*rec);
const ObjectID objID = chor.objID_;
auto iterAndDidCreate =
hostObjects_.emplace(objID, createHostObject(objID));
assert(
iterAndDidCreate.second &&
"This should always be creating a new host object");
addJSIValueToDefs(
call,
objID,
globalRecordNum,
Value(rt_, iterAndDidCreate.first->second),
locals);
break;
}
case RecordType::CreateHostFunction: {
const auto &chfr =
static_cast<const SynthTrace::CreateHostFunctionRecord &>(*rec);
auto iterAndDidCreate = hostFunctions_.emplace(
chfr.objID_,
createHostFunction(
chfr, getPropNameIDForUse(chfr.propNameID_)));
assert(
iterAndDidCreate.second &&
"This should always be creating a new host function");
addJSIValueToDefs(
call,
chfr.objID_,
globalRecordNum,
Value(rt_, iterAndDidCreate.first->second),
locals);
break;
}
case RecordType::DrainMicrotasks: {
const auto &drainRecord =
static_cast<const SynthTrace::DrainMicrotasksRecord &>(*rec);
rt_.drainMicrotasks(drainRecord.maxMicrotasksHint_);
break;
}
case RecordType::GetProperty: {
const auto &gpr =
static_cast<const SynthTrace::GetPropertyRecord &>(*rec);
// Call get property on the object specified, and possibly define
// the result.
jsi::Value value;
const auto &obj = getObjForUse(gpr.objID_);
auto propIDValOpt = getJSIValueForUseOpt(gpr.propID_);
// TODO(T111638575): We have to check whether the value is a string,
// because we cannot disambiguate Symbols from PropNameIDs.
if (propIDValOpt && propIDValOpt->isString()) {
const jsi::String propString = (*propIDValOpt).asString(rt_);
#ifdef HERMESVM_API_TRACE_DEBUG
assert(propString.utf8(rt_) == gpr.propNameDbg_);
#endif
value = obj.getProperty(rt_, propString);
} else {
auto propNameID = getPropNameIDForUse(gpr.propID_);
#ifdef HERMESVM_API_TRACE_DEBUG
assert(propNameID.utf8(rt_) == gpr.propNameDbg_);
#endif
value = obj.getProperty(rt_, propNameID);
}
ifObjectAddToDefs(
gpr.value_, std::move(value), call, globalRecordNum, locals);
break;
}
case RecordType::SetProperty: {
const auto &spr =
static_cast<const SynthTrace::SetPropertyRecord &>(*rec);
auto obj = getObjForUse(spr.objID_);
// Call set property on the object specified and give it the value.
auto propIDValOpt = getJSIValueForUseOpt(spr.propID_);
// TODO(T111638575)
if (propIDValOpt && propIDValOpt->isString()) {
const jsi::String propString = (*propIDValOpt).asString(rt_);
#ifdef HERMESVM_API_TRACE_DEBUG
assert(propString.utf8(rt_) == spr.propNameDbg_);
#endif
obj.setProperty(
rt_,
propString,
traceValueToJSIValue(
rt_, trace_, getJSIValueForUse, spr.value_));
} else {
auto propNameID = getPropNameIDForUse(spr.propID_);
#ifdef HERMESVM_API_TRACE_DEBUG
assert(propNameID.utf8(rt_) == spr.propNameDbg_);
#endif
obj.setProperty(
rt_,
propNameID,
traceValueToJSIValue(
rt_, trace_, getJSIValueForUse, spr.value_));
}
break;
}
case RecordType::HasProperty: {
const auto &hpr =
static_cast<const SynthTrace::HasPropertyRecord &>(*rec);
auto obj = getObjForUse(hpr.objID_);
auto propIDValOpt = getJSIValueForUseOpt(hpr.propID_);
// TODO(T111638575)
if (propIDValOpt && propIDValOpt->isString()) {
const jsi::String propString = (*propIDValOpt).asString(rt_);
#ifdef HERMESVM_API_TRACE_DEBUG
assert(propString.utf8(rt_) == hpr.propNameDbg_);
#endif
obj.hasProperty(rt_, propString);
} else {
auto propNameID = getPropNameIDForUse(hpr.propID_);
#ifdef HERMESVM_API_TRACE_DEBUG
assert(propNameID.utf8(rt_) == hpr.propNameDbg_);
#endif
obj.hasProperty(rt_, propNameID);
}
break;
}
case RecordType::GetPropertyNames: {
const auto &gpnr =
static_cast<const SynthTrace::GetPropertyNamesRecord &>(*rec);
jsi::Array arr = getObjForUse(gpnr.objID_).getPropertyNames(rt_);
addJSIValueToDefs(
call,
gpnr.propNamesID_,
globalRecordNum,
std::move(arr),
locals);
break;
}
case RecordType::CreateArray: {
const auto &car =
static_cast<const SynthTrace::CreateArrayRecord &>(*rec);
// Make an array of the appropriate length to be used.
addJSIValueToDefs(
call,
car.objID_,
globalRecordNum,
Array(rt_, car.length_),
locals);
break;
}
case RecordType::ArrayRead: {
const auto &arr =
static_cast<const SynthTrace::ArrayReadRecord &>(*rec);
// Read from the specified array, and possibly define the result.
auto value = getObjForUse(arr.objID_)
.asArray(rt_)
.getValueAtIndex(rt_, arr.index_);
ifObjectAddToDefs(
arr.value_, std::move(value), call, globalRecordNum, locals);
break;
}
case RecordType::ArrayWrite: {
const auto &awr =
static_cast<const SynthTrace::ArrayWriteRecord &>(*rec);
// Write to the array and give it the value.
getObjForUse(awr.objID_)
.asArray(rt_)
.setValueAtIndex(
rt_,
awr.index_,
traceValueToJSIValue(
rt_, trace_, getJSIValueForUse, awr.value_));
break;
}
case RecordType::CallFromNative: {
const auto &cfnr =
static_cast<const SynthTrace::CallFromNativeRecord &>(*rec);
auto func = getObjForUse(cfnr.functionID_).asFunction(rt_);
std::vector<Value> args;
for (const auto &arg : cfnr.args_) {
args.emplace_back(
traceValueToJSIValue(rt_, trace_, getJSIValueForUse, arg));
}
// Save the return result into retval so that ReturnToNative can
// access it and put it at the correct object id.
const Value *argStart = args.data();
if (cfnr.thisArg_.isUndefined()) {
retval = func.call(rt_, argStart, args.size());
} else {
assert(
cfnr.thisArg_.isObject() &&
"Encountered a thisArg which was not undefined or an object");
retval = func.callWithThis(
rt_,
getObjForUse(cfnr.thisArg_.getUID()),
argStart,
args.size());
}
break;
}
case RecordType::ConstructFromNative: {
const auto &cfnr =
static_cast<const SynthTrace::ConstructFromNativeRecord &>(
*rec);
// Essentially the same implementation as CallFromNative, except
// calls the construct path.
auto func = getObjForUse(cfnr.functionID_).asFunction(rt_);
std::vector<Value> args;
for (const auto &arg : cfnr.args_) {
args.emplace_back(
traceValueToJSIValue(rt_, trace_, getJSIValueForUse, arg));
}
assert(
cfnr.thisArg_.isUndefined() &&
"The this arg should always be undefined for a construct call");
// Save the return result into retval so that ReturnToNative can
// access it and put it at the correct object id.
const Value *argStart = args.data();
retval = func.callAsConstructor(rt_, argStart, args.size());
break;
}
case RecordType::ReturnFromNative: {
const auto &rfnr =
dynamic_cast<const SynthTrace::ReturnFromNativeRecord &>(*rec);
return traceValueToJSIValue(
rt_, trace_, getJSIValueForUse, rfnr.retVal_);
}
case RecordType::ReturnToNative: {
const auto &rtnr =
dynamic_cast<const SynthTrace::ReturnToNativeRecord &>(*rec);
ifObjectAddToDefs(
rtnr.retVal_, std::move(retval), call, globalRecordNum, locals);
// If the return value wasn't an object, it can be ignored.
break;
}
case RecordType::CallToNative: {
const auto &ctnr =
static_cast<const SynthTrace::CallToNativeRecord &>(*rec);
// Associate the this arg with its object id.
ifObjectAddToDefs(
ctnr.thisArg_,
std::move(thisVal),
call,
globalRecordNum,
locals,
/* isThis = */ true);
// Associate each argument with its object id.
assert(
ctnr.args_.size() == count &&
"Called at runtime with a different number of args than "
"the trace expected");
for (uint64_t i = 0; i < ctnr.args_.size(); ++i) {
ifObjectAddToDefs(
ctnr.args_[i],
std::move(args[i]),
call,
globalRecordNum,
locals);
}
break;
}
case RecordType::GetPropertyNative: {
assert(count == 0 && "Should have no arguments");
const auto &gpnr =
static_cast<const SynthTrace::GetPropertyNativeRecord &>(*rec);
// The propName is a definition.
assert(nativePropNameToConsumeAsDef);
// This must be the first record in the call.
assert(rec == firstRec);
addPropNameIDToDefs(
call,
gpnr.propNameID_,
globalRecordNum,
*nativePropNameToConsumeAsDef,
pniLocals);
// Don't add this to the locals, it is not technically provided to
// the function.
break;
}
case RecordType::GetPropertyNativeReturn: {
const auto &gpnrr =
dynamic_cast<const SynthTrace::GetPropertyNativeReturnRecord &>(
*rec);
return traceValueToJSIValue(
rt_, trace_, getObjForUse, gpnrr.retVal_);
}
case RecordType::SetPropertyNative: {
const auto &spnr =
static_cast<const SynthTrace::SetPropertyNativeRecord &>(*rec);
assert(
count == 1 &&
"There should be exactly one argument to SetPropertyNative");
// The propName is a definition.
assert(nativePropNameToConsumeAsDef);
// This must be the first record in the call.
assert(rec == firstRec);
addPropNameIDToDefs(
call,
spnr.propNameID_,
globalRecordNum,
*nativePropNameToConsumeAsDef,
pniLocals);
// Associate the single argument with its object id (if it's an
// object).
ifObjectAddToDefs(
spnr.value_, std::move(args[0]), call, globalRecordNum, locals);
break;
}
case RecordType::SetPropertyNativeReturn: {
const auto &spnr =
static_cast<const SynthTrace::SetPropertyNativeRecord &>(*rec);
// The propName is a definition.
assert(nativePropNameToConsumeAsDef);
// This must be the first record in the call.
assert(rec == firstRec);
addPropNameIDToDefs(
call,
spnr.propNameID_,
globalRecordNum,
*nativePropNameToConsumeAsDef,
pniLocals);
// Since a SetPropertyNative does not have a return value, return
// undefined.
return Value::undefined();
break;
}
case RecordType::GetNativePropertyNames: {
// Nothing actually needs to happen here, as no defs are provided to
// the local function. The HostObject already handles accessing the
// property names.
break;
}
case RecordType::GetNativePropertyNamesReturn: {
// Accessing the list of property names on a host object doesn't
// return a JS value.
return Value::undefined();
break;
}
}
} catch (const std::exception &e) {
crashOnException(e, globalRecordNum);
}
// If the top of the stack is reached after a marker flushed the stats,
// exit early.
if (depth_ == 1 && markerFound_) {
return Value::undefined();
}
globalRecordNum++;
}
}
if (depth_ != 1) {
// For the non-entry point, there should always be an explicit
// ReturnFromNative or Get/SetPropertyNativeReturn which will return early
// from this function. If there was no explicit return, the trace is
// malformed.
llvm_unreachable("There was no return in the call");
}
// Return undefined for the entry point, it is ignored anyway.
return Value::undefined();
}