Rust · 19095 bytes Raw Blame History
1 //! Initializer lowering for declared variables.
2 //!
3 //! Extracted from `core.rs` in Sprint 11 Stage E. Pure mechanical
4 //! move — behavior unchanged.
5
6 use std::collections::HashMap;
7
8 use crate::ast::decl::Decl;
9 use crate::ast::expr::Expr;
10 use crate::ir::builder::FuncBuilder;
11 use crate::ir::inst::*;
12 use crate::ir::types::*;
13 use crate::sema::symtab::SymbolTable;
14
15 use super::core::*;
16 use super::ctx::{CharKind, LocalInfo};
17 use super::helpers::coerce_to_type;
18
19 /// Lower initializer expressions for declared variables.
20 ///
21 /// Handles two AST shapes:
22 /// 1. `Decl::TypeDecl` entities with `entity.init` set. This
23 /// covers BOTH `integer :: x = 42` and
24 /// `integer, parameter :: pi = 3.14` — the parameter
25 /// attribute doesn't change the lowering, only sema's
26 /// classification of the symbol.
27 /// 2. Standalone `Decl::ParameterStmt { pairs }`, where each
28 /// pair refers to an already-allocated local declared
29 /// elsewhere in the same decl list.
30 ///
31 /// Most scalar locals with const-evaluable initializers are
32 /// SAVE-promoted to module globals back in `alloc_decls`; for
33 /// those, `is_global_addr` returns true and this pass leaves the
34 /// initialization to the .data section. The remaining cases this
35 /// pass handles are non-const initializers (rare).
36 ///
37 /// Must run *after* `alloc_decls` so that all locals exist. Only
38 /// stores into scalar slots — array, character, derived-type, and
39 /// allocatable initializers have their own paths in alloc_decls.
40 pub(crate) fn init_decls(
41 b: &mut FuncBuilder,
42 locals: &HashMap<String, LocalInfo>,
43 decls: &[crate::ast::decl::SpannedDecl],
44 st: &SymbolTable,
45 type_layouts: Option<&crate::sema::type_layout::TypeLayoutRegistry>,
46 ) {
47 // Pre-collect the set of GlobalAddr-defining ValueIds so the
48 // inner skip check is O(1). Audit Maj-3.
49 let global_addr_ids = collect_global_addr_values(b);
50 for decl in decls {
51 match &decl.node {
52 Decl::TypeDecl { entities, .. } => {
53 for entity in entities {
54 let Some(init_expr) = &entity.init else {
55 continue;
56 };
57 let key = entity.name.to_lowercase();
58 let Some(info) = locals.get(&key) else {
59 continue;
60 };
61 // Dummy arguments (by_ref locals) cannot have
62 // initializers per the Fortran standard — they
63 // bind to caller storage. If sema lets one
64 // through it would be a bug; the debug_assert
65 // catches it in development without crashing
66 // release builds. Audit Min-4.
67 debug_assert!(
68 !info.by_ref,
69 "init_decls: dummy argument {:?} should not have an initializer",
70 key,
71 );
72 if info.by_ref {
73 continue;
74 }
75
76 // Array entity with an array constructor init:
77 // store each literal element into the slot.
78 // Only stack/non-allocatable arrays are handled
79 // here; allocatable arrays would need their
80 // descriptor allocated first.
81 if !info.dims.is_empty()
82 && !info.allocatable
83 && matches!(info.char_kind, CharKind::None)
84 && info.derived_type.is_none()
85 {
86 if let Expr::ArrayConstructor { values, .. } = &init_expr.node {
87 store_ac_values_into(
88 b,
89 locals,
90 info.addr,
91 &info.ty,
92 info.derived_type.as_deref(),
93 values,
94 st,
95 type_layouts,
96 None,
97 None,
98 None,
99 );
100 } else if let Some(values) = super::core::extract_reshape_source_ac(&init_expr.node) {
101 // F2018 §16.9.169 RESHAPE used as a declared
102 // initializer for a fixed-shape stack array.
103 // The source AC is laid out column-major into
104 // the destination; for a contiguous source the
105 // reshape is a pure reinterpretation, so we
106 // can store the flat element list straight
107 // into the slot via the existing AC writer.
108 // Pre-fix `reshape([...], [...])` initializers
109 // were silently dropped here, leaving every
110 // rank-2+ stack array with garbage data — every
111 // example that did `real :: y(2,3) =
112 // reshape([1.,2.,3.,4.,5.,6.], [2,3])` saw
113 // y(1,1) come back as a junk float.
114 store_ac_values_into(
115 b,
116 locals,
117 info.addr,
118 &info.ty,
119 info.derived_type.as_deref(),
120 values,
121 st,
122 type_layouts,
123 None,
124 None,
125 None,
126 );
127 } else if matches!(
128 &init_expr.node,
129 Expr::IntegerLiteral { .. }
130 | Expr::RealLiteral { .. }
131 | Expr::LogicalLiteral { .. }
132 ) && !is_complex_ty(&info.ty)
133 {
134 // F2018 §7.6.6: scalar literal initializer broadcast
135 // to every element of the array. Previously this
136 // path skipped non-AC initializers and left the
137 // stack array uninitialized — `logical :: a(4)
138 // = .true.` returned all-junk for any array
139 // size > 0. Lower the literal once, then store
140 // it at each element offset. Restricted to
141 // literal scalars: compound expressions like
142 // `reshape(...)` return an array descriptor that
143 // must be element-wise copied via a different
144 // path.
145 let total: i64 = info.dims.iter().map(|(_, n)| *n).product();
146 if total > 0 {
147 let raw = super::expr::lower_expr(b, locals, init_expr, st);
148 let val = coerce_to_type(b, raw, &info.ty);
149 for i in 0..total {
150 let idx = b.const_i64(i);
151 let slot = b.gep(info.addr, vec![idx], info.ty.clone());
152 b.store(val, slot);
153 }
154 }
155 }
156 continue;
157 }
158 if !info.dims.is_empty()
159 && !info.allocatable
160 && info.derived_type.is_none()
161 && matches!(info.char_kind, CharKind::Fixed(_))
162 {
163 if let Expr::ArrayConstructor { values, .. } = &init_expr.node {
164 if let CharKind::Fixed(len) = info.char_kind {
165 store_char_ac_values_into(
166 b,
167 locals,
168 info.addr,
169 len,
170 values,
171 st,
172 type_layouts,
173 None,
174 None,
175 None,
176 );
177 }
178 }
179 continue;
180 }
181
182 // Fixed-length character initializer: copy the
183 // literal bytes into the stack buffer with
184 // space-padding to the declared length. Previously
185 // the character arm was unconditionally skipped,
186 // leaving every `character(len=N) :: s = 'hello'`
187 // zero-initialized and silently blank at runtime
188 // (audit31 Finding 3).
189 if let CharKind::Fixed(len) = info.char_kind {
190 let (src_ptr, src_len) = lower_string_expr(b, locals, init_expr, st);
191 let dest_len = b.const_i64(len);
192 b.call(
193 FuncRef::External("afs_assign_char_fixed".into()),
194 vec![info.addr, dest_len, src_ptr, src_len],
195 IrType::Void,
196 );
197 continue;
198 }
199 if let CharKind::FixedRuntime { len_addr } = info.char_kind {
200 let (src_ptr, src_len) = lower_string_expr(b, locals, init_expr, st);
201 let (dest_ptr, dest_len) =
202 fixed_runtime_char_ptr_and_len(b, info, len_addr);
203 b.call(
204 FuncRef::External("afs_assign_char_fixed".into()),
205 vec![dest_ptr, dest_len, src_ptr, src_len],
206 IrType::Void,
207 );
208 continue;
209 }
210 if info.dims.is_empty() && !info.allocatable && !info.is_pointer {
211 if let Some(type_name) = info.derived_type.as_deref() {
212 if let Some(tl) = type_layouts {
213 if let Some(layout) = tl.get(type_name) {
214 let src = super::expr::lower_expr_full(
215 b,
216 locals,
217 init_expr,
218 st,
219 type_layouts,
220 None,
221 None,
222 None,
223 );
224 let sz = b.const_i64(layout.size as i64);
225 b.call(
226 FuncRef::External("memcpy".into()),
227 vec![info.addr, src, sz],
228 IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))),
229 );
230 continue;
231 }
232 }
233 }
234 }
235 // Other non-plain-scalar shapes are handled
236 // elsewhere (allocatables, derived types) or not
237 // at all (deferred-length character, which gets
238 // its store through afs_assign_char_deferred at
239 // the declaration's assignment lowering).
240 if !info.dims.is_empty()
241 || info.allocatable
242 || !matches!(info.char_kind, CharKind::None)
243 || info.derived_type.is_some()
244 {
245 continue;
246 }
247 // SAVE-promoted locals are backed by a module
248 // global already initialized at link time. Don't
249 // re-store on every call — that would defeat
250 // the SAVE semantics (audit MAJOR-1).
251 if global_addr_ids.contains(&info.addr) {
252 continue;
253 }
254 // Audit5 MAJOR-3: PARAMETER scalars folded by
255 // alloc_decls have inline_const set and a
256 // sentinel alloca that is never loaded — every
257 // use materializes the constant directly. The
258 // store here would be dead in the IR forever
259 // at -O0 (mem2reg cleans it up at -O1+, but
260 // we shouldn't generate dead code in the first
261 // place).
262 if info.inline_const.is_some() {
263 continue;
264 }
265 // Complex scalar init: ComplexLiteral lowers to an
266 // address of a [f32/f64 x 2] buffer. Copying a
267 // pointer into the slot (whose pointee is the
268 // 2-element array) would fail IR verification — do
269 // a byte memcpy of the inline buffer instead.
270 if is_complex_ty(&info.ty) && !info.is_pointer {
271 let src = super::expr::lower_expr(b, locals, init_expr, st);
272 let bytes = complex_byte_size(&info.ty);
273 let sz = b.const_i64(bytes);
274 b.call(
275 FuncRef::External("memcpy".into()),
276 vec![info.addr, src, sz],
277 IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))),
278 );
279 continue;
280 }
281 let val = super::expr::lower_expr(b, locals, init_expr, st);
282 let coerced = coerce_to_type(b, val, &info.ty);
283 b.store(coerced, info.addr);
284 }
285 }
286 Decl::ParameterStmt { pairs } => {
287 for (name, expr) in pairs {
288 let key = name.to_lowercase();
289 let Some(info) = locals.get(&key) else {
290 continue;
291 };
292 if let CharKind::Fixed(len) = info.char_kind {
293 let (src_ptr, src_len) = lower_string_expr(b, locals, expr, st);
294 let dest_len = b.const_i64(len);
295 b.call(
296 FuncRef::External("afs_assign_char_fixed".into()),
297 vec![info.addr, dest_len, src_ptr, src_len],
298 IrType::Void,
299 );
300 continue;
301 }
302 if let CharKind::FixedRuntime { len_addr } = info.char_kind {
303 let (src_ptr, src_len) = lower_string_expr(b, locals, expr, st);
304 let (dest_ptr, dest_len) =
305 fixed_runtime_char_ptr_and_len(b, info, len_addr);
306 b.call(
307 FuncRef::External("afs_assign_char_fixed".into()),
308 vec![dest_ptr, dest_len, src_ptr, src_len],
309 IrType::Void,
310 );
311 continue;
312 }
313 if !info.dims.is_empty()
314 || info.allocatable
315 || info.by_ref
316 || !matches!(info.char_kind, CharKind::None)
317 || info.derived_type.is_some()
318 {
319 continue;
320 }
321 // SAVE-promoted locals are backed by a module
322 // global; the initial value is already baked
323 // into .data at link time, so skip the runtime
324 // store. Audit MAJOR-1 interaction.
325 if global_addr_ids.contains(&info.addr) {
326 continue;
327 }
328 // Audit5 MAJOR-3: same dead-store skip as the
329 // TypeDecl arm above. Standalone PARAMETER
330 // statements also produce inline_const-tagged
331 // locals when alloc_decls successfully folds
332 // the value.
333 if info.inline_const.is_some() {
334 continue;
335 }
336 let val = super::expr::lower_expr(b, locals, expr, st);
337 let coerced = coerce_to_type(b, val, &info.ty);
338 b.store(coerced, info.addr);
339 }
340 }
341 // Audit MEDIUM-3: DATA statements. Each set pairs
342 // target objects with values. For the simple form
343 // `data x /42/, y /3.14/`, walk objects + values
344 // pairwise and emit a store per scalar Name target.
345 // Implied-do object lists and value-side repetition
346 // (`r*v`) are not yet supported — they fall through
347 // silently and are tracked as future work.
348 Decl::DataStmt { sets } => {
349 for set in sets {
350 let n = set.objects.len().min(set.values.len());
351 for (target, value) in set.objects.iter().zip(set.values.iter()).take(n) {
352 let Expr::Name { name } = &target.node else {
353 continue;
354 };
355 let key = name.to_lowercase();
356 let Some(info) = locals.get(&key) else {
357 continue;
358 };
359 if !info.dims.is_empty()
360 || info.allocatable
361 || info.by_ref
362 || !matches!(info.char_kind, CharKind::None)
363 || info.derived_type.is_some()
364 {
365 continue;
366 }
367 // Don't shadow a SAVE-promoted global —
368 // its initial value is in .data already.
369 if global_addr_ids.contains(&info.addr) {
370 continue;
371 }
372 let val = super::expr::lower_expr(b, locals, value, st);
373 let coerced = coerce_to_type(b, val, &info.ty);
374 b.store(coerced, info.addr);
375 }
376 }
377 }
378 _ => {}
379 }
380 }
381 }
382