Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::translator::values::{ValueMap, establish_declared_pointer_type};
use crate::translator::{payload_store, rvalue};
use dialect_mir::{
attributes::MirPointerKindAuthorityAttr,
ops::{MirCallOp, MirConstructArrayOp, MirGotoOp},
ops::{MirCallOp, MirConstantOp, MirConstructArrayOp, MirGotoOp},
types::{MirArrayType, MirPtrType},
};
use pliron::basic_block::BasicBlock;
Expand Down Expand Up @@ -683,6 +683,73 @@ pub fn emit_unit_noop_intrinsic(
)
}

/// Emits a `bool`-returning intrinsic whose result is a fixed constant.
///
/// Used for `core::intrinsics::is_val_statically_known`, which may return
/// `false` for any argument. The argument is a MIR operand with no side
/// effects, so it is not evaluated.
#[allow(clippy::too_many_arguments)]
pub fn emit_bool_constant_intrinsic(
ctx: &mut Context,
body: &mir::Body,
destination: &mir::Place,
target: &Option<usize>,
block_ptr: Ptr<BasicBlock>,
prev_op: Option<Ptr<Operation>>,
value_map: &mut ValueMap,
block_map: &[Ptr<BasicBlock>],
loc: Location,
intrinsic_name: &str,
value: bool,
) -> TranslationResult<Ptr<Operation>> {
use pliron::builtin::attributes::IntegerAttr;
use pliron::utils::apint::APInt;

let (prepared_destination, prepared_last_op) = prepare_destination_write(
ctx,
body,
destination,
value_map,
block_ptr,
prev_op,
loc.clone(),
)?;

let bool_ty = IntegerType::get(ctx, 1, Signedness::Signless);
let const_op = Operation::new(
ctx,
MirConstantOp::get_concrete_op_info(),
vec![bool_ty.into()],
vec![],
vec![],
0,
);
const_op.deref_mut(ctx).set_loc(loc.clone());
MirConstantOp::new(const_op).set_attr_value(
ctx,
IntegerAttr::new(
bool_ty,
APInt::from_u64(u64::from(value), std::num::NonZeroUsize::new(1).unwrap()),
),
);
insert_op(ctx, const_op, block_ptr, prepared_last_op);

let const_val = const_op.deref(ctx).get_result(0);
let no_target_msg = format!("{} call without target not supported", intrinsic_name);
emit_prepared_result_and_goto(
ctx,
prepared_destination,
const_val,
target,
block_ptr,
const_op,
value_map,
block_map,
loc,
&no_target_msg,
)
}

#[cfg(test)]
// Tests build kinded fixture types directly; production code mints via facts::PointerOrigin.
#[allow(clippy::disallowed_methods)]
Expand Down
19 changes: 19 additions & 0 deletions cuda-oxide/crates/mir-importer/src/translator/terminator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2864,6 +2864,25 @@ fn try_dispatch_intrinsic(
name,
)?))
}
// `false` is a valid answer for any argument. libcore calls this from
// integer `checked_pow`/`overflowing_pow` and `checked_ilog`, so
// rejecting it also rejects integer `pow` and `ilog`, which call them.
"core::intrinsics::is_val_statically_known"
| "std::intrinsics::is_val_statically_known" => {
Ok(Some(helpers::emit_bool_constant_intrinsic(
ctx,
body,
destination,
target,
block_ptr,
prev_op,
value_map,
block_map,
loc,
name,
false,
)?))
}
"core::intrinsics::copy" | "std::intrinsics::copy" => Ok(Some(emit_ptr_memmove(
ctx,
body,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
//! Unsigned `u8` and signed `i8` cases cover both overflow and non-overflow
//! paths, including `Some(0)` so it cannot be confused with `None`.
//!
//! The `pow_family` kernel covers integer `pow`, `checked_pow`,
//! `overflowing_pow`, `saturating_pow`, `wrapping_pow`, `ilog` and
//! `checked_ilog`, which failed to compile while `is_val_statically_known`
//! was unsupported.
//!
//! Run:
//! cargo oxide run checked_arith
//! CUDA_OXIDE_NO_OPT=1 cargo oxide run checked_arith
Expand Down Expand Up @@ -136,6 +141,75 @@ mod kernels {
};
}
}

/// Exercise the integer `pow` family and `ilog`. libcore implements them
/// with `is_val_statically_known`, so they must compile for the device.
///
/// Encodings:
/// - `checked_pow`: `None` -> 0, `Some(v)` -> bit 32 set, `v` in bits 0..31
/// - `overflowing_pow`: result in bits 0..31, overflow flag in bit 32
/// - `pow` on `u64`: the plain result; every input here is in range
#[kernel]
pub fn pow_family(
base: &[i32],
exp: &[u32],
mut checked_out: DisjointSlice<u64>,
mut overflowing_out: DisjointSlice<u64>,
mut misc_out: DisjointSlice<u64>,
mut pow_out: DisjointSlice<u64>,
) {
let i = thread::index_1d().get();
if i >= base.len() {
return;
}
let (b, e) = (base[i], exp[i]);

if let Some(o) = checked_out.get_mut(thread::index_1d()) {
*o = match b.checked_pow(e) {
Some(value) => (1 << 32) | value as u32 as u64,
None => 0,
};
}

if let Some(o) = overflowing_out.get_mut(thread::index_1d()) {
let (value, overflow) = (b as u32).overflowing_pow(e);
*o = value as u64 | ((overflow as u64) << 32);
}

if let Some(o) = misc_out.get_mut(thread::index_1d()) {
let saturating = (b as u8).saturating_pow(e) as u64;
let wrapping = (b as i16).wrapping_pow(e) as u16 as u64;
let ilog = match (b as u32).checked_ilog(10) {
Some(value) => 0x80 | value as u64,
None => 0,
};
let ilog10 = (b.unsigned_abs() | 1).ilog(10) as u64;
*o = saturating | (wrapping << 8) | (ilog << 24) | (ilog10 << 32);
}

if let Some(o) = pow_out.get_mut(thread::index_1d()) {
*o = (b.unsigned_abs() as u64).pow(e);
}
}
}

