5
$\begingroup$

Consider the following pseudocode:

function make_counter(start: i32) {
    let mut count: i32 = start;

    function increase() {
        count += 1;
        print(count);
        return increase
    }

    return increase
}

let counter = make_counter()
counter() // 1
counter() // 2

How should similar code be compiled into WebAssembly?


My main question is, where should the variables be stored? Where should the functions be stored?

  • Should variables be stored in global?

But every time make_counter is called, there will be one more variable. Do I need to statically analyze how many times this function is called?

  • Should functions be defined in table?

Dynamically called functions need to be defined in the table as element, and there isn't no way to call them through function pointers. Moreover, tables cannot be dynamically expanded in WebAssembly, and function pointers cannot be returned.

$\endgroup$
7
  • $\begingroup$ What an interesting construction! So let counter = make_counter(); counter(); counter() is equivalent to let counter = make_counter(); counter()() or even just make_counter()()()? $\endgroup$ Commented Mar 6 at 13:20
  • $\begingroup$ Related: Can "doubt" sometimes mean "question"? $\endgroup$ Commented Mar 6 at 14:42
  • $\begingroup$ @PeterMortensen, sorry, I used Google Translate, my original meaning should be closer to confuse than doubt. $\endgroup$
    – Aster
    Commented Mar 6 at 15:39
  • $\begingroup$ There's a chapter of SICP on how to implement closures. Do you think there's something about WebAssembly that implies a unique method of implementation? $\endgroup$
    – Barmar
    Commented Mar 7 at 22:04
  • $\begingroup$ @Barmar WebAssembly handles function pointers differently to other assembly languages - essentially they are opaque values, not actual pointers. In particular, you cannot create new functions at runtime and then point to them. This means some methods for implementing closures are not possible in WebAssembly (though others are still possible). $\endgroup$
    – kaya3
    Commented Mar 10 at 15:58

2 Answers 2

5
$\begingroup$

The typical high-level approach to compiling closures is that they combine both a function pointer and a data value representing all captured variables (e.g. a struct). You need both of those to execute the closure; you can think of this as being essentially the same way you would compile an object.

When the function runs, it is given that data structure as an additional argument, and in its preamble it copies values or pointers out of the data structure. Alternatively, your calling convention might unpack the structure before the function runs.

There is only ever one copy of the function compiled, and all the closures share it. This function is compiled statically and you know how many there will be in advance. Each instantiation of the closure is a separate capture structure containing the same function pointer and its own data. This will work the same way as first-class functions that aren’t closures, just using the function index.

For a closure over shared mutable state, you need to store pointers to the long-term storage locations. This may mean sharing these capture structures between multiple closures, or chaining them. You can sort out how this ought to work under your language semantics, or if it’s even possible.

You can do all of this in WebAssembly. The only thing you allocate dynamically is the capture storage structure, which is fine. That can contain the function index within the table to use when you execute it. You do need to know whether something is a closure or not in order to call it, which may require another layer of indirection in some cases, but otherwise there’s no particular complication.

$\endgroup$
1
$\begingroup$

The accepted answer illustrates how closures are compiled in general. To supplement with some WebAssembly specific details, I find the following references helpful:

  1. The WebAssembly's GC proposal has an example similar to the program in your question, and manually translates it to WAT format.

  2. The MoonBit language is a Rust-like-but-with-GC language that targets WebAssembly. I find it also emits somewhat readable WAT output. It's not on Godbolt yet, but I'd recommend you play with it and study how closures are compiled by it.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .