Inside SPy, part 2: Language semantics - Antonio Cuni's blog
Skip to content
Initializing search
Categories
Related links
Inside SPy 🥸, part 2: Language semantics¶
This is the second post of the Inside SPy series. The first<br>post was mostly about motivations and<br>goals of SPy. This post will cover in more detail the<br>semantics of SPy, including the parts which make it different from CPython.
We will talk about phases of execution, colors, redshifting, the very peculiar way<br>SPy implements static typing, and we will start to dive into metaprogramming.
Before diving in, I want to express my gratitude to my employer,<br>Anaconda, for giving me the opportunity to dedicate<br>100% of my time to this open-source project.
Motivation and goals, recap¶
Shameless plug: give SPy a star ⭐
I admit I never cared much about GitHub stars, but it looks like nowadays it's what<br>you need to be considered<br>important. We are at 678 stars at<br>the moment of writing, let's try to get to 5000!
Part 1 describes motivations and<br>goals in great detail, but let's do a quick recap.
The main motivation is to make Python faster; by "faster" I mean comparable to C, Rust<br>and Go. After spending 20 years in this problem space, I am convinced that it's<br>impossible to achieve such a goal without breaking compatibility.
The second motivation is that static typing is playing a more and more important role in<br>the Python community, but Python is not a language designed for that, which leads to a<br>suboptimal<br>experience.
There are two possible definitions of SPy; both of them are accurate, although from very<br>different perspectives:
SPy is an interpreter and a compiler for a statically typed variant of Python,<br>with focus on performance.
and:
SPy is a thought experiment to determine how much dynamicity we can remove from Python<br>while still feeling Pythonic.
The part about "interpreter and compiler" is fundamental: the interpreter is needed<br>for ease of development and debugging, the compiler is needed for speed. The job of SPy<br>is to ensure that the two pieces have the exact same semantics so that the compilation<br>step is just a transparent speedup.
100% compatibility with Python is explicitly not a goal . The Zen of<br>SPy contains the goals<br>and design guidelines of SPy. This is a shortened version, see the link for full<br>details:
Easy to use and implement.
We have an interpreter.
We have a compiler.
Static typing.
Performance matters.
Predictable performance.
Rich metaprogramming capabilities.
Zero cost abstractions.
Opt-in dynamism.
One language, two levels.
SPy version and playground
At the moment of writing, SPy is still changing very rapidly and it's very likely<br>that some of the examples will break in the future. We don't have any official<br>release yet, but all the following examples have been tried on SPy commit<br>229235b8.
All the examples have a Try it yourself button which opens the code snippet in the<br>SPy Playground, a PyScript app to try SPy directly in the browser. The official SPy<br>playground tracks the latest git main, while this<br>blog post uses a custom<br>version pinned<br>to this exact commit.
Phases of execution and compilation pipeline¶
From the point of view of the user, SPy code runs in three distinct execution<br>phases :
Import time : this is when we run all the module-level code, including global<br>variable initializers, decorators, metaclasses, etc.. After this phase, all the<br>globals are frozen .
Redshift : during this phase we apply partial evaluation to all expressions that<br>are safe to be evaluated eagerly. This is an optional phase which happens only<br>during compilation or when explicitly requested. The presence/absence of redshift<br>should not have any visible effects on the behavior of the program.
Runtime : the actual execution of the program, starting from a main function.
The following is a diagram representing the compilation pipeline in the simplified case<br>of a single .spy file:
graph TD
subgraph FRONTEND["Import time"]<br>SRC["*.spy source"]:::node<br>AST["Untyped AST"]:::node<br>SYMAST["Untyped AST + symtable"]:::node<br>IMPORTLABEL(["import"]):::label
SRC -- parse --> AST<br>AST -- ScopeAnalyzer --> SYMAST<br>SYMAST --- IMPORTLABEL<br>end
SPyVM["SPyVM"]:::node<br>IMPORTLABEL --> SPyVM
subgraph RS[" "]<br>RSLABEL(["redshift"]):::label<br>REDSHIFTED["Typed AST"]:::node<br>end
C["C Source (.c)"]:::node<br>EXE_NAT["Native exe"]:::node<br>EXE_WASI["WASI exe"]:::node<br>EXE_EM["Emscripten exe"]:::node
subgraph RT["Runtime"]<br>INTERP(["interp"]):::label<br>DOPPLER(["interp(doppler)"]):::label<br>EXECUTE_NAT(["execute"]):::label<br>EXECUTE_WASI(["execute"]):::label<br>EXECUTE_EM(["execute"]):::label<br>end
OUT["Output"]:::node<br>SPyVM --- INTERP --> OUT
SPyVM --- RSLABEL --> REDSHIFTED<br>REDSHIFTED -- cwrite --> C<br>REDSHIFTED --- DOPPLER --> OUT
C -- cc --> EXE_NAT --- EXECUTE_NAT --> OUT<br>C -- cc --> EXE_WASI --- EXECUTE_WASI --> OUT<br>C -- cc --> EXE_EM --- EXECUTE_EM --> OUT
style FRONTEND fill:#fafaff,stroke:#9090cc,stroke-dasharray:5 5<br>style RS...