Skip to content

Commit 82cc70d

Browse files
Carl Meyerfacebook-github-bot
authored andcommitted
(re-land) use func watchers
Summary: This reverts D47602760, and re-lands D47156535, D47165628, and D47306492, with changes to adapt to the new `cinder.cpp` compilation unit for Cinder-wide infra. Since Static Python relies on function watchers, function watchers go there rather than in the JIT. The revert wasn't due to any problems discovered in this code, it was just to minimize conflicts in the dict watchers revert. With this diff, all `#ifdef ENABLE_CINDERX` couplings have been removed from `funcobject.c`. This removes the `InitFunction` HIR opcode from the JIT, since that opcode existed to do Cinder entrypoint initialization for new functions, and this now happens automatically as part of `MakeFunction`, due to the function watcher. In order to properly initialize functions created before `Cinder_Init` is called, we use the new (upstream and backported) `PyUnstable_GC_VisitObjects` API to visit all GC objects and initialize the ones that are functions. I eliminated the call to `PyEntry_init` after a change to function defaults. This only happened if the function was not JIT-compiled. I think this dates back to Cinder 3.8 where we had many more (non-JIT) function entrypoint variants, depending on number of arguments, argument defaults, etc, and needed to ensure we set the right one. But in 3.10 we eliminated all those custom entry points, so I don't think this is needed anymore. I had to make a fix to the func watchers tests so they would work correctly when running with another func watcher active. I also submitted this fix upstream: python/cpython#106286 And I had to delete a C++ test that was passing only due to a series of accidents. The func-modified callbacks (both before and after this diff) are global and dispatch only to the global singleton `jit_ctx` in `pyjit.cpp`, so they can't be tested correctly by a unit test that never globally enables the JIT and only constructs its own private JIT context. The function-modified callback in this test was doing nothing, but the entrypoint of the function was getting re-set to `_PyFunction_Vectorcall` anyway due to `PyEntry_init` seeing the JIT as not enabled; this seems unlikely to be a realistic scenario the test was intended to check. There is already a Python-level test (`test_funcattrs.FunctionPropertiesTest.test_copying___code__`) that verifies that re-assigning `__code__` changes the behavior of the function; we run this test under the JIT, and it fails if we fail to deopt the function on `__code__` reassignment. So the behavior we care about is already tested. Reviewed By: alexmalyshev Differential Revision: D48128982 fbshipit-source-id: cd56b4c485f62e33580b4838882cd4c610653a4f
1 parent a7ad728 commit 82cc70d

20 files changed

+95
-118
lines changed

Cinder/cinder.cpp

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "Jit/pyjit.h"
66

77
static int cinder_dict_watcher_id = -1;
8+
static int cinder_func_watcher_id = -1;
89

910
static int cinder_dict_watcher(
1011
PyDict_WatchEvent event,
@@ -55,10 +56,72 @@ void Cinder_UnwatchDict(PyObject* dict) {
5556
}
5657
}
5758

