Skip to content

Commit

Permalink
Implement queueMicrotask in Hermes
Browse files Browse the repository at this point in the history
Summary: This adds a basic implementation for `jsi::Runtime::queueMicrotask` in Hermes that adds the callbacks to the existing microtask queue in Hermes (used in `drainMicrotasks`).

Reviewed By: neildhar

Differential Revision: D54302535
  • Loading branch information
rubennorte authored and facebook-github-bot committed Mar 4, 2024
1 parent b23a5e9 commit 8bc4baf
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 0 deletions.
15 changes: 15 additions & 0 deletions API/hermes/SynthTrace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,14 @@ bool SynthTrace::CreateObjectRecord::operator==(const Record &that) const {
return objID_ == thatCasted.objID_;
}

bool SynthTrace::QueueMicrotaskRecord::operator==(const Record &that) const {
if (!Record::operator==(that)) {
return false;
}
const auto &thatCasted = dynamic_cast<const QueueMicrotaskRecord &>(that);
return callbackID_ == thatCasted.callbackID_;
}

bool SynthTrace::DrainMicrotasksRecord::operator==(const Record &that) const {
if (!Record::operator==(that)) {
return false;
Expand Down Expand Up @@ -634,6 +642,11 @@ void SynthTrace::HasPropertyRecord::toJSONInternal(JSONEmitter &json) const {
#endif
}

void SynthTrace::QueueMicrotaskRecord::toJSONInternal(JSONEmitter &json) const {
Record::toJSONInternal(json);
json.emitKeyValue("callbackID", callbackID_);
}

void SynthTrace::DrainMicrotasksRecord::toJSONInternal(
JSONEmitter &json) const {
Record::toJSONInternal(json);
Expand Down Expand Up @@ -831,6 +844,7 @@ llvh::raw_ostream &operator<<(
CASE(CreatePropNameID);
CASE(CreateHostObject);
CASE(CreateHostFunction);
CASE(QueueMicrotask);
CASE(DrainMicrotasks);
CASE(GetProperty);
CASE(SetProperty);
Expand Down Expand Up @@ -877,6 +891,7 @@ std::istream &operator>>(std::istream &is, SynthTrace::RecordType &type) {
CASE(CreatePropNameID)
CASE(CreateHostObject)
CASE(CreateHostFunction)
CASE(QueueMicrotask)
CASE(DrainMicrotasks)
CASE(GetProperty)
CASE(SetProperty)
Expand Down
21 changes: 21 additions & 0 deletions API/hermes/SynthTrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ class SynthTrace {
CreatePropNameID,
CreateHostObject,
CreateHostFunction,
QueueMicrotask,
DrainMicrotasks,
GetProperty,
SetProperty,
Expand Down Expand Up @@ -723,6 +724,26 @@ class SynthTrace {
void toJSONInternal(::hermes::JSONEmitter &json) const override;
};

struct QueueMicrotaskRecord : public Record {
static constexpr RecordType type{RecordType::QueueMicrotask};
const ObjectID callbackID_;

QueueMicrotaskRecord(TimeSinceStart time, ObjectID callbackID)
: Record(time), callbackID_(callbackID) {}

bool operator==(const Record &that) const final;

RecordType getType() const override {
return type;
}

void toJSONInternal(::hermes::JSONEmitter &json) const override;

std::vector<ObjectID> uses() const override {
return {callbackID_};
}
};

struct DrainMicrotasksRecord : public Record {
static constexpr RecordType type{RecordType::DrainMicrotasks};
int maxMicrotasksHint_;
Expand Down
7 changes: 7 additions & 0 deletions API/hermes/SynthTraceParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,13 @@ SynthTrace getTrace(JSONArray *array, SynthTrace::ObjectID globalObjID) {
trace.emplace_back<SynthTrace::CreateObjectRecord>(
timeFromStart, objID->getValue());
break;
case RecordType::QueueMicrotask: {
auto callbackID =
getNumberAs<SynthTrace::ObjectID>(obj->get("callbackID"));
trace.emplace_back<SynthTrace::QueueMicrotaskRecord>(
timeFromStart, callbackID);
break;
}
case RecordType::DrainMicrotasks: {
int maxMicrotasksHint = getNumberAs<int>(obj->get("maxMicrotasksHint"));
trace.emplace_back<SynthTrace::DrainMicrotasksRecord>(
Expand Down
8 changes: 8 additions & 0 deletions API/hermes/TraceInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1165,6 +1165,14 @@ Value TraceInterpreter::execFunction(
locals);
break;
}
case RecordType::QueueMicrotask: {
const auto &queueRecord =
static_cast<const SynthTrace::QueueMicrotaskRecord &>(*rec);
jsi::Function callback =
getObjForUse(queueRecord.callbackID_).asFunction(rt_);
rt_.queueMicrotask(callback);
break;
}
case RecordType::DrainMicrotasks: {
const auto &drainRecord =
static_cast<const SynthTrace::DrainMicrotasksRecord &>(*rec);
Expand Down
6 changes: 6 additions & 0 deletions API/hermes/TracingRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ jsi::Value TracingRuntime::evaluateJavaScript(
return res;
}

void TracingRuntime::queueMicrotask(const jsi::Function &callback) {
RD::queueMicrotask(callback);
trace_.emplace_back<SynthTrace::QueueMicrotaskRecord>(
getTimeSinceStart(), getUniqueID(callback));
}

bool TracingRuntime::drainMicrotasks(int maxMicrotasksHint) {
auto res = RD::drainMicrotasks(maxMicrotasksHint);
trace_.emplace_back<SynthTrace::DrainMicrotasksRecord>(
Expand Down
1 change: 1 addition & 0 deletions API/hermes/TracingRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class TracingRuntime : public jsi::RuntimeDecorator<jsi::Runtime> {
const std::shared_ptr<const jsi::Buffer> &buffer,
const std::string &sourceURL) override;

void queueMicrotask(const jsi::Function &callback) override;
bool drainMicrotasks(int maxMicrotasksHint = -1) override;

jsi::Object createObject() override;
Expand Down
12 changes: 12 additions & 0 deletions API/hermes/hermes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ class HermesRuntimeImpl final : public HermesRuntime,
jsi::Value evaluateJavaScript(
const std::shared_ptr<const jsi::Buffer> &buffer,
const std::string &sourceURL) override;
void queueMicrotask(const jsi::Function &callback) override;
bool drainMicrotasks(int maxMicrotasksHint = -1) override;
jsi::Object global() override;

Expand Down Expand Up @@ -1502,6 +1503,17 @@ jsi::Value HermesRuntimeImpl::evaluateJavaScript(
return evaluateJavaScriptWithSourceMap(buffer, nullptr, sourceURL);
}

void HermesRuntimeImpl::queueMicrotask(const jsi::Function &callback) {
if (LLVM_UNLIKELY(!runtime_.hasMicrotaskQueue())) {
throw jsi::JSINativeException(
"Could not enqueue microtask because they are disabled in this runtime");
}

vm::Handle<vm::Callable> handle =
vm::Handle<vm::Callable>::vmcast(&phv(callback));
runtime_.enqueueJob(handle.get());
}

bool HermesRuntimeImpl::drainMicrotasks(int maxMicrotasksHint) {
if (runtime_.hasMicrotaskQueue()) {
checkStatus(runtime_.drainJobs());
Expand Down
19 changes: 19 additions & 0 deletions unittests/API/SynthTraceTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ struct SynthTraceTest : public ::testing::Test {
::hermes::vm::RuntimeConfig::Builder()
.withSynthTraceMode(
::hermes::vm::SynthTraceMode::TracingAndReplaying)
.withMicrotaskQueue(true)
.build();
// We pass "forReplay = true" below, to prevent the TracingHermesRuntime
// from interactions it does automatically on non-replay runs.
Expand Down Expand Up @@ -1519,6 +1520,24 @@ HermesInternal.enqueueJob(inc);
}
}

TEST_F(JobQueueReplayTest, QueueMicrotask) {
{
auto &rt = *traceRt;
auto microtask =
eval(rt, "var x = 3; function updateX() { x = 4; }; updateX")
.asObject(rt)
.asFunction(rt);
rt.queueMicrotask(microtask);
rt.drainMicrotasks();
EXPECT_EQ(eval(rt, "x").asNumber(), 4);
}
replay();
{
auto &rt = *replayRt;
EXPECT_EQ(eval(rt, "x").asNumber(), 4);
}
}

using NonDeterminismReplayTest = SynthTraceReplayTest;

TEST_F(NonDeterminismReplayTest, DateNowTest) {
Expand Down

0 comments on commit 8bc4baf

Please sign in to comment.