Merge branch 'trunk' of github.com:rtfeldman/roc into wasm-runtime-error

This commit is contained in:
Brian Carroll 2022-03-07 19:16:55 +00:00
commit eaf76aa879
84 changed files with 4796 additions and 2243 deletions

View file

@ -65,3 +65,6 @@ Mats Sigge <<mats.sigge@gmail.com>>
Drew Lazzeri <dlazzeri1@gmail.com> Drew Lazzeri <dlazzeri1@gmail.com>
Tom Dohrmann <erbse.13@gmx.de> Tom Dohrmann <erbse.13@gmx.de>
Elijah Schow <elijah.schow@gmail.com> Elijah Schow <elijah.schow@gmail.com>
Derek Gustafson <degustaf@gmail.com>
Philippe Vinchon <p.vinchon@gmail.com>
Pierre-Henri Trivier <phtrivier@yahoo.fr>

24
Cargo.lock generated
View file

@ -1709,7 +1709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46e977036f7f5139d580c7f19ad62df9cb8ebd8410bb569e73585226be80a86f" checksum = "46e977036f7f5139d580c7f19ad62df9cb8ebd8410bb569e73585226be80a86f"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"static_assertions", "static_assertions 1.1.0",
] ]
[[package]] [[package]]
@ -3357,6 +3357,7 @@ dependencies = [
"roc_problem", "roc_problem",
"roc_region", "roc_region",
"roc_types", "roc_types",
"static_assertions 1.1.0",
"ven_graph", "ven_graph",
] ]
@ -3426,6 +3427,7 @@ dependencies = [
name = "roc_constrain" name = "roc_constrain"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"arrayvec 0.7.2",
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
@ -3659,7 +3661,7 @@ dependencies = [
"roc_ident", "roc_ident",
"roc_region", "roc_region",
"snafu", "snafu",
"static_assertions", "static_assertions 1.1.0",
] ]
[[package]] [[package]]
@ -3682,7 +3684,7 @@ dependencies = [
"roc_target", "roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"static_assertions", "static_assertions 1.1.0",
"ven_graph", "ven_graph",
"ven_pretty", "ven_pretty",
] ]
@ -3718,7 +3720,7 @@ dependencies = [
name = "roc_region" name = "roc_region"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"static_assertions", "static_assertions 1.1.0",
] ]
[[package]] [[package]]
@ -3760,6 +3762,7 @@ dependencies = [
"roc_parse", "roc_parse",
"roc_region", "roc_region",
"roc_reporting", "roc_reporting",
"roc_std",
"roc_target", "roc_target",
"roc_types", "roc_types",
] ]
@ -3834,6 +3837,9 @@ dependencies = [
[[package]] [[package]]
name = "roc_std" name = "roc_std"
version = "0.1.0" version = "0.1.0"
dependencies = [
"static_assertions 0.1.1",
]
[[package]] [[package]]
name = "roc_target" name = "roc_target"
@ -3858,7 +3864,7 @@ dependencies = [
"roc_error_macros", "roc_error_macros",
"roc_module", "roc_module",
"roc_region", "roc_region",
"static_assertions", "static_assertions 1.1.0",
"ven_ena", "ven_ena",
] ]
@ -4278,6 +4284,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_assertions"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7"
[[package]] [[package]]
name = "static_assertions" name = "static_assertions"
version = "1.1.0" version = "1.1.0"
@ -4566,7 +4578,7 @@ checksum = "1f559b464de2e2bdabcac6a210d12e9b5a5973c251e102c44c585c71d51bd78e"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"rand", "rand",
"static_assertions", "static_assertions 1.1.0",
] ]
[[package]] [[package]]

View file

@ -1,4 +1,4 @@
FROM rust:1.58.0-slim-bullseye # make sure to update nixpkgs-unstable in sources.json too so that it uses the same rust version > search for cargo on unstable here: https://search.nixos.org/packages FROM rust:1.58.0-slim-bullseye # make sure to update rust-toolchain.toml and nixpkgs-unstable in sources.json too so that it uses the same rust version > search for cargo on unstable here: https://search.nixos.org/packages
WORKDIR /earthbuild WORKDIR /earthbuild
prep-debian: prep-debian:
@ -93,7 +93,7 @@ test-rust:
RUN --mount=type=cache,target=$SCCACHE_DIR \ RUN --mount=type=cache,target=$SCCACHE_DIR \
repl_test/test_wasm.sh && sccache --show-stats repl_test/test_wasm.sh && sccache --show-stats
# run i386 (32-bit linux) cli tests # run i386 (32-bit linux) cli tests
RUN echo "4" | cargo run --locked --release --features="target-x86" -- --backend=x86_32 examples/benchmarks/NQueens.roc RUN echo "4" | cargo run --locked --release --features="target-x86" -- --target=x86_32 examples/benchmarks/NQueens.roc
RUN --mount=type=cache,target=$SCCACHE_DIR \ RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --locked --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats cargo test --locked --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats

View file

