Skip to content

Commit 3c46ae9

Browse files
committed
Move super-instruction special-casing to generator
1 parent 1868f91 commit 3c46ae9

File tree

3 files changed

+78
-35
lines changed

3 files changed

+78
-35
lines changed

Python/opcode_metadata.h

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/optimizer.c

+10-33
Original file line numberDiff line numberDiff line change
@@ -424,35 +424,6 @@ translate_bytecode_to_trace(
424424
operand = (operand & 0xffffff00) | executor->vm_data.oparg;
425425
}
426426
switch (opcode) {
427-
case LOAD_FAST_LOAD_FAST:
428-
case STORE_FAST_LOAD_FAST:
429-
case STORE_FAST_STORE_FAST:
430-
{
431-
// Reserve space for two uops (+ SAVE_IP + EXIT_TRACE)
432-
if (trace_length + 4 > max_length) {
433-
DPRINTF(1, "Ran out of space for LOAD_FAST_LOAD_FAST\n");
434-
goto done;
435-
}
436-
uint64_t oparg1 = operand >> 4;
437-
uint64_t oparg2 = operand & 15;
438-
switch (opcode) {
439-
case LOAD_FAST_LOAD_FAST:
440-
ADD_TO_TRACE(LOAD_FAST, oparg1);
441-
ADD_TO_TRACE(LOAD_FAST, oparg2);
442-
break;
443-
case STORE_FAST_LOAD_FAST:
444-
ADD_TO_TRACE(STORE_FAST, oparg1);
445-
ADD_TO_TRACE(LOAD_FAST, oparg2);
446-
break;
447-
case STORE_FAST_STORE_FAST:
448-
ADD_TO_TRACE(STORE_FAST, oparg1);
449-
ADD_TO_TRACE(STORE_FAST, oparg2);
450-
break;
451-
default:
452-
Py_FatalError("Missing case");
453-
}
454-
break;
455-
}
456427
case JUMP_BACKWARD:
457428
{
458429
if (instr + 2 - operand == initial_instr
@@ -480,7 +451,7 @@ translate_bytecode_to_trace(
480451
for (int i = 0; i < nuops; i++) {
481452
int offset = expansion->uops[i].offset;
482453
switch (expansion->uops[i].size) {
483-
case 0:
454+
case OPARG_FULL:
484455
if (extras && OPCODE_HAS_JUMP(opcode)) {
485456
if (opcode == JUMP_BACKWARD_NO_INTERRUPT) {
486457
operand -= extras;
@@ -491,15 +462,21 @@ translate_bytecode_to_trace(
491462
}
492463
}
493464
break;
494-
case 1:
465+
case OPARG_CACHE_1:
495466
operand = read_u16(&instr[offset].cache);
496467
break;
497-
case 2:
468+
case OPARG_CACHE_2:
498469
operand = read_u32(&instr[offset].cache);
499470
break;
500-
case 4:
471+
case OPARG_CACHE_4:
501472
operand = read_u64(&instr[offset].cache);
502473
break;
474+
case OPARG_TOP: // First half of super-instr
475+
operand = operand >> 4;
476+
break;
477+
case OPARG_BOTTOM: // Second half of super-instr
478+
operand = operand & 0xF;
479+
break;
503480
default:
504481
fprintf(stderr,
505482
"opcode=%d, operand=%" PRIu64 "; nuops=%d, i=%d; size=%d, offset=%d\n",

Tools/cases_generator/generate_cases.py

+59-2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@
4040
UNUSED = "unused"
4141
BITS_PER_CODE_UNIT = 16
4242

43+
# Constants used instead of size for macro expansions.
44+
# Note: 1, 2, 4 must match actual cache entry sizes.
45+
OPARG_SIZES = {
46+
"OPARG_FULL": 0,
47+
"OPARG_CACHE_1": 1,
48+
"OPARG_CACHE_2": 2,
49+
"OPARG_CACHE_4": 4,
50+
"OPARG_TOP": 5,
51+
"OPARG_BOTTOM": 6,
52+
}
53+
4354
RESERVED_WORDS = {
4455
"co_consts" : "Use FRAME_CO_CONSTS.",
4556
"co_names": "Use FRAME_CO_NAMES.",
@@ -1218,7 +1229,10 @@ def write_metadata(self) -> None:
12181229
self.out.emit("struct { int16_t uop; int8_t size; int8_t offset; } uops[8];")
12191230
self.out.emit("")
12201231

1232+
for key, value in OPARG_SIZES.items():
1233+
self.out.emit(f"#define {key} {value}")
12211234
self.out.emit("")
1235+
12221236
self.out.emit("#define OPCODE_METADATA_FMT(OP) "
12231237
"(_PyOpcode_opcode_metadata[(OP)].instr_format)")
12241238
self.out.emit("#define SAME_OPCODE_METADATA(OP1, OP2) \\")
@@ -1268,6 +1282,9 @@ def write_metadata(self) -> None:
12681282
# Construct a dummy Component -- input/output mappings are not used
12691283
part = Component(instr, [], [], instr.active_caches)
12701284
self.write_macro_expansions(instr.name, [part])
1285+
elif instr.kind == "inst" and variable_used(instr.inst, "oparg1"):
1286+
assert variable_used(instr.inst, "oparg2"), "Half super-instr?"
1287+
self.write_super_expansions(instr.name)
12711288
case parser.Macro():
12721289
mac = self.macro_instrs[thing.name]
12731290
self.write_macro_expansions(mac.name, mac.parts)
@@ -1348,18 +1365,58 @@ def write_macro_expansions(self, name: str, parts: MacroParts) -> None:
13481365
print(f"NOTE: Part {part.instr.name} of {name} is not a viable uop")
13491366
return
13501367
if part.instr.instr_flags.HAS_ARG_FLAG or not part.active_caches:
1351-
size, offset = 0, 0
1368+
size, offset = OPARG_SIZES["OPARG_FULL"], 0
13521369
else:
13531370
# If this assert triggers, is_viable_uops() lied
13541371
assert len(part.active_caches) == 1, (name, part.instr.name)
13551372
cache = part.active_caches[0]
13561373
size, offset = cache.effect.size, cache.offset
13571374
expansions.append((part.instr.name, size, offset))
13581375
assert len(expansions) > 0, f"Macro {name} has empty expansion?!"
1376+
self.write_expansions(name, expansions)
1377+
1378+
def write_super_expansions(self, name: str) -> None:
1379+
"""Write special macro expansions for super-instructions.
1380+
1381+
If you get an assertion failure here, you probably have accidentally
1382+
violated one of the assumptions here.
1383+
1384+
- A super-instruction's name is of the form FIRST_SECOND where
1385+
FIRST and SECOND are regular instructions whose name has the
1386+
form FOO_BAR. Thus, there must be exactly 3 underscores.
1387+
Example: LOAD_CONST_STORE_FAST.
1388+
1389+
- A super-instruction's body uses `oparg1 and `oparg2`, and no
1390+
other instruction's body uses those variable names.
1391+
1392+
- A super-instruction has no active (used) cache entries.
1393+
1394+
In the expansion, the first instruction's operand is all but the
1395+
bottom 4 bits of the super-instruction's oparg, and the second
1396+
instruction's operand is the bottom 4 bits. We use the special
1397+
size codes OPARG_TOP and OPARG_BOTTOM for these.
1398+
"""
1399+
pieces = name.split("_")
1400+
assert len(pieces) == 4, f"{name} doesn't look like a super-instr"
1401+
name1 = "_".join(pieces[:2])
1402+
name2 = "_".join(pieces[2:])
1403+
assert name1 in self.instrs, f"{name1} doesn't match any instr"
1404+
assert name2 in self.instrs, f"{name2} doesn't match any instr"
1405+
instr1 = self.instrs[name1]
1406+
instr2 = self.instrs[name2]
1407+
assert not instr1.active_caches, f"{name1} has active caches"
1408+
assert not instr2.active_caches, f"{name2} has active caches"
1409+
expansions = [
1410+
(name1, OPARG_SIZES["OPARG_TOP"], 0),
1411+
(name2, OPARG_SIZES["OPARG_BOTTOM"], 0),
1412+
]
1413+
self.write_expansions(name, expansions)
1414+
1415+
def write_expansions(self, name: str, expansions: list[tuple[str, int, int]]) -> None:
13591416
pieces = [f"{{ {name}, {size}, {offset} }}" for name, size, offset in expansions]
13601417
self.out.emit(
13611418
f"[{name}] = "
1362-
f"{{ .nuops = {len(expansions)}, .uops = {{ {', '.join(pieces)} }} }},"
1419+
f"{{ .nuops = {len(pieces)}, .uops = {{ {', '.join(pieces)} }} }},"
13631420
)
13641421

13651422
def emit_metadata_entry(

0 commit comments

Comments
 (0)