59+
static int cinder_func_watcher(
60+
PyFunction_WatchEvent event,
61+
PyFunctionObject* func,
62+
PyObject* new_value) {
63+
switch (event) {
64+
case PyFunction_EVENT_CREATE:
65+
PyEntry_init(func);
66+
break;
67+
case PyFunction_EVENT_MODIFY_CODE:
68+
_PyJIT_FuncModified(func);
69+
// having deopted the func, we want to immediately consider recompiling.
70+
// func_set_code will assign this again later, but we do it early so
71+
// PyEntry_init can consider the new code object now
72+
Py_INCREF(new_value);
73+
Py_XSETREF(func->func_code, new_value);
74+
PyEntry_init(func);
75+
break;
76+
case PyFunction_EVENT_MODIFY_DEFAULTS:
77+
break;
78+
case PyFunction_EVENT_MODIFY_KWDEFAULTS:
79+
break;
80+
case PyFunction_EVENT_MODIFY_QUALNAME:
81+
// allow reconsideration of whether this function should be compiled
82+
if (!_PyJIT_IsCompiled((PyObject*)func)) {
83+
// func_set_qualname will assign this again, but we need to assign it
84+
// now so that PyEntry_init can consider the new qualname
85+
Py_INCREF(new_value);
86+
Py_XSETREF(func->func_qualname, new_value);
87+
PyEntry_init(func);
88+
}
89+
break;
90+
case PyFunction_EVENT_DESTROY:
91+
_PyJIT_FuncDestroyed(func);
92+
break;
93+
}
94+
return 0;
95+
}
96+
97+
static int init_funcs_visitor(PyObject* obj, void*) {
98+
if (PyFunction_Check(obj)) {
99+
PyEntry_init((PyFunctionObject*)obj);
100+
}
101+
return 1;
102+
}
103+
104+
static void init_already_existing_funcs() {
105+
PyUnstable_GC_VisitObjects(init_funcs_visitor, NULL);
106+
}
107+
108+
static int cinder_install_func_watcher() {
109+
int watcher_id = PyFunction_AddWatcher(cinder_func_watcher);
110+
if (watcher_id < 0) {
111+
return -1;
112+
}
113+
cinder_func_watcher_id = watcher_id;
114+
return 0;
115+
}
116+
58117
int Cinder_Init() {
59118
if (cinder_install_dict_watcher() < 0) {
60119
return -1;
61120
}
121+
if (cinder_install_func_watcher() < 0) {
122+
return -1;
123+
}
124+
init_already_existing_funcs();
62125
return _PyJIT_Initialize();
63126
}
64127

@@ -67,18 +130,29 @@ int Cinder_Fini() {
67130
}
68131

69132
int Cinder_InitSubInterp() {
70-
// HACK: for now we assume we are the only dict watcher out there, so that we
71-
// can just keep track of a single dict watcher ID rather than one per
72-
// interpreter.
73-
int prev_watcher_id = cinder_dict_watcher_id;
133+
// HACK: for now we assume we are the only watcher out there, so that we can
134+
// just keep track of a single watcher ID rather than one per interpreter.
135+
int prev_dict_watcher_id = cinder_dict_watcher_id;
74136
JIT_CHECK(
75-
prev_watcher_id >= 0,
137+
prev_dict_watcher_id >= 0,
76138
"Initializing sub-interpreter without main interpreter?");
77139
if (cinder_install_dict_watcher() < 0) {
78140
return -1;
79141
}
80142
JIT_CHECK(
81-
cinder_dict_watcher_id == prev_watcher_id,
143+
cinder_dict_watcher_id == prev_dict_watcher_id,
82144
"Somebody else watching dicts?");
145+
146+
int prev_func_watcher_id = cinder_func_watcher_id;
147+
JIT_CHECK(
148+
prev_func_watcher_id >= 0,
149+
"Initializing sub-interpreter without main interpreter?");
150+
if (cinder_install_func_watcher() < 0) {
151+
return -1;
152+
}
153+
JIT_CHECK(
154+
cinder_func_watcher_id == prev_func_watcher_id,
155+
"Somebody else watching functions?");
156+
83157
return 0;
84158
}

Jit/hir/analysis.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,6 @@ bool isPassthrough(const Instr& instr) {
178178
case Opcode::kHintType:
179179
case Opcode::kSnapshot:
180180
case Opcode::kIncref:
181-
case Opcode::kInitFunction:
182181
case Opcode::kReturn:
183182
case Opcode::kSetCellItem:
184183
case Opcode::kSetFunctionAttr:

Jit/hir/builder.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2917,7 +2917,6 @@ void HIRBuilder::emitMakeFunction(
29172917
tc.emit<SetFunctionAttr>(defaults, func, FunctionAttr::kDefaults);
29182918
}
29192919

2920-
tc.emit<InitFunction>(func);
29212920
tc.frame.stack.push(func);
29222921
}
29232922

