JIT compiler and runtime for a toy language, using Cranelift

Overview

Hello!

This is a simple demo that JIT-compiles a toy language, using Cranelift.

It uses the new JIT interface in development here. JIT takes care of managing a symbol table, allocating memory, and performing relocations, offering a relatively simple API.

This is inspired in part by Ulysse Carion's llvm-rust-getting-started and Jonathan Turner's rustyjit.

A quick introduction to Cranelift: Cranelift is a compiler backend. It's light-weight, supports no_std mode, doesn't use of floating-point itself, and it makes efficient use of memory.

And Cranelift is being architected to allow flexibility in how one uses it. Sometimes that flexibility can be a burden, which we've recently started to address in a new set of crates, cranelift-module, cranelift-jit, and cranelift-faerie, which put the pieces together in some easy-to-use configurations for working with multiple functions at once. cranelift-module is a common interface for working with multiple functions and data interfaces at once. This interface can sit on top of cranelift-jit, which writes code and data to memory where they can be executed and accessed. And, it can sit on top of cranelift-faerie, which writes code and data to native .o files which can be linked into native executables.

This post introduces Cranelift by walking through a JIT demo, using the cranelift-jit crate. Currently this demo works on Linux x86-64 platforms. It may also work on Mac x86-64 platforms, though I haven't specifically tested that yet. And Cranelift is being designed to support many other kinds of platforms in the future.

A walkthrough

First, let's take a quick look at the toy language in use. It's a very simple language, in which all variables have type isize. (Cranelift does have full support for other integer and floating-point types, so this is just to keep the toy language simple).

For a quick flavor, here's our first example in the toy language:

        fn foo(a, b) -> (c) {
            c = if a {
                if b {
                    30
                } else {
                    40
                }
            } else {
                50
            }
            c = c + 2
        }

The grammar for this toy language is defined here, and this demo uses the peg parser generator library to generate actual parser code for it.

The output of parsing is a custom AST type:

pub enum Expr {
    Literal(String),
    Identifier(String),
    Assign(String, Box<Expr>),
    Eq(Box<Expr>, Box<Expr>),
    Ne(Box<Expr>, Box<Expr>),
    Lt(Box<Expr>, Box<Expr>),
    Le(Box<Expr>, Box<Expr>),
    Gt(Box<Expr>, Box<Expr>),
    Ge(Box<Expr>, Box<Expr>),
    Add(Box<Expr>, Box<Expr>),
    Sub(Box<Expr>, Box<Expr>),
    Mul(Box<Expr>, Box<Expr>),
    Div(Box<Expr>, Box<Expr>),
    IfElse(Box<Expr>, Vec<Expr>, Vec<Expr>),
    WhileLoop(Box<Expr>, Vec<Expr>),
    Call(String, Vec<Expr>),
    GlobalDataAddr(String),
}

It's pretty minimal and straightforward. The IfElse can return a value, to show how that's done in Cranelift (see below).

The first thing we do is create an instance of our JIT:

let mut jit = jit::JIT::new();

The JIT class is defined here and contains several fields:

  • builder_context - Cranelift uses this to reuse dynamic allocations between compiling multiple functions.
  • ctx - This is the main Context object for compiling functions.
  • data_ctx - Similar to ctx, but for "compiling" data sections.
  • module - The Module which holds information about all functions and data objects defined in the current JIT.

Before we go any further, let's talk about the underlying model here. The Module class divides the world into two kinds of things: functions, and data objects. Both functions and data objects have names, and can be imported into a module, defined and only referenced locally, or defined and exported for use in outside code. Functions are immutable, while data objects can be declared either readonly or writable.

Both functions and data objects can contain references to other functions and data objects. Cranelift is designed to allow the low-level parts operate on each function and data object independently, so each function and data object maintains its own individual namespace of imported names. The Module struct takes care of maintaining a set of declarations for use across multiple functions and data objects.

These concepts are sufficiently general that they're applicable to JITing as well as native object files (more discussion below!), and Module provides an interface which abstracts over both.

Once we've initialized the JIT data structures, we then use our JIT to compile some functions.

