Returning records on the stack from Wasm dev backend!

This commit is contained in:
Brian Carroll 2021-09-28 08:06:59 +01:00
parent 39fda3e675
commit 02bb9028ef
5 changed files with 504 additions and 410 deletions

View File

@ -11,6 +11,7 @@ use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
use roc_mono::layout::{Builtin, Layout};
use crate::layout::WasmLayout;
use crate::storage::SymbolStorage;
use crate::{allocate_stack_frame, copy_memory, free_stack_frame, LocalId, PTR_TYPE};
// Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone.
@ -20,54 +21,6 @@ const UNUSED_DATA_SECTION_BYTES: u32 = 1024;
#[derive(Clone, Copy, Debug)]
struct LabelId(u32);
#[derive(Debug)]
enum SymbolStorage {
ParamPrimitive {
local_id: LocalId,
value_type: ValueType,
size: u32,
},
ParamPointer {
local_id: LocalId,
},
VarPrimitive {
local_id: LocalId,
value_type: ValueType,
size: u32,
},
VarStackMemory {
local_id: LocalId,
size: u32,
offset: u32,
},
VarHeapMemory {
local_id: LocalId,
},
}
impl SymbolStorage {
fn local_id(&self) -> LocalId {
match self {
Self::ParamPrimitive { local_id, .. } => *local_id,
Self::ParamPointer { local_id, .. } => *local_id,
Self::VarPrimitive { local_id, .. } => *local_id,
Self::VarStackMemory { local_id, .. } => *local_id,
Self::VarHeapMemory { local_id, .. } => *local_id,
}
}
#[allow(dead_code)]
fn value_type(&self) -> ValueType {
match self {
Self::ParamPrimitive { value_type, .. } => *value_type,
Self::VarPrimitive { value_type, .. } => *value_type,
Self::ParamPointer { .. } => ValueType::I32,
Self::VarStackMemory { .. } => ValueType::I32,
Self::VarHeapMemory { .. } => ValueType::I32,
}
}
}
enum LocalKind {
Parameter,
Variable,
@ -205,7 +158,7 @@ impl<'a> WasmBackend<'a> {
wasm_layout: WasmLayout,
symbol: Symbol,
kind: LocalKind,
) -> LocalId {
) -> SymbolStorage {
let local_index = (self.arg_types.len() + self.locals.len()) as u32;
let local_id = LocalId(local_index);
@ -219,7 +172,10 @@ impl<'a> WasmBackend<'a> {
value_type,
size,
},
_ => SymbolStorage::ParamPointer { local_id },
_ => SymbolStorage::ParamPointer {
local_id,
wasm_layout,
},
}
}
LocalKind::Variable => {
@ -242,11 +198,13 @@ impl<'a> WasmBackend<'a> {
let mut offset = self.stack_memory;
offset += align - 1;
offset &= -align;
self.stack_memory = offset + (size - alignment_bytes) as i32;
self.stack_memory = offset + size as i32;
// TODO: if we're creating the frame pointer just reuse the same local_id!
let frame_pointer = self.get_or_create_frame_pointer();
// initialise the local with the appropriate address
// TODO: skip this the first time, no point adding zero offset!
self.instructions.extend([
GetLocal(frame_pointer.0),
I32Const(offset),
@ -258,15 +216,16 @@ impl<'a> WasmBackend<'a> {
local_id,
size,
offset: offset as u32,
alignment_bytes,
}
}
}
}
};
self.symbol_storage_map.insert(symbol, storage);
self.symbol_storage_map.insert(symbol, storage.clone());
local_id
storage
}
fn get_or_create_frame_pointer(&mut self) -> LocalId {
@ -334,17 +293,20 @@ impl<'a> WasmBackend<'a> {
// Saves us from having to copy it later
let storage = SymbolStorage::ParamPointer {
local_id: LocalId(0),
wasm_layout,
};
self.symbol_storage_map.insert(*let_sym, storage);
}
self.build_expr(let_sym, expr, layout)?;
self.instructions.push(Return);
self.instructions.push(Return); // TODO: branch instead of return so we can clean up stack
Ok(())
}
Stmt::Let(sym, expr, layout, following) => {
let wasm_layout = WasmLayout::new(layout);
let local_id = self.insert_local(wasm_layout, *sym, LocalKind::Variable);
let local_id = self
.insert_local(wasm_layout, *sym, LocalKind::Variable)
.local_id();
self.build_expr(sym, expr, layout)?;
self.instructions.push(SetLocal(local_id.0));
@ -354,35 +316,37 @@ impl<'a> WasmBackend<'a> {
}
Stmt::Ret(sym) => {
use crate::layout::WasmLayout::*;
use crate::storage::SymbolStorage::*;
let storage = self.symbol_storage_map.get(sym).unwrap();
match storage {
SymbolStorage::ParamPrimitive { local_id, .. }
| SymbolStorage::VarPrimitive { local_id, .. }
| SymbolStorage::ParamPointer { local_id, .. }
| SymbolStorage::VarHeapMemory { local_id, .. } => {
self.instructions.push(GetLocal(local_id.0));
self.instructions.push(Return);
VarStackMemory {
local_id,
size,
alignment_bytes,
..
}
| ParamPointer {
local_id,
wasm_layout:
WasmLayout::StackMemory {
size,
alignment_bytes,
..
},
} => {
let from = local_id.clone();
let to = LocalId(0);
copy_memory(&mut self.instructions, from, to, *size, *alignment_bytes, 0)?;
}
SymbolStorage::VarStackMemory { local_id, size, .. } => {
let ret_wasm_layout = WasmLayout::new(ret_layout);
if let StackMemory { alignment_bytes, .. } = ret_wasm_layout {
let from = local_id.clone();
let to = LocalId(0);
let copy_size: u32 = *size;
copy_memory(
&mut self.instructions,
from,
to,
copy_size,
alignment_bytes,
)?;
} else {
panic!("Return layout doesn't match");
}
ParamPrimitive { local_id, .. }
| VarPrimitive { local_id, .. }
| ParamPointer { local_id, .. }
| VarHeapMemory { local_id, .. } => {
self.instructions.push(GetLocal(local_id.0));
self.instructions.push(Return); // TODO: branch instead of return so we can clean up stack
}
}
@ -446,8 +410,9 @@ impl<'a> WasmBackend<'a> {
let mut jp_parameter_local_ids = std::vec::Vec::with_capacity(parameters.len());
for parameter in parameters.iter() {
let wasm_layout = WasmLayout::new(&parameter.layout);
let local_id =
self.insert_local(wasm_layout, parameter.symbol, LocalKind::Variable);
let local_id = self
.insert_local(wasm_layout, parameter.symbol, LocalKind::Variable)
.local_id();
jp_parameter_local_ids.push(local_id);
}
@ -524,6 +489,8 @@ impl<'a> WasmBackend<'a> {
x => Err(format!("the call type, {:?}, is not yet implemented", x)),
},
Expr::Struct(fields) => self.create_struct(sym, layout, fields),
x => Err(format!("Expression is not yet implemented {:?}", x)),
}
}
@ -560,6 +527,78 @@ impl<'a> WasmBackend<'a> {
Ok(())
}
fn create_struct(
&mut self,
sym: &Symbol,
layout: &Layout<'a>,
fields: &'a [Symbol],
) -> Result<(), String> {
let storage = self.get_symbol_storage(sym)?.to_owned();
if let Layout::Struct(field_layouts) = layout {
match storage {
SymbolStorage::VarStackMemory { local_id, size, .. }
| SymbolStorage::ParamPointer {
local_id,
wasm_layout: WasmLayout::StackMemory { size, .. },
} => {
if size > 0 {
let mut relative_offset = 0;
for (field, _) in fields.iter().zip(field_layouts.iter()) {
relative_offset += self.copy_symbol_to_pointer_at_offset(
local_id,
relative_offset,
field,
)?;
}
} else {
return Err(format!("Not supported yet: zero-size struct at {:?}", sym));
}
}
_ => {
return Err(format!("Cannot create struct {:?} with storage {:?}", sym, storage));
}
}
} else {
// Struct expression but not Struct layout => single element. Copy it.
let field_storage = self.get_symbol_storage(&fields[0])?.to_owned();
self.copy_storage(&storage, &field_storage)?;
}
Ok(())
}
fn copy_symbol_to_pointer_at_offset(
&mut self,
to_ptr: LocalId,
to_offset: u32,
from_symbol: &Symbol,
) -> Result<u32, String> {
let from_storage = self.get_symbol_storage(from_symbol)?.to_owned();
from_storage.copy_to_memory(&mut self.instructions, to_ptr, to_offset)
}
fn copy_storage(&mut self, to: &SymbolStorage, from: &SymbolStorage) -> Result<(), String> {
let has_stack_memory = to.has_stack_memory();
debug_assert!(from.has_stack_memory() == has_stack_memory);
if !has_stack_memory {
debug_assert!(from.value_type() == to.value_type());
self.instructions.push(GetLocal(from.local_id().0));
self.instructions.push(SetLocal(to.local_id().0));
Ok(())
} else {
let (size, alignment_bytes) = from.stack_size_and_alignment();
copy_memory(
&mut self.instructions,
from.local_id(),
to.local_id(),
size,
alignment_bytes,
0,
)
}
}
fn build_call_low_level(
&mut self,
lowlevel: &LowLevel,

View File

@ -1,10 +1,10 @@
use parity_wasm::elements::{Instruction, Instruction::*, ValueType};
use parity_wasm::elements::ValueType;
use roc_mono::layout::{Layout, UnionLayout};
use crate::{copy_memory, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8, PTR_SIZE, PTR_TYPE};
use crate::{PTR_SIZE, PTR_TYPE};
// See README for background information on Wasm locals, memory and function calls
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum WasmLayout {
// Primitive number value. Just a Wasm local, without any stack memory.
// For example, Roc i8 is represented as Wasm i32. Store the type and the original size.
@ -79,81 +79,4 @@ impl WasmLayout {
_ => 0,
}
}
#[allow(dead_code)]
fn load(&self, offset: u32) -> Result<Instruction, String> {
use crate::layout::WasmLayout::*;
use ValueType::*;
match self {
LocalOnly(I32, 4) => Ok(I32Load(ALIGN_4, offset)),
LocalOnly(I32, 2) => Ok(I32Load16S(ALIGN_2, offset)),
LocalOnly(I32, 1) => Ok(I32Load8S(ALIGN_1, offset)),
LocalOnly(I64, 8) => Ok(I64Load(ALIGN_8, offset)),
LocalOnly(F64, 8) => Ok(F64Load(ALIGN_8, offset)),
LocalOnly(F32, 4) => Ok(F32Load(ALIGN_4, offset)),
// TODO: Come back to this when we need to access fields of structs
// LocalOnly(F32, 2) => Ok(), // convert F16 to F32 (lowlevel function? Wasm-only?)
// StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR?
HeapMemory => {
if PTR_TYPE == I64 {
Ok(I64Load(ALIGN_8, offset))
} else {
Ok(I32Load(ALIGN_4, offset))
}
}
_ => Err(format!(
"Failed to generate load instruction for WasmLayout {:?}",
self
)),
}
}
#[allow(dead_code)]
pub fn store(&self, offset: u32, instructions: &mut Vec<Instruction>) -> Result<(), String> {
use crate::layout::WasmLayout::*;
use ValueType::*;
let mut result = Ok(());
match self {
LocalOnly(I32, 4) => instructions.push(I32Store(ALIGN_4, offset)),
LocalOnly(I32, 2) => instructions.push(I32Store16(ALIGN_2, offset)),
LocalOnly(I32, 1) => instructions.push(I32Store8(ALIGN_1, offset)),
LocalOnly(I64, 8) => instructions.push(I64Store(ALIGN_8, offset)),
LocalOnly(F64, 8) => instructions.push(F64Store(ALIGN_8, offset)),
LocalOnly(F32, 4) => instructions.push(F32Store(ALIGN_4, offset)),
StackMemory {
size,
alignment_bytes,
} => {
// TODO
// Need extra arguments for this case that we don't need for primitives.
// Maybe it should be somewhere we have more relevant context?
// Come back to it when we need to insert things into structs.
let from_ptr = LocalId(0); // TODO
let to_ptr = LocalId(0); // TODO
copy_memory(instructions, from_ptr, to_ptr, *size, *alignment_bytes)?;
}
HeapMemory => {
if PTR_TYPE == I64 {
instructions.push(I64Store(ALIGN_8, offset));
} else {
instructions.push(I32Store(ALIGN_4, offset));
}
}
_ => {
result = Err(format!(
"Failed to generate store instruction for WasmLayout {:?}",
self
));
}
}
result
}
}

View File

@ -1,6 +1,7 @@
mod backend;
pub mod from_wasm32_memory;
mod layout;
mod storage;
use bumpalo::Bump;
use parity_wasm::builder;
@ -110,13 +111,13 @@ pub fn build_module_help<'a>(
Ok((backend.builder, main_function_index))
}
fn encode_alignment(bytes: u32) -> Result<u32, String> {
fn encode_alignment(bytes: u32) -> u32 {
match bytes {
1 => Ok(ALIGN_1),
2 => Ok(ALIGN_2),
4 => Ok(ALIGN_4),
8 => Ok(ALIGN_8),
_ => Err(format!("{:?}-byte alignment is not supported", bytes)),
1 => ALIGN_1,
2 => ALIGN_2,
4 => ALIGN_4,
8 => ALIGN_8,
_ => panic!("{:?}-byte alignment is not supported", bytes),
}
}
@ -124,32 +125,32 @@ fn copy_memory(
instructions: &mut Vec<Instruction>,
from_ptr: LocalId,
to_ptr: LocalId,
size_with_alignment: u32,
size: u32,
alignment_bytes: u32,
offset: u32,
) -> Result<(), String> {
let alignment_flag = encode_alignment(alignment_bytes)?;
let size = size_with_alignment - alignment_bytes;
let mut offset = 0;
while size - offset >= 8 {
let alignment_flag = encode_alignment(alignment_bytes);
let mut current_offset = offset;
while size - current_offset >= 8 {
instructions.push(GetLocal(to_ptr.0));
instructions.push(GetLocal(from_ptr.0));
instructions.push(I64Load(alignment_flag, offset));
instructions.push(I64Store(alignment_flag, offset));
offset += 8;
instructions.push(I64Load(alignment_flag, current_offset));
instructions.push(I64Store(alignment_flag, current_offset));
current_offset += 8;
}
if size - offset >= 4 {
if size - current_offset >= 4 {
instructions.push(GetLocal(to_ptr.0));
instructions.push(GetLocal(from_ptr.0));
instructions.push(I32Load(alignment_flag, offset));
instructions.push(I32Store(alignment_flag, offset));
offset += 4;
instructions.push(I32Load(alignment_flag, current_offset));
instructions.push(I32Store(alignment_flag, current_offset));
current_offset += 4;
}
while size - offset > 0 {
while size - current_offset > 0 {
instructions.push(GetLocal(to_ptr.0));
instructions.push(GetLocal(from_ptr.0));
instructions.push(I32Load8U(alignment_flag, offset));
instructions.push(I32Store8(alignment_flag, offset));
offset += 1;
instructions.push(I32Load8U(alignment_flag, current_offset));
instructions.push(I32Store8(alignment_flag, current_offset));
current_offset += 1;
}
Ok(())
}

View File

@ -0,0 +1,156 @@
use crate::{copy_memory, layout::WasmLayout, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8};
use parity_wasm::elements::{Instruction, Instruction::*, ValueType};
#[derive(Debug, Clone)]
pub enum SymbolStorage {
ParamPrimitive {
local_id: LocalId,
value_type: ValueType,
size: u32,
},
ParamPointer {
local_id: LocalId,
wasm_layout: WasmLayout,
},
VarPrimitive {
local_id: LocalId,
value_type: ValueType,
size: u32,
},
VarStackMemory {
local_id: LocalId,
size: u32,
offset: u32,
alignment_bytes: u32,
},
VarHeapMemory {
local_id: LocalId,
},
}
impl SymbolStorage {
pub fn local_id(&self) -> LocalId {
match self {
Self::ParamPrimitive { local_id, .. } => *local_id,
Self::ParamPointer { local_id, .. } => *local_id,
Self::VarPrimitive { local_id, .. } => *local_id,
Self::VarStackMemory { local_id, .. } => *local_id,
Self::VarHeapMemory { local_id, .. } => *local_id,
}
}
pub fn value_type(&self) -> ValueType {
match self {
Self::ParamPrimitive { value_type, .. } => *value_type,
Self::VarPrimitive { value_type, .. } => *value_type,
Self::ParamPointer { .. } => ValueType::I32,
Self::VarStackMemory { .. } => ValueType::I32,
Self::VarHeapMemory { .. } => ValueType::I32,
}
}
pub fn has_stack_memory(&self) -> bool {
match self {
Self::ParamPointer {
wasm_layout: WasmLayout::StackMemory { .. },
..
} => true,
Self::ParamPointer { .. } => false,
Self::VarStackMemory { .. } => true,
Self::ParamPrimitive { .. } => false,
Self::VarPrimitive { .. } => false,
Self::VarHeapMemory { .. } => false,
}
}
pub fn stack_size_and_alignment(&self) -> (u32, u32) {
match self {
Self::VarStackMemory {
size,
alignment_bytes,
..
}
| Self::ParamPointer {
wasm_layout:
WasmLayout::StackMemory {
size,
alignment_bytes,
..
},
..
} => (*size, *alignment_bytes),
_ => (0, 0),
}
}
pub fn copy_to_memory(
&self,
instructions: &mut Vec<Instruction>,
to_pointer: LocalId,
to_offset: u32,
) -> Result<u32, String> {
match self {
Self::ParamPrimitive {
local_id,
value_type,
size,
..
}
| Self::VarPrimitive {
local_id,
value_type,
size,
..
} => {
let store_instruction = match (value_type, size) {
(ValueType::I64, 8) => I64Store(ALIGN_8, to_offset),
(ValueType::I32, 4) => I32Store(ALIGN_4, to_offset),
(ValueType::I32, 2) => I32Store16(ALIGN_2, to_offset),
(ValueType::I32, 1) => I32Store8(ALIGN_1, to_offset),
(ValueType::F32, 4) => F32Store(ALIGN_4, to_offset),
(ValueType::F64, 8) => F64Store(ALIGN_8, to_offset),
_ => {
return Err(format!("Cannot store {:?} with alignment of {:?}", value_type, size));
}
};
instructions.push(GetLocal(to_pointer.0));
instructions.push(GetLocal(local_id.0));
instructions.push(store_instruction);
Ok(*size)
}
Self::ParamPointer {
local_id,
wasm_layout:
WasmLayout::StackMemory {
size,
alignment_bytes,
},
}
| Self::VarStackMemory {
local_id,
size,
alignment_bytes,
..
} => {
copy_memory(
instructions,
*local_id,
to_pointer,
*size,
*alignment_bytes,
to_offset,
)?;
Ok(*size)
}
Self::ParamPointer { local_id, .. } | Self::VarHeapMemory { local_id, .. } => {
instructions.push(GetLocal(to_pointer.0));
instructions.push(GetLocal(local_id.0));
instructions.push(I32Store(ALIGN_4, to_offset));
Ok(4)
}
}
}
}

View File

@ -308,125 +308,100 @@ mod wasm_records {
// );
// }
//
// #[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#"
{ x: 3 }
"#
),
3,
i64
);
}
#[test]
fn i64_record1_literal() {
fn i64_record2_literal() {
assert_evals_to!(
indoc!(
r#"
{ a: 3 }
{ x: 3, y: 5 }
"#
),
3,
i64
(3, 5),
(i64, 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 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: False, c : False, d : True }
record
"#
),
[true, false, false, true],
[bool; 4]
);
}
#[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; 9]
);
}
// // #[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() {
@ -667,135 +642,135 @@ mod wasm_records {
// );
// }
// #[test]
// fn return_record_2() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3, y: 5 }
// "#
// ),
// [3, 5],
// [i64; 2]
// );
// }
#[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_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_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_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_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_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_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_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() {
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_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() {