Repository: calyxir/calyx (language: rust)

Issue:
Primitives aren't included in file output
When using the Verilog backend, the primitives aren't included in the output when writing to a file rather than standard out. This is because the file is truncated when it's opened a second time in the `OutputFile::get_write` call in `VerilogBackend::emit`, which overwrites anything that was written by `VerilogBackend::link_externs`.

I'll fix this, but I'm recording it in an issue since I probably won't get to it immediately.


Discussion / hints:
Thanks @bcarlet! Can you provide an MRE?
Sure, take any file that imports primitives:
```
import "primitives/compile.futil";

// The component doesn't matter
component main() -> (out: 8) {
  cells { r = std_reg(8); }
  wires { out = r.out; }
  control { invoke r(in=8'd42)(); }
}
```

If we we compile it to Verilog and send the result to standard out,
```
calyx -b verilog example.futil > out.sv
```
then `out.sv` contains a `module std_reg`. But if we send the result directly to a file,
```
calyx -b verilog example.futil -o out.sv
```
then `out.sv` does not contain a `module std_reg`.

Fix patch:
diff --git a/calyx-opt/src/passes/cell_share.rs b/calyx-opt/src/passes/cell_share.rs
index 228f2e9dc4..fe85667807 100644
--- a/calyx-opt/src/passes/cell_share.rs
+++ b/calyx-opt/src/passes/cell_share.rs
@@ -232,7 +232,7 @@ impl CellShare {
             comp.name,
             &self.live,
         );
-        if let Some(stream) = &self.print_par_timing {
+        if let Some(stream) = &mut self.print_par_timing {
             write!(stream.get_write(), "{:?}", self.par_timing_map).unwrap();
         }
     }
@@ -254,8 +254,8 @@ impl CellShare {
     }
 
     // prints the json if self.print_share_freqs is not None
