Preserve driver and ABI fixes
Authored by
mfwolffe <wolffemf@dukes.jmu.edu>
- SHA
e7acdc6ef04e01d1556a4492fccf416d65ff3daf- Parents
-
44ed46a - Tree
93b66fd
e7acdc6
e7acdc6ef04e01d1556a4492fccf416d65ff3daf44ed46a
93b66fd| Status | File | + | - |
|---|---|---|---|
| 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 { | ||
| 112 | 112 | pub emit_tokens: bool, // --emit-tokens |
| 113 | 113 | pub preprocess_only: bool, // -E |
| 114 | 114 | pub preprocessor_defines: Vec<(String, String)>, |
| 115 | + pub cpp_compat: bool, // -cpp (accepted; preprocessing already runs) | |
| 115 | 116 | |
| 116 | 117 | // ---- Language ---- |
| 117 | 118 | pub std: Option<crate::sema::validate::FortranStandard>, |
@@ -142,6 +143,7 @@ pub struct Options { | ||
| 142 | 143 | pub diagnostics_format: DiagnosticsFormat, // --diagnostics-format= |
| 143 | 144 | pub check_bounds: bool, // -fcheck=bounds |
| 144 | 145 | pub check_all: bool, // -fcheck=all |
| 146 | + pub backtrace_requested: bool, // -fbacktrace (accepted; runtime wiring TODO) | |
| 145 | 147 | |
| 146 | 148 | // ---- Search paths / linking ---- |
| 147 | 149 | /// Directories to search for `.amod` module files (`-I <dir>`). |
@@ -178,6 +180,7 @@ impl Default for Options { | ||
| 178 | 180 | emit_tokens: false, |
| 179 | 181 | preprocess_only: false, |
| 180 | 182 | preprocessor_defines: Vec::new(), |
| 183 | + cpp_compat: false, | |
| 181 | 184 | std: Some(crate::sema::validate::FortranStandard::F2018), |
| 182 | 185 | source_form_override: None, |
| 183 | 186 | default_integer_8: false, |
@@ -200,6 +203,7 @@ impl Default for Options { | ||
| 200 | 203 | diagnostics_format: DiagnosticsFormat::Text, |
| 201 | 204 | check_bounds: false, |
| 202 | 205 | check_all: false, |
| 206 | + backtrace_requested: false, | |
| 203 | 207 | module_search_paths: Vec::new(), |
| 204 | 208 | module_output_dir: None, |
| 205 | 209 | library_search_paths: Vec::new(), |
@@ -283,6 +287,7 @@ pub fn parse_cli(raw_args: &[String]) -> Result<ParsedCli, String> { | ||
| 283 | 287 | "-S" => opts.emit_asm = true, |
| 284 | 288 | "-c" => opts.emit_obj = true, |
| 285 | 289 | "-E" => opts.preprocess_only = true, |
| 290 | + "-cpp" => opts.cpp_compat = true, | |
| 286 | 291 | "-D" => { |
| 287 | 292 | i += 1; |
| 288 | 293 | 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> { | ||
| 359 | 364 | "-static" => opts.static_link = true, |
| 360 | 365 | |
| 361 | 366 | // ---- 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 | + } | |
| 362 | 382 | arg if arg.starts_with("--std=") => { |
| 363 | 383 | let val = &arg["--std=".len()..]; |
| 364 | 384 | opts.std = Some( |
@@ -396,6 +416,7 @@ pub fn parse_cli(raw_args: &[String]) -> Result<ParsedCli, String> { | ||
| 396 | 416 | opts.check_bounds = true; |
| 397 | 417 | opts.check_all = true; |
| 398 | 418 | } |
| 419 | + "-fbacktrace" => opts.backtrace_requested = true, | |
| 399 | 420 | |
| 400 | 421 | // ---- Warnings (accepted; gating is gradual sprint work) ---- |
| 401 | 422 | "-Wall" => opts.warn_all = true, |
@@ -628,6 +649,13 @@ fn set_output_path(opts: &mut Options, value: &str) -> Result<(), String> { | ||
| 628 | 649 | } |
| 629 | 650 | |
| 630 | 651 | 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 | + | |
| 631 | 659 | if opts.check_all { |
| 632 | 660 | opts.cli_warnings.push( |
| 633 | 661 | "-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]) { | ||
| 665 | 693 | opts.cli_warnings |
| 666 | 694 | .push("-g is accepted, but debug info emission is not yet implemented".into()); |
| 667 | 695 | } |
| 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 | + } | |
| 668 | 701 | |
| 669 | 702 | let suppress_unknown_warning_option = opts |
| 670 | 703 | .disabled_warnings |
@@ -687,10 +720,12 @@ COMPILATION: | ||
| 687 | 720 | -c Compile to object file only (no linking) |
| 688 | 721 | -S Emit assembly text |
| 689 | 722 | -E Preprocess only |
| 723 | + -cpp Accept GNU-style preprocessing flag | |
| 690 | 724 | -D<name>[=<value>] Define a preprocessor macro |
| 691 | 725 | -o <file> Output file name |
| 692 | 726 | |
| 693 | 727 | LANGUAGE: |
| 728 | + -std=<standard> GNU-compatible alias for --std=<standard> | |
| 694 | 729 | --std=<standard> Fortran standard (f77, f90, f95, f2003, f2008, f2018, f2023) |
| 695 | 730 | -ffree-form Force free-form source |
| 696 | 731 | -ffixed-form Force fixed-form source |
@@ -716,6 +751,7 @@ WARNINGS: | ||
| 716 | 751 | |
| 717 | 752 | DEBUGGING: |
| 718 | 753 | -g Generate debug information (DWARF emission TODO) |
| 754 | + -fbacktrace Accept GNU-style runtime backtrace flag | |
| 719 | 755 | --emit-ir Dump IR to the output path |
| 720 | 756 | --emit-ast Dump AST to the output path |
| 721 | 757 | --emit-tokens Dump token stream to the output path |
@@ -1923,6 +1959,52 @@ mod tests { | ||
| 1923 | 1959 | ); |
| 1924 | 1960 | } |
| 1925 | 1961 | |
| 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 | + | |
| 1926 | 2008 | fn i128_fixture() -> PathBuf { |
| 1927 | 2009 | let path = PathBuf::from("tests/fixtures").join("integer16_ir.f90"); |
| 1928 | 2010 | assert!(path.exists(), "missing test fixture {}", path.display()); |
src/ir/lower.rsmodified@@ -2852,7 +2852,13 @@ fn lower_unit( | ||
| 2852 | 2852 | else { |
| 2853 | 2853 | continue; |
| 2854 | 2854 | }; |
| 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 | + ); | |
| 2856 | 2862 | let len_addr = b.alloca(IrType::Int(IntWidth::I64)); |
| 2857 | 2863 | let len_val = clamp_nonnegative_i64(&mut b, len_raw); |
| 2858 | 2864 | b.store(len_val, len_addr); |
@@ -2875,6 +2881,7 @@ fn lower_unit( | ||
| 2875 | 2881 | decls, |
| 2876 | 2882 | &visible_param_consts, |
| 2877 | 2883 | ctx.st, |
| 2884 | + type_layouts, | |
| 2878 | 2885 | ); |
| 2879 | 2886 | |
| 2880 | 2887 | install_common_locals(&mut b, &mut ctx.locals, decls); |
@@ -3266,6 +3273,7 @@ fn lower_unit( | ||
| 3266 | 3273 | decls, |
| 3267 | 3274 | &visible_param_consts, |
| 3268 | 3275 | ctx.st, |
| 3276 | + type_layouts, | |
| 3269 | 3277 | ); |
| 3270 | 3278 | |
| 3271 | 3279 | let result_name = result.as_deref().unwrap_or(name.as_str()).to_lowercase(); |
@@ -5234,7 +5242,13 @@ fn alloc_decls( | ||
| 5234 | 5242 | .map(|value| b.const_i64(value)) |
| 5235 | 5243 | .unwrap_or_else(|| { |
| 5236 | 5244 | 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 | + ); | |
| 5238 | 5252 | widen_to_i64(b, raw) |
| 5239 | 5253 | } else { |
| 5240 | 5254 | b.const_i64(1) |
@@ -5244,7 +5258,13 @@ fn alloc_decls( | ||
| 5244 | 5258 | eval_const_array_bound(upper, ¶m_consts, Some(st)) |
| 5245 | 5259 | .map(|value| b.const_i64(value)) |
| 5246 | 5260 | .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 | + ); | |
| 5248 | 5268 | widen_to_i64(b, raw) |
| 5249 | 5269 | }); |
| 5250 | 5270 | (lo64, up64) |
@@ -5454,7 +5474,13 @@ fn alloc_decls( | ||
| 5454 | 5474 | // runtime expression such as LEN(input). Materialize a |
| 5455 | 5475 | // heap buffer now and remember the runtime length for |
| 5456 | 5476 | // 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 | + ); | |
| 5458 | 5484 | let len_val = clamp_nonnegative_i64(b, raw_len); |
| 5459 | 5485 | let len_addr = b.alloca(IrType::Int(IntWidth::I64)); |
| 5460 | 5486 | b.store(len_val, len_addr); |
@@ -7863,10 +7889,14 @@ fn lower_intrinsic(b: &mut FuncBuilder, name: &str, args: &[ValueId]) -> Option< | ||
| 7863 | 7889 | // ishft(a, shift): positive shift = left, negative = right. |
| 7864 | 7890 | // For now, only handle positive (left shift). Full impl needs Select. |
| 7865 | 7891 | 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); | |
| 7867 | 7894 | let is_left = b.icmp(CmpOp::Ge, args[1], zero); |
| 7868 | 7895 | 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); | |
| 7870 | 7900 | let right = b.lshr(args[0], neg_shift); |
| 7871 | 7901 | Some(b.select(is_left, left, right)) |
| 7872 | 7902 | } else { |
@@ -7876,10 +7906,12 @@ fn lower_intrinsic(b: &mut FuncBuilder, name: &str, args: &[ValueId]) -> Option< | ||
| 7876 | 7906 | "btest" => { |
| 7877 | 7907 | // btest(a, pos) = (a >> pos) & 1 /= 0 |
| 7878 | 7908 | 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); | |
| 7881 | 7913 | 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); | |
| 7883 | 7915 | Some(b.icmp(CmpOp::Ne, masked, zero)) |
| 7884 | 7916 | } else { |
| 7885 | 7917 | None |
@@ -7888,8 +7920,10 @@ fn lower_intrinsic(b: &mut FuncBuilder, name: &str, args: &[ValueId]) -> Option< | ||
| 7888 | 7920 | "ibset" => { |
| 7889 | 7921 | // ibset(a, pos) = a | (1 << pos) |
| 7890 | 7922 | 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); | |
| 7893 | 7927 | Some(b.bit_or(args[0], mask)) |
| 7894 | 7928 | } else { |
| 7895 | 7929 | None |
@@ -7898,8 +7932,10 @@ fn lower_intrinsic(b: &mut FuncBuilder, name: &str, args: &[ValueId]) -> Option< | ||
| 7898 | 7932 | "ibclr" => { |
| 7899 | 7933 | // ibclr(a, pos) = a & ~(1 << pos) |
| 7900 | 7934 | 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); | |
| 7903 | 7939 | let inv = b.bit_not(mask); |
| 7904 | 7940 | Some(b.bit_and(args[0], inv)) |
| 7905 | 7941 | } else { |
@@ -7909,10 +7945,13 @@ fn lower_intrinsic(b: &mut FuncBuilder, name: &str, args: &[ValueId]) -> Option< | ||
| 7909 | 7945 | "ibits" => { |
| 7910 | 7946 | // ibits(i, pos, len) = (i >> pos) & ((1 << len) - 1) |
| 7911 | 7947 | 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); | |
| 7916 | 7955 | let mask = b.isub(mask_hi, one2); |
| 7917 | 7956 | Some(b.bit_and(shifted, mask)) |
| 7918 | 7957 | } else { |
@@ -8071,31 +8110,28 @@ fn lower_intrinsic(b: &mut FuncBuilder, name: &str, args: &[ValueId]) -> Option< | ||
| 8071 | 8110 | "ishftc" => { |
| 8072 | 8111 | // ishftc(a, shift, size): circular shift of the rightmost `size` bits. |
| 8073 | 8112 | 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, | |
| 8082 | 8118 | _ => 32, |
| 8083 | 8119 | }; |
| 8084 | 8120 | let size = if args.len() >= 3 { |
| 8085 | - args[2] | |
| 8121 | + coerce_int_like_to_width(b, args[2], value_width) | |
| 8086 | 8122 | } else { |
| 8087 | - b.const_i32(default_size) | |
| 8123 | + int_const_for_width(b, value_width, default_size) | |
| 8088 | 8124 | }; |
| 8089 | - let shift = args[1]; | |
| 8125 | + let shift = coerce_int_like_to_width(b, args[1], value_width); | |
| 8090 | 8126 | // left = (a << shift) | (a >> (size - shift)), masked to size bits. |
| 8091 | 8127 | let left = b.shl(args[0], shift); |
| 8092 | 8128 | let diff = b.isub(size, shift); |
| 8093 | 8129 | let right = b.lshr(args[0], diff); |
| 8094 | 8130 | let combined = b.bit_or(left, right); |
| 8095 | 8131 | // 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); | |
| 8097 | 8133 | 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); | |
| 8099 | 8135 | let mask = b.isub(shifted_one, one2); |
| 8100 | 8136 | Some(b.bit_and(combined, mask)) |
| 8101 | 8137 | } else { |
@@ -8665,17 +8701,23 @@ fn reorder_args_by_keyword_slots( | ||
| 8665 | 8701 | args: &[crate::ast::expr::Argument], |
| 8666 | 8702 | callee_key: &str, |
| 8667 | 8703 | 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, | |
| 8668 | 8713 | ) -> Vec<Option<crate::ast::expr::Argument>> { |
| 8669 | 8714 | // 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 | - } | |
| 8673 | 8715 | // Look up the callee's declared param order. NamedInterface |
| 8674 | 8716 | // symbols live in scope.symbols keyed by lowercase name and |
| 8675 | 8717 | // carry the specifics in arg_names; we want the actual |
| 8676 | 8718 | // procedure's arg_order, which lives on its scope. |
| 8677 | 8719 | use crate::sema::symtab::ScopeKind; |
| 8678 | - let arg_order: Vec<String> = { | |
| 8720 | + let arg_order: Option<Vec<String>> = { | |
| 8679 | 8721 | let mut found: Option<Vec<String>> = None; |
| 8680 | 8722 | for scope in st.all_scopes() { |
| 8681 | 8723 | if let ScopeKind::Function(n) | ScopeKind::Subroutine(n) = &scope.kind { |
@@ -8686,20 +8728,23 @@ fn reorder_args_by_keyword_slots( | ||
| 8686 | 8728 | } |
| 8687 | 8729 | } |
| 8688 | 8730 | 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()), | |
| 8694 | 8734 | } |
| 8695 | 8735 | }; |
| 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 | + }; | |
| 8696 | 8741 | // Build slot list the size of the callee's declared params. |
| 8697 | 8742 | // Positional actuals fill slots 0..K. Keyword actuals look up |
| 8698 | 8743 | // their slot by name. Unused slots stay None (OPTIONAL args |
| 8699 | 8744 | // get their runtime null treatment in the subsequent per-call |
| 8700 | 8745 | // hidden-arg logic). |
| 8701 | 8746 | 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()); | |
| 8703 | 8748 | for a in args { |
| 8704 | 8749 | if let Some(kw) = &a.keyword { |
| 8705 | 8750 | let key = kw.to_lowercase(); |
@@ -8923,6 +8968,45 @@ fn symbol_link_name(st: &SymbolTable, sym: &crate::sema::symtab::Symbol) -> Stri | ||
| 8923 | 8968 | sym.name.clone() |
| 8924 | 8969 | } |
| 8925 | 8970 | |
| 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 | + | |
| 8926 | 9010 | fn find_linkable_symbol_any_scope<'a>( |
| 8927 | 9011 | st: &'a SymbolTable, |
| 8928 | 9012 | key: &str, |
@@ -9272,6 +9356,211 @@ fn emit_named_function_call( | ||
| 9272 | 9356 | b.call(func_ref, call_args, ret_ty) |
| 9273 | 9357 | } |
| 9274 | 9358 | |
| 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 | + | |
| 9275 | 9564 | fn lower_alloc_return_call_into_desc( |
| 9276 | 9565 | b: &mut FuncBuilder, |
| 9277 | 9566 | ctx: &LowerCtx, |
@@ -9558,6 +9847,20 @@ fn ir_types_dispatch_equal(decl: &IrType, actual: &IrType) -> bool { | ||
| 9558 | 9847 | } |
| 9559 | 9848 | } |
| 9560 | 9849 | |
| 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 | + | |
| 9561 | 9864 | /// Resolve a kind suffix (literal integer or named constant) to a kind width. |
| 9562 | 9865 | /// Returns 4 as the default when unresolvable. |
| 9563 | 9866 | fn real_kind_to_width(kind_str: &str, st: &SymbolTable) -> u8 { |
@@ -10251,6 +10554,7 @@ fn install_runtime_dim_bounds( | ||
| 10251 | 10554 | decls: &[crate::ast::decl::SpannedDecl], |
| 10252 | 10555 | visible_param_consts: &HashMap<String, ConstScalar>, |
| 10253 | 10556 | st: &SymbolTable, |
| 10557 | + type_layouts: &crate::sema::type_layout::TypeLayoutRegistry, | |
| 10254 | 10558 | ) { |
| 10255 | 10559 | use crate::ast::decl::{ArraySpec, Attribute}; |
| 10256 | 10560 | for decl in decls { |
@@ -10318,7 +10622,8 @@ fn install_runtime_dim_bounds( | ||
| 10318 | 10622 | runtime.push(None); |
| 10319 | 10623 | continue; |
| 10320 | 10624 | } |
| 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)); | |
| 10322 | 10627 | let as_i64 = match b.func().value_type(val) { |
| 10323 | 10628 | Some(IrType::Int(IntWidth::I64)) => val, |
| 10324 | 10629 | Some(IrType::Int(_)) => b.int_extend(val, IntWidth::I64, true), |
@@ -10351,7 +10656,7 @@ fn arg_derived_type_name( | ||
| 10351 | 10656 | { |
| 10352 | 10657 | for entity in entities { |
| 10353 | 10658 | 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 { | |
| 10355 | 10660 | return Some(name.clone()); |
| 10356 | 10661 | } |
| 10357 | 10662 | } |
@@ -11895,6 +12200,75 @@ fn lower_string_expr_full( | ||
| 11895 | 12200 | } |
| 11896 | 12201 | if let Expr::ComponentAccess { .. } = &callee.node { |
| 11897 | 12202 | 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 | + } | |
| 11898 | 12272 | if args.len() == 1 { |
| 11899 | 12273 | if let crate::ast::expr::SectionSubscript::Element(_) = &args[0].value { |
| 11900 | 12274 | 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) { | ||
| 12834 | 13208 | } |
| 12835 | 13209 | } |
| 12836 | 13210 | } |
| 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 | + } | |
| 12837 | 13354 | } else if let Expr::ComponentAccess { .. } = &callee.node { |
| 12838 | 13355 | if let Some((dest_ptr, dest_len)) = |
| 12839 | 13356 | fixed_component_char_array_elem_ptr_and_len( |
@@ -13187,19 +13704,162 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) { | ||
| 13187 | 13704 | if let Some(layout) = ctx.type_layouts.get(&type_name) { |
| 13188 | 13705 | if let Some(bp) = layout.bound_proc(component) { |
| 13189 | 13706 | 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); | |
| 13190 | 13711 | 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 | + }); | |
| 13191 | 13744 | |
| 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); | |
| 13196 | 13854 | } |
| 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 | + } | |
| 13200 | 13860 | } |
| 13201 | 13861 | } |
| 13202 | - b.call(FuncRef::External(target), call_args, IrType::Void); | |
| 13862 | + b.call(FuncRef::External(call_name), call_args, IrType::Void); | |
| 13203 | 13863 | } |
| 13204 | 13864 | } |
| 13205 | 13865 | } |
@@ -13926,8 +14586,13 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) { | ||
| 13926 | 14586 | } => { |
| 13927 | 14587 | let stat_addr = allocate_status_target_addr(b, ctx, opts); |
| 13928 | 14588 | 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 | + ); | |
| 13931 | 14596 | let source_desc = allocate_descriptor_keyword_expr(b, ctx, opts, "source"); |
| 13932 | 14597 | let mold_desc = allocate_descriptor_keyword_expr(b, ctx, opts, "mold"); |
| 13933 | 14598 | let shape_desc = source_desc.or(mold_desc); |
@@ -16744,6 +17409,29 @@ fn unify_int_widths(b: &mut FuncBuilder, lhs: ValueId, rhs: ValueId) -> (ValueId | ||
| 16744 | 17409 | (l, r) |
| 16745 | 17410 | } |
| 16746 | 17411 | |
| 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 | + | |
| 16747 | 17435 | /// Widen an i32 (or smaller) index value to i64 for pointer |
| 16748 | 17436 | /// arithmetic. Pass through for values already i64 or larger. |
| 16749 | 17437 | fn widen_idx_to_i64(b: &mut FuncBuilder, idx: ValueId) -> ValueId { |
@@ -20033,7 +20721,7 @@ fn lower_array_expr_descriptor( | ||
| 20033 | 20721 | |
| 20034 | 20722 | let first_ft = crate::sema::types::expr_type(first, st); |
| 20035 | 20723 | 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)); | |
| 20037 | 20725 | let n = expr_values.len() as i64; |
| 20038 | 20726 | let arr_ty = IrType::Array(Box::new(elem_ty.clone()), n.max(1) as u64); |
| 20039 | 20727 | let buf = b.alloca(arr_ty); |
@@ -22533,12 +23221,13 @@ fn typed_allocate_char_len( | ||
| 22533 | 23221 | locals: &HashMap<String, LocalInfo>, |
| 22534 | 23222 | type_spec: Option<&TypeSpec>, |
| 22535 | 23223 | st: &SymbolTable, |
| 23224 | + type_layouts: Option<&crate::sema::type_layout::TypeLayoutRegistry>, | |
| 22536 | 23225 | ) -> Option<ValueId> { |
| 22537 | 23226 | match type_spec { |
| 22538 | 23227 | Some(TypeSpec::Character(None)) => Some(b.const_i64(1)), |
| 22539 | 23228 | Some(TypeSpec::Character(Some(sel))) => match &sel.len { |
| 22540 | 23229 | 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); | |
| 22542 | 23231 | Some(clamp_nonnegative_i64(b, raw_len)) |
| 22543 | 23232 | } |
| 22544 | 23233 | None => Some(b.const_i64(1)), |
@@ -22884,6 +23573,11 @@ fn resolve_component_base_for_method( | ||
| 22884 | 23573 | return Some((elem_addr, type_name)); |
| 22885 | 23574 | } |
| 22886 | 23575 | 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 | + } | |
| 22887 | 23581 | let (field_ptr, field) = resolve_component_field_access(b, locals, callee, st, tl)?; |
| 22888 | 23582 | let crate::sema::symtab::TypeInfo::Derived(type_name) = &field.type_info else { |
| 22889 | 23583 | return None; |
@@ -23440,6 +24134,20 @@ fn lower_expr( | ||
| 23440 | 24134 | lower_expr_full(b, locals, expr, st, None, None, None, None) |
| 23441 | 24135 | } |
| 23442 | 24136 | |
| 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 | + | |
| 23443 | 24151 | fn lower_expr_ctx( |
| 23444 | 24152 | b: &mut FuncBuilder, |
| 23445 | 24153 | ctx: &LowerCtx, |
@@ -23615,13 +24323,17 @@ fn lower_expr_full( | ||
| 23615 | 24323 | ) -> ValueId { |
| 23616 | 24324 | match &expr.node { |
| 23617 | 24325 | 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)) | |
| 23621 | 24333 | } 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) | |
| 23625 | 24337 | } else { |
| 23626 | 24338 | b.const_i32(val as i32) |
| 23627 | 24339 | } |
@@ -23703,6 +24415,20 @@ fn lower_expr_full( | ||
| 23703 | 24415 | // what's stored in the pointer slot. |
| 23704 | 24416 | b.load_typed(info.addr, IrType::Ptr(Box::new(IrType::Int(IntWidth::I8)))) |
| 23705 | 24417 | } 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 | |
| 23706 | 24432 | // Derived type variable: storage is `alloca [i8 x size]`. |
| 23707 | 24433 | // Consumers of the value treat it as a pointer to the |
| 23708 | 24434 | // struct (memcpy for whole-struct assignment, GEP for |
@@ -23711,12 +24437,14 @@ fn lower_expr_full( | ||
| 23711 | 24437 | // of the struct as if they were a pointer, turning |
| 23712 | 24438 | // `b = a` into a memcpy from the garbage address held |
| 23713 | 24439 | // 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 | + } | |
| 23720 | 24448 | } |
| 23721 | 24449 | } else if is_complex_ty(&info.ty) { |
| 23722 | 24450 | if info.by_ref { |
@@ -24694,7 +25422,7 @@ fn lower_expr_full( | ||
| 24694 | 25422 | .unwrap_or_else(|| FuncRef::External(call_name)) |
| 24695 | 25423 | }; |
| 24696 | 25424 | 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 { | |
| 24698 | 25426 | if let Some(tl) = type_layouts { |
| 24699 | 25427 | if let Some(info) = component_intrinsic_local_info(b, locals, callee, st, tl) { |
| 24700 | 25428 | let has_range = args.iter().any(|a| { |
@@ -24705,6 +25433,228 @@ fn lower_expr_full( | ||
| 24705 | 25433 | } |
| 24706 | 25434 | return lower_array_element(b, locals, &info, args, st, type_layouts); |
| 24707 | 25435 | } |
| 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 | + } | |
| 24708 | 25658 | } |
| 24709 | 25659 | b.const_i32(0) |
| 24710 | 25660 | } else { |
@@ -24740,6 +25690,45 @@ fn lower_expr_full( | ||
| 24740 | 25690 | return Some((base_ptr, ret_type_name)); |
| 24741 | 25691 | } |
| 24742 | 25692 | } |
| 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 | + } | |
| 24743 | 25732 | } |
| 24744 | 25733 | None |
| 24745 | 25734 | }); |
@@ -24835,7 +25824,7 @@ fn lower_expr_full( | ||
| 24835 | 25824 | // than actually lower (and have to undo), |
| 24836 | 25825 | // approximate from the AST: integer |
| 24837 | 25826 | // literals → i32, real → f64, etc. |
| 24838 | - Some(infer_const_expr_ty(&e.node)) | |
| 25827 | + Some(infer_const_expr_ty(&e.node, st)) | |
| 24839 | 25828 | } |
| 24840 | 25829 | _ => None, |
| 24841 | 25830 | }) |
@@ -24908,16 +25897,14 @@ fn lower_expr_full( | ||
| 24908 | 25897 | /// lowering to pick an element type without actually emitting IR. |
| 24909 | 25898 | /// Conservative — falls back to i32 for anything it can't |
| 24910 | 25899 | /// classify. |
| 24911 | -fn infer_const_expr_ty(e: &Expr) -> IrType { | |
| 25900 | +fn infer_const_expr_ty(e: &Expr, st: &SymbolTable) -> IrType { | |
| 24912 | 25901 | match e { |
| 24913 | 25902 | 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) | |
| 24921 | 25908 | } |
| 24922 | 25909 | Expr::RealLiteral { text, .. } => { |
| 24923 | 25910 | if text.to_lowercase().contains('d') { |
@@ -24927,8 +25914,8 @@ fn infer_const_expr_ty(e: &Expr) -> IrType { | ||
| 24927 | 25914 | } |
| 24928 | 25915 | } |
| 24929 | 25916 | 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), | |
| 24932 | 25919 | _ => IrType::Int(IntWidth::I32), |
| 24933 | 25920 | } |
| 24934 | 25921 | } |
src/sema/amod.rsmodified@@ -118,12 +118,33 @@ pub fn write_amod( | ||
| 118 | 118 | .filter(|(_, sym)| matches!(sym.kind, SymbolKind::NamedInterface)) |
| 119 | 119 | .flat_map(|(_, sym)| sym.arg_names.iter().cloned()) |
| 120 | 120 | .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 | + } | |
| 121 | 142 | let mut procs: Vec<_> = scope |
| 122 | 143 | .symbols |
| 123 | 144 | .iter() |
| 124 | 145 | .filter(|(name, sym)| { |
| 125 | 146 | 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()) | |
| 127 | 148 | }) |
| 128 | 149 | .collect(); |
| 129 | 150 | procs.sort_by_key(|(k, _)| k.to_lowercase()); |
@@ -1210,9 +1231,11 @@ fn parse_type( | ||
| 1210 | 1231 | let m = clean.trim().to_string(); |
| 1211 | 1232 | (m.clone(), m) |
| 1212 | 1233 | }; |
| 1234 | + let abi_name = method.to_lowercase(); | |
| 1213 | 1235 | bound_procs.push(BoundProc { |
| 1214 | 1236 | method_name: method, |
| 1215 | 1237 | target_name: target, |
| 1238 | + abi_name, | |
| 1216 | 1239 | nopass, |
| 1217 | 1240 | }); |
| 1218 | 1241 | } else if let Some(rest) = trimmed.strip_prefix("@final ") { |
src/sema/resolve.rsmodified@@ -1095,6 +1095,11 @@ fn collect_derived_type_layouts( | ||
| 1095 | 1095 | visible_param_cache, |
| 1096 | 1096 | exported_param_cache, |
| 1097 | 1097 | )); |
| 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 | + }; | |
| 1098 | 1103 | let const_params = collect_const_int_params(decls, &seed_params); |
| 1099 | 1104 | for decl in decls { |
| 1100 | 1105 | if let Decl::DerivedTypeDef { |
@@ -1109,6 +1114,7 @@ fn collect_derived_type_layouts( | ||
| 1109 | 1114 | let parent = extends.as_ref().and_then(|p| layouts.get(p)).cloned(); |
| 1110 | 1115 | let layout = super::type_layout::compute_layout( |
| 1111 | 1116 | name, |
| 1117 | + host_module, | |
| 1112 | 1118 | type_bound_procs, |
| 1113 | 1119 | final_procs, |
| 1114 | 1120 | components, |
src/sema/type_layout.rsmodified@@ -37,6 +37,7 @@ pub struct FieldLayout { | ||
| 37 | 37 | pub struct BoundProc { |
| 38 | 38 | pub method_name: String, |
| 39 | 39 | pub target_name: String, |
| 40 | + pub abi_name: String, | |
| 40 | 41 | pub nopass: bool, |
| 41 | 42 | } |
| 42 | 43 | |
@@ -333,6 +334,7 @@ fn type_spec_to_type_info( | ||
| 333 | 334 | /// Compute the layout of a derived type from its component declarations. |
| 334 | 335 | pub fn compute_layout( |
| 335 | 336 | type_name: &str, |
| 337 | + host_module: Option<&str>, | |
| 336 | 338 | type_bound_procs: &[crate::ast::decl::TypeBoundProc], |
| 337 | 339 | final_proc_names: &[String], |
| 338 | 340 | components: &[crate::ast::decl::SpannedDecl], |
@@ -461,9 +463,19 @@ pub fn compute_layout( | ||
| 461 | 463 | .map(|tbp| { |
| 462 | 464 | let target = tbp.binding.as_deref().unwrap_or(&tbp.name); |
| 463 | 465 | 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 | + }; | |
| 464 | 475 | BoundProc { |
| 465 | 476 | method_name: tbp.name.clone(), |
| 466 | - target_name: target.to_string(), | |
| 477 | + target_name, | |
| 478 | + abi_name: target.to_lowercase(), | |
| 467 | 479 | nopass, |
| 468 | 480 | } |
| 469 | 481 | }) |
@@ -722,7 +734,16 @@ mod tests { | ||
| 722 | 734 | make_component("x", crate::ast::decl::TypeSpec::Integer(None)), |
| 723 | 735 | make_component("y", crate::ast::decl::TypeSpec::Real(None)), |
| 724 | 736 | ]; |
| 725 | - let layout = compute_layout("pair", &[], &[], &components, None, ®, &empty_params()); | |
| 737 | + let layout = compute_layout( | |
| 738 | + "pair", | |
| 739 | + None, | |
| 740 | + &[], | |
| 741 | + &[], | |
| 742 | + &components, | |
| 743 | + None, | |
| 744 | + ®, | |
| 745 | + &empty_params(), | |
| 746 | + ); | |
| 726 | 747 | assert_eq!(layout.name, "pair"); |
| 727 | 748 | assert_eq!(layout.size, 8); // 4 + 4, no padding needed |
| 728 | 749 | assert_eq!(layout.align, 4); |
@@ -755,7 +776,16 @@ mod tests { | ||
| 755 | 776 | ), |
| 756 | 777 | make_component("b", crate::ast::decl::TypeSpec::DoublePrecision), |
| 757 | 778 | ]; |
| 758 | - let layout = compute_layout("padded", &[], &[], &components, None, ®, &empty_params()); | |
| 779 | + let layout = compute_layout( | |
| 780 | + "padded", | |
| 781 | + None, | |
| 782 | + &[], | |
| 783 | + &[], | |
| 784 | + &components, | |
| 785 | + None, | |
| 786 | + ®, | |
| 787 | + &empty_params(), | |
| 788 | + ); | |
| 759 | 789 | assert_eq!(layout.field("a").unwrap().offset, 0); |
| 760 | 790 | assert_eq!(layout.field("a").unwrap().size, 1); |
| 761 | 791 | assert_eq!(layout.field("b").unwrap().offset, 8); // padded to 8-byte alignment |
@@ -773,13 +803,22 @@ mod tests { | ||
| 773 | 803 | "x", |
| 774 | 804 | crate::ast::decl::TypeSpec::Integer(None), |
| 775 | 805 | )]; |
| 776 | - let base_layout = | |
| 777 | - compute_layout("base", &[], &[], &base_comps, None, ®, &empty_params()); | |
| 806 | + let base_layout = compute_layout( | |
| 807 | + "base", | |
| 808 | + None, | |
| 809 | + &[], | |
| 810 | + &[], | |
| 811 | + &base_comps, | |
| 812 | + None, | |
| 813 | + ®, | |
| 814 | + &empty_params(), | |
| 815 | + ); | |
| 778 | 816 | assert_eq!(base_layout.size, 4); |
| 779 | 817 | |
| 780 | 818 | let child_comps = vec![make_component("y", crate::ast::decl::TypeSpec::Real(None))]; |
| 781 | 819 | let child_layout = compute_layout( |
| 782 | 820 | "child", |
| 821 | + None, | |
| 783 | 822 | &[], |
| 784 | 823 | &[], |
| 785 | 824 | &child_comps, |
@@ -812,7 +851,16 @@ mod tests { | ||
| 812 | 851 | vec![crate::ast::decl::Attribute::Pointer], |
| 813 | 852 | )]; |
| 814 | 853 | |
| 815 | - let layout = compute_layout("list_t", &[], &[], &components, None, ®, &empty_params()); | |
| 854 | + let layout = compute_layout( | |
| 855 | + "list_t", | |
| 856 | + None, | |
| 857 | + &[], | |
| 858 | + &[], | |
| 859 | + &components, | |
| 860 | + None, | |
| 861 | + ®, | |
| 862 | + &empty_params(), | |
| 863 | + ); | |
| 816 | 864 | let field = layout.field("left").expect("missing left field"); |
| 817 | 865 | |
| 818 | 866 | assert_eq!(field.size, 8); |
@@ -866,6 +914,7 @@ mod tests { | ||
| 866 | 914 | |
| 867 | 915 | let layout = compute_layout( |
| 868 | 916 | "state_t", |
| 917 | + None, | |
| 869 | 918 | &[], |
| 870 | 919 | &[], |
| 871 | 920 | &components, |
@@ -922,7 +971,7 @@ mod tests { | ||
| 922 | 971 | let mut params = std::collections::HashMap::new(); |
| 923 | 972 | params.insert("max_token_len".into(), 8); |
| 924 | 973 | |
| 925 | - let layout = compute_layout("token_t", &[], &[], &components, None, ®, ¶ms); | |
| 974 | + let layout = compute_layout("token_t", None, &[], &[], &components, None, ®, ¶ms); | |
| 926 | 975 | let field = layout.field("value").expect("missing value field"); |
| 927 | 976 | |
| 928 | 977 | assert_eq!(field.size, 8); |
tests/allocate_constructor_runtime.rsmodified@@ -77,7 +77,10 @@ fn allocate_source_array_constructor_infers_shape_and_copies_values() { | ||
| 77 | 77 | ); |
| 78 | 78 | let stdout = String::from_utf8_lossy(&run.stdout); |
| 79 | 79 | 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"), | |
| 81 | 84 | "unexpected source array constructor output: {}", |
| 82 | 85 | stdout |
| 83 | 86 | ); |
tests/allocate_validation.rsmodified@@ -26,7 +26,10 @@ fn compiler(name: &str) -> PathBuf { | ||
| 26 | 26 | fn unique_path(stem: &str, ext: &str) -> PathBuf { |
| 27 | 27 | let pid = std::process::id(); |
| 28 | 28 | 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 | + )) | |
| 30 | 33 | } |
| 31 | 34 | |
| 32 | 35 | fn unique_dir(stem: &str) -> PathBuf { |
tests/cli_driver.rsmodified@@ -1731,6 +1731,150 @@ fn bind_c_c_char_value_arg_passes_actual_byte_after_value_handle() { | ||
| 1731 | 1731 | let _ = std::fs::remove_dir_all(&dir); |
| 1732 | 1732 | } |
| 1733 | 1733 | |
| 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 | + | |
| 1734 | 1878 | #[test] |
| 1735 | 1879 | fn bind_c_c_char_function_result_round_trips_through_wrapper_module() { |
| 1736 | 1880 | let dir = unique_dir("bind_c_c_char_result"); |
@@ -2261,6 +2405,40 @@ fn allocate_bounds_size_intrinsic_lowers_without_raw_symbol() { | ||
| 2261 | 2405 | let _ = std::fs::remove_file(&src); |
| 2262 | 2406 | } |
| 2263 | 2407 | |
| 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 | + | |
| 2264 | 2442 | #[test] |
| 2265 | 2443 | fn fixed_component_array_element_assignment_compiles() { |
| 2266 | 2444 | let src = write_program( |
@@ -2761,6 +2939,84 @@ fn deferred_char_component_allocate_source_copies_runtime_string_value() { | ||
| 2761 | 2939 | let _ = std::fs::remove_file(&src); |
| 2762 | 2940 | } |
| 2763 | 2941 | |
| 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 | + | |
| 2764 | 3020 | #[test] |
| 2765 | 3021 | fn scalar_char_substring_argument_avoids_raw_local_symbol() { |
| 2766 | 3022 | let src = write_program( |
@@ -4060,114 +4316,482 @@ fn prebuilt_archive_input_links_after_objects() { | ||
| 4060 | 4316 | main_obj.to_str().unwrap(), |
| 4061 | 4317 | ]) |
| 4062 | 4318 | .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"); | |
| 4064 | 4727 | 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) | |
| 4068 | 4731 | ); |
| 4069 | 4732 | |
| 4070 | - let exe = dir.join("linked_archive"); | |
| 4733 | + let out = dir.join("p.bin"); | |
| 4071 | 4734 | let link = Command::new(compiler("armfortas")) |
| 4072 | - .current_dir(&dir) | |
| 4073 | 4735 | .args([ |
| 4074 | 4736 | main_obj.to_str().unwrap(), |
| 4075 | - archive.to_str().unwrap(), | |
| 4737 | + mod_obj.to_str().unwrap(), | |
| 4076 | 4738 | "-o", |
| 4077 | - exe.to_str().unwrap(), | |
| 4739 | + out.to_str().unwrap(), | |
| 4078 | 4740 | ]) |
| 4079 | 4741 | .output() |
| 4080 | - .expect("archive link spawn failed"); | |
| 4742 | + .expect("spawn failed"); | |
| 4081 | 4743 | assert!( |
| 4082 | 4744 | link.status.success(), |
| 4083 | - "prebuilt archive link failed: {}", | |
| 4745 | + "link should succeed: {}", | |
| 4084 | 4746 | String::from_utf8_lossy(&link.stderr) |
| 4085 | 4747 | ); |
| 4086 | - assert!( | |
| 4087 | - exe.exists(), | |
| 4088 | - "prebuilt archive link should write the binary" | |
| 4089 | - ); | |
| 4090 | - | |
| 4091 | - let _ = std::fs::remove_dir_all(&dir); | |
| 4092 | -} | |
| 4093 | 4748 | |
| 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"); | |
| 4102 | 4750 | 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) | |
| 4106 | 4755 | ); |
| 4107 | - let asm = std::fs::read_to_string(&out).expect("missing asm output"); | |
| 4756 | + let stdout = String::from_utf8_lossy(&run.stdout); | |
| 4108 | 4757 | 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 | |
| 4111 | 4761 | ); |
| 4112 | - let _ = std::fs::remove_file(&out); | |
| 4113 | - let _ = std::fs::remove_file(&src); | |
| 4114 | 4762 | } |
| 4115 | 4763 | |
| 4116 | 4764 | #[test] |
| 4117 | -fn dash_capital_e_preprocesses_only() { | |
| 4765 | +fn type_bound_call_on_allocatable_component_array_element_mutates_real_receiver() { | |
| 4118 | 4766 | 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", | |
| 4121 | 4769 | ); |
| 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()]) | |
| 4125 | 4773 | .output() |
| 4126 | 4774 | .expect("spawn failed"); |
| 4127 | 4775 | 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) | |
| 4149 | 4779 | ); |
| 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"); | |
| 4155 | 4781 | 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) | |
| 4159 | 4786 | ); |
| 4160 | - let stdout = String::from_utf8_lossy(&result.stdout); | |
| 4787 | + let stdout = String::from_utf8_lossy(&run.stdout); | |
| 4161 | 4788 | 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: {}", | |
| 4164 | 4791 | stdout |
| 4165 | 4792 | ); |
| 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); | |
| 4171 | 4795 | } |
| 4172 | 4796 | |
| 4173 | 4797 | #[test] |
@@ -8060,6 +8684,176 @@ fn public_defined_assignment_in_private_module_round_trips_through_amod_and_runs | ||
| 8060 | 8684 | let _ = std::fs::remove_dir_all(&dir); |
| 8061 | 8685 | } |
| 8062 | 8686 | |
| 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 | + | |
| 8063 | 8857 | #[test] |
| 8064 | 8858 | fn enum_bind_c_enumerators_compile_and_run() { |
| 8065 | 8859 | let src = write_program( |
@@ -9486,6 +10280,41 @@ fn fixed_allocatable_character_substring_compiles_and_runs() { | ||
| 9486 | 10280 | let _ = std::fs::remove_file(&src); |
| 9487 | 10281 | } |
| 9488 | 10282 | |
| 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 | + | |
| 9489 | 10318 | #[test] |
| 9490 | 10319 | fn allocatable_scalar_substring_actual_preserves_hidden_len() { |
| 9491 | 10320 | let src = write_program( |
tests/memory_runtime.rsmodified@@ -224,7 +224,11 @@ fn allocate_source_array_infers_shape_and_copies_values() { | ||
| 224 | 224 | String::from_utf8_lossy(&run.stderr) |
| 225 | 225 | ); |
| 226 | 226 | 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 | + ); | |
| 228 | 232 | assert!( |
| 229 | 233 | stdout.contains("10") && stdout.contains("20") && stdout.contains("30"), |
| 230 | 234 | "expected copied values in output: {}", |
@@ -261,7 +265,11 @@ fn allocate_mold_array_infers_shape_without_source_copy() { | ||
| 261 | 265 | String::from_utf8_lossy(&run.stderr) |
| 262 | 266 | ); |
| 263 | 267 | 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 | + ); | |
| 265 | 273 | |
| 266 | 274 | let _ = std::fs::remove_dir_all(&dir); |
| 267 | 275 | } |
@@ -293,7 +301,11 @@ fn allocate_source_scalar_initializes_allocatable_scalar() { | ||
| 293 | 301 | String::from_utf8_lossy(&run.stderr) |
| 294 | 302 | ); |
| 295 | 303 | 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 | + ); | |
| 297 | 309 | |
| 298 | 310 | let _ = std::fs::remove_dir_all(&dir); |
| 299 | 311 | } |
@@ -325,7 +337,11 @@ fn allocate_component_source_array_infers_shape_and_copies_values() { | ||
| 325 | 337 | String::from_utf8_lossy(&run.stderr) |
| 326 | 338 | ); |
| 327 | 339 | 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 | + ); | |
| 329 | 345 | assert!( |
| 330 | 346 | stdout.contains("4") && stdout.contains("5"), |
| 331 | 347 | "expected copied component values in output: {}", |
tests/regalloc_runtime.rsmodified@@ -146,7 +146,14 @@ fn gp_values_live_across_call_pressure_survive() { | ||
| 146 | 146 | 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"; |
| 147 | 147 | |
| 148 | 148 | 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 | + ); | |
| 150 | 157 | } |
| 151 | 158 | } |
| 152 | 159 | |
@@ -156,7 +163,14 @@ fn fp_values_live_across_call_pressure_survive() { | ||
| 156 | 163 | 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"; |
| 157 | 164 | |
| 158 | 165 | 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 | + ); | |
| 160 | 174 | } |
| 161 | 175 | } |
| 162 | 176 | |