diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml new file mode 100644 index 000000000..67ec50710 --- /dev/null +++ b/.github/workflows/jit.yml @@ -0,0 +1,18 @@ +name: Test JIT + +on: + pull_request: + workflow_dispatch: + +jobs: + test-jit: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Setup Rust + uses: ./.github/actions/setup-rust + with: + rust-version: nightly + targets: x86_64-unknown-linux-gnu + - name: Test with JIT + run: cargo +nightly test --features jit diff --git a/Cargo.lock b/Cargo.lock index 43806baf7..d5217c040 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom", @@ -108,6 +108,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "anyhow" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + [[package]] name = "arrayvec" version = "0.5.2" @@ -480,6 +492,137 @@ dependencies = [ "libc", ] +[[package]] +name = "cranelift" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c23938094c74206f455ac334e8166a2f3e78cfa604933c97925f159621e6061" +dependencies = [ + "cranelift-codegen", + "cranelift-frontend", +] + +[[package]] +name = "cranelift-bforest" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29daf137addc15da6bab6eae2c4a11e274b1d270bf2759508e62f6145e863ef6" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de619867d5de4c644b7fd9904d6e3295269c93d8a71013df796ab338681222d4" +dependencies = [ + "bumpalo", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.14.3", + "log", + "regalloc2", + "rustc-hash", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29f5cf277490037d8dae9513d35e0ee8134670ae4a964a5ed5b198d4249d7c10" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3e22ecad1123343a3c09ac6ecc532bb5c184b6fcb7888df0ea953727f79924" + +[[package]] +name = "cranelift-control" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53ca3ec6d30bce84ccf59c81fead4d16381a3ef0ef75e8403bc1e7385980da09" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eabb8d36b0ca8906bec93c78ea516741cac2d7e6b266fa7b0ffddcc09004990" + +[[package]] +name = "cranelift-frontend" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44b42630229e49a8cfcae90bdc43c8c4c08f7a7aa4618b67f79265cd2f996dd2" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d1e36361805dfe0b6cdfd5a5ffdb5d03fa796170c5717d2727cbe623b93a0" + +[[package]] +name = "cranelift-jit" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399bad3a10a12c475f812dd889b5a5d296eaacc22d4c9652cdefb25dfb9ae56c" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-module", + "cranelift-native", + "libc", + "log", + "region", + "target-lexicon", + "wasmtime-jit-icache-coherence", + "windows-sys 0.52.0", +] + +[[package]] +name = "cranelift-module" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed398b2df2a409a189dbe7950d365b50aa61380cec1f53d9891d4ad8374edb7" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-control", +] + +[[package]] +name = "cranelift-native" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75aea85a0d7e1800b14ce9d3f53adf8ad4d1ee8a9e23b0269bdc50285e93b9b3" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + [[package]] name = "criterion" version = "0.5.1" @@ -831,6 +974,12 @@ dependencies = [ "str-buf", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + [[package]] name = "fastrand" version = "2.0.1" @@ -1053,6 +1202,11 @@ name = "gimli" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +dependencies = [ + "fallible-iterator", + "indexmap 2.1.0", + "stable_deref_trait", +] [[package]] name = "git-version" @@ -1111,6 +1265,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -1543,6 +1706,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "maplit" version = "1.0.2" @@ -2297,6 +2469,19 @@ version = "0.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d813022b2e00774a48eaf43caaa3c20b45f040ba8cbf398e2e8911a06668dbe6" +[[package]] +name = "regalloc2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" +dependencies = [ + "hashbrown 0.13.2", + "log", + "rustc-hash", + "slice-group-by", + "smallvec", +] + [[package]] name = "regex" version = "1.10.2" @@ -2332,6 +2517,18 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "region" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" +dependencies = [ + "bitflags 1.3.2", + "libc", + "mach", + "winapi", +] + [[package]] name = "reqwest" version = "0.11.23" @@ -2434,6 +2631,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" version = "0.38.28" @@ -2567,6 +2770,10 @@ dependencies = [ "chrono", "console_error_panic_hook", "cpu-time", + "cranelift", + "cranelift-codegen", + "cranelift-jit", + "cranelift-module", "criterion", "crossterm", "crrl", @@ -2863,6 +3070,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slice-group-by" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" + [[package]] name = "smallvec" version = "1.11.2" @@ -3075,6 +3288,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "target-lexicon" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" + [[package]] name = "tempfile" version = "3.9.0" @@ -3581,6 +3800,18 @@ version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +[[package]] +name = "wasmtime-jit-icache-coherence" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afe088f9b56bb353adaf837bf7e10f1c2e1676719dd5be4cac8e37f2ba1ee5bc" +dependencies = [ + "anyhow", + "cfg-if", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "web-sys" version = "0.3.66" diff --git a/Cargo.toml b/Cargo.toml index 6753ee013..820d61a0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ build = "build/main.rs" rust-version = "1.77" [lib] -crate-type = ["cdylib", "rlib"] +crate-type = ["cdylib", "rlib", "staticlib"] [features] default = ["ffi", "repl", "hostname", "tls", "http", "crypto-full"] @@ -24,6 +24,7 @@ tls = ["dep:native-tls"] http = ["dep:warp", "dep:reqwest"] rust_beta_channel = [] crypto-full = [] +jit = ["dep:cranelift", "dep:cranelift-jit", "dep:cranelift-module", "dep:cranelift-codegen"] [build-dependencies] indexmap = "1.0.2" @@ -85,6 +86,10 @@ native-tls = { version = "0.2.4", optional = true } reqwest = { version = "0.11.18", optional = true } rustyline = { version = "12.0.0", optional = true } tokio = { version = "1.28.2", features = ["full"] } +cranelift = { version = "0.108.1", optional = true } +cranelift-jit = { version = "0.108.1", optional = true } +cranelift-module = { version = "0.108.1", optional = true } +cranelift-codegen = { version = "0.108.1", optional = true } warp = { version = "=0.3.5", features = ["tls"], optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/build/instructions_template.rs b/build/instructions_template.rs index 86cde2f36..2b227beec 100644 --- a/build/instructions_template.rs +++ b/build/instructions_template.rs @@ -608,6 +608,8 @@ enum SystemClauseType { InferenceLimitExceeded, #[strum_discriminants(strum(props(Arity = "1", Name = "$argv")))] Argv, + #[strum_discriminants(strum(props(Arity = "3", Name = "$jit_compile")))] + JitCompile, REPL(REPLCodePtr), } @@ -1912,6 +1914,7 @@ fn generate_instruction_preface() -> TokenStream { &Instruction::CallAddNonCountedBacktracking | &Instruction::CallPopCount | &Instruction::CallArgv | + &Instruction::CallJitCompile | &Instruction::CallEd25519SignRaw | &Instruction::CallEd25519VerifyRaw | &Instruction::CallEd25519SeedToPublicKey => { @@ -2149,6 +2152,7 @@ fn generate_instruction_preface() -> TokenStream { &Instruction::ExecuteAddNonCountedBacktracking | &Instruction::ExecutePopCount | &Instruction::ExecuteArgv | + &Instruction::ExecuteJitCompile | &Instruction::ExecuteEd25519SignRaw | &Instruction::ExecuteEd25519VerifyRaw | &Instruction::ExecuteEd25519SeedToPublicKey => { diff --git a/src/lib/jit.pl b/src/lib/jit.pl new file mode 100644 index 000000000..d04b2766c --- /dev/null +++ b/src/lib/jit.pl @@ -0,0 +1,11 @@ +:- module(jit, [jit_compile/1]). + +jit_compile(Clause) :- + ( nonvar(Clause) -> + ( Clause = Name / Arity -> + '$jit_compile'(user, Name, Arity) + ; Clause = Module : (Name / Arity) -> + '$jit_compile'(Module, Name, Arity) + ) + ; throw(error(instantiation_error, jit_compile/1)) + ). diff --git a/src/machine/dispatch.rs b/src/machine/dispatch.rs index 7fac89934..b44aa19c1 100644 --- a/src/machine/dispatch.rs +++ b/src/machine/dispatch.rs @@ -4173,6 +4173,14 @@ impl Machine { try_or_throw!(self.machine_st, self.argv()); step_or_fail!(self, self.machine_st.p = self.machine_st.cp); } + &Instruction::CallJitCompile => { + try_or_throw!(self.machine_st, self.jit_compile()); + step_or_fail!(self, self.machine_st.p += 1); + } + &Instruction::ExecuteJitCompile => { + try_or_throw!(self.machine_st, self.jit_compile()); + step_or_fail!(self, self.machine_st.p = self.machine_st.cp); + } &Instruction::CallCurrentTime => { self.current_time(); step_or_fail!(self, self.machine_st.p += 1); diff --git a/src/machine/jit2.rs b/src/machine/jit2.rs new file mode 100644 index 000000000..ece962726 --- /dev/null +++ b/src/machine/jit2.rs @@ -0,0 +1,1752 @@ +// This is another implementation of the JIT compiler +// In this implementation we do not share data between the interpreter and the compiled code +// We do copies before and after the execution + +use crate::instructions::*; +use crate::machine::*; +use crate::parser::ast::*; + +use cranelift::prelude::{*, Value}; +use cranelift_jit::{JITBuilder, JITModule}; +use cranelift_module::{Linkage, Module, FuncId}; +use cranelift_codegen::{ir::stackslot::*, ir::entities::*, Context}; +use cranelift::prelude::codegen::ir::immediates::Offset32; + +use std::collections::HashMap; + +#[derive(Debug, PartialEq)] +pub enum JitCompileError { + UndefinedPredicate, + InstructionNotImplemented, +} + + +pub struct JitMachine { + trampolines: Vec<*const u8>, + module: JITModule, + ctx: Context, + func_ctx: FunctionBuilderContext, + vec_as_ptr: *const u8, + vec_as_ptr_sig: Signature, + vec_push: *const u8, + vec_push_sig: Signature, + vec_len: *const u8, + vec_len_sig: Signature, + vec_truncate: *const u8, + vec_truncate_sig: Signature, + vec_reserve: *const u8, + vec_reserve_sig: Signature, + vec_capacity: *const u8, + vec_capacity_sig: Signature, + print_func: FuncId, + print_func8: FuncId, + vec_pop: FuncId, + predicates: HashMap<(String, usize), JitPredicate>, +} + +pub struct JitPredicate { + code_ptr: *const u8, + func_id: FuncId, +} + +struct Backtrack { + block: Block, + trail_len_at_start: Value, + heap_len_at_start: Value, +} + +impl std::fmt::Debug for JitMachine { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "JitMachine") + } +} + + +impl JitMachine { + pub fn new() -> Self { + let mut builder = JITBuilder::with_flags(&[ + ("preserve_frame_pointers", "true"), + ("enable_llvm_abi_extensions", "1"), + ], + cranelift_module::default_libcall_names() + ).unwrap(); + builder.symbol("print_func", print_syscall as *const u8); + builder.symbol("print_func8", print_syscall8 as *const u8); + builder.symbol("vec_pop", vec_pop_syscall as *const u8); + let mut module = JITModule::new(builder); + let pointer_type = module.isa().pointer_type(); + let call_conv = module.isa().default_call_conv(); + + let mut ctx = module.make_context(); + let mut func_ctx = FunctionBuilderContext::new(); + + let mut sig = module.make_signature(); + sig.params.push(AbiParam::new(pointer_type)); + sig.params.push(AbiParam::new(pointer_type)); + sig.params.push(AbiParam::new(pointer_type)); + sig.params.push(AbiParam::new(pointer_type)); + sig.params.push(AbiParam::new(pointer_type)); + sig.params.push(AbiParam::new(pointer_type)); + sig.returns.push(AbiParam::new(types::I8)); + sig.call_conv = call_conv; + + let mut trampolines = vec![]; + + // Should be MAX_ARITY + for n in 0..4 { + ctx.func.signature = sig.clone(); + let mut fn_builder = FunctionBuilder::new(&mut ctx.func, &mut func_ctx); + let block = fn_builder.create_block(); + fn_builder.append_block_params_for_function_params(block); + fn_builder.switch_to_block(block); + let func_addr = fn_builder.block_params(block)[0]; + let registers = fn_builder.block_params(block)[1]; + let heap = fn_builder.block_params(block)[2]; + let pdl = fn_builder.block_params(block)[3]; + let stack = fn_builder.block_params(block)[4]; + let stack_size = fn_builder.ins().iconst(types::I64, 0); + let trail = fn_builder.block_params(block)[5]; + let mut jump_sig = module.make_signature(); + jump_sig.call_conv = isa::CallConv::Tail; + jump_sig.params.push(AbiParam::new(types::I64)); + jump_sig.params.push(AbiParam::new(types::I64)); + jump_sig.params.push(AbiParam::new(types::I64)); + jump_sig.params.push(AbiParam::new(types::I64)); + jump_sig.params.push(AbiParam::new(types::I64)); + let mut params = vec![]; + params.push(heap); + params.push(pdl); + params.push(stack); + params.push(stack_size); + params.push(trail); + for i in 1..=n { + jump_sig.params.push(AbiParam::new(types::I64)); + // jump_sig.returns.push(AbiParam::new(types::I64)); + let reg_value = fn_builder.ins().load(types::I64, MemFlags::trusted(), registers, Offset32::new((i as i32)*8)); + params.push(reg_value); + } + jump_sig.returns.push(AbiParam::new(types::I8)); + let jump_sig_ref = fn_builder.import_signature(jump_sig); + let jump_call = fn_builder.ins().call_indirect(jump_sig_ref, func_addr, ¶ms); + /*for i in 0..n { + let reg_value = fn_builder.inst_results(jump_call)[i]; + fn_builder.ins().store(MemFlags::trusted(), reg_value, registers, Offset32::new(((i as i32) + 1) * 8)); + }*/ + let fail = fn_builder.inst_results(jump_call)[0]; + fn_builder.ins().return_(&[fail]); + fn_builder.seal_block(block); + fn_builder.finalize(); + + let func = module.declare_function(&format!("$trampoline/{}", n), Linkage::Local, &sig).unwrap(); + module.define_function(func, &mut ctx).unwrap(); + // println!("{}", ctx.func.display()); + module.finalize_definitions().unwrap(); + module.clear_context(&mut ctx); + let code_ptr: *const u8 = unsafe { std::mem::transmute(module.get_finalized_function(func)) }; + trampolines.push(code_ptr); + } + + let mut print_func_sig = module.make_signature(); + print_func_sig.params.push(AbiParam::new(types::I64)); + let print_func = module + .declare_function("print_func", Linkage::Import, &print_func_sig) + .unwrap(); + let mut print_func_sig8 = module.make_signature(); + print_func_sig8.params.push(AbiParam::new(types::I8)); + let print_func8 = module + .declare_function("print_func8", Linkage::Import, &print_func_sig8) + .unwrap(); + + let mut vec_pop_sig = module.make_signature(); + vec_pop_sig.params.push(AbiParam::new(pointer_type)); + vec_pop_sig.returns.push(AbiParam::new(types::I64)); + let vec_pop = module + .declare_function("vec_pop", Linkage::Import, &vec_pop_sig) + .unwrap(); + + let vec_as_ptr = Vec::::as_ptr as *const u8; + let mut vec_as_ptr_sig = module.make_signature(); + vec_as_ptr_sig.params.push(AbiParam::new(pointer_type)); + vec_as_ptr_sig.returns.push(AbiParam::new(pointer_type)); + let vec_push = Vec::::push as *const u8; + let mut vec_push_sig = module.make_signature(); + vec_push_sig.params.push(AbiParam::new(pointer_type)); + vec_push_sig.params.push(AbiParam::new(types::I64)); + let vec_len = Vec::::len as *const u8; + let mut vec_len_sig = module.make_signature(); + vec_len_sig.params.push(AbiParam::new(pointer_type)); + vec_len_sig.returns.push(AbiParam::new(types::I64)); + let vec_truncate = Vec::::truncate as *const u8; + let mut vec_truncate_sig = module.make_signature(); + vec_truncate_sig.params.push(AbiParam::new(pointer_type)); + vec_truncate_sig.params.push(AbiParam::new(types::I64)); + let vec_reserve = Vec::::reserve as *const u8; + let mut vec_reserve_sig = module.make_signature(); + vec_reserve_sig.params.push(AbiParam::new(pointer_type)); + vec_reserve_sig.params.push(AbiParam::new(types::I64)); + let vec_capacity = Vec::::capacity as *const u8; + let mut vec_capacity_sig = module.make_signature(); + vec_capacity_sig.params.push(AbiParam::new(pointer_type)); + vec_capacity_sig.returns.push(AbiParam::new(types::I64)); + + let predicates = HashMap::new(); + JitMachine { + trampolines, + module, + ctx, + func_ctx, + vec_as_ptr, + vec_as_ptr_sig, + vec_push, + vec_push_sig, + vec_len, + vec_len_sig, + vec_truncate, + vec_truncate_sig, + vec_reserve, + vec_reserve_sig, + vec_capacity, + vec_capacity_sig, + print_func, + print_func8, + vec_pop, + predicates + } + } + + pub fn compile(&mut self, name: &str, arity: usize, code: Code) -> Result<(), JitCompileError> { + + let mut sig = self.module.make_signature(); + sig.params.push(AbiParam::new(types::I64)); + sig.params.push(AbiParam::new(types::I64)); + sig.params.push(AbiParam::new(types::I64)); + sig.params.push(AbiParam::new(types::I64)); + sig.params.push(AbiParam::new(types::I64)); + for _ in 1..=arity { + sig.params.push(AbiParam::new(types::I64)); + // sig.returns.push(AbiParam::new(types::I64)); + } + sig.returns.push(AbiParam::new(types::I8)); + sig.call_conv = isa::CallConv::Tail; + self.ctx.func.signature = sig.clone(); + self.ctx.set_disasm(true); + + let mut fn_builder = FunctionBuilder::new(&mut self.ctx.func, &mut self.func_ctx); + let block = fn_builder.create_block(); + fn_builder.append_block_params_for_function_params(block); + fn_builder.switch_to_block(block); + fn_builder.seal_block(block); + + let heap = fn_builder.block_params(block)[0]; + let pdl = fn_builder.block_params(block)[1]; + let stack = fn_builder.block_params(block)[2]; + let stack_size = fn_builder.block_params(block)[3]; + let trail = fn_builder.block_params(block)[4]; + let mode = Variable::new(0); + fn_builder.declare_var(mode, types::I8); + let s = Variable::new(1); + fn_builder.declare_var(s, types::I64); + let e = Variable::new(2); + fn_builder.declare_var(e, types::I64); + + let mut backtracks: Vec = vec![]; + + let mut registers = vec![]; + // TODO: This could be optimized more, we know the maximum register we're using + for i in 1..MAX_ARITY { + if i <= arity { + let reg = fn_builder.block_params(block)[i + 4]; + registers.push(reg); + } else { + let reg = fn_builder.ins().iconst(types::I64, 0); + registers.push(reg); + } + } + + macro_rules! print_rt { + ($x:expr) => { + { + let print_func_fn = self.module.declare_func_in_func(self.print_func, &mut fn_builder.func); + fn_builder.ins().call(print_func_fn, &[$x]); + } + } + } + + macro_rules! print_rt8 { + ($x:expr) => { + { + let print_func_fn = self.module.declare_func_in_func(self.print_func8, &mut fn_builder.func); + fn_builder.ins().call(print_func_fn, &[$x]); + } + } + } + + macro_rules! vec_len { + ($x:expr) => { + {let sig_ref = fn_builder.import_signature(self.vec_len_sig.clone()); + let vec_len_fn = fn_builder.ins().iconst(types::I64, self.vec_len as i64); + let call_vec_len = fn_builder.ins().call_indirect(sig_ref, vec_len_fn, &[$x]); + let vec_len = fn_builder.inst_results(call_vec_len)[0]; + vec_len} + } + } + + macro_rules! vec_pop { + ($x:expr) => { + { + let vec_pop_fn = self.module.declare_func_in_func(self.vec_pop, &mut fn_builder.func); + let call_vec_pop = fn_builder.ins().call(vec_pop_fn, &[$x]); + let value = fn_builder.inst_results(call_vec_pop)[0]; + value + } + } + } + + macro_rules! vec_push { + ($x:expr, $y:expr) => { + { + let sig_ref = fn_builder.import_signature(self.vec_push_sig.clone()); + let vec_push_fn = fn_builder.ins().iconst(types::I64, self.vec_push as i64); + fn_builder.ins().call_indirect(sig_ref, vec_push_fn, &[$x, $y]); + } + } + } + + macro_rules! vec_truncate { + ($x:expr, $y:expr) => { + { + let sig_ref = fn_builder.import_signature(self.vec_truncate_sig.clone()); + let vec_truncate_fn = fn_builder.ins().iconst(types::I64, self.vec_truncate as i64); + fn_builder.ins().call_indirect(sig_ref, vec_truncate_fn, &[$x, $y]); + } + } + } + + macro_rules! vec_reserve { + ($x:expr, $y:expr) => { + { + let sig_ref = fn_builder.import_signature(self.vec_reserve_sig.clone()); + let vec_reserve_fn = fn_builder.ins().iconst(types::I64, self.vec_reserve as i64); + fn_builder.ins().call_indirect(sig_ref, vec_reserve_fn, &[$x, $y]); + } + } + } + + macro_rules! vec_capacity { + ($x:expr, $y:expr) => { + { + let sig_ref = fn_builder.import_signature(self.vec_capacity_sig.clone()); + let vec_capacity_fn = fn_builder.ins().iconst(types::I64, self.vec_capacity as i64); + let call = fn_builder.ins().call_indirect(sig_ref, vec_capacity_fn, &[$x]); + fn_builder.inst_results(call)[0] + } + } + } + + macro_rules! vec_as_ptr { + ($x:expr) => { + { + let sig_ref = fn_builder.import_signature(self.vec_as_ptr_sig.clone()); + let vec_as_ptr_fn = fn_builder.ins().iconst(types::I64, self.vec_as_ptr as i64); + let call_vec_as_ptr = fn_builder.ins().call_indirect(sig_ref, vec_as_ptr_fn, &[$x]); + let heap_ptr = fn_builder.inst_results(call_vec_as_ptr)[0]; + heap_ptr + } + } + } + + /* STORE is an operation that abstracts the access to a data cell. It can return the data cell itself + * or some other data cell if it points to some data cell in the heap + */ + macro_rules! store { + ($x:expr) => { + { + let merge_block = fn_builder.create_block(); + fn_builder.append_block_param(merge_block, types::I64); + let is_var_block = fn_builder.create_block(); + fn_builder.append_block_param(is_var_block, types::I64); + let is_not_var_block = fn_builder.create_block(); + fn_builder.append_block_param(is_not_var_block, types::I64); + let tag = fn_builder.ins().ushr_imm($x, 58); + let is_var = fn_builder.ins().icmp_imm(IntCC::Equal, tag, HeapCellValueTag::Var as i64); + fn_builder.ins().brif(is_var, is_var_block, &[$x], is_not_var_block, &[$x]); + // is_var + fn_builder.switch_to_block(is_var_block); + fn_builder.seal_block(is_var_block); + let param = fn_builder.block_params(is_var_block)[0]; + let heap_ptr = vec_as_ptr!(heap); + let idx = fn_builder.ins().ishl_imm(param, 8); + let idx = fn_builder.ins().ushr_imm(idx, 5); + let idx = fn_builder.ins().iadd(heap_ptr, idx); + let heap_value = fn_builder.ins().load(types::I64, MemFlags::trusted(), idx, Offset32::new(0)); + fn_builder.ins().jump(merge_block, &[heap_value]); + // is_not_var + fn_builder.switch_to_block(is_not_var_block); + fn_builder.seal_block(is_not_var_block); + let param = fn_builder.block_params(is_not_var_block)[0]; + fn_builder.ins().jump(merge_block, &[param]); + // merge + fn_builder.switch_to_block(merge_block); + fn_builder.seal_block(merge_block); + fn_builder.block_params(merge_block)[0] + } + } + } + + /* DEREF is an operation that follows a chain of REF until arriving at a self-referential REF + * (unbounded variable) or something that is not a REF + */ + macro_rules! deref { + ($x:expr) => { + { + let exit_block = fn_builder.create_block(); + fn_builder.append_block_param(exit_block, types::I64); + let loop_block = fn_builder.create_block(); + fn_builder.append_block_param(loop_block, types::I64); + fn_builder.ins().jump(loop_block, &[$x]); + fn_builder.switch_to_block(loop_block); + let addr = fn_builder.block_params(loop_block)[0]; + let value = store!(addr); + // check if is var + let tag = fn_builder.ins().ushr_imm($x, 58); + let is_var = fn_builder.ins().icmp_imm(IntCC::Equal, tag, HeapCellValueTag::Var as i64); + let not_equal = fn_builder.ins().icmp(IntCC::NotEqual, value, addr); + let check = fn_builder.ins().band(is_var, not_equal); + fn_builder.ins().brif(check, loop_block, &[value], exit_block, &[value]); + // exit + fn_builder.seal_block(loop_block); + fn_builder.seal_block(exit_block); + fn_builder.switch_to_block(exit_block); + fn_builder.block_params(exit_block)[0] + + } + } + } + + /* BIND is an operation that takes two data cells, one of them being an unbounded REF / Var + * and makes that REF point the other cell on the heap + */ + macro_rules! bind { + ($x:expr, $y:expr) => { + { + let first_var_block = fn_builder.create_block(); + let else_first_var_block = fn_builder.create_block(); + let exit_block = fn_builder.create_block(); + let second_check_block = fn_builder.create_block(); + let third_check_block = fn_builder.create_block(); + let push_var_y = fn_builder.create_block(); + let push_stackvar_y = fn_builder.create_block(); + let heap_ptr = vec_as_ptr!(heap); + let stack_ptr = vec_as_ptr!(stack); + // The order of HeapCellValue is TAG (6), M (1), F (1), VALUE (56) + let idx = fn_builder.ins().ishl_imm($x, 8); + let idx = fn_builder.ins().ushr_imm(idx, 5); + let idx = fn_builder.ins().iadd(heap_ptr, idx); + let idy = fn_builder.ins().ishl_imm($y, 8); + let idy = fn_builder.ins().ushr_imm(idy, 5); + let heap_idy = fn_builder.ins().iadd(heap_ptr, idy); + let stack_idy = fn_builder.ins().iadd(stack_ptr, idy); + // first case: X is a var, Y is not a stack var, (if Y is a var too, it should be lower) + // second case: else + let x_is_var = is_var!($x); + let y_is_stack_var = is_stack_var!($y); + let y_is_var = is_var!($y); + let y_is_lower_than_x = fn_builder.ins().icmp(IntCC::SignedLessThan, heap_idy, idx); + let check = fn_builder.ins().band_not(x_is_var, y_is_stack_var); + fn_builder.ins().brif(check, second_check_block, &[], else_first_var_block, &[]); + fn_builder.seal_block(second_check_block); + fn_builder.switch_to_block(second_check_block); + fn_builder.ins().brif(y_is_var, third_check_block, &[], first_var_block, &[]); + fn_builder.seal_block(third_check_block); + fn_builder.switch_to_block(third_check_block); + fn_builder.ins().brif(y_is_lower_than_x, first_var_block, &[], else_first_var_block, &[]); + // first var block + fn_builder.seal_block(first_var_block); + fn_builder.seal_block(else_first_var_block); + fn_builder.switch_to_block(first_var_block); + fn_builder.ins().store(MemFlags::trusted(), $y, idx, Offset32::new(0)); + trail!($x); + fn_builder.ins().jump(exit_block, &[]); + // else_first_var_block + fn_builder.switch_to_block(else_first_var_block); + fn_builder.ins().brif(y_is_var, push_var_y, &[], push_stackvar_y, &[]); + fn_builder.seal_block(push_var_y); + fn_builder.seal_block(push_stackvar_y); + fn_builder.switch_to_block(push_var_y); + fn_builder.ins().store(MemFlags::trusted(), $x, heap_idy, Offset32::new(0)); + trail!($y); + fn_builder.ins().jump(exit_block, &[]); + fn_builder.switch_to_block(push_stackvar_y); + fn_builder.ins().store(MemFlags::trusted(), $x, stack_idy, Offset32::new(0)); + trail!($y); + fn_builder.ins().jump(exit_block, &[]); + // exit + fn_builder.seal_block(exit_block); + fn_builder.switch_to_block(exit_block); + } + } + } + + // TODO: Manage stack vars + // Stack vars are always cleaned. If there was an allocation after a choicepoint, they need to be removed. If there was + // an allocation before a choicepoint, the vars used need to be cleaned up. + // As we now allocate everything at the beginning, we can just clean everything at RetryMeElse and TrustMe + // However, we need to differentiate StackVar from Vars + macro_rules! trail { + ($x:expr) => { + if !backtracks.is_empty() { + let push_var = fn_builder.create_block(); + let check_push_stack_var = fn_builder.create_block(); + let exit = fn_builder.create_block(); + let current_frame = backtracks.get(backtracks.len() - 1).unwrap(); + let idx = fn_builder.ins().ishl_imm($x, 8); + let idx = fn_builder.ins().ushr_imm(idx, 8); + let var_is_older = fn_builder.ins().icmp(IntCC::SignedLessThan, idx, current_frame.heap_len_at_start); + let x_is_var = is_var!($x); + let var_is_older_and_var = fn_builder.ins().band(var_is_older, x_is_var); + fn_builder.ins().brif(var_is_older_and_var, push_var, &[], check_push_stack_var, &[]); + fn_builder.seal_block(check_push_stack_var); + fn_builder.switch_to_block(push_var); + vec_push!(trail, $x); + fn_builder.ins().jump(exit, &[]); + fn_builder.switch_to_block(check_push_stack_var); + let var_is_older = fn_builder.ins().icmp(IntCC::SignedLessThan, idx, stack_size); + fn_builder.ins().brif(var_is_older, push_var, &[], exit, &[]); + fn_builder.seal_block(push_var); + fn_builder.seal_block(exit); + fn_builder.switch_to_block(exit); + } + } + } + + macro_rules! unwind_trail { + () => { + { + if !backtracks.is_empty() { + let heap_ptr = vec_as_ptr!(heap); + let stack_ptr = vec_as_ptr!(stack); + let current_frame = backtracks.get(backtracks.len() - 1).unwrap(); + let trail_len_at_start = current_frame.trail_len_at_start; + let trail_len_now = vec_len!(trail); + let num_items = fn_builder.ins().isub(trail_len_now, trail_len_at_start); + let check_loop = fn_builder.create_block(); + fn_builder.append_block_param(check_loop, types::I64); + let exit = fn_builder.create_block(); + let loop_body = fn_builder.create_block(); + fn_builder.ins().jump(check_loop, &[num_items]); + fn_builder.switch_to_block(check_loop); + let num_items = fn_builder.block_params(check_loop)[0]; + let is_zero = fn_builder.ins().icmp_imm(IntCC::Equal, num_items, 0); + fn_builder.ins().brif(is_zero, exit, &[], loop_body, &[]); + fn_builder.seal_block(exit); + fn_builder.seal_block(loop_body); + fn_builder.switch_to_block(loop_body); + // unwind here + let cell = vec_pop!(trail); + let is_var = is_var!(cell); + let restore_var = fn_builder.create_block(); + fn_builder.append_block_param(restore_var, types::I64); + let restore_stack_var = fn_builder.create_block(); + fn_builder.append_block_param(restore_stack_var, types::I64); + fn_builder.ins().brif(is_var, restore_var, &[num_items], restore_stack_var, &[num_items]); + fn_builder.seal_block(restore_var); + fn_builder.seal_block(restore_stack_var); + fn_builder.switch_to_block(restore_var); + let num_items = fn_builder.block_params(restore_var)[0]; + let idx = fn_builder.ins().ishl_imm(cell, 8); + let idx = fn_builder.ins().ushr_imm(idx, 5); + let idx = fn_builder.ins().iadd(heap_ptr, idx); + fn_builder.ins().store(MemFlags::trusted(), cell, idx, Offset32::new(0)); + let num_items = fn_builder.ins().iadd_imm(num_items, -1); + fn_builder.ins().jump(check_loop, &[num_items]); + fn_builder.switch_to_block(restore_stack_var); + let num_items = fn_builder.block_params(restore_stack_var)[0]; + let idx = fn_builder.ins().ishl_imm(cell, 8); + let idx = fn_builder.ins().ushr_imm(idx, 5); + let idx = fn_builder.ins().iadd(stack_ptr, idx); + fn_builder.ins().store(MemFlags::trusted(), cell, idx, Offset32::new(0)); + let num_items = fn_builder.ins().iadd_imm(num_items, -1); + fn_builder.ins().jump(check_loop, &[num_items]); + fn_builder.seal_block(check_loop); + fn_builder.switch_to_block(exit); + } + } + } + } + + macro_rules! is_var { + ($x:expr) => { + { + let tag = fn_builder.ins().ushr_imm($x, 58); + fn_builder.ins().icmp_imm(IntCC::Equal, tag, HeapCellValueTag::Var as i64) + } + } + } + + macro_rules! is_stack_var { + ($x:expr) => { + { + let tag = fn_builder.ins().ushr_imm($x, 58); + fn_builder.ins().icmp_imm(IntCC::Equal, tag, HeapCellValueTag::StackVar as i64) + } + } + } + + macro_rules! is_str { + ($x:expr) => { + { + let tag = fn_builder.ins().ushr_imm($x, 58); + fn_builder.ins().icmp_imm(IntCC::Equal, tag, HeapCellValueTag::Str as i64) + } + } + } + + macro_rules! unify { + ($x:expr, $y:expr) => { + { + let exit = fn_builder.create_block(); + fn_builder.append_block_param(exit, types::I8); + + // start unification + vec_push!(pdl, $x); + vec_push!(pdl, $y); + let pdl_size = fn_builder.ins().iconst(types::I64, 2); + let fail_status = fn_builder.ins().iconst(types::I8, 0); + + let pre_loop_block = fn_builder.create_block(); + fn_builder.append_block_param(pre_loop_block, types::I64); // pdl_size + fn_builder.append_block_param(pre_loop_block, types::I8); // fail_status + fn_builder.ins().jump(pre_loop_block, &[pdl_size, fail_status]); + fn_builder.switch_to_block(pre_loop_block); + // pre_loop_block + let pdl_size = fn_builder.block_params(pre_loop_block)[0]; + let fail_status = fn_builder.block_params(pre_loop_block)[1]; + let pdl_size_is_zero = fn_builder.ins().icmp_imm(IntCC::Equal, pdl_size, 0); + let fail_status_is_true = fn_builder.ins().icmp_imm(IntCC::NotEqual, fail_status, 0); + let loop_check = fn_builder.ins().bor(pdl_size_is_zero, fail_status_is_true); + let loop_block = fn_builder.create_block(); + fn_builder.append_block_param(loop_block, types::I64); // pdl_size + fn_builder.append_block_param(loop_block, types::I8); // fail_status + + fn_builder.ins().brif(loop_check, exit, &[fail_status], loop_block, &[pdl_size, fail_status]); + fn_builder.seal_block(exit); + fn_builder.seal_block(loop_block); + fn_builder.switch_to_block(loop_block); + // loop_block + let pdl_size = fn_builder.block_params(loop_block)[0]; + let fail_status = fn_builder.block_params(loop_block)[1]; + let d1 = vec_pop!(pdl); + let d2 = vec_pop!(pdl); + let d1 = deref!(d1); + let d2 = deref!(d2); + let pdl_size = fn_builder.ins().iadd_imm(pdl_size, -2); + let are_equal = fn_builder.ins().icmp(IntCC::Equal, d1, d2); + let unify_two_unequal_cells = fn_builder.create_block(); + fn_builder.append_block_param(unify_two_unequal_cells, types::I64); + fn_builder.append_block_param(unify_two_unequal_cells, types::I8); + + fn_builder.ins().brif(are_equal, pre_loop_block, &[pdl_size, fail_status], unify_two_unequal_cells, &[pdl_size, fail_status]); + fn_builder.seal_block(unify_two_unequal_cells); + + // unify_two_unequal_cells + fn_builder.switch_to_block(unify_two_unequal_cells); + let pdl_size = fn_builder.block_params(unify_two_unequal_cells)[0]; + let fail_status = fn_builder.block_params(unify_two_unequal_cells)[1]; + let is_var_d1 = is_var!(d1); + let is_var_d2 = is_var!(d2); + let any_is_var = fn_builder.ins().bor(is_var_d1, is_var_d2); + let bind_var = fn_builder.create_block(); + fn_builder.append_block_param(bind_var, types::I64); + fn_builder.append_block_param(bind_var, types::I8); + let unify_str = fn_builder.create_block(); + fn_builder.append_block_param(unify_str, types::I64); + fn_builder.append_block_param(unify_str, types::I8); + fn_builder.ins().brif(any_is_var, bind_var, &[pdl_size, fail_status], unify_str, &[pdl_size, fail_status]); + fn_builder.seal_block(bind_var); + fn_builder.seal_block(unify_str); + + // bind_var + fn_builder.switch_to_block(bind_var); + let pdl_size = fn_builder.block_params(bind_var)[0]; + let fail_status = fn_builder.block_params(bind_var)[1]; + bind!(d1, d2); + fn_builder.ins().jump(pre_loop_block, &[pdl_size, fail_status]); + + // unify_str + fn_builder.switch_to_block(unify_str); + let pdl_size = fn_builder.block_params(unify_str)[0]; + let fail_status = fn_builder.block_params(unify_str)[1]; + let heap_ptr = vec_as_ptr!(heap); + let idx1 = fn_builder.ins().ishl_imm(d1, 8); + let idx1 = fn_builder.ins().ushr_imm(idx1, 5); + let idx1 = fn_builder.ins().iadd(heap_ptr, idx1); + let d1 = fn_builder.ins().load(types::I64, MemFlags::trusted(), idx1, Offset32::new(0)); + let idx2 = fn_builder.ins().ishl_imm(d2, 8); + let idx2 = fn_builder.ins().ushr_imm(idx2, 5); + let idx2 = fn_builder.ins().iadd(heap_ptr, idx2); + let d2 = fn_builder.ins().load(types::I64, MemFlags::trusted(), idx2, Offset32::new(0)); + let are_atom_arity_equal = fn_builder.ins().icmp(IntCC::Equal, d1, d2); + let fail_status_bad = fn_builder.ins().iconst(types::I8, 1); + let pre_push_subterms = fn_builder.create_block(); + fn_builder.append_block_param(pre_push_subterms, types::I64); + fn_builder.append_block_param(pre_push_subterms, types::I64); + fn_builder.append_block_param(pre_push_subterms, types::I8); + let arity = fn_builder.ins().ishl_imm(d1, 8); + let arity = fn_builder.ins().ushr_imm(arity, 54); + fn_builder.ins().brif(are_atom_arity_equal, pre_push_subterms, &[arity, pdl_size, fail_status], pre_loop_block, &[pdl_size, fail_status_bad]); + + // pre_push_subterms + fn_builder.switch_to_block(pre_push_subterms); + let arity = fn_builder.block_params(pre_push_subterms)[0]; + let pdl_size = fn_builder.block_params(pre_push_subterms)[1]; + let fail_status = fn_builder.block_params(pre_push_subterms)[2]; + let zero_remaining = fn_builder.ins().icmp_imm(IntCC::Equal, arity, 0); + let push_subterms = fn_builder.create_block(); + fn_builder.append_block_param(push_subterms, types::I64); + fn_builder.append_block_param(push_subterms, types::I64); + fn_builder.append_block_param(push_subterms, types::I8); + fn_builder.ins().brif(zero_remaining, pre_loop_block, &[pdl_size, fail_status], push_subterms, &[arity, pdl_size, fail_status]); + fn_builder.seal_block(pre_loop_block); + fn_builder.seal_block(push_subterms); + // push_subterms + fn_builder.switch_to_block(push_subterms); + let d1_next = fn_builder.ins().iadd_imm(idx1, 8); + let d1_next = fn_builder.ins().iadd(heap_ptr, d1_next); + let d1_next = fn_builder.ins().load(types::I64, MemFlags::trusted(), d1_next, Offset32::new(0)); + vec_push!(pdl, d1_next); + let d2_next = fn_builder.ins().iadd_imm(idx2, 8); + let d2_next = fn_builder.ins().iadd(heap_ptr, d2_next); + let d2_next = fn_builder.ins().load(types::I64, MemFlags::trusted(), d2_next, Offset32::new(0)); + vec_push!(pdl, d2_next); + let pdl_size = fn_builder.ins().iadd_imm(pdl_size, 2); + let arity = fn_builder.ins().iadd_imm(arity, -1); + fn_builder.ins().jump(pre_push_subterms, &[arity, pdl_size, fail_status]); + fn_builder.seal_block(pre_push_subterms); + + // exit + fn_builder.switch_to_block(exit); + let fail_status = fn_builder.block_params(exit)[0]; + let exit_early = fn_builder.create_block(); + let exit_normal = fn_builder.create_block(); + fn_builder.ins().brif(fail_status, exit_early, &[], exit_normal, &[]); + fn_builder.seal_block(exit_early); + fn_builder.seal_block(exit_normal); + fn_builder.switch_to_block(exit_early); + fn_builder.ins().return_(&[fail_status]); + fn_builder.switch_to_block(exit_normal); + } + } + } + + macro_rules! read_reg { + ($x:expr) => { + { + match $x { + RegType::Temp(x) => { + registers[x-1] + } + RegType::Perm(y) => { + let idy = ((y as i32) - 1) * 8; + let stack_frame = fn_builder.use_var(e); + fn_builder.ins().load(types::I64, MemFlags::trusted(), stack_frame, Offset32::new(idy)) + } + } + } + } + } + + macro_rules! write_reg { + ($x:expr, $y:expr) => { + match $x { + RegType::Temp(x) => { + registers[x-1] = $y; + } + RegType::Perm(y) => { + let idy = ((y as i32) - 1) * 8; + let stack_frame = fn_builder.use_var(e); + fn_builder.ins().store(MemFlags::trusted(), $y, stack_frame, Offset32::new(idy)); + } + } + } + } + + // reserve all allocations at once + let mut allocation_size: i64 = 0; + for wam_instr in &code { + if let Instruction::Allocate(n) = wam_instr { + allocation_size = i64::max(allocation_size, *n as i64); + } + } + + let allocation_size_value = fn_builder.ins().iconst(types::I64, allocation_size); + let new_stack_size = fn_builder.ins().iadd(stack_size, allocation_size_value); + vec_reserve!(stack, new_stack_size); + let stack_ptr = vec_as_ptr!(stack); + let stack_size_bytes = fn_builder.ins().imul_imm(stack_size, 8); + let env_ptr = fn_builder.ins().iadd(stack_ptr, stack_size_bytes); + fn_builder.def_var(e, env_ptr); + + for wam_instr in code { + match wam_instr { + /* put_structure is an instruction that puts a new STR in the heap + * (STR cell, plus functor + arity cell) + * It also saves the STR cell into a register + */ + Instruction::PutStructure(name, arity, reg) => { + let str_cell = fn_builder.ins().iconst(types::I64, i64::from_le_bytes(str_loc_as_cell!(0).into_bytes())); + let vec_len = vec_len!(heap); + let str_loc = fn_builder.ins().iadd_imm(vec_len, 1); + let str_cell = fn_builder.ins().bor(str_loc, str_cell); + + let atom_cell = atom_as_cell!(name, arity); + let atom = fn_builder.ins().iconst(types::I64, i64::from_le_bytes(atom_cell.into_bytes())); + vec_push!(heap, str_cell); + vec_push!(heap, atom); + + write_reg!(reg, str_cell); + } + /* set_variable is an instruction that creates a new self-referential REF + * (unbounded variable) in the heap and saves that into a register + */ + Instruction::SetVariable(reg) => { + let heap_loc_cell = heap_loc_as_cell!(0); + let heap_loc_cell = fn_builder.ins().iconst(types::I64, i64::from_le_bytes(heap_loc_cell.into_bytes())); + let vec_len = vec_len!(heap); + let heap_loc_cell = fn_builder.ins().bor(vec_len, heap_loc_cell); + vec_push!(heap, heap_loc_cell); + write_reg!(reg, heap_loc_cell); + } + /* set_value is an instruction that pushes a new data cell from a register to the heap + */ + Instruction::SetValue(reg) => { + let value = read_reg!(reg); + let value = store!(value); + vec_push!(heap, value); + } + /* get_structure is an instruction that either matches an existing STR or starts writing a new + * STR into the heap. If the cell passed via register is an unbounded REF, we start WRITE mode + * and unify_variable, unify_value behave similar to set_variable, set_value. + * If it's an existing STR, we check functor and arity, set S pointer and start READ mode, + * in that mod unify_variable and unify_value will follow the unification procedure + */ + Instruction::GetStructure(_lvl, name, arity, reg) => { + let xi = read_reg!(reg); + let deref = deref!(xi); + let store = store!(deref); + + let is_var_block = fn_builder.create_block(); + let else_is_var_block = fn_builder.create_block(); + let is_str_block = fn_builder.create_block(); + let start_read_mode_block = fn_builder.create_block(); + let fail_block = fn_builder.create_block(); + let exit_block = fn_builder.create_block(); + let is_var = is_var!(store); + + fn_builder.ins().brif(is_var, is_var_block, &[], else_is_var_block, &[]); + fn_builder.seal_block(is_var_block); + fn_builder.seal_block(else_is_var_block); + // is_var_block + fn_builder.switch_to_block(is_var_block); + let sig_ref = fn_builder.import_signature(self.vec_push_sig.clone()); + let vec_push_fn = fn_builder.ins().iconst(types::I64, self.vec_push as i64); + let str_cell = fn_builder.ins().iconst(types::I64, i64::from_le_bytes(str_loc_as_cell!(0).into_bytes())); + let vec_len = vec_len!(heap); + let str_cell = fn_builder.ins().bor(vec_len, str_cell); + let atom_cell = atom_as_cell!(name, arity); + let atom = fn_builder.ins().iconst(types::I64, i64::from_le_bytes(atom_cell.into_bytes())); + fn_builder.ins().call_indirect(sig_ref, vec_push_fn, &[heap, atom]); + bind!(store, str_cell); + let mode_value = fn_builder.ins().iconst(types::I8, 1); + fn_builder.def_var(mode, mode_value); + fn_builder.ins().jump(exit_block, &[]); + + // else_is_var_block + fn_builder.switch_to_block(else_is_var_block); + let is_str = is_str!(store); + fn_builder.ins().brif(is_str, is_str_block, &[], fail_block, &[]); + fn_builder.seal_block(is_str_block); + + // is_str_block + fn_builder.switch_to_block(is_str_block); + let atom = fn_builder.ins().iconst(types::I64, i64::from_le_bytes(atom_cell.into_bytes())); + let heap_ptr = vec_as_ptr!(heap); + let idx = fn_builder.ins().ishl_imm(store, 8); + let idx = fn_builder.ins().ushr_imm(idx, 5); + let idx = fn_builder.ins().iadd(heap_ptr, idx); + let heap_value = fn_builder.ins().load(types::I64, MemFlags::trusted(), idx, Offset32::new(0)); + let result = fn_builder.ins().icmp(IntCC::Equal, heap_value, atom); + fn_builder.ins().brif(result, start_read_mode_block, &[], fail_block, &[]); + fn_builder.seal_block(start_read_mode_block); + fn_builder.seal_block(fail_block); + + // start_read_mode_block + fn_builder.switch_to_block(start_read_mode_block); + let s_ptr = fn_builder.ins().iadd_imm(idx, 8); + fn_builder.def_var(s, s_ptr); + let mode_value = fn_builder.ins().iconst(types::I8, 0); + fn_builder.def_var(mode, mode_value); + fn_builder.ins().jump(exit_block, &[]); + + // fail_block + fn_builder.switch_to_block(fail_block); + let fail_value = fn_builder.ins().iconst(types::I8, 1); + fn_builder.ins().return_(&[fail_value]); + + // exit_block + fn_builder.seal_block(exit_block); + fn_builder.switch_to_block(exit_block); + + } + // TODO: Missing support for PStr and CStr + /* unify_variable is an instruction that in WRITE mode is identical to set_variable but + * in READ mode it reads the data cell from the S pointer to a register + */ + Instruction::UnifyVariable(reg) => { + let read_block = fn_builder.create_block(); + let write_block = fn_builder.create_block(); + let exit_block = fn_builder.create_block(); + let mode_value = fn_builder.use_var(mode); + fn_builder.ins().brif(mode_value, write_block, &[], read_block, &[]); + fn_builder.seal_block(read_block); + fn_builder.seal_block(write_block); + // read + fn_builder.switch_to_block(read_block); + let s_value = fn_builder.use_var(s); + let value = fn_builder.ins().load(types::I64, MemFlags::trusted(), s_value, Offset32::new(0)); + let value = deref!(value); + write_reg!(reg, value); + let sum_s = fn_builder.ins().iadd_imm(s_value, 8); + fn_builder.def_var(s, sum_s); + fn_builder.ins().jump(exit_block, &[]); + // write (equal to SetVariable) + fn_builder.switch_to_block(write_block); + let heap_loc_cell = heap_loc_as_cell!(0); + let heap_loc_cell = fn_builder.ins().iconst(types::I64, i64::from_le_bytes(heap_loc_cell.into_bytes())); + let vec_len = vec_len!(heap); + let heap_loc_cell = fn_builder.ins().bor(vec_len, heap_loc_cell); + vec_push!(heap, heap_loc_cell); + write_reg!(reg, heap_loc_cell); + fn_builder.ins().jump(exit_block, &[]); + // exit + fn_builder.switch_to_block(exit_block); + fn_builder.seal_block(exit_block); + + } + /* unify_value is an instruction that on WRITE mode behaves like set_value, and in READ mode + * executes unification + */ + Instruction::UnifyValue(reg) => { + let reg = read_reg!(reg); + let read_block = fn_builder.create_block(); + let write_block = fn_builder.create_block(); + let exit_block = fn_builder.create_block(); + let mode_value = fn_builder.use_var(mode); + fn_builder.ins().brif(mode_value, write_block, &[], read_block, &[]); + fn_builder.seal_block(read_block); + fn_builder.seal_block(write_block); + // read + fn_builder.switch_to_block(read_block); + let s_value = fn_builder.use_var(s); + let value = fn_builder.ins().load(types::I64, MemFlags::trusted(), s_value, Offset32::new(0)); + unify!(reg, value); + let s_value = fn_builder.ins().iadd_imm(s_value, 8); + fn_builder.def_var(s, s_value); + fn_builder.ins().jump(exit_block, &[]); + // write + fn_builder.switch_to_block(write_block); + vec_push!(heap, reg); + fn_builder.ins().jump(exit_block, &[]); + fn_builder.seal_block(exit_block); + + // exit + fn_builder.switch_to_block(exit_block); + + } + /* put_variable works similar to set_variable, but it stores the cell in two registers, + * Xi normal register and Ai argument register + */ + Instruction::PutVariable(reg, arg) => { + match reg { + RegType::Temp(_) => { + let heap_loc_cell = heap_loc_as_cell!(0); + let heap_loc_cell = fn_builder.ins().iconst(types::I64, i64::from_le_bytes(heap_loc_cell.into_bytes())); + let vec_len = vec_len!(heap); + let heap_loc_cell = fn_builder.ins().bor(vec_len, heap_loc_cell); + vec_push!(heap, heap_loc_cell); + write_reg!(reg, heap_loc_cell); + registers[arg - 1] = heap_loc_cell; + } + RegType::Perm(y) => { + let stack_loc_cell = stack_loc_as_cell!(y - 1); + let stack_loc_cell = fn_builder.ins().iconst(types::I64, i64::from_le_bytes(stack_loc_cell.into_bytes())); + write_reg!(reg, stack_loc_cell); + registers[arg - 1] = stack_loc_cell; + } + } + } + /* put_value moves the content from Xi to Ai + */ + Instruction::PutValue(reg, arg) => { + registers[arg - 1] = read_reg!(reg); + } + /* get_variable moves the content from Ai to Xi + */ + Instruction::GetVariable(reg, arg) => { + write_reg!(reg, registers[arg - 1]); + } + /* get_value perform unification between Xi and Ai + */ + Instruction::GetValue(reg1, reg2) => { + let reg1 = read_reg!(reg1); + let reg2 = registers[reg2 - 1]; + unify!(reg1, reg2); + } + /* call executes another predicate in a normal way. It passes all the argument registers + * as function arguments + */ + Instruction::CallNamed(arity, name, _ ) => { +let Some(predicate) = self.predicates.get(&(name.as_str().to_string(), arity)) else { + return Err(JitCompileError::UndefinedPredicate); +}; + let func = self.module.declare_func_in_func(predicate.func_id, fn_builder.func); + let mut args = vec![]; + args.push(heap); + args.push(pdl); + args.push(stack); + args.push(new_stack_size); + args.push(trail); + for i in 1..=arity { + args.push(registers[i-1]); + } + let call = fn_builder.ins().call(func, &args); + let fail_status = fn_builder.inst_results(call)[0]; + let exit_early = fn_builder.create_block(); + let resume = fn_builder.create_block(); + fn_builder.ins().brif(fail_status, exit_early, &[], resume, &[]); + fn_builder.seal_block(exit_early); + fn_builder.seal_block(resume); + fn_builder.switch_to_block(exit_early); + if backtracks.is_empty() { + fn_builder.ins().return_(&[fail_status]); + } else { + //let last_backtrack = backtracks[backtracks.len() -1 ]; + // TODO: Clean TRAIL + //fn_builder.ins().jump(last_backtrack.next_block, &[]); + } + fn_builder.switch_to_block(resume); + } + /* execute does a tail call instead of a normal call + */ + Instruction::ExecuteNamed(arity, name, _) => { + let Some(predicate) = self.predicates.get(&(name.as_str().to_string(), arity)) else { + return Err(JitCompileError::UndefinedPredicate); + }; + let func = self.module.declare_func_in_func(predicate.func_id, fn_builder.func); + let mut args = vec![]; + args.push(heap); + args.push(pdl); + args.push(stack); + args.push(stack_size); + args.push(trail); + for i in 1..=arity { + args.push(registers[i-1]); + } + if backtracks.is_empty() { + // we can only optimize tail calls if there's no backtracking in this level + fn_builder.ins().return_call(func, &args); + } else { + // TODO CLEAN TRAIL + let backtrack = backtracks.get(backtracks.len() -1 ).unwrap(); + let call = fn_builder.ins().call(func, &args); + let fail_status = fn_builder.inst_results(call)[0]; + let exit_normal = fn_builder.create_block(); + fn_builder.ins().brif(fail_status, backtrack.block, &[], exit_normal, &[]); + fn_builder.seal_block(exit_normal); + fn_builder.switch_to_block(exit_normal); + let fail_value = fn_builder.ins().iconst(types::I8, 0); + // if we exit here, it's because it succeeded + fn_builder.ins().return_(&[fail_value]); + } + } + /* allocate creates a new environment frame (ANDFrame). Every frame contains a pointer + * to the previous frame start, the continuation pointer (in this case we do not store it + * as we have a tree of calls with Cranelift managing this for us), the number of permanent + * variables and the permanent variables themselves. + */ + Instruction::Allocate(_n) => { + + } + /* deallocate restores the previous frame, freeing the current frame + */ + Instruction::Deallocate => { + + } + // TODO: manage NonVar cases + // TODO: Manage unification case + // TODO: manage STORE[addr] is not REF + Instruction::GetConstant(_, c, reg) => { + let value = read_reg!(reg); + let value = deref!(value); + let value = store!(value); + // The order of HeapCellValue is TAG (6), M (1), F (1), VALUE (56) + let c = fn_builder.ins().iconst(types::I64, i64::from_le_bytes(c.into_bytes())); + bind!(value, c); + } + Instruction::Proceed => { + // do we really need to return registers? + //fn_builder.ins().return_(®isters[0..arity]); + let fail_value = fn_builder.ins().iconst(types::I8, 0); + // if we exit here, it's because it succeeded + fn_builder.ins().return_(&[fail_value]); + break; + }, + Instruction::TryMeElse(_offset) => { + let block = fn_builder.create_block(); + let heap_len_at_start = vec_len!(heap); + let trail_len_at_start = vec_len!(trail); + backtracks.push(Backtrack { + block, + heap_len_at_start, + trail_len_at_start, + }); + } + Instruction::RetryMeElse(_offset) => { + let continuation = backtracks.get(backtracks.len() - 1).unwrap(); + fn_builder.seal_block(continuation.block); + fn_builder.switch_to_block(continuation.block); + let heap_len_at_start = continuation.heap_len_at_start; + let trail_len_at_start = continuation.trail_len_at_start; + unwind_trail!(); + backtracks.truncate(backtracks.len() - 1); + let block = fn_builder.create_block(); + backtracks.push(Backtrack { + block, + heap_len_at_start, + trail_len_at_start, + }); + } + Instruction::TrustMe(_) => { + let continuation = backtracks.get(backtracks.len() - 1).unwrap(); + fn_builder.seal_block(continuation.block); + fn_builder.switch_to_block(continuation.block); + unwind_trail!(); + backtracks.truncate(backtracks.len() - 1); + } + _ => { + dbg!(wam_instr); + fn_builder.finalize(); + self.module.clear_context(&mut self.ctx); + return Err(JitCompileError::InstructionNotImplemented); + } + } + } + fn_builder.seal_all_blocks(); + fn_builder.finalize(); + + let func_id = self.module.declare_function(&format!("{}/{}", name, arity), Linkage::Local, &sig).unwrap(); + self.module.define_function(func_id, &mut self.ctx).unwrap(); + println!("{}", self.ctx.func.display()); + self.module.finalize_definitions().unwrap(); + let code_ptr = self.module.get_finalized_function(func_id); + self.predicates.insert((name.to_string(), arity), JitPredicate { + code_ptr, + func_id + }); + println!("{}", self.ctx.compiled_code().unwrap().vcode.clone().unwrap()); + self.module.clear_context(&mut self.ctx); + Ok(()) + } + + pub fn exec(&self, name: &str, arity: usize, machine_st: &mut MachineState) -> Result<(), ()> { + let Some(predicate) = self.predicates.get(&(name.to_string(), arity)) else { + return Err(()); + }; + let trampoline: extern "C" fn (*const u8, *mut Registers, *const Vec, *const Vec, *const Vec, *const Vec) -> u8 = unsafe { std::mem::transmute(self.trampolines[arity])}; + let registers = machine_st.registers.as_ptr() as *mut Registers; + let heap = &machine_st.heap as *const Vec; + let pdl = &machine_st.pdl as *const Vec; + let stack_vec: Vec = Vec::with_capacity(1024); + let stack = &stack_vec as *const Vec; + let trail_vec: Vec = Vec::with_capacity(1024); + let trail = &trail_vec as *const Vec; + let fail = trampoline(predicate.code_ptr, registers, heap, pdl, stack, trail); + machine_st.p = machine_st.cp; + machine_st.fail = if fail == 1 { + true + } else { + false + }; + Ok(()) + } +} + + +fn print_syscall(value: i64) { + println!("{}", value); +} + +fn print_syscall8(value: i8) { + println!("{}", value); +} + +fn vec_pop_syscall(value: &mut Vec) -> HeapCellValue { + value.pop().unwrap() +} + + +#[test] +fn test_put_structure() { + let code = vec![ + Instruction::PutStructure(atom!("f"), 2, RegType::Temp(1)), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("test_put_structure", 1, code).unwrap(); + let mut machine_st = MachineState::new(); + jit.exec("test_put_structure", 1, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(str_loc_as_cell!(1)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 2)); + // machine_st_expected.registers[1] = str_loc_as_cell!(0); + assert_eq!(machine_st.heap, machine_st_expected.heap); + // assert_eq!(machine_st.registers, machine_st_expected.registers); + assert_eq!(machine_st.fail, false); +} + +#[test] +fn test_set_variable() { + let code = vec![ + Instruction::SetVariable(RegType::Temp(1)), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("test_set_variable", 1, code).unwrap(); + let mut machine_st = MachineState::new(); + jit.exec("test_set_variable", 1, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(heap_loc_as_cell!(0)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + // assert_eq!(machine_st.registers, machine_st_expected.registers); + assert_eq!(machine_st.fail, false); +} + +#[test] +fn test_set_value() { + let code = vec![ + Instruction::SetValue(RegType::Temp(1)), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("test_set_value", 1, code).unwrap(); + let mut machine_st = MachineState::new(); + machine_st.registers[1] = atom_as_cell!(atom!("a"), 0); + jit.exec("test_set_value", 1, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(atom_as_cell!(atom!("a"), 0)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); +} + +#[test] +fn test_fig23_wambook() { + let code = vec![ + Instruction::PutStructure(atom!("h"), 2, RegType::Temp(3)), + Instruction::SetVariable(RegType::Temp(2)), + Instruction::SetVariable(RegType::Temp(5)), + Instruction::PutStructure(atom!("f"), 1, RegType::Temp(4)), + Instruction::SetValue(RegType::Temp(5)), + Instruction::PutStructure(atom!("p"), 3, RegType::Temp(1)), + Instruction::SetValue(RegType::Temp(2)), + Instruction::SetValue(RegType::Temp(3)), + Instruction::SetValue(RegType::Temp(4)), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("test_fig23_wambook", 0, code).unwrap(); + let mut machine_st = MachineState::new(); + jit.exec("test_fig23_wambook", 0, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(str_loc_as_cell!(1)); + machine_st_expected.heap.push(atom_as_cell!(atom!("h"), 2)); + machine_st_expected.heap.push(heap_loc_as_cell!(2)); + machine_st_expected.heap.push(heap_loc_as_cell!(3)); + machine_st_expected.heap.push(str_loc_as_cell!(5)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 1)); + machine_st_expected.heap.push(heap_loc_as_cell!(3)); + machine_st_expected.heap.push(str_loc_as_cell!(8)); + machine_st_expected.heap.push(atom_as_cell!(atom!("p"), 3)); + machine_st_expected.heap.push(heap_loc_as_cell!(2)); + machine_st_expected.heap.push(str_loc_as_cell!(1)); + machine_st_expected.heap.push(str_loc_as_cell!(5)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); +} + +#[test] +fn test_get_structure_read() { + let code = vec![ + Instruction::PutStructure(atom!("f"), 1, RegType::Temp(1)), + Instruction::SetVariable(RegType::Temp(2)), + Instruction::GetStructure(Level::Shallow, atom!("f"), 1, RegType::Temp(1)), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("test_get_structure", 0, code).unwrap(); + let mut machine_st = MachineState::new(); + jit.exec("test_get_structure", 0, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(str_loc_as_cell!(1)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 1)); + machine_st_expected.heap.push(heap_loc_as_cell!(2)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); +} + +#[test] +fn test_get_structure_write() { + let code = vec![ + Instruction::SetVariable(RegType::Temp(1)), + Instruction::SetVariable(RegType::Temp(2)), + Instruction::GetStructure(Level::Shallow, atom!("f"), 1, RegType::Temp(1)), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("test_get_structure", 0, code).unwrap(); + let mut machine_st = MachineState::new(); + jit.exec("test_get_structure", 0, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(str_loc_as_cell!(2)); + machine_st_expected.heap.push(heap_loc_as_cell!(1)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 1)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); +} + +#[test] +fn test_get_structure_read_fail() { + let code = vec![ + Instruction::PutStructure(atom!("h"), 1, RegType::Temp(1)), + Instruction::SetVariable(RegType::Temp(2)), + Instruction::GetStructure(Level::Shallow, atom!("f"), 1, RegType::Temp(1)), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("test_get_structure", 0, code).unwrap(); + let mut machine_st = MachineState::new(); + jit.exec("test_get_structure", 0, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(str_loc_as_cell!(1)); + machine_st_expected.heap.push(atom_as_cell!(atom!("h"), 1)); + machine_st_expected.heap.push(heap_loc_as_cell!(2)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, true); +} + +#[test] +fn test_unify_variable_write() { + let code = vec![ + Instruction::SetVariable(RegType::Temp(1)), + Instruction::GetStructure(Level::Shallow, atom!("f"), 1, RegType::Temp(1)), + Instruction::UnifyVariable(RegType::Temp(2)), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("test_unify_variable_write", 0, code).unwrap(); + let mut machine_st = MachineState::new(); + jit.exec("test_unify_variable_write", 0, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(str_loc_as_cell!(1)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 1)); + machine_st_expected.heap.push(heap_loc_as_cell!(2)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); +} + +#[test] +fn test_unify_value_write() { + let code = vec![ + Instruction::SetVariable(RegType::Temp(1)), + Instruction::SetVariable(RegType::Temp(2)), + Instruction::GetStructure(Level::Shallow, atom!("f"), 1, RegType::Temp(1)), + Instruction::UnifyValue(RegType::Temp(2)), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("test_unify_value_write", 0, code).unwrap(); + let mut machine_st = MachineState::new(); + jit.exec("test_unify_value_write", 0, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(str_loc_as_cell!(2)); + machine_st_expected.heap.push(heap_loc_as_cell!(1)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 1)); + machine_st_expected.heap.push(heap_loc_as_cell!(1)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); +} + +#[test] +fn test_unify_value_read_str() { + let code = vec![ + Instruction::PutStructure(atom!("f"), 1, RegType::Temp(1)), + Instruction::PutStructure(atom!("h"), 0, RegType::Temp(2)), + Instruction::GetStructure(Level::Shallow, atom!("f"), 1, RegType::Temp(1)), + Instruction::SetVariable(RegType::Temp(3)), + Instruction::UnifyValue(RegType::Temp(3)), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("test_unify_value_read", 0, code).unwrap(); + let mut machine_st = MachineState::new(); + jit.exec("test_unify_value_read", 0, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(str_loc_as_cell!(1)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 1)); + machine_st_expected.heap.push(str_loc_as_cell!(3)); + machine_st_expected.heap.push(atom_as_cell!(atom!("h"), 0)); + machine_st_expected.heap.push(str_loc_as_cell!(3)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); +} + +#[test] +fn test_unify_value_read_str_2() { + let code = vec![ + Instruction::PutStructure(atom!("f"), 0, RegType::Temp(1)), + Instruction::PutStructure(atom!("f"), 0, RegType::Temp(2)), + Instruction::GetValue(RegType::Temp(1), 2), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("test_unify_value_read", 0, code).unwrap(); + let mut machine_st = MachineState::new(); + jit.exec("test_unify_value_read", 0, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(str_loc_as_cell!(1)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 0)); + machine_st_expected.heap.push(str_loc_as_cell!(3)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 0)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); + +} + +#[test] +fn test_unify_value_read_str_3() { + let code = vec![ + Instruction::PutStructure(atom!("f"), 1, RegType::Temp(1)), + Instruction::SetVariable(RegType::Temp(2)), + Instruction::SetVariable(RegType::Temp(3)), + Instruction::GetStructure(Level::Shallow, atom!("f"), 1, RegType::Temp(1)), + Instruction::UnifyValue(RegType::Temp(3)), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("test_unify_value_read", 0, code).unwrap(); + let mut machine_st = MachineState::new(); + jit.exec("test_unify_value_read", 0, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(str_loc_as_cell!(1)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 1)); + machine_st_expected.heap.push(heap_loc_as_cell!(2)); + machine_st_expected.heap.push(heap_loc_as_cell!(2)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); + +} + + +#[test] +fn test_unify_value_1() { + let code = vec![ + Instruction::SetVariable(RegType::Temp(1)), + Instruction::GetValue(RegType::Temp(1), 1), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("test_unify_value_read", 0, code).unwrap(); + let mut machine_st = MachineState::new(); + jit.exec("test_unify_value_read", 0, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(heap_loc_as_cell!(0)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); +} + + +#[test] +fn test_unify_value_2() { + let code = vec![ + Instruction::SetVariable(RegType::Temp(1)), + Instruction::SetVariable(RegType::Temp(2)), + Instruction::GetValue(RegType::Temp(1), 2), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("test_unify_value_read", 0, code).unwrap(); + let mut machine_st = MachineState::new(); + jit.exec("test_unify_value_read", 0, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(heap_loc_as_cell!(0)); + machine_st_expected.heap.push(heap_loc_as_cell!(0)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); +} + + +#[test] +fn test_unify_value_read_fail() { + let code = vec![ + Instruction::PutStructure(atom!("h"), 0, RegType::Temp(2)), + Instruction::PutStructure(atom!("z"), 0, RegType::Temp(3)), + Instruction::PutStructure(atom!("f"), 1, RegType::Temp(1)), + Instruction::SetValue(RegType::Temp(2)), + Instruction::SetValue(RegType::Temp(3)), + Instruction::GetStructure(Level::Shallow, atom!("f"), 1, RegType::Temp(1)), + Instruction::UnifyValue(RegType::Temp(3)), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("test_unify_value_read", 0, code).unwrap(); + let mut machine_st = MachineState::new(); + jit.exec("test_unify_value_read", 0, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(str_loc_as_cell!(1)); + machine_st_expected.heap.push(atom_as_cell!(atom!("h"), 0)); + machine_st_expected.heap.push(str_loc_as_cell!(3)); + machine_st_expected.heap.push(atom_as_cell!(atom!("z"), 0)); + machine_st_expected.heap.push(str_loc_as_cell!(5)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 1)); + machine_st_expected.heap.push(str_loc_as_cell!(1)); + machine_st_expected.heap.push(str_loc_as_cell!(3)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, true); +} + +#[test] +fn test_execute_named() { + let mut machine_st = MachineState::new(); + let code_b = vec![ + Instruction::GetConstant(Level::Shallow, atom_as_cell!(atom!("f"), 0), RegType::Temp(1)), + Instruction::Proceed + ]; + let code_a = vec![ + Instruction::PutVariable(RegType::Temp(1), 1), + Instruction::ExecuteNamed(1, atom!("b"), CodeIndex::default(&mut machine_st.arena)), + ]; + let mut jit = JitMachine::new(); + jit.compile("b", 1, code_b).unwrap(); + jit.compile("a", 0, code_a).unwrap(); + jit.exec("a", 0, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 0)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); +} + +#[test] +fn test_allocate() { + let mut machine_st = MachineState::new(); + let code_a = vec![ + Instruction::GetConstant(Level::Shallow, atom_as_cell!(atom!("a"), 0), RegType::Temp(1)), + Instruction::Proceed + ]; + let code_b = vec![ + Instruction::GetConstant(Level::Shallow, atom_as_cell!(atom!("b"), 0), RegType::Temp(1)), + Instruction::Proceed + ]; + let code_c = vec![ + Instruction::Allocate(1), + Instruction::GetVariable(RegType::Perm(1), 2), + Instruction::CallNamed(1, atom!("a"), CodeIndex::default(&mut machine_st.arena)), + Instruction::PutValue(RegType::Perm(1), 1), + Instruction::Deallocate, + Instruction::ExecuteNamed(1, atom!("b"), CodeIndex::default(&mut machine_st.arena)), + ]; + let x = heap_loc_as_cell!(0); + let y = heap_loc_as_cell!(1); + machine_st.registers[1] = x; + machine_st.registers[2] = y; + machine_st.heap.push(x); + machine_st.heap.push(y); + let mut jit = JitMachine::new(); + jit.compile("a", 1, code_a).unwrap(); + jit.compile("b", 1, code_b).unwrap(); + jit.compile("c", 2, code_c).unwrap(); + jit.exec("c", 2, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(atom_as_cell!(atom!("a"), 0)); + machine_st_expected.heap.push(atom_as_cell!(atom!("b"), 0)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); + + +} + +#[test] +fn test_backtracking_1() { + let mut machine_st = MachineState::new(); + let code_fail = vec![ + Instruction::PutStructure(atom!("f"), 0, RegType::Temp(2)), + Instruction::GetStructure(Level::Shallow, atom!("h"), 1, RegType::Temp(2)), + Instruction::Proceed + ]; + let code_ok = vec![ + Instruction::GetConstant(Level::Shallow, atom_as_cell!(atom!("a"), 0), RegType::Temp(1)), + Instruction::Proceed + ]; + let code = vec![ + Instruction::Allocate(1), + Instruction::GetVariable(RegType::Perm(1), 1), + Instruction::TryMeElse(4), + Instruction::PutValue(RegType::Perm(1), 1), + Instruction::Deallocate, + Instruction::ExecuteNamed(1, atom!("a"), CodeIndex::default(&mut machine_st.arena)), + Instruction::RetryMeElse(4), + Instruction::PutValue(RegType::Perm(1), 1), + Instruction::Deallocate, + Instruction::ExecuteNamed(1, atom!("b"), CodeIndex::default(&mut machine_st.arena)), + Instruction::TrustMe(0), + Instruction::PutValue(RegType::Perm(1), 1), + Instruction::Deallocate, + Instruction::ExecuteNamed(1, atom!("c"), CodeIndex::default(&mut machine_st.arena)) + ]; + let x = heap_loc_as_cell!(0); + machine_st.registers[1] = x; + machine_st.heap.push(x); + let mut jit = JitMachine::new(); + jit.compile("a", 1, code_fail.clone()).unwrap(); + jit.compile("b", 1, code_fail).unwrap(); + jit.compile("c", 1, code_ok).unwrap(); + jit.compile("d", 1, code).unwrap(); + jit.exec("d", 1, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(atom_as_cell!(atom!("a"), 0)); + machine_st_expected.heap.push(str_loc_as_cell!(2)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 0)); + machine_st_expected.heap.push(str_loc_as_cell!(4)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 0)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); +} + + +#[test] +fn test_backtracking_2() { + let mut machine_st = MachineState::new(); + let code_fail = vec![ + Instruction::PutStructure(atom!("f"), 0, RegType::Temp(2)), + Instruction::GetStructure(Level::Shallow, atom!("h"), 1, RegType::Temp(2)), + Instruction::Proceed + ]; + let code = vec![ + Instruction::TryMeElse(0), + Instruction::GetConstant(Level::Shallow, atom_as_cell!(atom!("a"), 0), RegType::Temp(1)), + Instruction::ExecuteNamed(0, atom!("f"), CodeIndex::default(&mut machine_st.arena)), + Instruction::RetryMeElse(0), + Instruction::GetConstant(Level::Shallow, atom_as_cell!(atom!("b"), 0), RegType::Temp(1)), + Instruction::ExecuteNamed(0, atom!("f"), CodeIndex::default(&mut machine_st.arena)), + Instruction::RetryMeElse(0), + Instruction::GetConstant(Level::Shallow, atom_as_cell!(atom!("c"), 0), RegType::Temp(1)), + Instruction::ExecuteNamed(0, atom!("f"), CodeIndex::default(&mut machine_st.arena)), + Instruction::TrustMe(0), + Instruction::GetConstant(Level::Shallow, atom_as_cell!(atom!("d"), 0), RegType::Temp(1)), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("f", 0, code_fail).unwrap(); + jit.compile("a", 1, code).unwrap(); + let x = heap_loc_as_cell!(0); + machine_st.registers[1] = x; + machine_st.heap.push(x); + jit.exec("a", 1, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(atom_as_cell!(atom!("d"), 0)); + machine_st_expected.heap.push(str_loc_as_cell!(2)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 0)); + machine_st_expected.heap.push(str_loc_as_cell!(4)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 0)); + machine_st_expected.heap.push(str_loc_as_cell!(6)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 0)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); +} + +#[test] +fn test_backtracking_3() { + let mut machine_st = MachineState::new(); + let code_fail = vec![ + Instruction::PutStructure(atom!("f"), 0, RegType::Temp(2)), + Instruction::GetStructure(Level::Shallow, atom!("h"), 1, RegType::Temp(2)), + Instruction::Proceed + ]; + let code = vec![ + Instruction::TryMeElse(0), + Instruction::GetConstant(Level::Shallow, atom_as_cell!(atom!("a"), 0), RegType::Temp(1)), + Instruction::ExecuteNamed(0, atom!("f"), CodeIndex::default(&mut machine_st.arena)), + Instruction::RetryMeElse(0), + Instruction::GetConstant(Level::Shallow, atom_as_cell!(atom!("b"), 0), RegType::Temp(1)), + Instruction::ExecuteNamed(0, atom!("f"), CodeIndex::default(&mut machine_st.arena)), + Instruction::RetryMeElse(0), + Instruction::GetConstant(Level::Shallow, atom_as_cell!(atom!("c"), 0), RegType::Temp(1)), + Instruction::ExecuteNamed(0, atom!("f"), CodeIndex::default(&mut machine_st.arena)), + Instruction::TrustMe(0), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("f", 0, code_fail).unwrap(); + jit.compile("a", 1, code).unwrap(); + let x = heap_loc_as_cell!(0); + machine_st.registers[1] = x; + machine_st.heap.push(x); + jit.exec("a", 1, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + machine_st_expected.heap.push(heap_loc_as_cell!(0)); + machine_st_expected.heap.push(str_loc_as_cell!(2)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 0)); + machine_st_expected.heap.push(str_loc_as_cell!(4)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 0)); + machine_st_expected.heap.push(str_loc_as_cell!(6)); + machine_st_expected.heap.push(atom_as_cell!(atom!("f"), 0)); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); +} + +/*#[test] +fn test_backtracking_4() { + let mut machine_st = MachineState::new(); + let code_fail = vec![ + Instruction::PutStructure(atom!("f"), 0, RegType::Temp(2)), + Instruction::GetStructure(Level::Shallow, atom!("h"), 1, RegType::Temp(2)), + Instruction::Proceed + ]; + let code = vec![ + Instruction::Allocate(1), + Instruction::PutVariable(RegType::Perm(1), 1), + Instruction::TryMeElse(0), + Instruction::GetConstant(Level::Shallow, atom_as_cell!(atom!("a"), 0), RegType::Perm(1)), + Instruction::ExecuteNamed(0, atom!("f"), CodeIndex::default(&mut machine_st.arena)), + Instruction::RetryMeElse(0), + Instruction::GetConstant(Level::Shallow, atom_as_cell!(atom!("b"), 0), RegType::Perm(1)), + Instruction::ExecuteNamed(0, atom!("f"), CodeIndex::default(&mut machine_st.arena)), + Instruction::TrustMe(0), + Instruction::GetConstant(Level::Shallow, atom_as_cell!(atom!("c"), 0), RegType::Perm(1)), + Instruction::GetVariable(RegType::Temp(1), 1), + Instruction::SetValue(RegType::Temp(1)), + Instruction::Proceed + ]; + let mut jit = JitMachine::new(); + jit.compile("f", 0, code_fail).unwrap(); + jit.compile("a", 1, code).unwrap(); + let x = heap_loc_as_cell!(0); + machine_st.registers[1] = x; + machine_st.heap.push(x); + jit.exec("a", 1, &mut machine_st).unwrap(); + let mut machine_st_expected = MachineState::new(); + assert_eq!(machine_st.heap, machine_st_expected.heap); + assert_eq!(machine_st.fail, false); +}*/ +// TODO: Backtracking 4 test stack trail + +// TODO: Continue with more tests + +// TODO: Move heap, stack, pdl and trail to GlobalValue diff --git a/src/machine/mod.rs b/src/machine/mod.rs index 5326c83cf..f1597688c 100644 --- a/src/machine/mod.rs +++ b/src/machine/mod.rs @@ -12,6 +12,8 @@ pub mod disjuncts; pub mod dispatch; pub mod gc; pub mod heap; +#[cfg(feature = "jit")] +pub mod jit2; pub mod lib_machine; pub mod load_state; pub mod machine_errors; @@ -39,6 +41,8 @@ use crate::machine::args::*; use crate::machine::compile::*; use crate::machine::copier::*; use crate::machine::heap::*; +#[cfg(feature = "jit")] +use crate::machine::jit2::*; use crate::machine::loader::*; use crate::machine::machine_errors::*; use crate::machine::machine_indices::*; @@ -80,6 +84,8 @@ pub struct Machine { #[cfg(feature = "ffi")] pub(super) foreign_function_table: ForeignFunctionTable, pub(super) rng: StdRng, + #[cfg(feature = "jit")] + pub(super) jit_machine: JitMachine, } #[derive(Debug)] @@ -477,6 +483,8 @@ impl Machine { #[cfg(feature = "ffi")] foreign_function_table: Default::default(), rng: StdRng::from_entropy(), + #[cfg(feature = "jit")] + jit_machine: JitMachine::new(), }; let mut lib_path = current_dir(); @@ -1140,10 +1148,19 @@ impl Machine { Ok(()) } + #[inline(always)] fn try_execute(&mut self, name: Atom, arity: usize, idx: IndexPtr) -> CallResult { let compiled_tl_index = idx.p() as usize; + #[cfg(feature = "jit")] + { + if let Ok(_) = self.jit_machine.exec(&name.as_str(), arity, &mut self.machine_st) { + // println!("jit_compiler: executed JIT predicate"); + return Ok(()); + } + } + match idx.tag() { IndexPtrTag::DynamicUndefined => { self.machine_st.fail = true; diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index 9b11a6e33..eefce8992 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -19,6 +19,8 @@ use crate::machine; use crate::machine::code_walker::*; use crate::machine::copier::*; use crate::machine::heap::*; +#[cfg(feature = "jit")] +use crate::machine::jit2::*; use crate::machine::machine_errors::*; use crate::machine::machine_indices::*; use crate::machine::machine_state::*; @@ -4977,6 +4979,86 @@ impl Machine { Ok(()) } + #[cfg(not(feature = "jit"))] + #[inline(always)] + pub(crate) fn jit_compile(&mut self) -> CallResult { + Ok(()) + } + + // Tries to compile an existing predicate into native code + // For this to work: the predicate must be loaded, must use the subset of Prolog supported by the JIT + // and every call to a predicate must have been compiled previously + #[cfg(feature = "jit")] + #[inline(always)] + pub(crate) fn jit_compile(&mut self) -> CallResult { + let module_name = cell_as_atom!(self.deref_register(1)); + let name = cell_as_atom!(self.deref_register(2)); + let arity = self.deref_register(3); + + let arity = match Number::try_from(arity) { + Ok(Number::Fixnum(n)) => n.get_num() as usize, + Ok(Number::Integer(n)) => { + let value: usize = (&*n).try_into().unwrap(); + value + } + _ => { + unreachable!() + } + }; + + let key = (name, arity); + + let first_idx = match module_name { + atom!("user") => self.indices.code_dir.get(&key), + _ => match self.indices.modules.get(&module_name) { + Some(module) => module.code_dir.get(&key), + None => { + let stub = functor_stub(key.0, key.1); + let err = self.machine_st.session_error(SessionError::from( + CompilationError::InvalidModuleResolution(module_name), + )); + + return Err(self.machine_st.error_form(err, stub)); + } + }, + }; + + let first_idx = match first_idx { + Some(idx) if idx.local().is_some() => { + if let Some(idx) = idx.local() { + idx + } else { + unreachable!() + } + } + _ => { + let stub = functor_stub(name, arity); + let err = self + .machine_st + .existence_error(ExistenceError::Procedure(name, arity)); + + return Err(self.machine_st.error_form(err, stub)); + } + }; + + let mut code = vec![]; + walk_code(&self.code, first_idx, |instr| code.push(instr.clone())); + + match self.jit_machine.compile(&name.as_str(), arity, code) { + Err(JitCompileError::UndefinedPredicate) => { + eprintln!("jit_compiler: undefined_predicate"); + self.machine_st.fail = true; + } + Err(JitCompileError::InstructionNotImplemented) => { + eprintln!("jit_compiler: instruction not implemented"); + self.machine_st.fail = true; + } + _ => {} + } + + Ok(()) + } + #[inline(always)] pub(crate) fn current_time(&mut self) { let timestamp = self.systemtime_to_timestamp(SystemTime::now()); diff --git a/src/tests/jit_test.pl b/src/tests/jit_test.pl new file mode 100644 index 000000000..a2dfc344a --- /dev/null +++ b/src/tests/jit_test.pl @@ -0,0 +1,13 @@ +:- use_module(library(jit)). + +a(X) :- b(X). +b(X) :- X is 1 + 1 + 1 + 1. +c(X, Y) :- X is Y + 1. + +test :- + jit_compile(b/1), + jit_compile(a/1), + a(X), + X = 4, + jit_compile(c/2), + c(2, 1). diff --git a/tests/scryer/cli/src_tests/jit.stderr b/tests/scryer/cli/src_tests/jit.stderr new file mode 100644 index 000000000..e69de29bb diff --git a/tests/scryer/cli/src_tests/jit.stdout b/tests/scryer/cli/src_tests/jit.stdout new file mode 100644 index 000000000..e69de29bb diff --git a/tests/scryer/cli/src_tests/jit.toml b/tests/scryer/cli/src_tests/jit.toml new file mode 100644 index 000000000..d053abe9a --- /dev/null +++ b/tests/scryer/cli/src_tests/jit.toml @@ -0,0 +1 @@ +args = ["-f", "--no-add-history", "src/tests/jit_test.pl", "-f", "-g", "test"]