{"id":13632751,"url":"https://github.com/bytecodealliance/cranelift-jit-demo","last_synced_at":"2025-07-12T01:13:13.289Z","repository":{"id":38036806,"uuid":"128800533","full_name":"bytecodealliance/cranelift-jit-demo","owner":"bytecodealliance","description":"JIT compiler and runtime for a toy language, using Cranelift","archived":false,"fork":false,"pushed_at":"2025-04-15T19:59:39.000Z","size":110,"stargazers_count":693,"open_issues_count":9,"forks_count":63,"subscribers_count":24,"default_branch":"main","last_synced_at":"2025-07-12T00:44:55.515Z","etag":null,"topics":["cranelift","demo","jit","toy-language"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bytecodealliance.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-04-09T16:20:16.000Z","updated_at":"2025-07-09T16:55:55.000Z","dependencies_parsed_at":"2024-01-14T07:13:46.596Z","dependency_job_id":"75269d71-3613-4bb6-bc4d-f2a9388c012a","html_url":"https://github.com/bytecodealliance/cranelift-jit-demo","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bytecodealliance/cranelift-jit-demo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bytecodealliance%2Fcranelift-jit-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bytecodealliance%2Fcranelift-jit-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bytecodealliance%2Fcranelift-jit-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bytecodealliance%2Fcranelift-jit-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bytecodealliance","download_url":"https://codeload.github.com/bytecodealliance/cranelift-jit-demo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bytecodealliance%2Fcranelift-jit-demo/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264922908,"owners_count":23683705,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cranelift","demo","jit","toy-language"],"created_at":"2024-08-01T22:03:14.172Z","updated_at":"2025-07-12T01:13:13.247Z","avatar_url":"https://github.com/bytecodealliance.png","language":"Rust","funding_links":[],"categories":["Rust"],"sub_categories":[],"readme":"Hello!\n\nThis is a simple demo that JIT-compiles a toy language, using Cranelift.\n\nIt uses the new JIT interface in development\n[here](https://github.com/bytecodealliance/wasmtime/tree/main/cranelift/jit). JIT takes care\nof managing a symbol table, allocating memory, and performing relocations, offering\na relatively simple API.\n\nThis is inspired in part by Ulysse Carion's\n[llvm-rust-getting-started](https://github.com/ucarion/llvm-rust-getting-started)\nand JT's [rustyjit](https://github.com/jntrnr/rustyjit).\n\nA quick introduction to Cranelift: Cranelift is a compiler backend. It's\nlight-weight, supports `no_std` mode, doesn't use floating-point itself,\nand it makes efficient use of memory.\n\nAnd Cranelift is being architected to allow flexibility in how one uses it.\nSometimes that flexibility can be a burden, which we've recently started to\naddress in a new set of crates, `cranelift-module`, `cranelift-jit`, and\n`cranelift-faerie`, which put the pieces together in some easy-to-use\nconfigurations for working with multiple functions at once. `cranelift-module`\nis a common interface for working with multiple functions and data interfaces\nat once. This interface can sit on top of `cranelift-jit`, which writes\ncode and data to memory where they can be executed and accessed. And, it can\nsit on top of `cranelift-faerie`, which writes code and data to native .o files\nwhich can be linked into native executables.\n\nThis post introduces Cranelift by walking through a JIT demo, using\nthe [`cranelift-jit`](https://crates.io/crates/cranelift-jit) crate.\nCurrently this demo works on Linux x86-64 platforms. It may also work on Mac\nx86-64 platforms, though I haven't specifically tested that yet. And Cranelift\nis being designed to support many other kinds of platforms in the future.\n\n### A walkthrough\n\nFirst, let's take a quick look at the toy language in use. It's a very simple\nlanguage, in which all variables have type `isize`. (Cranelift does have full\nsupport for other integer and floating-point types, so this is just to keep the\ntoy language simple).\n\nFor a quick flavor, here's our\n[first example](./src/bin/toy.rs#L63)\nin the toy language:\n\n```\n        fn foo(a, b) -\u003e (c) {\n            c = if a {\n                if b {\n                    30\n                } else {\n                    40\n                }\n            } else {\n                50\n            }\n            c = c + 2\n        }\n```\n\nThe grammar for this toy language is defined [here](./src/frontend.rs#L23), and\nthis demo uses the [peg](https://crates.io/crates/peg) parser generator library\nto generate actual parser code for it.\n\nThe output of parsing is a [custom AST type](./src/frontend.rs#L1):\n\n```rust\npub enum Expr {\n    Literal(String),\n    Identifier(String),\n    Assign(String, Box\u003cExpr\u003e),\n    Eq(Box\u003cExpr\u003e, Box\u003cExpr\u003e),\n    Ne(Box\u003cExpr\u003e, Box\u003cExpr\u003e),\n    Lt(Box\u003cExpr\u003e, Box\u003cExpr\u003e),\n    Le(Box\u003cExpr\u003e, Box\u003cExpr\u003e),\n    Gt(Box\u003cExpr\u003e, Box\u003cExpr\u003e),\n    Ge(Box\u003cExpr\u003e, Box\u003cExpr\u003e),\n    Add(Box\u003cExpr\u003e, Box\u003cExpr\u003e),\n    Sub(Box\u003cExpr\u003e, Box\u003cExpr\u003e),\n    Mul(Box\u003cExpr\u003e, Box\u003cExpr\u003e),\n    Div(Box\u003cExpr\u003e, Box\u003cExpr\u003e),\n    IfElse(Box\u003cExpr\u003e, Vec\u003cExpr\u003e, Vec\u003cExpr\u003e),\n    WhileLoop(Box\u003cExpr\u003e, Vec\u003cExpr\u003e),\n    Call(String, Vec\u003cExpr\u003e),\n    GlobalDataAddr(String),\n}\n```\n\nIt's pretty minimal and straightforward. The `IfElse` can return a value, to\nshow how that's done in Cranelift (see below).\n\nThe [first thing we do](./src/bin/toy.rs#L6) is create an instance of our `JIT`:\n\n```rust\nlet mut jit = jit::JIT::new();\n```\n\nThe `JIT` class is defined [here](./src/jit.rs#L9) and contains several fields:\n\n - `builder_context` - Cranelift uses this to reuse dynamic allocations between\n   compiling multiple functions.\n - `ctx` - This is the main `Context` object for compiling functions.\n - `data_description` - Similar to `ctx`, but for \"compiling\" data sections.\n - `module` - The `Module` which holds information about all functions and data\n   objects defined in the current `JIT`.\n\nBefore we go any further, let's talk about the underlying model here. The\n`Module` class divides the world into two kinds of things: functions, and data\nobjects. Both functions and data objects have *names*, and can be imported into\na module, defined and only referenced locally, or defined and exported for use\nin outside code. Functions are immutable, while data objects can be declared\neither readonly or writable.\n\nBoth functions and data objects can contain references to other functions and\ndata objects. Cranelift is designed to allow the low-level parts operate on each\nfunction and data object independently, so each function and data object maintains\nits own individual namespace of imported names. The\n[`Module`](https://docs.rs/cranelift-module/0.6.8/cranelift_module/trait.Module.html)\nstruct takes care of maintaining a set of declarations for use across multiple\nfunctions and data objects.\n\nThese concepts are sufficiently general that they're applicable to JITing as\nwell as native object files (more discussion below!), and `Module` provides an\ninterface which abstracts over both.\n\nOnce we've [initialized the JIT data structures](./src/jit.rs#L28), we then use\nour `JIT` to [compile](./src/jit.rs#L52) some functions.\n\nThe `JIT`'s `compile` function takes a string containing a function in the toy\nlanguage. It [parses](./src/jit.rs#L55) the string into an AST, and then\n[translates](./src/jit.rs#L58) the AST into Cranelift IR.\n\nOur toy language only supports one type, so we start by [declaring that\ntype](./src/jit.rs#L123) for convenience.\n\nWe then start translating the function by adding [the function\nparameters](./src/jit.rs#L125) and [return types](./src/jit.rs#L131) to the\nCranelift function signature.\n\nThen we [create](./src/jit.rs#L134) a\n[FunctionBuilder](https://docs.rs/cranelift-frontend/latest/cranelift_frontend/struct.FunctionBuilder.html)\nwhich is a utility for building up the contents of a Cranelift IR function. As\nwe'll see below, `FunctionBuilder` includes functionality for constructing SSA\nform automatically so that users don't have to worry about it.\n\nNext, we [start](./src/jit.rs#L137) an initial basic block (block), which is the\nentry block of the function, and the place where we'll insert some code.\n\n - A basic block is a sequence of IR instructions which have a single entry\n   point, and no branches until the very end, so execution always starts at the\n   top and proceeds straight through to the end.\n\nCranelift's basic blocks can have parameters. These take the place of PHI\nfunctions in other IRs.\n\nHere's an example of a block, showing branches (`brif` and `jump`) that are at\nthe end of the block, and demonstrating some block parameters.\n\n```\nblock0(v0: i32, v1: i32, v2: i32, v507: i64):\n    v508 = iconst.i32 0\n    v509 = iconst.i64 0\n    v404 = ifcmp_imm v2, 0\n    v10 = iadd_imm v2, -7\n    v405 = ifcmp_imm v2, 7\n    brif ugt v405, block29(v10)\n    jump block29(v508)\n```\n\nThe `FunctionBuilder` library will take care of inserting block parameters\nautomatically, so frontends that don't need to use them directly generally don't\nneed to worry about them, though one place they do come up is that incoming\narguments to a function are represented as block parameters to the entry\nblock. We must tell Cranelift to add the parameters, using\n[`append_block_params_for_function_params`](https://docs.rs/cranelift-frontend/latest/cranelift_frontend/struct.FunctionBuilder.html#method.append_block_params_for_function_params)\nlike [so](./src/jit.rs#L143).\n\nThe `FunctionBuilder` keeps track of a \"current\" block that new instructions are\nto be inserted into; we next [inform](./src/jit.rs#L146) it of our new block,\nusing\n[`switch_to_block`](https://docs.rs/cranelift-frontend/latest/cranelift_frontend/struct.FunctionBuilder.html#method.switch_to_block),\nso that we can start inserting instructions into it.\n\nThe one major concept about blocks is that the `FunctionBuilder` wants to know when\nall branches which could branch to a block have been seen, at which point it can\n*seal* the block, which allows it to perform SSA construction. All blocks must be\nsealed by the end of the function. We\n[seal](./src/jit.rs#L151)\na block with\n[`seal_block`](https://docs.rs/cranelift-frontend/latest/cranelift_frontend/struct.FunctionBuilder.html#method.seal_block).\n\nNext, our toy language doesn't have explicit variable declarations, so we walk the\nAST to discover all the variables, so that we can\n[declare](./src/jit.rs#L156)\nthem to the `FunctionBuilder`. These variables need not be in SSA form; the\n`FunctionBuilder` will take care of constructing SSA form internally.\n\nFor convenience when walking the function body, the demo here\n[uses](./src/jit.rs#L159)\n a `FunctionTranslator` object, which holds the `FunctionBuilder`, the current\n`Module`, as well as the symbol table for looking up variables. Now we can start\n[walking the function body](./src/jit.rs#L166).\n\n[AST translation](./src/jit.rs#L196) utilizes the instruction-building features\nof `FunctionBuilder`. Let's start with a simple example translating integer\nliterals:\n\n```rust\n    Expr::Literal(literal) =\u003e {\n        let imm: i32 = literal.parse().unwrap();\n        self.builder.ins().iconst(self.int, i64::from(imm))\n    }\n```\n\nThe first part is just extracting the integer value from the AST. The next line\nis the builder line:\n\n - The `.ins()` returns an \"insertion object\", which allows inserting an\n   instruction at the end of the currently active block.\n - `iconst` is the name of the builder routine for creating [integer\n   constants](https://docs.rs/cranelift-codegen/latest/cranelift_codegen/ir/trait.InstBuilder.html#method.iconst)\n   in Cranelift. Every instruction in the IR can be created directly through\n   such a function call.\n\nTranslation of [Add nodes](./src/jit.rs#L203) and other arithmetic operations is\nsimilarly straightforward.\n\nTranslation of [variable references](./src/jit.rs#L235) is mostly handled by\n`FunctionBuilder`'s `use_var` function:\n\n```rust\n    Expr::Identifier(name) =\u003e {\n        // `use_var` is used to read the value of a variable.\n        let variable = self.variables.get(\u0026name).expect(\"variable not defined\");\n        self.builder.use_var(*variable)\n    }\n```\n`use_var` is for reading the value of a (non-SSA) variable. (Internally,\n`FunctionBuilder` constructs SSA form to satisfy all uses).\n\nIts companion is `def_var`, which is used to write the value of a (non-SSA)\nvariable, which we use to implement assignment:\n\n```rust\n    fn translate_assign(\u0026mut self, name: String, expr: Expr) -\u003e Value {\n        // `def_var` is used to write the value of a variable. Note that\n        // variables can have multiple definitions. Cranelift will\n        // convert them into SSA form for itself automatically.\n        let new_value = self.translate_expr(*expr);\n        let variable = self.variables.get(\u0026name).unwrap();\n        self.builder.def_var(*variable, new_value);\n        new_value\n    }\n```\n\nNext, let's dive into [if-else](./src/jit.rs#L241) expressions. In order to\ndemonstrate explicit SSA construction, this demo gives if-else expressions\nreturn values. The way this looks in Cranelift is that the true and false arms\nof the if-else both have branches to a common merge point, and they each pass\ntheir \"return value\" as a block parameter to the merge point.\n\nNote that we seal the blocks we create once we know we'll have no more predecessors,\nwhich is something that a typical AST makes it easy to know.\n\nPutting it all together, here's the Cranelift IR for the function named\n[foo](./src/toy.rs#L63) in the demo program, which contains multiple ifs:\n\n```\nfunction u0:0(i64, i64) -\u003e i64 system_v {\nblock0(v0: i64, v1: i64):\n    v2 = iconst.i64 0\n    brz v0, block2\n    jump block1\n\nblock1:\n    v4 = iconst.i64 0\n    brz.i64 v1, block5\n    jump block4\n\nblock4:\n    v6 = iconst.i64 0\n    v7 = iconst.i64 30\n    jump block6(v7)\n\nblock5:\n    v8 = iconst.i64 0\n    v9 = iconst.i64 40\n    jump block6(v9)\n\nblock6(v5: i64):\n    jump block3(v5)\n\nblock2:\n    v10 = iconst.i64 0\n    v11 = iconst.i64 50\n    jump block3(v11)\n\nblock3(v3: i64):\n    v12 = iconst.i64 2\n    v13 = iadd v3, v12\n    return v13\n}\n```\n\nThe [while loop](./src/jit.rs#L323) translation is also straightforward.\n\nHere's the Cranelift IR for the function named [iterative_fib](./src/toy.rs#L94)\nin the demo program, which contains a while loop:\n\n```\nfunction u0:0(i64) -\u003e i64 system_v {\nblock0(v0: i64):\n    v1 = iconst.i64 0\n    v2 = iconst.i64 0\n    v3 = icmp eq v0, v2\n    v4 = bint.i64 v3\n    brz v4, block2\n    jump block1\n\nblock1:\n    v6 = iconst.i64 0\n    v7 = iconst.i64 0\n    jump block3(v7, v7)\n\nblock2:\n    v8 = iconst.i64 0\n    v9 = iconst.i64 1\n    v10 = isub.i64 v0, v9\n    v11 = iconst.i64 0\n    v12 = iconst.i64 1\n    jump block4(v10, v12, v11)\n\nblock4(v13: i64, v17: i64, v18: i64):\n    v14 = iconst.i64 0\n    v15 = icmp ne v13, v14\n    v16 = bint.i64 v15\n    brz v16, block6\n    jump block5\n\nblock5:\n    v19 = iadd.i64 v17, v18\n    v20 = iconst.i64 1\n    v21 = isub.i64 v13, v20\n    jump block4(v21, v19, v17)\n\nblock6:\n    v22 = iconst.i64 0\n    jump block3(v22, v17)\n\nblock3(v5: i64, v23: i64):\n    return v23\n}\n```\n\nFor [calls](./src/jit.rs#L355), the basic steps are to determine the call\nsignature, declare the function to be called, put the values to be passed in an\narray, and then call the `call` function.\n\nThe translation for [global data symbols](./src/jit.rs#L381), is similar; first\ndeclare the symbol to the module, then declare it to the current function, and\nthen use the `symbol_value` instruction to produce the value.\n\nAnd with that, we can return to our main `toy.rs` file and run some more examples.\nThere are examples of recursive and iterative fibonacci, which demonstrate more use\nof calls and control flow.\n\nAnd there's a hello world example which demonstrates several other features.\n\nThis program needs to allocate some [data](./src/toy.rs#L33) to hold the string\ndata. Inside jit.rs, [`create_data`](./src/jit.rs#L95) we initialize a\n`DataDescription` with the contents of the hello string, and also declare a data\nobject. Then we use the `DataDescription` object to define the object.  At that\npoint, we're done with the `DataDescription` object and can clear it. We then call\n`finalize_data` to perform linking (although our simple hello string doesn't\nmake any references so there isn't anything to do) and to obtain the final\nruntime address of the data, which we then convert back into a Rust slice for\nconvenience.\n\nAnd to show off a handy feature of the jit backend, it can look up symbols\nwith `libc::dlsym`, so you can call libc functions such as `puts` (being careful\nto NUL-terminate your strings!). Unfortunately, `printf` requires varargs, which\nCranelift does not yet support.\n\nAnd with all that, we can say \"hello world!\".\n\n\n### Native object files\n\nBecause of the `Module` abstraction, this demo can be adapted to write out an ELF\n.o file rather than JITing the code to memory with only minor changes, and I've done\nso in a branch [here](https://github.com/bytecodealliance/simplejit-demo/tree/faerie).\nThis writes a `test.o` file, which on an x86-64 ELF platform you can link with\n`cc test.o` and it produces an executable that calls the generated functions,\nincluding printing \"hello world!\".\n\nAnother branch [here](https://github.com/bytecodealliance/simplejit-demo/tree/faerie-macho)\nshows how to write Mach-O object files.\n\nObject files are written using the [faerie](https://github.com/m4b/faerie)\nlibrary.\n\n### Have fun!\n\nCranelift is still evolving, so if there are things here which are confusing or\nawkward, please let us know, via [github\nissues](https://github.com/bytecodealliance/wasmtime/issues?q=is%3Aissue+is%3Aopen+label%3Acranelift)\nor just stop by the [gitter chat](https://gitter.im/CraneStation/Lobby/~chat).\nVery few things in Cranelift's design are set in stone at this time, and we're\nreally interested to hear from people about what makes sense what doesn't.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbytecodealliance%2Fcranelift-jit-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbytecodealliance%2Fcranelift-jit-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbytecodealliance%2Fcranelift-jit-demo/lists"}