{"id":23240618,"url":"https://github.com/luisagroup/luisa-compute-rs","last_synced_at":"2025-08-20T01:32:27.728Z","repository":{"id":61578552,"uuid":"542142160","full_name":"LuisaGroup/luisa-compute-rs","owner":"LuisaGroup","description":"Rust frontend to LuisaCompute and more! ","archived":false,"fork":false,"pushed_at":"2024-04-11T18:16:22.000Z","size":2513,"stargazers_count":44,"open_issues_count":1,"forks_count":6,"subscribers_count":10,"default_branch":"main","last_synced_at":"2024-04-11T23:56:15.826Z","etag":null,"topics":["computer-graphics","cuda","differentiable-programming","differentiable-rendering","directx","dsl","dx","gpu","gpu-programming","graphics","raytracing","rendering","rust","shading-language","vulkan"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LuisaGroup.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,"dei":null}},"created_at":"2022-09-27T14:55:16.000Z","updated_at":"2024-04-15T02:28:11.132Z","dependencies_parsed_at":"2023-10-15T19:27:57.312Z","dependency_job_id":"95d8bcbc-be20-409e-8eac-78764d0e8dda","html_url":"https://github.com/LuisaGroup/luisa-compute-rs","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/LuisaGroup/luisa-compute-rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LuisaGroup%2Fluisa-compute-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LuisaGroup%2Fluisa-compute-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LuisaGroup%2Fluisa-compute-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LuisaGroup%2Fluisa-compute-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LuisaGroup","download_url":"https://codeload.github.com/LuisaGroup/luisa-compute-rs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LuisaGroup%2Fluisa-compute-rs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271252423,"owners_count":24726911,"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","status":"online","status_checked_at":"2025-08-19T02:00:09.176Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["computer-graphics","cuda","differentiable-programming","differentiable-rendering","directx","dsl","dx","gpu","gpu-programming","graphics","raytracing","rendering","rust","shading-language","vulkan"],"created_at":"2024-12-19T05:13:17.136Z","updated_at":"2025-08-20T01:32:27.720Z","avatar_url":"https://github.com/LuisaGroup.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `luisa-compute`\n\nRust frontend to LuisaCompute and more! Unified API and embedded DSL for high performance computing on stream architectures. \n\nTo see the use of `luisa-compute` in a high performance offline rendering system, checkout [our research renderer](https://github.com/shiinamiyuki/akari_render)\n\n## Table of Contents\n\n- [luisa-compute-rs](#luisa-compute-rs)\n  - [Table of Contents](#table-of-contents)\n  - [Example](#example)\n    - [Vecadd](#vecadd)\n  - [Overview](#overview)\n    - [Embedded Domain-Specific Language](#embedded-domain-specific-language)\n    - [Automatic Differentiation](#automatic-differentiation)\n    - [CPU Backend](#cpu-backend)\n    - [IR Module](#ir-module)\n    - [Debuggability](#debuggability)\n  - [Usage](#usage)\n    - [Building](#building)\n    - [`track!` and #[tracked] Macro](#track-and-tracked-macro)\n    - [Variables and Expressions](#variables-and-expressions)\n    - [Builtin Functions](#builtin-functions)\n    - [Control Flow](#control-flow)\n    - [Custom Data Types](#custom-data-types)\n    - [Polymorphism](#polymorphism)\n    - [Autodiff](#autodiff)\n    - [Custom Operators](#custom-operators)\n    - [Callable](#callable)\n    - [Outlining](#outlining)\n    - [Kernel](#kernel)\n    - [Debugging](#debugging)\n  - [Advanced Usage](#advanced-usage)\n  - [Safety](#safety)\n    - [API](#api)\n    - [Backend](#backend)\n  - [Citation](#citation)\n\n## Example\n\nTry:\n\n```\ncargo run --release --example path_tracer -- [cpu|cuda|dx|metal]\n```\n\n### Vecadd\n\n```rust\nuse luisa::prelude::*;\nuse luisa_compute as luisa;\n\n#[tracked]\nfn main() {\n    use luisa::*;\n    init_logger();\n\n    let args: Vec\u003cString\u003e = std::env::args().collect();\n    assert!(\n        args.len() \u003c= 2,\n        \"Usage: {} \u003cbackend\u003e. \u003cbackend\u003e: cpu, cuda, dx, metal, remote\",\n        args[0]\n    );\n\n    let ctx = Context::new(current_exe());\n    let device = ctx.create_device(if args.len() == 2 {\n        args[1].as_str()\n    } else {\n        \"cpu\"\n    });\n\n    let x = device.create_buffer::\u003cf32\u003e(1024);\n    let y = device.create_buffer::\u003cf32\u003e(1024);\n    let z = device.create_buffer::\u003cf32\u003e(1024);\n    x.view(..).fill_fn(|i| i as f32);\n    y.view(..).fill_fn(|i| 1000.0 * i as f32);\n\n    let kernel = device.create_kernel::\u003cfn(Buffer\u003cf32\u003e)\u003e(\u0026device, |buf_z| {\n        // z is pass by arg\n        let buf_x = \u0026x; // x and y are captured\n        let buf_y = \u0026y;\n        let tid = dispatch_id().x;\n        let x = buf_x.read(tid);\n        let y = buf_y.read(tid);\n        let vx = 0.0f32.var(); // create a local mutable variable\n        *vx += x;\n        buf_z.write(tid, vx + y);\n    });\n\n    kernel.dispatch([1024, 1, 1], \u0026z);\n    let z_data = z.view(..).copy_to_vec();\n    println!(\"{:?}\", \u0026z_data[0..16]);\n}\n```\n\nOther examples in [examples](luisa_compute/examples)\n\n| Example | Description |\n| ----------- | ----------- |\n| [Atomic](luisa_compute/examples/atomic.rs) | Atomic buffer operations |\n| [Bindless](luisa_compute/examples/bindless.rs) | Bindless array access |\n| [Custom Aggregate](luisa_compute/examples/custom_aggregate.rs) | Use `#[derive(Aggregate)]` for kernel-only data types |\n| [Custom Op](luisa_compute/examples/custom_op.rs) | Custom operator for CPU backend |\n| [Polymporphism](luisa_compute/examples/polymorphism.rs) | Simple usage of `Polymorphic\u003cK, T\u003e` |\n| [Advanced Polymporphism](luisa_compute/examples/polymorphism_advanced.rs) | Use `Polymorphic\u003cK, T\u003e` to implement recursive polymorphic call |\n| [Ray Tracing](luisa_compute/examples/raytracing.rs) | A simple raytracing kernel with GUI |\n| [Path Tracer](luisa_compute/examples/path_tracer.rs) | A path tracer with GUI |\n\n## Overview\n\n### Embedded Domain-Specific Language\n\nWe provided an Rust-flavored implementation of LuisaCompute EDSL that tightly integrates with Rust language via traits and proc-macros.\n\n### Automatic Differentiation\n\nWe implemented a source-to-source reverse mode automatic differentiation that supports complex control flow.\n\nThe autodiff works tightly with builtin functions and the type system. Instead of implementing every function using basic arithmetic operations and apply autodiff on it, all builtin functions are differentiated using efficient VJP formulae.\n\n### CPU Backend\n\nThis crate also provides a CPU backend implementation in Rust that will eventually replace the current LLVM backend in LuisaCompute. This backend emphasizes on debuggability, flexibility and as well as safety.\n\n### IR Module\n\nThe EDSL and code generation are built atop of an SSA-based IR module. The IR module is in a separate crate and can be used to implement other backends and IR transformation such as autodiff.\n\n### Debuggability\n\nThe CPU backend is designed to be debuggable. If needed, it will perform runtime checks to detect errors such as out-of-bound access, bindless array type mismatch, etc. It will display error message containing the **host** stacktrace for pinpointing the error location.\n\n## Usage\n### Building\n\nTo try out the examples, clone the repo using `git clone --recursive https://github.com/LuisaGroup/luisa-compute-rs.git`.\n\nTo use it in your project, add the following to your `Cargo.toml`:\n\n```toml\n[dependencies]\nluisa_compute = { git= \"https://github.com/LuisaGroup/luisa-compute-rs.git\"}\n```\n\nYou need to install `CMake` and `Ninja` to build the backends. More details about prerequistes can be found in [here](https://github.com/LuisaGroup/LuisaCompute#building).\n\nIn your project, the following to your files:\n\n```rust\nuse luisa_compute as luisa;\nuse luisa::prelude::*;\n```\n\n### `track!` and `#[tracked]` Macro\nTo start writing using DSL, let's first introduce the `track!` macro. `track!(expr)` rewrites `expr` and redirect operators/control flows to DSL's internal traits. It resolves the fundamental issue that Rust is unable to overload `operator=`.\n\n**Every operation involving a DSL object must be enclosed within `track!`**, except `Var\u003cT\u003e::store()` and `Var\u003cT\u003e::load()`\n\nFor example:\n\n```rust\nlet a = 1.0f32.expr();\nlet b = 1.0f32.expr();\nlet c = a + b; // Compile error\n\nlet c = track!(a + b); // c is now 2.0\n\n// Or even better,\ntrack!({\n  let a = 1.0f32.expr();\n  let b = 1.0f32.expr();\n  let c = a + b;\n});\n```\n\n**We* highly encourage you to enclose the entire kernel inside `track!`**\n\nInside `track!` normal Rust syntax is still supported (with a few exceptions). Operations involving non-DSL values are still performed using native Rust operators. For example:\n\n```rust\n// This is still valid\ntrack!({\n  let mut v = vec![1.0f32, 2.0,f32];\n  v.push(3.0);\n\n  let a = RefCell::new(1.0f32);\n  *a.borrow_mut() += 1.0;\n\n  match v.len() {\n    1 =\u003e println!(\"1\"),\n    _ =\u003e println!(\"not 1\"),\n  }\n\n  if v.is_empty() {\n    println!(\"empty\");\n  }\n\n  let a = 1.0f32.expr();\n  let b = a + 1.0;\n});\n```\n\nWe also offer a `#[tracked]` macro that applies to a function. It transform the body of the function using `track!`.\n\n ```rust\n#[tracked]\nfn add(a:Expr\u003cf32\u003e, b:Expr\u003cf32\u003e) -\u003e Expr\u003cf32\u003e {\n  a + b\n}\n```\n\nHowever, not every kernel can be constructed using `track!` code only. We still need the ability to use native control flow directly in kernel. \n\nFor example, we can use native `for` loops to unroll a DSL loop. We first starts with a native version using DSL loops.\n\n```rust\n#[tracked]\nfn pow_naive(x:Expr\u003cf32\u003e, i:u32) -\u003e Expr\u003cf32\u003e {\n  let p = 1.0f32.var();\n  for _ in 0..i {\n    p *= x;\n  }\n  **p // converts Var\u003cf32\u003e to Expr\u003cf32\u003e, only required when passing a Var\u003cT\u003e to fn(Expr\u003cT\u003e) and return from fn(...)-\u003eExpr\u003cT\u003e\n}\n```\n\nTo unroll the loop, we basically just what the DSL to produce `p*=x` for `i` times. We can use the `escape!(expr)` macro so that it leaves `expr` as is, preserving the native loop. \n\n```rust\n#[tracked]\nfn pow_unrolled(x:Expr\u003cf32\u003e, i:u32)-\u003eExpr\u003cf32\u003e {\n  let p = 1.0f32.var();\n  escape!({\n    for _ in 0..i {\n      track!({\n        p *= x;\n      });\n  });\n  **p \n}\n```\n\nOf course this can be tedius if you just want to unroll a loop. Thus we provide a `for_unrolled` function that unrolls a loop for you. \n\n```rust\n#[tracked]\nfn pow_unrolled(x:Expr\u003cf32\u003e, i:u32)-\u003eExpr\u003cf32\u003e {\n  let p = 1.0f32.var();\n  for_unrolled(0..i, |_|{\n      p *= x;\n  });\n\n  /*\n  Equivalently, you can use\n  (0..i).for_each(|_|{\n      p *= x;\n  });\n  */\n\n  **p \n}\n```\n\n### Variables and Expressions\n\nWe support the following primitive types on backend `bool`, `i32`, `u32`, `i64`, `u64`, `f32`. Additional primitive types such as `u8`, `i8`, `i16`, `u16`, and `f64` are supported on some backends.\nFor each type, there are two EDSL proxy objects `Expr\u003cT\u003e` and `Var\u003cT\u003e`. `Expr\u003cT\u003e` is an immutable object that represents a value. `Var\u003cT\u003e` is also an **immutable** object that represents a variable (mutable value). \n\n**Warning**: Every DSL object in host code **must** be immutable due to Rust unable to overload `operator =`. Attempting to circumvent this limitation using `Cell` and `RefCell` would likely result in uncompilable kernels/wrong results. For example:\n\n```rust\n// **no good**\nlet v = Cell::new(0.0f32.expr());\ntrack!(if cond {\n  v.set(v.get() + 1.0);\n));\n\n// **good**\nlet v = 0.0f32.var();\ntrack!(if cond {\n  *v += 1.0;\n));\n```\n\nAll operations except load/store should be performed on `Expr\u003cT\u003e`. `Var\u003cT\u003e` can only be used to load/store values.\n\nAs in the C++ EDSL, we additionally supports the vector of length 2-4 for all primitives and float square matrices with dimension 2-4 such as:\n\n```rust\nluisa_compute::lang::types::vector::alias::{\n  Bool2 \n  Bool3 \n  Bool4 \n  Float2 \n  Float3 \n  Float4\n  Int2\n  Int3 \n  Int4 \n  Uint2\n  Uint3 \n  Uint4\n};\n\nluisa_compute::lang::types::vector::{Mat2, Mat3, Mat4};\n```\n\nArray types `[T;N]` are also supported. Call `arr.read(i)` and `arr.write(i, value)` on `ArrayVar\u003cT, N\u003e` for element access. `ArrayExpr\u003cT,N\u003e` can be stored to and loaded from `ArrayVar\u003cT, N\u003e`. The limitation is however the array length must be determined during host compile time. If runtime length is required, use `VLArrayVar\u003cT\u003e`. `VLArrayVar\u003cT\u003e::zero(length: usize)` would create a zero initialized array. Similarly you can use `read` and `write` methods as well. To query the length of a `VLArrayVar\u003cT\u003e` in host, use `VLArrayVar\u003cT\u003e::static_len()-\u003eusize`. To query the length in kernel, use `VLArrayVar\u003cT\u003e::len()-\u003eExpr\u003cu32\u003e`\n\nMost operators are already overloaded with the only exception is comparision. We cannot overload comparision operators as `PartialOrd` cannot return a DSL type. Instead, use `cmpxx` methods such as `cmpgt, cmpeq`, etc. To cast a primitive/vector into another type, use `v.as_::\u003cType\u003e()`, `v.as_Type()` and `v.as_PrimitiveType()`. For example:\n\n```rust\nlet iv = Int2::expr(1, 1, 1);\nlet fv = iv.as_::\u003cFloat2\u003e(); //fv is Expr\u003cFloat2\u003e\nlet also_fv = iv.as_float2();\nlet also_fv = iv.cast_f32(); \n```\n\nTo perform a bitwise cast, use the `bitcast` function. `let fv:Expr\u003cf32\u003e = bitcast::\u003cu32, f32\u003e(0u32);`\n\n### Builtin Functions\n\nWe have extentded primitive types with methods similar to their host counterpart: `v.sin(), luisa::max(a, b)`, etc. Most methods accepts both a `Expr\u003cT\u003e` or a literal such as `0.0`. However, the `select` function is slightly different as it does not accept literals. You need to use `select(cond, f_var, 1.0f32.expr())`.\n\n### Control Flow\n\n*Note*, you cannot modify outer scope variables inside a control flow block by declaring the variable as `mut`. To modify outer scope variables, use `Var\u003cT\u003e` instead and store the value back to the outer scope.\n\n`if`, `while`, `break`, `continue`, `return` and `loop` are supported via `tracked!` macro. It is also possible to construct these control flows without `track!`. \n\nThe `switch_` statement has to be constructe manually. For example:\n\n```rust\nlet (x,y) = switch::\u003c(Expr\u003ci32\u003e, Expr\u003cf32\u003e)\u003e(value)\n    .case(1, || { ... })\n    .case(2, || { ... })\n    .default(|| { ... })\n    .finish();\n```\n\n**Warning**:  due to backend generates C-like source code instead of LLVM IR/PTX/DXIL directly, it is not possible to use `break` inside switch cases.\n\n### Custom Data Types\n\nTo add custom data types to the EDSL, simply derive from `Value` macro. Note that `#[repr(C)]` is required for the struct to be compatible with C ABI.\n`#[derive(Value)]` would generate two proxies types: `XXExpr` and `XXVar`. Implement your methods on these proxies instead of `Expr\u003cT\u003e` and `Var\u003cT\u003e` directly.\n\n```rust\n#[derive(Copy, Clone, Default, Debug, Value)]\n#[repr(C)]\n#[value_new(pub)]\npub struct MyVec2 {\n    pub x: f32,\n    pub y: f32,\n}\n\nimpl MyVec2Expr {\n  // pass arguments using `AsExpr` so that they accept both Var and Expr\n  #[tracked]\n  pub fn dot(\u0026self, other: impl AsExpr\u003cValue=MyVec2\u003e) {\n    self.x * other.x + self.y * other.y\n  }\n}\n\nimpl MyVec2Var {\n  #[tracked]\n  pub fn set_to_one(\u0026self) {\n    // you can access the current `Var\u003cSelf\u003e` using `self_`\n    self.self_ = MyVec2::new_expr(1.0, 1.0);\n  }\n}\n\ntrack!({\n  let v = MyVec2::var_zeroed();\n  let sum = v.x +*v.y; \n  *v.x += 1.0;\n  let v = MyVec2::from_comps_expr(MyVec2Comps{ x: 1.0f32.expr(), y: 1.0f32.expr()});\n  let v = MyVec2::new_expr(1.0f32, 2.0f32); // only if #[value_new] is present\n});\n\n// You can also control the order of arguments in `#[value_new]`\n#[derive(Copy, Clone, Default, Debug, Value)]\n#[repr(C)]\n#[value_new(pub y, x)]\npub struct Foo {\n    pub x: f32,\n    pub y: i32,\n}\n\nlet v = MyVec2::new_expr(1.0fi32, 2.0f32);\n// v.x == 2.0\n// v.y == 1\n```\n\n### Polymorphism\n\nWe prvoide a powerful `Polymorphic\u003cDevirtualizationKey, dyn Trait\u003e` construct as in the C++ DSL. See examples for more detail\n\n```rust\ntrait Area {\n    fn area(\u0026self) -\u003e Expr\u003cf32\u003e;\n}\n\n#[derive(Value, Clone, Copy)]\n#[repr(C)]\npub struct Circle {\n    radius: f32,\n}\n\nimpl Area for CircleExpr {\n    fn area(\u0026self) -\u003e Expr\u003cf32\u003e {\n        PI * self.radius * self.radius\n    }\n}\n\nimpl_polymorphic!(Area, Circle);\n\nlet circles = device.create_buffer(..);\nlet mut poly_area: Polymorphic\u003c(), dyn Area\u003e = Polymorphic::new();\npoly_area.register((), \u0026circles);\nlet area = poly_area.dispatch(tag, index, |obj|{\n    obj.area()\n});\n```\n\n### Autodiff\n\nAutodiff code should be enclosed in the `autodiff` call. The `requires_grad` call is used to mark the variables that need to be differentiated. Any type including user defined ones can receive gradients. The `backward` call triggers the backward pass. Subsequent calls to `gradient` will return the gradient of the variable passed in. User can also supply custom gradients with `backward_with_grad`.\n\nNote: Only one backward call is allowed in a single autodiff block. The autodiff block does not return any value. To store any side effects, use of local variables or buffers is required.\n\n```rust\nautodiff(||{\n    let v: Expr\u003cVec3\u003e = buf_v.read(..);\n    let m: Expr\u003cMat3\u003e = buf_m.read(..);\n    requires_grad(v);\n    requires_grad(m);\n    let z = v.dot(m * v) * 0.5;\n    backward(z);\n    let dv = gradient(dv);\n    let dm = gradient(dm);\n    buf_dv.write(.., dv);\n    buf_dm.write(.., dm);\n});\n```\n\n### Custom Operators\n\nLuisaCompute supports injecting arbitrary user code to implement a custom operator. This is handled differently on different backends.\nOn CPU backends, user can directly pass a closure to the kernel. The closure needs to have a `Fn(\u0026mut T)` signature where it modifies the argument inplace. The EDSL frontend would then wrap the closure into a `T-\u003eT` function object.\n\n```rust\n#[derive(Clone, Copy, Value, Debug)]\n#[repr(C)]\npub struct MyAddArgs {\n    pub x: f32,\n    pub y: f32,\n    pub result: f32,\n}\nlet my_add = CpuFn::new(|args: \u0026mut MyAddArgs| {\n    args.result = args.x + args.y;\n});\n\nlet args = MyAddArgsExpr::new(x, y, 0.0.expr());\nlet result = my_add.call(args);\n```\n\n### Callable\n\n**Note:** Almost all usage of callables are largely replaced with `outline(...)` in the [Outlining](#outlining) section. We keep this section for reference.\n\nUsers can define device-only functions using Callables. Callables have similar type signature to kernels: `Callable\u003cfn(Args)-\u003eRet\u003e`. \nThe difference is that Callables are not dispatchable and can only be called from other Callables or Kernels. Callables can be created using `Device::create_callable`. To invoke a Callable, use `Callable::call(args...)`. Callables accepts arguments such as resources (`BufferVar\u003cT\u003e`, .etc), expressions and references (pass a `Var\u003cT\u003e` to the callable). For example:\n\n```rust\nlet add = Callable::\u003cfn(Expr\u003cf32\u003e, Expr\u003cf32\u003e)-\u003e Expr\u003cf32\u003e\u003e::new(\u0026device, track!(|a, b| {\n    a + b\n}));\nlet z = add.call(x, y);\nlet pass_by_ref =  Callable::\u003cfn(Var\u003cf32\u003e)\u003e::new(\u0026device, track!(|a| {\n   a += 1.0;\n}));\nlet a = 1.0f32.var();\npass_by_ref.call(a);\ncpu_dbg!(*a); // prints 2.0\n```\n***Note***: You create callables within another callable and even capture variables from the outer callable. The only limitation is that you cannot return a callable from another callable.\n```rust\nlet add = track!(Callable::\u003cfn(Expr\u003cf32\u003e, Expr\u003cf32\u003e) -\u003e Expr\u003cf32\u003e\u003e::new(\n    \u0026device,\n    |a, b| {\n        // callables can be defined within callables\n        let partial_add = Callable::\u003cfn(Expr\u003cf32\u003e) -\u003e Expr\u003cf32\u003e\u003e::new(\u0026device, |y| a + y);\n        partial_add.call(b)\n    }\n));\n```\n\n#### Static Callables\n\nA static callable does not capture any resources and thus can be safely recorded inside any callable/kernel. To create a static callable, use `create_static_callable(fn)`. For example,\n\n```rust\nlazy_static! {\n    static ref ADD:Callable\u003cfn(Expr\u003cf32\u003e, Expr\u003cf32\u003e)-\u003eExpr\u003cf32\u003e\u003e = Callable::\u003cfn(Expr\u003cf32\u003e, Expr\u003cf32\u003e)-\u003eExpr\u003cf32\u003e\u003e::new_static(|a, b| {\n    track!(a + b)\n});\n}\nADD.call(x, y);\n```\n\n#### Dynamic Callables (Templates)\n\nUse `DynCallable`. These are callables that defer recording until being called. As a result, it requires you to pass a `'static` closure, avoiding the capture issue. To create a `DynCallable`, use `Device::create_dyn_callable(Box::new(fn))`. The syntax is the same as `create_callable`. Furthermore, `DynCallable` supports `DynExpr` and `DynVar`, which provides some capablitiy of implementing template/overloading inside EDSL.\n\n```rust\nlet add = DynCallable::\u003cfn(DynExpr, DynExpr) -\u003e DynExpr\u003e::new(\n    \u0026device,\n    Box::new(|a: DynExpr, b: DynExpr| -\u003e DynExpr {\n        if let Some(a) = a.downcast::\u003cf32\u003e() {\n            let b = b.downcast::\u003cf32\u003e().unwrap();\n            return DynExpr::new(track!(a + b));\n        } else if let Some(a) = a.downcast::\u003ci32\u003e() {\n            let b = b.downcast::\u003ci32\u003e().unwrap();\n            return DynExpr::new(track!(a + b));\n        } else {\n            unreachable!()\n        }\n    }),\n);\n```\n\n### Outlining\n\nThe `outline(|| { .. })` extracts a code snippet and deduplicate it into a callable. The callable is then called from the original location. \n\n```rust\nlet add = |a:Expr\u003cf32\u003e, b:Expr\u003cf32\u003e|{\n  let sum = 0.0f32.var();\n  // automatically generates a callable and call it\n  outline(|| {\n      let a = 1.0f32.expr();\n      let b = 2.0f32.expr();\n      *sum = a + b;\n  });\n  **sum\n};\nlet z = add(x, y);\n// the following code is deduplicated\nlet w = add(z, y);\n```\n\nSince `outline` works via capturing, it is possible to pass arbitrary types or ever types that are not statically known. \n\n### Kernel\n\nA kernel can be written in a closure or a function. The closure/function should have a `Fn(/*args*/)-\u003e()` signature, where the args are taking the `Var` type of resources, such as `BufferVar\u003cT\u003e`, `Tex2D\u003cT\u003e`, etc.\n\n```rust\nlet kernel = device.create_kernel::\u003cfn(Arg0, Arg1, ...)\u003e(\u0026|/*args*/| {\n    /*body*/\n});\nkernel.dispatch([/*dispatch size*/], \u0026arg0, \u0026arg1, ...);\n```\n\nThere are two ways to pass arguments to a kernel: by arguments or by capture.\n\n```rust\nlet captured:Buffer\u003cf32\u003e = device.create_buffer(...);\nlet kernel = device.create_kernel::\u003cfn(Buffer\u003cf32\u003e\u003e(arg| {\n    let v = arg.read(..);\n    let u = captured.read(..);\n}));\n```\n\nUser can pass a maximum of 16 arguments to kernel and unlimited number of captured variables. If more than 16 arguments are needed, user can pack them into a struct and pass the struct as a single argument.\n\n```rust\n#[derive(BindGroup)]\npub struct BufferPair {\n    a:Buffer\u003cf32\u003e,\n    b:Buffer\u003cf32\u003e\n}\n\nlet kernel = device.create_kernel::\u003cfn(BufferPair)\u003e(\u0026|| {\n    // ...\n});\n\nlet a = device.create_buffer(...);\nlet b = device.create_buffer(...);\nlet pair = BufferPair{a,b};\nkernel.dispatch([...], \u0026packed);\nlet BufferPair{a, b} = packed; // unpack if you need to use them later\n```\n\n### Debugging\n\nWe provide logging through the `log` crate. Users can either setup their own logger or use the `init_logger()` and `init_logger_verbose()` for handy initialization.\nFor `debug` builds,  oob checks are automatically inserted so that an assertion failure would occur if oob access is detected. On CPU/CUDA backend, it will be accompanied by an informative message such as `assertion failed: i.cmplt(self.len()) at xx.rs:yy:zz`. Setting the environment variable `LUISA_BACKTRACE=1` would display a stacktrace containing the *DSL* code that records the kernel. For other backends, assertion with message is still *WIP*.\n\nFor `release` builds however, these checks are disabled by default for performance reasons. To enable them, set environment variable `LUISA_DEBUG=1` prior to launching the application.\n\n## Advanced Usage\n\nNote that the IR module has a public interface. If needed, user can implement their own DSL syntax sugar. Every EDSL object implements either `Aggregate` or `FromNode` trait, which allows any EDSL type to be destructured into its underlying IR nodes and reconstructed from them.\n\nTODO\n\n## Safety\n\n### API\n\nHost-side safety: The API aims to be 100% safe on host side. However, the safety of async operations are gauranteed via staticly know sync points (such as `Stream::submit_and_sync`). If fully dynamic async operations are needed, user need to manually lift the liftime and use unsafe code accordingly.\n\nDevice-side safety: Due to the async nature of device-side operations. It is both very difficult to propose a safe **host** API that captures **device** resource lifetime. While device-side safety isn't guaranteed at compile time, on `cpu` backend runtime checks will catch any illegal memory access during execution. However, for other backends such check is either too expensive or impractical and memory errors would result in undefined behavior instead.\n\n### Backend\n\nSafety checks such as OOB is generally not available for many GPU backends. As it is difficult to produce meaningful debug message in event of a crash. However, the CPU backend provided in the crate contains full safety checks and is recommended for debugging.\n\n## Citation\n\nWhen using luisa-compute-rs in an academic project, we encourage you to cite\n\n```bibtex\n@misc{LuisaComputeRust\n    author = {Xiaochun Tong, et al},\n    year = {2023},\n    note = {https://github.com/LuisaGroup/luisa-compute-rs},\n    title = {Rust frontend to LuisaCompute}\n}\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluisagroup%2Fluisa-compute-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fluisagroup%2Fluisa-compute-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluisagroup%2Fluisa-compute-rs/lists"}