| 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 |