logo

Tutorial @ PyCon USA 2022. April 28, 2022. Salt Lake, UT, USA.

Check out the slides for this tutorial.

1. Introduction

Wasm is a binary code format specification first released in 2017. This technology can be implemented in web browsers or standalone applications in a secure, open, portable, and efficient fashion. More precisely, Wasm is an intermediate language for a stack-based virtual machine that uses a just-in-time (JIT) compiler to produce native machine code. Although Wasm was primarily designed as a compilation target for languages such as C/C++ or Rust, it can be integrated with Python in interesting ways. And that’s what we’ll be focusing on during this tutorial. Some experience with JavaScript and web development might come in handy but is not strictly required. At the end, we’ll show how to develop a tiny compiler that has Wasm as its compilation target.

2. Preparing the Gitpod Workspace

2.1. Create the Workspace

You must have a GitHub account in order to proceed. Please sign up if you don’t have one.

When the option appears, press “Continue with GitHub” and authorize Gitpod to access your account. A few moments later a small notification will appear in the bottom right corner asking “Do you want to open this workspace in VS Code Desktop?”. Press “Don’t Show Again” because we’ll be using VS Code in the browser.

From now on the gitpod.io URL will take you to your Gitpod dashboard as long as you’re logged-in.

2.2. Using the Terminal

In this tutorial we’ll make frequent use of the terminal. To open a terminal window on your workspace press Ctrl-J (Windows and Linux) or Cmd-J (macOS). When using this Gitpod workspace every time you open a terminal you’ll need to type:

source pycon2022

This command sets the environment variables that are required to correctly run the software we’ll be using during the tutorial.

2.3. About Gitpod Free Plan

The Gitpod free plan (called the “Open Source” plan) includes 50 Gitpod hours per month. One Gitpod hour allows you to use one workspace for one hour. To see your remaining hours go to your Gitpod dashboard and select the “Settings” tab and then select “Plans” from the left-hand menu. If you want to stop the workspace at any moment, press the “Gitpod” button in the bottom left corner and select “Gitpod: Stop Workspace” in the dropdown menu. Otherwise, workspaces in the free plan have a 30 minute inactivity timeout.


With all this in mind, you are now ready to continue with the rest of the tutorial.

3. Pyodide: Python on the Browser

Pyodide uses the Emscripten compiler toolchain to port CPython to WebAssembly. This means that we are able to execute Python code directly from within a web browser. Pyodide was created in 2018 by Michael Droettboom at Mozilla.

Try Pyodide in a REPL directly in your browser (no installation needed).

3.1. A Simple Demo

Let’s demonstrate how to use Pyodide with some ordinary Python code. Open the pyodide/some_python_code.py, which has the following content:

File: pyodide/some_python_code.py
# Copyright (C) 2022 Ariel Ortiz

"""Some Python code to test Pyodide."""

from datetime import datetime
from random import choice
from sys import version

ONELINERS = [
    'A data scientist is a person who is better at '
        'statistics than any programmer and better at '
        'programming than any statistician.',
    "!false (It's funny because it's true.)",
    'Why do programmers always mix up Christmas and '
        'Halloween? Because Dec 25 is Oct 31.',
    'Have you tried turning it off and on again?'
]


def get_version():
    """Returns a string with the Python version used by the
    Pyodide runtime.
    """
    return 'Python ' + version.split()[0]


def get_date_and_time():
    """Returns a string with the current date and time using the
    ISO 8601 format.
    """
    now = datetime.now()
    result = now.isoformat()
    return result


def get_quote():
    """Obtains and returns a string with a funny random oneline
    quote.
    """
    return choice(ONELINERS)


if __name__ == '__main__':
    # Some simple tests to make sure that the code works as expected.
    print(get_version())
    print(get_date_and_time())
    print(get_quote())

Let’s run this Python code from the terminal to make sure that it works. Change the current working directory to pyodide by typing the following at the terminal:

cd /workspace/pycon2022-wasm/pyodide

Now, to run the Python file type:

python some_python_code.py

The output should de someting like this:

Python 3.10.4
2022-04-14T23:01:24.389322
A data scientist is a person who is better at statistics than any programmer and better at programming than any statistician.

Now, let’s see how to integrate it in a web page using Pyodide. Open the pyodide/index.html file and review its contents:

File: pyodide/index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Pyodide Test</title>
    <script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"> (1)
    </script>
  </head>
  <body>
    <h1>Pyodide Test</h1>
    <p id="id_1"><em>Wait for it...</em></p> (2)
    <p id="id_2"></p>
    <p id="id_3"></p>
    </ul>
    <script> (3)
      async function main() { (4)
        let pyodide = await loadPyodide(); (5)
        pyodide.runPython( (6)
          await (await fetch("some_python_code.py")).text()
        );
        let version = pyodide.runPython('get_version()'); (7)
        let date_and_time = pyodide.runPython('get_date_and_time()'); (8)
        let quote = pyodide.runPython('get_quote()') (9)
        document.getElementById("id_1").innerHTML = version; (10)
        document.getElementById("id_2").innerHTML = date_and_time;
        document.getElementById("id_3").innerHTML = quote;
      }

      main(); (11)
    </script>
  </body>
