From 6b1e1a86353ce8332fce44a41f7781522f39777f Mon Sep 17 00:00:00 2001 From: nihalpasham Date: Fri, 16 Oct 2026 13:18:22 +0530 Subject: [PATCH 1/2] Update cuda-oxide to published Pliron 0.19.0 Signed-off-by: nihalpasham --- cuda-oxide/Cargo.lock | 19 +- cuda-oxide/Cargo.toml | 18 +- .../dialect-mir/src/ops/control_flow.rs | 154 +++++------ .../crates/dialect-mir/src/ops/function.rs | 23 +- .../tests/ops_test/control_flow.rs | 251 +++++++++++++++++- .../tests/ops_test/scalars_and_funcs.rs | 96 +++++++ .../crates/llvm-export/src/export/debug.rs | 4 +- .../crates/llvm-export/src/export/function.rs | 43 ++- .../tests/export_test/control_flow.rs | 122 ++++++++- .../tests/export_test/grid_constant.rs | 11 +- .../mir-lower/src/convert/ops/control_flow.rs | 2 +- .../crates/rustc-codegen-cuda/Cargo.lock | 19 +- .../crates/rustc-codegen-cuda/Cargo.toml | 10 +- cuda-oxide/deny.toml | 1 - cuda-oxide/dependency-licenses.csv | 8 +- 15 files changed, 649 insertions(+), 132 deletions(-) diff --git a/cuda-oxide/Cargo.lock b/cuda-oxide/Cargo.lock index 5bb9ee62fa..c65fa5b21b 100644 --- a/cuda-oxide/Cargo.lock +++ b/cuda-oxide/Cargo.lock @@ -295,9 +295,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "combine" -version = "4.6.7" +version = "4.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" dependencies = [ "bytes", "memchr", @@ -1123,8 +1123,9 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pliron" -version = "0.18.0" -source = "git+https://github.com/pliron-org/pliron?rev=06d99a3df938394583715aacf5eadb65b4791c09#06d99a3df938394583715aacf5eadb65b4791c09" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d59d619aada2eb8f68bacac30cfa373f4e247c7667cdac69370f5eaf0b87178" dependencies = [ "awint", "combine", @@ -1150,8 +1151,9 @@ dependencies = [ [[package]] name = "pliron-derive" -version = "0.18.0" -source = "git+https://github.com/pliron-org/pliron?rev=06d99a3df938394583715aacf5eadb65b4791c09#06d99a3df938394583715aacf5eadb65b4791c09" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e9d7f08f5162e44988420d46a12bd9aa776a9c5a0bee5d1555741060c87a73" dependencies = [ "combine", "convert_case", @@ -1164,8 +1166,9 @@ dependencies = [ [[package]] name = "pliron-llvm" -version = "0.18.0" -source = "git+https://github.com/pliron-org/pliron?rev=06d99a3df938394583715aacf5eadb65b4791c09#06d99a3df938394583715aacf5eadb65b4791c09" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b04f59dc2ac2198d67f24775a5d5b37f7d4da1feedc893cd43f2cb7d8ff1d9a" dependencies = [ "bitflags", "log", diff --git a/cuda-oxide/Cargo.toml b/cuda-oxide/Cargo.toml index 288d20dc55..dc3c8078bc 100644 --- a/cuda-oxide/Cargo.toml +++ b/cuda-oxide/Cargo.toml @@ -45,15 +45,16 @@ repository = "https://github.com/NVIDIA/cuda-rust" # Examples are in: crates/rustc-codegen-cuda/examples/ [workspace.dependencies] -# Pliron MLIR framework, pinned to upstream struct-layout, GEP no-wrap, volatile, -# global-constant, inline-asm, and generic LLVM attribute semantics. +# Pliron MLIR framework, pinned to the published 0.19.0 release, retaining +# struct-layout, GEP no-wrap, volatile, global-constant, inline-asm, and generic +# LLVM attribute semantics. # pliron-llvm uses default-features = false to keep llvm-sys (LLVM 23) out: # cuda-oxide consumes the dialect modeling only, not the FFI bridge. -# `crates/rustc-codegen-cuda/Cargo.toml` MUST stay on this same rev, or pliron -# resolves to two distinct crates and types stop unifying. -pliron = { git = "https://github.com/pliron-org/pliron", rev = "06d99a3df938394583715aacf5eadb65b4791c09" } -pliron-derive = { git = "https://github.com/pliron-org/pliron", rev = "06d99a3df938394583715aacf5eadb65b4791c09" } -pliron-llvm = { git = "https://github.com/pliron-org/pliron", rev = "06d99a3df938394583715aacf5eadb65b4791c09", default-features = false, features = [ +# `crates/rustc-codegen-cuda/Cargo.toml` MUST use the same registry source and +# exact release, or pliron resolves to distinct crates and types stop unifying. +pliron = { version = "=0.19.0" } +pliron-derive = { version = "=0.19.0" } +pliron-llvm = { version = "=0.19.0", default-features = false, features = [ "std", ] } @@ -108,7 +109,8 @@ thiserror = "2" linkme = "0.3" anyhow = "1.0" once_cell = "1.18" -combine = "4.6" +# Pliron 0.19 requires SourcePosition: Hash, introduced in combine 4.6.8. +combine = "4.6.8" log = "0.4" half = "2.7" libloading = "0.8" diff --git a/cuda-oxide/crates/dialect-mir/src/ops/control_flow.rs b/cuda-oxide/crates/dialect-mir/src/ops/control_flow.rs index 358f457b1c..ace01f7537 100644 --- a/cuda-oxide/crates/dialect-mir/src/ops/control_flow.rs +++ b/cuda-oxide/crates/dialect-mir/src/ops/control_flow.rs @@ -7,10 +7,14 @@ //! //! This module defines assertions, terminators, and branches for the MIR dialect. +use std::ops::Range; + use pliron::{ builtin::{ + attributes::OperandSegmentSizesAttr, op_interfaces::{ - BranchOpInterface, IsTerminatorInterface, NResultsInterface, OperandSegmentInterface, + ATTR_KEY_OPERAND_SEGMENT_SIZES, BranchOpInterface, IsTerminatorInterface, + NResultsInterface, OperandSegmentInterface, }, type_interfaces::FunctionTypeInterface, types::IntegerType, @@ -146,41 +150,23 @@ impl MirGotoOp { impl Verify for MirGotoOp { fn verify(&self, ctx: &Context) -> Result<(), Error> { - let op = &*self.get_operation().deref(ctx); + ::verify(self, ctx) + } +} +#[op_interface_impl] +impl BranchOpInterface for MirGotoOp { + fn verify_successor_operand_layout(&self, ctx: &Context) -> Result<(), Error> { + let op = self.get_operation().deref(ctx); if op.get_num_successors() != 1 { return verify_err!(op.loc(), "MirGotoOp must have exactly 1 successor"); } - let succ = op.get_successor(0); - let succ_block = succ.deref(ctx); - - if op.get_num_operands() != succ_block.get_num_arguments() { - return verify_err!( - op.loc(), - "MirGotoOp operand count must match successor argument count" - ); - } - - for i in 0..succ_block.get_num_arguments() { - let arg = succ_block.get_argument(i); - let opd = op.get_operand(i); - if opd.get_type(ctx) != arg.get_type(ctx) { - return verify_err!( - op.loc(), - "MirGotoOp operand type mismatch with successor argument" - ); - } - } - Ok(()) } -} -#[op_interface_impl] -impl BranchOpInterface for MirGotoOp { - fn successor_operands(&self, ctx: &Context, succ_idx: usize) -> Vec { + fn successor_operand_range(&self, ctx: &Context, succ_idx: usize) -> Range { assert!(succ_idx == 0, "MirGotoOp has exactly one successor"); - self.get_operation().deref(ctx).operands().collect() + 0..self.get_operation().deref(ctx).get_num_operands() } fn add_successor_operand(&self, ctx: &mut Context, succ_idx: usize, operand: Value) -> usize { @@ -218,13 +204,14 @@ impl BranchOpInterface for MirGotoOp { /// /// # Verification /// +/// - Exactly three operand segments, with one condition operand. /// - Condition must be `i1`. /// - Must have exactly 2 successors. /// - Operand types must match successor block argument types. #[pliron_op( name = "mir.cond_br", format, - interfaces = [NResultsInterface<0>, IsTerminatorInterface, OperandSegmentInterface] + interfaces = [NResultsInterface<0>, IsTerminatorInterface] )] pub struct MirCondBranchOp; @@ -237,20 +224,10 @@ impl MirCondBranchOp { impl Verify for MirCondBranchOp { fn verify(&self, ctx: &Context) -> Result<(), Error> { - let op = &*self.get_operation().deref(ctx); - - if op.get_num_successors() != 2 { - return verify_err!(op.loc(), "MirCondBranchOp must have exactly 2 successors"); - } - let true_block = op.get_successor(0).deref(ctx); - let false_block = op.get_successor(1).deref(ctx); - - if op.get_num_operands() < 1 { - return verify_err!( - op.loc(), - "MirCondBranchOp must have at least 1 operand (condition)" - ); - } + // Validate segment metadata before reading operands. The shared branch + // verifier also checks each segment against its target block arguments. + ::verify(self, ctx)?; + let op = self.get_operation().deref(ctx); let cond = op.get_operand(0); let cond_ty = cond.get_type(ctx); let cond_ty_obj = cond_ty.deref(ctx); @@ -263,55 +240,83 @@ impl Verify for MirCondBranchOp { return verify_err!(op.loc(), "MirCondBranchOp condition must be integer type"); } - let expected_args = 1 + true_block.get_num_arguments() + false_block.get_num_arguments(); - if op.get_num_operands() != expected_args { + Ok(()) + } +} + +#[op_interface_impl] +impl OperandSegmentInterface for MirCondBranchOp { + fn expected_num_segments(&self, _ctx: &Context) -> Option { + // Condition, true successor arguments, false successor arguments. + Some(3) + } + + fn verify(op: &dyn Op, ctx: &Context) -> Result<(), Error> { + let op = op.get_operation().deref(ctx); + let Some(sizes) = op + .attributes + .get::(&ATTR_KEY_OPERAND_SEGMENT_SIZES) + else { + return verify_err!(op.loc(), "MirCondBranchOp requires operand segment sizes"); + }; + if sizes.0.len() != 3 { return verify_err!( op.loc(), - "MirCondBranchOp operand count must match condition + successors arguments" + "MirCondBranchOp must have exactly 3 operand segments" ); } - - let mut op_idx = 1; - for i in 0..true_block.get_num_arguments() { - let arg = true_block.get_argument(i); - let opd = op.get_operand(op_idx); - if opd.get_type(ctx) != arg.get_type(ctx) { - return verify_err!( - op.loc(), - "MirCondBranchOp true successor argument type mismatch" - ); - } - op_idx += 1; - } - - for i in 0..false_block.get_num_arguments() { - let arg = false_block.get_argument(i); - let opd = op.get_operand(op_idx); - if opd.get_type(ctx) != arg.get_type(ctx) { - return verify_err!( - op.loc(), - "MirCondBranchOp false successor argument type mismatch" - ); - } - op_idx += 1; + // Pliron 0.19's default verifier sums in u32 without checking overflow. + // Keep its range arithmetic valid even for untrusted segment metadata. + let Some(total) = sizes + .0 + .iter() + .try_fold(0u32, |sum, size| sum.checked_add(*size)) + else { + return verify_err!( + op.loc(), + "MirCondBranchOp operand segment total overflows u32" + ); + }; + if usize::try_from(total).ok() != Some(op.get_num_operands()) { + return verify_err!( + op.loc(), + "MirCondBranchOp operand segment total must match operand count" + ); } - Ok(()) } } #[op_interface_impl] impl BranchOpInterface for MirCondBranchOp { - fn successor_operands(&self, ctx: &Context, succ_idx: usize) -> Vec { + fn verify_successor_operand_layout(&self, ctx: &Context) -> Result<(), Error> { + // Interface verification can run before the operation's own verifier. + // Check every precondition of segment_range here, before it is called. + ::verify(self, ctx)?; + let op = self.get_operation().deref(ctx); + if op.get_num_successors() != 2 { + return verify_err!(op.loc(), "MirCondBranchOp must have exactly 2 successors"); + } + if self.segment_size(ctx, 0) != 1 { + return verify_err!( + op.loc(), + "MirCondBranchOp must have exactly 1 condition operand" + ); + } + Ok(()) + } + + fn successor_operand_range(&self, ctx: &Context, succ_idx: usize) -> Range { assert!( succ_idx == 0 || succ_idx == 1, "MirCondBranchOp has exactly two successors" ); - // Segment 0 = condition, segment 1 = true args, segment 2 = false args - self.get_segment(ctx, succ_idx + 1) + // Segment 0 = condition, segment 1 = true args, segment 2 = false args. + self.segment_range(ctx, succ_idx + 1) } fn add_successor_operand(&self, ctx: &mut Context, succ_idx: usize, operand: Value) -> usize { + assert!(succ_idx < 2, "MirCondBranchOp has exactly two successors"); self.push_to_segment(ctx, succ_idx + 1, operand) } @@ -321,6 +326,7 @@ impl BranchOpInterface for MirCondBranchOp { succ_idx: usize, opd_idx: usize, ) -> Value { + assert!(succ_idx < 2, "MirCondBranchOp has exactly two successors"); self.remove_from_segment(ctx, succ_idx + 1, opd_idx) } } diff --git a/cuda-oxide/crates/dialect-mir/src/ops/function.rs b/cuda-oxide/crates/dialect-mir/src/ops/function.rs index 7716e39c78..60cdda19b4 100644 --- a/cuda-oxide/crates/dialect-mir/src/ops/function.rs +++ b/cuda-oxide/crates/dialect-mir/src/ops/function.rs @@ -25,7 +25,7 @@ use pliron::{ common_traits::Verify, context::{Context, Ptr}, identifier::Identifier, - indented_block, input_err, + input_err, irfmt::{ parsers::{spaced, type_parser}, printers::op::{region, typed_symb_op_header}, @@ -171,10 +171,9 @@ impl Printable for MirFuncOp { .retain(|key, _| key != &*ATTR_KEY_MIR_FUNC_TYPE && key != &ATTR_KEY_SYM_NAME); if !attributes_to_print_separately.0.is_empty() { - indented_block!(state, { - write!(f, "{}", indented_nl(state))?; - attributes_to_print_separately.fmt(ctx, state, f)?; - }); + let _indent = state.indent(); + write!(f, "{}", indented_nl(state))?; + attributes_to_print_separately.fmt(ctx, state, f)?; } write!(f, " ")?; region(self).fmt(ctx, state, f)?; @@ -202,19 +201,25 @@ impl Parsable for MirFuncOp { vec![], vec![], vec![], - 1, + // Region::parser creates the body region itself. + 0, ); let mut parser = ( spaced(token('@').with(Identifier::parser(()))).skip(spaced(token(':'))), spaced(type_parser()), - spaced(AttributeDict::parser(())), + spaced(optional(AttributeDict::parser(()))), spaced(optional(Region::parser(op))), ); parser .parse_stream(state_stream) - .map(|(fname, fty, attrs, _region)| -> OpObj { + .map(|(fname, fty, attrs, region)| -> OpObj { let ctx = &mut state_stream.state.ctx; - op.deref_mut(ctx).attributes = attrs; + // Declarations still have one empty region, matching the + // function operation's OneRegionInterface contract. + if region.is_none() { + Operation::add_region(op, ctx); + } + op.deref_mut(ctx).attributes = attrs.unwrap_or_default(); let ty_attr = TypeAttr::new(fty); let opop = MirFuncOp { op }; opop.set_symbol_name(ctx, fname); diff --git a/cuda-oxide/crates/dialect-mir/tests/ops_test/control_flow.rs b/cuda-oxide/crates/dialect-mir/tests/ops_test/control_flow.rs index cb8936f5fa..5384d397b5 100644 --- a/cuda-oxide/crates/dialect-mir/tests/ops_test/control_flow.rs +++ b/cuda-oxide/crates/dialect-mir/tests/ops_test/control_flow.rs @@ -7,8 +7,10 @@ use dialect_mir::ops::{MirAssertOp, MirCondBranchOp, MirFuncOp, MirGotoOp, MirRe use pliron::{ basic_block::BasicBlock, builtin::{ - attributes::TypeAttr, - op_interfaces::OperandSegmentInterface, + attributes::{OperandSegmentSizesAttr, StringAttr, TypeAttr}, + op_interfaces::{ + ATTR_KEY_OPERAND_SEGMENT_SIZES, BranchOpInterface, OperandSegmentInterface, + }, types::{FP32Type, FunctionType, IntegerType, Signedness}, }, common_traits::Verify, @@ -171,3 +173,248 @@ fn test_mir_control_flow_verify() { ); } } + +/// Conditional branches must reject malformed segment metadata before indexing it. +#[test] +fn test_mir_cond_branch_rejects_malformed_segments() { + let mut ctx = Context::new(); + dialect_mir::register(&mut ctx); + let i1_ty = IntegerType::get(&ctx, 1, Signedness::Signless); + let i32_ty = IntegerType::get(&ctx, 32, Signedness::Signed); + let source = BasicBlock::new(&mut ctx, None, vec![i1_ty.into(), i32_ty.into()]); + let condition = source.deref(&ctx).get_argument(0); + let value = source.deref(&ctx).get_argument(1); + let true_block = BasicBlock::new(&mut ctx, None, vec![i32_ty.into()]); + let false_block = BasicBlock::new(&mut ctx, None, vec![]); + let branch = MirCondBranchOp::new(Operation::new( + &mut ctx, + MirCondBranchOp::get_concrete_op_info(), + vec![], + vec![condition, value], + vec![true_block, false_block], + 0, + )); + + assert!(branch.verify(&ctx).is_err(), "Missing segment attribute"); + assert!(branch.verify_interfaces(&ctx).is_err()); + branch.get_operation().deref_mut(&ctx).attributes.set( + ATTR_KEY_OPERAND_SEGMENT_SIZES.clone(), + StringAttr::new("wrong attribute type".to_owned()), + ); + assert!(branch.verify(&ctx).is_err(), "Wrong segment attribute type"); + assert!(branch.verify_interfaces(&ctx).is_err()); + + for sizes in [ + vec![], + vec![2], + vec![1, 1], + vec![1, 1, 0, 0], + vec![1, 0, 0], + vec![0, 2, 0], + vec![2, 0, 0], + vec![1, 0, 1], + ] { + branch.set_operand_segment_sizes(&ctx, OperandSegmentSizesAttr(sizes.clone())); + assert!(branch.verify(&ctx).is_err(), "Accepted malformed {sizes:?}"); + assert!( + branch.verify_interfaces(&ctx).is_err(), + "Interfaces accepted {sizes:?}" + ); + } + + branch.set_operand_segment_sizes(&ctx, OperandSegmentSizesAttr(vec![1, 1, 0])); + assert!(branch.verify(&ctx).is_ok()); + assert!(branch.verify_interfaces(&ctx).is_ok()); +} + +/// Segment totals must not wrap to the operand count or panic during verification. +#[test] +fn test_mir_cond_branch_rejects_overflowing_segment_totals() { + let mut ctx = Context::new(); + dialect_mir::register(&mut ctx); + let i1_ty = IntegerType::get(&ctx, 1, Signedness::Signless); + let i32_ty = IntegerType::get(&ctx, 32, Signedness::Signed); + let source = BasicBlock::new(&mut ctx, None, vec![i1_ty.into(), i32_ty.into()]); + let condition = source.deref(&ctx).get_argument(0); + let value = source.deref(&ctx).get_argument(1); + let true_block = BasicBlock::new(&mut ctx, None, vec![i32_ty.into()]); + let false_block = BasicBlock::new(&mut ctx, None, vec![]); + let branch = MirCondBranchOp::new(Operation::new( + &mut ctx, + MirCondBranchOp::get_concrete_op_info(), + vec![], + vec![condition, value], + vec![true_block, false_block], + 0, + )); + + // Both totals equal two modulo 2^32, matching the actual operand count. + for sizes in [vec![1, u32::MAX, 2], vec![u32::MAX, 1, 2]] { + branch.set_operand_segment_sizes(&ctx, OperandSegmentSizesAttr(sizes)); + assert!(branch.verify(&ctx).is_err()); + assert!(branch.verify_successor_operand_layout(&ctx).is_err()); + assert!(::verify(&branch, &ctx).is_err()); + assert!(branch.verify_interfaces(&ctx).is_err()); + } +} + +/// Invalid successor counts must be rejected without asking for an invalid range. +#[test] +fn test_mir_branches_reject_wrong_successor_counts() { + let mut ctx = Context::new(); + dialect_mir::register(&mut ctx); + let i1_ty = IntegerType::get(&ctx, 1, Signedness::Signless); + let source = BasicBlock::new(&mut ctx, None, vec![i1_ty.into()]); + let condition = source.deref(&ctx).get_argument(0); + let target = BasicBlock::new(&mut ctx, None, vec![]); + + for count in [0, 2] { + let branch = MirGotoOp::new(Operation::new( + &mut ctx, + MirGotoOp::get_concrete_op_info(), + vec![], + vec![], + vec![target; count], + 0, + )); + assert!(branch.verify(&ctx).is_err()); + assert!(branch.verify_interfaces(&ctx).is_err()); + } + for count in [0, 1, 3] { + let branch = MirCondBranchOp::new(Operation::new( + &mut ctx, + MirCondBranchOp::get_concrete_op_info(), + vec![], + vec![condition], + vec![target; count], + 0, + )); + branch.set_operand_segment_sizes(&ctx, OperandSegmentSizesAttr(vec![1, 0, 0])); + assert!(branch.verify(&ctx).is_err()); + assert!(branch.verify_interfaces(&ctx).is_err()); + } +} + +/// Range-based mutation must preserve the condition and the other successor's operands. +#[test] +fn test_mir_cond_branch_successor_operand_mutation() { + let mut ctx = Context::new(); + dialect_mir::register(&mut ctx); + let i1_ty = IntegerType::get(&ctx, 1, Signedness::Signless); + let i32_ty = IntegerType::get(&ctx, 32, Signedness::Signed); + let f32_ty = FP32Type::get(&ctx); + let source = BasicBlock::new( + &mut ctx, + None, + vec![i1_ty.into(), i32_ty.into(), i32_ty.into(), f32_ty.into()], + ); + let condition = source.deref(&ctx).get_argument(0); + let first = source.deref(&ctx).get_argument(1); + let replacement = source.deref(&ctx).get_argument(2); + let second = source.deref(&ctx).get_argument(3); + let true_block = BasicBlock::new(&mut ctx, None, vec![]); + let false_block = BasicBlock::new(&mut ctx, None, vec![]); + let branch = MirCondBranchOp::new(Operation::new( + &mut ctx, + MirCondBranchOp::get_concrete_op_info(), + vec![], + vec![condition], + vec![true_block, false_block], + 0, + )); + branch.set_operand_segment_sizes(&ctx, OperandSegmentSizesAttr(vec![1, 0, 0])); + assert!(branch.verify(&ctx).is_ok()); + assert!(branch.verify_interfaces(&ctx).is_ok()); + assert_eq!(branch.successor_operand_range(&ctx, 0), 1..1); + assert_eq!(branch.successor_operand_range(&ctx, 1), 1..1); + + // Add false first: inserting true later must move, rather than overwrite, false. + assert_eq!(branch.add_successor_operand(&mut ctx, 1, second), 0); + BasicBlock::push_argument(false_block, &ctx, f32_ty.into()); + assert_eq!(branch.add_successor_operand(&mut ctx, 0, first), 0); + BasicBlock::push_argument(true_block, &ctx, i32_ty.into()); + assert_eq!(branch.successor_operand_range(&ctx, 0), 1..2); + assert_eq!(branch.successor_operand_range(&ctx, 1), 2..3); + assert_eq!(branch.successor_operands(&ctx, 0), vec![first]); + assert_eq!(branch.successor_operands(&ctx, 1), vec![second]); + assert!(branch.verify(&ctx).is_ok()); + assert!(branch.verify_interfaces(&ctx).is_ok()); + + branch.set_successor_operand(&ctx, 0, 0, second); + assert!(branch.verify(&ctx).is_err(), "Wrong true argument type"); + assert!(branch.verify_interfaces(&ctx).is_err()); + branch.set_successor_operand(&ctx, 0, 0, replacement); + branch.set_successor_operand(&ctx, 1, 0, first); + assert!(branch.verify(&ctx).is_err(), "Wrong false argument type"); + assert!(branch.verify_interfaces(&ctx).is_err()); + branch.set_successor_operand(&ctx, 1, 0, second); + assert_eq!(branch.successor_operands(&ctx, 0), vec![replacement]); + assert_eq!(branch.successor_operands(&ctx, 1), vec![second]); + assert_eq!(branch.get_operation().deref(&ctx).get_operand(0), condition); + + assert_eq!(branch.remove_successor_operand(&mut ctx, 0, 0), replacement); + BasicBlock::pop_argument(true_block, &ctx); + assert_eq!(branch.successor_operand_range(&ctx, 1), 1..2); + assert_eq!(branch.successor_operands(&ctx, 1), vec![second]); + assert!(branch.verify(&ctx).is_ok()); + assert!(branch.verify_interfaces(&ctx).is_ok()); + assert_eq!(branch.remove_successor_operand(&mut ctx, 1, 0), second); + BasicBlock::pop_argument(false_block, &ctx); + assert_eq!( + branch + .get_operation() + .deref(&ctx) + .operands() + .collect::>(), + vec![condition] + ); + assert!(branch.verify(&ctx).is_ok()); + assert!(branch.verify_interfaces(&ctx).is_ok()); +} + +/// Goto ranges must support empty and nonempty successors and type-checked mutation. +#[test] +fn test_mir_goto_successor_operand_mutation() { + let mut ctx = Context::new(); + dialect_mir::register(&mut ctx); + let i32_ty = IntegerType::get(&ctx, 32, Signedness::Signed); + let f32_ty = FP32Type::get(&ctx); + let source = BasicBlock::new( + &mut ctx, + None, + vec![i32_ty.into(), i32_ty.into(), f32_ty.into()], + ); + let first = source.deref(&ctx).get_argument(0); + let replacement = source.deref(&ctx).get_argument(1); + let wrong_type = source.deref(&ctx).get_argument(2); + let target = BasicBlock::new(&mut ctx, None, vec![]); + let branch = MirGotoOp::new(Operation::new( + &mut ctx, + MirGotoOp::get_concrete_op_info(), + vec![], + vec![], + vec![target], + 0, + )); + assert_eq!(branch.successor_operand_range(&ctx, 0), 0..0); + assert!(branch.verify(&ctx).is_ok()); + assert!(branch.verify_interfaces(&ctx).is_ok()); + assert_eq!(branch.add_successor_operand(&mut ctx, 0, first), 0); + assert!( + branch.verify(&ctx).is_err(), + "Successor argument is missing" + ); + BasicBlock::push_argument(target, &ctx, i32_ty.into()); + assert_eq!(branch.successor_operand_range(&ctx, 0), 0..1); + branch.set_successor_operand(&ctx, 0, 0, wrong_type); + assert!(branch.verify(&ctx).is_err()); + assert!(branch.verify_interfaces(&ctx).is_err()); + branch.set_successor_operand(&ctx, 0, 0, replacement); + assert_eq!(branch.successor_operands(&ctx, 0), vec![replacement]); + assert!(branch.verify(&ctx).is_ok()); + assert!(branch.verify_interfaces(&ctx).is_ok()); + assert_eq!(branch.remove_successor_operand(&mut ctx, 0, 0), replacement); + BasicBlock::pop_argument(target, &ctx); + assert!(branch.verify(&ctx).is_ok()); + assert!(branch.verify_interfaces(&ctx).is_ok()); +} diff --git a/cuda-oxide/crates/dialect-mir/tests/ops_test/scalars_and_funcs.rs b/cuda-oxide/crates/dialect-mir/tests/ops_test/scalars_and_funcs.rs index e0ce9f0104..bf478b3813 100644 --- a/cuda-oxide/crates/dialect-mir/tests/ops_test/scalars_and_funcs.rs +++ b/cuda-oxide/crates/dialect-mir/tests/ops_test/scalars_and_funcs.rs @@ -385,6 +385,102 @@ fn test_mir_func_verify() { ); } +/// Function attributes must remain parseable without leaking their indentation +/// into the function body or later operations. +#[test] +fn function_attributes_print_and_parse_with_scoped_indentation() { + use pliron::{ + builtin::op_interfaces::SymbolOpInterface, + linked_list::ContainsLinkedList, + parsable::parse_from_str, + printable::{Printable, State}, + }; + + let mut ctx = Context::new(); + dialect_mir::register(&mut ctx); + let ty = FunctionType::get(&ctx, vec![], vec![]); + let op = Operation::new( + &mut ctx, + MirFuncOp::get_concrete_op_info(), + vec![], + vec![], + vec![], + 1, + ); + let function = MirFuncOp::new(&mut ctx, op, TypeAttr::new(ty.into())); + function.set_symbol_name(&mut ctx, "roundtrip".try_into().unwrap()); + op.deref_mut(&ctx).attributes.set( + "note".try_into().unwrap(), + StringAttr::new("quoted \"text\"\nnext line\\tail".into()), + ); + let entry = BasicBlock::new(&mut ctx, None, vec![]); + entry.insert_at_back(op.deref(&ctx).get_region(0), &ctx); + let return_op = Operation::new( + &mut ctx, + dialect_mir::ops::MirReturnOp::get_concrete_op_info(), + vec![], + vec![], + vec![], + 0, + ); + return_op.insert_at_back(entry, &ctx); + op.verify(&ctx).unwrap(); + let state = State::default(); + state.set_indent_width(4); + let printed = function.print(&ctx, &state).to_string(); + assert_eq!(state.current_indent(), 0); + assert!(printed.contains("\n ["), "{printed}"); + + let mut parsed_ctx = Context::new(); + dialect_mir::register(&mut parsed_ctx); + let parsed = parse_from_str(Operation::top_level_parser(), &mut parsed_ctx, &printed) + .expect("printed function attributes must parse"); + assert_eq!(parsed.deref(&parsed_ctx).num_regions(), 1); + assert!( + parsed + .deref(&parsed_ctx) + .get_region(0) + .deref(&parsed_ctx) + .get_head() + .is_some() + ); + assert_eq!( + parsed + .deref(&parsed_ctx) + .attributes + .get::(&"note".try_into().unwrap()), + op.deref(&ctx) + .attributes + .get::(&"note".try_into().unwrap()), + ); + parsed + .verify(&parsed_ctx) + .expect("parsed function must verify"); +} + +/// Parsing a declaration supplies its empty body once; printing it without +/// extra attributes must still produce a valid function on the next parse. +#[test] +fn function_declaration_without_attributes_round_trips() { + use pliron::{parsable::parse_from_str, printable::Printable}; + + let mut ctx = Context::new(); + dialect_mir::register(&mut ctx); + let ty = FunctionType::get(&ctx, vec![], vec![]); + let text = format!("mir.func @declaration : {} []", ty.disp(&ctx)); + let parsed = parse_from_str(Operation::top_level_parser(), &mut ctx, &text).unwrap(); + assert_eq!(parsed.deref(&ctx).num_regions(), 1); + parsed.verify(&ctx).unwrap(); + let printed = parsed.disp(&ctx).to_string(); + + let mut parsed_ctx = Context::new(); + dialect_mir::register(&mut parsed_ctx); + let reparsed = parse_from_str(Operation::top_level_parser(), &mut parsed_ctx, &printed) + .expect("function without extra attributes must parse"); + assert_eq!(reparsed.deref(&parsed_ctx).num_regions(), 1); + reparsed.verify(&parsed_ctx).unwrap(); +} + #[test] fn test_mir_assign_verify() { let mut ctx = Context::new(); diff --git a/cuda-oxide/crates/llvm-export/src/export/debug.rs b/cuda-oxide/crates/llvm-export/src/export/debug.rs index 5b21a70906..ca0bfcfc31 100644 --- a/cuda-oxide/crates/llvm-export/src/export/debug.rs +++ b/cuda-oxide/crates/llvm-export/src/export/debug.rs @@ -1309,11 +1309,11 @@ mod tests { #[test] fn source_position_from_location_unwraps_named_locations() { - let mut ctx = Context::new(); + let ctx = Context::new(); let loc = Location::Named { name: "lowered".to_string(), child_loc: Box::new(Location::SrcPos { - src: Source::new_from_file(&mut ctx, PathBuf::from("/tmp/kernel.rs")), + src: Source::new_from_file(&ctx, PathBuf::from("/tmp/kernel.rs")), pos: SourcePosition { line: 12, column: 4, diff --git a/cuda-oxide/crates/llvm-export/src/export/function.rs b/cuda-oxide/crates/llvm-export/src/export/function.rs index 3fa26fa0ae..0282a8ac6c 100644 --- a/cuda-oxide/crates/llvm-export/src/export/function.rs +++ b/cuda-oxide/crates/llvm-export/src/export/function.rs @@ -16,12 +16,12 @@ use pliron::{ attribute::Attribute, basic_block::BasicBlock, builtin::{ - attributes::{FPDoubleAttr, FPSingleAttr, IntegerAttr, TypeAttr}, - op_interfaces::{BranchOpInterface, SymbolOpInterface}, + attributes::{FPDoubleAttr, FPSingleAttr, IntegerAttr, OperandSegmentSizesAttr, TypeAttr}, + op_interfaces::{ATTR_KEY_OPERAND_SEGMENT_SIZES, BranchOpInterface, SymbolOpInterface}, type_interfaces::FunctionTypeInterface, types::IntegerType, }, - context::Ptr, + context::{Context, Ptr}, linked_list::ContainsLinkedList, location::Located, op::Op, @@ -61,6 +61,42 @@ fn sanitize_comment(text: &str) -> String { .collect() } +/// Check conditional-branch layout before Pliron's infallible range accessors. +/// +/// Public export callers need not have verified the source module. Pliron 0.19 +/// also sums segment sizes as u32 without checking overflow, so validate the +/// complete layout here before it can index or allocate from malformed counts. +fn validate_conditional_branch_layout(ctx: &Context, branch: &ops::CondBrOp) -> Result<(), String> { + let operation = branch.get_operation().deref(ctx); + let sizes = operation + .attributes + .get::(&ATTR_KEY_OPERAND_SEGMENT_SIZES) + .ok_or_else(|| { + "conditional branch requires an operand-segment-sizes attribute".to_owned() + })?; + if !matches!(sizes.0.as_slice(), [1, _, _]) { + return Err( + "conditional branch requires three operand segments with exactly one condition" + .to_owned(), + ); + } + let total = sizes + .0 + .iter() + .try_fold(0_u32, |total, size| total.checked_add(*size)) + .ok_or_else(|| "conditional branch operand segment total overflows u32".to_owned())?; + if total as usize != operation.get_num_operands() { + return Err(format!( + "conditional branch operand segments describe {total} values, but it has {} operands", + operation.get_num_operands() + )); + } + if operation.get_num_successors() != 2 { + return Err("conditional branch requires exactly two successors".to_owned()); + } + Ok(()) +} + /// Spell only the facts already proven at the import boundary. Shared by /// definitions and declarations, including the empty-region declaration path. fn export_reference_parameter_attrs( @@ -1349,6 +1385,7 @@ impl<'a> ModuleExportState<'a> { validate_edge(block, dest, &args)?; pred_map.entry(dest).or_default().push((block, args)); } else if let Some(branch) = term_dyn.downcast_ref::() { + validate_conditional_branch_layout(self.ctx, branch)?; let succs: Vec<_> = term.deref(self.ctx).successors().collect(); let true_dest = succs[0]; let false_dest = succs[1]; diff --git a/cuda-oxide/crates/llvm-export/tests/export_test/control_flow.rs b/cuda-oxide/crates/llvm-export/tests/export_test/control_flow.rs index 33f9fc3cc3..4a94078212 100644 --- a/cuda-oxide/crates/llvm-export/tests/export_test/control_flow.rs +++ b/cuda-oxide/crates/llvm-export/tests/export_test/control_flow.rs @@ -11,7 +11,8 @@ use llvm_export::{ use pliron::{ basic_block::BasicBlock, builtin::{ - attributes::IntegerAttr, + attributes::{IntegerAttr, OperandSegmentSizesAttr, UnitAttr}, + op_interfaces::{ATTR_KEY_OPERAND_SEGMENT_SIZES, OperandSegmentInterface}, ops::ModuleOp, types::{IntegerType, Signedness}, }, @@ -24,6 +25,125 @@ use std::num::NonZero; use crate::common::module_top_block; +/// Build a conditional branch forwarding one i32 argument to each destination. +fn conditional_branch_module(ctx: &mut Context) -> (ModuleOp, CondBrOp) { + let module = ModuleOp::new(ctx, "conditional_segments".try_into().unwrap()); + let module_block = module_top_block(ctx, &module); + let i1_ty = IntegerType::get(ctx, 1, Signedness::Signless); + let i32_ty = IntegerType::get(ctx, 32, Signedness::Signless); + let func_ty = FuncType::get( + ctx, + i32_ty.into(), + vec![i1_ty.into(), i32_ty.into(), i32_ty.into()], + false, + ); + let func = FuncOp::new(ctx, "conditional".try_into().unwrap(), func_ty); + let entry = func.get_or_create_entry_block(ctx); + let condition = entry.deref(ctx).get_argument(0); + let if_true = entry.deref(ctx).get_argument(1); + let if_false = entry.deref(ctx).get_argument(2); + let region = func.get_operation().deref(ctx).get_region(0); + let true_dest = BasicBlock::new(ctx, None, vec![i32_ty.into()]); + let false_dest = BasicBlock::new(ctx, None, vec![i32_ty.into()]); + for destination in [true_dest, false_dest] { + destination.insert_at_back(region, ctx); + let result = destination.deref(ctx).get_argument(0); + ReturnOp::new(ctx, Some(result)) + .get_operation() + .insert_at_back(destination, ctx); + } + let branch = CondBrOp::new( + ctx, + condition, + true_dest, + vec![if_true], + false_dest, + vec![if_false], + ); + branch.get_operation().insert_at_back(entry, ctx); + func.get_operation().insert_at_back(module_block, ctx); + (module, branch) +} + +/// Export through the public entry point without assuming prior source verification. +fn export_conditional_module(ctx: &Context, module: &ModuleOp) -> Result { + export_module_to_string_with_config( + ctx, + module, + &NvvmExportConfig::new(NvvmIrDialect::LegacyLlvm7), + ) +} + +/// Invalid segment totals must return an error before accessing successor operands. +#[test] +fn exporter_rejects_out_of_bounds_conditional_segments() { + let mut ctx = Context::new(); + let (module, branch) = conditional_branch_module(&mut ctx); + branch.set_operand_segment_sizes(&ctx, OperandSegmentSizesAttr(vec![1, 3, 0])); + assert!(export_conditional_module(&ctx, &module).is_err()); +} + +/// A missing segment attribute must not reach Pliron's infallible segment accessor. +#[test] +fn exporter_rejects_missing_conditional_segments() { + let mut ctx = Context::new(); + let (module, branch) = conditional_branch_module(&mut ctx); + branch + .get_operation() + .deref_mut(&ctx) + .attributes + .0 + .remove(&ATTR_KEY_OPERAND_SEGMENT_SIZES); + assert!(export_conditional_module(&ctx, &module).is_err()); +} + +/// The segment attribute must have its expected typed representation. +#[test] +fn exporter_rejects_wrong_conditional_segment_attribute_type() { + let mut ctx = Context::new(); + let (module, branch) = conditional_branch_module(&mut ctx); + branch + .get_operation() + .deref_mut(&ctx) + .attributes + .set(ATTR_KEY_OPERAND_SEGMENT_SIZES.clone(), UnitAttr); + assert!(export_conditional_module(&ctx, &module).is_err()); +} + +/// Oversized counts must fail without overflowing either total or prefix arithmetic. +#[test] +fn exporter_rejects_overflowing_conditional_segments() { + let mut ctx = Context::new(); + let (module, branch) = conditional_branch_module(&mut ctx); + branch.set_operand_segment_sizes(&ctx, OperandSegmentSizesAttr(vec![1, u32::MAX, 2])); + assert!(export_conditional_module(&ctx, &module).is_err()); +} + +/// Conditional branches need three segments, starting with exactly one condition. +#[test] +fn exporter_rejects_invalid_conditional_segment_shapes() { + for sizes in [vec![], vec![1, 2], vec![0, 2, 1], vec![2, 0, 1]] { + let mut ctx = Context::new(); + let (module, branch) = conditional_branch_module(&mut ctx); + branch.set_operand_segment_sizes(&ctx, OperandSegmentSizesAttr(sizes.clone())); + assert!( + export_conditional_module(&ctx, &module).is_err(), + "{sizes:?}" + ); + } +} + +/// Valid segment metadata keeps both conditional edges and their distinct forwarded values. +#[test] +fn exporter_preserves_valid_conditional_segments() { + let mut ctx = Context::new(); + let (module, _) = conditional_branch_module(&mut ctx); + let ir = export_conditional_module(&ctx, &module).unwrap(); + assert!(ir.contains("br i1 %v0, label %bb0, label %bb1"), "{ir}"); + assert!(ir.contains("phi i32 [ %v1, %entry ]"), "{ir}"); + assert!(ir.contains("phi i32 [ %v2, %entry ]"), "{ir}"); +} + #[test] fn exporter_rejects_extra_predecessor_values_before_emitting_phis() { let mut ctx = Context::new(); diff --git a/cuda-oxide/crates/llvm-export/tests/export_test/grid_constant.rs b/cuda-oxide/crates/llvm-export/tests/export_test/grid_constant.rs index ce6404520e..e6628f0993 100644 --- a/cuda-oxide/crates/llvm-export/tests/export_test/grid_constant.rs +++ b/cuda-oxide/crates/llvm-export/tests/export_test/grid_constant.rs @@ -23,6 +23,7 @@ use pliron::{ }, common_traits::Verify, context::Context, + identifier::Identifier, linked_list::ContainsLinkedList, op::Op, operation::Operation, @@ -496,12 +497,10 @@ fn grid_constant_rejects_invalid_parameter_contracts() { ); } "incomplete" => { - attrs.0.remove( - &format!("{LLVM_GRID_CONSTANT_ALIGN_ATTR_PREFIX}0") - .as_str() - .try_into() - .unwrap(), - ); + let key = + Identifier::try_new(format!("{LLVM_GRID_CONSTANT_ALIGN_ATTR_PREFIX}0")) + .unwrap(); + attrs.0.remove(&key); } "wrong_attribute_kind" => { attrs.set( diff --git a/cuda-oxide/crates/mir-lower/src/convert/ops/control_flow.rs b/cuda-oxide/crates/mir-lower/src/convert/ops/control_flow.rs index 4e4f899cf4..0b52222a0c 100644 --- a/cuda-oxide/crates/mir-lower/src/convert/ops/control_flow.rs +++ b/cuda-oxide/crates/mir-lower/src/convert/ops/control_flow.rs @@ -626,7 +626,7 @@ mod tests { let (module_ptr, entry) = build_kernel(&mut ctx, vec![i1_ty], vec![]); let cond = entry.deref(&ctx).get_argument(0); let assert_loc = Location::SrcPos { - src: Source::new_from_file(&mut ctx, PathBuf::from("kernel.rs")), + src: Source::new_from_file(&ctx, PathBuf::from("kernel.rs")), pos: combine::stream::position::SourcePosition { line: 44, column: 36, diff --git a/cuda-oxide/crates/rustc-codegen-cuda/Cargo.lock b/cuda-oxide/crates/rustc-codegen-cuda/Cargo.lock index 0c08699d3f..58cebceecf 100644 --- a/cuda-oxide/crates/rustc-codegen-cuda/Cargo.lock +++ b/cuda-oxide/crates/rustc-codegen-cuda/Cargo.lock @@ -139,9 +139,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "combine" -version = "4.6.7" +version = "4.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" dependencies = [ "bytes", "memchr", @@ -591,8 +591,9 @@ dependencies = [ [[package]] name = "pliron" -version = "0.18.0" -source = "git+https://github.com/pliron-org/pliron?rev=06d99a3df938394583715aacf5eadb65b4791c09#06d99a3df938394583715aacf5eadb65b4791c09" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d59d619aada2eb8f68bacac30cfa373f4e247c7667cdac69370f5eaf0b87178" dependencies = [ "awint", "combine", @@ -618,8 +619,9 @@ dependencies = [ [[package]] name = "pliron-derive" -version = "0.18.0" -source = "git+https://github.com/pliron-org/pliron?rev=06d99a3df938394583715aacf5eadb65b4791c09#06d99a3df938394583715aacf5eadb65b4791c09" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e9d7f08f5162e44988420d46a12bd9aa776a9c5a0bee5d1555741060c87a73" dependencies = [ "combine", "convert_case", @@ -632,8 +634,9 @@ dependencies = [ [[package]] name = "pliron-llvm" -version = "0.18.0" -source = "git+https://github.com/pliron-org/pliron?rev=06d99a3df938394583715aacf5eadb65b4791c09#06d99a3df938394583715aacf5eadb65b4791c09" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b04f59dc2ac2198d67f24775a5d5b37f7d4da1feedc893cd43f2cb7d8ff1d9a" dependencies = [ "bitflags", "log", diff --git a/cuda-oxide/crates/rustc-codegen-cuda/Cargo.toml b/cuda-oxide/crates/rustc-codegen-cuda/Cargo.toml index 088e232975..4f1ba79834 100644 --- a/cuda-oxide/crates/rustc-codegen-cuda/Cargo.toml +++ b/cuda-oxide/crates/rustc-codegen-cuda/Cargo.toml @@ -31,12 +31,12 @@ cuda-artifact-finalizer = { path = "../cuda-artifact-finalizer" } # IMPORTANT: This crate is isolated from the main workspace, so we cannot use # `{ workspace = true }`. Keep these versions synchronized with root Cargo.toml -# [workspace.dependencies] section. Source MUST match the root (same git + rev), -# or pliron resolves to two distinct crates and types stop unifying. -pliron = { git = "https://github.com/pliron-org/pliron", rev = "06d99a3df938394583715aacf5eadb65b4791c09" } -pliron-derive = { git = "https://github.com/pliron-org/pliron", rev = "06d99a3df938394583715aacf5eadb65b4791c09" } +# [workspace.dependencies] section. Use the same registry source and exact +# release, or pliron resolves to distinct crates and types stop unifying. +pliron = { version = "=0.19.0" } +pliron-derive = { version = "=0.19.0" } linkme = "0.3" # must match workspace -combine = "4.6" # must match workspace +combine = "4.6.8" # must match workspace; Pliron needs SourcePosition: Hash once_cell = "1.18" # must match workspace thiserror = "2" # must match workspace serde_json = "1" # must match workspace diff --git a/cuda-oxide/deny.toml b/cuda-oxide/deny.toml index 7a7cae1af0..f608d8d640 100644 --- a/cuda-oxide/deny.toml +++ b/cuda-oxide/deny.toml @@ -23,4 +23,3 @@ wildcards = "allow" unknown-registry = "deny" unknown-git = "deny" allow-registry = ["https://github.com/rust-lang/crates.io-index"] -allow-git = ["https://github.com/pliron-org/pliron"] diff --git a/cuda-oxide/dependency-licenses.csv b/cuda-oxide/dependency-licenses.csv index 12de033608..460895e7f4 100644 --- a/cuda-oxide/dependency-licenses.csv +++ b/cuda-oxide/dependency-licenses.csv @@ -18,7 +18,7 @@ cexpr,0.6.0,Apache-2.0/MIT,Yes,registry+https://github.com/rust-lang/crates.io-i cfg-if,1.0.4,MIT OR Apache-2.0,Yes,registry+https://github.com/rust-lang/crates.io-index,3rd Party (crates.io),https://github.com/rust-lang/cfg-if,"A macro to ergonomically define an item depending on a large number of #[cfg] parameters. Structured like an if-else chain, the first matching branch is the item that gets emitted.", clang-sys,1.8.1,Apache-2.0,Yes,registry+https://github.com/rust-lang/crates.io-index,3rd Party (crates.io),https://github.com/KyleMayes/clang-sys,Rust bindings for libclang.,Build-time only: used by bindgen for libclang access. clap,4.6.1,MIT OR Apache-2.0,Yes,registry+https://github.com/rust-lang/crates.io-index,3rd Party (crates.io),https://github.com/clap-rs/clap,"A simple to use, efficient, and full-featured Command Line Argument Parser",Command-line parsing for the cargo-oxide subcommand. -combine,4.6.7,MIT,Yes,registry+https://github.com/rust-lang/crates.io-index,3rd Party (crates.io),https://github.com/Marwes/combine,Fast parser combinators on arbitrary streams with zero-copy support., +combine,4.6.8,MIT,Yes,registry+https://github.com/rust-lang/crates.io-index,3rd Party (crates.io),https://github.com/Marwes/combine,Fast parser combinators on arbitrary streams with zero-copy support., const_fn,0.4.11,Apache-2.0 OR MIT,Yes,registry+https://github.com/rust-lang/crates.io-index,3rd Party (crates.io),https://github.com/taiki-e/const_fn,A lightweight attribute for easy generation of const functions with conditional compilations., convert_case,0.10.0,MIT,Yes,registry+https://github.com/rust-lang/crates.io-index,3rd Party (crates.io),https://github.com/rutrum/convert-case,Convert strings into any case, crossbeam-deque,0.8.6,MIT OR Apache-2.0,Yes,registry+https://github.com/rust-lang/crates.io-index,3rd Party (crates.io),https://github.com/crossbeam-rs/crossbeam,Concurrent work-stealing deque,Work-stealing deque behind rayon; pulled in by hashmap_v2 and hashmap_v3. @@ -82,9 +82,9 @@ nvvm-transforms,0.2.1,Apache-2.0,Yes,local (workspace),1st Party (workspace),htt object,0.36.7,Apache-2.0 OR MIT,Yes,registry+https://github.com/rust-lang/crates.io-index,3rd Party (crates.io),https://github.com/gimli-rs/object,A unified interface for reading and writing object file formats.,ELF reading and writing behind oxide-artifacts' object features. once_cell,1.21.3,MIT OR Apache-2.0,Yes,registry+https://github.com/rust-lang/crates.io-index,3rd Party (crates.io),https://github.com/matklad/once_cell,Single assignment cells and lazy values., oxide-artifacts,0.2.1,Apache-2.0,Yes,registry+https://github.com/rust-lang/crates.io-index,1st Party (crates.io),https://github.com/NVIDIA/cuda-rust,Architecture-neutral embedded device artifact metadata for cuda-oxide,"Consumed from crates.io by the backend (writer) and by the shared cuda-core (reader) so both see one crate; crates/oxide-artifacts in this workspace is the source it is published from." -pliron,0.18.0,Apache-2.0,Yes,git+https://github.com/pliron-org/pliron?rev=06d99a3df938394583715aacf5eadb65b4791c09,3rd Party (git),https://github.com/pliron-org/pliron,Programming Languages Intermediate RepresentatiON,"Primary dependency: MLIR-inspired IR framework. Custom MLIR dialects (MIR, LLVM, NVVM) built on this. Pinned to a git revision; this version is not published to crates.io." -pliron-derive,0.18.0,Apache-2.0,Yes,git+https://github.com/pliron-org/pliron?rev=06d99a3df938394583715aacf5eadb65b4791c09,3rd Party (git),https://github.com/pliron-org/pliron,LLVM Dialect for pliron,"Proc-macro companion to pliron: the Description column copies upstream's own text, which is inaccurate for this crate. Same git pin as pliron." -pliron-llvm,0.18.0,Apache-2.0,Yes,git+https://github.com/pliron-org/pliron?rev=06d99a3df938394583715aacf5eadb65b4791c09,3rd Party (git),https://github.com/pliron-org/pliron,LLVM dialect for pliron,"Upstream LLVM dialect modeling; re-exported by the llvm-export crate. Same git pin as pliron." +pliron,0.19.0,Apache-2.0,Yes,registry+https://github.com/rust-lang/crates.io-index,3rd Party (crates.io),https://github.com/pliron-org/pliron,Programming Languages Intermediate RepresentatiON,"Primary dependency: MLIR-inspired IR framework. Custom MLIR dialects (MIR, LLVM, NVVM) built on this. Exact published release shared by the compiler workspaces." +pliron-derive,0.19.0,Apache-2.0,Yes,registry+https://github.com/rust-lang/crates.io-index,3rd Party (crates.io),https://github.com/pliron-org/pliron,Derive macros for pliron,Proc-macro companion pinned to the same published release as pliron. +pliron-llvm,0.19.0,Apache-2.0,Yes,registry+https://github.com/rust-lang/crates.io-index,3rd Party (crates.io),https://github.com/pliron-org/pliron,LLVM dialect for pliron,Upstream LLVM dialect modeling; re-exported by llvm-export with std enabled and the llvm-sys FFI bridge disabled. Same published release as pliron. png,0.17.16,MIT OR Apache-2.0,Yes,registry+https://github.com/rust-lang/crates.io-index,3rd Party (crates.io),https://github.com/image-rs/image-png,PNG decoding and encoding library in pure Rust,Host-side image input and output for image_convolution. prettyplease,0.2.37,MIT OR Apache-2.0,Yes,registry+https://github.com/rust-lang/crates.io-index,3rd Party (crates.io),https://github.com/dtolnay/prettyplease,A minimal `syn` syntax tree pretty-printer, proc-macro2,1.0.106,MIT OR Apache-2.0,Yes,registry+https://github.com/rust-lang/crates.io-index,3rd Party (crates.io),https://github.com/dtolnay/proc-macro2,A substitute implementation of the compiler's `proc_macro` API to decouple token-based libraries from the procedural macro use case., From e943d182dd7b7fe5b78c1e64dfb4f783f4406d6e Mon Sep 17 00:00:00 2001 From: nihalpasham Date: Fri, 16 Oct 2026 18:37:53 +0530 Subject: [PATCH 2/2] Finish the pliron 0.19 migration in the policy copy and the docs The root deny.toml is a copy of cuda-oxide/deny.toml, so it drops the same pliron allow-git exception and no longer mentions it in its header. The llvm-export README and the book's pliron page now describe pliron as a published crates.io release instead of a git dependency. Signed-off-by: nihalpasham --- cuda-oxide/crates/llvm-export/README.md | 4 ++-- cuda-oxide/cuda-oxide-book/compiler/pliron.md | 2 +- deny.toml | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/cuda-oxide/crates/llvm-export/README.md b/cuda-oxide/crates/llvm-export/README.md index 86ca7d2a81..98381cdc5f 100644 --- a/cuda-oxide/crates/llvm-export/README.md +++ b/cuda-oxide/crates/llvm-export/README.md @@ -5,8 +5,8 @@ pure-Rust textual LLVM IR (`.ll`) exporter, targeting the NVPTX backend. The LLVM dialect *modeling* (ops like `llvm.add`, types like `llvm.ptr`, attributes, and op-interfaces) lives upstream in the `pliron-llvm` crate. -cuda-oxide consumes it as a git dependency and no longer models the LLVM -dialect locally. This crate re-exports `pliron-llvm` so existing +cuda-oxide consumes it as a published crates.io release and no longer models +the LLVM dialect locally. This crate re-exports `pliron-llvm` so existing `llvm_export::{ops,types,attributes,op_interfaces}` paths keep resolving, adds a few GPU-specific extensions pliron-llvm lacks, and keeps the textual `.ll` exporter local (pliron-llvm only emits real `.ll` via an `llvm-sys` bridge, diff --git a/cuda-oxide/cuda-oxide-book/compiler/pliron.md b/cuda-oxide/cuda-oxide-book/compiler/pliron.md index 99f1c38751..ec8d2b1bd6 100644 --- a/cuda-oxide/cuda-oxide-book/compiler/pliron.md +++ b/cuda-oxide/cuda-oxide-book/compiler/pliron.md @@ -73,7 +73,7 @@ model -- operations, regions, basic blocks, types, attributes, dialects, passes | DSLs required | None -- just Rust macros | TableGen, ODS | | Debugging | Standard Rust tooling (`dbg!`, rust-gdb) | gdb on C++ templates | | Extensibility | Add a dialect as a Rust crate | C++ extension against LLVM headers | -| Dependency weight | One crate (git dependency) | Gigabytes of LLVM build artifacts | +| Dependency weight | One published crate | Gigabytes of LLVM build artifacts | For cuda-oxide, this means the entire compiler -- from MIR import to LLVM IR export -- stays within the same Rust build and debugging environment as the rest diff --git a/deny.toml b/deny.toml index af51d7a690..31c0d1773a 100644 --- a/deny.toml +++ b/deny.toml @@ -11,8 +11,8 @@ # # The policy stays in cuda-oxide/deny.toml. That is the file the SIMT, # codegen, device-only, and example walks stop at. The root workspace is -# only the four host crates; the allow-list and the pliron git exception -# belong to the SIMT tree. A single deny.toml at the git root would make +# only the four host crates; the allow-list belongs to the SIMT tree. A +# single deny.toml at the git root would make # those walks continue upward, and any cutile-rs directory that lacked its # own deny.toml would then inherit the Oxide policy. cutile-rs/deny.toml is # a separate product policy and must remain the file a walk inside that @@ -49,4 +49,3 @@ wildcards = "allow" unknown-registry = "deny" unknown-git = "deny" allow-registry = ["https://github.com/rust-lang/crates.io-index"] -allow-git = ["https://github.com/pliron-org/pliron"]