Skip to content

Commit b236e9c

Browse files
DinoVmeta-codesync[bot]
authored andcommitted
Replace yield from w/ decomposed yield value loop
Summary: At some point CPython replaced `YIELD_FROM` with a loop in the bytecode. But we were detecting the loop and turning it into a yield from instruction. Instead of doing that this switches to turning the old yield from into a loop which builds things at the HIR level. This immediately reduces the amount of custom assembly we need to maintain in autogen and will let us further clean things up. Reviewed By: kddnewton, alexmalyshev Differential Revision: D95744943 fbshipit-source-id: 6715777240226eb94f06f2ca360a2fdb4a7523fa
1 parent 8635acf commit b236e9c

21 files changed

Lines changed: 615 additions & 666 deletions

cinderx/Jit/codegen/autogen.cpp

Lines changed: 26 additions & 209 deletions
Original file line numberDiff line numberDiff line change
@@ -556,9 +556,7 @@ void emitStoreGenYieldPoint(
556556
asmjit::Label resume_label,
557557
arch::Gp suspend_data_r,
558558
arch::Gp scratch_r) {
559-
bool is_yield_from = yield->isYieldFrom() ||
560-
yield->isYieldFromSkipInitialSend() ||
561-
yield->isYieldFromHandleStopAsyncIteration();
559+
bool is_yield_from = yield->isInYieldFromContext();
562560

563561
auto calc_spill_offset = [&](size_t live_input_n) {
564562
PhyLocation mem = yield->getInput(live_input_n)->getStackSlot();
@@ -577,8 +575,9 @@ void emitStoreGenYieldPoint(
577575
live_regs_input - num_live_regs,
578576
live_regs_input);
579577

580-
auto yield_from_offset =
581-
is_yield_from ? calc_spill_offset(2) : kInvalidYieldFromOffset;
578+
auto yield_from_offset = is_yield_from
579+
? calc_spill_offset(yield->yieldFromInputIdx())
580+
: kInvalidYieldFromOffset;
582581
GenYieldPoint* gen_yield_point = env->code_rt->addGenYieldPoint(
583582
GenYieldPoint{deopt_idx, yield_from_offset});
584583

@@ -836,6 +835,16 @@ void translateYieldValue(Environ* env, const Instruction* instr) {
836835
// Resumed execution in this generator begins here
837836
as->bind(resume_label);
838837

838+
#if PY_VERSION_HEX < 0x030C0000
839+
// On 3.10, for yield-from yield points, store the finish_yield_from arg
840+
// (RDX from resume entry) into GenDataFooter so the subsequent Send
841+
// instruction can load it.
842+
if (instr->isInYieldFromContext()) {
843+
auto fyf_offset = offsetof(GenDataFooter, finishYieldFrom);
844+
as->mov(x86::qword_ptr(x86::rbp, fyf_offset), x86::rdx);
845+
}
846+
#endif
847+
839848
// Sent in value is in RSI, and tstate is in RCX from resume entry-point args
840849
emitLoadResumedYieldInputs(as, instr, RSI, x86::rcx);
841850
#elif defined(CINDER_AARCH64)
@@ -862,168 +871,20 @@ void translateYieldValue(Environ* env, const Instruction* instr) {
862871
// Resumed execution in this generator begins here
863872
as->bind(resume_label);
864873

865-
// Sent in value is in x1, and tstate is in x3 from resume entry-point args
866-
emitLoadResumedYieldInputs(as, instr, X1, a64::x3);
867-
#else
868-
CINDER_UNSUPPORTED
869-
#endif
870-
}
871-
872-
void translateYieldFrom(Environ* env, const Instruction* instr) {
873-
#if defined(CINDER_X86_64)
874-
arch::Builder* as = env->as;
875-
bool skip_initial_send = instr->isYieldFromSkipInitialSend();
876-
877-
// Make sure tstate is in RDI for use in epilogue and here.
878-
PhyLocation tstate = instr->getInput(0)->getStackSlot();
879-
auto tstate_phys_reg = x86::rdi;
880-
as->mov(tstate_phys_reg, x86::ptr(x86::rbp, tstate.loc));
881-
882-
// If we're skipping the initial send the send value is actually the first
883-
// value to yield and so needs to go into RAX to be returned. Otherwise,
884-
// put initial send value in RSI, the same location future send values will
885-
// be on resume.
886-
PhyLocation send_value = instr->getInput(1)->getStackSlot();
887-
const auto send_value_phys_reg = skip_initial_send ? RAX : RSI;
888-
as->mov(
889-
x86::gpq(send_value_phys_reg.loc), x86::ptr(x86::rbp, send_value.loc));
890-
891-
asmjit::Label yield_label = as->newLabel();
892-
if (skip_initial_send) {
893-
as->jmp(yield_label);
894-
} else {
895-
// Setup call to JITRT_GenSend
896-
897-
// Put tstate and the current generator into RCX and RDI respectively, and
898-
// set finish_yield_from (RDX) to 0. This register setup matches that when
899-
// `resume_label` is reached from the resume entry.
900-
auto gen_offs = offsetof(GenDataFooter, gen);
901-
as->mov(x86::rcx, tstate_phys_reg);
902-
as->mov(x86::rdi, x86::ptr(x86::rbp, gen_offs));
903-
as->xor_(x86::rdx, x86::rdx);
904-
}
905-
906-
// Resumed execution begins here
907-
auto resume_label = as->newLabel();
908-
as->bind(resume_label);
909-
910-
// Save tstate from resume to callee-saved reigster.
911-
as->mov(x86::rbx, x86::rcx);
912-
913-
// 'send_value', and 'finish_yield_from' will already be in RSI and RCX
914-
// respectively, either from code above on initial start or from resume entry
915-
// point args.
916-
917-
// Load sub-iterator into RDI
918-
PhyLocation iter_slot = instr->getInput(2)->getStackSlot();
919-
as->mov(x86::rdi, x86::ptr(x86::rbp, iter_slot.loc));
920-
921-
uint64_t func = reinterpret_cast<uint64_t>(
922-
instr->isYieldFromHandleStopAsyncIteration()
923-
? JITRT_GenSendHandleStopAsyncIteration
924-
: JITRT_GenSend);
925-
emitCall(*env, func, instr);
926-
// Yielded or final result value now in RAX. If the result was nullptr then
927-
// done will be set so we'll correctly jump to the following CheckExc.
928-
const auto yf_result_phys_reg = RAX;
929-
const auto done_r = x86::rdx;
930-
931-
// Restore tstate from callee-saved register.
932-
as->mov(tstate_phys_reg, x86::rbx);
933-
934-
// If not done, jump to epilogue which will yield/return the value from
935-
// JITRT_GenSend in RAX.
936-
as->test(done_r, done_r);
937-
asmjit::Label done_label = as->newLabel();
938-
as->jnz(done_label);
939-
940-
as->bind(yield_label);
941-
// Arbitrary scratch register for use in emitStoreGenYieldPoint()
942-
auto scratch_r = x86::r9;
943-
emitStoreGenYieldPoint(as, env, instr, resume_label, x86::rbp, scratch_r);
944-
as->jmp(env->exit_for_yield_label);
945-
946-
as->bind(done_label);
947-
emitLoadResumedYieldInputs(as, instr, yf_result_phys_reg, tstate_phys_reg);
948-
#elif defined(CINDER_AARCH64)
949-
arch::Builder* as = env->as;
950-
bool skip_initial_send = instr->isYieldFromSkipInitialSend();
951-
952-
// Make sure tstate is in X0 for use in epilogue and here.
953-
PhyLocation tstate = instr->getInput(0)->getStackSlot();
954-
auto tstate_phys_reg = a64::x0;
955-
as->ldr(
956-
tstate_phys_reg,
957-
arch::ptr_resolve(as, arch::fp, tstate.loc, arch::reg_scratch_0));
958-
959-
// If we're skipping the initial send the send value is actually the first
960-
// value to yield and so needs to go into X0 to be returned. Otherwise,
961-
// put initial send value in X1, the same location future send values will
962-
// be on resume.
963-
PhyLocation send_value = instr->getInput(1)->getStackSlot();
964-
const auto send_value_phys_reg = skip_initial_send ? X0 : X1;
965-
as->ldr(
966-
a64::x(send_value_phys_reg.loc),
967-
arch::ptr_resolve(as, arch::fp, send_value.loc, arch::reg_scratch_0));
968-
969-
asmjit::Label yield_label = as->newLabel();
970-
if (skip_initial_send) {
971-
as->b(yield_label);
972-
} else {
973-
// Setup call to JITRT_GenSend
974-
975-
// Put tstate and the current generator into X3 and X0 respectively, and
976-
// set finish_yield_from (X2) to 0. This register setup matches that when
977-
// `resume_label` is reached from the resume entry.
978-
auto gen_offs = offsetof(GenDataFooter, gen);
979-
as->mov(a64::x3, tstate_phys_reg);
980-
as->ldr(a64::x0, arch::ptr_offset(arch::fp, gen_offs));
981-
as->mov(a64::x2, a64::xzr);
874+
#if PY_VERSION_HEX < 0x030C0000
875+
// On 3.10, for yield-from yield points, store the finish_yield_from arg
876+
// (X2 from resume entry) into GenDataFooter so the subsequent Send
877+
// instruction can load it.
878+
if (instr->isInYieldFromContext()) {
879+
auto fyf_offset = offsetof(GenDataFooter, finishYieldFrom);
880+
as->str(
881+
a64::x2,
882+
arch::ptr_resolve(as, arch::fp, fyf_offset, arch::reg_scratch_0));
982883
}
884+
#endif
983885

984-
// Resumed execution begins here
985-
auto resume_label = as->newLabel();
986-
as->bind(resume_label);
987-
988-
// Save tstate from resume to callee-saved reigster.
989-
as->mov(a64::x19, a64::x3);
990-
991-
// 'send_value', and 'finish_yield_from' will already be in X1 and X3
992-
// respectively, either from code above on initial start or from resume entry
993-
// point args.
994-
995-
// Load sub-iterator into X0
996-
PhyLocation iter_slot = instr->getInput(2)->getStackSlot();
997-
as->ldr(
998-
a64::x0,
999-
arch::ptr_resolve(as, arch::fp, iter_slot.loc, arch::reg_scratch_0));
1000-
1001-
uint64_t func = reinterpret_cast<uint64_t>(
1002-
instr->isYieldFromHandleStopAsyncIteration()
1003-
? JITRT_GenSendHandleStopAsyncIteration
1004-
: JITRT_GenSend);
1005-
emitCall(*env, func, instr);
1006-
// Yielded or final result value now in X0. If the result was nullptr then
1007-
// done will be set so we'll correctly jump to the following CheckExc.
1008-
const auto yf_result_phys_reg = X0;
1009-
const auto done_r = a64::x2;
1010-
1011-
// Restore tstate from callee-saved register.
1012-
as->mov(tstate_phys_reg, a64::x19);
1013-
1014-
// If not done, jump to epilogue which will yield/return the value from
1015-
// JITRT_GenSend in X0.
1016-
asmjit::Label done_label = as->newLabel();
1017-
as->cbnz(done_r, done_label);
1018-
1019-
as->bind(yield_label);
1020-
// Arbitrary scratch register for use in emitStoreGenYieldPoint()
1021-
auto scratch_r = arch::reg_scratch_0;
1022-
emitStoreGenYieldPoint(as, env, instr, resume_label, arch::fp, scratch_r);
1023-
as->b(env->exit_for_yield_label);
1024-
1025-
as->bind(done_label);
1026-
emitLoadResumedYieldInputs(as, instr, yf_result_phys_reg, tstate_phys_reg);
886+
// Sent in value is in x1, and tstate is in x3 from resume entry-point args
887+
emitLoadResumedYieldInputs(as, instr, X1, a64::x3);
1027888
#else
1028889
CINDER_UNSUPPORTED
1029890
#endif
@@ -1837,28 +1698,6 @@ BEGIN_RULES(Instruction::kYieldInitial)
18371698
GEN(ANY, CALL_C(translateYieldInitial))
18381699
END_RULES
18391700

1840-
#if PY_VERSION_HEX < 0x030C0000
1841-
BEGIN_RULES(Instruction::kYieldFrom)
1842-
GEN(ANY, CALL_C(translateYieldFrom))
1843-
END_RULES
1844-
#else
1845-
// In 3.12+ YieldFrom is a pseudo-op which is YieldValue plus enough
1846-
// information to know which live value contains the target iterator. See
1847-
// emitStoreGenYieldPoint() for where this is captured. The target iterator is
1848-
// used for things like the result of reading gi_yieldfrom.
1849-
BEGIN_RULES(Instruction::kYieldFrom)
1850-
GEN(ANY, CALL_C(translateYieldValue))
1851-
END_RULES
1852-
#endif
1853-
1854-
BEGIN_RULES(Instruction::kYieldFromSkipInitialSend)
1855-
GEN(ANY, CALL_C(translateYieldFrom))
1856-
END_RULES
1857-
1858-
BEGIN_RULES(Instruction::kYieldFromHandleStopAsyncIteration)
1859-
GEN(ANY, CALL_C(translateYieldFrom))
1860-
END_RULES
1861-
18621701
BEGIN_RULES(Instruction::kYieldValue)
18631702
GEN(ANY, CALL_C(translateYieldValue))
18641703
END_RULES
@@ -3150,28 +2989,6 @@ BEGIN_RULES(Instruction::kYieldInitial)
31502989
GEN(ANY, CALL_C(translateYieldInitial))
31512990
END_RULES
31522991

3153-
#if PY_VERSION_HEX < 0x030C0000
3154-
BEGIN_RULES(Instruction::kYieldFrom)
3155-
GEN(ANY, CALL_C(translateYieldFrom))
3156-
END_RULES
3157-
#else
3158-
// In 3.12+ YieldFrom is a pseudo-op which is YieldValue plus enough
3159-
// information to know which live value contains the target iterator. See
3160-
// emitStoreGenYieldPoint() for where this is captured. The target iterator is
3161-
// used for things like the result of reading gi_yieldfrom.
3162-
BEGIN_RULES(Instruction::kYieldFrom)
3163-
GEN(ANY, CALL_C(translateYieldValue))
3164-
END_RULES
3165-
#endif
3166-
3167-
BEGIN_RULES(Instruction::kYieldFromSkipInitialSend)
3168-
GEN(ANY, CALL_C(translateYieldFrom))
3169-
END_RULES
3170-
3171-
BEGIN_RULES(Instruction::kYieldFromHandleStopAsyncIteration)
3172-
GEN(ANY, CALL_C(translateYieldFrom))
3173-
END_RULES
3174-
31752992
BEGIN_RULES(Instruction::kYieldValue)
31762993
GEN(ANY, CALL_C(translateYieldValue))
31772994
END_RULES

cinderx/Jit/deopt.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -421,11 +421,6 @@ static DeoptReason getDeoptReason(const jit::hir::DeoptBase& instr) {
421421
case jit::hir::Opcode::kLoadSplitDictItem: {
422422
return DeoptReason::kGuardFailure;
423423
}
424-
case jit::hir::Opcode::kYieldAndYieldFrom:
425-
case jit::hir::Opcode::kYieldFromHandleStopAsyncIteration:
426-
case jit::hir::Opcode::kYieldFrom: {
427-
return DeoptReason::kYieldFrom;
428-
}
429424
case jit::hir::Opcode::kRaise: {
430425
return DeoptReason::kRaise;
431426
}

cinderx/Jit/gen_data_footer.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ struct GenDataFooter {
5959
// Current overall state of the JIT.
6060
// In 3.12+ we use the new PyGenObject::gi_frame_state field instead.
6161
CiJITGenState state{};
62+
63+
// On resume from a yield-from yield point, the resume entry receives a
64+
// finish_yield_from flag. translateYieldValue stores it here so that the
65+
// subsequent Send instruction can load it.
66+
uint64_t finishYieldFrom{0};
6267
#endif
6368

6469
// Allocated space before this struct in 64-bit words.

0 commit comments

Comments
 (0)