diff --git a/flake.nix b/flake.nix index 4febca24..a6ce5fce 100644 --- a/flake.nix +++ b/flake.nix @@ -13,6 +13,7 @@ '' mkdir -p $out/bin ln -s ${pkgs.llvmPackages_14.clang-unwrapped}/bin/clang $out/bin/clang-irrt + ln -s ${pkgs.llvmPackages_14.clang}/bin/clang $out/bin/clang-irrt-test ln -s ${pkgs.llvmPackages_14.llvm.out}/bin/llvm-as $out/bin/llvm-as-irrt ''; nac3artiq = pkgs.python3Packages.toPythonModule ( @@ -23,6 +24,7 @@ cargoLock = { lockFile = ./Cargo.lock; }; + cargoTestFlags = [ "--features" "test" ]; passthru.cargoLock = cargoLock; nativeBuildInputs = [ pkgs.python3 pkgs.llvmPackages_14.clang llvm-tools-irrt pkgs.llvmPackages_14.llvm.out llvm-nac3 ]; buildInputs = [ pkgs.python3 llvm-nac3 ]; diff --git a/nac3core/Cargo.toml b/nac3core/Cargo.toml index 724e0c8c..6e66f440 100644 --- a/nac3core/Cargo.toml +++ b/nac3core/Cargo.toml @@ -1,3 +1,6 @@ +[features] +test = [] + [package] name = "nac3core" version = "0.1.0" diff --git a/nac3core/build.rs b/nac3core/build.rs index 9d6292a2..7f4e0454 100644 --- a/nac3core/build.rs +++ b/nac3core/build.rs @@ -3,22 +3,32 @@ use std::{ env, fs::File, io::Write, - path::Path, + path::{Path, PathBuf}, process::{Command, Stdio}, }; -fn main() { - // Define relevant directories - let out_dir = env::var("OUT_DIR").unwrap(); - let out_dir = Path::new(&out_dir); - let irrt_dir = Path::new("irrt"); +const CMD_IRRT_CLANG: &str = "clang-irrt"; +const CMD_IRRT_CLANG_TEST: &str = "clang-irrt-test"; +const CMD_IRRT_LLVM_AS: &str = "llvm-as-irrt"; - let irrt_cpp_path = irrt_dir.join("irrt.cpp"); +fn get_out_dir() -> PathBuf { + PathBuf::from(env::var("OUT_DIR").unwrap()) +} + +fn get_irrt_dir() -> &'static Path { + Path::new("irrt") +} + +/// Compile `irrt.cpp` for use in `src/codegen` +fn compile_irrt_cpp() { + let out_dir = get_out_dir(); + let irrt_dir = get_irrt_dir(); /* * HACK: Sadly, clang doesn't let us emit generic LLVM bitcode. * Compiling for WASM32 and filtering the output with regex is the closest we can get. */ + let irrt_cpp_path = irrt_dir.join("irrt.cpp"); let flags: &[&str] = &[ "--target=wasm32", "-x", @@ -45,7 +55,7 @@ fn main() { println!("cargo:rerun-if-changed={}", irrt_dir.to_str().unwrap()); // Compile IRRT and capture the LLVM IR output - let output = Command::new("clang-irrt") + let output = Command::new(CMD_IRRT_CLANG) .args(flags) .output() .map(|o| { @@ -88,7 +98,7 @@ fn main() { // Assemble the emitted and filtered IR to .bc // That .bc will be integrated into nac3core's codegen - let mut llvm_as = Command::new("llvm-as-irrt") + let mut llvm_as = Command::new(CMD_IRRT_LLVM_AS) .stdin(Stdio::piped()) .arg("-o") .arg(out_dir.join("irrt.bc")) @@ -97,3 +107,48 @@ fn main() { llvm_as.stdin.as_mut().unwrap().write_all(filtered_output.as_bytes()).unwrap(); assert!(llvm_as.wait().unwrap().success()); } + +/// Compile `irrt_test.cpp` for testing +fn compile_irrt_test_cpp() { + let out_dir = get_out_dir(); + let irrt_dir = get_irrt_dir(); + + let exe_path = out_dir.join("irrt_test.out"); // Output path of the compiled test executable + let irrt_test_cpp_path = irrt_dir.join("irrt_test.cpp"); + let flags: &[&str] = &[ + irrt_test_cpp_path.to_str().unwrap(), + "-x", + "c++", + "-I", + irrt_dir.to_str().unwrap(), + "-g", + "-fno-discard-value-names", + "-O0", + "-Wall", + "-Wextra", + "-Werror=return-type", + "-lm", // for `tgamma()`, `lgamma()` + "-o", + exe_path.to_str().unwrap(), + ]; + + Command::new(CMD_IRRT_CLANG_TEST) + .args(flags) + .output() + .map(|o| { + assert!(o.status.success(), "{}", std::str::from_utf8(&o.stderr).unwrap()); + o + }) + .unwrap(); + println!("cargo:rerun-if-changed={}", irrt_dir.to_str().unwrap()); +} + +fn main() { + compile_irrt_cpp(); + + // https://github.com/rust-lang/cargo/issues/2549 + // `cargo test -F test` to also build `irrt_test.cpp + if cfg!(feature = "test") { + compile_irrt_test_cpp(); + } +} diff --git a/nac3core/irrt/irrt.cpp b/nac3core/irrt/irrt.cpp index 4aa710c8..489ce090 100644 --- a/nac3core/irrt/irrt.cpp +++ b/nac3core/irrt/irrt.cpp @@ -1,3 +1,4 @@ +#define IRRT_DEFINE_TYPEDEF_INTS #include "irrt_everything.hpp" /* diff --git a/nac3core/irrt/irrt/int_defs.hpp b/nac3core/irrt/irrt/int_defs.hpp index c24f7394..a610ea9c 100644 --- a/nac3core/irrt/irrt/int_defs.hpp +++ b/nac3core/irrt/irrt/int_defs.hpp @@ -1,8 +1,12 @@ #pragma once -using int8_t = _BitInt(8); -using uint8_t = unsigned _BitInt(8); -using int32_t = _BitInt(32); -using uint32_t = unsigned _BitInt(32); -using int64_t = _BitInt(64); -using uint64_t = unsigned _BitInt(64); \ No newline at end of file +// This is made toggleable since `irrt_test.cpp` itself would include +// headers that define these typedefs +#ifdef IRRT_DEFINE_TYPEDEF_INTS +typedef _BitInt(8) int8_t; +typedef unsigned _BitInt(8) uint8_t; +typedef _BitInt(32) int32_t; +typedef unsigned _BitInt(32) uint32_t; +typedef _BitInt(64) int64_t; +typedef unsigned _BitInt(64) uint64_t; +#endif \ No newline at end of file diff --git a/nac3core/irrt/irrt/utils.hpp b/nac3core/irrt/irrt/utils.hpp index 728d5f81..6735f36b 100644 --- a/nac3core/irrt/irrt/utils.hpp +++ b/nac3core/irrt/irrt/utils.hpp @@ -10,4 +10,12 @@ template const T& min(const T& a, const T& b) { return a > b ? b : a; } + +template +bool arrays_match(int len, T* as, T* bs) { + for (int i = 0; i < len; i++) { + if (as[i] != bs[i]) return false; + } + return true; } +} \ No newline at end of file diff --git a/nac3core/irrt/irrt_test.cpp b/nac3core/irrt/irrt_test.cpp new file mode 100644 index 00000000..8aa64c2f --- /dev/null +++ b/nac3core/irrt/irrt_test.cpp @@ -0,0 +1,16 @@ +// This file will be compiled like a real C++ program, +// and we do have the luxury to use the standard libraries. +// That is if the nix flakes do not have issues... especially on msys2... +#include +#include +#include + +#include "irrt_everything.hpp" + +#include "test/core.hpp" +#include "test/test_core.hpp" + +int main() { + test_int_exp(); + return 0; +} \ No newline at end of file diff --git a/nac3core/irrt/test/core.hpp b/nac3core/irrt/test/core.hpp new file mode 100644 index 00000000..34063aa4 --- /dev/null +++ b/nac3core/irrt/test/core.hpp @@ -0,0 +1,88 @@ +#pragma once + +// Include this header for every test_*.cpp + +#include +#include +#include + +#include "print.hpp" + +// Some utils can be used here +#include "../irrt/utils.hpp" + +void __begin_test(const char* function_name, const char* file, int line) { + printf("######### Running %s @ %s:%d\n", function_name, file, line); +} + +#define BEGIN_TEST() __begin_test(__FUNCTION__, __FILE__, __LINE__) + +void test_fail() { + printf("[!] Test failed. Exiting with status code 1.\n"); + exit(1); +} + +template +void debug_print_array(int len, const T* as) { + printf("["); + for (int i = 0; i < len; i++) { + if (i != 0) printf(", "); + print_value(as[i]); + } + printf("]"); +} + +void print_assertion_passed(const char* file, int line) { + printf("[*] Assertion passed on %s:%d\n", file, line); +} + +void print_assertion_failed(const char* file, int line) { + printf("[!] Assertion failed on %s:%d\n", file, line); +} + +void __assert_true(const char* file, int line, bool cond) { + if (cond) { + print_assertion_passed(file, line); + } else { + print_assertion_failed(file, line); + test_fail(); + } +} + +#define assert_true(cond) __assert_true(__FILE__, __LINE__, cond) + +template +void __assert_arrays_match(const char* file, int line, int len, const T* expected, const T* got) { + if (arrays_match(len, expected, got)) { + print_assertion_passed(file, line); + } else { + print_assertion_failed(file, line); + printf("Expect = "); + debug_print_array(len, expected); + printf("\n"); + printf(" Got = "); + debug_print_array(len, got); + printf("\n"); + test_fail(); + } +} + +#define assert_arrays_match(len, expected, got) __assert_arrays_match(__FILE__, __LINE__, len, expected, got) + +template +void __assert_values_match(const char* file, int line, T expected, T got) { + if (expected == got) { + print_assertion_passed(file, line); + } else { + print_assertion_failed(file, line); + printf("Expect = "); + print_value(expected); + printf("\n"); + printf(" Got = "); + print_value(got); + printf("\n"); + test_fail(); + } +} + +#define assert_values_match(expected, got) __assert_values_match(__FILE__, __LINE__, expected, got) \ No newline at end of file diff --git a/nac3core/irrt/test/print.hpp b/nac3core/irrt/test/print.hpp new file mode 100644 index 00000000..bd0ec746 --- /dev/null +++ b/nac3core/irrt/test/print.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +template +void print_value(const T& value) {} + +template <> +void print_value(const int8_t& value) { + printf("%d", value); +} + +template <> +void print_value(const int32_t& value) { + printf("%d", value); +} + +template <> +void print_value(const uint8_t& value) { + printf("%u", value); +} + +template <> +void print_value(const uint32_t& value) { + printf("%u", value); +} + +template <> +void print_value(const double& value) { + printf("%f", value); +} + +// template +// void print_value(const double& value) { +// printf("%f", value); +// } +// +// template +// void print_value(const char*& value) { +// printf("%f", value); +// } \ No newline at end of file diff --git a/nac3core/irrt/test/test_core.hpp b/nac3core/irrt/test/test_core.hpp new file mode 100644 index 00000000..ec2a5493 --- /dev/null +++ b/nac3core/irrt/test/test_core.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "core.hpp" +#include "../irrt/core.hpp" + +void test_int_exp() { + BEGIN_TEST(); + + assert_values_match(125, __nac3_int_exp_impl(5, 3)); + assert_values_match(3125, __nac3_int_exp_impl(5, 5)); +} \ No newline at end of file diff --git a/nac3core/src/codegen/irrt/mod.rs b/nac3core/src/codegen/irrt/mod.rs index 755bcf57..dfb91611 100644 --- a/nac3core/src/codegen/irrt/mod.rs +++ b/nac3core/src/codegen/irrt/mod.rs @@ -1,5 +1,7 @@ use crate::typecheck::typedef::Type; +mod test; + use super::{ classes::{ ArrayLikeIndexer, ArrayLikeValue, ArraySliceValue, ListValue, NDArrayValue, diff --git a/nac3core/src/codegen/irrt/test.rs b/nac3core/src/codegen/irrt/test.rs new file mode 100644 index 00000000..9a91f41f --- /dev/null +++ b/nac3core/src/codegen/irrt/test.rs @@ -0,0 +1,26 @@ +#[cfg(test)] +mod tests { + use std::{path::Path, process::Command}; + + #[test] + fn run_irrt_test() { + assert!( + cfg!(feature = "test"), + "Please do `cargo test -F test` to compile `irrt_test.out` and run test" + ); + + let irrt_test_out_path = Path::new(concat!(env!("OUT_DIR"), "/irrt_test.out")); + let output = Command::new(irrt_test_out_path.to_str().unwrap()).output().unwrap(); + + if !output.status.success() { + eprintln!("irrt_test failed with status {}:", output.status); + eprintln!("====== stdout ======"); + eprintln!("{}", String::from_utf8(output.stdout).unwrap()); + eprintln!("====== stderr ======"); + eprintln!("{}", String::from_utf8(output.stderr).unwrap()); + eprintln!("===================="); + + panic!("irrt_test failed"); + } + } +}