Skip to content

Instantly share code, notes, and snippets.

@gertvv
Last active July 14, 2024 13:51
Show Gist options
  • Save gertvv/39475c39f6419b5a31786e4f64231da6 to your computer and use it in GitHub Desktop.
Save gertvv/39475c39f6419b5a31786e4f64231da6 to your computer and use it in GitHub Desktop.
Gleam transcribed to WebAssembly

Gleam transcribed to WebAssembly

For some reason, I have a concurrent fascination with Gleam and WASM/WASM-GC/WASI at the moment.

I'd like to be able to compile the one to the other, but hacking the Gleam compiler feels daunting.

As a starting point, I built very simple implementations of List and Int, and used these to transcribe some Gleam code to WebAssembly.

fn add(a: Int, b: Int) -> Int {
a + b
}
fn fold(list: List(a), initial: acc, fun: fn(acc, a) -> acc) -> acc {
case list {
[] -> initial
[x, ..rest] -> fold(rest, fun(initial, x), fun)
}
}
pub fn sum(numbers: List(Int)) -> Int {
fold(numbers, 0, add)
}
(module
;; Compile using binaryen:
;; wasm-as example.wat --enable-gc --enable-reference-types --enable-tail-call
;; Possibly this can be used for codegen in the gleam compiler:
;; https://docs.rs/wasm-encoder/latest/wasm_encoder/
;; Import gleam built-ins
(type $gleam/List
(struct
(field $value (ref any))
(field $tail (ref null $gleam/List))
)
)
(type $gleam/Int (struct i64))
(import "gleam" "list_head" (func $gleam/list_head (param $list (ref $gleam/List)) (result (ref any))))
(import "gleam" "list_tail" (func $gleam/list_tail (param $list (ref $gleam/List)) (result (ref null $gleam/List))))
(import "gleam" "list_is_empty" (func $gleam/list_is_empty (param $list (ref null $gleam/List)) (result i32)))
(import "gleam" "int_box" (func $gleam/int_box (param $value i64) (result (ref $gleam/Int))))
(import "gleam" "int_unbox" (func $gleam/int_unbox (param $boxed (ref $gleam/Int)) (result i64)))
;; -- "compiled" gleam --
;; typed implementation of add
(func $add (param $a (ref $gleam/Int)) (param $b (ref $gleam/Int)) (result (ref $gleam/Int))
(call $gleam/int_box
(i64.add
(call $gleam/int_unbox (local.get $a))
(call $gleam/int_unbox (local.get $b))
)
)
)
;; wrapper for "generic" calls
(func $add_anyref (param $a (ref any)) (param $b (ref any)) (result (ref any))
(call $add
(ref.cast (ref $gleam/Int) (local.get $a))
(ref.cast (ref $gleam/Int) (local.get $b))
)
)
(type $folder (func (param (ref any)) (param (ref any)) (result (ref any))))
(func $fold
(param $l (ref null $gleam/List))
(param $i (ref any))
(param $f (ref $folder))
(result (ref any))
(local $nel (ref $gleam/List))
(if (call $gleam/list_is_empty (local.get $l)) (then (return (local.get $i))))
(local.set $nel (ref.as_non_null (local.get $l)))
(return_call $fold
(call $gleam/list_tail (local.get $nel))
(call_ref $folder
(local.get $i)
(call $gleam/list_head (local.get $nel))
(local.get $f)
)
(local.get $f)
)
)
(func $sum
(param $l (ref $gleam/List))
(result (ref $gleam/Int))
(ref.cast (ref $gleam/Int)
(call $fold (local.get $l) (struct.new $gleam/Int (i64.const 0)) (ref.func $add_anyref))
)
)
(export "sum" (func $sum))
)
(module
;; -- List implementation --
(type $list
(struct
(field $value (ref any))
(field $tail (ref null $list))
)
)
(func $list_empty (result (ref null $list))
(ref.null $list)
)
(func $list_non_empty (param $value (ref any)) (param $tail (ref null $list)) (result (ref $list))
(struct.new $list
(local.get $value)
(local.get $tail)
)
)
(func $list_head (param $list (ref $list)) (result (ref any))
(struct.get $list $value (local.get $list))
)
(func $list_tail (param $list (ref $list)) (result (ref null $list))
(struct.get $list $tail (local.get $list))
)
(func $list_is_empty (param $list (ref null $list)) (result i32)
(ref.is_null (local.get $list))
)
;; -- Int implementation: boxed i64 --
(type $int (struct i64))
(func $int_unbox (param $boxed (ref $int)) (result i64)
(struct.get $int 0 (local.get $boxed))
)
(func $int_box (param $value i64) (result (ref $int))
(struct.new $int (local.get $value))
)
(export "list_empty" (func $list_empty))
(export "list_non_empty" (func $list_non_empty))
(export "list_head" (func $list_head))
(export "list_tail" (func $list_tail))
(export "list_is_empty" (func $list_is_empty))
(export "int_box" (func $int_box))
(export "int_unbox" (func $int_unbox))
)
import fs from 'node:fs';
const load = (filename, imports) => {
const wasmBuffer = fs.readFileSync(filename);
return WebAssembly.instantiate(wasmBuffer, imports);
}
load('gleam.wasm').then(gleamModule => {
const gleam = gleamModule.instance.exports;
const { list_empty, list_non_empty, int_box, int_unbox } = gleamModule.instance.exports;
load('example.wasm', { gleam }).then(exampleModule => {
const { sum } = exampleModule.instance.exports;
const list = list_non_empty(
int_box(BigInt(1)),
list_non_empty(
int_box(BigInt(41)),
list_empty()));
console.log(int_unbox(sum(list)));
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment