mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
Merge branch 'trunk' of github.com:rtfeldman/roc into new-syntax-highlight
This commit is contained in:
commit
0ca66cc41b
120 changed files with 5459 additions and 2598 deletions
3
AUTHORS
3
AUTHORS
|
@ -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>
|
||||
|
|
|
@ -37,7 +37,7 @@ If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.ne
|
|||
|
||||
### Editor
|
||||
|
||||
The editor is a WIP and not ready yet to replace your favorite editor, although if you want to try it out on nix, read on.
|
||||
The editor is a :construction:WIP:construction: and not ready yet to replace your favorite editor, although if you want to try it out on nix, read on.
|
||||
`cargo run edit` should work from NixOS, if you use a nix-shell from inside another OS, follow the instructions below.
|
||||
|
||||
#### Nvidia GPU
|
||||
|
@ -196,20 +196,24 @@ export CPPFLAGS="-I/usr/local/opt/llvm/include"
|
|||
|
||||
### LLVM installation on Windows
|
||||
|
||||
Installing LLVM's prebuilt binaries doesn't seem to be enough for the `llvm-sys` crate that Roc depends on, so I had to build LLVM from source
|
||||
on Windows. After lots of help from [**@IanMacKenzie**](https://github.com/IanMacKenzie) (thank you, Ian!), here's what worked for me:
|
||||
**Warning** While `cargo build` works on windows, linking roc programs does not yet, see issue #2608. This also means the repl, the editor and many tests will not work on windows.
|
||||
Installing LLVM's prebuilt binaries doesn't seem to be enough for the `llvm-sys` crate that Roc depends on, so I had to follow the steps below:
|
||||
|
||||
1. I downloaded and installed [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16) (a full Visual Studio install should work tool; the Build Tools are just the CLI tools, which is all I wanted)
|
||||
1. In the installation configuration, under "additional components" I had to check both "C++ ATL for latest v142 build tools (x86 & x64)" and also "C++/CLI support for v142 build tools" [note: as of September 2021 this should no longer be necessary - the next time anyone tries this, please try it without this step and make a PR to delete this step if it's no longer needed!]
|
||||
1. I launched the "x64 Native Tools Command Prompt for Visual Studio 2019" application (note: not the similarly-named "x86" one!)
|
||||
1. Make sure [Python 2.7](https://www.python.org/) and [CMake 3.17](http://cmake.org/) are installed on your system.
|
||||
1. I followed most of the steps under LLVM's [building from source instructions](https://github.com/llvm/llvm-project#getting-the-source-code-and-building-llvm) up to the `cmake -G ...` command, which didn't work for me. Instead, at that point I did the following step.
|
||||
1. I ran `cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release ../llvm` to generate a NMake makefile.
|
||||
1. Once that completed, I ran `nmake` to build LLVM. (This took about 2 hours on my laptop.)
|
||||
1. Finally, I set an environment variable `LLVM_SYS_100_PREFIX` to point to the `build` directory where I ran the `cmake` command.
|
||||
1. I downloaded and installed [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16) (a full Visual Studio install should work too; the Build Tools are just the CLI tools, which is all I wanted)
|
||||
1. Download the custom LLVM 7z archive [here](https://github.com/PLC-lang/llvm-package-windows/releases/tag/v12.0.1).
|
||||
1. [Download 7-zip](https://www.7-zip.org/) to be able to extract this archive.
|
||||
1. Extract the 7z file to where you want to permanently keep the folder.
|
||||
1. In powershell, set the `LLVM_SYS_120_PREFIX` environment variable (check [here](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-7.2#saving-changes-to-environment-variables) to make this a permanent environment variable):
|
||||
```
|
||||
[Environment]::SetEnvironmentVariable(
|
||||
"Path",
|
||||
[Environment]::GetEnvironmentVariable("Path", "User") + ";C:\Users\anton\Downloads\LLVM-12.0.1-win64\bin",
|
||||
"User"
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
Once all that was done, `cargo` ran successfully for Roc!
|
||||
Once all that was done, `cargo build` ran successfully for Roc!
|
||||
|
||||
### Build speed on WSL/WSL2
|
||||
|
||||
|
|
51
Cargo.lock
generated
51
Cargo.lock
generated
|
@ -1222,7 +1222,7 @@ checksum = "1d428afc93ad288f6dffc1fa5f4a78201ad2eec33c5a522e51c181009eb09061"
|
|||
dependencies = [
|
||||
"byteorder",
|
||||
"dynasm",
|
||||
"memmap2 0.5.0",
|
||||
"memmap2 0.5.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -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]]
|
||||
|
@ -2095,9 +2095,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.5.0"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e"
|
||||
checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -3290,6 +3290,16 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_alias_analysis"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"morphic_lib",
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_ast"
|
||||
version = "0.1.0"
|
||||
|
@ -3313,6 +3323,7 @@ dependencies = [
|
|||
"roc_unify",
|
||||
"snafu",
|
||||
"ven_graph",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3373,6 +3384,7 @@ dependencies = [
|
|||
"roc_problem",
|
||||
"roc_region",
|
||||
"roc_types",
|
||||
"static_assertions",
|
||||
"ven_graph",
|
||||
]
|
||||
|
||||
|
@ -3442,6 +3454,7 @@ dependencies = [
|
|||
name = "roc_constrain"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.2",
|
||||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
|
@ -3587,6 +3600,7 @@ dependencies = [
|
|||
"bumpalo",
|
||||
"inkwell 0.1.0",
|
||||
"morphic_lib",
|
||||
"roc_alias_analysis",
|
||||
"roc_builtins",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
|
@ -3631,7 +3645,7 @@ dependencies = [
|
|||
"bumpalo",
|
||||
"clap 3.0.0-beta.5",
|
||||
"iced-x86",
|
||||
"memmap2 0.5.0",
|
||||
"memmap2 0.5.3",
|
||||
"object 0.26.2",
|
||||
"roc_build",
|
||||
"roc_collections",
|
||||
|
@ -3684,7 +3698,7 @@ dependencies = [
|
|||
"roc_ident",
|
||||
"roc_region",
|
||||
"snafu",
|
||||
"static_assertions",
|
||||
"static_assertions 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3707,7 +3721,7 @@ dependencies = [
|
|||
"roc_target",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"static_assertions",
|
||||
"static_assertions 1.1.0",
|
||||
"ven_graph",
|
||||
"ven_pretty",
|
||||
]
|
||||
|
@ -3744,7 +3758,7 @@ dependencies = [
|
|||
name = "roc_region"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"static_assertions",
|
||||
"static_assertions 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3786,6 +3800,7 @@ dependencies = [
|
|||
"roc_parse",
|
||||
"roc_region",
|
||||
"roc_reporting",
|
||||
"roc_std",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
]
|
||||
|
@ -3861,11 +3876,7 @@ dependencies = [
|
|||
name = "roc_std"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"indoc",
|
||||
"libc",
|
||||
"pretty_assertions",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
"static_assertions 0.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3891,7 +3902,7 @@ dependencies = [
|
|||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_region",
|
||||
"static_assertions",
|
||||
"static_assertions 1.1.0",
|
||||
"ven_ena",
|
||||
]
|
||||
|
||||
|
@ -3965,7 +3976,7 @@ checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
|
|||
[[package]]
|
||||
name = "rustyline"
|
||||
version = "9.1.1"
|
||||
source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc"
|
||||
source = "git+https://github.com/rtfeldman/rustyline?rev=e74333c#e74333c0d618896b88175bf06645108f996fe6d0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if 1.0.0",
|
||||
|
@ -3988,7 +3999,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "rustyline-derive"
|
||||
version = "0.6.0"
|
||||
source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc"
|
||||
source = "git+https://github.com/rtfeldman/rustyline?rev=e74333c#e74333c0d618896b88175bf06645108f996fe6d0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
|
@ -4311,6 +4322,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"
|
||||
|
@ -4599,7 +4616,7 @@ checksum = "1f559b464de2e2bdabcac6a210d12e9b5a5973c251e102c44c585c71d51bd78e"
|
|||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"rand",
|
||||
"static_assertions",
|
||||
"static_assertions 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -15,6 +15,7 @@ members = [
|
|||
"compiler/solve",
|
||||
"compiler/fmt",
|
||||
"compiler/mono",
|
||||
"compiler/alias_analysis",
|
||||
"compiler/test_mono",
|
||||
"compiler/load",
|
||||
"compiler/gen_llvm",
|
||||
|
@ -39,7 +40,6 @@ members = [
|
|||
"repl_eval",
|
||||
"repl_test",
|
||||
"repl_wasm",
|
||||
"roc_std",
|
||||
"test_utils",
|
||||
"utils",
|
||||
"docs",
|
||||
|
@ -51,6 +51,8 @@ exclude = [
|
|||
# The tests will still correctly build them.
|
||||
"cli_utils",
|
||||
"compiler/test_mono_macros",
|
||||
# `cargo build` would cause roc_std to be built with default features which errors on windows
|
||||
"roc_std",
|
||||
]
|
||||
# Needed to be able to run `cargo run -p roc_cli --no-default-features` -
|
||||
# see www/build.sh for more.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM rust:1.57.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
|
||||
|
||||
|
|
|
@ -21,10 +21,14 @@ roc_target = { path = "../compiler/roc_target" }
|
|||
roc_error_macros = { path = "../error_macros" }
|
||||
arrayvec = "0.7.2"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
libc = "0.2.106"
|
||||
page_size = "0.4.2"
|
||||
snafu = { version = "0.6.10", features = ["backtraces"] }
|
||||
ven_graph = { path = "../vendor/pathfinding" }
|
||||
libc = "0.2.106"
|
||||
|
||||
[dev-dependencies]
|
||||
indoc = "1.0.3"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3.9", features = ["memoryapi"]}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -10,12 +10,10 @@
|
|||
///
|
||||
/// Pages also use the node value 0 (all 0 bits) to mark nodes as unoccupied.
|
||||
/// This is important for performance.
|
||||
use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
|
||||
use std::any::type_name;
|
||||
use std::ffi::c_void;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::{align_of, size_of, MaybeUninit};
|
||||
use std::ptr::null;
|
||||
|
||||
pub const NODE_BYTES: usize = 32;
|
||||
|
||||
|
@ -108,14 +106,32 @@ impl Pool {
|
|||
// addresses from the OS which will be lazily translated into
|
||||
// physical memory one 4096-byte page at a time, once we actually
|
||||
// try to read or write in that page's address range.
|
||||
libc::mmap(
|
||||
null::<c_void>() as *mut c_void,
|
||||
bytes_to_mmap,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
|
||||
|
||||
libc::mmap(
|
||||
std::ptr::null_mut(),
|
||||
bytes_to_mmap,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use winapi::um::memoryapi::VirtualAlloc;
|
||||
use winapi::um::winnt::PAGE_READWRITE;
|
||||
use winapi::um::winnt::{MEM_COMMIT, MEM_RESERVE};
|
||||
|
||||
VirtualAlloc(
|
||||
std::ptr::null_mut(),
|
||||
bytes_to_mmap,
|
||||
MEM_COMMIT | MEM_RESERVE,
|
||||
PAGE_READWRITE,
|
||||
)
|
||||
}
|
||||
} as *mut [MaybeUninit<u8>; NODE_BYTES];
|
||||
|
||||
// This is our actual capacity, in nodes.
|
||||
|
@ -230,10 +246,24 @@ impl<T> std::ops::IndexMut<NodeId<T>> for Pool {
|
|||
impl Drop for Pool {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
libc::munmap(
|
||||
self.nodes as *mut c_void,
|
||||
NODE_BYTES * self.capacity as usize,
|
||||
);
|
||||
#[cfg(unix)]
|
||||
{
|
||||
libc::munmap(
|
||||
self.nodes as *mut c_void,
|
||||
NODE_BYTES * self.capacity as usize,
|
||||
);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use winapi::um::memoryapi::VirtualFree;
|
||||
use winapi::um::winnt::MEM_RELEASE;
|
||||
|
||||
VirtualFree(
|
||||
self.nodes as *mut c_void,
|
||||
NODE_BYTES * self.capacity as usize,
|
||||
MEM_RELEASE,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"#
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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(()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -20,4 +20,6 @@ serde = { version = "1.0.130", features = ["derive"] }
|
|||
serde-xml-rs = "0.5.1"
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
tempfile = "3.2.0"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
rlimit = "0.6.2"
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use crate::helpers::{example_file, run_cmd, run_roc};
|
||||
use criterion::{black_box, measurement::Measurement, BenchmarkGroup};
|
||||
use rlimit::{setrlimit, Resource};
|
||||
use std::path::Path;
|
||||
use std::{path::Path, thread};
|
||||
|
||||
const CFOLD_STACK_SIZE: usize = 8192 * 100000;
|
||||
|
||||
fn exec_bench_w_input<T: Measurement>(
|
||||
file: &Path,
|
||||
stdin_str: &str,
|
||||
stdin_str: &'static str,
|
||||
executable_filename: &str,
|
||||
expected_ending: &str,
|
||||
bench_group_opt: Option<&mut BenchmarkGroup<T>>,
|
||||
|
@ -31,7 +32,7 @@ fn exec_bench_w_input<T: Measurement>(
|
|||
|
||||
fn check_cmd_output(
|
||||
file: &Path,
|
||||
stdin_str: &str,
|
||||
stdin_str: &'static str,
|
||||
executable_filename: &str,
|
||||
expected_ending: &str,
|
||||
) {
|
||||
|
@ -41,11 +42,16 @@ fn check_cmd_output(
|
|||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
if cmd_str.contains("cfold") {
|
||||
increase_stack_limit();
|
||||
}
|
||||
let out = if cmd_str.contains("cfold") {
|
||||
let child = thread::Builder::new()
|
||||
.stack_size(CFOLD_STACK_SIZE)
|
||||
.spawn(move || run_cmd(&cmd_str, &[stdin_str], &[]))
|
||||
.unwrap();
|
||||
|
||||
let out = run_cmd(&cmd_str, &[stdin_str], &[]);
|
||||
child.join().unwrap()
|
||||
} else {
|
||||
run_cmd(&cmd_str, &[stdin_str], &[])
|
||||
};
|
||||
|
||||
if !&out.stdout.ends_with(expected_ending) {
|
||||
panic!(
|
||||
|
@ -69,7 +75,20 @@ fn bench_cmd<T: Measurement>(
|
|||
.to_string();
|
||||
|
||||
if cmd_str.contains("cfold") {
|
||||
increase_stack_limit();
|
||||
#[cfg(unix)]
|
||||
use rlimit::{setrlimit, Resource};
|
||||
#[cfg(unix)]
|
||||
setrlimit(
|
||||
Resource::STACK,
|
||||
CFOLD_STACK_SIZE as u64,
|
||||
CFOLD_STACK_SIZE as u64,
|
||||
)
|
||||
.expect("Failed to increase stack limit.");
|
||||
|
||||
#[cfg(windows)]
|
||||
println!("Skipping the cfold benchmark on windows, I can't adjust the stack size and use criterion at the same time.");
|
||||
#[cfg(windows)]
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(bench_group) = bench_group_opt {
|
||||
|
@ -85,12 +104,6 @@ fn bench_cmd<T: Measurement>(
|
|||
}
|
||||
}
|
||||
|
||||
fn increase_stack_limit() {
|
||||
let new_stack_limit = 8192 * 100000;
|
||||
setrlimit(Resource::STACK, new_stack_limit, new_stack_limit)
|
||||
.expect("Failed to increase stack limit.");
|
||||
}
|
||||
|
||||
pub fn bench_nqueens<T: Measurement>(bench_group_opt: Option<&mut BenchmarkGroup<T>>) {
|
||||
exec_bench_w_input(
|
||||
&example_file("benchmarks", "NQueens.roc"),
|
||||
|
|
13
compiler/alias_analysis/Cargo.toml
Normal file
13
compiler/alias_analysis/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
authors = ["The Roc Contributors"]
|
||||
edition = "2018"
|
||||
license = "UPL-1.0"
|
||||
name = "roc_alias_analysis"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
morphic_lib = {path = "../../vendor/morphic_lib"}
|
||||
roc_collections = {path = "../collections"}
|
||||
roc_module = {path = "../module"}
|
||||
roc_mono = {path = "../mono"}
|
||||
|
|
@ -8,11 +8,11 @@ use roc_collections::all::{MutMap, MutSet};
|
|||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
use crate::ir::{
|
||||
use roc_mono::ir::{
|
||||
Call, CallType, Expr, HigherOrderLowLevel, HostExposedLayouts, ListLiteralElement, Literal,
|
||||
ModifyRc, OptLevel, Proc, Stmt,
|
||||
};
|
||||
use crate::layout::{Builtin, Layout, RawFunctionLayout, UnionLayout};
|
||||
use roc_mono::layout::{Builtin, Layout, RawFunctionLayout, UnionLayout};
|
||||
|
||||
// just using one module for now
|
||||
pub const MOD_APP: ModName = ModName(b"UserApp");
|
||||
|
@ -110,7 +110,7 @@ fn bytes_as_ascii(bytes: &[u8]) -> String {
|
|||
|
||||
pub fn spec_program<'a, I>(
|
||||
opt_level: OptLevel,
|
||||
entry_point: crate::ir::EntryPoint<'a>,
|
||||
entry_point: roc_mono::ir::EntryPoint<'a>,
|
||||
procs: I,
|
||||
) -> Result<morphic_lib::Solutions>
|
||||
where
|
||||
|
@ -266,7 +266,7 @@ fn terrible_hack(builder: &mut FuncDefBuilder, block: BlockId, type_id: TypeId)
|
|||
}
|
||||
|
||||
fn build_entry_point(
|
||||
layout: crate::ir::ProcLayout,
|
||||
layout: roc_mono::ir::ProcLayout,
|
||||
func_name: FuncName,
|
||||
host_exposed_functions: &[([u8; SIZE], &[Layout])],
|
||||
) -> Result<FuncDef> {
|
||||
|
@ -363,7 +363,7 @@ fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet<UnionLayout<'a>>)>
|
|||
#[derive(Default)]
|
||||
struct Env<'a> {
|
||||
symbols: MutMap<Symbol, ValueId>,
|
||||
join_points: MutMap<crate::ir::JoinPointId, morphic_lib::ContinuationId>,
|
||||
join_points: MutMap<roc_mono::ir::JoinPointId, morphic_lib::ContinuationId>,
|
||||
type_names: MutSet<UnionLayout<'a>>,
|
||||
}
|
||||
|
||||
|
@ -711,7 +711,7 @@ fn call_spec(
|
|||
passed_function,
|
||||
..
|
||||
}) => {
|
||||
use crate::low_level::HigherOrder::*;
|
||||
use roc_mono::low_level::HigherOrder::*;
|
||||
|
||||
let array = passed_function.specialization_id.to_bytes();
|
||||
let spec_var = CalleeSpecVar(&array);
|
||||
|
@ -1196,7 +1196,7 @@ fn lowlevel_spec(
|
|||
block: BlockId,
|
||||
layout: &Layout,
|
||||
op: &LowLevel,
|
||||
update_mode: crate::ir::UpdateModeId,
|
||||
update_mode: roc_mono::ir::UpdateModeId,
|
||||
arguments: &[Symbol],
|
||||
) -> Result<ValueId> {
|
||||
use LowLevel::*;
|
||||
|
@ -1258,22 +1258,21 @@ fn lowlevel_spec(
|
|||
|
||||
builder.add_bag_get(block, bag)
|
||||
}
|
||||
ListSet => {
|
||||
ListReplaceUnsafe => {
|
||||
let list = env.symbols[&arguments[0]];
|
||||
let to_insert = env.symbols[&arguments[2]];
|
||||
|
||||
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
|
||||
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
|
||||
|
||||
// decrement the overwritten element
|
||||
let overwritten = builder.add_bag_get(block, bag)?;
|
||||
let _unit = builder.add_recursive_touch(block, overwritten)?;
|
||||
|
||||
let _unit = builder.add_update(block, update_mode_var, cell)?;
|
||||
let _unit1 = builder.add_touch(block, cell)?;
|
||||
let _unit2 = builder.add_update(block, update_mode_var, cell)?;
|
||||
|
||||
builder.add_bag_insert(block, bag, to_insert)?;
|
||||
|
||||
with_new_heap_cell(builder, block, bag)
|
||||
let old_value = builder.add_bag_get(block, bag)?;
|
||||
let new_list = with_new_heap_cell(builder, block, bag)?;
|
||||
builder.add_make_tuple(block, &[new_list, old_value])
|
||||
}
|
||||
ListSwap => {
|
||||
let list = env.symbols[&arguments[0]];
|
|
@ -46,6 +46,10 @@ pub fn link(
|
|||
operating_system: OperatingSystem::Darwin,
|
||||
..
|
||||
} => link_macos(target, output_path, input_paths, link_type),
|
||||
Triple {
|
||||
operating_system: OperatingSystem::Windows,
|
||||
..
|
||||
} => link_windows(target, output_path, input_paths, link_type),
|
||||
_ => panic!("TODO gracefully handle unsupported target: {:?}", target),
|
||||
}
|
||||
}
|
||||
|
@ -1049,6 +1053,15 @@ fn link_wasm32(
|
|||
Ok((child, output_path))
|
||||
}
|
||||
|
||||
fn link_windows(
|
||||
_target: &Triple,
|
||||
_output_path: PathBuf,
|
||||
_input_paths: &[&str],
|
||||
_link_type: LinkType,
|
||||
) -> io::Result<(Child, PathBuf)> {
|
||||
todo!("Add windows support to the surgical linker. See issue #2608.")
|
||||
}
|
||||
|
||||
#[cfg(feature = "llvm")]
|
||||
pub fn module_to_dylib(
|
||||
module: &inkwell::module::Module,
|
||||
|
|
|
@ -41,6 +41,11 @@ pub fn target_triple_str(target: &Triple) -> &'static str {
|
|||
operating_system: OperatingSystem::Darwin,
|
||||
..
|
||||
} => "x86_64-unknown-darwin10",
|
||||
Triple {
|
||||
architecture: Architecture::X86_64,
|
||||
operating_system: OperatingSystem::Windows,
|
||||
..
|
||||
} => "x86_64-pc-windows-gnu",
|
||||
_ => panic!("TODO gracefully handle unsupported target: {:?}", target),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ To add a builtin:
|
|||
2. Make sure the function is public with the `pub` keyword and uses the C calling convention. This is really easy, just add `pub` and `callconv(.C)` to the function declaration like so: `pub fn atan(num: f64) callconv(.C) f64 { ... }`
|
||||
3. In `src/main.zig`, export the function. This is also organized by module. For example, for a `Num` function find the `Num` section and add: `comptime { exportNumFn(num.atan, "atan"); }`. The first argument is the function, the second is the name of it in LLVM.
|
||||
4. In `compiler/builtins/src/bitcode.rs`, add a constant for the new function. This is how we use it in Rust. Once again, this is organized by module, so just find the relevant area and add your new function.
|
||||
5. You can now your function in Rust using `call_bitcode_fn` in `llvm/src/build.rs`!
|
||||
5. You can now use your function in Rust using `call_bitcode_fn` in `llvm/src/build.rs`!
|
||||
|
||||
## How it works
|
||||
|
||||
|
|
|
@ -26,21 +26,19 @@ pub const RocDec = extern struct {
|
|||
return .{ .num = num * one_point_zero_i128 };
|
||||
}
|
||||
|
||||
// TODO: There's got to be a better way to do this other than converting to Str
|
||||
pub fn fromF64(num: f64) ?RocDec {
|
||||
var digit_bytes: [19]u8 = undefined; // 19 = max f64 digits + '.' + '-'
|
||||
var result: f64 = num * comptime @intToFloat(f64, one_point_zero_i128);
|
||||
|
||||
var fbs = std.io.fixedBufferStream(digit_bytes[0..]);
|
||||
std.fmt.formatFloatDecimal(num, .{}, fbs.writer()) catch
|
||||
return null;
|
||||
|
||||
var dec = RocDec.fromStr(RocStr.init(&digit_bytes, fbs.pos));
|
||||
|
||||
if (dec) |d| {
|
||||
return d;
|
||||
} else {
|
||||
if (result > comptime @intToFloat(f64, math.maxInt(i128))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (result < comptime @intToFloat(f64, math.minInt(i128))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var ret: RocDec = .{ .num = @floatToInt(i128, result) };
|
||||
return ret;
|
||||
}
|
||||
|
||||
pub fn fromStr(roc_str: RocStr) ?RocDec {
|
||||
|
@ -729,6 +727,11 @@ test "fromF64" {
|
|||
try expectEqual(RocDec{ .num = 25500000000000000000 }, dec.?);
|
||||
}
|
||||
|
||||
test "fromF64 overflow" {
|
||||
var dec = RocDec.fromF64(1e308);
|
||||
try expectEqual(dec, null);
|
||||
}
|
||||
|
||||
test "fromStr: empty" {
|
||||
var roc_str = RocStr.init("", 0);
|
||||
var dec = RocDec.fromStr(roc_str);
|
||||
|
|
|
@ -1256,95 +1256,56 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
|
|||
return output;
|
||||
}
|
||||
|
||||
pub fn listSetInPlace(
|
||||
bytes: ?[*]u8,
|
||||
pub fn listReplaceInPlace(
|
||||
list: RocList,
|
||||
index: usize,
|
||||
element: Opaque,
|
||||
element_width: usize,
|
||||
dec: Dec,
|
||||
) callconv(.C) ?[*]u8 {
|
||||
out_element: ?[*]u8,
|
||||
) callconv(.C) RocList {
|
||||
// INVARIANT: bounds checking happens on the roc side
|
||||
//
|
||||
// at the time of writing, the function is implemented roughly as
|
||||
// `if inBounds then LowLevelListGet input index item else input`
|
||||
// `if inBounds then LowLevelListReplace input index item else input`
|
||||
// so we don't do a bounds check here. Hence, the list is also non-empty,
|
||||
// because inserting into an empty list is always out of bounds
|
||||
|
||||
return listSetInPlaceHelp(bytes, index, element, element_width, dec);
|
||||
return listReplaceInPlaceHelp(list, index, element, element_width, out_element);
|
||||
}
|
||||
|
||||
pub fn listSet(
|
||||
bytes: ?[*]u8,
|
||||
length: usize,
|
||||
pub fn listReplace(
|
||||
list: RocList,
|
||||
alignment: u32,
|
||||
index: usize,
|
||||
element: Opaque,
|
||||
element_width: usize,
|
||||
dec: Dec,
|
||||
) callconv(.C) ?[*]u8 {
|
||||
out_element: ?[*]u8,
|
||||
) callconv(.C) RocList {
|
||||
// INVARIANT: bounds checking happens on the roc side
|
||||
//
|
||||
// at the time of writing, the function is implemented roughly as
|
||||
// `if inBounds then LowLevelListGet input index item else input`
|
||||
// `if inBounds then LowLevelListReplace input index item else input`
|
||||
// so we don't do a bounds check here. Hence, the list is also non-empty,
|
||||
// because inserting into an empty list is always out of bounds
|
||||
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), bytes));
|
||||
|
||||
if ((ptr - 1)[0] == utils.REFCOUNT_ONE) {
|
||||
return listSetInPlaceHelp(bytes, index, element, element_width, dec);
|
||||
} else {
|
||||
return listSetImmutable(bytes, length, alignment, index, element, element_width, dec);
|
||||
}
|
||||
return listReplaceInPlaceHelp(list.makeUnique(alignment, element_width), index, element, element_width, out_element);
|
||||
}
|
||||
|
||||
inline fn listSetInPlaceHelp(
|
||||
bytes: ?[*]u8,
|
||||
inline fn listReplaceInPlaceHelp(
|
||||
list: RocList,
|
||||
index: usize,
|
||||
element: Opaque,
|
||||
element_width: usize,
|
||||
dec: Dec,
|
||||
) ?[*]u8 {
|
||||
out_element: ?[*]u8,
|
||||
) RocList {
|
||||
// the element we will replace
|
||||
var element_at_index = (bytes orelse undefined) + (index * element_width);
|
||||
var element_at_index = (list.bytes orelse undefined) + (index * element_width);
|
||||
|
||||
// decrement its refcount
|
||||
dec(element_at_index);
|
||||
// copy out the old element
|
||||
@memcpy(out_element orelse undefined, element_at_index, element_width);
|
||||
|
||||
// copy in the new element
|
||||
@memcpy(element_at_index, element orelse undefined, element_width);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
inline fn listSetImmutable(
|
||||
old_bytes: ?[*]u8,
|
||||
length: usize,
|
||||
alignment: u32,
|
||||
index: usize,
|
||||
element: Opaque,
|
||||
element_width: usize,
|
||||
dec: Dec,
|
||||
) ?[*]u8 {
|
||||
const data_bytes = length * element_width;
|
||||
|
||||
var new_bytes = utils.allocateWithRefcount(data_bytes, alignment);
|
||||
|
||||
@memcpy(new_bytes, old_bytes orelse undefined, data_bytes);
|
||||
|
||||
// the element we will replace
|
||||
var element_at_index = new_bytes + (index * element_width);
|
||||
|
||||
// decrement its refcount
|
||||
dec(element_at_index);
|
||||
|
||||
// copy in the new element
|
||||
@memcpy(element_at_index, element orelse undefined, element_width);
|
||||
|
||||
// consume RC token of original
|
||||
utils.decref(old_bytes, data_bytes, alignment);
|
||||
|
||||
//return list;
|
||||
return new_bytes;
|
||||
return list;
|
||||
}
|
||||
|
||||
pub fn listFindUnsafe(
|
||||
|
|
|
@ -49,8 +49,8 @@ comptime {
|
|||
exportListFn(list.listConcat, "concat");
|
||||
exportListFn(list.listSublist, "sublist");
|
||||
exportListFn(list.listDropAt, "drop_at");
|
||||
exportListFn(list.listSet, "set");
|
||||
exportListFn(list.listSetInPlace, "set_in_place");
|
||||
exportListFn(list.listReplace, "replace");
|
||||
exportListFn(list.listReplaceInPlace, "replace_in_place");
|
||||
exportListFn(list.listSwap, "swap");
|
||||
exportListFn(list.listAny, "any");
|
||||
exportListFn(list.listAll, "all");
|
||||
|
|
|
@ -50,12 +50,17 @@ fn main() {
|
|||
);
|
||||
|
||||
// OBJECT FILES
|
||||
#[cfg(windows)]
|
||||
const BUILTINS_HOST_FILE: &str = "builtins-host.obj";
|
||||
|
||||
#[cfg(not(windows))]
|
||||
const BUILTINS_HOST_FILE: &str = "builtins-host.o";
|
||||
|
||||
generate_object_file(
|
||||
&bitcode_path,
|
||||
"BUILTINS_HOST_O",
|
||||
"object",
|
||||
"builtins-host.o",
|
||||
BUILTINS_HOST_FILE,
|
||||
);
|
||||
|
||||
generate_object_file(
|
||||
|
@ -104,7 +109,7 @@ fn generate_object_file(
|
|||
println!("Moving zig object `{}` to: {}", zig_object, dest_obj);
|
||||
|
||||
// we store this .o file in rust's `target` folder (for wasm we need to leave a copy here too)
|
||||
run_command(&bitcode_path, "cp", &[src_obj, dest_obj]);
|
||||
fs::copy(src_obj, dest_obj).expect("Failed to copy object file.");
|
||||
}
|
||||
|
||||
fn generate_bc_file(
|
||||
|
|
|
@ -354,8 +354,8 @@ pub const LIST_RANGE: &str = "roc_builtins.list.range";
|
|||
pub const LIST_REVERSE: &str = "roc_builtins.list.reverse";
|
||||
pub const LIST_SORT_WITH: &str = "roc_builtins.list.sort_with";
|
||||
pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
|
||||
pub const LIST_SET: &str = "roc_builtins.list.set";
|
||||
pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place";
|
||||
pub const LIST_REPLACE: &str = "roc_builtins.list.replace";
|
||||
pub const LIST_REPLACE_IN_PLACE: &str = "roc_builtins.list.replace_in_place";
|
||||
pub const LIST_ANY: &str = "roc_builtins.list.any";
|
||||
pub const LIST_ALL: &str = "roc_builtins.list.all";
|
||||
pub const LIST_FIND_UNSAFE: &str = "roc_builtins.list.find_unsafe";
|
||||
|
|
|
@ -1056,6 +1056,19 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
|||
Box::new(result_type(flex(TVAR1), list_was_empty.clone())),
|
||||
);
|
||||
|
||||
// replace : List elem, Nat, elem -> { list: List elem, value: elem }
|
||||
add_top_level_function_type!(
|
||||
Symbol::LIST_REPLACE,
|
||||
vec![list_type(flex(TVAR1)), nat_type(), flex(TVAR1)],
|
||||
Box::new(SolvedType::Record {
|
||||
fields: vec![
|
||||
("list".into(), RecordField::Required(list_type(flex(TVAR1)))),
|
||||
("value".into(), RecordField::Required(flex(TVAR1))),
|
||||
],
|
||||
ext: Box::new(SolvedType::EmptyRecord),
|
||||
}),
|
||||
);
|
||||
|
||||
// set : List elem, Nat, elem -> List elem
|
||||
add_top_level_function_type!(
|
||||
Symbol::LIST_SET,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -57,6 +57,7 @@ pub fn builtin_dependencies(symbol: Symbol) -> &'static [Symbol] {
|
|||
Symbol::LIST_PRODUCT => &[Symbol::LIST_WALK, Symbol::NUM_MUL],
|
||||
Symbol::LIST_SUM => &[Symbol::LIST_WALK, Symbol::NUM_ADD],
|
||||
Symbol::LIST_JOIN_MAP => &[Symbol::LIST_WALK, Symbol::LIST_CONCAT],
|
||||
Symbol::LIST_SET => &[Symbol::LIST_REPLACE],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +103,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
|||
STR_TO_I8 => str_to_num,
|
||||
LIST_LEN => list_len,
|
||||
LIST_GET => list_get,
|
||||
LIST_REPLACE => list_replace,
|
||||
LIST_SET => list_set,
|
||||
LIST_APPEND => list_append,
|
||||
LIST_FIRST => list_first,
|
||||
|
@ -2303,6 +2305,91 @@ fn list_get(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
)
|
||||
}
|
||||
|
||||
/// List.replace : List elem, Nat, elem -> { list: List elem, value: elem }
|
||||
fn list_replace(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let arg_list = Symbol::ARG_1;
|
||||
let arg_index = Symbol::ARG_2;
|
||||
let arg_elem = Symbol::ARG_3;
|
||||
let bool_var = var_store.fresh();
|
||||
let len_var = var_store.fresh();
|
||||
let elem_var = var_store.fresh();
|
||||
let list_arg_var = var_store.fresh();
|
||||
let ret_record_var = var_store.fresh();
|
||||
let ret_result_var = var_store.fresh();
|
||||
|
||||
let list_field = Field {
|
||||
var: list_arg_var,
|
||||
region: Region::zero(),
|
||||
loc_expr: Box::new(Loc::at_zero(Expr::Var(arg_list))),
|
||||
};
|
||||
|
||||
let value_field = Field {
|
||||
var: elem_var,
|
||||
region: Region::zero(),
|
||||
loc_expr: Box::new(Loc::at_zero(Expr::Var(arg_elem))),
|
||||
};
|
||||
|
||||
// Perform a bounds check. If it passes, run LowLevel::ListReplaceUnsafe.
|
||||
// Otherwise, return the list unmodified.
|
||||
let body = If {
|
||||
cond_var: bool_var,
|
||||
branch_var: ret_result_var,
|
||||
branches: vec![(
|
||||
// if-condition
|
||||
no_region(
|
||||
// index < List.len list
|
||||
RunLowLevel {
|
||||
op: LowLevel::NumLt,
|
||||
args: vec![
|
||||
(len_var, Var(arg_index)),
|
||||
(
|
||||
len_var,
|
||||
RunLowLevel {
|
||||
op: LowLevel::ListLen,
|
||||
args: vec![(list_arg_var, Var(arg_list))],
|
||||
ret_var: len_var,
|
||||
},
|
||||
),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
},
|
||||
),
|
||||
// then-branch
|
||||
no_region(
|
||||
// List.replaceUnsafe list index elem
|
||||
RunLowLevel {
|
||||
op: LowLevel::ListReplaceUnsafe,
|
||||
args: vec![
|
||||
(list_arg_var, Var(arg_list)),
|
||||
(len_var, Var(arg_index)),
|
||||
(elem_var, Var(arg_elem)),
|
||||
],
|
||||
ret_var: ret_record_var,
|
||||
},
|
||||
),
|
||||
)],
|
||||
final_else: Box::new(
|
||||
// else-branch
|
||||
no_region(record(
|
||||
vec![("list".into(), list_field), ("value".into(), value_field)],
|
||||
var_store,
|
||||
)),
|
||||
),
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![
|
||||
(list_arg_var, Symbol::ARG_1),
|
||||
(len_var, Symbol::ARG_2),
|
||||
(elem_var, Symbol::ARG_3),
|
||||
],
|
||||
var_store,
|
||||
body,
|
||||
ret_result_var,
|
||||
)
|
||||
}
|
||||
|
||||
/// List.set : List elem, Nat, elem -> List elem
|
||||
///
|
||||
/// List.set :
|
||||
|
@ -2317,9 +2404,27 @@ fn list_set(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
let bool_var = var_store.fresh();
|
||||
let len_var = var_store.fresh();
|
||||
let elem_var = var_store.fresh();
|
||||
let replace_record_var = var_store.fresh();
|
||||
let list_arg_var = var_store.fresh(); // Uniqueness type Attr differs between
|
||||
let list_ret_var = var_store.fresh(); // the arg list and the returned list
|
||||
|
||||
let replace_function = (
|
||||
var_store.fresh(),
|
||||
Loc::at_zero(Expr::Var(Symbol::LIST_REPLACE)),
|
||||
var_store.fresh(),
|
||||
replace_record_var,
|
||||
);
|
||||
|
||||
let replace_call = Expr::Call(
|
||||
Box::new(replace_function),
|
||||
vec![
|
||||
(list_arg_var, Loc::at_zero(Var(arg_list))),
|
||||
(len_var, Loc::at_zero(Var(arg_index))),
|
||||
(elem_var, Loc::at_zero(Var(arg_elem))),
|
||||
],
|
||||
CalledVia::Space,
|
||||
);
|
||||
|
||||
// Perform a bounds check. If it passes, run LowLevel::ListSet.
|
||||
// Otherwise, return the list unmodified.
|
||||
let body = If {
|
||||
|
@ -2346,18 +2451,16 @@ fn list_set(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
},
|
||||
),
|
||||
// then-branch
|
||||
no_region(
|
||||
// List.setUnsafe list index
|
||||
RunLowLevel {
|
||||
op: LowLevel::ListSet,
|
||||
args: vec![
|
||||
(list_arg_var, Var(arg_list)),
|
||||
(len_var, Var(arg_index)),
|
||||
(elem_var, Var(arg_elem)),
|
||||
],
|
||||
ret_var: list_ret_var,
|
||||
},
|
||||
),
|
||||
no_region(Access {
|
||||
record_var: replace_record_var,
|
||||
ext_var: var_store.fresh(),
|
||||
field_var: list_ret_var,
|
||||
loc_expr: Box::new(no_region(
|
||||
// List.replaceUnsafe list index elem
|
||||
replace_call,
|
||||
)),
|
||||
field: "list".into(),
|
||||
}),
|
||||
)],
|
||||
final_else: Box::new(
|
||||
// else-branch
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -399,7 +399,7 @@ pub fn sort_can_defs(
|
|||
) -> (Result<Vec<Declaration>, RuntimeError>, Output) {
|
||||
let CanDefs {
|
||||
refs_by_symbol,
|
||||
can_defs_by_symbol,
|
||||
mut can_defs_by_symbol,
|
||||
aliases,
|
||||
} = defs;
|
||||
|
||||
|
@ -583,7 +583,7 @@ pub fn sort_can_defs(
|
|||
&group,
|
||||
&env.closures,
|
||||
&mut all_successors_with_self,
|
||||
&can_defs_by_symbol,
|
||||
&mut can_defs_by_symbol,
|
||||
&mut declarations,
|
||||
);
|
||||
}
|
||||
|
@ -717,7 +717,7 @@ pub fn sort_can_defs(
|
|||
group,
|
||||
&env.closures,
|
||||
&mut all_successors_with_self,
|
||||
&can_defs_by_symbol,
|
||||
&mut can_defs_by_symbol,
|
||||
&mut declarations,
|
||||
);
|
||||
}
|
||||
|
@ -739,7 +739,7 @@ fn group_to_declaration(
|
|||
group: &[Symbol],
|
||||
closures: &MutMap<Symbol, References>,
|
||||
successors: &mut dyn FnMut(&Symbol) -> ImSet<Symbol>,
|
||||
can_defs_by_symbol: &MutMap<Symbol, Def>,
|
||||
can_defs_by_symbol: &mut MutMap<Symbol, Def>,
|
||||
declarations: &mut Vec<Declaration>,
|
||||
) {
|
||||
use Declaration::*;
|
||||
|
@ -759,57 +759,60 @@ fn group_to_declaration(
|
|||
// Can bind multiple symbols. When not incorrectly recursive (which is guaranteed in this function),
|
||||
// normally `someDef` would be inserted twice. We use the region of the pattern as a unique key
|
||||
// for a definition, so every definition is only inserted (thus typechecked and emitted) once
|
||||
let mut seen_pattern_regions: ImSet<Region> = ImSet::default();
|
||||
let mut seen_pattern_regions: Vec<Region> = Vec::with_capacity(2);
|
||||
|
||||
for cycle in strongly_connected_components(group, filtered_successors) {
|
||||
if cycle.len() == 1 {
|
||||
let symbol = &cycle[0];
|
||||
|
||||
if let Some(can_def) = can_defs_by_symbol.get(symbol) {
|
||||
let mut new_def = can_def.clone();
|
||||
|
||||
// Determine recursivity of closures that are not tail-recursive
|
||||
if let Closure(ClosureData {
|
||||
recursive: recursive @ Recursive::NotRecursive,
|
||||
..
|
||||
}) = &mut new_def.loc_expr.value
|
||||
{
|
||||
*recursive = closure_recursivity(*symbol, closures);
|
||||
}
|
||||
|
||||
let is_recursive = successors(symbol).contains(symbol);
|
||||
|
||||
if !seen_pattern_regions.contains(&new_def.loc_pattern.region) {
|
||||
if is_recursive {
|
||||
declarations.push(DeclareRec(vec![new_def.clone()]));
|
||||
} else {
|
||||
declarations.push(Declare(new_def.clone()));
|
||||
}
|
||||
seen_pattern_regions.insert(new_def.loc_pattern.region);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut can_defs = Vec::new();
|
||||
|
||||
// Topological sort gives us the reverse of the sorting we want!
|
||||
for symbol in cycle.into_iter().rev() {
|
||||
if let Some(can_def) = can_defs_by_symbol.get(&symbol) {
|
||||
let mut new_def = can_def.clone();
|
||||
|
||||
match can_defs_by_symbol.remove(symbol) {
|
||||
Some(mut new_def) => {
|
||||
// Determine recursivity of closures that are not tail-recursive
|
||||
if let Closure(ClosureData {
|
||||
recursive: recursive @ Recursive::NotRecursive,
|
||||
..
|
||||
}) = &mut new_def.loc_expr.value
|
||||
{
|
||||
*recursive = closure_recursivity(symbol, closures);
|
||||
*recursive = closure_recursivity(*symbol, closures);
|
||||
}
|
||||
|
||||
let is_recursive = successors(symbol).contains(symbol);
|
||||
|
||||
if !seen_pattern_regions.contains(&new_def.loc_pattern.region) {
|
||||
can_defs.push(new_def.clone());
|
||||
}
|
||||
seen_pattern_regions.push(new_def.loc_pattern.region);
|
||||
|
||||
seen_pattern_regions.insert(new_def.loc_pattern.region);
|
||||
if is_recursive {
|
||||
declarations.push(DeclareRec(vec![new_def]));
|
||||
} else {
|
||||
declarations.push(Declare(new_def));
|
||||
}
|
||||
}
|
||||
}
|
||||
None => roc_error_macros::internal_error!("def not available {:?}", symbol),
|
||||
}
|
||||
} else {
|
||||
let mut can_defs = Vec::new();
|
||||
|
||||
// Topological sort gives us the reverse of the sorting we want!
|
||||
for symbol in cycle.into_iter().rev() {
|
||||
match can_defs_by_symbol.remove(&symbol) {
|
||||
Some(mut new_def) => {
|
||||
// Determine recursivity of closures that are not tail-recursive
|
||||
if let Closure(ClosureData {
|
||||
recursive: recursive @ Recursive::NotRecursive,
|
||||
..
|
||||
}) = &mut new_def.loc_expr.value
|
||||
{
|
||||
*recursive = closure_recursivity(symbol, closures);
|
||||
}
|
||||
|
||||
if !seen_pattern_regions.contains(&new_def.loc_pattern.region) {
|
||||
seen_pattern_regions.push(new_def.loc_pattern.region);
|
||||
|
||||
can_defs.push(new_def);
|
||||
}
|
||||
}
|
||||
None => roc_error_macros::internal_error!("def not available {:?}", symbol),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -861,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)]
|
||||
|
@ -884,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 {
|
||||
|
@ -960,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,
|
||||
}),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -995,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.
|
||||
|
@ -1038,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
|
||||
|
@ -1181,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);
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
#![allow(clippy::large_enum_variant)]
|
||||
|
||||
pub mod all;
|
||||
pub mod soa;
|
||||
|
|
119
compiler/collections/src/soa.rs
Normal file
119
compiler/collections/src/soa.rs
Normal 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 _))
|
||||
}
|
||||
}
|
|
@ -14,3 +14,4 @@ roc_parse = { path = "../parse" }
|
|||
roc_types = { path = "../types" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
arrayvec = "0.7.2"
|
||||
|
|
|
@ -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
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -55,7 +55,6 @@ impl<'a> Buf<'a> {
|
|||
|
||||
pub fn push_str_allow_spaces(&mut self, s: &str) {
|
||||
debug_assert!(!self.beginning_of_line);
|
||||
debug_assert!(!s.contains('\n'));
|
||||
|
||||
self.flush_spaces();
|
||||
|
||||
|
@ -91,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");
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ license = "UPL-1.0"
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
roc_alias_analysis = { path = "../alias_analysis" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::llvm::build_list::{
|
|||
self, allocate_list, empty_polymorphic_list, list_all, list_any, list_append, list_concat,
|
||||
list_contains, list_drop_at, list_find_unsafe, list_get_unsafe, list_join, list_keep_errs,
|
||||
list_keep_if, list_keep_oks, list_len, list_map, list_map2, list_map3, list_map4,
|
||||
list_map_with_index, list_prepend, list_range, list_repeat, list_reverse, list_set,
|
||||
list_map_with_index, list_prepend, list_range, list_repeat, list_replace_unsafe, list_reverse,
|
||||
list_single, list_sort_with, list_sublist, list_swap,
|
||||
};
|
||||
use crate::llvm::build_str::{
|
||||
|
@ -710,7 +710,7 @@ fn promote_to_main_function<'a, 'ctx, 'env>(
|
|||
top_level: ProcLayout<'a>,
|
||||
) -> (&'static str, FunctionValue<'ctx>) {
|
||||
let it = top_level.arguments.iter().copied();
|
||||
let bytes = roc_mono::alias_analysis::func_name_bytes_help(symbol, it, &top_level.result);
|
||||
let bytes = roc_alias_analysis::func_name_bytes_help(symbol, it, &top_level.result);
|
||||
let func_name = FuncName(&bytes);
|
||||
let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
|
||||
|
||||
|
@ -4045,7 +4045,7 @@ pub fn build_proc_headers<'a, 'ctx, 'env>(
|
|||
// Populate Procs further and get the low-level Expr from the canonical Expr
|
||||
let mut headers = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||
for ((symbol, layout), proc) in procedures {
|
||||
let name_bytes = roc_mono::alias_analysis::func_name_bytes(&proc);
|
||||
let name_bytes = roc_alias_analysis::func_name_bytes(&proc);
|
||||
let func_name = FuncName(&name_bytes);
|
||||
|
||||
let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
|
||||
|
@ -4110,7 +4110,7 @@ fn build_procedures_help<'a, 'ctx, 'env>(
|
|||
|
||||
let it = procedures.iter().map(|x| x.1);
|
||||
|
||||
let solutions = match roc_mono::alias_analysis::spec_program(opt_level, entry_point, it) {
|
||||
let solutions = match roc_alias_analysis::spec_program(opt_level, entry_point, it) {
|
||||
Err(e) => panic!("Error in alias analysis: {}", e),
|
||||
Ok(solutions) => solutions,
|
||||
};
|
||||
|
@ -4118,7 +4118,7 @@ fn build_procedures_help<'a, 'ctx, 'env>(
|
|||
let solutions = env.arena.alloc(solutions);
|
||||
|
||||
let mod_solutions = solutions
|
||||
.mod_solutions(roc_mono::alias_analysis::MOD_APP)
|
||||
.mod_solutions(roc_alias_analysis::MOD_APP)
|
||||
.unwrap();
|
||||
|
||||
// Add all the Proc headers to the module.
|
||||
|
@ -4470,11 +4470,8 @@ pub fn build_proc<'a, 'ctx, 'env>(
|
|||
// * roc__mainForHost_1_Update_result_size() -> i64
|
||||
|
||||
let it = top_level.arguments.iter().copied();
|
||||
let bytes = roc_mono::alias_analysis::func_name_bytes_help(
|
||||
symbol,
|
||||
it,
|
||||
&top_level.result,
|
||||
);
|
||||
let bytes =
|
||||
roc_alias_analysis::func_name_bytes_help(symbol, it, &top_level.result);
|
||||
let func_name = FuncName(&bytes);
|
||||
let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
|
||||
|
||||
|
@ -5666,12 +5663,12 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
wrapper_struct,
|
||||
)
|
||||
}
|
||||
ListSet => {
|
||||
ListReplaceUnsafe => {
|
||||
let list = load_symbol(scope, &args[0]);
|
||||
let index = load_symbol(scope, &args[1]);
|
||||
let (element, element_layout) = load_symbol_and_layout(scope, &args[2]);
|
||||
|
||||
list_set(
|
||||
list_replace_unsafe(
|
||||
env,
|
||||
layout_ids,
|
||||
list,
|
||||
|
|
|
@ -291,52 +291,70 @@ pub fn list_drop_at<'a, 'ctx, 'env>(
|
|||
)
|
||||
}
|
||||
|
||||
/// List.set : List elem, Nat, elem -> List elem
|
||||
pub fn list_set<'a, 'ctx, 'env>(
|
||||
/// List.replace_unsafe : List elem, Nat, elem -> { list: List elem, value: elem }
|
||||
pub fn list_replace_unsafe<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
_layout_ids: &mut LayoutIds<'a>,
|
||||
list: BasicValueEnum<'ctx>,
|
||||
index: IntValue<'ctx>,
|
||||
element: BasicValueEnum<'ctx>,
|
||||
element_layout: &Layout<'a>,
|
||||
update_mode: UpdateMode,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
|
||||
let element_type = basic_type_from_layout(env, element_layout);
|
||||
let element_ptr = env
|
||||
.builder
|
||||
.build_alloca(element_type, "output_element_as_opaque");
|
||||
|
||||
let (length, bytes) = load_list(
|
||||
env.builder,
|
||||
list.into_struct_value(),
|
||||
env.context.i8_type().ptr_type(AddressSpace::Generic),
|
||||
);
|
||||
|
||||
let new_bytes = match update_mode {
|
||||
UpdateMode::InPlace => call_bitcode_fn(
|
||||
// Assume the bounds have already been checked earlier
|
||||
// (e.g. by List.replace or List.set, which wrap List.#replaceUnsafe)
|
||||
let new_list = match update_mode {
|
||||
UpdateMode::InPlace => call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
bytes.into(),
|
||||
pass_list_cc(env, list),
|
||||
index.into(),
|
||||
pass_element_as_opaque(env, element, *element_layout),
|
||||
layout_width(env, element_layout),
|
||||
dec_element_fn.as_global_value().as_pointer_value().into(),
|
||||
pass_as_opaque(env, element_ptr),
|
||||
],
|
||||
bitcode::LIST_SET_IN_PLACE,
|
||||
bitcode::LIST_REPLACE_IN_PLACE,
|
||||
),
|
||||
UpdateMode::Immutable => call_bitcode_fn(
|
||||
UpdateMode::Immutable => call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
bytes.into(),
|
||||
length.into(),
|
||||
pass_list_cc(env, list),
|
||||
env.alignment_intvalue(element_layout),
|
||||
index.into(),
|
||||
pass_element_as_opaque(env, element, *element_layout),
|
||||
layout_width(env, element_layout),
|
||||
dec_element_fn.as_global_value().as_pointer_value().into(),
|
||||
pass_as_opaque(env, element_ptr),
|
||||
],
|
||||
bitcode::LIST_SET,
|
||||
bitcode::LIST_REPLACE,
|
||||
),
|
||||
};
|
||||
|
||||
store_list(env, new_bytes.into_pointer_value(), length)
|
||||
// Load the element and returned list into a struct.
|
||||
let old_element = env.builder.build_load(element_ptr, "load_element");
|
||||
|
||||
let result = env
|
||||
.context
|
||||
.struct_type(
|
||||
&[super::convert::zig_list_type(env).into(), element_type],
|
||||
false,
|
||||
)
|
||||
.const_zero();
|
||||
|
||||
let result = env
|
||||
.builder
|
||||
.build_insert_value(result, new_list, 0, "insert_list")
|
||||
.unwrap();
|
||||
|
||||
env.builder
|
||||
.build_insert_value(result, old_element, 1, "insert_value")
|
||||
.unwrap()
|
||||
.into_struct_value()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn bounds_check_comparison<'ctx>(
|
||||
|
|
|
@ -260,14 +260,14 @@ impl<'a> LowLevelCall<'a> {
|
|||
_ => internal_error!("invalid storage for List"),
|
||||
},
|
||||
|
||||
ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat
|
||||
| ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap
|
||||
| ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk
|
||||
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith
|
||||
| ListSublist | ListDropAt | ListSwap | ListAny | ListAll | ListFindUnsafe
|
||||
| DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe
|
||||
| DictKeys | DictValues | DictUnion | DictIntersection | DictDifference | DictWalk
|
||||
| SetFromList => {
|
||||
ListGetUnsafe | ListReplaceUnsafe | ListSingle | ListRepeat | ListReverse
|
||||
| ListConcat | ListContains | ListAppend | ListPrepend | ListJoin | ListRange
|
||||
| ListMap | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf
|
||||
| ListWalk | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs
|
||||
| ListSortWith | ListSublist | ListDropAt | ListSwap | ListAny | ListAll
|
||||
| ListFindUnsafe | DictSize | DictEmpty | DictInsert | DictRemove | DictContains
|
||||
| DictGetUnsafe | DictKeys | DictValues | DictUnion | DictIntersection
|
||||
| DictDifference | DictWalk | SetFromList => {
|
||||
todo!("{:?}", self.lowlevel);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>,
|
||||
|
@ -502,7 +505,7 @@ enum Msg<'a> {
|
|||
},
|
||||
FinishedAllTypeChecking {
|
||||
solved_subs: Solved<Subs>,
|
||||
exposed_vars_by_symbol: MutMap<Symbol, Variable>,
|
||||
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
|
||||
exposed_values: Vec<Symbol>,
|
||||
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>,
|
||||
|
@ -2131,7 +2135,7 @@ fn finish(
|
|||
solved: Solved<Subs>,
|
||||
exposed_values: Vec<Symbol>,
|
||||
exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
|
||||
exposed_vars_by_symbol: MutMap<Symbol, Variable>,
|
||||
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||
) -> LoadedModule {
|
||||
|
@ -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,15 +3109,16 @@ 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 mut exposed_vars_by_symbol: MutMap<Symbol, Variable> = solved_env.vars_by_symbol();
|
||||
exposed_vars_by_symbol.retain(|k, _| exposed_symbols.contains(k));
|
||||
let exposed_vars_by_symbol: Vec<_> = solved_env
|
||||
.vars_by_symbol()
|
||||
.filter(|(k, _)| exposed_symbols.contains(k))
|
||||
.collect();
|
||||
|
||||
let solved_types = roc_solve::module::make_solved_types(&solved_subs, &exposed_vars_by_symbol);
|
||||
|
||||
|
@ -3245,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,
|
||||
|
@ -3261,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,
|
||||
|
@ -3743,6 +3759,7 @@ fn run_task<'a>(
|
|||
module,
|
||||
module_timing,
|
||||
imported_symbols,
|
||||
constraints,
|
||||
constraint,
|
||||
var_store,
|
||||
ident_ids,
|
||||
|
@ -3754,6 +3771,7 @@ fn run_task<'a>(
|
|||
ident_ids,
|
||||
module_timing,
|
||||
imported_symbols,
|
||||
constraints,
|
||||
constraint,
|
||||
var_store,
|
||||
declarations,
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -25,9 +25,9 @@ pub enum LowLevel {
|
|||
StrToNum,
|
||||
ListLen,
|
||||
ListGetUnsafe,
|
||||
ListSet,
|
||||
ListSingle,
|
||||
ListRepeat,
|
||||
ListReplaceUnsafe,
|
||||
ListReverse,
|
||||
ListConcat,
|
||||
ListContains,
|
||||
|
@ -228,7 +228,7 @@ impl LowLevelWrapperType {
|
|||
Symbol::STR_TO_I8 => WrapperIsRequired,
|
||||
Symbol::LIST_LEN => CanBeReplacedBy(ListLen),
|
||||
Symbol::LIST_GET => WrapperIsRequired,
|
||||
Symbol::LIST_SET => WrapperIsRequired,
|
||||
Symbol::LIST_REPLACE => WrapperIsRequired,
|
||||
Symbol::LIST_SINGLE => CanBeReplacedBy(ListSingle),
|
||||
Symbol::LIST_REPEAT => CanBeReplacedBy(ListRepeat),
|
||||
Symbol::LIST_REVERSE => CanBeReplacedBy(ListReverse),
|
||||
|
|
|
@ -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
|
||||
|
@ -1142,6 +1146,7 @@ define_builtins! {
|
|||
55 LIST_SORT_ASC: "sortAsc"
|
||||
56 LIST_SORT_DESC: "sortDesc"
|
||||
57 LIST_SORT_DESC_COMPARE: "#sortDescCompare"
|
||||
58 LIST_REPLACE: "replace"
|
||||
}
|
||||
5 RESULT: "Result" => {
|
||||
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
||||
|
|
|
@ -934,7 +934,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
|||
// - other refcounted arguments are Borrowed
|
||||
match op {
|
||||
ListLen | StrIsEmpty | StrCountGraphemes => arena.alloc_slice_copy(&[borrowed]),
|
||||
ListSet => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
||||
ListReplaceUnsafe => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
||||
ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||
ListConcat => arena.alloc_slice_copy(&[owned, owned]),
|
||||
StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),
|
||||
|
|
|
@ -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()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
|
||||
#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
|
||||
|
||||
pub mod alias_analysis;
|
||||
pub mod borrow;
|
||||
pub mod code_gen_help;
|
||||
pub mod inc_dec;
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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;
|
||||
|
@ -12,13 +12,14 @@ pub struct SolvedModule {
|
|||
pub solved_types: MutMap<Symbol, SolvedType>,
|
||||
pub aliases: MutMap<Symbol, Alias>,
|
||||
pub exposed_symbols: Vec<Symbol>,
|
||||
pub exposed_vars_by_symbol: MutMap<Symbol, Variable>,
|
||||
pub exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
pub problems: Vec<solve::TypeError>,
|
||||
}
|
||||
|
||||
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,14 +35,14 @@ 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)
|
||||
}
|
||||
|
||||
pub fn make_solved_types(
|
||||
solved_subs: &Solved<Subs>,
|
||||
exposed_vars_by_symbol: &MutMap<Symbol, Variable>,
|
||||
exposed_vars_by_symbol: &[(Symbol, Variable)],
|
||||
) -> MutMap<Symbol, SolvedType> {
|
||||
let mut solved_types = MutMap::default();
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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 ]"#,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
@ -1763,6 +1763,97 @@ fn get_int_list_oob() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn replace_unique_int_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
record = List.replace [ 12, 9, 7, 1, 5 ] 2 33
|
||||
record.list
|
||||
"#
|
||||
),
|
||||
RocList::from_slice(&[12, 9, 33, 1, 5]),
|
||||
RocList<i64>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn replace_unique_int_list_out_of_bounds() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
record = List.replace [ 12, 9, 7, 1, 5 ] 5 33
|
||||
record.value
|
||||
"#
|
||||
),
|
||||
33,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn replace_unique_int_list_get_old_value() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
record = List.replace [ 12, 9, 7, 1, 5 ] 2 33
|
||||
record.value
|
||||
"#
|
||||
),
|
||||
7,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn replace_unique_get_large_value() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
list : List { a : U64, b: U64, c: U64, d: U64 }
|
||||
list = [ { a: 1, b: 2, c: 3, d: 4 }, { a: 5, b: 6, c: 7, d: 8 }, { a: 9, b: 10, c: 11, d: 12 } ]
|
||||
record = List.replace list 1 { a: 13, b: 14, c: 15, d: 16 }
|
||||
record.value
|
||||
"#
|
||||
),
|
||||
(5, 6, 7, 8),
|
||||
(u64, u64, u64, u64)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn replace_shared_int_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
wrapper = \shared ->
|
||||
# This should not mutate the original
|
||||
replaced = (List.replace shared 1 7.7).list
|
||||
x =
|
||||
when List.get replaced 1 is
|
||||
Ok num -> num
|
||||
Err _ -> 0
|
||||
|
||||
y =
|
||||
when List.get shared 1 is
|
||||
Ok num -> num
|
||||
Err _ -> 0
|
||||
|
||||
{ x, y }
|
||||
|
||||
wrapper [ 2.1, 4.3 ]
|
||||
"#
|
||||
),
|
||||
(7.7, 4.3),
|
||||
(f64, f64)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn get_set_unique_int_list() {
|
||||
|
|
|
@ -14,7 +14,7 @@ use crate::helpers::wasm::assert_evals_to;
|
|||
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"))]
|
||||
|
@ -275,7 +275,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!(
|
||||
|
@ -302,7 +302,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!(
|
||||
|
@ -404,7 +404,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!(
|
||||
|
@ -655,7 +655,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!(
|
||||
|
@ -669,7 +669,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!(
|
||||
|
@ -683,7 +683,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!(
|
||||
|
@ -697,7 +697,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!(
|
||||
|
@ -711,7 +711,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!(
|
||||
|
@ -725,7 +725,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!(
|
||||
|
@ -795,7 +795,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!(
|
||||
|
@ -1058,3 +1058,19 @@ fn call_with_bad_record_runtime_error() {
|
|||
"#
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>),
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
),
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
|
||||
|
||||
## :construction: Work In Progress :construction:
|
||||
|
||||
The editor is a work in progress, only a limited subset of Roc expressions are currently supported.
|
||||
|
||||
Unlike most editors, we use projectional or structural editing to edit the [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) directly. This will allow for cool features like excellent auto-complete, refactoring and never needing to format your code.
|
||||
|
@ -68,6 +71,7 @@ Important folders/files outside the editor folder:
|
|||
- ast/src/lang/core/ast.rs
|
||||
- ast/src/lang/env.rs
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome new contributors :heart: and are happy to help you get started.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -49,4 +49,3 @@ closure4 = \_ ->
|
|||
Task.succeed {}
|
||||
|> Task.after (\_ -> Task.succeed x)
|
||||
|> Task.map (\_ -> {})
|
||||
|
||||
|
|
|
@ -73,4 +73,3 @@ swap = \i, j, list ->
|
|||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
|
|
@ -22,4 +22,4 @@ main =
|
|||
|
||||
sort : List I64 -> List I64
|
||||
sort = \list ->
|
||||
Quicksort.sortWith list (\x, y -> Num.compare x y)
|
||||
Quicksort.sortWith list (\x, y -> Num.compare x y)
|
||||
|
|
|
@ -112,4 +112,3 @@ balance = \color, key, value, left, right ->
|
|||
|
||||
_ ->
|
||||
Node color key value left right
|
||||
|
||||
|
|
|
@ -21,4 +21,3 @@ main =
|
|||
|
||||
Err _ ->
|
||||
Task.putLine "sadness"
|
||||
|
||||
|
|
9
examples/cli/platform/Cargo.lock
generated
9
examples/cli/platform/Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -122,4 +122,4 @@ inWhileScope = \ctx ->
|
|||
scope.whileInfo != None
|
||||
|
||||
Err ListWasEmpty ->
|
||||
False
|
||||
False
|
||||
|
|
9
examples/false-interpreter/platform/Cargo.lock
generated
9
examples/false-interpreter/platform/Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
11
examples/gui/platform/Cargo.lock
generated
11
examples/gui/platform/Cargo.lock
generated
|
@ -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]]
|
||||
|
|
9
examples/hello-rust/platform/Cargo.lock
generated
9
examples/hello-rust/platform/Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
Windows is not yet supported, we have a big project in the works that will make it easier to achieve this.
|
||||
Windows is not yet supported, we have a big project in the works (see issue #2608) that will allow this.
|
||||
Until then we recommend using Ubuntu through the "Windows Subsystem for Linux".
|
|
@ -24,7 +24,7 @@ roc_collections = { path = "../compiler/collections" }
|
|||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
clap = { version = "3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
|
||||
iced-x86 = { version = "1.15.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] }
|
||||
memmap2 = "0.5.0"
|
||||
memmap2 = "0.5.3"
|
||||
object = { version = "0.26.2", features = ["read", "write"] }
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
bincode = "1.3.3"
|
||||
|
|
|
@ -20,7 +20,6 @@ use std::io;
|
|||
use std::io::{BufReader, BufWriter};
|
||||
use std::mem;
|
||||
use std::os::raw::c_char;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
@ -1615,9 +1614,14 @@ fn surgery_impl(
|
|||
let flushing_data_duration = flushing_data_start.elapsed().unwrap();
|
||||
|
||||
// Make sure the final executable has permision to execute.
|
||||
let mut perms = fs::metadata(out_filename)?.permissions();
|
||||
perms.set_mode(perms.mode() | 0o111);
|
||||
fs::set_permissions(out_filename, perms)?;
|
||||
// TODO windows alternative?
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = fs::metadata(out_filename)?.permissions();
|
||||
perms.set_mode(perms.mode() | 0o111);
|
||||
fs::set_permissions(out_filename, perms)?;
|
||||
}
|
||||
|
||||
let total_duration = total_start.elapsed().unwrap();
|
||||
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
"homepage": "https://github.com/nmattia/niv",
|
||||
"owner": "nmattia",
|
||||
"repo": "niv",
|
||||
"rev": "5830a4dd348d77e39a0f3c4c762ff2663b602d4c",
|
||||
"sha256": "1d3lsrqvci4qz2hwjrcnd8h5vfkg8aypq3sjd4g3izbc8frwz5sm",
|
||||
"rev": "9cb7ef336bb71fd1ca84fc7f2dff15ef4b033f2a",
|
||||
"sha256": "1ajyqr8zka1zlb25jx1v4xys3zqmdy3prbm1vxlid6ah27a8qnzh",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/nmattia/niv/archive/5830a4dd348d77e39a0f3c4c762ff2663b602d4c.tar.gz",
|
||||
"url": "https://github.com/nmattia/niv/archive/9cb7ef336bb71fd1ca84fc7f2dff15ef4b033f2a.tar.gz",
|
||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||
},
|
||||
"nixpkgs": {
|
||||
|
@ -17,10 +17,10 @@
|
|||
"homepage": "",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "fe6f208d68ac254873b659db9676d44dea9b0555",
|
||||
"sha256": "0ybvy1zx97k811bz73xmgsb41d33i2kr2dfqcxzq9m9h958178nq",
|
||||
"rev": "ed02c2ba0384b2800db41333045a6fb781f12aac",
|
||||
"sha256": "040rawxqbpblxpsq73qxlk25my2cm0g3gx1pksiacsj15q5fi84q",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/fe6f208d68ac254873b659db9676d44dea9b0555.tar.gz",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/ed02c2ba0384b2800db41333045a6fb781f12aac.tar.gz",
|
||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||
},
|
||||
"nixpkgs-unstable": {
|
||||
|
@ -29,10 +29,10 @@
|
|||
"homepage": "",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ea171bc81fcb3c6f21deeb46dbc10000087777ef",
|
||||
"sha256": "15hh28c98kb6pf7wgydc07bx2ivq04a2cay5mhwnqk5cpa8dbiap",
|
||||
"rev": "684c73c9e6ac8f4d0c6dea3251292e758ac375b5",
|
||||
"sha256": "0hl2nzizn4pwd3sn9gxkngzn88k9in01xm14afpj7716j8y0j2qa",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/ea171bc81fcb3c6f21deeb46dbc10000087777ef.tar.gz",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/684c73c9e6ac8f4d0c6dea3251292e758ac375b5.tar.gz",
|
||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
|
|||
const_format = "0.2.22"
|
||||
inkwell = {path = "../vendor/inkwell"}
|
||||
libloading = "0.7.1"
|
||||
rustyline = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"}
|
||||
rustyline-derive = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"}
|
||||
rustyline = {git = "https://github.com/rtfeldman/rustyline", rev = "e74333c"}
|
||||
rustyline-derive = {git = "https://github.com/rtfeldman/rustyline", rev = "e74333c"}
|
||||
target-lexicon = "0.12.2"
|
||||
|
||||
# TODO: make llvm optional
|
||||
|
@ -31,7 +31,7 @@ roc_load = {path = "../compiler/load"}
|
|||
roc_mono = {path = "../compiler/mono"}
|
||||
roc_parse = {path = "../compiler/parse"}
|
||||
roc_repl_eval = {path = "../repl_eval"}
|
||||
roc_std = {path = "../roc_std"}
|
||||
roc_std = {path = "../roc_std", default-features = false}
|
||||
roc_target = {path = "../compiler/roc_target"}
|
||||
roc_types = {path = "../compiler/types"}
|
||||
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue