Merge pull request #1623 from rtfeldman/gen-dev/records-base

This commit is contained in:
Richard Feldman 2021-08-25 01:09:19 -04:00 committed by GitHub
commit e07057388b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 1261 additions and 49 deletions

View file

@ -228,6 +228,7 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
fn load_args<'a>( fn load_args<'a>(
_symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>, _symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>,
_args: &'a [(Layout<'a>, Symbol)], _args: &'a [(Layout<'a>, Symbol)],
_ret_layout: &Layout<'a>,
) -> Result<(), String> { ) -> Result<(), String> {
Err("Loading args not yet implemented for AArch64".to_string()) Err("Loading args not yet implemented for AArch64".to_string())
} }
@ -242,6 +243,20 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
) -> Result<u32, String> { ) -> Result<u32, String> {
Err("Storing args not yet implemented for AArch64".to_string()) Err("Storing args not yet implemented for AArch64".to_string())
} }
fn return_struct<'a>(
_buf: &mut Vec<'a, u8>,
_struct_offset: i32,
_struct_size: u32,
_field_layouts: &[Layout<'a>],
_ret_reg: Option<AArch64GeneralReg>,
) -> Result<(), String> {
Err("Returning structs not yet implemented for AArch64".to_string())
}
fn returns_via_arg_pointer(_ret_layout: &Layout) -> Result<bool, String> {
Err("Returning via arg pointer not yet implemented for AArch64".to_string())
}
} }
impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler { impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {

View file

@ -10,6 +10,8 @@ use target_lexicon::Triple;
pub mod aarch64; pub mod aarch64;
pub mod x86_64; pub mod x86_64;
const PTR_SIZE: u32 = 64;
pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> { pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
const GENERAL_PARAM_REGS: &'static [GeneralReg]; const GENERAL_PARAM_REGS: &'static [GeneralReg];
const GENERAL_RETURN_REGS: &'static [GeneralReg]; const GENERAL_RETURN_REGS: &'static [GeneralReg];
@ -49,6 +51,8 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
fn load_args<'a>( fn load_args<'a>(
symbol_map: &mut MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>, symbol_map: &mut MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
// ret_layout is needed because if it is a complex type, we pass a pointer as the first arg.
ret_layout: &Layout<'a>,
) -> Result<(), String>; ) -> Result<(), String>;
// store_args stores the args in registers and on the stack for function calling. // store_args stores the args in registers and on the stack for function calling.
@ -61,6 +65,19 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
// ret_layout is needed because if it is a complex type, we pass a pointer as the first arg. // ret_layout is needed because if it is a complex type, we pass a pointer as the first arg.
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) -> Result<u32, String>; ) -> Result<u32, String>;
// return_struct returns a struct currently on the stack at `struct_offset`.
// It does so using registers and stack as necessary.
fn return_struct<'a>(
buf: &mut Vec<'a, u8>,
struct_offset: i32,
struct_size: u32,
field_layouts: &[Layout<'a>],
ret_reg: Option<GeneralReg>,
) -> Result<(), String>;
// returns true if the layout should be returned via an argument pointer.
fn returns_via_arg_pointer(ret_layout: &Layout) -> Result<bool, String>;
} }
/// Assembler contains calls to the backend assembly generator. /// Assembler contains calls to the backend assembly generator.
@ -160,8 +177,6 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[allow(dead_code)] #[allow(dead_code)]
pub enum SymbolStorage<GeneralReg: RegTrait, FloatReg: RegTrait> { pub enum SymbolStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
// These may need layout, but I am not sure.
// I think whenever a symbol would be used, we specify layout anyways.
GeneralReg(GeneralReg), GeneralReg(GeneralReg),
FloatReg(FloatReg), FloatReg(FloatReg),
Base(i32), Base(i32),
@ -186,7 +201,7 @@ pub struct Backend64Bit<
last_seen_map: MutMap<Symbol, *const Stmt<'a>>, last_seen_map: MutMap<Symbol, *const Stmt<'a>>,
free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>, free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>,
symbols_map: MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>, symbol_storage_map: MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
literal_map: MutMap<Symbol, Literal<'a>>, literal_map: MutMap<Symbol, Literal<'a>>,
// This should probably be smarter than a vec. // This should probably be smarter than a vec.
@ -226,7 +241,7 @@ impl<
relocs: bumpalo::vec!(in env.arena), relocs: bumpalo::vec!(in env.arena),
last_seen_map: MutMap::default(), last_seen_map: MutMap::default(),
free_map: MutMap::default(), free_map: MutMap::default(),
symbols_map: MutMap::default(), symbol_storage_map: MutMap::default(),
literal_map: MutMap::default(), literal_map: MutMap::default(),
general_free_regs: bumpalo::vec![in env.arena], general_free_regs: bumpalo::vec![in env.arena],
general_used_regs: bumpalo::vec![in env.arena], general_used_regs: bumpalo::vec![in env.arena],
@ -248,7 +263,7 @@ impl<
self.fn_call_stack_size = 0; self.fn_call_stack_size = 0;
self.last_seen_map.clear(); self.last_seen_map.clear();
self.free_map.clear(); self.free_map.clear();
self.symbols_map.clear(); self.symbol_storage_map.clear();
self.buf.clear(); self.buf.clear();
self.general_used_callee_saved_regs.clear(); self.general_used_callee_saved_regs.clear();
self.general_free_regs.clear(); self.general_free_regs.clear();
@ -324,10 +339,14 @@ impl<
Ok((out.into_bump_slice(), out_relocs.into_bump_slice())) Ok((out.into_bump_slice(), out_relocs.into_bump_slice()))
} }
fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)]) -> Result<(), String> { fn load_args(
CC::load_args(&mut self.symbols_map, args)?; &mut self,
args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>,
) -> Result<(), String> {
CC::load_args(&mut self.symbol_storage_map, args, ret_layout)?;
// Update used and free regs. // Update used and free regs.
for (sym, storage) in &self.symbols_map { for (sym, storage) in &self.symbol_storage_map {
match storage { match storage {
SymbolStorage::GeneralReg(reg) | SymbolStorage::BaseAndGeneralReg(reg, _) => { SymbolStorage::GeneralReg(reg) | SymbolStorage::BaseAndGeneralReg(reg, _) => {
self.general_free_regs.retain(|r| *r != *reg); self.general_free_regs.retain(|r| *r != *reg);
@ -386,7 +405,7 @@ impl<
// Put values in param regs or on top of the stack. // Put values in param regs or on top of the stack.
let tmp_stack_size = CC::store_args( let tmp_stack_size = CC::store_args(
&mut self.buf, &mut self.buf,
&self.symbols_map, &self.symbol_storage_map,
args, args,
arg_layouts, arg_layouts,
ret_layout, ret_layout,
@ -421,7 +440,7 @@ impl<
_cond_layout: &Layout<'a>, // cond_layout must be a integer due to potential jump table optimizations. _cond_layout: &Layout<'a>, // cond_layout must be a integer due to potential jump table optimizations.
branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)], branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)],
default_branch: &(BranchInfo<'a>, &'a Stmt<'a>), default_branch: &(BranchInfo<'a>, &'a Stmt<'a>),
_ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) -> Result<(), String> { ) -> Result<(), String> {
// Switches are a little complex due to keeping track of jumps. // Switches are a little complex due to keeping track of jumps.
// In general I am trying to not have to loop over things multiple times or waste memory. // In general I am trying to not have to loop over things multiple times or waste memory.
@ -439,7 +458,7 @@ impl<
let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0); let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0);
// Build all statements in this branch. // Build all statements in this branch.
self.build_stmt(stmt)?; self.build_stmt(stmt, ret_layout)?;
// Build unconditional jump to the end of this switch. // Build unconditional jump to the end of this switch.
// Since we don't know the offset yet, set it to 0 and overwrite later. // Since we don't know the offset yet, set it to 0 and overwrite later.
@ -463,7 +482,7 @@ impl<
} }
let (branch_info, stmt) = default_branch; let (branch_info, stmt) = default_branch;
if let BranchInfo::None = branch_info { if let BranchInfo::None = branch_info {
self.build_stmt(stmt)?; self.build_stmt(stmt, ret_layout)?;
// Update all return jumps to jump past the default case. // Update all return jumps to jump past the default case.
let ret_offset = self.buf.len(); let ret_offset = self.buf.len();
@ -560,6 +579,61 @@ impl<
Ok(()) Ok(())
} }
fn create_struct(
&mut self,
sym: &Symbol,
layout: &Layout<'a>,
fields: &'a [Symbol],
) -> Result<(), String> {
if let Layout::Struct(field_layouts) = layout {
let struct_size = layout.stack_size(PTR_SIZE);
if struct_size > 0 {
let offset = self.increase_stack_size(struct_size)?;
self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(offset));
let mut current_offset = offset;
for (field, field_layout) in fields.iter().zip(field_layouts.iter()) {
self.copy_symbol_to_stack_offset(current_offset, field, field_layout)?;
let field_size = field_layout.stack_size(PTR_SIZE);
current_offset += field_size as i32;
}
} else {
self.symbol_storage_map.insert(*sym, SymbolStorage::Base(0));
}
Ok(())
} else {
// This is a single element struct. Just copy the single field to the stack.
let struct_size = layout.stack_size(PTR_SIZE);
let offset = self.increase_stack_size(struct_size)?;
self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(offset));
self.copy_symbol_to_stack_offset(offset, &fields[0], layout)?;
Ok(())
}
}
fn load_struct_at_index(
&mut self,
sym: &Symbol,
structure: &Symbol,
index: u64,
field_layouts: &'a [Layout<'a>],
) -> Result<(), String> {
if let Some(SymbolStorage::Base(struct_offset)) = self.symbol_storage_map.get(structure) {
let mut data_offset = *struct_offset;
for i in 0..index {
let field_size = field_layouts[i as usize].stack_size(PTR_SIZE);
data_offset += field_size as i32;
}
self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(data_offset));
Ok(())
} else {
Err(format!("unknown struct: {:?}", structure))
}
}
fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String> { fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String> {
match lit { match lit {
Literal::Int(x) => { Literal::Int(x) => {
@ -579,7 +653,7 @@ impl<
} }
fn free_symbol(&mut self, sym: &Symbol) { fn free_symbol(&mut self, sym: &Symbol) {
self.symbols_map.remove(sym); self.symbol_storage_map.remove(sym);
for i in 0..self.general_used_regs.len() { for i in 0..self.general_used_regs.len() {
let (reg, saved_sym) = self.general_used_regs[i]; let (reg, saved_sym) = self.general_used_regs[i];
if saved_sym == *sym { if saved_sym == *sym {
@ -590,8 +664,8 @@ impl<
} }
} }
fn return_symbol(&mut self, sym: &Symbol) -> Result<(), String> { fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String> {
let val = self.symbols_map.get(sym); let val = self.symbol_storage_map.get(sym);
match val { match val {
Some(SymbolStorage::GeneralReg(reg)) if *reg == CC::GENERAL_RETURN_REGS[0] => Ok(()), Some(SymbolStorage::GeneralReg(reg)) if *reg == CC::GENERAL_RETURN_REGS[0] => Ok(()),
Some(SymbolStorage::GeneralReg(reg)) => { Some(SymbolStorage::GeneralReg(reg)) => {
@ -605,6 +679,48 @@ impl<
ASM::mov_freg64_freg64(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *reg); ASM::mov_freg64_freg64(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *reg);
Ok(()) Ok(())
} }
Some(SymbolStorage::Base(offset)) => match layout {
Layout::Builtin(Builtin::Int64) => {
ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset);
Ok(())
}
Layout::Builtin(Builtin::Float64) => {
ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset);
Ok(())
}
Layout::Struct(field_layouts) => {
let struct_size = layout.stack_size(PTR_SIZE);
if struct_size > 0 {
let struct_offset = if let Some(SymbolStorage::Base(offset)) =
self.symbol_storage_map.get(sym)
{
Ok(*offset)
} else {
Err(format!("unknown struct: {:?}", sym))
}?;
let ret_reg = if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER)
{
Some(self.load_to_general_reg(&Symbol::RET_POINTER)?)
} else {
None
};
CC::return_struct(
&mut self.buf,
struct_offset,
struct_size,
field_layouts,
ret_reg,
)
} else {
// Nothing to do for empty struct
Ok(())
}
}
x => Err(format!(
"returning symbol with layout, {:?}, is not yet implemented",
x
)),
},
Some(x) => Err(format!( Some(x) => Err(format!(
"returning symbol storage, {:?}, is not yet implemented", "returning symbol storage, {:?}, is not yet implemented",
x x
@ -624,6 +740,25 @@ impl<
CC: CallConv<GeneralReg, FloatReg>, CC: CallConv<GeneralReg, FloatReg>,
> Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC> > Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC>
{ {
fn get_tmp_general_reg(&mut self) -> Result<GeneralReg, String> {
if !self.general_free_regs.is_empty() {
let free_reg = *self
.general_free_regs
.get(self.general_free_regs.len() - 1)
.unwrap();
if CC::general_callee_saved(&free_reg) {
self.general_used_callee_saved_regs.insert(free_reg);
}
Ok(free_reg)
} else if !self.general_used_regs.is_empty() {
let (reg, sym) = self.general_used_regs.remove(0);
self.free_to_stack(&sym)?;
Ok(reg)
} else {
Err("completely out of general purpose registers".to_string())
}
}
fn claim_general_reg(&mut self, sym: &Symbol) -> Result<GeneralReg, String> { fn claim_general_reg(&mut self, sym: &Symbol) -> Result<GeneralReg, String> {
let reg = if !self.general_free_regs.is_empty() { let reg = if !self.general_free_regs.is_empty() {
let free_reg = self.general_free_regs.pop().unwrap(); let free_reg = self.general_free_regs.pop().unwrap();
@ -640,7 +775,7 @@ impl<
}?; }?;
self.general_used_regs.push((reg, *sym)); self.general_used_regs.push((reg, *sym));
self.symbols_map self.symbol_storage_map
.insert(*sym, SymbolStorage::GeneralReg(reg)); .insert(*sym, SymbolStorage::GeneralReg(reg));
Ok(reg) Ok(reg)
} }
@ -661,27 +796,28 @@ impl<
}?; }?;
self.float_used_regs.push((reg, *sym)); self.float_used_regs.push((reg, *sym));
self.symbols_map.insert(*sym, SymbolStorage::FloatReg(reg)); self.symbol_storage_map
.insert(*sym, SymbolStorage::FloatReg(reg));
Ok(reg) Ok(reg)
} }
fn load_to_general_reg(&mut self, sym: &Symbol) -> Result<GeneralReg, String> { fn load_to_general_reg(&mut self, sym: &Symbol) -> Result<GeneralReg, String> {
let val = self.symbols_map.remove(sym); let val = self.symbol_storage_map.remove(sym);
match val { match val {
Some(SymbolStorage::GeneralReg(reg)) => { Some(SymbolStorage::GeneralReg(reg)) => {
self.symbols_map self.symbol_storage_map
.insert(*sym, SymbolStorage::GeneralReg(reg)); .insert(*sym, SymbolStorage::GeneralReg(reg));
Ok(reg) Ok(reg)
} }
Some(SymbolStorage::Base(offset)) => { Some(SymbolStorage::Base(offset)) => {
let reg = self.claim_general_reg(sym)?; let reg = self.claim_general_reg(sym)?;
self.symbols_map self.symbol_storage_map
.insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset)); .insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset));
ASM::mov_reg64_base32(&mut self.buf, reg, offset as i32); ASM::mov_reg64_base32(&mut self.buf, reg, offset as i32);
Ok(reg) Ok(reg)
} }
Some(SymbolStorage::BaseAndGeneralReg(reg, offset)) => { Some(SymbolStorage::BaseAndGeneralReg(reg, offset)) => {
self.symbols_map self.symbol_storage_map
.insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset)); .insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset));
Ok(reg) Ok(reg)
} }
@ -693,21 +829,22 @@ impl<
} }
fn load_to_float_reg(&mut self, sym: &Symbol) -> Result<FloatReg, String> { fn load_to_float_reg(&mut self, sym: &Symbol) -> Result<FloatReg, String> {
let val = self.symbols_map.remove(sym); let val = self.symbol_storage_map.remove(sym);
match val { match val {
Some(SymbolStorage::FloatReg(reg)) => { Some(SymbolStorage::FloatReg(reg)) => {
self.symbols_map.insert(*sym, SymbolStorage::FloatReg(reg)); self.symbol_storage_map
.insert(*sym, SymbolStorage::FloatReg(reg));
Ok(reg) Ok(reg)
} }
Some(SymbolStorage::Base(offset)) => { Some(SymbolStorage::Base(offset)) => {
let reg = self.claim_float_reg(sym)?; let reg = self.claim_float_reg(sym)?;
self.symbols_map self.symbol_storage_map
.insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset)); .insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset));
ASM::mov_freg64_base32(&mut self.buf, reg, offset as i32); ASM::mov_freg64_base32(&mut self.buf, reg, offset as i32);
Ok(reg) Ok(reg)
} }
Some(SymbolStorage::BaseAndFloatReg(reg, offset)) => { Some(SymbolStorage::BaseAndFloatReg(reg, offset)) => {
self.symbols_map self.symbol_storage_map
.insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset)); .insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset));
Ok(reg) Ok(reg)
} }
@ -719,54 +856,94 @@ impl<
} }
fn free_to_stack(&mut self, sym: &Symbol) -> Result<(), String> { fn free_to_stack(&mut self, sym: &Symbol) -> Result<(), String> {
let val = self.symbols_map.remove(sym); let val = self.symbol_storage_map.remove(sym);
match val { match val {
Some(SymbolStorage::GeneralReg(reg)) => { Some(SymbolStorage::GeneralReg(reg)) => {
let offset = self.increase_stack_size(8)? as i32; let offset = self.increase_stack_size(8)?;
// For base addresssing, use the negative offset - 8. // For base addresssing, use the negative offset - 8.
ASM::mov_base32_reg64(&mut self.buf, -offset - 8, reg); ASM::mov_base32_reg64(&mut self.buf, offset, reg);
self.symbols_map self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(-offset - 8)); .insert(*sym, SymbolStorage::Base(offset));
Ok(()) Ok(())
} }
Some(SymbolStorage::FloatReg(reg)) => { Some(SymbolStorage::FloatReg(reg)) => {
let offset = self.increase_stack_size(8)? as i32; let offset = self.increase_stack_size(8)?;
// For base addresssing, use the negative offset. // For base addresssing, use the negative offset.
ASM::mov_base32_freg64(&mut self.buf, -offset - 8, reg); ASM::mov_base32_freg64(&mut self.buf, offset, reg);
self.symbols_map self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(-offset - 8)); .insert(*sym, SymbolStorage::Base(offset));
Ok(()) Ok(())
} }
Some(SymbolStorage::Base(offset)) => { Some(SymbolStorage::Base(offset)) => {
self.symbols_map.insert(*sym, SymbolStorage::Base(offset)); self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(offset));
Ok(()) Ok(())
} }
Some(SymbolStorage::BaseAndGeneralReg(_, offset)) => { Some(SymbolStorage::BaseAndGeneralReg(_, offset)) => {
self.symbols_map.insert(*sym, SymbolStorage::Base(offset)); self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(offset));
Ok(()) Ok(())
} }
Some(SymbolStorage::BaseAndFloatReg(_, offset)) => { Some(SymbolStorage::BaseAndFloatReg(_, offset)) => {
self.symbols_map.insert(*sym, SymbolStorage::Base(offset)); self.symbol_storage_map
.insert(*sym, SymbolStorage::Base(offset));
Ok(()) Ok(())
} }
None => Err(format!("Unknown symbol: {}", sym)), None => Err(format!("Unknown symbol: {}", sym)),
} }
} }
/// increase_stack_size increase the current stack size and returns the offset of the stack. /// increase_stack_size increase the current stack size `amount` bytes.
fn increase_stack_size(&mut self, amount: u32) -> Result<u32, String> { /// It returns base pointer relative offset of the new data.
fn increase_stack_size(&mut self, amount: u32) -> Result<i32, String> {
debug_assert!(amount > 0); debug_assert!(amount > 0);
let offset = self.stack_size;
if let Some(new_size) = self.stack_size.checked_add(amount) { if let Some(new_size) = self.stack_size.checked_add(amount) {
// Since stack size is u32, but the max offset is i32, if we pass i32 max, we have overflowed. // Since stack size is u32, but the max offset is i32, if we pass i32 max, we have overflowed.
if new_size > i32::MAX as u32 { if new_size > i32::MAX as u32 {
Err("Ran out of stack space".to_string()) Err("Ran out of stack space".to_string())
} else { } else {
self.stack_size = new_size; self.stack_size = new_size;
let offset = -(self.stack_size as i32);
Ok(offset) Ok(offset)
} }
} else { } else {
Err("Ran out of stack space".to_string()) Err("Ran out of stack space".to_string())
} }
} }
fn copy_symbol_to_stack_offset(
&mut self,
to_offset: i32,
sym: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String> {
match layout {
Layout::Builtin(Builtin::Int64) => {
let reg = self.load_to_general_reg(sym)?;
ASM::mov_base32_reg64(&mut self.buf, to_offset, reg);
Ok(())
}
Layout::Builtin(Builtin::Float64) => {
let reg = self.load_to_float_reg(sym)?;
ASM::mov_base32_freg64(&mut self.buf, to_offset, reg);
Ok(())
}
Layout::Struct(_) if layout.safe_to_memcpy() => {
let tmp_reg = self.get_tmp_general_reg()?;
if let Some(SymbolStorage::Base(from_offset)) = self.symbol_storage_map.get(sym) {
for i in 0..layout.stack_size(PTR_SIZE) as i32 {
ASM::mov_reg64_base32(&mut self.buf, tmp_reg, from_offset + i);
ASM::mov_base32_reg64(&mut self.buf, to_offset + i, tmp_reg);
}
Ok(())
} else {
Err(format!("unknown struct: {:?}", sym))
}
}
x => Err(format!(
"copying data to the stack with layout, {:?}, not implemented yet",
x
)),
}
}
} }