The JIT's compile function takes a string containing a function in the toy language. It parses the string into an AST, and then translates the AST into Cranelift IR.

Our toy language only supports one type, so we start by declaring that type for convenience.

We then start translating the function by adding the function parameters and return types to the Cranelift function signature.

Then we create a FunctionBuilder which is a utility for building up the contents of a Cranelift IR function. As we'll see below, FunctionBuilder includes functionality for constructing SSA form automatically so that users don't have to worry about it.

Next, we start an initial basic block (block), which is the entry block of the function, and the place where we'll insert some code.

  • A basic block is a sequence of IR instructions which have a single entry point, and no branches until the very end, so execution always starts at the top and proceeds straight through to the end.

Cranelift's basic blocks can have parameters. These take the place of PHI functions in other IRs.

Here's an example of a block, showing branches (brif and jump) that are at the end of the block, and demonstrating some block parameters.

block0(v0: i32, v1: i32, v2: i32, v507: i64):
    v508 = iconst.i32 0
    v509 = iconst.i64 0
    v404 = ifcmp_imm v2, 0
    v10 = iadd_imm v2, -7
    v405 = ifcmp_imm v2, 7
    brif ugt v405, block29(v10)
    jump block29(v508)

The FunctionBuilder library will take care of inserting block parameters automatically, so frontends that don't need to use them directly generally don't need to worry about them, though one place they do come up is that incoming arguments to a function are represented as block parameters to the entry block. We must tell Cranelift to add the parameters, using append_block_params_for_function_params like so.

The FunctionBuilder keeps track of a "current" block that new instructions are to be inserted into; we next inform it of our new block, using switch_to_block, so that we can start inserting instructions into it.

The one major concept about blocks is that the FunctionBuilder wants to know when all branches which could branch to a block have been seen, at which point it can seal the block, which allows it to perform SSA construction. All blocks must be sealed by the end of the function. We seal a block with seal_block.

Next, our toy language doesn't have explicit variable declarations, so we walk the AST to discover all the variables, so that we can declare then to the FunctionBuilder. These variables need not be in SSA form; the FunctionBuilder will take care of constructing SSA form internally.

For convenience when walking the function body, the demo here uses a FunctionTranslator object, which holds the FunctionBuilder, the current Module, as well as the symbol table for looking up variables. Now we can start walking the function body.

AST translation utilizes the instruction-building features of FunctionBuilder. Let's start with a simple example translating integer literals:

    Expr::Literal(literal) => {
        let imm: i32 = literal.parse().unwrap();
        self.builder.ins().iconst(self.int, i64::from(imm))
    }

The first part is just extracting the integer value from the AST. The next line is the builder line:

  • The .ins() returns an "insertion object", which allows inserting an instruction at the end of the currently active block.
  • iconst is the name of the builder routine for creating integer constants in Cranelift. Every instruction in the IR can be created directly through such a function call.

Translation of Add nodes and other arithmetic operations is similarly straightforward.

Translation of variable references is mostly handled by FunctionBuilder's use_var function:

    Expr::Identifier(name) => {
        // `use_var` is used to read the value of a variable.
        let variable = self.variables.get(&name).expect("variable not defined");
        self.builder.use_var(*variable)
    }

use_var is for reading the value of a (non-SSA) variable. (Internally, FunctionBuilder constructs SSA form to satisfy all uses).

Its companion is def_var, which is used to write the value of a (non-SSA) variable, which we use to implement assignment:

    fn translate_assign(&mut self, name: String, expr: Expr) -> Value {
        // `def_var` is used to write the value of a variable. Note that
        // variables can have multiple definitions. Cranelift will
        // convert them into SSA form for itself automatically.
        let new_value = self.translate_expr(*expr);
        let variable = self.variables.get(&name).unwrap();
        self.builder.def_var(*variable, new_value);
        new_value
    }

Next, let's dive into if-else expressions. In order to demonstrate explicit SSA construction, this demo gives if-else expressions return values. The way this looks in Cranelift is that the true and false arms of the if-else both have branches to a common merge point, and they each pass their "return value" as a block parameter to the merge point.

