Skip to content

Commit 6dff293

Browse files
jbower-fbfacebook-github-bot
authored andcommitted
Add JIT support for LOAD_BUILD_CLASS
Summary: Apparently we've never supported this?! Quite surprising as it must mean we were at least missing quite a few functions from JIT during test. The code assumes the builtins are always a dictionary, whereas the interpreters bytecode can handle a non-dictionary `PyObject*`. I think this assumption is safe as the preloader seems to always have the builtins as a dictionary. Reviewed By: alexmalyshev Differential Revision: D82689837 fbshipit-source-id: 6d10820eea803a365d14ec5268dbfe56aa6b502d
1 parent 8d3e32a commit 6dff293

12 files changed

Lines changed: 216 additions & 70 deletions

cinderx/Jit/hir/builder.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ bool isSupportedOpcode(int opcode) {
152152
case LOAD_ASSERTION_ERROR:
153153
case LOAD_ATTR:
154154
case LOAD_ATTR_SUPER:
155+
case LOAD_BUILD_CLASS:
155156
case LOAD_CLOSURE:
156157
case LOAD_COMMON_CONSTANT:
157158
case LOAD_CONST:
@@ -1553,6 +1554,10 @@ void HIRBuilder::translate(
15531554
emitSetFunctionAttribute(tc, bc_instr);
15541555
break;
15551556
}
1557+
case LOAD_BUILD_CLASS: {
1558+
emitLoadBuildClass(tc);
1559+
break;
1560+
}
15561561
case CHECK_EG_MATCH:
15571562
case CHECK_EXC_MATCH:
15581563
case CLEANUP_THROW:
@@ -4986,6 +4991,20 @@ void HIRBuilder::emitSetFunctionAttribute(
49864991
stack.push(func);
49874992
}
49884993

4994+
void HIRBuilder::emitLoadBuildClass(TranslationContext& tc) {
4995+
Register* result = temps_.AllocateStack();
4996+
Register* builtins = temps_.AllocateNonStack();
4997+
Register* key = temps_.AllocateNonStack();
4998+
tc.emit<LoadConst>(builtins, Type::fromObject(tc.frame.builtins));
4999+
// Starting at the preloader the JIT seems to assume builtins will be a
5000+
// dictionary, however I'm not sure there's any guarantee of this.
5001+
Register* builtins_dict = temps_.AllocateNonStack();
5002+
tc.emit<GuardType>(builtins_dict, TDictExact, builtins, tc.frame);
5003+
tc.emit<LoadConst>(key, Type::fromObject(Runtime::get()->strBuildClass()));
5004+
tc.emit<DictSubscr>(result, builtins_dict, key, tc.frame);
5005+
tc.frame.stack.push(result);
5006+
}
5007+
49895008
void HIRBuilder::insertEvalBreakerCheck(
49905009
CFG& cfg,
49915010
BasicBlock* check_block,

cinderx/Jit/hir/builder.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,8 @@ class HIRBuilder {
477477
TranslationContext& tc,
478478
const BytecodeInstruction& bc_instr);
479479

480+
void emitLoadBuildClass(TranslationContext& tc);
481+
480482
BorrowedRef<> constArg(const jit::BytecodeInstruction& bc_instr);
481483

482484
ExecutionBlock popBlock(CFG& cfg, TranslationContext& tc);

cinderx/Jit/pyjit.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,14 @@ hir::Preloader* preload(BorrowedRef<> unit) {
931931
return nullptr;
932932
}
933933
BorrowedRef<PyFunctionObject>& outer_func = it->second;
934+
// Assuming the builtins will always be a dictionary goes way back in the
935+
// JIT's history. I'm not sure what guarantees this though. Tread carefully
936+
// but try not to blow things up if this happens in production code.
937+
JIT_DCHECK(
938+
PyDict_CheckExact(outer_func->func_builtins),
939+
"Unexpected type for builtins ({}) on function {}",
940+
Py_TYPE(outer_func->func_builtins)->tp_name,
941+
funcFullname(outer_func));
934942
preloader = hir::Preloader::makePreloader(
935943
code,
936944
outer_func->func_builtins,

cinderx/Jit/runtime.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,15 @@
1313

1414
namespace jit {
1515

16-
Runtime::Runtime() : zero_(Ref<>::create(PyLong_FromLong(0))) {
16+
Runtime::Runtime()
17+
: zero_(Ref<>::create(PyLong_FromLong(0))),
18+
#if PY_VERSION_HEX >= 0x030C0000
19+
str_build_class_(Ref<>::create(&_Py_ID(__build_class__)))
20+
#else
21+
str_build_class_(
22+
Ref<>::create(PyUnicode_InternFromString("__build_class__")))
23+
#endif
24+
{
1725
#if PY_VERSION_HEX >= 0x030E0000
1826
PyObject** common_consts = PyThreadState_GET()->interp->common_consts;
1927
for (int i = 0; i < NUM_COMMON_CONSTANTS; i++) {

cinderx/Jit/runtime.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,10 @@ class Runtime : public IRuntime {
274274
return zero_.get();
275275
}
276276

277+
BorrowedRef<> strBuildClass() {
278+
return str_build_class_.get();
279+
}
280+
277281
void watchPendingTypes();
278282
void fixupFunctionEntryCachePostMultiThreadedCompile();
279283

@@ -315,6 +319,7 @@ class Runtime : public IRuntime {
315319
type_deopt_patchers_;
316320

317321
Ref<> zero_;
322+
Ref<> str_build_class_;
318323
std::unordered_set<BorrowedRef<PyTypeObject>> pending_watches_;
319324

320325
std::vector<hir::Type> common_constant_types_;

cinderx/PythonLib/cinderx/test_support.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# Copyright (c) Meta Platforms, Inc. and affiliates.
22
# pyre-strict
33

4+
import abc
45
import ctypes
6+
import dis
57
import importlib
68
import multiprocessing
79
import os.path
@@ -212,3 +214,49 @@ def wrapped(*args: object) -> TRet:
212214
return value
213215

214216
return wrapped
217+
218+
219+
class AssertBytecodeContainsMixin(abc.ABC):
220+
@abc.abstractmethod
221+
def assertIn(
222+
self, expected: object, actual: Sequence[object], msg: str | None = None
223+
) -> None:
224+
raise NotImplementedError
225+
226+
@abc.abstractmethod
227+
def assertTrue(self, expr: object, msg: str | None = None) -> None:
228+
raise NotImplementedError
229+
230+
def assertBytecodeContains(
231+
self,
232+
func: object,
233+
expected_opcode: str,
234+
expected_oparg: int | None = None,
235+
) -> None:
236+
try:
237+
# pyre-ignore[16] - for things wrapped by fail_if_deopt()
238+
inner_function = func.inner_function
239+
except AttributeError:
240+
pass
241+
else:
242+
func = inner_function
243+
244+
bytecode_instructions = dis.get_instructions(func)
245+
246+
if expected_oparg is None:
247+
opcodes = [instr.opname for instr in bytecode_instructions]
248+
self.assertIn(
249+
expected_opcode,
250+
opcodes,
251+
f"{expected_opcode} opcode should be present in {func.__name__} bytecode",
252+
)
253+
else:
254+
matching_instructions = [
255+
instr
256+
for instr in bytecode_instructions
257+
if instr.opname == expected_opcode and instr.arg == expected_oparg
258+
]
259+
self.assertTrue(
260+
len(matching_instructions) > 0,
261+
f"{expected_opcode} opcode with oparg {expected_oparg} should be present in {func.__name__} bytecode",
262+
)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
3+
import sys
4+
import unittest
5+
6+
import cinderx.test_support as cinder_support
7+
8+
9+
@unittest.skipUnless(
10+
sys.version_info >= (3, 10) and sys.version_info < (3, 11), "Python 3.10 only"
11+
)
12+
class Python310Bytecodes(unittest.TestCase, cinder_support.AssertBytecodeContainsMixin):
13+
def test_LOAD_BUILD_CLASS(self):
14+
@cinder_support.fail_if_deopt
15+
@cinder_support.failUnlessJITCompiled
16+
def x():
17+
class TestClass:
18+
pass
19+
20+
return TestClass()
21+
22+
result = x()
23+
self.assertIsNotNone(result)
24+
self.assertEqual(result.__class__.__name__, "TestClass")
25+
self.assertBytecodeContains(x, "LOAD_BUILD_CLASS")
26+
27+
28+
if __name__ == "__main__":
29+
unittest.main()
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
3+
import sys
4+
import unittest
5+
6+
import cinderx.test_support as cinder_support
7+
8+
9+
@unittest.skipUnless(
10+
sys.version_info >= (3, 12) and sys.version_info < (3, 13), "Python 3.12 only"
11+
)
12+
class Python312Bytecodes(unittest.TestCase, cinder_support.AssertBytecodeContainsMixin):
13+
def test_LOAD_BUILD_CLASS(self):
14+
@cinder_support.fail_if_deopt
15+
@cinder_support.failUnlessJITCompiled
16+
def x():
17+
class TestClass:
18+
pass
19+
20+
return TestClass()
21+
22+
result = x()
23+
self.assertIsNotNone(result)
24+
self.assertEqual(result.__class__.__name__, "TestClass")
25+
self.assertBytecodeContains(x, "LOAD_BUILD_CLASS")
26+
27+
28+
if __name__ == "__main__":
29+
unittest.main()

0 commit comments

Comments
 (0)