Rust · 17304 bytes Raw Blame History
1 //! Lowering context — `LowerCtx`, `LocalInfo`, RAII guards for the
2 //! thread-local proc-scope/submodule state, loop-scope tracking, and
3 //! the small helper enums (`CharKind`, `HiddenResultAbi`) that
4 //! `LocalInfo` depends on.
5 //!
6 //! Extracted from `lower::core` in sprint 04 step 2. Visibility is
7 //! kept tight (`pub(super)` for the bulk of items, `pub(crate)` only
8 //! where the type already crossed a module boundary — namely
9 //! `CharKind`, which `sema::amod` constructs by name).
10
11 use crate::ir::inst::{BlockId, ValueId};
12 use crate::ir::types::IrType;
13 use crate::sema::symtab::SymbolTable;
14 use std::cell::RefCell;
15 use std::collections::{HashMap, HashSet};
16 use std::rc::Rc;
17
18 use super::const_scalar::ConstScalar;
19 use super::core::ModuleGlobalInfo;
20
21 pub(super) type AmbiguousUseWarnings = Rc<RefCell<HashSet<(String, String, String)>>>;
22
23 thread_local! {
24 /// Sema scope id of the procedure currently being lowered. Set by
25 /// `LowerCtx::with_proc_scope` around `lower_stmts` so the
26 /// stateless `lower_expr_full` recursion can intercept F77
27 /// statement-function call sites without threading an extra
28 /// parameter through 60-plus call sites. `None` when lowering
29 /// outside any procedure body (e.g. expression evaluation during
30 /// `init_decls`).
31 static CURRENT_PROC_SCOPE: RefCell<Option<crate::sema::symtab::ScopeId>> =
32 const { RefCell::new(None) };
33
34 /// For SMP body lowering: the submodule containing the body. The
35 /// procedure's link name lives under the parent module, but the
36 /// install_globals_as_locals path also needs the submodule so it
37 /// can pull in the submodule's locally-declared parameters
38 /// (mangled under the submodule name post-d770b77).
39 static SMP_EXTRA_HOST: RefCell<Option<String>> = const { RefCell::new(None) };
40 }
41
42 pub(super) fn current_proc_scope() -> Option<crate::sema::symtab::ScopeId> {
43 CURRENT_PROC_SCOPE.with(|c| *c.borrow())
44 }
45
46 pub(super) fn current_smp_extra_host() -> Option<String> {
47 SMP_EXTRA_HOST.with(|c| c.borrow().clone())
48 }
49
50 /// RAII guard: install `scope` as the current procedure scope until
51 /// the guard is dropped, then restore the previous value. Lets nested
52 /// contained-subprogram lowering recover its outer scope cleanly.
53 pub(super) struct ProcScopeGuard(Option<crate::sema::symtab::ScopeId>);
54
55 impl ProcScopeGuard {
56 pub(super) fn enter(scope: Option<crate::sema::symtab::ScopeId>) -> Self {
57 let prev = CURRENT_PROC_SCOPE.with(|c| c.replace(scope));
58 ProcScopeGuard(prev)
59 }
60 }
61
62 impl Drop for ProcScopeGuard {
63 fn drop(&mut self) {
64 let prev = self.0;
65 CURRENT_PROC_SCOPE.with(|c| *c.borrow_mut() = prev);
66 }
67 }
68
69 pub(super) struct SmpExtraHostGuard(Option<String>);
70
71 impl SmpExtraHostGuard {
72 pub(super) fn set(name: String) -> Self {
73 let prev = SMP_EXTRA_HOST.with(|c| c.replace(Some(name)));
74 SmpExtraHostGuard(prev)
75 }
76 }
77
78 impl Drop for SmpExtraHostGuard {
79 fn drop(&mut self) {
80 let prev = self.0.take();
81 SMP_EXTRA_HOST.with(|c| *c.borrow_mut() = prev);
82 }
83 }
84
85 /// Maximum array rank (Fortran allows up to 15).
86 #[allow(dead_code)]
87 pub(super) const MAX_RANK: usize = 15;
88
89 /// Loop context for EXIT/CYCLE targeting.
90 pub(super) struct LoopScope {
91 pub(super) name: Option<String>,
92 pub(super) header: BlockId, // CYCLE target
93 pub(super) exit: BlockId, // EXIT target
94 }
95
96 /// Character variable kind: how string storage is managed.
97 #[derive(Clone, PartialEq)]
98 pub(crate) enum CharKind {
99 /// Not a character variable.
100 None,
101 /// Fixed-length character(N): addr points to N-byte stack buffer.
102 Fixed(i64),
103 /// Fixed-length character whose length is only known at runtime.
104 /// For locals, `addr` is a stack slot holding the heap buffer
105 /// pointer. For by-ref dummies, `addr` is the usual slot holding
106 /// the caller's pointer. `len_addr` stores the runtime length.
107 FixedRuntime { len_addr: ValueId },
108 /// Deferred-length character(:), allocatable: addr points to 32-byte StringDescriptor.
109 Deferred,
110 /// Assumed-length character(*) dummy parameter. The runtime
111 /// length is held in a hidden i64 parameter appended after the
112 /// normal positional args. `len_addr` is the alloca holding
113 /// the hidden param's value so reads can load it at runtime.
114 AssumedLen { len_addr: ValueId },
115 }
116
117 #[derive(Clone, Copy, PartialEq, Eq)]
118 pub(super) enum HiddenResultAbi {
119 None,
120 ArrayDescriptor,
121 StringDescriptor,
122 DerivedAggregate,
123 /// Complex scalar function result. Caller allocates an 8-byte
124 /// (real(sp)) or 16-byte (real(dp)) buffer, passes its address as
125 /// the hidden first param; callee writes the two float lanes
126 /// through that pointer and returns void. Without this, the
127 /// IR-level return type was `[Float x 2]` aggregate which codegen
128 /// packed into x0 as 8 bytes — the caller then memcpy'd from x0
129 /// treating the value AS a pointer (SEGV on first complex-returning
130 /// call to e.g. stdlib's `gamma_dist_pdf_csp`).
131 ComplexBuffer,
132 }
133
134 /// Info about a local variable.
135 #[derive(Clone)]
136 pub(super) struct LocalInfo {
137 pub(super) addr: ValueId,
138 pub(super) ty: IrType,
139 /// For arrays: (lower_bound, extent) per dimension. Empty for scalars.
140 pub(super) dims: Vec<(i64, i64)>,
141 /// Is this an allocatable variable?
142 pub(super) allocatable: bool,
143 /// Does this local carry runtime array metadata through a descriptor even
144 /// though it is not allocatable (for example an assumed-shape dummy)?
145 pub(super) descriptor_arg: bool,
146 /// Is this a pass-by-reference parameter? If true, `addr` holds a pointer
147 /// to the caller's storage. Reads/writes go through the pointer.
148 pub(super) by_ref: bool,
149 /// Character variable kind (fixed-length, deferred, or not character).
150 pub(super) char_kind: CharKind,
151 /// Derived type name (for component access resolution). Empty for non-derived.
152 pub(super) derived_type: Option<String>,
153 /// For PARAMETER-attributed locals whose initializer const-folds:
154 /// the compile-time value to inline at every use. When `Some`,
155 /// `Expr::Name` lookups should materialize this constant
156 /// directly via `b.const_i32`/`b.const_i64`/etc., instead of
157 /// loading through `addr`. Audit MAJOR-4: this lets parameters
158 /// avoid wasting a `.data` slot per scope.
159 pub(super) inline_const: Option<ConstScalar>,
160 /// Fortran `POINTER` attribute on a scalar local. When true,
161 /// `addr` is an `alloca ptr<ty>` — a pointer slot that holds
162 /// the address of the associated target (or null when
163 /// unassociated). Reads/writes go through the slot just like
164 /// `by_ref`, but `by_ref` is reserved for dummy arguments that
165 /// cannot carry a Fortran `POINTER` attribute's semantics
166 /// (reassociation via `=>`, dereference on plain assignment).
167 pub(super) is_pointer: bool,
168 /// Per-dimension runtime upper bound (i64 value id) for arrays
169 /// whose bounds are not compile-time constants — most commonly
170 /// explicit-shape dummies like `xs(n)` where `n` is a dummy
171 /// argument. When `Some`, bounds checks and cumulative-stride
172 /// computation consult this value instead of `dims[i].1`.
173 /// Empty vec (or an all-`None` vec) means the compile-time
174 /// `dims` is authoritative. Parallel to `dims`.
175 pub(super) runtime_dim_upper: Vec<Option<ValueId>>,
176 /// True when the declaration used CLASS(...) rather than TYPE(...).
177 pub(super) is_class: bool,
178 /// Logical kind from the source declaration (1, 2, 4, or 8) when the
179 /// variable is `logical(kind)`. Default-kind logical leaves this as
180 /// Some(4); non-logical declarations leave it None. Needed because
181 /// kind=1/2/8 logicals are stored as `IrType::Int(I8/I16/I64)` to get
182 /// kind-correct storage size, which would otherwise be indistinguishable
183 /// from real integer locals at later lookup points
184 /// (semantic-type recovery, generic dispatch, print formatting, etc.).
185 pub(super) logical_kind: Option<u8>,
186 /// True for explicit-shape dummy arrays whose last dimension is
187 /// `*` (assumed-size, F2018 §8.5.8.5). Such a dummy carries no
188 /// upper bound on the last dim — accesses past `size(actual)` are
189 /// legal as long as the underlying storage permits — so bounds
190 /// checks must be skipped on that dim. The caller's descriptor
191 /// (or the static `(1, 0)` sentinel emitted by extract_array_dims)
192 /// would otherwise reject every legal access.
193 pub(super) last_dim_assumed_size: bool,
194 }
195
196 /// Lowering context — tracks locals, loop scopes, and symbol table.
197 pub(super) struct LowerCtx<'a> {
198 pub(super) locals: HashMap<String, LocalInfo>,
199 /// Lowercase names of OPTIONAL dummy arguments in the current subprogram.
200 /// Hidden character-length forwarding must treat an absent optional
201 /// character dummy as length zero instead of dereferencing its null slot.
202 pub(super) optional_locals: HashSet<String>,
203 pub(super) loops: Vec<LoopScope>,
204 pub(super) st: &'a SymbolTable,
205 /// Module-scoped globals visible by (lowercase module name,
206 /// lowercase variable name). Populated by the lower_file
207 /// pre-pass over `ProgramUnit::Module` units so any subsequent
208 /// function that USE-imports the module can resolve the name
209 /// to a `GlobalAddr`. Keying by (module, var) is what lets
210 /// install_globals_as_locals filter by the current function's
211 /// USE statements, honor ONLY lists, and apply renames.
212 pub(super) globals: &'a HashMap<(String, String), ModuleGlobalInfo>,
213 pub(super) type_layouts: &'a crate::sema::type_layout::TypeLayoutRegistry,
214 /// Names that a `use mod, only: ...` statement explicitly
215 /// excluded. install_globals_as_locals populates this from the
216 /// difference between a module's exported globals and the
217 /// only-list. Audit MAJOR-1: a reference to a name in this
218 /// set must produce a compile error rather than silently
219 /// lowering to const_int 0.
220 pub(super) filtered_names: HashSet<String>,
221 /// For functions: address of the result variable (for RETURN).
222 pub(super) result_addr: Option<ValueId>,
223 /// For functions: the return type.
224 pub(super) result_type: Option<IrType>,
225 /// For functions: lowercase result variable name when one exists.
226 pub(super) result_name: Option<String>,
227 /// Hidden result ABI for this function, if any.
228 pub(super) hidden_result_abi: HiddenResultAbi,
229 /// Names of functions in the compilation unit that return allocatable
230 /// arrays (sret convention). Used at call sites to detect when to
231 /// pass a temp descriptor as the hidden first arg. Audit6 BLOCKING-1.
232 pub(super) alloc_return_funcs: &'a HashSet<String>,
233 /// Per-subroutine optional-parameter bitmap: maps lowercase callee name
234 /// to a Vec<bool> (one entry per positional parameter, true = OPTIONAL).
235 /// Pre-populated by `collect_optional_params` so call sites can pass
236 /// null pointers for absent optional arguments (PRESENT support).
237 pub(super) optional_params: &'a HashMap<String, Vec<bool>>,
238 /// Per-subroutine/function descriptor-parameter bitmap: maps lowercase
239 /// callee name to a Vec<bool> (one entry per positional parameter,
240 /// true = lower this dummy through an ArrayDescriptor).
241 pub(super) descriptor_params: &'a HashMap<String, Vec<bool>>,
242 /// Lowercase same-module subprogram name → Module::functions index.
243 /// Used so same-compilation-unit calls lower to FuncRef::Internal instead
244 /// of pretending to be external references.
245 pub(super) internal_funcs: &'a HashMap<String, u32>,
246 /// Lowercase names of functions declared ELEMENTAL in this compilation unit.
247 pub(super) elemental_funcs: &'a HashSet<String>,
248 /// Per-callee bitmap of which params are character(len=*).
249 /// Call sites append the string length as a hidden i64 arg for
250 /// each flagged position.
251 pub(super) char_len_star_params: &'a HashMap<String, Vec<bool>>,
252 /// Per-callee list of host-associated variable names the callee
253 /// needs threaded in as hidden trailing pointer args. See the
254 /// closure-passing ABI documented on `lower_file::contained_host_refs`.
255 pub(super) contained_host_refs: &'a HashMap<String, Vec<String>>,
256 /// Map from Fortran statement label (u64) to the IR basic block that
257 /// begins at that label. Pre-populated by `collect_label_blocks` before
258 /// lowering so that GOTO can branch forward as well as backward.
259 pub(super) label_blocks: HashMap<u64, BlockId>,
260 /// Cross-function dedupe for ambiguous USE-import warnings emitted by
261 /// install_globals_as_locals. Large fortsh units can otherwise print the
262 /// exact same ambiguity hundreds or thousands of times while lowering each
263 /// contained procedure separately.
264 pub(super) ambiguous_use_warnings: AmbiguousUseWarnings,
265 /// Sema's `ScopeId` for the procedure currently being lowered.
266 /// Set in the Program/Subroutine/Function arms of `lower_unit`.
267 /// Statement-function lookup keys off this — without it we can't
268 /// distinguish `cabs1` defined in one stdlib BLAS routine from a
269 /// homonymous statement function in another.
270 pub(super) proc_scope_id: Option<crate::sema::symtab::ScopeId>,
271 }
272
273 impl<'a> LowerCtx<'a> {
274 #[allow(clippy::too_many_arguments)]
275 pub(super) fn new(
276 st: &'a SymbolTable,
277 globals: &'a HashMap<(String, String), ModuleGlobalInfo>,
278 type_layouts: &'a crate::sema::type_layout::TypeLayoutRegistry,
279 alloc_return_funcs: &'a HashSet<String>,
280 optional_params: &'a HashMap<String, Vec<bool>>,
281 descriptor_params: &'a HashMap<String, Vec<bool>>,
282 internal_funcs: &'a HashMap<String, u32>,
283 elemental_funcs: &'a HashSet<String>,
284 char_len_star_params: &'a HashMap<String, Vec<bool>>,
285 contained_host_refs: &'a HashMap<String, Vec<String>>,
286 ambiguous_use_warnings: AmbiguousUseWarnings,
287 ) -> Self {
288 Self {
289 locals: HashMap::new(),
290 optional_locals: HashSet::new(),
291 loops: Vec::new(),
292 st,
293 globals,
294 type_layouts,
295 filtered_names: HashSet::new(),
296 result_addr: None,
297 result_type: None,
298 result_name: None,
299 hidden_result_abi: HiddenResultAbi::None,
300 alloc_return_funcs,
301 optional_params,
302 descriptor_params,
303 internal_funcs,
304 elemental_funcs,
305 char_len_star_params,
306 contained_host_refs,
307 label_blocks: HashMap::new(),
308 ambiguous_use_warnings,
309 proc_scope_id: None,
310 }
311 }
312
313 pub(super) fn insert_scalar(&mut self, name: String, addr: ValueId, ty: IrType) {
314 self.locals.insert(
315 name,
316 LocalInfo {
317 addr,
318 ty,
319 dims: vec![],
320 allocatable: false,
321 descriptor_arg: false,
322 by_ref: false,
323 char_kind: CharKind::None,
324 derived_type: None,
325 inline_const: None,
326 is_pointer: false,
327 runtime_dim_upper: vec![],
328 is_class: false,
329 logical_kind: None,
330 last_dim_assumed_size: false,
331 },
332 );
333 }
334
335 pub(super) fn insert_param_by_ref(&mut self, name: String, addr: ValueId, ty: IrType) {
336 self.locals.insert(
337 name,
338 LocalInfo {
339 addr,
340 ty,
341 dims: vec![],
342 allocatable: false,
343 descriptor_arg: false,
344 by_ref: true,
345 char_kind: CharKind::None,
346 derived_type: None,
347 inline_const: None,
348 is_pointer: false,
349 runtime_dim_upper: vec![],
350 is_class: false,
351 logical_kind: None,
352 last_dim_assumed_size: false,
353 },
354 );
355 }
356
357 pub(super) fn push_loop(&mut self, name: Option<String>, header: BlockId, exit: BlockId) {
358 self.loops.push(LoopScope { name, header, exit });
359 }
360
361 pub(super) fn pop_loop(&mut self) {
362 self.loops.pop();
363 }
364
365 /// Look up an F77 statement function by name in the current
366 /// procedure scope. Returns `None` when the name doesn't refer to
367 /// a statement function (or no procedure scope is set).
368 pub(super) fn lookup_statement_function(
369 &self,
370 name: &str,
371 ) -> Option<&'a crate::sema::symtab::StatementFunctionDef> {
372 let scope_id = self.proc_scope_id?;
373 self.st.lookup_statement_function(scope_id, name)
374 }
375
376 /// Find loop by construct name (or innermost if None).
377 pub(super) fn find_loop(&self, name: &Option<String>) -> Option<&LoopScope> {
378 if let Some(n) = name {
379 self.loops.iter().rev().find(|l| {
380 l.name
381 .as_deref()
382 .map(|s| s.eq_ignore_ascii_case(n))
383 .unwrap_or(false)
384 })
385 } else {
386 self.loops.last()
387 }
388 }
389 }
390