From f727883f46ceae2bae6f04ceddae2aec291a7be4 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Tue, 28 Jul 2026 23:21:45 +0300 Subject: [PATCH] x86_64 compiler backend wip --- .../pslang/jit/arch/linux_x86_64/compiler.hpp | 11 + .../arch/linux_x86_64/instruction_builder.hpp | 344 ++++++ .../jit/source/arch/linux_x86_64/compiler.cpp | 1039 +++++++++++++++++ .../arch/linux_x86_64/instruction_builder.cpp | 609 ++++++++++ libs/jit/source/jit.cpp | 10 + .../types/include/pslang/types/half_float.hpp | 6 +- 6 files changed, 2017 insertions(+), 2 deletions(-) create mode 100644 libs/jit/include/pslang/jit/arch/linux_x86_64/compiler.hpp create mode 100644 libs/jit/include/pslang/jit/arch/linux_x86_64/instruction_builder.hpp create mode 100644 libs/jit/source/arch/linux_x86_64/compiler.cpp create mode 100644 libs/jit/source/arch/linux_x86_64/instruction_builder.cpp diff --git a/libs/jit/include/pslang/jit/arch/linux_x86_64/compiler.hpp b/libs/jit/include/pslang/jit/arch/linux_x86_64/compiler.hpp new file mode 100644 index 0000000..4157d8a --- /dev/null +++ b/libs/jit/include/pslang/jit/arch/linux_x86_64/compiler.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +namespace pslang::jit::linux_x86_64 +{ + + void compile(program_context & pcontext, ir::module_context const & mcontext); + +} diff --git a/libs/jit/include/pslang/jit/arch/linux_x86_64/instruction_builder.hpp b/libs/jit/include/pslang/jit/arch/linux_x86_64/instruction_builder.hpp new file mode 100644 index 0000000..af6588f --- /dev/null +++ b/libs/jit/include/pslang/jit/arch/linux_x86_64/instruction_builder.hpp @@ -0,0 +1,344 @@ +#pragma once + +#include +#include + +namespace pslang::jit::linux_x86_64 +{ + + enum class reg : std::uint8_t + { + rax = 0, + rcx = 1, + rdx = 2, + rbx = 3, + rsp = 4, + rbp = 5, + rsi = 6, + rdi = 7, + r8 = 8, + r9 = 9, + r10 = 10, + r11 = 11, + r12 = 12, + r13 = 13, + r14 = 14, + r15 = 15, + + xmm0 = 0, + xmm1 = 1, + xmm2 = 2, + xmm3 = 3, + xmm4 = 4, + xmm5 = 5, + xmm6 = 6, + xmm7 = 7, + xmm8 = 8, + xmm9 = 9, + xmm10 = 10, + xmm11 = 11, + xmm12 = 12, + xmm13 = 13, + xmm14 = 14, + xmm15 = 15, + }; + + struct instruction_builder + { + std::vector & code; + + void nop(); + + // Return from procedure: pop the 64-bit return address from the top of the stack, + // optionally pop extra @count bytes from the stack, then transfer execution + // to the return address + void ret(std::uint16_t count = 0); + + // Add a signed 32-bit @value to the register @reg + void add_imm(reg reg, std::int32_t value); + + // Subtract a signed 32-bit @value from the register @reg + void sub_imm(reg reg, std::int32_t value); + + // Store the zero-extended 32-bit @value in register @reg_dst + void movzd(std::uint32_t value, reg reg_dst); + + // Store the sign-extended 32-bit @value in register @reg_dst + void movsd(std::int32_t value, reg reg_dst); + + // Store the zero-extended 8-bit GPR @reg_src in 64-bit register @reg_dst + void movzx_8(reg reg_src, reg reg_dst); + + // Store the zero-extended 16-bit GPR @reg_src in 64-bit register @reg_dst + void movzx_16(reg reg_src, reg reg_dst); + + // Store the sign-extended 8-bit GPR @reg_src in 64-bit register @reg_dst + void movsx_8(reg reg_src, reg reg_dst); + + // Store the sign-extended 16-bit GPR @reg_src in 64-bit register @reg_dst + void movsx_16(reg reg_src, reg reg_dst); + + // Store the sign-extended 32-bit GPR @reg_src in 64-bit register @reg_dst + void movsx_32(reg reg_src, reg reg_dst); + + // Store the 64-bit @value in register @reg_dst + void mov(std::uint64_t value, reg reg_dst); + + // Copy the 64-bit GPR @reg_src to 64-bit GPR @reg_dst + void mov(reg reg_src, reg reg_dst); + + // Copy the 64-bit value of GPR @reg_src into XMM @reg_dst + void mov_gpr_to_xmm(reg reg_src, reg reg_dst); + + // Copy the 32-bit value of GPR @reg_src into XMM @reg_dst + void mov_gpr_to_xmm_32(reg reg_src, reg reg_dst); + + // Read the 64-bit value from @reg_addr + @offset into @reg_dst + void mov_read(reg reg_addr, std::int32_t offset, reg reg_dst); + + // Read the 32-bit value from @reg_addr + @offset into @reg_dst, and zero-extend @reg_dst + void mov_read_32(reg reg_addr, std::int32_t offset, reg reg_dst); + + // Read the 16-bit value from @reg_addr + @offset into @reg_dst + void mov_read_16(reg reg_addr, std::int32_t offset, reg reg_dst); + + // Read the 8-bit value from @reg_addr + @offset into @reg_dst + void mov_read_8(reg reg_addr, std::int32_t offset, reg reg_dst); + + // Read the 64-bit value from RIP + @offset into @reg_dst + // NB: RIP holds the address of the _next_ instruction + void mov_read_rip(std::int32_t offset, reg reg_dst); + + // Read the 64-bit value from RIP + @offset into @reg_dst + // NB: this function compensates for MOV instruction size and behaves + // as if RIP pointed to the _current_ instruction (i.e. MOV itself) + void mov_read_rip_prev(std::int32_t offset, reg reg_dst); + + // Write the 64-bit value from @reg_src to @reg_addr + @offset + void mov_write(reg reg_src, reg reg_addr, std::int32_t offset); + + // Write the 32-bit value from @reg_src to @reg_addr + @offset + void mov_write_32(reg reg_src, reg reg_addr, std::int32_t offset); + + // Write the 16-bit value from @reg_src to @reg_addr + @offset + void mov_write_16(reg reg_src, reg reg_addr, std::int32_t offset); + + // Write the 8-bit value from @reg_src to @reg_addr + @offset + void mov_write_8(reg reg_src, reg reg_addr, std::int32_t offset); + + // Write the 64-bit value from @reg_src to RIP + @offset + // NB: RIP holds the address of the _next_ instruction + void mov_write_rip(reg reg_src, std::int32_t offset); + + // Write the 64-bit value from @reg_src to RIP + @offset + // NB: RIP holds the address of the _next_ instruction + // NB: this function compensates for MOV instruction size and behaves + // as if RIP pointed to the _current_ instruction (i.e. MOV itself) + void mov_write_rip_prev(reg reg_src, std::int32_t offset); + + // Read the 64-bit value from GPR @reg_addr + @offset into XMM @reg_dst + void mov_read_xmm(reg reg_addr, std::int32_t offset, reg reg_dst); + + // Write the 64-bit value from XMM @reg_src to GPR @reg_addr + @offset + void mov_write_xmm(reg reg_src, reg reg_addr, std::int32_t offset); + + // Read the 32-bit value from GPR @reg_addr + @offset into XMM @reg_dst + void mov_read_xmm_32(reg reg_addr, std::int32_t offset, reg reg_dst); + + // Write the 32-bit value from XMM @reg_src to GPR @reg_addr + @offset + void mov_write_xmm_32(reg reg_src, reg reg_addr, std::int32_t offset); + + // Add the 32-bit signed @offset to 64-bit GPR @reg_addr and store the result in GPR @reg_dst + void lea(reg reg_addr, std::int32_t offset, reg reg_dst); + + // Add the 32-bit signed @offset to RIP and store the result in GPR @reg_dst + // NB: RIP holds the address of the _next_ instruction + void lea_rip(std::int32_t offset, reg reg_dst); + + // Add the 32-bit signed @offset to RIP and store the result in GPR @reg_dst + // NB: this function compensates for LEA instruction size and behaves + // as if RIP pointed to the _current_ instruction (i.e. LEA itself) + void lea_rip_prev(std::int32_t offset, reg reg_dst); + + // Push the 64-bit GPR @reg onto the stack (also subtracts 8 from RSP) + void push(reg reg); + + // Pop the 64-bit GPR @reg from the stack (also adds 8 to RSP) + void pop(reg reg); + + // Negate the 64-bit GPR @reg + void neg(reg reg); + + // Bitwise not the 64-bit GPR @reg + void not_(reg reg); + + // Bitwise-and 64-bit GPR @reg1 and @reg2 and only compute the flags (without writing the result anywhere) + void test(reg reg1, reg reg2); + + // Add the 64-bit GPR @reg_dst to the 64-bit GPR @reg_src and store the result in @reg_dst + void add(reg reg_src, reg reg_dst); + + // Subtract the 64-bit GPR @reg_src from the 64-bit GPR @reg_dst and store the result in @reg_dst + void sub(reg reg_src, reg reg_dst); + + // Multiply the 64-bit GPR @reg_dst with the 64-bit GPR @reg_src and store the result in @reg_dst + void mul(reg reg_src, reg reg_dst); + + // Divide the unsigned 128-bit value of RDX:RAX by unsigned GPR @reg_src, + // and store the quotient in RAX and the remainder in RDX + void udiv(reg reg_src); + + // Divide the signed 128-bit value of RDX:RAX by signed GPR @reg_src, + // and store the quotient in RAX and the remainder in RDX + void idiv(reg reg_src); + + // Sign-extend RAX into RDX (needed to prepare RDX before signed division) + void cqo(); + + // Bitwise and the 64-bit GPR @reg_src with the 64-bit GPR @reg_dst and store the result in @reg_dst + void and_(reg reg_src, reg reg_dst); + + // Bitwise or the 64-bit GPR @reg_src with the 64-bit GPR @reg_dst and store the result in @reg_dst + void or_(reg reg_src, reg reg_dst); + + // Bitwise xor the 64-bit GPR @reg_src with the 64-bit GPR @reg_dst and store the result in @reg_dst + void xor_(reg reg_src, reg reg_dst); + + // Left-shift the 64-bit GPR @reg_dst by the value of the low 6 bits of RCX and store the result in @reg_dst + void shl(reg reg_dst); + + // Right-shift with zero-filling the 64-bit GPR @reg_dst by the value of the low 6 bits of RCX and store the result in @reg_dst + void shr(reg reg_dst); + + // Right-shift with sign-extending the 64-bit GPR @reg_dst by the value of the low 6 bits of RCX and store the result in @reg_dst + void sar(reg reg_dst); + + // Compare the 64-bit GPR @reg1 and @reg2 and only compute the flags (without writing the result anywhere) + // Effectively computes (@reg1 - @reg2) + void cmp(reg reg1, reg reg2); + + // If ZF=1, copy the 64-bit GPR @reg_src into @reg_dst, otherwise do nothing + void cmovz(reg reg_src, reg reg_dst); + + // If ZF=0, copy the 64-bit GPR @reg_src into @reg_dst, otherwise do nothing + void cmovnz(reg reg_src, reg reg_dst); + + // If CF=1, copy the 64-bit GPR @reg_src into @reg_dst, otherwise do nothing + void cmovb(reg reg_src, reg reg_dst); + + // If CF=0, copy the 64-bit GPR @reg_src into @reg_dst, otherwise do nothing + void cmovnb(reg reg_src, reg reg_dst); + + // If SF != OF, copy the 64-bit GPR @reg_src into @reg_dst, otherwise do nothing + void cmovl(reg reg_src, reg reg_dst); + + // If SF == OF, copy the 64-bit GPR @reg_src into @reg_dst, otherwise do nothing + void cmovnl(reg reg_src, reg reg_dst); + + // Add the 64-bit XMM @reg_dst to the 64-bit XMM @reg_src and store the result in @reg_dst + void add_xmm(reg reg_src, reg reg_dst); + + // Add the 32-bit XMM @reg_dst to the 32-bit XMM @reg_src and store the result in @reg_dst + void add_xmm_32(reg reg_src, reg reg_dst); + + // Subtract the 64-bit XMM @reg_src from the 64-bit XMM @reg_dst and store the result in @reg_dst + void sub_xmm(reg reg_src, reg reg_dst); + + // Subtract the 32-bit XMM @reg_src from the 32-bit XMM @reg_dst and store the result in @reg_dst + void sub_xmm_32(reg reg_src, reg reg_dst); + + // Multiply the 64-bit XMM @reg_dst with the 64-bit XMM @reg_src and store the result in @reg_dst + void mul_xmm(reg reg_src, reg reg_dst); + + // Multiply the 32-bit XMM @reg_dst with the 32-bit XMM @reg_src and store the result in @reg_dst + void mul_xmm_32(reg reg_src, reg reg_dst); + + // Divide the 64-bit XMM @reg_dst by the 64-bit XMM @reg_src and store the result in @reg_dst + void div_xmm(reg reg_src, reg reg_dst); + + // Divide the 32-bit XMM @reg_dst by the 32-bit XMM @reg_src and store the result in @reg_dst + void div_xmm_32(reg reg_src, reg reg_dst); + + // Bitwise-xor the 64-bit XMM @reg_dst with the 64-bit XMM @reg_src and store the result in @reg_dst + void xor_xmm(reg reg_src, reg reg_dst); + + // Bitwise-xor the 32-bit XMM @reg_dst with the 32-bit XMM @reg_src and store the result in @reg_dst + void xor_xmm_32(reg reg_src, reg reg_dst); + + // Compare the 64-bit XMM @reg1 and @reg2 and only compute the flags (without writing the result anywhere) + void cmp_xmm(reg reg1, reg reg2); + + // Compare the 32-bit XMM @reg1 and @reg2 and only compute the flags (without writing the result anywhere) + void cmp_xmm_32(reg reg1, reg reg2); + + // Convert the 64-bit signed value in GPR @reg_src into the 64-bit XMM @reg_dst + void gpr_to_xmm(reg reg_src, reg reg_dst); + + // Convert the 64-bit signed value in GPR @reg_src into the 32-bit XMM @reg_dst + void gpr_to_xmm_32(reg reg_src, reg reg_dst); + + // Convert the 64-bit in XMM @reg_src into the signed 64-bit XMM @reg_dst + void xmm_to_gpr(reg reg_src, reg reg_dst); + + // Convert the 32-bit in XMM @reg_src into the signed 64-bit XMM @reg_dst + void xmm_32_to_gpr(reg reg_src, reg reg_dst); + + // Convert the low 4x16-bit values from XMM @reg_src into 4x32-bit values in XMM @reg_dst + void xmm_16_to_32(reg reg_src, reg reg_dst); + + // Convert the 4x32-bit values from XMM @reg_src into the low 4x16-bit values in XMM @reg_dst + void xmm_32_to_16(reg reg_src, reg reg_dst); + + // Convert the low 2x32-bit values from XMM @reg_src into 2x64-bit values in XMM @reg_dst + void xmm_32_to_64(reg reg_src, reg reg_dst); + + // Convert the 2x64-bit values from XMM @reg_src into the low 2x32-bit values in XMM @reg_dst + void xmm_64_to_32(reg reg_src, reg reg_dst); + + // Jump to 32-bit signed @offset relative to RIP + // NB: RIP holds the address of the _next_ instruction + void jump(std::int32_t offset); + + // Conditional jump to 32-bit signed @offset relative to RIP + // if ZF flag is set + // NB: RIP holds the address of the _next_ instruction + void jump_if_zero(std::int32_t offset); + + // Conditional jump to 32-bit signed @offset relative to RIP + // if ZF flag is NOT set + // NB: RIP holds the address of the _next_ instruction + void jump_if_nonzero(std::int32_t offset); + + // Assuming that @opcode refers to the location of a JUMP instruction, + // replace its 32-bit jump offset with @offset + void jump_inject(std::uint8_t * opcode, std::int32_t offset); + + // Assuming that @opcode refers to the location of a JUMP instruction, + // replace its 32-bit jump offset with @offset, compensating for the size + // of the JUMP instruction itself + void jump_inject_prev(std::uint8_t * opcode, std::int32_t offset); + + // Assuming that @opcode refers to the location of a conditional JUMP instruction + // (JZ or JNZ), replace its 32-bit jump offset with @offset + void cjump_inject(std::uint8_t * opcode, std::int32_t offset); + + // Assuming that @opcode refers to the location of a conditional JUMP instruction + // (JZ or JNZ), replace its 32-bit jump offset with @offset, compensating for the size + // of the JUMP instruction itself + void cjump_inject_prev(std::uint8_t * opcode, std::int32_t offset); + + // Assuming that @opcode refers to the location of a RIP-relative LEA instruction, + // replace its 32-bit jump offset with @offset + void lea_rip_inject(std::uint8_t * opcode, std::int32_t offset); + + // Assuming that @opcode refers to the location of a RIP-relative LEA instruction, + // replace its 32-bit jump offset with @offset, compensating for the size + // of the LEA instruction itself + void lea_rip_inject_prev(std::uint8_t * opcode, std::int32_t offset); + + private: + template + void do_push(Args ... values); + }; + +} diff --git a/libs/jit/source/arch/linux_x86_64/compiler.cpp b/libs/jit/source/arch/linux_x86_64/compiler.cpp new file mode 100644 index 0000000..07fbbf8 --- /dev/null +++ b/libs/jit/source/arch/linux_x86_64/compiler.cpp @@ -0,0 +1,1039 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace pslang::jit::linux_x86_64 +{ + + namespace + { + + template + reg find_free_reg(Regs ... regs) + { + for (int i = 0; i < 16; ++i) + { + if ((true && ... && (regs != reg(i)))) + return reg(i); + } + throw std::runtime_error("Unable to find a free register"); + } + + static std::array integer_arg_reg = { + reg::rdi, + reg::rsi, + reg::rdx, + reg::rcx, + reg::r8, + reg::r9, + }; + + struct value_address + { + // None means RIP-based addressing + std::optional base; + + std::int32_t offset; + }; + + struct local_context + { + bool use_frame_pointer = true; + + std::unordered_map extern_symbols; + + std::unordered_map nodes; + + struct resolve_data + { + std::int32_t offset; + ir::node_ref target; + }; + + std::vector jump_resolve; + std::vector cjump_resolve; + std::vector node_resolve; + }; + + struct populate_globals_visitor + { + std::vector & storage; + local_context & lcontext; + + template + void apply(ir::node_ref, Node const &, types::type_ptr const &) + {} + + void apply(ir::node_ref it, ir::global const & node, types::type_ptr const & type) + { + auto size = ast::type_size(*type); + if (node.initializer.size() > size) + throw std::runtime_error("global IR node with initializer larger than type size"); + + auto alignment = ast::type_alignment(*type); + auto offset = storage.size(); + offset = ((offset + (alignment - 1)) / alignment) * alignment; + storage.resize(offset + size); + std::copy(node.initializer.begin(), node.initializer.end(), storage.begin() + offset); + lcontext.nodes[it] = offset; + } + }; + + struct populate_const_data_visitor + { + program_context & pcontext; + local_context & lcontext; + + template + void apply(Node const & node, types::type_ptr const &) + {} + + void apply(ir::extern_symbol const & node, types::type_ptr const &) + { + std::int32_t offset = pcontext.storage.size(); + lcontext.extern_symbols[node.name] = offset; + pcontext.foreign_resolve.push_back({node.name, offset}); + push_bytes(pcontext.storage.storage, (void *)nullptr); + } + }; + + struct literal_visitor + { + program_context & pcontext; + local_context & lcontext; + instruction_builder & builder; + + void operator()(ast::bool_literal const & node) + { + if (node.value) + builder.mov(-1, reg::rax); + else + builder.xor_(reg::rax, reg::rax); + } + + template + requires(std::is_integral_v && !std::is_same_v) + void operator()(ast::primitive_literal_base const & node) + { + if (sizeof(T) <= 4) + { + if (std::is_signed_v) + builder.movsd(std::int32_t(node.value), reg::rax); + else + builder.movzd(std::uint32_t(node.value), reg::rax); + } + else + builder.mov(std::uint64_t(node.value), reg::rax); + } + + void operator()(ast::f16_literal const & node) + { + throw std::runtime_error("Not implemented"); + } + + void operator()(ast::f32_literal const & node) + { + builder.movzd(*(std::uint32_t const *)(&node.value), reg::rax); + builder.mov_gpr_to_xmm_32(reg::rax, reg::xmm0); + } + + void operator()(ast::f64_literal const & node) + { + builder.mov(*(std::uint64_t const *)(&node.value), reg::rax); + builder.mov_gpr_to_xmm(reg::rax, reg::xmm0); + } + }; + + struct compile_visitor + { + program_context & pcontext; + ir::module_context const & mcontext; + local_context & lcontext; + instruction_builder & builder; + + std::vector argument_position; + std::unordered_map stack_position; + std::int32_t stack_size = 0; + + void apply(ir::node_ref, ir::label const &, types::type_ptr const &) + {} + + void apply(ir::node_ref it, ir::literal const & node, types::type_ptr const & type) + { + std::visit(literal_visitor{pcontext, lcontext, builder}, node.value); + if (types::is_integer_like_type(*type)) + store(it, reg::rax); + else if (types::is_floating_point_type(*type)) + store_xmm(it, reg::xmm0, ast::type_size(*type)); + } + + void apply(ir::node_ref it, ir::alloc const & node, types::type_ptr const & type) + { + // Nothing to do: alloc just allocates a node of some type, + // but we already allocated stack space for it + } + + void apply(ir::node_ref it, ir::global const & node, types::type_ptr const & type) + { + // Globals are added in a separate pass before code + } + + void apply(ir::node_ref it, ir::copy const & node, types::type_ptr const & type) + { + auto size = ast::type_size(*type); + auto dst_address = node_address(it); + + auto src_type = node.source->inferred_type; + auto src_address = node_address(node.source); + for (auto field_id : node.path) + { + if (auto struct_type = std::get_if(src_type.get())) + { + auto struct_node = struct_type->node; + src_type = struct_node->fields[field_id].inferred_type; + src_address.offset += struct_node->fields[field_id].layout.offset; + } + else if (auto array_type = std::get_if(src_type.get())) + { + src_type = array_type->element_type; + src_address.offset += field_id * ast::type_size(*array_type->element_type); + } + else + throw std::runtime_error("Unknown object type for field copy"); + } + + copy_memory(src_address, dst_address, size); + } + + void apply(ir::node_ref it, ir::load const & node, types::type_ptr const & type) + { + load(node.ptr, reg::rax); + auto size = ast::type_size(*type); + auto dst_address = node_address(it); + copy_memory({.base = reg::rax, .offset = 0}, dst_address, size); + } + + void apply(ir::node_ref it, ir::store const & node, types::type_ptr const & type) + { + load(node.ptr, reg::rax); + auto size = ast::type_size(*type); + auto src_address = node_address(node.value); + copy_memory(src_address, {.base = reg::rax, .offset = 0}, size); + } + + void apply(ir::node_ref it, ir::unary_operation const & node, types::type_ptr const & type) + { + switch (node.type) + { + case ast::unary_operation_type::negation: + if (types::is_integer_type(*type)) + { + load(node.arg1, reg::rax); + builder.neg(reg::rax); + store(it, reg::rax); + } + else if (types::is_floating_point_type(*type)) + { + auto const size = ast::type_size(*type); + load_xmm(node.arg1, reg::xmm0, size); + if (size <= 4) + { + // f16 or f32 + builder.movzd(0x80000000u, reg::rax); + builder.mov_gpr_to_xmm_32(reg::rax, reg::xmm1); + builder.xor_xmm_32(reg::xmm1, reg::xmm0); + } + else + { + // f64 + builder.mov(0x8000000000000000ull, reg::rax); + builder.mov_gpr_to_xmm(reg::rax, reg::xmm1); + builder.xor_xmm(reg::xmm1, reg::xmm0); + } + store_xmm(it, reg::xmm0, size); + } + break; + case ast::unary_operation_type::logical_not: + load(node.arg1, reg::rax); + builder.not_(reg::rax); + store(it, reg::rax); + break; + case ast::unary_operation_type::address_of: + case ast::unary_operation_type::mutable_address_of: + { + auto const address = node_address(node.arg1); + if (address.base) + builder.lea(*address.base, address.offset, reg::rax); + else + builder.lea_rip_prev(address.offset, reg::rax); + store(it, reg::rax); + } + break; + case ast::unary_operation_type::dereference: + throw std::runtime_error("Dereference operator mush not be present in compiled IR"); + } + } + + void apply(ir::node_ref it, ir::binary_operation const & node, types::type_ptr const & type) + { + auto const arg1_type = node.arg1->inferred_type; + bool const is_fp = types::is_floating_point_type(*arg1_type); + bool const result_is_fp = types::is_floating_point_type(*type); + auto const arg_size = ast::type_size(*arg1_type); + + if (is_fp) + { + load_xmm(node.arg1, reg::xmm0, arg_size); + load_xmm(node.arg2, reg::xmm1, arg_size); + } + else + { + load(node.arg1, reg::rax); + load(node.arg2, reg::rbx); + } + + switch (node.type) + { + case ast::binary_operation_type::addition: + if (is_fp) + { + if (arg_size <= 4) + builder.add_xmm_32(reg::xmm1, reg::xmm0); + else + builder.add_xmm(reg::xmm1, reg::xmm0); + } + else + builder.add(reg::rbx, reg::rax); + break; + case ast::binary_operation_type::subtraction: + if (is_fp) + { + if (arg_size <= 4) + builder.sub_xmm_32(reg::xmm1, reg::xmm0); + else + builder.sub_xmm(reg::xmm1, reg::xmm0); + } + else + builder.sub(reg::rbx, reg::rax); + break; + case ast::binary_operation_type::multiplication: + if (is_fp) + { + if (arg_size <= 4) + builder.mul_xmm_32(reg::xmm1, reg::xmm0); + else + builder.mul_xmm(reg::xmm1, reg::xmm0); + } + else + builder.mul(reg::rbx, reg::rax); + break; + case ast::binary_operation_type::division: + if (is_fp) + { + if (arg_size <= 4) + builder.div_xmm_32(reg::xmm1, reg::xmm0); + else + builder.div_xmm(reg::xmm1, reg::xmm0); + } + else + { + reg_extend(reg::rax, reg::rax, *arg1_type); + reg_extend(reg::rbx, reg::rbx, *arg1_type); + if (types::is_unsigned_integer_type(*arg1_type)) + { + builder.xor_(reg::rdx, reg::rdx); + builder.udiv(reg::rbx); + } + else + { + builder.cqo(); + builder.idiv(reg::rbx); + } + } + break; + case ast::binary_operation_type::remainder: + reg_extend(reg::rax, reg::rax, *arg1_type); + reg_extend(reg::rbx, reg::rbx, *arg1_type); + if (types::is_unsigned_integer_type(*arg1_type)) + { + builder.xor_(reg::rdx, reg::rdx); + builder.udiv(reg::rbx); + builder.mov(reg::rdx, reg::rax); + } + else + { + builder.cqo(); + builder.idiv(reg::rbx); + builder.mov(reg::rdx, reg::rax); + } + break; + case ast::binary_operation_type::binary_and: + builder.and_(reg::rbx, reg::rax); + break; + case ast::binary_operation_type::logical_and: + throw std::runtime_error("Short-circuiting operators must have been unwrapped in IR compiler"); + case ast::binary_operation_type::binary_or: + builder.or_(reg::rbx, reg::rax); + break; + case ast::binary_operation_type::logical_or: + throw std::runtime_error("Short-circuiting operators must have been unwrapped in IR compiler"); + case ast::binary_operation_type::logical_xor: + builder.xor_(reg::rbx, reg::rax); + break; + case ast::binary_operation_type::left_shift: + builder.mov(reg::rbx, reg::rcx); + builder.shl(reg::rax); + break; + case ast::binary_operation_type::right_shift: + reg_extend(reg::rax, reg::rax, *arg1_type); + reg_extend(reg::rbx, reg::rcx, *arg1_type); + if (types::is_unsigned_integer_type(*arg1_type)) + builder.shr(reg::rax); + else + builder.sar(reg::rax); + break; + case ast::binary_operation_type::equals: + if (is_fp) + { + if (arg_size <= 4) + builder.cmp_xmm_32(reg::xmm0, reg::xmm1); + else + builder.cmp_xmm(reg::xmm0, reg::xmm1); + } + else + { + reg_extend(reg::rax, reg::rax, *arg1_type); + reg_extend(reg::rbx, reg::rbx, *arg1_type); + builder.cmp(reg::rax, reg::rbx); + } + builder.mov(0, reg::rax); + builder.mov(-1, reg::rbx); + builder.cmovz(reg::rbx, reg::rax); + break; + case ast::binary_operation_type::not_equals: + if (is_fp) + { + if (arg_size <= 4) + builder.cmp_xmm_32(reg::xmm0, reg::xmm1); + else + builder.cmp_xmm(reg::xmm0, reg::xmm1); + } + else + { + reg_extend(reg::rax, reg::rax, *arg1_type); + reg_extend(reg::rbx, reg::rbx, *arg1_type); + builder.cmp(reg::rax, reg::rbx); + } + builder.mov(0, reg::rax); + builder.mov(-1, reg::rbx); + builder.cmovnz(reg::rbx, reg::rax); + break; + case ast::binary_operation_type::less: + if (is_fp) + { + if (arg_size <= 4) + builder.cmp_xmm_32(reg::xmm0, reg::xmm1); + else + builder.cmp_xmm(reg::xmm0, reg::xmm1); + } + else + { + reg_extend(reg::rax, reg::rax, *arg1_type); + reg_extend(reg::rbx, reg::rbx, *arg1_type); + builder.cmp(reg::rax, reg::rbx); + } + builder.mov(0, reg::rax); + builder.mov(-1, reg::rbx); + if (is_fp || types::is_bool_type(*arg1_type) || types::is_unsigned_integer_type(*arg1_type)) + builder.cmovb(reg::rbx, reg::rax); + else + builder.cmovl(reg::rbx, reg::rax); + break; + case ast::binary_operation_type::greater: + if (is_fp) + { + if (arg_size <= 4) + builder.cmp_xmm_32(reg::xmm1, reg::xmm0); + else + builder.cmp_xmm(reg::xmm1, reg::xmm0); + } + else + { + reg_extend(reg::rax, reg::rax, *arg1_type); + reg_extend(reg::rbx, reg::rbx, *arg1_type); + builder.cmp(reg::rbx, reg::rax); + } + builder.mov(0, reg::rax); + builder.mov(-1, reg::rbx); + if (is_fp || types::is_bool_type(*arg1_type) || types::is_unsigned_integer_type(*arg1_type)) + builder.cmovb(reg::rbx, reg::rax); + else + builder.cmovl(reg::rbx, reg::rax); + break; + case ast::binary_operation_type::less_equals: + if (is_fp) + { + if (arg_size <= 4) + builder.cmp_xmm_32(reg::xmm1, reg::xmm0); + else + builder.cmp_xmm(reg::xmm1, reg::xmm0); + } + else + { + reg_extend(reg::rax, reg::rax, *arg1_type); + reg_extend(reg::rbx, reg::rbx, *arg1_type); + builder.cmp(reg::rbx, reg::rax); + } + builder.mov(0, reg::rax); + builder.mov(-1, reg::rbx); + if (is_fp || types::is_bool_type(*arg1_type) || types::is_unsigned_integer_type(*arg1_type)) + builder.cmovnb(reg::rbx, reg::rax); + else + builder.cmovnl(reg::rbx, reg::rax); + break; + case ast::binary_operation_type::greater_equals: + if (is_fp) + { + if (arg_size <= 4) + builder.cmp_xmm_32(reg::xmm0, reg::xmm1); + else + builder.cmp_xmm(reg::xmm0, reg::xmm1); + } + else + { + reg_extend(reg::rax, reg::rax, *arg1_type); + reg_extend(reg::rbx, reg::rbx, *arg1_type); + builder.cmp(reg::rax, reg::rbx); + } + builder.mov(0, reg::rax); + builder.mov(-1, reg::rbx); + if (is_fp || types::is_bool_type(*arg1_type) || types::is_unsigned_integer_type(*arg1_type)) + builder.cmovnb(reg::rbx, reg::rax); + else + builder.cmovnl(reg::rbx, reg::rax); + break; + } + + if (result_is_fp) + store_xmm(it, reg::xmm0, arg_size); + else + store(it, reg::rax); + } + + void apply(ir::node_ref it, ir::cast_operation const & node, types::type_ptr const & type) + { + auto src_type = node.arg1->inferred_type; + auto dst_type = node.target_type; + + auto u64_type = types::primitive_type{types::u64_type{}}; + + if (false + || (types::is_builtin_type(*src_type) && types::equal(*src_type, *dst_type)) + || (types::is_pointer_type(*src_type) && types::is_pointer_type(*dst_type)) + || (types::equal(*src_type, u64_type) && types::is_pointer_type(*dst_type)) + || (types::equal(*dst_type, u64_type) && types::is_pointer_type(*src_type)) + ) + { + load(node.arg1, reg::rax); + store(it, reg::rax); + return; + } + + if (auto array_type = std::get_if(src_type.get())) + { + if (auto pointer_type = std::get_if(dst_type.get())) + { + if (types::equal(*array_type->element_type, *pointer_type->referenced_type)) + { + auto arg1_address = node_address(node.arg1); + if (arg1_address.base) + builder.lea(*arg1_address.base, arg1_address.offset, reg::rax); + else + builder.lea_rip_prev(arg1_address.offset, reg::rax); + store(it, reg::rax); + return; + } + } + } + + if (types::is_numeric_type(*src_type) && types::is_numeric_type(*dst_type)) + { + if (types::is_integer_type(*src_type)) + { + load(node.arg1, reg::rax); + if (types::is_integer_type(*dst_type)) + { + reg_extend(reg::rax, reg::rax, *src_type); + } + else if (types::is_floating_point_type(*dst_type)) + { + if (types::is_unsigned_integer_type(*src_type) && ast::type_size(*src_type) == 8) + throw std::runtime_error("64-bit unsigned to floating-point conversion is not implemented"); + + reg_extend(reg::rax, reg::rax, *src_type); + + auto dst_size = ast::type_size(*dst_type); + if (dst_size <= 4) + builder.gpr_to_xmm_32(reg::rax, reg::xmm0); + else + builder.gpr_to_xmm(reg::rax, reg::xmm0); + } + } + else if (types::is_floating_point_type(*src_type)) + { + if (types::is_integer_type(*dst_type)) + { + if (types::is_unsigned_integer_type(*dst_type) && ast::type_size(*dst_type) == 8) + throw std::runtime_error("Floating-point to 64-bit unsigned conversion is not implemented"); + + auto src_size = ast::type_size(*src_type); + if (src_size <= 4) + builder.xmm_32_to_gpr(reg::xmm0, reg::rax); + else + builder.xmm_to_gpr(reg::xmm0, reg::rax); + } + else if (types::is_floating_point_type(*dst_type)) + { + auto src_size = ast::type_size(*src_type); + auto dst_size = ast::type_size(*dst_type); + + if (src_size <= 4 && dst_size == 8) + builder.xmm_32_to_64(reg::xmm0, reg::xmm0); + else if (src_size == 8 && dst_size <= 4) + builder.xmm_64_to_32(reg::xmm0, reg::xmm0); + } + } + + if (types::is_integer_type(*dst_type)) + { + store(it, reg::rax); + } + else if (types::is_floating_point_type(*dst_type)) + { + store_xmm(it, reg::xmm0, ast::type_size(*dst_type)); + } + + return; + } + + throw std::runtime_error("Unknown types for cast instruction"); + } + + void apply(ir::node_ref, ir::argument const &, types::type_ptr const &) + { + // Nothing to do: arguments already pushed on stack in function preamble + } + + void apply(ir::node_ref it, ir::instruction_address const & node, types::type_ptr const & type) + { + lcontext.node_resolve.emplace_back(pcontext.storage.size(), node.target); + builder.lea_rip(0, reg::rax); + store(it, reg::rax); + } + + void apply(ir::node_ref it, ir::extern_symbol const & node, types::type_ptr const & type) + { + builder.lea_rip(lcontext.extern_symbols[node.name] - (std::int32_t)pcontext.storage.size(), reg::rax); + store(it, reg::rax); + } + + void apply(ir::node_ref, ir::assignment const & node, types::type_ptr const & type) + { + auto src_address = node_address(node.rhs); + + auto dst_type = node.lhs->inferred_type; + auto dst_address = node_address(node.lhs); + for (auto field_id : node.path) + { + if (auto struct_type = std::get_if(dst_type.get())) + { + auto struct_node = struct_type->node; + dst_type = struct_node->fields[field_id].inferred_type; + dst_address.offset += struct_node->fields[field_id].layout.offset; + } + else if (auto array_type = std::get_if(dst_type.get())) + { + dst_type = array_type->element_type; + dst_address.offset += field_id * ast::type_size(*array_type->element_type); + } + else + throw std::runtime_error("Unknown object type for field assignment"); + } + + copy_memory(src_address, dst_address, ast::type_size(*dst_type)); + } + + void apply(ir::node_ref, ir::jump const & node, types::type_ptr const & type) + { + lcontext.jump_resolve.push_back({(std::int32_t)pcontext.storage.size(), node.target}); + builder.jump(0); + } + + void apply(ir::node_ref, ir::jump_if_zero const & node, types::type_ptr const & type) + { + load(node.condition, reg::rax); + builder.test(reg::rax, reg::rax); + lcontext.cjump_resolve.push_back({(std::int32_t)pcontext.storage.size(), node.target}); + builder.jump_if_zero(0); + } + + void apply(ir::node_ref, ir::jump_if_nonzero const & node, types::type_ptr const & type) + { + load(node.condition, reg::rax); + builder.test(reg::rax, reg::rax); + lcontext.cjump_resolve.push_back({(std::int32_t)pcontext.storage.size(), node.target}); + builder.jump_if_nonzero(0); + } + + void apply(ir::node_ref, ir::call const & node, types::type_ptr const & type) + { + throw std::runtime_error("Not implemented"); + } + + void apply(ir::node_ref, ir::call_pointer const & node, types::type_ptr const & type) + { + throw std::runtime_error("Not implemented"); + } + + void apply(ir::node_ref, ir::return_value const & node, types::type_ptr const & type) + { + if (node.value) + { + auto type = (*node.value)->inferred_type; + auto size = ast::type_size(*type); + auto struct_type = std::get_if(type.get()); + auto array_type = std::get_if(type.get()); + if (size == 0) + {} + else if (struct_type || array_type) + { + throw std::runtime_error("Not implemented"); + } + else if (types::is_integer_like_type(*type)) + load(*node.value, reg::rax); + else if (types::is_floating_point_type(*type)) + load_xmm(*node.value, reg::xmm0, ast::type_size(*type)); + else + throw std::runtime_error("Unsupported return value type"); + } + if (stack_size > 0) + { + builder.add_imm(reg::rsp, stack_size); + if (lcontext.use_frame_pointer) + builder.pop(reg::rbp); + } + builder.ret(); + } + + void compile(ast::function_definition const * function_definition, ir::node_ref begin, ir::node_ref end) + { + auto result_type = function_definition->inferred_result_type; + + if (lcontext.use_frame_pointer) + { + builder.push(reg::rbp); + builder.mov(reg::rsp, reg::rbp); + } + + stack_size = 0; + + for (auto const & argument : function_definition->arguments) + { + auto size = ast::type_size(*argument.inferred_type); + // Ensure max alignment for simplicity + stack_size += ((size + 7) / 8) * 8; + argument_position.push_back(stack_size); + } + + for (auto it = begin; it != end; ++it) + { + if (auto argument = std::get_if(&it->instruction)) + { + stack_position[it] = argument_position[argument->index]; + } + else if (std::holds_alternative(it->instruction)) + { + // stack position doesn't make sense for globals + } + else if (ir::is_value_instruction(it->instruction)) + { + auto size = ast::type_size(*it->inferred_type); + if (size > 0) + stack_size += ((size + 7) / 8) * 8; + stack_position[it] = stack_size; + } + } + + if (!std::holds_alternative(begin->instruction)) + throw std::runtime_error("First IR node of a function must be a label"); + + auto it = begin; + + lcontext.nodes[it] = pcontext.storage.size(); + if (stack_size > 0) + builder.sub_imm(reg::rsp, stack_size); + + // TODO: handle the case when there weren't enough registers + + std::uint8_t reg_index = 0; + std::uint8_t fp_reg = 0; + for (std::size_t i = 0; i < function_definition->arguments.size(); ++i) + { + auto const & argument = function_definition->arguments[i]; + auto size = ast::type_size(*argument.inferred_type); + auto struct_type = std::get_if(argument.inferred_type.get()); + auto array_type = std::get_if(argument.inferred_type.get()); + if (size == 0) continue; + if (struct_type || array_type) + { + throw std::runtime_error("Not implemented"); + } + else if (types::is_integer_like_type(*argument.inferred_type)) + { + builder.mov_write(integer_arg_reg[reg_index++], reg::rsp, stack_size - argument_position[i]); + } + else if (types::is_floating_point_type(*argument.inferred_type)) + { + auto const size = ast::type_size(*argument.inferred_type); + if (size == 4) + builder.mov_write_xmm_32((reg)(fp_reg++), reg::rsp, stack_size - argument_position[i]); + else if (size == 8) + builder.mov_write_xmm((reg)(fp_reg++), reg::rsp, stack_size - argument_position[i]); + else + throw std::runtime_error("Bad type size for floating-point argument"); + } + else + throw std::runtime_error("Unknown argument type"); + } + ++it; + + for (; it != end; ++it) + { + // Globals are added in a separate pass before code + if (lcontext.nodes.contains(it)) + continue; + + // Uncomment to debug per-node instruction generation: + // builder.nop(); + + lcontext.nodes[it] = pcontext.storage.size(); + std::visit([&](auto const & instruction){ apply(it, instruction, it->inferred_type); }, it->instruction); + } + } + + private: + + void reg_extend(reg reg_src, reg reg_dst, types::type const & type) + { + auto const size = ast::type_size(type); + if (types::is_unsigned_integer_type(type)) + { + if (size == 1) + builder.movzx_8(reg_src, reg_dst); + else if (size == 2) + builder.movzx_16(reg_src, reg_dst); + } + else if (types::is_signed_integer_type(type)) + { + if (size == 1) + builder.movsx_8(reg_src, reg_dst); + else if (size == 2) + builder.movsx_16(reg_src, reg_dst); + else if (size == 4) + builder.movsx_32(reg_src, reg_dst); + } + else if (types::is_bool_type(type)) + { + builder.movzx_8(reg_src, reg_dst); + } + } + + value_address node_address(ir::node_ref it) + { + if (std::holds_alternative(it->instruction)) + return {.base = std::nullopt, .offset = lcontext.nodes.at(it) - static_cast(builder.code.size())}; + else + return {.base = reg::rsp, .offset = stack_size - stack_position.at(it)}; + } + + void load(ir::node_ref it, reg reg_dst) + { + auto const address = node_address(it); + if (address.base) + builder.mov_read(*address.base, address.offset, reg_dst); + else + builder.mov_read_rip_prev(address.offset, reg_dst); + } + + void store(ir::node_ref it, reg reg_src) + { + auto const address = node_address(it); + if (address.base) + builder.mov_write(reg_src, *address.base, address.offset); + else + builder.mov_write_rip_prev(reg_src, address.offset); + } + + void load_xmm(ir::node_ref it, reg reg_dst, std::uint8_t size) + { + if (size != 4 && size != 8) + throw std::runtime_error("Bad type size for load_xmm"); + + auto const address = node_address(it); + if (address.base) + { + if (size == 4) + builder.mov_read_xmm_32(*address.base, address.offset, reg_dst); + else + builder.mov_read_xmm(*address.base, address.offset, reg_dst); + } + else + throw std::runtime_error("RIP-relative XMM read is not supported"); + } + + void store_xmm(ir::node_ref it, reg reg_src, std::uint8_t size) + { + if (size != 4 && size != 8) + throw std::runtime_error("Bad type size for store_xmm"); + + auto const address = node_address(it); + if (address.base) + { + if (size == 4) + builder.mov_write_xmm_32(reg_src, *address.base, address.offset); + else + builder.mov_write_xmm(reg_src, *address.base, address.offset); + } + else + throw std::runtime_error("RIP-relative XMM write is not supported"); + } + + void copy_memory(value_address src, value_address dst, std::size_t size) + { + auto const storage_size_at_start = (std::int32_t)pcontext.storage.size(); + + reg reg_src; + if (src.base) + reg_src = *src.base; + else + { + reg_src = find_free_reg(dst.base); + builder.lea_rip_prev(0, reg_src); + } + + reg reg_dst; + if (dst.base) + reg_dst = *dst.base; + else + { + reg_dst = find_free_reg(reg_src); + builder.lea_rip_prev(storage_size_at_start - (std::int32_t)pcontext.storage.size(), reg_dst); + } + + std::int32_t offset = 0; + + auto copy_reg = find_free_reg(reg_src, reg_dst); + + while (size > 0) + { + auto check_step = [&](std::size_t step) + { + return size >= step; + }; + + if (check_step(8)) + { + builder.mov_read(reg_src, src.offset + offset, copy_reg); + builder.mov_write(copy_reg, reg_dst, dst.offset + offset); + size -= 8; + offset += 8; + } + else if (check_step(4)) + { + builder.mov_read_32(reg_src, src.offset + offset, copy_reg); + builder.mov_write_32(copy_reg, reg_dst, dst.offset + offset); + size -= 4; + offset += 4; + } + else if (check_step(2)) + { + builder.mov_read_16(reg_src, src.offset + offset, copy_reg); + builder.mov_write_16(copy_reg, reg_dst, dst.offset + offset); + size -= 2; + offset += 2; + } + else + { + builder.mov_read_8(reg_src, src.offset + offset, copy_reg); + builder.mov_write_8(copy_reg, reg_dst, dst.offset + offset); + size -= 1; + offset += 1; + } + } + } + }; + + } + + void compile(program_context & pcontext, ir::module_context const & mcontext) + { + local_context lcontext; + + auto data_begin = pcontext.storage.align(); + { + populate_globals_visitor visitor{.storage = pcontext.storage.storage, .lcontext = lcontext}; + for (auto it = mcontext.nodes->begin(); it != mcontext.nodes->end(); ++it) + std::visit([&](auto const & instruction){ visitor.apply(it, instruction, it->inferred_type); }, it->instruction); + } + +#ifndef NDEBUG + // Force the data page to be allocated + // Helps with debugging in QtCreator (which pokes ~16 bytes before the code + // page and can hit unmapped memory) + if (data_begin == pcontext.storage.size()) + pcontext.storage.storage.push_back(0); +#endif + + auto data_end = pcontext.storage.align(); + if (data_begin != data_end) + pcontext.storage.data.push_back({data_begin, data_end}); + + auto code_begin = data_end; + + { + populate_const_data_visitor visitor{pcontext, lcontext}; + for (auto it = mcontext.nodes->begin(); it != mcontext.nodes->end(); ++it) + std::visit([&](auto const & instruction){ visitor.apply(instruction, it->inferred_type); }, it->instruction); + } + + instruction_builder builder{pcontext.storage.storage}; + + for (auto const & symbol : mcontext.symbols) + { + pcontext.symbols[symbol.first] = pcontext.storage.size(); + compile_visitor visitor{pcontext, mcontext, lcontext, builder}; + visitor.compile(symbol.first, symbol.second.begin, symbol.second.end); + } + + pcontext.entry_point = lcontext.nodes.at(mcontext.entry_point); + + for (auto const & resolve : lcontext.jump_resolve) + builder.jump_inject_prev(pcontext.storage.storage.data() + resolve.offset, lcontext.nodes.at(resolve.target) - resolve.offset); + + for (auto const & resolve : lcontext.cjump_resolve) + builder.cjump_inject_prev(pcontext.storage.storage.data() + resolve.offset, lcontext.nodes.at(resolve.target) - resolve.offset); + + for (auto const & resolve : lcontext.node_resolve) + builder.lea_rip_inject_prev(pcontext.storage.storage.data() + resolve.offset, lcontext.nodes.at(resolve.target) - resolve.offset); + + auto code_end = pcontext.storage.align(); + pcontext.storage.code.push_back({code_begin, code_end}); + } + +} diff --git a/libs/jit/source/arch/linux_x86_64/instruction_builder.cpp b/libs/jit/source/arch/linux_x86_64/instruction_builder.cpp new file mode 100644 index 0000000..8546959 --- /dev/null +++ b/libs/jit/source/arch/linux_x86_64/instruction_builder.cpp @@ -0,0 +1,609 @@ +#include +#include + +namespace pslang::jit::linux_x86_64 +{ + + namespace + { + + // Construct the REX prefix: + // W (0/1) - enable 64-bit operand + // R (0/1) - extend ModR/M reg field + // X (0/1) - extend the SIB index field + // B (0/1) - extend the base or opcode register field + std::uint8_t rex(std::uint8_t W, std::uint8_t R, std::uint8_t X, std::uint8_t B) + { + check_bits(W, 1, "REX prefix W"); + check_bits(R, 1, "REX prefix R"); + check_bits(X, 1, "REX prefix X"); + check_bits(B, 1, "REX prefix B"); + return 0x40u | (W << 3) | (R << 2) | (X << 1) | B; + } + + // Same, but using `reg` enum instead - passes the highest (#3) bit of + // the register number to rex() + std::uint8_t rex(std::uint8_t W, reg R, reg X, reg B) + { + return rex(W, std::uint8_t(R) >> 3, std::uint8_t(X) >> 3, std::uint8_t(B) >> 3); + } + + // Memory + static constexpr std::uint8_t MOD_MEM = 0; + // Memory + 8-bit offset + static constexpr std::uint8_t MOD_MEM8 = 1; + // Memory + 32-bit offset + static constexpr std::uint8_t MOD_MEM32 = 2; + // Register + static constexpr std::uint8_t MOD_REG = 3; + + std::uint8_t modrm(std::uint8_t mod, std::uint8_t reg, std::uint8_t rm) + { + check_bits(mod, 2, "ModR/M mod"); + check_bits(reg, 3, "ModR/M reg"); + check_bits(rm, 3, "ModR/M rm"); + return (mod << 6) | (reg << 3) | rm; + } + + std::uint8_t modrm(std::uint8_t mod, reg reg, std::uint8_t rm) + { + return modrm(mod, (std::uint8_t)(reg) & 0b111, rm); + } + + std::uint8_t modrm(std::uint8_t mod, std::uint8_t reg_, reg rm) + { + return modrm(mod, reg_, (std::uint8_t)(rm) & 0b111); + } + + std::uint8_t modrm(std::uint8_t mod, reg reg_, reg rm) + { + return modrm(mod, (std::uint8_t)(reg_) & 0b111, (std::uint8_t)(rm) & 0b111); + } + + static constexpr std::uint8_t SIB_NOINDEX = 4; + + std::uint8_t sib(std::uint8_t scale, std::uint8_t index, std::uint8_t base) + { + check_bits(scale, 2, "SIB scale"); + check_bits(index, 3, "SIB index"); + check_bits(base, 3, "SIB base"); + return (scale << 6) | (index << 3) | base; + } + + std::uint8_t sib(std::uint8_t scale, reg reg_, std::uint8_t base) + { + return sib(scale, (std::uint8_t)(reg_) & 0b111, base); + } + + std::uint8_t sib(std::uint8_t scale, std::uint8_t reg_, reg base) + { + return sib(scale, reg_, (std::uint8_t)(base) & 0b111); + } + + std::uint8_t sib(std::uint8_t scale, reg reg_, reg base) + { + return sib(scale, (std::uint8_t)(reg_) & 0b111, (std::uint8_t)(base) & 0b111); + } + + std::uint8_t opcode_add_reg(std::uint8_t opcode, reg reg) + { + return opcode + ((std::uint8_t)(reg) & 0b111); + } + + bool need_sib(reg reg) + { + return ((std::uint8_t)(reg) & 0b111) == 4; + } + + std::uint8_t vex(std::uint8_t R, std::uint8_t X, std::uint8_t B, std::uint8_t MMMMM) + { + check_bits(R, 1, "VEX prefix R"); + check_bits(X, 1, "VEX prefix X"); + check_bits(B, 1, "VEX prefix B"); + check_bits(MMMMM, 1, "VEX prefix m-mmmm"); + return ((1 - R) << 7) | ((1 - X) << 6) | ((1 - B) << 5) | MMMMM; + } + + std::uint8_t vex(reg R, reg X, reg B, std::uint8_t MMMMM) + { + return rex(std::uint8_t(R) >> 3, std::uint8_t(X) >> 3, std::uint8_t(B) >> 3, MMMMM); + } + + } + + void instruction_builder::nop() + { + do_push(0x90_ub); + } + + void instruction_builder::ret(std::uint16_t count) + { + if (count == 0) + do_push(0xc3_ub); + else + do_push(0xc2_ub, count); + } + + void instruction_builder::add_imm(reg reg, std::int32_t value) + { + if (value >= -128 && value <= 127) + do_push(rex(1, {}, {}, reg), 0x83_ub, modrm(MOD_REG, 0, reg), (std::int8_t)value); + else + do_push(rex(1, {}, {}, reg), 0x81_ub, modrm(MOD_REG, 0, reg), value); + } + + void instruction_builder::sub_imm(reg reg, std::int32_t value) + { + if (value >= -128 && value <= 127) + do_push(rex(1, {}, {}, reg), 0x83_ub, modrm(MOD_REG, 5, reg), (std::int8_t)value); + else + do_push(rex(1, {}, {}, reg), 0x81_ub, modrm(MOD_REG, 5, reg), value); + } + + void instruction_builder::movzd(std::uint32_t value, reg reg_dst) + { + if (((std::uint8_t)(reg_dst) & 0b1000) != 0) + do_push(rex(0, {}, {}, reg_dst), opcode_add_reg(0xb8, reg_dst), value); + else + do_push(opcode_add_reg(0xb8, reg_dst), value); + } + + void instruction_builder::movsd(std::int32_t value, reg reg_dst) + { + do_push(rex(1, {}, {}, reg_dst), 0xc7_ub, modrm(MOD_REG, 0, reg_dst), value); + } + + void instruction_builder::movzx_8(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x0f_ub, 0xb6_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::movzx_16(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x0f_ub, 0xb7_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::movsx_8(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x0f_ub, 0xbe_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::movsx_16(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x0f_ub, 0xbf_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::movsx_32(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x63_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::mov(std::uint64_t value, reg reg_dst) + { + do_push(rex(1, {}, {}, reg_dst), opcode_add_reg(0xb8, reg_dst), value); + } + + void instruction_builder::mov(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x8b_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::mov_gpr_to_xmm(reg reg_src, reg reg_dst) + { + do_push(0x66_ub, rex(1, reg_dst, {}, reg_src), 0x0f_ub, 0x6e_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::mov_gpr_to_xmm_32(reg reg_src, reg reg_dst) + { + do_push(0x66_ub, rex(0, reg_dst, {}, reg_src), 0x0f_ub, 0x6e_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::mov_read(reg reg_addr, std::int32_t offset, reg reg_dst) + { + if (need_sib(reg_addr)) + do_push(rex(1, reg_dst, {}, reg_addr), 0x8b_ub, modrm(MOD_MEM32, reg_dst, reg_addr), sib(0, SIB_NOINDEX, reg_addr), offset); + else + do_push(rex(1, reg_dst, {}, reg_addr), 0x8b_ub, modrm(MOD_MEM32, reg_dst, reg_addr), offset); + } + + void instruction_builder::mov_read_32(reg reg_addr, std::int32_t offset, reg reg_dst) + { + if (need_sib(reg_addr)) + do_push(rex(0, reg_dst, {}, reg_addr), 0x8b_ub, modrm(MOD_MEM32, reg_dst, reg_addr), sib(0, SIB_NOINDEX, reg_addr), offset); + else + do_push(rex(0, reg_dst, {}, reg_addr), 0x8b_ub, modrm(MOD_MEM32, reg_dst, reg_addr), offset); + } + + void instruction_builder::mov_read_16(reg reg_addr, std::int32_t offset, reg reg_dst) + { + if (need_sib(reg_addr)) + do_push(0x66_ub, rex(0, reg_dst, {}, reg_addr), 0x8b_ub, modrm(MOD_MEM32, reg_dst, reg_addr), sib(0, SIB_NOINDEX, reg_addr), offset); + else + do_push(0x66_ub, rex(0, reg_dst, {}, reg_addr), 0x8b_ub, modrm(MOD_MEM32, reg_dst, reg_addr), offset); + } + + void instruction_builder::mov_read_8(reg reg_addr, std::int32_t offset, reg reg_dst) + { + if (need_sib(reg_addr)) + do_push(rex(0, reg_dst, {}, reg_addr), 0x8a_ub, modrm(MOD_MEM32, reg_dst, reg_addr), sib(0, SIB_NOINDEX, reg_addr), offset); + else + do_push(rex(0, reg_dst, {}, reg_addr), 0x8a_ub, modrm(MOD_MEM32, reg_dst, reg_addr), offset); + } + + void instruction_builder::mov_read_rip(std::int32_t offset, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, {}), 0x8b_ub, modrm(0, reg_dst, 0b101), offset); + } + + void instruction_builder::mov_read_rip_prev(std::int32_t offset, reg reg_dst) + { + mov_read_rip(offset - 7, reg_dst); + } + + void instruction_builder::mov_write(reg reg_src, reg reg_addr, std::int32_t offset) + { + if (need_sib(reg_addr)) + do_push(rex(1, reg_src, {}, reg_addr), 0x89_ub, modrm(MOD_MEM32, reg_src, reg_addr), sib(0, SIB_NOINDEX, reg_addr), offset); + else + do_push(rex(1, reg_src, {}, reg_addr), 0x89_ub, modrm(MOD_MEM32, reg_src, reg_addr), offset); + } + + void instruction_builder::mov_write_32(reg reg_src, reg reg_addr, std::int32_t offset) + { + if (need_sib(reg_addr)) + do_push(rex(0, reg_src, {}, reg_addr), 0x89_ub, modrm(MOD_MEM32, reg_src, reg_addr), sib(0, SIB_NOINDEX, reg_addr), offset); + else + do_push(rex(0, reg_src, {}, reg_addr), 0x89_ub, modrm(MOD_MEM32, reg_src, reg_addr), offset); + } + + void instruction_builder::mov_write_16(reg reg_src, reg reg_addr, std::int32_t offset) + { + if (need_sib(reg_addr)) + do_push(0x66_ub, rex(0, reg_src, {}, reg_addr), 0x89_ub, modrm(MOD_MEM32, reg_src, reg_addr), sib(0, SIB_NOINDEX, reg_addr), offset); + else + do_push(0x66_ub, rex(0, reg_src, {}, reg_addr), 0x89_ub, modrm(MOD_MEM32, reg_src, reg_addr), offset); + } + + void instruction_builder::mov_write_8(reg reg_src, reg reg_addr, std::int32_t offset) + { + if (need_sib(reg_addr)) + do_push(rex(0, reg_src, {}, reg_addr), 0x88_ub, modrm(MOD_MEM32, reg_src, reg_addr), sib(0, SIB_NOINDEX, reg_addr), offset); + else + do_push(rex(0, reg_src, {}, reg_addr), 0x88_ub, modrm(MOD_MEM32, reg_src, reg_addr), offset); + } + + void instruction_builder::mov_write_rip(reg reg_src, std::int32_t offset) + { + do_push(rex(1, reg_src, {}, {}), 0x89_ub, modrm(0, reg_src, 0b101), offset); + } + + void instruction_builder::mov_write_rip_prev(reg reg_src, std::int32_t offset) + { + mov_write_rip(reg_src, offset - 7); + } + + void instruction_builder::mov_read_xmm(reg reg_addr, std::int32_t offset, reg reg_dst) + { + if (need_sib(reg_addr)) + do_push(0x66_ub, rex(1, reg_dst, {}, reg_addr), 0x0f_ub, 0x6e_ub, modrm(MOD_MEM32, reg_dst, reg_addr), sib(0, SIB_NOINDEX, reg_addr), offset); + else + do_push(0x66_ub, rex(1, reg_dst, {}, reg_addr), 0x0f_ub, 0x6e_ub, modrm(MOD_MEM32, reg_dst, reg_addr), offset); + } + + void instruction_builder::mov_write_xmm(reg reg_src, reg reg_addr, std::int32_t offset) + { + if (need_sib(reg_addr)) + do_push(0x66_ub, rex(0, reg_src, {}, reg_addr), 0x0f_ub, 0xd6_ub, modrm(MOD_MEM32, reg_src, reg_addr), sib(0, SIB_NOINDEX, reg_addr), offset); + else + do_push(0x66_ub, rex(0, reg_src, {}, reg_addr), 0x0f_ub, 0xd6_ub, modrm(MOD_MEM32, reg_src, reg_addr), offset); + } + + void instruction_builder::mov_read_xmm_32(reg reg_addr, std::int32_t offset, reg reg_dst) + { + if (need_sib(reg_addr)) + do_push(0xf3_ub, rex(0, reg_dst, {}, reg_addr), 0x0f_ub, 0x10_ub, modrm(MOD_MEM32, reg_dst, reg_addr), sib(0, SIB_NOINDEX, reg_addr), offset); + else + do_push(0xf3_ub, rex(0, reg_dst, {}, reg_addr), 0x0f_ub, 0x10_ub, modrm(MOD_MEM32, reg_dst, reg_addr), offset); + } + + void instruction_builder::mov_write_xmm_32(reg reg_src, reg reg_addr, std::int32_t offset) + { + if (need_sib(reg_addr)) + do_push(0xf3_ub, rex(0, reg_src, {}, reg_addr), 0x0f_ub, 0x11_ub, modrm(MOD_MEM32, reg_src, reg_addr), sib(0, SIB_NOINDEX, reg_addr), offset); + else + do_push(0xf3_ub, rex(0, reg_src, {}, reg_addr), 0x0f_ub, 0x11_ub, modrm(MOD_MEM32, reg_src, reg_addr), offset); + } + + void instruction_builder::lea(reg reg_addr, std::int32_t offset, reg reg_dst) + { + if (need_sib(reg_addr)) + do_push(rex(1, reg_dst, {}, reg_addr), 0x8d_ub, modrm(MOD_MEM32, reg_dst, reg_addr), sib(0, SIB_NOINDEX, reg_addr), offset); + else + do_push(rex(1, reg_dst, {}, reg_addr), 0x8d_ub, modrm(MOD_MEM32, reg_dst, reg_addr), offset); + } + + void instruction_builder::lea_rip(std::int32_t offset, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, {}), 0x8d_ub, modrm(0, reg_dst, 0b101), offset); + } + + void instruction_builder::lea_rip_prev(std::int32_t offset, reg reg_dst) + { + lea_rip(offset - 7, reg_dst); + } + + void instruction_builder::push(reg reg) + { + do_push(rex(0, {}, {}, reg), opcode_add_reg(0x50, reg)); + } + + void instruction_builder::pop(reg reg) + { + do_push(rex(0, {}, {}, reg), opcode_add_reg(0x58, reg)); + } + + void instruction_builder::neg(reg reg) + { + do_push(rex(1, {}, {}, reg), 0xf7_ub, modrm(MOD_REG, 3, reg)); + } + + void instruction_builder::not_(reg reg) + { + do_push(rex(1, {}, {}, reg), 0xf7_ub, modrm(MOD_REG, 2, reg)); + } + + void instruction_builder::test(reg reg1, reg reg2) + { + do_push(rex(1, reg1, {}, reg2), 0x85_ub, modrm(MOD_REG, reg1, reg2)); + } + + void instruction_builder::add(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x03_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::sub(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x2b_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::mul(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x0f_ub, 0xaf_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::udiv(reg reg_src) + { + do_push(rex(1, {}, {}, reg_src), 0xf7_ub, modrm(MOD_REG, 0b110, reg_src)); + } + + void instruction_builder::idiv(reg reg_src) + { + do_push(rex(1, {}, {}, reg_src), 0xf7_ub, modrm(MOD_REG, 0b111, reg_src)); + } + + void instruction_builder::cqo() + { + do_push(0x48_ub, 0x99_ub); + } + + void instruction_builder::and_(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x23_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::or_(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x0b_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::xor_(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x33_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::shl(reg reg_dst) + { + do_push(rex(1, {}, {}, reg_dst), 0xd3_ub, modrm(MOD_REG, 0b100, reg_dst)); + } + + void instruction_builder::shr(reg reg_dst) + { + do_push(rex(1, {}, {}, reg_dst), 0xd3_ub, modrm(MOD_REG, 0b101, reg_dst)); + } + + void instruction_builder::sar(reg reg_dst) + { + do_push(rex(1, {}, {}, reg_dst), 0xd3_ub, modrm(MOD_REG, 0b111, reg_dst)); + } + + void instruction_builder::cmp(reg reg1, reg reg2) + { + do_push(rex(1, reg1, {}, reg2), 0x3b_ub, modrm(MOD_REG, reg1, reg2)); + } + + void instruction_builder::cmovz(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x0f_ub, 0x44_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::cmovnz(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x0f_ub, 0x45_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::cmovb(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x0f_ub, 0x42_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::cmovnb(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x0f_ub, 0x43_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::cmovl(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x0f_ub, 0x4c_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::cmovnl(reg reg_src, reg reg_dst) + { + do_push(rex(1, reg_dst, {}, reg_src), 0x0f_ub, 0x4d_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::add_xmm(reg reg_src, reg reg_dst) + { + do_push(0xf2_ub, rex(0, reg_dst, {}, reg_src), 0x0f_ub, 0x58_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::add_xmm_32(reg reg_src, reg reg_dst) + { + do_push(0xf3_ub, rex(0, reg_dst, {}, reg_src), 0x0f_ub, 0x58_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::sub_xmm(reg reg_src, reg reg_dst) + { + do_push(0xf2_ub, rex(0, reg_dst, {}, reg_src), 0x0f_ub, 0x5c_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::sub_xmm_32(reg reg_src, reg reg_dst) + { + do_push(0xf3_ub, rex(0, reg_dst, {}, reg_src), 0x0f_ub, 0x5c_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::mul_xmm(reg reg_src, reg reg_dst) + { + do_push(0xf2_ub, rex(0, reg_dst, {}, reg_src), 0x0f_ub, 0x59_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::mul_xmm_32(reg reg_src, reg reg_dst) + { + do_push(0xf3_ub, rex(0, reg_dst, {}, reg_src), 0x0f_ub, 0x59_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::div_xmm(reg reg_src, reg reg_dst) + { + do_push(0xf2_ub, rex(0, reg_dst, {}, reg_src), 0x0f_ub, 0x5e_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::div_xmm_32(reg reg_src, reg reg_dst) + { + do_push(0xf3_ub, rex(0, reg_dst, {}, reg_src), 0x0f_ub, 0x5e_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::xor_xmm(reg reg_src, reg reg_dst) + { + do_push(0x66_ub, rex(0, reg_dst, {}, reg_src), 0x0f_ub, 0x57_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::xor_xmm_32(reg reg_src, reg reg_dst) + { + do_push(rex(0, reg_dst, {}, reg_src), 0x0f_ub, 0x57_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::cmp_xmm(reg reg1, reg reg2) + { + do_push(0x66_ub, rex(0, reg1, {}, reg2), 0x0f_ub, 0x2e_ub, modrm(MOD_REG, reg1, reg2)); + } + + void instruction_builder::cmp_xmm_32(reg reg1, reg reg2) + { + do_push(rex(0, reg1, {}, reg2), 0x0f_ub, 0x2e_ub, modrm(MOD_REG, reg1, reg2)); + } + + void instruction_builder::gpr_to_xmm(reg reg_src, reg reg_dst) + { + do_push(0xf2_ub, rex(1, reg_dst, {}, reg_src), 0x0f_ub, 0x2a_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::gpr_to_xmm_32(reg reg_src, reg reg_dst) + { + do_push(0xf3_ub, rex(1, reg_dst, {}, reg_src), 0x0f_ub, 0x2a_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::xmm_to_gpr(reg reg_src, reg reg_dst) + { + do_push(0xf2_ub, rex(1, reg_dst, {}, reg_src), 0x0f_ub, 0x2d_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::xmm_32_to_gpr(reg reg_src, reg reg_dst) + { + do_push(0xf3_ub, rex(1, reg_dst, {}, reg_src), 0x0f_ub, 0x2d_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::xmm_16_to_32(reg reg_src, reg reg_dst) + { + do_push(0xc4_ub, vex(reg_dst, {}, reg_src, 0b00010), 0b01111001_ub, 0x13_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::xmm_32_to_16(reg reg_src, reg reg_dst) + { + do_push(0xc4_ub, vex(reg_dst, {}, reg_src, 0b00011), std::uint8_t(1 | ~(std::uint8_t(reg_src) << 3)), 0x1d_ub, modrm(MOD_REG, reg_dst, 0)); + } + + void instruction_builder::xmm_32_to_64(reg reg_src, reg reg_dst) + { + do_push(0xf3_ub, rex(0, reg_dst, {}, reg_src), 0x0f_ub, 0x5a_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::xmm_64_to_32(reg reg_src, reg reg_dst) + { + do_push(0xf2_ub, rex(0, reg_dst, {}, reg_src), 0x0f_ub, 0x5a_ub, modrm(MOD_REG, reg_dst, reg_src)); + } + + void instruction_builder::jump(std::int32_t offset) + { + do_push(0xe9_ub, offset); + } + + void instruction_builder::jump_if_zero(std::int32_t offset) + { + do_push(0x0f_ub, 0x84_ub, offset); + } + + void instruction_builder::jump_if_nonzero(std::int32_t offset) + { + do_push(0x0f_ub, 0x85_ub, offset); + } + + void instruction_builder::jump_inject(std::uint8_t * opcode, std::int32_t offset) + { + auto src = (std::uint8_t const *)(&offset); + std::copy(src, src + 4, opcode + 1); + } + + void instruction_builder::jump_inject_prev(std::uint8_t * opcode, std::int32_t offset) + { + jump_inject(opcode, offset - 5); + } + + void instruction_builder::cjump_inject(std::uint8_t * opcode, std::int32_t offset) + { + auto src = (std::uint8_t const *)(&offset); + std::copy(src, src + 4, opcode + 2); + } + + void instruction_builder::cjump_inject_prev(std::uint8_t * opcode, std::int32_t offset) + { + cjump_inject(opcode, offset - 6); + } + + void instruction_builder::lea_rip_inject(std::uint8_t * opcode, std::int32_t offset) + { + auto src = (std::uint8_t const *)(&offset); + std::copy(src, src + 4, opcode + 3); + } + + void instruction_builder::lea_rip_inject_prev(std::uint8_t * opcode, std::int32_t offset) + { + lea_rip_inject(opcode, offset - 7); + } + + template + void instruction_builder::do_push(Args ... values) + { + push_bytes(code, values...); + } + +} diff --git a/libs/jit/source/jit.cpp b/libs/jit/source/jit.cpp index 7761588..b59e63d 100644 --- a/libs/jit/source/jit.cpp +++ b/libs/jit/source/jit.cpp @@ -1,5 +1,6 @@ #include #include +#include #include @@ -11,6 +12,15 @@ namespace pslang::jit switch (pcontext.abi.platform) { case platform::linux: + switch (pcontext.abi.isa) + { + case isa::x86_64: + linux_x86_64::compile(pcontext, mcontext); + break; + case isa::aarch64: + throw std::runtime_error("Linux aarch64 JIT compilation not supported"); + } + break; throw std::runtime_error("Linux JIT compilation not supported"); case platform::windows: throw std::runtime_error("Windows JIT compilation not supported"); diff --git a/libs/types/include/pslang/types/half_float.hpp b/libs/types/include/pslang/types/half_float.hpp index 8b22d30..8141a9a 100644 --- a/libs/types/include/pslang/types/half_float.hpp +++ b/libs/types/include/pslang/types/half_float.hpp @@ -7,7 +7,9 @@ namespace pslang::types { - // TODO: actual half-float operations? Maybe C++23 std::float16_t? + // TODO: actual 16-bit half-float + // C++23 std::float16_t doesn't cut it, because it isn't + // exposed if the host platform doesn't support float16 natively struct half_float { @@ -42,4 +44,4 @@ namespace pslang::types return {f1.repr / f2.repr}; } -} \ No newline at end of file +}