@ -1,7 +1,7 @@
use bumpalo::{collections::Vec as BumpVec, Bump}; use bumpalo::{collections::Vec as BumpVec, Bump};
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
use roc_collections::all::{BumpMap, BumpMapDefault, Index, SendMap}; use roc_collections::all::{BumpMap, BumpMapDefault, HumanIndex, SendMap};
use roc_module::{ use roc_module::{
ident::{Lowercase, TagName}, ident::{Lowercase, TagName},
symbol::Symbol, symbol::Symbol,
@ -163,7 +163,7 @@ pub fn constrain_expr<'a>(
let elem_expected = Expected::ForReason( let elem_expected = Expected::ForReason(
Reason::ElemInList { Reason::ElemInList {
index: Index::zero_based(index), index: HumanIndex::zero_based(index),
}, },
list_elem_type.shallow_clone(), list_elem_type.shallow_clone(),
region, region,
@ -339,7 +339,7 @@ pub fn constrain_expr<'a>(
let reason = Reason::FnArg { let reason = Reason::FnArg {
name: opt_symbol, name: opt_symbol,
arg_index: Index::zero_based(index), arg_index: HumanIndex::zero_based(index),
}; };
let expected_arg = Expected::ForReason(reason, arg_type.shallow_clone(), region); let expected_arg = Expected::ForReason(reason, arg_type.shallow_clone(), region);
@ -538,7 +538,7 @@ pub fn constrain_expr<'a>(
name.clone(), name.clone(),
arity, arity,
AnnotationSource::TypedIfBranch { AnnotationSource::TypedIfBranch {
index: Index::zero_based(index), index: HumanIndex::zero_based(index),
num_branches, num_branches,
region: ann_source.region(), region: ann_source.region(),
}, },
@ -559,7 +559,7 @@ pub fn constrain_expr<'a>(
name, name,
arity, arity,
AnnotationSource::TypedIfBranch { AnnotationSource::TypedIfBranch {
index: Index::zero_based(branches.len()), index: HumanIndex::zero_based(branches.len()),
num_branches, num_branches,
region: ann_source.region(), region: ann_source.region(),
}, },
@ -596,7 +596,7 @@ pub fn constrain_expr<'a>(
body, body,
Expected::ForReason( Expected::ForReason(
Reason::IfBranch { Reason::IfBranch {
index: Index::zero_based(index), index: HumanIndex::zero_based(index),
total_branches: branches.len(), total_branches: branches.len(),
}, },
Type2::Variable(*expr_var), Type2::Variable(*expr_var),
@ -616,7 +616,7 @@ pub fn constrain_expr<'a>(
final_else_expr, final_else_expr,
Expected::ForReason( Expected::ForReason(
Reason::IfBranch { Reason::IfBranch {
index: Index::zero_based(branches.len()), index: HumanIndex::zero_based(branches.len()),
total_branches: branches.len() + 1, total_branches: branches.len() + 1,
}, },
Type2::Variable(*expr_var), Type2::Variable(*expr_var),
@ -691,7 +691,7 @@ pub fn constrain_expr<'a>(
when_branch, when_branch,
PExpected::ForReason( PExpected::ForReason(
PReason::WhenMatch { PReason::WhenMatch {
index: Index::zero_based(index), index: HumanIndex::zero_based(index),
}, },
cond_type.shallow_clone(), cond_type.shallow_clone(),
pattern_region, pattern_region,
@ -700,7 +700,7 @@ pub fn constrain_expr<'a>(
name.clone(), name.clone(),
*arity, *arity,
AnnotationSource::TypedWhenBranch { AnnotationSource::TypedWhenBranch {
index: Index::zero_based(index), index: HumanIndex::zero_based(index),
region: ann_source.region(), region: ann_source.region(),
}, },
typ.shallow_clone(), typ.shallow_clone(),
@ -733,14 +733,14 @@ pub fn constrain_expr<'a>(
when_branch, when_branch,
PExpected::ForReason( PExpected::ForReason(
PReason::WhenMatch { PReason::WhenMatch {
index: Index::zero_based(index), index: HumanIndex::zero_based(index),
}, },
cond_type.shallow_clone(), cond_type.shallow_clone(),
pattern_region, pattern_region,
), ),
Expected::ForReason( Expected::ForReason(
Reason::WhenBranch { Reason::WhenBranch {
index: Index::zero_based(index), index: HumanIndex::zero_based(index),
}, },
branch_type.shallow_clone(), branch_type.shallow_clone(),
// TODO: when_branch.value.region, // TODO: when_branch.value.region,
@ -1065,7 +1065,7 @@ pub fn constrain_expr<'a>(
let reason = Reason::LowLevelOpArg { let reason = Reason::LowLevelOpArg {
op: *op, op: *op,
arg_index: Index::zero_based(index), arg_index: HumanIndex::zero_based(index),
}; };
let expected_arg = let expected_arg =
Expected::ForReason(reason, arg_type.shallow_clone(), Region::zero()); Expected::ForReason(reason, arg_type.shallow_clone(), Region::zero());
@ -1681,7 +1681,7 @@ fn constrain_tag_pattern<'a>(
let expected = PExpected::ForReason( let expected = PExpected::ForReason(
PReason::TagArg { PReason::TagArg {
tag_name: tag_name.clone(), tag_name: tag_name.clone(),
index: Index::zero_based(index), index: HumanIndex::zero_based(index),
}, },
pattern_type, pattern_type,
region, region,

View file

@ -1,133 +0,0 @@
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module;
use roc_parse::ast::{Def, Module};
use roc_parse::module::module_defs;
use roc_parse::parser;
use roc_parse::parser::{Parser, SyntaxError};
use roc_region::all::Located;
use std::ffi::OsStr;
use std::path::Path;
use std::{fs, io};
#[derive(Debug)]
pub struct File<'a> {
path: &'a Path,
module_header: Module<'a>,
content: Vec<'a, Located<Def<'a>>>,
}
#[derive(Debug)]
pub enum ReadError<'a> {
Read(std::io::Error),
ParseDefs(SyntaxError<'a>),
ParseHeader(SyntaxError<'a>),
DoesntHaveRocExtension,
}
impl<'a> File<'a> {
pub fn read(path: &'a Path, arena: &'a Bump) -> Result<File<'a>, ReadError<'a>> {
if path.extension() != Some(OsStr::new("roc")) {
return Err(ReadError::DoesntHaveRocExtension);
}
let bytes = fs::read(path).map_err(ReadError::Read)?;
let allocation = arena.alloc(bytes);
let module_parse_state = parser::State::new(allocation);
let parsed_module = roc_parse::module::parse_header(arena, module_parse_state);
match parsed_module {
Ok((module, state)) => {
let parsed_defs = module_defs().parse(arena, state);
match parsed_defs {
Ok((_, defs, _)) => Ok(File {
path,
module_header: module,
content: defs,
}),
Err((_, error, _)) => Err(ReadError::ParseDefs(error)),
}
}
Err(error) => Err(ReadError::ParseHeader(SyntaxError::Header(error))),
}
}
pub fn fmt(&self) -> String {
let arena = Bump::new();
let mut formatted_file = String::new();
let mut module_header_buf = bumpalo::collections::String::new_in(&arena);
fmt_module(&mut module_header_buf, &self.module_header);
formatted_file.push_str(module_header_buf.as_str());
for def in &self.content {
let mut def_buf = bumpalo::collections::String::new_in(&arena);
fmt_def(&mut def_buf, &def.value, 0);
formatted_file.push_str(def_buf.as_str());
}
formatted_file
}
pub fn fmt_then_write_to(&self, write_path: &'a Path) -> io::Result<()> {
let formatted_file = self.fmt();
fs::write(write_path, formatted_file)
}
pub fn fmt_then_write_with_name(&self, new_name: &str) -> io::Result<()> {
self.fmt_then_write_to(
self.path
.with_file_name(new_name)
.with_extension("roc")
.as_path(),
)
}
pub fn fmt_then_write(&self) -> io::Result<()> {
self.fmt_then_write_to(self.path)
}
}
#[cfg(test)]
mod test_file {
use crate::lang::roc_file;
use bumpalo::Bump;
use std::path::Path;
#[test]
fn read_and_fmt_simple_roc_module() {
let simple_module_path = Path::new("./tests/modules/SimpleUnformatted.roc");
let arena = Bump::new();
let file = roc_file::File::read(simple_module_path, &arena)
.expect("Could not read SimpleUnformatted.roc in test_file test");
assert_eq!(
file.fmt(),
indoc!(
r#"
interface Simple
exposes [
v, x
]
imports []
v : Str
v = "Value!"
x : Int
x = 4"#
)
);
}
}

View file

@ -180,6 +180,8 @@ fn fmt_all<'a>(arena: &'a Bump, buf: &mut Buf<'a>, ast: &'a Ast) {
for def in &ast.defs { for def in &ast.defs {
fmt_def(buf, arena.alloc(def.value), 0); fmt_def(buf, arena.alloc(def.value), 0);
} }
buf.fmt_end_of_file();
} }
/// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting. /// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting.

View file

@ -34,7 +34,7 @@ pub const FLAG_DEV: &str = "dev";
pub const FLAG_OPTIMIZE: &str = "optimize"; pub const FLAG_OPTIMIZE: &str = "optimize";
pub const FLAG_OPT_SIZE: &str = "opt-size"; pub const FLAG_OPT_SIZE: &str = "opt-size";
pub const FLAG_LIB: &str = "lib"; pub const FLAG_LIB: &str = "lib";
pub const FLAG_BACKEND: &str = "backend"; pub const FLAG_TARGET: &str = "target";
pub const FLAG_TIME: &str = "time"; pub const FLAG_TIME: &str = "time";
pub const FLAG_LINK: &str = "roc-linker"; pub const FLAG_LINK: &str = "roc-linker";
pub const FLAG_PRECOMPILED: &str = "precompiled-host"; pub const FLAG_PRECOMPILED: &str = "precompiled-host";
@ -42,7 +42,6 @@ pub const FLAG_VALGRIND: &str = "valgrind";
pub const FLAG_CHECK: &str = "check"; pub const FLAG_CHECK: &str = "check";
pub const ROC_FILE: &str = "ROC_FILE"; pub const ROC_FILE: &str = "ROC_FILE";
pub const ROC_DIR: &str = "ROC_DIR"; pub const ROC_DIR: &str = "ROC_DIR";
pub const BACKEND: &str = "BACKEND";
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES"; pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP"; pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
@ -76,12 +75,11 @@ pub fn build_app<'a>() -> App<'a> {
.required(false), .required(false),
) )
.arg( .arg(
Arg::new(FLAG_BACKEND) Arg::new(FLAG_TARGET)
.long(FLAG_BACKEND) .long(FLAG_TARGET)
.about("Choose a different backend") .about("Choose a different target")
// .requires(BACKEND) .default_value(Target::default().as_str())
.default_value(Backend::default().as_str()) .possible_values(Target::OPTIONS)
.possible_values(Backend::OPTIONS)
.required(false), .required(false),
) )
.arg( .arg(
@ -212,12 +210,11 @@ pub fn build_app<'a>() -> App<'a> {
.required(false), .required(false),
) )
.arg( .arg(
Arg::new(FLAG_BACKEND) Arg::new(FLAG_TARGET)
.long(FLAG_BACKEND) .long(FLAG_TARGET)
.about("Choose a different backend") .about("Choose a different target")
// .requires(BACKEND) .default_value(Target::default().as_str())
.default_value(Backend::default().as_str()) .possible_values(Target::OPTIONS)
.possible_values(Backend::OPTIONS)
.required(false), .required(false),
) )
.arg( .arg(
@ -273,12 +270,12 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
use std::str::FromStr; use std::str::FromStr;
use BuildConfig::*; use BuildConfig::*;
let backend = match matches.value_of(FLAG_BACKEND) { let target = match matches.value_of(FLAG_TARGET) {
Some(name) => Backend::from_str(name).unwrap(), Some(name) => Target::from_str(name).unwrap(),
None => Backend::default(), None => Target::default(),
}; };
let target = backend.to_triple(); let triple = target.to_triple();
let arena = Bump::new(); let arena = Bump::new();
let filename = matches.value_of(ROC_FILE).unwrap(); let filename = matches.value_of(ROC_FILE).unwrap();
@ -306,10 +303,10 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
let surgically_link = matches.is_present(FLAG_LINK); let surgically_link = matches.is_present(FLAG_LINK);
let precompiled = matches.is_present(FLAG_PRECOMPILED); let precompiled = matches.is_present(FLAG_PRECOMPILED);
if surgically_link && !roc_linker::supported(&link_type, &target) { if surgically_link && !roc_linker::supported(&link_type, &triple) {
panic!( panic!(
"Link type, {:?}, with target, {}, not supported by roc linker", "Link type, {:?}, with target, {}, not supported by roc linker",
link_type, target link_type, triple
); );
} }
@ -338,7 +335,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
let target_valgrind = matches.is_present(FLAG_VALGRIND); let target_valgrind = matches.is_present(FLAG_VALGRIND);
let res_binary_path = build_file( let res_binary_path = build_file(
&arena, &arena,
&target, &triple,
src_dir, src_dir,
path, path,
opt_level, opt_level,
@ -377,7 +374,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
Ok(outcome.status_code()) Ok(outcome.status_code())
} }
BuildAndRun { roc_file_arg_index } => { BuildAndRun { roc_file_arg_index } => {
let mut cmd = match target.architecture { let mut cmd = match triple.architecture {
Architecture::Wasm32 => { Architecture::Wasm32 => {
// If possible, report the generated executable name relative to the current dir. // If possible, report the generated executable name relative to the current dir.
let generated_filename = binary_path let generated_filename = binary_path
@ -398,7 +395,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
_ => Command::new(&binary_path), _ => Command::new(&binary_path),
}; };
if let Architecture::Wasm32 = target.architecture { if let Architecture::Wasm32 = triple.architecture {
cmd.arg(binary_path); cmd.arg(binary_path);
} }
@ -503,43 +500,43 @@ fn run_with_wasmer(_wasm_path: &std::path::Path, _args: &[String]) {
println!("Running wasm files not support"); println!("Running wasm files not support");
} }
enum Backend { enum Target {
Host, Host,
X86_32, X86_32,
X86_64, X86_64,
Wasm32, Wasm32,
} }
impl Default for Backend { impl Default for Target {
fn default() -> Self { fn default() -> Self {
Backend::Host Target::Host
} }
} }
impl Backend { impl Target {
const fn as_str(&self) -> &'static str { const fn as_str(&self) -> &'static str {
match self { match self {
Backend::Host => "host", Target::Host => "host",
Backend::X86_32 => "x86_32", Target::X86_32 => "x86_32",
Backend::X86_64 => "x86_64", Target::X86_64 => "x86_64",
Backend::Wasm32 => "wasm32", Target::Wasm32 => "wasm32",
} }
} }
/// NOTE keep up to date! /// NOTE keep up to date!
const OPTIONS: &'static [&'static str] = &[ const OPTIONS: &'static [&'static str] = &[
Backend::Host.as_str(), Target::Host.as_str(),
Backend::X86_32.as_str(), Target::X86_32.as_str(),
Backend::X86_64.as_str(), Target::X86_64.as_str(),
Backend::Wasm32.as_str(), Target::Wasm32.as_str(),
]; ];
fn to_triple(&self) -> Triple { fn to_triple(&self) -> Triple {
let mut triple = Triple::unknown(); let mut triple = Triple::unknown();
match self { match self {
Backend::Host => Triple::host(), Target::Host => Triple::host(),
Backend::X86_32 => { Target::X86_32 => {
triple.architecture = Architecture::X86_32(X86_32Architecture::I386); triple.architecture = Architecture::X86_32(X86_32Architecture::I386);
triple.binary_format = BinaryFormat::Elf; triple.binary_format = BinaryFormat::Elf;
@ -548,13 +545,13 @@ impl Backend {
triple triple
} }
Backend::X86_64 => { Target::X86_64 => {
triple.architecture = Architecture::X86_64; triple.architecture = Architecture::X86_64;
triple.binary_format = BinaryFormat::Elf; triple.binary_format = BinaryFormat::Elf;
triple triple
} }
Backend::Wasm32 => { Target::Wasm32 => {
triple.architecture = Architecture::Wasm32; triple.architecture = Architecture::Wasm32;
triple.binary_format = BinaryFormat::Wasm; triple.binary_format = BinaryFormat::Wasm;
@ -564,21 +561,21 @@ impl Backend {
} }
} }
impl std::fmt::Display for Backend { impl std::fmt::Display for Target {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.as_str()) write!(f, "{}", self.as_str())
} }
} }
impl std::str::FromStr for Backend { impl std::str::FromStr for Target {
type Err = (); type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s {
"host" => Ok(Backend::Host), "host" => Ok(Target::Host),
"x86_32" => Ok(Backend::X86_32), "x86_32" => Ok(Target::X86_32),
"x86_64" => Ok(Backend::X86_64), "x86_64" => Ok(Target::X86_64),
"wasm32" => Ok(Backend::Wasm32), "wasm32" => Ok(Target::Wasm32),
_ => Err(()), _ => Err(()),
} }
} }

View file

@ -64,15 +64,15 @@ mod cli_run {
} }
fn check_compile_error(file: &Path, flags: &[&str], expected: &str) { fn check_compile_error(file: &Path, flags: &[&str], expected: &str) {
let compile_out = run_roc(&[&["check", file.to_str().unwrap()], &flags[..]].concat()); let compile_out = run_roc(&[&["check", file.to_str().unwrap()], flags].concat());
let err = compile_out.stdout.trim(); let err = compile_out.stdout.trim();
let err = strip_colors(&err); let err = strip_colors(err);
assert_multiline_str_eq!(err, expected.into()); assert_multiline_str_eq!(err, expected.into());
} }
fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) { fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) {
let flags = &["--check"]; let flags = &["--check"];
let out = run_roc(&[&["format", &file.to_str().unwrap()], &flags[..]].concat()); let out = run_roc(&[&["format", file.to_str().unwrap()], &flags[..]].concat());
if expects_success_exit_code { if expects_success_exit_code {
assert!(out.status.success()); assert!(out.status.success());
@ -194,7 +194,7 @@ mod cli_run {
) { ) {
assert_eq!(input_file, None, "Wasm does not support input files"); assert_eq!(input_file, None, "Wasm does not support input files");
let mut flags = flags.to_vec(); let mut flags = flags.to_vec();
flags.push("--backend=wasm32"); flags.push("--target=wasm32");
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags.as_slice()].concat()); let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags.as_slice()].concat());
if !compile_out.stderr.is_empty() { if !compile_out.stderr.is_empty() {
@ -565,7 +565,7 @@ mod cli_run {
&file_name, &file_name,
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
&["--backend=x86_32"], &["--target=x86_32"],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))), benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending, benchmark.expected_ending,
benchmark.use_valgrind, benchmark.use_valgrind,
@ -575,7 +575,7 @@ mod cli_run {
&file_name, &file_name,
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
&["--backend=x86_32", "--optimize"], &["--target=x86_32", "--optimize"],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))), benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending, benchmark.expected_ending,
benchmark.use_valgrind, benchmark.use_valgrind,

View file

@ -61,15 +61,15 @@ pub export fn main() i32 {
// actually call roc to populate the callresult // actually call roc to populate the callresult
const callresult = roc__mainForHost_1_exposed(); const callresult = roc__mainForHost_1_exposed();
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
// stdout the result // stdout the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
callresult.deinit(); callresult.deinit();
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
const delta = to_seconds(ts2) - to_seconds(ts1); const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;

View file

@ -60,15 +60,15 @@ pub export fn main() i32 {
// actually call roc to populate the callresult // actually call roc to populate the callresult
const callresult = roc__mainForHost_1_exposed(); const callresult = roc__mainForHost_1_exposed();
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
// stdout the result // stdout the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
callresult.deinit(); callresult.deinit();
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
const delta = to_seconds(ts2) - to_seconds(ts1); const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;

View file

@ -16,6 +16,7 @@ roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
ven_graph = { path = "../../vendor/pathfinding" } ven_graph = { path = "../../vendor/pathfinding" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }
static_assertions = "1.1.0"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.0.0" pretty_assertions = "1.0.0"

View file

@ -29,6 +29,8 @@ pub struct IntroducedVariables {
// but a variable can only have one name. Therefore // but a variable can only have one name. Therefore
// `ftv : SendMap<Variable, Lowercase>`. // `ftv : SendMap<Variable, Lowercase>`.
pub wildcards: Vec<Variable>, pub wildcards: Vec<Variable>,
pub lambda_sets: Vec<Variable>,
pub inferred: Vec<Variable>,
pub var_by_name: SendMap<Lowercase, Variable>, pub var_by_name: SendMap<Lowercase, Variable>,
pub name_by_var: SendMap<Variable, Lowercase>, pub name_by_var: SendMap<Variable, Lowercase>,
pub host_exposed_aliases: MutMap<Symbol, Variable>, pub host_exposed_aliases: MutMap<Symbol, Variable>,
@ -44,12 +46,22 @@ impl IntroducedVariables {
self.wildcards.push(var); self.wildcards.push(var);
} }
pub fn insert_inferred(&mut self, var: Variable) {
self.inferred.push(var);
}
fn insert_lambda_set(&mut self, var: Variable) {
self.lambda_sets.push(var);
}
pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) { pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) {
self.host_exposed_aliases.insert(symbol, var); self.host_exposed_aliases.insert(symbol, var);
} }
pub fn union(&mut self, other: &Self) { pub fn union(&mut self, other: &Self) {
self.wildcards.extend(other.wildcards.iter().cloned()); self.wildcards.extend(other.wildcards.iter().cloned());
self.lambda_sets.extend(other.lambda_sets.iter().cloned());
self.inferred.extend(other.inferred.iter().cloned());
self.var_by_name.extend(other.var_by_name.clone()); self.var_by_name.extend(other.var_by_name.clone());
self.name_by_var.extend(other.name_by_var.clone()); self.name_by_var.extend(other.name_by_var.clone());
self.host_exposed_aliases self.host_exposed_aliases
@ -280,7 +292,9 @@ fn can_annotation_help(
references, references,
); );
let closure = Type::Variable(var_store.fresh()); let lambda_set = var_store.fresh();
introduced_variables.insert_lambda_set(lambda_set);
let closure = Type::Variable(lambda_set);
Type::Function(args, Box::new(closure), Box::new(ret)) Type::Function(args, Box::new(closure), Box::new(ret))
} }
@ -326,6 +340,7 @@ fn can_annotation_help(
let (type_arguments, lambda_set_variables, actual) = let (type_arguments, lambda_set_variables, actual) =
instantiate_and_freshen_alias_type( instantiate_and_freshen_alias_type(
var_store, var_store,
introduced_variables,
&alias.type_variables, &alias.type_variables,
args, args,
&alias.lambda_set_variables, &alias.lambda_set_variables,
@ -505,19 +520,16 @@ fn can_annotation_help(
} }
Record { fields, ext } => { Record { fields, ext } => {
let ext_type = match ext { let ext_type = can_extension_type(
Some(loc_ann) => can_annotation_help( env,
env, scope,
&loc_ann.value, var_store,
region, introduced_variables,
scope, local_aliases,
var_store, references,
introduced_variables, ext,
local_aliases, roc_problem::can::ExtensionTypeKind::Record,
references, );
),
None => Type::EmptyRec,
};
if fields.is_empty() { if fields.is_empty() {
match ext { match ext {
@ -546,19 +558,16 @@ fn can_annotation_help(
} }
} }
TagUnion { tags, ext, .. } => { TagUnion { tags, ext, .. } => {
let ext_type = match ext { let ext_type = can_extension_type(
Some(loc_ann) => can_annotation_help( env,
env, scope,
&loc_ann.value, var_store,
loc_ann.region, introduced_variables,
scope, local_aliases,
var_store, references,
introduced_variables, ext,
local_aliases, roc_problem::can::ExtensionTypeKind::TagUnion,
references, );
),
None => Type::EmptyTagUnion,
};
if tags.is_empty() { if tags.is_empty() {
match ext { match ext {
@ -612,6 +621,9 @@ fn can_annotation_help(
// Inference variables aren't bound to a rigid or a wildcard, so all we have to do is // Inference variables aren't bound to a rigid or a wildcard, so all we have to do is
// make a fresh unconstrained variable, and let the type solver fill it in for us 🤠 // make a fresh unconstrained variable, and let the type solver fill it in for us 🤠
let var = var_store.fresh(); let var = var_store.fresh();
introduced_variables.insert_inferred(var);
Type::Variable(var) Type::Variable(var)
} }
Malformed(string) => { Malformed(string) => {
@ -626,8 +638,77 @@ fn can_annotation_help(
} }
} }
#[allow(clippy::too_many_arguments)]
fn can_extension_type<'a>(
env: &mut Env,
scope: &mut Scope,
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
references: &mut MutSet<Symbol>,
opt_ext: &Option<&Loc<TypeAnnotation<'a>>>,
ext_problem_kind: roc_problem::can::ExtensionTypeKind,
) -> Type {
fn valid_record_ext_type(typ: &Type) -> bool {
// Include erroneous types so that we don't overreport errors.
matches!(
typ,
Type::EmptyRec | Type::Record(..) | Type::Variable(..) | Type::Erroneous(..)
)
}
fn valid_tag_ext_type(typ: &Type) -> bool {
matches!(
typ,
Type::EmptyTagUnion | Type::TagUnion(..) | Type::Variable(..) | Type::Erroneous(..)
)
}
use roc_problem::can::ExtensionTypeKind;
let (empty_ext_type, valid_extension_type): (_, fn(&Type) -> bool) = match ext_problem_kind {
ExtensionTypeKind::Record => (Type::EmptyRec, valid_record_ext_type),
ExtensionTypeKind::TagUnion => (Type::EmptyTagUnion, valid_tag_ext_type),
};
match opt_ext {
Some(loc_ann) => {
let ext_type = can_annotation_help(
env,
&loc_ann.value,
loc_ann.region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
if valid_extension_type(ext_type.shallow_dealias()) {
ext_type
} else {
// Report an error but mark the extension variable to be inferred
// so that we're as permissive as possible.
//
// THEORY: invalid extension types can appear in this position. Otherwise
// they would be caught as errors during unification.
env.problem(roc_problem::can::Problem::InvalidExtensionType {
region: loc_ann.region,
kind: ext_problem_kind,
});
let var = var_store.fresh();
introduced_variables.insert_inferred(var);
Type::Variable(var)
}
}
None => empty_ext_type,
}
}
pub fn instantiate_and_freshen_alias_type( pub fn instantiate_and_freshen_alias_type(
var_store: &mut VarStore, var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
type_variables: &[Loc<(Lowercase, Variable)>], type_variables: &[Loc<(Lowercase, Variable)>],
type_arguments: Vec<Type>, type_arguments: Vec<Type>,
lambda_set_variables: &[LambdaSet], lambda_set_variables: &[LambdaSet],
@ -657,6 +738,7 @@ pub fn instantiate_and_freshen_alias_type(
if let Type::Variable(var) = typ.0 { if let Type::Variable(var) = typ.0 {
let fresh = var_store.fresh(); let fresh = var_store.fresh();
substitutions.insert(var, Type::Variable(fresh)); substitutions.insert(var, Type::Variable(fresh));
introduced_variables.insert_lambda_set(fresh);
new_lambda_set_variables.push(LambdaSet(Type::Variable(fresh))); new_lambda_set_variables.push(LambdaSet(Type::Variable(fresh)));
} else { } else {
unreachable!("at this point there should be only vars in there"); unreachable!("at this point there should be only vars in there");
@ -681,8 +763,12 @@ pub fn freshen_opaque_def(
.map(|_| Type::Variable(var_store.fresh())) .map(|_| Type::Variable(var_store.fresh()))
.collect(); .collect();
// TODO this gets ignored; is that a problem
let mut introduced_variables = IntroducedVariables::default();
instantiate_and_freshen_alias_type( instantiate_and_freshen_alias_type(
var_store, var_store,
&mut introduced_variables,
&opaque.type_variables, &opaque.type_variables,
fresh_arguments, fresh_arguments,
&opaque.lambda_set_variables, &opaque.lambda_set_variables,

View file

@ -1,175 +1,505 @@
use crate::expected::{Expected, PExpected}; use crate::expected::{Expected, PExpected};
use roc_collections::all::{MutSet, SendMap}; use roc_collections::soa::{Index, Slice};
use roc_module::{ident::TagName, symbol::Symbol}; use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
use roc_types::types::{Category, PatternCategory, Type}; use roc_types::types::{Category, PatternCategory, Type};
use roc_types::{subs::Variable, types::VariableDetail};
/// A presence constraint is an additive constraint that defines the lower bound #[derive(Debug)]
/// of a type. For example, `Present(t1, IncludesTag(A, []))` means that the pub struct Constraints {
/// type `t1` must contain at least the tag `A`. The additive nature of these pub constraints: Vec<Constraint>,
/// constraints makes them behaviorally different from unification-based constraints. pub types: Vec<Type>,
#[derive(Debug, Clone, PartialEq)] pub variables: Vec<Variable>,
pub enum PresenceConstraint { pub loc_symbols: Vec<(Symbol, Region)>,
IncludesTag(TagName, Vec<Type>, Region, PatternCategory), pub let_constraints: Vec<LetConstraint>,
IsOpen, pub categories: Vec<Category>,
Pattern(Region, PatternCategory, PExpected<Type>), pub pattern_categories: Vec<PatternCategory>,
pub expectations: Vec<Expected<Type>>,
pub pattern_expectations: Vec<PExpected<Type>>,
pub includes_tags: Vec<IncludesTag>,
pub strings: Vec<&'static str>,
} }
impl Default for Constraints {
fn default() -> Self {
Self::new()
}
}
impl Constraints {
pub fn new() -> Self {
let constraints = Vec::new();
let mut types = Vec::new();
let variables = Vec::new();
let loc_symbols = Vec::new();
let let_constraints = Vec::new();
let mut categories = Vec::with_capacity(16);
let mut pattern_categories = Vec::with_capacity(16);
let expectations = Vec::new();
let pattern_expectations = Vec::new();
let includes_tags = Vec::new();
let strings = Vec::new();
types.extend([Type::EmptyRec, Type::EmptyTagUnion]);
categories.extend([
Category::Record,
Category::ForeignCall,
Category::OpaqueArg,
Category::Lambda,
Category::ClosureSize,
Category::StrInterpolation,
Category::If,
Category::When,
Category::Float,
Category::Int,
Category::Num,
Category::List,
Category::Str,
Category::Character,
]);
pattern_categories.extend([
PatternCategory::Record,
PatternCategory::EmptyRecord,
PatternCategory::PatternGuard,
PatternCategory::PatternDefault,
PatternCategory::Set,
PatternCategory::Map,
PatternCategory::Str,
PatternCategory::Num,
PatternCategory::Int,
PatternCategory::Float,
PatternCategory::Character,
]);
Self {
constraints,
types,
variables,
loc_symbols,
let_constraints,
categories,
pattern_categories,
expectations,
pattern_expectations,
includes_tags,
strings,
}
}
pub const EMPTY_RECORD: Index<Type> = Index::new(0);
pub const EMPTY_TAG_UNION: Index<Type> = Index::new(1);
pub const CATEGORY_RECORD: Index<Category> = Index::new(0);
pub const CATEGORY_FOREIGNCALL: Index<Category> = Index::new(1);
pub const CATEGORY_OPAQUEARG: Index<Category> = Index::new(2);
pub const CATEGORY_LAMBDA: Index<Category> = Index::new(3);
pub const CATEGORY_CLOSURESIZE: Index<Category> = Index::new(4);
pub const CATEGORY_STRINTERPOLATION: Index<Category> = Index::new(5);
pub const CATEGORY_IF: Index<Category> = Index::new(6);
pub const CATEGORY_WHEN: Index<Category> = Index::new(7);
pub const CATEGORY_FLOAT: Index<Category> = Index::new(8);
pub const CATEGORY_INT: Index<Category> = Index::new(9);
pub const CATEGORY_NUM: Index<Category> = Index::new(10);
pub const CATEGORY_LIST: Index<Category> = Index::new(11);
pub const CATEGORY_STR: Index<Category> = Index::new(12);
pub const CATEGORY_CHARACTER: Index<Category> = Index::new(13);
pub const PCATEGORY_RECORD: Index<PatternCategory> = Index::new(0);
pub const PCATEGORY_EMPTYRECORD: Index<PatternCategory> = Index::new(1);
pub const PCATEGORY_PATTERNGUARD: Index<PatternCategory> = Index::new(2);
pub const PCATEGORY_PATTERNDEFAULT: Index<PatternCategory> = Index::new(3);
pub const PCATEGORY_SET: Index<PatternCategory> = Index::new(4);
pub const PCATEGORY_MAP: Index<PatternCategory> = Index::new(5);
pub const PCATEGORY_STR: Index<PatternCategory> = Index::new(6);
pub const PCATEGORY_NUM: Index<PatternCategory> = Index::new(7);
pub const PCATEGORY_INT: Index<PatternCategory> = Index::new(8);
pub const PCATEGORY_FLOAT: Index<PatternCategory> = Index::new(9);
pub const PCATEGORY_CHARACTER: Index<PatternCategory> = Index::new(10);
#[inline(always)]
pub fn push_type(&mut self, typ: Type) -> Index<Type> {
match typ {
Type::EmptyRec => Self::EMPTY_RECORD,
Type::EmptyTagUnion => Self::EMPTY_TAG_UNION,
other => Index::push_new(&mut self.types, other),
}
}
#[inline(always)]
pub fn push_expected_type(&mut self, expected: Expected<Type>) -> Index<Expected<Type>> {
Index::push_new(&mut self.expectations, expected)
}
#[inline(always)]
pub fn push_category(&mut self, category: Category) -> Index<Category> {
match category {
Category::Record => Self::CATEGORY_RECORD,
Category::ForeignCall => Self::CATEGORY_FOREIGNCALL,
Category::OpaqueArg => Self::CATEGORY_OPAQUEARG,
Category::Lambda => Self::CATEGORY_LAMBDA,
Category::ClosureSize => Self::CATEGORY_CLOSURESIZE,
Category::StrInterpolation => Self::CATEGORY_STRINTERPOLATION,
Category::If => Self::CATEGORY_IF,
Category::When => Self::CATEGORY_WHEN,
Category::Float => Self::CATEGORY_FLOAT,
Category::Int => Self::CATEGORY_INT,
Category::Num => Self::CATEGORY_NUM,
Category::List => Self::CATEGORY_LIST,
Category::Str => Self::CATEGORY_STR,
Category::Character => Self::CATEGORY_CHARACTER,
other => Index::push_new(&mut self.categories, other),
}
}
#[inline(always)]
pub fn push_pattern_category(&mut self, category: PatternCategory) -> Index<PatternCategory> {
match category {
PatternCategory::Record => Self::PCATEGORY_RECORD,
PatternCategory::EmptyRecord => Self::PCATEGORY_EMPTYRECORD,
PatternCategory::PatternGuard => Self::PCATEGORY_PATTERNGUARD,
PatternCategory::PatternDefault => Self::PCATEGORY_PATTERNDEFAULT,
PatternCategory::Set => Self::PCATEGORY_SET,
PatternCategory::Map => Self::PCATEGORY_MAP,
PatternCategory::Str => Self::PCATEGORY_STR,
PatternCategory::Num => Self::PCATEGORY_NUM,
PatternCategory::Int => Self::PCATEGORY_INT,
PatternCategory::Float => Self::PCATEGORY_FLOAT,
PatternCategory::Character => Self::PCATEGORY_CHARACTER,
other => Index::push_new(&mut self.pattern_categories, other),
}
}
#[inline(always)]
pub fn equal_types(
&mut self,
typ: Type,
expected: Expected<Type>,
category: Category,
region: Region,
) -> Constraint {
let type_index = Index::push_new(&mut self.types, typ);
let expected_index = Index::push_new(&mut self.expectations, expected);
let category_index = Self::push_category(self, category);
Constraint::Eq(type_index, expected_index, category_index, region)
}
pub fn equal_pattern_types(
&mut self,
typ: Type,
expected: PExpected<Type>,
category: PatternCategory,
region: Region,
) -> Constraint {
let type_index = Index::push_new(&mut self.types, typ);
let expected_index = Index::push_new(&mut self.pattern_expectations, expected);
let category_index = Self::push_pattern_category(self, category);
Constraint::Pattern(type_index, expected_index, category_index, region)
}
pub fn pattern_presence(
&mut self,
typ: Type,
expected: PExpected<Type>,
category: PatternCategory,
region: Region,
) -> Constraint {
let type_index = Index::push_new(&mut self.types, typ);
let expected_index = Index::push_new(&mut self.pattern_expectations, expected);
let category_index = Index::push_new(&mut self.pattern_categories, category);
Constraint::PatternPresence(type_index, expected_index, category_index, region)
}
pub fn is_open_type(&mut self, typ: Type) -> Constraint {
let type_index = Index::push_new(&mut self.types, typ);
Constraint::IsOpenType(type_index)
}
pub fn includes_tag<I>(
&mut self,
typ: Type,
tag_name: TagName,
types: I,
category: PatternCategory,
region: Region,
) -> Constraint
where
I: IntoIterator<Item = Type>,
{
let type_index = Index::push_new(&mut self.types, typ);
let category_index = Index::push_new(&mut self.pattern_categories, category);
let types_slice = Slice::extend_new(&mut self.types, types);
let includes_tag = IncludesTag {
type_index,
tag_name,
types: types_slice,
pattern_category: category_index,
region,
};
let includes_tag_index = Index::push_new(&mut self.includes_tags, includes_tag);
Constraint::IncludesTag(includes_tag_index)
}
fn variable_slice<I>(&mut self, it: I) -> Slice<Variable>
where
I: IntoIterator<Item = Variable>,
{
let start = self.variables.len();
self.variables.extend(it);
let length = self.variables.len() - start;
Slice::new(start as _, length as _)
}
fn def_types_slice<I>(&mut self, it: I) -> DefTypes
where
I: IntoIterator<Item = (Symbol, Loc<Type>)>,
I::IntoIter: ExactSizeIterator,
{
let it = it.into_iter();
let types_start = self.types.len();
let loc_symbols_start = self.loc_symbols.len();
// because we have an ExactSizeIterator, we can reserve space here
let length = it.len();
self.types.reserve(length);
self.loc_symbols.reserve(length);
for (symbol, loc_type) in it {
let Loc { region, value } = loc_type;
self.types.push(value);
self.loc_symbols.push((symbol, region));
}
DefTypes {
types: Slice::new(types_start as _, length as _),
loc_symbols: Slice::new(loc_symbols_start as _, length as _),
}
}
#[inline(always)]
pub fn exists<I>(&mut self, flex_vars: I, defs_constraint: Constraint) -> Constraint
where
I: IntoIterator<Item = Variable>,
{
let defs_and_ret_constraint = Index::new(self.constraints.len() as _);
self.constraints.push(defs_constraint);
self.constraints.push(Constraint::True);
let let_contraint = LetConstraint {
rigid_vars: Slice::default(),
flex_vars: self.variable_slice(flex_vars),
def_types: DefTypes::default(),
defs_and_ret_constraint,
};
let let_index = Index::new(self.let_constraints.len() as _);
self.let_constraints.push(let_contraint);
Constraint::Let(let_index)
}
#[inline(always)]
pub fn exists_many<I, C>(&mut self, flex_vars: I, defs_constraint: C) -> Constraint
where
I: IntoIterator<Item = Variable>,
C: IntoIterator<Item = Constraint>,
C::IntoIter: ExactSizeIterator,
{
let defs_constraint = self.and_constraint(defs_constraint);
let defs_and_ret_constraint = Index::new(self.constraints.len() as _);
self.constraints.push(defs_constraint);
self.constraints.push(Constraint::True);
let let_contraint = LetConstraint {
rigid_vars: Slice::default(),
flex_vars: self.variable_slice(flex_vars),
def_types: DefTypes::default(),
defs_and_ret_constraint,
};
let let_index = Index::new(self.let_constraints.len() as _);
self.let_constraints.push(let_contraint);
Constraint::Let(let_index)
}
#[inline(always)]
pub fn let_constraint<I1, I2, I3>(
&mut self,
rigid_vars: I1,
flex_vars: I2,
def_types: I3,
defs_constraint: Constraint,
ret_constraint: Constraint,
) -> Constraint
where
I1: IntoIterator<Item = Variable>,
I2: IntoIterator<Item = Variable>,
I3: IntoIterator<Item = (Symbol, Loc<Type>)>,
I3::IntoIter: ExactSizeIterator,
{
let defs_and_ret_constraint = Index::new(self.constraints.len() as _);
self.constraints.push(defs_constraint);
self.constraints.push(ret_constraint);
let let_contraint = LetConstraint {
rigid_vars: self.variable_slice(rigid_vars),
flex_vars: self.variable_slice(flex_vars),
def_types: self.def_types_slice(def_types),
defs_and_ret_constraint,
};
let let_index = Index::new(self.let_constraints.len() as _);
self.let_constraints.push(let_contraint);
Constraint::Let(let_index)
}
#[inline(always)]
pub fn and_constraint<I>(&mut self, constraints: I) -> Constraint
where
I: IntoIterator<Item = Constraint>,
I::IntoIter: ExactSizeIterator,
{
let mut it = constraints.into_iter();
match it.len() {
0 => Constraint::True,
1 => it.next().unwrap(),
_ => {
let start = self.constraints.len() as u32;
self.constraints.extend(it);
let end = self.constraints.len() as u32;
let slice = Slice::new(start, (end - start) as u16);
Constraint::And(slice)
}
}
}
pub fn lookup(
&mut self,
symbol: Symbol,
expected: Expected<Type>,
region: Region,
) -> Constraint {
Constraint::Lookup(
symbol,
Index::push_new(&mut self.expectations, expected),
region,
)
}
pub fn contains_save_the_environment(&self, constraint: &Constraint) -> bool {
match constraint {
Constraint::Eq(..) => false,
Constraint::Store(..) => false,
Constraint::Lookup(..) => false,
Constraint::Pattern(..) => false,
Constraint::True => false,
Constraint::SaveTheEnvironment => true,
Constraint::Let(index) => {
let let_constraint = &self.let_constraints[index.index()];
let offset = let_constraint.defs_and_ret_constraint.index();
let defs_constraint = &self.constraints[offset];
let ret_constraint = &self.constraints[offset + 1];
self.contains_save_the_environment(defs_constraint)
|| self.contains_save_the_environment(ret_constraint)
}
Constraint::And(slice) => {
let constraints = &self.constraints[slice.indices()];
constraints
.iter()
.any(|c| self.contains_save_the_environment(c))
}
Constraint::IsOpenType(_) => false,
Constraint::IncludesTag(_) => false,
Constraint::PatternPresence(_, _, _, _) => false,
}
}
pub fn store(
&mut self,
typ: Type,
variable: Variable,
filename: &'static str,
line_number: u32,
) -> Constraint {
let type_index = Index::push_new(&mut self.types, typ);
let string_index = Index::push_new(&mut self.strings, filename);
Constraint::Store(type_index, variable, string_index, line_number)
}
}
static_assertions::assert_eq_size!([u8; 3 * 8], Constraint);
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Constraint { pub enum Constraint {
Eq(Type, Expected<Type>, Category, Region), Eq(Index<Type>, Index<Expected<Type>>, Index<Category>, Region),
Store(Type, Variable, &'static str, u32), Store(Index<Type>, Variable, Index<&'static str>, u32),
Lookup(Symbol, Expected<Type>, Region), Lookup(Symbol, Index<Expected<Type>>, Region),
Pattern(Region, PatternCategory, Type, PExpected<Type>), Pattern(
Index<Type>,
Index<PExpected<Type>>,
Index<PatternCategory>,
Region,
),
True, // Used for things that always unify, e.g. blanks and runtime errors True, // Used for things that always unify, e.g. blanks and runtime errors
SaveTheEnvironment, SaveTheEnvironment,
Let(Box<LetConstraint>), Let(Index<LetConstraint>),
And(Vec<Constraint>), And(Slice<Constraint>),
Present(Type, PresenceConstraint), /// Presence constraints
IsOpenType(Index<Type>), // Theory; always applied to a variable? if yes the use that
IncludesTag(Index<IncludesTag>),
PatternPresence(
Index<Type>,
Index<PExpected<Type>>,
Index<PatternCategory>,
Region,
),
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct DefTypes {
pub types: Slice<Type>,
pub loc_symbols: Slice<(Symbol, Region)>,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct LetConstraint { pub struct LetConstraint {
pub rigid_vars: Vec<Variable>, pub rigid_vars: Slice<Variable>,
pub flex_vars: Vec<Variable>, pub flex_vars: Slice<Variable>,
pub def_types: SendMap<Symbol, Loc<Type>>, pub def_types: DefTypes,
pub defs_constraint: Constraint, pub defs_and_ret_constraint: Index<(Constraint, Constraint)>,
pub ret_constraint: Constraint,
} }
// VALIDATE #[derive(Debug, Clone, PartialEq)]
pub struct IncludesTag {
#[derive(Default, Clone)] pub type_index: Index<Type>,
struct Declared { pub tag_name: TagName,
pub rigid_vars: MutSet<Variable>, pub types: Slice<Type>,
pub flex_vars: MutSet<Variable>, pub pattern_category: Index<PatternCategory>,
} pub region: Region,
impl Constraint {
pub fn validate(&self) -> bool {
let mut unbound = Default::default();
validate_help(self, &Declared::default(), &mut unbound);
if !unbound.type_variables.is_empty() {
panic!("found unbound type variables {:?}", &unbound.type_variables);
}
if !unbound.lambda_set_variables.is_empty() {
panic!(
"found unbound lambda set variables {:?}",
&unbound.lambda_set_variables
);
}
if !unbound.recursion_variables.is_empty() {
panic!(
"found unbound recursion variables {:?}",
&unbound.recursion_variables
);
}
true
}
pub fn contains_save_the_environment(&self) -> bool {
match self {
Constraint::Eq(_, _, _, _) => false,
Constraint::Store(_, _, _, _) => false,
Constraint::Lookup(_, _, _) => false,
Constraint::Pattern(_, _, _, _) => false,
Constraint::True => false,
Constraint::SaveTheEnvironment => true,
Constraint::Let(boxed) => {
boxed.ret_constraint.contains_save_the_environment()
|| boxed.defs_constraint.contains_save_the_environment()
}
Constraint::And(cs) => cs.iter().any(|c| c.contains_save_the_environment()),
Constraint::Present(_, _) => false,
}
}
}
fn subtract(declared: &Declared, detail: &VariableDetail, accum: &mut VariableDetail) {
for var in &detail.type_variables {
if !(declared.rigid_vars.contains(var) || declared.flex_vars.contains(var)) {
accum.type_variables.insert(*var);
}
}
// lambda set variables are always flex
for var in &detail.lambda_set_variables {
if declared.rigid_vars.contains(var) {
panic!("lambda set variable {:?} is declared as rigid", var);
}
if !declared.flex_vars.contains(var) {
accum.lambda_set_variables.push(*var);
}
}
// recursion vars should be always rigid
for var in &detail.recursion_variables {
if declared.flex_vars.contains(var) {
panic!("recursion variable {:?} is declared as flex", var);
}
if !declared.rigid_vars.contains(var) {
accum.recursion_variables.insert(*var);
}
}
}
fn validate_help(constraint: &Constraint, declared: &Declared, accum: &mut VariableDetail) {
use Constraint::*;
match constraint {
True | SaveTheEnvironment | Lookup(_, _, _) => { /* nothing */ }
Store(typ, var, _, _) => {
subtract(declared, &typ.variables_detail(), accum);
if !declared.flex_vars.contains(var) {
accum.type_variables.insert(*var);
}
}
Constraint::Eq(typ, expected, _, _) => {
subtract(declared, &typ.variables_detail(), accum);
subtract(declared, &expected.get_type_ref().variables_detail(), accum);
}
Constraint::Pattern(_, _, typ, expected) => {
subtract(declared, &typ.variables_detail(), accum);
subtract(declared, &expected.get_type_ref().variables_detail(), accum);
}
Constraint::Let(letcon) => {
let mut declared = declared.clone();
declared
.rigid_vars
.extend(letcon.rigid_vars.iter().copied());
declared.flex_vars.extend(letcon.flex_vars.iter().copied());
validate_help(&letcon.defs_constraint, &declared, accum);
validate_help(&letcon.ret_constraint, &declared, accum);
}
Constraint::And(inner) => {
for c in inner {
validate_help(c, declared, accum);
}
}
Constraint::Present(typ, constr) => {
subtract(declared, &typ.variables_detail(), accum);
match constr {
PresenceConstraint::IncludesTag(_, tys, _, _) => {
for ty in tys {
subtract(declared, &ty.variables_detail(), accum);
}
}
PresenceConstraint::IsOpen => {}
PresenceConstraint::Pattern(_, _, expected) => {
subtract(declared, &typ.variables_detail(), accum);
subtract(declared, &expected.get_type_ref().variables_detail(), accum);
}
}
}
}
} }

View file

@ -864,6 +864,32 @@ fn pattern_to_vars_by_symbol(
} }
} }
fn single_can_def(
loc_can_pattern: Loc<Pattern>,
loc_can_expr: Loc<Expr>,
expr_var: Variable,
opt_loc_annotation: Option<Loc<crate::annotation::Annotation>>,
pattern_vars: SendMap<Symbol, Variable>,
) -> Def {
let def_annotation = opt_loc_annotation.map(|loc_annotation| Annotation {
signature: loc_annotation.value.typ,
introduced_variables: loc_annotation.value.introduced_variables,
aliases: loc_annotation.value.aliases,
region: loc_annotation.region,
});
Def {
expr_var,
loc_pattern: loc_can_pattern,
loc_expr: Loc {
region: loc_can_expr.region,
value: loc_can_expr.value,
},
pattern_vars,
annotation: def_annotation,
}
}
// TODO trim down these arguments! // TODO trim down these arguments!
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
@ -887,25 +913,25 @@ fn canonicalize_pending_def<'a>(
AnnotationOnly(_, loc_can_pattern, loc_ann) => { AnnotationOnly(_, loc_can_pattern, loc_ann) => {
// annotation sans body cannot introduce new rigids that are visible in other annotations // annotation sans body cannot introduce new rigids that are visible in other annotations
// but the rigids can show up in type error messages, so still register them // but the rigids can show up in type error messages, so still register them
let ann = let type_annotation =
canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store);
// Record all the annotation's references in output.references.lookups // Record all the annotation's references in output.references.lookups
for symbol in ann.references { for symbol in type_annotation.references.iter() {
output.references.lookups.insert(symbol); output.references.lookups.insert(*symbol);
output.references.referenced_type_defs.insert(symbol); output.references.referenced_type_defs.insert(*symbol);
} }
aliases.extend(ann.aliases.clone()); aliases.extend(type_annotation.aliases.clone());
output.introduced_variables.union(&ann.introduced_variables); output
.introduced_variables
.union(&type_annotation.introduced_variables);
pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var); pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var);
let typ = ann.typ; let arity = type_annotation.typ.arity();
let arity = typ.arity();
let problem = match &loc_can_pattern.value { let problem = match &loc_can_pattern.value {
Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed { Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed {
@ -963,33 +989,44 @@ fn canonicalize_pending_def<'a>(
} }
}; };
for (_, (symbol, _)) in scope.idents() { if let Pattern::Identifier(symbol) = loc_can_pattern.value {
if !vars_by_symbol.contains_key(symbol) { let def = single_can_def(
continue; loc_can_pattern,
} loc_can_expr,
expr_var,
// We could potentially avoid some clones here by using Rc strategically, Some(Loc::at(loc_ann.region, type_annotation)),
// but the total amount of cloning going on here should typically be minimal. vars_by_symbol.clone(),
can_defs_by_symbol.insert(
*symbol,
Def {
expr_var,
// TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(),
loc_expr: Loc {
region: loc_can_expr.region,
// TODO try to remove this .clone()!
value: loc_can_expr.value.clone(),
},
pattern_vars: vars_by_symbol.clone(),
annotation: Some(Annotation {
signature: typ.clone(),
introduced_variables: output.introduced_variables.clone(),
aliases: ann.aliases.clone(),
region: loc_ann.region,
}),
},
); );
can_defs_by_symbol.insert(symbol, def);
} else {
for (_, (symbol, _)) in scope.idents() {
if !vars_by_symbol.contains_key(symbol) {
continue;
}
// We could potentially avoid some clones here by using Rc strategically,
// but the total amount of cloning going on here should typically be minimal.
can_defs_by_symbol.insert(
*symbol,
Def {
expr_var,
// TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(),
loc_expr: Loc {
region: loc_can_expr.region,
// TODO try to remove this .clone()!
value: loc_can_expr.value.clone(),
},
pattern_vars: vars_by_symbol.clone(),
annotation: Some(Annotation {
signature: type_annotation.typ.clone(),
introduced_variables: output.introduced_variables.clone(),
aliases: type_annotation.aliases.clone(),
region: loc_ann.region,
}),
},
);
}
} }
} }
@ -998,23 +1035,23 @@ fn canonicalize_pending_def<'a>(
InvalidAlias { .. } => { InvalidAlias { .. } => {
// invalid aliases and opaques (shadowed, incorrect patterns) get ignored // invalid aliases and opaques (shadowed, incorrect patterns) get ignored
} }
TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { TypedBody(_loc_pattern, loc_can_pattern, loc_ann, loc_expr) => {
let ann = let type_annotation =
canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store);
// Record all the annotation's references in output.references.lookups // Record all the annotation's references in output.references.lookups
for symbol in ann.references { for symbol in type_annotation.references.iter() {
output.references.lookups.insert(symbol); output.references.lookups.insert(*symbol);
output.references.referenced_type_defs.insert(symbol); output.references.referenced_type_defs.insert(*symbol);
} }
let typ = ann.typ; for (symbol, alias) in type_annotation.aliases.clone() {
for (symbol, alias) in ann.aliases.clone() {
aliases.insert(symbol, alias); aliases.insert(symbol, alias);
} }
output.introduced_variables.union(&ann.introduced_variables); output
.introduced_variables
.union(&type_annotation.introduced_variables);
// bookkeeping for tail-call detection. If we're assigning to an // bookkeeping for tail-call detection. If we're assigning to an
// identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called.
@ -1041,118 +1078,115 @@ fn canonicalize_pending_def<'a>(
// reset the tailcallable_symbol // reset the tailcallable_symbol
env.tailcallable_symbol = outer_identifier; env.tailcallable_symbol = outer_identifier;
// see below: a closure needs a fresh References!
let mut is_closure = false;
// First, make sure we are actually assigning an identifier instead of (for example) a tag. // First, make sure we are actually assigning an identifier instead of (for example) a tag.
// //
// If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration,
// which also implies it's not a self tail call! // which also implies it's not a self tail call!
// //
// Only defs of the form (foo = ...) can be closure declarations or self tail calls. // Only defs of the form (foo = ...) can be closure declarations or self tail calls.
if let ( if let Pattern::Identifier(symbol) = loc_can_pattern.value {
&ast::Pattern::Identifier(_name), if let Closure(ClosureData {
&Pattern::Identifier(ref defined_symbol),
&Closure(ClosureData {
function_type, function_type,
closure_type, closure_type,
closure_ext_var, closure_ext_var,
return_type, return_type,
name: ref symbol, name: ref closure_name,
ref arguments, ref arguments,
loc_body: ref body, loc_body: ref body,
ref captured_symbols, ref captured_symbols,
.. ..
}), }) = loc_can_expr.value
) = ( {
&loc_pattern.value, // Since everywhere in the code it'll be referred to by its defined name,
&loc_can_pattern.value, // remove its generated name from the closure map. (We'll re-insert it later.)
&loc_can_expr.value, let references = env.closures.remove(closure_name).unwrap_or_else(|| {
) { panic!(
is_closure = true; "Tried to remove symbol {:?} from procedures, but it was not found: {:?}",
closure_name, env.closures
// Since everywhere in the code it'll be referred to by its defined name, )
// remove its generated name from the closure map. (We'll re-insert it later.)
let references = env.closures.remove(symbol).unwrap_or_else(|| {
panic!(
"Tried to remove symbol {:?} from procedures, but it was not found: {:?}",
symbol, env.closures
)
});
// Re-insert the closure into the map, under its defined name.
// closures don't have a name, and therefore pick a fresh symbol. But in this
// case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...`
// and we want to reference it by that name.
env.closures.insert(*defined_symbol, references);
// The closure is self tail recursive iff it tail calls itself (by defined name).
let is_recursive = match can_output.tail_call {
Some(ref symbol) if symbol == defined_symbol => Recursive::TailRecursive,
_ => Recursive::NotRecursive,
};
// Recursion doesn't count as referencing. (If it did, all recursive functions
// would result in circular def errors!)
refs_by_symbol
.entry(*defined_symbol)
.and_modify(|(_, refs)| {
refs.lookups = refs.lookups.without(defined_symbol);
}); });
// renamed_closure_def = Some(&defined_symbol); // Re-insert the closure into the map, under its defined name.
loc_can_expr.value = Closure(ClosureData { // closures don't have a name, and therefore pick a fresh symbol. But in this
function_type, // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...`
closure_type, // and we want to reference it by that name.
closure_ext_var, env.closures.insert(symbol, references);
return_type,
name: *defined_symbol,
captured_symbols: captured_symbols.clone(),
recursive: is_recursive,
arguments: arguments.clone(),
loc_body: body.clone(),
});
}
// Store the referenced locals in the refs_by_symbol map, so we can later figure out // The closure is self tail recursive iff it tail calls itself (by defined name).
// which defined names reference each other. let is_recursive = match can_output.tail_call {
for (_, (symbol, region)) in scope.idents() { Some(tail_symbol) if tail_symbol == symbol => Recursive::TailRecursive,
if !vars_by_symbol.contains_key(symbol) { _ => Recursive::NotRecursive,
continue; };
}
// Recursion doesn't count as referencing. (If it did, all recursive functions
// would result in circular def errors!)
refs_by_symbol.entry(symbol).and_modify(|(_, refs)| {
refs.lookups = refs.lookups.without(&symbol);
});
// renamed_closure_def = Some(&symbol);
loc_can_expr.value = Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
return_type,
name: symbol,
captured_symbols: captured_symbols.clone(),
recursive: is_recursive,
arguments: arguments.clone(),
loc_body: body.clone(),
});
let refs =
// Functions' references don't count in defs. // Functions' references don't count in defs.
// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its
// parent commit for the bug this fixed! // parent commit for the bug this fixed!
if is_closure { let refs = References::new();
References::new()
} else {
can_output.references.clone()
};
refs_by_symbol.insert(*symbol, (*region, refs)); refs_by_symbol.insert(symbol, (loc_can_pattern.region, refs));
} else {
let refs = can_output.references;
refs_by_symbol.insert(symbol, (loc_ann.region, refs));
}
can_defs_by_symbol.insert( let def = single_can_def(
*symbol, loc_can_pattern,
Def { loc_can_expr,
expr_var, expr_var,
// TODO try to remove this .clone()! Some(Loc::at(loc_ann.region, type_annotation)),
loc_pattern: loc_can_pattern.clone(), vars_by_symbol.clone(),
loc_expr: Loc {
region: loc_can_expr.region,
// TODO try to remove this .clone()!
value: loc_can_expr.value.clone(),
},
pattern_vars: vars_by_symbol.clone(),
annotation: Some(Annotation {
signature: typ.clone(),
introduced_variables: output.introduced_variables.clone(),
aliases: ann.aliases.clone(),
region: loc_ann.region,
}),
},
); );
can_defs_by_symbol.insert(symbol, def);
} else {
for (_, (symbol, region)) in scope.idents() {
if !vars_by_symbol.contains_key(symbol) {
continue;
}
let refs = can_output.references.clone();
refs_by_symbol.insert(*symbol, (*region, refs));
can_defs_by_symbol.insert(
*symbol,
Def {
expr_var,
// TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(),
loc_expr: Loc {
region: loc_can_expr.region,
// TODO try to remove this .clone()!
value: loc_can_expr.value.clone(),
},
pattern_vars: vars_by_symbol.clone(),
annotation: Some(Annotation {
signature: type_annotation.typ.clone(),
introduced_variables: type_annotation.introduced_variables.clone(),
aliases: type_annotation.aliases.clone(),
region: loc_ann.region,
}),
},
);
}
} }
} }
// If we have a pattern, then the def has a body (that is, it's not a // If we have a pattern, then the def has a body (that is, it's not a
@ -1184,108 +1218,105 @@ fn canonicalize_pending_def<'a>(
// reset the tailcallable_symbol // reset the tailcallable_symbol
env.tailcallable_symbol = outer_identifier; env.tailcallable_symbol = outer_identifier;
// see below: a closure needs a fresh References!
let mut is_closure = false;
// First, make sure we are actually assigning an identifier instead of (for example) a tag. // First, make sure we are actually assigning an identifier instead of (for example) a tag.
// //
// If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration,
// which also implies it's not a self tail call! // which also implies it's not a self tail call!
// //
// Only defs of the form (foo = ...) can be closure declarations or self tail calls. // Only defs of the form (foo = ...) can be closure declarations or self tail calls.
if let ( if let Pattern::Identifier(symbol) = loc_can_pattern.value {
&ast::Pattern::Identifier(_name), if let Closure(ClosureData {
&Pattern::Identifier(ref defined_symbol),
&Closure(ClosureData {
function_type, function_type,
closure_type, closure_type,
closure_ext_var, closure_ext_var,
return_type, return_type,
name: ref symbol, name: ref closure_name,
ref arguments, ref arguments,
loc_body: ref body, loc_body: ref body,
ref captured_symbols, ref captured_symbols,
.. ..
}), }) = loc_can_expr.value
) = ( {
&loc_pattern.value, // Since everywhere in the code it'll be referred to by its defined name,
&loc_can_pattern.value, // remove its generated name from the closure map. (We'll re-insert it later.)
&loc_can_expr.value, let references = env.closures.remove(closure_name).unwrap_or_else(|| {
) { panic!(
is_closure = true; "Tried to remove symbol {:?} from procedures, but it was not found: {:?}",
closure_name, env.closures
// Since everywhere in the code it'll be referred to by its defined name, )
// remove its generated name from the closure map. (We'll re-insert it later.)
let references = env.closures.remove(symbol).unwrap_or_else(|| {
panic!(
"Tried to remove symbol {:?} from procedures, but it was not found: {:?}",
symbol, env.closures
)
});
// Re-insert the closure into the map, under its defined name.
// closures don't have a name, and therefore pick a fresh symbol. But in this
// case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...`
// and we want to reference it by that name.
env.closures.insert(*defined_symbol, references);
// The closure is self tail recursive iff it tail calls itself (by defined name).
let is_recursive = match can_output.tail_call {
Some(ref symbol) if symbol == defined_symbol => Recursive::TailRecursive,
_ => Recursive::NotRecursive,
};
// Recursion doesn't count as referencing. (If it did, all recursive functions
// would result in circular def errors!)
refs_by_symbol
.entry(*defined_symbol)
.and_modify(|(_, refs)| {
refs.lookups = refs.lookups.without(defined_symbol);
}); });
loc_can_expr.value = Closure(ClosureData { // Re-insert the closure into the map, under its defined name.
function_type, // closures don't have a name, and therefore pick a fresh symbol. But in this
closure_type, // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...`
closure_ext_var, // and we want to reference it by that name.
return_type, env.closures.insert(symbol, references);
name: *defined_symbol,
captured_symbols: captured_symbols.clone(), // The closure is self tail recursive iff it tail calls itself (by defined name).
recursive: is_recursive, let is_recursive = match can_output.tail_call {
arguments: arguments.clone(), Some(tail_symbol) if tail_symbol == symbol => Recursive::TailRecursive,
loc_body: body.clone(), _ => Recursive::NotRecursive,
}); };
}
// Recursion doesn't count as referencing. (If it did, all recursive functions
// would result in circular def errors!)
refs_by_symbol.entry(symbol).and_modify(|(_, refs)| {
refs.lookups = refs.lookups.without(&symbol);
});
loc_can_expr.value = Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
return_type,
name: symbol,
captured_symbols: captured_symbols.clone(),
recursive: is_recursive,
arguments: arguments.clone(),
loc_body: body.clone(),
});
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
// which defined names reference each other.
for (symbol, region) in bindings_from_patterns(std::iter::once(&loc_can_pattern)) {
let refs =
// Functions' references don't count in defs. // Functions' references don't count in defs.
// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its
// parent commit for the bug this fixed! // parent commit for the bug this fixed!
if is_closure { let refs = References::new();
References::new() refs_by_symbol.insert(symbol, (loc_pattern.region, refs));
} else { } else {
can_output.references.clone() let refs = can_output.references.clone();
}; refs_by_symbol.insert(symbol, (loc_pattern.region, refs));
}
refs_by_symbol.insert(symbol, (region, refs)); let def = single_can_def(
loc_can_pattern,
can_defs_by_symbol.insert( loc_can_expr,
symbol, expr_var,
Def { None,
expr_var, vars_by_symbol.clone(),
// TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(),
loc_expr: Loc {
// TODO try to remove this .clone()!
region: loc_can_expr.region,
value: loc_can_expr.value.clone(),
},
pattern_vars: vars_by_symbol.clone(),
annotation: None,
},
); );
can_defs_by_symbol.insert(symbol, def);
} else {
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
// which defined names reference each other.
for (symbol, region) in bindings_from_patterns(std::iter::once(&loc_can_pattern)) {
let refs = can_output.references.clone();
refs_by_symbol.insert(symbol, (region, refs));
can_defs_by_symbol.insert(
symbol,
Def {
expr_var,
// TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(),
loc_expr: Loc {
// TODO try to remove this .clone()!
region: loc_can_expr.region,
value: loc_can_expr.value.clone(),
},
pattern_vars: vars_by_symbol.clone(),
annotation: None,
},
);
}
} }
output.union(can_output); output.union(can_output);

View file

@ -138,17 +138,7 @@ pub enum Expr {
field: Lowercase, field: Lowercase,
}, },
/// field accessor as a function, e.g. (.foo) expr /// field accessor as a function, e.g. (.foo) expr
Accessor { Accessor(AccessorData),
/// accessors are desugared to closures; they need to have a name
/// so the closure can have a correct lambda set
name: Symbol,
function_var: Variable,
record_var: Variable,
closure_ext_var: Variable,
ext_var: Variable,
field_var: Variable,
field: Lowercase,
},
Update { Update {
record_var: Variable, record_var: Variable,
@ -217,6 +207,70 @@ pub struct ClosureData {
pub loc_body: Box<Loc<Expr>>, pub loc_body: Box<Loc<Expr>>,
} }
/// A record accessor like `.foo`, which is equivalent to `\r -> r.foo`
/// Accessors are desugared to closures; they need to have a name
/// so the closure can have a correct lambda set.
///
/// We distinguish them from closures so we can have better error messages
/// during constraint generation.
#[derive(Clone, Debug, PartialEq)]
pub struct AccessorData {
pub name: Symbol,
pub function_var: Variable,
pub record_var: Variable,
pub closure_var: Variable,
pub closure_ext_var: Variable,
pub ext_var: Variable,
pub field_var: Variable,
pub field: Lowercase,
}
impl AccessorData {
pub fn to_closure_data(self, record_symbol: Symbol) -> ClosureData {
let AccessorData {
name,
function_var,
record_var,
closure_var,
closure_ext_var,
ext_var,
field_var,
field,
} = self;
// IDEA: convert accessor from
//
// .foo
//
// into
//
// (\r -> r.foo)
let body = Expr::Access {
record_var,
ext_var,
field_var,
loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol))),
field,
};
let loc_body = Loc::at_zero(body);
let arguments = vec![(record_var, Loc::at_zero(Pattern::Identifier(record_symbol)))];
ClosureData {
function_type: function_var,
closure_type: closure_var,
closure_ext_var,
return_type: field_var,
name,
captured_symbols: vec![],
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(loc_body),
}
}
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Field { pub struct Field {
pub var: Variable, pub var: Variable,
@ -735,15 +789,16 @@ pub fn canonicalize_expr<'a>(
) )
} }
ast::Expr::AccessorFunction(field) => ( ast::Expr::AccessorFunction(field) => (
Accessor { Accessor(AccessorData {
name: env.gen_unique_symbol(), name: env.gen_unique_symbol(),
function_var: var_store.fresh(), function_var: var_store.fresh(),
record_var: var_store.fresh(), record_var: var_store.fresh(),
ext_var: var_store.fresh(), ext_var: var_store.fresh(),
closure_var: var_store.fresh(),
closure_ext_var: var_store.fresh(), closure_ext_var: var_store.fresh(),
field_var: var_store.fresh(), field_var: var_store.fresh(),
field: (*field).into(), field: (*field).into(),
}, }),
Output::default(), Output::default(),
), ),
ast::Expr::GlobalTag(tag) => { ast::Expr::GlobalTag(tag) => {

View file

@ -161,13 +161,13 @@ where
} }
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Index(usize); pub struct HumanIndex(usize);
impl Index { impl HumanIndex {
pub const FIRST: Self = Index(0); pub const FIRST: Self = HumanIndex(0);
pub fn zero_based(i: usize) -> Self { pub fn zero_based(i: usize) -> Self {
Index(i) HumanIndex(i)
} }
pub fn to_zero_based(self) -> usize { pub fn to_zero_based(self) -> usize {
@ -175,7 +175,7 @@ impl Index {
} }
pub fn one_based(i: usize) -> Self { pub fn one_based(i: usize) -> Self {
Index(i - 1) HumanIndex(i - 1)
} }
pub fn ordinal(self) -> std::string::String { pub fn ordinal(self) -> std::string::String {

View file

@ -3,3 +3,4 @@
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
pub mod all; pub mod all;
pub mod soa;

View file

@ -0,0 +1,119 @@
use std::usize;
#[derive(PartialEq, Eq)]
pub struct Index<T> {
index: u32,
_marker: std::marker::PhantomData<T>,
}
impl<T> Clone for Index<T> {
fn clone(&self) -> Self {
Self {
index: self.index,
_marker: self._marker,
}
}
}
impl<T> Copy for Index<T> {}
impl<T> std::fmt::Debug for Index<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Index({})", self.index)
}
}
impl<T> Index<T> {
pub const fn new(index: u32) -> Self {
Self {
index,
_marker: std::marker::PhantomData,
}
}
pub const fn index(&self) -> usize {
self.index as usize
}
pub fn push_new(vector: &mut Vec<T>, value: T) -> Index<T> {
let index = Self::new(vector.len() as _);
vector.push(value);
index
}
}
#[derive(PartialEq, Eq)]
pub struct Slice<T> {
start: u32,
length: u16,
_marker: std::marker::PhantomData<T>,
}
impl<T> Clone for Slice<T> {
fn clone(&self) -> Self {
Self {
start: self.start,
length: self.length,
_marker: self._marker,
}
}
}
impl<T> Copy for Slice<T> {}
impl<T> std::fmt::Debug for Slice<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Slice(start = {}, length = {})", self.start, self.length)
}
}
impl<T> Default for Slice<T> {
fn default() -> Self {
Self {
start: Default::default(),
length: Default::default(),
_marker: Default::default(),
}
}
}
impl<T> Slice<T> {
pub const fn new(start: u32, length: u16) -> Self {
Self {
start,
length,
_marker: std::marker::PhantomData,
}
}
pub fn extend_new<I>(vector: &mut Vec<T>, values: I) -> Slice<T>
where
I: IntoIterator<Item = T>,
{
let start = vector.len() as u32;
vector.extend(values);
let end = vector.len() as u32;
Self::new(start, (end - start) as u16)
}
pub const fn len(&self) -> usize {
self.length as _
}
pub const fn is_empty(&self) -> bool {
self.length == 0
}
pub const fn indices(&self) -> std::ops::Range<usize> {
self.start as usize..(self.start as usize + self.length as usize)
}
pub fn into_iter(&self) -> impl Iterator<Item = Index<T>> {
self.indices().map(|i| Index::new(i as _))
}
}

View file

@ -14,3 +14,4 @@ roc_parse = { path = "../parse" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
arrayvec = "0.7.2"

View file

@ -1,8 +1,7 @@
use roc_can::constraint::Constraint::{self, *}; use arrayvec::ArrayVec;
use roc_can::constraint::LetConstraint; use roc_can::constraint::{Constraint, Constraints};
use roc_can::expected::Expected::{self, *}; use roc_can::expected::Expected::{self, *};
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand}; use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand};
use roc_collections::all::SendMap;
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::Region; use roc_region::all::Region;
@ -12,8 +11,10 @@ use roc_types::types::Type::{self, *};
use roc_types::types::{AliasKind, Category}; use roc_types::types::{AliasKind, Category};
#[must_use] #[must_use]
#[inline(always)]
pub fn add_numeric_bound_constr( pub fn add_numeric_bound_constr(
constrs: &mut Vec<Constraint>, constraints: &mut Constraints,
num_constraints: &mut impl Extend<Constraint>,
num_type: Type, num_type: Type,
bound: impl TypedNumericBound, bound: impl TypedNumericBound,
region: Region, region: Region,
@ -27,12 +28,12 @@ pub fn add_numeric_bound_constr(
0 => total_num_type, 0 => total_num_type,
1 => { 1 => {
let actual_type = Variable(range[0]); let actual_type = Variable(range[0]);
constrs.push(Eq( let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region);
total_num_type.clone(), let because_suffix =
Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region), constraints.equal_types(total_num_type.clone(), expected, category, region);
category,
region, num_constraints.extend([because_suffix]);
));
total_num_type total_num_type
} }
_ => RangedNumber(Box::new(total_num_type), range), _ => RangedNumber(Box::new(total_num_type), range),
@ -41,6 +42,7 @@ pub fn add_numeric_bound_constr(
#[inline(always)] #[inline(always)]
pub fn int_literal( pub fn int_literal(
constraints: &mut Constraints,
num_var: Variable, num_var: Variable,
precision_var: Variable, precision_var: Variable,
expected: Expected<Type>, expected: Expected<Type>,
@ -49,31 +51,35 @@ pub fn int_literal(
) -> Constraint { ) -> Constraint {
let reason = Reason::IntLiteral; let reason = Reason::IntLiteral;
let mut constrs = Vec::with_capacity(3); // Always add the bound first; this improves the resolved type quality in case it's an alias like "U8".
// Always add the bound first; this improves the resolved type quality in case it's an alias let mut constrs = ArrayVec::<_, 3>::new();
// like "U8".
let num_type = add_numeric_bound_constr( let num_type = add_numeric_bound_constr(
constraints,
&mut constrs, &mut constrs,
Variable(num_var), Variable(num_var),
bound, bound,
region, region,
Category::Num, Category::Num,
); );
constrs.extend(vec![
Eq( constrs.extend([
constraints.equal_types(
num_type.clone(), num_type.clone(),
ForReason(reason, num_int(Type::Variable(precision_var)), region), ForReason(reason, num_int(Type::Variable(precision_var)), region),
Category::Int, Category::Int,
region, region,
), ),
Eq(num_type, expected, Category::Int, region), constraints.equal_types(num_type, expected, Category::Int, region),
]); ]);
exists(vec![num_var], And(constrs)) // TODO the precision_var is not part of the exists here; for float it is. Which is correct?
let and_constraint = constraints.and_constraint(constrs);
constraints.exists([num_var], and_constraint)
} }
#[inline(always)] #[inline(always)]
pub fn float_literal( pub fn float_literal(
constraints: &mut Constraints,
num_var: Variable, num_var: Variable,
precision_var: Variable, precision_var: Variable,
expected: Expected<Type>, expected: Expected<Type>,
@ -82,29 +88,33 @@ pub fn float_literal(
) -> Constraint { ) -> Constraint {
let reason = Reason::FloatLiteral; let reason = Reason::FloatLiteral;
let mut constrs = Vec::with_capacity(3); let mut constrs = ArrayVec::<_, 3>::new();
let num_type = add_numeric_bound_constr( let num_type = add_numeric_bound_constr(
constraints,
&mut constrs, &mut constrs,
Variable(num_var), Variable(num_var),
bound, bound,
region, region,
Category::Float, Category::Float,
); );
constrs.extend(vec![
Eq( constrs.extend([
constraints.equal_types(
num_type.clone(), num_type.clone(),
ForReason(reason, num_float(Type::Variable(precision_var)), region), ForReason(reason, num_float(Type::Variable(precision_var)), region),
Category::Float, Category::Float,
region, region,
), ),
Eq(num_type, expected, Category::Float, region), constraints.equal_types(num_type, expected, Category::Float, region),
]); ]);
exists(vec![num_var, precision_var], And(constrs)) let and_constraint = constraints.and_constraint(constrs);
constraints.exists([num_var, precision_var], and_constraint)
} }
#[inline(always)] #[inline(always)]
pub fn num_literal( pub fn num_literal(
constraints: &mut Constraints,
num_var: Variable, num_var: Variable,
expected: Expected<Type>, expected: Expected<Type>,
region: Region, region: Region,
@ -112,23 +122,20 @@ pub fn num_literal(
) -> Constraint { ) -> Constraint {
let open_number_type = crate::builtins::num_num(Type::Variable(num_var)); let open_number_type = crate::builtins::num_num(Type::Variable(num_var));
let mut constrs = Vec::with_capacity(3); let mut constrs = ArrayVec::<_, 2>::new();
let num_type = let num_type = add_numeric_bound_constr(
add_numeric_bound_constr(&mut constrs, open_number_type, bound, region, Category::Num); constraints,
constrs.extend(vec![Eq(num_type, expected, Category::Num, region)]); &mut constrs,
open_number_type,
bound,
region,
Category::Num,
);
exists(vec![num_var], And(constrs)) constrs.extend([constraints.equal_types(num_type, expected, Category::Num, region)]);
}
#[inline(always)] let and_constraint = constraints.and_constraint(constrs);
pub fn exists(flex_vars: Vec<Variable>, constraint: Constraint) -> Constraint { constraints.exists([num_var], and_constraint)
Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars,
def_types: SendMap::default(),
defs_constraint: constraint,
ret_constraint: Constraint::True,
}))
} }
#[inline(always)] #[inline(always)]

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,5 @@
use crate::expr::constrain_decls;
use roc_builtins::std::StdLib; use roc_builtins::std::StdLib;
use roc_can::constraint::{Constraint, LetConstraint}; use roc_can::constraint::{Constraint, Constraints};
use roc_can::def::Declaration; use roc_can::def::Declaration;
use roc_collections::all::{MutMap, MutSet, SendMap}; use roc_collections::all::{MutMap, MutSet, SendMap};
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
@ -17,13 +16,12 @@ pub enum ExposedModuleTypes {
Valid(MutMap<Symbol, SolvedType>, MutMap<Symbol, Alias>), Valid(MutMap<Symbol, SolvedType>, MutMap<Symbol, Alias>),
} }
pub struct ConstrainedModule { pub fn constrain_module(
pub unused_imports: MutMap<ModuleId, Region>, constraints: &mut Constraints,
pub constraint: Constraint, declarations: &[Declaration],
} home: ModuleId,
) -> Constraint {
pub fn constrain_module(declarations: &[Declaration], home: ModuleId) -> Constraint { crate::expr::constrain_decls(constraints, home, declarations)
constrain_decls(home, declarations)
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -33,11 +31,11 @@ pub struct Import {
} }
pub fn constrain_imported_values( pub fn constrain_imported_values(
constraints: &mut Constraints,
imports: Vec<Import>, imports: Vec<Import>,
body_con: Constraint, body_con: Constraint,
var_store: &mut VarStore, var_store: &mut VarStore,
) -> (Vec<Variable>, Constraint) { ) -> (Vec<Variable>, Constraint) {
use Constraint::*;
let mut def_types = SendMap::default(); let mut def_types = SendMap::default();
let mut rigid_vars = Vec::new(); let mut rigid_vars = Vec::new();
@ -84,24 +82,19 @@ pub fn constrain_imported_values(
( (
rigid_vars.clone(), rigid_vars.clone(),
Let(Box::new(LetConstraint { constraints.let_constraint(rigid_vars, [], def_types, Constraint::True, body_con),
rigid_vars,
flex_vars: Vec::new(),
def_types,
defs_constraint: True,
ret_constraint: body_con,
})),
) )
} }
/// Run pre_constrain_imports to get imported_symbols and imported_aliases. /// Run pre_constrain_imports to get imported_symbols and imported_aliases.
pub fn constrain_imports( pub fn constrain_imports(
constraints: &mut Constraints,
imported_symbols: Vec<Import>, imported_symbols: Vec<Import>,
constraint: Constraint, constraint: Constraint,
var_store: &mut VarStore, var_store: &mut VarStore,
) -> Constraint { ) -> Constraint {
let (_introduced_rigids, constraint) = let (_introduced_rigids, constraint) =
constrain_imported_values(imported_symbols, constraint, var_store); constrain_imported_values(constraints, imported_symbols, constraint, var_store);
// TODO determine what to do with those rigids // TODO determine what to do with those rigids
// for var in introduced_rigids { // for var in introduced_rigids {

View file

@ -1,10 +1,10 @@
use crate::builtins; use crate::builtins;
use crate::expr::{constrain_expr, Env}; use crate::expr::{constrain_expr, Env};
use roc_can::constraint::{Constraint, PresenceConstraint}; use roc_can::constraint::{Constraint, Constraints};
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
use roc_can::pattern::Pattern::{self, *}; use roc_can::pattern::Pattern::{self, *};
use roc_can::pattern::{DestructType, RecordDestruct}; use roc_can::pattern::{DestructType, RecordDestruct};
use roc_collections::all::{Index, SendMap}; use roc_collections::all::{HumanIndex, SendMap};
use roc_module::ident::Lowercase; use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
@ -27,7 +27,7 @@ pub struct PatternState {
/// definition has an annotation, we instead now add `x => Int`. /// definition has an annotation, we instead now add `x => Int`.
pub fn headers_from_annotation( pub fn headers_from_annotation(
pattern: &Pattern, pattern: &Pattern,
annotation: &Loc<Type>, annotation: &Loc<&Type>,
) -> Option<SendMap<Symbol, Loc<Type>>> { ) -> Option<SendMap<Symbol, Loc<Type>>> {
let mut headers = SendMap::default(); let mut headers = SendMap::default();
// Check that the annotation structurally agrees with the pattern, preventing e.g. `{ x, y } : Int` // Check that the annotation structurally agrees with the pattern, preventing e.g. `{ x, y } : Int`
@ -44,12 +44,13 @@ pub fn headers_from_annotation(
fn headers_from_annotation_help( fn headers_from_annotation_help(
pattern: &Pattern, pattern: &Pattern,
annotation: &Loc<Type>, annotation: &Loc<&Type>,
headers: &mut SendMap<Symbol, Loc<Type>>, headers: &mut SendMap<Symbol, Loc<Type>>,
) -> bool { ) -> bool {
match pattern { match pattern {
Identifier(symbol) | Shadowed(_, _, symbol) => { Identifier(symbol) | Shadowed(_, _, symbol) => {
headers.insert(*symbol, annotation.clone()); let typ = Loc::at(annotation.region, annotation.value.clone());
headers.insert(*symbol, typ);
true true
} }
Underscore Underscore
@ -106,7 +107,7 @@ fn headers_from_annotation_help(
.all(|(arg_pattern, arg_type)| { .all(|(arg_pattern, arg_type)| {
headers_from_annotation_help( headers_from_annotation_help(
&arg_pattern.1.value, &arg_pattern.1.value,
&Loc::at(annotation.region, arg_type.clone()), &Loc::at(annotation.region, arg_type),
headers, headers,
) )
}) })
@ -135,12 +136,13 @@ fn headers_from_annotation_help(
&& type_arguments.len() == pat_type_arguments.len() && type_arguments.len() == pat_type_arguments.len()
&& lambda_set_variables.len() == pat_lambda_set_variables.len() => && lambda_set_variables.len() == pat_lambda_set_variables.len() =>
{ {
headers.insert(*opaque, annotation.clone()); let typ = Loc::at(annotation.region, annotation.value.clone());
headers.insert(*opaque, typ);
let (_, argument_pat) = &**argument; let (_, argument_pat) = &**argument;
headers_from_annotation_help( headers_from_annotation_help(
&argument_pat.value, &argument_pat.value,
&Loc::at(annotation.region, (**actual).clone()), &Loc::at(annotation.region, actual),
headers, headers,
) )
} }
@ -153,6 +155,7 @@ fn headers_from_annotation_help(
/// initialize the Vecs in PatternState using with_capacity /// initialize the Vecs in PatternState using with_capacity
/// based on its knowledge of their lengths. /// based on its knowledge of their lengths.
pub fn constrain_pattern( pub fn constrain_pattern(
constraints: &mut Constraints,
env: &Env, env: &Env,
pattern: &Pattern, pattern: &Pattern,
region: Region, region: Region,
@ -167,20 +170,18 @@ pub fn constrain_pattern(
// A -> "" // A -> ""
// _ -> "" // _ -> ""
// so, we know that "x" (in this case, a tag union) must be open. // so, we know that "x" (in this case, a tag union) must be open.
state.constraints.push(Constraint::Present( state
expected.get_type(), .constraints
PresenceConstraint::IsOpen, .push(constraints.is_open_type(expected.get_type()));
));
} }
UnsupportedPattern(_) | MalformedPattern(_, _) | OpaqueNotInScope(..) => { UnsupportedPattern(_) | MalformedPattern(_, _) | OpaqueNotInScope(..) => {
// Erroneous patterns don't add any constraints. // Erroneous patterns don't add any constraints.
} }
Identifier(symbol) | Shadowed(_, _, symbol) => { Identifier(symbol) | Shadowed(_, _, symbol) => {
state.constraints.push(Constraint::Present( state
expected.get_type_ref().clone(), .constraints
PresenceConstraint::IsOpen, .push(constraints.is_open_type(expected.get_type_ref().clone()));
));
state.headers.insert( state.headers.insert(
*symbol, *symbol,
Loc { Loc {
@ -196,6 +197,7 @@ pub fn constrain_pattern(
let num_type = builtins::num_num(Type::Variable(var)); let num_type = builtins::num_num(Type::Variable(var));
let num_type = builtins::add_numeric_bound_constr( let num_type = builtins::add_numeric_bound_constr(
constraints,
&mut state.constraints, &mut state.constraints,
num_type, num_type,
bound, bound,
@ -203,11 +205,11 @@ pub fn constrain_pattern(
Category::Num, Category::Num,
); );
state.constraints.push(Constraint::Pattern( state.constraints.push(constraints.equal_pattern_types(
region,
PatternCategory::Num,
num_type, num_type,
expected, expected,
PatternCategory::Num,
region,
)); ));
} }
@ -215,6 +217,7 @@ pub fn constrain_pattern(
// First constraint on the free num var; this improves the resolved type quality in // First constraint on the free num var; this improves the resolved type quality in
// case the bound is an alias. // case the bound is an alias.
let num_type = builtins::add_numeric_bound_constr( let num_type = builtins::add_numeric_bound_constr(
constraints,
&mut state.constraints, &mut state.constraints,
Type::Variable(num_var), Type::Variable(num_var),
bound, bound,
@ -225,7 +228,7 @@ pub fn constrain_pattern(
// Link the free num var with the int var and our expectation. // Link the free num var with the int var and our expectation.
let int_type = builtins::num_int(Type::Variable(precision_var)); let int_type = builtins::num_int(Type::Variable(precision_var));
state.constraints.push(Constraint::Eq( state.constraints.push(constraints.equal_types(
num_type, // TODO check me if something breaks! num_type, // TODO check me if something breaks!
Expected::NoExpectation(int_type), Expected::NoExpectation(int_type),
Category::Int, Category::Int,
@ -233,11 +236,11 @@ pub fn constrain_pattern(
)); ));
// Also constrain the pattern against the num var, again to reuse aliases if they're present. // Also constrain the pattern against the num var, again to reuse aliases if they're present.
state.constraints.push(Constraint::Pattern( state.constraints.push(constraints.equal_pattern_types(
region,
PatternCategory::Int,
Type::Variable(num_var), Type::Variable(num_var),
expected, expected,
PatternCategory::Int,
region,
)); ));
} }
@ -245,6 +248,7 @@ pub fn constrain_pattern(
// First constraint on the free num var; this improves the resolved type quality in // First constraint on the free num var; this improves the resolved type quality in
// case the bound is an alias. // case the bound is an alias.
let num_type = builtins::add_numeric_bound_constr( let num_type = builtins::add_numeric_bound_constr(
constraints,
&mut state.constraints, &mut state.constraints,
Type::Variable(num_var), Type::Variable(num_var),
bound, bound,
@ -255,7 +259,7 @@ pub fn constrain_pattern(
// Link the free num var with the float var and our expectation. // Link the free num var with the float var and our expectation.
let float_type = builtins::num_float(Type::Variable(precision_var)); let float_type = builtins::num_float(Type::Variable(precision_var));
state.constraints.push(Constraint::Eq( state.constraints.push(constraints.equal_types(
num_type.clone(), // TODO check me if something breaks! num_type.clone(), // TODO check me if something breaks!
Expected::NoExpectation(float_type), Expected::NoExpectation(float_type),
Category::Float, Category::Float,
@ -263,29 +267,29 @@ pub fn constrain_pattern(
)); ));
// Also constrain the pattern against the num var, again to reuse aliases if they're present. // Also constrain the pattern against the num var, again to reuse aliases if they're present.
state.constraints.push(Constraint::Pattern( state.constraints.push(constraints.equal_pattern_types(
region,
PatternCategory::Float,
num_type, // TODO check me if something breaks! num_type, // TODO check me if something breaks!
expected, expected,
PatternCategory::Float,
region,
)); ));
} }
StrLiteral(_) => { StrLiteral(_) => {
state.constraints.push(Constraint::Pattern( state.constraints.push(constraints.equal_pattern_types(
region,
PatternCategory::Str,
builtins::str_type(), builtins::str_type(),
expected, expected,
PatternCategory::Str,
region,
)); ));
} }
SingleQuote(_) => { SingleQuote(_) => {
state.constraints.push(Constraint::Pattern( state.constraints.push(constraints.equal_pattern_types(
region,
PatternCategory::Character,
builtins::num_u32(), builtins::num_u32(),
expected, expected,
PatternCategory::Character,
region,
)); ));
} }
@ -322,36 +326,39 @@ pub fn constrain_pattern(
let field_type = match typ { let field_type = match typ {
DestructType::Guard(guard_var, loc_guard) => { DestructType::Guard(guard_var, loc_guard) => {
state.constraints.push(Constraint::Present( state.constraints.push(constraints.pattern_presence(
Type::Variable(*guard_var), Type::Variable(*guard_var),
PresenceConstraint::Pattern( PExpected::ForReason(
region, PReason::PatternGuard,
PatternCategory::PatternGuard, pat_type.clone(),
PExpected::ForReason( loc_guard.region,
PReason::PatternGuard,
pat_type.clone(),
loc_guard.region,
),
), ),
PatternCategory::PatternGuard,
region,
)); ));
state.vars.push(*guard_var); state.vars.push(*guard_var);
constrain_pattern(env, &loc_guard.value, loc_guard.region, expected, state); constrain_pattern(
constraints,
env,
&loc_guard.value,
loc_guard.region,
expected,
state,
);
RecordField::Demanded(pat_type) RecordField::Demanded(pat_type)
} }
DestructType::Optional(expr_var, loc_expr) => { DestructType::Optional(expr_var, loc_expr) => {
state.constraints.push(Constraint::Present( state.constraints.push(constraints.pattern_presence(
Type::Variable(*expr_var), Type::Variable(*expr_var),
PresenceConstraint::Pattern( PExpected::ForReason(
region, PReason::OptionalField,
PatternCategory::PatternDefault, pat_type.clone(),
PExpected::ForReason( loc_expr.region,
PReason::OptionalField,
pat_type.clone(),
loc_expr.region,
),
), ),
PatternCategory::PatternDefault,
region,
)); ));
state.vars.push(*expr_var); state.vars.push(*expr_var);
@ -362,8 +369,13 @@ pub fn constrain_pattern(
loc_expr.region, loc_expr.region,
); );
let expr_con = let expr_con = constrain_expr(
constrain_expr(env, loc_expr.region, &loc_expr.value, expr_expected); constraints,
env,
loc_expr.region,
&loc_expr.value,
expr_expected,
);
state.constraints.push(expr_con); state.constraints.push(expr_con);
RecordField::Optional(pat_type) RecordField::Optional(pat_type)
@ -381,16 +393,18 @@ pub fn constrain_pattern(
let record_type = Type::Record(field_types, Box::new(ext_type)); let record_type = Type::Record(field_types, Box::new(ext_type));
let whole_con = Constraint::Eq( let whole_con = constraints.equal_types(
Type::Variable(*whole_var), Type::Variable(*whole_var),
Expected::NoExpectation(record_type), Expected::NoExpectation(record_type),
Category::Storage(std::file!(), std::line!()), Category::Storage(std::file!(), std::line!()),
region, region,
); );
let record_con = Constraint::Present( let record_con = constraints.pattern_presence(
Type::Variable(*whole_var), Type::Variable(*whole_var),
PresenceConstraint::Pattern(region, PatternCategory::Record, expected), expected,
PatternCategory::Record,
region,
); );
state.constraints.push(whole_con); state.constraints.push(whole_con);
@ -412,29 +426,36 @@ pub fn constrain_pattern(
let expected = PExpected::ForReason( let expected = PExpected::ForReason(
PReason::TagArg { PReason::TagArg {
tag_name: tag_name.clone(), tag_name: tag_name.clone(),
index: Index::zero_based(index), index: HumanIndex::zero_based(index),
}, },
pattern_type, pattern_type,
region, region,
); );
constrain_pattern(env, &loc_pattern.value, loc_pattern.region, expected, state); constrain_pattern(
constraints,
env,
&loc_pattern.value,
loc_pattern.region,
expected,
state,
);
} }
let pat_category = PatternCategory::Ctor(tag_name.clone()); let pat_category = PatternCategory::Ctor(tag_name.clone());
let whole_con = Constraint::Present( let whole_con = constraints.includes_tag(
expected.clone().get_type(), expected.clone().get_type(),
PresenceConstraint::IncludesTag( tag_name.clone(),
tag_name.clone(), argument_types.clone(),
argument_types.clone(), pat_category.clone(),
region, region,
pat_category.clone(),
),
); );
let tag_con = Constraint::Present( let tag_con = constraints.pattern_presence(
Type::Variable(*whole_var), Type::Variable(*whole_var),
PresenceConstraint::Pattern(region, pat_category, expected), expected,
pat_category,
region,
); );
state.vars.push(*whole_var); state.vars.push(*whole_var);
@ -466,6 +487,7 @@ pub fn constrain_pattern(
// First, add a constraint for the argument "who" // First, add a constraint for the argument "who"
let arg_pattern_expected = PExpected::NoExpectation(arg_pattern_type.clone()); let arg_pattern_expected = PExpected::NoExpectation(arg_pattern_type.clone());
constrain_pattern( constrain_pattern(
constraints,
env, env,
&loc_arg_pattern.value, &loc_arg_pattern.value,
loc_arg_pattern.region, loc_arg_pattern.region,
@ -474,7 +496,7 @@ pub fn constrain_pattern(
); );
// Next, link `whole_var` to the opaque type of "@Id who" // Next, link `whole_var` to the opaque type of "@Id who"
let whole_con = Constraint::Eq( let whole_con = constraints.equal_types(
Type::Variable(*whole_var), Type::Variable(*whole_var),
Expected::NoExpectation(opaque_type), Expected::NoExpectation(opaque_type),
Category::Storage(std::file!(), std::line!()), Category::Storage(std::file!(), std::line!()),
@ -484,7 +506,7 @@ pub fn constrain_pattern(
// Link the entire wrapped opaque type (with the now-constrained argument) to the type // Link the entire wrapped opaque type (with the now-constrained argument) to the type
// variables of the opaque type // variables of the opaque type
// TODO: better expectation here // TODO: better expectation here
let link_type_variables_con = Constraint::Eq( let link_type_variables_con = constraints.equal_types(
(**specialized_def_type).clone(), (**specialized_def_type).clone(),
Expected::NoExpectation(arg_pattern_type), Expected::NoExpectation(arg_pattern_type),
Category::OpaqueWrap(*opaque), Category::OpaqueWrap(*opaque),
@ -492,9 +514,11 @@ pub fn constrain_pattern(
); );
// Next, link `whole_var` (the type of "@Id who") to the expected type // Next, link `whole_var` (the type of "@Id who") to the expected type
let opaque_pattern_con = Constraint::Present( let opaque_pattern_con = constraints.pattern_presence(
Type::Variable(*whole_var), Type::Variable(*whole_var),
PresenceConstraint::Pattern(region, PatternCategory::Opaque(*opaque), expected), expected,
PatternCategory::Opaque(*opaque),
region,
); );
state state

View file

@ -1,4 +1,7 @@
use roc_collections::all::{Index, MutMap}; //! Exhaustiveness checking, based on "Warning for pattern matching" (Luc Maranget, 2007).
//! http://moscova.inria.fr/~maranget/papers/warn/warn.pdf
use roc_collections::all::{HumanIndex, MutMap};
use roc_module::ident::{Lowercase, TagIdIntType, TagName}; use roc_module::ident::{Lowercase, TagIdIntType, TagName};
use roc_region::all::Region; use roc_region::all::Region;
use roc_std::RocDec; use roc_std::RocDec;
@ -70,7 +73,7 @@ pub enum Error {
Redundant { Redundant {
overall_region: Region, overall_region: Region,
branch_region: Region, branch_region: Region,
index: Index, index: HumanIndex,
}, },
} }

View file

@ -293,6 +293,9 @@ impl<'a> Formattable for TypeAnnotation<'a> {
SpaceBefore(ann, spaces) => { SpaceBefore(ann, spaces) => {
buf.newline(); buf.newline();
buf.indent(indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
ann.format_with_options(buf, parens, Newlines::No, indent) ann.format_with_options(buf, parens, Newlines::No, indent)
} }

View file

@ -90,4 +90,116 @@ impl<'a> Buf<'a> {
self.spaces_to_flush = 0; self.spaces_to_flush = 0;
} }
} }
/// Ensures the text ends in a newline with no whitespace preceding it.
pub fn fmt_end_of_file(&mut self) {
fmt_text_eof(&mut self.text)
}
}
/// Ensures the text ends in a newline with no whitespace preceding it.
fn fmt_text_eof(text: &mut bumpalo::collections::String<'_>) {
let mut chars_rev = text.chars().rev();
let mut last_whitespace = None;
let mut last_whitespace_index = text.len();
// Keep going until we either run out of characters or encounter one
// that isn't whitespace.
loop {
match chars_rev.next() {
Some(ch) if ch.is_whitespace() => {
last_whitespace = Some(ch);
last_whitespace_index -= 1;
}
_ => {
break;
}
}
}
match last_whitespace {
Some('\n') => {
// There may have been more whitespace after this newline; remove it!
text.truncate(last_whitespace_index + '\n'.len_utf8());
}
Some(_) => {
// There's some whitespace at the end of this file, but the first
// whitespace char after the last non-whitespace char isn't a newline.
// So replace that whitespace char (and everything after it) with a newline.
text.replace_range(last_whitespace_index.., "\n");
}
None => {
debug_assert!(last_whitespace_index == text.len());
debug_assert!(!text.ends_with(char::is_whitespace));
// This doesn't end in whitespace at all, so add a newline.
text.push('\n');
}
}
}
#[test]
fn eof_text_ends_with_newline() {
use bumpalo::{collections::String, Bump};
let arena = Bump::new();
let input = "This should be a newline:\n";
let mut text = String::from_str_in(input, &arena);
fmt_text_eof(&mut text);
// This should be unchanged!
assert_eq!(text.as_str(), input);
}
#[test]
fn eof_text_ends_with_whitespace() {
use bumpalo::{collections::String, Bump};
let arena = Bump::new();
let input = "This should be a newline: \t";
let mut text = String::from_str_in(input, &arena);
fmt_text_eof(&mut text);
assert_eq!(text.as_str(), "This should be a newline:\n");
}
#[test]
fn eof_text_ends_with_whitespace_then_newline() {
use bumpalo::{collections::String, Bump};
let arena = Bump::new();
let input = "This should be a newline: \n";
let mut text = String::from_str_in(input, &arena);
fmt_text_eof(&mut text);
assert_eq!(text.as_str(), "This should be a newline:\n");
}
#[test]
fn eof_text_ends_with_no_whitespace() {
use bumpalo::{collections::String, Bump};
let arena = Bump::new();
let input = "This should be a newline:";
let mut text = String::from_str_in(input, &arena);
fmt_text_eof(&mut text);
assert_eq!(text.as_str(), "This should be a newline:\n");
}
#[test]
fn eof_text_is_empty() {
use bumpalo::{collections::String, Bump};
let arena = Bump::new();
let input = "";
let mut text = String::from_str_in(input, &arena);
fmt_text_eof(&mut text);
assert_eq!(text.as_str(), "\n");
} }

View file

@ -2984,6 +2984,18 @@ mod test_fmt {
)); ));
} }
#[test]
fn multiline_higher_order_function() {
expr_formats_same(indoc!(
r#"
foo :
(Str -> Bool) -> Bool
42
"#
));
}
#[test] #[test]
/// Test that everything under examples/ is formatted correctly /// Test that everything under examples/ is formatted correctly
/// If this test fails on your diff, it probably means you need to re-format the examples. /// If this test fails on your diff, it probably means you need to re-format the examples.
@ -3002,7 +3014,7 @@ mod test_fmt {
for entry in walkdir::WalkDir::new(&root) { for entry in walkdir::WalkDir::new(&root) {
let entry = entry.unwrap(); let entry = entry.unwrap();
let path = entry.path(); let path = entry.path();
if path.extension() == Some(&std::ffi::OsStr::new("roc")) { if path.extension() == Some(std::ffi::OsStr::new("roc")) {
count += 1; count += 1;
let src = std::fs::read_to_string(path).unwrap(); let src = std::fs::read_to_string(path).unwrap();
println!("Now trying to format {}", path.display()); println!("Now trying to format {}", path.display());

View file

@ -66,10 +66,12 @@ impl RegTrait for AArch64FloatReg {
} }
} }
#[derive(Copy, Clone)]
pub struct AArch64Assembler {} pub struct AArch64Assembler {}
// AArch64Call may need to eventually be split by OS, // AArch64Call may need to eventually be split by OS,
// but I think with how we use it, they may all be the same. // but I think with how we use it, they may all be the same.
#[derive(Copy, Clone)]
pub struct AArch64Call {} pub struct AArch64Call {}
const STACK_ALIGNMENT: u8 = 16; const STACK_ALIGNMENT: u8 = 16;
@ -281,7 +283,8 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg, AArch64Assembler> for AArch64C
AArch64Assembler, AArch64Assembler,
AArch64Call, AArch64Call,
>, >,
_args: &'a [Symbol], _dst: &Symbol,
_args: &[Symbol],
_arg_layouts: &[Layout<'a>], _arg_layouts: &[Layout<'a>],
_ret_layout: &Layout<'a>, _ret_layout: &Layout<'a>,
) { ) {
@ -480,6 +483,70 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
} }
} }
#[inline(always)]
fn mov_reg64_mem64_offset32(
buf: &mut Vec<'_, u8>,
dst: AArch64GeneralReg,
src: AArch64GeneralReg,
offset: i32,
) {
if offset < 0 {
todo!("negative mem offsets for AArch64");
} else if offset < (0xFFF << 8) {
debug_assert!(offset % 8 == 0);
ldr_reg64_imm12(buf, dst, src, (offset as u16) >> 3);
} else {
todo!("mem offsets over 32k for AArch64");
}
}
#[inline(always)]
fn mov_mem64_offset32_reg64(
buf: &mut Vec<'_, u8>,
dst: AArch64GeneralReg,
offset: i32,
src: AArch64GeneralReg,
) {
if offset < 0 {
todo!("negative mem offsets for AArch64");
} else if offset < (0xFFF << 8) {
debug_assert!(offset % 8 == 0);
str_reg64_imm12(buf, src, dst, (offset as u16) >> 3);
} else {
todo!("mem offsets over 32k for AArch64");
}
}
#[inline(always)]
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) {
debug_assert!(size <= 8);
if size == 8 {
Self::mov_reg64_base32(buf, dst, offset);
} else if size == 4 {
todo!("sign extending 4 byte values");
} else if size == 2 {
todo!("sign extending 2 byte values");
} else if size == 1 {
todo!("sign extending 1 byte values");
} else {
internal_error!("Invalid size for sign extension: {}", size);
}
}
#[inline(always)]
fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) {
debug_assert!(size <= 8);
if size == 8 {
Self::mov_reg64_base32(buf, dst, offset);
} else if size == 4 {
todo!("zero extending 4 byte values");
} else if size == 2 {
todo!("zero extending 2 byte values");
} else if size == 1 {
todo!("zero extending 1 byte values");
} else {
internal_error!("Invalid size for zero extension: {}", size);
}
}
#[inline(always)] #[inline(always)]
fn mov_freg64_stack32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) { fn mov_freg64_stack32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) {
todo!("loading floating point reg from stack for AArch64"); todo!("loading floating point reg from stack for AArch64");
@ -606,6 +673,16 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
todo!("registers to float for AArch64"); todo!("registers to float for AArch64");
} }
#[inline(always)]
fn lte_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) {
todo!("registers less than or equal for AArch64");
}
#[inline(always)] #[inline(always)]
fn gte_reg64_reg64_reg64( fn gte_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>, _buf: &mut Vec<'_, u8>,

View file

@ -1,12 +1,15 @@
use crate::{single_register_floats, single_register_integers, Backend, Env, Relocation}; use crate::{
single_register_floats, single_register_int_builtins, single_register_integers, Backend, Env,
Relocation,
};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::code_gen_help::CodeGenHelp;
use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, ProcLayout, SelfRecursive, Stmt}; use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, ProcLayout, SelfRecursive, Stmt};
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout, TagIdIntType, UnionLayout};
use roc_target::TargetInfo; use roc_target::TargetInfo;
use std::marker::PhantomData; use std::marker::PhantomData;
@ -16,8 +19,10 @@ pub(crate) mod x86_64;
use storage::StorageManager; use storage::StorageManager;
// TODO: on all number functions double check and deal with over/underflow.
pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler<GeneralReg, FloatReg>>: pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler<GeneralReg, FloatReg>>:
Sized Sized + Copy
{ {
const BASE_PTR_REG: GeneralReg; const BASE_PTR_REG: GeneralReg;
const STACK_PTR_REG: GeneralReg; const STACK_PTR_REG: GeneralReg;
@ -72,7 +77,8 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler<Gene
fn store_args<'a>( fn store_args<'a>(
buf: &mut Vec<'a, u8>, buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>, storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>,
args: &'a [Symbol], dst: &Symbol,
args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
// ret_layout is needed because if it is a complex type, we pass a pointer as the first arg. // ret_layout is needed because if it is a complex type, we pass a pointer as the first arg.
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
@ -103,7 +109,7 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler<Gene
/// Thus, some backends will need to use mulitiple instructions to preform a single one of this calls. /// Thus, some backends will need to use mulitiple instructions to preform a single one of this calls.
/// Generally, I prefer explicit sources, as opposed to dst being one of the sources. Ex: `x = x + y` would be `add x, x, y` instead of `add x, y`. /// Generally, I prefer explicit sources, as opposed to dst being one of the sources. Ex: `x = x + y` would be `add x, x, y` instead of `add x, y`.
/// dst should always come before sources. /// dst should always come before sources.
pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized { pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
fn abs_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg); fn abs_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
fn abs_freg64_freg64( fn abs_freg64_freg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
@ -167,6 +173,26 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized {
fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg); fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
fn mov_reg64_mem64_offset32(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src: GeneralReg,
offset: i32,
);
fn mov_mem64_offset32_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
offset: i32,
src: GeneralReg,
);
/// Sign extends the data at `offset` with `size` as it copies it to `dst`
/// size must be less than or equal to 8.
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, size: u8);
/// Zero extends the data at `offset` with `size` as it copies it to `dst`
/// size must be less than or equal to 8.
fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, size: u8);
fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32); fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32);
fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32); fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg); fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
@ -217,6 +243,13 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized {
fn to_float_freg64_freg32(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg); fn to_float_freg64_freg32(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg);
fn lte_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
);
fn gte_reg64_reg64_reg64( fn gte_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
dst: GeneralReg, dst: GeneralReg,
@ -256,7 +289,7 @@ pub struct Backend64Bit<
free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>, free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>,
literal_map: MutMap<Symbol, (*const Literal<'a>, *const Layout<'a>)>, literal_map: MutMap<Symbol, (*const Literal<'a>, *const Layout<'a>)>,
join_map: MutMap<JoinPointId, u64>, join_map: MutMap<JoinPointId, Vec<'a, (u64, u64)>>,
storage_manager: StorageManager<'a, GeneralReg, FloatReg, ASM, CC>, storage_manager: StorageManager<'a, GeneralReg, FloatReg, ASM, CC>,
} }
@ -328,7 +361,6 @@ impl<
self.join_map.clear(); self.join_map.clear();
self.free_map.clear(); self.free_map.clear();
self.buf.clear(); self.buf.clear();
self.helper_proc_symbols.clear();
self.storage_manager.reset(); self.storage_manager.reset();
} }
@ -462,7 +494,7 @@ impl<
&mut self, &mut self,
dst: &Symbol, dst: &Symbol,
fn_name: String, fn_name: String,
args: &'a [Symbol], args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) { ) {
@ -479,6 +511,7 @@ impl<
CC::store_args( CC::store_args(
&mut self.buf, &mut self.buf,
&mut self.storage_manager, &mut self.storage_manager,
dst,
args, args,
arg_layouts, arg_layouts,
ret_layout, ret_layout,
@ -523,52 +556,56 @@ impl<
.storage_manager .storage_manager
.load_to_general_reg(&mut self.buf, cond_symbol); .load_to_general_reg(&mut self.buf, cond_symbol);
let mut base_storage = self.storage_manager.clone();
let mut max_branch_stack_size = 0;
let mut ret_jumps = bumpalo::vec![in self.env.arena]; let mut ret_jumps = bumpalo::vec![in self.env.arena];
let mut tmp = bumpalo::vec![in self.env.arena]; let mut tmp = bumpalo::vec![in self.env.arena];
for (val, branch_info, stmt) in branches.iter() { for (val, _branch_info, stmt) in branches.iter() {
// TODO: look into branch info and if it matters here.
tmp.clear(); tmp.clear();
if let BranchInfo::None = branch_info { // Create jump to next branch if cond_sym not equal to value.
// Create jump to next branch if not cond_sym not equal to value. // Since we don't know the offset yet, set it to 0 and overwrite later.
// Since we don't know the offset yet, set it to 0 and overwrite later. let jne_location = self.buf.len();
let jne_location = self.buf.len(); let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0);
let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0);
// Build all statements in this branch. // Build all statements in this branch. Using storage as from before any branch.
self.build_stmt(stmt, ret_layout); self.storage_manager = base_storage.clone();
// Build unconditional jump to the end of this switch.
// Since we don't know the offset yet, set it to 0 and overwrite later.
let jmp_location = self.buf.len();
let jmp_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
ret_jumps.push((jmp_location, jmp_offset));
// Overwrite the original jne with the correct offset.
let end_offset = self.buf.len();
let jne_offset = end_offset - start_offset;
ASM::jne_reg64_imm64_imm32(&mut tmp, cond_reg, *val, jne_offset as i32);
for (i, byte) in tmp.iter().enumerate() {
self.buf[jne_location + i] = *byte;
}
} else {
todo!("Switch: branch info, {:?}", branch_info);
}
}
let (branch_info, stmt) = default_branch;
if let BranchInfo::None = branch_info {
self.build_stmt(stmt, ret_layout); self.build_stmt(stmt, ret_layout);
// Update all return jumps to jump past the default case. // Build unconditional jump to the end of this switch.
let ret_offset = self.buf.len(); // Since we don't know the offset yet, set it to 0 and overwrite later.
for (jmp_location, start_offset) in ret_jumps.into_iter() { let jmp_location = self.buf.len();
self.update_jmp_imm32_offset( let jmp_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
&mut tmp, ret_jumps.push((jmp_location, jmp_offset));
jmp_location as u64,
start_offset as u64, // Overwrite the original jne with the correct offset.
ret_offset as u64, let end_offset = self.buf.len();
); let jne_offset = end_offset - start_offset;
ASM::jne_reg64_imm64_imm32(&mut tmp, cond_reg, *val, jne_offset as i32);
for (i, byte) in tmp.iter().enumerate() {
self.buf[jne_location + i] = *byte;
} }
} else {
todo!("Switch: branch info, {:?}", branch_info); // Update important storage information to avoid overwrites.
max_branch_stack_size =
std::cmp::max(max_branch_stack_size, self.storage_manager.stack_size());
base_storage.update_fn_call_stack_size(self.storage_manager.fn_call_stack_size());
}
self.storage_manager = base_storage;
self.storage_manager
.update_stack_size(max_branch_stack_size);
let (_branch_info, stmt) = default_branch;
self.build_stmt(stmt, ret_layout);
// Update all return jumps to jump past the default case.
let ret_offset = self.buf.len();
for (jmp_location, start_offset) in ret_jumps.into_iter() {
self.update_jmp_imm32_offset(
&mut tmp,
jmp_location as u64,
start_offset as u64,
ret_offset as u64,
);
} }
} }
@ -580,36 +617,41 @@ impl<
remainder: &'a Stmt<'a>, remainder: &'a Stmt<'a>,
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) { ) {
// Free everything to the stack to make sure they don't get messed up when looping back to this point.
// TODO: look into a nicer solution.
self.storage_manager.free_all_to_stack(&mut self.buf);
// Ensure all the joinpoint parameters have storage locations. // Ensure all the joinpoint parameters have storage locations.
// On jumps to the joinpoint, we will overwrite those locations as a way to "pass parameters" to the joinpoint. // On jumps to the joinpoint, we will overwrite those locations as a way to "pass parameters" to the joinpoint.
self.storage_manager self.storage_manager
.setup_joinpoint(&mut self.buf, id, parameters); .setup_joinpoint(&mut self.buf, id, parameters);
// Create jump to remaining. self.join_map.insert(*id, bumpalo::vec![in self.env.arena]);
let jmp_location = self.buf.len();
let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); // Build remainder of function first. It is what gets run and jumps to join.
self.build_stmt(remainder, ret_layout);
let join_location = self.buf.len() as u64;
// Build all statements in body. // Build all statements in body.
self.join_map.insert(*id, self.buf.len() as u64);
self.build_stmt(body, ret_layout); self.build_stmt(body, ret_layout);
// Overwrite the original jump with the correct offset. // Overwrite the all jumps to the joinpoint with the correct offset.
let mut tmp = bumpalo::vec![in self.env.arena]; let mut tmp = bumpalo::vec![in self.env.arena];
self.update_jmp_imm32_offset( for (jmp_location, start_offset) in self
&mut tmp, .join_map
jmp_location as u64, .remove(id)
start_offset as u64, .unwrap_or_else(|| internal_error!("join point not defined"))
self.buf.len() as u64, {
); tmp.clear();
self.update_jmp_imm32_offset(&mut tmp, jmp_location, start_offset, join_location);
// Build remainder of function. }
self.build_stmt(remainder, ret_layout)
} }
fn build_jump( fn build_jump(
&mut self, &mut self,
id: &JoinPointId, id: &JoinPointId,
args: &'a [Symbol], args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
_ret_layout: &Layout<'a>, _ret_layout: &Layout<'a>,
) { ) {
@ -619,15 +661,8 @@ impl<
let jmp_location = self.buf.len(); let jmp_location = self.buf.len();
let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
if let Some(offset) = self.join_map.get(id) { if let Some(vec) = self.join_map.get_mut(id) {
let offset = *offset; vec.push((jmp_location as u64, start_offset as u64))
let mut tmp = bumpalo::vec![in self.env.arena];
self.update_jmp_imm32_offset(
&mut tmp,
jmp_location as u64,
start_offset as u64,
offset,
);
} else { } else {
internal_error!("Jump: unknown point specified to jump to: {:?}", id); internal_error!("Jump: unknown point specified to jump to: {:?}", id);
} }
@ -716,7 +751,7 @@ impl<
fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>) { fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>) {
match arg_layout { match arg_layout {
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { Layout::Builtin(single_register_int_builtins!()) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self let src1_reg = self
.storage_manager .storage_manager
@ -823,6 +858,28 @@ impl<
} }
} }
fn build_num_lte(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
arg_layout: &Layout<'a>,
) {
match arg_layout {
Layout::Builtin(single_register_int_builtins!()) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src1);
let src2_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src2);
ASM::lte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
x => todo!("NumLte: layout, {:?}", x),
}
}
fn build_num_gte( fn build_num_gte(
&mut self, &mut self,
dst: &Symbol, dst: &Symbol,
@ -831,7 +888,7 @@ impl<
arg_layout: &Layout<'a>, arg_layout: &Layout<'a>,
) { ) {
match arg_layout { match arg_layout {
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { Layout::Builtin(single_register_int_builtins!()) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self let src1_reg = self
.storage_manager .storage_manager
@ -845,13 +902,173 @@ impl<
} }
} }
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol) {
self.storage_manager.list_len(&mut self.buf, dst, list);
}
fn build_list_get_unsafe(
&mut self,
dst: &Symbol,
list: &Symbol,
index: &Symbol,
ret_layout: &Layout<'a>,
) {
let (base_offset, _) = self.storage_manager.stack_offset_and_size(list);
let index_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, index);
let ret_stack_size = ret_layout.stack_size(self.storage_manager.target_info());
// TODO: This can be optimized with smarter instructions.
// Also can probably be moved into storage manager at least partly.
self.storage_manager.with_tmp_general_reg(
&mut self.buf,
|storage_manager, buf, list_ptr| {
ASM::mov_reg64_base32(buf, list_ptr, base_offset as i32);
storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp| {
ASM::mov_reg64_imm64(buf, tmp, ret_stack_size as i64);
ASM::imul_reg64_reg64_reg64(buf, tmp, tmp, index_reg);
ASM::add_reg64_reg64_reg64(buf, tmp, tmp, list_ptr);
match ret_layout {
single_register_integers!() if ret_stack_size == 8 => {
let dst_reg = storage_manager.claim_general_reg(buf, dst);
ASM::mov_reg64_mem64_offset32(buf, dst_reg, tmp, 0);
}
x => internal_error!("Loading list element with layout: {:?}", x),
}
});
},
);
}
fn build_list_replace_unsafe(
&mut self,
dst: &Symbol,
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) {
// We want to delegate to the zig builtin, but it takes some extra parameters.
// Firstly, it takes the alignment of the list.
// Secondly, it takes the stack size of an element.
// Thirdly, it takes a pointer that it will write the output element to.
let list = args[0];
let list_layout = arg_layouts[0];
let index = args[1];
let index_layout = arg_layouts[1];
let elem = args[2];
let elem_layout = arg_layouts[2];
let u32_layout = &Layout::Builtin(Builtin::Int(IntWidth::U32));
let list_alignment = list_layout.alignment_bytes(self.storage_manager.target_info());
self.load_literal(
&Symbol::DEV_TMP,
u32_layout,
&Literal::Int(list_alignment as i128),
);
// Have to pass the input element by pointer, so put it on the stack and load it's address.
self.storage_manager
.ensure_symbol_on_stack(&mut self.buf, &elem);
let u64_layout = &Layout::Builtin(Builtin::Int(IntWidth::U64));
let (new_elem_offset, _) = self.storage_manager.stack_offset_and_size(&elem);
// Load address of output element into register.
let reg = self
.storage_manager
.claim_general_reg(&mut self.buf, &Symbol::DEV_TMP2);
ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, new_elem_offset);
// Load the elements size.
let elem_stack_size = elem_layout.stack_size(self.storage_manager.target_info());
self.load_literal(
&Symbol::DEV_TMP3,
u64_layout,
&Literal::Int(elem_stack_size as i128),
);
// Setup the return location.
let base_offset = self.storage_manager.claim_stack_area(
dst,
ret_layout.stack_size(self.storage_manager.target_info()),
);
let ret_fields = if let Layout::Struct { field_layouts, .. } = ret_layout {
field_layouts
} else {
internal_error!(
"Expected replace to return a struct instead found: {:?}",
ret_layout
)
};
// Only return list and old element.
debug_assert_eq!(ret_fields.len(), 2);
let (out_list_offset, out_elem_offset) = if ret_fields[0] == elem_layout {
(
base_offset + ret_fields[0].stack_size(self.storage_manager.target_info()) as i32,
base_offset,
)
} else {
(
base_offset,
base_offset + ret_fields[0].stack_size(self.storage_manager.target_info()) as i32,
)
};
// Load address of output element into register.
let reg = self
.storage_manager
.claim_general_reg(&mut self.buf, &Symbol::DEV_TMP4);
ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, out_elem_offset);
let lowlevel_args = bumpalo::vec![
in self.env.arena;
list,
Symbol::DEV_TMP,
index,
Symbol::DEV_TMP2,
Symbol::DEV_TMP3,
Symbol::DEV_TMP4,
];
let lowlevel_arg_layouts = bumpalo::vec![
in self.env.arena;
list_layout,
*u32_layout,
index_layout,
*u64_layout,
*u64_layout,
*u64_layout,
];
self.build_fn_call(
&Symbol::DEV_TMP5,
bitcode::LIST_REPLACE.to_string(),
&lowlevel_args,
&lowlevel_arg_layouts,
&list_layout,
);
self.free_symbol(&Symbol::DEV_TMP);
self.free_symbol(&Symbol::DEV_TMP2);
self.free_symbol(&Symbol::DEV_TMP3);
self.free_symbol(&Symbol::DEV_TMP4);
// Copy from list to the output record.
self.storage_manager.copy_symbol_to_stack_offset(
&mut self.buf,
out_list_offset,
&Symbol::DEV_TMP5,
&list_layout,
);
self.free_symbol(&Symbol::DEV_TMP5);
}
fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol) { fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol) {
// We may not strictly need an instruction here.
// What's important is to load the value, and for src and dest to have different Layouts.
// This is used for pointer math in refcounting and for pointer equality
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src); self.storage_manager
ASM::mov_reg64_reg64(&mut self.buf, dst_reg, src_reg); .ensure_symbol_on_stack(&mut self.buf, src);
let (offset, _) = self.storage_manager.stack_offset_and_size(src);
ASM::add_reg64_reg64_imm32(&mut self.buf, dst_reg, CC::BASE_PTR_REG, offset);
} }
fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) { fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) {
@ -870,6 +1087,43 @@ impl<
.load_field_at_index(sym, structure, index, field_layouts); .load_field_at_index(sym, structure, index, field_layouts);
} }
fn load_union_at_index(
&mut self,
sym: &Symbol,
structure: &Symbol,
tag_id: TagIdIntType,
index: u64,
union_layout: &UnionLayout<'a>,
) {
match union_layout {
UnionLayout::NonRecursive(tag_layouts) | UnionLayout::Recursive(tag_layouts) => {
self.storage_manager.load_field_at_index(
sym,
structure,
index,
tag_layouts[tag_id as usize],
);
}
x => todo!("loading from union type: {:?}", x),
}
}
fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>) {
self.storage_manager
.load_union_tag_id(&mut self.buf, sym, structure, union_layout);
}
fn tag(
&mut self,
sym: &Symbol,
fields: &'a [Symbol],
union_layout: &UnionLayout<'a>,
tag_id: TagIdIntType,
) {
self.storage_manager
.create_union(&mut self.buf, sym, union_layout, fields, tag_id)
}
fn load_literal(&mut self, sym: &Symbol, layout: &Layout<'a>, lit: &Literal<'a>) { fn load_literal(&mut self, sym: &Symbol, layout: &Layout<'a>, lit: &Literal<'a>) {
match (lit, layout) { match (lit, layout) {
( (
@ -993,22 +1247,40 @@ impl<
} }
} }
#[macro_export]
macro_rules! sign_extended_int_builtins {
() => {
Builtin::Int(IntWidth::I8 | IntWidth::I16 | IntWidth::I32 | IntWidth::I64 | IntWidth::I128)
};
}
#[macro_export]
macro_rules! zero_extended_int_builtins {
() => {
Builtin::Int(IntWidth::U8 | IntWidth::U16 | IntWidth::U32 | IntWidth::U64 | IntWidth::U128)
};
}
#[macro_export]
macro_rules! single_register_int_builtins {
() => {
Builtin::Int(
IntWidth::I8
| IntWidth::I16
| IntWidth::I32
| IntWidth::I64
| IntWidth::U8
| IntWidth::U16
| IntWidth::U32
| IntWidth::U64,
)
};
}
#[macro_export] #[macro_export]
macro_rules! single_register_integers { macro_rules! single_register_integers {
() => { () => {
Layout::Builtin( Layout::Builtin(Builtin::Bool | single_register_int_builtins!()) | Layout::RecursivePointer
Builtin::Bool
| Builtin::Int(
IntWidth::I8
| IntWidth::I16
| IntWidth::I32
| IntWidth::I64
| IntWidth::U8
| IntWidth::U16
| IntWidth::U32
| IntWidth::U64,
),
) | Layout::RecursivePointer
}; };
} }

View file

@ -1,6 +1,7 @@
use crate::{ use crate::{
generic64::{Assembler, CallConv, RegTrait}, generic64::{Assembler, CallConv, RegTrait},
single_register_floats, single_register_integers, single_register_layouts, Env, sign_extended_int_builtins, single_register_floats, single_register_int_builtins,
single_register_integers, single_register_layouts, Env,
}; };
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
@ -9,7 +10,7 @@ use roc_error_macros::internal_error;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::{ use roc_mono::{
ir::{JoinPointId, Param}, ir::{JoinPointId, Param},
layout::{Builtin, Layout}, layout::{Builtin, Layout, TagIdIntType, UnionLayout},
}; };
use roc_target::TargetInfo; use roc_target::TargetInfo;
use std::cmp::max; use std::cmp::max;
@ -48,6 +49,9 @@ enum StackStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
base_offset: i32, base_offset: i32,
// Size on the stack in bytes. // Size on the stack in bytes.
size: u32, size: u32,
// Whether or not the data is need to be sign extended on load.
// If not, it must be zero extended.
sign_extend: bool,
}, },
/// Complex data (lists, unions, structs, str) stored on the stack. /// Complex data (lists, unions, structs, str) stored on the stack.
/// Note, this is also used for referencing a value within a struct/union. /// Note, this is also used for referencing a value within a struct/union.
@ -72,6 +76,7 @@ enum Storage<GeneralReg: RegTrait, FloatReg: RegTrait> {
NoData, NoData,
} }
#[derive(Clone)]
pub struct StorageManager< pub struct StorageManager<
'a, 'a,
GeneralReg: RegTrait, GeneralReg: RegTrait,
@ -177,6 +182,10 @@ impl<
self.fn_call_stack_size = 0; self.fn_call_stack_size = 0;
} }
pub fn target_info(&self) -> TargetInfo {
self.target_info
}
pub fn stack_size(&self) -> u32 { pub fn stack_size(&self) -> u32 {
self.stack_size self.stack_size
} }
@ -323,20 +332,22 @@ impl<
); );
reg reg
} }
Stack(ReferencedPrimitive { base_offset, size }) Stack(ReferencedPrimitive {
if base_offset % 8 == 0 && size == 8 => base_offset,
{ size,
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack. sign_extend,
}) => {
let reg = self.get_general_reg(buf); let reg = self.get_general_reg(buf);
ASM::mov_reg64_base32(buf, reg, base_offset); if sign_extend {
ASM::movsx_reg64_base32(buf, reg, base_offset, size as u8);
} else {
ASM::movzx_reg64_base32(buf, reg, base_offset, size as u8);
}
self.general_used_regs.push((reg, *sym)); self.general_used_regs.push((reg, *sym));
self.symbol_storage_map.insert(*sym, Reg(General(reg))); self.symbol_storage_map.insert(*sym, Reg(General(reg)));
self.free_reference(sym); self.free_reference(sym);
reg reg
} }
Stack(ReferencedPrimitive { .. }) => {
todo!("loading referenced primitives")
}
Stack(Complex { .. }) => { Stack(Complex { .. }) => {
internal_error!("Cannot load large values into general registers: {}", sym) internal_error!("Cannot load large values into general registers: {}", sym)
} }
@ -385,9 +396,9 @@ impl<
); );
reg reg
} }
Stack(ReferencedPrimitive { base_offset, size }) Stack(ReferencedPrimitive {
if base_offset % 8 == 0 && size == 8 => base_offset, size, ..
{ }) if base_offset % 8 == 0 && size == 8 => {
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack. // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
let reg = self.get_float_reg(buf); let reg = self.get_float_reg(buf);
ASM::mov_freg64_base32(buf, reg, base_offset); ASM::mov_freg64_base32(buf, reg, base_offset);
@ -444,9 +455,9 @@ impl<
debug_assert_eq!(base_offset % 8, 0); debug_assert_eq!(base_offset % 8, 0);
ASM::mov_reg64_base32(buf, reg, *base_offset); ASM::mov_reg64_base32(buf, reg, *base_offset);
} }
Stack(ReferencedPrimitive { base_offset, size }) Stack(ReferencedPrimitive {
if base_offset % 8 == 0 && *size == 8 => base_offset, size, ..
{ }) if base_offset % 8 == 0 && *size == 8 => {
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack. // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
ASM::mov_reg64_base32(buf, reg, *base_offset); ASM::mov_reg64_base32(buf, reg, *base_offset);
} }
@ -493,9 +504,9 @@ impl<
debug_assert_eq!(base_offset % 8, 0); debug_assert_eq!(base_offset % 8, 0);
ASM::mov_freg64_base32(buf, reg, *base_offset); ASM::mov_freg64_base32(buf, reg, *base_offset);
} }
Stack(ReferencedPrimitive { base_offset, size }) Stack(ReferencedPrimitive {
if base_offset % 8 == 0 && *size == 8 => base_offset, size, ..
{ }) if base_offset % 8 == 0 && *size == 8 => {
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack. // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
ASM::mov_freg64_base32(buf, reg, *base_offset); ASM::mov_freg64_base32(buf, reg, *base_offset);
} }
@ -522,11 +533,7 @@ impl<
) { ) {
debug_assert!(index < field_layouts.len() as u64); debug_assert!(index < field_layouts.len() as u64);
// This must be removed and reinserted for ownership and mutability reasons. // This must be removed and reinserted for ownership and mutability reasons.
let owned_data = if let Some(owned_data) = self.allocation_map.remove(structure) { let owned_data = self.remove_allocation_for_sym(structure);
owned_data
} else {
internal_error!("Unknown symbol: {}", structure);
};
self.allocation_map self.allocation_map
.insert(*structure, Rc::clone(&owned_data)); .insert(*structure, Rc::clone(&owned_data));
match self.get_storage_for_sym(structure) { match self.get_storage_for_sym(structure) {
@ -538,15 +545,19 @@ impl<
data_offset += field_size as i32; data_offset += field_size as i32;
} }
debug_assert!(data_offset < base_offset + size as i32); debug_assert!(data_offset < base_offset + size as i32);
self.allocation_map.insert(*sym, owned_data);
let layout = field_layouts[index as usize]; let layout = field_layouts[index as usize];
let size = layout.stack_size(self.target_info); let size = layout.stack_size(self.target_info);
self.allocation_map.insert(*sym, owned_data);
self.symbol_storage_map.insert( self.symbol_storage_map.insert(
*sym, *sym,
Stack(if is_primitive(&layout) { Stack(if is_primitive(&layout) {
ReferencedPrimitive { ReferencedPrimitive {
base_offset: data_offset, base_offset: data_offset,
size, size,
sign_extend: matches!(
layout,
Layout::Builtin(sign_extended_int_builtins!())
),
} }
} else { } else {
Complex { Complex {
@ -565,6 +576,57 @@ impl<
} }
} }
pub fn load_union_tag_id(
&mut self,
_buf: &mut Vec<'a, u8>,
sym: &Symbol,
structure: &Symbol,
union_layout: &UnionLayout<'a>,
) {
// This must be removed and reinserted for ownership and mutability reasons.
let owned_data = self.remove_allocation_for_sym(structure);
self.allocation_map
.insert(*structure, Rc::clone(&owned_data));
match union_layout {
UnionLayout::NonRecursive(_) => {
let (union_offset, _) = self.stack_offset_and_size(structure);
let (data_size, data_alignment) =
union_layout.data_size_and_alignment(self.target_info);
let id_offset = data_size - data_alignment;
let id_builtin = union_layout.tag_id_builtin();
let size = id_builtin.stack_size(self.target_info);
self.allocation_map.insert(*sym, owned_data);
self.symbol_storage_map.insert(
*sym,
Stack(ReferencedPrimitive {
base_offset: union_offset + id_offset as i32,
size,
sign_extend: matches!(id_builtin, sign_extended_int_builtins!()),
}),
);
}
x => todo!("getting tag id of union with layout ({:?})", x),
}
}
// Loads the dst to be the later 64 bits of a list (its length).
pub fn list_len(&mut self, _buf: &mut Vec<'a, u8>, dst: &Symbol, list: &Symbol) {
let owned_data = self.remove_allocation_for_sym(list);
self.allocation_map.insert(*list, Rc::clone(&owned_data));
self.allocation_map.insert(*dst, owned_data);
let (list_offset, _) = self.stack_offset_and_size(list);
self.symbol_storage_map.insert(
*dst,
Stack(ReferencedPrimitive {
base_offset: list_offset + 8,
size: 8,
sign_extend: false,
}),
);
}
/// Creates a struct on the stack, moving the data in fields into the struct. /// Creates a struct on the stack, moving the data in fields into the struct.
pub fn create_struct( pub fn create_struct(
&mut self, &mut self,
@ -594,11 +656,66 @@ impl<
} }
} }
/// Creates a union on the stack, moving the data in fields into the union and tagging it.
pub fn create_union(
&mut self,
buf: &mut Vec<'a, u8>,
sym: &Symbol,
union_layout: &UnionLayout<'a>,
fields: &'a [Symbol],
tag_id: TagIdIntType,
) {
match union_layout {
UnionLayout::NonRecursive(field_layouts) => {
let (data_size, data_alignment) =
union_layout.data_size_and_alignment(self.target_info);
let id_offset = data_size - data_alignment;
if data_alignment < 8 || data_alignment % 8 != 0 {
todo!("small/unaligned tagging");
}
let base_offset = self.claim_stack_area(sym, data_size);
let mut current_offset = base_offset;
for (field, field_layout) in
fields.iter().zip(field_layouts[tag_id as usize].iter())
{
self.copy_symbol_to_stack_offset(buf, current_offset, field, field_layout);
let field_size = field_layout.stack_size(self.target_info);
current_offset += field_size as i32;
}
self.with_tmp_general_reg(buf, |_symbol_storage, buf, reg| {
ASM::mov_reg64_imm64(buf, reg, tag_id as i64);
debug_assert!((base_offset + id_offset as i32) % 8 == 0);
ASM::mov_base32_reg64(buf, base_offset + id_offset as i32, reg);
});
}
x => todo!("creating unions with layout: {:?}", x),
}
}
/// Copies a complex symbol on the stack to the arg pointer.
pub fn copy_symbol_to_arg_pointer(
&mut self,
buf: &mut Vec<'a, u8>,
sym: &Symbol,
_layout: &Layout<'a>,
) {
let ret_reg = self.load_to_general_reg(buf, &Symbol::RET_POINTER);
let (base_offset, size) = self.stack_offset_and_size(sym);
debug_assert!(base_offset % 8 == 0);
debug_assert!(size % 8 == 0);
self.with_tmp_general_reg(buf, |_storage_manager, buf, tmp_reg| {
for i in (0..size as i32).step_by(8) {
ASM::mov_reg64_base32(buf, tmp_reg, base_offset + i);
ASM::mov_mem64_offset32_reg64(buf, ret_reg, i, tmp_reg);
}
});
}
/// Copies a symbol to the specified stack offset. This is used for things like filling structs. /// Copies a symbol to the specified stack offset. This is used for things like filling structs.
/// The offset is not guarenteed to be perfectly aligned, it follows Roc's alignment plan. /// The offset is not guarenteed to be perfectly aligned, it follows Roc's alignment plan.
/// This means that, for example 2 I32s might be back to back on the stack. /// This means that, for example 2 I32s might be back to back on the stack.
/// Always interact with the stack using aligned 64bit movement. /// Always interact with the stack using aligned 64bit movement.
fn copy_symbol_to_stack_offset( pub fn copy_symbol_to_stack_offset(
&mut self, &mut self,
buf: &mut Vec<'a, u8>, buf: &mut Vec<'a, u8>,
to_offset: i32, to_offset: i32,
@ -616,32 +733,33 @@ impl<
let reg = self.load_to_float_reg(buf, sym); let reg = self.load_to_float_reg(buf, sym);
ASM::mov_base32_freg64(buf, to_offset, reg); ASM::mov_base32_freg64(buf, to_offset, reg);
} }
// Layout::Struct(_) if layout.safe_to_memcpy() => { Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
// // self.storage_manager.with_tmp_float_reg(&mut self.buf, |buf, storage, ) let (from_offset, _) = self.stack_offset_and_size(sym);
// // if let Some(SymbolStorage::Base { self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
// // offset: from_offset, ASM::mov_reg64_base32(buf, reg, from_offset);
// // size, ASM::mov_base32_reg64(buf, to_offset, reg);
// // .. ASM::mov_reg64_base32(buf, reg, from_offset + 8);
// // }) = self.symbol_storage_map.get(sym) ASM::mov_base32_reg64(buf, to_offset + 8, reg);
// // { });
// // debug_assert_eq!( }
// // *size, _ if layout.stack_size(self.target_info) == 0 => {}
// // layout.stack_size(self.target_info), _ if layout.safe_to_memcpy() && layout.stack_size(self.target_info) > 8 => {
// // "expected struct to have same size as data being stored in it" let (from_offset, size) = self.stack_offset_and_size(sym);
// // ); debug_assert!(from_offset % 8 == 0);
// // for i in 0..layout.stack_size(self.target_info) as i32 { debug_assert!(size % 8 == 0);
// // ASM::mov_reg64_base32(&mut self.buf, tmp_reg, from_offset + i); debug_assert_eq!(size, layout.stack_size(self.target_info));
// // ASM::mov_base32_reg64(&mut self.buf, to_offset + i, tmp_reg); self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
// // } for i in (0..size as i32).step_by(8) {
// todo!() ASM::mov_reg64_base32(buf, reg, from_offset + i);
// } else { ASM::mov_base32_reg64(buf, to_offset + i, reg);
// internal_error!("unknown struct: {:?}", sym); }
// } });
// } }
x => todo!("copying data to the stack with layout, {:?}", x), x => todo!("copying data to the stack with layout, {:?}", x),
} }
} }
#[allow(dead_code)]
/// Ensures that a register is free. If it is not free, data will be moved to make it free. /// Ensures that a register is free. If it is not free, data will be moved to make it free.
fn ensure_reg_free( fn ensure_reg_free(
&mut self, &mut self,
@ -690,6 +808,58 @@ impl<
} }
} }
pub fn ensure_symbol_on_stack(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) {
match self.remove_storage_for_sym(sym) {
Reg(reg_storage) => {
let base_offset = self.claim_stack_size(8);
match reg_storage {
General(reg) => ASM::mov_base32_reg64(buf, base_offset, reg),
Float(reg) => ASM::mov_base32_freg64(buf, base_offset, reg),
}
self.symbol_storage_map.insert(
*sym,
Stack(Primitive {
base_offset,
reg: Some(reg_storage),
}),
);
}
x => {
self.symbol_storage_map.insert(*sym, x);
}
}
}
/// Frees all symbols to the stack setuping up a clean slate.
pub fn free_all_to_stack(&mut self, buf: &mut Vec<'a, u8>) {
let mut free_list = bumpalo::vec![in self.env.arena];
for (sym, storage) in self.symbol_storage_map.iter() {
match storage {
Reg(reg_storage)
| Stack(Primitive {
reg: Some(reg_storage),
..
}) => {
free_list.push((*sym, *reg_storage));
}
_ => {}
}
}
for (sym, reg_storage) in free_list {
match reg_storage {
General(reg) => {
self.general_free_regs.push(reg);
self.general_used_regs.retain(|(r, _)| *r != reg);
}
Float(reg) => {
self.float_free_regs.push(reg);
self.float_used_regs.retain(|(r, _)| *r != reg);
}
}
self.free_to_stack(buf, &sym, reg_storage);
}
}
/// Frees `wanted_reg` which is currently owned by `sym` by making sure the value is loaded on the stack. /// Frees `wanted_reg` which is currently owned by `sym` by making sure the value is loaded on the stack.
/// Note, used and free regs are expected to be updated outside of this function. /// Note, used and free regs are expected to be updated outside of this function.
fn free_to_stack( fn free_to_stack(
@ -739,9 +909,12 @@ impl<
pub fn stack_offset_and_size(&self, sym: &Symbol) -> (i32, u32) { pub fn stack_offset_and_size(&self, sym: &Symbol) -> (i32, u32) {
match self.get_storage_for_sym(sym) { match self.get_storage_for_sym(sym) {
Stack(Primitive { base_offset, .. }) => (*base_offset, 8), Stack(Primitive { base_offset, .. }) => (*base_offset, 8),
Stack(ReferencedPrimitive { base_offset, size } | Complex { base_offset, size }) => { Stack(
(*base_offset, *size) ReferencedPrimitive {
} base_offset, size, ..
}
| Complex { base_offset, size },
) => (*base_offset, *size),
storage => { storage => {
internal_error!( internal_error!(
"Data not on the stack for sym ({}) with storage ({:?})", "Data not on the stack for sym ({}) with storage ({:?})",
@ -775,12 +948,33 @@ impl<
reg: None, reg: None,
}), }),
); );
self.allocation_map.insert(*sym, Rc::new((base_offset, 8)));
}
/// Specifies a complex is loaded at the specific base offset.
pub fn complex_stack_arg(&mut self, sym: &Symbol, base_offset: i32, size: u32) {
self.symbol_storage_map
.insert(*sym, Stack(Complex { base_offset, size }));
self.allocation_map
.insert(*sym, Rc::new((base_offset, size)));
}
/// Specifies a no data exists.
pub fn no_data_arg(&mut self, sym: &Symbol) {
self.symbol_storage_map.insert(*sym, NoData);
} }
/// Loads the arg pointer symbol to the specified general reg. /// Loads the arg pointer symbol to the specified general reg.
pub fn ret_pointer_arg(&mut self, reg: GeneralReg) { pub fn ret_pointer_arg(&mut self, reg: GeneralReg) {
self.symbol_storage_map self.symbol_storage_map
.insert(Symbol::RET_POINTER, Reg(General(reg))); .insert(Symbol::RET_POINTER, Reg(General(reg)));
self.general_free_regs.retain(|x| *x != reg);
self.general_used_regs.push((reg, Symbol::RET_POINTER));
}
/// updates the stack size to the max of its current value and the tmp size needed.
pub fn update_stack_size(&mut self, tmp_size: u32) {
self.stack_size = max(self.stack_size, tmp_size);
} }
/// updates the function call stack size to the max of its current value and the size need for this call. /// updates the function call stack size to the max of its current value and the size need for this call.
@ -794,7 +988,7 @@ impl<
/// Later jumps to the join point can overwrite the stored locations to pass parameters. /// Later jumps to the join point can overwrite the stored locations to pass parameters.
pub fn setup_joinpoint( pub fn setup_joinpoint(
&mut self, &mut self,
buf: &mut Vec<'a, u8>, _buf: &mut Vec<'a, u8>,
id: &JoinPointId, id: &JoinPointId,
params: &'a [Param<'a>], params: &'a [Param<'a>],
) { ) {
@ -812,12 +1006,19 @@ impl<
todo!("joinpoints with borrowed parameters"); todo!("joinpoints with borrowed parameters");
} }
// Claim a location for every join point parameter to be loaded at. // Claim a location for every join point parameter to be loaded at.
// Put everything on the stack for simplicity.
match layout { match layout {
single_register_integers!() => { single_register_layouts!() => {
self.claim_general_reg(buf, symbol); let base_offset = self.claim_stack_size(8);
} self.symbol_storage_map.insert(
single_register_floats!() => { *symbol,
self.claim_float_reg(buf, symbol); Stack(Primitive {
base_offset,
reg: None,
}),
);
self.allocation_map
.insert(*symbol, Rc::new((base_offset, 8)));
} }
_ => { _ => {
let stack_size = layout.stack_size(self.target_info); let stack_size = layout.stack_size(self.target_info);
@ -839,7 +1040,7 @@ impl<
&mut self, &mut self,
buf: &mut Vec<'a, u8>, buf: &mut Vec<'a, u8>,
id: &JoinPointId, id: &JoinPointId,
args: &'a [Symbol], args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
) { ) {
// TODO: remove was use here and for current_storage to deal with borrow checker. // TODO: remove was use here and for current_storage to deal with borrow checker.
@ -856,28 +1057,45 @@ impl<
continue; continue;
} }
match wanted_storage { match wanted_storage {
Reg(General(reg)) => { Reg(_) => {
// Ensure the reg is free, if not free it. internal_error!("Register storage is not allowed for jumping to joinpoint")
self.ensure_reg_free(buf, General(*reg));
// Copy the value over to the reg.
self.load_to_specified_general_reg(buf, sym, *reg)
} }
Reg(Float(reg)) => { Stack(Complex { base_offset, .. }) => {
// Ensure the reg is free, if not free it.
self.ensure_reg_free(buf, Float(*reg));
// Copy the value over to the reg.
self.load_to_specified_float_reg(buf, sym, *reg)
}
Stack(ReferencedPrimitive { base_offset, .. } | Complex { base_offset, .. }) => {
// TODO: This might be better not to call. // TODO: This might be better not to call.
// Maybe we want a more memcpy like method to directly get called here. // Maybe we want a more memcpy like method to directly get called here.
// That would also be capable of asserting the size. // That would also be capable of asserting the size.
// Maybe copy stack to stack or something. // Maybe copy stack to stack or something.
self.copy_symbol_to_stack_offset(buf, *base_offset, sym, layout); self.copy_symbol_to_stack_offset(buf, *base_offset, sym, layout);
} }
Stack(Primitive {
base_offset,
reg: None,
}) => match layout {
single_register_integers!() => {
let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg64(buf, *base_offset, reg);
}
single_register_floats!() => {
let reg = self.load_to_float_reg(buf, sym);
ASM::mov_base32_freg64(buf, *base_offset, reg);
}
_ => {
internal_error!(
"cannot load non-primitive layout ({:?}) to primitive stack location",
layout
);
}
},
NoData => {} NoData => {}
Stack(Primitive { .. }) => { Stack(Primitive { reg: Some(_), .. }) => {
internal_error!("Primitive stack storage is not allowed for jumping") internal_error!(
"primitives with register storage are not allowed for jumping to joinpoint"
)
}
Stack(ReferencedPrimitive { .. }) => {
internal_error!(
"referenced primitive stack storage is not allowed for jumping to joinpoint"
)
} }
} }
} }
@ -973,11 +1191,7 @@ impl<
/// Frees an reference and release an allocation if it is no longer used. /// Frees an reference and release an allocation if it is no longer used.
fn free_reference(&mut self, sym: &Symbol) { fn free_reference(&mut self, sym: &Symbol) {
let owned_data = if let Some(owned_data) = self.allocation_map.remove(sym) { let owned_data = self.remove_allocation_for_sym(sym);
owned_data
} else {
internal_error!("Unknown symbol: {:?}", sym);
};
if Rc::strong_count(&owned_data) == 1 { if Rc::strong_count(&owned_data) == 1 {
self.free_stack_chunk(owned_data.0, owned_data.1); self.free_stack_chunk(owned_data.0, owned_data.1);
} }
@ -1060,7 +1274,26 @@ impl<
} }
} }
/// Gets a value from storage. They index symbol must be defined. #[allow(dead_code)]
/// Gets the allocated area for a symbol. The index symbol must be defined.
fn get_allocation_for_sym(&self, sym: &Symbol) -> &Rc<(i32, u32)> {
if let Some(allocation) = self.allocation_map.get(sym) {
allocation
} else {
internal_error!("Unknown symbol: {:?}", sym);
}
}
/// Removes and returns the allocated area for a symbol. They index symbol must be defined.
fn remove_allocation_for_sym(&mut self, sym: &Symbol) -> Rc<(i32, u32)> {
if let Some(allocation) = self.allocation_map.remove(sym) {
allocation
} else {
internal_error!("Unknown symbol: {:?}", sym);
}
}
/// Gets a value from storage. The index symbol must be defined.
fn get_storage_for_sym(&self, sym: &Symbol) -> &Storage<GeneralReg, FloatReg> { fn get_storage_for_sym(&self, sym: &Symbol) -> &Storage<GeneralReg, FloatReg> {
if let Some(storage) = self.symbol_storage_map.get(sym) { if let Some(storage) = self.symbol_storage_map.get(sym) {
storage storage

View file

@ -1,6 +1,7 @@
use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait}; use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
use crate::{ use crate::{
single_register_floats, single_register_integers, single_register_layouts, Relocation, single_register_floats, single_register_int_builtins, single_register_integers,
single_register_layouts, Relocation,
}; };
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
@ -63,8 +64,11 @@ impl RegTrait for X86_64FloatReg {
} }
} }
#[derive(Copy, Clone)]
pub struct X86_64Assembler {} pub struct X86_64Assembler {}
#[derive(Copy, Clone)]
pub struct X86_64WindowsFastcall {} pub struct X86_64WindowsFastcall {}
#[derive(Copy, Clone)]
pub struct X86_64SystemV {} pub struct X86_64SystemV {}
const STACK_ALIGNMENT: u8 = 16; const STACK_ALIGNMENT: u8 = 16;
@ -215,6 +219,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
general_i += 1; general_i += 1;
} }
for (layout, sym) in args.iter() { for (layout, sym) in args.iter() {
let stack_size = layout.stack_size(TARGET_INFO);
match layout { match layout {
single_register_integers!() => { single_register_integers!() => {
if general_i < Self::GENERAL_PARAM_REGS.len() { if general_i < Self::GENERAL_PARAM_REGS.len() {
@ -247,7 +252,14 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
todo!("loading lists and strings args on the stack"); todo!("loading lists and strings args on the stack");
} }
} }
x if x.stack_size(TARGET_INFO) == 0 => {} _ if stack_size == 0 => {
storage_manager.no_data_arg(sym);
}
_ if stack_size > 16 => {
// TODO: Double check this.
storage_manager.complex_stack_arg(sym, arg_offset, stack_size);
arg_offset += stack_size as i32;
}
x => { x => {
todo!("Loading args with layout {:?}", x); todo!("Loading args with layout {:?}", x);
} }
@ -265,19 +277,28 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
X86_64Assembler, X86_64Assembler,
X86_64SystemV, X86_64SystemV,
>, >,
args: &'a [Symbol], dst: &Symbol,
args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) { ) {
let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32; let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32;
if Self::returns_via_arg_pointer(ret_layout) {
// Save space on the stack for the arg we will return.
storage_manager
.claim_stack_area(&Symbol::RET_POINTER, ret_layout.stack_size(TARGET_INFO));
todo!("claim first parama reg for the address");
}
let mut general_i = 0; let mut general_i = 0;
let mut float_i = 0; let mut float_i = 0;
if Self::returns_via_arg_pointer(ret_layout) {
// Save space on the stack for the result we will be return.
let base_offset =
storage_manager.claim_stack_area(dst, ret_layout.stack_size(TARGET_INFO));
// Set the first reg to the address base + offset.
let ret_reg = Self::GENERAL_PARAM_REGS[general_i];
general_i += 1;
X86_64Assembler::add_reg64_reg64_imm32(
buf,
ret_reg,
X86_64GeneralReg::RBP,
base_offset,
);
}
for (sym, layout) in args.iter().zip(arg_layouts.iter()) { for (sym, layout) in args.iter().zip(arg_layouts.iter()) {
match layout { match layout {
single_register_integers!() => { single_register_integers!() => {
@ -326,7 +347,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
tmp_stack_offset += 8; tmp_stack_offset += 8;
} }
} }
Layout::Builtin(Builtin::Str) => { Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
if general_i + 1 < Self::GENERAL_PARAM_REGS.len() { if general_i + 1 < Self::GENERAL_PARAM_REGS.len() {
let (base_offset, _size) = storage_manager.stack_offset_and_size(sym); let (base_offset, _size) = storage_manager.stack_offset_and_size(sym);
debug_assert_eq!(base_offset % 8, 0); debug_assert_eq!(base_offset % 8, 0);
@ -346,6 +367,19 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
} }
} }
x if x.stack_size(TARGET_INFO) == 0 => {} x if x.stack_size(TARGET_INFO) == 0 => {}
x if x.stack_size(TARGET_INFO) > 16 => {
// TODO: Double check this.
// Just copy onto the stack.
let (base_offset, size) = storage_manager.stack_offset_and_size(sym);
debug_assert_eq!(base_offset % 8, 0);
storage_manager.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
for i in (0..size as i32).step_by(8) {
X86_64Assembler::mov_reg64_base32(buf, reg, base_offset + i);
X86_64Assembler::mov_stack32_reg64(buf, tmp_stack_offset + i, reg);
}
});
tmp_stack_offset += size as i32;
}
x => { x => {
todo!("calling with arg type, {:?}", x); todo!("calling with arg type, {:?}", x);
} }
@ -381,7 +415,42 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
); );
} }
x if x.stack_size(TARGET_INFO) == 0 => {} x if x.stack_size(TARGET_INFO) == 0 => {}
x => todo!("returning complex type, {:?}", x), x if !Self::returns_via_arg_pointer(x) => {
let (base_offset, size) = storage_manager.stack_offset_and_size(sym);
debug_assert_eq!(base_offset % 8, 0);
if size <= 8 {
X86_64Assembler::mov_reg64_base32(
buf,
Self::GENERAL_RETURN_REGS[0],
base_offset,
);
} else if size <= 16 {
X86_64Assembler::mov_reg64_base32(
buf,
Self::GENERAL_RETURN_REGS[0],
base_offset,
);
X86_64Assembler::mov_reg64_base32(
buf,
Self::GENERAL_RETURN_REGS[1],
base_offset + 8,
);
} else {
internal_error!(
"types that don't return via arg pointer must be less than 16 bytes"
);
}
}
_ => {
// This is a large type returned via the arg pointer.
storage_manager.copy_symbol_to_arg_pointer(buf, sym, layout);
// Also set the return reg to the arg pointer.
storage_manager.load_to_specified_general_reg(
buf,
&Symbol::RET_POINTER,
Self::GENERAL_RETURN_REGS[0],
);
}
} }
} }
@ -407,7 +476,29 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
X86_64Assembler::mov_base32_reg64(buf, offset + 8, Self::GENERAL_RETURN_REGS[1]); X86_64Assembler::mov_base32_reg64(buf, offset + 8, Self::GENERAL_RETURN_REGS[1]);
} }
x if x.stack_size(TARGET_INFO) == 0 => {} x if x.stack_size(TARGET_INFO) == 0 => {}
x => todo!("receiving complex return type, {:?}", x), x if !Self::returns_via_arg_pointer(x) => {
let size = layout.stack_size(TARGET_INFO);
let offset = storage_manager.claim_stack_area(sym, size);
if size <= 8 {
X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]);
} else if size <= 16 {
X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]);
X86_64Assembler::mov_base32_reg64(
buf,
offset + 8,
Self::GENERAL_RETURN_REGS[1],
);
} else {
internal_error!(
"types that don't return via arg pointer must be less than 16 bytes"
);
}
}
_ => {
// This should have been recieved via an arg pointer.
// That means the value is already loaded onto the stack area we allocated before the call.
// Nothing to do.
}
} }
} }
} }
@ -612,15 +703,15 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
X86_64Assembler, X86_64Assembler,
X86_64WindowsFastcall, X86_64WindowsFastcall,
>, >,
args: &'a [Symbol], dst: &Symbol,
args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) { ) {
let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32; let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32;
if Self::returns_via_arg_pointer(ret_layout) { if Self::returns_via_arg_pointer(ret_layout) {
// Save space on the stack for the arg we will return. // Save space on the stack for the arg we will return.
storage_manager storage_manager.claim_stack_area(dst, ret_layout.stack_size(TARGET_INFO));
.claim_stack_area(&Symbol::RET_POINTER, ret_layout.stack_size(TARGET_INFO));
todo!("claim first parama reg for the address"); todo!("claim first parama reg for the address");
} }
for (i, (sym, layout)) in args.iter().zip(arg_layouts.iter()).enumerate() { for (i, (sym, layout)) in args.iter().zip(arg_layouts.iter()).enumerate() {
@ -669,7 +760,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
tmp_stack_offset += 8; tmp_stack_offset += 8;
} }
} }
Layout::Builtin(Builtin::Str) => { Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
// I think this just needs to be passed on the stack, so not a huge deal. // I think this just needs to be passed on the stack, so not a huge deal.
todo!("Passing str args with Windows fast call"); todo!("Passing str args with Windows fast call");
} }
@ -988,6 +1079,56 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
mov_base64_offset32_reg64(buf, X86_64GeneralReg::RBP, offset, src) mov_base64_offset32_reg64(buf, X86_64GeneralReg::RBP, offset, src)
} }
#[inline(always)]
fn mov_reg64_mem64_offset32(
buf: &mut Vec<'_, u8>,
dst: X86_64GeneralReg,
src: X86_64GeneralReg,
offset: i32,
) {
mov_reg64_base64_offset32(buf, dst, src, offset)
}
#[inline(always)]
fn mov_mem64_offset32_reg64(
buf: &mut Vec<'_, u8>,
dst: X86_64GeneralReg,
offset: i32,
src: X86_64GeneralReg,
) {
mov_base64_offset32_reg64(buf, dst, offset, src)
}
#[inline(always)]
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) {
debug_assert!(size <= 8);
if size == 8 {
Self::mov_reg64_base32(buf, dst, offset);
} else if size == 4 {
todo!("sign extending 4 byte values");
} else if size == 2 {
todo!("sign extending 2 byte values");
} else if size == 1 {
todo!("sign extending 1 byte values");
} else {
internal_error!("Invalid size for sign extension: {}", size);
}
}
#[inline(always)]
fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) {
debug_assert!(size <= 8);
if size == 8 {
Self::mov_reg64_base32(buf, dst, offset);
} else if size == 4 {
todo!("zero extending 4 byte values");
} else if size == 2 {
todo!("zero extending 2 byte values");
} else if size == 1 {
movzx_reg64_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset);
} else {
internal_error!("Invalid size for zero extension: {}", size);
}
}
#[inline(always)] #[inline(always)]
fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) { fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) {
movsd_freg64_base64_offset32(buf, dst, X86_64GeneralReg::RSP, offset) movsd_freg64_base64_offset32(buf, dst, X86_64GeneralReg::RSP, offset)
@ -1091,6 +1232,17 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
cvtsi2sd_freg64_reg64(buf, dst, src); cvtsi2sd_freg64_reg64(buf, dst, src);
} }
#[inline(always)]
fn lte_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: X86_64GeneralReg,
src1: X86_64GeneralReg,
src2: X86_64GeneralReg,
) {
cmp_reg64_reg64(buf, src1, src2);
setle_reg64(buf, dst);
}
#[inline(always)] #[inline(always)]
fn gte_reg64_reg64_reg64( fn gte_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
@ -1378,6 +1530,27 @@ fn mov_reg64_base64_offset32(
buf.extend(&offset.to_le_bytes()); buf.extend(&offset.to_le_bytes());
} }
/// `MOVZX r64,r/m8` -> Move r/m8 with zero extention to r64, where m8 references a base + offset.
#[inline(always)]
fn movzx_reg64_base8_offset32(
buf: &mut Vec<'_, u8>,
dst: X86_64GeneralReg,
base: X86_64GeneralReg,
offset: i32,
) {
let rex = add_rm_extension(base, REX_W);
let rex = add_reg_extension(dst, rex);
let dst_mod = (dst as u8 % 8) << 3;
let base_mod = base as u8 % 8;
buf.reserve(9);
buf.extend(&[rex, 0x0F, 0xB6, 0x80 + dst_mod + base_mod]);
// Using RSP or R12 requires a secondary index byte.
if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 {
buf.push(0x24);
}
buf.extend(&offset.to_le_bytes());
}
/// `MOVSD xmm1,xmm2` -> Move scalar double-precision floating-point value from xmm2 to xmm1 register. /// `MOVSD xmm1,xmm2` -> Move scalar double-precision floating-point value from xmm2 to xmm1 register.
#[inline(always)] #[inline(always)]
fn movsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { fn movsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
@ -1429,7 +1602,7 @@ fn movsd_freg64_rip_offset32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset:
buf.extend(&offset.to_le_bytes()); buf.extend(&offset.to_le_bytes());
} }
/// `MOVSD r/m64,xmm1` -> Move xmm1 to r/m64. where m64 references the base pionter. /// `MOVSD r/m64,xmm1` -> Move xmm1 to r/m64. where m64 references the base pointer.
#[inline(always)] #[inline(always)]
fn movsd_base64_offset32_freg64( fn movsd_base64_offset32_freg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
@ -1452,7 +1625,7 @@ fn movsd_base64_offset32_freg64(
buf.extend(&offset.to_le_bytes()); buf.extend(&offset.to_le_bytes());
} }
/// `MOVSD xmm1,r/m64` -> Move r/m64 to xmm1. where m64 references the base pionter. /// `MOVSD xmm1,r/m64` -> Move r/m64 to xmm1. where m64 references the base pointer.
#[inline(always)] #[inline(always)]
fn movsd_freg64_base64_offset32( fn movsd_freg64_base64_offset32(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
@ -1585,6 +1758,12 @@ fn setl_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
set_reg64_help(0x9c, buf, reg); set_reg64_help(0x9c, buf, reg);
} }
/// `SETLE r/m64` -> Set byte if less or equal (ZF=1 or SF≠ OF).
#[inline(always)]
fn setle_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
set_reg64_help(0x9e, buf, reg);
}
/// `SETGE r/m64` -> Set byte if greater or equal (SF=OF). /// `SETGE r/m64` -> Set byte if greater or equal (SF=OF).
#[inline(always)] #[inline(always)]
fn setge_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) { fn setge_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
@ -2081,6 +2260,35 @@ mod tests {
} }
} }
#[test]
fn test_movzx_reg64_base8_offset32() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for ((dst, src, offset), expected) in &[
(
(X86_64GeneralReg::RAX, X86_64GeneralReg::RBP, TEST_I32),
vec![0x48, 0x0F, 0xB6, 0x85],
),
(
(X86_64GeneralReg::R15, X86_64GeneralReg::RBP, TEST_I32),
vec![0x4C, 0x0F, 0xB6, 0xBD],
),
(
(X86_64GeneralReg::RAX, X86_64GeneralReg::RSP, TEST_I32),
vec![0x48, 0x0F, 0xB6, 0x84, 0x24],
),
(
(X86_64GeneralReg::R15, X86_64GeneralReg::RSP, TEST_I32),
vec![0x4C, 0x0F, 0xB6, 0xBC, 0x24],
),
] {
buf.clear();
movzx_reg64_base8_offset32(&mut buf, *dst, *src, *offset);
assert_eq!(expected, &buf[..expected.len()]);
assert_eq!(TEST_I32.to_le_bytes(), &buf[expected.len()..]);
}
}
#[test] #[test]
fn test_mov_reg64_stack32() { fn test_mov_reg64_stack32() {
let arena = bumpalo::Bump::new(); let arena = bumpalo::Bump::new();

View file

@ -14,7 +14,7 @@ use roc_mono::ir::{
BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout, BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout,
SelfRecursive, Stmt, SelfRecursive, Stmt,
}; };
use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds}; use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds, TagIdIntType, UnionLayout};
mod generic64; mod generic64;
mod object_builder; mod object_builder;
@ -233,7 +233,7 @@ trait Backend<'a> {
fn build_jump( fn build_jump(
&mut self, &mut self,
id: &JoinPointId, id: &JoinPointId,
args: &'a [Symbol], args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
); );
@ -277,13 +277,7 @@ trait Backend<'a> {
self.load_literal_symbols(arguments); self.load_literal_symbols(arguments);
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout) self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
} else { } else {
self.build_inline_builtin( self.build_builtin(sym, *func_sym, arguments, arg_layouts, ret_layout)
sym,
*func_sym,
arguments,
arg_layouts,
ret_layout,
)
} }
} }
@ -321,6 +315,29 @@ trait Backend<'a> {
} => { } => {
self.load_struct_at_index(sym, structure, *index, field_layouts); self.load_struct_at_index(sym, structure, *index, field_layouts);
} }
Expr::UnionAtIndex {
structure,
tag_id,
union_layout,
index,
} => {
self.load_union_at_index(sym, structure, *tag_id, *index, union_layout);
}
Expr::GetTagId {
structure,
union_layout,
} => {
self.get_tag_id(sym, structure, union_layout);
}
Expr::Tag {
tag_layout,
tag_id,
arguments,
..
} => {
self.load_literal_symbols(arguments);
self.tag(sym, arguments, tag_layout, *tag_id);
}
x => todo!("the expression, {:?}", x), x => todo!("the expression, {:?}", x),
} }
} }
@ -501,6 +518,23 @@ trait Backend<'a> {
); );
self.build_num_to_float(sym, &args[0], &arg_layouts[0], ret_layout) self.build_num_to_float(sym, &args[0], &arg_layouts[0], ret_layout)
} }
LowLevel::NumLte => {
debug_assert_eq!(
2,
args.len(),
"NumLte: expected to have exactly two argument"
);
debug_assert_eq!(
arg_layouts[0], arg_layouts[1],
"NumLte: expected all arguments of to have the same layout"
);
debug_assert_eq!(
Layout::Builtin(Builtin::Bool),
*ret_layout,
"NumLte: expected to have return layout of type Bool"
);
self.build_num_lte(sym, &args[0], &args[1], &arg_layouts[0])
}
LowLevel::NumGte => { LowLevel::NumGte => {
debug_assert_eq!( debug_assert_eq!(
2, 2,
@ -525,6 +559,30 @@ trait Backend<'a> {
arg_layouts, arg_layouts,
ret_layout, ret_layout,
), ),
LowLevel::ListLen => {
debug_assert_eq!(
1,
args.len(),
"ListLen: expected to have exactly one argument"
);
self.build_list_len(sym, &args[0])
}
LowLevel::ListGetUnsafe => {
debug_assert_eq!(
2,
args.len(),
"ListGetUnsafe: expected to have exactly two arguments"
);
self.build_list_get_unsafe(sym, &args[0], &args[1], ret_layout)
}
LowLevel::ListReplaceUnsafe => {
debug_assert_eq!(
3,
args.len(),
"ListReplaceUnsafe: expected to have exactly three arguments"
);
self.build_list_replace_unsafe(sym, args, arg_layouts, ret_layout)
}
LowLevel::StrConcat => self.build_fn_call( LowLevel::StrConcat => self.build_fn_call(
sym, sym,
bitcode::STR_CONCAT.to_string(), bitcode::STR_CONCAT.to_string(),
@ -558,8 +616,9 @@ trait Backend<'a> {
} }
} }
// inlines simple builtin functions that do not map directly to a low level /// Builds a builtin functions that do not map directly to a low level
fn build_inline_builtin( /// If the builtin is simple enough, it will be inlined.
fn build_builtin(
&mut self, &mut self,
sym: &Symbol, sym: &Symbol,
func_sym: Symbol, func_sym: Symbol,
@ -585,6 +644,14 @@ trait Backend<'a> {
self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]); self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]);
self.free_symbol(&Symbol::DEV_TMP) self.free_symbol(&Symbol::DEV_TMP)
} }
Symbol::LIST_GET | Symbol::LIST_SET | Symbol::LIST_REPLACE => {
// TODO: This is probably simple enough to be worth inlining.
let layout_id = LayoutIds::default().get(func_sym, ret_layout);
let fn_name = self.symbol_to_string(func_sym, layout_id);
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(args);
self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
}
_ => todo!("the function, {:?}", func_sym), _ => todo!("the function, {:?}", func_sym),
} }
} }
@ -595,7 +662,7 @@ trait Backend<'a> {
&mut self, &mut self,
dst: &Symbol, dst: &Symbol,
fn_name: String, fn_name: String,
args: &'a [Symbol], args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
); );
@ -633,6 +700,15 @@ trait Backend<'a> {
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
); );
/// build_num_lte stores the result of `src1 <= src2` into dst.
fn build_num_lte(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
arg_layout: &Layout<'a>,
);
/// build_num_gte stores the result of `src1 >= src2` into dst. /// build_num_gte stores the result of `src1 >= src2` into dst.
fn build_num_gte( fn build_num_gte(
&mut self, &mut self,
@ -642,6 +718,27 @@ trait Backend<'a> {
arg_layout: &Layout<'a>, arg_layout: &Layout<'a>,
); );
/// build_list_len returns the length of a list.
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol);
/// build_list_get_unsafe loads the element from the list at the index.
fn build_list_get_unsafe(
&mut self,
dst: &Symbol,
list: &Symbol,
index: &Symbol,
ret_layout: &Layout<'a>,
);
/// build_list_replace_unsafe returns the old element and new list with the list having the new element inserted.
fn build_list_replace_unsafe(
&mut self,
dst: &Symbol,
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
);
/// build_refcount_getptr loads the pointer to the reference count of src into dst. /// build_refcount_getptr loads the pointer to the reference count of src into dst.
fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol); fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol);
@ -677,6 +774,28 @@ trait Backend<'a> {
field_layouts: &'a [Layout<'a>], field_layouts: &'a [Layout<'a>],
); );
/// load_union_at_index loads into `sym` the value at `index` for `tag_id`.
fn load_union_at_index(
&mut self,
sym: &Symbol,
structure: &Symbol,
tag_id: TagIdIntType,
index: u64,
union_layout: &UnionLayout<'a>,
);
/// get_tag_id loads the tag id from a the union.
fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>);
/// tag sets the tag for a union.
fn tag(
&mut self,
sym: &Symbol,
args: &'a [Symbol],
tag_layout: &UnionLayout<'a>,
tag_id: TagIdIntType,
);
/// return_symbol moves a symbol to the correct return location for the backend and adds a jump to the end of the function. /// return_symbol moves a symbol to the correct return location for the backend and adds a jump to the end of the function.
fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>); fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>);
@ -831,15 +950,16 @@ trait Backend<'a> {
parameters, parameters,
body: continuation, body: continuation,
remainder, remainder,
id, id: JoinPointId(sym),
.. ..
} => { } => {
join_map.insert(*id, parameters); self.set_last_seen(*sym, stmt);
join_map.insert(JoinPointId(*sym), parameters);
for param in *parameters { for param in *parameters {
self.set_last_seen(param.symbol, stmt); self.set_last_seen(param.symbol, stmt);
} }
self.scan_ast(continuation);
self.scan_ast(remainder); self.scan_ast(remainder);
self.scan_ast(continuation);
} }
Stmt::Jump(JoinPointId(sym), symbols) => { Stmt::Jump(JoinPointId(sym), symbols) => {
if let Some(parameters) = join_map.get(&JoinPointId(*sym)) { if let Some(parameters) = join_map.get(&JoinPointId(*sym)) {
@ -848,7 +968,6 @@ trait Backend<'a> {
self.set_last_seen(param.symbol, stmt); self.set_last_seen(param.symbol, stmt);
} }
} }
self.set_last_seen(*sym, stmt);
for sym in *symbols { for sym in *symbols {
self.set_last_seen(*sym, stmt); self.set_last_seen(*sym, stmt);
} }