fn expected_pow_family(b: i32, e: u32) -> (u64, u64, u64, u64) {
let checked = match b.checked_pow(e) {
Some(value) => (1 << 32) | value as u32 as u64,
None => 0,
};
let (value, overflow) = (b as u32).overflowing_pow(e);
let overflowing = value as u64 | ((overflow as u64) << 32);
let saturating = (b as u8).saturating_pow(e) as u64;
let wrapping = (b as i16).wrapping_pow(e) as u16 as u64;
let ilog = match (b as u32).checked_ilog(10) {
Some(value) => 0x80 | value as u64,
None => 0,
};
let ilog10 = (b.unsigned_abs() | 1).ilog(10) as u64;
let misc = saturating | (wrapping << 8) | (ilog << 24) | (ilog10 << 32);
let pow = (b.unsigned_abs() as u64).pow(e);
(checked, overflowing, misc, pow)
}

fn check(label: &str, got: u32, expected_result: u8, expected_overflow: bool) -> bool {
Expand Down Expand Up @@ -300,6 +374,40 @@ fn main() {
let checked_i8_sub = checked_i8_sub_dev.to_host_vec(&stream).unwrap();
let checked_i8_mul = checked_i8_mul_dev.to_host_vec(&stream).unwrap();

// --- integer pow family and ilog ---
//
// Includes a zero exponent, negative bases with odd exponents, and both
// in-range and overflowing results (7^12 and 46341^2 overflow i32).
let pow_base: Vec<i32> = vec![2, -3, 10, 0, 1, -1, 7, 46341];
let pow_exp: Vec<u32> = vec![10, 5, 9, 0, 100, 3, 12, 2];
let pow_n = pow_base.len();
let pow_base_dev = DeviceBuffer::from_host(&stream, &pow_base).unwrap();
let pow_exp_dev = DeviceBuffer::from_host(&stream, &pow_exp).unwrap();
let mut pow_checked_dev = DeviceBuffer::<u64>::zeroed(&stream, pow_n).unwrap();
let mut pow_overflowing_dev = DeviceBuffer::<u64>::zeroed(&stream, pow_n).unwrap();
let mut pow_misc_dev = DeviceBuffer::<u64>::zeroed(&stream, pow_n).unwrap();
let mut pow_plain_dev = DeviceBuffer::<u64>::zeroed(&stream, pow_n).unwrap();

// SAFETY: launch shape/resources match the kernel; buffers cover its accesses.
unsafe {
module.pow_family(
&stream,
LaunchConfig::for_num_elems(pow_n as u32),
&pow_base_dev,
&pow_exp_dev,
&mut pow_checked_dev,
&mut pow_overflowing_dev,
&mut pow_misc_dev,
&mut pow_plain_dev,
)
}
.expect("pow_family launch");

let pow_checked = pow_checked_dev.to_host_vec(&stream).unwrap();
let pow_overflowing = pow_overflowing_dev.to_host_vec(&stream).unwrap();
let pow_misc = pow_misc_dev.to_host_vec(&stream).unwrap();
let pow_plain = pow_plain_dev.to_host_vec(&stream).unwrap();

let mut ok = true;

// Historical overflowing arithmetic regression.
Expand Down Expand Up @@ -364,10 +472,27 @@ fn main() {
);
}

// Integer pow family and ilog, against the host.
for i in 0..pow_n {
let (b, e) = (pow_base[i], pow_exp[i]);
let got = (
pow_checked[i],
pow_overflowing[i],
pow_misc[i],
pow_plain[i],
);
let want = expected_pow_family(b, e);
if got != want {
eprintln!(" FAIL pow_family[{i}] {b}^{e}: got {got:#x?}, want {want:#x?}");
ok = false;
}
}

if ok {
println!("SUCCESS: all overflowing_{{add,sub,mul}} results correct");
println!("PASS: checked_add/sub/mul (unsigned u8)");
println!("PASS: checked_add/sub/mul (signed i8)");
println!("PASS: integer pow family and ilog");
println!("PASS: checked_arith");
} else {
std::process::exit(1);
Expand Down