Jit/hir/hir.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,6 @@ bool Instr::isReplayable() const {
216216
case Opcode::kInPlaceOp:
217217
case Opcode::kIncref:
218218
case Opcode::kInitialYield:
219-
case Opcode::kInitFunction:
220219
case Opcode::kInvokeIterNext:
221220
case Opcode::kInvokeStaticFunction:
222221
case Opcode::kInvokeMethod:

Jit/hir/hir.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,6 @@ struct FrameState {
267267
V(ImportName) \
268268
V(InPlaceOp) \
269269
V(Incref) \
270-
V(InitFunction) \
271270
V(InitialYield) \
272271
V(IntBinaryOp) \
273272
V(PrimitiveBoxBool) \
@@ -1267,9 +1266,6 @@ DEFINE_SIMPLE_INSTR(
12671266
Operands<2>,
12681267
DeoptBase);
12691268

1270-
// Calls PyEntry_Init(func)
1271-
DEFINE_SIMPLE_INSTR(InitFunction, (TFunc), Operands<1>);
1272-
12731269
// Takes a list as operand 0
12741270
// Takes an item as operand 1
12751271
DEFINE_SIMPLE_INSTR(

Jit/hir/memory_effects.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ MemoryEffects memoryEffects(const Instr& inst) {
5050
case Opcode::kMakeCell:
5151
case Opcode::kMakeCheckedDict:
5252
case Opcode::kMakeDict:
53-
case Opcode::kMakeFunction:
5453
case Opcode::kMakeSet:
5554
case Opcode::kMakeTupleFromList:
5655
case Opcode::kPrimitiveCompare:
@@ -179,9 +178,8 @@ MemoryEffects memoryEffects(const Instr& inst) {
179178
case Opcode::kXDecref:
180179
return {false, AEmpty, {1, 1}, AManagedHeapAny};
181180

182-
case Opcode::kInitFunction:
183-
// InitFunction mostly writes to a bunch of func fields we don't track,
184-
// but it can also invoke the JIT which may at some point have effects
181+
case Opcode::kMakeFunction:
182+
// MakeFunction can invoke the JIT which may at some point have effects
185183
// worth tracking.
186184
return commonEffects(inst, AOther);
187185

Jit/hir/printer.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,6 @@ static std::string format_immediates(const Instr& instr) {
253253
case Opcode::kGuard:
254254
case Opcode::kIncref:
255255
case Opcode::kInitialYield:
256-
case Opcode::kInitFunction:
257256
case Opcode::kInvokeIterNext:
258257
case Opcode::kIsInstance:
259258
case Opcode::kIsNegativeAndErrOccurred:

Jit/hir/ssa.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,6 @@ Type outputType(
696696
case Opcode::kGuard:
697697
case Opcode::kHintType:
698698
case Opcode::kIncref:
699-
case Opcode::kInitFunction:
700699
case Opcode::kRaise:
701700
case Opcode::kRaiseAwaitableError:
702701
case Opcode::kRaiseStatic:

Jit/lir/generator.cpp

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2345,12 +2345,6 @@ LIRGenerator::TranslatedBlock LIRGenerator::TranslateOneBasicBlock(
23452345
bbb.AppendCode(ss.str());
23462346
break;
23472347
}
2348-
case Opcode::kInitFunction: {
2349-
auto instr = static_cast<const InitFunction*>(&i);
2350-
2351-
bbb.AppendInvoke(PyEntry_init, instr->GetOperand(0));
2352-
break;
2353-
}
23542348
case Opcode::kMakeFunction: {
23552349
auto instr = static_cast<const MakeFunction*>(&i);
23562350
auto qualname = instr->GetOperand(0);

Jit/pyjit.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1739,7 +1739,7 @@ static int install_jit_audit_hook() {
17391739
int _PyJIT_Initialize() {
17401740
// If we have data symbols which are public but not used within CPython code,
17411741
// we need to ensure the linker doesn't GC the .data section containing them.
1742-
// We can do this by referencing at least symbol from that sourfe module.
1742+
// We can do this by referencing at least symbol from that source module.
17431743
// In future versions of clang/gcc we may be able to eliminate this with
17441744
// 'keep' and/or 'used' attributes.
17451745
//

0 commit comments

Comments
 (0)