Skip to content

Commit f52497b

Browse files
committed
Add fast path for pure binary integer operations in JIT execution
1 parent fc9d01a commit f52497b

File tree

5 files changed

+99
-15
lines changed

5 files changed

+99
-15
lines changed

rpython/jit/codewriter/genextension.py

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from rpython.jit.metainterp.history import (Const, ConstInt, ConstPtr,
55
ConstFloat, CONST_NULL, getkind, AbstractDescr)
66
from rpython.jit.metainterp import support
7+
from rpython.jit.metainterp.resoperation import rop
78
from rpython.flowspace.model import Constant
89
from rpython.jit.codewriter.flatten import (
910
Register, TLabel, Label, ListOfKind, IndirectCallTargets)
@@ -131,7 +132,7 @@ def generate(self):
131132
full_code = _apply_peephole_optimizations(full_code)
132133
self.jitcode._genext_source = full_code
133134
d = {"ConstInt": ConstInt, "ConstPtr": ConstPtr, "ConstFloat": ConstFloat, "JitCode": JitCode, "ChangeFrame": ChangeFrame,
134-
"lltype": lltype, "rstr": rstr, 'llmemory': llmemory, 'OBJECTPTR': OBJECTPTR, 'support': support}
135+
"lltype": lltype, "rstr": rstr, 'llmemory': llmemory, 'OBJECTPTR': OBJECTPTR, 'support': support, 'rop': rop}
135136
d.update(self.globals)
136137
source = py.code.Source(self.jitcode._genext_source)
137138
exec source.compile() in d
@@ -2818,6 +2819,52 @@ def _emit_unspecialized_binary(self):
28182819
self._emit_jump(lines)
28192820
return lines
28202821

2822+
# Mapping from operation name to rop constant for pure binary int ops
2823+
_PURE_BINARY_ROP = {
2824+
'int_add': 'rop.INT_ADD',
2825+
'int_sub': 'rop.INT_SUB',
2826+
'int_mul': 'rop.INT_MUL',
2827+
'int_and': 'rop.INT_AND',
2828+
'int_or': 'rop.INT_OR',
2829+
'int_xor': 'rop.INT_XOR',
2830+
'int_lshift': 'rop.INT_LSHIFT',
2831+
'int_rshift': 'rop.INT_RSHIFT',
2832+
'int_lt': 'rop.INT_LT',
2833+
'int_le': 'rop.INT_LE',
2834+
'int_gt': 'rop.INT_GT',
2835+
'int_ge': 'rop.INT_GE',
2836+
'int_eq': 'rop.INT_EQ',
2837+
'int_ne': 'rop.INT_NE',
2838+
}
2839+
2840+
def _emit_unspecialized_pure_binary_i(self):
2841+
"""Fast path for pure integer binary operations.
2842+
2843+
Uses MetaInterp.execute_and_record_pure_i() which bypasses:
2844+
- Method lookup chain (opimpl_* -> execute -> execute_and_record)
2845+
- Unnecessary constant-fold checks (we already know args aren't const)
2846+
- Heapcache invalidation (pure ops can't affect it)
2847+
"""
2848+
lines = []
2849+
arg0, arg1, result = self._get_args_and_res()
2850+
self._emit_n_ary_if([arg0, arg1], lines)
2851+
self._emit_jump(lines, constant_registers=self.constant_registers.union({arg0, arg1}),
2852+
indent=' ', target_pc=self.orig_pc)
2853+
lines.append("else:")
2854+
# Use fast path: direct execute_and_record_pure_i
2855+
rop_const = self._PURE_BINARY_ROP.get(self.name)
2856+
if rop_const is not None:
2857+
lines.append(" self.registers_i[%d] = self.metainterp.execute_and_record_pure_i(%s, %s, %s)" % (
2858+
result.index, rop_const,
2859+
self._get_as_box(arg0), self._get_as_box(arg1)))
2860+
else:
2861+
# Fallback to opimpl for operations not in fast-path list
2862+
lines.append(" self.registers_i[%d] = self.%s(%s, %s)" % (
2863+
result.index, self.methodname,
2864+
self._get_as_box(arg0), self._get_as_box(arg1)))
2865+
self._emit_jump(lines)
2866+
return lines
2867+
28212868
def _emit_unspecialized_float_binary(self):
28222869
lines = []
28232870
arg0, arg1, result = self._get_args_and_res()
@@ -2831,20 +2878,22 @@ def _emit_unspecialized_float_binary(self):
28312878
self._emit_jump(lines)
28322879
return lines
28332880

