diff --git a/README.md b/README.md index 2af28f1..a591c9b 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,43 @@ cd tachyon ./bolt.sh setup ./bolt.sh build ./bolt.sh test +``` + +## Sample Usage +### Parse and inspect an expression +```rust +use compute::data_type::DataType; +use compute::error::ErrorMode; +use compute::expr::SchemaContext; +use compute::parser::parse_scheme_expr; + +let expr = parse_scheme_expr("(+, a, 10)")?; +let schema = SchemaContext::new() + .with_column("a", DataType::I32) + .with_error_mode(ErrorMode::Tachyon); + +assert_eq!(expr.infer_type(&schema)?, DataType::I32); +``` + +### Evaluate on GPU +```rust +use std::sync::Arc; + +use compute::column::{Column, VecArray}; +use compute::data_type::DataType; +use compute::error::ErrorMode; +use compute::evaluate::{Device, evaluate}; +use compute::expr::Expr; +use compute::operator::Operator; + +// Requires the `gpu` feature and a CUDA-capable environment. +let input = Column::::new( + "a", + Arc::new(VecArray { data: vec![1_i32, 2, 3], datatype: DataType::I32 }), + None, +); +let expr = Expr::binary(Operator::Add, Expr::col("a"), Expr::i32(5)); +let output = evaluate(Device::GPU, ErrorMode::Tachyon, &expr, &[input]).await?; + +assert_eq!(output[0].data_as_slice::(), Some(&[6, 7, 8][..])); +``` diff --git a/tachyon/compute/src/bit_vector.rs b/tachyon/compute/src/bit_vector.rs index 8bcde42..4282ae8 100644 --- a/tachyon/compute/src/bit_vector.rs +++ b/tachyon/compute/src/bit_vector.rs @@ -5,6 +5,7 @@ * as found in the LICENSE file in the root directory of this source tree. */ +/// A bit block primitive used by [`BitVector`]. pub trait BitBlock: Copy + Sized @@ -26,7 +27,7 @@ pub trait BitBlock: const C_TYPE: &'static str; - /// Count the number of 1 bits + /// Counts the number of set bits. fn count_ones(self) -> u32; } @@ -51,19 +52,23 @@ bit_block!(u32, "uint32_t"); bit_block!(u64, "uint64_t"); #[derive(Debug, Clone)] +/// Compact bitmap used for null/valid tracking. pub struct BitVector { bits: Vec, num_bits: usize, } impl BitVector { + /// Creates a bitmap from raw blocks and an explicit logical bit length. pub fn new(bits: Vec, num_bits: usize) -> Self { Self { bits, num_bits } } + /// Creates a bitmap with all entries initialized to null/invalid (`0`). pub fn new_all_null(num_bits: usize) -> Self { Self { bits: vec![T::ZERO; Self::num_blocks(num_bits)], num_bits } } + /// Creates a bitmap with all entries initialized to valid (`1`). pub fn new_all_valid(num_bits: usize) -> Self { let num_blocks = Self::num_blocks(num_bits); let mut bits = vec![T::MAX; num_blocks]; @@ -110,6 +115,7 @@ impl BitVector { self.bits[unit] &= !mask } + /// Returns `true` if the bit at `idx` is valid (`1`). pub fn is_valid(&self, idx: usize) -> bool { if idx >= self.num_bits { panic!("Index out of bounds: is_valid"); @@ -118,22 +124,27 @@ impl BitVector { (self.bits[unit] & mask) != T::ZERO } + /// Counts valid entries. pub fn count_valid(&self) -> usize { self.bits.iter().map(|&block| block.count_ones() as usize).sum() } + /// Counts null entries. pub fn count_null(&self) -> usize { self.num_bits - self.count_valid() } + /// Returns `true` if the bit at `idx` is null (`0`). pub fn is_null(&self, idx: usize) -> bool { !self.is_valid(idx) } + /// Returns the underlying block slice. pub fn as_slice(&self) -> &[T] { self.bits.as_slice() } + /// Returns the underlying block vector. pub fn as_vec(&self) -> &Vec { &self.bits } diff --git a/tachyon/compute/src/codegen.rs b/tachyon/compute/src/codegen.rs index 9bcfc38..370ffcc 100644 --- a/tachyon/compute/src/codegen.rs +++ b/tachyon/compute/src/codegen.rs @@ -16,7 +16,8 @@ use crate::expr::{Expr, Literal, SchemaContext, TypeError}; use crate::operator::Operator; #[derive(Debug, Default)] -pub struct CodeBlock { +/// Mutable code-generation buffer for building kernel snippets. +pub(crate) struct CodeBlock { code: String, var_counter: u16, column_var_map: HashMap, @@ -52,7 +53,8 @@ impl CodeBlock { self.add_code("\t}\n") } - pub fn add_load_column<'a, B: BitBlock>( + /// Emits code to load a source column value into a temporary variable. + pub(crate) fn add_load_column<'a, B: BitBlock>( &'a mut self, col_name: &str, col_idx: u16, col_type: &DataType, ) -> &'a str { if !self.column_var_map.contains_key(col_name) { @@ -69,7 +71,10 @@ impl CodeBlock { self.column_var_map.get(col_name).unwrap() } - pub fn add_store_column(&mut self, col_idx: u16, col_type: &DataType, var: &str) { + /// Emits code to store a temporary value into an output column. + pub(crate) fn add_store_column( + &mut self, col_idx: u16, col_type: &DataType, var: &str, + ) { let kernel_type = col_type.kernel_type(); let bits_type = B::C_TYPE; let code = format!( @@ -84,15 +89,19 @@ impl CodeBlock { var } - pub fn code(&self) -> &str { + /// Returns the generated code buffer. + pub(crate) fn code(&self) -> &str { &self.code } } -pub trait CodeGen { +/// Trait for converting expression nodes to NVRTC kernel code. +pub(crate) trait CodeGen { + /// Appends expression evaluation statements into `code_block`. fn to_nvrtc( &self, schema: &SchemaContext, code_block: &mut CodeBlock, ) -> Result<(), TypeError>; + /// Builds an expression value and returns the temporary variable name. fn build_nvrtc_code( &self, schema: &SchemaContext, code_block: &mut CodeBlock, ) -> Result; @@ -266,7 +275,360 @@ fn op_kernel_fn(op: Operator) -> String { fn escape_c_string(s: &str) -> String { s.replace('"', "\\\"") } -pub fn float_literal_to_str + Copy + PartialEq>(f: T) -> String { +/// Formats floating-point literals for CUDA code generation. +pub(crate) fn float_literal_to_str + Copy + PartialEq>(f: T) -> String { let f64_val = f.into(); if f64_val.fract() == 0.0 { format!("{}.0", f64_val) } else { format!("{}", f64_val) } } + +#[cfg(test)] +mod tests { + use half::{bf16, f16}; + + use crate::codegen::{CodeBlock, CodeGen, float_literal_to_str}; + use crate::data_type::DataType; + use crate::expr::{Expr, SchemaContext}; + use crate::operator::Operator; + + macro_rules! define_type_test { + ($test_name:ident, $col_name:expr, $data_type:expr) => { + #[test] + fn $test_name() { + let schema = SchemaContext::new().with_column($col_name, $data_type); + + let expr = Expr::col($col_name); + assert_eq!( + expr.infer_type(&schema).unwrap(), + $data_type, + "Type inference failed for column '{}'", + $col_name + ); + } + }; + } + + define_type_test!(test_type_inference_i8, "i8_col", DataType::I8); + define_type_test!(test_type_inference_i16, "i16_col", DataType::I16); + define_type_test!(test_type_inference_i32, "i32_col", DataType::I32); + define_type_test!(test_type_inference_i64, "i64_col", DataType::I64); + define_type_test!(test_type_inference_u8, "u8_col", DataType::U8); + define_type_test!(test_type_inference_u16, "u16_col", DataType::U16); + define_type_test!(test_type_inference_u32, "u32_col", DataType::U32); + define_type_test!(test_type_inference_u64, "u64_col", DataType::U64); + define_type_test!(test_type_inference_f32, "f32_col", DataType::F32); + define_type_test!(test_type_inference_f64, "f64_col", DataType::F64); + define_type_test!(test_type_inference_bool, "bool_col", DataType::Bool); + define_type_test!(test_type_inference_str, "str_col", DataType::Str); + + #[test] + fn test_type_inference_unary_neg() { + let expr_neg = Expr::unary(Operator::Neg, Expr::i32(10)); + let schema = SchemaContext::new(); + let inferred = expr_neg.infer_type(&schema).unwrap(); + assert_eq!(inferred, DataType::I32); + } + + #[test] + fn test_type_inference_unary_not() { + let schema = SchemaContext::new().with_column("flag", DataType::Bool); + let expr_not = Expr::unary(Operator::Not, Expr::col("flag")); + let inferred = expr_not.infer_type(&schema).unwrap(); + assert_eq!(inferred, DataType::Bool); + } + + fn normalize_code(code: &str) -> String { + code.lines() + .map(|line| line.trim()) + .filter(|line| !line.is_empty()) + .collect::>() + .join("\n") + } + + #[macro_export] + macro_rules! test_codegen_literal { + ( + $name:ident, + rust_lit = $rust_lit:expr, + expr_ctor = $expr_ctor:expr, + datatype = $datatype:expr, + expected = $expected:expr + ) => { + #[test] + fn $name() { + let schema = SchemaContext::new(); + let expr = $expr_ctor($rust_lit); + + let ty = expr.infer_type(&schema).expect("type infers"); + assert_eq!(ty, $datatype); + + let mut code_block = CodeBlock::default(); + expr.to_nvrtc::(&schema, &mut code_block).expect("codegen"); + + println!("Generated Code:\n{}", code_block.code()); + + assert_eq!(normalize_code(code_block.code()), normalize_code($expected)); + } + }; + } + test_codegen_literal!( + test_codegen_literal_i8, + rust_lit = 10, + expr_ctor = Expr::i8, + datatype = DataType::I8, + expected = r#"Int8 var0; + var0.valid = true; + var0.value = (int8_t)10; + output[0].store(row_idx, var0);"# + ); + + test_codegen_literal!( + test_codegen_literal_i16, + rust_lit = 1000, + expr_ctor = Expr::i16, + datatype = DataType::I16, + expected = r#"Int16 var0; + var0.valid = true; + var0.value = (int16_t)1000; + output[0].store(row_idx, var0);"# + ); + + test_codegen_literal!( + test_codegen_literal_i32, + rust_lit = -123, + expr_ctor = Expr::i32, + datatype = DataType::I32, + expected = r#"Int32 var0; + var0.valid = true; + var0.value = (int32_t)-123; + output[0].store(row_idx, var0);"# + ); + + test_codegen_literal!( + test_codegen_literal_i64, + rust_lit = 12334444, + expr_ctor = Expr::i64, + datatype = DataType::I64, + expected = r#"Int64 var0; + var0.valid = true; + var0.value = (int64_t)12334444ll; + output[0].store(row_idx, var0);"# + ); + + test_codegen_literal!( + test_codegen_literal_u8, + rust_lit = 10, + expr_ctor = Expr::u8, + datatype = DataType::U8, + expected = r#" UInt8 var0; + var0.valid = true; + var0.value = (uint8_t)10; + output[0].store(row_idx, var0);"# + ); + + test_codegen_literal!( + test_codegen_literal_u16, + rust_lit = 1000, + expr_ctor = Expr::u16, + datatype = DataType::U16, + expected = r#"UInt16 var0; + var0.valid = true; + var0.value = (uint16_t)1000; + output[0].store(row_idx, var0);"# + ); + + test_codegen_literal!( + test_codegen_literal_u32, + rust_lit = 5667777, + expr_ctor = Expr::u32, + datatype = DataType::U32, + expected = r#"UInt32 var0; + var0.valid = true; + var0.value = (uint32_t)5667777u; + output[0].store(row_idx, var0);"# + ); + + test_codegen_literal!( + test_codegen_literal_u64, + rust_lit = 100_000_000, + expr_ctor = Expr::u64, + datatype = DataType::U64, + expected = r#"UInt64 var0; + var0.valid = true; + var0.value = (uint64_t)100000000ull; + output[0].store(row_idx, var0);"# + ); + + test_codegen_literal!( + test_codegen_literal_bf16, + rust_lit = bf16::from_f32(2.0), + expr_ctor = Expr::bf16, + datatype = DataType::BF16, + expected = r#"BFloat16 var0; + var0.valid = true; + var0.value = (bfloat16)(__float2bfloat16(2.0f)); + output[0].store(row_idx, var0);"# + ); + + test_codegen_literal!( + test_codegen_literal_f16, + rust_lit = f16::from_f32(1.5), + expr_ctor = Expr::f16, + datatype = DataType::F16, + expected = r#"Float16 var0; + var0.valid = true; + var0.value = (float16)(__float2half(1.5f)); + output[0].store(row_idx, var0);"# + ); + + test_codegen_literal!( + test_codegen_literal_f32, + rust_lit = 1.5f32, + expr_ctor = Expr::f32, + datatype = DataType::F32, + expected = r#"Float32 var0; + var0.valid = true; + var0.value = (float)1.5f; + output[0].store(row_idx, var0);"# + ); + + test_codegen_literal!( + test_codegen_literal_f64, + rust_lit = 1.5e26, + expr_ctor = Expr::f64, + datatype = DataType::F64, + expected = r#"Float64 var0; + var0.valid = true; + var0.value = (double)150000000000000000000000000.0; + output[0].store(row_idx, var0);"# + ); + + test_codegen_literal!( + test_codegen_literal_bool, + rust_lit = false, + expr_ctor = Expr::bool_lit, + datatype = DataType::Bool, + expected = r#"Bool var0; + var0.valid = true; + var0.value = (bool)false; + output[0].store(row_idx, var0);"# + ); + + #[test] + fn test_codegen_unary() { + let schema = SchemaContext::new().with_column("a", DataType::F64); + let expr = Expr::unary(Operator::Neg, Expr::col("a")); + + let ty = expr.infer_type(&schema).expect("type infers"); + assert_eq!(ty, DataType::F64); + + let mut code_block = CodeBlock::default(); + let _ = expr.to_nvrtc::(&schema, &mut code_block).expect("codegen"); + println!("Code:"); + println!("{}", code_block.code()); + let expected = r#"Float64 var0 = input[0].load(row_idx); + var1.valid = var0.valid; + if (var1.valid) { + var1.value = (double)((-(var0.value)).value); + } + output[0].store(row_idx, var1);"#; + assert_eq!(normalize_code(code_block.code()), normalize_code(expected)) + } + + #[test] + fn test_codegen_binary_same_type_cast() { + let schema = SchemaContext::new() + .with_column("a", DataType::F64) + .with_column("b", DataType::F64) + .with_column("flag", DataType::Bool); + + let expr = Expr::binary( + Operator::Add, + Expr::binary(Operator::Mul, Expr::col("a"), Expr::f32(2.5)), + Expr::col("b").cast(DataType::F64), + ); + + let ty = expr.infer_type(&schema).expect("type infers"); + assert_eq!(ty, DataType::F64); + + let mut code_block = CodeBlock::default(); + let _ = expr.to_nvrtc::(&schema, &mut code_block).expect("codegen"); + println!("Code:"); + println!("{}", code_block.code()); + let expected = r#" Float64 var0 = input[0].load(row_idx); + Float32 var1; + var1.valid = true; + var1.value = (float)2.5f; + Float64 var2; + var2.valid = var1.valid; + if (var2.valid) { + var2.value = (double)(var1.value); + } + Float64 var3 = math::mul(ctx, var0, var2); + Float64 var4 = input[1].load(row_idx); + Float64 var5 = math::add(ctx, var3, var4); + output[0].store(row_idx, var5);"#; + assert_eq!(normalize_code(code_block.code()), normalize_code(expected)) + } + + #[test] + fn test_codegen_binary_different_type_cast() { + let schema = SchemaContext::new() + .with_column("a", DataType::F64) + .with_column("b", DataType::I64) + .with_column("flag", DataType::Bool); + + let expr = Expr::binary( + Operator::Add, + Expr::binary(Operator::Mul, Expr::col("a"), Expr::f32(2.5)), + Expr::col("b").cast(DataType::F32), + ); + + let ty = expr.infer_type(&schema).expect("type infers"); + assert_eq!(ty, DataType::F64); + + let mut code_block = CodeBlock::default(); + let _ = expr.to_nvrtc::(&schema, &mut code_block).expect("codegen"); + println!("Code:"); + println!("{}", code_block.code()); + let expected = r#" Float64 var0 = input[0].load(row_idx); + Float32 var1; + var1.valid = true; + var1.value = (float)2.5f; + Float64 var2; + var2.valid = var1.valid; + if (var2.valid) { + var2.value = (double)(var1.value); + } + Float64 var3 = math::mul(ctx, var0, var2); + Int64 var4 = input[1].load(row_idx); + Float32 var5; + var5.valid = var4.valid; + if (var5.valid) { + var5.value = (float)(var4.value); + } + Float64 var6; + var6.valid = var5.valid; + if (var6.valid) { + var6.value = (double)(var5.value); + } + Float64 var7 = math::add(ctx, var3, var6); + output[0].store(row_idx, var7);"#; + assert_eq!(normalize_code(code_block.code()), normalize_code(expected)) + } + + #[test] + fn test_float_literal_str() { + assert_eq!(float_literal_to_str(3.0_f64), "3.0"); + assert_eq!(float_literal_to_str(2.5_f64), "2.5"); + assert_eq!(float_literal_to_str(4.0_f32), "4.0"); + assert_eq!(float_literal_to_str(7.75_f32), "7.75"); + } + + #[test] + fn test_bool_ops() { + let schema = SchemaContext::new().with_column("flag", DataType::Bool); + let e = Expr::binary(Operator::And, Expr::col("flag"), Expr::bool_lit(true)); + let ty = e.infer_type(&schema).unwrap(); + assert_eq!(ty, DataType::Bool); + } +} diff --git a/tachyon/compute/src/column.rs b/tachyon/compute/src/column.rs index 020c84d..87984d8 100644 --- a/tachyon/compute/src/column.rs +++ b/tachyon/compute/src/column.rs @@ -9,21 +9,30 @@ use std::error::Error; use std::fmt::Debug; use std::sync::Arc; -use gpu::ffi::column as gpu_column; +use gpu::column as gpu_column; use half::{bf16, f16}; use crate::bit_vector::{BitBlock, BitVector}; use crate::data_type::DataType; + +/// Type-erased column storage interface. pub trait Array: std::fmt::Debug + Send + Sync { + /// Number of values in the array. fn len(&self) -> usize; + /// Returns `true` when the array has no values. fn is_empty(&self) -> bool; + /// Logical type of the underlying values. fn data_type(&self) -> DataType; + /// Returns a `dyn Any` reference for downcasting. fn as_any(&self) -> &dyn std::any::Any; } #[derive(Debug)] +/// Generic vector-backed implementation of [`Array`]. pub struct VecArray { + /// Typed value buffer. pub data: Vec, + /// Logical data type for `data`. pub datatype: DataType, } @@ -43,10 +52,15 @@ impl Array for VecArray { } #[derive(Debug, Clone)] +/// A named column with values and optional null bitmap. pub struct Column { + /// Column name. pub name: String, + /// Logical value type. pub data_type: DataType, + /// Type-erased values container. pub values: Arc, + /// Null bitmap where `1` indicates valid and `0` indicates null. pub null_bits: Option>, } @@ -70,28 +84,34 @@ macro_rules! to_gpu_column { } impl Column { + /// Creates a new column from an [`Array`] implementation and optional null bitmap. pub fn new( name: &str, values: Arc, null_bits: Option>, ) -> Self { Self { name: name.to_string(), data_type: values.data_type(), values, null_bits } } + /// Number of rows in the column. pub fn len(&self) -> usize { self.values.len() } + /// Returns `true` when this column has no rows. pub fn is_empty(&self) -> bool { self.values.is_empty() } + /// Returns `true` when this column carries a null bitmap. pub fn have_null(&self) -> bool { self.null_bits.is_some() } + /// Attempts to view the underlying values as a typed slice. pub fn data_as_slice(&self) -> Option<&[T]> { self.values.as_any().downcast_ref::>().map(|a| a.data.as_slice()) } + /// Converts a GPU column into a host [`Column`]. pub fn from_gpu_column( column: &gpu_column::Column, name: &str, data_type: DataType, ) -> Result> { @@ -114,6 +134,7 @@ impl Column { Ok(col) } + /// Converts this host column into its GPU representation. pub fn to_gpu_column(&self) -> Result> { match self.data_type { DataType::I8 => to_gpu_column!(self, i8), @@ -133,6 +154,7 @@ impl Column { } } + /// Returns the null bitmap if one is present. pub fn null_bits_as_slice(&self) -> Option<&BitVector> { self.null_bits.as_ref() } diff --git a/tachyon/compute/src/data_type.rs b/tachyon/compute/src/data_type.rs index 859fe13..0ef826d 100644 --- a/tachyon/compute/src/data_type.rs +++ b/tachyon/compute/src/data_type.rs @@ -7,6 +7,7 @@ use half::{bf16, f16}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// Logical value types supported by Tachyon expressions and columns. pub enum DataType { Bool, I8, @@ -25,6 +26,7 @@ pub enum DataType { } impl DataType { + /// Returns the native Rust memory size for this type. pub fn native_size(&self) -> usize { match self { DataType::Bool => std::mem::size_of::(), @@ -44,6 +46,7 @@ impl DataType { } } + /// Returns the C/CUDA type name used in generated kernels. pub fn c_type(&self) -> &'static str { match self { DataType::Bool => "bool", @@ -63,6 +66,7 @@ impl DataType { } } + /// Returns the internal kernel type discriminator. pub fn kernel_type(&self) -> &'static str { match self { DataType::Bool => "Bool", @@ -82,14 +86,17 @@ impl DataType { } } + /// Returns `true` for signed integer types. pub fn is_signed(&self) -> bool { matches!(self, DataType::I8 | DataType::I16 | DataType::I32 | DataType::I64) } + /// Returns `true` for unsigned integer types. pub fn is_unsigned(&self) -> bool { matches!(self, DataType::U8 | DataType::U16 | DataType::U32 | DataType::U64) } + /// Returns `true` for integer types. pub fn is_integer(&self) -> bool { matches!( self, @@ -104,10 +111,12 @@ impl DataType { ) } + /// Returns `true` for floating-point types. pub fn is_float(&self) -> bool { matches!(self, DataType::BF16 | DataType::F16 | DataType::F32 | DataType::F64) } + /// Returns `true` for all numeric types. pub fn is_numeric(&self) -> bool { matches!( self, @@ -126,10 +135,12 @@ impl DataType { ) } + /// Returns `true` for UTF-8 string values. pub fn is_string(&self) -> bool { matches!(self, DataType::Str) } + /// Returns `true` for boolean values. pub fn is_boolean(&self) -> bool { matches!(self, DataType::Bool) } diff --git a/tachyon/compute/src/error.rs b/tachyon/compute/src/error.rs index b96c7a6..faa08f3 100644 --- a/tachyon/compute/src/error.rs +++ b/tachyon/compute/src/error.rs @@ -6,13 +6,17 @@ */ #[derive(Clone, Copy, Debug, PartialEq)] +/// Error semantics used during expression evaluation. pub enum ErrorMode { + /// ANSI-like semantics (for example, overflow returns an error). Ansi, + /// Tachyon-native semantics. Tachyon, } #[repr(C)] #[derive(Debug, thiserror::Error)] +/// Arithmetic/runtime error emitted by generated kernels. pub enum MathError { #[error("Add Overflow")] AddOverflow, diff --git a/tachyon/compute/src/evaluate.rs b/tachyon/compute/src/evaluate.rs index e97e278..1d291da 100644 --- a/tachyon/compute/src/evaluate.rs +++ b/tachyon/compute/src/evaluate.rs @@ -7,8 +7,7 @@ use std::collections::HashMap; use std::error::Error; -use gpu::cuda_launcher; -use gpu::ffi::column as gpu_column; +use gpu::column as gpu_column; use crate::bit_vector::BitBlock; use crate::codegen::{CodeBlock, CodeGen}; @@ -19,10 +18,17 @@ use crate::expr::{Expr, SchemaContext}; use crate::operator::Operator; #[derive(Debug, Clone, PartialEq, Eq)] +/// Execution target for expression evaluation. pub enum Device { + /// Evaluate expressions on the GPU backend. GPU, } +/// Evaluates an expression against a set of input columns. +/// +/// Returns a vector of output columns. Row-wise expressions produce one output +/// column with one value per input row. Aggregate expressions produce a single +/// row output. pub async fn evaluate( device: Device, error_mode: ErrorMode, expr: &Expr, columns: &[Column], ) -> Result>, Box> { @@ -70,7 +76,7 @@ async fn evaluate_gpu_row( )?; output_cols.push(gpu_col); - cuda_launcher::launch::(code_block.code(), &input_cols, &output_cols).await?; + gpu::launch::(code_block.code(), &input_cols, &output_cols).await?; let result_cols = output_cols .into_iter() @@ -118,7 +124,7 @@ async fn evaluate_gpu_aggregate( )?; output_cols.push(gpu_col); - cuda_launcher::launch_aggregate::(&code, &input_cols, &output_cols).await?; + gpu::launch_aggregate::(&code, &input_cols, &output_cols).await?; let result_cols = output_cols .into_iter() diff --git a/tachyon/compute/src/expr.rs b/tachyon/compute/src/expr.rs index 9d1a55e..85eca0d 100644 --- a/tachyon/compute/src/expr.rs +++ b/tachyon/compute/src/expr.rs @@ -15,6 +15,7 @@ use crate::error::ErrorMode; pub use crate::operator::Operator; #[derive(Debug, Clone, PartialEq)] +/// Literal values representable in the expression AST. pub enum Literal { I8(i8), I16(i16), @@ -33,6 +34,7 @@ pub enum Literal { } #[derive(Debug, Clone, PartialEq)] +/// Expression tree used for planning, code generation, and evaluation. pub enum Expr { Column(String), @@ -52,10 +54,12 @@ pub enum Expr { } impl Expr { + /// Creates a column reference expression. pub fn col>(name: S) -> Self { Expr::Column(name.into()) } + /// Creates a literal expression. pub fn lit(l: Literal) -> Self { Expr::Literal(l) } @@ -112,26 +116,32 @@ impl Expr { Expr::Literal(Literal::Bool(b)) } + /// Creates a binary expression with `left right`. pub fn binary(op: Operator, left: Expr, right: Expr) -> Self { Expr::Binary { op, left: Box::new(left), right: Box::new(right) } } + /// Creates a unary expression. pub fn unary(op: Operator, expr: Expr) -> Self { Expr::Unary { op, expr: Box::new(expr) } } + /// Creates a function call expression. pub fn call>(name: N, args: Vec) -> Self { Expr::Call { name: name.into(), args } } + /// Casts this expression to a target [`DataType`]. pub fn cast(self, to: DataType) -> Self { Expr::Cast { expr: Box::new(self), to } } + /// Creates an aggregate expression. pub fn aggregate(op: Operator, arg: Expr, distinct: bool) -> Self { Expr::Aggregate { op, arg: Box::new(arg), distinct } } + /// Returns direct children of this expression node. pub fn children(&self) -> Vec<&Expr> { match self { Expr::Column(_) | Expr::Literal(_) => vec![], @@ -145,6 +155,7 @@ impl Expr { } } +/// Visitor for traversing an [`Expr`] tree. pub trait ExprVisitor { fn enter(&mut self, _expr: &Expr) -> bool { true @@ -153,6 +164,7 @@ pub trait ExprVisitor { fn exit(&mut self, _expr: &Expr) {} } +/// Walks an expression tree in depth-first order. pub fn walk_expr(expr: &Expr, visitor: &mut V) { if !visitor.enter(expr) { return; @@ -164,6 +176,7 @@ pub fn walk_expr(expr: &Expr, visitor: &mut V) { } #[derive(Debug, thiserror::Error)] +/// Type inference and validation errors for expressions. pub enum TypeError { #[error("unknown column: {0}")] UnknownColumn(String), @@ -176,36 +189,45 @@ pub enum TypeError { } #[derive(Debug, Clone)] +/// Column/type bindings and evaluation options used by expression analysis. pub struct SchemaContext { + /// Known columns and their `(index, type)` mapping. pub columns: HashMap, + /// Error behavior used by generated code. pub error_mode: ErrorMode, } impl SchemaContext { + /// Creates an empty schema context. pub fn new() -> Self { Self { columns: Default::default(), error_mode: ErrorMode::Tachyon } } + /// Replaces known columns with `columns`. pub fn with_columns(mut self, columns: &HashMap) -> Self { self.columns = columns.clone(); self } + /// Sets error mode for downstream planning/evaluation. pub fn with_error_mode(mut self, error_mode: ErrorMode) -> Self { self.error_mode = error_mode; self } + /// Adds a single named column and inferred index. pub fn with_column>(mut self, name: S, dt: DataType) -> Self { let size = self.columns.len(); self.columns.insert(name.into(), (size.try_into().unwrap(), dt)); self } + /// Looks up `(index, type)` metadata for a column name. pub fn lookup(&self, name: &str) -> Option<&(u16, DataType)> { self.columns.get(name) } + /// Returns the configured error mode. pub fn error_mode(&self) -> ErrorMode { self.error_mode } @@ -218,6 +240,7 @@ impl Default for SchemaContext { } impl Expr { + /// Infers the output [`DataType`] for an expression under a given schema. pub fn infer_type(&self, schema: &SchemaContext) -> Result { match self { Expr::Column(name) => match schema.lookup(name) { diff --git a/tachyon/compute/src/lib.rs b/tachyon/compute/src/lib.rs index 6fe7f02..68695a8 100644 --- a/tachyon/compute/src/lib.rs +++ b/tachyon/compute/src/lib.rs @@ -1,3 +1,9 @@ +//! Compute engine APIs for expression parsing, planning, and evaluation. +//! +//! This crate exposes the core DataFrame compute primitives used by Tachyon, +//! including expression ASTs, column containers, parser helpers, and GPU-backed +//! expression evaluation. + /* * Copyright (c) NeoCraft Technologies. * @@ -5,12 +11,21 @@ * as found in the LICENSE file in the root directory of this source tree. */ +/// Bitset utilities used for null tracking in columns. pub mod bit_vector; -pub mod codegen; +/// Code generation interfaces used to produce execution kernels. +mod codegen; +/// Columnar in-memory data containers. pub mod column; +/// Logical and physical data type definitions. pub mod data_type; +/// Error handling modes and math/runtime errors. pub mod error; +/// Expression evaluation entry points. pub mod evaluate; +/// Expression AST and type inference utilities. pub mod expr; +/// Operators used by expressions. pub mod operator; +/// Parser for scheme-style expression syntax. pub mod parser; diff --git a/tachyon/compute/src/operator.rs b/tachyon/compute/src/operator.rs index cc43186..0f3b83d 100644 --- a/tachyon/compute/src/operator.rs +++ b/tachyon/compute/src/operator.rs @@ -6,6 +6,7 @@ */ #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Hash)] +/// Operators supported by the expression AST. pub enum Operator { Add, Sub, @@ -88,6 +89,7 @@ impl From<&str> for Operator { } impl Operator { + /// Returns the printable symbol/name for the operator. pub fn as_symbol(&self) -> &str { match self { Operator::Add => "+", diff --git a/tachyon/compute/src/parser.rs b/tachyon/compute/src/parser.rs index 26cb171..3542f92 100644 --- a/tachyon/compute/src/parser.rs +++ b/tachyon/compute/src/parser.rs @@ -10,6 +10,7 @@ use crate::expr::{Expr, Literal}; use crate::operator::Operator; #[derive(Debug, thiserror::Error)] +/// Parsing errors for scheme-style expression syntax. pub enum ParseError { #[error("unexpected end of input")] UnexpectedEof, @@ -208,13 +209,15 @@ impl Lexer { } } -pub struct Parser { +/// Parses scheme-style expression strings into [`Expr`] trees. +struct Parser { tokens: Vec, pos: usize, } impl Parser { - pub fn new(input: &str) -> Result { + /// Creates a parser from input text. + fn new(input: &str) -> Result { let mut lexer = Lexer::new(input); let mut tokens = Vec::new(); @@ -277,7 +280,7 @@ impl Parser { Err(ParseError::InvalidLiteral(s.to_string())) } - pub fn parse_expr(&mut self) -> Result { + fn parse_expr(&mut self) -> Result { match self.peek() { Some(Token::OpenParen) => self.parse_call(), Some(Token::Ident(name)) => { @@ -401,11 +404,13 @@ impl Parser { } } - pub fn parse(&mut self) -> Result { + /// Parses the full input expression. + fn parse(&mut self) -> Result { self.parse_expr() } } +/// Convenience helper to parse a single scheme-style expression. pub fn parse_scheme_expr(input: &str) -> Result { let mut parser = Parser::new(input)?; parser.parse() diff --git a/tachyon/compute/tests/codegen_tests.rs b/tachyon/compute/tests/codegen_tests.rs deleted file mode 100644 index 2d41752..0000000 --- a/tachyon/compute/tests/codegen_tests.rs +++ /dev/null @@ -1,347 +0,0 @@ -use compute::codegen::{CodeBlock, CodeGen, float_literal_to_str}; -use compute::data_type::DataType; -use compute::expr::{Expr, SchemaContext}; -use compute::operator::Operator; -use half::{bf16, f16}; - -macro_rules! define_type_test { - ($test_name:ident, $col_name:expr, $data_type:expr) => { - #[test] - fn $test_name() { - let schema = SchemaContext::new().with_column($col_name, $data_type); - - let expr = Expr::col($col_name); - assert_eq!( - expr.infer_type(&schema).unwrap(), - $data_type, - "Type inference failed for column '{}'", - $col_name - ); - } - }; -} - -define_type_test!(test_type_inference_i8, "i8_col", DataType::I8); -define_type_test!(test_type_inference_i16, "i16_col", DataType::I16); -define_type_test!(test_type_inference_i32, "i32_col", DataType::I32); -define_type_test!(test_type_inference_i64, "i64_col", DataType::I64); -define_type_test!(test_type_inference_u8, "u8_col", DataType::U8); -define_type_test!(test_type_inference_u16, "u16_col", DataType::U16); -define_type_test!(test_type_inference_u32, "u32_col", DataType::U32); -define_type_test!(test_type_inference_u64, "u64_col", DataType::U64); -define_type_test!(test_type_inference_f32, "f32_col", DataType::F32); -define_type_test!(test_type_inference_f64, "f64_col", DataType::F64); -define_type_test!(test_type_inference_bool, "bool_col", DataType::Bool); -define_type_test!(test_type_inference_str, "str_col", DataType::Str); - -#[test] -fn test_type_inference_unary_neg() { - let expr_neg = Expr::unary(Operator::Neg, Expr::i32(10)); - let schema = SchemaContext::new(); - let inferred = expr_neg.infer_type(&schema).unwrap(); - assert_eq!(inferred, DataType::I32); -} - -#[test] -fn test_type_inference_unary_not() { - let schema = SchemaContext::new().with_column("flag", DataType::Bool); - let expr_not = Expr::unary(Operator::Not, Expr::col("flag")); - let inferred = expr_not.infer_type(&schema).unwrap(); - assert_eq!(inferred, DataType::Bool); -} - -fn normalize_code(code: &str) -> String { - code.lines() - .map(|line| line.trim()) - .filter(|line| !line.is_empty()) - .collect::>() - .join("\n") -} - -#[macro_export] -macro_rules! test_codegen_literal { - ( - $name:ident, - rust_lit = $rust_lit:expr, - expr_ctor = $expr_ctor:expr, - datatype = $datatype:expr, - expected = $expected:expr - ) => { - #[test] - fn $name() { - let schema = SchemaContext::new(); - let expr = $expr_ctor($rust_lit); - - let ty = expr.infer_type(&schema).expect("type infers"); - assert_eq!(ty, $datatype); - - let mut code_block = CodeBlock::default(); - expr.to_nvrtc::(&schema, &mut code_block).expect("codegen"); - - println!("Generated Code:\n{}", code_block.code()); - - assert_eq!(normalize_code(code_block.code()), normalize_code($expected)); - } - }; -} -test_codegen_literal!( - test_codegen_literal_i8, - rust_lit = 10, - expr_ctor = Expr::i8, - datatype = DataType::I8, - expected = r#"Int8 var0; - var0.valid = true; - var0.value = (int8_t)10; - output[0].store(row_idx, var0);"# -); - -test_codegen_literal!( - test_codegen_literal_i16, - rust_lit = 1000, - expr_ctor = Expr::i16, - datatype = DataType::I16, - expected = r#"Int16 var0; - var0.valid = true; - var0.value = (int16_t)1000; - output[0].store(row_idx, var0);"# -); - -test_codegen_literal!( - test_codegen_literal_i32, - rust_lit = -123, - expr_ctor = Expr::i32, - datatype = DataType::I32, - expected = r#"Int32 var0; - var0.valid = true; - var0.value = (int32_t)-123; - output[0].store(row_idx, var0);"# -); - -test_codegen_literal!( - test_codegen_literal_i64, - rust_lit = 12334444, - expr_ctor = Expr::i64, - datatype = DataType::I64, - expected = r#"Int64 var0; - var0.valid = true; - var0.value = (int64_t)12334444ll; - output[0].store(row_idx, var0);"# -); - -test_codegen_literal!( - test_codegen_literal_u8, - rust_lit = 10, - expr_ctor = Expr::u8, - datatype = DataType::U8, - expected = r#" UInt8 var0; - var0.valid = true; - var0.value = (uint8_t)10; - output[0].store(row_idx, var0);"# -); - -test_codegen_literal!( - test_codegen_literal_u16, - rust_lit = 1000, - expr_ctor = Expr::u16, - datatype = DataType::U16, - expected = r#"UInt16 var0; - var0.valid = true; - var0.value = (uint16_t)1000; - output[0].store(row_idx, var0);"# -); - -test_codegen_literal!( - test_codegen_literal_u32, - rust_lit = 5667777, - expr_ctor = Expr::u32, - datatype = DataType::U32, - expected = r#"UInt32 var0; - var0.valid = true; - var0.value = (uint32_t)5667777u; - output[0].store(row_idx, var0);"# -); - -test_codegen_literal!( - test_codegen_literal_u64, - rust_lit = 100_000_000, - expr_ctor = Expr::u64, - datatype = DataType::U64, - expected = r#"UInt64 var0; - var0.valid = true; - var0.value = (uint64_t)100000000ull; - output[0].store(row_idx, var0);"# -); - -test_codegen_literal!( - test_codegen_literal_bf16, - rust_lit = bf16::from_f32(2.0), - expr_ctor = Expr::bf16, - datatype = DataType::BF16, - expected = r#"BFloat16 var0; - var0.valid = true; - var0.value = (bfloat16)(__float2bfloat16(2.0f)); - output[0].store(row_idx, var0);"# -); - -test_codegen_literal!( - test_codegen_literal_f16, - rust_lit = f16::from_f32(1.5), - expr_ctor = Expr::f16, - datatype = DataType::F16, - expected = r#"Float16 var0; - var0.valid = true; - var0.value = (float16)(__float2half(1.5f)); - output[0].store(row_idx, var0);"# -); - -test_codegen_literal!( - test_codegen_literal_f32, - rust_lit = 1.5f32, - expr_ctor = Expr::f32, - datatype = DataType::F32, - expected = r#"Float32 var0; - var0.valid = true; - var0.value = (float)1.5f; - output[0].store(row_idx, var0);"# -); - -test_codegen_literal!( - test_codegen_literal_f64, - rust_lit = 1.5e26, - expr_ctor = Expr::f64, - datatype = DataType::F64, - expected = r#"Float64 var0; - var0.valid = true; - var0.value = (double)150000000000000000000000000.0; - output[0].store(row_idx, var0);"# -); - -test_codegen_literal!( - test_codegen_literal_bool, - rust_lit = false, - expr_ctor = Expr::bool_lit, - datatype = DataType::Bool, - expected = r#"Bool var0; - var0.valid = true; - var0.value = (bool)false; - output[0].store(row_idx, var0);"# -); - -#[test] -fn test_codegen_unary() { - let schema = SchemaContext::new().with_column("a", DataType::F64); - let expr = Expr::unary(Operator::Neg, Expr::col("a")); - - let ty = expr.infer_type(&schema).expect("type infers"); - assert_eq!(ty, DataType::F64); - - let mut code_block = CodeBlock::default(); - let _ = expr.to_nvrtc::(&schema, &mut code_block).expect("codegen"); - println!("Code:"); - println!("{}", code_block.code()); - let expected = r#"Float64 var0 = input[0].load(row_idx); - var1.valid = var0.valid; - if (var1.valid) { - var1.value = (double)((-(var0.value)).value); - } - output[0].store(row_idx, var1);"#; - assert_eq!(normalize_code(code_block.code()), normalize_code(expected)) -} - -#[test] -fn test_codegen_binary_same_type_cast() { - let schema = SchemaContext::new() - .with_column("a", DataType::F64) - .with_column("b", DataType::F64) - .with_column("flag", DataType::Bool); - - let expr = Expr::binary( - Operator::Add, - Expr::binary(Operator::Mul, Expr::col("a"), Expr::f32(2.5)), - Expr::col("b").cast(DataType::F64), - ); - - let ty = expr.infer_type(&schema).expect("type infers"); - assert_eq!(ty, DataType::F64); - - let mut code_block = CodeBlock::default(); - let _ = expr.to_nvrtc::(&schema, &mut code_block).expect("codegen"); - println!("Code:"); - println!("{}", code_block.code()); - let expected = r#" Float64 var0 = input[0].load(row_idx); - Float32 var1; - var1.valid = true; - var1.value = (float)2.5f; - Float64 var2; - var2.valid = var1.valid; - if (var2.valid) { - var2.value = (double)(var1.value); - } - Float64 var3 = math::mul(ctx, var0, var2); - Float64 var4 = input[1].load(row_idx); - Float64 var5 = math::add(ctx, var3, var4); - output[0].store(row_idx, var5);"#; - assert_eq!(normalize_code(code_block.code()), normalize_code(expected)) -} - -#[test] -fn test_codegen_binary_different_type_cast() { - let schema = SchemaContext::new() - .with_column("a", DataType::F64) - .with_column("b", DataType::I64) - .with_column("flag", DataType::Bool); - - let expr = Expr::binary( - Operator::Add, - Expr::binary(Operator::Mul, Expr::col("a"), Expr::f32(2.5)), - Expr::col("b").cast(DataType::F32), - ); - - let ty = expr.infer_type(&schema).expect("type infers"); - assert_eq!(ty, DataType::F64); - - let mut code_block = CodeBlock::default(); - let _ = expr.to_nvrtc::(&schema, &mut code_block).expect("codegen"); - println!("Code:"); - println!("{}", code_block.code()); - let expected = r#" Float64 var0 = input[0].load(row_idx); - Float32 var1; - var1.valid = true; - var1.value = (float)2.5f; - Float64 var2; - var2.valid = var1.valid; - if (var2.valid) { - var2.value = (double)(var1.value); - } - Float64 var3 = math::mul(ctx, var0, var2); - Int64 var4 = input[1].load(row_idx); - Float32 var5; - var5.valid = var4.valid; - if (var5.valid) { - var5.value = (float)(var4.value); - } - Float64 var6; - var6.valid = var5.valid; - if (var6.valid) { - var6.value = (double)(var5.value); - } - Float64 var7 = math::add(ctx, var3, var6); - output[0].store(row_idx, var7);"#; - assert_eq!(normalize_code(code_block.code()), normalize_code(expected)) -} - -#[test] -fn test_float_literal_str() { - assert_eq!(float_literal_to_str(3.0_f64), "3.0"); - assert_eq!(float_literal_to_str(2.5_f64), "2.5"); - assert_eq!(float_literal_to_str(4.0_f32), "4.0"); - assert_eq!(float_literal_to_str(7.75_f32), "7.75"); -} - -#[test] -fn test_bool_ops() { - let schema = SchemaContext::new().with_column("flag", DataType::Bool); - let e = Expr::binary(Operator::And, Expr::col("flag"), Expr::bool_lit(true)); - let ty = e.infer_type(&schema).unwrap(); - assert_eq!(ty, DataType::Bool); -} diff --git a/tachyon/gpu/src/column.rs b/tachyon/gpu/src/column.rs new file mode 100644 index 0000000..691a595 --- /dev/null +++ b/tachyon/gpu/src/column.rs @@ -0,0 +1,8 @@ +/* + * Copyright (c) NeoCraft Technologies. + * + * This source code is licensed under the Apache License, Version 2.0, + * as found in the LICENSE file in the root directory of this source tree. + */ + +pub use crate::ffi::column::Column; diff --git a/tachyon/gpu/src/cuda_launcher.rs b/tachyon/gpu/src/cuda_launcher.rs index 1f84d0d..e0eeabc 100644 --- a/tachyon/gpu/src/cuda_launcher.rs +++ b/tachyon/gpu/src/cuda_launcher.rs @@ -80,7 +80,8 @@ fn compose_aggregate_kernel_source(kernel_name: &str, code: &str) -> String { kernel_source } -pub fn kernel_name(code: &str) -> String { +/// Computes a stable kernel symbol name from generated code. +fn kernel_name(code: &str) -> String { scoped_kernel_name("kernel", code, EVAL_KERNEL_HEADERS_FINGERPRINT) } @@ -169,6 +170,7 @@ async fn launch_aggregate_kernel( Ok(()) } +/// Builds/loads and launches a row-wise GPU kernel. pub async fn launch(code: &str, input: &[Column], output: &[Column]) -> GpuResult<()> { cuda::init_cuda()?; let device = cuda::get_device(0)?; @@ -204,6 +206,7 @@ pub async fn launch(code: &str, input: &[Column], output: &[Column]) - Ok(()) } +/// Builds/loads and launches an aggregate GPU kernel. pub async fn launch_aggregate( code: &str, input: &[Column], output: &[Column], ) -> GpuResult<()> { @@ -244,6 +247,7 @@ pub async fn launch_aggregate( #[repr(C)] #[derive(Clone, Copy, Debug)] +/// Device context shared with kernels for error reporting. pub struct ContextFFI { pub error_code: u32, } diff --git a/tachyon/gpu/src/error.rs b/tachyon/gpu/src/error.rs index 6ebe381..13cb12b 100644 --- a/tachyon/gpu/src/error.rs +++ b/tachyon/gpu/src/error.rs @@ -9,6 +9,7 @@ use thiserror::Error; use crate::ffi::cuda_error::CudaError; #[derive(Debug, Error)] +/// Errors returned by the GPU execution layer. pub enum GpuError { #[error("CUDA error: {0}")] Cuda(#[from] CudaError), @@ -16,4 +17,5 @@ pub enum GpuError { Math(String), } +/// Result alias for GPU operations. pub type GpuResult = Result; diff --git a/tachyon/gpu/src/ffi/column.rs b/tachyon/gpu/src/ffi/column.rs index 79ae02b..8d84c4a 100644 --- a/tachyon/gpu/src/ffi/column.rs +++ b/tachyon/gpu/src/ffi/column.rs @@ -68,7 +68,7 @@ impl Column { self.len() == 0 } - pub fn as_ffi_column(&self) -> ColumnFFI { + pub(crate) fn as_ffi_column(&self) -> ColumnFFI { let data_ptr = self.data_memory.device_ptr(); let validity_ptr = @@ -100,7 +100,7 @@ impl Column { #[repr(C)] #[derive(Debug)] -pub struct ColumnFFI { +pub(crate) struct ColumnFFI { pub data: *const std::os::raw::c_void, pub null_bits: *const B, pub size: usize, diff --git a/tachyon/gpu/src/lib.rs b/tachyon/gpu/src/lib.rs index ceaddd5..3c885a8 100644 --- a/tachyon/gpu/src/lib.rs +++ b/tachyon/gpu/src/lib.rs @@ -1,3 +1,5 @@ +//! GPU runtime and CUDA FFI helpers for Tachyon compute execution. + /* * Copyright (c) NeoCraft Technologies. * @@ -5,7 +7,13 @@ * as found in the LICENSE file in the root directory of this source tree. */ -pub mod cuda_launcher; +/// Public GPU column facade. +pub mod column; +mod cuda_launcher; +/// GPU-facing error types. pub mod error; -pub mod ffi; +/// Low-level CUDA and column FFI bindings. +mod ffi; pub(crate) mod kernel_cache; //TODO Hide it + +pub use cuda_launcher::{launch, launch_aggregate};