Fiber implements an proof-of-concept Python decorator that rewrites a function

Related tags

Miscellaneousfiber
Overview

Fiber

Fiber implements an proof-of-concept Python decorator that rewrites a function so that it can be paused and resumed (by moving stack variables to a heap frame and adding if statements to simulate jumps/gotos to specific lines of code).

Then, using a trampoline function that simulates the call stack on the heap, we can call functions that recurse arbitrarily deeply without stack overflowing (assuming we don't run out of heap memory).

cache = {}

@fiber.fiber(locals=locals())
def fib(n):
    assert n >= 0
    if n in cache:
        return cache[n]
    if n == 0:
        return 0
    if n == 1:
        return 1
    cache[n] = fib(n-1) + fib(n-2)
    return cache[n]

print(sys.getrecursionlimit())  # 1000 by default

# https://www.wolframalpha.com/input/?i=fib%281010%29+mod+10**5
print(trampoline.run(fib, [1010]) % 10 ** 5) # 74305

Please do not use this in production.

TOC

How it works

A quick refresher on the call stack: normally, when some function A calls another function B, A is "paused" while B runs to completion. Then, once B finishes, A is resumed.

In order to move the call stack to the heap, we need to transform function A to (1) store all variables on the heap, and (2) be able to resume execution at specific lines of code within the function.

The first step is easy: we rewrite all local loads and stores to instead load and store in a frame dictionary that is passed into the function. The second is more difficult: because Python doesn't support goto statements, we have to insert if statements to skip the code prefix that we don't want to execute.

There are a variety of "special forms" that cannot be jumped into. These we must handle by rewriting them into a form that we do handle.

For example, if we recursively call a function inside a for loop, we would like to be able to resume execution on the same iteration. However, when Python executes a for loop on an non-iterator iterable it will create a new iterator every time. To handle this case, we rewrite for loops into the equivalent while loop. Similarly, we must rewrite boolean expressions that short circuit (and, or) into the equivalent if statements.

Lastly, we must replace all recursive calls and normal returns by instead returning an instruction to a trampoline to call the child function or return the value to the parent function, respectively.

To recap, here are the AST passes we currently implement:

  1. Rewrite special forms:
    • for_to_while: Transforms for loops into the equivalent while loops.
    • promote_while_cond: Rewrites the while conditional to use a temporary variable that is updated every loop iteration so that we can control when it is evaluated (e.g. if the loop condition includes a recursive call).
    • bool_exps_to_if: Converts and and or expressions into the equivalent if statements.
  2. promote_to_temporary: Assigns the results of recursive calls into temporary variables. This is necessary when we make multiple recursive calls in the same statement (e.g. fib(n-1) + fib(n-2)): we need to resume execution in the middle of the expression.
  3. remove_trivial_temporaries: Removes temporaries that are assigned to only once and are directly assigned to some other variable, replacing subsequent usages with that other variable. This helps us detect tail calls.
  4. insert_jumps: Marks the statement after yield points (currently recursive calls and normal returns) with a pc index, and inserts if statements so that re-execution of the function will resume at that program counter.
  5. lift_locals_to_frame: Replaces loads and stores of local variables to loads and stores in the frame object.
  6. add_trampoline_returns: Replaces places where we must yield (recursive calls and normal returns) with returns to the trampoline function.
  7. fix_fn_def: Rewrites the function defintion to take a frame parameter.

See the examples directory for functions and the results after each AST pass. Also, see src/trampoline_test.py for some test cases.

Performance

A simple tail-recursive function that computes the sum of an array takes about 10-11 seconds to compute with Fiber. 1000 iterations of the equivalent for loop takes 7-8 seconds to compute. So we are slower by roughly a factor of 1000.

lst = list(range(1, 100001))

# fiber
@fiber.fiber(locals=locals())
def sum(lst, acc):
    if not lst:
        return acc
    return sum(lst[1:], acc + lst[0])

# for loop
total = 0
for i in lst:
    total += i

print(total, trampoline.run(sum, [lst, 0]))  # 5000050000, 5000050000

We could improve the performance of the code by eliminating redundant if checks in the generated code. Also, as we statically know the stack variables, we can use an array for the stack frame and integer indexes (instead of a dictionary and string hashes + lookups). This should improve the performance significantly, but there will still probably be a large amount of overhead.

Another performance improvement is to inline the stack array: instead of storing a list of frames in the trampoline, we could variables directly in the stack. Again, we can compute the frame size statically. Based on some tests in a handwritten JavaScript implementation, this has the potential to speed up the code by roughly a factor of 2-3, at the cost of a more complex implementation.

Limitations

  • The transformation works on the AST level, so we don't support other decorators (for example, we cannot use functools.cache in the above Fibonacci example).

  • The function can only access variables that are passed in the locals= argument. As a consequence of this, to resolve recursive function calls, we maintain a global mapping of all fiber functions by name. This means that fibers must have distinct names.

  • We don't support some special forms (ternaries, comprehensions). These can easily be added as a rewrite transformation.

  • We don't support exceptions. This would require us to keep track of exception handlers in the trampoline and insert returns to the trampoline to register and deregister handlers.

  • We don't support generators. To add support, we would have to modify the trampoline to accept another operation type (yield) that sends a value to the function that called next(). Also, the trampoline would have to support multiple call stacks.

Possible improvements

  • Improve test coverage on some of the AST transformations.
    • remove_trivial_temporaries may have a bug if the variable that it is replaced with is reassigned to another value.
  • Support more special forms (comprehensions, generators).
  • Support exceptions.
  • Support recursive calls that don't read the return value.

Questions

Why didn't you use Python generators?

It's less interesting as the transformations are easier. Here, we are effectively implementing generators in userspace (i.e. not needing VM support); see the answer to the next question for why this is useful.

Also, people have used generators to do this; see one recent generator example.

Why did you write this?

  • A+ project for CS 61A at Berkeley. During the course, we created a Scheme interpreter. The extra credit question we to replace tail calls in Python with a return to a trampoline, with the goal that tail call optimization in Python would let us evaluate tail calls to arbitrary depth in Scheme, in constant space.

    The test cases for the question checked whether interpreting tail-call recursive functions in Scheme caused a Python stack overflow. Using this Fiber implementation, (1) without tail call optimization in our trampoline, we would still be able to pass the test cases (we just wouldn't use constant space) and (2) we can now evaluate any Scheme expression to arbitrary depth, even if they are not in tail form.

  • The React framework has an a bug open which explores a compiler transform to rewrite JavaScript generators to a state machine so that recursive operations (render, reconcilation) can be written more easily. This is necessary because some JavaScript engines still don't support generators.

    This project basically implements a rough version of that compiler transform as a proof of concept, just in Python. https://github.com/facebook/react/pull/18942

Contributing

See CONTRIBUTING.md for more details.

License

Apache 2.0; see LICENSE for more details.

Disclaimer

This is a personal project, not an official Google project. It is not supported by Google and Google specifically disclaims all warranties as to its quality, merchantability, or fitness for a particular purpose.

Owner
Tyler Hou
Tyler Hou
AlexaUsingPython - Alexa will pay attention to your order, as: Hello Alexa, play music, Hello Alexa

AlexaUsingPython - Alexa will pay attention to your order, as: Hello Alexa, play music, Hello Alexa, what's the time? Alexa will pay attention to your order, get it, and afterward do some activity as

Abubakar Sattar 10 Aug 18, 2022
Python Interactive Graphical System made during Computer Graphics classes (INE5420-2021.1)

PY-IGS - The PYthon Interactive Graphical System The PY-IGS Installation To install this software you will need these dependencies (with their thevelo

Enzo Coelho Albornoz 4 Dec 03, 2021
30DaysOfCode-PhoenixClub - Solution of everyday coding problem given in 30DaysofCode contest held on Hackerrank

30DaysOfCode-PhoenixClub 👨‍💻 Every day problems solution given in 30DaysOfCode

Urveshkumar 8 Jan 30, 2022
A submodule of rmcrkd/ODE-Uniqueness

Heston-ODE This repo contains the Heston-related code that accompanies the article One-sided maximal uniqueness for a class of spatially irregular ord

0 Jan 05, 2022
Practice10 - Operasi String With Python

Operasi String MY SOSIAL MEDIA : Apa itu Python String ? String adalah urutan si

Maulana Reza Badrudin 1 Jan 05, 2022
A Python version of Canvacord

A copy of canvacord made in python! Table of contents Installation Examples Creating Images Links Downloads Installation Run any of these commands in

10 Mar 28, 2022
Simple Python API for the Ergo Platform Explorer

Ergo is a "Resilient Platform for Contractual Money." It is designed to be a platform for applications with the main focus to provide an efficient, se

7 Jul 06, 2021
This repo presents you the official code of "VISTA: Boosting 3D Object Detection via Dual Cross-VIew SpaTial Attention"

VISTA VISTA: Boosting 3D Object Detection via Dual Cross-VIew SpaTial Attention Shengheng Deng, Zhihao Liang, Lin Sun and Kui Jia* (*) Corresponding a

104 Dec 29, 2022
LibreMind is a free meditation app made in under 24 hours. It has various meditation, breathwork, and visualization exercises.

libreMind Meditation exercises What is it? LibreMind is a free meditation app made in under 24 hours. It has various meditation, breathwork, and visua

1 May 24, 2022
Listen Surah, prepare for next and Endless life...

Al-Quran In this repository, I have linked up all Surah with Arabic-Bangla Audio From Youtube. So, you just need to choose and listen. and the ( surah

SpiderX 1 Sep 30, 2022
A stupid obfuscation thing

StupidObfuscation A stupid obfuscation thing How it works The obfuscator takes a string, splits into pieces of one, then, using the table from letter.

Echo 2 May 03, 2022
Py-Parser est un parser de code python en python encore en plien dévlopement.

PY - PARSER Py-Parser est un parser de code python en python encore en plien dévlopement. Une fois achevé, il servira a de nombreux projets comme glad

pf4 3 Feb 21, 2022
PORTSCANNING-IN-PYTHON - A python threaded portscanner to scan websites and ipaddresses

PORTSCANNING-IN-PYTHON This is a python threaded portscanner to scan websites an

1 Feb 16, 2022
Automatically load and dump your dataclasses 📂🙋

file dataclasses Installation By default, filedataclasses comes with support for JSON files only. To support other formats like YAML and TOML, filedat

Alon 1 Dec 30, 2021
Repository for DNN training, theory to practice, part of the Large Scale Machine Learning class at Mines Paritech

DNN Training, from theory to practice This repository is complementary to the deep learning training lesson given to les Mines ParisTech on the 11th o

Alexandre Défossez 6 Nov 14, 2022
If Google News had a Python library

pygooglenews If Google News had a Python library Created by Artem from newscatcherapi.com but you do not need anything from us or from anyone else to

Artem Bugara 1.1k Jan 08, 2023
A StarkNet project template based on a Pythonic environment

StarkNet Project Template This is an opinionated StarkNet project template. It is based around the Python's ecosystem and best practices. tox to manag

Francesco Ceccon 5 Apr 21, 2022
Repo to store back end infrastructure for Message in a Bottle

Message in a Bottle Backend API RESTful API for Message in a Bottle frontend application consumption. About The Project • Tools Used • Local Set Up •

4 Dec 05, 2021
Diff Match Patch is a high-performance library in multiple languages that manipulates plain text.

The Diff Match and Patch libraries offer robust algorithms to perform the operations required for synchronizing plain text. Diff: Compare two blocks o

Google 5.9k Dec 30, 2022
Resizing using nnedi3/znedi3/nnedi3cl with center alignment and correct chroma placement

nnedi3_resample A VapourSynth script for easy resizing using nnedi3/znedi3/nnedi3cl with center alignment and correct chroma placement. Requirements n

Home Of VapourSynth Evolution 12 Sep 08, 2022