2834-
emit_unspecialized_int_add = _emit_unspecialized_binary
2835-
emit_unspecialized_int_sub = _emit_unspecialized_binary
2836-
emit_unspecialized_int_mul = _emit_unspecialized_binary
2837-
emit_unspecialized_int_or = _emit_unspecialized_binary
2838-
emit_unspecialized_int_and = _emit_unspecialized_binary
2839-
emit_unspecialized_int_rshift = _emit_unspecialized_binary
2840-
emit_unspecialized_int_lshift = _emit_unspecialized_binary
2841-
emit_unspecialized_int_le = _emit_unspecialized_binary
2842-
emit_unspecialized_int_lt = _emit_unspecialized_binary
2843-
emit_unspecialized_int_ge = _emit_unspecialized_binary
2844-
emit_unspecialized_int_gt = _emit_unspecialized_binary
2845-
emit_unspecialized_int_eq = _emit_unspecialized_binary
2846-
emit_unspecialized_int_ne = _emit_unspecialized_binary
2847-
emit_unspecialized_int_xor = _emit_unspecialized_binary
2881+
# Pure integer binary ops use fast path (skip method chain overhead)
2882+
emit_unspecialized_int_add = _emit_unspecialized_pure_binary_i
2883+
emit_unspecialized_int_sub = _emit_unspecialized_pure_binary_i
2884+
emit_unspecialized_int_mul = _emit_unspecialized_pure_binary_i
2885+
emit_unspecialized_int_or = _emit_unspecialized_pure_binary_i
2886+
emit_unspecialized_int_and = _emit_unspecialized_pure_binary_i
2887+
emit_unspecialized_int_rshift = _emit_unspecialized_pure_binary_i
2888+
emit_unspecialized_int_lshift = _emit_unspecialized_pure_binary_i
2889+
emit_unspecialized_int_le = _emit_unspecialized_pure_binary_i
2890+
emit_unspecialized_int_lt = _emit_unspecialized_pure_binary_i
2891+
emit_unspecialized_int_ge = _emit_unspecialized_pure_binary_i
2892+
emit_unspecialized_int_gt = _emit_unspecialized_pure_binary_i
2893+
emit_unspecialized_int_eq = _emit_unspecialized_pure_binary_i
2894+
emit_unspecialized_int_ne = _emit_unspecialized_pure_binary_i
2895+
emit_unspecialized_int_xor = _emit_unspecialized_pure_binary_i
2896+
# int_mod and int_floordiv use old path (can raise ZeroDivisionError)
28482897
emit_unspecialized_int_mod = _emit_unspecialized_binary
28492898
emit_unspecialized_int_floordiv = _emit_unspecialized_binary
28502899

rpython/jit/metainterp/heapcache.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,13 +233,19 @@ def update_version(self, ref_frontend_op):
233233
ref_frontend_op._heapc_deps = None
234234

235235
def invalidate_caches_varargs(self, opnum, descr, argboxes):
236+
# Pure operations cannot escape objects or invalidate caches - skip entirely
237+
if rop.is_always_pure(opnum):
238+
return
236239
self.mark_escaped_varargs(opnum, descr, argboxes)
237240
if self.clear_caches_not_necessary(opnum, descr):
238241
return
239242
self.clear_caches_varargs(opnum, descr, argboxes)
240243

241244
@specialize.arg(1)
242245
def invalidate_caches(self, opnum, descr, *argboxes):
246+
# Pure operations cannot escape objects or invalidate caches - skip entirely
247+
if rop.is_always_pure(opnum):
248+
return
243249
self.mark_escaped(opnum, descr, *argboxes)
244250
if self.clear_caches_not_necessary(opnum, descr):
245251
return

rpython/jit/metainterp/jitprof.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ def _print_stats(self):
243243
self._print_intline("forcings", cnt[Counters.OPT_FORCINGS])
244244
self._print_intline("slow tracing function executions", cnt[Counters.SLOW_TRACING_FUNCTION_EXECUTIONS])
245245
self._print_intline("fast tracing function executions", cnt[Counters.FAST_TRACING_FUNCTION_EXECUTIONS])
246+
self._print_intline("pure binary fastpath ops", cnt[Counters.PURE_BINARY_FASTPATH_OPS])
246247
self._print_intline("abort: trace too long",
247248
cnt[Counters.ABORT_TOO_LONG])
248249
self._print_intline("abort: compiling", cnt[Counters.ABORT_BRIDGE])

rpython/jit/metainterp/pyjitpl.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2811,6 +2811,33 @@ def _record_helper(self, opnum, resvalue, descr, *argboxes):
28112811
if op.type != 'v':
28122812
return op
28132813

2814+
@specialize.arg(1)
2815+
def execute_and_record_pure_i(self, opnum, box0, box1):
2816+
"""Fast path for pure integer binary operations.
2817+
2818+
Bypasses unnecessary overhead for operations that:
2819+
- Are always pure (no side effects)
2820+
- Have no descr
2821+
- Cannot raise exceptions
2822+
- Return integer result
2823+
2824+
Used by GenExtension for operations like INT_ADD, INT_SUB, etc.
2825+
"""
2826+
# Execute the operation
2827+
profiler = self.staticdata.profiler
2828+
profiler.count_ops(opnum)
2829+
profiler.count(Counters.PURE_BINARY_FASTPATH_OPS) # Track fast-path usage
2830+
resvalue = executor.execute(self.cpu, self, opnum, None, box0, box1)
2831+
# Record directly - skip heapcache (pure ops don't affect it)
2832+
# and skip constant fold check (caller already verified non-const)
2833+
profiler.count_ops(opnum, Counters.RECORDED_OPS)
2834+
# heapcache.invalidate_caches is skipped for pure ops (returns early)
2835+
if self.framestack:
2836+
self.framestack[-1].jitcode.traced_operations += 1
2837+
op = self.history.record2(opnum, box0, box1, resvalue, None)
2838+
self.attach_debug_info(op)
2839+
return op
2840+
28142841
def execute_new_with_vtable(self, descr):
28152842
resbox = self.execute_and_record(rop.NEW_WITH_VTABLE, descr)
28162843
self.heapcache.new(resbox)

rpython/rlib/jit.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,6 +1444,7 @@ class Counters(object):
14441444
NVREUSED
14451445
FAST_TRACING_FUNCTION_EXECUTIONS
14461446
SLOW_TRACING_FUNCTION_EXECUTIONS
1447+
PURE_BINARY_FASTPATH_OPS
14471448
TOTAL_COMPILED_LOOPS
14481449
TOTAL_COMPILED_BRIDGES
14491450
TOTAL_FREED_LOOPS

0 commit comments

Comments
 (0)