fortrangoingonforty/armfortas / 92d1134

Browse files

Fix deferred string descriptor ABI

Authored by espadonne
SHA
92d11341f6797340fc822f3c0f5d536bf0b4daf4
Parents
559dabb
Tree
24f1fa2

24 changed files

StatusFile+-
M src/ast/stmt.rs 1 0
M src/driver/mod.rs 35 26
M src/ir/lower.rs 1558 243
M src/lexer/mod.rs 4 5
M src/opt/gvn.rs 1 0
M src/parser/stmt.rs 26 4
M src/sema/amod.rs 98 13
M src/sema/resolve.rs 31 0
M src/sema/validate.rs 1 1
M src/testing.rs 1 0
M tests/claims_audit_29_11.rs 6 2
M tests/cli_driver.rs 843 8
M tests/elemental_array_map.rs 6 2
M tests/fortran_alias_licm.rs 6 2
M tests/gvn_dse_audit_29_11.rs 6 2
M tests/ipo_const_arg.rs 6 2
M tests/ipo_dead_arg.rs 6 2
M tests/ipo_return_prop.rs 6 2
M tests/licm_lsf_audit_29_11.rs 6 2
M tests/module_host_audit.rs 6 2
M tests/opt_audit_29_11.rs 6 2
M tests/pure_call_reuse.rs 6 2
M tests/pure_dead_call_elim.rs 6 2
M tests/sroa_shape_audit_29_11.rs 6 2
src/ast/stmt.rsmodified
@@ -196,6 +196,7 @@ pub enum Stmt {
196196
 
197197
     // ---- Memory ----
198198
     Allocate {
199
+        type_spec: Option<super::decl::TypeSpec>,
199200
         items: Vec<SpannedExpr>,
200201
         opts: Vec<IoControl>,
201202
     },
src/driver/mod.rsmodified
@@ -316,11 +316,8 @@ pub fn parse_cli(raw_args: &[String]) -> Result<ParsedCli, String> {
316316
                     Some(PathBuf::from(args.get(i).ok_or("-J requires a directory")?));
317317
             }
318318
             arg if arg.starts_with("-J") => {
319
-                opts.module_output_dir = Some(PathBuf::from(short_option_value(
320
-                    arg,
321
-                    "-J",
322
-                    "a directory",
323
-                )?));
319
+                opts.module_output_dir =
320
+                    Some(PathBuf::from(short_option_value(arg, "-J", "a directory")?));
324321
             }
325322
 
326323
             // ---- Linker search / libs / rpath ----
@@ -329,19 +326,18 @@ pub fn parse_cli(raw_args: &[String]) -> Result<ParsedCli, String> {
329326
                 opts.library_search_paths
330327
                     .push(PathBuf::from(args.get(i).ok_or("-L requires a directory")?));
331328
             }
332
-            arg if arg.starts_with("-L") => {
333
-                opts.library_search_paths
334
-                    .push(PathBuf::from(short_option_value(arg, "-L", "a directory")?))
335
-            }
329
+            arg if arg.starts_with("-L") => opts
330
+                .library_search_paths
331
+                .push(PathBuf::from(short_option_value(arg, "-L", "a directory")?)),
336332
 
337333
             "-l" => {
338334
                 i += 1;
339335
                 opts.link_libs
340336
                     .push(args.get(i).ok_or("-l requires a library name")?.clone());
341337
             }
342
-            arg if arg.starts_with("-l") => opts.link_libs.push(
343
-                short_option_value(arg, "-l", "a library name")?.to_string(),
344
-            ),
338
+            arg if arg.starts_with("-l") => opts
339
+                .link_libs
340
+                .push(short_option_value(arg, "-l", "a library name")?.to_string()),
345341
 
346342
             "-rpath" | "--rpath" => {
347343
                 i += 1;
@@ -1043,7 +1039,10 @@ pub fn compile(opts: &Options) -> Result<(), String> {
10431039
                 1,
10441040
             );
10451041
             phases.report();
1046
-            return Err(format!("aborting due to errors in {}", opts.input.display()));
1042
+            return Err(format!(
1043
+                "aborting due to errors in {}",
1044
+                opts.input.display()
1045
+            ));
10471046
         }
10481047
     };
10491048
     phase.end(&mut phases);
