1#![cfg(feature = "cuda-oxide-backend")]
27
28use cranelift_codegen::ir::{self, AbiParam, ArgumentPurpose, Signature};
29use cranelift_codegen::isa::CallConv;
30
31use crate::lowered_ir::{LoweredSignature, LoweredType};
32
33#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
35pub enum SignatureLoweringError {
36 #[error("unsupported type at {position}: {ty}")]
38 UnsupportedType {
39 position: String,
41 ty: String,
43 },
44 #[error("unsupported argument purpose at {position}: {purpose}")]
48 UnsupportedArgumentPurpose {
49 position: String,
51 purpose: String,
53 },
54 #[error("unsupported call conv: {conv}")]
56 UnsupportedCallConv {
57 conv: String,
59 },
60}
61
62pub fn lower_signature(sig: &Signature) -> Result<LoweredSignature, SignatureLoweringError> {
82 if !is_supported_call_conv(sig.call_conv) {
84 return Err(SignatureLoweringError::UnsupportedCallConv {
85 conv: sig.call_conv.to_string(),
86 });
87 }
88
89 let mut params = Vec::with_capacity(sig.params.len());
90 for (i, p) in sig.params.iter().enumerate() {
91 params.push(lower_abi_param(p, "param", i)?);
92 }
93
94 let mut returns = Vec::with_capacity(sig.returns.len());
95 for (i, r) in sig.returns.iter().enumerate() {
96 returns.push(lower_abi_param(r, "return", i)?);
97 }
98
99 Ok(LoweredSignature { params, returns })
100}
101
102fn lower_abi_param(
106 p: &AbiParam,
107 kind: &str,
108 idx: usize,
109) -> Result<LoweredType, SignatureLoweringError> {
110 if p.purpose != ArgumentPurpose::Normal {
111 return Err(SignatureLoweringError::UnsupportedArgumentPurpose {
112 position: format!("{kind} {idx}"),
113 purpose: p.purpose.to_string(),
114 });
115 }
116 cranelift_type_to_lowered(p.value_type).ok_or_else(|| SignatureLoweringError::UnsupportedType {
117 position: format!("{kind} {idx}"),
118 ty: p.value_type.to_string(),
119 })
120}
121
122fn is_supported_call_conv(conv: CallConv) -> bool {
128 matches!(conv, CallConv::SystemV | CallConv::Fast | CallConv::Cold)
129}
130
131pub fn cranelift_type_to_lowered(ty: ir::Type) -> Option<LoweredType> {
149 use ir::types as t;
150
151 if ty == t::I8 {
153 return Some(LoweredType::I8);
154 }
155 if ty == t::I16 {
156 return Some(LoweredType::I16);
157 }
158 if ty == t::I32 {
159 return Some(LoweredType::I32);
160 }
161 if ty == t::I64 {
162 return Some(LoweredType::I64);
163 }
164 if ty == t::F32 {
169 return Some(LoweredType::F32);
170 }
171 if ty == t::F64 {
172 return Some(LoweredType::F64);
173 }
174 if ty == t::R32 || ty == t::R64 {
181 return Some(LoweredType::Ptr);
182 }
183
184 if ty.is_vector() && !ty.is_dynamic_vector() {
190 let lane_ty = cranelift_type_to_lowered(ty.lane_type())?;
191 if !(lane_ty.is_int() || lane_ty.is_float()) {
194 return None;
195 }
196 let lanes_u32 = ty.lane_count();
197 let lanes: u8 = u8::try_from(lanes_u32).ok()?;
198 return Some(LoweredType::V128 {
199 lane_type: Box::new(lane_ty),
200 lanes,
201 });
202 }
203
204 None
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use cranelift_codegen::ir::{types, AbiParam, ArgumentPurpose, Signature};
211 use cranelift_codegen::isa::CallConv;
212
213 #[test]
216 fn maps_i8() {
217 assert_eq!(cranelift_type_to_lowered(types::I8), Some(LoweredType::I8));
218 }
219
220 #[test]
221 fn maps_i16() {
222 assert_eq!(
223 cranelift_type_to_lowered(types::I16),
224 Some(LoweredType::I16)
225 );
226 }
227
228 #[test]
229 fn maps_i32() {
230 assert_eq!(
231 cranelift_type_to_lowered(types::I32),
232 Some(LoweredType::I32)
233 );
234 }
235
236 #[test]
237 fn maps_i64() {
238 assert_eq!(
239 cranelift_type_to_lowered(types::I64),
240 Some(LoweredType::I64)
241 );
242 }
243
244 #[test]
245 fn rejects_i128() {
246 assert_eq!(cranelift_type_to_lowered(types::I128), None);
250 }
251
252 #[test]
253 fn maps_f32() {
254 assert_eq!(
255 cranelift_type_to_lowered(types::F32),
256 Some(LoweredType::F32)
257 );
258 }
259
260 #[test]
261 fn maps_f64() {
262 assert_eq!(
263 cranelift_type_to_lowered(types::F64),
264 Some(LoweredType::F64)
265 );
266 }
267
268 #[test]
269 fn rejects_f16() {
270 assert_eq!(cranelift_type_to_lowered(types::F16), None);
271 }
272
273 #[test]
274 fn rejects_f128() {
275 assert_eq!(cranelift_type_to_lowered(types::F128), None);
276 }
277
278 #[test]
279 fn maps_r32_to_ptr() {
280 assert_eq!(
281 cranelift_type_to_lowered(types::R32),
282 Some(LoweredType::Ptr)
283 );
284 }
285
286 #[test]
287 fn maps_r64_to_ptr() {
288 assert_eq!(
289 cranelift_type_to_lowered(types::R64),
290 Some(LoweredType::Ptr)
291 );
292 }
293
294 #[test]
295 fn maps_i8x16() {
296 assert_eq!(
297 cranelift_type_to_lowered(types::I8X16),
298 Some(LoweredType::V128 {
299 lane_type: Box::new(LoweredType::I8),
300 lanes: 16,
301 })
302 );
303 }
304
305 #[test]
306 fn maps_i16x8() {
307 assert_eq!(
308 cranelift_type_to_lowered(types::I16X8),
309 Some(LoweredType::V128 {
310 lane_type: Box::new(LoweredType::I16),
311 lanes: 8,
312 })
313 );
314 }
315
316 #[test]
317 fn maps_i32x4() {
318 assert_eq!(
319 cranelift_type_to_lowered(types::I32X4),
320 Some(LoweredType::V128 {
321 lane_type: Box::new(LoweredType::I32),
322 lanes: 4,
323 })
324 );
325 }
326
327 #[test]
328 fn maps_i64x2() {
329 assert_eq!(
330 cranelift_type_to_lowered(types::I64X2),
331 Some(LoweredType::V128 {
332 lane_type: Box::new(LoweredType::I64),
333 lanes: 2,
334 })
335 );
336 }
337
338 #[test]
339 fn maps_f32x4() {
340 assert_eq!(
341 cranelift_type_to_lowered(types::F32X4),
342 Some(LoweredType::V128 {
343 lane_type: Box::new(LoweredType::F32),
344 lanes: 4,
345 })
346 );
347 }
348
349 #[test]
350 fn maps_f64x2() {
351 assert_eq!(
352 cranelift_type_to_lowered(types::F64X2),
353 Some(LoweredType::V128 {
354 lane_type: Box::new(LoweredType::F64),
355 lanes: 2,
356 })
357 );
358 }
359
360 #[test]
361 fn rejects_invalid_type() {
362 assert_eq!(cranelift_type_to_lowered(types::INVALID), None);
363 }
364
365 #[test]
368 fn lowers_empty_signature() {
369 let sig = Signature::new(CallConv::SystemV);
370 let lowered = lower_signature(&sig).expect("empty sig lowers");
371 assert!(lowered.params.is_empty());
372 assert!(lowered.returns.is_empty());
373 assert_eq!(lowered, LoweredSignature::default());
374 }
375
376 #[test]
377 fn lowers_i32_f32_to_i64() {
378 let mut sig = Signature::new(CallConv::SystemV);
380 sig.params.push(AbiParam::new(types::I32));
381 sig.params.push(AbiParam::new(types::F32));
382 sig.returns.push(AbiParam::new(types::I64));
383
384 let lowered = lower_signature(&sig).expect("happy-path sig lowers");
385 assert_eq!(
386 lowered,
387 LoweredSignature {
388 params: vec![LoweredType::I32, LoweredType::F32],
389 returns: vec![LoweredType::I64],
390 }
391 );
392 }
393
394 #[test]
395 fn rejects_i128_param_at_position_zero() {
396 let mut sig = Signature::new(CallConv::SystemV);
397 sig.params.push(AbiParam::new(types::I128));
398 sig.returns.push(AbiParam::new(types::I32));
399
400 let err = lower_signature(&sig).expect_err("I128 must be rejected");
401 match err {
402 SignatureLoweringError::UnsupportedType { position, ty } => {
403 assert_eq!(position, "param 0");
404 assert_eq!(ty, types::I128.to_string());
406 }
407 other => panic!("expected UnsupportedType, got {other:?}"),
408 }
409 }
410
411 #[test]
412 fn rejects_i128_return_at_position_one() {
413 let mut sig = Signature::new(CallConv::SystemV);
416 sig.params.push(AbiParam::new(types::I32));
417 sig.returns.push(AbiParam::new(types::I32));
418 sig.returns.push(AbiParam::new(types::I128));
419
420 let err = lower_signature(&sig).expect_err("I128 return must be rejected");
421 match err {
422 SignatureLoweringError::UnsupportedType { position, ty } => {
423 assert_eq!(position, "return 1");
424 assert_eq!(ty, types::I128.to_string());
425 }
426 other => panic!("expected UnsupportedType, got {other:?}"),
427 }
428 }
429
430 #[test]
431 fn lowers_v128_return_with_lane_info() {
432 let mut sig = Signature::new(CallConv::SystemV);
434 sig.returns.push(AbiParam::new(types::F32X4));
435
436 let lowered = lower_signature(&sig).expect("vector return lowers");
437 assert_eq!(
438 lowered,
439 LoweredSignature {
440 params: vec![],
441 returns: vec![LoweredType::V128 {
442 lane_type: Box::new(LoweredType::F32),
443 lanes: 4,
444 }],
445 }
446 );
447 }
448
449 #[test]
450 fn lowers_v128_param() {
451 let mut sig = Signature::new(CallConv::SystemV);
453 sig.params.push(AbiParam::new(types::I32X4));
454
455 let lowered = lower_signature(&sig).expect("vector param lowers");
456 assert_eq!(
457 lowered.params,
458 vec![LoweredType::V128 {
459 lane_type: Box::new(LoweredType::I32),
460 lanes: 4,
461 }]
462 );
463 assert!(lowered.returns.is_empty());
464 }
465
466 #[test]
467 fn rejects_struct_return_purpose() {
468 let mut sig = Signature::new(CallConv::SystemV);
469 sig.params
470 .push(AbiParam::special(types::I64, ArgumentPurpose::StructReturn));
471
472 let err = lower_signature(&sig).expect_err("StructReturn must be rejected");
473 match err {
474 SignatureLoweringError::UnsupportedArgumentPurpose { position, purpose } => {
475 assert_eq!(position, "param 0");
476 assert_eq!(purpose, ArgumentPurpose::StructReturn.to_string());
477 }
478 other => panic!("expected UnsupportedArgumentPurpose, got {other:?}"),
479 }
480 }
481
482 #[test]
483 fn rejects_vmcontext_purpose() {
484 let mut sig = Signature::new(CallConv::SystemV);
485 sig.params.push(AbiParam::new(types::I32));
486 sig.params
487 .push(AbiParam::special(types::I64, ArgumentPurpose::VMContext));
488
489 let err = lower_signature(&sig).expect_err("VMContext must be rejected");
490 match err {
491 SignatureLoweringError::UnsupportedArgumentPurpose { position, .. } => {
492 assert_eq!(position, "param 1");
493 }
494 other => panic!("expected UnsupportedArgumentPurpose, got {other:?}"),
495 }
496 }
497
498 #[test]
499 fn accepts_systemv_fast_cold_callconv() {
500 for cc in [CallConv::SystemV, CallConv::Fast, CallConv::Cold] {
501 let sig = Signature::new(cc);
502 assert!(
503 lower_signature(&sig).is_ok(),
504 "callconv {cc:?} should be accepted",
505 );
506 }
507 }
508
509 #[test]
510 fn rejects_windows_fastcall_callconv() {
511 let sig = Signature::new(CallConv::WindowsFastcall);
512 let err = lower_signature(&sig).expect_err("WindowsFastcall must be rejected");
513 match err {
514 SignatureLoweringError::UnsupportedCallConv { conv } => {
515 assert_eq!(conv, CallConv::WindowsFastcall.to_string());
516 }
517 other => panic!("expected UnsupportedCallConv, got {other:?}"),
518 }
519 }
520
521 #[test]
522 fn rejects_wasmtime_systemv_callconv() {
523 let sig = Signature::new(CallConv::WasmtimeSystemV);
524 assert!(matches!(
525 lower_signature(&sig),
526 Err(SignatureLoweringError::UnsupportedCallConv { .. })
527 ));
528 }
529
530 #[test]
531 fn callconv_check_runs_before_param_check() {
532 let mut sig = Signature::new(CallConv::WindowsFastcall);
535 sig.params.push(AbiParam::new(types::I128));
536 assert!(matches!(
537 lower_signature(&sig),
538 Err(SignatureLoweringError::UnsupportedCallConv { .. })
539 ));
540 }
541
542 #[test]
543 fn error_display_includes_position_and_type() {
544 let err = SignatureLoweringError::UnsupportedType {
545 position: "param 0".to_string(),
546 ty: "i128".to_string(),
547 };
548 let s = err.to_string();
549 assert!(s.contains("param 0"));
550 assert!(s.contains("i128"));
551 }
552}