mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-22 16:30:04 +03:00
Merge branch 'trunk' into editor-file-access
This commit is contained in:
commit
85e96a36d7
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
target
|
||||
zig-cache
|
||||
.direnv
|
||||
*.rs.bk
|
||||
|
||||
@ -7,3 +8,4 @@ vgcore.*
|
||||
|
||||
#editors
|
||||
.idea/
|
||||
|
||||
|
@ -92,6 +92,8 @@ Now with nix installed you just need to run one command:
|
||||
|
||||
> This may not output anything for a little while. This is normal, hang in there. Also make sure you are in the roc project root.
|
||||
|
||||
> Also, if you're on NixOS you'll need to enable opengl at the system-wide level. You can do this in configuration.nix with `hardware.opengl.enable = true;`. If you don't do this, nix-shell will fail!
|
||||
|
||||
You should be in a shell with everything needed to build already installed. Next run:
|
||||
|
||||
`cargo run repl`
|
||||
|
@ -189,6 +189,11 @@ fn jit_to_ast_help<'a>(
|
||||
Content::Structure(FlatType::RecursiveTagUnion(_, _, _)) => {
|
||||
todo!("print recursive tag unions in the REPL")
|
||||
}
|
||||
Content::Alias(_, _, actual) => {
|
||||
let content = env.subs.get_without_compacting(*actual).content;
|
||||
|
||||
jit_to_ast_help(env, lib, main_fn_name, layout, &content)
|
||||
}
|
||||
other => unreachable!("Weird content for Union layout: {:?}", other),
|
||||
},
|
||||
Layout::RecursiveUnion(_) | Layout::RecursivePointer => {
|
||||
|
@ -89,6 +89,25 @@ pub fn run_with_valgrind(args: &[&str]) -> (Out, String) {
|
||||
|
||||
cmd.arg("--tool=memcheck");
|
||||
cmd.arg("--xml=yes");
|
||||
|
||||
// If you are having valgrind issues on MacOS, you may need to suppress some
|
||||
// of the errors. Read more here: https://github.com/rtfeldman/roc/issues/746
|
||||
if let Some(suppressions_file_os_str) = env::var_os("VALGRIND_SUPPRESSIONS") {
|
||||
match suppressions_file_os_str.to_str() {
|
||||
None => {
|
||||
panic!("Could not determine suppression file location from OsStr");
|
||||
}
|
||||
Some(suppressions_file) => {
|
||||
let mut buf = String::new();
|
||||
|
||||
buf.push_str("--suppressions=");
|
||||
buf.push_str(suppressions_file);
|
||||
|
||||
cmd.arg(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmd.arg(format!("--xml-file={}", filepath));
|
||||
|
||||
for arg in args {
|
||||
|
@ -87,6 +87,11 @@ mod repl_eval {
|
||||
expect_success("1.1 + 2", "3.1 : F64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_rem() {
|
||||
expect_success("299 % 10", "Ok 9 : Result Int [ DivByZero ]*");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bool_in_record() {
|
||||
expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }");
|
||||
|
@ -54,3 +54,8 @@ maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
|
||||
[features]
|
||||
target-arm = []
|
||||
target-aarch64 = []
|
||||
target-webassembly = []
|
||||
|
@ -42,26 +42,40 @@ pub fn link(
|
||||
pub fn rebuild_host(host_input_path: &Path) {
|
||||
let c_host_src = host_input_path.with_file_name("host.c");
|
||||
let c_host_dest = host_input_path.with_file_name("c_host.o");
|
||||
let zig_host_src = host_input_path.with_file_name("host.zig");
|
||||
let rust_host_src = host_input_path.with_file_name("host.rs");
|
||||
let rust_host_dest = host_input_path.with_file_name("rust_host.o");
|
||||
let cargo_host_src = host_input_path.with_file_name("Cargo.toml");
|
||||
let host_dest = host_input_path.with_file_name("host.o");
|
||||
|
||||
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
|
||||
// Compile host.c
|
||||
let output = Command::new("clang")
|
||||
.env_clear()
|
||||
.env("PATH", &env_path)
|
||||
.args(&[
|
||||
"-c",
|
||||
c_host_src.to_str().unwrap(),
|
||||
"-o",
|
||||
c_host_dest.to_str().unwrap(),
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
validate_output("host.c", "clang", output);
|
||||
if zig_host_src.exists() {
|
||||
// Compile host.zig
|
||||
let output = Command::new("zig")
|
||||
.env_clear()
|
||||
.env("PATH", &env_path)
|
||||
.args(&["build-obj", zig_host_src.to_str().unwrap()])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
validate_output("host.zig", "zig", output);
|
||||
} else {
|
||||
// Compile host.c
|
||||
let output = Command::new("clang")
|
||||
.env_clear()
|
||||
.env("PATH", &env_path)
|
||||
.args(&[
|
||||
"-c",
|
||||
c_host_src.to_str().unwrap(),
|
||||
"-o",
|
||||
c_host_dest.to_str().unwrap(),
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
validate_output("host.c", "clang", output);
|
||||
}
|
||||
|
||||
if cargo_host_src.exists() {
|
||||
// Compile and link Cargo.toml, if it exists
|
||||
@ -132,15 +146,15 @@ pub fn rebuild_host(host_input_path: &Path) {
|
||||
.unwrap();
|
||||
|
||||
validate_output("rust_host.o", "rm", output);
|
||||
} else {
|
||||
// Clean up rust_host.o
|
||||
} else if c_host_dest.exists() {
|
||||
// Clean up c_host.o
|
||||
let output = Command::new("mv")
|
||||
.env_clear()
|
||||
.args(&[c_host_dest, host_dest])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
validate_output("rust_host.o", "mv", output);
|
||||
validate_output("c_host.o", "mv", output);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ pub fn arch_str(target: &Triple) -> &'static str {
|
||||
|
||||
"x86-64"
|
||||
}
|
||||
Architecture::Aarch64(_) => {
|
||||
Architecture::Aarch64(_) if cfg!(feature = "target-aarch64") => {
|
||||
Target::initialize_aarch64(&InitializationConfig::default());
|
||||
"aarch64"
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ pub fn build(b: *Builder) void {
|
||||
const ir_to_bitcode = b.addSystemCommand(&[_][]const u8{
|
||||
"llvm-as-10",
|
||||
ir_out_file,
|
||||
bitcode_path_arg
|
||||
bitcode_path_arg,
|
||||
});
|
||||
|
||||
const bicode = b.step("bc", "Build LLVM ir and convert to bitcode");
|
||||
|
@ -1,6 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eux
|
||||
set -euxo pipefail
|
||||
|
||||
# Test every zig
|
||||
find src/*.zig -type f -exec zig test {} \;
|
||||
find src/*.zig -type f -print0 | xargs -n 1 -0 zig test --library c
|
||||
|
||||
# fmt every zig
|
||||
find src/*.zig -type f -print0 | xargs -n 1 -0 zig fmt --check
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,28 +4,36 @@ const testing = std.testing;
|
||||
|
||||
// Num Module
|
||||
const num = @import("num.zig");
|
||||
comptime { exportNumFn(num.atan, "atan"); }
|
||||
comptime { exportNumFn(num.isFinite, "is_finite"); }
|
||||
comptime { exportNumFn(num.powInt, "pow_int"); }
|
||||
comptime { exportNumFn(num.acos, "acos"); }
|
||||
comptime { exportNumFn(num.asin, "asin"); }
|
||||
comptime {
|
||||
exportNumFn(num.atan, "atan");
|
||||
exportNumFn(num.isFinite, "is_finite");
|
||||
exportNumFn(num.powInt, "pow_int");
|
||||
exportNumFn(num.acos, "acos");
|
||||
exportNumFn(num.asin, "asin");
|
||||
}
|
||||
|
||||
// Str Module
|
||||
const str = @import("str.zig");
|
||||
comptime { exportStrFn(str.strSplitInPlace, "str_split_in_place"); }
|
||||
comptime { exportStrFn(str.countSegments, "count_segments"); }
|
||||
comptime { exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters"); }
|
||||
comptime { exportStrFn(str.startsWith, "starts_with"); }
|
||||
comptime {
|
||||
exportStrFn(str.strSplitInPlace, "str_split_in_place");
|
||||
exportStrFn(str.countSegments, "count_segments");
|
||||
exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters");
|
||||
exportStrFn(str.startsWith, "starts_with");
|
||||
exportStrFn(str.endsWith, "ends_with");
|
||||
exportStrFn(str.strConcat, "concat");
|
||||
exportStrFn(str.strNumberOfBytes, "number_of_bytes");
|
||||
exportStrFn(str.strFromInt, "from_int");
|
||||
}
|
||||
|
||||
// Export helpers - Must be run inside a comptime
|
||||
fn exportBuiltinFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
|
||||
@export(fn_target, .{ .name = "roc_builtins." ++ fn_name, .linkage = .Strong });
|
||||
fn exportBuiltinFn(comptime func: anytype, comptime funcName: []const u8) void {
|
||||
@export(func, .{ .name = "roc_builtins." ++ funcName, .linkage = .Strong });
|
||||
}
|
||||
fn exportNumFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
|
||||
exportBuiltinFn(fn_target, "num." ++ fn_name);
|
||||
fn exportNumFn(comptime func: anytype, comptime funcName: []const u8) void {
|
||||
exportBuiltinFn(func, "num." ++ funcName);
|
||||
}
|
||||
fn exportStrFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
|
||||
exportBuiltinFn(fn_target, "str." ++ fn_name);
|
||||
fn exportStrFn(comptime func: anytype, comptime funcName: []const u8) void {
|
||||
exportBuiltinFn(func, "str." ++ funcName);
|
||||
}
|
||||
|
||||
// Run all tests in imported modules
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -24,6 +24,10 @@ pub const NUM_IS_FINITE: &str = "roc_builtins.num.is_finite";
|
||||
pub const NUM_POW_INT: &str = "roc_builtins.num.pow_int";
|
||||
|
||||
pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments";
|
||||
pub const STR_CONCAT: &str = "roc_builtins.str.concat";
|
||||
pub const STR_STR_SPLIT_IN_PLACE: &str = "roc_builtins.str.str_split_in_place";
|
||||
pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters";
|
||||
pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with";
|
||||
pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
|
||||
pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes";
|
||||
pub const STR_FROM_INT: &str = "roc_builtins.str.from_int";
|
||||
|
@ -417,12 +417,24 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||
top_level_function(vec![str_type(), str_type()], Box::new(bool_type())),
|
||||
);
|
||||
|
||||
// endsWith : Str, Str -> Bool
|
||||
add_type(
|
||||
Symbol::STR_ENDS_WITH,
|
||||
top_level_function(vec![str_type(), str_type()], Box::new(bool_type())),
|
||||
);
|
||||
|
||||
// countGraphemes : Str -> Int
|
||||
add_type(
|
||||
Symbol::STR_COUNT_GRAPHEMES,
|
||||
top_level_function(vec![str_type()], Box::new(int_type())),
|
||||
);
|
||||
|
||||
// fromInt : Int -> Str
|
||||
add_type(
|
||||
Symbol::STR_FROM_INT,
|
||||
top_level_function(vec![int_type()], Box::new(str_type())),
|
||||
);
|
||||
|
||||
// List module
|
||||
|
||||
// get : List elem, Int -> Result elem [ OutOfBounds ]*
|
||||
|
@ -1096,12 +1096,24 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||
unique_function(vec![str_type(star1), str_type(star2)], bool_type(star3))
|
||||
});
|
||||
|
||||
// Str.endsWith : Attr * Str, Attr * Str -> Attr * Bool
|
||||
add_type(Symbol::STR_ENDS_WITH, {
|
||||
let_tvars! { star1, star2, star3 };
|
||||
unique_function(vec![str_type(star1), str_type(star2)], bool_type(star3))
|
||||
});
|
||||
|
||||
// Str.countGraphemes : Attr * Str, -> Attr * Int
|
||||
add_type(Symbol::STR_COUNT_GRAPHEMES, {
|
||||
let_tvars! { star1, star2 };
|
||||
unique_function(vec![str_type(star1)], int_type(star2))
|
||||
});
|
||||
|
||||
// fromInt : Attr * Int -> Attr * Str
|
||||
add_type(Symbol::STR_FROM_INT, {
|
||||
let_tvars! { star1, star2 };
|
||||
unique_function(vec![int_type(star1)], str_type(star2))
|
||||
});
|
||||
|
||||
// Result module
|
||||
|
||||
// map : Attr * (Result (Attr a e))
|
||||
|
@ -54,7 +54,9 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
|
||||
Symbol::STR_SPLIT => str_split,
|
||||
Symbol::STR_IS_EMPTY => str_is_empty,
|
||||
Symbol::STR_STARTS_WITH => str_starts_with,
|
||||
Symbol::STR_ENDS_WITH => str_ends_with,
|
||||
Symbol::STR_COUNT_GRAPHEMES => str_count_graphemes,
|
||||
Symbol::STR_FROM_INT => str_from_int,
|
||||
Symbol::LIST_LEN => list_len,
|
||||
Symbol::LIST_GET => list_get,
|
||||
Symbol::LIST_SET => list_set,
|
||||
@ -989,6 +991,26 @@ fn str_starts_with(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
)
|
||||
}
|
||||
|
||||
/// Str.endsWith : Str, Str -> Bool
|
||||
fn str_ends_with(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let str_var = var_store.fresh();
|
||||
let bool_var = var_store.fresh();
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::StrEndsWith,
|
||||
args: vec![(str_var, Var(Symbol::ARG_1)), (str_var, Var(Symbol::ARG_2))],
|
||||
ret_var: bool_var,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(str_var, Symbol::ARG_1), (str_var, Symbol::ARG_2)],
|
||||
var_store,
|
||||
body,
|
||||
bool_var,
|
||||
)
|
||||
}
|
||||
|
||||
/// Str.countGraphemes : Str -> Int
|
||||
fn str_count_graphemes(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let str_var = var_store.fresh();
|
||||
@ -1009,6 +1031,26 @@ fn str_count_graphemes(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
)
|
||||
}
|
||||
|
||||
/// Str.fromInt : Int -> Str
|
||||
fn str_from_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let int_var = var_store.fresh();
|
||||
let str_var = var_store.fresh();
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::StrFromInt,
|
||||
args: vec![(int_var, Var(Symbol::ARG_1))],
|
||||
ret_var: str_var,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(int_var, Symbol::ARG_1)],
|
||||
var_store,
|
||||
body,
|
||||
str_var,
|
||||
)
|
||||
}
|
||||
|
||||
/// List.concat : List elem, List elem -> List elem
|
||||
fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_var = var_store.fresh();
|
||||
|
@ -4,7 +4,8 @@ use crate::llvm::build_list::{
|
||||
list_reverse, list_set, list_single, list_sum, list_walk, list_walk_backwards,
|
||||
};
|
||||
use crate::llvm::build_str::{
|
||||
str_concat, str_count_graphemes, str_len, str_split, str_starts_with, CHAR_LAYOUT,
|
||||
str_concat, str_count_graphemes, str_ends_with, str_from_int, str_number_of_bytes, str_split,
|
||||
str_starts_with, CHAR_LAYOUT,
|
||||
};
|
||||
use crate::llvm::compare::{build_eq, build_neq};
|
||||
use crate::llvm::convert::{
|
||||
@ -604,7 +605,9 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||
|
||||
match expr {
|
||||
Literal(literal) => build_exp_literal(env, literal),
|
||||
RunLowLevel(op, symbols) => run_low_level(env, scope, parent, layout, *op, symbols),
|
||||
RunLowLevel(op, symbols) => {
|
||||
run_low_level(env, layout_ids, scope, parent, layout, *op, symbols)
|
||||
}
|
||||
|
||||
ForeignCall {
|
||||
foreign_symbol,
|
||||
@ -1165,12 +1168,10 @@ fn list_literal<'a, 'ctx, 'env>(
|
||||
let builder = env.builder;
|
||||
|
||||
let len_u64 = elems.len() as u64;
|
||||
let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64;
|
||||
|
||||
let ptr = {
|
||||
let bytes_len = elem_bytes * len_u64;
|
||||
let len_type = env.ptr_int();
|
||||
let len = len_type.const_int(bytes_len, false);
|
||||
let len = len_type.const_int(len_u64, false);
|
||||
|
||||
allocate_list(env, inplace, elem_layout, len)
|
||||
|
||||
@ -2383,6 +2384,7 @@ fn call_with_args<'a, 'ctx, 'env>(
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(u8)]
|
||||
pub enum InPlace {
|
||||
InPlace,
|
||||
Clone,
|
||||
@ -2409,6 +2411,7 @@ pub static COLD_CALL_CONV: u32 = 9;
|
||||
|
||||
fn run_low_level<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
layout: &Layout<'a>,
|
||||
@ -2424,15 +2427,25 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||
|
||||
let inplace = get_inplace_from_layout(layout);
|
||||
|
||||
str_concat(env, inplace, scope, parent, args[0], args[1])
|
||||
str_concat(env, inplace, scope, args[0], args[1])
|
||||
}
|
||||
StrStartsWith => {
|
||||
// Str.startsWith : Str, Str -> Bool
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
let inplace = get_inplace_from_layout(layout);
|
||||
str_starts_with(env, scope, args[0], args[1])
|
||||
}
|
||||
StrEndsWith => {
|
||||
// Str.startsWith : Str, Str -> Bool
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
str_starts_with(env, inplace, scope, parent, args[0], args[1])
|
||||
str_ends_with(env, scope, args[0], args[1])
|
||||
}
|
||||
StrFromInt => {
|
||||
// Str.fromInt : Int -> Str
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
str_from_int(env, scope, args[0])
|
||||
}
|
||||
StrSplit => {
|
||||
// Str.split : Str, Str -> List Str
|
||||
@ -2440,14 +2453,13 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||
|
||||
let inplace = get_inplace_from_layout(layout);
|
||||
|
||||
str_split(env, scope, parent, inplace, args[0], args[1])
|
||||
str_split(env, scope, inplace, args[0], args[1])
|
||||
}
|
||||
StrIsEmpty => {
|
||||
// Str.isEmpty : Str -> Str
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let wrapper_ptr = ptr_from_symbol(scope, args[0]);
|
||||
let len = str_len(env, parent, *wrapper_ptr);
|
||||
let len = str_number_of_bytes(env, scope, args[0]);
|
||||
let is_zero = env.builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
len,
|
||||
@ -2460,7 +2472,7 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||
// Str.countGraphemes : Str -> Int
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
str_count_graphemes(env, scope, parent, args[0])
|
||||
str_count_graphemes(env, scope, args[0])
|
||||
}
|
||||
ListLen => {
|
||||
// List.len : List * -> Int
|
||||
@ -2522,7 +2534,16 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||
|
||||
let inplace = get_inplace_from_layout(layout);
|
||||
|
||||
list_map(env, inplace, parent, func, func_layout, list, list_layout)
|
||||
list_map(
|
||||
env,
|
||||
layout_ids,
|
||||
inplace,
|
||||
parent,
|
||||
func,
|
||||
func_layout,
|
||||
list,
|
||||
list_layout,
|
||||
)
|
||||
}
|
||||
ListKeepIf => {
|
||||
// List.keepIf : List elem, (elem -> Bool) -> List elem
|
||||
|
@ -3,12 +3,13 @@ use crate::llvm::build::{
|
||||
};
|
||||
use crate::llvm::compare::build_eq;
|
||||
use crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type};
|
||||
use crate::llvm::refcounting::{decrement_refcount_layout, increment_refcount_layout};
|
||||
use inkwell::builder::Builder;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::types::{BasicTypeEnum, PointerType};
|
||||
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
|
||||
use inkwell::{AddressSpace, IntPredicate};
|
||||
use roc_mono::layout::{Builtin, Layout, MemoryMode};
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutIds, MemoryMode};
|
||||
|
||||
/// List.single : a -> List a
|
||||
pub fn list_single<'a, 'ctx, 'env>(
|
||||
@ -1318,8 +1319,89 @@ pub fn list_keep_if_help<'a, 'ctx, 'env>(
|
||||
}
|
||||
|
||||
/// List.map : List before, (before -> after) -> List after
|
||||
macro_rules! list_map_help {
|
||||
($env:expr, $layout_ids:expr, $inplace:expr, $parent:expr, $func:expr, $func_layout:expr, $list:expr, $list_layout:expr, $function_ptr:expr, $function_return_layout: expr, $closure_info:expr) => {{
|
||||
let layout_ids = $layout_ids;
|
||||
let inplace = $inplace;
|
||||
let parent = $parent;
|
||||
let func = $func;
|
||||
let func_layout = $func_layout;
|
||||
let list = $list;
|
||||
let list_layout = $list_layout;
|
||||
let function_ptr = $function_ptr;
|
||||
let function_return_layout = $function_return_layout;
|
||||
let closure_info : Option<(&Layout, BasicValueEnum)> = $closure_info;
|
||||
|
||||
|
||||
let non_empty_fn = |elem_layout: &Layout<'a>,
|
||||
len: IntValue<'ctx>,
|
||||
list_wrapper: StructValue<'ctx>| {
|
||||
let ctx = $env.context;
|
||||
let builder = $env.builder;
|
||||
|
||||
let ret_list_ptr = allocate_list($env, inplace, function_return_layout, len);
|
||||
|
||||
let elem_type = basic_type_from_layout($env.arena, ctx, elem_layout, $env.ptr_bytes);
|
||||
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
|
||||
|
||||
let list_ptr = load_list_ptr(builder, list_wrapper, ptr_type);
|
||||
|
||||
let list_loop = |index, before_elem| {
|
||||
increment_refcount_layout($env, parent, layout_ids, before_elem, elem_layout);
|
||||
|
||||
let arguments = match closure_info {
|
||||
Some((closure_data_layout, closure_data)) => {
|
||||
increment_refcount_layout( $env, parent, layout_ids, closure_data, closure_data_layout);
|
||||
|
||||
bumpalo::vec![in $env.arena; before_elem, closure_data]
|
||||
}
|
||||
None => bumpalo::vec![in $env.arena; before_elem],
|
||||
};
|
||||
|
||||
|
||||
let call_site_value = builder.build_call(function_ptr, &arguments, "map_func");
|
||||
|
||||
// set the calling convention explicitly for this call
|
||||
call_site_value.set_call_convention(crate::llvm::build::FAST_CALL_CONV);
|
||||
|
||||
let after_elem = call_site_value
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."));
|
||||
|
||||
// The pointer to the element in the mapped-over list
|
||||
let after_elem_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(ret_list_ptr, &[index], "load_index_after_list")
|
||||
};
|
||||
|
||||
// Mutate the new array in-place to change the element.
|
||||
builder.build_store(after_elem_ptr, after_elem);
|
||||
};
|
||||
|
||||
incrementing_elem_loop(builder, ctx, parent, list_ptr, len, "#index", list_loop);
|
||||
|
||||
let result = store_list($env, ret_list_ptr, len);
|
||||
|
||||
// decrement the input list and function (if it's a closure)
|
||||
decrement_refcount_layout($env, parent, layout_ids, list, list_layout);
|
||||
decrement_refcount_layout($env, parent, layout_ids, func, func_layout);
|
||||
|
||||
if let Some((closure_data_layout, closure_data)) = closure_info {
|
||||
decrement_refcount_layout( $env, parent, layout_ids, closure_data, closure_data_layout);
|
||||
}
|
||||
|
||||
result
|
||||
};
|
||||
|
||||
if_list_is_not_empty($env, parent, non_empty_fn, list, list_layout, "List.map")
|
||||
}};
|
||||
}
|
||||
|
||||
/// List.map : List before, (before -> after) -> List after
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn list_map<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
inplace: InPlace,
|
||||
parent: FunctionValue<'ctx>,
|
||||
func: BasicValueEnum<'ctx>,
|
||||
@ -1329,46 +1411,50 @@ pub fn list_map<'a, 'ctx, 'env>(
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
match (func, func_layout) {
|
||||
(BasicValueEnum::PointerValue(func_ptr), Layout::FunctionPointer(_, ret_elem_layout)) => {
|
||||
let non_empty_fn = |elem_layout: &Layout<'a>,
|
||||
len: IntValue<'ctx>,
|
||||
list_wrapper: StructValue<'ctx>| {
|
||||
let ctx = env.context;
|
||||
let builder = env.builder;
|
||||
list_map_help!(
|
||||
env,
|
||||
layout_ids,
|
||||
inplace,
|
||||
parent,
|
||||
func,
|
||||
func_layout,
|
||||
list,
|
||||
list_layout,
|
||||
func_ptr,
|
||||
ret_elem_layout,
|
||||
None
|
||||
)
|
||||
}
|
||||
(
|
||||
BasicValueEnum::StructValue(ptr_and_data),
|
||||
Layout::Closure(_, closure_layout, ret_elem_layout),
|
||||
) => {
|
||||
let builder = env.builder;
|
||||
|
||||
let ret_list_ptr = allocate_list(env, inplace, ret_elem_layout, len);
|
||||
let func_ptr = builder
|
||||
.build_extract_value(ptr_and_data, 0, "function_ptr")
|
||||
.unwrap()
|
||||
.into_pointer_value();
|
||||
|
||||
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
|
||||
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
|
||||
let closure_data = builder
|
||||
.build_extract_value(ptr_and_data, 1, "closure_data")
|
||||
.unwrap();
|
||||
|
||||
let list_ptr = load_list_ptr(builder, list_wrapper, ptr_type);
|
||||
let closure_data_layout = closure_layout.as_block_of_memory_layout();
|
||||
|
||||
let list_loop = |index, before_elem| {
|
||||
let call_site_value =
|
||||
builder.build_call(func_ptr, env.arena.alloc([before_elem]), "map_func");
|
||||
|
||||
// set the calling convention explicitly for this call
|
||||
call_site_value.set_call_convention(crate::llvm::build::FAST_CALL_CONV);
|
||||
|
||||
let after_elem = call_site_value
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."));
|
||||
|
||||
// The pointer to the element in the mapped-over list
|
||||
let after_elem_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(ret_list_ptr, &[index], "load_index_after_list")
|
||||
};
|
||||
|
||||
// Mutate the new array in-place to change the element.
|
||||
builder.build_store(after_elem_ptr, after_elem);
|
||||
};
|
||||
|
||||
incrementing_elem_loop(builder, ctx, parent, list_ptr, len, "#index", list_loop);
|
||||
|
||||
store_list(env, ret_list_ptr, len)
|
||||
};
|
||||
|
||||
if_list_is_not_empty(env, parent, non_empty_fn, list, list_layout, "List.map")
|
||||
list_map_help!(
|
||||
env,
|
||||
layout_ids,
|
||||
inplace,
|
||||
parent,
|
||||
func,
|
||||
func_layout,
|
||||
list,
|
||||
list_layout,
|
||||
func_ptr,
|
||||
ret_elem_layout,
|
||||
Some((&closure_data_layout, closure_data))
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
unreachable!(
|
||||
@ -1989,7 +2075,6 @@ pub fn allocate_list<'a, 'ctx, 'env>(
|
||||
let len_type = env.ptr_int();
|
||||
let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64;
|
||||
let bytes_per_element = len_type.const_int(elem_bytes, false);
|
||||
|
||||
let number_of_data_bytes = builder.build_int_mul(bytes_per_element, length, "data_length");
|
||||
|
||||
let rc1 = match inplace {
|
||||
|
@ -1,93 +1,120 @@
|
||||
use crate::llvm::build::{
|
||||
call_bitcode_fn, call_void_bitcode_fn, ptr_from_symbol, Env, InPlace, Scope,
|
||||
};
|
||||
use crate::llvm::build_list::{
|
||||
allocate_list, build_basic_phi2, empty_list, incrementing_elem_loop, load_list_ptr, store_list,
|
||||
};
|
||||
use crate::llvm::build_list::{allocate_list, store_list};
|
||||
use crate::llvm::convert::collection;
|
||||
use inkwell::builder::Builder;
|
||||
use inkwell::types::BasicTypeEnum;
|
||||
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
|
||||
use inkwell::{AddressSpace, IntPredicate};
|
||||
use inkwell::values::{BasicValueEnum, IntValue, StructValue};
|
||||
use inkwell::AddressSpace;
|
||||
use roc_builtins::bitcode;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{Builtin, Layout};
|
||||
|
||||
use super::build::load_symbol;
|
||||
|
||||
pub static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8);
|
||||
|
||||
/// Str.split : Str, Str -> List Str
|
||||
pub fn str_split<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
inplace: InPlace,
|
||||
str_symbol: Symbol,
|
||||
delimiter_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
|
||||
let str_ptr = ptr_from_symbol(scope, str_symbol);
|
||||
let delimiter_ptr = ptr_from_symbol(scope, delimiter_symbol);
|
||||
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
|
||||
let delim_i128 = str_symbol_to_i128(env, scope, delimiter_symbol);
|
||||
|
||||
let str_wrapper_type = BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes));
|
||||
|
||||
load_str(
|
||||
let segment_count = call_bitcode_fn(
|
||||
env,
|
||||
parent,
|
||||
*str_ptr,
|
||||
str_wrapper_type,
|
||||
|str_bytes_ptr, str_len, _str_smallness| {
|
||||
load_str(
|
||||
env,
|
||||
parent,
|
||||
*delimiter_ptr,
|
||||
str_wrapper_type,
|
||||
|delimiter_bytes_ptr, delimiter_len, _delimiter_smallness| {
|
||||
let segment_count = call_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
BasicValueEnum::PointerValue(str_bytes_ptr),
|
||||
BasicValueEnum::IntValue(str_len),
|
||||
BasicValueEnum::PointerValue(delimiter_bytes_ptr),
|
||||
BasicValueEnum::IntValue(delimiter_len),
|
||||
],
|
||||
&bitcode::STR_COUNT_SEGMENTS,
|
||||
)
|
||||
.into_int_value();
|
||||
|
||||
// a pointer to the elements
|
||||
let ret_list_ptr =
|
||||
allocate_list(env, inplace, &Layout::Builtin(Builtin::Str), segment_count);
|
||||
|
||||
// get the RocStr type defined by zig
|
||||
let roc_str_type = env.module.get_struct_type("str.RocStr").unwrap();
|
||||
|
||||
// convert `*mut { *mut u8, i64 }` to `*mut RocStr`
|
||||
let ret_list_ptr_zig_rocstr = builder.build_bitcast(
|
||||
ret_list_ptr,
|
||||
roc_str_type.ptr_type(AddressSpace::Generic),
|
||||
"convert_to_zig_rocstr",
|
||||
);
|
||||
|
||||
call_void_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
ret_list_ptr_zig_rocstr,
|
||||
BasicValueEnum::IntValue(segment_count),
|
||||
BasicValueEnum::PointerValue(str_bytes_ptr),
|
||||
BasicValueEnum::IntValue(str_len),
|
||||
BasicValueEnum::PointerValue(delimiter_bytes_ptr),
|
||||
BasicValueEnum::IntValue(delimiter_len),
|
||||
],
|
||||
&bitcode::STR_STR_SPLIT_IN_PLACE,
|
||||
);
|
||||
|
||||
store_list(env, ret_list_ptr, segment_count)
|
||||
},
|
||||
)
|
||||
},
|
||||
&[str_i128.into(), delim_i128.into()],
|
||||
&bitcode::STR_COUNT_SEGMENTS,
|
||||
)
|
||||
.into_int_value();
|
||||
|
||||
// a pointer to the elements
|
||||
let ret_list_ptr = allocate_list(env, inplace, &Layout::Builtin(Builtin::Str), segment_count);
|
||||
|
||||
// get the RocStr type defined by zig
|
||||
let roc_str_type = env.module.get_struct_type("str.RocStr").unwrap();
|
||||
|
||||
// convert `*mut { *mut u8, i64 }` to `*mut RocStr`
|
||||
let ret_list_ptr_zig_rocstr = builder.build_bitcast(
|
||||
ret_list_ptr,
|
||||
roc_str_type.ptr_type(AddressSpace::Generic),
|
||||
"convert_to_zig_rocstr",
|
||||
);
|
||||
|
||||
call_void_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
ret_list_ptr_zig_rocstr,
|
||||
BasicValueEnum::IntValue(segment_count),
|
||||
str_i128.into(),
|
||||
delim_i128.into(),
|
||||
],
|
||||
&bitcode::STR_STR_SPLIT_IN_PLACE,
|
||||
);
|
||||
|
||||
store_list(env, ret_list_ptr, segment_count)
|
||||
}
|
||||
|
||||
fn str_symbol_to_i128<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
symbol: Symbol,
|
||||
) -> IntValue<'ctx> {
|
||||
let str_ptr = ptr_from_symbol(scope, symbol);
|
||||
|
||||
let i128_ptr = env
|
||||
.builder
|
||||
.build_bitcast(
|
||||
*str_ptr,
|
||||
env.context.i128_type().ptr_type(AddressSpace::Generic),
|
||||
"cast",
|
||||
)
|
||||
.into_pointer_value();
|
||||
|
||||
env.builder
|
||||
.build_load(i128_ptr, "load_as_i128")
|
||||
.into_int_value()
|
||||
}
|
||||
|
||||
fn zig_str_to_struct<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
zig_str: StructValue<'ctx>,
|
||||
) -> StructValue<'ctx> {
|
||||
let builder = env.builder;
|
||||
|
||||
// get the RocStr type defined by zig
|
||||
let zig_str_type = env.module.get_struct_type("str.RocStr").unwrap();
|
||||
|
||||
let ret_type = BasicTypeEnum::StructType(collection(env.context, env.ptr_bytes));
|
||||
|
||||
// a roundabout way of casting (LLVM does not accept a standard bitcast)
|
||||
let allocation = builder.build_alloca(zig_str_type, "zig_result");
|
||||
|
||||
builder.build_store(allocation, zig_str);
|
||||
|
||||
let ptr3 = builder
|
||||
.build_bitcast(
|
||||
allocation,
|
||||
env.context.i128_type().ptr_type(AddressSpace::Generic),
|
||||
"cast",
|
||||
)
|
||||
.into_pointer_value();
|
||||
|
||||
let ptr4 = builder
|
||||
.build_bitcast(
|
||||
ptr3,
|
||||
ret_type.into_struct_type().ptr_type(AddressSpace::Generic),
|
||||
"cast",
|
||||
)
|
||||
.into_pointer_value();
|
||||
|
||||
builder.build_load(ptr4, "load").into_struct_value()
|
||||
}
|
||||
|
||||
/// Str.concat : Str, Str -> Str
|
||||
@ -95,622 +122,81 @@ pub fn str_concat<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
inplace: InPlace,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
first_str_symbol: Symbol,
|
||||
second_str_symbol: Symbol,
|
||||
str1_symbol: Symbol,
|
||||
str2_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
// swap the arguments; second argument comes before the second in the output string
|
||||
let str1_i128 = str_symbol_to_i128(env, scope, str1_symbol);
|
||||
let str2_i128 = str_symbol_to_i128(env, scope, str2_symbol);
|
||||
|
||||
let second_str_ptr = ptr_from_symbol(scope, second_str_symbol);
|
||||
let first_str_ptr = ptr_from_symbol(scope, first_str_symbol);
|
||||
|
||||
let ret_type = BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes));
|
||||
|
||||
load_str(
|
||||
let zig_result = call_bitcode_fn(
|
||||
env,
|
||||
parent,
|
||||
*second_str_ptr,
|
||||
ret_type,
|
||||
|second_str_ptr, second_str_len, second_str_smallness| {
|
||||
load_str(
|
||||
env,
|
||||
parent,
|
||||
*first_str_ptr,
|
||||
ret_type,
|
||||
|first_str_ptr, first_str_len, first_str_smallness| {
|
||||
// first_str_len > 0
|
||||
// We do this check to avoid allocating memory. If the first input
|
||||
// str is empty, then we can just return the second str cloned
|
||||
let first_str_length_comparison = str_is_not_empty(env, first_str_len);
|
||||
|
||||
let if_first_str_is_empty = || {
|
||||
// second_str_len > 0
|
||||
// We do this check to avoid allocating memory. If the second input
|
||||
// str is empty, then we can just return an empty str
|
||||
let second_str_length_comparison = str_is_not_empty(env, second_str_len);
|
||||
|
||||
let if_second_str_is_nonempty = || {
|
||||
let (new_wrapper, _) = clone_nonempty_str(
|
||||
env,
|
||||
inplace,
|
||||
second_str_smallness,
|
||||
second_str_len,
|
||||
second_str_ptr,
|
||||
);
|
||||
|
||||
BasicValueEnum::StructValue(new_wrapper)
|
||||
};
|
||||
|
||||
let if_second_str_is_empty = || empty_list(env);
|
||||
|
||||
build_basic_phi2(
|
||||
env,
|
||||
parent,
|
||||
second_str_length_comparison,
|
||||
if_second_str_is_nonempty,
|
||||
if_second_str_is_empty,
|
||||
ret_type,
|
||||
)
|
||||
};
|
||||
|
||||
let if_first_str_is_not_empty = || {
|
||||
let if_second_str_is_empty = || {
|
||||
let (new_wrapper, _) = clone_nonempty_str(
|
||||
env,
|
||||
inplace,
|
||||
first_str_smallness,
|
||||
first_str_len,
|
||||
first_str_ptr,
|
||||
);
|
||||
|
||||
BasicValueEnum::StructValue(new_wrapper)
|
||||
};
|
||||
|
||||
// second_str_len > 0
|
||||
// We do this check to avoid allocating memory. If the second input
|
||||
// str is empty, then we can just return the first str cloned
|
||||
let second_str_length_comparison = str_is_not_empty(env, second_str_len);
|
||||
|
||||
let if_second_str_is_not_empty = || {
|
||||
let combined_str_len = builder.build_int_add(
|
||||
first_str_len,
|
||||
second_str_len,
|
||||
"add_list_lengths",
|
||||
);
|
||||
|
||||
// The combined string is big iff its length is
|
||||
// greater than or equal to the size in memory
|
||||
// of a small str (e.g. len >= 16 on 64-bit targets)
|
||||
let is_big = env.builder.build_int_compare(
|
||||
IntPredicate::UGE,
|
||||
combined_str_len,
|
||||
env.ptr_int().const_int(env.small_str_bytes() as u64, false),
|
||||
"str_is_big",
|
||||
);
|
||||
|
||||
let if_big = || {
|
||||
let combined_str_ptr =
|
||||
allocate_list(env, inplace, &CHAR_LAYOUT, combined_str_len);
|
||||
|
||||
// TODO replace FIRST_LOOP with a memcpy!
|
||||
// FIRST LOOP
|
||||
let first_loop = |first_index, first_str_elem| {
|
||||
// The pointer to the element in the combined list
|
||||
let combined_str_elem_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
combined_str_ptr,
|
||||
&[first_index],
|
||||
"load_index_combined_list",
|
||||
)
|
||||
};
|
||||
|
||||
// Mutate the new array in-place to change the element.
|
||||
builder.build_store(combined_str_elem_ptr, first_str_elem);
|
||||
};
|
||||
|
||||
let index_name = "#index";
|
||||
|
||||
let index_alloca = incrementing_elem_loop(
|
||||
builder,
|
||||
ctx,
|
||||
parent,
|
||||
first_str_ptr,
|
||||
first_str_len,
|
||||
index_name,
|
||||
first_loop,
|
||||
);
|
||||
|
||||
// Reset the index variable to 0
|
||||
builder
|
||||
.build_store(index_alloca, ctx.i64_type().const_int(0, false));
|
||||
|
||||
// TODO replace SECOND_LOOP with a memcpy!
|
||||
// SECOND LOOP
|
||||
let second_loop = |second_index, second_str_elem| {
|
||||
// The pointer to the element in the combined str.
|
||||
// Note that the pointer does not start at the index
|
||||
// 0, it starts at the index of first_str_len. In that
|
||||
// sense it is "offset".
|
||||
let offset_combined_str_char_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
combined_str_ptr,
|
||||
&[first_str_len],
|
||||
"elem",
|
||||
)
|
||||
};
|
||||
|
||||
// The pointer to the char from the second str
|
||||
// in the combined list
|
||||
let combined_str_char_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
offset_combined_str_char_ptr,
|
||||
&[second_index],
|
||||
"load_index_combined_list",
|
||||
)
|
||||
};
|
||||
|
||||
// Mutate the new array in-place to change the element.
|
||||
builder.build_store(combined_str_char_ptr, second_str_elem);
|
||||
};
|
||||
|
||||
incrementing_elem_loop(
|
||||
builder,
|
||||
ctx,
|
||||
parent,
|
||||
second_str_ptr,
|
||||
second_str_len,
|
||||
index_name,
|
||||
second_loop,
|
||||
);
|
||||
|
||||
store_list(env, combined_str_ptr, combined_str_len)
|
||||
};
|
||||
|
||||
let if_small = || {
|
||||
let combined_str_ptr = builder.build_array_alloca(
|
||||
ctx.i8_type(),
|
||||
ctx.i8_type().const_int(env.small_str_bytes() as u64, false),
|
||||
"alloca_small_str",
|
||||
);
|
||||
|
||||
// TODO replace FIRST_LOOP with a memcpy!
|
||||
// FIRST LOOP
|
||||
let first_loop = |first_index, first_str_elem| {
|
||||
// The pointer to the element in the combined list
|
||||
let combined_str_elem_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
combined_str_ptr,
|
||||
&[first_index],
|
||||
"load_index_combined_list",
|
||||
)
|
||||
};
|
||||
|
||||
// Mutate the new array in-place to change the element.
|
||||
builder.build_store(combined_str_elem_ptr, first_str_elem);
|
||||
};
|
||||
|
||||
let index_name = "#index";
|
||||
|
||||
let index_alloca = incrementing_elem_loop(
|
||||
builder,
|
||||
ctx,
|
||||
parent,
|
||||
first_str_ptr,
|
||||
first_str_len,
|
||||
index_name,
|
||||
first_loop,
|
||||
);
|
||||
|
||||
// Reset the index variable to 0
|
||||
builder
|
||||
.build_store(index_alloca, ctx.i64_type().const_int(0, false));
|
||||
|
||||
// TODO replace SECOND_LOOP with a memcpy!
|
||||
// SECOND LOOP
|
||||
let second_loop = |second_index, second_str_elem| {
|
||||
// The pointer to the element in the combined str.
|
||||
// Note that the pointer does not start at the index
|
||||
// 0, it starts at the index of first_str_len. In that
|
||||
// sense it is "offset".
|
||||
let offset_combined_str_char_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
combined_str_ptr,
|
||||
&[first_str_len],
|
||||
"elem",
|
||||
)
|
||||
};
|
||||
|
||||
// The pointer to the char from the second str
|
||||
// in the combined list
|
||||
let combined_str_char_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
offset_combined_str_char_ptr,
|
||||
&[second_index],
|
||||
"load_index_combined_list",
|
||||
)
|
||||
};
|
||||
|
||||
// Mutate the new array in-place to change the element.
|
||||
builder.build_store(combined_str_char_ptr, second_str_elem);
|
||||
};
|
||||
|
||||
incrementing_elem_loop(
|
||||
builder,
|
||||
ctx,
|
||||
parent,
|
||||
second_str_ptr,
|
||||
second_str_len,
|
||||
index_name,
|
||||
second_loop,
|
||||
);
|
||||
|
||||
let final_byte = builder.build_int_cast(
|
||||
combined_str_len,
|
||||
ctx.i8_type(),
|
||||
"str_len_to_i8",
|
||||
);
|
||||
|
||||
let final_byte = builder.build_or(
|
||||
final_byte,
|
||||
ctx.i8_type().const_int(0b1000_0000, false),
|
||||
"str_len_set_discriminant",
|
||||
);
|
||||
|
||||
let final_byte_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
combined_str_ptr,
|
||||
&[ctx
|
||||
.i8_type()
|
||||
.const_int(env.small_str_bytes() as u64 - 1, false)],
|
||||
"str_literal_final_byte",
|
||||
)
|
||||
};
|
||||
|
||||
builder.build_store(final_byte_ptr, final_byte);
|
||||
|
||||
builder.build_load(
|
||||
builder
|
||||
.build_bitcast(
|
||||
combined_str_ptr,
|
||||
collection(ctx, env.ptr_bytes)
|
||||
.ptr_type(AddressSpace::Generic),
|
||||
"cast_collection",
|
||||
)
|
||||
.into_pointer_value(),
|
||||
"small_str_array",
|
||||
)
|
||||
};
|
||||
|
||||
// If the combined length fits in a small string,
|
||||
// write into a small string!
|
||||
build_basic_phi2(
|
||||
env,
|
||||
parent,
|
||||
is_big,
|
||||
// the result of a Str.concat is most likely big
|
||||
if_big,
|
||||
if_small,
|
||||
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
|
||||
)
|
||||
};
|
||||
|
||||
build_basic_phi2(
|
||||
env,
|
||||
parent,
|
||||
second_str_length_comparison,
|
||||
if_second_str_is_not_empty,
|
||||
if_second_str_is_empty,
|
||||
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
|
||||
)
|
||||
};
|
||||
|
||||
build_basic_phi2(
|
||||
env,
|
||||
parent,
|
||||
first_str_length_comparison,
|
||||
if_first_str_is_not_empty,
|
||||
if_first_str_is_empty,
|
||||
BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Obtain the string's length, cast from i8 to usize
|
||||
fn str_len_from_final_byte<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
final_byte: IntValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
let bitmask = ctx.i8_type().const_int(0b0111_1111, false);
|
||||
let len_i8 = builder.build_and(final_byte, bitmask, "small_str_length");
|
||||
|
||||
builder.build_int_cast(len_i8, env.ptr_int(), "len_as_usize")
|
||||
}
|
||||
|
||||
/// Used by LowLevel::StrIsEmpty
|
||||
pub fn str_len<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
wrapper_ptr: PointerValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
let builder = env.builder;
|
||||
|
||||
let if_small = |final_byte| {
|
||||
let len = str_len_from_final_byte(env, final_byte);
|
||||
|
||||
BasicValueEnum::IntValue(len)
|
||||
};
|
||||
|
||||
let if_big = |_| {
|
||||
let len = big_str_len(
|
||||
builder,
|
||||
builder
|
||||
.build_load(wrapper_ptr, "big_str")
|
||||
.into_struct_value(),
|
||||
);
|
||||
|
||||
BasicValueEnum::IntValue(len)
|
||||
};
|
||||
|
||||
if_small_str(
|
||||
env,
|
||||
parent,
|
||||
wrapper_ptr,
|
||||
if_small,
|
||||
if_big,
|
||||
BasicTypeEnum::IntType(env.ptr_int()),
|
||||
)
|
||||
.into_int_value()
|
||||
}
|
||||
|
||||
fn load_str<'a, 'ctx, 'env, Callback>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
wrapper_ptr: PointerValue<'ctx>,
|
||||
ret_type: BasicTypeEnum<'ctx>,
|
||||
cb: Callback,
|
||||
) -> BasicValueEnum<'ctx>
|
||||
where
|
||||
Callback: Fn(PointerValue<'ctx>, IntValue<'ctx>, Smallness) -> BasicValueEnum<'ctx>,
|
||||
{
|
||||
let builder = env.builder;
|
||||
|
||||
let if_small = |final_byte| {
|
||||
cb(
|
||||
cast_str_wrapper_to_array(env, wrapper_ptr),
|
||||
str_len_from_final_byte(env, final_byte),
|
||||
Smallness::Small,
|
||||
)
|
||||
};
|
||||
|
||||
let if_big = |wrapper_struct| {
|
||||
let list_ptr = load_list_ptr(
|
||||
builder,
|
||||
wrapper_struct,
|
||||
env.context.i8_type().ptr_type(AddressSpace::Generic),
|
||||
);
|
||||
|
||||
cb(
|
||||
list_ptr,
|
||||
big_str_len(builder, wrapper_struct),
|
||||
Smallness::Big,
|
||||
)
|
||||
};
|
||||
|
||||
if_small_str(env, parent, wrapper_ptr, if_small, if_big, ret_type)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum Smallness {
|
||||
Small,
|
||||
Big,
|
||||
}
|
||||
|
||||
fn clone_nonempty_str<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
inplace: InPlace,
|
||||
smallness: Smallness,
|
||||
len: IntValue<'ctx>,
|
||||
bytes_ptr: PointerValue<'ctx>,
|
||||
) -> (StructValue<'ctx>, PointerValue<'ctx>) {
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
let ptr_bytes = env.ptr_bytes;
|
||||
|
||||
// Allocate space for the new str that we'll copy into.
|
||||
match smallness {
|
||||
Smallness::Small => {
|
||||
let wrapper_struct_ptr = cast_str_bytes_to_wrapper(env, bytes_ptr);
|
||||
let wrapper_struct = builder.build_load(wrapper_struct_ptr, "str_wrapper");
|
||||
let alloca = builder.build_alloca(collection(ctx, ptr_bytes), "small_str_clone");
|
||||
|
||||
builder.build_store(alloca, wrapper_struct);
|
||||
|
||||
(wrapper_struct.into_struct_value(), alloca)
|
||||
}
|
||||
Smallness::Big => {
|
||||
let clone_ptr = allocate_list(env, inplace, &CHAR_LAYOUT, len);
|
||||
|
||||
// TODO check if malloc returned null; if so, runtime error for OOM!
|
||||
|
||||
// Copy the bytes from the original array into the new
|
||||
// one we just malloc'd.
|
||||
builder
|
||||
.build_memcpy(clone_ptr, ptr_bytes, bytes_ptr, ptr_bytes, len)
|
||||
.unwrap();
|
||||
|
||||
// Create a fresh wrapper struct for the newly populated array
|
||||
let struct_type = collection(ctx, env.ptr_bytes);
|
||||
let mut struct_val;
|
||||
|
||||
// Store the pointer
|
||||
struct_val = builder
|
||||
.build_insert_value(
|
||||
struct_type.get_undef(),
|
||||
clone_ptr,
|
||||
Builtin::WRAPPER_PTR,
|
||||
"insert_ptr",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Store the length
|
||||
struct_val = builder
|
||||
.build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len")
|
||||
.unwrap();
|
||||
|
||||
let answer = builder
|
||||
.build_bitcast(
|
||||
struct_val.into_struct_value(),
|
||||
collection(ctx, ptr_bytes),
|
||||
"cast_collection",
|
||||
)
|
||||
.into_struct_value();
|
||||
|
||||
(answer, clone_ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cast_str_bytes_to_wrapper<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
bytes_ptr: PointerValue<'ctx>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let struct_ptr_type = collection(env.context, env.ptr_bytes).ptr_type(AddressSpace::Generic);
|
||||
|
||||
env.builder
|
||||
.build_bitcast(bytes_ptr, struct_ptr_type, "str_as_struct_ptr")
|
||||
.into_pointer_value()
|
||||
}
|
||||
|
||||
fn cast_str_wrapper_to_array<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
wrapper_ptr: PointerValue<'ctx>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let array_ptr_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
|
||||
|
||||
env.builder
|
||||
.build_bitcast(wrapper_ptr, array_ptr_type, "str_as_array_ptr")
|
||||
.into_pointer_value()
|
||||
}
|
||||
|
||||
fn if_small_str<'a, 'ctx, 'env, IfSmallFn, IfBigFn>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
wrapper_ptr: PointerValue<'ctx>,
|
||||
mut if_small: IfSmallFn,
|
||||
mut if_big: IfBigFn,
|
||||
ret_type: BasicTypeEnum<'ctx>,
|
||||
) -> BasicValueEnum<'ctx>
|
||||
where
|
||||
IfSmallFn: FnMut(IntValue<'ctx>) -> BasicValueEnum<'ctx>,
|
||||
IfBigFn: FnMut(StructValue<'ctx>) -> BasicValueEnum<'ctx>,
|
||||
{
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
let byte_array_ptr = cast_str_wrapper_to_array(env, wrapper_ptr);
|
||||
let final_byte_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
byte_array_ptr,
|
||||
&[ctx
|
||||
&[
|
||||
env.context
|
||||
.i32_type()
|
||||
.const_int(env.ptr_bytes as u64, false)
|
||||
.into(),
|
||||
env.context
|
||||
.i8_type()
|
||||
.const_int(env.small_str_bytes() as u64 - 1, false)],
|
||||
"final_byte_ptr",
|
||||
)
|
||||
};
|
||||
|
||||
let final_byte = builder
|
||||
.build_load(final_byte_ptr, "load_final_byte")
|
||||
.into_int_value();
|
||||
|
||||
let bitmask = ctx.i8_type().const_int(0b1000_0000, false);
|
||||
|
||||
let is_small_i8 = builder.build_int_compare(
|
||||
IntPredicate::NE,
|
||||
ctx.i8_type().const_zero(),
|
||||
builder.build_and(final_byte, bitmask, "is_small"),
|
||||
"is_small_comparison",
|
||||
);
|
||||
|
||||
let is_small = builder.build_int_cast(is_small_i8, ctx.bool_type(), "is_small_as_bool");
|
||||
|
||||
build_basic_phi2(
|
||||
env,
|
||||
parent,
|
||||
is_small,
|
||||
|| if_small(final_byte),
|
||||
|| {
|
||||
if_big(
|
||||
builder
|
||||
.build_load(wrapper_ptr, "load_wrapper_struct")
|
||||
.into_struct_value(),
|
||||
)
|
||||
},
|
||||
ret_type,
|
||||
.const_int(inplace as u64, false)
|
||||
.into(),
|
||||
str1_i128.into(),
|
||||
str2_i128.into(),
|
||||
],
|
||||
&bitcode::STR_CONCAT,
|
||||
)
|
||||
.into_struct_value();
|
||||
|
||||
zig_str_to_struct(env, zig_result).into()
|
||||
}
|
||||
|
||||
fn big_str_len<'ctx>(builder: &Builder<'ctx>, wrapper_struct: StructValue<'ctx>) -> IntValue<'ctx> {
|
||||
builder
|
||||
.build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "big_str_len")
|
||||
.unwrap()
|
||||
.into_int_value()
|
||||
}
|
||||
pub fn str_number_of_bytes<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
str_symbol: Symbol,
|
||||
) -> IntValue<'ctx> {
|
||||
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
|
||||
|
||||
fn str_is_not_empty<'ctx>(env: &Env<'_, 'ctx, '_>, len: IntValue<'ctx>) -> IntValue<'ctx> {
|
||||
env.builder.build_int_compare(
|
||||
IntPredicate::UGT,
|
||||
len,
|
||||
env.ptr_int().const_zero(),
|
||||
"str_len_is_nonzero",
|
||||
)
|
||||
// the builtin will always return an u64
|
||||
let length =
|
||||
call_bitcode_fn(env, &[str_i128.into()], &bitcode::STR_NUMBER_OF_BYTES).into_int_value();
|
||||
|
||||
// cast to the appropriate usize of the current build
|
||||
env.builder
|
||||
.build_int_cast(length, env.ptr_int(), "len_as_usize")
|
||||
}
|
||||
|
||||
/// Str.startsWith : Str, Str -> Bool
|
||||
pub fn str_starts_with<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
_inplace: InPlace,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
str_symbol: Symbol,
|
||||
prefix_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let ctx = env.context;
|
||||
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
|
||||
let prefix_i128 = str_symbol_to_i128(env, scope, prefix_symbol);
|
||||
|
||||
let str_ptr = ptr_from_symbol(scope, str_symbol);
|
||||
let prefix_ptr = ptr_from_symbol(scope, prefix_symbol);
|
||||
|
||||
let ret_type = BasicTypeEnum::IntType(ctx.bool_type());
|
||||
|
||||
load_str(
|
||||
call_bitcode_fn(
|
||||
env,
|
||||
parent,
|
||||
*str_ptr,
|
||||
ret_type,
|
||||
|str_bytes_ptr, str_len, _str_smallness| {
|
||||
load_str(
|
||||
env,
|
||||
parent,
|
||||
*prefix_ptr,
|
||||
ret_type,
|
||||
|prefix_bytes_ptr, prefix_len, _prefix_smallness| {
|
||||
call_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
BasicValueEnum::PointerValue(str_bytes_ptr),
|
||||
BasicValueEnum::IntValue(str_len),
|
||||
BasicValueEnum::PointerValue(prefix_bytes_ptr),
|
||||
BasicValueEnum::IntValue(prefix_len),
|
||||
],
|
||||
&bitcode::STR_STARTS_WITH,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
&[str_i128.into(), prefix_i128.into()],
|
||||
&bitcode::STR_STARTS_WITH,
|
||||
)
|
||||
}
|
||||
|
||||
/// Str.endsWith : Str, Str -> Bool
|
||||
pub fn str_ends_with<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
str_symbol: Symbol,
|
||||
prefix_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
|
||||
let prefix_i128 = str_symbol_to_i128(env, scope, prefix_symbol);
|
||||
|
||||
call_bitcode_fn(
|
||||
env,
|
||||
&[str_i128.into(), prefix_i128.into()],
|
||||
&bitcode::STR_ENDS_WITH,
|
||||
)
|
||||
}
|
||||
|
||||
@ -718,28 +204,26 @@ pub fn str_starts_with<'a, 'ctx, 'env>(
|
||||
pub fn str_count_graphemes<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
str_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let ctx = env.context;
|
||||
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
|
||||
|
||||
let sym_str_ptr = ptr_from_symbol(scope, str_symbol);
|
||||
let ret_type = BasicTypeEnum::IntType(ctx.i64_type());
|
||||
|
||||
load_str(
|
||||
call_bitcode_fn(
|
||||
env,
|
||||
parent,
|
||||
*sym_str_ptr,
|
||||
ret_type,
|
||||
|str_ptr, str_len, _str_smallness| {
|
||||
call_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
BasicValueEnum::PointerValue(str_ptr),
|
||||
BasicValueEnum::IntValue(str_len),
|
||||
],
|
||||
&bitcode::STR_COUNT_GRAPEHEME_CLUSTERS,
|
||||
)
|
||||
},
|
||||
&[str_i128.into()],
|
||||
&bitcode::STR_COUNT_GRAPEHEME_CLUSTERS,
|
||||
)
|
||||
}
|
||||
|
||||
/// Str.fromInt : Int -> Str
|
||||
pub fn str_from_int<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
int_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let int = load_symbol(env, scope, &int_symbol);
|
||||
|
||||
let zig_result = call_bitcode_fn(env, &[int], &bitcode::STR_FROM_INT).into_struct_value();
|
||||
|
||||
zig_str_to_struct(env, zig_result).into()
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use crate::llvm::build::{
|
||||
cast_basic_basic, cast_struct_struct, create_entry_block_alloca, set_name, Env, Scope,
|
||||
FAST_CALL_CONV, LLVM_SADD_WITH_OVERFLOW_I64,
|
||||
};
|
||||
use crate::llvm::build_list::list_len;
|
||||
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
|
||||
use crate::llvm::convert::{basic_type_from_layout, block_of_memory, ptr_int};
|
||||
use bumpalo::collections::Vec;
|
||||
use inkwell::context::Context;
|
||||
@ -367,7 +367,6 @@ fn decrement_refcount_builtin<'a, 'ctx, 'env>(
|
||||
List(memory_mode, element_layout) => {
|
||||
let wrapper_struct = value.into_struct_value();
|
||||
if element_layout.contains_refcounted() {
|
||||
use crate::llvm::build_list::{incrementing_elem_loop, load_list};
|
||||
use inkwell::types::BasicType;
|
||||
|
||||
let ptr_type =
|
||||
@ -451,7 +450,6 @@ fn increment_refcount_builtin<'a, 'ctx, 'env>(
|
||||
List(memory_mode, element_layout) => {
|
||||
let wrapper_struct = value.into_struct_value();
|
||||
if element_layout.contains_refcounted() {
|
||||
use crate::llvm::build_list::{incrementing_elem_loop, load_list};
|
||||
use inkwell::types::BasicType;
|
||||
|
||||
let ptr_type =
|
||||
|
@ -557,6 +557,26 @@ mod gen_list {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_map_closure() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
pi : F64
|
||||
pi = 3.14
|
||||
|
||||
single : List F64
|
||||
single =
|
||||
[ 0 ]
|
||||
|
||||
List.map single (\x -> x + pi)
|
||||
"#
|
||||
),
|
||||
RocList::from_slice(&[3.14]),
|
||||
RocList<f64>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_join_empty_list() {
|
||||
assert_evals_to!("List.join []", RocList::from_slice(&[]), RocList<i64>);
|
||||
|
@ -433,6 +433,13 @@ mod gen_str {
|
||||
assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_ends_with() {
|
||||
assert_evals_to!(r#"Str.endsWith "hello world" "world""#, true, bool);
|
||||
assert_evals_to!(r#"Str.endsWith "nope" "hello world""#, false, bool);
|
||||
assert_evals_to!(r#"Str.endsWith "" "hello world""#, false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_count_graphemes_small_str() {
|
||||
assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize);
|
||||
@ -483,4 +490,29 @@ mod gen_str {
|
||||
fn str_starts_with_false_small_str() {
|
||||
assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_from_int() {
|
||||
assert_evals_to!(
|
||||
r#"Str.fromInt 1234"#,
|
||||
roc_std::RocStr::from_slice("1234".as_bytes()),
|
||||
roc_std::RocStr
|
||||
);
|
||||
assert_evals_to!(
|
||||
r#"Str.fromInt 0"#,
|
||||
roc_std::RocStr::from_slice("0".as_bytes()),
|
||||
roc_std::RocStr
|
||||
);
|
||||
assert_evals_to!(
|
||||
r#"Str.fromInt -1"#,
|
||||
roc_std::RocStr::from_slice("-1".as_bytes()),
|
||||
roc_std::RocStr
|
||||
);
|
||||
|
||||
let max = format!("{}", i64::MAX);
|
||||
assert_evals_to!(r#"Str.fromInt Num.maxInt"#, &max, &'static str);
|
||||
|
||||
let min = format!("{}", i64::MIN);
|
||||
assert_evals_to!(r#"Str.fromInt Num.minInt"#, &min, &'static str);
|
||||
}
|
||||
}
|
||||
|
@ -42,3 +42,6 @@ bumpalo = { version = "3.2", features = ["collections"] }
|
||||
libc = "0.2"
|
||||
tempfile = "3.1.0"
|
||||
itertools = "0.9"
|
||||
|
||||
[features]
|
||||
target-aarch64 = ["roc_build/target-aarch64"]
|
||||
|
814
compiler/gen_dev/src/generic64/aarch64.rs
Normal file
814
compiler/gen_dev/src/generic64/aarch64.rs
Normal file
@ -0,0 +1,814 @@
|
||||
use crate::generic64::{Assembler, CallConv, GPRegTrait};
|
||||
use bumpalo::collections::Vec;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum AArch64GPReg {
|
||||
X0 = 0,
|
||||
X1 = 1,
|
||||
X2 = 2,
|
||||
X3 = 3,
|
||||
X4 = 4,
|
||||
X5 = 5,
|
||||
X6 = 6,
|
||||
X7 = 7,
|
||||
XR = 8,
|
||||
X9 = 9,
|
||||
X10 = 10,
|
||||
X11 = 11,
|
||||
X12 = 12,
|
||||
X13 = 13,
|
||||
X14 = 14,
|
||||
X15 = 15,
|
||||
IP0 = 16,
|
||||
IP1 = 17,
|
||||
PR = 18,
|
||||
X19 = 19,
|
||||
X20 = 20,
|
||||
X21 = 21,
|
||||
X22 = 22,
|
||||
X23 = 23,
|
||||
X24 = 24,
|
||||
X25 = 25,
|
||||
X26 = 26,
|
||||
X27 = 27,
|
||||
X28 = 28,
|
||||
FP = 29,
|
||||
LR = 30,
|
||||
/// This can mean Zero or Stack Pointer depending on the context.
|
||||
ZRSP = 31,
|
||||
}
|
||||
|
||||
impl GPRegTrait for AArch64GPReg {}
|
||||
|
||||
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.
|
||||
pub struct AArch64Call {}
|
||||
|
||||
const STACK_ALIGNMENT: u8 = 16;
|
||||
|
||||
impl CallConv<AArch64GPReg> for AArch64Call {
|
||||
const GP_PARAM_REGS: &'static [AArch64GPReg] = &[
|
||||
AArch64GPReg::X0,
|
||||
AArch64GPReg::X1,
|
||||
AArch64GPReg::X2,
|
||||
AArch64GPReg::X3,
|
||||
AArch64GPReg::X4,
|
||||
AArch64GPReg::X5,
|
||||
AArch64GPReg::X6,
|
||||
AArch64GPReg::X7,
|
||||
];
|
||||
const GP_RETURN_REGS: &'static [AArch64GPReg] = Self::GP_PARAM_REGS;
|
||||
const GP_DEFAULT_FREE_REGS: &'static [AArch64GPReg] = &[
|
||||
// The regs we want to use first should be at the end of this vec.
|
||||
// We will use pop to get which reg to use next
|
||||
|
||||
// Don't use frame pointer: AArch64GPReg::FP,
|
||||
// Don't user indirect result location: AArch64GPReg::XR,
|
||||
// Don't use platform register: AArch64GPReg::PR,
|
||||
// Don't use link register: AArch64GPReg::LR,
|
||||
// Don't use zero register/stack pointer: AArch64GPReg::ZRSP,
|
||||
|
||||
// Use callee saved regs last.
|
||||
AArch64GPReg::X19,
|
||||
AArch64GPReg::X20,
|
||||
AArch64GPReg::X21,
|
||||
AArch64GPReg::X22,
|
||||
AArch64GPReg::X23,
|
||||
AArch64GPReg::X24,
|
||||
AArch64GPReg::X25,
|
||||
AArch64GPReg::X26,
|
||||
AArch64GPReg::X27,
|
||||
AArch64GPReg::X28,
|
||||
// Use caller saved regs first.
|
||||
AArch64GPReg::X0,
|
||||
AArch64GPReg::X1,
|
||||
AArch64GPReg::X2,
|
||||
AArch64GPReg::X3,
|
||||
AArch64GPReg::X4,
|
||||
AArch64GPReg::X5,
|
||||
AArch64GPReg::X6,
|
||||
AArch64GPReg::X7,
|
||||
AArch64GPReg::X9,
|
||||
AArch64GPReg::X10,
|
||||
AArch64GPReg::X11,
|
||||
AArch64GPReg::X12,
|
||||
AArch64GPReg::X13,
|
||||
AArch64GPReg::X14,
|
||||
AArch64GPReg::X15,
|
||||
AArch64GPReg::IP0,
|
||||
AArch64GPReg::IP1,
|
||||
];
|
||||
|
||||
const SHADOW_SPACE_SIZE: u8 = 0;
|
||||
|
||||
#[inline(always)]
|
||||
fn callee_saved(reg: &AArch64GPReg) -> bool {
|
||||
matches!(
|
||||
reg,
|
||||
AArch64GPReg::X19
|
||||
| AArch64GPReg::X20
|
||||
| AArch64GPReg::X21
|
||||
| AArch64GPReg::X22
|
||||
| AArch64GPReg::X23
|
||||
| AArch64GPReg::X24
|
||||
| AArch64GPReg::X25
|
||||
| AArch64GPReg::X26
|
||||
| AArch64GPReg::X27
|
||||
| AArch64GPReg::X28
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn setup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
leaf_function: bool,
|
||||
saved_regs: &[AArch64GPReg],
|
||||
requested_stack_size: i32,
|
||||
) -> Result<i32, String> {
|
||||
// full size is upcast to i64 to make sure we don't overflow here.
|
||||
let mut full_size = 8 * saved_regs.len() as i64 + requested_stack_size as i64;
|
||||
if !leaf_function {
|
||||
full_size += 8;
|
||||
}
|
||||
let alignment = if full_size <= 0 {
|
||||
0
|
||||
} else {
|
||||
full_size % STACK_ALIGNMENT as i64
|
||||
};
|
||||
let offset = if alignment == 0 {
|
||||
0
|
||||
} else {
|
||||
STACK_ALIGNMENT - alignment as u8
|
||||
};
|
||||
if let Some(aligned_stack_size) =
|
||||
requested_stack_size.checked_add(8 * saved_regs.len() as i32 + offset as i32)
|
||||
{
|
||||
if aligned_stack_size > 0 {
|
||||
AArch64Assembler::sub_reg64_reg64_imm32(
|
||||
buf,
|
||||
AArch64GPReg::ZRSP,
|
||||
AArch64GPReg::ZRSP,
|
||||
aligned_stack_size,
|
||||
);
|
||||
|
||||
// All the following stores could be optimized by using `STP` to store pairs.
|
||||
let mut offset = aligned_stack_size;
|
||||
if !leaf_function {
|
||||
offset -= 8;
|
||||
AArch64Assembler::mov_stack32_reg64(buf, offset, AArch64GPReg::LR);
|
||||
offset -= 8;
|
||||
AArch64Assembler::mov_stack32_reg64(buf, offset, AArch64GPReg::FP);
|
||||
}
|
||||
for reg in saved_regs {
|
||||
offset -= 8;
|
||||
AArch64Assembler::mov_stack32_reg64(buf, offset, *reg);
|
||||
}
|
||||
Ok(aligned_stack_size)
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
} else {
|
||||
Err("Ran out of stack space".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn cleanup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
leaf_function: bool,
|
||||
saved_regs: &[AArch64GPReg],
|
||||
aligned_stack_size: i32,
|
||||
) -> Result<(), String> {
|
||||
if aligned_stack_size > 0 {
|
||||
// All the following stores could be optimized by using `STP` to store pairs.
|
||||
let mut offset = aligned_stack_size;
|
||||
if !leaf_function {
|
||||
offset -= 8;
|
||||
AArch64Assembler::mov_reg64_stack32(buf, AArch64GPReg::LR, offset);
|
||||
offset -= 8;
|
||||
AArch64Assembler::mov_reg64_stack32(buf, AArch64GPReg::FP, offset);
|
||||
}
|
||||
for reg in saved_regs {
|
||||
offset -= 8;
|
||||
AArch64Assembler::mov_reg64_stack32(buf, *reg, offset);
|
||||
}
|
||||
AArch64Assembler::add_reg64_reg64_imm32(
|
||||
buf,
|
||||
AArch64GPReg::ZRSP,
|
||||
AArch64GPReg::ZRSP,
|
||||
aligned_stack_size,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Assembler<AArch64GPReg> for AArch64Assembler {
|
||||
#[inline(always)]
|
||||
fn abs_reg64_reg64<'a>(_buf: &mut Vec<'a, u8>, _dst: AArch64GPReg, _src: AArch64GPReg) {
|
||||
unimplemented!("abs_reg64_reg64 is not yet implement for AArch64");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn add_reg64_reg64_imm32<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: AArch64GPReg,
|
||||
src: AArch64GPReg,
|
||||
imm32: i32,
|
||||
) {
|
||||
if imm32 < 0 {
|
||||
unimplemented!("immediate addition with values less than 0 are not yet implemented");
|
||||
} else if imm32 < 0xFFF {
|
||||
add_reg64_reg64_imm12(buf, dst, src, imm32 as u16);
|
||||
} else {
|
||||
unimplemented!(
|
||||
"immediate additions with values greater than 12bits are not yet implemented"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn add_reg64_reg64_reg64<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: AArch64GPReg,
|
||||
src1: AArch64GPReg,
|
||||
src2: AArch64GPReg,
|
||||
) {
|
||||
add_reg64_reg64_reg64(buf, dst, src1, src2);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mov_reg64_imm64<'a>(buf: &mut Vec<'a, u8>, dst: AArch64GPReg, imm: i64) {
|
||||
let mut remaining = imm as u64;
|
||||
movz_reg64_imm16(buf, dst, remaining as u16, 0);
|
||||
remaining >>= 16;
|
||||
if remaining > 0 {
|
||||
movk_reg64_imm16(buf, dst, remaining as u16, 1);
|
||||
}
|
||||
remaining >>= 16;
|
||||
if remaining > 0 {
|
||||
movk_reg64_imm16(buf, dst, remaining as u16, 2);
|
||||
}
|
||||
remaining >>= 16;
|
||||
if remaining > 0 {
|
||||
movk_reg64_imm16(buf, dst, remaining as u16, 3);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mov_reg64_reg64<'a>(buf: &mut Vec<'a, u8>, dst: AArch64GPReg, src: AArch64GPReg) {
|
||||
mov_reg64_reg64(buf, dst, src);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mov_reg64_stack32<'a>(buf: &mut Vec<'a, u8>, dst: AArch64GPReg, offset: i32) {
|
||||
if offset < 0 {
|
||||
unimplemented!("negative stack offsets are not yet implement for AArch64");
|
||||
} else if offset < (0xFFF << 8) {
|
||||
debug_assert!(offset % 8 == 0);
|
||||
ldr_reg64_imm12(buf, dst, AArch64GPReg::ZRSP, (offset as u16) >> 3);
|
||||
} else {
|
||||
unimplemented!("stack offsets over 32k are not yet implement for AArch64");
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mov_stack32_reg64<'a>(buf: &mut Vec<'a, u8>, offset: i32, src: AArch64GPReg) {
|
||||
if offset < 0 {
|
||||
unimplemented!("negative stack offsets are not yet implement for AArch64");
|
||||
} else if offset < (0xFFF << 8) {
|
||||
debug_assert!(offset % 8 == 0);
|
||||
str_reg64_imm12(buf, src, AArch64GPReg::ZRSP, (offset as u16) >> 3);
|
||||
} else {
|
||||
unimplemented!("stack offsets over 32k are not yet implement for AArch64");
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn sub_reg64_reg64_imm32<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: AArch64GPReg,
|
||||
src: AArch64GPReg,
|
||||
imm32: i32,
|
||||
) {
|
||||
if imm32 < 0 {
|
||||
unimplemented!(
|
||||
"immediate subtractions with values less than 0 are not yet implemented"
|
||||
);
|
||||
} else if imm32 < 0xFFF {
|
||||
sub_reg64_reg64_imm12(buf, dst, src, imm32 as u16);
|
||||
} else {
|
||||
unimplemented!(
|
||||
"immediate subtractions with values greater than 12bits are not yet implemented"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn ret<'a>(buf: &mut Vec<'a, u8>) {
|
||||
ret_reg64(buf, AArch64GPReg::LR)
|
||||
}
|
||||
}
|
||||
|
||||
impl AArch64Assembler {}
|
||||
|
||||
/// AArch64Instruction, maps all instructions to an enum.
|
||||
/// Decoding the function should be cheap because we will always inline.
|
||||
/// All of the operations should resolved by constants, leave just some bit manipulation.
|
||||
/// Enums may not be complete since we will only add what we need.
|
||||
#[derive(Debug)]
|
||||
enum AArch64Instruction {
|
||||
_Reserved,
|
||||
_SVE,
|
||||
DPImm(DPImmGroup),
|
||||
Branch(BranchGroup),
|
||||
LdStr(LdStrGroup),
|
||||
DPReg(DPRegGroup),
|
||||
_DPFloat,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum BranchGroup {
|
||||
UnconditionBranchReg {
|
||||
opc: u8,
|
||||
op2: u8,
|
||||
op3: u8,
|
||||
reg_n: AArch64GPReg,
|
||||
op4: u8,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum DPRegGroup {
|
||||
AddSubShifted {
|
||||
sf: bool,
|
||||
subtract: bool,
|
||||
set_flags: bool,
|
||||
shift: u8,
|
||||
reg_m: AArch64GPReg,
|
||||
imm6: u8,
|
||||
reg_n: AArch64GPReg,
|
||||
reg_d: AArch64GPReg,
|
||||
},
|
||||
Logical {
|
||||
sf: bool,
|
||||
op: DPRegLogicalOp,
|
||||
shift: u8,
|
||||
reg_m: AArch64GPReg,
|
||||
imm6: u8,
|
||||
reg_n: AArch64GPReg,
|
||||
reg_d: AArch64GPReg,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum DPImmGroup {
|
||||
AddSubImm {
|
||||
sf: bool,
|
||||
subtract: bool,
|
||||
set_flags: bool,
|
||||
shift: bool,
|
||||
imm12: u16,
|
||||
reg_n: AArch64GPReg,
|
||||
reg_d: AArch64GPReg,
|
||||
},
|
||||
MoveWide {
|
||||
sf: bool,
|
||||
opc: u8,
|
||||
hw: u8,
|
||||
imm16: u16,
|
||||
reg_d: AArch64GPReg,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum LdStrGroup {
|
||||
UnsignedImm {
|
||||
size: u8,
|
||||
v: bool,
|
||||
opc: u8,
|
||||
imm12: u16,
|
||||
reg_n: AArch64GPReg,
|
||||
reg_t: AArch64GPReg,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
enum DPRegLogicalOp {
|
||||
AND,
|
||||
BIC,
|
||||
ORR,
|
||||
ORN,
|
||||
EOR,
|
||||
EON,
|
||||
ANDS,
|
||||
BICS,
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn build_instruction(inst: AArch64Instruction) -> [u8; 4] {
|
||||
let mut out: u32 = 0;
|
||||
match inst {
|
||||
AArch64Instruction::Branch(branch) => {
|
||||
out |= 0b101 << 26;
|
||||
match branch {
|
||||
BranchGroup::UnconditionBranchReg {
|
||||
opc,
|
||||
op2,
|
||||
op3,
|
||||
reg_n,
|
||||
op4,
|
||||
} => {
|
||||
debug_assert!(opc <= 0b1111);
|
||||
debug_assert!(op2 <= 0b11111);
|
||||
debug_assert!(op3 <= 0b111111);
|
||||
debug_assert!(op4 <= 0b1111);
|
||||
out |= 0b1101011 << 25;
|
||||
out |= (opc as u32) << 21;
|
||||
out |= (op2 as u32) << 16;
|
||||
out |= (op3 as u32) << 10;
|
||||
out |= (reg_n as u32) << 5;
|
||||
out |= op4 as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
AArch64Instruction::DPImm(dpimm) => {
|
||||
out |= 0b100 << 26;
|
||||
match dpimm {
|
||||
DPImmGroup::MoveWide {
|
||||
sf,
|
||||
opc,
|
||||
hw,
|
||||
imm16,
|
||||
reg_d,
|
||||
} => {
|
||||
out |= (sf as u32) << 31;
|
||||
out |= (opc as u32) << 29;
|
||||
out |= 0b101 << 23;
|
||||
out |= (hw as u32) << 21;
|
||||
out |= (imm16 as u32) << 5;
|
||||
out |= reg_d as u32;
|
||||
}
|
||||
DPImmGroup::AddSubImm {
|
||||
sf,
|
||||
subtract,
|
||||
set_flags,
|
||||
shift,
|
||||
imm12,
|
||||
reg_n,
|
||||
reg_d,
|
||||
} => {
|
||||
debug_assert!(imm12 <= 0xFFF);
|
||||
out |= (sf as u32) << 31;
|
||||
out |= (subtract as u32) << 30;
|
||||
out |= (set_flags as u32) << 29;
|
||||
out |= 0b010 << 23;
|
||||
out |= (shift as u32) << 22;
|
||||
out |= (imm12 as u32) << 10;
|
||||
out |= (reg_n as u32) << 5;
|
||||
out |= reg_d as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
AArch64Instruction::DPReg(dpreg) => {
|
||||
out |= 0b101 << 25;
|
||||
match dpreg {
|
||||
DPRegGroup::Logical {
|
||||
sf,
|
||||
op,
|
||||
shift,
|
||||
reg_m,
|
||||
imm6,
|
||||
reg_n,
|
||||
reg_d,
|
||||
} => {
|
||||
debug_assert!(shift <= 0b11);
|
||||
debug_assert!(imm6 <= 0b111111);
|
||||
let (opc, n) = match op {
|
||||
DPRegLogicalOp::AND => (0b00, 0),
|
||||
DPRegLogicalOp::BIC => (0b00, 1),
|
||||
DPRegLogicalOp::ORR => (0b01, 0),
|
||||
DPRegLogicalOp::ORN => (0b01, 1),
|
||||
DPRegLogicalOp::EOR => (0b10, 0),
|
||||
DPRegLogicalOp::EON => (0b10, 1),
|
||||
DPRegLogicalOp::ANDS => (0b11, 0),
|
||||
DPRegLogicalOp::BICS => (0b11, 1),
|
||||
};
|
||||
out |= (sf as u32) << 31;
|
||||
out |= opc << 29;
|
||||
out |= (shift as u32) << 22;
|
||||
out |= n << 21;
|
||||
out |= (reg_m as u32) << 16;
|
||||
out |= (imm6 as u32) << 10;
|
||||
out |= (reg_n as u32) << 5;
|
||||
out |= reg_d as u32;
|
||||
}
|
||||
DPRegGroup::AddSubShifted {
|
||||
sf,
|
||||
subtract,
|
||||
set_flags,
|
||||
shift,
|
||||
reg_m,
|
||||
imm6,
|
||||
reg_n,
|
||||
reg_d,
|
||||
} => {
|
||||
debug_assert!(shift <= 0b11);
|
||||
debug_assert!(imm6 <= 0b111111);
|
||||
out |= (sf as u32) << 31;
|
||||
out |= (subtract as u32) << 30;
|
||||
out |= (set_flags as u32) << 29;
|
||||
out |= 0b1 << 24;
|
||||
out |= (shift as u32) << 22;
|
||||
out |= (reg_m as u32) << 16;
|
||||
out |= (imm6 as u32) << 10;
|
||||
out |= (reg_n as u32) << 5;
|
||||
out |= reg_d as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
AArch64Instruction::LdStr(ldstr) => {
|
||||
out |= 0b1 << 27;
|
||||
match ldstr {
|
||||
LdStrGroup::UnsignedImm {
|
||||
size,
|
||||
v,
|
||||
opc,
|
||||
imm12,
|
||||
reg_n,
|
||||
reg_t,
|
||||
} => {
|
||||
debug_assert!(size <= 0b11);
|
||||
debug_assert!(imm12 <= 0xFFF);
|
||||
out |= (size as u32) << 30;
|
||||
out |= 0b11 << 28;
|
||||
out |= (v as u32) << 26;
|
||||
out |= 0b1 << 24;
|
||||
out |= (opc as u32) << 22;
|
||||
out |= (imm12 as u32) << 10;
|
||||
out |= (reg_n as u32) << 5;
|
||||
out |= reg_t as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
x => unimplemented!("The instruction, {:?}, has not be implemented yet", x),
|
||||
}
|
||||
out.to_le_bytes()
|
||||
}
|
||||
|
||||
// Below here are the functions for all of the assembly instructions.
|
||||
// Their names are based on the instruction and operators combined.
|
||||
// You should call `buf.reserve()` if you push or extend more than once.
|
||||
// Unit tests are added at the bottom of the file to ensure correct asm generation.
|
||||
// Please keep these in alphanumeric order.
|
||||
|
||||
/// `ADD Xd, Xn, imm12` -> Add Xn and imm12 and place the result into Xd.
|
||||
#[inline(always)]
|
||||
fn add_reg64_reg64_imm12<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: AArch64GPReg,
|
||||
src: AArch64GPReg,
|
||||
imm12: u16,
|
||||
) {
|
||||
buf.extend(&build_instruction(AArch64Instruction::DPImm(
|
||||
DPImmGroup::AddSubImm {
|
||||
sf: true,
|
||||
subtract: false,
|
||||
set_flags: false,
|
||||
shift: false,
|
||||
imm12,
|
||||
reg_n: src,
|
||||
reg_d: dst,
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
/// `ADD Xd, Xm, Xn` -> Add Xm and Xn and place the result into Xd.
|
||||
#[inline(always)]
|
||||
fn add_reg64_reg64_reg64<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: AArch64GPReg,
|
||||
src1: AArch64GPReg,
|
||||
src2: AArch64GPReg,
|
||||
) {
|
||||
buf.extend(&build_instruction(AArch64Instruction::DPReg(
|
||||
DPRegGroup::AddSubShifted {
|
||||
sf: true,
|
||||
subtract: false,
|
||||
set_flags: false,
|
||||
shift: 0,
|
||||
reg_m: src1,
|
||||
imm6: 0,
|
||||
reg_n: src2,
|
||||
reg_d: dst,
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
/// `LDR Xt, [Xn, #offset]` -> Load Xn + Offset Xt. ZRSP is SP.
|
||||
/// Note: imm12 is the offest divided by 8.
|
||||
#[inline(always)]
|
||||
fn ldr_reg64_imm12<'a>(buf: &mut Vec<'a, u8>, dst: AArch64GPReg, base: AArch64GPReg, imm12: u16) {
|
||||
debug_assert!(imm12 <= 0xFFF);
|
||||
buf.extend(&build_instruction(AArch64Instruction::LdStr(
|
||||
LdStrGroup::UnsignedImm {
|
||||
size: 0b11,
|
||||
v: false,
|
||||
opc: 0b01,
|
||||
imm12,
|
||||
reg_n: base,
|
||||
reg_t: dst,
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
/// `MOV Xd, Xm` -> Move Xm to Xd.
|
||||
#[inline(always)]
|
||||
fn mov_reg64_reg64<'a>(buf: &mut Vec<'a, u8>, dst: AArch64GPReg, src: AArch64GPReg) {
|
||||
// MOV is equvalent to `ORR Xd, XZR, XM` in AARCH64.
|
||||
buf.extend(&build_instruction(AArch64Instruction::DPReg(
|
||||
DPRegGroup::Logical {
|
||||
sf: true,
|
||||
op: DPRegLogicalOp::ORR,
|
||||
shift: 0,
|
||||
reg_m: src,
|
||||
imm6: 0,
|
||||
reg_n: AArch64GPReg::ZRSP,
|
||||
reg_d: dst,
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
/// `MOVK Xd, imm16` -> Keeps Xd and moves an optionally shifted imm16 to Xd.
|
||||
#[inline(always)]
|
||||
fn movk_reg64_imm16<'a>(buf: &mut Vec<'a, u8>, dst: AArch64GPReg, imm16: u16, hw: u8) {
|
||||
debug_assert!(hw <= 0b11);
|
||||
// MOV is equvalent to `ORR Xd, XZR, XM` in AARCH64.
|
||||
buf.extend(&build_instruction(AArch64Instruction::DPImm(
|
||||
DPImmGroup::MoveWide {
|
||||
sf: true,
|
||||
opc: 0b11,
|
||||
hw,
|
||||
imm16,
|
||||
reg_d: dst,
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
/// `MOVZ Xd, imm16` -> Zeros Xd and moves an optionally shifted imm16 to Xd.
|
||||
#[inline(always)]
|
||||
fn movz_reg64_imm16<'a>(buf: &mut Vec<'a, u8>, dst: AArch64GPReg, imm16: u16, hw: u8) {
|
||||
debug_assert!(hw <= 0b11);
|
||||
// MOV is equvalent to `ORR Xd, XZR, XM` in AARCH64.
|
||||
buf.extend(&build_instruction(AArch64Instruction::DPImm(
|
||||
DPImmGroup::MoveWide {
|
||||
sf: true,
|
||||
opc: 0b10,
|
||||
hw,
|
||||
imm16,
|
||||
reg_d: dst,
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
/// `STR Xt, [Xn, #offset]` -> Store Xt to Xn + Offset. ZRSP is SP.
|
||||
/// Note: imm12 is the offest divided by 8.
|
||||
#[inline(always)]
|
||||
fn str_reg64_imm12<'a>(buf: &mut Vec<'a, u8>, src: AArch64GPReg, base: AArch64GPReg, imm12: u16) {
|
||||
debug_assert!(imm12 <= 0xFFF);
|
||||
buf.extend(&build_instruction(AArch64Instruction::LdStr(
|
||||
LdStrGroup::UnsignedImm {
|
||||
size: 0b11,
|
||||
v: false,
|
||||
opc: 0b00,
|
||||
imm12,
|
||||
reg_n: base,
|
||||
reg_t: src,
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
/// `SUB Xd, Xn, imm12` -> Subtract Xn and imm12 and place the result into Xd.
|
||||
#[inline(always)]
|
||||
fn sub_reg64_reg64_imm12<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: AArch64GPReg,
|
||||
src: AArch64GPReg,
|
||||
imm12: u16,
|
||||
) {
|
||||
buf.extend(&build_instruction(AArch64Instruction::DPImm(
|
||||
DPImmGroup::AddSubImm {
|
||||
sf: true,
|
||||
subtract: true,
|
||||
set_flags: false,
|
||||
shift: false,
|
||||
imm12,
|
||||
reg_n: src,
|
||||
reg_d: dst,
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
/// `RET Xn` -> Return to the address stored in Xn.
|
||||
#[inline(always)]
|
||||
fn ret_reg64<'a>(buf: &mut Vec<'a, u8>, xn: AArch64GPReg) {
|
||||
buf.extend(&build_instruction(AArch64Instruction::Branch(
|
||||
BranchGroup::UnconditionBranchReg {
|
||||
opc: 0b0010,
|
||||
op2: 0b11111,
|
||||
op3: 0b000000,
|
||||
reg_n: xn,
|
||||
op4: 0b000,
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const TEST_U16: u16 = 0x1234;
|
||||
//const TEST_I32: i32 = 0x12345678;
|
||||
//const TEST_I64: i64 = 0x12345678_9ABCDEF0;
|
||||
|
||||
#[test]
|
||||
fn test_add_reg64_reg64_reg64() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
add_reg64_reg64_reg64(
|
||||
&mut buf,
|
||||
AArch64GPReg::X10,
|
||||
AArch64GPReg::ZRSP,
|
||||
AArch64GPReg::X21,
|
||||
);
|
||||
assert_eq!(&buf, &[0xAA, 0x02, 0x1F, 0x8B]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_reg64_reg64_imm12() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
add_reg64_reg64_imm12(&mut buf, AArch64GPReg::X10, AArch64GPReg::X21, 0x123);
|
||||
assert_eq!(&buf, &[0xAA, 0x8E, 0x04, 0x91]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ldr_reg64_imm12() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
ldr_reg64_imm12(&mut buf, AArch64GPReg::X21, AArch64GPReg::ZRSP, 0x123);
|
||||
assert_eq!(&buf, &[0xF5, 0x8F, 0x44, 0xF9]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mov_reg64_reg64() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
mov_reg64_reg64(&mut buf, AArch64GPReg::X10, AArch64GPReg::X21);
|
||||
assert_eq!(&buf, &[0xEA, 0x03, 0x15, 0xAA]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movk_reg64_imm16() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
movk_reg64_imm16(&mut buf, AArch64GPReg::X21, TEST_U16, 3);
|
||||
assert_eq!(&buf, &[0x95, 0x46, 0xE2, 0xF2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movz_reg64_imm16() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
movz_reg64_imm16(&mut buf, AArch64GPReg::X21, TEST_U16, 3);
|
||||
assert_eq!(&buf, &[0x95, 0x46, 0xE2, 0xD2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_str_reg64_imm12() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
str_reg64_imm12(&mut buf, AArch64GPReg::X21, AArch64GPReg::ZRSP, 0x123);
|
||||
assert_eq!(&buf, &[0xF5, 0x8F, 0x04, 0xF9]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub_reg64_reg64_imm12() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
sub_reg64_reg64_imm12(&mut buf, AArch64GPReg::X10, AArch64GPReg::X21, 0x123);
|
||||
assert_eq!(&buf, &[0xAA, 0x8E, 0x04, 0xD1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ret_reg64() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
ret_reg64(&mut buf, AArch64GPReg::LR);
|
||||
assert_eq!(&buf, &[0xC0, 0x03, 0x5F, 0xD6]);
|
||||
}
|
||||
}
|
@ -1,49 +1,61 @@
|
||||
use crate::{Backend, Env, Relocation};
|
||||
use bumpalo::collections::Vec;
|
||||
use roc_collections::all::{ImSet, MutMap, MutSet};
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::ir::{Literal, Stmt};
|
||||
use std::marker::PhantomData;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
pub mod aarch64;
|
||||
pub mod x86_64;
|
||||
|
||||
pub trait CallConv<GPReg> {
|
||||
fn gp_param_regs() -> &'static [GPReg];
|
||||
fn gp_return_regs() -> &'static [GPReg];
|
||||
fn gp_default_free_regs() -> &'static [GPReg];
|
||||
pub trait CallConv<GPReg: GPRegTrait> {
|
||||
const GP_PARAM_REGS: &'static [GPReg];
|
||||
const GP_RETURN_REGS: &'static [GPReg];
|
||||
const GP_DEFAULT_FREE_REGS: &'static [GPReg];
|
||||
|
||||
// A linear scan of an array may be faster than a set technically.
|
||||
// That being said, fastest would likely be a trait based on calling convention/register.
|
||||
fn caller_saved_regs() -> ImSet<GPReg>;
|
||||
fn callee_saved_regs() -> ImSet<GPReg>;
|
||||
const SHADOW_SPACE_SIZE: u8;
|
||||
|
||||
fn stack_pointer() -> GPReg;
|
||||
fn frame_pointer() -> GPReg;
|
||||
fn callee_saved(reg: &GPReg) -> bool;
|
||||
#[inline(always)]
|
||||
fn caller_saved_regs(reg: &GPReg) -> bool {
|
||||
!Self::callee_saved(reg)
|
||||
}
|
||||
|
||||
fn shadow_space_size() -> u8;
|
||||
// It may be worth ignoring the red zone and keeping things simpler.
|
||||
fn red_zone_size() -> u8;
|
||||
fn setup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
leaf_function: bool,
|
||||
saved_regs: &[GPReg],
|
||||
requested_stack_size: i32,
|
||||
) -> Result<i32, String>;
|
||||
fn cleanup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
leaf_function: bool,
|
||||
saved_regs: &[GPReg],
|
||||
aligned_stack_size: i32,
|
||||
) -> Result<(), String>;
|
||||
}
|
||||
|
||||
pub trait Assembler<GPReg> {
|
||||
fn add_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, imm: i32);
|
||||
fn add_register64bit_register64bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, src: GPReg);
|
||||
fn cmovl_register64bit_register64bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, src: GPReg);
|
||||
fn mov_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, imm: i32);
|
||||
fn mov_register64bit_immediate64bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, imm: i64);
|
||||
fn mov_register64bit_register64bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, src: GPReg);
|
||||
fn mov_register64bit_stackoffset32bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, offset: i32);
|
||||
fn mov_stackoffset32bit_register64bit<'a>(buf: &mut Vec<'a, u8>, offset: i32, src: GPReg);
|
||||
fn neg_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: GPReg);
|
||||
/// Assembler contains calls to the backend assembly generator.
|
||||
/// These calls do not necessarily map directly to a single assembly instruction.
|
||||
/// They are higher level in cases where an instruction would not be common and shared between multiple architectures.
|
||||
/// 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<GPReg: GPRegTrait> {
|
||||
fn abs_reg64_reg64<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, src: GPReg);
|
||||
fn add_reg64_reg64_imm32<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, src1: GPReg, imm32: i32);
|
||||
fn add_reg64_reg64_reg64<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, src1: GPReg, src2: GPReg);
|
||||
fn mov_reg64_imm64<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, imm: i64);
|
||||
fn mov_reg64_reg64<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, src: GPReg);
|
||||
fn mov_reg64_stack32<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, offset: i32);
|
||||
fn mov_stack32_reg64<'a>(buf: &mut Vec<'a, u8>, offset: i32, src: GPReg);
|
||||
fn sub_reg64_reg64_imm32<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, src1: GPReg, imm32: i32);
|
||||
fn ret<'a>(buf: &mut Vec<'a, u8>);
|
||||
fn sub_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: GPReg, imm: i32);
|
||||
fn pop_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: GPReg);
|
||||
fn push_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: GPReg);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum SymbolStorage<GPReg> {
|
||||
enum SymbolStorage<GPReg: GPRegTrait> {
|
||||
// These may need layout, but I am not sure.
|
||||
// I think whenever a symbol would be used, we specify layout anyways.
|
||||
GPRegeg(GPReg),
|
||||
@ -69,7 +81,7 @@ pub struct Backend64Bit<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallCo
|
||||
literal_map: MutMap<Symbol, Literal<'a>>,
|
||||
|
||||
// This should probably be smarter than a vec.
|
||||
// There are certain registers we should always use first. With pushing and poping, this could get mixed.
|
||||
// There are certain registers we should always use first. With pushing and popping, this could get mixed.
|
||||
gp_free_regs: Vec<'a, GPReg>,
|
||||
|
||||
// The last major thing we need is a way to decide what reg to free when all of them are full.
|
||||
@ -109,7 +121,7 @@ impl<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>> Backend<
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.stack_size = -(CC::red_zone_size() as i32);
|
||||
self.stack_size = 0;
|
||||
self.leaf_function = true;
|
||||
self.last_seen_map.clear();
|
||||
self.free_map.clear();
|
||||
@ -119,13 +131,12 @@ impl<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>> Backend<
|
||||
self.gp_free_regs.clear();
|
||||
self.gp_used_regs.clear();
|
||||
self.gp_free_regs
|
||||
.extend_from_slice(CC::gp_default_free_regs());
|
||||
.extend_from_slice(CC::GP_DEFAULT_FREE_REGS);
|
||||
}
|
||||
|
||||
fn set_not_leaf_function(&mut self) {
|
||||
self.leaf_function = false;
|
||||
// If this is not a leaf function, it can't use the shadow space.
|
||||
self.stack_size = CC::shadow_space_size() as i32 - CC::red_zone_size() as i32;
|
||||
self.stack_size = CC::SHADOW_SPACE_SIZE as i32;
|
||||
}
|
||||
|
||||
fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>> {
|
||||
@ -147,38 +158,17 @@ impl<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>> Backend<
|
||||
fn finalize(&mut self) -> Result<(&'a [u8], &[Relocation]), String> {
|
||||
let mut out = bumpalo::vec![in self.env.arena];
|
||||
|
||||
if !self.leaf_function {
|
||||
// I believe that this will have to move away from push and to mov to be generic across backends.
|
||||
ASM::push_register64bit(&mut out, CC::frame_pointer());
|
||||
ASM::mov_register64bit_register64bit(
|
||||
&mut out,
|
||||
CC::frame_pointer(),
|
||||
CC::stack_pointer(),
|
||||
);
|
||||
}
|
||||
// Save data in all callee saved regs.
|
||||
let mut pop_order = bumpalo::vec![in self.env.arena];
|
||||
for reg in &self.used_callee_saved_regs {
|
||||
ASM::push_register64bit(&mut out, *reg);
|
||||
pop_order.push(*reg);
|
||||
}
|
||||
if self.stack_size > 0 {
|
||||
ASM::sub_register64bit_immediate32bit(&mut out, CC::stack_pointer(), self.stack_size);
|
||||
}
|
||||
// Setup stack.
|
||||
let mut used_regs = bumpalo::vec![in self.env.arena];
|
||||
used_regs.extend(&self.used_callee_saved_regs);
|
||||
let aligned_stack_size =
|
||||
CC::setup_stack(&mut out, self.leaf_function, &used_regs, self.stack_size)?;
|
||||
|
||||
// Add function body.
|
||||
out.extend(&self.buf);
|
||||
|
||||
if self.stack_size > 0 {
|
||||
ASM::add_register64bit_immediate32bit(&mut out, CC::stack_pointer(), self.stack_size);
|
||||
}
|
||||
// Restore data in callee saved regs.
|
||||
while let Some(reg) = pop_order.pop() {
|
||||
ASM::pop_register64bit(&mut out, reg);
|
||||
}
|
||||
if !self.leaf_function {
|
||||
ASM::pop_register64bit(&mut out, CC::frame_pointer());
|
||||
}
|
||||
// Cleanup stack.
|
||||
CC::cleanup_stack(&mut out, self.leaf_function, &used_regs, aligned_stack_size)?;
|
||||
ASM::ret(&mut out);
|
||||
|
||||
Ok((out.into_bump_slice(), &[]))
|
||||
@ -187,9 +177,7 @@ impl<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>> Backend<
|
||||
fn build_num_abs_i64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String> {
|
||||
let dst_reg = self.claim_gp_reg(dst)?;
|
||||
let src_reg = self.load_to_reg(src)?;
|
||||
ASM::mov_register64bit_register64bit(&mut self.buf, dst_reg, src_reg);
|
||||
ASM::neg_register64bit(&mut self.buf, dst_reg);
|
||||
ASM::cmovl_register64bit_register64bit(&mut self.buf, dst_reg, src_reg);
|
||||
ASM::abs_reg64_reg64(&mut self.buf, dst_reg, src_reg);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -201,9 +189,8 @@ impl<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>> Backend<
|
||||
) -> Result<(), String> {
|
||||
let dst_reg = self.claim_gp_reg(dst)?;
|
||||
let src1_reg = self.load_to_reg(src1)?;
|
||||
ASM::mov_register64bit_register64bit(&mut self.buf, dst_reg, src1_reg);
|
||||
let src2_reg = self.load_to_reg(src2)?;
|
||||
ASM::add_register64bit_register64bit(&mut self.buf, dst_reg, src2_reg);
|
||||
ASM::add_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -212,7 +199,7 @@ impl<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>> Backend<
|
||||
Literal::Int(x) => {
|
||||
let reg = self.claim_gp_reg(sym)?;
|
||||
let val = *x;
|
||||
ASM::mov_register64bit_immediate64bit(&mut self.buf, reg, val);
|
||||
ASM::mov_reg64_imm64(&mut self.buf, reg, val);
|
||||
Ok(())
|
||||
}
|
||||
x => Err(format!("loading literal, {:?}, is not yet implemented", x)),
|
||||
@ -234,11 +221,11 @@ impl<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>> Backend<
|
||||
fn return_symbol(&mut self, sym: &Symbol) -> Result<(), String> {
|
||||
let val = self.symbols_map.get(sym);
|
||||
match val {
|
||||
Some(SymbolStorage::GPRegeg(reg)) if *reg == CC::gp_return_regs()[0] => Ok(()),
|
||||
Some(SymbolStorage::GPRegeg(reg)) if *reg == CC::GP_RETURN_REGS[0] => Ok(()),
|
||||
Some(SymbolStorage::GPRegeg(reg)) => {
|
||||
// If it fits in a general purpose register, just copy it over to.
|
||||
// Technically this can be optimized to produce shorter instructions if less than 64bits.
|
||||
ASM::mov_register64bit_register64bit(&mut self.buf, CC::gp_return_regs()[0], *reg);
|
||||
ASM::mov_reg64_reg64(&mut self.buf, CC::GP_RETURN_REGS[0], *reg);
|
||||
Ok(())
|
||||
}
|
||||
Some(x) => Err(format!(
|
||||
@ -258,7 +245,7 @@ impl<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>>
|
||||
fn claim_gp_reg(&mut self, sym: &Symbol) -> Result<GPReg, String> {
|
||||
let reg = if !self.gp_free_regs.is_empty() {
|
||||
let free_reg = self.gp_free_regs.pop().unwrap();
|
||||
if CC::callee_saved_regs().contains(&free_reg) {
|
||||
if CC::callee_saved(&free_reg) {
|
||||
self.used_callee_saved_regs.insert(free_reg);
|
||||
}
|
||||
Ok(free_reg)
|
||||
@ -291,7 +278,7 @@ impl<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>>
|
||||
let reg = self.claim_gp_reg(sym)?;
|
||||
self.symbols_map
|
||||
.insert(*sym, SymbolStorage::StackAndGPRegeg(reg, offset));
|
||||
ASM::mov_register64bit_stackoffset32bit(&mut self.buf, reg, offset as i32);
|
||||
ASM::mov_reg64_stack32(&mut self.buf, reg, offset as i32);
|
||||
Ok(reg)
|
||||
}
|
||||
None => Err(format!("Unknown symbol: {}", sym)),
|
||||
@ -302,19 +289,9 @@ impl<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>>
|
||||
let val = self.symbols_map.remove(sym);
|
||||
match val {
|
||||
Some(SymbolStorage::GPRegeg(reg)) => {
|
||||
let offset = self.stack_size;
|
||||
self.stack_size += 8;
|
||||
if let Some(size) = self.stack_size.checked_add(8) {
|
||||
self.stack_size = size;
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Ran out of stack space while saving symbol: {}",
|
||||
sym
|
||||
));
|
||||
}
|
||||
ASM::mov_stackoffset32bit_register64bit(&mut self.buf, offset as i32, reg);
|
||||
self.symbols_map
|
||||
.insert(*sym, SymbolStorage::Stack(offset as i32));
|
||||
let offset = self.increase_stack_size(8)?;
|
||||
ASM::mov_stack32_reg64(&mut self.buf, offset as i32, reg);
|
||||
self.symbols_map.insert(*sym, SymbolStorage::Stack(offset));
|
||||
Ok(())
|
||||
}
|
||||
Some(SymbolStorage::StackAndGPRegeg(_, offset)) => {
|
||||
@ -328,4 +305,16 @@ impl<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>>
|
||||
None => Err(format!("Unknown symbol: {}", sym)),
|
||||
}
|
||||
}
|
||||
|
||||
/// increase_stack_size increase the current stack size and returns the offset of the stack.
|
||||
fn increase_stack_size(&mut self, amount: i32) -> Result<i32, String> {
|
||||
debug_assert!(amount > 0);
|
||||
let offset = self.stack_size;
|
||||
if let Some(new_size) = self.stack_size.checked_add(amount) {
|
||||
self.stack_size = new_size;
|
||||
Ok(offset)
|
||||
} else {
|
||||
Err("Ran out of stack space".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use crate::generic64::{Assembler, CallConv, GPRegTrait};
|
||||
use bumpalo::collections::Vec;
|
||||
use roc_collections::all::ImSet;
|
||||
|
||||
// Not sure exactly how I want to represent registers.
|
||||
// If we want max speed, we would likely make them structs that impl the same trait to avoid ifs.
|
||||
@ -26,10 +25,312 @@ pub enum X86_64GPReg {
|
||||
|
||||
impl GPRegTrait for X86_64GPReg {}
|
||||
|
||||
pub struct X86_64Assembler {}
|
||||
pub struct X86_64WindowsFastcall {}
|
||||
pub struct X86_64SystemV {}
|
||||
|
||||
const STACK_ALIGNMENT: u8 = 16;
|
||||
|
||||
impl CallConv<X86_64GPReg> for X86_64SystemV {
|
||||
const GP_PARAM_REGS: &'static [X86_64GPReg] = &[
|
||||
X86_64GPReg::RDI,
|
||||
X86_64GPReg::RSI,
|
||||
X86_64GPReg::RDX,
|
||||
X86_64GPReg::RCX,
|
||||
X86_64GPReg::R8,
|
||||
X86_64GPReg::R9,
|
||||
];
|
||||
const GP_RETURN_REGS: &'static [X86_64GPReg] = &[X86_64GPReg::RAX, X86_64GPReg::RDX];
|
||||
|
||||
const GP_DEFAULT_FREE_REGS: &'static [X86_64GPReg] = &[
|
||||
// The regs we want to use first should be at the end of this vec.
|
||||
// We will use pop to get which reg to use next
|
||||
// Use callee saved regs last.
|
||||
X86_64GPReg::RBX,
|
||||
// Don't use frame pointer: X86_64GPReg::RBP,
|
||||
X86_64GPReg::R12,
|
||||
X86_64GPReg::R13,
|
||||
X86_64GPReg::R14,
|
||||
X86_64GPReg::R15,
|
||||
// Use caller saved regs first.
|
||||
X86_64GPReg::RAX,
|
||||
X86_64GPReg::RCX,
|
||||
X86_64GPReg::RDX,
|
||||
// Don't use stack pionter: X86_64GPReg::RSP,
|
||||
X86_64GPReg::RSI,
|
||||
X86_64GPReg::RDI,
|
||||
X86_64GPReg::R8,
|
||||
X86_64GPReg::R9,
|
||||
X86_64GPReg::R10,
|
||||
X86_64GPReg::R11,
|
||||
];
|
||||
const SHADOW_SPACE_SIZE: u8 = 0;
|
||||
|
||||
#[inline(always)]
|
||||
fn callee_saved(reg: &X86_64GPReg) -> bool {
|
||||
matches!(
|
||||
reg,
|
||||
X86_64GPReg::RBX
|
||||
| X86_64GPReg::RBP
|
||||
| X86_64GPReg::R12
|
||||
| X86_64GPReg::R13
|
||||
| X86_64GPReg::R14
|
||||
| X86_64GPReg::R15
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn setup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
leaf_function: bool,
|
||||
saved_regs: &[X86_64GPReg],
|
||||
requested_stack_size: i32,
|
||||
) -> Result<i32, String> {
|
||||
x86_64_generic_setup_stack(buf, leaf_function, saved_regs, requested_stack_size)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn cleanup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
leaf_function: bool,
|
||||
saved_regs: &[X86_64GPReg],
|
||||
aligned_stack_size: i32,
|
||||
) -> Result<(), String> {
|
||||
x86_64_generic_cleanup_stack(buf, leaf_function, saved_regs, aligned_stack_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl CallConv<X86_64GPReg> for X86_64WindowsFastcall {
|
||||
const GP_PARAM_REGS: &'static [X86_64GPReg] = &[
|
||||
X86_64GPReg::RCX,
|
||||
X86_64GPReg::RDX,
|
||||
X86_64GPReg::R8,
|
||||
X86_64GPReg::R9,
|
||||
];
|
||||
const GP_RETURN_REGS: &'static [X86_64GPReg] = &[X86_64GPReg::RAX];
|
||||
const GP_DEFAULT_FREE_REGS: &'static [X86_64GPReg] = &[
|
||||
// The regs we want to use first should be at the end of this vec.
|
||||
// We will use pop to get which reg to use next
|
||||
|
||||
// Don't use stack pionter: X86_64GPReg::RSP,
|
||||
// Don't use frame pointer: X86_64GPReg::RBP,
|
||||
|
||||
// Use callee saved regs last.
|
||||
X86_64GPReg::RBX,
|
||||
X86_64GPReg::RSI,
|
||||
X86_64GPReg::RDI,
|
||||
X86_64GPReg::R12,
|
||||
X86_64GPReg::R13,
|
||||
X86_64GPReg::R14,
|
||||
X86_64GPReg::R15,
|
||||
// Use caller saved regs first.
|
||||
X86_64GPReg::RAX,
|
||||
X86_64GPReg::RCX,
|
||||
X86_64GPReg::RDX,
|
||||
X86_64GPReg::R8,
|
||||
X86_64GPReg::R9,
|
||||
X86_64GPReg::R10,
|
||||
X86_64GPReg::R11,
|
||||
];
|
||||
const SHADOW_SPACE_SIZE: u8 = 32;
|
||||
|
||||
#[inline(always)]
|
||||
fn callee_saved(reg: &X86_64GPReg) -> bool {
|
||||
matches!(
|
||||
reg,
|
||||
X86_64GPReg::RBX
|
||||
| X86_64GPReg::RBP
|
||||
| X86_64GPReg::RSI
|
||||
| X86_64GPReg::RSP
|
||||
| X86_64GPReg::RDI
|
||||
| X86_64GPReg::R12
|
||||
| X86_64GPReg::R13
|
||||
| X86_64GPReg::R14
|
||||
| X86_64GPReg::R15
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn setup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
leaf_function: bool,
|
||||
saved_regs: &[X86_64GPReg],
|
||||
requested_stack_size: i32,
|
||||
) -> Result<i32, String> {
|
||||
x86_64_generic_setup_stack(buf, leaf_function, saved_regs, requested_stack_size)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn cleanup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
leaf_function: bool,
|
||||
saved_regs: &[X86_64GPReg],
|
||||
aligned_stack_size: i32,
|
||||
) -> Result<(), String> {
|
||||
x86_64_generic_cleanup_stack(buf, leaf_function, saved_regs, aligned_stack_size)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn x86_64_generic_setup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
leaf_function: bool,
|
||||
saved_regs: &[X86_64GPReg],
|
||||
requested_stack_size: i32,
|
||||
) -> Result<i32, String> {
|
||||
if !leaf_function {
|
||||
X86_64Assembler::push_reg64(buf, X86_64GPReg::RBP);
|
||||
X86_64Assembler::mov_reg64_reg64(buf, X86_64GPReg::RBP, X86_64GPReg::RSP);
|
||||
}
|
||||
for reg in saved_regs {
|
||||
X86_64Assembler::push_reg64(buf, *reg);
|
||||
}
|
||||
|
||||
// full size is upcast to i64 to make sure we don't overflow here.
|
||||
let full_size = 8 * saved_regs.len() as i64 + requested_stack_size as i64;
|
||||
let alignment = if full_size <= 0 {
|
||||
0
|
||||
} else {
|
||||
full_size % STACK_ALIGNMENT as i64
|
||||
};
|
||||
let offset = if alignment == 0 {
|
||||
0
|
||||
} else {
|
||||
STACK_ALIGNMENT - alignment as u8
|
||||
};
|
||||
if let Some(aligned_stack_size) = requested_stack_size.checked_add(offset as i32) {
|
||||
if aligned_stack_size > 0 {
|
||||
X86_64Assembler::sub_reg64_reg64_imm32(
|
||||
buf,
|
||||
X86_64GPReg::RSP,
|
||||
X86_64GPReg::RSP,
|
||||
aligned_stack_size,
|
||||
);
|
||||
Ok(aligned_stack_size)
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
} else {
|
||||
Err("Ran out of stack space".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn x86_64_generic_cleanup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
leaf_function: bool,
|
||||
saved_regs: &[X86_64GPReg],
|
||||
aligned_stack_size: i32,
|
||||
) -> Result<(), String> {
|
||||
if aligned_stack_size > 0 {
|
||||
X86_64Assembler::add_reg64_reg64_imm32(
|
||||
buf,
|
||||
X86_64GPReg::RSP,
|
||||
X86_64GPReg::RSP,
|
||||
aligned_stack_size,
|
||||
);
|
||||
}
|
||||
for reg in saved_regs.iter().rev() {
|
||||
X86_64Assembler::pop_reg64(buf, *reg);
|
||||
}
|
||||
if !leaf_function {
|
||||
X86_64Assembler::mov_reg64_reg64(buf, X86_64GPReg::RSP, X86_64GPReg::RBP);
|
||||
X86_64Assembler::pop_reg64(buf, X86_64GPReg::RBP);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Assembler<X86_64GPReg> for X86_64Assembler {
|
||||
// These functions should map to the raw assembly functions below.
|
||||
// In some cases, that means you can just directly call one of the direct assembly functions.
|
||||
#[inline(always)]
|
||||
fn abs_reg64_reg64<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, src: X86_64GPReg) {
|
||||
mov_reg64_reg64(buf, dst, src);
|
||||
neg_reg64(buf, dst);
|
||||
cmovl_reg64_reg64(buf, dst, src);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn add_reg64_reg64_imm32<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: X86_64GPReg,
|
||||
src1: X86_64GPReg,
|
||||
imm32: i32,
|
||||
) {
|
||||
if dst == src1 {
|
||||
add_reg64_imm32(buf, dst, imm32);
|
||||
} else {
|
||||
mov_reg64_reg64(buf, dst, src1);
|
||||
add_reg64_imm32(buf, dst, imm32);
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn add_reg64_reg64_reg64<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: X86_64GPReg,
|
||||
src1: X86_64GPReg,
|
||||
src2: X86_64GPReg,
|
||||
) {
|
||||
if dst == src1 {
|
||||
add_reg64_reg64(buf, dst, src2);
|
||||
} else if dst == src2 {
|
||||
add_reg64_reg64(buf, dst, src1);
|
||||
} else {
|
||||
mov_reg64_reg64(buf, dst, src1);
|
||||
add_reg64_reg64(buf, dst, src2);
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn mov_reg64_imm64<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i64) {
|
||||
mov_reg64_imm64(buf, dst, imm);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn mov_reg64_reg64<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, src: X86_64GPReg) {
|
||||
mov_reg64_reg64(buf, dst, src);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn mov_reg64_stack32<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, offset: i32) {
|
||||
mov_reg64_stack32(buf, dst, offset);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn mov_stack32_reg64<'a>(buf: &mut Vec<'a, u8>, offset: i32, src: X86_64GPReg) {
|
||||
mov_stack32_reg64(buf, offset, src);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn sub_reg64_reg64_imm32<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: X86_64GPReg,
|
||||
src1: X86_64GPReg,
|
||||
imm32: i32,
|
||||
) {
|
||||
if dst == src1 {
|
||||
sub_reg64_imm32(buf, dst, imm32);
|
||||
} else {
|
||||
mov_reg64_reg64(buf, dst, src1);
|
||||
sub_reg64_imm32(buf, dst, imm32);
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn ret<'a>(buf: &mut Vec<'a, u8>) {
|
||||
ret(buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl X86_64Assembler {
|
||||
#[inline(always)]
|
||||
fn pop_reg64<'a>(buf: &mut Vec<'a, u8>, reg: X86_64GPReg) {
|
||||
pop_reg64(buf, reg);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn push_reg64<'a>(buf: &mut Vec<'a, u8>, reg: X86_64GPReg) {
|
||||
push_reg64(buf, reg);
|
||||
}
|
||||
}
|
||||
const REX: u8 = 0x40;
|
||||
const REX_W: u8 = REX + 0x8;
|
||||
|
||||
fn add_rm_extension(reg: X86_64GPReg, byte: u8) -> u8 {
|
||||
#[inline(always)]
|
||||
const fn add_rm_extension(reg: X86_64GPReg, byte: u8) -> u8 {
|
||||
if reg as u8 > 7 {
|
||||
byte + 1
|
||||
} else {
|
||||
@ -37,11 +338,13 @@ fn add_rm_extension(reg: X86_64GPReg, byte: u8) -> u8 {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_opcode_extension(reg: X86_64GPReg, byte: u8) -> u8 {
|
||||
#[inline(always)]
|
||||
const fn add_opcode_extension(reg: X86_64GPReg, byte: u8) -> u8 {
|
||||
add_rm_extension(reg, byte)
|
||||
}
|
||||
|
||||
fn add_reg_extension(reg: X86_64GPReg, byte: u8) -> u8 {
|
||||
#[inline(always)]
|
||||
const fn add_reg_extension(reg: X86_64GPReg, byte: u8) -> u8 {
|
||||
if reg as u8 > 7 {
|
||||
byte + 4
|
||||
} else {
|
||||
@ -49,316 +352,149 @@ fn add_reg_extension(reg: X86_64GPReg, byte: u8) -> u8 {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct X86_64Assembler {}
|
||||
pub struct X86_64WindowsFastcall {}
|
||||
pub struct X86_64SystemV {}
|
||||
// Below here are the functions for all of the assembly instructions.
|
||||
// Their names are based on the instruction and operators combined.
|
||||
// You should call `buf.reserve()` if you push or extend more than once.
|
||||
// Unit tests are added at the bottom of the file to ensure correct asm generation.
|
||||
// Please keep these in alphanumeric order.
|
||||
|
||||
impl CallConv<X86_64GPReg> for X86_64SystemV {
|
||||
fn gp_param_regs() -> &'static [X86_64GPReg] {
|
||||
&[
|
||||
X86_64GPReg::RDI,
|
||||
X86_64GPReg::RSI,
|
||||
X86_64GPReg::RDX,
|
||||
X86_64GPReg::RCX,
|
||||
X86_64GPReg::R8,
|
||||
X86_64GPReg::R9,
|
||||
]
|
||||
}
|
||||
fn gp_return_regs() -> &'static [X86_64GPReg] {
|
||||
&[X86_64GPReg::RAX, X86_64GPReg::RDX]
|
||||
}
|
||||
fn gp_default_free_regs() -> &'static [X86_64GPReg] {
|
||||
&[
|
||||
// The regs we want to use first should be at the end of this vec.
|
||||
// We will use pop to get which reg to use next
|
||||
// Use callee saved regs last.
|
||||
X86_64GPReg::RBX,
|
||||
// Don't use frame pointer: X86_64GPReg::RBP,
|
||||
X86_64GPReg::R12,
|
||||
X86_64GPReg::R13,
|
||||
X86_64GPReg::R14,
|
||||
X86_64GPReg::R15,
|
||||
// Use caller saved regs first.
|
||||
X86_64GPReg::RAX,
|
||||
X86_64GPReg::RCX,
|
||||
X86_64GPReg::RDX,
|
||||
// Don't use stack pionter: X86_64GPReg::RSP,
|
||||
X86_64GPReg::RSI,
|
||||
X86_64GPReg::RDI,
|
||||
X86_64GPReg::R8,
|
||||
X86_64GPReg::R9,
|
||||
X86_64GPReg::R10,
|
||||
X86_64GPReg::R11,
|
||||
]
|
||||
}
|
||||
fn caller_saved_regs() -> ImSet<X86_64GPReg> {
|
||||
// TODO: stop using vec! here. I was just have trouble with some errors, but it shouldn't be needed.
|
||||
ImSet::from(vec![
|
||||
X86_64GPReg::RAX,
|
||||
X86_64GPReg::RCX,
|
||||
X86_64GPReg::RDX,
|
||||
X86_64GPReg::RSP,
|
||||
X86_64GPReg::RSI,
|
||||
X86_64GPReg::RDI,
|
||||
X86_64GPReg::R8,
|
||||
X86_64GPReg::R9,
|
||||
X86_64GPReg::R10,
|
||||
X86_64GPReg::R11,
|
||||
])
|
||||
}
|
||||
fn callee_saved_regs() -> ImSet<X86_64GPReg> {
|
||||
// TODO: stop using vec! here. I was just have trouble with some errors, but it shouldn't be needed.
|
||||
ImSet::from(vec![
|
||||
X86_64GPReg::RBX,
|
||||
X86_64GPReg::RBP,
|
||||
X86_64GPReg::R12,
|
||||
X86_64GPReg::R13,
|
||||
X86_64GPReg::R14,
|
||||
X86_64GPReg::R15,
|
||||
])
|
||||
}
|
||||
fn stack_pointer() -> X86_64GPReg {
|
||||
X86_64GPReg::RSP
|
||||
}
|
||||
fn frame_pointer() -> X86_64GPReg {
|
||||
X86_64GPReg::RBP
|
||||
}
|
||||
fn shadow_space_size() -> u8 {
|
||||
0
|
||||
}
|
||||
fn red_zone_size() -> u8 {
|
||||
128
|
||||
/// `ADD r/m64, imm32` -> Add imm32 sign-extended to 64-bits from r/m64.
|
||||
#[inline(always)]
|
||||
fn add_reg64_imm32<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i32) {
|
||||
// This can be optimized if the immediate is 1 byte.
|
||||
let rex = add_rm_extension(dst, REX_W);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
buf.reserve(7);
|
||||
buf.extend(&[rex, 0x81, 0xC0 + dst_mod]);
|
||||
buf.extend(&imm.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `ADD r/m64,r64` -> Add r64 to r/m64.
|
||||
#[inline(always)]
|
||||
fn add_reg64_reg64<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, src: X86_64GPReg) {
|
||||
let rex = add_rm_extension(dst, REX_W);
|
||||
let rex = add_reg_extension(src, rex);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
let src_mod = (src as u8 % 8) << 3;
|
||||
buf.extend(&[rex, 0x01, 0xC0 + dst_mod + src_mod]);
|
||||
}
|
||||
|
||||
/// `CMOVL r64,r/m64` -> Move if less (SF≠ OF).
|
||||
#[inline(always)]
|
||||
fn cmovl_reg64_reg64<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, src: X86_64GPReg) {
|
||||
let rex = add_reg_extension(dst, REX_W);
|
||||
let rex = add_rm_extension(src, rex);
|
||||
let dst_mod = (dst as u8 % 8) << 3;
|
||||
let src_mod = src as u8 % 8;
|
||||
buf.extend(&[rex, 0x0F, 0x4C, 0xC0 + dst_mod + src_mod]);
|
||||
}
|
||||
|
||||
/// `MOV r/m64, imm32` -> Move imm32 sign extended to 64-bits to r/m64.
|
||||
#[inline(always)]
|
||||
fn mov_reg64_imm32<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i32) {
|
||||
let rex = add_rm_extension(dst, REX_W);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
buf.reserve(7);
|
||||
buf.extend(&[rex, 0xC7, 0xC0 + dst_mod]);
|
||||
buf.extend(&imm.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `MOV r64, imm64` -> Move imm64 to r64.
|
||||
#[inline(always)]
|
||||
fn mov_reg64_imm64<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i64) {
|
||||
if imm <= i32::MAX as i64 && imm >= i32::MIN as i64 {
|
||||
mov_reg64_imm32(buf, dst, imm as i32)
|
||||
} else {
|
||||
let rex = add_opcode_extension(dst, REX_W);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
buf.reserve(10);
|
||||
buf.extend(&[rex, 0xB8 + dst_mod]);
|
||||
buf.extend(&imm.to_le_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl CallConv<X86_64GPReg> for X86_64WindowsFastcall {
|
||||
fn gp_param_regs() -> &'static [X86_64GPReg] {
|
||||
&[
|
||||
X86_64GPReg::RCX,
|
||||
X86_64GPReg::RDX,
|
||||
X86_64GPReg::R8,
|
||||
X86_64GPReg::R9,
|
||||
]
|
||||
}
|
||||
fn gp_return_regs() -> &'static [X86_64GPReg] {
|
||||
&[X86_64GPReg::RAX]
|
||||
}
|
||||
fn gp_default_free_regs() -> &'static [X86_64GPReg] {
|
||||
&[
|
||||
// The regs we want to use first should be at the end of this vec.
|
||||
// We will use pop to get which reg to use next
|
||||
// Use callee saved regs last.
|
||||
X86_64GPReg::RBX,
|
||||
// Don't use frame pointer: X86_64GPReg::RBP,
|
||||
X86_64GPReg::RSI,
|
||||
// Don't use stack pionter: X86_64GPReg::RSP,
|
||||
X86_64GPReg::RDI,
|
||||
X86_64GPReg::R12,
|
||||
X86_64GPReg::R13,
|
||||
X86_64GPReg::R14,
|
||||
X86_64GPReg::R15,
|
||||
// Use caller saved regs first.
|
||||
X86_64GPReg::RAX,
|
||||
X86_64GPReg::RCX,
|
||||
X86_64GPReg::RDX,
|
||||
X86_64GPReg::R8,
|
||||
X86_64GPReg::R9,
|
||||
X86_64GPReg::R10,
|
||||
X86_64GPReg::R11,
|
||||
]
|
||||
}
|
||||
fn caller_saved_regs() -> ImSet<X86_64GPReg> {
|
||||
// TODO: stop using vec! here. I was just have trouble with some errors, but it shouldn't be needed.
|
||||
ImSet::from(vec![
|
||||
X86_64GPReg::RAX,
|
||||
X86_64GPReg::RCX,
|
||||
X86_64GPReg::RDX,
|
||||
X86_64GPReg::R8,
|
||||
X86_64GPReg::R9,
|
||||
X86_64GPReg::R10,
|
||||
X86_64GPReg::R11,
|
||||
])
|
||||
}
|
||||
fn callee_saved_regs() -> ImSet<X86_64GPReg> {
|
||||
// TODO: stop using vec! here. I was just have trouble with some errors, but it shouldn't be needed.
|
||||
ImSet::from(vec![
|
||||
X86_64GPReg::RBX,
|
||||
X86_64GPReg::RBP,
|
||||
X86_64GPReg::RSI,
|
||||
X86_64GPReg::RSP,
|
||||
X86_64GPReg::RDI,
|
||||
X86_64GPReg::R12,
|
||||
X86_64GPReg::R13,
|
||||
X86_64GPReg::R14,
|
||||
X86_64GPReg::R15,
|
||||
])
|
||||
}
|
||||
fn stack_pointer() -> X86_64GPReg {
|
||||
X86_64GPReg::RSP
|
||||
}
|
||||
fn frame_pointer() -> X86_64GPReg {
|
||||
X86_64GPReg::RBP
|
||||
}
|
||||
fn shadow_space_size() -> u8 {
|
||||
32
|
||||
}
|
||||
fn red_zone_size() -> u8 {
|
||||
0
|
||||
/// `MOV r/m64,r64` -> Move r64 to r/m64.
|
||||
#[inline(always)]
|
||||
fn mov_reg64_reg64<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, src: X86_64GPReg) {
|
||||
let rex = add_rm_extension(dst, REX_W);
|
||||
let rex = add_reg_extension(src, rex);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
let src_mod = (src as u8 % 8) << 3;
|
||||
buf.extend(&[rex, 0x89, 0xC0 + dst_mod + src_mod]);
|
||||
}
|
||||
|
||||
/// `MOV r64,r/m64` -> Move r/m64 to r64.
|
||||
#[inline(always)]
|
||||
fn mov_reg64_stack32<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, offset: i32) {
|
||||
// This can be optimized based on how many bytes the offset actually is.
|
||||
// This function can probably be made to take any memory offset, I didn't feel like figuring it out rn.
|
||||
// Also, this may technically be faster genration since stack operations should be so common.
|
||||
let rex = add_reg_extension(dst, REX_W);
|
||||
let dst_mod = (dst as u8 % 8) << 3;
|
||||
buf.reserve(8);
|
||||
buf.extend(&[rex, 0x8B, 0x84 + dst_mod, 0x24]);
|
||||
buf.extend(&offset.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `MOV r/m64,r64` -> Move r64 to r/m64.
|
||||
#[inline(always)]
|
||||
fn mov_stack32_reg64<'a>(buf: &mut Vec<'a, u8>, offset: i32, src: X86_64GPReg) {
|
||||
// This can be optimized based on how many bytes the offset actually is.
|
||||
// This function can probably be made to take any memory offset, I didn't feel like figuring it out rn.
|
||||
// Also, this may technically be faster genration since stack operations should be so common.
|
||||
let rex = add_reg_extension(src, REX_W);
|
||||
let src_mod = (src as u8 % 8) << 3;
|
||||
buf.reserve(8);
|
||||
buf.extend(&[rex, 0x89, 0x84 + src_mod, 0x24]);
|
||||
buf.extend(&offset.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `NEG r/m64` -> Two's complement negate r/m64.
|
||||
#[inline(always)]
|
||||
fn neg_reg64<'a>(buf: &mut Vec<'a, u8>, reg: X86_64GPReg) {
|
||||
let rex = add_rm_extension(reg, REX_W);
|
||||
let reg_mod = reg as u8 % 8;
|
||||
buf.extend(&[rex, 0xF7, 0xD8 + reg_mod]);
|
||||
}
|
||||
|
||||
/// `RET` -> Near return to calling procedure.
|
||||
#[inline(always)]
|
||||
fn ret<'a>(buf: &mut Vec<'a, u8>) {
|
||||
buf.push(0xC3);
|
||||
}
|
||||
|
||||
/// `SUB r/m64, imm32` -> Subtract imm32 sign-extended to 64-bits from r/m64.
|
||||
#[inline(always)]
|
||||
fn sub_reg64_imm32<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i32) {
|
||||
// This can be optimized if the immediate is 1 byte.
|
||||
let rex = add_rm_extension(dst, REX_W);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
buf.reserve(7);
|
||||
buf.extend(&[rex, 0x81, 0xE8 + dst_mod]);
|
||||
buf.extend(&imm.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `POP r64` -> Pop top of stack into r64; increment stack pointer. Cannot encode 32-bit operand size.
|
||||
#[inline(always)]
|
||||
fn pop_reg64<'a>(buf: &mut Vec<'a, u8>, reg: X86_64GPReg) {
|
||||
let reg_mod = reg as u8 % 8;
|
||||
if reg as u8 > 7 {
|
||||
let rex = add_opcode_extension(reg, REX);
|
||||
buf.extend(&[rex, 0x58 + reg_mod]);
|
||||
} else {
|
||||
buf.push(0x58 + reg_mod);
|
||||
}
|
||||
}
|
||||
|
||||
impl Assembler<X86_64GPReg> for X86_64Assembler {
|
||||
// Below here are the functions for all of the assembly instructions.
|
||||
// Their names are based on the instruction and operators combined.
|
||||
// You should call `buf.reserve()` if you push or extend more than once.
|
||||
// Unit tests are added at the bottom of the file to ensure correct asm generation.
|
||||
// Please keep these in alphanumeric order.
|
||||
|
||||
/// `ADD r/m64, imm32` -> Add imm32 sign-extended to 64-bits from r/m64.
|
||||
fn add_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i32) {
|
||||
// This can be optimized if the immediate is 1 byte.
|
||||
let rex = add_rm_extension(dst, REX_W);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
buf.reserve(7);
|
||||
buf.extend(&[rex, 0x81, 0xC0 + dst_mod]);
|
||||
buf.extend(&imm.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `ADD r/m64,r64` -> Add r64 to r/m64.
|
||||
fn add_register64bit_register64bit<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: X86_64GPReg,
|
||||
src: X86_64GPReg,
|
||||
) {
|
||||
let rex = add_rm_extension(dst, REX_W);
|
||||
let rex = add_reg_extension(src, rex);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
let src_mod = (src as u8 % 8) << 3;
|
||||
buf.extend(&[rex, 0x01, 0xC0 + dst_mod + src_mod]);
|
||||
}
|
||||
|
||||
/// `CMOVL r64,r/m64` -> Move if less (SF≠ OF).
|
||||
fn cmovl_register64bit_register64bit<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: X86_64GPReg,
|
||||
src: X86_64GPReg,
|
||||
) {
|
||||
let rex = add_reg_extension(dst, REX_W);
|
||||
let rex = add_rm_extension(src, rex);
|
||||
let dst_mod = (dst as u8 % 8) << 3;
|
||||
let src_mod = src as u8 % 8;
|
||||
buf.extend(&[rex, 0x0F, 0x4C, 0xC0 + dst_mod + src_mod]);
|
||||
}
|
||||
|
||||
/// `MOV r/m64, imm32` -> Move imm32 sign extended to 64-bits to r/m64.
|
||||
fn mov_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i32) {
|
||||
let rex = add_rm_extension(dst, REX_W);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
buf.reserve(7);
|
||||
buf.extend(&[rex, 0xC7, 0xC0 + dst_mod]);
|
||||
buf.extend(&imm.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `MOV r64, imm64` -> Move imm64 to r64.
|
||||
fn mov_register64bit_immediate64bit<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i64) {
|
||||
if imm <= i32::MAX as i64 && imm >= i32::MIN as i64 {
|
||||
Self::mov_register64bit_immediate32bit(buf, dst, imm as i32)
|
||||
} else {
|
||||
let rex = add_opcode_extension(dst, REX_W);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
buf.reserve(10);
|
||||
buf.extend(&[rex, 0xB8 + dst_mod]);
|
||||
buf.extend(&imm.to_le_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
/// `MOV r/m64,r64` -> Move r64 to r/m64.
|
||||
fn mov_register64bit_register64bit<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: X86_64GPReg,
|
||||
src: X86_64GPReg,
|
||||
) {
|
||||
let rex = add_rm_extension(dst, REX_W);
|
||||
let rex = add_reg_extension(src, rex);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
let src_mod = (src as u8 % 8) << 3;
|
||||
buf.extend(&[rex, 0x89, 0xC0 + dst_mod + src_mod]);
|
||||
}
|
||||
|
||||
/// `MOV r64,r/m64` -> Move r/m64 to r64.
|
||||
fn mov_register64bit_stackoffset32bit<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
dst: X86_64GPReg,
|
||||
offset: i32,
|
||||
) {
|
||||
// This can be optimized based on how many bytes the offset actually is.
|
||||
// This function can probably be made to take any memory offset, I didn't feel like figuring it out rn.
|
||||
// Also, this may technically be faster genration since stack operations should be so common.
|
||||
let rex = add_reg_extension(dst, REX_W);
|
||||
let dst_mod = (dst as u8 % 8) << 3;
|
||||
buf.reserve(8);
|
||||
buf.extend(&[rex, 0x8B, 0x84 + dst_mod, 0x24]);
|
||||
buf.extend(&offset.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `MOV r/m64,r64` -> Move r64 to r/m64.
|
||||
fn mov_stackoffset32bit_register64bit<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
offset: i32,
|
||||
src: X86_64GPReg,
|
||||
) {
|
||||
// This can be optimized based on how many bytes the offset actually is.
|
||||
// This function can probably be made to take any memory offset, I didn't feel like figuring it out rn.
|
||||
// Also, this may technically be faster genration since stack operations should be so common.
|
||||
let rex = add_reg_extension(src, REX_W);
|
||||
let src_mod = (src as u8 % 8) << 3;
|
||||
buf.reserve(8);
|
||||
buf.extend(&[rex, 0x89, 0x84 + src_mod, 0x24]);
|
||||
buf.extend(&offset.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `NEG r/m64` -> Two's complement negate r/m64.
|
||||
fn neg_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: X86_64GPReg) {
|
||||
let rex = add_rm_extension(reg, REX_W);
|
||||
let reg_mod = reg as u8 % 8;
|
||||
buf.extend(&[rex, 0xF7, 0xD8 + reg_mod]);
|
||||
}
|
||||
|
||||
/// `RET` -> Near return to calling procedure.
|
||||
fn ret<'a>(buf: &mut Vec<'a, u8>) {
|
||||
buf.push(0xC3);
|
||||
}
|
||||
|
||||
/// `SUB r/m64, imm32` -> Subtract imm32 sign-extended to 64-bits from r/m64.
|
||||
fn sub_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: X86_64GPReg, imm: i32) {
|
||||
// This can be optimized if the immediate is 1 byte.
|
||||
let rex = add_rm_extension(dst, REX_W);
|
||||
let dst_mod = dst as u8 % 8;
|
||||
buf.reserve(7);
|
||||
buf.extend(&[rex, 0x81, 0xE8 + dst_mod]);
|
||||
buf.extend(&imm.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `POP r64` -> Pop top of stack into r64; increment stack pointer. Cannot encode 32-bit operand size.
|
||||
fn pop_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: X86_64GPReg) {
|
||||
let reg_mod = reg as u8 % 8;
|
||||
if reg as u8 > 7 {
|
||||
let rex = add_opcode_extension(reg, REX);
|
||||
buf.extend(&[rex, 0x58 + reg_mod]);
|
||||
} else {
|
||||
buf.push(0x58 + reg_mod);
|
||||
}
|
||||
}
|
||||
|
||||
/// `PUSH r64` -> Push r64,
|
||||
fn push_register64bit<'a>(buf: &mut Vec<'a, u8>, reg: X86_64GPReg) {
|
||||
let reg_mod = reg as u8 % 8;
|
||||
if reg as u8 > 7 {
|
||||
let rex = add_opcode_extension(reg, REX);
|
||||
buf.extend(&[rex, 0x50 + reg_mod]);
|
||||
} else {
|
||||
buf.push(0x50 + reg_mod);
|
||||
}
|
||||
/// `PUSH r64` -> Push r64,
|
||||
#[inline(always)]
|
||||
fn push_reg64<'a>(buf: &mut Vec<'a, u8>, reg: X86_64GPReg) {
|
||||
let reg_mod = reg as u8 % 8;
|
||||
if reg as u8 > 7 {
|
||||
let rex = add_opcode_extension(reg, REX);
|
||||
buf.extend(&[rex, 0x50 + reg_mod]);
|
||||
} else {
|
||||
buf.push(0x50 + reg_mod);
|
||||
}
|
||||
}
|
||||
|
||||
@ -372,7 +508,7 @@ mod tests {
|
||||
const TEST_I64: i64 = 0x12345678_9ABCDEF0;
|
||||
|
||||
#[test]
|
||||
fn test_add_register64bit_immediate32bit() {
|
||||
fn test_add_reg64_imm32() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for (dst, expected) in &[
|
||||
@ -380,14 +516,14 @@ mod tests {
|
||||
(X86_64GPReg::R15, [0x49, 0x81, 0xC7]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::add_register64bit_immediate32bit(&mut buf, *dst, TEST_I32);
|
||||
add_reg64_imm32(&mut buf, *dst, TEST_I32);
|
||||
assert_eq!(expected, &buf[..3]);
|
||||
assert_eq!(TEST_I32.to_le_bytes(), &buf[3..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_register64bit_register64bit() {
|
||||
fn test_add_reg64_reg64() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for ((dst, src), expected) in &[
|
||||
@ -397,13 +533,13 @@ mod tests {
|
||||
((X86_64GPReg::R15, X86_64GPReg::R15), [0x4D, 0x01, 0xFF]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::add_register64bit_register64bit(&mut buf, *dst, *src);
|
||||
add_reg64_reg64(&mut buf, *dst, *src);
|
||||
assert_eq!(expected, &buf[..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmovl_register64bit_register64bit() {
|
||||
fn test_cmovl_reg64_reg64() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for ((dst, src), expected) in &[
|
||||
@ -425,13 +561,13 @@ mod tests {
|
||||
),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::cmovl_register64bit_register64bit(&mut buf, *dst, *src);
|
||||
cmovl_reg64_reg64(&mut buf, *dst, *src);
|
||||
assert_eq!(expected, &buf[..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mov_register64bit_immediate32bit() {
|
||||
fn test_mov_reg64_imm32() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for (dst, expected) in &[
|
||||
@ -439,14 +575,14 @@ mod tests {
|
||||
(X86_64GPReg::R15, [0x49, 0xC7, 0xC7]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::mov_register64bit_immediate32bit(&mut buf, *dst, TEST_I32);
|
||||
mov_reg64_imm32(&mut buf, *dst, TEST_I32);
|
||||
assert_eq!(expected, &buf[..3]);
|
||||
assert_eq!(TEST_I32.to_le_bytes(), &buf[3..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mov_register64bit_immediate64bit() {
|
||||
fn test_mov_reg64_imm64() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for (dst, expected) in &[
|
||||
@ -454,7 +590,7 @@ mod tests {
|
||||
(X86_64GPReg::R15, [0x49, 0xBF]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::mov_register64bit_immediate64bit(&mut buf, *dst, TEST_I64);
|
||||
mov_reg64_imm64(&mut buf, *dst, TEST_I64);
|
||||
assert_eq!(expected, &buf[..2]);
|
||||
assert_eq!(TEST_I64.to_le_bytes(), &buf[2..]);
|
||||
}
|
||||
@ -463,14 +599,14 @@ mod tests {
|
||||
(X86_64GPReg::R15, [0x49, 0xC7, 0xC7]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::mov_register64bit_immediate64bit(&mut buf, *dst, TEST_I32 as i64);
|
||||
mov_reg64_imm64(&mut buf, *dst, TEST_I32 as i64);
|
||||
assert_eq!(expected, &buf[..3]);
|
||||
assert_eq!(TEST_I32.to_le_bytes(), &buf[3..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mov_register64bit_register64bit() {
|
||||
fn test_mov_reg64_reg64() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for ((dst, src), expected) in &[
|
||||
@ -480,13 +616,13 @@ mod tests {
|
||||
((X86_64GPReg::R15, X86_64GPReg::R15), [0x4D, 0x89, 0xFF]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::mov_register64bit_register64bit(&mut buf, *dst, *src);
|
||||
mov_reg64_reg64(&mut buf, *dst, *src);
|
||||
assert_eq!(expected, &buf[..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mov_register64bit_stackoffset32bit() {
|
||||
fn test_mov_reg64_stack32() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for ((dst, offset), expected) in &[
|
||||
@ -494,14 +630,14 @@ mod tests {
|
||||
((X86_64GPReg::R15, TEST_I32), [0x4C, 0x8B, 0xBC, 0x24]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::mov_register64bit_stackoffset32bit(&mut buf, *dst, *offset);
|
||||
mov_reg64_stack32(&mut buf, *dst, *offset);
|
||||
assert_eq!(expected, &buf[..4]);
|
||||
assert_eq!(TEST_I32.to_le_bytes(), &buf[4..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mov_stackoffset32bit_register64bit() {
|
||||
fn test_mov_stack32_reg64() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for ((offset, src), expected) in &[
|
||||
@ -509,14 +645,14 @@ mod tests {
|
||||
((TEST_I32, X86_64GPReg::R15), [0x4C, 0x89, 0xBC, 0x24]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::mov_stackoffset32bit_register64bit(&mut buf, *offset, *src);
|
||||
mov_stack32_reg64(&mut buf, *offset, *src);
|
||||
assert_eq!(expected, &buf[..4]);
|
||||
assert_eq!(TEST_I32.to_le_bytes(), &buf[4..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_neg_register64bit() {
|
||||
fn test_neg_reg64() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for (reg, expected) in &[
|
||||
@ -524,7 +660,7 @@ mod tests {
|
||||
(X86_64GPReg::R15, [0x49, 0xF7, 0xDF]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::neg_register64bit(&mut buf, *reg);
|
||||
neg_reg64(&mut buf, *reg);
|
||||
assert_eq!(expected, &buf[..]);
|
||||
}
|
||||
}
|
||||
@ -533,12 +669,12 @@ mod tests {
|
||||
fn test_ret() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
X86_64Assembler::ret(&mut buf);
|
||||
ret(&mut buf);
|
||||
assert_eq!(&[0xC3], &buf[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub_register64bit_immediate32bit() {
|
||||
fn test_sub_reg64_imm32() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for (dst, expected) in &[
|
||||
@ -546,14 +682,14 @@ mod tests {
|
||||
(X86_64GPReg::R15, [0x49, 0x81, 0xEF]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::sub_register64bit_immediate32bit(&mut buf, *dst, TEST_I32);
|
||||
sub_reg64_imm32(&mut buf, *dst, TEST_I32);
|
||||
assert_eq!(expected, &buf[..3]);
|
||||
assert_eq!(TEST_I32.to_le_bytes(), &buf[3..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pop_register64bit() {
|
||||
fn test_pop_reg64() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for (dst, expected) in &[
|
||||
@ -561,13 +697,13 @@ mod tests {
|
||||
(X86_64GPReg::R15, vec![0x41, 0x5F]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::pop_register64bit(&mut buf, *dst);
|
||||
pop_reg64(&mut buf, *dst);
|
||||
assert_eq!(&expected[..], &buf[..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_push_register64bit() {
|
||||
fn test_push_reg64() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for (src, expected) in &[
|
||||
@ -575,7 +711,7 @@ mod tests {
|
||||
(X86_64GPReg::R15, vec![0x41, 0x57]),
|
||||
] {
|
||||
buf.clear();
|
||||
X86_64Assembler::push_register64bit(&mut buf, *src);
|
||||
push_reg64(&mut buf, *src);
|
||||
assert_eq!(&expected[..], &buf[..]);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::generic64::{x86_64, Backend64Bit};
|
||||
use crate::generic64::{aarch64, x86_64, Backend64Bit};
|
||||
use crate::{Backend, Env, Relocation, INLINED_SYMBOLS};
|
||||
use bumpalo::collections::Vec;
|
||||
use object::write;
|
||||
@ -22,7 +22,7 @@ pub fn build_module<'a>(
|
||||
target: &Triple,
|
||||
procedures: MutMap<(symbol::Symbol, Layout<'a>), Proc<'a>>,
|
||||
) -> Result<Object, String> {
|
||||
let (mut output, mut backend) = match target {
|
||||
match target {
|
||||
Triple {
|
||||
architecture: TargetArch::X86_64,
|
||||
binary_format: TargetBF::Elf,
|
||||
@ -33,15 +33,42 @@ pub fn build_module<'a>(
|
||||
x86_64::X86_64Assembler,
|
||||
x86_64::X86_64SystemV,
|
||||
> = Backend::new(env, target)?;
|
||||
Ok((
|
||||
Object::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little),
|
||||
build_object(
|
||||
env,
|
||||
procedures,
|
||||
backend,
|
||||
))
|
||||
Object::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little),
|
||||
)
|
||||
}
|
||||
Triple {
|
||||
architecture: TargetArch::Aarch64(_),
|
||||
binary_format: TargetBF::Elf,
|
||||
..
|
||||
} => {
|
||||
let backend: Backend64Bit<
|
||||
aarch64::AArch64GPReg,
|
||||
aarch64::AArch64Assembler,
|
||||
aarch64::AArch64Call,
|
||||
> = Backend::new(env, target)?;
|
||||
build_object(
|
||||
env,
|
||||
procedures,
|
||||
backend,
|
||||
Object::new(BinaryFormat::Elf, Architecture::Aarch64, Endianness::Little),
|
||||
)
|
||||
}
|
||||
x => Err(format! {
|
||||
"the target, {:?}, is not yet implemented",
|
||||
x}),
|
||||
}?;
|
||||
}
|
||||
}
|
||||
|
||||
fn build_object<'a, B: Backend<'a>>(
|
||||
env: &'a Env,
|
||||
procedures: MutMap<(symbol::Symbol, Layout<'a>), Proc<'a>>,
|
||||
mut backend: B,
|
||||
mut output: Object,
|
||||
) -> Result<Object, String> {
|
||||
let text = output.section_id(StandardSection::Text);
|
||||
let data_section = output.section_id(StandardSection::Data);
|
||||
let comment = output.add_section(vec![], b"comment".to_vec(), SectionKind::OtherString);
|
||||
|
@ -9,7 +9,7 @@ extern crate libc;
|
||||
#[macro_use]
|
||||
mod helpers;
|
||||
|
||||
#[cfg(all(test, target_os = "linux", target_arch = "x86_64"))]
|
||||
#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
|
||||
mod gen_num {
|
||||
//use roc_std::RocOrder;
|
||||
|
||||
|
@ -6,8 +6,10 @@ pub enum LowLevel {
|
||||
StrConcat,
|
||||
StrIsEmpty,
|
||||
StrStartsWith,
|
||||
StrEndsWith,
|
||||
StrSplit,
|
||||
StrCountGraphemes,
|
||||
StrFromInt,
|
||||
ListLen,
|
||||
ListGetUnsafe,
|
||||
ListSet,
|
||||
|
@ -673,6 +673,8 @@ define_builtins! {
|
||||
5 STR_SPLIT: "split"
|
||||
6 STR_COUNT_GRAPHEMES: "countGraphemes"
|
||||
7 STR_STARTS_WITH: "startsWith"
|
||||
8 STR_ENDS_WITH: "endsWith"
|
||||
9 STR_FROM_INT: "fromInt"
|
||||
}
|
||||
4 LIST: "List" => {
|
||||
0 LIST_LIST: "List" imported // the List.List type alias
|
||||
|
@ -547,6 +547,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
||||
| NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin => {
|
||||
arena.alloc_slice_copy(&[irrelevant])
|
||||
}
|
||||
StrStartsWith => arena.alloc_slice_copy(&[owned, borrowed]),
|
||||
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),
|
||||
StrFromInt => arena.alloc_slice_copy(&[irrelevant]),
|
||||
}
|
||||
}
|
||||
|
@ -460,7 +460,7 @@ impl<'a> Layout<'a> {
|
||||
|
||||
pub fn is_refcounted(&self) -> bool {
|
||||
match self {
|
||||
Layout::Builtin(Builtin::List(_, _)) => true,
|
||||
Layout::Builtin(Builtin::List(MemoryMode::Refcounted, _)) => true,
|
||||
Layout::Builtin(Builtin::Str) => true,
|
||||
Layout::RecursiveUnion(_) => true,
|
||||
Layout::RecursivePointer => true,
|
||||
@ -477,12 +477,12 @@ impl<'a> Layout<'a> {
|
||||
match self {
|
||||
Builtin(builtin) => builtin.is_refcounted(),
|
||||
PhantomEmptyStruct => false,
|
||||
Struct(fields) => fields.iter().any(|f| f.is_refcounted()),
|
||||
Struct(fields) => fields.iter().any(|f| f.contains_refcounted()),
|
||||
Union(fields) => fields
|
||||
.iter()
|
||||
.map(|ls| ls.iter())
|
||||
.flatten()
|
||||
.any(|f| f.is_refcounted()),
|
||||
.any(|f| f.contains_refcounted()),
|
||||
RecursiveUnion(_) => true,
|
||||
Closure(_, closure_layout, _) => closure_layout.contains_refcounted(),
|
||||
FunctionPointer(_, _) | RecursivePointer | Pointer(_) => false,
|
||||
|
@ -917,9 +917,13 @@ fn parse_def_signature<'a>(
|
||||
|
||||
fn loc_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Expr<'a>>> {
|
||||
skip_first!(
|
||||
// If this is a reserved keyword ("if", "then", "case, "when"), then
|
||||
// it is not a function argument!
|
||||
not(reserved_keyword()),
|
||||
// If this is a reserved keyword ("if", "then", "case, "when"),
|
||||
// followed by a blank space, then it is not a function argument!
|
||||
//
|
||||
// (The space is necessary because otherwise we'll get a false
|
||||
// positive on function arguments beginning with keywords,
|
||||
// e.g. `ifBlah` or `isSomething` will register as `if`/`is` keywords)
|
||||
not(and!(reserved_keyword(), space1(min_indent))),
|
||||
// Don't parse operators, because they have a higher precedence than function application.
|
||||
// If we encounter one, we're done parsing function args!
|
||||
move |arena, state| loc_parse_function_arg(min_indent, arena, state)
|
||||
|
@ -777,24 +777,25 @@ macro_rules! skip_first {
|
||||
use $crate::parser::Fail;
|
||||
|
||||
let original_attempting = state.attempting;
|
||||
let original_state = state.clone();
|
||||
|
||||
match $p1.parse(arena, state) {
|
||||
Ok((_, state)) => match $p2.parse(arena, state) {
|
||||
Ok((out2, state)) => Ok((out2, state)),
|
||||
Err((fail, state)) => Err((
|
||||
Err((fail, _)) => Err((
|
||||
Fail {
|
||||
attempting: original_attempting,
|
||||
..fail
|
||||
},
|
||||
state,
|
||||
original_state,
|
||||
)),
|
||||
},
|
||||
Err((fail, state)) => Err((
|
||||
Err((fail, _)) => Err((
|
||||
Fail {
|
||||
attempting: original_attempting,
|
||||
..fail
|
||||
},
|
||||
state,
|
||||
original_state,
|
||||
)),
|
||||
}
|
||||
}
|
||||
@ -810,24 +811,25 @@ macro_rules! skip_second {
|
||||
use $crate::parser::Fail;
|
||||
|
||||
let original_attempting = state.attempting;
|
||||
let original_state = state.clone();
|
||||
|
||||
match $p1.parse(arena, state) {
|
||||
Ok((out1, state)) => match $p2.parse(arena, state) {
|
||||
Ok((_, state)) => Ok((out1, state)),
|
||||
Err((fail, state)) => Err((
|
||||
Err((fail, _)) => Err((
|
||||
Fail {
|
||||
attempting: original_attempting,
|
||||
..fail
|
||||
},
|
||||
state,
|
||||
original_state,
|
||||
)),
|
||||
},
|
||||
Err((fail, state)) => Err((
|
||||
Err((fail, _)) => Err((
|
||||
Fail {
|
||||
attempting: original_attempting,
|
||||
..fail
|
||||
},
|
||||
state,
|
||||
original_state,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
@ -814,6 +814,71 @@ mod test_parse {
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_when() {
|
||||
// Regression test for identifiers beginning with keywords (if/then/else/when/is)
|
||||
let arena = Bump::new();
|
||||
let expected = Var {
|
||||
module_name: "",
|
||||
ident: "whenever",
|
||||
};
|
||||
let actual = parse_expr_with(&arena, "whenever");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_is() {
|
||||
// Regression test for identifiers beginning with keywords (if/then/else/when/is)
|
||||
let arena = Bump::new();
|
||||
let expected = Var {
|
||||
module_name: "",
|
||||
ident: "isnt",
|
||||
};
|
||||
let actual = parse_expr_with(&arena, "isnt");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_if() {
|
||||
// Regression test for identifiers beginning with keywords (if/then/else/when/is)
|
||||
let arena = Bump::new();
|
||||
let expected = Var {
|
||||
module_name: "",
|
||||
ident: "iffy",
|
||||
};
|
||||
let actual = parse_expr_with(&arena, "iffy");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_then() {
|
||||
// Regression test for identifiers beginning with keywords (if/then/else/when/is)
|
||||
let arena = Bump::new();
|
||||
let expected = Var {
|
||||
module_name: "",
|
||||
ident: "thenever",
|
||||
};
|
||||
let actual = parse_expr_with(&arena, "thenever");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_else() {
|
||||
// Regression test for identifiers beginning with keywords (if/then/else/when/is)
|
||||
let arena = Bump::new();
|
||||
let expected = Var {
|
||||
module_name: "",
|
||||
ident: "elsewhere",
|
||||
};
|
||||
let actual = parse_expr_with(&arena, "elsewhere");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parenthetical_var() {
|
||||
let arena = Bump::new();
|
||||
@ -1511,6 +1576,32 @@ mod test_parse {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_def() {
|
||||
let arena = Bump::new();
|
||||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||
let def = Def::Body(
|
||||
arena.alloc(Located::new(0, 0, 0, 4, Identifier("iffy"))),
|
||||
arena.alloc(Located::new(0, 0, 5, 6, Num("5"))),
|
||||
);
|
||||
let loc_def = &*arena.alloc(Located::new(0, 0, 0, 6, def));
|
||||
let defs = &[loc_def];
|
||||
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice());
|
||||
let loc_ret = Located::new(2, 2, 0, 2, ret);
|
||||
let expected = Defs(defs, arena.alloc(loc_ret));
|
||||
|
||||
assert_parses_to(
|
||||
indoc!(
|
||||
r#"
|
||||
iffy=5
|
||||
|
||||
42
|
||||
"#
|
||||
),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_spaced_def() {
|
||||
let arena = Bump::new();
|
||||
@ -2573,6 +2664,41 @@ mod test_parse {
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repro_keyword_bug() {
|
||||
// Reproducing this bug requires a bizarre set of things to all be true:
|
||||
//
|
||||
// * Must be parsing a *module* def (nested expr defs don't repro this)
|
||||
// * That top-level module def conatins a def inside it
|
||||
// * That inner def is defining a function
|
||||
// * The name of the inner def begins with a keyword (`if`, `then`, `else`, `when`, `is`)
|
||||
//
|
||||
// If all of these are true, then lookups on that def get skipped over by the parser.
|
||||
// If any one of the above is false, then everything works.
|
||||
|
||||
let arena = Bump::new();
|
||||
let src = indoc!(
|
||||
r#"
|
||||
foo = \list ->
|
||||
isTest = \_ -> 5
|
||||
List.map list isTest
|
||||
"#
|
||||
);
|
||||
let actual = module_defs()
|
||||
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
|
||||
.map(|tuple| tuple.0);
|
||||
|
||||
// It should occur twice in the debug output - once for the pattern,
|
||||
// and then again for the lookup.
|
||||
let occurrences = format!("{:?}", actual)
|
||||
.split("isTest")
|
||||
.collect::<std::vec::Vec<_>>()
|
||||
.len()
|
||||
- 1;
|
||||
|
||||
assert_eq!(occurrences, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn standalone_module_defs() {
|
||||
use roc_parse::ast::Def::*;
|
||||
|
@ -62,6 +62,16 @@ These are potentially inspirational resources for the editor's design.
|
||||
* Excel and Google Sheets
|
||||
* Not sure, maybe something they do well that we (code editors) could learn from
|
||||
|
||||
|
||||
## Machine Learning Ideas
|
||||
|
||||
* Ability to record all changes to abstract syntax tree with user permission.
|
||||
* I think it is possible to create powerful automatic error resolution by having a dataset available of ast's with a specific error and the subsequent transformation that fixed the error.
|
||||
* GPT-3 can generate correct python functions based on a comment describing the functionality, video [here](https://www.youtube.com/watch?v=utuz7wBGjKM). It's possible that training a model using ast's may lead to better results than text based models.
|
||||
* Users with large private code bases could (re)train a publicly available error recovery model to experience benefits without having to share their code.
|
||||
* It could be useful to a user who is creating a function to show them the most similar function (type signature, name, comment) in a public+their private database. Say I was using a web framework and I just created a function that has a multipart form as argument, it would be great to have an example instantly available.
|
||||
|
||||
|
||||
## General Thoughts/Ideas
|
||||
|
||||
Thoughts and ideas possibly taken from above inspirations or separate.
|
||||
@ -76,6 +86,11 @@ Thoughts and ideas possibly taken from above inspirations or separate.
|
||||
* Ability to show import connection within project visually
|
||||
* This could be done by drawing connections between files or functions in the tree view. This would make it easier for people to get their bearings in new big projects.
|
||||
* Connections could also be drawn between functions that call each other in the tree view. The connections could be animated to show the execution flow of the program.
|
||||
* Ability to inline statements contained in called functions into the callee function for debugging.
|
||||
* The value of expressions can be shown at the end of the line like in the [Inventing on Principle talk](https://youtu.be/8QiPFmIMxFc?t=1181)
|
||||
* This would give a clear overview of the execution and should make it easy to pinpoint the line where the bug originates.
|
||||
* That specific line can then be right clicked to go to the actual function.
|
||||
* Having to jump around between different functions and files is unnecessary and makes it difficult to see the forest through the trees.
|
||||
* "Error mode" where the editor jumps you to the next error
|
||||
* Similar in theory to diff tools that jump you to the next merge conflict
|
||||
* dependency recommendation
|
||||
|
0
editor/src/expr.rs
Normal file
0
editor/src/expr.rs
Normal file
@ -4,7 +4,7 @@ app "effect-example" imports [ Effect ] provides [ main ] to "./platform"
|
||||
main : Effect.Effect {} as Fx
|
||||
main =
|
||||
when if 1 == 1 then True 3 else False 3.14 is
|
||||
True 3 -> Effect.putLine "Yay"
|
||||
True n -> Effect.putLine (Str.fromInt n)
|
||||
_ -> Effect.putLine "Yay"
|
||||
|
||||
# main : Effect.Effect {} as Fx
|
||||
|
71
shell.nix
71
shell.nix
@ -15,36 +15,48 @@ in with {
|
||||
}) { };
|
||||
|
||||
isMacOS = currentOS == "darwin";
|
||||
isLinux = currentOS == "linux";
|
||||
isAarch64 = currentArch == "aarch64";
|
||||
};
|
||||
|
||||
with (pkgs);
|
||||
|
||||
let
|
||||
darwin-frameworks = if isMacOS then
|
||||
with pkgs.darwin.apple_sdk.frameworks; [
|
||||
AppKit
|
||||
CoreFoundation
|
||||
CoreServices
|
||||
CoreVideo
|
||||
Foundation
|
||||
Metal
|
||||
Security
|
||||
]
|
||||
else
|
||||
[ ];
|
||||
darwin-inputs =
|
||||
if isMacOS then
|
||||
with pkgs.darwin.apple_sdk.frameworks; [
|
||||
AppKit
|
||||
CoreFoundation
|
||||
CoreServices
|
||||
CoreVideo
|
||||
Foundation
|
||||
Metal
|
||||
Security
|
||||
]
|
||||
else
|
||||
[ ];
|
||||
|
||||
linux-only = if !isMacOS then [
|
||||
vulkan-headers
|
||||
vulkan-loader
|
||||
vulkan-tools
|
||||
vulkan-validation-layers
|
||||
xorg.libX11
|
||||
xorg.libXcursor
|
||||
xorg.libXrandr
|
||||
xorg.libXi
|
||||
] else
|
||||
[ ];
|
||||
linux-inputs =
|
||||
if isLinux then
|
||||
[
|
||||
vulkan-headers
|
||||
vulkan-loader
|
||||
vulkan-tools
|
||||
vulkan-validation-layers
|
||||
xorg.libX11
|
||||
xorg.libXcursor
|
||||
xorg.libXrandr
|
||||
xorg.libXi
|
||||
]
|
||||
else
|
||||
[ ];
|
||||
|
||||
nixos-env =
|
||||
if isLinux && builtins.pathExists /etc/nixos/configuration.nix then
|
||||
{ XDG_DATA_DIRS = "/run/opengl-driver/share:$XDG_DATA_DIRS";
|
||||
}
|
||||
else
|
||||
{ };
|
||||
|
||||
llvmPkgs = pkgs.llvmPackages_10;
|
||||
zig = import ./nix/zig.nix { inherit pkgs isMacOS isAarch64; };
|
||||
@ -77,17 +89,18 @@ let
|
||||
ccls
|
||||
];
|
||||
|
||||
in mkShell {
|
||||
buildInputs = inputs ++ darwin-frameworks ++ linux-only;
|
||||
LLVM_SYS_100_PREFIX = "${llvmPkgs.llvm}";
|
||||
in mkShell (nixos-env // {
|
||||
buildInputs = inputs ++ darwin-inputs ++ linux-inputs;
|
||||
|
||||
# Additional Env vars
|
||||
LLVM_SYS_100_PREFIX = "${llvmPkgs.llvm}";
|
||||
APPEND_LIBRARY_PATH = stdenv.lib.makeLibraryPath
|
||||
([ pkg-config llvmPkgs.libcxx llvmPkgs.libcxxabi libunwind ] ++ linux-only);
|
||||
([ pkg-config llvmPkgs.libcxx llvmPkgs.libcxxabi libunwind ] ++ linux-inputs);
|
||||
LD_LIBRARY_PATH = "$LD_LIBRARY_PATH:$APPEND_LIBRARY_PATH";
|
||||
|
||||
# Aliases don't work cross shell, so we do this
|
||||
shellHook = ''
|
||||
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$APPEND_LIBRARY_PATH"
|
||||
export PATH="$PATH:$PWD/nix/bin"
|
||||
'';
|
||||
}
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user