Skip to content
Merged
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
4 changes: 2 additions & 2 deletions descend_derive/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::path::Path;
fn main() {
// Tell Cargo to re-run this build script if any .desc files change
let examples_dir = "examples/core";

if Path::new(examples_dir).exists() {
// Walk through the directory and tell Cargo to re-run if any .desc files change
if let Ok(entries) = fs::read_dir(examples_dir) {
Expand All @@ -17,7 +17,7 @@ fn main() {
}
}
}

// Also watch the entire directory for new files
println!("cargo:rerun-if-changed={}", examples_dir);
}
7 changes: 7 additions & 0 deletions examples/core/assign.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn assign<n: nat, r: prv>(
a: &r shrd gpu.global [i16; 16],
b: &r uniq gpu.global [i16; 16]
) -[grid: gpu.grid<X<1>, X<16>>]-> () {
b = a;
()
}
File renamed without changes.
7 changes: 7 additions & 0 deletions examples/core/vdiv.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn div<n: nat, r: prv>(
a: &r shrd gpu.global [i16; 16],
b: &r shrd gpu.global [i16; 16]
) -[grid: gpu.grid<X<1>, X<16>>]-> () {
a / b;
()
}
48 changes: 48 additions & 0 deletions examples/core/vec_add.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Vector addition kernel demonstrating Descend's safe GPU programming model
// This function showcases extended borrow checking, memory safety, and execution context tracking

// Generic function with type parameters:
// - n: nat - Natural number parameter (for array size, though not used in this specific function)
// - r: prv - Provenance parameter tracking memory region/lifetime for all references
fn add<n: nat, r: prv>(
// Shared reference to first input vector - multiple threads can read simultaneously
// Memory space: gpu.global (GPU global memory)
// Ownership: shrd (shared) - prevents write-after-read data races
// Type: 16-element array of 16-bit signed integers
a: &r shrd gpu.global [i16; 16],

// Shared reference to second input vector - multiple threads can read simultaneously
// Same memory space and ownership constraints as 'a'
b: &r shrd gpu.global [i16; 16],

// Unique reference to output vector - only one thread can write at a time
// Ownership: uniq (unique) - prevents write-after-write data races
// The compiler statically ensures no conflicting borrows exist
c: &r uniq gpu.global [i16; 16]

// Execution context specification - defines how this function runs on GPU hardware
// - grid: gpu.grid<X<1>, X<16>> - GPU execution grid with 1 block containing 16 threads
// - The type system ensures GPU memory is only accessed in GPU execution contexts
// - Prevents invalid cross-device memory accesses (CPU accessing GPU memory)
) -[grid: gpu.grid<X<1>, X<16>>]-> () {

// Vector addition operation - element-wise addition of arrays
// The compiler generates safe parallel code that:
// 1. Loads data from global memory to local memory for each thread
// 2. Performs vectorized addition using HIVM dialect operations
// 3. Stores results back to global memory safely
// The ownership system ensures this operation is race-free
//
// LAZY LOADING: Descend's compiler implements lazy loading strategies:
// - Memory loads are deferred until actually needed by computation
// - The HIVM dialect generates 'hivm.hir.load' operations that load from
// global memory (gm) to local memory (ub) only when data is accessed
// - This minimizes memory bandwidth usage and improves cache efficiency
// - The type system ensures loads happen in the correct execution context
// - Shared references enable read-only access without unnecessary copies
c = a + b;

// Unit return value - indicates successful completion
// In MLIR, this becomes a 'return' operation
()
}
8 changes: 0 additions & 8 deletions examples/core/vec_add.desc.off

This file was deleted.

8 changes: 0 additions & 8 deletions examples/core/vec_add_1.desc.off

This file was deleted.