View file

@ -267,6 +267,27 @@ fn build_object<'a, B: Backend<'a>>(
helper_names_symbols_procs.push((fn_name, section_id, proc_id, proc)); helper_names_symbols_procs.push((fn_name, section_id, proc_id, proc));
continue; continue;
} }
} else {
// The symbol isn't defined yet and will just be used by other rc procs.
let section_id = output.add_section(
output.segment_name(StandardSegment::Text).to_vec(),
format!(".text.{:x}", sym.as_u64()).as_bytes().to_vec(),
SectionKind::Text,
);
let rc_symbol = Symbol {
name: fn_name.as_bytes().to_vec(),
value: 0,
size: 0,
kind: SymbolKind::Text,
scope: SymbolScope::Linkage,
weak: false,
section: SymbolSection::Section(section_id),
flags: SymbolFlags::None,
};
let proc_id = output.add_symbol(rc_symbol);
helper_names_symbols_procs.push((fn_name, section_id, proc_id, proc));
continue;
} }
internal_error!("failed to create rc fn for symbol {:?}", sym); internal_error!("failed to create rc fn for symbol {:?}", sym);
} }

View file

@ -5,14 +5,14 @@ use crossbeam::deque::{Injector, Stealer, Worker};
use crossbeam::thread; use crossbeam::thread;
use parking_lot::Mutex; use parking_lot::Mutex;
use roc_builtins::std::StdLib; use roc_builtins::std::StdLib;
use roc_can::constraint::Constraint; use roc_can::constraint::{Constraint as ConstraintSoa, Constraints};
use roc_can::def::Declaration; use roc_can::def::Declaration;
use roc_can::module::{canonicalize_module_defs, Module}; use roc_can::module::{canonicalize_module_defs, Module};
use roc_collections::all::{default_hasher, BumpMap, MutMap, MutSet}; use roc_collections::all::{default_hasher, BumpMap, MutMap, MutSet};
use roc_constrain::module::{ use roc_constrain::module::{
constrain_imports, pre_constrain_imports, ConstrainableImports, Import, constrain_imports, constrain_module, pre_constrain_imports, ConstrainableImports,
ExposedModuleTypes, Import, SubsByModule,
}; };
use roc_constrain::module::{constrain_module, ExposedModuleTypes, SubsByModule};
use roc_module::ident::{Ident, ModuleName, QualifiedModuleName}; use roc_module::ident::{Ident, ModuleName, QualifiedModuleName};
use roc_module::symbol::{ use roc_module::symbol::{
IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, PackageQualified, IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, PackageQualified,
@ -229,6 +229,7 @@ fn start_phase<'a>(
module, module,
ident_ids, ident_ids,
module_timing, module_timing,
constraints,
constraint, constraint,
var_store, var_store,
imported_modules, imported_modules,
@ -241,6 +242,7 @@ fn start_phase<'a>(
module, module,
ident_ids, ident_ids,
module_timing, module_timing,
constraints,
constraint, constraint,
var_store, var_store,
imported_modules, imported_modules,
@ -391,7 +393,8 @@ struct ConstrainedModule {
module: Module, module: Module,
declarations: Vec<Declaration>, declarations: Vec<Declaration>,
imported_modules: MutMap<ModuleId, Region>, imported_modules: MutMap<ModuleId, Region>,
constraint: Constraint, constraints: Constraints,
constraint: ConstraintSoa,
ident_ids: IdentIds, ident_ids: IdentIds,
var_store: VarStore, var_store: VarStore,
dep_idents: MutMap<ModuleId, IdentIds>, dep_idents: MutMap<ModuleId, IdentIds>,
@ -728,7 +731,8 @@ enum BuildTask<'a> {
ident_ids: IdentIds, ident_ids: IdentIds,
imported_symbols: Vec<Import>, imported_symbols: Vec<Import>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
constraint: Constraint, constraints: Constraints,
constraint: ConstraintSoa,
var_store: VarStore, var_store: VarStore,
declarations: Vec<Declaration>, declarations: Vec<Declaration>,
dep_idents: MutMap<ModuleId, IdentIds>, dep_idents: MutMap<ModuleId, IdentIds>,
@ -3027,7 +3031,8 @@ impl<'a> BuildTask<'a> {
module: Module, module: Module,
ident_ids: IdentIds, ident_ids: IdentIds,
module_timing: ModuleTiming, module_timing: ModuleTiming,
constraint: Constraint, constraints: Constraints,
constraint: ConstraintSoa,
var_store: VarStore, var_store: VarStore,
imported_modules: MutMap<ModuleId, Region>, imported_modules: MutMap<ModuleId, Region>,
exposed_types: &mut SubsByModule, exposed_types: &mut SubsByModule,
@ -3057,6 +3062,7 @@ impl<'a> BuildTask<'a> {
module, module,
ident_ids, ident_ids,
imported_symbols, imported_symbols,
constraints,
constraint, constraint,
var_store, var_store,
declarations, declarations,
@ -3073,7 +3079,8 @@ fn run_solve<'a>(
ident_ids: IdentIds, ident_ids: IdentIds,
mut module_timing: ModuleTiming, mut module_timing: ModuleTiming,
imported_symbols: Vec<Import>, imported_symbols: Vec<Import>,
constraint: Constraint, mut constraints: Constraints,
constraint: ConstraintSoa,
mut var_store: VarStore, mut var_store: VarStore,
decls: Vec<Declaration>, decls: Vec<Declaration>,
dep_idents: MutMap<ModuleId, IdentIds>, dep_idents: MutMap<ModuleId, IdentIds>,
@ -3084,7 +3091,12 @@ fn run_solve<'a>(
// Finish constraining the module by wrapping the existing Constraint // Finish constraining the module by wrapping the existing Constraint
// in the ones we just computed. We can do this off the main thread. // in the ones we just computed. We can do this off the main thread.
let constraint = constrain_imports(imported_symbols, constraint, &mut var_store); let constraint = constrain_imports(
&mut constraints,
imported_symbols,
constraint,
&mut var_store,
);
let constrain_end = SystemTime::now(); let constrain_end = SystemTime::now();
@ -3097,12 +3109,11 @@ fn run_solve<'a>(
.. ..
} = module; } = module;
if false { // TODO
debug_assert!(constraint.validate(), "{:?}", &constraint); // if false { debug_assert!(constraint.validate(), "{:?}", &constraint); }
}
let (solved_subs, solved_env, problems) = let (solved_subs, solved_env, problems) =
roc_solve::module::run_solve(rigid_variables, constraint, var_store); roc_solve::module::run_solve(&constraints, constraint, rigid_variables, var_store);
let exposed_vars_by_symbol: Vec<_> = solved_env let exposed_vars_by_symbol: Vec<_> = solved_env
.vars_by_symbol() .vars_by_symbol()
@ -3247,7 +3258,9 @@ fn canonicalize_and_constrain<'a>(
} }
}; };
let constraint = constrain_module(&module_output.declarations, module_id); let mut constraints = Constraints::new();
let constraint =
constrain_module(&mut constraints, &module_output.declarations, module_id);
let module = Module { let module = Module {
module_id, module_id,
@ -3263,6 +3276,7 @@ fn canonicalize_and_constrain<'a>(
declarations: module_output.declarations, declarations: module_output.declarations,
imported_modules, imported_modules,
var_store, var_store,
constraints,
constraint, constraint,
ident_ids: module_output.ident_ids, ident_ids: module_output.ident_ids,
dep_idents, dep_idents,
@ -3745,6 +3759,7 @@ fn run_task<'a>(
module, module,
module_timing, module_timing,
imported_symbols, imported_symbols,
constraints,
constraint, constraint,
var_store, var_store,
ident_ids, ident_ids,
@ -3756,6 +3771,7 @@ fn run_task<'a>(
ident_ids, ident_ids,
module_timing, module_timing,
imported_symbols, imported_symbols,
constraints,
constraint, constraint,
var_store, var_store,
declarations, declarations,

View file

@ -47,7 +47,7 @@ mod test_load {
let src_lines: Vec<&str> = src.split('\n').collect(); let src_lines: Vec<&str> = src.split('\n').collect();
let lines = LineInfo::new(src); let lines = LineInfo::new(src);
let alloc = RocDocAllocator::new(&src_lines, home, &interns); let alloc = RocDocAllocator::new(&src_lines, home, interns);
let reports = problems let reports = problems
.into_iter() .into_iter()
.map(|problem| can_problem(&alloc, &lines, filename.clone(), problem).pretty(&alloc)); .map(|problem| can_problem(&alloc, &lines, filename.clone(), problem).pretty(&alloc));

View file

@ -884,6 +884,10 @@ define_builtins! {
// used in dev backend // used in dev backend
26 DEV_TMP: "#dev_tmp" 26 DEV_TMP: "#dev_tmp"
27 DEV_TMP2: "#dev_tmp2"
28 DEV_TMP3: "#dev_tmp3"
29 DEV_TMP4: "#dev_tmp4"
30 DEV_TMP5: "#dev_tmp5"
} }
1 NUM: "Num" => { 1 NUM: "Num" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias 0 NUM_NUM: "Num" imported // the Num.Num type alias

View file

@ -1,5 +1,5 @@
use crate::ir::DestructType; use crate::ir::DestructType;
use roc_collections::all::Index; use roc_collections::all::HumanIndex;
use roc_exhaustive::{ use roc_exhaustive::{
is_useful, Context, Ctor, Error, Guard, Literal, Pattern, RenderAs, TagId, Union, is_useful, Context, Ctor, Error, Guard, Literal, Pattern, RenderAs, TagId, Union,
}; };
@ -177,7 +177,10 @@ fn to_nonredundant_rows(
vec![Pattern::Ctor( vec![Pattern::Ctor(
union, union,
tag_id, tag_id,
vec![simplify(&loc_pat.value), guard_pattern], // NB: ordering the guard pattern first seems to be better at catching
// non-exhaustive constructors in the second argument; see the paper to see if
// there is a way to improve this in general.
vec![guard_pattern, simplify(&loc_pat.value)],
)] )]
} else { } else {
vec![simplify(&loc_pat.value)] vec![simplify(&loc_pat.value)]
@ -189,7 +192,7 @@ fn to_nonredundant_rows(
return Err(Error::Redundant { return Err(Error::Redundant {
overall_region, overall_region,
branch_region: region, branch_region: region,
index: Index::zero_based(checked_rows.len()), index: HumanIndex::zero_based(checked_rows.len()),
}); });
} }
} }

View file

@ -3882,44 +3882,24 @@ pub fn with_hole<'a>(
stmt stmt
} }
Accessor { Accessor(accessor_data) => {
name, let field_var = accessor_data.field_var;
function_var, let fresh_record_symbol = env.unique_symbol();
record_var,
closure_ext_var: _,
ext_var,
field_var,
field,
} => {
// IDEA: convert accessor fromt
//
// .foo
//
// into
//
// (\r -> r.foo)
let record_symbol = env.unique_symbol();
let body = roc_can::expr::Expr::Access {
record_var,
ext_var,
field_var,
loc_expr: Box::new(Loc::at_zero(roc_can::expr::Expr::Var(record_symbol))),
field,
};
let loc_body = Loc::at_zero(body); let ClosureData {
name,
let arguments = vec![( function_type,
record_var, arguments,
Loc::at_zero(roc_can::pattern::Pattern::Identifier(record_symbol)), loc_body,
)]; ..
} = accessor_data.to_closure_data(fresh_record_symbol);
match procs.insert_anonymous( match procs.insert_anonymous(
env, env,
name, name,
function_var, function_type,
arguments, arguments,
loc_body, *loc_body,
CapturedSymbols::None, CapturedSymbols::None,
field_var, field_var,
layout_cache, layout_cache,
@ -3927,7 +3907,7 @@ pub fn with_hole<'a>(
Ok(_) => { Ok(_) => {
let raw_layout = return_on_layout_error!( let raw_layout = return_on_layout_error!(
env, env,
layout_cache.raw_from_var(env.arena, function_var, env.subs) layout_cache.raw_from_var(env.arena, function_type, env.subs)
); );
match raw_layout { match raw_layout {
@ -5445,6 +5425,18 @@ pub fn from_can<'a>(
return from_can(env, variable, cont.value, procs, layout_cache); return from_can(env, variable, cont.value, procs, layout_cache);
} }
roc_can::expr::Expr::Accessor(accessor_data) => {
let fresh_record_symbol = env.unique_symbol();
register_noncapturing_closure(
env,
procs,
layout_cache,
*symbol,
accessor_data.to_closure_data(fresh_record_symbol),
);
return from_can(env, variable, cont.value, procs, layout_cache);
}
roc_can::expr::Expr::Var(original) => { roc_can::expr::Expr::Var(original) => {
// a variable is aliased, e.g. // a variable is aliased, e.g.
// //

View file

@ -74,7 +74,7 @@ impl<'a> RawFunctionLayout<'a> {
FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)), FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)),
RecursionVar { structure, .. } => { RecursionVar { structure, .. } => {
let structure_content = env.subs.get_content_without_compacting(structure); let structure_content = env.subs.get_content_without_compacting(structure);
Self::new_help(env, structure, structure_content.clone()) Self::new_help(env, structure, *structure_content)
} }
Structure(flat_type) => Self::layout_from_flat_type(env, flat_type), Structure(flat_type) => Self::layout_from_flat_type(env, flat_type),
RangedNumber(typ, _) => Self::from_var(env, typ), RangedNumber(typ, _) => Self::from_var(env, typ),
@ -207,7 +207,7 @@ impl<'a> RawFunctionLayout<'a> {
unreachable!("The initial variable of a signature cannot be seen already") unreachable!("The initial variable of a signature cannot be seen already")
} else { } else {
let content = env.subs.get_content_without_compacting(var); let content = env.subs.get_content_without_compacting(var);
Self::new_help(env, var, content.clone()) Self::new_help(env, var, *content)
} }
} }
} }
@ -928,7 +928,7 @@ impl<'a> Layout<'a> {
FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)), FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)),
RecursionVar { structure, .. } => { RecursionVar { structure, .. } => {
let structure_content = env.subs.get_content_without_compacting(structure); let structure_content = env.subs.get_content_without_compacting(structure);
Self::new_help(env, structure, structure_content.clone()) Self::new_help(env, structure, *structure_content)
} }
Structure(flat_type) => layout_from_flat_type(env, flat_type), Structure(flat_type) => layout_from_flat_type(env, flat_type),
@ -968,7 +968,7 @@ impl<'a> Layout<'a> {
Ok(Layout::RecursivePointer) Ok(Layout::RecursivePointer)
} else { } else {
let content = env.subs.get_content_without_compacting(var); let content = env.subs.get_content_without_compacting(var);
Self::new_help(env, var, content.clone()) Self::new_help(env, var, *content)
} }
} }

