Rust · 61032 bytes Raw Blame History
1 //! Lowering of Fortran intrinsic functions to IR.
2 //!
3 //! Extracted from `core.rs` in Sprint 11 Stage B.1. Pure mechanical
4 //! move — behavior unchanged. Helpers consulted via `core::*`.
5
6 use crate::ir::builder::FuncBuilder;
7 use crate::ir::inst::*;
8 use crate::ir::types::*;
9
10 use super::core::*;
11 use super::helpers::{coerce_to_type, storage_size_bits_for_ir_type};
12
13 /// Lower a Fortran intrinsic function call to IR instructions.
14 /// Returns Some(ValueId) if recognized, None for external functions.
15 pub(crate) fn lower_intrinsic(b: &mut FuncBuilder, name: &str, args: &[ValueId]) -> Option<ValueId> {
16 match name {
17 "cmplx" => {
18 if let Some(real_arg) = args.first() {
19 let kind = args
20 .get(2)
21 .and_then(|arg| extract_const_int_from_value(b, *arg))
22 .unwrap_or_else(|| {
23 if args.iter().any(|arg| {
24 let ty = b.func().value_type(*arg);
25 matches!(ty, Some(IrType::Float(FloatWidth::F64)))
26 || ty.as_ref().is_some_and(|ty| {
27 is_complex_ty(ty) && complex_float_width(ty) == FloatWidth::F64
28 })
29 }) {
30 8
31 } else {
32 4
33 }
34 });
35 let fw = if kind == 8 {
36 FloatWidth::F64
37 } else {
38 FloatWidth::F32
39 };
40 let elem_ty = IrType::Float(fw);
41 let buf = b.alloca(IrType::Array(Box::new(elem_ty.clone()), 2));
42 let zero = b.const_i64(0);
43 let imag_offset = b.const_i64(if fw == FloatWidth::F64 { 8 } else { 4 });
44 let real_val = coerce_to_type(b, *real_arg, &elem_ty);
45 let imag_val = if let Some(imag_arg) = args.get(1) {
46 coerce_to_type(b, *imag_arg, &elem_ty)
47 } else {
48 match fw {
49 FloatWidth::F64 => b.const_f64(0.0),
50 FloatWidth::F32 => b.const_f32(0.0),
51 }
52 };
53 let real_ptr = b.gep(buf, vec![zero], IrType::Int(IntWidth::I8));
54 b.store(real_val, real_ptr);
55 let imag_ptr = b.gep(buf, vec![imag_offset], IrType::Int(IntWidth::I8));
56 b.store(imag_val, imag_ptr);
57 Some(buf)
58 } else {
59 None
60 }
61 }
62 "conjg" => {
63 if let Some(arg) = args.first() {
64 let ty = b
65 .func()
66 .value_type(*arg)
67 .unwrap_or(IrType::Int(IntWidth::I32));
68 if is_complex_ty(&ty) {
69 let fw = complex_float_width(&ty);
70 let elem_ty = IrType::Float(fw);
71 let buf = b.alloca(IrType::Array(Box::new(elem_ty.clone()), 2));
72 let zero = b.const_i64(0);
73 let imag_offset = b.const_i64(if fw == FloatWidth::F64 { 8 } else { 4 });
74 let real_ptr = b.gep(*arg, vec![zero], IrType::Int(IntWidth::I8));
75 let imag_ptr = b.gep(*arg, vec![imag_offset], IrType::Int(IntWidth::I8));
76 let real_val = b.load_typed(real_ptr, elem_ty.clone());
77 let imag_val = b.load_typed(imag_ptr, elem_ty.clone());
78 let neg_imag = b.fneg(imag_val);
79 let out_real_ptr = b.gep(buf, vec![zero], IrType::Int(IntWidth::I8));
80 b.store(real_val, out_real_ptr);
81 let out_imag_ptr = b.gep(buf, vec![imag_offset], IrType::Int(IntWidth::I8));
82 b.store(neg_imag, out_imag_ptr);
83 Some(buf)
84 } else {
85 None
86 }
87 } else {
88 None
89 }
90 }
91 "merge" => {
92 if args.len() >= 3 {
93 let mut ty = b
94 .func()
95 .value_type(args[0])
96 .unwrap_or(IrType::Int(IntWidth::I32));
97 if ty.is_float() {
98 if matches!(
99 b.func().value_type(args[1]),
100 Some(IrType::Float(FloatWidth::F64))
101 ) {
102 ty = IrType::Float(FloatWidth::F64);
103 }
104 } else if ty.is_int() {
105 let width = [args[0], args[1]]
106 .iter()
107 .filter_map(|arg| b.func().value_type(*arg).and_then(|ty| ty.int_width()))
108 .max_by_key(|width| width.bits())
109 .unwrap_or(IntWidth::I32);
110 ty = IrType::Int(width);
111 }
112 let true_val = coerce_to_type(b, args[0], &ty);
113 let false_val = coerce_to_type(b, args[1], &ty);
114 let mask = coerce_to_type(b, args[2], &IrType::Bool);
115 Some(b.select(mask, true_val, false_val))
116 } else {
117 None
118 }
119 }
120 "transfer" => {
121 if args.len() >= 2 {
122 let mold_ty = b
123 .func()
124 .value_type(args[1])
125 .unwrap_or(IrType::Int(IntWidth::I32));
126 Some(coerce_to_type(b, args[0], &mold_ty))
127 } else {
128 None
129 }
130 }
131 "mod" => {
132 // MOD(a, p) = a - INT(a/p) * p (sign of dividend)
133 if args.len() >= 2 {
134 let (lhs, rhs) = unify_int_widths(b, args[0], args[1]);
135 let ty = b
136 .func()
137 .value_type(lhs)
138 .unwrap_or(IrType::Int(IntWidth::I32));
139 if ty.is_float() {
140 Some(b.call(FuncRef::External("fmod".into()), vec![lhs, rhs], ty))
141 } else {
142 Some(b.imod(lhs, rhs))
143 }
144 } else {
145 None
146 }
147 }
148 "modulo" => {
149 // MODULO(a, p) = a - FLOOR(a/p) * p (sign of divisor, result in [0, |p|))
150 // For integers: if result has opposite sign to p, add p.
151 if args.len() >= 2 {
152 let (lhs, rhs) = unify_int_widths(b, args[0], args[1]);
153 let ty = b
154 .func()
155 .value_type(lhs)
156 .unwrap_or(IrType::Int(IntWidth::I32));
157 if ty.is_float() {
158 // Float modulo: use fmod then adjust.
159 let rem = b.call(FuncRef::External("fmod".into()), vec![lhs, rhs], ty.clone());
160 let sum = b.fadd(rem, rhs);
161 let rem2 = b.call(FuncRef::External("fmod".into()), vec![sum, rhs], ty);
162 Some(rem2)
163 } else {
164 // Integer modulo: rem = a % p; if (rem != 0 && (rem ^ p) < 0) rem += p
165 let rem = b.imod(lhs, rhs);
166 let zero = match &ty {
167 IrType::Int(IntWidth::I64) => b.const_i64(0),
168 _ => b.const_i32(0),
169 };
170 let rem_ne_zero = b.icmp(CmpOp::Ne, rem, zero);
171 let rem_xor_p = b.bit_xor(rem, rhs);
172 let sign_differs = b.icmp(CmpOp::Lt, rem_xor_p, zero);
173 let needs_adjust = b.and(rem_ne_zero, sign_differs);
174 let adjusted = b.iadd(rem, rhs);
175 Some(b.select(needs_adjust, adjusted, rem))
176 }
177 } else {
178 None
179 }
180 }
181 "abs" | "iabs" | "dabs" => {
182 if let Some(arg) = args.first() {
183 let ty = b
184 .func()
185 .value_type(*arg)
186 .unwrap_or(IrType::Int(IntWidth::I32));
187 match &ty {
188 IrType::Int(w) => {
189 let zero = match w {
190 IntWidth::I64 => b.const_i64(0),
191 _ => b.const_i32(0),
192 };
193 let is_pos = b.icmp(CmpOp::Ge, *arg, zero);
194 let neg = b.ineg(*arg);
195 Some(b.select(is_pos, *arg, neg))
196 }
197 IrType::Float(_) => Some(b.fabs(*arg)),
198 _ => None,
199 }
200 } else {
201 None
202 }
203 }
204 "int" | "idint" | "ifix" => {
205 if let Some(arg) = args.first() {
206 let ty = b
207 .func()
208 .value_type(*arg)
209 .unwrap_or(IrType::Int(IntWidth::I32));
210 let requested_width = args
211 .get(1)
212 .and_then(|kind| extract_const_int_from_value(b, *kind))
213 .and_then(int_width_from_kind_value)
214 .unwrap_or(IntWidth::I32);
215 if ty.is_float() {
216 Some(b.float_to_int(*arg, requested_width))
217 } else {
218 Some(coerce_to_type(b, *arg, &IrType::Int(requested_width)))
219 }
220 } else {
221 None
222 }
223 }
224 "nint" | "idnint" => {
225 // NINT: round to nearest integer (not truncate).
226 // Round via libm round(), then convert to int.
227 if let Some(arg) = args.first() {
228 let ty = b
229 .func()
230 .value_type(*arg)
231 .unwrap_or(IrType::Float(FloatWidth::F64));
232 let requested_width = args
233 .get(1)
234 .and_then(|kind| extract_const_int_from_value(b, *kind))
235 .and_then(int_width_from_kind_value)
236 .unwrap_or(IntWidth::I32);
237 if ty.is_float() {
238 let func = if matches!(ty, IrType::Float(FloatWidth::F32)) {
239 "roundf"
240 } else {
241 "round"
242 };
243 let rounded = b.call(FuncRef::External(func.into()), vec![*arg], ty.clone());
244 Some(b.float_to_int(rounded, requested_width))
245 } else {
246 Some(coerce_to_type(b, *arg, &IrType::Int(requested_width)))
247 }
248 } else {
249 None
250 }
251 }
252 "anint" | "dnint" => {
253 // ANINT: round to nearest whole number, return as real.
254 if let Some(arg) = args.first() {
255 let ty = b
256 .func()
257 .value_type(*arg)
258 .unwrap_or(IrType::Float(FloatWidth::F64));
259 let func = if matches!(ty, IrType::Float(FloatWidth::F32)) {
260 "roundf"
261 } else {
262 "round"
263 };
264 Some(b.call(FuncRef::External(func.into()), vec![*arg], ty))
265 } else {
266 None
267 }
268 }
269 "real" | "float" | "sngl" => {
270 if let Some(arg) = args.first() {
271 let ty = b
272 .func()
273 .value_type(*arg)
274 .unwrap_or(IrType::Int(IntWidth::I32));
275 let requested_fw = args
276 .get(1)
277 .and_then(|kind| extract_const_int_from_value(b, *kind))
278 .map(|kind| {
279 if kind == 8 {
280 FloatWidth::F64
281 } else {
282 FloatWidth::F32
283 }
284 });
285 let default_fw = if matches!(ty, IrType::Float(FloatWidth::F64))
286 || (is_complex_ty(&ty) && complex_float_width(&ty) == FloatWidth::F64)
287 {
288 FloatWidth::F64
289 } else {
290 FloatWidth::F32
291 };
292 let target_ty = IrType::Float(requested_fw.unwrap_or(default_fw));
293 if ty.is_int() || ty.is_float() {
294 Some(coerce_to_type(b, *arg, &target_ty))
295 } else if is_complex_ty(&ty) {
296 // real(z) extracts the real component of a complex number.
297 // Complex values live as ptr<[f32/f64 x 2]>; load element 0.
298 let fw = complex_float_width(&ty);
299 let zero = b.const_i64(0);
300 let re_ptr = b.gep(*arg, vec![zero], IrType::Int(IntWidth::I8));
301 let real_part = b.load_typed(re_ptr, IrType::Float(fw));
302 Some(coerce_to_type(b, real_part, &target_ty))
303 } else {
304 Some(*arg)
305 }
306 } else {
307 None
308 }
309 }
310 "logical" => {
311 if let Some(arg) = args.first() {
312 let requested_ty = args
313 .get(1)
314 .and_then(|kind| extract_const_int_from_value(b, *kind))
315 .and_then(|kind| match kind {
316 1 => Some(IrType::Int(IntWidth::I8)),
317 2 => Some(IrType::Int(IntWidth::I16)),
318 4 => Some(IrType::Bool),
319 8 => Some(IrType::Int(IntWidth::I64)),
320 _ => None,
321 })
322 .unwrap_or(IrType::Bool);
323 let ty = b.func().value_type(*arg).unwrap_or(IrType::Bool);
324 match ty {
325 IrType::Bool | IrType::Int(_) => Some(coerce_to_type(b, *arg, &requested_ty)),
326 _ => None,
327 }
328 } else {
329 None
330 }
331 }
332 "aimag" | "dimag" => {
333 // aimag(z) extracts the imaginary component of a complex number.
334 // Complex values live as ptr<[f32/f64 x 2]>; load element 1 at
335 // byte offset 4 (f32) or 8 (f64).
336 if let Some(arg) = args.first() {
337 let ty = b
338 .func()
339 .value_type(*arg)
340 .unwrap_or(IrType::Int(IntWidth::I32));
341 if is_complex_ty(&ty) {
342 let fw = complex_float_width(&ty);
343 let offset = b.const_i64(if fw == FloatWidth::F64 { 8 } else { 4 });
344 let im_ptr = b.gep(*arg, vec![offset], IrType::Int(IntWidth::I8));
345 Some(b.load_typed(im_ptr, IrType::Float(fw)))
346 } else {
347 None
348 }
349 } else {
350 None
351 }
352 }
353 "dble" | "dfloat" => {
354 if let Some(arg) = args.first() {
355 let ty = b
356 .func()
357 .value_type(*arg)
358 .unwrap_or(IrType::Int(IntWidth::I32));
359 if ty.is_int() {
360 Some(b.int_to_float(*arg, FloatWidth::F64))
361 } else if matches!(ty, IrType::Float(FloatWidth::F32)) {
362 Some(b.float_extend(*arg, FloatWidth::F64))
363 } else {
364 Some(*arg)
365 }
366 } else {
367 None
368 }
369 }
370 "max" | "max0" | "amax1" | "dmax1" => {
371 if args.len() >= 2 {
372 let mut ty = b
373 .func()
374 .value_type(args[0])
375 .unwrap_or(IrType::Int(IntWidth::I32));
376 if ty.is_float() {
377 if args.iter().any(|arg| {
378 matches!(
379 b.func().value_type(*arg),
380 Some(IrType::Float(FloatWidth::F64))
381 )
382 }) {
383 ty = IrType::Float(FloatWidth::F64);
384 }
385 } else if ty.is_int() {
386 let width = args
387 .iter()
388 .filter_map(|arg| b.func().value_type(*arg).and_then(|ty| ty.int_width()))
389 .max_by_key(|width| width.bits())
390 .unwrap_or(IntWidth::I32);
391 ty = IrType::Int(width);
392 }
393 let coerced: Vec<ValueId> = args
394 .iter()
395 .map(|arg| coerce_to_type(b, *arg, &ty))
396 .collect();
397 let cmp = if ty.is_float() {
398 b.fcmp(CmpOp::Ge, coerced[0], coerced[1])
399 } else {
400 b.icmp(CmpOp::Ge, coerced[0], coerced[1])
401 };
402 let mut result = b.select(cmp, coerced[0], coerced[1]);
403 // Variadic: max(a, b, c, ...) chains.
404 for arg in &coerced[2..] {
405 let cmp = if ty.is_float() {
406 b.fcmp(CmpOp::Ge, result, *arg)
407 } else {
408 b.icmp(CmpOp::Ge, result, *arg)
409 };
410 result = b.select(cmp, result, *arg);
411 }
412 Some(result)
413 } else {
414 None
415 }
416 }
417 "min" | "min0" | "amin1" | "dmin1" => {
418 if args.len() >= 2 {
419 let mut ty = b
420 .func()
421 .value_type(args[0])
422 .unwrap_or(IrType::Int(IntWidth::I32));
423 if ty.is_float() {
424 if args.iter().any(|arg| {
425 matches!(
426 b.func().value_type(*arg),
427 Some(IrType::Float(FloatWidth::F64))
428 )
429 }) {
430 ty = IrType::Float(FloatWidth::F64);
431 }
432 } else if ty.is_int() {
433 let width = args
434 .iter()
435 .filter_map(|arg| b.func().value_type(*arg).and_then(|ty| ty.int_width()))
436 .max_by_key(|width| width.bits())
437 .unwrap_or(IntWidth::I32);
438 ty = IrType::Int(width);
439 }
440 let coerced: Vec<ValueId> = args
441 .iter()
442 .map(|arg| coerce_to_type(b, *arg, &ty))
443 .collect();
444 let cmp = if ty.is_float() {
445 b.fcmp(CmpOp::Le, coerced[0], coerced[1])
446 } else {
447 b.icmp(CmpOp::Le, coerced[0], coerced[1])
448 };
449 let mut result = b.select(cmp, coerced[0], coerced[1]);
450 for arg in &coerced[2..] {
451 let cmp = if ty.is_float() {
452 b.fcmp(CmpOp::Le, result, *arg)
453 } else {
454 b.icmp(CmpOp::Le, result, *arg)
455 };
456 result = b.select(cmp, result, *arg);
457 }
458 Some(result)
459 } else {
460 None
461 }
462 }
463 "sign" | "dsign" | "isign" => {
464 // sign(a, b) = abs(a) * sign_of(b) = b >= 0 ? abs(a) : -abs(a)
465 if args.len() >= 2 {
466 let ty = b
467 .func()
468 .value_type(args[0])
469 .unwrap_or(IrType::Int(IntWidth::I32));
470 let abs_a = if ty.is_float() {
471 b.fabs(args[0])
472 } else {
473 let zero = match &ty {
474 IrType::Int(IntWidth::I64) => b.const_i64(0),
475 _ => b.const_i32(0),
476 };
477 let is_pos = b.icmp(CmpOp::Ge, args[0], zero);
478 let neg = b.ineg(args[0]);
479 b.select(is_pos, args[0], neg)
480 };
481 let neg_abs = if ty.is_float() {
482 b.fneg(abs_a)
483 } else {
484 b.ineg(abs_a)
485 };
486 let zero = match &ty {
487 IrType::Float(FloatWidth::F32) => b.const_f32(0.0),
488 IrType::Float(_) => b.const_f64(0.0),
489 IrType::Int(IntWidth::I64) => b.const_i64(0),
490 _ => b.const_i32(0),
491 };
492 let b_pos = if ty.is_float() {
493 b.fcmp(CmpOp::Ge, args[1], zero)
494 } else {
495 b.icmp(CmpOp::Ge, args[1], zero)
496 };
497 Some(b.select(b_pos, abs_a, neg_abs))
498 } else {
499 None
500 }
501 }
502 "sqrt" | "dsqrt" => args.first().map(|a| b.fsqrt(*a)),
503 // ---- Bit manipulation (inline) ----
504 // Mixed-kind bit ops (e.g. iand(c_long, c_int)) must unify
505 // widths to the wider operand before the IR-level bit_and,
506 // or the verifier rejects "operand width mismatch". F2018
507 // §16.9.104 doesn't require same kinds; gfortran silently
508 // promotes. Audit31 Finding 14.
509 "iand" => {
510 if args.len() >= 2 {
511 let (l, r) = unify_int_widths(b, args[0], args[1]);
512 Some(b.bit_and(l, r))
513 } else {
514 None
515 }
516 }
517 "ior" => {
518 if args.len() >= 2 {
519 let (l, r) = unify_int_widths(b, args[0], args[1]);
520 Some(b.bit_or(l, r))
521 } else {
522 None
523 }
524 }
525 "ieor" => {
526 if args.len() >= 2 {
527 let (l, r) = unify_int_widths(b, args[0], args[1]);
528 Some(b.bit_xor(l, r))
529 } else {
530 None
531 }
532 }
533 "not" => args.first().map(|a| b.bit_not(*a)),
534 "leadz" => args.first().map(|a| b.clz(*a)),
535 "trailz" => args.first().map(|a| b.ctz(*a)),
536 "popcount" | "popcnt" => {
537 // Use __builtin_popcountll via runtime call since ARM64 NEON popcount
538 // requires a complex instruction sequence.
539 args.first().map(|a| {
540 let widened = b.int_extend(*a, IntWidth::I64, false);
541 b.call(
542 FuncRef::External("afs_popcount".into()),
543 vec![widened],
544 IrType::Int(IntWidth::I32),
545 )
546 })
547 }
548 "ishft" => {
549 // F2018 §16.9.95: ISHFT does a *logical* shift on the bit
550 // representation of the integer. For int8/int16 values
551 // already sign-extended into the 32-bit AArch64 register
552 // (e.g. -32767_int16 = 0x8001 lives as 0xFFFF8001 in
553 // w-reg), `lshr` would shift the upper sign-fill bits in
554 // alongside, producing 0x00FFFF80 instead of 0x0080.
555 // Mask args[0] to the kind's width before the shift so
556 // the logical-right shift sees the unsigned bit pattern.
557 if args.len() >= 2 {
558 let shift_cmp_width = int_width_of_value(b, args[1]).unwrap_or(IntWidth::I32);
559 let zero = int_const_for_width(b, shift_cmp_width, 0);
560 let is_left = b.icmp(CmpOp::Ge, args[1], zero);
561 let neg_shift = b.ineg(args[1]);
562 let value_width = int_width_of_value(b, args[0]).unwrap_or(IntWidth::I32);
563 let shift = coerce_int_like_to_width(b, args[1], value_width);
564 let neg_shift = coerce_int_like_to_width(b, neg_shift, value_width);
565 let masked_value = match value_width {
566 IntWidth::I8 => {
567 let mask = int_const_for_width(b, value_width, 0xFF);
568 b.bit_and(args[0], mask)
569 }
570 IntWidth::I16 => {
571 let mask = int_const_for_width(b, value_width, 0xFFFF);
572 b.bit_and(args[0], mask)
573 }
574 _ => args[0],
575 };
576 let left = b.shl(args[0], shift);
577 let right = b.lshr(masked_value, neg_shift);
578 Some(b.select(is_left, left, right))
579 } else {
580 None
581 }
582 }
583 "shiftl" => {
584 // F2008 §13.7.150: logical left shift, shift>=0.
585 if args.len() >= 2 {
586 let value_width = int_width_of_value(b, args[0]).unwrap_or(IntWidth::I32);
587 let shift = coerce_int_like_to_width(b, args[1], value_width);
588 Some(b.shl(args[0], shift))
589 } else {
590 None
591 }
592 }
593 "shiftr" => {
594 // F2008 §13.7.151: logical right shift (zero fill).
595 if args.len() >= 2 {
596 let value_width = int_width_of_value(b, args[0]).unwrap_or(IntWidth::I32);
597 let shift = coerce_int_like_to_width(b, args[1], value_width);
598 Some(b.lshr(args[0], shift))
599 } else {
600 None
601 }
602 }
603 "shifta" => {
604 // F2008 §13.7.149: arithmetic right shift (sign-extending).
605 // No native ashr in the IR yet — synthesize as
606 // shifta(x, n) = lshr(x, n) | (sign_mask << (width - n))
607 // where sign_mask is all-ones if MSB(x) is set, else 0.
608 if args.len() >= 2 {
609 let value_width = int_width_of_value(b, args[0]).unwrap_or(IntWidth::I32);
610 let shift = coerce_int_like_to_width(b, args[1], value_width);
611 let logical = b.lshr(args[0], shift);
612 let bits = int_const_for_width(b, value_width, value_width.bits() as i64);
613 let pos_top = b.isub(bits, shift);
614 // Pre-fill: -1 if top bit of x is 1, else 0.
615 let one = int_const_for_width(b, value_width, 1);
616 let top_bit_pos = int_const_for_width(b, value_width, (value_width.bits() - 1) as i64);
617 let top_bit = b.lshr(args[0], top_bit_pos);
618 let sign = b.bit_and(top_bit, one);
619 let neg_one = int_const_for_width(b, value_width, -1);
620 let zero = int_const_for_width(b, value_width, 0);
621 let is_neg = b.icmp(CmpOp::Ne, sign, zero);
622 let mask_full = b.select(is_neg, neg_one, zero);
623 let high_mask = b.shl(mask_full, pos_top);
624 Some(b.bit_or(logical, high_mask))
625 } else {
626 None
627 }
628 }
629 "btest" => {
630 // btest(a, pos) = (a >> pos) & 1 /= 0
631 if args.len() >= 2 {
632 let value_width = int_width_of_value(b, args[0]).unwrap_or(IntWidth::I32);
633 let pos = coerce_int_like_to_width(b, args[1], value_width);
634 let shifted = b.lshr(args[0], pos);
635 let one = int_const_for_width(b, value_width, 1);
636 let masked = b.bit_and(shifted, one);
637 let zero = int_const_for_width(b, value_width, 0);
638 Some(b.icmp(CmpOp::Ne, masked, zero))
639 } else {
640 None
641 }
642 }
643 "ibset" => {
644 // ibset(a, pos) = a | (1 << pos)
645 if args.len() >= 2 {
646 let value_width = int_width_of_value(b, args[0]).unwrap_or(IntWidth::I32);
647 let one = int_const_for_width(b, value_width, 1);
648 let pos = coerce_int_like_to_width(b, args[1], value_width);
649 let mask = b.shl(one, pos);
650 Some(b.bit_or(args[0], mask))
651 } else {
652 None
653 }
654 }
655 "ibclr" => {
656 // ibclr(a, pos) = a & ~(1 << pos)
657 if args.len() >= 2 {
658 let value_width = int_width_of_value(b, args[0]).unwrap_or(IntWidth::I32);
659 let one = int_const_for_width(b, value_width, 1);
660 let pos = coerce_int_like_to_width(b, args[1], value_width);
661 let mask = b.shl(one, pos);
662 let inv = b.bit_not(mask);
663 Some(b.bit_and(args[0], inv))
664 } else {
665 None
666 }
667 }
668 "ibits" => {
669 // ibits(i, pos, len) = (i >> pos) & ((1 << len) - 1)
670 if args.len() >= 3 {
671 let value_width = int_width_of_value(b, args[0]).unwrap_or(IntWidth::I32);
672 let pos = coerce_int_like_to_width(b, args[1], value_width);
673 let len = coerce_int_like_to_width(b, args[2], value_width);
674 let shifted = b.lshr(args[0], pos);
675 let one = int_const_for_width(b, value_width, 1);
676 let mask_hi = b.shl(one, len);
677 let one2 = int_const_for_width(b, value_width, 1);
678 let mask = b.isub(mask_hi, one2);
679 Some(b.bit_and(shifted, mask))
680 } else {
681 None
682 }
683 }
684 // F2018 §16.9.21-24 — unsigned bitwise comparisons. Implemented
685 // via the "flip the sign bit, then signed compare" trick so the
686 // existing signed icmp ops produce unsigned ordering.
687 "bge" | "bgt" | "ble" | "blt" => {
688 if args.len() >= 2 {
689 let (l, r) = unify_int_widths(b, args[0], args[1]);
690 let value_width = int_width_of_value(b, l).unwrap_or(IntWidth::I32);
691 let sign_bit_pos =
692 int_const_for_width(b, value_width, (value_width.bits() - 1) as i64);
693 let one = int_const_for_width(b, value_width, 1);
694 let sign_mask = b.shl(one, sign_bit_pos);
695 let l_flipped = b.bit_xor(l, sign_mask);
696 let r_flipped = b.bit_xor(r, sign_mask);
697 let op = match name {
698 "bge" => CmpOp::Ge,
699 "bgt" => CmpOp::Gt,
700 "ble" => CmpOp::Le,
701 "blt" => CmpOp::Lt,
702 _ => unreachable!(),
703 };
704 Some(b.icmp(op, l_flipped, r_flipped))
705 } else {
706 None
707 }
708 }
709 "poppar" => {
710 // F2018 §16.9.179: 1 if popcount(i) is odd, 0 otherwise.
711 args.first().map(|a| {
712 let widened = b.int_extend(*a, IntWidth::I64, false);
713 let cnt = b.call(
714 FuncRef::External("afs_popcount".into()),
715 vec![widened],
716 IrType::Int(IntWidth::I32),
717 );
718 let one = b.const_i32(1);
719 b.bit_and(cnt, one)
720 })
721 }
722 "merge_bits" => {
723 // F2018 §16.9.150: (i AND mask) IOR (j AND NOT mask)
724 if args.len() >= 3 {
725 let (i, j) = unify_int_widths(b, args[0], args[1]);
726 let value_width = int_width_of_value(b, i).unwrap_or(IntWidth::I32);
727 let mask = coerce_int_like_to_width(b, args[2], value_width);
728 let lhs = b.bit_and(i, mask);
729 let inv_mask = b.bit_not(mask);
730 let rhs = b.bit_and(j, inv_mask);
731 Some(b.bit_or(lhs, rhs))
732 } else {
733 None
734 }
735 }
736 "maskl" => {
737 // F2018 §16.9.139: i leftmost bits set; rest cleared.
738 // maskl(i) = (-1) << (bits - i) if i > 0, else 0.
739 args.first().map(|a| {
740 let value_width = int_width_of_value(b, *a).unwrap_or(IntWidth::I32);
741 let bits = int_const_for_width(b, value_width, value_width.bits() as i64);
742 let i_in_w = coerce_int_like_to_width(b, *a, value_width);
743 let shift = b.isub(bits, i_in_w);
744 let neg_one = int_const_for_width(b, value_width, -1);
745 let zero = int_const_for_width(b, value_width, 0);
746 let shifted = b.shl(neg_one, shift);
747 let is_zero = b.icmp(CmpOp::Le, i_in_w, zero);
748 b.select(is_zero, zero, shifted)
749 })
750 }
751 "maskr" => {
752 // F2018 §16.9.140: i rightmost bits set; rest cleared.
753 // maskr(i) = (1 << i) - 1 for 0 < i < bits; 0 for i==0; -1 for i>=bits.
754 args.first().map(|a| {
755 let value_width = int_width_of_value(b, *a).unwrap_or(IntWidth::I32);
756 let one = int_const_for_width(b, value_width, 1);
757 let i_in_w = coerce_int_like_to_width(b, *a, value_width);
758 let shifted = b.shl(one, i_in_w);
759 let one_again = int_const_for_width(b, value_width, 1);
760 let computed = b.isub(shifted, one_again);
761 let zero = int_const_for_width(b, value_width, 0);
762 let bits = int_const_for_width(b, value_width, value_width.bits() as i64);
763 let neg_one = int_const_for_width(b, value_width, -1);
764 let too_big = b.icmp(CmpOp::Ge, i_in_w, bits);
765 let is_zero = b.icmp(CmpOp::Le, i_in_w, zero);
766 let big_or_normal = b.select(too_big, neg_one, computed);
767 b.select(is_zero, zero, big_or_normal)
768 })
769 }
770 "dshiftl" => {
771 // F2018 §16.9.59: combine i and j as a 2*bits value with
772 // i on the left, then logical-shift left by `shift` and
773 // return the leftmost `bits` bits.
774 // dshiftl(i, j, s) = (i << s) | (j >> (bits - s))
775 if args.len() >= 3 {
776 let (i, j) = unify_int_widths(b, args[0], args[1]);
777 let value_width = int_width_of_value(b, i).unwrap_or(IntWidth::I32);
778 let shift = coerce_int_like_to_width(b, args[2], value_width);
779 let bits = int_const_for_width(b, value_width, value_width.bits() as i64);
780 let comp = b.isub(bits, shift);
781 let left = b.shl(i, shift);
782 let right = b.lshr(j, comp);
783 Some(b.bit_or(left, right))
784 } else {
785 None
786 }
787 }
788 "dshiftr" => {
789 // F2018 §16.9.60: combine i and j and shift right by `shift`,
790 // returning the rightmost `bits` bits.
791 // dshiftr(i, j, s) = (j >> s) | (i << (bits - s))
792 if args.len() >= 3 {
793 let (i, j) = unify_int_widths(b, args[0], args[1]);
794 let value_width = int_width_of_value(b, i).unwrap_or(IntWidth::I32);
795 let shift = coerce_int_like_to_width(b, args[2], value_width);
796 let bits = int_const_for_width(b, value_width, value_width.bits() as i64);
797 let comp = b.isub(bits, shift);
798 let right = b.lshr(j, shift);
799 let left = b.shl(i, comp);
800 Some(b.bit_or(left, right))
801 } else {
802 None
803 }
804 }
805 // ---- Math intrinsics → libm calls ----
806 // Dispatch to sinf/sin based on argument type for F32/F64 correctness.
807 "sin" | "dsin" | "cos" | "dcos" | "tan" | "dtan" | "asin" | "dasin" | "acos" | "dacos"
808 | "atan" | "datan" | "sinh" | "dsinh" | "cosh" | "dcosh" | "tanh" | "dtanh" | "asinh"
809 | "acosh" | "atanh" | "exp" | "dexp" | "log" | "dlog" | "alog" | "log10" | "dlog10"
810 | "alog10" | "erf" | "derf" | "erfc" | "derfc" | "ceiling" | "floor" => {
811 if let Some(arg) = args.first() {
812 let ty = b
813 .func()
814 .value_type(*arg)
815 .unwrap_or(IrType::Float(FloatWidth::F64));
816 let is_f32 = matches!(ty, IrType::Float(FloatWidth::F32));
817 let base_name = match name {
818 "dsin" | "sin" => "sin",
819 "dcos" | "cos" => "cos",
820 "dtan" | "tan" => "tan",
821 "dasin" | "asin" => "asin",
822 "dacos" | "acos" => "acos",
823 "datan" | "atan" => "atan",
824 "dsinh" | "sinh" => "sinh",
825 "dcosh" | "cosh" => "cosh",
826 "dtanh" | "tanh" => "tanh",
827 "asinh" => "asinh",
828 "acosh" => "acosh",
829 "atanh" => "atanh",
830 "dexp" | "exp" => "exp",
831 "dlog" | "log" | "alog" => "log",
832 "dlog10" | "log10" | "alog10" => "log10",
833 "derf" | "erf" => "erf",
834 "derfc" | "erfc" => "erfc",
835 "ceiling" => "ceil",
836 "floor" => "floor",
837 _ => name,
838 };
839 let func_name = if is_f32 {
840 format!("{}f", base_name)
841 } else {
842 base_name.to_string()
843 };
844 let ret_ty = if is_f32 {
845 IrType::Float(FloatWidth::F32)
846 } else {
847 IrType::Float(FloatWidth::F64)
848 };
849 Some(b.call(FuncRef::External(func_name), vec![*arg], ret_ty))
850 } else {
851 None
852 }
853 }
854 "atan2" | "datan2" => {
855 if args.len() >= 2 {
856 let ty = b
857 .func()
858 .value_type(args[0])
859 .unwrap_or(IrType::Float(FloatWidth::F64));
860 let is_f32 = matches!(ty, IrType::Float(FloatWidth::F32));
861 let func = if is_f32 { "atan2f" } else { "atan2" };
862 let ret_ty = if is_f32 {
863 IrType::Float(FloatWidth::F32)
864 } else {
865 IrType::Float(FloatWidth::F64)
866 };
867 Some(b.call(
868 FuncRef::External(func.into()),
869 vec![args[0], args[1]],
870 ret_ty,
871 ))
872 } else {
873 None
874 }
875 }
876 "gamma" | "dgamma" => args.first().map(|a| {
877 let ty = b
878 .func()
879 .value_type(*a)
880 .unwrap_or(IrType::Float(FloatWidth::F64));
881 let is_f32 = matches!(ty, IrType::Float(FloatWidth::F32));
882 let func = if is_f32 { "tgammaf" } else { "tgamma" };
883 let ret_ty = if is_f32 {
884 IrType::Float(FloatWidth::F32)
885 } else {
886 IrType::Float(FloatWidth::F64)
887 };
888 b.call(FuncRef::External(func.into()), vec![*a], ret_ty)
889 }),
890 "log_gamma" => args.first().map(|a| {
891 let ty = b
892 .func()
893 .value_type(*a)
894 .unwrap_or(IrType::Float(FloatWidth::F64));
895 let is_f32 = matches!(ty, IrType::Float(FloatWidth::F32));
896 let func = if is_f32 { "lgammaf" } else { "lgamma" };
897 let ret_ty = if is_f32 {
898 IrType::Float(FloatWidth::F32)
899 } else {
900 IrType::Float(FloatWidth::F64)
901 };
902 b.call(FuncRef::External(func.into()), vec![*a], ret_ty)
903 }),
904 "bessel_j0" => args.first().map(|a| {
905 b.call(
906 FuncRef::External("j0".into()),
907 vec![*a],
908 IrType::Float(FloatWidth::F64),
909 )
910 }),
911 "bessel_j1" => args.first().map(|a| {
912 b.call(
913 FuncRef::External("j1".into()),
914 vec![*a],
915 IrType::Float(FloatWidth::F64),
916 )
917 }),
918 "bessel_y0" => args.first().map(|a| {
919 b.call(
920 FuncRef::External("y0".into()),
921 vec![*a],
922 IrType::Float(FloatWidth::F64),
923 )
924 }),
925 "bessel_y1" => args.first().map(|a| {
926 b.call(
927 FuncRef::External("y1".into()),
928 vec![*a],
929 IrType::Float(FloatWidth::F64),
930 )
931 }),
932 "hypot" => {
933 if args.len() >= 2 {
934 let ty = b
935 .func()
936 .value_type(args[0])
937 .unwrap_or(IrType::Float(FloatWidth::F64));
938 let is_f32 = matches!(ty, IrType::Float(FloatWidth::F32));
939 let func = if is_f32 { "hypotf" } else { "hypot" };
940 let ret_ty = if is_f32 {
941 IrType::Float(FloatWidth::F32)
942 } else {
943 IrType::Float(FloatWidth::F64)
944 };
945 Some(b.call(
946 FuncRef::External(func.into()),
947 vec![args[0], args[1]],
948 ret_ty,
949 ))
950 } else {
951 None
952 }
953 }
954 "ishftc" => {
955 // ishftc(a, shift, size): circular shift of the rightmost `size` bits.
956 // F2018 §16.9.108. The previous implementation used
957 // mask = (1 << size) - 1
958 // which is undefined when size equals the operand bit width
959 // (AArch64 LSL masks the shift amount mod 64, so 1 << 64 wraps
960 // back to 1, leaving mask = 0 and erasing the result).
961 // stdlib_random's xoshiro256ss called ishftc(x, 7) with the
962 // default size = 64 and got 0 every time.
963 if args.len() >= 2 {
964 let value_width = int_width_of_value(b, args[0]).unwrap_or(IntWidth::I32);
965 let bit_width = match value_width {
966 IntWidth::I64 => 64,
967 IntWidth::I16 => 16,
968 IntWidth::I8 => 8,
969 _ => 32,
970 };
971 let shift = coerce_int_like_to_width(b, args[1], value_width);
972 let bw_minus_1 = int_const_for_width(b, value_width, bit_width - 1);
973 if args.len() >= 3 {
974 // Explicit size: rotate within the rightmost `size` bits,
975 // leaving the upper (bit_width - size) bits untouched.
976 let size = coerce_int_like_to_width(b, args[2], value_width);
977 // Build mask = ((1 << (size-1)) - 1) << 1 | 1.
978 // Valid for size in [1, bit_width]; at size == bit_width
979 // this yields all ones without ever shifting by bit_width.
980 let one_a = int_const_for_width(b, value_width, 1);
981 let one_b = int_const_for_width(b, value_width, 1);
982 let one_c = int_const_for_width(b, value_width, 1);
983 let one_d = int_const_for_width(b, value_width, 1);
984 let size_minus_1 = b.isub(size, one_a);
985 let half = b.shl(one_b, size_minus_1);
986 let half_minus_1 = b.isub(half, one_c);
987 let half_minus_1_shifted = b.shl(half_minus_1, one_d);
988 let one_e = int_const_for_width(b, value_width, 1);
989 let mask = b.bit_or(half_minus_1_shifted, one_e);
990 let not_mask_pre = int_const_for_width(b, value_width, -1);
991 let not_mask = b.bit_xor(not_mask_pre, mask);
992 // Rotate the low bits, preserve the high bits.
993 let low = b.bit_and(args[0], mask);
994 let high = b.bit_and(args[0], not_mask);
995 // shift_safe = shift mod size (avoid UB when shift == size).
996 // For the common stdlib usage shift < size, so the modulo is
997 // a no-op; we still emit isub-based fallback in case.
998 let left_pre = b.shl(low, shift);
999 let left = b.bit_and(left_pre, mask);
1000 let diff = b.isub(size, shift);
1001 let right = b.lshr(low, diff);
1002 let rotated = b.bit_or(left, right);
1003 let rotated_low = b.bit_and(rotated, mask);
1004 Some(b.bit_or(rotated_low, high))
1005 } else {
1006 // Default size: full-width rotate. Use the standard
1007 // (a << (s & (BITS-1))) | (a >> ((-s) & (BITS-1)))
1008 // formula which is well-defined for shift = 0 because
1009 // both lanes shift by 0 and OR back the same value.
1010 let s_masked = b.bit_and(shift, bw_minus_1);
1011 let bw_const = int_const_for_width(b, value_width, bit_width);
1012 let neg_s = b.isub(bw_const, s_masked);
1013 let bw_minus_1_b = int_const_for_width(b, value_width, bit_width - 1);
1014 let neg_s_masked = b.bit_and(neg_s, bw_minus_1_b);
1015 let left = b.shl(args[0], s_masked);
1016 let right = b.lshr(args[0], neg_s_masked);
1017 Some(b.bit_or(left, right))
1018 }
1019 } else {
1020 None
1021 }
1022 }
1023
1024 // ---- Numeric inquiry intrinsics (compile-time constants) ----
1025 // These depend on the argument's type, which we determine from the first arg.
1026 "huge" => {
1027 if let Some(arg) = args.first() {
1028 let ty = b
1029 .func()
1030 .value_type(*arg)
1031 .unwrap_or(IrType::Int(IntWidth::I32));
1032 match &ty {
1033 IrType::Int(IntWidth::I8) => Some(b.const_i32(i8::MAX as i64 as i32)),
1034 IrType::Int(IntWidth::I16) => Some(b.const_i32(i16::MAX as i64 as i32)),
1035 IrType::Int(IntWidth::I32) => Some(b.const_i32(i32::MAX)),
1036 IrType::Int(IntWidth::I64) => Some(b.const_i64(i64::MAX)),
1037 IrType::Float(FloatWidth::F32) => Some(b.const_f32(f32::MAX)),
1038 IrType::Float(FloatWidth::F64) => Some(b.const_f64(f64::MAX)),
1039 _ => None,
1040 }
1041 } else {
1042 None
1043 }
1044 }
1045 "tiny" => {
1046 if let Some(arg) = args.first() {
1047 let ty = b
1048 .func()
1049 .value_type(*arg)
1050 .unwrap_or(IrType::Float(FloatWidth::F32));
1051 match &ty {
1052 IrType::Float(FloatWidth::F32) => Some(b.const_f32(f32::MIN_POSITIVE)),
1053 IrType::Float(FloatWidth::F64) => Some(b.const_f64(f64::MIN_POSITIVE)),
1054 _ => None,
1055 }
1056 } else {
1057 None
1058 }
1059 }
1060 "epsilon" => {
1061 if let Some(arg) = args.first() {
1062 let ty = b
1063 .func()
1064 .value_type(*arg)
1065 .unwrap_or(IrType::Float(FloatWidth::F32));
1066 match &ty {
1067 IrType::Float(FloatWidth::F32) => Some(b.const_f32(f32::EPSILON)),
1068 IrType::Float(FloatWidth::F64) => Some(b.const_f64(f64::EPSILON)),
1069 _ => None,
1070 }
1071 } else {
1072 None
1073 }
1074 }
1075 "precision" => {
1076 if let Some(arg) = args.first() {
1077 let ty = b
1078 .func()
1079 .value_type(*arg)
1080 .unwrap_or(IrType::Float(FloatWidth::F32));
1081 let prec = match &ty {
1082 IrType::Float(FloatWidth::F32) => 6, // ~7.2 decimal digits → 6
1083 IrType::Float(FloatWidth::F64) => 15, // ~15.9 decimal digits → 15
1084 _ => 0,
1085 };
1086 Some(b.const_i32(prec))
1087 } else {
1088 None
1089 }
1090 }
1091 "range" => {
1092 if let Some(arg) = args.first() {
1093 let ty = b
1094 .func()
1095 .value_type(*arg)
1096 .unwrap_or(IrType::Int(IntWidth::I32));
1097 let range = match &ty {
1098 IrType::Int(IntWidth::I8) => 2,
1099 IrType::Int(IntWidth::I16) => 4,
1100 IrType::Int(IntWidth::I32) => 9,
1101 IrType::Int(IntWidth::I64) => 18,
1102 IrType::Int(IntWidth::I128) => 38,
1103 IrType::Float(FloatWidth::F32) => 37,
1104 IrType::Float(FloatWidth::F64) => 307,
1105 _ => 0,
1106 };
1107 Some(b.const_i32(range))
1108 } else {
1109 None
1110 }
1111 }
1112 "digits" => {
1113 if let Some(arg) = args.first() {
1114 let ty = b
1115 .func()
1116 .value_type(*arg)
1117 .unwrap_or(IrType::Int(IntWidth::I32));
1118 let digits = match &ty {
1119 IrType::Int(IntWidth::I8) => 7,
1120 IrType::Int(IntWidth::I16) => 15,
1121 IrType::Int(IntWidth::I32) => 31,
1122 IrType::Int(IntWidth::I64) => 63,
1123 IrType::Int(IntWidth::I128) => 127,
1124 IrType::Float(FloatWidth::F32) => 24, // significand bits
1125 IrType::Float(FloatWidth::F64) => 53,
1126 _ => 0,
1127 };
1128 Some(b.const_i32(digits))
1129 } else {
1130 None
1131 }
1132 }
1133 "radix" => {
1134 // Always 2 for binary machines.
1135 Some(b.const_i32(2))
1136 }
1137 "bit_size" => {
1138 if let Some(arg) = args.first() {
1139 let ty = b
1140 .func()
1141 .value_type(*arg)
1142 .unwrap_or(IrType::Int(IntWidth::I32));
1143 let bits = match &ty {
1144 IrType::Int(IntWidth::I8) => 8,
1145 IrType::Int(IntWidth::I16) => 16,
1146 IrType::Int(IntWidth::I32) => 32,
1147 IrType::Int(IntWidth::I64) => 64,
1148 IrType::Int(IntWidth::I128) => 128,
1149 _ => 0,
1150 };
1151 Some(b.const_i32(bits))
1152 } else {
1153 None
1154 }
1155 }
1156 // F2018 §16.9.196: STORAGE_SIZE(A [, KIND]) returns the size in
1157 // bits a value of the same type as A occupies. For non-polymorphic
1158 // arguments this is determined entirely by the argument's IR type
1159 // and can be folded at compile time (8 * sizeof). The optional
1160 // KIND argument names the kind of the result; we always return I32
1161 // and let downstream coerce.
1162 "storage_size" => {
1163 if let Some(arg) = args.first() {
1164 let ty = b
1165 .func()
1166 .value_type(*arg)
1167 .unwrap_or(IrType::Int(IntWidth::I32));
1168 let bits = storage_size_bits_for_ir_type(&ty);
1169 Some(b.const_i32(bits))
1170 } else {
1171 None
1172 }
1173 }
1174 "kind" => {
1175 if let Some(arg) = args.first() {
1176 let ty = b
1177 .func()
1178 .value_type(*arg)
1179 .unwrap_or(IrType::Int(IntWidth::I32));
1180 let kind = match &ty {
1181 IrType::Int(IntWidth::I8) => 1,
1182 IrType::Int(IntWidth::I16) => 2,
1183 IrType::Int(IntWidth::I32) => 4,
1184 IrType::Int(IntWidth::I64) => 8,
1185 IrType::Int(IntWidth::I128) => 16,
1186 IrType::Float(FloatWidth::F32) => 4,
1187 IrType::Float(FloatWidth::F64) => 8,
1188 IrType::Bool => 4,
1189 _ => 4,
1190 };
1191 Some(b.const_i32(kind))
1192 } else {
1193 None
1194 }
1195 }
1196 // ---- System inquiry functions ----
1197 "command_argument_count" => Some(b.call(
1198 FuncRef::External("afs_command_argument_count".into()),
1199 vec![],
1200 IrType::Int(IntWidth::I32),
1201 )),
1202 "is_iostat_end" => args.first().map(|status| {
1203 let zero = match b
1204 .func()
1205 .value_type(*status)
1206 .unwrap_or(IrType::Int(IntWidth::I32))
1207 {
1208 IrType::Int(IntWidth::I64) => b.const_i64(-1),
1209 _ => b.const_i32(-1),
1210 };
1211 b.icmp(CmpOp::Eq, *status, zero)
1212 }),
1213 "is_iostat_eor" => args.first().map(|status| {
1214 let zero = match b
1215 .func()
1216 .value_type(*status)
1217 .unwrap_or(IrType::Int(IntWidth::I32))
1218 {
1219 IrType::Int(IntWidth::I64) => b.const_i64(-2),
1220 _ => b.const_i32(-2),
1221 };
1222 b.icmp(CmpOp::Eq, *status, zero)
1223 }),
1224
1225 // ---- iso_c_binding functions ----
1226 "c_loc" => None,
1227 "c_sizeof" => {
1228 // c_sizeof(x) — return byte size of x's C representation.
1229 if let Some(arg) = args.first() {
1230 let ty = b
1231 .func()
1232 .value_type(*arg)
1233 .unwrap_or(IrType::Int(IntWidth::I32));
1234 let size: i64 = match &ty {
1235 IrType::Int(IntWidth::I8) | IrType::Bool => 1,
1236 IrType::Int(IntWidth::I16) => 2,
1237 IrType::Int(IntWidth::I32) | IrType::Float(FloatWidth::F32) => 4,
1238 IrType::Int(IntWidth::I64) | IrType::Float(FloatWidth::F64) => 8,
1239 IrType::Int(IntWidth::I128) => 16,
1240 IrType::Ptr(_) => 8, // pointers are 8 bytes on ARM64
1241 // Arrays use element size * count, but we don't have shape info here.
1242 // For now, return element size. Proper impl needs descriptor access.
1243 IrType::Array(elem, count) => {
1244 let elem_size = ir_scalar_byte_size(elem.as_ref());
1245 elem_size * (*count as i64)
1246 }
1247 _ => 8, // default to pointer size for unknown types
1248 };
1249 Some(b.const_i64(size))
1250 } else {
1251 None
1252 }
1253 }
1254 "c_associated" => {
1255 // c_associated(p) → p /= null
1256 // c_associated(p, q) → p == q
1257 if args.len() >= 2 {
1258 Some(b.icmp(CmpOp::Eq, args[0], args[1]))
1259 } else if let Some(p) = args.first() {
1260 // Use type-matched zero to avoid register width mismatch.
1261 let ty = b
1262 .func()
1263 .value_type(*p)
1264 .unwrap_or(IrType::Int(IntWidth::I64));
1265 let null = match &ty {
1266 IrType::Int(IntWidth::I32) => b.const_i32(0),
1267 _ => b.const_i64(0),
1268 };
1269 Some(b.icmp(CmpOp::Ne, *p, null))
1270 } else {
1271 None
1272 }
1273 }
1274
1275 // ---- Kind selection intrinsics ----
1276 "selected_int_kind" => {
1277 // selected_int_kind(r): smallest integer kind whose range covers [-10^r, 10^r].
1278 if let Some(arg) = args.first() {
1279 if let Some(r) = extract_const_int_from_value(b, *arg) {
1280 let kind: i32 = if r <= 2 {
1281 1
1282 }
1283 // i8: ±127
1284 else if r <= 4 {
1285 2
1286 }
1287 // i16: ±32767
1288 else if r <= 9 {
1289 4
1290 }
1291 // i32: ±2.1e9
1292 else if r <= 18 {
1293 8
1294 }
1295 // i64: ±9.2e18
1296 else if r <= 38 {
1297 16
1298 }
1299 // i128: ±1.7e38
1300 else {
1301 -1
1302 }; // no kind available
1303 Some(b.const_i32(kind))
1304 } else {
1305 Some(b.const_i32(4)) // non-constant: default to 4
1306 }
1307 } else {
1308 None
1309 }
1310 }
1311 "selected_real_kind" => {
1312 // selected_real_kind(p[, r]): smallest real kind with ≥p decimal digits.
1313 if let Some(arg) = args.first() {
1314 if let Some(p) = extract_const_int_from_value(b, *arg) {
1315 let kind: i32 = if p <= 6 {
1316 4
1317 }
1318 // f32: ~7 digits
1319 else if p <= 15 {
1320 8
1321 }
1322 // f64: ~15 digits
1323 else {
1324 -1
1325 }; // no kind available
1326 Some(b.const_i32(kind))
1327 } else {
1328 Some(b.const_i32(8)) // non-constant: default to 8
1329 }
1330 } else {
1331 None
1332 }
1333 }
1334
1335 // ---- IEEE arithmetic intrinsics ----
1336 "ieee_is_nan" => {
1337 // IEEE_IS_NAN(x) → x != x (NaN is the only value that is not equal to itself)
1338 args.first().map(|arg| b.fcmp(CmpOp::Ne, *arg, *arg))
1339 }
1340 "ieee_is_finite" => {
1341 // IEEE_IS_FINITE(x) → (x - x) == 0.0
1342 // For finite values, x-x is 0.0. For inf, x-x is NaN. For NaN, x-x is NaN.
1343 if let Some(arg) = args.first() {
1344 let diff = b.fsub(*arg, *arg);
1345 let ty = b
1346 .func()
1347 .value_type(*arg)
1348 .unwrap_or(IrType::Float(FloatWidth::F64));
1349 let zero = match &ty {
1350 IrType::Float(FloatWidth::F32) => b.const_f32(0.0),
1351 _ => b.const_f64(0.0),
1352 };
1353 Some(b.fcmp(CmpOp::Eq, diff, zero))
1354 } else {
1355 None
1356 }
1357 }
1358 "ieee_support_datatype"
1359 | "ieee_support_denormal"
1360 | "ieee_support_inf"
1361 | "ieee_support_nan"
1362 | "ieee_support_subnormal" => {
1363 // ARM64 + Apple Silicon supports the full IEEE 754 model.
1364 Some(b.const_bool(true))
1365 }
1366 "maxexponent" => {
1367 // F2018 §16.9.124: returns the maximum exponent in the model
1368 // for the same kind as the argument. For IEEE binary32 = 128,
1369 // binary64 = 1024.
1370 let arg_ty = args
1371 .first()
1372 .and_then(|a| b.func().value_type(*a))
1373 .unwrap_or(IrType::Float(FloatWidth::F32));
1374 let val = match arg_ty {
1375 IrType::Float(FloatWidth::F64) => 1024_i32,
1376 _ => 128_i32,
1377 };
1378 Some(b.const_i32(val))
1379 }
1380 "minexponent" => {
1381 // F2018 §16.9.146: minimum exponent in the model. binary32 = -125,
1382 // binary64 = -1021.
1383 let arg_ty = args
1384 .first()
1385 .and_then(|a| b.func().value_type(*a))
1386 .unwrap_or(IrType::Float(FloatWidth::F32));
1387 let val = match arg_ty {
1388 IrType::Float(FloatWidth::F64) => -1021_i32,
1389 _ => -125_i32,
1390 };
1391 Some(b.const_i32(val))
1392 }
1393 "ieee_value" => {
1394 if args.len() < 2 {
1395 None
1396 } else {
1397 let ty = b
1398 .func()
1399 .value_type(args[0])
1400 .unwrap_or(IrType::Float(FloatWidth::F64));
1401 let class = extract_const_int_from_value(b, args[1]).unwrap_or(0);
1402 let zero = match ty {
1403 IrType::Float(FloatWidth::F32) => b.const_f32(0.0),
1404 _ => b.const_f64(0.0),
1405 };
1406 let one = match ty {
1407 IrType::Float(FloatWidth::F32) => b.const_f32(1.0),
1408 _ => b.const_f64(1.0),
1409 };
1410 match class {
1411 1 | 4 => Some(b.fdiv(zero, zero)),
1412 2 => Some(b.fdiv(one, zero)),
1413 3 => {
1414 let neg_one = match ty {
1415 IrType::Float(FloatWidth::F32) => b.const_f32(-1.0),
1416 _ => b.const_f64(-1.0),
1417 };
1418 Some(b.fdiv(neg_one, zero))
1419 }
1420 5 => Some(zero),
1421 6 => {
1422 let neg_one = match ty {
1423 IrType::Float(FloatWidth::F32) => b.const_f32(-1.0),
1424 _ => b.const_f64(-1.0),
1425 };
1426 Some(b.fmul(neg_one, zero))
1427 }
1428 _ => Some(args[0]),
1429 }
1430 }
1431 }
1432
1433 _ => None,
1434 }
1435 }
1436