diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ceb9b51..dfe726d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,6 @@ name: ci.yml -on: - push: - branches: ['master', 'next'] - pull_request: - types: [ opened, synchronize, reopened ] - branches: ['master', 'next'] - +on: [ push, pull_request ] jobs: linux-x86: runs-on: ubuntu-latest @@ -27,6 +21,26 @@ jobs: - name: Test x86 run: cd build && ./test + linux-qemu-riscv: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: uraimo/run-on-arch-action@v3 + name: Run commands + with: + arch: riscv64 + distro: ubuntu24.04 + shell: /bin/sh + + install: | + apt-get update + apt-get install -y cmake g++ git python3 + + run: | + cmake -B build + cmake --build build + cd build && ./test + linux-aarch64: runs-on: ubuntu-24.04-arm steps: diff --git a/.gitignore b/.gitignore index de08f6d..2ae9df8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea -cmake-build-* \ No newline at end of file +cmake-build-* +docker-build-* \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index d3a6b16..379406f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.31) +cmake_minimum_required(VERSION 2.28...3.31) project(ASMIOV C CXX) include(FetchContent) @@ -7,24 +7,28 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Python3 COMPONENTS Interpreter) +option(USE_MOLD "Use mold linker if installed" ON) + # Use the mold linker automatically for GCC if available, # as it is faster, more modern, and produces better errors. -if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - find_program(MOLD_PROGRAM mold) - - if (MOLD_PROGRAM) - message(STATUS "The MOLD linker was found: ${MOLD_PROGRAM}") - set(CMAKE_EXE_LINKER_FLAGS "-fuse-ld=mold") - set(CMAKE_SHARED_LINKER_FLAGS "-fuse-ld=mold") - else() - message(STATUS "The MOLD linker was NOT found!") +if (USE_MOLD) + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + find_program(MOLD_PROGRAM mold) + + if (MOLD_PROGRAM) + message(STATUS "The MOLD linker was found: ${MOLD_PROGRAM}") + set(CMAKE_EXE_LINKER_FLAGS "-fuse-ld=mold") + set(CMAKE_SHARED_LINKER_FLAGS "-fuse-ld=mold") + else() + message(STATUS "The MOLD linker was NOT found!") + endif() endif() endif() FetchContent_Declare( vstl GIT_REPOSITORY https://github.com/magistermaks/lib-vstl - GIT_TAG c7f1069ea12fbe8087067841c491940fc3f6dc7f + GIT_TAG v3.5 ) FetchContent_MakeAvailable(vstl) @@ -32,6 +36,7 @@ FetchContent_MakeAvailable(vstl) set(ASMIO_WRITERS src/asmio/x86/writer.hpp src/asmio/aarch64/writer.hpp + src/asmio/riscv/writer.hpp ) set(ASMIO_BRIDGES "${ASMIO_WRITERS}") @@ -68,6 +73,7 @@ add_executable(test test/x86.cpp test/aarch64.cpp test/elf.cpp + test/riscv.cpp ) target_link_libraries(test PRIVATE asmiov) target_include_directories(test PRIVATE ${ASMIOV_INCLUDE_DIRS}) diff --git a/src/asmio/aarch64/argument/condition.hpp b/src/asmio/aarch64/argument/condition.hpp index 6848a5f..823a87d 100644 --- a/src/asmio/aarch64/argument/condition.hpp +++ b/src/asmio/aarch64/argument/condition.hpp @@ -5,24 +5,35 @@ namespace asmio::arm { enum struct Condition : uint8_t { - EQ = 0b0000, NE = 0b0001, - CS = 0b0010, CC = 0b0011, - MI = 0b0100, PL = 0b0101, - VS = 0b0110, VC = 0b0111, - HI = 0b1000, LS = 0b1001, - GE = 0b1010, LT = 0b1011, - GT = 0b1100, LE = 0b1101, + EQ = 0b0000, // equal + NE = 0b0001, // not equal + CS = 0b0010, // carry set + CC = 0b0011, // carry clear + MI = 0b0100, // minus + PL = 0b0101, // plus + VS = 0b0110, // overflow set + VC = 0b0111, // overflow clear + HI = 0b1000, // higher + LS = 0b1001, // lower or same + GE = 0b1010, // greater or equal + LT = 0b1011, // less than + GT = 0b1100, // greater than + LE = 0b1101, // less or equal + AL = 0b1110, // always + NV = 0b1111, // never, unused and treated the same as AL - // the NV condition code is unused and treated the same as AL - AL = 0b1110, NV = 0b1111, + GEU = CC, // greater or equal unsigned + LTU = CS, // less than unsigned + GTU = HI, // greater than unsigned + LEU = LS, // less or equal unsigned }; /** * Invert the condition as if it had been negated, - * always true condition can't be inverted as AArch64 dropped support for the 'never' condition code. + * the always true condition can't be inverted as there is no functional 'never' condition code. */ constexpr Condition invert(Condition condition) { - if (condition == Condition::AL) throw std::runtime_error {"The 'always' condition can't be inverted, as there is no 'never' condition!"}; + if (condition == Condition::AL) throw std::runtime_error {"The 'always' condition can't be inverted, as there is no functional 'never' condition!"}; return static_cast(static_cast(condition) ^ 1); } diff --git a/src/asmio/aarch64/argument/registry.hpp b/src/asmio/aarch64/argument/registry.hpp index fbedf2f..0b1bfa8 100644 --- a/src/asmio/aarch64/argument/registry.hpp +++ b/src/asmio/aarch64/argument/registry.hpp @@ -55,39 +55,40 @@ namespace asmio::arm { constexpr Registry SP {QWORD, 31, Registry::STACK}; constexpr Registry LR = X(30); constexpr Registry FP = X(29); + constexpr Registry XR = X(8); // general purpose QWORD registers - constexpr Registry X0 = X(0); - constexpr Registry X1 = X(1); - constexpr Registry X2 = X(2); - constexpr Registry X3 = X(3); - constexpr Registry X4 = X(4); - constexpr Registry X5 = X(5); - constexpr Registry X6 = X(6); - constexpr Registry X7 = X(7); - constexpr Registry X8 = X(8); - constexpr Registry X9 = X(9); - constexpr Registry X10 = X(10); - constexpr Registry X11 = X(11); - constexpr Registry X12 = X(12); - constexpr Registry X13 = X(13); - constexpr Registry X14 = X(14); - constexpr Registry X15 = X(15); - constexpr Registry X16 = X(16); - constexpr Registry X17 = X(17); - constexpr Registry X18 = X(18); - constexpr Registry X19 = X(19); - constexpr Registry X20 = X(20); - constexpr Registry X21 = X(21); - constexpr Registry X22 = X(22); - constexpr Registry X23 = X(23); - constexpr Registry X24 = X(24); - constexpr Registry X25 = X(25); - constexpr Registry X26 = X(26); - constexpr Registry X27 = X(27); - constexpr Registry X28 = X(28); - constexpr Registry X29 = X(29); - constexpr Registry X30 = X(30); + constexpr Registry X0 = X(0); // argument register + constexpr Registry X1 = X(1); // argument register + constexpr Registry X2 = X(2); // argument register + constexpr Registry X3 = X(3); // argument register + constexpr Registry X4 = X(4); // argument register + constexpr Registry X5 = X(5); // argument register + constexpr Registry X6 = X(6); // argument register + constexpr Registry X7 = X(7); // argument register + constexpr Registry X8 = X(8); // indirect return value pointer + constexpr Registry X9 = X(9); // temporary register + constexpr Registry X10 = X(10); // temporary register + constexpr Registry X11 = X(11); // temporary register + constexpr Registry X12 = X(12); // temporary register + constexpr Registry X13 = X(13); // temporary register + constexpr Registry X14 = X(14); // temporary register + constexpr Registry X15 = X(15); // temporary register + constexpr Registry X16 = X(16); // temporatry intra-procedure-call register + constexpr Registry X17 = X(17); // temporatry intra-procedure-call register + constexpr Registry X18 = X(18); // platform register + constexpr Registry X19 = X(19); // calee saved register + constexpr Registry X20 = X(20); // calee saved register + constexpr Registry X21 = X(21); // calee saved register + constexpr Registry X22 = X(22); // calee saved register + constexpr Registry X23 = X(23); // calee saved register + constexpr Registry X24 = X(24); // calee saved register + constexpr Registry X25 = X(25); // calee saved register + constexpr Registry X26 = X(26); // calee saved register + constexpr Registry X27 = X(27); // calee saved register + constexpr Registry X28 = X(28); // calee saved register + constexpr Registry X29 = X(29); // frame pointer + constexpr Registry X30 = X(30); // procedure link register // general purpose DWARD registers constexpr Registry W0 = W(0); diff --git a/src/asmio/elf/dwarf/abbrev.cpp b/src/asmio/elf/dwarf/abbrev.cpp index 8db699e..d6f114f 100644 --- a/src/asmio/elf/dwarf/abbrev.cpp +++ b/src/asmio/elf/dwarf/abbrev.cpp @@ -16,7 +16,7 @@ namespace asmio { } void DwarfAbbrevHeader::rehash(const DwarfFormatedAttributeEntry* attribute_array) { - hash = util::hash_djb2(reinterpret_cast(attribute_array), size); + hash = util::djb2(reinterpret_cast(attribute_array), size); hash ^= (static_cast(tag) * 31) ^ (children * 109); } diff --git a/src/asmio/elf/dwarf/abbrev.hpp b/src/asmio/elf/dwarf/abbrev.hpp index ea3a71e..5b02f9e 100644 --- a/src/asmio/elf/dwarf/abbrev.hpp +++ b/src/asmio/elf/dwarf/abbrev.hpp @@ -1,7 +1,7 @@ #pragma once #include #include -#include +#include #include #include #include diff --git a/src/asmio/elf/export.cpp b/src/asmio/elf/export.cpp index ed7f4de..1cd4748 100644 --- a/src/asmio/elf/export.cpp +++ b/src/asmio/elf/export.cpp @@ -77,7 +77,7 @@ namespace asmio { for (auto& linkage : symbols) { auto mapping = section_map[linkage.target.section]; ElfModel::Symbol* symbol = model.symbol(mapping.content, linkage.label.view(), ElfSymbolBinding::GLOBAL, ElfSymbolVisibility::DEFAULT, nullptr, 0, 0); - model.relocation(linkage.type.relocation, symbol, mapping.section, linkage.target.offset, linkage.addend); + model.relocation(linkage.type->relocation, symbol, mapping.section, linkage.target.offset, linkage.addend); } } diff --git a/src/asmio/elf/header.hpp b/src/asmio/elf/header.hpp index 1000c81..4eec67a 100644 --- a/src/asmio/elf/header.hpp +++ b/src/asmio/elf/header.hpp @@ -33,6 +33,10 @@ namespace asmio { #if ARCH_X86 NATIVE = X86_64, #endif + +#if ARCH_RISCV64 + NATIVE = RISCV, +#endif }; enum struct ElfClass : uint8_t { diff --git a/src/asmio/elf/object.cpp b/src/asmio/elf/object.cpp index 58c4c3d..c0bfa34 100644 --- a/src/asmio/elf/object.cpp +++ b/src/asmio/elf/object.cpp @@ -2,6 +2,7 @@ #include "object.hpp" #include +#include #include namespace asmio { diff --git a/src/asmio/external.hpp b/src/asmio/external.hpp index 17a895d..490b659 100644 --- a/src/asmio/external.hpp +++ b/src/asmio/external.hpp @@ -41,6 +41,33 @@ # define ARCH_AARCH64 false #endif -#if !(ARCH_X86 || ARCH_AARCH64) +#if defined(__riscv) +# if (__riscv_xlen == 64) +# define ARCH_RISCV64 true +# else +# define ARCH_RISCV64 false +# endif +#else +# define ARCH_RISCV64 false +#endif + +// platforms +#if defined(_WIN32) +# define PLATFORM_WINDOWS true +#else +# define PLATFORM_WINDOWS false +#endif + +#if defined(__linux__) || defined(__APPLE__) || defined(__unix__) +# define PLATFORM_UNIX true +#else +# define PLATFORM_UNIX false +#endif + +#if !(ARCH_X86 || ARCH_AARCH64 || ARCH_RISCV64) # error "Unsupported target architecture!" +#endif + +#if !(PLATFORM_WINDOWS || PLATFORM_UNIX) +# error "Unsupported target platform!" #endif \ No newline at end of file diff --git a/src/asmio/program/executable.cpp b/src/asmio/program/executable.cpp index 711ad19..1f992c7 100644 --- a/src/asmio/program/executable.cpp +++ b/src/asmio/program/executable.cpp @@ -2,6 +2,7 @@ #include "executable.hpp" #include +#include namespace asmio { @@ -25,6 +26,10 @@ namespace asmio { } + ExecutableBuffer::ExecutableBuffer(uint8_t* buffer, size_t length) noexcept + : buffer(buffer), length(length) { + } + ExecutableBuffer::ExecutableBuffer(ExecutableBuffer&& other) noexcept { labels = std::move(other.labels); @@ -85,8 +90,18 @@ namespace asmio { return buffer; } - uint8_t* ExecutableBuffer::address(Label label) const { - return buffer + labels.at(label); + uint8_t* ExecutableBuffer::own() { + uint8_t* ptr = buffer; + buffer = nullptr; + return ptr; + } + + uint8_t* ExecutableBuffer::address(const Label& label) const { + return buffer + offset(label); + } + + uint64_t ExecutableBuffer::offset(const Label& label) const { + return labels.at(label); } size_t ExecutableBuffer::size() const { @@ -106,7 +121,7 @@ namespace asmio { ExecutableBuffer buffer {segmented.total()}; // now that we have a buffer allocated we can link - segmented.link((uint64_t) buffer.address()); + segmented.link(reinterpret_cast(buffer.address())); // finally copy data and setting to the final image buffer.bake(segmented); diff --git a/src/asmio/program/executable.hpp b/src/asmio/program/executable.hpp index 49741df..c516b5b 100644 --- a/src/asmio/program/executable.hpp +++ b/src/asmio/program/executable.hpp @@ -42,6 +42,7 @@ namespace asmio { ExecutableBuffer() = default; explicit ExecutableBuffer(size_t total); + explicit ExecutableBuffer(uint8_t* buffer, size_t length) noexcept; ExecutableBuffer(ExecutableBuffer&& other) noexcept; explicit ExecutableBuffer(const ExecutableBuffer& other); @@ -55,8 +56,14 @@ namespace asmio { /// Get the base address of this buffer uint8_t* address() const; + /// Take ownership of the underlying buffer, delete it with free_pages(uint8_t*, size_t) + uint8_t* own(); + /// Get the address of a specific label - uint8_t* address(Label label) const; + uint8_t* address(const Label& label) const; + + /// Get the offset of a specific label + uint64_t offset(const Label& label) const; /// get the total size, in bytes size_t size() const; diff --git a/src/asmio/program/label.cpp b/src/asmio/program/label.cpp index 4228ce7..ef768ab 100644 --- a/src/asmio/program/label.cpp +++ b/src/asmio/program/label.cpp @@ -27,7 +27,7 @@ namespace asmio { allocated = true; ptr = ref_allocate(length); - hash = util::hash_djb2(str, length); + hash = util::djb2(str, length); memcpy(ptr, str, length); } @@ -41,7 +41,7 @@ namespace asmio { allocated = true; ptr = ref_allocate(length); - hash = util::hash_djb2(view.data(), length); + hash = util::djb2(view.data(), length); memcpy(ptr, view.data(), length); } @@ -55,7 +55,7 @@ namespace asmio { ptr = ref_allocate(str.size()); length = str.length(); - hash = util::hash_djb2(str.c_str(), length); + hash = util::djb2(str.c_str(), length); memcpy(ptr, str.c_str(), length); } @@ -83,7 +83,7 @@ namespace asmio { } label.length = strlen(str); - label.hash = util::hash_djb2(str, label.length); + label.hash = util::djb2(str, label.length); if (label.length == 0) { throw std::runtime_error {"Label text can't be empty!"}; @@ -98,7 +98,7 @@ namespace asmio { label.allocated = false; label.str = view.data(); label.length = view.length(); - label.hash = util::hash_djb2(label.str, label.length); + label.hash = util::djb2(label.str, label.length); if (label.length == 0) { throw std::runtime_error {"Label text can't be empty!"}; diff --git a/src/asmio/program/label.hpp b/src/asmio/program/label.hpp index 0ca4402..0670eaa 100644 --- a/src/asmio/program/label.hpp +++ b/src/asmio/program/label.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include namespace asmio { @@ -32,7 +32,7 @@ namespace asmio { uint32_t hash; constexpr explicit Label(uint64_t id) - : id(id), allocated(false), length(0), hash(util::hash_tmix64(id)) { + : id(id), allocated(false), length(0), hash(util::tmix64(id)) { } public: @@ -98,7 +98,7 @@ namespace asmio { return false; } - // we know the lengths are the same, as the hash_view includes it + // we know the lengths are the same, as the hashed() includes it // this loop will be completely skipped for non-text labels as then length == 0 for (uint64_t i = 0; i < length; i ++) { if (label.str[i] != str[i]) return false; diff --git a/src/asmio/program/linkage.cpp b/src/asmio/program/linkage.cpp index 31e6916..4639302 100644 --- a/src/asmio/program/linkage.cpp +++ b/src/asmio/program/linkage.cpp @@ -1,15 +1,31 @@ #include "linkage.hpp" +#include +#include namespace asmio { - static void encode_21_5_lo_hi(SegmentedBuffer* buffer, const Linkage& linkage, BufferMarker src, size_t mount) { + [[noreturn]] + static void it_wont_fit(const Linkage& linkage, int64_t value, int64_t dst, int64_t size) { + throw std::runtime_error {"Can't fit label '" + linkage.label.string() + "' (resolved to " + util::to_hex(value) + ") into target " + util::to_hex(dst) + " of size " + std::to_string(size) + ", some data would have been truncated!"}; + } + + [[noreturn]] + static void it_isnt_aligned(const Linkage& linkage, int64_t value, int64_t dst, int64_t alignment) { + throw std::runtime_error {"Can't reference label '" + linkage.label.string() + "' (resolved to " + util::to_hex(value) + ") into target " + util::to_hex(dst) + ", value is not " + std::to_string(alignment) + "-aligned!"}; + } + + /* + * Linker Implementation + */ + + static void encode_21_5_lo_hi(SegmentedBuffer* buffer, const Linkage& linkage, BufferMarker src, size_t) { BufferMarker dst = linkage.target; const int64_t offset = buffer->get_offset(src) - buffer->get_offset(dst); if (!util::is_signed_encodable(offset, 21)) { - throw std::runtime_error {"Can't fit label '" + linkage.label.string() + "' (offset " + util::to_hex(offset) + ") into target " + util::to_hex(dst.offset) + ", some data would have been truncated!"}; + it_wont_fit(linkage, offset, dst.offset, 21); } const uint64_t masked = util::bit_fill(21) & offset; @@ -20,32 +36,32 @@ namespace asmio { } template - static void encode_shifted_aligned(SegmentedBuffer* buffer, const Linkage& linkage, BufferMarker src, size_t mount) { + static void encode_shifted_aligned(SegmentedBuffer* buffer, const Linkage& linkage, BufferMarker src, size_t) { BufferMarker dst = linkage.target; const int64_t distance = buffer->get_offset(src) - buffer->get_offset(dst) + linkage.addend; if (distance & 0b11) { - throw std::runtime_error {"Can't reference label '" + linkage.label.string() + "' (offset " + util::to_hex(distance) + ") into target " + util::to_hex(dst.offset) + ", offset is not aligned!"}; + it_isnt_aligned(linkage, distance, dst.offset, 4); } const int64_t offset = distance >> 2; if (!util::is_signed_encodable(offset, bits)) { - throw std::runtime_error {"Can't fit label '" + linkage.label.string() + "' (offset " + util::to_hex(distance) + ") into target " + util::to_hex(dst.offset) + ", some data would have been truncated!"}; + it_wont_fit(linkage, offset, dst.offset, bits); } *reinterpret_cast(buffer->get_pointer(dst)) |= ((util::bit_fill(bits) & offset) << left_shift); } template - static void encode_relative(SegmentedBuffer* buffer, const Linkage& linkage, BufferMarker src, size_t mount) { + static void encode_relative(SegmentedBuffer* buffer, const Linkage& linkage, BufferMarker src, size_t) { BufferMarker dst = linkage.target; const int64_t value = buffer->get_offset(src) - buffer->get_offset(dst) + linkage.addend; if (util::min_signed_bytes(value) > width) { - throw std::runtime_error {"Can't fit label '" + linkage.label.string() + "' (" + util::to_hex(value) + ") into target of size " + std::to_string(width) + ", some data would have been truncated!"}; + it_wont_fit(linkage, value, dst.offset, width * 8); } const uint8_t* value_ptr = reinterpret_cast(&value); @@ -59,30 +75,92 @@ namespace asmio { const int64_t value = buffer->get_offset(src) + mount + linkage.addend; if (util::min_unsigned_bytes(value) > width) { - throw std::runtime_error {"Can't fit label '" + linkage.label.string() + "' (" + util::to_hex(value) + ") into target of size " + std::to_string(width) + ", some data would have been truncated!"}; + it_wont_fit(linkage, value, dst.offset, width * 8); } const uint8_t* value_ptr = reinterpret_cast(&value); std::memcpy(buffer->get_pointer(dst), value_ptr, width); } + static void encode_riscv_b(SegmentedBuffer* buffer, const Linkage& linkage, BufferMarker src, size_t) { + BufferMarker dst = linkage.target; + const int64_t dist = buffer->get_offset(src) - buffer->get_offset(dst) + linkage.addend; + + if (dist & 1) { + it_isnt_aligned(linkage, dist, dst.offset, 2); + } + + const int64_t imm12 = dist >> 1; + + if (!util::is_signed_encodable(imm12, 12)) { + it_wont_fit(linkage, imm12, dst.offset, 12); + } + + const uint32_t si = (imm12 & 0b1'0'000000'0000) >> 11; + const uint32_t b7 = (imm12 & 0b0'1'000000'0000) >> 10; + const uint32_t hi = (imm12 & 0b0'0'111111'0000) >> 4; + const uint32_t lo = (imm12 & 0b0'0'000000'1111) >> 0; + + *reinterpret_cast(buffer->get_pointer(dst)) |= (si << 31 | hi << 25 | lo << 8 | b7 << 7); + } + + static void encode_riscv_j(SegmentedBuffer* buffer, const Linkage& linkage, BufferMarker src, size_t ) { + BufferMarker dst = linkage.target; + const int64_t dist = buffer->get_offset(src) - buffer->get_offset(dst) + linkage.addend; + + if (dist & 1) { + it_isnt_aligned(linkage, dist, dst.offset, 2); + } + + const int64_t imm20 = dist >> 1; + + if (!util::is_signed_encodable(imm20, 20)) { + it_wont_fit(linkage, imm20, dst.offset, 20); + } + + const uint32_t si = (imm20 & 0b1'00000000'0'0000000000) >> 19; + const uint32_t hi = (imm20 & 0b0'11111111'0'0000000000) >> 11; + const uint32_t b2 = (imm20 & 0b0'00000000'1'0000000000) >> 10; + const uint32_t lo = (imm20 & 0b0'00000000'0'1111111111) >> 0; + + *reinterpret_cast(buffer->get_pointer(dst)) |= (si << 31 | lo << 21 | b2 << 20 | hi << 12); + } + + template + static void encode_riscv_relative_shifted(SegmentedBuffer* buffer, const Linkage& linkage, BufferMarker src, size_t) { + BufferMarker dst = linkage.target; + const int64_t dist = buffer->get_offset(src) - buffer->get_offset(dst) + linkage.addend + 4; + + if (!util::is_signed_encodable(dist, 32)) { + it_wont_fit(linkage, dist, dst.offset, 32); + } + + const int64_t value = ((dist >> part) & util::bit_fill(bits)) + (dist & sign_mask ? 1 : 0); + *reinterpret_cast(buffer->get_pointer(dst)) |= (value << left_shift); + } + /* * class LinkageType */ - Linkage::Type LinkageType::AARCH64_21_5_LO_HI {ElfRelocationType::AARCH64_ADR_PREL_LO21, encode_21_5_lo_hi}; - Linkage::Type LinkageType::AARCH64_14_5_ALIGNED {ElfRelocationType::AARCH64_TSTBR14, encode_shifted_aligned<14, 5>}; - Linkage::Type LinkageType::AARCH64_19_5_ALIGNED {ElfRelocationType::AARCH64_CONDBR19, encode_shifted_aligned<19, 5>}; - Linkage::Type LinkageType::AARCH64_26_0_ALIGNED {ElfRelocationType::AARCH64_JUMP26, encode_shifted_aligned<26, 0>}; - - Linkage::Type LinkageType::X86_64_ABSOLUTE {ElfRelocationType::X86_64_64, encode_absolute<8>}; - Linkage::Type LinkageType::X86_64_RELATIVE {ElfRelocationType::X86_64_PC64, encode_relative<8>}; - Linkage::Type LinkageType::X86_32_ABSOLUTE {ElfRelocationType::X86_64_32, encode_absolute<4>}; - Linkage::Type LinkageType::X86_32_SIGN_ABSOLUTE {ElfRelocationType::X86_64_32S, encode_absolute<4>}; - Linkage::Type LinkageType::X86_32_SIGN_RELATIVE {ElfRelocationType::X86_64_PC32, encode_relative<4>}; - Linkage::Type LinkageType::X86_16_ABSOLUTE {ElfRelocationType::X86_64_16, encode_absolute<2>}; - Linkage::Type LinkageType::X86_16_SIGN_RELATIVE {ElfRelocationType::X86_64_PC16, encode_relative<2>}; - Linkage::Type LinkageType::X86_8_SIGN_ABSOLUTE {ElfRelocationType::X86_64_8, encode_absolute<1>}; - Linkage::Type LinkageType::X86_8_SIGN_RELATIVE {ElfRelocationType::X86_64_PC8, encode_relative<1>}; + Linkage::Type LinkageType::RISCV_BRANCH {ElfRelocationType::RISCV_BRANCH, Linkage::RELATIVE, encode_riscv_b}; + Linkage::Type LinkageType::RISCV_JUMP {ElfRelocationType::RISCV_JAL, Linkage::RELATIVE, encode_riscv_j}; + Linkage::Type LinkageType::RISCV_PCREL_HI20 {ElfRelocationType::RISCV_PCREL_HI20, Linkage::RELATIVE, encode_riscv_relative_shifted<12, 20, 12, 0x0000'0800>}; + Linkage::Type LinkageType::RISCV_PCREL_LO12 {ElfRelocationType::RISCV_PCREL_LO12_I, Linkage::RELATIVE, encode_riscv_relative_shifted<0, 12, 20, 0x0000'0000>}; + + Linkage::Type LinkageType::AARCH64_21_5_LO_HI {ElfRelocationType::AARCH64_ADR_PREL_LO21, Linkage::RELATIVE, encode_21_5_lo_hi}; + Linkage::Type LinkageType::AARCH64_14_5_ALIGNED {ElfRelocationType::AARCH64_TSTBR14, Linkage::RELATIVE, encode_shifted_aligned<14, 5>}; + Linkage::Type LinkageType::AARCH64_19_5_ALIGNED {ElfRelocationType::AARCH64_CONDBR19, Linkage::RELATIVE, encode_shifted_aligned<19, 5>}; + Linkage::Type LinkageType::AARCH64_26_0_ALIGNED {ElfRelocationType::AARCH64_JUMP26, Linkage::RELATIVE, encode_shifted_aligned<26, 0>}; + + Linkage::Type LinkageType::X86_64_ABSOLUTE {ElfRelocationType::X86_64_64, Linkage::ABSOLUTE, encode_absolute<8>}; + Linkage::Type LinkageType::X86_64_RELATIVE {ElfRelocationType::X86_64_PC64, Linkage::RELATIVE, encode_relative<8>}; + Linkage::Type LinkageType::X86_32_ABSOLUTE {ElfRelocationType::X86_64_32, Linkage::ABSOLUTE, encode_absolute<4>}; + Linkage::Type LinkageType::X86_32_SIGN_ABSOLUTE {ElfRelocationType::X86_64_32S, Linkage::ABSOLUTE, encode_absolute<4>}; + Linkage::Type LinkageType::X86_32_SIGN_RELATIVE {ElfRelocationType::X86_64_PC32, Linkage::RELATIVE, encode_relative<4>}; + Linkage::Type LinkageType::X86_16_ABSOLUTE {ElfRelocationType::X86_64_16, Linkage::ABSOLUTE, encode_absolute<2>}; + Linkage::Type LinkageType::X86_16_SIGN_RELATIVE {ElfRelocationType::X86_64_PC16, Linkage::RELATIVE, encode_relative<2>}; + Linkage::Type LinkageType::X86_8_SIGN_ABSOLUTE {ElfRelocationType::X86_64_8, Linkage::ABSOLUTE, encode_absolute<1>}; + Linkage::Type LinkageType::X86_8_SIGN_RELATIVE {ElfRelocationType::X86_64_PC8, Linkage::RELATIVE, encode_relative<1>}; } \ No newline at end of file diff --git a/src/asmio/program/linkage.hpp b/src/asmio/program/linkage.hpp index 59c639f..2716bd3 100644 --- a/src/asmio/program/linkage.hpp +++ b/src/asmio/program/linkage.hpp @@ -6,6 +6,11 @@ namespace asmio { struct LinkageType { + static Linkage::Type RISCV_BRANCH; + static Linkage::Type RISCV_JUMP; + static Linkage::Type RISCV_PCREL_HI20; + static Linkage::Type RISCV_PCREL_LO12; + static Linkage::Type AARCH64_21_5_LO_HI; static Linkage::Type AARCH64_14_5_ALIGNED; static Linkage::Type AARCH64_19_5_ALIGNED; diff --git a/src/asmio/program/segmented.cpp b/src/asmio/program/segmented.cpp index f3bba74..7aabfd5 100644 --- a/src/asmio/program/segmented.cpp +++ b/src/asmio/program/segmented.cpp @@ -1,5 +1,6 @@ #include "segmented.hpp" +#include #include @@ -99,7 +100,8 @@ namespace asmio { throw std::runtime_error {"Undefined label '" + linkage.label.string() + "' used"}; } - linkage.type.linker(this, linkage, it->second, base); + // we never pass the mount (base address) to linkages that self report as relative + linkage.type->linker(this, linkage, it->second, linkage.type->is_relative() ? 0 : base); } catch (const std::runtime_error& error) { if (handler) handler(linkage, error.what()); else throw; } @@ -110,7 +112,7 @@ namespace asmio { void SegmentedBuffer::add_linkage(const Label& label, const Linkage::Type& linker, int64_t addend) { uint32_t offset = sections[selected].buffer.size(); - linkages.emplace_back(label, BufferMarker {static_cast(selected), offset}, linker, addend); + linkages.emplace_back(label, BufferMarker {static_cast(selected), offset}, std::addressof(linker), addend); } BufferMarker SegmentedBuffer::get_label(const Label& label) { @@ -229,4 +231,47 @@ namespace asmio { return source_files.items(); } + void SegmentedBuffer::merge(SegmentedBuffer&& other) { + + int section_offset = sections.size(); + + for (BufferSegment& segment : other.sections) { + if (segment.empty()) { + section_offset --; + continue; + } + + segment.index += section_offset; + sections.emplace_back(std::move(segment)); + } + + for (auto& entry : other.labels) { + entry.second.section += section_offset; + labels.emplace(std::move(entry)); + } + + for (Linkage& linkage : other.linkages) { + linkage.target.section += section_offset; + linkages.emplace_back(std::move(linkage)); + } + + for (ExportSymbol& exported : other.exported_symbols) { + exported_symbols.emplace_back(std::move(exported)); + } + + for (auto& entry : other.external_symbols) { + external_symbols.emplace(std::move(entry)); + } + + for (SourceLocation& location : other.source_locations) { + location.marker.section += section_offset; + source_locations.emplace_back(std::move(location)); + } + + for (const std::string& file : other.source_files.items()) { + source_files.put(std::move(file)); + } + + } + } diff --git a/src/asmio/program/segmented.hpp b/src/asmio/program/segmented.hpp index 8e459d0..297c2be 100644 --- a/src/asmio/program/segmented.hpp +++ b/src/asmio/program/segmented.hpp @@ -21,17 +21,31 @@ namespace asmio { using Linker = std::function; + enum Kind : uint32_t { + RELATIVE, //< Those linkages don't need the mount to be resolved + ABSOLUTE //< Those linkages require the mount to be known before resolving + }; + struct Type { ElfRelocationType relocation; + Kind kind; Linker linker; + + constexpr bool is_relative() const noexcept { + return kind == RELATIVE; + } + + constexpr Type(ElfRelocationType elf_relocation, Kind kind, const Linker& linker) noexcept + : relocation(elf_relocation), kind(kind), linker(linker) { + } }; Label label; BufferMarker target; - Type type; + const Type* type; int64_t addend; - constexpr Linkage(Label label, BufferMarker target, Type type, int64_t addend) + constexpr Linkage(const Label& label, BufferMarker target, const Type* type, int64_t addend) noexcept : label(label), target(target), type(type), addend(addend) { } @@ -193,6 +207,9 @@ namespace asmio { /// Get source file list const std::vector& files() const; + /// Merge the other buffer into this one + void merge(SegmentedBuffer&& other); + }; } diff --git a/src/asmio/program/writer.hpp b/src/asmio/program/writer.hpp index 138d9c0..328de5b 100644 --- a/src/asmio/program/writer.hpp +++ b/src/asmio/program/writer.hpp @@ -14,6 +14,10 @@ namespace asmio { public: + constexpr operator bool() const { + return true; + } + BasicBufferWriter(SegmentedBuffer& buffer); /// Begin (or continue) writing to a section with the given memory flags diff --git a/src/asmio/riscv/argument/condition.hpp b/src/asmio/riscv/argument/condition.hpp new file mode 100644 index 0000000..efd1d16 --- /dev/null +++ b/src/asmio/riscv/argument/condition.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include + +namespace asmio::riscv { + + enum struct Condition : uint8_t { + EQ = 0x00, NE = 0x01, + LT = 0x04, GT = 0x14, + GE = 0x05, LE = 0x15, + LTU = 0x06, GTU = 0x16, + GEU = 0x07, LEU = 0x17, + }; + + /** + * Extract the condition code used during instruction encoding + */ + constexpr uint8_t get_condition_code(Condition cond) { + return static_cast(cond) & 0xf; + } + + /** + * Check if during instruction encoding this condition requires inverting the arguments + */ + constexpr bool get_condition_swap(Condition cond) { + return static_cast(cond) & 0x10; + } + + inline Condition parse_condition_enum(const std::string_view& view) { + if (view == "eq") return Condition::EQ; + if (view == "ne") return Condition::NE; + if (view == "lt") return Condition::LT; + if (view == "ge") return Condition::GE; + if (view == "gt") return Condition::GT; + if (view == "le") return Condition::LE; + if (view == "ltu") return Condition::LTU; + if (view == "geu") return Condition::GEU; + if (view == "gtu") return Condition::GTU; + if (view == "leu") return Condition::LEU; + + throw std::runtime_error {"Invalid condition enumeration"}; + } + +} \ No newline at end of file diff --git a/src/asmio/riscv/argument/order.hpp b/src/asmio/riscv/argument/order.hpp new file mode 100644 index 0000000..7a8ceb7 --- /dev/null +++ b/src/asmio/riscv/argument/order.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +namespace asmio::riscv { + + // Copied from the AArch64 + // TODO: unify? + enum struct Order : uint8_t { + NONE = 0b00, ///< Ensure no ordering of operations + RELEASE = 0b01, ///< Ensure that the preceding operations finish before this one starts + ACQUIRE = 0b10, ///< Ensure that the following operations wait for this one to finish + ACQUIRE_RELEASE = 0b11, ///< Ensure that both previous and preceding operations are executed in order + }; + + constexpr Order parse_order_enum(const std::string_view& view) { + if (view == "none") return Order::NONE; + if (view == "rl") return Order::RELEASE; + if (view == "aq") return Order::ACQUIRE; + if (view == "ra" || view == "ar" || view == "aqrl" || view == "rlaq") return Order::ACQUIRE_RELEASE; + + throw std::runtime_error {"Invalid order enumeration"}; + } + +} \ No newline at end of file diff --git a/src/asmio/riscv/argument/registry.hpp b/src/asmio/riscv/argument/registry.hpp new file mode 100644 index 0000000..7e654a4 --- /dev/null +++ b/src/asmio/riscv/argument/registry.hpp @@ -0,0 +1,91 @@ +#pragma once + +#include + +namespace asmio::riscv { + + struct PACKED Registry { + + public: + + const uint8_t reg; // registry Risc-V code + + public: + + constexpr Registry(uint8_t reg) noexcept : reg(reg) {} + + }; + + /// Reference arbitrary register, 'number' MUST be in range [0, 31] + constexpr Registry X(uint8_t number) { + return {static_cast(number & 0b11111)}; + } + + // general purpose QWORD registers + constexpr Registry X0 = X(0); // always equal 0 + constexpr Registry X1 = X(1); + constexpr Registry X2 = X(2); + constexpr Registry X3 = X(3); + constexpr Registry X4 = X(4); + constexpr Registry X5 = X(5); + constexpr Registry X6 = X(6); + constexpr Registry X7 = X(7); + constexpr Registry X8 = X(8); + constexpr Registry X9 = X(9); + constexpr Registry X10 = X(10); + constexpr Registry X11 = X(11); + constexpr Registry X12 = X(12); + constexpr Registry X13 = X(13); + constexpr Registry X14 = X(14); + constexpr Registry X15 = X(15); + constexpr Registry X16 = X(16); + constexpr Registry X17 = X(17); + constexpr Registry X18 = X(18); + constexpr Registry X19 = X(19); + constexpr Registry X20 = X(20); + constexpr Registry X21 = X(21); + constexpr Registry X22 = X(22); + constexpr Registry X23 = X(23); + constexpr Registry X24 = X(24); + constexpr Registry X25 = X(25); + constexpr Registry X26 = X(26); + constexpr Registry X27 = X(27); + constexpr Registry X28 = X(28); + constexpr Registry X29 = X(29); + constexpr Registry X30 = X(30); + constexpr Registry X31 = X(31); + + // register name aliases + constexpr Registry RA = X1; // return address + constexpr Registry SP = X2; // stack pointer + constexpr Registry GP = X3; // global data pointer (read-only) + constexpr Registry TP = X4; // thread data pointer (read-only) + constexpr Registry T0 = X5; // temporary register + constexpr Registry T1 = X6; // temporary register + constexpr Registry T2 = X7; // temporary register + constexpr Registry S0 = X8; // saved register + constexpr Registry S1 = X9; // saved register + constexpr Registry A0 = X10; // argument register + constexpr Registry A1 = X11; // argument register + constexpr Registry A2 = X12; // argument register + constexpr Registry A3 = X13; // argument register + constexpr Registry A4 = X14; // argument register + constexpr Registry A5 = X15; // argument register + constexpr Registry A6 = X16; // argument register + constexpr Registry A7 = X17; // argument register + constexpr Registry S2 = X18; // saved register + constexpr Registry S3 = X19; // saved register + constexpr Registry S4 = X20; // saved register + constexpr Registry S5 = X21; // saved register + constexpr Registry S6 = X22; // saved register + constexpr Registry S7 = X23; // saved register + constexpr Registry S8 = X24; // saved register + constexpr Registry S9 = X25; // saved register + constexpr Registry S10 = X26; // saved register + constexpr Registry S11 = X27; // saved register + constexpr Registry T3 = X28; // temporary register + constexpr Registry T4 = X29; // temporary register + constexpr Registry T5 = X30; // temporary register + constexpr Registry T6 = X31; // temporary register + +} \ No newline at end of file diff --git a/src/asmio/riscv/module.cpp b/src/asmio/riscv/module.cpp new file mode 100644 index 0000000..97ee697 --- /dev/null +++ b/src/asmio/riscv/module.cpp @@ -0,0 +1,129 @@ +#include "module.hpp" +#include "writer.hpp" + +#include + +namespace asmio::riscv { + + using namespace tasml; + + template + T parse_argument(TokenStream stream); + + template + T parse_argument(TokenStream stream) { + return stream.expect(Token::INT).as_int(); + } + + template <> + Label parse_argument(TokenStream stream) { + const Token& label = stream.expect(Token::REFERENCE); + return {label.raw.c_str() + 1}; + } + + template <> + Registry parse_argument(TokenStream stream) { + const Token& token = stream.expect(Token::NAME); + std::string raw = util::to_lower(token.raw); + + if (raw[0] == 'x') { + int index = util::parse_decimal(raw.substr(1)); + + if (index >= 0 && index <= 31) { + return X(index); + } + + throw std::runtime_error {"Invalid register number, expected value in range [0, 31]"}; + } + + if (raw == "ra") return RA; + if (raw == "sp") return SP; + if (raw == "gp") return GP; + if (raw == "tp") return TP; + if (raw == "t0") return T0; + if (raw == "t1") return T1; + if (raw == "t2") return T2; + if (raw == "s0") return S0; + if (raw == "s1") return S1; + if (raw == "a0") return A0; + if (raw == "a1") return A1; + if (raw == "a2") return A2; + if (raw == "a3") return A3; + if (raw == "a4") return A4; + if (raw == "a5") return A5; + if (raw == "a6") return A6; + if (raw == "a7") return A7; + if (raw == "s2") return S2; + if (raw == "s3") return S3; + if (raw == "s4") return S4; + if (raw == "s5") return S5; + if (raw == "s6") return S6; + if (raw == "s7") return S7; + if (raw == "s8") return S8; + if (raw == "s9") return S9; + if (raw == "s10") return S10; + if (raw == "s11") return S11; + if (raw == "t3") return T3; + if (raw == "t4") return T4; + if (raw == "t5") return T5; + if (raw == "t6") return T6; + + throw std::runtime_error {"Invalid argument format, expected register"}; + } + + template <> + Condition parse_argument(TokenStream stream) { + const Token& token = stream.expect(Token::NAME); + std::string raw = util::to_lower(token.raw); + + return parse_condition_enum(raw); + } + + template <> + Order parse_argument(TokenStream stream) { + const Token& token = stream.expect(Token::NAME); + std::string raw = util::to_lower(token.raw); + + return parse_order_enum(raw); + } + + template <> + Size parse_argument(TokenStream stream) { + const Token& token = stream.expect(Token::NAME); + std::string raw = util::to_lower(token.raw); + + if (raw == "dword") return DWORD; + if (raw == "qword") return QWORD; + + throw std::runtime_error {"Invalid size"}; + } + +# include "generated/riscv.hpp" + + /* + * class LanguageModule + */ + + const char* LanguageModule::name() const { + return "riscv"; + } + + FeatureSet LanguageModule::features() const { + return {}; + } + + void LanguageModule::parse(ErrorHandler& reporter, TokenStream stream, SegmentedBuffer& buffer) const { + BufferWriter writer {buffer}; + + if (try_parse_instruction(stream, writer)) { + return; + } + + Module::parse(reporter, stream, buffer); + } + + ElfMachine LanguageModule::machine() const { + return ElfMachine::RISCV; + } + +} diff --git a/src/asmio/riscv/module.hpp b/src/asmio/riscv/module.hpp new file mode 100644 index 0000000..8391e1f --- /dev/null +++ b/src/asmio/riscv/module.hpp @@ -0,0 +1,18 @@ +#pragma once +#include +#include + +namespace asmio::riscv { + + struct LanguageModule : Module { + + const char* name() const override; + FeatureSet features() const override; + void parse(tasml::ErrorHandler& reporter, tasml::TokenStream stream, SegmentedBuffer& buffer) const override; + ElfMachine machine() const override; + + }; + + REGISTER_MODULE(LanguageModule); + +} diff --git a/src/asmio/riscv/writer.cpp b/src/asmio/riscv/writer.cpp new file mode 100644 index 0000000..97bf37f --- /dev/null +++ b/src/asmio/riscv/writer.cpp @@ -0,0 +1,497 @@ +#include "writer.hpp" + +#include +#include + +namespace asmio::riscv { + + /* + * class BufferWriter + */ + + BufferWriter::BufferWriter(SegmentedBuffer& buffer) + : BasicBufferWriter(buffer) { + } + + void BufferWriter::put_inst_r(uint8_t func7, Registry rs2, Registry rs1, uint8_t func3, Registry rd, uint8_t opc7) { + put_dword((func7 & 0b1111111) << 25 | rs2.reg << 20 | rs1.reg << 15 | (func3 & 0b111) << 12 | rd.reg << 7 | (opc7 & 0b1111111)); + } + + void BufferWriter::put_inst_i(uint16_t imm12, Registry rs1, uint8_t func3, Registry rd, uint8_t opc7) { + put_dword((imm12 & 0xfff) << 20 | rs1.reg << 15 | (func3 & 0b111) << 12 | rd.reg << 7 | (opc7 & 0b1111111)); + } + + void BufferWriter::put_inst_s(uint16_t imm12, Registry rs2, Registry rs1, uint8_t func3, uint8_t opc7) { + const uint32_t hi = (imm12 & 0b1111111'00000) >> 5; + const uint32_t lo = (imm12 & 0b0000000'11111) >> 0; + + put_dword(hi << 25 | rs2.reg << 20 | rs1.reg << 15 | (func3 & 0b111) << 12 | lo << 7 | (opc7 & 0b1111111)); + } + + void BufferWriter::put_inst_b(uint16_t imm12, Registry rs2, Registry rs1, uint8_t func3, uint8_t opc7) { + const uint32_t si = (imm12 & 0b1'0'000000'0000) >> 11; + const uint32_t b7 = (imm12 & 0b0'1'000000'0000) >> 10; + const uint32_t hi = (imm12 & 0b0'0'111111'0000) >> 4; + const uint32_t lo = (imm12 & 0b0'0'000000'1111) >> 0; + + put_dword(si << 31 | hi << 25 | rs2.reg << 20 | rs1.reg << 15 | (func3 & 0b111) << 12 | lo << 8 | b7 << 7 | (opc7 & 0b1111111)); + } + + void BufferWriter::put_inst_u(uint32_t imm20, Registry rd, uint8_t opc7) { + put_dword((imm20 & 0xfffff) << 12 | rd.reg << 7 | (opc7 & 0b1111111)); + } + + void BufferWriter::put_inst_j(uint32_t imm20, Registry rd, uint8_t opc7) { + const uint32_t si = (imm20 & 0b1'00000000'0'0000000000) >> 19; + const uint32_t hi = (imm20 & 0b0'11111111'0'0000000000) >> 11; + const uint32_t b2 = (imm20 & 0b0'00000000'1'0000000000) >> 10; + const uint32_t lo = (imm20 & 0b0'00000000'0'1111111111) >> 0; + + put_dword(si << 31 | lo << 21 | b2 << 20 | hi << 12 | rd.reg << 7 | (opc7 & 0b1111111)); + } + + void BufferWriter::put_inst_a(uint8_t func5, Registry rs2, Registry rs1, Size size, Registry rd, Order order) { + uint8_t func3; + + if (size == DWORD) { + func3 = 0b010; + } else if (size == QWORD) { + func3 = 0b011; + } else { + throw std::runtime_error {"Invalid operand, atomic operation size must be dword or qword"}; + } + + put_inst_r(func5 << 2 | static_cast(order), rs2, rs1, func3, rd, 0b0101111); + } + + void BufferWriter::put_add(Registry rd, Registry rs, int16_t imm12) { + put_inst_i(imm12, rs, 0x0, rd, 0b0010011); + } + + void BufferWriter::put_xor(Registry rd, Registry rs, int16_t imm12) { + put_inst_i(imm12, rs, 0x4, rd, 0b0010011); + } + + void BufferWriter::put_or(Registry rd, Registry rs, int16_t imm12) { + put_inst_i(imm12, rs, 0x6, rd, 0b0010011); + } + + void BufferWriter::put_and(Registry rd, Registry rs, int16_t imm12) { + put_inst_i(imm12, rs, 0x7, rd, 0b0010011); + } + + void BufferWriter::put_sll(Registry rd, Registry rs, int16_t imm5) { + imm5 &= 0b000000'111111; + put_inst_i(imm5, rs, 0x1, rd, 0b0010011); + } + + void BufferWriter::put_srl(Registry rd, Registry rs, int16_t imm5) { + imm5 &= 0b000000'111111; + put_inst_i(imm5, rs, 0x5, rd, 0b0010011); + } + + void BufferWriter::put_sra(Registry rd, Registry rs, int16_t imm5) { + imm5 &= 0b000000'111111; + imm5 |= 0b010000'000000; + put_inst_i(imm5, rs, 0x5, rd, 0b0010011); + } + + void BufferWriter::put_slt(Registry rd, Registry rs, int16_t imm12) { + put_inst_i(imm12, rs, 0x2, rd, 0b0010011); + } + + void BufferWriter::put_sltu(Registry rd, Registry rs, int16_t imm12) { + put_inst_i(imm12, rs, 0x3, rd, 0b0010011); + } + + void BufferWriter::put_add(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x00, rs2, rs1, 0x0, rd, 0b0110011); + } + + void BufferWriter::put_sub(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x20, rs2, rs1, 0x0, rd, 0b0110011); + } + + void BufferWriter::put_xor(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x00, rs2, rs1, 0x4, rd, 0b0110011); + } + + void BufferWriter::put_or(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x00, rs2, rs1, 0x6, rd, 0b0110011); + } + + void BufferWriter::put_and(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x00, rs2, rs1, 0x7, rd, 0b0110011); + } + + void BufferWriter::put_sll(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x00, rs2, rs1, 0x1, rd, 0b0110011); + } + + void BufferWriter::put_srl(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x00, rs2, rs1, 0x5, rd, 0b0110011); + } + + void BufferWriter::put_sra(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x20, rs2, rs1, 0x5, rd, 0b0110011); + } + + void BufferWriter::put_slt(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x00, rs2, rs1, 0x2, rd, 0b0110011); + } + + void BufferWriter::put_sltu(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x00, rs2, rs1, 0x3, rd, 0b0110011); + } + + void BufferWriter::put_lb(Registry rd, Registry rs, int16_t imm12) { + put_inst_i(imm12, rs, 0x0, rd, 0b0000011); + } + + void BufferWriter::put_lw(Registry rd, Registry rs, int16_t imm12) { + put_inst_i(imm12, rs, 0x1, rd, 0b0000011); + } + + void BufferWriter::put_ld(Registry rd, Registry rs, int16_t imm12) { + put_inst_i(imm12, rs, 0x2, rd, 0b0000011); + } + + void BufferWriter::put_lbu(Registry rd, Registry rs, int16_t imm12) { + put_inst_i(imm12, rs, 0x4, rd, 0b0000011); + } + + void BufferWriter::put_lwu(Registry rd, Registry rs, int16_t imm12) { + put_inst_i(imm12, rs, 0x5, rd, 0b0000011); + } + + void BufferWriter::put_ldu(Registry rd, Registry rs, int16_t imm12) { + put_inst_i(imm12, rs, 0x6, rd, 0b0000011); + } + + void BufferWriter::put_lq(Registry rd, Registry rs, int16_t imm12) { + put_inst_i(imm12, rs, 0x3, rd, 0b0000011); + } + + void BufferWriter::put_sb(Registry rs1, Registry rs2, int16_t imm12) { + put_inst_s(imm12, rs1, rs2, 0x0, 0b0100011); + } + + void BufferWriter::put_sw(Registry rs1, Registry rs2, int16_t imm12) { + put_inst_s(imm12, rs1, rs2, 0x1, 0b0100011); + } + + void BufferWriter::put_sd(Registry rs1, Registry rs2, int16_t imm12) { + put_inst_s(imm12, rs1, rs2, 0x2, 0b0100011); + } + + void BufferWriter::put_sq(Registry rs1, Registry rs2, int16_t imm12) { + put_inst_s(imm12, rs1, rs2, 0x3, 0b0100011); + } + + void BufferWriter::put_b(Condition cond, Registry rs1, Registry rs2, const Label& label) { + buffer.add_linkage(label, LinkageType::RISCV_BRANCH); + + Registry* rp1 = &rs1; + Registry* rp2 = &rs2; + + if (get_condition_swap(cond)) { + std::swap(rp1, rp2); + } + + put_inst_b(0, *rp2, *rp1, get_condition_code(cond), 0b1100011); + } + + void BufferWriter::put_jal(const Label& label) { + put_jal(X1, label); + } + + void BufferWriter::put_jal(Registry rd, const Label& label) { + buffer.add_linkage(label, LinkageType::RISCV_JUMP); + put_inst_j(0, rd, 0b1101111); + } + + void BufferWriter::put_jalr(Registry rd, Registry rs, int16_t offset) { + put_inst_i(offset, rs, 0x0, rd, 0b1100111); + } + + void BufferWriter::put_beq(Registry rs1, Registry rs2, const Label& label) { + put_b(Condition::EQ, rs1, rs2, label); + } + + void BufferWriter::put_bne(Registry rs1, Registry rs2, const Label& label) { + put_b(Condition::NE, rs1, rs2, label); + } + + void BufferWriter::put_blt(Registry rs1, Registry rs2, const Label& label) { + put_b(Condition::LT, rs1, rs2, label); + } + + void BufferWriter::put_bge(Registry rs1, Registry rs2, const Label& label) { + put_b(Condition::GE, rs1, rs2, label); + } + + void BufferWriter::put_bltu(Registry rs1, Registry rs2, const Label& label) { + put_b(Condition::LTU, rs1, rs2, label); + } + + void BufferWriter::put_bgeu(Registry rs1, Registry rs2, const Label& label) { + put_b(Condition::GEU, rs1, rs2, label); + } + + void BufferWriter::put_bgt(Registry rs1, Registry rs2, const Label& label) { + put_b(Condition::GT, rs1, rs2, label); + } + + void BufferWriter::put_ble(Registry rs1, Registry rs2, const Label& label) { + put_b(Condition::LE, rs1, rs2, label); + } + + void BufferWriter::put_bgtu(Registry rs1, Registry rs2, const Label& label) { + put_b(Condition::GTU, rs1, rs2, label); + } + + void BufferWriter::put_bleu(Registry rs1, Registry rs2, const Label& label) { + put_b(Condition::LEU, rs1, rs2, label); + } + + void BufferWriter::put_lui(Registry rd, uint32_t imm20) { + put_inst_u(imm20, rd, 0b0110111); + } + + void BufferWriter::put_ecall() { + put_inst_i(0, X0, 0x0, X0, 0b1110011); + } + + void BufferWriter::put_ebreak() { + put_inst_i(1, X0, 0x0, X0, 0b1110011); + } + + void BufferWriter::put_auipc(Registry rd, const Label& label) { + if (!label.empty()) { + buffer.add_linkage(label, LinkageType::RISCV_PCREL_HI20); + } + + put_inst_u(0, rd, 0b0010111); + } + + void BufferWriter::put_mul(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x01, rs2, rs1, 0x0, rd, 0b0110011); + } + + void BufferWriter::put_mulh(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x01, rs2, rs1, 0x1, rd, 0b0110011); + } + + void BufferWriter::put_mulhsu(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x01, rs2, rs1, 0x2, rd, 0b0110011); + } + + void BufferWriter::put_mulhu(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x01, rs2, rs1, 0x3, rd, 0b0110011); + } + + void BufferWriter::put_div(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x01, rs2, rs1, 0x4, rd, 0b0110011); + } + + void BufferWriter::put_divu(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x01, rs2, rs1, 0x5, rd, 0b0110011); + } + + void BufferWriter::put_rem(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x01, rs2, rs1, 0x6, rd, 0b0110011); + } + + void BufferWriter::put_remu(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x01, rs2, rs1, 0x7, rd, 0b0110011); + } + + void BufferWriter::put_muld(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x01, rs2, rs1, 0x0, rd, 0b0111011); + } + + void BufferWriter::put_divd(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x01, rs2, rs1, 0x4, rd, 0b0111011); + } + + void BufferWriter::put_divud(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x01, rs2, rs1, 0x5, rd, 0b0111011); + } + + void BufferWriter::put_remd(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x01, rs2, rs1, 0x6, rd, 0b0111011); + } + + void BufferWriter::put_remud(Registry rd, Registry rs1, Registry rs2) { + put_inst_r(0x01, rs2, rs1, 0x7, rd, 0b0111011); + } + + void BufferWriter::put_lr(Registry rd, Registry rs, Size s, Order order) { + put_inst_a(0x02, X0, rs, s, rd, order); + } + + void BufferWriter::put_sc(Registry rd, Registry rt, Registry rs, Size s, Order order) { + put_inst_a(0x03, rs, rt, s, rd, order); + } + + void BufferWriter::put_amoswap(Registry old, Registry ptr, Registry val, Size s, Order order) { + put_inst_a(0x01, val, ptr, s, old, order); + } + + void BufferWriter::put_amoadd(Registry old, Registry ptr, Registry val, Size s, Order order) { + put_inst_a(0x00, val, ptr, s, old, order); + } + + void BufferWriter::put_amoand(Registry old, Registry ptr, Registry val, Size s, Order order) { + put_inst_a(0x0C, val, ptr, s, old, order); + } + + void BufferWriter::put_amoor(Registry old, Registry ptr, Registry val, Size s, Order order) { + put_inst_a(0x08, val, ptr, s, old, order); + } + + void BufferWriter::put_amoxor(Registry old, Registry ptr, Registry val, Size s, Order order) { + put_inst_a(0x04, val, ptr, s, old, order); + } + + void BufferWriter::put_amomax(Registry old, Registry ptr, Registry val, Size s, Order order) { + put_inst_a(0x14, val, ptr, s, old, order); + } + + void BufferWriter::put_amomin(Registry old, Registry ptr, Registry val, Size s, Order order) { + put_inst_a(0x10, val, ptr, s, old, order); + } + + void BufferWriter::put_amomaxu(Registry old, Registry ptr, Registry val, Size s, Order order) { + put_inst_a(0x1C, val, ptr, s, old, order); + } + + void BufferWriter::put_amominu(Registry old, Registry ptr, Registry val, Size s, Order order) { + put_inst_a(0x18, val, ptr, s, old, order); + } + + void BufferWriter::put_mov(Registry rd, Registry rs) { + put_add(rd, rs, 0); + } + + void BufferWriter::put_mov(Registry rd, uint64_t imm64) { + auto value = static_cast(imm64); + + if (imm64 == 0) { + put_and(rd, X0, 0); + return; + } + + if (util::is_signed_encodable(value, 12)) { + put_add(rd, X0, static_cast(imm64 & 0xfff)); + return; + } + + // technically 4 would be fine, 6 if we ignore that we also have to fit LUI, using 8 to be safe + int16_t parts[8] = {}; + int64_t index = 0; + + while (true) { + + // subtract one as the sign bit still needs to exist + int64_t heading = util::count_leading(value) - 1; + + // how many bits still cary some meaning + int64_t bits = 64 - heading; + + // finish encoding and begin writing with the mandatory LUI + if ((bits <= 20) && (index != 0)) { + put_lui(rd, value); + break; + } + + // next part, offset to account for the borrow from the previous part + auto imm12 = static_cast(value & 0xfff); + + parts[index ++] = imm12; + value = value >> 12; + + // If the high bit of the lower immediate is set the 12 bit lower immediate + // will be negative thus forcing us to make the next constant one larger to offset the borrow. + // This is fine even with overflow - 0xfffff will overflow to 0x00000, then, when subtracted, return to 0xfffff. + if (imm12 & 0x800) { + value += 1; + } + + } + + int16_t shift = 0; + + while (index > 0) { + index --; + + int16_t chunk = parts[index]; + + // ship empty chunks + if (chunk != 0) { + + // apply the accumulated shift + if (shift > 0) { + put_sll(rd, rd, shift); + shift = 0; + } + + put_add(rd, rd, chunk); + } + + shift += 12; + } + + // undo the last shift increment + shift -= 12; + + if (shift > 0) { + put_sll(rd, rd, shift); + } + + } + + void BufferWriter::put_mov(Registry rd, const Label& label) { + + if (label.empty()) { + put_mov(rd, 0); + return; + } + + // we support only 32 bit offsets here + put_auipc(rd, label); + + buffer.add_linkage(label, LinkageType::RISCV_PCREL_LO12); + put_add(rd, rd, 0); + + } + + void BufferWriter::put_nop() { + put_add(X0, X0, 0); + } + + void BufferWriter::put_not(Registry rd, Registry rs) { + put_xor(rd, rs, -1); + } + + void BufferWriter::put_neg(Registry rd, Registry rs) { + put_sub(rd, X0, rs); + } + + void BufferWriter::put_j(const Label& label) { + put_jal(X0, label); + } + + void BufferWriter::put_jr(Registry rs) { + put_jalr(X0, rs); + } + + void BufferWriter::put_jlr(Registry rd, Registry rs) { + put_jalr(rd, rs); + } + + void BufferWriter::put_ret() { + put_jalr(X0, X1, 0); + } + +} diff --git a/src/asmio/riscv/writer.hpp b/src/asmio/riscv/writer.hpp new file mode 100644 index 0000000..e1c3b42 --- /dev/null +++ b/src/asmio/riscv/writer.hpp @@ -0,0 +1,118 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace asmio::riscv { + + class BufferWriter : public BasicBufferWriter { + + protected: + + void put_inst_r(uint8_t func7, Registry rs2, Registry rs1, uint8_t func3, Registry rd, uint8_t opc7); + void put_inst_i(uint16_t imm12, Registry rs1, uint8_t func3, Registry rd, uint8_t opc7); + void put_inst_s(uint16_t imm12, Registry rs2, Registry rs1, uint8_t func3, uint8_t opc7); + void put_inst_b(uint16_t imm12, Registry rs2, Registry rs1, uint8_t func3, uint8_t opc7); + void put_inst_u(uint32_t imm20, Registry rd, uint8_t opc7); + void put_inst_j(uint32_t imm20, Registry rd, uint8_t opc7); + void put_inst_a(uint8_t func5, Registry rs2, Registry rs1, Size size, Registry rd, Order order); + + public: + + BufferWriter(SegmentedBuffer& buffer); + + INST put_add(Registry rd, Registry rs, int16_t imm12); ///< Add signed 12 bit immediate to rs and save result to rd + INST put_xor(Registry rd, Registry rs, int16_t imm12); ///< Bitwise XOR 12 bit immediate rs and save result to rd + INST put_or(Registry rd, Registry rs, int16_t imm12); ///< Bitwise OR 12 bit immediate rs and save result to rd + INST put_and(Registry rd, Registry rs, int16_t imm12); ///< Bitwise AND 12 bit immediate rs and save result to rd + INST put_sll(Registry rd, Registry rs, int16_t imm5); ///< Shift register rs left by a 12 bit immediate and save result to rd + INST put_srl(Registry rd, Registry rs, int16_t imm5); ///< Shift register rs right logically by a 12 bit immediate and save result to rd + INST put_sra(Registry rd, Registry rs, int16_t imm5); ///< Shift register rs right arythetically by a 12 bit immediate and save result to rd + INST put_slt(Registry rd, Registry rs, int16_t imm12); ///< Set rd to 1 if rs is less than a 12 bit immediate, and to 0 otherwise (signed) + INST put_sltu(Registry rd, Registry rs, int16_t imm12); ///< Set rd to 1 if rs1 is less than a 12 bit immediate, and to 0 otherwise (unsigned) + INST put_add(Registry rd, Registry rs1, Registry rs2); ///< Add register rs1 to rs2 and save result to rd + INST put_sub(Registry rd, Registry rs1, Registry rs2); ///< Subtract register rs1 from rs2 and save result to rd + INST put_xor(Registry rd, Registry rs1, Registry rs2); ///< Bitwise XOR register rs1 with rs2 and save result to rd + INST put_or(Registry rd, Registry rs1, Registry rs2); ///< Bitwise OR register rs1 with rs2 and save result to rd + INST put_and(Registry rd, Registry rs1, Registry rs2); ///< Bitwise AND register rs1 with rs2 and save result to rd + INST put_sll(Registry rd, Registry rs1, Registry rs2); ///< Shift register rs1 left by rs2 and save result to rd + INST put_srl(Registry rd, Registry rs1, Registry rs2); ///< Shift register rs1 right logically by rs2 and save result to rd + INST put_sra(Registry rd, Registry rs1, Registry rs2); ///< Shift register rs1 right arythetically by rs2 and save result to rd + INST put_slt(Registry rd, Registry rs1, Registry rs2); ///< Set rd to 1 if rs1 is less than rs2, and to 0 otherwise (signed) + INST put_sltu(Registry rd, Registry rs1, Registry rs2); ///< Set rd to 1 if rs1 is less than rs2, and to 0 otherwise (unsigned) + INST put_lb(Registry rd, Registry rs, int16_t imm12 = 0); ///< Load byte at rs + imm12 into rd (with sign extension) + INST put_lw(Registry rd, Registry rs, int16_t imm12 = 0); ///< Load word at rs + imm12 into rd (with sign extension) + INST put_ld(Registry rd, Registry rs, int16_t imm12 = 0); ///< Load dword at rs + imm12 into rd (with sign extension) + INST put_lbu(Registry rd, Registry rs, int16_t imm12 = 0); ///< Load byte at rs + imm12 into rd (with zero extension) + INST put_lwu(Registry rd, Registry rs, int16_t imm12 = 0); ///< Load word at rs + imm12 into rd (with zero extension) + INST put_ldu(Registry rd, Registry rs, int16_t imm12 = 0); ///< Load dword at rs + imm12 into rd (with zero extension) + INST put_lq(Registry rd, Registry rs, int16_t imm12 = 0); ///< Load qword at rs + imm12 into rd + INST put_sb(Registry rs1, Registry rs2, int16_t imm12 = 0); ///< Store byte from rs1 at rs2 + imm + INST put_sw(Registry rs1, Registry rs2, int16_t imm12 = 0); ///< Store word from rs1 at rs2 + imm + INST put_sd(Registry rs1, Registry rs2, int16_t imm12 = 0); ///< Store dword from rs1 at rs2 + imm + INST put_sq(Registry rs1, Registry rs2, int16_t imm12 = 0); ///< Store qword from rs1 at rs2 + imm + INST put_b(Condition cond, Registry rs1, Registry rs2, const Label& label); ///< Branch to label if condition is met between rs1 and rs2 + INST put_jal(const Label& label); ///< Jump and link + INST put_jal(Registry rd, const Label& label); ///< Jump and link + INST put_jalr(Registry rd, Registry rs, int16_t offset = 0); ///< Jump and link to register + INST put_beq(Registry rs1, Registry rs2, const Label& label); ///< Branch if rs1 == rs2 + INST put_bne(Registry rs1, Registry rs2, const Label& label); ///< Branch if rs1 != rs2 + INST put_blt(Registry rs1, Registry rs2, const Label& label); ///< Branch if rs1 < rs2 + INST put_bge(Registry rs1, Registry rs2, const Label& label); ///< Branch if rs1 >= rs2 + INST put_bltu(Registry rs1, Registry rs2, const Label& label); ///< Branch if rs1 < rs2 (zero-extended) + INST put_bgeu(Registry rs1, Registry rs2, const Label& label); ///< Branch if rs1 >= rs2 (zero-extended) + INST put_bgt(Registry rs1, Registry rs2, const Label& label); ///< Branch if rs1 > rs2 + INST put_ble(Registry rs1, Registry rs2, const Label& label); ///< Branch if rs1 <= rs2 + INST put_bgtu(Registry rs1, Registry rs2, const Label& label); ///< Branch if rs1 > rs2 (zero-extended) + INST put_bleu(Registry rs1, Registry rs2, const Label& label); ///< Branch if rs1 <= rs2 (zero-extended) + INST put_lui(Registry rd, uint32_t imm20); ///< Load an upper constant to register, rd = imm << 12 + INST put_ecall(); ///< Environment Call + INST put_ebreak(); ///< Environment Break + INST put_auipc(Registry rd, const Label& label); ///< Add Upper Immediate to PC and store it in rd + + // "M" extension + INST put_mul(Registry rd, Registry rs1, Registry rs2); ///< Store lower 64 bits of rs1 * rs2 into rd + INST put_mulh(Registry rd, Registry rs1, Registry rs2); ///< Store upper 64 bits of rs1 * rs2 into rd + INST put_mulhsu(Registry rd, Registry rs1, Registry rs2); ///< Store upper 64 bits of rs1 * rs2 into rd + INST put_mulhu(Registry rd, Registry rs1, Registry rs2); ///< Store upper 64 bits of rs1 * rs2 into rd + INST put_div(Registry rd, Registry rs1, Registry rs2); ///< Store rs1 / rs2 into rd + INST put_divu(Registry rd, Registry rs1, Registry rs2); ///< Store rs1 / rs2 into rd + INST put_rem(Registry rd, Registry rs1, Registry rs2); ///< Store reminder of rs1 / rs2 into rd + INST put_remu(Registry rd, Registry rs1, Registry rs2); ///< Store reminder of rs1 / rs2 into rd + INST put_muld(Registry rd, Registry rs1, Registry rs2); ///< Multiply dword rs1 and rs2, store result in rd + INST put_divd(Registry rd, Registry rs1, Registry rs2); ///< Divide dword rs1 by rs2, store result in rd + INST put_divud(Registry rd, Registry rs1, Registry rs2); ///< Unsigned divide dword rs1 by rs2, store result in rd + INST put_remd(Registry rd, Registry rs1, Registry rs2); ///< Get reminder of dword rs1 and rs2 division, store result in rd + INST put_remud(Registry rd, Registry rs1, Registry rs2); ///< Get reminder of dword rs1 and rs2 unsigned division, store result in rd + + // "A" extension + INST put_lr(Registry rd, Registry rs, Size s = DWORD, Order order = Order::NONE); ///< Load reserved + INST put_sc(Registry rd, Registry rt, Registry rs, Size s = DWORD, Order order = Order::NONE); ///< Store conditional, writes rs at address rt, status (0 on success, 1 otherwise) is written to rd + INST put_amoswap(Registry old, Registry ptr, Registry val, Size s = DWORD, Order order = Order::NONE); ///< Atomic Swap + INST put_amoadd(Registry old, Registry ptr, Registry val, Size s = DWORD, Order order = Order::NONE); ///< Atomic Add + INST put_amoand(Registry old, Registry ptr, Registry val, Size s = DWORD, Order order = Order::NONE); ///< Atomic bitwise AND + INST put_amoor(Registry old, Registry ptr, Registry val, Size s = DWORD, Order order = Order::NONE); ///< Atomic bitwise OR + INST put_amoxor(Registry old, Registry ptr, Registry val, Size s = DWORD, Order order = Order::NONE); ///< Atomic bitwise XOR + INST put_amomax(Registry old, Registry ptr, Registry val, Size s = DWORD, Order order = Order::NONE); ///< Atomic Maximum + INST put_amomin(Registry old, Registry ptr, Registry val, Size s = DWORD, Order order = Order::NONE); ///< Atomic Minimum + INST put_amomaxu(Registry old, Registry ptr, Registry val, Size s = DWORD, Order order = Order::NONE); ///< Atomic Maximum (unsigned) + INST put_amominu(Registry old, Registry ptr, Registry val, Size s = DWORD, Order order = Order::NONE); ///< Atomic Minimum (unsigned) + + // Aliases + INST put_mov(Registry rd, Registry rs); ///< Copy value from rs to rd + INST put_mov(Registry rd, uint64_t imm); ///< Load immediate into rd + INST put_mov(Registry rd, const Label& label); ///< Load address of label into rd + INST put_nop(); ///< No Operation + INST put_not(Registry rd, Registry rs); ///< Invert bits + INST put_neg(Registry rd, Registry rs); ///< Negate two-complement number + INST put_j(const Label& label); ///< Jump + INST put_jr(Registry rs); ///< Jump register + INST put_jlr(Registry rd, Registry rs); ///< Jump register with link + INST put_ret(); ///< Return + + }; + +} diff --git a/src/asmio/util.hpp b/src/asmio/util.hpp index 0bec413..1998e8f 100644 --- a/src/asmio/util.hpp +++ b/src/asmio/util.hpp @@ -1,15 +1,9 @@ #pragma once -#include - -template -concept trivially_copyable = std::is_trivially_copyable_v; - -template -concept castable = requires (const A& arg) { static_cast(arg); }; - -#define ENUM_BEGIN __INTERNAL__ = __LINE__, -#define ENUM_END ENUM_LENGTH = __LINE__ - __INTERNAL__ - 1, +#include +#include +#include +#include #define EXIT_OK 0 #define EXIT_ERROR 1 @@ -19,24 +13,6 @@ concept castable = requires (const A& arg) { static_cast(arg); }; namespace asmio::util { - template - concept is_enumeration = requires { std::is_enum_v; }; - - template - constexpr size_t enum_length = static_cast(T::ENUM_LENGTH); - - template - struct function_decompose : std::false_type {}; - - template - struct function_decompose { - using return_type = R; - using arguments = std::tuple; - - template - using arg_type = std::tuple_element_t; - }; - template struct UniqueHandle { @@ -57,132 +33,6 @@ namespace asmio::util { }; - // https://stackoverflow.com/a/6500499 - inline std::string trim(const std::string& str) { - int prefix = 0; // will point to the first non-space char - int suffix = str.length() - 1; - - while (prefix < static_cast(str.length())) { - if (!std::isspace(str[prefix])) { - break; - } - - prefix ++; - } - - while (suffix > 0) { - if (!std::isspace(str[suffix])) { - break; - } - - suffix --; - } - - int count = suffix - prefix + 1; - - if (count < 0) { - count = 0; - } - - return str.substr(prefix, count); - } - - // https://stackoverflow.com/a/46931770 - inline std::vector split_string(const std::string& str, const std::string_view& delim) { - size_t pos_start = 0, pos_end, delim_len = delim.length(); - std::string token; - std::vector res; - - while ((pos_end = str.find(delim, pos_start)) != std::string::npos) { - token = str.substr (pos_start, pos_end - pos_start); - pos_start = pos_end + delim_len; - res.push_back (token); - } - - res.push_back(str.substr(pos_start)); - return res; - } - - // https://stackoverflow.com/a/46931770 - inline std::vector split_string(const std::string& str, char delim = '\n') { - std::vector result; - std::stringstream ss (str); - size_t count = 0; - - for (char c : str) { - if (c == delim) count ++; - } - - result.reserve(count); - std::string item; - - while (getline(ss, item, delim)) { - result.push_back(item); - } - - return result; - } - - inline std::vector normalize_strings(const std::vector& strings) { - std::vector output; - output.reserve(strings.size()); - - for (auto& string : strings) { - if (string.empty()) { - continue; - } - - output.push_back(trim(string)); - } - - return output; - } - - // https://codereview.stackexchange.com/a/22907 - inline std::vector read_whole(const std::string& path) { - std::ifstream ifs(path, std::ios::binary|std::ios::ate); - - if (!ifs.is_open() || ifs.bad()) { - throw std::runtime_error {"Could not open file '" + path + "'"}; - } - - std::ifstream::pos_type pos = ifs.tellg(); - - if (pos == 0) { - return {}; - } - - std::vector result(pos); - - ifs.seekg(0, std::ios::beg); - ifs.read(result.data(), pos); - - return result; - } - - /// Generate random ASCII string of the given length - inline std::string random_string(size_t length) { - static const std::string_view alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - static std::uniform_int_distribution dist(0, static_cast(alphabet.size() - 1)); - thread_local std::mt19937 rng {std::random_device{} ()}; - - std::string out; - out.reserve(length); - - for (std::size_t i = 0; i < length; ++i) { - out.push_back(alphabet[dist(rng)]); - } - - return out; - } - - inline void load_file_into(std::ifstream& file, std::string& string) { - file.seekg(0, std::ios::end); - string.reserve(file.tellg()); - file.seekg(0, std::ios::beg); - string.assign(std::istreambuf_iterator {file}, std::istreambuf_iterator {}); - } - /// Unsigned divide (round up) template constexpr auto divide_up(T a, T b) { @@ -207,16 +57,6 @@ namespace asmio::util { return std::endian::native == endian ? value : std::byteswap(value); } - template - auto get_int_or(T value) { - if constexpr (std::is_pointer_v) return 0; else return value; - } - - template - auto get_ptr_or(T value) { - if constexpr (std::is_pointer_v) return value; else return nullptr; - } - /// Iterate the container and check if it contains the given value template bool contains(const T& container, const auto& value) { @@ -227,208 +67,4 @@ namespace asmio::util { return false; } - /// Convert string to lower case - inline std::string to_lower(std::string s) { - std::ranges::transform(s, s.begin(), [] (const int c) noexcept -> int { return std::tolower(c); }); - return s; - } - - /// Get the minimal number of bytes (in power-of-two increments) needed to encode an unsigned value - constexpr int min_unsigned_bytes(uint64_t value) { - if (value > 0xFFFFFFFF) return 8; - if (value > 0xFFFF) return 4; - if (value > 0xFF) return 2; - - return 1; - } - - /** - * Check how many bits can be truncated from a signed number before - * it changed its value, assuming one bit is needed for the sign. - */ - constexpr int count_redundant_sign_bits(const int64_t value) { - return __builtin_clzll(value >= 0 ? value : ~value) - 1; - } - - /** - * Check if given number signed number can be losslessly - * encoded in the given number of bits, taking into account the sign bits. - */ - constexpr bool is_signed_encodable(int64_t value, int64_t bits) { - return (64 - count_redundant_sign_bits(value)) <= bits; - } - - /** - * Count the number of 'ones' form the leading (most - * significant) side of a number. - */ - constexpr int count_trailing_ones(uint64_t value) { - return __builtin_ctzll(~value); // ctz(~x) == cto(x) - } - - /** - * Create a constant with a specific number of bits set, - * starting on the least-significant side. - */ - template - constexpr T bit_fill(uint64_t count) { - if (count >= sizeof(T) * 8) { - return std::numeric_limits::max(); - } - - return (T(1) << count) - T(1); - } - - /** - * Get the minimal number of bytes (in power-of-two increments) needed - * to losslessly encode the given signed integer. - */ - constexpr int min_signed_bytes(int64_t value) { - const uint64_t uval = static_cast(value); - - if ((value & 0xFFFF'FFFF'FFFF'FF80) == 0xFFFF'FFFF'FFFF'FF80) return 1; // 1 byte long negative - if (uval <= 0x0000'0000'0000'007F) return 1; // 1 byte long positive - - if ((value & 0xFFFF'FFFF'FFFF'8000) == 0xFFFF'FFFF'FFFF'8000) return 2; // 2 byte long negative - if (uval <= 0x0000'0000'0000'7FFF) return 2; // 2 byte long positive - - if ((value & 0xFFFF'FFFF'8000'0000) == 0xFFFF'FFFF'8000'0000) return 4; // 4 byte long negative - if (uval <= 0x0000'0000'7FFF'FFFF) return 4; // 4 byte long positive - - return 8; - } - - constexpr int min_optimistic_bytes(uint64_t value) { - return std::min(min_unsigned_bytes(value), min_signed_bytes(value)); - } - - /// Convert integer into hex string - /// @see https://stackoverflow.com/a/5100745 - template - std::string to_hex(T value) { - std::stringstream stream; - stream << "0x" << std::setfill('0') << std::setw(sizeof(T)*2) << std::hex << value; - return stream.str(); - } - - constexpr uint64_t hash_djb2(const char* str, size_t bytes) { - uint64_t hash = 5381; - - for (size_t i = 0; i < bytes; i ++) { - hash = (hash << 5) + hash * 33 + str[i]; - } - - if (hash == 0) { - return 1; - } - - return hash; - } - - constexpr uint64_t hash_tmix64(uint64_t x) { - x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9; - x = (x ^ (x >> 27)) * 0x94d049bb133111eb; - x = (x ^ (x >> 31)); - - if (x == 0) { - return 1; - } - - return x; - } - - template - constexpr uint64_t hash_djb2(const std::vector& data) { - return hash_djb2(reinterpret_cast(data.data()), data.size() * sizeof(T)); - } - - constexpr int digit_value(char c) { - if (c >= '0' && c <= '9') { - return c - '0'; - } - - if (c >= 'a' && c <= 'f') { - return c - 'a' + 10; - } - - if (c >= 'A' && c <= 'F') { - return c - 'A' + 10; - } - - throw std::runtime_error {"Invalid digit '" + std::string(1, c) + "'"}; - } - - constexpr int64_t parse_int(const char* str) { - - int base = 10; - size_t length = strlen(str); - int64_t sign = 1; - - if (str[0] == '+') { - str ++; - } else if (str[0] == '-') { - str ++; - sign = -1; - } - - if (length > 2 && str[0] == '0') { - if (str[1] == 'x') { str += 2; base = 16; } - else if (str[1] == 'o') { str += 2; base = 8; } - else if (str[1] == 'b') { str += 2; base = 2; } - } - - // update length as we could have changed starting point - length = strlen(str); - int64_t value = 0; - - for (size_t i = 0; i < length; i ++) { - char c = str[i]; - - if (c == '\'' || c == '_') { - continue; - } - - int next = digit_value(c); - - if (next >= base) { - throw std::runtime_error {"invalid number format"}; - } - - value = (value * base) + next; - } - - return value * sign; - - } - - inline int64_t parse_decimal(const std::string_view& str) { - - int64_t value; - - if (std::from_chars(str.data(), str.data() + str.size(), value).ec == std::errc {}) { - return value; - } - - throw std::runtime_error {"Can't parse '" + std::string(str) + "' as an integer!"}; - } - - inline long double parse_float(const char* str) { - - size_t offset; - long double value; - - try { - value = std::stold(str, &offset); - } catch(...) { - throw std::runtime_error {"exception thrown"}; - } - - if (offset != strlen(str)) { - throw std::runtime_error {"some input ignored"}; - } - - return value; - - } - } \ No newline at end of file diff --git a/src/asmio/util/axon.hpp b/src/asmio/util/axon.hpp new file mode 100644 index 0000000..a0bee11 --- /dev/null +++ b/src/asmio/util/axon.hpp @@ -0,0 +1,306 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "trait.hpp" + +namespace asmio::axon { + + // external raw pointer call (Args...) + // | + // \|/ + // +-----------------------+ +-----buffer-----+ + // | bind_native( | =generated=> | asm trampoline | --------> set_call_context() + // | forward_lambda_call, | | ContextWrapper | | + // | std::function | | buffer size | | thread_local + // | ) -> calls emit_*() | +----------------+ | ContextWrapper* + // +-----------------------+ | | context_side_channel + // /|\ | raw ptr \|/ | + // | \|/ +-------------------------------+ \|/ + // +-----------------------+ | forward_with_context(Args...) | <- get_call_context() + // | coerce(std::function) | +-------------------------------+ + // +-----------------------+ | + // /|\ | raw ptr | If we used decay(lambda) + // | \|/ \|/ context *is* the lambda (with capture) + // +---------------+ +--------------------------------------+ + // | decay(lambda) | | forward_lambda_call(lambda, Args...) | + // +---------------+ +--------------------------------------+ + // | | + // \|/ \|/ + // raw function ptr lambda::operator(Args...) + + namespace detail { + + // Used at the trampoline generation level to wrap the given function and context, this way to + // common trampoline function can call the given callback with the given context (and any + // extra arguments). This architecture allows us to handle adding the extra Context argument in + // C++, avoiding all issues related from screwing with the calling convention too much. + template + struct ContextWrapper { + R (* function) (const T*, Args...); + T context; + }; + + // Used for a very small window during each call to the raw function to pass the ContextWrapper + // pointer from generated assembly trampoline to the C++ trampoline (forward_with_context). + inline thread_local void* context_side_channel; + + // Called from the generated assembly trampoline + template + void set_call_context(T* wrapped) noexcept { + context_side_channel = wrapped; + } + + // Called from the C++ trampoline (forward_with_context) + template + T* get_call_context() noexcept { + return static_cast(context_side_channel); + } + + // The C++ trampoline called from generated assembly + template + auto forward_with_context(Args... args) { + auto wrapped = get_call_context(); + return wrapped->function(&wrapped->context, args...); + } + + template + void emit_x86(SegmentedBuffer& buffer) { + x86::BufferWriter writer (buffer); + +# if PLATFORM_WINDOWS + x86::Registry arg_0 = x86::RCX; +# elif PLATFORM_UNIX + x86::Registry arg_0 = x86::RDI; +# endif + + writer.section(MemoryFlag::X); + writer.put_mov(x86::RAX, set_call_context); + +#if PLATFORM_UNIX + writer.put_push(x86::RDI); // sysv + writer.put_push(x86::RSI); // sysv +#endif + + writer.put_push(x86::RDX); // sysv, windows + writer.put_push(x86::RCX); // sysv, windows + writer.put_push(x86::R8); // sysv, windows + writer.put_push(x86::R9); // sysv, windows + + writer.put_mov(arg_0, "context"); + writer.put_call(x86::RAX); + writer.put_mov(x86::RAX, forward_with_context); + + writer.put_pop(x86::R9); + writer.put_pop(x86::R8); + writer.put_pop(x86::RCX); + writer.put_pop(x86::RDX); + +#if PLATFORM_UNIX + writer.put_pop(x86::RSI); + writer.put_pop(x86::RDI); +#endif + + writer.put_jmp(x86::RAX); // tail call + } + + template + void emit_riscv(SegmentedBuffer& buffer) { + riscv::BufferWriter writer (buffer); + + constexpr int64_t size = 8 * 10; + + writer.section(MemoryFlag::X); + writer.put_add(riscv::SP, riscv::SP, -size); + writer.put_sq(riscv::RA, riscv::SP, 0*8); // return address + writer.put_sq(riscv::A0, riscv::SP, 1*8); + writer.put_sq(riscv::A1, riscv::SP, 2*8); + writer.put_sq(riscv::A2, riscv::SP, 3*8); + writer.put_sq(riscv::A3, riscv::SP, 4*8); + writer.put_sq(riscv::A4, riscv::SP, 5*8); + writer.put_sq(riscv::A5, riscv::SP, 6*8); + writer.put_sq(riscv::A6, riscv::SP, 7*8); + writer.put_sq(riscv::A7, riscv::SP, 8*8); + + writer.put_mov(riscv::T0, reinterpret_cast(set_call_context)); + writer.put_mov(riscv::A0, "context"); + writer.put_jalr(riscv::RA, riscv::T0); + + // restore + writer.put_lq(riscv::RA, riscv::SP, 0*8); + writer.put_lq(riscv::A0, riscv::SP, 1*8); + writer.put_lq(riscv::A1, riscv::SP, 2*8); + writer.put_lq(riscv::A2, riscv::SP, 3*8); + writer.put_lq(riscv::A3, riscv::SP, 4*8); + writer.put_lq(riscv::A4, riscv::SP, 5*8); + writer.put_lq(riscv::A5, riscv::SP, 6*8); + writer.put_lq(riscv::A6, riscv::SP, 7*8); + writer.put_lq(riscv::A7, riscv::SP, 8*8); + + writer.put_add(riscv::SP, riscv::SP, size); + + writer.put_mov(riscv::T0, reinterpret_cast(forward_with_context)); + writer.put_jr(riscv::T0); // tail call + } + + template + void emit_aarch64(SegmentedBuffer& buffer) { + arm::BufferWriter writer (buffer); + + writer.section(MemoryFlag::X); + writer.put_istp(arm::X29, arm::X30, arm::SP, -16); + writer.put_istp(arm::X0, arm::X1, arm::SP, -16); + writer.put_istp(arm::X2, arm::X3, arm::SP, -16); + writer.put_istp(arm::X4, arm::X5, arm::SP, -16); + writer.put_istp(arm::X6, arm::X7, arm::SP, -16); + + writer.put_mov(arm::X16, reinterpret_cast(set_call_context)); + writer.put_adr(arm::X0, "context"); + writer.put_blr(arm::X16); + + // restore + writer.put_ldpi(arm::X6, arm::X7, arm::SP, 16); + writer.put_ldpi(arm::X4, arm::X5, arm::SP, 16); + writer.put_ldpi(arm::X2, arm::X3, arm::SP, 16); + writer.put_ldpi(arm::X0, arm::X1, arm::SP, 16); + writer.put_ldpi(arm::X29, arm::X30, arm::SP, 16); + + writer.put_mov(arm::X16, reinterpret_cast(forward_with_context)); + writer.put_br(arm::X16); // tail call + } + + // Get a pointer to the size field of generated function, + // that starts at the first byte of the second page + inline uint64_t* get_function_size(uint8_t* function) { + return reinterpret_cast(function + page_size()); + } + + // Generate the assembly trampoline and create the final raw function pointer + template + auto bind_native(R (* function) (const T* ctx, Args...), T context) -> R (*) (Args...) { + + using Wrapper = ContextWrapper; + + SegmentedBuffer buffer; + + // We stores the context in thread_local, we could avoid that if we + // handled the calling convention ourselves but that is hard and best left to the compiler. + +#if ARCH_X86 + emit_x86(buffer); +#elif ARCH_RISCV64 + emit_riscv(buffer); +#elif ARCH_AARCH64 + emit_aarch64(buffer); +#elif +# error "Unimplemented architecture!" +#endif + + BasicBufferWriter writer(buffer); + + writer.section(MemoryFlag::R | MemoryFlag::W); + writer.label("size"); + writer.put_qword(0); // buffer size will be written here + writer.label("context"); + writer.put_space(sizeof(Wrapper)); + writer.put_space(util::align_padding(sizeof(Wrapper), static_cast(16))); + + ExecutableBuffer exe = to_executable(buffer); + const auto page = page_size(); + + Wrapper* target = reinterpret_cast(exe.address("context")); + std::construct_at(target, function, context); + + if (exe.size() < 2 * page) { + throw std::runtime_error("Bound function too small, got " + std::to_string(exe.size()) + " bytes, expected " + std::to_string(2 * page) + " bytes!"); + } + + if (exe.offset("size") != page) { + throw std::runtime_error("Size marker misplaced, got offset +" + std::to_string(exe.offset("size")) + ", expected +" + std::to_string(page) + "!"); + } + + *get_function_size(exe.address()) = exe.size(); + return reinterpret_cast(exe.own()); + } + + template + struct capture_forwarder {}; + + template + struct capture_forwarder> { + + // The function that gets bound to context in bind_native() for all lambdas + // it then forward the call to the object held in the context - the actual lambda. + template + static auto forward_lambda_call(const L* ctx_lambda, Args... args) noexcept(util::function_traits::nothrow) { + return (*ctx_lambda)(std::forward(args)...); + } + + // Thin wrapper, we need this to unpack the parameter pack from the tuple object + // we obtained from function_traits::arguments. + template + static auto coerce(L&& ctx_lambda) { + return bind_native(forward_lambda_call, std::forward(ctx_lambda)); + } + + }; + + } + + /* + * Free a raw allocated function object. Needs te be be used on pointers + * returned by function in this module after they are no longer in use to + * avoid a memory leak. + */ + template + void free_function(R (* function) (Args...)) { + auto* buffer = reinterpret_cast(function); + free_pages(buffer, *detail::get_function_size(buffer)); + } + + /** + * Bind a set of arguments to a function pointer and return a raw function pointer. + * This can be useful when interfacing with C code that lacks "userdata" call context. + * The returned raw pointer must be freed after use using free_function(). + */ + template + auto specialize(R (* function) (Args... args), Args... args) -> R (*) () { + struct Context { + R (* function) (Args... args); + std::tuple args; + }; + + Context ctx {}; + ctx.function = function; + ctx.args = {args...}; + + using Trampoline = R (*) (const Context* ctx); + + Trampoline func = [] (const Context* ctx) { + return std::apply(ctx->function, ctx->args); + }; + + return detail::bind_native(func, std::move(ctx)); + } + + /** + * Convert a capturing (non-polymorphic) lambda into a raw function pointer. + * This can be useful when interfacing with C code that lacks "userdata" call context. + * The returned raw pointer must be freed after use using free_function(). + */ + template requires util::functional + auto decay(F callable) -> util::function_traits::pointer_type { + using trait = util::function_traits; + + if constexpr (util::decayable_lambda) { + return + callable; + } else { + return detail::capture_forwarder::coerce(std::move(static_cast(callable))); + } + } + +} \ No newline at end of file diff --git a/src/asmio/util/bits.hpp b/src/asmio/util/bits.hpp new file mode 100644 index 0000000..b5a797f --- /dev/null +++ b/src/asmio/util/bits.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include + +namespace asmio::util { + + /** + * Get the minimal number of bytes (in power-of-two increments) needed to encode an unsigned value + */ + constexpr int min_unsigned_bytes(uint64_t value) { + if (value > 0xFFFFFFFF) return 8; + if (value > 0xFFFF) return 4; + if (value > 0xFF) return 2; + + return 1; + } + + /** + * Check how many bits can be truncated from a signed number before + * it changed its value, assuming one bit is needed for the sign. + */ + constexpr int count_redundant_sign_bits(const int64_t value) { + return __builtin_clzll(value >= 0 ? value : ~value) - 1; + } + + /** + * Check if given number signed number can be losslessly + * encoded in the given number of bits, taking into account the sign bits. + */ + constexpr bool is_signed_encodable(int64_t value, int64_t bits) { + return (64 - count_redundant_sign_bits(value)) <= bits; + } + + /** + * Count the number of 'zeros' form the trailing (least + * significant) side of a number. + */ + constexpr int count_trailing_zeros(uint64_t value) { + return __builtin_ctzll(value); + } + + /** + * Count the number of 'ones' form the trailing (least + * significant) side of a number. + */ + constexpr int count_trailing_ones(uint64_t value) { + return count_trailing_zeros(~value); // ctz(~x) == cto(x) + } + + /** + * Count the number of 'zeros' form the leading (most + * significant) side of a number. + */ + constexpr int count_leading_zeros(uint64_t value) { + return __builtin_clzll(value); + } + + /** + * Count the number of 'ones' form the leading (most + * significant) side of a number. + */ + constexpr int count_leading_ones(uint64_t value) { + return count_leading_zeros(~value); // clz(~x) == clo(x) + } + + /** + * Count the number of repeating bits form the leading (most + * significant) side of a number. + */ + constexpr int count_leading(uint64_t value) { + return std::max(count_leading_zeros(value), count_leading_ones(value)); + } + + /** + * Create a constant with a specific number of bits set, + * starting on the least-significant side. + */ + template + constexpr T bit_fill(uint64_t count) { + if (count >= sizeof(T) * 8) { + return std::numeric_limits::max(); + } + + return (T(1) << count) - T(1); + } + + /** + * Get the minimal number of bytes (in power-of-two increments) needed + * to losslessly encode the given signed integer. + */ + constexpr int min_signed_bytes(int64_t value) { + const uint64_t uval = static_cast(value); + + if ((value & 0xFFFF'FFFF'FFFF'FF80) == 0xFFFF'FFFF'FFFF'FF80) return 1; // 1 byte long negative + if (uval <= 0x0000'0000'0000'007F) return 1; // 1 byte long positive + + if ((value & 0xFFFF'FFFF'FFFF'8000) == 0xFFFF'FFFF'FFFF'8000) return 2; // 2 byte long negative + if (uval <= 0x0000'0000'0000'7FFF) return 2; // 2 byte long positive + + if ((value & 0xFFFF'FFFF'8000'0000) == 0xFFFF'FFFF'8000'0000) return 4; // 4 byte long negative + if (uval <= 0x0000'0000'7FFF'FFFF) return 4; // 4 byte long positive + + return 8; + } + + constexpr int min_optimistic_bytes(uint64_t value) { + return std::min(min_unsigned_bytes(value), min_signed_bytes(value)); + } + + +} \ No newline at end of file diff --git a/src/asmio/util/chunk.hpp b/src/asmio/util/chunk.hpp index 476064f..8d6be13 100644 --- a/src/asmio/util/chunk.hpp +++ b/src/asmio/util/chunk.hpp @@ -14,7 +14,7 @@ namespace asmio { concept can_static_cast = requires (F from) { static_cast(from); }; template - using codec_param_of = util::function_decompose::template arg_type<1>; + using codec_param_of = util::function_traits::template arg_type<1>; class ChunkBuffer { @@ -274,8 +274,8 @@ namespace asmio { * Safer variant of insert(), use this method * when possible to avoid concealing the type. */ - template - requires ((!codec_for && castable) && ...) + template + requires ((!codec_for && util::castable) && ...) ChunkBuffer* put(const A&... value) { if constexpr (std::is_integral_v && sizeof(T) > 1) { return insert(util::native_to_endian(static_cast(value), endianness)...); diff --git a/src/asmio/util/hash.hpp b/src/asmio/util/hash.hpp new file mode 100644 index 0000000..7b4cee1 --- /dev/null +++ b/src/asmio/util/hash.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include + +namespace asmio::util { + + constexpr uint64_t djb2(const char* str, size_t bytes) { + uint64_t hash = 5381; + + for (size_t i = 0; i < bytes; i ++) { + hash = (hash << 5) + hash * 33 + str[i]; + } + + if (hash == 0) { + return 1; + } + + return hash; + } + + constexpr uint64_t tmix64(uint64_t x) { + x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9; + x = (x ^ (x >> 27)) * 0x94d049bb133111eb; + x = (x ^ (x >> 31)); + + if (x == 0) { + return 1; + } + + return x; + } + + template + constexpr uint64_t djb2(const std::vector& data) { + return hash_djb2(reinterpret_cast(data.data()), data.size() * sizeof(T)); + } + +} \ No newline at end of file diff --git a/src/asmio/util/string.hpp b/src/asmio/util/string.hpp new file mode 100644 index 0000000..bcee223 --- /dev/null +++ b/src/asmio/util/string.hpp @@ -0,0 +1,240 @@ +#pragma once + +#include + +namespace asmio::util { + + // https://stackoverflow.com/a/6500499 + inline std::string trim(const std::string& str) { + int prefix = 0; // will point to the first non-space char + int suffix = str.length() - 1; + + while (prefix < static_cast(str.length())) { + if (!std::isspace(str[prefix])) { + break; + } + + prefix ++; + } + + while (suffix > 0) { + if (!std::isspace(str[suffix])) { + break; + } + + suffix --; + } + + int count = suffix - prefix + 1; + + if (count < 0) { + count = 0; + } + + return str.substr(prefix, count); + } + + inline std::vector normalize_strings(const std::vector& strings) { + std::vector output; + output.reserve(strings.size()); + + for (auto& string : strings) { + if (string.empty()) { + continue; + } + + output.push_back(trim(string)); + } + + return output; + } + + // https://stackoverflow.com/a/46931770 + inline std::vector split_string(const std::string& str, const std::string_view& delim) { + size_t pos_start = 0, pos_end, delim_len = delim.length(); + std::string token; + std::vector res; + + while ((pos_end = str.find(delim, pos_start)) != std::string::npos) { + token = str.substr (pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + res.push_back (token); + } + + res.push_back(str.substr(pos_start)); + return res; + } + + // https://stackoverflow.com/a/46931770 + inline std::vector split_string(const std::string& str, char delim = '\n') { + std::vector result; + std::stringstream ss (str); + size_t count = 0; + + for (char c : str) { + if (c == delim) count ++; + } + + result.reserve(count); + std::string item; + + while (getline(ss, item, delim)) { + result.push_back(item); + } + + return result; + } + + /// Generate random ASCII string of the given length + inline std::string random_string(size_t length) { + static const std::string_view alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + static std::uniform_int_distribution dist(0, static_cast(alphabet.size() - 1)); + thread_local std::mt19937 rng {std::random_device{} ()}; + + std::string out; + out.reserve(length); + + for (std::size_t i = 0; i < length; ++i) { + out.push_back(alphabet[dist(rng)]); + } + + return out; + } + + /// Convert string to lower case + inline std::string to_lower(std::string s) { + std::ranges::transform(s, s.begin(), [] (const int c) noexcept -> int { return std::tolower(c); }); + return s; + } + + /// Convert integer into hex string + /// @see https://stackoverflow.com/a/5100745 + template + std::string to_hex(T value) { + std::stringstream stream; + stream << "0x" << std::setfill('0') << std::setw(sizeof(T)*2) << std::hex << value; + return stream.str(); + } + + /// Get numerical value of a hexadecimal (or decimal) digit + constexpr int digit_value(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + + if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } + + if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + + throw std::runtime_error {"Invalid digit '" + std::string(1, c) + "'"}; + } + + /// parse any number from string + constexpr int64_t parse_int(const char* str) { + + int base = 10; + size_t length = strlen(str); + int64_t sign = 1; + + if (str[0] == '+') { + str ++; + } else if (str[0] == '-') { + str ++; + sign = -1; + } + + if (length > 2 && str[0] == '0') { + if (str[1] == 'x') { str += 2; base = 16; } + else if (str[1] == 'o') { str += 2; base = 8; } + else if (str[1] == 'b') { str += 2; base = 2; } + } + + // update length as we could have changed starting point + length = strlen(str); + int64_t value = 0; + + for (size_t i = 0; i < length; i ++) { + char c = str[i]; + + if (c == '\'' || c == '_') { + continue; + } + + int next = digit_value(c); + + if (next >= base) { + throw std::runtime_error {"invalid number format"}; + } + + value = (value * base) + next; + } + + return value * sign; + + } + + /// parse decimal number from string + inline int64_t parse_decimal(const std::string_view& str) { + + int64_t value; + + if (std::from_chars(str.data(), str.data() + str.size(), value).ec == std::errc {}) { + return value; + } + + throw std::runtime_error {"Can't parse '" + std::string(str) + "' as an integer!"}; + } + /// parse float number from string + inline long double parse_float(const char* str) { + + size_t offset; + long double value; + + try { + value = std::stold(str, &offset); + } catch(...) { + throw std::runtime_error {"exception thrown"}; + } + + if (offset != strlen(str)) { + throw std::runtime_error {"some input ignored"}; + } + + return value; + + } + + // https://codereview.stackexchange.com/a/22907 + inline std::vector read_whole(const std::string& path) { + std::ifstream ifs(path, std::ios::binary|std::ios::ate); + + if (!ifs.is_open() || ifs.bad()) { + throw std::runtime_error {"Could not open file '" + path + "'"}; + } + + std::ifstream::pos_type pos = ifs.tellg(); + + if (pos == 0) { + return {}; + } + + std::vector result(pos); + + ifs.seekg(0, std::ios::beg); + ifs.read(result.data(), pos); + + return result; + } + + inline void load_file_into(std::ifstream& file, std::string& string) { + file.seekg(0, std::ios::end); + string.reserve(file.tellg()); + file.seekg(0, std::ios::beg); + string.assign(std::istreambuf_iterator {file}, std::istreambuf_iterator {}); + } + +} \ No newline at end of file diff --git a/src/asmio/util/trait.hpp b/src/asmio/util/trait.hpp new file mode 100644 index 0000000..431a7ee --- /dev/null +++ b/src/asmio/util/trait.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include + +#define ENUM_BEGIN __INTERNAL__ = __LINE__, +#define ENUM_END ENUM_LENGTH = __LINE__ - __INTERNAL__ - 1, + +namespace asmio::util { + + template + concept integer_like = std::is_integral_v || std::is_enum_v; + + template + concept nothrow_constuctable = std::is_nothrow_constructible_v; + + template + concept trivially_copyable = std::is_trivially_copyable_v; + + template + concept castable = requires (const A& arg) { static_cast(arg); }; + + template + concept is_enumeration = requires { std::is_enum_v; }; + + template + constexpr size_t enum_length = static_cast(T::ENUM_LENGTH); + + template + struct base_function_traits { + using return_type = R; + using arguments = std::tuple; + + template + using arg_type = std::tuple_element_t; + + static constexpr size_t arity = sizeof...(Args); + + using function_type = R(Args...); + using capture_type = std::function; + using pointer_type = std::add_pointer_t; + }; + + template + struct function_traits; + + // function type + template + struct function_traits : base_function_traits { + static constexpr bool nothrow = false; + }; + + template + struct function_traits : base_function_traits { + static constexpr bool nothrow = true; + }; + + // function pointer + template + struct function_traits : function_traits {}; + + template + struct function_traits : function_traits {}; + + // member function pointer + template + struct function_traits : function_traits {}; + + template + struct function_traits : function_traits {}; + + // const member function pointer + template + struct function_traits : function_traits {}; + + template + struct function_traits : function_traits {}; + + // functors / lambdas + template + struct function_traits : function_traits {}; + + template + concept functional = requires { typename function_traits; }; + + template + concept decayable_lambda = functional && requires(T lambda) { { + lambda } -> std::same_as::pointer_type>; }; + +} \ No newline at end of file diff --git a/src/asmio/x86/argument/condition.hpp b/src/asmio/x86/argument/condition.hpp index 87cc2bc..e96cd59 100644 --- a/src/asmio/x86/argument/condition.hpp +++ b/src/asmio/x86/argument/condition.hpp @@ -15,23 +15,48 @@ namespace asmio::x86 { NRD = 3, ///< Unordered (unordered, non-signaling) }; + // The words "Above" and "Below" imply unsigned comparisons, + // while "Greater" and "Less" imply signed comparisons. + // + // | RiscV | AArch64 | x86-64 | + // | ------ | -------- | ------ | + // | EQ | EQ | EQ | + // | NE | NE | NE | + // | LT | LT | LT | + // | GT | GT | GT | + // | GE | GE | GE | + // | LE | LE | LE | + // | LTU* | CS | LTU | + // | GTU* | HI | GTU | + // | GEU* | CC | GEU | + // | LEU* | LS | LEU | + // | | MI | S | + // | | PL | NS | + // | | VS | O | + // | | VC | NO | + // | | AL, NV | | + // | | | P | + // | | | NP | + // + // (*) Non standard, implemented by swaping instruction operands enum struct Condition : uint8_t { - O = 0x0, ///< Overflow - NO = 0x1, ///< Not Overflow - B = 0x2, C = 0x2, NAE = 0x2, ///< Below, Carry, Not Above or Equal - NB = 0x3, NC = 0x3, AE = 0x3, ///< Not Below, Not Carry, Above or Equal - E = 0x4, Z = 0x4, ///< Equal, Zero - NE = 0x5, NZ = 0x5, ///< Not Equal, Not Zero - BE = 0x6, NA = 0x6, ///< Below or Equal, Not Above - NBE = 0x7, A = 0x7, ///< Not Below or Equal, Above - S = 0x8, ///< Sign - NS = 0x9, ///< Not Sign - P = 0xA, PE = 0xA, ///< Parity, Parity Even - NP = 0xB, PO = 0xB, ///< Not Parity, Parity Odd - L = 0xC, NGE = 0xC, ///< Less, Not Greater or Equal - NL = 0xD, GE = 0xD, ///< Not Less, Greater or Equal - LE = 0xE, NG = 0xE, ///< Less or Equal, Not Greater - NLE = 0xf, G = 0xF, ///< Not Less or Equal, Greater + O = 0x0, ///< Overflow + NO = 0x1, ///< No Overflow + LTU = 0x2, B = 0x2, C = 0x2, NAE = 0x2, ///< Less Than Unsigned, Below, Carry, Not Above or Equal + GEU = 0x3, NB = 0x3, NC = 0x3, AE = 0x3, ///< Greaten Than Unsinged, Not Below, Not Carry, Above or Equal + EQ = 0x4, E = 0x4, Z = 0x4, ///< Equal, Zero + NE = 0x5, NZ = 0x5, ///< Not Equal, Not Zero + LEU = 0x6, BE = 0x6, NA = 0x6, ///< Less or Equal Unsigned, Below or Equal, Not Above + GTU = 0x7, NBE = 0x7, A = 0x7, ///< Grater than or Equal Unsigned, Not Below or Equal, Above + + S = 0x8, ///< Minus, Sign + NS = 0x9, ///< Plus, No Sign + P = 0xA, PE = 0xA, ///< Parity, Parity Even + NP = 0xB, PO = 0xB, ///< Not Parity, Parity Odd + L = 0xC, LT = 0xC, NGE = 0xC, ///< Less, Not Greater or Equal + NL = 0xD, GE = 0xD, ///< Not Less, Greater or Equal + LE = 0xE, NG = 0xE, ///< Less or Equal, Not Greater + NLE = 0xf, GT = 0xF, G = 0xF, ///< Not Less or Equal, Greater }; } \ No newline at end of file diff --git a/src/asmio/x86/argument/location.hpp b/src/asmio/x86/argument/location.hpp index ac4fbac..5a0cb2b 100644 --- a/src/asmio/x86/argument/location.hpp +++ b/src/asmio/x86/argument/location.hpp @@ -30,11 +30,16 @@ namespace asmio::x86 { public: template - Location(T offset = 0) // can the same thing be archived with some smart overload? - : Location(UNSET, UNSET, 1, util::get_int_or(offset), util::get_ptr_or(offset), VOID, false) {} + Location(T* ptr = 0) + : Location(UNSET, UNSET, 1, reinterpret_cast(ptr), nullptr, VOID, false) {} - Location(const Label& label) - : Location(UNSET, UNSET, 1, 0, label, VOID, false) {} + template + Location(T value = 0) + : Location(UNSET, UNSET, 1, static_cast(value), nullptr, VOID, false) {} + + Location(const std::string_view& label) : Location(Label{label}) {} + Location(const char* label) : Location(Label{label}) {} + Location(const Label& label) : Location(UNSET, UNSET, 1, 0, label, VOID, false) {} Location(Registry registry) : Location(registry, UNSET, 1, 0, {}, registry.size, false) {} diff --git a/src/asmio/x86/argument/registry.hpp b/src/asmio/x86/argument/registry.hpp index 141a762..7468ab6 100644 --- a/src/asmio/x86/argument/registry.hpp +++ b/src/asmio/x86/argument/registry.hpp @@ -94,6 +94,56 @@ namespace asmio::x86 { }; + /* + * Windows x86-64 calling convention: + * RAX - temporary register + * RCX - temporary register + * RDX - temporary register + * R8 - temporary register + * R9 - temporary register + * R10 - temporary register + * R11 - temporary register + * + * RBX - saved register + * RBP - saved register + * RDI - saved register + * RSI - saved register + * RSP - saved register + * R12 - saved register + * R13 - saved register + * R14 - saved register + * R15 - saved register + * + * Aditionally when a function is called the stack needs to be 16-aligned, + * and a shadow space of 32 bytes needs to be allocated on it. Extra arguments + * are pushed onto the stack after the scrach space. + */ + + /* + * Linux (and Unix-like) x86-64 calling convention: + * RAX - temporary register + * RDI - temporary register (return value pointer) + * RSI - temporary register + * RDX - temporary register + * RCX - temporary register + * R8 - temporary register + * R9 - temporary register + * R10 - temporary register (link register) + * R11 - temporary register + * + * RBX - saved register + * RBP - saved register + * RSP - saved register + * R12 - saved register + * R13 - saved register + * R14 - saved register + * R15 - saved register + * + * Return value is stored in RAX:RDX, or in the space pointed to by RDI. + * Leaf function can use 128 bytes of stack without allocating it (a so called + * red-zone), as system first substracts that many bytes before touching the stack. + */ + /* * i386 */ diff --git a/src/asmio/x86/module.cpp b/src/asmio/x86/module.cpp index d7de809..12946f6 100644 --- a/src/asmio/x86/module.cpp +++ b/src/asmio/x86/module.cpp @@ -348,6 +348,12 @@ namespace asmio::x86 { if (raw == "ng") return Condition::NG; if (raw == "nle") return Condition::NLE; if (raw == "g") return Condition::G; + if (raw == "gt") return Condition::GT; + if (raw == "lt") return Condition::LT; + if (raw == "gtu") return Condition::GTU; + if (raw == "ltu") return Condition::LTU; + if (raw == "geu") return Condition::GEU; + if (raw == "leu") return Condition::LEU; throw std::runtime_error {"Unknown condition '" + raw + "'"}; } diff --git a/src/asmio/x86/writer_cpu.cpp b/src/asmio/x86/writer_cpu.cpp index 9c7d962..d401180 100644 --- a/src/asmio/x86/writer_cpu.cpp +++ b/src/asmio/x86/writer_cpu.cpp @@ -740,7 +740,7 @@ namespace asmio::x86 { return; } - throw std::runtime_error {"Invalid operand"}; + throw std::runtime_error {"Invalid operand, expected label, memory location, or register"}; } ///< Jump on Condition diff --git a/src/tasml/error.hpp b/src/tasml/error.hpp index 39c1227..77d2e55 100644 --- a/src/tasml/error.hpp +++ b/src/tasml/error.hpp @@ -2,6 +2,7 @@ #include #include +#include namespace tasml { diff --git a/test/aarch64.cpp b/test/aarch64.cpp index 24f17f7..7fa924a 100644 --- a/test/aarch64.cpp +++ b/test/aarch64.cpp @@ -2353,6 +2353,46 @@ namespace test { }; + TEST (aarch64_exec_external_call) { + + int (*function) () = [] () { + return 41; + }; + + SegmentedBuffer buffer; + BufferWriter writer(buffer); + + writer.put_istp(X29, X30, SP, -16); + writer.put_mov(X28, SP); + + writer.put_mov(X9, reinterpret_cast(function)); + writer.put_blr(X9); + + writer.put_ldpi(X29, X30, SP, 16); + writer.put_ret(); + + ExecutableBuffer exe = to_executable(buffer); + CHECK(exe.call_u64(), 41); + + }; + + TEST (aarch64_exec_external_tail_call) { + + int (*function) () = [] () { + return 42; + }; + + SegmentedBuffer buffer; + BufferWriter writer(buffer); + + writer.put_mov(X9, reinterpret_cast(function)); + writer.put_br(X9); + + ExecutableBuffer exe = to_executable(buffer); + CHECK(exe.call_u64(), 42); + + }; + #endif } \ No newline at end of file diff --git a/test/riscv.cpp b/test/riscv.cpp new file mode 100644 index 0000000..a03624b --- /dev/null +++ b/test/riscv.cpp @@ -0,0 +1,536 @@ +#include +#include +#include + +#include "test.hpp" +#include "vstl.hpp" + +namespace test { + + using namespace asmio; + using namespace asmio::riscv; + + TEST (rv32i_check_nop) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_nop(); + + segmented.link(0); + std::vector s0 = {0x13, 0x00, 0x00, 0x00}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + } + + TEST (rv32i_check_add) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_add(X1, X7, 13); + + segmented.link(0); + std::vector s0 = {0x93, 0x80, 0xd3, 0x00}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (rv32i_check_basic_op_imm) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_add(X0, X4, 12); + writer.put_xor(X4, X11, 7); + writer.put_or(X11, X12, 77); + writer.put_and(X12, X11, 88); + writer.put_sll(X11, X3, 7); + writer.put_srl(X3, X7, 4); + writer.put_sra(X7, X1, 3); + writer.put_slt(X1, X12, 3); + writer.put_sltu(X12, X2, 1000); + + segmented.link(0); + std::vector s0 = {0x13, 0x00, 0xc2, 0x00, 0x13, 0xc2, 0x75, 0x00, 0x93, 0x65, 0xd6, 0x04, 0x13, 0xf6, 0x85, 0x05, 0x93, 0x95, 0x71, 0x00, 0x93, 0xd1, 0x43, 0x00, 0x93, 0xd3, 0x30, 0x40, 0x93, 0x20, 0x36, 0x00, 0x13, 0x36, 0x81, 0x3e}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (rv32i_check_basic_op_reg) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_add(X0, X4, X22); + writer.put_sub(X0, X4, X22); + writer.put_xor(X4, X11, X22); + writer.put_or(X11, X12, X22); + writer.put_and(X12, X11, X22); + writer.put_sll(X11, X3, X22); + writer.put_srl(X3, X7, X22); + writer.put_sra(X7, X1, X22); + writer.put_slt(X1, X12, X22); + writer.put_sltu(X12, X2, X22); + + segmented.link(0); + std::vector s0 = {0x33, 0x00, 0x62, 0x01, 0x33, 0x00, 0x62, 0x41, 0x33, 0xc2, 0x65, 0x01, 0xb3, 0x65, 0x66, 0x01, 0x33, 0xf6, 0x65, 0x01, 0xb3, 0x95, 0x61, 0x01, 0xb3, 0xd1, 0x63, 0x01, 0xb3, 0xd3, 0x60, 0x41, 0xb3, 0x20, 0x66, 0x01, 0x33, 0x36, 0x61, 0x01}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (rv32i_check_basic_loads) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_lb(X0, X5, 100); + writer.put_lw(X1, X6, 200); + writer.put_ld(X2, X7, 300); + writer.put_lbu(X3, X8, 400); + writer.put_lwu(X4, X9, 500); + writer.put_ldu(X4, X9, 600); + writer.put_lq(X4, X9, 700); + + segmented.link(0); + std::vector s0 = {0x03, 0x80, 0x42, 0x06, 0x83, 0x10, 0x83, 0x0c, 0x03, 0xa1, 0xc3, 0x12, 0x83, 0x41, 0x04, 0x19, 0x03, 0xd2, 0x44, 0x1f, 0x03, 0xe2, 0x84, 0x25, 0x03, 0xb2, 0xc4, 0x2b}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (rv32i_check_basic_stores) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_sb(X1, X4, 0x100); + writer.put_sw(X2, X5, 0x200); + writer.put_sd(X3, X6, 0x300); + writer.put_sq(X4, X7, 0x400); + + segmented.link(0); + std::vector s0 = {0x23, 0x00, 0x12, 0x10, 0x23, 0x90, 0x22, 0x20, 0x23, 0x20, 0x33, 0x30, 0x23, 0xb0, 0x43, 0x40}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (rv32i_check_ecall_ebreak) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_ecall(); + writer.put_ebreak(); + + segmented.link(0); + std::vector s0 = {0x73, 0x00, 0x00, 0x00, 0x73, 0x00, 0x10, 0x00}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (rv32i_check_branch) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_beq(X2, X2, "end"); + writer.put_and(X1, X0, X0); + writer.label("start"); + writer.put_add(X1, X1, 10); + writer.put_bgt(X2, X1, "start"); + writer.label("end"); + + segmented.link(0); + std::vector s0 = {0x63, 0x08, 0x21, 0x00, 0xb3, 0x70, 0x00, 0x00, 0x93, 0x80, 0xa0, 0x00, 0xe3, 0xce, 0x20, 0xfe}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (rv32i_check_branch_inverted) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.label("test"); + writer.put_b(Condition::GE, X10, X11, "test"); + writer.put_b(Condition::LE, X10, X11, "test"); + writer.put_b(Condition::GTU, X10, X11, "test"); + + segmented.link(0); + std::vector s0 = {0x63, 0x50, 0xb5, 0x00, 0xe3, 0xde, 0xa5, 0xfe, 0xe3, 0xec, 0xa5, 0xfe}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (rv32i_check_jump) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.label("start"); + writer.put_add(X1, X1, 10); + writer.put_jal("start"); + writer.put_jal(X8, "start"); + writer.put_jalr(X6, X7); + + segmented.link(0); + std::vector s0 = {0x93, 0x80, 0xa0, 0x00, 0xef, 0xf0, 0xdf, 0xff, 0x6f, 0xf4, 0x9f, 0xff, 0x67, 0x83, 0x03, 0x00}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (rv32i_check_lui) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_lui(X1, 1); + + segmented.link(0); + std::vector s0 = {0xb7, 0x10, 0x00, 0x00}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (rv32i_check_aliases) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_mov(X11, X12); + writer.put_neg(X12, X13); + writer.put_not(X12, X13); + writer.put_ret(); + + segmented.link(0); + std::vector s0 = {0x93, 0x05, 0x06, 0x00, 0x33, 0x06, 0xd0, 0x40, 0x13, 0xc6, 0xf6, 0xff, 0x67, 0x80, 0x00, 0x00}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + } + + TEST (rv32m_check) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_mul(X1, X2, X3); + writer.put_mulh(X1, X2, X3); + writer.put_mulhsu(X1, X2, X3); + writer.put_mulhu(X1, X2, X3); + writer.put_div(X1, X2, X3); + writer.put_divu(X1, X2, X3); + writer.put_rem(X1, X2, X3); + writer.put_remu(X1, X2, X3); + + segmented.link(0); + std::vector s0 = {0xb3, 0x00, 0x31, 0x02, 0xb3, 0x10, 0x31, 0x02, 0xb3, 0x20, 0x31, 0x02, 0xb3, 0x30, 0x31, 0x02, 0xb3, 0x40, 0x31, 0x02, 0xb3, 0x50, 0x31, 0x02, 0xb3, 0x60, 0x31, 0x02, 0xb3, 0x70, 0x31, 0x02}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (rv64m_check) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_muld(X1, X2, X3); + writer.put_divd(X1, X2, X3); + writer.put_divud(X1, X2, X3); + writer.put_remd(X1, X2, X3); + writer.put_remud(X1, X2, X3); + + segmented.link(0); + std::vector s0 = {0xbb, 0x00, 0x31, 0x02, 0xbb, 0x40, 0x31, 0x02, 0xbb, 0x50, 0x31, 0x02, 0xbb, 0x60, 0x31, 0x02, 0xbb, 0x70, 0x31, 0x02}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (rv64a_check) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_lr(T0, T1, DWORD, Order::ACQUIRE); + writer.put_lr(T6, T5, QWORD, Order::RELEASE); + writer.put_sc(T0, T1, T2, DWORD, Order::NONE); + + writer.put_amoswap(T0, T1, T2, DWORD, Order::ACQUIRE); + writer.put_amoadd(T0, T1, T2, QWORD, Order::RELEASE); + writer.put_amoand(T0, T1, T2, DWORD, Order::ACQUIRE_RELEASE); + writer.put_amoor(T0, T1, T2, QWORD, Order::NONE); + writer.put_amoxor(T0, T1, T2, DWORD, Order::ACQUIRE); + writer.put_amomax(T0, T1, T2, QWORD, Order::RELEASE); + writer.put_amomin(T0, T1, T2, DWORD, Order::ACQUIRE_RELEASE); + writer.put_amomaxu(T0, T1, T2, QWORD, Order::NONE); + writer.put_amominu(T0, T1, T2, DWORD, Order::ACQUIRE); + + EXPECT_THROW(std::runtime_error) { + writer.put_amomin(T0, T1, T2, WORD, Order::NONE); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_amomin(T0, T1, T2, BYTE, Order::NONE); + }; + + segmented.link(0); + std::vector s0 = {0xaf, 0x22, 0x03, 0x14, 0xaf, 0x3f, 0x0f, 0x12, 0xaf, 0x22, 0x73, 0x18, 0xaf, 0x22, 0x73, 0x0c, 0xaf, 0x32, 0x73, 0x02, 0xaf, 0x22, 0x73, 0x66, 0xaf, 0x32, 0x73, 0x40, 0xaf, 0x22, 0x73, 0x24, 0xaf, 0x32, 0x73, 0xa2, 0xaf, 0x22, 0x73, 0x86, 0xaf, 0x32, 0x73, 0xe0, 0xaf, 0x22, 0x73, 0xc4}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + /* + * region Executable + * Begin architecture depended tests for Risc-V + */ + +#if ARCH_RISCV64 + + TEST (riscv_exec_leaf_function) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_add(A0, X0, 42); + writer.put_ret(); + + auto exe = to_executable(segmented); + CHECK(exe.call_u64(), 42); + + }; + + TEST (riscv_exec_nop_neg) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_nop(); + writer.put_add(T0, X0, 7); + writer.put_neg(A0, T0); + writer.put_ret(); + + auto exe = to_executable(segmented); + CHECK(exe.call_i64(), -7); + + }; + + TEST (riscv_exec_jump) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_j("skip_30"); + writer.put_add(A0, X0, 30); + writer.put_ret(); + + writer.label("skip_30"); + writer.put_add(A0, X0, 40); + writer.put_ret(); + + auto exe = to_executable(segmented); + CHECK(exe.call_u64(), 40); + + }; + + TEST (riscv_exec_multiply) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_add(T0, X0, 11); + writer.put_add(T1, X0, 4); + writer.put_add(T2, X0, 3); + writer.put_or(T1, T1, T2); + writer.put_mul(A0, T0, T1); + writer.put_ret(); + + auto exe = to_executable(segmented); + CHECK(exe.call_u64(), 77); + + }; + + TEST (riscv_exec_lui) { + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_or(A0, X0, 42); + writer.put_lui(A0, 0xfffff); + writer.put_sll(A0, A0, 4); + writer.put_add(A0, A0, 0x7ff); + writer.put_ret(); + + auto exe = to_executable(segmented); + CHECK(exe.call_u64(), 0xffffffffffff07ff); + }; + + TEST (riscv_exec_mov_imm) { + + auto verify = [] (uint64_t value, uint32_t instructions) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_or(A0, X0, 0xABC); + const uint32_t start = segmented.current().offset; + writer.put_mov(A0, value); + const uint32_t end = segmented.current().offset; + + writer.put_ret(); + + auto exe = to_executable(segmented); + + // "0x00" added as VSTL formatting hint + CHECK(exe.call_u64(), 0x00 + value); + CHECK((end - start), (instructions * 4)); + + }; + + verify(0, 1); + verify(1, 1); + verify(-1, 1); + verify(0x7ff, 1); + verify(0xfff, 2); + verify(0x8bcd'1234, 4); + verify(0x7bcd'1234, 2); + verify(0x7bcd'7fff, 2); + verify(0x1234'1234'1234'1234, 8); + verify(0xffff'ffff'8223'4523, 2); + verify(0xffff'ff12'3452'3123, 4); + verify(0x5555'0000'0044'4444, 5); + verify(0x66666'000, 1); + verify(0x1000'0000'0000'0001, 3); + + }; + + TEST (riscv_exec_mov_fuzzer) { + + for (int i = 0; i < 5000; i ++) { + uint64_t value = vstl_self.random.next(); + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::RISCV; + + writer.put_and(A0, X0, 0); + writer.put_mov(A0, value); + writer.put_ret(); + + auto exe = to_executable(segmented); + + // "0x00" added as VSTL formatting hint + CHECK(exe.call_u64(), 0x00 + value); + CHECK(exe.call_u64(), 0x00 + value); + } + + }; + + TEST (riscv_exec_external_call) { + + int (*function) () = [] () { + return 42; + }; + + SegmentedBuffer buffer; + BufferWriter writer(buffer); + + writer.put_add(SP, SP, -16); + writer.put_sq(RA, SP, 8); + writer.put_mov(T0, reinterpret_cast(function)); + writer.put_jalr(RA, T0); + writer.put_lq(RA, SP, 8); + writer.put_add(SP, SP, 16); + writer.put_jr(RA); + + ExecutableBuffer exe = to_executable(buffer); + CHECK(exe.call_u64(), 42); + + }; + + TEST (riscv_exec_external_tail_call) { + + int (*function) () = [] () { + return 42; + }; + + SegmentedBuffer buffer; + BufferWriter writer(buffer); + + writer.put_mov(T0, reinterpret_cast(function)); + writer.put_jr(T0); + + ExecutableBuffer exe = to_executable(buffer); + CHECK(exe.call_u64(), 42); + + }; + + TEST (riscv_exec_lookup_symbol_forward) { + + SegmentedBuffer buffer; + BufferWriter writer(buffer); + + writer.label("start"); + writer.put_mov(T0, "var"); // look forward + writer.put_lq(A0, T0); + writer.put_add(A0, A0, -0x11); + writer.put_ret(); + + writer.put_nop(); + writer.put_nop(); + writer.put_nop(); + writer.put_nop(); + + writer.label("var"); + writer.put_qword(0x54); + + ExecutableBuffer exe = to_executable(buffer); + CHECK(exe.call_u64("start"), 0x43); + + }; + + TEST (riscv_exec_lookup_symbol_back) { + + SegmentedBuffer buffer; + BufferWriter writer(buffer); + + writer.label("var"); + writer.put_qword(0x54); + + writer.put_nop(); + writer.put_nop(); + writer.put_nop(); + writer.put_nop(); + + writer.label("start"); + writer.put_mov(T0, "var"); // look back + writer.put_lq(A0, T0); + writer.put_add(A0, A0, -0x12); + writer.put_ret(); + + ExecutableBuffer exe = to_executable(buffer); + CHECK(exe.call_u64("start"), 0x42); + + }; + +#endif + +} \ No newline at end of file diff --git a/test/tasml.cpp b/test/tasml.cpp index e402722..d5885f8 100644 --- a/test/tasml.cpp +++ b/test/tasml.cpp @@ -332,7 +332,7 @@ namespace test { CHECK(unresolved.size(), 1); CHECK(unresolved[0].label.string(), "bar"); - CHECK(unresolved[0].type.relocation, ElfRelocationType::X86_64_PC32); + CHECK(unresolved[0].type->relocation, ElfRelocationType::X86_64_PC32); }; diff --git a/test/test.hpp b/test/test.hpp index 962f676..35d2b10 100644 --- a/test/test.hpp +++ b/test/test.hpp @@ -2,6 +2,10 @@ #include #include +#include +#include +#include +#include namespace test { @@ -20,7 +24,7 @@ namespace test { asmio::ObjectFile baked = asmio::to_elf(buffer, asmio::Label::UNSET).bake(); asmio::util::TempFile temp {baked}; - std::string out = asmio::call_shell("objdump --visualize-jumps -wxd " + extra_flags + temp.path()); + std::string out = asmio::call_shell("riscv64-linux-gnu-objdump --visualize-jumps -wxd " + extra_flags + temp.path()); printf("%s\n", out.c_str()); printf("\nAuto-generated assertions:\n\n"); diff --git a/test/unit.cpp b/test/unit.cpp index a1e9e89..f4bce27 100644 --- a/test/unit.cpp +++ b/test/unit.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "vstl.hpp" @@ -32,6 +33,15 @@ namespace test { }; + TEST (unit_count_trailing) { + + CHECK(util::count_trailing_zeros(0b11100), 2); + CHECK(util::count_trailing_zeros(0b11011), 0); + CHECK(util::count_trailing_ones(0b11100), 0); + CHECK(util::count_trailing_ones(0b11011), 2); + + }; + TEST (unit_label_string_view) { std::string base = "aaabbb"; @@ -501,4 +511,117 @@ namespace test { }; + TEST (util_function_traits) { + + auto f1 = [] (int a, int b) noexcept { + return a; + }; + + auto f2 = [f1] (int a, std::string, bool flag) { + return a; + }; + + CHECK(util::function_traits::arity, 2); + CHECK(util::function_traits::nothrow, true); + + CHECK(util::function_traits::arity, 3); + CHECK(util::function_traits::nothrow, false); + + }; + + TEST (util_baked_function) { + + int a = 42; + + struct MyData { + int* ap; + int magic; + }; + + using Func = int (*) (const MyData*, int, int); + + Func f = [] (const MyData* ctx, int p1, int p2) noexcept { + *ctx->ap = ctx->magic * 2 + p1; + return ctx->magic * 3 + p2; + }; + + MyData ctx {&a, 7}; + + // testing the internals :P + auto bound = axon::detail::bind_native(f, ctx); + + int r = bound(4, 5); + + CHECK(r, 26); + CHECK(a, 18); + + axon::free_function(bound); + + }; + + TEST (util_baked_function_pack) { + + int (*raw) (int, int) = [] (int a, int b) noexcept { + return a * 3 + b; + }; + + using RawFunction = int (*) (); + + RawFunction f11 = axon::specialize(raw, 1, 1); + RawFunction f35 = axon::specialize(raw, 3, 5); + RawFunction f74 = axon::specialize(raw, 7, 4); + + CHECK(f11(), 4); + CHECK(f35(), 14); + CHECK(f74(), 25); + + axon::free_function(f11); + axon::free_function(f35); + axon::free_function(f74); + + }; + + TEST (util_baked_function_decay) { + + int cb = 11; + + int(*f1)(int) = axon::decay([&] (int a) noexcept -> int { + cb ++; + return a + cb; + }); + + auto lambda = [&] (std::string str, int i) noexcept -> int { + cb += i; + return str.length(); + }; + + int(*f2)(std::string, int) = axon::decay(lambda); + + CHECK(f1(1), 13); + CHECK(f1(1), 14); + CHECK(f2("hmmm", 1), 4); + CHECK(f1(2), 17); + CHECK(f1(1), 17); + + CHECK(cb, 16); + + axon::free_function(f1); + axon::free_function(f2); + + }; + + TEST (util_baked_function_throw) { + std::string msg = "hmm"; + + int (*f)() = axon::decay([&] () -> int { + throw std::runtime_error (msg); + }); + + EXPECT_THROW(std::runtime_error) { + f(); + }; + + axon::free_function(f); + }; + } \ No newline at end of file diff --git a/test/x86.cpp b/test/x86.cpp index 0f4f0f7..d25fe03 100644 --- a/test/x86.cpp +++ b/test/x86.cpp @@ -4253,6 +4253,24 @@ namespace test { }; + TEST (exec_external_call) { + + int (*function) () = [] () { + return 42; + }; + + SegmentedBuffer buffer; + BufferWriter writer(buffer); + + writer.put_mov(RAX, function); + writer.put_call(RAX); + writer.put_ret(); + + ExecutableBuffer exe = to_executable(buffer); + CHECK(exe.call_u64(), 42); + + }; + TEST (tasml_exec_string_prefix) { std::string code = R"( @@ -4285,5 +4303,47 @@ namespace test { }; + TEST (exec_segmented_buffer_merge) { + SegmentedBuffer a, b; + + if (BufferWriter writer {a}) { + + writer.section(MemoryFlag::R); + writer.label("blob"); + writer.put_qword(100); + + writer.section(MemoryFlag::X | MemoryFlag::R); + writer.label("helper"); + writer.put_add(RAX, ref("blob")); + writer.put_ret(); + + } + + if (BufferWriter writer {b}) { + + writer.section(MemoryFlag::R); + writer.label("goomba"); + writer.put_qword(50); + + writer.section(MemoryFlag::X | MemoryFlag::R); + writer.label("start"); + writer.put_xor(RAX, RAX); + writer.put_add(RAX, ref("goomba")); + writer.put_call("helper"); + writer.put_ret(); + + } + + a.merge(std::move(b)); + + CHECK(a.segments().size(), 5); // on of the RWX sections gets dropped + + auto exe = to_executable(a); + int result = exe.call_u64("start"); + + CHECK(result, 150); + + } + #endif ;} diff --git a/util/Dockerfile b/util/Dockerfile new file mode 100644 index 0000000..1bef69d --- /dev/null +++ b/util/Dockerfile @@ -0,0 +1,3 @@ +FROM ubuntu:24.04 + +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y cmake git python3 build-essential mold \ No newline at end of file diff --git a/util/test-aarch64 b/util/test-aarch64 new file mode 100755 index 0000000..7aec6a1 --- /dev/null +++ b/util/test-aarch64 @@ -0,0 +1,14 @@ +#!/bin/env bash + +docker build --platform="linux/arm64" -f util/Dockerfile -t asmiov/arm64 . + +BUILD_DIR="docker-build-arm64" + +read -r -d '' COMMAND <<- EOM + cmake -B $BUILD_DIR . && + cmake --build $BUILD_DIR -j\$(nproc) && + cd $BUILD_DIR && + ./test +EOM + +docker run --rm -it -v "$PWD:/src" -w /src asmiov/arm64 bash -c "$COMMAND" \ No newline at end of file diff --git a/util/test-riscv64 b/util/test-riscv64 new file mode 100755 index 0000000..0c46463 --- /dev/null +++ b/util/test-riscv64 @@ -0,0 +1,14 @@ +#!/bin/env bash + +docker build --platform="linux/riscv64" -f util/Dockerfile -t asmiov/riscv64 . + +BUILD_DIR="docker-build-riscv64" + +read -r -d '' COMMAND <<- EOM + cmake -B $BUILD_DIR . && + cmake --build $BUILD_DIR -j\$(nproc) && + cd $BUILD_DIR && + ./test +EOM + +docker run --rm -it -v "$PWD:/src" -w /src asmiov/riscv64 bash -c "$COMMAND" \ No newline at end of file