diff --git a/crates/mir-lower/src/convert/ops/arithmetic.rs b/crates/mir-lower/src/convert/ops/arithmetic.rs index 1bdb75b9ca..d988150e7d 100644 --- a/crates/mir-lower/src/convert/ops/arithmetic.rs +++ b/crates/mir-lower/src/convert/ops/arithmetic.rs @@ -72,7 +72,9 @@ fn is_float_type(ctx: &Context, val: Value) -> bool { /// /// Uses `operands_info` to read the *pre-conversion* MIR type (which preserves /// Rust's signedness). After DialectConversion, the live operand type is already -/// signless. Pointer types are treated as unsigned. +/// signless. Pointer types are treated as unsigned. Operands with no conversion +/// history (types that were never convertible, today only signless i1 bools) +/// fall back to the live type's signedness. fn is_signed_int_op( ctx: &Context, op: Ptr, @@ -87,10 +89,24 @@ fn is_signed_int_op( { Ok(false) } else { - pliron::input_err!( - op.deref(ctx).loc(), - "expected IntegerType or MirPtrType operand in arithmetic op" - ) + // No conversion history exists for this operand: its type was never + // convertible, so DialectConversion recorded nothing for it. Today + // that is only signless i1 bools (e.g. a phi produced by + // short-circuit `||`/`&&`). Fall back to the live operand type and + // honor its signedness: signless lowers as unsigned, and pointers + // are always unsigned. + let live = operand.get_type(ctx); + let live_ref = live.deref(ctx); + if let Some(int_ty) = live_ref.downcast_ref::() { + Ok(int_ty.signedness() == Signedness::Signed) + } else if live_ref.is::() { + Ok(false) + } else { + pliron::input_err!( + op.deref(ctx).loc(), + "expected IntegerType or MirPtrType operand in arithmetic op" + ) + } } } diff --git a/crates/mir-lower/tests/lowering_test.rs b/crates/mir-lower/tests/lowering_test.rs index 4a68dcd5e3..e9aba2d347 100644 --- a/crates/mir-lower/tests/lowering_test.rs +++ b/crates/mir-lower/tests/lowering_test.rs @@ -853,3 +853,161 @@ fn test_cvt_f16x2_f32_lowers_to_inline_asm() -> Result<(), anyhow::Error> { assert_inline_asm_lowering(&mut ctx, module_ptr, "cvt.rn.f16x2.f32") } + +/// Regression cover for PR #141: comparisons whose operand is a bool phi. +/// +/// Bools are signless i1, which `can_convert_type` rejects (signless is +/// already the LLVM form), so DialectConversion records no type history for +/// a bool block argument. `is_signed_int_op` used to error out for such +/// operands ("expected IntegerType or MirPtrType operand in arithmetic op"); +/// it must instead fall back to the live operand type and lower the +/// comparison as unsigned. +/// +/// The function mirrors the MIR of a short-circuit kernel: +/// +/// ```text +/// let p = a || b; // bool phi: merge block argument +/// out = (p == q, p < q); // icmp eq i1 / icmp ult i1 +/// ``` +/// +/// ```text +/// bb0(a: i1, b: i1, q: i1): mir.cond_br a, bb2(a), bb1() +/// bb1(): mir.goto bb2(b) +/// bb2(p: i1): mir.eq p, q ; mir.lt p, q ; mir.return +/// ``` +#[test] +fn test_bool_phi_cmp_lowers_to_unsigned_i1_icmp() -> Result<(), anyhow::Error> { + use llvm_export::attributes::ICmpPredicateAttr; + use pliron::basic_block::BasicBlock; + use pliron::builtin::op_interfaces::OperandSegmentInterface; + use pliron::builtin::types::{FunctionType, IntegerType, Signedness}; + use pliron::r#type::Typed; + + let mut ctx = Context::new(); + dialect_mir::register(&mut ctx); + dialect_nvvm::register(&mut ctx); + mir_lower::register(&mut ctx); + + let module = ModuleOp::new(&mut ctx, "test_module".try_into().unwrap()); + let module_ptr = module.get_operation(); + + let bool_ty = IntegerType::get(&mut ctx, 1, Signedness::Signless); + let arg_tys: Vec> = + vec![bool_ty.into(), bool_ty.into(), bool_ty.into()]; + let func_name = "bool_phi_cmp"; + let func_ty = FunctionType::get(&mut ctx, arg_tys.clone(), vec![]); + + let func_op_ptr = Operation::new( + &mut ctx, + mir::MirFuncOp::get_concrete_op_info(), + vec![], + vec![], + vec![], + 1, + ); + let func_ty_attr = pliron::builtin::attributes::TypeAttr::new(func_ty.into()); + let func = mir::MirFuncOp::new(&mut ctx, func_op_ptr, func_ty_attr); + func.set_symbol_name(&mut ctx, func_name.try_into().unwrap()); + + let region = func.get_operation().deref(&ctx).get_region(0); + + // bb0(a, b, q): the function entry. + let bb0 = BasicBlock::new(&mut ctx, None, arg_tys); + bb0.insert_at_back(region, &ctx); + let a = bb0.deref(&ctx).get_argument(0); + let b = bb0.deref(&ctx).get_argument(1); + let q = bb0.deref(&ctx).get_argument(2); + + // bb1(): the short-circuit "evaluate b" block. + let bb1 = BasicBlock::new(&mut ctx, None, vec![]); + bb1.insert_at_back(region, &ctx); + + // bb2(p): the merge block; `p` is the bool phi. + let bb2 = BasicBlock::new(&mut ctx, None, vec![bool_ty.into()]); + bb2.insert_at_back(region, &ctx); + let p = bb2.deref(&ctx).get_argument(0); + + // bb0: cond_br a, bb2(a), bb1(). On the true edge `a` is true, so + // passing `a` itself is `a || b` without needing a constant. + let (flat_operands, segment_sizes) = + mir::MirCondBranchOp::compute_segment_sizes(vec![vec![a], vec![a], vec![]]); + let cond_br = Operation::new( + &mut ctx, + mir::MirCondBranchOp::get_concrete_op_info(), + vec![], + flat_operands, + vec![bb2, bb1], + 0, + ); + Operation::get_op::(cond_br, &ctx) + .expect("MirCondBranchOp") + .set_operand_segment_sizes(&mut ctx, segment_sizes); + cond_br.insert_at_back(bb0, &ctx); + + // bb1: goto bb2(b). + let goto = Operation::new( + &mut ctx, + mir::MirGotoOp::get_concrete_op_info(), + vec![], + vec![b], + vec![bb2], + 0, + ); + goto.insert_at_back(bb1, &ctx); + + // bb2: p == q, then p < q. + for info in [ + mir::MirEqOp::get_concrete_op_info(), + mir::MirLtOp::get_concrete_op_info(), + ] { + let cmp = Operation::new(&mut ctx, info, vec![bool_ty.into()], vec![p, q], vec![], 0); + cmp.insert_at_back(bb2, &ctx); + } + let ret_op = Operation::new( + &mut ctx, + mir::MirReturnOp::get_concrete_op_info(), + vec![], + vec![], + vec![], + 0, + ); + ret_op.insert_at_back(bb2, &ctx); + + let module_region = module.get_operation().deref(&ctx).get_region(0); + let module_block = module_region.deref(&ctx).iter(&ctx).next().unwrap(); + func.get_operation().insert_at_back(module_block, &ctx); + + // Before the fallback, this failed with "expected IntegerType or + // MirPtrType operand in arithmetic op". + mir_lower::lower_mir_to_llvm(&mut ctx, module_ptr).map_err(|e| anyhow::anyhow!("{}", e))?; + + let mut icmps = Vec::new(); + let module_op = module_ptr.deref(&ctx); + let region = module_op.get_region(0); + let block = region.deref(&ctx).iter(&ctx).next().unwrap(); + for op in block.deref(&ctx).iter(&ctx) { + let Some(func_op) = Operation::get_op::(op, &ctx) else { + continue; + }; + if func_op.get_symbol_name(&ctx).to_string() != func_name { + continue; + } + let func_region = func_op.get_operation().deref(&ctx).get_region(0); + for func_block in func_region.deref(&ctx).iter(&ctx) { + for body_op in func_block.deref(&ctx).iter(&ctx) { + if let Some(icmp) = Operation::get_op::(body_op, &ctx) { + let lhs_ty = body_op.deref(&ctx).get_operand(0).get_type(&ctx); + icmps.push((icmp.predicate(&ctx), lhs_ty)); + } + } + } + } + + let i1: pliron::context::Ptr = bool_ty.into(); + assert_eq!( + icmps, + vec![(ICmpPredicateAttr::EQ, i1), (ICmpPredicateAttr::ULT, i1),], + "bool-phi comparisons must lower to `icmp eq i1` and `icmp ult i1`" + ); + Ok(()) +} diff --git a/crates/rustc-codegen-cuda/examples/bool_phi_cmp/Cargo.lock b/crates/rustc-codegen-cuda/examples/bool_phi_cmp/Cargo.lock new file mode 100644 index 0000000000..60be1b8bd6 --- /dev/null +++ b/crates/rustc-codegen-cuda/examples/bool_phi_cmp/Cargo.lock @@ -0,0 +1,424 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "bool_phi_cmp" +version = "0.1.0" +dependencies = [ + "cuda-core", + "cuda-device", + "cuda-host", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "cuda-bindings" +version = "0.2.1" +dependencies = [ + "bindgen", +] + +[[package]] +name = "cuda-core" +version = "0.2.1" +dependencies = [ + "anyhow", + "cuda-bindings", + "half", + "oxide-artifacts", +] + +[[package]] +name = "cuda-device" +version = "0.2.1" +dependencies = [ + "cuda-macros", +] + +[[package]] +name = "cuda-host" +version = "0.2.1" +dependencies = [ + "cuda-core", + "cuda-macros", + "half", + "libnvvm-sys", + "nvjitlink-sys", + "thiserror", +] + +[[package]] +name = "cuda-macros" +version = "0.2.1" +dependencies = [ + "proc-macro2", + "quote", + "reserved-oxide-symbols", + "syn", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libnvvm-sys" +version = "0.2.1" +dependencies = [ + "libloading", + "thiserror", +] + +[[package]] +name = "log" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nvjitlink-sys" +version = "0.2.1" +dependencies = [ + "libloading", + "thiserror", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "crc32fast", + "hashbrown 0.15.5", + "indexmap", + "memchr", +] + +[[package]] +name = "oxide-artifacts" +version = "0.2.1" +dependencies = [ + "object", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reserved-oxide-symbols" +version = "0.2.1" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/crates/rustc-codegen-cuda/examples/bool_phi_cmp/Cargo.toml b/crates/rustc-codegen-cuda/examples/bool_phi_cmp/Cargo.toml new file mode 100644 index 0000000000..99846e1ff7 --- /dev/null +++ b/crates/rustc-codegen-cuda/examples/bool_phi_cmp/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "bool_phi_cmp" +version = "0.1.0" +edition = "2024" +license = "Apache-2.0" + +[workspace] + +[dependencies] +cuda-device = { path = "../../../cuda-device" } +cuda-host = { path = "../../../cuda-host" } +cuda-core = { path = "../../../cuda-core" } diff --git a/crates/rustc-codegen-cuda/examples/bool_phi_cmp/src/main.rs b/crates/rustc-codegen-cuda/examples/bool_phi_cmp/src/main.rs new file mode 100644 index 0000000000..b5f016544a --- /dev/null +++ b/crates/rustc-codegen-cuda/examples/bool_phi_cmp/src/main.rs @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Comparisons on a bool produced by short-circuit `||` (PR #141). + * + * A bool assigned in two arms of a short-circuit `||`/`&&` becomes a + * block argument (phi) after mem2reg. Bools are signless i1 in + * dialect-mir, which `can_convert_type` rejects, so DialectConversion + * records no pre-conversion type for the phi. `is_signed_int_op` used to + * fail the whole lowering for such operands: + * + * expected IntegerType or MirPtrType operand in arithmetic op + * + * It now falls back to the live operand type and lowers the comparison + * as unsigned (`icmp eq i1` / `icmp ult i1`). + * + * The kernel feeds the phi into both `==` and `<`; the inputs cover all + * four (p, q) truth combinations so a wrong predicate fails loudly. + * + * Build: + * cargo oxide run bool_phi_cmp + */ + +use cuda_core::{CudaContext, DeviceBuffer, LaunchConfig}; +use cuda_device::{DisjointSlice, kernel, thread}; +use cuda_host::cuda_module; + +#[cuda_module] +mod kernels { + use super::*; + + /// `p` is the short-circuit bool phi; `q` is an independent bool. + /// Encodes both comparisons in one lane: `(p == q) + 2 * (p < q)`. + // The bool `<` is the regression under test: it must reach mir-lower as a + // comparison on an untracked i1 phi and lower to `icmp ult i1`. Clippy's + // simplification (`!p & q`) would change the MIR shape and skip that path. + #[allow(clippy::bool_comparison)] + #[kernel] + pub fn bool_phi_cmp(a: &[u32], b: &[u32], mut out: DisjointSlice) { + let idx = thread::index_1d(); + let i = idx.get(); + if let Some(e) = out.get_mut(idx) { + let p = a[i] > 1 || b[i] > 2; + let q = a[i] == 0; + *e = (p == q) as u32 + 2 * ((p < q) as u32); + } + } +} + +fn main() { + let ctx = CudaContext::new(0).expect("CUDA init"); + let stream = ctx.default_stream(); + let module = kernels::load(&ctx).expect("Load embedded PTX"); + + // Lanes cover all four (p, q) combinations: + // i=0: a=0, b=0 -> p=false, q=true -> eq=0, lt=1 -> 2 + // i=1: a=2, b=0 -> p=true, q=false -> eq=0, lt=0 -> 0 + // i=2: a=0, b=5 -> p=true, q=true -> eq=1, lt=0 -> 1 + // i=3: a=1, b=1 -> p=false, q=false -> eq=1, lt=0 -> 1 + let a = vec![0u32, 2, 0, 1]; + let b = vec![0u32, 0, 5, 1]; + let expect = vec![2u32, 0, 1, 1]; + + let dev_a = DeviceBuffer::from_host(&stream, &a).unwrap(); + let dev_b = DeviceBuffer::from_host(&stream, &b).unwrap(); + let mut dev_out = DeviceBuffer::::zeroed(&stream, a.len()).unwrap(); + + module + .bool_phi_cmp( + &stream, + LaunchConfig::for_num_elems(a.len() as u32), + &dev_a, + &dev_b, + &mut dev_out, + ) + .expect("launch"); + + let out = dev_out.to_host_vec(&stream).unwrap(); + println!("got : {out:?}"); + println!("expect : {expect:?}"); + if out == expect { + println!("SUCCESS"); + } else { + println!("FAILURE: bool-phi comparison produced wrong lanes"); + std::process::exit(1); + } +}