)
with open(self.temp_output_filename) as temp_output:
- lines = temp_output.readlines()
- while lines and lines[0].startswith(("// ", "#", " #", "\n")):
- lines.pop(0)
- while lines and lines[-1].startswith(("#", "\n")):
- lines.pop(-1)
- actual = "".join(lines)
+ lines = temp_output.read()
+ _, rest = lines.split(tier1_generator.INSTRUCTION_START_MARKER)
+ instructions, labels_with_prelude_and_postlude = rest.split(tier1_generator.INSTRUCTION_END_MARKER)
+ _, labels_with_postlude = labels_with_prelude_and_postlude.split(tier1_generator.LABEL_START_MARKER)
+ labels, _ = labels_with_postlude.split(tier1_generator.LABEL_END_MARKER)
+ actual = instructions + labels
# if actual.strip() != expected.strip():
# print("Actual:")
# print(actual)
with self.assertRaises(SyntaxError):
self.run_cases_test(input, "")
+ def test_complex_label(self):
+ input = """
+ label(my_label) {
+ // Comment
+ do_thing()
+ if (complex) {
+ goto other_label;
+ }
+ goto other_label2;
+ }
+ """
+
+ output = """
+ my_label:
+ {
+ // Comment
+ do_thing()
+ if (complex) {
+ goto other_label;
+ }
+ goto other_label2;
+ }
+ """
+ self.run_cases_test(input, output)
+
+ def test_multiple_labels(self):
+ input = """
+ label(my_label_1) {
+ // Comment
+ do_thing1();
+ goto my_label_2;
+ }
+
+ label(my_label_2) {
+ // Comment
+ do_thing2();
+ goto my_label_3;
+ }
+ """
+
+ output = """
+ my_label_1:
+ {
+ // Comment
+ do_thing1();
+ goto my_label_2;
+ }
+
+ my_label_2:
+ {
+ // Comment
+ do_thing2();
+ goto my_label_3;
+ }
+ """
class TestGeneratedAbstractCases(unittest.TestCase):
def setUp(self) -> None:
#define super(name) static int SUPER_##name
#define family(name, ...) static int family_##name
#define pseudo(name) static int pseudo_##name
+#define label(name) name:
/* Annotations */
#define guard
PyObject *codeobj;
PyObject *cond;
PyObject *descr;
- _PyInterpreterFrame entry_frame;
PyObject *exc;
PyObject *exit;
PyObject *fget;
assert(tstate->tracing || eval_breaker == FT_ATOMIC_LOAD_UINTPTR_ACQUIRE(_PyFrame_GetCode(frame)->_co_instrumentation_version));
}
+ label(pop_4_error) {
+ STACK_SHRINK(1);
+ goto pop_3_error;
+ }
+
+ label(pop_3_error) {
+ STACK_SHRINK(1);
+ goto pop_2_error;
+ }
+
+ label(pop_2_error) {
+ STACK_SHRINK(1);
+ goto pop_1_error;
+ }
+
+ label(pop_1_error) {
+ STACK_SHRINK(1);
+ goto error;
+ }
+
+ label(error) {
+ /* Double-check exception status. */
+#ifdef NDEBUG
+ if (!_PyErr_Occurred(tstate)) {
+ _PyErr_SetString(tstate, PyExc_SystemError,
+ "error return without exception set");
+ }
+#else
+ assert(_PyErr_Occurred(tstate));
+#endif
+
+ /* Log traceback info. */
+ assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
+ if (!_PyFrame_IsIncomplete(frame)) {
+ PyFrameObject *f = _PyFrame_GetFrameObject(frame);
+ if (f != NULL) {
+ PyTraceBack_Here(f);
+ }
+ }
+ _PyEval_MonitorRaise(tstate, frame, next_instr-1);
+ goto exception_unwind;
+ }
+
+ label(exception_unwind) {
+ /* We can't use frame->instr_ptr here, as RERAISE may have set it */
+ int offset = INSTR_OFFSET()-1;
+ int level, handler, lasti;
+ if (get_exception_handler(_PyFrame_GetCode(frame), offset, &level, &handler, &lasti) == 0) {
+ // No handlers, so exit.
+ assert(_PyErr_Occurred(tstate));
+
+ /* Pop remaining stack entries. */
+ _PyStackRef *stackbase = _PyFrame_Stackbase(frame);
+ while (stack_pointer > stackbase) {
+ PyStackRef_XCLOSE(POP());
+ }
+ assert(STACK_LEVEL() == 0);
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ monitor_unwind(tstate, frame, next_instr-1);
+ goto exit_unwind;
+ }
+
+ assert(STACK_LEVEL() >= level);
+ _PyStackRef *new_top = _PyFrame_Stackbase(frame) + level;
+ while (stack_pointer > new_top) {
+ PyStackRef_XCLOSE(POP());
+ }
+ if (lasti) {
+ int frame_lasti = _PyInterpreterFrame_LASTI(frame);
+ PyObject *lasti = PyLong_FromLong(frame_lasti);
+ if (lasti == NULL) {
+ goto exception_unwind;
+ }
+ PUSH(PyStackRef_FromPyObjectSteal(lasti));
+ }
+
+ /* Make the raw exception data
+ available to the handler,
+ so a program can emulate the
+ Python main loop. */
+ PyObject *exc = _PyErr_GetRaisedException(tstate);
+ PUSH(PyStackRef_FromPyObjectSteal(exc));
+ next_instr = _PyFrame_GetBytecode(frame) + handler;
+
+ if (monitor_handled(tstate, frame, next_instr, exc) < 0) {
+ goto exception_unwind;
+ }
+ /* Resume normal execution */
+#ifdef LLTRACE
+ if (frame->lltrace >= 5) {
+ lltrace_resume_frame(frame);
+ }
+#endif
+ DISPATCH();
+ }
+
+ label(exit_unwind) {
+ assert(_PyErr_Occurred(tstate));
+ _Py_LeaveRecursiveCallPy(tstate);
+ assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
+ // GH-99729: We need to unlink the frame *before* clearing it:
+ _PyInterpreterFrame *dying = frame;
+ frame = tstate->current_frame = dying->previous;
+ _PyEval_FrameClearAndPop(tstate, dying);
+ frame->return_offset = 0;
+ if (frame->owner == FRAME_OWNED_BY_INTERPRETER) {
+ /* Restore previous frame and exit */
+ tstate->current_frame = frame->previous;
+ tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
+ return NULL;
+ }
+ goto resume_with_error;
+ }
+
+ label(resume_with_error) {
+ next_instr = frame->instr_ptr;
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ goto error;
+ }
// END BYTECODES //
}
DISPATCH();
- {
- /* Start instructions */
-#if !USE_COMPUTED_GOTOS
- dispatch_opcode:
- switch (opcode)
-#endif
- {
-
#include "generated_cases.c.h"
-#if USE_COMPUTED_GOTOS
- _unknown_opcode:
-#else
- EXTRA_CASES // From pycore_opcode_metadata.h, a 'case' for each unused opcode
-#endif
- /* Tell C compilers not to hold the opcode variable in the loop.
- next_instr points the current instruction without TARGET(). */
- opcode = next_instr->op.code;
- _PyErr_Format(tstate, PyExc_SystemError,
- "%U:%d: unknown opcode %d",
- _PyFrame_GetCode(frame)->co_filename,
- PyUnstable_InterpreterFrame_GetLine(frame),
- opcode);
- goto error;
-
- } /* End instructions */
-
- /* This should never be reached. Every opcode should end with DISPATCH()
- or goto error. */
- Py_UNREACHABLE();
-
-pop_4_error:
- STACK_SHRINK(1);
-pop_3_error:
- STACK_SHRINK(1);
-pop_2_error:
- STACK_SHRINK(1);
-pop_1_error:
- STACK_SHRINK(1);
-error:
- /* Double-check exception status. */
-#ifdef NDEBUG
- if (!_PyErr_Occurred(tstate)) {
- _PyErr_SetString(tstate, PyExc_SystemError,
- "error return without exception set");
- }
-#else
- assert(_PyErr_Occurred(tstate));
-#endif
-
- /* Log traceback info. */
- assert(frame != &entry_frame);
- if (!_PyFrame_IsIncomplete(frame)) {
- PyFrameObject *f = _PyFrame_GetFrameObject(frame);
- if (f != NULL) {
- PyTraceBack_Here(f);
- }
- }
- _PyEval_MonitorRaise(tstate, frame, next_instr-1);
-exception_unwind:
- {
- /* We can't use frame->instr_ptr here, as RERAISE may have set it */
- int offset = INSTR_OFFSET()-1;
- int level, handler, lasti;
- if (get_exception_handler(_PyFrame_GetCode(frame), offset, &level, &handler, &lasti) == 0) {
- // No handlers, so exit.
- assert(_PyErr_Occurred(tstate));
-
- /* Pop remaining stack entries. */
- _PyStackRef *stackbase = _PyFrame_Stackbase(frame);
- while (stack_pointer > stackbase) {
- PyStackRef_XCLOSE(POP());
- }
- assert(STACK_LEVEL() == 0);
- _PyFrame_SetStackPointer(frame, stack_pointer);
- monitor_unwind(tstate, frame, next_instr-1);
- goto exit_unwind;
- }
-
- assert(STACK_LEVEL() >= level);
- _PyStackRef *new_top = _PyFrame_Stackbase(frame) + level;
- while (stack_pointer > new_top) {
- PyStackRef_XCLOSE(POP());
- }
- if (lasti) {
- int frame_lasti = _PyInterpreterFrame_LASTI(frame);
- PyObject *lasti = PyLong_FromLong(frame_lasti);
- if (lasti == NULL) {
- goto exception_unwind;
- }
- PUSH(PyStackRef_FromPyObjectSteal(lasti));
- }
-
- /* Make the raw exception data
- available to the handler,
- so a program can emulate the
- Python main loop. */
- PyObject *exc = _PyErr_GetRaisedException(tstate);
- PUSH(PyStackRef_FromPyObjectSteal(exc));
- next_instr = _PyFrame_GetBytecode(frame) + handler;
-
- if (monitor_handled(tstate, frame, next_instr, exc) < 0) {
- goto exception_unwind;
- }
- /* Resume normal execution */
-#ifdef LLTRACE
- if (frame->lltrace >= 5) {
- lltrace_resume_frame(frame);
- }
-#endif
- DISPATCH();
- }
- }
-
-exit_unwind:
- assert(_PyErr_Occurred(tstate));
- _Py_LeaveRecursiveCallPy(tstate);
- assert(frame != &entry_frame);
- // GH-99729: We need to unlink the frame *before* clearing it:
- _PyInterpreterFrame *dying = frame;
- frame = tstate->current_frame = dying->previous;
- _PyEval_FrameClearAndPop(tstate, dying);
- frame->return_offset = 0;
- if (frame == &entry_frame) {
- /* Restore previous frame and exit */
- tstate->current_frame = frame->previous;
- tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
- return NULL;
- }
-
-resume_with_error:
- next_instr = frame->instr_ptr;
- stack_pointer = _PyFrame_GetStackPointer(frame);
- goto error;
-
-
#ifdef _Py_TIER2
// Tier 2 is also here!
#endif
#define TIER_ONE 1
+#if !USE_COMPUTED_GOTOS
+ dispatch_opcode:
+ switch (opcode)
+#endif
+ {
+ /* BEGIN INSTRUCTIONS */
+
TARGET(BINARY_OP) {
frame->instr_ptr = next_instr;
assert(WITHIN_STACK_BOUNDS());
DISPATCH();
}
+
+ /* END INSTRUCTIONS */
+#if USE_COMPUTED_GOTOS
+ _unknown_opcode:
+#else
+ EXTRA_CASES // From pycore_opcode_metadata.h, a 'case' for each unused opcode
+#endif
+ /* Tell C compilers not to hold the opcode variable in the loop.
+ next_instr points the current instruction without TARGET(). */
+ opcode = next_instr->op.code;
+ _PyErr_Format(tstate, PyExc_SystemError,
+ "%U:%d: unknown opcode %d",
+ _PyFrame_GetCode(frame)->co_filename,
+ PyUnstable_InterpreterFrame_GetLine(frame),
+ opcode);
+ goto error;
+
+ }
+
+ /* This should never be reached. Every opcode should end with DISPATCH()
+ or goto error. */
+ Py_UNREACHABLE();
+ /* BEGIN LABELS */
+
+ pop_4_error:
+ {
+ STACK_SHRINK(1);
+ goto pop_3_error;
+ }
+
+ pop_3_error:
+ {
+ STACK_SHRINK(1);
+ goto pop_2_error;
+ }
+
+ pop_2_error:
+ {
+ STACK_SHRINK(1);
+ goto pop_1_error;
+ }
+
+ pop_1_error:
+ {
+ STACK_SHRINK(1);
+ goto error;
+ }
+
+ error:
+ {
+ /* Double-check exception status. */
+ #ifdef NDEBUG
+ if (!_PyErr_Occurred(tstate)) {
+ _PyErr_SetString(tstate, PyExc_SystemError,
+ "error return without exception set");
+ }
+ #else
+ assert(_PyErr_Occurred(tstate));
+ #endif
+
+ /* Log traceback info. */
+ assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
+ if (!_PyFrame_IsIncomplete(frame)) {
+ PyFrameObject *f = _PyFrame_GetFrameObject(frame);
+ if (f != NULL) {
+ PyTraceBack_Here(f);
+ }
+ }
+ _PyEval_MonitorRaise(tstate, frame, next_instr-1);
+ goto exception_unwind;
+ }
+
+ exception_unwind:
+ {
+ /* We can't use frame->instr_ptr here, as RERAISE may have set it */
+ int offset = INSTR_OFFSET()-1;
+ int level, handler, lasti;
+ if (get_exception_handler(_PyFrame_GetCode(frame), offset, &level, &handler, &lasti) == 0) {
+ // No handlers, so exit.
+ assert(_PyErr_Occurred(tstate));
+ /* Pop remaining stack entries. */
+ _PyStackRef *stackbase = _PyFrame_Stackbase(frame);
+ while (stack_pointer > stackbase) {
+ PyStackRef_XCLOSE(POP());
+ }
+ assert(STACK_LEVEL() == 0);
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ monitor_unwind(tstate, frame, next_instr-1);
+ goto exit_unwind;
+ }
+ assert(STACK_LEVEL() >= level);
+ _PyStackRef *new_top = _PyFrame_Stackbase(frame) + level;
+ while (stack_pointer > new_top) {
+ PyStackRef_XCLOSE(POP());
+ }
+ if (lasti) {
+ int frame_lasti = _PyInterpreterFrame_LASTI(frame);
+ PyObject *lasti = PyLong_FromLong(frame_lasti);
+ if (lasti == NULL) {
+ goto exception_unwind;
+ }
+ PUSH(PyStackRef_FromPyObjectSteal(lasti));
+ }
+ /* Make the raw exception data
+ available to the handler,
+ so a program can emulate the
+ Python main loop. */
+ PyObject *exc = _PyErr_GetRaisedException(tstate);
+ PUSH(PyStackRef_FromPyObjectSteal(exc));
+ next_instr = _PyFrame_GetBytecode(frame) + handler;
+ if (monitor_handled(tstate, frame, next_instr, exc) < 0) {
+ goto exception_unwind;
+ }
+ /* Resume normal execution */
+ #ifdef LLTRACE
+ if (frame->lltrace >= 5) {
+ lltrace_resume_frame(frame);
+ }
+ #endif
+ DISPATCH();
+ }
+
+ exit_unwind:
+ {
+ assert(_PyErr_Occurred(tstate));
+ _Py_LeaveRecursiveCallPy(tstate);
+ assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
+ // GH-99729: We need to unlink the frame *before* clearing it:
+ _PyInterpreterFrame *dying = frame;
+ frame = tstate->current_frame = dying->previous;
+ _PyEval_FrameClearAndPop(tstate, dying);
+ frame->return_offset = 0;
+ if (frame->owner == FRAME_OWNED_BY_INTERPRETER) {
+ /* Restore previous frame and exit */
+ tstate->current_frame = frame->previous;
+ tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
+ return NULL;
+ }
+ goto resume_with_error;
+ }
+
+ resume_with_error:
+ {
+ next_instr = frame->instr_ptr;
+ stack_pointer = _PyFrame_GetStackPointer(frame);
+ goto error;
+ }
+
+/* END LABELS */
#undef TIER_ONE
return False
+@dataclass
+class Label:
+ name: str
+ body: list[lexer.Token]
+
+
@dataclass
class PseudoInstruction:
name: str
uops: dict[str, Uop]
families: dict[str, Family]
pseudos: dict[str, PseudoInstruction]
+ labels: dict[str, Label]
opmap: dict[str, int]
have_arg: int
min_instrumented: int
)
+def add_label(
+ label: parser.LabelDef,
+ labels: dict[str, Label],
+) -> None:
+ labels[label.name] = Label(label.name, label.block.tokens)
+
+
def assign_opcodes(
instructions: dict[str, Instruction],
families: dict[str, Family],
uops: dict[str, Uop] = {}
families: dict[str, Family] = {}
pseudos: dict[str, PseudoInstruction] = {}
+ labels: dict[str, Label] = {}
for node in forest:
match node:
case parser.InstDef(name):
pass
case parser.Pseudo():
pass
+ case parser.LabelDef():
+ pass
case _:
assert False
for node in forest:
add_family(node, instructions, families)
case parser.Pseudo():
add_pseudo(node, instructions, pseudos)
+ case parser.LabelDef():
+ add_label(node, labels)
case _:
pass
for uop in uops.values():
families["BINARY_OP"].members.append(inst)
opmap, first_arg, min_instrumented = assign_opcodes(instructions, families, pseudos)
return Analysis(
- instructions, uops, families, pseudos, opmap, first_arg, min_instrumented
+ instructions, uops, families, pseudos, labels, opmap, first_arg, min_instrumented
)
# A macro in the DSL
MACRO = "MACRO"
kwds.append(MACRO)
+# A label in the DSL
+LABEL = "LABEL"
+kwds.append(LABEL)
keywords = {name.lower(): name for name in kwds}
ANNOTATION = "ANNOTATION"
Macro,
Pseudo,
Family,
+ LabelDef,
Parser,
Context,
CacheEffect,
targets: list[str] # opcodes this can be replaced by
as_sequence: bool
+@dataclass
+class LabelDef(Node):
+ name: str
+ block: Block
-AstNode = InstDef | Macro | Pseudo | Family
+
+AstNode = InstDef | Macro | Pseudo | Family | LabelDef
class Parser(PLexer):
return pseudo
if inst := self.inst_def():
return inst
+ if label := self.label_def():
+ return label
+ return None
+
+ @contextual
+ def label_def(self) -> LabelDef | None:
+ if self.expect(lx.LABEL):
+ if self.expect(lx.LPAREN):
+ if tkn := self.expect(lx.IDENTIFIER):
+ if self.expect(lx.RPAREN):
+ if block := self.block():
+ return LabelDef(tkn.text, block)
return None
@contextual
FOOTER = "#undef TIER_ONE\n"
+INSTRUCTION_START_MARKER = "/* BEGIN INSTRUCTIONS */"
+INSTRUCTION_END_MARKER = "/* END INSTRUCTIONS */"
+LABEL_START_MARKER = "/* BEGIN LABELS */"
+LABEL_END_MARKER = "/* END LABELS */"
def declare_variable(var: StackItem, out: CWriter) -> None:
) -> None:
write_header(__file__, filenames, outfile)
outfile.write(
- """
+ f"""
#ifdef TIER_TWO
#error "This file is for Tier 1 only"
#endif
#define TIER_ONE 1
+
+#if !USE_COMPUTED_GOTOS
+ dispatch_opcode:
+ switch (opcode)
+#endif
+ {{
+ {INSTRUCTION_START_MARKER}
"""
)
+ generate_tier1_cases(analysis, outfile, lines)
+ outfile.write(f"""
+ {INSTRUCTION_END_MARKER}
+#if USE_COMPUTED_GOTOS
+ _unknown_opcode:
+#else
+ EXTRA_CASES // From pycore_opcode_metadata.h, a 'case' for each unused opcode
+#endif
+ /* Tell C compilers not to hold the opcode variable in the loop.
+ next_instr points the current instruction without TARGET(). */
+ opcode = next_instr->op.code;
+ _PyErr_Format(tstate, PyExc_SystemError,
+ "%U:%d: unknown opcode %d",
+ _PyFrame_GetCode(frame)->co_filename,
+ PyUnstable_InterpreterFrame_GetLine(frame),
+ opcode);
+ goto error;
+
+ }}
+
+ /* This should never be reached. Every opcode should end with DISPATCH()
+ or goto error. */
+ Py_UNREACHABLE();
+ {LABEL_START_MARKER}
+""")
+ generate_tier1_labels(analysis, outfile, lines)
+ outfile.write(f"{LABEL_END_MARKER}\n")
+ outfile.write(FOOTER)
+
+def generate_tier1_labels(
+ analysis: Analysis, outfile: TextIO, lines: bool
+) -> None:
+ out = CWriter(outfile, 2, lines)
+ out.emit("\n")
+ for name, label in analysis.labels.items():
+ out.emit(f"{name}:\n")
+ for tkn in label.body:
+ out.emit(tkn)
+ out.emit("\n")
+ out.emit("\n")
+
+def generate_tier1_cases(
+ analysis: Analysis, outfile: TextIO, lines: bool
+) -> None:
out = CWriter(outfile, 2, lines)
emitter = Emitter(out)
out.emit("\n")
out.start_line()
out.emit("}")
out.emit("\n")
- outfile.write(FOOTER)
arg_parser = argparse.ArgumentParser(