Skip to content

Commit 9f7f089

Browse files
committed
Add ZynML runtime profiles, event bridge, and SIMD scaffolding
1 parent e13cf01 commit 9f7f089

16 files changed

Lines changed: 839 additions & 62 deletions

File tree

.github/workflows/ci.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@ jobs:
3131
- name: Run clippy
3232
run: cargo clippy --workspace --all-targets
3333

34+
llvm-feature-check:
35+
name: llvm feature check
36+
runs-on: ubuntu-latest
37+
continue-on-error: true
38+
steps:
39+
- name: Checkout repository
40+
uses: actions/checkout@v4
41+
42+
- name: Setup Rust toolchain
43+
uses: dtolnay/rust-toolchain@stable
44+
45+
- name: Cache cargo artifacts
46+
uses: Swatinem/rust-cache@v2
47+
48+
- name: Check zynml with llvm-backend feature
49+
run: cargo check -p zynml --features llvm-backend
50+
3451
parse-conformance:
3552
name: parse conformance
3653
runs-on: ubuntu-latest
@@ -67,3 +84,5 @@ jobs:
6784
run: |
6885
RUST_MIN_STACK=134217728 cargo test -p zynml --test e2e_tests execution::test_execute_simple_expression -- --nocapture
6986
RUST_MIN_STACK=134217728 cargo test -p zynml --test e2e_tests execution::test_execute_prelude_only_example -- --nocapture
87+
RUST_MIN_STACK=134217728 cargo test -p zynml --test e2e_tests runtime::test_runtime_profile_tiered_development -- --nocapture
88+
RUST_MIN_STACK=134217728 cargo test -p zynml --test e2e_tests runtime::test_runtime_event_capture_render_and_stream -- --nocapture

crates/compiler/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,14 @@ inkwell = { version = "0.7.1", features = ["llvm21-1"], optional = true } # LLV
6060

6161
[dev-dependencies]
6262
env_logger = "0.11"
63+
criterion = "0.5"
64+
65+
[[bench]]
66+
name = "simd_vectorization"
67+
harness = false
6368

6469
[features]
6570
default = ["cranelift-backend"]
6671
cranelift-backend = ["cranelift", "cranelift-codegen", "cranelift-frontend", "cranelift-jit", "cranelift-module", "cranelift-native"]
6772
llvm-backend = ["inkwell"]
68-
all-backends = ["cranelift-backend", "llvm-backend"]
73+
all-backends = ["cranelift-backend", "llvm-backend"]
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
2+
3+
fn scalar_add(dst: &mut [f32], a: &[f32], b: &[f32]) {
4+
for i in 0..dst.len() {
5+
dst[i] = a[i] + b[i];
6+
}
7+
}
8+
9+
fn chunked_add(dst: &mut [f32], a: &[f32], b: &[f32]) {
10+
let mut i = 0;
11+
while i + 4 <= dst.len() {
12+
dst[i] = a[i] + b[i];
13+
dst[i + 1] = a[i + 1] + b[i + 1];
14+
dst[i + 2] = a[i + 2] + b[i + 2];
15+
dst[i + 3] = a[i + 3] + b[i + 3];
16+
i += 4;
17+
}
18+
19+
while i < dst.len() {
20+
dst[i] = a[i] + b[i];
21+
i += 1;
22+
}
23+
}
24+
25+
fn bench_vector_add(c: &mut Criterion) {
26+
let mut group = c.benchmark_group("vector-add");
27+
for size in [1 << 10, 1 << 14, 1 << 18] {
28+
let a = vec![1.0f32; size];
29+
let b = vec![2.0f32; size];
30+
let mut dst = vec![0.0f32; size];
31+
32+
group.bench_with_input(BenchmarkId::new("scalar", size), &size, |bencher, _| {
33+
bencher.iter(|| scalar_add(black_box(&mut dst), black_box(&a), black_box(&b)));
34+
});
35+
36+
group.bench_with_input(BenchmarkId::new("chunked", size), &size, |bencher, _| {
37+
bencher.iter(|| chunked_add(black_box(&mut dst), black_box(&a), black_box(&b)));
38+
});
39+
}
40+
group.finish();
41+
}
42+
43+
criterion_group!(simd_benches, bench_vector_add);
44+
criterion_main!(simd_benches);

