diff --git a/Cargo.lock b/Cargo.lock index a1db58bf6..08d70b660 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,12 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "arrayvec" version = "0.5.2" @@ -1751,9 +1757,9 @@ dependencies = [ [[package]] name = "im-lists" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c38da2a11f128e1af1585abdd7041a96fc1adcd6ca2cb33e7fc5728326edbe8a" +checksum = "88485149c4fcec01ebce4e4b8284a3c75b3d8a4749169f5481144e6433e9bcd2" dependencies = [ "smallvec", ] @@ -3359,6 +3365,7 @@ version = "0.6.0" dependencies = [ "abi_stable", "anyhow", + "arc-swap", "async-ffi", "bigdecimal", "bincode", diff --git a/Cargo.toml b/Cargo.toml index 9afeb319d..41d165d52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ path = "src/main.rs" [workspace.dependencies] # This has to line up with the workspace version above -steel-core = { path = "./crates/steel-core", version = "0.6.0", features = ["dylibs", "markdown", "stacker", "sync"] } +steel-core = { path = "./crates/steel-core", version = "0.6.0", features = ["dylibs", "markdown", "stacker", "sync", "rooted-instructions"] } [features] default = ["mimalloc"] diff --git a/benchmarks/fib/fib.py b/benchmarks/fib/fib.py index ae96d1e64..430f8d959 100644 --- a/benchmarks/fib/fib.py +++ b/benchmarks/fib/fib.py @@ -1,7 +1,10 @@ +import time def fib(n): if n <= 2: return 1 return fib(n - 1) + fib(n - 2) - -fib(30) +start = time.time() +fib(35) +end = time.time() +print(end - start) diff --git a/benchmarks/fib/fib.scm b/benchmarks/fib/fib.scm index df5399d3e..edc16b632 100644 --- a/benchmarks/fib/fib.scm +++ b/benchmarks/fib/fib.scm @@ -1,4 +1,33 @@ +(provide fib) + +;; This should return an int, and should always +;; return an int - we should be able to do +;; ADDINT... Or also could do loop unrolling? (define (fib n) - (if (<= n 2) 1 (+ (fib (- n 1)) (fib (- n 2))))) + (if (<= n 2) + 1 + (+ (fib (- n 1)) (fib (- n 2))))) + +; (fib 41) + +(define (fib2 n) + (if (<= n 2) + 1 + (+ (if (<= n 3) + 1 + (+ (fib2 (- n 2)) (fib2 (- n 3)))) + (if (<= n 4) + 1 + (+ (fib2 (- n 3)) (fib2 (- n 4))))))) + +;; Take a callsite, and unroll it multiple times? +;; How that would be done; given a function definition, +;; go through and rewrite the recursive calls +;; to include the value? + +;; Loop unrolling +;; Constant propagation, with some rules. +;; +;; What that looks like -> Inlining a function call? -(fib 28) +(fib 35) diff --git a/cogs/installer/download.scm b/cogs/installer/download.scm index a09e31adf..ddc8ede08 100644 --- a/cogs/installer/download.scm +++ b/cogs/installer/download.scm @@ -116,7 +116,9 @@ (#%build-dylib (list "--manifest-path" (append-with-separator target "Cargo.toml")) (list (list "CARGO_TARGET_DIR" (append-with-separator *CARGO_TARGET_DIR* - (file-name target-directory))))))) + (file-name target-directory))))) + + (displayln "Finished building"))) ;; This... should be run in the background? (~> (command "cargo-steel-lib" '()) @@ -135,7 +137,12 @@ #:subdir [subdir ""] #:sha [*sha* void]) - (~> (maybe-git-clone library-name git-url *COG-SOURCES* #:sha *sha*) + (define found-library-name + (if (void? library-name) + (~> (split-many git-url "/") last (trim-end-matches ".git")) + library-name)) + + (~> (maybe-git-clone found-library-name git-url *COG-SOURCES* #:sha *sha*) ;; If we're attempting to install the package from a subdirectory of ;; git urls, we should do that accordingly here. (append-with-separator subdir) diff --git a/cogs/installer/main.scm b/cogs/installer/main.scm index b543d331c..a8b057d9a 100644 --- a/cogs/installer/main.scm +++ b/cogs/installer/main.scm @@ -88,6 +88,19 @@ (displayln "Package is not currently installed.") (install-package-and-log cog-to-install)))) +(define (install-package-from-git index git-url args) + ;; First, install the source to a temporary location. + (define package-spec (download-cog-to-sources-and-parse-module void git-url)) + + (define force (member "--force" args)) + + (displayln args) + (displayln package-spec) + + (if force + (install-package-and-log package-spec) + (install-package-if-not-installed index package-spec))) + ;; TODO: Move this to `installer/package.scm` (define (install-package-from-pkg-index index package args) (define pkg-index (list-package-index)) @@ -261,6 +274,17 @@ Commands: ;; List the remote package index [(equal? '("pkg" "list") command-line-args) (print-package-index)] + [(equal? '("pkg" "install" "--git") (take command-line-args 3)) + + (displayln command-line-args) + (displayln (list-ref command-line-args 3)) + (displayln (drop command-line-args 4)) + + ;; Install using a git url + (install-package-from-git package-index + (list-ref command-line-args 3) + (drop command-line-args 3))] + ;; Install package from remote [(equal? '("pkg" "install") (take command-line-args 2)) ;; Force a re-install diff --git a/crates/steel-core/Cargo.toml b/crates/steel-core/Cargo.toml index c1ad618c1..332fa3dd6 100644 --- a/crates/steel-core/Cargo.toml +++ b/crates/steel-core/Cargo.toml @@ -27,7 +27,8 @@ serde = { version = "1.0.193", features = ["derive", "rc"] } serde_derive = "1.0.193" bincode = "1.3.3" pretty = "0.12.1" -im-lists = "0.8.1" +im-lists = "0.9.0" + strsim = "0.11.0" quickscope = "0.2.0" @@ -83,6 +84,9 @@ compact_str = { version = "0.8.0", features = ["serde"] } git2 = { version = "0.19.0", optional = true, features = ["vendored-openssl"] } +# For the constant map +arc-swap = "1.7.1" + [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "*", features = ["js"] } @@ -123,6 +127,8 @@ recycle = [] git = ["dep:git2", "anyhow"] experimental-drop-handler = [] unsandboxed-kernel = [] +inline-captures = [] +experimental = [] [[bench]] name = "my_benchmark" diff --git a/crates/steel-core/src/compiler/code_gen.rs b/crates/steel-core/src/compiler/code_gen.rs index 69121476b..cbc3198bc 100644 --- a/crates/steel-core/src/compiler/code_gen.rs +++ b/crates/steel-core/src/compiler/code_gen.rs @@ -128,9 +128,9 @@ impl<'a> CodeGenerator<'a> { if let Some(info) = self.analysis.get(function.atom_syntax_object()?) { if info.kind == Free || info.kind == Global { return match function.atom_identifier().unwrap().resolve() { - "+" => Some(OpCode::ADDIMMEDIATE), - "-" => Some(OpCode::SUBIMMEDIATE), - "<=" => Some(OpCode::LTEIMMEDIATE), + "+" | "#%prim.+" => Some(OpCode::ADDIMMEDIATE), + "-" | "#%prim.-" => Some(OpCode::SUBIMMEDIATE), + "<=" | "#%prim.<=" => Some(OpCode::LTEIMMEDIATE), _ => None, }; } @@ -547,6 +547,14 @@ impl<'a> VisitorMut for CodeGenerator<'a> { } } + let size = body_instructions.len(); + + for instr in &mut body_instructions { + if instr.op_code == OpCode::JMP && instr.payload_size.to_usize() == size { + instr.op_code = OpCode::POPJMP; + } + } + body_instructions .push(LabeledInstruction::builder(pop_op_code).payload(lambda_function.args.len())); diff --git a/crates/steel-core/src/compiler/compiler.rs b/crates/steel-core/src/compiler/compiler.rs index 85c9d3e40..38148e056 100644 --- a/crates/steel-core/src/compiler/compiler.rs +++ b/crates/steel-core/src/compiler/compiler.rs @@ -667,6 +667,9 @@ impl Compiler { results.push(instructions); } + // Push down the readable constant map + self.constant_map.flush(); + // This... cannot be efficient? // for idx in index_buffer { // let extracted: Vec = instruction_buffer.drain(0..idx).collect(); diff --git a/crates/steel-core/src/compiler/constants.rs b/crates/steel-core/src/compiler/constants.rs index e9e260783..c6ff55d54 100644 --- a/crates/steel-core/src/compiler/constants.rs +++ b/crates/steel-core/src/compiler/constants.rs @@ -9,7 +9,9 @@ use crate::parser::{ }; use std::collections::HashMap; +use std::sync::Arc; +use arc_swap::ArcSwap; // TODO add the serializing and deserializing for constants use serde::{Deserialize, Serialize}; use steel_parser::parser::{lower_entire_ast, SourceId}; @@ -20,6 +22,8 @@ use steel_parser::parser::{lower_entire_ast, SourceId}; pub struct ConstantMap { map: SharedMut>, values: SharedMut>, + // TODO: Flush to these values after a compilation. - maybe have two of them to + reified_values: Arc>>, } #[derive(Serialize, Deserialize)] @@ -36,6 +40,7 @@ impl Clone for ConstantMap { Self { values: Shared::clone(&self.values), map: Shared::clone(&self.map), + reified_values: Arc::clone(&self.reified_values), } } } @@ -45,9 +50,16 @@ impl ConstantMap { ConstantMap { values: Shared::new(MutContainer::new(Vec::new())), map: Shared::new(MutContainer::new(HashMap::new())), + // Does this help at all? + reified_values: Arc::new(ArcSwap::from_pointee(Vec::new())), } } + pub fn flush(&self) { + let values = self.values.read().clone(); + self.reified_values.store(Arc::new(values)); + } + pub fn deep_clone(&self) -> ConstantMap { Self { map: Shared::new(MutContainer::new( @@ -60,6 +72,9 @@ impl ConstantMap { values: Shared::new(MutContainer::new( self.values.read().iter().cloned().collect(), )), + reified_values: Arc::new(ArcSwap::from_pointee( + self.values.read().iter().cloned().collect(), + )), } } @@ -90,7 +105,8 @@ impl ConstantMap { .map(|x| (x.1, x.0)) .collect(), )), - values: Shared::new(MutContainer::new(vec)), + values: Shared::new(MutContainer::new(vec.clone())), + reified_values: Arc::new(ArcSwap::from_pointee(vec)), } } @@ -167,6 +183,10 @@ impl ConstantMap { self.values.read()[idx].clone() } + pub fn get_value(&self, idx: usize) -> SteelVal { + self.reified_values.load()[idx].clone() + } + pub fn try_get(&self, idx: usize) -> Option { self.values.read().get(idx).cloned() } diff --git a/crates/steel-core/src/compiler/program.rs b/crates/steel-core/src/compiler/program.rs index c6b6e3f3f..ce38c848b 100644 --- a/crates/steel-core/src/compiler/program.rs +++ b/crates/steel-core/src/compiler/program.rs @@ -135,30 +135,62 @@ pub fn specialize_read_local(instructions: &mut [Instruction]) { } pub fn specialize_constants(instructions: &mut [Instruction]) -> Result<()> { - for instruction in instructions.iter_mut() { - match instruction { - Instruction { - op_code: OpCode::PUSHCONST, - contents: - Some(Expr::Atom(SyntaxObject { - ty: TokenType::Identifier(_), - .. - })), - .. - } => continue, - Instruction { - op_code: OpCode::PUSHCONST, - contents: Some(Expr::Atom(syn)), - .. - } => { - let value = eval_atom(syn)?; + for i in 0..instructions.len() - 1 { + let instruction = instructions.get(i); + let next = instructions.get(i + 1); + + match (instruction, next) { + ( + Some(Instruction { + op_code: OpCode::PUSHCONST, + contents: + Some(Expr::Atom(SyntaxObject { + ty: TokenType::Identifier(_), + .. + })), + .. + }), + .., + ) => continue, + + #[cfg(feature = "experimental")] + ( + Some(Instruction { + op_code: OpCode::PUSHCONST, + contents: Some(Expr::Atom(syn)), + .. + }), + Some(Instruction { + op_code: OpCode::POPJMP | OpCode::POPPURE, + .. + }), + ) => { + let value = eval_atom(&syn)?; + let opcode = match &value { + SteelVal::IntV(0) => OpCode::LOADINT0POP, + SteelVal::IntV(1) => OpCode::LOADINT1POP, + SteelVal::IntV(2) => OpCode::LOADINT2POP, + _ => continue, + }; + instructions.get_mut(i).unwrap().op_code = opcode; + } + + ( + Some(Instruction { + op_code: OpCode::PUSHCONST, + contents: Some(Expr::Atom(syn)), + .. + }), + .., + ) => { + let value = eval_atom(&syn)?; let opcode = match &value { SteelVal::IntV(0) => OpCode::LOADINT0, SteelVal::IntV(1) => OpCode::LOADINT1, SteelVal::IntV(2) => OpCode::LOADINT2, _ => continue, }; - instruction.op_code = opcode; + instructions.get_mut(i).unwrap().op_code = opcode; } _ => continue, } @@ -460,11 +492,11 @@ pub fn inline_num_operations(instructions: &mut [Instruction]) { if let ( Some(Instruction { - op_code: OpCode::PUSH, + op_code: OpCode::PUSH | OpCode::CALLGLOBAL | OpCode::CALLGLOBALTAIL, .. }), Some(Instruction { - op_code: OpCode::FUNC | OpCode::TAILCALL, + op_code: op @ OpCode::FUNC | op @ OpCode::TAILCALL, contents: Some(Expr::Atom(RawSyntaxObject { ty: TokenType::Identifier(ident), @@ -478,6 +510,9 @@ pub fn inline_num_operations(instructions: &mut [Instruction]) { let payload_size = payload_size.to_u32(); let replaced = match *ident { + x if x == *PRIM_PLUS && payload_size == 2 && *op == OpCode::TAILCALL => { + Some(OpCode::BINOPADDTAIL) + } x if x == *PRIM_PLUS && payload_size == 2 => Some(OpCode::BINOPADD), x if x == *PRIM_PLUS && payload_size > 0 => Some(OpCode::ADD), // x if x == *PRIM_MINUS && *payload_size == 2 => Some(OpCode::BINOPSUB), @@ -960,26 +995,14 @@ impl RawProgramWithSymbols { // Apply the optimizations to raw bytecode pub(crate) fn apply_optimizations(&mut self) -> &mut Self { - // if std::env::var("CODE_GEN_V2").is_err() { // Run down the optimizations here for instructions in &mut self.instructions { inline_num_operations(instructions); convert_call_globals(instructions); - - // gimmick_super_instruction(instructions); - // move_read_local_call_global(instructions); specialize_read_local(instructions); - merge_conditions_with_if(instructions); - specialize_constants(instructions).unwrap(); - - // Apply the super instruction tiling! tile_super_instructions(instructions); - - // specialize_exit_jmp(instructions); - - // loop_condition_local_const_arity_two(instructions); } self diff --git a/crates/steel-core/src/core/instructions.rs b/crates/steel-core/src/core/instructions.rs index 821e0aadc..2da5f4731 100644 --- a/crates/steel-core/src/core/instructions.rs +++ b/crates/steel-core/src/core/instructions.rs @@ -103,9 +103,6 @@ pub fn disassemble(instructions: &[Instruction]) -> String { #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct DenseInstruction { pub op_code: OpCode, - // Function IDs need to be interned _again_ before patched into the code? - // Also: We should be able to get away with a u16 here. Just grab places where u16 - // won't fit and convert to something else. pub payload_size: u24, } diff --git a/crates/steel-core/src/env.rs b/crates/steel-core/src/env.rs index b39609608..c10e66663 100644 --- a/crates/steel-core/src/env.rs +++ b/crates/steel-core/src/env.rs @@ -1,5 +1,9 @@ +// #[cfg(feature = "sync")] +// use parking_lot::{RwLock, RwLockReadGuard}; + #[cfg(feature = "sync")] -use parking_lot::{RwLock, RwLockReadGuard}; +use std::sync::{RwLock, RwLockReadGuard}; + #[cfg(feature = "sync")] use std::sync::Arc; @@ -16,11 +20,32 @@ pub struct Env { // there needs to be a lock on all globals since that way // things are relatively consistent. #[cfg(feature = "sync")] + // pub(crate) bindings_vec: Arc>>, + + // TODO: This is NO GOOD! - This causes much contention when + // trying to read the variables. What we really want to do + // is rethink how globals are stored and accessed. + // + // Perhaps updates using `set!` are instead atomic w.r.t a thread, + // however would require using an atomic box in order to see + // an update that occurs on another thread? - In addition, we could + // push down defines to other threads, in order for them to see it. + // + // At a safepoint, we could "refresh" if dirty? It might be faster? + // + // That way we don't need to necessarily have _every_ thread constantly + // checking its own stuff. + // + // TODO: Just make set! and `define` make all threads come to a safepoint + // before continuing to work, and then apply the definition across the board. + // + // This will remove the need to use a RwLock at all, and we can get away with + // just pushing the changes across together. pub(crate) bindings_vec: Arc>>, // Keep a copy of the globals that we can access // just by offset. - // #[cfg(feature = "sync")] - // pub(crate) thread_local_bindings: Vec, + #[cfg(feature = "sync")] + pub(crate) thread_local_bindings: Vec, } #[cfg(feature = "sync")] @@ -28,7 +53,7 @@ impl Clone for Env { fn clone(&self) -> Self { Self { bindings_vec: self.bindings_vec.clone(), - // thread_local_bindings: self.thread_local_bindings.clone(), + thread_local_bindings: self.thread_local_bindings.clone(), } } } @@ -123,27 +148,31 @@ impl Env { #[cfg(feature = "sync")] impl Env { pub fn extract(&self, idx: usize) -> Option { - self.bindings_vec.read().get(idx).cloned() + self.bindings_vec.read().unwrap().get(idx).cloned() } pub fn len(&self) -> usize { - self.bindings_vec.read().len() + self.bindings_vec.read().unwrap().len() } /// top level global env has no parent pub fn root() -> Self { Env { bindings_vec: Arc::new(RwLock::new(Vec::with_capacity(1024))), - // thread_local_bindings: Vec::with_capacity(1024), + thread_local_bindings: Vec::with_capacity(1024), } } pub fn deep_clone(&self) -> Self { + let guard = self.bindings_vec.read().unwrap(); + let bindings_vec = Arc::new(RwLock::new(guard.iter().map(|x| x.clone()).collect())); + // let thread_local_bindings = guard.iter().map(|x| x.clone()).collect(); + + let thread_local_bindings = self.thread_local_bindings.clone(); + Self { - bindings_vec: Arc::new(RwLock::new( - self.bindings_vec.read().iter().map(|x| x.clone()).collect(), - )), - // thread_local_bindings: self.thread_local_bindings.clone(), + bindings_vec, + thread_local_bindings, } } @@ -161,8 +190,11 @@ impl Env { #[inline(always)] pub fn repl_lookup_idx(&self, idx: usize) -> SteelVal { - self.bindings_vec.read()[idx].clone() - // self.thread_local_bindings[idx].clone() + // TODO: Signal to the other threads to update their stuff? + // get them all to a safepoint? Is that worth it? + + // self.bindings_vec.read().unwrap()[idx].clone() + self.thread_local_bindings[idx].clone() } // /// Get the value located at that index @@ -172,36 +204,43 @@ impl Env { #[inline] pub fn repl_define_idx(&mut self, idx: usize, val: SteelVal) { - let mut guard = self.bindings_vec.write(); + let mut guard = self.bindings_vec.write().unwrap(); - if idx < guard.len() { + // println!("{} - {}", guard.len(), self.thread_local_bindings.len()); + + // if idx < guard.len() { + if idx < self.thread_local_bindings.len() { guard[idx] = val.clone(); - // self.thread_local_bindings[idx] = val; + self.thread_local_bindings[idx] = val; } else { - if idx > guard.len() { + // if idx > guard.len() { + if idx > self.thread_local_bindings.len() { // TODO: This seems suspect. Try to understand // what is happening here. This would be that values // are getting interned to be at a global offset in the // wrong order, which seems to be fine in general, // assuming that the values then get actually updated // to the correct values. - for _ in 0..(idx - guard.len()) { + // for _ in 0..(idx - guard.len()) { + for _ in 0..(idx - self.thread_local_bindings.len()) { guard.push(SteelVal::Void); - // self.thread_local_bindings.push(SteelVal::Void); + self.thread_local_bindings.push(SteelVal::Void); } } guard.push(val.clone()); - // self.thread_local_bindings.push(val); - assert_eq!(guard.len() - 1, idx); + self.thread_local_bindings.push(val); + // assert_eq!(guard.len() - 1, idx); } + + // assert_eq!(self.thread_local_bindings.len(), guard.len()) } pub fn repl_set_idx(&mut self, idx: usize, val: SteelVal) -> Result { - let mut guard = self.bindings_vec.write(); + let mut guard = self.bindings_vec.write().unwrap(); let output = guard[idx].clone(); guard[idx] = val.clone(); - // self.thread_local_bindings[idx] = val; + self.thread_local_bindings[idx] = val; Ok(output) } @@ -214,7 +253,7 @@ impl Env { // TODO: This needs to be fixed! #[cfg(feature = "sync")] pub fn roots(&self) -> RwLockReadGuard<'_, Vec> { - self.bindings_vec.read() + self.bindings_vec.read().unwrap() } #[cfg(not(feature = "sync"))] diff --git a/crates/steel-core/src/primitives.rs b/crates/steel-core/src/primitives.rs index 319f1c217..7e9b27a5e 100644 --- a/crates/steel-core/src/primitives.rs +++ b/crates/steel-core/src/primitives.rs @@ -68,6 +68,7 @@ macro_rules! try_from_impl { $( impl TryFrom for $body { type Error = SteelErr; + #[inline] fn try_from(value: SteelVal) -> result::Result { match value { SteelVal::$type(x) => Ok(x.clone() as $body), @@ -78,6 +79,7 @@ macro_rules! try_from_impl { impl TryFrom<&SteelVal> for $body { type Error = SteelErr; + #[inline] fn try_from(value: &SteelVal) -> result::Result { match value { SteelVal::$type(x) => Ok(x.clone() as $body), @@ -87,6 +89,7 @@ macro_rules! try_from_impl { } impl FromSteelVal for $body { + #[inline] fn from_steelval(value: &SteelVal) -> result::Result { match value { SteelVal::$type(x) => Ok(x.clone() as $body), @@ -103,12 +106,14 @@ macro_rules! from_f64 { ($($body:ty),*) => { $( impl From<$body> for SteelVal { + #[inline] fn from(val: $body) -> SteelVal { SteelVal::NumV(val as f64) } } impl IntoSteelVal for $body { + #[inline] fn into_steelval(self) -> Result { Ok(SteelVal::NumV(self as f64)) } @@ -121,12 +126,14 @@ macro_rules! from_for_isize { ($($body:ty),*) => { $( impl From<$body> for SteelVal { + #[inline] fn from(val: $body) -> SteelVal { SteelVal::IntV(val as isize) } } impl IntoSteelVal for $body { + #[inline] fn into_steelval(self) -> Result { Ok(SteelVal::IntV(self as isize)) } @@ -146,6 +153,7 @@ impl From for SteelVal { } impl FromSteelVal for u8 { + #[inline] fn from_steelval(val: &SteelVal) -> crate::rvals::Result { match val { SteelVal::IntV(v) => (*v).try_into().map_err(|_err| { @@ -169,6 +177,7 @@ impl FromSteelVal for u8 { } impl From for SteelVal { + #[inline] fn from(value: usize) -> Self { if value > isize::MAX as usize { SteelVal::BigNum(Gc::new(value.into())) @@ -179,12 +188,14 @@ impl From for SteelVal { } impl IntoSteelVal for usize { + #[inline] fn into_steelval(self) -> crate::rvals::Result { Ok(SteelVal::from(self)) } } impl IntoSteelVal for i64 { + #[inline] fn into_steelval(self) -> crate::rvals::Result { Ok(self.into()) } @@ -214,12 +225,14 @@ impl FromSteelVal for i64 { } impl From for SteelVal { + #[inline] fn from(val: char) -> SteelVal { SteelVal::CharV(val) } } impl IntoSteelVal for char { + #[inline] fn into_steelval(self) -> Result { Ok(SteelVal::CharV(self)) } @@ -239,6 +252,7 @@ impl FromSteelVal for char { } impl> From> for SteelVal { + #[inline] fn from(val: Option) -> SteelVal { if let Some(s) = val { s.into() @@ -249,6 +263,7 @@ impl> From> for SteelVal { } impl IntoSteelVal for Option { + #[inline] fn into_steelval(self) -> Result { if let Some(s) = self { s.into_steelval() @@ -259,6 +274,7 @@ impl IntoSteelVal for Option { } impl FromSteelVal for Option { + #[inline] fn from_steelval(val: &SteelVal) -> Result { if val.is_truthy() { Ok(Some(T::from_steelval(val)?)) @@ -269,6 +285,7 @@ impl FromSteelVal for Option { } impl FromSteelVal for SteelVal { + #[inline] fn from_steelval(val: &SteelVal) -> Result { Ok(val.clone()) } @@ -285,18 +302,21 @@ impl FromSteelVal for () { } impl IntoSteelVal for () { + #[inline] fn into_steelval(self) -> Result { Ok(SteelVal::Void) } } impl From<()> for SteelVal { + #[inline] fn from(_: ()) -> SteelVal { SteelVal::Void } } impl IntoSteelVal for Rational32 { + #[inline] fn into_steelval(self) -> Result { if self.is_integer() { self.numer().into_steelval() @@ -307,6 +327,7 @@ impl IntoSteelVal for Rational32 { } impl IntoSteelVal for BigInt { + #[inline] fn into_steelval(self) -> Result { match self.to_isize() { Some(i) => i.into_steelval(), @@ -316,6 +337,7 @@ impl IntoSteelVal for BigInt { } impl IntoSteelVal for BigRational { + #[inline] fn into_steelval(self) -> Result { if self.is_integer() { let (n, _) = self.into(); @@ -335,6 +357,7 @@ try_from_impl!(IntV => i32, i16, i8, u16, u32, u64, usize, isize); impl TryFrom for String { type Error = SteelErr; + #[inline] fn try_from(value: SteelVal) -> result::Result { match value { SteelVal::StringV(ref x) => Ok(x.to_string()), @@ -348,18 +371,21 @@ impl TryFrom for String { } impl From for Gc { + #[inline] fn from(val: SteelVal) -> Self { Gc::new(val) } } impl From> for SteelVal { + #[inline] fn from(val: Gc) -> Self { (*val).clone() } } impl FromSteelVal for String { + #[inline] fn from_steelval(val: &SteelVal) -> Result { match val { SteelVal::StringV(s) | SteelVal::SymbolV(s) => Ok(s.to_string()), @@ -373,6 +399,7 @@ impl FromSteelVal for String { impl TryFrom<&SteelVal> for String { type Error = SteelErr; + #[inline] fn try_from(value: &SteelVal) -> result::Result { match value { SteelVal::StringV(x) => Ok(x.to_string()), @@ -386,19 +413,21 @@ impl TryFrom<&SteelVal> for String { } impl From for SteelVal { + #[inline] fn from(val: String) -> SteelVal { SteelVal::StringV(val.into()) } } impl IntoSteelVal for &str { - #[inline(always)] + #[inline] fn into_steelval(self) -> crate::rvals::Result { Ok(SteelVal::StringV(self.into())) } } impl FromSteelVal for SteelString { + #[inline] fn from_steelval(val: &SteelVal) -> crate::rvals::Result { if let SteelVal::StringV(s) = val { Ok(s.clone()) @@ -828,30 +857,35 @@ impl<'a> PrimitiveAsRef<'a> for &'a SteelHashMap { } impl IntoSteelVal for String { + #[inline(always)] fn into_steelval(self) -> Result { Ok(SteelVal::StringV(self.into())) } } impl IntoSteelVal for SteelString { + #[inline(always)] fn into_steelval(self) -> Result { Ok(SteelVal::StringV(self)) } } impl From for Gc { + #[inline(always)] fn from(val: String) -> Gc { Gc::new(val.into()) } } impl From for SteelVal { + #[inline(always)] fn from(val: bool) -> SteelVal { SteelVal::BoolV(val) } } impl FromSteelVal for bool { + #[inline(always)] fn from_steelval(val: &SteelVal) -> crate::rvals::Result { if let SteelVal::BoolV(b) = val { Ok(*b) @@ -862,12 +896,14 @@ impl FromSteelVal for bool { } impl IntoSteelVal for bool { + #[inline(always)] fn into_steelval(self) -> Result { Ok(SteelVal::BoolV(self)) } } impl From> for SteelVal { + #[inline(always)] fn from(val: Vector) -> SteelVal { SteelVal::VectorV(Gc::new(val).into()) } diff --git a/crates/steel-core/src/primitives/lists.rs b/crates/steel-core/src/primitives/lists.rs index c9acb98ad..6295f665f 100644 --- a/crates/steel-core/src/primitives/lists.rs +++ b/crates/steel-core/src/primitives/lists.rs @@ -1,15 +1,9 @@ +use crate::rvals::{IntoSteelVal, Result, SteelVal}; use crate::{ gc::Gc, - steel_vm::{ - builtin::BuiltInModule, - vm::{VmContext, APPLY_DEFINITION}, - }, + steel_vm::{builtin::BuiltInModule, vm::APPLY_DEFINITION}, values::lists::Pair, }; -use crate::{ - rvals::{IntoSteelVal, Result, SteelVal}, - steel_vm::vm::VmCore, -}; use crate::{stop, throw}; use crate::values::lists::List; @@ -96,11 +90,21 @@ pub fn list_module() -> BuiltInModule { .register_native_fn_definition(PLIST_TRY_GET_POSITIONAL_DEFINITION) .register_native_fn_definition(PLIST_GET_POSITIONAL_LIST_DEFINITION) .register_native_fn_definition(PLIST_VALIDATE_ARGS_DEFINITION) - .register_native_fn_definition(DROP_START_DEFINITION); + .register_native_fn_definition(DROP_START_DEFINITION) + .register_native_fn_definition(CHUNKS_DEFINITION); module } +#[steel_derive::function(name = "list-chunks", constant = true)] +pub fn chunks(list: &List) -> Result { + let nodes = list.nodes(); + + Ok(SteelVal::ListV( + nodes.into_iter().map(|x| SteelVal::ListV(x)).collect(), + )) +} + /// Get the second element of the list. Raises an error if the list does not have an element in the second position. /// /// (second l) -> any/c @@ -143,32 +147,6 @@ pub(crate) fn third(list: &List) -> Result { list.get(2).cloned().ok_or_else(throw!(Generic => "third: Index out of bounds - list did not have an element in the second position: {:?}", list)) } -fn _test_map(ctx: &mut VmCore, args: &[SteelVal]) -> Result { - arity_check!(test_map, args, 2); - - let mut arg_iter = args.iter(); - let arg1 = arg_iter.next().unwrap(); - let arg2 = arg_iter.next().unwrap(); - - if let SteelVal::ListV(l) = arg2 { - if arg1.is_function() { - // unimplemented!() - - Ok(SteelVal::ListV( - l.into_iter() - .map(|x| ctx.call_function_one_arg(arg1, x.clone())) - .collect::>()?, - )) - - // ctx.call_function_one_arg_or_else(function, arg) - } else { - stop!(TypeMismatch => "test-map expected a function") - } - } else { - stop!(TypeMismatch => "test-map expects a list") - } -} - #[steel_derive::function(name = "list-tail")] pub fn list_tail(list_or_pair: &SteelVal, pos: usize) -> Result { match list_or_pair { diff --git a/crates/steel-core/src/rvals.rs b/crates/steel-core/src/rvals.rs index 29d8178ae..035c7df1d 100644 --- a/crates/steel-core/src/rvals.rs +++ b/crates/steel-core/src/rvals.rs @@ -1359,6 +1359,7 @@ impl SteelComplex { } impl IntoSteelVal for SteelComplex { + #[inline(always)] fn into_steelval(self) -> Result { Ok(match self.im { NumV(n) if n.is_zero() => self.re, diff --git a/crates/steel-core/src/scheme/modules/parameters.scm b/crates/steel-core/src/scheme/modules/parameters.scm index b9a0a3c56..ec0c248ad 100644 --- a/crates/steel-core/src/scheme/modules/parameters.scm +++ b/crates/steel-core/src/scheme/modules/parameters.scm @@ -148,10 +148,16 @@ (lambda (x y) (let ([lx (length x)] [ly (length y)]) - (let loop ([x (if (> lx ly) (list-tail x (- lx ly)) x)] - [y (if (> ly lx) (list-tail y (- ly lx)) y)]) - - (if (equal? x y) x (loop (cdr x) (cdr y))))))) + (let loop ([x (if (> lx ly) + (list-tail x (- lx ly)) + x)] + [y (if (> ly lx) + (list-tail y (- ly lx)) + y)]) + + (if (equal? x y) + x + (loop (cdr x) (cdr y))))))) (define do-wind (lambda (new) diff --git a/crates/steel-core/src/scheme/modules/sync.scm b/crates/steel-core/src/scheme/modules/sync.scm index c60029e1f..21d580992 100644 --- a/crates/steel-core/src/scheme/modules/sync.scm +++ b/crates/steel-core/src/scheme/modules/sync.scm @@ -4,7 +4,8 @@ block-on-task task-done? task-err - task) + task + pmap) ;;@doc ;; Lock the given lock during the duration @@ -41,7 +42,9 @@ (define func (Task-func-or-result next-task)) ;; Does this work? - (with-handler (lambda (err) (set-Task-err! next-task err)) + (with-handler (lambda (err) + (set-Task-done! next-task #t) + (set-Task-err! next-task err)) ;; Capture exception, if it exists. Store it in the task (lock! (Task-lock next-task) (lambda () @@ -82,9 +85,37 @@ (cond ;; If its an error, we don't immediately raise ;; the exception for now - [(Task-done task) (if (Task-err task) (Task-err task) (Task-func-or-result task))] + [(Task-done task) + (if (Task-err task) + (Task-err task) + (Task-func-or-result task))] [else (try-block task) (loop task)])) (loop task)) + +;; Thread pool for parallel map - will just be static for all pmaps. +(define tp (make-thread-pool 16)) + +(define (pmap func lst) + ;; Convert list into chunks that it can operate on, independently - since the + ;; list is already stored as a bunch of exponential things in a row, we can + ;; slice it up into those pieces nicely - for now, we can just assume + ;; that this is something we _could_ implement, and _should_ implement, but I + ;; don't feel like going through that exercise right now. + (define chunks (list-chunks lst)) + (define tasks + (map (lambda (chunk) + (submit-task tp + (lambda () + ;; Find out where the overhead is coming from + (define res (map func chunk)) + + res))) + chunks)) + ;; Reducing contention... how to do it? Probably need to do some kind of work with + ;; making sure that the globals don't get locked up - I'm guessing that is where most of + ;; the wait time here is - if each thread can get its own copies of the values, then + ;; they don't have to be locked up reading a global. + (transduce tasks (flat-mapping (lambda (x) (block-on-task x))) (into-list))) diff --git a/crates/steel-core/src/scheme/stdlib.scm b/crates/steel-core/src/scheme/stdlib.scm index 42f25e97f..53645fb14 100644 --- a/crates/steel-core/src/scheme/stdlib.scm +++ b/crates/steel-core/src/scheme/stdlib.scm @@ -181,7 +181,9 @@ ;; Internal, we don't do anything special [(quasisyntax #%internal-crunch x) - (if (empty? 'x) (#%syntax/raw '() '() (#%syntax-span x)) (#%syntax/raw 'x 'x (#%syntax-span x)))] + (if (empty? 'x) + (#%syntax/raw '() '() (#%syntax-span x)) + (#%syntax/raw 'x 'x (#%syntax-span x)))] [(quasisyntax (x xs ...)) (syntax (#%syntax/raw (quote (x xs ...)) @@ -526,7 +528,9 @@ ; (define compose (lambda (f g) (lambda (arg) (f (g arg))))) (define (foldl func accum lst) - (if (null? lst) accum (foldl func (func (car lst) accum) (cdr lst)))) + (if (null? lst) + accum + (foldl func (func (car lst) accum) (cdr lst)))) (define (map func lst . lsts) @@ -550,11 +554,16 @@ ; (transduce lst (mapping func) (into-list)))) (define foldr - (lambda (func accum lst) (if (null? lst) accum (func (car lst) (foldr func accum (cdr lst)))))) + (lambda (func accum lst) + (if (null? lst) + accum + (func (car lst) (foldr func accum (cdr lst)))))) (define unfold (lambda (func init pred) - (if (pred init) (cons init '()) (cons init (unfold func (func init) pred))))) + (if (pred init) + (cons init '()) + (cons init (unfold func (func init) pred))))) (define fold (lambda (f a l) (foldl f a l))) (define reduce (lambda (f a l) (fold f a l))) @@ -594,13 +603,25 @@ [else (contains? pred? (cdr lst))])) (define (assoc thing alist) - (if (null? alist) #f (if (equal? (car (car alist)) thing) (car alist) (assoc thing (cdr alist))))) + (if (null? alist) + #f + (if (equal? (car (car alist)) thing) + (car alist) + (assoc thing (cdr alist))))) (define (assq thing alist) - (if (null? alist) #f (if (eq? (car (car alist)) thing) (car alist) (assq thing (cdr alist))))) + (if (null? alist) + #f + (if (eq? (car (car alist)) thing) + (car alist) + (assq thing (cdr alist))))) (define (assv thing alist) - (if (null? alist) #f (if (eq? (car (car alist)) thing) (car alist) (assv thing (cdr alist))))) + (if (null? alist) + #f + (if (eq? (car (car alist)) thing) + (car alist) + (assv thing (cdr alist))))) ;;@doc ;; Returns new list, keeping elements from `lst` which applying `pred` to the element @@ -613,7 +634,9 @@ ;; (filter even? (range 0 5)) ;; '(0 2 4) ;; ``` (define (filter pred lst) - (if (empty? lst) '() (transduce lst (filtering pred) (into-list)))) + (if (empty? lst) + '() + (transduce lst (filtering pred) (into-list)))) ; (define (fact n) ; (define factorial-tail (lambda (n acc) @@ -622,8 +645,16 @@ ; (factorial-tail (- n 1) (* acc n ))))) ; (factorial-tail n 1)) -(define even-rec? (lambda (x) (if (= x 0) #t (odd-rec? (- x 1))))) -(define odd-rec? (lambda (x) (if (= x 0) #f (even-rec? (- x 1))))) +(define even-rec? + (lambda (x) + (if (= x 0) + #t + (odd-rec? (- x 1))))) +(define odd-rec? + (lambda (x) + (if (= x 0) + #f + (even-rec? (- x 1))))) (define sum (lambda (x) (reduce + 0 x))) ;; (define head car) @@ -645,7 +676,9 @@ (define (drop lst n) (define (loop x l) - (if (zero? x) l (loop (sub1 x) (cdr l)))) + (if (zero? x) + l + (loop (sub1 x) (cdr l)))) (loop n lst)) (define (slice l offset n) @@ -663,7 +696,9 @@ [else (gcd b (modulo a b))])) (define (lcm a b) - (if (or (zero? a) (zero? b)) 0 (abs (* b (floor (/ a (gcd a b))))))) + (if (or (zero? a) (zero? b)) + 0 + (abs (* b (floor (/ a (gcd a b))))))) (define (for-each func lst) (if (null? lst) diff --git a/crates/steel-core/src/steel_vm/engine.rs b/crates/steel-core/src/steel_vm/engine.rs index eec5215bb..c1d390e5c 100644 --- a/crates/steel-core/src/steel_vm/engine.rs +++ b/crates/steel-core/src/steel_vm/engine.rs @@ -3,7 +3,7 @@ use super::{ builtin::{BuiltInModule, FunctionSignatureMetadata}, primitives::{register_builtin_modules, CONSTANTS}, - vm::{SteelThread, ThreadStateController}, + vm::{SteelThread, Synchronizer, ThreadStateController}, }; #[cfg(feature = "dylibs")] @@ -217,9 +217,25 @@ pub struct Engine { pub(crate) id: EngineId, } +impl Engine { + pub fn enter_safepoint T>(&mut self, mut thunk: F) -> T { + let mut res = None; + + self.virtual_machine.enter_safepoint(|_| { + res = Some((thunk)()); + Ok(SteelVal::Void) + }); + + res.unwrap() + } +} + impl Clone for Engine { fn clone(&self) -> Self { let mut virtual_machine = self.virtual_machine.clone(); + + virtual_machine.synchronizer = Synchronizer::new(); + let compiler = Arc::new(RwLock::new(self.virtual_machine.compiler.write().clone())); // virtual_machine.compiler = Some(Arc::downgrade(&compiler)); @@ -1696,6 +1712,7 @@ impl Engine { .global_env .bindings_vec .write() + .unwrap() .truncate(checkpoint.globals_offset); } @@ -1727,7 +1744,12 @@ impl Engine { #[cfg(feature = "sync")] { GlobalSlotRecycler::free_shadowed_rooted_values( - &mut self.virtual_machine.global_env.bindings_vec.write(), + &mut self + .virtual_machine + .global_env + .bindings_vec + .write() + .unwrap(), &mut self.virtual_machine.compiler.write().symbol_map, &mut self.virtual_machine.heap.lock().unwrap(), ); @@ -2019,12 +2041,6 @@ impl Engine { // .execute_program::(program) // } - // TODO this does not take into account the issues with - // people registering new functions that shadow the original one - // fn constants(&mut self) -> ImmutableHashMap { - // CONSTANT_PRIMITIVES.clone() - // } - pub fn add_module(&mut self, path: String) -> Result<()> { self.virtual_machine.compiler.write().compile_module( path.into(), diff --git a/crates/steel-core/src/steel_vm/primitives.rs b/crates/steel-core/src/steel_vm/primitives.rs index 8debe77d3..93b5e7054 100644 --- a/crates/steel-core/src/steel_vm/primitives.rs +++ b/crates/steel-core/src/steel_vm/primitives.rs @@ -1156,18 +1156,18 @@ fn ord_module() -> BuiltInModule { match args { [x] => { ensure_real(x)?; - true.into_steelval() + Ok(SteelVal::BoolV(true)) } [x, rest @ ..] => { let mut left = ensure_real(x)?; for r in rest { let right = ensure_real(r)?; if !ordering_f(left.partial_cmp(right)) { - return false.into_steelval(); + return Ok(SteelVal::BoolV(false)); } left = right; } - true.into_steelval() + Ok(SteelVal::BoolV(true)) } _ => stop!(ArityMismatch => "expected at least one argument"), } diff --git a/crates/steel-core/src/steel_vm/vm.rs b/crates/steel-core/src/steel_vm/vm.rs index 60177a5d3..a55d02f3e 100644 --- a/crates/steel-core/src/steel_vm/vm.rs +++ b/crates/steel-core/src/steel_vm/vm.rs @@ -1,8 +1,4 @@ -#![allow(unused)] - -use crate::compiler::code_gen::fresh_function_id; use crate::compiler::compiler::Compiler; -use crate::core::instructions::pretty_print_dense_instructions; use crate::core::instructions::u24; use crate::gc::shared::MutContainer; use crate::gc::shared::ShareableMut; @@ -15,7 +11,6 @@ use crate::parser::parser::Sources; use crate::parser::replace_idents::expand_template; use crate::primitives::lists::car; use crate::primitives::lists::cdr; -use crate::primitives::lists::cons; use crate::primitives::lists::is_empty; use crate::primitives::lists::new as new_list; use crate::primitives::lists::steel_cons; @@ -23,16 +18,15 @@ use crate::primitives::numbers::add_two; use crate::rvals::as_underlying_type; use crate::rvals::cycles::BreadthFirstSearchSteelValVisitor; use crate::rvals::number_equality; -use crate::rvals::AsRefMutSteelVal as _; use crate::rvals::BoxedAsyncFunctionSignature; use crate::rvals::FromSteelVal as _; use crate::rvals::SteelString; use crate::steel_vm::primitives::steel_not; use crate::steel_vm::primitives::steel_set_box_mutable; use crate::steel_vm::primitives::steel_unbox_mutable; -use crate::steel_vm::primitives::THREADING_MODULE; use crate::values::closed::Heap; use crate::values::closed::MarkAndSweepContext; +use crate::values::functions::CaptureVec; use crate::values::functions::RootedInstructions; use crate::values::functions::SerializedLambda; use crate::values::structs::UserDefinedStruct; @@ -40,7 +34,6 @@ use crate::values::transducers::Reducer; use crate::{ compiler::constants::ConstantMap, core::{instructions::DenseInstruction, opcode::OpCode}, - rvals::FutureResult, }; use crate::{ compiler::program::Executable, @@ -58,27 +51,19 @@ use crate::{ stop, values::functions::ByteCodeLambda, }; -use std::cell::UnsafeCell; use std::io::Read as _; -use std::rc::Weak; use std::sync::atomic::AtomicBool; -use std::sync::mpsc::Sender; use std::sync::Arc; use std::sync::Mutex; -use std::thread::JoinHandle; -use std::thread::ThreadId; use std::{cell::RefCell, collections::HashMap, iter::Iterator, rc::Rc}; -use super::builtin::DocTemplate; -use super::builtin::MarkdownDoc; use super::engine::EngineId; -use crate::values::lists::List; - use crossbeam::atomic::AtomicCell; #[cfg(feature = "profiling")] use log::{debug, log_enabled}; -use once_cell::sync::Lazy; +use num::BigInt; +use num::CheckedSub; use parking_lot::RwLock; use smallvec::SmallVec; #[cfg(feature = "profiling")] @@ -89,7 +74,7 @@ use threads::ThreadHandle; use crate::rvals::{from_serializable_value, into_serializable_value, IntoSteelVal}; pub(crate) mod threads; -pub(crate) use threads::{spawn_thread, thread_join}; +pub(crate) use threads::spawn_thread; pub use threads::{mutex_lock, mutex_unlock}; @@ -116,7 +101,7 @@ pub fn unlikely(b: bool) -> bool { const STACK_LIMIT: usize = 1000000; const _JIT_THRESHOLD: usize = 100; -const USE_SUPER_INSTRUCTIONS: bool = false; +const _USE_SUPER_INSTRUCTIONS: bool = false; const CHECK_STACK_OVERFLOW: bool = false; #[repr(C)] @@ -178,30 +163,12 @@ pub struct StackFrameAttachments { pub struct StackFrame { sp: usize, - // This _has_ to be a function - // pub(crate) handler: Option>, - // This should get added to the GC as well - #[cfg(not(feature = "unsafe-internals"))] pub(crate) function: Gc, - // Whenever the StackFrame object leaves the context of _this_ VM, these functions - // need to become rooted, otherwise we'll have an issue with use after free - #[cfg(feature = "unsafe-internals")] - pub(crate) function: crate::gc::unsafe_roots::MaybeRooted, ip: usize, - // // TODO: This should just be... *const [DenseInstruction] - // // Since Rc should always just be alive? - // instructions: Shared<[DenseInstruction]>, - - // // TODO: Delete this one! - // // continuation_mark: Option, - // weak_continuation_mark: Option, instructions: RootedInstructions, - // TODO: Delete this one! - // continuation_mark: Option, - // weak_continuation_mark: Option, pub(crate) attachments: Option>, } @@ -240,24 +207,19 @@ thread_local! { } impl StackFrame { + #[inline(always)] pub fn new( stack_index: usize, function: Gc, ip: usize, - // instructions: Shared<[DenseInstruction]>, instructions: RootedInstructions, ) -> Self { Self { sp: stack_index, - #[cfg(feature = "unsafe-internals")] - function: crate::gc::unsafe_roots::MaybeRooted::Reference(function), - #[cfg(not(feature = "unsafe-internals"))] function, ip, instructions, - // handler: None, attachments: None, - // weak_continuation_mark: None, } } @@ -282,7 +244,6 @@ impl StackFrame { pub fn main() -> Self { let function = Gc::new(ByteCodeLambda::main(Vec::new())); - // StackFrame::new(0, function, 0, Shared::from([])) StackFrame::new( 0, function, @@ -293,15 +254,7 @@ impl StackFrame { #[inline(always)] pub fn set_function(&mut self, function: Gc) { - #[cfg(not(feature = "unsafe-internals"))] - { - self.function = function; - } - - #[cfg(feature = "unsafe-internals")] - { - self.function = crate::gc::unsafe_roots::MaybeRooted::Reference(function); - } + self.function = function; } #[inline(always)] @@ -346,38 +299,6 @@ thread_local! { pub(crate) static DEFAULT_CONSTANT_MAP: ConstantMap = ConstantMap::new(); } -// Incredibly unsafe, however I think it is what we're gonna -// need in order to do this safepoint business. -// -// Once we're within a safe point, the thread _could_ be running -// a native function. This is more or less the only time in which -// we can take a peek at the contents of the thread. -// -// Note: we won't mutate, however it is almost assuredly, not safe. -pub struct SafepointablePointer { - within_safepoint: Arc, - paused: Arc, - value: Arc>, -} - -impl SafepointablePointer { - pub unsafe fn get_mut(&self) -> &mut T { - &mut *self.value.get() - } - - pub unsafe fn get_safepoint_safe(&self) -> Option<&T> { - if self - .within_safepoint - .load(std::sync::atomic::Ordering::Relaxed) - && self.paused.load(std::sync::atomic::Ordering::Relaxed) - { - Some(&*self.value.get()) - } else { - None - } - } -} - #[derive(Copy, Clone, Default)] pub enum ThreadState { #[default] @@ -390,9 +311,16 @@ pub enum ThreadState { /// The thread execution context #[derive(Clone)] pub struct SteelThread { + // TODO: Figure out how to best broadcast changes + // to the rest of the world? Right now pausing threads + // means we can get away with one environment that is + // shared, but in reality this should just be pub(crate) global_env: Env, pub(crate) stack: Vec, + + #[cfg(feature = "dynamic")] profiler: OpCodeOccurenceProfiler, + pub(crate) function_interner: FunctionInterner, pub(crate) heap: Arc>, pub(crate) runtime_options: RunTimeOptions, @@ -428,15 +356,6 @@ impl RunTimeOptions { } } -// struct InstructionChunk { -// start: usize, -// end: usize, -// id: usize, -// } - -#[derive(PartialEq)] -struct SpanId(usize); - // TODO: This object probably needs to be shared as well #[derive(Default, Clone)] pub struct FunctionInterner { @@ -453,9 +372,6 @@ pub struct FunctionInterner { // reference to the existing thread in which it was created, and if passed in externally by // another run time, we can nuke it? spans: fxhash::FxHashMap>, - // Keep these around - each thread keeps track of the instructions on the bytecode object, but we shouldn't - // need to dereference that until later? When we actually move to that - instructions: fxhash::FxHashMap>, } #[derive(Clone, Default)] @@ -492,7 +408,7 @@ impl ThreadStateController { #[derive(Clone)] struct ThreadContext { - ctx: std::sync::Weak>>, + ctx: std::sync::Weak>>, handle: SteelVal, } @@ -508,7 +424,7 @@ pub struct Synchronizer { // If we're at a safe point, then this will include a _live_ pointer // to the context. Once we exit the safe point, we're done. - ctx: Arc>>, + ctx: Arc>>, } // TODO: Until I figure out how to note have this be the case @@ -527,13 +443,56 @@ impl Synchronizer { } } + pub(crate) unsafe fn call_per_ctx(&mut self, mut func: impl FnMut(&mut SteelThread)) { + let guard = self.threads.lock().unwrap(); + + // IMPORTANT - This needs to be all threads except the currently + // executing one. + for ThreadContext { ctx, .. } in guard.iter() { + if let Some(ctx) = ctx.upgrade() { + if Arc::ptr_eq(&ctx, &self.ctx) { + continue; + } + + // TODO: Have to use a condvar + loop { + if let Some(ctx) = ctx.load() { + log::debug!("Broadcasting `set!` operation"); + + unsafe { + let live_ctx = &mut (*ctx); + (func)(live_ctx) + } + + break; + } else { + log::debug!("Waiting for thread...") + + // println!("Waiting for thread..."); + + // TODO: Some kind of condvar or message passing + // is probably a better scheme here, but the idea is to just + // wait until all the threads are done. + } + } + } else { + continue; + } + } + } + pub(crate) unsafe fn enumerate_stacks(&mut self, context: &mut MarkAndSweepContext) { // TODO: Continue... - let mut guard = self.threads.lock().unwrap(); + let guard = self.threads.lock().unwrap(); // Wait for all the threads to be legal - for ThreadContext { ctx, handle } in guard.iter() { + for ThreadContext { ctx, .. } in guard.iter() { if let Some(ctx) = ctx.upgrade() { + // Don't pause myself, enter safepoint from main thread? + if Arc::ptr_eq(&ctx, &self.ctx) { + continue; + } + // TODO: Have to use a condvar loop { if let Some(ctx) = ctx.load() { @@ -607,10 +566,23 @@ impl Synchronizer { impl SteelThread { pub fn new(sources: Sources, compiler: std::sync::Arc>) -> SteelThread { + let synchronizer = Synchronizer::new(); + let weak_ctx = Arc::downgrade(&synchronizer.ctx); + + // TODO: Entering safepoint should happen often + // for the main thread? + synchronizer.threads.lock().unwrap().push(ThreadContext { + ctx: weak_ctx, + handle: SteelVal::Void, + }); + SteelThread { global_env: Env::root(), stack: Vec::with_capacity(128), + + #[cfg(feature = "dynamic")] profiler: OpCodeOccurenceProfiler::new(), + function_interner: FunctionInterner::default(), // _super_instructions: Vec::new(), heap: Arc::new(Mutex::new(Heap::new())), @@ -624,7 +596,7 @@ impl SteelThread { // with the executables constant_map: DEFAULT_CONSTANT_MAP.with(|x| x.clone()), interrupted: Default::default(), - synchronizer: Synchronizer::new(), + synchronizer, thread_local_storage: Vec::new(), sources, compiler, @@ -648,7 +620,8 @@ impl SteelThread { // thread exists if cfg!(feature = "sync") && self.safepoints_enabled { - self.synchronizer.ctx.store(Some(self as _)); + let ptr = self as _; + self.synchronizer.ctx.store(Some(ptr)); } let res = finish(self); @@ -765,13 +738,16 @@ impl SteelThread { SteelVal::Closure(closure) => { // Create phony span vec - let spans = closure.body_exp().iter().map(|_| Span::default()).collect(); + let spans = closure + .body_exp() + .iter() + .map(|_| Span::default()) + .collect::>(); let mut vm_instance = VmCore::new_unchecked( // Shared::new([]), RootedInstructions::new(THE_EMPTY_INSTRUCTION_SET.with(|x| x.clone())), constant_map, - Shared::clone(&spans), self, &spans, ); @@ -821,7 +797,6 @@ impl SteelThread { let mut vm_instance = VmCore::new_unchecked( RootedInstructions::new(THE_EMPTY_INSTRUCTION_SET.with(|x| x.clone())), constant_map, - Shared::new([]), self, &[], ); @@ -830,12 +805,15 @@ impl SteelThread { } SteelVal::Closure(closure) => { // TODO: Revisit if we need this phony span vec! - let spans = closure.body_exp().iter().map(|_| Span::default()).collect(); + let spans = closure + .body_exp() + .iter() + .map(|_| Span::default()) + .collect::>(); let mut vm_instance = VmCore::new_unchecked( RootedInstructions::new(THE_EMPTY_INSTRUCTION_SET.with(|x| x.clone())), constant_map, - Shared::clone(&spans), self, &spans, ); @@ -848,46 +826,31 @@ impl SteelThread { } } - pub fn execute_eval( - &mut self, - instructions: Shared<[DenseInstruction]>, - constant_map: ConstantMap, - spans: Shared<[Span]>, - ) -> Result { - todo!() - - // let stack = std::mem::take(&mut self.stack); - // let frames = std::mem::take(&mut self.stack_frames); - } - pub fn execute( &mut self, instructions: Shared<[DenseInstruction]>, constant_map: ConstantMap, spans: Shared<[Span]>, ) -> Result { + #[cfg(feature = "dynamic")] self.profiler.reset(); #[cfg(feature = "profiling")] let execution_time = Instant::now(); - // let mut vm_instance = VmCore::new( - // instructions, - // constant_map, - // Shared::clone(&spans), - // self, - // &spans, - // )?; - let keep_alive = instructions.clone(); - // TODO: Very important! Convert this back before we return + self.current_frame + .set_function(Gc::new(ByteCodeLambda::rooted(keep_alive.clone()))); + let raw_keep_alive = Shared::into_raw(keep_alive); + // TODO: Figure out how to keep the first set of instructions around + // during a continuation? Does it get allocated into something? If its the + // root one, we should move it into a function? let mut vm_instance = VmCore::new( RootedInstructions::new(instructions), constant_map, - Shared::clone(&spans), self, &spans, )?; @@ -895,7 +858,7 @@ impl SteelThread { // This is our pseudo "dynamic unwind" // If we need to, we'll walk back on the stack and find any handlers to pop 'outer: loop { - let result = vm_instance.vm().map_err(|mut error| { + let result = vm_instance.vm().map_err(|error| { error .set_span_if_none(vm_instance.current_span()) .with_stack_trace(vm_instance.snapshot_stack_trace()) @@ -915,7 +878,7 @@ impl SteelThread { if last .attachments .as_mut() - .and_then(|mut x| x.weak_continuation_mark.take()) + .and_then(|x| x.weak_continuation_mark.take()) .is_some() { vm_instance.thread.stack.truncate(last.sp as _); @@ -927,8 +890,7 @@ impl SteelThread { vm_instance.close_continuation_marks(&last); } - if let Some(handler) = - last.attachments.as_mut().and_then(|mut x| x.handler.take()) + if let Some(handler) = last.attachments.as_mut().and_then(|x| x.handler.take()) { // Drop the stack BACK to where it was on this level vm_instance.thread.stack.truncate(last.sp); @@ -962,10 +924,7 @@ impl SteelThread { panic!("This shouldn't happen") } - #[cfg(not(feature = "unsafe-internals"))] - { - last.function = closure.clone(); - } + last.function = closure.clone(); vm_instance.ip = 0; @@ -984,8 +943,6 @@ impl SteelThread { } self.stack.clear(); - // self.current_frame = StackFrame::main(); - unsafe { Shared::from_raw(raw_keep_alive) }; return Err(e); @@ -996,11 +953,6 @@ impl SteelThread { // Clean up self.stack.clear(); - - // self.current_frame = StackFrame::main(); - - // dbg!(&self.stack_frames); - unsafe { Shared::from_raw(raw_keep_alive) }; return result; @@ -1014,7 +966,6 @@ pub struct OpenContinuationMark { // Lazily capture the frames we need to? pub(crate) current_frame: StackFrame, pub(crate) stack_frame_offset: usize, - // instructions: Shared<[DenseInstruction]>, instructions: RootedInstructions, // Captured at creation, everything on the stack @@ -1253,35 +1204,19 @@ impl WeakContinuation { pub struct ClosedContinuation { pub(crate) stack: Vec, pub(crate) current_frame: StackFrame, - // instructions: Shared<[DenseInstruction]>, instructions: RootedInstructions, pub(crate) stack_frames: Vec, ip: usize, sp: usize, pop_count: usize, + // #[cfg(feature = "rooted-instructions")] + // rooted_instructions: Vec>, #[cfg(debug_assertions)] closed_continuation: Option>, } pub trait VmContext { - // This allows for some funky self calling business - fn call_function_one_arg(&mut self, function: &SteelVal, arg: SteelVal) -> Result; - - // Call with two args - fn call_function_two_arg( - &mut self, - function: &SteelVal, - arg1: SteelVal, - arg2: SteelVal, - ) -> Result; - - fn call_function_many_args( - &mut self, - function: &SteelVal, - args: List, - ) -> Result; - fn call_transduce( &mut self, ops: &[Transducers], @@ -1301,46 +1236,6 @@ pub type BuiltInSignature = for<'a, 'b> fn(&'a mut VmCore<'b>, &[SteelVal]) -> Option>; impl<'a> VmContext for VmCore<'a> { - fn call_function_one_arg(&mut self, function: &SteelVal, arg: SteelVal) -> Result { - let span = Span::default(); - self.call_func_or_else( - function, - arg, - &span, - throw!(TypeMismatch => format!("application not a procedure: {function}")), - ) - } - - fn call_function_two_arg( - &mut self, - function: &SteelVal, - arg1: SteelVal, - arg2: SteelVal, - ) -> Result { - let span = Span::default(); - self.call_func_or_else_two_args( - function, - arg1, - arg2, - &span, - throw!(TypeMismatch => format!("application not a procedure: {function}")), - ) - } - - fn call_function_many_args( - &mut self, - function: &SteelVal, - args: List, - ) -> Result { - let span = Span::default(); - self.call_func_or_else_many_args( - function, - args, - &span, - throw!(TypeMismatch => format!("application not a procedure: {function}")), - ) - } - fn call_transduce( &mut self, ops: &[Transducers], @@ -1433,8 +1328,11 @@ impl<'a> VmContext for VmCore<'a> { // } pub struct VmCore<'a> { - // pub(crate) instructions: Shared<[DenseInstruction]>, pub(crate) instructions: RootedInstructions, + + // TODO: Replace this with a thread local constant map! + // that way reads are fast - and any updates to it are + // broadcast from the shared constant map. pub(crate) constants: ConstantMap, pub(crate) ip: usize, pub(crate) sp: usize, @@ -1448,10 +1346,8 @@ pub struct VmCore<'a> { // impl<'a> VmCore<'a> { fn new_unchecked( - // instructions: Shared<[DenseInstruction]>, instructions: RootedInstructions, constants: ConstantMap, - spans: Shared<[Span]>, thread: &'a mut SteelThread, root_spans: &'a [Span], ) -> VmCore<'a> { @@ -1468,10 +1364,8 @@ impl<'a> VmCore<'a> { } fn new( - // instructions: Shared<[DenseInstruction]>, instructions: RootedInstructions, constants: ConstantMap, - spans: Shared<[Span]>, thread: &'a mut SteelThread, root_spans: &'a [Span], ) -> Result> { @@ -1479,11 +1373,6 @@ impl<'a> VmCore<'a> { stop!(Generic => "empty stack!") } - // Set up the instruction pointers here - // let function = Gc::new(ByteCodeLambda::main(instructions.iter().copied().collect())); - - // let current_frame = StackFrame::new(0, function, 0, Rc::clone(&instructions)); - Ok(VmCore { instructions, constants, @@ -1538,7 +1427,7 @@ impl<'a> VmCore<'a> { } #[inline(always)] - pub fn safepoint_or_interrupt(&self) -> Result<()> { + pub fn safepoint_or_interrupt(&mut self) -> Result<()> { // Check if we need to be paused if self .thread @@ -1557,7 +1446,8 @@ impl<'a> VmCore<'a> { ThreadState::PausedAtSafepoint => { // TODO: // Insert the code to do the stack things here - self.thread.synchronizer.ctx.store(Some(self.thread as _)); + let ptr = self.thread as _; + self.thread.synchronizer.ctx.store(Some(ptr)); self.park_thread_while_paused(); self.thread.synchronizer.ctx.store(None); } @@ -1607,7 +1497,7 @@ impl<'a> VmCore<'a> { ) } - fn weak_collection(&mut self) { + pub fn weak_collection(&mut self) { self.thread.heap.lock().unwrap().weak_collection(); } @@ -1653,7 +1543,7 @@ impl<'a> VmCore<'a> { // how the existing call/cc implementation works already, which would be nice. However - // when _replaying_ the continuation, we should also assume that it can only be replayed // once to avoid copying the whole thing. - fn new_oneshot_continuation_from_state(&mut self) -> ClosedContinuation { + pub fn new_oneshot_continuation_from_state(&mut self) -> ClosedContinuation { ClosedContinuation { stack: std::mem::take(&mut self.thread.stack), instructions: self.instructions.clone(), @@ -1761,7 +1651,6 @@ impl<'a> VmCore<'a> { // Reset state FULLY pub(crate) fn call_with_instructions_and_reset_state( &mut self, - // closure: Shared<[DenseInstruction]>, closure: RootedInstructions, ) -> Result { let old_ip = self.ip; @@ -1774,9 +1663,7 @@ impl<'a> VmCore<'a> { self.depth += 1; - // println!("Before: {:?}", self.thread.stack_frames.len()); - - let mut res = Ok(SteelVal::Void); + let res; 'outer: loop { let result = self @@ -1799,7 +1686,7 @@ impl<'a> VmCore<'a> { if last .attachments .as_mut() - .and_then(|mut x| x.weak_continuation_mark.take()) + .and_then(|x| x.weak_continuation_mark.take()) .is_some() { self.thread.stack.truncate(last.sp); @@ -1812,8 +1699,7 @@ impl<'a> VmCore<'a> { self.close_continuation_marks(&last); } - if let Some(handler) = - last.attachments.as_mut().and_then(|mut x| x.handler.take()) + if let Some(handler) = last.attachments.as_mut().and_then(|x| x.handler.take()) { // Drop the stack BACK to where it was on this level self.thread.stack.truncate(last.sp); @@ -1842,12 +1728,7 @@ impl<'a> VmCore<'a> { self.sp = last.sp; self.instructions = closure.body_exp(); - // last.handler = None; - - #[cfg(not(feature = "unsafe-internals"))] - { - last.function = closure.clone(); - } + last.function = closure.clone(); self.ip = 0; @@ -1993,8 +1874,6 @@ impl<'a> VmCore<'a> { cont: Continuation, args: impl IntoIterator, ) -> Result { - let prev_length = self.thread.stack.len(); - for arg in args { self.thread.stack.push(arg); } @@ -2101,7 +1980,6 @@ impl<'a> VmCore<'a> { Gc::clone(closure), 0, RootedInstructions::new(THE_EMPTY_INSTRUCTION_SET.with(|x| x.clone())), - // Rc::from([]), )); self.sp = prev_length; @@ -2122,10 +2000,6 @@ impl<'a> VmCore<'a> { closure: &Gc, arg: SteelVal, ) -> Result { - // thread_local! { - // static EMPTY_INSTRUCTIONS: Shared<[DenseInstruction]> = Shared::new([]); - // } - let prev_length = self.thread.stack.len(); self.thread.stack_frames.push(StackFrame::new( @@ -2178,7 +2052,7 @@ impl<'a> VmCore<'a> { self.thread.stack[read_local.payload_size.to_usize() + offset].clone(); // get the const - let const_val = self.constants.get(push_const.payload_size.to_usize()); + let const_val = self.constants.get_value(push_const.payload_size.to_usize()); let result = match $name(&[local_value, const_val]) { Ok(value) => value, @@ -2458,18 +2332,21 @@ impl<'a> VmCore<'a> { // get the const value, if it can fit into the value... let r = push_const.payload_size.to_usize() as isize; - // sub_handler_none_int - - // TODO: Inline this here - so that we can just refer to the value - // and don't have to invoke a clone here. - // let result = sub_handler_none_int(self, local_value, const_val)?; - let result = match l { - SteelVal::IntV(_) - | SteelVal::NumV(_) + // Fast path with an integer, otherwise slow path + SteelVal::IntV(l) => { + match l.checked_sub(&r) { + Some(r) => SteelVal::IntV(r), + // Slow path + None => SteelVal::BigNum(Gc::new(BigInt::from(*l) - r)), + } + } + + SteelVal::NumV(_) | SteelVal::Rational(_) | SteelVal::BigNum(_) | SteelVal::BigRational(_) => { + // TODO: Create a specialized version of this! subtract_primitive(&[l.clone(), SteelVal::IntV(r)]) .map_err(|x| x.set_span_if_none(self.current_span()))? } @@ -2488,7 +2365,6 @@ impl<'a> VmCore<'a> { .. } => { // inline_register_primitive_immediate!(subtract_primitive) - let read_local = &self.instructions[self.ip]; let push_const = &self.instructions[self.ip + 1]; @@ -2578,17 +2454,39 @@ impl<'a> VmCore<'a> { // add_handler_payload(self, 2)?; let right = self.thread.stack.pop().unwrap(); - let left = self.thread.stack.last().unwrap(); + let left = self.thread.stack.last_mut().unwrap(); - let result = match handlers::add_handler_none_none(left, &right) { + let result = match add_two(left, &right) { Ok(value) => value, Err(e) => return Err(e.set_span_if_none(self.current_span())), }; - *self.thread.stack.last_mut().unwrap() = result; + *left = result; self.ip += 2; } + + DenseInstruction { + op_code: OpCode::BINOPADDTAIL, + .. + } => { + let right = self.thread.stack.pop().unwrap(); + let left = self.thread.stack.pop().unwrap(); + + let result = match handlers::add_handler_none_none(&left, &right) { + Ok(value) => value, + Err(e) => return Err(e.set_span_if_none(self.current_span())), + }; + + // *left = result; + + if let Some(r) = self.handle_pop_pure_value(result) { + return r; + } + + // self.ip += 2; + } + DenseInstruction { op_code: OpCode::SUB, payload_size, @@ -2662,7 +2560,7 @@ impl<'a> VmCore<'a> { payload_size, .. } => { - let val = self.constants.get(payload_size.to_usize()); + let val = self.constants.get_value(payload_size.to_usize()); self.thread.stack.push(val); self.ip += 1; } @@ -2771,6 +2669,18 @@ impl<'a> VmCore<'a> { self.thread.stack.push(SteelVal::INT_TWO); self.ip += 1; } + + DenseInstruction { + op_code: OpCode::LOADINT1POP, + .. + } => { + // self.thread.stack.push(SteelVal::INT_TWO); + self.ip += 1; + if let Some(r) = self.handle_pop_pure_value(SteelVal::INT_TWO) { + return r; + } + } + // DenseInstruction { // op_code: OpCode::CGLOCALCONST, // payload_size, @@ -2814,10 +2724,56 @@ impl<'a> VmCore<'a> { .. } => { let next_inst = self.instructions[self.ip + 1]; - self.handle_tail_call_global( - payload_size.to_usize(), - next_inst.payload_size.to_usize(), - )?; + // self.handle_tail_call_global( + // payload_size.to_usize(), + // next_inst.payload_size.to_usize(), + // )?; + + let stack_func = self + .thread + .global_env + .repl_lookup_idx(payload_size.to_usize()); + self.ip += 1; + let payload_size = next_inst.payload_size.to_usize(); + // self.handle_tail_call(stack_func, next_inst.payload_size.to_usize()); + + use SteelVal::*; + + match stack_func { + FuncV(f) => { + let last_index = self.thread.stack.len() - payload_size; + let result = + match self.thread.enter_safepoint(move |ctx: &SteelThread| { + f(&ctx.stack[last_index..]) + }) { + Ok(v) => v, + Err(e) => return Err(e.set_span_if_none(self.current_span())), + }; + + // This is the old way... lets see if the below way improves the speed + self.thread.stack.truncate(last_index); + if let Some(r) = self.handle_pop_pure_value(result) { + return r; + } + Ok(()) + } + MutFunc(f) => self.call_primitive_mut_func(f, payload_size), + BoxedFunction(f) => self.call_boxed_func(f.func(), payload_size), + Closure(closure) => { + self.new_handle_tail_call_closure(closure, payload_size) + } + BuiltIn(f) => self.call_builtin_func(f, payload_size), + CustomStruct(s) => self.call_custom_struct(&s, payload_size), + ContinuationFunction(cc) => self.call_continuation(cc), + _ => { + // println!("{:?}", self.stack); + // println!("{:?}", self.stack_index); + // println!("Bad tail call"); + // crate::core::instructions::pretty_print_dense_instructions(&self.instructions); + stop!(BadSyntax => format!("TailCall - Application not a procedure or function type + not supported: {stack_func}"); self.current_span()); + } + }? } DenseInstruction { op_code: OpCode::FUNC, @@ -2881,8 +2837,6 @@ impl<'a> VmCore<'a> { self.ip = 0; - let mut closure_arity = last_stack_frame.function.arity(); - // TODO: Adjust the stack for multiple arity functions let is_multi_arity = last_stack_frame.function.is_multi_arity; let original_arity = last_stack_frame.function.arity(); @@ -2944,12 +2898,10 @@ impl<'a> VmCore<'a> { // self.stack[offset + i] = self.stack[back + i].clone(); // } - // self.stack.truncate(offset + current_arity); - let _ = self.thread.stack.drain(offset..back); - - // println!("stack after truncating: {:?}", self.stack); } + + // Blindly go to the next instructions DenseInstruction { op_code: OpCode::JMP, payload_size, @@ -2957,6 +2909,18 @@ impl<'a> VmCore<'a> { } => { self.ip = payload_size.to_usize(); } + + // If the JMP points to a pop, just pop + DenseInstruction { + op_code: OpCode::POPJMP, + .. + } => { + // self.ip = payload_size.to_usize(); + if let Some(r) = self.handle_pop_pure() { + return r; + } + } + DenseInstruction { op_code: OpCode::BEGINSCOPE, .. @@ -3117,11 +3081,8 @@ impl<'a> VmCore<'a> { // if the last function was called in tail position. fn enclosing_span(&self) -> Option { if self.thread.stack_frames.len() > 1 { - let back_two = self.thread.stack_frames.len() - 2; - if let [second, last] = &self.thread.stack_frames[self.thread.stack_frames.len() - 2..] { - let id = second.function.id; let spans = self.thread.function_interner.spans.get(&second.function.id); spans @@ -3167,10 +3128,69 @@ impl<'a> VmCore<'a> { } #[inline(always)] - fn handle_pop_pure(&mut self) -> Option> { + fn handle_pop_pure_value(&mut self, value: SteelVal) -> Option> { // Check that the amount we're looking to pop and the function stack length are equivalent // otherwise we have a problem + // println!("{} - {}", self.pop_count, self.thread.stack_frames.len()); + self.pop_count -= 1; + + let last = self.thread.stack_frames.pop(); + + // let should_return = self.stack_frames.is_empty(); + let should_continue = self.pop_count != 0; + + if should_continue { + let last = last.unwrap(); + // let last = unsafe { last.unwrap_unchecked() }; + let rollback_index = last.sp; + + self.close_continuation_marks(&last); + + // let _ = self + // .thread + // .stack + // .drain(rollback_index..self.thread.stack.len() - 1); + + self.thread.stack.truncate(rollback_index); + self.thread.stack.push(value); + + self.ip = last.ip; + self.instructions = last.instructions; + + self.sp = self.get_last_stack_frame_sp(); + + None + } else { + // let ret_val = self.thread.stack.pop().ok_or_else(|| { + // SteelErr::new(ErrorKind::Generic, "stack empty at pop".to_string()) + // .with_span(self.current_span()) + // }); + + let ret_val = Ok(value); + + let rollback_index = last + .map(|x| { + self.close_continuation_marks(&x); + x.sp + }) + .unwrap_or(0); + + // Move forward past the pop + self.ip += 1; + + self.thread.stack.truncate(rollback_index); + self.sp = 0; + + Some(ret_val) + } + } + + #[inline(always)] + fn handle_pop_pure(&mut self) -> Option> { + // Check that the amount we're looking to pop and the function stack length are equivalent + // otherwise we have a problem + // println!("{} - {}", self.pop_count, self.thread.stack_frames.len()); self.pop_count -= 1; let last = self.thread.stack_frames.pop(); @@ -3180,6 +3200,7 @@ impl<'a> VmCore<'a> { if should_continue { let last = last.unwrap(); + // let last = unsafe { last.unwrap_unchecked() }; let rollback_index = last.sp; @@ -3219,13 +3240,6 @@ impl<'a> VmCore<'a> { } } - #[inline(always)] - fn update_state_with_frame(&mut self, last: StackFrame) { - self.ip = last.ip; - self.instructions = last.instructions; - // self.spans = last.spans; - } - #[inline(always)] fn get_last_stack_frame_sp(&self) -> usize { self.thread.stack_frames.last().map(|x| x.sp).unwrap_or(0) @@ -3245,10 +3259,28 @@ impl<'a> VmCore<'a> { fn handle_set(&mut self, index: usize) -> Result<()> { let value_to_assign = self.thread.stack.pop().unwrap(); + // STOP THREADS -> apply the set index across all of them. + // set! is _much_ slower than it needs to be. + self.thread.synchronizer.stop_threads(); + let value = self .thread .global_env - .repl_set_idx(index, value_to_assign)?; + .repl_set_idx(index, value_to_assign.clone())?; + + // Updating on all + unsafe { + self.thread.synchronizer.call_per_ctx(|thread| { + thread + .global_env + .repl_set_idx(index, value_to_assign.clone()) + .unwrap(); + }); + } + + // Resume. + // Apply these to all of the things. + self.thread.synchronizer.resume_threads(); self.thread.stack.push(value); self.ip += 1; @@ -3362,24 +3394,15 @@ impl<'a> VmCore<'a> { // let offset = self.stack_frames.last().map(|x| x.index).unwrap_or(0); let offset = self.get_offset(); - // if index + offset >= self.thread.stack.len() { - // dbg!(&self.thread.stack.get(offset..)); - // // dbg!(&self.current_span()); - - // pretty_print_dense_instructions(&self.instructions); - // dbg!(self.ip); - // } - let value = self.thread.stack[index + offset].clone(); - self.thread.stack.push(value); self.ip += 1; Ok(()) } + // How many captures? fn handle_read_captures(&mut self, index: usize) -> Result<()> { let value = self.thread.stack_frames.last().unwrap().function.captures()[index].clone(); - self.thread.stack.push(value); self.ip += 1; Ok(()) @@ -3544,9 +3567,7 @@ impl<'a> VmCore<'a> { closure_body, arity.to_usize(), is_multi_arity, - Vec::new(), - // Vec::new(), - // Rc::clone(&spans), + CaptureVec::new(), )); self.thread @@ -3606,7 +3627,7 @@ impl<'a> VmCore<'a> { self.ip += 1; // TODO preallocate size - let mut captures = Vec::with_capacity(ndefs); + let mut captures = CaptureVec::with_capacity(ndefs); // TODO clean this up a bit // hold the spot for where we need to jump aftwards @@ -3622,9 +3643,6 @@ impl<'a> VmCore<'a> { // So, this should probably do something like this: if let Some(guard) = self.thread.stack_frames.last() { - let guard = self.thread.stack_frames.last().unwrap(); - // let stack_index = self.stack_index.last().copied().unwrap_or(0); - // let stack_index = self.stack_frames.last().map(|x| x.index).unwrap_or(0); let stack_index = self.get_offset(); for _ in 0..ndefs { @@ -3766,8 +3784,7 @@ impl<'a> VmCore<'a> { closure_body, arity.to_usize(), is_multi_arity, - Vec::new(), - // Vec::new(), + CaptureVec::new(), ); self.thread @@ -3794,19 +3811,32 @@ impl<'a> VmCore<'a> { Ok(()) } - // Enter a new thread, passing values that can be serialized - // Resolve all references, attempt to instantiate a new engine on the other side? - fn new_thread(&mut self, function: Gc) { - todo!() - - // Analyze the dependencies of the function, and see if its safe to be spawned on another thread - } - // #[inline(always)] fn handle_bind(&mut self, payload_size: usize) { + let value = self.thread.stack.pop().unwrap(); + + // TODO: Do the same thing here: + self.thread.synchronizer.stop_threads(); + + // println!("Pausing threads to define new variable"); self.thread .global_env - .repl_define_idx(payload_size, self.thread.stack.pop().unwrap()); + .repl_define_idx(payload_size, value.clone()); + + // Updating on all + unsafe { + self.thread.synchronizer.call_per_ctx(|thread| { + thread + .global_env + .repl_define_idx(payload_size, value.clone()); + }); + } + + // println!("Finished broadcasting new variable"); + + // Resume. + // Apply these to all of the things. + self.thread.synchronizer.resume_threads(); self.ip += 1; } @@ -3873,60 +3903,9 @@ impl<'a> VmCore<'a> { } #[inline(always)] - fn adjust_stack_for_multi_arity_tco( + fn adjust_stack_for_multi_arity( &mut self, - is_multi_arity: bool, - original_arity: usize, - payload_size: usize, - new_arity: &mut usize, - ) -> Result<()> { - if likely(!is_multi_arity) { - if unlikely(original_arity != payload_size) { - stop!(ArityMismatch => format!("function expected {} arguments, found {}", original_arity, payload_size); self.current_span()); - } - } else { - // println!( - // "multi closure function, multi arity, arity: {:?}", - // closure.arity() - // ); - - if payload_size < original_arity - 1 { - stop!(ArityMismatch => format!("function expected at least {} arguments, found {}", original_arity, payload_size); self.current_span()); - } - - // (define (test x . y)) - // (test 1 2 3 4 5) - // in this case, arity = 2 and payload size = 5 - // pop off the last 4, collect into a list - let amount_to_remove = 1 + payload_size - original_arity; - - let values = self - .thread - .stack - .drain(self.thread.stack.len() - amount_to_remove..) - .collect(); - // .split_off(self.thread.stack.len() - amount_to_remove); - - let list = SteelVal::ListV(values); - - self.thread.stack.push(list); - - *new_arity = original_arity; - - // println!("Stack after list conversion: {:?}", self.stack); - } - - // else if closure.arity() != payload_size { - // stop!(ArityMismatch => format!("function expected {} arguments, found {}", closure.arity(), payload_size); self.current_span()); - // } - - Ok(()) - } - - #[inline(always)] - fn adjust_stack_for_multi_arity( - &mut self, - closure: &Gc, + closure: &Gc, payload_size: usize, new_arity: &mut usize, ) -> Result<()> { @@ -4125,11 +4104,18 @@ impl<'a> VmCore<'a> { let last_index = self.thread.stack.len() - payload_size; // Register safepoint - let result = self + // let result = self + // .thread + // .enter_safepoint(move |ctx: &SteelThread| f(&ctx.stack[last_index..])) + // .map_err(|e| e.set_span_if_none(self.current_span()))?; + + let result = match self .thread .enter_safepoint(move |ctx: &SteelThread| f(&ctx.stack[last_index..])) - // // TODO: Can we just apply this at the end? - .map_err(|e| e.set_span_if_none(self.current_span()))?; + { + Ok(v) => v, + Err(e) => return Err(e.set_span_if_none(self.current_span())), + }; // This is the old way... lets see if the below way improves the speed self.thread.stack.truncate(last_index); @@ -4139,37 +4125,6 @@ impl<'a> VmCore<'a> { Ok(()) } - fn call_future_func_on_stack( - &mut self, - func: Rc Result>, - payload_size: usize, - ) -> Result<()> { - // stack is [args ... function] - let len = self.thread.stack.len(); - // This is the start of the arguments - let last_index = len - payload_size - 1; - - // Peek the range for the [args ... function] - // ~~~~~~~~~~ - // let result = func(self.stack.peek_range_double(last_index..len)) - // .map_err(|x| x.set_span_if_none(self.current_span()))?; - - let result = match func(&self.thread.stack[last_index..len]) { - Ok(value) => value, - Err(e) => return Err(e.set_span_if_none(self.current_span())), - }; - - // This is the old way, but now given that the function is included on the stack, this should work... - // self.stack.truncate(last_index); - // self.stack.push(result); - - self.thread.stack.truncate(last_index + 1); - *self.thread.stack.last_mut().unwrap() = SteelVal::FutureV(Gc::new(result)); - - self.ip += 1; - Ok(()) - } - // #[inline(always)] fn call_future_func( &mut self, @@ -4218,74 +4173,6 @@ impl<'a> VmCore<'a> { Ok(()) } - // #[inline(always)] - fn handle_lazy_closure( - &mut self, - closure: &Gc, - local: SteelVal, - const_value: SteelVal, - ) -> Result<()> { - self.cut_sequence(); - - let prev_length = self.thread.stack.len(); - - // push them onto the stack if we need to - self.thread.stack.push(local); - self.thread.stack.push(const_value); - - // Push new stack frame - self.thread.stack_frames.push( - StackFrame::new( - prev_length, - Gc::clone(closure), - self.ip + 4, - // Shared::clone(&self.instructions), - self.instructions.clone(), - // Rc::clone(&self.spans), - ), // .with_span(self.current_span()), - ); - - // self.current_frame.sp = prev_length; - - // self.current_frame.ip += 4; - - // // Set the sp to be the current values on this - // let mut current_frame = StackFrame::new( - // prev_length, - // Gc::clone(closure), - // self.ip + 4, - // Rc::clone(&self.instructions), - // ) - // .with_span(self.current_span()); - - // std::mem::swap(&mut current_frame, &mut self.current_frame); - // self.stack_frames.push(current_frame); - - self.sp = prev_length; - - // Push on the function stack so we have access to it later - // self.function_stack - // .push(CallContext::new(Gc::clone(closure)).with_span(self.current_span())); - - if closure.is_multi_arity { - panic!("Calling lazy closure with multi arity"); - } - - if closure.arity() != 2 { - stop!(ArityMismatch => format!("function expected {} arguments, found {}", closure.arity(), 2); self.current_span()); - } - - // self.current_arity = Some(closure.arity()); - - self.check_stack_overflow()?; - self.pop_count += 1; - - self.instructions = closure.body_exp(); - // self.spans = closure.spans(); - self.ip = 0; - Ok(()) - } - #[inline(always)] fn check_stack_overflow(&self) -> Result<()> { if CHECK_STACK_OVERFLOW { @@ -4305,97 +4192,6 @@ impl<'a> VmCore<'a> { // self.stack_frames.last().map(|x| x.index).unwrap_or(0) } - // #[inline(always)] - fn handle_lazy_function_call( - &mut self, - stack_func: SteelVal, - local: SteelVal, - const_value: SteelVal, - ) -> Result<()> { - use SteelVal::*; - - match &stack_func { - BoxedFunction(f) => { - self.thread.stack.push( - f.func()(&[local, const_value]) - .map_err(|x| x.set_span_if_none(self.current_span()))?, - ); - self.ip += 4; - } - FuncV(f) => { - // self.stack - // .push(f(&[local, const_value]).map_err(|x| x.set_span_if_none(self.current_span()))?); - // self.ip += 4; - - match f(&[local, const_value]) { - Ok(value) => self.thread.stack.push(value), - Err(e) => return Err(e.set_span_if_none(self.current_span())), - } - - // self.stack - // .push(f(&[local, const_value]).map_err(|x| x.set_span_if_none(self.current_span()))?); - self.ip += 4; - } - FutureFunc(f) => { - let result = SteelVal::FutureV(Gc::new( - f(&[local, const_value]) - .map_err(|x| x.set_span_if_none(self.current_span()))?, - )); - - self.thread.stack.push(result); - self.ip += 4; - } - // ContractedFunction(cf) => { - // if let Some(arity) = cf.arity() { - // if arity != 2 { - // stop!(ArityMismatch => format!("function expected {} arguments, found {}", arity, 2); self.current_span()); - // } - // } - - // // if A::enforce_contracts() { - // let result = cf.apply(vec![local, const_value], &self.current_span(), self)?; - - // self.thread.stack.push(result); - // self.ip += 4; - // // } else { - // // self.handle_lazy_function_call(cf.function.clone(), local, const_value)?; - // // } - // } - // Contract(c) => self.call_contract(c, payload_size, span)?, - ContinuationFunction(_cc) => { - unimplemented!("calling continuation lazily not yet handled"); - } - Closure(closure) => self.handle_lazy_closure(closure, local, const_value)?, - MutFunc(func) => { - let mut args = [local, const_value]; - self.thread - .stack - .push(func(&mut args).map_err(|x| x.set_span_if_none(self.current_span()))?); - - self.ip += 4; - } - CustomStruct(s) => { - if let Some(proc) = s.maybe_proc() { - return self.handle_lazy_function_call(proc.clone(), local, const_value); - } else { - stop!(Generic => "attempted to call struct as function, but the struct does not have a function to call!") - } - } - // BuiltIn(func) => { - // let args = [local, const_value]; - // let result = - // func(self, &args).map_err(|x| x.set_span_if_none(self.current_span()))?; - // self.stack.push(result); - // self.ip += 4; - // } - _ => { - log::error!("{stack_func:?}"); - stop!(BadSyntax => format!("Function application not a procedure or function type not supported, {stack_func}"); self.current_span()); - } - } - Ok(()) - } - // // #[inline(always)] pub(crate) fn handle_function_call_closure( &mut self, @@ -4413,13 +4209,12 @@ impl<'a> VmCore<'a> { self.sp = self.thread.stack.len() - closure.arity(); - let mut instructions = closure.body_exp(); + let instructions = closure.body_exp(); self.thread.stack_frames.push(StackFrame::new( self.sp, closure, self.ip + 1, - // Rc::clone(&self.instructions), std::mem::replace(&mut self.instructions, instructions), )); @@ -4433,35 +4228,6 @@ impl<'a> VmCore<'a> { Ok(()) } - // TODO improve this a bit - #[inline(always)] - fn handle_function_call_closure_jit_without_profiling( - &mut self, - closure: Gc, - payload_size: usize, - ) -> Result<()> { - self.adjust_stack_for_multi_arity(&closure, payload_size, &mut 0)?; - - self.sp = self.thread.stack.len() - closure.arity(); - - let mut instructions = closure.body_exp(); - - std::mem::swap(&mut instructions, &mut self.instructions); - - // Do this _after_ the multi arity business - // TODO: can these rcs be avoided - self.thread.stack_frames.push( - StackFrame::new(self.sp, closure, self.ip + 1, instructions), // .with_span(self.current_span()), - ); - - self.check_stack_overflow()?; - - self.pop_count += 1; - - self.ip = 0; - Ok(()) - } - // TODO improve this a bit // #[inline(always)] #[inline(always)] @@ -4471,7 +4237,7 @@ impl<'a> VmCore<'a> { payload_size: usize, ) -> Result<()> { // Record the end of the existing sequence - self.cut_sequence(); + // self.cut_sequence(); // Jit profiling -> Make sure that we really only trace once we pass a certain threshold // For instance, if this function @@ -4480,7 +4246,39 @@ impl<'a> VmCore<'a> { closure.increment_call_count(); } - self.handle_function_call_closure_jit_without_profiling(closure, payload_size) + self.adjust_stack_for_multi_arity(&closure, payload_size, &mut 0)?; + self.sp = self.thread.stack.len() - closure.arity(); + + #[cfg(not(feature = "rooted-instructions"))] + { + let mut instructions = closure.body_exp(); + + std::mem::swap(&mut instructions, &mut self.instructions); + + // Do this _after_ the multi arity business + // TODO: can these rcs be avoided + self.thread.stack_frames.push( + StackFrame::new(self.sp, closure, self.ip + 1, instructions), // .with_span(self.current_span()), + ); + } + + #[cfg(feature = "rooted-instructions")] + { + let frame = StackFrame { + sp: self.sp, + function: closure, + ip: self.ip + 1, + instructions: self.instructions, + attachments: None, + }; + self.instructions = frame.function.body_exp(); + self.thread.stack_frames.push(frame); + } + + self.check_stack_overflow()?; + self.pop_count += 1; + self.ip = 0; + Ok(()) } #[inline(always)] @@ -4498,149 +4296,14 @@ impl<'a> VmCore<'a> { MutFunc(f) => self.call_primitive_mut_func(f, payload_size), FutureFunc(f) => self.call_future_func(f, payload_size), ContinuationFunction(cc) => self.call_continuation(cc), - BuiltIn(f) => { - // self.ip -= 1; - self.call_builtin_func(f, payload_size) - } - CustomStruct(s) => self.call_custom_struct(&s, payload_size), - _ => { - // Explicitly mark this as unlikely - cold(); - log::error!("{stack_func:?}"); - log::error!("Stack: {:?}", self.thread.stack); - stop!(BadSyntax => format!("Function application not a procedure or function type not supported: {}", stack_func); self.current_span()); - } - } - } - - #[inline(always)] - fn handle_global_function_call_by_ref( - &mut self, - stack_func: &SteelVal, - payload_size: usize, - ) -> Result<()> { - use SteelVal::*; - - match stack_func { - Closure(closure) => { - self.handle_function_call_closure_jit(closure.clone(), payload_size) - } - FuncV(f) => self.call_primitive_func(*f, payload_size), - BoxedFunction(f) => self.call_boxed_func(f.func(), payload_size), - MutFunc(f) => self.call_primitive_mut_func(*f, payload_size), - FutureFunc(f) => self.call_future_func(f.clone(), payload_size), - ContinuationFunction(cc) => self.call_continuation(cc.clone()), - BuiltIn(f) => { - // self.ip -= 1; - self.call_builtin_func(*f, payload_size) - } + BuiltIn(f) => self.call_builtin_func(f, payload_size), CustomStruct(s) => self.call_custom_struct(&s, payload_size), _ => { - // Explicitly mark this as unlikely cold(); - log::error!("{stack_func:?}"); - log::error!("Stack: {:?}", self.thread.stack); stop!(BadSyntax => format!("Function application not a procedure or function type not supported: {}", stack_func); self.current_span()); } } } - #[inline(always)] - fn handle_non_instr_global_function_call( - &mut self, - stack_func: SteelVal, - args: &mut [SteelVal], - ) -> Result> { - use SteelVal::*; - - self.ip += 1; - - match stack_func { - BoxedFunction(f) => f.func()(args).map(Some), - MutFunc(f) => f(args).map(Some), - FuncV(f) => f(args).map(Some), - FutureFunc(f) => Ok(SteelVal::FutureV(Gc::new(f(args)?))).map(Some), - Closure(closure) => { - let arity = args.len(); - - for arg in args { - self.thread - .stack - .push(std::mem::replace(arg, SteelVal::Void)); - } - - self.handle_function_call_closure_jit(closure, arity) - .map(|_| None) - } - // TODO: Implement this for other functions - _ => { - log::error!("{stack_func:?}"); - log::error!("Stack: {:?}", self.thread.stack); - stop!(BadSyntax => format!("Function application not a procedure or function type not supported: {stack_func}"); self.current_span()); - } - } - - // Ok(()) - } - - // #[inline(always)] - // fn handle_non_instr_global_function_call_lazy_push( - // &mut self, - // stack_func: SteelVal, - // args: &mut [SteelVal], - // ) -> Result<()> { - // use SteelVal::*; - - // // self.ip += 1; - - // match stack_func { - // BoxedFunction(f) => { - // self.ip += 1; - // self.thread.stack.push(f.func()(args)?) - // } - // MutFunc(f) => { - // self.ip += 1; - // self.thread.stack.push(f(args)?) - // } - // FuncV(f) => { - // self.ip += 1; - // self.thread.stack.push(f(args)?) - // } - // FutureFunc(f) => { - // self.ip += 1; - // self.thread.stack.push(SteelVal::FutureV(Gc::new(f(args)?))) - // } - // Closure(closure) => { - // let arity = args.len(); - - // self.thread.stack.reserve(arity); - - // for arg in args { - // self.thread.stack.push(arg.clone()); - // } - - // // If we're here, we're already done profiling, and don't need to profile anymore - // self.handle_function_call_closure_jit(closure, arity)?; - // } - // // BuiltIn(f) => f(self, args), - // _ => { - // log::error!("Lazy push: {stack_func:?}"); - // log::error!("Stack: {:?}", self.thread.stack); - // stop!(BadSyntax => format!("Function application not a procedure or function type not supported: {stack_func}"); self.current_span()); - // } - // } - - // Ok(()) - // } - - // #[inline(always)] - // fn call_contract(&mut self, contract: &Gc, payload_size: usize) -> Result<()> { - // match contract.as_ref() { - // ContractType::Flat(f) => self.handle_function_call(f.predicate.clone(), payload_size), - // _ => { - // stop!(BadSyntax => "Function application not a procedure - cannot apply function contract to argument"); - // } - // } - // } // #[inline(always)] fn handle_function_call(&mut self, stack_func: SteelVal, payload_size: usize) -> Result<()> { @@ -4681,17 +4344,6 @@ pub fn current_function_span(ctx: &mut VmCore, args: &[SteelVal]) -> Option Option> { - if !args.is_empty() { - builtin_stop!(ArityMismatch => format!("current-function-span requires no arguments, found {}", args.len())) - } - - match ctx.enclosing_span() { - Some(s) => Some(Span::into_steelval(s)), - None => Some(Ok(SteelVal::Void)), - } -} - #[steel_derive::context(name = "inspect", arity = "Exact(1)")] pub fn inspect(ctx: &mut VmCore, args: &[SteelVal]) -> Option> { let guard = ctx.thread.sources.sources.lock().unwrap(); @@ -4769,7 +4421,7 @@ pub fn inspect(ctx: &mut VmCore, args: &[SteelVal]) -> Option> /// Inspect the locals at the given function. Probably need to provide a way to /// loop this back into the sources, in order to resolve any span information. -pub fn breakpoint(ctx: &mut VmCore, args: &[SteelVal]) -> Option> { +pub fn breakpoint(ctx: &mut VmCore, _args: &[SteelVal]) -> Option> { let offset = ctx.get_offset(); // Wait for user input to continue... @@ -4898,10 +4550,6 @@ pub fn call_with_exception_handler( None } -pub fn oneshot_call_cc(ctx: &mut VmCore, args: &[SteelVal]) -> Option> { - todo!("Create continuation that can only be used once!") -} - #[steel_derive::context(name = "call/cc", arity = "Exact(1)")] pub fn call_cc(ctx: &mut VmCore, args: &[SteelVal]) -> Option> { /* @@ -5019,12 +4667,9 @@ fn eval_program(program: crate::compiler::program::Executable, ctx: &mut VmCore) ); let Executable { - name, - version, - time_stamp, instructions, - constant_map, spans, + .. } = program; let mut bytecode = Vec::new(); let mut new_spans = Vec::new(); @@ -5091,7 +4736,7 @@ fn eval_program(program: crate::compiler::program::Executable, ctx: &mut VmCore) Shared::from(bytecode), 0, false, - Vec::new(), + CaptureVec::new(), )); ctx.thread .function_interner @@ -5111,7 +4756,7 @@ fn eval_program(program: crate::compiler::program::Executable, ctx: &mut VmCore) fn emit_expanded_file(path: String) { let mut engine = crate::steel_vm::engine::Engine::new(); - let mut contents = std::fs::read_to_string(&path).unwrap(); + let contents = std::fs::read_to_string(&path).unwrap(); engine.expand_to_file(contents, std::path::PathBuf::from(path)) } @@ -5189,7 +4834,6 @@ fn eval_file_impl(ctx: &mut crate::steel_vm::vm::VmCore, args: &[SteelVal]) -> R match res { Ok(program) => { - let symbol_map_offset = ctx.thread.compiler.read().symbol_map.len(); let result = program.build( "eval-context".to_string(), &mut ctx.thread.compiler.write().symbol_map, @@ -5216,7 +4860,6 @@ fn eval_string_impl(ctx: &mut crate::steel_vm::vm::VmCore, args: &[SteelVal]) -> match res { Ok(program) => { - let symbol_map_offset = ctx.thread.compiler.read().symbol_map.len(); let result = program.build( "eval-context".to_string(), &mut ctx.thread.compiler.write().symbol_map, @@ -5258,7 +4901,7 @@ pub(crate) fn list_modules(ctx: &mut VmCore, _args: &[SteelVal]) -> Option Option> { +pub(crate) fn environment_offset(ctx: &mut VmCore, _args: &[SteelVal]) -> Option> { Some(Ok(ctx.thread.global_env.len().into_steelval().unwrap())) } @@ -5266,7 +4909,7 @@ pub(crate) fn environment_offset(ctx: &mut VmCore, args: &[SteelVal]) -> Option< // Snag values, then expand them, then convert back? The constant conversion // back and forth will probably hamper performance significantly. That being said, // it is entirely at compile time, so probably _okay_ -pub(crate) fn expand_syntax_case_impl(ctx: &mut VmCore, args: &[SteelVal]) -> Result { +pub(crate) fn expand_syntax_case_impl(_ctx: &mut VmCore, args: &[SteelVal]) -> Result { if args.len() != 3 { stop!(ArityMismatch => format!("#%expand-template expected 3 arguments, found: {}", args.len())) } @@ -5274,7 +4917,7 @@ pub(crate) fn expand_syntax_case_impl(ctx: &mut VmCore, args: &[SteelVal]) -> Re let mut bindings: fxhash::FxHashMap<_, _> = if let SteelVal::HashMapV(h) = &args[1] { h.iter() .map(|(k, v)| match (k, v) { - (SteelVal::SymbolV(k), e) => Ok(( + (SteelVal::SymbolV(k), _e) => Ok(( InternedString::from_str(k.as_str()), crate::parser::ast::TryFromSteelValVisitorForExprKind::root(v)?, )), @@ -5309,7 +4952,7 @@ pub(crate) fn expand_syntax_case_impl(ctx: &mut VmCore, args: &[SteelVal]) -> Re let mut template = crate::parser::ast::TryFromSteelValVisitorForExprKind::root(&args[0])?; - expand_template(&mut template, &mut bindings, &mut binding_kind); + expand_template(&mut template, &mut bindings, &mut binding_kind)?; crate::parser::tryfrom_visitor::SyntaxObjectFromExprKind::try_from_expr_kind(template) } @@ -5471,7 +5114,7 @@ pub(crate) fn apply(ctx: &mut VmCore, args: &[SteelVal]) -> Option { - let mut args = l.into_iter().cloned().collect::>(); + let args = l.into_iter().cloned().collect::>(); let result = f.func()(&args).map_err(|e| e.set_span_if_none(ctx.current_span())); @@ -5536,6 +5179,7 @@ pub struct InstructionPattern { pub(crate) pattern: BlockPattern, } +#[cfg(feature = "dynamic")] impl InstructionPattern { pub fn new(block: Rc<[(OpCode, usize)]>, pattern: BlockPattern) -> Self { Self { block, pattern } @@ -5555,6 +5199,7 @@ pub struct BlockMetadata { created: bool, } +#[cfg(feature = "dynamic")] #[derive(Clone)] pub struct OpCodeOccurenceProfiler { occurrences: HashMap<(OpCode, usize), usize>, @@ -5564,6 +5209,7 @@ pub struct OpCodeOccurenceProfiler { sample_count: usize, } +#[cfg(feature = "dynamic")] impl OpCodeOccurenceProfiler { pub fn new() -> Self { OpCodeOccurenceProfiler { @@ -6244,6 +5890,7 @@ fn local_handler3(ctx: &mut VmCore<'_>) -> Result<()> { ctx.handle_local(3) } +#[cfg(feature = "dynamic")] // OpCode::SETLOCAL fn set_local_handler(ctx: &mut VmCore<'_>) -> Result<()> { let offset = ctx.instructions[ctx.ip].payload_size; @@ -6251,12 +5898,14 @@ fn set_local_handler(ctx: &mut VmCore<'_>) -> Result<()> { Ok(()) } +#[cfg(feature = "dynamic")] // OpCode::SETLOCAL fn set_local_handler_with_payload(ctx: &mut VmCore<'_>, payload: usize) -> Result<()> { ctx.handle_set_local(payload); Ok(()) } +#[cfg(feature = "dynamic")] // OpCode::CALLGLOBAL fn call_global_handler(ctx: &mut VmCore<'_>) -> Result<()> { // assert!(ctx.ip + 1 < ctx.instructions.len()); @@ -6267,6 +5916,7 @@ fn call_global_handler(ctx: &mut VmCore<'_>) -> Result<()> { ctx.handle_call_global(payload_size.to_usize(), next_inst.payload_size.to_usize()) } +#[cfg(feature = "dynamic")] // OpCode::CALLGLOBAL // TODO: Fix this! fn call_global_handler_with_payload(ctx: &mut VmCore<'_>, payload: usize) -> Result<()> { @@ -6275,28 +5925,26 @@ fn call_global_handler_with_payload(ctx: &mut VmCore<'_>, payload: usize) -> Res ctx.handle_call_global(payload, next_inst.payload_size.to_usize()) } -// TODO: Have a way to know the correct arity? -fn call_global_handler_no_stack( - ctx: &mut VmCore<'_>, - args: &mut [SteelVal], -) -> Result> { - // ctx.ip += 1; - let payload_size = ctx.instructions[ctx.ip].payload_size; - ctx.ip += 1; - - // TODO: Track the op codes of the surrounding values as well - // let next_inst = ctx.instructions[ctx.ip]; - - // println!("Looking up a function at index: {}", payload_size.to_usize()); - - let func = ctx - .thread - .global_env - .repl_lookup_idx(payload_size.to_usize()); - ctx.handle_non_instr_global_function_call(func, args) -} +// // TODO: Have a way to know the correct arity? +// fn call_global_handler_no_stack( +// ctx: &mut VmCore<'_>, +// args: &mut [SteelVal], +// ) -> Result> { +// // ctx.ip += 1; +// let payload_size = ctx.instructions[ctx.ip].payload_size; +// ctx.ip += 1; +// // TODO: Track the op codes of the surrounding values as well +// // let next_inst = ctx.instructions[ctx.ip]; +// // println!("Looking up a function at index: {}", payload_size.to_usize()); +// let func = ctx +// .thread +// .global_env +// .repl_lookup_idx(payload_size.to_usize()); +// ctx.handle_non_instr_global_function_call(func, args) +// } -fn num_equal_handler_no_stack(ctx: &mut VmCore<'_>, l: SteelVal, r: SteelVal) -> Result { +#[cfg(feature = "dynamic")] +fn num_equal_handler_no_stack(_ctx: &mut VmCore<'_>, l: SteelVal, r: SteelVal) -> Result { if let SteelVal::BoolV(b) = number_equality(&l, &r)? { Ok(b) } else { @@ -6304,6 +5952,7 @@ fn num_equal_handler_no_stack(ctx: &mut VmCore<'_>, l: SteelVal, r: SteelVal) -> } } +#[cfg(feature = "dynamic")] // OpCode::LOADINT0 fn handle_load_int0(ctx: &mut VmCore<'_>) -> Result<()> { ctx.thread.stack.push(SteelVal::INT_ZERO); @@ -6311,6 +5960,7 @@ fn handle_load_int0(ctx: &mut VmCore<'_>) -> Result<()> { Ok(()) } +#[cfg(feature = "dynamic")] // OpCode::LOADINT1 fn handle_load_int1(ctx: &mut VmCore<'_>) -> Result<()> { ctx.thread.stack.push(SteelVal::INT_ONE); @@ -6318,6 +5968,7 @@ fn handle_load_int1(ctx: &mut VmCore<'_>) -> Result<()> { Ok(()) } +#[cfg(feature = "dynamic")] // OpCode::LOADINT2 fn handle_load_int2(ctx: &mut VmCore<'_>) -> Result<()> { ctx.thread.stack.push(SteelVal::INT_TWO); @@ -6325,48 +5976,57 @@ fn handle_load_int2(ctx: &mut VmCore<'_>) -> Result<()> { Ok(()) } +#[cfg(feature = "dynamic")] // OpCode::MOVEREADLOCAL fn move_local_handler(ctx: &mut VmCore<'_>) -> Result<()> { let index = ctx.instructions[ctx.ip].payload_size; ctx.handle_move_local(index.to_usize()) } +#[cfg(feature = "dynamic")] // OpCode::MOVEREADLOCAL fn move_local_handler_with_payload(ctx: &mut VmCore<'_>, index: usize) -> Result<()> { ctx.handle_move_local(index) } +#[cfg(feature = "dynamic")] // OpCode::MOVEREADLOCAL0 fn move_local_handler0(ctx: &mut VmCore<'_>) -> Result<()> { ctx.handle_move_local(0) } +#[cfg(feature = "dynamic")] // OpCode::MOVEREADLOCAL1 fn move_local_handler1(ctx: &mut VmCore<'_>) -> Result<()> { ctx.handle_move_local(1) } +#[cfg(feature = "dynamic")] // OpCode::MOVEREADLOCAL2 fn move_local_handler2(ctx: &mut VmCore<'_>) -> Result<()> { ctx.handle_move_local(2) } +#[cfg(feature = "dynamic")] // OpCode::MOVEREADLOCAL3 fn move_local_handler3(ctx: &mut VmCore<'_>) -> Result<()> { ctx.handle_move_local(3) } +#[cfg(feature = "dynamic")] // OpCode::READCAPTURED fn read_captured_handler(ctx: &mut VmCore<'_>) -> Result<()> { let payload_size = ctx.instructions[ctx.ip].payload_size; ctx.handle_read_captures(payload_size.to_usize()) } +#[cfg(feature = "dynamic")] // OpCode::READCAPTURED fn read_captured_handler_with_payload(ctx: &mut VmCore<'_>, payload_size: usize) -> Result<()> { ctx.handle_read_captures(payload_size) } +#[cfg(feature = "dynamic")] // OpCode::BEGINSCOPE fn begin_scope_handler(ctx: &mut VmCore<'_>) -> Result<()> { ctx.ip += 1; @@ -6417,6 +6077,7 @@ fn let_end_scope_handler_with_payload(ctx: &mut VmCore<'_>, beginning_scope: usi Ok(()) } +#[cfg(feature = "dynamic")] // OpCode::PUREFUNC fn pure_function_handler(ctx: &mut VmCore<'_>) -> Result<()> { let payload_size = ctx.instructions[ctx.ip].payload_size.to_usize(); @@ -6424,6 +6085,7 @@ fn pure_function_handler(ctx: &mut VmCore<'_>) -> Result<()> { Ok(()) } +#[cfg(feature = "dynamic")] // OpCode::PUREFUNC fn pure_function_handler_with_payload(ctx: &mut VmCore<'_>, payload_size: usize) -> Result<()> { ctx.handle_pure_function(payload_size); @@ -6605,7 +6267,7 @@ fn lte_handler_payload(ctx: &mut VmCore<'_>, payload: usize) -> Result<()> { } // OpCode::ALLOC -fn alloc_handler(ctx: &mut VmCore<'_>) -> Result<()> { +fn alloc_handler(_ctx: &mut VmCore<'_>) -> Result<()> { panic!("Deprecated now - this shouldn't be hit"); /* @@ -6638,7 +6300,7 @@ fn alloc_handler(ctx: &mut VmCore<'_>) -> Result<()> { // OpCode::READALLOC #[inline(always)] -fn read_alloc_handler(ctx: &mut VmCore<'_>) -> Result<()> { +fn read_alloc_handler(_ctx: &mut VmCore<'_>) -> Result<()> { panic!("Deprecated - this shouldn't be hit") /* @@ -6663,7 +6325,7 @@ fn read_alloc_handler(ctx: &mut VmCore<'_>) -> Result<()> { // OpCode::SETALLOC #[inline(always)] -fn set_alloc_handler(ctx: &mut VmCore<'_>) -> Result<()> { +fn set_alloc_handler(_ctx: &mut VmCore<'_>) -> Result<()> { panic!("Deprecated - this shouldn't be hit") /* @@ -6767,6 +6429,7 @@ mod handlers { Ok(()) } + #[cfg(feature = "dynamic")] fn specialized_sub01(ctx: &mut VmCore<'_>) -> Result<()> { let offset = ctx.get_offset(); // let offset = ctx.stack_frames.last().map(|x| x.index).unwrap_or(0); @@ -6921,7 +6584,7 @@ mod handlers { // OpCode::PUSHCONST fn push_const_handler(ctx: &mut VmCore<'_>) -> Result<()> { let payload_size = ctx.instructions[ctx.ip].payload_size; - let val = ctx.constants.get(payload_size.to_usize()); + let val = ctx.constants.get_value(payload_size.to_usize()); ctx.thread.stack.push(val); ctx.ip += 1; Ok(()) @@ -6929,14 +6592,14 @@ mod handlers { pub fn push_const_handler_no_stack(ctx: &mut VmCore<'_>) -> Result { let payload_size = ctx.instructions[ctx.ip].payload_size; - let val = ctx.constants.get(payload_size.to_usize()); + let val = ctx.constants.get_value(payload_size.to_usize()); ctx.ip += 1; Ok(val) } // OpCode::PUSHCONST fn push_const_handler_with_payload(ctx: &mut VmCore<'_>, payload: usize) -> Result<()> { - let val = ctx.constants.get(payload); + let val = ctx.constants.get_value(payload); ctx.thread.stack.push(val); ctx.ip += 1; Ok(()) diff --git a/crates/steel-core/src/steel_vm/vm/threads.rs b/crates/steel-core/src/steel_vm/vm/threads.rs index b48210b96..f5a27f580 100644 --- a/crates/steel-core/src/steel_vm/vm/threads.rs +++ b/crates/steel-core/src/steel_vm/vm/threads.rs @@ -1,7 +1,6 @@ use std::collections::HashSet; use fxhash::FxHashMap; -use parking_lot::{lock_api::RawMutex, RwLock}; use steel_derive::function; use crate::{ @@ -159,7 +158,7 @@ pub fn closure_into_serializable( serializer: &mut std::collections::HashMap, visited: &mut std::collections::HashSet, ) -> Result { - if let Some(mut prototype) = CACHED_CLOSURES.with(|x| x.borrow().get(&c.id).cloned()) { + if let Some(prototype) = CACHED_CLOSURES.with(|x| x.borrow().get(&c.id).cloned()) { let mut prototype = SerializedLambda { id: prototype.id, body_exp: prototype.body_exp, @@ -177,7 +176,7 @@ pub fn closure_into_serializable( Ok(prototype) } else { - let mut prototype = SerializedLambdaPrototype { + let prototype = SerializedLambdaPrototype { id: c.id, #[cfg(not(feature = "dynamic"))] @@ -222,9 +221,9 @@ struct MovableFunctionInterner { closure_interner: fxhash::FxHashMap, pure_function_interner: fxhash::FxHashMap, spans: fxhash::FxHashMap>, - instructions: fxhash::FxHashMap>, } +#[allow(unused)] /// This will naively deep clone the environment, by attempting to translate every value into a `SerializableSteelVal` /// While this does work, it does result in a fairly hefty deep clone of the environment. It does _not_ smartly attempt /// to keep track of what values this function could touch - rather it assumes every value is possible to be touched @@ -310,6 +309,7 @@ fn spawn_thread_result(ctx: &mut VmCore, args: &[SteelVal]) -> Result .global_env .bindings_vec .read() + .unwrap() .iter() .cloned() .map(|x| into_serializable_value(x, &mut initial_map, &mut visited)) @@ -366,13 +366,6 @@ fn spawn_thread_result(ctx: &mut VmCore, args: &[SteelVal]) -> Result .iter() .map(|(k, v)| (*k, v.iter().copied().collect())) .collect(), - instructions: ctx - .thread - .function_interner - .instructions - .iter() - .map(|(k, v)| (*k, v.iter().copied().collect())) - .collect(), } ), @@ -384,7 +377,7 @@ fn spawn_thread_result(ctx: &mut VmCore, args: &[SteelVal]) -> Result // TODO: Spawn a bunch of threads at the start to handle requests. That way we don't need to do this // the whole time they're in there. let handle = std::thread::spawn(move || { - let mut heap = time!("Heap Creation", Arc::new(Mutex::new(Heap::new()))); + let heap = time!("Heap Creation", Arc::new(Mutex::new(Heap::new()))); // Move across threads? let mut mapping = initial_map @@ -424,7 +417,7 @@ fn spawn_thread_result(ctx: &mut VmCore, args: &[SteelVal]) -> Result let global_env = time!( "Global env creation", Env { - bindings_vec: Arc::new(RwLock::new( + bindings_vec: Arc::new(std::sync::RwLock::new( thread .global_env .into_iter() @@ -432,7 +425,7 @@ fn spawn_thread_result(ctx: &mut VmCore, args: &[SteelVal]) -> Result .collect() )), // TODO: - // thread_local_bindings: Vec::new(), + thread_local_bindings: Vec::new(), } ); @@ -476,12 +469,6 @@ fn spawn_thread_result(ctx: &mut VmCore, args: &[SteelVal]) -> Result .into_iter() .map(|(k, v)| (k, v.into())) .collect(), - instructions: thread - .function_interner - .instructions - .into_iter() - .map(|(k, v)| (k, v.into())) - .collect(), } ); @@ -512,7 +499,10 @@ fn spawn_thread_result(ctx: &mut VmCore, args: &[SteelVal]) -> Result global_env, sources, stack: Vec::with_capacity(64), + + #[cfg(feature = "dynamic")] profiler: OpCodeOccurenceProfiler::new(), + function_interner, heap, runtime_options: thread.runtime_options, @@ -593,7 +583,7 @@ impl Channels { pub fn select(values: &[SteelVal]) -> Result { let mut selector = crossbeam::channel::Select::new(); - let mut borrows = values + let borrows = values .iter() .map(|x| SteelReceiver::as_ref(x)) .collect::>>()?; @@ -638,7 +628,7 @@ pub fn channel_recv(receiver: &SteelVal) -> Result { SteelReceiver::as_ref(receiver)? .receiver .recv() - .map_err(|e| { + .map_err(|_| { throw!(Generic => "Unable to receive on the channel. The channel is empty and disconnected")() }) @@ -749,7 +739,7 @@ pub fn disconnected_channel() -> SteelVal { } #[steel_derive::context(name = "current-thread-id", arity = "Exact(0)")] -pub fn engine_id(ctx: &mut VmCore, args: &[SteelVal]) -> Option> { +pub fn engine_id(ctx: &mut VmCore, _args: &[SteelVal]) -> Option> { Some(Ok(SteelVal::IntV(ctx.thread.id.0 as _))) } @@ -775,8 +765,13 @@ pub(crate) fn spawn_native_thread(ctx: &mut VmCore, args: &[SteelVal]) -> Option ctx.thread.safepoints_enabled = true; let thread_time = std::time::Instant::now(); + + // Do this here? let mut thread = ctx.thread.clone(); - let interrupt = Arc::new(AtomicBool::new(false)); + + // println!("Created thread"); + + // let interrupt = Arc::new(AtomicBool::new(false)); // Let this thread have its own interrupt handler let controller = ThreadStateController::default(); thread.synchronizer.state = controller.clone(); diff --git a/crates/steel-core/src/tests/success/native_threads.scm b/crates/steel-core/src/tests/success/native_threads.scm index 2204b1332..f9b712bf9 100644 --- a/crates/steel-core/src/tests/success/native_threads.scm +++ b/crates/steel-core/src/tests/success/native_threads.scm @@ -1,69 +1,6 @@ ;;;;;;;;;;;; Native threads ;;;;;;;;;;;;;;;; (require-builtin steel/time) - -(define (lock! lock thunk) - (dynamic-wind (lambda () (lock-acquire! lock)) - (lambda () (thunk)) - (lambda () (lock-release! lock)))) - -(struct ThreadPool (task-sender capacity thread-handles)) - -;; This will be updated when the task is finished -(struct Task (lock done func-or-result err) #:mutable) - -(define (make-thread-pool capacity) - (define channels (channels/new)) - (define sender (channels-sender channels)) - (define receiver (channels-receiver channels)) - - (define (listen-for-tasks) - (define next-task (channel/recv receiver)) - (define func (Task-func-or-result next-task)) - - (with-handler (lambda (err) - (set-Task-err! next-task err) - (set-Task-done! next-task #t)) - ;; Capture exception, if it exists. Store it in the task - (lock! (Task-lock next-task) - (lambda () - ;; This should be fine, we're updating the task to be finished, - ;; so we can check the progress of it - (set-Task-func-or-result! next-task (func)) - (set-Task-done! next-task #t)))) - - (listen-for-tasks)) - - ;; Give me back a thread pool to do some work - (ThreadPool sender - capacity - (map (lambda (_) (spawn-native-thread listen-for-tasks)) (range 0 capacity)))) - -;; Should _probably_ get back some lightweight task object -(define (submit-task tp func) - ;; Create the task. We'll update this to done, and replace - ;; the func with the proper value afterwards - (define task (Task (mutex) #f func #f)) - (channel/send (ThreadPool-task-sender tp) task) - task) - -(define (try-block task) - (lock! (Task-lock task) (lambda () (Task-func-or-result task)))) - -(define (block-on-task task) - ;; This could be acquiring the lock too quickly, since - ;; it could reach the thread pool after we've acquired this lock. - ;; So we should attempt to grab the lock, but if the task has not - ;; been locked yet, then we won't actually wait on it. We should - ;; just spin lock until the lock can be acquired, and then - ;; block on it - (define (loop task) - (cond - [(Task-done task) (Task-func-or-result task)] - [else - (try-block task) - (loop task)])) - - (loop task)) +(require "steel/sync") ;; Create work for the thread-pool diff --git a/crates/steel-core/src/tests/success/y_combinator.scm b/crates/steel-core/src/tests/success/y_combinator.scm index 01873e0fa..6012a3736 100644 --- a/crates/steel-core/src/tests/success/y_combinator.scm +++ b/crates/steel-core/src/tests/success/y_combinator.scm @@ -1,25 +1,20 @@ -(define Y - (lambda (f) - ((lambda (x) (x x)) - (lambda (x) (f (lambda (y) ((x x) y))))))) +(define Y (lambda (f) ((lambda (x) (x x)) (lambda (x) (f (lambda (y) ((x x) y))))))) ;; head-recursive factorial -(define fac ; fac = (Y f) = (f (lambda a (apply (Y f) a))) - (Y (lambda (r) ; = (lambda (x) ... (r (- x 1)) ... ) - (lambda (x) ; where r = (lambda a (apply (Y f) a)) - (if (< x 2) ; (r ... ) == ((Y f) ... ) - 1 ; == (lambda (x) ... (fac (- x 1)) ... ) - (* x (r (- x 1)))))))) - +(define fac ; fac = (Y f) = (f (lambda a (apply (Y f) a))) + (Y (lambda (r) ; = (lambda (x) ... (r (- x 1)) ... ) + (lambda (x) ; where r = (lambda a (apply (Y f) a)) + (if (< x 2) ; (r ... ) == ((Y f) ... ) + 1 ; == (lambda (x) ... (fac (- x 1)) ... ) + (* x (r (- x 1)))))))) ; double-recursive Fibonacci (define fib - (Y (lambda (f) - (lambda (x) - (if (< x 2) - x - (+ (f (- x 1)) (f (- x 2)))))))) - + (Y (lambda (f) + (lambda (x) + (if (< x 2) + x + (+ (f (- x 1)) (f (- x 2)))))))) -(assert! (equal? 720 (fac 6))) -(assert! (equal? 233 (fib 13))) \ No newline at end of file +(assert! (equal? 720 (dbg! (fac 6)))) +(assert! (equal? 233 (dbg! (fib 13)))) diff --git a/crates/steel-core/src/values/functions.rs b/crates/steel-core/src/values/functions.rs index c1cd36448..172c30af0 100644 --- a/crates/steel-core/src/values/functions.rs +++ b/crates/steel-core/src/values/functions.rs @@ -89,6 +89,15 @@ impl LambdaMetadataTable { } } +#[cfg(feature = "inline-captures")] +const INLINE_CAPTURE_SIZE: usize = 3; + +#[cfg(not(feature = "inline-captures"))] +pub type CaptureVec = Vec; + +#[cfg(feature = "inline-captures")] +pub type CaptureVec = smallvec::SmallVec<[SteelVal; INLINE_CAPTURE_SIZE]>; + #[derive(Clone, Debug)] pub struct ByteCodeLambda { pub(crate) id: u32, @@ -106,7 +115,11 @@ pub struct ByteCodeLambda { pub(crate) is_multi_arity: bool, - pub(crate) captures: Vec, + // Store... some amount inline? + // pub(crate) captures: Vec, + pub(crate) captures: CaptureVec, + + // pub(crate) captures: Box<[SteelVal]> #[cfg(feature = "dynamic")] pub(crate) blocks: RefCell>, @@ -165,6 +178,9 @@ pub struct RootedInstructions { inner: Shared<[DenseInstruction]>, } +#[cfg(feature = "rooted-instructions")] +impl Copy for RootedInstructions {} + // TODO: Come back to this unsafe impl Send for RootedInstructions {} unsafe impl Sync for RootedInstructions {} @@ -200,20 +216,14 @@ impl std::ops::Deref for RootedInstructions { } } -// TODO -// is_let can probably be localized to a specific kind of function -// is_multi_arity can also be localized to a specific kind of function impl ByteCodeLambda { pub fn new( id: u32, body_exp: Shared<[DenseInstruction]>, arity: usize, is_multi_arity: bool, - captures: Vec, - // heap_allocated: Vec>, + captures: CaptureVec, ) -> ByteCodeLambda { - // debug_assert_eq!(body_exp.len(), spans.len()); - ByteCodeLambda { id, @@ -252,7 +262,16 @@ impl ByteCodeLambda { .into_iter() .map(|x| from_serializable_value(heap, x)) .collect(), - // Vec::new(), + ) + } + + pub fn rooted(instructions: Shared<[DenseInstruction]>) -> ByteCodeLambda { + Self::new( + SyntaxObjectId::fresh().into(), + instructions, + 0, + false, + CaptureVec::default(), ) } @@ -262,25 +281,21 @@ impl ByteCodeLambda { instructions.into(), 0, false, - Vec::default(), - // Vec::default(), - // Rc::from([]), + CaptureVec::default(), ) } - // pub fn id(&self) -> usize { - // self.id - // } - - pub fn set_captures(&mut self, captures: Vec) { + pub fn set_captures(&mut self, captures: CaptureVec) { self.captures = captures; } - // pub fn set_heap_allocated(&mut self, heap_allocated: Vec>) { - // self.heap_allocated = RefCell::new(heap_allocated); - // } - - pub fn body_exp(&self) -> RootedInstructions { + // TODO: The lifecycle of `RootedInstructions` should not be + // beyond the scope of execution. This invariant should in + // general hold - with the exception of continuations, which + // should probably hold on to any functions that are contains + // strongly - so there should be some kind of slot on the continuation + // to hold on to a strong reference to each instruction set. + pub(crate) fn body_exp(&self) -> RootedInstructions { // #[cfg(feature = "dynamic")] // return Shared::clone(&self.body_exp.borrow()); diff --git a/crates/steel-gen/src/opcode.rs b/crates/steel-gen/src/opcode.rs index 74e079e4e..ede29ca2f 100644 --- a/crates/steel-gen/src/opcode.rs +++ b/crates/steel-gen/src/opcode.rs @@ -117,7 +117,13 @@ declare_opcodes! { LTEIMMEDIATEIF; NOT; VEC; - Apply + Apply; + POPJMP; + CALLGLOBALTAILPOP; + BINOPADDTAIL; + LOADINT0POP; // Load const 0 + LOADINT1POP; + LOADINT2POP } // Super instructions diff --git a/crates/steel-repl/src/highlight.rs b/crates/steel-repl/src/highlight.rs index 720771d0d..ea32ff9a2 100644 --- a/crates/steel-repl/src/highlight.rs +++ b/crates/steel-repl/src/highlight.rs @@ -1,7 +1,9 @@ extern crate rustyline; use colored::*; +use steel_parser::interner::InternedString; use steel_parser::parser::SourceId; +use std::collections::HashSet; use std::sync::{Arc, Mutex}; use rustyline::highlight::Highlighter; @@ -17,22 +19,20 @@ use rustyline::completion::Pair; use std::borrow::Cow; -use steel::steel_vm::engine::Engine; - impl Completer for RustylineHelper { type Candidate = Pair; } #[derive(Helper)] pub struct RustylineHelper { - engine: Arc>, + globals: Arc>>, bracket: crossbeam::atomic::AtomicCell>, // keywords: HashSet<&'static str>, } impl RustylineHelper { - pub fn new(engine: Arc>) -> Self { + pub fn new(globals: Arc>>) -> Self { Self { - engine, + globals, bracket: crossbeam::atomic::AtomicCell::new(None), } } @@ -195,18 +195,21 @@ impl Highlighter for RustylineHelper { } TokenType::Identifier(ident) => { // If its a free identifier, nix it? - if self.engine.lock().unwrap().global_exists(ident) { - // println!("before length: {}", token.source().as_bytes().len()); - let highlighted = format!("{}", token.source().bright_blue()); - // println!("After length: {}", highlighted.as_bytes().len()); - - // println!("paren pos: {:?}", self.bracket.get()); + if self + .globals + .lock() + .unwrap() + .contains(&InternedString::from(*ident)) + { + let highlighted = format!("{}", token.source().bright_blue()); ranges_to_replace.push((token.span().range(), highlighted)); } - // else if self.engine.borrow().in_scope_macros().contains_key(*ident) { - // let highlighted = format!("{}", token.source().bright_cyan()); + // TODO: + // if self.engine.lock().unwrap().global_exists(ident) { + // // println!("before length: {}", token.source().as_bytes().len()); + // let highlighted = format!("{}", token.source().bright_blue()); // ranges_to_replace.push((token.span().range(), highlighted)); // } } diff --git a/crates/steel-repl/src/repl.rs b/crates/steel-repl/src/repl.rs index 4fa6da51b..837378190 100644 --- a/crates/steel-repl/src/repl.rs +++ b/crates/steel-repl/src/repl.rs @@ -219,8 +219,10 @@ pub fn repl_base(mut vm: Engine) -> std::io::Result<()> { vm.register_fn("quit", cancellation_function); let safepoint = vm.get_thread_state_controller(); - let engine = Arc::new(Mutex::new(vm)); - rl.set_helper(Some(RustylineHelper::new(engine.clone()))); + let mut engine = vm; + let globals = Arc::new(Mutex::new(engine.globals().iter().copied().collect())); + + rl.set_helper(Some(RustylineHelper::new(globals.clone()))); let safepoint = safepoint.clone(); let ctrlc_safepoint = safepoint.clone(); @@ -235,7 +237,20 @@ pub fn repl_base(mut vm: Engine) -> std::io::Result<()> { }; while rx.try_recv().is_err() { - let readline = rl.readline(&prompt); + // Update globals for highlighting + // TODO: Come up with some kind of subscription API? + let known_globals_length = globals.lock().unwrap().len(); + let updated_globals_length = engine.globals().len(); + if updated_globals_length > known_globals_length { + let mut guard = globals.lock().unwrap(); + if let Some(range) = engine.globals().get(known_globals_length..) { + for var in range { + guard.insert(*var); + } + } + } + + let readline = engine.enter_safepoint(|| rl.readline(&prompt)); match readline { Ok(line) => { @@ -281,11 +296,7 @@ pub fn repl_base(mut vm: Engine) -> std::io::Result<()> { clear_interrupted(); - finish_load_or_interrupt( - &mut engine.lock().unwrap(), - exprs, - path.to_path_buf(), - ); + finish_load_or_interrupt(&mut engine, exprs, path.to_path_buf()); } _ => { // TODO also include this for loading files @@ -293,7 +304,7 @@ pub fn repl_base(mut vm: Engine) -> std::io::Result<()> { clear_interrupted(); - finish_or_interrupt(&mut engine.lock().unwrap(), line); + finish_or_interrupt(&mut engine, line); if print_time { println!("Time taken: {:?}", now.elapsed());