Skip to content

Releases: leostera/lam

Message Passing support and more!

27 Nov 10:51
bdfd82c
Compare
Choose a tag to compare

Second public release of this little actor machine and we have gone quite a bit from v0.0.6.

This is a ton of fun, and its starting to look like something that could be used for real. Binary outputs are small-ish, and they boot crazy fast too, so I can't wait to see what a properly optimized version of the emulator and bytecode encoding could do for it!

Changelog

lam command line interface 🧑‍💻

The cli has seen some improvements, in particular the build command, while still available, has been split into compile and link. The main reason for this is that I'd like to start playing around with lam integration into the Crane build system and having a split compilation and linking stages means distributing these tasks can be done more easily.

In short:

  • compile can be used to translate a number of *.beam files into *.lam files that include the LAM bytecode.
  • link can then be used to link LAM bytecode into an executable for a target, like Native, WASM, or Web.

You normally use it like this:

# this example assumes there's a my_module.erl that exports a `main/1` function 
$ erlc *.erl
$ lam compile *.beam
$ lam link *.lam --output project.exe --target native --entrypoint my_module
$ ./project.exe
...
$ lam link *.lam --output project.wasm --target wasm --entrypoint my_module
$ wasmtime run ./project.exe
...

But of course you can use the shorthand build too, that doesn't create intermediary outputs.

$ erlc *.erl
$ lam build *.beam -o project.exe -t native -e my_module
$ ./project.exe
...

Examples

I've included a few examples that I'm using to test out what this thing can do:

  • cat a GNU cat clone
  • grep a message-passing based "find exact match in files" tool that spins up one process per file
  • empty, just an empty binary to measure boot time
  • simple_send a small program that sends itself a message, prints it and exits
  • hello_joe, the mandatory hello world
  • fib, a fibonacci number calculator (it goes really high! try it with like 100,000

Most examples have a ./build.sh and a ./bench.sh script that you can use to build them and test them for speed. But also most scripts rely on a globally installed lam binary and the Rust toolchain (since ./build.sh just does debug builds).

Emulator 🚀

New instructions allow us to:

  • Spawn — spin up a process from a lambda and place its Pid on a register
  • Send { message, process } — send a message to a process (even ourselves!) and have it delivered before we continue executing
  • Receive messages with PeekMessage { register }, and if we like the message we can just move it into somewhere else and use RemoveMessage to remove it from the queue. This allows us to support Selective Receives.
  • Sleep to put a process to sleept
  • Test(IsFunctionWithArity { fun, arity }) &mdash to check if an argument is a function of arity parameters
  • ShiftLocals { amount } &mdash to shift local registers, dropping the first amount of them
  • SplitListTail and SplitListHead to get only the tail or only the head of a list
  • BadMatch to essentially terminate a process when we could not pass some Test(..)

New emulator behavior will:

  • Push new local registers onto the stack on Call instructions
  • Terminate a process on a BadMatch

Additionally we've introduced a ProcessRegistry to support the instructions above,

Runtime: Native 🏗️

The native runtime now includes a few more FFI calls, but I'm looking for ways that these could be either imported verbatim from the OTP sources, or reused across runtimes.

You can now call:

  • binary:list_to_bin/1 — to convert "erlang strings" into <<"erlang binary strings">>
  • binary:split/3 — to split binary strings by one or more separators
  • length/1 — to get the length of a list

And there's been some change in behaviors:

  • io:format/2 will now print out both the pattern and the arguments. It's still a little ugly but its better than no outputs!
  • file:read_file/1 will not remove new lines and you'll have to use binary:split/3 for that.

Roadmap 🗺️

Going forward I'm gonna see how hard it is to reuse the built-in functions that are now part of the runtime, and try to bring the WASM runtime to parity there. In particular I've had some trouble getting the file:read_file/1 to run on Wasmtime. If that goes well, I might as well start rewriting Crane in Erlang, and shipping it as a native binary.

And the ROADMAP doc should reflect a bigger picture of where this thing is right now.

Enjoy it and let me know what works, what breaks, and cool things you want to build with LAM! 🚀

/ Leandro

First pipelined release

22 Nov 16:57
6ad66b6
Compare
Choose a tag to compare

NOTE: this is very very much alpha software that is not ready to be used.

The lam binary included in the following tarballs should allow you to build some Erlang bytecode into native and wasm binaries.

It only supports the following calls:

  • erlang:spawn/1
  • file:read_file/1
  • io:format/2
  • binary:split/3

But it should run most of the custom Erlang you write, although it has some quirks with lambdas.

Try building and running some of the example projects. The best way to build the binaries is by calling: lam build *.beam --output my_output.exe --target native --entrypoint <module>.

So for example, in the examples/grep folder you can run: lam build *.beam -o grep.exe -t native -e grep to get a native binary called grep.exe that will run the grep:main/1 function when it starts.

Try --target wasm to get a WebAssembly output. I'd recommend you set a different output name as well, e.g, -o grep.wasm.

Enjoy and let me know how it goes in the issues 🙌🏼

/ Leandro