├── .editorconfig ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── CMakeModules └── FindVTune.cmake ├── LICENSE ├── README.md ├── docs └── MISSING.md ├── external ├── CMakeLists.txt └── dynarmic │ ├── CMakeLists.txt │ ├── LICENSE-dynarmic │ ├── LICENSE-mcl │ └── include │ ├── dynarmic │ └── devirtualize_x64.hpp │ └── mcl │ ├── bit_cast.hpp │ ├── mp │ └── typelist │ │ └── list.hpp │ └── type_traits │ └── function_info.hpp ├── include └── lunatic │ ├── coprocessor.hpp │ ├── cpu.hpp │ ├── detail │ ├── meta.hpp │ └── punning.hpp │ ├── integer.hpp │ └── memory.hpp ├── src ├── CMakeLists.txt ├── backend │ ├── backend.hpp │ └── x86_64 │ │ ├── backend.cpp │ │ ├── backend.hpp │ │ ├── common.hpp │ │ ├── compile_alu.cpp │ │ ├── compile_context.cpp │ │ ├── compile_coprocessor.cpp │ │ ├── compile_flags.cpp │ │ ├── compile_flush.cpp │ │ ├── compile_memory.cpp │ │ ├── compile_multiply.cpp │ │ ├── compile_shift.cpp │ │ ├── register_allocator.cpp │ │ ├── register_allocator.hpp │ │ └── vtune.hpp ├── common │ ├── aligned_memory.hpp │ ├── bit.hpp │ ├── compiler.hpp │ ├── meta.hpp │ ├── optional.hpp │ ├── pool_allocator.cpp │ └── pool_allocator.hpp ├── frontend │ ├── basic_block.hpp │ ├── basic_block_cache.hpp │ ├── decode │ │ ├── arm.hpp │ │ ├── definition │ │ │ ├── block_data_transfer.hpp │ │ │ ├── branch_exchange.hpp │ │ │ ├── branch_relative.hpp │ │ │ ├── common.hpp │ │ │ ├── coprocessor_register_transfer.hpp │ │ │ ├── count_leading_zeros.hpp │ │ │ ├── data_processing.hpp │ │ │ ├── exception.hpp │ │ │ ├── halfword_signed_transfer.hpp │ │ │ ├── multiply.hpp │ │ │ ├── multiply_long.hpp │ │ │ ├── saturating_add_sub.hpp │ │ │ ├── signed_halfword_multiply.hpp │ │ │ ├── single_data_swap.hpp │ │ │ ├── single_data_transfer.hpp │ │ │ ├── status_transfer.hpp │ │ │ └── thumb_bl_suffix.hpp │ │ └── thumb.hpp │ ├── ir │ │ ├── emitter.cpp │ │ ├── emitter.hpp │ │ ├── opcode.hpp │ │ ├── register.hpp │ │ └── value.hpp │ ├── ir_opt │ │ ├── constant_propagation.cpp │ │ ├── constant_propagation.hpp │ │ ├── context_load_store_elision.cpp │ │ ├── context_load_store_elision.hpp │ │ ├── dead_code_elision.cpp │ │ ├── dead_code_elision.hpp │ │ ├── dead_flag_elision.cpp │ │ ├── dead_flag_elision.hpp │ │ └── pass.hpp │ ├── state.cpp │ ├── state.hpp │ └── translator │ │ ├── handle │ │ ├── block_data_transfer.cpp │ │ ├── branch_exchange.cpp │ │ ├── branch_relative.cpp │ │ ├── coprocessor_register_transfer.cpp │ │ ├── count_leading_zeros.cpp │ │ ├── data_processing.cpp │ │ ├── exception.cpp │ │ ├── halfword_signed_transfer.cpp │ │ ├── multiply.cpp │ │ ├── multiply_long.cpp │ │ ├── saturating_add_sub.cpp │ │ ├── signed_halfword_multiply.cpp │ │ ├── single_data_swap.cpp │ │ ├── single_data_transfer.cpp │ │ ├── status_transfer.cpp │ │ └── thumb_bl_suffix.cpp │ │ ├── translator.cpp │ │ └── translator.hpp └── jit.cpp └── test ├── CMakeLists.txt ├── CMakeModules └── FindSDL2.cmake └── main.cpp /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.{cpp, hpp}] 10 | charset = utf-8 11 | indent_style = space 12 | indent_size = 2 13 | max_line_length = 100 14 | 15 | [{CMakeLists.txt, *.cmake}] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | 3 | # Visual Studio 4 | out 5 | .vs 6 | CMakeSettings.json 7 | 8 | # JetBrains CLion 9 | .idea 10 | cmake-build-* 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/fmt"] 2 | path = external/fmt 3 | url = https://github.com/fmtlib/fmt 4 | [submodule "external/xbyak"] 5 | path = external/xbyak 6 | url = https://github.com/herumi/xbyak 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.2) 2 | 3 | # Determine whether this is a standalone project or included by other projects 4 | set(IS_SUBPROJECT ON) 5 | if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 6 | set(IS_SUBPROJECT OFF) 7 | endif() 8 | 9 | option(LUNATIC_USE_EXTERNAL_FMT "Use externally provided {fmt} library." OFF) 10 | option(LUNATIC_USE_VTUNE "Use VTune JIT Profiling API if available" OFF) 11 | option(LUNATIC_INCLUDE_XBYAK_FROM_DIRECTORY "Get Xbyak from xbyak/xbyak.h and not xbyak.h" ON) 12 | 13 | project(lunatic-root) 14 | 15 | add_subdirectory(external) 16 | add_subdirectory(src) 17 | if(NOT IS_SUBPROJECT) 18 | add_subdirectory(test) 19 | endif() 20 | -------------------------------------------------------------------------------- /CMakeModules/FindVTune.cmake: -------------------------------------------------------------------------------- 1 | # - Find VTune jitprofiling. 2 | # Defines: 3 | # VTune_FOUND 4 | # VTune_INCLUDE_DIRS 5 | # VTune_LIBRARIES 6 | 7 | set(dirs 8 | "$ENV{VTUNE_PROFILER_2022_DIR}/" 9 | "C:/Program Files (x86)/Intel/oneAPI/vtune/2022.2.0" 10 | "$ENV{VTUNE_AMPLIFIER_XE_2013_DIR}/" 11 | "C:/Program Files (x86)/Intel/VTune Amplifier XE 2013/" 12 | "$ENV{VTUNE_AMPLIFIER_XE_2011_DIR}/" 13 | "C:/Program Files (x86)/Intel/VTune Amplifier XE 2011/" 14 | ) 15 | 16 | find_path(VTune_INCLUDE_DIRS jitprofiling.h 17 | PATHS ${dirs} 18 | PATH_SUFFIXES include) 19 | 20 | if (CMAKE_SIZEOF_VOID_P MATCHES "8") 21 | set(vtune_lib_dir lib64) 22 | else() 23 | set(vtune_lib_dir lib32) 24 | endif() 25 | 26 | find_library(VTune_LIBRARIES jitprofiling 27 | HINTS "${VTune_INCLUDE_DIRS}/.." 28 | PATHS ${dirs} 29 | PATH_SUFFIXES ${vtune_lib_dir}) 30 | 31 | include(FindPackageHandleStandardArgs) 32 | find_package_handle_standard_args( 33 | VTune DEFAULT_MSG VTune_LIBRARIES VTune_INCLUDE_DIRS) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2021 fleroviux 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or 8 | other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without 11 | specific prior written permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 15 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 16 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 17 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 18 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## lunatic 2 | 3 | ![license](https://img.shields.io/github/license/fleroviux/lunatic) 4 | [![CodeFactor](https://www.codefactor.io/repository/github/fleroviux/lunatic/badge)](https://www.codefactor.io/repository/github/fleroviux/lunatic) 5 | 6 | lunatic is an ARM (32-bit) dynamic recompiler for low-level emulators. 7 | 8 | ## General 9 | 10 | lunatic currently supports the following guest and host architectures: 11 | 12 | #### Guest architectures 13 | - ARMv4T 14 | - ARMv5TE 15 | 16 | See ![MISSING.md](docs/MISSING.md) for a list of missing instructions. 17 | 18 | #### Host architectures 19 | - x86_64 20 | 21 | ## Credit 22 | 23 | I would like to thank the following people: 24 | - [merry](https://github.com/merryhime) 25 | - [wheremyfoodat](https://github.com/wheremyfoodat) 26 | 27 | for answering my questions (especially related to x86_64) and pointing out bugs and improvements. 28 | 29 | ## License 30 | 31 | lunatic is licensed under the New/Modified BSD license. See [LICENSE](LICENSE) file for details. 32 | 33 | lunatic uses [Xbyak](https://github.com/herumi/xbyak/) which is licensed under the New/Modified BSD license. 34 | 35 | lunatic uses C++ devirtualization code from [dynarmic](https://github.com/merryhime/dynarmic) (including supporting code from [mcl](https://github.com/merryhime/mcl)). 36 | dynarmic is licensed under the 0BSD license. mcl is licensed under the MIT license. 37 | 38 | lunatic uses [{fmt}](https://github.com/fmtlib/fmt/) which is licensed under a modified [MIT license](https://github.com/fmtlib/fmt/blob/master/LICENSE.rst): 39 | ``` 40 | Copyright (c) 2012 - present, Victor Zverovich 41 | 42 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 43 | 44 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 45 | 46 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 47 | 48 | --- Optional exception to the license --- 49 | 50 | As an exception, if, as a result of your compiling your source code, portions of this Software are embedded into a machine-executable object form of such source code, you may redistribute such embedded portions in such object form without including the above copyright and permission notices. 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/MISSING.md: -------------------------------------------------------------------------------- 1 | The following instructions are currently **NOT** emulated: 2 | - BKPT (ARMv5) 3 | - LDRT/STRT (treated like LDR/STR) 4 | - VFP instructions 5 | - CDP (coprocessor data processing) 6 | - STC (store coprocessor) 7 | - LDC (load coprocessor) 8 | - CDP2, STC2, LDC2, MCR2, MRC2 (ARMv5?) 9 | - MCRR (ARMv5TE) 10 | - MRRC (ARMv5TE) 11 | -------------------------------------------------------------------------------- /external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(NOT LUNATIC_USE_EXTERNAL_FMT) 2 | add_subdirectory(fmt) 3 | endif() 4 | 5 | if (NOT TARGET xbyak) 6 | add_subdirectory(xbyak) 7 | endif() 8 | 9 | add_subdirectory(dynarmic) -------------------------------------------------------------------------------- /external/dynarmic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 3.5) 3 | 4 | project(dynarmic CXX) 5 | 6 | set(CMAKE_CXX_STANDARD 17) 7 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 8 | 9 | set(HEADERS_PUBLIC 10 | include/dynarmic/devirtualize_x64.hpp 11 | include/mcl/bit_cast.hpp 12 | include/mcl/mp/typelist/list.hpp 13 | include/mcl/type_traits/function_info.hpp 14 | ) 15 | 16 | add_library(dynarmic INTERFACE ${HEADERS_PUBLIC}) 17 | target_include_directories(dynarmic INTERFACE include) -------------------------------------------------------------------------------- /external/dynarmic/LICENSE-dynarmic: -------------------------------------------------------------------------------- 1 | Copyright (C) 2017 merryhime 2 | 3 | Permission to use, copy, modify, and/or distribute this software for 4 | any purpose with or without fee is hereby granted. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 7 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 8 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 9 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 10 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 11 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 12 | OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 13 | -------------------------------------------------------------------------------- /external/dynarmic/LICENSE-mcl: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 merryhime 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /external/dynarmic/include/dynarmic/devirtualize_x64.hpp: -------------------------------------------------------------------------------- 1 | /* This file is part of the dynarmic project. 2 | * Copyright (c) 2018 MerryMage 3 | * SPDX-License-Identifier: 0BSD 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | using u64 = std::uint64_t; 13 | 14 | #include 15 | #include 16 | 17 | namespace Dynarmic { 18 | namespace Backend::X64 { 19 | 20 | struct DevirtualizedCall { 21 | u64 fn; 22 | u64 arg; 23 | }; 24 | 25 | namespace impl { 26 | 27 | template 28 | struct ThunkBuilder; 29 | 30 | template 31 | struct ThunkBuilder { 32 | static R Thunk(C* this_, Args... args) { 33 | return (this_->*mfp)(std::forward(args)...); 34 | } 35 | }; 36 | 37 | } // namespace impl 38 | 39 | template 40 | DevirtualizedCall DevirtualizeGeneric(mcl::class_type* this_) { 41 | return DevirtualizedCall{&impl::ThunkBuilder::Thunk, reinterpret_cast(this_)}; 42 | } 43 | 44 | template 45 | DevirtualizedCall DevirtualizeWindows(mcl::class_type* this_) { 46 | static_assert(sizeof(mfp) == 8); 47 | return DevirtualizedCall{mcl::bit_cast(mfp), reinterpret_cast(this_)}; 48 | } 49 | 50 | template 51 | DevirtualizedCall DevirtualizeItanium(mcl::class_type* this_) { 52 | struct MemberFunctionPointer { 53 | /// For a non-virtual function, this is a simple function pointer. 54 | /// For a virtual function, it is (1 + virtual table offset in bytes). 55 | u64 ptr; 56 | /// The required adjustment to `this`, prior to the call. 57 | u64 adj; 58 | } mfp_struct = mcl::bit_cast(mfp); 59 | 60 | static_assert(sizeof(MemberFunctionPointer) == 16); 61 | static_assert(sizeof(MemberFunctionPointer) == sizeof(mfp)); 62 | 63 | u64 fn_ptr = mfp_struct.ptr; 64 | u64 this_ptr = reinterpret_cast(this_) + mfp_struct.adj; 65 | if (mfp_struct.ptr & 1) { 66 | u64 vtable = mcl::bit_cast_pointee(this_ptr); 67 | fn_ptr = mcl::bit_cast_pointee(vtable + fn_ptr - 1); 68 | } 69 | return DevirtualizedCall{fn_ptr, this_ptr}; 70 | } 71 | 72 | template 73 | DevirtualizedCall Devirtualize(mcl::class_type* this_) { 74 | #if defined(__APPLE__) || defined(linux) || defined(__linux) || defined(__linux__) 75 | return DevirtualizeItanium(this_); 76 | #elif defined(__MINGW64__) 77 | return DevirtualizeItanium(this_); 78 | #elif defined(_WIN32) 79 | return DevirtualizeWindows(this_); 80 | #else 81 | return DevirtualizeGeneric(this_); 82 | #endif 83 | } 84 | 85 | } // namespace Backend::X64 86 | } // namespace Dynarmic 87 | -------------------------------------------------------------------------------- /external/dynarmic/include/mcl/bit_cast.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of the mcl project. 2 | // Copyright (c) 2022 merryhime 3 | // SPDX-License-Identifier: MIT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace mcl { 11 | 12 | /// Reinterpret objects of one type as another by bit-casting between object representations. 13 | template 14 | inline Dest bit_cast(const Source& source) noexcept 15 | { 16 | static_assert(sizeof(Dest) == sizeof(Source), "size of destination and source objects must be equal"); 17 | static_assert(std::is_trivially_copyable_v, "destination type must be trivially copyable."); 18 | static_assert(std::is_trivially_copyable_v, "source type must be trivially copyable"); 19 | 20 | std::aligned_storage_t dest; 21 | std::memcpy(&dest, &source, sizeof(dest)); 22 | return reinterpret_cast(dest); 23 | } 24 | 25 | /// Reinterpret objects of any arbitrary type as another type by bit-casting between object representations. 26 | /// Note that here we do not verify if source pointed to by source_ptr has enough bytes to read from. 27 | template 28 | inline Dest bit_cast_pointee(const SourcePtr source_ptr) noexcept 29 | { 30 | static_assert(sizeof(SourcePtr) == sizeof(void*), "source pointer must have size of a pointer"); 31 | static_assert(std::is_trivially_copyable_v, "destination type must be trivially copyable."); 32 | 33 | std::aligned_storage_t dest; 34 | std::memcpy(&dest, bit_cast(source_ptr), sizeof(dest)); 35 | return reinterpret_cast(dest); 36 | } 37 | 38 | } // namespace mcl 39 | -------------------------------------------------------------------------------- /external/dynarmic/include/mcl/mp/typelist/list.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of the mcl project. 2 | // Copyright (c) 2022 merryhime 3 | // SPDX-License-Identifier: MIT 4 | 5 | #pragma once 6 | 7 | namespace mcl::mp { 8 | 9 | /// Contains a list of types 10 | template 11 | struct list {}; 12 | 13 | } // namespace mcl::mp 14 | -------------------------------------------------------------------------------- /external/dynarmic/include/mcl/type_traits/function_info.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of the mcl project. 2 | // Copyright (c) 2022 merryhime 3 | // SPDX-License-Identifier: MIT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace mcl { 13 | 14 | template 15 | struct function_info : function_info {}; 16 | 17 | template 18 | struct function_info { 19 | using return_type = R; 20 | using parameter_list = mp::list; 21 | static constexpr std::size_t parameter_count = sizeof...(As); 22 | 23 | using equivalent_function_type = R(As...); 24 | 25 | template 26 | struct parameter { 27 | static_assert(I < parameter_count, "Non-existent parameter"); 28 | using type = std::tuple_element_t>; 29 | }; 30 | }; 31 | 32 | template 33 | struct function_info : function_info {}; 34 | 35 | template 36 | struct function_info : function_info { 37 | using class_type = C; 38 | 39 | using equivalent_function_type_with_class = R(C*, As...); 40 | }; 41 | 42 | template 43 | struct function_info : function_info { 44 | using class_type = C; 45 | 46 | using equivalent_function_type_with_class = R(C*, As...); 47 | }; 48 | 49 | template 50 | constexpr size_t parameter_count_v = function_info::parameter_count; 51 | 52 | template 53 | using parameter_list = typename function_info::parameter_list; 54 | 55 | template 56 | using get_parameter = typename function_info::template parameter::type; 57 | 58 | template 59 | using equivalent_function_type = typename function_info::equivalent_function_type; 60 | 61 | template 62 | using equivalent_function_type_with_class = typename function_info::equivalent_function_type_with_class; 63 | 64 | template 65 | using return_type = typename function_info::return_type; 66 | 67 | template 68 | using class_type = typename function_info::class_type; 69 | 70 | } // namespace mcl 71 | -------------------------------------------------------------------------------- /include/lunatic/coprocessor.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | namespace lunatic { 13 | 14 | struct Coprocessor { 15 | virtual ~Coprocessor() = default; 16 | 17 | virtual void Reset() {} 18 | 19 | virtual bool ShouldWriteBreakBasicBlock( 20 | int opcode1, 21 | int cn, 22 | int cm, 23 | int opcode2 24 | ) { 25 | return false; 26 | } 27 | 28 | virtual auto Read( 29 | int opcode1, 30 | int cn, 31 | int cm, 32 | int opcode2 33 | ) -> u32 = 0; 34 | 35 | virtual void Write( 36 | int opcode1, 37 | int cn, 38 | int cm, 39 | int opcode2, 40 | u32 value 41 | ) = 0; 42 | }; 43 | 44 | } // namespace lunatic 45 | -------------------------------------------------------------------------------- /include/lunatic/cpu.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace lunatic { 15 | 16 | enum class GPR { 17 | R0 = 0, 18 | R1 = 1, 19 | R2 = 2, 20 | R3 = 3, 21 | R4 = 4, 22 | R5 = 5, 23 | R6 = 6, 24 | R7 = 7, 25 | R8 = 8, 26 | R9 = 9, 27 | R10 = 10, 28 | R11 = 11, 29 | R12 = 12, 30 | SP = 13, 31 | LR = 14, 32 | PC = 15, 33 | }; 34 | 35 | enum class Mode : uint { 36 | User = 0x10, 37 | FIQ = 0x11, 38 | IRQ = 0x12, 39 | Supervisor = 0x13, 40 | Abort = 0x17, 41 | Undefined = 0x1B, 42 | System = 0x1F 43 | }; 44 | 45 | union StatusRegister { 46 | struct { 47 | Mode mode : 5; 48 | uint thumb : 1; 49 | uint mask_fiq : 1; 50 | uint mask_irq : 1; 51 | uint reserved : 19; 52 | uint q : 1; 53 | uint v : 1; 54 | uint c : 1; 55 | uint z : 1; 56 | uint n : 1; 57 | } f; 58 | u32 v = static_cast(Mode::System); 59 | }; 60 | 61 | struct CPU { 62 | struct Descriptor { 63 | Memory& memory; 64 | std::array coprocessors = {}; 65 | u32 exception_base = 0; 66 | enum class Model { 67 | ARM7, 68 | ARM9 69 | } model = Model::ARM9; 70 | int block_size = 32; 71 | }; 72 | 73 | virtual ~CPU() = default; 74 | 75 | virtual void Reset() = 0; 76 | virtual auto IRQLine() -> bool& = 0; 77 | virtual auto WaitForIRQ() -> bool& = 0; 78 | virtual auto GetExceptionBase() const -> u32 = 0; 79 | virtual void SetExceptionBase(u32 exception_base) = 0; 80 | virtual void ClearICache() = 0; 81 | virtual void ClearICacheRange(u32 address_lo, u32 address_hi) = 0; 82 | virtual auto Run(int cycles) -> int = 0; 83 | 84 | virtual auto GetGPR(GPR reg) const -> u32 = 0; 85 | virtual auto GetGPR(GPR reg, Mode mode) const -> u32 = 0; 86 | virtual auto GetCPSR() const -> StatusRegister = 0; 87 | virtual auto GetSPSR(Mode mode) const -> StatusRegister = 0; 88 | virtual void SetGPR(GPR reg, u32 value) = 0; 89 | virtual void SetGPR(GPR reg, Mode mode, u32 value) = 0; 90 | virtual void SetCPSR(StatusRegister value) = 0; 91 | virtual void SetSPSR(Mode mode, StatusRegister value) = 0; 92 | }; 93 | 94 | auto CreateCPU(CPU::Descriptor const& descriptor) -> std::unique_ptr; 95 | 96 | } // namespace lunatic 97 | -------------------------------------------------------------------------------- /include/lunatic/detail/meta.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | // TODO: consider moving this to lunatic::detail 14 | 15 | namespace lunatic { 16 | 17 | template 18 | struct is_one_of { 19 | static constexpr bool value = (... || std::is_same_v); 20 | }; 21 | 22 | template 23 | inline constexpr bool is_one_of_v = is_one_of::value; 24 | 25 | } // namespace lunatic 26 | -------------------------------------------------------------------------------- /include/lunatic/detail/punning.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | // TODO: consider moving this to lunatic::detail 14 | 15 | namespace lunatic { 16 | 17 | template 18 | auto read(void* data, uint offset) -> T { 19 | T value; 20 | std::memcpy(&value, (u8*)data + offset, sizeof(T)); 21 | return value; 22 | } 23 | 24 | template 25 | void write(void* data, uint offset, T value) { 26 | std::memcpy((u8*)data + offset, &value, sizeof(T)); 27 | } 28 | 29 | } // namespace lunatic 30 | -------------------------------------------------------------------------------- /include/lunatic/integer.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #ifdef LUNATIC_NO_CUSTOM_INT_TYPES 13 | namespace lunatic { 14 | #endif 15 | 16 | using u8 = std::uint8_t; 17 | using s8 = std::int8_t; 18 | using u16 = std::uint16_t; 19 | using s16 = std::int16_t; 20 | using u32 = std::uint32_t; 21 | using s32 = std::int32_t; 22 | using u64 = std::uint64_t; 23 | using s64 = std::int64_t; 24 | 25 | using uint = unsigned int; 26 | using uintptr = std::uintptr_t; 27 | 28 | #ifdef LUNATIC_NO_CUSTOM_INT_TYPES 29 | } // namespace lunatic 30 | #endif 31 | -------------------------------------------------------------------------------- /include/lunatic/memory.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace lunatic { 17 | 18 | // TODO: FastRead/FastWrite are not relevant to the public interface. 19 | // Move into the internal code as helper functions? 20 | 21 | struct Memory { 22 | enum class Bus : int { 23 | Code, 24 | Data, 25 | System 26 | }; 27 | 28 | virtual ~Memory() = default; 29 | 30 | virtual auto ReadByte(u32 address, Bus bus) -> u8 = 0; 31 | virtual auto ReadHalf(u32 address, Bus bus) -> u16 = 0; 32 | virtual auto ReadWord(u32 address, Bus bus) -> u32 = 0; 33 | 34 | virtual void WriteByte(u32 address, u8 value, Bus bus) = 0; 35 | virtual void WriteHalf(u32 address, u16 value, Bus bus) = 0; 36 | virtual void WriteWord(u32 address, u32 value, Bus bus) = 0; 37 | 38 | template 39 | auto FastRead(u32 address) -> T { 40 | static_assert(is_one_of_v); 41 | 42 | address &= ~(sizeof(T) - 1); 43 | 44 | if constexpr (bus != Bus::System) { 45 | if (itcm.config.enable_read && 46 | address >= itcm.config.base && 47 | address <= itcm.config.limit) { 48 | return read(itcm.data, (address - itcm.config.base) & itcm.mask); 49 | } 50 | } 51 | 52 | if constexpr (bus == Bus::Data) { 53 | if (dtcm.config.enable_read && address >= dtcm.config.base && address <= dtcm.config.limit) { 54 | return read(dtcm.data, (address - dtcm.config.base) & dtcm.mask); 55 | } 56 | } 57 | 58 | if (pagetable != nullptr) { 59 | auto page = (*pagetable)[address >> kPageShift]; 60 | if (page != nullptr) { 61 | return read(page, address & kPageMask); 62 | } 63 | } 64 | 65 | if constexpr (std::is_same_v) return ReadByte(address, bus); 66 | if constexpr (std::is_same_v) return ReadHalf(address, bus); 67 | if constexpr (std::is_same_v) return ReadWord(address, bus); 68 | } 69 | 70 | template 71 | void FastWrite(u32 address, T value) { 72 | static_assert(is_one_of_v); 73 | 74 | address &= ~(sizeof(T) - 1); 75 | 76 | if constexpr (bus != Bus::System) { 77 | if (itcm.config.enable && 78 | address >= itcm.config.base && 79 | address <= itcm.config.limit) { 80 | write(itcm.data, (address - itcm.config.base) & itcm.mask, value); 81 | return; 82 | } 83 | 84 | if (dtcm.config.enable && 85 | address >= dtcm.config.base && 86 | address <= dtcm.config.limit) { 87 | write(dtcm.data, (address - dtcm.config.base) & dtcm.mask, value); 88 | return; 89 | } 90 | } 91 | 92 | if (pagetable != nullptr) { 93 | auto page = (*pagetable)[address >> kPageShift]; 94 | if (page != nullptr) { 95 | write(page, address & kPageMask, value); 96 | return; 97 | } 98 | } 99 | 100 | if constexpr (std::is_same_v) WriteByte(address, value, bus); 101 | if constexpr (std::is_same_v) WriteHalf(address, value, bus); 102 | if constexpr (std::is_same_v) WriteWord(address, value, bus); 103 | } 104 | 105 | static constexpr int kPageShift = 12; // 2^12 = 4096 106 | static constexpr int kPageMask = (1 << kPageShift) - 1; 107 | 108 | std::unique_ptr> pagetable = nullptr; 109 | 110 | struct TCM { 111 | u8* data = nullptr; 112 | u32 mask = 0; 113 | 114 | struct Config { 115 | bool enable = false; 116 | bool enable_read = false; 117 | u32 base = 0; 118 | u32 limit = 0; 119 | } config; 120 | } itcm, dtcm; 121 | }; 122 | 123 | } // namespace lunatic 124 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.2) 2 | 3 | project(lunatic CXX) 4 | 5 | include(../CMakeModules/FindVTune.cmake) 6 | 7 | set(CMAKE_CXX_STANDARD 17) 8 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 9 | 10 | if (${CMAKE_SYSTEM_PROCESSOR} MATCHES "x86_64|AMD64") 11 | set(ARCH_SPECIFIC_SOURCES 12 | backend/x86_64/backend.cpp 13 | backend/x86_64/compile_alu.cpp 14 | backend/x86_64/compile_context.cpp 15 | backend/x86_64/compile_coprocessor.cpp 16 | backend/x86_64/compile_flags.cpp 17 | backend/x86_64/compile_flush.cpp 18 | backend/x86_64/compile_memory.cpp 19 | backend/x86_64/compile_multiply.cpp 20 | backend/x86_64/compile_shift.cpp 21 | backend/x86_64/register_allocator.cpp 22 | ) 23 | 24 | set(ARCH_SPECIFIC_HEADERS 25 | backend/x86_64/backend.hpp 26 | backend/x86_64/common.hpp 27 | backend/x86_64/register_allocator.hpp 28 | backend/x86_64/vtune.hpp 29 | ) 30 | else() 31 | message(FATAL_ERROR "Unsupported architecture: ${CMAKE_SYSTEM_PROCESSOR}") 32 | endif() 33 | 34 | set(SOURCES 35 | common/pool_allocator.cpp 36 | frontend/ir/emitter.cpp 37 | frontend/ir_opt/constant_propagation.cpp 38 | frontend/ir_opt/context_load_store_elision.cpp 39 | frontend/ir_opt/dead_code_elision.cpp 40 | frontend/ir_opt/dead_flag_elision.cpp 41 | frontend/translator/handle/block_data_transfer.cpp 42 | frontend/translator/handle/branch_exchange.cpp 43 | frontend/translator/handle/branch_relative.cpp 44 | frontend/translator/handle/coprocessor_register_transfer.cpp 45 | frontend/translator/handle/count_leading_zeros.cpp 46 | frontend/translator/handle/data_processing.cpp 47 | frontend/translator/handle/exception.cpp 48 | frontend/translator/handle/halfword_signed_transfer.cpp 49 | frontend/translator/handle/multiply.cpp 50 | frontend/translator/handle/multiply_long.cpp 51 | frontend/translator/handle/saturating_add_sub.cpp 52 | frontend/translator/handle/signed_halfword_multiply.cpp 53 | frontend/translator/handle/single_data_swap.cpp 54 | frontend/translator/handle/single_data_transfer.cpp 55 | frontend/translator/handle/status_transfer.cpp 56 | frontend/translator/handle/thumb_bl_suffix.cpp 57 | frontend/translator/translator.cpp 58 | frontend/state.cpp 59 | jit.cpp 60 | ) 61 | 62 | set(HEADERS 63 | backend/backend.hpp 64 | common/bit.hpp 65 | common/compiler.hpp 66 | common/aligned_memory.hpp 67 | common/meta.hpp 68 | common/optional.hpp 69 | common/pool_allocator.hpp 70 | frontend/decode/definition/block_data_transfer.hpp 71 | frontend/decode/definition/branch_relative.hpp 72 | frontend/decode/definition/coprocessor_register_transfer.hpp 73 | frontend/decode/definition/count_leading_zeros.hpp 74 | frontend/decode/definition/branch_exchange.hpp 75 | frontend/decode/definition/common.hpp 76 | frontend/decode/definition/data_processing.hpp 77 | frontend/decode/definition/exception.hpp 78 | frontend/decode/definition/halfword_signed_transfer.hpp 79 | frontend/decode/definition/multiply.hpp 80 | frontend/decode/definition/multiply_long.hpp 81 | frontend/decode/definition/saturating_add_sub.hpp 82 | frontend/decode/definition/signed_halfword_multiply.hpp 83 | frontend/decode/definition/single_data_swap.hpp 84 | frontend/decode/definition/single_data_transfer.hpp 85 | frontend/decode/definition/status_transfer.hpp 86 | frontend/decode/definition/thumb_bl_suffix.hpp 87 | frontend/decode/arm.hpp 88 | frontend/decode/thumb.hpp 89 | frontend/ir/emitter.hpp 90 | frontend/ir/opcode.hpp 91 | frontend/ir/register.hpp 92 | frontend/ir/value.hpp 93 | frontend/ir_opt/constant_propagation.hpp 94 | frontend/ir_opt/context_load_store_elision.hpp 95 | frontend/ir_opt/dead_code_elision.hpp 96 | frontend/ir_opt/dead_flag_elision.hpp 97 | frontend/ir_opt/pass.hpp 98 | frontend/translator/translator.hpp 99 | frontend/basic_block.hpp 100 | frontend/basic_block_cache.hpp 101 | frontend/state.hpp 102 | ) 103 | 104 | set(HEADERS_PUBLIC 105 | ../include/lunatic/detail/meta.hpp 106 | ../include/lunatic/detail/punning.hpp 107 | ../include/lunatic/coprocessor.hpp 108 | ../include/lunatic/cpu.hpp 109 | ../include/lunatic/integer.hpp 110 | ../include/lunatic/memory.hpp 111 | ) 112 | 113 | add_library(lunatic STATIC ${SOURCES} ${HEADERS} ${ARCH_SPECIFIC_SOURCES} ${ARCH_SPECIFIC_HEADERS} ${HEADERS_PUBLIC}) 114 | 115 | target_include_directories(lunatic PRIVATE .) 116 | target_include_directories(lunatic PUBLIC 117 | $ 118 | $) 119 | target_link_libraries(lunatic PRIVATE fmt) 120 | 121 | if (${CMAKE_SYSTEM_PROCESSOR} MATCHES "x86_64|AMD64") 122 | target_link_libraries(lunatic PRIVATE xbyak dynarmic) 123 | 124 | if (LUNATIC_INCLUDE_XBYAK_FROM_DIRECTORY) 125 | target_compile_definitions(lunatic PRIVATE LUNATIC_INCLUDE_XBYAK_FROM_DIRECTORY) 126 | endif() 127 | 128 | if (VTune_FOUND AND LUNATIC_USE_VTUNE) 129 | message(STATUS "lunatic: Adding VTune JIT Profiling API from ${VTune_LIBRARIES}") 130 | target_include_directories(lunatic PRIVATE ${VTune_INCLUDE_DIRS}) 131 | target_link_libraries(lunatic PRIVATE ${VTune_LIBRARIES}) 132 | target_compile_definitions(lunatic PRIVATE LUNATIC_USE_VTUNE=1) 133 | endif() 134 | endif() 135 | 136 | if (CMAKE_SYSTEM_NAME STREQUAL "Windows") 137 | target_compile_definitions(lunatic PRIVATE NOMINMAX) 138 | endif() 139 | -------------------------------------------------------------------------------- /src/backend/backend.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "frontend/basic_block_cache.hpp" 11 | 12 | namespace lunatic { 13 | namespace backend { 14 | 15 | struct Backend { 16 | virtual ~Backend() = default; 17 | 18 | virtual void Compile(frontend::BasicBlock& basic_block) = 0; 19 | virtual int Call(frontend::BasicBlock const& basic_block, int max_cycles) = 0; 20 | 21 | static std::unique_ptr CreateBackend(CPU::Descriptor const& descriptor, 22 | frontend::State& state, 23 | frontend::BasicBlockCache& block_cache, 24 | bool const& irq_line); 25 | }; 26 | 27 | } // namespace lunatic::backend 28 | } // namespace lunatic 29 | -------------------------------------------------------------------------------- /src/backend/x86_64/backend.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "backend/backend.hpp" 17 | #include "common/aligned_memory.hpp" 18 | #include "frontend/basic_block_cache.hpp" 19 | #include "frontend/state.hpp" 20 | #include "register_allocator.hpp" 21 | 22 | using namespace lunatic::frontend; 23 | 24 | // TODO: Add a runtime check for that. 25 | #ifndef __APPLE__ 26 | #define LUNATIC_SUPPORT_BMI 1 27 | #endif 28 | 29 | namespace lunatic { 30 | namespace backend { 31 | 32 | struct X64Backend : Backend { 33 | X64Backend( 34 | CPU::Descriptor const& descriptor, 35 | State& state, 36 | BasicBlockCache& block_cache, 37 | bool const& irq_line 38 | ); 39 | 40 | ~X64Backend(); 41 | 42 | void Compile(BasicBlock& basic_block) override; 43 | int Call(frontend::BasicBlock const& basic_block, int max_cycles) override; 44 | 45 | private: 46 | static constexpr size_t kCodeBufferSize = 32 * 1024 * 1024; 47 | 48 | struct CompileContext { 49 | Xbyak::CodeGenerator& code; 50 | X64RegisterAllocator& reg_alloc; 51 | State& state; 52 | }; 53 | 54 | void DevirtualizeMemoryReadWriteMethods(); 55 | void CreateCodeGenerator(); 56 | void EmitCallBlock(); 57 | 58 | void EmitConditionalBranch(Condition condition, Xbyak::Label& label_skip); 59 | 60 | void EmitReturnToDispatchIfNeeded(BasicBlock& basic_block, Xbyak::Label& label_return_to_dispatch); 61 | void EmitBasicBlockDispatch(Xbyak::Label& label_cache_miss); 62 | void EmitBlockLinkingEpilogue(BasicBlock& basic_block); 63 | 64 | void Link(BasicBlock& basic_block); 65 | 66 | void OnBasicBlockToBeDeleted(BasicBlock const& basic_block); 67 | 68 | void CompileIROp( 69 | CompileContext const& context, 70 | std::unique_ptr const& op 71 | ); 72 | 73 | void Push( 74 | Xbyak::CodeGenerator& code, 75 | std::vector const& regs 76 | ); 77 | 78 | void Pop( 79 | Xbyak::CodeGenerator& code, 80 | std::vector const& regs 81 | ); 82 | 83 | auto GetUsedHostRegsFromList( 84 | X64RegisterAllocator const& reg_alloc, 85 | std::vector const& regs 86 | ) -> std::vector; 87 | 88 | void CompileLoadGPR(CompileContext const& context, IRLoadGPR* op); 89 | void CompileStoreGPR(CompileContext const& context, IRStoreGPR* op); 90 | void CompileLoadSPSR(CompileContext const& context, IRLoadSPSR* op); 91 | void CompileStoreSPSR(CompileContext const& context, IRStoreSPSR* op); 92 | void CompileLoadCPSR(CompileContext const& context, IRLoadCPSR* op); 93 | void CompileStoreCPSR(CompileContext const& context, IRStoreCPSR* op); 94 | void CompileClearCarry(CompileContext const& context, IRClearCarry* op); 95 | void CompileSetCarry(CompileContext const& context, IRSetCarry* op); 96 | void CompileUpdateFlags(CompileContext const& context, IRUpdateFlags* op); 97 | void CompileUpdateSticky(CompileContext const& context, IRUpdateSticky* op); 98 | void CompileLSL(CompileContext const& context, IRLogicalShiftLeft* op); 99 | void CompileLSR(CompileContext const& context, IRLogicalShiftRight* op); 100 | void CompileASR(CompileContext const& context, IRArithmeticShiftRight* op); 101 | void CompileROR(CompileContext const& context, IRRotateRight* op); 102 | void CompileAND(CompileContext const& context, IRBitwiseAND* op); 103 | void CompileBIC(CompileContext const& context, IRBitwiseBIC* op); 104 | void CompileEOR(CompileContext const& context, IRBitwiseEOR* op); 105 | void CompileSUB(CompileContext const& context, IRSub* op); 106 | void CompileRSB(CompileContext const& context, IRRsb* op); 107 | void CompileADD(CompileContext const& context, IRAdd* op); 108 | void CompileADC(CompileContext const& context, IRAdc* op); 109 | void CompileSBC(CompileContext const& context, IRSbc* op); 110 | void CompileRSC(CompileContext const& context, IRRsc* op); 111 | void CompileORR(CompileContext const& context, IRBitwiseORR* op); 112 | void CompileMOV(CompileContext const& context, IRMov* op); 113 | void CompileMVN(CompileContext const& context, IRMvn* op); 114 | void CompileCLZ(CompileContext const& context, IRCountLeadingZeros* op); 115 | void CompileQADD(CompileContext const& context, IRSaturatingAdd* op); 116 | void CompileQSUB(CompileContext const& context, IRSaturatingSub* op); 117 | void CompileMUL(CompileContext const& context, IRMultiply* op); 118 | void CompileADD64(CompileContext const& context, IRAdd64* op); 119 | void CompileMemoryRead(CompileContext const& context, IRMemoryRead* op); 120 | void CompileMemoryWrite(CompileContext const& context, IRMemoryWrite* op); 121 | void CompileFlush(CompileContext const& context, IRFlush* op); 122 | void CompileFlushExchange(CompileContext const& context, IRFlushExchange* op); 123 | void CompileMRC(CompileContext const& context, IRReadCoprocessorRegister* op); 124 | void CompileMCR(CompileContext const& context, IRWriteCoprocessorRegister* op); 125 | 126 | Memory& memory; 127 | State& state; 128 | std::array coprocessors; 129 | BasicBlockCache& block_cache; 130 | bool const& irq_line; 131 | int (*CallBlock)(BasicBlock::CompiledFn, int); 132 | 133 | memory::CodeBlockMemory *code_memory_block; 134 | bool is_writeable; 135 | Xbyak::CodeGenerator* code; 136 | 137 | std::unordered_map> block_linking_table; 138 | 139 | Dynarmic::Backend::X64::DevirtualizedCall read_byte_call; 140 | Dynarmic::Backend::X64::DevirtualizedCall read_half_call; 141 | Dynarmic::Backend::X64::DevirtualizedCall read_word_call; 142 | 143 | Dynarmic::Backend::X64::DevirtualizedCall write_byte_call; 144 | Dynarmic::Backend::X64::DevirtualizedCall write_half_call; 145 | Dynarmic::Backend::X64::DevirtualizedCall write_word_call; 146 | }; 147 | 148 | } // namespace lunatic::backend 149 | } // namespace lunatic 150 | -------------------------------------------------------------------------------- /src/backend/x86_64/common.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "backend.hpp" 11 | 12 | #define DESTRUCTURE_CONTEXT auto& [code, reg_alloc, state] = context; 13 | 14 | using namespace Xbyak::util; 15 | 16 | #ifdef _WIN64 17 | #define ABI_MSVC 18 | #else 19 | #define ABI_SYSV 20 | #endif 21 | 22 | #ifdef ABI_MSVC 23 | static constexpr Xbyak::Reg64 kRegArg0 = rcx; 24 | static constexpr Xbyak::Reg64 kRegArg1 = rdx; 25 | static constexpr Xbyak::Reg64 kRegArg2 = r8; 26 | static constexpr Xbyak::Reg64 kRegArg3 = r9; 27 | #else 28 | static constexpr Xbyak::Reg64 kRegArg0 = rdi; 29 | static constexpr Xbyak::Reg64 kRegArg1 = rsi; 30 | static constexpr Xbyak::Reg64 kRegArg2 = rdx; 31 | static constexpr Xbyak::Reg64 kRegArg3 = rcx; 32 | static constexpr Xbyak::Reg64 kRegArg4 = r8; 33 | static constexpr Xbyak::Reg64 kRegArg5 = r9; 34 | #endif 35 | -------------------------------------------------------------------------------- /src/backend/x86_64/compile_context.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "common.hpp" 9 | 10 | namespace lunatic::backend { 11 | 12 | void X64Backend::CompileLoadGPR(CompileContext const& context, IRLoadGPR* op) { 13 | DESTRUCTURE_CONTEXT; 14 | 15 | auto address = rcx + state.GetOffsetToGPR(op->reg.mode, op->reg.reg); 16 | auto host_reg = reg_alloc.GetVariableHostReg(op->result.Get()); 17 | 18 | code.mov(host_reg, dword[address]); 19 | } 20 | 21 | void X64Backend::CompileStoreGPR(CompileContext const& context, IRStoreGPR* op) { 22 | DESTRUCTURE_CONTEXT; 23 | 24 | auto address = rcx + state.GetOffsetToGPR(op->reg.mode, op->reg.reg); 25 | 26 | if (op->value.IsConstant()) { 27 | code.mov(dword[address], op->value.GetConst().value); 28 | } else { 29 | auto host_reg = reg_alloc.GetVariableHostReg(op->value.GetVar()); 30 | 31 | code.mov(dword[address], host_reg); 32 | } 33 | } 34 | 35 | void X64Backend::CompileLoadSPSR(CompileContext const& context, IRLoadSPSR* op) { 36 | DESTRUCTURE_CONTEXT; 37 | 38 | auto address = rcx + state.GetOffsetToSPSR(op->mode); 39 | auto host_reg = reg_alloc.GetVariableHostReg(op->result.Get()); 40 | 41 | code.mov(host_reg, dword[address]); 42 | } 43 | 44 | void X64Backend::CompileStoreSPSR(const CompileContext &context, IRStoreSPSR *op) { 45 | DESTRUCTURE_CONTEXT; 46 | 47 | auto address = rcx + state.GetOffsetToSPSR(op->mode); 48 | 49 | if (op->value.IsConstant()) { 50 | code.mov(dword[address], op->value.GetConst().value); 51 | } else { 52 | auto host_reg = reg_alloc.GetVariableHostReg(op->value.GetVar()); 53 | 54 | code.mov(dword[address], host_reg); 55 | } 56 | } 57 | 58 | void X64Backend::CompileLoadCPSR(CompileContext const& context, IRLoadCPSR* op) { 59 | DESTRUCTURE_CONTEXT; 60 | 61 | auto address = rcx + state.GetOffsetToCPSR(); 62 | auto host_reg = reg_alloc.GetVariableHostReg(op->result.Get()); 63 | 64 | code.mov(host_reg, dword[address]); 65 | } 66 | 67 | void X64Backend::CompileStoreCPSR(CompileContext const& context, IRStoreCPSR* op) { 68 | DESTRUCTURE_CONTEXT; 69 | 70 | auto address = rcx + state.GetOffsetToCPSR(); 71 | 72 | if (op->value.IsConstant()) { 73 | code.mov(dword[address], op->value.GetConst().value); 74 | } else { 75 | auto host_reg = reg_alloc.GetVariableHostReg(op->value.GetVar()); 76 | 77 | code.mov(dword[address], host_reg); 78 | } 79 | } 80 | 81 | } // namespace lunatic::backend 82 | -------------------------------------------------------------------------------- /src/backend/x86_64/compile_coprocessor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "common.hpp" 9 | 10 | namespace lunatic::backend { 11 | 12 | void X64Backend::CompileMRC(CompileContext const& context, IRReadCoprocessorRegister* op) { 13 | DESTRUCTURE_CONTEXT; 14 | 15 | code.push(rax); 16 | 17 | auto regs_saved = GetUsedHostRegsFromList(reg_alloc, { 18 | rcx, rdx, r8, r9, r10, r11, 19 | 20 | #ifdef ABI_SYSV 21 | rsi, rdi 22 | #endif 23 | }); 24 | 25 | bool must_align_rsp = (regs_saved.size() % 2) == 1; 26 | 27 | Push(code, regs_saved); 28 | 29 | #ifdef ABI_MSVC 30 | // the 'push' below changes the stack-alignment. 31 | if (!must_align_rsp) { 32 | code.sub(rsp, sizeof(u64)); 33 | } 34 | code.push(op->opcode2); 35 | code.sub(rsp, 0x20); 36 | #else 37 | if (must_align_rsp) { 38 | code.sub(rsp, sizeof(u64)); 39 | } 40 | code.mov(kRegArg4, op->opcode2); 41 | #endif 42 | 43 | Coprocessor* coprocessor = coprocessors[op->coprocessor_id]; 44 | 45 | code.mov(kRegArg0, u64(coprocessor)); 46 | code.mov(kRegArg1.cvt32(), op->opcode1); 47 | code.mov(kRegArg2.cvt32(), op->cn); 48 | code.mov(kRegArg3.cvt32(), op->cm); 49 | 50 | // TODO(fleroviux): cache devirtualized coprocessor member function pointers. 51 | const auto read_cop_call = Dynarmic::Backend::X64::Devirtualize<&Coprocessor::Read>(coprocessor); 52 | code.mov(rax, read_cop_call.fn); 53 | code.call(rax); 54 | 55 | #ifdef ABI_MSVC 56 | if (must_align_rsp) { 57 | code.add(rsp, 0x28); 58 | } else { 59 | code.add(rsp, 0x30); 60 | } 61 | #else 62 | if (must_align_rsp) { 63 | code.add(rsp, sizeof(u64)); 64 | } 65 | #endif 66 | 67 | Pop(code, regs_saved); 68 | 69 | code.mov(reg_alloc.GetVariableHostReg(op->result.Get()), eax); 70 | code.pop(rax); 71 | } 72 | 73 | void X64Backend::CompileMCR(CompileContext const& context, IRWriteCoprocessorRegister* op) { 74 | DESTRUCTURE_CONTEXT; 75 | 76 | auto regs_saved = GetUsedHostRegsFromList(reg_alloc, { 77 | rax, rcx, rdx, r8, r9, r10, r11, 78 | 79 | #ifdef ABI_SYSV 80 | rsi, rdi 81 | #endif 82 | }); 83 | 84 | bool must_align_rsp = (regs_saved.size() % 2) == 0;//1? 85 | 86 | Push(code, regs_saved); 87 | 88 | if (must_align_rsp) { 89 | code.sub(rsp, sizeof(u64)); 90 | } 91 | 92 | #ifdef ABI_MSVC 93 | if (op->value.IsConstant()) { 94 | code.push(op->value.GetConst().value); 95 | } else { 96 | code.push(reg_alloc.GetVariableHostReg(op->value.GetVar()).cvt64()); 97 | } 98 | code.push(op->opcode2); 99 | code.sub(rsp, 0x20); 100 | #else 101 | if (op->value.IsConstant()) { 102 | code.mov(kRegArg5, op->value.GetConst().value); 103 | } else { 104 | code.mov(kRegArg5, reg_alloc.GetVariableHostReg(op->value.GetVar())); 105 | } 106 | code.mov(kRegArg4, op->opcode2); 107 | #endif 108 | 109 | Coprocessor* coprocessor = coprocessors[op->coprocessor_id]; 110 | 111 | code.mov(kRegArg0, u64(coprocessor)); 112 | code.mov(kRegArg1.cvt32(), op->opcode1); 113 | code.mov(kRegArg2.cvt32(), op->cn); 114 | code.mov(kRegArg3.cvt32(), op->cm); 115 | 116 | // TODO(fleroviux): cache devirtualized coprocessor member function pointers. 117 | const auto write_cop_call = Dynarmic::Backend::X64::Devirtualize<&Coprocessor::Write>(coprocessor); 118 | code.mov(rax, write_cop_call.fn); 119 | code.call(rax); 120 | 121 | #ifdef ABI_MSVC 122 | if (must_align_rsp) { 123 | code.add(rsp, 0x38); 124 | } else { 125 | code.add(rsp, 0x30); 126 | } 127 | #else 128 | if (must_align_rsp) { 129 | code.add(rsp, sizeof(u64)); 130 | } 131 | #endif 132 | 133 | Pop(code, regs_saved); 134 | } 135 | 136 | } // namespace lunatic::backend 137 | -------------------------------------------------------------------------------- /src/backend/x86_64/compile_flags.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "common.hpp" 9 | 10 | namespace lunatic::backend { 11 | 12 | void X64Backend::CompileClearCarry(CompileContext const& context, IRClearCarry* op) { 13 | context.code.and_(ah, ~1); 14 | } 15 | 16 | void X64Backend::CompileSetCarry(CompileContext const& context, IRSetCarry* op) { 17 | context.code.or_(ah, 1); 18 | } 19 | 20 | void X64Backend::CompileUpdateFlags(CompileContext const& context, IRUpdateFlags* op) { 21 | DESTRUCTURE_CONTEXT; 22 | 23 | u32 mask = 0; 24 | auto& result_var = op->result.Get(); 25 | auto& input_var = op->input.Get(); 26 | auto input_reg = reg_alloc.GetVariableHostReg(input_var); 27 | 28 | reg_alloc.ReleaseVarAndReuseHostReg(input_var, result_var); 29 | 30 | auto result_reg = reg_alloc.GetVariableHostReg(result_var); 31 | 32 | if (op->flag_n) mask |= 0x80000000; 33 | if (op->flag_z) mask |= 0x40000000; 34 | if (op->flag_c) mask |= 0x20000000; 35 | if (op->flag_v) mask |= 0x10000000; 36 | 37 | auto flags_reg = reg_alloc.GetTemporaryHostReg(); 38 | 39 | // Convert NZCV bits from AX register into the guest format. 40 | // Clear the bits which are not to be updated. 41 | #ifdef LUNATIC_SUPPORT_BMI 42 | auto pext_mask_reg = reg_alloc.GetTemporaryHostReg(); 43 | 44 | code.mov(pext_mask_reg, 0xC101); 45 | code.pext(flags_reg, eax, pext_mask_reg); 46 | code.shl(flags_reg, 28); 47 | #else 48 | code.mov(flags_reg, eax); 49 | code.and_(flags_reg, 0xC101); 50 | code.imul(flags_reg, flags_reg, 0x1021'0000); 51 | #endif 52 | 53 | code.and_(flags_reg, mask); 54 | 55 | if (result_reg != input_reg) { 56 | code.mov(result_reg, input_reg); 57 | } 58 | 59 | // Clear the bits to be updated, then OR the new values. 60 | code.and_(result_reg, ~mask); 61 | code.or_(result_reg, flags_reg); 62 | } 63 | 64 | void X64Backend::CompileUpdateSticky(CompileContext const& context, IRUpdateSticky* op) { 65 | DESTRUCTURE_CONTEXT; 66 | 67 | auto result_reg = reg_alloc.GetVariableHostReg(op->result.Get()); 68 | auto input_reg = reg_alloc.GetVariableHostReg(op->input.Get()); 69 | 70 | code.movzx(result_reg, al); 71 | code.shl(result_reg, 27); 72 | code.or_(result_reg, input_reg); 73 | } 74 | 75 | } // namespace lunatic::backend 76 | -------------------------------------------------------------------------------- /src/backend/x86_64/compile_flush.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "common.hpp" 9 | 10 | namespace lunatic::backend { 11 | 12 | void X64Backend::CompileFlush(CompileContext const& context, IRFlush* op) { 13 | DESTRUCTURE_CONTEXT; 14 | 15 | auto cpsr_reg = reg_alloc.GetVariableHostReg(op->cpsr_in.Get()); 16 | auto r15_in_reg = reg_alloc.GetVariableHostReg(op->address_in.Get()); 17 | auto r15_out_reg = reg_alloc.GetVariableHostReg(op->address_out.Get()); 18 | 19 | // Thanks to @wheremyfoodat (github.com/wheremyfoodat) for coming up with this. 20 | code.test(cpsr_reg, 1 << 5); 21 | code.sete(r15_out_reg.cvt8()); 22 | code.movzx(r15_out_reg, r15_out_reg.cvt8()); 23 | code.lea(r15_out_reg, dword[r15_in_reg + r15_out_reg * 4 + 4]); 24 | } 25 | 26 | void X64Backend::CompileFlushExchange(const CompileContext &context, IRFlushExchange *op) { 27 | DESTRUCTURE_CONTEXT; 28 | 29 | auto& address_in_var = op->address_in.Get(); 30 | auto address_in_reg = reg_alloc.GetVariableHostReg(address_in_var); 31 | auto& cpsr_in_var = op->cpsr_in.Get(); 32 | auto cpsr_in_reg = reg_alloc.GetVariableHostReg(cpsr_in_var); 33 | 34 | auto& address_out_var = op->address_out.Get(); 35 | auto& cpsr_out_var = op->cpsr_out.Get(); 36 | 37 | reg_alloc.ReleaseVarAndReuseHostReg(address_in_var, address_out_var); 38 | reg_alloc.ReleaseVarAndReuseHostReg(cpsr_in_var, cpsr_out_var); 39 | 40 | auto address_out_reg = reg_alloc.GetVariableHostReg(address_out_var); 41 | auto cpsr_out_reg = reg_alloc.GetVariableHostReg(cpsr_out_var); 42 | 43 | auto label_arm = Xbyak::Label{}; 44 | auto label_done = Xbyak::Label{}; 45 | 46 | if (address_out_reg != address_in_reg) { 47 | code.mov(address_out_reg, address_in_reg); 48 | } 49 | 50 | if (cpsr_out_reg != cpsr_in_reg) { 51 | code.mov(cpsr_out_reg, cpsr_in_reg); 52 | } 53 | 54 | // TODO: attempt to make this branchless. 55 | 56 | code.test(address_in_reg, 1); 57 | code.je(label_arm); 58 | 59 | // Thumb 60 | code.or_(cpsr_out_reg, 1 << 5); 61 | code.and_(address_out_reg, ~1); 62 | code.add(address_out_reg, sizeof(u16) * 2); 63 | code.jmp(label_done); 64 | 65 | // ARM 66 | code.L(label_arm); 67 | code.and_(cpsr_out_reg, ~(1 << 5)); 68 | code.and_(address_out_reg, ~3); 69 | code.add(address_out_reg, sizeof(u32) * 2); 70 | 71 | code.L(label_done); 72 | } 73 | 74 | } // namespace lunatic::backend 75 | -------------------------------------------------------------------------------- /src/backend/x86_64/compile_multiply.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "common.hpp" 9 | 10 | namespace lunatic::backend { 11 | 12 | void X64Backend::CompileMUL(CompileContext const& context, IRMultiply* op) { 13 | DESTRUCTURE_CONTEXT; 14 | 15 | auto& result_lo_var = op->result_lo.Get(); 16 | auto& lhs_var = op->lhs.Get(); 17 | auto& rhs_var = op->rhs.Get(); 18 | auto lhs_reg = reg_alloc.GetVariableHostReg(op->lhs.Get()); 19 | auto rhs_reg = reg_alloc.GetVariableHostReg(op->rhs.Get()); 20 | 21 | if (op->result_hi.HasValue()) { 22 | auto result_lo_reg = reg_alloc.GetVariableHostReg(result_lo_var); 23 | auto result_hi_reg = reg_alloc.GetVariableHostReg(op->result_hi.Unwrap()); 24 | auto rhs_ext_reg = reg_alloc.GetTemporaryHostReg().cvt64(); 25 | 26 | if (op->lhs.Get().data_type == IRDataType::SInt32) { 27 | code.movsxd(result_hi_reg.cvt64(), lhs_reg); 28 | code.movsxd(rhs_ext_reg, rhs_reg); 29 | } else { 30 | code.mov(result_hi_reg, lhs_reg); 31 | code.mov(rhs_ext_reg.cvt32(), rhs_reg); 32 | } 33 | 34 | code.imul(result_hi_reg.cvt64(), rhs_ext_reg); 35 | 36 | if (op->update_host_flags) { 37 | code.test(result_hi_reg.cvt64(), result_hi_reg.cvt64()); 38 | code.lahf(); 39 | } 40 | 41 | code.mov(result_lo_reg, result_hi_reg); 42 | code.shr(result_hi_reg.cvt64(), 32); 43 | } else { 44 | reg_alloc.ReleaseVarAndReuseHostReg(lhs_var, result_lo_var); 45 | reg_alloc.ReleaseVarAndReuseHostReg(rhs_var, result_lo_var); 46 | 47 | auto result_lo_reg = reg_alloc.GetVariableHostReg(result_lo_var); 48 | 49 | if (result_lo_reg == lhs_reg) { 50 | code.imul(lhs_reg, rhs_reg); 51 | } else if (result_lo_reg == rhs_reg) { 52 | code.imul(rhs_reg, lhs_reg); 53 | } else { 54 | code.mov(result_lo_reg, lhs_reg); 55 | code.imul(result_lo_reg, rhs_reg); 56 | } 57 | 58 | if (op->update_host_flags) { 59 | code.test(result_lo_reg, result_lo_reg); 60 | code.lahf(); 61 | } 62 | } 63 | } 64 | 65 | void X64Backend::CompileADD64(CompileContext const& context, IRAdd64* op) { 66 | DESTRUCTURE_CONTEXT; 67 | 68 | auto& lhs_hi_var = op->lhs_hi.Get(); 69 | auto& lhs_lo_var = op->lhs_lo.Get(); 70 | auto lhs_hi_reg = reg_alloc.GetVariableHostReg(lhs_hi_var); 71 | auto lhs_lo_reg = reg_alloc.GetVariableHostReg(lhs_lo_var); 72 | 73 | auto& rhs_hi_var = op->rhs_hi.Get(); 74 | auto& rhs_lo_var = op->rhs_lo.Get(); 75 | auto rhs_hi_reg = reg_alloc.GetVariableHostReg(rhs_hi_var); 76 | auto rhs_lo_reg = reg_alloc.GetVariableHostReg(rhs_lo_var); 77 | 78 | auto& result_hi_var = op->result_hi.Get(); 79 | auto& result_lo_var = op->result_lo.Get(); 80 | 81 | if (op->update_host_flags) { 82 | reg_alloc.ReleaseVarAndReuseHostReg(lhs_hi_var, result_hi_var); 83 | reg_alloc.ReleaseVarAndReuseHostReg(rhs_hi_var, result_lo_var); 84 | 85 | auto result_hi_reg = reg_alloc.GetVariableHostReg(result_hi_var); 86 | auto result_lo_reg = reg_alloc.GetVariableHostReg(result_lo_var); 87 | 88 | // Pack (lhs_hi, lhs_lo) into result_hi 89 | if (result_hi_reg != lhs_hi_reg) { 90 | code.mov(result_hi_reg, lhs_hi_reg); 91 | } 92 | code.shl(result_hi_reg.cvt64(), 32); 93 | code.or_(result_hi_reg.cvt64(), lhs_lo_reg); 94 | 95 | // Pack (rhs_hi, rhs_lo) into result_lo 96 | if (result_lo_reg != rhs_hi_reg) { 97 | code.mov(result_lo_reg, rhs_hi_reg); 98 | } 99 | code.shl(result_lo_reg.cvt64(), 32); 100 | code.or_(result_lo_reg.cvt64(), rhs_lo_reg); 101 | 102 | code.add(result_hi_reg.cvt64(), result_lo_reg.cvt64()); 103 | code.lahf(); 104 | 105 | code.mov(result_lo_reg, result_hi_reg); 106 | code.shr(result_hi_reg.cvt64(), 32); 107 | } else { 108 | reg_alloc.ReleaseVarAndReuseHostReg(lhs_lo_var, result_lo_var); 109 | reg_alloc.ReleaseVarAndReuseHostReg(lhs_hi_var, result_hi_var); 110 | 111 | auto result_hi_reg = reg_alloc.GetVariableHostReg(result_hi_var); 112 | auto result_lo_reg = reg_alloc.GetVariableHostReg(result_lo_var); 113 | 114 | if (result_lo_reg != lhs_lo_reg) { 115 | code.mov(result_lo_reg, lhs_lo_reg); 116 | } 117 | if (result_hi_reg != lhs_hi_reg) { 118 | code.mov(result_hi_reg, lhs_hi_reg); 119 | } 120 | 121 | code.add(result_lo_reg, rhs_lo_reg); 122 | code.adc(result_hi_reg, rhs_hi_reg); 123 | } 124 | } 125 | 126 | } // namespace lunatic::backend 127 | -------------------------------------------------------------------------------- /src/backend/x86_64/compile_shift.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "common.hpp" 9 | 10 | namespace lunatic::backend { 11 | 12 | void X64Backend::CompileLSL(CompileContext const& context, IRLogicalShiftLeft* op) { 13 | DESTRUCTURE_CONTEXT; 14 | 15 | auto& amount = op->amount; 16 | auto& result_var = op->result.Get(); 17 | auto& operand_var = op->operand.Get(); 18 | auto operand_reg = reg_alloc.GetVariableHostReg(operand_var); 19 | 20 | reg_alloc.ReleaseVarAndReuseHostReg(operand_var, result_var); 21 | 22 | auto result_reg = reg_alloc.GetVariableHostReg(result_var); 23 | 24 | if (result_reg != operand_reg) { 25 | code.mov(result_reg, operand_reg); 26 | } 27 | 28 | code.shl(result_reg.cvt64(), 32); 29 | 30 | if (amount.IsConstant()) { 31 | if (op->update_host_flags) { 32 | code.sahf(); 33 | } 34 | code.shl(result_reg.cvt64(), u8(std::min(amount.GetConst().value, 33U))); 35 | } else { 36 | auto amount_reg = reg_alloc.GetVariableHostReg(amount.GetVar()); 37 | 38 | code.push(rcx); 39 | code.mov(cl, 33); 40 | code.cmp(amount_reg.cvt8(), u8(33)); 41 | code.cmovl(ecx, amount_reg); 42 | 43 | if (op->update_host_flags) { 44 | code.sahf(); 45 | } 46 | code.shl(result_reg.cvt64(), cl); 47 | code.pop(rcx); 48 | } 49 | 50 | if (op->update_host_flags) { 51 | code.lahf(); 52 | } 53 | 54 | code.shr(result_reg.cvt64(), 32); 55 | } 56 | 57 | void X64Backend::CompileLSR(CompileContext const& context, IRLogicalShiftRight* op) { 58 | DESTRUCTURE_CONTEXT; 59 | 60 | auto& amount = op->amount; 61 | auto& result_var = op->result.Get(); 62 | auto& operand_var = op->operand.Get(); 63 | auto operand_reg = reg_alloc.GetVariableHostReg(operand_var); 64 | 65 | reg_alloc.ReleaseVarAndReuseHostReg(operand_var, result_var); 66 | 67 | auto result_reg = reg_alloc.GetVariableHostReg(result_var); 68 | 69 | if (result_reg != operand_reg) { 70 | code.mov(result_reg, operand_reg); 71 | } 72 | 73 | if (amount.IsConstant()) { 74 | auto amount_value = amount.GetConst().value; 75 | 76 | // LSR #0 equals to LSR #32 77 | if (amount_value == 0) { 78 | amount_value = 32; 79 | } 80 | 81 | if (op->update_host_flags) { 82 | code.sahf(); 83 | } 84 | 85 | code.shr(result_reg.cvt64(), u8(std::min(amount_value, 33U))); 86 | } else { 87 | auto amount_reg = reg_alloc.GetVariableHostReg(op->amount.GetVar()); 88 | code.push(rcx); 89 | code.mov(cl, 33); 90 | code.cmp(amount_reg.cvt8(), u8(33)); 91 | code.cmovl(ecx, amount_reg); 92 | if (op->update_host_flags) { 93 | code.sahf(); 94 | } 95 | code.shr(result_reg.cvt64(), cl); 96 | code.pop(rcx); 97 | } 98 | 99 | if (op->update_host_flags) { 100 | code.lahf(); 101 | } 102 | } 103 | 104 | void X64Backend::CompileASR(CompileContext const& context, IRArithmeticShiftRight* op) { 105 | DESTRUCTURE_CONTEXT; 106 | 107 | auto& amount = op->amount; 108 | auto& result_var = op->result.Get(); 109 | auto& operand_var = op->operand.Get(); 110 | auto operand_reg = reg_alloc.GetVariableHostReg(operand_var); 111 | 112 | reg_alloc.ReleaseVarAndReuseHostReg(operand_var, result_var); 113 | 114 | auto result_reg = reg_alloc.GetVariableHostReg(result_var); 115 | 116 | // Mirror sign-bit in the upper 32-bit of the full 64-bit register. 117 | code.movsxd(result_reg.cvt64(), operand_reg); 118 | 119 | // TODO: change shift amount saturation from 33 to 32 for ASR? 32 would also work I guess? 120 | 121 | if (amount.IsConstant()) { 122 | auto amount_value = amount.GetConst().value; 123 | 124 | // ASR #0 equals to ASR #32 125 | if (amount_value == 0) { 126 | amount_value = 32; 127 | } 128 | 129 | if (op->update_host_flags) { 130 | code.sahf(); 131 | } 132 | 133 | code.sar(result_reg.cvt64(), u8(std::min(amount_value, 33U))); 134 | } else { 135 | auto amount_reg = reg_alloc.GetVariableHostReg(op->amount.GetVar()); 136 | code.push(rcx); 137 | code.mov(cl, 33); 138 | code.cmp(amount_reg.cvt8(), u8(33)); 139 | code.cmovl(ecx, amount_reg); 140 | if (op->update_host_flags) { 141 | code.sahf(); 142 | } 143 | code.sar(result_reg.cvt64(), cl); 144 | code.pop(rcx); 145 | } 146 | 147 | if (op->update_host_flags) { 148 | code.lahf(); 149 | } 150 | 151 | // Clear upper 32-bit of the result 152 | code.mov(result_reg, result_reg); 153 | } 154 | 155 | void X64Backend::CompileROR(CompileContext const& context, IRRotateRight* op) { 156 | DESTRUCTURE_CONTEXT; 157 | 158 | auto& amount = op->amount; 159 | auto& result_var = op->result.Get(); 160 | auto& operand_var = op->operand.Get(); 161 | auto operand_reg = reg_alloc.GetVariableHostReg(operand_var); 162 | 163 | reg_alloc.ReleaseVarAndReuseHostReg(operand_var, result_var); 164 | 165 | auto result_reg = reg_alloc.GetVariableHostReg(result_var); 166 | auto label_done = Xbyak::Label{}; 167 | 168 | if (result_reg != operand_reg) { 169 | code.mov(result_reg, operand_reg); 170 | } 171 | 172 | if (amount.IsConstant()) { 173 | auto amount_value = amount.GetConst().value; 174 | 175 | // ROR #0 equals to RRX #1 176 | if (amount_value == 0) { 177 | code.sahf(); 178 | code.rcr(result_reg, 1); 179 | } else { 180 | if (op->update_host_flags) { 181 | code.sahf(); 182 | } 183 | code.ror(result_reg, u8(amount_value)); 184 | } 185 | } else { 186 | auto amount_reg = reg_alloc.GetVariableHostReg(op->amount.GetVar()); 187 | auto label_ok = Xbyak::Label{}; 188 | 189 | // Handle (amount % 32) == 0 and amount == 0 cases. 190 | if (op->update_host_flags) { 191 | code.test(amount_reg.cvt8(), 31); 192 | code.jnz(label_ok); 193 | 194 | code.cmp(amount_reg.cvt8(), 0); 195 | code.jz(label_done); 196 | 197 | code.bt(result_reg, 31); 198 | code.lahf(); 199 | code.jmp(label_done); 200 | } 201 | 202 | code.L(label_ok); 203 | code.push(rcx); 204 | code.mov(cl, amount_reg.cvt8()); 205 | code.ror(result_reg, cl); 206 | code.pop(rcx); 207 | } 208 | 209 | if (op->update_host_flags) { 210 | code.lahf(); 211 | } 212 | 213 | code.L(label_done); 214 | } 215 | 216 | } // namespace lunatic::backend 217 | -------------------------------------------------------------------------------- /src/backend/x86_64/register_allocator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include 9 | 10 | #include "register_allocator.hpp" 11 | 12 | using namespace lunatic::frontend; 13 | using namespace Xbyak::util; 14 | 15 | namespace lunatic { 16 | namespace backend { 17 | 18 | X64RegisterAllocator::X64RegisterAllocator( 19 | IREmitter const& emitter, 20 | Xbyak::CodeGenerator& code 21 | ) : emitter(emitter), code(code) { 22 | // Static allocation: 23 | // - rax: host flags via lahf (overflow flag in al) 24 | // - rbx: number of cycles left 25 | // - rcx: pointer to guest state (lunatic::frontend::State) 26 | // - rbp: pointer to stack frame / spill area. 27 | free_host_regs = { 28 | edx, 29 | esi, 30 | edi, 31 | r8d, 32 | r9d, 33 | r10d, 34 | r11d, 35 | r12d, 36 | r13d, 37 | r14d, 38 | r15d 39 | }; 40 | 41 | auto number_of_vars = emitter.Vars().size(); 42 | var_id_to_host_reg.resize(number_of_vars); 43 | var_id_to_point_of_last_use.resize(number_of_vars); 44 | var_id_to_spill_slot.resize(number_of_vars); 45 | 46 | EvaluateVariableLifetimes(); 47 | 48 | current_op_iter = emitter.Code().begin(); 49 | } 50 | 51 | void X64RegisterAllocator::AdvanceLocation() { 52 | location++; 53 | current_op_iter++; 54 | 55 | // Release host regs that hold variables which now are dead. 56 | ReleaseDeadVariables(); 57 | 58 | // Release host regs the previous opcode allocated temporarily. 59 | ReleaseTemporaryHostRegs(); 60 | } 61 | 62 | auto X64RegisterAllocator::GetVariableHostReg(IRVariable const& var) -> Xbyak::Reg32 { 63 | // Check if the variable is already allocated to a register at the moment. 64 | auto maybe_reg = var_id_to_host_reg[var.id]; 65 | if (maybe_reg.HasValue()) { 66 | return maybe_reg.Unwrap(); 67 | } 68 | 69 | auto reg = FindFreeHostReg(); 70 | 71 | // If the variable was spilled previously then restore its previous value. 72 | auto maybe_spill = var_id_to_spill_slot[var.id]; 73 | if (maybe_spill.HasValue()) { 74 | auto slot = maybe_spill.Unwrap(); 75 | code.mov(reg, dword[rbp + slot * sizeof(u32)]); 76 | free_spill_bitmap[slot] = false; 77 | var_id_to_spill_slot[var.id] = {}; 78 | } 79 | 80 | var_id_to_host_reg[var.id] = reg; 81 | return reg; 82 | } 83 | 84 | auto X64RegisterAllocator::GetTemporaryHostReg() -> Xbyak::Reg32 { 85 | auto reg = FindFreeHostReg(); 86 | temp_host_regs.push_back(reg); 87 | return reg; 88 | } 89 | 90 | void X64RegisterAllocator::ReleaseVarAndReuseHostReg( 91 | IRVariable const& var_old, 92 | IRVariable const& var_new 93 | ) { 94 | if (var_id_to_host_reg[var_new.id].HasValue()) { 95 | return; 96 | } 97 | 98 | auto point_of_last_use = var_id_to_point_of_last_use[var_old.id]; 99 | 100 | if (point_of_last_use == location) { 101 | auto maybe_reg = var_id_to_host_reg[var_old.id]; 102 | 103 | if (maybe_reg.HasValue()) { 104 | var_id_to_host_reg[var_new.id] = maybe_reg; 105 | var_id_to_host_reg[var_old.id] = {}; 106 | } 107 | } 108 | } 109 | 110 | bool X64RegisterAllocator::IsHostRegFree(Xbyak::Reg64 reg) const { 111 | auto begin = free_host_regs.begin(); 112 | auto end = free_host_regs.end(); 113 | 114 | return std::find(begin, end, reg.cvt32()) != end; 115 | } 116 | 117 | void X64RegisterAllocator::EvaluateVariableLifetimes() { 118 | for (auto const& var : emitter.Vars()) { 119 | int point_of_last_use = -1; 120 | int location = 0; 121 | 122 | for (auto const& op : emitter.Code()) { 123 | if (op->Writes(*var) || op->Reads(*var)) { 124 | point_of_last_use = location; 125 | } 126 | 127 | location++; 128 | } 129 | 130 | if (point_of_last_use != -1) { 131 | var_id_to_point_of_last_use[var->id] = point_of_last_use; 132 | } 133 | } 134 | } 135 | 136 | void X64RegisterAllocator::ReleaseDeadVariables() { 137 | for (auto const& var : emitter.Vars()) { 138 | auto point_of_last_use = var_id_to_point_of_last_use[var->id]; 139 | 140 | if (location > point_of_last_use) { 141 | auto maybe_reg = var_id_to_host_reg[var->id]; 142 | if (maybe_reg.HasValue()) { 143 | free_host_regs.push_back(maybe_reg.Unwrap()); 144 | var_id_to_host_reg[var->id] = {}; 145 | } 146 | } 147 | } 148 | } 149 | 150 | void X64RegisterAllocator::ReleaseTemporaryHostRegs() { 151 | for (auto reg : temp_host_regs) { 152 | free_host_regs.push_back(reg); 153 | } 154 | temp_host_regs.clear(); 155 | } 156 | 157 | auto X64RegisterAllocator::FindFreeHostReg() -> Xbyak::Reg32 { 158 | if (free_host_regs.size() != 0) { 159 | auto reg = free_host_regs.back(); 160 | free_host_regs.pop_back(); 161 | return reg; 162 | } 163 | 164 | auto const& current_op = *current_op_iter; 165 | 166 | // Find a variable to be spilled and deallocate it. 167 | // TODO: think of a smart way to pick which variable/register to spill. 168 | for (auto const& var : emitter.Vars()) { 169 | if (var_id_to_host_reg[var->id].HasValue()) { 170 | // Make sure the variable that we spill is not currently used. 171 | if (current_op->Reads(*var) || current_op->Writes(*var)) { 172 | continue; 173 | } 174 | 175 | auto reg = var_id_to_host_reg[var->id].Unwrap(); 176 | 177 | var_id_to_host_reg[var->id] = {}; 178 | 179 | // Spill the variable into one of the free slots. 180 | for (int slot = 0; slot < kSpillAreaSize; slot++) { 181 | if (!free_spill_bitmap[slot]) { 182 | code.mov(dword[rbp + slot * sizeof(u32)], reg); 183 | free_spill_bitmap[slot] = true; 184 | var_id_to_spill_slot[var->id] = slot; 185 | return reg; 186 | } 187 | } 188 | } 189 | } 190 | 191 | throw std::runtime_error("X64RegisterAllocator: out of registers and spill space."); 192 | } 193 | 194 | } // namespace lunatic::backend 195 | } // namespace lunatic 196 | -------------------------------------------------------------------------------- /src/backend/x86_64/register_allocator.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | #ifdef LUNATIC_INCLUDE_XBYAK_FROM_DIRECTORY 14 | #include 15 | #else 16 | #include 17 | #endif 18 | 19 | #include "common/optional.hpp" 20 | #include "frontend/ir/emitter.hpp" 21 | 22 | namespace lunatic { 23 | namespace backend { 24 | 25 | struct X64RegisterAllocator { 26 | using IREmitter = lunatic::frontend::IREmitter; 27 | 28 | static constexpr int kSpillAreaSize = 32; 29 | 30 | X64RegisterAllocator( 31 | IREmitter const& emitter, 32 | Xbyak::CodeGenerator& code 33 | ); 34 | 35 | /** 36 | * Advance to the next IR opcode in the IR program. 37 | */ 38 | void AdvanceLocation(); 39 | 40 | /** 41 | * Get the host register currently allocated to a variable. 42 | * 43 | * @param var The variable 44 | * @returns the host register 45 | */ 46 | auto GetVariableHostReg( 47 | lunatic::frontend::IRVariable const& var 48 | ) -> Xbyak::Reg32; 49 | 50 | /** 51 | * Get a temporary host register for use during the current opcode. 52 | * 53 | * @returns the host register 54 | */ 55 | auto GetTemporaryHostReg() -> Xbyak::Reg32; 56 | 57 | /** 58 | * If var_old will be released after the current opcode, 59 | * then it will be released early and the host register 60 | * allocated to it will be moved to var_new. 61 | * The caller is responsible to not read var_old after writing var_new. 62 | * 63 | * @param var_old the variable to release 64 | * @param var_new the variable to receive the released host register 65 | */ 66 | void ReleaseVarAndReuseHostReg( 67 | lunatic::frontend::IRVariable const& var_old, 68 | lunatic::frontend::IRVariable const& var_new 69 | ); 70 | 71 | bool IsHostRegFree(Xbyak::Reg64 reg) const; 72 | 73 | private: 74 | /// Determine when each variable will be dead. 75 | void EvaluateVariableLifetimes(); 76 | 77 | /// Release host registers allocated to variables that are dead. 78 | void ReleaseDeadVariables(); 79 | 80 | /// Release host registers allocated for temporary storage. 81 | void ReleaseTemporaryHostRegs(); 82 | 83 | /** 84 | * Find and allocate a host register that is currently unused. 85 | * If no register is free attempt to spill a variable to the stack to 86 | * free its register up. 87 | * 88 | * @returns the host register 89 | */ 90 | auto FindFreeHostReg() -> Xbyak::Reg32; 91 | 92 | IREmitter const& emitter; 93 | Xbyak::CodeGenerator& code; 94 | 95 | /// Host register that are free and can be allocated. 96 | std::vector free_host_regs; 97 | 98 | /// Map variable to its allocated host register (if any). 99 | std::vector> var_id_to_host_reg; 100 | 101 | /// Map variable to the last location where it's accessed. 102 | std::vector var_id_to_point_of_last_use; 103 | 104 | /// The set of free/unused spill slots. 105 | std::bitset free_spill_bitmap; 106 | 107 | /// Map variable to the slot it was spilled to (if it is spilled). 108 | std::vector> var_id_to_spill_slot; 109 | 110 | /// Array of currently allocated scratch registers. 111 | std::vector temp_host_regs; 112 | 113 | /// The current IR program location. 114 | int location = 0; 115 | 116 | /// Iterator pointing to the current IR program location. 117 | IREmitter::InstructionList::const_iterator current_op_iter; 118 | }; 119 | 120 | } // namespace lunatic::backend 121 | } // namespace lunatic 122 | -------------------------------------------------------------------------------- /src/backend/x86_64/vtune.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | #if LUNATIC_USE_VTUNE 14 | 15 | #include 16 | 17 | namespace vtune { 18 | 19 | static void ReportCallBlock(u8* codeBegin, const u8* codeEnd) { 20 | if (iJIT_IsProfilingActive() == iJIT_SAMPLING_ON) { 21 | char methodName[] = "lunatic_x64_callblock"; 22 | char moduleName[] = "lunatic-JIT"; 23 | 24 | iJIT_Method_Load_V2 jmethod = { 0 }; 25 | jmethod.method_id = iJIT_GetNewMethodID(); 26 | jmethod.method_name = methodName; 27 | jmethod.method_load_address = static_cast(codeBegin); 28 | jmethod.method_size = static_cast(codeEnd - codeBegin); 29 | jmethod.module_name = moduleName; 30 | iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED_V2, static_cast(&jmethod)); 31 | } 32 | } 33 | 34 | static void ReportBasicBlock(lunatic::frontend::BasicBlock& basic_block, const u8* codeEnd) { 35 | if (iJIT_IsProfilingActive() == iJIT_SAMPLING_ON) { 36 | auto& key = basic_block.key; 37 | 38 | auto modeStr = [&]() -> std::string { 39 | using lunatic::Mode; 40 | 41 | switch (key.Mode()) { 42 | case Mode::User: return "USR"; 43 | case Mode::FIQ: return "FIQ"; 44 | case Mode::IRQ: return "IRQ"; 45 | case Mode::Supervisor: return "SVC"; 46 | case Mode::Abort: return "ABT"; 47 | case Mode::Undefined: return "UND"; 48 | case Mode::System: return "SYS"; 49 | default: return fmt::format("{:02X}", static_cast(key.Mode())); 50 | } 51 | }(); 52 | 53 | auto thumbStr = key.Thumb() ? "Thumb" : "ARM"; 54 | auto methodName = fmt::format("lunatic_func_{:X}_{}_{}", key.Address(), modeStr, thumbStr); 55 | char moduleName[] = "lunatic-JIT"; 56 | 57 | iJIT_Method_Load_V2 jmethod = { 0 }; 58 | jmethod.method_id = iJIT_GetNewMethodID(); 59 | jmethod.method_name = methodName.data(); 60 | jmethod.method_load_address = reinterpret_cast(basic_block.function); 61 | jmethod.method_size = static_cast(codeEnd - reinterpret_cast(basic_block.function)); 62 | jmethod.module_name = moduleName; 63 | iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED_V2, static_cast(&jmethod)); 64 | } 65 | } 66 | 67 | } // namespace vtune 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /src/common/aligned_memory.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #ifdef __APPLE__ 13 | #include 14 | #endif 15 | 16 | #ifndef _WIN32 17 | #include 18 | #include 19 | #endif 20 | 21 | #ifdef _WIN32 22 | #define WIN32_LEAN_AND_MEAN 23 | #include 24 | #endif 25 | 26 | namespace memory { 27 | enum MemoryProtection { 28 | MemoryProtection_None = 0, 29 | MemoryProtection_Read = 1 << 0, 30 | MemoryProtection_Write = 1 << 1, 31 | MemoryProtection_Execute = 1 << 2, 32 | }; 33 | 34 | #ifdef _WIN32 35 | static std::size_t s_PageSize = 4096; 36 | #else 37 | static std::size_t s_PageSize = sysconf(_SC_PAGESIZE); 38 | #endif 39 | 40 | 41 | 42 | class MemoryBlock { 43 | private: 44 | static std::size_t CalculateRealSize(std::size_t size) { 45 | return (size + s_PageSize) & ~(s_PageSize - 1); 46 | } 47 | 48 | #ifdef _WIN32 49 | static DWORD TranslateMemoryProtection(MemoryProtection prot) { 50 | bool has_read; 51 | bool has_write; 52 | bool has_execute; 53 | 54 | has_read = (prot & MemoryProtection_Read) == MemoryProtection_Read; 55 | has_write = (prot & MemoryProtection_Write) == MemoryProtection_Write; 56 | has_execute = (prot & MemoryProtection_Execute) == MemoryProtection_Execute; 57 | 58 | if (has_write && !has_execute) { 59 | return PAGE_READWRITE; 60 | } else if (has_read && has_write && has_execute) { 61 | return PAGE_EXECUTE_READWRITE; 62 | } else if (has_read && has_execute) { 63 | return PAGE_EXECUTE_READ; 64 | } else if (has_execute) { 65 | return PAGE_EXECUTE; 66 | } else if (has_read) { 67 | return PAGE_READONLY; 68 | } else { 69 | return PAGE_NOACCESS; 70 | } 71 | } 72 | #else 73 | static int TranslateMemoryProtection(MemoryProtection prot) { 74 | int native_prot = PROT_NONE; 75 | 76 | if ((prot & MemoryProtection_Read) == MemoryProtection_Read) { 77 | native_prot |= PROT_READ; 78 | } 79 | 80 | if ((prot & MemoryProtection_Write) == MemoryProtection_Write) { 81 | native_prot |= PROT_WRITE; 82 | } 83 | 84 | if ((prot & MemoryProtection_Execute) == MemoryProtection_Execute) { 85 | native_prot |= PROT_EXEC; 86 | } 87 | 88 | return native_prot; 89 | } 90 | #endif 91 | 92 | static void* AllocateMemory(std::size_t aligned_size, MemoryProtection prot) { 93 | auto native_prot = MemoryBlock::TranslateMemoryProtection(prot); 94 | 95 | #ifdef _WIN32 96 | return VirtualAlloc(nullptr, aligned_size, MEM_COMMIT, native_prot); 97 | #else 98 | int mode = MAP_PRIVATE; 99 | 100 | #if defined(MAP_ANONYMOUS) 101 | mode |= MAP_ANONYMOUS; 102 | #elif defined(MAP_ANON) 103 | mode |= MAP_ANON; 104 | #else 105 | #error "Anonymous page not supported" 106 | #endif 107 | 108 | #if defined(MAP_JIT) 109 | mode |= MAP_JIT; 110 | #endif 111 | 112 | void* p = mmap(nullptr, aligned_size, native_prot, mode, -1, 0); 113 | 114 | if (p == MAP_FAILED) { 115 | return nullptr; 116 | } 117 | 118 | return p; 119 | #endif 120 | } 121 | 122 | static void FreeMemory(void *ptr, size_t size) { 123 | #ifdef _WIN32 124 | VirtualFree(ptr, size, MEM_RELEASE); 125 | #else 126 | munmap(ptr, size); 127 | #endif 128 | } 129 | 130 | public: 131 | void *ptr; 132 | std::size_t size; 133 | std::size_t real_size; 134 | 135 | explicit MemoryBlock(std::size_t size, MemoryProtection prot) : size(size), real_size(MemoryBlock::CalculateRealSize(size)) { 136 | this->ptr = MemoryBlock::AllocateMemory(this->real_size, prot); 137 | 138 | if (this->ptr == nullptr) { 139 | throw new std::bad_alloc(); 140 | } 141 | } 142 | 143 | ~MemoryBlock() { 144 | if (this->ptr != nullptr) { 145 | MemoryBlock::FreeMemory(this->ptr, this->real_size); 146 | } 147 | } 148 | 149 | bool Protect(MemoryProtection prot) { 150 | auto native_prot = MemoryBlock::TranslateMemoryProtection(prot); 151 | #ifdef _WIN32 152 | DWORD oldProtect = 0; 153 | return VirtualProtect(this->ptr, this->real_size, native_prot, &oldProtect) != 0; 154 | #else 155 | return mprotect(this->ptr, this->real_size, native_prot) == 0; 156 | #endif 157 | } 158 | }; 159 | 160 | class CodeBlockMemory { 161 | MemoryBlock memory_block; 162 | 163 | #if (defined (__APPLE__) && defined(__aarch64__)) || defined(_WIN32) 164 | static const MemoryProtection InitialMemoryProtection = static_cast(MemoryProtection_Read | MemoryProtection_Write | MemoryProtection_Execute); 165 | #else 166 | static const MemoryProtection InitialMemoryProtection = static_cast(MemoryProtection_Read | MemoryProtection_Write); 167 | #endif 168 | 169 | public: 170 | CodeBlockMemory(std::size_t size) : memory_block(size, InitialMemoryProtection) { 171 | #if defined(__APPLE__) && defined(__aarch64__) 172 | ProtectForWrite(); 173 | #endif 174 | } 175 | 176 | bool ProtectForWrite() { 177 | #if defined(_WIN32) 178 | return true; 179 | #elif defined(__APPLE__) && defined(__aarch64__) 180 | pthread_jit_write_protect_np(0); 181 | return true; 182 | #else 183 | return memory_block.Protect(static_cast(MemoryProtection_Read | MemoryProtection_Write)); 184 | #endif 185 | } 186 | 187 | bool ProtectForExecute() { 188 | #if defined(_WIN32) 189 | return true; 190 | #elif defined(__APPLE__) && defined(__aarch64__) 191 | pthread_jit_write_protect_np(1); 192 | return true; 193 | #else 194 | return memory_block.Protect(static_cast(MemoryProtection_Read | MemoryProtection_Execute)); 195 | #endif 196 | } 197 | 198 | void Invalidate() { 199 | #if defined (__APPLE__) 200 | sys_icache_invalidate(memory_block.ptr, memory_block.real_size); 201 | #elif defined(_WIN32) 202 | FlushInstructionCache(GetCurrentProcess(), memory_block.ptr, memory_block.real_size); 203 | #elif defined(__linux__) && defined(__aarch64__) 204 | #error "Implement cache invalidation logic for linux aarch64" 205 | #endif 206 | } 207 | 208 | void *GetPointer() { 209 | return memory_block.ptr; 210 | } 211 | }; 212 | 213 | 214 | } // namespace memory 215 | -------------------------------------------------------------------------------- /src/common/bit.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace bit { 16 | 17 | template 18 | constexpr auto number_of_bits() -> uint { 19 | return CHAR_BIT * sizeof(T); 20 | } 21 | 22 | template 23 | constexpr auto get_bit(T value, uint bit) -> U { 24 | return static_cast((value >> bit) & 1); 25 | } 26 | 27 | template 28 | constexpr auto get_field(T value, uint lowest_bit, uint count) -> U { 29 | return static_cast((value >> lowest_bit) & ~(static_cast(-1) << count)); 30 | } 31 | 32 | template 33 | constexpr auto rotate_right(T value, uint amount) -> T { 34 | auto bits = number_of_bits(); 35 | if (amount == 0) 36 | return value; 37 | return (value >> amount) | (value << (bits - amount)); 38 | } 39 | 40 | namespace detail { 41 | template 42 | constexpr auto build_pattern_mask(const char* pattern) -> T { 43 | auto result = T{}; 44 | auto i = 0U; 45 | auto bits = number_of_bits(); 46 | while (i < bits && pattern[i] != 0) { 47 | if (pattern[i] == '0' || pattern[i] == '1') 48 | result |= 1ULL << (bits - i - 1); 49 | i++; 50 | } 51 | result >>= bits - i; 52 | return result; 53 | } 54 | 55 | template 56 | constexpr auto build_pattern_value(const char* pattern) -> T { 57 | auto result = T{}; 58 | auto i = 0U; 59 | auto bits = number_of_bits(); 60 | while (i < bits && pattern[i] != 0) { 61 | if (pattern[i] == '1') 62 | result |= 1ULL << (bits - i - 1); 63 | i++; 64 | } 65 | result >>= bits - i; 66 | return result; 67 | } 68 | } // namespace bit::detail 69 | 70 | template 71 | constexpr auto match_pattern(T value, const char* pattern) -> bool { 72 | return (value & detail::build_pattern_mask(pattern)) == detail::build_pattern_value(pattern); 73 | } 74 | 75 | } // namespace bit 76 | -------------------------------------------------------------------------------- /src/common/compiler.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #if defined(__GNUC__) 11 | #define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__)) 12 | #elif defined(_MSC_VER) 13 | #define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop)) 14 | #else 15 | #error "Unsupported compiler" 16 | #endif -------------------------------------------------------------------------------- /src/common/meta.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | template 11 | struct Pointerify { 12 | using type = T; 13 | }; 14 | 15 | template 16 | struct Pointerify { 17 | using type = T*; 18 | }; 19 | 20 | template 21 | struct IsReference { 22 | static constexpr bool value = false; 23 | }; 24 | 25 | template 26 | struct IsReference { 27 | static constexpr bool value = true; 28 | }; 29 | -------------------------------------------------------------------------------- /src/common/optional.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #include "meta.hpp" 13 | 14 | /// std::optional like class template which supports optional references (unlike std::optional) 15 | template 16 | struct Optional { 17 | Optional() : is_null(true) {} 18 | 19 | Optional(T value) : is_null(false) { 20 | if constexpr (IsReference::value) { 21 | this->value = &value; 22 | } else { 23 | this->value = value; 24 | } 25 | } 26 | 27 | bool IsNull() const { return is_null; } 28 | bool HasValue() const { return !IsNull(); } 29 | 30 | // TODO: support Unwrap() on const Optional. 31 | auto Unwrap() -> T { 32 | if (IsNull()) { 33 | throw std::runtime_error("attempt to unwrap empty Optional"); 34 | } 35 | if constexpr (IsReference::value) { 36 | return *value; 37 | } else { 38 | return value; 39 | } 40 | } 41 | 42 | private: 43 | bool is_null; 44 | typename Pointerify::type value{}; 45 | }; 46 | -------------------------------------------------------------------------------- /src/common/pool_allocator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "pool_allocator.hpp" 9 | 10 | namespace lunatic { 11 | 12 | PoolAllocator g_pool_alloc; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/common/pool_allocator.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | #ifdef _WIN32 14 | #define WIN32_LEAN_AND_MEAN 15 | #include 16 | #endif 17 | 18 | #include "compiler.hpp" 19 | 20 | namespace lunatic { 21 | 22 | // T = data-type for object IDs (local to a pool) 23 | // capacity = number of objects in a pool 24 | // size = size of each object 25 | template 26 | struct PoolAllocator { 27 | static constexpr size_t max_size = size; 28 | 29 | auto Allocate() -> void* { 30 | if (free_pools.head == nullptr) { 31 | free_pools.head = new Pool{}; 32 | free_pools.tail = free_pools.head; 33 | } 34 | 35 | auto pool = free_pools.head; 36 | auto object = pool->Pop(); 37 | 38 | if (pool->IsFull()) { 39 | // Remove pool from the front of the free list. 40 | free_pools.Remove(pool); 41 | 42 | // Add pool to the end of the full list. 43 | full_pools.Append(pool); 44 | } 45 | 46 | return object; 47 | } 48 | 49 | void Release(void* object) { 50 | auto obj = (typename Pool::Object*)object; 51 | auto pool = (Pool*)(obj - obj->id); 52 | 53 | if (pool->IsFull()) { 54 | // Remove pool from the full list. 55 | full_pools.Remove(pool); 56 | 57 | // Add pool to the end of the free list. 58 | free_pools.Append(pool); 59 | } 60 | 61 | pool->Push(obj); 62 | 63 | // TODO: keep track of how many objects are available 64 | // and decide if we should release the pool based on that. 65 | if (pool->IsEmpty()) { 66 | free_pools.Remove(pool); 67 | delete pool; 68 | } 69 | } 70 | 71 | private: 72 | struct Pool { 73 | Pool() { 74 | T invert = capacity - 1; 75 | 76 | for (size_t id = 0; id < capacity; id++) { 77 | objects[id].id = id; 78 | 79 | stack.data[id] = invert - static_cast(id); 80 | } 81 | 82 | stack.length = capacity; 83 | } 84 | 85 | #ifdef _WIN32 86 | auto operator new(size_t) -> void* { 87 | return VirtualAlloc(nullptr, sizeof(Pool), MEM_COMMIT, PAGE_READWRITE); 88 | } 89 | 90 | void operator delete(void* object) { 91 | VirtualFree(object, sizeof(Pool), MEM_RELEASE); 92 | } 93 | #endif 94 | 95 | bool IsFull() const { return stack.length == 0; } 96 | 97 | bool IsEmpty() const { return stack.length == capacity; } 98 | 99 | void Push(void* object) { 100 | stack.data[stack.length++] = ((Object*)object)->id; 101 | } 102 | 103 | auto Pop() -> void* { 104 | return &objects[stack.data[--stack.length]]; 105 | } 106 | 107 | PACK(struct Object { 108 | u8 data[size]; 109 | T id; 110 | }) objects[capacity]; 111 | 112 | struct Stack { 113 | T data[capacity]; 114 | size_t length; 115 | } stack; 116 | 117 | Pool* prev = nullptr; 118 | Pool* next = nullptr; 119 | }; 120 | 121 | struct List { 122 | void Remove(Pool* pool) { 123 | if (pool->next == nullptr) { 124 | tail = pool->prev; 125 | if (tail) { 126 | tail->next = nullptr; 127 | } 128 | } else { 129 | pool->next->prev = pool->prev; 130 | } 131 | 132 | if (pool->prev == nullptr) { 133 | head = pool->next; 134 | if (head) { 135 | head->prev = nullptr; 136 | } 137 | } else { 138 | pool->prev->next = pool->next; 139 | } 140 | } 141 | 142 | void Append(Pool* pool) { 143 | if (head == nullptr) { 144 | head = pool; 145 | tail = pool; 146 | pool->prev = nullptr; 147 | } else { 148 | auto old_tail = tail; 149 | 150 | old_tail->next = pool; 151 | pool->prev = old_tail; 152 | tail = pool; 153 | } 154 | 155 | pool->next = nullptr; 156 | } 157 | 158 | ~List() { 159 | Pool* pool = head; 160 | 161 | while (pool != nullptr) { 162 | Pool* next_pool = pool->next; 163 | delete pool; 164 | pool = next_pool; 165 | } 166 | } 167 | 168 | Pool* head = nullptr; 169 | Pool* tail = nullptr; 170 | }; 171 | 172 | List free_pools; 173 | List full_pools; 174 | }; 175 | 176 | extern PoolAllocator g_pool_alloc; 177 | 178 | struct PoolObject { 179 | auto operator new(size_t size) -> void* { 180 | #ifndef NDEBUG 181 | if (size > decltype(g_pool_alloc)::max_size) { 182 | throw std::runtime_error( 183 | fmt::format("PoolObject: requested size ({}) is larger than the supported maximum ({})", 184 | size, decltype(g_pool_alloc)::max_size)); 185 | } 186 | #endif 187 | return g_pool_alloc.Allocate(); 188 | } 189 | 190 | void operator delete(void* object) { 191 | g_pool_alloc.Release(object); 192 | } 193 | }; 194 | 195 | // Wrapper around our g_pool_alloc that implements the 'Allocator' named requirements: 196 | // https://en.cppreference.com/w/cpp/named_req/Allocator 197 | template 198 | struct StdPoolAlloc { 199 | static_assert(sizeof(T) <= decltype(g_pool_alloc)::max_size, 200 | "StdPoolAlloc: type exceeds maxmimum supported allocation size"); 201 | 202 | using value_type = T; 203 | 204 | StdPoolAlloc() = default; 205 | 206 | template 207 | StdPoolAlloc(const StdPoolAlloc&) {} 208 | 209 | bool operator==(const StdPoolAlloc&) const noexcept { 210 | return true; 211 | } 212 | 213 | bool operator!=(const StdPoolAlloc&) const noexcept { 214 | return false; 215 | } 216 | 217 | auto allocate(std::size_t n) -> T* { 218 | return (T*)g_pool_alloc.Allocate(); 219 | } 220 | 221 | void deallocate(T* p, size_t n) { 222 | g_pool_alloc.Release(p); 223 | } 224 | }; 225 | 226 | } // namespace lunatic 227 | -------------------------------------------------------------------------------- /src/frontend/basic_block.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "common/pool_allocator.hpp" 15 | #include "decode/definition/common.hpp" 16 | #include "ir/emitter.hpp" 17 | #include "state.hpp" 18 | 19 | namespace lunatic { 20 | namespace frontend { 21 | 22 | struct BasicBlock : PoolObject { 23 | using CompiledFn = uintptr; 24 | 25 | union Key { 26 | Key() = default; 27 | 28 | explicit Key(State& state) { 29 | value = state.GetGPR(Mode::User, GPR::PC) >> 1; 30 | value |= u64(state.GetCPSR().v & 0x3F) << 31; // mode and thumb bit 31 | } 32 | 33 | explicit Key(u64 value) : value(value) {} 34 | 35 | Key(u32 address, Mode mode, bool thumb) { 36 | value = address >> 1; 37 | value |= static_cast(mode) << 31; 38 | if (thumb) { 39 | value |= 1ULL << 36; 40 | } 41 | } 42 | 43 | [[nodiscard]] auto Address() const -> u32 { 44 | return (value & 0x7FFFFFFF) << 1; 45 | } 46 | 47 | [[nodiscard]] auto Mode() const -> Mode { 48 | return static_cast((value >> 31) & 0x1F); 49 | } 50 | 51 | [[nodiscard]] bool Thumb() const { 52 | return value & (1ULL << 36); 53 | } 54 | 55 | [[nodiscard]] bool IsEmpty() const { 56 | return value == 0; 57 | } 58 | 59 | bool operator==(Key const& other) const { 60 | return value == other.value; 61 | } 62 | 63 | bool operator!=(Key const& other) const { 64 | return value != other.value; 65 | } 66 | 67 | explicit operator bool() const { 68 | return value != 0u; 69 | } 70 | 71 | // bits 0 - 30: address[31:1] 72 | // bits 31 - 35: CPU mode 73 | // bit 36: thumb-flag 74 | u64 value = 0; 75 | } key; 76 | 77 | BasicBlock() = default; 78 | explicit BasicBlock(Key key) : key(key) {} 79 | 80 | ~BasicBlock() { 81 | for (auto& callback : release_callbacks) callback(*this); 82 | } 83 | 84 | bool operator==(BasicBlock const& other) const { 85 | return key == other.key; 86 | } 87 | 88 | bool operator!=(BasicBlock const& other) const { 89 | return key != other.key; 90 | } 91 | 92 | void RegisterReleaseCallback(std::function const& callback) { 93 | release_callbacks.push_back(callback); 94 | } 95 | 96 | int length = 0; 97 | 98 | struct MicroBlock { 99 | Condition condition; 100 | IREmitter emitter; 101 | int length = 0; 102 | }; 103 | 104 | std::vector micro_blocks; 105 | 106 | // Pointer to the compiled code. 107 | CompiledFn function = (CompiledFn)0; 108 | 109 | struct BranchTarget { 110 | Key key{}; 111 | u8* patch_location = nullptr; 112 | Condition condition = Condition::AL; 113 | } branch_target; 114 | 115 | std::vector linking_blocks; 116 | 117 | u32 hash = 0; 118 | bool enable_fast_dispatch = true; 119 | bool uses_exception_base = false; 120 | 121 | private: 122 | std::vector> release_callbacks; 123 | }; 124 | 125 | } // namespace lunatic::frontend 126 | } // namespace lunatic 127 | 128 | template<> 129 | struct std::hash { 130 | std::size_t operator()(lunatic::frontend::BasicBlock::Key const& key) const { 131 | return std::hash{}(key.value); 132 | } 133 | }; -------------------------------------------------------------------------------- /src/frontend/basic_block_cache.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "basic_block.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct BasicBlockCache { 16 | ~BasicBlockCache() { 17 | /* Make sure that the cache does not consist of stale points, 18 | * once the basic blocks are deleted and X64Backend::OnBasicBlockToBeDeleted() will be called. 19 | */ 20 | Flush(); 21 | } 22 | 23 | void Flush() { 24 | for (int i = 0; i < 0x40000; i++) { 25 | data[i] = {}; 26 | } 27 | } 28 | 29 | void Flush(u32 address_lo, u32 address_hi) { 30 | // bits 0 - 30: address[31:1] 31 | // bits 31 - 35: CPU mode 32 | // bit 36: thumb-flag 33 | address_lo >>= 1; 34 | address_hi >>= 1; 35 | 36 | // TODO: there probably is a faster way to implement this. 37 | for (u64 status = 0; status <= 63; status++) { 38 | for (u32 address = address_lo; address <= address_hi; address++) { 39 | auto key = BasicBlock::Key{(status << 31) | address}; 40 | if (Get(key)) { 41 | Set(key, nullptr); 42 | } 43 | } 44 | } 45 | } 46 | 47 | auto Get(BasicBlock::Key key) const -> BasicBlock* { 48 | auto& table = data[key.value >> 19]; 49 | if (table == nullptr) { 50 | return nullptr; 51 | } 52 | return table->data[key.value & 0x7FFFF].get(); 53 | } 54 | 55 | void Set(BasicBlock::Key key, BasicBlock* block) { 56 | auto hash0 = key.value >> 19; 57 | auto hash1 = key.value & 0x7FFFF; 58 | auto& table = data[hash0]; 59 | 60 | if (table == nullptr) { 61 | data[hash0] = std::make_unique(); 62 | } 63 | 64 | auto current_block = std::move(table->data[hash1]); 65 | 66 | // Temporary fix: remove any linked blocks from the cache as well. 67 | if (current_block && current_block.get() != block) { 68 | for (auto linking_block : current_block->linking_blocks) { 69 | if (linking_block != current_block.get()) { 70 | Set(linking_block->key, nullptr); 71 | } 72 | } 73 | } 74 | 75 | table->data[hash1] = std::unique_ptr{block}; 76 | } 77 | 78 | struct Table { 79 | // int use_count = 0; 80 | std::unique_ptr data[0x80000]; 81 | }; 82 | 83 | // TODO: better manage the lifetimes of the tables. 84 | std::unique_ptr
data[0x40000]; 85 | }; 86 | 87 | } // namespace lunatic::frontend 88 | } // namespace lunatic 89 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/block_data_transfer.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "common.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct ARMBlockDataTransfer { 16 | Condition condition; 17 | 18 | bool pre_increment; 19 | bool add; 20 | bool user_mode; 21 | bool writeback; 22 | bool load; 23 | GPR reg_base; 24 | u16 reg_list; 25 | }; 26 | 27 | } // namespace lunatic::frontend 28 | } // namespace lunatic 29 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/branch_exchange.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "common.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct ARMBranchExchange { 16 | Condition condition; 17 | GPR reg; 18 | bool link; 19 | }; 20 | 21 | } // namespace lunatic::frontend 22 | } // namespace lunatic 23 | 24 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/branch_relative.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "common.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct ARMBranchRelative { 16 | Condition condition; 17 | s32 offset; 18 | bool link; 19 | bool exchange; 20 | }; 21 | 22 | } // namespace lunatic::frontend 23 | } // namespace lunatic 24 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/common.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | enum class Condition { 16 | EQ = 0, 17 | NE = 1, 18 | CS = 2, 19 | CC = 3, 20 | MI = 4, 21 | PL = 5, 22 | VS = 6, 23 | VC = 7, 24 | HI = 8, 25 | LS = 9, 26 | GE = 10, 27 | LT = 11, 28 | GT = 12, 29 | LE = 13, 30 | AL = 14, 31 | NV = 15 32 | }; 33 | 34 | enum class Shift { 35 | LSL = 0, 36 | LSR = 1, 37 | ASR = 2, 38 | ROR = 3 39 | }; 40 | 41 | enum class Exception { 42 | Reset = 0x00, 43 | Undefined = 0x04, 44 | Supervisor = 0x08, 45 | PrefetchAbort = 0x0C, 46 | DataAbort = 0x10, 47 | IRQ = 0x18, 48 | FIQ = 0x1C 49 | }; 50 | 51 | } // namespace lunatic::frontend 52 | } // namespace lunatic 53 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/coprocessor_register_transfer.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "common.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct ARMCoprocessorRegisterTransfer { 16 | Condition condition; 17 | 18 | bool load; 19 | GPR reg_dst; 20 | uint coprocessor_id; 21 | uint opcode1; 22 | uint cn; 23 | uint cm; 24 | uint opcode2; 25 | }; 26 | 27 | } // namespace lunatic::frontend 28 | } // namespace lunatic 29 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/count_leading_zeros.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "common.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct ARMCountLeadingZeros { 16 | Condition condition; 17 | GPR reg_src; 18 | GPR reg_dst; 19 | }; 20 | 21 | } // namespace lunatic::frontend 22 | } // namespace lunatic 23 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/data_processing.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "common.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct ARMDataProcessing { 16 | Condition condition; 17 | 18 | enum class Opcode { 19 | AND = 0, 20 | EOR = 1, 21 | SUB = 2, 22 | RSB = 3, 23 | ADD = 4, 24 | ADC = 5, 25 | SBC = 6, 26 | RSC = 7, 27 | TST = 8, 28 | TEQ = 9, 29 | CMP = 10, 30 | CMN = 11, 31 | ORR = 12, 32 | MOV = 13, 33 | BIC = 14, 34 | MVN = 15 35 | } opcode; 36 | 37 | bool immediate; 38 | bool set_flags; 39 | 40 | GPR reg_dst; 41 | GPR reg_op1; 42 | 43 | /// Valid if immediate = false 44 | struct { 45 | GPR reg; 46 | struct { 47 | Shift type; 48 | bool immediate; 49 | GPR amount_reg; 50 | uint amount_imm; 51 | } shift; 52 | } op2_reg; 53 | 54 | /// Valid if immediate = true 55 | struct { 56 | u32 value; 57 | uint shift; 58 | } op2_imm; 59 | 60 | bool thumb_load_address = false; 61 | }; 62 | 63 | } // namespace lunatic::frontend 64 | } // namespace lunatic 65 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/exception.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "common.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct ARMException { 16 | Condition condition; 17 | Exception exception; 18 | u32 svc_comment = 0; 19 | }; 20 | 21 | } // namespace lunatic::frontend 22 | } // namespace lunatic 23 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/halfword_signed_transfer.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "common.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct ARMHalfwordSignedTransfer { 16 | Condition condition; 17 | 18 | bool pre_increment; 19 | bool add; 20 | bool immediate; 21 | bool writeback; 22 | // TODO: clean this up... there must be a nice way... 23 | bool load; 24 | int opcode; 25 | 26 | GPR reg_dst; 27 | GPR reg_base; 28 | 29 | u32 offset_imm; 30 | GPR offset_reg; 31 | }; 32 | 33 | } // namespace lunatic::frontend 34 | } // namespace lunatic 35 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/multiply.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "common.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct ARMMultiply { 16 | Condition condition; 17 | bool accumulate; 18 | bool set_flags; 19 | GPR reg_op1; 20 | GPR reg_op2; 21 | GPR reg_op3; 22 | GPR reg_dst; 23 | }; 24 | 25 | } // namespace lunatic::frontend 26 | } // namespace lunatic 27 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/multiply_long.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "common.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct ARMMultiplyLong { 16 | Condition condition; 17 | bool sign_extend; 18 | bool accumulate; 19 | bool set_flags; 20 | GPR reg_op1; 21 | GPR reg_op2; 22 | GPR reg_dst_lo; 23 | GPR reg_dst_hi; 24 | }; 25 | 26 | } // namespace lunatic::frontend 27 | } // namespace lunatic 28 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/saturating_add_sub.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "common.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct ARMSaturatingAddSub { 16 | Condition condition; 17 | bool subtract; 18 | bool double_rhs; 19 | GPR reg_dst; 20 | GPR reg_lhs; 21 | GPR reg_rhs; 22 | }; 23 | 24 | } // namespace lunatic::frontend 25 | } // namespace lunatic 26 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/signed_halfword_multiply.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "common.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct ARMSignedHalfwordMultiply { 16 | Condition condition; 17 | bool accumulate; 18 | bool x; 19 | bool y; 20 | GPR reg_dst; 21 | GPR reg_lhs; 22 | GPR reg_rhs; 23 | GPR reg_op3; 24 | }; 25 | 26 | struct ARMSignedWordHalfwordMultiply { 27 | Condition condition; 28 | bool accumulate; 29 | bool y; 30 | GPR reg_dst; 31 | GPR reg_lhs; 32 | GPR reg_rhs; 33 | GPR reg_op3; 34 | }; 35 | 36 | struct ARMSignedHalfwordMultiplyAccumulateLong { 37 | Condition condition; 38 | bool x; 39 | bool y; 40 | GPR reg_dst_hi; 41 | GPR reg_dst_lo; 42 | GPR reg_lhs; 43 | GPR reg_rhs; 44 | }; 45 | 46 | } // namespace lunatic::frontend 47 | } // namespace lunatic 48 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/single_data_swap.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "common.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct ARMSingleDataSwap { 16 | Condition condition; 17 | bool byte; 18 | GPR reg_src; 19 | GPR reg_dst; 20 | GPR reg_base; 21 | }; 22 | 23 | } // namespace lunatic::frontend 24 | } // namespace lunatic 25 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/single_data_transfer.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "common.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct ARMSingleDataTransfer { 16 | Condition condition; 17 | 18 | bool immediate; 19 | bool pre_increment; 20 | bool add; 21 | bool byte; 22 | bool writeback; 23 | bool load; 24 | 25 | GPR reg_dst; 26 | GPR reg_base; 27 | 28 | u32 offset_imm; 29 | 30 | struct { 31 | GPR reg; 32 | Shift shift; 33 | u32 amount; 34 | } offset_reg; 35 | }; 36 | 37 | } // namespace lunatic::frontend 38 | } // namespace lunatic 39 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/status_transfer.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "common.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct ARMMoveStatusRegister { 16 | Condition condition; 17 | bool immediate; 18 | bool spsr; 19 | int fsxc; 20 | GPR reg; // if immediate == false 21 | u32 imm; // if immediate == true 22 | }; 23 | 24 | struct ARMMoveRegisterStatus { 25 | Condition condition; 26 | bool spsr; 27 | GPR reg; 28 | }; 29 | 30 | } // namespace lunatic::frontend 31 | } // namespace lunatic 32 | 33 | -------------------------------------------------------------------------------- /src/frontend/decode/definition/thumb_bl_suffix.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "common.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct ThumbBranchLinkSuffix { 16 | u32 offset; 17 | bool exchange; 18 | }; 19 | 20 | } // namespace lunatic::frontend 21 | } // namespace lunatic 22 | -------------------------------------------------------------------------------- /src/frontend/ir/emitter.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "common/optional.hpp" 15 | #include "common/pool_allocator.hpp" 16 | #include "opcode.hpp" 17 | 18 | namespace lunatic { 19 | namespace frontend { 20 | 21 | struct IREmitter { 22 | using OpcodePtr = std::unique_ptr; 23 | using InstructionList = std::list>; 24 | using VariableList = std::vector>; 25 | 26 | IREmitter() = default; 27 | IREmitter(const IREmitter&) = delete; 28 | IREmitter& operator=(const IREmitter&) = delete; 29 | 30 | IREmitter(IREmitter&& emitter) { 31 | operator=(std::move(emitter)); 32 | } 33 | 34 | IREmitter& operator=(IREmitter&& emitter) { 35 | std::swap(code, emitter.code); 36 | std::swap(variables, emitter.variables); 37 | return *this; 38 | } 39 | 40 | auto Code() -> InstructionList& { return code; } 41 | auto Code() const -> InstructionList const& { return code; } 42 | auto Vars() const -> VariableList const& { return variables; } 43 | auto ToString() const -> std::string; 44 | 45 | auto CreateVar( 46 | IRDataType data_type, 47 | char const* label = nullptr 48 | ) -> IRVariable const&; 49 | 50 | void LoadGPR (IRGuestReg reg, IRVariable const& result); 51 | void StoreGPR(IRGuestReg reg, IRAnyRef value); 52 | 53 | void LoadSPSR (IRVariable const& result, Mode mode); 54 | void StoreSPSR(IRAnyRef value, Mode mode); 55 | void LoadCPSR (IRVariable const& result); 56 | void StoreCPSR(IRAnyRef value); 57 | 58 | void ClearCarry(); 59 | void SetCarry(); 60 | 61 | void UpdateNZ( 62 | IRVariable const& result, 63 | IRVariable const& input 64 | ); 65 | 66 | void UpdateNZC( 67 | IRVariable const& result, 68 | IRVariable const& input 69 | ); 70 | 71 | void UpdateNZCV( 72 | IRVariable const& result, 73 | IRVariable const& input 74 | ); 75 | 76 | void UpdateQ( 77 | IRVariable const& result, 78 | IRVariable const& input 79 | ); 80 | 81 | void LSL( 82 | IRVariable const& result, 83 | IRVariable const& operand, 84 | IRAnyRef amount, 85 | bool update_host_flags 86 | ); 87 | 88 | void LSR( 89 | IRVariable const& result, 90 | IRVariable const& operand, 91 | IRAnyRef amount, 92 | bool update_host_flags 93 | ); 94 | 95 | void ASR( 96 | IRVariable const& result, 97 | IRVariable const& operand, 98 | IRAnyRef amount, 99 | bool update_host_flags 100 | ); 101 | 102 | void ROR( 103 | IRVariable const& result, 104 | IRVariable const& operand, 105 | IRAnyRef amount, 106 | bool update_host_flags 107 | ); 108 | 109 | void AND( 110 | Optional result, 111 | IRVariable const& lhs, 112 | IRAnyRef rhs, 113 | bool update_host_flags 114 | ); 115 | 116 | void BIC( 117 | IRVariable const& result, 118 | IRVariable const& lhs, 119 | IRAnyRef rhs, 120 | bool update_host_flags 121 | ); 122 | 123 | void EOR( 124 | Optional result, 125 | IRVariable const& lhs, 126 | IRAnyRef rhs, 127 | bool update_host_flags 128 | ); 129 | 130 | void SUB( 131 | Optional result, 132 | IRVariable const& lhs, 133 | IRAnyRef rhs, 134 | bool update_host_flags 135 | ); 136 | 137 | void RSB( 138 | IRVariable const& result, 139 | IRVariable const& lhs, 140 | IRAnyRef rhs, 141 | bool update_host_flags 142 | ); 143 | 144 | void ADD( 145 | Optional result, 146 | IRVariable const& lhs, 147 | IRAnyRef rhs, 148 | bool update_host_flags 149 | ); 150 | 151 | void ADC( 152 | IRVariable const& result, 153 | IRVariable const& lhs, 154 | IRAnyRef rhs, 155 | bool update_host_flags 156 | ); 157 | 158 | void SBC( 159 | IRVariable const& result, 160 | IRVariable const& lhs, 161 | IRAnyRef rhs, 162 | bool update_host_flags 163 | ); 164 | 165 | void RSC( 166 | IRVariable const& result, 167 | IRVariable const& lhs, 168 | IRAnyRef rhs, 169 | bool update_host_flags 170 | ); 171 | 172 | void ORR( 173 | IRVariable const& result, 174 | IRVariable const& lhs, 175 | IRAnyRef rhs, 176 | bool update_host_flags 177 | ); 178 | 179 | void MOV( 180 | IRVariable const& result, 181 | IRAnyRef source, 182 | bool update_host_flags 183 | ); 184 | 185 | void MVN( 186 | IRVariable const& result, 187 | IRAnyRef source, 188 | bool update_host_flags 189 | ); 190 | 191 | void MUL( 192 | Optional result_hi, 193 | IRVariable const& result_lo, 194 | IRVariable const& lhs, 195 | IRVariable const& rhs, 196 | bool update_host_flags 197 | ); 198 | 199 | void ADD64( 200 | IRVariable const& result_hi, 201 | IRVariable const& result_lo, 202 | IRVariable const& lhs_hi, 203 | IRVariable const& lhs_lo, 204 | IRVariable const& rhs_hi, 205 | IRVariable const& rhs_lo, 206 | bool update_host_flags 207 | ); 208 | 209 | void LDR( 210 | IRMemoryFlags flags, 211 | IRVariable const& result, 212 | IRVariable const& address 213 | ); 214 | 215 | void STR( 216 | IRMemoryFlags flags, 217 | IRVariable const& source, 218 | IRVariable const& address 219 | ); 220 | 221 | void Flush( 222 | IRVariable const& address_out, 223 | IRVariable const& address_in, 224 | IRVariable const& cpsr_in 225 | ); 226 | 227 | void FlushExchange( 228 | IRVariable const& address_out, 229 | IRVariable const& cpsr_out, 230 | IRVariable const& address_in, 231 | IRVariable const& cpsr_in 232 | ); 233 | 234 | void CLZ( 235 | IRVariable const& result, 236 | IRVariable const& operand 237 | ); 238 | 239 | void QADD( 240 | IRVariable const& result, 241 | IRVariable const& lhs, 242 | IRVariable const& rhs 243 | ); 244 | 245 | void QSUB( 246 | IRVariable const& result, 247 | IRVariable const& lhs, 248 | IRVariable const& rhs 249 | ); 250 | 251 | void MRC( 252 | IRVariable const& result, 253 | int coprocessor_id, 254 | int opcode1, 255 | int cn, 256 | int cm, 257 | int opcode2 258 | ); 259 | 260 | void MCR( 261 | IRAnyRef value, 262 | int coprocessor_id, 263 | int opcode1, 264 | int cn, 265 | int cm, 266 | int opcode2 267 | ); 268 | 269 | private: 270 | template 271 | void Push(Args&&... args) { 272 | code.push_back(std::make_unique(args...)); 273 | } 274 | 275 | InstructionList code; 276 | VariableList variables; 277 | }; 278 | 279 | } // namespace lunatic::frontend 280 | } // namespace lunatic 281 | -------------------------------------------------------------------------------- /src/frontend/ir/register.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #include "frontend/state.hpp" 13 | 14 | namespace lunatic { 15 | namespace frontend { 16 | 17 | /// References an ARM guest register with respect to the processor mode. 18 | struct IRGuestReg { 19 | IRGuestReg(GPR reg, Mode mode) : reg(reg), mode(mode) {} 20 | 21 | /// The ARM general purpose register 22 | const GPR reg; 23 | 24 | /// The ARM processor mode 25 | const Mode mode; 26 | 27 | auto ID() -> int { 28 | auto id = static_cast(reg); 29 | 30 | if (id <= 7 || (id <= 12 && mode != Mode::FIQ) || id == 15) { 31 | return id; 32 | } 33 | 34 | if (mode == Mode::User) { 35 | id |= static_cast(Mode::System) << 4; 36 | } else { 37 | id |= static_cast(mode) << 4; 38 | } 39 | 40 | return id; 41 | } 42 | }; 43 | 44 | } // namespace lunatic::frontend 45 | } // namespace lunatic 46 | 47 | namespace std { 48 | 49 | inline auto to_string(lunatic::frontend::IRGuestReg const& guest_reg) -> std::string { 50 | using Mode = lunatic::Mode; 51 | 52 | auto id = static_cast(guest_reg.reg); 53 | auto mode = guest_reg.mode; 54 | 55 | if (id <= 7 || (id <= 12 && mode != Mode::FIQ) || id == 15) { 56 | return fmt::format("r{0}", id); 57 | } 58 | 59 | return fmt::format("r{0}_{1}", id, std::to_string(mode)); 60 | } 61 | 62 | } // namespace std 63 | -------------------------------------------------------------------------------- /src/frontend/ir/value.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "common/optional.hpp" 15 | #include "common/pool_allocator.hpp" 16 | 17 | namespace lunatic { 18 | namespace frontend { 19 | 20 | enum class IRDataType { 21 | UInt32, 22 | SInt32 23 | }; 24 | 25 | /// Represents an immutable variable 26 | struct IRVariable : PoolObject { 27 | IRVariable(IRVariable const& other) = delete; 28 | 29 | /// ID that is unique inside the IREmitter instance. 30 | const u32 id; 31 | 32 | /// The underlying data type 33 | const IRDataType data_type; 34 | 35 | /// An optional label to hint at the variable usage 36 | char const* const label; 37 | 38 | private: 39 | friend struct IREmitter; 40 | 41 | IRVariable( 42 | u32 id, 43 | IRDataType data_type, 44 | char const* label 45 | ) : id(id), data_type(data_type), label(label) {} 46 | }; 47 | 48 | /// Represents an immediate (constant) value 49 | struct IRConstant { 50 | IRConstant() : data_type(IRDataType::UInt32), value(0) {} 51 | IRConstant(u32 value) : data_type(IRDataType::UInt32), value(value) {} 52 | 53 | /// The underlying data type 54 | IRDataType data_type; 55 | 56 | /// The underlying constant value 57 | u32 value; 58 | }; 59 | 60 | /// Represents an IR argument that can be null, a constant or a variable. 61 | struct IRAnyRef { 62 | IRAnyRef() {} 63 | IRAnyRef(IRVariable const& variable) : type(Type::Variable), variable(&variable) {} 64 | IRAnyRef(IRConstant const& constant) : type(Type::Constant), constant( constant) {} 65 | 66 | auto operator=(IRAnyRef const& other) -> IRAnyRef& { 67 | type = other.type; 68 | if (IsConstant()) { 69 | constant = other.constant; 70 | } else { 71 | variable = other.variable; 72 | } 73 | return *this; 74 | } 75 | 76 | bool IsNull() const { return type == Type::Null; } 77 | bool IsVariable() const { return type == Type::Variable; } 78 | bool IsConstant() const { return type == Type::Constant; } 79 | 80 | auto GetVar() const -> IRVariable const& { 81 | if (!IsVariable()) { 82 | throw std::runtime_error("called GetVar() but value is a constant or null"); 83 | } 84 | return *variable; 85 | } 86 | 87 | auto GetConst() const -> IRConstant const& { 88 | if (!IsConstant()) { 89 | throw std::runtime_error("called GetConst() but value is a variable or null"); 90 | } 91 | return constant; 92 | } 93 | 94 | void Repoint( 95 | IRVariable const& var_old, 96 | IRVariable const& var_new 97 | ) { 98 | if (IsVariable() && (&GetVar() == &var_old)) { 99 | variable = &var_new; 100 | } 101 | } 102 | 103 | void PropagateConstant( 104 | IRVariable const& var, 105 | IRConstant const& constant 106 | ) { 107 | if (IsVariable() && (&GetVar() == &var)) { 108 | type = Type::Constant; 109 | this->constant = constant; 110 | } 111 | } 112 | 113 | private: 114 | enum class Type { 115 | Null, 116 | Variable, 117 | Constant 118 | }; 119 | 120 | Type type = Type::Null; 121 | 122 | union { 123 | IRVariable const* variable; 124 | IRConstant constant; 125 | }; 126 | }; 127 | 128 | /// Represents an IR argument that always is a variable. 129 | struct IRVarRef { 130 | IRVarRef(IRVariable const& var) : p_var(&var) {} 131 | 132 | auto Get() const -> IRVariable const& { 133 | return *p_var; 134 | } 135 | 136 | void Repoint( 137 | IRVariable const& var_old, 138 | IRVariable const& var_new 139 | ) { 140 | if (&var_old == p_var) { 141 | p_var = &var_new; 142 | } 143 | } 144 | 145 | private: 146 | IRVariable const* p_var; 147 | }; 148 | 149 | } // namespace lunatic::frontend 150 | } // namespace lunatic 151 | 152 | namespace std { 153 | 154 | inline auto to_string(lunatic::frontend::IRDataType data_type) -> std::string { 155 | switch (data_type) { 156 | case lunatic::frontend::IRDataType::UInt32: 157 | return "u32"; 158 | default: 159 | return "???"; 160 | } 161 | } 162 | 163 | inline auto to_string(lunatic::frontend::IRVariable const& variable) -> std::string { 164 | if (variable.label) { 165 | return fmt::format("var{}_{}", variable.id, variable.label); 166 | } 167 | return fmt::format("var{}", variable.id); 168 | } 169 | 170 | inline auto to_string(lunatic::frontend::IRConstant const& constant) -> std::string { 171 | return fmt::format("0x{:08X}", constant.value); 172 | } 173 | 174 | inline auto to_string(lunatic::frontend::IRAnyRef const& value) -> std::string { 175 | if (value.IsNull()) { 176 | return "(null)"; 177 | } 178 | if (value.IsConstant()) { 179 | return std::to_string(value.GetConst()); 180 | } 181 | return std::to_string(value.GetVar()); 182 | } 183 | 184 | inline auto to_string(Optional value) -> std::string { 185 | if (value.IsNull()) { 186 | return "(null)"; 187 | } 188 | return std::to_string(value.Unwrap()); 189 | } 190 | 191 | inline auto to_string(lunatic::frontend::IRVarRef const& variable) -> std::string { 192 | return std::to_string(variable.Get()); 193 | } 194 | 195 | } // namespace std 196 | -------------------------------------------------------------------------------- /src/frontend/ir_opt/constant_propagation.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "common/bit.hpp" 9 | #include "frontend/ir_opt/constant_propagation.hpp" 10 | 11 | namespace lunatic { 12 | namespace frontend { 13 | 14 | void IRConstantPropagationPass::Run(IREmitter& emitter) { 15 | this->emitter = &emitter; 16 | var_to_const.clear(); 17 | var_to_const.resize(emitter.Vars().size()); 18 | 19 | for (auto& op : emitter.Code()) { 20 | switch (op->GetClass()) { 21 | case IROpcodeClass::MOV: DoMOV(op); break; 22 | case IROpcodeClass::LSL: DoLSL(op); break; 23 | case IROpcodeClass::LSR: DoLSR(op); break; 24 | case IROpcodeClass::ASR: DoASR(op); break; 25 | case IROpcodeClass::ROR: DoROR(op); break; 26 | case IROpcodeClass::ADD: DoBinaryOp(op); break; 27 | case IROpcodeClass::SUB: DoBinaryOp(op); break; 28 | case IROpcodeClass::AND: DoBinaryOp(op); break; 29 | case IROpcodeClass::BIC: DoBinaryOp(op); break; 30 | case IROpcodeClass::EOR: DoBinaryOp(op); break; 31 | case IROpcodeClass::ORR: DoBinaryOp(op); break; 32 | case IROpcodeClass::MUL: DoMUL(op); break; 33 | } 34 | } 35 | } 36 | 37 | void IRConstantPropagationPass::Propagate(IRVariable const& var, IRConstant const& constant) { 38 | var_to_const[var.id] = constant; 39 | 40 | // TODO: start at the opcode where the variable is first written. 41 | for (auto& op : emitter->Code()) { 42 | if (op->Reads(var)) { 43 | op->PropagateConstant(var, constant); 44 | } 45 | } 46 | }; 47 | 48 | auto IRConstantPropagationPass::GetKnownConstant(IRVarRef const& var) -> Optional& { 49 | return var_to_const[var.Get().id]; 50 | }; 51 | 52 | void IRConstantPropagationPass::DoMOV(std::unique_ptr& op) { 53 | auto mov_op = lunatic_cast(op.get()); 54 | 55 | if (mov_op->source.IsConstant()) { 56 | Propagate(mov_op->result.Get(), mov_op->source.GetConst()); 57 | } 58 | } 59 | 60 | void IRConstantPropagationPass::DoLSL(std::unique_ptr& op) { 61 | auto lsl_op = lunatic_cast(op.get()); 62 | 63 | auto& result = lsl_op->result.Get(); 64 | auto& operand = GetKnownConstant(lsl_op->operand); 65 | auto& amount = lsl_op->amount; 66 | 67 | if (operand.HasValue() && amount.IsConstant()) { 68 | int shift_amount = (int)(amount.GetConst().value & 255); 69 | IRConstant constant = shift_amount >= 32 ? 0 : (operand.Unwrap().value << shift_amount); 70 | 71 | Propagate(result, constant); 72 | 73 | if (!lsl_op->update_host_flags) { 74 | op = MOV(result, constant); 75 | } 76 | } 77 | } 78 | 79 | void IRConstantPropagationPass::DoLSR(std::unique_ptr& op) { 80 | auto lsr_op = lunatic_cast(op.get()); 81 | 82 | auto& result = lsr_op->result.Get(); 83 | auto& operand = GetKnownConstant(lsr_op->operand); 84 | auto& amount = lsr_op->amount; 85 | 86 | if (operand.HasValue() && amount.IsConstant()) { 87 | int shift_amount = (int)(amount.GetConst().value & 255); 88 | IRConstant constant = (shift_amount == 0 || shift_amount >= 32) ? 0 : (operand.Unwrap().value >> shift_amount); 89 | 90 | Propagate(result, constant); 91 | 92 | if (!lsr_op->update_host_flags) { 93 | op = MOV(result, constant); 94 | } 95 | } 96 | } 97 | 98 | void IRConstantPropagationPass::DoASR(std::unique_ptr& op) { 99 | auto asr_op = lunatic_cast(op.get()); 100 | 101 | auto& result = asr_op->result.Get(); 102 | auto& operand = GetKnownConstant(asr_op->operand); 103 | auto& amount = asr_op->amount; 104 | 105 | if (operand.HasValue() && amount.IsConstant()) { 106 | int shift_amount = (int)(amount.GetConst().value & 255); 107 | u32 operand_value = operand.Unwrap().value; 108 | 109 | if (shift_amount == 0 || shift_amount >= 32) { 110 | shift_amount = 31; 111 | } 112 | 113 | IRConstant constant = (u32)((s32)operand_value >> shift_amount); 114 | 115 | Propagate(result, constant); 116 | 117 | if (!asr_op->update_host_flags) { 118 | op = MOV(result, constant); 119 | } 120 | } 121 | } 122 | 123 | void IRConstantPropagationPass::DoROR(std::unique_ptr& op) { 124 | auto ror_op = lunatic_cast(op.get()); 125 | 126 | auto& result = ror_op->result.Get(); 127 | auto& operand = GetKnownConstant(ror_op->operand); 128 | auto& amount = ror_op->amount; 129 | 130 | if (operand.HasValue() && amount.IsConstant()) { 131 | u32 shift_amount = amount.GetConst().value; 132 | 133 | if (shift_amount == 0) { 134 | // RRX #1 135 | return; 136 | } 137 | 138 | IRConstant constant = bit::rotate_right(operand.Unwrap().value, shift_amount & 31); 139 | 140 | Propagate(ror_op->result.Get(), constant); 141 | 142 | if (!ror_op->update_host_flags) { 143 | op = MOV(result, constant); 144 | } 145 | } 146 | } 147 | 148 | template 149 | void IRConstantPropagationPass::DoBinaryOp(std::unique_ptr& op) { 150 | constexpr IROpcodeClass klass = OpcodeType::klass; 151 | 152 | auto bin_op = lunatic_cast(op.get()); 153 | auto& result = bin_op->result; 154 | auto& lhs = GetKnownConstant(bin_op->lhs); 155 | auto& rhs = bin_op->rhs; 156 | 157 | if (lhs.HasValue() && rhs.IsConstant()) { 158 | IRConstant constant; 159 | 160 | u32 lhs_value = lhs.Unwrap().value; 161 | u32 rhs_value = rhs.GetConst().value; 162 | 163 | switch (klass) { 164 | case IROpcodeClass::ADD: constant = lhs_value + rhs_value; break; 165 | case IROpcodeClass::SUB: constant = lhs_value - rhs_value; break; 166 | case IROpcodeClass::AND: constant = lhs_value & rhs_value; break; 167 | case IROpcodeClass::BIC: constant = lhs_value & ~rhs_value; break; 168 | case IROpcodeClass::EOR: constant = lhs_value ^ rhs_value; break; 169 | case IROpcodeClass::ORR: constant = lhs_value | rhs_value; break; 170 | } 171 | 172 | if (result.HasValue()) { 173 | Propagate(result.Unwrap(), constant); 174 | } 175 | 176 | bool update_host_flags = bin_op->update_host_flags; 177 | 178 | // Attempt to replace opcode with a MOV (removes dependencies on operand variables) 179 | if (result.HasValue()) { 180 | if (klass == IROpcodeClass::ADD || klass == IROpcodeClass::SUB) { 181 | if (!update_host_flags) { 182 | op = MOV(result.Unwrap(), constant, false); 183 | } 184 | } else { 185 | // AND, BIC, EOR, ORR 186 | op = MOV(result.Unwrap(), constant, update_host_flags); 187 | } 188 | } else if (!update_host_flags) { 189 | op = NOP(); 190 | } 191 | } 192 | } 193 | 194 | void IRConstantPropagationPass::DoMUL(std::unique_ptr& op) { 195 | const auto mul_op = (IRMultiply*)op.get(); 196 | 197 | auto& lhs = GetKnownConstant(mul_op->lhs.Get()); 198 | auto& rhs = GetKnownConstant(mul_op->rhs.Get()); 199 | 200 | if (lhs.HasValue() && rhs.HasValue()) { 201 | if (mul_op->result_hi.HasValue()) { 202 | if (mul_op->lhs.Get().data_type == IRDataType::SInt32) { 203 | s64 result = (s64)(s32)lhs.Unwrap().value * (s64)(s32)rhs.Unwrap().value; 204 | IRConstant constant_lo = (u32)result; 205 | IRConstant constant_hi = (u32)(result >> 32); 206 | 207 | Propagate(mul_op->result_lo.Get(), constant_lo); 208 | Propagate(mul_op->result_hi.Unwrap(), constant_hi); 209 | } else { 210 | u64 result = (u64)lhs.Unwrap().value * (u64)rhs.Unwrap().value; 211 | IRConstant constant_lo = (u32)result; 212 | IRConstant constant_hi = (u32)(result >> 32); 213 | 214 | Propagate(mul_op->result_lo.Get(), constant_lo); 215 | Propagate(mul_op->result_hi.Unwrap(), constant_hi); 216 | } 217 | } else { 218 | IRConstant constant = lhs.Unwrap().value * rhs.Unwrap().value; 219 | 220 | Propagate(mul_op->result_lo.Get(), constant); 221 | op = MOV(mul_op->result_lo.Get(), constant, mul_op->update_host_flags); 222 | } 223 | } 224 | } 225 | 226 | } // namespace lunatic::frontend 227 | } // namespace lunatic 228 | -------------------------------------------------------------------------------- /src/frontend/ir_opt/constant_propagation.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #include "frontend/ir_opt/pass.hpp" 13 | 14 | namespace lunatic { 15 | namespace frontend { 16 | 17 | struct IRConstantPropagationPass final : IRPass { 18 | void Run(IREmitter& emitter) override; 19 | 20 | private: 21 | void Propagate(IRVariable const& var, IRConstant const& constant); 22 | auto GetKnownConstant(IRVarRef const& var) -> Optional&; 23 | 24 | void DoMOV(std::unique_ptr& op); 25 | void DoLSL(std::unique_ptr& op); 26 | void DoLSR(std::unique_ptr& op); 27 | void DoASR(std::unique_ptr& op); 28 | void DoROR(std::unique_ptr& op); 29 | void DoMUL(std::unique_ptr& op); 30 | 31 | template 32 | void DoBinaryOp(std::unique_ptr& op); 33 | 34 | static auto MOV(IRVariable const& result, IRConstant const& constant, bool update_host_flags = false) { 35 | return std::make_unique(result, constant, update_host_flags); 36 | }; 37 | 38 | static auto NOP() { 39 | return std::make_unique(); 40 | }; 41 | 42 | IREmitter* emitter; 43 | std::vector> var_to_const{}; 44 | }; 45 | 46 | } // namespace lunatic::frontend 47 | } // namespace lunatic 48 | -------------------------------------------------------------------------------- /src/frontend/ir_opt/context_load_store_elision.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/ir_opt/context_load_store_elision.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | void IRContextLoadStoreElisionPass::Run(IREmitter& emitter) { 14 | // Forward pass: remove redundant GPR and CPSR reads 15 | RemoveLoads(emitter); 16 | 17 | // Backward pass: remove redundant GPR and CPSR stores 18 | RemoveStores(emitter); 19 | } 20 | 21 | void IRContextLoadStoreElisionPass::RemoveLoads(IREmitter& emitter) { 22 | auto& code = emitter.Code(); 23 | auto it = code.begin(); 24 | auto end = code.end(); 25 | IRAnyRef current_gpr_value[512] {}; 26 | IRAnyRef current_cpsr_value; 27 | 28 | auto Move = [&](IRVariable const& dst, IRAnyRef src) { 29 | code.insert(it, std::make_unique(dst, src, false)); 30 | }; 31 | 32 | while (it != end) { 33 | switch (it->get()->GetClass()) { 34 | case IROpcodeClass::StoreGPR: { 35 | auto op = lunatic_cast(it->get()); 36 | auto gpr_id = op->reg.ID(); 37 | 38 | current_gpr_value[gpr_id] = op->value; 39 | break; 40 | } 41 | case IROpcodeClass::LoadGPR: { 42 | auto op = lunatic_cast(it->get()); 43 | auto gpr_id = op->reg.ID(); 44 | auto var_src = current_gpr_value[gpr_id]; 45 | auto& var_dst = op->result.Get(); 46 | 47 | if (!var_src.IsNull()) { 48 | it = code.erase(it); 49 | 50 | // TODO: if var_src is constant attempt updating IRAnyRefs. 51 | if (var_src.IsConstant() || !Repoint(var_dst, var_src.GetVar(), it, end)) { 52 | Move(var_dst, var_src); 53 | } 54 | continue; 55 | } else { 56 | current_gpr_value[gpr_id] = var_dst; 57 | } 58 | break; 59 | } 60 | case IROpcodeClass::StoreCPSR: { 61 | current_cpsr_value = lunatic_cast(it->get())->value; 62 | break; 63 | } 64 | case IROpcodeClass::LoadCPSR: { 65 | auto op = lunatic_cast(it->get()); 66 | auto var_src = current_cpsr_value; 67 | auto& var_dst = op->result.Get(); 68 | 69 | if (!var_src.IsNull()) { 70 | it = code.erase(it); 71 | 72 | // TODO: if var_src is constant attempt updating IRAnyRefs. 73 | if (var_src.IsConstant() || !Repoint(var_dst, var_src.GetVar(), it, end)) { 74 | Move(var_dst, var_src); 75 | } 76 | continue; 77 | } else { 78 | current_cpsr_value = var_dst; 79 | } 80 | break; 81 | } 82 | default: { 83 | break; 84 | } 85 | } 86 | 87 | ++it; 88 | } 89 | } 90 | 91 | void IRContextLoadStoreElisionPass::RemoveStores(IREmitter& emitter) { 92 | auto& code = emitter.Code(); 93 | auto it = code.rbegin(); 94 | auto end = code.rend(); 95 | bool gpr_already_stored[512] {false}; 96 | bool cpsr_already_stored = false; 97 | 98 | while (it != end) { 99 | switch (it->get()->GetClass()) { 100 | case IROpcodeClass::StoreGPR: { 101 | auto op = lunatic_cast(it->get()); 102 | auto gpr_id = op->reg.ID(); 103 | 104 | if (gpr_already_stored[gpr_id]) { 105 | it = std::reverse_iterator{code.erase(std::next(it).base())}; 106 | end = code.rend(); 107 | continue; 108 | } else { 109 | gpr_already_stored[gpr_id] = true; 110 | } 111 | break; 112 | } 113 | case IROpcodeClass::StoreCPSR: { 114 | if (cpsr_already_stored) { 115 | it = std::reverse_iterator{code.erase(std::next(it).base())}; 116 | end = code.rend(); 117 | continue; 118 | } else { 119 | cpsr_already_stored = true; 120 | } 121 | break; 122 | } 123 | default: { 124 | break; 125 | } 126 | } 127 | 128 | ++it; 129 | } 130 | } 131 | 132 | } // namespace lunatic::frontend 133 | } // namespace lunatic 134 | -------------------------------------------------------------------------------- /src/frontend/ir_opt/context_load_store_elision.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "frontend/ir_opt/pass.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct IRContextLoadStoreElisionPass final : IRPass { 16 | void Run(IREmitter& emitter) override; 17 | 18 | private: 19 | void RemoveLoads(IREmitter& emitter); 20 | void RemoveStores(IREmitter& emitter); 21 | }; 22 | 23 | } // namespace lunatic::frontend 24 | } // namespace lunatic 25 | -------------------------------------------------------------------------------- /src/frontend/ir_opt/dead_code_elision.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/ir_opt/dead_code_elision.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | void IRDeadCodeElisionPass::Run(IREmitter& emitter) { 14 | auto& code = emitter.Code(); 15 | this->emitter = &emitter; 16 | it = code.begin(); 17 | end = code.end(); 18 | 19 | while (it != end) { 20 | bool dead = false; 21 | 22 | switch (it->get()->GetClass()) { 23 | case IROpcodeClass::MOV: dead = CheckMOV(lunatic_cast(it->get())); break; 24 | case IROpcodeClass::LSL: dead = CheckShifterOp(lunatic_cast(it->get())); break; 25 | case IROpcodeClass::LSR: dead = CheckShifterOp(lunatic_cast(it->get())); break; 26 | case IROpcodeClass::ASR: dead = CheckShifterOp(lunatic_cast(it->get())); break; 27 | case IROpcodeClass::ROR: dead = CheckShifterOp(lunatic_cast(it->get())); break; 28 | case IROpcodeClass::ADD: dead = CheckBinaryOp(lunatic_cast(it->get())); break; 29 | case IROpcodeClass::SUB: dead = CheckBinaryOp(lunatic_cast(it->get())); break; 30 | case IROpcodeClass::AND: dead = CheckBinaryOp(lunatic_cast(it->get())); break; 31 | case IROpcodeClass::BIC: dead = CheckBinaryOp(lunatic_cast(it->get())); break; 32 | case IROpcodeClass::EOR: dead = CheckBinaryOp(lunatic_cast(it->get())); break; 33 | case IROpcodeClass::ORR: dead = CheckBinaryOp(lunatic_cast(it->get())); break; 34 | case IROpcodeClass::MUL: dead = CheckMUL(lunatic_cast(it->get())); break; 35 | } 36 | 37 | if (dead) { 38 | it = code.erase(it); 39 | } else { 40 | ++it; 41 | } 42 | } 43 | } 44 | 45 | bool IRDeadCodeElisionPass::CheckMOV(IRMov* op) { 46 | if (IsValueUnused(op->result.Get()) && !op->update_host_flags) { 47 | return true; 48 | } 49 | 50 | // MOV var_a, var_b: var_a is a redundant variable. 51 | if (op->source.IsVariable() && 52 | !op->update_host_flags && 53 | Repoint(op->result.Get(), op->source.GetVar(), it, end) 54 | ) { 55 | return true; 56 | } 57 | 58 | return false; 59 | } 60 | 61 | template 62 | bool IRDeadCodeElisionPass::CheckShifterOp(OpcodeType* op) { 63 | if (IsValueUnused(op->result.Get()) && !op->update_host_flags) { 64 | return true; 65 | } 66 | 67 | // LSL #0 is a no-operation 68 | if constexpr(OpcodeType::klass == IROpcodeClass::LSL) { 69 | if (op->amount.IsConstant() && 70 | op->amount.GetConst().value == 0 && 71 | Repoint(op->result.Get(), op->operand.Get(), it, end) 72 | ) { 73 | return true; 74 | } 75 | } 76 | 77 | return false; 78 | } 79 | 80 | template 81 | bool IRDeadCodeElisionPass::CheckBinaryOp(OpcodeType* op) { 82 | if ((!op->result.HasValue() ||IsValueUnused(op->result.Unwrap())) && !op->update_host_flags) { 83 | return true; 84 | } 85 | 86 | // ADD #0 is a no-operation 87 | if constexpr(OpcodeType::klass == IROpcodeClass::ADD) { 88 | if (op->result.HasValue() && 89 | op->rhs.IsConstant() && 90 | op->rhs.GetConst().value == 0 && 91 | !op->update_host_flags && 92 | Repoint(op->result.Unwrap(), op->lhs.Get(), it, end) 93 | ) { 94 | return true; 95 | } 96 | } 97 | 98 | return false; 99 | } 100 | 101 | bool IRDeadCodeElisionPass::CheckMUL(IRMultiply* op) { 102 | if (IsValueUnused(op->result_lo.Get()) && 103 | (!op->result_hi.HasValue() || IsValueUnused(op->result_hi.Unwrap())) && 104 | !op->update_host_flags 105 | ) { 106 | return true; 107 | } 108 | 109 | return false; 110 | } 111 | 112 | bool IRDeadCodeElisionPass::IsValueUnused(IRVariable const& var) { 113 | auto local_it = it; 114 | 115 | ++local_it; 116 | 117 | while (local_it != end) { 118 | if (local_it->get()->Reads(var)) 119 | return false; 120 | ++local_it; 121 | } 122 | 123 | return true; 124 | } 125 | 126 | } // namespace lunatic::frontend 127 | } // namespace lunatic -------------------------------------------------------------------------------- /src/frontend/ir_opt/dead_code_elision.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "frontend/ir_opt/pass.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct IRDeadCodeElisionPass final : IRPass { 16 | void Run(IREmitter& emitter) override; 17 | 18 | private: 19 | bool CheckMOV(IRMov* op); 20 | 21 | template 22 | bool CheckShifterOp(OpcodeType* op); 23 | 24 | template 25 | bool CheckBinaryOp(OpcodeType* op); 26 | 27 | bool CheckMUL(IRMultiply* op); 28 | 29 | bool IsValueUnused(IRVariable const& var); 30 | 31 | IREmitter* emitter; 32 | IREmitter::InstructionList::iterator it; 33 | IREmitter::InstructionList::iterator end; 34 | }; 35 | 36 | } // namespace lunatic::frontend 37 | } // namespace lunatic 38 | -------------------------------------------------------------------------------- /src/frontend/ir_opt/dead_flag_elision.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | 9 | #include "frontend/ir_opt/dead_flag_elision.hpp" 10 | 11 | namespace lunatic { 12 | namespace frontend { 13 | 14 | void IRDeadFlagElisionPass::Run(IREmitter& emitter) { 15 | RemoveRedundantUpdateFlagsOpcodes(emitter); 16 | DisableRedundantFlagCalculations(emitter); 17 | } 18 | 19 | void IRDeadFlagElisionPass::RemoveRedundantUpdateFlagsOpcodes(IREmitter& emitter) { 20 | /** 21 | * TODO: implement the same logic for the Q-flag (update.q) 22 | */ 23 | 24 | bool unused_n = false; 25 | bool unused_z = false; 26 | bool unused_c = false; 27 | bool unused_v = false; 28 | 29 | auto& code = emitter.Code(); 30 | auto it = code.rbegin(); 31 | auto end = code.rend(); 32 | 33 | Optional current_cpsr_in{}; 34 | 35 | while (it != end) { 36 | switch (it->get()->GetClass()) { 37 | case IROpcodeClass::UpdateFlags: { 38 | auto op = lunatic_cast(it->get()); 39 | 40 | if (current_cpsr_in.HasValue() && op->Writes(current_cpsr_in.Unwrap())) { 41 | if (unused_n) op->flag_n = false; 42 | if (unused_z) op->flag_z = false; 43 | if (unused_c) op->flag_c = false; 44 | if (unused_v) op->flag_v = false; 45 | 46 | // Elide update.nzcv opcodes that now don't update any flag. 47 | if (!op->flag_n && !op->flag_z && !op->flag_c && !op->flag_v) { 48 | // TODO: do not use begin()? 49 | if (Repoint(op->result.Get(), op->input.Get(), code.begin(), code.end())) { 50 | current_cpsr_in = op->input.Get(); 51 | it = std::reverse_iterator{code.erase(std::next(it).base())}; 52 | end = code.rend(); 53 | continue; 54 | } 55 | } 56 | } 57 | 58 | if (op->flag_n) unused_n = true; 59 | if (op->flag_z) unused_z = true; 60 | if (op->flag_c) unused_c = true; 61 | if (op->flag_v) unused_v = true; 62 | 63 | current_cpsr_in = op->input.Get(); 64 | 65 | break; 66 | } 67 | default: { 68 | if (current_cpsr_in.HasValue() && it->get()->Reads(current_cpsr_in.Unwrap())) { 69 | /* We are reading the CPSR value between two NZCV updates. 70 | * This means that the first NZCV update must update all flags, 71 | * otherwise this opcode will read the wrong CPSR value. 72 | */ 73 | unused_n = false; 74 | unused_z = false; 75 | unused_c = false; 76 | unused_v = false; 77 | current_cpsr_in = {}; 78 | } 79 | break; 80 | } 81 | } 82 | 83 | ++it; 84 | } 85 | } 86 | 87 | void IRDeadFlagElisionPass::DisableRedundantFlagCalculations(IREmitter& emitter) { 88 | auto& code = emitter.Code(); 89 | auto it = code.rbegin(); 90 | auto end = code.rend(); 91 | 92 | bool used_n = false; 93 | bool used_z = false; 94 | bool used_c = false; 95 | bool used_v = false; 96 | 97 | while (it != end) { 98 | auto op_class = it->get()->GetClass(); 99 | 100 | switch (op_class) { 101 | case IROpcodeClass::UpdateFlags: { 102 | auto op = lunatic_cast(it->get()); 103 | 104 | if (op->flag_n) used_n = true; 105 | if (op->flag_z) used_z = true; 106 | if (op->flag_c) used_c = true; 107 | if (op->flag_v) used_v = true; 108 | break; 109 | } 110 | case IROpcodeClass::UpdateSticky: { 111 | used_v = true; // Q-flag is kept in the host V-flag 112 | break; 113 | } 114 | case IROpcodeClass::ClearCarry: 115 | case IROpcodeClass::SetCarry: { 116 | if (!used_c) { 117 | *it = std::make_unique(); 118 | } 119 | used_c = false; 120 | break; 121 | } 122 | case IROpcodeClass::LSL: 123 | case IROpcodeClass::LSR: 124 | case IROpcodeClass::ASR: 125 | case IROpcodeClass::ROR: { 126 | auto op = (IRLogicalShiftLeft*)it->get(); 127 | 128 | if (!used_c) { 129 | op->update_host_flags = false; 130 | } else if (op->update_host_flags && op->amount.IsConstant() && op_class != IROpcodeClass::LSL) { 131 | used_c = false; 132 | } 133 | 134 | // Special case: RRX #1 (encoded as ROR #0) reads the carry flag. 135 | if (op_class == IROpcodeClass::ROR && op->amount.IsConstant() && op->amount.GetConst().value == 0) { 136 | used_c = true; 137 | } 138 | break; 139 | } 140 | case IROpcodeClass::AND: 141 | case IROpcodeClass::BIC: 142 | case IROpcodeClass::EOR: 143 | case IROpcodeClass::ORR: { 144 | auto op = (IRBitwiseAND*)it->get(); 145 | 146 | if (!used_n && !used_z) { 147 | op->update_host_flags = false; 148 | } else if (op->update_host_flags) { 149 | used_n = false; 150 | used_z = false; 151 | } 152 | break; 153 | } 154 | case IROpcodeClass::ADD: 155 | case IROpcodeClass::SUB: 156 | case IROpcodeClass::RSB: { 157 | auto op = (IRAdd*)it->get(); 158 | 159 | if (!used_n && !used_z && !used_c && !used_v) { 160 | op->update_host_flags = false; 161 | } else if (op->update_host_flags) { 162 | used_n = false; 163 | used_z = false; 164 | used_c = false; 165 | used_v = false; 166 | } 167 | break; 168 | } 169 | case IROpcodeClass::ADC: 170 | case IROpcodeClass::SBC: 171 | case IROpcodeClass::RSC: { 172 | auto op = (IRAdc*)it->get(); 173 | 174 | if (!used_n && !used_z && !used_c && !used_v) { 175 | op->update_host_flags = false; 176 | } else if (op->update_host_flags) { 177 | used_n = false; 178 | used_z = false; 179 | used_v = false; 180 | } 181 | 182 | used_c = true; 183 | break; 184 | } 185 | case IROpcodeClass::MOV: 186 | case IROpcodeClass::MVN: { 187 | auto op = (IRMov*)it->get(); 188 | 189 | if (!used_n && !used_z) { 190 | op->update_host_flags = false; 191 | } else if (op->update_host_flags) { 192 | used_n = false; 193 | used_z = false; 194 | } 195 | break; 196 | } 197 | case IROpcodeClass::MUL: { 198 | auto op = lunatic_cast(it->get()); 199 | 200 | if (!used_n && !used_z) { 201 | op->update_host_flags = false; 202 | } else if (op->update_host_flags) { 203 | used_n = false; 204 | used_z = false; 205 | } 206 | break; 207 | } 208 | case IROpcodeClass::ADD64: { 209 | auto op = lunatic_cast(it->get()); 210 | 211 | if (!used_n && !used_z) { 212 | op->update_host_flags = false; 213 | } else if (op->update_host_flags) { 214 | used_n = false; 215 | used_z = false; 216 | } 217 | break; 218 | } 219 | case IROpcodeClass::QADD: 220 | case IROpcodeClass::QSUB: { 221 | used_v = true; 222 | break; 223 | } 224 | } 225 | 226 | ++it; 227 | } 228 | } 229 | 230 | } // namespace lunatic::frontend 231 | } // namespace lunatic 232 | -------------------------------------------------------------------------------- /src/frontend/ir_opt/dead_flag_elision.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "frontend/ir_opt/pass.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct IRDeadFlagElisionPass final : IRPass { 16 | void Run(IREmitter& emitter) override; 17 | 18 | private: 19 | void RemoveRedundantUpdateFlagsOpcodes(IREmitter& emitter); 20 | void DisableRedundantFlagCalculations(IREmitter& emitter); 21 | }; 22 | 23 | } // namespace lunatic::frontend 24 | } // namespace lunatic 25 | -------------------------------------------------------------------------------- /src/frontend/ir_opt/pass.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "frontend/ir/emitter.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | struct IRPass { 16 | virtual ~IRPass() = default; 17 | 18 | virtual void Run(IREmitter& emitter) = 0; 19 | 20 | protected: 21 | using InstructionList = IREmitter::InstructionList; 22 | 23 | bool Repoint( 24 | IRVariable const& var_old, 25 | IRVariable const& var_new, 26 | InstructionList::const_iterator begin, 27 | InstructionList::const_iterator end 28 | ) { 29 | if (var_old.data_type != var_new.data_type) { 30 | return false; 31 | } 32 | 33 | for (auto it = begin; it != end; ++it) { 34 | (*it)->Repoint(var_old, var_new); 35 | } 36 | 37 | return true; 38 | } 39 | }; 40 | 41 | } // namespace lunatic::frontend 42 | } // namespace lunatic 43 | -------------------------------------------------------------------------------- /src/frontend/state.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include 9 | 10 | #include "state.hpp" 11 | 12 | namespace lunatic { 13 | namespace frontend { 14 | 15 | State::State() { 16 | InitializeLookupTable(); 17 | Reset(); 18 | } 19 | 20 | void State::Reset() { 21 | common = {}; 22 | fiq = {}; 23 | sys = {}; 24 | irq = {}; 25 | svc = {}; 26 | abt = {}; 27 | und = {}; 28 | } 29 | 30 | auto State::GetPointerToGPR(Mode mode, GPR reg) -> u32* { 31 | auto id = static_cast(reg); 32 | if (id > 15) { 33 | throw std::runtime_error("GetPointerToGPR: requirement not met: 'id' must be <= 15."); 34 | } 35 | auto pointer = table[static_cast(mode)].gpr[id]; 36 | if (pointer == nullptr) { 37 | throw std::runtime_error( 38 | "GetPointerToGPR: requirement not met: 'mode' must be valid ARM processor mode."); 39 | } 40 | return pointer; 41 | } 42 | 43 | auto State::GetPointerToSPSR(Mode mode) -> StatusRegister* { 44 | auto pointer = table[static_cast(mode)].spsr; 45 | if (pointer == nullptr) { 46 | throw std::runtime_error( 47 | "GetPointerToSPSR: requirement not met: 'mode' must be valid ARM processor mode " 48 | "and may not be System or User mode."); 49 | } 50 | return pointer; 51 | } 52 | 53 | auto State::GetPointerToCPSR() -> StatusRegister* { 54 | return &common.cpsr; 55 | } 56 | 57 | auto State::GetOffsetToSPSR(Mode mode) -> uintptr { 58 | return uintptr(GetPointerToSPSR(mode)) - uintptr(this); 59 | } 60 | 61 | auto State::GetOffsetToCPSR() -> uintptr { 62 | return uintptr(GetPointerToCPSR()) - uintptr(this); 63 | } 64 | 65 | auto State::GetOffsetToGPR(Mode mode, GPR reg) -> uintptr { 66 | return uintptr(GetPointerToGPR(mode, reg)) - uintptr(this); 67 | } 68 | 69 | void State::InitializeLookupTable() { 70 | Mode modes[] = { 71 | Mode::User, 72 | Mode::FIQ, 73 | Mode::IRQ, 74 | Mode::Supervisor, 75 | Mode::Abort, 76 | Mode::Undefined, 77 | Mode::System 78 | }; 79 | 80 | for (auto mode : modes) { 81 | for (int i = 0; i <= 7; i++) 82 | table[static_cast(mode)].gpr[i] = &common.reg[i]; 83 | auto source = mode == Mode::FIQ ? fiq.reg : sys.reg; 84 | for (int i = 8; i <= 12; i++) 85 | table[static_cast(mode)].gpr[i] = &source[i - 8]; 86 | table[static_cast(mode)].gpr[15] = &common.r15; 87 | } 88 | 89 | for (int i = 0; i < 2; i++) { 90 | table[static_cast(Mode::User)].gpr[13 + i] = &sys.reg[5 + i]; 91 | table[static_cast(Mode::FIQ)].gpr[13 + i] = &fiq.reg[5 + i]; 92 | table[static_cast(Mode::IRQ)].gpr[13 + i] = &irq.reg[i]; 93 | table[static_cast(Mode::Supervisor)].gpr[13 + i] = &svc.reg[i]; 94 | table[static_cast(Mode::Abort)].gpr[13 + i] = &abt.reg[i]; 95 | table[static_cast(Mode::Undefined)].gpr[13 + i] = &und.reg[i]; 96 | table[static_cast(Mode::System)].gpr[13 + i] = &sys.reg[5 + i]; 97 | } 98 | 99 | table[static_cast(Mode::User)].spsr = nullptr; 100 | table[static_cast(Mode::FIQ)].spsr = &fiq.spsr; 101 | table[static_cast(Mode::IRQ)].spsr = &irq.spsr; 102 | table[static_cast(Mode::Supervisor)].spsr = &svc.spsr; 103 | table[static_cast(Mode::Abort)].spsr = &abt.spsr; 104 | table[static_cast(Mode::Undefined)].spsr = &und.spsr; 105 | table[static_cast(Mode::System)].spsr = nullptr; 106 | } 107 | 108 | } // namespace lunatic::frontend 109 | } // namespace lunatic 110 | -------------------------------------------------------------------------------- /src/frontend/state.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | namespace lunatic { 14 | namespace frontend { 15 | 16 | /// Stores the state of the emulated ARM core. 17 | struct State { 18 | State(); 19 | 20 | /// Reset the ARM core. 21 | void Reset(); 22 | 23 | /// \returns for a given processor mode a reference to a general-purpose register. 24 | auto GetGPR(Mode mode, GPR reg) -> u32& { return *GetPointerToGPR(mode, reg); } 25 | 26 | /// \returns reference to the current program status register (cpsr). 27 | auto GetCPSR() -> StatusRegister& { return common.cpsr; } 28 | 29 | /// \returns for a given processor mode the pointer to a general-purpose register. 30 | auto GetPointerToGPR(Mode mode, GPR reg) -> u32*; 31 | 32 | /// \returns for a given processor mode the pointer to the saved program status register (spsr). 33 | auto GetPointerToSPSR(Mode mode) -> StatusRegister*; 34 | 35 | /// \returns pointer to the current program status register (cpsr). 36 | auto GetPointerToCPSR() -> StatusRegister*; 37 | 38 | /// \returns for a given processor mode the offset to the saved program status register (spsr). 39 | auto GetOffsetToSPSR(Mode mode) -> uintptr; 40 | 41 | /// \returns the offset to the current program status register (cpsr). 42 | auto GetOffsetToCPSR() -> uintptr; 43 | 44 | /// \returns for a given processor mode the offset of a general-purpose register. 45 | auto GetOffsetToGPR(Mode mode, GPR reg) -> uintptr; 46 | 47 | private: 48 | void InitializeLookupTable(); 49 | 50 | /// Common registers r0 - r7, r15 and cpsr. 51 | /// These registers are visible in all ARM processor modes. 52 | struct { 53 | u32 reg[8] {0}; 54 | u32 r15 = 0x00000008; 55 | StatusRegister cpsr = {}; 56 | } common; 57 | 58 | /// Banked registers r8 - r14 and spsr for FIQ and User/System processor modes. 59 | /// User/System mode r8 - r12 are also used by all other modes except FIQ. 60 | struct { 61 | u32 reg[7] {0}; 62 | StatusRegister spsr = {}; 63 | } fiq, sys; 64 | 65 | /// Banked registers r13 - r14 and spsr for IRQ, supervisor, 66 | /// abort and undefined processor modes. 67 | struct { 68 | u32 reg[2] {0}; 69 | StatusRegister spsr = {}; 70 | } irq, svc, abt, und; 71 | 72 | /// Per ARM processor mode address/pointer lookup table for GPRs and SPSRs. 73 | struct LookupTable { 74 | u32* gpr[16] {nullptr}; 75 | StatusRegister* spsr {nullptr}; 76 | } table[0x20] = {}; 77 | }; 78 | 79 | } // namespace lunatic::frontend 80 | } // namespace lunatic 81 | 82 | namespace std { 83 | 84 | // TODO: move this somewhere more appropriate? 85 | inline auto to_string(lunatic::Mode value) -> std::string { 86 | using Mode = lunatic::Mode; 87 | 88 | switch (value) { 89 | case Mode::User: return "usr"; 90 | case Mode::FIQ: return "fiq"; 91 | case Mode::IRQ: return "irq"; 92 | case Mode::Supervisor: return "svc"; 93 | case Mode::Abort: return "abt"; 94 | case Mode::Undefined: return "und"; 95 | case Mode::System: return "sys"; 96 | } 97 | 98 | return "???"; 99 | } 100 | 101 | } // namespace std 102 | -------------------------------------------------------------------------------- /src/frontend/translator/handle/block_data_transfer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/translator/translator.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | auto Translator::Handle(ARMBlockDataTransfer const& opcode) -> Status { 14 | auto list = opcode.reg_list; 15 | auto reg_base = IRGuestReg{opcode.reg_base, mode}; 16 | bool base_is_first = false; 17 | bool base_is_last = false; 18 | 19 | u32 bytes = 0; 20 | 21 | if (list == 0) { 22 | bytes = 16 * sizeof(u32); 23 | if (!armv5te) { 24 | list = 1 << 15; 25 | base_is_last = false; 26 | base_is_first = false; 27 | } 28 | } else { 29 | // TODO: clean this up and document "base is not in rlist" caveat 30 | base_is_first = (list & ((1 << int(opcode.reg_base)) - 1)) == 0; 31 | base_is_last = (list >> int(opcode.reg_base)) == 1; 32 | 33 | // Calculate the number of bytes to transfer. 34 | for (int i = 0; i <= 15; i++) { 35 | if (bit::get_bit(list, i)) 36 | bytes += sizeof(u32); 37 | } 38 | } 39 | 40 | auto& base_lo = emitter->CreateVar(IRDataType::UInt32, "base_lo"); 41 | auto& base_hi = emitter->CreateVar(IRDataType::UInt32, "base_hi"); 42 | 43 | // Calculate the low and high addresses. 44 | if (opcode.add) { 45 | emitter->LoadGPR(reg_base, base_lo); 46 | emitter->ADD(base_hi, base_lo, IRConstant{bytes}, false); 47 | } else { 48 | emitter->LoadGPR(reg_base, base_hi); 49 | emitter->SUB(base_lo, base_hi, IRConstant{bytes}, false); 50 | } 51 | 52 | auto writeback = [&]() { 53 | // TODO: is there a cleaner solution without a conditional? 54 | if (opcode.add) { 55 | emitter->StoreGPR(reg_base, base_hi); 56 | } else { 57 | emitter->StoreGPR(reg_base, base_lo); 58 | } 59 | }; 60 | 61 | auto loading_pc = opcode.load && bit::get_bit(list, 15); 62 | 63 | if (!loading_pc) { 64 | EmitAdvancePC(); 65 | } 66 | 67 | auto forced_mode = (opcode.user_mode && !loading_pc) ? Mode::User : mode; 68 | auto address = &base_lo; 69 | 70 | bool early_writeback = opcode.writeback && !opcode.load && !armv5te && !base_is_first; 71 | 72 | // STM ARMv4: store new base unless it is the first register 73 | // STM ARMv5: always store old base. 74 | if (early_writeback) { 75 | writeback(); 76 | } 77 | 78 | // Load or store a set of registers from/to memory. 79 | for (int i = 0; i <= 15; i++) { 80 | if (!bit::get_bit(list, i)) 81 | continue; 82 | 83 | auto reg = static_cast(i); 84 | auto& data = emitter->CreateVar(IRDataType::UInt32, "data"); 85 | auto& address_next = emitter->CreateVar(IRDataType::UInt32, "address"); 86 | 87 | emitter->ADD(address_next, *address, IRConstant{sizeof(u32)}, false); 88 | 89 | if (opcode.pre_increment == opcode.add) { 90 | address = &address_next; 91 | } 92 | 93 | if (opcode.load) { 94 | emitter->LDR(Word, data, *address); 95 | emitter->StoreGPR(IRGuestReg{reg, forced_mode}, data); 96 | } else { 97 | emitter->LoadGPR(IRGuestReg{reg, forced_mode}, data); 98 | emitter->STR(Word, data, *address); 99 | } 100 | 101 | if (opcode.pre_increment != opcode.add) { 102 | address = &address_next; 103 | } 104 | } 105 | 106 | if (opcode.user_mode && loading_pc) { 107 | // TODO: base writeback happens in which mode? (this is unpredictable) 108 | // If writeback happens in the new mode, then this might be difficult 109 | // to emulate because we cannot know the value of SPSR at compile-time. 110 | EmitLoadSPSRToCPSR(); 111 | } 112 | 113 | if (opcode.writeback) { 114 | if (opcode.load) { 115 | if (armv5te) { 116 | // LDM ARMv5: writeback if base is the only register or not the last register. 117 | if (!base_is_last || list == (1 << int(opcode.reg_base))) 118 | writeback(); 119 | } else { 120 | // LDM ARMv4: writeback if base in not in the register list. 121 | if (!bit::get_bit(list, int(opcode.reg_base))) 122 | writeback(); 123 | } 124 | } else if (!early_writeback) { 125 | writeback(); 126 | } 127 | } 128 | 129 | // Flush the pipeline if we loaded R15. 130 | if (loading_pc) { 131 | if (opcode.user_mode) { 132 | EmitFlush(); 133 | } else if (armv5te) { 134 | // Branch with exchange 135 | auto& address = emitter->CreateVar(IRDataType::UInt32, "address"); 136 | emitter->LoadGPR(IRGuestReg{GPR::PC, mode}, address); 137 | EmitFlushExchange(address); 138 | } else { 139 | EmitFlushNoSwitch(); 140 | } 141 | 142 | return Status::BreakBasicBlock; 143 | } 144 | 145 | return Status::Continue; 146 | } 147 | 148 | } // namespace lunatic::frontend 149 | } // namespace lunatic 150 | -------------------------------------------------------------------------------- /src/frontend/translator/handle/branch_exchange.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/translator/translator.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | auto Translator::Handle(ARMBranchExchange const& opcode) -> Status { 14 | auto& address = emitter->CreateVar(IRDataType::UInt32, "address"); 15 | 16 | emitter->LoadGPR(IRGuestReg{opcode.reg, mode}, address); 17 | 18 | if (armv5te && opcode.link) { 19 | auto link_address = code_address + opcode_size; 20 | if (thumb_mode) { 21 | link_address |= 1; 22 | } 23 | emitter->StoreGPR(IRGuestReg{GPR::LR, mode}, IRConstant{link_address}); 24 | } 25 | 26 | EmitFlushExchange(address); 27 | 28 | return Status::BreakBasicBlock; 29 | } 30 | 31 | } // namespace lunatic::frontend 32 | } // namespace lunatic 33 | -------------------------------------------------------------------------------- /src/frontend/translator/handle/branch_relative.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/translator/translator.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | auto Translator::Handle(ARMBranchRelative const& opcode) -> Status { 14 | auto branch_address = code_address + opcode_size * 2 + opcode.offset; 15 | 16 | if (opcode.link) { 17 | // Note: thumb BL consists of two 16-bit opcodes. 18 | u32 link_address = code_address + sizeof(u32); 19 | if (thumb_mode) { 20 | link_address |= 1; 21 | } 22 | emitter->StoreGPR(IRGuestReg{GPR::LR, mode}, IRConstant{link_address}); 23 | } 24 | 25 | if (opcode.exchange) { 26 | auto& cpsr_in = emitter->CreateVar(IRDataType::UInt32, "cpsr_in"); 27 | auto& cpsr_out = emitter->CreateVar(IRDataType::UInt32, "cpsr_out"); 28 | 29 | emitter->LoadCPSR(cpsr_in); 30 | 31 | if (thumb_mode) { 32 | branch_address &= ~3; 33 | branch_address += sizeof(u32) * 2; 34 | emitter->BIC(cpsr_out, cpsr_in, IRConstant{32}, false); 35 | } else { 36 | branch_address += sizeof(u16) * 2; 37 | emitter->ORR(cpsr_out, cpsr_in, IRConstant{32}, false); 38 | } 39 | 40 | emitter->StoreCPSR(cpsr_out); 41 | } else { 42 | branch_address += opcode_size * 2; 43 | } 44 | 45 | emitter->StoreGPR(IRGuestReg{GPR::PC, mode}, IRConstant{branch_address}); 46 | 47 | if (!opcode.exchange && opcode.condition == Condition::AL) { 48 | code_address = branch_address - opcode_size * 3; 49 | basic_block->branch_target.key = {}; 50 | return Status::Continue; 51 | } else { 52 | if (opcode.exchange) { 53 | thumb_mode = !thumb_mode; 54 | } 55 | basic_block->branch_target.key = BasicBlock::Key{ 56 | branch_address, 57 | mode, 58 | thumb_mode 59 | }; 60 | basic_block->branch_target.condition = opcode.condition; 61 | } 62 | 63 | return Status::BreakBasicBlock; 64 | } 65 | 66 | } // namespace lunatic::frontend 67 | } // namespace lunatic 68 | -------------------------------------------------------------------------------- /src/frontend/translator/handle/coprocessor_register_transfer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/translator/translator.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | auto Translator::Handle(ARMCoprocessorRegisterTransfer const& opcode) -> Status { 14 | auto load = opcode.load; 15 | auto coprocessor_id = opcode.coprocessor_id; 16 | auto opcode1 = opcode.opcode1; 17 | auto cn = opcode.cn; 18 | auto cm = opcode.cm; 19 | auto opcode2 = opcode.opcode2; 20 | auto coprocessor = coprocessors[coprocessor_id]; 21 | 22 | // TODO: throw an undefined opcode exception. 23 | if (coprocessor == nullptr) { 24 | return Status::Unimplemented; 25 | } 26 | 27 | auto& data = emitter->CreateVar(IRDataType::UInt32, "data"); 28 | 29 | if (load) { 30 | emitter->MRC(data, coprocessor_id, opcode1, cn, cm, opcode2); 31 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst, mode}, data); 32 | } else { 33 | emitter->LoadGPR(IRGuestReg{opcode.reg_dst, mode}, data); 34 | emitter->MCR(data, coprocessor_id, opcode1, cn, cm, opcode2); 35 | } 36 | 37 | EmitAdvancePC(); 38 | 39 | if (!load && coprocessor->ShouldWriteBreakBasicBlock(opcode1, cn, cm, opcode2)) { 40 | basic_block->enable_fast_dispatch = false; 41 | return Status::BreakBasicBlock; 42 | } 43 | 44 | return Status::Continue; 45 | } 46 | 47 | } // namespace lunatic::frontend 48 | } // namespace lunatic 49 | -------------------------------------------------------------------------------- /src/frontend/translator/handle/count_leading_zeros.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/translator/translator.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | auto Translator::Handle(ARMCountLeadingZeros const& opcode) -> Status { 14 | auto& result = emitter->CreateVar(IRDataType::UInt32, "result"); 15 | auto& operand = emitter->CreateVar(IRDataType::UInt32, "operand"); 16 | 17 | emitter->LoadGPR(IRGuestReg{opcode.reg_src, mode}, operand); 18 | emitter->CLZ(result, operand); 19 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst, mode}, result); 20 | 21 | EmitAdvancePC(); 22 | return Status::Continue; 23 | } 24 | 25 | } // namespace lunatic::frontend 26 | } // namespace lunatic 27 | -------------------------------------------------------------------------------- /src/frontend/translator/handle/exception.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/translator/translator.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | auto Translator::Handle(ARMException const& opcode) -> Status { 14 | Mode new_mode; 15 | auto exception = opcode.exception; 16 | u32 branch_address = exception_base + static_cast(exception) + sizeof(u32) * 2; 17 | 18 | basic_block->uses_exception_base = true; 19 | 20 | switch (exception) { 21 | case Exception::Supervisor: 22 | new_mode = Mode::Supervisor; 23 | break; 24 | case Exception::Undefined: 25 | new_mode = Mode::Undefined; 26 | break; 27 | default: 28 | throw std::runtime_error(fmt::format("unhandled exception vector: 0x{:X}", static_cast(exception))); 29 | } 30 | 31 | auto& cpsr_old = emitter->CreateVar(IRDataType::UInt32, "cpsr_old"); 32 | 33 | // Save current PSR in the saved PSR. 34 | emitter->LoadCPSR(cpsr_old); 35 | emitter->StoreSPSR(cpsr_old, new_mode); 36 | 37 | // Enter supervisor mode and disable IRQs. 38 | auto& tmp = emitter->CreateVar(IRDataType::UInt32); 39 | auto& cpsr_new = emitter->CreateVar(IRDataType::UInt32, "cpsr_new"); 40 | emitter->AND(tmp, cpsr_old, IRConstant{~0x3FU}, false); 41 | emitter->ORR(cpsr_new, tmp, IRConstant{static_cast(new_mode) | 0x80}, false); 42 | emitter->StoreCPSR(cpsr_new); 43 | 44 | // Save next PC in LR 45 | emitter->StoreGPR(IRGuestReg{GPR::LR, new_mode}, IRConstant{code_address + opcode_size}); 46 | 47 | // Set PC to the exception vector. 48 | emitter->StoreGPR(IRGuestReg{GPR::PC, new_mode}, IRConstant{branch_address}); 49 | 50 | if (opcode.condition == Condition::AL && !thumb_mode) { 51 | code_address = branch_address - 3 * opcode_size; 52 | mode = new_mode; 53 | basic_block->branch_target.key = {}; 54 | return Status::Continue; 55 | } else { 56 | basic_block->branch_target.key = BasicBlock::Key{ 57 | branch_address, 58 | new_mode, 59 | false 60 | }; 61 | basic_block->branch_target.condition = opcode.condition; 62 | } 63 | 64 | return Status::BreakBasicBlock; 65 | } 66 | 67 | } // namespace lunatic::frontend 68 | } // namespace lunatic 69 | -------------------------------------------------------------------------------- /src/frontend/translator/handle/halfword_signed_transfer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/translator/translator.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | auto Translator::Handle(ARMHalfwordSignedTransfer const& opcode) -> Status { 14 | auto offset = IRAnyRef{}; 15 | bool should_writeback = !opcode.pre_increment || opcode.writeback; 16 | bool should_flush_pipeline = opcode.load && opcode.reg_dst == GPR::PC; 17 | 18 | if (opcode.immediate) { 19 | offset = IRConstant{opcode.offset_imm}; 20 | } else { 21 | auto& offset_reg = emitter->CreateVar(IRDataType::UInt32, "base_offset"); 22 | emitter->LoadGPR(IRGuestReg{opcode.offset_reg, mode}, offset_reg); 23 | offset = offset_reg; 24 | } 25 | 26 | auto& base_old = emitter->CreateVar(IRDataType::UInt32, "base_old"); 27 | auto& base_new = emitter->CreateVar(IRDataType::UInt32, "base_new"); 28 | 29 | emitter->LoadGPR(IRGuestReg{opcode.reg_base, mode}, base_old); 30 | 31 | if (opcode.add) { 32 | emitter->ADD(base_new, base_old, offset, false); 33 | } else { 34 | emitter->SUB(base_new, base_old, offset, false); 35 | } 36 | 37 | auto& address_a = opcode.pre_increment ? base_new : base_old; 38 | auto& address_b = emitter->CreateVar(IRDataType::UInt32); 39 | auto& data_a = emitter->CreateVar(IRDataType::UInt32, "data_a"); 40 | auto& data_b = emitter->CreateVar(IRDataType::UInt32, "data_b"); 41 | 42 | auto writeback = [&]() { 43 | if (should_writeback) { 44 | emitter->StoreGPR(IRGuestReg{opcode.reg_base, mode}, base_new); 45 | } 46 | }; 47 | 48 | EmitAdvancePC(); 49 | 50 | switch (opcode.opcode) { 51 | case 1: { 52 | if (opcode.load) { 53 | writeback(); 54 | if (armv5te) { 55 | emitter->LDR(Half, data_a, address_a); 56 | } else { 57 | emitter->LDR(Half | Rotate, data_a, address_a); 58 | } 59 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst, mode}, data_a); 60 | } else { 61 | emitter->LoadGPR(IRGuestReg{opcode.reg_dst, mode}, data_a); 62 | emitter->STR(Half, data_a, address_a); 63 | writeback(); 64 | } 65 | break; 66 | } 67 | case 2: { 68 | if (opcode.load) { 69 | writeback(); 70 | emitter->LDR(Byte | Signed, data_a, address_a); 71 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst, mode}, data_a); 72 | } else if (armv5te) { 73 | auto reg_dst_a = opcode.reg_dst; 74 | auto reg_dst_b = static_cast(static_cast(reg_dst_a) + 1); 75 | 76 | // LDRD with odd-numbered destination register is undefined. 77 | if ((static_cast(reg_dst_a) & 1) == 1) { 78 | return Status::Unimplemented; 79 | } 80 | 81 | emitter->ADD(address_b, address_a, IRConstant{sizeof(u32)}, false); 82 | emitter->LDR(Word, data_a, address_a); 83 | emitter->LDR(Word, data_b, address_b); 84 | emitter->StoreGPR(IRGuestReg{reg_dst_a, mode}, data_a); 85 | writeback(); 86 | emitter->StoreGPR(IRGuestReg{reg_dst_b, mode}, data_b); 87 | 88 | if (reg_dst_b == GPR::PC) { 89 | should_flush_pipeline = true; 90 | } 91 | } else { 92 | writeback(); 93 | } 94 | break; 95 | } 96 | case 3: { 97 | if (opcode.load) { 98 | writeback(); 99 | if (armv5te) { 100 | emitter->LDR(Half | Signed, data_a, address_a); 101 | } else { 102 | emitter->LDR(Half | Signed | ARMv4T, data_a, address_a); 103 | } 104 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst, mode}, data_a); 105 | } else { 106 | if (armv5te) { 107 | auto reg_dst_a = opcode.reg_dst; 108 | auto reg_dst_b = static_cast(static_cast(reg_dst_a) + 1); 109 | 110 | // STRD with odd-numbered destination register is undefined. 111 | if ((static_cast(reg_dst_a) & 1) == 1) { 112 | return Status::Unimplemented; 113 | } 114 | 115 | emitter->LoadGPR(IRGuestReg{reg_dst_a, mode}, data_a); 116 | emitter->LoadGPR(IRGuestReg{reg_dst_b, mode}, data_b); 117 | emitter->ADD(address_b, address_a, IRConstant{sizeof(u32)}, false); 118 | emitter->STR(Word, data_a, address_a); 119 | emitter->STR(Word, data_b, address_b); 120 | } 121 | 122 | writeback(); 123 | } 124 | break; 125 | } 126 | default: { 127 | // Unreachable? 128 | return Status::Unimplemented; 129 | } 130 | } 131 | 132 | if (should_flush_pipeline) { 133 | if (armv5te) { 134 | // TODO: this probably can be optimized. 135 | auto& address = emitter->CreateVar(IRDataType::UInt32, "address"); 136 | emitter->LoadGPR(IRGuestReg{GPR::PC, mode}, address); 137 | EmitFlushExchange(address); 138 | } else { 139 | EmitFlushNoSwitch(); 140 | } 141 | return Status::BreakBasicBlock; 142 | } 143 | 144 | return Status::Continue; 145 | } 146 | 147 | } // namespace lunatic::frontend 148 | } // namespace lunatic 149 | -------------------------------------------------------------------------------- /src/frontend/translator/handle/multiply.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/translator/translator.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | auto Translator::Handle(ARMMultiply const& opcode) -> Status { 14 | auto& lhs = emitter->CreateVar(IRDataType::UInt32, "lhs"); 15 | auto& rhs = emitter->CreateVar(IRDataType::UInt32, "rhs"); 16 | auto& result = emitter->CreateVar(IRDataType::UInt32, "result"); 17 | 18 | emitter->LoadGPR(IRGuestReg{opcode.reg_op1, mode}, lhs); 19 | emitter->LoadGPR(IRGuestReg{opcode.reg_op2, mode}, rhs); 20 | 21 | if (opcode.accumulate) { 22 | auto& op3 = emitter->CreateVar(IRDataType::UInt32, "op3"); 23 | auto& result_acc = emitter->CreateVar(IRDataType::UInt32, "result_acc"); 24 | 25 | emitter->LoadGPR(IRGuestReg{opcode.reg_op3, mode}, op3); 26 | emitter->MUL({}, result, lhs, rhs, false); 27 | emitter->ADD(result_acc, result, op3, opcode.set_flags); 28 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst, mode}, result_acc); 29 | } else { 30 | emitter->MUL({}, result, lhs, rhs, opcode.set_flags); 31 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst, mode}, result); 32 | } 33 | 34 | EmitAdvancePC(); 35 | 36 | if (opcode.set_flags) { 37 | EmitUpdateNZ(); 38 | return Status::BreakMicroBlock; 39 | } 40 | 41 | return Status::Continue; 42 | } 43 | 44 | } // namespace lunatic::frontend 45 | } // namespace lunatic 46 | -------------------------------------------------------------------------------- /src/frontend/translator/handle/multiply_long.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/translator/translator.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | auto Translator::Handle(ARMMultiplyLong const& opcode) -> Status { 14 | 15 | auto data_type = opcode.sign_extend ? IRDataType::SInt32 : IRDataType::UInt32; 16 | 17 | auto& lhs = emitter->CreateVar(data_type, "lhs"); 18 | auto& rhs = emitter->CreateVar(data_type, "rhs"); 19 | auto& result_hi = emitter->CreateVar(IRDataType::UInt32, "result_hi"); 20 | auto& result_lo = emitter->CreateVar(IRDataType::UInt32, "result_lo"); 21 | 22 | emitter->LoadGPR(IRGuestReg{opcode.reg_op1, mode}, lhs); 23 | emitter->LoadGPR(IRGuestReg{opcode.reg_op2, mode}, rhs); 24 | 25 | emitter->MUL(result_hi, result_lo, lhs, rhs, opcode.set_flags && !opcode.accumulate); 26 | 27 | if (opcode.accumulate) { 28 | auto& dst_hi = emitter->CreateVar(IRDataType::UInt32, "dst_hi"); 29 | auto& dst_lo = emitter->CreateVar(IRDataType::UInt32, "dst_lo"); 30 | auto& result_acc_hi = emitter->CreateVar(IRDataType::UInt32, "result_acc_hi"); 31 | auto& result_acc_lo = emitter->CreateVar(IRDataType::UInt32, "result_acc_lo"); 32 | 33 | emitter->LoadGPR(IRGuestReg{opcode.reg_dst_hi, mode}, dst_hi); 34 | emitter->LoadGPR(IRGuestReg{opcode.reg_dst_lo, mode}, dst_lo); 35 | 36 | emitter->ADD64( 37 | result_acc_hi, 38 | result_acc_lo, 39 | result_hi, 40 | result_lo, 41 | dst_hi, 42 | dst_lo, 43 | opcode.set_flags); 44 | 45 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst_hi, mode}, result_acc_hi); 46 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst_lo, mode}, result_acc_lo); 47 | } else { 48 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst_hi, mode}, result_hi); 49 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst_lo, mode}, result_lo); 50 | } 51 | 52 | EmitAdvancePC(); 53 | 54 | if (opcode.set_flags) { 55 | EmitUpdateNZ(); 56 | return Status::BreakMicroBlock; 57 | } 58 | 59 | return Status::Continue; 60 | } 61 | 62 | } // namespace lunatic::frontend 63 | } // namespace lunatic 64 | -------------------------------------------------------------------------------- /src/frontend/translator/handle/saturating_add_sub.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/translator/translator.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | auto Translator::Handle(ARMSaturatingAddSub const& opcode) -> Status { 14 | auto& result = emitter->CreateVar(IRDataType::UInt32); 15 | auto& lhs = emitter->CreateVar(IRDataType::UInt32); 16 | auto& rhs = emitter->CreateVar(IRDataType::UInt32); 17 | 18 | emitter->LoadGPR(IRGuestReg{opcode.reg_lhs, mode}, lhs); 19 | emitter->LoadGPR(IRGuestReg{opcode.reg_rhs, mode}, rhs); 20 | 21 | auto rhs_value = IRAnyRef{}; 22 | 23 | if (opcode.double_rhs) { 24 | // TODO: this likely can be optimized since both operands are equal. 25 | rhs_value = emitter->CreateVar(IRDataType::UInt32); 26 | emitter->QADD(rhs_value.GetVar(), rhs, rhs); 27 | EmitUpdateQ(); 28 | } else { 29 | rhs_value = rhs; 30 | } 31 | 32 | if (opcode.subtract) { 33 | emitter->QSUB(result, lhs, rhs_value.GetVar()); 34 | } else { 35 | emitter->QADD(result, lhs, rhs_value.GetVar()); 36 | } 37 | 38 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst, mode}, result); 39 | 40 | EmitUpdateQ(); 41 | EmitAdvancePC(); 42 | return Status::BreakMicroBlock; 43 | } 44 | 45 | } // namespace lunatic::frontend 46 | } // namespace lunatic 47 | -------------------------------------------------------------------------------- /src/frontend/translator/handle/signed_halfword_multiply.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/translator/translator.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | auto Translator::Handle(ARMSignedHalfwordMultiply const& opcode) -> Status { 14 | auto& result = emitter->CreateVar(IRDataType::SInt32, "result"); 15 | auto& lhs = emitter->CreateVar(IRDataType::SInt32, "lhs"); 16 | auto& rhs = emitter->CreateVar(IRDataType::SInt32, "rhs"); 17 | auto& lhs_reg = emitter->CreateVar(IRDataType::SInt32, "lhs_reg"); 18 | auto& rhs_reg = emitter->CreateVar(IRDataType::SInt32, "rhs_reg"); 19 | 20 | emitter->LoadGPR(IRGuestReg{opcode.reg_lhs, mode}, lhs_reg); 21 | emitter->LoadGPR(IRGuestReg{opcode.reg_rhs, mode}, rhs_reg); 22 | 23 | if (opcode.x) { 24 | emitter->ASR(lhs, lhs_reg, IRConstant{16}, false); 25 | } else { 26 | auto& tmp = emitter->CreateVar(IRDataType::SInt32); 27 | emitter->LSL(tmp, lhs_reg, IRConstant{16}, false); 28 | emitter->ASR(lhs, tmp, IRConstant{16}, false); 29 | } 30 | 31 | if (opcode.y) { 32 | emitter->ASR(rhs, rhs_reg, IRConstant{16}, false); 33 | } else { 34 | auto& tmp = emitter->CreateVar(IRDataType::SInt32); 35 | emitter->LSL(tmp, rhs_reg, IRConstant{16}, false); 36 | emitter->ASR(rhs, tmp, IRConstant{16}, false); 37 | } 38 | 39 | emitter->MUL({}, result, lhs, rhs, false); 40 | 41 | if (opcode.accumulate) { 42 | auto& op3 = emitter->CreateVar(IRDataType::SInt32, "op3"); 43 | auto& result_acc = emitter->CreateVar(IRDataType::SInt32, "result_acc"); 44 | 45 | emitter->LoadGPR(IRGuestReg{opcode.reg_op3, mode}, op3); 46 | emitter->ADD(result_acc, result, op3, true); 47 | EmitUpdateQ(); 48 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst, mode}, result_acc); 49 | 50 | EmitAdvancePC(); 51 | return Status::BreakBasicBlock; 52 | } else { 53 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst, mode}, result); 54 | EmitAdvancePC(); 55 | return Status::Continue; 56 | } 57 | } 58 | 59 | auto Translator::Handle(ARMSignedWordHalfwordMultiply const& opcode) -> Status { 60 | auto& result_mul = emitter->CreateVar(IRDataType::SInt32, "result_mul"); 61 | auto& result_asr = emitter->CreateVar(IRDataType::SInt32, "result_asr"); 62 | auto& lhs = emitter->CreateVar(IRDataType::SInt32, "lhs"); 63 | auto& rhs = emitter->CreateVar(IRDataType::SInt32, "rhs"); 64 | auto& rhs_reg = emitter->CreateVar(IRDataType::SInt32, "rhs_reg"); 65 | 66 | emitter->LoadGPR(IRGuestReg{opcode.reg_lhs, mode}, lhs); 67 | emitter->LoadGPR(IRGuestReg{opcode.reg_rhs, mode}, rhs_reg); 68 | 69 | if (opcode.y) { 70 | emitter->ASR(rhs, rhs_reg, IRConstant{16}, false); 71 | } else { 72 | auto& tmp = emitter->CreateVar(IRDataType::SInt32); 73 | emitter->LSL(tmp, rhs_reg, IRConstant{16}, false); 74 | emitter->ASR(rhs, tmp, IRConstant{16}, false); 75 | } 76 | 77 | emitter->MUL({}, result_mul, lhs, rhs, false); 78 | emitter->ASR(result_asr, result_mul, IRConstant{16}, false); 79 | 80 | if (opcode.accumulate) { 81 | auto& op3 = emitter->CreateVar(IRDataType::SInt32, "op3"); 82 | auto& result_acc = emitter->CreateVar(IRDataType::SInt32, "result_acc"); 83 | 84 | emitter->LoadGPR(IRGuestReg{opcode.reg_op3, mode}, op3); 85 | emitter->ADD(result_acc, result_asr, op3, true); 86 | EmitUpdateQ(); 87 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst, mode}, result_acc); 88 | 89 | EmitAdvancePC(); 90 | return Status::BreakBasicBlock; 91 | } else { 92 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst, mode}, result_asr); 93 | EmitAdvancePC(); 94 | return Status::Continue; 95 | } 96 | } 97 | 98 | auto Translator::Handle(ARMSignedHalfwordMultiplyAccumulateLong const& opcode) -> Status { 99 | auto& result_hi = emitter->CreateVar(IRDataType::SInt32, "result_hi"); 100 | auto& result_lo = emitter->CreateVar(IRDataType::SInt32, "result_lo"); 101 | auto& result_acc_hi = emitter->CreateVar(IRDataType::SInt32, "result_acc_hi"); 102 | auto& result_acc_lo = emitter->CreateVar(IRDataType::SInt32, "result_acc_lo"); 103 | auto& lhs = emitter->CreateVar(IRDataType::SInt32, "lhs"); 104 | auto& rhs = emitter->CreateVar(IRDataType::SInt32, "rhs"); 105 | auto& lhs_reg = emitter->CreateVar(IRDataType::SInt32, "lhs_reg"); 106 | auto& rhs_reg = emitter->CreateVar(IRDataType::SInt32, "rhs_reg"); 107 | auto& dst_hi = emitter->CreateVar(IRDataType::SInt32, "dst_hi"); 108 | auto& dst_lo = emitter->CreateVar(IRDataType::SInt32, "dst_lo"); 109 | 110 | emitter->LoadGPR(IRGuestReg{opcode.reg_lhs, mode}, lhs_reg); 111 | emitter->LoadGPR(IRGuestReg{opcode.reg_rhs, mode}, rhs_reg); 112 | emitter->LoadGPR(IRGuestReg{opcode.reg_dst_hi, mode}, dst_hi); 113 | emitter->LoadGPR(IRGuestReg{opcode.reg_dst_lo, mode}, dst_lo); 114 | 115 | if (opcode.x) { 116 | emitter->ASR(lhs, lhs_reg, IRConstant{16}, false); 117 | } else { 118 | auto& tmp = emitter->CreateVar(IRDataType::SInt32); 119 | emitter->LSL(tmp, lhs_reg, IRConstant{16}, false); 120 | emitter->ASR(lhs, tmp, IRConstant{16}, false); 121 | } 122 | 123 | if (opcode.y) { 124 | emitter->ASR(rhs, rhs_reg, IRConstant{16}, false); 125 | } else { 126 | auto& tmp = emitter->CreateVar(IRDataType::SInt32); 127 | emitter->LSL(tmp, rhs_reg, IRConstant{16}, false); 128 | emitter->ASR(rhs, tmp, IRConstant{16}, false); 129 | } 130 | 131 | emitter->MUL(result_hi, result_lo, lhs, rhs, false); 132 | 133 | emitter->ADD64( 134 | result_acc_hi, 135 | result_acc_lo, 136 | dst_hi, 137 | dst_lo, 138 | result_hi, 139 | result_lo, 140 | false 141 | ); 142 | 143 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst_hi, mode}, result_acc_hi); 144 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst_lo, mode}, result_acc_lo); 145 | 146 | EmitAdvancePC(); 147 | return Status::Continue; 148 | } 149 | 150 | } // namespace lunatic::frontend 151 | } // namespace lunatic 152 | -------------------------------------------------------------------------------- /src/frontend/translator/handle/single_data_swap.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/translator/translator.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | auto Translator::Handle(ARMSingleDataSwap const& opcode) -> Status { 14 | if (opcode.reg_dst == GPR::PC) { 15 | return Status::Unimplemented; 16 | } 17 | 18 | auto& tmp = emitter->CreateVar(IRDataType::UInt32, "tmp"); 19 | auto& address = emitter->CreateVar(IRDataType::UInt32, "address"); 20 | auto& source = emitter->CreateVar(IRDataType::UInt32, "source"); 21 | 22 | emitter->LoadGPR(IRGuestReg{opcode.reg_base, mode}, address); 23 | emitter->LoadGPR(IRGuestReg{opcode.reg_src, mode}, source); 24 | 25 | if (opcode.byte) { 26 | emitter->LDR(Byte, tmp, address); 27 | emitter->STR(Byte, source, address); 28 | } else { 29 | emitter->LDR(Word | Rotate, tmp, address); 30 | emitter->STR(Word, source, address); 31 | } 32 | 33 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst, mode}, tmp); 34 | EmitAdvancePC(); 35 | return Status::Continue; 36 | } 37 | 38 | } // namespace lunatic::frontend 39 | } // namespace lunatic 40 | -------------------------------------------------------------------------------- /src/frontend/translator/handle/single_data_transfer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/translator/translator.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | /* 14 | * Note: 15 | * Since lunatic does not distinguish system and user memory accesses, 16 | * LDRT and STRT are treated like normal LDR/STR for now. 17 | */ 18 | 19 | auto Translator::Handle(ARMSingleDataTransfer const& opcode) -> Status { 20 | auto offset = IRAnyRef{}; 21 | 22 | if (opcode.immediate) { 23 | offset = IRConstant{opcode.offset_imm}; 24 | } else { 25 | auto& offset_reg = emitter->CreateVar(IRDataType::UInt32, "base_offset_reg"); 26 | auto& offset_var = emitter->CreateVar(IRDataType::UInt32, "base_offset_shifted"); 27 | 28 | emitter->LoadGPR(IRGuestReg{opcode.offset_reg.reg, mode}, offset_reg); 29 | 30 | // TODO: write a helper method to make this pattern less verbose. 31 | // Or alternatively pass the shift type to the emitter directly? 32 | switch (opcode.offset_reg.shift) { 33 | case Shift::LSL: { 34 | emitter->LSL(offset_var, offset_reg, IRConstant{opcode.offset_reg.amount}, false); 35 | break; 36 | } 37 | case Shift::LSR: { 38 | emitter->LSR(offset_var, offset_reg, IRConstant{opcode.offset_reg.amount}, false); 39 | break; 40 | } 41 | case Shift::ASR: { 42 | emitter->ASR(offset_var, offset_reg, IRConstant{opcode.offset_reg.amount}, false); 43 | break; 44 | } 45 | case Shift::ROR: { 46 | emitter->ROR(offset_var, offset_reg, IRConstant{opcode.offset_reg.amount}, false); 47 | break; 48 | } 49 | } 50 | 51 | offset = offset_var; 52 | } 53 | 54 | auto& base_old = emitter->CreateVar(IRDataType::UInt32, "base_old"); 55 | auto& base_new = emitter->CreateVar(IRDataType::UInt32, "base_new"); 56 | 57 | if (opcode.reg_base == GPR::PC) { 58 | /* This handles an edge-case in PC-relative loads in Thumb-mode. 59 | * The value of PC will be word-aligned before forming the final address, 60 | * so that no rotated read will happen. 61 | */ 62 | emitter->MOV(base_old, IRConstant{(code_address & ~3) + opcode_size * 2}, false); 63 | } else { 64 | emitter->LoadGPR(IRGuestReg{opcode.reg_base, mode}, base_old); 65 | } 66 | 67 | if (opcode.add) { 68 | emitter->ADD(base_new, base_old, offset, false); 69 | } else { 70 | emitter->SUB(base_new, base_old, offset, false); 71 | } 72 | 73 | auto& address = opcode.pre_increment ? base_new : base_old; 74 | 75 | EmitAdvancePC(); 76 | 77 | auto writeback = [&]() { 78 | if (!opcode.pre_increment || opcode.writeback) { 79 | emitter->StoreGPR(IRGuestReg{opcode.reg_base, mode}, base_new); 80 | } 81 | }; 82 | 83 | // TODO: maybe it's cleaner to decode opcode into an enum upfront. 84 | if (opcode.load) { 85 | auto& data = emitter->CreateVar(IRDataType::UInt32, "data"); 86 | 87 | writeback(); 88 | 89 | if (opcode.byte) { 90 | emitter->LDR(Byte, data, address); 91 | } else { 92 | emitter->LDR(Word | Rotate, data, address); 93 | } 94 | 95 | emitter->StoreGPR(IRGuestReg{opcode.reg_dst, mode}, data); 96 | } else { 97 | auto& data = emitter->CreateVar(IRDataType::UInt32, "data"); 98 | 99 | emitter->LoadGPR(IRGuestReg{opcode.reg_dst, mode}, data); 100 | 101 | if (opcode.byte) { 102 | emitter->STR(Byte, data, address); 103 | } else { 104 | emitter->STR(Word, data, address); 105 | } 106 | 107 | writeback(); 108 | } 109 | 110 | if (opcode.load && opcode.reg_dst == GPR::PC) { 111 | if (armv5te) { 112 | // Branch with exchange 113 | auto& address = emitter->CreateVar(IRDataType::UInt32, "address"); 114 | emitter->LoadGPR(IRGuestReg{GPR::PC, mode}, address); 115 | EmitFlushExchange(address); 116 | } else { 117 | EmitFlushNoSwitch(); 118 | } 119 | return Status::BreakBasicBlock; 120 | } 121 | 122 | return Status::Continue; 123 | } 124 | 125 | } // namespace lunatic::frontend 126 | } // namespace lunatic 127 | -------------------------------------------------------------------------------- /src/frontend/translator/handle/status_transfer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/translator/translator.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | auto Translator::Handle(ARMMoveStatusRegister const& opcode) -> Status { 14 | u32 mask = 0; 15 | 16 | if (opcode.fsxc & 1) mask |= 0x000000FF; 17 | if (opcode.fsxc & 2) mask |= 0x0000FF00; 18 | if (opcode.fsxc & 4) mask |= 0x00FF0000; 19 | if (opcode.fsxc & 8) mask |= 0xFF000000; 20 | 21 | auto& psr = emitter->CreateVar(IRDataType::UInt32, "psr"); 22 | auto& psr_masked = emitter->CreateVar(IRDataType::UInt32, "psr_masked"); 23 | auto& psr_result = emitter->CreateVar(IRDataType::UInt32, "psr_result"); 24 | 25 | if (opcode.spsr) { 26 | emitter->LoadSPSR(psr, mode); 27 | } else { 28 | emitter->LoadCPSR(psr); 29 | } 30 | 31 | emitter->AND(psr_masked, psr, IRConstant{~mask}, false); 32 | 33 | if (opcode.immediate) { 34 | emitter->ORR(psr_result, psr_masked, IRConstant{opcode.imm & mask}, false); 35 | } else { 36 | auto& reg = emitter->CreateVar(IRDataType::UInt32, "reg"); 37 | auto& reg_masked = emitter->CreateVar(IRDataType::UInt32, "reg_masked"); 38 | 39 | emitter->LoadGPR(IRGuestReg{opcode.reg, mode}, reg); 40 | emitter->AND(reg_masked, reg, IRConstant{mask}, false); 41 | emitter->ORR(psr_result, psr_masked, reg_masked, false); 42 | } 43 | 44 | EmitAdvancePC(); 45 | 46 | if (opcode.spsr) { 47 | emitter->StoreSPSR(psr_result, mode); 48 | return Status::Continue; 49 | } else { 50 | // TODO: if only the flags change then we should only break the micro block. 51 | emitter->StoreCPSR(psr_result); 52 | return Status::BreakBasicBlock; 53 | } 54 | } 55 | 56 | auto Translator::Handle(ARMMoveRegisterStatus const& opcode) -> Status { 57 | auto& psr = emitter->CreateVar(IRDataType::UInt32, "psr"); 58 | 59 | if (opcode.spsr) { 60 | emitter->LoadSPSR(psr, mode); 61 | } else { 62 | emitter->LoadCPSR(psr); 63 | } 64 | 65 | emitter->StoreGPR(IRGuestReg{opcode.reg, mode}, psr); 66 | EmitAdvancePC(); 67 | 68 | return Status::Continue; 69 | } 70 | 71 | } // namespace lunatic::frontend 72 | } // namespace lunatic 73 | -------------------------------------------------------------------------------- /src/frontend/translator/handle/thumb_bl_suffix.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #include "frontend/translator/translator.hpp" 9 | 10 | namespace lunatic { 11 | namespace frontend { 12 | 13 | auto Translator::Handle(ThumbBranchLinkSuffix const& opcode) -> Status { 14 | auto& lr = emitter->CreateVar(IRDataType::UInt32, "lr"); 15 | auto& pc1 = emitter->CreateVar(IRDataType::UInt32, "pc"); 16 | auto& pc2 = emitter->CreateVar(IRDataType::UInt32, "pc"); 17 | auto& pc3 = emitter->CreateVar(IRDataType::UInt32, "pc"); 18 | 19 | emitter->LoadGPR(IRGuestReg{GPR::LR, mode}, lr); 20 | emitter->ADD(pc1, lr, IRConstant{opcode.offset}, false); 21 | emitter->StoreGPR(IRGuestReg{GPR::LR, mode}, IRConstant{u32((code_address + sizeof(u16)) | 1)}); 22 | 23 | if (armv5te && opcode.exchange) { 24 | auto& cpsr_in = emitter->CreateVar(IRDataType::UInt32, "cpsr_in"); 25 | auto& cpsr_out = emitter->CreateVar(IRDataType::UInt32, "cpsr_out"); 26 | 27 | emitter->LoadCPSR(cpsr_in); 28 | emitter->AND(cpsr_out, cpsr_in, IRConstant{~32U}, false); 29 | emitter->StoreCPSR(cpsr_out); 30 | 31 | emitter->AND(pc2, pc1, IRConstant{~3U}, false); 32 | emitter->ADD(pc3, pc2, IRConstant{sizeof(u32) * 2}, false); 33 | emitter->StoreGPR(IRGuestReg{GPR::PC, mode}, pc3); 34 | } else { 35 | emitter->AND(pc2, pc1, IRConstant{~1U}, false); 36 | emitter->ADD(pc3, pc2, IRConstant{sizeof(u16) * 2}, false); 37 | emitter->StoreGPR(IRGuestReg{GPR::PC, mode}, pc3); 38 | } 39 | 40 | return Status::BreakBasicBlock; 41 | } 42 | 43 | } // namespace lunatic::frontend 44 | } // namespace lunatic 45 | -------------------------------------------------------------------------------- /src/frontend/translator/translator.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE file. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #include "frontend/decode/arm.hpp" 13 | #include "frontend/decode/thumb.hpp" 14 | #include "frontend/basic_block.hpp" 15 | 16 | namespace lunatic { 17 | namespace frontend { 18 | 19 | // TODO: can we move this into Translator despite the templated base class? 20 | enum class Status { 21 | Continue, 22 | BreakBasicBlock, 23 | BreakMicroBlock, 24 | Unimplemented 25 | }; 26 | 27 | struct Translator final : ARMDecodeClient { 28 | Translator(CPU::Descriptor const& descriptor); 29 | 30 | void SetExceptionBase(u32 new_exception_base) { 31 | exception_base = new_exception_base; 32 | } 33 | 34 | void Translate(BasicBlock& basic_block); 35 | 36 | auto Handle(ARMDataProcessing const& opcode) -> Status override; 37 | auto Handle(ARMMoveStatusRegister const& opcode) -> Status override; 38 | auto Handle(ARMMoveRegisterStatus const& opcode) -> Status override; 39 | auto Handle(ARMMultiply const& opcode) -> Status override; 40 | auto Handle(ARMMultiplyLong const& opcode) -> Status override; 41 | auto Handle(ARMSingleDataSwap const& opcode) -> Status override; 42 | auto Handle(ARMBranchExchange const& opcode) -> Status override; 43 | auto Handle(ARMHalfwordSignedTransfer const& opcode) -> Status override; 44 | auto Handle(ARMSingleDataTransfer const& opcode) -> Status override; 45 | auto Handle(ARMBlockDataTransfer const& opcode) -> Status override; 46 | auto Handle(ARMBranchRelative const& opcode) -> Status override; 47 | auto Handle(ARMCoprocessorRegisterTransfer const& opcode) -> Status override; 48 | auto Handle(ARMException const& opcode) -> Status override; 49 | auto Handle(ARMCountLeadingZeros const& opcode) -> Status override; 50 | auto Handle(ARMSaturatingAddSub const& opcode) -> Status override; 51 | auto Handle(ARMSignedHalfwordMultiply const& opcode) -> Status override; 52 | auto Handle(ARMSignedWordHalfwordMultiply const& opcode) -> Status override; 53 | auto Handle(ARMSignedHalfwordMultiplyAccumulateLong const& opcode) -> Status override; 54 | auto Handle(ThumbBranchLinkSuffix const& opcode) -> Status override; 55 | auto Undefined(u32 opcode) -> Status override; 56 | 57 | private: 58 | Status TranslateARM(BasicBlock& basic_block); 59 | Status TranslateThumb(BasicBlock& basic_block); 60 | 61 | void EmitUpdateNZ(); 62 | void EmitUpdateNZC(); 63 | void EmitUpdateNZCV(); 64 | void EmitUpdateQ(); 65 | void EmitAdvancePC(); 66 | void EmitFlush(); 67 | void EmitFlushExchange(IRVariable const& address); 68 | void EmitFlushNoSwitch(); 69 | void EmitLoadSPSRToCPSR(); 70 | 71 | // TODO: deduce opcode_size from thumb_mode. this is redundant. 72 | u32 code_address; 73 | bool thumb_mode; 74 | u32 opcode_size; 75 | Mode mode; 76 | bool armv5te; 77 | int max_block_size; 78 | u32 exception_base; 79 | Memory& memory; 80 | std::array coprocessors; 81 | IREmitter* emitter = nullptr; 82 | BasicBlock* basic_block = nullptr; 83 | }; 84 | 85 | } // namespace lunatic::frontend 86 | } // namespace lunatic 87 | -------------------------------------------------------------------------------- /src/jit.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 fleroviux 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "frontend/ir_opt/constant_propagation.hpp" 10 | #include "frontend/ir_opt/context_load_store_elision.hpp" 11 | #include "frontend/ir_opt/dead_code_elision.hpp" 12 | #include "frontend/ir_opt/dead_flag_elision.hpp" 13 | #include "frontend/state.hpp" 14 | #include "frontend/translator/translator.hpp" 15 | 16 | #include "backend/backend.hpp" 17 | 18 | using namespace lunatic::frontend; 19 | using namespace lunatic::backend; 20 | 21 | namespace lunatic { 22 | 23 | struct JIT final : CPU { 24 | JIT(CPU::Descriptor const& descriptor) 25 | : exception_base(descriptor.exception_base) 26 | , memory(descriptor.memory) 27 | , translator(descriptor) { 28 | backend = Backend::CreateBackend(descriptor, state, block_cache, irq_line); 29 | passes.push_back(std::make_unique()); 30 | passes.push_back(std::make_unique()); 31 | passes.push_back(std::make_unique()); 32 | passes.push_back(std::make_unique()); 33 | } 34 | 35 | void Reset() override { 36 | irq_line = false; 37 | wait_for_irq = false; 38 | cycles_to_run = 0; 39 | state.Reset(); 40 | SetGPR(GPR::PC, exception_base); 41 | block_cache.Flush(); 42 | exception_causing_basic_blocks.clear(); 43 | } 44 | 45 | auto IRQLine() -> bool& override { 46 | return irq_line; 47 | } 48 | 49 | auto WaitForIRQ() -> bool& override { 50 | return wait_for_irq; 51 | } 52 | 53 | auto GetExceptionBase() const -> u32 { 54 | return exception_base; 55 | } 56 | 57 | void SetExceptionBase(u32 new_exception_base) override { 58 | if (new_exception_base != this->exception_base) { 59 | // this is expected to happen rarely, so we just invalidate all blocks that may cause an exception. 60 | while (!exception_causing_basic_blocks.empty()) { 61 | block_cache.Set(exception_causing_basic_blocks.front()->key, nullptr); 62 | } 63 | 64 | translator.SetExceptionBase(new_exception_base); 65 | this->exception_base = new_exception_base; 66 | } 67 | } 68 | 69 | void ClearICache() override { 70 | block_cache.Flush(); 71 | } 72 | 73 | void ClearICacheRange(u32 address_lo, u32 address_hi) override { 74 | block_cache.Flush(address_lo, address_hi); 75 | } 76 | 77 | auto Run(int cycles) -> int override { 78 | if (WaitForIRQ() && !IRQLine()) { 79 | return 0; 80 | } 81 | 82 | cycles_to_run += cycles; 83 | 84 | int cycles_available = cycles_to_run; 85 | 86 | while (cycles_to_run > 0) { 87 | if (IRQLine()) { 88 | SignalIRQ(); 89 | } 90 | 91 | auto block_key = BasicBlock::Key{state}; 92 | auto basic_block = block_cache.Get(block_key); 93 | auto hash = GetBasicBlockHash(block_key); 94 | 95 | if (basic_block == nullptr || basic_block->hash != hash) { 96 | basic_block = Compile(block_key); 97 | } 98 | 99 | cycles_to_run = backend->Call(*basic_block, cycles_to_run); 100 | 101 | if (WaitForIRQ()) { 102 | int cycles_executed = cycles_available - cycles_to_run; 103 | cycles_to_run = 0; 104 | return cycles_executed; 105 | } 106 | } 107 | 108 | return cycles_available - cycles_to_run; 109 | } 110 | 111 | auto GetGPR(GPR reg) const -> u32 override { 112 | return GetGPR(reg, GetCPSR().f.mode); 113 | } 114 | 115 | auto GetGPR(GPR reg, Mode mode) const -> u32 override { 116 | return const_cast(this)->GetGPR(reg, mode); 117 | } 118 | 119 | auto GetCPSR() const -> StatusRegister override { 120 | return const_cast(this)->GetCPSR(); 121 | } 122 | 123 | auto GetSPSR(Mode mode) const -> StatusRegister override { 124 | return const_cast(this)->GetSPSR(mode); 125 | } 126 | 127 | void SetGPR(GPR reg, u32 value) override { 128 | SetGPR(reg, state.GetCPSR().f.mode, value); 129 | } 130 | 131 | void SetGPR(GPR reg, Mode mode, u32 value) override { 132 | state.GetGPR(mode, reg) = value; 133 | 134 | if (reg == GPR::PC) { 135 | if (GetCPSR().f.thumb) { 136 | state.GetGPR(mode, GPR::PC) += sizeof(u16) * 2; 137 | } else { 138 | state.GetGPR(mode, GPR::PC) += sizeof(u32) * 2; 139 | } 140 | } 141 | } 142 | 143 | void SetCPSR(StatusRegister value) override { 144 | state.GetCPSR() = value; 145 | } 146 | 147 | void SetSPSR(Mode mode, StatusRegister value) override { 148 | *state.GetPointerToSPSR(mode) = value; 149 | } 150 | 151 | private: 152 | auto Compile(BasicBlock::Key block_key) -> BasicBlock* { 153 | auto basic_block = new BasicBlock{block_key}; 154 | 155 | basic_block->hash = GetBasicBlockHash(block_key); 156 | 157 | translator.Translate(*basic_block); 158 | Optimize(basic_block); 159 | 160 | if (basic_block->uses_exception_base) { 161 | exception_causing_basic_blocks.push_back(basic_block); 162 | 163 | basic_block->RegisterReleaseCallback([this](BasicBlock const& block) { 164 | auto match = std::find( 165 | exception_causing_basic_blocks.begin(), exception_causing_basic_blocks.end(), &block); 166 | 167 | if (match != exception_causing_basic_blocks.end()) { 168 | exception_causing_basic_blocks.erase(match); 169 | } 170 | }); 171 | } 172 | 173 | backend->Compile(*basic_block); 174 | block_cache.Set(block_key, basic_block); 175 | basic_block->micro_blocks.clear(); 176 | return basic_block; 177 | } 178 | 179 | void Optimize(BasicBlock* basic_block) { 180 | for (auto µ_block : basic_block->micro_blocks) { 181 | for (auto& pass : passes) { 182 | pass->Run(micro_block.emitter); 183 | } 184 | } 185 | } 186 | 187 | void SignalIRQ() { 188 | auto& cpsr = GetCPSR(); 189 | 190 | wait_for_irq = false; 191 | 192 | if (!cpsr.f.mask_irq) { 193 | GetSPSR(Mode::IRQ) = cpsr; 194 | 195 | cpsr.f.mode = Mode::IRQ; 196 | cpsr.f.mask_irq = 1; 197 | if (cpsr.f.thumb) { 198 | GetGPR(GPR::LR) = GetGPR(GPR::PC); 199 | } else { 200 | GetGPR(GPR::LR) = GetGPR(GPR::PC) - 4; 201 | } 202 | cpsr.f.thumb = 0; 203 | 204 | GetGPR(GPR::PC) = exception_base + 0x18 + sizeof(u32) * 2; 205 | } 206 | } 207 | 208 | auto GetBasicBlockHash(BasicBlock::Key block_key) -> u32 { 209 | return memory.FastRead(block_key.Address()); 210 | } 211 | 212 | auto GetGPR(GPR reg) -> u32& { 213 | return GetGPR(reg, GetCPSR().f.mode); 214 | } 215 | 216 | auto GetGPR(GPR reg, Mode mode) -> u32& { 217 | return state.GetGPR(mode, reg); 218 | } 219 | 220 | auto GetCPSR() -> StatusRegister& { 221 | return state.GetCPSR(); 222 | } 223 | 224 | auto GetSPSR(Mode mode) -> StatusRegister& { 225 | return *state.GetPointerToSPSR(mode); 226 | } 227 | 228 | bool irq_line = false; 229 | bool wait_for_irq = false; 230 | int cycles_to_run = 0; 231 | u32 exception_base; 232 | Memory& memory; 233 | State state; 234 | Translator translator; 235 | BasicBlockCache block_cache; 236 | std::unique_ptr backend; 237 | std::vector> passes; 238 | std::vector exception_causing_basic_blocks; 239 | }; 240 | 241 | auto CreateCPU(CPU::Descriptor const& descriptor) -> std::unique_ptr { 242 | return std::make_unique(descriptor); 243 | } 244 | 245 | } // namespace lunatic 246 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.2) 2 | project(lunatic-test CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules) 7 | 8 | set(SOURCES 9 | main.cpp) 10 | 11 | set(HEADERS) 12 | 13 | include(FindSDL2) 14 | find_package(SDL2 REQUIRED) 15 | 16 | add_executable(test ${SOURCES} ${HEADERS}) 17 | target_link_libraries(test lunatic fmt ${SDL2_LIBRARY}) 18 | target_include_directories(test PRIVATE . ${SDL2_INCLUDE_DIR}) 19 | 20 | # Hack to access the internal library headers for testing 21 | target_include_directories(test PRIVATE ../src) 22 | target_link_libraries(test xbyak) 23 | 24 | if (CMAKE_SYSTEM_NAME STREQUAL "Windows") 25 | if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 26 | target_compile_options(test PRIVATE /clang:-fbracket-depth=4096) 27 | endif() 28 | else () 29 | if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 30 | target_compile_options(test PRIVATE -fbracket-depth=4096) 31 | endif() 32 | endif() 33 | -------------------------------------------------------------------------------- /test/CMakeModules/FindSDL2.cmake: -------------------------------------------------------------------------------- 1 | # Locate SDL2 library 2 | # This module defines 3 | # SDL2_LIBRARY, the name of the library to link against 4 | # SDL2_FOUND, if false, do not try to link to SDL2 5 | # SDL2_INCLUDE_DIR, where to find SDL.h 6 | # 7 | # This module responds to the the flag: 8 | # SDL2_BUILDING_LIBRARY 9 | # If this is defined, then no SDL2_main will be linked in because 10 | # only applications need main(). 11 | # Otherwise, it is assumed you are building an application and this 12 | # module will attempt to locate and set the the proper link flags 13 | # as part of the returned SDL2_LIBRARY variable. 14 | # 15 | # Don't forget to include SDL2main.h and SDL2main.m your project for the 16 | # OS X framework based version. (Other versions link to -lSDL2main which 17 | # this module will try to find on your behalf.) Also for OS X, this 18 | # module will automatically add the -framework Cocoa on your behalf. 19 | # 20 | # 21 | # Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration 22 | # and no SDL2_LIBRARY, it means CMake did not find your SDL2 library 23 | # (SDL2.dll, libsdl2.so, SDL2.framework, etc). 24 | # Set SDL2_LIBRARY_TEMP to point to your SDL2 library, and configure again. 25 | # Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value 26 | # as appropriate. These values are used to generate the final SDL2_LIBRARY 27 | # variable, but when these values are unset, SDL2_LIBRARY does not get created. 28 | # 29 | # 30 | # $SDL2DIR is an environment variable that would 31 | # correspond to the ./configure --prefix=$SDL2DIR 32 | # used in building SDL2. 33 | # l.e.galup 9-20-02 34 | # 35 | # Modified by Eric Wing. 36 | # Added code to assist with automated building by using environmental variables 37 | # and providing a more controlled/consistent search behavior. 38 | # Added new modifications to recognize OS X frameworks and 39 | # additional Unix paths (FreeBSD, etc). 40 | # Also corrected the header search path to follow "proper" SDL2 guidelines. 41 | # Added a search for SDL2main which is needed by some platforms. 42 | # Added a search for threads which is needed by some platforms. 43 | # Added needed compile switches for MinGW. 44 | # 45 | # On OSX, this will prefer the Framework version (if found) over others. 46 | # People will have to manually change the cache values of 47 | # SDL2_LIBRARY to override this selection or set the CMake environment 48 | # CMAKE_INCLUDE_PATH to modify the search paths. 49 | # 50 | # Note that the header path has changed from SDL2/SDL.h to just SDL.h 51 | # This needed to change because "proper" SDL2 convention 52 | # is #include "SDL.h", not . This is done for portability 53 | # reasons because not all systems place things in SDL2/ (see FreeBSD). 54 | # 55 | # Ported by Johnny Patterson. This is a literal port for SDL2 of the FindSDL.cmake 56 | # module with the minor edit of changing "SDL" to "SDL2" where necessary. This 57 | # was not created for redistribution, and exists temporarily pending official 58 | # SDL2 CMake modules. 59 | 60 | #============================================================================= 61 | # Copyright 2003-2009 Kitware, Inc. 62 | # 63 | # Distributed under the OSI-approved BSD License (the "License"); 64 | # see accompanying file Copyright.txt for details. 65 | # 66 | # This software is distributed WITHOUT ANY WARRANTY; without even the 67 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 68 | # See the License for more information. 69 | #============================================================================= 70 | # (To distribute this file outside of CMake, substitute the full 71 | # License text for the above reference.) 72 | 73 | FIND_PATH(SDL2_INCLUDE_DIR SDL.h 74 | HINTS 75 | $ENV{SDL2DIR} 76 | PATH_SUFFIXES include/SDL2 include 77 | PATHS 78 | ~/Library/Frameworks 79 | /Library/Frameworks 80 | /usr/local/include/SDL2 81 | /usr/include/SDL2 82 | /sw # Fink 83 | /opt/local # DarwinPorts 84 | /opt/csw # Blastwave 85 | /opt 86 | ) 87 | #MESSAGE("SDL2_INCLUDE_DIR is ${SDL2_INCLUDE_DIR}") 88 | 89 | FIND_LIBRARY(SDL2_LIBRARY_TEMP 90 | NAMES SDL2 91 | HINTS 92 | $ENV{SDL2DIR} 93 | PATH_SUFFIXES lib64 lib 94 | PATHS 95 | /sw 96 | /opt/local 97 | /opt/csw 98 | /opt 99 | ) 100 | 101 | #MESSAGE("SDL2_LIBRARY_TEMP is ${SDL2_LIBRARY_TEMP}") 102 | 103 | IF(NOT SDL2_BUILDING_LIBRARY) 104 | IF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") 105 | # Non-OS X framework versions expect you to also dynamically link to 106 | # SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms 107 | # seem to provide SDL2main for compatibility even though they don't 108 | # necessarily need it. 109 | FIND_LIBRARY(SDL2MAIN_LIBRARY 110 | NAMES SDL2main 111 | HINTS 112 | $ENV{SDL2DIR} 113 | PATH_SUFFIXES lib64 lib 114 | PATHS 115 | /sw 116 | /opt/local 117 | /opt/csw 118 | /opt 119 | ) 120 | ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") 121 | ENDIF(NOT SDL2_BUILDING_LIBRARY) 122 | 123 | # SDL2 may require threads on your system. 124 | # The Apple build may not need an explicit flag because one of the 125 | # frameworks may already provide it. 126 | # But for non-OSX systems, I will use the CMake Threads package. 127 | IF(NOT APPLE) 128 | FIND_PACKAGE(Threads) 129 | ENDIF(NOT APPLE) 130 | 131 | # MinGW needs an additional library, mwindows 132 | # It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -lmwindows 133 | # (Actually on second look, I think it only needs one of the m* libraries.) 134 | IF(MINGW) 135 | SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW") 136 | ENDIF(MINGW) 137 | 138 | SET(SDL2_FOUND "NO") 139 | IF(SDL2_LIBRARY_TEMP) 140 | # For SDL2main 141 | IF(NOT SDL2_BUILDING_LIBRARY) 142 | IF(SDL2MAIN_LIBRARY) 143 | SET(SDL2_LIBRARY_TEMP ${SDL2MAIN_LIBRARY} ${SDL2_LIBRARY_TEMP}) 144 | ENDIF(SDL2MAIN_LIBRARY) 145 | ENDIF(NOT SDL2_BUILDING_LIBRARY) 146 | 147 | # For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa. 148 | # CMake doesn't display the -framework Cocoa string in the UI even 149 | # though it actually is there if I modify a pre-used variable. 150 | # I think it has something to do with the CACHE STRING. 151 | # So I use a temporary variable until the end so I can set the 152 | # "real" variable in one-shot. 153 | IF(APPLE) 154 | SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa") 155 | ENDIF(APPLE) 156 | 157 | # For threads, as mentioned Apple doesn't need this. 158 | # In fact, there seems to be a problem if I used the Threads package 159 | # and try using this line, so I'm just skipping it entirely for OS X. 160 | IF(NOT APPLE) 161 | SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) 162 | ENDIF(NOT APPLE) 163 | 164 | # For MinGW library 165 | IF(MINGW) 166 | SET(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP}) 167 | ENDIF(MINGW) 168 | 169 | # Set the final string here so the GUI reflects the final state. 170 | SET(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL2 Library can be found") 171 | # Set the temp variable to INTERNAL so it is not seen in the CMake GUI 172 | SET(SDL2_LIBRARY_TEMP "${SDL2_LIBRARY_TEMP}" CACHE INTERNAL "") 173 | 174 | SET(SDL2_FOUND "YES") 175 | ENDIF(SDL2_LIBRARY_TEMP) 176 | 177 | INCLUDE(FindPackageHandleStandardArgs) 178 | 179 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 180 | REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR) --------------------------------------------------------------------------------