Aquileo | PyPyhttps://www.pypy.org/A Faster PythonenContents © 2026 <a href="mailto:pypy-dev@pypy.org">The PyPy Team</a> Fri, 10 Jul 2026 07:03:22 GMTNikola (getnikola.com)http://blogs.law.harvard.edu/tech/rssAquileo | A new benchmark runner for PyPyhttps://www.pypy.org/posts/2026/06/benchmarker2-for-pypy.htmlmattip<p>The <a href="https://speed.pypy.org">https://speed.pypy.org</a> site has been running the <a href="https://foss.heptapod.net/pypy/benchmarks">PyPy benchmark suite</a> since 2010. Our first benchmarking machine was called tannit, and it faithfully ran the suite from May 2010 to Dec 2016. For a brief period in the middle we had a machine called speed-python, but tannit was the gold standard. In June 2016 we started running benchmarks on our current machine, <strong>benchmarker</strong> (Intel i7-7700). It has been graciously sponsored by <a href="https://baroquesoftware.com/">Baroque Software</a>. Based on an Ubuntu xenial chroot, the machine has been quite stable but over the years has had a few kernel exploits blocked in firmware that changed its base performance.</p> <p>It is time to update. Rather than use the same machine with updated software, we decided to opt for different hardware. Since the beginning of May we have been running the benchmark suite on <strong>benchmarker2</strong>: an AMD Ryzen 5 3600 machine. In order to try to stabilize benchmarks the machine was set up:</p> <ul> <li>without SMT (hyper-threading)</li> <li>using <code>cpuset</code> to partition CPUs 3,4,5 off (the CPU has 2 CCD chiplets so the CPU sets are truly independent, the reason we chose the Zen2 architecture) and use them exclusively for benchmarking</li> <li>disable turbo speed strategy.</li> </ul> <p>It runs debian13 as a base operating system, and the benchmarks run in a <code>manylinux2_28</code> docker, which provides gcc14.</p> <p>In order to establish a baseline, I compiled CPython 3.11.5 with:</p> <div class="code"><pre class="code literal-block">./configure --prefix=/opt/cpython-3.11 --enable-optimizations \ --with-computed-gotos --enable-shared LDFLAGS='-Wl,-rpath,\$$ORIGIN/../lib' </pre></div> <p>The difference between the two machines is striking: where the xenial image (with GCC 5.4) benchmark comparison to CPython 3.11.9 shows a 3x improvement when run on PyPy on benchmarker, the newer machine with the newer compiler and a fresh baseline shows a 4.3x improvement. I can only speculate that the major differences between the results is:</p> <ul> <li>The CPython 3.11.9 run was done in June 2024. This was before some firmware kernel changes applied to the host machine that slowed it down. I did notice at the time the exploit migitagion firmware was applied that the overall comparison dropped from 3.3x to 3x, but felt the additional protection was warrented.</li> <li>The newer software image uses GCC 14, where the older one used GCC 5.</li> <li>The AMD machine has 32MB of L3 cache, the Intel machine has 8MB.</li> <li>The AMD machine uses RAM at 3200MHz, the Intel at 2400MHz.</li> </ul> <p>The last 3 points may affect PyPy more than CPython, since PyPy's JIT is more memory intensive and the RPython codegen may be handled better by newer compilers.</p> <p>This is the first step in an overhaul of PyPy's infrastructure. Other plans in the pipeline:</p> <ul> <li>Move all the buildbot builds from <code>manylinux_2014</code> to <code>manylinux2_28</code>-based images. This will match the move on benchmarker2. It will require some adaptations so that tests will pass on the newer compiler, see <a href="https://github.com/pypy/pypy/pull/5488">pypy/pypy#5488</a>. This will mean an ABI break, so the next PyPy release will leave behind the 7.3.x series.</li> <li>Think about <a href="https://github.com/pypy/buildbot/issues/1">updating our use of buildbot 0.8.8</a>, which is woefully out of date. Since we have a heavily customized <a href="https://buildbot.pypy.org/summary?branch=py3.11">summary page</a>, and the twistd-based endpoints are not supported on buildbot 0.9 and up, we set up a <a href="https://build-summary.pypy.org/summary?branch=py3.11">build-summary</a> alternative that is synchronized to the buildbot work.</li> <li>Perhaps make more use of the free GitHub actions workers to replace or enhance the buildbot workers. Some of that can be seen in PR 5488. The build-summary service is also able to ingest github action testing results.</li> <li>Continue to push on in CPython compatibility, performance improvements, and bugfixes, as well as work on a <a href="https://github.com/pypy/pypy/issues?q=is%3Aissue%20state%3Aopen%20milestone%3A%22Python%203.12%22">PyPy 3.12 version</a></li> </ul> <p>Help of course is welcome.</p> <p>Matti</p>benchmarksinfrastructureperformancehttps://www.pypy.org/posts/2026/06/benchmarker2-for-pypy.htmlSun, 14 Jun 2026 15:07:09 GMTAquileo | PyPy v7.3.23 releasehttps://www.pypy.org/posts/2026/05/pypy-v7322-release.htmlmattip<section id="pypy-v7-3-23-release-of-python-2-7-3-11"> <h2>PyPy v7.3.23: release of python 2.7, 3.11</h2> <p>The PyPy team is proud to release version 7.3.23 of PyPy after the previous release on April 26, 2026. This is a bug-fix release that fixes an overeager warning about unused coroutines, and some problems around multiple inheritance in c-extensions.</p> <p>This version includes a change to the bytecode interpreter to use <a class="reference external" href="https://github.com/python/cpython/blob/main/InternalDocs/exception_handling.md">exception tables</a> instead of dedicated opcodes. Now the PyPy disassembly will be closer to CPython format. So far it does not impact performance.</p> <p>The release includes two different interpreters:</p> <ul class="simple"> <li><p>PyPy2.7, which is an interpreter supporting the syntax and the features of Python 2.7 including the stdlib for CPython 2.7.18+ (the <code class="docutils literal">+</code> is for backported security updates)</p></li> <li><p>PyPy3.11, which is an interpreter supporting the syntax and the features of Python 3.11, including the stdlib for CPython 3.11.15.</p></li> </ul> <p>The interpreters are based on much the same codebase, thus the double release. This is a micro release, all APIs are compatible with the other 7.3 releases.</p> <p>We recommend updating. You can find links to download the releases here:</p> <blockquote> <p><a class="reference external" href="https://pypy.org/download.html">https://pypy.org/download.html</a></p> </blockquote> <p>We would like to thank our donors for the continued support of the PyPy project. If PyPy is not quite good enough for your needs, we are available for <a class="reference external" href="https://www.pypy.org/pypy-sponsors.html">direct consulting</a> work. If PyPy is helping you out, we would love to hear about it and encourage submissions to our <a class="reference external" href="https://pypy.org/blog">blog</a> via a pull request to <a class="reference external" href="https://github.com/pypy/pypy.org">https://github.com/pypy/pypy.org</a></p> <p>We would also like to thank our contributors and encourage new people to join the project. PyPy has many layers and we need help with all of them: bug fixes, <a class="reference external" href="https://doc.pypy.org/">PyPy</a> and <a class="reference external" href="https://rpython.readthedocs.org">RPython</a> documentation improvements, or general <a class="reference external" href="https://doc.pypy.org/project-ideas.html">help</a> with making RPython's JIT even better.</p> <p>If you are a python library maintainer and use C-extensions, please consider making a <a class="reference external" href="https://hpyproject.org/">HPy</a> / <a class="reference external" href="https://cffi.readthedocs.io">CFFI</a> / <a class="reference external" href="https://cppyy.readthedocs.io">cppyy</a> version of your library that would be performant on PyPy. In any case, <a class="reference external" href="https://github.com/joerick/cibuildwheel">cibuildwheel</a> supports building wheels for PyPy.</p> <section id="what-is-pypy"> <h3>What is PyPy?</h3> <p>PyPy is a Python interpreter, a drop-in replacement for CPython. It's fast (<a class="reference external" href="https://speed.pypy.org">PyPy and CPython</a> performance comparison) due to its integrated tracing JIT compiler.</p> <p>We also welcome developers of other <a class="reference external" href="https://rpython.readthedocs.io/en/latest/examples.html">dynamic languages</a> to see what RPython can do for them.</p> <p>We provide binary builds for:</p> <ul class="simple"> <li><p><strong>x86</strong> machines on most common operating systems (Linux 32/64 bits, Mac OS 64 bits, Windows 64 bits)</p></li> <li><p>64-bit <strong>ARM</strong> machines running Linux (<code class="docutils literal">aarch64</code>) and macos (<code class="docutils literal">macos_arm64</code>).</p></li> </ul> <p>PyPy supports Windows 32-bit, Linux PPC64 big- and little-endian, Linux ARM 32 bit, RISC-V RV64IMAFD Linux, and s390x Linux but does not release binaries. Please reach out to us if you wish to sponsor binary releases for those platforms. Downstream packagers provide binary builds for debian, Fedora, conda, OpenBSD, FreeBSD, Gentoo, and more.</p> </section> <section id="what-else-is-new"> <h3>What else is new?</h3> <p>For more information about the 7.3.23 release, see the <a class="reference external" href="https://doc.pypy.org/en/latest/release-v7.3.23.html#changelog">full changelog</a>.</p> <p>Please update, and continue to help us make pypy better.</p> <p>Cheers, The PyPy Team</p> </section> </section>releasehttps://www.pypy.org/posts/2026/05/pypy-v7322-release.htmlWed, 27 May 2026 10:00:00 GMTAquileo | PyPy v7.3.22 releasehttps://www.pypy.org/posts/2026/04/pypy-v7322-release.htmlmattip<section id="pypy-v7-3-22-release-of-python-2-7-3-11"> <h2>PyPy v7.3.22: release of python 2.7, 3.11</h2> <p>The PyPy team is proud to release version 7.3.22 of PyPy after the previous release on March 13, 2026. This is a bug-fix release that fixes several issues in the JIT. Among them, a long-standing JIT bug that started appearing when some instance optimizations exposed it. We also cleaned up many of the remaining stdlib test suite failures, which improves CPython compatibility around line numbers in dis.dis, signatures and objclass attributes for builtins, and other quality of life features.</p> <p>There is now an RPython <code class="docutils literal">_pickle</code> module that mirrors the CPython one, greatly speeding up pickling operations. Where before PyPy was 5.7x slower than CPython on the pickle benchmark from the pyperformance benchmark suite, now it is only 1.6x slower <a class="brackets" href="https://www.pypy.org/posts/2026/04/pypy-v7322-release.html#footnote-1" id="footnote-reference-1" role="doc-noteref"><span class="fn-bracket">[</span>0<span class="fn-bracket">]</span></a>. We also added pypy pickler extensions to dump and load lists using list strategies, and enabled them in the <code class="docutils literal">ForkingPickler</code> used by multiprocessing, speeding up cases where such objects are passed between PyPy multiprocessing instances.</p> <p>We also added an RPython json encoder, speeding up json_bench from being 2.6x slower than CPython to being 0.7x (meaning faster).</p> <p>The release includes two different interpreters:</p> <ul class="simple"> <li><p>PyPy2.7, which is an interpreter supporting the syntax and the features of Python 2.7 including the stdlib for CPython 2.7.18+ (the <code class="docutils literal">+</code> is for backported security updates)</p></li> <li><p>PyPy3.11, which is an interpreter supporting the syntax and the features of Python 3.11, including the stdlib for CPython 3.11.15.</p></li> </ul> <p>The interpreters are based on much the same codebase, thus the double release. This is a micro release, all APIs are compatible with the other 7.3 releases.</p> <p>We recommend updating. You can find links to download the releases here:</p> <blockquote> <p><a class="reference external" href="https://pypy.org/download.html">https://pypy.org/download.html</a></p> </blockquote> <p>We would like to thank our donors for the continued support of the PyPy project. If PyPy is not quite good enough for your needs, we are available for <a class="reference external" href="https://www.pypy.org/pypy-sponsors.html">direct consulting</a> work. If PyPy is helping you out, we would love to hear about it and encourage submissions to our <a class="reference external" href="https://pypy.org/blog">blog</a> via a pull request to <a class="reference external" href="https://github.com/pypy/pypy.org">https://github.com/pypy/pypy.org</a></p> <p>We would also like to thank our contributors and encourage new people to join the project. PyPy has many layers and we need help with all of them: bug fixes, <a class="reference external" href="https://doc.pypy.org/">PyPy</a> and <a class="reference external" href="https://rpython.readthedocs.org">RPython</a> documentation improvements, or general <a class="reference external" href="https://doc.pypy.org/project-ideas.html">help</a> with making RPython's JIT even better.</p> <p>If you are a python library maintainer and use C-extensions, please consider making a <a class="reference external" href="https://hpyproject.org/">HPy</a> / <a class="reference external" href="https://cffi.readthedocs.io">CFFI</a> / <a class="reference external" href="https://cppyy.readthedocs.io">cppyy</a> version of your library that would be performant on PyPy. In any case, <a class="reference external" href="https://github.com/joerick/cibuildwheel">cibuildwheel</a> supports building wheels for PyPy.</p> <p class="rubric">Footnotes</p> <aside class="footnote-list brackets"> <aside class="footnote brackets" id="footnote-1" role="doc-footnote"> <span class="label"><span class="fn-bracket">[</span><a role="doc-backlink" href="https://www.pypy.org/posts/2026/04/pypy-v7322-release.html#footnote-reference-1">0</a><span class="fn-bracket">]</span></span> <p>Once <a class="reference external" href="https://github.com/python/pyperformance/pull/461">a PR to pyperformance</a> to use the _pickle module on PyPy is accepted</p> </aside> </aside> <section id="what-is-pypy"> <h3>What is PyPy?</h3> <p>PyPy is a Python interpreter, a drop-in replacement for CPython It's fast (<a class="reference external" href="https://speed.pypy.org">PyPy and CPython</a> performance comparison) due to its integrated tracing JIT compiler.</p> <p>We also welcome developers of other <a class="reference external" href="https://rpython.readthedocs.io/en/latest/examples.html">dynamic languages</a> to see what RPython can do for them.</p> <p>We provide binary builds for:</p> <ul class="simple"> <li><p><strong>x86</strong> machines on most common operating systems (Linux 32/64 bits, Mac OS 64 bits, Windows 64 bits)</p></li> <li><p>64-bit <strong>ARM</strong> machines running Linux (<code class="docutils literal">aarch64</code>) and macos (<code class="docutils literal">macos_arm64</code>).</p></li> </ul> <p>PyPy supports Windows 32-bit, Linux PPC64 big- and little-endian, Linux ARM 32 bit, RISC-V RV64IMAFD Linux, and s390x Linux but does not release binaries. Please reach out to us if you wish to sponsor binary releases for those platforms. Downstream packagers provide binary builds for debian, Fedora, conda, OpenBSD, FreeBSD, Gentoo, and more.</p> </section> <section id="what-else-is-new"> <h3>What else is new?</h3> <p>For more information about the 7.3.22 release, see the <a class="reference external" href="https://doc.pypy.org/release-v7.3.22.html#changelog">full changelog</a>.</p> <p>Please update, and continue to help us make pypy better.</p> <p>Cheers, The PyPy Team</p> </section> </section>releasehttps://www.pypy.org/posts/2026/04/pypy-v7322-release.htmlTue, 28 Apr 2026 10:00:00 GMTAquileo | Using Claude to fix PyPy3.11 test failures securelyhttps://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.htmlmattip<p>I got access to Claude Max for 6 months, as a promotional move Anthropic made to Open Source Software contributors. My main OSS impact is as a maintainer for NumPy, but I decided to see what claude-code could to for PyPy's failing 3.11 tests. Most of these failures are edge cases: error messages that differ from CPython, or debugging tools that fail in certain cases. I was worried about letting an AI agent loose on my development machine. I noticed <a class="reference external" href="https://patrickmccanna.net/a-better-way-to-limit-claude-code-and-other-coding-agents-access-to-secrets/">a post</a> by Patrick McCanna (thanks Patrick!) that pointed to using bubblewrap to sandbox the agent. So I set it all up and (hopefully securely) pointed claude-code at some tests.</p> <!-- TEASER_END: Read more to find out how it went --> <section id="setting-up"> <h2>Setting up</h2> <p>There were a few steps to make sure I didn't open myself up to obvious gotchas. There are stories about agents wiping out data bases, or deleting mail boxes.</p> <section id="bubblewrap"> <h3>Bubblewrap</h3> <p>First I needed to see what bubblewrap does. I followed the instructions in the blog post to set things up with some minor variations:</p> <div class="code"><pre class="code bash"><a id="rest_code_df961e614955443dab737653371cf53f-1" name="rest_code_df961e614955443dab737653371cf53f-1" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_df961e614955443dab737653371cf53f-1"></a>sudo<span class="w"> </span>apt<span class="w"> </span>install<span class="w"> </span>bubblewrap </pre></div> <p>I couldn't run <code class="docutils literal">bwrap</code>. After digging around a bit, I found I needed to add an exception for appamor on Ubuntu 24.04:</p> <div class="code"><pre class="code bash"><a id="rest_code_93c4ef3d31e14ab7b8d28808fe237944-1" name="rest_code_93c4ef3d31e14ab7b8d28808fe237944-1" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_93c4ef3d31e14ab7b8d28808fe237944-1"></a>sudo<span class="w"> </span>bash<span class="w"> </span>-c<span class="w"> </span><span class="s1">'cat &gt; /etc/apparmor.d/bwrap &lt;&lt; EOF</span> <a id="rest_code_93c4ef3d31e14ab7b8d28808fe237944-2" name="rest_code_93c4ef3d31e14ab7b8d28808fe237944-2" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_93c4ef3d31e14ab7b8d28808fe237944-2"></a><span class="s1">abi &lt;abi/4.0&gt;,</span> <a id="rest_code_93c4ef3d31e14ab7b8d28808fe237944-3" name="rest_code_93c4ef3d31e14ab7b8d28808fe237944-3" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_93c4ef3d31e14ab7b8d28808fe237944-3"></a><span class="s1">include &lt;tunables/global&gt;</span> <a id="rest_code_93c4ef3d31e14ab7b8d28808fe237944-4" name="rest_code_93c4ef3d31e14ab7b8d28808fe237944-4" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_93c4ef3d31e14ab7b8d28808fe237944-4"></a> <a id="rest_code_93c4ef3d31e14ab7b8d28808fe237944-5" name="rest_code_93c4ef3d31e14ab7b8d28808fe237944-5" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_93c4ef3d31e14ab7b8d28808fe237944-5"></a><span class="s1">profile bwrap /usr/bin/bwrap flags=(unconfined) {</span> <a id="rest_code_93c4ef3d31e14ab7b8d28808fe237944-6" name="rest_code_93c4ef3d31e14ab7b8d28808fe237944-6" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_93c4ef3d31e14ab7b8d28808fe237944-6"></a><span class="s1"> userns,</span> <a id="rest_code_93c4ef3d31e14ab7b8d28808fe237944-7" name="rest_code_93c4ef3d31e14ab7b8d28808fe237944-7" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_93c4ef3d31e14ab7b8d28808fe237944-7"></a><span class="s1">}</span> <a id="rest_code_93c4ef3d31e14ab7b8d28808fe237944-8" name="rest_code_93c4ef3d31e14ab7b8d28808fe237944-8" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_93c4ef3d31e14ab7b8d28808fe237944-8"></a><span class="s1">EOF'</span> <a id="rest_code_93c4ef3d31e14ab7b8d28808fe237944-9" name="rest_code_93c4ef3d31e14ab7b8d28808fe237944-9" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_93c4ef3d31e14ab7b8d28808fe237944-9"></a>sudo<span class="w"> </span>apparmor_parser<span class="w"> </span>-r<span class="w"> </span>/etc/apparmor.d/bwrap </pre></div> <p>Then <code class="docutils literal">bwrap</code> would run. It is all locked down by default, so I opened up some exceptions. The arguments are pretty self-explanatory. Ubuntu spreads the executables around the operating system, so I needed access to various directories. I wanted a <code class="docutils literal">/tmp</code> for running pytest. I also wanted the prompt to reflect the use of bubblewrap, so changed the <code class="docutils literal">hostname</code>:</p> <div class="code"><pre class="code bash"><a id="rest_code_9bfc1706c3734ebbac9212256daf2132-1" name="rest_code_9bfc1706c3734ebbac9212256daf2132-1" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-1"></a>cat<span class="w"> </span><span class="s">&lt;&lt; 'EOL' &gt;&gt; ./run_bwrap.sh</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-2" name="rest_code_9bfc1706c3734ebbac9212256daf2132-2" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-2"></a><span class="s"> function call_bwrap() {</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-3" name="rest_code_9bfc1706c3734ebbac9212256daf2132-3" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-3"></a><span class="s"> bwrap \</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-4" name="rest_code_9bfc1706c3734ebbac9212256daf2132-4" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-4"></a><span class="s"> --ro-bind /usr /usr \</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-5" name="rest_code_9bfc1706c3734ebbac9212256daf2132-5" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-5"></a><span class="s"> --ro-bind /etc /etc \</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-6" name="rest_code_9bfc1706c3734ebbac9212256daf2132-6" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-6"></a><span class="s"> --ro-bind /run /run \</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-7" name="rest_code_9bfc1706c3734ebbac9212256daf2132-7" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-7"></a><span class="s"> --symlink usr/lib /lib \</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-8" name="rest_code_9bfc1706c3734ebbac9212256daf2132-8" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-8"></a><span class="s"> --symlink usr/lib64 /lib64 \</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-9" name="rest_code_9bfc1706c3734ebbac9212256daf2132-9" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-9"></a><span class="s"> --symlink usr/bin /bin \</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-10" name="rest_code_9bfc1706c3734ebbac9212256daf2132-10" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-10"></a><span class="s"> --proc /proc \</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-11" name="rest_code_9bfc1706c3734ebbac9212256daf2132-11" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-11"></a><span class="s"> --dev /dev \</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-12" name="rest_code_9bfc1706c3734ebbac9212256daf2132-12" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-12"></a><span class="s"> --bind $(pwd) $(pwd) \</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-13" name="rest_code_9bfc1706c3734ebbac9212256daf2132-13" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-13"></a><span class="s"> --chdir $(pwd) \</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-14" name="rest_code_9bfc1706c3734ebbac9212256daf2132-14" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-14"></a><span class="s"> --unshare-user --unshare-pid --unshare-ipc --unshare-uts --unshare-cgroup \</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-15" name="rest_code_9bfc1706c3734ebbac9212256daf2132-15" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-15"></a><span class="s"> --die-with-parent \</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-16" name="rest_code_9bfc1706c3734ebbac9212256daf2132-16" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-16"></a><span class="s"> --hostname bwrap \</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-17" name="rest_code_9bfc1706c3734ebbac9212256daf2132-17" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-17"></a><span class="s"> --tmpfs /tmp \</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-18" name="rest_code_9bfc1706c3734ebbac9212256daf2132-18" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-18"></a><span class="s"> /bin/bash "$@"</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-19" name="rest_code_9bfc1706c3734ebbac9212256daf2132-19" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-19"></a><span class="s"> }</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-20" name="rest_code_9bfc1706c3734ebbac9212256daf2132-20" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-20"></a><span class="s">EOL</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-21" name="rest_code_9bfc1706c3734ebbac9212256daf2132-21" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-21"></a> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-22" name="rest_code_9bfc1706c3734ebbac9212256daf2132-22" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-22"></a><span class="nb">source</span><span class="w"> </span>./run_bwrap.sh <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-23" name="rest_code_9bfc1706c3734ebbac9212256daf2132-23" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-23"></a>call_bwrap <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-24" name="rest_code_9bfc1706c3734ebbac9212256daf2132-24" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-24"></a><span class="c1"># now I am in a sandboxed bash shell</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-25" name="rest_code_9bfc1706c3734ebbac9212256daf2132-25" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-25"></a><span class="c1"># play around, try seeing other directories, getting sudo, or writing outside</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-26" name="rest_code_9bfc1706c3734ebbac9212256daf2132-26" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-26"></a><span class="c1"># the sandbox</span> <a id="rest_code_9bfc1706c3734ebbac9212256daf2132-27" name="rest_code_9bfc1706c3734ebbac9212256daf2132-27" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_9bfc1706c3734ebbac9212256daf2132-27"></a><span class="nb">exit</span> </pre></div> <p>I did not do <code class="docutils literal"><span class="pre">--unshare-network</span></code> since, after all, I want to use claude and that needs network access. I did add rw access to <code class="docutils literal">$(pwd)</code> since I want it to edit code in the current directory, that is the whole point.</p> </section> <section id="basic-claude"> <h3>Basic claude</h3> <p>After trying out bubblewrap and convincing myself it does actually work, I installed claude code</p> <div class="code"><pre class="code bash"><a id="rest_code_a18c62f549ae445fbf83f91ee375eb63-1" name="rest_code_a18c62f549ae445fbf83f91ee375eb63-1" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_a18c62f549ae445fbf83f91ee375eb63-1"></a>curl<span class="w"> </span>-fsSL<span class="w"> </span>https://claude.ai/install.sh<span class="w"> </span><span class="p">|</span><span class="w"> </span>bash </pre></div> <p>Really Anthropic, this is the best way to install claude? No dpkg?</p> <p>I ran claude once (unsafely) to get logged in. It opened a webpage, and saved the login to the <code class="docutils literal">oathAccount</code> field in <code class="docutils literal"><span class="pre">~/.claude.json</span></code>. Now I changed my bash script to this to get claude to run inside the bubblewrap sandbox:</p> <div class="code"><pre class="code bash"><a id="rest_code_087a171ea29345e198e0aad07e0775b1-1" name="rest_code_087a171ea29345e198e0aad07e0775b1-1" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-1"></a>cat<span class="w"> </span><span class="s">&lt;&lt; 'EOL' &gt;&gt; ./run_claude.sh</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-2" name="rest_code_087a171ea29345e198e0aad07e0775b1-2" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-2"></a><span class="s"> claude-safe() {</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-3" name="rest_code_087a171ea29345e198e0aad07e0775b1-3" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-3"></a><span class="s"> bwrap \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-4" name="rest_code_087a171ea29345e198e0aad07e0775b1-4" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-4"></a><span class="s"> --ro-bind /usr /usr \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-5" name="rest_code_087a171ea29345e198e0aad07e0775b1-5" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-5"></a><span class="s"> --ro-bind /etc /etc \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-6" name="rest_code_087a171ea29345e198e0aad07e0775b1-6" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-6"></a><span class="s"> --ro-bind /run /run \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-7" name="rest_code_087a171ea29345e198e0aad07e0775b1-7" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-7"></a><span class="s"> --ro-bind "$HOME/.local/share/claude" "$HOME/.local/share/claude" \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-8" name="rest_code_087a171ea29345e198e0aad07e0775b1-8" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-8"></a><span class="s"> --symlink usr/lib /lib \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-9" name="rest_code_087a171ea29345e198e0aad07e0775b1-9" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-9"></a><span class="s"> --symlink usr/lib64 /lib64 \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-10" name="rest_code_087a171ea29345e198e0aad07e0775b1-10" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-10"></a><span class="s"> --symlink usr/bin /bin \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-11" name="rest_code_087a171ea29345e198e0aad07e0775b1-11" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-11"></a><span class="s"> --symlink "$HOME/.local/share/claude/versions/2.1.81" "$HOME/.local/bin/claude" \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-12" name="rest_code_087a171ea29345e198e0aad07e0775b1-12" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-12"></a><span class="s"> --proc /proc \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-13" name="rest_code_087a171ea29345e198e0aad07e0775b1-13" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-13"></a><span class="s"> --dev /dev \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-14" name="rest_code_087a171ea29345e198e0aad07e0775b1-14" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-14"></a><span class="s"> --bind $(pwd) $(pwd) \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-15" name="rest_code_087a171ea29345e198e0aad07e0775b1-15" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-15"></a><span class="s"> --bind "$HOME/.claude" "$HOME/.claude" \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-16" name="rest_code_087a171ea29345e198e0aad07e0775b1-16" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-16"></a><span class="s"> --bind "$HOME/.claude.json" "$HOME/.claude.json" \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-17" name="rest_code_087a171ea29345e198e0aad07e0775b1-17" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-17"></a><span class="s"> --chdir $(pwd) \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-18" name="rest_code_087a171ea29345e198e0aad07e0775b1-18" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-18"></a><span class="s"> --unshare-user --unshare-pid --unshare-ipc --unshare-uts --unshare-cgroup \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-19" name="rest_code_087a171ea29345e198e0aad07e0775b1-19" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-19"></a><span class="s"> --die-with-parent \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-20" name="rest_code_087a171ea29345e198e0aad07e0775b1-20" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-20"></a><span class="s"> --hostname bwrap \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-21" name="rest_code_087a171ea29345e198e0aad07e0775b1-21" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-21"></a><span class="s"> --tmpfs /tmp \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-22" name="rest_code_087a171ea29345e198e0aad07e0775b1-22" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-22"></a><span class="s"> --setenv PATH "$HOME/.local/bin:$PATH" \</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-23" name="rest_code_087a171ea29345e198e0aad07e0775b1-23" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-23"></a><span class="s"> claude "$@"</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-24" name="rest_code_087a171ea29345e198e0aad07e0775b1-24" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-24"></a><span class="s"> }</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-25" name="rest_code_087a171ea29345e198e0aad07e0775b1-25" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-25"></a><span class="s">EOL</span> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-26" name="rest_code_087a171ea29345e198e0aad07e0775b1-26" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-26"></a> <a id="rest_code_087a171ea29345e198e0aad07e0775b1-27" name="rest_code_087a171ea29345e198e0aad07e0775b1-27" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-27"></a><span class="nb">source</span><span class="w"> </span>./run_claude.sh <a id="rest_code_087a171ea29345e198e0aad07e0775b1-28" name="rest_code_087a171ea29345e198e0aad07e0775b1-28" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_087a171ea29345e198e0aad07e0775b1-28"></a>claude-safe </pre></div> <p>Now I can use claude. Note it needs some more directories in order to run. This script hard-codes the version, in the future YMMV. I want it to be able to look at github, and also my local checkout of cpython so it can examine differences. I created a read-only token by clicking on my avatar in the upper right corner of a github we page, then going to Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token. Since pypy is in the pypy org, I used "Repository owner: pypy", "Repository access: pypy (only)" and "Permissions: Contents". Then I made doubly sure the token permissions were read-only. And checked again. Then I copied the token to the bash script. I also added a <code class="docutils literal"><span class="pre">ro-bind</span></code> to the cpython checkout, so I could tell claude code where to look for CPython implementations of missing PyPy functionality.</p> <div class="code"><pre class="code bash"><a id="rest_code_6435b3d90ceb4ee8b094b10e5a6db7a7-1" name="rest_code_6435b3d90ceb4ee8b094b10e5a6db7a7-1" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_6435b3d90ceb4ee8b094b10e5a6db7a7-1"></a>--ro-bind<span class="w"> </span><span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/oss/cpython"</span><span class="w"> </span><span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/oss/cpython"</span><span class="w"> </span><span class="se">\</span> <a id="rest_code_6435b3d90ceb4ee8b094b10e5a6db7a7-2" name="rest_code_6435b3d90ceb4ee8b094b10e5a6db7a7-2" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_6435b3d90ceb4ee8b094b10e5a6db7a7-2"></a>--setenv<span class="w"> </span>GH_TOKEN<span class="w"> </span><span class="s2">"hah, sharing my token would not have been smart"</span><span class="w"> </span><span class="se">\</span> </pre></div> </section> <section id="claude-sandbox"> <h3>Claude /sandbox</h3> <p>Claude comes with its own sandbox, configured by using the <code class="docutils literal">/sandbox</code> command. I chose the defaults, which prevents malicious code in the repo from accessing the file system and the network. I was missing some packages to get this to work. Claude would hang until I installed them, and I needed to kill it with <code class="docutils literal">kill</code>.</p> <div class="code"><pre class="code bash"><a id="rest_code_d38750a3953b43d18289337d2b6504c3-1" name="rest_code_d38750a3953b43d18289337d2b6504c3-1" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_d38750a3953b43d18289337d2b6504c3-1"></a>sudo<span class="w"> </span>apt<span class="w"> </span>install<span class="w"> </span>socat <a id="rest_code_d38750a3953b43d18289337d2b6504c3-2" name="rest_code_d38750a3953b43d18289337d2b6504c3-2" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_d38750a3953b43d18289337d2b6504c3-2"></a>sudo<span class="w"> </span>npm<span class="w"> </span>install<span class="w"> </span>-g<span class="w"> </span>@anthropic-ai/sandbox-runtime </pre></div> </section> <section id="final-touches"> <h3>Final touches</h3> <p>One last thing that I discovered later: I needed to give claude access to some grepping and git tools. While git should be locked down externally so it cannot push to the repo, I do want claude to look at other issues and pull requests in read-only mode. So I added a local <code class="docutils literal">.claude/settings.json</code> file inside the repo (see below for which directory to do this):</p> <div class="code"><pre class="code json"><a id="rest_code_112c31cd4c4c46948448ebb28b3100cf-1" name="rest_code_112c31cd4c4c46948448ebb28b3100cf-1" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_112c31cd4c4c46948448ebb28b3100cf-1"></a><span class="p">{</span> <a id="rest_code_112c31cd4c4c46948448ebb28b3100cf-2" name="rest_code_112c31cd4c4c46948448ebb28b3100cf-2" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_112c31cd4c4c46948448ebb28b3100cf-2"></a><span class="w"> </span><span class="nt">"permissions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span> <a id="rest_code_112c31cd4c4c46948448ebb28b3100cf-3" name="rest_code_112c31cd4c4c46948448ebb28b3100cf-3" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_112c31cd4c4c46948448ebb28b3100cf-3"></a><span class="w"> </span><span class="nt">"allow"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span> <a id="rest_code_112c31cd4c4c46948448ebb28b3100cf-4" name="rest_code_112c31cd4c4c46948448ebb28b3100cf-4" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_112c31cd4c4c46948448ebb28b3100cf-4"></a><span class="w"> </span><span class="s2">"Bash(sed*)"</span><span class="p">,</span> <a id="rest_code_112c31cd4c4c46948448ebb28b3100cf-5" name="rest_code_112c31cd4c4c46948448ebb28b3100cf-5" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_112c31cd4c4c46948448ebb28b3100cf-5"></a><span class="w"> </span><span class="s2">"Bash(grep*)"</span><span class="p">,</span> <a id="rest_code_112c31cd4c4c46948448ebb28b3100cf-6" name="rest_code_112c31cd4c4c46948448ebb28b3100cf-6" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_112c31cd4c4c46948448ebb28b3100cf-6"></a><span class="w"> </span><span class="s2">"Bash(cat*)"</span><span class="p">,</span> <a id="rest_code_112c31cd4c4c46948448ebb28b3100cf-7" name="rest_code_112c31cd4c4c46948448ebb28b3100cf-7" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_112c31cd4c4c46948448ebb28b3100cf-7"></a><span class="w"> </span><span class="s2">"Bash(find*)"</span><span class="p">,</span> <a id="rest_code_112c31cd4c4c46948448ebb28b3100cf-8" name="rest_code_112c31cd4c4c46948448ebb28b3100cf-8" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_112c31cd4c4c46948448ebb28b3100cf-8"></a><span class="w"> </span><span class="s2">"Bash(rg*)"</span><span class="p">,</span> <a id="rest_code_112c31cd4c4c46948448ebb28b3100cf-9" name="rest_code_112c31cd4c4c46948448ebb28b3100cf-9" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_112c31cd4c4c46948448ebb28b3100cf-9"></a><span class="w"> </span><span class="s2">"Bash(python*)"</span><span class="p">,</span> <a id="rest_code_112c31cd4c4c46948448ebb28b3100cf-10" name="rest_code_112c31cd4c4c46948448ebb28b3100cf-10" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_112c31cd4c4c46948448ebb28b3100cf-10"></a><span class="w"> </span><span class="s2">"Bash(pytest*)"</span> <a id="rest_code_112c31cd4c4c46948448ebb28b3100cf-11" name="rest_code_112c31cd4c4c46948448ebb28b3100cf-11" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_112c31cd4c4c46948448ebb28b3100cf-11"></a><span class="w"> </span><span class="p">]</span> <a id="rest_code_112c31cd4c4c46948448ebb28b3100cf-12" name="rest_code_112c31cd4c4c46948448ebb28b3100cf-12" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_112c31cd4c4c46948448ebb28b3100cf-12"></a><span class="w"> </span><span class="p">}</span> <a id="rest_code_112c31cd4c4c46948448ebb28b3100cf-13" name="rest_code_112c31cd4c4c46948448ebb28b3100cf-13" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_112c31cd4c4c46948448ebb28b3100cf-13"></a><span class="p">}</span> </pre></div> <p>Then I made git ignore it, even when doing a <code class="docutils literal">git clean</code>, in a local (not part of the repo) configuration</p> <div class="code"><pre class="code bash"><a id="rest_code_3cfdb161a1044f919a6726f8d7818319-1" name="rest_code_3cfdb161a1044f919a6726f8d7818319-1" href="https://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.html#rest_code_3cfdb161a1044f919a6726f8d7818319-1"></a><span class="nb">echo</span><span class="w"> </span>-n<span class="w"> </span>.claude<span class="w"> </span>&gt;&gt;<span class="w"> </span>~/.config/git/ignore </pre></div> </section> <section id="what-about-git-push"> <h3>What about <code class="docutils literal">git push</code>?</h3> <p>I don't want claude messing around with the upstream repo, only read access. But I did not actively prevent <code class="docutils literal">git push</code>. So instead of using my actual pypy repo, I cloned it to a separate directory and did not add a remote pointing to github.com.</p> </section> </section> <section id="fixing-tests-easy"> <h2>Fixing tests - easy</h2> <p>Now that everything is set up (I hope I remembered everything), I could start asking questions. The technique I chose was to feed claude the whole test failure from the buildbot. So starting from the <a class="reference external" href="https://buildbot.pypy.org/summary?branch=py3.11">buildbot py3.11 summary</a>, click on one of the <code class="docutils literal">F</code> links and copy-paste all that into the claude prompt. It didn't take long for claude to come up with solutions for the long-standing <a class="reference external" href="https://github.com/pypy/pypy/commit/9e8e121b545dbea3f26ca436ae8a797617904306#diff-ab042b3dd16bf22b7e3d8595f182ad39d3823d76b414da7debe96081a884d16bR64-R330">ctype error missing exception</a> which turned out to be due to an missing error trap when already handling an error.</p> <p>Also a <a class="reference external" href="https://github.com/pypy/pypy/commit/9e8e121b545dbea3f26ca436ae8a797617904306#diff-ab042b3dd16bf22b7e3d8595f182ad39d3823d76b414da7debe96081a884d16bR64-R53">CTYPES_MAX_ARGCOUNT check</a> was missing. At first, claude wanted to change the ctypes code from CPython's stdlib, and so I had to make it clear that claude was not to touch the files in <code class="docutils literal"><span class="pre">lib-python</span></code>. They are copied verbatim from CPython and should not be modified without really good reasons.</p> <p>The <a class="reference external" href="https://github.com/pypy/pypy/commit/39ca7a1def272742e8aafd2a649ed4f8fed7038d">fix to raise</a> <code class="docutils literal">TypeError</code> rather than <code class="docutils literal">Attribute Error</code> for deleting ctype object's <code class="docutils literal">value</code> was maybe a little trickier: claude needed to create its own <code class="docutils literal">property</code> class and use it in assignments.</p> <p>The <a class="reference external" href="https://github.com/pypy/pypy/commit/e0e401699c20a92d8db657879183c68ea44246b4">fix for a failing test</a> for a correct <code class="docutils literal">repr</code> of a ctypes array was a little more involved. Claude needed to figure out that <code class="docutils literal">newmemoryview</code> was raising an exception, dive into the RPython implementation and fix the problem, and then also fix a pure-python <code class="docutils literal">__buffer__</code> shape edge case error.</p> <p>There were more, but you get the idea. With a little bit of coaching, and by showing claude where the CPython implementation was, more tests are now passing.</p> </section> <section id="fixing-tests-harder"> <h2>Fixing tests - harder</h2> <p>PyPy has a HPy backend. There were some test failures that were easy to fix (a handle not being closed, an annotation warning). But the big one was a problem with the context tracking before and after ffi function calls. In debug mode there is a check that the ffi call is done using the correct HPy context. It turns out to be tricky to hang on to a reference to a context in RPython since the context RPython object is pre-built. The solution, which took quite a few tokens and translation cycles to work out, was to assign the context on the C level, and have a getter to fish it out in RPython.</p> <section id="conclusion"> <h3>Conclusion</h3> <p>I started this journey not more than 24 hours ago, after some successful sessions using claude to refactor some web sites off hosting platforms and make them static pages. I was impressed enough to try coding with it from the terminal. It helps that I was given a generous budget to use Anthropic's tool.</p> <p>Claude seems capable of understanding the layers of PyPy: from the pure python stdlib to RPython and into the small amount of C code. I even asked it to examine a <a class="reference external" href="https://github.com/pypy/pypy/issues/5398">segfault</a> in the recently released PyPy7.3.21, and it seems to have found the general area where there was a latent bug in the JIT.</p> <p>Like any tool, agentic programming must be used carefully to make sure it cannot do damage. I hope I closed the most obvious foot-guns, if you have other ideas of things I should do to protect myself while using an agent like this, I would love to hear about them.</p> </section> </section>AIhttps://www.pypy.org/posts/2026/03/using-claude-to-fix-pypy311-test-failures-securely.htmlMon, 23 Mar 2026 10:27:55 GMTAquileo | PyPy v7.3.21 releasehttps://www.pypy.org/posts/2026/03/pypy-v7321-release.htmlmattip<section id="pypy-v7-3-21-release-of-python-2-7-3-11"> <h2>PyPy v7.3.21: release of python 2.7, 3.11</h2> <aside class="admonition warning"> <p class="admonition-title">Warning</p> <p>This release has some known crashes. We recommend you use a different version</p> </aside> <p>The PyPy team is proud to release version 7.3.21 of PyPy after the previous release on July 4, 2025. This is a bug-fix release that also updates to Python 3.11.15.</p> <p>The release includes two different interpreters:</p> <ul class="simple"> <li><p>PyPy2.7, which is an interpreter supporting the syntax and the features of Python 2.7 including the stdlib for CPython 2.7.18+ (the <code class="docutils literal">+</code> is for backported security updates)</p></li> <li><p>PyPy3.11, which is an interpreter supporting the syntax and the features of Python 3.11, including the stdlib for CPython 3.11.15.</p></li> </ul> <p>The interpreters are based on much the same codebase, thus the double release. This is a micro release, all APIs are compatible with the other 7.3 releases.</p> <p>We recommend updating. You can find links to download the releases here:</p> <blockquote> <p><a class="reference external" href="https://pypy.org/download.html">https://pypy.org/download.html</a></p> </blockquote> <p>We would like to thank our donors for the continued support of the PyPy project. If PyPy is not quite good enough for your needs, we are available for <a class="reference external" href="https://www.pypy.org/pypy-sponsors.html">direct consulting</a> work. If PyPy is helping you out, we would love to hear about it and encourage submissions to our <a class="reference external" href="https://pypy.org/blog">blog</a> via a pull request to <a class="reference external" href="https://github.com/pypy/pypy.org">https://github.com/pypy/pypy.org</a></p> <p>We would also like to thank our contributors and encourage new people to join the project. PyPy has many layers and we need help with all of them: bug fixes, <a class="reference external" href="https://doc.pypy.org/">PyPy</a> and <a class="reference external" href="https://rpython.readthedocs.org">RPython</a> documentation improvements, or general <a class="reference external" href="https://doc.pypy.org/project-ideas.html">help</a> with making RPython's JIT even better.</p> <p>If you are a python library maintainer and use C-extensions, please consider making a <a class="reference external" href="https://hpyproject.org/">HPy</a> / <a class="reference external" href="https://cffi.readthedocs.io">CFFI</a> / <a class="reference external" href="https://cppyy.readthedocs.io">cppyy</a> version of your library that would be performant on PyPy. In any case, <a class="reference external" href="https://github.com/joerick/cibuildwheel">cibuildwheel</a> supports building wheels for PyPy.</p> <section id="what-is-pypy"> <h3>What is PyPy?</h3> <p>PyPy is a Python interpreter, a drop-in replacement for CPython It's fast (<a class="reference external" href="https://speed.pypy.org">PyPy and CPython</a> performance comparison) due to its integrated tracing JIT compiler.</p> <p>We also welcome developers of other <a class="reference external" href="https://rpython.readthedocs.io/en/latest/examples.html">dynamic languages</a> to see what RPython can do for them.</p> <p>We provide binary builds for:</p> <ul class="simple"> <li><p><strong>x86</strong> machines on most common operating systems (Linux 32/64 bits, Mac OS 64 bits, Windows 64 bits)</p></li> <li><p>64-bit <strong>ARM</strong> machines running Linux (<code class="docutils literal">aarch64</code>) and macos (<code class="docutils literal">macos_arm64</code>).</p></li> </ul> <p>PyPy supports Windows 32-bit, Linux PPC64 big- and little-endian, Linux ARM 32 bit, RISC-V RV64IMAFD Linux, and s390x Linux but does not release binaries. Please reach out to us if you wish to sponsor binary releases for those platforms. Downstream packagers provide binary builds for debian, Fedora, conda, OpenBSD, FreeBSD, Gentoo, and more.</p> </section> <section id="what-else-is-new"> <h3>What else is new?</h3> <p>For more information about the 7.3.21 release, see the <a class="reference external" href="https://doc.pypy.org/release-v7.3.21.html#changelog">full changelog</a>.</p> <p>Please update, and continue to help us make pypy better.</p> <p>Cheers, The PyPy Team</p> </section> </section>releasehttps://www.pypy.org/posts/2026/03/pypy-v7321-release.htmlFri, 13 Mar 2026 10:00:00 GMTAquileo | Load and store forwarding in the Toy Optimizerhttps://www.pypy.org/posts/2025/12/toy-load-store.htmlMax Bernstein<p>This is a <a href="https://bernsteinbear.com/blog/toy-load-store/" rel="canonical">cross-post</a> from Max Bernstein from his blog where he writes about programming languages, compilers, optimizations, virtual machines.</p> <hr> <p>A long, long time ago (two years!) <a href="https://cfbolz.de/">CF Bolz-Tereick</a> and I made a <a href="https://www.youtube.com/watch?v=w-UHg0yOPSE">video about load/store forwarding</a> and an accompanying <a href="https://gist.github.com/tekknolagi/4e3fa26d350f6d3b39ede40d372b97fe">GitHub Gist</a> about load/store forwarding (also called load elimination) in the Toy Optimizer. I said I would write a blog post about it, but never found the time—it got lost amid a sea of large life changes.</p> <p>It's a neat idea: do an abstract interpretation over the trace, modeling the heap at compile-time, eliminating redundant loads and stores. That means it's possible to optimize traces like this:</p> <div class="code"><pre class="code literal-block"><span class="n">v0</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">...</span> <span class="n">v1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">load</span><span class="p">(</span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">)</span> <span class="n">v2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">store</span><span class="p">(</span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w"> </span><span class="mi">123</span><span class="p">)</span> <span class="n">v3</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">load</span><span class="p">(</span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="mi">6</span><span class="p">)</span> <span class="n">v4</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">load</span><span class="p">(</span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">)</span> <span class="n">v5</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">do_something</span><span class="p">(</span><span class="n">v1</span><span class="p">,</span><span class="w"> </span><span class="n">v3</span><span class="p">,</span><span class="w"> </span><span class="n">v4</span><span class="p">)</span> </pre></div> <p>into traces like this:</p> <div class="code"><pre class="code literal-block"><span class="n">v0</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">...</span> <span class="n">v1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">load</span><span class="p">(</span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">)</span> <span class="n">v2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">store</span><span class="p">(</span><span class="n">v0</span><span class="p">,</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w"> </span><span class="mi">123</span><span class="p">)</span> <span class="n">v5</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">do_something</span><span class="p">(</span><span class="n">v1</span><span class="p">,</span><span class="w"> </span><span class="mi">123</span><span class="p">,</span><span class="w"> </span><span class="n">v1</span><span class="p">)</span> </pre></div> <p>(where <code>load(v0, 5)</code> is equivalent to <code>*(v0+5)</code> in C syntax and <code>store(v0, 6, 123)</code> is equvialent to <code>*(v0+6)=123</code> in C syntax)</p> <p>This indicates that we were able to eliminate two redundant loads by keeping around information about previous loads and stores. Let's get to work making this possible.</p> <h3 id="the-usual-infrastructure">The usual infrastructure</h3> <p>We'll start off with the usual infrastructure from the <a href="https://pypy.org/categories/toy-optimizer.html">Toy Optimizer series</a>: a very stringly-typed representation of a <a href="https://gist.github.com/tekknolagi/4e3fa26d350f6d3b39ede40d372b97fe#file-port-py-L4-L112">trace-based SSA IR</a> and a union-find rewrite mechanism.</p> <p>This means we can start writing some new optimization pass and our first test:</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">optimize_load_store</span><span class="p">(</span><span class="n">bb</span><span class="p">:</span> <span class="n">Block</span><span class="p">):</span> <span class="n">opt_bb</span> <span class="o">=</span> <span class="n">Block</span><span class="p">()</span> <span class="c1"># TODO: copy an optimized version of bb into opt_bb</span> <span class="k">return</span> <span class="n">opt_bb</span> <span class="k">def</span><span class="w"> </span><span class="nf">test_two_loads</span><span class="p">():</span> <span class="n">bb</span> <span class="o">=</span> <span class="n">Block</span><span class="p">()</span> <span class="n">var0</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">getarg</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="n">var1</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">var0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="n">var2</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">var0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="n">bb</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">var1</span><span class="p">)</span> <span class="n">bb</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">var2</span><span class="p">)</span> <span class="n">opt_bb</span> <span class="o">=</span> <span class="n">optimize_load_store</span><span class="p">(</span><span class="n">bb</span><span class="p">)</span> <span class="k">assert</span> <span class="n">bb_to_str</span><span class="p">(</span><span class="n">opt_bb</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"""</span><span class="se">\</span> <span class="s2">var0 = getarg(0)</span> <span class="s2">var1 = load(var0, 0)</span> <span class="s2">var2 = escape(var1)</span> <span class="s2">var3 = escape(var1)"""</span> </pre></div> <p>This test is asserting that we can remove duplicate loads. Why load twice if we can cache the result? Let's make that happen.</p> <h3 id="caching-loads">Caching loads</h3> <p>To do this, we'll model the the heap at compile-time. When I say "model", I mean that we will have an imprecise but correct abstract representation of the heap: we don't (and can't) have knowledge of every value, but we can know for sure that some addresses have certain values.</p> <p>For example, if we have observed a load from object <em>O</em> at offset <em>8</em> <code>v0 = load(O, 8)</code>, we know that the SSA value <code>v0</code> is at <code>heap[(O, 8)]</code>. That sounds tautological, but it's not. Future loads can make use of this information.</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">get_num</span><span class="p">(</span><span class="n">op</span><span class="p">:</span> <span class="n">Operation</span><span class="p">,</span> <span class="n">index</span><span class="p">:</span> <span class="nb">int</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span> <span class="k">assert</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">op</span><span class="o">.</span><span class="n">arg</span><span class="p">(</span><span class="n">index</span><span class="p">),</span> <span class="n">Constant</span><span class="p">)</span> <span class="k">return</span> <span class="n">op</span><span class="o">.</span><span class="n">arg</span><span class="p">(</span><span class="n">index</span><span class="p">)</span><span class="o">.</span><span class="n">value</span> <span class="k">def</span><span class="w"> </span><span class="nf">optimize_load_store</span><span class="p">(</span><span class="n">bb</span><span class="p">:</span> <span class="n">Block</span><span class="p">):</span> <span class="n">opt_bb</span> <span class="o">=</span> <span class="n">Block</span><span class="p">()</span> <span class="c1"># Stores things we know about the heap at... compile-time.</span> <span class="c1"># Key: an object and an offset pair acting as a heap address</span> <span class="c1"># Value: a previous SSA value we know exists at that address</span> <span class="n">compile_time_heap</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="n">Value</span><span class="p">,</span> <span class="nb">int</span><span class="p">],</span> <span class="n">Value</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span> <span class="k">for</span> <span class="n">op</span> <span class="ow">in</span> <span class="n">bb</span><span class="p">:</span> <span class="k">if</span> <span class="n">op</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"load"</span><span class="p">:</span> <span class="n">obj</span> <span class="o">=</span> <span class="n">op</span><span class="o">.</span><span class="n">arg</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="n">offset</span> <span class="o">=</span> <span class="n">get_num</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="n">load_info</span> <span class="o">=</span> <span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">offset</span><span class="p">)</span> <span class="n">previous</span> <span class="o">=</span> <span class="n">compile_time_heap</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">load_info</span><span class="p">)</span> <span class="k">if</span> <span class="n">previous</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span> <span class="n">op</span><span class="o">.</span><span class="n">make_equal_to</span><span class="p">(</span><span class="n">previous</span><span class="p">)</span> <span class="k">continue</span> <span class="n">compile_time_heap</span><span class="p">[</span><span class="n">load_info</span><span class="p">]</span> <span class="o">=</span> <span class="n">op</span> <span class="n">opt_bb</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">op</span><span class="p">)</span> <span class="k">return</span> <span class="n">opt_bb</span> </pre></div> <p>This pass records information about loads and uses the result of a previous cached load operation if available. We treat the pair of (SSA value, offset) as an address into our abstract heap.</p> <p>That's great! If you run our simple test, it should now pass. But what happens if we store into that address before the second load? Oops...</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">test_store_to_same_object_offset_invalidates_load</span><span class="p">():</span> <span class="n">bb</span> <span class="o">=</span> <span class="n">Block</span><span class="p">()</span> <span class="n">var0</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">getarg</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="n">var1</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">var0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="n">var2</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">store</span><span class="p">(</span><span class="n">var0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span> <span class="n">var3</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">var0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="n">bb</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">var1</span><span class="p">)</span> <span class="n">bb</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">var3</span><span class="p">)</span> <span class="n">opt_bb</span> <span class="o">=</span> <span class="n">optimize_load_store</span><span class="p">(</span><span class="n">bb</span><span class="p">)</span> <span class="k">assert</span> <span class="n">bb_to_str</span><span class="p">(</span><span class="n">opt_bb</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"""</span><span class="se">\</span> <span class="s2">var0 = getarg(0)</span> <span class="s2">var1 = load(var0, 0)</span> <span class="s2">var2 = store(var0, 0, 5)</span> <span class="s2">var3 = load(var0, 0)</span> <span class="s2">var4 = escape(var1)</span> <span class="s2">var5 = escape(var3)"""</span> </pre></div> <p>This test fails because we are incorrectly keeping around <code>var1</code> in our abstract heap. We need to get rid of it and not replace <code>var3</code> with <code>var1</code>.</p> <h3 id="invalidating-cached-loads">Invalidating cached loads</h3> <p>So it turns out we have to also model stores in order to cache loads correctly. One valid, albeit aggressive, way to do that is to throw away all the information we know at each store operation:</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">optimize_load_store</span><span class="p">(</span><span class="n">bb</span><span class="p">:</span> <span class="n">Block</span><span class="p">):</span> <span class="n">opt_bb</span> <span class="o">=</span> <span class="n">Block</span><span class="p">()</span> <span class="n">compile_time_heap</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="n">Value</span><span class="p">,</span> <span class="nb">int</span><span class="p">],</span> <span class="n">Value</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span> <span class="k">for</span> <span class="n">op</span> <span class="ow">in</span> <span class="n">bb</span><span class="p">:</span> <span class="k">if</span> <span class="n">op</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"store"</span><span class="p">:</span> <span class="n">compile_time_heap</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span> <span class="k">elif</span> <span class="n">op</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"load"</span><span class="p">:</span> <span class="c1"># ...</span> <span class="n">opt_bb</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">op</span><span class="p">)</span> <span class="k">return</span> <span class="n">opt_bb</span> </pre></div> <p>That makes our test pass—yay!—but at great cost. It means any store operation mucks up redundant loads. In our world where we frequently read from and write to objects, this is what we call a huge bummer.</p> <p>For example, a store to offset 4 on some object should never interfere with a load from a different offset on the same object<sup id="fnref:size"><a class="footnote-ref" href="https://www.pypy.org/posts/2025/12/toy-load-store.html#fn:size">1</a></sup>. We should be able to keep our load from offset 0 cached here:</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">test_store_to_same_object_different_offset_does_not_invalidate_load</span><span class="p">():</span> <span class="n">bb</span> <span class="o">=</span> <span class="n">Block</span><span class="p">()</span> <span class="n">var0</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">getarg</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="n">var1</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">var0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="n">var2</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">store</span><span class="p">(</span><span class="n">var0</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span> <span class="n">var3</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">var0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="n">bb</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">var1</span><span class="p">)</span> <span class="n">bb</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">var3</span><span class="p">)</span> <span class="n">opt_bb</span> <span class="o">=</span> <span class="n">optimize_load_store</span><span class="p">(</span><span class="n">bb</span><span class="p">)</span> <span class="k">assert</span> <span class="n">bb_to_str</span><span class="p">(</span><span class="n">opt_bb</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"""</span><span class="se">\</span> <span class="s2">var0 = getarg(0)</span> <span class="s2">var1 = load(var0, 0)</span> <span class="s2">var2 = store(var0, 4, 5)</span> <span class="s2">var3 = escape(var1)</span> <span class="s2">var4 = escape(var1)"""</span> </pre></div> <p>We could try instead checking if our specific (object, offset) pair is in the heap and only removing cached information about that offset and that object. That would definitely help!</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">optimize_load_store</span><span class="p">(</span><span class="n">bb</span><span class="p">:</span> <span class="n">Block</span><span class="p">):</span> <span class="n">opt_bb</span> <span class="o">=</span> <span class="n">Block</span><span class="p">()</span> <span class="n">compile_time_heap</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="n">Value</span><span class="p">,</span> <span class="nb">int</span><span class="p">],</span> <span class="n">Value</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span> <span class="k">for</span> <span class="n">op</span> <span class="ow">in</span> <span class="n">bb</span><span class="p">:</span> <span class="k">if</span> <span class="n">op</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"store"</span><span class="p">:</span> <span class="n">load_info</span> <span class="o">=</span> <span class="p">(</span><span class="n">op</span><span class="o">.</span><span class="n">arg</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="n">get_num</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span> <span class="k">if</span> <span class="n">load_info</span> <span class="ow">in</span> <span class="n">compile_time_heap</span><span class="p">:</span> <span class="k">del</span> <span class="n">compile_time_heap</span><span class="p">[</span><span class="n">load_info</span><span class="p">]</span> <span class="k">elif</span> <span class="n">op</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"load"</span><span class="p">:</span> <span class="c1"># ...</span> <span class="n">opt_bb</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">op</span><span class="p">)</span> <span class="k">return</span> <span class="n">opt_bb</span> </pre></div> <p>It makes our test pass, too, which is great news.</p> <p>Unfortunately, this runs into problems due to aliasing: it's entirely possible that our compile-time heap could contain a pair <code>(v0, 0)</code> and a pair <code>(v1, 0)</code> where <code>v0</code> and <code>v1</code> are the same object (but not known to the optimizer). Then we might run into a situation where we incorrectly cache loads because the optimizer doesn't know our abstract addresses <code>(v0, 0)</code> and <code>(v1, 0)</code> are actually the same pointer at run-time.</p> <p>This means that we are breaking abstract interpretation rules: our abstract interpreter has to correctly model <em>all</em> possible outcomes at run-time. This means to me that we should instead pick some tactic in-between clearing all information (correct but over-eager) and clearing only exact matches of object+offset (incorrect).</p> <p>The term that will help us here is called an <em>alias class</em>. It is a name for a way to efficiently partition objects in your abstract heap into completely disjoint sets. Writes to any object in one class never affect objects in another class.</p> <p>Our very scrappy alias classes will be just based on the offset: each offset is a different alias class. If we write to any object at offset K, we have to invalidate all of our compile-time offset K knowledge—even if it's for another object. This is a nice middle ground, and it's possible because our (made up) object system guarantees that distinct objects do not overlap, and also that we are not writing out-of-bounds.<sup id="fnref:tbaa"><a class="footnote-ref" href="https://www.pypy.org/posts/2025/12/toy-load-store.html#fn:tbaa">2</a></sup></p> <p>So let's remove all of the entries from <code>compile_time_heap</code> where the offset matches the offset in the current <code>store</code>:</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">optimize_load_store</span><span class="p">(</span><span class="n">bb</span><span class="p">:</span> <span class="n">Block</span><span class="p">):</span> <span class="n">opt_bb</span> <span class="o">=</span> <span class="n">Block</span><span class="p">()</span> <span class="n">compile_time_heap</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="n">Value</span><span class="p">,</span> <span class="nb">int</span><span class="p">],</span> <span class="n">Value</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span> <span class="k">for</span> <span class="n">op</span> <span class="ow">in</span> <span class="n">bb</span><span class="p">:</span> <span class="k">if</span> <span class="n">op</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"store"</span><span class="p">:</span> <span class="n">offset</span> <span class="o">=</span> <span class="n">get_num</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="n">compile_time_heap</span> <span class="o">=</span> <span class="p">{</span> <span class="n">load_info</span><span class="p">:</span> <span class="n">value</span> <span class="k">for</span> <span class="n">load_info</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">compile_time_heap</span><span class="o">.</span><span class="n">items</span><span class="p">()</span> <span class="k">if</span> <span class="n">load_info</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">!=</span> <span class="n">offset</span> <span class="p">}</span> <span class="k">elif</span> <span class="n">op</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"load"</span><span class="p">:</span> <span class="c1"># ...</span> <span class="n">opt_bb</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">op</span><span class="p">)</span> <span class="k">return</span> <span class="n">opt_bb</span> </pre></div> <p>Great! Now our test passes.</p> <p>This concludes the load optimization section of the post. We have modeled enough of loads and stores that we can eliminate redundant loads. Very cool. But we can go further.</p> <h3 id="caching-stores">Caching stores</h3> <p>Stores don't just invalidate information. They also give us new information! Any time we see an operation of the form <code>v1 = store(v0, 8, 5)</code> we also learn that <code>load(v0, 8) == 5</code>! Until it gets invalidated, anyway.</p> <p>For example, in this test, we can eliminate the load from <code>var0</code> at offset 0:</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">test_load_after_store_removed</span><span class="p">():</span> <span class="n">bb</span> <span class="o">=</span> <span class="n">Block</span><span class="p">()</span> <span class="n">var0</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">getarg</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="n">bb</span><span class="o">.</span><span class="n">store</span><span class="p">(</span><span class="n">var0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span> <span class="n">var1</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">var0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="n">var2</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">var0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="n">bb</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">var1</span><span class="p">)</span> <span class="n">bb</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">var2</span><span class="p">)</span> <span class="n">opt_bb</span> <span class="o">=</span> <span class="n">optimize_load_store</span><span class="p">(</span><span class="n">bb</span><span class="p">)</span> <span class="k">assert</span> <span class="n">bb_to_str</span><span class="p">(</span><span class="n">opt_bb</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"""</span><span class="se">\</span> <span class="s2">var0 = getarg(0)</span> <span class="s2">var1 = store(var0, 0, 5)</span> <span class="s2">var2 = load(var0, 1)</span> <span class="s2">var3 = escape(5)</span> <span class="s2">var4 = escape(var2)"""</span> </pre></div> <p>Making that work is thankfully not very hard; we need only add that new information to the compile-time heap after removing all the potentially-aliased info:</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">optimize_load_store</span><span class="p">(</span><span class="n">bb</span><span class="p">:</span> <span class="n">Block</span><span class="p">):</span> <span class="n">opt_bb</span> <span class="o">=</span> <span class="n">Block</span><span class="p">()</span> <span class="n">compile_time_heap</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="n">Value</span><span class="p">,</span> <span class="nb">int</span><span class="p">],</span> <span class="n">Value</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span> <span class="k">for</span> <span class="n">op</span> <span class="ow">in</span> <span class="n">bb</span><span class="p">:</span> <span class="k">if</span> <span class="n">op</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"store"</span><span class="p">:</span> <span class="n">offset</span> <span class="o">=</span> <span class="n">get_num</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="n">compile_time_heap</span> <span class="o">=</span> <span class="c1"># ... as before ...</span> <span class="n">obj</span> <span class="o">=</span> <span class="n">op</span><span class="o">.</span><span class="n">arg</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="n">new_value</span> <span class="o">=</span> <span class="n">op</span><span class="o">.</span><span class="n">arg</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="n">compile_time_heap</span><span class="p">[(</span><span class="n">obj</span><span class="p">,</span> <span class="n">offset</span><span class="p">)]</span> <span class="o">=</span> <span class="n">new_value</span> <span class="c1"># NEW!</span> <span class="k">elif</span> <span class="n">op</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"load"</span><span class="p">:</span> <span class="c1"># ...</span> <span class="n">opt_bb</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">op</span><span class="p">)</span> <span class="k">return</span> <span class="n">opt_bb</span> </pre></div> <p>This makes the test pass. It makes another test fail, but only because—oops—we now know more. You can delete the old test because the new test supersedes it.</p> <p>Now, note that we are not removing the store. This is because we have nothing in our optimizer that keeps track of what might have observed the side-effects of the store. What if the object got <code>escape</code>d? Or someone did a load later on? We would only be able to remove the store (<code>continue</code>) if we could guarantee it was not observable.</p> <p>In our current framework, this only happens in one case: someone is doing a store of the exact same value that already exists in our compile-time heap. That is, either the same constant, or the same SSA value. If we see this, then we can completely skip the second store instruction.</p> <p>Here's a test case for that, where we have gained information from the load instruction that we can then use to get rid of the store instruction:</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">test_load_then_store</span><span class="p">():</span> <span class="n">bb</span> <span class="o">=</span> <span class="n">Block</span><span class="p">()</span> <span class="n">arg1</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">getarg</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="n">var1</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="n">bb</span><span class="o">.</span><span class="n">store</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">var1</span><span class="p">)</span> <span class="n">bb</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">var1</span><span class="p">)</span> <span class="n">opt_bb</span> <span class="o">=</span> <span class="n">optimize_load_store</span><span class="p">(</span><span class="n">bb</span><span class="p">)</span> <span class="k">assert</span> <span class="n">bb_to_str</span><span class="p">(</span><span class="n">opt_bb</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"""</span><span class="se">\</span> <span class="s2">var0 = getarg(0)</span> <span class="s2">var1 = load(var0, 0)</span> <span class="s2">var2 = escape(var1)"""</span> </pre></div> <p>Let's make it pass. To do that, first we'll make an equality function that works for both constants and operations. Constants are equal if their values are equal, and operations are equal if they are the identical (by address/pointer) operation.</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">eq_value</span><span class="p">(</span><span class="n">left</span><span class="p">:</span> <span class="n">Value</span><span class="o">|</span><span class="kc">None</span><span class="p">,</span> <span class="n">right</span><span class="p">:</span> <span class="n">Value</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">left</span><span class="p">,</span> <span class="n">Constant</span><span class="p">)</span> <span class="ow">and</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">right</span><span class="p">,</span> <span class="n">Constant</span><span class="p">):</span> <span class="k">return</span> <span class="n">left</span><span class="o">.</span><span class="n">value</span> <span class="o">==</span> <span class="n">right</span><span class="o">.</span><span class="n">value</span> <span class="k">return</span> <span class="n">left</span> <span class="ow">is</span> <span class="n">right</span> </pre></div> <p>This is a partial equality: if two operations are not equal under <code>eq_value</code>, it doesn't mean that they are different, only that we don't know that they are the same.</p> <p>Then, after that, we need only check if the current value in the compile-time heap is the same as the value being stored in. If it is, wonderful. No need to store. <code>continue</code> and don't append the operation to <code>opt_bb</code>:</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">optimize_load_store</span><span class="p">(</span><span class="n">bb</span><span class="p">:</span> <span class="n">Block</span><span class="p">):</span> <span class="n">opt_bb</span> <span class="o">=</span> <span class="n">Block</span><span class="p">()</span> <span class="n">compile_time_heap</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="n">Value</span><span class="p">,</span> <span class="nb">int</span><span class="p">],</span> <span class="n">Value</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span> <span class="k">for</span> <span class="n">op</span> <span class="ow">in</span> <span class="n">bb</span><span class="p">:</span> <span class="k">if</span> <span class="n">op</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"store"</span><span class="p">:</span> <span class="n">obj</span> <span class="o">=</span> <span class="n">op</span><span class="o">.</span><span class="n">arg</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="n">offset</span> <span class="o">=</span> <span class="n">get_num</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="n">store_info</span> <span class="o">=</span> <span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">offset</span><span class="p">)</span> <span class="n">current_value</span> <span class="o">=</span> <span class="n">compile_time_heap</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">store_info</span><span class="p">)</span> <span class="n">new_value</span> <span class="o">=</span> <span class="n">op</span><span class="o">.</span><span class="n">arg</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="k">if</span> <span class="n">eq_value</span><span class="p">(</span><span class="n">current_value</span><span class="p">,</span> <span class="n">new_value</span><span class="p">):</span> <span class="c1"># NEW!</span> <span class="k">continue</span> <span class="n">compile_time_heap</span> <span class="o">=</span> <span class="c1"># ... as before ...</span> <span class="c1"># ...</span> <span class="k">elif</span> <span class="n">op</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">"load"</span><span class="p">:</span> <span class="n">load_info</span> <span class="o">=</span> <span class="p">(</span><span class="n">op</span><span class="o">.</span><span class="n">arg</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="n">get_num</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span> <span class="k">if</span> <span class="n">load_info</span> <span class="ow">in</span> <span class="n">compile_time_heap</span><span class="p">:</span> <span class="n">op</span><span class="o">.</span><span class="n">make_equal_to</span><span class="p">(</span><span class="n">compile_time_heap</span><span class="p">[</span><span class="n">load_info</span><span class="p">])</span> <span class="k">continue</span> <span class="n">compile_time_heap</span><span class="p">[</span><span class="n">load_info</span><span class="p">]</span> <span class="o">=</span> <span class="n">op</span> <span class="n">opt_bb</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">op</span><span class="p">)</span> <span class="k">return</span> <span class="n">opt_bb</span> </pre></div> <p>This makes our load-then-store pass and it also makes other tests pass too, like eliminating a store after another store!</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">test_store_after_store</span><span class="p">():</span> <span class="n">bb</span> <span class="o">=</span> <span class="n">Block</span><span class="p">()</span> <span class="n">arg1</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">getarg</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="n">bb</span><span class="o">.</span><span class="n">store</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span> <span class="n">bb</span><span class="o">.</span><span class="n">store</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span> <span class="n">opt_bb</span> <span class="o">=</span> <span class="n">optimize_load_store</span><span class="p">(</span><span class="n">bb</span><span class="p">)</span> <span class="k">assert</span> <span class="n">bb_to_str</span><span class="p">(</span><span class="n">opt_bb</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"""</span><span class="se">\</span> <span class="s2">var0 = getarg(0)</span> <span class="s2">var1 = store(var0, 0, 5)"""</span> </pre></div> <p>Unfortunately, this only works if the values—constants or SSA values—are known to be the same. If we store <em>different</em> values, we can't optimize. In the live stream, we left this an exercise for the viewer:</p> <div class="code"><pre class="code literal-block"><span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">xfail</span> <span class="k">def</span><span class="w"> </span><span class="nf">test_exercise_for_the_reader</span><span class="p">():</span> <span class="n">bb</span> <span class="o">=</span> <span class="n">Block</span><span class="p">()</span> <span class="n">arg0</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">getarg</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="n">var0</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">store</span><span class="p">(</span><span class="n">arg0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span> <span class="n">var1</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">store</span><span class="p">(</span><span class="n">arg0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">7</span><span class="p">)</span> <span class="n">var2</span> <span class="o">=</span> <span class="n">bb</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">arg0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="n">bb</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">var2</span><span class="p">)</span> <span class="n">opt_bb</span> <span class="o">=</span> <span class="n">optimize_load_store</span><span class="p">(</span><span class="n">bb</span><span class="p">)</span> <span class="k">assert</span> <span class="n">bb_to_str</span><span class="p">(</span><span class="n">opt_bb</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"""</span><span class="se">\</span> <span class="s2">var0 = getarg(0)</span> <span class="s2">var1 = store(var0, 0, 7)</span> <span class="s2">var2 = escape(7)"""</span> </pre></div> <p>We would only be able to optimize this away if we had some notion of a store being <em>dead</em>. In this case, that is a store in which the value is never read before being overwritten.</p> <h3 id="removing-dead-stores">Removing dead stores</h3> <p>TODO, I suppose. I have not gotten this far yet. If I get around to it, I will come back and update the post.</p> <h3 id="in-the-real-world">In the real world</h3> <p>This small optimization pass may seem silly or fiddly—when would we ever see something like this in a real IR?—but it's pretty useful. Here's the Ruby code that got me thinking about it again some years later for ZJIT:</p> <div class="code"><pre class="code literal-block"><span class="k">class</span><span class="w"> </span><span class="nc">C</span> <span class="w"> </span><span class="k">def</span><span class="w"> </span><span class="nf">initialize</span> <span class="w"> </span><span class="vi">@a</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span> <span class="w"> </span><span class="vi">@b</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2</span> <span class="w"> </span><span class="vi">@c</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">3</span> <span class="w"> </span><span class="k">end</span> <span class="k">end</span> </pre></div> <p>CRuby has a shape system and ZJIT makes use of it, so we end up optimizing this code (if it's monomorphic) into a series of shape checks and stores. The HIR might end up looking something like the mess below, where I've annotated the shape guards (can be thought of as loads) and stores with asterisks:</p> <div class="code"><pre class="code literal-block"><span class="n">fn</span><span class="w"> </span><span class="n">initialize</span><span class="p">@</span><span class="n">tmp</span><span class="o">/</span><span class="n">init</span><span class="p">.</span><span class="n">rb</span><span class="o">:</span><span class="mi">3</span><span class="o">:</span> <span class="cp"># ...</span> <span class="n">bb2</span><span class="p">(</span><span class="n">v6</span><span class="o">:</span><span class="n">BasicObject</span><span class="p">)</span><span class="o">:</span> <span class="w"> </span><span class="nl">v10</span><span class="p">:</span><span class="n">Fixnum</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Const</span><span class="w"> </span><span class="n">Value</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="w"> </span><span class="nl">v31</span><span class="p">:</span><span class="n">HeapBasicObject</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">GuardType</span><span class="w"> </span><span class="n">v6</span><span class="p">,</span><span class="w"> </span><span class="n">HeapBasicObject</span> <span class="o">*</span><span class="w"> </span><span class="n">v32</span><span class="o">:</span><span class="n">HeapBasicObject</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">GuardShape</span><span class="w"> </span><span class="n">v31</span><span class="p">,</span><span class="w"> </span><span class="mh">0x400000</span> <span class="o">*</span><span class="w"> </span><span class="n">StoreField</span><span class="w"> </span><span class="n">v32</span><span class="p">,</span><span class="w"> </span><span class="o">:</span><span class="p">@</span><span class="n">a</span><span class="mh">@0x10</span><span class="p">,</span><span class="w"> </span><span class="n">v10</span> <span class="w"> </span><span class="n">WriteBarrier</span><span class="w"> </span><span class="n">v32</span><span class="p">,</span><span class="w"> </span><span class="n">v10</span> <span class="w"> </span><span class="nl">v35</span><span class="p">:</span><span class="n">CShape</span><span class="p">[</span><span class="mh">0x40008e</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Const</span><span class="w"> </span><span class="n">CShape</span><span class="p">(</span><span class="mh">0x40008e</span><span class="p">)</span> <span class="o">*</span><span class="w"> </span><span class="n">StoreField</span><span class="w"> </span><span class="n">v32</span><span class="p">,</span><span class="w"> </span><span class="o">:</span><span class="n">_shape_id</span><span class="mh">@0x4</span><span class="p">,</span><span class="w"> </span><span class="n">v35</span> <span class="w"> </span><span class="nl">v16</span><span class="p">:</span><span class="n">Fixnum</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Const</span><span class="w"> </span><span class="n">Value</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="w"> </span><span class="nl">v37</span><span class="p">:</span><span class="n">HeapBasicObject</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">GuardType</span><span class="w"> </span><span class="n">v6</span><span class="p">,</span><span class="w"> </span><span class="n">HeapBasicObject</span> <span class="o">*</span><span class="w"> </span><span class="n">v38</span><span class="o">:</span><span class="n">HeapBasicObject</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">GuardShape</span><span class="w"> </span><span class="n">v37</span><span class="p">,</span><span class="w"> </span><span class="mh">0x40008e</span> <span class="o">*</span><span class="w"> </span><span class="n">StoreField</span><span class="w"> </span><span class="n">v38</span><span class="p">,</span><span class="w"> </span><span class="o">:</span><span class="p">@</span><span class="n">b</span><span class="mh">@0x18</span><span class="p">,</span><span class="w"> </span><span class="n">v16</span> <span class="w"> </span><span class="n">WriteBarrier</span><span class="w"> </span><span class="n">v38</span><span class="p">,</span><span class="w"> </span><span class="n">v16</span> <span class="w"> </span><span class="nl">v41</span><span class="p">:</span><span class="n">CShape</span><span class="p">[</span><span class="mh">0x40008f</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Const</span><span class="w"> </span><span class="n">CShape</span><span class="p">(</span><span class="mh">0x40008f</span><span class="p">)</span> <span class="o">*</span><span class="w"> </span><span class="n">StoreField</span><span class="w"> </span><span class="n">v38</span><span class="p">,</span><span class="w"> </span><span class="o">:</span><span class="n">_shape_id</span><span class="mh">@0x4</span><span class="p">,</span><span class="w"> </span><span class="n">v41</span> <span class="w"> </span><span class="nl">v22</span><span class="p">:</span><span class="n">Fixnum</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Const</span><span class="w"> </span><span class="n">Value</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="w"> </span><span class="nl">v43</span><span class="p">:</span><span class="n">HeapBasicObject</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">GuardType</span><span class="w"> </span><span class="n">v6</span><span class="p">,</span><span class="w"> </span><span class="n">HeapBasicObject</span> <span class="o">*</span><span class="w"> </span><span class="n">v44</span><span class="o">:</span><span class="n">HeapBasicObject</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">GuardShape</span><span class="w"> </span><span class="n">v43</span><span class="p">,</span><span class="w"> </span><span class="mh">0x40008f</span> <span class="o">*</span><span class="w"> </span><span class="n">StoreField</span><span class="w"> </span><span class="n">v44</span><span class="p">,</span><span class="w"> </span><span class="o">:</span><span class="p">@</span><span class="n">c</span><span class="mh">@0x20</span><span class="p">,</span><span class="w"> </span><span class="n">v22</span> <span class="w"> </span><span class="n">WriteBarrier</span><span class="w"> </span><span class="n">v44</span><span class="p">,</span><span class="w"> </span><span class="n">v22</span> <span class="w"> </span><span class="nl">v47</span><span class="p">:</span><span class="n">CShape</span><span class="p">[</span><span class="mh">0x400090</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Const</span><span class="w"> </span><span class="n">CShape</span><span class="p">(</span><span class="mh">0x400090</span><span class="p">)</span> <span class="o">*</span><span class="w"> </span><span class="n">StoreField</span><span class="w"> </span><span class="n">v44</span><span class="p">,</span><span class="w"> </span><span class="o">:</span><span class="n">_shape_id</span><span class="mh">@0x4</span><span class="p">,</span><span class="w"> </span><span class="n">v47</span> <span class="w"> </span><span class="n">CheckInterrupts</span> <span class="w"> </span><span class="n">Return</span><span class="w"> </span><span class="n">v22</span> </pre></div> <p>If we had store-load forwarding in ZJIT, we could get rid of the intermediate shape guards; they would know the shape from the previous <code>StoreField</code> instruction. If we had dead store elimination, we could get rid of the intermediate shape writes; they are never read. (And the repeated type guards to check if it's a heap object still are just silly and need to get removed eventually.)</p> <p>This is on the roadmap and will make object initialization even faster than it is right now.</p> <h3 id="wrapping-up">Wrapping up</h3> <p>Thanks for reading the text version of the video that CF and I made a while back. Now you know how to do load/store elimination on traces.</p> <p>I think this does not need too much extra work to get it going on full CFGs; a block is pretty much the same as a trace, so you can do a block-local version without much fuss. If you want to go global, you need dominator information and gen-kill sets.</p> <p>Maybe I will touch on this in a future post...</p> <h3 id="thank-you">Thank you</h3> <p>Thank you to CF, who walked me through this live on a stream two years ago! This blog post wouldn't be possible without you.</p> <div class="footnote"> <hr> <ol> <li id="fn:size"> <p>In this toy optimizer example, we are assuming that all reads and writes are the same size and different offsets don't overlap at all. This is often the case for managed runtimes, where object fields are pointer-sized and all reads/writes are pointed aligned. <a class="footnote-backref" href="https://www.pypy.org/posts/2025/12/toy-load-store.html#fnref:size" title="Jump back to footnote 1 in the text">↩</a></p> </li> <li id="fn:tbaa"> <p>We could do better. If we had type information, we could also use that to make alias classes. Writes to a List will never overlap with writes to a Map, for example. This requires your compiler to have strict aliasing—if you can freely cast between types, as in C, then this tactic goes out the window.</p> <p>This is called <a href="https://www.pypy.org/assets/img/tbaa.pdf">Type-based alias analysis</a> (PDF). <a class="footnote-backref" href="https://www.pypy.org/posts/2025/12/toy-load-store.html#fnref:tbaa" title="Jump back to footnote 2 in the text">↩</a></p> </li> </ol> </div>toy-optimizerhttps://www.pypy.org/posts/2025/12/toy-load-store.htmlWed, 24 Dec 2025 23:00:00 GMTAquileo | PyPy v7.3.20 releasehttps://www.pypy.org/posts/2025/07/pypy-v7320-release.htmlmattip<section id="pypy-v7-3-20-release-of-python-2-7-3-11"> <h2>PyPy v7.3.20: release of python 2.7, 3.11</h2> <p>The PyPy team is proud to release version 7.3.20 of PyPy after the previous release on Feb 26, 2025. The release fixes some subtle bugs in ctypes and <code class="docutils literal">OrderedDict</code> and makes PyPy3.11 compatible with an upcoming release of Cython.</p> <p>The release includes two different interpreters:</p> <ul class="simple"> <li><p>PyPy2.7, which is an interpreter supporting the syntax and the features of Python 2.7 including the stdlib for CPython 2.7.18+ (the <code class="docutils literal">+</code> is for backported security updates)</p></li> <li><p>PyPy3.11, which is an interpreter supporting the syntax and the features of Python 3.11, including the stdlib for CPython 3.11.13.</p></li> </ul> <p>The interpreters are based on much the same codebase, thus the double release. This is a micro release, all APIs are compatible with the other 7.3 releases.</p> <p>We recommend updating. You can find links to download the releases here:</p> <blockquote> <p><a class="reference external" href="https://pypy.org/download.html">https://pypy.org/download.html</a></p> </blockquote> <p>We would like to thank our donors for the continued support of the PyPy project. If PyPy is not quite good enough for your needs, we are available for <a class="reference external" href="https://www.pypy.org/pypy-sponsors.html">direct consulting</a> work. If PyPy is helping you out, we would love to hear about it and encourage submissions to our <a class="reference external" href="https://pypy.org/blog">blog</a> via a pull request to <a class="reference external" href="https://github.com/pypy/pypy.org">https://github.com/pypy/pypy.org</a></p> <p>We would also like to thank our contributors and encourage new people to join the project. PyPy has many layers and we need help with all of them: bug fixes, <a class="reference external" href="https://doc.pypy.org/">PyPy</a> and <a class="reference external" href="https://rpython.readthedocs.org">RPython</a> documentation improvements, or general <a class="reference external" href="https://doc.pypy.org/project-ideas.html">help</a> with making RPython's JIT even better.</p> <p>If you are a python library maintainer and use C-extensions, please consider making a <a class="reference external" href="https://hpyproject.org/">HPy</a> / <a class="reference external" href="https://cffi.readthedocs.io">CFFI</a> / <a class="reference external" href="https://cppyy.readthedocs.io">cppyy</a> version of your library that would be performant on PyPy. In any case, <a class="reference external" href="https://github.com/joerick/cibuildwheel">cibuildwheel</a> supports building wheels for PyPy.</p> <section id="what-is-pypy"> <h3>What is PyPy?</h3> <p>PyPy is a Python interpreter, a drop-in replacement for CPython It's fast (<a class="reference external" href="https://speed.pypy.org">PyPy and CPython</a> performance comparison) due to its integrated tracing JIT compiler.</p> <p>We also welcome developers of other <a class="reference external" href="https://rpython.readthedocs.io/en/latest/examples.html">dynamic languages</a> to see what RPython can do for them.</p> <p>We provide binary builds for:</p> <ul class="simple"> <li><p><strong>x86</strong> machines on most common operating systems (Linux 32/64 bits, Mac OS 64 bits, Windows 64 bits)</p></li> <li><p>64-bit <strong>ARM</strong> machines running Linux (<code class="docutils literal">aarch64</code>) and macos (<code class="docutils literal">macos_arm64</code>).</p></li> </ul> <p>PyPy supports Windows 32-bit, Linux PPC64 big- and little-endian, Linux ARM 32 bit, RISC-V RV64IMAFD Linux, and s390x Linux but does not release binaries. Please reach out to us if you wish to sponsor binary releases for those platforms. Downstream packagers provide binary builds for debian, Fedora, conda, OpenBSD, FreeBSD, Gentoo, and more.</p> </section> <section id="what-else-is-new"> <h3>What else is new?</h3> <p>For more information about the 7.3.20 release, see the <a class="reference external" href="https://doc.pypy.org/release-v7.3.20.html#changelog">full changelog</a>.</p> <p>Please update, and continue to help us make pypy better.</p> <p>Cheers, The PyPy Team</p> </section> </section>releasehttps://www.pypy.org/posts/2025/07/pypy-v7320-release.htmlFri, 04 Jul 2025 12:00:00 GMTAquileo | How fast can the RPython GC allocate?https://www.pypy.org/posts/2025/06/rpython-gc-allocation-speed.htmlCF Bolz-Tereick<p>While working on a paper about <a href="https://pypy.org/posts/2025/02/pypy-gc-sampling.html">allocation profiling in VMProf</a> I got curious about how quickly the RPython GC can allocate an object. I wrote a small RPython benchmark program to get an idea of the order of magnitude.</p> <p>The basic idea is to just allocate an instance in a tight loop:</p> <div class="code"><pre class="code literal-block"><span class="k">class</span><span class="w"> </span><span class="nc">A</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span> <span class="k">pass</span> <span class="k">def</span><span class="w"> </span><span class="nf">run</span><span class="p">(</span><span class="n">loops</span><span class="p">):</span> <span class="c1"># preliminary idea, see below</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">loops</span><span class="p">):</span> <span class="n">a</span> <span class="o">=</span> <span class="n">A</span><span class="p">()</span> <span class="n">a</span><span class="o">.</span><span class="n">i</span> <span class="o">=</span> <span class="n">i</span> </pre></div> <p>The RPython type inference will find out that instances of <code>A</code> have a single <code>i</code> field, which is an integer. In addition to that field, every RPython object needs one word of GC meta-information. Therefore one instance of <code>A</code> needs 16 bytes on a 64-bit architecture.</p> <p>However, measuring like this is not good enough, because the RPython static optimizer would remove the allocation since the object isn't used. But we can confuse the escape analysis sufficiently by always keeping two instances alive at the same time:</p> <div class="code"><pre class="code literal-block"><span class="k">class</span><span class="w"> </span><span class="nc">A</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span> <span class="k">pass</span> <span class="k">def</span><span class="w"> </span><span class="nf">run</span><span class="p">(</span><span class="n">loops</span><span class="p">):</span> <span class="n">a</span> <span class="o">=</span> <span class="n">prev</span> <span class="o">=</span> <span class="kc">None</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">loops</span><span class="p">):</span> <span class="n">prev</span> <span class="o">=</span> <span class="n">a</span> <span class="n">a</span> <span class="o">=</span> <span class="n">A</span><span class="p">()</span> <span class="n">a</span><span class="o">.</span><span class="n">i</span> <span class="o">=</span> <span class="n">i</span> <span class="nb">print</span><span class="p">(</span><span class="n">prev</span><span class="p">,</span> <span class="n">a</span><span class="p">)</span> <span class="c1"># print the instances at the end</span> </pre></div> <p>(I confirmed that the allocation isn't being removed by looking at the C code that the RPython compiler generates from this.)</p> <p>This is doing a little bit more work than needed, because of the <code>a.i = i</code> instance attribute write. We can also (optionally) leave the field uninitialized.</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">run</span><span class="p">(</span><span class="n">initialize_field</span><span class="p">,</span> <span class="n">loops</span><span class="p">):</span> <span class="n">t1</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="k">if</span> <span class="n">initialize_field</span><span class="p">:</span> <span class="n">a</span> <span class="o">=</span> <span class="n">prev</span> <span class="o">=</span> <span class="kc">None</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">loops</span><span class="p">):</span> <span class="n">prev</span> <span class="o">=</span> <span class="n">a</span> <span class="n">a</span> <span class="o">=</span> <span class="n">A</span><span class="p">()</span> <span class="n">a</span><span class="o">.</span><span class="n">i</span> <span class="o">=</span> <span class="n">i</span> <span class="nb">print</span><span class="p">(</span><span class="n">prev</span><span class="p">,</span> <span class="n">a</span><span class="p">)</span> <span class="c1"># make sure always two objects are alive</span> <span class="k">else</span><span class="p">:</span> <span class="n">a</span> <span class="o">=</span> <span class="n">prev</span> <span class="o">=</span> <span class="kc">None</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">loops</span><span class="p">):</span> <span class="n">prev</span> <span class="o">=</span> <span class="n">a</span> <span class="n">a</span> <span class="o">=</span> <span class="n">A</span><span class="p">()</span> <span class="nb">print</span><span class="p">(</span><span class="n">prev</span><span class="p">,</span> <span class="n">a</span><span class="p">)</span> <span class="n">t2</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="nb">print</span><span class="p">(</span><span class="n">t2</span> <span class="o">-</span> <span class="n">t1</span><span class="p">,</span> <span class="s1">'s'</span><span class="p">)</span> <span class="n">object_size_in_words</span> <span class="o">=</span> <span class="mi">2</span> <span class="c1"># GC header, one integer field</span> <span class="n">mem</span> <span class="o">=</span> <span class="n">loops</span> <span class="o">*</span> <span class="mi">8</span> <span class="o">*</span> <span class="n">object_size_in_words</span> <span class="o">/</span> <span class="mf">1024.0</span> <span class="o">/</span> <span class="mf">1024.0</span> <span class="o">/</span> <span class="mf">1024.0</span> <span class="nb">print</span><span class="p">(</span><span class="n">mem</span><span class="p">,</span> <span class="s1">'GB'</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="n">mem</span> <span class="o">/</span> <span class="p">(</span><span class="n">t2</span> <span class="o">-</span> <span class="n">t1</span><span class="p">),</span> <span class="s1">'GB/s'</span><span class="p">)</span> </pre></div> <p>Then we need to add some RPython scaffolding:</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">argv</span><span class="p">):</span> <span class="n">loops</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="n">with_init</span> <span class="o">=</span> <span class="nb">bool</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]))</span> <span class="k">if</span> <span class="n">with_init</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="s2">"with initialization"</span><span class="p">)</span> <span class="k">else</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="s2">"without initialization"</span><span class="p">)</span> <span class="n">run</span><span class="p">(</span><span class="n">with_init</span><span class="p">,</span> <span class="n">loops</span><span class="p">)</span> <span class="k">return</span> <span class="mi">0</span> <span class="k">def</span><span class="w"> </span><span class="nf">target</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">):</span> <span class="k">return</span> <span class="n">main</span> </pre></div> <p>To build a binary:</p> <div class="code"><pre class="code literal-block"><span class="go">pypy rpython/bin/rpython targetallocatealot.py</span> </pre></div> <p>Which will turn the RPython code into C code and use a C compiler to turn that into a binary, containing both our code above as well as the RPython garbage collector.</p> <p>Then we can run it (all results again from my AMD Ryzen 7 PRO 7840U, running Ubuntu Linux 24.04.2):</p> <div class="code"><pre class="code literal-block"><span class="gp">$ </span>./targetallocatealot-c<span class="w"> </span><span class="m">1000000000</span><span class="w"> </span><span class="m">0</span> <span class="go">without initialization</span> <span class="go">&lt;A object at 0x7c71ad84cf60&gt; &lt;A object at 0x7c71ad84cf70&gt;</span> <span class="go">0.433825 s</span> <span class="go">14.901161 GB</span> <span class="go">34.348322 GB/s</span> <span class="gp">$ </span>./targetallocatealot-c<span class="w"> </span><span class="m">1000000000</span><span class="w"> </span><span class="m">1</span> <span class="go">with initialization</span> <span class="go">&lt;A object at 0x71b41c82cf60&gt; &lt;A object at 0x71b41c82cf70&gt;</span> <span class="go">0.501856 s</span> <span class="go">14.901161 GB</span> <span class="go">29.692100 GB/s</span> </pre></div> <p>Let's compare it with the Boehm GC:</p> <div class="code"><pre class="code literal-block"><span class="gp">$ </span>pypy<span class="w"> </span>rpython/bin/rpython<span class="w"> </span>--gc<span class="o">=</span>boehm<span class="w"> </span>--output<span class="o">=</span>targetallocatealot-c-boehm<span class="w"> </span>targetallocatealot.py<span class="w"> </span> <span class="go">...</span> <span class="gp">$ </span>./targetallocatealot-c-boehm<span class="w"> </span><span class="m">1000000000</span><span class="w"> </span><span class="m">0</span> <span class="go">without initialization</span> <span class="go">&lt;A object at 0xffff8bd058a6e3af&gt; &lt;A object at 0xffff8bd058a6e3bf&gt;</span> <span class="go">9.722585 s</span> <span class="go">14.901161 GB</span> <span class="go">1.532634 GB/s</span> <span class="gp">$ </span>./targetallocatealot-c-boehm<span class="w"> </span><span class="m">1000000000</span><span class="w"> </span><span class="m">1</span> <span class="go">with initialization</span> <span class="go">&lt;A object at 0xffff88e1132983af&gt; &lt;A object at 0xffff88e1132983bf&gt;</span> <span class="go">9.684149 s</span> <span class="go">14.901161 GB</span> <span class="go">1.538717 GB/s</span> </pre></div> <p>This is not a fair comparison, because the Boehm GC uses conservative stack scanning, therefore it cannot move objects, which requires much more complicated allocation.</p> <h3 id="lets-look-at-perf-stats">Let's look at <code>perf stats</code></h3> <p>We can use <code>perf</code> to get some statistics about the executions:</p> <div class="code"><pre class="code literal-block"><span class="gp">$ </span>perf<span class="w"> </span>stat<span class="w"> </span>-e<span class="w"> </span>cache-references,cache-misses,cycles,instructions,branches,faults,migrations<span class="w"> </span>./targetallocatealot-c<span class="w"> </span><span class="m">10000000000</span><span class="w"> </span><span class="m">0</span> <span class="go">without initialization</span> <span class="go">&lt;A object at 0x7aa260e35980&gt; &lt;A object at 0x7aa260e35990&gt;</span> <span class="go">4.301442 s</span> <span class="go">149.011612 GB</span> <span class="go">34.642245 GB/s</span> <span class="go"> Performance counter stats for './targetallocatealot-c 10000000000 0':</span> <span class="go"> 7,244,117,828 cache-references </span> <span class="go"> 23,446,661 cache-misses # 0.32% of all cache refs </span> <span class="go"> 21,074,240,395 cycles </span> <span class="go"> 110,116,790,943 instructions # 5.23 insn per cycle </span> <span class="go"> 20,024,347,488 branches </span> <span class="go"> 1,287 faults </span> <span class="go"> 24 migrations </span> <span class="go"> 4.303071693 seconds time elapsed</span> <span class="go"> 4.297557000 seconds user</span> <span class="go"> 0.003998000 seconds sys</span> <span class="gp">$ </span>perf<span class="w"> </span>stat<span class="w"> </span>-e<span class="w"> </span>cache-references,cache-misses,cycles,instructions,branches,faults,migrations<span class="w"> </span>./targetallocatealot-c<span class="w"> </span><span class="m">10000000000</span><span class="w"> </span><span class="m">1</span> <span class="go">with initialization</span> <span class="go">&lt;A object at 0x77ceb0235980&gt; &lt;A object at 0x77ceb0235990&gt;</span> <span class="go">5.016772 s</span> <span class="go">149.011612 GB</span> <span class="go">29.702688 GB/s</span> <span class="go"> Performance counter stats for './targetallocatealot-c 10000000000 1':</span> <span class="go"> 7,571,461,470 cache-references </span> <span class="go"> 241,915,266 cache-misses # 3.20% of all cache refs </span> <span class="go"> 24,503,497,532 cycles </span> <span class="go"> 130,126,387,460 instructions # 5.31 insn per cycle </span> <span class="go"> 20,026,280,693 branches </span> <span class="go"> 1,285 faults </span> <span class="go"> 21 migrations </span> <span class="go"> 5.019444749 seconds time elapsed</span> <span class="go"> 5.012924000 seconds user</span> <span class="go"> 0.005999000 seconds sys</span> </pre></div> <p>This is pretty cool, we can run this loop with &gt;5 instructions per cycle. Every allocation takes <code>110116790943 / 10000000000 ≈ 11</code> instructions and <code>21074240395 / 10000000000 ≈ 2.1</code> cycles, including the loop around it.</p> <h3 id="how-often-does-the-gc-run">How often does the GC run?</h3> <p>The RPython GC queries the L2 cache size to determine the size of the nursery. We can find out what it is by turning on PYPYLOG, selecting the proper logging categories, and printing to <code>stdout</code> via <code>:-</code>:</p> <div class="code"><pre class="code literal-block"><span class="gp">$ </span><span class="nv">PYPYLOG</span><span class="o">=</span>gc-set-nursery-size,gc-hardware:-<span class="w"> </span>./targetallocatealot-c<span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="m">1</span> <span class="go">[f3e6970465723] {gc-set-nursery-size</span> <span class="go">nursery size: 270336</span> <span class="go">[f3e69704758f3] gc-set-nursery-size}</span> <span class="go">[f3e697047b9a1] {gc-hardware</span> <span class="go">L2cache = 1048576</span> <span class="go">[f3e69705ced19] gc-hardware}</span> <span class="go">[f3e69705d11b5] {gc-hardware</span> <span class="go">memtotal = 32274210816.000000</span> <span class="go">[f3e69705f4948] gc-hardware}</span> <span class="go">[f3e6970615f78] {gc-set-nursery-size</span> <span class="go">nursery size: 4194304</span> <span class="go">[f3e697061ecc0] gc-set-nursery-size}</span> <span class="go">with initialization</span> <span class="go">NULL &lt;A object at 0x7fa7b1434020&gt;</span> <span class="go">0.000008 s</span> <span class="go">0.000000 GB</span> <span class="go">0.001894 GB/s</span> </pre></div> <p>So the nursery is 4 MiB. This means that when we allocate 14.9 GiB the GC needs to perform <code>10000000000 * 16 / 4194304 ≈ 38146</code> minor collections. Let's confirm that:</p> <div class="code"><pre class="code literal-block"><span class="gp">$ </span><span class="nv">PYPYLOG</span><span class="o">=</span>gc-minor:out<span class="w"> </span>./targetallocatealot-c<span class="w"> </span><span class="m">10000000000</span><span class="w"> </span><span class="m">1</span> <span class="go">with initialization</span> <span class="go">w&lt;A object at 0x7991e3835980&gt; &lt;A object at 0x7991e3835990&gt;</span> <span class="go">5.315511 s</span> <span class="go">149.011612 GB</span> <span class="go">28.033356 GB/s</span> <span class="gp">$ </span>head<span class="w"> </span>out <span class="go">[f3ee482f4cd97] {gc-minor</span> <span class="go">[f3ee482f53874] {gc-minor-walkroots</span> <span class="go">[f3ee482f54117] gc-minor-walkroots}</span> <span class="go">minor collect, total memory used: 0</span> <span class="go">number of pinned objects: 0</span> <span class="go">total size of surviving objects: 0</span> <span class="go">time taken: 0.000029</span> <span class="go">[f3ee482f67b7e] gc-minor}</span> <span class="go">[f3ee4838097c5] {gc-minor</span> <span class="go">[f3ee48380c945] {gc-minor-walkroots</span> <span class="gp">$ </span>grep<span class="w"> </span><span class="s2">"{gc-minor-walkroots"</span><span class="w"> </span>out<span class="w"> </span><span class="p">|</span><span class="w"> </span>wc<span class="w"> </span>-l <span class="go">38147</span> </pre></div> <p>Each minor collection is very quick, because a minor collection is O(surviving objects), and in this program only one object survive each time (the other instance is in the process of being allocated). Also, the GC root shadow stack is only one entry, so walking that is super quick as well. The time the minor collections take is logged to the out file:</p> <div class="code"><pre class="code literal-block"><span class="gp">$ </span>grep<span class="w"> </span><span class="s2">"time taken"</span><span class="w"> </span>out<span class="w"> </span><span class="p">|</span><span class="w"> </span>tail <span class="go">time taken: 0.000002</span> <span class="go">time taken: 0.000002</span> <span class="go">time taken: 0.000002</span> <span class="go">time taken: 0.000002</span> <span class="go">time taken: 0.000002</span> <span class="go">time taken: 0.000002</span> <span class="go">time taken: 0.000002</span> <span class="go">time taken: 0.000003</span> <span class="go">time taken: 0.000002</span> <span class="go">time taken: 0.000002</span> <span class="gp">$ </span>grep<span class="w"> </span><span class="s2">"time taken"</span><span class="w"> </span>out<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-o<span class="w"> </span><span class="s2">"0.*"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>numsum <span class="go">0.0988160000000011</span> </pre></div> <p>(This number is super approximate due to float formatting rounding.)</p> <p>that means that <code>0.0988160000000011 / 5.315511 ≈ 2%</code> of the time is spent in the GC.</p> <h3 id="what-does-the-generated-machine-code-look-like">What does the generated machine code look like?</h3> <p>The allocation fast path of the RPython GC is a simple bump pointer, in Python pseudo-code it would look roughly like this:</p> <div class="code"><pre class="code literal-block"><span class="n">result</span> <span class="o">=</span> <span class="n">gc</span><span class="o">.</span><span class="n">nursery_free</span> <span class="c1"># Move nursery_free pointer forward by totalsize</span> <span class="n">gc</span><span class="o">.</span><span class="n">nursery_free</span> <span class="o">=</span> <span class="n">result</span> <span class="o">+</span> <span class="n">totalsize</span> <span class="c1"># Check if this allocation would exceed the nursery</span> <span class="k">if</span> <span class="n">gc</span><span class="o">.</span><span class="n">nursery_free</span> <span class="o">&gt;</span> <span class="n">gc</span><span class="o">.</span><span class="n">nursery_top</span><span class="p">:</span> <span class="c1"># If it does =&gt; collect the nursery and al</span> <span class="n">result</span> <span class="o">=</span> <span class="n">collect_and_reserve</span><span class="p">(</span><span class="n">totalsize</span><span class="p">)</span> <span class="n">result</span><span class="o">.</span><span class="n">hdr</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">GC</span> <span class="n">flags</span> <span class="ow">and</span> <span class="nb">type</span> <span class="nb">id</span> <span class="n">of</span> <span class="n">A</span><span class="o">&gt;</span> </pre></div> <p>So we can disassemble the compiled binary <code>targetallocatealot-c</code> and try to find the equivalent logic in machine code. I'm super bad at reading machine code, but I tried to annotate what I think is the core loop (the version without initializing the <code>i</code> field) below:</p> <div class="code"><pre class="code literal-block"><span class="w"> </span><span class="p">...</span> <span class="w"> </span><span class="nl">cb68</span><span class="p">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="o">%</span><span class="n">rbx</span><span class="p">,</span><span class="o">%</span><span class="n">rdi</span><span class="w"> </span> <span class="w"> </span><span class="nl">cb6b</span><span class="p">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="o">%</span><span class="n">rdx</span><span class="p">,</span><span class="o">%</span><span class="n">rbx</span> <span class="w"> </span><span class="cp"># initialize object header of object allocated in previous iteration</span> <span class="w"> </span><span class="nl">cb6e</span><span class="p">:</span><span class="w"> </span><span class="n">movq</span><span class="w"> </span><span class="n">$0x4c8</span><span class="p">,(</span><span class="o">%</span><span class="n">rbx</span><span class="p">)</span> <span class="w"> </span><span class="cp"># loop termination check</span> <span class="w"> </span><span class="nl">cb75</span><span class="p">:</span><span class="w"> </span><span class="n">cmp</span><span class="w"> </span><span class="o">%</span><span class="n">rbp</span><span class="p">,</span><span class="o">%</span><span class="n">r12</span> <span class="w"> </span><span class="nl">cb78</span><span class="p">:</span><span class="w"> </span><span class="n">je</span><span class="w"> </span><span class="n">ccb8</span> <span class="w"> </span><span class="cp"># load nursery_free</span> <span class="w"> </span><span class="nl">cb7e</span><span class="p">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="mh">0x33c13</span><span class="p">(</span><span class="o">%</span><span class="n">rip</span><span class="p">),</span><span class="o">%</span><span class="n">rdx</span> <span class="w"> </span><span class="cp"># increment loop counter</span> <span class="w"> </span><span class="nl">cb85</span><span class="p">:</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">$0x1</span><span class="p">,</span><span class="o">%</span><span class="n">rbp</span> <span class="w"> </span><span class="cp"># add 16 (size of object) to nursery_free</span> <span class="w"> </span><span class="nl">cb89</span><span class="p">:</span><span class="w"> </span><span class="n">lea</span><span class="w"> </span><span class="mh">0x10</span><span class="p">(</span><span class="o">%</span><span class="n">rdx</span><span class="p">),</span><span class="o">%</span><span class="n">rax</span> <span class="w"> </span><span class="cp"># compare nursery_top with new nursery_free</span> <span class="w"> </span><span class="nl">cb8d</span><span class="p">:</span><span class="w"> </span><span class="n">cmp</span><span class="w"> </span><span class="o">%</span><span class="n">rax</span><span class="p">,</span><span class="mh">0x33c24</span><span class="p">(</span><span class="o">%</span><span class="n">rip</span><span class="p">)</span> <span class="w"> </span><span class="cp"># store new nursery_free</span> <span class="w"> </span><span class="nl">cb94</span><span class="p">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="o">%</span><span class="n">rax</span><span class="p">,</span><span class="mh">0x33bfd</span><span class="p">(</span><span class="o">%</span><span class="n">rip</span><span class="p">)</span> <span class="w"> </span><span class="cp"># if new nursery_free exceeds nursery_top, fall through to slow path, if not, start at top</span> <span class="w"> </span><span class="nl">cb9b</span><span class="p">:</span><span class="w"> </span><span class="n">jae</span><span class="w"> </span><span class="n">cb68</span> <span class="w"> </span><span class="cp"># slow path from here on:</span> <span class="w"> </span><span class="cp"># save live object from last iteration to GC shadow stack</span> <span class="w"> </span><span class="nl">cb9d</span><span class="p">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="o">%</span><span class="n">rbx</span><span class="p">,</span><span class="mh">-0x8</span><span class="p">(</span><span class="o">%</span><span class="n">rcx</span><span class="p">)</span> <span class="w"> </span><span class="nl">cba1</span><span class="p">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="o">%</span><span class="n">r13</span><span class="p">,</span><span class="o">%</span><span class="n">rdi</span> <span class="w"> </span><span class="nl">cba4</span><span class="p">:</span><span class="w"> </span><span class="n">mov</span><span class="w"> </span><span class="n">$0x10</span><span class="p">,</span><span class="o">%</span><span class="n">esi</span> <span class="w"> </span><span class="cp"># do minor collection</span> <span class="w"> </span><span class="nl">cba9</span><span class="p">:</span><span class="w"> </span><span class="n">call</span><span class="w"> </span><span class="mi">20800</span><span class="w"> </span><span class="o">&lt;</span><span class="n">pypy_g_IncrementalMiniMarkGC_collect_and_reserve</span><span class="o">&gt;</span> <span class="w"> </span><span class="p">...</span> </pre></div> <h3 id="running-the-benchmark-as-regular-python-code">Running the benchmark as regular Python code</h3> <p>So far we ran this code as <em>RPython</em>, i.e. type inference is performed and the program is translated to a C binary. We can also run it on top of PyPy, as a regular Python3 program. However, an instance of a user-defined class in regular Python when run on PyPy is actually a much larger object, due to <a href="https://pypy.org/posts/2010/11/efficiently-implementing-python-objects-3838329944323946932.html">dynamic typing</a>. It's at least 7 words, which is 56 bytes.</p> <p>However, we can simply use <code>int</code> objects instead. Integers are allocated on the heap and consist of two words, one for the GC and one with the machine-word-sized integer value, if the integer fits into a signed 64-bit representation (otherwise a less compact different representation is used, which can represent arbitrarily large integers).</p> <p>Therefore, we can simply use this kind of code:</p> <div class="code"><pre class="code literal-block"><span class="kn">import</span><span class="w"> </span><span class="nn">sys</span><span class="o">,</span><span class="w"> </span><span class="nn">time</span> <span class="k">def</span><span class="w"> </span><span class="nf">run</span><span class="p">(</span><span class="n">loops</span><span class="p">):</span> <span class="n">t1</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="n">a</span> <span class="o">=</span> <span class="n">prev</span> <span class="o">=</span> <span class="kc">None</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">loops</span><span class="p">):</span> <span class="n">prev</span> <span class="o">=</span> <span class="n">a</span> <span class="n">a</span> <span class="o">=</span> <span class="n">i</span> <span class="nb">print</span><span class="p">(</span><span class="n">prev</span><span class="p">,</span> <span class="n">a</span><span class="p">)</span> <span class="c1"># make sure always two objects are alive</span> <span class="n">t2</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="n">object_size_in_words</span> <span class="o">=</span> <span class="mi">2</span> <span class="c1"># GC header, one integer field</span> <span class="n">mem</span> <span class="o">=</span> <span class="n">loops</span> <span class="o">*</span> <span class="mi">28</span> <span class="o">/</span> <span class="mf">1024.0</span> <span class="o">/</span> <span class="mf">1024.0</span> <span class="o">/</span> <span class="mf">1024.0</span> <span class="nb">print</span><span class="p">(</span><span class="n">mem</span><span class="p">,</span> <span class="s1">'GB'</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="n">mem</span> <span class="o">/</span> <span class="p">(</span><span class="n">t2</span> <span class="o">-</span> <span class="n">t1</span><span class="p">),</span> <span class="s1">'GB/s'</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">main</span><span class="p">(</span><span class="n">argv</span><span class="p">):</span> <span class="n">loops</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="n">run</span><span class="p">(</span><span class="n">loops</span><span class="p">)</span> <span class="k">return</span> <span class="mi">0</span> <span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span> <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">main</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">))</span> </pre></div> <p>In this case we can't really leave the value uninitialized though.</p> <p>We can run this both with and without the JIT:</p> <div class="code"><pre class="code literal-block"><span class="gp">$ </span>pypy3<span class="w"> </span>allocatealot.py<span class="w"> </span><span class="m">1000000000</span> <span class="go">999999998 999999999</span> <span class="go">14.901161193847656 GB</span> <span class="go">17.857494904899553 GB/s</span> <span class="gp">$ </span>pypy3<span class="w"> </span>--jit<span class="w"> </span>off<span class="w"> </span>allocatealot.py<span class="w"> </span><span class="m">1000000000</span> <span class="go">999999998 999999999</span> <span class="go">14.901161193847656 GB</span> <span class="go">0.8275382375297171 GB/s</span> </pre></div> <p>This is obviously much less efficient than the C code, the PyPy JIT generates much less efficient machine code than GCC. Still, "only" twice as slow is kind of cool anyway.</p> <p>(Running it with CPython doesn't really make sense for this measurements, since CPython ints are bigger – <code>sys.getsizeof(5)</code> reports 28 bytes.)</p> <h3 id="the-machine-code-that-the-jit-generates">The machine code that the JIT generates</h3> <p>Unfortunately it's a bit of a journey to show the machine code that PyPy's JIT generates for this. First we need to run with all jit logging categories:</p> <div class="code"><pre class="code literal-block"><span class="gp">$ </span><span class="nv">PYPYLOG</span><span class="o">=</span>jit:out<span class="w"> </span>pypy3<span class="w"> </span>allocatealot.py<span class="w"> </span><span class="m">1000000000</span> </pre></div> <p>Then we can read the log file to find the trace IR for the loop under the logging category <code>jit-log-opt</code>:</p> <div class="code"><pre class="code literal-block"><span class="o">+</span><span class="mi">532</span><span class="p">:</span><span class="w"> </span><span class="n">label</span><span class="p">(</span><span class="n">p0</span><span class="p">,</span><span class="w"> </span><span class="n">p1</span><span class="p">,</span><span class="w"> </span><span class="n">p6</span><span class="p">,</span><span class="w"> </span><span class="n">p9</span><span class="p">,</span><span class="w"> </span><span class="n">p11</span><span class="p">,</span><span class="w"> </span><span class="n">i34</span><span class="p">,</span><span class="w"> </span><span class="n">p13</span><span class="p">,</span><span class="w"> </span><span class="n">p19</span><span class="p">,</span><span class="w"> </span><span class="n">p21</span><span class="p">,</span><span class="w"> </span><span class="n">p23</span><span class="p">,</span><span class="w"> </span><span class="n">p25</span><span class="p">,</span><span class="w"> </span><span class="n">p29</span><span class="p">,</span><span class="w"> </span><span class="n">p31</span><span class="p">,</span><span class="w"> </span><span class="n">i44</span><span class="p">,</span><span class="w"> </span><span class="n">i35</span><span class="p">,</span><span class="w"> </span><span class="n">descr</span><span class="o">=</span><span class="n">TargetToken</span><span class="p">(</span><span class="mi">137358545605472</span><span class="p">))</span> <span class="n">debug_merge_point</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="s1">'run;/home/cfbolz/projects/gitpypy/allocatealot.py:6-9~#24 FOR_ITER'</span><span class="p">)</span> <span class="c1"># are we at the end of the loop</span> <span class="o">+</span><span class="mi">552</span><span class="p">:</span><span class="w"> </span><span class="n">i45</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">int_lt</span><span class="p">(</span><span class="n">i44</span><span class="p">,</span><span class="w"> </span><span class="n">i35</span><span class="p">)</span> <span class="o">+</span><span class="mi">555</span><span class="p">:</span><span class="w"> </span><span class="n">guard_true</span><span class="p">(</span><span class="n">i45</span><span class="p">,</span><span class="w"> </span><span class="n">descr</span><span class="o">=&lt;</span><span class="n">Guard0x7ced4756a160</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span><span class="p">[</span><span class="n">p0</span><span class="p">,</span><span class="w"> </span><span class="n">p6</span><span class="p">,</span><span class="w"> </span><span class="n">p9</span><span class="p">,</span><span class="w"> </span><span class="n">p11</span><span class="p">,</span><span class="w"> </span><span class="n">p13</span><span class="p">,</span><span class="w"> </span><span class="n">p19</span><span class="p">,</span><span class="w"> </span><span class="n">p21</span><span class="p">,</span><span class="w"> </span><span class="n">p23</span><span class="p">,</span><span class="w"> </span><span class="n">p25</span><span class="p">,</span><span class="w"> </span><span class="n">p29</span><span class="p">,</span><span class="w"> </span><span class="n">p31</span><span class="p">,</span><span class="w"> </span><span class="n">p1</span><span class="p">,</span><span class="w"> </span><span class="n">i44</span><span class="p">,</span><span class="w"> </span><span class="n">i35</span><span class="p">,</span><span class="w"> </span><span class="n">i34</span><span class="p">]</span> <span class="o">+</span><span class="mi">561</span><span class="p">:</span><span class="w"> </span><span class="n">i47</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">int_add</span><span class="p">(</span><span class="n">i44</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span> <span class="n">debug_merge_point</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="s1">'run;/home/cfbolz/projects/gitpypy/allocatealot.py:6-9~#26 STORE_FAST'</span><span class="p">)</span> <span class="n">debug_merge_point</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="s1">'run;/home/cfbolz/projects/gitpypy/allocatealot.py:6-10~#28 LOAD_FAST'</span><span class="p">)</span> <span class="n">debug_merge_point</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="s1">'run;/home/cfbolz/projects/gitpypy/allocatealot.py:6-10~#30 STORE_FAST'</span><span class="p">)</span> <span class="n">debug_merge_point</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="s1">'run;/home/cfbolz/projects/gitpypy/allocatealot.py:6-11~#32 LOAD_FAST'</span><span class="p">)</span> <span class="n">debug_merge_point</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="s1">'run;/home/cfbolz/projects/gitpypy/allocatealot.py:6-11~#34 STORE_FAST'</span><span class="p">)</span> <span class="n">debug_merge_point</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="s1">'run;/home/cfbolz/projects/gitpypy/allocatealot.py:6-11~#36 JUMP_ABSOLUTE'</span><span class="p">)</span> <span class="c1"># update iterator object</span> <span class="o">+</span><span class="mi">565</span><span class="p">:</span><span class="w"> </span><span class="n">setfield_gc</span><span class="p">(</span><span class="n">p25</span><span class="p">,</span><span class="w"> </span><span class="n">i47</span><span class="p">,</span><span class="w"> </span><span class="n">descr</span><span class="o">=&lt;</span><span class="n">FieldS</span><span class="w"> </span><span class="n">pypy</span><span class="o">.</span><span class="n">module</span><span class="o">.</span><span class="n">__builtin__</span><span class="o">.</span><span class="n">functional</span><span class="o">.</span><span class="n">W_IntRangeIterator</span><span class="o">.</span><span class="n">inst_current</span><span class="w"> </span><span class="mi">8</span><span class="o">&gt;</span><span class="p">)</span> <span class="o">+</span><span class="mi">569</span><span class="p">:</span><span class="w"> </span><span class="n">guard_not_invalidated</span><span class="p">(</span><span class="n">descr</span><span class="o">=&lt;</span><span class="n">Guard0x7ced4756a1b0</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span><span class="p">[</span><span class="n">p0</span><span class="p">,</span><span class="w"> </span><span class="n">p6</span><span class="p">,</span><span class="w"> </span><span class="n">p9</span><span class="p">,</span><span class="w"> </span><span class="n">p11</span><span class="p">,</span><span class="w"> </span><span class="n">p19</span><span class="p">,</span><span class="w"> </span><span class="n">p21</span><span class="p">,</span><span class="w"> </span><span class="n">p23</span><span class="p">,</span><span class="w"> </span><span class="n">p25</span><span class="p">,</span><span class="w"> </span><span class="n">p29</span><span class="p">,</span><span class="w"> </span><span class="n">p31</span><span class="p">,</span><span class="w"> </span><span class="n">p1</span><span class="p">,</span><span class="w"> </span><span class="n">i44</span><span class="p">,</span><span class="w"> </span><span class="n">i34</span><span class="p">]</span> <span class="c1"># check for signals</span> <span class="o">+</span><span class="mi">569</span><span class="p">:</span><span class="w"> </span><span class="n">i49</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">getfield_raw_i</span><span class="p">(</span><span class="mi">137358624889824</span><span class="p">,</span><span class="w"> </span><span class="n">descr</span><span class="o">=&lt;</span><span class="n">FieldS</span><span class="w"> </span><span class="n">pypysig_long_struct_inner</span><span class="o">.</span><span class="n">c_value</span><span class="w"> </span><span class="mi">0</span><span class="o">&gt;</span><span class="p">)</span> <span class="o">+</span><span class="mi">582</span><span class="p">:</span><span class="w"> </span><span class="n">i51</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">int_lt</span><span class="p">(</span><span class="n">i49</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span> <span class="o">+</span><span class="mi">586</span><span class="p">:</span><span class="w"> </span><span class="n">guard_false</span><span class="p">(</span><span class="n">i51</span><span class="p">,</span><span class="w"> </span><span class="n">descr</span><span class="o">=&lt;</span><span class="n">Guard0x7ced4754db78</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span><span class="p">[</span><span class="n">p0</span><span class="p">,</span><span class="w"> </span><span class="n">p6</span><span class="p">,</span><span class="w"> </span><span class="n">p9</span><span class="p">,</span><span class="w"> </span><span class="n">p11</span><span class="p">,</span><span class="w"> </span><span class="n">p19</span><span class="p">,</span><span class="w"> </span><span class="n">p21</span><span class="p">,</span><span class="w"> </span><span class="n">p23</span><span class="p">,</span><span class="w"> </span><span class="n">p25</span><span class="p">,</span><span class="w"> </span><span class="n">p29</span><span class="p">,</span><span class="w"> </span><span class="n">p31</span><span class="p">,</span><span class="w"> </span><span class="n">p1</span><span class="p">,</span><span class="w"> </span><span class="n">i44</span><span class="p">,</span><span class="w"> </span><span class="n">i34</span><span class="p">]</span> <span class="n">debug_merge_point</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="s1">'run;/home/cfbolz/projects/gitpypy/allocatealot.py:6-9~#24 FOR_ITER'</span><span class="p">)</span> <span class="c1"># allocate the integer (allocation sunk to the end of the trace)</span> <span class="o">+</span><span class="mi">592</span><span class="p">:</span><span class="w"> </span><span class="n">p52</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">new_with_vtable</span><span class="p">(</span><span class="n">descr</span><span class="o">=&lt;</span><span class="n">SizeDescr</span><span class="w"> </span><span class="mi">16</span><span class="o">&gt;</span><span class="p">)</span> <span class="o">+</span><span class="mi">630</span><span class="p">:</span><span class="w"> </span><span class="n">setfield_gc</span><span class="p">(</span><span class="n">p52</span><span class="p">,</span><span class="w"> </span><span class="n">i34</span><span class="p">,</span><span class="w"> </span><span class="n">descr</span><span class="o">=&lt;</span><span class="n">FieldS</span><span class="w"> </span><span class="n">pypy</span><span class="o">.</span><span class="n">objspace</span><span class="o">.</span><span class="n">std</span><span class="o">.</span><span class="n">intobject</span><span class="o">.</span><span class="n">W_IntObject</span><span class="o">.</span><span class="n">inst_intval</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="n">pure</span><span class="o">&gt;</span><span class="p">)</span> <span class="o">+</span><span class="mi">634</span><span class="p">:</span><span class="w"> </span><span class="n">jump</span><span class="p">(</span><span class="n">p0</span><span class="p">,</span><span class="w"> </span><span class="n">p1</span><span class="p">,</span><span class="w"> </span><span class="n">p6</span><span class="p">,</span><span class="w"> </span><span class="n">p9</span><span class="p">,</span><span class="w"> </span><span class="n">p11</span><span class="p">,</span><span class="w"> </span><span class="n">i44</span><span class="p">,</span><span class="w"> </span><span class="n">p52</span><span class="p">,</span><span class="w"> </span><span class="n">p19</span><span class="p">,</span><span class="w"> </span><span class="n">p21</span><span class="p">,</span><span class="w"> </span><span class="n">p23</span><span class="p">,</span><span class="w"> </span><span class="n">p25</span><span class="p">,</span><span class="w"> </span><span class="n">p29</span><span class="p">,</span><span class="w"> </span><span class="n">p31</span><span class="p">,</span><span class="w"> </span><span class="n">i47</span><span class="p">,</span><span class="w"> </span><span class="n">i35</span><span class="p">,</span><span class="w"> </span><span class="n">descr</span><span class="o">=</span><span class="n">TargetToken</span><span class="p">(</span><span class="mi">137358545605472</span><span class="p">))</span> </pre></div> <p>To find the machine code address of the trace, we need to search for this line:</p> <div class="code"><pre class="code literal-block"><span class="nx">Loop</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">(</span><span class="nx">run</span><span class="p">;</span><span class="o">/</span><span class="nx">home</span><span class="o">/</span><span class="nx">cfbolz</span><span class="o">/</span><span class="nx">projects</span><span class="o">/</span><span class="nx">gitpypy</span><span class="o">/</span><span class="nx">allocatealot</span><span class="p">.</span><span class="nx">py</span><span class="p">:</span><span class="mi">6</span><span class="o">-</span><span class="mi">9</span><span class="o">~</span><span class="err">#</span><span class="mi">24</span><span class="w"> </span><span class="nx">FOR_ITER</span><span class="p">)</span><span class="w"> </span>\ <span class="w"> </span><span class="nx">has</span><span class="w"> </span><span class="nx">address</span><span class="w"> </span><span class="mh">0x7ced473ffa0b</span><span class="w"> </span><span class="nx">to</span><span class="w"> </span><span class="mh">0x7ced473ffbb0</span><span class="w"> </span><span class="p">(</span><span class="nx">bootstrap</span><span class="w"> </span><span class="mh">0x7ced473ff980</span><span class="p">)</span> </pre></div> <p>Then we can use a script in the PyPy repo to disassemble the generated machine code:</p> <div class="code"><pre class="code literal-block"><span class="gp">$ </span>pypy<span class="w"> </span>rpython/jit/backend/tool/viewcode.py<span class="w"> </span>out </pre></div> <p>This will dump all the machine code to stdout, and open a <a href="https://pypy.org/posts/2021/04/ways-pypy-graphviz.html">pygame-based graphviz cfg</a>. In there we can search for the address and see this:</p> <p><img alt="Graphviz based visualization of the machine code the JIT generates" src="https://www.pypy.org/images/2025-allocatealot-machine-code.png"></p> <p>Here's an annotated version with what I think this code does:</p> <div class="code"><pre class="code literal-block"><span class="x"># increment the profile counter</span> <span class="x">7ced473ffb40: 48 ff 04 25 20 9e 33 incq 0x38339e20</span> <span class="x">7ced473ffb47: 38 </span> <span class="x"># check whether the loop is done</span> <span class="x">7ced473ffb48: 4c 39 fe cmp %r15,%rsi</span> <span class="x">7ced473ffb4b: 0f 8d 76 01 00 00 jge 0x7ced473ffcc7</span> <span class="x"># increment iteration variable</span> <span class="x">7ced473ffb51: 4c 8d 66 01 lea 0x1(%rsi),%r12</span> <span class="x"># update iterator object</span> <span class="x">7ced473ffb55: 4d 89 61 08 mov %r12,0x8(%r9)</span> <span class="x"># check for ctrl-c/thread switch</span> <span class="x">7ced473ffb59: 49 bb e0 1b 0b 4c ed movabs $0x7ced4c0b1be0,%r11</span> <span class="x">7ced473ffb60: 7c 00 00 </span> <span class="x">7ced473ffb63: 49 8b 0b mov (%r11),%rcx</span> <span class="x">7ced473ffb66: 48 83 f9 00 cmp $0x0,%rcx</span> <span class="x">7ced473ffb6a: 0f 8c 8f 01 00 00 jl 0x7ced473ffcff</span> <span class="x"># load nursery_free pointer</span> <span class="x">7ced473ffb70: 49 8b 8b d8 30 f6 fe mov -0x109cf28(%r11),%rcx</span> <span class="x"># add size (16)</span> <span class="x">7ced473ffb77: 48 8d 51 10 lea 0x10(%rcx),%rdx</span> <span class="x"># compare against nursery top</span> <span class="x">7ced473ffb7b: 49 3b 93 f8 30 f6 fe cmp -0x109cf08(%r11),%rdx</span> <span class="x"># jump to slow path if nursery is full</span> <span class="x">7ced473ffb82: 0f 87 41 00 00 00 ja 0x7ced473ffbc9</span> <span class="x"># store new value of nursery free</span> <span class="x">7ced473ffb88: 49 89 93 d8 30 f6 fe mov %rdx,-0x109cf28(%r11)</span> <span class="x"># initialize GC header</span> <span class="x">7ced473ffb8f: 48 c7 01 30 11 00 00 movq $0x1130,(%rcx)</span> <span class="x"># initialize integer field</span> <span class="x">7ced473ffb96: 48 89 41 08 mov %rax,0x8(%rcx)</span> <span class="x">7ced473ffb9a: 48 89 f0 mov %rsi,%rax</span> <span class="x">7ced473ffb9d: 48 89 8d 60 01 00 00 mov %rcx,0x160(%rbp)</span> <span class="x">7ced473ffba4: 4c 89 e6 mov %r12,%rsi</span> <span class="x">7ced473ffba7: e9 94 ff ff ff jmp 0x7ced473ffb40</span> <span class="x">7ced473ffbac: 0f 1f 40 00 nopl 0x0(%rax)</span> </pre></div> <h3 id="conclusion">Conclusion</h3> <p>The careful design of the RPython GC's allocation fast path gives pretty good allocation rates. This technique isn't really new, it's a pretty typical way to design a GC. Apart from that, my main conclusion would be that computers are fast or something? Indeed, when we ran the same code on my colleague's two-year-old AMD, we got quite a bit worse results, so a lot of the speed seems to be due to the hard work of CPU architects.</p>benchmarkinggcrpythonhttps://www.pypy.org/posts/2025/06/rpython-gc-allocation-speed.htmlSun, 15 Jun 2025 13:48:30 GMTAquileo | Doing the Prospero-Challenge in RPythonhttps://www.pypy.org/posts/2025/04/prospero-in-rpython.htmlCF Bolz-Tereick<p>Recently I had a lot of fun playing with the <a href="https://www.mattkeeter.com/projects/prospero/">Prospero Challenge</a> by <a href="https://www.mattkeeter.com/">Matt Keeter</a>. The challenge is to render a 1024x1024 image of a quote from The Tempest by Shakespeare. The input is a mathematical formula with 7866 operations, which is evaluated once per pixel.</p> <p>What made the challenge particularly enticing for me personally was the fact that the formula is basically a trace in <a href="https://en.wikipedia.org/wiki/Static_single-assignment_form">SSA-form</a> – a linear sequence of operations, where every variable is assigned exactly once. The challenge is to evaluate the formula as fast as possible. I tried a number of ideas how to speed up execution and will talk about them in this somewhat meandering post. Most of it follows Matt's implementation <a href="https://github.com/mkeeter/fidget">Fidget</a> very closely. There are two points of difference:</p> <ul> <li>I tried to add more peephole optimizations, but they didn't end up helping much.</li> <li>I implemented a "demanded information" optimization that removes a lot of operations by only keeping the sign of the result. This optimization ended up being useful.</li> </ul> <p>Most of the prototyping in this post was done in RPython (a statically typable subset of Python2, that can be compiled to C), but I later rewrote the program in C to get better performance. All the code <a href="https://github.com/cfbolz/pyfidget/">can be found on Github</a>.</p> <h3 id="input-program">Input program</h3> <p>The input program is a sequence of operations, like this:</p> <div class="code"><pre class="code literal-block"><span class="n">_0</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="mf">2.95</span> <span class="n">_1</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">x</span> <span class="n">_2</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="mf">8.13008</span> <span class="n">_3</span><span class="w"> </span><span class="n">mul</span><span class="w"> </span><span class="n">_1</span><span class="w"> </span><span class="n">_2</span> <span class="n">_4</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">_0</span><span class="w"> </span><span class="n">_3</span> <span class="n">_5</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="mf">3.675</span> <span class="n">_6</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">_5</span><span class="w"> </span><span class="n">_3</span> <span class="n">_7</span><span class="w"> </span><span class="n">neg</span><span class="w"> </span><span class="n">_6</span> <span class="n">_8</span><span class="w"> </span><span class="nb">max</span><span class="w"> </span><span class="n">_4</span><span class="w"> </span><span class="n">_7</span> <span class="o">...</span> </pre></div> <p>The first column is the name of the result variable, the second column is the operation, and the rest are the arguments to the operation. <code>var-x</code> is a special operation that returns the x-coordinate of the pixel being rendered, and equivalently for <code>var-y</code> the y-coordinate. The sign of the result gives the color of the pixel, the absolute value is not important.</p> <h3 id="a-baseline-interpreter">A baseline interpreter</h3> <p>To run the program, I first parse them and replace the register names with indexes, to avoid any dictionary lookups at runtime. Then I implemented a simple interpreter for the SSA-form input program. The interpreter is a simple register machine, where every operation is executed in order. The result of the operation is stored into a list of results, and the next operation is executed. This was the slow baseline implementation of the interpreter but it's very useful to compare against the optimized versions.</p> <p>This is roughly what the code looks like</p> <div class="code"><pre class="code literal-block"><span class="k">class</span><span class="w"> </span><span class="nc">DirectFrame</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span> <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">program</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">program</span> <span class="o">=</span> <span class="n">program</span> <span class="bp">self</span><span class="o">.</span><span class="n">next</span> <span class="o">=</span> <span class="kc">None</span> <span class="k">def</span><span class="w"> </span><span class="nf">run_floats</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">setxyz</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">)</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">run</span><span class="p">()</span> <span class="k">def</span><span class="w"> </span><span class="nf">setxyz</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">x</span> <span class="bp">self</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">y</span> <span class="bp">self</span><span class="o">.</span><span class="n">z</span> <span class="o">=</span> <span class="n">z</span> <span class="k">def</span><span class="w"> </span><span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="n">program</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">program</span> <span class="n">num_ops</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="n">num_operations</span><span class="p">()</span> <span class="n">floatvalues</span> <span class="o">=</span> <span class="p">[</span><span class="mf">0.0</span><span class="p">]</span> <span class="o">*</span> <span class="n">num_ops</span> <span class="k">for</span> <span class="n">op</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_ops</span><span class="p">):</span> <span class="n">func</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="n">get_func_and_args</span><span class="p">(</span><span class="n">op</span><span class="p">)</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">const</span><span class="p">:</span> <span class="n">floatvalues</span><span class="p">[</span><span class="n">op</span><span class="p">]</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="n">consts</span><span class="p">[</span><span class="n">arg0</span><span class="p">]</span> <span class="k">continue</span> <span class="n">farg0</span> <span class="o">=</span> <span class="n">floatvalues</span><span class="p">[</span><span class="n">arg0</span><span class="p">]</span> <span class="n">farg1</span> <span class="o">=</span> <span class="n">floatvalues</span><span class="p">[</span><span class="n">arg1</span><span class="p">]</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">var_x</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">x</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">var_y</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">y</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">var_z</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">z</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">add</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">farg0</span><span class="p">,</span> <span class="n">farg1</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">sub</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="n">farg0</span><span class="p">,</span> <span class="n">farg1</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">mul</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">mul</span><span class="p">(</span><span class="n">farg0</span><span class="p">,</span> <span class="n">farg1</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">max</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">max</span><span class="p">(</span><span class="n">farg0</span><span class="p">,</span> <span class="n">farg1</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">min</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">min</span><span class="p">(</span><span class="n">farg0</span><span class="p">,</span> <span class="n">farg1</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">square</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">square</span><span class="p">(</span><span class="n">farg0</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">sqrt</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">farg0</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">exp</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">exp</span><span class="p">(</span><span class="n">farg0</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">neg</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">neg</span><span class="p">(</span><span class="n">farg0</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">abs</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">abs</span><span class="p">(</span><span class="n">farg0</span><span class="p">)</span> <span class="k">else</span><span class="p">:</span> <span class="k">assert</span> <span class="mi">0</span> <span class="n">floatvalues</span><span class="p">[</span><span class="n">op</span><span class="p">]</span> <span class="o">=</span> <span class="n">res</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">floatvalues</span><span class="p">[</span><span class="n">num_ops</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="k">def</span><span class="w"> </span><span class="nf">add</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">):</span> <span class="k">return</span> <span class="n">arg0</span> <span class="o">+</span> <span class="n">arg1</span> <span class="k">def</span><span class="w"> </span><span class="nf">sub</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">):</span> <span class="k">return</span> <span class="n">arg0</span> <span class="o">-</span> <span class="n">arg1</span> <span class="k">def</span><span class="w"> </span><span class="nf">mul</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">):</span> <span class="k">return</span> <span class="n">arg0</span> <span class="o">*</span> <span class="n">arg1</span> <span class="k">def</span><span class="w"> </span><span class="nf">max</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">):</span> <span class="k">return</span> <span class="nb">max</span><span class="p">(</span><span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">min</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">):</span> <span class="k">return</span> <span class="nb">min</span><span class="p">(</span><span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">square</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">):</span> <span class="n">val</span> <span class="o">=</span> <span class="n">arg0</span> <span class="k">return</span> <span class="n">val</span><span class="o">*</span><span class="n">val</span> <span class="k">def</span><span class="w"> </span><span class="nf">sqrt</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">):</span> <span class="k">return</span> <span class="n">math</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">exp</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">):</span> <span class="k">return</span> <span class="n">math</span><span class="o">.</span><span class="n">exp</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">neg</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">):</span> <span class="k">return</span> <span class="o">-</span><span class="n">arg0</span> <span class="k">def</span><span class="w"> </span><span class="nf">abs</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">):</span> <span class="k">return</span> <span class="nb">abs</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> </pre></div> <p>Running the naive interpreter on the prospero image file is super slow, since it performs 7866 * 1024 * 1024 float operations, plus the interpretation overhead.</p> <h3 id="using-quadtrees-to-render-the-picture">Using Quadtrees to render the picture</h3> <p>The approach that Matt describes in his really excellent <a href="https://www.youtube.com/watch?v=UxGxsGnbyJ4">talk</a> is to use <a href="https://en.wikipedia.org/wiki/Quadtree">quadtrees</a>: recursively subdivide the image into quadrants, and evaluate the formula in each quadrant. For every quadrant you can simplify the formula by doing a range analysis. After a few recursion steps, the formula becomes significantly smaller, often only a few hundred or a few dozen operations.</p> <p>At the bottom of the recursion you either reach a square where the range analysis reveals that the sign for all pixels is determined, then you can fill in all the pixels of the quadrant. Or you can evaluate the (now much simpler) formula in the quadrant by executing it for every pixel.</p> <p>This is an interesting use case of JIT compiler/optimization techniques, requiring the optimizer itself to execute really quickly since it is an essential part of the performance of the algorithm. The optimizer runs literally hundreds of times to render a single image. If the algorithm is used for 3D models it becomes even more crucial.</p> <h3 id="writing-a-simple-optimizer">Writing a simple optimizer</h3> <p>Implementing the quadtree recursion is straightforward. Since the program has no control flow the optimizer is very simple to write. I've written a couple of blog posts on how to easily write optimizers for linear sequences of operations, and I'm using the approach described in these <a href="https://pypy.org/categories/toy-optimizer.html">Toy Optimizer</a> posts. The interval analysis is basically an <a href="https://pypy.org/posts/2024/08/toy-knownbits.html">abstract interpretation</a> of the operations. The optimizer does a sequential forward pass over the input program. For every operation, the output interval is computed. The optimizer also performs optimizations based on the computed intervals, which helps in reducing the number of operations executed (I'll talk about this further down).</p> <p>Here's a sketch of the Python code that does the optimization:</p> <div class="code"><pre class="code literal-block"><span class="k">class</span><span class="w"> </span><span class="nc">Optimizer</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span> <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">program</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">program</span> <span class="o">=</span> <span class="n">program</span> <span class="n">num_operations</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="n">num_operations</span><span class="p">()</span> <span class="bp">self</span><span class="o">.</span><span class="n">resultops</span> <span class="o">=</span> <span class="n">ProgramBuilder</span><span class="p">(</span><span class="n">num_operations</span><span class="p">)</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span> <span class="o">=</span> <span class="n">IntervalFrame</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">program</span><span class="p">)</span> <span class="c1"># old index -&gt; new index</span> <span class="bp">self</span><span class="o">.</span><span class="n">opreplacements</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">num_operations</span> <span class="bp">self</span><span class="o">.</span><span class="n">index</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">def</span><span class="w"> </span><span class="nf">get_replacement</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">op</span><span class="p">):</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opreplacements</span><span class="p">[</span><span class="n">op</span><span class="p">]</span> <span class="k">def</span><span class="w"> </span><span class="nf">newop</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="n">arg0</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">arg1</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">resultops</span><span class="o">.</span><span class="n">add_op</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">newconst</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span> <span class="n">const</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">resultops</span><span class="o">.</span><span class="n">add_const</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span><span class="o">.</span><span class="n">minvalues</span><span class="p">[</span><span class="n">const</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span><span class="o">.</span><span class="n">maxvalues</span><span class="p">[</span><span class="n">const</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span> <span class="c1">#self.seen_consts[value] = const</span> <span class="k">return</span> <span class="n">const</span> <span class="k">def</span><span class="w"> </span><span class="nf">optimize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">d</span><span class="p">,</span> <span class="n">e</span><span class="p">,</span> <span class="n">f</span><span class="p">):</span> <span class="n">program</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">program</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span><span class="o">.</span><span class="n">setxyz</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">d</span><span class="p">,</span> <span class="n">e</span><span class="p">,</span> <span class="n">f</span><span class="p">)</span> <span class="n">numops</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="n">num_operations</span><span class="p">()</span> <span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">numops</span><span class="p">):</span> <span class="n">newop</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_optimize_op</span><span class="p">(</span><span class="n">index</span><span class="p">)</span> <span class="bp">self</span><span class="o">.</span><span class="n">opreplacements</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="o">=</span> <span class="n">newop</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opreplacements</span><span class="p">[</span><span class="n">numops</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="k">def</span><span class="w"> </span><span class="nf">_optimize_op</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">op</span><span class="p">):</span> <span class="n">program</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">program</span> <span class="n">intervalframe</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span> <span class="n">func</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="n">get_func_and_args</span><span class="p">(</span><span class="n">op</span><span class="p">)</span> <span class="k">assert</span> <span class="n">arg0</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="k">assert</span> <span class="n">arg1</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">var_x</span><span class="p">:</span> <span class="n">minimum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">minx</span> <span class="n">maximum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">maxx</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_default</span><span class="p">(</span><span class="n">OPS</span><span class="o">.</span><span class="n">var_x</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">)</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">var_y</span><span class="p">:</span> <span class="n">minimum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">miny</span> <span class="n">maximum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">maxy</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_default</span><span class="p">(</span><span class="n">OPS</span><span class="o">.</span><span class="n">var_y</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">)</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">var_z</span><span class="p">:</span> <span class="n">minimum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">minz</span> <span class="n">maximum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">maxz</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_default</span><span class="p">(</span><span class="n">OPS</span><span class="o">.</span><span class="n">var_z</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">)</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">const</span><span class="p">:</span> <span class="n">const</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="n">consts</span><span class="p">[</span><span class="n">arg0</span><span class="p">]</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">newconst</span><span class="p">(</span><span class="n">const</span><span class="p">)</span> <span class="n">arg0</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_replacement</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> <span class="n">arg1</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_replacement</span><span class="p">(</span><span class="n">arg1</span><span class="p">)</span> <span class="k">assert</span> <span class="n">arg0</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="k">assert</span> <span class="n">arg1</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="n">arg0minimum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">minvalues</span><span class="p">[</span><span class="n">arg0</span><span class="p">]</span> <span class="n">arg0maximum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">maxvalues</span><span class="p">[</span><span class="n">arg0</span><span class="p">]</span> <span class="n">arg1minimum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">minvalues</span><span class="p">[</span><span class="n">arg1</span><span class="p">]</span> <span class="n">arg1maximum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">maxvalues</span><span class="p">[</span><span class="n">arg1</span><span class="p">]</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">neg</span><span class="p">:</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_neg</span><span class="p">(</span><span class="n">arg0</span><span class="p">,</span> <span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">)</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">min</span><span class="p">:</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_min</span><span class="p">(</span><span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">,</span> <span class="n">arg1minimum</span><span class="p">,</span> <span class="n">arg1maximum</span><span class="p">)</span> <span class="o">...</span> <span class="k">def</span><span class="w"> </span><span class="nf">opt_default</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">,</span> <span class="n">arg0</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">arg1</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span><span class="o">.</span><span class="n">_set</span><span class="p">(</span><span class="n">newop</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">)</span> <span class="k">return</span> <span class="n">newop</span> <span class="k">def</span><span class="w"> </span><span class="nf">opt_neg</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">):</span> <span class="c1"># peephole rules go here, see below</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span><span class="o">.</span><span class="n">_neg</span><span class="p">(</span><span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">)</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_default</span><span class="p">(</span><span class="n">OPS</span><span class="o">.</span><span class="n">neg</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">,</span> <span class="n">arg0</span><span class="p">)</span> <span class="nd">@symmetric</span> <span class="k">def</span><span class="w"> </span><span class="nf">opt_min</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">,</span> <span class="n">arg1minimum</span><span class="p">,</span> <span class="n">arg1maximum</span><span class="p">):</span> <span class="c1"># peephole rules go here, see below</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span><span class="o">.</span><span class="n">_max</span><span class="p">(</span><span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">,</span> <span class="n">arg1minimum</span><span class="p">,</span> <span class="n">arg1maximum</span><span class="p">)</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_default</span><span class="p">(</span><span class="n">OPS</span><span class="o">.</span><span class="n">max</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">)</span> <span class="o">...</span> </pre></div> <p>The resulting optimized traces are then simply interpreted at the bottom of the quadtree recursion. Matt talks about also generating machine code from them, but when I tried to use PyPy's JIT for that it was way too slow at producing machine code.</p> <h3 id="testing-soundness-of-the-interval-abstract-domain">Testing soundness of the interval abstract domain</h3> <p>To make sure that my interval computation in the optimizer is correct, I implemented a hypothesis-based property based test. It checks the abstract transfer functions of the interval domain for soundness. It does so by generating random concrete input values for an operation and random intervals that surround the random concrete values, then performs the concrete operation to get the concrete output, and finally checks that the abstract transfer function applied to the input intervals gives an interval that contains the concrete output.</p> <p>For example, the random test for the <code>square</code> operation would look like this:</p> <div class="code"><pre class="code literal-block"><span class="kn">from</span><span class="w"> </span><span class="nn">hypothesis</span><span class="w"> </span><span class="kn">import</span> <span class="n">given</span><span class="p">,</span> <span class="n">strategies</span><span class="p">,</span> <span class="n">assume</span> <span class="kn">from</span><span class="w"> </span><span class="nn">pyfidget.vm</span><span class="w"> </span><span class="kn">import</span> <span class="n">IntervalFrame</span><span class="p">,</span> <span class="n">DirectFrame</span> <span class="kn">import</span><span class="w"> </span><span class="nn">math</span> <span class="n">regular_floats</span> <span class="o">=</span> <span class="n">strategies</span><span class="o">.</span><span class="n">floats</span><span class="p">(</span><span class="n">allow_nan</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">allow_infinity</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">make_range_and_contained_float</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">):</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">([</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">])</span> <span class="k">return</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span> <span class="n">frame</span> <span class="o">=</span> <span class="n">DirectFrame</span><span class="p">(</span><span class="kc">None</span><span class="p">)</span> <span class="n">intervalframe</span> <span class="o">=</span> <span class="n">IntervalFrame</span><span class="p">(</span><span class="kc">None</span><span class="p">)</span> <span class="n">range_and_contained_float</span> <span class="o">=</span> <span class="n">strategies</span><span class="o">.</span><span class="n">builds</span><span class="p">(</span><span class="n">make_range_and_contained_float</span><span class="p">,</span> <span class="n">regular_floats</span><span class="p">,</span> <span class="n">regular_floats</span><span class="p">,</span> <span class="n">regular_floats</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">contains</span><span class="p">(</span><span class="n">res</span><span class="p">,</span> <span class="n">rmin</span><span class="p">,</span> <span class="n">rmax</span><span class="p">):</span> <span class="k">if</span> <span class="n">math</span><span class="o">.</span><span class="n">isnan</span><span class="p">(</span><span class="n">rmin</span><span class="p">)</span> <span class="ow">or</span> <span class="n">math</span><span class="o">.</span><span class="n">isnan</span><span class="p">(</span><span class="n">rmax</span><span class="p">):</span> <span class="k">return</span> <span class="kc">True</span> <span class="k">return</span> <span class="n">rmin</span> <span class="o">&lt;=</span> <span class="n">res</span> <span class="o">&lt;=</span> <span class="n">rmax</span> <span class="nd">@given</span><span class="p">(</span><span class="n">range_and_contained_float</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">test_square</span><span class="p">(</span><span class="n">val</span><span class="p">):</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span> <span class="o">=</span> <span class="n">val</span> <span class="n">rmin</span><span class="p">,</span> <span class="n">rmax</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">_square</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span> <span class="n">res</span> <span class="o">=</span> <span class="n">frame</span><span class="o">.</span><span class="n">square</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="k">assert</span> <span class="n">contains</span><span class="p">(</span><span class="n">res</span><span class="p">,</span> <span class="n">rmin</span><span class="p">,</span> <span class="n">rmax</span><span class="p">)</span> </pre></div> <p>This test generates a random float <code>b</code>, and two other floats <code>a</code> and <code>c</code> such that the interval <code>[a, c]</code> contains <code>b</code>. The test then checks that the result of the <code>square</code> operation on <code>b</code> is contained in the interval <code>[rmin, rmax]</code> returned by the abstract transfer function for the <code>square</code> operation.</p> <h3 id="peephole-rewrites">Peephole rewrites</h3> <p>The only optimization that Matt does in his implementation is a peephole optimization rule that removes <code>min</code> and <code>max</code> operations where the intervals of the arguments don't overlap. In that case, the optimizer statically can know which of the arguments will be the result of the operation. I implemented this peephole optimization in my implementation as well, but I also added a few more peephole optimizations that I thought would be useful.</p> <div class="code"><pre class="code literal-block"><span class="k">class</span><span class="w"> </span><span class="nc">Optimizer</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span> <span class="k">def</span><span class="w"> </span><span class="nf">opt_neg</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">):</span> <span class="c1"># new: add peephole rule --x =&gt; x</span> <span class="n">func</span><span class="p">,</span> <span class="n">arg0arg0</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">resultops</span><span class="o">.</span><span class="n">get_func_and_args</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">neg</span><span class="p">:</span> <span class="k">return</span> <span class="n">arg0arg0</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span><span class="o">.</span><span class="n">_neg</span><span class="p">(</span><span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">)</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_default</span><span class="p">(</span><span class="n">OPS</span><span class="o">.</span><span class="n">neg</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">,</span> <span class="n">arg0</span><span class="p">)</span> <span class="nd">@symmetric</span> <span class="k">def</span><span class="w"> </span><span class="nf">opt_min</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">,</span> <span class="n">arg1minimum</span><span class="p">,</span> <span class="n">arg1maximum</span><span class="p">):</span> <span class="c1"># Matt's peephole rule</span> <span class="k">if</span> <span class="n">arg0maximum</span> <span class="o">&lt;</span> <span class="n">arg1minimum</span><span class="p">:</span> <span class="k">return</span> <span class="n">arg0</span> <span class="c1"># we can use the intervals to decide which argument will be returned</span> <span class="c1"># new one by me: min(x, x) =&gt; x </span> <span class="k">if</span> <span class="n">arg0</span> <span class="o">==</span> <span class="n">arg1</span><span class="p">:</span> <span class="k">return</span> <span class="n">arg0</span> <span class="n">func</span><span class="p">,</span> <span class="n">arg0arg0</span><span class="p">,</span> <span class="n">arg0arg1</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">resultops</span><span class="o">.</span><span class="n">get_func_and_args</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span><span class="o">.</span><span class="n">_max</span><span class="p">(</span><span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">,</span> <span class="n">arg1minimum</span><span class="p">,</span> <span class="n">arg1maximum</span><span class="p">)</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_default</span><span class="p">(</span><span class="n">OPS</span><span class="o">.</span><span class="n">max</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">)</span> <span class="o">...</span> </pre></div> <p>However, it turns out that all my attempts at adding other peephole optimization rules were not very useful. Most rules never fired, and the ones that did only had a small effect on the performance of the program. The only peephole optimization that I found to be useful was the one that Matt describes in his talk. Matt's <code>min</code>/<code>max</code> optimization were 96% of all rewrites that my peephole optimizer applied for the <code>prospero.vm</code> input. The remaining 4% of rewrites were (the percentages are of that 4%):</p> <div class="code"><pre class="code literal-block">--x =&gt; x 4.65% (-x)**2 =&gt; x ** 2 0.99% min(x, x) =&gt; x 20.86% min(x, min(x, y)) =&gt; min(x, y) 52.87% max(x, x) =&gt; x 16.40% max(x, max(x, y)) =&gt; max(x, y) 4.23% </pre></div> <p>In the end it turned out that having these extra optimization rules made the total runtime of the system go up. Checking for the rewrites isn't free, and since they apply so rarely they don't pay for their own cost in terms of improved performance.</p> <p>There are some further rules that I tried that never fired at all:</p> <div class="code"><pre class="code literal-block">a <span class="gs">* 0 =&gt; 0</span> <span class="gs">a *</span> 1 =&gt; a a <span class="gs">* a =&gt; a *</span>* 2 a <span class="gs">* -1 =&gt; -a</span> <span class="gs">a + 0 =&gt; a</span> <span class="gs">a - 0 =&gt; a</span> <span class="gs">x - x =&gt; 0</span> <span class="gs">abs(known positive number x) =&gt; x</span> <span class="gs">abs(known negative number x) =&gt; -x</span> <span class="gs">abs(-x) =&gt; abs(x)</span> <span class="gs">(-x) *</span>* 2 =&gt; x ** 2 </pre></div> <p>This investigation is clearly way too focused on a single program and should be re-done with a larger set of example inputs, if this were an actually serious implementation.</p> <h3 id="demanded-information-optimization">Demanded Information Optimization</h3> <p>LLVM has an static analysis pass called 'demanded bits'. It is a backwards analysis that allows you to determine which bits of a value are actually used in the final result. This information can then be used in peephole optimizations. For example, if you have an expression that computes a value, but only the last byte of that value is used in the final result, you can optimize the expression to only compute the last byte.</p> <p>Here's an example. Let's say we first byte-swap a 64-bit int, and then mask off the last byte:</p> <div class="code"><pre class="code literal-block"><span class="kt">uint64_t</span><span class="w"> </span><span class="nf">byteswap_then_mask</span><span class="p">(</span><span class="kt">uint64_t</span><span class="w"> </span><span class="n">a</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">byteswap</span><span class="p">(</span><span class="n">a</span><span class="p">)</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="mh">0xff</span><span class="p">;</span> <span class="p">}</span> </pre></div> <p>In this case, the "demanded bits" of the <code>byteswap(a)</code> expression are <code>0b0...011111111</code>, which inversely means that we don't care about the upper 56 bits. Therefore the whole expression can be optimized to <code>a &gt;&gt; 56</code>.</p> <p>For the Prospero challenge, we can observe that for the resulting pixel values, the value of the result is not used at all, only its sign. Essentially, every program ends implicitly with a <code>sign</code> operation that returns <code>0.0</code> for negative values and <code>1.0</code> for positive values. For clarity, I will show this <code>sign</code> operation in the rest of the section, even if it's not actually in the real code.</p> <p>This makes it possible to simplify certain min/max operations further. Here is an example of a program, together with the intervals of the variables:</p> <div class="code"><pre class="code literal-block"><span class="n">x</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">x</span><span class="w"> </span><span class="c1"># [0.1, 1]</span> <span class="n">y</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">y</span><span class="w"> </span><span class="c1"># [-1, 1]</span> <span class="n">m</span><span class="w"> </span><span class="nb">min</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="c1"># [-1, 1]</span> <span class="n">out</span><span class="w"> </span><span class="nb">sign</span><span class="w"> </span><span class="n">m</span> </pre></div> <p>This program can be optimized to:</p> <div class="code"><pre class="code literal-block"><span class="n">y</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">y</span> <span class="n">out</span><span class="w"> </span><span class="nb">sign</span><span class="w"> </span><span class="n">m</span> </pre></div> <p>Because that expression has the same result as the original expression: if <code>x &gt; 0.1</code>, for the result of <code>min(x, y)</code> to be negative then <code>y</code> needs to be negative.</p> <p>Another, more complex, example is this:</p> <div class="code"><pre class="code literal-block"><span class="n">x</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">x</span><span class="w"> </span><span class="c1"># [1, 100]</span> <span class="n">y</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">y</span><span class="w"> </span><span class="c1"># [-10, 10]</span> <span class="n">z</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">z</span><span class="w"> </span><span class="c1"># [-100, 100]</span> <span class="n">m1</span><span class="w"> </span><span class="nb">min</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="c1"># [-10, 10]</span> <span class="n">m2</span><span class="w"> </span><span class="nb">max</span><span class="w"> </span><span class="n">z</span><span class="w"> </span><span class="n">out</span><span class="w"> </span><span class="c1"># [-10, 100]</span> <span class="n">out</span><span class="w"> </span><span class="nb">sign</span><span class="w"> </span><span class="n">m2</span> </pre></div> <p>Which can be optimized to this:</p> <div class="code"><pre class="code literal-block"><span class="n">y</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">y</span> <span class="n">z</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">z</span> <span class="n">m2</span><span class="w"> </span><span class="nb">max</span><span class="w"> </span><span class="n">z</span><span class="w"> </span><span class="n">y</span> <span class="n">out</span><span class="w"> </span><span class="nb">sign</span><span class="w"> </span><span class="n">m2</span> </pre></div> <p>This is because the sign of <code>min(x, y)</code> is the same as the sign of <code>y</code> if <code>x &gt; 0</code>, and the sign of <code>max(z, min(x, y))</code> is thus the same as the sign of <code>max(z, y)</code>.</p> <p>To implement this optimization, I do a backwards pass over the program after the peephole optimization forward pass. For every <code>min</code> call I encounter, where one of the arguments is positive, I can optimize the <code>min</code> call away and replace it with the other argument. For <code>max</code> calls I simplify their arguments recursively.</p> <p>The code looks roughly like this:</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">work_backwards</span><span class="p">(</span><span class="n">resultops</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> <span class="n">minvalues</span><span class="p">,</span> <span class="n">maxvalues</span><span class="p">):</span> <span class="k">def</span><span class="w"> </span><span class="nf">demand_sign_simplify</span><span class="p">(</span><span class="n">op</span><span class="p">):</span> <span class="n">func</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span> <span class="o">=</span> <span class="n">resultops</span><span class="o">.</span><span class="n">get_func_and_args</span><span class="p">(</span><span class="n">op</span><span class="p">)</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">max</span><span class="p">:</span> <span class="n">narg0</span> <span class="o">=</span> <span class="n">demand_sign_simplify</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> <span class="k">if</span> <span class="n">narg0</span> <span class="o">!=</span> <span class="n">arg0</span><span class="p">:</span> <span class="n">resultops</span><span class="o">.</span><span class="n">setarg</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">narg0</span><span class="p">)</span> <span class="n">narg1</span> <span class="o">=</span> <span class="n">demand_sign_simplify</span><span class="p">(</span><span class="n">arg1</span><span class="p">)</span> <span class="k">if</span> <span class="n">narg1</span> <span class="o">!=</span> <span class="n">arg1</span><span class="p">:</span> <span class="n">resultops</span><span class="o">.</span><span class="n">setarg</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">narg1</span><span class="p">)</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">min</span><span class="p">:</span> <span class="k">if</span> <span class="n">minvalues</span><span class="p">[</span><span class="n">arg0</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mf">0.0</span><span class="p">:</span> <span class="k">return</span> <span class="n">demand_sign_simplify</span><span class="p">(</span><span class="n">arg1</span><span class="p">)</span> <span class="k">if</span> <span class="n">minvalues</span><span class="p">[</span><span class="n">arg1</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mf">0.0</span><span class="p">:</span> <span class="k">return</span> <span class="n">demand_sign_simplify</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> <span class="n">narg0</span> <span class="o">=</span> <span class="n">demand_sign_simplify</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> <span class="k">if</span> <span class="n">narg0</span> <span class="o">!=</span> <span class="n">arg0</span><span class="p">:</span> <span class="n">resultops</span><span class="o">.</span><span class="n">setarg</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">narg0</span><span class="p">)</span> <span class="n">narg1</span> <span class="o">=</span> <span class="n">demand_sign_simplify</span><span class="p">(</span><span class="n">arg1</span><span class="p">)</span> <span class="k">if</span> <span class="n">narg1</span> <span class="o">!=</span> <span class="n">arg1</span><span class="p">:</span> <span class="n">resultops</span><span class="o">.</span><span class="n">setarg</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">narg1</span><span class="p">)</span> <span class="k">return</span> <span class="n">op</span> <span class="k">return</span> <span class="n">demand_sign_simplify</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> </pre></div> <p>In my experiment, this optimization lets me remove 25% of all operations in prospero, at the various levels of my octree. I'll briefly look at performance results further down.</p> <h3 id="further-ideas-about-the-demanded-sign-simplification">Further ideas about the demanded sign simplification</h3> <p>There is another idea how to short-circuit the evaluation of expressions that I tried briefly but didn't pursue to the end. Let's go back to the first example of the previous subsection, but with different intervals:</p> <div class="code"><pre class="code literal-block"><span class="n">x</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">x</span><span class="w"> </span><span class="c1"># [-1, 1]</span> <span class="n">y</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">y</span><span class="w"> </span><span class="c1"># [-1, 1]</span> <span class="n">m</span><span class="w"> </span><span class="nb">min</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="c1"># [-1, 1]</span> <span class="n">out</span><span class="w"> </span><span class="nb">sign</span><span class="w"> </span><span class="n">m</span> </pre></div> <p>Now we can't use the "demanded sign" trick in the optimizer, because neither <code>x</code> nor <code>y</code> are known positive. However, during <em>execution</em> of the program, if <code>x</code> turns out to be negative we can end the execution of this trace immediately, since we know that the result must be negative.</p> <p>So I experimented with adding <code>return_early_if_neg</code> flags to all operations with this property. The interpreter then checks whether the flag is set on an operation and if the result is negative, it stops the execution of the program early:</p> <div class="code"><pre class="code literal-block"><span class="n">x</span><span class="w"> </span><span class="nf">var</span><span class="o">-</span><span class="n">x</span><span class="o">[</span><span class="n">return_early_if_neg</span><span class="o">]</span> <span class="n">y</span><span class="w"> </span><span class="nf">var</span><span class="o">-</span><span class="n">y</span><span class="o">[</span><span class="n">return_early_if_neg</span><span class="o">]</span> <span class="n">m</span><span class="w"> </span><span class="nf">min</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span> <span class="k">out</span><span class="w"> </span><span class="nf">sign</span><span class="w"> </span><span class="n">m</span> </pre></div> <p>This looked pretty promising, but it's also a trade-off because the cost of checking the flag and the value isn't zero. Here's a sketch to the change in the interpreter:</p> <div class="code"><pre class="code literal-block"><span class="k">class</span><span class="w"> </span><span class="nc">DirectFrame</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span> <span class="o">...</span> <span class="k">def</span><span class="w"> </span><span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="n">program</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">program</span> <span class="n">num_ops</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="n">num_operations</span><span class="p">()</span> <span class="n">floatvalues</span> <span class="o">=</span> <span class="p">[</span><span class="mf">0.0</span><span class="p">]</span> <span class="o">*</span> <span class="n">num_ops</span> <span class="k">for</span> <span class="n">op</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_ops</span><span class="p">):</span> <span class="o">...</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">var_x</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">x</span> <span class="o">...</span> <span class="k">else</span><span class="p">:</span> <span class="k">assert</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">program</span><span class="o">.</span><span class="n">get_flags</span><span class="p">(</span><span class="n">op</span><span class="p">)</span> <span class="o">&amp;</span> <span class="n">OPS</span><span class="o">.</span><span class="n">should_return_if_neg</span> <span class="ow">and</span> <span class="n">res</span> <span class="o">&lt;</span> <span class="mf">0.0</span><span class="p">:</span> <span class="k">return</span> <span class="n">res</span> <span class="n">floatvalues</span><span class="p">[</span><span class="n">op</span><span class="p">]</span> <span class="o">=</span> <span class="n">res</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">floatvalues</span><span class="p">[</span><span class="n">num_ops</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> </pre></div> <p>I implemented this in the RPython version, but didn't end up porting it to C, because it interferes with SIMD.</p> <h3 id="dead-code-elimination">Dead code elimination</h3> <p>Matt performs dead code elimination in his implementation by doing a single backwards pass over the program. This is a very simple and effective optimization, and I implemented it in my implementation as well. The dead code elimination pass is very simple: It starts by marking the result operation as used. Then it goes backwards over the program. If the current operation is used, its arguments are marked as used as well. Afterwards, all the operations that are not marked as used are removed from the program. The PyPy JIT actually performs dead code elimination on traces in exactly the same way (and I don't think we ever explained how this works on the blog), so I thought it was worth mentioning.</p> <p>Matt also performs register allocation as part of the backwards pass, but I didn't implement it because I wasn't too interested in that aspect.</p> <h3 id="random-testing-of-the-optimizer">Random testing of the optimizer</h3> <p>To make sure I didn't break anything in the optimizer, I implemented a test that generates random input programs and checks that the output of the optimizer is equivalent to the input program. The test generates random operations, random intervals for the operations and a random input value within that interval. It then runs the optimizer on the input program and checks that the output program has the same result as the input program. This is again implemented with <code>hypothesis</code>. Hypothesis' test case minimization feature is super useful for finding optimizer bugs. It's just not fun to analyze a problem on a many-thousand-operation input file, but Hypothesis often generated reduced test cases that were only a few operations long.</p> <h3 id="visualizing-programs">Visualizing programs</h3> <p>It's actually surprisingly annoying to visualize <code>prospero.vm</code> well, because it's quite a bit too large to just feed it into Graphviz. I made the problem slightly easier by grouping several operations together, where only the first operation in a group is used as the argument for more than one operation further in the program. This made it slightly more manageable for Graphviz. But it still wasn't a big enough improvement to be able to visualize all of <code>prospero.vm</code> in its unoptimized form at the top of the octree.</p> <p>Here's a visualization of the optimized <code>prospero.vm</code> at one of the octree levels:</p> <p><img alt="graph visualization of a part of the input program" src="https://www.pypy.org/images/2025-image-prospero-dataflow.png"></p> <p>The result is on top, every node points to its arguments. The <code>min</code> and <code>max</code> operations form a kind of "spine" of the expression tree, because they are unions and intersection in the constructive solid geometry sense.</p> <p>I also wrote a function to visualize the octree recursion itself, the output looks like this:</p> <p><img alt="graph visualization of the octree recursion, zoomed out" src="https://www.pypy.org/images/2025-image-octree-zoomed-out.png"></p> <p><img alt="graph visualization of the octree recursion, zoomed in" src="https://www.pypy.org/images/2025-image-octree-zoomed-in.png"></p> <p>Green nodes are where the interval analysis determined that the output must be entirely outside the shape. Yellow nodes are where the octree recursion bottomed out.</p> <h3 id="c-implementation">C implementation</h3> <p>To achieve even faster performance, I decided to rewrite the implementation in C. While RPython is great for prototyping, it can be challenging to control low-level aspects of the code. The rewrite in C allowed me to experiment with several techniques I had been curious about:</p> <ul> <li><a href="https://blog.reverberate.org/2021/04/21/musttail-efficient-interpreters.html"><code>musttail</code> optimization</a> for the interpreter.</li> <li>SIMD (Single Instruction, Multiple Data): Using Clang's <a href="https://clang.llvm.org/docs/LanguageExtensions.html#vectors-and-extended-vectors"><code>ext_vector_type</code></a>, I process eight pixels at once using AVX (or some other SIMD magic that I don't properly understand).</li> <li>Efficient struct packing: I packed the operations struct into just 8 bytes by limiting the maximum number of operations to 65,536, with the idea of making the optimizer faster.</li> </ul> <p>I didn't rigorously study the performance impact of each of these techniques individually, so it's possible that some of them might not have contributed significantly. However, the rewrite was a fun exercise for me to explore these techniques. The code can be found <a href="https://github.com/cfbolz/pyfidget/blob/main/pyfidget/experiments.c">here</a>.</p> <h3 id="testing-the-c-implementation">Testing the C implementation</h3> <p>At various points I had bugs in the C implementation, leading to a fun glitchy version of prospero:</p> <p><img alt="glitchy prospero" src="https://www.pypy.org/images/2025-glitchy-prospero.png"></p> <p>To find these bugs, I used the same random testing approach as in the RPython version. I generated random input programs as strings in Python and checked that the output of the C implementation was equivalent to the output of the RPython implementation (simply by calling out to the shell and reading the generated image, then comparing pixels). This helped ensure that the C implementation was correct and didn't introduce any bugs. It was surprisingly tricky to get this right, for reasons that I didn't expect. At lot of them are related to the fact that in C I used <code>float</code> and Python uses <code>double</code> for its (Python) <code>float</code> type. This made the random tester find weird floating point corner cases where rounding behaviour between the widths was different.</p> <p>I solved those by using <code>double</code> in C when running the random tests by means of an <code>IFDEF</code>.</p> <p>It's super fun to watch the random program generator produce random images, here are a few:</p> <iframe width="560" height="560" src="https://www.youtube.com/embed/VqU5n3zzOjc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> <h3 id="performance">Performance</h3> <p>Some very rough performance results on my laptop (an AMD Ryzen 7 PRO 7840U with 32 GiB RAM running Ubuntu 24.04), comparing the RPython version, the C version (with and without demanded info), and Fidget (in <code>vm</code> mode, its JIT made things worse for me), both for 1024x1024 and 4096x4096 images:</p> <table> <thead> <tr> <th>Implementation</th> <th>1024x1024</th> <th>4096x4096</th> </tr> </thead> <tbody> <tr> <td>RPython</td> <td>26.8ms</td> <td>75.0ms</td> </tr> <tr> <td>C (no demanded info)</td> <td>24.5ms</td> <td>45.0ms</td> </tr> <tr> <td>C (demanded info)</td> <td>18.0ms</td> <td>37.0ms</td> </tr> <tr> <td>Fidget</td> <td>10.8ms</td> <td>57.8ms</td> </tr> </tbody> </table> <p>The demanded info seem to help quite a bit, which was nice to see.</p> <h3 id="conclusion">Conclusion</h3> <p>That's it! I had lots of fun with the challenge and have a whole bunch of other ideas I want to try out, thanks Matt for this interesting puzzle.</p>toy-optimizerhttps://www.pypy.org/posts/2025/04/prospero-in-rpython.htmlWed, 09 Apr 2025 15:07:09 GMTAquileo | PyPy v7.3.19 releasehttps://www.pypy.org/posts/2025/02/pypy-v7319-release.htmlmattip<section id="pypy-v7-3-19-release-of-python-2-7-3-10-and-3-11-beta"> <h2>PyPy v7.3.19: release of python 2.7, 3.10 and 3.11 beta</h2> <p>The PyPy team is proud to release version 7.3.19 of PyPy. This is primarily a bug-fix release fixing JIT-related problems and follows quickly on the heels of the previous release on Feb 6, 2025.</p> <p>This release includes a python 3.11 interpreter. There were bugs in the first beta that could prevent its wider use, so we are continuing to call this release "beta". In the next release we will drop 3.10 and remove the "beta" label.</p> <p>The release includes three different interpreters:</p> <ul class="simple"> <li><p>PyPy2.7, which is an interpreter supporting the syntax and the features of Python 2.7 including the stdlib for CPython 2.7.18+ (the <code class="docutils literal">+</code> is for backported security updates)</p></li> <li><p>PyPy3.10, which is an interpreter supporting the syntax and the features of Python 3.10, including the stdlib for CPython 3.10.16.</p></li> <li><p>PyPy3.11, which is an interpreter supporting the syntax and the features of Python 3.11, including the stdlib for CPython 3.11.11.</p></li> </ul> <p>The interpreters are based on much the same codebase, thus the triple release. This is a micro release, all APIs are compatible with the other 7.3 releases. It follows after 7.3.17 release on August 28, 2024.</p> <p>We recommend updating. You can find links to download the releases here:</p> <blockquote> <p><a class="reference external" href="https://pypy.org/download.html">https://pypy.org/download.html</a></p> </blockquote> <p>We would like to thank our donors for the continued support of the PyPy project. If PyPy is not quite good enough for your needs, we are available for <a class="reference external" href="https://www.pypy.org/pypy-sponsors.html">direct consulting</a> work. If PyPy is helping you out, we would love to hear about it and encourage submissions to our <a class="reference external" href="https://pypy.org/blog">blog</a> via a pull request to <a class="reference external" href="https://github.com/pypy/pypy.org">https://github.com/pypy/pypy.org</a></p> <p>We would also like to thank our contributors and encourage new people to join the project. PyPy has many layers and we need help with all of them: bug fixes, <a class="reference external" href="https://doc.pypy.org/">PyPy</a> and <a class="reference external" href="https://rpython.readthedocs.org">RPython</a> documentation improvements, or general <a class="reference external" href="https://doc.pypy.org/project-ideas.html">help</a> with making RPython's JIT even better.</p> <p>If you are a python library maintainer and use C-extensions, please consider making a <a class="reference external" href="https://hpyproject.org/">HPy</a> / <a class="reference external" href="https://cffi.readthedocs.io">CFFI</a> / <a class="reference external" href="https://cppyy.readthedocs.io">cppyy</a> version of your library that would be performant on PyPy. In any case, both <a class="reference external" href="https://github.com/joerick/cibuildwheel">cibuildwheel</a> and the <a class="reference external" href="https://github.com/matthew-brett/multibuild">multibuild system</a> support building wheels for PyPy.</p> <section id="what-is-pypy"> <h3>What is PyPy?</h3> <p>PyPy is a Python interpreter, a drop-in replacement for CPython It's fast (<a class="reference external" href="https://speed.pypy.org">PyPy and CPython</a> performance comparison) due to its integrated tracing JIT compiler.</p> <p>We also welcome developers of other <a class="reference external" href="https://rpython.readthedocs.io/en/latest/examples.html">dynamic languages</a> to see what RPython can do for them.</p> <p>We provide binary builds for:</p> <ul class="simple"> <li><p><strong>x86</strong> machines on most common operating systems (Linux 32/64 bits, Mac OS 64 bits, Windows 64 bits)</p></li> <li><p>64-bit <strong>ARM</strong> machines running Linux (<code class="docutils literal">aarch64</code>) and macos (<code class="docutils literal">macos_arm64</code>).</p></li> </ul> <p>PyPy supports Windows 32-bit, Linux PPC64 big- and little-endian, Linux ARM 32 bit, RISC-V RV64IMAFD Linux, and s390x Linux but does not release binaries. Please reach out to us if you wish to sponsor binary releases for those platforms. Downstream packagers provide binary builds for debian, Fedora, conda, OpenBSD, FreeBSD, Gentoo, and more.</p> </section> <section id="what-else-is-new"> <h3>What else is new?</h3> <p>For more information about the 7.3.19 release, see the <a class="reference external" href="https://doc.pypy.org/release-v7.3.19.html#changelog">full changelog</a>.</p> <p>Please update, and continue to help us make pypy better.</p> <p>Cheers, The PyPy Team</p> </section> </section>releasehttps://www.pypy.org/posts/2025/02/pypy-v7319-release.htmlWed, 26 Feb 2025 12:00:00 GMT