From bcf878385c09291a4b9256688a6f76818039a888 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sat, 23 Nov 2019 23:12:22 +0100 Subject: [PATCH] fix: Inline functions that are re-exports --- Cargo.lock | 1 - tests/inline.rs | 166 +++++++ tests/optimize/inline_num.glu | 12 + tests/optimize/inline_through_module.glu | 9 + tests/optimize/inline_through_module2.glu | 39 ++ vm/Cargo.toml | 2 +- vm/src/core/grammar.lalrpop | 2 +- vm/src/core/interpreter.rs | 575 ++++++++++++++++------ vm/src/core/mod.rs | 20 +- vm/src/core/optimize.rs | 2 +- vm/src/core/pretty.rs | 27 +- vm/src/core/purity.rs | 63 ++- 12 files changed, 748 insertions(+), 170 deletions(-) create mode 100644 tests/optimize/inline_num.glu create mode 100644 tests/optimize/inline_through_module.glu create mode 100644 tests/optimize/inline_through_module2.glu diff --git a/Cargo.lock b/Cargo.lock index d7f30d31f1..0ae0c5241f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1370,7 +1370,6 @@ dependencies = [ "env_logger 0.7.1", "frunk_core", "futures 0.3.5", - "gluon", "gluon_base", "gluon_check", "gluon_codegen", diff --git a/tests/inline.rs b/tests/inline.rs index 2a48421486..0a3363c834 100644 --- a/tests/inline.rs +++ b/tests/inline.rs @@ -33,3 +33,169 @@ async fn inline_cross_module() { "#; check_expr_eq(core_expr.value.expr(), expected_str); } + +#[test] +fn inline_with_record_pattern_in_module() { + let _ = env_logger::try_init(); + + let thread = make_vm(); + thread.get_database_mut().set_implicit_prelude(false); + + thread + .load_script( + "test", + r#" +let f x = x +let m1 = { + f, +} + +let m2 = + let { f } = m1 + + let num = { + (+) = \l -> l, + } + + { + f, + num, + } + +let { num } = m2 +num.(+) 3 + "#, + ) + .unwrap_or_else(|err| panic!("{}", err)); + + let db = thread.get_database(); + let core_expr = db + .core_expr("test".into()) + .unwrap_or_else(|err| panic!("{}", err)); + let expected_str = r#" + 3 + "#; + check_expr_eq(core_expr.value.expr(), expected_str); +} + +#[test] +fn inline_across_two_modules() { + let _ = env_logger::try_init(); + + let thread = make_vm(); + thread.get_database_mut().set_implicit_prelude(false); + + thread + .load_script( + "test", + r#" + let { (+) } = import! tests.optimize.inline_through_module + let { ? } = import! tests.optimize.inline_through_module2 + 1 + 2 + "#, + ) + .unwrap_or_else(|err| panic!("{}", err)); + + let db = thread.get_database(); + let core_expr = db + .core_expr("test".into()) + .unwrap_or_else(|err| panic!("{}", err)); + let expected_str = r#" + 3 + "#; + check_expr_eq(core_expr.value.expr(), expected_str); +} + +#[test] +fn prune_prelude() { + let _ = env_logger::try_init(); + + let thread = make_vm(); + thread.get_database_mut().set_implicit_prelude(false); + + thread + .load_script( + "test", + r#" + let { (+) } = import! std.prelude + let { ? } = import! std.int + in 1 + 2 + "#, + ) + .unwrap_or_else(|err| panic!("{}", err)); + + let db = thread.get_database(); + let core_expr = db + .core_expr("test".into()) + .unwrap_or_else(|err| panic!("{}", err)); + let expected_str = r#" + 3 + "#; + check_expr_eq(core_expr.value.expr(), expected_str); +} + +#[test] +fn prune_factorial() { + let _ = env_logger::try_init(); + + let thread = make_vm(); + thread.get_database_mut().set_implicit_prelude(false); + + thread + .load_script( + "test", + r#" + let { (-), (*), (<) } = import! std.prelude + let { ? } = import! std.int + let factorial n = + if n < 2 + then 1 + else n * factorial (n - 1) + factorial + "#, + ) + .unwrap_or_else(|err| panic!("{}", err)); + + let db = thread.get_database(); + let core_expr = db + .core_expr("test".into()) + .unwrap_or_else(|err| panic!("{}", err)); + let expected_str = r#" + rec let factorial n = + match (#Int<) n 2 with + | True -> 1 + | False -> (#Int*) n (factorial ( (#Int-) n 1)) + end + in + factorial + "#; + check_expr_eq(core_expr.value.expr(), expected_str); +} + +#[test] +fn inline_num() { + let _ = env_logger::try_init(); + + let thread = make_vm(); + thread.get_database_mut().set_implicit_prelude(false); + + thread + .load_script( + "test", + r#" +let mod = import! tests.optimize.inline_num +let no_inline x = if x #Int== 0 then no_inline x else x +mod.(+) (no_inline 1) 2 + "#, + ) + .unwrap_or_else(|err| panic!("{}", err)); + + let db = thread.get_database(); + let core_expr = db + .core_expr("test".into()) + .unwrap_or_else(|err| panic!("{}", err)); + let expected_str = r#" + 3 + "#; + check_expr_eq(core_expr.value.expr(), expected_str); +} diff --git a/tests/optimize/inline_num.glu b/tests/optimize/inline_num.glu new file mode 100644 index 0000000000..36feaf178b --- /dev/null +++ b/tests/optimize/inline_num.glu @@ -0,0 +1,12 @@ +let additive = + let semigroup = { + append = \x y -> x #Int+ y + } + + { semigroup } + + +{ + (+) = additive.semigroup.append, + additive, +} diff --git a/tests/optimize/inline_through_module.glu b/tests/optimize/inline_through_module.glu new file mode 100644 index 0000000000..5c73a750cd --- /dev/null +++ b/tests/optimize/inline_through_module.glu @@ -0,0 +1,9 @@ +//@NO-IMPLICIT-PRELUDE +//! Definitions which gets implicit re-export in every file. + +let { Num, (+), (-), (*), (/) } = import! tests.optimize.inline_through_module2 + +{ + Num, + (+), (-), (*), (/), +} diff --git a/tests/optimize/inline_through_module2.glu b/tests/optimize/inline_through_module2.glu new file mode 100644 index 0000000000..e9029faf67 --- /dev/null +++ b/tests/optimize/inline_through_module2.glu @@ -0,0 +1,39 @@ +//@NO-IMPLICIT-PRELUDE + +#[implicit] +type Num a = { + /// The addition operator + (+) : a -> a -> a, + /// The subtraction operator + (-) : a -> a -> a, + /// The multiplication operator + (*) : a -> a -> a, + /// The division operator + (/) : a -> a -> a, +} + +let num : Num Int = { + (+) = \l r -> l #Int+ r, + (-) = \l r -> l #Int- r, + (*) = \l r -> l #Int* r, + (/) = \l r -> l #Int/ r, +} + + +#[infix(left, 6)] +let (+) ?num : [Num a] -> a -> a -> a = num.(+) +#[infix(left, 6)] +let (-) ?num : [Num a] -> a -> a -> a = num.(-) +#[infix(left, 7)] +let (*) ?num : [Num a] -> a -> a -> a = num.(*) +#[infix(left, 7)] +let (/) ?num : [Num a] -> a -> a -> a = num.(/) + +{ + Num, + + (+), (-), (*), (/), + + num, +} + diff --git a/vm/Cargo.toml b/vm/Cargo.toml index f818757055..56d345bd60 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -61,7 +61,7 @@ pretty_assertions = "0.6" # HACK Trick crates.io into letting letting this be published with a dependency on gluon # (which requires gluon_vm to be published) -gluon = { path = "..", version = ">=0.9" } +# gluon = { path = "..", version = ">=0.9" } lalrpop-util = "0.19" regex = "1" diff --git a/vm/src/core/grammar.lalrpop b/vm/src/core/grammar.lalrpop index 64b7df0899..55f266fd8e 100644 --- a/vm/src/core/grammar.lalrpop +++ b/vm/src/core/grammar.lalrpop @@ -19,7 +19,7 @@ Comma: Vec = Identifier: Symbol = { => symbols.symbol(SymbolData::<&Name>::from(<>)), - => symbols.simple_symbol(&<>[1..<>.len() - 1]), + => symbols.simple_symbol(&<>[1..<>.len() - 1]), <+]+\)"> => symbols.simple_symbol(&<>[1..<>.len() - 1]), }; diff --git a/vm/src/core/interpreter.rs b/vm/src/core/interpreter.rs index 8ac22235d9..a11987c4e4 100644 --- a/vm/src/core/interpreter.rs +++ b/vm/src/core/interpreter.rs @@ -1,4 +1,5 @@ use std::{ + cell::RefCell, fmt, hash::{Hash, Hasher}, marker::PhantomData, @@ -23,7 +24,10 @@ use crate::{ core::{ self, costs::{Cost, Costs}, - optimize::{self, walk_expr_alloc, DifferentLifetime, ExprProducer, SameLifetime, Visitor}, + optimize::{ + self, walk_expr, walk_expr_alloc, DifferentLifetime, ExprProducer, SameLifetime, + Visitor, + }, purity::PurityMap, Allocator, Alternative, ArenaExt, CExpr, Closure, ClosureRef, CoreClosure, CoreExpr, Expr, LetBinding, Literal, Named, Pattern, @@ -32,7 +36,7 @@ use crate::{ }; #[derive(Copy, Clone, Debug)] -enum Reduced { +pub enum Reduced { Local(L), Global(G), } @@ -56,13 +60,27 @@ pub struct Global { pub info: Arc, } -#[derive(Default)] +type OwnedReduced = Reduced>; + +#[derive(Debug, Default)] pub struct OptimizerInfo { - pub local_bindings: FnvMap>, + pub local_bindings: FnvMap, pub pure_symbols: PurityMap, pub costs: Costs, } +impl Global { + fn new(info: &Arc, value: Reduced>) -> Global { + match value { + Reduced::Local(value) => Global { + value, + info: info.clone(), + }, + Reduced::Global(value) => value, + } + } +} + impl Eq for Global where T: Eq {} impl PartialEq for Global @@ -102,20 +120,42 @@ where } type ReducedExpr<'l> = Reduced, Global>; +type ReducedClosure<'l> = Reduced, Global>; +type StackBinding<'l> = Binding, ReducedClosure<'l>>; + +pub type OptimizerBinding = Binding, OwnedReduced>; +pub type GlobalBinding = Binding, Global>; trait Resolver<'l, 'a> { - fn produce(&mut self, expr: CExpr<'a>) -> CExpr<'l>; - fn produce_slice(&mut self, expr: &'a [Expr<'a>]) -> &'l [Expr<'l>]; + fn produce( + &mut self, + + inlined_global_bindings: &mut FnvMap>>, + expr: CExpr<'a>, + ) -> CExpr<'l>; + fn produce_slice( + &mut self, + inlined_global_bindings: &mut FnvMap>>, + expr: &'a [Expr<'a>], + ) -> &'l [Expr<'l>]; fn wrap(&self, expr: CExpr<'a>) -> ReducedExpr<'l>; fn find(&self, s: &Symbol) -> Option; fn cost(&self, s: &SymbolRef) -> Cost; } impl<'a> Resolver<'a, 'a> for SameLifetime<'a> { - fn produce(&mut self, expr: CExpr<'a>) -> CExpr<'a> { + fn produce( + &mut self, + _inlined_global_bindings: &mut FnvMap>>, + expr: CExpr<'a>, + ) -> CExpr<'a> { expr } - fn produce_slice(&mut self, expr: &'a [Expr<'a>]) -> &'a [Expr<'a>] { + fn produce_slice( + &mut self, + _inlined_global_bindings: &mut FnvMap>>, + expr: &'a [Expr<'a>], + ) -> &'a [Expr<'a>] { expr } fn wrap(&self, expr: CExpr<'a>) -> ReducedExpr<'a> { @@ -129,20 +169,40 @@ impl<'a> Resolver<'a, 'a> for SameLifetime<'a> { } } -struct Different<'l, 'g, 'a, F> { - producer: DifferentLifetime<'l, 'g>, +struct Different<'l, 'a, F> { + allocator: &'l Allocator<'l>, info: &'a Arc, make_expr: F, } -impl<'l, 'b, 'a, F> Resolver<'l, 'b> for Different<'l, 'b, 'a, F> +impl<'l, 'b, 'a, F> Resolver<'l, 'b> for Different<'l, 'a, F> where F: Fn(CExpr<'b>) -> CoreExpr, { - fn produce(&mut self, expr: CExpr<'b>) -> CExpr<'l> { - self.producer.produce(expr) + fn produce( + &mut self, + inlined_global_bindings: &mut FnvMap>>, + expr: CExpr<'b>, + ) -> CExpr<'l> { + IntoLocal { + allocator: self.allocator, + info: &self.info, + inlined_global_bindings, + _marker: PhantomData, + } + .produce(expr) } - fn produce_slice(&mut self, expr: &'b [Expr<'b>]) -> &'l [Expr<'l>] { - self.producer.produce_slice(expr) + fn produce_slice( + &mut self, + inlined_global_bindings: &mut FnvMap>>, + expr: &'b [Expr<'b>], + ) -> &'l [Expr<'l>] { + IntoLocal { + allocator: self.allocator, + info: &self.info, + inlined_global_bindings, + _marker: PhantomData, + } + .produce_slice(expr) } fn wrap(&self, expr: CExpr<'b>) -> ReducedExpr<'l> { Reduced::Global(Global { @@ -152,14 +212,8 @@ where } fn find(&self, s: &Symbol) -> Option { self.info.local_bindings.get(s).map(|bind| match bind { - Binding::Expr(expr) => Binding::Expr(Global { - info: self.info.clone(), - value: expr.clone(), - }), - Binding::Closure(closure) => Binding::Closure(Global { - info: self.info.clone(), - value: closure.clone(), - }), + Binding::Expr(expr) => Binding::Expr(Global::new(&self.info, expr.clone())), + Binding::Closure(closure) => Binding::Closure(Global::new(&self.info, closure.clone())), }) } fn cost(&self, s: &SymbolRef) -> Cost { @@ -167,11 +221,99 @@ where } } +struct IntoLocal<'a, 'b> { + allocator: &'a Allocator<'a>, + info: &'b Arc, + inlined_global_bindings: &'b mut FnvMap>>, + _marker: PhantomData<&'b ()>, +} + +impl<'a, 'b> Visitor<'a, 'b> for IntoLocal<'a, 'b> { + type Producer = IntoLocal<'a, 'b>; + + fn visit_expr(&mut self, expr: &'b Expr<'b>) -> Option<&'a Expr<'a>> { + Some(self.produce(expr)) + } + + fn detach_allocator(&self) -> Option<&'a Allocator<'a>> { + Some(self.allocator) + } +} + +impl<'a, 'b> ExprProducer<'a, 'b> for IntoLocal<'a, 'b> { + fn new(_allocator: &'a Allocator<'a>) -> Self { + unreachable!() + } + fn produce(&mut self, expr: CExpr<'b>) -> CExpr<'a> { + match *expr { + Expr::Const(ref id, ref span) => self + .allocator + .arena + .alloc(Expr::Const(id.clone(), span.clone())), + Expr::Ident(ref id, ref span) => { + if let Some(bind) = self.info.local_bindings.get(&id.name) { + self.inlined_global_bindings.insert( + id.name.clone(), + Some(CostBinding { + cost: 0, + bind: match bind { + Binding::Expr(expr) => Binding::Expr(Reduced::Global(Global::new( + &self.info, + expr.clone(), + ))), + Binding::Closure(c) => Binding::Closure(Reduced::Global( + Global::new(&self.info, c.clone()), + )), + }, + }), + ); + } + self.allocator + .arena + .alloc(Expr::Ident(id.clone(), span.clone())) + } + Expr::Data(ref id, args, pos) if args.is_empty() => self + .allocator + .arena + .alloc(Expr::Data(id.clone(), &[], pos.clone())), + _ => walk_expr_alloc(self, expr).unwrap(), + } + } + fn produce_slice(&mut self, exprs: &'b [Expr<'b>]) -> &'a [Expr<'a>] { + self.allocator + .arena + .alloc_fixed(exprs.iter().map(|expr| match *expr { + Expr::Const(ref id, ref span) => Expr::Const(id.clone(), span.clone()), + Expr::Ident(ref id, ref span) => Expr::Ident(id.clone(), span.clone()), + Expr::Data(ref id, args, pos) if args.is_empty() => { + Expr::Data(id.clone(), &[], pos.clone()) + } + _ => walk_expr(self, expr).unwrap(), + })) + } + fn produce_alt(&mut self, alt: &'b Alternative<'b>) -> Alternative<'a> { + Alternative { + pattern: alt.pattern.clone(), + expr: self.produce(alt.expr), + } + } +} + impl<'l> ReducedExpr<'l> { - fn into_local(self, allocator: &'l Allocator<'l>) -> CExpr<'l> { + fn into_local( + self, + inlined_global_bindings: &mut FnvMap>>, + allocator: &'l Allocator<'l>, + ) -> CExpr<'l> { match self { Reduced::Local(e) => e, - Reduced::Global(e) => DifferentLifetime::new(allocator).produce(e.value.expr()), + Reduced::Global(e) => IntoLocal { + allocator, + info: &e.info, + inlined_global_bindings, + _marker: PhantomData, + } + .produce(e.value.expr()), } } @@ -194,7 +336,7 @@ impl<'l> ReducedExpr<'l> { global.value.with(|make_expr, _, expr| { f( &mut Different { - producer: DifferentLifetime::new(allocator), + allocator, info, make_expr, }, @@ -222,7 +364,7 @@ impl<'l> ReducedClosure<'l> { global.value.clone().with(|make_expr, _, _| { f( &mut Different { - producer: DifferentLifetime::new(allocator), + allocator, info, make_expr, }, @@ -257,8 +399,6 @@ enum TailCall<'a, T> { Value(T), } -type ReducedClosure<'l> = Reduced, Global>; - impl<'l, 'g> ReducedClosure<'l> { fn as_ref(&self) -> (&TypedIdent, &[TypedIdent], ReducedExpr<'l>) { match self { @@ -306,6 +446,30 @@ impl<'l, 'g, E> From> for Binding> { } } +impl From for OptimizerBinding { + fn from(expr: CoreExpr) -> Self { + Binding::Expr(Reduced::Local(expr)) + } +} + +impl From for OptimizerBinding { + fn from(expr: CoreClosure) -> Self { + Binding::Closure(Reduced::Local(expr)) + } +} + +impl From> for GlobalBinding { + fn from(expr: Global) -> Self { + Binding::Expr(expr) + } +} + +impl From> for GlobalBinding { + fn from(expr: Global) -> Self { + Binding::Closure(expr) + } +} + impl Binding { fn as_expr(&self) -> Option<&E> { match self { @@ -315,8 +479,6 @@ impl Binding { } } -type StackBinding<'l> = Binding, ReducedClosure<'l>>; - #[derive(Clone, Debug)] struct CostBinding<'l> { cost: Cost, @@ -430,12 +592,11 @@ impl<'l, 'g> FunctionEnv<'l, 'g> { } } -pub type GlobalBinding = Binding, Global>; - pub struct Compiler<'a, 'e> { allocator: &'e Allocator<'e>, globals: &'a dyn Fn(&Symbol) -> Option, local_bindings: FnvMap>>, + inlined_global_bindings: RefCell>>>, bindings_in_scope: ScopedMap, stack_constructors: ScopedMap, stack_types: ScopedMap>, @@ -471,6 +632,7 @@ impl<'a, 'e> Compiler<'a, 'e> { allocator, globals, local_bindings: FnvMap::default(), + inlined_global_bindings: Default::default(), bindings_in_scope: ScopedMap::default(), stack_constructors: ScopedMap::new(), stack_types: ScopedMap::new(), @@ -485,25 +647,31 @@ impl<'a, 'e> Compiler<'a, 'e> { local_bindings: self .local_bindings .into_iter() - .map(|(key, value)| { - ( + .chain(self.inlined_global_bindings.into_inner().into_iter()) + .filter_map(|(key, value)| { + Some(( key, match value.as_ref().map(|b| &b.bind) { - Some(Binding::Expr(Reduced::Local(expr))) => { - Some(Binding::Expr(crate::core::freeze_expr(allocator, expr))) - } + Some(Binding::Expr(Reduced::Local(expr))) => Binding::Expr( + Reduced::Local(crate::core::freeze_expr(allocator, expr)), + ), Some(Binding::Closure(Reduced::Local(ClosureRef { id, args, body, - }))) => Some(Binding::Closure(crate::core::freeze_closure( + }))) => Binding::Closure(Reduced::Local(crate::core::freeze_closure( allocator, id, args, body, ))), - _ => None, + Some(Binding::Expr(Reduced::Global(global))) => { + Binding::Expr(Reduced::Global(global.clone())) + } + Some(Binding::Closure(Reduced::Global(global))) => { + Binding::Closure(Reduced::Global(global.clone())) + } + None => return None, }, - ) + )) }) - .filter_map(|(key, opt)| opt.map(|value| (key.clone(), value))) .collect(), pure_symbols: self.pure_symbols.cloned().unwrap_or_default(), costs: self.costs, @@ -592,12 +760,18 @@ impl<'a, 'e> Compiler<'a, 'e> { .get(id) .cloned() .or_else(|| { - Some((self.globals)(id).map(|global| CostBinding { - cost: 0, - bind: match global { - Binding::Expr(expr) => Binding::Expr(Reduced::Global(expr)), - Binding::Closure(c) => Binding::Closure(Reduced::Global(c)), - }, + Some((self.globals)(id).map(|global| { + let bind = CostBinding { + cost: 0, + bind: match global { + Binding::Expr(expr) => Binding::Expr(Reduced::Global(expr)), + Binding::Closure(c) => Binding::Closure(Reduced::Global(c)), + }, + }; + self.inlined_global_bindings + .borrow_mut() + .insert(id.clone(), Some(bind.clone())); + bind })) }) .and_then(|e| e) @@ -981,36 +1155,29 @@ impl<'a, 'e> Compiler<'a, 'e> { .peek_reduced_expr_fn(expr.clone(), &mut |cost, e| { replaced_expr.push((cost, e.clone())) }) - .with( - self.allocator, - |resolver, reduced_expr| -> Option> { - match reduced_expr { - Expr::Call(..) => { - let (f, args) = split_call(reduced_expr); - self.inline_call(function, resolver, reduced_expr, f, args) - } - Expr::Ident(..) => self - .inline_call( - function, - resolver, - reduced_expr, - reduced_expr, - [].iter(), - ) - .or_else(|| { - if !ptr::eq::(expr.as_ref(), reduced_expr) { - Some(resolver.produce(reduced_expr)) - } else { - None - } - }), - Expr::Const(..) if !ptr::eq::(expr.as_ref(), reduced_expr) => { - Some(resolver.produce(reduced_expr)) - } - _ => None, + .with(self.allocator, |resolver, reduced_expr| -> Option<_> { + match reduced_expr { + Expr::Call(..) => { + let (f, args) = split_call(reduced_expr); + self.inline_call(function, resolver, reduced_expr, f, args) + .map(Reduced::Local) } - }, - ); + Expr::Ident(..) => self + .inline_call(function, resolver, reduced_expr, reduced_expr, [].iter()) + .map(Reduced::Local) + .or_else(|| { + if !ptr::eq::(expr.as_ref(), reduced_expr) { + Some(resolver.wrap(reduced_expr)) + } else { + None + } + }), + Expr::Const(..) if !ptr::eq::(expr.as_ref(), reduced_expr) => { + Some(resolver.wrap(reduced_expr)) + } + _ => None, + } + }); match new_expr { None => { @@ -1022,8 +1189,8 @@ impl<'a, 'e> Compiler<'a, 'e> { break; } Some(e) => { - expr = Reduced::Local(e); - replaced_expr.push((0, Reduced::Local(e))); + expr = e.clone(); + replaced_expr.push((0, e)); } } } @@ -1032,7 +1199,12 @@ impl<'a, 'e> Compiler<'a, 'e> { .into_iter() .rev() .find(|(cost, e)| *cost <= 10 && !self.contains_unbound_variables(e.as_ref())) - .map(|(_, e)| e.into_local(&self.allocator)) + .map(|(_, e)| { + e.into_local( + &mut self.inlined_global_bindings.borrow_mut(), + &self.allocator, + ) + }) } fn inline_call<'b>( @@ -1072,7 +1244,10 @@ impl<'a, 'e> Compiler<'a, 'e> { function.start_function(self); trace!("{} -- {}", closure_args.len(), args.len()); // FIXME Avoid doing into_local here somehow - let closure_body = closure_body.into_local(self.allocator); + let closure_body = closure_body.into_local( + &mut self.inlined_global_bindings.borrow_mut(), + self.allocator, + ); let mut inline = true; @@ -1094,10 +1269,11 @@ impl<'a, 'e> Compiler<'a, 'e> { let expr = if inline { self.compile(closure_body, function).map(|new_expr| { - let args = self - .allocator - .arena - .alloc_fixed(args.map(|e| resolver.produce(e).clone())); + let args = self.allocator.arena.alloc_fixed(args.map(|e| { + resolver + .produce(&mut self.inlined_global_bindings.borrow_mut(), e) + .clone() + })); let new_expr = if !args.is_empty() { // TODO Avoid allocating args and cloning them after into the // slice @@ -1194,6 +1370,10 @@ impl<'a, 'e> Compiler<'a, 'e> { bind: Binding::Expr(projected), }) } + (Pattern::Record(_), _) | (Pattern::Ident(_), _) => Some(CostBinding { + cost: 0, + bind: Binding::Expr(resolver.wrap(alt.expr)), + }), _ => None, } } @@ -1296,9 +1476,7 @@ impl<'a, 'e> Compiler<'a, 'e> { pattern_expr: ReducedExpr<'e>, _function: &mut FunctionEnvs<'e, 'a>, ) { - let pattern_expr = pattern_expr.with(self.allocator, |resolver, expr| { - resolver.wrap(peek_through_lets(expr)) - }); + let pattern_expr = self.peek_reduced_expr(pattern_expr); trace!("### {}", pattern_expr); match *pattern { Pattern::Ident(ref name) => { @@ -1538,54 +1716,63 @@ pub(crate) mod tests { allocator.arena.alloc(actual_expr) } - macro_rules! assert_eq_expr { - ($actual:expr, $expected:expr) => { - assert_eq_expr!($actual, $expected, |_: &Symbol| None) - }; - ($actual:expr, $expected:expr, $globals:expr) => {{ - let mut symbols = Symbols::new(); - let globals = $globals; + fn compile_and_optimize( + globals: &dyn Fn(&Symbol) -> Option, + actual: &str, + ) -> Global { + let mut symbols = Symbols::new(); - let allocator = Allocator::new(); + let allocator = Arc::new(Allocator::new()); - let actual_expr = parse_expr(&mut symbols, &allocator, $actual); + let actual_expr = parse_expr(&mut symbols, &allocator, actual); - let mut dep_graph = crate::core::dead_code::DepGraph::default(); - let used_bindings = dep_graph.used_bindings(actual_expr); + let mut dep_graph = crate::core::dead_code::DepGraph::default(); + let used_bindings = dep_graph.used_bindings(actual_expr); - let pure_symbols = crate::core::purity::purity(actual_expr); + let pure_symbols = crate::core::purity::purity(actual_expr); - let actual_expr = crate::core::dead_code::dead_code_elimination( - &used_bindings, - &pure_symbols, - &allocator, - actual_expr, - ); + let actual_expr = crate::core::dead_code::dead_code_elimination( + &used_bindings, + &pure_symbols, + &allocator, + actual_expr, + ); - let cyclic_bindings: FnvSet<_> = dep_graph.cycles().flat_map(|cycle| cycle).collect(); + let cyclic_bindings: FnvSet<_> = dep_graph.cycles().flat_map(|cycle| cycle).collect(); - let costs = crate::core::costs::analyze_costs(&cyclic_bindings, actual_expr); + let costs = crate::core::costs::analyze_costs(&cyclic_bindings, actual_expr); - let actual_expr = { - Compiler::new(&allocator, &globals) - .costs(costs) - .pure_symbols(&pure_symbols) - .compile_expr(actual_expr) - .unwrap() - }; + let mut interpreter = Compiler::new(&allocator, globals) + .costs(costs) + .pure_symbols(&pure_symbols); + let actual_expr = interpreter.compile_expr(actual_expr).unwrap(); + + let mut dep_graph = crate::core::dead_code::DepGraph::default(); + let used_bindings = dep_graph.used_bindings(actual_expr); + let expr = crate::core::dead_code::dead_code_elimination( + &used_bindings, + &pure_symbols, + &allocator, + actual_expr, + ); + Global { + value: crate::core::freeze_expr(&allocator, expr), + info: Arc::new(interpreter.optimizer_info(&allocator)), + } + } - let mut dep_graph = crate::core::dead_code::DepGraph::default(); - let used_bindings = dep_graph.used_bindings(actual_expr); - let actual_expr = crate::core::dead_code::dead_code_elimination( - &used_bindings, - &pure_symbols, - &allocator, - actual_expr, - ); + macro_rules! assert_eq_expr { + ($actual:expr, $expected:expr) => { + assert_eq_expr!($actual, $expected, |_: &Symbol| None) + }; + ($actual:expr, $expected:expr, $globals:expr) => {{ + let mut symbols = Symbols::new(); + let allocator = Allocator::new(); + let actual = compile_and_optimize(&$globals, $actual); let expected_expr = parse_expr(&mut symbols, &allocator, $expected); - assert_deq!(actual_expr, expected_expr); + assert_deq!(actual.value.expr(), expected_expr); }}; } @@ -1635,7 +1822,7 @@ pub(crate) mod tests { let expr = r#" x"#; - assert_eq_expr!(expr, "1", move |_: &_| Some(Binding::Expr(Global { + assert_eq_expr!(expr, "1", move |_: &_| Some(Binding::from(Global { info: Default::default(), value: global.clone(), }))); @@ -1743,6 +1930,33 @@ pub(crate) mod tests { assert_eq_expr!(expr, expected); } + #[test] + fn abort_on_self_recursive_function_2() { + let _ = ::env_logger::try_init(); + + let expr = r#" + rec let no_inline x = + match (#Int<) x 0 with + | True -> no_inline x + | False -> x + end + in + rec let f x = x + in f (no_inline 1) + "#; + let expected = r#" + rec let no_inline x = + match (#Int<) x 0 with + | True -> no_inline x + | False -> x + end + in + rec let f x = x + in f (no_inline 1) + "#; + assert_eq_expr!(expr, expected); + } + #[test] fn abort_on_indirect_self_recursive_function() { let _ = ::env_logger::try_init(); @@ -1833,6 +2047,38 @@ pub(crate) mod tests { assert_eq_expr!(expr, expected); } + #[test] + fn factorial() { + let _ = ::env_logger::try_init(); + + let expr = r#" + rec let lt l r = (#Int<) l r + in + rec let mul l r = (#Int*) l r + in + rec let sub l r = (#Int-) l r + in + + rec let factorial n = + match lt n 2 with + | True -> 1 + | False -> mul n (factorial (sub n 1)) + end + in + factorial + "#; + let expected = r#" + rec let factorial n = + match (#Int<) n 2 with + | True -> 1 + | False -> (#Int*) n (factorial ( (#Int-) n 1)) + end + in + factorial + "#; + assert_eq_expr!(expr, expected); + } + #[test] fn match_record_nested_match() { let _ = ::env_logger::try_init(); @@ -1859,16 +2105,14 @@ pub(crate) mod tests { assert_eq_expr!(expr, expected); } - #[test] - fn fold_global_function_call_with_unknown_parameters() { - let _ = ::env_logger::try_init(); - let mut symbols = Symbols::new(); + fn global_func(symbols: &mut Symbols) -> Global { let mut pure_symbols = None; let mut costs = Default::default(); + let global = with_allocator(|global_allocator| { let expr = ExprParser::new() .parse( - &mut symbols, + symbols, &global_allocator, "rec let f x y = (#Int+) x y in { f }", ) @@ -1883,11 +2127,6 @@ pub(crate) mod tests { crate::core::freeze_expr(global_allocator, global_allocator.arena.alloc(expr)) }); - let expr = r#" - rec let g y = global.f 2 y in - g - "#; - let info = Arc::new(OptimizerInfo { local_bindings: vec![( symbols.simple_symbol("f"), @@ -1895,7 +2134,7 @@ pub(crate) mod tests { .clone() .with(|_, make_closure, global| match *global { Expr::Let(ref bind, _) => match bind.expr { - Named::Recursive(ref closures) => Binding::Closure(make_closure( + Named::Recursive(ref closures) => Binding::from(make_closure( &closures[0].name, &closures[0].args[..], closures[0].expr, @@ -1910,20 +2149,68 @@ pub(crate) mod tests { pure_symbols: pure_symbols.unwrap(), costs: costs, }); - let f = move |s: &Symbol| match s.as_ref() { - "global" => Some(Binding::Expr(Global { - info: info.clone(), - value: global.clone(), - })), - _ => None, - }; + Global { + info, + value: global, + } + } + + #[test] + fn fold_global_function_call_with_unknown_parameters() { + let _ = ::env_logger::try_init(); + let mut symbols = Symbols::new(); + let global = global_func(&mut symbols); + + let expr = r#" + rec let g y = global.f 2 y in + g + "#; + assert_eq_expr!( expr, r#" rec let g y = (#Int+) 2 y in g "#, - f + |s: &Symbol| match s.as_ref() { + "global" => Some(Binding::from(global.clone())), + _ => None, + } + ); + } + + #[test] + fn fold_global_function_call_through_two_modules_simple() { + let _ = ::env_logger::try_init(); + let mut symbols = Symbols::new(); + let global = global_func(&mut symbols); + + let global2 = compile_and_optimize( + &|s: &Symbol| match s.as_ref() { + "global1" => Some(Binding::from(global.clone())), + _ => None, + }, + "let x = global1 in x", + ); + + assert_eq!( + global2.info.local_bindings.len(), + 2, + "x and global should be in the local_bindings" + ); + assert_eq_expr!( + r#" + rec let g y = global2.f 2 y in + g + "#, + r#" + rec let g y = (#Int+) 2 y in + g + "#, + |s: &Symbol| match s.as_ref() { + "global2" => Some(Binding::from(global2.clone())), + _ => None, + } ); } } diff --git a/vm/src/core/mod.rs b/vm/src/core/mod.rs index 23c03f5f77..72755822bf 100644 --- a/vm/src/core/mod.rs +++ b/vm/src/core/mod.rs @@ -61,6 +61,20 @@ pub struct Closure<'a> { pub expr: &'a Expr<'a>, } +impl fmt::Display for Closure<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + ClosureRef { + id: &self.name, + args: &self.args, + body: self.expr + } + ) + } +} + #[derive(Clone, Debug, PartialEq)] pub enum Named<'a> { Recursive(Vec>), @@ -1199,17 +1213,19 @@ impl<'a, 'e> Translator<'a, 'e> { projected_type: &ArcType, ) -> Expr<'a> { let arena = &self.allocator.arena; + // Id should be distinct from the field so bindings aren't shadowed + let projection_id = Symbol::from(projection.as_str()); let alt = Alternative { pattern: Pattern::Record(vec![( TypedIdent { - name: projection.clone(), + name: projection_id.clone(), typ: projected_type.clone(), }, None, )]), expr: arena.alloc(Expr::Ident( TypedIdent { - name: projection.clone(), + name: projection_id.clone(), typ: projected_type.clone(), }, span, diff --git a/vm/src/core/optimize.rs b/vm/src/core/optimize.rs index 5ac56d143b..64d6cd721b 100644 --- a/vm/src/core/optimize.rs +++ b/vm/src/core/optimize.rs @@ -275,7 +275,7 @@ pub fn optimize<'a>( let f = |symbol: &Symbol| { env.find_expr(symbol) - .map(crate::core::interpreter::Binding::Expr) + .map(crate::core::interpreter::Binding::from) }; let mut interpreter = crate::core::interpreter::Compiler::new(allocator, &f) .costs(costs) diff --git a/vm/src/core/pretty.rs b/vm/src/core/pretty.rs index 88534c680d..f0dc1c3cec 100644 --- a/vm/src/core/pretty.rs +++ b/vm/src/core/pretty.rs @@ -202,13 +202,15 @@ impl<'a> Expr<'a> { let doc = chain![ arena, "match ", - expr.pretty(arena, Prec::Top), + expr.pretty(arena, Prec::Top).nest(INDENT), " with", - arena.hardline(), + arena.newline(), chain![arena, "| ", alt.pattern.pretty(arena), arena.space(), "->"] .group(), - arena.hardline(), - alt.expr.pretty(arena, Prec::Top).group() + arena.newline(), + alt.expr.pretty(arena, Prec::Top).group(), + arena.newline(), + "end" ] .group(); prec.enclose(arena, doc) @@ -218,8 +220,9 @@ impl<'a> Expr<'a> { let doc = chain![ arena, "match ", - expr.pretty(arena, Prec::Top), + expr.pretty(arena, Prec::Top).nest(INDENT), " with", +<<<<<<< HEAD arena.hardline(), arena.concat( alts.iter() @@ -236,6 +239,20 @@ impl<'a> Expr<'a> { }) .intersperse(arena.hardline()) ) +======= + arena.newline(), + arena.concat(alts.iter().map(|alt| { + chain![arena; + "| ", + alt.pattern.pretty(arena), + " ->", + arena.space(), + alt.expr.pretty(arena, Prec::Top).nest(INDENT).group() + ].nest(INDENT) + }).intersperse(arena.newline())), + arena.newline(), + "end" +>>>>>>> a ] .group(); prec.enclose(arena, doc) diff --git a/vm/src/core/purity.rs b/vm/src/core/purity.rs index 70e523ff1d..28165e45e8 100644 --- a/vm/src/core/purity.rs +++ b/vm/src/core/purity.rs @@ -8,10 +8,17 @@ use crate::core::{ Allocator, CExpr, Expr, Named, Pattern, }; -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] enum Pureness { - Call, + None, Load, + Call, +} + +impl Pureness { + fn merge(&mut self, pureness: Pureness) { + *self = (*self).min(pureness); + } } #[derive(Clone, Default, Debug)] @@ -31,7 +38,7 @@ pub fn purity<'a>(expr: CExpr<'a>) -> PurityMap { let mut pure_symbols = PurityMap(FnvMap::default()); let mut visitor = Pure { - is_pure: true, + is_pure: Pureness::Call, pure_symbols: &mut pure_symbols, }; @@ -41,20 +48,20 @@ pub fn purity<'a>(expr: CExpr<'a>) -> PurityMap { } struct Pure<'b> { - is_pure: bool, + is_pure: Pureness, pure_symbols: &'b mut PurityMap, } impl Pure<'_> { - fn is_pure(&mut self, symbol: &Symbol, expr: CExpr) -> bool { + fn is_pure(&mut self, symbol: &Symbol, expr: CExpr) -> Pureness { let mut visitor = Pure { - is_pure: true, + is_pure: Pureness::Call, pure_symbols: self.pure_symbols, }; visitor.visit_expr(expr); let is_pure = visitor.is_pure; - if is_pure { - self.pure_symbols.0.insert(symbol.clone(), Pureness::Call); + if is_pure != Pureness::None { + self.pure_symbols.0.insert(symbol.clone(), is_pure); } is_pure } @@ -94,11 +101,11 @@ impl<'l, 'expr> Visitor<'l, 'expr> for Pure<'_> { if self.pure_symbols.pure_call(&*id.name) || id.name.is_primitive() { walk_expr(self, expr); } else { - self.is_pure = false; + self.is_pure = Pureness::None; } } _ => { - self.is_pure = false; + self.is_pure = Pureness::None; } }, @@ -107,7 +114,7 @@ impl<'l, 'expr> Visitor<'l, 'expr> for Pure<'_> { && !id.name.is_primitive() && !id.name.is_global() { - self.is_pure = false; + self.is_pure.merge(Pureness::Load); } } @@ -123,7 +130,8 @@ impl<'l, 'expr> Visitor<'l, 'expr> for Pure<'_> { } } Named::Expr(expr) => { - self.is_pure &= self.is_pure(&bind.name.name, expr); + let is_pure = self.is_pure(&bind.name.name, expr); + self.is_pure.merge(is_pure); } } self.visit_expr(expr); @@ -132,13 +140,13 @@ impl<'l, 'expr> Visitor<'l, 'expr> for Pure<'_> { Expr::Match(scrutinee, alts) => { let is_pure = self.is_pure; - self.is_pure = true; + self.is_pure = Pureness::Call; self.visit_expr(scrutinee); let scrutinee_is_pure = self.is_pure; - self.is_pure &= is_pure; + self.is_pure.merge(is_pure); - if scrutinee_is_pure { + if scrutinee_is_pure != Pureness::None { for alt in alts { self.mark_pure(&alt.pattern); } @@ -158,3 +166,28 @@ impl<'l, 'expr> Visitor<'l, 'expr> for Pure<'_> { None } } + +#[cfg(test)] +mod tests { + use super::*; + + use std::sync::Arc; + + use base::symbol::Symbols; + + use crate::core::interpreter::tests::parse_expr; + + #[test] + fn pure_global() { + let mut symbols = Symbols::new(); + + let allocator = Arc::new(Allocator::new()); + + let expr = parse_expr(&mut symbols, &allocator, "let x = global in x"); + + assert_eq!( + purity(expr).0, + collect![(symbols.simple_symbol("x"), Pureness::Load)] + ); + } +}