-    fn print_share_json(&self) {
-        if let Some(file) = &self.print_share_freqs {
+    fn print_share_json(&mut self) {
+        if let Some(file) = &mut self.print_share_freqs {
             let printable_share_freqs: HashMap<String, HashMap<String, _>> =
                 self.share_freqs
                     .iter()
diff --git a/calyx-opt/src/passes/infer_share.rs b/calyx-opt/src/passes/infer_share.rs
index 7a81e4cf29..08a69f9c8c 100644
--- a/calyx-opt/src/passes/infer_share.rs
+++ b/calyx-opt/src/passes/infer_share.rs
@@ -113,10 +113,10 @@ impl Visitor for InferShare {
             DominatorMap::new(&mut comp.control.borrow_mut(), comp.name);
 
         // print the domination map if command line argument says so
-        if let Some(s) = &self.print_dmap {
+        if let Some(s) = &mut self.print_dmap {
             write!(s.get_write(), "{dmap:?}").unwrap();
         }
-        if let Some(s) = &self.print_static_analysis {
+        if let Some(s) = &mut self.print_static_analysis {
             write!(s.get_write(), "{:?}", dmap.static_par_domination).unwrap();
         }
 
diff --git a/calyx-opt/src/passes/top_down_compile_control.rs b/calyx-opt/src/passes/top_down_compile_control.rs
index de1634da6e..6968e91d68 100644
--- a/calyx-opt/src/passes/top_down_compile_control.rs
+++ b/calyx-opt/src/passes/top_down_compile_control.rs
@@ -1461,7 +1461,7 @@ impl Visitor for TopDownCompileControl {
 
     /// If requested, emit FSM json after all components are processed
     fn finish_context(&mut self, _ctx: &mut calyx_ir::Context) -> VisResult {
-        if let Some(json_out_file) = &self.dump_fsm_json {
+        if let Some(json_out_file) = &mut self.dump_fsm_json {
             let _ = serde_json::to_writer_pretty(
                 json_out_file.get_write(),
                 &self.fsm_groups,
diff --git a/calyx-utils/src/out_file.rs b/calyx-utils/src/out_file.rs
index 9191576d57..16ac5ff654 100644
--- a/calyx-utils/src/out_file.rs
+++ b/calyx-utils/src/out_file.rs
@@ -14,16 +14,24 @@ pub enum OutputFile {
     Null,
     Stdout,
     Stderr,
-    File(PathBuf),
+    File {
+        path: PathBuf,
+        // Has the writer been initialized?
+        init: bool,
+    },
 }
 
 impl OutputFile {
+    pub fn file(path: PathBuf) -> Self {
+        OutputFile::File { path, init: false }
+    }
+
     pub fn as_path_string(&self) -> String {
         match self {
             OutputFile::Null => "<null>".to_string(),
             OutputFile::Stdout => "<stdout>".to_string(),
             OutputFile::Stderr => "<stderr>".to_string(),
-            OutputFile::File(path) => path.to_string_lossy().to_string(),
+            OutputFile::File { path, .. } => path.to_string_lossy().to_string(),
         }
     }
 }
@@ -35,7 +43,7 @@ impl FromStr for OutputFile {
             "-" | "<out>" => Ok(OutputFile::Stdout),
             "<err>" => Ok(OutputFile::Stderr),
             "<null>" => Ok(OutputFile::Null),
-            _ => Ok(OutputFile::File(PathBuf::from(s))),
+            _ => Ok(OutputFile::file(PathBuf::from(s))),
         }
     }
 }
@@ -46,7 +54,7 @@ impl ToString for OutputFile {
             OutputFile::Stdout => "-".to_string(),
             OutputFile::Stderr => "<err>".to_string(),
             OutputFile::Null => "<null>".to_string(),
-            OutputFile::File(p) => p.to_str().unwrap().to_string(),
+            OutputFile::File { path, .. } => path.to_str().unwrap().to_string(),
         }
     }
 }
@@ -56,16 +64,32 @@ impl OutputFile {
         match self {
             OutputFile::Stdout => atty::is(atty::Stream::Stdout),
             OutputFile::Stderr => atty::is(atty::Stream::Stderr),
-            OutputFile::Null | OutputFile::File(_) => false,
+            OutputFile::Null | OutputFile::File { .. } => false,
         }
     }
 
-    pub fn get_write(&self) -> Box<dyn io::Write> {
+    pub fn get_write(&mut self) -> Box<dyn io::Write> {
         match self {
             OutputFile::Stdout => Box::new(BufWriter::new(std::io::stdout())),
             OutputFile::Stderr => Box::new(BufWriter::new(std::io::stderr())),
-            OutputFile::File(path) => {
-                Box::new(BufWriter::new(std::fs::File::create(path).unwrap()))
+            OutputFile::File { path, init } => {
+                // If the file does not exist, create it. Otherwise, open it for appending.
+                let buf = if *init {
+                    assert!(
+                        path.exists(),
+                        "writer initialized but file does not exist"
+                    );
+                    BufWriter::new(
+                        std::fs::OpenOptions::new()
+                            .append(true)
+                            .open(path)
+                            .unwrap(),
+                    )
+                } else {
+                    *init = true;
+                    BufWriter::new(std::fs::File::create(path).unwrap())
+                };
+                Box::new(buf)
             }
             OutputFile::Null => Box::new(io::sink()),
         }
diff --git a/src/cmdline.rs b/src/cmdline.rs
index 108bb87107..f03eb0b365 100644
--- a/src/cmdline.rs
+++ b/src/cmdline.rs
@@ -136,7 +136,7 @@ impl FromStr for CompileMode {
 
 impl Opts {
     /// Given a context, calls the backend corresponding to the `BackendOpt` variant
-    pub fn run_backend(self, context: ir::Context) -> CalyxResult<()> {
+    pub fn run_backend(mut self, context: ir::Context) -> CalyxResult<()> {
         match self.backend {
             BackendOpt::Mlir => {
                 let backend = MlirBackend;
diff --git a/tools/component_cells/src/main.rs b/tools/component_cells/src/main.rs
index 85c8f0eaec..8b1be9eb40 100644
--- a/tools/component_cells/src/main.rs
+++ b/tools/component_cells/src/main.rs
@@ -103,7 +103,7 @@ fn gen_component_info(
 /// Write the collected set of component information to a JSON file.
 fn write_json(
     component_info: HashSet<ComponentInfo>,
-    file: OutputFile,
+    mut file: OutputFile,
 ) -> Result<(), io::Error> {
     let created_vec: Vec<ComponentInfo> = component_info.into_iter().collect();
     serde_json::to_writer_pretty(file.get_write(), &created_vec)?;


Test patch: