Browse Source
Add support for implicit type conversions to ISLE. This feature allows the DSL user to register to the compiler that a particular term (used as a constructor or extractor) converts from one type to another. The compiler will then *automatically* insert this term whenever a type mismatch involving that specific pair of types occurs. This significantly cleans up many uses of the ISLE DSL. For example, when defining the compiler backends, we often have newtypes like `Gpr` around `Reg` (signifying a particular type of register); we can define a conversion from Gpr to Reg automatically. Conversions can also have side-effects, as long as these side-effects are idempotent. For example, `put_value_in_reg` in a compiler backend has the effect of marking the value as used, causing codegen to produce it, and assigns a register to the value; but multiple invocations of this will return the same register for the same value. Thus it is safe to use it as an implicit conversion that may be invoked multiple times. This is documented in the ISLE-Cranelift integration document. This PR also adds some testing infrastructure to the ISLE compiler, checking that "pass" tests pass through the DSL compiler, "fail" tests do not, and "link" tests are able to generate code and link that code with corresponding Rust code.pull/3846/head
Chris Fallin
3 years ago
committed by
GitHub
23 changed files with 492 additions and 9 deletions
@ -0,0 +1,33 @@ |
|||
use std::fmt::Write; |
|||
|
|||
fn main() { |
|||
println!("cargo:rerun-if-changed=build.rs"); |
|||
|
|||
let out_dir = std::path::PathBuf::from( |
|||
std::env::var_os("OUT_DIR").expect("The OUT_DIR environment variable must be set"), |
|||
); |
|||
|
|||
let mut out = String::new(); |
|||
|
|||
emit_tests(&mut out, "isle_examples/pass", "run_pass"); |
|||
emit_tests(&mut out, "isle_examples/fail", "run_fail"); |
|||
emit_tests(&mut out, "isle_examples/link", "run_link"); |
|||
|
|||
let output = out_dir.join("isle_tests.rs"); |
|||
std::fs::write(output, out).unwrap(); |
|||
} |
|||
|
|||
fn emit_tests(out: &mut String, dir_name: &str, runner_func: &str) { |
|||
for test_file in std::fs::read_dir(dir_name).unwrap() { |
|||
let test_file = test_file.unwrap().file_name().into_string().unwrap(); |
|||
if !test_file.ends_with(".isle") { |
|||
continue; |
|||
} |
|||
let test_file_base = test_file.replace(".isle", ""); |
|||
|
|||
writeln!(out, "#[test]").unwrap(); |
|||
writeln!(out, "fn test_{}_{}() {{", runner_func, test_file_base).unwrap(); |
|||
writeln!(out, " {}(\"{}/{}\");", runner_func, dir_name, test_file).unwrap(); |
|||
writeln!(out, "}}").unwrap(); |
|||
} |
|||
} |
@ -0,0 +1,10 @@ |
|||
(type T (enum)) |
|||
(type U (enum)) |
|||
|
|||
(decl t_to_u_1 (T) U) |
|||
(decl t_to_u_2 (T) U) |
|||
|
|||
(convert T U t_to_u_1) |
|||
(convert T U t_to_u_2) |
|||
|
|||
(convert T Undef undefined_term) |
@ -0,0 +1,29 @@ |
|||
(type T (enum)) |
|||
(type U (enum)) |
|||
(type V (enum)) |
|||
|
|||
(convert T U t_to_u) |
|||
(convert U V u_to_v) |
|||
|
|||
(decl t_to_u (T) U) |
|||
(decl u_to_v (U) V) |
|||
(decl t_to_v (T) V) |
|||
(decl v_to_t (V) T) |
|||
|
|||
(extern constructor t_to_u t_to_u) |
|||
(extern extractor u_to_v u_to_v) |
|||
(extern constructor t_to_v t_to_v_ctor) |
|||
(extern extractor t_to_v t_to_v_etor) |
|||
(extern extractor v_to_t v_to_u_etor) |
|||
|
|||
;; We should fail to find a converter here. Given only the types, we |
|||
;; might expect u_to_v to be implicitly inserted in the RHS, but |
|||
;; u_to_v has only an extractor, not a constructor, associated. |
|||
(decl Test1 (U) V) |
|||
(rule (Test1 u) u) |
|||
|
|||
;; We should fail to find a converter here. Given only the types, we |
|||
;; might expect t_to_u to be implicitly inserted in the LHS, but t_to_u |
|||
;; has only a constructor, not an extractor, associated. |
|||
(decl Test2 (U) V) |
|||
(rule (Test2 (v_to_t v)) v) |
@ -0,0 +1,28 @@ |
|||
(type T (enum (A) (B))) |
|||
(type U (enum (C) (D))) |
|||
(type V (enum (E) (F))) |
|||
(type u32 (primitive u32)) |
|||
|
|||
(convert T U t_to_u) |
|||
(convert U T u_to_t) |
|||
(convert U V u_to_v) |
|||
|
|||
(decl t_to_u (T) U) |
|||
(decl u_to_t (U) T) |
|||
(decl u_to_v (U) V) |
|||
|
|||
(rule (t_to_u (T.A)) (U.C)) |
|||
(rule (t_to_u (T.B)) (U.D)) |
|||
|
|||
(rule (u_to_t (U.C)) (T.A)) |
|||
(rule (u_to_t (U.D)) (T.B)) |
|||
|
|||
(rule (u_to_v (U.C)) (V.E)) |
|||
(rule (u_to_v (U.D)) (V.F)) |
|||
|
|||
(extern extractor u_to_t u_to_t) |
|||
|
|||
|
|||
(decl X (T U) V) |
|||
(rule (X (U.C) (U.D)) |
|||
(U.D)) |
@ -0,0 +1,54 @@ |
|||
//! Helper for autogenerated unit tests.
|
|||
|
|||
use cranelift_isle::error::Result; |
|||
use cranelift_isle::{compile, lexer, parser}; |
|||
|
|||
fn build(filename: &str) -> Result<String> { |
|||
let lexer = lexer::Lexer::from_files(vec![filename])?; |
|||
let defs = parser::parse(lexer)?; |
|||
compile::compile(&defs) |
|||
} |
|||
|
|||
pub fn run_pass(filename: &str) { |
|||
assert!(build(filename).is_ok()); |
|||
} |
|||
|
|||
pub fn run_fail(filename: &str) { |
|||
assert!(build(filename).is_err()); |
|||
} |
|||
|
|||
pub fn run_link(isle_filename: &str) { |
|||
let tempdir = tempfile::tempdir().unwrap(); |
|||
let code = build(isle_filename).unwrap(); |
|||
|
|||
let isle_filename_base = std::path::Path::new(isle_filename) |
|||
.file_stem() |
|||
.unwrap() |
|||
.to_str() |
|||
.unwrap() |
|||
.to_string(); |
|||
let isle_generated_code = tempdir |
|||
.path() |
|||
.to_path_buf() |
|||
.join(isle_filename_base.clone() + ".rs"); |
|||
std::fs::write(isle_generated_code, code).unwrap(); |
|||
|
|||
let rust_filename = isle_filename.replace(".isle", "").to_string() + "_main.rs"; |
|||
let rust_filename_base = std::path::Path::new(&rust_filename).file_name().unwrap(); |
|||
let rust_driver = tempdir.path().to_path_buf().join(&rust_filename_base); |
|||
println!("copying {} to {:?}", rust_filename, rust_driver); |
|||
std::fs::copy(&rust_filename, &rust_driver).unwrap(); |
|||
|
|||
let output = tempdir.path().to_path_buf().join("out"); |
|||
|
|||
let mut rustc = std::process::Command::new("rustc") |
|||
.arg(&rust_driver) |
|||
.arg("-o") |
|||
.arg(output) |
|||
.spawn() |
|||
.unwrap(); |
|||
assert!(rustc.wait().unwrap().success()); |
|||
} |
|||
|
|||
// Generated by build.rs.
|
|||
include!(concat!(env!("OUT_DIR"), "/isle_tests.rs")); |
Loading…
Reference in new issue