Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
use std::io::Write;
use std::process::Command;

#[allow(dead_code)]
#[path = "src/loop_recipe.rs"]
mod loop_recipe;
#[allow(dead_code)]
#[path = "src/pointwise.rs"]
mod pointwise;
#[path = "build/pointwise.rs"]
mod pointwise_build;
#[allow(dead_code)]
#[path = "src/recurrence.rs"]
mod recurrence;
#[path = "build/recurrence.rs"]
mod recurrence_build;

fn main() {
let out_dir = std::env::var("OUT_DIR").unwrap();
pointwise_build::generate(std::path::Path::new(&out_dir));
recurrence_build::generate(std::path::Path::new(&out_dir));
println!("cargo:rerun-if-changed=src/loop_recipe.rs");
println!("cargo:rerun-if-changed=src/pointwise.rs");
println!("cargo:rerun-if-changed=build/pointwise.rs");
println!("cargo:rerun-if-changed=src/recurrence.rs");
println!("cargo:rerun-if-changed=build/recurrence.rs");
// Compile the C stack interpreter with preserve_none + musttail.
// Clang-only feature (skip on GCC). The handler signature keeps
// the TOS window (t0..t3), the int stack pointer, the float
Expand All @@ -17,6 +39,7 @@ fn main() {
cc::Build::new()
.file("src/stack_interp.c")
.include("src")
.include(&out_dir)
.opt_level(3)
.flag("-std=c11")
.flag("-Wno-unused-parameter")
Expand Down Expand Up @@ -63,7 +86,6 @@ fn main() {
println!("cargo:rerun-if-changed=src/stack_interp.c");
println!("cargo:rerun-if-changed=src/stack_interp.h");
// Determine sizeof(jmp_buf) on this platform by compiling and running a C snippet.
let out_dir = std::env::var("OUT_DIR").unwrap();
let c_path = format!("{}/jmpbuf_size.c", out_dir);
let bin_path = format!("{}/jmpbuf_size", out_dir);

Expand Down
112 changes: 112 additions & 0 deletions build/pointwise.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//! Build-time expansion of the shared pointwise recipes into native C bodies.
//!
//! Every recipe produces typed SSA statements in its declared order. Handler
//! selection happens while the backend is constructed; constant kernel kinds
//! remove the generated switch before the interpreter is compiled.

use crate::pointwise::{self, BinaryOp, Node, Recipe};
use std::fmt::Write;
use std::path::Path;

pub fn generate(out_dir: &Path) {
pointwise::validate().expect("invalid native pointwise recipe catalog");
// The compact native encoding has exactly four coefficient slots.
assert_eq!(pointwise::MAX_COEFFICIENTS, 4);

let mut catalog = String::from("// Generated from src/pointwise.rs; do not edit.\n");
for (id, recipe) in pointwise::recipes() {
writeln!(
catalog,
"POINTWISE_RECIPE({}, {}, {}, {})",
id.index(),
recipe.name.to_ascii_uppercase(),
recipe.name,
recipe.coefficient_count(),
)
.unwrap();
}
std::fs::write(out_dir.join("pointwise_catalog.inc"), catalog)
.expect("failed to write native pointwise catalog");

let mut chunks = String::from("// Generated from src/pointwise.rs; do not edit.\n");
for (suffix, scalar, load) in [
("f32", "float", "load_f32_unaligned"),
("f64", "double", "load_f64_unaligned"),
] {
writeln!(
chunks,
"static inline __attribute__((always_inline)) void native_pointwise_{suffix}(\n\
NativeKernelKind kind, const uint8_t* input, uint8_t* output,\n\
int64_t count, const {scalar}* coefficients\n\
) {{\n switch (kind) {{"
)
.unwrap();
for (_, recipe) in pointwise::recipes() {
writeln!(
chunks,
" case NATIVE_POINTWISE_{}: {{",
recipe.name.to_ascii_uppercase()
)
.unwrap();
for (index, node) in recipe.nodes.iter().enumerate() {
if let Node::Parameter(parameter) = node {
writeln!(
chunks,
" {scalar} v{index} = coefficients[{parameter}];"
)
.unwrap();
}
}
// The one-pointer spelling lets LLVM prove exact in-place access
// safe for SIMD without making a restrict promise about host spans.
chunks.push_str(" if (input == output) {\n");
emit_loop(&mut chunks, recipe, scalar, load, "output");
chunks.push_str(" } else {\n");
emit_loop(&mut chunks, recipe, scalar, load, "input");
chunks.push_str(" }\n return;\n }\n");
}
chunks.push_str(" default: __builtin_unreachable();\n }\n}\n");
}
std::fs::write(out_dir.join("pointwise_chunks.inc"), chunks)
.expect("failed to write native pointwise arithmetic");
}

fn emit_loop(code: &mut String, recipe: &Recipe, scalar: &str, load: &str, input: &str) {
writeln!(
code,
" for (int64_t item = 0; item < count; ++item) {{"
)
.unwrap();
writeln!(
code,
" size_t offset = (size_t)item * sizeof({scalar});"
)
.unwrap();
for (index, node) in recipe.nodes.iter().enumerate() {
let expression = match node {
Node::Read => format!("{load}({input} + offset)"),
Node::Parameter(_) => continue,
Node::Carry(_) => unreachable!("validated pointwise recipes have no carries"),
Node::Binary(operator, left, right) => {
let operator = match operator {
BinaryOp::Add => '+',
BinaryOp::Sub => '-',
BinaryOp::Mul => '*',
};
format!("v{left} {operator} v{right}")
}
};
writeln!(
code,
" {scalar} v{index} = {expression};"
)
.unwrap();
}
writeln!(
code,
" __builtin_memcpy(output + offset, &v{}, sizeof(v{}));",
recipe.result, recipe.result,
)
.unwrap();
code.push_str(" }\n");
}
113 changes: 113 additions & 0 deletions build/recurrence.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//! Expand the checked recurrence recipes into statically specialized C loops.
//!
//! Carry nodes name iteration-entry values. The next-carry map is published only
//! after every node has been evaluated, so history shifts do not depend on the
//! order in which mutable C carry variables are assigned.

use crate::loop_recipe::{BinaryOp, Node};
use crate::recurrence;
use std::fmt::Write;
use std::path::Path;

pub fn generate(out_dir: &Path) {
recurrence::validate().expect("invalid native recurrence recipe catalog");
// Preserve the existing native boundary and biquad descriptor capacities.
assert_eq!(recurrence::MAX_COEFFICIENTS, 5);
assert_eq!(recurrence::MAX_CARRIES, 4);

let mut catalog = String::from("// Generated from src/recurrence.rs; do not edit.\n");
for (id, recipe) in recurrence::recipes() {
writeln!(
catalog,
"RECURRENCE_RECIPE({}, {}, {}, {}, {})",
id.index(),
recipe.name.to_ascii_uppercase(),
recipe.name,
recipe.coefficient_count(),
recipe.carry_count(),
)
.unwrap();
}
std::fs::write(out_dir.join("recurrence_catalog.inc"), catalog)
.expect("failed to write native recurrence catalog");

let mut chunks = String::from("// Generated from src/recurrence.rs; do not edit.\n");
for (suffix, scalar, load) in [
("f32", "float", "load_f32_unaligned"),
("f64", "double", "load_f64_unaligned"),
] {
writeln!(
chunks,
"static inline __attribute__((always_inline)) void native_recurrence_{suffix}(\n\
NativeKernelKind kind, const uint8_t* input, uint8_t* output,\n\
int64_t count, const {scalar}* coefficients, {scalar}* carried\n\
) {{\n switch (kind) {{"
)
.unwrap();
for (_, recipe) in recurrence::recipes() {
writeln!(
chunks,
" case NATIVE_{}: {{",
recipe.name.to_ascii_uppercase()
)
.unwrap();
for (index, node) in recipe.nodes.iter().enumerate() {
if let Node::Parameter(parameter) = node {
writeln!(
chunks,
" {scalar} v{index} = coefficients[{parameter}];"
)
.unwrap();
}
}
for carry in 0..recipe.carry_count() {
writeln!(chunks, " {scalar} c{carry} = carried[{carry}];").unwrap();
}
writeln!(
chunks,
" for (int64_t item = 0; item < count; ++item) {{"
)
.unwrap();
writeln!(
chunks,
" size_t offset = (size_t)item * sizeof({scalar});"
)
.unwrap();
for (index, node) in recipe.nodes.iter().enumerate() {
let expression = match node {
Node::Read => format!("{load}(input + offset)"),
Node::Parameter(_) => continue,
Node::Carry(carry) => format!("c{carry}"),
Node::Binary(operator, left, right) => {
let operator = match operator {
BinaryOp::Add => '+',
BinaryOp::Sub => '-',
BinaryOp::Mul => '*',
};
format!("v{left} {operator} v{right}")
}
};
writeln!(chunks, " {scalar} v{index} = {expression};").unwrap();
}
// Every root is an SSA value from the completed iteration. Updating
// c0 cannot change a later root which refers to the old c0 snapshot.
for (carry, next) in recipe.next_carries.iter().enumerate() {
writeln!(chunks, " c{carry} = v{next};").unwrap();
}
writeln!(
chunks,
" __builtin_memcpy(output + offset, &v{}, sizeof(v{}));",
recipe.result, recipe.result,
)
.unwrap();
chunks.push_str(" }\n");
for carry in 0..recipe.carry_count() {
writeln!(chunks, " carried[{carry}] = c{carry};").unwrap();
}
chunks.push_str(" return;\n }\n");
}
chunks.push_str(" default: __builtin_unreachable();\n }\n}\n");
}
std::fs::write(out_dir.join("recurrence_chunks.inc"), chunks)
.expect("failed to write native recurrence arithmetic");
}
Loading
Loading