From 47e92dd2fddb75143172db8816f0ad61a2146b01 Mon Sep 17 00:00:00 2001
From: Aaron Councilman <aaronjc4@illinois.edu>
Date: Sun, 17 Nov 2024 11:28:56 -0600
Subject: [PATCH] Add ability to compile .hir file through Juno Compiler

---
 Cargo.lock                                   |  25 +++++
 Cargo.toml                                   |   6 +-
 hercules_samples/dot/Cargo.toml              |   5 +
 hercules_samples/dot/build.rs                |  16 ++-
 hercules_samples/dot/{ => src}/dot.hir       |   0
 hercules_samples/dot/src/main.rs             |   8 +-
 hercules_samples/fac/Cargo.toml              |   5 +
 hercules_samples/fac/build.rs                |  16 ++-
 hercules_samples/fac/{ => src}/fac.hir       |   0
 hercules_samples/fac/src/main.rs             |   8 +-
 hercules_samples/matmul/Cargo.toml           |   5 +
 hercules_samples/matmul/build.rs             |  16 ++-
 hercules_samples/matmul/src/main.rs          |   8 +-
 hercules_samples/matmul/{ => src}/matmul.hir |   0
 juno_build/Cargo.toml                        |   1 +
 juno_build/src/lib.rs                        | 100 ++++++++++++++++---
 juno_frontend/src/lib.rs                     |  48 +++++++--
 17 files changed, 194 insertions(+), 73 deletions(-)
 rename hercules_samples/dot/{ => src}/dot.hir (100%)
 rename hercules_samples/fac/{ => src}/fac.hir (100%)
 rename hercules_samples/matmul/{ => src}/matmul.hir (100%)

diff --git a/Cargo.lock b/Cargo.lock
index ff31e6d6..7cbeacf8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -369,6 +369,18 @@ dependencies = [
  "powerfmt",
 ]
 
+[[package]]
+name = "dot"
+version = "0.1.0"
+dependencies = [
+ "async-std",
+ "clap",
+ "hercules_rt",
+ "juno_build",
+ "rand",
+ "with_builtin_macros",
+]
+
 [[package]]
 name = "either"
 version = "1.13.0"
