@@ -181,6 +181,24 @@ def assemble(self) -> bytes:
181181
182182 return bytes (b )
183183
184+ @classmethod
185+ def _from_opcode (
186+ cls : Type [T ],
187+ name : str ,
188+ opcode : int ,
189+ arg : int ,
190+ location : Optional [InstrLocation ],
191+ ) -> T :
192+ """Fast path for from_code: arg is a raw byte (0-255), size is always 2."""
193+ new = object .__new__ (cls )
194+ new ._name = name
195+ new ._opcode = opcode
196+ new ._arg = arg
197+ new ._location = location
198+ new ._extended_args = None
199+ new ._size = 2
200+ return new
201+
184202 @classmethod
185203 def disassemble (cls : Type [T ], lineno : Optional [int ], code : bytes , offset : int ) -> T :
186204 index = 2 * offset
@@ -336,21 +354,24 @@ def from_code(
336354 code : types .CodeType , * , extended_arg : bool = False
337355 ) -> ConcreteBytecode :
338356 instructions : MutableSequence [Union [SetLineno , ConcreteInstr ]] = []
339- for i in dis .get_instructions (code , show_caches = True ):
340- loc = InstrLocation .from_positions (i .positions ) if i .positions else None
341- # dis.get_instructions automatically handle extended arg which
342- # we do not want, so we fold back arguments to be between 0 and 255
343- instructions .append (
344- ConcreteInstr (
345- i .opname ,
346- i .arg % 256 if i .arg is not None else UNSET ,
347- location = loc ,
348- )
357+ bc = code .co_code
358+ opname = _opcode .opname
359+ # co_positions() yields one (lineno, end_lineno, col_offset,
360+ # end_col_offset) per instruction word (including CACHE entries),
361+ # available from Python 3.11+. CACHE entries are already inline in
362+ # co_code on all supported versions, so iterating co_code directly
363+ # handles all versions without dis overhead.
364+ pos_iter : Iterator [
365+ Tuple [Optional [int ], Optional [int ], Optional [int ], Optional [int ]]
366+ ] = iter (code .co_positions ())
367+ for offset in range (0 , len (bc ), 2 ):
368+ op = bc [offset ]
369+ arg = bc [offset + 1 ] if opcode_has_argument (op ) else UNSET
370+ pos = next (pos_iter , None )
371+ loc : Optional [InstrLocation ] = (
372+ InstrLocation ._from_tuple (* pos ) if pos is not None else None
349373 )
350- # cache_info only exist on 3.13+
351- for _ , size , _ in (i .cache_info or ()) if PY313 else (): # type: ignore
352- for _ in range (size ):
353- instructions .append (ConcreteInstr ("CACHE" , 0 , location = loc ))
374+ instructions .append (ConcreteInstr ._from_opcode (opname [op ], op , arg , loc ))
354375
355376 bytecode = ConcreteBytecode ()
356377
@@ -575,7 +596,9 @@ def _assemble_locations(
575596
576597 _ , size , lineno , old_location = next (iter_in )
577598 # Infer the line if location is None
578- old_location = old_location or InstrLocation (lineno , None , None , None )
599+ old_location = old_location or InstrLocation ._from_tuple (
600+ lineno , None , None , None
601+ )
579602 lineno = first_lineno
580603
581604 # We track the last set lineno to be able to compute deltas
@@ -930,14 +953,18 @@ def to_bytecode(
930953 else :
931954 arg = c_arg
932955
933- location = c_instr .location or InstrLocation (lineno , None , None , None )
956+ location = c_instr .location or InstrLocation ._from_tuple (
957+ lineno , None , None , None
958+ )
934959
935960 if jump_target is not None :
936961 arg = PLACEHOLDER_LABEL
937962 instr_index = len (instructions )
938963 jumps .append ((instr_index , jump_target ))
939964
940- instructions .append (Instr (c_instr .name , arg , location = location ))
965+ instructions .append (
966+ Instr ._from_trusted (c_instr ._name , c_instr ._opcode , arg , location )
967+ )
941968
942969 # We now insert the TryEnd entries
943970 if current_instr_offset in ex_end :
@@ -1030,7 +1057,9 @@ def add(names: list[str], name: str) -> int:
10301057 return index
10311058
10321059 def concrete_instructions (self ) -> None :
1033- location = InstrLocation (self .bytecode .first_lineno , None , None , None )
1060+ location = InstrLocation ._from_tuple (
1061+ self .bytecode .first_lineno , None , None , None
1062+ )
10341063 # Track instruction (index) using cell vars and free vars to be able to update
10351064 # the index used once all the names are known.
10361065 cell_instrs : list [int ] = []
@@ -1086,7 +1115,7 @@ def concrete_instructions(self) -> None:
10861115 continue
10871116
10881117 if isinstance (instr , SetLineno ):
1089- location = InstrLocation (instr .lineno , None , None , None )
1118+ location = InstrLocation . _from_tuple (instr .lineno , None , None , None )
10901119 continue
10911120
10921121 if isinstance (instr , TryBegin ):
0 commit comments