View file

@ -84,6 +84,16 @@ pub enum Problem {
def_region: Region, def_region: Region,
differing_recursion_region: Region, differing_recursion_region: Region,
}, },
InvalidExtensionType {
region: Region,
kind: ExtensionTypeKind,
},
}
#[derive(Clone, Debug, PartialEq)]
pub enum ExtensionTypeKind {
Record,
TagUnion,
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]

View file

@ -1,5 +1,5 @@
use crate::solve; use crate::solve;
use roc_can::constraint::Constraint; use roc_can::constraint::{Constraint as ConstraintSoa, Constraints};
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_module::ident::Lowercase; use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -17,8 +17,9 @@ pub struct SolvedModule {
} }
pub fn run_solve( pub fn run_solve(
constraints: &Constraints,
constraint: ConstraintSoa,
rigid_variables: MutMap<Variable, Lowercase>, rigid_variables: MutMap<Variable, Lowercase>,
constraint: Constraint,
var_store: VarStore, var_store: VarStore,
) -> (Solved<Subs>, solve::Env, Vec<solve::TypeError>) { ) -> (Solved<Subs>, solve::Env, Vec<solve::TypeError>) {
let env = solve::Env::default(); let env = solve::Env::default();
@ -34,7 +35,7 @@ pub fn run_solve(
let mut problems = Vec::new(); let mut problems = Vec::new();
// Run the solver to populate Subs. // Run the solver to populate Subs.
let (solved_subs, solved_env) = solve::run(&env, &mut problems, subs, &constraint); let (solved_subs, solved_env) = solve::run(constraints, &env, &mut problems, subs, &constraint);
(solved_subs, solved_env, problems) (solved_subs, solved_env, problems)
} }

File diff suppressed because it is too large Load diff

View file

@ -5509,4 +5509,68 @@ mod solve_expr {
r#"Id [ A, B, C { a : Str }e ] -> Str"#, r#"Id [ A, B, C { a : Str }e ] -> Str"#,
) )
} }
#[test]
fn lambda_set_within_alias_is_quantified() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [ effectAlways ] to "./platform"
Effect a : [ @Effect ({} -> a) ]
effectAlways : a -> Effect a
effectAlways = \x ->
inner = \{} -> x
@Effect inner
"#
),
r#"a -> Effect a"#,
)
}
#[test]
fn generalized_accessor_function_applied() {
infer_eq_without_problem(
indoc!(
r#"
returnFoo = .foo
returnFoo { foo: "foo" }
"#
),
"Str",
)
}
#[test]
fn record_extension_variable_is_alias() {
infer_eq_without_problem(
indoc!(
r#"
Other a b : { y: a, z: b }
f : { x : Str }(Other Str Str)
f
"#
),
r#"{ x : Str, y : Str, z : Str }"#,
)
}
#[test]
fn tag_extension_variable_is_alias() {
infer_eq_without_problem(
indoc!(
r#"
Other : [ B, C ]
f : [ A ]Other
f
"#
),
r#"[ A, B, C ]"#,
)
}
} }