</html>
1 To include Pyodide runtime in a project you must use this Content Delivery Network (CDN) link.
2 We define three HTML <p> elements uniquely identified with id_1, id_2, and id_3. These will be used to place the results of our Python code.
3 We add some JavaScript code in order to interact with the Pyodide runtime.
4 An asynchronous function is required to load the Pyodide runtime.
5 The pyodide.js file defines a single async function called loadPyodide which sets up the Python environment and returns the Pyodide top level namespace.
6 Python code is executed using the pyodide.runPython function. It takes as input a string of Python code. If the code ends in an expression, it returns the result of the expression, converted to an equivalent JavaScript object. Here, using the fetch function, we get the full contents of the some_python_code.py Python source file.
7 This Python code obtains and returns the Python version used by the Pyodide runtime.
8 This Python code obtains and returns the current date and time using the ISO 8601 format.
9 This Python code obtains and returns a funny random quote.
10 With the obtained results, we now update the corresponding HTML elements in our page.
11 We call our asynchronous function.

To see this working we need to run a web server. At the terminal type:

python -m http.server

Click on the “Open Browser” button that appears in the bottom right-hand side of the screen. After a few seconds you should see something like this:

index

Press Ctrl-C at the terminal where the web server is running in order to stop it. For the moment keep it running.

3.2. Exercise A ★

Modify the get_date_and_time function in the pyodide/some_python_code.py file so that the returned time and date string is concatenated with one of the following texts:

  • "Happy birthday to me!", if today happens to be your birthday

  • "A very merry unbirthday to me!", if today is not your birthday

Note that you will need to hard code your personal birthdate information into the program.

Test your code first at the terminal. Type:

python some_python_code.py

If the code produces the expected output you can now test it on the web page by refreshing it. Make sure the web server is still running.

Once the variable now has been initialized with today’s date, use the attributes now.day and now.month to get today’s integer day and month values.

4. Hand coding WebAssembly

WebAssembly, or WASM, is a portable binary code format that is delivered to the browser or other runtime system as modules using the .wasm file extension. It also defines a corresponding textual assembly language, called WebAssembly Text Format or WAT, which uses the .wat extension. There is a close correspondence between the binary and textual formats, with the latter simply making it easier for humans to read .wasm modules.

4.1. S-expressions

In both the binary and textual formats, the fundamental unit of code in WebAssembly is a module. In the text format, a module is represented as one big S-expression. S-expressions are a fairly old and simple textual format for representing trees, and thus we can think of a module as a tree of nodes that describe the module’s structure and its code.

S-expression stands for “symbolic expression”, and it is commonly encountered in the different Lisp language dialects such as Common Lisp, Scheme, Racket, and Clojure. These programming languages use s-expressions to represent the computer program and also the program’s data.

Let’s see what an S-expression looks like. Each node in the tree goes inside a pair of parentheses: ( …​ ). The first label inside the parentheses tells you what type of node it is, and after that there is a space-separated list of either attributes or child nodes. For example:

(module
  (func
    (export "myFunction")
    return))

In this example the node types are module, func and export. The root of the s-expression is the module node. This node has func as its only child. The func node has two children: the export node and the return attribute. Finally, the export node has only one child, which is the "myFunction" attribute. Indentation is used here to clarify the tree-structure of the s-expression.

4.2. Data Types

WebAssembly programs support only four data types:

  • i32: 32-bit integer

  • i64: 64-bit integer

  • f32: 32-bit float

  • f64: 64-bit float

These types are used as prefixes in most WebAssembly instructions. They are also used when declaring variables, parameters, and function return types.

4.3. Stack Machine

A stack is a data structure with two operations: push and pop. Items are pushed (inserted) onto the stack and subsequently popped (removed) from the stack in last in, first out (LIFO) order.

A real-life example is a stack of pancakes: you can only take a pancake (at least easily) from the top of the stack, and you can only add a pancake to the top of the stack.

pancakes

WebAssembly execution is defined in terms of a stack machine where the basic idea is that every instruction pushes and/or pops a certain number to/from a stack. For example, to add the numbers 7 and 5, the following three steps need to be carried out on the stack:

  1. Push the 7.

  2. Push the 5.

  3. Pop the two top values (5 and 7), perform the addition (7 + 5), and finally push the result (12).

The following figure illustrates the three steps described above: