Rust · 12920 bytes Raw Blame History
1 //! Pure leaf helpers used throughout lowering.
2 //!
3 //! Type coercion, integer widening, and storage-size queries — small
4 //! self-contained utilities that don't carry any `LowerCtx` state.
5 //!
6 //! Extracted from `lower::core` in sprint 04 step 4. No behavior
7 //! change.
8
9 use crate::ir::builder::FuncBuilder;
10 use crate::ir::inst::{CmpOp, ValueId};
11 use crate::ir::types::{FloatWidth, IntWidth, IrType};
12
13 /// Coerce a scalar value to a target type for initializer storage.
14 ///
15 /// Covers every Fortran scalar coercion that can show up at an
16 /// initializer-store site:
17 /// * Int → Int width change (sign-extend or truncate). Audit
18 /// Min-3: Fortran integers are always signed, so the int_extend
19 /// `signed` flag is hardcoded to `true`.
20 /// * Int ↔ Float (round to nearest for Float→Int).
21 /// * F32 ↔ F64 (extend / truncate).
22 /// * Bool ↔ Int (round-trip via int_extend; Fortran logicals
23 /// occupy a full kind so this is rare but legal).
24 ///
25 /// Anything that doesn't match one of those cases falls into the
26 /// `_ => val` arm and a `debug_assert!` fires — silently passing
27 /// the wrong-typed value would let a future caller wire mismatched
28 /// types into a Store, which the verifier (after MAJOR-4) would
29 /// then catch much later. Better to fail loudly at the source.
30 pub(super) fn coerce_to_type(b: &mut FuncBuilder, val: ValueId, target: &IrType) -> ValueId {
31 let src = match b.func().value_type(val) {
32 Some(t) => t,
33 None => return val,
34 };
35 if src == *target {
36 return val;
37 }
38 match (&src, target) {
39 // Complex values commonly travel as ptr<[f32/f64 x 2]> buffers.
40 // When a by-value complex slot expects the aggregate itself,
41 // materialize it by loading the pointed-to pair.
42 (IrType::Ptr(inner), target)
43 if matches!(inner.as_ref(), IrType::Array(_, 2)) && inner.as_ref() == target =>
44 {
45 b.load_typed(val, target.clone())
46 }
47 // Complex → real coercion (F2018 §10.2.1.3): assigning a
48 // complex value to a real variable extracts its real
49 // component. Common in BLAS code like `rtemp = rtemp +
50 // conjg(z)*z` where the RHS is mathematically real but
51 // typed complex.
52 (IrType::Ptr(inner), IrType::Float(target_fw))
53 if matches!(inner.as_ref(),
54 IrType::Array(elem, 2) if matches!(elem.as_ref(), IrType::Float(_))) =>
55 {
56 let elem_fw = match inner.as_ref() {
57 IrType::Array(elem, _) => match elem.as_ref() {
58 IrType::Float(fw) => *fw,
59 _ => unreachable!(),
60 },
61 _ => unreachable!(),
62 };
63 let zero = b.const_i64(0);
64 let re_ptr = b.gep(val, vec![zero], IrType::Int(IntWidth::I8));
65 let re = b.load_typed(re_ptr, IrType::Float(elem_fw));
66 // Width adjust if target precision differs from element.
67 if elem_fw == *target_fw {
68 re
69 } else if elem_fw == FloatWidth::F32 && *target_fw == FloatWidth::F64 {
70 b.float_extend(re, FloatWidth::F64)
71 } else {
72 b.float_trunc(re, *target_fw)
73 }
74 }
75 // Int → Complex (F2018 §10.1.10.1, F2018 §16.9.43): cmplx(i)
76 // semantics — integer becomes the real part, imaginary part 0.
77 // Common via `zero_value_for_ir_type` for missing optional
78 // complex args returning a scalar zero, or coerce_value_call_arg
79 // when an integer literal flows into a complex VALUE parameter.
80 (IrType::Int(_), IrType::Array(elem, 2)) if matches!(elem.as_ref(), IrType::Float(_)) => {
81 let fw = match elem.as_ref() {
82 IrType::Float(fw) => *fw,
83 _ => unreachable!(),
84 };
85 let re = b.int_to_float(val, fw);
86 let zero = match fw {
87 FloatWidth::F32 => b.const_f32(0.0),
88 FloatWidth::F64 => b.const_f64(0.0),
89 };
90 let buf = b.alloca(IrType::Array(Box::new(IrType::Float(fw)), 2));
91 let zero_off = b.const_i64(0);
92 let lane_bytes =
93 b.const_i64(if fw == FloatWidth::F64 { 8 } else { 4 });
94 let re_ptr = b.gep(buf, vec![zero_off], IrType::Int(IntWidth::I8));
95 let im_ptr = b.gep(buf, vec![lane_bytes], IrType::Int(IntWidth::I8));
96 b.store(re, re_ptr);
97 b.store(zero, im_ptr);
98 b.load_typed(buf, IrType::Array(Box::new(IrType::Float(fw)), 2))
99 }
100 // Real → Complex (F2018 §10.1.10.1): real becomes real part,
101 // imaginary part 0. Width-adjusts when source and target
102 // float widths differ (e.g. real(sp) literal → complex(dp)).
103 (IrType::Float(src_fw), IrType::Array(elem, 2)) if matches!(elem.as_ref(), IrType::Float(_)) => {
104 let target_fw = match elem.as_ref() {
105 IrType::Float(fw) => *fw,
106 _ => unreachable!(),
107 };
108 let re = if *src_fw == target_fw {
109 val
110 } else if *src_fw == FloatWidth::F32 && target_fw == FloatWidth::F64 {
111 b.float_extend(val, FloatWidth::F64)
112 } else {
113 b.float_trunc(val, target_fw)
114 };
115 let zero = match target_fw {
116 FloatWidth::F32 => b.const_f32(0.0),
117 FloatWidth::F64 => b.const_f64(0.0),
118 };
119 let buf = b.alloca(IrType::Array(Box::new(IrType::Float(target_fw)), 2));
120 let zero_off = b.const_i64(0);
121 let lane_bytes =
122 b.const_i64(if target_fw == FloatWidth::F64 { 8 } else { 4 });
123 let re_ptr = b.gep(buf, vec![zero_off], IrType::Int(IntWidth::I8));
124 let im_ptr = b.gep(buf, vec![lane_bytes], IrType::Int(IntWidth::I8));
125 b.store(re, re_ptr);
126 b.store(zero, im_ptr);
127 b.load_typed(buf, IrType::Array(Box::new(IrType::Float(target_fw)), 2))
128 }
129 // Int → Float
130 (IrType::Int(_), IrType::Float(fw)) => b.int_to_float(val, *fw),
131 // Float → Int
132 (IrType::Float(_), IrType::Int(iw)) => b.float_to_int(val, *iw),
133 // F32 ↔ F64
134 (IrType::Float(FloatWidth::F32), IrType::Float(FloatWidth::F64)) => {
135 b.float_extend(val, FloatWidth::F64)
136 }
137 (IrType::Float(FloatWidth::F64), IrType::Float(FloatWidth::F32)) => {
138 b.float_trunc(val, FloatWidth::F32)
139 }
140 // Int width change. Audit Min-3: Fortran integers are signed.
141 (IrType::Int(src_w), IrType::Int(dst_w)) => {
142 if dst_w.bits() > src_w.bits() {
143 b.int_extend(val, *dst_w, true)
144 } else if dst_w.bits() < src_w.bits() {
145 b.int_trunc(val, *dst_w)
146 } else {
147 val
148 }
149 }
150 // Bool ↔ Int via int_extend. Bool is i1 in our model.
151 (IrType::Bool, IrType::Int(iw)) => b.int_extend(val, *iw, false),
152 // Int → Bool: compare against zero to produce a true Bool
153 // rather than truncating to i8 (which the verifier would
154 // then reject on any .and./.or. operand). Common path:
155 // LOGICAL fields in derived types load as i8 and need to
156 // reach Bool before a logical op (audit31 Finding 13).
157 (IrType::Int(_), IrType::Bool) => {
158 let zero = match &src {
159 IrType::Int(IntWidth::I64) => b.const_i64(0),
160 IrType::Int(IntWidth::I16) => b.const_i32(0),
161 IrType::Int(IntWidth::I8) => b.const_i32(0),
162 _ => b.const_i32(0),
163 };
164 // Widen to i32 first if the source is narrower so
165 // icmp gets matching operand widths.
166 let widened = match &src {
167 IrType::Int(IntWidth::I8) | IrType::Int(IntWidth::I16) => {
168 b.int_extend(val, IntWidth::I32, false)
169 }
170 _ => val,
171 };
172 b.icmp(CmpOp::Ne, widened, zero)
173 }
174 // Ptr<Array<T, N>> → Ptr<T>: pointer to array used as pointer to element.
175 // Common for character arrays (Ptr<[i8 x 20]> → Ptr<i8>).
176 (IrType::Ptr(_), IrType::Ptr(_)) => {
177 // Pointers are all the same size on ARM64 — pass through.
178 val
179 }
180 // Int → Ptr: value used in pointer context (e.g., byte as char*).
181 (IrType::Int(_), IrType::Ptr(_)) => b.int_to_ptr(val, IrType::Int(IntWidth::I8)),
182 // Ptr → Int: pointer used in integer context.
183 (IrType::Ptr(_), IrType::Int(IntWidth::I64)) => b.ptr_to_int(val),
184 (IrType::Ptr(_), IrType::Int(iw)) => {
185 let i64_val = b.ptr_to_int(val);
186 b.int_trunc(i64_val, *iw)
187 }
188 // Ptr<derived/byte-aggregate> → Float: nothing to do at the
189 // IR level. This arises when generic dispatch picks a
190 // wrong-typed specific (e.g. a structure constructor for a
191 // type that has a same-named generic interface). Returning
192 // the val unchanged would propagate a struct ptr to a store
193 // that expects a float and trip the IR verifier; emit a
194 // typed zero of the target so the call/store stays well-typed
195 // (the call's runtime semantics are already broken — this
196 // just keeps the verifier from rejecting the surrounding IR).
197 (IrType::Ptr(_), IrType::Float(fw)) => match fw {
198 FloatWidth::F32 => b.const_f32(0.0),
199 FloatWidth::F64 => b.const_f64(0.0),
200 },
201 // Ptr<Bool> → Bool: dereference the pointer. Stdlib's masked
202 // reductions (`stdlib_sum_1d_sp_mask` etc.) hit this when the
203 // mask actual is passed by reference but the inner expression
204 // expects a bool value. A typed load keeps the IR consistent
205 // and the runtime correct.
206 (IrType::Ptr(inner), IrType::Bool) if matches!(**inner, IrType::Bool) => {
207 b.load_typed(val, IrType::Bool)
208 }
209 // Ptr<i8> → Bool: load the byte and treat it as a logical.
210 // Fortran logical(1) flows through the IR as a pointer to i8
211 // (the in-memory representation), but element-wise intrinsic
212 // bodies (merge/where) expect the element value as a Bool.
213 // Without this, merge() with a logical(1) mask array failed
214 // to compile and crashed the IR verifier.
215 (IrType::Ptr(inner), IrType::Bool)
216 if matches!(**inner, IrType::Int(IntWidth::I8)) =>
217 {
218 let byte = b.load_typed(val, IrType::Int(IntWidth::I8));
219 let zero = b.const_int(0, IntWidth::I8);
220 b.icmp(CmpOp::Ne, byte, zero)
221 }
222 _ => {
223 eprintln!(
224 "coerce_to_type: unhandled coercion {:?}{:?}",
225 src, target
226 );
227 val
228 }
229 }
230 }
231
232 pub(super) fn widen_to_i64(b: &mut FuncBuilder, value: ValueId) -> ValueId {
233 match b.func().value_type(value) {
234 Some(IrType::Int(IntWidth::I64)) => value,
235 _ => coerce_to_type(b, value, &IrType::Int(IntWidth::I64)),
236 }
237 }
238
239 pub(super) fn clamp_nonnegative_i64(b: &mut FuncBuilder, value: ValueId) -> ValueId {
240 let widened = widen_to_i64(b, value);
241 let zero = b.const_i64(0);
242 let is_nonnegative = b.icmp(CmpOp::Ge, widened, zero);
243 b.select(is_nonnegative, widened, zero)
244 }
245
246 pub(super) fn const_range_for_ir_type(ty: &IrType) -> Option<i128> {
247 Some(match ty {
248 IrType::Int(IntWidth::I8) => 2,
249 IrType::Int(IntWidth::I16) => 4,
250 IrType::Int(IntWidth::I32) => 9,
251 IrType::Int(IntWidth::I64) => 18,
252 IrType::Int(IntWidth::I128) => 38,
253 IrType::Float(FloatWidth::F32) => 37,
254 IrType::Float(FloatWidth::F64) => 307,
255 _ => return None,
256 })
257 }
258
259 /// Storage size in bits (F2018 §16.9.196 STORAGE_SIZE) for a value of
260 /// the given IR type. Walks pointer/array wrappers so callers passing
261 /// the *address* of a value still get the value's storage size.
262 pub(super) fn storage_size_bits_for_ir_type(ty: &IrType) -> i32 {
263 match ty {
264 IrType::Int(IntWidth::I8) => 8,
265 IrType::Int(IntWidth::I16) => 16,
266 IrType::Int(IntWidth::I32) => 32,
267 IrType::Int(IntWidth::I64) => 64,
268 IrType::Int(IntWidth::I128) => 128,
269 IrType::Float(FloatWidth::F32) => 32,
270 IrType::Float(FloatWidth::F64) => 64,
271 IrType::Bool => 32,
272 IrType::Array(elem, n) => {
273 let elem_bits = storage_size_bits_for_ir_type(elem);
274 elem_bits.saturating_mul(*n as i32)
275 }
276 // Pointer to a value — return the storage size of the pointee
277 // (this is the form taken by descriptor-passed actuals).
278 IrType::Ptr(inner) => storage_size_bits_for_ir_type(inner),
279 _ => 0,
280 }
281 }
282