resume_execution.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. # mypy: allow-untyped-defs
  2. import copy
  3. import dataclasses
  4. import sys
  5. import types
  6. from typing import Any, cast, Dict, List, Optional, Tuple
  7. from .bytecode_transformation import (
  8. create_call_function,
  9. create_call_method,
  10. create_dup_top,
  11. create_instruction,
  12. create_jump_absolute,
  13. create_load_method,
  14. Instruction,
  15. InstructionExnTabEntry,
  16. transform_code_object,
  17. unique_id,
  18. )
  19. from .utils import ExactWeakKeyDictionary
  20. # taken from code.h in cpython
  21. CO_OPTIMIZED = 0x0001
  22. CO_NEWLOCALS = 0x0002
  23. CO_VARARGS = 0x0004
  24. CO_VARKEYWORDS = 0x0008
  25. CO_NESTED = 0x0010
  26. CO_GENERATOR = 0x0020
  27. CO_NOFREE = 0x0040
  28. CO_COROUTINE = 0x0080
  29. CO_ITERABLE_COROUTINE = 0x0100
  30. CO_ASYNC_GENERATOR = 0x0200
  31. # trace_rules.py import this constant for consistency
  32. TORCH_DYNAMO_RESUME_IN_PREFIX = "torch_dynamo_resume_in"
  33. @dataclasses.dataclass(frozen=True)
  34. class ReenterWith:
  35. stack_index: int
  36. target_values: Optional[Tuple[Any, ...]] = None
  37. # If we do not want to destroy the stack, we can do the same thing as a
  38. # `SETUP_WITH` block, only that we store the context manager in a local_symbol
  39. def try_except(self, code_options, cleanup: List[Instruction]):
  40. """
  41. Codegen based off of:
  42. load args
  43. enter context
  44. try:
  45. (rest)
  46. finally:
  47. exit context
  48. """
  49. # NOTE: we assume that TOS is a context manager CLASS!
  50. load_args = []
  51. if self.target_values:
  52. load_args = [
  53. create_instruction("LOAD_CONST", argval=val)
  54. for val in self.target_values
  55. ]
  56. ctx_name = unique_id(f"___context_manager_{self.stack_index}")
  57. if ctx_name not in code_options["co_varnames"]:
  58. code_options["co_varnames"] += (ctx_name,)
  59. for name in ["__enter__", "__exit__"]:
  60. if name not in code_options["co_names"]:
  61. code_options["co_names"] += (name,)
  62. except_jump_target = create_instruction(
  63. "NOP" if sys.version_info < (3, 11) else "PUSH_EXC_INFO"
  64. )
  65. cleanup_complete_jump_target = create_instruction("NOP")
  66. setup_finally = [
  67. *load_args,
  68. *create_call_function(len(load_args), True),
  69. create_instruction("STORE_FAST", argval=ctx_name),
  70. create_instruction("LOAD_FAST", argval=ctx_name),
  71. create_load_method("__enter__"),
  72. *create_call_method(0),
  73. create_instruction("POP_TOP"),
  74. ]
  75. if sys.version_info < (3, 11):
  76. setup_finally.append(
  77. create_instruction("SETUP_FINALLY", target=except_jump_target)
  78. )
  79. else:
  80. exn_tab_begin = create_instruction("NOP")
  81. exn_tab_end = create_instruction("NOP")
  82. exn_tab_begin.exn_tab_entry = InstructionExnTabEntry(
  83. exn_tab_begin,
  84. exn_tab_end,
  85. except_jump_target,
  86. self.stack_index + 1,
  87. False,
  88. )
  89. setup_finally.append(exn_tab_begin)
  90. def create_reset():
  91. return [
  92. create_instruction("LOAD_FAST", argval=ctx_name),
  93. create_load_method("__exit__"),
  94. create_instruction("LOAD_CONST", argval=None),
  95. create_dup_top(),
  96. create_dup_top(),
  97. *create_call_method(3),
  98. create_instruction("POP_TOP"),
  99. ]
  100. if sys.version_info < (3, 9):
  101. epilogue = [
  102. create_instruction("POP_BLOCK"),
  103. create_instruction("BEGIN_FINALLY"),
  104. except_jump_target,
  105. *create_reset(),
  106. create_instruction("END_FINALLY"),
  107. ]
  108. elif sys.version_info < (3, 11):
  109. epilogue = [
  110. create_instruction("POP_BLOCK"),
  111. *create_reset(),
  112. create_instruction("JUMP_FORWARD", target=cleanup_complete_jump_target),
  113. except_jump_target,
  114. *create_reset(),
  115. create_instruction("RERAISE"),
  116. cleanup_complete_jump_target,
  117. ]
  118. else:
  119. finally_exn_tab_end = create_instruction("RERAISE", arg=0)
  120. finally_exn_tab_target = create_instruction("COPY", arg=3)
  121. except_jump_target.exn_tab_entry = InstructionExnTabEntry(
  122. except_jump_target,
  123. finally_exn_tab_end,
  124. finally_exn_tab_target,
  125. self.stack_index + 2,
  126. True,
  127. )
  128. epilogue = [
  129. exn_tab_end,
  130. *create_reset(),
  131. create_instruction("JUMP_FORWARD", target=cleanup_complete_jump_target),
  132. except_jump_target, # PUSH_EXC_INFO
  133. *create_reset(),
  134. finally_exn_tab_end, # RERAISE 0
  135. finally_exn_tab_target, # COPY 3
  136. create_instruction("POP_EXCEPT"),
  137. create_instruction("RERAISE", arg=1),
  138. cleanup_complete_jump_target,
  139. ]
  140. cleanup[:] = epilogue + cleanup
  141. return setup_finally
  142. def __call__(self, code_options, cleanup):
  143. """
  144. Codegen based off of:
  145. with ctx(args):
  146. (rest)
  147. """
  148. # NOTE: we assume that TOS is a context manager CLASS!
  149. load_args = []
  150. if self.target_values:
  151. load_args = [
  152. create_instruction("LOAD_CONST", argval=val)
  153. for val in self.target_values
  154. ]
  155. if sys.version_info < (3, 9):
  156. with_cleanup_start = create_instruction("WITH_CLEANUP_START")
  157. begin_finally = create_instruction("BEGIN_FINALLY")
  158. cleanup[:] = [
  159. create_instruction("POP_BLOCK"),
  160. begin_finally,
  161. with_cleanup_start,
  162. create_instruction("WITH_CLEANUP_FINISH"),
  163. create_instruction("END_FINALLY"),
  164. ] + cleanup
  165. return [
  166. *load_args,
  167. create_instruction("CALL_FUNCTION", arg=len(load_args)),
  168. create_instruction("SETUP_WITH", target=with_cleanup_start),
  169. create_instruction("POP_TOP"),
  170. ], None
  171. elif sys.version_info < (3, 11):
  172. with_except_start = create_instruction("WITH_EXCEPT_START")
  173. pop_top_after_with_except_start = create_instruction("POP_TOP")
  174. cleanup_complete_jump_target = create_instruction("NOP")
  175. cleanup[:] = [
  176. create_instruction("POP_BLOCK"),
  177. create_instruction("LOAD_CONST", argval=None),
  178. create_instruction("DUP_TOP"),
  179. create_instruction("DUP_TOP"),
  180. create_instruction("CALL_FUNCTION", arg=3),
  181. create_instruction("POP_TOP"),
  182. create_instruction("JUMP_FORWARD", target=cleanup_complete_jump_target),
  183. with_except_start,
  184. create_instruction(
  185. "POP_JUMP_IF_TRUE", target=pop_top_after_with_except_start
  186. ),
  187. create_instruction("RERAISE"),
  188. pop_top_after_with_except_start,
  189. create_instruction("POP_TOP"),
  190. create_instruction("POP_TOP"),
  191. create_instruction("POP_EXCEPT"),
  192. create_instruction("POP_TOP"),
  193. cleanup_complete_jump_target,
  194. ] + cleanup
  195. return [
  196. *load_args,
  197. create_instruction("CALL_FUNCTION", arg=len(load_args)),
  198. create_instruction("SETUP_WITH", target=with_except_start),
  199. create_instruction("POP_TOP"),
  200. ], None
  201. else:
  202. pop_top_after_with_except_start = create_instruction("POP_TOP")
  203. cleanup_complete_jump_target = create_instruction("NOP")
  204. def create_load_none():
  205. return create_instruction("LOAD_CONST", argval=None)
  206. exn_tab_1_begin = create_instruction("POP_TOP")
  207. exn_tab_1_end = create_instruction("NOP")
  208. exn_tab_1_target = create_instruction("PUSH_EXC_INFO")
  209. exn_tab_2_end = create_instruction("RERAISE", arg=2)
  210. exn_tab_2_target = create_instruction("COPY", arg=3)
  211. exn_tab_1_begin.exn_tab_entry = InstructionExnTabEntry(
  212. exn_tab_1_begin,
  213. exn_tab_1_end,
  214. exn_tab_1_target,
  215. self.stack_index + 1,
  216. True,
  217. )
  218. exn_tab_1_target.exn_tab_entry = InstructionExnTabEntry(
  219. exn_tab_1_target,
  220. exn_tab_2_end,
  221. exn_tab_2_target,
  222. self.stack_index + 3,
  223. True,
  224. )
  225. pop_top_after_with_except_start.exn_tab_entry = InstructionExnTabEntry(
  226. pop_top_after_with_except_start,
  227. pop_top_after_with_except_start,
  228. exn_tab_2_target,
  229. self.stack_index + 3,
  230. True,
  231. )
  232. cleanup[:] = [
  233. exn_tab_1_end,
  234. create_load_none(),
  235. create_load_none(),
  236. create_load_none(),
  237. *create_call_function(2, False),
  238. create_instruction("POP_TOP"),
  239. create_instruction("JUMP_FORWARD", target=cleanup_complete_jump_target),
  240. exn_tab_1_target, # PUSH_EXC_INFO
  241. create_instruction("WITH_EXCEPT_START"),
  242. create_instruction(
  243. "POP_JUMP_FORWARD_IF_TRUE"
  244. if sys.version_info < (3, 12)
  245. else "POP_JUMP_IF_TRUE",
  246. target=pop_top_after_with_except_start,
  247. ),
  248. exn_tab_2_end, # RERAISE 2
  249. exn_tab_2_target, # COPY 3
  250. create_instruction("POP_EXCEPT"),
  251. create_instruction("RERAISE", arg=1),
  252. pop_top_after_with_except_start,
  253. create_instruction("POP_EXCEPT"),
  254. create_instruction("POP_TOP"),
  255. create_instruction("POP_TOP"),
  256. cleanup_complete_jump_target,
  257. ] + cleanup
  258. return [
  259. *load_args,
  260. *create_call_function(len(load_args), True),
  261. create_instruction("BEFORE_WITH"),
  262. exn_tab_1_begin, # POP_TOP
  263. ], exn_tab_1_target
  264. @dataclasses.dataclass
  265. class ResumeFunctionMetadata:
  266. code: types.CodeType
  267. instructions: List[Instruction] = dataclasses.field(default_factory=list)
  268. # Python 3.11+ fields
  269. # NOTE: Python 3.11 removed blocks, but for our purposes, a "block" consists
  270. # of instructions of all exception table entries that have the same target.
  271. # map from PUSH_EXC_INFO's in the prefix to original block target offset
  272. prefix_block_target_offset_remap: List[int] = dataclasses.field(
  273. default_factory=list
  274. )
  275. # map from new block target offsets to original block target offsets
  276. block_target_offset_remap: Optional[Dict[int, int]] = None
  277. def _filter_iter(l1, l2, cond):
  278. """
  279. Two-pointer conditional filter.
  280. e.g. _filter_iter(insts, sorted_offsets, lambda i, o: i.offset == o)
  281. returns the instructions with offsets in sorted_offsets
  282. """
  283. it = iter(l2)
  284. res = []
  285. try:
  286. cur = next(it)
  287. for val in l1:
  288. if cond(val, cur):
  289. res.append(val)
  290. cur = next(it)
  291. except StopIteration:
  292. pass
  293. return res
  294. def _load_tuple_and_call(tup):
  295. insts = []
  296. if sys.version_info >= (3, 11):
  297. insts.append(create_instruction("PUSH_NULL"))
  298. insts.append(create_instruction("SWAP", arg=2))
  299. for val in tup:
  300. insts.append(create_instruction("LOAD_CONST", argval=val))
  301. insts.extend(create_call_function(len(tup), False))
  302. return insts
  303. class ContinueExecutionCache:
  304. cache = ExactWeakKeyDictionary()
  305. generated_code_metadata = ExactWeakKeyDictionary()
  306. @classmethod
  307. def lookup(cls, code, lineno, *key):
  308. if code not in cls.cache:
  309. cls.cache[code] = dict()
  310. key = tuple(key)
  311. if key not in cls.cache[code]:
  312. cls.cache[code][key] = cls.generate(code, lineno, *key)
  313. return cls.cache[code][key]
  314. @classmethod
  315. def generate(
  316. cls,
  317. code,
  318. lineno,
  319. offset: int,
  320. setup_fn_target_offsets: Tuple[int], # only used in Python 3.11+
  321. nstack: int,
  322. argnames: Tuple[str],
  323. argnames_null: Tuple[str],
  324. setup_fns: Tuple[ReenterWith],
  325. stack_ctx_vars: Tuple[int, Tuple[Any]],
  326. argnames_ctx_vars: Tuple[str, Tuple[Any]],
  327. null_idxes: Tuple[int],
  328. ) -> types.CodeType:
  329. assert offset is not None
  330. assert not (
  331. code.co_flags
  332. & (CO_GENERATOR | CO_COROUTINE | CO_ITERABLE_COROUTINE | CO_ASYNC_GENERATOR)
  333. )
  334. assert code.co_flags & CO_OPTIMIZED
  335. if code in ContinueExecutionCache.generated_code_metadata:
  336. return cls.generate_based_on_original_code_object(
  337. code,
  338. lineno,
  339. offset,
  340. setup_fn_target_offsets,
  341. nstack,
  342. argnames,
  343. argnames_null,
  344. setup_fns,
  345. stack_ctx_vars,
  346. argnames_ctx_vars,
  347. null_idxes,
  348. )
  349. is_py311_plus = sys.version_info >= (3, 11)
  350. meta = ResumeFunctionMetadata(code)
  351. def update(instructions: List[Instruction], code_options: Dict[str, Any]):
  352. meta.instructions = copy.deepcopy(instructions)
  353. args = [f"___stack{i}" for i in range(nstack)]
  354. args.extend(v for v in argnames if v not in args)
  355. freevars = tuple(code_options["co_cellvars"] or []) + tuple(
  356. code_options["co_freevars"] or []
  357. )
  358. freevars = tuple(sorted(freevars))
  359. code_options[
  360. "co_name"
  361. ] = f"{TORCH_DYNAMO_RESUME_IN_PREFIX}_{code_options['co_name']}_at_{lineno}"
  362. if is_py311_plus:
  363. qualified_path = code_options["co_qualname"].rsplit(".", maxsplit=1)
  364. if len(qualified_path) == 1:
  365. code_options["co_qualname"] = code_options["co_name"]
  366. else:
  367. assert len(qualified_path) == 2
  368. module_name, co_name = qualified_path
  369. code_options[
  370. "co_qualname"
  371. ] = f"{module_name}.{TORCH_DYNAMO_RESUME_IN_PREFIX}_{co_name}_at_{lineno}"
  372. code_options["co_firstlineno"] = lineno
  373. code_options["co_cellvars"] = tuple()
  374. code_options["co_freevars"] = freevars
  375. code_options["co_argcount"] = len(args)
  376. code_options["co_posonlyargcount"] = 0
  377. code_options["co_kwonlyargcount"] = 0
  378. code_options["co_varnames"] = tuple(
  379. args
  380. + [v for v in argnames_null if v not in args]
  381. + [v for v in code_options["co_varnames"] if v not in args]
  382. )
  383. code_options["co_flags"] = code_options["co_flags"] & ~(
  384. CO_VARARGS | CO_VARKEYWORDS
  385. )
  386. target = next(i for i in instructions if i.offset == offset)
  387. prefix = []
  388. if is_py311_plus:
  389. if freevars:
  390. prefix.append(
  391. create_instruction("COPY_FREE_VARS", arg=len(freevars))
  392. )
  393. prefix.append(create_instruction("RESUME", arg=0))
  394. cleanup: List[Instruction] = []
  395. hooks = {fn.stack_index: fn for fn in setup_fns}
  396. hook_target_offsets = {
  397. fn.stack_index: setup_fn_target_offsets[i]
  398. for i, fn in enumerate(setup_fns)
  399. }
  400. offset_to_inst = {inst.offset: inst for inst in instructions}
  401. # map old hook targets to new targets generated by the hook
  402. old_hook_target_remap = {}
  403. null_idxes_i = 0
  404. stack_ctx_vars_d = dict(stack_ctx_vars) # type: ignore[var-annotated,arg-type]
  405. for i in range(nstack):
  406. while (
  407. null_idxes_i < len(null_idxes)
  408. and null_idxes[null_idxes_i] == i + null_idxes_i
  409. ):
  410. prefix.append(create_instruction("PUSH_NULL"))
  411. null_idxes_i += 1
  412. prefix.append(create_instruction("LOAD_FAST", argval=f"___stack{i}"))
  413. if i in hooks:
  414. hook = hooks.pop(i)
  415. hook_insts, exn_target = hook(code_options, cleanup)
  416. prefix.extend(hook_insts)
  417. if is_py311_plus:
  418. hook_target_offset = hook_target_offsets.pop(i)
  419. old_hook_target = offset_to_inst[hook_target_offset]
  420. meta.prefix_block_target_offset_remap.append(hook_target_offset)
  421. old_hook_target_remap[old_hook_target] = exn_target
  422. real_i = i + null_idxes_i
  423. if real_i in stack_ctx_vars_d:
  424. # NOTE: we assume that current stack var is a context manager CLASS!
  425. # Load args for context variable and construct it
  426. prefix.extend(_load_tuple_and_call(stack_ctx_vars_d[real_i]))
  427. if is_py311_plus:
  428. # reverse the mapping since targets of later/nested contexts are inserted
  429. # into the mapping later, but show up earlier in the prefix.
  430. meta.prefix_block_target_offset_remap = list(
  431. reversed(meta.prefix_block_target_offset_remap)
  432. )
  433. assert not hooks
  434. # NOTE: we assume that local var is a context manager CLASS!
  435. # initialize inactive context vars in argnames
  436. for name, vals in argnames_ctx_vars:
  437. prefix.append(create_instruction("LOAD_FAST", argval=name))
  438. prefix.extend(_load_tuple_and_call(vals))
  439. prefix.append(create_instruction("STORE_FAST", argval=name))
  440. # 3.12+: store NULL into variables that were NULL
  441. if argnames_null:
  442. assert sys.version_info >= (3, 12)
  443. for v in argnames_null:
  444. assert v not in args
  445. prefix.extend(
  446. [
  447. create_instruction("PUSH_NULL"),
  448. create_instruction("STORE_FAST", argval=v),
  449. ]
  450. )
  451. prefix.append(create_jump_absolute(target))
  452. # because the line number table monotonically increases from co_firstlineno
  453. # remove starts_line for any instructions before the graph break instruction
  454. # this will ensure the instructions after the break have the correct line numbers
  455. for inst in instructions:
  456. if inst.offset == target.offset:
  457. break
  458. inst.starts_line = None
  459. if sys.version_info >= (3, 11):
  460. inst.positions = None
  461. if cleanup:
  462. prefix.extend(cleanup)
  463. prefix.extend(cls.unreachable_codes(code_options))
  464. # remap original instructions' exception table entries
  465. if old_hook_target_remap:
  466. assert is_py311_plus
  467. for inst in instructions:
  468. if (
  469. inst.exn_tab_entry
  470. and inst.exn_tab_entry.target in old_hook_target_remap
  471. ):
  472. inst.exn_tab_entry.target = old_hook_target_remap[
  473. inst.exn_tab_entry.target
  474. ]
  475. # TODO(jansel): add dead code elimination here
  476. instructions[:] = prefix + instructions
  477. new_code = transform_code_object(code, update)
  478. ContinueExecutionCache.generated_code_metadata[new_code] = meta
  479. return new_code
  480. @staticmethod
  481. def unreachable_codes(code_options) -> List[Instruction]:
  482. """Codegen a `raise None` to make analysis work for unreachable code"""
  483. return [
  484. create_instruction("LOAD_CONST", argval=None),
  485. create_instruction("RAISE_VARARGS", arg=1),
  486. ]
  487. @classmethod
  488. def generate_based_on_original_code_object(
  489. cls, code, lineno, offset: int, setup_fn_target_offsets: Tuple[int, ...], *args
  490. ):
  491. """
  492. This handles the case of generating a resume into code generated
  493. to resume something else. We want to always generate starting
  494. from the original code object so that if control flow paths
  495. converge we only generated 1 resume function (rather than 2^n
  496. resume functions).
  497. """
  498. meta: ResumeFunctionMetadata = ContinueExecutionCache.generated_code_metadata[
  499. code
  500. ]
  501. new_offset = None
  502. def find_new_offset(
  503. instructions: List[Instruction], code_options: Dict[str, Any]
  504. ):
  505. nonlocal new_offset
  506. (target,) = (i for i in instructions if i.offset == offset)
  507. # match the functions starting at the last instruction as we have added a prefix
  508. (new_target,) = (
  509. i2
  510. for i1, i2 in zip(reversed(instructions), reversed(meta.instructions))
  511. if i1 is target
  512. )
  513. assert target.opcode == new_target.opcode
  514. new_offset = new_target.offset
  515. transform_code_object(code, find_new_offset)
  516. if sys.version_info >= (3, 11):
  517. # setup_fn_target_offsets currently contains the target offset of
  518. # each setup_fn, based on `code`. When we codegen the resume function
  519. # based on the original code object, `meta.code`, the offsets in
  520. # setup_fn_target_offsets must be based on `meta.code` instead.
  521. if not meta.block_target_offset_remap:
  522. block_target_offset_remap = meta.block_target_offset_remap = {}
  523. def remap_block_offsets(
  524. instructions: List[Instruction], code_options: Dict[str, Any]
  525. ):
  526. # NOTE: each prefix block generates exactly one PUSH_EXC_INFO,
  527. # so we can tell which block a prefix PUSH_EXC_INFO belongs to,
  528. # by counting. Then we can use meta.prefix_block-target_offset_remap
  529. # to determine where in the original code the PUSH_EXC_INFO offset
  530. # replaced.
  531. prefix_blocks: List[Instruction] = []
  532. for inst in instructions:
  533. if len(prefix_blocks) == len(
  534. meta.prefix_block_target_offset_remap
  535. ):
  536. break
  537. if inst.opname == "PUSH_EXC_INFO":
  538. prefix_blocks.append(inst)
  539. # offsets into prefix
  540. for inst, o in zip(
  541. prefix_blocks, meta.prefix_block_target_offset_remap
  542. ):
  543. block_target_offset_remap[cast(int, inst.offset)] = o
  544. # old bytecode targets are after the prefix PUSH_EXC_INFO's
  545. old_start_offset = (
  546. cast(int, prefix_blocks[-1].offset) if prefix_blocks else -1
  547. )
  548. # offsets into old bytecode
  549. old_inst_offsets = sorted(
  550. n for n in setup_fn_target_offsets if n > old_start_offset
  551. )
  552. targets = _filter_iter(
  553. instructions, old_inst_offsets, lambda inst, o: inst.offset == o
  554. )
  555. new_targets = _filter_iter(
  556. zip(reversed(instructions), reversed(meta.instructions)),
  557. targets,
  558. lambda v1, v2: v1[0] is v2,
  559. )
  560. for new, old in zip(new_targets, targets):
  561. block_target_offset_remap[old.offset] = new[1].offset
  562. transform_code_object(code, remap_block_offsets)
  563. # if offset is not in setup_fn_target_offsets, it is an error
  564. setup_fn_target_offsets = tuple(
  565. meta.block_target_offset_remap[n] for n in setup_fn_target_offsets
  566. )
  567. return ContinueExecutionCache.lookup(
  568. meta.code, lineno, new_offset, setup_fn_target_offsets, *args
  569. )
  570. """
  571. # partially finished support for with statements
  572. def convert_locals_to_cells(
  573. instructions: List[Instruction],
  574. code_options: Dict[str, Any]):
  575. code_options["co_cellvars"] = tuple(
  576. var
  577. for var in code_options["co_varnames"]
  578. if var not in code_options["co_freevars"]
  579. and not var.startswith("___stack")
  580. )
  581. cell_and_free = code_options["co_cellvars"] + code_options["co_freevars"]
  582. for inst in instructions:
  583. if str(inst.argval).startswith("___stack"):
  584. continue
  585. elif inst.opname == "LOAD_FAST":
  586. inst.opname = "LOAD_DEREF"
  587. elif inst.opname == "STORE_FAST":
  588. inst.opname = "STORE_DEREF"
  589. elif inst.opname == "DELETE_FAST":
  590. inst.opname = "DELETE_DEREF"
  591. else:
  592. continue
  593. inst.opcode = dis.opmap[inst.opname]
  594. assert inst.argval in cell_and_free, inst.argval
  595. inst.arg = cell_and_free.index(inst.argval)
  596. def patch_setup_with(
  597. instructions: List[Instruction],
  598. code_options: Dict[str, Any]
  599. ):
  600. nonlocal need_skip
  601. need_skip = True
  602. target_index = next(
  603. idx for idx, i in enumerate(instructions) if i.offset == offset
  604. )
  605. assert instructions[target_index].opname == "SETUP_WITH"
  606. convert_locals_to_cells(instructions, code_options)
  607. stack_depth_before = nstack + stack_effect(instructions[target_index].opcode,
  608. instructions[target_index].arg)
  609. inside_with = []
  610. inside_with_resume_at = None
  611. stack_depth = stack_depth_before
  612. idx = target_index + 1
  613. for idx in range(idx, len(instructions)):
  614. inst = instructions[idx]
  615. if inst.opname == "BEGIN_FINALLY":
  616. inside_with_resume_at = inst
  617. break
  618. elif inst.target is not None:
  619. unimplemented("jump from with not supported")
  620. elif inst.opname in ("BEGIN_FINALLY", "WITH_CLEANUP_START", "WITH_CLEANUP_FINISH", "END_FINALLY",
  621. "POP_FINALLY", "POP_EXCEPT",
  622. "POP_BLOCK", "END_ASYNC_FOR"):
  623. unimplemented("block ops not supported")
  624. inside_with.append(inst)
  625. stack_depth += stack_effect(inst.opcode, inst.arg)
  626. assert inside_with_resume_at
  627. instructions = [
  628. create_instruction("LOAD_FAST", f"___stack{i}") for i in range(nstack)
  629. ] + [
  630. create_instruction("SETUP_WITH", target=instructions[target_index].target)
  631. ... call the function ...
  632. unpack_tuple
  633. ] + [
  634. create_instruction("JUMP_ABSOLUTE", target=inside_with_resume_at)
  635. ]
  636. """