fortrangoingonforty/armfortas / f8aae86

Browse files

Fix fgof-temp file semantics

Authored by espadonne
SHA
f8aae86920dc51a9a51a183aa4e146ec5be43b04
Parents
cc571da
Tree
7402ad4

3 changed files

StatusFile+-
M runtime/src/io_system.rs 133 17
M src/ir/lower.rs 63 3
M tests/cli_driver.rs 122 0
runtime/src/io_system.rsmodified
@@ -97,6 +97,10 @@ struct Unit {
9797
 }
9898
 
9999
 impl Unit {
100
+    fn is_stream_unformatted(&self) -> bool {
101
+        self.form == Form::Unformatted && self.access == Access::Stream
102
+    }
103
+
100104
     fn write_bytes(&mut self, data: &[u8]) -> io::Result<()> {
101105
         match &mut self.stream {
102106
             UnitStream::Stdout => {
@@ -510,15 +514,43 @@ pub extern "C" fn afs_open(cb: *const OpenControlBlock) {
510514
 /// Close a unit.
511515
 #[no_mangle]
512516
 pub extern "C" fn afs_close(unit: i32, iostat: *mut i32) {
517
+    afs_close_ex(unit, std::ptr::null(), 0, iostat);
518
+}
519
+
520
+/// Close a unit with optional STATUS= semantics.
521
+#[no_mangle]
522
+pub extern "C" fn afs_close_ex(unit: i32, status: *const u8, status_len: i64, iostat: *mut i32) {
523
+    let delete_on_close = if status.is_null() || status_len <= 0 {
524
+        false
525
+    } else {
526
+        let raw = unsafe { std::slice::from_raw_parts(status, status_len as usize) };
527
+        std::str::from_utf8(raw)
528
+            .map(|s| s.trim().eq_ignore_ascii_case("delete"))
529
+            .unwrap_or(false)
530
+    };
531
+
513532
     let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner());
514533
     if let Some(mut u) = state.units.remove(&unit) {
515534
         let _ = u.flush();
516
-        // File is dropped here, closing the handle.
517
-        if !iostat.is_null() {
518
-            unsafe {
519
-                *iostat = 0;
535
+        let filename = u.filename.clone();
536
+        drop(u);
537
+
538
+        let mut close_status = 0;
539
+        if delete_on_close
540
+            && !matches!(filename.as_str(), "stdin" | "stdout" | "stderr")
541
+            && !filename.is_empty()
542
+        {
543
+            if let Err(e) = std::fs::remove_file(&filename) {
544
+                close_status = e.raw_os_error().unwrap_or(1);
520545
             }
521546
         }
547
+
548
+        if !iostat.is_null() {
549
+            unsafe { *iostat = close_status };
550
+        } else if close_status != 0 {
551
+            eprintln!("CLOSE: {}: {}", filename, io::Error::from_raw_os_error(close_status));
552
+            std::process::exit(1);
553
+        }
522554
     } else {
523555
         if !iostat.is_null() {
524556
             unsafe {
@@ -535,7 +567,11 @@ pub extern "C" fn afs_close(unit: i32, iostat: *mut i32) {
535567
 pub extern "C" fn afs_write_int(unit: i32, val: i32) {
536568
     let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner());
537569
     if let Some(u) = state.get_unit(unit) {
538
-        let _ = u.write_str(&format!(" {}", val));
570
+        if u.is_stream_unformatted() {
571
+            let _ = u.write_raw(&val.to_ne_bytes());
572
+        } else {
573
+            let _ = u.write_str(&format!(" {}", val));
574
+        }
539575
     }
540576
 }
541577
 
@@ -544,7 +580,11 @@ pub extern "C" fn afs_write_int(unit: i32, val: i32) {
544580
 pub extern "C" fn afs_write_int64(unit: i32, val: i64) {
545581
     let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner());
546582
     if let Some(u) = state.get_unit(unit) {
547
-        let _ = u.write_str(&format!(" {}", val));
583
+        if u.is_stream_unformatted() {
584
+            let _ = u.write_raw(&val.to_ne_bytes());
585
+        } else {
586
+            let _ = u.write_str(&format!(" {}", val));
587
+        }
548588
     }
549589
 }
550590
 
@@ -553,7 +593,11 @@ pub extern "C" fn afs_write_int64(unit: i32, val: i64) {
553593
 pub extern "C" fn afs_write_int128(unit: i32, val: i128) {
554594
     let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner());
555595
     if let Some(u) = state.get_unit(unit) {
556
-        let _ = u.write_str(&format!(" {}", val));
596
+        if u.is_stream_unformatted() {
597
+            let _ = u.write_raw(&val.to_ne_bytes());
598
+        } else {
599
+            let _ = u.write_str(&format!(" {}", val));
600
+        }
557601
     }
558602
 }
559603
 
@@ -562,7 +606,11 @@ pub extern "C" fn afs_write_int128(unit: i32, val: i128) {
562606
 pub extern "C" fn afs_write_real(unit: i32, val: f32) {
563607
     let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner());
564608
     if let Some(u) = state.get_unit(unit) {
565
-        let _ = u.write_str(&format!("  {:14.7E}", val));
609
+        if u.is_stream_unformatted() {
610
+            let _ = u.write_raw(&val.to_ne_bytes());
611
+        } else {
612
+            let _ = u.write_str(&format!("  {:14.7E}", val));
613
+        }
566614
     }
567615
 }
568616
 
@@ -571,7 +619,11 @@ pub extern "C" fn afs_write_real(unit: i32, val: f32) {
571619
 pub extern "C" fn afs_write_real64(unit: i32, val: f64) {
572620
     let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner());
573621
     if let Some(u) = state.get_unit(unit) {
574
-        let _ = u.write_str(&format!("  {:22.15E}", val));
622
+        if u.is_stream_unformatted() {
623
+            let _ = u.write_raw(&val.to_ne_bytes());
624
+        } else {
625
+            let _ = u.write_str(&format!("  {:22.15E}", val));
626
+        }
575627
     }
576628
 }
577629
 
@@ -582,7 +634,12 @@ pub extern "C" fn afs_write_complex_f32(unit: i32, ptr: *const f32) {
582634
     let (re, im) = unsafe { (*ptr, *ptr.add(1)) };
583635
     let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner());
584636
     if let Some(u) = state.get_unit(unit) {
585
-        let _ = u.write_str(&format!(" ({:14.7E},{:14.7E})", re, im));
637
+        if u.is_stream_unformatted() {
638
+            let _ = u.write_raw(&re.to_ne_bytes());
639
+            let _ = u.write_raw(&im.to_ne_bytes());
640
+        } else {
641
+            let _ = u.write_str(&format!(" ({:14.7E},{:14.7E})", re, im));
642
+        }
586643
     }
587644
 }
588645
 
@@ -593,7 +650,12 @@ pub extern "C" fn afs_write_complex_f64(unit: i32, ptr: *const f64) {
593650
     let (re, im) = unsafe { (*ptr, *ptr.add(1)) };
594651
     let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner());
595652
     if let Some(u) = state.get_unit(unit) {
596
-        let _ = u.write_str(&format!(" ({:22.15E},{:22.15E})", re, im));
653
+        if u.is_stream_unformatted() {
654
+            let _ = u.write_raw(&re.to_ne_bytes());
655
+            let _ = u.write_raw(&im.to_ne_bytes());
656
+        } else {
657
+            let _ = u.write_str(&format!(" ({:22.15E},{:22.15E})", re, im));
658
+        }
597659
     }
598660
 }
599661
 
@@ -602,10 +664,17 @@ pub extern "C" fn afs_write_complex_f64(unit: i32, ptr: *const f64) {
602664
 pub extern "C" fn afs_write_string(unit: i32, ptr: *const u8, len: i64) {
603665
     let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner());
604666
     if let Some(u) = state.get_unit(unit) {
605
-        let _ = u.write_str(" ");
606
-        if !ptr.is_null() && len > 0 {
607
-            let slice = unsafe { std::slice::from_raw_parts(ptr, len as usize) };
608
-            let _ = u.write_bytes(slice);
667
+        if u.is_stream_unformatted() {
668
+            if !ptr.is_null() && len > 0 {
669
+                let slice = unsafe { std::slice::from_raw_parts(ptr, len as usize) };
670
+                let _ = u.write_raw(slice);
671
+            }
672
+        } else {
673
+            let _ = u.write_str(" ");
674
+            if !ptr.is_null() && len > 0 {
675
+                let slice = unsafe { std::slice::from_raw_parts(ptr, len as usize) };
676
+                let _ = u.write_bytes(slice);
677
+            }
609678
         }
610679
     }
611680
 }
@@ -615,7 +684,11 @@ pub extern "C" fn afs_write_string(unit: i32, ptr: *const u8, len: i64) {
615684
 pub extern "C" fn afs_write_logical(unit: i32, val: i32) {
616685
     let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner());
617686
     if let Some(u) = state.get_unit(unit) {
618
-        let _ = u.write_str(if val != 0 { " T" } else { " F" });
687
+        if u.is_stream_unformatted() {
688
+            let _ = u.write_raw(&val.to_ne_bytes());
689
+        } else {
690
+            let _ = u.write_str(if val != 0 { " T" } else { " F" });
691
+        }
619692
     }
620693
 }
621694
 
@@ -624,6 +697,10 @@ pub extern "C" fn afs_write_logical(unit: i32, val: i32) {
624697
 pub extern "C" fn afs_write_newline(unit: i32) {
625698
     let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner());
626699
     if let Some(u) = state.get_unit(unit) {
700
+        if u.is_stream_unformatted() {
701
+            let _ = u.flush();
702
+            return;
703
+        }
627704
         let _ = u.write_str("\n");
628705
         let _ = u.flush();
629706
     }
@@ -943,7 +1020,12 @@ pub extern "C" fn afs_read_string(unit: i32, dest: *mut u8, dest_len: i64, iosta
9431020
 
9441021
     match u.next_read_token() {
9451022
         Ok(Some(token)) => {
946
-            crate::string::afs_assign_char_fixed(dest, dest_len, token.as_ptr(), token.len() as i64);
1023
+            crate::string::afs_assign_char_fixed(
1024
+                dest,
1025
+                dest_len,
1026
+                token.as_ptr(),
1027
+                token.len() as i64,
1028
+            );
9471029
             if !iostat.is_null() {
9481030
                 unsafe {
9491031
                     *iostat = 0;
@@ -3268,6 +3350,40 @@ mod tests {
32683350
         afs_write_newline(6);
32693351
     }
32703352
 
3353
+    #[test]
3354
+    fn stream_unformatted_string_write_preserves_exact_bytes() {
3355
+        let path = "/tmp/afs_stream_unformatted_string_write.dat";
3356
+        let mut iostat = -99i32;
3357
+        let cb = OpenControlBlock {
3358
+            unit: 94,
3359
+            filename: path.as_ptr(),
3360
+            filename_len: path.len() as i64,
3361
+            status: "replace".as_ptr(),
3362
+            status_len: 7,
3363
+            action: "write".as_ptr(),
3364
+            action_len: 5,
3365
+            access: "stream".as_ptr(),
3366
+            access_len: 6,
3367
+            form: "unformatted".as_ptr(),
3368
+            form_len: 11,
3369
+            recl: 0,
3370
+            iostat: &mut iostat,
3371
+            newunit: std::ptr::null_mut(),
3372
+            position: std::ptr::null(),
3373
+            position_len: 0,
3374
+        };
3375
+
3376
+        afs_open(&cb);
3377
+        assert_eq!(iostat, 0, "expected stream-unformatted OPEN to succeed");
3378
+
3379
+        afs_write_string(94, "alpha".as_ptr(), 5);
3380
+        afs_write_newline(94);
3381
+        afs_close(94, std::ptr::null_mut());
3382
+
3383
+        let content = std::fs::read(path).unwrap();
3384
+        assert_eq!(content, b"alpha", "expected exact stream bytes");
3385
+    }
3386
+
32713387
     #[test]
32723388
     fn write_i128_to_file() {
32733389
         let path = "/tmp/afs_write_i128_test.dat";
src/ir/lower.rsmodified
@@ -16228,15 +16228,51 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) {
1622816228
         }
1622916229
 
1623016230
         Stmt::Close { specs } => {
16231
-            let unit = if let Some(s) = specs.first() {
16231
+            let unit_spec = specs
16232
+                .iter()
16233
+                .find(|s| {
16234
+                    s.keyword
16235
+                        .as_deref()
16236
+                        .map(|k| k.eq_ignore_ascii_case("unit"))
16237
+                        .unwrap_or(false)
16238
+                })
16239
+                .or_else(|| specs.iter().find(|s| s.keyword.is_none()));
16240
+            let iostat_spec = specs.iter().find(|s| {
16241
+                s.keyword
16242
+                    .as_deref()
16243
+                    .map(|k| k.eq_ignore_ascii_case("iostat"))
16244
+                    .unwrap_or(false)
16245
+            });
16246
+            let status_spec = specs.iter().find(|s| {
16247
+                s.keyword
16248
+                    .as_deref()
16249
+                    .map(|k| k.eq_ignore_ascii_case("status"))
16250
+                    .unwrap_or(false)
16251
+            });
16252
+            let unit = if let Some(s) = unit_spec {
1623216253
                 lower_expr_ctx(b, ctx, &s.value)
1623316254
             } else {
1623416255
                 b.const_i32(6)
1623516256
             };
1623616257
             let null = b.const_i64(0);
16258
+            let unit_i32 = coerce_to_type(b, unit, &IrType::Int(IntWidth::I32));
16259
+            let iostat_ptr = iostat_spec
16260
+                .map(|spec| lower_arg_by_ref_ctx(b, ctx, &spec.value))
16261
+                .unwrap_or(null);
16262
+            let (status_ptr, status_len) = status_spec
16263
+                .map(|spec| {
16264
+                    lower_string_expr_with_layouts(
16265
+                        b,
16266
+                        &ctx.locals,
16267
+                        &spec.value,
16268
+                        ctx.st,
16269
+                        Some(ctx.type_layouts),
16270
+                    )
16271
+                })
16272
+                .unwrap_or_else(|| (null, null));
1623716273
             b.call(
16238
-                FuncRef::External("afs_close".into()),
16239
-                vec![unit, null],
16274
+                FuncRef::External("afs_close_ex".into()),
16275
+                vec![unit_i32, status_ptr, status_len, iostat_ptr],
1624016276
                 IrType::Void,
1624116277
             );
1624216278
         }
@@ -24015,12 +24051,36 @@ fn emit_derived_value_copy(
2401524051
         let src_field = b.gep(src_ptr, vec![offset], IrType::Int(IntWidth::I8));
2401624052
 
2401724053
         if field.allocatable && is_deferred_char_component_field(field) {
24054
+            let src_alloc = b.call(
24055
+                FuncRef::External("afs_string_allocated".into()),
24056
+                vec![src_field],
24057
+                IrType::Int(IntWidth::I32),
24058
+            );
24059
+            let zero_i32 = b.const_i32(0);
24060
+            let is_unallocated = b.icmp(CmpOp::Eq, src_alloc, zero_i32);
24061
+            let unalloc_bb = b.create_block("derived_char_unalloc");
24062
+            let copy_bb = b.create_block("derived_char_copy");
24063
+            let join_bb = b.create_block("derived_char_copy_join");
24064
+            b.cond_branch(is_unallocated, unalloc_bb, vec![], copy_bb, vec![]);
24065
+
24066
+            b.set_block(unalloc_bb);
24067
+            b.call(
24068
+                FuncRef::External("afs_dealloc_string".into()),
24069
+                vec![dest_field],
24070
+                IrType::Void,
24071
+            );
24072
+            b.branch(join_bb, vec![]);
24073
+
24074
+            b.set_block(copy_bb);
2401824075
             let (src_data, src_len) = load_string_descriptor_view(b, src_field);
2401924076
             b.call(
2402024077
                 FuncRef::External("afs_assign_char_deferred".into()),
2402124078
                 vec![dest_field, src_data, src_len],
2402224079
                 IrType::Void,
2402324080
             );
24081
+            b.branch(join_bb, vec![]);
24082
+
24083
+            b.set_block(join_bb);
2402424084
             continue;
2402524085
         }
2402624086
 
tests/cli_driver.rsmodified
@@ -496,6 +496,50 @@ fn stream_unformatted_scalar_char_read_preserves_each_byte() {
496496
     let _ = std::fs::remove_file(&src);
497497
 }
498498
 
499
+#[test]
500
+fn stream_unformatted_char_write_preserves_exact_bytes() {
501
+    let output_file = unique_path("stream_unformatted_char_write", "bin");
502
+    let src = write_program(
503
+        &format!(
504
+            "program p\n  implicit none\n  integer :: unit_num, ios\n  open(newunit=unit_num, file='{}', status='replace', action='write', access='stream', form='unformatted', iostat=ios)\n  if (ios /= 0) error stop 1\n  write(unit_num, iostat=ios) 'alpha'\n  if (ios /= 0) error stop 2\n  close(unit_num)\nend program\n",
505
+            output_file.display()
506
+        ),
507
+        "stream_unformatted_char_write.f90",
508
+    );
509
+    let out = unique_path("stream_unformatted_char_write", "bin");
510
+    let compile = Command::new(compiler("armfortas"))
511
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
512
+        .output()
513
+        .expect("stream unformatted char write compile failed to spawn");
514
+    assert!(
515
+        compile.status.success(),
516
+        "stream unformatted char write compile failed: {}",
517
+        String::from_utf8_lossy(&compile.stderr)
518
+    );
519
+
520
+    let run = Command::new(&out)
521
+        .output()
522
+        .expect("stream unformatted char write run failed");
523
+    assert!(
524
+        run.status.success(),
525
+        "stream unformatted char write run failed: status={:?} stderr={}",
526
+        run.status,
527
+        String::from_utf8_lossy(&run.stderr)
528
+    );
529
+
530
+    let bytes = std::fs::read(&output_file).expect("cannot read stream unformatted char output");
531
+    assert_eq!(
532
+        bytes,
533
+        b"alpha",
534
+        "expected exact bytes from stream-unformatted character write, got {:?}",
535
+        bytes
536
+    );
537
+
538
+    let _ = std::fs::remove_file(&output_file);
539
+    let _ = std::fs::remove_file(&out);
540
+    let _ = std::fs::remove_file(&src);
541
+}
542
+
499543
 #[test]
500544
 fn repeated_nonadvancing_a1_read_preserves_embedded_nul_bytes() {
501545
     let input = unique_path("nonadvancing_a1_char_read", "bin");
@@ -2780,6 +2824,47 @@ fn contained_char_function_in_comparison_uses_internal_call_target() {
27802824
     let _ = std::fs::remove_dir_all(&dir);
27812825
 }
27822826
 
2827
+#[test]
2828
+fn close_status_delete_removes_file() {
2829
+    let path = unique_path("close_status_delete", "txt");
2830
+    let src = write_program(
2831
+        &format!(
2832
+            "program p\n  implicit none\n  integer :: unit, ios\n  open(newunit=unit, file='{}', status='replace', action='write', iostat=ios)\n  if (ios /= 0) error stop 1\n  write(unit, '(a)', iostat=ios) 'hello'\n  if (ios /= 0) error stop 2\n  close(unit, status='delete', iostat=ios)\n  if (ios /= 0) error stop 3\n  print *, 'ok'\nend program\n",
2833
+            path.display()
2834
+        ),
2835
+        "f90",
2836
+    );
2837
+    let out = unique_path("close_status_delete", "bin");
2838
+    let compile = Command::new(compiler("armfortas"))
2839
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
2840
+        .output()
2841
+        .expect("close status delete compile failed to spawn");
2842
+    assert!(
2843
+        compile.status.success(),
2844
+        "close status delete compile failed: {}",
2845
+        String::from_utf8_lossy(&compile.stderr)
2846
+    );
2847
+
2848
+    let run = Command::new(&out)
2849
+        .output()
2850
+        .expect("close status delete run failed");
2851
+    assert!(
2852
+        run.status.success(),
2853
+        "close status delete run failed: status={:?} stderr={}",
2854
+        run.status,
2855
+        String::from_utf8_lossy(&run.stderr)
2856
+    );
2857
+    assert!(
2858
+        !path.exists(),
2859
+        "close(status='delete') should remove the file: {}",
2860
+        path.display()
2861
+    );
2862
+
2863
+    let _ = std::fs::remove_file(&out);
2864
+    let _ = std::fs::remove_file(&src);
2865
+    let _ = std::fs::remove_file(&path);
2866
+}
2867
+
27832868
 #[test]
27842869
 fn bind_c_interface_function_returning_c_ptr_runs() {
27852870
     let dir = unique_dir("bind_c_c_ptr_return");
@@ -12539,6 +12624,43 @@ fn empty_allocatable_char_component_copy_stays_allocated() {
1253912624
     let _ = std::fs::remove_file(&src);
1254012625
 }
1254112626
 
12627
+#[test]
12628
+fn derived_function_result_keeps_unallocated_allocatable_char_components_unallocated() {
12629
+    let src = write_program(
12630
+        "module m\n  implicit none\n  type :: options_t\n    logical :: cleanup_on_close = .true.\n    character(len=:), allocatable :: prefix\n    character(len=:), allocatable :: suffix\n    character(len=:), allocatable :: parent_dir\n  end type options_t\ncontains\n  function clear_options() result(options)\n    type(options_t) :: options\n    options%cleanup_on_close = .true.\n  end function\nend module\n\nprogram p\n  use m\n  implicit none\n  type(options_t) :: options\n  options = clear_options()\n  if (.not. options%cleanup_on_close) error stop 1\n  if (allocated(options%prefix)) error stop 2\n  if (allocated(options%suffix)) error stop 3\n  if (allocated(options%parent_dir)) error stop 4\n  print *, 'ok'\nend program\n",
12631
+        "f90",
12632
+    );
12633
+    let out = unique_path("derived_result_unalloc_char_components", "bin");
12634
+    let compile = Command::new(compiler("armfortas"))
12635
+        .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()])
12636
+        .output()
12637
+        .expect("derived result unalloc char component compile failed to spawn");
12638
+    assert!(
12639
+        compile.status.success(),
12640
+        "derived result unalloc char component compile failed: {}",
12641
+        String::from_utf8_lossy(&compile.stderr)
12642
+    );
12643
+
12644
+    let run = Command::new(&out)
12645
+        .output()
12646
+        .expect("derived result unalloc char component run failed");
12647
+    assert!(
12648
+        run.status.success(),
12649
+        "derived result unalloc char component run failed: status={:?} stderr={}",
12650
+        run.status,
12651
+        String::from_utf8_lossy(&run.stderr)
12652
+    );
12653
+    let stdout = String::from_utf8_lossy(&run.stdout);
12654
+    assert!(
12655
+        stdout.contains("ok"),
12656
+        "unexpected derived result unalloc char component output: {}",
12657
+        stdout
12658
+    );
12659
+
12660
+    let _ = std::fs::remove_file(&out);
12661
+    let _ = std::fs::remove_file(&src);
12662
+}
12663
+
1254212664
 #[test]
1254312665
 fn derived_array_growth_keeps_unallocated_allocatable_components_clear() {
1254412666
     let src = write_program(