View file

@ -1,4 +1,4 @@
use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage}; use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage, PTR_SIZE};
use crate::Relocation; use crate::Relocation;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
@ -177,10 +177,18 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
fn load_args<'a>( fn load_args<'a>(
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>, symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>,
) -> Result<(), String> { ) -> Result<(), String> {
let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer. let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer.
let mut general_i = 0; let mut general_i = 0;
let mut float_i = 0; let mut float_i = 0;
if X86_64SystemV::returns_via_arg_pointer(ret_layout)? {
symbol_map.insert(
Symbol::RET_POINTER,
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[general_i]),
);
general_i += 1;
}
for (layout, sym) in args.iter() { for (layout, sym) in args.iter() {
match layout { match layout {
Layout::Builtin(Builtin::Int64) => { Layout::Builtin(Builtin::Int64) => {
@ -359,6 +367,22 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
} }
Ok(stack_offset as u32) Ok(stack_offset as u32)
} }
fn return_struct<'a>(
_buf: &mut Vec<'a, u8>,
_struct_offset: i32,
_struct_size: u32,
_field_layouts: &[Layout<'a>],
_ret_reg: Option<X86_64GeneralReg>,
) -> Result<(), String> {
Err("Returning structs not yet implemented for X86_64".to_string())
}
fn returns_via_arg_pointer(ret_layout: &Layout) -> Result<bool, String> {
// TODO: This may need to be more complex/extended to fully support the calling convention.
// details here: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf
Ok(ret_layout.stack_size(PTR_SIZE) > 16)
}
} }
impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall { impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
@ -477,9 +501,18 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
fn load_args<'a>( fn load_args<'a>(
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>, symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>,
) -> Result<(), String> { ) -> Result<(), String> {
let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer. let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer.
for (i, (layout, sym)) in args.iter().enumerate() { let mut i = 0;
if X86_64WindowsFastcall::returns_via_arg_pointer(ret_layout)? {
symbol_map.insert(
Symbol::RET_POINTER,
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]),
);
i += 1;
}
for (layout, sym) in args.iter() {
if i < Self::GENERAL_PARAM_REGS.len() { if i < Self::GENERAL_PARAM_REGS.len() {
match layout { match layout {
Layout::Builtin(Builtin::Int64) => { Layout::Builtin(Builtin::Int64) => {
@ -496,6 +529,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
)); ));
} }
} }
i += 1;
} else { } else {
base_offset += match layout { base_offset += match layout {
Layout::Builtin(Builtin::Int64) => 8, Layout::Builtin(Builtin::Int64) => 8,
@ -653,6 +687,22 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
} }
Ok(stack_offset as u32) Ok(stack_offset as u32)
} }
fn return_struct<'a>(
_buf: &mut Vec<'a, u8>,
_struct_offset: i32,
_struct_size: u32,
_field_layouts: &[Layout<'a>],
_ret_reg: Option<X86_64GeneralReg>,
) -> Result<(), String> {
Err("Returning structs not yet implemented for X86_64WindowsFastCall".to_string())
}
fn returns_via_arg_pointer(ret_layout: &Layout) -> Result<bool, String> {
// TODO: This is not fully correct there are some exceptions for "vector" types.
// details here: https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-160#return-values
Ok(ret_layout.stack_size(PTR_SIZE) > 8)
}
} }
#[inline(always)] #[inline(always)]