@@ -1067,12 +1066,12 @@ pub fn compile(opts: &Options) -> Result<(), String> {
10671066
         Ok(units) => units,
10681067
         Err(e) => {
10691068
             phase.end(&mut phases);
1070
-            let span_len = if e.span.end.line == e.span.start.line && e.span.end.col > e.span.start.col
1071
-            {
1072
-                (e.span.end.col - e.span.start.col) as usize
1073
-            } else {
1074
-                1
1075
-            };
1069
+            let span_len =
1070
+                if e.span.end.line == e.span.start.line && e.span.end.col > e.span.start.col {
1071
+                    (e.span.end.col - e.span.start.col) as usize
1072
+                } else {
1073
+                    1
1074
+                };
10761075
             diag::render(
10771076
                 &file_str,
10781077
                 &source,
@@ -1082,7 +1081,10 @@ pub fn compile(opts: &Options) -> Result<(), String> {
10821081
                 span_len,
10831082
             );
10841083
             phases.report();
1085
-            return Err(format!("aborting due to errors in {}", opts.input.display()));
1084
+            return Err(format!(
1085
+                "aborting due to errors in {}",
1086
+                opts.input.display()
1087
+            ));
10861088
         }
10871089
     };
10881090
     phase.end(&mut phases);
@@ -1164,6 +1166,11 @@ pub fn compile(opts: &Options) -> Result<(), String> {
11641166
     }
11651167
 
11661168
     // 6. Lower to IR.
1169
+    let mut external_descriptor_params = std::collections::HashMap::new();
1170
+    for ext_mod in &resolve_result.external_modules {
1171
+        external_descriptor_params.extend(crate::sema::amod::extract_descriptor_params(ext_mod));
1172
+    }
1173
+
11671174
     // Build external char_len_star_params from .amod-loaded modules.
11681175
     let mut external_char_len_star = std::collections::HashMap::new();
11691176
     for ext_mod in &resolve_result.external_modules {
@@ -1175,6 +1182,7 @@ pub fn compile(opts: &Options) -> Result<(), String> {
11751182
         &st,
11761183
         &type_layouts,
11771184
         external_globals,
1185
+        external_descriptor_params,
11781186
         external_char_len_star,
11791187
     );
11801188
     let ir_errors = verify::verify_module(&ir_module);
@@ -1398,12 +1406,13 @@ _main:
13981406
                     &ir_module,
13991407
                     &std::collections::HashMap::new(), // char_len_star computed by writer from scope
14001408
                 );
1401
-                let amod_dir: std::path::PathBuf = opts.module_output_dir.clone().unwrap_or_else(|| {
1402
-                    opts.output_path()
1403
-                        .parent()
1404
-                        .unwrap_or_else(|| std::path::Path::new("."))
1405
-                        .to_path_buf()
1406
-                });
1409
+                let amod_dir: std::path::PathBuf =
1410
+                    opts.module_output_dir.clone().unwrap_or_else(|| {
1411
+                        opts.output_path()
1412
+                            .parent()
1413
+                            .unwrap_or_else(|| std::path::Path::new("."))
1414
+                            .to_path_buf()
1415
+                    });
14071416
                 let amod_path = amod_dir.join(format!("{}.amod", mod_key));
14081417
                 fs::write(&amod_path, &amod_text)
14091418
                     .map_err(|e| format!("cannot write '{}': {}", amod_path.display(), e))?;
src/ir/lower.rsmodified
2482 lines changed — click to load
@@ -47,6 +47,13 @@ pub(crate) enum CharKind {
4747
     AssumedLen { len_addr: ValueId },
4848
 }
4949
 
50
+#[derive(Clone, Copy, PartialEq, Eq)]
51
+enum HiddenResultAbi {
52
+    None,
53
+    ArrayDescriptor,
54
+    StringDescriptor,
55
+}
56
+
5057
 /// Info about a local variable.
5158
 #[derive(Clone)]
5259
 struct LocalInfo {
@@ -264,6 +271,7 @@ pub fn lower_file(
264271
     st: &SymbolTable,
265272
     type_layouts: &crate::sema::type_layout::TypeLayoutRegistry,
266273
     external_globals: HashMap<(String, String), ModuleGlobalInfo>,
274
+    external_descriptor_params: HashMap<String, Vec<bool>>,
267275
     external_char_len_star: HashMap<String, Vec<bool>>,
268276
 ) -> (Module, HashMap<(String, String), ModuleGlobalInfo>) {
269277
     let mut module = Module::new("main".into());
@@ -353,7 +361,7 @@ pub fn lower_file(
353361
         collect_optional_params(&unit.node, &mut optional_params);
354362
     }
355363
 
356
-    let mut descriptor_params: HashMap<String, Vec<bool>> = HashMap::new();
364
+    let mut descriptor_params: HashMap<String, Vec<bool>> = external_descriptor_params;
357365
     for unit in units {
358366
         collect_descriptor_params(&unit.node, &mut descriptor_params);
359367
     }
@@ -582,10 +590,63 @@ fn collect_internal_func_names(
582590
     }
583591
 }
584592
 
593
+fn function_hidden_result_abi(
594
+    function_name: &str,
595
+    result: &Option<String>,
596
+    decls: &[crate::ast::decl::SpannedDecl],
597
+) -> HiddenResultAbi {
598
+    use crate::ast::decl::Attribute;
599
+    let result_key = result
600
+        .as_deref()
601
+        .unwrap_or(function_name)
602
+        .to_ascii_lowercase();
603
+    for decl in decls {
604
+        let Decl::TypeDecl {
605
+            type_spec,
606
+            attrs,
607
+            entities,
608
+            ..
609
+        } = &decl.node
610
+        else {
611
+            continue;
612
+        };
613
+        let attr_dims: Option<&Vec<ArraySpec>> = attrs.iter().find_map(|a| {
614
+            if let Attribute::Dimension(specs) = a {
615
+                Some(specs)
616
+            } else {
617
+                None
618
+            }
619
+        });
620
+        for entity in entities {
621
+            if entity.name.to_ascii_lowercase() != result_key {
622
+                continue;
623
+            }
624
+            let has_dims = entity.array_spec.as_ref().or(attr_dims).is_some();
625
+            let is_hidden_string = matches!(
626
+                type_spec,
627
+                TypeSpec::Character(Some(sel))
628
+                    if matches!(sel.len, Some(crate::ast::decl::LenSpec::Colon))
629
+            ) && !has_dims
630
+                && attrs
631
+                    .iter()
632
+                    .any(|a| matches!(a, Attribute::Allocatable | Attribute::Pointer));
633
+            if is_hidden_string {
634
+                return HiddenResultAbi::StringDescriptor;
635
+            }
636
+            if attrs.iter().any(|a| matches!(a, Attribute::Allocatable)) {
637
+                return HiddenResultAbi::ArrayDescriptor;
638
+            }
639
+            return HiddenResultAbi::None;
640
+        }
641
+    }
642
+    HiddenResultAbi::None
643
+}
644
+
585645
 /// Walk a program unit and any nested `contains` to collect the
586
-/// names of functions whose result variable is declared
587
-/// `allocatable`. The set is keyed by lowercase function name and
588
-/// is consulted at call sites in pass 2.
646
+/// names of functions whose result variable is lowered through the
647
+/// 384-byte array descriptor hidden-result ABI. Deferred-length
648
+/// scalar character results use the 32-byte string descriptor ABI
649
+/// and are intentionally excluded here.
589650
 ///
590651
 /// Audit6 BLOCKING-1: a function `function f() result(r); integer,
591652
 /// allocatable :: r(:)` cannot be returned by value through the
@@ -595,23 +656,6 @@ fn collect_internal_func_names(
595656
 /// that the caller passes in, and the function writes its result
596657
 /// into that descriptor.
597658
 fn collect_alloc_return_funcs(unit: &ProgramUnit, out: &mut HashSet<String>) {
598
-    use crate::ast::decl::Attribute;
599
-    let scan_decls = |decls: &[crate::ast::decl::SpannedDecl], result_name: &str| -> bool {
600
-        let key = result_name.to_lowercase();
601
-        for decl in decls {
602
-            if let Decl::TypeDecl {
603
-                entities, attrs, ..
604
-            } = &decl.node
605
-            {
606
-                for entity in entities {
607
-                    if entity.name.to_lowercase() == key {
608
-                        return attrs.iter().any(|a| matches!(a, Attribute::Allocatable));
609
-                    }
610
-                }
611
-            }
612
-        }
613
-        false
614
-    };
615659
     match unit {
616660
         ProgramUnit::Function {
617661
             name,
@@ -620,8 +664,7 @@ fn collect_alloc_return_funcs(unit: &ProgramUnit, out: &mut HashSet<String>) {
620664
             result,
621665
             ..
622666
         } => {
623
-            let result_name = result.as_deref().unwrap_or(name.as_str());
624
-            if scan_decls(decls, result_name) {
667
+            if function_hidden_result_abi(name, result, decls) == HiddenResultAbi::ArrayDescriptor {
625668
                 out.insert(name.to_lowercase());
626669
             }
627670
             for sub in contains {
@@ -1769,14 +1812,11 @@ fn collect_module_globals(
17691812
             let is_pointer = attrs.iter().any(|a| matches!(a, Attribute::Pointer));
17701813
             let global_char_kind = match type_spec {
17711814
                 TypeSpec::Character(Some(sel)) => match &sel.len {
1772
-                    Some(crate::ast::decl::LenSpec::Expr(e)) => match &e.node {
1773
-                        crate::ast::expr::Expr::IntegerLiteral { text, .. } => text
1774
-                            .parse::<i64>()
1775
-                            .ok()
1815
+                    Some(crate::ast::decl::LenSpec::Expr(e)) => {
1816
+                        eval_const_int_in_scope_or_any_scope(e, &HashMap::new(), st)
17761817
                             .map(CharKind::Fixed)
1777
-                            .unwrap_or(CharKind::None),
1778
-                        _ => CharKind::None,
1779
-                    },
1818
+                            .unwrap_or(CharKind::None)
1819
+                    }
17801820
                     Some(crate::ast::decl::LenSpec::Colon) => CharKind::Deferred,
17811821
                     Some(crate::ast::decl::LenSpec::Star) => CharKind::None,
17821822
                     None => CharKind::Fixed(1),
@@ -1800,6 +1840,12 @@ fn collect_module_globals(
18001840
                 // descriptor body rather than an extra layer of
18011841
                 // indirection.
18021842
                 if (is_allocatable || is_pointer) && array_spec.is_some() {
1843
+                    let storage_ty = if let Some(type_name) = &derived_type_name {
1844
+                        derived_storage_ir_type(type_name, type_layouts)
1845
+                            .unwrap_or_else(|| ir_ty.clone())
1846
+                    } else {
1847
+                        ir_ty.clone()
1848
+                    };
18031849
                     let desc_ty = IrType::Array(Box::new(IrType::Int(IntWidth::I8)), 384);
18041850
                     module.add_global(Global {
18051851
                         name: symbol.clone(),
@@ -1810,12 +1856,12 @@ fn collect_module_globals(
18101856
                         (mod_name.to_lowercase(), entity.name.to_lowercase()),
18111857
                         ModuleGlobalInfo {
18121858
                             symbol,
1813
-                            ty: ir_ty.clone(),
1859
+                            ty: storage_ty,
18141860
                             dims: vec![],
18151861
                             allocatable: true,
18161862
                             is_pointer,
18171863
                             deferred_char: false,
1818
-                            derived_type: None,
1864
+                            derived_type: derived_type_name.clone(),
18191865
                             char_kind: global_char_kind.clone(),
18201866
                             external: false,
18211867
                         },
@@ -2502,7 +2548,7 @@ fn lower_unit(
25022548
                         let ck = if let Some(&len_slot) = hidden_len_addrs.get(pname) {
25032549
                             CharKind::AssumedLen { len_addr: len_slot }
25042550
                         } else {
2505
-                            arg_char_kind_from_decls(pname, decls)
2551
+                            arg_char_kind_from_decls(pname, decls, st)
25062552
                         };
25072553
                         let info = LocalInfo {
25082554
                             addr: slot,
@@ -2525,7 +2571,8 @@ fn lower_unit(
25252571
                     if *is_value || hidden_len_addrs.contains_key(pname) {
25262572
                         continue;
25272573
                     }
2528
-                    let Some(len_expr) = arg_runtime_char_len_expr_from_decls(pname, decls) else {
2574
+                    let Some(len_expr) = arg_runtime_char_len_expr_from_decls(pname, decls, st)
2575
+                    else {
25292576
                         continue;
25302577
                     };
25312578
                     let len_raw = lower_expr(&mut b, &ctx.locals, &len_expr, ctx.st);
@@ -2663,18 +2710,22 @@ fn lower_unit(
26632710
             let visible_param_consts =
26642711
                 collect_decl_param_consts_with_host(decls, host_param_consts);
26652712
 
2666
-            // Audit6 BLOCKING-1: functions with allocatable result use the
2667
-            // sret (hidden-output-param) convention. The caller allocas a
2668
-            // 384-byte descriptor and passes its address as param 0; the
2669
-            // function writes its result into that descriptor and returns
2670
-            // void. This avoids trying to return 384 bytes "by value".
2671
-            let is_alloc_return = alloc_return_funcs.contains(&name.to_lowercase());
2672
-
2673
-            let (func_params, ir_ret_ty) = if is_alloc_return {
2674
-                // Hidden first param: ptr to caller-provided 384-byte descriptor.
2713
+            // Hidden-result ABI: allocatable arrays use a 384-byte array
2714
+            // descriptor, while deferred-length scalar character results use
2715
+            // a 32-byte string descriptor. In both cases the caller provides
2716
+            // the descriptor storage as param 0 and the callee returns void.
2717
+            let hidden_result_abi = function_hidden_result_abi(name, result, decls);
2718
+            let uses_hidden_result = hidden_result_abi != HiddenResultAbi::None;
2719
+
2720
+            let (func_params, ir_ret_ty) = if uses_hidden_result {
2721
+                let desc_size = match hidden_result_abi {
2722
+                    HiddenResultAbi::ArrayDescriptor => 384,
2723
+                    HiddenResultAbi::StringDescriptor => 32,
2724
+                    HiddenResultAbi::None => 0,
2725
+                };
26752726
                 let desc_ptr_ty = IrType::Ptr(Box::new(IrType::Array(
26762727
                     Box::new(IrType::Int(IntWidth::I8)),
2677
-                    384,
2728
+                    desc_size,
26782729
                 )));
26792730
                 let sret = Param {
26802731
                     name: "_sret".into(),
@@ -2832,7 +2883,7 @@ fn lower_unit(
28322883
                 char_len_star_params,
28332884
                 contained_host_refs,
28342885
             );
2835
-            ctx.is_alloc_return = is_alloc_return;
2886
+            ctx.is_alloc_return = uses_hidden_result;
28362887
             let mut pending_globals: Vec<PendingGlobal> = Vec::new();
28372888
             let combined_uses: Vec<crate::ast::decl::SpannedDecl> =
28382889
                 host_uses.iter().chain(uses.iter()).cloned().collect();
@@ -2893,7 +2944,7 @@ fn lower_unit(
28932944
                         let ck = if let Some(&len_slot) = hidden_len_addrs.get(pname) {
28942945
                             CharKind::AssumedLen { len_addr: len_slot }
28952946
                         } else {
2896
-                            arg_char_kind_from_decls(pname, decls)
2947
+                            arg_char_kind_from_decls(pname, decls, st)
28972948
                         };
28982949
                         ctx.locals.insert(
28992950
                             pname.clone(),
@@ -2918,7 +2969,8 @@ fn lower_unit(
29182969
                     if *is_value || hidden_len_addrs.contains_key(pname) {
29192970
                         continue;
29202971
                     }
2921
-                    let Some(len_expr) = arg_runtime_char_len_expr_from_decls(pname, decls) else {
2972
+                    let Some(len_expr) = arg_runtime_char_len_expr_from_decls(pname, decls, st)
2973
+                    else {
29222974
                         continue;
29232975
                     };
29242976
                     let len_raw = lower_expr(&mut b, &ctx.locals, &len_expr, ctx.st);
@@ -2941,10 +2993,8 @@ fn lower_unit(
29412993
 
29422994
                 let result_is_pointer = decl_is_pointer(&result_name, decls);
29432995
 
2944
-                if is_alloc_return {
2945
-                    // The sret param (ValueId 0) IS the descriptor address.
2946
-                    // Pre-insert the result variable as an allocatable backed by that
2947
-                    // descriptor so alloc_decls skips it (locals.contains_key → continue).
2996
+                if hidden_result_abi == HiddenResultAbi::ArrayDescriptor {
2997
+                    // The hidden first param is the caller-provided array descriptor.
29482998
                     let elem_ty = arg_type_from_decls(&result_name, decls, Some(st));
29492999
                     ctx.locals.insert(
29503000
                         result_name.clone(),
@@ -2962,7 +3012,25 @@ fn lower_unit(
29623012
                             runtime_dim_upper: vec![],
29633013
                         },
29643014
                     );
2965
-                    // result_addr = None; is_alloc_return = true tells Stmt::Return to emit ret void.
3015
+                } else if hidden_result_abi == HiddenResultAbi::StringDescriptor {
3016
+                    // Deferred-length scalar character result: the hidden first
3017
+                    // param is a caller-provided StringDescriptor.
3018
+                    ctx.locals.insert(
3019
+                        result_name.clone(),
3020
+                        LocalInfo {
3021
+                            addr: ValueId(0),
3022
+                            ty: IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))),
3023
+                            dims: vec![],
3024
+                            allocatable: true,
3025
+                            descriptor_arg: false,
3026
+                            by_ref: false,
3027
+                            char_kind: CharKind::Deferred,
3028
+                            derived_type: None,
3029
+                            inline_const: None,
3030
+                            is_pointer: result_is_pointer,
3031
+                            runtime_dim_upper: vec![],
3032
+                        },
3033
+                    );
29663034
                 } else if result_is_pointer {
29673035
                     let result_addr = b.alloca(ir_ret_ty.clone());
29683036
                     let zero_byte = b.const_i32(0);
@@ -3070,7 +3138,7 @@ fn lower_unit(
30703138
                 lower_stmts(&mut b, &mut ctx, body);
30713139
 
30723140
                 if b.func().block(b.current_block()).terminator.is_none() {
3073
-                    let skip = if is_alloc_return {
3141
+                    let skip = if uses_hidden_result {
30743142
                         Some(ValueId(0))
30753143
                     } else {
30763144
                         None
@@ -3085,7 +3153,7 @@ fn lower_unit(
30853153
                         Some(ctx.contained_host_refs),
30863154
                         skip,
30873155
                     );
3088
-                    if is_alloc_return {
3156
+                    if uses_hidden_result {
30893157
                         b.ret(None);
30903158
                     } else if derived_type_name_for_result_var(return_type, &result_name, decls)
30913159
                         .is_some()
@@ -3873,7 +3941,7 @@ fn check_filtered_in_stmt(stmt: &crate::ast::stmt::SpannedStmt, filtered: &HashS
38733941
         }
38743942
 
38753943
         // ---- Memory ----
3876
-        Stmt::Allocate { items, opts } | Stmt::Deallocate { items, opts } => {
3944
+        Stmt::Allocate { items, opts, .. } | Stmt::Deallocate { items, opts } => {
38773945
             for item in items {
38783946
                 check_filtered_in_expr(item, filtered);
38793947
             }
@@ -4361,7 +4429,7 @@ fn alloc_decls(
43614429
                     TypeSpec::Character(Some(sel)) => {
43624430
                         match &sel.len {
43634431
                             Some(crate::ast::decl::LenSpec::Expr(e)) => {
4364
-                                eval_const_int_in_scope(e, &param_consts)
4432
+                                eval_const_int_in_scope_or_any_scope(e, &param_consts, st)
43654433
                             }
43664434
                             Some(crate::ast::decl::LenSpec::Star) => None, // assumed
43674435
                             Some(crate::ast::decl::LenSpec::Colon) => None, // deferred
@@ -4374,7 +4442,8 @@ fn alloc_decls(
43744442
                 let runtime_char_len_expr = match type_spec {
43754443
                     TypeSpec::Character(Some(sel)) => match &sel.len {
43764444
                         Some(crate::ast::decl::LenSpec::Expr(e))
4377
-                            if eval_const_int_in_scope(e, &param_consts).is_none() =>
4445
+                            if eval_const_int_in_scope_or_any_scope(e, &param_consts, st)
4446
+                                .is_none() =>
43784447
                         {
43794448
                             Some(e)
43804449
                         }
@@ -4419,17 +4488,39 @@ fn alloc_decls(
44194488
                     // dims is left empty for a deferred-shape pointer;
44204489
                     // the descriptor carries the runtime rank and
44214490
                     // bounds after `=>` binds it to a target.
4491
+                    let pointer_elem_ty = if matches!(type_spec, TypeSpec::Character(_)) {
4492
+                        match char_len {
4493
+                            Some(len) => fixed_char_storage_ir_type(len),
4494
+                            None => elem_ty.clone(),
4495
+                        }
4496
+                    } else if let TypeSpec::Type(ref type_name) = type_spec {
4497
+                        derived_storage_ir_type(type_name, type_layouts)
4498
+                            .unwrap_or_else(|| elem_ty.clone())
4499
+                    } else {
4500
+                        elem_ty.clone()
4501
+                    };
4502
+                    let pointer_char_kind = if matches!(type_spec, TypeSpec::Character(_)) {
4503
+                        match char_len {
4504
+                            Some(len) => CharKind::Fixed(len),
4505
+                            None => CharKind::None,
4506
+                        }
4507
+                    } else {
4508
+                        CharKind::None
4509
+                    };
44224510
                     locals.insert(
44234511
                         key,
44244512
                         LocalInfo {
44254513
                             addr,
4426
-                            ty: elem_ty.clone(),
4514
+                            ty: pointer_elem_ty,
44274515
                             dims: vec![],
44284516
                             allocatable: true,
44294517
                             descriptor_arg: false,
44304518
                             by_ref: false,
4431
-                            char_kind: CharKind::None,
4432
-                            derived_type: None,
4519
+                            char_kind: pointer_char_kind,
4520
+                            derived_type: match type_spec {
4521
+                                TypeSpec::Type(type_name) => Some(type_name.clone()),
4522
+                                _ => None,
4523
+                            },
44334524
                             inline_const: None,
44344525
                             is_pointer: true,
44354526
                             runtime_dim_upper: vec![],
@@ -4472,8 +4563,11 @@ fn alloc_decls(
44724563
                         continue;
44734564
                     }
44744565
                 }
4475
-                if is_deferred_char && is_allocatable {
4476
-                    // Deferred-length allocatable character: 32-byte StringDescriptor.
4566
+                if is_deferred_char && is_allocatable && array_spec.is_none() {
4567
+                    // Deferred-length allocatable scalar character:
4568
+                    // 32-byte StringDescriptor. Deferred-length
4569
+                    // allocatable arrays fall through to the general
4570
+                    // array-descriptor path below.
44774571
                     let desc_ty = IrType::Array(Box::new(IrType::Int(IntWidth::I8)), 32);
44784572
                     let addr = b.alloca(desc_ty);
44794573
                     let zero = b.const_i32(0);
@@ -4504,6 +4598,89 @@ fn alloc_decls(
45044598
                     if let Some(specs) = array_spec.filter(|_| !is_allocatable) {
45054599
                         let dims = extract_array_dims(specs, &param_consts);
45064600
                         let total_size: i64 = dims.iter().map(|(_, size)| *size).product();
4601
+                        if len == 1 {
4602
+                            // `character(len=1)` arrays are byte arrays, not
4603
+                            // pointer tables. Keeping them contiguous matches
4604
+                            // descriptor-backed `character(kind=c_char)` locals
4605
+                            // and lets element stores land in `ptr<i8>` slots.
4606
+                            const STACK_THRESHOLD: i64 = 64 * 1024;
4607
+                            if total_size >= STACK_THRESHOLD {
4608
+                                let desc_ty =
4609
+                                    IrType::Array(Box::new(IrType::Int(IntWidth::I8)), 384);
4610
+                                let addr = b.alloca(desc_ty);
4611
+                                let zero = b.const_i32(0);
4612
+                                let size384 = b.const_i64(384);
4613
+                                b.call(
4614
+                                    FuncRef::External("memset".into()),
4615
+                                    vec![addr, zero, size384],
4616
+                                    IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))),
4617
+                                );
4618
+                                let es = b.const_i64(1);
4619
+                                let n = b.const_i64(total_size);
4620
+                                b.call(
4621
+                                    FuncRef::External("afs_allocate_1d".into()),
4622
+                                    vec![addr, es, n],
4623
+                                    IrType::Void,
4624
+                                );
4625
+                                let space = b.const_i32(b' ' as i32);
4626
+                                let base = b.load_typed(
4627
+                                    addr,
4628
+                                    IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))),
4629
+                                );
4630
+                                let bytes = b.const_i64(total_size);
4631
+                                b.call(
4632
+                                    FuncRef::External("memset".into()),
4633
+                                    vec![base, space, bytes],
4634
+                                    IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))),
4635
+                                );
4636
+                                locals.insert(
4637
+                                    key,
4638
+                                    LocalInfo {
4639
+                                        addr,
4640
+                                        ty: IrType::Int(IntWidth::I8),
4641
+                                        dims,
4642
+                                        allocatable: true,
4643
+                                        descriptor_arg: false,
4644
+                                        by_ref: false,
4645
+                                        char_kind: CharKind::Fixed(1),
4646
+                                        derived_type: None,
4647
+                                        inline_const: None,
4648
+                                        is_pointer: false,
4649
+                                        runtime_dim_upper: vec![],
4650
+                                    },
4651
+                                );
4652
+                            } else {
4653
+                                let arr_ty = IrType::Array(
4654
+                                    Box::new(IrType::Int(IntWidth::I8)),
4655
+                                    total_size as u64,
4656
+                                );
4657
+                                let addr = b.alloca(arr_ty);
4658
+                                let space = b.const_i32(b' ' as i32);
4659
+                                let bytes = b.const_i64(total_size);
4660
+                                b.call(
4661
+                                    FuncRef::External("memset".into()),
4662
+                                    vec![addr, space, bytes],
4663
+                                    IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))),
4664
+                                );
4665
+                                locals.insert(
4666
+                                    key,
4667
+                                    LocalInfo {
4668
+                                        addr,
4669
+                                        ty: IrType::Int(IntWidth::I8),
4670
+                                        dims,
4671
+                                        allocatable: false,
4672
+                                        descriptor_arg: false,
4673
+                                        by_ref: false,
4674
+                                        char_kind: CharKind::Fixed(1),
4675
+                                        derived_type: None,
4676
+                                        inline_const: None,
4677
+                                        is_pointer: false,
4678
+                                        runtime_dim_upper: vec![],
4679
+                                    },
4680
+                                );
4681
+                            }
4682
+                            continue;
4683
+                        }
45074684
                         let table_ty = IrType::Array(
45084685
                             Box::new(IrType::Ptr(Box::new(IrType::Int(IntWidth::I8)))),
45094686
                             total_size as u64,
@@ -4663,6 +4840,17 @@ fn alloc_decls(
46634840
                         vec![addr, zero, size],
46644841
                         IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))),
46654842
                     );
4843
+                    let alloc_elem_ty = if matches!(type_spec, TypeSpec::Character(_)) {
4844
+                        match char_len {
4845
+                            Some(len) => fixed_char_storage_ir_type(len),
4846
+                            None => elem_ty.clone(),
4847
+                        }
4848
+                    } else if let TypeSpec::Type(ref type_name) = type_spec {
4849
+                        derived_storage_ir_type(type_name, type_layouts)
4850
+                            .unwrap_or_else(|| elem_ty.clone())
4851
+                    } else {
4852
+                        elem_ty.clone()
4853
+                    };
46664854
                     let char_kind = match char_len {
46674855
                         Some(len) if array_spec.is_some() => CharKind::Fixed(len),
46684856
                         _ => CharKind::None,
@@ -4671,7 +4859,7 @@ fn alloc_decls(
46714859
                         key,
46724860
                         LocalInfo {
46734861
                             addr,
4674
-                            ty: elem_ty.clone(),
4862
+                            ty: alloc_elem_ty,
46754863
                             dims: vec![],
46764864
                             allocatable: true,
46774865
                             descriptor_arg: false,
@@ -5323,6 +5511,100 @@ fn eval_const_int_in_scope(
53235511
     }
53245512
 }
53255513
 
5514
+fn eval_const_scalar_with_any_scope(
5515
+    expr: &crate::ast::expr::SpannedExpr,
5516
+    param_consts: &HashMap<String, ConstScalar>,
5517
+    st: &SymbolTable,
5518
+) -> Option<ConstScalar> {
5519
+    if let Some(value) = eval_const_scalar(expr, param_consts) {
5520
+        return Some(value);
5521
+    }
5522
+    match &expr.node {
5523
+        Expr::Name { name } => {
5524
+            let key = name.to_ascii_lowercase();
5525
+            param_consts.get(&key).copied().or_else(|| {
5526
+                st.find_symbol_any_scope(&key)
5527
+                    .and_then(|sym| sym.const_value.map(|v| ConstScalar::Int(v as i128)))
5528
+            })
5529
+        }
5530
+        Expr::ParenExpr { inner } => eval_const_scalar_with_any_scope(inner, param_consts, st),
5531
+        Expr::UnaryOp {
5532
+            op: UnaryOp::Minus,
5533
+            operand,
5534
+        } => {
5535
+            let value = eval_const_scalar_with_any_scope(operand, param_consts, st)?;
5536
+            Some(match value {
5537
+                ConstScalar::Int(i) => ConstScalar::Int(-i),
5538
+                ConstScalar::Float(f) => ConstScalar::Float(-f),
5539
+            })
5540
+        }
5541
+        Expr::BinaryOp { op, left, right } => {
5542
+            let lv = eval_const_scalar_with_any_scope(left, param_consts, st)?;
5543
+            let rv = eval_const_scalar_with_any_scope(right, param_consts, st)?;
5544
+            let promote_float =
5545
+                matches!(lv, ConstScalar::Float(_)) || matches!(rv, ConstScalar::Float(_));
5546
+            if promote_float {
5547
+                let l = lv.to_float();
5548
+                let r = rv.to_float();
5549
+                match op {
5550
+                    BinaryOp::Add => Some(ConstScalar::Float(l + r)),
5551
+                    BinaryOp::Sub => Some(ConstScalar::Float(l - r)),
5552
+                    BinaryOp::Mul => Some(ConstScalar::Float(l * r)),
5553
+                    BinaryOp::Div => {
5554
+                        if r == 0.0 {
5555
+                            None
5556
+                        } else {
5557
+                            Some(ConstScalar::Float(l / r))
5558
+                        }
5559
+                    }
5560
+                    BinaryOp::Pow => Some(ConstScalar::Float(l.powf(r))),
5561
+                    _ => None,
5562
+                }
5563
+            } else {
5564
+                let (ConstScalar::Int(l), ConstScalar::Int(r)) = (lv, rv) else {
5565
+                    return None;
5566
+                };
5567
+                match op {
5568
+                    BinaryOp::Add => Some(ConstScalar::Int(l.wrapping_add(r))),
5569
+                    BinaryOp::Sub => Some(ConstScalar::Int(l.wrapping_sub(r))),
5570
+                    BinaryOp::Mul => Some(ConstScalar::Int(l.wrapping_mul(r))),
5571
+                    BinaryOp::Div => {
5572
+                        if r == 0 {
5573
+                            None
5574
+                        } else {
5575
+                            Some(ConstScalar::Int(l / r))
5576
+                        }
5577
+                    }
5578
+                    BinaryOp::Pow => {
5579
+                        if r < 0 {
5580
+                            None
5581
+                        } else {
5582
+                            let mut acc: i128 = 1;
5583
+                            for _ in 0..(r as usize) {
5584
+                                acc = acc.wrapping_mul(l);
5585
+                            }
5586
+                            Some(ConstScalar::Int(acc))
5587
+                        }
5588
+                    }
5589
+                    _ => None,
5590
+                }
5591
+            }
5592
+        }
5593
+        _ => None,
5594
+    }
5595
+}
5596
+
5597
+fn eval_const_int_in_scope_or_any_scope(
5598
+    expr: &crate::ast::expr::SpannedExpr,
5599
+    param_consts: &HashMap<String, ConstScalar>,
5600
+    st: &SymbolTable,
5601
+) -> Option<i64> {
5602
+    match eval_const_scalar_with_any_scope(expr, param_consts, st)? {
5603
+        ConstScalar::Int(v) => i64::try_from(v).ok(),
5604
+        ConstScalar::Float(_) => None,
5605
+    }
5606
+}
5607
+
53265608
 /// Resolve the raw data pointer and declared length for a character argument expression.
53275609
 /// Returns `None` if the argument is not a recognized fixed-length character.
53285610
 /// Build (ptr, len) for a substring `base_ptr(start:end)` per F2018 7.4.4.2.
@@ -5399,6 +5681,19 @@ fn descriptor_elem_size(b: &mut FuncBuilder, desc: ValueId) -> ValueId {
53995681
     b.load_typed(ptr, IrType::Int(IntWidth::I64))
54005682
 }
54015683
 
5684
+fn descriptor_backed_runtime_char_array(info: &LocalInfo) -> bool {
5685
+    local_uses_array_descriptor(info)
5686
+        && matches!(
5687
+            info.ty,
5688
+            IrType::Ptr(ref inner) if matches!(inner.as_ref(), IrType::Int(IntWidth::I8))
5689
+        )
5690
+        && (!info.dims.is_empty()
5691
+            || info.descriptor_arg
5692
+            || (info.allocatable
5693
+                && info.char_kind == CharKind::None
5694
+                && local_fixed_char_allocatable_scalar_len(info).is_none()))
5695
+}
5696
+
54025697
 fn char_array_element_ptr_and_len(
54035698
     b: &mut FuncBuilder,
54045699
     locals: &HashMap<String, LocalInfo>,
@@ -5406,7 +5701,10 @@ fn char_array_element_ptr_and_len(
54065701
     args: &[crate::ast::expr::Argument],
54075702
     st: &SymbolTable,
54085703
 ) -> Option<(ValueId, ValueId)> {
5409
-    if info.char_kind == CharKind::None || args.is_empty() {
5704
+    if args.is_empty() {
5705
+        return None;
5706
+    }
5707
+    if info.char_kind == CharKind::None && !descriptor_backed_runtime_char_array(info) {
54105708
         return None;
54115709
     }
54125710
     let idx64 = compute_flat_elem_offset(b, locals, info, args, st);
@@ -5417,7 +5715,12 @@ fn char_array_element_ptr_and_len(
54175715
             let desc = array_descriptor_addr(b, info);
54185716
             descriptor_elem_size(b, desc)
54195717
         }
5420
-        CharKind::Deferred | CharKind::None => return None,
5718
+        CharKind::Deferred => return None,
5719
+        CharKind::None if descriptor_backed_runtime_char_array(info) => {
5720
+            let desc = array_descriptor_addr(b, info);
5721
+            descriptor_elem_size(b, desc)
5722
+        }
5723
+        CharKind::None => return None,
54215724
     };
54225725
     if !local_uses_array_descriptor(info) && !info.by_ref {
54235726
         let slot_ptr = b.gep(
@@ -5520,7 +5823,13 @@ fn char_addr_and_runtime_len(
55205823
                     Some((ptr, len))
55215824
                 }
55225825
                 CharKind::None => {
5523
-                    if info.by_ref
5826
+                    if let Some(len) = local_fixed_char_allocatable_scalar_len(info) {
5827
+                        let desc = array_descriptor_addr(b, info);
5828
+                        let base = b.load_typed(desc, IrType::Ptr(Box::new(info.ty.clone())));
5829
+                        let zero = b.const_i64(0);
5830
+                        let ptr = b.gep(base, vec![zero], IrType::Int(IntWidth::I8));
5831
+                        Some((ptr, b.const_i64(len)))
5832
+                    } else if info.by_ref
55245833
                         && matches!(
55255834
                             info.ty,
55265835
                             IrType::Ptr(ref inner) if matches!(inner.as_ref(), IrType::Int(IntWidth::I8))
@@ -5588,7 +5897,8 @@ fn actual_char_arg_runtime_len(
55885897
         Expr::FunctionCall { callee, args } => {
55895898
             if let Expr::Name { name } = &callee.node {
55905899
                 if let Some(info) = locals.get(&name.to_lowercase()) {
5591
-                    if let Some((_ptr, len)) = char_array_element_ptr_and_len(b, locals, info, args, st)
5900
+                    if let Some((_ptr, len)) =
5901
+                        char_array_element_ptr_and_len(b, locals, info, args, st)
55925902
                     {
55935903
                         return Some(len);
55945904
                     }
@@ -6753,11 +7063,7 @@ fn lower_intrinsic(b: &mut FuncBuilder, name: &str, args: &[ValueId]) -> Option<
67537063
         )),
67547064
 
67557065
         // ---- iso_c_binding functions ----
6756
-        "c_loc" => {
6757
-            // c_loc(x) — return address of x. The arg is already passed by reference,
6758
-            // so the arg value IS the address.
6759
-            args.first().copied()
6760
-        }
7066
+        "c_loc" => None,
67617067
         "c_sizeof" => {
67627068
             // c_sizeof(x) — return byte size of x's C representation.
67637069
             if let Some(arg) = args.first() {
@@ -7306,44 +7612,58 @@ fn resolved_symbol_call_target(
73067612
     (fallback_name.to_string(), key.to_string())
73077613
 }
73087614
 
7309
-fn lower_alloc_return_call_into_desc(
7615
+fn emit_named_function_call(
73107616
     b: &mut FuncBuilder,
7311
-    ctx: &LowerCtx,
7312
-    desc_addr: ValueId,
7617
+    locals: &HashMap<String, LocalInfo>,
7618
+    st: &SymbolTable,
7619
+    type_layouts: Option<&crate::sema::type_layout::TypeLayoutRegistry>,
7620
+    internal_funcs: Option<&HashMap<String, u32>>,
7621
+    contained_host_refs: Option<&HashMap<String, Vec<String>>>,
7622
+    descriptor_params: Option<&HashMap<String, Vec<bool>>>,
73137623
     callee_name: &str,
73147624
     args: &[crate::ast::expr::Argument],
7315
-) {
7625
+    hidden_result: Option<ValueId>,
7626
+    ret_ty: IrType,
7627
+) -> ValueId {
73167628
     let key = callee_name.to_lowercase();
7317
-    let reordered = reorder_args_by_keyword(args, &key, ctx.st);
7629
+    let reordered = reorder_args_by_keyword(args, &key, st);
73187630
     let args: &[crate::ast::expr::Argument] = &reordered;
73197631
 
73207632
     let intrinsic_arg_vals: Vec<ValueId> = args
73217633
         .iter()
73227634
         .map(|a| match &a.value {
7323
-            crate::ast::expr::SectionSubscript::Element(e) => lower_expr_ctx(b, ctx, e),
7635
+            crate::ast::expr::SectionSubscript::Element(e) => lower_expr_full(
7636
+                b,
7637
+                locals,
7638
+                e,
7639
+                st,
7640
+                type_layouts,
7641
+                internal_funcs,
7642
+                contained_host_refs,
7643
+                descriptor_params,
7644
+            ),
73247645
             _ => b.const_i32(0),
73257646
         })
73267647
         .collect();
73277648
 
7328
-    let resolved_name = match resolve_generic_call(ctx.st, b, &key, &intrinsic_arg_vals) {
7649
+    let resolved_name = match resolve_generic_call(st, b, &key, &intrinsic_arg_vals) {
73297650
         Some(name) => name,
73307651
         None => callee_name.to_string(),
73317652
     };
73327653
     let resolved_key = resolved_name.to_lowercase();
7333
-    let (call_name, callee_key) =
7334
-        resolved_symbol_call_target(ctx.st, &resolved_key, &resolved_name);
7654
+    let (call_name, callee_key) = resolved_symbol_call_target(st, &resolved_key, &resolved_name);
73357655
 
73367656
     let callee_value_args =
7337
-        callee_value_arg_mask(ctx.st, &callee_key).or_else(|| callee_value_arg_mask(ctx.st, &key));
7338
-    let callee_descriptor_args = ctx
7339
-        .descriptor_params
7340
-        .get(&callee_key)
7341
-        .cloned()
7342
-        .or_else(|| ctx.descriptor_params.get(&key).cloned());
7343
-    let callee_string_descriptor_args = callee_string_descriptor_arg_mask(ctx.st, &callee_key)
7344
-        .or_else(|| callee_string_descriptor_arg_mask(ctx.st, &key));
7657
+        callee_value_arg_mask(st, &callee_key).or_else(|| callee_value_arg_mask(st, &key));
7658
+    let callee_descriptor_args = descriptor_params
7659
+        .and_then(|m| m.get(&callee_key).cloned().or_else(|| m.get(&key).cloned()));
7660
+    let callee_string_descriptor_args = callee_string_descriptor_arg_mask(st, &callee_key)
7661
+        .or_else(|| callee_string_descriptor_arg_mask(st, &key));
73457662
 
7346
-    let mut call_args = vec![desc_addr];
7663
+    let mut call_args = Vec::new();
7664
+    if let Some(desc) = hidden_result {
7665
+        call_args.push(desc);
7666
+    }
73477667
     call_args.extend(args.iter().enumerate().map(|(i, a)| {
73487668
         let is_value = callee_value_args
73497669
             .as_ref()
@@ -7360,7 +7680,133 @@ fn lower_alloc_return_call_into_desc(
73607680
         match &a.value {
73617681
             crate::ast::expr::SectionSubscript::Element(e) => {
73627682
                 if is_value {
7363
-                    lower_expr_ctx(b, ctx, e)
7683
+                    lower_expr_full(
7684
+                        b,
7685
+                        locals,
7686
+                        e,
7687
+                        st,
7688
+                        type_layouts,
7689
+                        internal_funcs,
7690
+                        contained_host_refs,
7691
+                        descriptor_params,
7692
+                    )
7693
+                } else if wants_descriptor {
7694
+                    lower_arg_descriptor(b, locals, e, st)
7695
+                } else if wants_string_descriptor {
7696
+                    lower_arg_string_descriptor(b, locals, e, st, type_layouts)
7697
+                } else {
7698
+                    lower_arg_by_ref(b, locals, e, st)
7699
+                }
7700
+            }
7701
+            _ => b.const_i32(0),
7702
+        }
7703
+    }));
7704
+
7705
+    if let Some(cls_flags) =
7706
+        callee_char_len_star_mask(st, &callee_key).or_else(|| callee_char_len_star_mask(st, &key))
7707
+    {
7708
+        for (i, flag) in cls_flags.iter().enumerate() {
7709
+            if !*flag || i >= args.len() {
7710
+                continue;
7711
+            }
7712
+            if let crate::ast::expr::SectionSubscript::Element(e) = &args[i].value {
7713
+                if let Some((_ptr, len)) = char_addr_and_runtime_len(b, e, locals) {
7714
+                    call_args.push(len);
7715
+                } else if let Expr::StringLiteral { value, .. } = &e.node {
7716
+                    call_args.push(b.const_i64(value.len() as i64));
7717
+                } else if expr_is_character_expr(b, locals, e, st, type_layouts) {
7718
+                    let (_ptr, len) = lower_string_expr_full(
7719
+                        b,
7720
+                        locals,
7721
+                        e,
7722
+                        st,
7723
+                        type_layouts,
7724
+                        internal_funcs,
7725
+                        contained_host_refs,
7726
+                        descriptor_params,
7727
+                    );
7728
+                    call_args.push(len);
7729
+                } else {
7730
+                    call_args.push(b.const_i64(0));
7731
+                }
7732
+            } else {
7733
+                call_args.push(b.const_i64(0));
7734
+            }
7735
+        }
7736
+    }
7737
+
7738
+    let closure_key = if contained_host_refs
7739
+        .map(|m| m.contains_key(&callee_key))
7740
+        .unwrap_or(false)
7741
+    {
7742
+        &callee_key
7743
+    } else {
7744
+        &key
7745
+    };
7746
+    append_host_closure_args_raw(b, locals, contained_host_refs, closure_key, &mut call_args);
7747
+
7748
+    let func_ref = internal_funcs
7749
+        .and_then(|map| map.get(&callee_key).or_else(|| map.get(&key)).copied())
7750
+        .map(FuncRef::Internal)
7751
+        .unwrap_or_else(|| FuncRef::External(call_name));
7752
+    b.call(func_ref, call_args, ret_ty)
7753
+}
7754
+
7755
+fn lower_alloc_return_call_into_desc(
7756
+    b: &mut FuncBuilder,
7757
+    ctx: &LowerCtx,
7758
+    desc_addr: ValueId,
7759
+    callee_name: &str,
7760
+    args: &[crate::ast::expr::Argument],
7761
+) {
7762
+    let key = callee_name.to_lowercase();
7763
+    let reordered = reorder_args_by_keyword(args, &key, ctx.st);
7764
+    let args: &[crate::ast::expr::Argument] = &reordered;
7765
+
7766
+    let intrinsic_arg_vals: Vec<ValueId> = args
7767
+        .iter()
7768
+        .map(|a| match &a.value {
7769
+            crate::ast::expr::SectionSubscript::Element(e) => lower_expr_ctx(b, ctx, e),
7770
+            _ => b.const_i32(0),
7771
+        })
7772
+        .collect();
7773
+
7774
+    let resolved_name = match resolve_generic_call(ctx.st, b, &key, &intrinsic_arg_vals) {
7775
+        Some(name) => name,
7776
+        None => callee_name.to_string(),
7777
+    };
7778
+    let resolved_key = resolved_name.to_lowercase();
7779
+    let (call_name, callee_key) =
7780
+        resolved_symbol_call_target(ctx.st, &resolved_key, &resolved_name);
7781
+
7782
+    let callee_value_args =
7783
+        callee_value_arg_mask(ctx.st, &callee_key).or_else(|| callee_value_arg_mask(ctx.st, &key));
7784
+    let callee_descriptor_args = ctx
7785
+        .descriptor_params
7786
+        .get(&callee_key)
7787
+        .cloned()
7788
+        .or_else(|| ctx.descriptor_params.get(&key).cloned());
7789
+    let callee_string_descriptor_args = callee_string_descriptor_arg_mask(ctx.st, &callee_key)
7790
+        .or_else(|| callee_string_descriptor_arg_mask(ctx.st, &key));
7791
+
7792
+    let mut call_args = vec![desc_addr];
7793
+    call_args.extend(args.iter().enumerate().map(|(i, a)| {
7794
+        let is_value = callee_value_args
7795
+            .as_ref()
7796
+            .map(|mask| i < mask.len() && mask[i])
7797
+            .unwrap_or(false);
7798
+        let wants_descriptor = callee_descriptor_args
7799
+            .as_ref()
7800
+            .map(|mask| i < mask.len() && mask[i])
7801
+            .unwrap_or(false);
7802
+        let wants_string_descriptor = callee_string_descriptor_args
7803
+            .as_ref()
7804
+            .map(|mask| i < mask.len() && mask[i])
7805
+            .unwrap_or(false);
7806
+        match &a.value {
7807
+            crate::ast::expr::SectionSubscript::Element(e) => {
7808
+                if is_value {
7809
+                    lower_expr_ctx(b, ctx, e)
73647810
                 } else if wants_string_descriptor {
73657811
                     lower_arg_string_descriptor(b, &ctx.locals, e, ctx.st, Some(ctx.type_layouts))
73667812
                 } else if wants_descriptor {
@@ -7799,14 +8245,125 @@ fn lower_intrinsic_subroutine(
77998245
         // ---- iso_c_binding subroutines ----
78008246
         "c_f_pointer" => {
78018247
             // call c_f_pointer(cptr, fptr [, shape])
7802
-            // Store the C pointer value into the Fortran pointer variable.
7803
-            // cptr is i64 (c_ptr), fptr is a by-reference Fortran pointer.
7804
-            // Convert i64 address to pointer type before storing.
7805
-            let cptr = nth_arg_val(b, ctx, args, 0, 0);
8248
+            //
8249
+            // Scalar pointers store the raw address directly into the
8250
+            // pointer slot. Array pointers are descriptor-backed in
8251
+            // armfortas, so we must populate the 384-byte descriptor
8252
+            // with base_addr/elem_size/rank/bounds instead of
8253
+            // treating the second argument like a plain Ptr<T>.
8254
+            let raw_cptr = nth_arg_val(b, ctx, args, 0, 0);
8255
+            let cptr = match b.func().value_type(raw_cptr) {
8256
+                Some(IrType::Int(IntWidth::I64)) => raw_cptr,
8257
+                _ => b.int_extend(raw_cptr, IntWidth::I64, false),
8258
+            };
8259
+
8260
+            let target_expr = args.get(1).and_then(|arg| {
8261
+                if let crate::ast::expr::SectionSubscript::Element(expr) = &arg.value {
8262
+                    Some(expr)
8263
+                } else {
8264
+                    None
8265
+                }
8266
+            });
8267
+            if let Some(expr) = target_expr {
8268
+                if let Some((target_addr, elem_ty, descriptor_backed)) =
8269
+                    c_f_pointer_target(b, ctx, expr)
8270
+                {
8271
+                    if descriptor_backed {
8272
+                        let zero32 = b.const_i32(0);
8273
+                        let sz384 = b.const_i64(384);
8274
+                        b.call(
8275
+                            FuncRef::External("memset".into()),
8276
+                            vec![target_addr, zero32, sz384],
8277
+                            IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))),
8278
+                        );
8279
+
8280
+                        let base_ptr = b.int_to_ptr(cptr, elem_ty.clone());
8281
+                        store_byte_aggregate_field(
8282
+                            b,
8283
+                            target_addr,
8284
+                            0,
8285
+                            IrType::Ptr(Box::new(elem_ty.clone())),
8286
+                            base_ptr,
8287
+                        );
8288
+                        let elem_size = b.const_i64(ir_scalar_byte_size(&elem_ty));
8289
+                        store_byte_aggregate_field(
8290
+                            b,
8291
+                            target_addr,
8292
+                            8,
8293
+                            IrType::Int(IntWidth::I64),
8294
+                            elem_size,
8295
+                        );
8296
+
8297
+                        let shape_vals = c_f_pointer_shape_values(args);
8298
+                        let rank = shape_vals.map_or(0, |vals| vals.len());
8299
+                        let rank_val = b.const_i32(rank as i32);
8300
+                        store_byte_aggregate_field(
8301
+                            b,
8302
+                            target_addr,
8303
+                            16,
8304
+                            IrType::Int(IntWidth::I32),
8305
+                            rank_val,
8306
+                        );
8307
+
8308
+                        let null_cptr = b.const_i64(0);
8309
+                        let is_associated = b.icmp(CmpOp::Ne, cptr, null_cptr);
8310
+                        let assoc_flag = b.const_i32(2);
8311
+                        let disassoc_flag = b.const_i32(0);
8312
+                        let flags = b.select(is_associated, assoc_flag, disassoc_flag);
8313
+                        store_byte_aggregate_field(
8314
+                            b,
8315
+                            target_addr,
8316
+                            20,
8317
+                            IrType::Int(IntWidth::I32),
8318
+                            flags,
8319
+                        );
8320
+
8321
+                        if let Some(values) = shape_vals {
8322
+                            for (i, value) in values.iter().enumerate() {
8323
+                                let crate::ast::expr::AcValue::Expr(extent_expr) = value else {
8324
+                                    continue;
8325
+                                };
8326
+                                let raw_extent = lower_expr_ctx(b, ctx, extent_expr);
8327
+                                let extent = match b.func().value_type(raw_extent) {
8328
+                                    Some(IrType::Int(IntWidth::I64)) => raw_extent,
8329
+                                    _ => b.int_extend(raw_extent, IntWidth::I64, true),
8330
+                                };
8331
+                                let base = 24 + (i as i64) * 24;
8332
+                                let one64 = b.const_i64(1);
8333
+                                store_byte_aggregate_field(
8334
+                                    b,
8335
+                                    target_addr,
8336
+                                    base,
8337
+                                    IrType::Int(IntWidth::I64),
8338
+                                    one64,
8339
+                                );
8340
+                                store_byte_aggregate_field(
8341
+                                    b,
8342
+                                    target_addr,
8343
+                                    base + 8,
8344
+                                    IrType::Int(IntWidth::I64),
8345
+                                    extent,
8346
+                                );
8347
+                                let stride64 = b.const_i64(1);
8348
+                                store_byte_aggregate_field(
8349
+                                    b,
8350
+                                    target_addr,
8351
+                                    base + 16,
8352
+                                    IrType::Int(IntWidth::I64),
8353
+                                    stride64,
8354
+                                );
8355
+                            }
8356
+                        }
8357
+                        return true;
8358
+                    }
8359
+
8360
+                    let ptr_val = b.int_to_ptr(cptr, elem_ty);
8361
+                    b.store(ptr_val, target_addr);
8362
+                    return true;
8363
+                }
8364
+            }
8365
+
78068366
             let fptr = nth_arg_ref(b, ctx, args, 1);
7807
-            // fptr is Ptr<Ptr<T>> (by-ref pointer variable).
7808
-            // We need to store a Ptr<T> into it. Extract T from
7809
-            // the double-pointer type and build Ptr<T> via int_to_ptr.
78108367
             let inner_pointee = b
78118368
                 .func()
78128369
                 .value_type(fptr)
@@ -7833,6 +8390,55 @@ fn lower_intrinsic_subroutine(
78338390
 
78348391
 /// Look up a dummy argument's declared type from the declaration list.
78358392
 /// Returns the IR type for the argument, defaulting to I32 if not found.
8393
+fn c_f_pointer_target(
8394
+    b: &mut FuncBuilder,
8395
+    ctx: &LowerCtx,
8396
+    expr: &crate::ast::expr::SpannedExpr,
8397
+) -> Option<(ValueId, IrType, bool)> {
8398
+    match &expr.node {
8399
+        Expr::Name { name } => {
8400
+            let info = ctx.locals.get(&name.to_lowercase())?;
8401
+            if !info.is_pointer {
8402
+                return None;
8403
+            }
8404
+            if local_uses_array_descriptor(info) {
8405
+                Some((array_descriptor_addr(b, info), info.ty.clone(), true))
8406
+            } else {
8407
+                let addr = if info.by_ref {
8408
+                    b.load(info.addr)
8409
+                } else {
8410
+                    info.addr
8411
+                };
8412
+                Some((addr, info.ty.clone(), false))
8413
+            }
8414
+        }
8415
+        Expr::ComponentAccess { .. } => {
8416
+            let (field_ptr, field) =
8417
+                resolve_component_field_access(b, &ctx.locals, expr, ctx.st, ctx.type_layouts)?;
8418
+            if !field.pointer {
8419
+                return None;
8420
+            }
8421
+            let elem_ty = type_info_to_storage_ir_type(&field.type_info, ctx.type_layouts);
8422
+            Some((field_ptr, elem_ty, field.size == 384))
8423
+        }
8424
+        _ => None,
8425
+    }
8426
+}
8427
+
8428
+fn c_f_pointer_shape_values(
8429
+    args: &[crate::ast::expr::Argument],
8430
+) -> Option<&[crate::ast::expr::AcValue]> {
8431
+    let arg = args.get(2)?;
8432
+    let crate::ast::expr::SectionSubscript::Element(expr) = &arg.value else {
8433
+        return None;
8434
+    };
8435
+    if let Expr::ArrayConstructor { values, .. } = &expr.node {
8436
+        Some(values.as_slice())
8437
+    } else {
8438
+        None
8439
+    }
8440
+}
8441
+
78368442
 /// Determine the CharKind for a dummy argument from its declaration.
78378443
 ///
78388444
 /// Returns `CharKind::Fixed(n)` if the declaration is
@@ -7840,7 +8446,11 @@ fn lower_intrinsic_subroutine(
78408446
 /// dummies (`character(len=*)`) currently return `CharKind::None`
78418447
 /// because the hidden-length ABI parameter that would supply the
78428448
 /// runtime length is not yet implemented.
7843
-fn arg_char_kind_from_decls(arg_name: &str, decls: &[crate::ast::decl::SpannedDecl]) -> CharKind {
8449
+fn arg_char_kind_from_decls(
8450
+    arg_name: &str,
8451
+    decls: &[crate::ast::decl::SpannedDecl],
8452
+    st: &SymbolTable,
8453
+) -> CharKind {
78448454
     let key = arg_name.to_lowercase();
78458455
     for decl in decls {
78468456
         if let Decl::TypeDecl {
@@ -7859,7 +8469,9 @@ fn arg_char_kind_from_decls(arg_name: &str, decls: &[crate::ast::decl::SpannedDe
78598469
                                 return CharKind::Deferred;
78608470
                             }
78618471
                             if let Some(crate::ast::decl::LenSpec::Expr(e)) = &sel.len {
7862
-                                if let Some(n) = eval_const_int_in_scope(e, &HashMap::new()) {
8472
+                                if let Some(n) =
8473
+                                    eval_const_int_in_scope_or_any_scope(e, &HashMap::new(), st)
8474
+                                {
78638475
                                     return CharKind::Fixed(n);
78648476
                                 }
78658477
                             }
@@ -7878,6 +8490,7 @@ fn arg_char_kind_from_decls(arg_name: &str, decls: &[crate::ast::decl::SpannedDe
78788490
 fn arg_runtime_char_len_expr_from_decls(
78798491
     arg_name: &str,
78808492
     decls: &[crate::ast::decl::SpannedDecl],
8493
+    st: &SymbolTable,
78818494
 ) -> Option<crate::ast::expr::SpannedExpr> {
78828495
     let key = arg_name.to_lowercase();
78838496
     for decl in decls {
@@ -7891,7 +8504,9 @@ fn arg_runtime_char_len_expr_from_decls(
78918504
                 if entity.name.to_lowercase() == key {
78928505
                     if let TypeSpec::Character(Some(sel)) = type_spec {
78938506
                         if let Some(crate::ast::decl::LenSpec::Expr(e)) = &sel.len {
7894
-                            if eval_const_int_in_scope(e, &HashMap::new()).is_none() {
8507
+                            if eval_const_int_in_scope_or_any_scope(e, &HashMap::new(), st)
8508
+                                .is_none()
8509
+                            {
78958510
                                 return Some(e.clone());
78968511
                             }
78978512
                         }
@@ -8421,7 +9036,7 @@ fn build_host_ref_params(
84219036
             id: pid,
84229037
             elem_ty,
84239038
             dims: arg_dims_from_decls(hname, host_decls, &host_visible),
8424
-            char_kind: arg_char_kind_from_decls(hname, host_decls),
9039
+            char_kind: arg_char_kind_from_decls(hname, host_decls, st),
84259040
             derived_type: arg_derived_type_name(hname, host_decls),
84269041
             descriptor_arg,
84279042
             allocatable: alloc,
@@ -8686,6 +9301,72 @@ fn callee_return_ir_type(st: &SymbolTable, callee_name: &str) -> Option<IrType>
86869301
     result_type
86879302
 }
86889303
 
9304
+#[derive(Clone, Copy, PartialEq, Eq)]
9305
+enum CharacterReturnAbi {
9306
+    Direct(Option<i64>),
9307
+    HiddenDescriptor,
9308
+}
9309
+
9310
+fn callee_character_return_abi(st: &SymbolTable, callee_name: &str) -> Option<CharacterReturnAbi> {
9311
+    use crate::sema::symtab::{SymbolKind, TypeInfo};
9312
+
9313
+    let key = callee_name.to_ascii_lowercase();
9314
+    let sym = st.scopes.iter().find_map(|scope| scope.symbols.get(&key))?;
9315
+    match sym.kind {
9316
+        SymbolKind::Function
9317
+        | SymbolKind::ExternalProc
9318
+        | SymbolKind::IntrinsicProc
9319
+        | SymbolKind::ProcedurePointer => {}
9320
+        _ => return None,
9321
+    }
9322
+    let TypeInfo::Character { len, .. } = sym.type_info.as_ref()? else {
9323
+        return None;
9324
+    };
9325
+    if sym.attrs.allocatable || sym.attrs.pointer {
9326
+        Some(CharacterReturnAbi::HiddenDescriptor)
9327
+    } else {
9328
+        Some(CharacterReturnAbi::Direct(*len))
9329
+    }
9330
+}
9331
+
9332
+fn local_fixed_char_allocatable_scalar_len(info: &LocalInfo) -> Option<i64> {
9333
+    if !info.allocatable || !info.dims.is_empty() || info.char_kind != CharKind::None {
9334
+        return None;
9335
+    }
9336
+    match &info.ty {
9337
+        IrType::Array(inner, len)
9338
+            if matches!(inner.as_ref(), IrType::Int(IntWidth::I8)) && *len > 1 =>
9339
+        {
9340
+            Some(*len as i64)
9341
+        }
9342
+        _ => None,
9343
+    }
9344
+}
9345
+
9346
+fn local_is_string_scalar(info: &LocalInfo) -> bool {
9347
+    (info.allocatable && info.dims.is_empty() && matches!(info.char_kind, CharKind::Deferred))
9348
+        || local_fixed_char_allocatable_scalar_len(info).is_some()
9349
+}
9350
+
9351
+fn local_is_array_like(info: &LocalInfo) -> bool {
9352
+    (!info.dims.is_empty() || info.allocatable) && !local_is_string_scalar(info)
9353
+}
9354
+
9355
+fn named_expr_callable_character_return_abi(
9356
+    st: &SymbolTable,
9357
+    locals: &HashMap<String, LocalInfo>,
9358
+    callee_name: &str,
9359
+) -> Option<CharacterReturnAbi> {
9360
+    let key = callee_name.to_ascii_lowercase();
9361
+    if locals.contains_key(&key) {
9362
+        let sym = st.scopes.iter().find_map(|scope| scope.symbols.get(&key))?;
9363
+        if sym.kind != crate::sema::symtab::SymbolKind::ProcedurePointer {
9364
+            return None;
9365
+        }
9366
+    }
9367
+    callee_character_return_abi(st, &key)
9368
+}
9369
+
86899370
 /// Check if a dummy argument has the VALUE attribute in its declaration.
86909371
 fn arg_has_value_attr(arg_name: &str, decls: &[crate::ast::decl::SpannedDecl]) -> bool {
86919372
     let key = arg_name.to_lowercase();
@@ -8844,19 +9525,23 @@ fn lower_string_expr_full(
88449525
                         }
88459526
                     }
88469527
                     CharKind::None => {
8847
-                        // Not a character variable — shouldn't happen but fall back.
8848
-                        let val = lower_expr_full(
8849
-                            b,
8850
-                            locals,
8851
-                            expr,
8852
-                            st,
8853
-                            type_layouts,
8854
-                            internal_funcs,
8855
-                            contained_host_refs,
8856
-                            descriptor_params,
8857
-                        );
8858
-                        let zero = b.const_i64(0);
8859
-                        (val, zero)
9528
+                        if let Some((ptr, len)) = char_addr_and_runtime_len(b, expr, locals) {
9529
+                            (ptr, len)
9530
+                        } else {
9531
+                            // Not a character variable — shouldn't happen but fall back.
9532
+                            let val = lower_expr_full(
9533
+                                b,
9534
+                                locals,
9535
+                                expr,
9536
+                                st,
9537
+                                type_layouts,
9538
+                                internal_funcs,
9539
+                                contained_host_refs,
9540
+                                descriptor_params,
9541
+                            );
9542
+                            let zero = b.const_i64(0);
9543
+                            (val, zero)
9544
+                        }
88609545
                     }
88619546
                 }
88629547
             } else {
@@ -9108,7 +9793,8 @@ fn lower_string_expr_full(
91089793
                 if args.len() == 1 {
91099794
                     if let crate::ast::expr::SectionSubscript::Element(_) = &args[0].value {
91109795
                         if let Some(info) = locals.get(&key) {
9111
-                            if info.char_kind != CharKind::None
9796
+                            if (info.char_kind != CharKind::None
9797
+                                || descriptor_backed_runtime_char_array(info))
91129798
                                 && (!info.dims.is_empty() || local_uses_array_descriptor(info))
91139799
                             {
91149800
                                 if let Some(result) =
@@ -9133,7 +9819,8 @@ fn lower_string_expr_full(
91339819
                         } = args[1].value
91349820
                         {
91359821
                             if let Some(info) = locals.get(&key) {
9136
-                                if info.char_kind != CharKind::None
9822
+                                if (info.char_kind != CharKind::None
9823
+                                    || descriptor_backed_runtime_char_array(info))
91379824
                                     && (!info.dims.is_empty() || local_uses_array_descriptor(info))
91389825
                                 {
91399826
                                     if let Some((elem_ptr, elem_len)) =
@@ -9160,13 +9847,61 @@ fn lower_string_expr_full(
91609847
                         }
91619848
                     }
91629849
                 }
9850
+
9851
+                if let Some(ret_abi) = named_expr_callable_character_return_abi(st, locals, &key) {
9852
+                    match ret_abi {
9853
+                        CharacterReturnAbi::HiddenDescriptor => {
9854
+                            let desc =
9855
+                                b.alloca(IrType::Array(Box::new(IrType::Int(IntWidth::I8)), 32));
9856
+                            let zero_i32 = b.const_i32(0);
9857
+                            let size32 = b.const_i64(32);
9858
+                            b.call(
9859
+                                FuncRef::External("memset".into()),
9860
+                                vec![desc, zero_i32, size32],
9861
+                                IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))),
9862
+                            );
9863
+                            emit_named_function_call(
9864
+                                b,
9865
+                                locals,
9866
+                                st,
9867
+                                type_layouts,
9868
+                                internal_funcs,
9869
+                                contained_host_refs,
9870
+                                descriptor_params,
9871
+                                name,
9872
+                                args,
9873
+                                Some(desc),
9874
+                                IrType::Void,
9875
+                            );
9876
+                            return load_string_descriptor_view(b, desc);
9877
+                        }
9878
+                        CharacterReturnAbi::Direct(len) => {
9879
+                            let ptr = emit_named_function_call(
9880
+                                b,
9881
+                                locals,
9882
+                                st,
9883
+                                type_layouts,
9884
+                                internal_funcs,
9885
+                                contained_host_refs,
9886
+                                descriptor_params,
9887
+                                name,
9888
+                                args,
9889
+                                None,
9890
+                                IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))),
9891
+                            );
9892
+                            return (ptr, b.const_i64(len.unwrap_or(0)));
9893
+                        }
9894
+                    }
9895
+                }
91639896
             }
91649897
             if let Expr::ComponentAccess { .. } = &callee.node {
91659898
                 if let Some(tl) = type_layouts {
91669899
                     if let Some(info) = component_array_local_info(b, locals, callee, st, tl) {
91679900
                         if args.len() == 1 {
91689901
                             if let crate::ast::expr::SectionSubscript::Element(_) = &args[0].value {
9169
-                                if info.char_kind != CharKind::None {
9902
+                                if info.char_kind != CharKind::None
9903
+                                    || descriptor_backed_runtime_char_array(&info)
9904
+                                {
91709905
                                     if let Some(result) =
91719906
                                         char_array_element_ptr_and_len(b, locals, &info, args, st)
91729907
                                     {
@@ -9183,7 +9918,9 @@ fn lower_string_expr_full(
91839918
                                     ..
91849919
                                 } = args[1].value
91859920
                                 {
9186
-                                    if info.char_kind != CharKind::None {
9921
+                                    if info.char_kind != CharKind::None
9922
+                                        || descriptor_backed_runtime_char_array(&info)
9923
+                                    {
91879924
                                         if let Some((elem_ptr, elem_len)) =
91889925
                                             char_array_element_ptr_and_len(
91899926
                                                 b,
@@ -9529,6 +10266,14 @@ fn lower_type_spec(ts: &TypeSpec) -> IrType {
952910266
     lower_type_spec_st(ts, None)
953010267
 }
953110268
 
10269
+fn fixed_char_storage_ir_type(len: i64) -> IrType {
10270
+    if len <= 1 {
10271
+        IrType::Int(IntWidth::I8)
10272
+    } else {
10273
+        IrType::Array(Box::new(IrType::Int(IntWidth::I8)), len as u64)
10274
+    }
10275
+}
10276
+
953210277
 fn lower_type_spec_st(ts: &TypeSpec, st: Option<&SymbolTable>) -> IrType {
953310278
     match ts {
953410279
         TypeSpec::Integer(sel) => IrType::int_from_kind(extract_kind_with_st(
@@ -9648,6 +10393,14 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
964810393
                         return;
964910394
                     }
965010395
                     if let Some(info) = ctx.locals.get(&key).cloned() {
10396
+                        if local_is_array_like(&info)
10397
+                            && (info.char_kind != CharKind::None
10398
+                                || descriptor_backed_runtime_char_array(&info))
10399
+                            && local_uses_array_descriptor(&info)
10400
+                        {
10401
+                            lower_array_assign(b, ctx, name, &info, value);
10402
+                            return;
10403
+                        }
965110404
                         match &info.char_kind {
965210405
                             CharKind::Fixed(len) => {
965310406
                                 // Fixed-length character assignment: copy with space padding.
@@ -9698,7 +10451,18 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
969810451
                                 );
969910452
                             }
970010453
                             CharKind::None => {
9701
-                                if !info.dims.is_empty() || info.allocatable {
10454
+                                if local_fixed_char_allocatable_scalar_len(&info).is_some() {
10455
+                                    let (src_ptr, src_len) = lower_string_expr_ctx(b, ctx, value);
10456
+                                    if let Some((dest_ptr, dest_len)) =
10457
+                                        char_addr_and_runtime_len(b, target, &ctx.locals)
10458
+                                    {
10459
+                                        b.call(
10460
+                                            FuncRef::External("afs_assign_char_fixed".into()),
10461
+                                            vec![dest_ptr, dest_len, src_ptr, src_len],
10462
+                                            IrType::Void,
10463
+                                        );
10464
+                                    }
10465
+                                } else if !info.dims.is_empty() || info.allocatable {
970210466
                                     if try_lower_elemental_array_assign(b, ctx, name, &info, value)
970310467
                                     {
970410468
                                         return;
@@ -9821,7 +10585,12 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
982110585
                     if let Expr::Name { name } = &callee.node {
982210586
                         let akey = name.to_lowercase();
982310587
                         if let Some(info) = ctx.locals.get(&akey).cloned() {
9824
-                            if lower_1d_section_assign(b, ctx, &info, args, value) {
10588
+                            let is_scalar_fixed_alloc_char =
10589
+                                local_fixed_char_allocatable_scalar_len(&info).is_some();
10590
+                            if local_is_array_like(&info)
10591
+                                && !is_scalar_fixed_alloc_char
10592
+                                && lower_1d_section_assign(b, ctx, &info, args, value)
10593
+                            {
982510594
                                 return;
982610595
                             }
982710596
                             // Substring LHS: s(lo:hi) = rhs where s is a
@@ -9829,7 +10598,7 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
982910598
                             // pointer+length, get the RHS as (ptr, len), and
983010599
                             // call afs_assign_char_fixed to do the bounded
983110600
                             // copy with space-padding.
9832
-                            if info.char_kind != CharKind::None
10601
+                            if (info.char_kind != CharKind::None || is_scalar_fixed_alloc_char)
983310602
                                 && info.dims.is_empty()
983410603
                                 && args.len() == 1
983510604
                                 && matches!(
@@ -9864,9 +10633,11 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
986410633
                                         );
986510634
                                     }
986610635
                                 }
9867
-                            } else if !info.dims.is_empty() || info.allocatable {
10636
+                            } else if !is_scalar_fixed_alloc_char && local_is_array_like(&info) {
986810637
                                 // Array element assignment: a(i) = val
9869
-                                if matches!(info.char_kind, CharKind::Fixed(_)) {
10638
+                                if info.char_kind != CharKind::None
10639
+                                    || descriptor_backed_runtime_char_array(&info)
10640
+                                {
987010641
                                     lower_char_array_store(
987110642
                                         b,
987210643
                                         &ctx.locals,
@@ -9877,6 +10648,22 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
987710648
                                     );
987810649
                                 } else {
987910650
                                     let arr_val = lower_expr_ctx(b, ctx, value);
10651
+                                    if matches!(
10652
+                                        b.func().value_type(arr_val),
10653
+                                        Some(IrType::Array(inner, 4096))
10654
+                                            if matches!(inner.as_ref(), IrType::Int(IntWidth::I8))
10655
+                                    ) && matches!(info.ty, IrType::Ptr(ref inner) if matches!(inner.as_ref(), IrType::Int(IntWidth::I8)))
10656
+                                    {
10657
+                                        eprintln!(
10658
+                                            "DEBUG suspicious array store target={} dims={:?} alloc={} by_ref={} descriptor={} ty={:?}",
10659
+                                            name,
10660
+                                            info.dims,
10661
+                                            info.allocatable,
10662
+                                            info.by_ref,
10663
+                                            info.descriptor_arg,
10664
+                                            info.ty
10665
+                                        );
10666
+                                    }
988010667
                                     lower_array_store(b, &ctx.locals, &info, args, arr_val, ctx.st);
988110668
                                 }
988210669
                             }
@@ -9889,11 +10676,15 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
988910676
                             ctx.st,
989010677
                             ctx.type_layouts,
989110678
                         ) {
9892
-                            if lower_1d_section_assign(b, ctx, &info, args, value) {
10679
+                            if local_is_array_like(&info)
10680
+                                && lower_1d_section_assign(b, ctx, &info, args, value)
10681
+                            {
989310682
                                 return;
989410683
                             }
9895
-                            if !info.dims.is_empty() || info.allocatable {
9896
-                                if info.char_kind != CharKind::None {
10684
+                            if local_is_array_like(&info) {
10685
+                                if info.char_kind != CharKind::None
10686
+                                    || descriptor_backed_runtime_char_array(&info)
10687
+                                {
989710688
                                     lower_char_array_store(
989810689
                                         b,
989910690
                                         &ctx.locals,
@@ -9978,12 +10769,10 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
997810769
                                         IrType::Void,
997910770
                                     );
998010771
                                 } else if is_deferred_char_component_field(field) {
9981
-                                    let (dest_ptr, dest_len) =
9982
-                                        load_string_descriptor_view(b, field_ptr);
998310772
                                     let (src_ptr, src_len) = lower_string_expr_ctx(b, ctx, value);
998410773
                                     b.call(
9985
-                                        FuncRef::External("afs_assign_char_fixed".into()),
9986
-                                        vec![dest_ptr, dest_len, src_ptr, src_len],
10774
+                                        FuncRef::External("afs_assign_char_deferred".into()),
10775
+                                        vec![field_ptr, src_ptr, src_len],
998710776
                                         IrType::Void,
998810777
                                     );
998910778
                                 } else {
@@ -10189,12 +10978,35 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
1018910978
                     } else {
1019010979
                         resolve_subroutine_call_name(ctx.st, b, name, &key, &arg_vals, callee.span)
1019110980
                     };
10192
-                    if let Some(desc_mask) = ctx
10981
+                    if let Some(value_mask) = callee_value_arg_mask(ctx.st, &resolved_key)
10982
+                        .or_else(|| callee_value_arg_mask(ctx.st, &signature_key))
10983
+                        .or_else(|| callee_value_arg_mask(ctx.st, &key))
10984
+                    {
10985
+                        for (i, a) in args.iter().enumerate() {
10986
+                            if !value_mask.get(i).copied().unwrap_or(false) {
10987
+                                continue;
10988
+                            }
10989
+                            arg_vals[i] = match &a.value {
10990
+                                crate::ast::expr::SectionSubscript::Element(e) => lower_expr_full(
10991
+                                    b,
10992
+                                    &ctx.locals,
10993
+                                    e,
10994
+                                    ctx.st,
10995
+                                    Some(ctx.type_layouts),
10996
+                                    Some(ctx.internal_funcs),
10997
+                                    Some(ctx.contained_host_refs),
10998
+                                    Some(ctx.descriptor_params),
10999
+                                ),
11000
+                                _ => b.const_i32(0),
11001
+                            };
11002
+                        }
11003
+                    }
11004
+                    let desc_mask = ctx
1019311005
                         .descriptor_params
1019411006
                         .get(&resolved_key)
1019511007
                         .or_else(|| ctx.descriptor_params.get(&signature_key))
10196
-                        .or_else(|| ctx.descriptor_params.get(&key))
10197
-                    {
11008
+                        .or_else(|| ctx.descriptor_params.get(&key));
11009
+                    if let Some(desc_mask) = desc_mask {
1019811010
                         for (i, a) in args.iter().enumerate() {
1019911011
                             if !desc_mask.get(i).copied().unwrap_or(false) {
1020011012
                                 continue;
@@ -10213,6 +11025,12 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
1021311025
                             .or_else(|| callee_string_descriptor_arg_mask(ctx.st, &key))
1021411026
                     {
1021511027
                         for (i, a) in args.iter().enumerate() {
11028
+                            if desc_mask
11029
+                                .map(|mask| mask.get(i).copied().unwrap_or(false))
11030
+                                .unwrap_or(false)
11031
+                            {
11032
+                                continue;
11033
+                            }
1021611034
                             if !string_desc_mask.get(i).copied().unwrap_or(false) {
1021711035
                                 continue;
1021811036
                             }
@@ -10758,7 +11576,11 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
1075811576
             b.unreachable();
1075911577
         }
1076011578
 
10761
-        Stmt::Allocate { items, opts } => {
11579
+        Stmt::Allocate {
11580
+            type_spec,
11581
+            items,
11582
+            opts,
11583
+        } => {
1076211584
             // Resolve STAT= option: find the user's stat variable address.
1076311585
             // The runtime writes 0 on success or a nonzero error code to this slot.
1076411586
             // If absent, use a private scratch slot (allocation failure aborts).
@@ -10781,37 +11603,149 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
1078111603
                     } else {
1078211604
                         b.alloca(IrType::Int(IntWidth::I32))
1078311605
                     }
10784
-                } else {
10785
-                    b.alloca(IrType::Int(IntWidth::I32))
11606
+                } else {
11607
+                    b.alloca(IrType::Int(IntWidth::I32))
11608
+                }
11609
+            };
11610
+            let typed_char_len =
11611
+                typed_allocate_char_len(b, &ctx.locals, type_spec.as_ref(), ctx.st);
11612
+
11613
+            for item in items {
11614
+                if let Expr::FunctionCall { callee, args } = &item.node {
11615
+                    if let Expr::ComponentAccess { .. } = &callee.node {
11616
+                        if let Some((field_ptr, field)) = resolve_component_field_access(
11617
+                            b,
11618
+                            &ctx.locals,
11619
+                            callee,
11620
+                            ctx.st,
11621
+                            ctx.type_layouts,
11622
+                        ) {
11623
+                            if matches!(field_char_kind(&field), CharKind::Deferred)
11624
+                                && field.size == 32
11625
+                            {
11626
+                                let Some(len_val) = typed_char_len else {
11627
+                                    eprintln!(
11628
+                                        "armfortas: error: {}:{}: deferred-length character ALLOCATE requires a typed length or SOURCE/MOLD support",
11629
+                                        stmt.span.start.line, stmt.span.start.col
11630
+                                    );
11631
+                                    let _ = std::io::stderr().flush();
11632
+                                    std::process::exit(1);
11633
+                                };
11634
+                                init_allocated_string_descriptor(b, field_ptr, len_val);
11635
+                                continue;
11636
+                            }
11637
+                            if field.size == 384 && (field.allocatable || field.pointer) {
11638
+                                let elem_ty = type_info_to_storage_ir_type(
11639
+                                    &field.type_info,
11640
+                                    ctx.type_layouts,
11641
+                                );
11642
+                                let elem_size_bytes = descriptor_element_size_bytes(&LocalInfo {
11643
+                                    addr: field_ptr,
11644
+                                    ty: elem_ty.clone(),
11645
+                                    dims: vec![],
11646
+                                    allocatable: true,
11647
+                                    descriptor_arg: false,
11648
+                                    by_ref: false,
11649
+                                    char_kind: field_char_kind(&field),
11650
+                                    derived_type: field_derived_type_name(&field),
11651
+                                    inline_const: None,
11652
+                                    is_pointer: field.pointer,
11653
+                                    runtime_dim_upper: vec![],
11654
+                                });
11655
+                                let es = if matches!(
11656
+                                    field.type_info,
11657
+                                    crate::sema::symtab::TypeInfo::Character { len: None, .. }
11658
+                                ) {
11659
+                                    typed_char_len.unwrap_or_else(|| b.const_i64(elem_size_bytes))
11660
+                                } else {
11661
+                                    b.const_i64(elem_size_bytes)
11662
+                                };
11663
+                                let rank = args.len();
11664
+                                let one_i64 = b.const_i64(1);
11665
+                                let dim_buf = if rank == 0 {
11666
+                                    b.const_i64(0)
11667
+                                } else {
11668
+                                    let dim_buf_bytes = (rank * 24) as u64;
11669
+                                    let dim_buf = b.alloca(IrType::Array(
11670
+                                        Box::new(IrType::Int(IntWidth::I8)),
11671
+                                        dim_buf_bytes,
11672
+                                    ));
11673
+                                    for (i, arg) in args.iter().enumerate() {
11674
+                                        let (lo64, up64) = lower_alloc_bounds(b, ctx, &arg.value);
11675
+                                        let base = (i * 24) as i64;
11676
+                                        let off_lo = b.const_i64(base);
11677
+                                        let off_up = b.const_i64(base + 8);
11678
+                                        let off_st = b.const_i64(base + 16);
11679
+                                        let p_lo =
11680
+                                            b.gep(dim_buf, vec![off_lo], IrType::Int(IntWidth::I8));
11681
+                                        let p_up =
11682
+                                            b.gep(dim_buf, vec![off_up], IrType::Int(IntWidth::I8));
11683
+                                        let p_st =
11684
+                                            b.gep(dim_buf, vec![off_st], IrType::Int(IntWidth::I8));
11685
+                                        b.store(lo64, p_lo);
11686
+                                        b.store(up64, p_up);
11687
+                                        b.store(one_i64, p_st);
11688
+                                    }
11689
+                                    dim_buf
11690
+                                };
11691
+                                let rank_val = b.const_i32(rank as i32);
11692
+                                b.call(
11693
+                                    FuncRef::External("afs_allocate_array".into()),
11694
+                                    vec![field_ptr, es, rank_val, dim_buf, stat_addr],
11695
+                                    IrType::Void,
11696
+                                );
11697
+                                continue;
11698
+                            }
11699
+                        }
11700
+                    }
1078611701
                 }
10787
-            };
10788
-
10789
-            for item in items {
10790
-                if let Expr::FunctionCall { callee, args } = &item.node {
10791
-                    let base_name = extract_base_name(callee);
10792
-                    if let Some(name) = base_name {
10793
-                        if let Some(info) = ctx.locals.get(&name.to_lowercase()).cloned() {
10794
-                            let elem_size_bytes: i64 = match &info.ty {
10795
-                                IrType::Int(IntWidth::I64) | IrType::Float(FloatWidth::F64) => 8,
10796
-                                IrType::Int(IntWidth::I128) => 16,
10797
-                                IrType::Int(IntWidth::I32) | IrType::Float(FloatWidth::F32) => 4,
10798
-                                IrType::Bool => 4,
10799
-                                _ => 8,
11702
+                let (base_name, args): (Option<String>, &[crate::ast::expr::Argument]) =
11703
+                    match &item.node {
11704
+                        Expr::FunctionCall { callee, args } => (extract_base_name(callee), args),
11705
+                        Expr::Name { name } => (Some(name.clone()), &[]),
11706
+                        _ => (None, &[]),
11707
+                    };
11708
+                if let Some(name) = base_name {
11709
+                    if let Some(info) = ctx.locals.get(&name.to_lowercase()).cloned() {
11710
+                        if matches!(info.char_kind, CharKind::Deferred) {
11711
+                            let Some(len_val) = typed_char_len else {
11712
+                                eprintln!(
11713
+                                    "armfortas: error: {}:{}: deferred-length character ALLOCATE requires a typed length or SOURCE/MOLD support",
11714
+                                    stmt.span.start.line, stmt.span.start.col
11715
+                                );
11716
+                                let _ = std::io::stderr().flush();
11717
+                                std::process::exit(1);
1080011718
                             };
10801
-
10802
-                            if info.allocatable {
10803
-                                // Build a stack DimDescriptor[rank] honoring
10804
-                                // each subscript's actual (lower, upper) bounds,
10805
-                                // then call afs_allocate_array. Both 1-D and
10806
-                                // multi-D go through the same path now.
10807
-                                let es = b.const_i64(elem_size_bytes);
10808
-                                let rank = args.len();
11719
+                            let desc = string_descriptor_addr(b, &info);
11720
+                            init_allocated_string_descriptor(b, desc, len_val);
11721
+                            continue;
11722
+                        }
11723
+                        let elem_size_bytes = descriptor_element_size_bytes(&info);
11724
+
11725
+                        if info.allocatable || info.descriptor_arg {
11726
+                            // Build a stack DimDescriptor[rank] honoring
11727
+                            // each subscript's actual (lower, upper) bounds,
11728
+                            // then call afs_allocate_array. Descriptor-backed
11729
+                            // dummy arrays use the caller-owned descriptor
11730
+                            // rather than the local spill slot that holds its
11731
+                            // address. Scalar allocatables lower as a rank-0
11732
+                            // descriptor allocation.
11733
+                            let es = if descriptor_backed_runtime_char_array(&info) {
11734
+                                typed_char_len.unwrap_or_else(|| b.const_i64(elem_size_bytes))
11735
+                            } else {
11736
+                                b.const_i64(elem_size_bytes)
11737
+                            };
11738
+                            let desc = array_descriptor_addr(b, &info);
11739
+                            let rank = args.len();
11740
+                            let one_i64 = b.const_i64(1);
11741
+                            let dim_buf = if rank == 0 {
11742
+                                b.const_i64(0)
11743
+                            } else {
1080911744
                                 let dim_buf_bytes = (rank * 24) as u64;
1081011745
                                 let dim_buf = b.alloca(IrType::Array(
1081111746
                                     Box::new(IrType::Int(IntWidth::I8)),
1081211747
                                     dim_buf_bytes,
1081311748
                                 ));
10814
-                                let one_i64 = b.const_i64(1);
1081511749
                                 for (i, arg) in args.iter().enumerate() {
1081611750
                                     let (lo64, up64) = lower_alloc_bounds(b, ctx, &arg.value);
1081711751
                                     let base = (i * 24) as i64;
@@ -10828,22 +11762,23 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
1082811762
                                     b.store(up64, p_up);
1082911763
                                     b.store(one_i64, p_st);
1083011764
                                 }
10831
-                                let rank_val = b.const_i32(rank as i32);
10832
-                                b.call(
10833
-                                    FuncRef::External("afs_allocate_array".into()),
10834
-                                    vec![info.addr, es, rank_val, dim_buf, stat_addr],
10835
-                                    IrType::Void,
10836
-                                );
10837
-                            } else {
10838
-                                // Non-allocatable array: old path (shouldn't happen for ALLOCATE).
10839
-                                let size_val = b.const_i32(elem_size_bytes as i32);
10840
-                                let ptr = b.runtime_call(
10841
-                                    RuntimeFunc::Allocate,
10842
-                                    vec![size_val],
10843
-                                    IrType::Ptr(Box::new(info.ty.clone())),
10844
-                                );
10845
-                                b.store(ptr, info.addr);
10846
-                            }
11765
+                                dim_buf
11766
+                            };
11767
+                            let rank_val = b.const_i32(rank as i32);
11768
+                            b.call(
11769
+                                FuncRef::External("afs_allocate_array".into()),
11770
+                                vec![desc, es, rank_val, dim_buf, stat_addr],
11771
+                                IrType::Void,
11772
+                            );
11773
+                        } else {
11774
+                            // Non-allocatable array: old path (shouldn't happen for ALLOCATE).
11775
+                            let size_val = b.const_i32(elem_size_bytes as i32);
11776
+                            let ptr = b.runtime_call(
11777
+                                RuntimeFunc::Allocate,
11778
+                                vec![size_val],
11779
+                                IrType::Ptr(Box::new(info.ty.clone())),
11780
+                            );
11781
+                            b.store(ptr, info.addr);
1084711782
                         }
1084811783
                     }
1084911784
                 }
@@ -10852,16 +11787,44 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
1085211787
 
1085311788
         Stmt::Deallocate { items, .. } => {
1085411789
             for item in items {
11790
+                if let Expr::ComponentAccess { .. } = &item.node {
11791
+                    if let Some((field_ptr, field)) = resolve_component_field_access(
11792
+                        b,
11793
+                        &ctx.locals,
11794
+                        item,
11795
+                        ctx.st,
11796
+                        ctx.type_layouts,
11797
+                    ) {
11798
+                        if is_deferred_char_component_field(&field) {
11799
+                            b.call(
11800
+                                FuncRef::External("afs_dealloc_string".into()),
11801
+                                vec![field_ptr],
11802
+                                IrType::Void,
11803
+                            );
11804
+                            continue;
11805
+                        }
11806
+                        if field.size == 384 && (field.allocatable || field.pointer) {
11807
+                            let stat_slot = b.alloca(IrType::Int(IntWidth::I32));
11808
+                            b.call(
11809
+                                FuncRef::External("afs_deallocate_array".into()),
11810
+                                vec![field_ptr, stat_slot],
11811
+                                IrType::Void,
11812
+                            );
11813
+                            continue;
11814
+                        }
11815
+                    }
11816
+                }
1085511817
                 let base_name = extract_base_name(item);
1085611818
                 if let Some(name) = base_name {
1085711819
                     if let Some(info) = ctx.locals.get(&name.to_lowercase()) {
10858
-                        if info.allocatable {
11820
+                        if info.allocatable || info.descriptor_arg {
1085911821
                             // Pass descriptor address to runtime with null STAT.
1086011822
                             // Alloca a dummy STAT to avoid abort on already-deallocated.
1086111823
                             let stat_slot = b.alloca(IrType::Int(IntWidth::I32));
11824
+                            let desc = array_descriptor_addr(b, info);
1086211825
                             b.call(
1086311826
                                 FuncRef::External("afs_deallocate_array".into()),
10864
-                                vec![info.addr, stat_slot],
11827
+                                vec![desc, stat_slot],
1086511828
                                 IrType::Void,
1086611829
                             );
1086711830
                         } else {
@@ -11674,6 +12637,47 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
1167412637
             if !tgt_info.is_pointer {
1167512638
                 return;
1167612639
             }
12640
+            if matches!(tgt_info.char_kind, CharKind::Deferred) {
12641
+                let tgt_desc = string_descriptor_addr(b, &tgt_info);
12642
+                if let Expr::FunctionCall { callee, .. } = &value.node {
12643
+                    if let Expr::Name { name } = &callee.node {
12644
+                        if name.eq_ignore_ascii_case("null") {
12645
+                            let zero = b.const_i64(0);
12646
+                            let null = b.int_to_ptr(zero, IrType::Int(IntWidth::I8));
12647
+                            store_string_descriptor_view(b, tgt_desc, null, zero);
12648
+                            return;
12649
+                        }
12650
+                    }
12651
+                }
12652
+                if let Some((src_field_ptr, src_field)) =
12653
+                    resolve_component_field_access(b, &ctx.locals, value, ctx.st, ctx.type_layouts)
12654
+                {
12655
+                    if is_deferred_char_component_field(&src_field) {
12656
+                        let (ptr, len) = load_string_descriptor_view(b, src_field_ptr);
12657
+                        store_string_descriptor_view(b, tgt_desc, ptr, len);
12658
+                        return;
12659
+                    }
12660
+                }
12661
+                if let Expr::Name { name: src_name } = &value.node {
12662
+                    if let Some(src_info) = ctx.locals.get(&src_name.to_lowercase()) {
12663
+                        if matches!(src_info.char_kind, CharKind::Deferred) {
12664
+                            let src_desc = string_descriptor_addr(b, src_info);
12665
+                            let (ptr, len) = load_string_descriptor_view(b, src_desc);
12666
+                            store_string_descriptor_view(b, tgt_desc, ptr, len);
12667
+                            return;
12668
+                        }
12669
+                    }
12670
+                }
12671
+                let (ptr, len) = lower_string_expr_with_layouts(
12672
+                    b,
12673
+                    &ctx.locals,
12674
+                    value,
12675
+                    ctx.st,
12676
+                    Some(ctx.type_layouts),
12677
+                );
12678
+                store_string_descriptor_view(b, tgt_desc, ptr, len);
12679
+                return;
12680
+            }
1167712681
 
1167812682
             // Handle section-RHS: pa => ia(lo:hi).  The RHS is a
1167912683
             // FunctionCall{Name(arr), [Range(lo,hi)]}.  Build a
@@ -12483,9 +13487,7 @@ fn lower_array_element(
1248313487
     args: &[crate::ast::expr::Argument],
1248413488
     st: &SymbolTable,
1248513489
 ) -> ValueId {
12486
-    let idx64 = compute_flat_elem_offset(b, locals, info, args, st);
12487
-    let base = array_base_addr(b, info);
12488
-    let elem_ptr = b.gep(base, vec![idx64], info.ty.clone());
13490
+    let elem_ptr = lower_array_element_addr(b, locals, info, args, st);
1248913491
     if info.derived_type.is_some() {
1249013492
         elem_ptr
1249113493
     } else {
@@ -12493,6 +13495,18 @@ fn lower_array_element(
1249313495
     }
1249413496
 }
1249513497
 
13498
+fn lower_array_element_addr(
13499
+    b: &mut FuncBuilder,
13500
+    locals: &HashMap<String, LocalInfo>,
13501
+    info: &LocalInfo,
13502
+    args: &[crate::ast::expr::Argument],
13503
+    st: &SymbolTable,
13504
+) -> ValueId {
13505
+    let idx64 = compute_flat_elem_offset(b, locals, info, args, st);
13506
+    let base = array_base_addr(b, info);
13507
+    b.gep(base, vec![idx64], info.ty.clone())
13508
+}
13509
+
1249613510
 fn emit_bounds_check(b: &mut FuncBuilder, index: ValueId, lower: ValueId, upper: ValueId) {
1249713511
     b.runtime_call(
1249813512
         RuntimeFunc::CheckBounds,
@@ -12767,6 +13781,7 @@ fn lower_alloc_bounds(
1276713781
 /// over adjacent elements).
1276813782
 fn ir_scalar_byte_size(ty: &IrType) -> i64 {
1276913783
     match ty {
13784
+        IrType::Array(elem, count) => (elem.size_bytes() * count) as i64,
1277013785
         IrType::Int(IntWidth::I8) | IrType::Bool => 1,
1277113786
         IrType::Int(IntWidth::I16) => 2,
1277213787
         IrType::Int(IntWidth::I32) | IrType::Float(FloatWidth::F32) => 4,
@@ -12776,6 +13791,19 @@ fn ir_scalar_byte_size(ty: &IrType) -> i64 {
1277613791
     }
1277713792
 }
1277813793
 
13794
+fn descriptor_element_size_bytes(info: &LocalInfo) -> i64 {
13795
+    match &info.ty {
13796
+        IrType::Array(_, _) => info.ty.size_bytes() as i64,
13797
+        IrType::Int(IntWidth::I8) => 1,
13798
+        IrType::Int(IntWidth::I16) => 2,
13799
+        IrType::Int(IntWidth::I32) | IrType::Float(FloatWidth::F32) => 4,
13800
+        IrType::Int(IntWidth::I64) | IrType::Float(FloatWidth::F64) => 8,
13801
+        IrType::Int(IntWidth::I128) => 16,
13802
+        IrType::Bool => 4,
13803
+        _ => 8,
13804
+    }
13805
+}
13806
+
1277913807
 /// Store the literal values of an array constructor into a
1278013808
 /// destination buffer, one element at a time via byte-level GEP.
1278113809
 ///
@@ -13173,7 +14201,7 @@ fn lower_write_items_adv(
1317314201
                         );
1317414202
                         continue;
1317514203
                     }
13176
-                    if !info.dims.is_empty() || info.allocatable {
14204
+                    if local_is_array_like(&info) {
1317714205
                         lower_whole_array_write(b, ctx, &info, unit);
1317814206
                         continue;
1317914207
                     }
@@ -13211,7 +14239,7 @@ fn lower_write_items_adv(
1321114239
                 if let Expr::Name { name } = &callee.node {
1321214240
                     let key = name.to_lowercase();
1321314241
                     if let Some(info) = ctx.locals.get(&key).cloned() {
13214
-                        if !info.dims.is_empty() || info.allocatable {
14242
+                        if local_is_array_like(&info) {
1321514243
                             let has_range = args.iter().any(|a| {
1321614244
                                 matches!(a.value, crate::ast::expr::SectionSubscript::Range { .. })
1321714245
                             });
@@ -13971,7 +14999,7 @@ fn lower_read_target_addr(
1397114999
         Expr::Name { name } => {
1397215000
             let key = name.to_lowercase();
1397315001
             let info = ctx.locals.get(&key)?;
13974
-            if !info.dims.is_empty() || info.allocatable {
15002
+            if local_is_array_like(info) {
1397515003
                 return None;
1397615004
             }
1397715005
             let addr = if info.by_ref {
@@ -15177,7 +16205,7 @@ fn whole_array_expr_info(
1517716205
     let key = name.to_lowercase();
1517816206
     locals
1517916207
         .get(&key)
15180
-        .filter(|info| !info.dims.is_empty() || info.allocatable)
16208
+        .filter(|info| local_is_array_like(info))
1518116209
         .cloned()
1518216210
 }
1518316211
 
@@ -15191,7 +16219,7 @@ fn whole_array_named_info(
1519116219
     let key = name.to_lowercase();
1519216220
     let info = locals
1519316221
         .get(&key)
15194
-        .filter(|info| !info.dims.is_empty() || info.allocatable)
16222
+        .filter(|info| local_is_array_like(info))
1519516223
         .cloned()?;
1519616224
     Some((key, info))
1519716225
 }
@@ -15644,7 +16672,7 @@ fn lower_array_expr_descriptor(
1564416672
         Expr::ParenExpr { inner } => lower_array_expr_descriptor(b, locals, inner, st),
1564516673
         Expr::Name { name } => {
1564616674
             let info = locals.get(&name.to_lowercase())?;
15647
-            if !info.dims.is_empty() || local_uses_array_descriptor(info) {
16675
+            if local_is_array_like(info) {
1564816676
                 let desc = if local_uses_array_descriptor(info) {
1564916677
                     array_descriptor_addr(b, info)
1565016678
                 } else {
@@ -15660,7 +16688,7 @@ fn lower_array_expr_descriptor(
1566016688
                 return None;
1566116689
             };
1566216690
             let info = locals.get(&name.to_lowercase())?;
15663
-            if (!info.dims.is_empty() || local_uses_array_descriptor(info))
16691
+            if local_is_array_like(info)
1566416692
                 && args.iter().any(|arg| {
1566516693
                     matches!(arg.value, crate::ast::expr::SectionSubscript::Range { .. })
1566616694
                 })
@@ -15689,41 +16717,25 @@ fn lower_1d_section_assign(
1568916717
             dest_args[0].value,
1569016718
             crate::ast::expr::SectionSubscript::Range { .. }
1569116719
         )
15692
-        || !matches!(dest_info.char_kind, CharKind::None)
1569316720
     {
1569416721
         return false;
1569516722
     }
1569616723
 
1569716724
     let dest_desc = lower_array_section(b, &ctx.locals, dest_info, dest_args, ctx.st);
15698
-    let dest_base = b.load_typed(dest_desc, IrType::Ptr(Box::new(dest_info.ty.clone())));
1569916725
     let dest_n = b.call(
1570016726
         FuncRef::External("afs_array_size".into()),
1570116727
         vec![dest_desc],
1570216728
         IrType::Int(IntWidth::I64),
1570316729
     );
1570416730
     let dest_stride = load_array_desc_i64_field(b, dest_desc, 24 + 16);
15705
-    let elem_bytes = b.const_i64(ir_scalar_byte_size(&dest_info.ty));
15706
-
1570716731
     let src_desc = lower_array_expr_descriptor(b, &ctx.locals, value, ctx.st);
15708
-    let scalar = src_desc.is_none().then(|| {
15709
-        let raw = lower_expr_ctx_tl(b, ctx, value);
15710
-        coerce_to_type(b, raw, &dest_info.ty)
15711
-    });
15712
-
15713
-    let (src_base, src_stride, src_n, src_ty) = if let Some((desc, ty)) = src_desc {
15714
-        (
15715
-            Some(b.load_typed(desc, IrType::Ptr(Box::new(ty.clone())))),
15716
-            Some(load_array_desc_i64_field(b, desc, 24 + 16)),
15717
-            Some(b.call(
15718
-                FuncRef::External("afs_array_size".into()),
15719
-                vec![desc],
15720
-                IrType::Int(IntWidth::I64),
15721
-            )),
15722
-            Some(ty),
16732
+    let src_n = src_desc.as_ref().map(|(desc, _)| {
16733
+        b.call(
16734
+            FuncRef::External("afs_array_size".into()),
16735
+            vec![*desc],
16736
+            IrType::Int(IntWidth::I64),
1572316737
         )
15724
-    } else {
15725
-        (None, None, None, None)
15726
-    };
16738
+    });
1572716739
 
1572816740
     let i_addr = b.alloca(IrType::Int(IntWidth::I64));
1572916741
     let zero64 = b.const_i64(0);
@@ -15745,22 +16757,63 @@ fn lower_1d_section_assign(
1574516757
 
1574616758
     b.set_block(bb_body);
1574716759
     let i_val = b.load(i_addr);
15748
-    let dest_index = b.imul(i_val, dest_stride);
15749
-    let dest_off = b.imul(dest_index, elem_bytes);
15750
-    let dest_ptr = b.gep(dest_base, vec![dest_off], IrType::Int(IntWidth::I8));
1575116760
 
15752
-    let stored = if let (Some(src_base), Some(src_stride), Some(src_ty)) =
15753
-        (src_base, src_stride, src_ty.clone())
15754
-    {
15755
-        let src_index = b.imul(i_val, src_stride);
15756
-        let src_off = b.imul(src_index, elem_bytes);
15757
-        let src_ptr = b.gep(src_base, vec![src_off], IrType::Int(IntWidth::I8));
15758
-        let raw = b.load_typed(src_ptr, src_ty);
15759
-        coerce_to_type(b, raw, &dest_info.ty)
16761
+    if dest_info.char_kind != CharKind::None || descriptor_backed_runtime_char_array(dest_info) {
16762
+        let dest_base = b.load_typed(dest_desc, IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))));
16763
+        let dest_elem_len = descriptor_elem_size(b, dest_desc);
16764
+        let dest_index = b.imul(i_val, dest_stride);
16765
+        let dest_off = b.imul(dest_index, dest_elem_len);
16766
+        let dest_ptr = b.gep(dest_base, vec![dest_off], IrType::Int(IntWidth::I8));
16767
+
16768
+        let (src_ptr, src_len) = if let Some((desc, _)) = src_desc.as_ref() {
16769
+            let src_base = b.load_typed(*desc, IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))));
16770
+            let src_stride = load_array_desc_i64_field(b, *desc, 24 + 16);
16771
+            let src_index = b.imul(i_val, src_stride);
16772
+            let src_elem_len = descriptor_elem_size(b, *desc);
16773
+            let src_off = b.imul(src_index, src_elem_len);
16774
+            let src_ptr = b.gep(src_base, vec![src_off], IrType::Int(IntWidth::I8));
16775
+            (src_ptr, src_elem_len)
16776
+        } else {
16777
+            lower_string_expr_ctx(b, ctx, value)
16778
+        };
16779
+        b.call(
16780
+            FuncRef::External("afs_assign_char_fixed".into()),
16781
+            vec![dest_ptr, dest_elem_len, src_ptr, src_len],
16782
+            IrType::Void,
16783
+        );
1576016784
     } else {
15761
-        scalar.expect("scalar slice assignment should have scalar RHS")
15762
-    };
15763
-    b.store(stored, dest_ptr);
16785
+        let dest_base = b.load_typed(dest_desc, IrType::Ptr(Box::new(dest_info.ty.clone())));
16786
+        let elem_bytes = b.const_i64(ir_scalar_byte_size(&dest_info.ty));
16787
+        let scalar = src_desc.is_none().then(|| {
16788
+            let raw = lower_expr_ctx_tl(b, ctx, value);
16789
+            coerce_to_type(b, raw, &dest_info.ty)
16790
+        });
16791
+        let (src_base, src_stride, src_ty) = if let Some((desc, ty)) = src_desc.as_ref() {
16792
+            (
16793
+                Some(b.load_typed(*desc, IrType::Ptr(Box::new(ty.clone())))),
16794
+                Some(load_array_desc_i64_field(b, *desc, 24 + 16)),
16795
+                Some(ty.clone()),
16796
+            )
16797
+        } else {
16798
+            (None, None, None)
16799
+        };
16800
+
16801
+        let dest_index = b.imul(i_val, dest_stride);
16802
+        let dest_off = b.imul(dest_index, elem_bytes);
16803
+        let dest_ptr = b.gep(dest_base, vec![dest_off], IrType::Int(IntWidth::I8));
16804
+        let stored = if let (Some(src_base), Some(src_stride), Some(src_ty)) =
16805
+            (src_base, src_stride, src_ty)
16806
+        {
16807
+            let src_index = b.imul(i_val, src_stride);
16808
+            let src_off = b.imul(src_index, elem_bytes);
16809
+            let src_ptr = b.gep(src_base, vec![src_off], IrType::Int(IntWidth::I8));
16810
+            let raw = b.load_typed(src_ptr, src_ty);
16811
+            coerce_to_type(b, raw, &dest_info.ty)
16812
+        } else {
16813
+            scalar.expect("scalar slice assignment should have scalar RHS")
16814
+        };
16815
+        b.store(stored, dest_ptr);
16816
+    }
1576416817
 
1576516818
     let one = b.const_i64(1);
1576616819
     let next_i = b.iadd(i_val, one);
@@ -16053,6 +17106,77 @@ fn lower_array_assign(
1605317106
         return;
1605417107
     }
1605517108
 
17109
+    if (dest_info.char_kind != CharKind::None || descriptor_backed_runtime_char_array(dest_info))
17110
+        && local_uses_array_descriptor(dest_info)
17111
+    {
17112
+        let dest_desc = array_descriptor_addr(b, dest_info);
17113
+        let dest_base = b.load_typed(dest_desc, IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))));
17114
+        let dest_n = b.call(
17115
+            FuncRef::External("afs_array_size".into()),
17116
+            vec![dest_desc],
17117
+            IrType::Int(IntWidth::I64),
17118
+        );
17119
+        let dest_stride = load_array_desc_i64_field(b, dest_desc, 24 + 16);
17120
+        let dest_elem_len = descriptor_elem_size(b, dest_desc);
17121
+        let src_desc = lower_array_expr_descriptor(b, &ctx.locals, value, ctx.st);
17122
+        let src_n = src_desc.as_ref().map(|(desc, _)| {
17123
+            b.call(
17124
+                FuncRef::External("afs_array_size".into()),
17125
+                vec![*desc],
17126
+                IrType::Int(IntWidth::I64),
17127
+            )
17128
+        });
17129
+
17130
+        let i_addr = b.alloca(IrType::Int(IntWidth::I64));
17131
+        let zero = b.const_i64(0);
17132
+        b.store(zero, i_addr);
17133
+
17134
+        let bb_check = b.create_block("char_array_assign_check");
17135
+        let bb_body = b.create_block("char_array_assign_body");
17136
+        let bb_exit = b.create_block("char_array_assign_exit");
17137
+        b.branch(bb_check, vec![]);
17138
+
17139
+        b.set_block(bb_check);
17140
+        let i = b.load(i_addr);
17141
+        let mut done = b.icmp(CmpOp::Ge, i, dest_n);
17142
+        if let Some(src_len) = src_n {
17143
+            let src_done = b.icmp(CmpOp::Ge, i, src_len);
17144
+            done = b.or(done, src_done);
17145
+        }
17146
+        b.cond_branch(done, bb_exit, vec![], bb_body, vec![]);
17147
+
17148
+        b.set_block(bb_body);
17149
+        let i_val = b.load(i_addr);
17150
+        let dest_index = b.imul(i_val, dest_stride);
17151
+        let dest_off = b.imul(dest_index, dest_elem_len);
17152
+        let dest_ptr = b.gep(dest_base, vec![dest_off], IrType::Int(IntWidth::I8));
17153
+
17154
+        let (src_ptr, src_len) = if let Some((desc, _)) = src_desc.as_ref() {
17155
+            let src_base = b.load_typed(*desc, IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))));
17156
+            let src_stride = load_array_desc_i64_field(b, *desc, 24 + 16);
17157
+            let src_index = b.imul(i_val, src_stride);
17158
+            let src_elem_len = descriptor_elem_size(b, *desc);
17159
+            let src_off = b.imul(src_index, src_elem_len);
17160
+            let src_ptr = b.gep(src_base, vec![src_off], IrType::Int(IntWidth::I8));
17161
+            (src_ptr, src_elem_len)
17162
+        } else {
17163
+            lower_string_expr_ctx(b, ctx, value)
17164
+        };
17165
+        b.call(
17166
+            FuncRef::External("afs_assign_char_fixed".into()),
17167
+            vec![dest_ptr, dest_elem_len, src_ptr, src_len],
17168
+            IrType::Void,
17169
+        );
17170
+
17171
+        let one = b.const_i64(1);
17172
+        let next_i = b.iadd(i_val, one);
17173
+        b.store(next_i, i_addr);
17174
+        b.branch(bb_check, vec![]);
17175
+
17176
+        b.set_block(bb_exit);
17177
+        return;
17178
+    }
17179
+
1605617180
     if try_lower_elemental_array_assign(b, ctx, dest_name, dest_info, value) {
1605717181
         return;
1605817182
     }
@@ -16145,7 +17269,7 @@ fn collect_array_names(
1614517269
         Expr::Name { name } => {
1614617270
             let key = name.to_lowercase();
1614717271
             if let Some(info) = locals.get(&key) {
16148
-                if (!info.dims.is_empty() || info.allocatable) && !out.contains(&key) {
17272
+                if local_is_array_like(info) && !out.contains(&key) {
1614917273
                     out.push(key);
1615017274
                 }
1615117275
             }
@@ -16526,7 +17650,7 @@ fn component_intrinsic_local_info(
1652617650
     if field.size == 384 && (field.allocatable || field.pointer) {
1652717651
         return Some(LocalInfo {
1652817652
             addr: field_ptr,
16529
-            ty: type_info_to_ir_type(&field.type_info),
17653
+            ty: type_info_to_storage_ir_type(&field.type_info, tl),
1653017654
             dims: vec![],
1653117655
             allocatable: true,
1653217656
             descriptor_arg: false,
@@ -16543,7 +17667,7 @@ fn component_intrinsic_local_info(
1654317667
     }
1654417668
     Some(LocalInfo {
1654517669
         addr: field_ptr,
16546
-        ty: type_info_to_ir_type(&field.type_info),
17670
+        ty: type_info_to_storage_ir_type(&field.type_info, tl),
1654717671
         dims: field.dims.clone(),
1654817672
         allocatable: false,
1654917673
         descriptor_arg: false,
@@ -16970,6 +18094,29 @@ fn type_info_to_ir_type(ti: &crate::sema::symtab::TypeInfo) -> IrType {
1697018094
     }
1697118095
 }
1697218096
 
18097
+fn derived_storage_ir_type(
18098
+    type_name: &str,
18099
+    tl: &crate::sema::type_layout::TypeLayoutRegistry,
18100
+) -> Option<IrType> {
18101
+    let layout = tl.get(type_name)?;
18102
+    Some(IrType::Array(
18103
+        Box::new(IrType::Int(IntWidth::I8)),
18104
+        layout.size as u64,
18105
+    ))
18106
+}
18107
+
18108
+fn type_info_to_storage_ir_type(
18109
+    ti: &crate::sema::symtab::TypeInfo,
18110
+    tl: &crate::sema::type_layout::TypeLayoutRegistry,
18111
+) -> IrType {
18112
+    if let crate::sema::symtab::TypeInfo::Derived(type_name) = ti {
18113
+        if let Some(storage_ty) = derived_storage_ir_type(type_name, tl) {
18114
+            return storage_ty;
18115
+        }
18116
+    }
18117
+    type_info_to_ir_type(ti)
18118
+}
18119
+
1697318120
 fn lower_fixed_component_array_element_ptr(
1697418121
     b: &mut FuncBuilder,
1697518122
     locals: &HashMap<String, LocalInfo>,
@@ -17033,6 +18180,8 @@ fn resolve_component_base(
1703318180
             // Dereference once to get the struct base.
1703418181
             let addr = if info.is_pointer {
1703518182
                 b.load_typed(info.addr, IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))))
18183
+            } else if info.allocatable {
18184
+                array_base_addr(b, info)
1703618185
             } else if info.by_ref {
1703718186
                 b.load(info.addr)
1703818187
             } else {
@@ -17120,7 +18269,7 @@ fn resolve_component_field_access(
1712018269
 }
1712118270
 
1712218271
 fn is_deferred_char_component_field(field: &crate::sema::type_layout::FieldLayout) -> bool {
17123
-    field.pointer
18272
+    (field.pointer || field.allocatable)
1712418273
         && matches!(
1712518274
             field.type_info,
1712618275
             crate::sema::symtab::TypeInfo::Character { len: None, .. }
@@ -17169,7 +18318,7 @@ fn component_array_local_info(
1716918318
     }
1717018319
     Some(LocalInfo {
1717118320
         addr: field_ptr,
17172
-        ty: type_info_to_ir_type(&field.type_info),
18321
+        ty: type_info_to_storage_ir_type(&field.type_info, tl),
1717318322
         dims: vec![],
1717418323
         allocatable: true,
1717518324
         descriptor_arg: false,
@@ -17193,6 +18342,9 @@ fn expr_is_array_designator(
1719318342
         Expr::Name { name } => locals
1719418343
             .get(&name.to_lowercase())
1719518344
             .map(|info| {
18345
+                if local_fixed_char_allocatable_scalar_len(info).is_some() {
18346
+                    return false;
18347
+                }
1719618348
                 if matches!(info.char_kind, CharKind::Deferred) {
1719718349
                     false
1719818350
                 } else {
@@ -17222,7 +18374,10 @@ fn expr_is_character_expr(
1722218374
         } => true,
1722318375
         Expr::Name { name } => locals
1722418376
             .get(&name.to_lowercase())
17225
-            .map(|info| info.char_kind != CharKind::None)
18377
+            .map(|info| {
18378
+                info.char_kind != CharKind::None
18379
+                    || local_fixed_char_allocatable_scalar_len(info).is_some()
18380
+            })
1722618381
             .unwrap_or(false),
1722718382
         Expr::ComponentAccess { .. } => type_layouts
1722818383
             .and_then(|tl| resolve_component_field_access(b, locals, expr, st, tl))
@@ -17269,10 +18424,13 @@ fn expr_is_character_expr(
1726918424
                             }
1727018425
                         })
1727118426
                         .unwrap_or(false))
18427
+                    || named_expr_callable_character_return_abi(st, locals, &key).is_some()
1727218428
                     || locals
1727318429
                         .get(&key)
1727418430
                         .map(|info| {
17275
-                            info.char_kind != CharKind::None
18431
+                            (info.char_kind != CharKind::None
18432
+                                || descriptor_backed_runtime_char_array(info)
18433
+                                || local_fixed_char_allocatable_scalar_len(info).is_some())
1727618434
                                 && (info.dims.is_empty()
1727718435
                                     || args.iter().all(|arg| {
1727818436
                                         matches!(
@@ -17290,7 +18448,7 @@ fn expr_is_character_expr(
1729018448
                             resolve_component_field_access(b, locals, callee, st, tl).map(
1729118449
                                 |(field_ptr, field)| LocalInfo {
1729218450
                                     addr: field_ptr,
17293
-                                    ty: type_info_to_ir_type(&field.type_info),
18451
+                                    ty: type_info_to_storage_ir_type(&field.type_info, tl),
1729418452
                                     dims: vec![],
1729518453
                                     allocatable: field.allocatable,
1729618454
                                     descriptor_arg: false,
@@ -17304,7 +18462,10 @@ fn expr_is_character_expr(
1730418462
                             )
1730518463
                         })
1730618464
                     })
17307
-                    .map(|info| info.char_kind != CharKind::None)
18465
+                    .map(|info| {
18466
+                        info.char_kind != CharKind::None
18467
+                            || descriptor_backed_runtime_char_array(&info)
18468
+                    })
1730818469
                     .unwrap_or(false)
1730918470
             } else {
1731018471
                 expr_is_character_expr(b, locals, callee, st, type_layouts)
@@ -17328,6 +18489,51 @@ fn store_string_descriptor_view(b: &mut FuncBuilder, desc: ValueId, ptr: ValueId
1732818489
     store_byte_aggregate_field(b, desc, 24, IrType::Int(IntWidth::I32), flags);
1732918490
 }
1733018491
 
18492
+fn init_allocated_string_descriptor(b: &mut FuncBuilder, desc: ValueId, len: ValueId) {
18493
+    let buf = b.runtime_call(
18494
+        RuntimeFunc::Allocate,
18495
+        vec![len],
18496
+        IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))),
18497
+    );
18498
+    let space = b.const_i32(b' ' as i32);
18499
+    b.call(
18500
+        FuncRef::External("memset".into()),
18501
+        vec![buf, space, len],
18502
+        IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))),
18503
+    );
18504
+    store_byte_aggregate_field(
18505
+        b,
18506
+        desc,
18507
+        0,
18508
+        IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))),
18509
+        buf,
18510
+    );
18511
+    store_byte_aggregate_field(b, desc, 8, IrType::Int(IntWidth::I64), len);
18512
+    store_byte_aggregate_field(b, desc, 16, IrType::Int(IntWidth::I64), len);
18513
+    let flags = b.const_i32(3);
18514
+    store_byte_aggregate_field(b, desc, 24, IrType::Int(IntWidth::I32), flags);
18515
+}
18516
+
18517
+fn typed_allocate_char_len(
18518
+    b: &mut FuncBuilder,
18519
+    locals: &HashMap<String, LocalInfo>,
18520
+    type_spec: Option<&TypeSpec>,
18521
+    st: &SymbolTable,
18522
+) -> Option<ValueId> {
18523
+    match type_spec {
18524
+        Some(TypeSpec::Character(None)) => Some(b.const_i64(1)),
18525
+        Some(TypeSpec::Character(Some(sel))) => match &sel.len {
18526
+            Some(crate::ast::decl::LenSpec::Expr(e)) => {
18527
+                let raw_len = lower_expr(b, locals, e, st);
18528
+                Some(clamp_nonnegative_i64(b, raw_len))
18529
+            }
18530
+            None => Some(b.const_i64(1)),
18531
+            _ => None,
18532
+        },
18533
+        _ => None,
18534
+    }
18535
+}
18536
+
1733118537
 /// Resolve a base expression for a type-bound procedure call.
1733218538
 /// Returns (object_address, type_name) — the address of the base object.
1733318539
 /// For simple `obj%method()`, base is `obj` → returns (obj.addr, obj.type).
@@ -17344,7 +18550,9 @@ fn resolve_component_base_for_method(
1734418550
             let key = name.to_lowercase();
1734518551
             let info = locals.get(&key)?;
1734618552
             let type_name = info.derived_type.as_ref()?.clone();
17347
-            let addr = if info.by_ref {
18553
+            let addr = if info.allocatable {
18554
+                array_base_addr(b, info)
18555
+            } else if info.by_ref {
1734818556
                 b.load(info.addr)
1734918557
             } else {
1735018558
                 info.addr
@@ -17487,7 +18695,13 @@ fn lower_char_arg_by_ref(
1748718695
             if !matches!(info.char_kind, CharKind::Fixed(_)) || info.dims.is_empty() {
1748818696
                 return None;
1748918697
             }
17490
-            let (ptr, _len) = char_array_element_ptr_and_len(b, locals, info, args, st)?;
18698
+            let ptr = if local_uses_array_descriptor(info)
18699
+                || matches!(info.ty, IrType::Int(IntWidth::I8))
18700
+            {
18701
+                char_array_element_ptr_and_len(b, locals, info, args, st)?.0
18702
+            } else {
18703
+                lower_array_element(b, locals, info, args, st)
18704
+            };
1749118705
             let slot = b.alloca(IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))));
1749218706
             b.store(ptr, slot);
1749318707
             Some(slot)
@@ -17809,7 +19023,9 @@ fn lower_expr_full(
1780919023
                     // of the struct as if they were a pointer, turning
1781019024
                     // `b = a` into a memcpy from the garbage address held
1781119025
                     // by a's first field slot.
17812
-                    if info.by_ref {
19026
+                    if info.allocatable {
19027
+                        array_base_addr(b, info)
19028
+                    } else if info.by_ref {
1781319029
                         b.load(info.addr)
1781419030
                     } else {
1781519031
                         info.addr
@@ -18151,7 +19367,7 @@ fn lower_expr_full(
1815119367
 
1815219368
                 // Check if this is an array element or section access.
1815319369
                 if let Some(info) = locals.get(&key) {
18154
-                    if !info.dims.is_empty() || info.allocatable {
19370
+                    if local_is_array_like(info) {
1815519371
                         let has_range = args.iter().any(|a| {
1815619372
                             matches!(a.value, crate::ast::expr::SectionSubscript::Range { .. })
1815719373
                         });
@@ -18278,15 +19494,99 @@ fn lower_expr_full(
1827819494
                     return b.const_bool(true);
1827919495
                 }
1828019496
 
18281
-                // c_loc(x) / c_funloc(f): return the address of the argument
18282
-                // as an i64 integer (matching type(c_ptr) which lowers to i64).
18283
-                // Must be handled before generic intrinsic lowering because
18284
-                // intrinsic args are loaded by value, but c_loc needs the address.
18285
-                if key == "c_loc" || key == "c_funloc" {
19497
+                // c_loc(x): return the address of the target itself as an
19498
+                // i64 integer (matching type(c_ptr)). This must bypass the
19499
+                // normal by-ref argument path because character arguments use
19500
+                // temporary pointer slots there, while c_loc needs the real
19501
+                // underlying element/storage address.
19502
+                if key == "c_loc" {
19503
+                    if let Some(arg0) = args.first() {
19504
+                        if let crate::ast::expr::SectionSubscript::Element(e) = &arg0.value {
19505
+                            if let Expr::Name { name } = &e.node {
19506
+                                if let Some(info) = locals.get(&name.to_lowercase()) {
19507
+                                    let addr = if info.by_ref {
19508
+                                        if info.descriptor_arg {
19509
+                                            array_data_ptr_for_call(b, info)
19510
+                                        } else if info.char_kind != CharKind::None
19511
+                                            && info.dims.is_empty()
19512
+                                        {
19513
+                                            if let Some((ptr, _)) =
19514
+                                                char_addr_and_runtime_len(b, e, locals)
19515
+                                            {
19516
+                                                ptr
19517
+                                            } else {
19518
+                                                b.load(info.addr)
19519
+                                            }
19520
+                                        } else {
19521
+                                            b.load(info.addr)
19522
+                                        }
19523
+                                    } else if !info.dims.is_empty()
19524
+                                        || local_uses_array_descriptor(info)
19525
+                                    {
19526
+                                        array_data_ptr_for_call(b, info)
19527
+                                    } else if info.char_kind != CharKind::None {
19528
+                                        if let Some((ptr, _)) =
19529
+                                            char_addr_and_runtime_len(b, e, locals)
19530
+                                        {
19531
+                                            ptr
19532
+                                        } else {
19533
+                                            info.addr
19534
+                                        }
19535
+                                    } else {
19536
+                                        info.addr
19537
+                                    };
19538
+                                    return b.ptr_to_int(addr);
19539
+                                }
19540
+                            }
19541
+
19542
+                            if let Expr::FunctionCall {
19543
+                                callee,
19544
+                                args: subscripts,
19545
+                            } = &e.node
19546
+                            {
19547
+                                if let Expr::Name { name } = &callee.node {
19548
+                                    if let Some(info) = locals.get(&name.to_lowercase()) {
19549
+                                        if info.char_kind != CharKind::None {
19550
+                                            let addr = if local_uses_array_descriptor(info)
19551
+                                                || matches!(info.ty, IrType::Int(IntWidth::I8))
19552
+                                            {
19553
+                                                char_array_element_ptr_and_len(
19554
+                                                    b, locals, info, subscripts, st,
19555
+                                                )
19556
+                                                .map(|(ptr, _)| ptr)
19557
+                                                .unwrap_or_else(|| {
19558
+                                                    lower_array_element_addr(
19559
+                                                        b, locals, info, subscripts, st,
19560
+                                                    )
19561
+                                                })
19562
+                                            } else {
19563
+                                                lower_array_element(b, locals, info, subscripts, st)
19564
+                                            };
19565
+                                            return b.ptr_to_int(addr);
19566
+                                        }
19567
+                                        if !info.dims.is_empty()
19568
+                                            || local_uses_array_descriptor(info)
19569
+                                        {
19570
+                                            let addr = lower_array_element_addr(
19571
+                                                b, locals, info, subscripts, st,
19572
+                                            );
19573
+                                            return b.ptr_to_int(addr);
19574
+                                        }
19575
+                                    }
19576
+                                }
19577
+                            }
19578
+
19579
+                            let addr = lower_arg_by_ref(b, locals, e, st);
19580
+                            return b.ptr_to_int(addr);
19581
+                        }
19582
+                    }
19583
+                }
19584
+
19585
+                // c_funloc(f): return the entry address of the procedure.
19586
+                if key == "c_funloc" {
1828619587
                     if let Some(arg0) = args.first() {
1828719588
                         if let crate::ast::expr::SectionSubscript::Element(e) = &arg0.value {
1828819589
                             let addr = lower_arg_by_ref(b, locals, e, st);
18289
-                            // Convert pointer to integer (c_ptr is i64).
1829019590
                             return b.ptr_to_int(addr);
1829119591
                         }
1829219592
                     }
@@ -18474,6 +19774,13 @@ fn lower_expr_full(
1847419774
                     .or_else(|| callee_string_descriptor_arg_mask(st, &key))
1847519775
                 {
1847619776
                     for (i, flag) in string_desc_flags.iter().enumerate() {
19777
+                        if callee_descriptor_args
19778
+                            .as_ref()
19779
+                            .map(|mask| mask.get(i).copied().unwrap_or(false))
19780
+                            .unwrap_or(false)
19781
+                        {
19782
+                            continue;
19783
+                        }
1847719784
                         if !*flag || i >= args.len() {
1847819785
                             continue;
1847919786
                         }
@@ -18770,7 +20077,15 @@ mod tests {
1877020077
             let rr = resolve::resolve_file(&units, &[]).unwrap();
1877120078
             (rr.st, rr.type_layouts)
1877220079
         };
18773
-        lower_file(&units, &st, &layouts, HashMap::new(), HashMap::new()).0
20080
+        lower_file(
20081
+            &units,
20082
+            &st,
20083
+            &layouts,
20084
+            HashMap::new(),
20085
+            HashMap::new(),
20086
+            HashMap::new(),
20087
+        )
20088
+        .0
1877420089
     }
1877520090
 
1877620091
     fn lower_and_verify(src: &str) -> (Module, String) {
src/lexer/mod.rsmodified
@@ -1106,11 +1106,10 @@ impl<'a> Lexer<'a> {
11061106
             b'%' => (TokenKind::Percent, "%"),
11071107
             b'&' => (TokenKind::Ampersand, "&"),
11081108
             _ => {
1109
-                let unexpected = self.char_at(self.pos.saturating_sub(1)).unwrap_or(ch as char);
1110
-                return Err(self.err(
1111
-                    start,
1112
-                    format!("unexpected character: '{}'", unexpected),
1113
-                ));
1109
+                let unexpected = self
1110
+                    .char_at(self.pos.saturating_sub(1))
1111
+                    .unwrap_or(ch as char);
1112
+                return Err(self.err(start, format!("unexpected character: '{}'", unexpected)));
11141113
             }
11151114
         };
11161115
 
src/opt/gvn.rsmodified
@@ -629,6 +629,7 @@ mod tests {
629629
             &type_layouts,
630630
             std::collections::HashMap::new(),
631631
             std::collections::HashMap::new(),
632
+            std::collections::HashMap::new(),
632633
         )
633634
         .0
634635
     }
src/parser/stmt.rsmodified
@@ -1424,6 +1424,7 @@ impl<'a> Parser<'a> {
14241424
         self.expect(&TokenKind::LParen)?;
14251425
         let mut items = Vec::new();
14261426
         let mut opts = Vec::new();
1427
+        let mut type_spec = None;
14271428
 
14281429
         // Check for typed allocation: allocate(type-spec :: items)
14291430
         // E.g., allocate(integer :: x), allocate(base_type :: poly_var)
@@ -1431,6 +1432,7 @@ impl<'a> Parser<'a> {
14311432
             let save = self.pos;
14321433
             if let Some(ts_result) = self.try_parse_type_spec() {
14331434
                 if ts_result.is_ok() && self.peek() == &TokenKind::ColonColon {
1435
+                    type_spec = ts_result.ok();
14341436
                     self.advance(); // consume ::
14351437
                                     // Continue to parse items normally below.
14361438
                 } else {
@@ -1474,7 +1476,14 @@ impl<'a> Parser<'a> {
14741476
         if is_dealloc {
14751477
             Ok(Spanned::new(Stmt::Deallocate { items, opts }, span))
14761478
         } else {
1477
-            Ok(Spanned::new(Stmt::Allocate { items, opts }, span))
1479
+            Ok(Spanned::new(
1480
+                Stmt::Allocate {
1481
+                    type_spec,
1482
+                    items,
1483
+                    opts,
1484
+                },
1485
+                span,
1486
+            ))
14781487
         }
14791488
     }
14801489
 
@@ -2178,7 +2187,13 @@ end if
21782187
     #[test]
21792188
     fn allocate_simple() {
21802189
         let s = parse_one("allocate(a(n), b(m,k))\n");
2181
-        if let Stmt::Allocate { items, opts } = &s.node {
2190
+        if let Stmt::Allocate {
2191
+            type_spec,
2192
+            items,
2193
+            opts,
2194
+        } = &s.node
2195
+        {
2196
+            assert!(type_spec.is_none());
21822197
             assert_eq!(items.len(), 2);
21832198
             assert!(opts.is_empty());
21842199
         } else {
@@ -2189,7 +2204,7 @@ end if
21892204
     #[test]
21902205
     fn allocate_with_stat() {
21912206
         let s = parse_one("allocate(x(100), stat=ios, errmsg=msg)\n");
2192
-        if let Stmt::Allocate { items, opts } = &s.node {
2207
+        if let Stmt::Allocate { items, opts, .. } = &s.node {
21932208
             assert_eq!(items.len(), 1);
21942209
             assert!(opts.iter().any(|o| o.keyword.as_deref() == Some("stat")));
21952210
             assert!(opts.iter().any(|o| o.keyword.as_deref() == Some("errmsg")));
@@ -2285,7 +2300,14 @@ end if
22852300
     fn allocate_typed() {
22862301
         // allocate(integer :: x) — typed allocation.
22872302
         let s = parse_one("allocate(integer :: x(100))\n");
2288
-        assert!(matches!(s.node, Stmt::Allocate { .. }));
2303
+        if let Stmt::Allocate {
2304
+            type_spec: Some(crate::ast::decl::TypeSpec::Integer(_)),
2305
+            ..
2306
+        } = s.node
2307
+        {
2308
+        } else {
2309
+            panic!("typed allocation should preserve the type-spec");
2310
+        }
22892311
     }
22902312
 
22912313
     #[test]
src/sema/amod.rsmodified
@@ -249,6 +249,12 @@ fn emit_procedure(
249249
     if is_func {
250250
         let ret_str = type_info_to_string(sym.type_info.as_ref());
251251
         write!(out, "@function {} -> {}", sym.name, ret_str).unwrap();
252
+        if sym.attrs.allocatable {
253
+            write!(out, ", result_allocatable").unwrap();
254
+        }
255
+        if sym.attrs.pointer {
256
+            write!(out, ", result_pointer").unwrap();
257
+        }
252258
     } else {
253259
         write!(out, "@subroutine {}", sym.name).unwrap();
254260
     }
@@ -264,16 +270,46 @@ fn emit_procedure(
264270
     writeln!(out).unwrap();
265271
 
266272
     let name_lc = name.to_lowercase();
267
-
268
-    // Walk into the procedure's child scope for full arg info.
269
-    let proc_scope = st.scopes.iter().find(|s| {
270
-        s.parent == Some(mod_scope_id)
271
-            && match &s.kind {
272
-                ScopeKind::Function(n) | ScopeKind::Subroutine(n) => n.eq_ignore_ascii_case(name),
273
-                _ => false,
274
-            }
273
+    let ir_func = ir_module.functions.iter().find(|f| {
274
+        f.name.eq_ignore_ascii_case(name)
275
+            || f.name.eq_ignore_ascii_case(&name_lc)
276
+            || f.name.to_lowercase().ends_with(&format!("_{}", name_lc))
275277
     });
276278
 
279
+    // Walk into the procedure's scope for full arg info. Interface-declared
280
+    // procedures sit under an intermediate Interface scope rather than
281
+    // directly under the module, so check both shapes.
282
+    let proc_scope = st
283
+        .scopes
284
+        .iter()
285
+        .find(|s| {
286
+            s.parent == Some(mod_scope_id)
287
+                && match &s.kind {
288
+                    ScopeKind::Function(n) | ScopeKind::Subroutine(n) => {
289
+                        n.eq_ignore_ascii_case(name)
290
+                    }
291
+                    _ => false,
292
+                }
293
+        })
294
+        .or_else(|| {
295
+            st.scopes.iter().find(|s| {
296
+                let matches_name = match &s.kind {
297
+                    ScopeKind::Function(n) | ScopeKind::Subroutine(n) => {
298
+                        n.eq_ignore_ascii_case(name)
299
+                    }
300
+                    _ => false,
301
+                };
302
+                if !matches_name {
303
+                    return false;
304
+                }
305
+                let Some(parent_id) = s.parent else {
306
+                    return false;
307
+                };
308
+                let parent = st.scope(parent_id);
309
+                matches!(parent.kind, ScopeKind::Interface) && parent.parent == Some(mod_scope_id)
310
+            })
311
+        });
312
+
277313
     // Compute hidden char-length count from the scope's arg types.
278314
     let mut hidden_count = 0usize;
279315
     if let Some(pscope) = proc_scope {
@@ -295,7 +331,14 @@ fn emit_procedure(
295331
 
296332
     let mut reg_idx = 0usize;
297333
     if let Some(pscope) = proc_scope {
298
-        for arg_name in &pscope.arg_order {
334
+        let ir_arg_base = ir_func
335
+            .map(|func| {
336
+                func.params
337
+                    .len()
338
+                    .saturating_sub(pscope.arg_order.len() + hidden_count)
339
+            })
340
+            .unwrap_or(0);
341
+        for (arg_idx, arg_name) in pscope.arg_order.iter().enumerate() {
299342
             if let Some(arg_sym) = pscope.symbols.get(&arg_name.to_lowercase()) {
300343
                 let type_str = type_info_to_string(arg_sym.type_info.as_ref());
301344
                 write!(out, "  @arg {} : {}", arg_name, type_str).unwrap();
@@ -313,6 +356,28 @@ fn emit_procedure(
313356
                 if arg_sym.attrs.value {
314357
                     arg_attrs.push("value");
315358
                 }
359
+                let is_descriptor_arg = ir_func
360
+                    .and_then(|func| func.params.get(ir_arg_base + arg_idx))
361
+                    .map(|param| {
362
+                        matches!(
363
+                            &param.ty,
364
+                            crate::ir::types::IrType::Ptr(inner)
365
+                                if matches!(
366
+                                    inner.as_ref(),
367
+                                    crate::ir::types::IrType::Array(elem, 384)
368
+                                        if matches!(
369
+                                            elem.as_ref(),
370
+                                            crate::ir::types::IrType::Int(
371
+                                                crate::ir::types::IntWidth::I8
372
+                                            )
373
+                                        )
374
+                                )
375
+                        )
376
+                    })
377
+                    .unwrap_or(false);
378
+                if is_descriptor_arg {
379
+                    arg_attrs.push("descriptor");
380
+                }
316381
                 if arg_sym.attrs.allocatable {
317382
                     arg_attrs.push("allocatable");
318383
                 }
@@ -370,10 +435,6 @@ fn emit_procedure(
370435
     }
371436
 
372437
     // @hint line.
373
-    let ir_func = ir_module
374
-        .functions
375
-        .iter()
376
-        .find(|f| f.name.eq_ignore_ascii_case(name) || f.name.eq_ignore_ascii_case(&name_lc));
377438
     if let Some(func) = ir_func {
378439
         let mut hints = Vec::new();
379440
         if is_leaf(func) {
@@ -560,6 +621,7 @@ pub struct AmodArg {
560621
     pub intent: Option<Intent>,
561622
     pub optional: bool,
562623
     pub value: bool,
624
+    pub descriptor: bool,
563625
     pub allocatable: bool,
564626
     pub pointer: bool,
565627
     pub hidden: bool,
@@ -571,6 +633,8 @@ pub struct AmodProc {
571633
     pub name: String,
572634
     pub kind: SymbolKind,
573635
     pub return_type: Option<TypeInfo>,
636
+    pub result_allocatable: bool,
637
+    pub result_pointer: bool,
574638
     pub pure: bool,
575639
     pub elemental: bool,
576640
     pub binding_label: Option<String>,
@@ -842,6 +906,8 @@ fn parse_proc(header: &str, lines: &mut std::iter::Peekable<std::str::Lines>) ->
842906
 
843907
     let pure = attrs_str.contains("pure");
844908
     let elemental = attrs_str.contains("elemental");
909
+    let result_allocatable = attrs_str.contains("result_allocatable");
910
+    let result_pointer = attrs_str.contains("result_pointer");
845911
     let binding_label = attrs_str
846912
         .split(", ")
847913
         .find_map(|attr| attr.strip_prefix("bind=").map(|label| label.to_string()));
@@ -871,6 +937,8 @@ fn parse_proc(header: &str, lines: &mut std::iter::Peekable<std::str::Lines>) ->
871937
         name,
872938
         kind,
873939
         return_type,
940
+        result_allocatable,
941
+        result_pointer,
874942
         pure,
875943
         elemental,
876944
         binding_label,
@@ -910,6 +978,7 @@ fn parse_arg(line: &str) -> AmodArg {
910978
 
911979
     let optional = attr_str.contains("optional");
912980
     let value = attr_str.contains("value");
981
+    let descriptor = attr_str.contains("descriptor");
913982
     let allocatable = attr_str.contains("allocatable");
914983
     let pointer = attr_str.contains("pointer");
915984
 
@@ -919,6 +988,7 @@ fn parse_arg(line: &str) -> AmodArg {
919988
         intent,
920989
         optional,
921990
         value,
991
+        descriptor,
922992
         allocatable,
923993
         pointer,
924994
         hidden,
@@ -1213,6 +1283,21 @@ pub fn extract_char_len_star_params(iface: &ModuleInterface) -> HashMap<String,
12131283
     out
12141284
 }
12151285
 
1286
+/// Extract descriptor_params from a loaded ModuleInterface.
1287
+/// For each procedure with descriptor-backed dummies, produces a
1288
+/// Vec<bool> (per-position, true = pass the 384-byte descriptor).
1289
+pub fn extract_descriptor_params(iface: &ModuleInterface) -> HashMap<String, Vec<bool>> {
1290
+    let mut out = HashMap::new();
1291
+    for proc in &iface.procedures {
1292
+        let visible_args: Vec<&AmodArg> = proc.args.iter().filter(|a| !a.hidden).collect();
1293
+        let flags: Vec<bool> = visible_args.iter().map(|a| a.descriptor).collect();
1294
+        if flags.iter().any(|f| *f) {
1295
+            out.insert(proc.name.to_lowercase(), flags);
1296
+        }
1297
+    }
1298
+    out
1299
+}
1300
+
12161301
 fn type_info_to_ir_type(info: Option<&TypeInfo>) -> crate::ir::types::IrType {
12171302
     use crate::ir::types::{FloatWidth, IntWidth, IrType};
12181303
     match info {
src/sema/resolve.rsmodified
@@ -745,6 +745,8 @@ fn load_external_module(
745745
     for proc in &iface.procedures {
746746
         let attrs = SymbolAttrs {
747747
             access: Access::Public,
748
+            allocatable: proc.result_allocatable,
749
+            pointer: proc.result_pointer,
748750
             pure: proc.pure,
749751
             elemental: proc.elemental,
750752
             binding_label: proc.binding_label.clone(),
@@ -1295,7 +1297,10 @@ fn process_contains(
12951297
                     || prefix
12961298
                         .iter()
12971299
                         .any(|p| matches!(p, crate::ast::unit::Prefix::Pure));
1300
+                let result_attrs = function_result_attrs(name, result, decls);
12981301
                 let fn_attrs = SymbolAttrs {
1302
+                    allocatable: result_attrs.allocatable,
1303
+                    pointer: result_attrs.pointer,
12991304
                     pure: fn_pure,
13001305
                     elemental: fn_elemental,
13011306
                     binding_label: normalized_bind_name(bind.as_ref()),
@@ -1520,6 +1525,32 @@ fn attrs_to_symbol_attrs(attrs: &[Attribute], default_access: Access) -> SymbolA
15201525
     sa
15211526
 }
15221527
 
1528
+fn function_result_attrs(
1529
+    function_name: &str,
1530
+    result: &Option<String>,
1531
+    decls: &[crate::ast::decl::SpannedDecl],
1532
+) -> SymbolAttrs {
1533
+    let result_key = result
1534
+        .as_deref()
1535
+        .unwrap_or(function_name)
1536
+        .to_ascii_lowercase();
1537
+    for decl in decls {
1538
+        let crate::ast::decl::Decl::TypeDecl {
1539
+            attrs, entities, ..
1540
+        } = &decl.node
1541
+        else {
1542
+            continue;
1543
+        };
1544
+        if entities
1545
+            .iter()
1546
+            .any(|entity| entity.name.eq_ignore_ascii_case(&result_key))
1547
+        {
1548
+            return attrs_to_symbol_attrs(attrs, Access::Default);
1549
+        }
1550
+    }
1551
+    SymbolAttrs::default()
1552
+}
1553
+
15231554
 #[cfg(test)]
15241555
 mod tests {
15251556
     use super::*;
src/sema/validate.rsmodified
@@ -1236,7 +1236,7 @@ fn validate_stmt(ctx: &mut Ctx, stmt: &SpannedStmt) {
12361236
         }
12371237
 
12381238
         // ---- Allocate / Deallocate ----
1239
-        Stmt::Allocate { items, opts } => {
1239
+        Stmt::Allocate { items, opts, .. } => {
12401240
             if opts.iter().any(|opt| {
12411241
                 opt.keyword
12421242
                     .as_deref()
src/testing.rsmodified
@@ -347,6 +347,7 @@ pub fn capture_from_path(request: &CaptureRequest) -> Result<CaptureResult, Capt
347347
         &type_layouts,
348348
         std::collections::HashMap::new(),
349349
         std::collections::HashMap::new(),
350
+        std::collections::HashMap::new(),
350351
     );
351352
     let ir_errors = verify::verify_module(&ir_module);
352353
     if !ir_errors.is_empty() {
tests/claims_audit_29_11.rsmodified
@@ -45,8 +45,12 @@ fn function_sections(ir: &str) -> Vec<&str> {
4545
 
4646
 fn function_name<'a>(func_section: &'a str) -> &'a str {
4747
     let header = func_section.lines().next().expect("function header").trim();
48
-    let rest = header.strip_prefix("func @").expect("function header prefix");
49
-    let end = rest.find(|ch: char| ch == ' ' || ch == '(').unwrap_or(rest.len());
48
+    let rest = header
49
+        .strip_prefix("func @")
50
+        .expect("function header prefix");
51
+    let end = rest
52
+        .find(|ch: char| ch == ' ' || ch == '(')
53
+        .unwrap_or(rest.len());
5054
     &rest[..end]
5155
 }
5256
 
tests/cli_driver.rsmodified
@@ -74,6 +74,26 @@ fn undefined_symbols(path: &std::path::Path) -> Vec<String> {
7474
         .collect()
7575
 }
7676
 
77
+fn compile_c_object(source: &std::path::Path, output: &std::path::Path) {
78
+    let result = Command::new("clang")
79
+        .args([
80
+            "-arch",
81
+            "arm64",
82
+            "-c",
83
+            source.to_str().unwrap(),
84
+            "-o",
85
+            output.to_str().unwrap(),
86
+        ])
87
+        .output()
88
+        .expect("failed to spawn clang");
89
+    assert!(
90
+        result.status.success(),
91
+        "clang failed for {}: {}",
92
+        source.display(),
93
+        String::from_utf8_lossy(&result.stderr)
94
+    );
95
+}
96
+
7797
 #[test]
7898
 fn version_flag_prints_version_string_to_stdout() {
7999
     let out = Command::new(compiler("armfortas"))
@@ -228,6 +248,327 @@ fn fixed_form_program_compiles_and_runs() {
228248
     let _ = std::fs::remove_file(&src);
229249
 }
230250
 
251
+#[test]
252
+fn imported_param_fixed_char_len_preserves_get_command_argument_buffer() {
253
+    let dir = unique_dir("imported_param_char_len");
254
+    let mod_src = write_program_in(
255
+        &dir,
256
+        "cfg.f90",
257
+        "module cfg\n  implicit none\n  integer, parameter :: max_path_len = 32\nend module cfg\n",
258
+    );
259
+    let main_src = write_program_in(
260
+        &dir,
261
+        "main.f90",
262
+        "program p\n  use cfg, only: max_path_len\n  implicit none\n  character(len=max_path_len) :: arg1\n  call get_command_argument(1, arg1)\n  if (trim(arg1) /= '--version') error stop 1\n  print *, trim(arg1)\nend program\n",
263
+    );
264
+
265
+    let mod_obj = dir.join("cfg.o");
266
+    let compile_mod = Command::new(compiler("armfortas"))
267
+        .current_dir(&dir)
268
+        .args([
269
+            "-c",
270
+            "-J",
271
+            dir.to_str().unwrap(),
272
+            mod_src.to_str().unwrap(),
273
+            "-o",
274
+            mod_obj.to_str().unwrap(),
275
+        ])
276
+        .output()
277
+        .expect("cfg module compile failed to spawn");
278
+    assert!(
279
+        compile_mod.status.success(),
280
+        "cfg module should compile: {}",
281
+        String::from_utf8_lossy(&compile_mod.stderr)
282
+    );
283
+
284
+    let main_obj = dir.join("main.o");
285
+    let compile_main = Command::new(compiler("armfortas"))
286
+        .current_dir(&dir)
287
+        .args([
288
+            "-c",
289
+            "-I",
290
+            dir.to_str().unwrap(),
291
+            "-J",
292
+            dir.to_str().unwrap(),
293
+            main_src.to_str().unwrap(),
294
+            "-o",
295
+            main_obj.to_str().unwrap(),
296
+        ])
297
+        .output()
298
+        .expect("main compile failed to spawn");
299
+    assert!(
300
+        compile_main.status.success(),
301
+        "main should compile with imported fixed char len: {}",
302
+        String::from_utf8_lossy(&compile_main.stderr)
303
+    );
304
+
305
+    let exe = dir.join("imported_param_char_len.bin");
306
+    let link = Command::new(compiler("armfortas"))
307
+        .current_dir(&dir)
308
+        .args([
309
+            mod_obj.to_str().unwrap(),
310
+            main_obj.to_str().unwrap(),
311
+            "-o",
312
+            exe.to_str().unwrap(),
313
+        ])
314
+        .output()
315
+        .expect("link spawn failed");
316
+    assert!(
317
+        link.status.success(),
318
+        "imported-param char-len objects should link: {}",
319
+        String::from_utf8_lossy(&link.stderr)
320
+    );
321
+
322
+    let run = Command::new(&exe)
323
+        .arg("--version")
324
+        .output()
325
+        .expect("run spawn failed");
326
+    assert!(
327
+        run.status.success(),
328
+        "imported-param char-len binary should run: status={:?} stderr={}",
329
+        run.status,
330
+        String::from_utf8_lossy(&run.stderr)
331
+    );
332
+    let stdout = String::from_utf8_lossy(&run.stdout);
333
+    assert!(
334
+        stdout.contains("--version"),
335
+        "imported fixed char len should preserve command argument text: {}",
336
+        stdout
337
+    );
338
+
339
+    let _ = std::fs::remove_dir_all(&dir);
340
+}
341
+
342
+#[test]
343
+fn imported_param_char_dummy_element_assignment_runs() {
344
+    let dir = unique_dir("imported_param_char_dummy");
345
+    let cfg_src = write_program_in(
346
+        &dir,
347
+        "cfg.f90",
348
+        "module cfg\n  implicit none\n  integer, parameter :: max_token_len = 32\nend module cfg\n",
349
+    );
350
+    let ops_src = write_program_in(
351
+        &dir,
352
+        "ops.f90",
353
+        "module ops\n  use cfg, only: max_token_len\n  implicit none\ncontains\n  subroutine set_first(words)\n    character(len=max_token_len), intent(inout) :: words(:)\n    character(len=max_token_len) :: tmp\n    tmp = 'hello'\n    words(1) = tmp\n  end subroutine set_first\nend module ops\n",
354
+    );
355
+    let main_src = write_program_in(
356
+        &dir,
357
+        "main.f90",
358
+        "program p\n  use cfg, only: max_token_len\n  use ops, only: set_first\n  implicit none\n  character(len=max_token_len), allocatable :: words(:)\n  allocate(words(2))\n  words = ''\n  call set_first(words)\n  print *, trim(words(1))\nend program p\n",
359
+    );
360
+
361
+    let cfg_obj = dir.join("cfg.o");
362
+    let compile_cfg = Command::new(compiler("armfortas"))
363
+        .current_dir(&dir)
364
+        .args([
365
+            "-c",
366
+            "-J",
367
+            dir.to_str().unwrap(),
368
+            cfg_src.to_str().unwrap(),
369
+            "-o",
370
+            cfg_obj.to_str().unwrap(),
371
+        ])
372
+        .output()
373
+        .expect("cfg compile failed to spawn");
374
+    assert!(
375
+        compile_cfg.status.success(),
376
+        "cfg module should compile: {}",
377
+        String::from_utf8_lossy(&compile_cfg.stderr)
378
+    );
379
+
380
+    let ops_obj = dir.join("ops.o");
381
+    let compile_ops = Command::new(compiler("armfortas"))
382
+        .current_dir(&dir)
383
+        .args([
384
+            "-c",
385
+            "-I",
386
+            dir.to_str().unwrap(),
387
+            "-J",
388
+            dir.to_str().unwrap(),
389
+            ops_src.to_str().unwrap(),
390
+            "-o",
391
+            ops_obj.to_str().unwrap(),
392
+        ])
393
+        .output()
394
+        .expect("ops compile failed to spawn");
395
+    assert!(
396
+        compile_ops.status.success(),
397
+        "imported-param char dummy assignment should compile: {}",
398
+        String::from_utf8_lossy(&compile_ops.stderr)
399
+    );
400
+
401
+    let main_obj = dir.join("main.o");
402
+    let compile_main = Command::new(compiler("armfortas"))
403
+        .current_dir(&dir)
404
+        .args([
405
+            "-c",
406
+            "-I",
407
+            dir.to_str().unwrap(),
408
+            "-J",
409
+            dir.to_str().unwrap(),
410
+            main_src.to_str().unwrap(),
411
+            "-o",
412
+            main_obj.to_str().unwrap(),
413
+        ])
414
+        .output()
415
+        .expect("main compile failed to spawn");
416
+    assert!(
417
+        compile_main.status.success(),
418
+        "main should compile: {}",
419
+        String::from_utf8_lossy(&compile_main.stderr)
420
+    );
421
+
422
+    let exe = dir.join("imported_param_char_dummy.bin");
423
+    let link = Command::new(compiler("armfortas"))
424
+        .current_dir(&dir)
425
+        .args([
426
+            cfg_obj.to_str().unwrap(),
427
+            ops_obj.to_str().unwrap(),
428
+            main_obj.to_str().unwrap(),
429
+            "-o",
430
+            exe.to_str().unwrap(),
431
+        ])
432
+        .output()
433
+        .expect("link spawn failed");
434
+    assert!(
435
+        link.status.success(),
436
+        "imported-param char dummy objects should link: {}",
437
+        String::from_utf8_lossy(&link.stderr)
438
+    );
439
+
440
+    let run = Command::new(&exe).output().expect("run spawn failed");
441
+    assert!(
442
+        run.status.success(),
443
+        "imported-param char dummy binary should run: status={:?} stderr={}",
444
+        run.status,
445
+        String::from_utf8_lossy(&run.stderr)
446
+    );
447
+    let stdout = String::from_utf8_lossy(&run.stdout);
448
+    assert!(
449
+        stdout.contains("hello"),
450
+        "dummy char assignment should preserve fixed imported length: {}",
451
+        stdout
452
+    );
453
+
454
+    let _ = std::fs::remove_dir_all(&dir);
455
+}
456
+
457
+#[test]
458
+fn imported_param_char_section_assignment_preserves_elements() {
459
+    let dir = unique_dir("imported_param_char_section");
460
+    let cfg_src = write_program_in(
461
+        &dir,
462
+        "cfg.f90",
463
+        "module cfg\n  implicit none\n  integer, parameter :: max_token_len = 32\nend module cfg\n",
464
+    );
465
+    let ops_src = write_program_in(
466
+        &dir,
467
+        "ops.f90",
468
+        "module ops\n  use cfg, only: max_token_len\n  implicit none\ncontains\n  subroutine grow(words, current_size)\n    character(len=max_token_len), allocatable, intent(inout) :: words(:)\n    integer, intent(inout) :: current_size\n    character(len=max_token_len), allocatable :: new_words(:)\n    integer :: new_size\n    new_size = current_size * 2\n    allocate(new_words(new_size))\n    new_words(1:current_size) = words(1:current_size)\n    call move_alloc(new_words, words)\n    current_size = new_size\n  end subroutine grow\nend module ops\n",
469
+    );
470
+    let main_src = write_program_in(
471
+        &dir,
472
+        "main.f90",
473
+        "program p\n  use cfg, only: max_token_len\n  use ops, only: grow\n  implicit none\n  character(len=max_token_len), allocatable :: words(:)\n  integer :: n\n  n = 2\n  allocate(words(n))\n  words = ''\n  words(1) = 'one'\n  words(2) = 'two'\n  call grow(words, n)\n  print *, trim(words(1)), trim(words(2)), n\nend program p\n",
474
+    );
475
+
476
+    let cfg_obj = dir.join("cfg.o");
477
+    let compile_cfg = Command::new(compiler("armfortas"))
478
+        .current_dir(&dir)
479
+        .args([
480
+            "-c",
481
+            "-J",
482
+            dir.to_str().unwrap(),
483
+            cfg_src.to_str().unwrap(),
484
+            "-o",
485
+            cfg_obj.to_str().unwrap(),
486
+        ])
487
+        .output()
488
+        .expect("cfg compile failed to spawn");
489
+    assert!(
490
+        compile_cfg.status.success(),
491
+        "cfg module should compile: {}",
492
+        String::from_utf8_lossy(&compile_cfg.stderr)
493
+    );
494
+
495
+    let ops_obj = dir.join("ops.o");
496
+    let compile_ops = Command::new(compiler("armfortas"))
497
+        .current_dir(&dir)
498
+        .args([
499
+            "-c",
500
+            "-I",
501
+            dir.to_str().unwrap(),
502
+            "-J",
503
+            dir.to_str().unwrap(),
504
+            ops_src.to_str().unwrap(),
505
+            "-o",
506
+            ops_obj.to_str().unwrap(),
507
+        ])
508
+        .output()
509
+        .expect("ops compile failed to spawn");
510
+    assert!(
511
+        compile_ops.status.success(),
512
+        "imported-param char section assignment should compile: {}",
513
+        String::from_utf8_lossy(&compile_ops.stderr)
514
+    );
515
+
516
+    let main_obj = dir.join("main.o");
517
+    let compile_main = Command::new(compiler("armfortas"))
518
+        .current_dir(&dir)
519
+        .args([
520
+            "-c",
521
+            "-I",
522
+            dir.to_str().unwrap(),
523
+            "-J",
524
+            dir.to_str().unwrap(),
525
+            main_src.to_str().unwrap(),
526
+            "-o",
527
+            main_obj.to_str().unwrap(),
528
+        ])
529
+        .output()
530
+        .expect("main compile failed to spawn");
531
+    assert!(
532
+        compile_main.status.success(),
533
+        "main should compile: {}",
534
+        String::from_utf8_lossy(&compile_main.stderr)
535
+    );
536
+
537
+    let exe = dir.join("imported_param_char_section.bin");
538
+    let link = Command::new(compiler("armfortas"))
539
+        .current_dir(&dir)
540
+        .args([
541
+            cfg_obj.to_str().unwrap(),
542
+            ops_obj.to_str().unwrap(),
543
+            main_obj.to_str().unwrap(),
544
+            "-o",
545
+            exe.to_str().unwrap(),
546
+        ])
547
+        .output()
548
+        .expect("link spawn failed");
549
+    assert!(
550
+        link.status.success(),
551
+        "imported-param char section objects should link: {}",
552
+        String::from_utf8_lossy(&link.stderr)
553
+    );
554
+
555
+    let run = Command::new(&exe).output().expect("run spawn failed");
556
+    assert!(
557
+        run.status.success(),
558
+        "imported-param char section binary should run: status={:?} stderr={}",
559
+        run.status,
560
+        String::from_utf8_lossy(&run.stderr)
561
+    );
562
+    let stdout = String::from_utf8_lossy(&run.stdout);
563
+    assert!(
564
+        stdout.contains("one") && stdout.contains("two") && stdout.contains('4'),
565
+        "char section assignment should preserve copied elements: {}",
566
+        stdout
567
+    );
568
+
569
+    let _ = std::fs::remove_dir_all(&dir);
570
+}
571
+
231572
 #[test]
232573
 fn select_lowering_coerces_mixed_width_branch_values() {
233574
     let src = write_program(
@@ -385,18 +726,193 @@ fn bind_c_name_call_uses_declared_c_symbol() {
385726
 
386727
     let undefined = undefined_symbols(&out);
387728
     assert!(
388
-        undefined.iter().any(|sym| sym == "_getpid"),
389
-        "bind(c, name=...) should call the declared C symbol: {:?}",
390
-        undefined
729
+        undefined.iter().any(|sym| sym == "_getpid"),
730
+        "bind(c, name=...) should call the declared C symbol: {:?}",
731
+        undefined
732
+    );
733
+    assert!(
734
+        !undefined.iter().any(|sym| sym == "_getpid_c"),
735
+        "bind(c, name=...) should not call the local Fortran alias: {:?}",
736
+        undefined
737
+    );
738
+
739
+    let _ = std::fs::remove_file(&out);
740
+    let _ = std::fs::remove_file(&src);
741
+}
742
+
743
+#[test]
744
+fn bind_c_subroutine_value_arg_is_passed_by_value() {
745
+    let dir = unique_dir("bind_c_subroutine_value");
746
+    let c_src = write_program_in(
747
+        &dir,
748
+        "store_incremented.c",
749
+        "#include <stdint.h>\n\nvoid store_incremented(int32_t value, int32_t *out) {\n    *out = value + 1;\n}\n",
750
+    );
751
+    let c_obj = dir.join("store_incremented.o");
752
+    compile_c_object(&c_src, &c_obj);
753
+
754
+    let src = write_program_in(
755
+        &dir,
756
+        "main.f90",
757
+        "program p\n  use iso_c_binding, only: c_int\n  implicit none\n  interface\n    subroutine store_incremented(value, out) bind(C, name='store_incremented')\n      import :: c_int\n      integer(c_int), value :: value\n      integer(c_int), intent(out) :: out\n    end subroutine store_incremented\n  end interface\n  integer(c_int) :: out\n  call store_incremented(41_c_int, out)\n  if (out /= 42_c_int) error stop 1\n  print *, out\nend program\n",
758
+    );
759
+
760
+    let main_obj = dir.join("main.o");
761
+    let compile_obj = Command::new(compiler("armfortas"))
762
+        .current_dir(&dir)
763
+        .args([
764
+            "-c",
765
+            src.to_str().unwrap(),
766
+            "-o",
767
+            main_obj.to_str().unwrap(),
768
+        ])
769
+        .output()
770
+        .expect("bind(c) value subroutine object compile failed to spawn");
771
+    assert!(
772
+        compile_obj.status.success(),
773
+        "bind(c) value subroutine should compile to an object: {}",
774
+        String::from_utf8_lossy(&compile_obj.stderr)
775
+    );
776
+
777
+    let exe = dir.join("bind_c_subroutine_value.bin");
778
+    let link = Command::new(compiler("armfortas"))
779
+        .current_dir(&dir)
780
+        .args([
781
+            main_obj.to_str().unwrap(),
782
+            c_obj.to_str().unwrap(),
783
+            "-o",
784
+            exe.to_str().unwrap(),
785
+        ])
786
+        .output()
787
+        .expect("bind(c) value subroutine link failed to spawn");
788
+    assert!(
789
+        link.status.success(),
790
+        "bind(c) value subroutine objects should link: {}",
791
+        String::from_utf8_lossy(&link.stderr)
792
+    );
793
+
794
+    let run = Command::new(&exe)
795
+        .output()
796
+        .expect("bind(c) value run failed");
797
+    assert!(
798
+        run.status.success(),
799
+        "bind(c) value subroutine should run: status={:?} stderr={}",
800
+        run.status,
801
+        String::from_utf8_lossy(&run.stderr)
802
+    );
803
+    let stdout = String::from_utf8_lossy(&run.stdout);
804
+    assert!(
805
+        stdout.contains("42"),
806
+        "bind(c) value subroutine should observe the by-value integer argument: {}",
807
+        stdout
808
+    );
809
+
810
+    let _ = std::fs::remove_dir_all(&dir);
811
+}
812
+
813
+#[test]
814
+fn bind_c_interface_subroutine_value_survives_amod_import_and_runs() {
815
+    let dir = unique_dir("bind_c_interface_value_amod");
816
+    let c_src = write_program_in(
817
+        &dir,
818
+        "store_incremented.c",
819
+        "#include <stdint.h>\n\nvoid store_incremented(int32_t value, int32_t *out) {\n    *out = value + 1;\n}\n",
820
+    );
821
+    let c_obj = dir.join("store_incremented.o");
822
+    compile_c_object(&c_src, &c_obj);
823
+
824
+    let mod_src = write_program_in(
825
+        &dir,
826
+        "c_math.f90",
827
+        "module c_math\n  use iso_c_binding, only: c_int\n  implicit none\n  interface\n    subroutine store_incremented(value, out) bind(C, name='store_incremented')\n      import :: c_int\n      integer(c_int), value :: value\n      integer(c_int), intent(out) :: out\n    end subroutine store_incremented\n  end interface\nend module c_math\n",
828
+    );
829
+    let main_src = write_program_in(
830
+        &dir,
831
+        "main.f90",
832
+        "program p\n  use iso_c_binding, only: c_int\n  use c_math, only: store_incremented\n  implicit none\n  integer(c_int) :: out\n  call store_incremented(41_c_int, out)\n  if (out /= 42_c_int) error stop 1\n  print *, out\nend program\n",
833
+    );
834
+
835
+    let mod_obj = dir.join("c_math.o");
836
+    let compile_mod = Command::new(compiler("armfortas"))
837
+        .current_dir(&dir)
838
+        .args([
839
+            "-c",
840
+            "-J",
841
+            dir.to_str().unwrap(),
842
+            mod_src.to_str().unwrap(),
843
+            "-o",
844
+            mod_obj.to_str().unwrap(),
845
+        ])
846
+        .output()
847
+        .expect("bind(c) interface module compile failed to spawn");
848
+    assert!(
849
+        compile_mod.status.success(),
850
+        "bind(c) interface module should compile: {}",
851
+        String::from_utf8_lossy(&compile_mod.stderr)
852
+    );
853
+
854
+    let amod = std::fs::read_to_string(dir.join("c_math.amod")).expect("missing c_math.amod");
855
+    assert!(
856
+        amod.contains("@arg value") && amod.contains("value"),
857
+        "interface-declared VALUE arg should survive into .amod: {}",
858
+        amod
859
+    );
860
+
861
+    let main_obj = dir.join("main.o");
862
+    let compile_main = Command::new(compiler("armfortas"))
863
+        .current_dir(&dir)
864
+        .args([
865
+            "-c",
866
+            "-I",
867
+            dir.to_str().unwrap(),
868
+            "-J",
869
+            dir.to_str().unwrap(),
870
+            main_src.to_str().unwrap(),
871
+            "-o",
872
+            main_obj.to_str().unwrap(),
873
+        ])
874
+        .output()
875
+        .expect("bind(c) interface user compile failed to spawn");
876
+    assert!(
877
+        compile_main.status.success(),
878
+        "bind(c) interface user should compile: {}",
879
+        String::from_utf8_lossy(&compile_main.stderr)
880
+    );
881
+
882
+    let exe = dir.join("bind_c_interface_value.bin");
883
+    let link = Command::new(compiler("armfortas"))
884
+        .current_dir(&dir)
885
+        .args([
886
+            main_obj.to_str().unwrap(),
887
+            c_obj.to_str().unwrap(),
888
+            "-o",
889
+            exe.to_str().unwrap(),
890
+        ])
891
+        .output()
892
+        .expect("bind(c) interface user link failed to spawn");
893
+    assert!(
894
+        link.status.success(),
895
+        "bind(c) interface user objects should link: {}",
896
+        String::from_utf8_lossy(&link.stderr)
897
+    );
898
+
899
+    let run = Command::new(&exe)
900
+        .output()
901
+        .expect("bind(c) interface user run failed");
902
+    assert!(
903
+        run.status.success(),
904
+        "bind(c) interface user binary should run: status={:?} stderr={}",
905
+        run.status,
906
+        String::from_utf8_lossy(&run.stderr)
391907
     );
908
+    let stdout = String::from_utf8_lossy(&run.stdout);
392909
     assert!(
393
-        !undefined.iter().any(|sym| sym == "_getpid_c"),
394
-        "bind(c, name=...) should not call the local Fortran alias: {:?}",
395
-        undefined
910
+        stdout.contains("42"),
911
+        "unexpected bind(c) interface user output: {}",
912
+        stdout
396913
     );
397914
 
398
-    let _ = std::fs::remove_file(&out);
399
-    let _ = std::fs::remove_file(&src);
915
+    let _ = std::fs::remove_dir_all(&dir);
400916
 }
401917
 
402918
 #[test]
@@ -852,6 +1368,132 @@ fn allocated_on_derived_array_element_component_uses_descriptor_runtime() {
8521368
     let _ = std::fs::remove_file(&src);
8531369
 }
8541370
 
1371
+#[test]
1372
+fn allocatable_derived_shell_initialization_runs_through_components() {
1373
+    let src = write_program(
1374
+        "program p\n  implicit none\n  type :: string_t\n    character(:), allocatable :: str\n  end type string_t\n  type :: shell_t\n    type(string_t), allocatable :: positional_params(:)\n    integer, allocatable :: counts(:)\n  end type shell_t\n  type(shell_t), allocatable :: shell\n  allocate(shell)\n  call initialize_shell(shell)\n  if (.not. allocated(shell%positional_params)) stop 10\n  if (.not. allocated(shell%counts)) stop 11\n  if (shell%counts(1) /= 7) stop 12\n  print *, trim(shell%positional_params(1)%str)\ncontains\n  subroutine initialize_shell(shell)\n    type(shell_t), intent(out) :: shell\n    if (allocated(shell%positional_params)) stop 1\n    if (allocated(shell%counts)) stop 2\n    allocate(shell%positional_params(2))\n    allocate(shell%counts(2))\n    shell%positional_params(1)%str = 'ok'\n    shell%counts = [7, 9]\n  end subroutine initialize_shell\nend program\n",
1375
+        "f90",
1376
+    );
1377
+    let out = unique_path("allocatable_derived_shell", "bin");
1378
+    let compile = Command::new(compiler("armfortas"))
1379
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
1380
+        .output()
1381
+        .expect("allocatable derived shell compile failed to spawn");
1382
+    assert!(
1383
+        compile.status.success(),
1384
+        "allocatable derived shell compile failed: {}",
1385
+        String::from_utf8_lossy(&compile.stderr)
1386
+    );
1387
+
1388
+    let run = Command::new(&out)
1389
+        .output()
1390
+        .expect("allocatable derived shell run failed");
1391
+    assert!(
1392
+        run.status.success(),
1393
+        "allocatable derived shell run failed: {}",
1394
+        String::from_utf8_lossy(&run.stderr)
1395
+    );
1396
+    let stdout = String::from_utf8_lossy(&run.stdout);
1397
+    assert!(
1398
+        stdout.contains("ok"),
1399
+        "allocatable derived shell should initialize nested components: {}",
1400
+        stdout
1401
+    );
1402
+
1403
+    let _ = std::fs::remove_file(&out);
1404
+    let _ = std::fs::remove_file(&src);
1405
+}
1406
+
1407
+#[test]
1408
+fn c_f_pointer_array_target_builds_descriptor_backing() {
1409
+    let src = write_program(
1410
+        "program p\n  use iso_c_binding\n  implicit none\n  character(kind=c_char), target :: buf(4)\n  type(c_ptr) :: raw\n  character(kind=c_char), pointer :: view(:)\n  buf = [achar(111, kind=c_char), achar(107, kind=c_char), c_null_char, achar(120, kind=c_char)]\n  raw = c_loc(buf)\n  call c_f_pointer(raw, view, [4])\n  if (.not. associated(view)) stop 1\n  if (view(1) /= buf(1)) stop 2\n  if (view(2) /= buf(2)) stop 3\n  if (view(3) /= c_null_char) stop 4\n  print *, 'ok'\nend program\n",
1411
+        "f90",
1412
+    );
1413
+    let out = unique_path("c_f_pointer_array", "bin");
1414
+    let compile = Command::new(compiler("armfortas"))
1415
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
1416
+        .output()
1417
+        .expect("c_f_pointer array compile failed to spawn");
1418
+    assert!(
1419
+        compile.status.success(),
1420
+        "c_f_pointer array compile failed: {}",
1421
+        String::from_utf8_lossy(&compile.stderr)
1422
+    );
1423
+
1424
+    let run = Command::new(&out)
1425
+        .output()
1426
+        .expect("c_f_pointer array run failed");
1427
+    assert!(
1428
+        run.status.success(),
1429
+        "c_f_pointer array run failed: {}",
1430
+        String::from_utf8_lossy(&run.stderr)
1431
+    );
1432
+
1433
+    let _ = std::fs::remove_file(&out);
1434
+    let _ = std::fs::remove_file(&src);
1435
+}
1436
+
1437
+#[test]
1438
+fn fixed_c_char_array_element_assignment_compiles_and_runs() {
1439
+    let src = write_program(
1440
+        "program p\n  use iso_c_binding\n  implicit none\n  character(kind=c_char), target :: buf(4)\n  integer :: i\n  do i = 1, 3\n    buf(i) = achar(96 + i, kind=c_char)\n  end do\n  buf(4) = c_null_char\n  if (buf(1) /= achar(97, kind=c_char)) stop 1\n  if (buf(2) /= achar(98, kind=c_char)) stop 2\n  if (buf(3) /= achar(99, kind=c_char)) stop 3\n  if (buf(4) /= c_null_char) stop 4\n  print *, 'ok'\nend program\n",
1441
+        "f90",
1442
+    );
1443
+    let out = unique_path("fixed_c_char_array_store", "bin");
1444
+    let compile = Command::new(compiler("armfortas"))
1445
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
1446
+        .output()
1447
+        .expect("fixed c_char array compile failed to spawn");
1448
+    assert!(
1449
+        compile.status.success(),
1450
+        "fixed c_char array compile failed: {}",
1451
+        String::from_utf8_lossy(&compile.stderr)
1452
+    );
1453
+
1454
+    let run = Command::new(&out)
1455
+        .output()
1456
+        .expect("fixed c_char array run failed");
1457
+    assert!(
1458
+        run.status.success(),
1459
+        "fixed c_char array run failed: {}",
1460
+        String::from_utf8_lossy(&run.stderr)
1461
+    );
1462
+
1463
+    let _ = std::fs::remove_file(&out);
1464
+    let _ = std::fs::remove_file(&src);
1465
+}
1466
+
1467
+#[test]
1468
+fn c_loc_on_allocatable_c_char_array_element_compiles_and_runs() {
1469
+    let src = write_program(
1470
+        "program p\n  use iso_c_binding\n  implicit none\n  character(kind=c_char), allocatable, target :: c_tokens(:,:)\n  type(c_ptr) :: raw\n  integer :: i\n  allocate(c_tokens(4, 1))\n  do i = 1, 3\n    c_tokens(i, 1) = achar(96 + i, kind=c_char)\n  end do\n  c_tokens(4, 1) = c_null_char\n  raw = c_loc(c_tokens(1, 1))\n  if (.not. c_associated(raw)) stop 1\n  print *, 'ok'\nend program\n",
1471
+        "f90",
1472
+    );
1473
+    let out = unique_path("cloc_alloc_c_char_element", "bin");
1474
+    let compile = Command::new(compiler("armfortas"))
1475
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
1476
+        .output()
1477
+        .expect("c_loc allocatable c_char compile failed to spawn");
1478
+    assert!(
1479
+        compile.status.success(),
1480
+        "c_loc allocatable c_char compile failed: {}",
1481
+        String::from_utf8_lossy(&compile.stderr)
1482
+    );
1483
+
1484
+    let run = Command::new(&out)
1485
+        .output()
1486
+        .expect("c_loc allocatable c_char run failed");
1487
+    assert!(
1488
+        run.status.success(),
1489
+        "c_loc allocatable c_char run failed: {}",
1490
+        String::from_utf8_lossy(&run.stderr)
1491
+    );
1492
+
1493
+    let _ = std::fs::remove_file(&out);
1494
+    let _ = std::fs::remove_file(&src);
1495
+}
1496
+
8551497
 #[test]
8561498
 fn named_len_char_component_substring_and_trim_compile() {
8571499
     let src = write_program(
@@ -3039,6 +3681,41 @@ fn deferred_char_allocatable_dummy_uses_descriptor_abi() {
30393681
     let _ = std::fs::remove_file(&src);
30403682
 }
30413683
 
3684
+#[test]
3685
+fn deferred_char_allocatable_array_dummy_whole_and_element_assignment_runs() {
3686
+    let src = write_program(
3687
+        "module m\ncontains\n  subroutine fill(tokens)\n    character(len=:), allocatable, intent(out) :: tokens(:)\n    allocate(character(len=32) :: tokens(2))\n    tokens = ''\n    tokens(1) = 'hello'\n    tokens(2) = 'world'\n  end subroutine\nend module\nprogram p\n  use m, only: fill\n  implicit none\n  character(len=:), allocatable :: tokens(:)\n  call fill(tokens)\n  print *, trim(tokens(1)), trim(tokens(2))\nend program\n",
3688
+        "f90",
3689
+    );
3690
+    let out = unique_path("deferred_char_array_dummy", "bin");
3691
+    let compile = Command::new(compiler("armfortas"))
3692
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
3693
+        .output()
3694
+        .expect("spawn failed");
3695
+    assert!(
3696
+        compile.status.success(),
3697
+        "deferred-length allocatable character array dummy should compile and link: {}",
3698
+        String::from_utf8_lossy(&compile.stderr)
3699
+    );
3700
+
3701
+    let run = Command::new(&out).output().expect("run failed");
3702
+    assert!(
3703
+        run.status.success(),
3704
+        "deferred-length allocatable character array dummy binary should run: status={:?} stderr={}",
3705
+        run.status,
3706
+        String::from_utf8_lossy(&run.stderr)
3707
+    );
3708
+    let stdout = String::from_utf8_lossy(&run.stdout);
3709
+    assert!(
3710
+        stdout.contains("hello") && stdout.contains("world"),
3711
+        "deferred char array dummy assignments should preserve element text: {}",
3712
+        stdout
3713
+    );
3714
+
3715
+    let _ = std::fs::remove_file(&out);
3716
+    let _ = std::fs::remove_file(&src);
3717
+}
3718
+
30423719
 #[test]
30433720
 fn use_renamed_procedure_call_uses_remote_symbol() {
30443721
     let dir = unique_dir("use_rename_proc");
@@ -3425,6 +4102,164 @@ fn allocatable_result_helper_assignment_uses_resolved_symbol() {
34254102
     let _ = std::fs::remove_dir_all(&dir);
34264103
 }
34274104
 
4105
+#[test]
4106
+fn deferred_char_function_result_round_trips_across_amod_and_runs() {
4107
+    let dir = unique_dir("deferred_char_result_runtime");
4108
+    let mod_src = write_program_in(
4109
+        &dir,
4110
+        "builder.f90",
4111
+        "module builder\ncontains\n  function make_text(n) result(text)\n    integer, intent(in) :: n\n    integer :: i\n    character(len=:), allocatable :: text\n    allocate(character(len=n) :: text)\n    do i = 1, n\n      text(i:i) = 'x'\n    end do\n  end function make_text\nend module builder\n",
4112
+    );
4113
+    let main_src = write_program_in(
4114
+        &dir,
4115
+        "main.f90",
4116
+        "program p\n  use builder\n  implicit none\n  character(len=:), allocatable :: s\n  s = make_text(3)\n  if (len(s) /= 3) error stop 1\n  if (s /= 'xxx') error stop 2\n  print *, trim(s)\nend program\n",
4117
+    );
4118
+
4119
+    let mod_obj = dir.join("builder.o");
4120
+    let compile_mod = Command::new(compiler("armfortas"))
4121
+        .current_dir(&dir)
4122
+        .args([
4123
+            "-c",
4124
+            "-J",
4125
+            dir.to_str().unwrap(),
4126
+            mod_src.to_str().unwrap(),
4127
+            "-o",
4128
+            mod_obj.to_str().unwrap(),
4129
+        ])
4130
+        .output()
4131
+        .expect("builder module compile spawn failed");
4132
+    assert!(
4133
+        compile_mod.status.success(),
4134
+        "deferred-char builder module should compile: {}",
4135
+        String::from_utf8_lossy(&compile_mod.stderr)
4136
+    );
4137
+
4138
+    let main_obj = dir.join("main.o");
4139
+    let compile_main = Command::new(compiler("armfortas"))
4140
+        .current_dir(&dir)
4141
+        .args([
4142
+            "-c",
4143
+            "-I",
4144
+            dir.to_str().unwrap(),
4145
+            "-J",
4146
+            dir.to_str().unwrap(),
4147
+            main_src.to_str().unwrap(),
4148
+            "-o",
4149
+            main_obj.to_str().unwrap(),
4150
+        ])
4151
+        .output()
4152
+        .expect("main compile spawn failed");
4153
+    assert!(
4154
+        compile_main.status.success(),
4155
+        "imported deferred-char result caller should compile: {}",
4156
+        String::from_utf8_lossy(&compile_main.stderr)
4157
+    );
4158
+
4159
+    let exe = dir.join("deferred_char_result.bin");
4160
+    let link = Command::new(compiler("armfortas"))
4161
+        .current_dir(&dir)
4162
+        .args([
4163
+            mod_obj.to_str().unwrap(),
4164
+            main_obj.to_str().unwrap(),
4165
+            "-o",
4166
+            exe.to_str().unwrap(),
4167
+        ])
4168
+        .output()
4169
+        .expect("link spawn failed");
4170
+    assert!(
4171
+        link.status.success(),
4172
+        "deferred-char result objects should link: {}",
4173
+        String::from_utf8_lossy(&link.stderr)
4174
+    );
4175
+
4176
+    let run = Command::new(&exe).output().expect("run spawn failed");
4177
+    assert!(
4178
+        run.status.success(),
4179
+        "deferred-char result binary should run: status={:?} stderr={}",
4180
+        run.status,
4181
+        String::from_utf8_lossy(&run.stderr)
4182
+    );
4183
+    let stdout = String::from_utf8_lossy(&run.stdout);
4184
+    assert!(
4185
+        stdout.contains("xxx"),
4186
+        "unexpected deferred-char result output: {}",
4187
+        stdout
4188
+    );
4189
+
4190
+    let _ = std::fs::remove_dir_all(&dir);
4191
+}
4192
+
4193
+#[test]
4194
+fn fixed_allocatable_character_substring_compiles_and_runs() {
4195
+    let src = write_program(
4196
+        "program p\n  implicit none\n  character(len=16), allocatable :: buffer\n  allocate(buffer)\n  buffer = ''\n  buffer(1:1) = 'A'\n  if (buffer(1:1) /= 'A') error stop 1\n  print *, trim(buffer)\nend program\n",
4197
+        "f90",
4198
+    );
4199
+    let out = unique_path("alloc_char_substring", "bin");
4200
+    let compile = Command::new(compiler("armfortas"))
4201
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
4202
+        .output()
4203
+        .expect("allocatable-char substring compile spawn failed");
4204
+    assert!(
4205
+        compile.status.success(),
4206
+        "fixed allocatable character substring should compile: {}",
4207
+        String::from_utf8_lossy(&compile.stderr)
4208
+    );
4209
+
4210
+    let run = Command::new(&out).output().expect("run failed");
4211
+    assert!(
4212
+        run.status.success(),
4213
+        "fixed allocatable character substring should run: status={:?} stderr={}",
4214
+        run.status,
4215
+        String::from_utf8_lossy(&run.stderr)
4216
+    );
4217
+    let stdout = String::from_utf8_lossy(&run.stdout);
4218
+    assert!(
4219
+        stdout.contains('A'),
4220
+        "unexpected allocatable-char substring output: {}",
4221
+        stdout
4222
+    );
4223
+
4224
+    let _ = std::fs::remove_file(&out);
4225
+    let _ = std::fs::remove_file(&src);
4226
+}
4227
+
4228
+#[test]
4229
+fn local_array_element_does_not_fall_back_to_unrelated_char_symbol() {
4230
+    let src = write_program(
4231
+        "module helper_mod\ncontains\n  function values(i) result(out)\n    integer, intent(in) :: i\n    character(len=1) :: out\n    if (i > 0) then\n      out = 'x'\n    else\n      out = 'y'\n    end if\n  end function values\nend module helper_mod\n\nprogram p\n  implicit none\n  integer :: values(8)\n  values = 0\n  values(2) = 5\n  if (values(2) >= 1 .and. values(2) <= 12) print *, values(2)\nend program\n",
4232
+        "f90",
4233
+    );
4234
+    let out = unique_path("local_array_element_scope", "bin");
4235
+    let compile = Command::new(compiler("armfortas"))
4236
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
4237
+        .output()
4238
+        .expect("local-array-element compile spawn failed");
4239
+    assert!(
4240
+        compile.status.success(),
4241
+        "local array element should not lower as an unrelated character call: {}",
4242
+        String::from_utf8_lossy(&compile.stderr)
4243
+    );
4244
+
4245
+    let run = Command::new(&out).output().expect("run failed");
4246
+    assert!(
4247
+        run.status.success(),
4248
+        "local array element binary should run: status={:?} stderr={}",
4249
+        run.status,
4250
+        String::from_utf8_lossy(&run.stderr)
4251
+    );
4252
+    let stdout = String::from_utf8_lossy(&run.stdout);
4253
+    assert!(
4254
+        stdout.contains('5'),
4255
+        "unexpected local-array-element output: {}",
4256
+        stdout
4257
+    );
4258
+
4259
+    let _ = std::fs::remove_file(&out);
4260
+    let _ = std::fs::remove_file(&src);
4261
+}
4262
+
34284263
 #[test]
34294264
 fn derived_pointer_module_global_survives_amod_import() {
34304265
     let dir = unique_dir("derived_ptr_amod");
tests/elemental_array_map.rsmodified
@@ -33,8 +33,12 @@ fn function_sections(ir: &str) -> Vec<&str> {
3333
 
3434
 fn function_name<'a>(func_section: &'a str) -> &'a str {
3535
     let header = func_section.lines().next().expect("function header").trim();
36
-    let rest = header.strip_prefix("func @").expect("function header prefix");
37
-    let end = rest.find(|ch: char| ch == ' ' || ch == '(').unwrap_or(rest.len());
36
+    let rest = header
37
+        .strip_prefix("func @")
38
+        .expect("function header prefix");
39
+    let end = rest
40
+        .find(|ch: char| ch == ' ' || ch == '(')
41
+        .unwrap_or(rest.len());
3842
     &rest[..end]
3943
 }
4044
 
tests/fortran_alias_licm.rsmodified
@@ -45,8 +45,12 @@ fn function_sections(ir: &str) -> Vec<&str> {
4545
 
4646
 fn function_name<'a>(func_section: &'a str) -> &'a str {
4747
     let header = func_section.lines().next().expect("function header").trim();
48
-    let rest = header.strip_prefix("func @").expect("function header prefix");
49
-    let end = rest.find(|ch: char| ch == ' ' || ch == '(').unwrap_or(rest.len());
48
+    let rest = header
49
+        .strip_prefix("func @")
50
+        .expect("function header prefix");
51
+    let end = rest
52
+        .find(|ch: char| ch == ' ' || ch == '(')
53
+        .unwrap_or(rest.len());
5054
     &rest[..end]
5155
 }
5256
 
tests/gvn_dse_audit_29_11.rsmodified
@@ -45,8 +45,12 @@ fn function_sections(ir: &str) -> Vec<&str> {
4545
 
4646
 fn function_name<'a>(func_section: &'a str) -> &'a str {
4747
     let header = func_section.lines().next().expect("function header").trim();
48
-    let rest = header.strip_prefix("func @").expect("function header prefix");
49
-    let end = rest.find(|ch: char| ch == ' ' || ch == '(').unwrap_or(rest.len());
48
+    let rest = header
49
+        .strip_prefix("func @")
50
+        .expect("function header prefix");
51
+    let end = rest
52
+        .find(|ch: char| ch == ' ' || ch == '(')
53
+        .unwrap_or(rest.len());
5054
     &rest[..end]
5155
 }
5256
 
tests/ipo_const_arg.rsmodified
@@ -45,8 +45,12 @@ fn function_sections(ir: &str) -> Vec<&str> {
4545
 
4646
 fn function_name<'a>(func_section: &'a str) -> &'a str {
4747
     let header = func_section.lines().next().expect("function header").trim();
48
-    let rest = header.strip_prefix("func @").expect("function header prefix");
49
-    let end = rest.find(|ch: char| ch == ' ' || ch == '(').unwrap_or(rest.len());
48
+    let rest = header
49
+        .strip_prefix("func @")
50
+        .expect("function header prefix");
51
+    let end = rest
52
+        .find(|ch: char| ch == ' ' || ch == '(')
53
+        .unwrap_or(rest.len());
5054
     &rest[..end]
5155
 }
5256
 
tests/ipo_dead_arg.rsmodified
@@ -45,8 +45,12 @@ fn function_sections(ir: &str) -> Vec<&str> {
4545
 
4646
 fn function_name<'a>(func_section: &'a str) -> &'a str {
4747
     let header = func_section.lines().next().expect("function header").trim();
48
-    let rest = header.strip_prefix("func @").expect("function header prefix");
49
-    let end = rest.find(|ch: char| ch == ' ' || ch == '(').unwrap_or(rest.len());
48
+    let rest = header
49
+        .strip_prefix("func @")
50
+        .expect("function header prefix");
51
+    let end = rest
52
+        .find(|ch: char| ch == ' ' || ch == '(')
53
+        .unwrap_or(rest.len());
5054
     &rest[..end]
5155
 }
5256
 
tests/ipo_return_prop.rsmodified
@@ -45,8 +45,12 @@ fn function_sections(ir: &str) -> Vec<&str> {
4545
 
4646
 fn function_name<'a>(func_section: &'a str) -> &'a str {
4747
     let header = func_section.lines().next().expect("function header").trim();
48
-    let rest = header.strip_prefix("func @").expect("function header prefix");
49
-    let end = rest.find(|ch: char| ch == ' ' || ch == '(').unwrap_or(rest.len());
48
+    let rest = header
49
+        .strip_prefix("func @")
50
+        .expect("function header prefix");
51
+    let end = rest
52
+        .find(|ch: char| ch == ' ' || ch == '(')
53
+        .unwrap_or(rest.len());
5054
     &rest[..end]
5155
 }
5256
 
tests/licm_lsf_audit_29_11.rsmodified
@@ -45,8 +45,12 @@ fn function_sections(ir: &str) -> Vec<&str> {
4545
 
4646
 fn function_name<'a>(func_section: &'a str) -> &'a str {
4747
     let header = func_section.lines().next().expect("function header").trim();
48
-    let rest = header.strip_prefix("func @").expect("function header prefix");
49
-    let end = rest.find(|ch: char| ch == ' ' || ch == '(').unwrap_or(rest.len());
48
+    let rest = header
49
+        .strip_prefix("func @")
50
+        .expect("function header prefix");
51
+    let end = rest
52
+        .find(|ch: char| ch == ' ' || ch == '(')
53
+        .unwrap_or(rest.len());
5054
     &rest[..end]
5155
 }
5256
 
tests/module_host_audit.rsmodified
@@ -54,8 +54,12 @@ fn function_sections(ir: &str) -> Vec<&str> {
5454
 
5555
 fn function_name<'a>(func_section: &'a str) -> &'a str {
5656
     let header = func_section.lines().next().expect("function header").trim();
57
-    let rest = header.strip_prefix("func @").expect("function header prefix");
58
-    let end = rest.find(|ch: char| ch == ' ' || ch == '(').unwrap_or(rest.len());
57
+    let rest = header
58
+        .strip_prefix("func @")
59
+        .expect("function header prefix");
60
+    let end = rest
61
+        .find(|ch: char| ch == ' ' || ch == '(')
62
+        .unwrap_or(rest.len());
5963
     &rest[..end]
6064
 }
6165
 
tests/opt_audit_29_11.rsmodified
@@ -41,8 +41,12 @@ fn function_sections(ir: &str) -> Vec<&str> {
4141
 
4242
 fn function_name<'a>(func_section: &'a str) -> &'a str {
4343
     let header = func_section.lines().next().expect("function header").trim();
44
-    let rest = header.strip_prefix("func @").expect("function header prefix");
45
-    let end = rest.find(|ch: char| ch == ' ' || ch == '(').unwrap_or(rest.len());
44
+    let rest = header
45
+        .strip_prefix("func @")
46
+        .expect("function header prefix");
47
+    let end = rest
48
+        .find(|ch: char| ch == ' ' || ch == '(')
49
+        .unwrap_or(rest.len());
4650
     &rest[..end]
4751
 }
4852
 
tests/pure_call_reuse.rsmodified
@@ -45,8 +45,12 @@ fn function_sections(ir: &str) -> Vec<&str> {
4545
 
4646
 fn function_name<'a>(func_section: &'a str) -> &'a str {
4747
     let header = func_section.lines().next().expect("function header").trim();
48
-    let rest = header.strip_prefix("func @").expect("function header prefix");
49
-    let end = rest.find(|ch: char| ch == ' ' || ch == '(').unwrap_or(rest.len());
48
+    let rest = header
49
+        .strip_prefix("func @")
50
+        .expect("function header prefix");
51
+    let end = rest
52
+        .find(|ch: char| ch == ' ' || ch == '(')
53
+        .unwrap_or(rest.len());
5054
     &rest[..end]
5155
 }
5256
 
tests/pure_dead_call_elim.rsmodified
@@ -45,8 +45,12 @@ fn function_sections(ir: &str) -> Vec<&str> {
4545
 
4646
 fn function_name<'a>(func_section: &'a str) -> &'a str {
4747
     let header = func_section.lines().next().expect("function header").trim();
48
-    let rest = header.strip_prefix("func @").expect("function header prefix");
49
-    let end = rest.find(|ch: char| ch == ' ' || ch == '(').unwrap_or(rest.len());
48
+    let rest = header
49
+        .strip_prefix("func @")
50
+        .expect("function header prefix");
51
+    let end = rest
52
+        .find(|ch: char| ch == ' ' || ch == '(')
53
+        .unwrap_or(rest.len());
5054
     &rest[..end]
5155
 }
5256
 
tests/sroa_shape_audit_29_11.rsmodified
@@ -54,8 +54,12 @@ fn function_sections(ir: &str) -> Vec<&str> {
5454
 
5555
 fn function_name<'a>(func_section: &'a str) -> &'a str {
5656
     let header = func_section.lines().next().expect("function header").trim();
57
-    let rest = header.strip_prefix("func @").expect("function header prefix");
58
-    let end = rest.find(|ch: char| ch == ' ' || ch == '(').unwrap_or(rest.len());
57
+    let rest = header
58
+        .strip_prefix("func @")
59
+        .expect("function header prefix");
60
+    let end = rest
61
+        .find(|ch: char| ch == ' ' || ch == '(')
62
+        .unwrap_or(rest.len());
5963
     &rest[..end]
6064
 }
6165