View file

@ -19,7 +19,7 @@ use indoc::indoc;
use roc_std::{RocList, RocStr}; use roc_std::{RocList, RocStr};
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn roc_list_construction() { fn roc_list_construction() {
let list = RocList::from_slice(&[1i64; 23]); let list = RocList::from_slice(&[1i64; 23]);
assert_eq!(&list, &list); assert_eq!(&list, &list);

View file

@ -11,7 +11,7 @@ use crate::helpers::wasm::{assert_evals_to, expect_runtime_error_panic};
use indoc::indoc; use indoc::indoc;
#[cfg(test)] #[cfg(test)]
use roc_std::RocList; use roc_std::{RocList, RocStr};
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
@ -272,7 +272,7 @@ fn empty_record() {
); );
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn i64_record2_literal() { fn i64_record2_literal() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -299,7 +299,7 @@ fn i64_record2_literal() {
// ); // );
// } // }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn f64_record2_literal() { fn f64_record2_literal() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -401,7 +401,7 @@ fn bool_literal() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record() { fn return_record() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -652,7 +652,7 @@ fn optional_field_empty_record() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_2() { fn return_record_2() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -666,7 +666,7 @@ fn return_record_2() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_3() { fn return_record_3() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -680,7 +680,7 @@ fn return_record_3() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_4() { fn return_record_4() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -694,7 +694,7 @@ fn return_record_4() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_5() { fn return_record_5() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -708,7 +708,7 @@ fn return_record_5() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_6() { fn return_record_6() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -722,7 +722,7 @@ fn return_record_6() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_7() { fn return_record_7() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -792,7 +792,7 @@ fn return_record_float_float_float() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_nested_record() { fn return_nested_record() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -1072,3 +1072,18 @@ fn call_with_bad_record_runtime_error() {
"# "#
)) ))
} }
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn generalized_accessor() {
assert_evals_to!(
indoc!(
r#"
returnFoo = .foo
returnFoo { foo: "foo" }
"#
),
RocStr::from("foo"),
RocStr
);
}

