#![cfg(feature = "cuda-oxide-backend")]
use std::fmt;
pub type LoweredValueId = u32;
pub type LoweredBlockId = u32;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum LoweredType {
I8,
I16,
I32,
I64,
F32,
F64,
Ptr,
Bool,
V128 {
lane_type: Box<LoweredType>,
lanes: u8,
},
}
impl LoweredType {
pub fn is_int(&self) -> bool {
matches!(self, Self::I8 | Self::I16 | Self::I32 | Self::I64)
}
pub fn is_float(&self) -> bool {
matches!(self, Self::F32 | Self::F64)
}
pub fn is_vector(&self) -> bool {
matches!(self, Self::V128 { .. })
}
pub fn scalar_bits(&self) -> Option<u32> {
match self {
Self::I8 => Some(8),
Self::I16 => Some(16),
Self::I32 | Self::F32 => Some(32),
Self::I64 | Self::F64 => Some(64),
_ => None,
}
}
}
impl fmt::Display for LoweredType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::I8 => f.write_str("i8"),
Self::I16 => f.write_str("i16"),
Self::I32 => f.write_str("i32"),
Self::I64 => f.write_str("i64"),
Self::F32 => f.write_str("f32"),
Self::F64 => f.write_str("f64"),
Self::Ptr => f.write_str("ptr"),
Self::Bool => f.write_str("bool"),
Self::V128 { lane_type, lanes } => write!(f, "v{lanes}x{lane_type}"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum LoweredOp {
AddI {
ty: LoweredType,
lhs: LoweredValueId,
rhs: LoweredValueId,
result: LoweredValueId,
},
SubI {
ty: LoweredType,
lhs: LoweredValueId,
rhs: LoweredValueId,
result: LoweredValueId,
},
MulI {
ty: LoweredType,
lhs: LoweredValueId,
rhs: LoweredValueId,
result: LoweredValueId,
},
DivS {
ty: LoweredType,
lhs: LoweredValueId,
rhs: LoweredValueId,
result: LoweredValueId,
},
DivU {
ty: LoweredType,
lhs: LoweredValueId,
rhs: LoweredValueId,
result: LoweredValueId,
},
RemS {
ty: LoweredType,
lhs: LoweredValueId,
rhs: LoweredValueId,
result: LoweredValueId,
},
RemU {
ty: LoweredType,
lhs: LoweredValueId,
rhs: LoweredValueId,
result: LoweredValueId,
},
AddF {
ty: LoweredType,
lhs: LoweredValueId,
rhs: LoweredValueId,
result: LoweredValueId,
},
SubF {
ty: LoweredType,
lhs: LoweredValueId,
rhs: LoweredValueId,
result: LoweredValueId,
},
MulF {
ty: LoweredType,
lhs: LoweredValueId,
rhs: LoweredValueId,
result: LoweredValueId,
},
DivF {
ty: LoweredType,
lhs: LoweredValueId,
rhs: LoweredValueId,
result: LoweredValueId,
},
Fma {
ty: LoweredType,
a: LoweredValueId,
b: LoweredValueId,
c: LoweredValueId,
result: LoweredValueId,
},
FNeg {
ty: LoweredType,
src: LoweredValueId,
result: LoweredValueId,
},
FAbs {
ty: LoweredType,
src: LoweredValueId,
result: LoweredValueId,
},
Load {
ty: LoweredType,
base: LoweredValueId,
offset: i32,
result: LoweredValueId,
},
Store {
ty: LoweredType,
value: LoweredValueId,
base: LoweredValueId,
offset: i32,
},
StackAlloc {
ty: LoweredType,
bytes: u32,
result: LoweredValueId,
},
Br {
target: LoweredBlockId,
args: Vec<LoweredValueId>,
},
CondBr {
cond: LoweredValueId,
then_target: LoweredBlockId,
then_args: Vec<LoweredValueId>,
else_target: LoweredBlockId,
else_args: Vec<LoweredValueId>,
},
Switch {
value: LoweredValueId,
default_target: LoweredBlockId,
cases: Vec<(u32, LoweredBlockId)>,
},
Return {
values: Vec<LoweredValueId>,
},
VMin {
lane_ty: LoweredType,
signed: bool,
lhs: LoweredValueId,
rhs: LoweredValueId,
result: LoweredValueId,
},
VMax {
lane_ty: LoweredType,
signed: bool,
lhs: LoweredValueId,
rhs: LoweredValueId,
result: LoweredValueId,
},
VSplat {
lane_ty: LoweredType,
src: LoweredValueId,
result: LoweredValueId,
},
VSelect {
lane_ty: LoweredType,
cond: LoweredValueId,
then_v: LoweredValueId,
else_v: LoweredValueId,
result: LoweredValueId,
},
VAllTrue {
src: LoweredValueId,
result: LoweredValueId,
},
VAnyTrue {
src: LoweredValueId,
result: LoweredValueId,
},
Select {
ty: LoweredType,
cond: LoweredValueId,
then_v: LoweredValueId,
else_v: LoweredValueId,
result: LoweredValueId,
},
Bitcast {
from_ty: LoweredType,
to_ty: LoweredType,
src: LoweredValueId,
result: LoweredValueId,
},
TruncI {
from_ty: LoweredType,
to_ty: LoweredType,
src: LoweredValueId,
result: LoweredValueId,
},
ExtendU {
from_ty: LoweredType,
to_ty: LoweredType,
src: LoweredValueId,
result: LoweredValueId,
},
ExtendS {
from_ty: LoweredType,
to_ty: LoweredType,
src: LoweredValueId,
result: LoweredValueId,
},
}
impl LoweredOp {
pub fn result(&self) -> Option<LoweredValueId> {
match self {
Self::AddI { result, .. }
| Self::SubI { result, .. }
| Self::MulI { result, .. }
| Self::DivS { result, .. }
| Self::DivU { result, .. }
| Self::RemS { result, .. }
| Self::RemU { result, .. }
| Self::AddF { result, .. }
| Self::SubF { result, .. }
| Self::MulF { result, .. }
| Self::DivF { result, .. }
| Self::Fma { result, .. }
| Self::FNeg { result, .. }
| Self::FAbs { result, .. }
| Self::Load { result, .. }
| Self::StackAlloc { result, .. }
| Self::VMin { result, .. }
| Self::VMax { result, .. }
| Self::VSplat { result, .. }
| Self::VSelect { result, .. }
| Self::VAllTrue { result, .. }
| Self::VAnyTrue { result, .. }
| Self::Select { result, .. }
| Self::Bitcast { result, .. }
| Self::TruncI { result, .. }
| Self::ExtendU { result, .. }
| Self::ExtendS { result, .. } => Some(*result),
Self::Store { .. }
| Self::Br { .. }
| Self::CondBr { .. }
| Self::Switch { .. }
| Self::Return { .. } => None,
}
}
pub fn is_terminator(&self) -> bool {
matches!(
self,
Self::Br { .. } | Self::CondBr { .. } | Self::Switch { .. } | Self::Return { .. }
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LoweredSignature {
pub params: Vec<LoweredType>,
pub returns: Vec<LoweredType>,
}
impl LoweredSignature {
pub fn new() -> Self {
Self {
params: Vec::new(),
returns: Vec::new(),
}
}
}
impl Default for LoweredSignature {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct LoweredBlock {
pub id: LoweredBlockId,
pub params: Vec<(LoweredValueId, LoweredType)>,
pub ops: Vec<LoweredOp>,
}
impl LoweredBlock {
pub fn new(id: LoweredBlockId) -> Self {
Self {
id,
params: Vec::new(),
ops: Vec::new(),
}
}
pub fn is_well_formed(&self) -> bool {
self.ops
.last()
.map(LoweredOp::is_terminator)
.unwrap_or(false)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct LoweredFunction {
pub name: String,
pub signature: LoweredSignature,
pub blocks: Vec<LoweredBlock>,
pub entry: LoweredBlockId,
}
impl LoweredFunction {
pub fn new(name: impl Into<String>, signature: LoweredSignature) -> Self {
Self {
name: name.into(),
signature,
blocks: Vec::new(),
entry: 0,
}
}
pub fn block(&self, id: LoweredBlockId) -> Option<&LoweredBlock> {
self.blocks.iter().find(|b| b.id == id)
}
pub fn is_well_formed(&self) -> bool {
!self.blocks.is_empty() && self.blocks.iter().all(LoweredBlock::is_well_formed)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lowered_type_scalar_bits() {
assert_eq!(LoweredType::I32.scalar_bits(), Some(32));
assert_eq!(LoweredType::F64.scalar_bits(), Some(64));
assert_eq!(LoweredType::Ptr.scalar_bits(), None);
assert_eq!(LoweredType::Bool.scalar_bits(), None);
assert_eq!(
LoweredType::V128 {
lane_type: Box::new(LoweredType::F32),
lanes: 4,
}
.scalar_bits(),
None
);
}
#[test]
fn lowered_type_classification() {
assert!(LoweredType::I32.is_int());
assert!(!LoweredType::I32.is_float());
assert!(LoweredType::F32.is_float());
assert!(!LoweredType::F32.is_int());
assert!(LoweredType::V128 {
lane_type: Box::new(LoweredType::F32),
lanes: 4
}
.is_vector());
}
#[test]
fn lowered_type_display() {
assert_eq!(LoweredType::I32.to_string(), "i32");
assert_eq!(LoweredType::F32.to_string(), "f32");
assert_eq!(LoweredType::Ptr.to_string(), "ptr");
let v = LoweredType::V128 {
lane_type: Box::new(LoweredType::F32),
lanes: 4,
};
assert_eq!(v.to_string(), "v4xf32");
}
#[test]
fn op_result_arith() {
let op = LoweredOp::AddI {
ty: LoweredType::I32,
lhs: 1,
rhs: 2,
result: 3,
};
assert_eq!(op.result(), Some(3));
assert!(!op.is_terminator());
}
#[test]
fn op_result_store() {
let op = LoweredOp::Store {
ty: LoweredType::I32,
value: 1,
base: 2,
offset: 0,
};
assert_eq!(op.result(), None);
assert!(!op.is_terminator());
}
#[test]
fn op_is_terminator() {
assert!(LoweredOp::Br {
target: 0,
args: vec![]
}
.is_terminator());
assert!(LoweredOp::Return { values: vec![] }.is_terminator());
assert!(LoweredOp::CondBr {
cond: 0,
then_target: 1,
then_args: vec![],
else_target: 2,
else_args: vec![]
}
.is_terminator());
assert!(LoweredOp::Switch {
value: 0,
default_target: 1,
cases: vec![]
}
.is_terminator());
}
#[test]
fn block_well_formed_requires_terminator() {
let mut block = LoweredBlock::new(0);
assert!(!block.is_well_formed());
block.ops.push(LoweredOp::AddI {
ty: LoweredType::I32,
lhs: 1,
rhs: 2,
result: 3,
});
assert!(!block.is_well_formed());
block.ops.push(LoweredOp::Return { values: vec![3] });
assert!(block.is_well_formed());
}
#[test]
fn function_lookup_and_well_formed() {
let mut func = LoweredFunction::new(
"kernel",
LoweredSignature {
params: vec![LoweredType::Ptr],
returns: vec![],
},
);
let mut b0 = LoweredBlock::new(0);
b0.ops.push(LoweredOp::Return { values: vec![] });
func.blocks.push(b0);
assert!(func.block(0).is_some());
assert!(func.block(99).is_none());
assert!(func.is_well_formed());
}
#[test]
fn empty_function_not_well_formed() {
let func = LoweredFunction::new("empty", LoweredSignature::default());
assert!(!func.is_well_formed());
}
}