File renamed without changes.
7 changes: 7 additions & 0 deletions examples/core/vmul.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn mul<n: nat, r: prv>(
a: &r shrd gpu.global [i16; 16],
b: &r shrd gpu.global [i16; 16]
) -[grid: gpu.grid<X<1>, X<16>>]-> () {
a * b;
()
}
7 changes: 7 additions & 0 deletions examples/error-examples/assign_to_shared_ref.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn assign_to_shared_ref<n: nat, r: prv>(
a: &r shrd gpu.global [i16; 16],
b: &r shrd gpu.global [i16; 16]
) -[grid: gpu.grid<X<1>, X<16>>]-> () {
b = a; // This should fail - cannot assign to shared reference
()
}
50 changes: 50 additions & 0 deletions examples/error-examples/vec_add_memory_issue.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Vector addition kernel demonstrating Descend's safe GPU programming model
// This function showcases extended borrow checking, memory safety, and execution context tracking

// Generic function with type parameters:
// - n: nat - Natural number parameter (for array size, though not used in this specific function)
// - r: prv - Provenance parameter tracking memory region/lifetime for all references
fn add<n: nat, r: prv>(
// Shared reference to first input vector - multiple threads can read simultaneously
// Memory space: gpu.global (GPU global memory)
// Ownership: shrd (shared) - prevents write-after-read data races
// Type: 16-element array of 16-bit signed integers
a: &r shrd gpu.global [i16; 16],

// Shared reference to second input vector - multiple threads can read simultaneously
// Same memory space and ownership constraints as 'a'
b: &r shrd gpu.global [i16; 16],

// ERROR: This parameter should be declared as 'unq' (unique) instead of 'shrd' (shared)
// because it's used for assignment (c = a + b). Shared references are read-only and
// prevent data races by allowing multiple concurrent readers. Unique references are
// required for write operations to ensure exclusive access and prevent race conditions.
// The Descend compiler will detect this ownership violation and fail compilation.
c: &r shrd gpu.global [i16; 16]

// Execution context specification - defines how this function runs on GPU hardware
// - grid: gpu.grid<X<1>, X<16>> - GPU execution grid with 1 block containing 16 threads
// - The type system ensures GPU memory is only accessed in GPU execution contexts
// - Prevents invalid cross-device memory accesses (CPU accessing GPU memory)
) -[grid: gpu.grid<X<1>, X<16>>]-> () {

// Vector addition operation - element-wise addition of arrays
// The compiler generates safe parallel code that:
// 1. Loads data from global memory to local memory for each thread
// 2. Performs vectorized addition using HIVM dialect operations
// 3. Stores results back to global memory safely
// The ownership system ensures this operation is race-free
//
// LAZY LOADING: Descend's compiler implements lazy loading strategies:
// - Memory loads are deferred until actually needed by computation
// - The HIVM dialect generates 'hivm.hir.load' operations that load from
// global memory (gm) to local memory (ub) only when data is accessed
// - This minimizes memory bandwidth usage and improves cache efficiency
// - The type system ensures loads happen in the correct execution context
// - Shared references enable read-only access without unnecessary copies
c = a + b;

// Unit return value - indicates successful completion
// In MLIR, this becomes a 'return' operation
()
}
2 changes: 1 addition & 1 deletion src/codegen/mlir/builder/control_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ where
let (then_region, true_value) = build_branch_region(case_true, ctx, build_expr)?;

// Build the else region
let (else_region, false_value) = build_branch_region(case_false, ctx, build_expr)?;
let (else_region, _false_value) = build_branch_region(case_false, ctx, build_expr)?;