View file

@ -66,7 +66,11 @@ where
// load_args is used to let the backend know what the args are. // load_args is used to let the backend know what the args are.
// The backend should track these args so it can use them as needed. // The backend should track these args so it can use them as needed.
fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)]) -> Result<(), String>; fn load_args(
&mut self,
args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>,
) -> Result<(), String>;
/// Used for generating wrappers for malloc/realloc/free /// Used for generating wrappers for malloc/realloc/free
fn build_wrapped_jmp(&mut self) -> Result<(&'a [u8], u64), String>; fn build_wrapped_jmp(&mut self) -> Result<(&'a [u8], u64), String>;
@ -74,29 +78,29 @@ where
/// build_proc creates a procedure and outputs it to the wrapped object writer. /// build_proc creates a procedure and outputs it to the wrapped object writer.
fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> { fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> {
self.reset(); self.reset();
self.load_args(proc.args)?; self.load_args(proc.args, &proc.ret_layout)?;
// let start = std::time::Instant::now(); // let start = std::time::Instant::now();
self.scan_ast(&proc.body); self.scan_ast(&proc.body);
self.create_free_map(); self.create_free_map();
// let duration = start.elapsed(); // let duration = start.elapsed();
// println!("Time to calculate lifetimes: {:?}", duration); // println!("Time to calculate lifetimes: {:?}", duration);
// println!("{:?}", self.last_seen_map()); // println!("{:?}", self.last_seen_map());
self.build_stmt(&proc.body)?; self.build_stmt(&proc.body, &proc.ret_layout)?;
self.finalize() self.finalize()
} }
/// build_stmt builds a statement and outputs at the end of the buffer. /// build_stmt builds a statement and outputs at the end of the buffer.
fn build_stmt(&mut self, stmt: &Stmt<'a>) -> Result<(), String> { fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> {
match stmt { match stmt {
Stmt::Let(sym, expr, layout, following) => { Stmt::Let(sym, expr, layout, following) => {
self.build_expr(sym, expr, layout)?; self.build_expr(sym, expr, layout)?;
self.free_symbols(stmt); self.free_symbols(stmt);
self.build_stmt(following)?; self.build_stmt(following, ret_layout)?;
Ok(()) Ok(())
} }
Stmt::Ret(sym) => { Stmt::Ret(sym) => {
self.load_literal_symbols(&[*sym])?; self.load_literal_symbols(&[*sym])?;
self.return_symbol(sym)?; self.return_symbol(sym, ret_layout)?;
self.free_symbols(stmt); self.free_symbols(stmt);
Ok(()) Ok(())
} }
@ -218,6 +222,15 @@ where
x => Err(format!("the call type, {:?}, is not yet implemented", x)), x => Err(format!("the call type, {:?}, is not yet implemented", x)),
} }
} }
Expr::Struct(fields) => {
self.load_literal_symbols(fields)?;
self.create_struct(sym, layout, fields)
}
Expr::StructAtIndex {
index,
field_layouts,
structure,
} => self.load_struct_at_index(sym, structure, *index, field_layouts),
x => Err(format!("the expression, {:?}, is not yet implemented", x)), x => Err(format!("the expression, {:?}, is not yet implemented", x)),
} }
} }
@ -377,11 +390,28 @@ where
Ok(()) Ok(())
} }
/// create_struct creates a struct with the elements specified loaded into it as data.
fn create_struct(
&mut self,
sym: &Symbol,
layout: &Layout<'a>,
fields: &'a [Symbol],
) -> Result<(), String>;
/// load_struct_at_index loads into `sym` the value at `index` in `structure`.
fn load_struct_at_index(
&mut self,
sym: &Symbol,
structure: &Symbol,
index: u64,
field_layouts: &'a [Layout<'a>],
) -> Result<(), String>;
/// load_literal sets a symbol to be equal to a literal. /// load_literal sets a symbol to be equal to a literal.
fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String>; fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String>;
/// return_symbol moves a symbol to the correct return location for the backend. /// return_symbol moves a symbol to the correct return location for the backend.
fn return_symbol(&mut self, sym: &Symbol) -> Result<(), String>; fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String>;
/// free_symbols will free all symbols for the given statement. /// free_symbols will free all symbols for the given statement.
fn free_symbols(&mut self, stmt: &Stmt<'a>) { fn free_symbols(&mut self, stmt: &Stmt<'a>) {

View file

@ -0,0 +1,937 @@
#[macro_use]
extern crate indoc;
#[macro_use]
mod helpers;
#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
mod dev_records {
#[test]
fn basic_record() {
assert_evals_to!(
indoc!(
r#"
{ y: 17, x: 15, z: 19 }.x
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: 17, z: 19 }.y
"#
),
17,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: 17, z: 19 }.z
"#
),
19,
i64
);
}
#[test]
fn nested_record() {
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.x
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.a
"#
),
12,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.b
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.c
"#
),
2,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.z
"#
),
19,
i64
);
}
#[test]
fn f64_record() {
assert_evals_to!(
indoc!(
r#"
rec = { y: 17.2, x: 15.1, z: 19.3 }
rec.x
"#
),
15.1,
f64
);
assert_evals_to!(
indoc!(
r#"
rec = { y: 17.2, x: 15.1, z: 19.3 }
rec.y
"#
),
17.2,
f64
);
assert_evals_to!(
indoc!(
r#"
rec = { y: 17.2, x: 15.1, z: 19.3 }
rec.z
"#
),
19.3,
f64
);
}
// #[test]
// fn fn_record() {
// assert_evals_to!(
// indoc!(
// r#"
// getRec = \x -> { y: 17, x, z: 19 }
// (getRec 15).x
// "#
// ),
// 15,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.y
// "#
// ),
// 17,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.z
// "#
// ),
// 19,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.z + rec.x
// "#
// ),
// 34,
// i64
// );
// }
#[test]
fn def_record() {
assert_evals_to!(
indoc!(
r#"
rec = { y: 17, x: 15, z: 19 }
rec.x
"#
),
15,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = { x: 15, y: 17, z: 19 }
rec.y
"#
),
17,
i64
);
assert_evals_to!(
indoc!(
r#"
rec = { x: 15, y: 17, z: 19 }
rec.z
"#
),
19,
i64
);
}
#[test]
fn when_on_record() {
assert_evals_to!(
indoc!(
r#"
when { x: 0x2 } is
{ x } -> x + 3
"#
),
5,
i64
);
}
#[test]
fn when_record_with_guard_pattern() {
assert_evals_to!(
indoc!(
r#"
when { x: 0x2, y: 3.14 } is
{ x: var } -> var + 3
"#
),
5,
i64
);
}
#[test]
fn let_with_record_pattern() {
assert_evals_to!(
indoc!(
r#"
{ x } = { x: 0x2, y: 3.14 }
x
"#
),
2,
i64
);
}
#[test]
fn record_guard_pattern() {
assert_evals_to!(
indoc!(
r#"
when { x: 0x2, y: 3.14 } is
{ x: 0x4 } -> 5
{ x } -> x + 3
"#
),
5,
i64
);
}
#[test]
fn twice_record_access() {
assert_evals_to!(
indoc!(
r#"
x = {a: 0x2, b: 0x3 }
x.a + x.b
"#
),
5,
i64
);
}
#[test]
fn empty_record() {
assert_evals_to!(
indoc!(
r#"
v = {}
v
"#
),
(),
()
);
}
#[test]
fn i64_record1_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3 }
"#
),
3,
i64
);
}
// #[test]
// fn i64_record2_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3, y: 5 }
// "#
// ),
// (3, 5),
// (i64, i64)
// );
// }
// // #[test]
// // fn i64_record3_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { x: 3, y: 5, z: 17 }
// // "#
// // ),
// // (3, 5, 17),
// // (i64, i64, i64)
// // );
// // }
// #[test]
// fn f64_record2_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3.1, y: 5.1 }
// "#
// ),
// (3.1, 5.1),
// (f64, f64)
// );
// }
// // #[test]
// // fn f64_record3_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { x: 3.1, y: 5.1, z: 17.1 }
// // "#
// // ),
// // (3.1, 5.1, 17.1),
// // (f64, f64, f64)
// // );
// // }
// // #[test]
// // fn bool_record4_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // record : { a : Bool, b : Bool, c : Bool, d : Bool }
// // record = { a: True, b: True, c : True, d : Bool }
// // record
// // "#
// // ),
// // (true, false, false, true),
// // (bool, bool, bool, bool)
// // );
// // }
// #[test]
// fn i64_record1_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3 }
// "#
// ),
// 3,
// i64
// );
// }
// // #[test]
// // fn i64_record9_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 }
// // "#
// // ),
// // (3, 5, 17, 1, 9, 12, 13, 14, 15),
// // (i64, i64, i64, i64, i64, i64, i64, i64, i64)
// // );
// // }
// // #[test]
// // fn f64_record3_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { x: 3.1, y: 5.1, z: 17.1 }
// // "#
// // ),
// // (3.1, 5.1, 17.1),
// // (f64, f64, f64)
// // );
// // }
// #[test]
// fn bool_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// x : Bool
// x = True
// x
// "#
// ),
// true,
// bool
// );
// }
// #[test]
// fn optional_field_when_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// main =
// a = f { x: Blue, y: 7 }
// b = f { x: Blue }
// c = f { x: Red, y: 11 }
// d = f { x: Red }
// a * b * c * d
// "#
// ),
// 3 * 5 * 7 * 11,
// i64
// );
// }
// #[test]
// fn optional_field_when_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// a = f { x: Blue, y: 7 }
// b = f { x: Blue }
// c = f { x: Red, y: 11 }
// d = f { x: Red }
// a * b * c * d
// "#
// ),
// 3 * 5 * 7 * 11,
// i64
// );
// }
// #[test]
// fn optional_field_when_no_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_when_no_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// { x ? 10, y } = r
// x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_let_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { y: 9 }
// "#
// ),
// 19,
// i64
// );
// }
// #[test]
// fn optional_field_let_no_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_let_no_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// { x ? 10, y } = r
// x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_function_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \{ x ? 10, y } -> x + y
// f { y: 9 }
// "#
// ),
// 19,
// i64
// );
// }
// #[test]
// #[ignore]
// fn optional_field_function_no_use_default() {
// // blocked on https://github.com/rtfeldman/roc/issues/786
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \{ x ? 10, y } -> x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// #[ignore]
// fn optional_field_function_no_use_default_nested() {
// // blocked on https://github.com/rtfeldman/roc/issues/786
// assert_evals_to!(
// indoc!(
// r#"
// f = \{ x ? 10, y } -> x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_singleton_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { x : 4 } is
// { x ? 3 } -> x
// "#
// ),
// 4,
// i64
// );
// }
// #[test]
// fn optional_field_empty_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { } is
// { x ? 3 } -> x
// "#
// ),
// 3,
// i64
// );
// }
// #[test]
// fn return_record_2() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3, y: 5 }
// "#
// ),
// [3, 5],
// [i64; 2]
// );
// }
// #[test]
// fn return_record_3() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3, y: 5, z: 4 }
// "#
// ),
// (3, 5, 4),
// (i64, i64, i64)
// );
// }
// #[test]
// fn return_record_4() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 4, d: 2 }
// "#
// ),
// [3, 5, 4, 2],
// [i64; 4]
// );
// }
// #[test]
// fn return_record_5() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1 }
// "#
// ),
// [3, 5, 4, 2, 1],
// [i64; 5]
// );
// }
// #[test]
// fn return_record_6() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 }
// "#
// ),
// [3, 5, 4, 2, 1, 7],
// [i64; 6]
// );
// }
// #[test]
// fn return_record_7() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 }
// "#
// ),
// [3, 5, 4, 2, 1, 7, 8],
// [i64; 7]
// );
// }
// #[test]
// fn return_record_float_int() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 3.14, b: 0x1 }
// "#
// ),
// (3.14, 0x1),
// (f64, i64)
// );
// }
// #[test]
// fn return_record_int_float() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 0x1, b: 3.14 }
// "#
// ),
// (0x1, 3.14),
// (i64, f64)
// );
// }
// #[test]
// fn return_record_float_float() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 6.28, b: 3.14 }
// "#
// ),
// (6.28, 3.14),
// (f64, f64)
// );
// }
// #[test]
// fn return_record_float_float_float() {
// assert_evals_to!(
// indoc!(
// r#"
// { a: 6.28, b: 3.14, c: 0.1 }
// "#
// ),
// (6.28, 3.14, 0.1),
// (f64, f64, f64)
// );
// }
// #[test]
// fn return_nested_record() {
// assert_evals_to!(
// indoc!(
// r#"
// { flag: 0x0, payload: { a: 6.28, b: 3.14, c: 0.1 } }
// "#
// ),
// (0x0, (6.28, 3.14, 0.1)),
// (i64, (f64, f64, f64))
// );
// }
// #[test]
// fn accessor() {
// assert_evals_to!(
// indoc!(
// r#"
// .foo { foo: 4 } + .foo { bar: 6.28, foo: 3 }
// "#
// ),
// 7,
// i64
// );
// }
// #[test]
// fn accessor_single_element_record() {
// assert_evals_to!(
// indoc!(
// r#"
// .foo { foo: 4 }
// "#
// ),
// 4,
// i64
// );
// }
// #[test]
// fn update_record() {
// assert_evals_to!(
// indoc!(
// r#"
// rec = { foo: 42, bar: 6 }
// { rec & foo: rec.foo + 1 }
// "#
// ),
// (6, 43),
// (i64, i64)
// );
// }
#[test]
fn update_single_element_record() {
assert_evals_to!(
indoc!(
r#"
rec = { foo: 42}
{ rec & foo: rec.foo + 1 }
"#
),
43,
i64
);
}
// #[test]
// fn booleans_in_record() {
// assert_evals_to!(
// indoc!("{ x: 1 == 1, y: 1 == 1 }"),
// (true, true),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 != 1, y: 1 == 1 }"),
// (false, true),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 == 1, y: 1 != 1 }"),
// (true, false),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 != 1, y: 1 != 1 }"),
// (false, false),
// (bool, bool)
// );
// }
// #[test]
// fn alignment_in_record() {
// assert_evals_to!(
// indoc!("{ c: 32, b: if True then Red else if True then Green else Blue, a: 1 == 1 }"),
// (32i64, true, 2u8),
// (i64, bool, u8)
// );
// }
// #[test]
// fn blue_and_present() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// f { x: Blue, y: 7 }
// "#
// ),
// 7,
// i64
// );
// }
// #[test]
// fn blue_and_absent() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// f { x: Blue }
// "#
// ),
// 3,
// i64
// );
// }
}

View file

@ -786,6 +786,9 @@ define_builtins! {
// used to initialize parameters in borrow.rs // used to initialize parameters in borrow.rs
22 EMPTY_PARAM: "#empty_param" 22 EMPTY_PARAM: "#empty_param"
// used by the dev backend to store the pointer to where to store large return types
23 RET_POINTER: "#ret_pointer"
} }
1 NUM: "Num" => { 1 NUM: "Num" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias 0 NUM_NUM: "Num" imported // the Num.Num type alias