View file

@ -1,8 +1,8 @@
#[cfg(feature = "gen-llvm")] #[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to; use crate::helpers::llvm::assert_evals_to;
// #[cfg(feature = "gen-dev")] #[cfg(feature = "gen-dev")]
// use crate::helpers::dev::assert_evals_to; use crate::helpers::dev::assert_evals_to;
#[cfg(feature = "gen-wasm")] #[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to; use crate::helpers::wasm::assert_evals_to;
@ -29,7 +29,7 @@ fn width_and_alignment_u8_u8() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn applied_tag_nothing_ir() { fn applied_tag_nothing_ir() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -49,7 +49,7 @@ fn applied_tag_nothing_ir() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn applied_tag_nothing() { fn applied_tag_nothing() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -69,7 +69,7 @@ fn applied_tag_nothing() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn applied_tag_just() { fn applied_tag_just() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -88,7 +88,7 @@ fn applied_tag_just() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn applied_tag_just_ir() { fn applied_tag_just_ir() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -314,7 +314,7 @@ fn gen_if_float() {
); );
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn when_on_nothing() { fn when_on_nothing() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -333,7 +333,7 @@ fn when_on_nothing() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn when_on_just() { fn when_on_just() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -352,7 +352,7 @@ fn when_on_just() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn when_on_result() { fn when_on_result() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -371,7 +371,7 @@ fn when_on_result() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn when_on_these() { fn when_on_these() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -393,7 +393,7 @@ fn when_on_these() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn match_on_two_values() { fn match_on_two_values() {
// this will produce a Chain internally // this will produce a Chain internally
assert_evals_to!( assert_evals_to!(
@ -410,7 +410,7 @@ fn match_on_two_values() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn pair_with_underscore() { fn pair_with_underscore() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -427,7 +427,7 @@ fn pair_with_underscore() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn result_with_underscore() { fn result_with_underscore() {
// This test revealed an issue with hashing Test values // This test revealed an issue with hashing Test values
assert_evals_to!( assert_evals_to!(

View file

@ -1,4 +1,6 @@
use crate::subs::{AliasVariables, Content, FlatType, GetSubsSlice, Subs, UnionTags, Variable}; use crate::subs::{
AliasVariables, Content, FlatType, GetSubsSlice, Subs, SubsIndex, UnionTags, Variable,
};
use crate::types::{name_type_var, RecordField}; use crate::types::{name_type_var, RecordField};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
@ -134,20 +136,22 @@ fn find_names_needed(
} }
} }
RecursionVar { RecursionVar {
opt_name: Some(name), opt_name: Some(name_index),
.. ..
} }
| FlexVar(Some(name)) => { | FlexVar(Some(name_index)) => {
// This root already has a name. Nothing more to do here! // This root already has a name. Nothing more to do here!
// User-defined names are already taken. // User-defined names are already taken.
// We must not accidentally generate names that collide with them! // We must not accidentally generate names that collide with them!
names_taken.insert(name.clone()); let name = subs.field_names[name_index.index as usize].clone();
names_taken.insert(name);
} }
RigidVar(name) => { RigidVar(name_index) => {
// User-defined names are already taken. // User-defined names are already taken.
// We must not accidentally generate names that collide with them! // We must not accidentally generate names that collide with them!
names_taken.insert(name.clone()); let name = subs.field_names[name_index.index as usize].clone();
names_taken.insert(name);
} }
Structure(Apply(_, args)) => { Structure(Apply(_, args)) => {
for index in args.into_iter() { for index in args.into_iter() {
@ -257,16 +261,19 @@ fn set_root_name(root: Variable, name: Lowercase, subs: &mut Subs) {
match old_content { match old_content {
FlexVar(None) => { FlexVar(None) => {
let content = FlexVar(Some(name)); let name_index = SubsIndex::push_new(&mut subs.field_names, name);
let content = FlexVar(Some(name_index));
subs.set_content(root, content); subs.set_content(root, content);
} }
RecursionVar { RecursionVar {
opt_name: None, opt_name: None,
structure, structure,
} => { } => {
let structure = *structure;
let name_index = SubsIndex::push_new(&mut subs.field_names, name);
let content = RecursionVar { let content = RecursionVar {
structure: *structure, structure,
opt_name: Some(name), opt_name: Some(name_index),
}; };
subs.set_content(root, content); subs.set_content(root, content);
} }
@ -311,11 +318,20 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
use crate::subs::Content::*; use crate::subs::Content::*;
match content { match content {
FlexVar(Some(name)) => buf.push_str(name.as_str()), FlexVar(Some(name_index)) => {
let name = &subs.field_names[name_index.index as usize];
buf.push_str(name.as_str())
}
FlexVar(None) => buf.push_str(WILDCARD), FlexVar(None) => buf.push_str(WILDCARD),
RigidVar(name) => buf.push_str(name.as_str()), RigidVar(name_index) => {
let name = &subs.field_names[name_index.index as usize];
buf.push_str(name.as_str())
}
RecursionVar { opt_name, .. } => match opt_name { RecursionVar { opt_name, .. } => match opt_name {
Some(name) => buf.push_str(name.as_str()), Some(name_index) => {
let name = &subs.field_names[name_index.index as usize];
buf.push_str(name.as_str())
}
None => buf.push_str(WILDCARD), None => buf.push_str(WILDCARD),
}, },
Structure(flat_type) => write_flat_type(env, flat_type, subs, buf, parens), Structure(flat_type) => write_flat_type(env, flat_type, subs, buf, parens),
@ -382,7 +398,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
_ => write_parens!(write_parens, buf, { _ => write_parens!(write_parens, buf, {
write_symbol(env, *symbol, buf); write_symbol(env, *symbol, buf);
for var_index in args.into_iter() { for var_index in args.named_type_arguments() {
let var = subs[var_index]; let var = subs[var_index];
buf.push(' '); buf.push(' ');
write_content( write_content(

View file

@ -257,7 +257,10 @@ impl SolvedType {
// TODO should there be a SolvedType RecursionVar variant? // TODO should there be a SolvedType RecursionVar variant?
Self::from_var_help(subs, recursion_vars, *structure) Self::from_var_help(subs, recursion_vars, *structure)
} }
RigidVar(name) => SolvedType::Rigid(name.clone()), RigidVar(name_index) => {
let name = &subs.field_names[name_index.index as usize];
SolvedType::Rigid(name.clone())
}
Structure(flat_type) => Self::from_flat_type(subs, recursion_vars, flat_type), Structure(flat_type) => Self::from_flat_type(subs, recursion_vars, flat_type),
Alias(symbol, args, actual_var, kind) => { Alias(symbol, args, actual_var, kind) => {
let mut new_args = Vec::with_capacity(args.len()); let mut new_args = Vec::with_capacity(args.len());
@ -401,7 +404,10 @@ impl SolvedType {
} }
EmptyRecord => SolvedType::EmptyRecord, EmptyRecord => SolvedType::EmptyRecord,
EmptyTagUnion => SolvedType::EmptyTagUnion, EmptyTagUnion => SolvedType::EmptyTagUnion,
Erroneous(problem) => SolvedType::Erroneous(*problem.clone()), Erroneous(problem_index) => {
let problem = subs.problems[problem_index.index as usize].clone();
SolvedType::Erroneous(problem)
}
} }
} }
} }

View file

@ -1,5 +1,5 @@
use crate::types::{name_type_var, AliasKind, ErrorType, Problem, RecordField, TypeExt}; use crate::types::{name_type_var, AliasKind, ErrorType, Problem, RecordField, TypeExt};
use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap}; use roc_collections::all::{ImMap, ImSet, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName, Uppercase}; use roc_module::ident::{Lowercase, TagName, Uppercase};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use std::fmt; use std::fmt;
@ -68,7 +68,50 @@ pub struct Subs {
pub field_names: Vec<Lowercase>, pub field_names: Vec<Lowercase>,
pub record_fields: Vec<RecordField<()>>, pub record_fields: Vec<RecordField<()>>,
pub variable_slices: Vec<VariableSubsSlice>, pub variable_slices: Vec<VariableSubsSlice>,
pub tag_name_cache: MutMap<TagName, SubsSlice<TagName>>, pub tag_name_cache: TagNameCache,
pub problems: Vec<Problem>,
}
#[derive(Debug, Clone, Default)]
pub struct TagNameCache {
globals: Vec<Uppercase>,
globals_slices: Vec<SubsSlice<TagName>>,
/// Currently private tags and closure tags; in the future just closure tags
symbols: Vec<Symbol>,
symbols_slices: Vec<SubsSlice<TagName>>,
}
impl TagNameCache {
pub fn get_mut(&mut self, tag_name: &TagName) -> Option<&mut SubsSlice<TagName>> {
match tag_name {
TagName::Global(uppercase) => {
// force into block
match self.globals.iter().position(|u| u == uppercase) {
Some(index) => Some(&mut self.globals_slices[index]),
None => None,
}
}
TagName::Private(symbol) | TagName::Closure(symbol) => {
match self.symbols.iter().position(|s| s == symbol) {
Some(index) => Some(&mut self.symbols_slices[index]),
None => None,
}
}
}
}
pub fn push(&mut self, tag_name: &TagName, slice: SubsSlice<TagName>) {
match tag_name {
TagName::Global(uppercase) => {
self.globals.push(uppercase.clone());
self.globals_slices.push(slice);
}
TagName::Private(symbol) | TagName::Closure(symbol) => {
self.symbols.push(*symbol);
self.symbols_slices.push(slice);
}
}
}
} }
impl Default for Subs { impl Default for Subs {
@ -284,6 +327,14 @@ impl<T> SubsIndex<T> {
_marker: std::marker::PhantomData, _marker: std::marker::PhantomData,
} }
} }
pub fn push_new(vector: &mut Vec<T>, value: T) -> Self {
let index = Self::new(vector.len() as _);
vector.push(value);
index
}
} }
impl<T> IntoIterator for SubsSlice<T> { impl<T> IntoIterator for SubsSlice<T> {
@ -736,8 +787,8 @@ impl Variable {
/// # Safety /// # Safety
/// ///
/// This should only ever be called from tests! /// It is not guaranteed that the variable is in bounds.
pub unsafe fn unsafe_test_debug_variable(v: u32) -> Self { pub unsafe fn from_index(v: u32) -> Self {
debug_assert!(v >= Self::NUM_RESERVED_VARS as u32); debug_assert!(v >= Self::NUM_RESERVED_VARS as u32);
Variable(v) Variable(v)
} }
@ -1241,14 +1292,15 @@ impl Subs {
let mut subs = Subs { let mut subs = Subs {
utable: UnificationTable::default(), utable: UnificationTable::default(),
variables: Default::default(), variables: Vec::new(),
tag_names, tag_names,
field_names: Default::default(), field_names: Vec::new(),
record_fields: Default::default(), record_fields: Vec::new(),
// store an empty slice at the first position // store an empty slice at the first position
// used for "TagOrFunction" // used for "TagOrFunction"
variable_slices: vec![VariableSubsSlice::default()], variable_slices: vec![VariableSubsSlice::default()],
tag_name_cache: MutMap::default(), tag_name_cache: TagNameCache::default(),
problems: Vec::new(),
}; };
// NOTE the utable does not (currently) have a with_capacity; using this as the next-best thing // NOTE the utable does not (currently) have a with_capacity; using this as the next-best thing
@ -1324,7 +1376,8 @@ impl Subs {
} }
pub fn rigid_var(&mut self, var: Variable, name: Lowercase) { pub fn rigid_var(&mut self, var: Variable, name: Lowercase) {
let content = Content::RigidVar(name); let name_index = SubsIndex::push_new(&mut self.field_names, name);
let content = Content::RigidVar(name_index);
let desc = Descriptor::from(content); let desc = Descriptor::from(content);
self.set(var, desc); self.set(var, desc);
@ -1432,6 +1485,7 @@ impl Subs {
mapper(self.get_ref_mut(key)); mapper(self.get_ref_mut(key));
} }
#[inline(always)]
pub fn get_rank_set_mark(&mut self, key: Variable, mark: Mark) -> Rank { pub fn get_rank_set_mark(&mut self, key: Variable, mark: Mark) -> Rank {
let l_key = self.utable.get_root_key(key); let l_key = self.utable.get_root_key(key);
@ -1454,7 +1508,7 @@ impl Subs {
} }
pub fn occurs(&self, var: Variable) -> Result<(), (Variable, Vec<Variable>)> { pub fn occurs(&self, var: Variable) -> Result<(), (Variable, Vec<Variable>)> {
occurs(self, &ImSet::default(), var) occurs(self, &[], var)
} }
pub fn mark_tag_union_recursive( pub fn mark_tag_union_recursive(
@ -1680,19 +1734,19 @@ roc_error_macros::assert_sizeof_aarch64!((Variable, Option<Lowercase>), 4 * 8);
roc_error_macros::assert_sizeof_wasm!((Variable, Option<Lowercase>), 4 * 4); roc_error_macros::assert_sizeof_wasm!((Variable, Option<Lowercase>), 4 * 4);
roc_error_macros::assert_sizeof_default!((Variable, Option<Lowercase>), 4 * 8); roc_error_macros::assert_sizeof_default!((Variable, Option<Lowercase>), 4 * 8);
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Content { pub enum Content {
/// A type variable which the user did not name in an annotation, /// A type variable which the user did not name in an annotation,
/// ///
/// When we auto-generate a type var name, e.g. the "a" in (a -> a), we /// When we auto-generate a type var name, e.g. the "a" in (a -> a), we
/// change the Option in here from None to Some. /// change the Option in here from None to Some.
FlexVar(Option<Lowercase>), FlexVar(Option<SubsIndex<Lowercase>>),
/// name given in a user-written annotation /// name given in a user-written annotation
RigidVar(Lowercase), RigidVar(SubsIndex<Lowercase>),
/// name given to a recursion variable /// name given to a recursion variable
RecursionVar { RecursionVar {
structure: Variable, structure: Variable,
opt_name: Option<Lowercase>, opt_name: Option<SubsIndex<Lowercase>>,
}, },
Structure(FlatType), Structure(FlatType),
Alias(Symbol, AliasVariables, Variable, AliasKind), Alias(Symbol, AliasVariables, Variable, AliasKind),
@ -1816,7 +1870,7 @@ impl Content {
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub enum FlatType { pub enum FlatType {
Apply(Symbol, VariableSubsSlice), Apply(Symbol, VariableSubsSlice),
Func(VariableSubsSlice, Variable, Variable), Func(VariableSubsSlice, Variable, Variable),
@ -1824,7 +1878,7 @@ pub enum FlatType {
TagUnion(UnionTags, Variable), TagUnion(UnionTags, Variable),
FunctionOrTagUnion(SubsIndex<TagName>, Symbol, Variable), FunctionOrTagUnion(SubsIndex<TagName>, Symbol, Variable),
RecursiveTagUnion(Variable, UnionTags, Variable), RecursiveTagUnion(Variable, UnionTags, Variable),
Erroneous(Box<Problem>), Erroneous(SubsIndex<Problem>),
EmptyRecord, EmptyRecord,
EmptyTagUnion, EmptyTagUnion,
} }
@ -2380,7 +2434,7 @@ fn is_empty_record(subs: &Subs, mut var: Variable) -> bool {
fn occurs( fn occurs(
subs: &Subs, subs: &Subs,
seen: &ImSet<Variable>, seen: &[Variable],
input_var: Variable, input_var: Variable,
) -> Result<(), (Variable, Vec<Variable>)> { ) -> Result<(), (Variable, Vec<Variable>)> {
use self::Content::*; use self::Content::*;
@ -2395,9 +2449,9 @@ fn occurs(
FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => Ok(()), FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => Ok(()),
Structure(flat_type) => { Structure(flat_type) => {
let mut new_seen = seen.clone(); let mut new_seen = seen.to_owned();
new_seen.insert(root_var); new_seen.push(root_var);
match flat_type { match flat_type {
Apply(_, args) => { Apply(_, args) => {
@ -2446,8 +2500,8 @@ fn occurs(
} }
} }
Alias(_, args, _, _) => { Alias(_, args, _, _) => {
let mut new_seen = seen.clone(); let mut new_seen = seen.to_owned();
new_seen.insert(root_var); new_seen.push(root_var);
for var_index in args.into_iter() { for var_index in args.into_iter() {
let var = subs[var_index]; let var = subs[var_index];
@ -2457,8 +2511,8 @@ fn occurs(
Ok(()) Ok(())
} }
RangedNumber(typ, _range_vars) => { RangedNumber(typ, _range_vars) => {
let mut new_seen = seen.clone(); let mut new_seen = seen.to_owned();
new_seen.insert(root_var); new_seen.push(root_var);
short_circuit_help(subs, root_var, &new_seen, *typ)?; short_circuit_help(subs, root_var, &new_seen, *typ)?;
// _range_vars excluded because they are not explicitly part of the type. // _range_vars excluded because they are not explicitly part of the type.
@ -2469,10 +2523,11 @@ fn occurs(
} }
} }
#[inline(always)]
fn short_circuit<'a, T>( fn short_circuit<'a, T>(
subs: &Subs, subs: &Subs,
root_key: Variable, root_key: Variable,
seen: &ImSet<Variable>, seen: &[Variable],
iter: T, iter: T,
) -> Result<(), (Variable, Vec<Variable>)> ) -> Result<(), (Variable, Vec<Variable>)>
where where
@ -2485,10 +2540,11 @@ where
Ok(()) Ok(())
} }
#[inline(always)]
fn short_circuit_help( fn short_circuit_help(
subs: &Subs, subs: &Subs,
root_key: Variable, root_key: Variable,
seen: &ImSet<Variable>, seen: &[Variable],
var: Variable, var: Variable,
) -> Result<(), (Variable, Vec<Variable>)> { ) -> Result<(), (Variable, Vec<Variable>)> {
if let Err((v, mut vec)) = occurs(subs, seen, var) { if let Err((v, mut vec)) = occurs(subs, seen, var) {
@ -2688,18 +2744,23 @@ fn get_var_names(
match desc.content { match desc.content {
Error | FlexVar(None) => taken_names, Error | FlexVar(None) => taken_names,
FlexVar(Some(name)) => { FlexVar(Some(name_index)) => add_name(
add_name(subs, 0, name, var, |name| FlexVar(Some(name)), taken_names) subs,
} 0,
name_index,
var,
|name| FlexVar(Some(name)),
taken_names,
),
RecursionVar { RecursionVar {
opt_name, opt_name,
structure, structure,
} => match opt_name { } => match opt_name {
Some(name) => add_name( Some(name_index) => add_name(
subs, subs,
0, 0,
name, name_index,
var, var,
|name| RecursionVar { |name| RecursionVar {
opt_name: Some(name), opt_name: Some(name),
@ -2710,7 +2771,7 @@ fn get_var_names(
None => taken_names, None => taken_names,
}, },
RigidVar(name) => add_name(subs, 0, name, var, RigidVar, taken_names), RigidVar(name_index) => add_name(subs, 0, name_index, var, RigidVar, taken_names),
Alias(_, args, _, _) => args.into_iter().fold(taken_names, |answer, arg_var| { Alias(_, args, _, _) => args.into_iter().fold(taken_names, |answer, arg_var| {
get_var_names(subs, subs[arg_var], answer) get_var_names(subs, subs[arg_var], answer)
@ -2800,14 +2861,16 @@ fn get_var_names(
fn add_name<F>( fn add_name<F>(
subs: &mut Subs, subs: &mut Subs,
index: usize, index: usize,
given_name: Lowercase, given_name_index: SubsIndex<Lowercase>,
var: Variable, var: Variable,
content_from_name: F, content_from_name: F,
taken_names: ImMap<Lowercase, Variable>, taken_names: ImMap<Lowercase, Variable>,
) -> ImMap<Lowercase, Variable> ) -> ImMap<Lowercase, Variable>
where where
F: FnOnce(Lowercase) -> Content, F: FnOnce(SubsIndex<Lowercase>) -> Content,
{ {
let given_name = subs.field_names[given_name_index.index as usize].clone();
let indexed_name = if index == 0 { let indexed_name = if index == 0 {
given_name.clone() given_name.clone()
} else { } else {
@ -2819,7 +2882,9 @@ where
match taken_names.get(&indexed_name) { match taken_names.get(&indexed_name) {
None => { None => {
if indexed_name != given_name { if indexed_name != given_name {
subs.set_content(var, content_from_name(indexed_name.clone())); let indexed_name_index =
SubsIndex::push_new(&mut subs.field_names, indexed_name.clone());
subs.set_content(var, content_from_name(indexed_name_index));
} }
let mut answer = taken_names.clone(); let mut answer = taken_names.clone();
@ -2835,7 +2900,7 @@ where
add_name( add_name(
subs, subs,
index + 1, index + 1,
given_name, given_name_index,
var, var,
content_from_name, content_from_name,
taken_names, taken_names,
@ -2846,26 +2911,13 @@ where
} }
fn var_to_err_type(subs: &mut Subs, state: &mut ErrorTypeState, var: Variable) -> ErrorType { fn var_to_err_type(subs: &mut Subs, state: &mut ErrorTypeState, var: Variable) -> ErrorType {
let mut desc = subs.get(var); let desc = subs.get(var);
if desc.mark == Mark::OCCURS { if desc.mark == Mark::OCCURS {
ErrorType::Infinite ErrorType::Infinite
} else { } else {
subs.set_mark(var, Mark::OCCURS); subs.set_mark(var, Mark::OCCURS);
if false {
// useful for debugging
match desc.content {
Content::FlexVar(_) => {
desc.content = Content::FlexVar(Some(format!("{:?}", var).into()));
}
Content::RigidVar(_) => {
desc.content = Content::RigidVar(format!("{:?}", var).into());
}
_ => {}
}
}
let err_type = content_to_err_type(subs, state, var, desc.content); let err_type = content_to_err_type(subs, state, var, desc.content);
subs.set_mark(var, desc.mark); subs.set_mark(var, desc.mark);
@ -2885,15 +2937,20 @@ fn content_to_err_type(
match content { match content {
Structure(flat_type) => flat_type_to_err_type(subs, state, flat_type), Structure(flat_type) => flat_type_to_err_type(subs, state, flat_type),
FlexVar(Some(name)) => ErrorType::FlexVar(name), FlexVar(Some(name_index)) => {
let name = subs.field_names[name_index.index as usize].clone();
ErrorType::FlexVar(name)
}
FlexVar(opt_name) => { FlexVar(opt_name) => {
let name = match opt_name { let name = match opt_name {
Some(name) => name, Some(name_index) => subs.field_names[name_index.index as usize].clone(),
None => { None => {
// set the name so when this variable occurs elsewhere in the type it gets the same name
let name = get_fresh_var_name(state); let name = get_fresh_var_name(state);
let name_index = SubsIndex::push_new(&mut subs.field_names, name.clone());
subs.set_content(var, FlexVar(Some(name.clone()))); subs.set_content(var, FlexVar(Some(name_index)));
name name
} }
@ -2902,15 +2959,19 @@ fn content_to_err_type(
ErrorType::FlexVar(name) ErrorType::FlexVar(name)
} }
RigidVar(name) => ErrorType::RigidVar(name), RigidVar(name_index) => {
let name = subs.field_names[name_index.index as usize].clone();
ErrorType::RigidVar(name)
}
RecursionVar { opt_name, .. } => { RecursionVar { opt_name, .. } => {
let name = match opt_name { let name = match opt_name {
Some(name) => name, Some(name_index) => subs.field_names[name_index.index as usize].clone(),
None => { None => {
let name = get_fresh_var_name(state); let name = get_fresh_var_name(state);
let name_index = SubsIndex::push_new(&mut subs.field_names, name.clone());
subs.set_content(var, FlexVar(Some(name.clone()))); subs.set_content(var, FlexVar(Some(name_index)));
name name
} }
@ -3137,8 +3198,9 @@ fn flat_type_to_err_type(
} }
} }
Erroneous(problem) => { Erroneous(problem_index) => {
state.problems.push(*problem); let problem = subs.problems[problem_index.index as usize].clone();
state.problems.push(problem);
ErrorType::Error ErrorType::Error
} }
@ -3252,6 +3314,7 @@ struct StorageSubsOffsets {
field_names: u32, field_names: u32,
record_fields: u32, record_fields: u32,
variable_slices: u32, variable_slices: u32,
problems: u32,
} }
impl StorageSubs { impl StorageSubs {
@ -3271,6 +3334,7 @@ impl StorageSubs {
field_names: self.subs.field_names.len() as u32, field_names: self.subs.field_names.len() as u32,
record_fields: self.subs.record_fields.len() as u32, record_fields: self.subs.record_fields.len() as u32,
variable_slices: self.subs.variable_slices.len() as u32, variable_slices: self.subs.variable_slices.len() as u32,
problems: self.subs.problems.len() as u32,
}; };
let offsets = StorageSubsOffsets { let offsets = StorageSubsOffsets {
@ -3280,6 +3344,7 @@ impl StorageSubs {
field_names: target.field_names.len() as u32, field_names: target.field_names.len() as u32,
record_fields: target.record_fields.len() as u32, record_fields: target.record_fields.len() as u32,
variable_slices: target.variable_slices.len() as u32, variable_slices: target.variable_slices.len() as u32,
problems: target.problems.len() as u32,
}; };
// The first Variable::NUM_RESERVED_VARS are the same in every subs, // The first Variable::NUM_RESERVED_VARS are the same in every subs,
@ -3324,6 +3389,7 @@ impl StorageSubs {
target.tag_names.extend(self.subs.tag_names); target.tag_names.extend(self.subs.tag_names);
target.field_names.extend(self.subs.field_names); target.field_names.extend(self.subs.field_names);
target.record_fields.extend(self.subs.record_fields); target.record_fields.extend(self.subs.record_fields);
target.problems.extend(self.subs.problems);
debug_assert_eq!( debug_assert_eq!(
target.utable.len(), target.utable.len(),
@ -3340,6 +3406,7 @@ impl StorageSubs {
Self::offset_variable(&offsets, v) Self::offset_variable(&offsets, v)
} }
} }
fn offset_flat_type(offsets: &StorageSubsOffsets, flat_type: &FlatType) -> FlatType { fn offset_flat_type(offsets: &StorageSubsOffsets, flat_type: &FlatType) -> FlatType {
match flat_type { match flat_type {
FlatType::Apply(symbol, arguments) => { FlatType::Apply(symbol, arguments) => {
@ -3368,7 +3435,9 @@ impl StorageSubs {
Self::offset_union_tags(offsets, *union_tags), Self::offset_union_tags(offsets, *union_tags),
Self::offset_variable(offsets, *ext), Self::offset_variable(offsets, *ext),
), ),
FlatType::Erroneous(problem) => FlatType::Erroneous(problem.clone()), FlatType::Erroneous(problem) => {
FlatType::Erroneous(Self::offset_problem(offsets, *problem))
}
FlatType::EmptyRecord => FlatType::EmptyRecord, FlatType::EmptyRecord => FlatType::EmptyRecord,
FlatType::EmptyTagUnion => FlatType::EmptyTagUnion, FlatType::EmptyTagUnion => FlatType::EmptyTagUnion,
} }
@ -3378,14 +3447,14 @@ impl StorageSubs {
use Content::*; use Content::*;
match content { match content {
FlexVar(opt_name) => FlexVar(opt_name.clone()), FlexVar(opt_name) => FlexVar(*opt_name),
RigidVar(name) => RigidVar(name.clone()), RigidVar(name) => RigidVar(*name),
RecursionVar { RecursionVar {
structure, structure,
opt_name, opt_name,
} => RecursionVar { } => RecursionVar {
structure: Self::offset_variable(offsets, *structure), structure: Self::offset_variable(offsets, *structure),
opt_name: opt_name.clone(), opt_name: *opt_name,
}, },
Structure(flat_type) => Structure(Self::offset_flat_type(offsets, flat_type)), Structure(flat_type) => Structure(Self::offset_flat_type(offsets, flat_type)),
Alias(symbol, alias_variables, actual, kind) => Alias( Alias(symbol, alias_variables, actual, kind) => Alias(
@ -3455,6 +3524,15 @@ impl StorageSubs {
slice slice
} }
fn offset_problem(
offsets: &StorageSubsOffsets,
mut problem_index: SubsIndex<Problem>,
) -> SubsIndex<Problem> {
problem_index.index += offsets.problems;
problem_index
}
} }
use std::cell::RefCell; use std::cell::RefCell;

View file

@ -2,7 +2,7 @@ use crate::pretty_print::Parens;
use crate::subs::{ use crate::subs::{
GetSubsSlice, RecordFields, Subs, UnionTags, VarStore, Variable, VariableSubsSlice, GetSubsSlice, RecordFields, Subs, UnionTags, VarStore, Variable, VariableSubsSlice,
}; };
use roc_collections::all::{ImMap, ImSet, Index, MutSet, SendMap}; use roc_collections::all::{HumanIndex, ImMap, ImSet, MutSet, SendMap};
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_module::called_via::CalledVia; use roc_module::called_via::CalledVia;
use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName};
@ -722,10 +722,11 @@ impl Type {
/// a shallow dealias, continue until the first constructor is not an alias. /// a shallow dealias, continue until the first constructor is not an alias.
pub fn shallow_dealias(&self) -> &Self { pub fn shallow_dealias(&self) -> &Self {
match self { let mut result = self;
Type::Alias { actual, .. } => actual.shallow_dealias(), while let Type::Alias { actual, .. } = result {
_ => self, result = actual;
} }
result
} }
pub fn instantiate_aliases( pub fn instantiate_aliases(
@ -1203,14 +1204,14 @@ pub struct TagUnionStructure<'a> {
pub enum PReason { pub enum PReason {
TypedArg { TypedArg {
opt_name: Option<Symbol>, opt_name: Option<Symbol>,
index: Index, index: HumanIndex,
}, },
WhenMatch { WhenMatch {
index: Index, index: HumanIndex,
}, },
TagArg { TagArg {
tag_name: TagName, tag_name: TagName,
index: Index, index: HumanIndex,
}, },
PatternGuard, PatternGuard,
OptionalField, OptionalField,
@ -1219,12 +1220,12 @@ pub enum PReason {
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnnotationSource { pub enum AnnotationSource {
TypedIfBranch { TypedIfBranch {
index: Index, index: HumanIndex,
num_branches: usize, num_branches: usize,
region: Region, region: Region,
}, },
TypedWhenBranch { TypedWhenBranch {
index: Index, index: HumanIndex,
region: Region, region: Region,
}, },
TypedBody { TypedBody {
@ -1246,7 +1247,7 @@ impl AnnotationSource {
pub enum Reason { pub enum Reason {
FnArg { FnArg {
name: Option<Symbol>, name: Option<Symbol>,
arg_index: Index, arg_index: HumanIndex,
}, },
FnCall { FnCall {
name: Option<Symbol>, name: Option<Symbol>,
@ -1254,28 +1255,28 @@ pub enum Reason {
}, },
LowLevelOpArg { LowLevelOpArg {
op: LowLevel, op: LowLevel,
arg_index: Index, arg_index: HumanIndex,
}, },
ForeignCallArg { ForeignCallArg {
foreign_symbol: ForeignSymbol, foreign_symbol: ForeignSymbol,
arg_index: Index, arg_index: HumanIndex,
}, },
FloatLiteral, FloatLiteral,
IntLiteral, IntLiteral,
NumLiteral, NumLiteral,
StrInterpolation, StrInterpolation,
WhenBranch { WhenBranch {
index: Index, index: HumanIndex,
}, },
WhenGuard, WhenGuard,
ExpectCondition, ExpectCondition,
IfCondition, IfCondition,
IfBranch { IfBranch {
index: Index, index: HumanIndex,
total_branches: usize, total_branches: usize,
}, },
ElemInList { ElemInList {
index: Index, index: HumanIndex,
}, },
RecordUpdateValue(Lowercase), RecordUpdateValue(Lowercase),
RecordUpdateKeys(Symbol, SendMap<Lowercase, Region>), RecordUpdateKeys(Symbol, SendMap<Lowercase, Region>),

View file

@ -331,7 +331,7 @@ fn unify_alias(
} }
if problems.is_empty() { if problems.is_empty() {
problems.extend(merge(subs, ctx, other_content.clone())); problems.extend(merge(subs, ctx, *other_content));
} }
// if problems.is_empty() { problems.extend(unify_pool(subs, pool, real_var, *other_real_var)); } // if problems.is_empty() { problems.extend(unify_pool(subs, pool, real_var, *other_real_var)); }
@ -374,7 +374,7 @@ fn unify_structure(
match other { match other {
FlexVar(_) => { FlexVar(_) => {
// If the other is flex, Structure wins! // If the other is flex, Structure wins!
let outcome = merge(subs, ctx, Structure(flat_type.clone())); let outcome = merge(subs, ctx, Structure(*flat_type));
// And if we see a flex variable on the right hand side of a presence // And if we see a flex variable on the right hand side of a presence
// constraint, we know we need to open up the structure we're trying to unify with. // constraint, we know we need to open up the structure we're trying to unify with.
@ -1158,7 +1158,7 @@ fn unify_flat_type(
use roc_types::subs::FlatType::*; use roc_types::subs::FlatType::*;
match (left, right) { match (left, right) {
(EmptyRecord, EmptyRecord) => merge(subs, ctx, Structure(left.clone())), (EmptyRecord, EmptyRecord) => merge(subs, ctx, Structure(*left)),
(Record(fields, ext), EmptyRecord) if fields.has_only_optional_fields(subs) => { (Record(fields, ext), EmptyRecord) if fields.has_only_optional_fields(subs) => {
unify_pool(subs, pool, *ext, ctx.second, ctx.mode) unify_pool(subs, pool, *ext, ctx.second, ctx.mode)
@ -1172,7 +1172,7 @@ fn unify_flat_type(
unify_record(subs, pool, ctx, *fields1, *ext1, *fields2, *ext2) unify_record(subs, pool, ctx, *fields1, *ext1, *fields2, *ext2)
} }
(EmptyTagUnion, EmptyTagUnion) => merge(subs, ctx, Structure(left.clone())), (EmptyTagUnion, EmptyTagUnion) => merge(subs, ctx, Structure(*left)),
(TagUnion(tags, ext), EmptyTagUnion) if tags.is_empty() => { (TagUnion(tags, ext), EmptyTagUnion) if tags.is_empty() => {
unify_pool(subs, pool, *ext, ctx.second, ctx.mode) unify_pool(subs, pool, *ext, ctx.second, ctx.mode)
@ -1277,7 +1277,7 @@ fn unify_flat_type(
if tag_name_1_ref == tag_name_2_ref { if tag_name_1_ref == tag_name_2_ref {
let problems = unify_pool(subs, pool, *ext1, *ext2, ctx.mode); let problems = unify_pool(subs, pool, *ext1, *ext2, ctx.mode);
if problems.is_empty() { if problems.is_empty() {
let content = subs.get_content_without_compacting(ctx.second).clone(); let content = *subs.get_content_without_compacting(ctx.second);
merge(subs, ctx, content) merge(subs, ctx, content)
} else { } else {
problems problems
@ -1351,11 +1351,16 @@ fn unify_zip_slices(
} }
#[inline(always)] #[inline(always)]
fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content) -> Outcome { fn unify_rigid(
subs: &mut Subs,
ctx: &Context,
name: &SubsIndex<Lowercase>,
other: &Content,
) -> Outcome {
match other { match other {
FlexVar(_) => { FlexVar(_) => {
// If the other is flex, rigid wins! // If the other is flex, rigid wins!
merge(subs, ctx, RigidVar(name.clone())) merge(subs, ctx, RigidVar(*name))
} }
RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _, _) | RangedNumber(..) => { RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _, _) | RangedNumber(..) => {
if !ctx.mode.contains(Mode::RIGID_AS_FLEX) { if !ctx.mode.contains(Mode::RIGID_AS_FLEX) {
@ -1364,7 +1369,7 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content
mismatch!("Rigid {:?} with {:?}", ctx.first, &other) mismatch!("Rigid {:?} with {:?}", ctx.first, &other)
} else { } else {
// We are treating rigid vars as flex vars; admit this // We are treating rigid vars as flex vars; admit this
merge(subs, ctx, other.clone()) merge(subs, ctx, *other)
} }
} }
Error => { Error => {
@ -1378,13 +1383,13 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content
fn unify_flex( fn unify_flex(
subs: &mut Subs, subs: &mut Subs,
ctx: &Context, ctx: &Context,
opt_name: &Option<Lowercase>, opt_name: &Option<SubsIndex<Lowercase>>,
other: &Content, other: &Content,
) -> Outcome { ) -> Outcome {
match other { match other {
FlexVar(None) => { FlexVar(None) => {
// If both are flex, and only left has a name, keep the name around. // If both are flex, and only left has a name, keep the name around.
merge(subs, ctx, FlexVar(opt_name.clone())) merge(subs, ctx, FlexVar(*opt_name))
} }
FlexVar(Some(_)) FlexVar(Some(_))
@ -1396,7 +1401,7 @@ fn unify_flex(
// TODO special-case boolean here // TODO special-case boolean here
// In all other cases, if left is flex, defer to right. // In all other cases, if left is flex, defer to right.
// (This includes using right's name if both are flex and named.) // (This includes using right's name if both are flex and named.)
merge(subs, ctx, other.clone()) merge(subs, ctx, *other)
} }
Error => merge(subs, ctx, Error), Error => merge(subs, ctx, Error),
@ -1408,7 +1413,7 @@ fn unify_recursion(
subs: &mut Subs, subs: &mut Subs,
pool: &mut Pool, pool: &mut Pool,
ctx: &Context, ctx: &Context,
opt_name: &Option<Lowercase>, opt_name: &Option<SubsIndex<Lowercase>>,
structure: Variable, structure: Variable,
other: &Content, other: &Content,
) -> Outcome { ) -> Outcome {
@ -1419,7 +1424,7 @@ fn unify_recursion(
} => { } => {
// NOTE: structure and other_structure may not be unified yet, but will be // NOTE: structure and other_structure may not be unified yet, but will be
// we should not do that here, it would create an infinite loop! // we should not do that here, it would create an infinite loop!
let name = opt_name.clone().or_else(|| other_opt_name.clone()); let name = (*opt_name).or_else(|| *other_opt_name);
merge( merge(
subs, subs,
ctx, ctx,
@ -1441,7 +1446,7 @@ fn unify_recursion(
ctx, ctx,
RecursionVar { RecursionVar {
structure, structure,
opt_name: opt_name.clone(), opt_name: *opt_name,
}, },
), ),

View file

@ -21,6 +21,7 @@ use crate::ui::util::path_to_string;
use bumpalo::Bump; use bumpalo::Bump;
use cgmath::Vector2; use cgmath::Vector2;
use fs_extra::dir::{copy, ls, CopyOptions, DirEntryAttr, DirEntryValue}; use fs_extra::dir::{copy, ls, CopyOptions, DirEntryAttr, DirEntryValue};
use futures::TryFutureExt;
use pipelines::RectResources; use pipelines::RectResources;
use roc_ast::lang::env::Env; use roc_ast::lang::env::Env;
use roc_ast::mem_pool::pool::Pool; use roc_ast::mem_pool::pool::Pool;
@ -73,28 +74,25 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
// Initialize GPU // Initialize GPU
let (gpu_device, cmd_queue) = futures::executor::block_on(async { let (gpu_device, cmd_queue) = futures::executor::block_on(async {
let adapter = instance create_device(
.request_adapter(&wgpu::RequestAdapterOptions { &instance,
power_preference: wgpu::PowerPreference::HighPerformance, &surface,
compatible_surface: Some(&surface), wgpu::PowerPreference::HighPerformance,
force_fallback_adapter: false, false,
}) )
.await .or_else(|_| create_device(&instance, &surface, wgpu::PowerPreference::LowPower, false))
.expect(r#"Request adapter .or_else(|_| {
If you're running this from inside nix, follow the instructions here to resolve this: https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#editor create_device(
"#); &instance,
&surface,
adapter wgpu::PowerPreference::HighPerformance,
.request_device( true,
&wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::empty(),
limits: wgpu::Limits::default(),
},
None,
) )
.await })
.expect("Request device") .unwrap_or_else(|err| {
panic!("Failed to request device: `{}`", err);
})
.await
}); });
// Create staging belt and a local pool // Create staging belt and a local pool
@ -395,6 +393,39 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
Ok(()) Ok(())
} }
async fn create_device(
instance: &wgpu::Instance,
surface: &wgpu::Surface,
power_preference: wgpu::PowerPreference,
force_fallback_adapter: bool,
) -> Result<(wgpu::Device, wgpu::Queue), wgpu::RequestDeviceError> {
if force_fallback_adapter {
log::error!("Falling back to software renderer. GPU acceleration has been disabled.");
}
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference,
compatible_surface: Some(surface),
force_fallback_adapter,
})
.await
.expect(r#"Request adapter
If you're running this from inside nix, follow the instructions here to resolve this: https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#editor
"#);
adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::empty(),
limits: wgpu::Limits::default(),
},
None,
)
.await
}
fn draw_rects( fn draw_rects(
all_rects: &[Rect], all_rects: &[Rect],
encoder: &mut CommandEncoder, encoder: &mut CommandEncoder,

View file

@ -49,4 +49,3 @@ closure4 = \_ ->
Task.succeed {} Task.succeed {}
|> Task.after (\_ -> Task.succeed x) |> Task.after (\_ -> Task.succeed x)
|> Task.map (\_ -> {}) |> Task.map (\_ -> {})

View file

@ -73,4 +73,3 @@ swap = \i, j, list ->
_ -> _ ->
[] []

View file

@ -112,4 +112,3 @@ balance = \color, key, value, left, right ->
_ -> _ ->
Node color key value left right Node color key value left right

View file

@ -21,4 +21,3 @@ main =
Err _ -> Err _ ->
Task.putLine "sadness" Task.putLine "sadness"

View file

@ -19,3 +19,12 @@ checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673"
[[package]] [[package]]
name = "roc_std" name = "roc_std"
version = "0.1.0" version = "0.1.0"
dependencies = [
"static_assertions",
]
[[package]]
name = "static_assertions"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7"

View file

@ -19,3 +19,12 @@ checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5"
[[package]] [[package]]
name = "roc_std" name = "roc_std"
version = "0.1.0" version = "0.1.0"
dependencies = [
"static_assertions",
]
[[package]]
name = "static_assertions"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7"

View file

@ -79,12 +79,12 @@ pub export fn main() u8 {
const result = roc__mainForHost_1_exposed(10); const result = roc__mainForHost_1_exposed(10);
stdout.print("{d}\n", .{result}) catch unreachable;
// end time // end time
var ts2: std.os.timespec = undefined; var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
stdout.print("{d}\n", .{result}) catch unreachable;
const delta = to_seconds(ts2) - to_seconds(ts1); const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;

View file

@ -2090,6 +2090,9 @@ checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157"
[[package]] [[package]]
name = "roc_std" name = "roc_std"
version = "0.1.0" version = "0.1.0"
dependencies = [
"static_assertions 0.1.1",
]
[[package]] [[package]]
name = "rodio" name = "rodio"
@ -2292,6 +2295,12 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "static_assertions"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7"
[[package]] [[package]]
name = "static_assertions" name = "static_assertions"
version = "1.1.0" version = "1.1.0"
@ -2403,7 +2412,7 @@ checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"rand", "rand",
"static_assertions", "static_assertions 1.1.0",
] ]
[[package]] [[package]]

View file

@ -19,3 +19,12 @@ checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714"
[[package]] [[package]]
name = "roc_std" name = "roc_std"
version = "0.1.0" version = "0.1.0"
dependencies = [
"static_assertions",
]
[[package]]
name = "static_assertions"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7"

View file

@ -3,7 +3,7 @@
To run, go to the project home directory and run: To run, go to the project home directory and run:
```bash ```bash
$ cargo run -- build --backend=wasm32 examples/hello-web/Hello.roc $ cargo run -- build --target=wasm32 examples/hello-web/Hello.roc
``` ```
Then `cd` into the example directory and run any web server that can handle WebAssembly. Then `cd` into the example directory and run any web server that can handle WebAssembly.

View file

@ -92,15 +92,15 @@ pub fn main() u8 {
// actually call roc to populate the callresult // actually call roc to populate the callresult
var callresult = roc__mainForHost_1_exposed(); var callresult = roc__mainForHost_1_exposed();
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
// stdout the result // stdout the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
callresult.deinit(); callresult.deinit();
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
const delta = to_seconds(ts2) - to_seconds(ts1); const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;

View file

@ -112,6 +112,10 @@ pub export fn main() u8 {
const length = std.math.min(20, callresult.length); const length = std.math.min(20, callresult.length);
var result = callresult.elements[0..length]; var result = callresult.elements[0..length];
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
for (result) |x, i| { for (result) |x, i| {
if (i == 0) { if (i == 0) {
stdout.print("[{}, ", .{x}) catch unreachable; stdout.print("[{}, ", .{x}) catch unreachable;
@ -122,10 +126,6 @@ pub export fn main() u8 {
} }
} }
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
const delta = to_seconds(ts2) - to_seconds(ts1); const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;

View file

@ -18,5 +18,6 @@ roc_mono = {path = "../compiler/mono"}
roc_parse = {path = "../compiler/parse"} roc_parse = {path = "../compiler/parse"}
roc_region = {path = "../compiler/region"} roc_region = {path = "../compiler/region"}
roc_reporting = {path = "../reporting"} roc_reporting = {path = "../reporting"}
roc_std = {path = "../roc_std", default-features = false}
roc_target = {path = "../compiler/roc_target"} roc_target = {path = "../compiler/roc_target"}
roc_types = {path = "../compiler/types"} roc_types = {path = "../compiler/types"}

View file

@ -13,6 +13,7 @@ use roc_mono::layout::{
}; };
use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral}; use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_std::RocDec;
use roc_target::TargetInfo; use roc_target::TargetInfo;
use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable};
@ -279,6 +280,15 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
) -> Result<Expr<'a>, ToAstProblem> { ) -> Result<Expr<'a>, ToAstProblem> {
let (newtype_containers, content) = unroll_newtypes(env, content); let (newtype_containers, content) = unroll_newtypes(env, content);
let content = unroll_aliases(env, content); let content = unroll_aliases(env, content);
macro_rules! helper {
($ty:ty) => {
app.call_function(main_fn_name, |_, num: $ty| {
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
})
};
}
let result = match layout { let result = match layout {
Layout::Builtin(Builtin::Bool) => Ok(app Layout::Builtin(Builtin::Bool) => Ok(app
.call_function(main_fn_name, |mem: &A::Memory, num: bool| { .call_function(main_fn_name, |mem: &A::Memory, num: bool| {
@ -287,14 +297,6 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
Layout::Builtin(Builtin::Int(int_width)) => { Layout::Builtin(Builtin::Int(int_width)) => {
use IntWidth::*; use IntWidth::*;
macro_rules! helper {
($ty:ty) => {
app.call_function(main_fn_name, |_, num: $ty| {
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
})
};
}
let result = match int_width { let result = match int_width {
U8 | I8 => { U8 | I8 => {
// NOTE: `helper!` does not handle 8-bit numbers yet // NOTE: `helper!` does not handle 8-bit numbers yet
@ -317,14 +319,6 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
Layout::Builtin(Builtin::Float(float_width)) => { Layout::Builtin(Builtin::Float(float_width)) => {
use FloatWidth::*; use FloatWidth::*;
macro_rules! helper {
($ty:ty) => {
app.call_function(main_fn_name, |_, num: $ty| {
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
})
};
}
let result = match float_width { let result = match float_width {
F32 => helper!(f32), F32 => helper!(f32),
F64 => helper!(f64), F64 => helper!(f64),
@ -333,6 +327,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
Ok(result) Ok(result)
} }
Layout::Builtin(Builtin::Decimal) => Ok(helper!(RocDec)),
Layout::Builtin(Builtin::Str) => { Layout::Builtin(Builtin::Str) => {
let size = layout.stack_size(env.target_info) as usize; let size = layout.stack_size(env.target_info) as usize;
Ok( Ok(
@ -1246,5 +1241,9 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
/// This is centralized in case we want to format it differently later, /// This is centralized in case we want to format it differently later,
/// e.g. adding underscores for large numbers /// e.g. adding underscores for large numbers
fn number_literal_to_ast<T: std::fmt::Display>(arena: &Bump, num: T) -> Expr<'_> { fn number_literal_to_ast<T: std::fmt::Display>(arena: &Bump, num: T) -> Expr<'_> {
Expr::Num(arena.alloc(format!("{}", num))) use std::fmt::Write;
let mut string = bumpalo::collections::String::with_capacity_in(64, arena);
write!(string, "{}", num).unwrap();
Expr::Num(string.into_bump_str())
} }

View file

@ -1,4 +1,5 @@
use roc_parse::ast::Expr; use roc_parse::ast::Expr;
use roc_std::RocDec;
pub mod eval; pub mod eval;
pub mod gen; pub mod gen;
@ -47,5 +48,10 @@ pub trait ReplAppMemory {
fn deref_f32(&self, addr: usize) -> f32; fn deref_f32(&self, addr: usize) -> f32;
fn deref_f64(&self, addr: usize) -> f64; fn deref_f64(&self, addr: usize) -> f64;
fn deref_dec(&self, addr: usize) -> RocDec {
let bits = self.deref_i128(addr);
RocDec(bits)
}
fn deref_str(&self, addr: usize) -> &str; fn deref_str(&self, addr: usize) -> &str;
} }

View file

@ -994,6 +994,7 @@ fn issue_2588_record_with_function_and_nonfunction() {
) )
} }
#[test]
fn opaque_apply() { fn opaque_apply() {
expect_success( expect_success(
indoc!( indoc!(
@ -1036,3 +1037,17 @@ fn opaque_pattern_and_call() {
r#"Package {} A : F {} [ A ]*"#, r#"Package {} A : F {} [ A ]*"#,
) )
} }
#[test]
fn dec_in_repl() {
expect_success(
indoc!(
r#"
x: Dec
x=1.23
x
"#
),
r#"1.23 : Dec"#,
)
}

View file

@ -43,16 +43,20 @@ section.history {
margin: 16px 0; margin: 16px 0;
padding: 8px; padding: 8px;
} }
#history-text .input { .history-item {
margin-bottom: 24px;
}
.history-item .input {
margin: 0;
margin-bottom: 8px; margin-bottom: 8px;
} }
#history-text .output { .history-item .output {
margin-bottom: 16px; margin: 0;
} }
#history-text .output-ok { .history-item .output-ok {
color: #0f8; color: #0f8;
} }
#history-text .output-error { .history-item .output-error {
color: #f00; color: #f00;
} }
.code { .code {
@ -64,9 +68,7 @@ section.source {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
section.source textarea { section.source textarea {
height: 32px;
padding: 8px; padding: 8px;
margin-bottom: 16px; margin-bottom: 16px;
} }

View file

@ -82,7 +82,9 @@ function onInputKeyup(event) {
break; break;
case ENTER: case ENTER:
onInputChange({ target: repl.elemSourceInput }); if (!event.shiftKey) {
onInputChange({ target: repl.elemSourceInput });
}
break; break;
default: default:
@ -168,12 +170,13 @@ function createHistoryEntry(inputText) {
const historyIndex = repl.inputHistory.length; const historyIndex = repl.inputHistory.length;
repl.inputHistory.push(inputText); repl.inputHistory.push(inputText);
const inputElem = document.createElement("div"); const inputElem = document.createElement("pre");
inputElem.textContent = "> " + inputText; inputElem.textContent = inputText;
inputElem.classList.add("input"); inputElem.classList.add("input");
const historyItem = document.createElement("div"); const historyItem = document.createElement("div");
historyItem.appendChild(inputElem); historyItem.appendChild(inputElem);
historyItem.classList.add("history-item");
repl.elemHistory.appendChild(historyItem); repl.elemHistory.appendChild(historyItem);
repl.elemHistory.scrollTop = repl.elemHistory.scrollHeight; repl.elemHistory.scrollTop = repl.elemHistory.scrollHeight;
@ -182,7 +185,7 @@ function createHistoryEntry(inputText) {
} }
function updateHistoryEntry(index, ok, outputText) { function updateHistoryEntry(index, ok, outputText) {
const outputElem = document.createElement("div"); const outputElem = document.createElement("pre");
outputElem.textContent = outputText; outputElem.textContent = outputText;
outputElem.classList.add("output"); outputElem.classList.add("output");
outputElem.classList.add(ok ? "output-ok" : "output-error"); outputElem.classList.add(ok ? "output-ok" : "output-error");

View file

@ -1,29 +1,33 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<title>Roc REPL</title>
<link rel="stylesheet" href="/repl.css" />
</head>
<head> <body>
<title>Roc REPL</title> <div class="body-wrapper">
<link rel="stylesheet" href="/repl.css" /> <section class="text">
</head> <h1>The rockin' Roc REPL</h1>
</section>
<body> <section class="history">
<div class="body-wrapper"> <div class="scroll-wrap">
<section class="text"> <div id="history-text" class="scroll code"></div>
<h1>The rockin' Roc REPL</h1> </div>
</section> </section>
<section class="history">
<div class="scroll-wrap">
<div id="history-text" class="scroll code"></div>
</div>
</section>
<section class="source">
<textarea autofocus id="source-input" class="code" placeholder="Type some Roc code and press Enter"></textarea>
</section>
</div>
<script type="module" src="/repl.js"></script>
</body>
<section class="source">
<textarea
rows="5"
autofocus
id="source-input"
class="code"
placeholder="Type some Roc code and press Enter. (Use Shift+Enter for multi-line input)"
></textarea>
</section>
</div>
<script type="module" src="/repl.js"></script>
</body>
</html> </html>

View file

@ -1,7 +1,9 @@
use roc_collections::all::MutSet; use roc_collections::all::MutSet;
use roc_module::ident::{Ident, Lowercase, ModuleName}; use roc_module::ident::{Ident, Lowercase, ModuleName};
use roc_problem::can::PrecedenceProblem::BothNonAssociative; use roc_problem::can::PrecedenceProblem::BothNonAssociative;
use roc_problem::can::{BadPattern, FloatErrorKind, IntErrorKind, Problem, RuntimeError}; use roc_problem::can::{
BadPattern, ExtensionTypeKind, FloatErrorKind, IntErrorKind, Problem, RuntimeError,
};
use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Loc, Region}; use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Loc, Region};
use std::path::PathBuf; use std::path::PathBuf;
@ -33,6 +35,7 @@ const OPAQUE_NOT_DEFINED: &str = "OPAQUE TYPE NOT DEFINED";
const OPAQUE_DECLARED_OUTSIDE_SCOPE: &str = "OPAQUE TYPE DECLARED OUTSIDE SCOPE"; const OPAQUE_DECLARED_OUTSIDE_SCOPE: &str = "OPAQUE TYPE DECLARED OUTSIDE SCOPE";
const OPAQUE_NOT_APPLIED: &str = "OPAQUE TYPE NOT APPLIED"; const OPAQUE_NOT_APPLIED: &str = "OPAQUE TYPE NOT APPLIED";
const OPAQUE_OVER_APPLIED: &str = "OPAQUE TYPE APPLIED TO TOO MANY ARGS"; const OPAQUE_OVER_APPLIED: &str = "OPAQUE TYPE APPLIED TO TOO MANY ARGS";
const INVALID_EXTENSION_TYPE: &str = "INVALID_EXTENSION_TYPE";
pub fn can_problem<'b>( pub fn can_problem<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
@ -492,6 +495,34 @@ pub fn can_problem<'b>(
title = NESTED_DATATYPE.to_string(); title = NESTED_DATATYPE.to_string();
severity = Severity::RuntimeError; severity = Severity::RuntimeError;
} }
Problem::InvalidExtensionType { region, kind } => {
let (kind_str, can_only_contain) = match kind {
ExtensionTypeKind::Record => ("record", "a type variable or another record"),
ExtensionTypeKind::TagUnion => {
("tag union", "a type variable or another tag union")
}
};
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This "),
alloc.text(kind_str),
alloc.reflow(" extension type is invalid:"),
]),
alloc.region(lines.convert_region(region)),
alloc.concat(vec![
alloc.note("A "),
alloc.reflow(kind_str),
alloc.reflow(" extension variable can only contain "),
alloc.reflow(can_only_contain),
alloc.reflow("."),
]),
]);
title = INVALID_EXTENSION_TYPE.to_string();
severity = Severity::RuntimeError;
}
}; };
Report { Report {

View file

@ -136,6 +136,8 @@ fn pattern_to_doc<'b>(
pattern_to_doc_help(alloc, pattern, false) pattern_to_doc_help(alloc, pattern, false)
} }
const AFTER_TAG_INDENT: &str = " ";
fn pattern_to_doc_help<'b>( fn pattern_to_doc_help<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
pattern: roc_exhaustive::Pattern, pattern: roc_exhaustive::Pattern,
@ -160,7 +162,22 @@ fn pattern_to_doc_help<'b>(
}, },
Ctor(union, tag_id, args) => { Ctor(union, tag_id, args) => {
match union.render_as { match union.render_as {
RenderAs::Guard => panic!("can this happen? inform Folkert"), RenderAs::Guard => {
// #Guard <fake-condition-tag> <unexhausted-pattern>
debug_assert_eq!(
union.alternatives[tag_id.0 as usize].name,
TagName::Global("#Guard".into())
);
debug_assert!(args.len() == 2);
let tag = pattern_to_doc_help(alloc, args[1].clone(), in_type_param);
alloc.concat(vec![
tag,
alloc.text(AFTER_TAG_INDENT),
alloc.text("(note the lack of an "),
alloc.keyword("if"),
alloc.text(" clause)"),
])
}
RenderAs::Record(field_names) => { RenderAs::Record(field_names) => {
let mut arg_docs = Vec::with_capacity(args.len()); let mut arg_docs = Vec::with_capacity(args.len());

View file

@ -1,5 +1,5 @@
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
use roc_collections::all::{Index, MutSet, SendMap}; use roc_collections::all::{HumanIndex, MutSet, SendMap};
use roc_module::called_via::{BinOp, CalledVia}; use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::{Ident, IdentStr, Lowercase, TagName}; use roc_module::ident::{Ident, IdentStr, Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -350,7 +350,7 @@ fn to_expr_report<'b>(
num_branches, num_branches,
.. ..
} if num_branches == 2 => alloc.concat(vec![ } if num_branches == 2 => alloc.concat(vec![
alloc.keyword(if index == Index::FIRST { alloc.keyword(if index == HumanIndex::FIRST {
"then" "then"
} else { } else {
"else" "else"
@ -1372,7 +1372,7 @@ fn to_pattern_report<'b>(
} }
} }
PReason::WhenMatch { index } => { PReason::WhenMatch { index } => {
if index == Index::FIRST { if index == HumanIndex::FIRST {
let doc = alloc.stack(vec![ let doc = alloc.stack(vec![
alloc alloc
.text("The 1st pattern in this ") .text("The 1st pattern in this ")

View file

@ -1,7 +1,7 @@
extern crate bumpalo; extern crate bumpalo;
use self::bumpalo::Bump; use self::bumpalo::Bump;
use roc_can::constraint::Constraint; use roc_can::constraint::{Constraint, Constraints};
use roc_can::env::Env; use roc_can::env::Env;
use roc_can::expected::Expected; use roc_can::expected::Expected;
use roc_can::expr::{canonicalize_expr, Expr, Output}; use roc_can::expr::{canonicalize_expr, Expr, Output};
@ -28,16 +28,14 @@ pub fn test_home() -> ModuleId {
pub fn infer_expr( pub fn infer_expr(
subs: Subs, subs: Subs,
problems: &mut Vec<solve::TypeError>, problems: &mut Vec<solve::TypeError>,
constraints: &Constraints,
constraint: &Constraint, constraint: &Constraint,
expr_var: Variable, expr_var: Variable,
) -> (Content, Subs) { ) -> (Content, Subs) {
let env = solve::Env::default(); let env = solve::Env::default();
let (solved, _) = solve::run(&env, problems, subs, constraint); let (solved, _) = solve::run(constraints, &env, problems, subs, constraint);
let content = solved let content = *solved.inner().get_content_without_compacting(expr_var);
.inner()
.get_content_without_compacting(expr_var)
.clone();
(content, solved.into_inner()) (content, solved.into_inner())
} }
@ -96,6 +94,7 @@ pub struct CanExprOut {
pub var_store: VarStore, pub var_store: VarStore,
pub var: Variable, pub var: Variable,
pub constraint: Constraint, pub constraint: Constraint,
pub constraints: Constraints,
} }
#[derive(Debug)] #[derive(Debug)]
@ -152,9 +151,11 @@ pub fn can_expr_with<'a>(
&loc_expr.value, &loc_expr.value,
); );
let mut constraints = Constraints::new();
let constraint = constrain_expr( let constraint = constrain_expr(
&mut constraints,
&roc_constrain::expr::Env { &roc_constrain::expr::Env {
rigids: ImMap::default(), rigids: MutMap::default(),
home, home,
}, },
loc_expr.region, loc_expr.region,
@ -174,7 +175,7 @@ pub fn can_expr_with<'a>(
//load builtin values //load builtin values
let (_introduced_rigids, constraint) = let (_introduced_rigids, constraint) =
constrain_imported_values(imports, constraint, &mut var_store); constrain_imported_values(&mut constraints, imports, constraint, &mut var_store);
let mut all_ident_ids = MutMap::default(); let mut all_ident_ids = MutMap::default();
@ -200,6 +201,7 @@ pub fn can_expr_with<'a>(
interns, interns,
var, var,
constraint, constraint,
constraints,
}) })
} }

View file

@ -62,6 +62,7 @@ mod test_reporting {
output, output,
var_store, var_store,
var, var,
constraints,
constraint, constraint,
home, home,
interns, interns,
@ -79,7 +80,8 @@ mod test_reporting {
} }
let mut unify_problems = Vec::new(); let mut unify_problems = Vec::new();
let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); let (_content, mut subs) =
infer_expr(subs, &mut unify_problems, &constraints, &constraint, var);
name_all_type_vars(var, &mut subs); name_all_type_vars(var, &mut subs);
@ -8440,4 +8442,127 @@ I need all branches in an `if` to have the same type!
), ),
) )
} }
#[test]
fn let_polymorphism_with_scoped_type_variables() {
report_problem_as(
indoc!(
r#"
f : a -> a
f = \x ->
y : a -> a
y = \z -> z
n = y 1u8
x1 = y x
(\_ -> x1) n
f
"#
),
indoc!(
r#"
TYPE MISMATCH
The 1st argument to `y` is not what I expect:
6 n = y 1u8
^^^
This argument is an integer of type:
U8
But `y` needs the 1st argument to be:
a
Tip: The type annotation uses the type variable `a` to say that this
definition can produce any type of value. But in the body I see that
it will only produce a `U8` value of a single specific type. Maybe
change the type annotation to be more specific? Maybe change the code
to be more general?
"#
),
)
}
#[test]
fn non_exhaustive_with_guard() {
report_problem_as(
indoc!(
r#"
x : [A]
when x is
A if True -> ""
"#
),
indoc!(
r#"
UNSAFE PATTERN
This `when` does not cover all the possibilities:
2> when x is
3> A if True -> ""
Other possibilities include:
A (note the lack of an if clause)
I would have to crash if I saw one of those! Add branches for them!
"#
),
)
}
#[test]
fn invalid_record_extension_type() {
report_problem_as(
indoc!(
r#"
f : { x : Nat }U32
f
"#
),
indoc!(
r#"
INVALID_EXTENSION_TYPE
This record extension type is invalid:
1 f : { x : Nat }U32
^^^
Note: A record extension variable can only contain a type variable or
another record.
"#
),
)
}
#[test]
fn invalid_tag_extension_type() {
report_problem_as(
indoc!(
r#"
f : [ A ]Str
f
"#
),
indoc!(
r#"
INVALID_EXTENSION_TYPE
This tag union extension type is invalid:
1 f : [ A ]Str
^^^
Note: A tag union extension variable can only contain a type variable
or another tag union.
"#
),
)
}
} }

View file

@ -8,6 +8,9 @@ readme = "README.md"
repository = "https://github.com/rtfeldman/roc" repository = "https://github.com/rtfeldman/roc"
version = "0.1.0" version = "0.1.0"
[dependencies]
static_assertions = "0.1"
[dev-dependencies] [dev-dependencies]
indoc = "1.0.3" indoc = "1.0.3"
pretty_assertions = "1.0.0" pretty_assertions = "1.0.0"

View file

@ -1,9 +1,11 @@
#![crate_type = "lib"] #![crate_type = "lib"]
#![no_std] // #![no_std]
use core::ffi::c_void; use core::ffi::c_void;
use core::fmt; use core::fmt;
use core::mem::{ManuallyDrop, MaybeUninit}; use core::mem::{ManuallyDrop, MaybeUninit};
use core::ops::Drop; use core::ops::Drop;
use core::str;
use std::io::Write;
mod rc; mod rc;
mod roc_list; mod roc_list;
@ -214,9 +216,10 @@ impl RocDec {
pub const MIN: Self = Self(i128::MIN); pub const MIN: Self = Self(i128::MIN);
pub const MAX: Self = Self(i128::MAX); pub const MAX: Self = Self(i128::MAX);
pub const DECIMAL_PLACES: u32 = 18; const DECIMAL_PLACES: usize = 18;
const ONE_POINT_ZERO: i128 = 10i128.pow(Self::DECIMAL_PLACES as u32);
pub const ONE_POINT_ZERO: i128 = 10i128.pow(Self::DECIMAL_PLACES); const MAX_DIGITS: usize = 39;
const MAX_STR_LENGTH: usize = Self::MAX_DIGITS + 2; // + 2 here to account for the sign & decimal dot
#[allow(clippy::should_implement_trait)] #[allow(clippy::should_implement_trait)]
pub fn from_str(value: &str) -> Option<Self> { pub fn from_str(value: &str) -> Option<Self> {
@ -231,7 +234,7 @@ impl RocDec {
}; };
let opt_after_point = match parts.next() { let opt_after_point = match parts.next() {
Some(answer) if answer.len() <= Self::DECIMAL_PLACES as usize => Some(answer), Some(answer) if answer.len() <= Self::DECIMAL_PLACES => Some(answer),
_ => None, _ => None,
}; };
@ -247,7 +250,7 @@ impl RocDec {
Ok(answer) => { Ok(answer) => {
// Translate e.g. the 1 from 0.1 into 10000000000000000000 // Translate e.g. the 1 from 0.1 into 10000000000000000000
// by "restoring" the elided trailing zeroes to the number! // by "restoring" the elided trailing zeroes to the number!
let trailing_zeroes = Self::DECIMAL_PLACES as usize - after_point.len(); let trailing_zeroes = Self::DECIMAL_PLACES - after_point.len();
let lo = answer * 10i128.pow(trailing_zeroes as u32); let lo = answer * 10i128.pow(trailing_zeroes as u32);
if !before_point.starts_with('-') { if !before_point.starts_with('-') {
@ -266,7 +269,7 @@ impl RocDec {
// Calculate the high digits - the ones before the decimal point. // Calculate the high digits - the ones before the decimal point.
match before_point.parse::<i128>() { match before_point.parse::<i128>() {
Ok(answer) => match answer.checked_mul(10i128.pow(Self::DECIMAL_PLACES)) { Ok(answer) => match answer.checked_mul(Self::ONE_POINT_ZERO) {
Some(hi) => hi.checked_add(lo).map(Self), Some(hi) => hi.checked_add(lo).map(Self),
None => None, None => None,
}, },
@ -277,4 +280,73 @@ impl RocDec {
pub fn from_str_to_i128_unsafe(val: &str) -> i128 { pub fn from_str_to_i128_unsafe(val: &str) -> i128 {
Self::from_str(val).unwrap().0 Self::from_str(val).unwrap().0
} }
fn to_str_helper(&self, bytes: &mut [u8; Self::MAX_STR_LENGTH]) -> usize {
if self.0 == 0 {
write!(&mut bytes[..], "{}", "0").unwrap();
return 1;
}
let is_negative = (self.0 < 0) as usize;
static_assertions::const_assert!(Self::DECIMAL_PLACES + 1 == 19);
// The :019 in the following write! is computed as Self::DECIMAL_PLACES + 1. If you change
// Self::DECIMAL_PLACES, this assert should remind you to change that format string as
// well.
//
// By using the :019 format, we're guaranteeing that numbers less than 1, say 0.01234
// get their leading zeros placed in bytes for us. i.e. bytes = b"0012340000000000000"
write!(&mut bytes[..], "{:019}", self.0).unwrap();
// If self represents 1234.5678, then bytes is b"1234567800000000000000".
let mut i = Self::MAX_STR_LENGTH - 1;
// Find the last place where we have actual data.
while bytes[i] == 0 {
i = i - 1;
}
// At this point i is 21 because bytes[21] is the final '0' in b"1234567800000000000000".
let decimal_location = i - Self::DECIMAL_PLACES + 1 + is_negative;
// decimal_location = 4
while bytes[i] == ('0' as u8) && i >= decimal_location {
bytes[i] = 0;
i = i - 1;
}
// Now i = 7, because bytes[7] = '8', and bytes = b"12345678"
if i < decimal_location {
// This means that we've removed trailing zeros and are left with an integer. Our
// convention is to print these without a decimal point or trailing zeros, so we're done.
return i + 1;
}
let ret = i + 1;
while i >= decimal_location {
bytes[i + 1] = bytes[i];
i = i - 1;
}
bytes[i + 1] = bytes[i];
// Now i = 4, and bytes = b"123455678"
bytes[decimal_location] = '.' as u8;
// Finally bytes = b"1234.5678"
ret + 1
}
pub fn to_str(&self) -> RocStr {
let mut bytes = [0 as u8; Self::MAX_STR_LENGTH];
let last_idx = self.to_str_helper(&mut bytes);
unsafe { RocStr::from_slice(&bytes[0..last_idx]) }
}
}
impl fmt::Display for RocDec {
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut bytes = [0 as u8; Self::MAX_STR_LENGTH];
let last_idx = self.to_str_helper(&mut bytes);
let result = unsafe { str::from_utf8_unchecked(&bytes[0..last_idx]) };
write!(fmtr, "{}", result)
}
} }

3
rust-toolchain.toml Normal file
View file

@ -0,0 +1,3 @@
[toolchain]
channel = "1.58.0" # make sure to update the rust version in Earthfile as well
profile = "default"

View file

@ -66,10 +66,7 @@ let
# tools for development environment # tools for development environment
less less
]) ++ (with unstable-pkgs; [ ]) ++ (with unstable-pkgs; [
rustc rustup
cargo
clippy
rustfmt
]); ]);
in pkgs.mkShell { in pkgs.mkShell {

View file

@ -470,7 +470,7 @@ where
K1: Into<K>, K1: Into<K>,
{ {
let id = id.into(); let id = id.into();
let id = self.get_root_key_without_compacting(id); let id = self.get_root_key(id);
self.value_mut(id) self.value_mut(id)
} }

View file

@ -12,13 +12,18 @@ rm -rf build/
cp -r public/ build/ cp -r public/ build/
pushd build pushd build
# grab the source code and copy it to Netlify's server; if it's not there, fail the build. # grab the source code and copy it to Netlify's server; if it's not there, fail the build.
wget https://github.com/rtfeldman/elm-css/files/8037422/roc-source-code.zip wget https://github.com/rtfeldman/elm-css/files/8037422/roc-source-code.zip
# grab the pre-compiled REPL and copy it to Netlify's server; if it's not there, fail the build. # grab the pre-compiled REPL and copy it to Netlify's server; if it's not there, fail the build.
wget https://github.com/brian-carroll/mock-repl/files/8167902/roc_repl_wasm.tar.gz wget https://github.com/brian-carroll/mock-repl/archive/refs/heads/deploy.zip
tar xzvf roc_repl_wasm.tar.gz unzip deploy.zip
rm roc_repl_wasm.tar.gz mv mock-repl-deploy/* .
rmdir mock-repl-deploy
rm deploy.zip
# Copy REPL webpage source files
cp -r ../../repl_www/public/* . cp -r ../../repl_www/public/* .
popd popd