// Determine result types based on whether branches produce values
let result_types: Vec<Type> = if let Some(val) = true_value {
Expand Down
15 changes: 15 additions & 0 deletions src/codegen/mlir/builder/place.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use super::super::error::MlirError;
use super::context::MlirContext;
use super::expr::build_expr;
use crate::ast as desc;
use crate::ast::{DataTyKind, Ownership};

/// Build a place expression (variable lookup)
pub fn build_place_expr<'ctx, 'a, 'b>(
Expand Down Expand Up @@ -57,6 +58,20 @@ where
{
use desc::PlaceExprKind;

// Check if we're assigning to a reference - if so, it must be unique
if let Some(ty) = &place_expr.ty {
if let desc::TyKind::Data(data_ty) = &ty.ty {
if let DataTyKind::Ref(ref_dty) = &data_ty.dty {
if ref_dty.own != Ownership::Uniq {
return Err(MlirError::General(format!(
"Assignment to non-unique reference is not allowed. Expected unique reference, found {:?}",
ref_dty.own
)));
}
}
}
}

// Evaluate the right-hand side value
let value = build_expr(value_expr, ctx)?
.ok_or_else(|| MlirError::General("Missing value for assignment".to_string()))?;
Expand Down
62 changes: 16 additions & 46 deletions src/codegen/mlir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ use builder::MlirBuilder;
use error::MlirError;
use melior::{
dialect::DialectRegistry,
ir::{Location, Module, operation::OperationLike},
ir::{operation::OperationLike, Location, Module},
utility::register_all_dialects,
Context,
};

use crate::ast::CompilUnit;
use crate::ast::{DataTyKind, Memory, TyKind};

/// Internal helper function to build MLIR module
fn build_module_internal(comp_unit: &CompilUnit) -> Result<String, MlirError> {
Expand Down Expand Up @@ -72,22 +73,6 @@ fn build_module_internal(comp_unit: &CompilUnit) -> Result<String, MlirError> {
Ok(builder.module().as_operation().to_string())
}

pub fn gen(comp_unit: &CompilUnit, _idx_checks: bool) -> String {
// Check if we need HIVM address spaces
if needs_hivm_address_space(comp_unit) {
to_mlir::types::generate_mlir_string_with_hivm(comp_unit)
} else {
// Use internal helper, but handle errors by falling back to string generation
match build_module_internal(comp_unit) {
Ok(mlir_string) => mlir_string,
Err(_) => {
// Fallback to string generation if internal building fails
to_mlir::types::generate_mlir_string_with_hivm(comp_unit)
}
}
}
}

pub fn gen_checked(comp_unit: &CompilUnit, _idx_checks: bool) -> Result<String, String> {
// Check if we need HIVM address spaces
if needs_hivm_address_space(comp_unit) {
Expand All @@ -108,13 +93,10 @@ pub fn gen_checked(comp_unit: &CompilUnit, _idx_checks: bool) -> Result<String,
fn needs_hivm_address_space(comp_unit: &CompilUnit) -> bool {
for item in &comp_unit.items {
if let crate::ast::Item::FunDef(fun) = item {
// Only check the main function or functions that are not HIVM placeholders
if fun.ident.name == "main".into() || !is_hivm_placeholder_function(fun) {
for param in &fun.param_decls {
if let Some(ty) = &param.ty {
if has_gpu_memory(ty) {
return true;
}
for param in &fun.param_decls {
if let Some(ty) = &param.ty {
if has_gpu_memory(ty) {
return true;
}
}
}
Expand All @@ -123,31 +105,19 @@ fn needs_hivm_address_space(comp_unit: &CompilUnit) -> bool {
false
}

/// Check if a function is a HIVM placeholder function
fn is_hivm_placeholder_function(fun: &crate::ast::FunDef) -> bool {
fun.ident.name.starts_with("hivm_")
}

/// Check if a type has GPU memory qualifiers
fn has_gpu_memory(ty: &crate::ast::Ty) -> bool {
fn mem_is_gpu(mem: &Memory) -> bool {
matches!(
mem,
Memory::GpuGlobal | Memory::GpuShared | Memory::GpuLocal
)
}

match &ty.ty {
crate::ast::TyKind::Data(data_ty) => match &data_ty.dty {
crate::ast::DataTyKind::At(_, mem) => {
matches!(
mem,
crate::ast::Memory::GpuGlobal
| crate::ast::Memory::GpuShared
| crate::ast::Memory::GpuLocal
)
}
crate::ast::DataTyKind::Ref(ref_dty) => {
matches!(
ref_dty.mem,
crate::ast::Memory::GpuGlobal
| crate::ast::Memory::GpuShared
| crate::ast::Memory::GpuLocal
)
}
TyKind::Data(data_ty) => match &data_ty.dty {
DataTyKind::At(_, mem) => mem_is_gpu(mem),
DataTyKind::Ref(ref_dty) => mem_is_gpu(&ref_dty.mem),
_ => false,
},
_ => false,
Expand Down
Loading