diff --git a/CMakeLists.txt b/CMakeLists.txt index b0fe285db..68377390b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -192,4 +192,12 @@ if(ENABLE_QT) include_directories(externals/qhexedit) add_subdirectory(externals/qhexedit) endif() + +option(ENABLE_BINARY_TRANSLATION "Enable binary translation. Requires LLVM" OFF) +if(ENABLE_BINARY_TRANSLATION) + find_package(LLVM REQUIRED CONFIG) + include_directories(${LLVM_INCLUDE_DIRS}) + add_definitions(${LLVM_DEFINITIONS}) + llvm_map_components_to_libnames(llvm_libs Core Support X86 Arm ArmCodeGen ArmAsmParser ArmAsmPrinter ArmDisassembler ExecutionEngine MCJIT BitWriter ipo) +endif() add_subdirectory(src) diff --git a/externals/nihstro b/externals/nihstro index 81f1804a4..4a78588b3 160000 --- a/externals/nihstro +++ b/externals/nihstro @@ -1 +1 @@ -Subproject commit 81f1804a43f625e3a1a20752c0db70a413410380 +Subproject commit 4a78588b308564f7ebae193e0ae00d9a0d5741d5 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cb09f3cd1..afa414fe9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,3 +10,6 @@ endif() if (ENABLE_QT) add_subdirectory(citra_qt) endif() +if(ENABLE_BINARY_TRANSLATION) + add_subdirectory(binary_translation) +endif() \ No newline at end of file diff --git a/src/binary_translation/CMakeLists.txt b/src/binary_translation/CMakeLists.txt new file mode 100644 index 000000000..198736c17 --- /dev/null +++ b/src/binary_translation/CMakeLists.txt @@ -0,0 +1,15 @@ +set(SRCS + main.cpp + CodeGen.cpp + ) +set(HEADERS + CodeGen.h + ) + +create_directory_groups(${SRCS} ${HEADERS}) + +add_executable(binary_translate ${SRCS} ${HEADERS}) +target_link_libraries(binary_translate ${llvm_libs}) +target_link_libraries(binary_translate core common video_core) +target_link_libraries(binary_translate ${GLFW_LIBRARIES} ${OPENGL_gl_LIBRARY} inih) +target_link_libraries(binary_translate ${PLATFORM_LIBRARIES}) \ No newline at end of file diff --git a/src/binary_translation/CodeGen.cpp b/src/binary_translation/CodeGen.cpp new file mode 100644 index 000000000..5132846d0 --- /dev/null +++ b/src/binary_translation/CodeGen.cpp @@ -0,0 +1,162 @@ +#include "CodeGen.h" +#include "core/loader/loader.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace llvm; + +CodeGen::CodeGen(const char* output_object_filename, const char* output_debug_filename) + : output_object_filename(output_object_filename), + output_debug_filename(output_debug_filename) +{ +} + +CodeGen::~CodeGen() +{ +} + +void CodeGen::Run() +{ + if (!Loader::ROMCodeStart) + { + LOG_CRITICAL(BinaryTranslator, "No information from the loader about ROM file."); + return; + } + + IntializeLLVM(); + GenerateModule(); + GenerateDebugFiles(); + if (!Verify()) return; + OptimizeAndGenerate(); +} + +void CodeGen::IntializeLLVM() +{ + InitializeNativeTarget(); + InitializeNativeTargetAsmPrinter(); + + auto triple_string = sys::getProcessTriple(); +#ifdef _WIN32 + // LLVM doesn't know how to load coff files + // It can handle elf files in every platform + triple_string += "-elf"; +#endif + + triple = llvm::make_unique(triple_string); + + // This engine builder is needed to get the target machine. It requires a module + // but takes ownership of it so the main module cannot be passed here. + EngineBuilder engine_builder(make_unique("", getGlobalContext())); + target_machine.reset(engine_builder.selectTarget(*triple, "", "", SmallVector())); + + module = make_unique("Module", getGlobalContext()); + module->setTargetTriple(triple_string); + ir_builder = make_unique>(getGlobalContext()); +} + +void CodeGen::GenerateModule() +{ + // TODO: +} + +void CodeGen::GenerateDebugFiles() +{ + if (!output_debug_filename) return; + + LOG_INFO(BinaryTranslator, "Writing debug file"); + std::ofstream file(output_debug_filename); + if (!file) + { + LOG_ERROR(BinaryTranslator, "Cannot create debug file: %s", output_debug_filename); + return; + } + raw_os_ostream stream(file); + + module->print(stream, nullptr); + file.close(); + LOG_INFO(BinaryTranslator, "Done"); +} + +bool CodeGen::Verify() +{ + LOG_INFO(BinaryTranslator, "Verifying"); + raw_os_ostream os(std::cout); + if (verifyModule(*module, &os)) + { + LOG_CRITICAL(BinaryTranslator, "Verify failed"); + return false; + } + LOG_INFO(BinaryTranslator, "Done"); + return true; +} + +void CodeGen::OptimizeAndGenerate() +{ + /* + * Taken from opt for O3 + */ + PassManagerBuilder pass_manager_builder; + + FunctionPassManager function_pass_manager(module.get()); + + module->setDataLayout(target_machine->getSubtargetImpl()->getDataLayout()); + + function_pass_manager.add(new DataLayoutPass()); + target_machine->addAnalysisPasses(function_pass_manager); + + pass_manager_builder.OptLevel = 3; + pass_manager_builder.SizeLevel = 0; + pass_manager_builder.Inliner = createFunctionInliningPass(3, 0); + pass_manager_builder.LoopVectorize = true; + pass_manager_builder.SLPVectorize = true; + + pass_manager_builder.populateFunctionPassManager(function_pass_manager); + + LOG_INFO(BinaryTranslator, "Optimizing functions"); + function_pass_manager.doInitialization(); + for (auto &function : *module) + function_pass_manager.run(function); + function_pass_manager.doFinalization(); + LOG_INFO(BinaryTranslator, "Done"); + + PassManager pass_manager; + + pass_manager.add(createVerifierPass()); + pass_manager.add(new TargetLibraryInfo(*triple)); + pass_manager.add(new DataLayoutPass()); + target_machine->addAnalysisPasses(pass_manager); + pass_manager_builder.populateModulePassManager(pass_manager); + pass_manager.add(createVerifierPass()); + + LOG_INFO(BinaryTranslator, "Optimizing module"); + pass_manager.run(*module); + LOG_INFO(BinaryTranslator, "Done"); + + MCContext *context; + std::ofstream file(output_object_filename, std::ios::binary); + if (!file) + { + LOG_CRITICAL(BinaryTranslator, "Cannot create object file: %s", output_object_filename); + return; + } + raw_os_ostream stream(file); + if (target_machine->addPassesToEmitMC(pass_manager, context, stream, false)) + { + LOG_CRITICAL(BinaryTranslator, "Target does not support MC emission!"); + return; + } + LOG_INFO(BinaryTranslator, "Generating code"); + pass_manager.run(*module); + file.close(); + LOG_INFO(BinaryTranslator, "Done"); +} \ No newline at end of file diff --git a/src/binary_translation/CodeGen.h b/src/binary_translation/CodeGen.h new file mode 100644 index 000000000..9f52eaca7 --- /dev/null +++ b/src/binary_translation/CodeGen.h @@ -0,0 +1,33 @@ +#include +#include + +namespace llvm +{ + class TargetMachine; + class Module; +} + +/* + * Holds alls the basic llvm structures + */ +class CodeGen +{ +public: + CodeGen(const char *output_object_filename, const char *output_debug_filename); + ~CodeGen(); + + void Run(); + void IntializeLLVM(); + void GenerateModule(); + void GenerateDebugFiles(); + bool Verify(); + void OptimizeAndGenerate(); +private: + const char *output_object_filename; + const char *output_debug_filename; + + std::unique_ptr triple; + std::unique_ptr target_machine; + std::unique_ptr module; + std::unique_ptr> ir_builder; +}; \ No newline at end of file diff --git a/src/binary_translation/main.cpp b/src/binary_translation/main.cpp new file mode 100644 index 000000000..1cd5f720e --- /dev/null +++ b/src/binary_translation/main.cpp @@ -0,0 +1,42 @@ +#include "common/logging/backend.h" +#include "common/logging/text_formatter.h" +#include "common/scope_exit.h" +#include "core/core.h" +#include "core/mem_map.h" +#include "core/loader/loader.h" +#include "codegen.h" + +int main(int argc, const char *const *argv) +{ + std::shared_ptr logger = Log::InitGlobalLogger(); + Log::Filter log_filter(Log::Level::Debug); + Log::SetFilter(&log_filter); + std::thread logging_thread(Log::TextLoggingLoop, logger); + SCOPE_EXIT({ + logger->Close(); + logging_thread.join(); + }); + + if (argc < 3) + { + LOG_CRITICAL(BinaryTranslator, "Usage: binary_translate []"); + return -1; + } + + auto input_rom = argv[1]; + auto output_object = argv[2]; + auto output_debug = argc > 3 ? argv[3] : nullptr; + + Core::Init(); + Memory::Init(); + + auto load_result = Loader::LoadFile(input_rom); + if (Loader::ResultStatus::Success != load_result) + { + LOG_CRITICAL(BinaryTranslator, "Failed to load ROM (Error %i)!", load_result); + return -1; + } + + CodeGen code_generator(output_object, output_debug); + code_generator.Run(); +} \ No newline at end of file diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index ff780cad4..35f580883 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -72,5 +72,8 @@ endif() target_link_libraries(citra-qt core common video_core qhexedit) target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS}) target_link_libraries(citra-qt ${PLATFORM_LIBRARIES}) +if(ENABLE_BINARY_TRANSLATION) + target_link_libraries(citra-qt ${llvm_libs}) +endif() #install(TARGETS citra-qt RUNTIME DESTINATION ${bindir}) diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 7d3534a43..3d1d9cf31 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -53,7 +53,8 @@ static std::shared_ptr global_logger; CLS(Render) \ SUB(Render, Software) \ SUB(Render, OpenGL) \ - CLS(Loader) + CLS(Loader) \ + CLS(BinaryTranslator) Logger::Logger() { // Register logging classes so that they can be queried at runtime diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 123641cb4..d88eb6452 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -74,6 +74,7 @@ enum class Class : ClassType { Render_Software, ///< Software renderer backend Render_OpenGL, ///< OpenGL backend Loader, ///< ROM loader + BinaryTranslator, ///< Binary translator Count ///< Total number of logging classes }; diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index e28986085..fcf6282c3 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -13,6 +13,7 @@ #include "core/loader/elf.h" #include "core/loader/ncch.h" #include "core/mem_map.h" +#include "core/loader/loader.h" #include "3dsx.h" @@ -203,6 +204,11 @@ static THREEDSX_Error Load3DSXFile(FileUtil::IOFile& file, u32 base_addr) // Write the data memcpy(Memory::GetPointer(base_addr), &all_mem[0], loadinfo.seg_sizes[0] + loadinfo.seg_sizes[1] + loadinfo.seg_sizes[2]); + Loader::ROMCodeStart = base_addr; + Loader::ROMCodeSize = loadinfo.seg_sizes[0]; + Loader::ROMReadOnlyDataStart = base_addr + loadinfo.seg_sizes[0]; + Loader::ROMReadOnlyDataSize = loadinfo.seg_sizes[1]; + LOG_DEBUG(Loader, "CODE: %u pages\n", loadinfo.seg_sizes[0] / 0x1000); LOG_DEBUG(Loader, "RODATA: %u pages\n", loadinfo.seg_sizes[1] / 0x1000); LOG_DEBUG(Loader, "DATA: %u pages\n", data_load_size / 0x1000); diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 4f881cf6f..faf1af51a 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -24,6 +24,10 @@ const std::initializer_list default_address_mappings = { { 0x1FF70000, 0x8000, true }, // part of DSP RAM { 0x1F000000, 0x600000, false }, // entire VRAM }; +u32 ROMCodeStart = 0; +u32 ROMCodeSize = 0; +u32 ROMReadOnlyDataStart = 0; +u32 ROMReadOnlyDataSize = 0; /** * Identifies the type of a bootable file diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 87e16fb98..abdac3b87 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -118,4 +118,12 @@ extern const std::initializer_list default_address_mappi */ ResultStatus LoadFile(const std::string& filename); +/* + * Infomation about ROM + */ +extern u32 ROMCodeStart; +extern u32 ROMCodeSize; +extern u32 ROMReadOnlyDataStart; +extern u32 ROMReadOnlyDataSize; + } // namespace diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 3325a6ab8..5755a280a 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -136,6 +136,13 @@ ResultStatus AppLoader_NCCH::LoadExec() const { s32 priority = exheader_header.arm11_system_local_caps.priority; u32 stack_size = exheader_header.codeset_info.stack_size; Kernel::g_current_process->Run(entry_point, priority, stack_size); + + Loader::ROMCodeStart = entry_point; + Loader::ROMCodeSize = entry_point + code.size(); + // TODO: Don't know where these are + Loader::ROMReadOnlyDataStart = 0; + Loader::ROMReadOnlyDataSize = 0; + return ResultStatus::Success; } return ResultStatus::Error;