@@ -682,6 +694,7 @@ dependencies = [
 name = "juno_build"
 version = "0.1.0"
 dependencies = [
+ "hercules_ir",
  "hercules_rt",
  "juno_frontend",
  "with_builtin_macros",
@@ -835,6 +848,18 @@ dependencies = [
  "vob",
 ]
 
+[[package]]
+name = "matmul"
+version = "0.1.0"
+dependencies = [
+ "async-std",
+ "clap",
+ "hercules_rt",
+ "juno_build",
+ "rand",
+ "with_builtin_macros",
+]
+
 [[package]]
 name = "memchr"
 version = "2.7.4"
diff --git a/Cargo.toml b/Cargo.toml
index d618b9b5..63579146 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,8 +12,8 @@ members = [
 	#"hercules_test/hercules_interpreter",
 	#"hercules_test/hercules_tests",
 
-	#"hercules_samples/dot",
-	#"hercules_samples/matmul",
+	"hercules_samples/dot",
+	"hercules_samples/matmul",
 	#"hercules_samples/fac",
 
 	"juno_frontend",
@@ -22,4 +22,4 @@ members = [
 
 	#"juno_samples/matmul",
 	"juno_samples/simple3",
-]
\ No newline at end of file
+]
diff --git a/hercules_samples/dot/Cargo.toml b/hercules_samples/dot/Cargo.toml
index ab10aeda..69cd39e3 100644
--- a/hercules_samples/dot/Cargo.toml
+++ b/hercules_samples/dot/Cargo.toml
@@ -4,8 +4,13 @@ version = "0.1.0"
 authors = ["Russel Arbore <rarbore2@illinois.edu>"]
 edition = "2021"
 
+[build-dependencies]
+juno_build = { path = "../../juno_build" }
+
 [dependencies]
 clap = { version = "*", features = ["derive"] }
+juno_build = { path = "../../juno_build" }
 hercules_rt = { path = "../../hercules_rt" }
 rand = "*"
 async-std = "*"
+with_builtin_macros = "0.1.0"
diff --git a/hercules_samples/dot/build.rs b/hercules_samples/dot/build.rs
index 47fb55e8..cfa03fd3 100644
--- a/hercules_samples/dot/build.rs
+++ b/hercules_samples/dot/build.rs
@@ -1,12 +1,10 @@
-use std::env::current_dir;
+extern crate juno_build;
+use juno_build::JunoCompiler;
 
 fn main() {
-    println!(
-        "cargo::rustc-link-search=native={}",
-        current_dir().unwrap().display()
-    );
-    println!("cargo::rustc-link-lib=static=dot");
-
-    println!("cargo::rerun-if-changed=dot.hman");
-    println!("cargo::rerun-if-changed=libdot.a");
+    JunoCompiler::new()
+        .ir_in_src("dot.hir")
+        .unwrap()
+        .build()
+        .unwrap();
 }
diff --git a/hercules_samples/dot/dot.hir b/hercules_samples/dot/src/dot.hir
similarity index 100%
rename from hercules_samples/dot/dot.hir
rename to hercules_samples/dot/src/dot.hir
diff --git a/hercules_samples/dot/src/main.rs b/hercules_samples/dot/src/main.rs
index dbf18713..3d282ae2 100644
--- a/hercules_samples/dot/src/main.rs
+++ b/hercules_samples/dot/src/main.rs
@@ -1,12 +1,8 @@
 extern crate async_std;
 extern crate clap;
-extern crate hercules_rt;
+extern crate juno_build;
 
-// To compile currently, run from the Hercules project root directory:
-// cargo run --bin hercules_driver hercules_samples/dot/dot.hir "Codegen(\"hercules_samples/dot\",\"dot\")"
-// Then, you can execute this example with:
-// cargo run --bin dot
-hercules_rt::use_hman!("hercules_samples/dot/dot.hman");
+juno_build::juno!("dot");
 
 fn main() {
     async_std::task::block_on(async {
diff --git a/hercules_samples/fac/Cargo.toml b/hercules_samples/fac/Cargo.toml
index bef11a91..9082a4fc 100644
--- a/hercules_samples/fac/Cargo.toml
+++ b/hercules_samples/fac/Cargo.toml
@@ -4,8 +4,13 @@ version = "0.1.0"
 authors = ["Russel Arbore <rarbore2@illinois.edu>"]
 edition = "2021"
 
+[build-dependencies]
+juno_build = { path = "../../juno_build" }
+
 [dependencies]
 clap = { version = "*", features = ["derive"] }
+juno_build = { path = "../../juno_build" }
 hercules_rt = { path = "../../hercules_rt" }
 rand = "*"
 async-std = "*"
+with_builtin_macros = "0.1.0"
diff --git a/hercules_samples/fac/build.rs b/hercules_samples/fac/build.rs
index f43f9fbd..49a60248 100644
--- a/hercules_samples/fac/build.rs
+++ b/hercules_samples/fac/build.rs
@@ -1,12 +1,10 @@
-use std::env::current_dir;
+extern crate juno_build;
+use juno_build::JunoCompiler;
 
 fn main() {
-    println!(
-        "cargo::rustc-link-search=native={}",
-        current_dir().unwrap().display()
-    );
-    println!("cargo::rustc-link-lib=static=fac");
-
-    println!("cargo::rerun-if-changed=fac.hman");
-    println!("cargo::rerun-if-changed=libfac.a");
+    JunoCompiler::new()
+        .ir_in_src("fac.hir")
+        .unwrap()
+        .build()
+        .unwrap();
 }
diff --git a/hercules_samples/fac/fac.hir b/hercules_samples/fac/src/fac.hir
similarity index 100%
rename from hercules_samples/fac/fac.hir
rename to hercules_samples/fac/src/fac.hir
diff --git a/hercules_samples/fac/src/main.rs b/hercules_samples/fac/src/main.rs
index 5e5b4dde..e29860ee 100644
--- a/hercules_samples/fac/src/main.rs
+++ b/hercules_samples/fac/src/main.rs
@@ -1,12 +1,8 @@
 extern crate async_std;
 extern crate clap;
-extern crate hercules_rt;
+extern crate juno_build;
 
-// To compile currently, run from the Hercules project root directory:
-// cargo run --bin hercules_driver hercules_samples/fac/fac.hir "Codegen(\"hercules_samples/fac\",\"fac\")"
-// Then, you can execute this example with:
-// cargo run --bin fac
-hercules_rt::use_hman!("hercules_samples/fac/fac.hman");
+juno_build::juno!("fac");
 
 fn main() {
     async_std::task::block_on(async {
diff --git a/hercules_samples/matmul/Cargo.toml b/hercules_samples/matmul/Cargo.toml
index 744b3f59..9066c153 100644
--- a/hercules_samples/matmul/Cargo.toml
+++ b/hercules_samples/matmul/Cargo.toml
@@ -4,8 +4,13 @@ version = "0.1.0"
 authors = ["Russel Arbore <rarbore2@illinois.edu>"]
 edition = "2021"
 
+[build-dependencies]
+juno_build = { path = "../../juno_build" }
+
 [dependencies]
 clap = { version = "*", features = ["derive"] }
+juno_build = { path = "../../juno_build" }
 hercules_rt = { path = "../../hercules_rt" }
 rand = "*"
 async-std = "*"
+with_builtin_macros = "0.1.0"
diff --git a/hercules_samples/matmul/build.rs b/hercules_samples/matmul/build.rs
index aa4cd97f..ec6eb892 100644
--- a/hercules_samples/matmul/build.rs
+++ b/hercules_samples/matmul/build.rs
@@ -1,12 +1,10 @@
-use std::env::current_dir;
+extern crate juno_build;
+use juno_build::JunoCompiler;
 
 fn main() {
-    println!(
-        "cargo::rustc-link-search=native={}",
-        current_dir().unwrap().display()
-    );
-    println!("cargo::rustc-link-lib=static=matmul");
-
-    println!("cargo::rerun-if-changed=matmul.hman");
-    println!("cargo::rerun-if-changed=libmatmul.a");
+    JunoCompiler::new()
+        .ir_in_src("matmul.hir")
+        .unwrap()
+        .build()
+        .unwrap();
 }
diff --git a/hercules_samples/matmul/src/main.rs b/hercules_samples/matmul/src/main.rs
index ac5e96b2..26168d4b 100644
--- a/hercules_samples/matmul/src/main.rs
+++ b/hercules_samples/matmul/src/main.rs
@@ -2,13 +2,9 @@
 
 extern crate async_std;
 extern crate clap;
-extern crate hercules_rt;
+extern crate juno_build;
 
-// To compile currently, run from the Hercules project root directory:
-// cargo run --bin hercules_driver hercules_samples/matmul/matmul.hir "Codegen(\"hercules_samples/matmul\",\"matmul\")"
-// Then, you can execute this example with:
-// cargo run --bin matmul
-hercules_rt::use_hman!("hercules_samples/matmul/matmul.hman");
+juno_build::juno!("matmul");
 
 fn main() {
     async_std::task::block_on(async {
diff --git a/hercules_samples/matmul/matmul.hir b/hercules_samples/matmul/src/matmul.hir
similarity index 100%
rename from hercules_samples/matmul/matmul.hir
rename to hercules_samples/matmul/src/matmul.hir
diff --git a/juno_build/Cargo.toml b/juno_build/Cargo.toml
index 2e35059e..4f623498 100644
--- a/juno_build/Cargo.toml
+++ b/juno_build/Cargo.toml
@@ -7,4 +7,5 @@ edition = "2021"
 [dependencies]
 juno_frontend = { path = "../juno_frontend" }
 hercules_rt = { path = "../hercules_rt" }
+hercules_ir = { path = "../hercules_ir" }
 with_builtin_macros = "0.1.0"
diff --git a/juno_build/src/lib.rs b/juno_build/src/lib.rs
index c40d8de1..e01a5187 100644
--- a/juno_build/src/lib.rs
+++ b/juno_build/src/lib.rs
@@ -1,16 +1,21 @@
+extern crate hercules_ir;
 extern crate hercules_rt;
+
 use juno_compiler::*;
 
 use std::env::{current_dir, var};
 use std::fmt::Write;
-use std::fs::create_dir_all;
+use std::fs::{create_dir_all, read_to_string};
 use std::path::{Path, PathBuf};
 
 use with_builtin_macros::with_builtin;
 
 // JunoCompiler is used to compile juno files into a library and manifest file appropriately to
 // import the definitions into a rust project via the juno! macro defined below
+// You can also specify a Hercules IR file instead of a Juno file and this will compile that IR
+// file (though in that case a schedule is not supported)
 pub struct JunoCompiler {
+    ir_src_path: Option<PathBuf>,
     src_path: Option<PathBuf>,
     out_path: Option<PathBuf>,
     verify: JunoVerify,
@@ -21,6 +26,7 @@ pub struct JunoCompiler {
 impl JunoCompiler {
     pub fn new() -> Self {
         JunoCompiler {
+            ir_src_path: None,
             src_path: None,
             out_path: None,
             verify: JunoVerify::None,
@@ -32,6 +38,33 @@ impl JunoCompiler {
     // Sets the name of the Juno file, this file should be contained in the src/
     // file of the package that the JunoCompiler is used in the build script of
     pub fn file_in_src<P>(mut self, file: P) -> Result<Self, String>
+    where
+        P: AsRef<Path>,
+    {
+        self.src_path = Some(Self::make_src_path(&file)?);
+        self.ir_src_path = None;
+        self.out_path = Some(Self::get_out_path(&file)?);
+        Self::print_cargo_info(file);
+
+        Ok(self)
+    }
+
+    // Sets the name of the input Hercules IR file which is contained in the src/ directory of the
+    // package that the JunoCompiler is used in the build script of
+    pub fn ir_in_src<P>(mut self, file: P) -> Result<Self, String>
+    where
+        P: AsRef<Path>,
+    {
+        self.ir_src_path = Some(Self::make_src_path(&file)?);
+        self.src_path = None;
+        self.out_path = Some(Self::get_out_path(&file)?);
+        Self::print_cargo_info(file);
+
+        Ok(self)
+    }
+
+    // Construct the path to a file in the source directory
+    fn make_src_path<P>(file: P) -> Result<PathBuf, String>
     where
         P: AsRef<Path>,
     {
@@ -48,34 +81,45 @@ impl JunoCompiler {
         };
         path.push("src");
         path.push(file.as_ref());
-        self.src_path = Some(path);
 
-        // We also set the output file in this process, under the OUT_DIR cargo provides
+        Ok(path)
+    }
+
+    // Construct the path to the output directory, under the OUT_DIR cargo provides
+    fn get_out_path<P>(file: P) -> Result<PathBuf, String>
+    where
+        P: AsRef<Path>,
+    {
         let mut out = PathBuf::new();
         out.push(var("OUT_DIR").unwrap());
         out.push(file.as_ref().parent().unwrap().to_str().unwrap());
         let Ok(()) = create_dir_all(&out) else {
             return Err("Failed to create output directory.".to_string());
         };
-        self.out_path = Some(out);
+        Ok(out)
+    }
 
-        // Tell cargo to rerun if the Juno file changes
+    // Prints information to tell cargo the following
+    // - Rerun if the source file changes
+    // - Add the output directory to its linker search
+    // - Link the compiled library file
+    // Tell cargo to rerun if the Juno file changes
+    fn print_cargo_info<P>(src_file: P)
+    where
+        P: AsRef<Path>,
+    {
         println!(
             "cargo::rerun-if-changed=src/{}",
-            file.as_ref().to_str().unwrap()
+            src_file.as_ref().to_str().unwrap()
         );
-        // Tell cargo to include the output directory in its linker search
-        // (and to link the resulting library)
         println!(
             "cargo::rustc-link-search=native={}",
             var("OUT_DIR").unwrap()
         );
         println!(
             "cargo::rustc-link-lib=static={}",
-            file.as_ref().file_stem().unwrap().to_str().unwrap()
+            src_file.as_ref().file_stem().unwrap().to_str().unwrap()
         );
-
-        Ok(self)
     }
 
     pub fn verify(mut self, enabled: bool) -> Self {
@@ -139,7 +183,8 @@ impl JunoCompiler {
     // Builds the juno file into a libary and a manifest file.
     pub fn build(self) -> Result<(), String> {
         let JunoCompiler {
-            src_path: Some(src_path),
+            ir_src_path: ir_src_path,
+            src_path: src_path,
             out_path: Some(out_path),
             verify,
             x_dot,
@@ -149,12 +194,35 @@ impl JunoCompiler {
             return Err("No source file specified.".to_string());
         };
 
-        let src_file = src_path.to_str().unwrap().to_string();
         let out_dir = out_path.to_str().unwrap().to_string();
 
-        match compile(src_file, verify, x_dot, schedule, out_dir) {
-            Ok(()) => Ok(()),
-            Err(errs) => Err(format!("{}", errs)),
+        if let Some(src_path) = src_path {
+            let src_file = src_path.to_str().unwrap().to_string();
+
+            match compile(src_file, verify, x_dot, schedule, out_dir) {
+                Ok(()) => Ok(()),
+                Err(errs) => Err(format!("{}", errs)),
+            }
+        } else {
+            let Some(ir_src_path) = ir_src_path else {
+                return Err("No source file specified.".to_string());
+            };
+
+            let ir_src_file = ir_src_path.to_str().unwrap().to_string();
+            let ir_src_path = Path::new(&ir_src_file);
+            let module_name = String::from(ir_src_path.file_stem().unwrap().to_str().unwrap());
+
+            let Ok(contents) = read_to_string(&ir_src_path) else {
+                return Err("Unable to open and read input file.".to_string());
+            };
+            let Ok(ir_mod) = hercules_ir::parse::parse(&contents) else {
+                return Err("Unable to parse Hercules IR file.".to_string());
+            };
+
+            match compile_ir(ir_mod, None, verify, x_dot, schedule, out_dir, module_name) {
+                Ok(()) => Ok(()),
+                Err(errs) => Err(format!("{}", errs)),
+            }
         }
     }
 }
diff --git a/juno_frontend/src/lib.rs b/juno_frontend/src/lib.rs
index 1ac61fe5..96fefb7f 100644
--- a/juno_frontend/src/lib.rs
+++ b/juno_frontend/src/lib.rs
@@ -14,6 +14,8 @@ extern crate hercules_ir;
 use std::fmt;
 use std::path::Path;
 
+use juno_scheduler::FunctionMap;
+
 pub enum JunoVerify {
     None,
     JunoOpts,
@@ -96,6 +98,26 @@ pub fn compile(
     };
     let (module, func_info) = codegen::codegen_program(prg);
 
+    compile_ir(
+        module,
+        Some(func_info),
+        verify,
+        x_dot,
+        schedule,
+        output_dir,
+        module_name,
+    )
+}
+
+pub fn compile_ir(
+    module: hercules_ir::ir::Module,
+    func_info: Option<FunctionMap>,
+    verify: JunoVerify,
+    x_dot: bool,
+    schedule: JunoSchedule,
+    output_dir: String,
+    module_name: String,
+) -> Result<(), ErrorMessage> {
     let mut pm = match schedule {
         JunoSchedule::None => hercules_opt::pass::PassManager::new(module),
         JunoSchedule::DefaultSchedule => {
@@ -103,16 +125,24 @@ pub fn compile(
             pm.make_plans();
             pm
         }
-        JunoSchedule::Schedule(file) => match juno_scheduler::schedule(&module, func_info, file) {
-            Ok(plans) => {
-                let mut pm = hercules_opt::pass::PassManager::new(module);
-                pm.set_plans(plans);
-                pm
-            }
-            Err(msg) => {
-                return Err(ErrorMessage::SchedulingError(msg));
+        JunoSchedule::Schedule(file) => {
+            let Some(func_info) = func_info else {
+                return Err(ErrorMessage::SchedulingError(
+                    "Cannot schedule, no function information provided".to_string(),
+                ));
+            };
+
+            match juno_scheduler::schedule(&module, func_info, file) {
+                Ok(plans) => {
+                    let mut pm = hercules_opt::pass::PassManager::new(module);
+                    pm.set_plans(plans);
+                    pm
+                }
+                Err(msg) => {
+                    return Err(ErrorMessage::SchedulingError(msg));
+                }
             }
-        },
+        }
     };
     if verify.verify() || verify.verify_all() {
         pm.add_pass(hercules_opt::pass::Pass::Verify);
-- 
GitLab