From 37a09f74facf17d6a36958755a201e270a1541d6 Mon Sep 17 00:00:00 2001 From: Mohammad Omidvar Date: Fri, 24 Jul 2026 11:49:14 -0700 Subject: [PATCH 1/9] Move book pages to a separate folder --- docs/{ => book}/.gitignore | 0 docs/{ => book}/book.toml | 0 docs/{ => book}/src/SUMMARY.md | 0 docs/book/src/intro.md | 18 ++++++++++++++++++ docs/{ => book}/src/leaf.md | 0 docs/{ => book}/src/user_guide/configs.md | 0 .../src/user_guide/getting_started.md | 0 .../{ => book}/src/user_guide/recipes/cargo.md | 0 .../src/user_guide/recipes/div_input.md | 0 .../src/user_guide/recipes/fuzzing.md | 0 .../src/user_guide/recipes/section.md | 0 docs/src/intro.md | 3 --- 12 files changed, 18 insertions(+), 3 deletions(-) rename docs/{ => book}/.gitignore (100%) rename docs/{ => book}/book.toml (100%) rename docs/{ => book}/src/SUMMARY.md (100%) create mode 100644 docs/book/src/intro.md rename docs/{ => book}/src/leaf.md (100%) rename docs/{ => book}/src/user_guide/configs.md (100%) rename docs/{ => book}/src/user_guide/getting_started.md (100%) rename docs/{ => book}/src/user_guide/recipes/cargo.md (100%) rename docs/{ => book}/src/user_guide/recipes/div_input.md (100%) rename docs/{ => book}/src/user_guide/recipes/fuzzing.md (100%) rename docs/{ => book}/src/user_guide/recipes/section.md (100%) delete mode 100644 docs/src/intro.md diff --git a/docs/.gitignore b/docs/book/.gitignore similarity index 100% rename from docs/.gitignore rename to docs/book/.gitignore diff --git a/docs/book.toml b/docs/book/book.toml similarity index 100% rename from docs/book.toml rename to docs/book/book.toml diff --git a/docs/src/SUMMARY.md b/docs/book/src/SUMMARY.md similarity index 100% rename from docs/src/SUMMARY.md rename to docs/book/src/SUMMARY.md diff --git a/docs/book/src/intro.md b/docs/book/src/intro.md new file mode 100644 index 00000000..2c8667d3 --- /dev/null +++ b/docs/book/src/intro.md @@ -0,0 +1,18 @@ +# Introduction + +Leaf is a framework for dynamic analysis of Rust programs built around MIR instrumentation. Instead of analyzing source code directly, Leaf compiles a program through `leafc`, rewrites the relevant MIR, and runs the resulting binary with a runtime backend attached. + +That split keeps the responsibilities clear: + +- the compiler frontend prepares the program for analysis, +- the runtime receives execution events and records or reacts to them, and +- higher-level tooling can orchestrate repeated runs or more advanced workflows. + +At a high level, the workflow looks like this: + +1. write or choose a Rust program, +2. compile it with Leaf’s instrumentation pipeline, +3. run the instrumented binary with the desired backend, and +4. inspect the emitted traces or analysis results. + +This book starts with a practical getting-started guide, then moves into recipes and configuration details for common workflows. Use it as a reference when you need to understand how Leaf’s compiler, runtime, and orchestration pieces fit together. \ No newline at end of file diff --git a/docs/src/leaf.md b/docs/book/src/leaf.md similarity index 100% rename from docs/src/leaf.md rename to docs/book/src/leaf.md diff --git a/docs/src/user_guide/configs.md b/docs/book/src/user_guide/configs.md similarity index 100% rename from docs/src/user_guide/configs.md rename to docs/book/src/user_guide/configs.md diff --git a/docs/src/user_guide/getting_started.md b/docs/book/src/user_guide/getting_started.md similarity index 100% rename from docs/src/user_guide/getting_started.md rename to docs/book/src/user_guide/getting_started.md diff --git a/docs/src/user_guide/recipes/cargo.md b/docs/book/src/user_guide/recipes/cargo.md similarity index 100% rename from docs/src/user_guide/recipes/cargo.md rename to docs/book/src/user_guide/recipes/cargo.md diff --git a/docs/src/user_guide/recipes/div_input.md b/docs/book/src/user_guide/recipes/div_input.md similarity index 100% rename from docs/src/user_guide/recipes/div_input.md rename to docs/book/src/user_guide/recipes/div_input.md diff --git a/docs/src/user_guide/recipes/fuzzing.md b/docs/book/src/user_guide/recipes/fuzzing.md similarity index 100% rename from docs/src/user_guide/recipes/fuzzing.md rename to docs/book/src/user_guide/recipes/fuzzing.md diff --git a/docs/src/user_guide/recipes/section.md b/docs/book/src/user_guide/recipes/section.md similarity index 100% rename from docs/src/user_guide/recipes/section.md rename to docs/book/src/user_guide/recipes/section.md diff --git a/docs/src/intro.md b/docs/src/intro.md deleted file mode 100644 index 883545e1..00000000 --- a/docs/src/intro.md +++ /dev/null @@ -1,3 +0,0 @@ -# Introduction - -(TBD) \ No newline at end of file From 04b030d1f02b5f9b66600a31a2da7667d0802218 Mon Sep 17 00:00:00 2001 From: Mohammad Omidvar Date: Fri, 24 Jul 2026 17:27:09 -0700 Subject: [PATCH 2/9] Restructure and clean up Leaf book pages Clean up docs Clean up and add a map of samples to the book --- docs/book/src/SUMMARY.md | 51 ++++++++++------- docs/book/src/explanations/callbacks.md | 1 + docs/book/src/explanations/dependencies.md | 1 + docs/book/src/explanations/dmir.md | 1 + docs/book/src/explanations/execution-model.md | 1 + docs/book/src/explanations/instrumentation.md | 1 + docs/book/src/explanations/runtime-support.md | 1 + .../{user_guide/recipes => how-to}/cargo.md | 2 +- .../recipes => how-to}/div_input.md | 2 +- .../{user_guide/recipes => how-to}/fuzzing.md | 0 docs/book/src/leaf.md | 4 +- docs/book/src/reference/compiler.md | 1 + docs/book/src/reference/configuration.md | 3 + docs/book/src/reference/dmir.md | 1 + docs/book/src/reference/glossary.md | 1 + docs/book/src/reference/intrinsics.md | 3 + docs/book/src/reference/orchestrator.md | 1 + docs/book/src/reference/runtime.md | 1 + docs/book/src/reference/samples.md | 56 +++++++++++++++++++ .../first_dynamic_analysis.md} | 2 +- docs/book/src/user_guide/configs.md | 3 - docs/book/src/user_guide/recipes/section.md | 3 - samples/.test_compile_rs.ignore | 2 +- samples/.test_execution_rs.ignore | 2 +- .../casting/numeric/discr/main.rs | 0 .../casting/numeric/floats/main.rs | 0 .../casting/numeric/int_only/main.rs | 0 .../casting/numeric/int_only/multi.rs | 0 .../casting/numeric/int_only/sym_main.rs | 0 .../casting/pointer/expose/ptr_to_usize.rs | 0 .../pointer/expose/sym_ptr_to_usize.rs | 0 .../to_pointer/fn_ptr_to_another_ptr.rs | 0 .../pointer/to_pointer/mut_to_const_ptr.rs | 0 .../pointer/to_pointer/ptr_to_another_ptr.rs | 0 .../pointer/to_pointer/usize_to_ptr.rs | 0 .../casting/pointer/unsize_dyn/main.rs | 0 .../casting/pointer/unsize_slice/main.rs | 0 .../{ => assignment}/casting/subtype/main.rs | 0 .../casting/transmute/basic.rs | 0 .../casting/transmute/sym_partial.rs | 0 .../casting/transmute/sym_partial_mid.rs | 0 samples/assignment/shallow_init_box/main.rs | 3 - samples/branching/match_enum/range.rs | 14 +++++ samples/branching/match_range/main.rs | 13 ----- samples/{assignment => }/const/addr.rs | 0 samples/{assignment => }/const/blocks.rs | 18 +++--- samples/{assignment => }/const/main.rs | 0 samples/{assignment => }/const/zst.rs | 0 samples/crates/sym_basic_bin/Cargo.lock | 7 --- samples/crates/sym_basic_bin/Cargo.toml | 8 --- samples/crates/sym_basic_bin/src/child.rs | 9 --- samples/crates/sym_basic_bin/src/main.rs | 14 ----- .../drop/{drop_in_place.rs => drop_glue.rs} | 0 .../main.rs => call_basic/return_unit.rs} | 2 +- samples/function/println/hello_world.rs | 3 - samples/hello_world.rs | 2 +- samples/misc/constant_folding/main.rs | 15 ----- samples/misc/external_fn/hash.rs | 17 ------ samples/misc/intrinsics/call_intrinsic.rs | 16 ------ samples/misc/multiple_vars/main.rs | 19 ------- samples/misc/sym_field/main.rs | 31 ---------- samples/misc/type_export/mono.rs | 9 --- .../projection/downcast/enum/main.rs | 0 samples/{ => place}/projection/field/main.rs | 0 samples/{ => place}/projection/index/main.rs | 0 .../projection/unwrap_unsafe_binder/main.rs | 0 66 files changed, 136 insertions(+), 208 deletions(-) create mode 100644 docs/book/src/explanations/callbacks.md create mode 100644 docs/book/src/explanations/dependencies.md create mode 100644 docs/book/src/explanations/dmir.md create mode 100644 docs/book/src/explanations/execution-model.md create mode 100644 docs/book/src/explanations/instrumentation.md create mode 100644 docs/book/src/explanations/runtime-support.md rename docs/book/src/{user_guide/recipes => how-to}/cargo.md (93%) rename docs/book/src/{user_guide/recipes => how-to}/div_input.md (99%) rename docs/book/src/{user_guide/recipes => how-to}/fuzzing.md (100%) create mode 100644 docs/book/src/reference/compiler.md create mode 100644 docs/book/src/reference/configuration.md create mode 100644 docs/book/src/reference/dmir.md create mode 100644 docs/book/src/reference/glossary.md create mode 100644 docs/book/src/reference/intrinsics.md create mode 100644 docs/book/src/reference/orchestrator.md create mode 100644 docs/book/src/reference/runtime.md create mode 100644 docs/book/src/reference/samples.md rename docs/book/src/{user_guide/getting_started.md => tutorials/first_dynamic_analysis.md} (98%) delete mode 100644 docs/book/src/user_guide/configs.md delete mode 100644 docs/book/src/user_guide/recipes/section.md rename samples/{ => assignment}/casting/numeric/discr/main.rs (100%) rename samples/{ => assignment}/casting/numeric/floats/main.rs (100%) rename samples/{ => assignment}/casting/numeric/int_only/main.rs (100%) rename samples/{ => assignment}/casting/numeric/int_only/multi.rs (100%) rename samples/{ => assignment}/casting/numeric/int_only/sym_main.rs (100%) rename samples/{ => assignment}/casting/pointer/expose/ptr_to_usize.rs (100%) rename samples/{ => assignment}/casting/pointer/expose/sym_ptr_to_usize.rs (100%) rename samples/{ => assignment}/casting/pointer/to_pointer/fn_ptr_to_another_ptr.rs (100%) rename samples/{ => assignment}/casting/pointer/to_pointer/mut_to_const_ptr.rs (100%) rename samples/{ => assignment}/casting/pointer/to_pointer/ptr_to_another_ptr.rs (100%) rename samples/{ => assignment}/casting/pointer/to_pointer/usize_to_ptr.rs (100%) rename samples/{ => assignment}/casting/pointer/unsize_dyn/main.rs (100%) rename samples/{ => assignment}/casting/pointer/unsize_slice/main.rs (100%) rename samples/{ => assignment}/casting/subtype/main.rs (100%) rename samples/{ => assignment}/casting/transmute/basic.rs (100%) rename samples/{ => assignment}/casting/transmute/sym_partial.rs (100%) rename samples/{ => assignment}/casting/transmute/sym_partial_mid.rs (100%) delete mode 100644 samples/assignment/shallow_init_box/main.rs create mode 100644 samples/branching/match_enum/range.rs delete mode 100644 samples/branching/match_range/main.rs rename samples/{assignment => }/const/addr.rs (100%) rename samples/{assignment => }/const/blocks.rs (81%) rename samples/{assignment => }/const/main.rs (100%) rename samples/{assignment => }/const/zst.rs (100%) delete mode 100644 samples/crates/sym_basic_bin/Cargo.lock delete mode 100644 samples/crates/sym_basic_bin/Cargo.toml delete mode 100644 samples/crates/sym_basic_bin/src/child.rs delete mode 100644 samples/crates/sym_basic_bin/src/main.rs rename samples/drop/{drop_in_place.rs => drop_glue.rs} (100%) rename samples/function/{return_unit/main.rs => call_basic/return_unit.rs} (96%) delete mode 100644 samples/function/println/hello_world.rs delete mode 100644 samples/misc/constant_folding/main.rs delete mode 100644 samples/misc/external_fn/hash.rs delete mode 100644 samples/misc/intrinsics/call_intrinsic.rs delete mode 100644 samples/misc/multiple_vars/main.rs delete mode 100644 samples/misc/sym_field/main.rs delete mode 100644 samples/misc/type_export/mono.rs rename samples/{ => place}/projection/downcast/enum/main.rs (100%) rename samples/{ => place}/projection/field/main.rs (100%) rename samples/{ => place}/projection/index/main.rs (100%) rename samples/{ => place}/projection/unwrap_unsafe_binder/main.rs (100%) diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md index cfde25d5..8d9b8774 100644 --- a/docs/book/src/SUMMARY.md +++ b/docs/book/src/SUMMARY.md @@ -1,21 +1,34 @@ # Summary -[Leaf](./leaf.md) -[Introduction](./intro.md) - -# User Guide - -- [Getting Started](./user_guide/getting_started.md) -- [Recipes](./user_guide/recipes/section.md) - - [Building Cargo Packages](./user_guide/recipes/cargo.md) - - [Diverging Input Generation](./user_guide/recipes/div_input.md) - - [Fuzzing](./user_guide/recipes/fuzzing.md) -- [Configurations](./user_guide/configs.md) - -# Technical Reference -- [Workflow Overview]() -- [Components]() - - [Compiler]() - - [Runtime]() - - [Orchestrators]() - - [Integrations]() \ No newline at end of file +- [Leaf](./leaf.md) +- [Introduction](./intro.md) + +# Tutorials + +- [First Dynamic Analysis with Leaf](./tutorials/first_dynamic_analysis.md) + +# How-to Guides + +- [Build a Cargo Package with Leaf](./how-to/cargo.md) +- [Generate Diverging Input](./how-to/div_input.md) +- [Run Fuzzing with Leaf](./how-to/fuzzing.md) + +# Explanations + +- [Leaf's execution model](./explanations/execution-model.md) +- [MIR, runtime facts, and DMIR](./explanations/dmir.md) +- [How MIR instrumentation becomes runtime events](./explanations/instrumentation.md) +- [How analysis callbacks receive and transform events](./explanations/callbacks.md) +- [Runtime support utilities and shared services](./explanations/runtime-support.md) +- [Dependency instrumentation and build strategy](./explanations/dependencies.md) + +# Reference + +- [Compiler](./reference/compiler.md) +- [Runtime](./reference/runtime.md) +- [Orchestrator](./reference/orchestrator.md) +- [DMIR](./reference/dmir-callback.md) +- [Configuration](./reference/configuration.md) +- [MIR Intrinsics](./reference/intrinsics.md) +- [Samples](./reference/samples.md) +- [Glossary](./reference/glossary.md) \ No newline at end of file diff --git a/docs/book/src/explanations/callbacks.md b/docs/book/src/explanations/callbacks.md new file mode 100644 index 00000000..64736319 --- /dev/null +++ b/docs/book/src/explanations/callbacks.md @@ -0,0 +1 @@ +# How analysis callbacks receive and transform events diff --git a/docs/book/src/explanations/dependencies.md b/docs/book/src/explanations/dependencies.md new file mode 100644 index 00000000..5430371f --- /dev/null +++ b/docs/book/src/explanations/dependencies.md @@ -0,0 +1 @@ +# Dependency instrumentation and build strategy diff --git a/docs/book/src/explanations/dmir.md b/docs/book/src/explanations/dmir.md new file mode 100644 index 00000000..2c9d633d --- /dev/null +++ b/docs/book/src/explanations/dmir.md @@ -0,0 +1 @@ +# MIR, runtime facts, and DMIR diff --git a/docs/book/src/explanations/execution-model.md b/docs/book/src/explanations/execution-model.md new file mode 100644 index 00000000..5e468459 --- /dev/null +++ b/docs/book/src/explanations/execution-model.md @@ -0,0 +1 @@ +# Leaf's execution model diff --git a/docs/book/src/explanations/instrumentation.md b/docs/book/src/explanations/instrumentation.md new file mode 100644 index 00000000..08b4c0b9 --- /dev/null +++ b/docs/book/src/explanations/instrumentation.md @@ -0,0 +1 @@ +# How MIR instrumentation becomes runtime events diff --git a/docs/book/src/explanations/runtime-support.md b/docs/book/src/explanations/runtime-support.md new file mode 100644 index 00000000..3e4e4bea --- /dev/null +++ b/docs/book/src/explanations/runtime-support.md @@ -0,0 +1 @@ +# Runtime support utilities and shared services diff --git a/docs/book/src/user_guide/recipes/cargo.md b/docs/book/src/how-to/cargo.md similarity index 93% rename from docs/book/src/user_guide/recipes/cargo.md rename to docs/book/src/how-to/cargo.md index 944d4a5b..c3532380 100644 --- a/docs/book/src/user_guide/recipes/cargo.md +++ b/docs/book/src/how-to/cargo.md @@ -1,4 +1,4 @@ -# Building Cargo Packages +# Build a Cargo Package with Leaf As mentioned before, you can look at `leafc` as a wrapper around `rustc` and it should be work in any command using `rustc`. diff --git a/docs/book/src/user_guide/recipes/div_input.md b/docs/book/src/how-to/div_input.md similarity index 99% rename from docs/book/src/user_guide/recipes/div_input.md rename to docs/book/src/how-to/div_input.md index 0a475f7a..a2eef225 100644 --- a/docs/book/src/user_guide/recipes/div_input.md +++ b/docs/book/src/how-to/div_input.md @@ -64,4 +64,4 @@ To run pure concolic testing loop for a target: ``` 1. The loop stops if no more new distinct input is found to be given to the program (can possibly run forever). -We recommend looking at the options available for tuning the loop by passing `--help`. +We recommend looking at the options available for tuning the loop by passing `--help`. \ No newline at end of file diff --git a/docs/book/src/user_guide/recipes/fuzzing.md b/docs/book/src/how-to/fuzzing.md similarity index 100% rename from docs/book/src/user_guide/recipes/fuzzing.md rename to docs/book/src/how-to/fuzzing.md diff --git a/docs/book/src/leaf.md b/docs/book/src/leaf.md index 29b72533..e943ae23 100644 --- a/docs/book/src/leaf.md +++ b/docs/book/src/leaf.md @@ -1,9 +1,9 @@ # Leaf Concolic execution for Rust through MIR instrumentation. -Welcome to project Leaf, a tool to perform dynamic symbolic execution for Rust programs. +Welcome to project Leaf, a tool to dynamic analysis for Rust programs by MIR instrumentation. Leaf aims to be robust, extensible, and easily-integrable within real world Rust testing stacks. Please refer to [Introduction](./intro.md) for more details about the workflow of the tool, -and to [Getting Started](./user_guide/getting_started.md) to learn how to use it. +and try to perform your [first dynamic analysis with Leaf](./tutorials/first_dynamic_analysis.md) to get a hands-on experience. diff --git a/docs/book/src/reference/compiler.md b/docs/book/src/reference/compiler.md new file mode 100644 index 00000000..198c4da2 --- /dev/null +++ b/docs/book/src/reference/compiler.md @@ -0,0 +1 @@ +# Compiler Reference diff --git a/docs/book/src/reference/configuration.md b/docs/book/src/reference/configuration.md new file mode 100644 index 00000000..79ab6258 --- /dev/null +++ b/docs/book/src/reference/configuration.md @@ -0,0 +1,3 @@ +# Configuration Reference + +This page will be written later. diff --git a/docs/book/src/reference/dmir.md b/docs/book/src/reference/dmir.md new file mode 100644 index 00000000..581eca2a --- /dev/null +++ b/docs/book/src/reference/dmir.md @@ -0,0 +1 @@ +# DMIR Reference diff --git a/docs/book/src/reference/glossary.md b/docs/book/src/reference/glossary.md new file mode 100644 index 00000000..ca3c8276 --- /dev/null +++ b/docs/book/src/reference/glossary.md @@ -0,0 +1 @@ +# Glossary diff --git a/docs/book/src/reference/intrinsics.md b/docs/book/src/reference/intrinsics.md new file mode 100644 index 00000000..e4ae3285 --- /dev/null +++ b/docs/book/src/reference/intrinsics.md @@ -0,0 +1,3 @@ +# MIR Intrinsics + +We keep track of the intrinsics to support [here](https://wirehaired-buttercup-010.notion.site/10c9cebab80b812da89cf9fd2b6d2679?v=10c9cebab80b801090ed000cfa24584b&pvs=143). \ No newline at end of file diff --git a/docs/book/src/reference/orchestrator.md b/docs/book/src/reference/orchestrator.md new file mode 100644 index 00000000..8800c32e --- /dev/null +++ b/docs/book/src/reference/orchestrator.md @@ -0,0 +1 @@ +# Orchestrator Reference diff --git a/docs/book/src/reference/runtime.md b/docs/book/src/reference/runtime.md new file mode 100644 index 00000000..23fc9927 --- /dev/null +++ b/docs/book/src/reference/runtime.md @@ -0,0 +1 @@ +# Runtime Reference diff --git a/docs/book/src/reference/samples.md b/docs/book/src/reference/samples.md new file mode 100644 index 00000000..ce226701 --- /dev/null +++ b/docs/book/src/reference/samples.md @@ -0,0 +1,56 @@ +# Samples Reference + +## Map + +Here is a map of sample programs put under `samples` in the work tree of the project. + +| Sample | Targets | +|---|---| +| `assignment/addr_of` | `Rvalue::RawPtr` | +| `assignment/aggregate` | `Rvalue::Aggregate` | +| `assignment/bin_op` | `Rvalue::BinaryOp` | +| `const` | `Operand::Constant::*` | +| `assignment/discr` | `Rvalue::Discriminant` | +| `assignment/ref` | `Rvalue::Ref` | +| `assignment/repeat_array` | `Rvalue::Repeat` | +| `assignment/set_discr` | `StatementKind::SetDiscriminant` | +| `assignment/thread_local_ref` | `Rvalue::ThreadLocalRef` | +| `assignment/un_op` | `Rvalue::UnaryOp` | +| `branching/assert` | `TerminatorKind::Assert` | +| `branching/if_basic` | `TerminatorKind::SwitchInt`, `if` | +| `branching/if_else` | `TerminatorKind::SwitchInt`, `if`, `else if`, `else` | +| `branching/if_let` | `TerminatorKind::SwitchInt`, `if let` | +| `branching/match_basic` | `TerminatorKind::SwitchInt`, `match` | +| `branching/match_enum` | `TerminatorKind::SwitchInt`, `Rvalue::Discriminant`, `match ` | +| `casting/numeric` | `Rvalue::Cast`, `CastKind::IntTo*`, `CastKind::FloatTo*` | +| `casting/pointer` | `Rvalue::Cast`, `CastKind::PtrToPtr`, `CastKind::PointerCoercion`, `PointerCoercion::*` | +| `casting/subtype` | `Rvalue::Cast`, `CastKind::Subtype` | +| `casting/transmute` | `Rvalue::Cast`, `CastKind::Transmute` | +| `drop` | `TerminatorKind::Drop`, `intrinsics::drop_glue` | +| `function/async` | Async Functions, `TyKind::CoroutineClosure` | +| `function/call_basic` | `TerminatorKind::Call` | +| `function/closures` | `TyKind::Closure`, `Fn*` traits, tupling/untupling arguments | +| `function/coroutines` | `TyKind::Coroutine` | +| `function/shims` | `ShimKind` | +| `intrinsics/atomic` | `intrinsics::atomic_*` | +| `intrinsics/memory` | (Raw) Memory-related intrinsics | +| `intrinsics/operators` | Intrinsic (arithmetic) operators | +| `misc/intrinsics` | Misc intrinsic usage | +| `misc/leaf_attr` | Using Leaf-specific attributes, `#[leaf_attr::instrument]` | +| `misc/no_diverge` | Pushing/popping tags | +| `misc/promoted` | Promoted bodies | +| `misc/static` | Static items and accesses | +| `place/deref_mut` | Dereferencing mutable references | +| `place/projection/downcast` | `PlaceElem::Downcast` | +| `place/projection/field` | `PlaceElem::Field` | +| `place/projection/index` | `PlaceElem::Index` | +| `place/projection/unwrap_unsafe_binder` | `PlaceElem::UnwrapUnsafeBinder` | +||| +| `sym_place/read` | #SymEx Reading symbolic places | +| `sym_place/write` | #SymEx Writing to symbolic places | +| `function/sym_*` | #SymEx Symbolic values transferred between functions | +| `basic` | Basic algorithm implementations | +| `crates/multi_file_bin` | Multi-file crate compilation | +| `crates/single_file_bin` | Single-file crate baseline | +| `crates/with_dep` | Crate with dependencies | +| `crates/with_shared_dep` | Crate with shared transitive dependency | \ No newline at end of file diff --git a/docs/book/src/user_guide/getting_started.md b/docs/book/src/tutorials/first_dynamic_analysis.md similarity index 98% rename from docs/book/src/user_guide/getting_started.md rename to docs/book/src/tutorials/first_dynamic_analysis.md index 80fe876d..a39394ca 100644 --- a/docs/book/src/user_guide/getting_started.md +++ b/docs/book/src/tutorials/first_dynamic_analysis.md @@ -1,4 +1,4 @@ -# Getting Started +# First Dynamic Analysis with Leaf Leaf is a Rust-oriented framework for dynamic analysis built around MIR instrumentation. The workflow is: diff --git a/docs/book/src/user_guide/configs.md b/docs/book/src/user_guide/configs.md deleted file mode 100644 index 4f422400..00000000 --- a/docs/book/src/user_guide/configs.md +++ /dev/null @@ -1,3 +0,0 @@ -# Configurations - -(TBD) \ No newline at end of file diff --git a/docs/book/src/user_guide/recipes/section.md b/docs/book/src/user_guide/recipes/section.md deleted file mode 100644 index 62b1c99f..00000000 --- a/docs/book/src/user_guide/recipes/section.md +++ /dev/null @@ -1,3 +0,0 @@ -# Recipes - -(TBD) \ No newline at end of file diff --git a/samples/.test_compile_rs.ignore b/samples/.test_compile_rs.ignore index a4f3873b..7f370f1a 100644 --- a/samples/.test_compile_rs.ignore +++ b/samples/.test_compile_rs.ignore @@ -1,3 +1,3 @@ crates/ -projection/unwrap_unsafe_binder/main.rs +place/projection/unwrap_unsafe_binder/main.rs playground/ \ No newline at end of file diff --git a/samples/.test_execution_rs.ignore b/samples/.test_execution_rs.ignore index 4f1c06e1..dbfd41b4 100644 --- a/samples/.test_execution_rs.ignore +++ b/samples/.test_execution_rs.ignore @@ -6,7 +6,7 @@ casting/pointer/expose/sym_ptr_to_usize.rs casting/transmute/sym_partial_mid.rs function/async/basic.rs function/coroutines/basic.rs -projection/unwrap_unsafe_binder/main.rs +place/projection/unwrap_unsafe_binder/main.rs sym_place/read/addr_of.rs sym_place/read/agg_raw_ptr.rs sym_place/read/deref_ptr.rs diff --git a/samples/casting/numeric/discr/main.rs b/samples/assignment/casting/numeric/discr/main.rs similarity index 100% rename from samples/casting/numeric/discr/main.rs rename to samples/assignment/casting/numeric/discr/main.rs diff --git a/samples/casting/numeric/floats/main.rs b/samples/assignment/casting/numeric/floats/main.rs similarity index 100% rename from samples/casting/numeric/floats/main.rs rename to samples/assignment/casting/numeric/floats/main.rs diff --git a/samples/casting/numeric/int_only/main.rs b/samples/assignment/casting/numeric/int_only/main.rs similarity index 100% rename from samples/casting/numeric/int_only/main.rs rename to samples/assignment/casting/numeric/int_only/main.rs diff --git a/samples/casting/numeric/int_only/multi.rs b/samples/assignment/casting/numeric/int_only/multi.rs similarity index 100% rename from samples/casting/numeric/int_only/multi.rs rename to samples/assignment/casting/numeric/int_only/multi.rs diff --git a/samples/casting/numeric/int_only/sym_main.rs b/samples/assignment/casting/numeric/int_only/sym_main.rs similarity index 100% rename from samples/casting/numeric/int_only/sym_main.rs rename to samples/assignment/casting/numeric/int_only/sym_main.rs diff --git a/samples/casting/pointer/expose/ptr_to_usize.rs b/samples/assignment/casting/pointer/expose/ptr_to_usize.rs similarity index 100% rename from samples/casting/pointer/expose/ptr_to_usize.rs rename to samples/assignment/casting/pointer/expose/ptr_to_usize.rs diff --git a/samples/casting/pointer/expose/sym_ptr_to_usize.rs b/samples/assignment/casting/pointer/expose/sym_ptr_to_usize.rs similarity index 100% rename from samples/casting/pointer/expose/sym_ptr_to_usize.rs rename to samples/assignment/casting/pointer/expose/sym_ptr_to_usize.rs diff --git a/samples/casting/pointer/to_pointer/fn_ptr_to_another_ptr.rs b/samples/assignment/casting/pointer/to_pointer/fn_ptr_to_another_ptr.rs similarity index 100% rename from samples/casting/pointer/to_pointer/fn_ptr_to_another_ptr.rs rename to samples/assignment/casting/pointer/to_pointer/fn_ptr_to_another_ptr.rs diff --git a/samples/casting/pointer/to_pointer/mut_to_const_ptr.rs b/samples/assignment/casting/pointer/to_pointer/mut_to_const_ptr.rs similarity index 100% rename from samples/casting/pointer/to_pointer/mut_to_const_ptr.rs rename to samples/assignment/casting/pointer/to_pointer/mut_to_const_ptr.rs diff --git a/samples/casting/pointer/to_pointer/ptr_to_another_ptr.rs b/samples/assignment/casting/pointer/to_pointer/ptr_to_another_ptr.rs similarity index 100% rename from samples/casting/pointer/to_pointer/ptr_to_another_ptr.rs rename to samples/assignment/casting/pointer/to_pointer/ptr_to_another_ptr.rs diff --git a/samples/casting/pointer/to_pointer/usize_to_ptr.rs b/samples/assignment/casting/pointer/to_pointer/usize_to_ptr.rs similarity index 100% rename from samples/casting/pointer/to_pointer/usize_to_ptr.rs rename to samples/assignment/casting/pointer/to_pointer/usize_to_ptr.rs diff --git a/samples/casting/pointer/unsize_dyn/main.rs b/samples/assignment/casting/pointer/unsize_dyn/main.rs similarity index 100% rename from samples/casting/pointer/unsize_dyn/main.rs rename to samples/assignment/casting/pointer/unsize_dyn/main.rs diff --git a/samples/casting/pointer/unsize_slice/main.rs b/samples/assignment/casting/pointer/unsize_slice/main.rs similarity index 100% rename from samples/casting/pointer/unsize_slice/main.rs rename to samples/assignment/casting/pointer/unsize_slice/main.rs diff --git a/samples/casting/subtype/main.rs b/samples/assignment/casting/subtype/main.rs similarity index 100% rename from samples/casting/subtype/main.rs rename to samples/assignment/casting/subtype/main.rs diff --git a/samples/casting/transmute/basic.rs b/samples/assignment/casting/transmute/basic.rs similarity index 100% rename from samples/casting/transmute/basic.rs rename to samples/assignment/casting/transmute/basic.rs diff --git a/samples/casting/transmute/sym_partial.rs b/samples/assignment/casting/transmute/sym_partial.rs similarity index 100% rename from samples/casting/transmute/sym_partial.rs rename to samples/assignment/casting/transmute/sym_partial.rs diff --git a/samples/casting/transmute/sym_partial_mid.rs b/samples/assignment/casting/transmute/sym_partial_mid.rs similarity index 100% rename from samples/casting/transmute/sym_partial_mid.rs rename to samples/assignment/casting/transmute/sym_partial_mid.rs diff --git a/samples/assignment/shallow_init_box/main.rs b/samples/assignment/shallow_init_box/main.rs deleted file mode 100644 index febb58ba..00000000 --- a/samples/assignment/shallow_init_box/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - let a = vec![1, 2, 3]; -} \ No newline at end of file diff --git a/samples/branching/match_enum/range.rs b/samples/branching/match_enum/range.rs new file mode 100644 index 00000000..3de231a9 --- /dev/null +++ b/samples/branching/match_enum/range.rs @@ -0,0 +1,14 @@ +fn main() { + let x = core::hint::black_box(10u8); + #[cfg(leafc)] + let x: u8 = { + use leaf::annotations::*; + x.mark_symbolic() + }; + + match x % 5 { + 1..=2 => core::hint::black_box(()), + 3..=4 => core::hint::black_box(()), + _ => core::hint::black_box(()), + } +} diff --git a/samples/branching/match_range/main.rs b/samples/branching/match_range/main.rs deleted file mode 100644 index a5a3a5bb..00000000 --- a/samples/branching/match_range/main.rs +++ /dev/null @@ -1,13 +0,0 @@ -use leaf::annotations::*; - -fn main() { - let x = 10u8.mark_symbolic(); - - match x % 5 { - 1..=3 => foo(), - 4 => foo(), - _ => foo(), - } -} - -fn foo() {} diff --git a/samples/assignment/const/addr.rs b/samples/const/addr.rs similarity index 100% rename from samples/assignment/const/addr.rs rename to samples/const/addr.rs diff --git a/samples/assignment/const/blocks.rs b/samples/const/blocks.rs similarity index 81% rename from samples/assignment/const/blocks.rs rename to samples/const/blocks.rs index 594ff591..0be5a400 100644 --- a/samples/assignment/const/blocks.rs +++ b/samples/const/blocks.rs @@ -44,11 +44,10 @@ impl TestTrait for TestStruct { const TRAIT_TEST: &dyn TestTrait = &TestStruct { a: 10 }; -// Not supported yet -// const UNION_TEST: TestUnion = TestUnion { a: 10 }; -// const CLOSURE_TEST: fn(i32) -> i32 = |x| x + 1; -// const RAW_PTR_TEST: *const i32 = &2 as *const i32; -// const FN_PTR_TEST: fn() -> () = foo; +const UNION_TEST: TestUnion = TestUnion { a: 10 }; +const CLOSURE_TEST: fn(i32) -> i32 = |x| x + 1; +const RAW_PTR_TEST: *const i32 = &2 as *const i32; +const FN_PTR_TEST: fn() -> () = foo; fn main() { use_item(BOOL_TEST); @@ -80,11 +79,10 @@ fn main() { let const_param = bar::<20>(); use_item(const_param); - // Not supported yet - // use_item(UNION_TEST); - // use_item(CLOSURE_TEST); - // use_item(RAW_PTR_TEST); - // use_item(FN_PTR_TEST); + use_item(UNION_TEST); + use_item(CLOSURE_TEST); + use_item(RAW_PTR_TEST); + use_item(FN_PTR_TEST); } fn use_item(x: T) -> () { diff --git a/samples/assignment/const/main.rs b/samples/const/main.rs similarity index 100% rename from samples/assignment/const/main.rs rename to samples/const/main.rs diff --git a/samples/assignment/const/zst.rs b/samples/const/zst.rs similarity index 100% rename from samples/assignment/const/zst.rs rename to samples/const/zst.rs diff --git a/samples/crates/sym_basic_bin/Cargo.lock b/samples/crates/sym_basic_bin/Cargo.lock deleted file mode 100644 index 16203e21..00000000 --- a/samples/crates/sym_basic_bin/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "sym_basic_bin" -version = "0.1.0" diff --git a/samples/crates/sym_basic_bin/Cargo.toml b/samples/crates/sym_basic_bin/Cargo.toml deleted file mode 100644 index b2beb989..00000000 --- a/samples/crates/sym_basic_bin/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "sym_basic_bin" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/samples/crates/sym_basic_bin/src/child.rs b/samples/crates/sym_basic_bin/src/child.rs deleted file mode 100644 index 3c49f43d..00000000 --- a/samples/crates/sym_basic_bin/src/child.rs +++ /dev/null @@ -1,9 +0,0 @@ -use super::*; - -pub fn calc(x: i32, y: i32) { - if x < y { - foo(); - } else { - bar(); - } -} diff --git a/samples/crates/sym_basic_bin/src/main.rs b/samples/crates/sym_basic_bin/src/main.rs deleted file mode 100644 index 73c7aad9..00000000 --- a/samples/crates/sym_basic_bin/src/main.rs +++ /dev/null @@ -1,14 +0,0 @@ -use leaf::annotations::Symbolizable; - -mod child; - -use child::calc; - -fn main() { - let x = 0.mark_symbolic(); - calc(x, 5); -} - -fn foo() {} - -fn bar() {} diff --git a/samples/drop/drop_in_place.rs b/samples/drop/drop_glue.rs similarity index 100% rename from samples/drop/drop_in_place.rs rename to samples/drop/drop_glue.rs diff --git a/samples/function/return_unit/main.rs b/samples/function/call_basic/return_unit.rs similarity index 96% rename from samples/function/return_unit/main.rs rename to samples/function/call_basic/return_unit.rs index e2af6cba..3249052d 100644 --- a/samples/function/return_unit/main.rs +++ b/samples/function/call_basic/return_unit.rs @@ -4,4 +4,4 @@ fn main() { fn foo() -> () { // Nothing -} \ No newline at end of file +} diff --git a/samples/function/println/hello_world.rs b/samples/function/println/hello_world.rs deleted file mode 100644 index 2fe40a14..00000000 --- a/samples/function/println/hello_world.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("hello world!"); -} diff --git a/samples/hello_world.rs b/samples/hello_world.rs index 17940788..a5970bbc 100644 --- a/samples/hello_world.rs +++ b/samples/hello_world.rs @@ -1,5 +1,5 @@ fn main() { - let x: u8 = core::hint::black_box(10); + let x: u8 = core::hint::black_box(2); #[cfg(leafc)] let x: u8 = { use leaf::annotations::*; diff --git a/samples/misc/constant_folding/main.rs b/samples/misc/constant_folding/main.rs deleted file mode 100644 index 1f940d84..00000000 --- a/samples/misc/constant_folding/main.rs +++ /dev/null @@ -1,15 +0,0 @@ -use leaf::annotations::Symbolizable; - -fn main() { - let x = 0_u8.mark_symbolic(); - - if (x + 5) + 10 < 100 { - do_something(); - } else { - do_something_else(); - } -} - -fn do_something() {} - -fn do_something_else() {} diff --git a/samples/misc/external_fn/hash.rs b/samples/misc/external_fn/hash.rs deleted file mode 100644 index a70fcdd8..00000000 --- a/samples/misc/external_fn/hash.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; - -#[derive(Hash)] -struct AA { - a: u32, - b: u64, -} - -// from: https://doc.rust-lang.org/std/hash/index.html -fn main() { - let aa = AA { a: 0, b: 20 }; - - let mut s = DefaultHasher::new(); - aa.hash(&mut s); - let res: u64 = s.finish(); -} diff --git a/samples/misc/intrinsics/call_intrinsic.rs b/samples/misc/intrinsics/call_intrinsic.rs deleted file mode 100644 index f16c0e2c..00000000 --- a/samples/misc/intrinsics/call_intrinsic.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![feature(core_intrinsics)] -/* NOTE: Normal programs are not expected to call intrinsics directly. - * However, this sample can imitate what happens in the core library. */ -fn main() { - let a = get_num(5); - if core::intrinsics::bitreverse(a) == a { - foo(); - } -} - -fn get_num(x: u8) -> u8 { - x + 5 -} - -#[inline(never)] -fn foo() {} diff --git a/samples/misc/multiple_vars/main.rs b/samples/misc/multiple_vars/main.rs deleted file mode 100644 index 8df70978..00000000 --- a/samples/misc/multiple_vars/main.rs +++ /dev/null @@ -1,19 +0,0 @@ -use leaf::annotations::Symbolizable; - -fn main() { - let x = 10.mark_symbolic(); - let a = calc(x, 5); - let y = 20.mark_symbolic(); - let b = calc(y, 3); - - // Using bitwise AND instead of logical AND to make them appear in one branch. - if (a == 15) & (b == 25) { - foo(); - } -} - -fn calc(x: u32, y: u32) -> u32 { - x + y -} - -fn foo() {} diff --git a/samples/misc/sym_field/main.rs b/samples/misc/sym_field/main.rs deleted file mode 100644 index 26f8a92d..00000000 --- a/samples/misc/sym_field/main.rs +++ /dev/null @@ -1,31 +0,0 @@ -use leaf::annotations::Symbolizable; - -fn main() { - let foo = Foo { - x: 10.mark_symbolic(), - y: 2.mark_symbolic(), - }; - if (foo.x < 10) & (foo.x > 5) { - error(); - } - let bar = Bar { foo, z: 3 }; - check_bar(bar); -} - -fn check_bar(bar: Bar) { - if bar.foo.x < bar.z { - error(); - } -} - -fn error() {} - -struct Foo { - x: u32, - y: u32, -} - -struct Bar { - foo: Foo, - z: u32, -} diff --git a/samples/misc/type_export/mono.rs b/samples/misc/type_export/mono.rs deleted file mode 100644 index c578e591..00000000 --- a/samples/misc/type_export/mono.rs +++ /dev/null @@ -1,9 +0,0 @@ -fn main() { - tupler(1_u16, 2_i16); // expect "(u16, i16)" in type export -} - -fn tupler(a: T1, b: T2) { - foo((a, b)); -} - -fn foo(x: T) {} diff --git a/samples/projection/downcast/enum/main.rs b/samples/place/projection/downcast/enum/main.rs similarity index 100% rename from samples/projection/downcast/enum/main.rs rename to samples/place/projection/downcast/enum/main.rs diff --git a/samples/projection/field/main.rs b/samples/place/projection/field/main.rs similarity index 100% rename from samples/projection/field/main.rs rename to samples/place/projection/field/main.rs diff --git a/samples/projection/index/main.rs b/samples/place/projection/index/main.rs similarity index 100% rename from samples/projection/index/main.rs rename to samples/place/projection/index/main.rs diff --git a/samples/projection/unwrap_unsafe_binder/main.rs b/samples/place/projection/unwrap_unsafe_binder/main.rs similarity index 100% rename from samples/projection/unwrap_unsafe_binder/main.rs rename to samples/place/projection/unwrap_unsafe_binder/main.rs From 32006b534fc0a71b36b516822331bb5d8aa4d958 Mon Sep 17 00:00:00 2001 From: Mohammad Omidvar Date: Wed, 29 Jul 2026 09:13:51 -0700 Subject: [PATCH 3/9] Add logo --- README.md | 1 + docs/book/src/assets/LeafLogo.svg | 169 ++++++++++++++++++++++++++++++ docs/book/src/leaf.md | 2 +- 3 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 docs/book/src/assets/LeafLogo.svg diff --git a/README.md b/README.md index 7d57ce78..29faf1ad 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Leaf +The Leaf logo Leaf is a Rust-oriented framework for dynamic analysis built around MIR instrumentation. It wraps the Rust compiler through `leafc`, instruments a program at compile time, and routes runtime events to pluggable backends for tracing, symbolic execution, and related analyses. diff --git a/docs/book/src/assets/LeafLogo.svg b/docs/book/src/assets/LeafLogo.svg new file mode 100644 index 00000000..d2e4aa39 --- /dev/null +++ b/docs/book/src/assets/LeafLogo.svg @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/book/src/leaf.md b/docs/book/src/leaf.md index e943ae23..32307136 100644 --- a/docs/book/src/leaf.md +++ b/docs/book/src/leaf.md @@ -1,5 +1,5 @@ # Leaf -Concolic execution for Rust through MIR instrumentation. +The Leaf logo Welcome to project Leaf, a tool to dynamic analysis for Rust programs by MIR instrumentation. From 5ffeba2036d45dbc1ba5d8b32e2e8e5cf94ac177 Mon Sep 17 00:00:00 2001 From: Mohammad Omidvar Date: Wed, 29 Jul 2026 16:51:05 -0700 Subject: [PATCH 4/9] Add note on agents for docs --- .gitignore | 1 - README.md | 4 ++-- docs/.gitignore | 2 ++ docs/README.md | 11 +++++++++++ 4 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 docs/.gitignore create mode 100644 docs/README.md diff --git a/.gitignore b/.gitignore index c81037f9..342f71c6 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,6 @@ samples/**/main # Performance test results *.bench *.data -*.svg scripts/**/out scripts/**/work diff --git a/README.md b/README.md index 29faf1ad..8b4087c2 100644 --- a/README.md +++ b/README.md @@ -50,8 +50,8 @@ The generated program will emit runtime events through the active backend, which ## Documentation -Further information, tutorials, and technical details are collected in Leaf Book hosted at: -[sfu-rsl.github.io/leaf](https://sfu-rsl.github.io/leaf). +Further information, tutorials, and technical details are collected in Leaf Book @ +[sfu-rsl.github.io/leaf](https://sfu-rsl.github.io/leaf) (WIP). ## License diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..3ca90839 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +.agents +.github/agents diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..6650a9f2 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,11 @@ +# Leaf Documentation + +## Note on Agents +We encourage using agents for writing the documentation. However, as there is not a consensus over how to have them in a repo, we currently do not commit them. + +The following agents and skills are recommended to be installed. +- `sfu-rsl/leaf_dev_plugin_marketplace/agents/leaf-book-maintainer.agent.md` +- `sfu-rsl/leaf_dev_plugin_marketplace/skills/framework-digestion` +- `sfu-rsl/leaf_dev_plugin_marketplace/skills/sfu-rsl-leaf-main-en-skill` +- `awesome-copilot/documentation-writer` +- `anthropics/skills/doc-coauthoring` \ No newline at end of file From c0403c63b1cee9afe808fb212cc043329e86df69 Mon Sep 17 00:00:00 2001 From: Mohammad Omidvar Date: Tue, 4 Aug 2026 17:04:27 -0700 Subject: [PATCH 5/9] Add tutorial for writing a backend --- docs/book/book.toml | 3 + docs/book/src/SUMMARY.md | 27 +- docs/book/src/reference/dmir-callback.md | 1 + docs/book/src/tutorials/write_a_backend.md | 544 +++++++++++++++++++++ 4 files changed, 562 insertions(+), 13 deletions(-) create mode 100644 docs/book/src/reference/dmir-callback.md create mode 100644 docs/book/src/tutorials/write_a_backend.md diff --git a/docs/book/book.toml b/docs/book/book.toml index b42cb922..24e87d5e 100644 --- a/docs/book/book.toml +++ b/docs/book/book.toml @@ -5,3 +5,6 @@ title = "Leaf Book" [output.html] git-repository-url = "https://github.com/sfu-rsl/leaf" + +[output.html.playground] +runnable = false diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md index 8d9b8774..c9a152d1 100644 --- a/docs/book/src/SUMMARY.md +++ b/docs/book/src/SUMMARY.md @@ -6,6 +6,7 @@ # Tutorials - [First Dynamic Analysis with Leaf](./tutorials/first_dynamic_analysis.md) +- [Write a Counter Backend](./tutorials/write_a_backend.md) # How-to Guides @@ -15,20 +16,20 @@ # Explanations -- [Leaf's execution model](./explanations/execution-model.md) -- [MIR, runtime facts, and DMIR](./explanations/dmir.md) -- [How MIR instrumentation becomes runtime events](./explanations/instrumentation.md) -- [How analysis callbacks receive and transform events](./explanations/callbacks.md) -- [Runtime support utilities and shared services](./explanations/runtime-support.md) -- [Dependency instrumentation and build strategy](./explanations/dependencies.md) +- [Leaf's execution model]() +- [MIR, runtime facts, and DMIR]() +- [How MIR instrumentation becomes runtime events]() +- [How analysis callbacks receive and transform events]() +- [Runtime support utilities and shared services]() +- [Dependency instrumentation and build strategy]() # Reference -- [Compiler](./reference/compiler.md) -- [Runtime](./reference/runtime.md) -- [Orchestrator](./reference/orchestrator.md) -- [DMIR](./reference/dmir-callback.md) -- [Configuration](./reference/configuration.md) -- [MIR Intrinsics](./reference/intrinsics.md) +- [Compiler]() +- [Runtime]() +- [Orchestrator]() +- [DMIR]() +- [Configuration]() +- [MIR Intrinsics]() - [Samples](./reference/samples.md) -- [Glossary](./reference/glossary.md) \ No newline at end of file +- [Glossary]() \ No newline at end of file diff --git a/docs/book/src/reference/dmir-callback.md b/docs/book/src/reference/dmir-callback.md new file mode 100644 index 00000000..23be7b08 --- /dev/null +++ b/docs/book/src/reference/dmir-callback.md @@ -0,0 +1 @@ +# DMIR diff --git a/docs/book/src/tutorials/write_a_backend.md b/docs/book/src/tutorials/write_a_backend.md new file mode 100644 index 00000000..4a5d751d --- /dev/null +++ b/docs/book/src/tutorials/write_a_backend.md @@ -0,0 +1,544 @@ +# Write a Counter Backend + +This tutorial shows how to write a minimal Leaf backend and plug it into an instrumented program. + +## Goal + +You will build a backend that counts assignments and run it through a Leaf-instrumented target. + +The backend reports: + +- total assignments +- unary assignment counts by `UnaryOp` +- binary assignment counts by `BinaryOp` + +You will do this in five steps: + +1. Define a minimal `RuntimeBackend`. +2. Define an `AssignmentHandler` that performs counting. +3. Add an `InstanceManager` for the backend. +4. Package the backend as a dynamic library. +5. Run an instrumented sample and verify the output. + +## Backend Design + +Before starting the implementation, it helps to identify the core pieces of the backend. + +The backend reports the total number of assignments, plus binary and unary assignment counts grouped by operation. + +### State + +The information that a backend maintains about the running program is its *state*. +For this backend, the state looks like this: + +```rust +# use std::collections::HashMap; +# use leaf_runtime::abs::{BinaryOp, UnaryOp}; +# +# #[derive(Default)] +struct AssignStats { + total_assignments: u64, + binary_by_op: HashMap, + unary_by_op: HashMap, +} +``` + + +### Updating the State + +We need to: + +- increment `total_assignments` for every assignment +- increment the appropriate map for every unary or binary assignment + +This behavior can be represented as follows: + +```rust +struct Counter<'a> { + stats: &'a mut AssignStats, +} + +impl Counter<'_> { + fn binary_op_between(mut self, op: BinaryOp) { + *self.stats.binary_by_op.entry(op).or_default() += 1; + self.some() + } + + fn unary_op_on(mut self, op: UnaryOp) { + *self.stats.unary_by_op.entry(op).or_default() += 1; + self.some() + } + + fn some(mut self) { + self.stats.total_assignments += 1; + } +} +``` + +This is the core functionality that the backend must provide. Next, we map it to Leaf's backend model. + +## Realizing the Backend + +### Step 1: Define a Backend + +> [!TIP] +> Boilerplate code is hidden by default, which can be displayed by clicking on the eyeball () button. + +An implementation of a backend follows the contract defined by `RuntimeBackend`. +Define the backend as follows: +```rust +# use leaf_runtime::{ +# abs::{AssignmentId, BasicBlockIndex, BinaryOp, PlaceUsage, UnaryOp, backend::Shutdown}, +# pri::fluent::backend::{RuntimeBackend, shared::noop::*}, +# }; +# +struct CounterBackend { + // Components that will live during the execution. +} + +impl RuntimeBackend for CounterBackend { + // TODO +# type PlaceHandler<'a> = /* ... */ +# where +# Self: 'a; +# +# type OperandHandler<'a> = /* ... */ +# where +# Self: 'a; +# +# type AssignmentHandler<'a> = /* ... */ +# where +# Self: 'a; +# +# type MemoryHandler<'a> = /* ... */ +# where +# Self: 'a; +# +# type RawMemoryHandler<'a> = /* ... */ +# where +# Self: 'a; +# +# type ConstraintHandler<'a> = /* ... */ +# where +# Self: 'a; +# +# type CallHandler<'a> = /* ... */ +# where +# Self: 'a; +# +# type DropHandler<'a> = /* ... */ +# where +# Self: 'a; +# +# type AnnotationHandler<'a> = /* ... */ +# where +# Self: 'a; +# +# type PlaceInfo = /* ... */; +# type Place = /* ... */; +# type DiscriminablePlace = /* ... */; +# type Operand = /* ... */; +# +# fn place<'a>(&'a mut self, _usage: PlaceUsage) -> Self::PlaceHandler<'a> { +# Default::default() +# } +# +# fn operand<'a>(&'a mut self) -> Self::OperandHandler<'a> { +# Default::default() +# } +# +# fn assign_to<'a>( +# &'a mut self, _id: AssignmentId, _dest: Self::Place, +# ) -> Self::AssignmentHandler<'a> { +# Default::default() +# } +# +# fn memory<'a>(&'a mut self) -> Self::MemoryHandler<'a> { +# Default::default() +# } +# +# fn raw_memory<'a>(&'a mut self) -> Self::RawMemoryHandler<'a> { +# Default::default() +# } +# +# fn constraint_at<'a>(&'a mut self, _loc: BasicBlockIndex) -> Self::ConstraintHandler<'a> { +# Default::default() +# } +# +# fn call_control<'a>(&'a mut self) -> Self::CallHandler<'a> { +# Default::default() +# } +# +# fn dropping<'a>(&'a mut self) -> Self::DropHandler<'a> { +# Default::default() +# } +# +# fn annotate<'a>(&'a mut self) -> Self::AnnotationHandler<'a> { +# Default::default() +# } +} +``` + +MIR contains several kinds of events that a backend can handle. This backend only needs assignments, so no-op definitions are sufficient for the other event types. + + +```rust +type PlaceHandler<'a> + = NoOpPlaceHandler +where + Self: 'a; + +// Similar for other associated types +# type OperandHandler<'a> +# = NoOpOperandHandler +# where +# Self: 'a; +# +# type AssignmentHandler<'a> +# = NoOpAssignmentHandler +# where +# Self: 'a; +# +# type MemoryHandler<'a> +# = NoOpLifetimeHandler +# where +# Self: 'a; +# +# type RawMemoryHandler<'a> +# = NoOpRawMemoryHandler +# where +# Self: 'a; +# +# type ConstraintHandler<'a> +# = NoOpConstraintHandler +# where +# Self: 'a; +# +# type CallHandler<'a> +# = NoOpCallHandler +# where +# Self: 'a; +# +# type DropHandler<'a> +# = NoOpDropHandler +# where +# Self: 'a; +# +# type AnnotationHandler<'a> +# = NoOpAnnotationHandler +# where +# Self: 'a; +# +# +# type PlaceInfo = NullPlaceInfo; +# type Place = NullPlace; +# type DiscriminablePlace = NullPlace; +# +# type Operand = NullOperand; +``` + +> [!NOTE] +> We explain each element elsewhere in the book. To keep this tutorial focused, treat these associated types and methods as holes filled by no-op definitions. + +The backend instance owns the state, so add it: + +```rust +#[derive(Default)] +struct CounterBackend { + stats: AssignStats, +} +``` + +Print the statistics when the runtime shuts down: + +```rust +impl Shutdown for CounterBackend { + fn shutdown(&mut self) { + println!( + "total assignments: {}\nbinary: {:?}\nunary: {:?}", + self.stats.total_assignments, self.stats.binary_by_op, self.stats.unary_by_op, + ); + } +} +``` + + +### Step 2: Handling Assignments + +Now we add the counting behavior shown earlier as an implementation for `AssignmentHandler`. + +```rust +struct CounterAssignmentHandler<'a> { + stats: &'a mut AssignStats, +} + +impl AssignmentHandler for CounterAssignmentHandler<'_> { + type Place = NullPlace; + type Operand = NullOperand; + + fn binary_op_between(self, op: BinaryOp, _a: Self::Operand, _b: Self::Operand) { + *self.stats.binary_by_op.entry(op).or_default() += 1; + self.some() + } + + fn unary_op_on(self, op: UnaryOp, _operand: Self::Operand) { + *self.stats.unary_by_op.entry(op).or_default() += 1; + self.some() + } + + // Catch-all for other assignment forms. + fn some(self) { + self.stats.total_assignments += 1; + } +} +``` + +Then installing it in the backend: + +```rust +impl RuntimeBackend for CounterBackend { + type AssignmentHandler<'a> + = CounterAssignmentHandler<'a> + where + Self: 'a; + + fn assign_to<'a>( + &'a mut self, + _id: AssignmentId, + _dest: Self::Place, + ) -> CounterAssignmentHandler<'a> { + CounterAssignmentHandler { stats: &mut self.stats } + } +} +``` + +> A few details are worth mentioning, although they are not specific to this backend: +> * The traits to implement for a backend and its handlers are defined for working in `FluentPri`. +> * The parameters given in an interface call chain provide the representation of the pieces in the original MIR event. For instance, in an assignment based on a unary operation like `_5 = Neg(move _4);`, a call chain of `assign_to(ID_X, p_dest).unary_op_on(UnaryOp::Neg, p_operand)` is expected where `ID_X` corresponds to the unique id for this assignment in its parent body, `p_dest` and `p_operand` correspond to place representations for locals `_5` and `_4`. +> * Handler components of a backend are designed to be short-lived instances that provide the expected interface. All durable information (e.g., program state) should be owned by the backend itself and borrowed by the handlers. + +## Step 3: Add an `InstanceManager` and `Pri` + +### `InstanceManager` +An instance manager constructs, provides access to, and destroys backend instances for probes. Probes can run at any point during execution and in any function in the program. In many cases, a simple instance manager that wraps a globally allocated backend instance suffices. + +This tutorial does not explain the lower-level details of this trait. Use the following implementation as a template, and see the rest of the book for details. + +```rust +mod instance { + use std::sync::{Mutex, Once}; + + use leaf_runtime::pri::{fluent::InstanceManager, refs::NoOpRefManager}; + + use super::*; + + static BACKEND: Mutex> = Mutex::new(None); + static mut PLACE_REF_MANAGER: NoOpRefManager = NoOpRefManager::new(()); + static mut OPERAND_REF_MANAGER: NoOpRefManager = NoOpRefManager::new(()); + + static INIT: Once = Once::new(); + + pub(crate) struct CounterInstanceManager; + + impl InstanceManager for CounterInstanceManager { + type PlaceInfo = NullPlace; + type Place = NullPlace; + type Operand = NullOperand; + + type Backend = CounterBackend; + + type PlaceBuilder = NoOpPlaceBuilder; + + type PlaceRefManager = NoOpRefManager; + + type OperandRefManager = NoOpRefManager; + + fn init() { + INIT.call_once(|| { + let mut guard = BACKEND.lock().unwrap(); + let backend = CounterBackend::default(); + *guard = Some(backend); + }); + } + + fn deinit() {} + + fn perform_on_backend(action: impl for<'a> FnOnce(&'a mut Self::Backend) -> T) -> T { + let mut guard = BACKEND.lock().unwrap(); + let backend = guard.as_mut().expect("Runtime is not initialized."); + action(backend) + } + + #[allow(static_mut_refs)] + fn perform_on_place_ref_manager( + action: impl FnOnce(&mut Self::PlaceRefManager) -> T, + ) -> T { + action(unsafe { &mut PLACE_REF_MANAGER }) + } + + #[allow(static_mut_refs)] + fn perform_on_operand_ref_manager( + action: impl FnOnce(&mut Self::OperandRefManager) -> T, + ) -> T { + action(unsafe { &mut OPERAND_REF_MANAGER }) + } + } +} +``` + +### Exporting a PRI + +The final step in the backend crate is to define the PRI implementation that the flavor exports through Leaf's C ABI. + +```rust +pub mod interface { + use leaf_runtime::pri::fluent::FluentPri; + + type CounterPri = FluentPri; + + leaf_runtime::make_late_init_pri_of!(CounterPri); + + pub type DefaultPri = CounterPriLateInit; +} +``` + +## Step 4: Package as a Dynamic Library + +To publish the backend as `libleafrt.so`, which can be loaded by an instrumented program, define a *flavor* as a separate crate and use the project template. + +For this tutorial, copy an existing flavor under `runtime/flavors` and point its `backend` dependency at the backend crate in `Cargo.toml`. + +```toml +[package] +name = "runtime_counter" +license = { workspace = true } +version = { workspace = true } +edition = "2021" + +[lib] +name = "leafrt_counter" +crate-type = ["cdylib"] + +[dependencies] +common = { workspace = true } +backend = { path = "../../backends/counter", package = "runtime_backend_counter" } +``` + +The flavor's `build.rs` sets the shared library's SONAME to `libleafrt.so`, and its `src/lib.rs` exports the backend through the common FFI template: + +```rust +include!("../shared_build.rs"); + +fn main() { + set_so_name(); +} +``` + +```rust +type PriImpl = backend::interface::DefaultPri; + +include!("../../ffi_template.rs"); +``` + +### Step 5: Run and inspect output +Build the flavor from the repository root: + +```console +$ cargo build -p runtime_counter +``` +Follow the same steps as in the [other tutorial](./first_dynamic_analysis.md) to load the dynamic library. + +Now compile and run the instrumented sample with `leafc`: + +For example, instrument the following program. +```rust +# fn main() { +let mut x: i8 = core::hint::black_box(20); + +if x < 5 { + x += 1; +} else { + x -= 1; +} +x = -x; + +core::hint::black_box(x); +# } +``` + +Save the example as `counter_sample.rs`, then run: + +```console +$ leafc counter_sample.rs +$ ./counter_sample +``` +
+MIR + +```txt +fn main() -> () { + let mut _0: (); + let mut _1: i8; + let mut _2: bool; + let mut _3: i8; + let mut _4: i8; + let _5: i8; + scope 1 { + debug x => _1; + scope 3 (inlined std::hint::black_box::) { + debug dummy => _1; + } + } + scope 2 (inlined std::hint::black_box::) { + debug dummy => const 20_i8; + } + + bb0: { + _1 = std::intrinsics::black_box::(const 20_i8) -> [return: bb4, unwind unreachable]; + } + + bb1: { + StorageDead(_3); + _1 = Add(copy _1, const 1_i8); + goto -> bb3; + } + + bb2: { + StorageDead(_3); + _1 = Sub(copy _1, const 1_i8); + goto -> bb3; + } + + bb3: { + StorageDead(_2); + StorageLive(_4); + _4 = copy _1; + _1 = Neg(move _4); + StorageDead(_4); + StorageLive(_5); + _5 = std::intrinsics::black_box::(move _1) -> [return: bb5, unwind unreachable]; + } + + bb4: { + StorageLive(_2); + StorageLive(_3); + _3 = copy _1; + _2 = Lt(move _3, const 5_i8); + switchInt(move _2) -> [0: bb2, otherwise: bb1]; + } + + bb5: { + StorageDead(_5); + return; + } +} +``` + +
+ +Then run it with the counter backend. For this input, the output is similar to: +```txt +total assignment: 7 +binary: {Lt: 1, Sub: 1} +unary: {Neg: 1} +``` \ No newline at end of file From 87dbda65f61a319af5e347e31a7800624a12ea5a Mon Sep 17 00:00:00 2001 From: Mohammad Omidvar Date: Tue, 4 Aug 2026 17:05:21 -0700 Subject: [PATCH 6/9] Add default types to no-op implementations --- runtime/backends/cf_tracer/src/lib.rs | 13 ++-- runtime/lib/src/pri/fluent/backend/shared.rs | 72 ++++++++++++++++++-- 2 files changed, 72 insertions(+), 13 deletions(-) diff --git a/runtime/backends/cf_tracer/src/lib.rs b/runtime/backends/cf_tracer/src/lib.rs index 008bf57f..9c215183 100644 --- a/runtime/backends/cf_tracer/src/lib.rs +++ b/runtime/backends/cf_tracer/src/lib.rs @@ -41,17 +41,17 @@ impl CftBackend { impl RuntimeBackend for CftBackend { type PlaceHandler<'a> - = NoOpPlaceHandler + = NoOpPlaceHandler where Self: 'a; type OperandHandler<'a> - = NoOpOperandHandler + = NoOpOperandHandler where Self: 'a; type AssignmentHandler<'a> - = NoOpAssignmentHandler + = NoOpAssignmentHandler where Self: 'a; @@ -61,7 +61,7 @@ impl RuntimeBackend for CftBackend { Self: 'a; type RawMemoryHandler<'a> - = NoOpRawMemoryHandler + = NoOpRawMemoryHandler where Self: 'a; @@ -85,7 +85,7 @@ impl RuntimeBackend for CftBackend { where Self: 'a; - type PlaceInfo = NullPlace; + type PlaceInfo = NullPlaceInfo; type Place = NullPlace; type DiscriminablePlace = NullPlace; @@ -144,6 +144,3 @@ impl RuntimeBackend for CftBackend { impl Shutdown for CftBackend { fn shutdown(&mut self) {} } - -pub(crate) type NullPlace = (); -pub(crate) type NullOperand = (); diff --git a/runtime/lib/src/pri/fluent/backend/shared.rs b/runtime/lib/src/pri/fluent/backend/shared.rs index 343c868b..b192129b 100644 --- a/runtime/lib/src/pri/fluent/backend/shared.rs +++ b/runtime/lib/src/pri/fluent/backend/shared.rs @@ -145,8 +145,12 @@ impl> PlaceMetadataHandler pub mod noop { use super::*; + pub type NullPlaceInfo = (); + pub type NullPlace = (); + pub type NullOperand = (); + #[derive(Default)] pub struct NoOpPlaceBuilder(PhantomData<(P, I)>); @@ -176,7 +180,7 @@ pub mod noop { } #[derive(Default)] - pub struct NoOpPlaceHandler(PhantomData<(PI, P)>); + pub struct NoOpPlaceHandler(PhantomData<(PI, P)>); impl PlaceHandler for NoOpPlaceHandler { type PlaceInfo<'a> = PI; @@ -192,7 +196,7 @@ pub mod noop { } #[derive(Default)] - pub struct NoOpOperandHandler(PhantomData<(P, O)>); + pub struct NoOpOperandHandler

(PhantomData<(P, O)>); impl OperandHandler for NoOpOperandHandler { type Operand = O; @@ -220,7 +224,7 @@ pub mod noop { } #[derive(Default)] - pub struct NoOpAssignmentHandler(PhantomData<(P, O)>); + pub struct NoOpAssignmentHandler

(PhantomData<(P, O)>); impl AssignmentHandler for NoOpAssignmentHandler { type Place = P; @@ -313,7 +317,7 @@ pub mod noop { } #[derive(Default)] - pub struct NoOpRawMemoryHandler(PhantomData<(P, O)>); + pub struct NoOpRawMemoryHandler

(PhantomData<(P, O)>); impl RawMemoryHandler for NoOpRawMemoryHandler { type Place = P; @@ -393,7 +397,7 @@ pub mod noop { } } - pub struct NoOpConstraintHandler(PhantomData); + pub struct NoOpConstraintHandler(PhantomData); impl Default for NoOpConstraintHandler { fn default() -> Self { @@ -433,6 +437,64 @@ pub mod noop { } } + #[derive(Default)] + pub struct NoOpCallHandler

(PhantomData<(P, O)>); + + impl CallHandler for NoOpCallHandler { + type Place = P; + type Operand = O; + + fn before_call(self, _def: CalleeDef, _call_site: BasicBlockIndex) {} + + fn before_call_some(self) {} + + fn take_data_before_call( + self, + _func: Self::Operand, + _args: impl IntoIterator, + _are_args_tupled: bool, + ) { + } + + fn enter(self, _def: FuncDef) {} + + fn emplace_arguments( + self, + _arg_places: Vec, + _ret_val_place: Self::Place, + _tupling: ArgsTupling, + ) { + } + + fn override_return_value(self, _value: Self::Operand) {} + + fn ret(self, _ret_point: BasicBlockIndex) {} + + fn after_call(self, _assignment_id: AssignmentId, _result_dest: Self::Place) {} + } + + #[derive(Default)] + pub struct NoOpDropHandler

(PhantomData<(P, O)>); + + impl DropHandler for NoOpDropHandler { + type Place = P; + type Operand = O; + + fn before_drop(self, _def: CalleeDef, _call_site: BasicBlockIndex) {} + + fn before_drop_some(self) {} + + fn take_data_before_drop( + self, + _func: Self::Operand, + _arg: Self::Operand, + _place: Self::Place, + ) { + } + + fn after_drop(self) {} + } + #[derive(Default)] pub struct NoOpAnnotationHandler; From c1c186d4c80aac49d2abd84dc925898b36f4d3ad Mon Sep 17 00:00:00 2001 From: Mohammad Omidvar Date: Fri, 7 Aug 2026 13:27:31 -0700 Subject: [PATCH 7/9] Fix incomplete refactor of macros --- runtime/lib/src/abs/mod.rs | 4 +- runtime/lib/src/pri/late_init.rs | 22 ++++----- runtime/lib/src/pri/late_init_x.rs | 74 ------------------------------ runtime/lib/src/pri/mod.rs | 4 +- 4 files changed, 14 insertions(+), 90 deletions(-) delete mode 100644 runtime/lib/src/pri/late_init_x.rs diff --git a/runtime/lib/src/abs/mod.rs b/runtime/lib/src/abs/mod.rs index afb31b94..75a75c9a 100644 --- a/runtime/lib/src/abs/mod.rs +++ b/runtime/lib/src/abs/mod.rs @@ -13,7 +13,7 @@ pub use common::{ types::{trace::*, *}, }; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(u8)] pub enum BinaryOp { Add = common::pri::BinaryOp::ADD.to_raw(), @@ -53,7 +53,7 @@ pub enum BinaryOp { Offset = common::pri::BinaryOp::OFFSET.to_raw(), } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(u8)] pub enum UnaryOp { Not = common::pri::UnaryOp::NOT.to_raw(), diff --git a/runtime/lib/src/pri/late_init.rs b/runtime/lib/src/pri/late_init.rs index f8c71822..a28bb27f 100644 --- a/runtime/lib/src/pri/late_init.rs +++ b/runtime/lib/src/pri/late_init.rs @@ -1,10 +1,5 @@ -#[derive(Default)] -pub struct LateInitPri

{ - _phantom: core::marker::PhantomData

, -} - #[macro_export] -macro_rules! late_init_func_defs { +macro_rules! def_late_init { ($(#[$($attr: meta)*])* fn init_runtime_lib ($($(#[$($arg_attr: meta)*])* $arg:ident : $arg_type:ty),* $(,)?) $(-> $ret_ty:ty)?;) => { $(#[$($attr)*])* #[inline(always)] @@ -35,22 +30,26 @@ macro_rules! late_init_func_defs { } #[macro_export] -macro_rules! impl_pri_for_late_init_pri_of { +macro_rules! make_late_init_pri_of { ($t:ident) => { paste::paste! { #[allow(non_snake_case)] mod [<_for_ $t>] { use common::pri::*; - use $crate::{abs, pri::{LateInitPri, NoOpPri}}; - + use $crate::{abs, pri::NoOpPri}; use super::*; type MainPri = $t; static mut IS_ACTIVE: bool = false; - impl common::pri::ProgramRuntimeInterface for LateInitPri { + #[derive(Default)] + pub struct [<$t LateInit>] { + _phantom: core::marker::PhantomData, + } + + impl common::pri::ProgramRuntimeInterface for [<$t LateInit>] { type U128 = u128; type Char = char; type ConstStr = &'static str; @@ -65,9 +64,10 @@ macro_rules! impl_pri_for_late_init_pri_of { type DebugInfo = common::pri::DebugInfo; type Tag = abs::Tag; - common::pri::list_func_decls! { modifier: $crate::late_init_func_defs, (from Self) } + common::pri::list_func_decls! { modifier: $crate::def_late_init, (from Self) } } } + pub use [<_for_ $t>]::[<$t LateInit>]; } }; } diff --git a/runtime/lib/src/pri/late_init_x.rs b/runtime/lib/src/pri/late_init_x.rs deleted file mode 100644 index ff59dc76..00000000 --- a/runtime/lib/src/pri/late_init_x.rs +++ /dev/null @@ -1,74 +0,0 @@ -#[macro_export] -macro_rules! def_late_init { - ($(#[$($attr: meta)*])* fn init_runtime_lib ($($(#[$($arg_attr: meta)*])* $arg:ident : $arg_type:ty),* $(,)?) $(-> $ret_ty:ty)?;) => { - $(#[$($attr)*])* - #[inline(always)] - fn init_runtime_lib ($($(#[$($arg_attr)*])* $arg : $arg_type),*) $(-> $ret_ty)? { - MainPri::init_runtime_lib($($arg.into()),*); - unsafe { IS_ACTIVE = true; } - } - }; - ($(#[$($attr: meta)*])* fn shutdown_runtime_lib ($($(#[$($arg_attr: meta)*])* $arg:ident : $arg_type:ty),* $(,)?) $(-> $ret_ty:ty)?;) => { - $(#[$($attr)*])* - #[inline(always)] - fn shutdown_runtime_lib ($($(#[$($arg_attr)*])* $arg : $arg_type),*) $(-> $ret_ty)? { - MainPri::shutdown_runtime_lib($($arg.into()),*); - unsafe { IS_ACTIVE = false; } - } - }; - ($(#[$($attr: meta)*])* fn $name:ident ($($(#[$($arg_attr: meta)*])* $arg:ident : $arg_type:ty),* $(,)?) $(-> $ret_ty:ty)?;) => { - $(#[$($attr)*])* - #[inline(always)] - fn $name ($($(#[$($arg_attr)*])* $arg : $arg_type),*) $(-> $ret_ty)? { - if core::hint::likely(unsafe { IS_ACTIVE }) { - MainPri::$name($($arg.into()),*).into() - } else { - NoOpPri::$name($($arg.into()),*).into() - } - } - }; -} - -#[macro_export] -macro_rules! make_late_init_pri_of { - ($t:ident) => { - paste::paste! { - #[allow(non_snake_case)] - mod [<_for_ $t>] { - use common::pri::*; - - use $crate::{abs, pri::{LateInitPri, NoOpPri}}; - - use super::*; - - type MainPri = $t; - - static mut IS_ACTIVE: bool = false; - - #[derive(Default)] - pub struct [<$t LateInit>] { - _phantom: core::marker::PhantomData, - } - - impl common::pri::ProgramRuntimeInterface for [<$t LateInit>] { - type U128 = u128; - type Char = char; - type ConstStr = &'static str; - type ConstByteStr = &'static [u8]; - type Slice<'a, T: 'a> = &'a [T]; - type TypeId = abs::TypeId; - type PrimitiveType = abs::PrimitiveType; - type BinaryOp = abs::BinaryOp; - type UnaryOp = abs::UnaryOp; - type AtomicOrdering = abs::AtomicOrdering; - type AtomicBinaryOp = abs::AtomicBinaryOp; - type DebugInfo = common::pri::DebugInfo; - type Tag = abs::Tag; - - common::pri::list_func_decls! { modifier: $crate::def_late_init, (from Self) } - } - } - pub use [<_for_ $t>]::[<$t LateInit>]; - } - }; -} diff --git a/runtime/lib/src/pri/mod.rs b/runtime/lib/src/pri/mod.rs index 972bd156..36d9ef5a 100644 --- a/runtime/lib/src/pri/mod.rs +++ b/runtime/lib/src/pri/mod.rs @@ -1,9 +1,7 @@ mod ffi; pub mod fluent; -mod late_init; -pub mod late_init_x; +pub mod late_init; mod noop; pub mod refs; -pub use late_init::LateInitPri; pub use noop::NoOpPri; From aeec511ce26964b77689575376bb6728adaa0772 Mon Sep 17 00:00:00 2001 From: Mohammad Omidvar Date: Fri, 7 Aug 2026 16:58:56 -0700 Subject: [PATCH 8/9] Update extensions --- .devcontainer/devcontainer.json.example | 1 - .vscode/extensions.json | 1 - 2 files changed, 2 deletions(-) diff --git a/.devcontainer/devcontainer.json.example b/.devcontainer/devcontainer.json.example index eec83f6f..2755ee36 100644 --- a/.devcontainer/devcontainer.json.example +++ b/.devcontainer/devcontainer.json.example @@ -43,7 +43,6 @@ "fill-labs.dependi", "vadimcn.vscode-lldb", "streetsidesoftware.code-spell-checker", - "GitHub.copilot", "GitHub.copilot-chat", "GitHub.vscode-pull-request-github", ], diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 0c82962f..cb26aabe 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -12,7 +12,6 @@ "phil294.git-log--graph", // Experience "streetsidesoftware.code-spell-checker", - "GitHub.copilot", "GitHub.copilot-chat", "GitHub.vscode-pull-request-github", "alefragnani.Bookmarks", From 5f75dcd8f14b9570efb24e3e10c8d973a68b2574 Mon Sep 17 00:00:00 2001 From: Mohammad Omidvar Date: Fri, 7 Aug 2026 16:59:19 -0700 Subject: [PATCH 9/9] Add publications section --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 8b4087c2..cbcb5adf 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,9 @@ The generated program will emit runtime events through the active backend, which Further information, tutorials, and technical details are collected in Leaf Book @ [sfu-rsl.github.io/leaf](https://sfu-rsl.github.io/leaf) (WIP). +## Publications +- Leaf: An Instrumentation-based Dynamic Analysis Framework for Rust: [arXiv](https://arxiv.org/abs/2607.15025) + ## License Leaf is licensed under the MIT or Apache-2.0 licenses.