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>
Tom Dohrmann <erbse.13@gmx.de>
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"
dependencies = [
"lazy_static",
"static_assertions",
"static_assertions 1.1.0",
]
[[package]]
@ -3357,6 +3357,7 @@ dependencies = [
"roc_problem",
"roc_region",
"roc_types",
"static_assertions 1.1.0",
"ven_graph",
]
@ -3426,6 +3427,7 @@ dependencies = [
name = "roc_constrain"
version = "0.1.0"
dependencies = [
"arrayvec 0.7.2",
"roc_builtins",
"roc_can",
"roc_collections",
@ -3659,7 +3661,7 @@ dependencies = [
"roc_ident",
"roc_region",
"snafu",
"static_assertions",
"static_assertions 1.1.0",
]
[[package]]
@ -3682,7 +3684,7 @@ dependencies = [
"roc_target",
"roc_types",
"roc_unify",
"static_assertions",
"static_assertions 1.1.0",
"ven_graph",
"ven_pretty",
]
@ -3718,7 +3720,7 @@ dependencies = [
name = "roc_region"
version = "0.1.0"
dependencies = [
"static_assertions",
"static_assertions 1.1.0",
]
[[package]]
@ -3760,6 +3762,7 @@ dependencies = [
"roc_parse",
"roc_region",
"roc_reporting",
"roc_std",
"roc_target",
"roc_types",
]
@ -3834,6 +3837,9 @@ dependencies = [
[[package]]
name = "roc_std"
version = "0.1.0"
dependencies = [
"static_assertions 0.1.1",
]
[[package]]
name = "roc_target"
@ -3858,7 +3864,7 @@ dependencies = [
"roc_error_macros",
"roc_module",
"roc_region",
"static_assertions",
"static_assertions 1.1.0",
"ven_ena",
]
@ -4278,6 +4284,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_assertions"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7"
[[package]]
name = "static_assertions"
version = "1.1.0"
@ -4566,7 +4578,7 @@ checksum = "1f559b464de2e2bdabcac6a210d12e9b5a5973c251e102c44c585c71d51bd78e"
dependencies = [
"cfg-if 1.0.0",
"rand",
"static_assertions",
"static_assertions 1.1.0",
]
[[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
prep-debian:
@ -93,7 +93,7 @@ test-rust:
RUN --mount=type=cache,target=$SCCACHE_DIR \
repl_test/test_wasm.sh && sccache --show-stats
# 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 \
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 roc_can::expected::{Expected, PExpected};
use roc_collections::all::{BumpMap, BumpMapDefault, Index, SendMap};
use roc_collections::all::{BumpMap, BumpMapDefault, HumanIndex, SendMap};
use roc_module::{
ident::{Lowercase, TagName},
symbol::Symbol,
@ -163,7 +163,7 @@ pub fn constrain_expr<'a>(
let elem_expected = Expected::ForReason(
Reason::ElemInList {
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
},
list_elem_type.shallow_clone(),
region,
@ -339,7 +339,7 @@ pub fn constrain_expr<'a>(
let reason = Reason::FnArg {
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);
@ -538,7 +538,7 @@ pub fn constrain_expr<'a>(
name.clone(),
arity,
AnnotationSource::TypedIfBranch {
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
num_branches,
region: ann_source.region(),
},
@ -559,7 +559,7 @@ pub fn constrain_expr<'a>(
name,
arity,
AnnotationSource::TypedIfBranch {
index: Index::zero_based(branches.len()),
index: HumanIndex::zero_based(branches.len()),
num_branches,
region: ann_source.region(),
},
@ -596,7 +596,7 @@ pub fn constrain_expr<'a>(
body,
Expected::ForReason(
Reason::IfBranch {
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
total_branches: branches.len(),
},
Type2::Variable(*expr_var),
@ -616,7 +616,7 @@ pub fn constrain_expr<'a>(
final_else_expr,
Expected::ForReason(
Reason::IfBranch {
index: Index::zero_based(branches.len()),
index: HumanIndex::zero_based(branches.len()),
total_branches: branches.len() + 1,
},
Type2::Variable(*expr_var),
@ -691,7 +691,7 @@ pub fn constrain_expr<'a>(
when_branch,
PExpected::ForReason(
PReason::WhenMatch {
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
},
cond_type.shallow_clone(),
pattern_region,
@ -700,7 +700,7 @@ pub fn constrain_expr<'a>(
name.clone(),
*arity,
AnnotationSource::TypedWhenBranch {
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
region: ann_source.region(),
},
typ.shallow_clone(),
@ -733,14 +733,14 @@ pub fn constrain_expr<'a>(
when_branch,
PExpected::ForReason(
PReason::WhenMatch {
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
},
cond_type.shallow_clone(),
pattern_region,
),
Expected::ForReason(
Reason::WhenBranch {
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
},
branch_type.shallow_clone(),
// TODO: when_branch.value.region,
@ -1065,7 +1065,7 @@ pub fn constrain_expr<'a>(
let reason = Reason::LowLevelOpArg {
op: *op,
arg_index: Index::zero_based(index),
arg_index: HumanIndex::zero_based(index),
};
let expected_arg =
Expected::ForReason(reason, arg_type.shallow_clone(), Region::zero());
@ -1681,7 +1681,7 @@ fn constrain_tag_pattern<'a>(
let expected = PExpected::ForReason(
PReason::TagArg {
tag_name: tag_name.clone(),
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
},
pattern_type,
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 {
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.

View file

@ -34,7 +34,7 @@ pub const FLAG_DEV: &str = "dev";
pub const FLAG_OPTIMIZE: &str = "optimize";
pub const FLAG_OPT_SIZE: &str = "opt-size";
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_LINK: &str = "roc-linker";
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 ROC_FILE: &str = "ROC_FILE";
pub const ROC_DIR: &str = "ROC_DIR";
pub const BACKEND: &str = "BACKEND";
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
@ -76,12 +75,11 @@ pub fn build_app<'a>() -> App<'a> {
.required(false),
)
.arg(
Arg::new(FLAG_BACKEND)
.long(FLAG_BACKEND)
.about("Choose a different backend")
// .requires(BACKEND)
.default_value(Backend::default().as_str())
.possible_values(Backend::OPTIONS)
Arg::new(FLAG_TARGET)
.long(FLAG_TARGET)
.about("Choose a different target")
.default_value(Target::default().as_str())
.possible_values(Target::OPTIONS)
.required(false),
)
.arg(
@ -212,12 +210,11 @@ pub fn build_app<'a>() -> App<'a> {
.required(false),
)
.arg(
Arg::new(FLAG_BACKEND)
.long(FLAG_BACKEND)
.about("Choose a different backend")
// .requires(BACKEND)
.default_value(Backend::default().as_str())
.possible_values(Backend::OPTIONS)
Arg::new(FLAG_TARGET)
.long(FLAG_TARGET)
.about("Choose a different target")
.default_value(Target::default().as_str())
.possible_values(Target::OPTIONS)
.required(false),
)
.arg(
@ -273,12 +270,12 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
use std::str::FromStr;
use BuildConfig::*;
let backend = match matches.value_of(FLAG_BACKEND) {
Some(name) => Backend::from_str(name).unwrap(),
None => Backend::default(),
let target = match matches.value_of(FLAG_TARGET) {
Some(name) => Target::from_str(name).unwrap(),
None => Target::default(),
};
let target = backend.to_triple();
let triple = target.to_triple();
let arena = Bump::new();
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 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!(
"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 res_binary_path = build_file(
&arena,
&target,
&triple,
src_dir,
path,
opt_level,
@ -377,7 +374,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
Ok(outcome.status_code())
}
BuildAndRun { roc_file_arg_index } => {
let mut cmd = match target.architecture {
let mut cmd = match triple.architecture {
Architecture::Wasm32 => {
// If possible, report the generated executable name relative to the current dir.
let generated_filename = binary_path
@ -398,7 +395,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
_ => Command::new(&binary_path),
};
if let Architecture::Wasm32 = target.architecture {
if let Architecture::Wasm32 = triple.architecture {
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");
}
enum Backend {
enum Target {
Host,
X86_32,
X86_64,
Wasm32,
}
impl Default for Backend {
impl Default for Target {
fn default() -> Self {
Backend::Host
Target::Host
}
}
impl Backend {
impl Target {
const fn as_str(&self) -> &'static str {
match self {
Backend::Host => "host",
Backend::X86_32 => "x86_32",
Backend::X86_64 => "x86_64",
Backend::Wasm32 => "wasm32",
Target::Host => "host",
Target::X86_32 => "x86_32",
Target::X86_64 => "x86_64",
Target::Wasm32 => "wasm32",
}
}
/// NOTE keep up to date!
const OPTIONS: &'static [&'static str] = &[
Backend::Host.as_str(),
Backend::X86_32.as_str(),
Backend::X86_64.as_str(),
Backend::Wasm32.as_str(),
Target::Host.as_str(),
Target::X86_32.as_str(),
Target::X86_64.as_str(),
Target::Wasm32.as_str(),
];
fn to_triple(&self) -> Triple {
let mut triple = Triple::unknown();
match self {
Backend::Host => Triple::host(),
Backend::X86_32 => {
Target::Host => Triple::host(),
Target::X86_32 => {
triple.architecture = Architecture::X86_32(X86_32Architecture::I386);
triple.binary_format = BinaryFormat::Elf;
@ -548,13 +545,13 @@ impl Backend {
triple
}
Backend::X86_64 => {
Target::X86_64 => {
triple.architecture = Architecture::X86_64;
triple.binary_format = BinaryFormat::Elf;
triple
}
Backend::Wasm32 => {
Target::Wasm32 => {
triple.architecture = Architecture::Wasm32;
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 {
write!(f, "{}", self.as_str())
}
}
impl std::str::FromStr for Backend {
impl std::str::FromStr for Target {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"host" => Ok(Backend::Host),
"x86_32" => Ok(Backend::X86_32),
"x86_64" => Ok(Backend::X86_64),
"wasm32" => Ok(Backend::Wasm32),
"host" => Ok(Target::Host),
"x86_32" => Ok(Target::X86_32),
"x86_64" => Ok(Target::X86_64),
"wasm32" => Ok(Target::Wasm32),
_ => Err(()),
}
}

View file

@ -64,15 +64,15 @@ mod cli_run {
}
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 = strip_colors(&err);
let err = strip_colors(err);
assert_multiline_str_eq!(err, expected.into());
}
fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) {
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 {
assert!(out.status.success());
@ -194,7 +194,7 @@ mod cli_run {
) {
assert_eq!(input_file, None, "Wasm does not support input files");
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());
if !compile_out.stderr.is_empty() {
@ -565,7 +565,7 @@ mod cli_run {
&file_name,
benchmark.stdin,
benchmark.executable_filename,
&["--backend=x86_32"],
&["--target=x86_32"],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending,
benchmark.use_valgrind,
@ -575,7 +575,7 @@ mod cli_run {
&file_name,
benchmark.stdin,
benchmark.executable_filename,
&["--backend=x86_32", "--optimize"],
&["--target=x86_32", "--optimize"],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending,
benchmark.use_valgrind,

View file

@ -61,15 +61,15 @@ pub export fn main() i32 {
// actually call roc to populate the callresult
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.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
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);
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
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.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
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);
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" }
ven_graph = { path = "../../vendor/pathfinding" }
bumpalo = { version = "3.8.0", features = ["collections"] }
static_assertions = "1.1.0"
[dev-dependencies]
pretty_assertions = "1.0.0"

View file

@ -29,6 +29,8 @@ pub struct IntroducedVariables {
// but a variable can only have one name. Therefore
// `ftv : SendMap<Variable, Lowercase>`.
pub wildcards: Vec<Variable>,
pub lambda_sets: Vec<Variable>,
pub inferred: Vec<Variable>,
pub var_by_name: SendMap<Lowercase, Variable>,
pub name_by_var: SendMap<Variable, Lowercase>,
pub host_exposed_aliases: MutMap<Symbol, Variable>,
@ -44,12 +46,22 @@ impl IntroducedVariables {
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) {
self.host_exposed_aliases.insert(symbol, var);
}
pub fn union(&mut self, other: &Self) {
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.name_by_var.extend(other.name_by_var.clone());
self.host_exposed_aliases
@ -280,7 +292,9 @@ fn can_annotation_help(
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))
}
@ -326,6 +340,7 @@ fn can_annotation_help(
let (type_arguments, lambda_set_variables, actual) =
instantiate_and_freshen_alias_type(
var_store,
introduced_variables,
&alias.type_variables,
args,
&alias.lambda_set_variables,
@ -505,19 +520,16 @@ fn can_annotation_help(
}
Record { fields, ext } => {
let ext_type = match ext {
Some(loc_ann) => can_annotation_help(
env,
&loc_ann.value,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
),
None => Type::EmptyRec,
};
let ext_type = can_extension_type(
env,
scope,
var_store,
introduced_variables,
local_aliases,
references,
ext,
roc_problem::can::ExtensionTypeKind::Record,
);
if fields.is_empty() {
match ext {
@ -546,19 +558,16 @@ fn can_annotation_help(
}
}
TagUnion { tags, ext, .. } => {
let ext_type = match ext {
Some(loc_ann) => can_annotation_help(
env,
&loc_ann.value,
loc_ann.region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
),
None => Type::EmptyTagUnion,
};
let ext_type = can_extension_type(
env,
scope,
var_store,
introduced_variables,
local_aliases,
references,
ext,
roc_problem::can::ExtensionTypeKind::TagUnion,
);
if tags.is_empty() {
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
// make a fresh unconstrained variable, and let the type solver fill it in for us 🤠
let var = var_store.fresh();
introduced_variables.insert_inferred(var);
Type::Variable(var)
}
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(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
type_variables: &[Loc<(Lowercase, Variable)>],
type_arguments: Vec<Type>,
lambda_set_variables: &[LambdaSet],
@ -657,6 +738,7 @@ pub fn instantiate_and_freshen_alias_type(
if let Type::Variable(var) = typ.0 {
let fresh = var_store.fresh();
substitutions.insert(var, Type::Variable(fresh));
introduced_variables.insert_lambda_set(fresh);
new_lambda_set_variables.push(LambdaSet(Type::Variable(fresh)));
} else {
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()))
.collect();
// TODO this gets ignored; is that a problem
let mut introduced_variables = IntroducedVariables::default();
instantiate_and_freshen_alias_type(
var_store,
&mut introduced_variables,
&opaque.type_variables,
fresh_arguments,
&opaque.lambda_set_variables,

View file

@ -1,175 +1,505 @@
use crate::expected::{Expected, PExpected};
use roc_collections::all::{MutSet, SendMap};
use roc_module::{ident::TagName, symbol::Symbol};
use roc_collections::soa::{Index, Slice};
use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
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
/// of a type. For example, `Present(t1, IncludesTag(A, []))` means that the
/// type `t1` must contain at least the tag `A`. The additive nature of these
/// constraints makes them behaviorally different from unification-based constraints.
#[derive(Debug, Clone, PartialEq)]
pub enum PresenceConstraint {
IncludesTag(TagName, Vec<Type>, Region, PatternCategory),
IsOpen,
Pattern(Region, PatternCategory, PExpected<Type>),
#[derive(Debug)]
pub struct Constraints {
pub constraints: Vec<Constraint>,
pub types: Vec<Type>,
pub variables: Vec<Variable>,
pub loc_symbols: Vec<(Symbol, Region)>,
pub let_constraints: Vec<LetConstraint>,
pub categories: Vec<Category>,
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)]
pub enum Constraint {
Eq(Type, Expected<Type>, Category, Region),
Store(Type, Variable, &'static str, u32),
Lookup(Symbol, Expected<Type>, Region),
Pattern(Region, PatternCategory, Type, PExpected<Type>),
Eq(Index<Type>, Index<Expected<Type>>, Index<Category>, Region),
Store(Index<Type>, Variable, Index<&'static str>, u32),
Lookup(Symbol, Index<Expected<Type>>, Region),
Pattern(
Index<Type>,
Index<PExpected<Type>>,
Index<PatternCategory>,
Region,
),
True, // Used for things that always unify, e.g. blanks and runtime errors
SaveTheEnvironment,
Let(Box<LetConstraint>),
And(Vec<Constraint>),
Present(Type, PresenceConstraint),
Let(Index<LetConstraint>),
And(Slice<Constraint>),
/// 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)]
pub struct LetConstraint {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
pub def_types: SendMap<Symbol, Loc<Type>>,
pub defs_constraint: Constraint,
pub ret_constraint: Constraint,
pub rigid_vars: Slice<Variable>,
pub flex_vars: Slice<Variable>,
pub def_types: DefTypes,
pub defs_and_ret_constraint: Index<(Constraint, Constraint)>,
}
// VALIDATE
#[derive(Default, Clone)]
struct Declared {
pub rigid_vars: MutSet<Variable>,
pub flex_vars: MutSet<Variable>,
}
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);
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct IncludesTag {
pub type_index: Index<Type>,
pub tag_name: TagName,
pub types: Slice<Type>,
pub pattern_category: Index<PatternCategory>,
pub region: Region,
}

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!
#[allow(clippy::too_many_arguments)]
#[allow(clippy::cognitive_complexity)]
@ -887,25 +913,25 @@ fn canonicalize_pending_def<'a>(
AnnotationOnly(_, loc_can_pattern, loc_ann) => {
// 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
let ann =
let type_annotation =
canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store);
// Record all the annotation's references in output.references.lookups
for symbol in ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_type_defs.insert(symbol);
for symbol in type_annotation.references.iter() {
output.references.lookups.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);
let typ = ann.typ;
let arity = typ.arity();
let arity = type_annotation.typ.arity();
let problem = match &loc_can_pattern.value {
Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed {
@ -963,33 +989,44 @@ fn canonicalize_pending_def<'a>(
}
};
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: typ.clone(),
introduced_variables: output.introduced_variables.clone(),
aliases: ann.aliases.clone(),
region: loc_ann.region,
}),
},
if let Pattern::Identifier(symbol) = loc_can_pattern.value {
let def = single_can_def(
loc_can_pattern,
loc_can_expr,
expr_var,
Some(Loc::at(loc_ann.region, type_annotation)),
vars_by_symbol.clone(),
);
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 { .. } => {
// invalid aliases and opaques (shadowed, incorrect patterns) get ignored
}
TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => {
let ann =
TypedBody(_loc_pattern, loc_can_pattern, loc_ann, loc_expr) => {
let type_annotation =
canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store);
// Record all the annotation's references in output.references.lookups
for symbol in ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_type_defs.insert(symbol);
for symbol in type_annotation.references.iter() {
output.references.lookups.insert(*symbol);
output.references.referenced_type_defs.insert(*symbol);
}
let typ = ann.typ;
for (symbol, alias) in ann.aliases.clone() {
for (symbol, alias) in type_annotation.aliases.clone() {
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
// 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
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.
//
// If we're assigning (UserId userId) = ... then this is certainly not a closure declaration,
// which also implies it's not a self tail call!
//
// Only defs of the form (foo = ...) can be closure declarations or self tail calls.
if let (
&ast::Pattern::Identifier(_name),
&Pattern::Identifier(ref defined_symbol),
&Closure(ClosureData {
if let Pattern::Identifier(symbol) = loc_can_pattern.value {
if let Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
return_type,
name: ref symbol,
name: ref closure_name,
ref arguments,
loc_body: ref body,
ref captured_symbols,
..
}),
) = (
&loc_pattern.value,
&loc_can_pattern.value,
&loc_can_expr.value,
) {
is_closure = true;
// 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
{
// 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(closure_name).unwrap_or_else(|| {
panic!(
"Tried to remove symbol {:?} from procedures, but it was not found: {:?}",
closure_name, env.closures
)
});
// renamed_closure_def = Some(&defined_symbol);
loc_can_expr.value = Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
return_type,
name: *defined_symbol,
captured_symbols: captured_symbols.clone(),
recursive: is_recursive,
arguments: arguments.clone(),
loc_body: body.clone(),
});
}
// 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(symbol, references);
// 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 scope.idents() {
if !vars_by_symbol.contains_key(symbol) {
continue;
}
// The closure is self tail recursive iff it tail calls itself (by defined name).
let is_recursive = match can_output.tail_call {
Some(tail_symbol) if tail_symbol == 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(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.
// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its
// parent commit for the bug this fixed!
if is_closure {
References::new()
} else {
can_output.references.clone()
};
let refs = References::new();
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(
*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,
}),
},
let def = single_can_def(
loc_can_pattern,
loc_can_expr,
expr_var,
Some(Loc::at(loc_ann.region, type_annotation)),
vars_by_symbol.clone(),
);
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
@ -1184,108 +1218,105 @@ fn canonicalize_pending_def<'a>(
// reset the tailcallable_symbol
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.
//
// If we're assigning (UserId userId) = ... then this is certainly not a closure declaration,
// which also implies it's not a self tail call!
//
// Only defs of the form (foo = ...) can be closure declarations or self tail calls.
if let (
&ast::Pattern::Identifier(_name),
&Pattern::Identifier(ref defined_symbol),
&Closure(ClosureData {
if let Pattern::Identifier(symbol) = loc_can_pattern.value {
if let Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
return_type,
name: ref symbol,
name: ref closure_name,
ref arguments,
loc_body: ref body,
ref captured_symbols,
..
}),
) = (
&loc_pattern.value,
&loc_can_pattern.value,
&loc_can_expr.value,
) {
is_closure = true;
// 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
{
// 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(closure_name).unwrap_or_else(|| {
panic!(
"Tried to remove symbol {:?} from procedures, but it was not found: {:?}",
closure_name, env.closures
)
});
loc_can_expr.value = Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
return_type,
name: *defined_symbol,
captured_symbols: captured_symbols.clone(),
recursive: is_recursive,
arguments: arguments.clone(),
loc_body: body.clone(),
});
}
// 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(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(tail_symbol) if tail_symbol == 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(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.
// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its
// parent commit for the bug this fixed!
if is_closure {
References::new()
} else {
can_output.references.clone()
};
let refs = References::new();
refs_by_symbol.insert(symbol, (loc_pattern.region, refs));
} else {
let refs = can_output.references.clone();
refs_by_symbol.insert(symbol, (loc_pattern.region, refs));
}
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,
},
let def = single_can_def(
loc_can_pattern,
loc_can_expr,
expr_var,
None,
vars_by_symbol.clone(),
);
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);

View file

@ -138,17 +138,7 @@ pub enum Expr {
field: Lowercase,
},
/// field accessor as a function, e.g. (.foo) expr
Accessor {
/// 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,
},
Accessor(AccessorData),
Update {
record_var: Variable,
@ -217,6 +207,70 @@ pub struct ClosureData {
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)]
pub struct Field {
pub var: Variable,
@ -735,15 +789,16 @@ pub fn canonicalize_expr<'a>(
)
}
ast::Expr::AccessorFunction(field) => (
Accessor {
Accessor(AccessorData {
name: env.gen_unique_symbol(),
function_var: var_store.fresh(),
record_var: var_store.fresh(),
ext_var: var_store.fresh(),
closure_var: var_store.fresh(),
closure_ext_var: var_store.fresh(),
field_var: var_store.fresh(),
field: (*field).into(),
},
}),
Output::default(),
),
ast::Expr::GlobalTag(tag) => {

View file

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

View file

@ -3,3 +3,4 @@
#![allow(clippy::large_enum_variant)]
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_can = { path = "../can" }
roc_builtins = { path = "../builtins" }
arrayvec = "0.7.2"

View file

@ -1,8 +1,7 @@
use roc_can::constraint::Constraint::{self, *};
use roc_can::constraint::LetConstraint;
use arrayvec::ArrayVec;
use roc_can::constraint::{Constraint, Constraints};
use roc_can::expected::Expected::{self, *};
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand};
use roc_collections::all::SendMap;
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_region::all::Region;
@ -12,8 +11,10 @@ use roc_types::types::Type::{self, *};
use roc_types::types::{AliasKind, Category};
#[must_use]
#[inline(always)]
pub fn add_numeric_bound_constr(
constrs: &mut Vec<Constraint>,
constraints: &mut Constraints,
num_constraints: &mut impl Extend<Constraint>,
num_type: Type,
bound: impl TypedNumericBound,
region: Region,
@ -27,12 +28,12 @@ pub fn add_numeric_bound_constr(
0 => total_num_type,
1 => {
let actual_type = Variable(range[0]);
constrs.push(Eq(
total_num_type.clone(),
Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region),
category,
region,
));
let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region);
let because_suffix =
constraints.equal_types(total_num_type.clone(), expected, category, region);
num_constraints.extend([because_suffix]);
total_num_type
}
_ => RangedNumber(Box::new(total_num_type), range),
@ -41,6 +42,7 @@ pub fn add_numeric_bound_constr(
#[inline(always)]
pub fn int_literal(
constraints: &mut Constraints,
num_var: Variable,
precision_var: Variable,
expected: Expected<Type>,
@ -49,31 +51,35 @@ pub fn int_literal(
) -> Constraint {
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 like "U8".
let mut constrs = ArrayVec::<_, 3>::new();
let num_type = add_numeric_bound_constr(
constraints,
&mut constrs,
Variable(num_var),
bound,
region,
Category::Num,
);
constrs.extend(vec![
Eq(
constrs.extend([
constraints.equal_types(
num_type.clone(),
ForReason(reason, num_int(Type::Variable(precision_var)), region),
Category::Int,
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)]
pub fn float_literal(
constraints: &mut Constraints,
num_var: Variable,
precision_var: Variable,
expected: Expected<Type>,
@ -82,29 +88,33 @@ pub fn float_literal(
) -> Constraint {
let reason = Reason::FloatLiteral;
let mut constrs = Vec::with_capacity(3);
let mut constrs = ArrayVec::<_, 3>::new();
let num_type = add_numeric_bound_constr(
constraints,
&mut constrs,
Variable(num_var),
bound,
region,
Category::Float,
);
constrs.extend(vec![
Eq(
constrs.extend([
constraints.equal_types(
num_type.clone(),
ForReason(reason, num_float(Type::Variable(precision_var)), region),
Category::Float,
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)]
pub fn num_literal(
constraints: &mut Constraints,
num_var: Variable,
expected: Expected<Type>,
region: Region,
@ -112,23 +122,20 @@ pub fn num_literal(
) -> Constraint {
let open_number_type = crate::builtins::num_num(Type::Variable(num_var));
let mut constrs = Vec::with_capacity(3);
let num_type =
add_numeric_bound_constr(&mut constrs, open_number_type, bound, region, Category::Num);
constrs.extend(vec![Eq(num_type, expected, Category::Num, region)]);
let mut constrs = ArrayVec::<_, 2>::new();
let num_type = add_numeric_bound_constr(
constraints,
&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)]
pub fn exists(flex_vars: Vec<Variable>, constraint: Constraint) -> Constraint {
Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars,
def_types: SendMap::default(),
defs_constraint: constraint,
ret_constraint: Constraint::True,
}))
let and_constraint = constraints.and_constraint(constrs);
constraints.exists([num_var], and_constraint)
}
#[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_can::constraint::{Constraint, LetConstraint};
use roc_can::constraint::{Constraint, Constraints};
use roc_can::def::Declaration;
use roc_collections::all::{MutMap, MutSet, SendMap};
use roc_module::symbol::{ModuleId, Symbol};
@ -17,13 +16,12 @@ pub enum ExposedModuleTypes {
Valid(MutMap<Symbol, SolvedType>, MutMap<Symbol, Alias>),
}
pub struct ConstrainedModule {
pub unused_imports: MutMap<ModuleId, Region>,
pub constraint: Constraint,
}
pub fn constrain_module(declarations: &[Declaration], home: ModuleId) -> Constraint {
constrain_decls(home, declarations)
pub fn constrain_module(
constraints: &mut Constraints,
declarations: &[Declaration],
home: ModuleId,
) -> Constraint {
crate::expr::constrain_decls(constraints, home, declarations)
}
#[derive(Debug, Clone)]
@ -33,11 +31,11 @@ pub struct Import {
}
pub fn constrain_imported_values(
constraints: &mut Constraints,
imports: Vec<Import>,
body_con: Constraint,
var_store: &mut VarStore,
) -> (Vec<Variable>, Constraint) {
use Constraint::*;
let mut def_types = SendMap::default();
let mut rigid_vars = Vec::new();
@ -84,24 +82,19 @@ pub fn constrain_imported_values(
(
rigid_vars.clone(),
Let(Box::new(LetConstraint {
rigid_vars,
flex_vars: Vec::new(),
def_types,
defs_constraint: True,
ret_constraint: body_con,
})),
constraints.let_constraint(rigid_vars, [], def_types, Constraint::True, body_con),
)
}
/// Run pre_constrain_imports to get imported_symbols and imported_aliases.
pub fn constrain_imports(
constraints: &mut Constraints,
imported_symbols: Vec<Import>,
constraint: Constraint,
var_store: &mut VarStore,
) -> 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
// for var in introduced_rigids {

View file

@ -1,10 +1,10 @@
use crate::builtins;
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::pattern::Pattern::{self, *};
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::symbol::Symbol;
use roc_region::all::{Loc, Region};
@ -27,7 +27,7 @@ pub struct PatternState {
/// definition has an annotation, we instead now add `x => Int`.
pub fn headers_from_annotation(
pattern: &Pattern,
annotation: &Loc<Type>,
annotation: &Loc<&Type>,
) -> Option<SendMap<Symbol, Loc<Type>>> {
let mut headers = SendMap::default();
// 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(
pattern: &Pattern,
annotation: &Loc<Type>,
annotation: &Loc<&Type>,
headers: &mut SendMap<Symbol, Loc<Type>>,
) -> bool {
match pattern {
Identifier(symbol) | Shadowed(_, _, symbol) => {
headers.insert(*symbol, annotation.clone());
let typ = Loc::at(annotation.region, annotation.value.clone());
headers.insert(*symbol, typ);
true
}
Underscore
@ -106,7 +107,7 @@ fn headers_from_annotation_help(
.all(|(arg_pattern, arg_type)| {
headers_from_annotation_help(
&arg_pattern.1.value,
&Loc::at(annotation.region, arg_type.clone()),
&Loc::at(annotation.region, arg_type),
headers,
)
})
@ -135,12 +136,13 @@ fn headers_from_annotation_help(
&& type_arguments.len() == pat_type_arguments.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;
headers_from_annotation_help(
&argument_pat.value,
&Loc::at(annotation.region, (**actual).clone()),
&Loc::at(annotation.region, actual),
headers,
)
}
@ -153,6 +155,7 @@ fn headers_from_annotation_help(
/// initialize the Vecs in PatternState using with_capacity
/// based on its knowledge of their lengths.
pub fn constrain_pattern(
constraints: &mut Constraints,
env: &Env,
pattern: &Pattern,
region: Region,
@ -167,20 +170,18 @@ pub fn constrain_pattern(
// A -> ""
// _ -> ""
// so, we know that "x" (in this case, a tag union) must be open.
state.constraints.push(Constraint::Present(
expected.get_type(),
PresenceConstraint::IsOpen,
));
state
.constraints
.push(constraints.is_open_type(expected.get_type()));
}
UnsupportedPattern(_) | MalformedPattern(_, _) | OpaqueNotInScope(..) => {
// Erroneous patterns don't add any constraints.
}
Identifier(symbol) | Shadowed(_, _, symbol) => {
state.constraints.push(Constraint::Present(
expected.get_type_ref().clone(),
PresenceConstraint::IsOpen,
));
state
.constraints
.push(constraints.is_open_type(expected.get_type_ref().clone()));
state.headers.insert(
*symbol,
Loc {
@ -196,6 +197,7 @@ pub fn constrain_pattern(
let num_type = builtins::num_num(Type::Variable(var));
let num_type = builtins::add_numeric_bound_constr(
constraints,
&mut state.constraints,
num_type,
bound,
@ -203,11 +205,11 @@ pub fn constrain_pattern(
Category::Num,
);
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Num,
state.constraints.push(constraints.equal_pattern_types(
num_type,
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
// case the bound is an alias.
let num_type = builtins::add_numeric_bound_constr(
constraints,
&mut state.constraints,
Type::Variable(num_var),
bound,
@ -225,7 +228,7 @@ pub fn constrain_pattern(
// Link the free num var with the int var and our expectation.
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!
Expected::NoExpectation(int_type),
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.
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Int,
state.constraints.push(constraints.equal_pattern_types(
Type::Variable(num_var),
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
// case the bound is an alias.
let num_type = builtins::add_numeric_bound_constr(
constraints,
&mut state.constraints,
Type::Variable(num_var),
bound,
@ -255,7 +259,7 @@ pub fn constrain_pattern(
// Link the free num var with the float var and our expectation.
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!
Expected::NoExpectation(float_type),
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.
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Float,
state.constraints.push(constraints.equal_pattern_types(
num_type, // TODO check me if something breaks!
expected,
PatternCategory::Float,
region,
));
}
StrLiteral(_) => {
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Str,
state.constraints.push(constraints.equal_pattern_types(
builtins::str_type(),
expected,
PatternCategory::Str,
region,
));
}
SingleQuote(_) => {
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Character,
state.constraints.push(constraints.equal_pattern_types(
builtins::num_u32(),
expected,
PatternCategory::Character,
region,
));
}
@ -322,36 +326,39 @@ pub fn constrain_pattern(
let field_type = match typ {
DestructType::Guard(guard_var, loc_guard) => {
state.constraints.push(Constraint::Present(
state.constraints.push(constraints.pattern_presence(
Type::Variable(*guard_var),
PresenceConstraint::Pattern(
region,
PatternCategory::PatternGuard,
PExpected::ForReason(
PReason::PatternGuard,
pat_type.clone(),
loc_guard.region,
),
PExpected::ForReason(
PReason::PatternGuard,
pat_type.clone(),
loc_guard.region,
),
PatternCategory::PatternGuard,
region,
));
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)
}
DestructType::Optional(expr_var, loc_expr) => {
state.constraints.push(Constraint::Present(
state.constraints.push(constraints.pattern_presence(
Type::Variable(*expr_var),
PresenceConstraint::Pattern(
region,
PatternCategory::PatternDefault,
PExpected::ForReason(
PReason::OptionalField,
pat_type.clone(),
loc_expr.region,
),
PExpected::ForReason(
PReason::OptionalField,
pat_type.clone(),
loc_expr.region,
),
PatternCategory::PatternDefault,
region,
));
state.vars.push(*expr_var);
@ -362,8 +369,13 @@ pub fn constrain_pattern(
loc_expr.region,
);
let expr_con =
constrain_expr(env, loc_expr.region, &loc_expr.value, expr_expected);
let expr_con = constrain_expr(
constraints,
env,
loc_expr.region,
&loc_expr.value,
expr_expected,
);
state.constraints.push(expr_con);
RecordField::Optional(pat_type)
@ -381,16 +393,18 @@ pub fn constrain_pattern(
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),
Expected::NoExpectation(record_type),
Category::Storage(std::file!(), std::line!()),
region,
);
let record_con = Constraint::Present(
let record_con = constraints.pattern_presence(
Type::Variable(*whole_var),
PresenceConstraint::Pattern(region, PatternCategory::Record, expected),
expected,
PatternCategory::Record,
region,
);
state.constraints.push(whole_con);
@ -412,29 +426,36 @@ pub fn constrain_pattern(
let expected = PExpected::ForReason(
PReason::TagArg {
tag_name: tag_name.clone(),
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
},
pattern_type,
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 whole_con = Constraint::Present(
let whole_con = constraints.includes_tag(
expected.clone().get_type(),
PresenceConstraint::IncludesTag(
tag_name.clone(),
argument_types.clone(),
region,
pat_category.clone(),
),
tag_name.clone(),
argument_types.clone(),
pat_category.clone(),
region,
);
let tag_con = Constraint::Present(
let tag_con = constraints.pattern_presence(
Type::Variable(*whole_var),
PresenceConstraint::Pattern(region, pat_category, expected),
expected,
pat_category,
region,
);
state.vars.push(*whole_var);
@ -466,6 +487,7 @@ pub fn constrain_pattern(
// First, add a constraint for the argument "who"
let arg_pattern_expected = PExpected::NoExpectation(arg_pattern_type.clone());
constrain_pattern(
constraints,
env,
&loc_arg_pattern.value,
loc_arg_pattern.region,
@ -474,7 +496,7 @@ pub fn constrain_pattern(
);
// 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),
Expected::NoExpectation(opaque_type),
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
// variables of the opaque type
// TODO: better expectation here
let link_type_variables_con = Constraint::Eq(
let link_type_variables_con = constraints.equal_types(
(**specialized_def_type).clone(),
Expected::NoExpectation(arg_pattern_type),
Category::OpaqueWrap(*opaque),
@ -492,9 +514,11 @@ pub fn constrain_pattern(
);
// 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),
PresenceConstraint::Pattern(region, PatternCategory::Opaque(*opaque), expected),
expected,
PatternCategory::Opaque(*opaque),
region,
);
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_region::all::Region;
use roc_std::RocDec;
@ -70,7 +73,7 @@ pub enum Error {
Redundant {
overall_region: Region,
branch_region: Region,
index: Index,
index: HumanIndex,
},
}

View file

@ -293,6 +293,9 @@ impl<'a> Formattable for TypeAnnotation<'a> {
SpaceBefore(ann, spaces) => {
buf.newline();
buf.indent(indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, 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;
}
}
/// 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 that everything under examples/ is formatted correctly
/// 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) {
let entry = entry.unwrap();
let path = entry.path();
if path.extension() == Some(&std::ffi::OsStr::new("roc")) {
if path.extension() == Some(std::ffi::OsStr::new("roc")) {
count += 1;
let src = std::fs::read_to_string(path).unwrap();
println!("Now trying to format {}", path.display());

View file

@ -66,10 +66,12 @@ impl RegTrait for AArch64FloatReg {
}
}
#[derive(Copy, Clone)]
pub struct AArch64Assembler {}
// AArch64Call may need to eventually be split by OS,
// but I think with how we use it, they may all be the same.
#[derive(Copy, Clone)]
pub struct AArch64Call {}
const STACK_ALIGNMENT: u8 = 16;
@ -281,7 +283,8 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg, AArch64Assembler> for AArch64C
AArch64Assembler,
AArch64Call,
>,
_args: &'a [Symbol],
_dst: &Symbol,
_args: &[Symbol],
_arg_layouts: &[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)]
fn mov_freg64_stack32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) {
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");
}
#[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)]
fn gte_reg64_reg64_reg64(
_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 roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_collections::all::MutMap;
use roc_error_macros::internal_error;
use roc_module::symbol::{Interns, Symbol};
use roc_mono::code_gen_help::CodeGenHelp;
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 std::marker::PhantomData;
@ -16,8 +19,10 @@ pub(crate) mod x86_64;
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>>:
Sized
Sized + Copy
{
const BASE_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>(
buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>,
args: &'a [Symbol],
dst: &Symbol,
args: &[Symbol],
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: &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.
/// 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.
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_freg64_freg64(
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_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_reg64_stack32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
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 lte_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
);
fn gte_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
@ -256,7 +289,7 @@ pub struct Backend64Bit<
free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>,
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>,
}
@ -328,7 +361,6 @@ impl<
self.join_map.clear();
self.free_map.clear();
self.buf.clear();
self.helper_proc_symbols.clear();
self.storage_manager.reset();
}
@ -462,7 +494,7 @@ impl<
&mut self,
dst: &Symbol,
fn_name: String,
args: &'a [Symbol],
args: &[Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) {
@ -479,6 +511,7 @@ impl<
CC::store_args(
&mut self.buf,
&mut self.storage_manager,
dst,
args,
arg_layouts,
ret_layout,
@ -523,52 +556,56 @@ impl<
.storage_manager
.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 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();
if let BranchInfo::None = branch_info {
// 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.
let jne_location = self.buf.len();
let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0);
// Create jump to next branch if cond_sym not equal to value.
// Since we don't know the offset yet, set it to 0 and overwrite later.
let jne_location = self.buf.len();
let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0);
// Build all statements in this branch.
self.build_stmt(stmt, ret_layout);
// 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 {
// Build all statements in this branch. Using storage as from before any branch.
self.storage_manager = base_storage.clone();
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,
);
// 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);
// 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>,
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.
// On jumps to the joinpoint, we will overwrite those locations as a way to "pass parameters" to the joinpoint.
self.storage_manager
.setup_joinpoint(&mut self.buf, id, parameters);
// Create jump to remaining.
let jmp_location = self.buf.len();
let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
self.join_map.insert(*id, bumpalo::vec![in self.env.arena]);
// 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.
self.join_map.insert(*id, self.buf.len() as u64);
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];
self.update_jmp_imm32_offset(
&mut tmp,
jmp_location as u64,
start_offset as u64,
self.buf.len() as u64,
);
// Build remainder of function.
self.build_stmt(remainder, ret_layout)
for (jmp_location, start_offset) in self
.join_map
.remove(id)
.unwrap_or_else(|| internal_error!("join point not defined"))
{
tmp.clear();
self.update_jmp_imm32_offset(&mut tmp, jmp_location, start_offset, join_location);
}
}
fn build_jump(
&mut self,
id: &JoinPointId,
args: &'a [Symbol],
args: &[Symbol],
arg_layouts: &[Layout<'a>],
_ret_layout: &Layout<'a>,
) {
@ -619,15 +661,8 @@ impl<
let jmp_location = self.buf.len();
let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
if let Some(offset) = self.join_map.get(id) {
let offset = *offset;
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,
);
if let Some(vec) = self.join_map.get_mut(id) {
vec.push((jmp_location as u64, start_offset as u64))
} else {
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>) {
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 src1_reg = self
.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(
&mut self,
dst: &Symbol,
@ -831,7 +888,7 @@ impl<
arg_layout: &Layout<'a>,
) {
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 src1_reg = self
.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) {
// 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 src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src);
ASM::mov_reg64_reg64(&mut self.buf, dst_reg, src_reg);
self.storage_manager
.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]) {
@ -870,6 +1087,43 @@ impl<
.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>) {
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_rules! single_register_integers {
() => {
Layout::Builtin(
Builtin::Bool
| Builtin::Int(
IntWidth::I8
| IntWidth::I16
| IntWidth::I32
| IntWidth::I64
| IntWidth::U8
| IntWidth::U16
| IntWidth::U32
| IntWidth::U64,
),
) | Layout::RecursivePointer
Layout::Builtin(Builtin::Bool | single_register_int_builtins!()) | Layout::RecursivePointer
};
}

View file

@ -1,6 +1,7 @@
use crate::{
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 roc_builtins::bitcode::{FloatWidth, IntWidth};
@ -9,7 +10,7 @@ use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_mono::{
ir::{JoinPointId, Param},
layout::{Builtin, Layout},
layout::{Builtin, Layout, TagIdIntType, UnionLayout},
};
use roc_target::TargetInfo;
use std::cmp::max;
@ -48,6 +49,9 @@ enum StackStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
base_offset: i32,
// Size on the stack in bytes.
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.
/// Note, this is also used for referencing a value within a struct/union.
@ -72,6 +76,7 @@ enum Storage<GeneralReg: RegTrait, FloatReg: RegTrait> {
NoData,
}
#[derive(Clone)]
pub struct StorageManager<
'a,
GeneralReg: RegTrait,
@ -177,6 +182,10 @@ impl<
self.fn_call_stack_size = 0;
}
pub fn target_info(&self) -> TargetInfo {
self.target_info
}
pub fn stack_size(&self) -> u32 {
self.stack_size
}
@ -323,20 +332,22 @@ impl<
);
reg
}
Stack(ReferencedPrimitive { 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.
Stack(ReferencedPrimitive {
base_offset,
size,
sign_extend,
}) => {
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.symbol_storage_map.insert(*sym, Reg(General(reg)));
self.free_reference(sym);
reg
}
Stack(ReferencedPrimitive { .. }) => {
todo!("loading referenced primitives")
}
Stack(Complex { .. }) => {
internal_error!("Cannot load large values into general registers: {}", sym)
}
@ -385,9 +396,9 @@ impl<
);
reg
}
Stack(ReferencedPrimitive { base_offset, size })
if base_offset % 8 == 0 && size == 8 =>
{
Stack(ReferencedPrimitive {
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.
let reg = self.get_float_reg(buf);
ASM::mov_freg64_base32(buf, reg, base_offset);
@ -444,9 +455,9 @@ impl<
debug_assert_eq!(base_offset % 8, 0);
ASM::mov_reg64_base32(buf, reg, *base_offset);
}
Stack(ReferencedPrimitive { base_offset, size })
if base_offset % 8 == 0 && *size == 8 =>
{
Stack(ReferencedPrimitive {
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.
ASM::mov_reg64_base32(buf, reg, *base_offset);
}
@ -493,9 +504,9 @@ impl<
debug_assert_eq!(base_offset % 8, 0);
ASM::mov_freg64_base32(buf, reg, *base_offset);
}
Stack(ReferencedPrimitive { base_offset, size })
if base_offset % 8 == 0 && *size == 8 =>
{
Stack(ReferencedPrimitive {
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.
ASM::mov_freg64_base32(buf, reg, *base_offset);
}
@ -522,11 +533,7 @@ impl<
) {
debug_assert!(index < field_layouts.len() as u64);
// This must be removed and reinserted for ownership and mutability reasons.
let owned_data = if let Some(owned_data) = self.allocation_map.remove(structure) {
owned_data
} else {
internal_error!("Unknown symbol: {}", structure);
};
let owned_data = self.remove_allocation_for_sym(structure);
self.allocation_map
.insert(*structure, Rc::clone(&owned_data));
match self.get_storage_for_sym(structure) {
@ -538,15 +545,19 @@ impl<
data_offset += field_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 size = layout.stack_size(self.target_info);
self.allocation_map.insert(*sym, owned_data);
self.symbol_storage_map.insert(
*sym,
Stack(if is_primitive(&layout) {
ReferencedPrimitive {
base_offset: data_offset,
size,
sign_extend: matches!(
layout,
Layout::Builtin(sign_extended_int_builtins!())
),
}
} else {
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.
pub fn create_struct(
&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.
/// 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.
/// Always interact with the stack using aligned 64bit movement.
fn copy_symbol_to_stack_offset(
pub fn copy_symbol_to_stack_offset(
&mut self,
buf: &mut Vec<'a, u8>,
to_offset: i32,
@ -616,32 +733,33 @@ impl<
let reg = self.load_to_float_reg(buf, sym);
ASM::mov_base32_freg64(buf, to_offset, reg);
}
// Layout::Struct(_) if layout.safe_to_memcpy() => {
// // self.storage_manager.with_tmp_float_reg(&mut self.buf, |buf, storage, )
// // if let Some(SymbolStorage::Base {
// // offset: from_offset,
// // size,
// // ..
// // }) = self.symbol_storage_map.get(sym)
// // {
// // debug_assert_eq!(
// // *size,
// // layout.stack_size(self.target_info),
// // "expected struct to have same size as data being stored in it"
// // );
// // for i in 0..layout.stack_size(self.target_info) as i32 {
// // ASM::mov_reg64_base32(&mut self.buf, tmp_reg, from_offset + i);
// // ASM::mov_base32_reg64(&mut self.buf, to_offset + i, tmp_reg);
// // }
// todo!()
// } else {
// internal_error!("unknown struct: {:?}", sym);
// }
// }
Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
let (from_offset, _) = self.stack_offset_and_size(sym);
self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
ASM::mov_reg64_base32(buf, reg, from_offset);
ASM::mov_base32_reg64(buf, to_offset, reg);
ASM::mov_reg64_base32(buf, reg, from_offset + 8);
ASM::mov_base32_reg64(buf, to_offset + 8, reg);
});
}
_ if layout.stack_size(self.target_info) == 0 => {}
_ if layout.safe_to_memcpy() && layout.stack_size(self.target_info) > 8 => {
let (from_offset, size) = self.stack_offset_and_size(sym);
debug_assert!(from_offset % 8 == 0);
debug_assert!(size % 8 == 0);
debug_assert_eq!(size, layout.stack_size(self.target_info));
self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
for i in (0..size as i32).step_by(8) {
ASM::mov_reg64_base32(buf, reg, from_offset + i);
ASM::mov_base32_reg64(buf, to_offset + i, reg);
}
});
}
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.
fn ensure_reg_free(
&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.
/// Note, used and free regs are expected to be updated outside of this function.
fn free_to_stack(
@ -739,9 +909,12 @@ impl<
pub fn stack_offset_and_size(&self, sym: &Symbol) -> (i32, u32) {
match self.get_storage_for_sym(sym) {
Stack(Primitive { base_offset, .. }) => (*base_offset, 8),
Stack(ReferencedPrimitive { base_offset, size } | Complex { base_offset, size }) => {
(*base_offset, *size)
}
Stack(
ReferencedPrimitive {
base_offset, size, ..
}
| Complex { base_offset, size },
) => (*base_offset, *size),
storage => {
internal_error!(
"Data not on the stack for sym ({}) with storage ({:?})",
@ -775,12 +948,33 @@ impl<
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.
pub fn ret_pointer_arg(&mut self, reg: GeneralReg) {
self.symbol_storage_map
.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.
@ -794,7 +988,7 @@ impl<
/// Later jumps to the join point can overwrite the stored locations to pass parameters.
pub fn setup_joinpoint(
&mut self,
buf: &mut Vec<'a, u8>,
_buf: &mut Vec<'a, u8>,
id: &JoinPointId,
params: &'a [Param<'a>],
) {
@ -812,12 +1006,19 @@ impl<
todo!("joinpoints with borrowed parameters");
}
// Claim a location for every join point parameter to be loaded at.
// Put everything on the stack for simplicity.
match layout {
single_register_integers!() => {
self.claim_general_reg(buf, symbol);
}
single_register_floats!() => {
self.claim_float_reg(buf, symbol);
single_register_layouts!() => {
let base_offset = self.claim_stack_size(8);
self.symbol_storage_map.insert(
*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);
@ -839,7 +1040,7 @@ impl<
&mut self,
buf: &mut Vec<'a, u8>,
id: &JoinPointId,
args: &'a [Symbol],
args: &[Symbol],
arg_layouts: &[Layout<'a>],
) {
// TODO: remove was use here and for current_storage to deal with borrow checker.
@ -856,28 +1057,45 @@ impl<
continue;
}
match wanted_storage {
Reg(General(reg)) => {
// Ensure the reg is free, if not free it.
self.ensure_reg_free(buf, General(*reg));
// Copy the value over to the reg.
self.load_to_specified_general_reg(buf, sym, *reg)
Reg(_) => {
internal_error!("Register storage is not allowed for jumping to joinpoint")
}
Reg(Float(reg)) => {
// 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, .. }) => {
Stack(Complex { base_offset, .. }) => {
// TODO: This might be better not to call.
// Maybe we want a more memcpy like method to directly get called here.
// That would also be capable of asserting the size.
// Maybe copy stack to stack or something.
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 => {}
Stack(Primitive { .. }) => {
internal_error!("Primitive stack storage is not allowed for jumping")
Stack(Primitive { reg: Some(_), .. }) => {
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.
fn free_reference(&mut self, sym: &Symbol) {
let owned_data = if let Some(owned_data) = self.allocation_map.remove(sym) {
owned_data
} else {
internal_error!("Unknown symbol: {:?}", sym);
};
let owned_data = self.remove_allocation_for_sym(sym);
if Rc::strong_count(&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> {
if let Some(storage) = self.symbol_storage_map.get(sym) {
storage

View file

@ -1,6 +1,7 @@
use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
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 roc_builtins::bitcode::{FloatWidth, IntWidth};
@ -63,8 +64,11 @@ impl RegTrait for X86_64FloatReg {
}
}
#[derive(Copy, Clone)]
pub struct X86_64Assembler {}
#[derive(Copy, Clone)]
pub struct X86_64WindowsFastcall {}
#[derive(Copy, Clone)]
pub struct X86_64SystemV {}
const STACK_ALIGNMENT: u8 = 16;
@ -215,6 +219,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
general_i += 1;
}
for (layout, sym) in args.iter() {
let stack_size = layout.stack_size(TARGET_INFO);
match layout {
single_register_integers!() => {
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");
}
}
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 => {
todo!("Loading args with layout {:?}", x);
}
@ -265,19 +277,28 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
X86_64Assembler,
X86_64SystemV,
>,
args: &'a [Symbol],
dst: &Symbol,
args: &[Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) {
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 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()) {
match layout {
single_register_integers!() => {
@ -326,7 +347,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
tmp_stack_offset += 8;
}
}
Layout::Builtin(Builtin::Str) => {
Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
if general_i + 1 < Self::GENERAL_PARAM_REGS.len() {
let (base_offset, _size) = storage_manager.stack_offset_and_size(sym);
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) > 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 => {
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 => 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]);
}
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_64WindowsFastcall,
>,
args: &'a [Symbol],
dst: &Symbol,
args: &[Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) {
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));
storage_manager.claim_stack_area(dst, ret_layout.stack_size(TARGET_INFO));
todo!("claim first parama reg for the address");
}
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;
}
}
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.
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)
}
#[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)]
fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) {
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);
}
#[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)]
fn gte_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
@ -1378,6 +1530,27 @@ fn mov_reg64_base64_offset32(
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.
#[inline(always)]
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());
}
/// `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)]
fn movsd_base64_offset32_freg64(
buf: &mut Vec<'_, u8>,
@ -1452,7 +1625,7 @@ fn movsd_base64_offset32_freg64(
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)]
fn movsd_freg64_base64_offset32(
buf: &mut Vec<'_, u8>,
@ -1585,6 +1758,12 @@ fn setl_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
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).
#[inline(always)]
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]
fn test_mov_reg64_stack32() {
let arena = bumpalo::Bump::new();

View file

@ -14,7 +14,7 @@ use roc_mono::ir::{
BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout,
SelfRecursive, Stmt,
};
use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds};
use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds, TagIdIntType, UnionLayout};
mod generic64;
mod object_builder;
@ -233,7 +233,7 @@ trait Backend<'a> {
fn build_jump(
&mut self,
id: &JoinPointId,
args: &'a [Symbol],
args: &[Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
);
@ -277,13 +277,7 @@ trait Backend<'a> {
self.load_literal_symbols(arguments);
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
} else {
self.build_inline_builtin(
sym,
*func_sym,
arguments,
arg_layouts,
ret_layout,
)
self.build_builtin(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);
}
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),
}
}
@ -501,6 +518,23 @@ trait Backend<'a> {
);
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 => {
debug_assert_eq!(
2,
@ -525,6 +559,30 @@ trait Backend<'a> {
arg_layouts,
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(
sym,
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
fn build_inline_builtin(
/// Builds a builtin functions that do not map directly to a low level
/// If the builtin is simple enough, it will be inlined.
fn build_builtin(
&mut self,
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.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),
}
}
@ -595,7 +662,7 @@ trait Backend<'a> {
&mut self,
dst: &Symbol,
fn_name: String,
args: &'a [Symbol],
args: &[Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
);
@ -633,6 +700,15 @@ trait Backend<'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.
fn build_num_gte(
&mut self,
@ -642,6 +718,27 @@ trait Backend<'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.
fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol);
@ -677,6 +774,28 @@ trait Backend<'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.
fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>);
@ -831,15 +950,16 @@ trait Backend<'a> {
parameters,
body: continuation,
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 {
self.set_last_seen(param.symbol, stmt);
}
self.scan_ast(continuation);
self.scan_ast(remainder);
self.scan_ast(continuation);
}
Stmt::Jump(JoinPointId(sym), symbols) => {
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(*sym, stmt);
for sym in *symbols {
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));
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);
}

View file

@ -5,14 +5,14 @@ use crossbeam::deque::{Injector, Stealer, Worker};
use crossbeam::thread;
use parking_lot::Mutex;
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::module::{canonicalize_module_defs, Module};
use roc_collections::all::{default_hasher, BumpMap, MutMap, MutSet};
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::symbol::{
IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, PackageQualified,
@ -229,6 +229,7 @@ fn start_phase<'a>(
module,
ident_ids,
module_timing,
constraints,
constraint,
var_store,
imported_modules,
@ -241,6 +242,7 @@ fn start_phase<'a>(
module,
ident_ids,
module_timing,
constraints,
constraint,
var_store,
imported_modules,
@ -391,7 +393,8 @@ struct ConstrainedModule {
module: Module,
declarations: Vec<Declaration>,
imported_modules: MutMap<ModuleId, Region>,
constraint: Constraint,
constraints: Constraints,
constraint: ConstraintSoa,
ident_ids: IdentIds,
var_store: VarStore,
dep_idents: MutMap<ModuleId, IdentIds>,
@ -728,7 +731,8 @@ enum BuildTask<'a> {
ident_ids: IdentIds,
imported_symbols: Vec<Import>,
module_timing: ModuleTiming,
constraint: Constraint,
constraints: Constraints,
constraint: ConstraintSoa,
var_store: VarStore,
declarations: Vec<Declaration>,
dep_idents: MutMap<ModuleId, IdentIds>,
@ -3027,7 +3031,8 @@ impl<'a> BuildTask<'a> {
module: Module,
ident_ids: IdentIds,
module_timing: ModuleTiming,
constraint: Constraint,
constraints: Constraints,
constraint: ConstraintSoa,
var_store: VarStore,
imported_modules: MutMap<ModuleId, Region>,
exposed_types: &mut SubsByModule,
@ -3057,6 +3062,7 @@ impl<'a> BuildTask<'a> {
module,
ident_ids,
imported_symbols,
constraints,
constraint,
var_store,
declarations,
@ -3073,7 +3079,8 @@ fn run_solve<'a>(
ident_ids: IdentIds,
mut module_timing: ModuleTiming,
imported_symbols: Vec<Import>,
constraint: Constraint,
mut constraints: Constraints,
constraint: ConstraintSoa,
mut var_store: VarStore,
decls: Vec<Declaration>,
dep_idents: MutMap<ModuleId, IdentIds>,
@ -3084,7 +3091,12 @@ fn run_solve<'a>(
// Finish constraining the module by wrapping the existing Constraint
// 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();
@ -3097,12 +3109,11 @@ fn run_solve<'a>(
..
} = module;
if false {
debug_assert!(constraint.validate(), "{:?}", &constraint);
}
// TODO
// if false { debug_assert!(constraint.validate(), "{:?}", &constraint); }
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
.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 {
module_id,
@ -3263,6 +3276,7 @@ fn canonicalize_and_constrain<'a>(
declarations: module_output.declarations,
imported_modules,
var_store,
constraints,
constraint,
ident_ids: module_output.ident_ids,
dep_idents,
@ -3745,6 +3759,7 @@ fn run_task<'a>(
module,
module_timing,
imported_symbols,
constraints,
constraint,
var_store,
ident_ids,
@ -3756,6 +3771,7 @@ fn run_task<'a>(
ident_ids,
module_timing,
imported_symbols,
constraints,
constraint,
var_store,
declarations,

View file

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

View file

@ -884,6 +884,10 @@ define_builtins! {
// used in dev backend
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" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias

View file

@ -1,5 +1,5 @@
use crate::ir::DestructType;
use roc_collections::all::Index;
use roc_collections::all::HumanIndex;
use roc_exhaustive::{
is_useful, Context, Ctor, Error, Guard, Literal, Pattern, RenderAs, TagId, Union,
};
@ -177,7 +177,10 @@ fn to_nonredundant_rows(
vec![Pattern::Ctor(
union,
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 {
vec![simplify(&loc_pat.value)]
@ -189,7 +192,7 @@ fn to_nonredundant_rows(
return Err(Error::Redundant {
overall_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
}
Accessor {
name,
function_var,
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,
};
Accessor(accessor_data) => {
let field_var = accessor_data.field_var;
let fresh_record_symbol = env.unique_symbol();
let loc_body = Loc::at_zero(body);
let arguments = vec![(
record_var,
Loc::at_zero(roc_can::pattern::Pattern::Identifier(record_symbol)),
)];
let ClosureData {
name,
function_type,
arguments,
loc_body,
..
} = accessor_data.to_closure_data(fresh_record_symbol);
match procs.insert_anonymous(
env,
name,
function_var,
function_type,
arguments,
loc_body,
*loc_body,
CapturedSymbols::None,
field_var,
layout_cache,
@ -3927,7 +3907,7 @@ pub fn with_hole<'a>(
Ok(_) => {
let raw_layout = return_on_layout_error!(
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 {
@ -5445,6 +5425,18 @@ pub fn from_can<'a>(
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) => {
// a variable is aliased, e.g.
//

View file

@ -74,7 +74,7 @@ impl<'a> RawFunctionLayout<'a> {
FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)),
RecursionVar { 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),
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")
} else {
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)),
RecursionVar { 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),
@ -968,7 +968,7 @@ impl<'a> Layout<'a> {
Ok(Layout::RecursivePointer)
} else {
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,
differing_recursion_region: Region,
},
InvalidExtensionType {
region: Region,
kind: ExtensionTypeKind,
},
}
#[derive(Clone, Debug, PartialEq)]
pub enum ExtensionTypeKind {
Record,
TagUnion,
}
#[derive(Clone, Debug, PartialEq)]

View file

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

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"#,
)
}
#[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};
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn roc_list_construction() {
let list = RocList::from_slice(&[1i64; 23]);
assert_eq!(&list, &list);

View file

@ -11,7 +11,7 @@ use crate::helpers::wasm::{assert_evals_to, expect_runtime_error_panic};
use indoc::indoc;
#[cfg(test)]
use roc_std::RocList;
use roc_std::{RocList, RocStr};
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
@ -272,7 +272,7 @@ fn empty_record() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn i64_record2_literal() {
assert_evals_to!(
indoc!(
@ -299,7 +299,7 @@ fn i64_record2_literal() {
// );
// }
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn f64_record2_literal() {
assert_evals_to!(
indoc!(
@ -401,7 +401,7 @@ fn bool_literal() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record() {
assert_evals_to!(
indoc!(
@ -652,7 +652,7 @@ fn optional_field_empty_record() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_2() {
assert_evals_to!(
indoc!(
@ -666,7 +666,7 @@ fn return_record_2() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_3() {
assert_evals_to!(
indoc!(
@ -680,7 +680,7 @@ fn return_record_3() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_4() {
assert_evals_to!(
indoc!(
@ -694,7 +694,7 @@ fn return_record_4() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_5() {
assert_evals_to!(
indoc!(
@ -708,7 +708,7 @@ fn return_record_5() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_6() {
assert_evals_to!(
indoc!(
@ -722,7 +722,7 @@ fn return_record_6() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_7() {
assert_evals_to!(
indoc!(
@ -792,7 +792,7 @@ fn return_record_float_float_float() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_nested_record() {
assert_evals_to!(
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")]
use crate::helpers::llvm::assert_evals_to;
// #[cfg(feature = "gen-dev")]
// use crate::helpers::dev::assert_evals_to;
#[cfg(feature = "gen-dev")]
use crate::helpers::dev::assert_evals_to;
#[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to;
@ -29,7 +29,7 @@ fn width_and_alignment_u8_u8() {
}
#[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() {
assert_evals_to!(
indoc!(
@ -49,7 +49,7 @@ fn applied_tag_nothing_ir() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn applied_tag_nothing() {
assert_evals_to!(
indoc!(
@ -69,7 +69,7 @@ fn applied_tag_nothing() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn applied_tag_just() {
assert_evals_to!(
indoc!(
@ -88,7 +88,7 @@ fn applied_tag_just() {
}
#[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() {
assert_evals_to!(
indoc!(
@ -314,7 +314,7 @@ fn gen_if_float() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn when_on_nothing() {
assert_evals_to!(
indoc!(
@ -333,7 +333,7 @@ fn when_on_nothing() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn when_on_just() {
assert_evals_to!(
indoc!(
@ -352,7 +352,7 @@ fn when_on_just() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn when_on_result() {
assert_evals_to!(
indoc!(
@ -371,7 +371,7 @@ fn when_on_result() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn when_on_these() {
assert_evals_to!(
indoc!(
@ -393,7 +393,7 @@ fn when_on_these() {
}
#[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() {
// this will produce a Chain internally
assert_evals_to!(
@ -410,7 +410,7 @@ fn match_on_two_values() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn pair_with_underscore() {
assert_evals_to!(
indoc!(
@ -427,7 +427,7 @@ fn pair_with_underscore() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn result_with_underscore() {
// This test revealed an issue with hashing Test values
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 roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Lowercase, TagName};
@ -134,20 +136,22 @@ fn find_names_needed(
}
}
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!
// User-defined names are already taken.
// 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.
// 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)) => {
for index in args.into_iter() {
@ -257,16 +261,19 @@ fn set_root_name(root: Variable, name: Lowercase, subs: &mut Subs) {
match old_content {
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);
}
RecursionVar {
opt_name: None,
structure,
} => {
let structure = *structure;
let name_index = SubsIndex::push_new(&mut subs.field_names, name);
let content = RecursionVar {
structure: *structure,
opt_name: Some(name),
structure,
opt_name: Some(name_index),
};
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::*;
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),
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 {
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),
},
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_symbol(env, *symbol, buf);
for var_index in args.into_iter() {
for var_index in args.named_type_arguments() {
let var = subs[var_index];
buf.push(' ');
write_content(

View file

@ -257,7 +257,10 @@ impl SolvedType {
// TODO should there be a SolvedType RecursionVar variant?
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),
Alias(symbol, args, actual_var, kind) => {
let mut new_args = Vec::with_capacity(args.len());
@ -401,7 +404,10 @@ impl SolvedType {
}
EmptyRecord => SolvedType::EmptyRecord,
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 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::symbol::Symbol;
use std::fmt;
@ -68,7 +68,50 @@ pub struct Subs {
pub field_names: Vec<Lowercase>,
pub record_fields: Vec<RecordField<()>>,
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 {
@ -284,6 +327,14 @@ impl<T> SubsIndex<T> {
_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> {
@ -736,8 +787,8 @@ impl Variable {
/// # Safety
///
/// This should only ever be called from tests!
pub unsafe fn unsafe_test_debug_variable(v: u32) -> Self {
/// It is not guaranteed that the variable is in bounds.
pub unsafe fn from_index(v: u32) -> Self {
debug_assert!(v >= Self::NUM_RESERVED_VARS as u32);
Variable(v)
}
@ -1241,14 +1292,15 @@ impl Subs {
let mut subs = Subs {
utable: UnificationTable::default(),
variables: Default::default(),
variables: Vec::new(),
tag_names,
field_names: Default::default(),
record_fields: Default::default(),
field_names: Vec::new(),
record_fields: Vec::new(),
// store an empty slice at the first position
// used for "TagOrFunction"
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
@ -1324,7 +1376,8 @@ impl Subs {
}
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);
self.set(var, desc);
@ -1432,6 +1485,7 @@ impl Subs {
mapper(self.get_ref_mut(key));
}
#[inline(always)]
pub fn get_rank_set_mark(&mut self, key: Variable, mark: Mark) -> Rank {
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>)> {
occurs(self, &ImSet::default(), var)
occurs(self, &[], var)
}
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_default!((Variable, Option<Lowercase>), 4 * 8);
#[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug)]
pub enum Content {
/// 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
/// change the Option in here from None to Some.
FlexVar(Option<Lowercase>),
FlexVar(Option<SubsIndex<Lowercase>>),
/// name given in a user-written annotation
RigidVar(Lowercase),
RigidVar(SubsIndex<Lowercase>),
/// name given to a recursion variable
RecursionVar {
structure: Variable,
opt_name: Option<Lowercase>,
opt_name: Option<SubsIndex<Lowercase>>,
},
Structure(FlatType),
Alias(Symbol, AliasVariables, Variable, AliasKind),
@ -1816,7 +1870,7 @@ impl Content {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug)]
pub enum FlatType {
Apply(Symbol, VariableSubsSlice),
Func(VariableSubsSlice, Variable, Variable),
@ -1824,7 +1878,7 @@ pub enum FlatType {
TagUnion(UnionTags, Variable),
FunctionOrTagUnion(SubsIndex<TagName>, Symbol, Variable),
RecursiveTagUnion(Variable, UnionTags, Variable),
Erroneous(Box<Problem>),
Erroneous(SubsIndex<Problem>),
EmptyRecord,
EmptyTagUnion,
}
@ -2380,7 +2434,7 @@ fn is_empty_record(subs: &Subs, mut var: Variable) -> bool {
fn occurs(
subs: &Subs,
seen: &ImSet<Variable>,
seen: &[Variable],
input_var: Variable,
) -> Result<(), (Variable, Vec<Variable>)> {
use self::Content::*;
@ -2395,9 +2449,9 @@ fn occurs(
FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => Ok(()),
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 {
Apply(_, args) => {
@ -2446,8 +2500,8 @@ fn occurs(
}
}
Alias(_, args, _, _) => {
let mut new_seen = seen.clone();
new_seen.insert(root_var);
let mut new_seen = seen.to_owned();
new_seen.push(root_var);
for var_index in args.into_iter() {
let var = subs[var_index];
@ -2457,8 +2511,8 @@ fn occurs(
Ok(())
}
RangedNumber(typ, _range_vars) => {
let mut new_seen = seen.clone();
new_seen.insert(root_var);
let mut new_seen = seen.to_owned();
new_seen.push(root_var);
short_circuit_help(subs, root_var, &new_seen, *typ)?;
// _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>(
subs: &Subs,
root_key: Variable,
seen: &ImSet<Variable>,
seen: &[Variable],
iter: T,
) -> Result<(), (Variable, Vec<Variable>)>
where
@ -2485,10 +2540,11 @@ where
Ok(())
}
#[inline(always)]
fn short_circuit_help(
subs: &Subs,
root_key: Variable,
seen: &ImSet<Variable>,
seen: &[Variable],
var: Variable,
) -> Result<(), (Variable, Vec<Variable>)> {
if let Err((v, mut vec)) = occurs(subs, seen, var) {
@ -2688,18 +2744,23 @@ fn get_var_names(
match desc.content {
Error | FlexVar(None) => taken_names,
FlexVar(Some(name)) => {
add_name(subs, 0, name, var, |name| FlexVar(Some(name)), taken_names)
}
FlexVar(Some(name_index)) => add_name(
subs,
0,
name_index,
var,
|name| FlexVar(Some(name)),
taken_names,
),
RecursionVar {
opt_name,
structure,
} => match opt_name {
Some(name) => add_name(
Some(name_index) => add_name(
subs,
0,
name,
name_index,
var,
|name| RecursionVar {
opt_name: Some(name),
@ -2710,7 +2771,7 @@ fn get_var_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| {
get_var_names(subs, subs[arg_var], answer)
@ -2800,14 +2861,16 @@ fn get_var_names(
fn add_name<F>(
subs: &mut Subs,
index: usize,
given_name: Lowercase,
given_name_index: SubsIndex<Lowercase>,
var: Variable,
content_from_name: F,
taken_names: ImMap<Lowercase, Variable>,
) -> ImMap<Lowercase, Variable>
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 {
given_name.clone()
} else {
@ -2819,7 +2882,9 @@ where
match taken_names.get(&indexed_name) {
None => {
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();
@ -2835,7 +2900,7 @@ where
add_name(
subs,
index + 1,
given_name,
given_name_index,
var,
content_from_name,
taken_names,
@ -2846,26 +2911,13 @@ where
}
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 {
ErrorType::Infinite
} else {
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);
subs.set_mark(var, desc.mark);
@ -2885,15 +2937,20 @@ fn content_to_err_type(
match content {
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) => {
let name = match opt_name {
Some(name) => name,
Some(name_index) => subs.field_names[name_index.index as usize].clone(),
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_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
}
@ -2902,15 +2959,19 @@ fn content_to_err_type(
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, .. } => {
let name = match opt_name {
Some(name) => name,
Some(name_index) => subs.field_names[name_index.index as usize].clone(),
None => {
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
}
@ -3137,8 +3198,9 @@ fn flat_type_to_err_type(
}
}
Erroneous(problem) => {
state.problems.push(*problem);
Erroneous(problem_index) => {
let problem = subs.problems[problem_index.index as usize].clone();
state.problems.push(problem);
ErrorType::Error
}
@ -3252,6 +3314,7 @@ struct StorageSubsOffsets {
field_names: u32,
record_fields: u32,
variable_slices: u32,
problems: u32,
}
impl StorageSubs {
@ -3271,6 +3334,7 @@ impl StorageSubs {
field_names: self.subs.field_names.len() as u32,
record_fields: self.subs.record_fields.len() as u32,
variable_slices: self.subs.variable_slices.len() as u32,
problems: self.subs.problems.len() as u32,
};
let offsets = StorageSubsOffsets {
@ -3280,6 +3344,7 @@ impl StorageSubs {
field_names: target.field_names.len() as u32,
record_fields: target.record_fields.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,
@ -3324,6 +3389,7 @@ impl StorageSubs {
target.tag_names.extend(self.subs.tag_names);
target.field_names.extend(self.subs.field_names);
target.record_fields.extend(self.subs.record_fields);
target.problems.extend(self.subs.problems);
debug_assert_eq!(
target.utable.len(),
@ -3340,6 +3406,7 @@ impl StorageSubs {
Self::offset_variable(&offsets, v)
}
}
fn offset_flat_type(offsets: &StorageSubsOffsets, flat_type: &FlatType) -> FlatType {
match flat_type {
FlatType::Apply(symbol, arguments) => {
@ -3368,7 +3435,9 @@ impl StorageSubs {
Self::offset_union_tags(offsets, *union_tags),
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::EmptyTagUnion => FlatType::EmptyTagUnion,
}
@ -3378,14 +3447,14 @@ impl StorageSubs {
use Content::*;
match content {
FlexVar(opt_name) => FlexVar(opt_name.clone()),
RigidVar(name) => RigidVar(name.clone()),
FlexVar(opt_name) => FlexVar(*opt_name),
RigidVar(name) => RigidVar(*name),
RecursionVar {
structure,
opt_name,
} => RecursionVar {
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)),
Alias(symbol, alias_variables, actual, kind) => Alias(
@ -3455,6 +3524,15 @@ impl StorageSubs {
slice
}
fn offset_problem(
offsets: &StorageSubsOffsets,
mut problem_index: SubsIndex<Problem>,
) -> SubsIndex<Problem> {
problem_index.index += offsets.problems;
problem_index
}
}
use std::cell::RefCell;

View file

@ -2,7 +2,7 @@ use crate::pretty_print::Parens;
use crate::subs::{
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_module::called_via::CalledVia;
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.
pub fn shallow_dealias(&self) -> &Self {
match self {
Type::Alias { actual, .. } => actual.shallow_dealias(),
_ => self,
let mut result = self;
while let Type::Alias { actual, .. } = result {
result = actual;
}
result
}
pub fn instantiate_aliases(
@ -1203,14 +1204,14 @@ pub struct TagUnionStructure<'a> {
pub enum PReason {
TypedArg {
opt_name: Option<Symbol>,
index: Index,
index: HumanIndex,
},
WhenMatch {
index: Index,
index: HumanIndex,
},
TagArg {
tag_name: TagName,
index: Index,
index: HumanIndex,
},
PatternGuard,
OptionalField,
@ -1219,12 +1220,12 @@ pub enum PReason {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnnotationSource {
TypedIfBranch {
index: Index,
index: HumanIndex,
num_branches: usize,
region: Region,
},
TypedWhenBranch {
index: Index,
index: HumanIndex,
region: Region,
},
TypedBody {
@ -1246,7 +1247,7 @@ impl AnnotationSource {
pub enum Reason {
FnArg {
name: Option<Symbol>,
arg_index: Index,
arg_index: HumanIndex,
},
FnCall {
name: Option<Symbol>,
@ -1254,28 +1255,28 @@ pub enum Reason {
},
LowLevelOpArg {
op: LowLevel,
arg_index: Index,
arg_index: HumanIndex,
},
ForeignCallArg {
foreign_symbol: ForeignSymbol,
arg_index: Index,
arg_index: HumanIndex,
},
FloatLiteral,
IntLiteral,
NumLiteral,
StrInterpolation,
WhenBranch {
index: Index,
index: HumanIndex,
},
WhenGuard,
ExpectCondition,
IfCondition,
IfBranch {
index: Index,
index: HumanIndex,
total_branches: usize,
},
ElemInList {
index: Index,
index: HumanIndex,
},
RecordUpdateValue(Lowercase),
RecordUpdateKeys(Symbol, SendMap<Lowercase, Region>),

View file

@ -331,7 +331,7 @@ fn unify_alias(
}
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)); }
@ -374,7 +374,7 @@ fn unify_structure(
match other {
FlexVar(_) => {
// 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
// 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::*;
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) => {
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)
}
(EmptyTagUnion, EmptyTagUnion) => merge(subs, ctx, Structure(left.clone())),
(EmptyTagUnion, EmptyTagUnion) => merge(subs, ctx, Structure(*left)),
(TagUnion(tags, ext), EmptyTagUnion) if tags.is_empty() => {
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 {
let problems = unify_pool(subs, pool, *ext1, *ext2, ctx.mode);
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)
} else {
problems
@ -1351,11 +1351,16 @@ fn unify_zip_slices(
}
#[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 {
FlexVar(_) => {
// If the other is flex, rigid wins!
merge(subs, ctx, RigidVar(name.clone()))
merge(subs, ctx, RigidVar(*name))
}
RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _, _) | RangedNumber(..) => {
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)
} else {
// We are treating rigid vars as flex vars; admit this
merge(subs, ctx, other.clone())
merge(subs, ctx, *other)
}
}
Error => {
@ -1378,13 +1383,13 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content
fn unify_flex(
subs: &mut Subs,
ctx: &Context,
opt_name: &Option<Lowercase>,
opt_name: &Option<SubsIndex<Lowercase>>,
other: &Content,
) -> Outcome {
match other {
FlexVar(None) => {
// 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(_))
@ -1396,7 +1401,7 @@ fn unify_flex(
// TODO special-case boolean here
// In all other cases, if left is flex, defer to right.
// (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),
@ -1408,7 +1413,7 @@ fn unify_recursion(
subs: &mut Subs,
pool: &mut Pool,
ctx: &Context,
opt_name: &Option<Lowercase>,
opt_name: &Option<SubsIndex<Lowercase>>,
structure: Variable,
other: &Content,
) -> Outcome {
@ -1419,7 +1424,7 @@ fn unify_recursion(
} => {
// 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!
let name = opt_name.clone().or_else(|| other_opt_name.clone());
let name = (*opt_name).or_else(|| *other_opt_name);
merge(
subs,
ctx,
@ -1441,7 +1446,7 @@ fn unify_recursion(
ctx,
RecursionVar {
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 cgmath::Vector2;
use fs_extra::dir::{copy, ls, CopyOptions, DirEntryAttr, DirEntryValue};
use futures::TryFutureExt;
use pipelines::RectResources;
use roc_ast::lang::env::Env;
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
let (gpu_device, cmd_queue) = futures::executor::block_on(async {
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.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,
create_device(
&instance,
&surface,
wgpu::PowerPreference::HighPerformance,
false,
)
.or_else(|_| create_device(&instance, &surface, wgpu::PowerPreference::LowPower, false))
.or_else(|_| {
create_device(
&instance,
&surface,
wgpu::PowerPreference::HighPerformance,
true,
)
.await
.expect("Request device")
})
.unwrap_or_else(|err| {
panic!("Failed to request device: `{}`", err);
})
.await
});
// 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(())
}
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(
all_rects: &[Rect],
encoder: &mut CommandEncoder,

View file

@ -49,4 +49,3 @@ closure4 = \_ ->
Task.succeed {}
|> Task.after (\_ -> Task.succeed x)
|> 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

View file

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

View file

@ -19,3 +19,12 @@ checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673"
[[package]]
name = "roc_std"
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]]
name = "roc_std"
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);
stdout.print("{d}\n", .{result}) catch unreachable;
// end time
var ts2: std.os.timespec = undefined;
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);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;

View file

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

View file

@ -19,3 +19,12 @@ checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714"
[[package]]
name = "roc_std"
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:
```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.

View file

@ -92,15 +92,15 @@ pub fn main() u8 {
// actually call roc to populate the callresult
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.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
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);
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);
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| {
if (i == 0) {
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);
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_region = {path = "../compiler/region"}
roc_reporting = {path = "../reporting"}
roc_std = {path = "../roc_std", default-features = false}
roc_target = {path = "../compiler/roc_target"}
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_region::all::{Loc, Region};
use roc_std::RocDec;
use roc_target::TargetInfo;
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> {
let (newtype_containers, content) = unroll_newtypes(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 {
Layout::Builtin(Builtin::Bool) => Ok(app
.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)) => {
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 {
U8 | I8 => {
// 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)) => {
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 {
F32 => helper!(f32),
F64 => helper!(f64),
@ -333,6 +327,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
Ok(result)
}
Layout::Builtin(Builtin::Decimal) => Ok(helper!(RocDec)),
Layout::Builtin(Builtin::Str) => {
let size = layout.stack_size(env.target_info) as usize;
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,
/// e.g. adding underscores for large numbers
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_std::RocDec;
pub mod eval;
pub mod gen;
@ -47,5 +48,10 @@ pub trait ReplAppMemory {
fn deref_f32(&self, addr: usize) -> f32;
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;
}

View file

@ -994,6 +994,7 @@ fn issue_2588_record_with_function_and_nonfunction() {
)
}
#[test]
fn opaque_apply() {
expect_success(
indoc!(
@ -1036,3 +1037,17 @@ fn opaque_pattern_and_call() {
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;
padding: 8px;
}
#history-text .input {
.history-item {
margin-bottom: 24px;
}
.history-item .input {
margin: 0;
margin-bottom: 8px;
}
#history-text .output {
margin-bottom: 16px;
.history-item .output {
margin: 0;
}
#history-text .output-ok {
.history-item .output-ok {
color: #0f8;
}
#history-text .output-error {
.history-item .output-error {
color: #f00;
}
.code {
@ -64,9 +68,7 @@ section.source {
display: flex;
flex-direction: column;
}
section.source textarea {
height: 32px;
padding: 8px;
margin-bottom: 16px;
}

View file

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

View file

@ -1,29 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<title>Roc REPL</title>
<link rel="stylesheet" href="/repl.css" />
</head>
<head>
<title>Roc REPL</title>
<link rel="stylesheet" href="/repl.css" />
</head>
<body>
<div class="body-wrapper">
<section class="text">
<h1>The rockin' Roc REPL</h1>
</section>
<body>
<div class="body-wrapper">
<section class="text">
<h1>The rockin' Roc REPL</h1>
</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="history">
<div class="scroll-wrap">
<div id="history-text" class="scroll code"></div>
</div>
</section>
<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>

View file

@ -1,7 +1,9 @@
use roc_collections::all::MutSet;
use roc_module::ident::{Ident, Lowercase, ModuleName};
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 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_NOT_APPLIED: &str = "OPAQUE TYPE NOT APPLIED";
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>(
alloc: &'b RocDocAllocator<'b>,
@ -492,6 +495,34 @@ pub fn can_problem<'b>(
title = NESTED_DATATYPE.to_string();
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 {

View file

@ -136,6 +136,8 @@ fn pattern_to_doc<'b>(
pattern_to_doc_help(alloc, pattern, false)
}
const AFTER_TAG_INDENT: &str = " ";
fn pattern_to_doc_help<'b>(
alloc: &'b RocDocAllocator<'b>,
pattern: roc_exhaustive::Pattern,
@ -160,7 +162,22 @@ fn pattern_to_doc_help<'b>(
},
Ctor(union, tag_id, args) => {
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) => {
let mut arg_docs = Vec::with_capacity(args.len());

View file

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

View file

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

View file

@ -62,6 +62,7 @@ mod test_reporting {
output,
var_store,
var,
constraints,
constraint,
home,
interns,
@ -79,7 +80,8 @@ mod test_reporting {
}
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);
@ -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"
version = "0.1.0"
[dependencies]
static_assertions = "0.1"
[dev-dependencies]
indoc = "1.0.3"
pretty_assertions = "1.0.0"

View file

@ -1,9 +1,11 @@
#![crate_type = "lib"]
#![no_std]
// #![no_std]
use core::ffi::c_void;
use core::fmt;
use core::mem::{ManuallyDrop, MaybeUninit};
use core::ops::Drop;
use core::str;
use std::io::Write;
mod rc;
mod roc_list;
@ -214,9 +216,10 @@ impl RocDec {
pub const MIN: Self = Self(i128::MIN);
pub const MAX: Self = Self(i128::MAX);
pub const DECIMAL_PLACES: u32 = 18;
pub const ONE_POINT_ZERO: i128 = 10i128.pow(Self::DECIMAL_PLACES);
const DECIMAL_PLACES: usize = 18;
const ONE_POINT_ZERO: i128 = 10i128.pow(Self::DECIMAL_PLACES as u32);
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)]
pub fn from_str(value: &str) -> Option<Self> {
@ -231,7 +234,7 @@ impl RocDec {
};
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,
};
@ -247,7 +250,7 @@ impl RocDec {
Ok(answer) => {
// Translate e.g. the 1 from 0.1 into 10000000000000000000
// 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);
if !before_point.starts_with('-') {
@ -266,7 +269,7 @@ impl RocDec {
// Calculate the high digits - the ones before the decimal point.
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),
None => None,
},
@ -277,4 +280,73 @@ impl RocDec {
pub fn from_str_to_i128_unsafe(val: &str) -> i128 {
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
less
]) ++ (with unstable-pkgs; [
rustc
cargo
clippy
rustfmt
rustup
]);
in pkgs.mkShell {

View file

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

View file

@ -12,13 +12,18 @@ rm -rf build/
cp -r public/ build/
pushd 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
# 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
tar xzvf roc_repl_wasm.tar.gz
rm roc_repl_wasm.tar.gz
wget https://github.com/brian-carroll/mock-repl/archive/refs/heads/deploy.zip
unzip deploy.zip
mv mock-repl-deploy/* .
rmdir mock-repl-deploy
rm deploy.zip
# Copy REPL webpage source files
cp -r ../../repl_www/public/* .
popd