Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Redline

A syntactic lint for allocation and syscall constructs in hot functions.

crates.io License

Quick Start  |  Constraints  |  Cost Model  |  Limitations


What Redline actually is

Redline is a proc-macro that walks the annotated function's own syntax tree and emits a hard compile_error! when it finds a construct from a fixed list of recognized allocation and syscall constructs, or when a weighted node count exceeds a number you wrote.

That is the whole mechanism. It is a lint with a hard failure mode.

What Redline is not

It does not prove anything about performance, and it cannot.

  • It has no type information, no name resolution, no MIR, no LLVM IR, no compiled code. It sees tokens.
  • It cannot see into any function you call. helper() could allocate a gigabyte; redline has no way to know. (It now refuses to guess — see Un-analyzable calls.)
  • Its latency numbers are uncalibrated weights on pre-optimization source nodes. rustc will inline, vectorize, hoist and delete much of what is counted. A passing latency = "< 1ms" is not evidence that the function runs in under 1ms, and a failing one is not evidence that it doesn't.
  • Real worst-case execution time analysis operates on IR or on the compiled binary with a hardware timing model, precisely because source-level estimation does not work. See Prior art.

Use it to keep String::from and println! out of a hot loop. Do not use it as a performance guarantee.

Install

[dependencies]
alia-redline = "0.3"

Usage

use redline::redline;

// Rejects the recognized allocation constructs: Vec/String/Box/HashMap
// constructors, .to_string(), .to_owned(), .collect(), vec!, format!, ...
#[redline(allocs = 0)]
fn hot_path(a: f64, b: f64) -> f64 {
    a * a + b * b
}

// Rejects the recognized syscall constructs: std::fs::*, File::open,
// TcpStream::connect, println!, ...
#[redline(syscalls = 0)]
fn pure_compute(x: i32) -> i32 {
    x * x + 2 * x + 1
}

// Compares a weighted AST node count against a number. Uncalibrated.
#[redline(latency = "< 1ms", allocs = 0, syscalls = 0)]
fn critical_section(data: &[u8]) -> u64 {
    let mut h: u64 = 0;
    for i in 0..64 {
        h ^= data.get(i).copied().unwrap_or(0) as u64;
    }
    h
}

What a violation looks like

#[redline(allocs = 0)]
fn oops() -> String {
    String::from("hello")
}
error: redline: function `oops` contains 1 recognized allocation(s), limit is 0
 --> src/main.rs:2:4

Un-analyzable calls

This used to compile clean, and that was the single worst bug in the crate:

#[redline(allocs = 0)]
fn body_local_only() -> usize {
    helper().len() // helper allocates. Redline never looked.
}

A proc-macro cannot follow a call — there is no cross-crate MIR, no type information, not even reliable name resolution. So redline no longer pretends. Any call it does not recognize is a hard error:

error: redline: cannot analyze `helper`: this path is not in redline's
recognized-construct table. Redline only sees this function's own syntax tree,
so this call's allocations, syscalls and latency are unknown and the constraint
cannot be checked. Inline the work, use a construct redline recognizes, or add
`assume_calls_free` to the attribute to state that un-analyzable calls are
irrelevant here (nothing verifies that).

The escape hatch is explicit and unverified by construction:

#[redline(allocs = 0, assume_calls_free)]
fn delegates(x: u64) -> u64 {
    helper(x) * 2 // unchecked. You are asserting, not proving.
}

.await is treated the same way: suspension time is not a syntactic property.

This means redline is only useful on leaf-ish functions written in terms of primitives, slices, iterators and the constructs in its tables. That is a real restriction, and it is the honest size of what a proc-macro can check.

Supported constraints

Constraint Syntax What is actually checked
Allocations allocs = 0, allocs = "< 5" Count of constructs in redline's allocation table
Syscalls syscalls = 0, syscalls = "< 5" Count of constructs in redline's syscall table
Latency latency = "< 1ms" Uncalibrated weighted node count vs. the bound
Stack size max_stack = "< 4KB" number of bindings × 8 bytes. There is no type information, so this is close to meaningless
Cost model cost_model = "ns" / "cycles" Which weight table to use
Escape hatch assume_calls_free Suppresses the un-analyzable-call error

Time units: ns, us, ms, s, cycles (with cost_model = "cycles"). Size units: B, KB, MB, GB.

Removed: throughput

throughput = "> 1GB/s" was documented as enforced. It parsed and then checked nothing — the match arm was empty. Throughput is bytes per unit of wall-clock time; an AST pass sees neither operand. It is now a hard error, because silently accepting an annotation that checks nothing is worse than rejecting it.

How the matcher works

