#![cfg(feature = "cuda-oxide-backend")]
use cranelift_codegen::ir::{self, AbiParam, ArgumentPurpose, Signature};
use cranelift_codegen::isa::CallConv;
use crate::lowered_ir::{LoweredSignature, LoweredType};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum SignatureLoweringError {
#[error("unsupported type at {position}: {ty}")]
UnsupportedType {
position: String,
ty: String,
},
#[error("unsupported argument purpose at {position}: {purpose}")]
UnsupportedArgumentPurpose {
position: String,
purpose: String,
},
#[error("unsupported call conv: {conv}")]
UnsupportedCallConv {
conv: String,
},
}
pub fn lower_signature(sig: &Signature) -> Result<LoweredSignature, SignatureLoweringError> {
if !is_supported_call_conv(sig.call_conv) {
return Err(SignatureLoweringError::UnsupportedCallConv {
conv: sig.call_conv.to_string(),
});
}
let mut params = Vec::with_capacity(sig.params.len());
for (i, p) in sig.params.iter().enumerate() {
params.push(lower_abi_param(p, "param", i)?);
}
let mut returns = Vec::with_capacity(sig.returns.len());
for (i, r) in sig.returns.iter().enumerate() {
returns.push(lower_abi_param(r, "return", i)?);
}
Ok(LoweredSignature { params, returns })
}
fn lower_abi_param(
p: &AbiParam,
kind: &str,
idx: usize,
) -> Result<LoweredType, SignatureLoweringError> {
if p.purpose != ArgumentPurpose::Normal {
return Err(SignatureLoweringError::UnsupportedArgumentPurpose {
position: format!("{kind} {idx}"),
purpose: p.purpose.to_string(),
});
}
cranelift_type_to_lowered(p.value_type).ok_or_else(|| SignatureLoweringError::UnsupportedType {
position: format!("{kind} {idx}"),
ty: p.value_type.to_string(),
})
}
fn is_supported_call_conv(conv: CallConv) -> bool {
matches!(conv, CallConv::SystemV | CallConv::Fast | CallConv::Cold)
}
pub fn cranelift_type_to_lowered(ty: ir::Type) -> Option<LoweredType> {
use ir::types as t;
if ty == t::I8 {
return Some(LoweredType::I8);
}
if ty == t::I16 {
return Some(LoweredType::I16);
}
if ty == t::I32 {
return Some(LoweredType::I32);
}
if ty == t::I64 {
return Some(LoweredType::I64);
}
if ty == t::F32 {
return Some(LoweredType::F32);
}
if ty == t::F64 {
return Some(LoweredType::F64);
}
if ty == t::R32 || ty == t::R64 {
return Some(LoweredType::Ptr);
}
if ty.is_vector() && !ty.is_dynamic_vector() {
let lane_ty = cranelift_type_to_lowered(ty.lane_type())?;
if !(lane_ty.is_int() || lane_ty.is_float()) {
return None;
}
let lanes_u32 = ty.lane_count();
let lanes: u8 = u8::try_from(lanes_u32).ok()?;
return Some(LoweredType::V128 {
lane_type: Box::new(lane_ty),
lanes,
});
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use cranelift_codegen::ir::{types, AbiParam, ArgumentPurpose, Signature};
use cranelift_codegen::isa::CallConv;
#[test]
fn maps_i8() {
assert_eq!(cranelift_type_to_lowered(types::I8), Some(LoweredType::I8));
}
#[test]
fn maps_i16() {
assert_eq!(
cranelift_type_to_lowered(types::I16),
Some(LoweredType::I16)
);
}
#[test]
fn maps_i32() {
assert_eq!(
cranelift_type_to_lowered(types::I32),
Some(LoweredType::I32)
);
}
#[test]
fn maps_i64() {
assert_eq!(
cranelift_type_to_lowered(types::I64),
Some(LoweredType::I64)
);
}
#[test]
fn rejects_i128() {
assert_eq!(cranelift_type_to_lowered(types::I128), None);
}
#[test]
fn maps_f32() {
assert_eq!(
cranelift_type_to_lowered(types::F32),
Some(LoweredType::F32)
);
}
#[test]
fn maps_f64() {
assert_eq!(
cranelift_type_to_lowered(types::F64),
Some(LoweredType::F64)
);
}
#[test]
fn rejects_f16() {
assert_eq!(cranelift_type_to_lowered(types::F16), None);
}
#[test]
fn rejects_f128() {
assert_eq!(cranelift_type_to_lowered(types::F128), None);
}
#[test]
fn maps_r32_to_ptr() {
assert_eq!(
cranelift_type_to_lowered(types::R32),
Some(LoweredType::Ptr)
);
}
#[test]
fn maps_r64_to_ptr() {
assert_eq!(
cranelift_type_to_lowered(types::R64),
Some(LoweredType::Ptr)
);
}
#[test]
fn maps_i8x16() {
assert_eq!(
cranelift_type_to_lowered(types::I8X16),
Some(LoweredType::V128 {
lane_type: Box::new(LoweredType::I8),
lanes: 16,
})
);
}
#[test]
fn maps_i16x8() {
assert_eq!(
cranelift_type_to_lowered(types::I16X8),
Some(LoweredType::V128 {
lane_type: Box::new(LoweredType::I16),
lanes: 8,
})
);
}
#[test]
fn maps_i32x4() {
assert_eq!(
cranelift_type_to_lowered(types::I32X4),
Some(LoweredType::V128 {
lane_type: Box::new(LoweredType::I32),
lanes: 4,
})
);
}
#[test]
fn maps_i64x2() {
assert_eq!(
cranelift_type_to_lowered(types::I64X2),
Some(LoweredType::V128 {
lane_type: Box::new(LoweredType::I64),
lanes: 2,
})
);
}
#[test]
fn maps_f32x4() {
assert_eq!(
cranelift_type_to_lowered(types::F32X4),
Some(LoweredType::V128 {
lane_type: Box::new(LoweredType::F32),
lanes: 4,
})
);
}
#[test]
fn maps_f64x2() {
assert_eq!(
cranelift_type_to_lowered(types::F64X2),
Some(LoweredType::V128 {
lane_type: Box::new(LoweredType::F64),
lanes: 2,
})
);
}
#[test]
fn rejects_invalid_type() {
assert_eq!(cranelift_type_to_lowered(types::INVALID), None);
}
#[test]
fn lowers_empty_signature() {
let sig = Signature::new(CallConv::SystemV);
let lowered = lower_signature(&sig).expect("empty sig lowers");
assert!(lowered.params.is_empty());
assert!(lowered.returns.is_empty());
assert_eq!(lowered, LoweredSignature::default());
}
#[test]
fn lowers_i32_f32_to_i64() {
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(types::I32));
sig.params.push(AbiParam::new(types::F32));
sig.returns.push(AbiParam::new(types::I64));
let lowered = lower_signature(&sig).expect("happy-path sig lowers");
assert_eq!(
lowered,
LoweredSignature {
params: vec![LoweredType::I32, LoweredType::F32],
returns: vec![LoweredType::I64],
}
);
}
#[test]
fn rejects_i128_param_at_position_zero() {
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(types::I128));
sig.returns.push(AbiParam::new(types::I32));
let err = lower_signature(&sig).expect_err("I128 must be rejected");
match err {
SignatureLoweringError::UnsupportedType { position, ty } => {
assert_eq!(position, "param 0");
assert_eq!(ty, types::I128.to_string());
}
other => panic!("expected UnsupportedType, got {other:?}"),
}
}
#[test]
fn rejects_i128_return_at_position_one() {
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(types::I32));
sig.returns.push(AbiParam::new(types::I32));
sig.returns.push(AbiParam::new(types::I128));
let err = lower_signature(&sig).expect_err("I128 return must be rejected");
match err {
SignatureLoweringError::UnsupportedType { position, ty } => {
assert_eq!(position, "return 1");
assert_eq!(ty, types::I128.to_string());
}
other => panic!("expected UnsupportedType, got {other:?}"),
}
}
#[test]
fn lowers_v128_return_with_lane_info() {
let mut sig = Signature::new(CallConv::SystemV);
sig.returns.push(AbiParam::new(types::F32X4));
let lowered = lower_signature(&sig).expect("vector return lowers");
assert_eq!(
lowered,
LoweredSignature {
params: vec![],
returns: vec![LoweredType::V128 {
lane_type: Box::new(LoweredType::F32),
lanes: 4,
}],
}
);
}
#[test]
fn lowers_v128_param() {
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(types::I32X4));
let lowered = lower_signature(&sig).expect("vector param lowers");
assert_eq!(
lowered.params,
vec![LoweredType::V128 {
lane_type: Box::new(LoweredType::I32),
lanes: 4,
}]
);
assert!(lowered.returns.is_empty());
}
#[test]
fn rejects_struct_return_purpose() {
let mut sig = Signature::new(CallConv::SystemV);
sig.params
.push(AbiParam::special(types::I64, ArgumentPurpose::StructReturn));
let err = lower_signature(&sig).expect_err("StructReturn must be rejected");
match err {
SignatureLoweringError::UnsupportedArgumentPurpose { position, purpose } => {
assert_eq!(position, "param 0");
assert_eq!(purpose, ArgumentPurpose::StructReturn.to_string());
}
other => panic!("expected UnsupportedArgumentPurpose, got {other:?}"),
}
}
#[test]
fn rejects_vmcontext_purpose() {
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(types::I32));
sig.params
.push(AbiParam::special(types::I64, ArgumentPurpose::VMContext));
let err = lower_signature(&sig).expect_err("VMContext must be rejected");
match err {
SignatureLoweringError::UnsupportedArgumentPurpose { position, .. } => {
assert_eq!(position, "param 1");
}
other => panic!("expected UnsupportedArgumentPurpose, got {other:?}"),
}
}
#[test]
fn accepts_systemv_fast_cold_callconv() {
for cc in [CallConv::SystemV, CallConv::Fast, CallConv::Cold] {
let sig = Signature::new(cc);
assert!(
lower_signature(&sig).is_ok(),
"callconv {cc:?} should be accepted",
);
}
}
#[test]
fn rejects_windows_fastcall_callconv() {
let sig = Signature::new(CallConv::WindowsFastcall);
let err = lower_signature(&sig).expect_err("WindowsFastcall must be rejected");
match err {
SignatureLoweringError::UnsupportedCallConv { conv } => {
assert_eq!(conv, CallConv::WindowsFastcall.to_string());
}
other => panic!("expected UnsupportedCallConv, got {other:?}"),
}
}
#[test]
fn rejects_wasmtime_systemv_callconv() {
let sig = Signature::new(CallConv::WasmtimeSystemV);
assert!(matches!(
lower_signature(&sig),
Err(SignatureLoweringError::UnsupportedCallConv { .. })
));
}
#[test]
fn callconv_check_runs_before_param_check() {
let mut sig = Signature::new(CallConv::WindowsFastcall);
sig.params.push(AbiParam::new(types::I128));
assert!(matches!(
lower_signature(&sig),
Err(SignatureLoweringError::UnsupportedCallConv { .. })
));
}
#[test]
fn error_display_includes_position_and_type() {
let err = SignatureLoweringError::UnsupportedType {
position: "param 0".to_string(),
ty: "i128".to_string(),
};
let s = err.to_string();
assert!(s.contains("param 0"));
assert!(s.contains("i128"));
}
}