From 32d36529fd0fbad24e746c4fb69ef6f77d7f46ba Mon Sep 17 00:00:00 2001 From: stepan Date: Mon, 9 Feb 2026 20:24:36 +0100 Subject: [PATCH 1/2] Fix PE with tracing, add tracing benchmark --- .../python/meso/chaos-traced.py | 283 ++++++++++++++++++ .../bytecode_dsl/PBytecodeDSLRootNode.java | 2 +- .../nodes/frame/GetFrameLocalsNode.java | 8 +- mx.graalpython/copyrights/overrides | 1 + mx.graalpython/mx_graalpython_bench_param.py | 3 +- 5 files changed, 294 insertions(+), 3 deletions(-) create mode 100644 graalpython/com.oracle.graal.python.benchmarks/python/meso/chaos-traced.py diff --git a/graalpython/com.oracle.graal.python.benchmarks/python/meso/chaos-traced.py b/graalpython/com.oracle.graal.python.benchmarks/python/meso/chaos-traced.py new file mode 100644 index 0000000000..92d05ddb5f --- /dev/null +++ b/graalpython/com.oracle.graal.python.benchmarks/python/meso/chaos-traced.py @@ -0,0 +1,283 @@ +# Copyright (c) 2020, 2026, Oracle and/or its affiliates. +# Copyright (c) 2017, The PyPy Project +# +# The MIT License +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +# Copyright (C) 2005, Carl Friedrich Bolz +# Variant of the chaos-sized2 benchmark with tracing enabled + +"""create chaosgame-like fractals +""" + +from __future__ import division, print_function + +import sys +import operator +import random +import math +from functools import reduce + +random.seed(1234) + +class GVector(object): + def __init__(self, x = 0, y = 0, z = 0): + self.x = x + self.y = y + self.z = z + + def Mag(self): + return math.sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2) + + def dist(self, other): + return math.sqrt((self.x - other.x) ** 2 + + (self.y - other.y) ** 2 + + (self.z - other.z) ** 2) + + def __add__(self, other): + if not isinstance(other, GVector): + raise ValueError( "Can't add GVector to " + str(type(other))) + v = GVector(self.x + other.x, self.y + other.y, self.z + other.z) + return v + + def __sub__(self, other): + return self + other * -1 + + def __mul__(self, other): + v = GVector(self.x * other, self.y * other, self.z * other) + return v + __rmul__ = __mul__ + + def linear_combination(self, other, l1, l2=None): + if l2 is None: + l2 = 1 - l1 + v = GVector(self.x * l1 + other.x * l2, + self.y * l1 + other.y * l2, + self.z * l1 + other.z * l2) + return v + + + def __str__(self): + return "<%f, %f, %f>" % (self.x, self.y, self.z) + + def __repr__(self): + return "GVector(%f, %f, %f)" % (self.x, self.y, self.z) + +def GetKnots(points, degree): + knots = [0] * degree + range(1, len(points) - degree) + knots += [len(points) - degree] * degree + return knots + +class Spline(object): + """Class for representing B-Splines and NURBS of arbitrary degree""" + def __init__(self, points, degree = 3, knots = None): + """Creates a Spline. points is a list of GVector, degree is the +degree of the Spline.""" + if knots == None: + self.knots = GetKnots(points, degree) + else: + if len(points) > len(knots) - degree + 1: + raise ValueError("too many control points") + elif len(points) < len(knots) - degree + 1: + raise ValueError("not enough control points") + last = knots[0] + for cur in knots[1:]: + if cur < last: + raise ValueError("knots not strictly increasing") + last = cur + self.knots = knots + self.points = points + self.degree = degree + + def GetDomain(self): + """Returns the domain of the B-Spline""" + return (self.knots[self.degree - 1], + self.knots[len(self.knots) - self.degree]) + + def __call__(self, u): + """Calculates a point of the B-Spline using de Boors Algorithm""" + dom = self.GetDomain() + if u < dom[0] or u > dom[1]: + raise ValueError("Function value not in domain") + if u == dom[0]: + return self.points[0] + if u == dom[1]: + return self.points[-1] + I = self.GetIndex(u) + d = [self.points[I - self.degree + 1 + ii] + for ii in range(self.degree + 1)] + U = self.knots + for ik in range(1, self.degree + 1): + for ii in range(I - self.degree + ik + 1, I + 2): + ua = U[ii + self.degree - ik] + ub = U[ii - 1] + co1 = (ua - u) / (ua - ub) + co2 = (u - ub) / (ua - ub) + index = ii - I + self.degree - ik - 1 + d[index] = d[index].linear_combination(d[index + 1], co1, co2) + return d[0] + + def GetIndex(self, u): + dom = self.GetDomain() + for ii in range(self.degree - 1, len(self.knots) - self.degree): + if u >= self.knots[ii] and u < self.knots[ii + 1]: + I = ii + break + else: + I = dom[1] - 1 + return I + + def __len__(self): + return len(self.points) + + def __repr__(self): + return "Spline(%r, %r, %r)" % (self.points, self.degree, self.knots) + + +class Chaosgame(object): + def __init__(self, splines, thickness=0.1): + self.splines = splines + self.thickness = thickness + self.minx = min([p.x for spl in splines for p in spl.points]) + self.miny = min([p.y for spl in splines for p in spl.points]) + self.maxx = max([p.x for spl in splines for p in spl.points]) + self.maxy = max([p.y for spl in splines for p in spl.points]) + self.height = self.maxy - self.miny + self.width = self.maxx - self.minx + self.num_trafos = [] + maxlength = thickness * self.width / self.height + for spl in splines: + length = 0 + curr = spl(0) + for i in range(1, 1000): + last = curr + t = 1 / 999 * i + curr = spl(t) + length += curr.dist(last) + self.num_trafos.append(max(1, int(length / maxlength * 1.5))) + self.num_total = reduce(operator.add, self.num_trafos, 0) + + + def get_random_trafo(self): + r = random.randrange(int(self.num_total) + 1) + l = 0 + for i in range(len(self.num_trafos)): + if r >= l and r < l + self.num_trafos[i]: + return i, random.randrange(self.num_trafos[i]) + l += self.num_trafos[i] + return len(self.num_trafos) - 1, random.randrange(self.num_trafos[-1]) + + def transform_point(self, point, trafo=None): + x = (point.x - self.minx) / self.width + y = (point.y - self.miny) / self.height + if trafo is None: + trafo = self.get_random_trafo() + start, end = self.splines[trafo[0]].GetDomain() + length = end - start + seg_length = length / self.num_trafos[trafo[0]] + t = start + seg_length * trafo[1] + seg_length * x + basepoint = self.splines[trafo[0]](t) + if t + 1/50000 > end: + neighbour = self.splines[trafo[0]](t - 1/50000) + derivative = neighbour - basepoint + else: + neighbour = self.splines[trafo[0]](t + 1/50000) + derivative = basepoint - neighbour + if derivative.Mag() != 0: + basepoint.x += derivative.y / derivative.Mag() * (y - 0.5) * \ + self.thickness + basepoint.y += -derivative.x / derivative.Mag() * (y - 0.5) * \ + self.thickness + else: + print("r", end='') # isn't really executed... + self.truncate(basepoint) + return basepoint + + def truncate(self, point): + if point.x >= self.maxx: + point.x = self.maxx + if point.y >= self.maxy: + point.y = self.maxy + if point.x < self.minx: + point.x = self.minx + if point.y < self.miny: + point.y = self.miny + + def create_image_chaos(self, w, h, n): + im = [[1] * h for i in range(w)] + point = GVector((self.maxx + self.minx) / 2, + (self.maxy + self.miny) / 2, 0) + colored = 0 + for _ in range(n): + for i in range(5000): + point = self.transform_point(point) + x = (point.x - self.minx) / self.width * w + y = (point.y - self.miny) / self.height * h + x = int(x) + y = int(y) + if x == w: + x -= 1 + if y == h: + y -= 1 + im[x][h - y - 1] = 0 + + +class Data: + def __init__(self): + self.c = None + +data = Data() + +def __setup__(iterations=10): + splines = [ + Spline([ + GVector(1.597350, 3.304460, 0.000000), + GVector(1.575810, 4.123260, 0.000000), + GVector(1.313210, 5.288350, 0.000000), + GVector(1.618900, 5.329910, 0.000000), + GVector(2.889940, 5.502700, 0.000000), + GVector(2.373060, 4.381830, 0.000000), + GVector(1.662000, 4.360280, 0.000000)], + 3, [0, 0, 0, 1, 1, 1, 2, 2, 2]), + Spline([ + GVector(2.804500, 4.017350, 0.000000), + GVector(2.550500, 3.525230, 0.000000), + GVector(1.979010, 2.620360, 0.000000), + GVector(1.979010, 2.620360, 0.000000)], + 3, [0, 0, 0, 1, 1, 1]), + Spline([ + GVector(2.001670, 4.011320, 0.000000), + GVector(2.335040, 3.312830, 0.000000), + GVector(2.366800, 3.233460, 0.000000), + GVector(2.366800, 3.233460, 0.000000)], + 3, [0, 0, 0, 1, 1, 1]) + ] + data.c = Chaosgame(splines, 0.25) + +def trace_fun(frame,event,arg): + return trace_fun + +def main(iterations=10, w=1000, h=1200): + sys.settrace(trace_fun) + data.c.create_image_chaos(w, h, iterations) + +def __benchmark__(iterations=10): + main(iterations) + diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java index 05c420cbcc..c537b60427 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java @@ -387,7 +387,7 @@ private static final class TracingNodes extends Node { } // Not a child of this root, adopted by the BytecodeNode - private transient TracingNodes tracingNodes; + @CompilationFinal private transient TracingNodes tracingNodes; // These fields are effectively final, but can only be set after construction. @CompilationFinal protected transient BytecodeDSLCodeUnit co; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/GetFrameLocalsNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/GetFrameLocalsNode.java index 2cc82aab5d..4bb6dae2a3 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/GetFrameLocalsNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/GetFrameLocalsNode.java @@ -51,12 +51,14 @@ import com.oracle.graal.python.compiler.CodeUnit; import com.oracle.graal.python.lib.PyDictGetItem; import com.oracle.graal.python.nodes.bytecode.FrameInfo; +import com.oracle.graal.python.nodes.bytecode_dsl.BytecodeDSLCodeUnit; import com.oracle.graal.python.nodes.bytecode_dsl.BytecodeDSLFrameInfo; import com.oracle.graal.python.nodes.frame.GetFrameLocalsNodeGen.CopyDSLLocalsToDictNodeGen; import com.oracle.graal.python.nodes.frame.GetFrameLocalsNodeGen.CopyLocalsToDictNodeGen; import com.oracle.graal.python.runtime.CallerFlags; import com.oracle.graal.python.runtime.PythonOptions; import com.oracle.graal.python.runtime.object.PFactory; +import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.bytecode.BytecodeFrame; import com.oracle.truffle.api.bytecode.BytecodeNode; @@ -245,7 +247,8 @@ public static void syncLocalsBackToFrame(CodeUnit co, PFrame pyFrame, Frame loca /** * Equivalent of CPython's {@code PyFrame_LocalsToFast} */ - public static void syncLocalsBackToFrame(CodeUnit co, BytecodeNode bytecodeNode, PFrame pyFrame, Frame localFrame) { + public static void syncLocalsBackToFrame(BytecodeDSLCodeUnit co, BytecodeNode bytecodeNode, PFrame pyFrame, Frame localFrame) { + CompilerAsserts.partialEvaluationConstant(co); if (!pyFrame.hasCustomLocals()) { PDict localsDict = (PDict) pyFrame.getLocalsDict(); copyLocalsArray(localFrame, bytecodeNode, localsDict, co.varnames, 0, false); @@ -254,7 +257,10 @@ public static void syncLocalsBackToFrame(CodeUnit co, BytecodeNode bytecodeNode, } } + @ExplodeLoop private static void copyLocalsArray(Frame localFrame, BytecodeNode bytecodeNode, PDict localsDict, TruffleString[] namesArray, int offset, boolean deref) { + CompilerAsserts.partialEvaluationConstant(namesArray); + CompilerAsserts.partialEvaluationConstant(offset); for (int i = 0; i < namesArray.length; i++) { TruffleString varname = namesArray[i]; Object value = getDictItemUncached(localsDict, varname); diff --git a/mx.graalpython/copyrights/overrides b/mx.graalpython/copyrights/overrides index d58d981571..24cdf56daf 100644 --- a/mx.graalpython/copyrights/overrides +++ b/mx.graalpython/copyrights/overrides @@ -7,6 +7,7 @@ graalpython/com.oracle.graal.python.benchmarks/python/meso/ai-nqueen.py,benchmar graalpython/com.oracle.graal.python.benchmarks/python/meso/binarytrees3.py,benchmarks.copyright graalpython/com.oracle.graal.python.benchmarks/python/meso/bm-float.py,benchmarks.copyright graalpython/com.oracle.graal.python.benchmarks/python/meso/chaos-sized2.py,pypy.copyright +graalpython/com.oracle.graal.python.benchmarks/python/meso/chaos-traced.py,pypy.copyright graalpython/com.oracle.graal.python.benchmarks/python/meso/chaos.py,pypy.copyright graalpython/com.oracle.graal.python.benchmarks/python/meso/euler11.py,benchmarks.copyright graalpython/com.oracle.graal.python.benchmarks/python/meso/euler31.py,benchmarks.copyright diff --git a/mx.graalpython/mx_graalpython_bench_param.py b/mx.graalpython/mx_graalpython_bench_param.py index 473a357994..5f84f836bd 100644 --- a/mx.graalpython/mx_graalpython_bench_param.py +++ b/mx.graalpython/mx_graalpython_bench_param.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017, 2025, Oracle and/or its affiliates. +# Copyright (c) 2017, 2026, Oracle and/or its affiliates. # Copyright (c) 2013, Regents of the University of California # # All rights reserved. @@ -256,6 +256,7 @@ def _pickling_benchmarks(module='pickle'): 'regexdna-sized2': ITER_25 + ['4'], 'knucleotide': ITER_25 + [], 'chaos-sized2': ITER_10 + ['500'], + 'chaos-traced': ITER_5 + ['500'], 'go-sized2': ITER_15 + ['50'], 'raytrace-simple': ITER_10 + [], 'lud-sized2': ITER_10 + ['1536'], From 57ff112fb8c128755a1c5df8809fe34c3e4c77a4 Mon Sep 17 00:00:00 2001 From: stepan Date: Thu, 29 Jan 2026 23:48:53 +0100 Subject: [PATCH 2/2] Improve assertion messages --- .../objects/traceback/TracebackBuiltins.java | 30 +++++++++++++++---- .../oracle/graal/python/util/PythonUtils.java | 4 ++- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/traceback/TracebackBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/traceback/TracebackBuiltins.java index cd841e063c..86d74e3279 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/traceback/TracebackBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/traceback/TracebackBuiltins.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. * Copyright (c) 2014, Regents of the University of California * * All rights reserved. @@ -34,6 +34,7 @@ import static com.oracle.graal.python.builtins.objects.traceback.PTraceback.J_TB_NEXT; import static com.oracle.graal.python.nodes.SpecialMethodNames.J___DIR__; +import java.util.Arrays; import java.util.List; import com.oracle.graal.python.PythonLanguage; @@ -64,6 +65,7 @@ import com.oracle.graal.python.nodes.function.builtins.PythonClinicBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentClinicProvider; import com.oracle.graal.python.runtime.PythonOptions; +import com.oracle.graal.python.runtime.exception.ExceptionUtils; import com.oracle.graal.python.runtime.exception.PException; import com.oracle.graal.python.runtime.exception.PythonErrorType; import com.oracle.graal.python.runtime.object.PFactory; @@ -195,7 +197,7 @@ static void doMaterialize(Node inliningTarget, PTraceback tb, for (int truffleIndex = pException.getTracebackStartIndex(), pyIndex = 0; truffleIndex < stackTrace.size() && pyIndex < pException.getTracebackFrameCount(); truffleIndex++) { TruffleStackTraceElement element = stackTrace.get(truffleIndex); if (LazyTraceback.elementWantedForTraceback(element)) { - PFrame pFrame = materializeFrame(element, materializeFrameNode); + PFrame pFrame = materializeFrame(element, materializeFrameNode, pException, stackTrace); next = PFactory.createTraceback(language, pFrame, pFrame.getLine(), next); next.setLocation(pFrame.getBci(), pFrame.getBytecodeNode()); pyIndex++; @@ -213,7 +215,7 @@ static void doMaterialize(Node inliningTarget, PTraceback tb, tb.markMaterialized(); // Marks the Truffle stacktrace part as materialized } - private static PFrame materializeFrame(TruffleStackTraceElement element, MaterializeFrameNode materializeFrameNode) { + private static PFrame materializeFrame(TruffleStackTraceElement element, MaterializeFrameNode materializeFrameNode, PException pException, List stackTrace) { Node location = element.getLocation(); RootNode rootNode = element.getTarget().getRootNode(); if (rootNode instanceof PBytecodeRootNode || rootNode instanceof PBytecodeGeneratorRootNode) { @@ -222,7 +224,9 @@ private static PFrame materializeFrame(TruffleStackTraceElement element, Materia // We must have a callNode for Bytecode DSL root nodes. If this assertion fires, // it can be because of missing BoundaryCallContext.enter/exit around // @TruffleBoundary calls that may call back into Python code. - assert !PythonOptions.ENABLE_BYTECODE_DSL_INTERPRETER || !(unwrapContinuationRoot(rootNode) instanceof PBytecodeDSLRootNode) || location != null : rootNode; + assert !PythonOptions.ENABLE_BYTECODE_DSL_INTERPRETER || !(unwrapContinuationRoot(rootNode) instanceof PBytecodeDSLRootNode) || + location != null : String.format("root: %s, frame count: %d, start frame index: %d, stack trace:\n\n%s", + rootNode, pException.getTracebackFrameCount(), pException.getTracebackStartIndex(), Arrays.toString(stackTrace.toArray())); // create the PFrame and refresh frame values Frame frame = PGenerator.unwrapDSLGeneratorFrame(element); return materializeFrameNode.execute(location, true, true, frame); @@ -264,15 +268,29 @@ static PFrame doMaterializedFrame(PTraceback tb) { static PFrame doOnStack(VirtualFrame frame, PTraceback tb, @Cached ReadFrameNode readCallerFrame) { Reference frameInfo = tb.getFrameInfo(); - assert frameInfo.isEscaped() : "cannot create traceback for non-escaped frame"; + assert frameInfo.isEscaped() : createAssertionMessage("Cannot create traceback for non-escaped frame", frameInfo); PFrame escapedFrame = readCallerFrame.getFrameForReference(frame, frameInfo, ReadFrameNode.AllPythonFramesSelector.INSTANCE, 0, 0); - assert escapedFrame != null : "Failed to find escaped frame on stack"; + assert escapedFrame != null : createAssertionMessage("Failed to find escaped frame on stack", frameInfo); tb.setFrame(escapedFrame); return escapedFrame; } + @TruffleBoundary + private static String createAssertionMessage(String prefix, Reference frameInfo) { + String stackTrace; + try { + stackTrace = ExceptionUtils.getPythonLikeStackTrace(); + } catch (Throwable ex) { + stackTrace = "Exception while getting the Python stack trace: " + ex; + } + boolean isCurrentThread = frameInfo.getPyFrame().getThread() != null && + frameInfo.getPyFrame().getThread() != Thread.currentThread(); + return String.format("%s. Frame reference: root node='%s', is current thread=%b. Current stack trace:\n%s.", + prefix, frameInfo.getRootNode(), isCurrentThread, stackTrace); + } + // case 3: there is no PFrame[Ref], we need to take the top frame from the Truffle // stacktrace instead @Specialization(guards = "!hasVisibleFrame(tb)") diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/PythonUtils.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/PythonUtils.java index 6515d828d1..1318c06068 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/PythonUtils.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/PythonUtils.java @@ -763,7 +763,9 @@ public static PBuiltinFunction createMethod(PythonLanguage language, Object klas @TruffleBoundary public static PBuiltinFunction createMethod(Object klass, Builtin builtin, RootCallTarget callTarget, Object type, int numDefaults) { - assert callTarget.getRootNode() instanceof BuiltinFunctionRootNode r && r.getBuiltin() == builtin; + RootNode rootNode = callTarget.getRootNode(); + assert rootNode instanceof BuiltinFunctionRootNode : String.format("root: %s, builtin: %s, klass: %s", rootNode, builtin, klass); + assert ((BuiltinFunctionRootNode) rootNode).getBuiltin() == builtin : String.format("%s != %s, klass: %s", ((BuiltinFunctionRootNode) rootNode).getBuiltin(), builtin, klass); int flags = PBuiltinFunction.getFlags(builtin, callTarget); TruffleString name = toInternedTruffleStringUncached(builtin.name()); PBuiltinFunction function = PFactory.createBuiltinFunction(PythonLanguage.get(null), name, type, numDefaults, flags, callTarget);