mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 23:31:12 +00:00
Overhaul canonicalization to use global symbols.
This commit is contained in:
parent
1b5df3f1c3
commit
74ad51aae9
4 changed files with 608 additions and 582 deletions
File diff suppressed because it is too large
Load diff
36
src/expr.rs
36
src/expr.rs
|
@ -40,7 +40,7 @@ pub enum Expr {
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum VariantName {
|
pub enum VariantName {
|
||||||
Unqualified(String),
|
Unqualified(String),
|
||||||
Qualified(Path, String),
|
Qualified(String, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An identifier, possibly fully-qualified with a module name
|
/// An identifier, possibly fully-qualified with a module name
|
||||||
|
@ -49,7 +49,7 @@ pub enum VariantName {
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum Ident {
|
pub enum Ident {
|
||||||
Unqualified(String),
|
Unqualified(String),
|
||||||
Qualified(Path, String),
|
Qualified(String, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ident {
|
impl Ident {
|
||||||
|
@ -68,34 +68,6 @@ impl Ident {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A path to a module, which may include the package it came from.
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
|
||||||
pub struct Path(String);
|
|
||||||
|
|
||||||
impl Path {
|
|
||||||
pub fn new(string: String) -> Path {
|
|
||||||
Path(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_string(self) -> String {
|
|
||||||
let Path(str) = self;
|
|
||||||
|
|
||||||
str
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<String> for Path {
|
|
||||||
fn into(self) -> String {
|
|
||||||
self.into_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for Path {
|
|
||||||
fn from(str: String) -> Self {
|
|
||||||
Path(str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Ident {
|
impl fmt::Display for Ident {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
@ -103,7 +75,7 @@ impl fmt::Display for Ident {
|
||||||
write!(f, "{}", name)
|
write!(f, "{}", name)
|
||||||
},
|
},
|
||||||
Ident::Qualified(path, name) => {
|
Ident::Qualified(path, name) => {
|
||||||
write!(f, "{}.{}", path.clone().into_string(), name)
|
write!(f, "{}.{}", path, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +88,7 @@ impl fmt::Display for VariantName {
|
||||||
write!(f, "{}", name)
|
write!(f, "{}", name)
|
||||||
},
|
},
|
||||||
VariantName::Qualified(path, name) => {
|
VariantName::Qualified(path, name) => {
|
||||||
write!(f, "{}.{}", path.clone().into_string(), name)
|
write!(f, "{}.{}", path, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
36
src/optimize.rs
Normal file
36
src/optimize.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// PHILOSOPHY
|
||||||
|
//
|
||||||
|
// Focus on optimizations which are only safe in the absence of side effects, and leave the rest to LLVM.
|
||||||
|
//
|
||||||
|
// This focus may lead to some optimizations becoming transitively in scope. For example, some deforestation
|
||||||
|
// examples in the MSR paper benefit from multiple rounds of interleaved deforestation, beta-reduction, and inlining.
|
||||||
|
// To get those benefits, we'd have to do some inlining and beta-reduction that we could otherwise leave to LLVM's
|
||||||
|
// inlining and constant propagation/folding.
|
||||||
|
//
|
||||||
|
// Even if we're doing those things, it may still make sense to have LLVM do a pass for them as well, since
|
||||||
|
// early LLVM optimization passes may unlock later opportunities for inlining and constant propagation/folding.
|
||||||
|
//
|
||||||
|
// INLINING
|
||||||
|
//
|
||||||
|
// If a function is called exactly once (it's a helper function), presumably we always want to inline those.
|
||||||
|
// If a function is "small enough" it's probably worth inlining too.
|
||||||
|
//
|
||||||
|
// FUSION
|
||||||
|
//
|
||||||
|
// https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/deforestation-short-cut.pdf
|
||||||
|
//
|
||||||
|
// Basic approach:
|
||||||
|
//
|
||||||
|
// Do list stuff using `build` passing Cons Nil (like a cons list) and then do foldr/build substitution/reduction.
|
||||||
|
// Afterwards, we can do a separate pass to flatten nested Cons structures into properly initialized RRBTs.
|
||||||
|
// This way we get both deforestation and efficient RRBT construction. Should work for the other collection types too.
|
||||||
|
//
|
||||||
|
// It looks like we need to do some amount of inlining and beta reductions on the Roc side, rather than
|
||||||
|
// leaving all of those to LLVM.
|
||||||
|
//
|
||||||
|
// Advanced approach:
|
||||||
|
//
|
||||||
|
// Express operations like map and filter in terms of toStream and fromStream, to unlock more deforestation.
|
||||||
|
// More info on here:
|
||||||
|
//
|
||||||
|
// https://wiki.haskell.org/GHC_optimisations#Fusion
|
|
@ -9,28 +9,27 @@ mod helpers;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_canonicalize {
|
mod test_canonicalize {
|
||||||
use roc::canonicalize;
|
use roc::canonicalize;
|
||||||
use roc::canonicalize::{Expr, Output, Problem, Resolved, LocalSymbol, Symbol};
|
use roc::canonicalize::{Expr, Output, Problem, Symbol, References};
|
||||||
use roc::canonicalize::Expr::*;
|
use roc::canonicalize::Expr::*;
|
||||||
use roc::canonicalize::Pattern::*;
|
use roc::expr::{Ident};
|
||||||
use roc::expr::{Path, Ident};
|
|
||||||
use roc::operator::Operator::*;
|
|
||||||
use roc::expr;
|
use roc::expr;
|
||||||
use roc::region::Located;
|
use roc::region::{Located, Region};
|
||||||
use roc::parse;
|
use roc::parse;
|
||||||
use roc::collections::{ImMap, ImSet};
|
use roc::collections::{ImMap, ImSet};
|
||||||
use roc::parse_state::{IndentablePosition};
|
use roc::parse_state::{IndentablePosition};
|
||||||
use combine::{Parser, eof};
|
use combine::{Parser, eof};
|
||||||
use combine::stream::state::{State};
|
use combine::stream::state::{State};
|
||||||
use helpers::{loc, loc_box, zero_loc_expr};
|
use helpers::{loc, zero_loc_expr};
|
||||||
|
|
||||||
fn can_expr(expr_str: &str) -> (Expr, Output, Vec<Problem>) {
|
fn can_expr(expr_str: &str) -> (Expr, Output, Vec<Problem>) {
|
||||||
can_expr_with(expr_str, &ImMap::default(), &ImMap::default())
|
can_expr_with("testDecl", expr_str, &ImMap::default(), &ImMap::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_expr_with(
|
fn can_expr_with(
|
||||||
|
name: &str,
|
||||||
expr_str: &str,
|
expr_str: &str,
|
||||||
declared_idents: &ImMap<Ident, Located<expr::Ident>>,
|
declared_idents: &ImMap<Ident, (Symbol, Region)>,
|
||||||
declared_variants: &ImMap<(Path, String), Located<expr::VariantName>>,
|
declared_variants: &ImMap<Symbol, Located<expr::VariantName>>,
|
||||||
) -> (Expr, Output, Vec<Problem>) {
|
) -> (Expr, Output, Vec<Problem>) {
|
||||||
let parse_state: State<&str, IndentablePosition> = State::with_positioner(expr_str, IndentablePosition::default());
|
let parse_state: State<&str, IndentablePosition> = State::with_positioner(expr_str, IndentablePosition::default());
|
||||||
let expr = match parse::expr().skip(eof()).easy_parse(parse_state) {
|
let expr = match parse::expr().skip(eof()).easy_parse(parse_state) {
|
||||||
|
@ -47,23 +46,15 @@ mod test_canonicalize {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let home = Path::new("TestModule".to_string());
|
let home = "TestModule".to_string();
|
||||||
let (loc_expr, output, problems) =
|
let (loc_expr, output, problems) =
|
||||||
canonicalize::canonicalize_declaration(home, loc(zero_loc_expr(expr)), declared_idents, declared_variants);
|
canonicalize::canonicalize_declaration(home, name, loc(zero_loc_expr(expr)), declared_idents, declared_variants);
|
||||||
|
|
||||||
(loc_expr.value, output, problems)
|
(loc_expr.value, output, problems)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recognized_local_sym(string: &str) -> Resolved<Symbol> {
|
fn sym(name: &str) -> Symbol {
|
||||||
Resolved::Recognized(local_sym(string))
|
Symbol::new("TestModule$testDecl$", name)
|
||||||
}
|
|
||||||
|
|
||||||
fn local_sym(string: &str) -> Symbol {
|
|
||||||
Symbol::Local(local(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn local(string: &str) -> LocalSymbol {
|
|
||||||
LocalSymbol::new(string.to_string())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unqualified(string :&str) -> Ident {
|
fn unqualified(string :&str) -> Ident {
|
||||||
|
@ -74,32 +65,29 @@ mod test_canonicalize {
|
||||||
Problem::UnusedAssignment(loc(unqualified(string)))
|
Problem::UnusedAssignment(loc(unqualified(string)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_output(
|
struct Out<'a> {
|
||||||
output: Output,
|
locals: Vec<&'a str>,
|
||||||
applied_variants: Vec<(Path, &str)>,
|
globals: Vec<&'a str>,
|
||||||
referenced_idents: Vec<(Option<Path>, &str)>,
|
variants: Vec<&'a str>,
|
||||||
tail_call: Option<Symbol>
|
tail_call: Option<&'a str>
|
||||||
) {
|
|
||||||
assert_eq!(
|
|
||||||
output,
|
|
||||||
Output {
|
|
||||||
referenced_idents:
|
|
||||||
ImSet::from(
|
|
||||||
referenced_idents.into_iter().map(|(opt_path, str_ref)|
|
|
||||||
match opt_path {
|
|
||||||
Some(path) => Ident::Qualified(path, str_ref.to_string()),
|
|
||||||
None => Ident::Unqualified(str_ref.to_string())
|
|
||||||
}
|
}
|
||||||
).collect::<Vec<_>>()
|
|
||||||
),
|
impl<'a> Into<Output> for Out<'a> {
|
||||||
applied_variants:
|
fn into(self) -> Output {
|
||||||
ImSet::from(
|
fn vec_to_set<'b>(vec: Vec<&'b str>) -> ImSet<Symbol> {
|
||||||
applied_variants.into_iter().map(|(path, str_ref)|
|
ImSet::from(vec.into_iter().map(sym).collect::<Vec<_>>())
|
||||||
(path, str_ref.to_string()),
|
}
|
||||||
).collect::<Vec<_>>()),
|
|
||||||
tail_call
|
let references = References {
|
||||||
|
locals: vec_to_set(self.locals),
|
||||||
|
globals: vec_to_set(self.globals),
|
||||||
|
variants: vec_to_set(self.variants)
|
||||||
|
};
|
||||||
|
|
||||||
|
let tail_call = self.tail_call.map(sym);
|
||||||
|
|
||||||
|
Output {references, tail_call}
|
||||||
}
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -113,15 +101,20 @@ mod test_canonicalize {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert_eq!(expr,
|
assert_eq!(expr,
|
||||||
Var(Resolved::UnrecognizedConstant(loc(Ident::Unqualified("x".to_string()))))
|
UnrecognizedConstant(loc(Ident::Unqualified("x".to_string())))
|
||||||
);
|
);
|
||||||
|
|
||||||
check_output(output, vec![], vec![], None);
|
assert_eq!(output, Out {
|
||||||
|
locals: vec![],
|
||||||
|
globals: vec![],
|
||||||
|
variants: vec![],
|
||||||
|
tail_call: None
|
||||||
|
}.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn complex_unrecognized_constant() {
|
fn complex_unrecognized_constant() {
|
||||||
let (expr, output, problems) = can_expr(indoc!(r#"
|
let (_, output, problems) = can_expr(indoc!(r#"
|
||||||
a = 5
|
a = 5
|
||||||
b = 6
|
b = 6
|
||||||
|
|
||||||
|
@ -132,25 +125,12 @@ mod test_canonicalize {
|
||||||
Problem::UnrecognizedConstant(loc(Ident::Unqualified("z".to_string())))
|
Problem::UnrecognizedConstant(loc(Ident::Unqualified("z".to_string())))
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert_eq!(expr,
|
assert_eq!(output, Out {
|
||||||
Assign(
|
locals: vec!["a", "b"],
|
||||||
vec![
|
globals: vec![],
|
||||||
(loc(Identifier(local("a"))), loc(Int(5))),
|
variants: vec![],
|
||||||
(loc(Identifier(local("b"))), loc(Int(6))),
|
tail_call: None
|
||||||
],
|
}.into());
|
||||||
loc_box(Operator(
|
|
||||||
loc_box(Var(recognized_local_sym("a"))),
|
|
||||||
loc(Plus),
|
|
||||||
loc_box(Operator(
|
|
||||||
loc_box(Var(recognized_local_sym("b"))),
|
|
||||||
loc(Star),
|
|
||||||
loc_box(Var(Resolved::UnrecognizedConstant(loc(Ident::Unqualified("z".to_string())))))
|
|
||||||
)),
|
|
||||||
))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
check_output(output, vec![], vec![(None, "a"), (None, "b")], None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -166,7 +146,12 @@ mod test_canonicalize {
|
||||||
|
|
||||||
assert_eq!(problems, vec![unused("b"), unused("a")]);
|
assert_eq!(problems, vec![unused("b"), unused("a")]);
|
||||||
|
|
||||||
check_output(output, vec![], vec![(None, "c")], None);
|
assert_eq!(output, Out {
|
||||||
|
locals: vec!["c"],
|
||||||
|
globals: vec![],
|
||||||
|
variants: vec![],
|
||||||
|
tail_call: None
|
||||||
|
}.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -184,11 +169,12 @@ mod test_canonicalize {
|
||||||
|
|
||||||
assert_eq!(problems, vec![]);
|
assert_eq!(problems, vec![]);
|
||||||
|
|
||||||
check_output(output,
|
assert_eq!(output, Out {
|
||||||
vec![],
|
locals: vec!["num", "fibonacci"],
|
||||||
vec![(None, "num"), (None, "fibonacci")],
|
globals: vec![],
|
||||||
Some(local_sym("fibonacci"))
|
variants: vec![],
|
||||||
);
|
tail_call: Some("fibonacci")
|
||||||
|
}.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// UNSUPPORTED PATTERNS
|
// UNSUPPORTED PATTERNS
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue