mirror of
https://github.com/python/cpython.git
synced 2024-11-23 18:04:37 +08:00
gh-98831: Support conditional effects; use for LOAD_ATTR (#101333)
This commit is contained in:
parent
c4170c36b0
commit
f5a3d91b6c
@ -51,7 +51,7 @@
|
||||
|
||||
// Dummy variables for stack effects.
|
||||
static PyObject *value, *value1, *value2, *left, *right, *res, *sum, *prod, *sub;
|
||||
static PyObject *container, *start, *stop, *v, *lhs, *rhs;
|
||||
static PyObject *container, *start, *stop, *v, *lhs, *rhs, *res2;
|
||||
static PyObject *list, *tuple, *dict, *owner, *set, *str, *tup, *map, *keys;
|
||||
static PyObject *exit_func, *lasti, *val, *retval, *obj, *iter;
|
||||
static PyObject *aiter, *awaitable, *iterable, *w, *exc_value, *bc;
|
||||
@ -1438,13 +1438,11 @@ dummy_func(
|
||||
PREDICT(JUMP_BACKWARD);
|
||||
}
|
||||
|
||||
// error: LOAD_ATTR has irregular stack effect
|
||||
inst(LOAD_ATTR) {
|
||||
inst(LOAD_ATTR, (unused/9, owner -- res2 if (oparg & 1), res)) {
|
||||
#if ENABLE_SPECIALIZATION
|
||||
_PyAttrCache *cache = (_PyAttrCache *)next_instr;
|
||||
if (ADAPTIVE_COUNTER_IS_ZERO(cache->counter)) {
|
||||
assert(cframe.use_tracing == 0);
|
||||
PyObject *owner = TOP();
|
||||
PyObject *name = GETITEM(names, oparg>>1);
|
||||
next_instr--;
|
||||
_Py_Specialize_LoadAttr(owner, next_instr, name);
|
||||
@ -1454,26 +1452,18 @@ dummy_func(
|
||||
DECREMENT_ADAPTIVE_COUNTER(cache->counter);
|
||||
#endif /* ENABLE_SPECIALIZATION */
|
||||
PyObject *name = GETITEM(names, oparg >> 1);
|
||||
PyObject *owner = TOP();
|
||||
if (oparg & 1) {
|
||||
/* Designed to work in tandem with CALL. */
|
||||
/* Designed to work in tandem with CALL, pushes two values. */
|
||||
PyObject* meth = NULL;
|
||||
|
||||
int meth_found = _PyObject_GetMethod(owner, name, &meth);
|
||||
|
||||
if (meth == NULL) {
|
||||
/* Most likely attribute wasn't found. */
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (meth_found) {
|
||||
if (_PyObject_GetMethod(owner, name, &meth)) {
|
||||
/* We can bypass temporary bound method object.
|
||||
meth is unbound method and obj is self.
|
||||
|
||||
meth | self | arg1 | ... | argN
|
||||
*/
|
||||
SET_TOP(meth);
|
||||
PUSH(owner); // self
|
||||
assert(meth != NULL); // No errors on this branch
|
||||
res2 = meth;
|
||||
res = owner; // Transfer ownership
|
||||
}
|
||||
else {
|
||||
/* meth is not an unbound method (but a regular attr, or
|
||||
@ -1483,20 +1473,18 @@ dummy_func(
|
||||
|
||||
NULL | meth | arg1 | ... | argN
|
||||
*/
|
||||
SET_TOP(NULL);
|
||||
Py_DECREF(owner);
|
||||
PUSH(meth);
|
||||
ERROR_IF(meth == NULL, error);
|
||||
res2 = NULL;
|
||||
res = meth;
|
||||
}
|
||||
}
|
||||
else {
|
||||
PyObject *res = PyObject_GetAttr(owner, name);
|
||||
if (res == NULL) {
|
||||
goto error;
|
||||
}
|
||||
/* Classic, pushes one value. */
|
||||
res = PyObject_GetAttr(owner, name);
|
||||
Py_DECREF(owner);
|
||||
SET_TOP(res);
|
||||
ERROR_IF(res == NULL, error);
|
||||
}
|
||||
JUMPBY(INLINE_CACHE_ENTRIES_LOAD_ATTR);
|
||||
}
|
||||
|
||||
// error: LOAD_ATTR has irregular stack effect
|
||||
|
40
Python/generated_cases.c.h
generated
40
Python/generated_cases.c.h
generated
@ -1745,11 +1745,13 @@
|
||||
|
||||
TARGET(LOAD_ATTR) {
|
||||
PREDICTED(LOAD_ATTR);
|
||||
PyObject *owner = PEEK(1);
|
||||
PyObject *res2 = NULL;
|
||||
PyObject *res;
|
||||
#if ENABLE_SPECIALIZATION
|
||||
_PyAttrCache *cache = (_PyAttrCache *)next_instr;
|
||||
if (ADAPTIVE_COUNTER_IS_ZERO(cache->counter)) {
|
||||
assert(cframe.use_tracing == 0);
|
||||
PyObject *owner = TOP();
|
||||
PyObject *name = GETITEM(names, oparg>>1);
|
||||
next_instr--;
|
||||
_Py_Specialize_LoadAttr(owner, next_instr, name);
|
||||
@ -1759,26 +1761,18 @@
|
||||
DECREMENT_ADAPTIVE_COUNTER(cache->counter);
|
||||
#endif /* ENABLE_SPECIALIZATION */
|
||||
PyObject *name = GETITEM(names, oparg >> 1);
|
||||
PyObject *owner = TOP();
|
||||
if (oparg & 1) {
|
||||
/* Designed to work in tandem with CALL. */
|
||||
/* Designed to work in tandem with CALL, pushes two values. */
|
||||
PyObject* meth = NULL;
|
||||
|
||||
int meth_found = _PyObject_GetMethod(owner, name, &meth);
|
||||
|
||||
if (meth == NULL) {
|
||||
/* Most likely attribute wasn't found. */
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (meth_found) {
|
||||
if (_PyObject_GetMethod(owner, name, &meth)) {
|
||||
/* We can bypass temporary bound method object.
|
||||
meth is unbound method and obj is self.
|
||||
|
||||
meth | self | arg1 | ... | argN
|
||||
*/
|
||||
SET_TOP(meth);
|
||||
PUSH(owner); // self
|
||||
assert(meth != NULL); // No errors on this branch
|
||||
res2 = meth;
|
||||
res = owner; // Transfer ownership
|
||||
}
|
||||
else {
|
||||
/* meth is not an unbound method (but a regular attr, or
|
||||
@ -1788,20 +1782,22 @@
|
||||
|
||||
NULL | meth | arg1 | ... | argN
|
||||
*/
|
||||
SET_TOP(NULL);
|
||||
Py_DECREF(owner);
|
||||
PUSH(meth);
|
||||
if (meth == NULL) goto pop_1_error;
|
||||
res2 = NULL;
|
||||
res = meth;
|
||||
}
|
||||
}
|
||||
else {
|
||||
PyObject *res = PyObject_GetAttr(owner, name);
|
||||
if (res == NULL) {
|
||||
goto error;
|
||||
}
|
||||
/* Classic, pushes one value. */
|
||||
res = PyObject_GetAttr(owner, name);
|
||||
Py_DECREF(owner);
|
||||
SET_TOP(res);
|
||||
if (res == NULL) goto pop_1_error;
|
||||
}
|
||||
JUMPBY(INLINE_CACHE_ENTRIES_LOAD_ATTR);
|
||||
STACK_GROW(((oparg & 1) ? 1 : 0));
|
||||
POKE(1, res);
|
||||
if (oparg & 1) { POKE(1 + ((oparg & 1) ? 1 : 0), res2); }
|
||||
JUMPBY(9);
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
|
@ -185,7 +185,7 @@ _PyOpcode_num_popped(int opcode, int oparg) {
|
||||
case MAP_ADD:
|
||||
return 2;
|
||||
case LOAD_ATTR:
|
||||
return -1;
|
||||
return 1;
|
||||
case LOAD_ATTR_INSTANCE_VALUE:
|
||||
return -1;
|
||||
case LOAD_ATTR_MODULE:
|
||||
@ -531,7 +531,7 @@ _PyOpcode_num_pushed(int opcode, int oparg) {
|
||||
case MAP_ADD:
|
||||
return 0;
|
||||
case LOAD_ATTR:
|
||||
return -1;
|
||||
return ((oparg & 1) ? 1 : 0) + 1;
|
||||
case LOAD_ATTR_INSTANCE_VALUE:
|
||||
return -1;
|
||||
case LOAD_ATTR_MODULE:
|
||||
@ -694,7 +694,7 @@ _PyOpcode_num_pushed(int opcode, int oparg) {
|
||||
}
|
||||
#endif
|
||||
enum Direction { DIR_NONE, DIR_READ, DIR_WRITE };
|
||||
enum InstructionFormat { INSTR_FMT_IB, INSTR_FMT_IBC, INSTR_FMT_IBC0, INSTR_FMT_IBC000, INSTR_FMT_IBIB, INSTR_FMT_IX, INSTR_FMT_IXC, INSTR_FMT_IXC000 };
|
||||
enum InstructionFormat { INSTR_FMT_IB, INSTR_FMT_IBC, INSTR_FMT_IBC0, INSTR_FMT_IBC000, INSTR_FMT_IBC00000000, INSTR_FMT_IBIB, INSTR_FMT_IX, INSTR_FMT_IXC, INSTR_FMT_IXC000 };
|
||||
struct opcode_metadata {
|
||||
enum Direction dir_op1;
|
||||
enum Direction dir_op2;
|
||||
@ -791,7 +791,7 @@ struct opcode_metadata {
|
||||
[DICT_UPDATE] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IB },
|
||||
[DICT_MERGE] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IB },
|
||||
[MAP_ADD] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IB },
|
||||
[LOAD_ATTR] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IB },
|
||||
[LOAD_ATTR] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IBC00000000 },
|
||||
[LOAD_ATTR_INSTANCE_VALUE] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IB },
|
||||
[LOAD_ATTR_MODULE] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IB },
|
||||
[LOAD_ATTR_WITH_HINT] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IB },
|
||||
|
@ -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():
|
||||
|
@ -48,9 +48,6 @@ class Node:
|
||||
context = self.context
|
||||
if not context:
|
||||
return ""
|
||||
tokens = context.owner.tokens
|
||||
begin = context.begin
|
||||
end = context.end
|
||||
return lx.to_text(self.tokens, dedent)
|
||||
|
||||
@property
|
||||
@ -74,13 +71,13 @@ class Block(Node):
|
||||
class StackEffect(Node):
|
||||
name: str
|
||||
type: str = "" # Optional `:type`
|
||||
cond: str = "" # Optional `if (cond)`
|
||||
size: str = "" # Optional `[size]`
|
||||
# Note: we can have type or size but not both
|
||||
# TODO: condition (can be combined with type but not size)
|
||||
# Note: size cannot be combined with type or cond
|
||||
|
||||
|
||||
@dataclass
|
||||
class Dimension(Node):
|
||||
class Expression(Node):
|
||||
size: str
|
||||
|
||||
|
||||
@ -239,31 +236,39 @@ class Parser(PLexer):
|
||||
|
||||
@contextual
|
||||
def stack_effect(self) -> StackEffect | None:
|
||||
# IDENTIFIER
|
||||
# | IDENTIFIER ':' IDENTIFIER
|
||||
# | IDENTIFIER '[' dimension ']'
|
||||
# TODO: Conditions
|
||||
# IDENTIFIER [':' IDENTIFIER] ['if' '(' expression ')']
|
||||
# | IDENTIFIER '[' expression ']'
|
||||
if tkn := self.expect(lx.IDENTIFIER):
|
||||
type_text = ""
|
||||
if self.expect(lx.COLON):
|
||||
typ = self.require(lx.IDENTIFIER)
|
||||
return StackEffect(tkn.text, typ.text)
|
||||
elif self.expect(lx.LBRACKET):
|
||||
if not (dim := self.dimension()):
|
||||
raise self.make_syntax_error("Expected dimension")
|
||||
type_text = self.require(lx.IDENTIFIER).text.strip()
|
||||
cond_text = ""
|
||||
if self.expect(lx.IF):
|
||||
self.require(lx.LPAREN)
|
||||
if not (cond := self.expression()):
|
||||
raise self.make_syntax_error("Expected condition")
|
||||
self.require(lx.RPAREN)
|
||||
cond_text = cond.text.strip()
|
||||
size_text = ""
|
||||
if self.expect(lx.LBRACKET):
|
||||
if type_text or cond_text:
|
||||
raise self.make_syntax_error("Unexpected [")
|
||||
if not (size := self.expression()):
|
||||
raise self.make_syntax_error("Expected expression")
|
||||
self.require(lx.RBRACKET)
|
||||
return StackEffect(tkn.text, "PyObject **", dim.text.strip())
|
||||
else:
|
||||
return StackEffect(tkn.text)
|
||||
type_text = "PyObject **"
|
||||
size_text = size.text.strip()
|
||||
return StackEffect(tkn.text, type_text, cond_text, size_text)
|
||||
|
||||
@contextual
|
||||
def dimension(self) -> Dimension | None:
|
||||
def expression(self) -> Expression | None:
|
||||
tokens: list[lx.Token] = []
|
||||
while (tkn := self.peek()) and tkn.kind != lx.RBRACKET:
|
||||
while (tkn := self.peek()) and tkn.kind not in (lx.RBRACKET, lx.RPAREN):
|
||||
tokens.append(tkn)
|
||||
self.next()
|
||||
if not tokens:
|
||||
return None
|
||||
return Dimension(lx.to_text(tokens).strip())
|
||||
return Expression(lx.to_text(tokens).strip())
|
||||
|
||||
@contextual
|
||||
def super_def(self) -> Super | None:
|
||||
@ -366,7 +371,7 @@ class Parser(PLexer):
|
||||
return None
|
||||
|
||||
@contextual
|
||||
def block(self) -> Block:
|
||||
def block(self) -> Block | None:
|
||||
if self.c_blob():
|
||||
return Block()
|
||||
|
||||
|
@ -9,19 +9,19 @@ from parser import StackEffect
|
||||
|
||||
def test_effect_sizes():
|
||||
input_effects = [
|
||||
x := StackEffect("x", "", ""),
|
||||
y := StackEffect("y", "", "oparg"),
|
||||
z := StackEffect("z", "", "oparg*2"),
|
||||
x := StackEffect("x", "", "", ""),
|
||||
y := StackEffect("y", "", "", "oparg"),
|
||||
z := StackEffect("z", "", "", "oparg*2"),
|
||||
]
|
||||
output_effects = [
|
||||
a := StackEffect("a", "", ""),
|
||||
b := StackEffect("b", "", "oparg*4"),
|
||||
c := StackEffect("c", "", ""),
|
||||
StackEffect("a", "", "", ""),
|
||||
StackEffect("b", "", "", "oparg*4"),
|
||||
StackEffect("c", "", "", ""),
|
||||
]
|
||||
other_effects = [
|
||||
p := StackEffect("p", "", "oparg<<1"),
|
||||
q := StackEffect("q", "", ""),
|
||||
r := StackEffect("r", "", ""),
|
||||
StackEffect("p", "", "", "oparg<<1"),
|
||||
StackEffect("q", "", "", ""),
|
||||
StackEffect("r", "", "", ""),
|
||||
]
|
||||
assert generate_cases.effect_size(x) == (1, "")
|
||||
assert generate_cases.effect_size(y) == (0, "oparg")
|
||||
@ -54,6 +54,12 @@ def run_cases_test(input: str, expected: str):
|
||||
while lines and lines[0].startswith("// "):
|
||||
lines.pop(0)
|
||||
actual = "".join(lines)
|
||||
# if actual.rstrip() != expected.rstrip():
|
||||
# print("Actual:")
|
||||
# print(actual)
|
||||
# print("Expected:")
|
||||
# print(expected)
|
||||
# print("End")
|
||||
assert actual.rstrip() == expected.rstrip()
|
||||
|
||||
def test_legacy():
|
||||
@ -475,3 +481,28 @@ def test_register():
|
||||
}
|
||||
"""
|
||||
run_cases_test(input, output)
|
||||
|
||||
def test_cond_effect():
|
||||
input = """
|
||||
inst(OP, (aa, input if (oparg & 1), cc -- xx, output if (oparg & 2), zz)) {
|
||||
output = spam(oparg, input);
|
||||
}
|
||||
"""
|
||||
output = """
|
||||
TARGET(OP) {
|
||||
PyObject *cc = PEEK(1);
|
||||
PyObject *input = (oparg & 1) ? PEEK(1 + ((oparg & 1) ? 1 : 0)) : NULL;
|
||||
PyObject *aa = PEEK(2 + ((oparg & 1) ? 1 : 0));
|
||||
PyObject *xx;
|
||||
PyObject *output = NULL;
|
||||
PyObject *zz;
|
||||
output = spam(oparg, input);
|
||||
STACK_SHRINK(((oparg & 1) ? 1 : 0));
|
||||
STACK_GROW(((oparg & 2) ? 1 : 0));
|
||||
POKE(1, zz);
|
||||
if (oparg & 2) { POKE(1 + ((oparg & 2) ? 1 : 0), output); }
|
||||
POKE(2 + ((oparg & 2) ? 1 : 0), xx);
|
||||
DISPATCH();
|
||||
}
|
||||
"""
|
||||
run_cases_test(input, output)
|
||||
|
Loading…
Reference in New Issue
Block a user