From 541039f7005c88c9f877359b85b80c09693b39f3 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 11 Feb 2023 13:52:03 +0100 Subject: [PATCH 01/48] add register names for 32-bit, 16-bit and 8-bit --- .../compiler/gen_dev/src/generic64/x86_64.rs | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index c2c865f057..48fdc39972 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -1591,6 +1591,7 @@ const REX: u8 = 0x40; /// If set, 64-bit operand size is used const REX_PREFIX_W: u8 = 0b1000; /// Extension to the MODRM.reg +/// Permits access to additional registers const REX_PREFIX_R: u8 = 0b0100; #[allow(unused)] /// Extension to the SIB.index field @@ -1598,7 +1599,7 @@ const REX_PREFIX_X: u8 = 0b0010; /// Extension to the MODRM.rm const REX_PREFIX_B: u8 = 0b0001; -/// Wide REX +/// Wide REX (64-bit) const REX_W: u8 = REX | REX_PREFIX_W; #[inline(always)] @@ -2431,6 +2432,50 @@ mod tests { use capstone::prelude::*; impl X86_64GeneralReg { + #[allow(dead_code)] + fn low_32bits_string(&self) -> &str { + match self { + X86_64GeneralReg::RAX => "eax", + X86_64GeneralReg::RBX => "ebx", + X86_64GeneralReg::RCX => "ecx", + X86_64GeneralReg::RDX => "edx", + X86_64GeneralReg::RBP => "ebp", + X86_64GeneralReg::RSP => "esp", + X86_64GeneralReg::RDI => "edi", + X86_64GeneralReg::RSI => "esi", + X86_64GeneralReg::R8 => "r8d", + X86_64GeneralReg::R9 => "r9d", + X86_64GeneralReg::R10 => "r10d", + X86_64GeneralReg::R11 => "r11d", + X86_64GeneralReg::R12 => "r12d", + X86_64GeneralReg::R13 => "r13d", + X86_64GeneralReg::R14 => "r14d", + X86_64GeneralReg::R15 => "r15d", + } + } + + #[allow(dead_code)] + fn low_16bits_string(&self) -> &str { + match self { + X86_64GeneralReg::RAX => "ax", + X86_64GeneralReg::RBX => "bx", + X86_64GeneralReg::RCX => "cx", + X86_64GeneralReg::RDX => "dx", + X86_64GeneralReg::RBP => "bp", + X86_64GeneralReg::RSP => "sp", + X86_64GeneralReg::RDI => "di", + X86_64GeneralReg::RSI => "si", + X86_64GeneralReg::R8 => "r8w", + X86_64GeneralReg::R9 => "r9w", + X86_64GeneralReg::R10 => "r10w", + X86_64GeneralReg::R11 => "r11w", + X86_64GeneralReg::R12 => "r12w", + X86_64GeneralReg::R13 => "r13w", + X86_64GeneralReg::R14 => "r14w", + X86_64GeneralReg::R15 => "r15w", + } + } + #[allow(dead_code)] fn low_8bits_string(&self) -> &str { match self { From 6ecafb85a4a4508278eb6da687c36619235dbde2 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 11 Feb 2023 13:52:56 +0100 Subject: [PATCH 02/48] add mov_reg32_base32_offset32 --- .../compiler/gen_dev/src/generic64/x86_64.rs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index 48fdc39972..af40fcba90 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -2071,6 +2071,29 @@ fn mov_reg64_base64_offset32( buf.extend(offset.to_le_bytes()); } +/// `MOV r/m32,r32` -> Move r32 to r/m32. +#[inline(always)] +fn mov_reg32_base32_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + let rex = add_rm_extension(base, REX); + let rex = add_reg_extension(dst, rex); + let dst_mod = (dst as u8 % 8) << 3; // (dst as u8 % 8) << 3; + let base_mod = base as u8 % 8; + buf.reserve(8); + + buf.extend([rex, 0x8B, 0x80 | dst_mod | base_mod]); + // Using RSP or R12 requires a secondary index byte. + // if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + buf.push(0x24); + } + buf.extend(offset.to_le_bytes()); +} + /// `MOVZX r64,r/m8` -> Move r/m8 with zero extention to r64, where m8 references a base + offset. #[inline(always)] fn movzx_reg64_base8_offset32( @@ -2855,6 +2878,22 @@ mod tests { ); } + #[test] + fn test_mov_reg32_base32_offset32() { + disassembler_test!( + mov_reg32_base32_offset32, + |reg1, reg2, imm| format!( + "mov {}, dword ptr [{} + 0x{:x}]", + X86_64GeneralReg::low_32bits_string(®1), + reg2, + imm + ), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS, + [TEST_I32] + ); + } + #[test] fn test_mov_base64_offset32_reg64() { disassembler_test!( From 49d52f5d9a2e1304f462e26e00c8086a2ee8e46d Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 11 Feb 2023 13:59:17 +0100 Subject: [PATCH 03/48] dev: box and unbox 32-bit value --- .../compiler/gen_dev/src/generic64/aarch64.rs | 33 +++++++++++++++ crates/compiler/gen_dev/src/generic64/mod.rs | 28 +++++++++++-- .../compiler/gen_dev/src/generic64/x86_64.rs | 41 +++++++++++++++++++ .../compiler/test_gen/src/gen_primitives.rs | 8 +++- 4 files changed, 106 insertions(+), 4 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index 01ebac5c90..72aaff7c03 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -641,6 +641,22 @@ impl Assembler for AArch64Assembler { } } #[inline(always)] + fn mov_reg32_mem32_offset32( + buf: &mut Vec<'_, u8>, + dst: AArch64GeneralReg, + src: AArch64GeneralReg, + offset: i32, + ) { + if offset < 0 { + todo!("negative mem offsets for AArch64"); + } else if offset < (0xFFF << 8) { + debug_assert!(offset % 8 == 0); + ldr_reg64_reg64_imm12(buf, dst, src, (offset as u16) >> 3); + } else { + todo!("mem offsets over 32k for AArch64"); + } + } + #[inline(always)] fn mov_mem64_offset32_reg64( buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, @@ -657,6 +673,23 @@ impl Assembler for AArch64Assembler { } } + #[inline(always)] + fn mov_mem32_offset32_reg32( + buf: &mut Vec<'_, u8>, + dst: AArch64GeneralReg, + offset: i32, + src: AArch64GeneralReg, + ) { + if offset < 0 { + todo!("negative mem offsets for AArch64"); + } else if offset < (0xFFF << 8) { + debug_assert!(offset % 8 == 0); + str_reg64_reg64_imm12(buf, src, dst, (offset as u16) >> 3); + } else { + todo!("mem offsets over 32k for AArch64"); + } + } + #[inline(always)] fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) { debug_assert!(size <= 8); diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 778274b086..8dfce89610 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -240,18 +240,33 @@ pub trait Assembler: Sized + Copy { fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg); fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); + // move from memory (a pointer) to register fn mov_reg64_mem64_offset32( buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg, offset: i32, ); + fn mov_reg32_mem32_offset32( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + src: GeneralReg, + offset: i32, + ); + + // move from register to memory fn mov_mem64_offset32_reg64( buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, src: GeneralReg, ); + fn mov_mem32_offset32_reg32( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + offset: i32, + src: GeneralReg, + ); /// Sign extends the data at `offset` with `size` as it copies it to `dst` /// size must be less than or equal to 8. @@ -2001,13 +2016,16 @@ impl< let element_offset = 0; // TODO: Expand to all types. + let storage_manager = &mut self.storage_manager; match self.layout_interner.get(element_layout) { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let sym_reg = self - .storage_manager - .load_to_general_reg(&mut self.buf, &value); + let sym_reg = storage_manager.load_to_general_reg(&mut self.buf, &value); ASM::mov_mem64_offset32_reg64(&mut self.buf, ptr_reg, element_offset, sym_reg); } + Layout::Builtin(Builtin::Int(IntWidth::I32 | IntWidth::U32)) => { + let sym_reg = storage_manager.load_to_general_reg(&mut self.buf, &value); + ASM::mov_mem32_offset32_reg32(&mut self.buf, ptr_reg, element_offset, sym_reg); + } _ if element_width == 0 => {} _ if element_width > 8 => { let (from_offset, size) = self.storage_manager.stack_offset_and_size(&value); @@ -2056,6 +2074,10 @@ impl< let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, &dst); ASM::mov_reg64_mem64_offset32(&mut self.buf, dst_reg, ptr_reg, 0); } + Layout::U32 | Layout::I32 => { + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, &dst); + ASM::mov_reg32_mem32_offset32(&mut self.buf, dst_reg, ptr_reg, 0); + } Layout::STR => { self.storage_manager.with_tmp_general_reg( &mut self.buf, diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index af40fcba90..dd1f2bbd61 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -1293,6 +1293,16 @@ impl Assembler for X86_64Assembler { ) { mov_reg64_base64_offset32(buf, dst, src, offset) } + #[inline(always)] + fn mov_reg32_mem32_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + src: X86_64GeneralReg, + offset: i32, + ) { + mov_reg32_base32_offset32(buf, dst, src, offset) + } + #[inline(always)] fn mov_mem64_offset32_reg64( buf: &mut Vec<'_, u8>, @@ -1303,6 +1313,16 @@ impl Assembler for X86_64Assembler { mov_base64_offset32_reg64(buf, dst, offset, src) } + #[inline(always)] + fn mov_mem32_offset32_reg32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + offset: i32, + src: X86_64GeneralReg, + ) { + mov_base32_offset32_reg32(buf, dst, offset, src) + } + #[inline(always)] fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) { debug_assert!(size <= 8); @@ -2050,6 +2070,27 @@ fn mov_base64_offset32_reg64( buf.extend(offset.to_le_bytes()); } +/// `MOV r/m32,r32` -> Move r32 to r/m32, where m32 references a base + offset. +#[inline(always)] +fn mov_base32_offset32_reg32( + buf: &mut Vec<'_, u8>, + base: X86_64GeneralReg, + offset: i32, + src: X86_64GeneralReg, +) { + let rex = add_rm_extension(base, REX); + let rex = add_reg_extension(src, rex); + let src_mod = (src as u8 % 8) << 3; + let base_mod = base as u8 % 8; + buf.reserve(8); + buf.extend([rex, 0x89, 0x80 | src_mod | base_mod]); + // Using RSP or R12 requires a secondary index byte. + if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + buf.push(0x24); + } + buf.extend(offset.to_le_bytes()); +} + /// `MOV r64,r/m64` -> Move r/m64 to r64, where m64 references a base + offset. #[inline(always)] fn mov_reg64_base64_offset32( diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index 51c9ed01a2..f00fe66de9 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -3307,10 +3307,16 @@ fn box_str() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] -fn box_and_unbox_num() { +fn box_and_unbox_64bit_num() { assert_evals_to!("Box.unbox (Box.box (123u64))", 123, u64) } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn box_and_unbox_32bit_num() { + assert_evals_to!("Box.unbox (Box.box (123u32))", 123, u32) +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn box_and_unbox_record() { From ee79a311f97b229794519bca048ebdbf456df9e5 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 11 Feb 2023 15:31:35 +0100 Subject: [PATCH 04/48] dev: box and unbox 16-bit and 8-bit values --- .../compiler/gen_dev/src/generic64/aarch64.rs | 56 ++++- crates/compiler/gen_dev/src/generic64/mod.rs | 30 +++ .../compiler/gen_dev/src/generic64/x86_64.rs | 207 ++++++++++++++++++ .../compiler/test_gen/src/gen_primitives.rs | 18 ++ 4 files changed, 299 insertions(+), 12 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index 72aaff7c03..b62f3d1689 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -656,6 +656,25 @@ impl Assembler for AArch64Assembler { todo!("mem offsets over 32k for AArch64"); } } + #[inline(always)] + fn mov_reg16_mem16_offset32( + _buf: &mut Vec<'_, u8>, + _dst: AArch64GeneralReg, + _src: AArch64GeneralReg, + _offset: i32, + ) { + todo!() + } + #[inline(always)] + fn mov_reg8_mem8_offset32( + _buf: &mut Vec<'_, u8>, + _dst: AArch64GeneralReg, + _src: AArch64GeneralReg, + _offset: i32, + ) { + todo!() + } + #[inline(always)] fn mov_mem64_offset32_reg64( buf: &mut Vec<'_, u8>, @@ -675,19 +694,32 @@ impl Assembler for AArch64Assembler { #[inline(always)] fn mov_mem32_offset32_reg32( - buf: &mut Vec<'_, u8>, - dst: AArch64GeneralReg, - offset: i32, - src: AArch64GeneralReg, + _buf: &mut Vec<'_, u8>, + _dst: AArch64GeneralReg, + _offset: i32, + _src: AArch64GeneralReg, ) { - if offset < 0 { - todo!("negative mem offsets for AArch64"); - } else if offset < (0xFFF << 8) { - debug_assert!(offset % 8 == 0); - str_reg64_reg64_imm12(buf, src, dst, (offset as u16) >> 3); - } else { - todo!("mem offsets over 32k for AArch64"); - } + todo!() + } + + #[inline(always)] + fn mov_mem16_offset32_reg16( + _buf: &mut Vec<'_, u8>, + _dst: AArch64GeneralReg, + _offset: i32, + _src: AArch64GeneralReg, + ) { + todo!() + } + + #[inline(always)] + fn mov_mem8_offset32_reg8( + _buf: &mut Vec<'_, u8>, + _dst: AArch64GeneralReg, + _offset: i32, + _src: AArch64GeneralReg, + ) { + todo!() } #[inline(always)] diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 8dfce89610..cb34448339 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -253,6 +253,13 @@ pub trait Assembler: Sized + Copy { src: GeneralReg, offset: i32, ); + fn mov_reg16_mem16_offset32( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + src: GeneralReg, + offset: i32, + ); + fn mov_reg8_mem8_offset32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg, offset: i32); // move from register to memory fn mov_mem64_offset32_reg64( @@ -267,6 +274,13 @@ pub trait Assembler: Sized + Copy { offset: i32, src: GeneralReg, ); + fn mov_mem16_offset32_reg16( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + offset: i32, + src: GeneralReg, + ); + fn mov_mem8_offset32_reg8(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, src: GeneralReg); /// Sign extends the data at `offset` with `size` as it copies it to `dst` /// size must be less than or equal to 8. @@ -2026,6 +2040,14 @@ impl< let sym_reg = storage_manager.load_to_general_reg(&mut self.buf, &value); ASM::mov_mem32_offset32_reg32(&mut self.buf, ptr_reg, element_offset, sym_reg); } + Layout::Builtin(Builtin::Int(IntWidth::I16 | IntWidth::U16)) => { + let sym_reg = storage_manager.load_to_general_reg(&mut self.buf, &value); + ASM::mov_mem16_offset32_reg16(&mut self.buf, ptr_reg, element_offset, sym_reg); + } + Layout::Builtin(Builtin::Int(IntWidth::I8 | IntWidth::U8) | Builtin::Bool) => { + let sym_reg = storage_manager.load_to_general_reg(&mut self.buf, &value); + ASM::mov_mem8_offset32_reg8(&mut self.buf, ptr_reg, element_offset, sym_reg); + } _ if element_width == 0 => {} _ if element_width > 8 => { let (from_offset, size) = self.storage_manager.stack_offset_and_size(&value); @@ -2078,6 +2100,14 @@ impl< let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, &dst); ASM::mov_reg32_mem32_offset32(&mut self.buf, dst_reg, ptr_reg, 0); } + Layout::U16 | Layout::I16 => { + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, &dst); + ASM::mov_reg16_mem16_offset32(&mut self.buf, dst_reg, ptr_reg, 0); + } + Layout::U8 | Layout::I8 | Layout::BOOL => { + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, &dst); + ASM::mov_reg8_mem8_offset32(&mut self.buf, dst_reg, ptr_reg, 0); + } Layout::STR => { self.storage_manager.with_tmp_general_reg( &mut self.buf, diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index dd1f2bbd61..5a4b117844 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -1302,6 +1302,22 @@ impl Assembler for X86_64Assembler { ) { mov_reg32_base32_offset32(buf, dst, src, offset) } + fn mov_reg16_mem16_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + src: X86_64GeneralReg, + offset: i32, + ) { + mov_reg16_base16_offset32(buf, dst, src, offset) + } + fn mov_reg8_mem8_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + src: X86_64GeneralReg, + offset: i32, + ) { + mov_reg8_base8_offset32(buf, dst, src, offset) + } #[inline(always)] fn mov_mem64_offset32_reg64( @@ -1323,6 +1339,26 @@ impl Assembler for X86_64Assembler { mov_base32_offset32_reg32(buf, dst, offset, src) } + #[inline(always)] + fn mov_mem16_offset32_reg16( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + offset: i32, + src: X86_64GeneralReg, + ) { + mov_base16_offset32_reg16(buf, dst, offset, src) + } + + #[inline(always)] + fn mov_mem8_offset32_reg8( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + offset: i32, + src: X86_64GeneralReg, + ) { + mov_base8_offset32_reg8(buf, dst, offset, src) + } + #[inline(always)] fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) { debug_assert!(size <= 8); @@ -1605,6 +1641,9 @@ impl X86_64Assembler { push_reg64(buf, reg); } } + +const GRP_4: u8 = 0x66; + const REX: u8 = 0x40; // see https://wiki.osdev.org/X86-64_Instruction_Encoding#Encoding @@ -2091,6 +2130,48 @@ fn mov_base32_offset32_reg32( buf.extend(offset.to_le_bytes()); } +/// `MOV r/m16,r16` -> Move r16 to r/m16, where m16 references a base + offset. +#[inline(always)] +fn mov_base16_offset32_reg16( + buf: &mut Vec<'_, u8>, + base: X86_64GeneralReg, + offset: i32, + src: X86_64GeneralReg, +) { + let rex = add_rm_extension(base, REX); + let rex = add_reg_extension(src, rex); + let src_mod = (src as u8 % 8) << 3; + let base_mod = base as u8 % 8; + buf.reserve(8); + buf.extend([GRP_4, rex, 0x89, 0x80 | src_mod | base_mod]); + // Using RSP or R12 requires a secondary index byte. + if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + buf.push(0x24); + } + buf.extend(offset.to_le_bytes()); +} + +/// `MOV r/m8,r8` -> Move r8 to r/m8, where m8 references a base + offset. +#[inline(always)] +fn mov_base8_offset32_reg8( + buf: &mut Vec<'_, u8>, + base: X86_64GeneralReg, + offset: i32, + src: X86_64GeneralReg, +) { + let rex = add_rm_extension(base, REX); + let rex = add_reg_extension(src, rex); + let src_mod = (src as u8 % 8) << 3; + let base_mod = base as u8 % 8; + buf.reserve(8); + buf.extend([rex, 0x88, 0x80 | src_mod | base_mod]); + // Using RSP or R12 requires a secondary index byte. + if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + buf.push(0x24); + } + buf.extend(offset.to_le_bytes()); +} + /// `MOV r64,r/m64` -> Move r/m64 to r64, where m64 references a base + offset. #[inline(always)] fn mov_reg64_base64_offset32( @@ -2135,6 +2216,52 @@ fn mov_reg32_base32_offset32( buf.extend(offset.to_le_bytes()); } +/// `MOV r/m16,r16` -> Move r16 to r/m16. +#[inline(always)] +fn mov_reg16_base16_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + let rex = add_rm_extension(base, REX); + let rex = add_reg_extension(dst, rex); + let dst_mod = (dst as u8 % 8) << 3; // (dst as u8 % 8) << 3; + let base_mod = base as u8 % 8; + buf.reserve(8); + + buf.extend([0x66, rex, 0x8B, 0x80 | dst_mod | base_mod]); + // Using RSP or R12 requires a secondary index byte. + // if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + buf.push(0x24); + } + buf.extend(offset.to_le_bytes()); +} + +/// `MOV r/m16,r16` -> Move r16 to r/m16. +#[inline(always)] +fn mov_reg8_base8_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + let rex = add_rm_extension(base, REX); + let rex = add_reg_extension(dst, rex); + let dst_mod = (dst as u8 % 8) << 3; // (dst as u8 % 8) << 3; + let base_mod = base as u8 % 8; + buf.reserve(8); + + buf.extend([rex, 0x8A, 0x80 | dst_mod | base_mod]); + // Using RSP or R12 requires a secondary index byte. + // if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + buf.push(0x24); + } + buf.extend(offset.to_le_bytes()); +} + /// `MOVZX r64,r/m8` -> Move r/m8 with zero extention to r64, where m8 references a base + offset. #[inline(always)] fn movzx_reg64_base8_offset32( @@ -2935,6 +3062,38 @@ mod tests { ); } + #[test] + fn test_mov_reg16_base16_offset32() { + disassembler_test!( + mov_reg16_base16_offset32, + |reg1, reg2, imm| format!( + "mov {}, word ptr [{} + 0x{:x}]", + X86_64GeneralReg::low_16bits_string(®1), + reg2, + imm + ), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS, + [TEST_I32] + ); + } + + #[test] + fn test_mov_reg8_base8_offset32() { + disassembler_test!( + mov_reg8_base8_offset32, + |reg1, reg2, imm| format!( + "mov {}, byte ptr [{} + 0x{:x}]", + X86_64GeneralReg::low_8bits_string(®1), + reg2, + imm + ), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS, + [TEST_I32] + ); + } + #[test] fn test_mov_base64_offset32_reg64() { disassembler_test!( @@ -2946,6 +3105,54 @@ mod tests { ); } + #[test] + fn test_mov_base32_offset32_reg32() { + disassembler_test!( + mov_base32_offset32_reg32, + |reg1, imm, reg2| format!( + "mov dword ptr [{} + 0x{:x}], {}", + reg1, + imm, + X86_64GeneralReg::low_32bits_string(®2), + ), + ALL_GENERAL_REGS, + [TEST_I32], + ALL_GENERAL_REGS + ); + } + + #[test] + fn test_mov_base16_offset32_reg16() { + disassembler_test!( + mov_base16_offset32_reg16, + |reg1, imm, reg2| format!( + "mov word ptr [{} + 0x{:x}], {}", + reg1, + imm, + X86_64GeneralReg::low_16bits_string(®2), + ), + ALL_GENERAL_REGS, + [TEST_I32], + ALL_GENERAL_REGS + ); + } + + #[test] + fn test_mov_base8_offset32_reg8() { + disassembler_test!( + mov_base8_offset32_reg8, + |reg1, imm, reg2| format!( + "mov byte ptr [{} + 0x{:x}], {}", + reg1, + imm, + X86_64GeneralReg::low_8bits_string(®2), + ), + ALL_GENERAL_REGS, + [TEST_I32], + ALL_GENERAL_REGS + ); + } + #[test] fn test_movzx_reg64_base8_offset32() { disassembler_test!( diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index f00fe66de9..147cb47c3a 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -3317,6 +3317,24 @@ fn box_and_unbox_32bit_num() { assert_evals_to!("Box.unbox (Box.box (123u32))", 123, u32) } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn box_and_unbox_16bit_num() { + assert_evals_to!("Box.unbox (Box.box (123u16))", 123, u16) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn box_and_unbox_8bit_num() { + assert_evals_to!("Box.unbox (Box.box (123u8))", 123, u8) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn box_and_unbox_1bit_num() { + assert_evals_to!("Box.unbox (Box.box (Bool.true))", true, bool) +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn box_and_unbox_record() { From b31edead543503647e115ed43d3da403e853e975 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 11 Feb 2023 15:53:09 +0100 Subject: [PATCH 05/48] centralize writing a stack value to a pointer --- crates/compiler/gen_dev/src/generic64/mod.rs | 161 +++++++++---------- 1 file changed, 76 insertions(+), 85 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index cb34448339..924f10c3f3 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -1851,10 +1851,11 @@ impl< fn create_array( &mut self, sym: &Symbol, - element_layout: &InLayout<'a>, + element_in_layout: &InLayout<'a>, elements: &'a [ListLiteralElement<'a>], ) { - let element_width = self.layout_interner.stack_size(*element_layout) as u64; + let element_layout = self.layout_interner.get(*element_in_layout); + let element_width = self.layout_interner.stack_size(*element_in_layout) as u64; // load the total size of the data we want to store (excludes refcount) let data_bytes_symbol = Symbol::DEV_TMP; @@ -1884,54 +1885,34 @@ impl< .load_to_general_reg(&mut self.buf, &Symbol::DEV_TMP3); // Copy everything into output array. - let mut elem_offset = 0; + let mut element_offset = 0; for elem in elements { // TODO: this could be a lot faster when loading large lists // if we move matching on the element layout to outside this loop. // It also greatly bloats the code here. // Refactor this and switch to one external match. // We also could make loadining indivitual literals much faster - let elem_sym = match elem { - ListLiteralElement::Symbol(sym) => sym, + let element_symbol = match elem { + ListLiteralElement::Symbol(sym) => *sym, ListLiteralElement::Literal(lit) => { - self.load_literal(&Symbol::DEV_TMP, element_layout, lit); - &Symbol::DEV_TMP + self.load_literal(&Symbol::DEV_TMP, element_in_layout, lit); + Symbol::DEV_TMP } }; - // TODO: Expand to all types. - match self.layout_interner.get(*element_layout) { - Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64) | Builtin::Bool) => { - let sym_reg = self - .storage_manager - .load_to_general_reg(&mut self.buf, elem_sym); - ASM::mov_mem64_offset32_reg64(&mut self.buf, ptr_reg, elem_offset, sym_reg); - } - _ if element_width == 0 => {} - _ if element_width > 8 => { - let (from_offset, size) = self.storage_manager.stack_offset_and_size(elem_sym); - debug_assert!(from_offset % 8 == 0); - debug_assert!(size % 8 == 0); - debug_assert_eq!(size as u64, element_width); - self.storage_manager.with_tmp_general_reg( - &mut self.buf, - |_storage_manager, buf, tmp_reg| { - for i in (0..size as i32).step_by(8) { - ASM::mov_reg64_base32(buf, tmp_reg, from_offset + i); - ASM::mov_mem64_offset32_reg64( - buf, - ptr_reg, - elem_offset + i, - tmp_reg, - ); - } - }, - ); - } - x => todo!("copying data to list with layout, {:?}", x), - } - elem_offset += element_width as i32; - if elem_sym == &Symbol::DEV_TMP { - self.free_symbol(elem_sym); + + Self::ptr_write( + &mut self.buf, + &mut self.storage_manager, + ptr_reg, + element_offset, + element_width, + element_layout, + element_symbol, + ); + + element_offset += element_width as i32; + if element_symbol == Symbol::DEV_TMP { + self.free_symbol(&element_symbol); } } @@ -2029,49 +2010,15 @@ impl< let element_width = self.layout_interner.stack_size(element_layout) as u64; let element_offset = 0; - // TODO: Expand to all types. - let storage_manager = &mut self.storage_manager; - match self.layout_interner.get(element_layout) { - Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let sym_reg = storage_manager.load_to_general_reg(&mut self.buf, &value); - ASM::mov_mem64_offset32_reg64(&mut self.buf, ptr_reg, element_offset, sym_reg); - } - Layout::Builtin(Builtin::Int(IntWidth::I32 | IntWidth::U32)) => { - let sym_reg = storage_manager.load_to_general_reg(&mut self.buf, &value); - ASM::mov_mem32_offset32_reg32(&mut self.buf, ptr_reg, element_offset, sym_reg); - } - Layout::Builtin(Builtin::Int(IntWidth::I16 | IntWidth::U16)) => { - let sym_reg = storage_manager.load_to_general_reg(&mut self.buf, &value); - ASM::mov_mem16_offset32_reg16(&mut self.buf, ptr_reg, element_offset, sym_reg); - } - Layout::Builtin(Builtin::Int(IntWidth::I8 | IntWidth::U8) | Builtin::Bool) => { - let sym_reg = storage_manager.load_to_general_reg(&mut self.buf, &value); - ASM::mov_mem8_offset32_reg8(&mut self.buf, ptr_reg, element_offset, sym_reg); - } - _ if element_width == 0 => {} - _ if element_width > 8 => { - let (from_offset, size) = self.storage_manager.stack_offset_and_size(&value); - debug_assert!(from_offset % 8 == 0); - debug_assert!(size % 8 == 0); - debug_assert_eq!(size as u64, element_width); - self.storage_manager.with_tmp_general_reg( - &mut self.buf, - |_storage_manager, buf, tmp_reg| { - // a crude memcpy - for i in (0..size as i32).step_by(8) { - ASM::mov_reg64_base32(buf, tmp_reg, from_offset + i); - ASM::mov_mem64_offset32_reg64( - buf, - ptr_reg, - element_offset + i, - tmp_reg, - ); - } - }, - ); - } - x => todo!("copying data to list with layout, {:?}", x), - } + Self::ptr_write( + &mut self.buf, + &mut self.storage_manager, + ptr_reg, + element_offset, + element_width, + self.layout_interner.get(element_layout), + value, + ); if value == Symbol::DEV_TMP { self.free_symbol(&value); @@ -2092,7 +2039,7 @@ impl< let ret_stack_size = self.layout_interner.stack_size(element_layout); match element_layout { - single_register_integers!() if ret_stack_size == 8 => { + Layout::U64 | Layout::I64 => { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, &dst); ASM::mov_reg64_mem64_offset32(&mut self.buf, dst_reg, ptr_reg, 0); } @@ -2497,6 +2444,50 @@ impl< ASM::mov_base32_reg64(buf, base_offset + 16, tmp_reg); } + fn ptr_write( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>, + ptr_reg: GeneralReg, + element_offset: i32, + element_width: u64, + element_layout: Layout<'a>, + value: Symbol, + ) { + match element_layout { + Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { + let sym_reg = storage_manager.load_to_general_reg(buf, &value); + ASM::mov_mem64_offset32_reg64(buf, ptr_reg, element_offset, sym_reg); + } + Layout::Builtin(Builtin::Int(IntWidth::I32 | IntWidth::U32)) => { + let sym_reg = storage_manager.load_to_general_reg(buf, &value); + ASM::mov_mem32_offset32_reg32(buf, ptr_reg, element_offset, sym_reg); + } + Layout::Builtin(Builtin::Int(IntWidth::I16 | IntWidth::U16)) => { + let sym_reg = storage_manager.load_to_general_reg(buf, &value); + ASM::mov_mem16_offset32_reg16(buf, ptr_reg, element_offset, sym_reg); + } + Layout::Builtin(Builtin::Int(IntWidth::I8 | IntWidth::U8) | Builtin::Bool) => { + let sym_reg = storage_manager.load_to_general_reg(buf, &value); + ASM::mov_mem8_offset32_reg8(buf, ptr_reg, element_offset, sym_reg); + } + _ if element_width == 0 => {} + _ if element_width > 8 => { + let (from_offset, size) = storage_manager.stack_offset_and_size(&value); + debug_assert!(from_offset % 8 == 0); + debug_assert!(size % 8 == 0); + debug_assert_eq!(size as u64, element_width); + storage_manager.with_tmp_general_reg(buf, |_storage_manager, buf, tmp_reg| { + // a crude memcpy + for i in (0..size as i32).step_by(8) { + ASM::mov_reg64_base32(buf, tmp_reg, from_offset + i); + ASM::mov_mem64_offset32_reg64(buf, ptr_reg, element_offset + i, tmp_reg); + } + }); + } + x => todo!("copying data to list with layout, {:?}", x), + } + } + /// Updates a jump instruction to a new offset and returns the number of bytes written. fn update_jmp_imm32_offset( &mut self, From 8ec3ab09636e0d846812a7bb3d61feeb5e000764 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 11 Feb 2023 16:13:03 +0100 Subject: [PATCH 06/48] centralize reading a pointer to some stack location --- crates/compiler/gen_dev/src/generic64/mod.rs | 145 ++++++++++--------- 1 file changed, 77 insertions(+), 68 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 924f10c3f3..e4ae34ff18 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -1547,43 +1547,14 @@ impl< ASM::add_reg64_reg64_reg64(buf, tmp, tmp, list_ptr); let element_ptr = tmp; - match *ret_layout { - single_register_integers!() if ret_stack_size == 8 => { - let dst_reg = storage_manager.claim_general_reg(buf, dst); - ASM::mov_reg64_mem64_offset32(buf, dst_reg, element_ptr, 0); - } - single_register_floats!() => { - let dst_reg = storage_manager.claim_float_reg(buf, dst); - ASM::mov_freg64_freg64(buf, dst_reg, CC::FLOAT_RETURN_REGS[0]); - } - Layout::STR => { - // the `list_ptr` register is now unused, and we can use it as scratch space - let tmp_reg = list_ptr; - - Self::unbox_str_or_list( - buf, - storage_manager, - *dst, - element_ptr, - tmp_reg, - ); - } - other => { - // - match self.layout_interner.get(other) { - Layout::Boxed(_) => { - let dst_reg = storage_manager.claim_general_reg(buf, dst); - ASM::mov_reg64_reg64(buf, dst_reg, CC::GENERAL_RETURN_REGS[0]); - } - _ => { - todo!( - "cannot load {} from the heap yet", - self.layout_interner.dbg(other) - ); - } - } - } - } + Self::ptr_read( + buf, + storage_manager, + self.layout_interner, + element_ptr, + *ret_layout, + *dst, + ); }); }, ); @@ -2036,37 +2007,14 @@ impl< .storage_manager .load_to_general_reg(&mut self.buf, &ptr); - let ret_stack_size = self.layout_interner.stack_size(element_layout); - - match element_layout { - Layout::U64 | Layout::I64 => { - let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, &dst); - ASM::mov_reg64_mem64_offset32(&mut self.buf, dst_reg, ptr_reg, 0); - } - Layout::U32 | Layout::I32 => { - let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, &dst); - ASM::mov_reg32_mem32_offset32(&mut self.buf, dst_reg, ptr_reg, 0); - } - Layout::U16 | Layout::I16 => { - let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, &dst); - ASM::mov_reg16_mem16_offset32(&mut self.buf, dst_reg, ptr_reg, 0); - } - Layout::U8 | Layout::I8 | Layout::BOOL => { - let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, &dst); - ASM::mov_reg8_mem8_offset32(&mut self.buf, dst_reg, ptr_reg, 0); - } - Layout::STR => { - self.storage_manager.with_tmp_general_reg( - &mut self.buf, - |storage_manager, buf, tmp_reg| { - Self::unbox_str_or_list(buf, storage_manager, dst, ptr_reg, tmp_reg); - }, - ); - } - _ => { - todo!("unboxing of {:?}", self.layout_interner.dbg(element_layout)) - } - } + Self::ptr_read( + &mut self.buf, + &mut self.storage_manager, + self.layout_interner, + ptr_reg, + element_layout, + dst, + ); } fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>) { @@ -2444,6 +2392,67 @@ impl< ASM::mov_base32_reg64(buf, base_offset + 16, tmp_reg); } + fn ptr_read( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>, + layout_interner: &STLayoutInterner<'a>, + ptr_reg: GeneralReg, + element_in_layout: InLayout<'a>, + dst: Symbol, + ) { + match layout_interner.get(element_in_layout) { + Layout::Builtin(builtin) => match builtin { + Builtin::Int(int_width) => match int_width { + IntWidth::I128 | IntWidth::U128 => { + // can we treat this as 2 u64's? + todo!() + } + IntWidth::I64 | IntWidth::U64 => { + let dst_reg = storage_manager.claim_general_reg(buf, &dst); + ASM::mov_reg64_mem64_offset32(buf, dst_reg, ptr_reg, 0); + } + IntWidth::I32 | IntWidth::U32 => { + let dst_reg = storage_manager.claim_general_reg(buf, &dst); + ASM::mov_reg32_mem32_offset32(buf, dst_reg, ptr_reg, 0); + } + IntWidth::I16 | IntWidth::U16 => { + let dst_reg = storage_manager.claim_general_reg(buf, &dst); + ASM::mov_reg16_mem16_offset32(buf, dst_reg, ptr_reg, 0); + } + IntWidth::I8 | IntWidth::U8 => { + let dst_reg = storage_manager.claim_general_reg(buf, &dst); + ASM::mov_reg8_mem8_offset32(buf, dst_reg, ptr_reg, 0); + } + }, + Builtin::Float(_) => { + let dst_reg = storage_manager.claim_float_reg(buf, &dst); + ASM::mov_freg64_freg64(buf, dst_reg, CC::FLOAT_RETURN_REGS[0]); + } + Builtin::Bool => { + // the same as an 8-bit integer + let dst_reg = storage_manager.claim_general_reg(buf, &dst); + ASM::mov_reg8_mem8_offset32(buf, dst_reg, ptr_reg, 0); + } + Builtin::Decimal => { + // same as 128-bit integer + } + Builtin::Str | Builtin::List(_) => { + storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp_reg| { + Self::unbox_str_or_list(buf, storage_manager, dst, ptr_reg, tmp_reg); + }); + } + }, + + Layout::Boxed(_) => { + // the same as 64-bit integer (for 64-bit targets) + let dst_reg = storage_manager.claim_general_reg(buf, &dst); + ASM::mov_reg64_mem64_offset32(buf, dst_reg, ptr_reg, 0); + } + + _ => todo!("unboxing of {:?}", layout_interner.dbg(element_in_layout)), + } + } + fn ptr_write( buf: &mut Vec<'a, u8>, storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>, From 6a84d6ee839860872c9ec6613f7cbc85c7588c89 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 11 Feb 2023 16:49:02 +0100 Subject: [PATCH 07/48] centralize stack movement --- .../compiler/gen_dev/src/generic64/aarch64.rs | 25 ++++ crates/compiler/gen_dev/src/generic64/mod.rs | 9 ++ .../compiler/gen_dev/src/generic64/storage.rs | 107 +++++++++++++++--- .../compiler/gen_dev/src/generic64/x86_64.rs | 27 +++++ crates/compiler/gen_dev/src/lib.rs | 7 ++ crates/compiler/test_gen/src/gen_str.rs | 2 +- 6 files changed, 160 insertions(+), 17 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index b62f3d1689..8c75095b38 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -609,6 +609,18 @@ impl Assembler for AArch64Assembler { } } #[inline(always)] + fn mov_reg32_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _offset: i32) { + todo!() + } + #[inline(always)] + fn mov_reg16_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _offset: i32) { + todo!() + } + #[inline(always)] + fn mov_reg8_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _offset: i32) { + todo!() + } + #[inline(always)] fn mov_base32_freg64(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64FloatReg) { todo!("saving floating point reg to base offset for AArch64"); } @@ -624,6 +636,19 @@ impl Assembler for AArch64Assembler { } } + #[inline(always)] + fn mov_base32_reg32(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64GeneralReg) { + todo!() + } + #[inline(always)] + fn mov_base32_reg16(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64GeneralReg) { + todo!() + } + #[inline(always)] + fn mov_base32_reg8(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64GeneralReg) { + todo!() + } + #[inline(always)] fn mov_reg64_mem64_offset32( buf: &mut Vec<'_, u8>, diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index e4ae34ff18..7b057f8ac4 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -236,9 +236,18 @@ pub trait Assembler: Sized + Copy { // base32 is similar to stack based instructions but they reference the base/frame pointer. fn mov_freg64_base32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32); + fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32); + fn mov_reg32_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32); + fn mov_reg16_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32); + fn mov_reg8_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32); + fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg); + fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); + fn mov_base32_reg32(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); + fn mov_base32_reg16(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); + fn mov_base32_reg8(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); // move from memory (a pointer) to register fn mov_reg64_mem64_offset32( diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs index 6c72b462a2..8e2ed687b1 100644 --- a/crates/compiler/gen_dev/src/generic64/storage.rs +++ b/crates/compiler/gen_dev/src/generic64/storage.rs @@ -739,16 +739,55 @@ impl< layout: &InLayout<'a>, ) { match layout_interner.get(*layout) { - Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - debug_assert_eq!(to_offset % 8, 0); - let reg = self.load_to_general_reg(buf, sym); - ASM::mov_base32_reg64(buf, to_offset, reg); - } - Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { - debug_assert_eq!(to_offset % 8, 0); - let reg = self.load_to_float_reg(buf, sym); - ASM::mov_base32_freg64(buf, to_offset, reg); - } + Layout::Builtin(builtin) => match builtin { + Builtin::Int(int_width) => match int_width { + IntWidth::I128 | IntWidth::U128 => { + // can we treat this as 2 u64's? + todo!() + } + IntWidth::I64 | IntWidth::U64 => { + debug_assert_eq!(to_offset % 8, 0); + let reg = self.load_to_general_reg(buf, sym); + ASM::mov_base32_reg64(buf, to_offset, reg); + } + IntWidth::I32 | IntWidth::U32 => { + debug_assert_eq!(to_offset % 4, 0); + let reg = self.load_to_general_reg(buf, sym); + ASM::mov_base32_reg32(buf, to_offset, reg); + } + IntWidth::I16 | IntWidth::U16 => { + debug_assert_eq!(to_offset % 2, 0); + let reg = self.load_to_general_reg(buf, sym); + ASM::mov_base32_reg16(buf, to_offset, reg); + } + IntWidth::I8 | IntWidth::U8 => { + let reg = self.load_to_general_reg(buf, sym); + ASM::mov_base32_reg8(buf, to_offset, reg); + } + }, + + Builtin::Float(float_width) => match float_width { + FloatWidth::F64 => { + debug_assert_eq!(to_offset % 8, 0); + let reg = self.load_to_float_reg(buf, sym); + ASM::mov_base32_freg64(buf, to_offset, reg); + } + FloatWidth::F32 => todo!(), + }, + Builtin::Bool => { + // same as 8-bit integer + let reg = self.load_to_general_reg(buf, sym); + ASM::mov_base32_reg8(buf, to_offset, reg); + } + Builtin::Decimal => todo!(), + Builtin::Str | Builtin::List(_) => { + let (from_offset, size) = self.stack_offset_and_size(sym); + debug_assert!(from_offset % 8 == 0); + debug_assert!(size % 8 == 0); + debug_assert_eq!(size, layout_interner.stack_size(*layout)); + self.copy_symbol_to_stack_offset_help(buf, size, from_offset, to_offset) + } + }, _ if layout_interner.stack_size(*layout) == 0 => {} // TODO: Verify this is always true. // The dev backend does not deal with refcounting and does not care about if data is safe to memcpy. @@ -759,17 +798,53 @@ impl< debug_assert!(from_offset % 8 == 0); debug_assert!(size % 8 == 0); debug_assert_eq!(size, layout_interner.stack_size(*layout)); - self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| { - for i in (0..size as i32).step_by(8) { - ASM::mov_reg64_base32(buf, reg, from_offset + i); - ASM::mov_base32_reg64(buf, to_offset + i, reg); - } - }); + self.copy_symbol_to_stack_offset_help(buf, size, from_offset, to_offset) } x => todo!("copying data to the stack with layout, {:?}", x), } } + pub fn copy_symbol_to_stack_offset_help( + &mut self, + buf: &mut Vec<'a, u8>, + size: u32, + from_offset: i32, + to_offset: i32, + ) { + let mut copied = 0; + let size = size as i32; + + self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| { + for _ in (0..(size - copied)).step_by(8) { + ASM::mov_reg64_base32(buf, reg, from_offset + copied); + ASM::mov_base32_reg64(buf, to_offset + copied, reg); + + copied += 8; + } + + for _ in (0..(size - copied)).step_by(4) { + ASM::mov_reg32_base32(buf, reg, from_offset + copied); + ASM::mov_base32_reg32(buf, to_offset + copied, reg); + + copied += 4; + } + + for _ in (0..(size - copied)).step_by(2) { + ASM::mov_reg16_base32(buf, reg, from_offset + copied); + ASM::mov_base32_reg16(buf, to_offset + copied, reg); + + copied += 2; + } + + for _ in (0..(size - copied)).step_by(1) { + ASM::mov_reg8_base32(buf, reg, from_offset + copied); + ASM::mov_base32_reg8(buf, to_offset + copied, reg); + + copied += 1; + } + }); + } + #[allow(dead_code)] /// Ensures that a register is free. If it is not free, data will be moved to make it free. pub fn ensure_reg_free( diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index 5a4b117844..eed30868b5 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -1271,18 +1271,45 @@ impl Assembler for X86_64Assembler { fn mov_freg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) { movsd_freg64_base64_offset32(buf, dst, X86_64GeneralReg::RBP, offset) } + #[inline(always)] fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32) { mov_reg64_base64_offset32(buf, dst, X86_64GeneralReg::RBP, offset) } + #[inline(always)] + fn mov_reg32_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32) { + mov_reg32_base32_offset32(buf, dst, X86_64GeneralReg::RBP, offset) + } + #[inline(always)] + fn mov_reg16_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32) { + mov_reg16_base16_offset32(buf, dst, X86_64GeneralReg::RBP, offset) + } + #[inline(always)] + fn mov_reg8_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32) { + mov_reg8_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset) + } + #[inline(always)] fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64FloatReg) { movsd_base64_offset32_freg64(buf, X86_64GeneralReg::RBP, offset, src) } + #[inline(always)] fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) { mov_base64_offset32_reg64(buf, X86_64GeneralReg::RBP, offset, src) } + #[inline(always)] + fn mov_base32_reg32(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) { + mov_base32_offset32_reg32(buf, X86_64GeneralReg::RBP, offset, src) + } + #[inline(always)] + fn mov_base32_reg16(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) { + mov_base16_offset32_reg16(buf, X86_64GeneralReg::RBP, offset, src) + } + #[inline(always)] + fn mov_base32_reg8(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) { + mov_base8_offset32_reg8(buf, X86_64GeneralReg::RBP, offset, src) + } #[inline(always)] fn mov_reg64_mem64_offset32( diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index f6d4fbaf06..9c532a8b4c 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -819,6 +819,13 @@ trait Backend<'a> { arg_layouts, ret_layout, ), + LowLevel::StrFromUtf8Range => self.build_fn_call( + sym, + bitcode::STR_FROM_UTF8_RANGE.to_string(), + args, + arg_layouts, + ret_layout, + ), LowLevel::PtrCast => { debug_assert_eq!( 1, diff --git a/crates/compiler/test_gen/src/gen_str.rs b/crates/compiler/test_gen/src/gen_str.rs index f540e62c61..9e247c7cda 100644 --- a/crates/compiler/test_gen/src/gen_str.rs +++ b/crates/compiler/test_gen/src/gen_str.rs @@ -577,7 +577,7 @@ fn str_starts_with_false_small_str() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_pass_single_ascii() { assert_evals_to!( indoc!( From 38b66765ab8778745c34364f9ba1eaee964fec4f Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 11 Feb 2023 16:58:25 +0100 Subject: [PATCH 08/48] enable more string tests --- crates/compiler/gen_dev/src/generic64/mod.rs | 5 ++++ crates/compiler/gen_dev/src/lib.rs | 7 +++++ crates/compiler/test_gen/src/gen_str.rs | 28 ++++++++++---------- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 7b057f8ac4..a59a04a7cb 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -2072,6 +2072,11 @@ impl< let val = *x; ASM::mov_reg64_imm64(&mut self.buf, reg, i128::from_ne_bytes(val) as i64); } + (Literal::Byte(x), Layout::Builtin(Builtin::Int(IntWidth::U8 | IntWidth::I8))) => { + let reg = self.storage_manager.claim_general_reg(&mut self.buf, sym); + let val = *x; + ASM::mov_reg64_imm64(&mut self.buf, reg, val as i64); + } (Literal::Bool(x), Layout::Builtin(Builtin::Bool)) => { let reg = self.storage_manager.claim_general_reg(&mut self.buf, sym); let val = [*x as u8; 16]; diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index 9c532a8b4c..26e25e66e8 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -784,6 +784,13 @@ trait Backend<'a> { arg_layouts, ret_layout, ), + LowLevel::StrJoinWith => self.build_fn_call( + sym, + bitcode::STR_JOIN_WITH.to_string(), + args, + arg_layouts, + ret_layout, + ), LowLevel::StrSplit => self.build_fn_call( sym, bitcode::STR_SPLIT.to_string(), diff --git a/crates/compiler/test_gen/src/gen_str.rs b/crates/compiler/test_gen/src/gen_str.rs index 9e247c7cda..dd40482a79 100644 --- a/crates/compiler/test_gen/src/gen_str.rs +++ b/crates/compiler/test_gen/src/gen_str.rs @@ -593,7 +593,7 @@ fn str_from_utf8_pass_single_ascii() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_pass_many_ascii() { assert_evals_to!( indoc!( @@ -609,7 +609,7 @@ fn str_from_utf8_pass_many_ascii() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_pass_single_unicode() { assert_evals_to!( indoc!( @@ -625,7 +625,7 @@ fn str_from_utf8_pass_single_unicode() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_pass_many_unicode() { assert_evals_to!( indoc!( @@ -641,7 +641,7 @@ fn str_from_utf8_pass_many_unicode() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_pass_single_grapheme() { assert_evals_to!( indoc!( @@ -657,7 +657,7 @@ fn str_from_utf8_pass_single_grapheme() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_pass_many_grapheme() { assert_evals_to!( indoc!( @@ -673,7 +673,7 @@ fn str_from_utf8_pass_many_grapheme() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_pass_all() { assert_evals_to!( indoc!( @@ -689,7 +689,7 @@ fn str_from_utf8_pass_all() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_fail_invalid_start_byte() { assert_evals_to!( indoc!( @@ -709,7 +709,7 @@ fn str_from_utf8_fail_invalid_start_byte() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_fail_unexpected_end_of_sequence() { assert_evals_to!( indoc!( @@ -729,7 +729,7 @@ fn str_from_utf8_fail_unexpected_end_of_sequence() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_fail_expected_continuation() { assert_evals_to!( indoc!( @@ -749,7 +749,7 @@ fn str_from_utf8_fail_expected_continuation() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_fail_overlong_encoding() { assert_evals_to!( indoc!( @@ -769,7 +769,7 @@ fn str_from_utf8_fail_overlong_encoding() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_fail_codepoint_too_large() { assert_evals_to!( indoc!( @@ -789,7 +789,7 @@ fn str_from_utf8_fail_codepoint_too_large() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_fail_surrogate_half() { assert_evals_to!( indoc!( @@ -865,7 +865,7 @@ fn nested_recursive_literal() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_join_comma_small() { assert_evals_to!( r#"Str.joinWith ["1", "2"] ", " "#, @@ -885,7 +885,7 @@ fn str_join_comma_big() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_join_comma_single() { assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr); } From 1e1da8a61b18aa8c2fca4c331d7eed954b785275 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 11 Feb 2023 17:17:39 +0100 Subject: [PATCH 09/48] reduce linecount --- .../compiler/gen_dev/src/generic64/x86_64.rs | 102 +++++++++--------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index eed30868b5..e9050428b9 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -2199,6 +2199,54 @@ fn mov_base8_offset32_reg8( buf.extend(offset.to_le_bytes()); } +enum RegisterWidth { + W8, + W16, + W32, + W64, +} + +#[inline(always)] +fn mov_reg_base_offset32( + buf: &mut Vec<'_, u8>, + register_width: RegisterWidth, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + use RegisterWidth::*; + + let rex = match register_width { + W64 => REX_W, + _ => REX, + }; + + let rex = add_rm_extension(base, rex); + let rex = add_reg_extension(dst, rex); + + let dst_mod = (dst as u8 % 8) << 3; + let base_mod = base as u8 % 8; + let operands = 0x80 | dst_mod | base_mod; + + buf.reserve(8); + + let instruction = match register_width { + W8 => 0x8A, + W16 | W32 | W64 => 0x8B, + }; + + match register_width { + W16 => buf.extend([GRP_4, rex, instruction, operands]), + _ => buf.extend([rex, instruction, operands]), + }; + + // Using RSP or R12 requires a secondary index byte. + if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + buf.push(0x24); + } + buf.extend(offset.to_le_bytes()); +} + /// `MOV r64,r/m64` -> Move r/m64 to r64, where m64 references a base + offset. #[inline(always)] fn mov_reg64_base64_offset32( @@ -2207,17 +2255,7 @@ fn mov_reg64_base64_offset32( base: X86_64GeneralReg, offset: i32, ) { - let rex = add_rm_extension(base, REX_W); - let rex = add_reg_extension(dst, rex); - let dst_mod = (dst as u8 % 8) << 3; - let base_mod = base as u8 % 8; - buf.reserve(8); - buf.extend([rex, 0x8B, 0x80 | dst_mod | base_mod]); - // Using RSP or R12 requires a secondary index byte. - if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { - buf.push(0x24); - } - buf.extend(offset.to_le_bytes()); + mov_reg_base_offset32(buf, RegisterWidth::W64, dst, base, offset) } /// `MOV r/m32,r32` -> Move r32 to r/m32. @@ -2228,19 +2266,7 @@ fn mov_reg32_base32_offset32( base: X86_64GeneralReg, offset: i32, ) { - let rex = add_rm_extension(base, REX); - let rex = add_reg_extension(dst, rex); - let dst_mod = (dst as u8 % 8) << 3; // (dst as u8 % 8) << 3; - let base_mod = base as u8 % 8; - buf.reserve(8); - - buf.extend([rex, 0x8B, 0x80 | dst_mod | base_mod]); - // Using RSP or R12 requires a secondary index byte. - // if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { - if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { - buf.push(0x24); - } - buf.extend(offset.to_le_bytes()); + mov_reg_base_offset32(buf, RegisterWidth::W32, dst, base, offset) } /// `MOV r/m16,r16` -> Move r16 to r/m16. @@ -2251,19 +2277,7 @@ fn mov_reg16_base16_offset32( base: X86_64GeneralReg, offset: i32, ) { - let rex = add_rm_extension(base, REX); - let rex = add_reg_extension(dst, rex); - let dst_mod = (dst as u8 % 8) << 3; // (dst as u8 % 8) << 3; - let base_mod = base as u8 % 8; - buf.reserve(8); - - buf.extend([0x66, rex, 0x8B, 0x80 | dst_mod | base_mod]); - // Using RSP or R12 requires a secondary index byte. - // if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { - if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { - buf.push(0x24); - } - buf.extend(offset.to_le_bytes()); + mov_reg_base_offset32(buf, RegisterWidth::W16, dst, base, offset) } /// `MOV r/m16,r16` -> Move r16 to r/m16. @@ -2274,19 +2288,7 @@ fn mov_reg8_base8_offset32( base: X86_64GeneralReg, offset: i32, ) { - let rex = add_rm_extension(base, REX); - let rex = add_reg_extension(dst, rex); - let dst_mod = (dst as u8 % 8) << 3; // (dst as u8 % 8) << 3; - let base_mod = base as u8 % 8; - buf.reserve(8); - - buf.extend([rex, 0x8A, 0x80 | dst_mod | base_mod]); - // Using RSP or R12 requires a secondary index byte. - // if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { - if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { - buf.push(0x24); - } - buf.extend(offset.to_le_bytes()); + mov_reg_base_offset32(buf, RegisterWidth::W8, dst, base, offset) } /// `MOVZX r64,r/m8` -> Move r/m8 with zero extention to r64, where m8 references a base + offset. From 4afeb1d67a123a1e5db6b993f96050d11aa6928b Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 11 Feb 2023 18:20:21 +0100 Subject: [PATCH 10/48] store big strings on the heap; enable a lot more string tests --- crates/compiler/gen_dev/src/generic64/mod.rs | 64 +++++++------ crates/compiler/gen_dev/src/lib.rs | 88 ++++++++++++++++++ crates/compiler/test_gen/src/gen_str.rs | 95 ++++++++++---------- 3 files changed, 173 insertions(+), 74 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index a59a04a7cb..f5812b12bb 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -2,7 +2,7 @@ use crate::{ single_register_floats, single_register_int_builtins, single_register_integers, Backend, Env, Relocation, }; -use bumpalo::collections::Vec; +use bumpalo::collections::{CollectIn, Vec}; use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_collections::all::MutMap; use roc_error_macros::internal_error; @@ -1832,7 +1832,7 @@ impl< &mut self, sym: &Symbol, element_in_layout: &InLayout<'a>, - elements: &'a [ListLiteralElement<'a>], + elements: &[ListLiteralElement<'a>], ) { let element_layout = self.layout_interner.get(*element_in_layout); let element_width = self.layout_interner.stack_size(*element_in_layout) as u64; @@ -2092,33 +2092,45 @@ impl< let val = *x as f32; ASM::mov_freg32_imm32(&mut self.buf, &mut self.relocs, reg, val); } - (Literal::Str(x), Layout::Builtin(Builtin::Str)) if x.len() < 24 => { - // Load small string. - self.storage_manager.with_tmp_general_reg( - &mut self.buf, - |storage_manager, buf, reg| { - let base_offset = storage_manager.claim_stack_area(sym, 24); - let mut bytes = [0; 24]; - bytes[..x.len()].copy_from_slice(x.as_bytes()); - bytes[23] = (x.len() as u8) | 0b1000_0000; + (Literal::Str(x), Layout::Builtin(Builtin::Str)) => { + if x.len() < 24 { + // Load small string. + self.storage_manager.with_tmp_general_reg( + &mut self.buf, + |storage_manager, buf, reg| { + let base_offset = storage_manager.claim_stack_area(sym, 24); + let mut bytes = [0; 24]; + bytes[..x.len()].copy_from_slice(x.as_bytes()); + bytes[23] = (x.len() as u8) | 0b1000_0000; - let mut num_bytes = [0; 8]; - num_bytes.copy_from_slice(&bytes[..8]); - let num = i64::from_ne_bytes(num_bytes); - ASM::mov_reg64_imm64(buf, reg, num); - ASM::mov_base32_reg64(buf, base_offset, reg); + let mut num_bytes = [0; 8]; + num_bytes.copy_from_slice(&bytes[..8]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(buf, reg, num); + ASM::mov_base32_reg64(buf, base_offset, reg); - num_bytes.copy_from_slice(&bytes[8..16]); - let num = i64::from_ne_bytes(num_bytes); - ASM::mov_reg64_imm64(buf, reg, num); - ASM::mov_base32_reg64(buf, base_offset + 8, reg); + num_bytes.copy_from_slice(&bytes[8..16]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(buf, reg, num); + ASM::mov_base32_reg64(buf, base_offset + 8, reg); - num_bytes.copy_from_slice(&bytes[16..]); - let num = i64::from_ne_bytes(num_bytes); - ASM::mov_reg64_imm64(buf, reg, num); - ASM::mov_base32_reg64(buf, base_offset + 16, reg); - }, - ); + num_bytes.copy_from_slice(&bytes[16..]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(buf, reg, num); + ASM::mov_base32_reg64(buf, base_offset + 16, reg); + }, + ); + } else { + // load large string (pretend it's a `List U8`). We should move this data into + // the binary eventually because our RC algorithm won't free this value + let elements: Vec<_> = x + .as_bytes() + .iter() + .map(|b| ListLiteralElement::Literal(Literal::Byte(*b))) + .collect_in(self.storage_manager.env.arena); + + self.create_array(sym, &Layout::U8, elements.into_bump_slice()) + } } x => todo!("loading literal, {:?}", x), } diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index 26e25e66e8..b19c6c4667 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -833,6 +833,94 @@ trait Backend<'a> { arg_layouts, ret_layout, ), + // LowLevel::StrToUtf8 => self.build_fn_call( + // sym, + // bitcode::STR_TO_UTF8.to_string(), + // args, + // arg_layouts, + // ret_layout, + // ), + LowLevel::StrRepeat => self.build_fn_call( + sym, + bitcode::STR_REPEAT.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrTrim => self.build_fn_call( + sym, + bitcode::STR_TRIM.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrTrimLeft => self.build_fn_call( + sym, + bitcode::STR_TRIM_LEFT.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrTrimRight => self.build_fn_call( + sym, + bitcode::STR_TRIM_RIGHT.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrReserve => self.build_fn_call( + sym, + bitcode::STR_RESERVE.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrWithCapacity => self.build_fn_call( + sym, + bitcode::STR_WITH_CAPACITY.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrToScalars => self.build_fn_call( + sym, + bitcode::STR_TO_SCALARS.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrGetUnsafe => self.build_fn_call( + sym, + bitcode::STR_GET_UNSAFE.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrGetScalarUnsafe => self.build_fn_call( + sym, + bitcode::STR_GET_SCALAR_UNSAFE.to_string(), + args, + arg_layouts, + ret_layout, + ), + // LowLevel::StrToNum => { + // let number_layout = match self.interner().get(*ret_layout) { + // Layout::Struct { field_layouts, .. } => field_layouts[0], // TODO: why is it sometimes a struct? + // _ => unreachable!(), + // }; + // + // // match on the return layout to figure out which zig builtin we need + // let intrinsic = match self.interner().get(number_layout) { + // Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], + // Layout::Builtin(Builtin::Float(float_width)) => { + // &bitcode::STR_TO_FLOAT[float_width] + // } + // Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, + // _ => unreachable!(), + // }; + // + // self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout) + // } LowLevel::PtrCast => { debug_assert_eq!( 1, diff --git a/crates/compiler/test_gen/src/gen_str.rs b/crates/compiler/test_gen/src/gen_str.rs index dd40482a79..979f8d77d1 100644 --- a/crates/compiler/test_gen/src/gen_str.rs +++ b/crates/compiler/test_gen/src/gen_str.rs @@ -16,7 +16,7 @@ use indoc::indoc; use roc_std::{RocList, RocResult, RocStr}; #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_empty_delimiter() { assert_evals_to!( indoc!( @@ -46,7 +46,7 @@ fn str_split_empty_delimiter() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_bigger_delimiter_small_str() { assert_evals_to!( indoc!( @@ -110,7 +110,7 @@ fn str_split_small_str_bigger_delimiter() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_big_str_small_delimiter() { assert_evals_to!( indoc!( @@ -154,7 +154,7 @@ fn str_split_small_str_small_delimiter() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_bigger_delimiter_big_strs() { assert_evals_to!( indoc!( @@ -198,7 +198,7 @@ fn str_split_minimal_example() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_small_str_big_delimiter() { assert_evals_to!( indoc!( @@ -227,7 +227,7 @@ fn str_split_small_str_big_delimiter() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_small_str_20_char_delimiter() { assert_evals_to!( indoc!( @@ -243,7 +243,7 @@ fn str_split_small_str_20_char_delimiter() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_concat_big_to_big() { assert_evals_to!( indoc!( @@ -402,7 +402,7 @@ fn small_str_concat_empty_second_arg() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn small_str_concat_small_to_big() { assert_evals_to!( r#"Str.concat "abc" " this is longer than 15 chars""#, @@ -530,7 +530,7 @@ fn str_count_graphemes_three_js() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_count_graphemes_big_str() { assert_evals_to!( r#"Str.countGraphemes "6🤔å🤔e¥🤔çppkd🙃1jdal🦯asdfa∆ltråø˚waia8918.,🏅jjc""#, @@ -540,7 +540,7 @@ fn str_count_graphemes_big_str() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_starts_with_same_big_str() { assert_evals_to!( r#"Str.startsWith "123456789123456789" "123456789123456789""#, @@ -550,7 +550,7 @@ fn str_starts_with_same_big_str() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_starts_with_different_big_str() { assert_evals_to!( r#"Str.startsWith "12345678912345678910" "123456789123456789""#, @@ -560,18 +560,18 @@ fn str_starts_with_different_big_str() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_starts_with_same_small_str() { assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_starts_with_different_small_str() { assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_starts_with_false_small_str() { assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool); } @@ -875,7 +875,7 @@ fn str_join_comma_small() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_join_comma_big() { assert_evals_to!( r#"Str.joinWith ["10000000", "2000000", "30000000"] ", " "#, @@ -1031,7 +1031,7 @@ fn str_from_utf8_range_count_too_high_for_start() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_repeat_small_stays_small() { assert_evals_to!( indoc!(r#"Str.repeat "Roc" 3"#), @@ -1041,7 +1041,7 @@ fn str_repeat_small_stays_small() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_repeat_small_becomes_big() { assert_evals_to!( indoc!(r#"Str.repeat "less than 23 characters" 2"#), @@ -1051,7 +1051,7 @@ fn str_repeat_small_becomes_big() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_repeat_big() { assert_evals_to!( indoc!(r#"Str.repeat "more than 23 characters now" 2"#), @@ -1061,27 +1061,26 @@ fn str_repeat_big() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_repeat_empty_string() { let a = indoc!(r#"Str.repeat "" 3"#); - let b = RocStr::from(""); - assert_evals_to!(a, b, RocStr); + assert_evals_to!(a, RocStr::from(""), RocStr); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_repeat_zero_times() { assert_evals_to!(indoc!(r#"Str.repeat "Roc" 0"#), RocStr::from(""), RocStr); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_empty_string() { assert_evals_to!(indoc!(r#"Str.trim """#), RocStr::from(""), RocStr); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_null_byte() { assert_evals_to!( indoc!(r#"Str.trim (Str.reserve "\u(0000)" 40)"#), @@ -1091,13 +1090,13 @@ fn str_trim_null_byte() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_small_blank_string() { assert_evals_to!(indoc!(r#"Str.trim " ""#), RocStr::from(""), RocStr); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_small_to_small() { assert_evals_to!( indoc!(r#"Str.trim " hello world ""#), @@ -1107,7 +1106,7 @@ fn str_trim_small_to_small() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_large_to_large_unique() { assert_evals_to!( indoc!(r#"Str.trim (Str.concat " " "hello world from a large string ")"#), @@ -1117,7 +1116,7 @@ fn str_trim_large_to_large_unique() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_large_to_small_unique() { assert_evals_to!( indoc!(r#"Str.trim (Str.concat " " "hello world ")"#), @@ -1184,13 +1183,13 @@ fn str_trim_small_to_small_shared() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_left_small_blank_string() { assert_evals_to!(indoc!(r#"Str.trimLeft " ""#), RocStr::from(""), RocStr); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_left_small_to_small() { assert_evals_to!( indoc!(r#"Str.trimLeft " hello world ""#), @@ -1200,7 +1199,7 @@ fn str_trim_left_small_to_small() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_left_large_to_large_unique() { assert_evals_to!( indoc!(r#"Str.trimLeft (Str.concat " " "hello world from a large string ")"#), @@ -1210,7 +1209,7 @@ fn str_trim_left_large_to_large_unique() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_left_large_to_small_unique() { assert_evals_to!( indoc!(r#"Str.trimLeft (Str.concat " " "hello world ")"#), @@ -1277,13 +1276,13 @@ fn str_trim_left_small_to_small_shared() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_right_small_blank_string() { assert_evals_to!(indoc!(r#"Str.trimRight " ""#), RocStr::from(""), RocStr); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_right_small_to_small() { assert_evals_to!( indoc!(r#"Str.trimRight " hello world ""#), @@ -1293,7 +1292,7 @@ fn str_trim_right_small_to_small() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_right_large_to_large_unique() { assert_evals_to!( indoc!(r#"Str.trimRight (Str.concat " hello world from a large string" " ")"#), @@ -1303,7 +1302,7 @@ fn str_trim_right_large_to_large_unique() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_right_large_to_small_unique() { assert_evals_to!( indoc!(r#"Str.trimRight (Str.concat " hello world" " ")"#), @@ -1585,7 +1584,7 @@ fn str_to_dec() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn issue_2811() { assert_evals_to!( indoc!( @@ -1601,7 +1600,7 @@ fn issue_2811() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn to_scalar_1_byte() { assert_evals_to!( indoc!( @@ -1625,7 +1624,7 @@ fn to_scalar_1_byte() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn to_scalar_2_byte() { assert_evals_to!( indoc!( @@ -1649,7 +1648,7 @@ fn to_scalar_2_byte() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn to_scalar_3_byte() { assert_evals_to!( indoc!( @@ -1673,7 +1672,7 @@ fn to_scalar_3_byte() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn to_scalar_4_byte() { // from https://design215.com/toolbox/utf8-4byte-characters.php assert_evals_to!( @@ -1812,7 +1811,7 @@ fn str_split_last_not_found() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_overlapping_substring_1() { assert_evals_to!( r#"Str.split "aaa" "aa""#, @@ -1822,7 +1821,7 @@ fn str_split_overlapping_substring_1() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_overlapping_substring_2() { assert_evals_to!( r#"Str.split "aaaa" "aa""#, @@ -1951,7 +1950,7 @@ fn when_on_strings() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn with_capacity() { assert_evals_to!( indoc!( @@ -1965,7 +1964,7 @@ fn with_capacity() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn with_capacity_concat() { assert_evals_to!( indoc!( @@ -1979,7 +1978,7 @@ fn with_capacity_concat() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn str_with_prefix() { assert_evals_to!( indoc!( @@ -2003,7 +2002,7 @@ fn str_with_prefix() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn destructure_pattern_assigned_from_thunk_opaque() { assert_evals_to!( indoc!( @@ -2025,7 +2024,7 @@ fn destructure_pattern_assigned_from_thunk_opaque() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn destructure_pattern_assigned_from_thunk_tag() { assert_evals_to!( indoc!( From f6fbfa002bfa3bbcaf120b7508048dae281e79d0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 11 Feb 2023 18:32:24 +0100 Subject: [PATCH 11/48] implment 8bit logical operators --- .../compiler/gen_dev/src/generic64/aarch64.rs | 27 ++++++ crates/compiler/gen_dev/src/generic64/mod.rs | 21 +++++ .../compiler/gen_dev/src/generic64/x86_64.rs | 89 +++++++++++++++++++ crates/compiler/test_gen/src/gen_str.rs | 2 +- 4 files changed, 138 insertions(+), 1 deletion(-) diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index 8c75095b38..5c601f03cc 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -963,6 +963,33 @@ impl Assembler for AArch64Assembler { ret_reg64(buf, AArch64GeneralReg::LR) } + fn and_reg8_reg8_reg8( + _buf: &mut Vec<'_, u8>, + _dst: AArch64GeneralReg, + _src1: AArch64GeneralReg, + _src2: AArch64GeneralReg, + ) { + todo!("bitwise and for AArch64") + } + + fn or_reg8_reg8_reg8( + _buf: &mut Vec<'_, u8>, + _dst: AArch64GeneralReg, + _src1: AArch64GeneralReg, + _src2: AArch64GeneralReg, + ) { + todo!("bitwise and for AArch64") + } + + fn xor_reg8_reg8_reg8( + _buf: &mut Vec<'_, u8>, + _dst: AArch64GeneralReg, + _src1: AArch64GeneralReg, + _src2: AArch64GeneralReg, + ) { + todo!("bitwise and for AArch64") + } + fn and_reg64_reg64_reg64( _buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index f5812b12bb..80825e38d9 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -148,6 +148,27 @@ pub trait Assembler: Sized + Copy { src2: GeneralReg, ); + fn and_reg8_reg8_reg8( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + src1: GeneralReg, + src2: GeneralReg, + ); + + fn or_reg8_reg8_reg8( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + src1: GeneralReg, + src2: GeneralReg, + ); + + fn xor_reg8_reg8_reg8( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + src1: GeneralReg, + src2: GeneralReg, + ); + fn and_reg64_reg64_reg64( buf: &mut Vec<'_, u8>, dst: GeneralReg, diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index e9050428b9..5d0e75d68f 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -1567,6 +1567,18 @@ impl Assembler for X86_64Assembler { seto_reg64(buf, dst); } + fn and_reg8_reg8_reg8(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) { + binop_move_src_to_dst_reg64(buf, and_reg8_reg8, dst, src1, src2) + } + + fn or_reg8_reg8_reg8(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) { + binop_move_src_to_dst_reg64(buf, or_reg8_reg8, dst, src1, src2) + } + + fn xor_reg8_reg8_reg8(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) { + binop_move_src_to_dst_reg64(buf, xor_reg8_reg8, dst, src1, src2) + } + fn and_reg64_reg64_reg64(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) { binop_move_src_to_dst_reg64(buf, and_reg64_reg64, dst, src1, src2) } @@ -1711,6 +1723,20 @@ fn add_reg_extension(reg: T, byte: u8) -> u8 { } } +#[inline(always)] +fn binop_reg8_reg8( + op_code: u8, + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + src: X86_64GeneralReg, +) { + let rex = add_rm_extension(dst, REX); + 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, op_code, 0xC0 | dst_mod | src_mod]); +} + #[inline(always)] fn binop_reg64_reg64( op_code: u8, @@ -1762,6 +1788,27 @@ fn add_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64Gene binop_reg64_reg64(0x01, buf, dst, src); } +/// `AND r/m8,r8` -> Bitwise logical and r8 to r/m8. +#[inline(always)] +fn and_reg8_reg8(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { + // NOTE: src and dst are flipped by design + binop_reg8_reg8(0x22, buf, src, dst); +} + +/// `OR r/m8,r8` -> Bitwise logical inclusive or r8 to r/m8. +#[inline(always)] +fn or_reg8_reg8(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { + // NOTE: src and dst are flipped by design + binop_reg8_reg8(0x0A, buf, src, dst); +} + +/// `XOR r/m8,r8` -> Bitwise logical exclusive or r8 to r/m8. +#[inline(always)] +fn xor_reg8_reg8(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { + // NOTE: src and dst are flipped by design + binop_reg8_reg8(0x32, buf, src, dst); +} + /// `AND r/m64,r64` -> Bitwise logical and r64 to r/m64. #[inline(always)] fn and_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { @@ -2842,6 +2889,48 @@ mod tests { ); } + #[test] + fn test_and_reg8_reg8() { + disassembler_test!( + and_reg8_reg8, + |reg1, reg2| format!( + "and {}, {}", + X86_64GeneralReg::low_8bits_string(®1), + X86_64GeneralReg::low_8bits_string(®2), + ), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS + ); + } + + #[test] + fn test_or_reg8_reg8() { + disassembler_test!( + or_reg8_reg8, + |reg1, reg2| format!( + "or {}, {}", + X86_64GeneralReg::low_8bits_string(®1), + X86_64GeneralReg::low_8bits_string(®2), + ), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS + ); + } + + #[test] + fn test_xor_reg8_reg8() { + disassembler_test!( + xor_reg8_reg8, + |reg1, reg2| format!( + "xor {}, {}", + X86_64GeneralReg::low_8bits_string(®1), + X86_64GeneralReg::low_8bits_string(®2), + ), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS + ); + } + #[test] fn test_or_reg64_reg64() { disassembler_test!( diff --git a/crates/compiler/test_gen/src/gen_str.rs b/crates/compiler/test_gen/src/gen_str.rs index 979f8d77d1..3e71085c79 100644 --- a/crates/compiler/test_gen/src/gen_str.rs +++ b/crates/compiler/test_gen/src/gen_str.rs @@ -1857,7 +1857,7 @@ fn str_walk_utf8_with_index() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_append_scalar() { assert_evals_to!( indoc!( From 92539fe3b0aadabcf3c0cb22b93d714e074c7e79 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 11 Feb 2023 18:32:33 +0100 Subject: [PATCH 12/48] Revert "implment 8bit logical operators" This reverts commit 87c49679e1ac5bcfb710b6d432a9f503db3ee501. --- .../compiler/gen_dev/src/generic64/aarch64.rs | 27 ------ crates/compiler/gen_dev/src/generic64/mod.rs | 21 ----- .../compiler/gen_dev/src/generic64/x86_64.rs | 89 ------------------- crates/compiler/test_gen/src/gen_str.rs | 2 +- 4 files changed, 1 insertion(+), 138 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index 5c601f03cc..8c75095b38 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -963,33 +963,6 @@ impl Assembler for AArch64Assembler { ret_reg64(buf, AArch64GeneralReg::LR) } - fn and_reg8_reg8_reg8( - _buf: &mut Vec<'_, u8>, - _dst: AArch64GeneralReg, - _src1: AArch64GeneralReg, - _src2: AArch64GeneralReg, - ) { - todo!("bitwise and for AArch64") - } - - fn or_reg8_reg8_reg8( - _buf: &mut Vec<'_, u8>, - _dst: AArch64GeneralReg, - _src1: AArch64GeneralReg, - _src2: AArch64GeneralReg, - ) { - todo!("bitwise and for AArch64") - } - - fn xor_reg8_reg8_reg8( - _buf: &mut Vec<'_, u8>, - _dst: AArch64GeneralReg, - _src1: AArch64GeneralReg, - _src2: AArch64GeneralReg, - ) { - todo!("bitwise and for AArch64") - } - fn and_reg64_reg64_reg64( _buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 80825e38d9..f5812b12bb 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -148,27 +148,6 @@ pub trait Assembler: Sized + Copy { src2: GeneralReg, ); - fn and_reg8_reg8_reg8( - buf: &mut Vec<'_, u8>, - dst: GeneralReg, - src1: GeneralReg, - src2: GeneralReg, - ); - - fn or_reg8_reg8_reg8( - buf: &mut Vec<'_, u8>, - dst: GeneralReg, - src1: GeneralReg, - src2: GeneralReg, - ); - - fn xor_reg8_reg8_reg8( - buf: &mut Vec<'_, u8>, - dst: GeneralReg, - src1: GeneralReg, - src2: GeneralReg, - ); - fn and_reg64_reg64_reg64( buf: &mut Vec<'_, u8>, dst: GeneralReg, diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index 5d0e75d68f..e9050428b9 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -1567,18 +1567,6 @@ impl Assembler for X86_64Assembler { seto_reg64(buf, dst); } - fn and_reg8_reg8_reg8(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) { - binop_move_src_to_dst_reg64(buf, and_reg8_reg8, dst, src1, src2) - } - - fn or_reg8_reg8_reg8(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) { - binop_move_src_to_dst_reg64(buf, or_reg8_reg8, dst, src1, src2) - } - - fn xor_reg8_reg8_reg8(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) { - binop_move_src_to_dst_reg64(buf, xor_reg8_reg8, dst, src1, src2) - } - fn and_reg64_reg64_reg64(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) { binop_move_src_to_dst_reg64(buf, and_reg64_reg64, dst, src1, src2) } @@ -1723,20 +1711,6 @@ fn add_reg_extension(reg: T, byte: u8) -> u8 { } } -#[inline(always)] -fn binop_reg8_reg8( - op_code: u8, - buf: &mut Vec<'_, u8>, - dst: X86_64GeneralReg, - src: X86_64GeneralReg, -) { - let rex = add_rm_extension(dst, REX); - 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, op_code, 0xC0 | dst_mod | src_mod]); -} - #[inline(always)] fn binop_reg64_reg64( op_code: u8, @@ -1788,27 +1762,6 @@ fn add_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64Gene binop_reg64_reg64(0x01, buf, dst, src); } -/// `AND r/m8,r8` -> Bitwise logical and r8 to r/m8. -#[inline(always)] -fn and_reg8_reg8(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { - // NOTE: src and dst are flipped by design - binop_reg8_reg8(0x22, buf, src, dst); -} - -/// `OR r/m8,r8` -> Bitwise logical inclusive or r8 to r/m8. -#[inline(always)] -fn or_reg8_reg8(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { - // NOTE: src and dst are flipped by design - binop_reg8_reg8(0x0A, buf, src, dst); -} - -/// `XOR r/m8,r8` -> Bitwise logical exclusive or r8 to r/m8. -#[inline(always)] -fn xor_reg8_reg8(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { - // NOTE: src and dst are flipped by design - binop_reg8_reg8(0x32, buf, src, dst); -} - /// `AND r/m64,r64` -> Bitwise logical and r64 to r/m64. #[inline(always)] fn and_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { @@ -2889,48 +2842,6 @@ mod tests { ); } - #[test] - fn test_and_reg8_reg8() { - disassembler_test!( - and_reg8_reg8, - |reg1, reg2| format!( - "and {}, {}", - X86_64GeneralReg::low_8bits_string(®1), - X86_64GeneralReg::low_8bits_string(®2), - ), - ALL_GENERAL_REGS, - ALL_GENERAL_REGS - ); - } - - #[test] - fn test_or_reg8_reg8() { - disassembler_test!( - or_reg8_reg8, - |reg1, reg2| format!( - "or {}, {}", - X86_64GeneralReg::low_8bits_string(®1), - X86_64GeneralReg::low_8bits_string(®2), - ), - ALL_GENERAL_REGS, - ALL_GENERAL_REGS - ); - } - - #[test] - fn test_xor_reg8_reg8() { - disassembler_test!( - xor_reg8_reg8, - |reg1, reg2| format!( - "xor {}, {}", - X86_64GeneralReg::low_8bits_string(®1), - X86_64GeneralReg::low_8bits_string(®2), - ), - ALL_GENERAL_REGS, - ALL_GENERAL_REGS - ); - } - #[test] fn test_or_reg64_reg64() { disassembler_test!( diff --git a/crates/compiler/test_gen/src/gen_str.rs b/crates/compiler/test_gen/src/gen_str.rs index 3e71085c79..979f8d77d1 100644 --- a/crates/compiler/test_gen/src/gen_str.rs +++ b/crates/compiler/test_gen/src/gen_str.rs @@ -1857,7 +1857,7 @@ fn str_walk_utf8_with_index() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm"))] fn str_append_scalar() { assert_evals_to!( indoc!( From e48a604862dae37b4b38174e91ffdae6dbdfc34c Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 11 Feb 2023 18:39:30 +0100 Subject: [PATCH 13/48] add Str.appendScalar --- crates/compiler/gen_dev/src/lib.rs | 28 +++++++++++++++++++++++++ crates/compiler/module/src/symbol.rs | 1 + crates/compiler/test_gen/src/gen_str.rs | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index b19c6c4667..4539f4ee9d 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -572,6 +572,20 @@ trait Backend<'a> { internal_error!("bitwise xor on a non-integer") } } + LowLevel::And => { + if let Layout::Builtin(Builtin::Bool) = self.interner().get(*ret_layout) { + self.build_int_bitwise_and(sym, &args[0], &args[1], IntWidth::U8) + } else { + internal_error!("bitwise and on a non-integer") + } + } + LowLevel::Or => { + if let Layout::Builtin(Builtin::Bool) = self.interner().get(*ret_layout) { + self.build_int_bitwise_or(sym, &args[0], &args[1], IntWidth::U8) + } else { + internal_error!("bitwise or on a non-integer") + } + } LowLevel::NumShiftLeftBy => { if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) { self.build_int_shift_left(sym, &args[0], &args[1], int_width) @@ -812,6 +826,13 @@ trait Backend<'a> { arg_layouts, ret_layout, ), + LowLevel::StrAppendScalar => self.build_fn_call( + sym, + bitcode::STR_APPEND_SCALAR.to_string(), + args, + arg_layouts, + ret_layout, + ), LowLevel::StrEndsWith => self.build_fn_call( sym, bitcode::STR_ENDS_WITH.to_string(), @@ -1006,6 +1027,13 @@ trait Backend<'a> { self.return_symbol(&Symbol::DEV_TMP, &bool_layout); self.free_symbol(&Symbol::DEV_TMP) } + Symbol::STR_IS_VALID_SCALAR => { + let layout_id = LayoutIds::default().get(func_sym, ret_layout); + let fn_name = self.symbol_to_string(func_sym, layout_id); + // Now that the arguments are needed, load them if they are literals. + self.load_literal_symbols(args); + self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout) + } _ => todo!("the function, {:?}", func_sym), } } diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index 4ab92ec50c..ca6d0fb1fa 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -1319,6 +1319,7 @@ define_builtins! { 53 STR_WITH_CAPACITY: "withCapacity" 54 STR_WITH_PREFIX: "withPrefix" 55 STR_GRAPHEMES: "graphemes" + 56 STR_IS_VALID_SCALAR: "isValidScalar" } 6 LIST: "List" => { 0 LIST_LIST: "List" exposed_apply_type=true // the List.List type alias diff --git a/crates/compiler/test_gen/src/gen_str.rs b/crates/compiler/test_gen/src/gen_str.rs index 979f8d77d1..3e71085c79 100644 --- a/crates/compiler/test_gen/src/gen_str.rs +++ b/crates/compiler/test_gen/src/gen_str.rs @@ -1857,7 +1857,7 @@ fn str_walk_utf8_with_index() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_append_scalar() { assert_evals_to!( indoc!( From a66320b00c173a0ccdb846c9dbab3ea30f76b5bc Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 11 Feb 2023 18:43:13 +0100 Subject: [PATCH 14/48] count utf bytes --- crates/compiler/gen_dev/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index 4539f4ee9d..60b0d4b247 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -847,6 +847,13 @@ trait Backend<'a> { arg_layouts, ret_layout, ), + LowLevel::StrCountUtf8Bytes => self.build_fn_call( + sym, + bitcode::STR_COUNT_UTF8_BYTES.to_string(), + args, + arg_layouts, + ret_layout, + ), LowLevel::StrFromUtf8Range => self.build_fn_call( sym, bitcode::STR_FROM_UTF8_RANGE.to_string(), From 9f5f80581ed73e693352bf74084950f9eee299f0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 11 Feb 2023 23:50:06 +0100 Subject: [PATCH 15/48] add test that passes a struct < 16 bytes --- crates/compiler/test_gen/src/gen_records.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/compiler/test_gen/src/gen_records.rs b/crates/compiler/test_gen/src/gen_records.rs index 9f1106e4a9..33f63a6d43 100644 --- a/crates/compiler/test_gen/src/gen_records.rs +++ b/crates/compiler/test_gen/src/gen_records.rs @@ -360,7 +360,7 @@ fn i64_record1_literal() { // ); // } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn bool_literal() { assert_evals_to!( indoc!( @@ -1111,3 +1111,22 @@ fn toplevel_accessor_fn_thunk() { u8 ) } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn pass_record_of_u8s() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + ra = \_ -> 1u8 + + main = + ra { a: 1u8, b: 0u8 } + "# + ), + true, + bool + ) +} From 78000b3f1924fd4524f3d368e565f19bec2c6c3d Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 12 Feb 2023 01:10:23 +0100 Subject: [PATCH 16/48] enable more list tests --- .../compiler/gen_dev/src/generic64/aarch64.rs | 10 ++ crates/compiler/gen_dev/src/generic64/mod.rs | 11 ++ .../compiler/gen_dev/src/generic64/x86_64.rs | 10 ++ crates/compiler/gen_dev/src/lib.rs | 21 +++ crates/compiler/test_gen/src/gen_list.rs | 145 ++++++++---------- 5 files changed, 118 insertions(+), 79 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index 8c75095b38..b5d5571f10 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -624,6 +624,16 @@ impl Assembler for AArch64Assembler { fn mov_base32_freg64(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64FloatReg) { todo!("saving floating point reg to base offset for AArch64"); } + #[inline(always)] + fn movesd_mem64_offset32_freg64( + _buf: &mut Vec<'_, u8>, + _ptr: AArch64GeneralReg, + _offset: i32, + _src: AArch64FloatReg, + ) { + todo!() + } + #[inline(always)] fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: AArch64GeneralReg) { if offset < 0 { diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index f5812b12bb..9af05a1cec 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -291,6 +291,13 @@ pub trait Assembler: Sized + Copy { ); fn mov_mem8_offset32_reg8(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, src: GeneralReg); + fn movesd_mem64_offset32_freg64( + buf: &mut Vec<'_, u8>, + ptr: GeneralReg, + offset: i32, + src: FloatReg, + ); + /// Sign extends the data at `offset` with `size` as it copies it to `dst` /// size must be less than or equal to 8. fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, size: u8); @@ -2505,6 +2512,10 @@ impl< let sym_reg = storage_manager.load_to_general_reg(buf, &value); ASM::mov_mem8_offset32_reg8(buf, ptr_reg, element_offset, sym_reg); } + Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { + let sym_reg = storage_manager.load_to_float_reg(buf, &value); + ASM::movesd_mem64_offset32_freg64(buf, ptr_reg, element_offset, sym_reg); + } _ if element_width == 0 => {} _ if element_width > 8 => { let (from_offset, size) = storage_manager.stack_offset_and_size(&value); diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index e9050428b9..c1d9e77d21 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -1294,6 +1294,16 @@ impl Assembler for X86_64Assembler { movsd_base64_offset32_freg64(buf, X86_64GeneralReg::RBP, offset, src) } + #[inline(always)] + fn movesd_mem64_offset32_freg64( + buf: &mut Vec<'_, u8>, + ptr: X86_64GeneralReg, + offset: i32, + src: X86_64FloatReg, + ) { + movsd_base64_offset32_freg64(buf, ptr, offset, src) + } + #[inline(always)] fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) { mov_base64_offset32_reg64(buf, X86_64GeneralReg::RBP, offset, src) diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index 60b0d4b247..77eec1fe02 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -551,6 +551,27 @@ trait Backend<'a> { ); self.build_num_sub_wrap(sym, &args[0], &args[1], ret_layout) } + LowLevel::NumSubSaturated => match self.interner().get(*ret_layout) { + Layout::Builtin(Builtin::Int(int_width)) => self.build_fn_call( + sym, + bitcode::NUM_SUB_SATURATED_INT[int_width].to_string(), + args, + arg_layouts, + ret_layout, + ), + Layout::Builtin(Builtin::Float(FloatWidth::F32)) => { + self.build_num_sub(sym, &args[0], &args[1], ret_layout) + } + Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { + // saturated sub is just normal sub + self.build_num_sub(sym, &args[0], &args[1], ret_layout) + } + Layout::Builtin(Builtin::Decimal) => { + // self.load_args_and_call_zig(backend, bitcode::DEC_SUB_SATURATED) + todo!() + } + _ => internal_error!("invalid return type"), + }, LowLevel::NumBitwiseAnd => { if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) { self.build_int_bitwise_and(sym, &args[0], &args[1], int_width) diff --git a/crates/compiler/test_gen/src/gen_list.rs b/crates/compiler/test_gen/src/gen_list.rs index bdbdd19153..251e1ec9b0 100644 --- a/crates/compiler/test_gen/src/gen_list.rs +++ b/crates/compiler/test_gen/src/gen_list.rs @@ -1859,13 +1859,11 @@ fn first_int_list() { assert_evals_to!( indoc!( r#" - when List.first [12, 9, 6, 3] is - Ok val -> val - Err _ -> -1 + List.first [12, 9, 6, 3] "# ), - 12, - i64 + RocResult::ok(12), + RocResult ); } @@ -1889,45 +1887,42 @@ fn first_wildcard_empty_list() { assert_evals_to!( indoc!( r#" - when List.first [] is - Ok _ -> 5 - Err _ -> -1 + List.first [] "# ), - -1, - i64 + RocResult::err(()), + RocResult ); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn first_empty_list() { assert_evals_to!( indoc!( r#" - when List.first [] is - Ok val -> val - Err _ -> -1 + list : List I64 + list = [] + + List.first list "# ), - -1, - i64 + RocResult::err(()), + RocResult ); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn last_int_list() { assert_evals_to!( indoc!( r#" - when List.last [12, 9, 6, 3] is - Ok val -> val - Err _ -> -1 + List.last [12, 9, 6, 3] "# ), - 3, - i64 + RocResult::ok(3), + RocResult ); } @@ -1937,13 +1932,11 @@ fn last_wildcard_empty_list() { assert_evals_to!( indoc!( r#" - when List.last [] is - Ok _ -> 5 - Err _ -> -1 + List.last [] "# ), - -1, - i64 + RocResult::err(()), + RocResult ); } @@ -1953,13 +1946,14 @@ fn last_empty_list() { assert_evals_to!( indoc!( r#" - when List.last [] is - Ok val -> val - Err _ -> -1 + list : List I64 + list = [] + + List.last list "# ), - -1, - i64 + RocResult::err(()), + RocResult ); } @@ -1969,29 +1963,32 @@ fn get_empty_list() { assert_evals_to!( indoc!( r#" - when List.get [] 0 is - Ok val -> val - Err _ -> -1 + list : List I64 + list = [] + + List.get list 0 "# ), - -1, - i64 + RocResult::err(()), + RocResult ); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn get_wildcard_empty_list() { + // NOTE: by default, the return type is `Result [] [NotFound]`, which is actually represented + // as just `[NotFound]`. Casting that to `RocResult<(), ()>` is invalid! But accepting any `()` + // would make the test pointless. Therefore, we must explicitly change the type on the roc side assert_evals_to!( indoc!( r#" - when List.get [] 0 is - Ok _ -> 5 - Err _ -> -1 + List.get [] 0 + |> Result.map (\_ -> {}) "# ), - -1, - i64 + RocResult::err(()), + RocResult<(), ()> ); } @@ -2010,39 +2007,35 @@ fn get_str_list_ok() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn get_int_list_ok() { assert_evals_to!( indoc!( r#" - when List.get [12, 9, 6] 1 is - Ok val -> val - Err _ -> -1 + List.get [12, 9, 6] 1 "# ), - 9, - i64 + RocResult::ok(9), + RocResult ); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn get_int_list_oob() { assert_evals_to!( indoc!( r#" - when List.get [12, 9, 6] 1000 is - Ok val -> val - Err _ -> -1 + List.get [12, 9, 6] 1000 "# ), - -1, - i64 + RocResult::err(()), + RocResult ); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn replace_unique_int_list() { assert_evals_to!( indoc!( @@ -2057,7 +2050,7 @@ fn replace_unique_int_list() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn replace_unique_int_list_out_of_bounds() { assert_evals_to!( indoc!( @@ -2072,7 +2065,7 @@ fn replace_unique_int_list_out_of_bounds() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn replace_unique_int_list_get_old_value() { assert_evals_to!( indoc!( @@ -2087,7 +2080,7 @@ fn replace_unique_int_list_get_old_value() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn replace_unique_get_large_value() { assert_evals_to!( indoc!( @@ -2138,29 +2131,25 @@ fn get_set_unique_int_list_i64() { assert_evals_to!( indoc!( r#" - when List.get (List.set [12, 9, 7, 3] 1 42) 1 is - Ok val -> val - Err _ -> -1 + List.get (List.set [12, 9, 7, 3] 1 42) 1 "# ), - 42, - i64 + RocResult::ok(42), + RocResult ); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn get_set_unique_int_list_i8() { assert_evals_to!( indoc!( r#" - when List.get (List.set [12, 9, 7, 3] 1 42i8) 1 is - Ok val -> val - Err _ -> -1i8 + List.get (List.set [12, 9, 7, 3] 1 42i8) 1 "# ), - 42, - i8 + RocResult::ok(42), + RocResult ); } @@ -2175,7 +2164,7 @@ fn set_unique_int_list() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn set_unique_list_oob() { assert_evals_to!( "List.set [3, 17, 4.1] 1337 9.25", @@ -2240,20 +2229,18 @@ fn set_shared_list_oob() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn get_unique_int_list() { assert_evals_to!( indoc!( r#" unique = [2, 4] - when List.get unique 1 is - Ok num -> num - Err _ -> -1 + List.get unique 1 "# ), - 4, - i64 + RocResult::ok(4), + RocResult ); } @@ -2275,7 +2262,7 @@ fn gen_wrap_len() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn gen_wrap_first() { assert_evals_to!( indoc!( @@ -2292,7 +2279,7 @@ fn gen_wrap_first() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn gen_duplicate() { assert_evals_to!( indoc!( @@ -3440,7 +3427,7 @@ fn with_capacity() { r#" l : List U64 l = List.withCapacity 10 - + l "# ), From 4b31c77b3720880dcb0e0e412ca8816d0b39c0ea Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 12 Feb 2023 02:06:20 +0100 Subject: [PATCH 17/48] paramter ownership is not important for codegen --- crates/compiler/gen_dev/src/generic64/storage.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs index 8e2ed687b1..f5738ea7b0 100644 --- a/crates/compiler/gen_dev/src/generic64/storage.rs +++ b/crates/compiler/gen_dev/src/generic64/storage.rs @@ -9,7 +9,6 @@ use roc_collections::all::{MutMap, MutSet}; use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::{ - borrow::Ownership, ir::{JoinPointId, Param}, layout::{ Builtin, InLayout, Layout, LayoutInterner, STLayoutInterner, TagIdIntType, UnionLayout, @@ -1083,15 +1082,10 @@ impl< param_storage.reserve(params.len()); for Param { symbol, - ownership, + ownership: _, layout, } in params { - if *ownership == Ownership::Borrowed { - // These probably need to be passed by pointer/reference? - // Otherwise, we probably need to copy back to the param at the end of the joinpoint. - todo!("joinpoints with borrowed parameters"); - } // Claim a location for every join point parameter to be loaded at. // Put everything on the stack for simplicity. match *layout { From 89201676ef8c30d4217721dbcd67e876992d9473 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 12 Feb 2023 02:06:44 +0100 Subject: [PATCH 18/48] make is_primitive look through LambdaSet --- crates/compiler/gen_dev/src/generic64/storage.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs index f5738ea7b0..343025082a 100644 --- a/crates/compiler/gen_dev/src/generic64/storage.rs +++ b/crates/compiler/gen_dev/src/generic64/storage.rs @@ -552,7 +552,7 @@ impl< self.allocation_map.insert(*sym, owned_data); self.symbol_storage_map.insert( *sym, - Stack(if is_primitive(layout) { + Stack(if is_primitive(layout_interner, layout) { ReferencedPrimitive { base_offset: data_offset, size, @@ -1400,6 +1400,15 @@ impl< } } -fn is_primitive(layout: InLayout<'_>) -> bool { - matches!(layout, single_register_layouts!()) +fn is_primitive(layout_interner: &mut STLayoutInterner<'_>, layout: InLayout<'_>) -> bool { + match layout { + single_register_layouts!() => true, + _ => match layout_interner.get(layout) { + Layout::Boxed(_) => true, + Layout::LambdaSet(lambda_set) => { + is_primitive(layout_interner, lambda_set.runtime_representation()) + } + _ => false, + }, + } } From b968122beef4e18b04d9a1a1f1186af37f00fe21 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 12 Feb 2023 02:08:02 +0100 Subject: [PATCH 19/48] simplify systemv argument storing and loading --- .../compiler/gen_dev/src/generic64/storage.rs | 16 + .../compiler/gen_dev/src/generic64/x86_64.rs | 387 ++++++++++-------- crates/compiler/gen_dev/src/lib.rs | 12 +- crates/compiler/module/src/symbol.rs | 1 + crates/compiler/test_gen/src/gen_list.rs | 6 +- 5 files changed, 254 insertions(+), 168 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs index 343025082a..6a48887841 100644 --- a/crates/compiler/gen_dev/src/generic64/storage.rs +++ b/crates/compiler/gen_dev/src/generic64/storage.rs @@ -787,6 +787,22 @@ impl< self.copy_symbol_to_stack_offset_help(buf, size, from_offset, to_offset) } }, + Layout::Boxed(_) => { + // like a 64-bit integer + debug_assert_eq!(to_offset % 8, 0); + let reg = self.load_to_general_reg(buf, sym); + ASM::mov_base32_reg64(buf, to_offset, reg); + } + Layout::LambdaSet(lambda_set) => { + // like its runtime representation + self.copy_symbol_to_stack_offset( + layout_interner, + buf, + to_offset, + sym, + &lambda_set.runtime_representation(), + ) + } _ if layout_interner.stack_size(*layout) == 0 => {} // TODO: Verify this is always true. // The dev backend does not deal with refcounting and does not care about if data is safe to memcpy. diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index c1d9e77d21..05520a3c79 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -261,59 +261,22 @@ impl CallConv for X86_64Syste args: &'a [(InLayout<'a>, Symbol)], ret_layout: &InLayout<'a>, ) { - let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 16; // 16 is the size of the pushed return address and base pointer. - let mut general_i = 0; - let mut float_i = 0; - if X86_64SystemV::returns_via_arg_pointer(layout_interner, ret_layout) { - storage_manager.ret_pointer_arg(Self::GENERAL_PARAM_REGS[0]); - general_i += 1; + let returns_via_pointer = + X86_64SystemV::returns_via_arg_pointer(layout_interner, &ret_layout); + + let mut state = X64_64SystemVLoadArgs { + general_i: if returns_via_pointer { 1 } else { 0 }, + float_i: 0, + // 16 is the size of the pushed return address and base pointer. + argument_offset: X86_64SystemV::SHADOW_SPACE_SIZE as i32 + 16, + }; + + if returns_via_pointer { + storage_manager.ret_pointer_arg(X86_64SystemV::GENERAL_PARAM_REGS[0]); } + for (in_layout, sym) in args.iter() { - let stack_size = layout_interner.stack_size(*in_layout); - match *in_layout { - single_register_integers!() => { - if general_i < Self::GENERAL_PARAM_REGS.len() { - storage_manager.general_reg_arg(sym, Self::GENERAL_PARAM_REGS[general_i]); - general_i += 1; - } else { - storage_manager.primitive_stack_arg(sym, arg_offset); - arg_offset += 8; - } - } - single_register_floats!() => { - if float_i < Self::FLOAT_PARAM_REGS.len() { - storage_manager.float_reg_arg(sym, Self::FLOAT_PARAM_REGS[float_i]); - float_i += 1; - } else { - storage_manager.primitive_stack_arg(sym, arg_offset); - arg_offset += 8; - } - } - _ if stack_size == 0 => { - storage_manager.no_data_arg(sym); - } - _ if stack_size > 16 => { - // TODO: Double check this. - storage_manager.complex_stack_arg(sym, arg_offset, stack_size); - arg_offset += stack_size as i32; - } - other => match layout_interner.get(other) { - Layout::Boxed(_) => { - // boxed layouts are pointers, which we treat as 64-bit integers - if general_i < Self::GENERAL_PARAM_REGS.len() { - storage_manager - .general_reg_arg(sym, Self::GENERAL_PARAM_REGS[general_i]); - general_i += 1; - } else { - storage_manager.primitive_stack_arg(sym, arg_offset); - arg_offset += 8; - } - } - _ => { - todo!("Loading args with layout {:?}", layout_interner.dbg(other)); - } - }, - } + state.load_arg(storage_manager, layout_interner, *sym, *in_layout); } } @@ -334,9 +297,8 @@ impl CallConv for X86_64Syste arg_layouts: &[InLayout<'a>], ret_layout: &InLayout<'a>, ) { - let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32; let mut general_i = 0; - let mut float_i = 0; + if Self::returns_via_arg_pointer(layout_interner, ret_layout) { // Save space on the stack for the result we will be return. let base_offset = @@ -352,110 +314,17 @@ impl CallConv for X86_64Syste ); } - for (sym, layout) in args.iter().zip(arg_layouts.iter()) { - match *layout { - single_register_integers!() => { - if general_i < Self::GENERAL_PARAM_REGS.len() { - storage_manager.load_to_specified_general_reg( - buf, - sym, - Self::GENERAL_PARAM_REGS[general_i], - ); - general_i += 1; - } else { - // Copy to stack using return reg as buffer. - storage_manager.load_to_specified_general_reg( - buf, - sym, - Self::GENERAL_RETURN_REGS[0], - ); - X86_64Assembler::mov_stack32_reg64( - buf, - tmp_stack_offset, - Self::GENERAL_RETURN_REGS[0], - ); - tmp_stack_offset += 8; - } - } - single_register_floats!() => { - if float_i < Self::FLOAT_PARAM_REGS.len() { - storage_manager.load_to_specified_float_reg( - buf, - sym, - Self::FLOAT_PARAM_REGS[float_i], - ); - float_i += 1; - } else { - // Copy to stack using return reg as buffer. - storage_manager.load_to_specified_float_reg( - buf, - sym, - Self::FLOAT_RETURN_REGS[0], - ); - X86_64Assembler::mov_stack32_freg64( - buf, - tmp_stack_offset, - Self::FLOAT_RETURN_REGS[0], - ); - tmp_stack_offset += 8; - } - } - x if layout_interner.stack_size(x) == 0 => {} - x if layout_interner.stack_size(x) > 16 => { - // TODO: Double check this. - // Just copy onto the stack. - // Use return reg as buffer because it will be empty right now. - let (base_offset, size) = storage_manager.stack_offset_and_size(sym); - debug_assert_eq!(base_offset % 8, 0); - for i in (0..size as i32).step_by(8) { - X86_64Assembler::mov_reg64_base32( - buf, - Self::GENERAL_RETURN_REGS[0], - base_offset + i, - ); - X86_64Assembler::mov_stack32_reg64( - buf, - tmp_stack_offset + i, - Self::GENERAL_RETURN_REGS[0], - ); - } - tmp_stack_offset += size as i32; - } - other => { - // look at the layout in more detail - match layout_interner.get(other) { - Layout::Boxed(_) => { - // treat boxed like a 64-bit integer - if general_i < Self::GENERAL_PARAM_REGS.len() { - storage_manager.load_to_specified_general_reg( - buf, - sym, - Self::GENERAL_PARAM_REGS[general_i], - ); - general_i += 1; - } else { - // Copy to stack using return reg as buffer. - storage_manager.load_to_specified_general_reg( - buf, - sym, - Self::GENERAL_RETURN_REGS[0], - ); - X86_64Assembler::mov_stack32_reg64( - buf, - tmp_stack_offset, - Self::GENERAL_RETURN_REGS[0], - ); - tmp_stack_offset += 8; - } - } - _ => { - todo!("calling with arg type, {:?}", layout_interner.dbg(other)); - } - } - } - } + let mut state = X64_64SystemVStoreArgs { + general_i, + float_i: 0, + tmp_stack_offset: Self::SHADOW_SPACE_SIZE as i32, + }; + + for (sym, in_layout) in args.iter().zip(arg_layouts.iter()) { + state.store_arg(buf, storage_manager, layout_interner, *sym, *in_layout); } - storage_manager.update_fn_call_stack_size(tmp_stack_offset as u32); + + storage_manager.update_fn_call_stack_size(state.tmp_stack_offset as u32); } fn return_complex_symbol<'a, 'r>( @@ -562,6 +431,203 @@ impl CallConv for X86_64Syste } } +struct X64_64SystemVStoreArgs { + general_i: usize, + float_i: usize, + tmp_stack_offset: i32, +} + +impl X64_64SystemVStoreArgs { + const GENERAL_PARAM_REGS: &'static [X86_64GeneralReg] = X86_64SystemV::GENERAL_PARAM_REGS; + const GENERAL_RETURN_REGS: &'static [X86_64GeneralReg] = X86_64SystemV::GENERAL_RETURN_REGS; + + const FLOAT_PARAM_REGS: &'static [X86_64FloatReg] = X86_64SystemV::FLOAT_PARAM_REGS; + const FLOAT_RETURN_REGS: &'static [X86_64FloatReg] = X86_64SystemV::FLOAT_RETURN_REGS; + + fn store_arg<'a, 'r>( + &mut self, + buf: &mut Vec<'a, u8>, + storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>, + layout_interner: &mut STLayoutInterner<'a>, + sym: Symbol, + in_layout: InLayout<'a>, + ) { + match in_layout { + single_register_integers!() => self.store_arg_general(buf, storage_manager, sym), + single_register_floats!() => self.store_arg_float(buf, storage_manager, sym), + x if layout_interner.stack_size(x) == 0 => {} + x if layout_interner.stack_size(x) > 16 => { + // TODO: Double check this. + // Just copy onto the stack. + // Use return reg as buffer because it will be empty right now. + let (base_offset, size) = storage_manager.stack_offset_and_size(&sym); + debug_assert_eq!(base_offset % 8, 0); + for i in (0..size as i32).step_by(8) { + X86_64Assembler::mov_reg64_base32( + buf, + Self::GENERAL_RETURN_REGS[0], + base_offset + i, + ); + X86_64Assembler::mov_stack32_reg64( + buf, + self.tmp_stack_offset + i, + Self::GENERAL_RETURN_REGS[0], + ); + } + self.tmp_stack_offset += size as i32; + } + other => { + // look at the layout in more detail + match layout_interner.get(other) { + Layout::Boxed(_) => { + // treat boxed like a 64-bit integer + self.store_arg_general(buf, storage_manager, sym) + } + Layout::LambdaSet(lambda_set) => self.store_arg( + buf, + storage_manager, + layout_interner, + sym, + lambda_set.runtime_representation(), + ), + _ => { + todo!("calling with arg type, {:?}", layout_interner.dbg(other)); + } + } + } + } + } + + fn store_arg_general<'a, 'r>( + &mut self, + buf: &mut Vec<'a, u8>, + storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>, + sym: Symbol, + ) { + if self.general_i < Self::GENERAL_PARAM_REGS.len() { + storage_manager.load_to_specified_general_reg( + buf, + &sym, + Self::GENERAL_PARAM_REGS[self.general_i], + ); + self.general_i += 1; + } else { + // Copy to stack using return reg as buffer. + storage_manager.load_to_specified_general_reg(buf, &sym, Self::GENERAL_RETURN_REGS[0]); + X86_64Assembler::mov_stack32_reg64( + buf, + self.tmp_stack_offset, + Self::GENERAL_RETURN_REGS[0], + ); + self.tmp_stack_offset += 8; + } + } + + fn store_arg_float<'a, 'r>( + &mut self, + buf: &mut Vec<'a, u8>, + storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>, + sym: Symbol, + ) { + if self.float_i < Self::FLOAT_PARAM_REGS.len() { + storage_manager.load_to_specified_float_reg( + buf, + &sym, + Self::FLOAT_PARAM_REGS[self.float_i], + ); + self.float_i += 1; + } else { + // Copy to stack using return reg as buffer. + storage_manager.load_to_specified_float_reg(buf, &sym, Self::FLOAT_RETURN_REGS[0]); + X86_64Assembler::mov_stack32_freg64( + buf, + self.tmp_stack_offset, + Self::FLOAT_RETURN_REGS[0], + ); + self.tmp_stack_offset += 8; + } + } +} + +struct X64_64SystemVLoadArgs { + general_i: usize, + float_i: usize, + argument_offset: i32, +} + +type X86_64StorageManager<'a, 'r, CallConv> = + StorageManager<'a, 'r, X86_64GeneralReg, X86_64FloatReg, X86_64Assembler, CallConv>; + +impl X64_64SystemVLoadArgs { + fn load_arg<'a, 'r>( + &mut self, + storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>, + layout_interner: &mut STLayoutInterner<'a>, + sym: Symbol, + in_layout: InLayout<'a>, + ) { + let stack_size = layout_interner.stack_size(in_layout); + match in_layout { + single_register_integers!() => self.load_arg_general(storage_manager, sym), + single_register_floats!() => self.load_arg_float(storage_manager, sym), + _ if stack_size == 0 => { + storage_manager.no_data_arg(&sym); + } + _ if stack_size > 16 => { + // TODO: Double check this. + storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size); + self.argument_offset += stack_size as i32; + } + other => match layout_interner.get(other) { + Layout::Boxed(_) => { + // boxed layouts are pointers, which we treat as 64-bit integers + self.load_arg_general(storage_manager, sym) + } + Layout::LambdaSet(lambda_set) => self.load_arg( + storage_manager, + layout_interner, + sym, + lambda_set.runtime_representation(), + ), + _ => { + dbg!(other, layout_interner.get(other)); + todo!("Loading args with layout {:?}", layout_interner.dbg(other)); + } + }, + } + } + + fn load_arg_general<'a, 'r>( + &mut self, + storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>, + sym: Symbol, + ) { + if self.general_i < X86_64SystemV::GENERAL_PARAM_REGS.len() { + let reg = X86_64SystemV::GENERAL_PARAM_REGS[self.general_i]; + storage_manager.general_reg_arg(&sym, reg); + self.general_i += 1; + } else { + storage_manager.primitive_stack_arg(&sym, self.argument_offset); + self.argument_offset += 8; + } + } + + fn load_arg_float<'a, 'r>( + &mut self, + storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>, + sym: Symbol, + ) { + if self.general_i < X86_64SystemV::GENERAL_PARAM_REGS.len() { + let reg = X86_64SystemV::FLOAT_PARAM_REGS[self.general_i]; + storage_manager.float_reg_arg(&sym, reg); + self.float_i += 1; + } else { + storage_manager.primitive_stack_arg(&sym, self.argument_offset); + self.argument_offset += 8; + } + } +} + impl X86_64SystemV { fn returns_via_arg_pointer<'a>( interner: &STLayoutInterner<'a>, @@ -705,14 +771,7 @@ impl CallConv for X86_64Windo #[inline(always)] fn load_args<'a, 'r>( _buf: &mut Vec<'a, u8>, - storage_manager: &mut StorageManager< - 'a, - 'r, - X86_64GeneralReg, - X86_64FloatReg, - X86_64Assembler, - X86_64WindowsFastcall, - >, + storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64WindowsFastcall>, layout_interner: &mut STLayoutInterner<'a>, args: &'a [(InLayout<'a>, Symbol)], ret_layout: &InLayout<'a>, diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index 77eec1fe02..ffd99b95e6 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -1056,13 +1056,23 @@ trait Backend<'a> { self.free_symbol(&Symbol::DEV_TMP) } Symbol::STR_IS_VALID_SCALAR => { + // just call the function + let layout_id = LayoutIds::default().get(func_sym, ret_layout); + let fn_name = self.symbol_to_string(func_sym, layout_id); + // Now that the arguments are needed, load them if they are literals. + self.load_literal_symbols(args); + self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout) + } + other => { + eprintln!("maybe {other:?} should have a custom implementation?"); + + // just call the function let layout_id = LayoutIds::default().get(func_sym, ret_layout); let fn_name = self.symbol_to_string(func_sym, layout_id); // Now that the arguments are needed, load them if they are literals. self.load_literal_symbols(args); self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout) } - _ => todo!("the function, {:?}", func_sym), } } diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index ca6d0fb1fa..3a93cbfac6 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -1402,6 +1402,7 @@ define_builtins! { 77 LIST_COUNT_IF: "countIf" 78 LIST_WALK_FROM: "walkFrom" 79 LIST_WALK_FROM_UNTIL: "walkFromUntil" + 80 LIST_ITER_HELP: "iterHelp" } 7 RESULT: "Result" => { 0 RESULT_RESULT: "Result" exposed_type=true // the Result.Result type alias diff --git a/crates/compiler/test_gen/src/gen_list.rs b/crates/compiler/test_gen/src/gen_list.rs index 251e1ec9b0..229da7d3eb 100644 --- a/crates/compiler/test_gen/src/gen_list.rs +++ b/crates/compiler/test_gen/src/gen_list.rs @@ -2140,7 +2140,7 @@ fn get_set_unique_int_list_i64() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn get_set_unique_int_list_i8() { assert_evals_to!( indoc!( @@ -2592,7 +2592,7 @@ fn list_literal_increment_decrement() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_pass_to_function() { assert_evals_to!( indoc!( @@ -2612,7 +2612,7 @@ fn list_pass_to_function() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_pass_to_set() { assert_evals_to!( indoc!( From 816828040de3318a30c8bff1e1062d936a16e804 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 12 Feb 2023 02:16:58 +0100 Subject: [PATCH 20/48] tests for boxing/unboxing floats --- crates/compiler/gen_dev/src/generic64/mod.rs | 2 +- crates/compiler/test_gen/src/gen_list.rs | 44 +++++++++---------- .../compiler/test_gen/src/gen_primitives.rs | 22 +++++++--- 3 files changed, 38 insertions(+), 30 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 9af05a1cec..95a734f17c 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -2512,7 +2512,7 @@ impl< let sym_reg = storage_manager.load_to_general_reg(buf, &value); ASM::mov_mem8_offset32_reg8(buf, ptr_reg, element_offset, sym_reg); } - Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { + Layout::Builtin(Builtin::Float(FloatWidth::F64 | FloatWidth::F32)) => { let sym_reg = storage_manager.load_to_float_reg(buf, &value); ASM::movesd_mem64_offset32_freg64(buf, ptr_reg, element_offset, sym_reg); } diff --git a/crates/compiler/test_gen/src/gen_list.rs b/crates/compiler/test_gen/src/gen_list.rs index 229da7d3eb..73128e9829 100644 --- a/crates/compiler/test_gen/src/gen_list.rs +++ b/crates/compiler/test_gen/src/gen_list.rs @@ -2700,24 +2700,22 @@ fn list_min() { assert_evals_to!( indoc!( r#" - when List.min [] is - Ok val -> val - Err _ -> -1 - "# + List.min [] + |> Result.map (\_ -> {}) + "# ), - -1, - i64 + RocResult::err(()), + RocResult<(), ()> ); + assert_evals_to!( indoc!( r#" - when List.min [3, 1, 2] is - Ok val -> val - Err _ -> -1 - "# + List.min [3, 1, 2] + "# ), - 1, - i64 + RocResult::ok(1), + RocResult ); } @@ -2727,24 +2725,22 @@ fn list_max() { assert_evals_to!( indoc!( r#" - when List.max [] is - Ok val -> val - Err _ -> -1 - "# + List.max [] + |> Result.map (\_ -> {}) + "# ), - -1, - i64 + RocResult::err(()), + RocResult<(), ()> ); + assert_evals_to!( indoc!( r#" - when List.max [3, 1, 2] is - Ok val -> val - Err _ -> -1 - "# + List.max [3, 1, 2] + "# ), - 3, - i64 + RocResult::ok(3), + RocResult ); } diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index 147cb47c3a..3c07349438 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -3307,34 +3307,46 @@ fn box_str() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] -fn box_and_unbox_64bit_num() { +fn box_and_unbox_u64() { assert_evals_to!("Box.unbox (Box.box (123u64))", 123, u64) } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] -fn box_and_unbox_32bit_num() { +fn box_and_unbox_u32() { assert_evals_to!("Box.unbox (Box.box (123u32))", 123, u32) } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] -fn box_and_unbox_16bit_num() { +fn box_and_unbox_u16() { assert_evals_to!("Box.unbox (Box.box (123u16))", 123, u16) } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] -fn box_and_unbox_8bit_num() { +fn box_and_unbox_u8() { assert_evals_to!("Box.unbox (Box.box (123u8))", 123, u8) } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] -fn box_and_unbox_1bit_num() { +fn box_and_unbox_bool() { assert_evals_to!("Box.unbox (Box.box (Bool.true))", true, bool) } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn box_and_unbox_f64() { + assert_evals_to!("Box.unbox (Box.box (123.0f64))", 123.0, f64) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn box_and_unbox_f32() { + assert_evals_to!("Box.unbox (Box.box (123.0f32))", 123.0, f32) +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn box_and_unbox_record() { From aa113f169c7511a19b6dedea651530837a4fa8ce Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 12 Feb 2023 13:59:19 +0100 Subject: [PATCH 21/48] string equality --- crates/compiler/gen_dev/src/generic64/mod.rs | 27 +++++++++++++++++++ .../compiler/gen_dev/src/generic64/storage.rs | 18 ++++++++----- crates/compiler/test_gen/src/gen_str.rs | 2 +- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 95a734f17c..682b377d30 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -1157,6 +1157,16 @@ impl< .load_to_general_reg(&mut self.buf, src2); ASM::eq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } + Layout::STR => { + // use a zig call + self.build_fn_call( + dst, + bitcode::STR_EQUAL.to_string(), + &[*src1, *src2], + &[Layout::STR, Layout::STR], + &Layout::BOOL, + ) + } x => todo!("NumEq: layout, {:?}", x), } } @@ -1173,6 +1183,23 @@ impl< .load_to_general_reg(&mut self.buf, src2); ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } + Layout::Builtin(Builtin::Str) => { + self.build_fn_call( + dst, + bitcode::STR_EQUAL.to_string(), + &[*src1, *src2], + &[Layout::STR, Layout::STR], + &Layout::BOOL, + ); + + // negate the result + let tmp = &Symbol::DEV_TMP; + let tmp_reg = self.storage_manager.claim_general_reg(&mut self.buf, tmp); + ASM::mov_reg64_imm64(&mut self.buf, tmp_reg, 164); + + let dst_reg = self.storage_manager.load_to_general_reg(&mut self.buf, dst); + ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, dst_reg, tmp_reg); + } x => todo!("NumNeq: layout, {:?}", x), } } diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs index 6a48887841..0d2efed0dd 100644 --- a/crates/compiler/gen_dev/src/generic64/storage.rs +++ b/crates/compiler/gen_dev/src/generic64/storage.rs @@ -314,7 +314,7 @@ impl< reg: Some(Float(_)), .. }) => { - internal_error!("Cannot load floating point symbol into GeneralReg: {}", sym) + internal_error!("Cannot load floating point symbol into GeneralReg: {sym:?}") } Stack(Primitive { reg: None, @@ -349,8 +349,10 @@ impl< self.free_reference(sym); reg } - Stack(Complex { .. }) => { - internal_error!("Cannot load large values into general registers: {}", sym) + Stack(Complex { size, .. }) => { + internal_error!( + "Cannot load large values (size {size}) into general registers: {sym:?}", + ) } NoData => { internal_error!("Cannot load no data into general registers: {}", sym) @@ -447,7 +449,7 @@ impl< reg: Some(Float(_)), .. }) => { - internal_error!("Cannot load floating point symbol into GeneralReg: {}", sym) + internal_error!("Cannot load floating point symbol into GeneralReg: {sym:?}",) } Stack(Primitive { reg: None, @@ -465,11 +467,13 @@ impl< Stack(ReferencedPrimitive { .. }) => { todo!("loading referenced primitives") } - Stack(Complex { .. }) => { - internal_error!("Cannot load large values into general registers: {}", sym) + Stack(Complex { size, .. }) => { + internal_error!( + "Cannot load large values (size {size}) into general registers: {sym:?}", + ) } NoData => { - internal_error!("Cannot load no data into general registers: {}", sym) + internal_error!("Cannot load no data into general registers: {:?}", sym) } } } diff --git a/crates/compiler/test_gen/src/gen_str.rs b/crates/compiler/test_gen/src/gen_str.rs index 3e71085c79..ee4c4dabd9 100644 --- a/crates/compiler/test_gen/src/gen_str.rs +++ b/crates/compiler/test_gen/src/gen_str.rs @@ -809,7 +809,7 @@ fn str_from_utf8_fail_surrogate_half() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_equality() { assert_evals_to!(r#""a" == "a""#, true, bool); assert_evals_to!( From 4e38a4ce3200fcc59d6d2cabc3016381e98caf0a Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 12 Feb 2023 14:04:54 +0100 Subject: [PATCH 22/48] enable some utf8 conversion tests --- .../compiler/gen_dev/src/generic64/x86_64.rs | 23 +++++++++++++++++++ crates/compiler/gen_dev/src/lib.rs | 7 ++++++ crates/compiler/test_gen/src/gen_str.rs | 16 ++++++------- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index 05520a3c79..a34dc54d74 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -490,6 +490,24 @@ impl X64_64SystemVStoreArgs { sym, lambda_set.runtime_representation(), ), + Layout::Struct { .. } => { + // for now, just also store this on the stack + let (base_offset, size) = storage_manager.stack_offset_and_size(&sym); + debug_assert_eq!(base_offset % 8, 0); + for i in (0..size as i32).step_by(8) { + X86_64Assembler::mov_reg64_base32( + buf, + Self::GENERAL_RETURN_REGS[0], + base_offset + i, + ); + X86_64Assembler::mov_stack32_reg64( + buf, + self.tmp_stack_offset + i, + Self::GENERAL_RETURN_REGS[0], + ); + } + self.tmp_stack_offset += size as i32; + } _ => { todo!("calling with arg type, {:?}", layout_interner.dbg(other)); } @@ -589,6 +607,11 @@ impl X64_64SystemVLoadArgs { sym, lambda_set.runtime_representation(), ), + Layout::Struct { .. } => { + // for now, just also store this on the stack + storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size); + self.argument_offset += stack_size as i32; + } _ => { dbg!(other, layout_interner.get(other)); todo!("Loading args with layout {:?}", layout_interner.dbg(other)); diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index ffd99b95e6..cba9e3e107 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -868,6 +868,13 @@ trait Backend<'a> { arg_layouts, ret_layout, ), + LowLevel::StrToUtf8 => self.build_fn_call( + sym, + bitcode::STR_TO_UTF8.to_string(), + args, + arg_layouts, + ret_layout, + ), LowLevel::StrCountUtf8Bytes => self.build_fn_call( sym, bitcode::STR_COUNT_UTF8_BYTES.to_string(), diff --git a/crates/compiler/test_gen/src/gen_str.rs b/crates/compiler/test_gen/src/gen_str.rs index ee4c4dabd9..7edfe16a1b 100644 --- a/crates/compiler/test_gen/src/gen_str.rs +++ b/crates/compiler/test_gen/src/gen_str.rs @@ -891,7 +891,7 @@ fn str_join_comma_single() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_to_utf8() { assert_evals_to!( r#"Str.toUtf8 "hello""#, @@ -909,7 +909,7 @@ fn str_to_utf8() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_range() { assert_evals_to!( indoc!( @@ -926,7 +926,7 @@ fn str_from_utf8_range() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_range_slice() { assert_evals_to!( indoc!( @@ -943,7 +943,7 @@ fn str_from_utf8_range_slice() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_range_slice_not_end() { assert_evals_to!( indoc!( @@ -960,7 +960,7 @@ fn str_from_utf8_range_slice_not_end() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_range_order_does_not_matter() { assert_evals_to!( indoc!( @@ -977,7 +977,7 @@ fn str_from_utf8_range_order_does_not_matter() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_range_out_of_bounds_start_value() { assert_evals_to!( indoc!( @@ -995,7 +995,7 @@ fn str_from_utf8_range_out_of_bounds_start_value() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_range_count_too_high() { assert_evals_to!( indoc!( @@ -1013,7 +1013,7 @@ fn str_from_utf8_range_count_too_high() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_range_count_too_high_for_start() { assert_evals_to!( indoc!( From a3d5e7bee5640dbfd864d0dc2de07526fce24c9d Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 12 Feb 2023 14:54:22 +0100 Subject: [PATCH 23/48] fully implement sign/zero extension --- .../compiler/gen_dev/src/generic64/storage.rs | 18 +- .../compiler/gen_dev/src/generic64/x86_64.rs | 165 ++++++++++++++++-- crates/compiler/test_gen/src/gen_str.rs | 2 +- 3 files changed, 160 insertions(+), 25 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs index 0d2efed0dd..c4e9100713 100644 --- a/crates/compiler/gen_dev/src/generic64/storage.rs +++ b/crates/compiler/gen_dev/src/generic64/storage.rs @@ -459,13 +459,17 @@ impl< ASM::mov_reg64_base32(buf, reg, *base_offset); } Stack(ReferencedPrimitive { - base_offset, size, .. - }) if base_offset % 8 == 0 && *size == 8 => { - // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack. - ASM::mov_reg64_base32(buf, reg, *base_offset); - } - Stack(ReferencedPrimitive { .. }) => { - todo!("loading referenced primitives") + base_offset, + size, + sign_extend, + }) => { + debug_assert!(*size <= 8); + + if *sign_extend { + ASM::movsx_reg64_base32(buf, reg, *base_offset, *size as u8) + } else { + ASM::movzx_reg64_base32(buf, reg, *base_offset, *size as u8) + } } Stack(Complex { size, .. }) => { internal_error!( diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index a34dc54d74..e4a16e2e90 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -262,10 +262,10 @@ impl CallConv for X86_64Syste ret_layout: &InLayout<'a>, ) { let returns_via_pointer = - X86_64SystemV::returns_via_arg_pointer(layout_interner, &ret_layout); + X86_64SystemV::returns_via_arg_pointer(layout_interner, ret_layout); let mut state = X64_64SystemVLoadArgs { - general_i: if returns_via_pointer { 1 } else { 0 }, + general_i: usize::from(returns_via_pointer), float_i: 0, // 16 is the size of the pushed return address and base pointer. argument_offset: X86_64SystemV::SHADOW_SPACE_SIZE as i32 + 16, @@ -613,7 +613,6 @@ impl X64_64SystemVLoadArgs { self.argument_offset += stack_size as i32; } _ => { - dbg!(other, layout_interner.get(other)); todo!("Loading args with layout {:?}", layout_interner.dbg(other)); } }, @@ -1483,7 +1482,9 @@ impl Assembler for X86_64Assembler { debug_assert!(size <= 8); match size { 8 => Self::mov_reg64_base32(buf, dst, offset), - 4 | 2 | 1 => todo!("sign extending {size} byte values"), + 4 => movsx_reg64_base32_offset32(buf, dst, X86_64GeneralReg::RBP, offset), + 2 => movsx_reg64_base16_offset32(buf, dst, X86_64GeneralReg::RBP, offset), + 1 => movsx_reg64_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset), _ => internal_error!("Invalid size for sign extension: {size}"), } } @@ -1492,7 +1493,12 @@ impl Assembler for X86_64Assembler { debug_assert!(size <= 8); match size { 8 => Self::mov_reg64_base32(buf, dst, offset), - 4 | 2 => todo!("zero extending {size} byte values"), + 4 => { + // The Intel documentation (3.4.1.1 General-Purpose Registers in 64-Bit Mode in manual Basic Architecture)) + // 32-bit operands generate a 32-bit result, zero-extended to a 64-bit result in the destination general-purpose register. + Self::mov_reg64_base32(buf, dst, offset) + } + 2 => movzx_reg64_base16_offset32(buf, dst, X86_64GeneralReg::RBP, offset), 1 => movzx_reg64_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset), _ => internal_error!("Invalid size for zero extension: {size}"), } @@ -2372,7 +2378,7 @@ fn mov_reg16_base16_offset32( mov_reg_base_offset32(buf, RegisterWidth::W16, dst, base, offset) } -/// `MOV r/m16,r16` -> Move r16 to r/m16. +/// `MOV r/m8,r8` -> Move r8 to r/m8. #[inline(always)] fn mov_reg8_base8_offset32( buf: &mut Vec<'_, u8>, @@ -2383,6 +2389,86 @@ fn mov_reg8_base8_offset32( mov_reg_base_offset32(buf, RegisterWidth::W8, dst, base, offset) } +#[inline(always)] +fn movsx_reg64_base_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, + opcode: &[u8], +) { + let rex = add_rm_extension(base, REX_W); + let rex = add_reg_extension(dst, rex); + let dst_mod = (dst as u8 % 8) << 3; + let base_mod = base as u8 % 8; + buf.reserve(9); + + // our output is a 64-bit value, so rex is always needed + buf.push(rex); + buf.extend(opcode); + buf.push(0x80 | dst_mod | base_mod); + + // Using RSP or R12 requires a secondary index byte. + if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + buf.push(0x24); + } + buf.extend(offset.to_le_bytes()); +} + +/// `MOVSX r64,r/m32` -> Move r/m32 with sign extention to r64, where m32 references a base + offset. +#[inline(always)] +fn movsx_reg64_base32_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + movsx_reg64_base_offset32(buf, dst, base, offset, &[0x63]) +} + +/// `MOVSX r64,r/m16` -> Move r/m16 with sign extention to r64, where m16 references a base + offset. +#[inline(always)] +fn movsx_reg64_base16_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + movsx_reg64_base_offset32(buf, dst, base, offset, &[0x0F, 0xBF]) +} + +/// `MOVSX r64,r/m8` -> Move r/m8 with sign extention to r64, where m8 references a base + offset. +#[inline(always)] +fn movsx_reg64_base8_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + movsx_reg64_base_offset32(buf, dst, base, offset, &[0x0F, 0xBE]) +} + +#[inline(always)] +fn movzx_reg64_base_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, + opcode: u8, +) { + let rex = add_rm_extension(base, REX_W); + let rex = add_reg_extension(dst, rex); + let dst_mod = (dst as u8 % 8) << 3; + let base_mod = base as u8 % 8; + buf.reserve(9); + buf.extend([rex, 0x0F, opcode, 0x80 | dst_mod | base_mod]); + // Using RSP or R12 requires a secondary index byte. + if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + buf.push(0x24); + } + buf.extend(offset.to_le_bytes()); +} + /// `MOVZX r64,r/m8` -> Move r/m8 with zero extention to r64, where m8 references a base + offset. #[inline(always)] fn movzx_reg64_base8_offset32( @@ -2391,17 +2477,18 @@ fn movzx_reg64_base8_offset32( base: X86_64GeneralReg, offset: i32, ) { - let rex = add_rm_extension(base, REX_W); - let rex = add_reg_extension(dst, rex); - let dst_mod = (dst as u8 % 8) << 3; - let base_mod = base as u8 % 8; - buf.reserve(9); - buf.extend([rex, 0x0F, 0xB6, 0x80 | dst_mod | base_mod]); - // Using RSP or R12 requires a secondary index byte. - if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { - buf.push(0x24); - } - buf.extend(offset.to_le_bytes()); + movzx_reg64_base_offset32(buf, dst, base, offset, 0xB6) +} + +/// `MOVZX r64,r/m16` -> Move r/m16 with zero extention to r64, where m16 references a base + offset. +#[inline(always)] +fn movzx_reg64_base16_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + movzx_reg64_base_offset32(buf, dst, base, offset, 0xB7) } /// `MOVSD xmm1,xmm2` -> Move scalar double-precision floating-point value from xmm2 to xmm1 register. @@ -3274,6 +3361,50 @@ mod tests { ); } + #[test] + fn test_movsx_reg64_base32_offset32() { + disassembler_test!( + movsx_reg64_base32_offset32, + |reg1, reg2, imm| format!("movsxd {}, dword ptr [{} + 0x{:x}]", reg1, reg2, imm), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS, + [TEST_I32] + ); + } + + #[test] + fn test_movsx_reg64_base16_offset32() { + disassembler_test!( + movsx_reg64_base16_offset32, + |reg1, reg2, imm| format!("movsx {}, word ptr [{} + 0x{:x}]", reg1, reg2, imm), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS, + [TEST_I32] + ); + } + + #[test] + fn test_movsx_reg64_base8_offset32() { + disassembler_test!( + movsx_reg64_base8_offset32, + |reg1, reg2, imm| format!("movsx {}, byte ptr [{} + 0x{:x}]", reg1, reg2, imm), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS, + [TEST_I32] + ); + } + + #[test] + fn test_movzx_reg64_base16_offset32() { + disassembler_test!( + movzx_reg64_base16_offset32, + |reg1, reg2, imm| format!("movzx {}, word ptr [{} + 0x{:x}]", reg1, reg2, imm), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS, + [TEST_I32] + ); + } + #[test] fn test_movzx_reg64_base8_offset32() { disassembler_test!( diff --git a/crates/compiler/test_gen/src/gen_str.rs b/crates/compiler/test_gen/src/gen_str.rs index 7edfe16a1b..85091d1273 100644 --- a/crates/compiler/test_gen/src/gen_str.rs +++ b/crates/compiler/test_gen/src/gen_str.rs @@ -1871,7 +1871,7 @@ fn str_append_scalar() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_walk_scalars() { assert_evals_to!( indoc!( From 93fb1c73e66c45e11213ced6618fe7c9c4d07b5c Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 12 Feb 2023 15:12:11 +0100 Subject: [PATCH 24/48] str splits --- crates/compiler/gen_dev/src/generic64/mod.rs | 24 +++++++++++++++++ crates/compiler/gen_dev/src/lib.rs | 27 +++++++++++++++----- crates/compiler/test_gen/src/gen_str.rs | 20 +++++++-------- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 682b377d30..0ecd2bdbce 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -1003,6 +1003,30 @@ impl< } } + fn build_num_sub_checked( + &mut self, + dst: &Symbol, + src1: &Symbol, + src2: &Symbol, + num_layout: &InLayout<'a>, + return_layout: &InLayout<'a>, + ) { + let function_name = match self.interner().get(*num_layout) { + Layout::Builtin(Builtin::Int(width)) => &bitcode::NUM_SUB_CHECKED_INT[width], + Layout::Builtin(Builtin::Float(width)) => &bitcode::NUM_SUB_CHECKED_FLOAT[width], + Layout::Builtin(Builtin::Decimal) => bitcode::DEC_SUB_WITH_OVERFLOW, + x => internal_error!("NumSubChecked is not defined for {:?}", x), + }; + + self.build_fn_call( + dst, + function_name.to_string(), + &[*src1, *src2], + &[*num_layout, *num_layout], + return_layout, + ) + } + fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>) { use Builtin::Int; diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index cba9e3e107..33f4762655 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -447,6 +447,9 @@ trait Backend<'a> { LowLevel::NumAddChecked => { self.build_num_add_checked(sym, &args[0], &args[1], &arg_layouts[0], ret_layout) } + LowLevel::NumSubChecked => { + self.build_num_sub_checked(sym, &args[0], &args[1], &arg_layouts[0], ret_layout) + } LowLevel::NumAcos => self.build_fn_call( sym, bitcode::NUM_ACOS[FloatWidth::F64].to_string(), @@ -868,6 +871,13 @@ trait Backend<'a> { arg_layouts, ret_layout, ), + LowLevel::StrSubstringUnsafe => self.build_fn_call( + sym, + bitcode::STR_SUBSTRING_UNSAFE.to_string(), + args, + arg_layouts, + ret_layout, + ), LowLevel::StrToUtf8 => self.build_fn_call( sym, bitcode::STR_TO_UTF8.to_string(), @@ -1043,13 +1053,6 @@ trait Backend<'a> { self.load_literal_symbols(args); self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout) } - Symbol::NUM_ADD_CHECKED => { - let layout_id = LayoutIds::default().get(func_sym, ret_layout); - let fn_name = self.symbol_to_string(func_sym, layout_id); - // Now that the arguments are needed, load them if they are literals. - self.load_literal_symbols(args); - self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout) - } Symbol::BOOL_TRUE => { let bool_layout = Layout::BOOL; self.load_literal(&Symbol::DEV_TMP, &bool_layout, &Literal::Bool(true)); @@ -1110,6 +1113,16 @@ trait Backend<'a> { return_layout: &InLayout<'a>, ); + /// build_num_sub_checked stores the sum of src1 and src2 into dst. + fn build_num_sub_checked( + &mut self, + dst: &Symbol, + src1: &Symbol, + src2: &Symbol, + num_layout: &InLayout<'a>, + return_layout: &InLayout<'a>, + ); + /// build_num_mul stores `src1 * src2` into dst. fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>); diff --git a/crates/compiler/test_gen/src/gen_str.rs b/crates/compiler/test_gen/src/gen_str.rs index 85091d1273..5b260abd4c 100644 --- a/crates/compiler/test_gen/src/gen_str.rs +++ b/crates/compiler/test_gen/src/gen_str.rs @@ -1697,7 +1697,7 @@ fn to_scalar_4_byte() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_first_one_char() { assert_evals_to!( indoc!( @@ -1713,7 +1713,7 @@ fn str_split_first_one_char() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_first_multiple_chars() { assert_evals_to!( indoc!( @@ -1727,7 +1727,7 @@ fn str_split_first_multiple_chars() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_first_entire_input() { assert_evals_to!( indoc!( @@ -1741,7 +1741,7 @@ fn str_split_first_entire_input() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_first_not_found() { assert_evals_to!( indoc!( @@ -1755,7 +1755,7 @@ fn str_split_first_not_found() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_last_one_char() { assert_evals_to!( indoc!( @@ -1769,7 +1769,7 @@ fn str_split_last_one_char() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_last_multiple_chars() { assert_evals_to!( indoc!( @@ -1783,7 +1783,7 @@ fn str_split_last_multiple_chars() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_last_entire_input() { assert_evals_to!( indoc!( @@ -1797,12 +1797,12 @@ fn str_split_last_entire_input() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_last_not_found() { assert_evals_to!( indoc!( r#" - Str.splitFirst "foo" "bar" + Str.splitLast "foo" "bar" "# ), RocResult::err(()), @@ -1831,7 +1831,7 @@ fn str_split_overlapping_substring_2() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_walk_utf8_with_index() { #[cfg(not(feature = "gen-llvm-wasm"))] assert_evals_to!( From 03867fdc1f7364627747a096785e310b06d6c9f2 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 12 Feb 2023 15:19:46 +0100 Subject: [PATCH 25/48] str to num --- crates/compiler/gen_dev/src/lib.rs | 36 +++---- crates/compiler/test_gen/src/gen_str.rs | 124 +++++++++++------------- 2 files changed, 76 insertions(+), 84 deletions(-) diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index 33f4762655..72ceabcd22 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -969,24 +969,24 @@ trait Backend<'a> { arg_layouts, ret_layout, ), - // LowLevel::StrToNum => { - // let number_layout = match self.interner().get(*ret_layout) { - // Layout::Struct { field_layouts, .. } => field_layouts[0], // TODO: why is it sometimes a struct? - // _ => unreachable!(), - // }; - // - // // match on the return layout to figure out which zig builtin we need - // let intrinsic = match self.interner().get(number_layout) { - // Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], - // Layout::Builtin(Builtin::Float(float_width)) => { - // &bitcode::STR_TO_FLOAT[float_width] - // } - // Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, - // _ => unreachable!(), - // }; - // - // self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout) - // } + LowLevel::StrToNum => { + let number_layout = match self.interner().get(*ret_layout) { + Layout::Struct { field_layouts, .. } => field_layouts[0], // TODO: why is it sometimes a struct? + _ => unreachable!(), + }; + + // match on the return layout to figure out which zig builtin we need + let intrinsic = match self.interner().get(number_layout) { + Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], + Layout::Builtin(Builtin::Float(float_width)) => { + &bitcode::STR_TO_FLOAT[float_width] + } + Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, + _ => unreachable!(), + }; + + self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout) + } LowLevel::PtrCast => { debug_assert_eq!( 1, diff --git a/crates/compiler/test_gen/src/gen_str.rs b/crates/compiler/test_gen/src/gen_str.rs index 5b260abd4c..0bacd0daa3 100644 --- a/crates/compiler/test_gen/src/gen_str.rs +++ b/crates/compiler/test_gen/src/gen_str.rs @@ -1369,9 +1369,17 @@ fn str_trim_right_small_to_small_shared() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_to_nat() { - assert_evals_to!(r#"Str.toNat "1" |> Result.withDefault 0"#, 1, usize); + assert_evals_to!( + indoc!( + r#" + Str.toNat "1" + "# + ), + RocResult::ok(1), + RocResult + ); } #[test] @@ -1380,14 +1388,11 @@ fn str_to_i128() { assert_evals_to!( indoc!( r#" - when Str.toI128 "1" is - Ok n -> n - Err _ -> 0 - - "# + Str.toI128 "1" + "# ), - 1, - i128 + RocResult::ok(1), + RocResult ); } @@ -1397,41 +1402,39 @@ fn str_to_u128() { assert_evals_to!( indoc!( r#" - when Str.toU128 "1" is - Ok n -> n - Err _ -> 0 - - "# + Str.toU128 "1" + "# ), - 1, - u128 + RocResult::ok(1), + RocResult ); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_to_i64() { assert_evals_to!( indoc!( r#" - when Str.toI64 "1" is - Ok n -> n - Err _ -> 0 - - "# + Str.toI64 "1" + "# ), - 1, - i64 + RocResult::ok(1), + RocResult ); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_to_u64() { assert_evals_to!( - r#"Str.toU64 "1""#, - RocResult::ok(1u64), - RocResult + indoc!( + r#" + Str.toU64 "1" + "# + ), + RocResult::ok(1), + RocResult ); } @@ -1441,14 +1444,11 @@ fn str_to_i32() { assert_evals_to!( indoc!( r#" - when Str.toI32 "1" is - Ok n -> n - Err _ -> 0 - - "# + Str.toI32 "1" + "# ), - 1, - i32 + RocResult::ok(1), + RocResult ); } @@ -1456,9 +1456,13 @@ fn str_to_i32() { #[cfg(any(feature = "gen-llvm"))] fn str_to_u32() { assert_evals_to!( - r#"Str.toU32 "1""#, - RocResult::ok(1u32), - RocResult + indoc!( + r#" + Str.toU32 "1" + "# + ), + RocResult::ok(1), + RocResult ); } @@ -1468,14 +1472,11 @@ fn str_to_i16() { assert_evals_to!( indoc!( r#" - when Str.toI16 "1" is - Ok n -> n - Err _ -> 0 - - "# + Str.toI16 "1" + "# ), - 1, - i16 + RocResult::ok(1), + RocResult ); } @@ -1485,14 +1486,11 @@ fn str_to_u16() { assert_evals_to!( indoc!( r#" - when Str.toU16 "1" is - Ok n -> n - Err _ -> 0 - - "# + Str.toU16 "1" + "# ), - 1, - u16 + RocResult::ok(1), + RocResult ); } @@ -1502,14 +1500,11 @@ fn str_to_i8() { assert_evals_to!( indoc!( r#" - when Str.toI8 "1" is - Ok n -> n - Err _ -> 0 - - "# + Str.toI8 "1" + "# ), - 1, - i8 + RocResult::ok(1), + RocResult ); } @@ -1519,14 +1514,11 @@ fn str_to_u8() { assert_evals_to!( indoc!( r#" - when Str.toU8 "1" is - Ok n -> n - Err _ -> 0 - - "# + Str.toU8 "1" + "# ), - 1, - u8 + RocResult::ok(1), + RocResult ); } From b2a875c3550e2f6dda95d6a7ede926c00271d127 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 14 Feb 2023 15:30:09 +0100 Subject: [PATCH 26/48] use debug_assert_eq --- crates/compiler/gen_dev/src/generic64/storage.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs index c4e9100713..f6e650c995 100644 --- a/crates/compiler/gen_dev/src/generic64/storage.rs +++ b/crates/compiler/gen_dev/src/generic64/storage.rs @@ -749,8 +749,11 @@ impl< Layout::Builtin(builtin) => match builtin { Builtin::Int(int_width) => match int_width { IntWidth::I128 | IntWidth::U128 => { - // can we treat this as 2 u64's? - todo!() + let (from_offset, size) = self.stack_offset_and_size(sym); + debug_assert_eq!(from_offset % 8, 0); + debug_assert_eq!(size % 8, 0); + debug_assert_eq!(size, layout_interner.stack_size(*layout)); + self.copy_symbol_to_stack_offset_help(buf, size, from_offset, to_offset) } IntWidth::I64 | IntWidth::U64 => { debug_assert_eq!(to_offset % 8, 0); @@ -789,8 +792,8 @@ impl< Builtin::Decimal => todo!(), Builtin::Str | Builtin::List(_) => { let (from_offset, size) = self.stack_offset_and_size(sym); - debug_assert!(from_offset % 8 == 0); - debug_assert!(size % 8 == 0); + debug_assert_eq!(from_offset % 8, 0); + debug_assert_eq!(size % 8, 0); debug_assert_eq!(size, layout_interner.stack_size(*layout)); self.copy_symbol_to_stack_offset_help(buf, size, from_offset, to_offset) } @@ -818,8 +821,8 @@ impl< // Later, it will be reloaded and stored in refcounted as needed. _ if layout_interner.stack_size(*layout) > 8 => { let (from_offset, size) = self.stack_offset_and_size(sym); - debug_assert!(from_offset % 8 == 0); - debug_assert!(size % 8 == 0); + debug_assert_eq!(from_offset % 8, 0); + debug_assert_eq!(size % 8, 0); debug_assert_eq!(size, layout_interner.stack_size(*layout)); self.copy_symbol_to_stack_offset_help(buf, size, from_offset, to_offset) } From 808262892e4880fb3f1eedeea24908e75d466686 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 14 Feb 2023 15:30:31 +0100 Subject: [PATCH 27/48] fix off-by-one error in loop condition --- .../compiler/gen_dev/src/generic64/storage.rs | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs index f6e650c995..87b0d61442 100644 --- a/crates/compiler/gen_dev/src/generic64/storage.rs +++ b/crates/compiler/gen_dev/src/generic64/storage.rs @@ -841,32 +841,40 @@ impl< let size = size as i32; self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| { - for _ in (0..(size - copied)).step_by(8) { - ASM::mov_reg64_base32(buf, reg, from_offset + copied); - ASM::mov_base32_reg64(buf, to_offset + copied, reg); + if size - copied >= 8 { + for _ in (0..(size - copied)).step_by(8) { + ASM::mov_reg64_base32(buf, reg, from_offset + copied); + ASM::mov_base32_reg64(buf, to_offset + copied, reg); - copied += 8; + copied += 8; + } } - for _ in (0..(size - copied)).step_by(4) { - ASM::mov_reg32_base32(buf, reg, from_offset + copied); - ASM::mov_base32_reg32(buf, to_offset + copied, reg); + if size - copied >= 4 { + for _ in (0..(size - copied)).step_by(4) { + ASM::mov_reg32_base32(buf, reg, from_offset + copied); + ASM::mov_base32_reg32(buf, to_offset + copied, reg); - copied += 4; + copied += 4; + } } - for _ in (0..(size - copied)).step_by(2) { - ASM::mov_reg16_base32(buf, reg, from_offset + copied); - ASM::mov_base32_reg16(buf, to_offset + copied, reg); + if size - copied >= 2 { + for _ in (0..(size - copied)).step_by(2) { + ASM::mov_reg16_base32(buf, reg, from_offset + copied); + ASM::mov_base32_reg16(buf, to_offset + copied, reg); - copied += 2; + copied += 2; + } } - for _ in (0..(size - copied)).step_by(1) { - ASM::mov_reg8_base32(buf, reg, from_offset + copied); - ASM::mov_base32_reg8(buf, to_offset + copied, reg); + if size - copied >= 1 { + for _ in (0..(size - copied)).step_by(1) { + ASM::mov_reg8_base32(buf, reg, from_offset + copied); + ASM::mov_base32_reg8(buf, to_offset + copied, reg); - copied += 1; + copied += 1; + } } }); } From aac8c76c5fd74339ed9ffb7d8d3d9a0019d020a1 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 18 Feb 2023 16:43:51 +0100 Subject: [PATCH 28/48] fix mono tests --- ...e_in_polymorphic_expression_issue_4717.txt | 16 +++++----- crates/compiler/test_mono/generated/dict.txt | 8 ++--- .../encode_derived_nested_record_string.txt | 32 +++++++++---------- ...encode_derived_record_one_field_string.txt | 16 +++++----- ...ncode_derived_record_two_field_strings.txt | 16 +++++----- .../encode_derived_tag_one_field_string.txt | 16 +++++----- ...encode_derived_tag_two_payloads_string.txt | 16 +++++----- .../weakening_avoids_overspecialization.txt | 16 +++++----- 8 files changed, 68 insertions(+), 68 deletions(-) diff --git a/crates/compiler/test_mono/generated/anonymous_closure_in_polymorphic_expression_issue_4717.txt b/crates/compiler/test_mono/generated/anonymous_closure_in_polymorphic_expression_issue_4717.txt index 4538c4e459..84ec5fb5b2 100644 --- a/crates/compiler/test_mono/generated/anonymous_closure_in_polymorphic_expression_issue_4717.txt +++ b/crates/compiler/test_mono/generated/anonymous_closure_in_polymorphic_expression_issue_4717.txt @@ -3,7 +3,7 @@ procedure Bool.11 (#Attr.2, #Attr.3): ret Bool.24; procedure List.26 (List.152, List.153, List.154): - let List.493 : [C U64, C U64] = CallByName List.90 List.152 List.153 List.154; + let List.493 : [C U64, C U64] = CallByName List.91 List.152 List.153 List.154; let List.496 : U8 = 1i64; let List.497 : U8 = GetTagId List.493; let List.498 : Int1 = lowlevel Eq List.496 List.497; @@ -53,13 +53,7 @@ procedure List.72 (#Attr.2, #Attr.3, #Attr.4): let List.484 : List U8 = lowlevel ListSublist #Attr.2 #Attr.3 #Attr.4; ret List.484; -procedure List.90 (List.426, List.427, List.428): - let List.500 : U64 = 0i64; - let List.501 : U64 = CallByName List.6 List.426; - let List.499 : [C U64, C U64] = CallByName List.91 List.426 List.427 List.428 List.500 List.501; - ret List.499; - -procedure List.91 (List.528, List.529, List.530, List.531, List.532): +procedure List.80 (List.528, List.529, List.530, List.531, List.532): joinpoint List.502 List.429 List.430 List.431 List.432 List.433: let List.504 : Int1 = CallByName Num.22 List.432 List.433; if List.504 then @@ -83,6 +77,12 @@ procedure List.91 (List.528, List.529, List.530, List.531, List.532): in jump List.502 List.528 List.529 List.530 List.531 List.532; +procedure List.91 (List.426, List.427, List.428): + let List.500 : U64 = 0i64; + let List.501 : U64 = CallByName List.6 List.426; + let List.499 : [C U64, C U64] = CallByName List.80 List.426 List.427 List.428 List.500 List.501; + ret List.499; + procedure Num.19 (#Attr.2, #Attr.3): let Num.258 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Num.258; diff --git a/crates/compiler/test_mono/generated/dict.txt b/crates/compiler/test_mono/generated/dict.txt index d1c66212c0..d85f844125 100644 --- a/crates/compiler/test_mono/generated/dict.txt +++ b/crates/compiler/test_mono/generated/dict.txt @@ -21,12 +21,12 @@ procedure Dict.4 (Dict.497): procedure List.11 (List.114, List.115): let List.479 : List I8 = CallByName List.68 List.115; - let List.478 : List I8 = CallByName List.80 List.114 List.115 List.479; + let List.478 : List I8 = CallByName List.81 List.114 List.115 List.479; ret List.478; procedure List.11 (List.114, List.115): let List.491 : List U64 = CallByName List.68 List.115; - let List.490 : List U64 = CallByName List.80 List.114 List.115 List.491; + let List.490 : List U64 = CallByName List.81 List.114 List.115 List.491; ret List.490; procedure List.68 (#Attr.2): @@ -45,7 +45,7 @@ procedure List.71 (#Attr.2, #Attr.3): let List.498 : List U64 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; ret List.498; -procedure List.80 (List.502, List.503, List.504): +procedure List.81 (List.502, List.503, List.504): joinpoint List.480 List.116 List.117 List.118: let List.488 : U64 = 0i64; let List.482 : Int1 = CallByName Num.24 List.117 List.488; @@ -59,7 +59,7 @@ procedure List.80 (List.502, List.503, List.504): in jump List.480 List.502 List.503 List.504; -procedure List.80 (List.510, List.511, List.512): +procedure List.81 (List.510, List.511, List.512): joinpoint List.492 List.116 List.117 List.118: let List.500 : U64 = 0i64; let List.494 : Int1 = CallByName Num.24 List.117 List.500; diff --git a/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt b/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt index 0c7233dd44..4a2a6c52ea 100644 --- a/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt @@ -213,11 +213,11 @@ procedure List.138 (List.139, List.140, List.137): ret List.592; procedure List.18 (List.135, List.136, List.137): - let List.500 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; + let List.500 : {List U8, U64} = CallByName List.91 List.135 List.136 List.137; ret List.500; procedure List.18 (List.135, List.136, List.137): - let List.573 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; + let List.573 : {List U8, U64} = CallByName List.91 List.135 List.136 List.137; ret List.573; procedure List.4 (List.106, List.107): @@ -258,19 +258,7 @@ procedure List.8 (#Attr.2, #Attr.3): let List.594 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; ret List.594; -procedure List.90 (List.426, List.427, List.428): - let List.504 : U64 = 0i64; - let List.505 : U64 = CallByName List.6 List.426; - let List.503 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.504 List.505; - ret List.503; - -procedure List.90 (List.426, List.427, List.428): - let List.577 : U64 = 0i64; - let List.578 : U64 = CallByName List.6 List.426; - let List.576 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.577 List.578; - ret List.576; - -procedure List.91 (List.531, List.532, List.533, List.534, List.535): +procedure List.80 (List.531, List.532, List.533, List.534, List.535): joinpoint List.506 List.429 List.430 List.431 List.432 List.433: let List.508 : Int1 = CallByName Num.22 List.432 List.433; if List.508 then @@ -284,7 +272,7 @@ procedure List.91 (List.531, List.532, List.533, List.534, List.535): in jump List.506 List.531 List.532 List.533 List.534 List.535; -procedure List.91 (List.605, List.606, List.607, List.608, List.609): +procedure List.80 (List.605, List.606, List.607, List.608, List.609): joinpoint List.579 List.429 List.430 List.431 List.432 List.433: let List.581 : Int1 = CallByName Num.22 List.432 List.433; if List.581 then @@ -298,6 +286,18 @@ procedure List.91 (List.605, List.606, List.607, List.608, List.609): in jump List.579 List.605 List.606 List.607 List.608 List.609; +procedure List.91 (List.426, List.427, List.428): + let List.504 : U64 = 0i64; + let List.505 : U64 = CallByName List.6 List.426; + let List.503 : {List U8, U64} = CallByName List.80 List.426 List.427 List.428 List.504 List.505; + ret List.503; + +procedure List.91 (List.426, List.427, List.428): + let List.577 : U64 = 0i64; + let List.578 : U64 = CallByName List.6 List.426; + let List.576 : {List U8, U64} = CallByName List.80 List.426 List.427 List.428 List.577 List.578; + ret List.576; + procedure Num.125 (#Attr.2): let Num.282 : U8 = lowlevel NumIntCast #Attr.2; ret Num.282; diff --git a/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt b/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt index 02d3f30240..7d79cabf6a 100644 --- a/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt @@ -122,7 +122,7 @@ procedure List.138 (List.139, List.140, List.137): ret List.525; procedure List.18 (List.135, List.136, List.137): - let List.506 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; + let List.506 : {List U8, U64} = CallByName List.91 List.135 List.136 List.137; ret List.506; procedure List.4 (List.106, List.107): @@ -155,13 +155,7 @@ procedure List.8 (#Attr.2, #Attr.3): let List.527 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; ret List.527; -procedure List.90 (List.426, List.427, List.428): - let List.510 : U64 = 0i64; - let List.511 : U64 = CallByName List.6 List.426; - let List.509 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.510 List.511; - ret List.509; - -procedure List.91 (List.538, List.539, List.540, List.541, List.542): +procedure List.80 (List.538, List.539, List.540, List.541, List.542): joinpoint List.512 List.429 List.430 List.431 List.432 List.433: let List.514 : Int1 = CallByName Num.22 List.432 List.433; if List.514 then @@ -175,6 +169,12 @@ procedure List.91 (List.538, List.539, List.540, List.541, List.542): in jump List.512 List.538 List.539 List.540 List.541 List.542; +procedure List.91 (List.426, List.427, List.428): + let List.510 : U64 = 0i64; + let List.511 : U64 = CallByName List.6 List.426; + let List.509 : {List U8, U64} = CallByName List.80 List.426 List.427 List.428 List.510 List.511; + ret List.509; + procedure Num.125 (#Attr.2): let Num.263 : U8 = lowlevel NumIntCast #Attr.2; ret Num.263; diff --git a/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt b/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt index 829fbcd5a2..5c223a1326 100644 --- a/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt +++ b/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt @@ -130,7 +130,7 @@ procedure List.138 (List.139, List.140, List.137): ret List.525; procedure List.18 (List.135, List.136, List.137): - let List.506 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; + let List.506 : {List U8, U64} = CallByName List.91 List.135 List.136 List.137; ret List.506; procedure List.4 (List.106, List.107): @@ -163,13 +163,7 @@ procedure List.8 (#Attr.2, #Attr.3): let List.527 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; ret List.527; -procedure List.90 (List.426, List.427, List.428): - let List.510 : U64 = 0i64; - let List.511 : U64 = CallByName List.6 List.426; - let List.509 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.510 List.511; - ret List.509; - -procedure List.91 (List.538, List.539, List.540, List.541, List.542): +procedure List.80 (List.538, List.539, List.540, List.541, List.542): joinpoint List.512 List.429 List.430 List.431 List.432 List.433: let List.514 : Int1 = CallByName Num.22 List.432 List.433; if List.514 then @@ -183,6 +177,12 @@ procedure List.91 (List.538, List.539, List.540, List.541, List.542): in jump List.512 List.538 List.539 List.540 List.541 List.542; +procedure List.91 (List.426, List.427, List.428): + let List.510 : U64 = 0i64; + let List.511 : U64 = CallByName List.6 List.426; + let List.509 : {List U8, U64} = CallByName List.80 List.426 List.427 List.428 List.510 List.511; + ret List.509; + procedure Num.125 (#Attr.2): let Num.263 : U8 = lowlevel NumIntCast #Attr.2; ret Num.263; diff --git a/crates/compiler/test_mono/generated/encode_derived_tag_one_field_string.txt b/crates/compiler/test_mono/generated/encode_derived_tag_one_field_string.txt index 40580ef311..51bb3b1c8c 100644 --- a/crates/compiler/test_mono/generated/encode_derived_tag_one_field_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_tag_one_field_string.txt @@ -131,7 +131,7 @@ procedure List.138 (List.139, List.140, List.137): ret List.531; procedure List.18 (List.135, List.136, List.137): - let List.512 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; + let List.512 : {List U8, U64} = CallByName List.91 List.135 List.136 List.137; ret List.512; procedure List.4 (List.106, List.107): @@ -164,13 +164,7 @@ procedure List.8 (#Attr.2, #Attr.3): let List.534 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; ret List.534; -procedure List.90 (List.426, List.427, List.428): - let List.516 : U64 = 0i64; - let List.517 : U64 = CallByName List.6 List.426; - let List.515 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.516 List.517; - ret List.515; - -procedure List.91 (List.544, List.545, List.546, List.547, List.548): +procedure List.80 (List.544, List.545, List.546, List.547, List.548): joinpoint List.518 List.429 List.430 List.431 List.432 List.433: let List.520 : Int1 = CallByName Num.22 List.432 List.433; if List.520 then @@ -184,6 +178,12 @@ procedure List.91 (List.544, List.545, List.546, List.547, List.548): in jump List.518 List.544 List.545 List.546 List.547 List.548; +procedure List.91 (List.426, List.427, List.428): + let List.516 : U64 = 0i64; + let List.517 : U64 = CallByName List.6 List.426; + let List.515 : {List U8, U64} = CallByName List.80 List.426 List.427 List.428 List.516 List.517; + ret List.515; + procedure Num.125 (#Attr.2): let Num.265 : U8 = lowlevel NumIntCast #Attr.2; ret Num.265; diff --git a/crates/compiler/test_mono/generated/encode_derived_tag_two_payloads_string.txt b/crates/compiler/test_mono/generated/encode_derived_tag_two_payloads_string.txt index 72a865f670..0995768410 100644 --- a/crates/compiler/test_mono/generated/encode_derived_tag_two_payloads_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_tag_two_payloads_string.txt @@ -137,7 +137,7 @@ procedure List.138 (List.139, List.140, List.137): ret List.531; procedure List.18 (List.135, List.136, List.137): - let List.512 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; + let List.512 : {List U8, U64} = CallByName List.91 List.135 List.136 List.137; ret List.512; procedure List.4 (List.106, List.107): @@ -170,13 +170,7 @@ procedure List.8 (#Attr.2, #Attr.3): let List.534 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; ret List.534; -procedure List.90 (List.426, List.427, List.428): - let List.516 : U64 = 0i64; - let List.517 : U64 = CallByName List.6 List.426; - let List.515 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.516 List.517; - ret List.515; - -procedure List.91 (List.544, List.545, List.546, List.547, List.548): +procedure List.80 (List.544, List.545, List.546, List.547, List.548): joinpoint List.518 List.429 List.430 List.431 List.432 List.433: let List.520 : Int1 = CallByName Num.22 List.432 List.433; if List.520 then @@ -190,6 +184,12 @@ procedure List.91 (List.544, List.545, List.546, List.547, List.548): in jump List.518 List.544 List.545 List.546 List.547 List.548; +procedure List.91 (List.426, List.427, List.428): + let List.516 : U64 = 0i64; + let List.517 : U64 = CallByName List.6 List.426; + let List.515 : {List U8, U64} = CallByName List.80 List.426 List.427 List.428 List.516 List.517; + ret List.515; + procedure Num.125 (#Attr.2): let Num.265 : U8 = lowlevel NumIntCast #Attr.2; ret Num.265; diff --git a/crates/compiler/test_mono/generated/weakening_avoids_overspecialization.txt b/crates/compiler/test_mono/generated/weakening_avoids_overspecialization.txt index 6720a3c26e..079dd957b6 100644 --- a/crates/compiler/test_mono/generated/weakening_avoids_overspecialization.txt +++ b/crates/compiler/test_mono/generated/weakening_avoids_overspecialization.txt @@ -3,7 +3,7 @@ procedure Bool.11 (#Attr.2, #Attr.3): ret Bool.24; procedure List.26 (List.152, List.153, List.154): - let List.493 : [C U64, C U64] = CallByName List.90 List.152 List.153 List.154; + let List.493 : [C U64, C U64] = CallByName List.91 List.152 List.153 List.154; let List.496 : U8 = 1i64; let List.497 : U8 = GetTagId List.493; let List.498 : Int1 = lowlevel Eq List.496 List.497; @@ -53,13 +53,7 @@ procedure List.72 (#Attr.2, #Attr.3, #Attr.4): let List.484 : List U8 = lowlevel ListSublist #Attr.2 #Attr.3 #Attr.4; ret List.484; -procedure List.90 (List.426, List.427, List.428): - let List.500 : U64 = 0i64; - let List.501 : U64 = CallByName List.6 List.426; - let List.499 : [C U64, C U64] = CallByName List.91 List.426 List.427 List.428 List.500 List.501; - ret List.499; - -procedure List.91 (List.528, List.529, List.530, List.531, List.532): +procedure List.80 (List.528, List.529, List.530, List.531, List.532): joinpoint List.502 List.429 List.430 List.431 List.432 List.433: let List.504 : Int1 = CallByName Num.22 List.432 List.433; if List.504 then @@ -83,6 +77,12 @@ procedure List.91 (List.528, List.529, List.530, List.531, List.532): in jump List.502 List.528 List.529 List.530 List.531 List.532; +procedure List.91 (List.426, List.427, List.428): + let List.500 : U64 = 0i64; + let List.501 : U64 = CallByName List.6 List.426; + let List.499 : [C U64, C U64] = CallByName List.80 List.426 List.427 List.428 List.500 List.501; + ret List.499; + procedure Num.19 (#Attr.2, #Attr.3): let Num.258 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Num.258; From bece06ba10029a2de094c68f68285066187d481d Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 18 Feb 2023 18:12:32 +0100 Subject: [PATCH 29/48] work around universally quantified type variable --- crates/compiler/test_gen/src/gen_list.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/compiler/test_gen/src/gen_list.rs b/crates/compiler/test_gen/src/gen_list.rs index 73128e9829..064c67b453 100644 --- a/crates/compiler/test_gen/src/gen_list.rs +++ b/crates/compiler/test_gen/src/gen_list.rs @@ -1887,7 +1887,7 @@ fn first_wildcard_empty_list() { assert_evals_to!( indoc!( r#" - List.first [] + List.last [] |> Result.map (\_ -> 0i64) "# ), RocResult::err(()), @@ -1932,7 +1932,7 @@ fn last_wildcard_empty_list() { assert_evals_to!( indoc!( r#" - List.last [] + List.last [] |> Result.map (\_ -> 0i64) "# ), RocResult::err(()), From 5b52cfe79b4d64adbefb8f46afcd813658953261 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 18 Feb 2023 23:18:48 +0100 Subject: [PATCH 30/48] disable str_append_scalar; it segfaults for unclear reasons --- crates/compiler/test_gen/src/gen_str.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/compiler/test_gen/src/gen_str.rs b/crates/compiler/test_gen/src/gen_str.rs index 0bacd0daa3..4b4b61f41a 100644 --- a/crates/compiler/test_gen/src/gen_str.rs +++ b/crates/compiler/test_gen/src/gen_str.rs @@ -1849,7 +1849,7 @@ fn str_walk_utf8_with_index() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm"))] fn str_append_scalar() { assert_evals_to!( indoc!( From fe4d81a245576839ab8210ffb79efcd5d7575b43 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Mon, 20 Feb 2023 15:19:27 +1100 Subject: [PATCH 31/48] correct all uses of Bool.true --- www/generate_tutorial/src/input/tutorial.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/www/generate_tutorial/src/input/tutorial.md b/www/generate_tutorial/src/input/tutorial.md index f1099b620c..114398626b 100644 --- a/www/generate_tutorial/src/input/tutorial.md +++ b/www/generate_tutorial/src/input/tutorial.md @@ -629,7 +629,7 @@ We can also give `List.map` a named function, instead of an anonymous one: List.map [1, 2, 3] Num.isOdd -This `Num.isOdd` function returns `Bool.true` if it's given an odd number, and `Bool.false` otherwise. So `Num.isOdd 5` returns true and `Num.isOdd 2` returns false. +This `Num.isOdd` function returns `Bool.true` if it's given an odd number, and `Bool.false` otherwise. So `Num.isOdd 5` returns `Bool.true` and `Num.isOdd 2` returns `Bool.false`. As such, calling `List.map [1, 2, 3] Num.isOdd` returns a new list of `[Bool.true, Bool.false, Bool.true]`. @@ -694,7 +694,7 @@ These two versions compile to the same thing. As a convenience, Roc lets you spe ### [`List.any` and `List.all`](#list-any-and-list-all) {#list-any-and-list-all} -There are several functions that work like `List.map`, they walk through each element of a list and do something with it. Another is `List.any`, which returns `Bool.true` if calling the given function on any element in the list returns `true`: +There are several functions that work like `List.map`, they walk through each element of a list and do something with it. Another is `List.any`, which returns `Bool.true` if calling the given function on any element in the list returns `Bool.true`:
List.any [1, 2, 3] Num.isOdd
 # returns `Bool.true` because 1 and 3 are odd
@@ -704,7 +704,7 @@ There are several functions that work like `List.map`, they walk through each el
 # returns `Bool.false` because none of these is negative
 
-There's also `List.all` which only returns `true` if all the elements in the list pass the test: +There's also `List.all` which only returns `Bool.true` if all the elements in the list pass the test:
List.all [1, 2, 3] Num.isOdd
 # returns `Bool.false` because 2 is not odd
@@ -722,7 +722,7 @@ You can also drop elements from a list. One way is `List.dropAt` - for example:
 # drops the element at offset 1 ("Lee") and returns ["Sam", "Ari"]
 
-Another way is to use `List.keepIf`, which passes each of the list's elements to the given function, and then keeps them only if that function returns `true`. +Another way is to use `List.keepIf`, which passes each of the list's elements to the given function, and then keeps them only if that function returns `Bool.true`.
List.keepIf [1, 2, 3, 4, 5] Num.isEven
 # returns [2, 4]
@@ -1279,7 +1279,7 @@ You can write automated tests for your Roc code like so:
 expect pluralize "cactus" "cacti" 2 == "2 cacti"
 
-If you put this in a file named `main.roc` and run `roc test`, Roc will execute the two `expect` expressions (that is, the two `pluralize` calls) and report any that returned `false`. +If you put this in a file named `main.roc` and run `roc test`, Roc will execute the two `expect` expressions (that is, the two `pluralize` calls) and report any that returned `Bool.false`. If a test fails, it will not show the actual value that differs from the expected value. To show the actual value, you can write the expect like this: From 3acf16bc74c96edee4b9e73d3447c84aaf861363 Mon Sep 17 00:00:00 2001 From: Giacomo Cavalieri Date: Mon, 20 Feb 2023 21:38:18 +0100 Subject: [PATCH 32/48] Wrap inline code on mobile devices Signed-off-by: Giacomo Cavalieri --- www/public/site.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/www/public/site.css b/www/public/site.css index 393bda01c4..4c4d32dcf9 100644 --- a/www/public/site.css +++ b/www/public/site.css @@ -322,6 +322,10 @@ p, aside, li, footer { h1 code, h2 code, h3 code, h4 code, h5 code { font-size: inherit; } + + code { + white-space: normal; + } #tutorial-toc-toggle-label, #close-tutorial-toc { From 5a1898b2850282f4f416c30e0c075733d349382c Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 20 Feb 2023 18:42:05 -0600 Subject: [PATCH 33/48] Pretty-print opaque refs --- crates/compiler/can/src/debug/pretty_print.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/compiler/can/src/debug/pretty_print.rs b/crates/compiler/can/src/debug/pretty_print.rs index fa981db290..361cdf0b70 100644 --- a/crates/compiler/can/src/debug/pretty_print.rs +++ b/crates/compiler/can/src/debug/pretty_print.rs @@ -385,7 +385,15 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, ), Crash { .. } => todo!(), ZeroArgumentTag { .. } => todo!(), - OpaqueRef { .. } => todo!(), + OpaqueRef { name, argument, .. } => maybe_paren!( + Free, + p, + || true, + pp_sym(c, f, *name) + .append(f.space()) + .append(expr(c, AppArg, f, &argument.1.value)) + .group() + ), Dbg { .. } => todo!(), Expect { .. } => todo!(), ExpectFx { .. } => todo!(), From e5c3376e9000a76f6fb43def40ce6956b9f52386 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 20 Feb 2023 18:49:18 -0600 Subject: [PATCH 34/48] Debug ProcLayouts --- crates/compiler/mono/src/ir.rs | 27 +++++++++++++++++++++++ crates/compiler/mono/src/layout.rs | 8 +++++++ crates/compiler/mono/src/layout/intern.rs | 8 +++++-- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index a55373ba33..cceb0abca9 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -3933,6 +3933,33 @@ impl<'a> ProcLayout<'a> { } } } + + pub fn dbg_deep<'r, I: LayoutInterner<'a>>(&self, interner: &'r I) -> DbgProcLayout<'a, 'r, I> { + DbgProcLayout { + layout: *self, + interner, + } + } +} + +pub struct DbgProcLayout<'a, 'r, I: LayoutInterner<'a>> { + layout: ProcLayout<'a>, + interner: &'r I, +} + +impl<'a, 'r, I: LayoutInterner<'a>> std::fmt::Debug for DbgProcLayout<'a, 'r, I> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let ProcLayout { + arguments, + result, + niche, + } = self.layout; + f.debug_struct("ProcLayout") + .field("arguments", &self.interner.dbg_deep_iter(arguments)) + .field("result", &self.interner.dbg_deep(result)) + .field("niche", &niche.dbg_deep(self.interner)) + .finish() + } } fn specialize_naked_symbol<'a>( diff --git a/crates/compiler/mono/src/layout.rs b/crates/compiler/mono/src/layout.rs index 428052f956..9dcc01ef27 100644 --- a/crates/compiler/mono/src/layout.rs +++ b/crates/compiler/mono/src/layout.rs @@ -1313,6 +1313,14 @@ impl<'a> Niche<'a> { ]), } } + + pub fn dbg_deep<'r, I: LayoutInterner<'a>>( + &'r self, + interner: &'r I, + ) -> crate::layout::intern::dbg::DbgFields<'a, 'r, I> { + let NichePriv::Captures(caps) = &self.0; + interner.dbg_deep_iter(caps) + } } #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] diff --git a/crates/compiler/mono/src/layout/intern.rs b/crates/compiler/mono/src/layout/intern.rs index 8687f010e9..158a47dd74 100644 --- a/crates/compiler/mono/src/layout/intern.rs +++ b/crates/compiler/mono/src/layout/intern.rs @@ -365,6 +365,10 @@ pub trait LayoutInterner<'a>: Sized { fn dbg_deep<'r>(&'r self, layout: InLayout<'a>) -> dbg::Dbg<'a, 'r, Self> { dbg::Dbg(self, layout) } + + fn dbg_deep_iter<'r>(&'r self, layouts: &'a [InLayout<'a>]) -> dbg::DbgFields<'a, 'r, Self> { + dbg::DbgFields(self, layouts) + } } /// An interned layout. @@ -1274,7 +1278,7 @@ mod equiv { } } -mod dbg { +pub mod dbg { use roc_module::symbol::Symbol; use crate::layout::{Builtin, LambdaSet, Layout, UnionLayout}; @@ -1311,7 +1315,7 @@ mod dbg { } } - struct DbgFields<'a, 'r, I: LayoutInterner<'a>>(&'r I, &'a [InLayout<'a>]); + pub struct DbgFields<'a, 'r, I: LayoutInterner<'a>>(pub &'r I, pub &'a [InLayout<'a>]); impl<'a, 'r, I: LayoutInterner<'a>> std::fmt::Debug for DbgFields<'a, 'r, I> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { From f7aa7e734d86cc7d3726c17a06257f9033496c56 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 20 Feb 2023 18:49:29 -0600 Subject: [PATCH 35/48] Pass interner through to debug proc layouts misisng in param map --- crates/compiler/mono/src/borrow.rs | 108 +++++++++++++++++++--------- crates/compiler/mono/src/inc_dec.rs | 24 +++++-- 2 files changed, 92 insertions(+), 40 deletions(-) diff --git a/crates/compiler/mono/src/borrow.rs b/crates/compiler/mono/src/borrow.rs index 3d31172173..ae88dbecce 100644 --- a/crates/compiler/mono/src/borrow.rs +++ b/crates/compiler/mono/src/borrow.rs @@ -100,8 +100,14 @@ pub fn infer_borrow<'a>( // host-exposed functions must always own their arguments. let is_host_exposed = host_exposed_procs.contains(&key.0); - let param_offset = param_map.get_param_offset(key.0, key.1); - env.collect_proc(&mut param_map, proc, param_offset, is_host_exposed); + let param_offset = param_map.get_param_offset(interner, key.0, key.1); + env.collect_proc( + interner, + &mut param_map, + proc, + param_offset, + is_host_exposed, + ); } if !env.modified { @@ -167,6 +173,7 @@ impl<'a> DeclarationToIndex<'a> { fn get_param_offset( &self, + interner: &STLayoutInterner<'a>, needle_symbol: Symbol, needle_layout: ProcLayout<'a>, ) -> ParamOffset { @@ -181,12 +188,14 @@ impl<'a> DeclarationToIndex<'a> { .elements .iter() .filter_map(|(Declaration { symbol, layout }, _)| { - (*symbol == needle_symbol).then_some(layout) + (*symbol == needle_symbol) + .then_some(layout) + .map(|l| l.dbg_deep(interner)) }) .collect::>(); unreachable!( "symbol/layout {:?} {:#?} combo must be in DeclarationToIndex\nHowever {} similar layouts were found:\n{:#?}", - needle_symbol, needle_layout, similar.len(), similar + needle_symbol, needle_layout.dbg_deep(interner), similar.len(), similar, ) } } @@ -206,13 +215,24 @@ pub struct ParamMap<'a> { } impl<'a> ParamMap<'a> { - pub fn get_param_offset(&self, symbol: Symbol, layout: ProcLayout<'a>) -> ParamOffset { - self.declaration_to_index.get_param_offset(symbol, layout) + pub fn get_param_offset( + &self, + interner: &STLayoutInterner<'a>, + symbol: Symbol, + layout: ProcLayout<'a>, + ) -> ParamOffset { + self.declaration_to_index + .get_param_offset(interner, symbol, layout) } - pub fn get_symbol(&self, symbol: Symbol, layout: ProcLayout<'a>) -> Option<&[Param<'a>]> { + pub fn get_symbol( + &self, + interner: &STLayoutInterner<'a>, + symbol: Symbol, + layout: ProcLayout<'a>, + ) -> Option<&[Param<'a>]> { // let index: usize = self.declaration_to_index[&(symbol, layout)].into(); - let index: usize = self.get_param_offset(symbol, layout).into(); + let index: usize = self.get_param_offset(interner, symbol, layout).into(); self.declarations.get(index..index + layout.arguments.len()) } @@ -292,7 +312,7 @@ impl<'a> ParamMap<'a> { return; } - let index: usize = self.get_param_offset(key.0, key.1).into(); + let index: usize = self.get_param_offset(interner, key.0, key.1).into(); for (i, param) in Self::init_borrow_args(arena, interner, proc.args) .iter() @@ -312,7 +332,7 @@ impl<'a> ParamMap<'a> { proc: &Proc<'a>, key: (Symbol, ProcLayout<'a>), ) { - let index: usize = self.get_param_offset(key.0, key.1).into(); + let index: usize = self.get_param_offset(interner, key.0, key.1).into(); for (i, param) in Self::init_borrow_args_always_owned(arena, proc.args) .iter() @@ -534,7 +554,13 @@ impl<'a> BorrowInfState<'a> { /// /// and determines whether z and which of the symbols used in e /// must be taken as owned parameters - fn collect_call(&mut self, param_map: &mut ParamMap<'a>, z: Symbol, e: &crate::ir::Call<'a>) { + fn collect_call( + &mut self, + interner: &STLayoutInterner<'a>, + param_map: &mut ParamMap<'a>, + z: Symbol, + e: &crate::ir::Call<'a>, + ) { use crate::ir::CallType::*; let crate::ir::Call { @@ -553,7 +579,7 @@ impl<'a> BorrowInfState<'a> { // get the borrow signature of the applied function let ps = param_map - .get_symbol(name.name(), top_level) + .get_symbol(interner, name.name(), top_level) .expect("function is defined"); // the return value will be owned @@ -595,11 +621,14 @@ impl<'a> BorrowInfState<'a> { niche: passed_function.name.niche(), }; - let function_ps = - match param_map.get_symbol(passed_function.name.name(), closure_layout) { - Some(function_ps) => function_ps, - None => unreachable!(), - }; + let function_ps = match param_map.get_symbol( + interner, + passed_function.name.name(), + closure_layout, + ) { + Some(function_ps) => function_ps, + None => unreachable!(), + }; match op { ListMap { xs } => { @@ -671,7 +700,13 @@ impl<'a> BorrowInfState<'a> { } } - fn collect_expr(&mut self, param_map: &mut ParamMap<'a>, z: Symbol, e: &Expr<'a>) { + fn collect_expr( + &mut self, + interner: &STLayoutInterner<'a>, + param_map: &mut ParamMap<'a>, + z: Symbol, + e: &Expr<'a>, + ) { use Expr::*; match e { @@ -724,7 +759,7 @@ impl<'a> BorrowInfState<'a> { self.own_var(z); } - Call(call) => self.collect_call(param_map, z, call), + Call(call) => self.collect_call(interner, param_map, z, call), Literal(_) | RuntimeErrorFunction(_) => {} @@ -757,6 +792,7 @@ impl<'a> BorrowInfState<'a> { #[allow(clippy::many_single_char_names)] fn preserve_tail_call( &mut self, + interner: &STLayoutInterner<'a>, param_map: &mut ParamMap<'a>, x: Symbol, v: &Expr<'a>, @@ -782,7 +818,7 @@ impl<'a> BorrowInfState<'a> { if self.current_proc == g.name() && x == *z { // anonymous functions (for which the ps may not be known) // can never be tail-recursive, so this is fine - if let Some(ps) = param_map.get_symbol(g.name(), top_level) { + if let Some(ps) = param_map.get_symbol(interner, g.name(), top_level) { self.own_params_using_args(ys, ps) } } @@ -801,7 +837,12 @@ impl<'a> BorrowInfState<'a> { } } - fn collect_stmt(&mut self, param_map: &mut ParamMap<'a>, stmt: &Stmt<'a>) { + fn collect_stmt( + &mut self, + interner: &STLayoutInterner<'a>, + param_map: &mut ParamMap<'a>, + stmt: &Stmt<'a>, + ) { use Stmt::*; match stmt { @@ -813,11 +854,11 @@ impl<'a> BorrowInfState<'a> { } => { let old = self.param_set.clone(); self.update_param_set(ys); - self.collect_stmt(param_map, v); + self.collect_stmt(interner, param_map, v); self.param_set = old; self.update_param_map_join_point(param_map, *j); - self.collect_stmt(param_map, b); + self.collect_stmt(interner, param_map, b); } Let(x, v, _, mut b) => { @@ -830,17 +871,17 @@ impl<'a> BorrowInfState<'a> { stack.push((*symbol, expr)); } - self.collect_stmt(param_map, b); + self.collect_stmt(interner, param_map, b); let mut it = stack.into_iter().rev(); // collect the final expr, and see if we need to preserve a tail call let (x, v) = it.next().unwrap(); - self.collect_expr(param_map, x, v); - self.preserve_tail_call(param_map, x, v, b); + self.collect_expr(interner, param_map, x, v); + self.preserve_tail_call(interner, param_map, x, v, b); for (x, v) in it { - self.collect_expr(param_map, x, v); + self.collect_expr(interner, param_map, x, v); } } @@ -859,21 +900,21 @@ impl<'a> BorrowInfState<'a> { .. } => { for (_, _, b) in branches.iter() { - self.collect_stmt(param_map, b); + self.collect_stmt(interner, param_map, b); } - self.collect_stmt(param_map, default_branch.1); + self.collect_stmt(interner, param_map, default_branch.1); } Dbg { remainder, .. } => { - self.collect_stmt(param_map, remainder); + self.collect_stmt(interner, param_map, remainder); } Expect { remainder, .. } => { - self.collect_stmt(param_map, remainder); + self.collect_stmt(interner, param_map, remainder); } ExpectFx { remainder, .. } => { - self.collect_stmt(param_map, remainder); + self.collect_stmt(interner, param_map, remainder); } Refcounting(_, _) => unreachable!("these have not been introduced yet"), @@ -891,6 +932,7 @@ impl<'a> BorrowInfState<'a> { fn collect_proc( &mut self, + interner: &STLayoutInterner<'a>, param_map: &mut ParamMap<'a>, proc: &Proc<'a>, param_offset: ParamOffset, @@ -912,7 +954,7 @@ impl<'a> BorrowInfState<'a> { owned_entry.extend(params.iter().map(|p| p.symbol)); } - self.collect_stmt(param_map, &proc.body); + self.collect_stmt(interner, param_map, &proc.body); self.update_param_map_declaration(param_map, param_offset, proc.args.len()); self.param_set = old; diff --git a/crates/compiler/mono/src/inc_dec.rs b/crates/compiler/mono/src/inc_dec.rs index eab6ec8acf..d4c5a5abc7 100644 --- a/crates/compiler/mono/src/inc_dec.rs +++ b/crates/compiler/mono/src/inc_dec.rs @@ -605,7 +605,7 @@ impl<'a, 'i> Context<'a, 'i> { // get the borrow signature let ps = self .param_map - .get_symbol(name.name(), top_level) + .get_symbol(self.layout_interner, name.name(), top_level) .expect("function is defined"); let v = Expr::Call(crate::ir::Call { @@ -653,10 +653,11 @@ impl<'a, 'i> Context<'a, 'i> { niche: passed_function.name.niche(), }; - let function_ps = match self - .param_map - .get_symbol(passed_function.name.name(), function_layout) - { + let function_ps = match self.param_map.get_symbol( + self.layout_interner, + passed_function.name.name(), + function_layout, + ) { Some(function_ps) => function_ps, None => unreachable!(), }; @@ -1510,19 +1511,28 @@ pub fn visit_procs<'a, 'i>( }; for (key, proc) in procs.iter_mut() { - visit_proc(arena, &mut codegen, param_map, &ctx, proc, key.1); + visit_proc( + arena, + layout_interner, + &mut codegen, + param_map, + &ctx, + proc, + key.1, + ); } } fn visit_proc<'a, 'i>( arena: &'a Bump, + interner: &STLayoutInterner<'a>, codegen: &mut CodegenTools<'i>, param_map: &'a ParamMap<'a>, ctx: &Context<'a, 'i>, proc: &mut Proc<'a>, layout: ProcLayout<'a>, ) { - let params = match param_map.get_symbol(proc.name.name(), layout) { + let params = match param_map.get_symbol(interner, proc.name.name(), layout) { Some(slice) => slice, None => Vec::from_iter_in( proc.args.iter().cloned().map(|(layout, symbol)| Param { From d7a069675b90b0f021d7ca4e8590ff8f6352a15e Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 20 Feb 2023 18:50:07 -0600 Subject: [PATCH 36/48] Lambda set compaction must preserve unique specializations of concrete types There are times that multiple concrete types may appear in unspecialized lambda sets that are being unified. The primary case is during monomorphization, when unspecialized lambda sets join at the same time that concrete types get instantiated. Since lambda set specialization and compaction happens only after unifications are complete, unifications that monomorphize can induce the above-described situation. In these cases, - unspecialized lambda sets that are due to equivalent type variables can be compacted, since they are in fact the same specialization. - unspecialized lambda sets that are due to different type variables cannot be compacted, even if their types unify, since they may point to different specializations. For example, consider the unspecialized lambda set `[[] + [A]:toEncoder:1 + [B]:toEncoder:1]` - this set wants two encoders, one for `[A]` and one for `[B]`, which is materially different from the set `[[] + [A, B]:toEncoder:1]`. --- crates/compiler/test_mono/src/tests.rs | 152 +++++++++++++++++++++++++ crates/compiler/unify/src/unify.rs | 59 +++++++--- 2 files changed, 197 insertions(+), 14 deletions(-) diff --git a/crates/compiler/test_mono/src/tests.rs b/crates/compiler/test_mono/src/tests.rs index 0fb8eaab3a..f553ce49d8 100644 --- a/crates/compiler/test_mono/src/tests.rs +++ b/crates/compiler/test_mono/src/tests.rs @@ -2571,3 +2571,155 @@ fn recursive_lambda_set_has_nested_non_recursive_lambda_sets_issue_5026() { "# ) } + +#[mono_test] +fn unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification() { + // This is a regression test for the ambient lambda set specialization algorithm. + // + // In the program below, monomorphization of `toEncoderQ` with the `Q` in `main` induces the + // resolution of `t.a` and `t.b`, and the unification of their pending unspecialization lambda + // sets, when `t.a` and `t.b` have been resolved to concrete types, but before the + // specialization procedure steps in to resolve the lambda sets concretely. That's because + // monomorphization unifies the general type of `toEncoderQ` with the concrete type, forcing + // concretization of `t`, but the specialization procedure runs only after the unification is + // complete. + // + // In this case, it's imperative that the unspecialized lambda sets of `toEncoder t.a` and + // `toEncoder t.b` wind up in the same lambda set, that is in + // + // tag : @MEncoder (Bytes, Linear -[[] + @MU8:toEncoder:1 + @MStr:toEncoder+1] -> Bytes) + // -[lTag]-> + // @MEncoder (Bytes, Linear -[[Linear:lTag:3 { @MEncoder (Bytes, Linear -[[] + @MU8:toEncoder:1 + @MStr:toEncoder:1] -> Bytes) }]] -> Bytes) + // + // rather than forcing the lambda set inside to `tag` to become disjoint, as e.g. + // + // tag : @MEncoder (Bytes, Linear -[[] + @MU8:toEncoder:1 + @MStr:toEncoder+1] -> Bytes) + // -[lTag]-> + // @MEncoder (Bytes, Linear -[[ + // Linear:lTag:3 { @MEncoder (Bytes, Linear -[[] + @MU8:toEncoder:1] -> Bytes) }, + // Linear:lTag:3 { @MEncoder (Bytes, Linear -[[] + @MStr:toEncoder:1] -> Bytes) }, + // ]] -> Bytes) + indoc!( + r#" + app "test" provides [main] to "./platform" + + MEncoder fmt := List U8, fmt -> List U8 | fmt has Format + + MEncoding has + toEncoder : val -> MEncoder fmt | val has MEncoding, fmt has Format + + Format has + u8 : {} -> MEncoder fmt | fmt has Format + str : {} -> MEncoder fmt | fmt has Format + tag : MEncoder fmt -> MEncoder fmt | fmt has Format + + Linear := {} has [Format {u8: lU8, str: lStr, tag: lTag}] + + MU8 := U8 has [MEncoding {toEncoder: toEncoderU8}] + MStr := Str has [MEncoding {toEncoder: toEncoderStr}] + + Q a b := { a: a, b: b } + + lU8 = \{} -> @MEncoder (\lst, @Linear {} -> lst) + lStr = \{} -> @MEncoder (\lst, @Linear {} -> lst) + + lTag = \@MEncoder doFormat -> @MEncoder (\lst, @Linear {} -> + doFormat lst (@Linear {}) + ) + + toEncoderU8 = \@MU8 _ -> u8 {} + + toEncoderStr = \@MStr _ -> str {} + + toEncoderQ = + \@Q t -> \fmt -> + @MEncoder doit = if Bool.true + then tag (toEncoder t.a) + else tag (toEncoder t.b) + + doit [] fmt + + main = + fmt = toEncoderQ (@Q {a : @MStr "", b: @MU8 7}) + fmt (@Linear {}) + "# + ) +} + +#[mono_test] +fn unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable() +{ + // This is a regression test for the ambient lambda set specialization algorithm. + // + // The principle of the test is equivalent to that of `unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification`. + // + // However, this test requires a larger reproduction because it is negative behavior is only + // visible in the presence of builtin ability usage (in this case, `Encoding` and + // `EncoderFormatting`). + // + // In this test, the payload types `[A]*` and `[B]*` of the encoded type `Q` are unifiable in + // their unspecialized lambda set representations under `toEncoderQ`; however, they must not + // be, because they in fact represent to different specializations of needed encoders. In + // particular, the lambda set `[[] + [A]:toEncoder:1 + [B]:toEncoder:1]` must be preserved, + // rather than collapsing to `[[] + [A, B]:toEncoder:1]`. + indoc!( + r#" + app "test" imports [Json] provides [main] to "./platform" + + Q a b := { a: a, b: b } has [Encoding {toEncoder: toEncoderQ}] + + toEncoderQ = + \@Q t -> Encode.custom \bytes, fmt -> + f = if Bool.true + then Encode.tag "A" [Encode.toEncoder t.a] + else Encode.tag "B" [Encode.toEncoder t.b] + + Encode.appendWith bytes f fmt + + accessor = @Q {a : A, b: B} + + main = + Encode.toBytes accessor Json.toUtf8 + "# + ) +} + +#[mono_test] +fn unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types() { + // This is a regression test for the ambient lambda set specialization algorithm. + // + // The principle of the test is equivalent to that of `unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification`. + // + // However, this test requires a larger reproduction because it is negative behavior is only + // visible in the presence of builtin ability usage (in this case, `Encoding` and + // `EncoderFormatting`). + // + // In this test, the payload types `Str` and `Str` of the encoded type `Q` are unifiable in + // their unspecialized lambda set representations under `toEncoderQ`, and moreoever they are + // equivalent specializations, since they both come from the same root variable `x`. In as + // such, the lambda set `[[] + Str:toEncoder:1]` should be produced during compaction, rather + // than staying as the expanded `[[] + Str:toEncoder:1 + Str:toEncoder:1]` after the types of + // `t.a` and `t.b` are filled in. + indoc!( + r#" + app "test" imports [Json] provides [main] to "./platform" + + Q a b := { a: a, b: b } has [Encoding {toEncoder: toEncoderQ}] + + toEncoderQ = + \@Q t -> Encode.custom \bytes, fmt -> + f = if Bool.true + then Encode.tag "A" [Encode.toEncoder t.a] + else Encode.tag "B" [Encode.toEncoder t.b] + + Encode.appendWith bytes f fmt + + accessor = + x = "" + @Q {a : x, b: x} + + main = + Encode.toBytes accessor Json.toUtf8 + "# + ) +} diff --git a/crates/compiler/unify/src/unify.rs b/crates/compiler/unify/src/unify.rs index 77c3317306..c38e9b73ae 100644 --- a/crates/compiler/unify/src/unify.rs +++ b/crates/compiler/unify/src/unify.rs @@ -1568,8 +1568,10 @@ fn unspecialized_lambda_set_sorter(subs: &Subs, uls1: Uls, uls2: Uls) -> std::cm } (FlexAbleVar(..), _) => Greater, (_, FlexAbleVar(..)) => Less, - // For everything else, the order is irrelevant - (_, _) => Less, + // For everything else, sort by the root key + (_, _) => subs + .get_root_key_without_compacting(var1) + .cmp(&subs.get_root_key_without_compacting(var2)), } } ord => ord, @@ -1731,6 +1733,8 @@ fn unify_unspecialized_lambdas( let kept = uls_left.next().unwrap(); merged_uls.push(*kept); } else { + // CASE: disjoint_flex_specializations + // // ... a1 ... // ... b1 ... // => ... a1, b1 ... @@ -1800,20 +1804,47 @@ fn unify_unspecialized_lambdas( let _dropped = uls_left.next().unwrap(); } (_, _) => { - // ... {foo: _} ... - // ... {foo: _} ... - // => ... {foo: _} ... - // - // Unify them, then advance one. - // (the choice is arbitrary, so we choose the left) + if env.subs.equivalent_without_compacting(var_l, var_r) { + // ... a1 ... + // ... b1=a1 ... + // => ... a1 ... + // + // Keep the one on the left, drop the one on the right. Then progress + // both, because the next variable on the left must be disjoint from + // the current on the right (resp. next variable on the right vs. + // current left) - if they aren't, then the invariant was broken. + // + // Then progress both, because the invariant tells us they must be + // disjoint, and if there were any concrete variables, they would have + // appeared earlier. + let _dropped = uls_right.next().unwrap(); + let kept = uls_left.next().unwrap(); + merged_uls.push(*kept); - let outcome = unify_pool(env, pool, var_l, var_r, mode); - if !outcome.mismatches.is_empty() { - return Err(outcome); + debug_assert!(uls_right + .peek() + .map(|r| env.subs.equivalent_without_compacting(var_l, r.0)) + .unwrap_or(true)); + debug_assert!(uls_left + .peek() + .map(|l| env.subs.equivalent_without_compacting(l.0, var_r)) + .unwrap_or(true)); + } else { + // Even if these two variables unify, since they are not equivalent, + // they correspond to different specializations! As such we must not + // merge them. + // + // Instead, keep both, but do so by adding and advancing the side with + // the lower root. See CASE disjoint_flex_specializations for + // reasoning. + if env.subs.get_root_key(var_l) < env.subs.get_root_key(var_r) { + let kept = uls_left.next().unwrap(); + merged_uls.push(*kept); + } else { + let kept = uls_right.next().unwrap(); + merged_uls.push(*kept); + } } - whole_outcome.union(outcome); - - let _dropped = uls_left.next().unwrap(); } } } From 7a4fed932d2015bc763d0632b60e769dc5dca84b Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 20 Feb 2023 18:59:48 -0600 Subject: [PATCH 37/48] Add gen tests for concerete unspecialized set conservation --- crates/compiler/test_gen/src/gen_abilities.rs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/crates/compiler/test_gen/src/gen_abilities.rs b/crates/compiler/test_gen/src/gen_abilities.rs index 996bfb61ef..498293824e 100644 --- a/crates/compiler/test_gen/src/gen_abilities.rs +++ b/crates/compiler/test_gen/src/gen_abilities.rs @@ -787,6 +787,59 @@ fn encode_derived_record_with_many_types() { ) } +#[test] +#[cfg(all(any(feature = "gen-llvm", feature = "gen-wasm")))] +fn encode_derived_generic_record_with_different_field_types() { + assert_evals_to!( + indoc!( + r#" + app "test" + imports [Encode, Json] + provides [main] to "./platform" + + Q a b := {a: a, b: b} has [Encoding] + + q = @Q {a: 10u32, b: "fieldb"} + + main = + result = Str.fromUtf8 (Encode.toBytes q Json.toUtf8) + when result is + Ok s -> s + _ -> "" + "# + ), + RocStr::from(r#"{"a":10,"b":"fieldb"}"#), + RocStr + ) +} + +#[test] +#[cfg(all(any(feature = "gen-llvm", feature = "gen-wasm")))] +fn encode_derived_generic_tag_with_different_field_types() { + assert_evals_to!( + indoc!( + r#" + app "test" + imports [Encode, Json] + provides [main] to "./platform" + + Q a b := [A a, B b] has [Encoding] + + q : Q Str U32 + q = @Q (B 67) + + main = + result = Str.fromUtf8 (Encode.toBytes q Json.toUtf8) + when result is + Ok s -> s + _ -> "" + "# + ), + RocStr::from(r#"{"B":[67]}"#), + RocStr + ) +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn decode_use_stdlib() { From fbc75016e14233560bd1cbf617747917e26b6bf1 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 20 Feb 2023 19:31:22 -0600 Subject: [PATCH 38/48] Check in mono goldens --- ...not_duplicate_identical_concrete_types.txt | 235 +++++++++++ ...all_concrete_types_without_unification.txt | 82 ++++ ...types_without_unification_of_unifiable.txt | 374 ++++++++++++++++++ 3 files changed, 691 insertions(+) create mode 100644 crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt create mode 100644 crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification.txt create mode 100644 crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable.txt diff --git a/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt new file mode 100644 index 0000000000..47100569d8 --- /dev/null +++ b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt @@ -0,0 +1,235 @@ +procedure Bool.2 (): + let Bool.23 : Int1 = true; + ret Bool.23; + +procedure Encode.22 (Encode.93): + ret Encode.93; + +procedure Encode.22 (Encode.93): + ret Encode.93; + +procedure Encode.22 (Encode.93): + ret Encode.93; + +procedure Encode.23 (Encode.94, Encode.102, Encode.96): + let Encode.106 : List U8 = CallByName Test.5 Encode.94 Encode.96 Encode.102; + ret Encode.106; + +procedure Encode.23 (Encode.94, Encode.102, Encode.96): + let Encode.113 : List U8 = CallByName Json.126 Encode.94 Encode.96 Encode.102; + ret Encode.113; + +procedure Encode.23 (Encode.94, Encode.102, Encode.96): + let Encode.118 : List U8 = CallByName Json.96 Encode.94 Encode.96 Encode.102; + ret Encode.118; + +procedure Encode.25 (Encode.100, Encode.101): + let Encode.104 : List U8 = Array []; + let Encode.105 : {Str, Str} = CallByName Test.2 Encode.100; + let Encode.103 : List U8 = CallByName Encode.23 Encode.104 Encode.105 Encode.101; + ret Encode.103; + +procedure Json.1 (): + let Json.396 : {} = Struct {}; + ret Json.396; + +procedure Json.126 (Json.127, Json.399, #Attr.12): + let Json.125 : List Str = StructAtIndex 1 #Attr.12; + inc Json.125; + let Json.124 : Str = StructAtIndex 0 #Attr.12; + inc Json.124; + dec #Attr.12; + let Json.437 : I64 = 123i64; + let Json.436 : U8 = CallByName Num.125 Json.437; + let Json.433 : List U8 = CallByName List.4 Json.127 Json.436; + let Json.435 : I64 = 34i64; + let Json.434 : U8 = CallByName Num.125 Json.435; + let Json.431 : List U8 = CallByName List.4 Json.433 Json.434; + let Json.432 : List U8 = CallByName Str.12 Json.124; + let Json.428 : List U8 = CallByName List.8 Json.431 Json.432; + let Json.430 : I64 = 34i64; + let Json.429 : U8 = CallByName Num.125 Json.430; + let Json.425 : List U8 = CallByName List.4 Json.428 Json.429; + let Json.427 : I64 = 58i64; + let Json.426 : U8 = CallByName Num.125 Json.427; + let Json.422 : List U8 = CallByName List.4 Json.425 Json.426; + let Json.424 : I64 = 91i64; + let Json.423 : U8 = CallByName Num.125 Json.424; + let Json.129 : List U8 = CallByName List.4 Json.422 Json.423; + let Json.421 : U64 = CallByName List.6 Json.125; + let Json.409 : {List U8, U64} = Struct {Json.129, Json.421}; + let Json.410 : {} = Struct {}; + let Json.408 : {List U8, U64} = CallByName List.18 Json.125 Json.409 Json.410; + dec Json.125; + let Json.131 : List U8 = StructAtIndex 0 Json.408; + inc Json.131; + dec Json.408; + let Json.407 : I64 = 93i64; + let Json.406 : U8 = CallByName Num.125 Json.407; + let Json.403 : List U8 = CallByName List.4 Json.131 Json.406; + let Json.405 : I64 = 125i64; + let Json.404 : U8 = CallByName Num.125 Json.405; + let Json.402 : List U8 = CallByName List.4 Json.403 Json.404; + ret Json.402; + +procedure Json.128 (Json.401, Json.134): + let Json.132 : List U8 = StructAtIndex 0 Json.401; + inc Json.132; + let Json.133 : U64 = StructAtIndex 1 Json.401; + dec Json.401; + let Json.420 : {} = Struct {}; + let Json.135 : List U8 = CallByName Encode.23 Json.132 Json.134 Json.420; + joinpoint Json.415 Json.136: + let Json.413 : U64 = 1i64; + let Json.412 : U64 = CallByName Num.20 Json.133 Json.413; + let Json.411 : {List U8, U64} = Struct {Json.136, Json.412}; + ret Json.411; + in + let Json.419 : U64 = 1i64; + let Json.416 : Int1 = CallByName Num.24 Json.133 Json.419; + if Json.416 then + let Json.418 : I64 = 44i64; + let Json.417 : U8 = CallByName Num.125 Json.418; + let Json.414 : List U8 = CallByName List.4 Json.135 Json.417; + jump Json.415 Json.414; + else + jump Json.415 Json.135; + +procedure Json.18 (Json.95): + let Json.453 : Str = CallByName Encode.22 Json.95; + ret Json.453; + +procedure Json.21 (Json.124, Json.125): + let Json.439 : {Str, List Str} = Struct {Json.124, Json.125}; + let Json.438 : {Str, List Str} = CallByName Encode.22 Json.439; + ret Json.438; + +procedure Json.96 (Json.97, Json.443, Json.95): + let Json.452 : I64 = 34i64; + let Json.451 : U8 = CallByName Num.125 Json.452; + let Json.449 : List U8 = CallByName List.4 Json.97 Json.451; + let Json.450 : List U8 = CallByName Str.12 Json.95; + let Json.446 : List U8 = CallByName List.8 Json.449 Json.450; + let Json.448 : I64 = 34i64; + let Json.447 : U8 = CallByName Num.125 Json.448; + let Json.445 : List U8 = CallByName List.4 Json.446 Json.447; + ret Json.445; + +procedure List.138 (List.139, List.140, List.137): + let List.529 : {List U8, U64} = CallByName Json.128 List.139 List.140; + ret List.529; + +procedure List.18 (List.135, List.136, List.137): + let List.510 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; + ret List.510; + +procedure List.4 (List.106, List.107): + let List.509 : U64 = 1i64; + let List.508 : List U8 = CallByName List.70 List.106 List.509; + let List.507 : List U8 = CallByName List.71 List.508 List.107; + ret List.507; + +procedure List.6 (#Attr.2): + let List.530 : U64 = lowlevel ListLen #Attr.2; + ret List.530; + +procedure List.66 (#Attr.2, #Attr.3): + let List.526 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.526; + +procedure List.70 (#Attr.2, #Attr.3): + let List.482 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; + ret List.482; + +procedure List.71 (#Attr.2, #Attr.3): + let List.480 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.480; + +procedure List.8 (#Attr.2, #Attr.3): + let List.532 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; + ret List.532; + +procedure List.90 (List.426, List.427, List.428): + let List.514 : U64 = 0i64; + let List.515 : U64 = CallByName List.6 List.426; + let List.513 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.514 List.515; + ret List.513; + +procedure List.91 (List.542, List.543, List.544, List.545, List.546): + joinpoint List.516 List.429 List.430 List.431 List.432 List.433: + let List.518 : Int1 = CallByName Num.22 List.432 List.433; + if List.518 then + let List.525 : Str = CallByName List.66 List.429 List.432; + let List.519 : {List U8, U64} = CallByName List.138 List.430 List.525 List.431; + let List.522 : U64 = 1i64; + let List.521 : U64 = CallByName Num.19 List.432 List.522; + jump List.516 List.429 List.519 List.431 List.521 List.433; + else + ret List.430; + in + jump List.516 List.542 List.543 List.544 List.545 List.546; + +procedure Num.125 (#Attr.2): + let Num.265 : U8 = lowlevel NumIntCast #Attr.2; + ret Num.265; + +procedure Num.19 (#Attr.2, #Attr.3): + let Num.268 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.268; + +procedure Num.20 (#Attr.2, #Attr.3): + let Num.266 : U64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.266; + +procedure Num.22 (#Attr.2, #Attr.3): + let Num.269 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.269; + +procedure Num.24 (#Attr.2, #Attr.3): + let Num.267 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; + ret Num.267; + +procedure Str.12 (#Attr.2): + let Str.266 : List U8 = lowlevel StrToUtf8 #Attr.2; + ret Str.266; + +procedure Test.2 (Test.10): + let Test.15 : {Str, Str} = CallByName Encode.22 Test.10; + ret Test.15; + +procedure Test.3 (): + let Test.9 : Str = ""; + inc Test.9; + let Test.14 : {Str, Str} = Struct {Test.9, Test.9}; + ret Test.14; + +procedure Test.5 (Test.6, Test.7, Test.4): + joinpoint Test.20 Test.8: + let Test.18 : List U8 = CallByName Encode.23 Test.6 Test.8 Test.7; + ret Test.18; + in + let Test.25 : Int1 = CallByName Bool.2; + if Test.25 then + let Test.26 : Str = "A"; + let Test.29 : Str = StructAtIndex 0 Test.4; + inc Test.29; + dec Test.4; + let Test.28 : Str = CallByName Json.18 Test.29; + let Test.27 : List Str = Array [Test.28]; + let Test.19 : {Str, List Str} = CallByName Json.21 Test.26 Test.27; + jump Test.20 Test.19; + else + let Test.21 : Str = "B"; + let Test.24 : Str = StructAtIndex 1 Test.4; + inc Test.24; + dec Test.4; + let Test.23 : Str = CallByName Json.18 Test.24; + let Test.22 : List Str = Array [Test.23]; + let Test.19 : {Str, List Str} = CallByName Json.21 Test.21 Test.22; + jump Test.20 Test.19; + +procedure Test.0 (): + let Test.12 : {Str, Str} = CallByName Test.3; + let Test.13 : {} = CallByName Json.1; + let Test.11 : List U8 = CallByName Encode.25 Test.12 Test.13; + ret Test.11; diff --git a/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification.txt b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification.txt new file mode 100644 index 0000000000..6781849fa6 --- /dev/null +++ b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification.txt @@ -0,0 +1,82 @@ +procedure Bool.2 (): + let Bool.23 : Int1 = true; + ret Bool.23; + +procedure Test.12 (Test.52): + let Test.75 : Int1 = false; + ret Test.75; + +procedure Test.13 (Test.51): + let Test.83 : Int1 = true; + ret Test.83; + +procedure Test.14 (Test.50): + ret Test.50; + +procedure Test.15 (Test.49): + let Test.74 : {} = Struct {}; + let Test.73 : Int1 = CallByName Test.12 Test.74; + ret Test.73; + +procedure Test.16 (Test.48): + let Test.82 : {} = Struct {}; + let Test.81 : Int1 = CallByName Test.13 Test.82; + ret Test.81; + +procedure Test.17 (Test.47): + ret Test.47; + +procedure Test.35 (Test.36, Test.76): + inc Test.36; + ret Test.36; + +procedure Test.37 (Test.38, Test.84): + inc Test.38; + ret Test.38; + +procedure Test.40 (Test.41, Test.65, Test.39): + let Test.68 : {} = Struct {}; + joinpoint Test.69 Test.67: + ret Test.67; + in + switch Test.39: + case 0: + let Test.70 : List U8 = CallByName Test.35 Test.41 Test.68; + jump Test.69 Test.70; + + default: + let Test.71 : List U8 = CallByName Test.37 Test.41 Test.68; + jump Test.69 Test.71; + + +procedure Test.43 (Test.44, Test.42): + joinpoint Test.62 Test.60: + let Test.59 : List U8 = Array []; + let Test.58 : List U8 = CallByName Test.40 Test.59 Test.44 Test.60; + dec Test.59; + ret Test.58; + in + let Test.78 : Int1 = CallByName Bool.2; + if Test.78 then + let Test.80 : Str = StructAtIndex 0 Test.42; + inc Test.80; + dec Test.42; + let Test.79 : Int1 = CallByName Test.16 Test.80; + dec Test.80; + let Test.61 : Int1 = CallByName Test.14 Test.79; + jump Test.62 Test.61; + else + let Test.72 : U8 = StructAtIndex 1 Test.42; + dec Test.42; + let Test.63 : Int1 = CallByName Test.15 Test.72; + let Test.61 : Int1 = CallByName Test.14 Test.63; + jump Test.62 Test.61; + +procedure Test.0 (): + let Test.86 : Str = ""; + let Test.87 : U8 = 7i64; + let Test.55 : {Str, U8} = Struct {Test.86, Test.87}; + let Test.46 : {Str, U8} = CallByName Test.17 Test.55; + let Test.54 : {} = Struct {}; + let Test.53 : List U8 = CallByName Test.43 Test.54 Test.46; + ret Test.53; diff --git a/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable.txt b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable.txt new file mode 100644 index 0000000000..fd86d2a0da --- /dev/null +++ b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable.txt @@ -0,0 +1,374 @@ +procedure #Derived.0 (#Derived.1): + let #Derived_gen.10 : [C {}, C {}] = TagId(0) #Derived.1; + let #Derived_gen.9 : [C {}, C {}] = CallByName Encode.22 #Derived_gen.10; + ret #Derived_gen.9; + +procedure #Derived.2 (#Derived.3, #Derived.4, #Attr.12): + let #Derived.1 : {} = UnionAtIndex (Id 0) (Index 0) #Attr.12; + joinpoint #Derived_gen.14 #Derived_gen.13: + let #Derived_gen.12 : List U8 = CallByName Encode.23 #Derived.3 #Derived_gen.13 #Derived.4; + ret #Derived_gen.12; + in + let #Derived_gen.16 : Str = "A"; + let #Derived_gen.17 : List [] = Array []; + let #Derived_gen.15 : {Str, List []} = CallByName Json.21 #Derived_gen.16 #Derived_gen.17; + jump #Derived_gen.14 #Derived_gen.15; + +procedure #Derived.5 (#Derived.6): + let #Derived_gen.1 : [C {}, C {}] = TagId(1) #Derived.6; + let #Derived_gen.0 : [C {}, C {}] = CallByName Encode.22 #Derived_gen.1; + ret #Derived_gen.0; + +procedure #Derived.7 (#Derived.8, #Derived.9, #Attr.12): + let #Derived.6 : {} = UnionAtIndex (Id 1) (Index 0) #Attr.12; + joinpoint #Derived_gen.5 #Derived_gen.4: + let #Derived_gen.3 : List U8 = CallByName Encode.23 #Derived.8 #Derived_gen.4 #Derived.9; + ret #Derived_gen.3; + in + let #Derived_gen.7 : Str = "B"; + let #Derived_gen.8 : List [] = Array []; + let #Derived_gen.6 : {Str, List []} = CallByName Json.21 #Derived_gen.7 #Derived_gen.8; + jump #Derived_gen.5 #Derived_gen.6; + +procedure Bool.2 (): + let Bool.23 : Int1 = true; + ret Bool.23; + +procedure Encode.22 (Encode.93): + ret Encode.93; + +procedure Encode.22 (Encode.93): + ret Encode.93; + +procedure Encode.22 (Encode.93): + ret Encode.93; + +procedure Encode.22 (Encode.93): + ret Encode.93; + +procedure Encode.23 (Encode.94, Encode.102, Encode.96): + let Encode.106 : List U8 = CallByName Test.5 Encode.94 Encode.96 Encode.102; + ret Encode.106; + +procedure Encode.23 (Encode.94, Encode.102, Encode.96): + let Encode.113 : List U8 = CallByName Json.126 Encode.94 Encode.96 Encode.102; + ret Encode.113; + +procedure Encode.23 (Encode.94, Encode.102, Encode.96): + let Encode.117 : U8 = GetTagId Encode.102; + joinpoint Encode.118 Encode.116: + ret Encode.116; + in + switch Encode.117: + case 0: + let Encode.119 : List U8 = CallByName #Derived.2 Encode.94 Encode.96 Encode.102; + jump Encode.118 Encode.119; + + default: + let Encode.120 : List U8 = CallByName #Derived.7 Encode.94 Encode.96 Encode.102; + jump Encode.118 Encode.120; + + +procedure Encode.23 (Encode.94, Encode.102, Encode.96): + let Encode.132 : List U8 = CallByName Json.126 Encode.94 Encode.96 Encode.102; + ret Encode.132; + +procedure Encode.23 (Encode.94, Encode.102, Encode.96): + let Encode.136 : Str = "a Lambda Set is empty. Most likely there is a type error in your program."; + Crash Encode.136 + +procedure Encode.25 (Encode.100, Encode.101): + let Encode.104 : List U8 = Array []; + let Encode.105 : {{}, {}} = CallByName Test.2 Encode.100; + let Encode.103 : List U8 = CallByName Encode.23 Encode.104 Encode.105 Encode.101; + ret Encode.103; + +procedure Json.1 (): + let Json.396 : {} = Struct {}; + ret Json.396; + +procedure Json.126 (Json.127, Json.399, #Attr.12): + let Json.125 : List [C {}, C {}] = StructAtIndex 1 #Attr.12; + inc Json.125; + let Json.124 : Str = StructAtIndex 0 #Attr.12; + inc Json.124; + dec #Attr.12; + let Json.437 : I64 = 123i64; + let Json.436 : U8 = CallByName Num.125 Json.437; + let Json.433 : List U8 = CallByName List.4 Json.127 Json.436; + let Json.435 : I64 = 34i64; + let Json.434 : U8 = CallByName Num.125 Json.435; + let Json.431 : List U8 = CallByName List.4 Json.433 Json.434; + let Json.432 : List U8 = CallByName Str.12 Json.124; + let Json.428 : List U8 = CallByName List.8 Json.431 Json.432; + let Json.430 : I64 = 34i64; + let Json.429 : U8 = CallByName Num.125 Json.430; + let Json.425 : List U8 = CallByName List.4 Json.428 Json.429; + let Json.427 : I64 = 58i64; + let Json.426 : U8 = CallByName Num.125 Json.427; + let Json.422 : List U8 = CallByName List.4 Json.425 Json.426; + let Json.424 : I64 = 91i64; + let Json.423 : U8 = CallByName Num.125 Json.424; + let Json.129 : List U8 = CallByName List.4 Json.422 Json.423; + let Json.421 : U64 = CallByName List.6 Json.125; + let Json.409 : {List U8, U64} = Struct {Json.129, Json.421}; + let Json.410 : {} = Struct {}; + let Json.408 : {List U8, U64} = CallByName List.18 Json.125 Json.409 Json.410; + dec Json.125; + let Json.131 : List U8 = StructAtIndex 0 Json.408; + inc Json.131; + dec Json.408; + let Json.407 : I64 = 93i64; + let Json.406 : U8 = CallByName Num.125 Json.407; + let Json.403 : List U8 = CallByName List.4 Json.131 Json.406; + let Json.405 : I64 = 125i64; + let Json.404 : U8 = CallByName Num.125 Json.405; + let Json.402 : List U8 = CallByName List.4 Json.403 Json.404; + ret Json.402; + +procedure Json.126 (Json.127, Json.399, #Attr.12): + let Json.125 : List [] = StructAtIndex 1 #Attr.12; + inc Json.125; + let Json.124 : Str = StructAtIndex 0 #Attr.12; + inc Json.124; + dec #Attr.12; + let Json.487 : I64 = 123i64; + let Json.486 : U8 = CallByName Num.125 Json.487; + let Json.483 : List U8 = CallByName List.4 Json.127 Json.486; + let Json.485 : I64 = 34i64; + let Json.484 : U8 = CallByName Num.125 Json.485; + let Json.481 : List U8 = CallByName List.4 Json.483 Json.484; + let Json.482 : List U8 = CallByName Str.12 Json.124; + let Json.478 : List U8 = CallByName List.8 Json.481 Json.482; + let Json.480 : I64 = 34i64; + let Json.479 : U8 = CallByName Num.125 Json.480; + let Json.475 : List U8 = CallByName List.4 Json.478 Json.479; + let Json.477 : I64 = 58i64; + let Json.476 : U8 = CallByName Num.125 Json.477; + let Json.472 : List U8 = CallByName List.4 Json.475 Json.476; + let Json.474 : I64 = 91i64; + let Json.473 : U8 = CallByName Num.125 Json.474; + let Json.129 : List U8 = CallByName List.4 Json.472 Json.473; + let Json.471 : U64 = CallByName List.6 Json.125; + let Json.459 : {List U8, U64} = Struct {Json.129, Json.471}; + let Json.460 : {} = Struct {}; + let Json.458 : {List U8, U64} = CallByName List.18 Json.125 Json.459 Json.460; + dec Json.125; + let Json.131 : List U8 = StructAtIndex 0 Json.458; + inc Json.131; + dec Json.458; + let Json.457 : I64 = 93i64; + let Json.456 : U8 = CallByName Num.125 Json.457; + let Json.453 : List U8 = CallByName List.4 Json.131 Json.456; + let Json.455 : I64 = 125i64; + let Json.454 : U8 = CallByName Num.125 Json.455; + let Json.452 : List U8 = CallByName List.4 Json.453 Json.454; + ret Json.452; + +procedure Json.128 (Json.401, Json.134): + let Json.132 : List U8 = StructAtIndex 0 Json.401; + inc Json.132; + let Json.133 : U64 = StructAtIndex 1 Json.401; + dec Json.401; + let Json.420 : {} = Struct {}; + let Json.135 : List U8 = CallByName Encode.23 Json.132 Json.134 Json.420; + joinpoint Json.415 Json.136: + let Json.413 : U64 = 1i64; + let Json.412 : U64 = CallByName Num.20 Json.133 Json.413; + let Json.411 : {List U8, U64} = Struct {Json.136, Json.412}; + ret Json.411; + in + let Json.419 : U64 = 1i64; + let Json.416 : Int1 = CallByName Num.24 Json.133 Json.419; + if Json.416 then + let Json.418 : I64 = 44i64; + let Json.417 : U8 = CallByName Num.125 Json.418; + let Json.414 : List U8 = CallByName List.4 Json.135 Json.417; + jump Json.415 Json.414; + else + jump Json.415 Json.135; + +procedure Json.128 (Json.401, Json.134): + let Json.132 : List U8 = StructAtIndex 0 Json.401; + inc Json.132; + let Json.133 : U64 = StructAtIndex 1 Json.401; + dec Json.401; + let Json.470 : {} = Struct {}; + let Json.135 : List U8 = CallByName Encode.23 Json.132 Json.134 Json.470; + dec Json.132; + joinpoint Json.465 Json.136: + let Json.463 : U64 = 1i64; + let Json.462 : U64 = CallByName Num.20 Json.133 Json.463; + let Json.461 : {List U8, U64} = Struct {Json.136, Json.462}; + ret Json.461; + in + let Json.469 : U64 = 1i64; + let Json.466 : Int1 = CallByName Num.24 Json.133 Json.469; + if Json.466 then + let Json.468 : I64 = 44i64; + let Json.467 : U8 = CallByName Num.125 Json.468; + let Json.464 : List U8 = CallByName List.4 Json.135 Json.467; + jump Json.465 Json.464; + else + jump Json.465 Json.135; + +procedure Json.21 (Json.124, Json.125): + let Json.439 : {Str, List [C {}, C {}]} = Struct {Json.124, Json.125}; + let Json.438 : {Str, List [C {}, C {}]} = CallByName Encode.22 Json.439; + ret Json.438; + +procedure Json.21 (Json.124, Json.125): + let Json.489 : {Str, List []} = Struct {Json.124, Json.125}; + let Json.488 : {Str, List []} = CallByName Encode.22 Json.489; + ret Json.488; + +procedure List.138 (List.139, List.140, List.137): + let List.523 : {List U8, U64} = CallByName Json.128 List.139 List.140; + ret List.523; + +procedure List.138 (List.139, List.140, List.137): + let List.596 : {List U8, U64} = CallByName Json.128 List.139 List.140; + ret List.596; + +procedure List.18 (List.135, List.136, List.137): + let List.504 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; + ret List.504; + +procedure List.18 (List.135, List.136, List.137): + let List.577 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; + ret List.577; + +procedure List.4 (List.106, List.107): + let List.576 : U64 = 1i64; + let List.575 : List U8 = CallByName List.70 List.106 List.576; + let List.574 : List U8 = CallByName List.71 List.575 List.107; + ret List.574; + +procedure List.6 (#Attr.2): + let List.524 : U64 = lowlevel ListLen #Attr.2; + ret List.524; + +procedure List.6 (#Attr.2): + let List.597 : U64 = lowlevel ListLen #Attr.2; + ret List.597; + +procedure List.66 (#Attr.2, #Attr.3): + let List.520 : [C {}, C {}] = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.520; + +procedure List.66 (#Attr.2, #Attr.3): + let List.593 : [] = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.593; + +procedure List.70 (#Attr.2, #Attr.3): + let List.555 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; + ret List.555; + +procedure List.71 (#Attr.2, #Attr.3): + let List.553 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.553; + +procedure List.8 (#Attr.2, #Attr.3): + let List.598 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; + ret List.598; + +procedure List.90 (List.426, List.427, List.428): + let List.508 : U64 = 0i64; + let List.509 : U64 = CallByName List.6 List.426; + let List.507 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.508 List.509; + ret List.507; + +procedure List.90 (List.426, List.427, List.428): + let List.581 : U64 = 0i64; + let List.582 : U64 = CallByName List.6 List.426; + let List.580 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.581 List.582; + ret List.580; + +procedure List.91 (List.535, List.536, List.537, List.538, List.539): + joinpoint List.510 List.429 List.430 List.431 List.432 List.433: + let List.512 : Int1 = CallByName Num.22 List.432 List.433; + if List.512 then + let List.519 : [C {}, C {}] = CallByName List.66 List.429 List.432; + let List.513 : {List U8, U64} = CallByName List.138 List.430 List.519 List.431; + let List.516 : U64 = 1i64; + let List.515 : U64 = CallByName Num.19 List.432 List.516; + jump List.510 List.429 List.513 List.431 List.515 List.433; + else + ret List.430; + in + jump List.510 List.535 List.536 List.537 List.538 List.539; + +procedure List.91 (List.608, List.609, List.610, List.611, List.612): + joinpoint List.583 List.429 List.430 List.431 List.432 List.433: + let List.585 : Int1 = CallByName Num.22 List.432 List.433; + if List.585 then + let List.592 : [] = CallByName List.66 List.429 List.432; + let List.586 : {List U8, U64} = CallByName List.138 List.430 List.592 List.431; + let List.589 : U64 = 1i64; + let List.588 : U64 = CallByName Num.19 List.432 List.589; + jump List.583 List.429 List.586 List.431 List.588 List.433; + else + ret List.430; + in + jump List.583 List.608 List.609 List.610 List.611 List.612; + +procedure Num.125 (#Attr.2): + let Num.284 : U8 = lowlevel NumIntCast #Attr.2; + ret Num.284; + +procedure Num.19 (#Attr.2, #Attr.3): + let Num.287 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.287; + +procedure Num.20 (#Attr.2, #Attr.3): + let Num.285 : U64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.285; + +procedure Num.22 (#Attr.2, #Attr.3): + let Num.288 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.288; + +procedure Num.24 (#Attr.2, #Attr.3): + let Num.286 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; + ret Num.286; + +procedure Str.12 (#Attr.2): + let Str.267 : List U8 = lowlevel StrToUtf8 #Attr.2; + ret Str.267; + +procedure Test.2 (Test.11): + let Test.18 : {{}, {}} = CallByName Encode.22 Test.11; + ret Test.18; + +procedure Test.3 (): + let Test.16 : {} = Struct {}; + let Test.17 : {} = Struct {}; + let Test.15 : {{}, {}} = Struct {Test.16, Test.17}; + ret Test.15; + +procedure Test.5 (Test.6, Test.7, Test.4): + joinpoint Test.23 Test.8: + let Test.21 : List U8 = CallByName Encode.23 Test.6 Test.8 Test.7; + ret Test.21; + in + let Test.28 : Int1 = CallByName Bool.2; + if Test.28 then + let Test.29 : Str = "A"; + let Test.32 : {} = StructAtIndex 0 Test.4; + let Test.31 : [C {}, C {}] = CallByName #Derived.0 Test.32; + let Test.30 : List [C {}, C {}] = Array [Test.31]; + let Test.22 : {Str, List [C {}, C {}]} = CallByName Json.21 Test.29 Test.30; + jump Test.23 Test.22; + else + let Test.24 : Str = "B"; + let Test.27 : {} = StructAtIndex 1 Test.4; + let Test.26 : [C {}, C {}] = CallByName #Derived.5 Test.27; + let Test.25 : List [C {}, C {}] = Array [Test.26]; + let Test.22 : {Str, List [C {}, C {}]} = CallByName Json.21 Test.24 Test.25; + jump Test.23 Test.22; + +procedure Test.0 (): + let Test.13 : {{}, {}} = CallByName Test.3; + let Test.14 : {} = CallByName Json.1; + let Test.12 : List U8 = CallByName Encode.25 Test.13 Test.14; + ret Test.12; From 091e043168de9a169247c9adf98b87b5a8b3a419 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Tue, 21 Feb 2023 15:38:11 +0100 Subject: [PATCH 39/48] removed rodio and deps --- .github/workflows/nightly_linux_x86_64.yml | 2 +- .github/workflows/nightly_macos_x86_64.yml | 2 +- Cargo.lock | 346 +-------------------- crates/editor/Cargo.toml | 2 - crates/editor/src/editor/mod.rs | 2 - crates/editor/src/editor/mvc/ed_update.rs | 8 - crates/editor/src/editor/sound.rs | 45 --- default.nix | 1 - examples/gui/breakout/platform/Cargo.toml | 2 - examples/gui/platform/Cargo.toml | 2 - flake.nix | 1 - 11 files changed, 8 insertions(+), 405 deletions(-) delete mode 100644 crates/editor/src/editor/sound.rs diff --git a/.github/workflows/nightly_linux_x86_64.yml b/.github/workflows/nightly_linux_x86_64.yml index 571517d7d6..9f127eb3ab 100644 --- a/.github/workflows/nightly_linux_x86_64.yml +++ b/.github/workflows/nightly_linux_x86_64.yml @@ -17,7 +17,7 @@ jobs: run: ./ci/write_version.sh - name: build release - run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release --locked + run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --release --locked # target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower. - name: get commit SHA diff --git a/.github/workflows/nightly_macos_x86_64.yml b/.github/workflows/nightly_macos_x86_64.yml index b9b6245b56..1af3be4317 100644 --- a/.github/workflows/nightly_macos_x86_64.yml +++ b/.github/workflows/nightly_macos_x86_64.yml @@ -22,7 +22,7 @@ jobs: # this issue may be caused by using older versions of XCode - name: build release - run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release --locked + run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --release --locked # target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower. - name: get commit SHA diff --git a/Cargo.lock b/Cargo.lock index ec06ea7ab1..ea3c42bfe4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,28 +68,6 @@ dependencies = [ "alloc-no-stdlib", ] -[[package]] -name = "alsa" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5915f52fe2cf65e83924d037b6c5290b7cee097c6b5c8700746e6168a343fd6b" -dependencies = [ - "alsa-sys", - "bitflags", - "libc", - "nix 0.23.1", -] - -[[package]] -name = "alsa-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" -dependencies = [ - "libc", - "pkg-config", -] - [[package]] name = "approx" version = "0.4.0" @@ -198,25 +176,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bindgen" -version = "0.59.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", -] - [[package]] name = "bit-set" version = "0.5.2" @@ -415,24 +374,6 @@ name = "cc" version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" -dependencies = [ - "jobserver", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] [[package]] name = "cfg-if" @@ -462,17 +403,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "clang-sys" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" version = "2.34.0" @@ -523,12 +453,6 @@ dependencies = [ "os_str_bytes", ] -[[package]] -name = "claxon" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688" - [[package]] name = "cli_utils" version = "0.0.1" @@ -620,16 +544,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "combine" -version = "4.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948" -dependencies = [ - "bytes", - "memchr", -] - [[package]] name = "confy" version = "0.5.0" @@ -792,50 +706,6 @@ dependencies = [ "objc", ] -[[package]] -name = "coreaudio-rs" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11894b20ebfe1ff903cbdc52259693389eea03b94918a2def2c30c3bf227ad88" -dependencies = [ - "bitflags", - "coreaudio-sys", -] - -[[package]] -name = "coreaudio-sys" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dff444d80630d7073077d38d40b4501fd518bd2b922c2a55edcc8b0f7be57e6" -dependencies = [ - "bindgen", -] - -[[package]] -name = "cpal" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74117836a5124f3629e4b474eed03e479abaf98988b4bb317e29f08cfe0e4116" -dependencies = [ - "alsa", - "core-foundation-sys 0.8.3", - "coreaudio-rs", - "jni", - "js-sys", - "lazy_static", - "libc", - "mach", - "ndk 0.6.0", - "ndk-glue 0.6.2", - "nix 0.23.1", - "oboe", - "parking_lot 0.11.2", - "stdweb", - "thiserror", - "web-sys", - "winapi", -] - [[package]] name = "cpufeatures" version = "0.2.2" @@ -1471,12 +1341,6 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" -[[package]] -name = "glob" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" - [[package]] name = "glow" version = "0.11.2" @@ -1632,12 +1496,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" -[[package]] -name = "hound" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" - [[package]] name = "http" version = "0.2.8" @@ -1876,35 +1734,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" -[[package]] -name = "jni" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" -dependencies = [ - "cesu8", - "combine", - "jni-sys", - "log", - "thiserror", - "walkdir", -] - [[package]] name = "jni-sys" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" -[[package]] -name = "jobserver" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.60" @@ -1946,23 +1781,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "lewton" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" -dependencies = [ - "byteorder", - "ogg", - "tinyvec", -] - [[package]] name = "libc" version = "0.2.135" @@ -2032,15 +1850,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "mach" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] - [[package]] name = "mach_object" version = "0.1.17" @@ -2155,26 +1964,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "minimp3" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985438f75febf74c392071a975a29641b420dd84431135a6e6db721de4b74372" -dependencies = [ - "minimp3-sys", - "slice-deque", - "thiserror", -] - -[[package]] -name = "minimp3-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e21c73734c69dc95696c9ed8926a2b393171d98b3f5f5935686a26a487ab9b90" -dependencies = [ - "cc", -] - [[package]] name = "miniz_oxide" version = "0.5.3" @@ -2232,20 +2021,7 @@ checksum = "96d868f654c72e75f8687572699cdabe755f03effbb62542768e995d5b8d699d" dependencies = [ "bitflags", "jni-sys", - "ndk-sys 0.2.2", - "num_enum", - "thiserror", -] - -[[package]] -name = "ndk" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" -dependencies = [ - "bitflags", - "jni-sys", - "ndk-sys 0.3.0", + "ndk-sys", "num_enum", "thiserror", ] @@ -2265,25 +2041,10 @@ dependencies = [ "lazy_static", "libc", "log", - "ndk 0.5.0", + "ndk", "ndk-context", "ndk-macro", - "ndk-sys 0.2.2", -] - -[[package]] -name = "ndk-glue" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0c4a7b83860226e6b4183edac21851f05d5a51756e97a1144b7f5a6b63e65f" -dependencies = [ - "lazy_static", - "libc", - "log", - "ndk 0.6.0", - "ndk-context", - "ndk-macro", - "ndk-sys 0.3.0", + "ndk-sys", ] [[package]] @@ -2305,15 +2066,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" -[[package]] -name = "ndk-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" -dependencies = [ - "jni-sys", -] - [[package]] name = "nibble_vec" version = "0.1.0" @@ -2386,17 +2138,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "num-derive" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "num-traits" version = "0.2.15" @@ -2498,38 +2239,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "oboe" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f63c358b4fa0fbcfefd7c8be5cfc39c08ce2389f5325687e7762a48d30a5c1" -dependencies = [ - "jni", - "ndk 0.6.0", - "ndk-context", - "num-derive", - "num-traits", - "oboe-sys", -] - -[[package]] -name = "oboe-sys" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3370abb7372ed744232c12954d920d1a40f1c4686de9e79e800021ef492294bd" -dependencies = [ - "cc", -] - -[[package]] -name = "ogg" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" -dependencies = [ - "byteorder", -] - [[package]] name = "once_cell" version = "1.17.0" @@ -2685,12 +2394,6 @@ dependencies = [ "windows-sys 0.36.1", ] -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "peg" version = "0.8.1" @@ -3570,7 +3273,6 @@ dependencies = [ "roc_types", "roc_unify", "roc_utils", - "rodio", "serde", "snafu", "tempfile", @@ -4187,19 +3889,6 @@ dependencies = [ "roc_error_macros", ] -[[package]] -name = "rodio" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0939e9f626e6c6f1989adb6226a039c855ca483053f0ee7c98b90e41cf731e" -dependencies = [ - "claxon", - "cpal", - "hound", - "lewton", - "minimp3", -] - [[package]] name = "rustc-demangle" version = "0.1.21" @@ -4501,12 +4190,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shlex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" - [[package]] name = "signal-hook" version = "0.3.14" @@ -4554,17 +4237,6 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" -[[package]] -name = "slice-deque" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31ef6ee280cdefba6d2d0b4b78a84a1c1a3f3a4cec98c2d4231c8bc225de0f25" -dependencies = [ - "libc", - "mach", - "winapi", -] - [[package]] name = "slotmap" version = "1.0.6" @@ -4682,12 +4354,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "stdweb" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e" - [[package]] name = "str-buf" version = "1.0.6" @@ -5792,9 +5458,9 @@ dependencies = [ "libc", "log", "mio", - "ndk 0.5.0", - "ndk-glue 0.5.2", - "ndk-sys 0.2.2", + "ndk", + "ndk-glue", + "ndk-sys", "objc", "parking_lot 0.11.2", "percent-encoding", diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 95162b1a44..6cff04153d 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -14,7 +14,6 @@ normal = ["confy"] [features] default = [] -with_sound = ["rodio"] [dependencies] roc_ast = { path = "../ast" } @@ -57,7 +56,6 @@ confy = { git = 'https://github.com/rust-cli/confy', features = [ ], default-features = false } serde = { version = "1.0.144", features = ["derive"] } nonempty = "0.8.0" -rodio = { version = "0.15.0", optional = true } # to play sounds threadpool = "1.8.1" fs_extra.workspace = true diff --git a/crates/editor/src/editor/mod.rs b/crates/editor/src/editor/mod.rs index 23d273400d..5012aa0b4d 100644 --- a/crates/editor/src/editor/mod.rs +++ b/crates/editor/src/editor/mod.rs @@ -8,7 +8,5 @@ mod mvc; mod render_ast; mod render_debug; mod resources; -#[cfg(feature = "with_sound")] -mod sound; mod theme; mod util; diff --git a/crates/editor/src/editor/mvc/ed_update.rs b/crates/editor/src/editor/mvc/ed_update.rs index 507fd98358..89816f142f 100644 --- a/crates/editor/src/editor/mvc/ed_update.rs +++ b/crates/editor/src/editor/mvc/ed_update.rs @@ -21,8 +21,6 @@ use crate::editor::mvc::string_update::start_new_string; use crate::editor::mvc::string_update::update_small_string; use crate::editor::mvc::string_update::update_string; use crate::editor::mvc::tld_value_update::{start_new_tld_value, update_tld_val_name}; -#[cfg(feature = "with_sound")] -use crate::editor::sound::play_sound; use crate::ui::text::caret_w_select::CaretWSelect; use crate::ui::text::lines::MoveCaretFun; use crate::ui::text::selection::validate_raw_sel; @@ -546,12 +544,6 @@ impl<'a> EdModel<'a> { self.show_debug_view = !self.show_debug_view; self.dirty = true; } - F12 => { - #[cfg(feature = "with_sound")] - _sound_thread_pool.execute(move || { - play_sound("./editor/src/editor/resources/sounds/bell_sound.mp3"); - }); - } _ => (), } diff --git a/crates/editor/src/editor/sound.rs b/crates/editor/src/editor/sound.rs deleted file mode 100644 index 76a9994de6..0000000000 --- a/crates/editor/src/editor/sound.rs +++ /dev/null @@ -1,45 +0,0 @@ -use rodio::{Decoder, OutputStream, Sink}; -use std::fs::File; -use std::io::BufReader; - -pub(crate) fn play_sound(sound_path_str: &str) { - let out_stream_res = OutputStream::try_default(); - - match out_stream_res { - Ok((_, out_stream_handle)) => match Sink::try_new(&out_stream_handle) { - Ok(sink) => match File::open(sound_path_str) { - Ok(file) => { - let reader = BufReader::new(file); - - match Decoder::new(reader) { - Ok(decoder) => { - sink.append(decoder); - sink.sleep_until_end(); - } - Err(e) => { - println!("Failed to create Decoder from BufReader from sound file at {}. Error message: {:?}", sound_path_str, e); - } - } - } - Err(e) => { - println!( - "Failed to open sound file at {}. Error message: {}", - sound_path_str, e - ); - } - }, - Err(e) => { - println!( - "Failed to create Sink to play sound. Error message: {:?}", - e - ); - } - }, - Err(e) => { - println!( - "Failed to create OutputStream to play sound. Error message: {:?}", - e - ); - } - } -} diff --git a/default.nix b/default.nix index 31c84f5bfe..0e4da8457d 100644 --- a/default.nix +++ b/default.nix @@ -65,7 +65,6 @@ rustPlatform.buildRustPackage { cargo makeWrapper # necessary for postBuild wrapProgram ] ++ lib.optionals pkgs.stdenv.isLinux [ - alsa-lib valgrind vulkan-headers vulkan-loader diff --git a/examples/gui/breakout/platform/Cargo.toml b/examples/gui/breakout/platform/Cargo.toml index 5b612df452..10fb6f4b7a 100644 --- a/examples/gui/breakout/platform/Cargo.toml +++ b/examples/gui/breakout/platform/Cargo.toml @@ -39,7 +39,6 @@ confy = { git = 'https://github.com/rust-cli/confy', features = [ serde = { version = "1.0.130", features = ["derive"] } nonempty = "0.7.0" fs_extra = "1.2.0" -rodio = { version = "0.14.0", optional = true } # to play sounds threadpool = "1.8.1" [package.metadata.cargo-udeps.ignore] @@ -50,7 +49,6 @@ normal = ["confy"] [features] default = [] -with_sound = ["rodio"] [dependencies.bytemuck] version = "1.7.2" diff --git a/examples/gui/platform/Cargo.toml b/examples/gui/platform/Cargo.toml index ec1ec5fad1..76357974f3 100644 --- a/examples/gui/platform/Cargo.toml +++ b/examples/gui/platform/Cargo.toml @@ -40,7 +40,6 @@ confy = { git = 'https://github.com/rust-cli/confy', features = [ serde = { version = "1.0.130", features = ["derive"] } nonempty = "0.7.0" fs_extra = "1.2.0" -rodio = { version = "0.14.0", optional = true } # to play sounds threadpool = "1.8.1" [package.metadata.cargo-udeps.ignore] @@ -51,7 +50,6 @@ normal = ["confy"] [features] default = [] -with_sound = ["rodio"] [dependencies.bytemuck] version = "1.7.2" diff --git a/flake.nix b/flake.nix index f7a59ceb5c..8f201c739e 100644 --- a/flake.nix +++ b/flake.nix @@ -44,7 +44,6 @@ xorg.libXrandr xorg.libXi xorg.libxcb - alsa-lib ]; darwinInputs = with pkgs; From 989736703881d1088bf81dd7e780598881b5ef1c Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 22 Feb 2023 13:13:40 +0100 Subject: [PATCH 40/48] fix merge conflict in the dev backend --- crates/compiler/gen_dev/src/generic64/mod.rs | 34 +------------------- crates/compiler/gen_dev/src/lib.rs | 32 ------------------ 2 files changed, 1 insertion(+), 65 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 7e7fded511..486d25bd53 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -1207,7 +1207,7 @@ impl< .load_to_general_reg(&mut self.buf, src2); ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } - Layout::Builtin(Builtin::Str) => { + Layout::STR => { self.build_fn_call( dst, bitcode::STR_EQUAL.to_string(), @@ -1228,38 +1228,6 @@ impl< } } - fn build_and(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>) { - match *arg_layout { - Layout::BOOL => { - let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); - let src1_reg = self - .storage_manager - .load_to_general_reg(&mut self.buf, src1); - let src2_reg = self - .storage_manager - .load_to_general_reg(&mut self.buf, src2); - ASM::and_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); - } - x => todo!("And: layout, {:?}", x), - } - } - - fn build_or(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>) { - match *arg_layout { - Layout::BOOL => { - let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); - let src1_reg = self - .storage_manager - .load_to_general_reg(&mut self.buf, src1); - let src2_reg = self - .storage_manager - .load_to_general_reg(&mut self.buf, src2); - ASM::or_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); - } - x => todo!("Or: layout, {:?}", x), - } - } - fn build_not(&mut self, dst: &Symbol, src: &Symbol, arg_layout: &InLayout<'a>) { match *arg_layout { Layout::BOOL => { diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index 0fd07e59df..00de23970d 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -661,32 +661,6 @@ trait Backend<'a> { ); self.build_neq(sym, &args[0], &args[1], &arg_layouts[0]) } - LowLevel::And => { - debug_assert_eq!(2, args.len(), "And: expected to have exactly two argument"); - debug_assert_eq!( - arg_layouts[0], arg_layouts[1], - "And: expected all arguments of to have the same layout" - ); - debug_assert_eq!( - Layout::BOOL, - *ret_layout, - "And: expected to have return layout of type Bool" - ); - self.build_and(sym, &args[0], &args[1], &arg_layouts[0]) - } - LowLevel::Or => { - debug_assert_eq!(2, args.len(), "Or: expected to have exactly two argument"); - debug_assert_eq!( - arg_layouts[0], arg_layouts[1], - "Or: expected all arguments of to have the same layout" - ); - debug_assert_eq!( - Layout::BOOL, - *ret_layout, - "Or: expected to have return layout of type Bool" - ); - self.build_or(sym, &args[0], &args[1], &arg_layouts[0]) - } LowLevel::Not => { debug_assert_eq!(1, args.len(), "Not: expected to have exactly one argument"); debug_assert_eq!( @@ -1239,12 +1213,6 @@ trait Backend<'a> { /// build_neq stores the result of `src1 != src2` into dst. fn build_neq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>); - /// build_and stores the result of `src1 && src2` into dst. - fn build_and(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>); - - /// build_or stores the result of `src1 || src2` into dst. - fn build_or(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>); - /// build_not stores the result of `!src` into dst. fn build_not(&mut self, dst: &Symbol, src: &Symbol, arg_layout: &InLayout<'a>); From 4d91fe01caa70f8aa8f6dbd786e305b15de81f7d Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Fri, 24 Feb 2023 14:35:05 +0100 Subject: [PATCH 41/48] update mono tests --- ...not_duplicate_identical_concrete_types.txt | 16 +++++----- ...types_without_unification_of_unifiable.txt | 32 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt index 47100569d8..18c4b302df 100644 --- a/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt +++ b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt @@ -120,7 +120,7 @@ procedure List.138 (List.139, List.140, List.137): ret List.529; procedure List.18 (List.135, List.136, List.137): - let List.510 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; + let List.510 : {List U8, U64} = CallByName List.91 List.135 List.136 List.137; ret List.510; procedure List.4 (List.106, List.107): @@ -149,13 +149,7 @@ procedure List.8 (#Attr.2, #Attr.3): let List.532 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; ret List.532; -procedure List.90 (List.426, List.427, List.428): - let List.514 : U64 = 0i64; - let List.515 : U64 = CallByName List.6 List.426; - let List.513 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.514 List.515; - ret List.513; - -procedure List.91 (List.542, List.543, List.544, List.545, List.546): +procedure List.80 (List.542, List.543, List.544, List.545, List.546): joinpoint List.516 List.429 List.430 List.431 List.432 List.433: let List.518 : Int1 = CallByName Num.22 List.432 List.433; if List.518 then @@ -169,6 +163,12 @@ procedure List.91 (List.542, List.543, List.544, List.545, List.546): in jump List.516 List.542 List.543 List.544 List.545 List.546; +procedure List.91 (List.426, List.427, List.428): + let List.514 : U64 = 0i64; + let List.515 : U64 = CallByName List.6 List.426; + let List.513 : {List U8, U64} = CallByName List.80 List.426 List.427 List.428 List.514 List.515; + ret List.513; + procedure Num.125 (#Attr.2): let Num.265 : U8 = lowlevel NumIntCast #Attr.2; ret Num.265; diff --git a/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable.txt b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable.txt index fd86d2a0da..c4fcf542bb 100644 --- a/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable.txt +++ b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable.txt @@ -231,11 +231,11 @@ procedure List.138 (List.139, List.140, List.137): ret List.596; procedure List.18 (List.135, List.136, List.137): - let List.504 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; + let List.504 : {List U8, U64} = CallByName List.91 List.135 List.136 List.137; ret List.504; procedure List.18 (List.135, List.136, List.137): - let List.577 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; + let List.577 : {List U8, U64} = CallByName List.91 List.135 List.136 List.137; ret List.577; procedure List.4 (List.106, List.107): @@ -272,19 +272,7 @@ procedure List.8 (#Attr.2, #Attr.3): let List.598 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; ret List.598; -procedure List.90 (List.426, List.427, List.428): - let List.508 : U64 = 0i64; - let List.509 : U64 = CallByName List.6 List.426; - let List.507 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.508 List.509; - ret List.507; - -procedure List.90 (List.426, List.427, List.428): - let List.581 : U64 = 0i64; - let List.582 : U64 = CallByName List.6 List.426; - let List.580 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.581 List.582; - ret List.580; - -procedure List.91 (List.535, List.536, List.537, List.538, List.539): +procedure List.80 (List.535, List.536, List.537, List.538, List.539): joinpoint List.510 List.429 List.430 List.431 List.432 List.433: let List.512 : Int1 = CallByName Num.22 List.432 List.433; if List.512 then @@ -298,7 +286,7 @@ procedure List.91 (List.535, List.536, List.537, List.538, List.539): in jump List.510 List.535 List.536 List.537 List.538 List.539; -procedure List.91 (List.608, List.609, List.610, List.611, List.612): +procedure List.80 (List.608, List.609, List.610, List.611, List.612): joinpoint List.583 List.429 List.430 List.431 List.432 List.433: let List.585 : Int1 = CallByName Num.22 List.432 List.433; if List.585 then @@ -312,6 +300,18 @@ procedure List.91 (List.608, List.609, List.610, List.611, List.612): in jump List.583 List.608 List.609 List.610 List.611 List.612; +procedure List.91 (List.426, List.427, List.428): + let List.508 : U64 = 0i64; + let List.509 : U64 = CallByName List.6 List.426; + let List.507 : {List U8, U64} = CallByName List.80 List.426 List.427 List.428 List.508 List.509; + ret List.507; + +procedure List.91 (List.426, List.427, List.428): + let List.581 : U64 = 0i64; + let List.582 : U64 = CallByName List.6 List.426; + let List.580 : {List U8, U64} = CallByName List.80 List.426 List.427 List.428 List.581 List.582; + ret List.580; + procedure Num.125 (#Attr.2): let Num.284 : U8 = lowlevel NumIntCast #Attr.2; ret Num.284; From f1fa0145244855ccfc2e54873d2d84fc01be50a0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 26 Feb 2023 15:11:49 +0100 Subject: [PATCH 42/48] support 128-bit literals --- crates/compiler/gen_dev/src/generic64/mod.rs | 20 ++++++++++++++ .../compiler/gen_dev/src/generic64/storage.rs | 8 +++--- crates/compiler/test_gen/src/gen_num.rs | 26 +++++++++---------- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 486d25bd53..746fa5d3fd 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -2147,6 +2147,26 @@ impl< let val = *x; ASM::mov_reg64_imm64(&mut self.buf, reg, i128::from_ne_bytes(val) as i64); } + (Literal::Int(x), Layout::Builtin(Builtin::Int(IntWidth::I128 | IntWidth::U128))) => { + self.storage_manager.with_tmp_general_reg( + &mut self.buf, + |storage_manager, buf, reg| { + let base_offset = storage_manager.claim_stack_area(sym, 16); + let bytes = *x; + + let mut num_bytes = [0; 8]; + num_bytes.copy_from_slice(&bytes[..8]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(buf, reg, num); + ASM::mov_base32_reg64(buf, base_offset, reg); + + num_bytes.copy_from_slice(&bytes[8..16]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(buf, reg, num); + ASM::mov_base32_reg64(buf, base_offset + 8, reg); + }, + ); + } (Literal::Byte(x), Layout::Builtin(Builtin::Int(IntWidth::U8 | IntWidth::I8))) => { let reg = self.storage_manager.claim_general_reg(&mut self.buf, sym); let val = *x; diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs index 87b0d61442..7e2eb7d4b4 100644 --- a/crates/compiler/gen_dev/src/generic64/storage.rs +++ b/crates/compiler/gen_dev/src/generic64/storage.rs @@ -753,7 +753,7 @@ impl< debug_assert_eq!(from_offset % 8, 0); debug_assert_eq!(size % 8, 0); debug_assert_eq!(size, layout_interner.stack_size(*layout)); - self.copy_symbol_to_stack_offset_help(buf, size, from_offset, to_offset) + self.copy_to_stack_offset(buf, size, from_offset, to_offset) } IntWidth::I64 | IntWidth::U64 => { debug_assert_eq!(to_offset % 8, 0); @@ -795,7 +795,7 @@ impl< debug_assert_eq!(from_offset % 8, 0); debug_assert_eq!(size % 8, 0); debug_assert_eq!(size, layout_interner.stack_size(*layout)); - self.copy_symbol_to_stack_offset_help(buf, size, from_offset, to_offset) + self.copy_to_stack_offset(buf, size, from_offset, to_offset) } }, Layout::Boxed(_) => { @@ -824,13 +824,13 @@ impl< debug_assert_eq!(from_offset % 8, 0); debug_assert_eq!(size % 8, 0); debug_assert_eq!(size, layout_interner.stack_size(*layout)); - self.copy_symbol_to_stack_offset_help(buf, size, from_offset, to_offset) + self.copy_to_stack_offset(buf, size, from_offset, to_offset) } x => todo!("copying data to the stack with layout, {:?}", x), } } - pub fn copy_symbol_to_stack_offset_help( + pub fn copy_to_stack_offset( &mut self, buf: &mut Vec<'a, u8>, size: u32, diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index afdd1b0118..8665a63f40 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -19,11 +19,11 @@ fn nat_alias() { assert_evals_to!( indoc!( r#" - i : Num.Nat - i = 1 + i : Num.Nat + i = 1 - i - "# + i + "# ), 1, usize @@ -31,16 +31,16 @@ fn nat_alias() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn i128_signed_int_alias() { assert_evals_to!( indoc!( r#" - i : I128 - i = 128 + i : I128 + i = 128 - i - "# + i + "# ), 128, i128 @@ -71,11 +71,11 @@ fn i32_signed_int_alias() { assert_evals_to!( indoc!( r#" - i : I32 - i = 32 + i : I32 + i = 32 - i - "# + i + "# ), 32, i32 From 6a40d7535316d4ad59f23b35ee1d3ff6f6b8a12d Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 26 Feb 2023 17:37:00 +0100 Subject: [PATCH 43/48] float comparisions --- .../compiler/gen_dev/src/generic64/aarch64.rs | 15 +++ crates/compiler/gen_dev/src/generic64/mod.rs | 109 +++++++++++++++++- .../compiler/gen_dev/src/generic64/x86_64.rs | 78 +++++++++++++ crates/compiler/test_gen/src/gen_num.rs | 34 +++--- 4 files changed, 219 insertions(+), 17 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index b5d5571f10..a11b254927 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -2,10 +2,13 @@ use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait}; use crate::Relocation; use bumpalo::collections::Vec; use packed_struct::prelude::*; +use roc_builtins::bitcode::FloatWidth; use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::layout::{InLayout, STLayoutInterner}; +use super::CompareOperation; + #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] #[allow(dead_code)] pub enum AArch64GeneralReg { @@ -888,6 +891,18 @@ impl Assembler for AArch64Assembler { todo!("registers unsigned less than for AArch64"); } + #[inline(always)] + fn cmp_freg_freg_reg64( + _buf: &mut Vec<'_, u8>, + _dst: AArch64GeneralReg, + _src1: AArch64FloatReg, + _src2: AArch64FloatReg, + _width: FloatWidth, + _operation: CompareOperation, + ) { + todo!("registers float comparison for AArch64"); + } + #[inline(always)] fn igt_reg64_reg64_reg64( _buf: &mut Vec<'_, u8>, diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 746fa5d3fd..c618731f1d 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -113,6 +113,13 @@ pub trait CallConv: Sized + Copy { src2: GeneralReg, ); + fn cmp_freg_freg_reg64( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + src1: FloatReg, + src2: FloatReg, + width: FloatWidth, + operation: CompareOperation, + ); + fn igt_reg64_reg64_reg64( buf: &mut Vec<'_, u8>, dst: GeneralReg, @@ -1273,6 +1289,20 @@ impl< .load_to_general_reg(&mut self.buf, src2); ASM::ult_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } + Layout::Builtin(Builtin::Float(width)) => { + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2); + + ASM::cmp_freg_freg_reg64( + &mut self.buf, + dst_reg, + src1_reg, + src2_reg, + width, + CompareOperation::LessThan, + ); + } x => todo!("NumLt: layout, {:?}", x), } } @@ -1305,6 +1335,20 @@ impl< .load_to_general_reg(&mut self.buf, src2); ASM::ugt_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } + Layout::Builtin(Builtin::Float(width)) => { + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2); + + ASM::cmp_freg_freg_reg64( + &mut self.buf, + dst_reg, + src1_reg, + src2_reg, + width, + CompareOperation::GreaterThan, + ); + } x => todo!("NumGt: layout, {:?}", x), } } @@ -1385,6 +1429,26 @@ impl< .load_to_general_reg(&mut self.buf, src2); ASM::lte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } + Layout::F64 | Layout::F32 => { + let width = if *arg_layout == Layout::F64 { + FloatWidth::F64 + } else { + FloatWidth::F32 + }; + + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2); + + ASM::cmp_freg_freg_reg64( + &mut self.buf, + dst_reg, + src1_reg, + src2_reg, + width, + CompareOperation::LessThanOrEqual, + ); + } x => todo!("NumLte: layout, {:?}", x), } } @@ -1407,6 +1471,26 @@ impl< .load_to_general_reg(&mut self.buf, src2); ASM::gte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } + Layout::F64 | Layout::F32 => { + let width = if *arg_layout == Layout::F64 { + FloatWidth::F64 + } else { + FloatWidth::F32 + }; + + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2); + + ASM::cmp_freg_freg_reg64( + &mut self.buf, + dst_reg, + src1_reg, + src2_reg, + width, + CompareOperation::GreaterThanOrEqual, + ); + } x => todo!("NumGte: layout, {:?}", x), } } @@ -2147,12 +2231,14 @@ impl< let val = *x; ASM::mov_reg64_imm64(&mut self.buf, reg, i128::from_ne_bytes(val) as i64); } - (Literal::Int(x), Layout::Builtin(Builtin::Int(IntWidth::I128 | IntWidth::U128))) => { + ( + Literal::Int(bytes), + Layout::Builtin(Builtin::Int(IntWidth::I128 | IntWidth::U128)), + ) => { self.storage_manager.with_tmp_general_reg( &mut self.buf, |storage_manager, buf, reg| { let base_offset = storage_manager.claim_stack_area(sym, 16); - let bytes = *x; let mut num_bytes = [0; 8]; num_bytes.copy_from_slice(&bytes[..8]); @@ -2187,6 +2273,25 @@ impl< let val = *x as f32; ASM::mov_freg32_imm32(&mut self.buf, &mut self.relocs, reg, val); } + (Literal::Decimal(bytes), Layout::Builtin(Builtin::Decimal)) => { + self.storage_manager.with_tmp_general_reg( + &mut self.buf, + |storage_manager, buf, reg| { + let base_offset = storage_manager.claim_stack_area(sym, 16); + + let mut num_bytes = [0; 8]; + num_bytes.copy_from_slice(&bytes[..8]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(buf, reg, num); + ASM::mov_base32_reg64(buf, base_offset, reg); + + num_bytes.copy_from_slice(&bytes[8..16]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(buf, reg, num); + ASM::mov_base32_reg64(buf, base_offset + 8, reg); + }, + ); + } (Literal::Str(x), Layout::Builtin(Builtin::Str)) => { if x.len() < 24 { // Load small string. diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index e4a16e2e90..edd3dcd547 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -4,10 +4,13 @@ use crate::{ single_register_layouts, Relocation, }; use bumpalo::collections::Vec; +use roc_builtins::bitcode::FloatWidth; use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::layout::{InLayout, Layout, LayoutInterner, STLayoutInterner}; +use super::CompareOperation; + // 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. #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] @@ -1592,6 +1595,33 @@ impl Assembler for X86_64Assembler { setb_reg64(buf, dst); } + #[inline(always)] + fn cmp_freg_freg_reg64( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + src1: X86_64FloatReg, + src2: X86_64FloatReg, + width: FloatWidth, + operation: CompareOperation, + ) { + use CompareOperation::*; + + let (arg1, arg2) = match operation { + LessThan | LessThanOrEqual => (src1, src2), + GreaterThan | GreaterThanOrEqual => (src2, src1), + }; + + match width { + FloatWidth::F32 => cmp_freg32_freg32(buf, arg2, arg1), + FloatWidth::F64 => cmp_freg64_freg64(buf, arg2, arg1), + } + + match operation { + LessThan | GreaterThan => seta_reg64(buf, dst), + LessThanOrEqual | GreaterThanOrEqual => setae_reg64(buf, dst), + }; + } + #[inline(always)] fn igt_reg64_reg64_reg64( buf: &mut Vec<'_, u8>, @@ -2085,6 +2115,48 @@ fn cmp_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64Gene binop_reg64_reg64(0x39, buf, dst, src); } +#[inline(always)] +fn cmp_freg64_freg64(buf: &mut Vec<'_, u8>, src1: X86_64FloatReg, src2: X86_64FloatReg) { + let src1_high = src1 as u8 > 7; + let src1_mod = src1 as u8 % 8; + + let src2_high = src2 as u8 > 7; + let src2_mod = src2 as u8 % 8; + + if src1_high || src2_high { + buf.extend([ + 0x66, + 0x40 | ((src1_high as u8) << 2) | (src2_high as u8), + 0x0F, + 0x2E, + 0xC0 | (src1_mod << 3) | (src2_mod), + ]) + } else { + buf.extend([0x66, 0x0F, 0x2E, 0xC0 | (src1_mod << 3) | (src2_mod)]) + } +} + +#[inline(always)] +fn cmp_freg32_freg32(buf: &mut Vec<'_, u8>, src1: X86_64FloatReg, src2: X86_64FloatReg) { + let src1_high = src1 as u8 > 7; + let src1_mod = src1 as u8 % 8; + + let src2_high = src2 as u8 > 7; + let src2_mod = src2 as u8 % 8; + + if src1_high || src2_high { + buf.extend([ + 0x65, + 0x40 | ((src1_high as u8) << 2) | (src2_high as u8), + 0x0F, + 0x2E, + 0xC0 | (src1_mod << 3) | (src2_mod), + ]) + } else { + buf.extend([0x65, 0x0F, 0x2E, 0xC0 | (src1_mod << 3) | (src2_mod)]) + } +} + /// `TEST r/m64,r64` -> AND r64 with r/m64; set SF, ZF, PF according to result. #[allow(dead_code)] #[inline(always)] @@ -2757,6 +2829,12 @@ fn seta_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) { set_reg64_help(0x97, buf, reg); } +/// `SETAE r/m64` -> Set byte if above or equal (CF=0). +#[inline(always)] +fn setae_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) { + set_reg64_help(0x93, buf, reg); +} + /// `SETLE r/m64` -> Set byte if less or equal (ZF=1 or SF≠ OF). #[inline(always)] fn setle_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) { diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index 8665a63f40..de9bfd9634 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -115,7 +115,7 @@ fn i8_signed_int_alias() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn i128_hex_int_alias() { assert_evals_to!( indoc!( @@ -196,7 +196,7 @@ fn i8_hex_int_alias() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn u128_signed_int_alias() { assert_evals_to!( indoc!( @@ -277,7 +277,7 @@ fn u8_signed_int_alias() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn u128_hex_int_alias() { assert_evals_to!( indoc!( @@ -418,7 +418,7 @@ fn character_literal_new_line() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn dec_float_alias() { assert_evals_to!( indoc!( @@ -451,7 +451,7 @@ fn f64_float_alias() { ); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn f32_float_alias() { assert_evals_to!( indoc!( @@ -890,16 +890,20 @@ fn gen_int_neq() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] -fn gen_int_less_than() { - assert_evals_to!( - indoc!( - r#" - 4 < 5 - "# - ), - true, - bool - ); +fn int_less_than() { + assert_evals_to!("4 < 5", true, bool); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn float_less_than() { + assert_evals_to!("4.0 < 5.0", true, bool); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn float_greater_than() { + assert_evals_to!("5.0 > 4.0", true, bool); } #[test] From 44f08f9e47abc44d55b158ef7e26570c9a24763f Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 26 Feb 2023 19:05:33 +0100 Subject: [PATCH 44/48] implement sqrt and log in the dev backend --- crates/compiler/builtins/roc/Num.roc | 1 + .../compiler/gen_dev/src/generic64/aarch64.rs | 8 ++ crates/compiler/gen_dev/src/generic64/mod.rs | 15 +++ .../compiler/gen_dev/src/generic64/x86_64.rs | 70 +++++++++++ crates/compiler/gen_dev/src/lib.rs | 27 +++++ crates/compiler/test_gen/src/gen_num.rs | 113 ++++-------------- crates/compiler/test_gen/src/gen_result.rs | 22 +++- 7 files changed, 168 insertions(+), 88 deletions(-) diff --git a/crates/compiler/builtins/roc/Num.roc b/crates/compiler/builtins/roc/Num.roc index eef9cf4741..6157934eca 100644 --- a/crates/compiler/builtins/roc/Num.roc +++ b/crates/compiler/builtins/roc/Num.roc @@ -748,6 +748,7 @@ sqrtChecked = \x -> else Ok (Num.sqrt x) +## Natural logarithm log : Frac a -> Frac a logChecked : Frac a -> Result (Frac a) [LogNeedsPositive] diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index a11b254927..67f312b624 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -1053,6 +1053,14 @@ impl Assembler for AArch64Assembler { { todo!("sar for AArch64") } + + fn sqrt_freg64_freg64(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _src: AArch64FloatReg) { + todo!("sqrt") + } + + fn sqrt_freg32_freg32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _src: AArch64FloatReg) { + todo!("sqrt") + } } impl AArch64Assembler {} diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index c618731f1d..81e8f6bc7a 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -317,6 +317,9 @@ pub trait Assembler: Sized + Copy { fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg); fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); + fn sqrt_freg64_freg64(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg); + fn sqrt_freg32_freg32(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg); + fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg); fn mul_freg32_freg32_freg32( buf: &mut Vec<'_, u8>, @@ -2571,6 +2574,18 @@ impl< } } } + + fn build_num_sqrt(&mut self, dst: Symbol, src: Symbol, float_width: FloatWidth) { + let buf = &mut self.buf; + + let dst_reg = self.storage_manager.claim_float_reg(buf, &dst); + let src_reg = self.storage_manager.load_to_float_reg(buf, &src); + + match float_width { + FloatWidth::F32 => ASM::sqrt_freg32_freg32(buf, dst_reg, src_reg), + FloatWidth::F64 => ASM::sqrt_freg64_freg64(buf, dst_reg, src_reg), + } + } } /// This impl block is for ir related instructions that need backend specific information. diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index edd3dcd547..0f3902e951 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -1745,6 +1745,14 @@ impl Assembler for X86_64Assembler { { shift_reg64_reg64_reg64(buf, storage_manager, sar_reg64_reg64, dst, src1, src2) } + + fn sqrt_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { + sqrtsd_freg64_freg64(buf, dst, src) + } + + fn sqrt_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { + sqrtss_freg32_freg32(buf, dst, src) + } } fn shift_reg64_reg64_reg64<'a, 'r, ASM, CC>( @@ -2157,6 +2165,48 @@ fn cmp_freg32_freg32(buf: &mut Vec<'_, u8>, src1: X86_64FloatReg, src2: X86_64Fl } } +#[inline(always)] +fn sqrtsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { + let dst_high = dst as u8 > 7; + let dst_mod = dst as u8 % 8; + + let src_high = src as u8 > 7; + let src_mod = src as u8 % 8; + + if dst_high || src_high { + buf.extend([ + 0xF2, + 0x40 | ((dst_high as u8) << 2) | (src_high as u8), + 0x0F, + 0x51, + 0xC0 | (dst_mod << 3) | (src_mod), + ]) + } else { + buf.extend([0xF2, 0x0F, 0x51, 0xC0 | (dst_mod << 3) | (src_mod)]) + } +} + +#[inline(always)] +fn sqrtss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { + let dst_high = dst as u8 > 7; + let dst_mod = dst as u8 % 8; + + let src_high = src as u8 > 7; + let src_mod = src as u8 % 8; + + if dst_high || src_high { + buf.extend([ + 0xF3, + 0x40 | ((dst_high as u8) << 2) | (src_high as u8), + 0x0F, + 0x51, + 0xC0 | (dst_mod << 3) | (src_mod), + ]) + } else { + buf.extend([0xF3, 0x0F, 0x51, 0xC0 | (dst_mod << 3) | (src_mod)]) + } +} + /// `TEST r/m64,r64` -> AND r64 with r/m64; set SF, ZF, PF according to result. #[allow(dead_code)] #[inline(always)] @@ -3601,4 +3651,24 @@ mod tests { fn test_push_reg64() { disassembler_test!(push_reg64, |reg| format!("push {}", reg), ALL_GENERAL_REGS); } + + #[test] + fn test_sqrt_freg64_freg64() { + disassembler_test!( + sqrtsd_freg64_freg64, + |dst, src| format!("sqrtsd {dst}, {src}"), + ALL_FLOAT_REGS, + ALL_FLOAT_REGS + ); + } + + #[test] + fn test_sqrt_freg32_freg32() { + disassembler_test!( + sqrtss_freg32_freg32, + |dst, src| format!("sqrtss {dst}, {src}"), + ALL_FLOAT_REGS, + ALL_FLOAT_REGS + ); + } } diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index 00de23970d..1aba223c90 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -751,6 +751,30 @@ trait Backend<'a> { ); self.build_num_gte(sym, &args[0], &args[1], &arg_layouts[0]) } + LowLevel::NumLogUnchecked => { + let float_width = match arg_layouts[0] { + Layout::F64 => FloatWidth::F64, + Layout::F32 => FloatWidth::F32, + _ => unreachable!("invalid layout for sqrt"), + }; + + self.build_fn_call( + sym, + bitcode::NUM_LOG[float_width].to_string(), + args, + arg_layouts, + ret_layout, + ) + } + LowLevel::NumSqrtUnchecked => { + let float_width = match arg_layouts[0] { + Layout::F64 => FloatWidth::F64, + Layout::F32 => FloatWidth::F32, + _ => unreachable!("invalid layout for sqrt"), + }; + + self.build_num_sqrt(*sym, args[0], float_width); + } LowLevel::NumRound => self.build_fn_call( sym, bitcode::NUM_ROUND_F64[IntWidth::I64].to_string(), @@ -1261,6 +1285,9 @@ trait Backend<'a> { arg_layout: &InLayout<'a>, ); + /// build_sqrt stores the result of `sqrt(src)` into dst. + fn build_num_sqrt(&mut self, dst: Symbol, src: Symbol, float_width: FloatWidth); + /// build_list_len returns the length of a list. fn build_list_len(&mut self, dst: &Symbol, list: &Symbol); diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index de9bfd9634..3c4f178ce1 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -468,112 +468,51 @@ fn f32_float_alias() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn f64_sqrt() { - assert_evals_to!( - indoc!( - r#" - when Num.sqrtChecked 100 is - Ok val -> val - Err _ -> -1 - "# - ), - 10.0, - f64 - ); +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn f64_sqrt_100() { + assert_evals_to!("Num.sqrt 100", 10.0, f64); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn f64_sqrt_checked_0() { + assert_evals_to!("Num.sqrt 0", 0.0, f64); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn f64_log() { - assert_evals_to!( - indoc!( - r#" - Num.log 7.38905609893 - "# - ), - 1.999999999999912, - f64 - ); -} - -#[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn f64_log_checked_one() { - assert_evals_to!( - indoc!( - r#" - when Num.logChecked 1 is - Ok val -> val - Err _ -> -1 - "# - ), - 0.0, - f64 - ); -} - -#[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn f64_sqrt_zero() { - assert_evals_to!( - indoc!( - r#" - when Num.sqrtChecked 0 is - Ok val -> val - Err _ -> -1 - "# - ), - 0.0, - f64 - ); +fn f64_sqrt_checked_positive() { + assert_evals_to!("Num.sqrtChecked 100", RocResult::ok(10.0), RocResult); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn f64_sqrt_checked_negative() { - assert_evals_to!( - indoc!( - r#" - when Num.sqrtChecked -1 is - Err _ -> 42 - Ok val -> val - "# - ), - 42.0, - f64 - ); + assert_evals_to!("Num.sqrtChecked -1f64", RocResult::err(()), RocResult); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn f64_log() { + assert_evals_to!("Num.log 7.38905609893", 1.999999999999912, f64); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn f64_log_checked_one() { + assert_evals_to!("Num.logChecked 1", RocResult::ok(1.0), RocResult); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn f64_log_checked_zero() { - assert_evals_to!( - indoc!( - r#" - when Num.logChecked 0 is - Err _ -> 42 - Ok val -> val - "# - ), - 42.0, - f64 - ); + assert_evals_to!("Num.logChecked 0", RocResult::err(()), RocResult); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn f64_log_negative() { - assert_evals_to!( - indoc!( - r#" - Num.log -1 - "# - ), - true, - f64, - |f: f64| f.is_nan() - ); + assert_evals_to!("Num.log -1", true, f64, |f: f64| f.is_nan()); } #[test] diff --git a/crates/compiler/test_gen/src/gen_result.rs b/crates/compiler/test_gen/src/gen_result.rs index 53cc8aaf76..9729fbe9ce 100644 --- a/crates/compiler/test_gen/src/gen_result.rs +++ b/crates/compiler/test_gen/src/gen_result.rs @@ -227,7 +227,7 @@ fn is_err() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] -fn roc_result_ok() { +fn roc_result_ok_i64() { assert_evals_to!( indoc!( r#" @@ -242,6 +242,26 @@ fn roc_result_ok() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn roc_result_ok_f64() { + // NOTE: the dev backend does not currently use float registers when returning a more + // complex type, but the rust side does expect it to. Hence this test fails with gen-dev + + assert_evals_to!( + indoc!( + r#" + result : Result F64 {} + result = Ok 42.0 + + result + "# + ), + RocResult::ok(42.0), + RocResult + ); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn roc_result_err() { From 1f296a590745fd652e984a31792233f3ece2ccc9 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 26 Feb 2023 22:32:19 +0100 Subject: [PATCH 45/48] fix test I broke --- crates/compiler/test_gen/src/gen_num.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index 3c4f178ce1..b0f7fe2b63 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -500,7 +500,7 @@ fn f64_log() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn f64_log_checked_one() { - assert_evals_to!("Num.logChecked 1", RocResult::ok(1.0), RocResult); + assert_evals_to!("Num.logChecked 1", RocResult::ok(0.0), RocResult); } #[test] From 4157c0e72d0c65824b7ce869bed5f77209cb0be0 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 27 Feb 2023 12:25:18 +0100 Subject: [PATCH 46/48] patch instruction reference link Signed-off-by: Anton-4 <17049058+Anton-4@users.noreply.github.com> --- crates/compiler/gen_dev/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/compiler/gen_dev/README.md b/crates/compiler/gen_dev/README.md index 15cffb0bc4..01b4bdd78f 100644 --- a/crates/compiler/gen_dev/README.md +++ b/crates/compiler/gen_dev/README.md @@ -105,7 +105,7 @@ This is the general procedure I follow with some helpful links: Also, sometimes it doesn't seem to generate things quite as you expect. - [Alternative Online Assembler](http://shell-storm.org/online/Online-Assembler-and-Disassembler/) - Like previous but with more architecture options. -- [x86 and amd64 instruction reference](https://www.felixcloutier.com/x86/) - +- [x86 and amd64 instruction reference](https://web.archive.org/web/20230221053750/https://www.felixcloutier.com/x86/) - Great for looking up x86_64 instructions and there bytes. Definitely missing information if you aren't used to reading it. - [Intel 64 ISA Reference](https://software.intel.com/content/dam/develop/public/us/en/documents/325383-sdm-vol-2abcd.pdf) - From 20ba7494faca105e7e26096b0043a812565345d5 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 27 Feb 2023 17:21:40 +0100 Subject: [PATCH 47/48] removed unused files --- examples/parser/parse-movies-csv.roc | 58 ------------- examples/parser/platform/host.zig | 125 --------------------------- examples/parser/platform/main.roc | 9 -- 3 files changed, 192 deletions(-) delete mode 100644 examples/parser/parse-movies-csv.roc delete mode 100755 examples/parser/platform/host.zig delete mode 100644 examples/parser/platform/main.roc diff --git a/examples/parser/parse-movies-csv.roc b/examples/parser/parse-movies-csv.roc deleted file mode 100644 index 46dd00b964..0000000000 --- a/examples/parser/parse-movies-csv.roc +++ /dev/null @@ -1,58 +0,0 @@ -app "parse-movies-csv" - packages { pf: "platform/main.roc" } - imports [Parser.Core.{ Parser, map, keep }, Parser.Str.{ RawStr }, Parser.CSV.{ CSV, record, field, string, nat }] - provides [main] to pf - -input : Str -input = "Airplane!,1980,\"Robert Hays,Julie Hagerty\"\r\nCaddyshack,1980,\"Chevy Chase,Rodney Dangerfield,Ted Knight,Michael O'Keefe,Bill Murray\"" - -main : Str -main = - when Parser.CSV.parseStr movieInfoParser input is - Ok movies -> - moviesString = - movies - |> List.map movieInfoExplanation - |> Str.joinWith ("\n") - nMovies = List.len movies |> Num.toStr - - "\(nMovies) movies were found:\n\n\(moviesString)\n\nParse success!\n" - - Err problem -> - when problem is - ParsingFailure failure -> - "Parsing failure: \(failure)\n" - - ParsingIncomplete leftover -> - leftoverStr = leftover |> List.map Parser.Str.strFromRaw |> List.map (\val -> "\"\(val)\"") |> Str.joinWith ", " - - "Parsing incomplete. Following leftover fields while parsing a record: \(leftoverStr)\n" - - SyntaxError error -> - "Parsing failure. Syntax error in the CSV: \(error)" - -MovieInfo := { title : Str, releaseYear : Nat, actors : List Str } - -movieInfoParser = - record (\title -> \releaseYear -> \actors -> @MovieInfo { title, releaseYear, actors }) - |> keep (field string) - |> keep (field nat) - |> keep (field actorsParser) - -actorsParser = - string - |> map (\val -> Str.split val ",") - -movieInfoExplanation = \@MovieInfo { title, releaseYear, actors } -> - enumeratedActors = enumerate actors - releaseYearStr = Num.toStr releaseYear - - "The movie '\(title)' was released in \(releaseYearStr) and stars \(enumeratedActors)" - -enumerate : List Str -> Str -enumerate = \elements -> - { before: inits, others: last } = List.split elements (List.len elements - 1) - - last - |> List.prepend (inits |> Str.joinWith ", ") - |> Str.joinWith " and " diff --git a/examples/parser/platform/host.zig b/examples/parser/platform/host.zig deleted file mode 100755 index b84d428034..0000000000 --- a/examples/parser/platform/host.zig +++ /dev/null @@ -1,125 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const str = @import("str"); -const RocStr = str.RocStr; -const testing = std.testing; -const expectEqual = testing.expectEqual; -const expect = testing.expect; - -extern fn roc__mainForHost_1_exposed_generic(*RocStr) void; - -pub fn main() u8 { - const stdout = std.io.getStdOut().writer(); - - // actually call roc to populate the callresult - var callresult = RocStr.empty(); - roc__mainForHost_1_exposed_generic(&callresult); - - // stdout the result - stdout.print("{s}", .{callresult.asSlice()}) catch unreachable; - - callresult.deinit(); - - return 0; -} - -comptime { - // This is a workaround for https://github.com/ziglang/zig/issues/8218 - // which is only necessary on macOS. - // - // Once that issue is fixed, we can undo the changes in - // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing - // -fcompiler-rt in link.rs instead of doing this. Note that this - // workaround is present in many host.zig files, so make sure to undo - // it everywhere! - if (builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - -const Align = 2 * @alignOf(usize); -extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque; -extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque; -extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void; -extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; -extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; - -const DEBUG: bool = false; - -export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque { - if (DEBUG) { - var ptr = malloc(size); - const stdout = std.io.getStdOut().writer(); - stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable; - return ptr; - } else { - return malloc(size); - } -} - -export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque { - if (DEBUG) { - const stdout = std.io.getStdOut().writer(); - stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable; - } - - return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size); -} - -export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void { - if (DEBUG) { - const stdout = std.io.getStdOut().writer(); - stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable; - } - - free(@alignCast(Align, @ptrCast([*]u8, c_ptr))); -} - -export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void { - _ = tag_id; - - const stderr = std.io.getStdErr().writer(); - const msg = @ptrCast([*:0]const u8, c_ptr); - stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; - std.process.exit(0); -} - -export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void { - return memcpy(dst, src, size); -} - -export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void { - return memset(dst, value, size); -} - -extern fn kill(pid: c_int, sig: c_int) c_int; -extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int; -extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque; -extern fn getppid() c_int; - -fn roc_getppid() callconv(.C) c_int { - return getppid(); -} - -fn roc_getppid_windows_stub() callconv(.C) c_int { - return 0; -} - -fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int { - return shm_open(name, oflag, mode); -} -fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque { - return mmap(addr, length, prot, flags, fd, offset); -} - -comptime { - if (builtin.os.tag == .macos or builtin.os.tag == .linux) { - @export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong }); - @export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong }); - @export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong }); - } - - if (builtin.os.tag == .windows) { - @export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .Strong }); - } -} diff --git a/examples/parser/platform/main.roc b/examples/parser/platform/main.roc deleted file mode 100644 index 175f7070d5..0000000000 --- a/examples/parser/platform/main.roc +++ /dev/null @@ -1,9 +0,0 @@ -platform "hello-world" - requires {} { main : Str } - exposes [] - packages {} - imports [] - provides [mainForHost] - -mainForHost : Str -mainForHost = main From 1590b30b19344697ef04284a724e5fe7111eb21b Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Tue, 28 Feb 2023 17:03:49 +1100 Subject: [PATCH 48/48] Add a lexing-based 'highlight' mode to the parser basic syntax highlighting added more syntax highlighting coverage add example of a markdown table with styling move FIXED_TOKEN logic into highlight refactor highlight, add support for backpassing escape html from source code fix bug with
 tag ordering

refactor out html from roc_parse

remove test, put highlight functionality into separate file

fix typo
---
 crates/compiler/parse/src/blankspace.rs       | 216 ++++---
 crates/compiler/parse/src/highlight.rs        | 565 ++++++++++++++++++
 crates/compiler/parse/src/lib.rs              |   1 +
 crates/compiler/region/src/all.rs             |   8 +
 .../test_syntax/tests/test_snapshots.rs       |   9 +
 examples/static-site-gen/README.md            |   2 +-
 examples/static-site-gen/input/apple.md       | 108 +++-
 examples/static-site-gen/output/style.css     |  99 ++-
 examples/static-site-gen/platform/Cargo.lock  | 374 ++++++++++++
 examples/static-site-gen/platform/Cargo.toml  |   4 +
 .../static-site-gen/platform/src/highlight.rs |  77 +++
 examples/static-site-gen/platform/src/lib.rs  |  64 +-
 12 files changed, 1413 insertions(+), 114 deletions(-)
 create mode 100644 crates/compiler/parse/src/highlight.rs
 create mode 100644 examples/static-site-gen/platform/src/highlight.rs

diff --git a/crates/compiler/parse/src/blankspace.rs b/crates/compiler/parse/src/blankspace.rs
index 3f9cc06d3d..b0dbb7bb35 100644
--- a/crates/compiler/parse/src/blankspace.rs
+++ b/crates/compiler/parse/src/blankspace.rs
@@ -1,5 +1,6 @@
 use crate::ast::CommentOrNewline;
 use crate::ast::Spaceable;
+use crate::parser::Progress;
 use crate::parser::SpaceProblem;
 use crate::parser::{self, and, backtrackable, BadInputError, Parser, Progress::*};
 use crate::state::State;
@@ -7,6 +8,7 @@ use bumpalo::collections::vec::Vec;
 use bumpalo::Bump;
 use roc_region::all::Loc;
 use roc_region::all::Position;
+use roc_region::all::Region;
 
 pub fn space0_around_ee<'a, P, S, E>(
     parser: P,
@@ -386,98 +388,132 @@ pub fn spaces<'a, E>() -> impl Parser<'a, &'a [CommentOrNewline<'a>], E>
 where
     E: 'a + SpaceProblem,
 {
-    move |arena, mut state: State<'a>, _min_indent: u32| {
+    move |arena, state: State<'a>, _min_indent: u32| {
         let mut newlines = Vec::new_in(arena);
-        let mut progress = NoProgress;
-        loop {
-            let whitespace = fast_eat_whitespace(state.bytes());
-            if whitespace > 0 {
-                state.advance_mut(whitespace);
-                progress = MadeProgress;
-            }
 
-            match state.bytes().first() {
-                Some(b'#') => {
-                    state.advance_mut(1);
-
-                    let is_doc_comment = state.bytes().first() == Some(&b'#')
-                        && (state.bytes().get(1) == Some(&b' ')
-                            || state.bytes().get(1) == Some(&b'\n')
-                            || begins_with_crlf(&state.bytes()[1..])
-                            || Option::is_none(&state.bytes().get(1)));
-
-                    if is_doc_comment {
-                        state.advance_mut(1);
-                        if state.bytes().first() == Some(&b' ') {
-                            state.advance_mut(1);
-                        }
-                    }
-
-                    let len = fast_eat_until_control_character(state.bytes());
-
-                    // We already checked that the string is valid UTF-8
-                    debug_assert!(std::str::from_utf8(&state.bytes()[..len]).is_ok());
-                    let text = unsafe { std::str::from_utf8_unchecked(&state.bytes()[..len]) };
-
-                    let comment = if is_doc_comment {
-                        CommentOrNewline::DocComment(text)
-                    } else {
-                        CommentOrNewline::LineComment(text)
-                    };
-                    newlines.push(comment);
-                    state.advance_mut(len);
-
-                    if begins_with_crlf(state.bytes()) {
-                        state.advance_mut(1);
-                        state = state.advance_newline();
-                    } else if state.bytes().first() == Some(&b'\n') {
-                        state = state.advance_newline();
-                    }
-
-                    progress = MadeProgress;
-                }
-                Some(b'\r') => {
-                    if state.bytes().get(1) == Some(&b'\n') {
-                        newlines.push(CommentOrNewline::Newline);
-                        state.advance_mut(1);
-                        state = state.advance_newline();
-                        progress = MadeProgress;
-                    } else {
-                        return Err((
-                            progress,
-                            E::space_problem(
-                                BadInputError::HasMisplacedCarriageReturn,
-                                state.pos(),
-                            ),
-                        ));
-                    }
-                }
-                Some(b'\n') => {
-                    newlines.push(CommentOrNewline::Newline);
-                    state = state.advance_newline();
-                    progress = MadeProgress;
-                }
-                Some(b'\t') => {
-                    return Err((
-                        progress,
-                        E::space_problem(BadInputError::HasTab, state.pos()),
-                    ));
-                }
-                Some(x) if *x < b' ' => {
-                    return Err((
-                        progress,
-                        E::space_problem(BadInputError::HasAsciiControl, state.pos()),
-                    ));
-                }
-                _ => {
-                    if !newlines.is_empty() {
-                        state = state.mark_current_indent();
-                    }
-                    break;
-                }
-            }
+        match consume_spaces(state, |_, space, _| newlines.push(space)) {
+            Ok((progress, state)) => Ok((progress, newlines.into_bump_slice(), state)),
+            Err((progress, err)) => Err((progress, err)),
         }
-
-        Ok((progress, newlines.into_bump_slice(), state))
     }
 }
+
+pub fn loc_spaces<'a, E>() -> impl Parser<'a, &'a [Loc>], E>
+where
+    E: 'a + SpaceProblem,
+{
+    move |arena, state: State<'a>, _min_indent: u32| {
+        let mut newlines = Vec::new_in(arena);
+
+        match consume_spaces(state, |start, space, end| {
+            newlines.push(Loc::at(Region::between(start, end), space))
+        }) {
+            Ok((progress, state)) => Ok((progress, newlines.into_bump_slice(), state)),
+            Err((progress, err)) => Err((progress, err)),
+        }
+    }
+}
+
+fn consume_spaces<'a, E, F>(
+    mut state: State<'a>,
+    mut on_space: F,
+) -> Result<(Progress, State<'a>), (Progress, E)>
+where
+    E: 'a + SpaceProblem,
+    F: FnMut(Position, CommentOrNewline<'a>, Position),
+{
+    let mut progress = NoProgress;
+    let mut found_newline = false;
+    loop {
+        let whitespace = fast_eat_whitespace(state.bytes());
+        if whitespace > 0 {
+            state.advance_mut(whitespace);
+            progress = MadeProgress;
+        }
+
+        let start = state.pos();
+
+        match state.bytes().first() {
+            Some(b'#') => {
+                state.advance_mut(1);
+
+                let is_doc_comment = state.bytes().first() == Some(&b'#')
+                    && (state.bytes().get(1) == Some(&b' ')
+                        || state.bytes().get(1) == Some(&b'\n')
+                        || begins_with_crlf(&state.bytes()[1..])
+                        || Option::is_none(&state.bytes().get(1)));
+
+                if is_doc_comment {
+                    state.advance_mut(1);
+                    if state.bytes().first() == Some(&b' ') {
+                        state.advance_mut(1);
+                    }
+                }
+
+                let len = fast_eat_until_control_character(state.bytes());
+
+                // We already checked that the string is valid UTF-8
+                debug_assert!(std::str::from_utf8(&state.bytes()[..len]).is_ok());
+                let text = unsafe { std::str::from_utf8_unchecked(&state.bytes()[..len]) };
+
+                let comment = if is_doc_comment {
+                    CommentOrNewline::DocComment(text)
+                } else {
+                    CommentOrNewline::LineComment(text)
+                };
+                state.advance_mut(len);
+                on_space(start, comment, state.pos());
+                found_newline = true;
+
+                if begins_with_crlf(state.bytes()) {
+                    state.advance_mut(1);
+                    state = state.advance_newline();
+                } else if state.bytes().first() == Some(&b'\n') {
+                    state = state.advance_newline();
+                }
+
+                progress = MadeProgress;
+            }
+            Some(b'\r') => {
+                if state.bytes().get(1) == Some(&b'\n') {
+                    state.advance_mut(1);
+                    state = state.advance_newline();
+                    on_space(start, CommentOrNewline::Newline, state.pos());
+                    found_newline = true;
+                    progress = MadeProgress;
+                } else {
+                    return Err((
+                        progress,
+                        E::space_problem(BadInputError::HasMisplacedCarriageReturn, state.pos()),
+                    ));
+                }
+            }
+            Some(b'\n') => {
+                state = state.advance_newline();
+                on_space(start, CommentOrNewline::Newline, state.pos());
+                found_newline = true;
+                progress = MadeProgress;
+            }
+            Some(b'\t') => {
+                return Err((
+                    progress,
+                    E::space_problem(BadInputError::HasTab, state.pos()),
+                ));
+            }
+            Some(x) if *x < b' ' => {
+                return Err((
+                    progress,
+                    E::space_problem(BadInputError::HasAsciiControl, state.pos()),
+                ));
+            }
+            _ => {
+                if found_newline {
+                    state = state.mark_current_indent();
+                }
+                break;
+            }
+        }
+    }
+
+    Ok((progress, state))
+}
diff --git a/crates/compiler/parse/src/highlight.rs b/crates/compiler/parse/src/highlight.rs
new file mode 100644
index 0000000000..53bd5c04c7
--- /dev/null
+++ b/crates/compiler/parse/src/highlight.rs
@@ -0,0 +1,565 @@
+use encode_unicode::CharExt;
+use std::collections::HashSet;
+
+use bumpalo::Bump;
+use roc_region::all::{Loc, Region};
+
+use crate::{
+    ast::CommentOrNewline,
+    blankspace::loc_spaces,
+    keyword::KEYWORDS,
+    number_literal::positive_number_literal,
+    parser::{EExpr, ParseResult, Parser},
+    state::State,
+    string_literal::{parse_str_like_literal, StrLikeLiteral},
+};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum Token {
+    LineComment,
+    DocComment,
+    Error,
+    SingleQuote,
+    String,
+    UnicodeEscape,
+    EscapedChar,
+    Interpolated,
+    Keyword,
+    UpperIdent,
+    LowerIdent,
+    Number,
+    Other,
+    Minus,
+    Plus,
+    Colon,
+    Bar,
+    Equals,
+    GreaterThan,
+    LessThan,
+    Comma,
+    Backslash,
+    Brace,
+    Bracket,
+    Paren,
+    Arrow,
+    Pipe,
+    Backpass,
+}
+
+pub fn highlight(text: &str) -> Vec> {
+    let mut tokens = Vec::new();
+    let state = State::new(text.as_bytes());
+
+    let arena = Bump::new();
+
+    let header_keywords = HEADER_KEYWORDS.iter().copied().collect::>();
+    let body_keywords = KEYWORDS.iter().copied().collect::>();
+
+    if let Ok((_prog, _, new_state)) = crate::module::header().parse(&arena, state.clone(), 0) {
+        let inner_state =
+            State::new(text[..state.bytes().len() - new_state.bytes().len()].as_bytes());
+        highlight_inner(&arena, inner_state, &mut tokens, &header_keywords);
+        highlight_inner(&arena, new_state, &mut tokens, &body_keywords);
+    } else {
+        highlight_inner(&arena, state, &mut tokens, &body_keywords);
+    }
+
+    tokens = combine_tokens(tokens);
+
+    tokens
+}
+
+fn combine_tokens(locations: Vec>) -> Vec> {
+    let mut tokens: Vec> = Vec::new();
+    let mut previous_location: Option> = None;
+
+    for location in locations {
+        match location.value {
+            // Catch tokens which may combine for a different meaning
+            Token::LessThan => match previous_location {
+                Some(prev) => {
+                    tokens.push(prev);
+                    tokens.push(location);
+                    previous_location = None;
+                }
+                None => {
+                    previous_location = Some(location);
+                }
+            },
+            Token::Bar => match previous_location {
+                Some(prev) => {
+                    tokens.push(prev);
+                    tokens.push(location);
+                    previous_location = None;
+                }
+                None => {
+                    previous_location = Some(location);
+                }
+            },
+            // Combination tokens
+            Token::GreaterThan => {
+                match previous_location {
+                    Some(prev) => {
+                        match prev.value {
+                            Token::Minus => {
+                                // arrow operator "->"
+                                tokens.push(Loc::at(
+                                    Region::between(prev.region.start(), location.region.end()),
+                                    Token::Arrow,
+                                ));
+                                previous_location = None;
+                            }
+                            Token::Bar => {
+                                // pipe operator "|>"
+                                tokens.push(Loc::at(
+                                    Region::between(prev.region.start(), location.region.end()),
+                                    Token::Pipe,
+                                ));
+                                previous_location = None;
+                            }
+                            _ => {
+                                tokens.push(prev);
+                                tokens.push(location);
+                                previous_location = None;
+                            }
+                        }
+                    }
+                    _ => {
+                        tokens.push(location);
+                    }
+                }
+            }
+            Token::Minus => {
+                match previous_location {
+                    Some(prev) => {
+                        match prev.value {
+                            Token::LessThan => {
+                                // backpass operator "<-"
+                                tokens.push(Loc::at(
+                                    Region::between(prev.region.start(), location.region.end()),
+                                    Token::Backpass,
+                                ));
+                                previous_location = None;
+                            }
+                            _ => {
+                                tokens.push(prev);
+                                tokens.push(location);
+                                previous_location = None;
+                            }
+                        }
+                    }
+                    None => {
+                        previous_location = Some(location);
+                    }
+                }
+            }
+            _ => {
+                tokens.push(location);
+            }
+        }
+    }
+
+    tokens
+}
+
+fn highlight_inner<'a>(
+    arena: &'a Bump,
+    mut state: State<'a>,
+    tokens: &mut Vec>,
+    keywords: &HashSet<&str>,
+) {
+    loop {
+        let start = state.pos();
+        if let Ok((b, _width)) = char::from_utf8_slice_start(state.bytes()) {
+            match b {
+                ' ' | '\n' | '\t' | '\r' | '#' => {
+                    let res: ParseResult<'a, _, EExpr<'a>> =
+                        loc_spaces().parse(arena, state.clone(), 0);
+                    if let Ok((_, spaces, new_state)) = res {
+                        state = new_state;
+                        for space in spaces {
+                            let token = match space.value {
+                                CommentOrNewline::Newline => {
+                                    continue;
+                                }
+                                CommentOrNewline::LineComment(_) => Token::LineComment,
+                                CommentOrNewline::DocComment(_) => Token::DocComment,
+                            };
+                            tokens.push(Loc::at(space.region, token));
+                        }
+                    } else {
+                        fast_forward_to(&mut state, tokens, start, |c| c == b'\n');
+                    }
+                }
+                '"' | '\'' => {
+                    if let Ok((_, item, new_state)) =
+                        parse_str_like_literal().parse(arena, state.clone(), 0)
+                    {
+                        state = new_state;
+                        match item {
+                            StrLikeLiteral::SingleQuote(_) => {
+                                tokens.push(Loc::at(
+                                    Region::between(start, state.pos()),
+                                    Token::SingleQuote,
+                                ));
+                            }
+                            StrLikeLiteral::Str(_) => {
+                                tokens.push(Loc::at(
+                                    Region::between(start, state.pos()),
+                                    Token::String,
+                                ));
+                            }
+                        }
+                    } else {
+                        fast_forward_to(&mut state, tokens, start, |c| c == b'\n');
+                    }
+                }
+                c if c.is_alphabetic() => {
+                    let buffer = state.bytes();
+                    let mut chomped = 0;
+
+                    let is_upper = c.is_uppercase();
+
+                    while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
+                        if ch.is_alphabetic() || ch.is_ascii_digit() {
+                            chomped += width;
+                        } else {
+                            // we're done
+                            break;
+                        }
+                    }
+
+                    let ident = std::str::from_utf8(&buffer[..chomped]).unwrap();
+                    state.advance_mut(chomped);
+
+                    if keywords.contains(ident) {
+                        tokens.push(Loc::at(Region::between(start, state.pos()), Token::Keyword));
+                    } else {
+                        tokens.push(Loc::at(
+                            Region::between(start, state.pos()),
+                            if is_upper {
+                                Token::UpperIdent
+                            } else {
+                                Token::LowerIdent
+                            },
+                        ));
+                    }
+                }
+                '0'..='9' => {
+                    if let Ok((_, _item, new_state)) =
+                        positive_number_literal().parse(arena, state.clone(), 0)
+                    {
+                        state = new_state;
+                        tokens.push(Loc::at(Region::between(start, state.pos()), Token::Number));
+                    } else {
+                        fast_forward_to(&mut state, tokens, start, |b| !b.is_ascii_digit());
+                    }
+                }
+                ':' => {
+                    state.advance_mut(1);
+                    tokens.push(Loc::at(Region::between(start, state.pos()), Token::Colon));
+                }
+                '|' => {
+                    state.advance_mut(1);
+                    tokens.push(Loc::at(Region::between(start, state.pos()), Token::Bar));
+                }
+                '-' => {
+                    state.advance_mut(1);
+                    tokens.push(Loc::at(Region::between(start, state.pos()), Token::Minus));
+                }
+                '+' => {
+                    state.advance_mut(1);
+                    tokens.push(Loc::at(Region::between(start, state.pos()), Token::Plus));
+                }
+                '=' => {
+                    state.advance_mut(1);
+                    tokens.push(Loc::at(Region::between(start, state.pos()), Token::Equals));
+                }
+                '>' => {
+                    state.advance_mut(1);
+                    tokens.push(Loc::at(
+                        Region::between(start, state.pos()),
+                        Token::GreaterThan,
+                    ));
+                }
+                '<' => {
+                    state.advance_mut(1);
+                    tokens.push(Loc::at(
+                        Region::between(start, state.pos()),
+                        Token::LessThan,
+                    ));
+                }
+                ',' => {
+                    state.advance_mut(1);
+                    tokens.push(Loc::at(Region::between(start, state.pos()), Token::Comma));
+                }
+                '\\' => {
+                    state.advance_mut(1);
+                    tokens.push(Loc::at(
+                        Region::between(start, state.pos()),
+                        Token::Backslash,
+                    ));
+                }
+                '{' | '}' => {
+                    state.advance_mut(1);
+                    tokens.push(Loc::at(Region::between(start, state.pos()), Token::Brace));
+                }
+                '[' | ']' => {
+                    state.advance_mut(1);
+                    tokens.push(Loc::at(Region::between(start, state.pos()), Token::Bracket));
+                }
+                '(' | ')' => {
+                    state.advance_mut(1);
+                    tokens.push(Loc::at(Region::between(start, state.pos()), Token::Paren));
+                }
+                _ => {
+                    state.advance_mut(1);
+                    tokens.push(Loc::at(Region::between(start, state.pos()), Token::Other));
+                }
+            }
+        } else {
+            break;
+        }
+    }
+}
+
+fn fast_forward_to(
+    state: &mut State,
+    tokens: &mut Vec>,
+    start: roc_region::all::Position,
+    end: impl Fn(u8) -> bool,
+) {
+    while let Some(b) = state.bytes().first() {
+        if end(*b) {
+            break;
+        }
+        state.advance_mut(1);
+    }
+    tokens.push(Loc::at(Region::between(start, state.pos()), Token::Error));
+}
+
+pub const HEADER_KEYWORDS: [&str; 14] = [
+    "interface",
+    "app",
+    "package",
+    "platform",
+    "hosted",
+    "exposes",
+    "imports",
+    "with",
+    "generates",
+    "package",
+    "packages",
+    "requires",
+    "provides",
+    "to",
+];
+
+#[cfg(test)]
+mod tests {
+    use roc_region::all::Position;
+
+    use super::*;
+
+    #[test]
+    fn test_highlight_comments() {
+        let text = "# a\n#b\n#c";
+        let tokens = highlight(text);
+        assert_eq!(
+            tokens,
+            vec![
+                Loc::at(
+                    Region::between(Position::new(0), Position::new(3)),
+                    Token::LineComment
+                ),
+                Loc::at(
+                    Region::between(Position::new(4), Position::new(6)),
+                    Token::LineComment
+                ),
+                Loc::at(
+                    Region::between(Position::new(7), Position::new(9)),
+                    Token::LineComment
+                ),
+            ]
+        );
+    }
+
+    #[test]
+    fn test_highlight_doc_comments() {
+        let text = "## a\n##b\n##c";
+        let tokens = highlight(text);
+        assert_eq!(
+            tokens,
+            vec![
+                Loc::at(
+                    Region::between(Position::new(0), Position::new(4)),
+                    Token::DocComment
+                ),
+                // the next two are line comments because there's not a space at the beginning
+                Loc::at(
+                    Region::between(Position::new(5), Position::new(8)),
+                    Token::LineComment
+                ),
+                Loc::at(
+                    Region::between(Position::new(9), Position::new(12)),
+                    Token::LineComment
+                ),
+            ]
+        );
+    }
+
+    #[test]
+    fn test_highlight_strings() {
+        let text = r#""a""#;
+        let tokens = highlight(text);
+        assert_eq!(
+            tokens,
+            vec![Loc::at(
+                Region::between(Position::new(0), Position::new(3)),
+                Token::String
+            )]
+        );
+    }
+
+    #[test]
+    fn test_highlight_single_quotes() {
+        let text = r#"'a'"#;
+        let tokens = highlight(text);
+        assert_eq!(
+            tokens,
+            vec![Loc::at(
+                Region::between(Position::new(0), Position::new(3)),
+                Token::SingleQuote
+            )]
+        );
+    }
+
+    #[test]
+    fn test_highlight_header() {
+        let text = r#"app "test-app" provides [] to "./blah""#;
+        let tokens = highlight(text);
+        assert_eq!(
+            tokens,
+            vec![
+                Loc::at(
+                    Region::between(Position::new(0), Position::new(3)),
+                    Token::Keyword
+                ),
+                Loc::at(
+                    Region::between(Position::new(4), Position::new(14)),
+                    Token::String
+                ),
+                Loc::at(
+                    Region::between(Position::new(15), Position::new(23)),
+                    Token::Keyword
+                ),
+                Loc::at(
+                    Region::between(Position::new(24), Position::new(25)),
+                    Token::Bracket
+                ),
+                Loc::at(
+                    Region::between(Position::new(25), Position::new(26)),
+                    Token::Bracket
+                ),
+                Loc::at(
+                    Region::between(Position::new(27), Position::new(29)),
+                    Token::Keyword
+                ),
+                Loc::at(
+                    Region::between(Position::new(30), Position::new(38)),
+                    Token::String
+                ),
+            ]
+        );
+    }
+
+    #[test]
+    fn test_highlight_numbers() {
+        let text = "123.0 123 123. 123.0e10 123e10 123e-10 0x123";
+        let tokens = highlight(text);
+        assert_eq!(
+            tokens,
+            vec![
+                Loc::at(
+                    Region::between(Position::new(0), Position::new(5)),
+                    Token::Number
+                ),
+                Loc::at(
+                    Region::between(Position::new(6), Position::new(9)),
+                    Token::Number
+                ),
+                Loc::at(
+                    Region::between(Position::new(10), Position::new(14)),
+                    Token::Number
+                ),
+                Loc::at(
+                    Region::between(Position::new(15), Position::new(23)),
+                    Token::Number
+                ),
+                Loc::at(
+                    Region::between(Position::new(24), Position::new(30)),
+                    Token::Number
+                ),
+                Loc::at(
+                    Region::between(Position::new(31), Position::new(38)),
+                    Token::Number
+                ),
+                Loc::at(
+                    Region::between(Position::new(39), Position::new(44)),
+                    Token::Number
+                ),
+            ]
+        );
+    }
+
+    #[test]
+    fn test_combine_tokens() {
+        let input: Vec> = vec![
+            // arrow operator "->"
+            Loc::at(
+                Region::between(Position::new(0), Position::new(5)),
+                Token::Minus,
+            ),
+            Loc::at(
+                Region::between(Position::new(6), Position::new(7)),
+                Token::GreaterThan,
+            ),
+            // pipe operator "|>"
+            Loc::at(
+                Region::between(Position::new(8), Position::new(9)),
+                Token::Bar,
+            ),
+            Loc::at(
+                Region::between(Position::new(10), Position::new(11)),
+                Token::GreaterThan,
+            ),
+            // backpass operator "<-"
+            Loc::at(
+                Region::between(Position::new(12), Position::new(13)),
+                Token::LessThan,
+            ),
+            Loc::at(
+                Region::between(Position::new(14), Position::new(15)),
+                Token::Minus,
+            ),
+        ];
+
+        let actual = combine_tokens(input);
+        let expected = vec![
+            Loc::at(
+                Region::between(Position::new(0), Position::new(7)),
+                Token::Arrow,
+            ),
+            Loc::at(
+                Region::between(Position::new(8), Position::new(11)),
+                Token::Pipe,
+            ),
+            Loc::at(
+                Region::between(Position::new(12), Position::new(15)),
+                Token::Backpass,
+            ),
+        ];
+
+        assert_eq!(actual, expected);
+    }
+}
diff --git a/crates/compiler/parse/src/lib.rs b/crates/compiler/parse/src/lib.rs
index dbdfe30ff4..41deda54f6 100644
--- a/crates/compiler/parse/src/lib.rs
+++ b/crates/compiler/parse/src/lib.rs
@@ -10,6 +10,7 @@ pub mod ast;
 pub mod blankspace;
 pub mod expr;
 pub mod header;
+pub mod highlight;
 pub mod ident;
 pub mod keyword;
 pub mod module;
diff --git a/crates/compiler/region/src/all.rs b/crates/compiler/region/src/all.rs
index 457810cdf9..fb83fe2b37 100644
--- a/crates/compiler/region/src/all.rs
+++ b/crates/compiler/region/src/all.rs
@@ -129,6 +129,10 @@ impl Position {
             offset: self.offset - count as u32,
         }
     }
+
+    pub fn byte_offset(&self) -> usize {
+        self.offset as usize
+    }
 }
 
 impl Debug for Position {
@@ -322,6 +326,10 @@ impl Loc {
             value: transform(self.value),
         }
     }
+
+    pub fn byte_range(&self) -> std::ops::Range {
+        self.region.start.byte_offset()..self.region.end.byte_offset()
+    }
 }
 
 impl fmt::Debug for Loc
diff --git a/crates/compiler/test_syntax/tests/test_snapshots.rs b/crates/compiler/test_syntax/tests/test_snapshots.rs
index afb979f2f3..34d5062c1b 100644
--- a/crates/compiler/test_syntax/tests/test_snapshots.rs
+++ b/crates/compiler/test_syntax/tests/test_snapshots.rs
@@ -568,6 +568,15 @@ mod test_snapshots {
             Err(err) => Err(format!("{:?}", err)),
         };
 
+        if expect == TestExpectation::Pass {
+            let tokens = roc_parse::highlight::highlight(&source);
+            for token in tokens {
+                if token.value == roc_parse::highlight::Token::Error {
+                    panic!("Found an error highlight token in the input: {:?}", token);
+                }
+            }
+        }
+
         let actual_result =
             if expect == TestExpectation::Pass || expect == TestExpectation::Malformed {
                 result.expect("The source code for this test did not successfully parse!")
diff --git a/examples/static-site-gen/README.md b/examples/static-site-gen/README.md
index af96a62bfa..f8dd72ab56 100644
--- a/examples/static-site-gen/README.md
+++ b/examples/static-site-gen/README.md
@@ -9,7 +9,7 @@ To run, `cd` into this directory and run this in your terminal:
 
 If `roc` is on your PATH:
 ```bash
-roc run static-site.roc input/ output/
+roc run static-site.roc -- input/ output/
 ```
 
 If not, and you're building Roc from source:
diff --git a/examples/static-site-gen/input/apple.md b/examples/static-site-gen/input/apple.md
index f4e28c985d..f5102a8eb7 100644
--- a/examples/static-site-gen/input/apple.md
+++ b/examples/static-site-gen/input/apple.md
@@ -44,24 +44,92 @@ ingeniis in pugna quadripedis glandes superos. Tanta quam, illo es prole est
 telis **unus verba** quisquis iuvenci annis. Nec velox sed sacra gaudia vacuos,
 Herculei undae calcata inmeriti quercus ignes parabant iam.
 
-    digitize(undoDhcp(card_record, cad_flash_dot));
-    supercomputer(2 - load_type, yobibyteTraceroute - installHibernate, 1);
-    burnPci.pop_wrap.usbEmulation(hostESmm, processor_impression(4, lanNntp),
-            -5);
-    map_camera -= 73;
-    if (53) {
-        dacRootkitDrive(publicActivex.bmpNumWhite.wins_pci_firmware(scroll_cell,
-                4, tShortcut));
-        horse_algorithm_eide -= 51;
-        flatbed_blob(flat);
-    } else {
-        surge.pci -= open_flash_dv(4, 4, usbStation);
-        led.memory_fsb.matrixBinaryUrl(umlEngineOsd.agp_thick_thin.t(58));
-        kindle_cookie(formulaLedVpn, digital_meme);
-    }
+### Example Table
 
-Foret inpendere, haec ipse ossa, dolentes das Caystro miscuit iunctoque
-spoliantis illae, ex! Bello istis nunc Aegides? Animo caelestia melior,
-furoribus optat maior invecta quid harenis [est](http://example.org) sollemnia modo
-Phineu. Suarum pectora. Relinquam in labore Medusae sororem Herculis [simillima
-corpora](http://example.org) plus regi ignibus, totum domus!
+| Tables        |      Are      |  Cool |
+| :------------ | :-----------: | ----: |
+| col 3 is      | right-aligned | $1600 |
+| col 2 is      |   centered    |   $12 |
+| zebra stripes |   are neat    |    $1 |
+
+### Example Code Blocks
+
+```sh
+# This isn't fenced roc code so its not formatted
+# Use a fence like ```roc to format code blocks
+```
+
+```roc
+## This is a documentation comment
+
+# This is a comment
+app "static-site"
+    packages { pf: "platform/main.roc" }
+    imports [
+        pf.Html.{ html, head, body, div, text, a, ul, li, link, meta },
+        pf.Html.Attributes.{ httpEquiv, content, href, rel, lang, class, title },
+    ]
+    provides [transformFileContent] to pf
+
+NavLink : {
+    # this is another comment
+    url : Str,
+    title : Str,
+    text : Str,
+}
+
+navLinks : List NavLink
+navLinks = [
+    { url: "apple.html", title: "Exempli Gratia Pagina Pomi", text: "Apple" },
+    { url: "banana.html", title: "Exempli Gratia Pagina Musa", text: "Banana" },
+    { url: "cherry.html", title: "Exempli Pagina Cerasus", text: "Cherry" },
+]
+
+transformFileContent : Str, Str -> Str
+transformFileContent = \currentUrl, htmlContent ->
+    List.findFirst navLinks (\{ url } -> url == currentUrl)
+    |> Result.map (\currentNavLink -> view currentNavLink htmlContent)
+    |> Result.map Html.render
+    |> Result.withDefault ""
+
+view : NavLink, Str -> Html.Node
+view = \currentNavLink, htmlContent ->
+    html [lang "en"] [
+        head [] [
+            meta [httpEquiv "content-type", content "text/html; charset=utf-8"] [],
+            Html.title [] [text currentNavLink.title],
+            link [rel "stylesheet", href "style.css"] [],
+        ],
+        body [] [
+            div [class "main"] [
+                div [class "navbar"] [
+                    viewNavbar currentNavLink,
+                ],
+                div [class "article"] [
+                    # For now `text` is not escaped so we can use it to insert HTML
+                    # We'll probably want something more explicit in the long term though!
+                    text htmlContent,
+                ],
+            ],
+        ],
+    ]
+
+viewNavbar : NavLink -> Html.Node
+viewNavbar = \currentNavLink ->
+    ul
+        []
+        (List.map navLinks \nl -> viewNavLink (nl == currentNavLink) nl)
+
+viewNavLink : Bool, NavLink -> Html.Node
+viewNavLink = \isCurrent, navlink ->
+    if isCurrent then
+        li [class "nav-link nav-link--current"] [
+            text navlink.text,
+        ]
+    else
+        li [class "nav-link"] [
+            a
+                [href navlink.url, title navlink.title]
+                [text navlink.text],
+        ]
+```
\ No newline at end of file
diff --git a/examples/static-site-gen/output/style.css b/examples/static-site-gen/output/style.css
index cdc5bf0e97..fe77ec0472 100644
--- a/examples/static-site-gen/output/style.css
+++ b/examples/static-site-gen/output/style.css
@@ -39,7 +39,102 @@
   color: #444;
 }
 .article pre {
-  background-color: #222;
-  color: yellow;
+  background-color: rgb(241, 241, 241);
+  color: rgb(27, 27, 27);
   padding: 16px;
 }
+
+pre {
+  white-space: pre-wrap;
+}
+
+samp .ann {
+  /* type annotation - purple in the repl */
+  color: #f384fd;
+}
+
+samp .autovar .comment {
+  /* automatic variable names in the repl, e.g. # val1 */
+  color: #338545;
+}
+
+samp .kw {
+  /* language keywords, e.g. `if`*/
+  color: #004cc2;
+}
+
+samp .arrow {
+  /* operators, e.g. `+` */
+  color: #0600c2;
+}
+
+samp .pipe {
+  /* operators, e.g. `+` */
+  color: #0600c2;
+}
+
+samp .op {
+  /* operators, e.g. `+` */
+  color: #0600c2;
+}
+
+samp .assign {
+  color: #48fd00;
+}
+
+samp .paren {
+  color: #ff0000;
+}
+
+samp .comma {
+  color: #ff00fb;
+}
+
+samp .colon {
+  color: #9b0098;
+}
+
+samp .number {
+/* number literals */
+color: #9669ff;
+}
+
+samp .str {
+  /* string literals */
+  color: #1dbf00;
+}
+
+samp .str-esc, samp .str-interp {
+  /* escapes inside string literals, e.g. \t */
+  color: #3474db;
+}
+
+samp .dim {
+  opacity: 0.55;
+}
+
+samp .comment {
+  color: #005a13;
+}
+
+
+table {
+  table-layout: fixed;
+  width: 100%;
+  border-collapse: collapse;
+  border: 3px solid rgb(161, 64, 0);
+}
+
+tbody tr:nth-child(even) {
+  background-color: #c6f4ff;
+}
+
+th {
+  background-color: #ffabab;
+}
+
+th,
+td {
+  
+  padding: 2px;
+}
\ No newline at end of file
diff --git a/examples/static-site-gen/platform/Cargo.lock b/examples/static-site-gen/platform/Cargo.lock
index 0e81055120..2aa6bd2bc5 100644
--- a/examples/static-site-gen/platform/Cargo.lock
+++ b/examples/static-site-gen/platform/Cargo.lock
@@ -2,27 +2,194 @@
 # It is not intended for manual editing.
 version = 3
 
+[[package]]
+name = "addr2line"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "ahash"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "version_check",
+]
+
 [[package]]
 name = "arrayvec"
 version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
 
+[[package]]
+name = "backtrace"
+version = "0.3.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
 [[package]]
 name = "bitflags"
 version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
+[[package]]
+name = "bitmaps"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "bitvec"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
+dependencies = [
+ "funty",
+ "radium",
+ "tap",
+ "wyz",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "doc-comment"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
+
+[[package]]
+name = "encode_unicode"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "funty"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
+
+[[package]]
+name = "getrandom"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gimli"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash",
+ "bumpalo",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
 [[package]]
 name = "host"
 version = "0.0.1"
 dependencies = [
  "libc",
  "pulldown-cmark",
+ "roc_parse",
+ "roc_region",
  "roc_std",
 ]
 
+[[package]]
+name = "im"
+version = "15.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9"
+dependencies = [
+ "bitmaps",
+ "rand_core",
+ "rand_xoshiro",
+ "sized-chunks",
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "im-rc"
+version = "15.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe"
+dependencies = [
+ "bitmaps",
+ "rand_core",
+ "rand_xoshiro",
+ "sized-chunks",
+ "typenum",
+ "version_check",
+]
+
 [[package]]
 name = "libc"
 version = "0.2.132"
@@ -35,6 +202,39 @@ version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
 
+[[package]]
+name = "miniz_oxide"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "object"
+version = "0.30.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
+dependencies = [
+ "unicode-ident",
+]
+
 [[package]]
 name = "pulldown-cmark"
 version = "0.9.2"
@@ -46,6 +246,88 @@ dependencies = [
  "unicase",
 ]
 
+[[package]]
+name = "quote"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "radium"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
+[[package]]
+name = "rand_xoshiro"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "roc_collections"
+version = "0.0.1"
+dependencies = [
+ "bitvec",
+ "bumpalo",
+ "fnv",
+ "hashbrown",
+ "im",
+ "im-rc",
+ "wyhash",
+]
+
+[[package]]
+name = "roc_error_macros"
+version = "0.0.1"
+
+[[package]]
+name = "roc_ident"
+version = "0.0.1"
+
+[[package]]
+name = "roc_module"
+version = "0.0.1"
+dependencies = [
+ "bumpalo",
+ "roc_collections",
+ "roc_error_macros",
+ "roc_ident",
+ "roc_region",
+ "snafu",
+ "static_assertions",
+]
+
+[[package]]
+name = "roc_parse"
+version = "0.0.1"
+dependencies = [
+ "bumpalo",
+ "encode_unicode",
+ "roc_collections",
+ "roc_module",
+ "roc_region",
+]
+
+[[package]]
+name = "roc_region"
+version = "0.0.1"
+dependencies = [
+ "static_assertions",
+]
+
 [[package]]
 name = "roc_std"
 version = "0.0.1"
@@ -54,12 +336,74 @@ dependencies = [
  "static_assertions",
 ]
 
+[[package]]
+name = "rustc-demangle"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
+
+[[package]]
+name = "sized-chunks"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
+dependencies = [
+ "bitmaps",
+ "typenum",
+]
+
+[[package]]
+name = "snafu"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb0656e7e3ffb70f6c39b3c2a86332bb74aa3c679da781642590f3c1118c5045"
+dependencies = [
+ "backtrace",
+ "doc-comment",
+ "snafu-derive",
+]
+
+[[package]]
+name = "snafu-derive"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "475b3bbe5245c26f2d8a6f62d67c1f30eb9fffeccee721c45d162c3ebbdf81b2"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "static_assertions"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
+[[package]]
+name = "syn"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+
+[[package]]
+name = "typenum"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+
 [[package]]
 name = "unicase"
 version = "2.6.0"
@@ -69,8 +413,38 @@ dependencies = [
  "version_check",
 ]
 
+[[package]]
+name = "unicode-ident"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
+
 [[package]]
 name = "version_check"
 version = "0.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wyhash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf6e163c25e3fac820b4b453185ea2dea3b6a3e0a721d4d23d75bd33734c295"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "wyz"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
+dependencies = [
+ "tap",
+]
diff --git a/examples/static-site-gen/platform/Cargo.toml b/examples/static-site-gen/platform/Cargo.toml
index 9b5becea49..64a9ab497d 100644
--- a/examples/static-site-gen/platform/Cargo.toml
+++ b/examples/static-site-gen/platform/Cargo.toml
@@ -18,7 +18,11 @@ path = "src/main.rs"
 
 [dependencies]
 roc_std = { path = "../../../crates/roc_std" }
+roc_region = { path = "../../../crates/compiler/region" }
+roc_parse = { path = "../../../crates/compiler/parse" }
 libc = "0.2"
+html-escape = "0.2"
+
 # Default features include building a binary that we don't need
 pulldown-cmark = { version = "0.9.2", default-features = false }
 
diff --git a/examples/static-site-gen/platform/src/highlight.rs b/examples/static-site-gen/platform/src/highlight.rs
new file mode 100644
index 0000000000..776918e362
--- /dev/null
+++ b/examples/static-site-gen/platform/src/highlight.rs
@@ -0,0 +1,77 @@
+use roc_parse::highlight::Token;
+use roc_region::all::Loc;
+
+pub fn highlight_roc_code(code: &str) -> String {
+    let locations: Vec> = roc_parse::highlight::highlight(code);
+    let mut buf: Vec = Vec::new();
+    let mut offset = 0;
+
+    for location in locations {
+        let current_text = &code[offset..location.byte_range().end];
+
+        match location.value {
+            Token::LineComment | Token::DocComment => {
+                buf = push_html_span(buf, current_text, "comment");
+            }
+            Token::SingleQuote
+            | Token::String
+            | Token::UnicodeEscape
+            | Token::EscapedChar
+            | Token::Interpolated => {
+                buf = push_html_span(buf, current_text, "str");
+            }
+            Token::Keyword => {
+                buf = push_html_span(buf, current_text, "kw");
+            }
+            Token::Number => {
+                buf = push_html_span(buf, current_text, "number");
+            }
+            Token::Pipe => {
+                buf = push_html_span(buf, current_text, "pipe");
+            }
+            Token::Arrow => {
+                buf = push_html_span(buf, current_text, "arrow");
+            }
+            Token::Backpass => {
+                buf = push_html_span(buf, current_text, "arrow");
+            }
+            Token::Comma => {
+                buf = push_html_span(buf, current_text, "comma");
+            }
+            Token::Colon | Token::Backslash => {
+                buf = push_html_span(buf, current_text, "colon");
+            }
+            Token::GreaterThan | Token::Minus | Token::LessThan | Token::Plus | Token::Equals => {
+                buf = push_html_span(buf, current_text, "op");
+            }
+            Token::Brace | Token::Bracket | Token::Paren => {
+                buf = push_html_span(buf, current_text, "paren");
+            }
+            _ => {
+                buf = push_html(buf, current_text);
+            }
+        }
+
+        offset = location.byte_range().end;
+    }
+
+    format!("
{}
", buf.join("")) +} + +fn push_html_span(mut buf: Vec, curr: &str, class: &str) -> Vec { + // html escape strings from source code + let escaped = html_escape::encode_text(curr); + + buf.push(format!("{}", class, escaped)); + + buf +} + +fn push_html(mut buf: Vec, curr: &str) -> Vec { + // html escape strings from source code + let escaped = html_escape::encode_text(curr); + + buf.push(format!("{}", escaped)); + + buf +} diff --git a/examples/static-site-gen/platform/src/lib.rs b/examples/static-site-gen/platform/src/lib.rs index 7db426008e..28f0070eba 100644 --- a/examples/static-site-gen/platform/src/lib.rs +++ b/examples/static-site-gen/platform/src/lib.rs @@ -8,6 +8,8 @@ use std::fs; use std::os::raw::c_char; use std::path::{Path, PathBuf}; +mod highlight; + extern "C" { #[link_name = "roc__transformFileContentForHost_1_exposed"] fn roc_transformFileContentForHost(relPath: &RocStr, content: &RocStr) -> RocStr; @@ -202,7 +204,54 @@ fn process_file(input_dir: &Path, output_dir: &Path, input_file: &Path) -> Resul options.remove(Options::ENABLE_SMART_PUNCTUATION); let parser = Parser::new_ext(&content_md, options); - html::push_html(&mut content_html, parser); + + // We'll build a new vector of events since we can only consume the parser once + let mut parser_with_highlighting = Vec::new(); + // As we go along, we'll want to highlight code in bundles, not lines + let mut to_highlight = String::new(); + // And track a little bit of state + let mut in_code_block = false; + let mut is_roc_code = false; + + for event in parser { + match event { + pulldown_cmark::Event::Start(pulldown_cmark::Tag::CodeBlock(cbk)) => { + in_code_block = true; + is_roc_code = is_roc_code_block(&cbk); + } + pulldown_cmark::Event::End(pulldown_cmark::Tag::CodeBlock(_)) => { + if in_code_block { + // Format the whole multi-line code block as HTML all at once + let highlighted_html: String; + if is_roc_code { + highlighted_html = crate::highlight::highlight_roc_code(&to_highlight) + } else { + highlighted_html = format!("
{}
", &to_highlight) + } + + // And put it into the vector + parser_with_highlighting.push(pulldown_cmark::Event::Html( + pulldown_cmark::CowStr::from(highlighted_html), + )); + to_highlight = String::new(); + in_code_block = false; + } + } + pulldown_cmark::Event::Text(t) => { + if in_code_block { + // If we're in a code block, build up the string of text + to_highlight.push_str(&t); + } else { + parser_with_highlighting.push(pulldown_cmark::Event::Text(t)) + } + } + e => { + parser_with_highlighting.push(e); + } + } + } + + html::push_html(&mut content_html, parser_with_highlighting.into_iter()); let roc_relpath = RocStr::from(output_relpath.to_str().unwrap()); let roc_content_html = RocStr::from(content_html.as_str()); @@ -240,3 +289,16 @@ pub fn strip_windows_prefix(path_buf: PathBuf) -> std::path::PathBuf { std::path::Path::new(path_str.trim_start_matches(r"\\?\")).to_path_buf() } + +fn is_roc_code_block(cbk: &pulldown_cmark::CodeBlockKind) -> bool { + match cbk { + pulldown_cmark::CodeBlockKind::Indented => false, + pulldown_cmark::CodeBlockKind::Fenced(cow_str) => { + if cow_str.contains("roc") { + true + } else { + false + } + } + } +}