diff options
Diffstat (limited to 'Tools/cases_generator/generate_cases.py')
-rw-r--r-- | Tools/cases_generator/generate_cases.py | 130 |
1 files changed, 92 insertions, 38 deletions
diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index 9d894d2..1d703a0 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -26,7 +26,9 @@ DEFAULT_METADATA_OUTPUT = os.path.relpath( ) BEGIN_MARKER = "// BEGIN BYTECODES //" END_MARKER = "// END BYTECODES //" -RE_PREDICTED = r"^\s*(?:PREDICT\(|GO_TO_INSTRUCTION\(|DEOPT_IF\(.*?,\s*)(\w+)\);\s*(?://.*)?$" +RE_PREDICTED = ( + r"^\s*(?:PREDICT\(|GO_TO_INSTRUCTION\(|DEOPT_IF\(.*?,\s*)(\w+)\);\s*(?://.*)?$" +) UNUSED = "unused" BITS_PER_CODE_UNIT = 16 @@ -59,7 +61,10 @@ def effect_size(effect: StackEffect) -> tuple[int, str]: At most one of these will be non-zero / non-empty. """ if effect.size: + assert not effect.cond, "Array effects cannot have a condition" return 0, effect.size + elif effect.cond: + return 0, f"{maybe_parenthesize(effect.cond)} ? 1 : 0" else: return 1, "" @@ -132,7 +137,12 @@ class Formatter: yield self.emit("}") - def stack_adjust(self, diff: int, input_effects: list[StackEffect], output_effects: list[StackEffect]): + def stack_adjust( + self, + diff: int, + input_effects: list[StackEffect], + output_effects: list[StackEffect], + ): # TODO: Get rid of 'diff' parameter shrink, isym = list_effect_size(input_effects) grow, osym = list_effect_size(output_effects) @@ -150,10 +160,13 @@ class Formatter: if dst.name == UNUSED: return typ = f"{dst.type}" if dst.type else "PyObject *" - init = "" if src: cast = self.cast(dst, src) init = f" = {cast}{src.name}" + elif dst.cond: + init = " = NULL" + else: + init = "" sepa = "" if typ.endswith("*") else " " self.emit(f"{typ}{sepa}{dst.name}{init};") @@ -162,7 +175,10 @@ class Formatter: return cast = self.cast(dst, src) if m := re.match(r"^PEEK\((.*)\)$", dst.name): - self.emit(f"POKE({m.group(1)}, {cast}{src.name});") + stmt = f"POKE({m.group(1)}, {cast}{src.name});" + if src.cond: + stmt = f"if ({src.cond}) {{ {stmt} }}" + self.emit(stmt) elif m := re.match(r"^&PEEK\(.*\)$", dst.name): # NOTE: MOVE_ITEMS() does not actually exist. # The only supported output array forms are: @@ -234,7 +250,7 @@ class Instruction: if self.register: num_regs = len(self.input_effects) + len(self.output_effects) num_dummies = (num_regs // 2) * 2 + 1 - num_regs - fmt = "I" + "B"*num_regs + "X"*num_dummies + fmt = "I" + "B" * num_regs + "X" * num_dummies else: if variable_used(inst, "oparg"): fmt = "IB" @@ -276,9 +292,13 @@ class Instruction: # Write input stack effect variable declarations and initializations ieffects = list(reversed(self.input_effects)) for i, ieffect in enumerate(ieffects): - isize = string_effect_size(list_effect_size(ieffects[:i+1])) + isize = string_effect_size( + list_effect_size([ieff for ieff in ieffects[: i + 1]]) + ) if ieffect.size: src = StackEffect(f"&PEEK({isize})", "PyObject **") + elif ieffect.cond: + src = StackEffect(f"({ieffect.cond}) ? PEEK({isize}) : NULL", "") else: src = StackEffect(f"PEEK({isize})", "") out.declare(ieffect, src) @@ -304,14 +324,20 @@ class Instruction: if not self.register: # Write net stack growth/shrinkage - out.stack_adjust(0, self.input_effects, self.output_effects) + out.stack_adjust( + 0, + [ieff for ieff in self.input_effects], + [oeff for oeff in self.output_effects], + ) # Write output stack effect assignments oeffects = list(reversed(self.output_effects)) for i, oeffect in enumerate(oeffects): if oeffect.name in self.unmoved_names: continue - osize = string_effect_size(list_effect_size(oeffects[:i+1])) + osize = string_effect_size( + list_effect_size([oeff for oeff in oeffects[: i + 1]]) + ) if oeffect.size: dst = StackEffect(f"&PEEK({osize})", "PyObject **") else: @@ -438,6 +464,7 @@ class MacroInstruction(SuperOrMacroInstruction): parts: list[Component | parser.CacheEffect] +AnyInstruction = Instruction | SuperInstruction | MacroInstruction INSTR_FMT_PREFIX = "INSTR_FMT_" @@ -506,6 +533,7 @@ class Analyzer: self.supers = {} self.macros = {} self.families = {} + thing: parser.InstDef | parser.Super | parser.Macro | parser.Family | None while thing := psr.definition(): match thing: case parser.InstDef(name=name): @@ -631,7 +659,9 @@ class Analyzer: parts.append(part) format += instr.instr_fmt final_sp = sp - return SuperInstruction(super.name, stack, initial_sp, final_sp, format, super, parts) + return SuperInstruction( + super.name, stack, initial_sp, final_sp, format, super, parts + ) def analyze_macro(self, macro: parser.Macro) -> MacroInstruction: components = self.check_macro_components(macro) @@ -657,7 +687,9 @@ class Analyzer: case _: typing.assert_never(component) final_sp = sp - return MacroInstruction(macro.name, stack, initial_sp, final_sp, format, macro, parts) + return MacroInstruction( + macro.name, stack, initial_sp, final_sp, format, macro, parts + ) def analyze_instruction( self, instr: Instruction, stack: list[StackEffect], sp: int @@ -710,7 +742,9 @@ class Analyzer: for thing in components: match thing: case Instruction() as instr: - if any(eff.size for eff in instr.input_effects + instr.output_effects): + if any( + eff.size for eff in instr.input_effects + instr.output_effects + ): # TODO: Eventually this will be needed, at least for macros. self.error( f"Instruction {instr.name!r} has variable-sized stack effect, " @@ -736,16 +770,16 @@ class Analyzer: def get_stack_effect_info( self, thing: parser.InstDef | parser.Super | parser.Macro - ) -> tuple[Instruction|None, str, str]: - - def effect_str(effect: list[StackEffect]) -> str: - if getattr(thing, 'kind', None) == 'legacy': + ) -> tuple[AnyInstruction | None, str, str]: + def effect_str(effects: list[StackEffect]) -> str: + if getattr(thing, "kind", None) == "legacy": return str(-1) - n_effect, sym_effect = list_effect_size(effect) + n_effect, sym_effect = list_effect_size(effects) if sym_effect: return f"{sym_effect} + {n_effect}" if n_effect else sym_effect return str(n_effect) + instr: AnyInstruction | None match thing: case parser.InstDef(): if thing.kind != "op": @@ -754,34 +788,43 @@ class Analyzer: pushed = effect_str(instr.output_effects) else: instr = None - popped = pushed = "", "" + popped = "" + pushed = "" case parser.Super(): instr = self.super_instrs[thing.name] - popped = '+'.join(effect_str(comp.instr.input_effects) for comp in instr.parts) - pushed = '+'.join(effect_str(comp.instr.output_effects) for comp in instr.parts) + popped = "+".join( + effect_str(comp.instr.input_effects) for comp in instr.parts + ) + pushed = "+".join( + effect_str(comp.instr.output_effects) for comp in instr.parts + ) case parser.Macro(): instr = self.macro_instrs[thing.name] parts = [comp for comp in instr.parts if isinstance(comp, Component)] - popped = '+'.join(effect_str(comp.instr.input_effects) for comp in parts) - pushed = '+'.join(effect_str(comp.instr.output_effects) for comp in parts) + popped = "+".join( + effect_str(comp.instr.input_effects) for comp in parts + ) + pushed = "+".join( + effect_str(comp.instr.output_effects) for comp in parts + ) case _: typing.assert_never(thing) return instr, popped, pushed def write_stack_effect_functions(self) -> None: - popped_data = [] - pushed_data = [] + popped_data: list[tuple[AnyInstruction, str]] = [] + pushed_data: list[tuple[AnyInstruction, str]] = [] for thing in self.everything: instr, popped, pushed = self.get_stack_effect_info(thing) if instr is not None: - popped_data.append( (instr, popped) ) - pushed_data.append( (instr, pushed) ) + popped_data.append((instr, popped)) + pushed_data.append((instr, pushed)) - def write_function(direction: str, data: list[tuple[Instruction, str]]) -> None: - self.out.emit("\n#ifndef NDEBUG"); - self.out.emit("static int"); + def write_function(direction: str, data: list[tuple[AnyInstruction, str]]) -> None: + self.out.emit("\n#ifndef NDEBUG") + self.out.emit("static int") self.out.emit(f"_PyOpcode_num_{direction}(int opcode, int oparg) {{") - self.out.emit(" switch(opcode) {"); + self.out.emit(" switch(opcode) {") for instr, effect in data: self.out.emit(f" case {instr.name}:") self.out.emit(f" return {effect};") @@ -789,10 +832,10 @@ class Analyzer: self.out.emit(" Py_UNREACHABLE();") self.out.emit(" }") self.out.emit("}") - self.out.emit("#endif"); + self.out.emit("#endif") - write_function('popped', popped_data) - write_function('pushed', pushed_data) + write_function("popped", popped_data) + write_function("pushed", pushed_data) def write_metadata(self) -> None: """Write instruction metadata to output file.""" @@ -865,21 +908,21 @@ class Analyzer: directions.extend("DIR_NONE" for _ in range(3)) dir_op1, dir_op2, dir_op3 = directions[:3] self.out.emit( - f' [{instr.name}] = {{ {dir_op1}, {dir_op2}, {dir_op3}, true, {INSTR_FMT_PREFIX}{instr.instr_fmt} }},' + f" [{instr.name}] = {{ {dir_op1}, {dir_op2}, {dir_op3}, true, {INSTR_FMT_PREFIX}{instr.instr_fmt} }}," ) def write_metadata_for_super(self, sup: SuperInstruction) -> None: """Write metadata for a super-instruction.""" dir_op1 = dir_op2 = dir_op3 = "DIR_NONE" self.out.emit( - f' [{sup.name}] = {{ {dir_op1}, {dir_op2}, {dir_op3}, true, {INSTR_FMT_PREFIX}{sup.instr_fmt} }},' + f" [{sup.name}] = {{ {dir_op1}, {dir_op2}, {dir_op3}, true, {INSTR_FMT_PREFIX}{sup.instr_fmt} }}," ) def write_metadata_for_macro(self, mac: MacroInstruction) -> None: """Write metadata for a macro-instruction.""" dir_op1 = dir_op2 = dir_op3 = "DIR_NONE" self.out.emit( - f' [{mac.name}] = {{ {dir_op1}, {dir_op2}, {dir_op3}, true, {INSTR_FMT_PREFIX}{mac.instr_fmt} }},' + f" [{mac.name}] = {{ {dir_op1}, {dir_op2}, {dir_op3}, true, {INSTR_FMT_PREFIX}{mac.instr_fmt} }}," ) def write_instructions(self) -> None: @@ -1012,7 +1055,9 @@ def extract_block_text(block: parser.Block) -> tuple[list[str], list[str]]: # Separate PREDICT(...) macros from end predictions: list[str] = [] - while blocklines and (m := re.match(r"^\s*PREDICT\((\w+)\);\s*(?://.*)?$", blocklines[-1])): + while blocklines and ( + m := re.match(r"^\s*PREDICT\((\w+)\);\s*(?://.*)?$", blocklines[-1]) + ): predictions.insert(0, m.group(1)) blocklines.pop() @@ -1029,13 +1074,22 @@ def always_exits(lines: list[str]) -> bool: return False line = line[12:] return line.startswith( - ("goto ", "return ", "DISPATCH", "GO_TO_", "Py_UNREACHABLE()", "ERROR_IF(true, ") + ( + "goto ", + "return ", + "DISPATCH", + "GO_TO_", + "Py_UNREACHABLE()", + "ERROR_IF(true, ", + ) ) def variable_used(node: parser.Node, name: str) -> bool: """Determine whether a variable with a given name is used in a node.""" - return any(token.kind == "IDENTIFIER" and token.text == name for token in node.tokens) + return any( + token.kind == "IDENTIFIER" and token.text == name for token in node.tokens + ) def main(): |