"""Generate the main interpreter switch. Reads the instruction definitions from bytecodes.c. Writes the cases to generated_cases.c.h, which is #included in ceval.c. """ import argparse import contextlib import itertools import os import posixpath import sys import textwrap import typing from collections.abc import Iterator import stacking # Early import to avoid circular import from _typing_backports import assert_never from analysis import Analyzer from formatting import Formatter, list_effect_size from flags import InstructionFlags, variable_used from instructions import ( AnyInstruction, AbstractInstruction, Component, Instruction, MacroInstruction, MacroParts, PseudoInstruction, OverriddenInstructionPlaceHolder, TIER_ONE, TIER_TWO, ) import parsing from parsing import StackEffect HERE = os.path.dirname(__file__) ROOT = os.path.join(HERE, "../..") THIS = os.path.relpath(__file__, ROOT).replace(os.path.sep, posixpath.sep) DEFAULT_INPUT = os.path.relpath(os.path.join(ROOT, "Python/bytecodes.c")) DEFAULT_OUTPUT = os.path.relpath(os.path.join(ROOT, "Python/generated_cases.c.h")) DEFAULT_OPCODE_IDS_H_OUTPUT = os.path.relpath( os.path.join(ROOT, "Include/opcode_ids.h") ) DEFAULT_OPCODE_TARGETS_H_OUTPUT = os.path.relpath( os.path.join(ROOT, "Python/opcode_targets.h") ) DEFAULT_METADATA_OUTPUT = os.path.relpath( os.path.join(ROOT, "Include/internal/pycore_opcode_metadata.h") ) DEFAULT_PYMETADATA_OUTPUT = os.path.relpath( os.path.join(ROOT, "Lib/_opcode_metadata.py") ) DEFAULT_EXECUTOR_OUTPUT = os.path.relpath( os.path.join(ROOT, "Python/executor_cases.c.h") ) DEFAULT_ABSTRACT_INTERPRETER_OUTPUT = os.path.relpath( os.path.join(ROOT, "Python/abstract_interp_cases.c.h") ) # Constants used instead of size for macro expansions. # Note: 1, 2, 4 must match actual cache entry sizes. OPARG_SIZES = { "OPARG_FULL": 0, "OPARG_CACHE_1": 1, "OPARG_CACHE_2": 2, "OPARG_CACHE_4": 4, "OPARG_TOP": 5, "OPARG_BOTTOM": 6, "OPARG_SAVE_IP": 7, } INSTR_FMT_PREFIX = "INSTR_FMT_" # TODO: generate all these after updating the DSL SPECIALLY_HANDLED_ABSTRACT_INSTR = { "LOAD_FAST", "LOAD_FAST_CHECK", "LOAD_FAST_AND_CLEAR", "LOAD_CONST", "STORE_FAST", "STORE_FAST_MAYBE_NULL", "COPY", # Arithmetic "_BINARY_OP_MULTIPLY_INT", "_BINARY_OP_ADD_INT", "_BINARY_OP_SUBTRACT_INT", } arg_parser = argparse.ArgumentParser( description="Generate the code for the interpreter switch.", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) arg_parser.add_argument( "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT ) arg_parser.add_argument( "-n", "--opcode_ids_h", type=str, help="Header file with opcode number definitions", default=DEFAULT_OPCODE_IDS_H_OUTPUT, ) arg_parser.add_argument( "-t", "--opcode_targets_h", type=str, help="File with opcode targets for computed gotos", default=DEFAULT_OPCODE_TARGETS_H_OUTPUT, ) arg_parser.add_argument( "-m", "--metadata", type=str, help="Generated C metadata", default=DEFAULT_METADATA_OUTPUT, ) arg_parser.add_argument( "-p", "--pymetadata", type=str, help="Generated Python metadata", default=DEFAULT_PYMETADATA_OUTPUT, ) arg_parser.add_argument( "-l", "--emit-line-directives", help="Emit #line directives", action="store_true" ) arg_parser.add_argument( "input", nargs=argparse.REMAINDER, help="Instruction definition file(s)" ) arg_parser.add_argument( "-e", "--executor-cases", type=str, help="Write executor cases to this file", default=DEFAULT_EXECUTOR_OUTPUT, ) arg_parser.add_argument( "-a", "--abstract-interpreter-cases", type=str, help="Write abstract interpreter cases to this file", default=DEFAULT_ABSTRACT_INTERPRETER_OUTPUT, ) class Generator(Analyzer): def get_stack_effect_info( self, thing: parsing.InstDef | parsing.Macro | parsing.Pseudo ) -> tuple[AnyInstruction | None, str, str]: def effect_str(effects: list[StackEffect]) -> str: 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 popped: str | None = None pushed: str | None = None match thing: case parsing.InstDef(): if thing.kind != "op" or self.instrs[thing.name].is_viable_uop(): instr = self.instrs[thing.name] popped = effect_str(instr.input_effects) pushed = effect_str(instr.output_effects) else: instr = None popped = "" pushed = "" case parsing.Macro(): instr = self.macro_instrs[thing.name] popped, pushed = stacking.get_stack_effect_info_for_macro(instr) case parsing.Pseudo(): instr = self.pseudo_instrs[thing.name] # Calculate stack effect, and check that it's the the same # for all targets. for target in self.pseudos[thing.name].targets: target_instr = self.instrs.get(target) # Currently target is always an instr. This could change # in the future, e.g., if we have a pseudo targetting a # macro instruction. assert target_instr target_popped = effect_str(target_instr.input_effects) target_pushed = effect_str(target_instr.output_effects) if popped is None: popped, pushed = target_popped, target_pushed else: assert popped == target_popped assert pushed == target_pushed case _: assert_never(thing) assert popped is not None and pushed is not None return instr, popped, pushed @contextlib.contextmanager def metadata_item(self, signature: str, open: str, close: str) -> Iterator[None]: self.out.emit("") self.out.emit(f"extern {signature};") self.out.emit("#ifdef NEED_OPCODE_METADATA") with self.out.block(f"{signature} {open}", close): yield self.out.emit("#endif // NEED_OPCODE_METADATA") def write_stack_effect_functions(self) -> None: popped_data: list[tuple[AnyInstruction, str]] = [] pushed_data: list[tuple[AnyInstruction, str]] = [] for thing in self.everything: if isinstance(thing, OverriddenInstructionPlaceHolder): continue instr, popped, pushed = self.get_stack_effect_info(thing) if instr is not None: popped_data.append((instr, popped)) pushed_data.append((instr, pushed)) def write_function( direction: str, data: list[tuple[AnyInstruction, str]] ) -> None: with self.metadata_item( f"int _PyOpcode_num_{direction}(int opcode, int oparg, bool jump)", "", "", ): with self.out.block("switch(opcode)"): for instr, effect in data: self.out.emit(f"case {instr.name}:") self.out.emit(f" return {effect};") self.out.emit("default:") self.out.emit(" return -1;") write_function("popped", popped_data) write_function("pushed", pushed_data) self.out.emit("") def from_source_files(self) -> str: filenames = [] for filename in self.input_filenames: try: filename = os.path.relpath(filename, ROOT) except ValueError: # May happen on Windows if root and temp on different volumes pass filenames.append(filename.replace(os.path.sep, posixpath.sep)) paths = f"\n{self.out.comment} ".join(filenames) return f"{self.out.comment} from:\n{self.out.comment} {paths}\n" def write_provenance_header(self) -> None: self.out.write_raw(f"{self.out.comment} This file is generated by {THIS}\n") self.out.write_raw(self.from_source_files()) self.out.write_raw(f"{self.out.comment} Do not edit!\n") def assign_opcode_ids(self) -> None: """Assign IDs to opcodes""" ops: list[tuple[bool, str]] = [] # (has_arg, name) for each opcode instrumented_ops: list[str] = [] for instr in itertools.chain( [instr for instr in self.instrs.values() if instr.kind != "op"], self.macro_instrs.values(), ): assert isinstance(instr, (Instruction, MacroInstruction, PseudoInstruction)) name = instr.name if name.startswith("INSTRUMENTED_"): instrumented_ops.append(name) else: ops.append((instr.instr_flags.HAS_ARG_FLAG, name)) # Special case: this instruction is implemented in ceval.c # rather than bytecodes.c, so we need to add it explicitly # here (at least until we add something to bytecodes.c to # declare external instructions). instrumented_ops.append("INSTRUMENTED_LINE") # assert lists are unique assert len(set(ops)) == len(ops) assert len(set(instrumented_ops)) == len(instrumented_ops) opname: list[str | None] = [None] * 512 opmap: dict[str, int] = {} markers: dict[str, int] = {} def map_op(op: int, name: str) -> None: assert op < len(opname) assert opname[op] is None assert name not in opmap opname[op] = name opmap[name] = op # 0 is reserved for cache entries. This helps debugging. map_op(0, "CACHE") # 17 is reserved as it is the initial value for the specializing counter. # This helps catch cases where we attempt to execute a cache. map_op(17, "RESERVED") # 166 is RESUME - it is hard coded as such in Tools/build/deepfreeze.py map_op(166, "RESUME") next_opcode = 1 for has_arg, name in sorted(ops): if name in opmap: continue # an anchored name, like CACHE while opname[next_opcode] is not None: next_opcode += 1 assert next_opcode < 255 map_op(next_opcode, name) if has_arg and "HAVE_ARGUMENT" not in markers: markers["HAVE_ARGUMENT"] = next_opcode # Instrumented opcodes are at the end of the valid range min_instrumented = 254 - (len(instrumented_ops) - 1) assert next_opcode <= min_instrumented markers["MIN_INSTRUMENTED_OPCODE"] = min_instrumented for i, op in enumerate(instrumented_ops): map_op(min_instrumented + i, op) # Pseudo opcodes are after the valid range for i, op in enumerate(sorted(self.pseudos)): map_op(256 + i, op) assert 255 not in opmap.values() # 255 is reserved self.opmap = opmap self.markers = markers def write_opcode_ids( self, opcode_ids_h_filename: str, opcode_targets_filename: str ) -> None: """Write header file that defined the opcode IDs""" with open(opcode_ids_h_filename, "w") as f: # Create formatter self.out = Formatter(f, 0) self.write_provenance_header() self.out.emit("") self.out.emit("#ifndef Py_OPCODE_IDS_H") self.out.emit("#define Py_OPCODE_IDS_H") self.out.emit("#ifdef __cplusplus") self.out.emit('extern "C" {') self.out.emit("#endif") self.out.emit("") self.out.emit("/* Instruction opcodes for compiled code */") def define(name: str, opcode: int) -> None: self.out.emit(f"#define {name:<38} {opcode:>3}") all_pairs: list[tuple[int, int, str]] = [] # the second item in the tuple sorts the markers before the ops all_pairs.extend((i, 1, name) for (name, i) in self.markers.items()) all_pairs.extend((i, 2, name) for (name, i) in self.opmap.items()) for i, _, name in sorted(all_pairs): assert name is not None define(name, i) self.out.emit("") self.out.emit("#ifdef __cplusplus") self.out.emit("}") self.out.emit("#endif") self.out.emit("#endif /* !Py_OPCODE_IDS_H */") with open(opcode_targets_filename, "w") as f: # Create formatter self.out = Formatter(f, 0) with self.out.block("static void *opcode_targets[256] =", ";"): targets = ["_unknown_opcode"] * 256 for name, op in self.opmap.items(): if op < 256: targets[op] = f"TARGET_{name}" f.write(",\n".join([f" &&{s}" for s in targets])) def write_metadata(self, metadata_filename: str, pymetadata_filename: str) -> None: """Write instruction metadata to output file.""" # Compute the set of all instruction formats. all_formats: set[str] = set() for thing in self.everything: format: str | None = None match thing: case OverriddenInstructionPlaceHolder(): continue case parsing.InstDef(): format = self.instrs[thing.name].instr_fmt case parsing.Macro(): format = self.macro_instrs[thing.name].instr_fmt case parsing.Pseudo(): for target in self.pseudos[thing.name].targets: target_instr = self.instrs.get(target) assert target_instr if format is None: format = target_instr.instr_fmt else: assert format == target_instr.instr_fmt case _: assert_never(thing) assert format is not None all_formats.add(format) # Turn it into a sorted list of enum values. format_enums = [INSTR_FMT_PREFIX + format for format in sorted(all_formats)] with open(metadata_filename, "w") as f: # Create formatter self.out = Formatter(f, 0) self.write_provenance_header() self.out.emit("\n" + textwrap.dedent(""" #ifndef Py_BUILD_CORE # error "this header requires Py_BUILD_CORE define" #endif """).strip()) self.out.emit("\n#include // bool") self.write_pseudo_instrs() self.out.emit("") self.write_uop_items(lambda name, counter: f"#define {name} {counter}") self.write_stack_effect_functions() # Write the enum definition for instruction formats. with self.out.block("enum InstructionFormat", ";"): for enum in format_enums: self.out.emit(enum + ",") self.out.emit("") self.out.emit( "#define IS_VALID_OPCODE(OP) \\\n" " (((OP) >= 0) && ((OP) < OPCODE_METADATA_SIZE) && \\\n" " (_PyOpcode_opcode_metadata[(OP)].valid_entry))" ) self.out.emit("") InstructionFlags.emit_macros(self.out) self.out.emit("") with self.out.block("struct opcode_metadata", ";"): self.out.emit("bool valid_entry;") self.out.emit("enum InstructionFormat instr_format;") self.out.emit("int flags;") self.out.emit("") with self.out.block("struct opcode_macro_expansion", ";"): self.out.emit("int nuops;") self.out.emit( "struct { int16_t uop; int8_t size; int8_t offset; } uops[12];" ) self.out.emit("") for key, value in OPARG_SIZES.items(): self.out.emit(f"#define {key} {value}") self.out.emit("") self.out.emit( "#define OPCODE_METADATA_FMT(OP) " "(_PyOpcode_opcode_metadata[(OP)].instr_format)" ) self.out.emit("#define SAME_OPCODE_METADATA(OP1, OP2) \\") self.out.emit( " (OPCODE_METADATA_FMT(OP1) == OPCODE_METADATA_FMT(OP2))" ) self.out.emit("") # Write metadata array declaration self.out.emit("#define OPCODE_METADATA_SIZE 512") self.out.emit("#define OPCODE_UOP_NAME_SIZE 512") self.out.emit("#define OPCODE_MACRO_EXPANSION_SIZE 256") with self.metadata_item( "const struct opcode_metadata " "_PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE]", "=", ";", ): # Write metadata for each instruction for thing in self.everything: match thing: case OverriddenInstructionPlaceHolder(): continue case parsing.InstDef(): self.write_metadata_for_inst(self.instrs[thing.name]) case parsing.Macro(): self.write_metadata_for_macro(self.macro_instrs[thing.name]) case parsing.Pseudo(): self.write_metadata_for_pseudo( self.pseudo_instrs[thing.name] ) case _: assert_never(thing) with self.metadata_item( "const struct opcode_macro_expansion " "_PyOpcode_macro_expansion[OPCODE_MACRO_EXPANSION_SIZE]", "=", ";", ): # Write macro expansion for each non-pseudo instruction for thing in self.everything: match thing: case OverriddenInstructionPlaceHolder(): pass case parsing.InstDef(name=name): instr = self.instrs[name] # Since an 'op' is not a bytecode, it has no expansion; but 'inst' is if instr.kind == "inst" and instr.is_viable_uop(): # Construct a dummy Component -- input/output mappings are not used part = Component(instr, instr.active_caches) self.write_macro_expansions( instr.name, [part], instr.cache_offset ) elif instr.kind == "inst" and variable_used( instr.inst, "oparg1" ): assert variable_used( instr.inst, "oparg2" ), "Half super-instr?" self.write_super_expansions(instr.name) case parsing.Macro(): mac = self.macro_instrs[thing.name] self.write_macro_expansions( mac.name, mac.parts, mac.cache_offset ) case parsing.Pseudo(): pass case _: assert_never(thing) with self.metadata_item( "const char * const _PyOpcode_uop_name[OPCODE_UOP_NAME_SIZE]", "=", ";" ): self.write_uop_items(lambda name, counter: f'[{name}] = "{name}",') with self.metadata_item( f"const char *const _PyOpcode_OpName[{1 + max(self.opmap.values())}]", "=", ";", ): for name in self.opmap: self.out.emit(f'[{name}] = "{name}",') with self.metadata_item( f"const uint8_t _PyOpcode_Caches[256]", "=", ";", ): for name, _ in self.families.items(): instr = self.instrs[name] if instr.cache_offset > 0: self.out.emit(f'[{name}] = {instr.cache_offset},') # Irregular case: self.out.emit('[JUMP_BACKWARD] = 1,') deoptcodes = {} for name, op in self.opmap.items(): if op < 256: deoptcodes[name] = name for name, family in self.families.items(): for m in family.members: deoptcodes[m] = name # special case: deoptcodes["BINARY_OP_INPLACE_ADD_UNICODE"] = "BINARY_OP" with self.metadata_item(f"const uint8_t _PyOpcode_Deopt[256]", "=", ";"): for opt, deopt in sorted(deoptcodes.items()): self.out.emit(f"[{opt}] = {deopt},") self.out.emit("") self.out.emit("#define EXTRA_CASES \\") valid_opcodes = set(self.opmap.values()) with self.out.indent(): for op in range(256): if op not in valid_opcodes: self.out.emit(f"case {op}: \\") self.out.emit(" ;\n") with open(pymetadata_filename, "w") as f: # Create formatter self.out = Formatter(f, 0, comment="#") self.write_provenance_header() # emit specializations specialized_ops = set() self.out.emit("") self.out.emit("_specializations = {") for name, family in self.families.items(): with self.out.indent(): self.out.emit(f'"{family.name}": [') with self.out.indent(): for m in family.members: self.out.emit(f'"{m}",') specialized_ops.update(family.members) self.out.emit(f"],") self.out.emit("}") # Handle special case self.out.emit("") self.out.emit("# An irregular case:") self.out.emit( '_specializations["BINARY_OP"].append(' '"BINARY_OP_INPLACE_ADD_UNICODE")' ) specialized_ops.add("BINARY_OP_INPLACE_ADD_UNICODE") ops = sorted((id, name) for (name, id) in self.opmap.items()) # emit specialized opmap self.out.emit("") with self.out.block("_specialized_opmap ="): for op, name in ops: if name in specialized_ops: self.out.emit(f"'{name}': {op},") # emit opmap self.out.emit("") with self.out.block("opmap ="): for op, name in ops: if name not in specialized_ops: self.out.emit(f"'{name}': {op},") for name in ["MIN_INSTRUMENTED_OPCODE", "HAVE_ARGUMENT"]: self.out.emit(f"{name} = {self.markers[name]}") def write_pseudo_instrs(self) -> None: """Write the IS_PSEUDO_INSTR macro""" self.out.emit("\n\n#define IS_PSEUDO_INSTR(OP) ( \\") for op in self.pseudos: self.out.emit(f" ((OP) == {op}) || \\") self.out.emit(f" 0)") def write_uop_items(self, make_text: typing.Callable[[str, int], str]) -> None: """Write '#define XXX NNN' for each uop""" counter = 300 # TODO: Avoid collision with pseudo instructions seen = set() def add(name: str) -> None: if name in seen: return nonlocal counter self.out.emit(make_text(name, counter)) counter += 1 seen.add(name) # These two are first by convention add("EXIT_TRACE") add("SAVE_IP") for instr in self.instrs.values(): if instr.kind == "op": add(instr.name) def write_macro_expansions( self, name: str, parts: MacroParts, cache_offset: int ) -> None: """Write the macro expansions for a macro-instruction.""" # TODO: Refactor to share code with write_cody(), is_viaible_uop(), etc. offset = 0 # Cache effect offset expansions: list[tuple[str, int, int]] = [] # [(name, size, offset), ...] for part in parts: if isinstance(part, Component): # All component instructions must be viable uops if not part.instr.is_viable_uop(): # This note just reminds us about macros that cannot # be expanded to Tier 2 uops. It is not an error. # It is sometimes emitted for macros that have a # manual translation in translate_bytecode_to_trace() # in Python/optimizer.c. self.note( f"Part {part.instr.name} of {name} is not a viable uop", part.instr.inst, ) return if not part.active_caches: if part.instr.name == "SAVE_IP": size, offset = OPARG_SIZES["OPARG_SAVE_IP"], cache_offset else: size, offset = OPARG_SIZES["OPARG_FULL"], 0 else: # If this assert triggers, is_viable_uops() lied assert len(part.active_caches) == 1, (name, part.instr.name) cache = part.active_caches[0] size, offset = cache.effect.size, cache.offset expansions.append((part.instr.name, size, offset)) assert len(expansions) > 0, f"Macro {name} has empty expansion?!" self.write_expansions(name, expansions) def write_super_expansions(self, name: str) -> None: """Write special macro expansions for super-instructions. If you get an assertion failure here, you probably have accidentally violated one of the assumptions here. - A super-instruction's name is of the form FIRST_SECOND where FIRST and SECOND are regular instructions whose name has the form FOO_BAR. Thus, there must be exactly 3 underscores. Example: LOAD_CONST_STORE_FAST. - A super-instruction's body uses `oparg1 and `oparg2`, and no other instruction's body uses those variable names. - A super-instruction has no active (used) cache entries. In the expansion, the first instruction's operand is all but the bottom 4 bits of the super-instruction's oparg, and the second instruction's operand is the bottom 4 bits. We use the special size codes OPARG_TOP and OPARG_BOTTOM for these. """ pieces = name.split("_") assert len(pieces) == 4, f"{name} doesn't look like a super-instr" name1 = "_".join(pieces[:2]) name2 = "_".join(pieces[2:]) assert name1 in self.instrs, f"{name1} doesn't match any instr" assert name2 in self.instrs, f"{name2} doesn't match any instr" instr1 = self.instrs[name1] instr2 = self.instrs[name2] assert not instr1.active_caches, f"{name1} has active caches" assert not instr2.active_caches, f"{name2} has active caches" expansions: list[tuple[str, int, int]] = [ (name1, OPARG_SIZES["OPARG_TOP"], 0), (name2, OPARG_SIZES["OPARG_BOTTOM"], 0), ] self.write_expansions(name, expansions) def write_expansions( self, name: str, expansions: list[tuple[str, int, int]] ) -> None: pieces = [ f"{{ {name}, {size}, {offset} }}" for name, size, offset in expansions ] self.out.emit( f"[{name}] = " f"{{ .nuops = {len(pieces)}, .uops = {{ {', '.join(pieces)} }} }}," ) def emit_metadata_entry(self, name: str, fmt: str, flags: InstructionFlags) -> None: flag_names = flags.names(value=True) if not flag_names: flag_names.append("0") self.out.emit( f"[{name}] = {{ true, {INSTR_FMT_PREFIX}{fmt}," f" {' | '.join(flag_names)} }}," ) def write_metadata_for_inst(self, instr: Instruction) -> None: """Write metadata for a single instruction.""" self.emit_metadata_entry(instr.name, instr.instr_fmt, instr.instr_flags) def write_metadata_for_macro(self, mac: MacroInstruction) -> None: """Write metadata for a macro-instruction.""" self.emit_metadata_entry(mac.name, mac.instr_fmt, mac.instr_flags) def write_metadata_for_pseudo(self, ps: PseudoInstruction) -> None: """Write metadata for a macro-instruction.""" self.emit_metadata_entry(ps.name, ps.instr_fmt, ps.instr_flags) def write_instructions( self, output_filename: str, emit_line_directives: bool ) -> None: """Write instructions to output file.""" with open(output_filename, "w") as f: # Create formatter self.out = Formatter(f, 8, emit_line_directives) self.write_provenance_header() # Write and count instructions of all kinds n_instrs = 0 n_macros = 0 for thing in self.everything: match thing: case OverriddenInstructionPlaceHolder(): self.write_overridden_instr_place_holder(thing) case parsing.InstDef(): if thing.kind != "op": n_instrs += 1 self.write_instr(self.instrs[thing.name]) case parsing.Macro(): n_macros += 1 mac = self.macro_instrs[thing.name] stacking.write_macro_instr( mac, self.out, self.families.get(mac.name) ) # self.write_macro(self.macro_instrs[thing.name]) case parsing.Pseudo(): pass case _: assert_never(thing) print( f"Wrote {n_instrs} instructions and {n_macros} macros " f"to {output_filename}", file=sys.stderr, ) def write_executor_instructions( self, executor_filename: str, emit_line_directives: bool ) -> None: """Generate cases for the Tier 2 interpreter.""" n_instrs = 0 n_uops = 0 with open(executor_filename, "w") as f: self.out = Formatter(f, 8, emit_line_directives) self.write_provenance_header() for thing in self.everything: match thing: case OverriddenInstructionPlaceHolder(): # TODO: Is this helpful? self.write_overridden_instr_place_holder(thing) case parsing.InstDef(): instr = self.instrs[thing.name] if instr.is_viable_uop(): if instr.kind == "op": n_uops += 1 else: n_instrs += 1 self.out.emit("") with self.out.block(f"case {thing.name}:"): stacking.write_single_instr( instr, self.out, tier=TIER_TWO ) if instr.check_eval_breaker: self.out.emit("CHECK_EVAL_BREAKER();") self.out.emit("break;") # elif instr.kind != "op": # print(f"NOTE: {thing.name} is not a viable uop") case parsing.Macro(): pass case parsing.Pseudo(): pass case _: assert_never(thing) print( f"Wrote {n_instrs} instructions and {n_uops} ops to {executor_filename}", file=sys.stderr, ) def write_abstract_interpreter_instructions( self, abstract_interpreter_filename: str, emit_line_directives: bool ) -> None: """Generate cases for the Tier 2 abstract interpreter/analzyer.""" with open(abstract_interpreter_filename, "w") as f: self.out = Formatter(f, 8, emit_line_directives) self.write_provenance_header() for thing in self.everything: match thing: case OverriddenInstructionPlaceHolder(): pass case parsing.InstDef(): instr = AbstractInstruction(self.instrs[thing.name].inst) if ( instr.is_viable_uop() and instr.name not in SPECIALLY_HANDLED_ABSTRACT_INSTR ): self.out.emit("") with self.out.block(f"case {thing.name}:"): instr.write(self.out, tier=TIER_TWO) self.out.emit("break;") case parsing.Macro(): pass case parsing.Pseudo(): pass case _: assert_never(thing) print( f"Wrote some stuff to {abstract_interpreter_filename}", file=sys.stderr, ) def write_overridden_instr_place_holder( self, place_holder: OverriddenInstructionPlaceHolder ) -> None: self.out.emit("") self.out.emit( f"{self.out.comment} TARGET({place_holder.name}) overridden by later definition" ) def write_instr(self, instr: Instruction) -> None: name = instr.name self.out.emit("") if instr.inst.override: self.out.emit("{self.out.comment} Override") with self.out.block(f"TARGET({name})"): if instr.predicted: self.out.emit(f"PREDICTED({name});") self.out.static_assert_family_size( instr.name, instr.family, instr.cache_offset ) stacking.write_single_instr(instr, self.out, tier=TIER_ONE) if not instr.always_exits: if instr.cache_offset: self.out.emit(f"next_instr += {instr.cache_offset};") if instr.check_eval_breaker: self.out.emit("CHECK_EVAL_BREAKER();") self.out.emit(f"DISPATCH();") def main() -> None: """Parse command line, parse input, analyze, write output.""" args = arg_parser.parse_args() # Prints message and sys.exit(2) on error if len(args.input) == 0: args.input.append(DEFAULT_INPUT) # Raises OSError if input unreadable a = Generator(args.input) a.parse() # Raises SyntaxError on failure a.analyze() # Prints messages and sets a.errors on failure if a.errors: sys.exit(f"Found {a.errors} errors") # These raise OSError if output can't be written a.write_instructions(args.output, args.emit_line_directives) a.assign_opcode_ids() a.write_opcode_ids(args.opcode_ids_h, args.opcode_targets_h) a.write_metadata(args.metadata, args.pymetadata) a.write_executor_instructions(args.executor_cases, args.emit_line_directives) a.write_abstract_interpreter_instructions( args.abstract_interpreter_cases, args.emit_line_directives ) if __name__ == "__main__": main()