Note that we seal the blocks we create once we know we'll have no more predecessors, which is something that a typical AST makes it easy to know.

Putting it all together, here's the Cranelift IR for the function named foo in the demo program, which contains multiple ifs:

function u0:0(i64, i64) -> i64 system_v {
block0(v0: i64, v1: i64):
    v2 = iconst.i64 0
    brz v0, block2
    jump block1

block1:
    v4 = iconst.i64 0
    brz.i64 v1, block5
    jump block4

block4:
    v6 = iconst.i64 0
    v7 = iconst.i64 30
    jump block6(v7)

block5:
    v8 = iconst.i64 0
    v9 = iconst.i64 40
    jump block6(v9)

block6(v5: i64):
    jump block3(v5)

block2:
    v10 = iconst.i64 0
    v11 = iconst.i64 50
    jump block3(v11)

block3(v3: i64):
    v12 = iconst.i64 2
    v13 = iadd v3, v12
    return v13
}

The while loop translation is also straightforward.

Here's the Cranelift IR for the function named iterative_fib in the demo program, which contains a while loop:

function u0:0(i64) -> i64 system_v {
block0(v0: i64):
    v1 = iconst.i64 0
    v2 = iconst.i64 0
    v3 = icmp eq v0, v2
    v4 = bint.i64 v3
    brz v4, block2
    jump block1

block1:
    v6 = iconst.i64 0
    v7 = iconst.i64 0
    jump block3(v7, v7)

block2:
    v8 = iconst.i64 0
    v9 = iconst.i64 1
    v10 = isub.i64 v0, v9
    v11 = iconst.i64 0
    v12 = iconst.i64 1
    jump block4(v10, v12, v11)

block4(v13: i64, v17: i64, v18: i64):
    v14 = iconst.i64 0
    v15 = icmp ne v13, v14
    v16 = bint.i64 v15
    brz v16, block6
    jump block5

block5:
    v19 = iadd.i64 v17, v18
    v20 = iconst.i64 1
    v21 = isub.i64 v13, v20
    jump block4(v21, v19, v17)

block6:
    v22 = iconst.i64 0
    jump block3(v22, v17)

block3(v5: i64, v23: i64):
    return v23
}

For calls, the basic steps are to determine the call signature, declare the function to be called, put the values to be passed in an array, and then call the call function.

The translation for global data symbols, is similar; first declare the symbol to the module, then declare it to the current function, and then use the symbol_value instruction to produce the value.

And with that, we can return to our main toy.rs file and run some more examples. There are examples of recursive and iterative fibonacci, which demonstrate more use of calls and control flow.

And there's a hello world example which demonstrates several other features.

This program needs to allocate some data to hold the string data. Inside jit.rs, create_data we initialize a DataContext with the contents of the hello string, and also declare a data object. Then we use the DataContext object to define the object. At that point, we're done with the DataContext object and can clear it. We then call finalize_data to perform linking (although our simple hello string doesn't make any references so there isn't anything to do) and to obtain the final runtime address of the data, which we then convert back into a Rust slice for convenience.

And to show off a handy feature of the jit backend, it can look up symbols with libc::dlsym, so you can call libc functions such as puts (being careful to NUL-terminate your strings!). Unfortunately, printf requires varargs, which Cranelift does not yet support.

And with all that, we can say "hello world!".

Native object files

Because of the Module abstraction, this demo can be adapted to write out an ELF .o file rather than JITing the code to memory with only minor changes, and I've done so in a branch here. This writes a test.o file, which on an x86-64 ELF platform you can link with cc test.o and it produces an executable that calls the generated functions, including printing "hello world!".

Another branch here shows how to write Mach-O object files.

Object files are written using the faerie library.

Have fun!

Cranelift is still evolving, so if there are things here which are confusing or awkward, please let us know, via github issues or just stop by the gitter chat. Very few things in Cranelift's design are set in stone at this time, and we're really interested to hear from people about what makes sense what doesn't.

