fortrangoingonforty/armfortas / e7acdc6

Browse files

Preserve driver and ABI fixes

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
e7acdc6ef04e01d1556a4492fccf416d65ff3daf
Parents
44ed46a
Tree
93b66fd

10 changed files

StatusFile+-
M src/driver/mod.rs 82 0
M src/ir/lower.rs 1065 78
M src/sema/amod.rs 24 1
M src/sema/resolve.rs 6 0
M src/sema/type_layout.rs 56 7
M tests/allocate_constructor_runtime.rs 4 1
M tests/allocate_validation.rs 4 1
M tests/cli_driver.rs 905 76
M tests/memory_runtime.rs 20 4
M tests/regalloc_runtime.rs 16 2
src/driver/mod.rsmodified
@@ -112,6 +112,7 @@ pub struct Options {
112112
     pub emit_tokens: bool,     // --emit-tokens
113113
     pub preprocess_only: bool, // -E
114114
     pub preprocessor_defines: Vec<(String, String)>,
115
+    pub cpp_compat: bool, // -cpp (accepted; preprocessing already runs)
115116
 
116117
     // ---- Language ----
117118
     pub std: Option<crate::sema::validate::FortranStandard>,
@@ -142,6 +143,7 @@ pub struct Options {
142143
     pub diagnostics_format: DiagnosticsFormat, // --diagnostics-format=
143144
     pub check_bounds: bool,                    // -fcheck=bounds
144145
     pub check_all: bool,                       // -fcheck=all
146
+    pub backtrace_requested: bool,             // -fbacktrace (accepted; runtime wiring TODO)
145147
 
146148
     // ---- Search paths / linking ----
147149
     /// Directories to search for `.amod` module files (`-I <dir>`).
@@ -178,6 +180,7 @@ impl Default for Options {
178180
             emit_tokens: false,
179181
             preprocess_only: false,
180182
             preprocessor_defines: Vec::new(),
183
+            cpp_compat: false,
181184
             std: Some(crate::sema::validate::FortranStandard::F2018),
182185
             source_form_override: None,
183186
             default_integer_8: false,
@@ -200,6 +203,7 @@ impl Default for Options {
200203
             diagnostics_format: DiagnosticsFormat::Text,
201204
             check_bounds: false,
202205
             check_all: false,
206
+            backtrace_requested: false,
203207
             module_search_paths: Vec::new(),
204208
             module_output_dir: None,
205209
             library_search_paths: Vec::new(),
@@ -283,6 +287,7 @@ pub fn parse_cli(raw_args: &[String]) -> Result<ParsedCli, String> {
283287
             "-S" => opts.emit_asm = true,
284288
             "-c" => opts.emit_obj = true,
285289
             "-E" => opts.preprocess_only = true,
290
+            "-cpp" => opts.cpp_compat = true,
286291
             "-D" => {
287292
                 i += 1;
288293
                 let spec = args.get(i).ok_or("-D requires a macro name")?;
@@ -359,6 +364,21 @@ pub fn parse_cli(raw_args: &[String]) -> Result<ParsedCli, String> {
359364
             "-static" => opts.static_link = true,
360365
 
361366
             // ---- Standards / language flags ----
367
+            arg if arg.starts_with("-std=") => {
368
+                let val = &arg["-std=".len()..];
369
+                opts.std = Some(
370
+                    crate::sema::validate::FortranStandard::parse_flag(val)
371
+                        .ok_or_else(|| format!("unknown -std value: {}", val))?,
372
+                );
373
+            }
374
+            "-std" => {
375
+                i += 1;
376
+                let val = args.get(i).ok_or("-std requires a value")?;
377
+                opts.std = Some(
378
+                    crate::sema::validate::FortranStandard::parse_flag(val)
379
+                        .ok_or_else(|| format!("unknown -std value: {}", val))?,
380
+                );
381
+            }
362382
             arg if arg.starts_with("--std=") => {
363383
                 let val = &arg["--std=".len()..];
364384
                 opts.std = Some(
@@ -396,6 +416,7 @@ pub fn parse_cli(raw_args: &[String]) -> Result<ParsedCli, String> {
396416
                 opts.check_bounds = true;
397417
                 opts.check_all = true;
398418
             }
419
+            "-fbacktrace" => opts.backtrace_requested = true,
399420
 
400421
             // ---- Warnings (accepted; gating is gradual sprint work) ----
401422
             "-Wall" => opts.warn_all = true,
@@ -628,6 +649,13 @@ fn set_output_path(opts: &mut Options, value: &str) -> Result<(), String> {
628649
 }
629650
 
630651
 fn collect_cli_warnings(opts: &mut Options, unknown_warning_flags: &[String]) {
652
+    if opts.cpp_compat {
653
+        opts.cli_warnings.push(
654
+            "-cpp is accepted for compatibility; preprocessing already runs for Fortran inputs"
655
+                .into(),
656
+        );
657
+    }
658
+
631659
     if opts.check_all {
632660
         opts.cli_warnings.push(
633661
             "-fcheck=all is accepted, but only array bounds checks exist today and those are already always enabled".into(),
@@ -665,6 +693,11 @@ fn collect_cli_warnings(opts: &mut Options, unknown_warning_flags: &[String]) {
665693
         opts.cli_warnings
666694
             .push("-g is accepted, but debug info emission is not yet implemented".into());
667695
     }
696
+    if opts.backtrace_requested {
697
+        opts.cli_warnings.push(
698
+            "-fbacktrace is accepted, but runtime backtrace control is not yet implemented".into(),
699
+        );
700
+    }
668701
 
669702
     let suppress_unknown_warning_option = opts
670703
         .disabled_warnings
@@ -687,10 +720,12 @@ COMPILATION:
687720
   -c                          Compile to object file only (no linking)
688721
   -S                          Emit assembly text
689722
   -E                          Preprocess only
723
+  -cpp                        Accept GNU-style preprocessing flag
690724
   -D<name>[=<value>]          Define a preprocessor macro
691725
   -o <file>                   Output file name
692726
 
693727
 LANGUAGE:
728
+  -std=<standard>             GNU-compatible alias for --std=<standard>
694729
   --std=<standard>            Fortran standard (f77, f90, f95, f2003, f2008, f2018, f2023)
695730
   -ffree-form                 Force free-form source
696731
   -ffixed-form                Force fixed-form source
@@ -716,6 +751,7 @@ WARNINGS:
716751
 
717752
 DEBUGGING:
718753
   -g                          Generate debug information (DWARF emission TODO)
754
+  -fbacktrace                 Accept GNU-style runtime backtrace flag
719755
   --emit-ir                   Dump IR to the output path
720756
   --emit-ast                  Dump AST to the output path
721757
   --emit-tokens               Dump token stream to the output path
@@ -1923,6 +1959,52 @@ mod tests {
19231959
         );
19241960
     }
19251961
 
1962
+    #[test]
1963
+    fn options_from_args_accepts_gnu_std_alias() {
1964
+        let args = vec!["-std=f2008".to_string(), "hello.f90".to_string()];
1965
+        let opts = Options::from_args(&args).expect("driver should accept -std=f2008");
1966
+        assert_eq!(
1967
+            opts.std,
1968
+            Some(crate::sema::validate::FortranStandard::F2008)
1969
+        );
1970
+    }
1971
+
1972
+    #[test]
1973
+    fn parse_cli_warns_for_cpp_compat_flag() {
1974
+        let args = vec!["-cpp".to_string(), "hello.f90".to_string()];
1975
+        let ParsedCli::Compile(opts) = parse_cli(&args).expect("driver should accept -cpp") else {
1976
+            panic!("expected compile options");
1977
+        };
1978
+        assert!(opts.cpp_compat, "-cpp should be recorded on the options");
1979
+        assert!(
1980
+            opts.cli_warnings
1981
+                .iter()
1982
+                .any(|warning| warning.contains("-cpp is accepted for compatibility")),
1983
+            "expected a compatibility warning for -cpp, got {:?}",
1984
+            opts.cli_warnings
1985
+        );
1986
+    }
1987
+
1988
+    #[test]
1989
+    fn parse_cli_warns_for_fbacktrace_flag() {
1990
+        let args = vec!["-fbacktrace".to_string(), "hello.f90".to_string()];
1991
+        let ParsedCli::Compile(opts) = parse_cli(&args).expect("driver should accept -fbacktrace")
1992
+        else {
1993
+            panic!("expected compile options");
1994
+        };
1995
+        assert!(
1996
+            opts.backtrace_requested,
1997
+            "-fbacktrace should be recorded on the options"
1998
+        );
1999
+        assert!(
2000
+            opts.cli_warnings.iter().any(|warning| warning.contains(
2001
+                "-fbacktrace is accepted, but runtime backtrace control is not yet implemented"
2002
+            )),
2003
+            "expected a compatibility warning for -fbacktrace, got {:?}",
2004
+            opts.cli_warnings
2005
+        );
2006
+    }
2007
+
19262008
     fn i128_fixture() -> PathBuf {
19272009
         let path = PathBuf::from("tests/fixtures").join("integer16_ir.f90");
19282010
         assert!(path.exists(), "missing test fixture {}", path.display());
src/ir/lower.rsmodified
1414 lines changed — click to load
@@ -2852,7 +2852,13 @@ fn lower_unit(
28522852
                     else {
28532853
                         continue;
28542854
                     };
2855
-                    let len_raw = lower_expr(&mut b, &ctx.locals, &len_expr, ctx.st);
2855
+                    let len_raw = lower_expr_with_optional_layouts(
2856
+                        &mut b,
2857
+                        &ctx.locals,
2858
+                        &len_expr,
2859
+                        ctx.st,
2860
+                        Some(type_layouts),
2861
+                    );
28562862
                     let len_addr = b.alloca(IrType::Int(IntWidth::I64));
28572863
                     let len_val = clamp_nonnegative_i64(&mut b, len_raw);
28582864
                     b.store(len_val, len_addr);
@@ -2875,6 +2881,7 @@ fn lower_unit(
28752881
                     decls,
28762882
                     &visible_param_consts,
28772883
                     ctx.st,
2884
+                    type_layouts,
28782885
                 );
28792886
 
28802887
                 install_common_locals(&mut b, &mut ctx.locals, decls);
@@ -3266,6 +3273,7 @@ fn lower_unit(
32663273
                     decls,
32673274
                     &visible_param_consts,
32683275
                     ctx.st,
3276
+                    type_layouts,
32693277
                 );
32703278
 
32713279
                 let result_name = result.as_deref().unwrap_or(name.as_str()).to_lowercase();
@@ -5234,7 +5242,13 @@ fn alloc_decls(
52345242
                                             .map(|value| b.const_i64(value))
52355243
                                             .unwrap_or_else(|| {
52365244
                                                 if let Some(expr) = lower.as_ref() {
5237
-                                                    let raw = lower_expr(b, locals, expr, st);
5245
+                                                    let raw = lower_expr_with_optional_layouts(
5246
+                                                        b,
5247
+                                                        locals,
5248
+                                                        expr,
5249
+                                                        st,
5250
+                                                        Some(type_layouts),
5251
+                                                    );
52385252
                                                     widen_to_i64(b, raw)
52395253
                                                 } else {
52405254
                                                     b.const_i64(1)
@@ -5244,7 +5258,13 @@ fn alloc_decls(
52445258
                                             eval_const_array_bound(upper, &param_consts, Some(st))
52455259
                                                 .map(|value| b.const_i64(value))
52465260
                                                 .unwrap_or_else(|| {
5247
-                                                    let raw = lower_expr(b, locals, upper, st);
5261
+                                                    let raw = lower_expr_with_optional_layouts(
5262
+                                                        b,
5263
+                                                        locals,
5264
+                                                        upper,
5265
+                                                        st,
5266
+                                                        Some(type_layouts),
5267
+                                                    );
52485268
                                                     widen_to_i64(b, raw)
52495269
                                                 });
52505270
                                         (lo64, up64)
@@ -5454,7 +5474,13 @@ fn alloc_decls(
54545474
                         // runtime expression such as LEN(input). Materialize a
54555475
                         // heap buffer now and remember the runtime length for
54565476
                         // substring and assignment lowering.
5457
-                        let raw_len = lower_expr(b, locals, len_expr, st);
5477
+                        let raw_len = lower_expr_with_optional_layouts(
5478
+                            b,
5479
+                            locals,
5480
+                            len_expr,
5481
+                            st,
5482
+                            Some(type_layouts),
5483
+                        );
54585484
                         let len_val = clamp_nonnegative_i64(b, raw_len);
54595485
                         let len_addr = b.alloca(IrType::Int(IntWidth::I64));
54605486
                         b.store(len_val, len_addr);
@@ -7863,10 +7889,14 @@ fn lower_intrinsic(b: &mut FuncBuilder, name: &str, args: &[ValueId]) -> Option<
78637889
             // ishft(a, shift): positive shift = left, negative = right.
78647890
             // For now, only handle positive (left shift). Full impl needs Select.
78657891
             if args.len() >= 2 {
7866
-                let zero = b.const_i32(0);
7892
+                let shift_cmp_width = int_width_of_value(b, args[1]).unwrap_or(IntWidth::I32);
7893
+                let zero = int_const_for_width(b, shift_cmp_width, 0);
78677894
                 let is_left = b.icmp(CmpOp::Ge, args[1], zero);
78687895
                 let neg_shift = b.ineg(args[1]);
7869
-                let left = b.shl(args[0], args[1]);
7896
+                let value_width = int_width_of_value(b, args[0]).unwrap_or(IntWidth::I32);
7897
+                let shift = coerce_int_like_to_width(b, args[1], value_width);
7898
+                let neg_shift = coerce_int_like_to_width(b, neg_shift, value_width);
7899
+                let left = b.shl(args[0], shift);
78707900
                 let right = b.lshr(args[0], neg_shift);
78717901
                 Some(b.select(is_left, left, right))
78727902
             } else {
@@ -7876,10 +7906,12 @@ fn lower_intrinsic(b: &mut FuncBuilder, name: &str, args: &[ValueId]) -> Option<
78767906
         "btest" => {
78777907
             // btest(a, pos) = (a >> pos) & 1 /= 0
78787908
             if args.len() >= 2 {
7879
-                let shifted = b.lshr(args[0], args[1]);
7880
-                let one = b.const_i32(1);
7909
+                let value_width = int_width_of_value(b, args[0]).unwrap_or(IntWidth::I32);
7910
+                let pos = coerce_int_like_to_width(b, args[1], value_width);
7911
+                let shifted = b.lshr(args[0], pos);
7912
+                let one = int_const_for_width(b, value_width, 1);
78817913
                 let masked = b.bit_and(shifted, one);
7882
-                let zero = b.const_i32(0);
7914
+                let zero = int_const_for_width(b, value_width, 0);
78837915
                 Some(b.icmp(CmpOp::Ne, masked, zero))
78847916
             } else {
78857917
                 None
@@ -7888,8 +7920,10 @@ fn lower_intrinsic(b: &mut FuncBuilder, name: &str, args: &[ValueId]) -> Option<
78887920
         "ibset" => {
78897921
             // ibset(a, pos) = a | (1 << pos)
78907922
             if args.len() >= 2 {
7891
-                let one = b.const_i32(1);
7892
-                let mask = b.shl(one, args[1]);
7923
+                let value_width = int_width_of_value(b, args[0]).unwrap_or(IntWidth::I32);
7924
+                let one = int_const_for_width(b, value_width, 1);
7925
+                let pos = coerce_int_like_to_width(b, args[1], value_width);
7926
+                let mask = b.shl(one, pos);
78937927
                 Some(b.bit_or(args[0], mask))
78947928
             } else {
78957929
                 None
@@ -7898,8 +7932,10 @@ fn lower_intrinsic(b: &mut FuncBuilder, name: &str, args: &[ValueId]) -> Option<
78987932
         "ibclr" => {
78997933
             // ibclr(a, pos) = a & ~(1 << pos)
79007934
             if args.len() >= 2 {
7901
-                let one = b.const_i32(1);
7902
-                let mask = b.shl(one, args[1]);
7935
+                let value_width = int_width_of_value(b, args[0]).unwrap_or(IntWidth::I32);
7936
+                let one = int_const_for_width(b, value_width, 1);
7937
+                let pos = coerce_int_like_to_width(b, args[1], value_width);
7938
+                let mask = b.shl(one, pos);
79037939
                 let inv = b.bit_not(mask);
79047940
                 Some(b.bit_and(args[0], inv))
79057941
             } else {
@@ -7909,10 +7945,13 @@ fn lower_intrinsic(b: &mut FuncBuilder, name: &str, args: &[ValueId]) -> Option<
79097945
         "ibits" => {
79107946
             // ibits(i, pos, len) = (i >> pos) & ((1 << len) - 1)
79117947
             if args.len() >= 3 {
7912
-                let shifted = b.lshr(args[0], args[1]);
7913
-                let one = b.const_i32(1);
7914
-                let mask_hi = b.shl(one, args[2]);
7915
-                let one2 = b.const_i32(1);
7948
+                let value_width = int_width_of_value(b, args[0]).unwrap_or(IntWidth::I32);
7949
+                let pos = coerce_int_like_to_width(b, args[1], value_width);
7950
+                let len = coerce_int_like_to_width(b, args[2], value_width);
7951
+                let shifted = b.lshr(args[0], pos);
7952
+                let one = int_const_for_width(b, value_width, 1);
7953
+                let mask_hi = b.shl(one, len);
7954
+                let one2 = int_const_for_width(b, value_width, 1);
79167955
                 let mask = b.isub(mask_hi, one2);
79177956
                 Some(b.bit_and(shifted, mask))
79187957
             } else {
@@ -8071,31 +8110,28 @@ fn lower_intrinsic(b: &mut FuncBuilder, name: &str, args: &[ValueId]) -> Option<
80718110
         "ishftc" => {
80728111
             // ishftc(a, shift, size): circular shift of the rightmost `size` bits.
80738112
             if args.len() >= 2 {
8074
-                let ty = b
8075
-                    .func()
8076
-                    .value_type(args[0])
8077
-                    .unwrap_or(IrType::Int(IntWidth::I32));
8078
-                let default_size = match &ty {
8079
-                    IrType::Int(IntWidth::I64) => 64,
8080
-                    IrType::Int(IntWidth::I16) => 16,
8081
-                    IrType::Int(IntWidth::I8) => 8,
8113
+                let value_width = int_width_of_value(b, args[0]).unwrap_or(IntWidth::I32);
8114
+                let default_size = match value_width {
8115
+                    IntWidth::I64 => 64,
8116
+                    IntWidth::I16 => 16,
8117
+                    IntWidth::I8 => 8,
80828118
                     _ => 32,
80838119
                 };
80848120
                 let size = if args.len() >= 3 {
8085
-                    args[2]
8121
+                    coerce_int_like_to_width(b, args[2], value_width)
80868122
                 } else {
8087
-                    b.const_i32(default_size)
8123
+                    int_const_for_width(b, value_width, default_size)
80888124
                 };
8089
-                let shift = args[1];
8125
+                let shift = coerce_int_like_to_width(b, args[1], value_width);
80908126
                 // left = (a << shift) | (a >> (size - shift)), masked to size bits.
80918127
                 let left = b.shl(args[0], shift);
80928128
                 let diff = b.isub(size, shift);
80938129
                 let right = b.lshr(args[0], diff);
80948130
                 let combined = b.bit_or(left, right);
80958131
                 // Mask to `size` bits: combined & ((1 << size) - 1).
8096
-                let one = b.const_i32(1);
8132
+                let one = int_const_for_width(b, value_width, 1);
80978133
                 let shifted_one = b.shl(one, size);
8098
-                let one2 = b.const_i32(1);
8134
+                let one2 = int_const_for_width(b, value_width, 1);
80998135
                 let mask = b.isub(shifted_one, one2);
81008136
                 Some(b.bit_and(combined, mask))
81018137
             } else {
@@ -8665,17 +8701,23 @@ fn reorder_args_by_keyword_slots(
86658701
     args: &[crate::ast::expr::Argument],
86668702
     callee_key: &str,
86678703
     st: &SymbolTable,
8704
+) -> Vec<Option<crate::ast::expr::Argument>> {
8705
+    reorder_args_by_keyword_slots_with_formal_skip(args, callee_key, st, 0)
8706
+}
8707
+
8708
+fn reorder_args_by_keyword_slots_with_formal_skip(
8709
+    args: &[crate::ast::expr::Argument],
8710
+    callee_key: &str,
8711
+    st: &SymbolTable,
8712
+    formal_skip: usize,
86688713
 ) -> Vec<Option<crate::ast::expr::Argument>> {
86698714
     // Fast path: no keyword args anywhere → pass through.
8670
-    if args.iter().all(|a| a.keyword.is_none()) {
8671
-        return args.iter().cloned().map(Some).collect();
8672
-    }
86738715
     // Look up the callee's declared param order. NamedInterface
86748716
     // symbols live in scope.symbols keyed by lowercase name and
86758717
     // carry the specifics in arg_names; we want the actual
86768718
     // procedure's arg_order, which lives on its scope.
86778719
     use crate::sema::symtab::ScopeKind;
8678
-    let arg_order: Vec<String> = {
8720
+    let arg_order: Option<Vec<String>> = {
86798721
         let mut found: Option<Vec<String>> = None;
86808722
         for scope in st.all_scopes() {
86818723
             if let ScopeKind::Function(n) | ScopeKind::Subroutine(n) = &scope.kind {
@@ -8686,20 +8728,23 @@ fn reorder_args_by_keyword_slots(
86868728
             }
86878729
         }
86888730
         match found {
8689
-            Some(v) if !v.is_empty() => v,
8690
-            _ => match intrinsic_subroutine_arg_order(callee_key) {
8691
-                Some(names) => names.iter().map(|name| (*name).to_string()).collect(),
8692
-                None => return args.iter().cloned().map(Some).collect(), // no signature info → no reorder
8693
-            },
8731
+            Some(v) if !v.is_empty() => Some(v),
8732
+            _ => intrinsic_subroutine_arg_order(callee_key)
8733
+                .map(|names| names.iter().map(|name| (*name).to_string()).collect()),
86948734
         }
86958735
     };
8736
+    let Some(arg_order) = arg_order else {
8737
+        let mut slots: Vec<Option<crate::ast::expr::Argument>> = vec![None; formal_skip];
8738
+        slots.extend(args.iter().cloned().map(Some));
8739
+        return slots;
8740
+    };
86968741
     // Build slot list the size of the callee's declared params.
86978742
     // Positional actuals fill slots 0..K. Keyword actuals look up
86988743
     // their slot by name. Unused slots stay None (OPTIONAL args
86998744
     // get their runtime null treatment in the subsequent per-call
87008745
     // hidden-arg logic).
87018746
     let mut slots: Vec<Option<crate::ast::expr::Argument>> = vec![None; arg_order.len()];
8702
-    let mut last_positional = 0usize;
8747
+    let mut last_positional = formal_skip.min(slots.len());
87038748
     for a in args {
87048749
         if let Some(kw) = &a.keyword {
87058750
             let key = kw.to_lowercase();
@@ -8923,6 +8968,45 @@ fn symbol_link_name(st: &SymbolTable, sym: &crate::sema::symtab::Symbol) -> Stri
89238968
     sym.name.clone()
89248969
 }
89258970
 
8971
+fn abi_key_for_link_name(st: &SymbolTable, link_name: &str) -> Option<String> {
8972
+    use crate::sema::symtab::SymbolKind;
8973
+
8974
+    let link_lower = link_name.to_lowercase();
8975
+    for scope in st.all_scopes() {
8976
+        for sym in scope.symbols.values() {
8977
+            if matches!(
8978
+                sym.kind,
8979
+                SymbolKind::Function
8980
+                    | SymbolKind::Subroutine
8981
+                    | SymbolKind::ExternalProc
8982
+                    | SymbolKind::IntrinsicProc
8983
+                    | SymbolKind::ProcedurePointer
8984
+            ) && symbol_link_name(st, sym).eq_ignore_ascii_case(link_name)
8985
+            {
8986
+                return Some(sym.name.to_lowercase());
8987
+            }
8988
+        }
8989
+    }
8990
+    for scope in st.all_scopes() {
8991
+        for sym in scope.symbols.values() {
8992
+            if matches!(
8993
+                sym.kind,
8994
+                SymbolKind::Function
8995
+                    | SymbolKind::Subroutine
8996
+                    | SymbolKind::ExternalProc
8997
+                    | SymbolKind::IntrinsicProc
8998
+                    | SymbolKind::ProcedurePointer
8999
+            ) {
9000
+                let suffix = format!("_{}", sym.name.to_lowercase());
9001
+                if link_lower.ends_with(&suffix) {
9002
+                    return Some(sym.name.to_lowercase());
9003
+                }
9004
+            }
9005
+        }
9006
+    }
9007
+    None
9008
+}
9009
+
89269010
 fn find_linkable_symbol_any_scope<'a>(
89279011
     st: &'a SymbolTable,
89289012
     key: &str,
@@ -9272,6 +9356,211 @@ fn emit_named_function_call(
92729356
     b.call(func_ref, call_args, ret_ty)
92739357
 }
92749358
 
9359
+fn emit_bound_function_call(
9360
+    b: &mut FuncBuilder,
9361
+    locals: &HashMap<String, LocalInfo>,
9362
+    st: &SymbolTable,
9363
+    type_layouts: Option<&crate::sema::type_layout::TypeLayoutRegistry>,
9364
+    internal_funcs: Option<&HashMap<String, u32>>,
9365
+    contained_host_refs: Option<&HashMap<String, Vec<String>>>,
9366
+    descriptor_params: Option<&HashMap<String, Vec<bool>>>,
9367
+    base: &crate::ast::expr::SpannedExpr,
9368
+    component: &str,
9369
+    args: &[crate::ast::expr::Argument],
9370
+    hidden_result: Option<ValueId>,
9371
+    ret_ty: IrType,
9372
+) -> Option<ValueId> {
9373
+    let tl = type_layouts?;
9374
+    let (obj_addr, type_name) = resolve_component_base_for_method(b, locals, base, st, tl)?;
9375
+    let layout = tl.get(&type_name)?;
9376
+    let bp = layout.bound_proc(component)?;
9377
+    let target = bp.target_name.clone();
9378
+    let target_key = abi_key_for_link_name(st, &target).unwrap_or_else(|| bp.abi_name.clone());
9379
+    let (call_name, _callee_key) = resolved_symbol_call_target(st, &target_key, &target);
9380
+    let nopass = bp.nopass;
9381
+    let arg_slots =
9382
+        reorder_args_by_keyword_slots_with_formal_skip(args, &target_key, st, if nopass { 0 } else { 1 });
9383
+    let abi_lookup_keys = procedure_abi_lookup_keys(st, &[&target_key]);
9384
+    let abi_primary_key = abi_lookup_keys
9385
+        .first()
9386
+        .map(String::as_str)
9387
+        .unwrap_or(target_key.as_str());
9388
+    let callee_value_args =
9389
+        first_procedure_lookup(&abi_lookup_keys, |k| callee_value_arg_mask(st, k));
9390
+    let callee_descriptor_args = first_procedure_lookup(&abi_lookup_keys, |k| {
9391
+        descriptor_params.and_then(|m| m.get(k).cloned())
9392
+    });
9393
+    let callee_string_descriptor_args = first_procedure_lookup(&abi_lookup_keys, |k| {
9394
+        callee_string_descriptor_arg_mask(st, k)
9395
+    });
9396
+    let callee_bind_c_char_args =
9397
+        first_procedure_lookup(&abi_lookup_keys, |k| callee_bind_c_char_arg_mask(st, k));
9398
+    let callee_pointer_args =
9399
+        first_procedure_lookup(&abi_lookup_keys, |k| callee_pointer_arg_mask(st, k));
9400
+    let opt_flags =
9401
+        first_procedure_lookup(&abi_lookup_keys, |k| callee_optional_arg_mask(st, k));
9402
+    let callee_char_len_star_args =
9403
+        first_procedure_lookup(&abi_lookup_keys, |k| callee_char_len_star_mask(st, k));
9404
+
9405
+    let mut call_args =
9406
+        Vec::with_capacity(arg_slots.len() + hidden_result.is_some() as usize + (!nopass) as usize);
9407
+    if let Some(result) = hidden_result {
9408
+        call_args.push(result);
9409
+    }
9410
+    if !nopass {
9411
+        call_args.push(obj_addr);
9412
+    }
9413
+
9414
+    for (i, slot) in arg_slots
9415
+        .iter()
9416
+        .enumerate()
9417
+        .skip(if nopass { 0 } else { 1 })
9418
+    {
9419
+        let is_value = callee_value_args
9420
+            .as_ref()
9421
+            .map(|mask| mask.get(i).copied().unwrap_or(false))
9422
+            .unwrap_or(false);
9423
+        let wants_descriptor = callee_descriptor_args
9424
+            .as_ref()
9425
+            .map(|mask| mask.get(i).copied().unwrap_or(false))
9426
+            .unwrap_or(false);
9427
+        let wants_string_descriptor = callee_string_descriptor_args
9428
+            .as_ref()
9429
+            .map(|mask| mask.get(i).copied().unwrap_or(false))
9430
+            .unwrap_or(false);
9431
+        let wants_bind_c_char = callee_bind_c_char_args
9432
+            .as_ref()
9433
+            .map(|mask| mask.get(i).copied().unwrap_or(false))
9434
+            .unwrap_or(false);
9435
+        let wants_pointer = callee_pointer_args
9436
+            .as_ref()
9437
+            .map(|mask| mask.get(i).copied().unwrap_or(false))
9438
+            .unwrap_or(false);
9439
+        let value = match slot {
9440
+            Some(arg) => match &arg.value {
9441
+                crate::ast::expr::SectionSubscript::Element(e) => {
9442
+                    if is_value && wants_bind_c_char {
9443
+                        lower_bind_c_char_value_arg(
9444
+                            b,
9445
+                            locals,
9446
+                            e,
9447
+                            st,
9448
+                            type_layouts,
9449
+                            internal_funcs,
9450
+                            contained_host_refs,
9451
+                            descriptor_params,
9452
+                        )
9453
+                    } else if is_value {
9454
+                        let raw = lower_expr_full(
9455
+                            b,
9456
+                            locals,
9457
+                            e,
9458
+                            st,
9459
+                            type_layouts,
9460
+                            internal_funcs,
9461
+                            contained_host_refs,
9462
+                            descriptor_params,
9463
+                        );
9464
+                        coerce_value_call_arg(b, st, abi_primary_key, i, raw)
9465
+                    } else if wants_descriptor {
9466
+                        lower_arg_descriptor(b, locals, e, st, type_layouts)
9467
+                    } else if wants_string_descriptor {
9468
+                        lower_arg_string_descriptor(b, locals, e, st, type_layouts)
9469
+                    } else if wants_bind_c_char {
9470
+                        lower_bind_c_char_arg_raw(
9471
+                            b,
9472
+                            locals,
9473
+                            e,
9474
+                            st,
9475
+                            type_layouts,
9476
+                            internal_funcs,
9477
+                            contained_host_refs,
9478
+                            descriptor_params,
9479
+                        )
9480
+                    } else if wants_pointer {
9481
+                        lower_pointer_dummy_actual(b, locals, e, st, type_layouts)
9482
+                            .unwrap_or_else(|| {
9483
+                                lower_arg_by_ref_full(
9484
+                                    b,
9485
+                                    locals,
9486
+                                    e,
9487
+                                    st,
9488
+                                    type_layouts,
9489
+                                    internal_funcs,
9490
+                                    contained_host_refs,
9491
+                                    descriptor_params,
9492
+                                )
9493
+                            })
9494
+                    } else {
9495
+                        lower_arg_by_ref_full(
9496
+                            b,
9497
+                            locals,
9498
+                            e,
9499
+                            st,
9500
+                            type_layouts,
9501
+                            internal_funcs,
9502
+                            contained_host_refs,
9503
+                            descriptor_params,
9504
+                        )
9505
+                    }
9506
+                }
9507
+                _ => b.const_i32(0),
9508
+            },
9509
+            None => missing_optional_call_arg(b, st, abi_primary_key, i, is_value),
9510
+        };
9511
+        call_args.push(value);
9512
+    }
9513
+
9514
+    if let Some(opt_flags) = opt_flags {
9515
+        for (i, flag) in opt_flags.iter().enumerate().skip(arg_slots.len()) {
9516
+            if *flag {
9517
+                let is_value = callee_value_args
9518
+                    .as_ref()
9519
+                    .map(|mask| mask.get(i).copied().unwrap_or(false))
9520
+                    .unwrap_or(false);
9521
+                call_args.push(missing_optional_call_arg(
9522
+                    b,
9523
+                    st,
9524
+                    abi_primary_key,
9525
+                    i,
9526
+                    is_value,
9527
+                ));
9528
+            }
9529
+        }
9530
+    }
9531
+
9532
+    if let Some(cls_flags) = &callee_char_len_star_args {
9533
+        for (i, flag) in cls_flags.iter().enumerate() {
9534
+            if !*flag || i >= arg_slots.len() || (!nopass && i == 0) {
9535
+                continue;
9536
+            }
9537
+            if let Some(arg) = &arg_slots[i] {
9538
+                if let crate::ast::expr::SectionSubscript::Element(e) = &arg.value {
9539
+                    call_args.push(
9540
+                        actual_char_arg_runtime_len(
9541
+                            b,
9542
+                            locals,
9543
+                            e,
9544
+                            st,
9545
+                            type_layouts,
9546
+                            internal_funcs,
9547
+                            contained_host_refs,
9548
+                            descriptor_params,
9549
+                        )
9550
+                        .unwrap_or_else(|| b.const_i64(0)),
9551
+                    );
9552
+                } else {
9553
+                    call_args.push(b.const_i64(0));
9554
+                }
9555
+            } else {
9556
+                call_args.push(b.const_i64(0));
9557
+            }
9558
+        }
9559
+    }
9560
+
9561
+    Some(b.call(FuncRef::External(call_name), call_args, ret_ty))
9562
+}
9563
+
92759564
 fn lower_alloc_return_call_into_desc(
92769565
     b: &mut FuncBuilder,
92779566
     ctx: &LowerCtx,
@@ -9558,6 +9847,20 @@ fn ir_types_dispatch_equal(decl: &IrType, actual: &IrType) -> bool {
95589847
     }
95599848
 }
95609849
 
9850
+/// Resolve an integer kind suffix (literal integer or named constant) to a
9851
+/// kind width. Returns the active default integer kind when unresolvable.
9852
+fn int_kind_to_width(kind_str: &str, st: &SymbolTable) -> u8 {
9853
+    if let Ok(n) = kind_str.parse::<u8>() {
9854
+        return n;
9855
+    }
9856
+    if let Some(sym) = st.find_symbol_any_scope(&kind_str.to_lowercase()) {
9857
+        if let Some(v) = sym.const_value.and_then(|v| u8::try_from(v).ok()) {
9858
+            return v;
9859
+        }
9860
+    }
9861
+    crate::driver::defaults::default_int_kind()
9862
+}
9863
+
95619864
 /// Resolve a kind suffix (literal integer or named constant) to a kind width.
95629865
 /// Returns 4 as the default when unresolvable.
95639866
 fn real_kind_to_width(kind_str: &str, st: &SymbolTable) -> u8 {
@@ -10251,6 +10554,7 @@ fn install_runtime_dim_bounds(
1025110554
     decls: &[crate::ast::decl::SpannedDecl],
1025210555
     visible_param_consts: &HashMap<String, ConstScalar>,
1025310556
     st: &SymbolTable,
10557
+    type_layouts: &crate::sema::type_layout::TypeLayoutRegistry,
1025410558
 ) {
1025510559
     use crate::ast::decl::{ArraySpec, Attribute};
1025610560
     for decl in decls {
@@ -10318,7 +10622,8 @@ fn install_runtime_dim_bounds(
1031810622
                     runtime.push(None);
1031910623
                     continue;
1032010624
                 }
10321
-                let val = lower_expr(b, locals, upper_expr, st);
10625
+                let val =
10626
+                    lower_expr_with_optional_layouts(b, locals, upper_expr, st, Some(type_layouts));
1032210627
                 let as_i64 = match b.func().value_type(val) {
1032310628
                     Some(IrType::Int(IntWidth::I64)) => val,
1032410629
                     Some(IrType::Int(_)) => b.int_extend(val, IntWidth::I64, true),
@@ -10351,7 +10656,7 @@ fn arg_derived_type_name(
1035110656
         {
1035210657
             for entity in entities {
1035310658
                 if entity.name.to_lowercase() == key {
10354
-                    if let TypeSpec::Type(ref name) = type_spec {
10659
+                    if let TypeSpec::Type(ref name) | TypeSpec::Class(ref name) = type_spec {
1035510660
                         return Some(name.clone());
1035610661
                     }
1035710662
                 }
@@ -11895,6 +12200,75 @@ fn lower_string_expr_full(
1189512200
             }
1189612201
             if let Expr::ComponentAccess { .. } = &callee.node {
1189712202
                 if let Some(tl) = type_layouts {
12203
+                    if let Expr::ComponentAccess { base, component } = &callee.node {
12204
+                        if let Some((_obj_addr, type_name)) =
12205
+                            resolve_component_base_for_method(b, locals, base, st, tl)
12206
+                        {
12207
+                            if let Some(layout) = tl.get(&type_name) {
12208
+                                if let Some(bp) = layout.bound_proc(component) {
12209
+                                    let target_key = abi_key_for_link_name(st, &bp.target_name)
12210
+                                        .unwrap_or_else(|| bp.abi_name.clone());
12211
+                                    if let Some(ret_abi) =
12212
+                                        callee_character_return_abi(st, &target_key)
12213
+                                    {
12214
+                                        match ret_abi {
12215
+                                            CharacterReturnAbi::HiddenDescriptor => {
12216
+                                                let desc = b.alloca(IrType::Array(
12217
+                                                    Box::new(IrType::Int(IntWidth::I8)),
12218
+                                                    32,
12219
+                                                ));
12220
+                                                let zero_i32 = b.const_i32(0);
12221
+                                                let size32 = b.const_i64(32);
12222
+                                                b.call(
12223
+                                                    FuncRef::External("memset".into()),
12224
+                                                    vec![desc, zero_i32, size32],
12225
+                                                    IrType::Ptr(Box::new(IrType::Int(
12226
+                                                        IntWidth::I8,
12227
+                                                    ))),
12228
+                                                );
12229
+                                                emit_bound_function_call(
12230
+                                                    b,
12231
+                                                    locals,
12232
+                                                    st,
12233
+                                                    type_layouts,
12234
+                                                    internal_funcs,
12235
+                                                    contained_host_refs,
12236
+                                                    descriptor_params,
12237
+                                                    base,
12238
+                                                    component,
12239
+                                                    args,
12240
+                                                    Some(desc),
12241
+                                                    IrType::Void,
12242
+                                                );
12243
+                                                return load_string_descriptor_view(b, desc);
12244
+                                            }
12245
+                                            CharacterReturnAbi::BindCScalarByte => {
12246
+                                                if let Some(byte) = emit_bound_function_call(
12247
+                                                    b,
12248
+                                                    locals,
12249
+                                                    st,
12250
+                                                    type_layouts,
12251
+                                                    internal_funcs,
12252
+                                                    contained_host_refs,
12253
+                                                    descriptor_params,
12254
+                                                    base,
12255
+                                                    component,
12256
+                                                    args,
12257
+                                                    None,
12258
+                                                    IrType::Int(IntWidth::I8),
12259
+                                                ) {
12260
+                                                    let slot =
12261
+                                                        b.alloca(IrType::Int(IntWidth::I8));
12262
+                                                    b.store(byte, slot);
12263
+                                                    return (slot, b.const_i64(1));
12264
+                                                }
12265
+                                            }
12266
+                                        }
12267
+                                    }
12268
+                                }
12269
+                            }
12270
+                        }
12271
+                    }
1189812272
                     if args.len() == 1 {
1189912273
                         if let crate::ast::expr::SectionSubscript::Element(_) = &args[0].value {
1190012274
                             if let Some(result) = fixed_component_char_array_elem_ptr_and_len(
@@ -12834,6 +13208,149 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
1283413208
                                 }
1283513209
                             }
1283613210
                         }
13211
+                    } else if let Expr::FunctionCall {
13212
+                        callee: inner_callee,
13213
+                        args: inner_args,
13214
+                    } = &callee.node
13215
+                    {
13216
+                        if args.len() == 1
13217
+                            && matches!(
13218
+                                args[0].value,
13219
+                                crate::ast::expr::SectionSubscript::Range { .. }
13220
+                            )
13221
+                        {
13222
+                            if let crate::ast::expr::SectionSubscript::Range {
13223
+                                ref start,
13224
+                                ref end,
13225
+                                ..
13226
+                            } = args[0].value
13227
+                            {
13228
+                                if let Expr::Name { name } = &inner_callee.node {
13229
+                                    let akey = name.to_lowercase();
13230
+                                    if let Some(info) = ctx.locals.get(&akey).cloned() {
13231
+                                        if (info.char_kind != CharKind::None
13232
+                                            || descriptor_backed_runtime_char_array(&info))
13233
+                                            && local_is_array_like(&info)
13234
+                                        {
13235
+                                            if let Some((elem_ptr, elem_len)) =
13236
+                                                char_array_element_ptr_and_len(
13237
+                                                    b,
13238
+                                                    &ctx.locals,
13239
+                                                    &info,
13240
+                                                    inner_args,
13241
+                                                    ctx.st,
13242
+                                                    Some(ctx.type_layouts),
13243
+                                                )
13244
+                                            {
13245
+                                                let (dest_ptr, dest_len) = lower_substring_full(
13246
+                                                    b,
13247
+                                                    &ctx.locals,
13248
+                                                    ctx.st,
13249
+                                                    elem_ptr,
13250
+                                                    elem_len,
13251
+                                                    start.as_ref(),
13252
+                                                    end.as_ref(),
13253
+                                                    Some(ctx.type_layouts),
13254
+                                                    Some(ctx.internal_funcs),
13255
+                                                    Some(ctx.contained_host_refs),
13256
+                                                    Some(ctx.descriptor_params),
13257
+                                                );
13258
+                                                let (src_ptr, src_len) =
13259
+                                                    lower_string_expr_ctx(b, ctx, value);
13260
+                                                b.call(
13261
+                                                    FuncRef::External(
13262
+                                                        "afs_assign_char_fixed".into(),
13263
+                                                    ),
13264
+                                                    vec![dest_ptr, dest_len, src_ptr, src_len],
13265
+                                                    IrType::Void,
13266
+                                                );
13267
+                                                return;
13268
+                                            }
13269
+                                        }
13270
+                                    }
13271
+                                } else if let Expr::ComponentAccess { .. } = &inner_callee.node {
13272
+                                    if let Some((elem_ptr, elem_len)) =
13273
+                                        fixed_component_char_array_elem_ptr_and_len(
13274
+                                            b,
13275
+                                            &ctx.locals,
13276
+                                            inner_callee,
13277
+                                            inner_args,
13278
+                                            ctx.st,
13279
+                                            ctx.type_layouts,
13280
+                                        )
13281
+                                    {
13282
+                                        let (dest_ptr, dest_len) = lower_substring_full(
13283
+                                            b,
13284
+                                            &ctx.locals,
13285
+                                            ctx.st,
13286
+                                            elem_ptr,
13287
+                                            elem_len,
13288
+                                            start.as_ref(),
13289
+                                            end.as_ref(),
13290
+                                            Some(ctx.type_layouts),
13291
+                                            Some(ctx.internal_funcs),
13292
+                                            Some(ctx.contained_host_refs),
13293
+                                            Some(ctx.descriptor_params),
13294
+                                        );
13295
+                                        let (src_ptr, src_len) =
13296
+                                            lower_string_expr_ctx(b, ctx, value);
13297
+                                        b.call(
13298
+                                            FuncRef::External("afs_assign_char_fixed".into()),
13299
+                                            vec![dest_ptr, dest_len, src_ptr, src_len],
13300
+                                            IrType::Void,
13301
+                                        );
13302
+                                        return;
13303
+                                    }
13304
+                                    if let Some(info) = component_intrinsic_local_info(
13305
+                                        b,
13306
+                                        &ctx.locals,
13307
+                                        inner_callee,
13308
+                                        ctx.st,
13309
+                                        ctx.type_layouts,
13310
+                                    ) {
13311
+                                        if (info.char_kind != CharKind::None
13312
+                                            || descriptor_backed_runtime_char_array(&info))
13313
+                                            && local_is_array_like(&info)
13314
+                                        {
13315
+                                            if let Some((elem_ptr, elem_len)) =
13316
+                                                char_array_element_ptr_and_len(
13317
+                                                    b,
13318
+                                                    &ctx.locals,
13319
+                                                    &info,
13320
+                                                    inner_args,
13321
+                                                    ctx.st,
13322
+                                                    Some(ctx.type_layouts),
13323
+                                                )
13324
+                                            {
13325
+                                                let (dest_ptr, dest_len) = lower_substring_full(
13326
+                                                    b,
13327
+                                                    &ctx.locals,
13328
+                                                    ctx.st,
13329
+                                                    elem_ptr,
13330
+                                                    elem_len,
13331
+                                                    start.as_ref(),
13332
+                                                    end.as_ref(),
13333
+                                                    Some(ctx.type_layouts),
13334
+                                                    Some(ctx.internal_funcs),
13335
+                                                    Some(ctx.contained_host_refs),
13336
+                                                    Some(ctx.descriptor_params),
13337
+                                                );
13338
+                                                let (src_ptr, src_len) =
13339
+                                                    lower_string_expr_ctx(b, ctx, value);
13340
+                                                b.call(
13341
+                                                    FuncRef::External(
13342
+                                                        "afs_assign_char_fixed".into(),
13343
+                                                    ),
13344
+                                                    vec![dest_ptr, dest_len, src_ptr, src_len],
13345
+                                                    IrType::Void,
13346
+                                                );
13347
+                                                return;
13348
+                                            }
13349
+                                        }
13350
+                                    }
13351
+                                }
13352
+                            }
13353
+                        }
1283713354
                     } else if let Expr::ComponentAccess { .. } = &callee.node {
1283813355
                         if let Some((dest_ptr, dest_len)) =
1283913356
                             fixed_component_char_array_elem_ptr_and_len(
@@ -13187,19 +13704,162 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
1318713704
                     if let Some(layout) = ctx.type_layouts.get(&type_name) {
1318813705
                         if let Some(bp) = layout.bound_proc(component) {
1318913706
                             let target = bp.target_name.clone();
13707
+                            let target_key = abi_key_for_link_name(ctx.st, &target)
13708
+                                .unwrap_or_else(|| bp.abi_name.clone());
13709
+                            let (call_name, _) =
13710
+                                resolved_symbol_call_target(ctx.st, &target_key, &target);
1319013711
                             let nopass = bp.nopass;
13712
+                            let arg_slots = reorder_args_by_keyword_slots_with_formal_skip(
13713
+                                args,
13714
+                                &target_key,
13715
+                                ctx.st,
13716
+                                if nopass { 0 } else { 1 },
13717
+                            );
13718
+                            let abi_lookup_keys = procedure_abi_lookup_keys(ctx.st, &[&target_key]);
13719
+                            let abi_primary_key = abi_lookup_keys
13720
+                                .first()
13721
+                                .map(String::as_str)
13722
+                                .unwrap_or(target_key.as_str());
13723
+                            let value_mask = first_procedure_lookup(&abi_lookup_keys, |k| {
13724
+                                callee_value_arg_mask(ctx.st, k)
13725
+                            });
13726
+                            let desc_mask = first_procedure_lookup(&abi_lookup_keys, |k| {
13727
+                                ctx.descriptor_params.get(k).cloned()
13728
+                            });
13729
+                            let bind_c_char_mask = first_procedure_lookup(&abi_lookup_keys, |k| {
13730
+                                callee_bind_c_char_arg_mask(ctx.st, k)
13731
+                            });
13732
+                            let pointer_mask = first_procedure_lookup(&abi_lookup_keys, |k| {
13733
+                                callee_pointer_arg_mask(ctx.st, k)
13734
+                            });
13735
+                            let string_desc_mask = first_procedure_lookup(&abi_lookup_keys, |k| {
13736
+                                callee_string_descriptor_arg_mask(ctx.st, k)
13737
+                            });
13738
+                            let opt_flags = first_procedure_lookup(&abi_lookup_keys, |k| {
13739
+                                ctx.optional_params
13740
+                                    .get(k)
13741
+                                    .cloned()
13742
+                                    .or_else(|| callee_optional_arg_mask(ctx.st, k))
13743
+                            });
1319113744
 
13192
-                            // Build argument list: obj as first arg (PASS), then explicit args.
13193
-                            let mut call_args = Vec::new();
13194
-                            if !nopass {
13195
-                                call_args.push(obj_addr); // PASS: object address
13745
+                            let mut call_args = Vec::with_capacity(arg_slots.len());
13746
+                            for (i, slot) in arg_slots.iter().enumerate() {
13747
+                                if !nopass && i == 0 {
13748
+                                    call_args.push(obj_addr);
13749
+                                    continue;
13750
+                                }
13751
+                                let is_value = value_mask
13752
+                                    .as_ref()
13753
+                                    .map(|mask| mask.get(i).copied().unwrap_or(false))
13754
+                                    .unwrap_or(false);
13755
+                                let wants_descriptor = desc_mask
13756
+                                    .as_ref()
13757
+                                    .map(|mask| mask.get(i).copied().unwrap_or(false))
13758
+                                    .unwrap_or(false);
13759
+                                let wants_bind_c_char = bind_c_char_mask
13760
+                                    .as_ref()
13761
+                                    .map(|mask| mask.get(i).copied().unwrap_or(false))
13762
+                                    .unwrap_or(false);
13763
+                                let wants_pointer = pointer_mask
13764
+                                    .as_ref()
13765
+                                    .map(|mask| mask.get(i).copied().unwrap_or(false))
13766
+                                    .unwrap_or(false);
13767
+                                let wants_string_descriptor = string_desc_mask
13768
+                                    .as_ref()
13769
+                                    .map(|mask| mask.get(i).copied().unwrap_or(false))
13770
+                                    .unwrap_or(false);
13771
+                                let value = match slot {
13772
+                                    Some(arg) => match &arg.value {
13773
+                                        crate::ast::expr::SectionSubscript::Element(e) => {
13774
+                                            if is_value && wants_bind_c_char {
13775
+                                                lower_bind_c_char_value_arg(
13776
+                                                    b,
13777
+                                                    &ctx.locals,
13778
+                                                    e,
13779
+                                                    ctx.st,
13780
+                                                    Some(ctx.type_layouts),
13781
+                                                    Some(ctx.internal_funcs),
13782
+                                                    Some(ctx.contained_host_refs),
13783
+                                                    Some(ctx.descriptor_params),
13784
+                                                )
13785
+                                            } else if is_value {
13786
+                                                let raw = lower_expr_full(
13787
+                                                    b,
13788
+                                                    &ctx.locals,
13789
+                                                    e,
13790
+                                                    ctx.st,
13791
+                                                    Some(ctx.type_layouts),
13792
+                                                    Some(ctx.internal_funcs),
13793
+                                                    Some(ctx.contained_host_refs),
13794
+                                                    Some(ctx.descriptor_params),
13795
+                                                );
13796
+                                                coerce_value_call_arg(
13797
+                                                    b,
13798
+                                                    ctx.st,
13799
+                                                    abi_primary_key,
13800
+                                                    i,
13801
+                                                    raw,
13802
+                                                )
13803
+                                            } else if wants_descriptor {
13804
+                                                lower_arg_descriptor(
13805
+                                                    b,
13806
+                                                    &ctx.locals,
13807
+                                                    e,
13808
+                                                    ctx.st,
13809
+                                                    Some(ctx.type_layouts),
13810
+                                                )
13811
+                                            } else if wants_string_descriptor {
13812
+                                                lower_arg_string_descriptor(
13813
+                                                    b,
13814
+                                                    &ctx.locals,
13815
+                                                    e,
13816
+                                                    ctx.st,
13817
+                                                    Some(ctx.type_layouts),
13818
+                                                )
13819
+                                            } else if wants_bind_c_char {
13820
+                                                lower_bind_c_char_arg_raw(
13821
+                                                    b,
13822
+                                                    &ctx.locals,
13823
+                                                    e,
13824
+                                                    ctx.st,
13825
+                                                    Some(ctx.type_layouts),
13826
+                                                    Some(ctx.internal_funcs),
13827
+                                                    Some(ctx.contained_host_refs),
13828
+                                                    Some(ctx.descriptor_params),
13829
+                                                )
13830
+                                            } else if wants_pointer {
13831
+                                                lower_pointer_dummy_actual(
13832
+                                                    b,
13833
+                                                    &ctx.locals,
13834
+                                                    e,
13835
+                                                    ctx.st,
13836
+                                                    Some(ctx.type_layouts),
13837
+                                                )
13838
+                                                .unwrap_or_else(|| lower_arg_by_ref_ctx(b, ctx, e))
13839
+                                            } else {
13840
+                                                lower_arg_by_ref_ctx(b, ctx, e)
13841
+                                            }
13842
+                                        }
13843
+                                        _ => b.const_i32(0),
13844
+                                    },
13845
+                                    None => missing_optional_call_arg(
13846
+                                        b,
13847
+                                        ctx.st,
13848
+                                        abi_primary_key,
13849
+                                        i,
13850
+                                        is_value,
13851
+                                    ),
13852
+                                };
13853
+                                call_args.push(value);
1319613854
                             }
13197
-                            for a in args {
13198
-                                if let crate::ast::expr::SectionSubscript::Element(e) = &a.value {
13199
-                                    call_args.push(lower_arg_by_ref_ctx(b, ctx, e));
13855
+                            if let Some(opt_flags) = opt_flags {
13856
+                                for flag in opt_flags.iter().skip(call_args.len()) {
13857
+                                    if *flag {
13858
+                                        call_args.push(b.const_i64(0));
13859
+                                    }
1320013860
                                 }
1320113861
                             }
13202
-                            b.call(FuncRef::External(target), call_args, IrType::Void);
13862
+                            b.call(FuncRef::External(call_name), call_args, IrType::Void);
1320313863
                         }
1320413864
                     }
1320513865
                 }
@@ -13926,8 +14586,13 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
1392614586
         } => {
1392714587
             let stat_addr = allocate_status_target_addr(b, ctx, opts);
1392814588
             let errmsg_target = allocate_errmsg_target(b, ctx, opts);
13929
-            let typed_char_len =
13930
-                typed_allocate_char_len(b, &ctx.locals, type_spec.as_ref(), ctx.st);
14589
+            let typed_char_len = typed_allocate_char_len(
14590
+                b,
14591
+                &ctx.locals,
14592
+                type_spec.as_ref(),
14593
+                ctx.st,
14594
+                Some(ctx.type_layouts),
14595
+            );
1393114596
             let source_desc = allocate_descriptor_keyword_expr(b, ctx, opts, "source");
1393214597
             let mold_desc = allocate_descriptor_keyword_expr(b, ctx, opts, "mold");
1393314598
             let shape_desc = source_desc.or(mold_desc);
@@ -16744,6 +17409,29 @@ fn unify_int_widths(b: &mut FuncBuilder, lhs: ValueId, rhs: ValueId) -> (ValueId
1674417409
     (l, r)
1674517410
 }
1674617411
 
17412
+fn int_width_of_value(b: &FuncBuilder, value: ValueId) -> Option<IntWidth> {
17413
+    match b.func().value_type(value) {
17414
+        Some(IrType::Int(width)) => Some(width),
17415
+        Some(IrType::Bool) => Some(IntWidth::I8),
17416
+        _ => None,
17417
+    }
17418
+}
17419
+
17420
+fn coerce_int_like_to_width(b: &mut FuncBuilder, value: ValueId, target: IntWidth) -> ValueId {
17421
+    match b.func().value_type(value) {
17422
+        Some(IrType::Int(width)) if width == target => value,
17423
+        Some(IrType::Int(_)) | Some(IrType::Bool) => b.int_extend(value, target, true),
17424
+        _ => value,
17425
+    }
17426
+}
17427
+
17428
+fn int_const_for_width(b: &mut FuncBuilder, width: IntWidth, value: i64) -> ValueId {
17429
+    match width {
17430
+        IntWidth::I64 => b.const_i64(value),
17431
+        _ => b.const_i32(value as i32),
17432
+    }
17433
+}
17434
+
1674717435
 /// Widen an i32 (or smaller) index value to i64 for pointer
1674817436
 /// arithmetic. Pass through for values already i64 or larger.
1674917437
 fn widen_idx_to_i64(b: &mut FuncBuilder, idx: ValueId) -> ValueId {
@@ -20033,7 +20721,7 @@ fn lower_array_expr_descriptor(
2003320721
 
2003420722
             let first_ft = crate::sema::types::expr_type(first, st);
2003520723
             let elem_ty = fortran_type_to_ir_scalar_type(&first_ft)
20036
-                .unwrap_or_else(|| infer_const_expr_ty(&first.node));
20724
+                .unwrap_or_else(|| infer_const_expr_ty(&first.node, st));
2003720725
             let n = expr_values.len() as i64;
2003820726
             let arr_ty = IrType::Array(Box::new(elem_ty.clone()), n.max(1) as u64);
2003920727
             let buf = b.alloca(arr_ty);
@@ -22533,12 +23221,13 @@ fn typed_allocate_char_len(
2253323221
     locals: &HashMap<String, LocalInfo>,
2253423222
     type_spec: Option<&TypeSpec>,
2253523223
     st: &SymbolTable,
23224
+    type_layouts: Option<&crate::sema::type_layout::TypeLayoutRegistry>,
2253623225
 ) -> Option<ValueId> {
2253723226
     match type_spec {
2253823227
         Some(TypeSpec::Character(None)) => Some(b.const_i64(1)),
2253923228
         Some(TypeSpec::Character(Some(sel))) => match &sel.len {
2254023229
             Some(crate::ast::decl::LenSpec::Expr(e)) => {
22541
-                let raw_len = lower_expr(b, locals, e, st);
23230
+                let raw_len = lower_expr_with_optional_layouts(b, locals, e, st, type_layouts);
2254223231
                 Some(clamp_nonnegative_i64(b, raw_len))
2254323232
             }
2254423233
             None => Some(b.const_i64(1)),
@@ -22884,6 +23573,11 @@ fn resolve_component_base_for_method(
2288423573
                 return Some((elem_addr, type_name));
2288523574
             }
2288623575
             if let Expr::ComponentAccess { .. } = &callee.node {
23576
+                if let Some(info) = component_array_local_info(b, locals, callee, st, tl) {
23577
+                    let type_name = info.derived_type.as_ref()?.clone();
23578
+                    let elem_addr = lower_array_element(b, locals, &info, args, st, Some(tl));
23579
+                    return Some((elem_addr, type_name));
23580
+                }
2288723581
                 let (field_ptr, field) = resolve_component_field_access(b, locals, callee, st, tl)?;
2288823582
                 let crate::sema::symtab::TypeInfo::Derived(type_name) = &field.type_info else {
2288923583
                     return None;
@@ -23440,6 +24134,20 @@ fn lower_expr(
2344024134
     lower_expr_full(b, locals, expr, st, None, None, None, None)
2344124135
 }
2344224136
 
24137
+fn lower_expr_with_optional_layouts(
24138
+    b: &mut FuncBuilder,
24139
+    locals: &HashMap<String, LocalInfo>,
24140
+    expr: &crate::ast::expr::SpannedExpr,
24141
+    st: &SymbolTable,
24142
+    type_layouts: Option<&crate::sema::type_layout::TypeLayoutRegistry>,
24143
+) -> ValueId {
24144
+    if let Some(tl) = type_layouts {
24145
+        lower_expr_tl(b, locals, expr, st, tl)
24146
+    } else {
24147
+        lower_expr(b, locals, expr, st)
24148
+    }
24149
+}
24150
+
2344324151
 fn lower_expr_ctx(
2344424152
     b: &mut FuncBuilder,
2344524153
     ctx: &LowerCtx,
@@ -23615,13 +24323,17 @@ fn lower_expr_full(
2361524323
 ) -> ValueId {
2361624324
     match &expr.node {
2361724325
         Expr::IntegerLiteral { text, kind, .. } => {
23618
-            let kind = kind.as_deref();
23619
-            if kind == Some("16") {
23620
-                b.const_i128(text.parse::<i128>().unwrap_or(0))
24326
+            let clean = text.split('_').next().unwrap_or(text);
24327
+            let kind_width = kind
24328
+                .as_deref()
24329
+                .map(|kind_str| int_kind_to_width(kind_str, st))
24330
+                .unwrap_or_else(crate::driver::defaults::default_int_kind);
24331
+            if kind_width >= 16 {
24332
+                b.const_i128(clean.parse::<i128>().unwrap_or(0))
2362124333
             } else {
23622
-                let val: i64 = text.parse().unwrap_or(0);
23623
-                if kind == Some("8") || val > i32::MAX as i64 || val < i32::MIN as i64 {
23624
-                    b.const_i64(val)
24334
+                let val: i128 = clean.parse().unwrap_or(0);
24335
+                if kind_width >= 8 || val > i32::MAX as i128 || val < i32::MIN as i128 {
24336
+                    b.const_i64(val as i64)
2362524337
                 } else {
2362624338
                     b.const_i32(val as i32)
2362724339
                 }
@@ -23703,6 +24415,20 @@ fn lower_expr_full(
2370324415
                     // what's stored in the pointer slot.
2370424416
                     b.load_typed(info.addr, IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))))
2370524417
                 } else if info.derived_type.is_some() {
24418
+                    if info
24419
+                        .derived_type
24420
+                        .as_ref()
24421
+                        .map(|name| is_opaque_c_handle_name(name))
24422
+                        .unwrap_or(false)
24423
+                    {
24424
+                        if info.by_ref {
24425
+                            let raw_ptr = b.load_typed(info.addr, IrType::Int(IntWidth::I64));
24426
+                            let ptr = b.int_to_ptr(raw_ptr, info.ty.clone());
24427
+                            b.load_typed(ptr, info.ty.clone())
24428
+                        } else {
24429
+                            b.load_typed(info.addr, info.ty.clone())
24430
+                        }
24431
+                    } else
2370624432
                     // Derived type variable: storage is `alloca [i8 x size]`.
2370724433
                     // Consumers of the value treat it as a pointer to the
2370824434
                     // struct (memcpy for whole-struct assignment, GEP for
@@ -23711,12 +24437,14 @@ fn lower_expr_full(
2371124437
                     // of the struct as if they were a pointer, turning
2371224438
                     // `b = a` into a memcpy from the garbage address held
2371324439
                     // by a's first field slot.
23714
-                    if info.allocatable {
23715
-                        array_base_addr(b, info)
23716
-                    } else if info.by_ref {
23717
-                        b.load(info.addr)
23718
-                    } else {
23719
-                        info.addr
24440
+                    {
24441
+                        if info.allocatable {
24442
+                            array_base_addr(b, info)
24443
+                        } else if info.by_ref {
24444
+                            b.load(info.addr)
24445
+                        } else {
24446
+                            info.addr
24447
+                        }
2372024448
                     }
2372124449
                 } else if is_complex_ty(&info.ty) {
2372224450
                     if info.by_ref {
@@ -24694,7 +25422,7 @@ fn lower_expr_full(
2469425422
                         .unwrap_or_else(|| FuncRef::External(call_name))
2469525423
                 };
2469625424
                 b.call(func_ref, ref_arg_vals, ret_ty)
24697
-            } else if let Expr::ComponentAccess { .. } = &callee.node {
25425
+            } else if let Expr::ComponentAccess { base, component } = &callee.node {
2469825426
                 if let Some(tl) = type_layouts {
2469925427
                     if let Some(info) = component_intrinsic_local_info(b, locals, callee, st, tl) {
2470025428
                         let has_range = args.iter().any(|a| {
@@ -24705,6 +25433,228 @@ fn lower_expr_full(
2470525433
                         }
2470625434
                         return lower_array_element(b, locals, &info, args, st, type_layouts);
2470725435
                     }
25436
+
25437
+                    if let Some((obj_addr, type_name)) =
25438
+                        resolve_component_base_for_method(b, locals, base, st, tl)
25439
+                    {
25440
+                        if let Some(layout) = tl.get(&type_name) {
25441
+                            if let Some(bp) = layout.bound_proc(component) {
25442
+                                let target = bp.target_name.clone();
25443
+                                let target_key = abi_key_for_link_name(st, &target)
25444
+                                    .unwrap_or_else(|| bp.abi_name.clone());
25445
+                                let (call_name, _callee_key) =
25446
+                                    resolved_symbol_call_target(st, &target_key, &target);
25447
+                                let nopass = bp.nopass;
25448
+                                let arg_slots = reorder_args_by_keyword_slots_with_formal_skip(
25449
+                                    args,
25450
+                                    &target_key,
25451
+                                    st,
25452
+                                    if nopass { 0 } else { 1 },
25453
+                                );
25454
+                                let abi_lookup_keys = procedure_abi_lookup_keys(st, &[&target_key]);
25455
+                                let abi_primary_key = abi_lookup_keys
25456
+                                    .first()
25457
+                                    .map(String::as_str)
25458
+                                    .unwrap_or(target_key.as_str());
25459
+                                let callee_value_args = first_procedure_lookup(&abi_lookup_keys, |k| {
25460
+                                    callee_value_arg_mask(st, k)
25461
+                                });
25462
+                                let callee_descriptor_args =
25463
+                                    first_procedure_lookup(&abi_lookup_keys, |k| {
25464
+                                        descriptor_params.and_then(|m| m.get(k).cloned())
25465
+                                    });
25466
+                                let callee_string_descriptor_args =
25467
+                                    first_procedure_lookup(&abi_lookup_keys, |k| {
25468
+                                        callee_string_descriptor_arg_mask(st, k)
25469
+                                    });
25470
+                                let callee_bind_c_char_args =
25471
+                                    first_procedure_lookup(&abi_lookup_keys, |k| {
25472
+                                        callee_bind_c_char_arg_mask(st, k)
25473
+                                    });
25474
+                                let callee_pointer_args =
25475
+                                    first_procedure_lookup(&abi_lookup_keys, |k| {
25476
+                                        callee_pointer_arg_mask(st, k)
25477
+                                    });
25478
+                                let opt_flags = first_procedure_lookup(&abi_lookup_keys, |k| {
25479
+                                    callee_optional_arg_mask(st, k)
25480
+                                });
25481
+                                let callee_char_len_star_args =
25482
+                                    first_procedure_lookup(&abi_lookup_keys, |k| {
25483
+                                        callee_char_len_star_mask(st, k)
25484
+                                    });
25485
+
25486
+                                let mut call_args = Vec::with_capacity(arg_slots.len() + 1);
25487
+                                for (i, slot) in arg_slots.iter().enumerate() {
25488
+                                    if !nopass && i == 0 {
25489
+                                        call_args.push(obj_addr);
25490
+                                        continue;
25491
+                                    }
25492
+                                    let is_value = callee_value_args
25493
+                                        .as_ref()
25494
+                                        .map(|mask| mask.get(i).copied().unwrap_or(false))
25495
+                                        .unwrap_or(false);
25496
+                                    let wants_descriptor = callee_descriptor_args
25497
+                                        .as_ref()
25498
+                                        .map(|mask| mask.get(i).copied().unwrap_or(false))
25499
+                                        .unwrap_or(false);
25500
+                                    let wants_string_descriptor = callee_string_descriptor_args
25501
+                                        .as_ref()
25502
+                                        .map(|mask| mask.get(i).copied().unwrap_or(false))
25503
+                                        .unwrap_or(false);
25504
+                                    let wants_bind_c_char = callee_bind_c_char_args
25505
+                                        .as_ref()
25506
+                                        .map(|mask| mask.get(i).copied().unwrap_or(false))
25507
+                                        .unwrap_or(false);
25508
+                                    let wants_pointer = callee_pointer_args
25509
+                                        .as_ref()
25510
+                                        .map(|mask| mask.get(i).copied().unwrap_or(false))
25511
+                                        .unwrap_or(false);
25512
+                                    let value = match slot {
25513
+                                        Some(arg) => match &arg.value {
25514
+                                            crate::ast::expr::SectionSubscript::Element(e) => {
25515
+                                                if is_value && wants_bind_c_char {
25516
+                                                    lower_bind_c_char_value_arg(
25517
+                                                        b,
25518
+                                                        locals,
25519
+                                                        e,
25520
+                                                        st,
25521
+                                                        type_layouts,
25522
+                                                        internal_funcs,
25523
+                                                        contained_host_refs,
25524
+                                                        descriptor_params,
25525
+                                                    )
25526
+                                                } else if is_value {
25527
+                                                    let raw = lower_expr_full(
25528
+                                                        b,
25529
+                                                        locals,
25530
+                                                        e,
25531
+                                                        st,
25532
+                                                        type_layouts,
25533
+                                                        internal_funcs,
25534
+                                                        contained_host_refs,
25535
+                                                        descriptor_params,
25536
+                                                    );
25537
+                                                    coerce_value_call_arg(
25538
+                                                        b,
25539
+                                                        st,
25540
+                                                        abi_primary_key,
25541
+                                                        i,
25542
+                                                        raw,
25543
+                                                    )
25544
+                                                } else if wants_descriptor {
25545
+                                                    lower_arg_descriptor(
25546
+                                                        b,
25547
+                                                        locals,
25548
+                                                        e,
25549
+                                                        st,
25550
+                                                        type_layouts,
25551
+                                                    )
25552
+                                                } else if wants_string_descriptor {
25553
+                                                    lower_arg_string_descriptor(
25554
+                                                        b,
25555
+                                                        locals,
25556
+                                                        e,
25557
+                                                        st,
25558
+                                                        type_layouts,
25559
+                                                    )
25560
+                                                } else if wants_bind_c_char {
25561
+                                                    lower_bind_c_char_arg_raw(
25562
+                                                        b,
25563
+                                                        locals,
25564
+                                                        e,
25565
+                                                        st,
25566
+                                                        type_layouts,
25567
+                                                        internal_funcs,
25568
+                                                        contained_host_refs,
25569
+                                                        descriptor_params,
25570
+                                                    )
25571
+                                                } else if wants_pointer {
25572
+                                                    lower_pointer_dummy_actual(
25573
+                                                        b,
25574
+                                                        locals,
25575
+                                                        e,
25576
+                                                        st,
25577
+                                                        type_layouts,
25578
+                                                    )
25579
+                                                    .unwrap_or_else(|| {
25580
+                                                        lower_arg_by_ref_full(
25581
+                                                            b,
25582
+                                                            locals,
25583
+                                                            e,
25584
+                                                            st,
25585
+                                                            type_layouts,
25586
+                                                            internal_funcs,
25587
+                                                            contained_host_refs,
25588
+                                                            descriptor_params,
25589
+                                                        )
25590
+                                                    })
25591
+                                                } else {
25592
+                                                    lower_arg_by_ref_full(
25593
+                                                        b,
25594
+                                                        locals,
25595
+                                                        e,
25596
+                                                        st,
25597
+                                                        type_layouts,
25598
+                                                        internal_funcs,
25599
+                                                        contained_host_refs,
25600
+                                                        descriptor_params,
25601
+                                                    )
25602
+                                                }
25603
+                                            }
25604
+                                            _ => b.const_i32(0),
25605
+                                        },
25606
+                                        None => {
25607
+                                            missing_optional_call_arg(b, st, abi_primary_key, i, is_value)
25608
+                                        }
25609
+                                    };
25610
+                                    call_args.push(value);
25611
+                                }
25612
+                                if let Some(opt_flags) = opt_flags {
25613
+                                    for flag in opt_flags.iter().skip(call_args.len()) {
25614
+                                        if *flag {
25615
+                                            call_args.push(b.const_i64(0));
25616
+                                        }
25617
+                                    }
25618
+                                }
25619
+                                if let Some(cls_flags) = &callee_char_len_star_args {
25620
+                                    for (i, flag) in cls_flags.iter().enumerate() {
25621
+                                        if !*flag || i >= arg_slots.len() {
25622
+                                            continue;
25623
+                                        }
25624
+                                        if let Some(arg) = &arg_slots[i] {
25625
+                                            if let crate::ast::expr::SectionSubscript::Element(e) =
25626
+                                                &arg.value
25627
+                                            {
25628
+                                                call_args.push(
25629
+                                                    actual_char_arg_runtime_len(
25630
+                                                        b,
25631
+                                                        locals,
25632
+                                                        e,
25633
+                                                        st,
25634
+                                                        type_layouts,
25635
+                                                        internal_funcs,
25636
+                                                        contained_host_refs,
25637
+                                                        descriptor_params,
25638
+                                                    )
25639
+                                                    .unwrap_or_else(|| b.const_i64(0)),
25640
+                                                );
25641
+                                            } else {
25642
+                                                call_args.push(b.const_i64(0));
25643
+                                            }
25644
+                                        } else {
25645
+                                            call_args.push(b.const_i64(0));
25646
+                                        }
25647
+                                    }
25648
+                                }
25649
+
25650
+                                let ret_ty = first_procedure_lookup(&abi_lookup_keys, |k| {
25651
+                                    callee_return_ir_type(st, k)
25652
+                                })
25653
+                                .unwrap_or(IrType::Int(IntWidth::I32));
25654
+                                return b.call(FuncRef::External(call_name), call_args, ret_ty);
25655
+                            }
25656
+                        }
25657
+                    }
2470825658
                 }
2470925659
                 b.const_i32(0)
2471025660
             } else {
@@ -24740,6 +25690,45 @@ fn lower_expr_full(
2474025690
                                 return Some((base_ptr, ret_type_name));
2474125691
                             }
2474225692
                         }
25693
+                        if let Expr::ComponentAccess {
25694
+                            base: method_base,
25695
+                            component,
25696
+                        } = &callee.node
25697
+                        {
25698
+                            if let Some(tl) = type_layouts {
25699
+                                if let Some((_obj_addr, type_name)) =
25700
+                                    resolve_component_base_for_method(
25701
+                                        b,
25702
+                                        locals,
25703
+                                        method_base,
25704
+                                        st,
25705
+                                        tl,
25706
+                                    )
25707
+                                {
25708
+                                    if let Some(layout) = tl.get(&type_name) {
25709
+                                        if let Some(bp) = layout.bound_proc(component) {
25710
+                                            let target_key = abi_key_for_link_name(st, &bp.target_name)
25711
+                                                .unwrap_or_else(|| bp.abi_name.clone());
25712
+                                            if let Some(ret_type_name) =
25713
+                                                callee_return_derived_type_name(st, &target_key)
25714
+                                            {
25715
+                                                let base_ptr = lower_expr_full(
25716
+                                                    b,
25717
+                                                    locals,
25718
+                                                    base,
25719
+                                                    st,
25720
+                                                    type_layouts,
25721
+                                                    internal_funcs,
25722
+                                                    contained_host_refs,
25723
+                                                    descriptor_params,
25724
+                                                );
25725
+                                                return Some((base_ptr, ret_type_name));
25726
+                                            }
25727
+                                        }
25728
+                                    }
25729
+                                }
25730
+                            }
25731
+                        }
2474325732
                     }
2474425733
                     None
2474525734
                 });
@@ -24835,7 +25824,7 @@ fn lower_expr_full(
2483525824
                         // than actually lower (and have to undo),
2483625825
                         // approximate from the AST: integer
2483725826
                         // literals → i32, real → f64, etc.
24838
-                        Some(infer_const_expr_ty(&e.node))
25827
+                        Some(infer_const_expr_ty(&e.node, st))
2483925828
                     }
2484025829
                     _ => None,
2484125830
                 })
@@ -24908,16 +25897,14 @@ fn lower_expr_full(
2490825897
 /// lowering to pick an element type without actually emitting IR.
2490925898
 /// Conservative — falls back to i32 for anything it can't
2491025899
 /// classify.
24911
-fn infer_const_expr_ty(e: &Expr) -> IrType {
25900
+fn infer_const_expr_ty(e: &Expr, st: &SymbolTable) -> IrType {
2491225901
     match e {
2491325902
         Expr::IntegerLiteral { kind, .. } => {
24914
-            if kind.as_deref() == Some("16") {
24915
-                IrType::Int(IntWidth::I128)
24916
-            } else if kind.as_deref() == Some("8") {
24917
-                IrType::Int(IntWidth::I64)
24918
-            } else {
24919
-                IrType::Int(IntWidth::I32)
24920
-            }
25903
+            let kind_width = kind
25904
+                .as_deref()
25905
+                .map(|kind_str| int_kind_to_width(kind_str, st))
25906
+                .unwrap_or_else(crate::driver::defaults::default_int_kind);
25907
+            IrType::int_from_kind(kind_width)
2492125908
         }
2492225909
         Expr::RealLiteral { text, .. } => {
2492325910
             if text.to_lowercase().contains('d') {
@@ -24927,8 +25914,8 @@ fn infer_const_expr_ty(e: &Expr) -> IrType {
2492725914
             }
2492825915
         }
2492925916
         Expr::LogicalLiteral { .. } => IrType::Bool,
24930
-        Expr::UnaryOp { operand, .. } => infer_const_expr_ty(&operand.node),
24931
-        Expr::ParenExpr { inner } => infer_const_expr_ty(&inner.node),
25917
+        Expr::UnaryOp { operand, .. } => infer_const_expr_ty(&operand.node, st),
25918
+        Expr::ParenExpr { inner } => infer_const_expr_ty(&inner.node, st),
2493225919
         _ => IrType::Int(IntWidth::I32),
2493325920
     }
2493425921
 }
src/sema/amod.rsmodified
@@ -118,12 +118,33 @@ pub fn write_amod(
118118
         .filter(|(_, sym)| matches!(sym.kind, SymbolKind::NamedInterface))
119119
         .flat_map(|(_, sym)| sym.arg_names.iter().cloned())
120120
         .collect();
121
+    // Public derived types can expose private bound procedure targets across
122
+    // translation units. Those targets must be serialized too so imported
123
+    // type-bound calls can recover full dummy-argument ABI metadata such as
124
+    // OPTIONAL slots.
125
+    let mut proc_export_names: BTreeSet<String> = interface_specifics;
126
+    for (name, sym) in &syms {
127
+        if matches!(sym.kind, SymbolKind::Function | SymbolKind::Subroutine) && is_public(sym, scope)
128
+        {
129
+            proc_export_names.insert(name.to_lowercase());
130
+        }
131
+    }
132
+    for (name, _sym) in syms
133
+        .iter()
134
+        .filter(|(_, sym)| matches!(sym.kind, SymbolKind::DerivedType))
135
+    {
136
+        if let Some(layout) = type_layouts.get(name) {
137
+            for bp in &layout.bound_procs {
138
+                proc_export_names.insert(bp.abi_name.to_lowercase());
139
+            }
140
+        }
141
+    }
121142
     let mut procs: Vec<_> = scope
122143
         .symbols
123144
         .iter()
124145
         .filter(|(name, sym)| {
125146
             matches!(sym.kind, SymbolKind::Function | SymbolKind::Subroutine)
126
-                && (is_public(sym, scope) || interface_specifics.contains(&name.to_lowercase()))
147
+                && proc_export_names.contains(&name.to_lowercase())
127148
         })
128149
         .collect();
129150
     procs.sort_by_key(|(k, _)| k.to_lowercase());
@@ -1210,9 +1231,11 @@ fn parse_type(
12101231
                 let m = clean.trim().to_string();
12111232
                 (m.clone(), m)
12121233
             };
1234
+            let abi_name = method.to_lowercase();
12131235
             bound_procs.push(BoundProc {
12141236
                 method_name: method,
12151237
                 target_name: target,
1238
+                abi_name,
12161239
                 nopass,
12171240
             });
12181241
         } else if let Some(rest) = trimmed.strip_prefix("@final ") {
src/sema/resolve.rsmodified
@@ -1095,6 +1095,11 @@ fn collect_derived_type_layouts(
10951095
         visible_param_cache,
10961096
         exported_param_cache,
10971097
     ));
1098
+    let host_module = match &st.scope(scope_id).kind {
1099
+        crate::sema::symtab::ScopeKind::Module(module_name)
1100
+        | crate::sema::symtab::ScopeKind::Submodule(module_name) => Some(module_name.as_str()),
1101
+        _ => None,
1102
+    };
10981103
     let const_params = collect_const_int_params(decls, &seed_params);
10991104
     for decl in decls {
11001105
         if let Decl::DerivedTypeDef {
@@ -1109,6 +1114,7 @@ fn collect_derived_type_layouts(
11091114
             let parent = extends.as_ref().and_then(|p| layouts.get(p)).cloned();
11101115
             let layout = super::type_layout::compute_layout(
11111116
                 name,
1117
+                host_module,
11121118
                 type_bound_procs,
11131119
                 final_procs,
11141120
                 components,
src/sema/type_layout.rsmodified
@@ -37,6 +37,7 @@ pub struct FieldLayout {
3737
 pub struct BoundProc {
3838
     pub method_name: String,
3939
     pub target_name: String,
40
+    pub abi_name: String,
4041
     pub nopass: bool,
4142
 }
4243
 
@@ -333,6 +334,7 @@ fn type_spec_to_type_info(
333334
 /// Compute the layout of a derived type from its component declarations.
334335
 pub fn compute_layout(
335336
     type_name: &str,
337
+    host_module: Option<&str>,
336338
     type_bound_procs: &[crate::ast::decl::TypeBoundProc],
337339
     final_proc_names: &[String],
338340
     components: &[crate::ast::decl::SpannedDecl],
@@ -461,9 +463,19 @@ pub fn compute_layout(
461463
         .map(|tbp| {
462464
             let target = tbp.binding.as_deref().unwrap_or(&tbp.name);
463465
             let nopass = tbp.attrs.iter().any(|a| a.eq_ignore_ascii_case("nopass"));
466
+            let target_name = if let Some(module_name) = host_module {
467
+                format!(
468
+                    "afs_modproc_{}_{}",
469
+                    module_name.to_lowercase(),
470
+                    target.to_lowercase()
471
+                )
472
+            } else {
473
+                target.to_string()
474
+            };
464475
             BoundProc {
465476
                 method_name: tbp.name.clone(),
466
-                target_name: target.to_string(),
477
+                target_name,
478
+                abi_name: target.to_lowercase(),
467479
                 nopass,
468480
             }
469481
         })
@@ -722,7 +734,16 @@ mod tests {
722734
             make_component("x", crate::ast::decl::TypeSpec::Integer(None)),
723735
             make_component("y", crate::ast::decl::TypeSpec::Real(None)),
724736
         ];
725
-        let layout = compute_layout("pair", &[], &[], &components, None, &reg, &empty_params());
737
+        let layout = compute_layout(
738
+            "pair",
739
+            None,
740
+            &[],
741
+            &[],
742
+            &components,
743
+            None,
744
+            &reg,
745
+            &empty_params(),
746
+        );
726747
         assert_eq!(layout.name, "pair");
727748
         assert_eq!(layout.size, 8); // 4 + 4, no padding needed
728749
         assert_eq!(layout.align, 4);
@@ -755,7 +776,16 @@ mod tests {
755776
             ),
756777
             make_component("b", crate::ast::decl::TypeSpec::DoublePrecision),
757778
         ];
758
-        let layout = compute_layout("padded", &[], &[], &components, None, &reg, &empty_params());
779
+        let layout = compute_layout(
780
+            "padded",
781
+            None,
782
+            &[],
783
+            &[],
784
+            &components,
785
+            None,
786
+            &reg,
787
+            &empty_params(),
788
+        );
759789
         assert_eq!(layout.field("a").unwrap().offset, 0);
760790
         assert_eq!(layout.field("a").unwrap().size, 1);
761791
         assert_eq!(layout.field("b").unwrap().offset, 8); // padded to 8-byte alignment
@@ -773,13 +803,22 @@ mod tests {
773803
             "x",
774804
             crate::ast::decl::TypeSpec::Integer(None),
775805
         )];
776
-        let base_layout =
777
-            compute_layout("base", &[], &[], &base_comps, None, &reg, &empty_params());
806
+        let base_layout = compute_layout(
807
+            "base",
808
+            None,
809
+            &[],
810
+            &[],
811
+            &base_comps,
812
+            None,
813
+            &reg,
814
+            &empty_params(),
815
+        );
778816
         assert_eq!(base_layout.size, 4);
779817
 
780818
         let child_comps = vec![make_component("y", crate::ast::decl::TypeSpec::Real(None))];
781819
         let child_layout = compute_layout(
782820
             "child",
821
+            None,
783822
             &[],
784823
             &[],
785824
             &child_comps,
@@ -812,7 +851,16 @@ mod tests {
812851
             vec![crate::ast::decl::Attribute::Pointer],
813852
         )];
814853
 
815
-        let layout = compute_layout("list_t", &[], &[], &components, None, &reg, &empty_params());
854
+        let layout = compute_layout(
855
+            "list_t",
856
+            None,
857
+            &[],
858
+            &[],
859
+            &components,
860
+            None,
861
+            &reg,
862
+            &empty_params(),
863
+        );
816864
         let field = layout.field("left").expect("missing left field");
817865
 
818866
         assert_eq!(field.size, 8);
@@ -866,6 +914,7 @@ mod tests {
866914
 
867915
         let layout = compute_layout(
868916
             "state_t",
917
+            None,
869918
             &[],
870919
             &[],
871920
             &components,
@@ -922,7 +971,7 @@ mod tests {
922971
         let mut params = std::collections::HashMap::new();
923972
         params.insert("max_token_len".into(), 8);
924973
 
925
-        let layout = compute_layout("token_t", &[], &[], &components, None, &reg, &params);
974
+        let layout = compute_layout("token_t", None, &[], &[], &components, None, &reg, &params);
926975
         let field = layout.field("value").expect("missing value field");
927976
 
928977
         assert_eq!(field.size, 8);
tests/allocate_constructor_runtime.rsmodified
@@ -77,7 +77,10 @@ fn allocate_source_array_constructor_infers_shape_and_copies_values() {
7777
     );
7878
     let stdout = String::from_utf8_lossy(&run.stdout);
7979
     assert!(
80
-        stdout.contains("3") && stdout.contains("1") && stdout.contains("2") && stdout.contains("3"),
80
+        stdout.contains("3")
81
+            && stdout.contains("1")
82
+            && stdout.contains("2")
83
+            && stdout.contains("3"),
8184
         "unexpected source array constructor output: {}",
8285
         stdout
8386
     );
tests/allocate_validation.rsmodified
@@ -26,7 +26,10 @@ fn compiler(name: &str) -> PathBuf {
2626
 fn unique_path(stem: &str, ext: &str) -> PathBuf {
2727
     let pid = std::process::id();
2828
     let id = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed);
29
-    std::env::temp_dir().join(format!("afs_alloc_validate_{}_{}_{}.{}", stem, pid, id, ext))
29
+    std::env::temp_dir().join(format!(
30
+        "afs_alloc_validate_{}_{}_{}.{}",
31
+        stem, pid, id, ext
32
+    ))
3033
 }
3134
 
3235
 fn unique_dir(stem: &str) -> PathBuf {
tests/cli_driver.rsmodified
1049 lines changed — click to load
@@ -1731,6 +1731,150 @@ fn bind_c_c_char_value_arg_passes_actual_byte_after_value_handle() {
17311731
     let _ = std::fs::remove_dir_all(&dir);
17321732
 }
17331733
 
1734
+#[test]
1735
+fn bind_c_c_ptr_value_and_i64_values_preserve_scalar_call_abi() {
1736
+    let dir = unique_dir("bind_c_c_ptr_i64_value_args");
1737
+    let c_src = write_program_in(
1738
+        &dir,
1739
+        "check_scan_args.c",
1740
+        "#include <stdint.h>\n\nint64_t check_scan_args(const char *buf, int64_t len, int64_t start, char needle) {\n    if (buf == 0) return -11;\n    if (len != 11) return -12;\n    if (start != 0) return -13;\n    if ((unsigned char)needle != 10) return -14;\n    if (buf[0] != 'h') return -15;\n    if (buf[5] != '\\n') return -16;\n    return 5;\n}\n",
1741
+    );
1742
+    let c_obj = dir.join("check_scan_args.o");
1743
+    compile_c_object(&c_src, &c_obj);
1744
+
1745
+    let src = write_program_in(
1746
+        &dir,
1747
+        "main.f90",
1748
+        "program p\n  use iso_c_binding, only: c_ptr, c_char, c_int64_t, c_loc\n  implicit none\n  interface\n    function check_scan_args(buf, lenv, startv, needle) result(pos) bind(C, name='check_scan_args')\n      import :: c_ptr, c_char, c_int64_t\n      type(c_ptr), value :: buf\n      integer(c_int64_t), value :: lenv\n      integer(c_int64_t), value :: startv\n      character(kind=c_char), value :: needle\n      integer(c_int64_t) :: pos\n    end function\n  end interface\n  character(kind=c_char), target :: buf(11)\n  integer(c_int64_t) :: pos\n\n  buf = [achar(104, kind=c_char), achar(101, kind=c_char), achar(108, kind=c_char), &\n         achar(108, kind=c_char), achar(111, kind=c_char), achar(10, kind=c_char), &\n         achar(119, kind=c_char), achar(111, kind=c_char), achar(114, kind=c_char), &\n         achar(108, kind=c_char), achar(100, kind=c_char)]\n\n  pos = check_scan_args(c_loc(buf(1)), 11_c_int64_t, 0_c_int64_t, achar(10, kind=c_char))\n  if (pos /= 5_c_int64_t) error stop 1\n  print *, 'ok'\nend program\n",
1749
+    );
1750
+
1751
+    let main_obj = dir.join("main.o");
1752
+    let compile_obj = Command::new(compiler("armfortas"))
1753
+        .current_dir(&dir)
1754
+        .args([
1755
+            "-c",
1756
+            src.to_str().unwrap(),
1757
+            "-o",
1758
+            main_obj.to_str().unwrap(),
1759
+        ])
1760
+        .output()
1761
+        .expect("bind(c) c_ptr+i64 value object compile failed to spawn");
1762
+    assert!(
1763
+        compile_obj.status.success(),
1764
+        "bind(c) c_ptr+i64 value object should compile: {}",
1765
+        String::from_utf8_lossy(&compile_obj.stderr)
1766
+    );
1767
+
1768
+    let exe = dir.join("bind_c_c_ptr_i64_value_args.bin");
1769
+    let link = Command::new(compiler("armfortas"))
1770
+        .current_dir(&dir)
1771
+        .args([
1772
+            main_obj.to_str().unwrap(),
1773
+            c_obj.to_str().unwrap(),
1774
+            "-o",
1775
+            exe.to_str().unwrap(),
1776
+        ])
1777
+        .output()
1778
+        .expect("bind(c) c_ptr+i64 value link failed to spawn");
1779
+    assert!(
1780
+        link.status.success(),
1781
+        "bind(c) c_ptr+i64 value objects should link: {}",
1782
+        String::from_utf8_lossy(&link.stderr)
1783
+    );
1784
+
1785
+    let run = Command::new(&exe)
1786
+        .output()
1787
+        .expect("bind(c) c_ptr+i64 value run failed");
1788
+    assert!(
1789
+        run.status.success(),
1790
+        "bind(c) c_ptr+i64 values should preserve the scalar call ABI: status={:?} stdout={} stderr={}",
1791
+        run.status,
1792
+        String::from_utf8_lossy(&run.stdout),
1793
+        String::from_utf8_lossy(&run.stderr)
1794
+    );
1795
+
1796
+    let stdout = String::from_utf8_lossy(&run.stdout);
1797
+    assert!(
1798
+        stdout.contains("ok"),
1799
+        "bind(c) c_ptr+i64 values should arrive at the C callee unchanged: {}",
1800
+        stdout
1801
+    );
1802
+
1803
+    let _ = std::fs::remove_dir_all(&dir);
1804
+}
1805
+
1806
+#[test]
1807
+fn bind_c_c_ptr_value_and_i64_values_survive_wrapper_dummy_call() {
1808
+    let dir = unique_dir("bind_c_c_ptr_i64_wrapper");
1809
+    let c_src = write_program_in(
1810
+        &dir,
1811
+        "check_scan_args.c",
1812
+        "#include <stdint.h>\n\nint64_t check_scan_args(const char *buf, int64_t len, int64_t start, char needle) {\n    if (buf == 0) return -11;\n    if (len != 11) return -12;\n    if (start != 0) return -13;\n    if ((unsigned char)needle != 10) return -14;\n    if (buf[0] != 'h') return -15;\n    if (buf[5] != '\\n') return -16;\n    return 5;\n}\n",
1813
+    );
1814
+    let c_obj = dir.join("check_scan_args.o");
1815
+    compile_c_object(&c_src, &c_obj);
1816
+
1817
+    let src = write_program_in(
1818
+        &dir,
1819
+        "main.f90",
1820
+        "module m\n  use iso_c_binding, only: c_ptr, c_char, c_int64_t\n  implicit none\n  interface\n    function check_scan_args(buf, lenv, startv, needle) result(pos) bind(C, name='check_scan_args')\n      import :: c_ptr, c_char, c_int64_t\n      type(c_ptr), value :: buf\n      integer(c_int64_t), value :: lenv\n      integer(c_int64_t), value :: startv\n      character(kind=c_char), value :: needle\n      integer(c_int64_t) :: pos\n    end function\n  end interface\ncontains\n  function wrapper(buf, lenv, startv, needle) result(pos)\n    type(c_ptr), intent(in) :: buf\n    integer(c_int64_t), intent(in) :: lenv, startv\n    character(len=1), intent(in) :: needle\n    integer(c_int64_t) :: pos\n    pos = check_scan_args(buf, lenv, startv, char(ichar(needle), kind=c_char))\n  end function\nend module\nprogram p\n  use iso_c_binding, only: c_ptr, c_char, c_int64_t, c_loc\n  use m\n  implicit none\n  character(kind=c_char), target :: buf(11)\n  integer(c_int64_t) :: pos\n\n  buf = [achar(104, kind=c_char), achar(101, kind=c_char), achar(108, kind=c_char), &\n         achar(108, kind=c_char), achar(111, kind=c_char), achar(10, kind=c_char), &\n         achar(119, kind=c_char), achar(111, kind=c_char), achar(114, kind=c_char), &\n         achar(108, kind=c_char), achar(100, kind=c_char)]\n\n  pos = wrapper(c_loc(buf(1)), 11_c_int64_t, 0_c_int64_t, char(10))\n  if (pos /= 5_c_int64_t) error stop 1\n  print *, 'ok'\nend program\n",
1821
+    );
1822
+
1823
+    let main_obj = dir.join("main.o");
1824
+    let compile_obj = Command::new(compiler("armfortas"))
1825
+        .current_dir(&dir)
1826
+        .args([
1827
+            "-c",
1828
+            src.to_str().unwrap(),
1829
+            "-o",
1830
+            main_obj.to_str().unwrap(),
1831
+        ])
1832
+        .output()
1833
+        .expect("bind(c) wrapper object compile failed to spawn");
1834
+    assert!(
1835
+        compile_obj.status.success(),
1836
+        "bind(c) wrapper object should compile: {}",
1837
+        String::from_utf8_lossy(&compile_obj.stderr)
1838
+    );
1839
+
1840
+    let exe = dir.join("bind_c_c_ptr_i64_wrapper.bin");
1841
+    let link = Command::new(compiler("armfortas"))
1842
+        .current_dir(&dir)
1843
+        .args([
1844
+            main_obj.to_str().unwrap(),
1845
+            c_obj.to_str().unwrap(),
1846
+            "-o",
1847
+            exe.to_str().unwrap(),
1848
+        ])
1849
+        .output()
1850
+        .expect("bind(c) wrapper link failed to spawn");
1851
+    assert!(
1852
+        link.status.success(),
1853
+        "bind(c) wrapper objects should link: {}",
1854
+        String::from_utf8_lossy(&link.stderr)
1855
+    );
1856
+
1857
+    let run = Command::new(&exe)
1858
+        .output()
1859
+        .expect("bind(c) wrapper run failed");
1860
+    assert!(
1861
+        run.status.success(),
1862
+        "bind(c) wrapper should preserve c_ptr and i64 dummy values: status={:?} stdout={} stderr={}",
1863
+        run.status,
1864
+        String::from_utf8_lossy(&run.stdout),
1865
+        String::from_utf8_lossy(&run.stderr)
1866
+    );
1867
+
1868
+    let stdout = String::from_utf8_lossy(&run.stdout);
1869
+    assert!(
1870
+        stdout.contains("ok"),
1871
+        "bind(c) wrapper should forward the original scalar values unchanged: {}",
1872
+        stdout
1873
+    );
1874
+
1875
+    let _ = std::fs::remove_dir_all(&dir);
1876
+}
1877
+
17341878
 #[test]
17351879
 fn bind_c_c_char_function_result_round_trips_through_wrapper_module() {
17361880
     let dir = unique_dir("bind_c_c_char_result");
@@ -2261,6 +2405,40 @@ fn allocate_bounds_size_intrinsic_lowers_without_raw_symbol() {
22612405
     let _ = std::fs::remove_file(&src);
22622406
 }
22632407
 
2408
+#[test]
2409
+fn automatic_component_array_bound_size_lowers_without_raw_symbol() {
2410
+    let src = write_program(
2411
+        "module m\n  implicit none\n  type :: state_set_t\n    integer(8) :: bits(4) = 0_8\n  contains\n    procedure :: f\n  end type\ncontains\n  subroutine f(state_set)\n    type(state_set_t), intent(inout) :: state_set\n    integer(8) :: original_bits(size(state_set%bits))\n    original_bits = state_set%bits\n    print *, size(original_bits)\n  end subroutine\nend module\n",
2412
+        "f90",
2413
+    );
2414
+    let out = unique_path("auto_component_bound_size", "o");
2415
+    let compile = Command::new(compiler("armfortas"))
2416
+        .args(["-c", src.to_str().unwrap(), "-o", out.to_str().unwrap()])
2417
+        .env("NO_COLOR", "1")
2418
+        .output()
2419
+        .expect("automatic component bound size compile failed to spawn");
2420
+    assert!(
2421
+        compile.status.success(),
2422
+        "automatic component bound size compile failed: {}",
2423
+        String::from_utf8_lossy(&compile.stderr)
2424
+    );
2425
+
2426
+    let undefined = undefined_symbols(&out);
2427
+    assert!(
2428
+        undefined.iter().any(|sym| sym == "_afs_array_size"),
2429
+        "automatic component bound size() should still lower through afs_array_size: {:?}",
2430
+        undefined
2431
+    );
2432
+    assert!(
2433
+        !undefined.iter().any(|sym| sym == "_size"),
2434
+        "automatic component bound size() should not escape as a raw symbol: {:?}",
2435
+        undefined
2436
+    );
2437
+
2438
+    let _ = std::fs::remove_file(&out);
2439
+    let _ = std::fs::remove_file(&src);
2440
+}
2441
+
22642442
 #[test]
22652443
 fn fixed_component_array_element_assignment_compiles() {
22662444
     let src = write_program(
@@ -2761,6 +2939,84 @@ fn deferred_char_component_allocate_source_copies_runtime_string_value() {
27612939
     let _ = std::fs::remove_file(&src);
27622940
 }
27632941
 
2942
+#[test]
2943
+fn typed_allocate_char_len_from_derived_component_expr_runs() {
2944
+    let src = write_program(
2945
+        "module m\n  use, intrinsic :: iso_c_binding\n  implicit none\n  type :: line_info_t\n    integer(c_size_t) :: start_pos = 0\n    integer(c_size_t) :: length = 0\n  end type line_info_t\ncontains\n  function get_line_text(data_ptr, data_size, info) result(line)\n    type(c_ptr), intent(in) :: data_ptr\n    integer(c_size_t), intent(in) :: data_size\n    type(line_info_t), intent(in) :: info\n    character(len=:), allocatable :: line\n    character(len=1, kind=c_char), pointer :: file_data(:)\n    integer :: i\n    if (info%length == 0) then\n      line = ''\n      return\n    end if\n    call c_f_pointer(data_ptr, file_data, [data_size])\n    allocate(character(len=info%length) :: line)\n    do i = 1, int(info%length)\n      line(i:i) = file_data(info%start_pos + i)\n    end do\n  end function get_line_text\nend module m\n\nprogram p\n  use, intrinsic :: iso_c_binding\n  use m\n  implicit none\n  character(kind=c_char), target :: buf(11)\n  type(line_info_t) :: info\n  character(len=:), allocatable :: line\n  buf = [char(104, kind=c_char), char(101, kind=c_char), char(108, kind=c_char), &\n         char(108, kind=c_char), char(111, kind=c_char), char(10, kind=c_char), &\n         char(119, kind=c_char), char(111, kind=c_char), char(114, kind=c_char), &\n         char(108, kind=c_char), char(100, kind=c_char)]\n  info%start_pos = 0\n  info%length = 5\n  line = get_line_text(c_loc(buf(1)), 11_c_size_t, info)\n  if (len(line) /= 5) error stop 1\n  if (line /= 'hello') error stop 2\n  print *, line\nend program p\n",
2946
+        "f90",
2947
+    );
2948
+    let out = unique_path("typed_allocate_char_len_from_component", "bin");
2949
+    let compile = Command::new(compiler("armfortas"))
2950
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
2951
+        .output()
2952
+        .expect("typed allocate char len from component compile failed to spawn");
2953
+    assert!(
2954
+        compile.status.success(),
2955
+        "typed allocate char len from component compile failed: {}",
2956
+        String::from_utf8_lossy(&compile.stderr)
2957
+    );
2958
+
2959
+    let run = Command::new(&out)
2960
+        .output()
2961
+        .expect("typed allocate char len from component run failed");
2962
+    assert!(
2963
+        run.status.success(),
2964
+        "typed allocate char len from component should preserve the runtime component length: status={:?} stdout={} stderr={}",
2965
+        run.status,
2966
+        String::from_utf8_lossy(&run.stdout),
2967
+        String::from_utf8_lossy(&run.stderr)
2968
+    );
2969
+
2970
+    let stdout = String::from_utf8_lossy(&run.stdout);
2971
+    assert!(
2972
+        stdout.contains("hello"),
2973
+        "typed allocate char len from component should return the copied text: {}",
2974
+        stdout
2975
+    );
2976
+
2977
+    let _ = std::fs::remove_file(&out);
2978
+    let _ = std::fs::remove_file(&src);
2979
+}
2980
+
2981
+#[test]
2982
+fn type_bound_deferred_char_result_preserves_pass_object_and_length() {
2983
+    let src = write_program(
2984
+        "module m\n  use, intrinsic :: iso_c_binding\n  implicit none\n  type :: line_info_t\n    integer(c_size_t) :: start_pos = 0\n    integer(c_size_t) :: length = 0\n  end type line_info_t\n  type :: holder_t\n    type(c_ptr) :: data = c_null_ptr\n    integer(c_size_t) :: size = 0\n    logical :: is_open = .false.\n  contains\n    procedure :: get_line_text\n  end type holder_t\ncontains\n  function get_line_text(this, info) result(line)\n    class(holder_t), intent(in) :: this\n    type(line_info_t), intent(in) :: info\n    character(len=:), allocatable :: line\n    character(len=1, kind=c_char), pointer :: file_data(:)\n    integer :: i\n    if (.not. this%is_open .or. .not. c_associated(this%data)) then\n      line = ''\n      return\n    end if\n    if (info%length == 0) then\n      line = ''\n      return\n    end if\n    call c_f_pointer(this%data, file_data, [this%size])\n    allocate(character(len=info%length) :: line)\n    do i = 1, int(info%length)\n      line(i:i) = file_data(info%start_pos + i)\n    end do\n  end function get_line_text\nend module m\n\nprogram p\n  use, intrinsic :: iso_c_binding\n  use m\n  implicit none\n  character(kind=c_char), target :: buf(11)\n  type(holder_t) :: holder\n  type(line_info_t) :: info\n  character(len=:), allocatable :: line\n  buf = [char(104, kind=c_char), char(101, kind=c_char), char(108, kind=c_char), &\n         char(108, kind=c_char), char(111, kind=c_char), char(10, kind=c_char), &\n         char(119, kind=c_char), char(111, kind=c_char), char(114, kind=c_char), &\n         char(108, kind=c_char), char(100, kind=c_char)]\n  holder%data = c_loc(buf(1))\n  holder%size = 11\n  holder%is_open = .true.\n  info%start_pos = 0\n  info%length = 5\n  line = holder%get_line_text(info)\n  if (len(line) /= 5) error stop 1\n  if (line /= 'hello') error stop 2\n  print *, line\nend program p\n",
2985
+        "f90",
2986
+    );
2987
+    let out = unique_path("type_bound_deferred_char_result", "bin");
2988
+    let compile = Command::new(compiler("armfortas"))
2989
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
2990
+        .output()
2991
+        .expect("type-bound deferred char result compile failed to spawn");
2992
+    assert!(
2993
+        compile.status.success(),
2994
+        "type-bound deferred char result compile failed: {}",
2995
+        String::from_utf8_lossy(&compile.stderr)
2996
+    );
2997
+
2998
+    let run = Command::new(&out)
2999
+        .output()
3000
+        .expect("type-bound deferred char result run failed");
3001
+    assert!(
3002
+        run.status.success(),
3003
+        "type-bound deferred char result should preserve the pass object and result descriptor: status={:?} stdout={} stderr={}",
3004
+        run.status,
3005
+        String::from_utf8_lossy(&run.stdout),
3006
+        String::from_utf8_lossy(&run.stderr)
3007
+    );
3008
+
3009
+    let stdout = String::from_utf8_lossy(&run.stdout);
3010
+    assert!(
3011
+        stdout.contains("hello"),
3012
+        "type-bound deferred char result should return the copied text: {}",
3013
+        stdout
3014
+    );
3015
+
3016
+    let _ = std::fs::remove_file(&out);
3017
+    let _ = std::fs::remove_file(&src);
3018
+}
3019
+
27643020
 #[test]
27653021
 fn scalar_char_substring_argument_avoids_raw_local_symbol() {
27663022
     let src = write_program(
@@ -4060,114 +4316,482 @@ fn prebuilt_archive_input_links_after_objects() {
40604316
             main_obj.to_str().unwrap(),
40614317
         ])
40624318
         .output()
4063
-        .expect("main compile spawn failed");
4319
+        .expect("main compile spawn failed");
4320
+    assert!(
4321
+        compile_main.status.success(),
4322
+        "main compile failed: {}",
4323
+        String::from_utf8_lossy(&compile_main.stderr)
4324
+    );
4325
+
4326
+    let exe = dir.join("linked_archive");
4327
+    let link = Command::new(compiler("armfortas"))
4328
+        .current_dir(&dir)
4329
+        .args([
4330
+            main_obj.to_str().unwrap(),
4331
+            archive.to_str().unwrap(),
4332
+            "-o",
4333
+            exe.to_str().unwrap(),
4334
+        ])
4335
+        .output()
4336
+        .expect("archive link spawn failed");
4337
+    assert!(
4338
+        link.status.success(),
4339
+        "prebuilt archive link failed: {}",
4340
+        String::from_utf8_lossy(&link.stderr)
4341
+    );
4342
+    assert!(
4343
+        exe.exists(),
4344
+        "prebuilt archive link should write the binary"
4345
+    );
4346
+
4347
+    let _ = std::fs::remove_dir_all(&dir);
4348
+}
4349
+
4350
+#[test]
4351
+fn dash_capital_s_produces_assembly_text() {
4352
+    let src = write_program("program p\n  print *, 1\nend program\n", "f90");
4353
+    let out = unique_path("asm", "s");
4354
+    let result = Command::new(compiler("armfortas"))
4355
+        .args(["-S", src.to_str().unwrap(), "-o", out.to_str().unwrap()])
4356
+        .output()
4357
+        .expect("spawn failed");
4358
+    assert!(
4359
+        result.status.success(),
4360
+        "-S compile failed: {}",
4361
+        String::from_utf8_lossy(&result.stderr)
4362
+    );
4363
+    let asm = std::fs::read_to_string(&out).expect("missing asm output");
4364
+    assert!(
4365
+        asm.contains("__TEXT"),
4366
+        ".s output should contain section directive"
4367
+    );
4368
+    let _ = std::fs::remove_file(&out);
4369
+    let _ = std::fs::remove_file(&src);
4370
+}
4371
+
4372
+#[test]
4373
+fn dash_capital_e_preprocesses_only() {
4374
+    let src = write_program(
4375
+        "#define X 99\nprogram p\n  print *, X\nend program\n",
4376
+        "F90",
4377
+    );
4378
+    let out = unique_path("pp", "f90");
4379
+    let result = Command::new(compiler("armfortas"))
4380
+        .args(["-E", src.to_str().unwrap(), "-o", out.to_str().unwrap()])
4381
+        .output()
4382
+        .expect("spawn failed");
4383
+    assert!(
4384
+        result.status.success(),
4385
+        "-E preprocess failed: {}",
4386
+        String::from_utf8_lossy(&result.stderr)
4387
+    );
4388
+    let pp = std::fs::read_to_string(&out).expect("missing preprocessed output");
4389
+    assert!(
4390
+        pp.contains(", 99"),
4391
+        "preprocessed text should expand the macro: {}",
4392
+        pp
4393
+    );
4394
+    let _ = std::fs::remove_file(&out);
4395
+    let _ = std::fs::remove_file(&src);
4396
+}
4397
+
4398
+#[test]
4399
+fn dash_capital_e_without_o_writes_to_stdout() {
4400
+    let dir = unique_dir("pp_stdout");
4401
+    write_program_in(
4402
+        &dir,
4403
+        "hello.F90",
4404
+        "#define X 99\nprogram p\n  print *, X\nend program\n",
4405
+    );
4406
+    let result = Command::new(compiler("armfortas"))
4407
+        .current_dir(&dir)
4408
+        .args(["-E", "hello.F90"])
4409
+        .output()
4410
+        .expect("spawn failed");
4411
+    assert!(
4412
+        result.status.success(),
4413
+        "-E preprocess failed: {}",
4414
+        String::from_utf8_lossy(&result.stderr)
4415
+    );
4416
+    let stdout = String::from_utf8_lossy(&result.stdout);
4417
+    assert!(
4418
+        stdout.contains(", 99"),
4419
+        "preprocessed output should be written to stdout: {}",
4420
+        stdout
4421
+    );
4422
+    assert!(
4423
+        !dir.join("hello").exists(),
4424
+        "default -E output should not create a bare-stem file"
4425
+    );
4426
+    let _ = std::fs::remove_dir_all(&dir);
4427
+}
4428
+
4429
+#[test]
4430
+fn dash_cpp_accepts_lowercase_preprocessor_source() {
4431
+    let src = write_program(
4432
+        "#define X 77\nprogram p\n  print *, X\nend program\n",
4433
+        "f90",
4434
+    );
4435
+    let out = unique_path("dash_cpp", "o");
4436
+    let result = Command::new(compiler("armfortas"))
4437
+        .args([
4438
+            "-cpp",
4439
+            "-c",
4440
+            src.to_str().unwrap(),
4441
+            "-o",
4442
+            out.to_str().unwrap(),
4443
+        ])
4444
+        .output()
4445
+        .expect("spawn failed");
4446
+    assert!(
4447
+        result.status.success(),
4448
+        "-cpp compile failed: {}",
4449
+        String::from_utf8_lossy(&result.stderr)
4450
+    );
4451
+    let stderr = String::from_utf8_lossy(&result.stderr);
4452
+    assert!(
4453
+        stderr.contains("-cpp is accepted for compatibility"),
4454
+        "expected a compatibility warning for -cpp: {}",
4455
+        stderr
4456
+    );
4457
+    assert!(out.exists(), "-cpp compile should produce an object file");
4458
+    let _ = std::fs::remove_file(&out);
4459
+    let _ = std::fs::remove_file(&src);
4460
+}
4461
+
4462
+#[test]
4463
+fn integer8_bit_intrinsics_accept_default_integer_positions() {
4464
+    let src = write_program(
4465
+        "program p\n  implicit none\n  integer(8) :: words(4) = 0_8\n  integer :: word_idx, bit_idx\n  word_idx = 1\n  bit_idx = 5\n  words(word_idx) = ior(words(word_idx), ishft(1_8, bit_idx))\n  print *, btest(words(word_idx), bit_idx)\nend program\n",
4466
+        "f90",
4467
+    );
4468
+    let out = unique_path("bit_intrinsics_i8", "bin");
4469
+    let compile = Command::new(compiler("armfortas"))
4470
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
4471
+        .output()
4472
+        .expect("spawn failed");
4473
+    assert!(
4474
+        compile.status.success(),
4475
+        "integer(8) bit intrinsic repro should compile: {}",
4476
+        String::from_utf8_lossy(&compile.stderr)
4477
+    );
4478
+    let run = Command::new(&out).output().expect("failed to run binary");
4479
+    assert!(
4480
+        run.status.success(),
4481
+        "integer(8) bit intrinsic repro should run:\nstdout:\n{}\nstderr:\n{}",
4482
+        String::from_utf8_lossy(&run.stdout),
4483
+        String::from_utf8_lossy(&run.stderr)
4484
+    );
4485
+    let stdout = String::from_utf8_lossy(&run.stdout);
4486
+    assert!(
4487
+        stdout.contains('T'),
4488
+        "expected btest result to stay true, got: {}",
4489
+        stdout
4490
+    );
4491
+    let _ = std::fs::remove_file(&out);
4492
+    let _ = std::fs::remove_file(&src);
4493
+}
4494
+
4495
+#[test]
4496
+fn move_alloc_into_allocatable_component_of_class_dummy_compiles_and_runs() {
4497
+    let src = write_program(
4498
+        "module m\n  implicit none\n  type :: token_t\n    integer :: x = 0\n  end type\n  type :: list_t\n    type(token_t), allocatable :: tokens(:)\n  end type\ncontains\n  subroutine grow(this)\n    class(list_t), intent(inout) :: this\n    type(token_t), allocatable :: temp(:)\n    allocate(temp(4))\n    temp(3)%x = 42\n    call move_alloc(temp, this%tokens)\n  end subroutine\nend module\nprogram p\n  use m\n  implicit none\n  type(list_t) :: items\n  call grow(items)\n  print *, size(items%tokens), items%tokens(3)%x\nend program\n",
4499
+        "f90",
4500
+    );
4501
+    let out = unique_path("move_alloc_class_component", "bin");
4502
+    let compile = Command::new(compiler("armfortas"))
4503
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
4504
+        .output()
4505
+        .expect("spawn failed");
4506
+    assert!(
4507
+        compile.status.success(),
4508
+        "class-dummy MOVE_ALLOC repro should compile: {}",
4509
+        String::from_utf8_lossy(&compile.stderr)
4510
+    );
4511
+    let run = Command::new(&out).output().expect("failed to run binary");
4512
+    assert!(
4513
+        run.status.success(),
4514
+        "class-dummy MOVE_ALLOC repro should run:\nstdout:\n{}\nstderr:\n{}",
4515
+        String::from_utf8_lossy(&run.stdout),
4516
+        String::from_utf8_lossy(&run.stderr)
4517
+    );
4518
+    let stdout = String::from_utf8_lossy(&run.stdout);
4519
+    assert!(
4520
+        stdout.contains("4") && stdout.contains("42"),
4521
+        "expected moved allocation to survive class-dummy component access, got: {}",
4522
+        stdout
4523
+    );
4524
+    let _ = std::fs::remove_file(&out);
4525
+    let _ = std::fs::remove_file(&src);
4526
+}
4527
+
4528
+#[test]
4529
+fn type_bound_subroutine_call_uses_module_qualified_symbol_and_links() {
4530
+    let src = write_program(
4531
+        "module m\n  implicit none\n  type :: counter_t\n    integer :: value = 0\n  contains\n    procedure :: bump => counter_bump\n  end type\ncontains\n  subroutine counter_bump(this, delta)\n    class(counter_t), intent(inout) :: this\n    integer, intent(in) :: delta\n    this%value = this%value + delta\n  end subroutine\nend module\nprogram p\n  use m\n  implicit none\n  type(counter_t) :: counter\n  call counter%bump(7)\n  print *, counter%value\nend program\n",
4532
+        "f90",
4533
+    );
4534
+    let out = unique_path("type_bound_subroutine_link", "bin");
4535
+    let compile = Command::new(compiler("armfortas"))
4536
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
4537
+        .output()
4538
+        .expect("spawn failed");
4539
+    assert!(
4540
+        compile.status.success(),
4541
+        "type-bound subroutine repro should compile and link: {}",
4542
+        String::from_utf8_lossy(&compile.stderr)
4543
+    );
4544
+    let run = Command::new(&out).output().expect("failed to run binary");
4545
+    assert!(
4546
+        run.status.success(),
4547
+        "type-bound subroutine repro should run:\nstdout:\n{}\nstderr:\n{}",
4548
+        String::from_utf8_lossy(&run.stdout),
4549
+        String::from_utf8_lossy(&run.stderr)
4550
+    );
4551
+    let stdout = String::from_utf8_lossy(&run.stdout);
4552
+    assert!(
4553
+        stdout.contains('7'),
4554
+        "expected type-bound subroutine call to mutate the receiver, got: {}",
4555
+        stdout
4556
+    );
4557
+    let _ = std::fs::remove_file(&out);
4558
+    let _ = std::fs::remove_file(&src);
4559
+}
4560
+
4561
+#[test]
4562
+fn type_bound_call_preserves_absent_optional_slots() {
4563
+    let src = write_program(
4564
+        "module m\n  implicit none\n  type :: list_t\n    integer :: x = 0\n  contains\n    procedure :: init\n    procedure :: ensure\n  end type\ncontains\n  subroutine init(this, n)\n    class(list_t), intent(inout) :: this\n    integer, intent(in), optional :: n\n    if (present(n)) then\n      this%x = n\n    else\n      this%x = 42\n    end if\n  end subroutine\n\n  subroutine ensure(this)\n    class(list_t), intent(inout) :: this\n    call this%init()\n  end subroutine\nend module\nprogram p\n  use m\n  implicit none\n  type(list_t) :: v\n  call v%ensure()\n  if (v%x /= 42) error stop 1\n  print *, v%x\nend program\n",
4565
+        "f90",
4566
+    );
4567
+    let out = unique_path("type_bound_optional_absent", "bin");
4568
+    let compile = Command::new(compiler("armfortas"))
4569
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
4570
+        .output()
4571
+        .expect("spawn failed");
4572
+    assert!(
4573
+        compile.status.success(),
4574
+        "type-bound optional repro should compile and link: {}",
4575
+        String::from_utf8_lossy(&compile.stderr)
4576
+    );
4577
+    let run = Command::new(&out).output().expect("failed to run binary");
4578
+    assert!(
4579
+        run.status.success(),
4580
+        "type-bound optional repro should run:\nstdout:\n{}\nstderr:\n{}",
4581
+        String::from_utf8_lossy(&run.stdout),
4582
+        String::from_utf8_lossy(&run.stderr)
4583
+    );
4584
+    let stdout = String::from_utf8_lossy(&run.stdout);
4585
+    assert!(
4586
+        stdout.contains("42"),
4587
+        "expected absent optional on type-bound call to arrive as not-present, got: {}",
4588
+        stdout
4589
+    );
4590
+    let _ = std::fs::remove_file(&out);
4591
+    let _ = std::fs::remove_file(&src);
4592
+}
4593
+
4594
+#[test]
4595
+fn imported_type_bound_call_preserves_absent_optional_slots() {
4596
+    let dir = unique_dir("imported_type_bound_optional");
4597
+    let mod_src = dir.join("m.f90");
4598
+    let main_src = dir.join("p.f90");
4599
+    std::fs::write(
4600
+        &mod_src,
4601
+        "module m\n  implicit none\n  type :: list_t\n    integer :: x = 0\n  contains\n    procedure :: init\n  end type\ncontains\n  subroutine init(this, n)\n    class(list_t), intent(inout) :: this\n    integer, intent(in), optional :: n\n    if (present(n)) then\n      this%x = n\n    else\n      this%x = 42\n    end if\n  end subroutine\nend module\n",
4602
+    )
4603
+    .expect("write module");
4604
+    std::fs::write(
4605
+        &main_src,
4606
+        "program p\n  use m\n  implicit none\n  type(list_t) :: v\n  call v%init()\n  if (v%x /= 42) error stop 1\n  print *, v%x\nend program\n",
4607
+    )
4608
+    .expect("write program");
4609
+
4610
+    let mod_obj = dir.join("m.o");
4611
+    let module_build = Command::new(compiler("armfortas"))
4612
+        .args([
4613
+            "-c",
4614
+            mod_src.to_str().unwrap(),
4615
+            "-J",
4616
+            dir.to_str().unwrap(),
4617
+            "-o",
4618
+            mod_obj.to_str().unwrap(),
4619
+        ])
4620
+        .output()
4621
+        .expect("spawn failed");
4622
+    assert!(
4623
+        module_build.status.success(),
4624
+        "module build should succeed: {}",
4625
+        String::from_utf8_lossy(&module_build.stderr)
4626
+    );
4627
+
4628
+    let main_obj = dir.join("p.o");
4629
+    let main_compile = Command::new(compiler("armfortas"))
4630
+        .args([
4631
+            "-c",
4632
+            main_src.to_str().unwrap(),
4633
+            "-I",
4634
+            dir.to_str().unwrap(),
4635
+            "-J",
4636
+            dir.to_str().unwrap(),
4637
+            "-o",
4638
+            main_obj.to_str().unwrap(),
4639
+        ])
4640
+        .output()
4641
+        .expect("spawn failed");
4642
+    assert!(
4643
+        main_compile.status.success(),
4644
+        "main compile should succeed: {}",
4645
+        String::from_utf8_lossy(&main_compile.stderr)
4646
+    );
4647
+
4648
+    let out = dir.join("p.bin");
4649
+    let link = Command::new(compiler("armfortas"))
4650
+        .args([
4651
+            main_obj.to_str().unwrap(),
4652
+            mod_obj.to_str().unwrap(),
4653
+            "-o",
4654
+            out.to_str().unwrap(),
4655
+        ])
4656
+        .output()
4657
+        .expect("spawn failed");
4658
+    assert!(
4659
+        link.status.success(),
4660
+        "link should succeed: {}",
4661
+        String::from_utf8_lossy(&link.stderr)
4662
+    );
4663
+
4664
+    let run = Command::new(&out).output().expect("failed to run binary");
4665
+    assert!(
4666
+        run.status.success(),
4667
+        "imported type-bound optional repro should run:\nstdout:\n{}\nstderr:\n{}",
4668
+        String::from_utf8_lossy(&run.stdout),
4669
+        String::from_utf8_lossy(&run.stderr)
4670
+    );
4671
+    let stdout = String::from_utf8_lossy(&run.stdout);
4672
+    assert!(
4673
+        stdout.contains("42"),
4674
+        "expected imported absent optional on type-bound call to arrive as not-present, got: {}",
4675
+        stdout
4676
+    );
4677
+}
4678
+
4679
+#[test]
4680
+fn imported_type_bound_alias_preserves_absent_optional_slots() {
4681
+    let dir = unique_dir("imported_type_bound_optional_alias");
4682
+    let mod_src = dir.join("m.f90");
4683
+    let main_src = dir.join("p.f90");
4684
+    std::fs::write(
4685
+        &mod_src,
4686
+        "module m\n  implicit none\n  type :: list_t\n    integer :: x = 0\n  contains\n    procedure :: init => token_list_init\n  end type\ncontains\n  subroutine token_list_init(this, n)\n    class(list_t), intent(inout) :: this\n    integer, intent(in), optional :: n\n    if (present(n)) then\n      this%x = n\n    else\n      this%x = 42\n    end if\n  end subroutine\nend module\n",
4687
+    )
4688
+    .expect("write module");
4689
+    std::fs::write(
4690
+        &main_src,
4691
+        "program p\n  use m\n  implicit none\n  type(list_t) :: v\n  call v%init()\n  if (v%x /= 42) error stop 1\n  print *, v%x\nend program\n",
4692
+    )
4693
+    .expect("write program");
4694
+
4695
+    let mod_obj = dir.join("m.o");
4696
+    let module_build = Command::new(compiler("armfortas"))
4697
+        .args([
4698
+            "-c",
4699
+            mod_src.to_str().unwrap(),
4700
+            "-J",
4701
+            dir.to_str().unwrap(),
4702
+            "-o",
4703
+            mod_obj.to_str().unwrap(),
4704
+        ])
4705
+        .output()
4706
+        .expect("spawn failed");
4707
+    assert!(
4708
+        module_build.status.success(),
4709
+        "module build should succeed: {}",
4710
+        String::from_utf8_lossy(&module_build.stderr)
4711
+    );
4712
+
4713
+    let main_obj = dir.join("p.o");
4714
+    let main_compile = Command::new(compiler("armfortas"))
4715
+        .args([
4716
+            "-c",
4717
+            main_src.to_str().unwrap(),
4718
+            "-I",
4719
+            dir.to_str().unwrap(),
4720
+            "-J",
4721
+            dir.to_str().unwrap(),
4722
+            "-o",
4723
+            main_obj.to_str().unwrap(),
4724
+        ])
4725
+        .output()
4726
+        .expect("spawn failed");
40644727
     assert!(
4065
-        compile_main.status.success(),
4066
-        "main compile failed: {}",
4067
-        String::from_utf8_lossy(&compile_main.stderr)
4728
+        main_compile.status.success(),
4729
+        "main compile should succeed: {}",
4730
+        String::from_utf8_lossy(&main_compile.stderr)
40684731
     );
40694732
 
4070
-    let exe = dir.join("linked_archive");
4733
+    let out = dir.join("p.bin");
40714734
     let link = Command::new(compiler("armfortas"))
4072
-        .current_dir(&dir)
40734735
         .args([
40744736
             main_obj.to_str().unwrap(),
4075
-            archive.to_str().unwrap(),
4737
+            mod_obj.to_str().unwrap(),
40764738
             "-o",
4077
-            exe.to_str().unwrap(),
4739
+            out.to_str().unwrap(),
40784740
         ])
40794741
         .output()
4080
-        .expect("archive link spawn failed");
4742
+        .expect("spawn failed");
40814743
     assert!(
40824744
         link.status.success(),
4083
-        "prebuilt archive link failed: {}",
4745
+        "link should succeed: {}",
40844746
         String::from_utf8_lossy(&link.stderr)
40854747
     );
4086
-    assert!(
4087
-        exe.exists(),
4088
-        "prebuilt archive link should write the binary"
4089
-    );
4090
-
4091
-    let _ = std::fs::remove_dir_all(&dir);
4092
-}
40934748
 
4094
-#[test]
4095
-fn dash_capital_s_produces_assembly_text() {
4096
-    let src = write_program("program p\n  print *, 1\nend program\n", "f90");
4097
-    let out = unique_path("asm", "s");
4098
-    let result = Command::new(compiler("armfortas"))
4099
-        .args(["-S", src.to_str().unwrap(), "-o", out.to_str().unwrap()])
4100
-        .output()
4101
-        .expect("spawn failed");
4749
+    let run = Command::new(&out).output().expect("failed to run binary");
41024750
     assert!(
4103
-        result.status.success(),
4104
-        "-S compile failed: {}",
4105
-        String::from_utf8_lossy(&result.stderr)
4751
+        run.status.success(),
4752
+        "imported aliased type-bound optional repro should run:\nstdout:\n{}\nstderr:\n{}",
4753
+        String::from_utf8_lossy(&run.stdout),
4754
+        String::from_utf8_lossy(&run.stderr)
41064755
     );
4107
-    let asm = std::fs::read_to_string(&out).expect("missing asm output");
4756
+    let stdout = String::from_utf8_lossy(&run.stdout);
41084757
     assert!(
4109
-        asm.contains("__TEXT"),
4110
-        ".s output should contain section directive"
4758
+        stdout.contains("42"),
4759
+        "expected imported aliased absent optional on type-bound call to arrive as not-present, got: {}",
4760
+        stdout
41114761
     );
4112
-    let _ = std::fs::remove_file(&out);
4113
-    let _ = std::fs::remove_file(&src);
41144762
 }
41154763
 
41164764
 #[test]
4117
-fn dash_capital_e_preprocesses_only() {
4765
+fn type_bound_call_on_allocatable_component_array_element_mutates_real_receiver() {
41184766
     let src = write_program(
4119
-        "#define X 99\nprogram p\n  print *, X\nend program\n",
4120
-        "F90",
4767
+        "module m\n  implicit none\n  type :: item_t\n    integer :: n = 0\n    integer, allocatable :: vals(:)\n  contains\n    procedure :: push\n  end type\n  type :: container_t\n    type(item_t), allocatable :: items(:)\n  contains\n    procedure :: init\n  end type\ncontains\n  subroutine push(this, value)\n    class(item_t), intent(inout) :: this\n    integer, intent(in) :: value\n    if (.not. allocated(this%vals)) allocate(this%vals(4))\n    this%n = this%n + 1\n    this%vals(this%n) = value\n  end subroutine\n  subroutine init(this, count)\n    class(container_t), intent(inout) :: this\n    integer, intent(in) :: count\n    if (allocated(this%items)) deallocate(this%items)\n    allocate(this%items(count))\n  end subroutine\nend module\nprogram p\n  use m\n  implicit none\n  type(container_t) :: box\n  call box%init(2)\n  call box%items(1)%push(19)\n  if (box%items(1)%n /= 1) error stop 1\n  if (box%items(1)%vals(1) /= 19) error stop 2\n  print *, box%items(1)%n, box%items(1)%vals(1)\nend program\n",
4768
+        "f90",
41214769
     );
4122
-    let out = unique_path("pp", "f90");
4123
-    let result = Command::new(compiler("armfortas"))
4124
-        .args(["-E", src.to_str().unwrap(), "-o", out.to_str().unwrap()])
4770
+    let out = unique_path("type_bound_component_array_elem", "bin");
4771
+    let compile = Command::new(compiler("armfortas"))
4772
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
41254773
         .output()
41264774
         .expect("spawn failed");
41274775
     assert!(
4128
-        result.status.success(),
4129
-        "-E preprocess failed: {}",
4130
-        String::from_utf8_lossy(&result.stderr)
4131
-    );
4132
-    let pp = std::fs::read_to_string(&out).expect("missing preprocessed output");
4133
-    assert!(
4134
-        pp.contains(", 99"),
4135
-        "preprocessed text should expand the macro: {}",
4136
-        pp
4137
-    );
4138
-    let _ = std::fs::remove_file(&out);
4139
-    let _ = std::fs::remove_file(&src);
4140
-}
4141
-
4142
-#[test]
4143
-fn dash_capital_e_without_o_writes_to_stdout() {
4144
-    let dir = unique_dir("pp_stdout");
4145
-    write_program_in(
4146
-        &dir,
4147
-        "hello.F90",
4148
-        "#define X 99\nprogram p\n  print *, X\nend program\n",
4776
+        compile.status.success(),
4777
+        "component array element type-bound repro should compile and link: {}",
4778
+        String::from_utf8_lossy(&compile.stderr)
41494779
     );
4150
-    let result = Command::new(compiler("armfortas"))
4151
-        .current_dir(&dir)
4152
-        .args(["-E", "hello.F90"])
4153
-        .output()
4154
-        .expect("spawn failed");
4780
+    let run = Command::new(&out).output().expect("failed to run binary");
41554781
     assert!(
4156
-        result.status.success(),
4157
-        "-E preprocess failed: {}",
4158
-        String::from_utf8_lossy(&result.stderr)
4782
+        run.status.success(),
4783
+        "component array element type-bound repro should run:\nstdout:\n{}\nstderr:\n{}",
4784
+        String::from_utf8_lossy(&run.stdout),
4785
+        String::from_utf8_lossy(&run.stderr)
41594786
     );
4160
-    let stdout = String::from_utf8_lossy(&result.stdout);
4787
+    let stdout = String::from_utf8_lossy(&run.stdout);
41614788
     assert!(
4162
-        stdout.contains(", 99"),
4163
-        "preprocessed output should be written to stdout: {}",
4789
+        stdout.contains("1") && stdout.contains("19"),
4790
+        "expected component array element type-bound call to mutate the real receiver, got: {}",
41644791
         stdout
41654792
     );
4166
-    assert!(
4167
-        !dir.join("hello").exists(),
4168
-        "default -E output should not create a bare-stem file"
4169
-    );
4170
-    let _ = std::fs::remove_dir_all(&dir);
4793
+    let _ = std::fs::remove_file(&out);
4794
+    let _ = std::fs::remove_file(&src);
41714795
 }
41724796
 
41734797
 #[test]
@@ -8060,6 +8684,176 @@ fn public_defined_assignment_in_private_module_round_trips_through_amod_and_runs
80608684
     let _ = std::fs::remove_dir_all(&dir);
80618685
 }
80628686
 
8687
+#[test]
8688
+fn imported_public_type_bound_private_target_preserves_absent_optional_slots() {
8689
+    let dir = unique_dir("imported_type_bound_private_target_optional");
8690
+    let mod_src = dir.join("m.f90");
8691
+    let main_src = dir.join("p.f90");
8692
+    std::fs::write(
8693
+        &mod_src,
8694
+        "module m\n  implicit none\n  private\n  public :: list_t\n  type :: list_t\n    integer :: x = 0\n  contains\n    procedure :: init => token_list_init\n  end type\ncontains\n  subroutine token_list_init(this, n)\n    class(list_t), intent(inout) :: this\n    integer, intent(in), optional :: n\n    if (present(n)) then\n      this%x = n\n    else\n      this%x = 42\n    end if\n  end subroutine\nend module\n",
8695
+    )
8696
+    .expect("write module");
8697
+    std::fs::write(
8698
+        &main_src,
8699
+        "program p\n  use m\n  implicit none\n  type(list_t) :: v\n  call v%init()\n  if (v%x /= 42) error stop 1\n  print *, v%x\nend program\n",
8700
+    )
8701
+    .expect("write program");
8702
+
8703
+    let mod_obj = dir.join("m.o");
8704
+    let module_build = Command::new(compiler("armfortas"))
8705
+        .args([
8706
+            "-c",
8707
+            mod_src.to_str().unwrap(),
8708
+            "-J",
8709
+            dir.to_str().unwrap(),
8710
+            "-o",
8711
+            mod_obj.to_str().unwrap(),
8712
+        ])
8713
+        .output()
8714
+        .expect("spawn failed");
8715
+    assert!(
8716
+        module_build.status.success(),
8717
+        "module build should succeed: {}",
8718
+        String::from_utf8_lossy(&module_build.stderr)
8719
+    );
8720
+
8721
+    let main_obj = dir.join("p.o");
8722
+    let main_compile = Command::new(compiler("armfortas"))
8723
+        .args([
8724
+            "-c",
8725
+            main_src.to_str().unwrap(),
8726
+            "-I",
8727
+            dir.to_str().unwrap(),
8728
+            "-J",
8729
+            dir.to_str().unwrap(),
8730
+            "-o",
8731
+            main_obj.to_str().unwrap(),
8732
+        ])
8733
+        .output()
8734
+        .expect("spawn failed");
8735
+    assert!(
8736
+        main_compile.status.success(),
8737
+        "main compile should succeed: {}",
8738
+        String::from_utf8_lossy(&main_compile.stderr)
8739
+    );
8740
+
8741
+    let out = dir.join("p.bin");
8742
+    let link = Command::new(compiler("armfortas"))
8743
+        .args([
8744
+            main_obj.to_str().unwrap(),
8745
+            mod_obj.to_str().unwrap(),
8746
+            "-o",
8747
+            out.to_str().unwrap(),
8748
+        ])
8749
+        .output()
8750
+        .expect("spawn failed");
8751
+    assert!(
8752
+        link.status.success(),
8753
+        "link should succeed: {}",
8754
+        String::from_utf8_lossy(&link.stderr)
8755
+    );
8756
+
8757
+    let run = Command::new(&out).output().expect("failed to run binary");
8758
+    assert!(
8759
+        run.status.success(),
8760
+        "imported private target type-bound optional repro should run:\nstdout:\n{}\nstderr:\n{}",
8761
+        String::from_utf8_lossy(&run.stdout),
8762
+        String::from_utf8_lossy(&run.stderr)
8763
+    );
8764
+    let stdout = String::from_utf8_lossy(&run.stdout);
8765
+    assert!(
8766
+        stdout.contains("42"),
8767
+        "expected imported private target absent optional on type-bound call to arrive as not-present, got: {}",
8768
+        stdout
8769
+    );
8770
+}
8771
+
8772
+#[test]
8773
+fn imported_type_bound_function_returning_derived_value_round_trips_through_private_target() {
8774
+    let dir = unique_dir("imported_type_bound_private_result");
8775
+    let mod_src = dir.join("m.f90");
8776
+    let main_src = dir.join("p.f90");
8777
+    std::fs::write(
8778
+        &mod_src,
8779
+        "module m\n  implicit none\n  private\n  public :: token_t, list_t\n  type :: token_t\n    integer :: value = 0\n  end type\n  type :: list_t\n  contains\n    procedure :: get => token_list_get\n  end type\ncontains\n  function token_list_get(this, idx) result(tok)\n    class(list_t), intent(in) :: this\n    integer, intent(in) :: idx\n    type(token_t) :: tok\n    tok%value = idx + 40\n  end function\nend module\n",
8780
+    )
8781
+    .expect("write module");
8782
+    std::fs::write(
8783
+        &main_src,
8784
+        "program p\n  use m\n  implicit none\n  type(list_t) :: v\n  type(token_t) :: tok\n  tok = v%get(2)\n  if (tok%value /= 42) error stop 1\n  if (v%get(3)%value /= 43) error stop 2\n  print *, tok%value, v%get(3)%value\nend program\n",
8785
+    )
8786
+    .expect("write program");
8787
+
8788
+    let mod_obj = dir.join("m.o");
8789
+    let module_build = Command::new(compiler("armfortas"))
8790
+        .args([
8791
+            "-c",
8792
+            mod_src.to_str().unwrap(),
8793
+            "-J",
8794
+            dir.to_str().unwrap(),
8795
+            "-o",
8796
+            mod_obj.to_str().unwrap(),
8797
+        ])
8798
+        .output()
8799
+        .expect("spawn failed");
8800
+    assert!(
8801
+        module_build.status.success(),
8802
+        "module build should succeed: {}",
8803
+        String::from_utf8_lossy(&module_build.stderr)
8804
+    );
8805
+
8806
+    let main_obj = dir.join("p.o");
8807
+    let main_compile = Command::new(compiler("armfortas"))
8808
+        .args([
8809
+            "-c",
8810
+            main_src.to_str().unwrap(),
8811
+            "-I",
8812
+            dir.to_str().unwrap(),
8813
+            "-J",
8814
+            dir.to_str().unwrap(),
8815
+            "-o",
8816
+            main_obj.to_str().unwrap(),
8817
+        ])
8818
+        .output()
8819
+        .expect("spawn failed");
8820
+    assert!(
8821
+        main_compile.status.success(),
8822
+        "main compile should succeed: {}",
8823
+        String::from_utf8_lossy(&main_compile.stderr)
8824
+    );
8825
+
8826
+    let out = dir.join("p.bin");
8827
+    let link = Command::new(compiler("armfortas"))
8828
+        .args([
8829
+            main_obj.to_str().unwrap(),
8830
+            mod_obj.to_str().unwrap(),
8831
+            "-o",
8832
+            out.to_str().unwrap(),
8833
+        ])
8834
+        .output()
8835
+        .expect("spawn failed");
8836
+    assert!(
8837
+        link.status.success(),
8838
+        "link should succeed: {}",
8839
+        String::from_utf8_lossy(&link.stderr)
8840
+    );
8841
+
8842
+    let run = Command::new(&out).output().expect("failed to run binary");
8843
+    assert!(
8844
+        run.status.success(),
8845
+        "imported private target type-bound function result repro should run:\nstdout:\n{}\nstderr:\n{}",
8846
+        String::from_utf8_lossy(&run.stdout),
8847
+        String::from_utf8_lossy(&run.stderr)
8848
+    );
8849
+    let stdout = String::from_utf8_lossy(&run.stdout);
8850
+    assert!(
8851
+        stdout.contains("42") && stdout.contains("43"),
8852
+        "expected imported type-bound derived results to survive private target round-trip, got: {}",
8853
+        stdout
8854
+    );
8855
+}
8856
+
80638857
 #[test]
80648858
 fn enum_bind_c_enumerators_compile_and_run() {
80658859
     let src = write_program(
@@ -9486,6 +10280,41 @@ fn fixed_allocatable_character_substring_compiles_and_runs() {
948610280
     let _ = std::fs::remove_file(&src);
948710281
 }
948810282
 
10283
+#[test]
10284
+fn allocatable_fixed_char_array_element_substring_assignment_preserves_written_byte() {
10285
+    let src = write_program(
10286
+        "program p\n  implicit none\n  character(len=8), allocatable :: temp(:)\n  allocate(temp(1))\n  temp(1) = 'hello'\n  temp(1)(6:6) = char(0)\n  if (iachar(temp(1)(6:6)) /= 0) error stop 1\n  print *, iachar(temp(1)(6:6))\nend program\n",
10287
+        "f90",
10288
+    );
10289
+    let out = unique_path("alloc_char_elem_substring", "bin");
10290
+    let compile = Command::new(compiler("armfortas"))
10291
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
10292
+        .output()
10293
+        .expect("allocatable char array element substring compile spawn failed");
10294
+    assert!(
10295
+        compile.status.success(),
10296
+        "allocatable char array element substring should compile: {}",
10297
+        String::from_utf8_lossy(&compile.stderr)
10298
+    );
10299
+
10300
+    let run = Command::new(&out).output().expect("run failed");
10301
+    assert!(
10302
+        run.status.success(),
10303
+        "allocatable char array element substring should run: status={:?} stderr={}",
10304
+        run.status,
10305
+        String::from_utf8_lossy(&run.stderr)
10306
+    );
10307
+    let stdout = String::from_utf8_lossy(&run.stdout);
10308
+    assert!(
10309
+        stdout.contains('0'),
10310
+        "unexpected allocatable char array element substring output: {}",
10311
+        stdout
10312
+    );
10313
+
10314
+    let _ = std::fs::remove_file(&out);
10315
+    let _ = std::fs::remove_file(&src);
10316
+}
10317
+
948910318
 #[test]
949010319
 fn allocatable_scalar_substring_actual_preserves_hidden_len() {
949110320
     let src = write_program(
tests/memory_runtime.rsmodified
@@ -224,7 +224,11 @@ fn allocate_source_array_infers_shape_and_copies_values() {
224224
         String::from_utf8_lossy(&run.stderr)
225225
     );
226226
     let stdout = String::from_utf8_lossy(&run.stdout);
227
-    assert!(stdout.contains("3"), "expected inferred size in output: {}", stdout);
227
+    assert!(
228
+        stdout.contains("3"),
229
+        "expected inferred size in output: {}",
230
+        stdout
231
+    );
228232
     assert!(
229233
         stdout.contains("10") && stdout.contains("20") && stdout.contains("30"),
230234
         "expected copied values in output: {}",
@@ -261,7 +265,11 @@ fn allocate_mold_array_infers_shape_without_source_copy() {
261265
         String::from_utf8_lossy(&run.stderr)
262266
     );
263267
     let stdout = String::from_utf8_lossy(&run.stdout);
264
-    assert!(stdout.contains("4"), "expected inferred mold size in output: {}", stdout);
268
+    assert!(
269
+        stdout.contains("4"),
270
+        "expected inferred mold size in output: {}",
271
+        stdout
272
+    );
265273
 
266274
     let _ = std::fs::remove_dir_all(&dir);
267275
 }
@@ -293,7 +301,11 @@ fn allocate_source_scalar_initializes_allocatable_scalar() {
293301
         String::from_utf8_lossy(&run.stderr)
294302
     );
295303
     let stdout = String::from_utf8_lossy(&run.stdout);
296
-    assert!(stdout.contains("7"), "expected initialized scalar in output: {}", stdout);
304
+    assert!(
305
+        stdout.contains("7"),
306
+        "expected initialized scalar in output: {}",
307
+        stdout
308
+    );
297309
 
298310
     let _ = std::fs::remove_dir_all(&dir);
299311
 }
@@ -325,7 +337,11 @@ fn allocate_component_source_array_infers_shape_and_copies_values() {
325337
         String::from_utf8_lossy(&run.stderr)
326338
     );
327339
     let stdout = String::from_utf8_lossy(&run.stdout);
328
-    assert!(stdout.contains("2"), "expected inferred component size in output: {}", stdout);
340
+    assert!(
341
+        stdout.contains("2"),
342
+        "expected inferred component size in output: {}",
343
+        stdout
344
+    );
329345
     assert!(
330346
         stdout.contains("4") && stdout.contains("5"),
331347
         "expected copied component values in output: {}",
tests/regalloc_runtime.rsmodified
@@ -146,7 +146,14 @@ fn gp_values_live_across_call_pressure_survive() {
146146
     let src = "program p\n  use iso_c_binding, only: c_int\n  implicit none\n  interface\n    function check_gp_live(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) result(v) bind(C, name='check_gp_live')\n      import :: c_int\n      integer(c_int), value :: a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16\n      integer(c_int) :: v\n    end function check_gp_live\n  end interface\n  integer(c_int) :: total\n  integer(c_int) :: a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16\n  a1 = 1\n  a2 = 2\n  a3 = 3\n  a4 = 4\n  a5 = 5\n  a6 = 6\n  a7 = 7\n  a8 = 8\n  a9 = 9\n  a10 = 10\n  a11 = 11\n  a12 = 12\n  a13 = 13\n  a14 = 14\n  a15 = 15\n  a16 = 16\n  total = check_gp_live(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16)\n  total = total + a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 + a14 + a15 + a16\n  if (total /= 272_c_int) error stop 1\n  print *, total\nend program\n";
147147
 
148148
     for opt in ["-O0", "-O2"] {
149
-        compile_and_run_with_c("gp_live_across_call", "check_gp_live.c", c_text, src, opt, "272");
149
+        compile_and_run_with_c(
150
+            "gp_live_across_call",
151
+            "check_gp_live.c",
152
+            c_text,
153
+            src,
154
+            opt,
155
+            "272",
156
+        );
150157
     }
151158
 }
152159
 
@@ -156,7 +163,14 @@ fn fp_values_live_across_call_pressure_survive() {
156163
     let src = "program p\n  use iso_c_binding, only: c_int, c_double\n  implicit none\n  interface\n    function check_fp_live(d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12) result(v) bind(C, name='check_fp_live')\n      import :: c_int, c_double\n      real(c_double), value :: d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12\n      integer(c_int) :: v\n    end function check_fp_live\n  end interface\n  integer(c_int) :: total\n  real(c_double) :: d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12\n  d1 = 1.0d0\n  d2 = 2.0d0\n  d3 = 3.0d0\n  d4 = 4.0d0\n  d5 = 5.0d0\n  d6 = 6.0d0\n  d7 = 7.0d0\n  d8 = 8.0d0\n  d9 = 9.0d0\n  d10 = 10.0d0\n  d11 = 11.0d0\n  d12 = 12.0d0\n  total = check_fp_live(d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12)\n  total = total + int(d1) + int(d2) + int(d3) + int(d4) + int(d5) + int(d6) + int(d7) + int(d8) + int(d9) + int(d10) + int(d11) + int(d12)\n  if (total /= 156_c_int) error stop 1\n  print *, total\nend program\n";
157164
 
158165
     for opt in ["-O0", "-O2"] {
159
-        compile_and_run_with_c("fp_live_across_call", "check_fp_live.c", c_text, src, opt, "156");
166
+        compile_and_run_with_c(
167
+            "fp_live_across_call",
168
+            "check_fp_live.c",
169
+            c_text,
170
+            src,
171
+            opt,
172
+            "156",
173
+        );
160174
     }
161175
 }
162176