Skip to content

Commit

Permalink
Cranelift: add option to use new single-pass register allocator.
Browse files Browse the repository at this point in the history
In bytecodealliance/regalloc2#181, @d-sonuga added a fast single-pass
algorithm option to regalloc2, in addition to its existing backtracking
allocator. This produces code much more quickly, at the expense of code
quality. Sometimes this tradeoff is desirable (e.g. when performing a
debug build in a fast-iteration development situation, or in an initial
JIT tier).

This PR adds a Cranelift option to select the RA2 algorithm, plumbs it
through to a Wasmtime option, and adds the option to Wasmtime fuzzing as
well.

An initial compile-time measurement in Wasmtime: `spidermonkey.wasm`
builds in 1.383s with backtracking (existing algorithm), and 1.065s with
single-pass. The resulting binary runs a simple Fibonacci benchmark in
2.060s with backtracking vs. 3.455s with single-pass.

Hence, the single-pass algorithm yields a 23% compile-time reduction, at
the cost of a 67% runtime increase.
  • Loading branch information
cfallin committed Nov 15, 2024
1 parent 3aac2af commit 73fd478
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 14 deletions.
26 changes: 14 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,9 @@ byte-array-literals = { path = "crates/wasi-preview1-component-adapter/byte-arra

# Bytecode Alliance maintained dependencies:
# ---------------------------
regalloc2 = "0.10.2"
#regalloc2 = "0.10.2"
# TODO: remove this and update PR once we release RA2 0.11.0.
regalloc2 = { path = "../regalloc2" }

# cap-std family:
target-lexicon = "0.12.16"
Expand Down
14 changes: 14 additions & 0 deletions cranelift/codegen/meta/src/shared/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ pub(crate) fn define() -> SettingGroup {
false,
);

settings.add_enum(
"regalloc_algorithm",
"Algorithm to use in register allocator.",
r#"
Supported options:
- `backtracking`: A backtracking allocator with range splitting; more expensive
but generates better code.
- `single_pass`: A single-pass algorithm that yields quick compilation but
results in code with more register spills and moves.
"#,
vec!["backtracking", "single_pass"],
);

settings.add_enum(
"opt_level",
"Optimization level for generated code.",
Expand Down
8 changes: 7 additions & 1 deletion cranelift/codegen/src/machinst/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ use crate::ir::pcc;
use crate::ir::Function;
use crate::isa::TargetIsa;
use crate::machinst::*;
use crate::settings::RegallocAlgorithm;
use crate::timing;
use crate::trace;
use crate::CodegenError;

use regalloc2::RegallocOptions;
use regalloc2::{Algorithm, RegallocOptions};

/// Compile the given function down to VCode with allocated registers, ready
/// for binary emission.
Expand Down Expand Up @@ -63,6 +64,11 @@ pub fn compile<B: LowerBackend + TargetIsa>(
options.validate_ssa = true;
}

options.algorithm = match b.flags().regalloc_algorithm() {
RegallocAlgorithm::Backtracking => Algorithm::Ion,
RegallocAlgorithm::SinglePass => Algorithm::Fastalloc,
};

regalloc2::run(&vcode, vcode.machine_env(), &options)
.map_err(|err| {
log::error!(
Expand Down
8 changes: 8 additions & 0 deletions crates/cli-flags/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ wasmtime_option_group! {
/// Optimization level of generated code (0-2, s; default: 2)
pub opt_level: Option<wasmtime::OptLevel>,

/// Register allocator algorithm choice.
pub regalloc_algorithm: Option<wasmtime::RegallocAlgorithm>,

/// Do not allow Wasm linear memories to move in the host process's
/// address space.
pub memory_may_move: Option<bool>,
Expand Down Expand Up @@ -585,6 +588,11 @@ impl CommonOptions {
level => config.cranelift_opt_level(level),
_ => err,
}
match_feature! {
["cranelift": self.opts.regalloc_algorithm]
algo => config.cranelift_regalloc_algorithm(algo),
_ => err,
}
match_feature! {
["cranelift" : self.wasm.nan_canonicalization]
enable => config.cranelift_nan_canonicalization(enable),
Expand Down
14 changes: 14 additions & 0 deletions crates/cli-flags/src/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,20 @@ impl WasmtimeOptionValue for wasmtime::OptLevel {
}
}

impl WasmtimeOptionValue for wasmtime::RegallocAlgorithm {
const VAL_HELP: &'static str = "=backtracking|single-pass";
fn parse(val: Option<&str>) -> Result<Self> {
match String::parse(val)?.as_str() {
"backtracking" => Ok(wasmtime::RegallocAlgorithm::Backtracking),
"single-pass" => Ok(wasmtime::RegallocAlgorithm::SinglePass),
other => bail!(
"unknown regalloc algorithm`{}`, only backtracking,single-pass accepted",
other
),
}
}
}

impl WasmtimeOptionValue for wasmtime::Strategy {
const VAL_HELP: &'static str = "=winch|cranelift";
fn parse(val: Option<&str>) -> Result<Self> {
Expand Down
17 changes: 17 additions & 0 deletions crates/fuzzing/src/generators/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ impl Config {
.native_unwind_info(cfg!(target_os = "windows") || self.wasmtime.native_unwind_info)
.cranelift_nan_canonicalization(self.wasmtime.canonicalize_nans)
.cranelift_opt_level(self.wasmtime.opt_level.to_wasmtime())
.cranelift_regalloc_algorithm(self.wasmtime.regalloc_algorithm.to_wasmtime())
.consume_fuel(self.wasmtime.consume_fuel)
.epoch_interruption(self.wasmtime.epoch_interruption)
.memory_guaranteed_dense_image_size(std::cmp::min(
Expand Down Expand Up @@ -474,6 +475,7 @@ impl<'a> Arbitrary<'a> for Config {
#[derive(Arbitrary, Clone, Debug, Eq, Hash, PartialEq)]
pub struct WasmtimeConfig {
opt_level: OptLevel,
regalloc_algorithm: RegallocAlgorithm,
debug_info: bool,
canonicalize_nans: bool,
interruptable: bool,
Expand Down Expand Up @@ -693,6 +695,21 @@ impl OptLevel {
}
}

#[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)]
enum RegallocAlgorithm {
Backtracking,
SinglePass,
}

impl RegallocAlgorithm {
fn to_wasmtime(&self) -> wasmtime::OptLevel {
match self {
RegallocAlgorithm::Backtracking => RegallocAlgorithm::Backtracking,
RegallocAlgorithm::SinglePass => RegallocAlgorithm::SinglePass,
}
}
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
/// Compiler to use.
pub enum CompilerStrategy {
Expand Down
44 changes: 44 additions & 0 deletions crates/wasmtime/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,27 @@ impl Config {
self
}

/// Configures the regalloc algorithm used by the Cranelift code generator.
///
/// Cranelift can select any of several register allocator algorithms. Each
/// of these algorithms generates correct code, but they represent different
/// tradeoffs between compile speed (how expensive the compilation process
/// is) and run-time speed (how fast the generated code runs).
/// For more information see the documentation of [`RegallocAlgorithm`].
///
/// The default value for this is `RegallocAlgorithm::Backtracking`.
#[cfg(any(feature = "cranelift", feature = "winch"))]
pub fn cranelift_regalloc_algorithm(&mut self, algo: RegallocAlgorithm) -> &mut Self {
let val = match algo {
RegallocAlgorithm::Backtracking => "backtracking",
RegallocAlgorithm::SinglePass => "single_pass",
};
self.compiler_config
.settings
.insert("regalloc_algorithm".to_string(), val.to_string());
self
}

/// Configures whether Cranelift should perform a NaN-canonicalization pass.
///
/// When Cranelift is used as a code generation backend this will configure
Expand Down Expand Up @@ -2618,6 +2639,29 @@ pub enum OptLevel {
SpeedAndSize,
}

/// Possible register allocator algorithms for the Cranelift codegen backend.
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub enum RegallocAlgorithm {
/// Generates the fastest possible code, but may take longer.
///
/// This algorithm performs "backtracking", which means that it may
/// undo its earlier work and retry as it discovers conflicts. This
/// results in better register utilization, producing fewer spills
/// and moves, but can cause super-linear compile runtime.
Backtracking,
/// Generates acceptable code very quickly.
///
/// This algorithm performs a single pass through the code,
/// guaranteed to work in linear time. (Note that the rest of
/// Cranelift is not necessarily guaranteed to run in linear time,
/// however.) It cannot undo earlier decisions, however, and it
/// cannot foresee constraints or issues that may occur further
/// ahead in the code, so the code may have more spills and moves as
/// a result.
SinglePass,
}

/// Select which profiling technique to support.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ProfilingStrategy {
Expand Down
1 change: 1 addition & 0 deletions crates/wasmtime/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ impl Engine {
| "enable_pcc"
| "regalloc_checker"
| "regalloc_verbose_logs"
| "regalloc_algorithm"
| "is_pic"
| "bb_padding_log2_minus_one"
| "machine_code_cfg_info"
Expand Down

0 comments on commit 73fd478

Please sign in to comment.