Recognition is done on syn::Path segments, comparing whole identifiers, requiring a suffix match of at least two segments:

  • std::fs::read, fs::read → syscall.
  • read, spread, myfs::read, fs::readable → not recognized.

The previous implementation ran .contains() on the stringified path against substrings like "read", "bind", "lock" and "accept". A user function named spread, rebind, unlock or blocked was reported as a syscall. There are now regression tests for exactly those names (src/cost.rs unit tests, tests/pass/no_false_syscalls.rs).

Method calls have no receiver type available, so they are matched on name alone: a small allocating list (to_string, collect, …), a small free list (len, get, wrapping_*, iterator adapters), and everything else is un-analyzable. A user type with a method named collect that allocates nothing is over-counted. That direction is deliberate: over-counting rejects, under-counting lies.

Cost model

CostTable::NS in src/cost.rs:

Construct Weight
ALU op, branch, index 1
Call 5
Heap allocation 50
format! 200
Syscall 1,000
println! 5,000
Network syscall 10,000

Loops with a literal range use the exact trip count. Every other loop — while, loop, for x in xs — is assumed to run 1,000 iterations. That number is arbitrary. A loop that runs a million times is under-counted by 1000x.

These weights were never calibrated. benchmarks/bench_cost_model.py measures each one against a microbenchmark on your machine and reports the ratio; on the development machine the println! weight is ~26,000x the measured cost of a buffered write and the file-syscall weight is ~14x too small. That benchmark compares isolated release-build microbenchmarks against pre-optimization source counts, so it does not validate whole-function estimates either.

There is no accuracy number for this crate. The previous one (100% accuracy, 0 false positives) was computed over 14 snippets hand-written to contain exactly the constructs the matcher looks for. That benchmark and its results file have been deleted rather than restated. Building a credible corpus means hand- labelling allocation and syscall behaviour of real third-party crate functions; until that exists, no number is claimed.

Prior art

The claim "nothing does this" was false. Redline is the weakest tool in this list; it is a lint, and everything below does more than it does.

Tool What it does Relation to Redline
iai-callgrind Deterministic instruction/cache counts on the compiled binary via Callgrind, with regression thresholds in CI What you should use if you want reproducible cost numbers. Operates on real machine code.
#[no_panic] Turns "this function may panic" into a link error Direct precedent for property-violation-as-build-failure, and it works because the linker sees the optimized program. Redline's check is strictly weaker.
Kani Bounded model checking of Rust via CBMC Real compile-time verification. Proofs, not heuristics.
Prusti Deductive verification (Viper) with pre/postconditions Real verification of functional properties.
MIRAI Abstract interpretation over MIR Analyses MIR, follows calls — the thing redline cannot do.
Creusot Deductive verification via Why3 Real proofs over MIR.
static-assertions const-evaluable assertions (sizes, alignment, trait impls) as compile errors Same "compile error on violated property" shape, on properties that are actually decidable at compile time.
cargo-bloat Per-function size in the built binary Measures the artifact instead of guessing from source.
Clippy Lints, including perf lints Nearest neighbour. Redline's contribution over clippy is the per-function budget syntax and the hard error.
WCET analysers (aiT, OTAWA, Heptane) Worst-case execution time for real-time/avionics Operate on binaries or IR with a hardware timing model and require loop-bound annotations. They work at that level because source-AST estimation is known not to work.

Architecture

src/
  lib.rs       Proc-macro entry point (#[redline(...)])
  parse.rs     Attribute parser; rejects anything unenforceable
  analyze.rs   AST walker: counts recognized constructs, flags un-analyzable calls
  cost.rs      Weight tables + the path/method recognition tables

Tests: cargo test runs 18 unit tests (path matching, false-positive regressions, opt-out behaviour) plus trybuild compile-pass/compile-fail cases in tests/.

Known unsoundness

Not a roadmap — a list of ways the tool is wrong today.

  • Macro bodies are not expanded. An unrecognized macro is counted as one call and its contents are invisible.
  • Method recognition is by name, with no receiver type. Both false positives and false negatives are possible.
  • Trait dispatch, generics and operator overloading are invisible. a + b is counted as one ALU op even if Add is implemented by allocating.
  • Drop is invisible. Dropping a Vec deallocates; nothing counts that.
  • Non-literal loop bounds are guessed at 1,000 iterations.
  • max_stack counts bindings, not sizes.
  • assume_calls_free disables the main safety net entirely.

License

Apache-2.0 | ALIA Labs

Built by Tushar Sharma at ALIA Labs.

About

Cross the performance line. Won't compile. Compile-time performance guarantees for Rust.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages