#![cfg(feature = "cuda-oxide-backend")]
use cranelift_codegen::ir as cl;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum LoweringError {
#[error("lowering: unsupported opcode `{op}` (no lower_* family matched)")]
UnsupportedOpcode {
op: String,
location: InstLocation,
},
#[error("lowering: unsupported type `{ty}` at {location}")]
UnsupportedType {
ty: String,
location: InstLocation,
},
#[error("lowering: undefined value `{value:?}` at {location}")]
UndefinedValue {
value: cl::Value,
location: InstLocation,
},
#[error("lowering: branch references unknown block `{block:?}` at {location}")]
BadBlockReference {
block: cl::Block,
location: InstLocation,
},
#[error("lowering: malformed terminator in block `{block_id}`")]
MalformedTerminator {
block_id: crate::lowered_ir::LoweredBlockId,
},
#[error("lowering: function rejected by reject-list: {reason}")]
Rejected {
reason: String,
location: InstLocation,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstLocation {
pub block: u32,
pub inst_index: u32,
}
impl std::fmt::Display for InstLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "block{}[{}]", self.block, self.inst_index)
}
}
impl InstLocation {
pub fn new(block: cl::Block, inst_index: u32) -> Self {
use cranelift_codegen::entity::EntityRef;
Self {
block: block.index() as u32,
inst_index,
}
}
}
impl From<LoweringError> for crate::pliron_dialect::PlironLoweringError {
fn from(err: LoweringError) -> Self {
Self::UnsupportedOp {
op: err.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pliron_dialect::PlironLoweringError;
use cranelift_codegen::entity::EntityRef;
fn loc(block: u32, inst_index: u32) -> InstLocation {
InstLocation { block, inst_index }
}
#[test]
fn inst_location_display_matches_block_index_format() {
assert_eq!(loc(5, 3).to_string(), "block5[3]");
assert_eq!(loc(0, 0).to_string(), "block0[0]");
assert_eq!(loc(42, 17).to_string(), "block42[17]");
}
#[test]
fn inst_location_new_round_trips_block_index() {
let block = cl::Block::new(7);
let location = InstLocation::new(block, 4);
assert_eq!(location.block, 7);
assert_eq!(location.inst_index, 4);
assert_eq!(location.to_string(), "block7[4]");
}
#[test]
fn inst_location_eq() {
assert_eq!(loc(1, 2), loc(1, 2));
assert_ne!(loc(1, 2), loc(1, 3));
assert_ne!(loc(1, 2), loc(2, 2));
}
#[test]
fn unsupported_opcode_displays_op_and_location() {
let err = LoweringError::UnsupportedOpcode {
op: "iadd_imm".to_string(),
location: loc(2, 5),
};
let s = err.to_string();
assert!(s.contains("iadd_imm"), "got: {s}");
assert!(s.contains("no lower_* family matched"), "got: {s}");
}
#[test]
fn unsupported_type_displays_ty_and_location() {
let err = LoweringError::UnsupportedType {
ty: "i128".to_string(),
location: loc(0, 1),
};
let s = err.to_string();
assert!(s.contains("i128"), "got: {s}");
assert!(s.contains("block0[1]"), "got: {s}");
}
#[test]
fn undefined_value_displays_value_and_location() {
let value = cl::Value::new(13);
let err = LoweringError::UndefinedValue {
value,
location: loc(3, 0),
};
let s = err.to_string();
assert!(s.contains("block3[0]"), "got: {s}");
assert!(s.contains("undefined value"), "got: {s}");
}
#[test]
fn bad_block_reference_displays_block_and_location() {
let block = cl::Block::new(9);
let err = LoweringError::BadBlockReference {
block,
location: loc(1, 7),
};
let s = err.to_string();
assert!(s.contains("block1[7]"), "got: {s}");
assert!(s.contains("unknown block"), "got: {s}");
}
#[test]
fn malformed_terminator_displays_block_id() {
let err = LoweringError::MalformedTerminator { block_id: 4 };
let s = err.to_string();
assert!(s.contains("malformed terminator"), "got: {s}");
assert!(s.contains("4"), "got: {s}");
}
#[test]
fn rejected_displays_reason_and_location() {
let err = LoweringError::Rejected {
reason: "atomic ops not supported".to_string(),
location: loc(0, 3),
};
let s = err.to_string();
assert!(s.contains("atomic ops not supported"), "got: {s}");
assert!(s.contains("rejected by reject-list"), "got: {s}");
}
#[test]
fn lowering_error_eq() {
let a = LoweringError::UnsupportedOpcode {
op: "iadd_imm".to_string(),
location: loc(2, 5),
};
let b = LoweringError::UnsupportedOpcode {
op: "iadd_imm".to_string(),
location: loc(2, 5),
};
let c = LoweringError::UnsupportedOpcode {
op: "iadd_imm".to_string(),
location: loc(2, 6),
};
assert_eq!(a, b);
assert_ne!(a, c);
let d = LoweringError::MalformedTerminator { block_id: 4 };
let e = LoweringError::MalformedTerminator { block_id: 4 };
let f = LoweringError::MalformedTerminator { block_id: 5 };
assert_eq!(d, e);
assert_ne!(d, f);
assert_ne!(a, d);
}
#[test]
fn from_lowering_error_into_pliron_unsupported_op_uses_display() {
let err = LoweringError::UnsupportedOpcode {
op: "iadd_imm".to_string(),
location: loc(2, 5),
};
let display = err.to_string();
let pliron: PlironLoweringError = err.into();
match pliron {
PlironLoweringError::UnsupportedOp { op } => {
assert_eq!(op, display);
assert!(op.contains("iadd_imm"), "got: {op}");
}
other => panic!("expected UnsupportedOp, got {other:?}"),
}
}
#[test]
fn from_lowering_error_into_pliron_for_rejected_variant() {
let err = LoweringError::Rejected {
reason: "atomics".to_string(),
location: loc(0, 0),
};
let display = err.to_string();
let pliron: PlironLoweringError = err.into();
match pliron {
PlironLoweringError::UnsupportedOp { op } => {
assert_eq!(op, display);
assert!(op.contains("atomics"), "got: {op}");
}
other => panic!("expected UnsupportedOp, got {other:?}"),
}
}
#[test]
fn inst_location_new_uses_entity_ref_index() {
let block = cl::Block::new(123);
assert_eq!(block.index(), 123);
let location = InstLocation::new(block, 0);
assert_eq!(location.block, 123);
}
}