Comments
  • EXC_BAD_ACCESS on OSX

    EXC_BAD_ACCESS on OSX

    Tys-MacBook-Pro-2:simplejit-demo tyoverby$ lldb target/debug/toy
    (lldb) target create "target/debug/toy"
    Current executable set to 'target/debug/toy' (x86_64).
    (lldb) run
    Process 82396 launched: '/Users/tyoverby/clean/simplejit-demo/target/debug/toy' (x86_64)
    the answer is: 42
    recursive_fib(10) = 55
    iterative_fib(10) = 55
    Process 82396 stopped
    * thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x102800408)
        frame #0: 0x00007fff684413b8 libsystem_malloc.dylib`small_free_list_add_ptr + 314
    libsystem_malloc.dylib`small_free_list_add_ptr:
    ->  0x7fff684413b8 <+314>: movb   %r10b, 0x8(%rdx)
        0x7fff684413bc <+318>: movq   $0x0, (%rdx)
        0x7fff684413c3 <+325>: movq   %rax, %r10
        0x7fff684413c6 <+328>: shrq   $0x8, %r10
    Target 0: (toy) stopped.
    (lldb) bt
    * thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x102800408)
      * frame #0: 0x00007fff684413b8 libsystem_malloc.dylib`small_free_list_add_ptr + 314
        frame #1: 0x00007fff6844d4f3 libsystem_malloc.dylib`free_small + 921
        frame #2: 0x00007fff68442d87 libsystem_malloc.dylib`szone_memalign + 1126
        frame #3: 0x00007fff684428d9 libsystem_malloc.dylib`malloc_zone_memalign + 154
        frame #4: 0x00007fff68444386 libsystem_malloc.dylib`posix_memalign + 25
        frame #5: 0x000000010005dae5 toy`cretonne_simplejit::memory::PtrLen::with_size::hfa3dd636d07d161e(size=12) at memory.rs:25
        frame #6: 0x000000010005ddbb toy`cretonne_simplejit::memory::Memory::allocate::h918029ba354eb4c1(self=0x00007ffeefbff3f8, size=12) at memory.rs:75
        frame #7: 0x0000000100059f3e toy`_$LT$cretonne_simplejit..backend..SimpleJITBackend$u20$as$u20$cretonne_module..backend..Backend$GT$::define_data::h573815a0e058ec5a(self=0x00007ffeefbff378, _name=(data_ptr = "hello_string\x04", length = 12), data=0x00007ffeefbff2a0, _namespace=0x00007ffeefbfe5b0) at backend.rs:159
        frame #8: 0x000000010003f81c toy`_$LT$cretonne_module..module..Module$LT$B$GT$$GT$::define_data::h2a9842b185d866dd(self=0x00007ffeefbff320, data=(__0= 0), data_ctx=0x00007ffeefbff2a0) at module.rs:517
        frame #9: 0x000000010004679a toy`toy::jit::JIT::create_data::h8851a848fef777bc(self=0x00007ffeefbfe9a8, name=(data_ptr = "hello_stringhello world", length = 12), contents=<unavailable>) at jit.rs:125
        frame #10: 0x00000001000422d8 toy`toy::main::hd9bd26c7b8da2354 at toy.rs:122
        frame #11: 0x000000010003a8c2 toy`std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hfac8eb5e2ec564ba at rt.rs:74
        frame #12: 0x00000001002a9db8 toy`std::panicking::try::do_call::h6688c75b958424d8 [inlined] std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::h577272da3909533e at rt.rs:59 [opt]
        frame #13: 0x00000001002a9dac toy`std::panicking::try::do_call::h6688c75b958424d8 at panicking.rs:305 [opt]
        frame #14: 0x00000001002bd0bf toy`__rust_maybe_catch_panic at lib.rs:101 [opt]
        frame #15: 0x00000001002a6878 toy`std::rt::lang_start_internal::h1d7922f9dfd201cc [inlined] std::panicking::try::h6dbcd1a44c5bb721 at panicking.rs:284 [opt]
        frame #16: 0x00000001002a6845 toy`std::rt::lang_start_internal::h1d7922f9dfd201cc [inlined] std::panic::catch_unwind::hdd63a658020b6001 at panic.rs:361 [opt]
        frame #17: 0x00000001002a6845 toy`std::rt::lang_start_internal::h1d7922f9dfd201cc at rt.rs:58 [opt]
        frame #18: 0x000000010003a8a2 toy`std::rt::lang_start::heba532bdd26b7181(main=(toy`toy::main::hd9bd26c7b8da2354 at toy.rs:10), argc=1, argv=0x00007ffeefbff818) at rt.rs:74
        frame #19: 0x0000000100042395 toy`main + 37
        frame #20: 0x00007fff68297115 libdyld.dylib`start + 1
        frame #21: 0x00007fff68297115 libdyld.dylib`start + 1
    (lldb)
    
    opened by TyOverby 10
  • How can I get type of Value?

    How can I get type of Value?

    This demo is great, so I'm enjoying creating some software with this, but currently I'm struggling to get type of "Value" of cranelift. How can I check if string is inside or if number is inside Value? Or can we use this immediate instead of Value?

    opened by lechatthecat 6
  • Jit demo does

    Jit demo does "PermissionDenied"

    my OS: Fedora 33, Rust: 1.49.0

    so when I do cargo run in "cranelift-jit-demo-main" it compiles but then:

    thread 'main' panicked at 'unable to make memory readable+executable: SystemCall(Os { code: 13, kind: PermissionDenied, message: "Permission denied" })', /home/adsick/.cargo/registry/src/github.com-1ecc6299db9ec823/cranelift-simplejit-0.68.0/src/memory.rs:197:30
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    

    why is it so and how do I fix it (or rather it should be fixed somewhere in the actual project, I dunno)

    opened by adsick 4
  • Update for basic blocks

    Update for basic blocks

    This PR resolves #39 and update cranelift version to 0.52.0. But I have two concerns related to updating for basic blocks.

    1. At what timing should we update document related to (extended) basic blocks? Cranelift now seems on inconsistent state about basic blocks. For example, cranlift-simplejit doesn't use basic blocks feature although other modules already using the feature as default. And I think it's better to wait until https://github.com/bytecodealliance/cranelift/issues/1303 is resolved to prevent rewriting document over and over.
    2. Should we indicate some caution about current state of cranelift untill its transition is fully completed?
    opened by Y-Nak 4
  • Is there a way to view the JIT compiled code?

    Is there a way to view the JIT compiled code?

    I know you can print the cranelift IR with ctx.func (#55), but is there a way to print the generated x86 instructions before or after the module.finalize_definitions() line? I want to see the final code that gets generated so I can debug issues with it and perhaps optimize the IR I'm generating.

    Thanks!

    opened by sunjay 3
  • Add `string` support and update for the lastest cranelift.

    Add `string` support and update for the lastest cranelift.

    1. Add string support, now you can use "Content" in the toy language.
    2. Add symbol log, you can use it to print content to the console.
    3. Replace fn -> extern fn.
    4. Update the examples.
    5. Format the code.
    6. Update for the lastesr cranelift (0.84).
    7. Add test.yml
    opened by EnabledFish 3
  • simplejit to jit

    simplejit to jit

    Updating the demo to use the updated cranelift-jit module since stuff has been renamed from SimpleJIT to JIT.

    We probably also want to change this package name from simplejit-demo to jit-demo, but someone with repo permissions will likely have to do that.

    This PR (plus changing the repo and package name from simplejit-demo to jit-demo should close https://github.com/bytecodealliance/simplejit-demo/issues/50

    opened by ghost 3
  • Some improvements + update to cranelift 0.68

    Some improvements + update to cranelift 0.68

    Updated to cranelift version 0.68.

    Also did some simplifications and removed some duplicate code that could make it harder to inspect the actual logic. I hope these simplifications are to the interest of the crate's authors.

    It would be nice if there were tests to check if the updates are actually correct.

    Running cargo run --bin toy yields the expected results after these changes:

    > cargo run --bin toy
    the answer is: 42
    recursive_fib(10) = 55
    iterative_fib(10) = 55
    hello world!
    

    toy.rs now clearly separates code from data and wraps the unsafe transmutes in functions: https://github.com/Robbepop/simplejit-demo/blob/robin-improvements/src/toy.rs

    The PR has removed plenty of duplicate code from jit.rs in order to emphasize the important parts about the codegen.

    opened by Robbepop 2
  • Update to Cranelift 0.33.0.

    Update to Cranelift 0.33.0.

    As far as I could tell, simplejit-demo doesn't need alignment on either of the declare_data calls - at least there was presumably no alignment constraints when using Cranelift 0.25.0.

    All my edits were single line edits, so none of the line numbers in README.md should need updating.

    opened by davidlattimore 2
  • How do I call a function with arbitrary arguments?

    How do I call a function with arbitrary arguments?

    At first, I appreciate your great effort, thank you for this nice demo of cranelift-jit. I love this demo a lot.

    My question is, it seems that functions are called with input of tuple. For example, (1, 0) is passed as arguments in the call below. https://github.com/bytecodealliance/cranelift-jit-demo/blob/4f70b58c3f797943b0a6ad06f392d66ed45a8cdc/src/bin/toy.rs#L21

    But tuple's number of elements cannot be defined dynamically. Let's say, a user defines a function with 5 arguments, then Rust program must supply a tuple of 5 elements as an input (arguments) to run the function.

    But Rust program cannot know how many arguments a user defines for a function before compilation, so I think it is impossible to define them as tuple. So how can I pass such arguments to a function?

    opened by lechatthecat 1
  • Cargo.toml update: change repo link to bytecodealliance

    Cargo.toml update: change repo link to bytecodealliance

    This PR changes the repository link in the Cargo.toml file from https://github.com/CraneStation/simplejit-demo to https://github.com/bytecodealliance/simplejit-demo

    opened by ghost 1
  • Add example that uses `load` and `store`

    Add example that uses `load` and `store`

    I've been trying to create a fixed length of memory and load from it. I keep getting

    terminated by signal SIGSEGV (Address boundary error)
    

    If you could add an example showing such a usecase it would be very helpful.

    opened by m-haisham 0
  • Not compiled on fedora with 'selinux-fix' feature

    Not compiled on fedora with 'selinux-fix' feature

    Error when compile on linux with cranelift-jit = { version = "0.79", features = ['selinux-fix'] } With versions 0.77, 0.78 also not compiled.

    error[E0308]: mismatched types --> /home/ali/.cargo/registry/src/github.com-1ecc6299db9ec823/cranelift-jit-0.79.0/src/memory.rs:37:9 | 35 | fn with_size(size: usize) -> io::Result { | ---------------- expected std::result::Result<PtrLen, std::io::Error> because of return type 36 | let alloc_size = region::page::ceil(size); 37 | / MmapMut::map_anon(alloc_size).map(|mut mmap| { 38 | | // The order here is important; we assign the pointer first to get 39 | | // around compile time borrow errors. 40 | | Ok(Self { ... | 44 | | }) 45 | | }) | |__________^ expected struct PtrLen, found enum std::result::Result | = note: expected enum std::result::Result<PtrLen, _> found enum std::result::Result<std::result::Result<PtrLen, _>, _>

    opened by alexantoshuk 0
  • gh-59 Add example of calling rust code from JIT

    gh-59 Add example of calling rust code from JIT

    This adds an example of calling a simple print function from the JIT. The print function is written in Rust and this demonstrates how to call functions declared in the host program from the JIT.

    opened by 0xekez 0
  • using cranelift-simplejit inside a

    using cranelift-simplejit inside a "rust compiled to wasm" application

    Hi,

    I am writing an in browser application via Rust compiled to wasm. In this in-browser application, I would like to be able to dynamically generate code and call it. Is it possible to do this via cranelift-simplejit ? [I am having problem building it for the wasm32-unknown-unknown target.]

    Thanks!

    opened by zeroexcuses 1
Owner
Bytecode Alliance
Bytecode Alliance
An example of Brainf*** JIT-compiler with only the standard library.

jit-compiler An example of Brainf*** JIT-compiler with only the standard library. Prerequisite Rust(1.56.0-nightly or later, but it must work kind of

Akihito KIRISAKI 18 Jan 22, 2022
The 峨眉 (EMei) JIT/AOT backend framework.

emei The 峨眉 (EMei) JIT/AOT backend framework. Support Instructions x86_64 mov mov mov_zero_extend_bit8/16 mov_sign_extend_bit8/16/32 mov_rev movs(is m

Lyzh 14 Apr 11, 2022
Compiler & Interpreter for the (rather new and very experimental) Y programming language.

Y Lang Why would anyone build a new (and rather experimental) language with no real world use case. Design Y (pronounced as why) is based on the idea

Louis Meyer 8 Mar 5, 2023
Milho (corn in portuguese) is a toy dialect of Lisp written as a way to learn more about compilers

Milho (corn in portuguese) is a toy dialect of Lisp written as a way to learn more about compilers. There are implementations in rust and go

Celso Bonutti 27 May 4, 2022
Payments Engine is a simple toy payments engine

Payments Engine is a simple toy payments engine that reads a series of transactions from a CSV, updates client accounts, handles disputes and chargebacks, and then outputs the state of clients accounts as a CSV.

Bogdan Arabadzhi 0 Feb 3, 2022
Toy: Layout-Engine

Toy: Layout-Engine

Oanakiaja 5 Mar 29, 2022
A toy-level BLE peripheral stack

bleps - A toy-level BLE peripheral stack This is a BLE peripheral stack in Rust. (no-std / no-alloc) To use it you need an implementation of embedded-

Björn Quentin 4 Oct 17, 2022
A compiler for the esoteric language ℂ.

The ℂ Programming Language It's a language where the only types are "complex number" and "matrix of complex numbers". In particular, this means you ca

Eleanor McMurtry 24 Jul 15, 2022
A compiler for a language representing plonk circuits

Plang A language representing PLONK circuits. Compiler This repository contains a compiler for a language representing PLONK circuits. It allows circu

Dusk Network 1 Nov 6, 2021
Compiler from a lisp-like language to mlog

slimlog slimlog compiles a lisp-like language to mlog Architecture slimlog is divided into three distinct parts Parser Parses the source file Compiler

The Potato Chronicler 6 May 7, 2022
Use explicit container types with Scrypto! Leverage the Rust compiler's type checking to increase security and productivity when developing Radix blueprints.

Scrypto Static Types Use explicit container types with Scrypto! Leverage the Rust compiler's type checking to increase security and productivity when

null 7 Aug 5, 2022
Equilibrium substrate-based node implementation and runtime

Equilibrium & Genshiro Equilibrium is a DeFi 2.0 cross-chain money market protocol with high leverage. With Equilibrium users can: Lend: lend out asse

null 6 Apr 18, 2023
The official zeta compiler

Torq What makes Torq the goto language for creating CLI's? Smaller Executables Inbuilt argument and flag parser Your code will work anywhere in any OS

ZetaLang 2 Nov 24, 2021
The nightly_crimes!{} macro commits horrible crimes to allow you to enable nightly features on the stable compiler.

The nightly_crimes!{} macro commits horrible crimes to allow you to enable nightly features on the stable compiler.

Mara Bos 151 Dec 16, 2022
Solidity-Compiler Version Manager

Solidity Compiler Version Manager

Rohit Narurkar 114 Jan 2, 2023
A fusion of OTP lib/dialyzer + lib/compiler for regular Erlang with type inference

Typed ERLC The Problem I have a dream, that one day there will be an Erlang compiler, which will generate high quality type-correct code from deduced

Dmytro Lytovchenko 35 Sep 5, 2022
An optimising Brainf*ck to x86-64 assembly compiler written in Rust

brainfrsk 2 An optimising Brainf*ck to x86-64 assembly compiler. This compiler can produce optimised binaries for macOS (Sytem V calling convention) b

Adam Soutar 1 Feb 20, 2022
A transpiler/compiler for CrabRave, a version of BrainFuck with sea-life emojis.

CrabRave Programming Language CrabRave is a fun and unique programming language based on Brainfuck, which utilizes crab and sea-life emojis as its syn

null 23 May 3, 2023
Rholang runtime in rust

Rholang Runtime A rholang runtime written in Rust.

Jerry.Wang 17 Sep 23, 2022