crates/compiler/src/cranelift_backend.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use cranelift_jit::{JITBuilder, JITModule};
1515
use cranelift_module::{DataDescription, FuncId, Linkage, Module};
1616
use log::{debug, error, info, warn};
1717
use std::collections::HashMap;
18+
use std::sync::atomic::{AtomicUsize, Ordering};
1819
use std::sync::{Arc, RwLock};
1920

2021
use crate::effect_codegen::{
@@ -28,6 +29,18 @@ use crate::hir::{
2829
};
2930
use crate::{CompilerError, CompilerResult};
3031

32+
static CRANELIFT_SKIPPED_FUNCTIONS: AtomicUsize = AtomicUsize::new(0);
33+
34+
/// Number of function bodies skipped by Cranelift due to recoverable codegen errors.
35+
pub fn cranelift_skipped_function_count() -> usize {
36+
CRANELIFT_SKIPPED_FUNCTIONS.load(Ordering::Relaxed)
37+
}
38+
39+
/// Reset Cranelift skip diagnostics counter.
40+
pub fn reset_cranelift_skipped_function_count() {
41+
CRANELIFT_SKIPPED_FUNCTIONS.store(0, Ordering::Relaxed);
42+
}
43+
3144
/// Convert ZRTL TypeTag to Cranelift type
3245
fn type_tag_to_cranelift_type(tag: &crate::zrtl::TypeTag) -> types::Type {
3346
use crate::zrtl::{PrimitiveSize, TypeCategory};
@@ -372,6 +385,7 @@ impl CraneliftBackend {
372385
if !function.is_external {
373386
// Skip functions that fail to compile (e.g., signature mismatches with ZRTL)
374387
if let Err(e) = self.compile_function_body(*id, function, module) {
388+
CRANELIFT_SKIPPED_FUNCTIONS.fetch_add(1, Ordering::Relaxed);
375389
eprintln!(
376390
"[CRANELIFT WARN] Skipping function '{}': {:?}",
377391
function.name, e
@@ -4061,10 +4075,16 @@ impl CraneliftBackend {
40614075
// Function pointers
40624076
Ok(self.module.target_config().pointer_type())
40634077
}
4064-
HirType::Vector(elem_ty, _count) => {
4065-
// Vector types for SIMD - use the element type for now
4066-
self.translate_type(elem_ty)
4067-
}
4078+
HirType::Vector(elem_ty, count) => match (&**elem_ty, *count) {
4079+
(HirType::F32, 4) => Ok(types::F32X4),
4080+
(HirType::F64, 2) => Ok(types::F64X2),
4081+
(HirType::I32, 4) | (HirType::U32, 4) => Ok(types::I32X4),
4082+
(HirType::I64, 2) | (HirType::U64, 2) => Ok(types::I64X2),
4083+
_ => Err(CompilerError::CodeGen(format!(
4084+
"unsupported SIMD vector lane shape in Cranelift backend: Vector({:?}, {})",
4085+
elem_ty, count
4086+
))),
4087+
},
40684088
HirType::Union(_) => {
40694089
// Unions are treated as pointers to stack-allocated memory
40704090
Ok(self.module.target_config().pointer_type())

crates/compiler/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ pub use borrow_check::{
6969
};
7070
pub use cfg::{BasicBlock, CfgEdge, ControlFlowGraph};
7171
pub use const_eval::{ConstEvalContext, ConstEvaluator};
72+
pub use cranelift_backend::{
73+
cranelift_skipped_function_count, reset_cranelift_skipped_function_count,
74+
};
7275
pub use effect_analysis::{
7376
analyze_effects, analyze_effects_with_call_graph, get_function_effect_summary,
7477
has_effect_errors, EffectAnalyzer, EffectCallGraph, EffectError, EffectErrorKind,
@@ -90,6 +93,8 @@ pub use hir::{HirBlock, HirFunction, HirId, HirInstruction, HirModule, HirValue}
9093
pub use hir_builder::HirBuilder; // HIR Builder API
9194
use log::debug;
9295
pub use lowering::{
96+
lowering_skipped_function_count,
97+
reset_lowering_skipped_function_count,
9398
AstLowering,
9499
BuiltinResolver,
95100
ChainedResolver,

crates/compiler/src/llvm_backend.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3291,6 +3291,18 @@ impl<'ctx> LLVMBackend<'ctx> {
32913291
// Return function pointer type
32923292
fn_type.ptr_type(AddressSpace::default()).into()
32933293
}
3294+
Vector(elem_ty, count) => match (&**elem_ty, *count) {
3295+
(F32, 4) => self.context.f32_type().vec_type(4).into(),
3296+
(F64, 2) => self.context.f64_type().vec_type(2).into(),
3297+
(I32, 4) | (U32, 4) => self.context.i32_type().vec_type(4).into(),
3298+
(I64, 2) | (U64, 2) => self.context.i64_type().vec_type(2).into(),
3299+
_ => {
3300+
return Err(CompilerError::CodeGen(format!(
3301+
"unsupported SIMD vector lane shape in LLVM backend: Vector({:?}, {})",
3302+
elem_ty, count
3303+
)));
3304+
}
3305+
},
32943306
_ => {
32953307
return Err(CompilerError::CodeGen(format!(
32963308
"Type translation not yet implemented: {:?}",

crates/compiler/src/lowering.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//! ready for both Cranelift and LLVM backends.
55
66
use std::hash::{Hash, Hasher};
7+
use std::sync::atomic::{AtomicUsize, Ordering};
78
use std::sync::{Arc, Mutex};
89
use zyntax_typed_ast::{
910
AstArena, InternedString, Mutability, Type, TypeId, TypedDeclaration, TypedEffect,
@@ -26,6 +27,18 @@ use crate::ssa::{SsaBuilder, SsaForm};
2627
use crate::CompilerResult;
2728
use std::collections::HashMap;
2829

30+
static LOWERING_SKIPPED_FUNCTIONS: AtomicUsize = AtomicUsize::new(0);
31+
32+
/// Number of functions skipped by lowering due to recoverable errors.
33+
pub fn lowering_skipped_function_count() -> usize {
34+
LOWERING_SKIPPED_FUNCTIONS.load(Ordering::Relaxed)
35+
}
36+
37+
/// Reset lowering skip diagnostics counter.
38+
pub fn reset_lowering_skipped_function_count() {
39+
LOWERING_SKIPPED_FUNCTIONS.store(0, Ordering::Relaxed);
40+
}
41+
2942
/// Target data layout information for precise size and alignment calculations
3043
#[derive(Debug, Clone)]
3144
pub struct TargetData {
@@ -1650,6 +1663,7 @@ impl LoweringContext {
16501663
// but we shouldn't fail the entire compilation for unused code
16511664
if let Err(e) = self.lower_function(func) {
16521665
let func_name = func.name.resolve_global().unwrap_or_default();
1666+
LOWERING_SKIPPED_FUNCTIONS.fetch_add(1, Ordering::Relaxed);
16531667
eprintln!("[LOWERING WARN] Skipping function '{}': {:?}", func_name, e);
16541668
// Remove from symbol table if it was registered
16551669
self.symbols.functions.remove(&func.name);

crates/compiler/src/ssa.rs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ use zyntax_typed_ast::{
2222
/// Internal alias used when lowering `compute(...) { ... }` expressions.
2323
/// This must not collide with user code.
2424
const INTERNAL_COMPUTE_ALIAS: &str = "__internal_compute_dispatch";
25+
const INTERNAL_RENDER_EVENT_ALIAS: &str = "__internal_render_event";
26+
const INTERNAL_STREAM_EVENT_ALIAS: &str = "__internal_stream_event";
2527

2628
/// SSA builder state
2729
pub struct SsaBuilder {
@@ -1957,6 +1959,20 @@ impl SsaBuilder {
19571959
.unwrap_or_default()
19581960
});
19591961

1962+
if name_str == INTERNAL_RENDER_EVENT_ALIAS
1963+
|| name_str == INTERNAL_STREAM_EVENT_ALIAS
1964+
{
1965+
// Internal runtime event calls are lowered as side-effect markers.
1966+
// Evaluate arguments for side effects, then erase the call from HIR.
1967+
for arg in &call.positional_args {
1968+
self.translate_expression(block_id, arg)?;
1969+
}
1970+
for named in &call.named_args {
1971+
self.translate_expression(block_id, &named.value)?;
1972+
}
1973+
return Ok(self.create_undef(HirType::Void));
1974+
}
1975+
19601976
// Check for enum constructors (Some, Ok, Err)
19611977
if name_str == "Some" || name_str == "Ok" || name_str == "Err" {
19621978
return self
@@ -2077,20 +2093,7 @@ impl SsaBuilder {
20772093
self.compute_yield_stack.push(Vec::new());
20782094
let mut current_block = block_id;
20792095
for stmt in &compute.body.statements {
2080-
match &stmt.node {
2081-
zyntax_typed_ast::typed_ast::TypedStatement::Let(_)
2082-
| zyntax_typed_ast::typed_ast::TypedStatement::Expression(_)
2083-
| zyntax_typed_ast::typed_ast::TypedStatement::Yield(_) => {
2084-
current_block = self.process_statement(current_block, stmt)?;
2085-
}
2086-
_ => {
2087-
self.compute_yield_stack.pop();
2088-
return Err(crate::CompilerError::Analysis(
2089-
"compute body fallback currently supports only let/expression/yield statements"
2090-
.to_string(),
2091-
));
2092-
}
2093-
}
2096+
current_block = self.process_statement(current_block, stmt)?;
20942097
}
20952098

20962099
let yields = self.compute_yield_stack.pop().unwrap_or_default();

crates/zynml/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,9 @@ env_logger = "0.11"
3535

3636
[dev-dependencies]
3737
tempfile = "3.16"
38+
zyntax_compiler = { path = "../compiler" }
39+
40+
[features]
41+
default = []
42+
llvm-backend = ["zyntax_embed/llvm-backend"]
43+
all-backends = ["llvm-backend"]

crates/zynml/ml.zyn

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
@builtin {
3232
// Internal dispatch aliases (language-level constructs lowered to plugin calls)
3333
__internal_compute_dispatch: "$ZynML$compute",
34+
__internal_render_event: "$ZynML$render_event",
35+
__internal_stream_event: "$ZynML$stream_event",
3436

3537
// === Tensor Operations (zrtl_tensor) ===
3638
tensor: "tensor_from_array", // tensor([1.0, 2.0]) -> tensor_from_array([1.0, 2.0])
@@ -640,7 +642,7 @@ render_stmt = { stmt:render_with_options | stmt:render_simple }
640642
render_with_options = { "render" ~ value:expr ~ "{" ~ config_entries ~ "}" }
641643
-> TypedStatement::Expression {
642644
expr: TypedExpression::Call {
643-
callee: Box::new(TypedExpression::Variable { name: intern("render") }),
645+
callee: Box::new(TypedExpression::Variable { name: intern("__internal_render_event") }),
644646
args: [value],
645647
named_args: config_entries,
646648
}
@@ -650,7 +652,7 @@ render_with_options = { "render" ~ value:expr ~ "{" ~ config_entries ~ "}" }
650652
render_simple = { "render" ~ value:expr }
651653
-> TypedStatement::Expression {
652654
expr: TypedExpression::Call {
653-
callee: Box::new(TypedExpression::Variable { name: intern("render") }),
655+
callee: Box::new(TypedExpression::Variable { name: intern("__internal_render_event") }),
654656
args: [value],
655657
}
656658
}
@@ -659,12 +661,21 @@ render_simple = { "render" ~ value:expr }
659661
// Stream Statement
660662
// ============================================================================
661663

664+
stream_source_call = { "stream" ~ source:identifier }
665+
-> TypedExpression::Call {
666+
callee: Box::new(TypedExpression::Variable { name: intern("stream") }),
667+
args: [TypedExpression::Variable { name: intern(source) }],
668+
}
669+
670+
stream_chain_expr = { first:stream_source_call ~ rest:pipe_rest* }
671+
-> fold_pipe(first, rest)
672+
662673
// stream sensor_data |> window(100) |> sink(alert_system)
663-
stream_stmt = { "stream" ~ source:identifier ~ ("|>" ~ pipe_call)+ }
674+
stream_stmt = { chain:stream_chain_expr }
664675
-> TypedStatement::Expression {
665676
expr: TypedExpression::Call {
666-
callee: Box::new(TypedExpression::Variable { name: intern("stream") }),
667-
args: [TypedExpression::Variable { name: intern(source) }],
677+
callee: Box::new(TypedExpression::Variable { name: intern("__internal_stream_event") }),
678+
args: [chain],
668679
}
669680
}
670681

@@ -1908,6 +1919,8 @@ statement = {
19081919
break_stmt |
19091920
continue_stmt |
19101921
assign_stmt |
1922+
render_stmt |
1923+
stream_stmt |
19111924
expr_stmt
19121925
}
19131926

0 commit comments

Comments
 (0)