diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..4bf423d --- /dev/null +++ b/.clang-format @@ -0,0 +1,47 @@ +--- +BasedOnStyle: Google +AccessModifierOffset: -2 +ConstructorInitializerIndentWidth: 4 +AlignEscapedNewlinesLeft: false +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortLoopsOnASingleLine: false +AlwaysBreakTemplateDeclarations: true +AlwaysBreakBeforeMultilineStrings: false +BreakBeforeBinaryOperators: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: true +BinPackParameters: true +ColumnLimit: 100 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +DerivePointerBinding: true +ExperimentalAutoDetectBinPacking: false +IndentCaseLabels: true +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 60 +PenaltyBreakString: 1 +PenaltyBreakFirstLessLess: 1000 +PenaltyExcessCharacter: 1000 +PenaltyReturnTypeOnItsOwnLine: 90 +PointerBindsToType: false +SpacesBeforeTrailingComments: 2 +Cpp11BracedListStyle: false +Standard: Auto +IndentWidth: 4 +TabWidth: 2 +UseTab: Never +BreakBeforeBraces: Custom +IndentFunctionDeclarationAfterType: false +SpacesInParentheses: false +SpacesInAngles: false +SpaceInEmptyParentheses: false +SpacesInCStyleCastParentheses: false +SpaceAfterControlStatementKeyword: true +SpaceBeforeAssignmentOperators: true +ContinuationIndentWidth: 4 diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml new file mode 100644 index 0000000..4d3d6f7 --- /dev/null +++ b/.github/workflows/cli.yml @@ -0,0 +1,233 @@ +name: CMake Build Matrix + +on: [push] + +jobs: + build: + name: ${{ matrix.config.name }} + runs-on: ${{ matrix.config.os }} + strategy: + fail-fast: false + matrix: + config: + - { + name: "Windows Latest MSVC", artifact: "Windows-MSVC.tar.xz", + os: windows-latest, + build_type: "Release", cc: "cl", cxx: "cl", + environment_script: "C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Auxiliary/Build/vcvars64.bat" + } + - { + name: "Windows Latest MinGW", artifact: "Windows-MinGW.tar.xz", + os: windows-latest, + build_type: "Release", cc: "gcc", cxx: "g++" + } + - { + name: "Ubuntu Latest GCC", artifact: "Linux.tar.xz", + os: ubuntu-latest, + build_type: "Release", cc: "gcc", cxx: "g++" + } + - { + name: "macOS Latest Clang", artifact: "macOS.tar.xz", + os: macos-latest, + build_type: "Release", cc: "clang", cxx: "clang++" + } + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + submodules: 'true' + + - name: Download Ninja and CMake + id: cmake_and_ninja + shell: cmake -P {0} + run: | + set(ninja_version "1.9.0") + set(cmake_version "3.16.2") + + message(STATUS "Using host CMake version: ${CMAKE_VERSION}") + + if ("${{ runner.os }}" STREQUAL "Windows") + set(ninja_suffix "win.zip") + set(cmake_suffix "win64-x64.zip") + set(cmake_dir "cmake-${cmake_version}-win64-x64/bin") + elseif ("${{ runner.os }}" STREQUAL "Linux") + set(ninja_suffix "linux.zip") + set(cmake_suffix "Linux-x86_64.tar.gz") + set(cmake_dir "cmake-${cmake_version}-Linux-x86_64/bin") + elseif ("${{ runner.os }}" STREQUAL "macOS") + set(ninja_suffix "mac.zip") + set(cmake_suffix "Darwin-x86_64.tar.gz") + set(cmake_dir "cmake-${cmake_version}-Darwin-x86_64/CMake.app/Contents/bin") + endif() + + set(ninja_url "https://github.com/ninja-build/ninja/releases/download/v${ninja_version}/ninja-${ninja_suffix}") + file(DOWNLOAD "${ninja_url}" ./ninja.zip SHOW_PROGRESS) + execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./ninja.zip) + + set(cmake_url "https://github.com/Kitware/CMake/releases/download/v${cmake_version}/cmake-${cmake_version}-${cmake_suffix}") + file(DOWNLOAD "${cmake_url}" ./cmake.zip SHOW_PROGRESS) + execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./cmake.zip) + + # Save the path for other steps + file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/${cmake_dir}" cmake_dir) + message("::set-output name=cmake_dir::${cmake_dir}") + + if (NOT "${{ runner.os }}" STREQUAL "Windows") + execute_process( + COMMAND chmod +x ninja + COMMAND chmod +x ${cmake_dir}/cmake + ) + endif() + + - name: Configure + shell: cmake -P {0} + run: | + set(ENV{CC} ${{ matrix.config.cc }}) + set(ENV{CXX} ${{ matrix.config.cxx }}) + + if ("${{ runner.os }}" STREQUAL "Windows" AND NOT "x${{ matrix.config.environment_script }}" STREQUAL "x") + execute_process( + COMMAND "${{ matrix.config.environment_script }}" && set + OUTPUT_FILE environment_script_output.txt + ) + file(STRINGS environment_script_output.txt output_lines) + foreach(line IN LISTS output_lines) + if (line MATCHES "^([a-zA-Z0-9_-]+)=(.*)$") + set(ENV{${CMAKE_MATCH_1}} "${CMAKE_MATCH_2}") + endif() + endforeach() + endif() + + file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/ninja" ninja_program) + + execute_process( + COMMAND ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake + -S . + -B build + -D CMAKE_BUILD_TYPE=${{ matrix.config.build_type }} + -G Ninja + -D CMAKE_MAKE_PROGRAM=${ninja_program} + RESULT_VARIABLE result + ) + if (NOT result EQUAL 0) + message(FATAL_ERROR "Bad exit status") + endif() + + + - name: Build + shell: cmake -P {0} + run: | + set(ENV{NINJA_STATUS} "[%f/%t %o/sec] ") + + if ("${{ runner.os }}" STREQUAL "Windows" AND NOT "x${{ matrix.config.environment_script }}" STREQUAL "x") + file(STRINGS environment_script_output.txt output_lines) + foreach(line IN LISTS output_lines) + if (line MATCHES "^([a-zA-Z0-9_-]+)=(.*)$") + set(ENV{${CMAKE_MATCH_1}} "${CMAKE_MATCH_2}") + endif() + endforeach() + endif() + + execute_process( + COMMAND ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake --build build + RESULT_VARIABLE result + ) + if (NOT result EQUAL 0) + message(FATAL_ERROR "Bad exit status") + endif() + + - name: Install Strip + run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake --install build --prefix instdir --strip + + + - name: Pack + working-directory: instdir + run: ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake -E tar cJfv ../${{ matrix.config.artifact }} . + + + - name: Upload + uses: actions/upload-artifact@v1 + with: + path: ./${{ matrix.config.artifact }} + name: ${{ matrix.config.artifact }} + + release: + if: contains(github.ref, 'tags/v') + runs-on: ubuntu-latest + needs: build + + steps: + - name: Create Release + id: create_release + uses: actions/create-release@v1.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + + - name: Store Release url + run: | + echo "${{ steps.create_release.outputs.upload_url }}" > ./upload_url + + - uses: actions/upload-artifact@v1 + with: + path: ./upload_url + name: upload_url + + publish: + if: contains(github.ref, 'tags/v') + name: ${{ matrix.config.name }} + runs-on: ${{ matrix.config.os }} + strategy: + fail-fast: false + matrix: + config: + - { + name: "Windows Latest MSVC", artifact: "Windows-MSVC.tar.xz", + os: ubuntu-latest + } + - { + name: "Windows Latest MinGW", artifact: "Windows-MinGW.tar.xz", + os: ubuntu-latest + } + - { + name: "Ubuntu Latest GCC", artifact: "Linux.tar.xz", + os: ubuntu-latest + } + - { + name: "macOS Latest Clang", artifact: "macOS.tar.xz", + os: ubuntu-latest + } + needs: release + + steps: + - name: Download artifact + uses: actions/download-artifact@v1 + with: + name: ${{ matrix.config.artifact }} + path: ./ + + - name: Download URL + uses: actions/download-artifact@v1 + with: + name: upload_url + path: ./ + - id: set_upload_url + run: | + upload_url=`cat ./upload_url` + echo ::set-output name=upload_url::$upload_url + + - name: Upload to Release + id: upload_to_release + uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.set_upload_url.outputs.upload_url }} + asset_path: ./${{ matrix.config.artifact }} + asset_name: ${{ matrix.config.artifact }} + asset_content_type: application/x-gtar diff --git a/.gitmodules b/.gitmodules index 1be73af..bebd985 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "external/asmjit"] path = external/asmjit - url = git@github.com:asmjit/asmjit.git +url=https://github.com/asmjit/asmjit.git +[submodule "external/fmt"] + path = external/fmt + url = https://github.com/fmtlib/fmt.git +[submodule "external/capstone"] + path = external/capstone + url = https://github.com/aquynh/capstone.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 74f7551..aa6d200 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.13.4) project(AheuiJIT) +option(BUILD_CLI "Build CLI" ON) + set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(CMAKE_CXX_STANDARD 17) @@ -9,4 +11,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) add_subdirectory(external) add_subdirectory(src/AheuiJIT) -add_subdirectory(src/AheuiJIT/Sample) \ No newline at end of file + +if (BUILD_CLI) + add_subdirectory(src/AheuiJIT/Cli) +endif() \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..57e443f --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# 아희짓 + +## Brief English introduction + +AheuiJIT is a just in time compiler for [Aheui language](https://aheui.readthedocs.io/en/latest/specs.en.html) built from scratch. Its design is inspired by LLVM and dynarmic project. + +## 개요 + +아희짓은 [아희 언어](https://aheui.readthedocs.io/en/latest/introduction.html)를 위한 JIT (Just in Time) 컴파일러입니다. 어셈블러와 유틸 라이브러리외에 외부 라이브러리에 전혀 의존하지 않고 JIT을 바닥부터 구현합니다. + +## 지원 사양 + +현재 64비트 x86 운영체제 만 지원합니다. 제작자가 시간이 생긴다면 ARM도 지원할 예정입니다. + +## 사용법 + +릴리즈 페이즈에 가서 운영체제에 맞는 압축파일을 다운받고 압축을 푸세요. +``` +bin/AheuiJITCli 아희파일 +``` +로 아희 프로그램을 실행해볼 수 있습니다. + +inlcude와 lib폴더에는 정적 라이브러리로 빌드된 AheuiJIT이 있습니다. + +## 구현 완성도 + +![](tests.png) + +[아희 테스트 케이스](https://github.com/aheui/snippets) 중 64 비트 정수 출력을 제외한 모든 테스트를 통과합니다. + +## 성능 + +logo 아희 테스트를 0.29초 안에 완료합니다. 이는 [기존 비교표](http://xnuk.github.io/Comparison-of-aheui-implementations/) 기준 Algy/aheui-cc (0.11초)를 제외한 모든 구현체보다 월등히 빠른 속도입니다. + +``` +/usr/bin/time AheuiJITCli logo.aheui > out.out +0.29 real 0.29 user 0.00 sys +``` + +## 빌드 + +C++17을 지원하는 컴파일러와 cmake을 깔고 아래 명령어를 실행하면 됩니다. + +``` +git submodul update --init +mkdir build +cd buildㅌ +cmake .. +``` + +## TODOs + +현재 기본적인 최적화만 구현된 상태입니다. 이정도로도 엄청나게 빠르지만 더 빨라질 여지가 많습니다. + +### pop optimization + +연산자가 저장소의 모든 원소를 다 사용한 경우 반대 방향으로 이동해야하는 아희 명세를 구현하기 위해 저장소 pop 연산이 약간 비효율적으로 구현되어있습니다. 저장소 끝자락 페이지의 읽기 권한을 없애고 예외 헨들러를 달아서 저장소 잔여 공간 체크 루틴을 없애버릴 수 있습니다. GPU 드라이버가 텍스쳐 캐시를 구현할 때 자주 쓰는 기법입니다. 저장소 소진이 매우 자주 일어난다면 더 느리겠지만 이 경우 기존 체크 루틴을 사용하게 다시 컴파일 하는 식으로 대응할 수 있습니다. + +### instruction scheduling + +logo 테스트 같이 커다란 아희 프로그램에서 연산자를 좀더 잘 나열하면 메모리를 선형으로 참조 & 갱신하게 만들 수 있을 것 같습니다. 이러면 vectorization을 할 여지도 생깁니다. + +### inline basic block + +push와 pop을 매칭하는 peephole 최적화는 굉장히 효과적입니다. 이 최적화를 끄고 logo 테스트 케이스를 돌리면 15초씩이나 걸립니다. 이 최적화는 특성상 basic block 크기가 클수록 더 효과가 커지는데 여러 basic block을 합치는 식으로 효과를 더 증대할 수 있습니다. \ No newline at end of file diff --git a/design.md b/design.md deleted file mode 100644 index 78571b8..0000000 --- a/design.md +++ /dev/null @@ -1,14 +0,0 @@ -# IR - -``` -IRContext ctx; -Module* module = Module::Create(ctx, "main"); -Function* func = Function::Create(ctx, module, "main", { ctx.makeTypeInt(32), ctx.makeTypeInt(32) }); -BasicBlock* bb = BasicBlock::Create(ctx, func, "entry"); -IRBuilder builder(bb); -Value* arg0 = func->getArg(0); -Value* arg1 = func->getArg(1); -Value* sum = builder.createBinOp(OpCode::Add, arg0, arg1); -func->retValue(sum); - -``` \ No newline at end of file diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index b72a50e..4b9235a 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -1,3 +1,12 @@ SET(ASMJIT_STATIC ON CACHE BOOL "" FORCE) -add_subdirectory(asmjit) \ No newline at end of file +add_subdirectory(asmjit EXCLUDE_FROM_ALL) +add_subdirectory(fmt EXCLUDE_FROM_ALL) + +option(CAPSTONE_BUILD_SHARED "Build shared library" OFF) +option(CAPSTONE_BUILD_TESTS "Build tests" OFF) +option(CAPSTONE_BUILD_CSTOOL "Build cstool" OFF) +option(CAPSTONE_ARCHITECTURE_DEFAULT "Whether architectures are enabled by default" OFF) +option(CAPSTONE_X86_SUPPORT "x86 support" ON) +add_subdirectory(capstone EXCLUDE_FROM_ALL) +set(capstone_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/capstone/include" PARENT_SCOPE) \ No newline at end of file diff --git a/external/capstone b/external/capstone new file mode 160000 index 0000000..f9c6a90 --- /dev/null +++ b/external/capstone @@ -0,0 +1 @@ +Subproject commit f9c6a90489be7b3637ff1c7298e45efafe7cf1b9 diff --git a/external/fmt b/external/fmt new file mode 160000 index 0000000..cd4af11 --- /dev/null +++ b/external/fmt @@ -0,0 +1 @@ +Subproject commit cd4af11efc9c622896a3e4cb599fa28668ca3d05 diff --git a/format.sh b/format.sh new file mode 100755 index 0000000..69cfb9c --- /dev/null +++ b/format.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -ex + +find src \( -name *.cpp -o -name *.h \) | xargs clang-format -i \ No newline at end of file diff --git a/src/AheuiJIT/CMakeLists.txt b/src/AheuiJIT/CMakeLists.txt index 5449be7..816a53f 100644 --- a/src/AheuiJIT/CMakeLists.txt +++ b/src/AheuiJIT/CMakeLists.txt @@ -4,20 +4,44 @@ set(SOURCE_LIST IR/Builder.h IR/Builder.cpp IR/Storage.h + IR/Instruction.cpp IR/Instruction.h + IR/InstructionVisitor.h IR/Instruction.inc + IR/Location.h + IR/Location.cpp IR/Value.h IR/Value.cpp + IR/Pass/Pass.h + IR/Pass/EndpointPass.cpp + IR/Pass/AggregatePushPopPass.cpp + IR/Pass/ConstantFoldPass.cpp + Runtime/JITContext.h Runtime/Runtime.h Runtime/Runtime.cpp + Translator/Decoder.cpp Translator/Emitter.cpp Translator/Emitter.h Translator/RegAlloc.h Translator/RegAlloc.cpp + Translator/TokenOp.inc Translator/Translator.h Translator/Translator.cpp + Translator/Token.h + Util/Util.h + Util/Disasm.h + Util/Disasm.cpp ) add_library(AheuiJIT ${SOURCE_LIST}) target_include_directories(AheuiJIT PUBLIC ..) -source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCE_LIST}) \ No newline at end of file +target_include_directories(AheuiJIT PRIVATE ${capstone_INCLUDE_DIRS} asmjit fmt) +target_link_libraries(AheuiJIT PRIVATE asmjit fmt capstone-static) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCE_LIST}) + + +install (TARGETS AheuiJIT RUNTIME DESTINATION aheuijit) +INSTALL ( + DIRECTORY ${CMAKE_SOURCE_DIR}/src/ + DESTINATION include + FILES_MATCHING PATTERN "*.h*") \ No newline at end of file diff --git a/src/AheuiJIT/Cli/CMakeLists.txt b/src/AheuiJIT/Cli/CMakeLists.txt new file mode 100644 index 0000000..9940a29 --- /dev/null +++ b/src/AheuiJIT/Cli/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(AheuiJITCli main.cpp) +target_link_libraries(AheuiJITCli AheuiJIT) + +install (TARGETS AheuiJITCli RUNTIME DESTINATION bin) \ No newline at end of file diff --git a/src/AheuiJIT/Cli/main.cpp b/src/AheuiJIT/Cli/main.cpp new file mode 100644 index 0000000..c107399 --- /dev/null +++ b/src/AheuiJIT/Cli/main.cpp @@ -0,0 +1,28 @@ +#include + +#include +#include +#include +#include + +std::string readFile(const char* filename) { + std::ifstream wif(filename); + std::stringstream wss; + wss << wif.rdbuf(); + return wss.str(); +} + +int main(int argc, char* argv[]) { + std::ios_base::sync_with_stdio(false); + std::cin.tie(nullptr); + + if (argc < 2) { + std::cout << "Usage " << argv[0] << " filename" << std::endl; + return 1; + } + + aheuijit::Runtime rt(std::cout, std::cin); + std::string str = readFile(argv[1]); + std::u16string codecode = aheuijit::covnert_utf8_to_utf16(str); + return rt.run(codecode); +} diff --git a/src/AheuiJIT/IR/BasicBlock.cpp b/src/AheuiJIT/IR/BasicBlock.cpp index e69de29..773ed35 100644 --- a/src/AheuiJIT/IR/BasicBlock.cpp +++ b/src/AheuiJIT/IR/BasicBlock.cpp @@ -0,0 +1,53 @@ +#include "BasicBlock.h" + +#include + +using namespace aheuijit; + +BasicBlock::~BasicBlock() { + if (terminal) { + delete terminal; + } + for (auto inst : ownedInsts) { + delete inst; + } + for (auto val : ownedValues) { + delete val; + } +} + +void BasicBlock::ownValue(Value *value) { + ownedValues.push_back(value); +} + +void BasicBlock::ownInstruction(Instruction *inst) { + ownedInsts.push_back(inst); +} + +std::string BasicBlock::description() const { + std::stringstream ss; + InstructionFormatter formatter; + ss << fmt::format("BasicBlock(x: {}, y: {})", location.x, location.y) << std::endl; + for (auto inst : insts) { + ss << "\t" << formatter.format(inst) << std::endl; + } + ss << terminal->description() << std::endl; + return ss.str(); +} + +std::string ExitTerminal::description() const { + return "ExitTerminal()"; +} + +std::string LinkTerminal::description() const { + return fmt::format("LinkTerminal(BasicBlock(x: {}, y: {}))", block->location.x, + block->location.y); +} + +std::string ConditionalTerminal::description() const { + if (pass && fail) + return fmt::format("ConditionalTerminal({}, pass: ({}, {}), fail: ({}, {}))", + predicate->description(), pass->location.x, pass->location.y, + fail->location.x, fail->location.y); + return fmt::format("ConditionalTerminal({}, pass: nil, fail: nil)", predicate->description()); +} diff --git a/src/AheuiJIT/IR/BasicBlock.h b/src/AheuiJIT/IR/BasicBlock.h index 392ffd9..721c1a7 100644 --- a/src/AheuiJIT/IR/BasicBlock.h +++ b/src/AheuiJIT/IR/BasicBlock.h @@ -1,32 +1,81 @@ #pragma once #include +#include #include -#include + #include +#include +#include -using Location = int; +namespace aheuijit { -struct LinkTerminal { - LinkTerminal(Location location); -}; +struct BasicBlock; -struct ExitTerminal { - ExitTerminal(); -}; +enum class TerminalType { Link, Exit, Conditional }; -struct ConditionalTerminal { - ConditionalTerminal(Value* predicate, Location pass, Location fail); +struct Terminal { + Terminal() { + } + virtual ~Terminal() { + } + virtual std::string description() const = 0; + virtual TerminalType getType() const = 0; }; -using Terminal = std::variant; - struct BasicBlock { - BasicBlock(); - ~BasicBlock() = default; + explicit BasicBlock(uint64_t id) : id(id) { + } + BasicBlock() = delete; + ~BasicBlock(); + + std::string description() const; + + void ownValue(Value *value); + void ownInstruction(Instruction *inst); + uint64_t id; Location location; - std::list insts; - std::list values; - Terminal terminal; + std::list insts; + std::list values; + Terminal *terminal = nullptr; + + private: + std::list ownedInsts; + std::list ownedValues; +}; + +struct LinkTerminal : public Terminal { + explicit LinkTerminal(BasicBlock *block) : block(block) { + } + ~LinkTerminal() = default; + TerminalType getType() const override { + return TerminalType::Link; + } + std::string description() const override; + BasicBlock *block; +}; + +struct ExitTerminal : public Terminal { + ExitTerminal() = default; + ~ExitTerminal() = default; + TerminalType getType() const override { + return TerminalType::Exit; + } + std::string description() const override; +}; + +struct ConditionalTerminal : public Terminal { + explicit ConditionalTerminal(Value *predicate) : predicate(predicate) { + } + ~ConditionalTerminal() = default; + TerminalType getType() const override { + return TerminalType::Conditional; + } + Value *predicate; + std::string description() const override; + BasicBlock *pass = nullptr; + BasicBlock *fail = nullptr; }; + +} // namespace aheuijit diff --git a/src/AheuiJIT/IR/Builder.cpp b/src/AheuiJIT/IR/Builder.cpp index 39a984a..84ab891 100644 --- a/src/AheuiJIT/IR/Builder.cpp +++ b/src/AheuiJIT/IR/Builder.cpp @@ -1,17 +1,182 @@ #include "Builder.h" -Instruction* Builder::createInstruction(Opcode opcode) { - Instruction* inst = new Instruction(opcode); +using namespace aheuijit; + +Builder::Builder() { +} + +void Builder::setCurrentLocation(const Location &location) { + cur = location; +} + +// TODO: I can pool this resources +Local *Builder::createLocal() { + Local *value = new Local(nextId++); + currentBlock->values.push_back(value); + currentBlock->ownValue(value); + return value; +} + +Constant *Builder::createConstant(int imm) { + Constant *value = new Constant(imm); + currentBlock->values.push_back(value); + currentBlock->ownValue(value); + return value; +} + +Instruction *Builder::createInstruction(Opcode opcode) { + Instruction *inst = new Instruction(opcode, currentBlock->insts.size(), cur); currentBlock->insts.push_back(inst); + currentBlock->ownInstruction(inst); return inst; } -RegValue* Builder::add(Value* lhs, Value* rhs) { - RegValue* output = createLocal(); - Instruction* inst = createInstruction(Opcode::add); +void Builder::setBasicBlock(BasicBlock *bb) { + currentBlock = bb; +} + +void Builder::setLinkTerminal(BasicBlock *block) { + currentBlock->terminal = new LinkTerminal(block); +} + +void Builder::setExitTerminal() { + currentBlock->terminal = new ExitTerminal(); +} + +void Builder::setConditionalTerminal(Value *predicate) { + currentBlock->terminal = new ConditionalTerminal(predicate); +} + +Local *Builder::add(Value *lhs, Value *rhs) { + Local *output = createLocal(); + Instruction *inst = createInstruction(Opcode::add); + inst->args[0] = lhs; + inst->args[1] = rhs; + inst->output = output; + return output; +} + +Local *Builder::sub(Value *lhs, Value *rhs) { + Local *output = createLocal(); + Instruction *inst = createInstruction(Opcode::sub); + inst->args[0] = lhs; + inst->args[1] = rhs; + inst->output = output; + return output; +} + +Local *Builder::div(Value *lhs, Value *rhs) { + Local *output = createLocal(); + Instruction *inst = createInstruction(Opcode::div); + inst->args[0] = lhs; + inst->args[1] = rhs; + inst->output = output; + return output; +} + +Local *Builder::mul(Value *lhs, Value *rhs) { + Local *output = createLocal(); + Instruction *inst = createInstruction(Opcode::mul); + inst->args[0] = lhs; + inst->args[1] = rhs; + inst->output = output; + return output; +} + +Local *Builder::mod(Value *lhs, Value *rhs) { + Local *output = createLocal(); + Instruction *inst = createInstruction(Opcode::mod); inst->args[0] = lhs; inst->args[1] = rhs; inst->output = output; return output; } +Local *Builder::cmp(Value *lhs, Value *rhs) { + Local *output = createLocal(); + Instruction *inst = createInstruction(Opcode::cmp); + inst->args[0] = lhs; + inst->args[1] = rhs; + inst->output = output; + return output; +} + +void Builder::setStore(Value *value) { + Instruction *inst = createInstruction(Opcode::setStore); + inst->args[0] = value; +} + +Local *Builder::getStore(Void *) { + Local *output = createLocal(); + Instruction *inst = createInstruction(Opcode::getStore); + inst->output = output; + return output; +} + +Local *Builder::inputNum(Void *) { + Local *output = createLocal(); + Instruction *inst = createInstruction(Opcode::inputNum); + inst->output = output; + return output; +} + +Local *Builder::inputChar(Void *) { + Local *output = createLocal(); + Instruction *inst = createInstruction(Opcode::inputChar); + inst->output = output; + return output; +} + +void Builder::outputNum(Value *value) { + Instruction *inst = createInstruction(Opcode::outputNum); + inst->args[0] = value; +} + +void Builder::outputChar(Value *value) { + Instruction *inst = createInstruction(Opcode::outputChar); + inst->args[0] = value; +} + +Local *Builder::pop(bool queue) { + if (queue) { + return popQueue(nullptr); + } + return popStack(nullptr); +} + +void Builder::push(bool queue, Value *value) { + if (queue) { + pushQueueBack(value); + } else { + pushStack(value); + } +} + +void Builder::pushStack(Value *value) { + Instruction *inst = createInstruction(Opcode::pushStack); + inst->args[0] = value; +} + +void Builder::pushQueueBack(Value *value) { + Instruction *inst = createInstruction(Opcode::pushQueueBack); + inst->args[0] = value; +} + +void Builder::pushQueueFront(Value *value) { + Instruction *inst = createInstruction(Opcode::pushQueueFront); + inst->args[0] = value; +} + +Local *Builder::popStack(Void *) { + Local *output = createLocal(); + Instruction *inst = createInstruction(Opcode::popStack); + inst->output = output; + return output; +} + +Local *Builder::popQueue(Void *) { + Local *output = createLocal(); + Instruction *inst = createInstruction(Opcode::popQueue); + inst->output = output; + return output; +} diff --git a/src/AheuiJIT/IR/Builder.h b/src/AheuiJIT/IR/Builder.h index 0007978..1542f3f 100644 --- a/src/AheuiJIT/IR/Builder.h +++ b/src/AheuiJIT/IR/Builder.h @@ -1,15 +1,38 @@ +#pragma once + #include -#include #include +#include +#include + +#include + +namespace aheuijit { struct Builder { - BasicBlock* currentBlock; - ConstantValue* createConstant(int imm); - RegValue* createLocal(); - Instruction* createInstruction(Opcode opcode); - - #define INST(name, ret, ...) ret name(__VA_ARGS__); - #include "Instruction.inc" - #undef INST + Builder(); + ~Builder() = default; + + void setBasicBlock(BasicBlock *bb); + + Constant *createConstant(int imm); + Local *createLocal(); + Instruction *createInstruction(Opcode opcode); + void setCurrentLocation(const Location &location); + void setLinkTerminal(BasicBlock *block); + void setExitTerminal(); + void setConditionalTerminal(Value *predicate); + + Local *pop(bool queue); + void push(bool queue, Value *value); +#define INST(name, ret, ...) ret name(__VA_ARGS__); +#include "Instruction.inc" +#undef INST + + private: + BasicBlock *currentBlock; + Location cur; + uint64_t nextId{ 1 }; }; +} // namespace aheuijit diff --git a/src/AheuiJIT/IR/Instruction.cpp b/src/AheuiJIT/IR/Instruction.cpp new file mode 100644 index 0000000..f5ebda2 --- /dev/null +++ b/src/AheuiJIT/IR/Instruction.cpp @@ -0,0 +1,95 @@ +#include "Instruction.h" + +#include +#include + +#include + +using namespace aheuijit; + +Instruction::Instruction(Opcode op, uint64_t offset, Location location) + : opcode(op), offset(offset), location(location) { + args.fill(0); +} + +std::string InstructionFormatter::format(Instruction *inst) { + static InstructionVisitorCaller caller; + this->inst = inst; + ss.str(""); + caller.call(*this, inst); + return ss.str(); +} + +void InstructionFormatter::add(Value *lhs, Value *rhs) { + ss << fmt::format("{} = Add({}, {})", inst->output->description(), lhs->description(), + rhs->description()); +} + +void InstructionFormatter::sub(Value *lhs, Value *rhs) { + ss << fmt::format("{} = Sub({}, {})", inst->output->description(), lhs->description(), + rhs->description()); +} + +void InstructionFormatter::div(Value *lhs, Value *rhs) { + ss << fmt::format("{} = Div({}, {})", inst->output->description(), lhs->description(), + rhs->description()); +} + +void InstructionFormatter::mul(Value *lhs, Value *rhs) { + ss << fmt::format("{} = Mul({}, {})", inst->output->description(), lhs->description(), + rhs->description()); +} + +void InstructionFormatter::mod(Value *lhs, Value *rhs) { + ss << fmt::format("{} = Mod({}, {})", inst->output->description(), lhs->description(), + rhs->description()); +} + +void InstructionFormatter::pushStack(Value *value) { + ss << fmt::format("PushStack({})", value->description()); +} + +void InstructionFormatter::popStack(Void *) { + ss << fmt::format("{} = PopStack()", inst->output->description()); +} + +void InstructionFormatter::pushQueueBack(Value *value) { + ss << fmt::format("pushQueueBack({})", value->description()); +} + +void InstructionFormatter::pushQueueFront(Value *value) { + ss << fmt::format("pushQueueFront({})", value->description()); +} + +void InstructionFormatter::popQueue(Void *) { + ss << fmt::format("{} = PopQueue()", inst->output->description()); +} + +void InstructionFormatter::inputChar(Void *) { + ss << fmt::format("{} = InputChar()", inst->output->description()); +} + +void InstructionFormatter::inputNum(Void *) { + ss << fmt::format("{} = InputNum()", inst->output->description()); +} + +void InstructionFormatter::cmp(Value *lhs, Value *rhs) { + ss << fmt::format("{} = Cmp({}, {})", inst->output->description(), lhs->description(), + rhs->description()); +} + +void InstructionFormatter::outputChar(Value *value) { + ss << fmt::format("OutputChar({})", value->description()); +} + +void InstructionFormatter::outputNum(Value *value) { + ss << fmt::format("OutputNum({})", value->description()); +} + +void InstructionFormatter::getStore(Void *) { + ss << fmt::format("{} = GetStore()", inst->output->description()); +} + +void InstructionFormatter::setStore(Value *value) { + ss << fmt::format("SetStore({})", value->description()); +} diff --git a/src/AheuiJIT/IR/Instruction.h b/src/AheuiJIT/IR/Instruction.h index 17142a0..bb8b101 100644 --- a/src/AheuiJIT/IR/Instruction.h +++ b/src/AheuiJIT/IR/Instruction.h @@ -1,24 +1,44 @@ #pragma once +#include #include + #include +#include +#include #include +namespace aheuijit { + #define INST(name, ret, ...) name, enum class Opcode { - #include +#include "Instruction.inc" }; #undef INST constexpr int MAX_INST_ARGUMENTS = 2; struct Instruction { - Instruction(Opcode opcode); + Instruction(Opcode opcode, uint64_t offset, Location location); ~Instruction() = default; Opcode opcode; - RegValue* output; - std::array args; + Location location; + Local *output = nullptr; + std::array args; + uint64_t offset; }; +struct InstructionFormatter { + std::string format(Instruction *inst); + +#define INST(name, ret, ...) void name(__VA_ARGS__); +#include "Instruction.inc" +#undef INST + + private: + std::stringstream ss; + Instruction *inst; +}; +} // namespace aheuijit diff --git a/src/AheuiJIT/IR/Instruction.inc b/src/AheuiJIT/IR/Instruction.inc index e5abd06..bece8eb 100644 --- a/src/AheuiJIT/IR/Instruction.inc +++ b/src/AheuiJIT/IR/Instruction.inc @@ -1,10 +1,17 @@ -INST(add, RegValue*, Value*, Value*) -INST(sub, RegValue*, Value*, Value*) -INST(div, RegValue*, Value*, Value*) -INST(mod, RegValue*, Value*, Value*) -INST(cmp, RegValue*, Value*, Value*) -INST(pop, RegValue*) -INST(push, void, Value*) -INST(selectStore, void, StorageValue*) -INST(input, RegValue*) -INST(output, RegValue*) +INST(add, Local*, Value*, Value*) +INST(sub, Local*, Value*, Value*) +INST(mul, Local*, Value*, Value*) +INST(div, Local*, Value*, Value*) +INST(mod, Local*, Value*, Value*) +INST(cmp, Local*, Value*, Value*) +INST(popStack, Local*, Void*) +INST(pushStack, void, Value*) +INST(popQueue, Local*, Void*) +INST(pushQueueFront, void, Value*) +INST(pushQueueBack, void, Value*) +INST(getStore, Local*, Void*) +INST(setStore, void, Value*) +INST(inputNum, Local*, Void*) +INST(inputChar, Local*, Void*) +INST(outputNum, void, Value*) +INST(outputChar, void, Value*) diff --git a/src/AheuiJIT/IR/InstructionVisitor.h b/src/AheuiJIT/IR/InstructionVisitor.h new file mode 100644 index 0000000..710e010 --- /dev/null +++ b/src/AheuiJIT/IR/InstructionVisitor.h @@ -0,0 +1,47 @@ +#pragma once +#include + +namespace aheuijit { + +template +void callVisitorImpl(T &obj, void (T::*mf)(Args...), const std::array &args, + std::index_sequence) { + return (obj.*mf)(args[I]...); +} + +template +auto callVisitor(T &obj, const std::array &args) { + callVisitorImpl(obj, mf, args, std::make_index_sequence{}); +} + +template +struct SizeGetter { + constexpr static size_t size = sizeof...(Args); +}; + +template +struct InstructionVisitorCaller { + void call(Impl &impl, Instruction *inst) { + switch (inst->opcode) { +#define INST(name, ret, ...) \ + case Opcode::name: \ + _call##name(impl, inst); \ + break; +#include "Instruction.inc" +#undef INST + } + } + +#define INST(name, retr, ...) \ + void _call##name(Impl &impl, Instruction *inst) { \ + std::array::size> tmp; \ + for (int i = 0; i < SizeGetter<__VA_ARGS__>::size; ++i) { \ + tmp[i] = inst->args[i]; \ + } \ + callVisitor::size>(impl, tmp); \ + } +#include "Instruction.inc" +#undef INST +}; + +} // namespace aheuijit diff --git a/src/AheuiJIT/IR/Location.cpp b/src/AheuiJIT/IR/Location.cpp new file mode 100644 index 0000000..ad7b860 --- /dev/null +++ b/src/AheuiJIT/IR/Location.cpp @@ -0,0 +1,77 @@ +#include "Location.h" + +#include + +using namespace aheuijit; + +inline uint64_t hash(uint64_t key) { + uint64_t v = key * 3935559000370003845 + 2691343689449507681; + + v ^= v >> 21; + v ^= v << 37; + v ^= v >> 4; + + v *= 4768777513237032717; + + v ^= v << 20; + v ^= v >> 41; + v ^= v << 5; + + return v; +} + +bool Pointer::isVertical() const { + return vy != 0; +} + +bool Pointer::isHorizontal() const { + return vx != 0; +} + +void Pointer::flip() { + vx *= -1; + vy *= -1; +} + +bool Pointer::operator<(const Pointer &other) const { + if (queue == other.queue) { + if (vx == other.vx) { + return vy < other.vy; + } + return vx < other.vx; + } + return queue < other.queue; +} + +bool Pointer::operator==(const Pointer &other) const { + return queue == other.queue && vx == other.vx && vy == other.vy; +} + +uint64_t Location::hash() const { + return ::hash(((uint64_t)x & 0xFFFFFF) | (((uint64_t)y & 0xFFFFFF) << 24) | + ((uint64_t)pointer.queue << 48) | ((uint64_t)(pointer.vx + 2) << 52) | + ((uint64_t)(pointer.vy + 2) << 56)); +} + +bool Location::operator<(const Location &other) const { + if (x == other.x) { + if (y == other.y) { + return pointer < other.pointer; + } + return y < other.y; + } + return x < other.x; +} + +bool Location::operator==(const Location &other) const { + return x == other.x && y == other.y && pointer == other.pointer; +} + +bool Location::operator!=(const Location &other) const { + return !(*this == other); +} + +std::string Location::description() const { + return fmt::format("(x: {}, y: {}, queue: {}, vx: {}, vy: {})", x, y, pointer.queue, pointer.vx, + pointer.vy); +} diff --git a/src/AheuiJIT/IR/Location.h b/src/AheuiJIT/IR/Location.h new file mode 100644 index 0000000..c1d7808 --- /dev/null +++ b/src/AheuiJIT/IR/Location.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +namespace aheuijit { + +struct Pointer { + Pointer() = default; + explicit Pointer(bool queue, int vx, int vy) : queue(queue), vx(vx), vy(vy) { + } + ~Pointer() = default; + int queue{ false }; + int vx{ 0 }; + int vy{ 0 }; + + bool isVertical() const; + bool isHorizontal() const; + + void flip(); + bool operator<(const Pointer &other) const; + bool operator==(const Pointer &other) const; +}; + +struct Location { + explicit Location(int x, int y, bool queue, int vx, int vy) + : x(x), y(y), pointer(queue, vx, vy) { + } + Location() = default; + ~Location() = default; + int x{ 0 }; + int y{ 0 }; + Pointer pointer; + + uint64_t hash() const; + + bool operator<(const Location &other) const; + bool operator==(const Location &other) const; + bool operator!=(const Location &other) const; + std::string description() const; +}; + +static_assert(sizeof(Location) == 20); + +static const Location DEFAULT_LOCATION = Location(0, 0, false, 0, 1); + +} // namespace aheuijit diff --git a/src/AheuiJIT/IR/Pass/AggregatePushPopPass.cpp b/src/AheuiJIT/IR/Pass/AggregatePushPopPass.cpp new file mode 100644 index 0000000..d738e48 --- /dev/null +++ b/src/AheuiJIT/IR/Pass/AggregatePushPopPass.cpp @@ -0,0 +1,198 @@ +#include + +#include +#include + +#include "Pass.h" + +using namespace aheuijit; + +// push x 0 - +// push x 1 * +// pop x 1 * +// pop x 0 - +// pop x -1 ignore +// pop x -2 ignore +// push x 0 - +// pop x 0 - +// Match pushStack and popStack to same storage + +bool AggregatePushPopPass::runOnBlock(Builder& b, BasicBlock* block) { + struct TrackedPush { + int offset; + Value* value; + }; + std::array counts; // 26: current storage + int trackedStorage = 26; + std::set removeInstOffsets; + std::map patchValues; + std::map pushes; + std::map popCounts; + std::map removeOffsetCounterpart; + + const auto getKey = [](uint32_t count, uint32_t storage) { + return ((uint64_t)count << 32) | (uint64_t)storage; + }; + + counts.fill(0); + + Value* getStorageMatch = nullptr; + int getStorageRes = 0; + for (auto inst : block->insts) { + switch (inst->opcode) { + case Opcode::setStore: { + // setStore(getStore()) + if (getStorageMatch && inst->args[0]->type() == ValueType::Local) { + trackedStorage = getStorageRes; + getStorageMatch = nullptr; + } else { + const uint32_t newStorage = dynamic_cast(inst->args[0])->imm; + if (newStorage != 0) // is not queue + trackedStorage = newStorage - 1; + } + ASSERT(trackedStorage < 27) + break; + } + case Opcode::getStore: { + getStorageRes = trackedStorage; + getStorageMatch = inst->output; + break; + } + case Opcode::pushStack: { + TrackedPush tp; + tp.offset = inst->offset; + tp.value = inst->args[0]; + const auto it = patchValues.find(tp.value); + if (it != patchValues.end()) { + tp.value = it->second; + } + const uint64_t key = getKey(counts[trackedStorage]++, trackedStorage); + pushes[key] = tp; + break; + } + case Opcode::popStack: { + ++popCounts[inst->location]; + if (counts[trackedStorage] == 0) { + break; + } + --popCounts[inst->location]; + const uint64_t key = getKey(--counts[trackedStorage], trackedStorage); + const TrackedPush tp = pushes[key]; + ASSERT(tp.value) + removeInstOffsets.insert(tp.offset); + removeInstOffsets.insert(inst->offset); + removeOffsetCounterpart.emplace(tp.offset, inst->location); + patchValues.emplace(inst->output, tp.value); + pushes.erase(key); + break; + } + } + } + + for (auto it = block->insts.begin(); it != block->insts.end();) { + Instruction* inst = *it; + if (removeInstOffsets.find(inst->offset) != removeInstOffsets.end()) { + if (inst->opcode == Opcode::popStack && popCounts[inst->location] == 0) { + block->insts.erase(it++); + removeInstOffsets.erase(inst->offset); + continue; + } else if (inst->opcode == Opcode::pushStack && + popCounts[removeOffsetCounterpart[inst->offset]] == 0) { + block->insts.erase(it++); + removeInstOffsets.erase(inst->offset); + continue; + } + } + + for (int i = 0; i < inst->args.size(); ++i) { + if (inst->args[i] && inst->args[i]->type() == ValueType::Local) { + const auto it = patchValues.find(inst->args[i]); + if (it != patchValues.end()) { + inst->args[i] = it->second; + } + } + } + ++it; + } + + if (block->terminal->getType() == TerminalType::Conditional) { + ConditionalTerminal* term = dynamic_cast(block->terminal); + const auto it = patchValues.find(term->predicate); + if (it != patchValues.end()) { + term->predicate = it->second; + } + } + return true; +} + +bool RemoveDeadStoreUpdate::runOnBlock(Builder& b, BasicBlock* block) { + auto storeIt = block->insts.end(); + int count = 0; + std::map getStoreQuery; + for (auto it = block->insts.begin(); it != block->insts.end(); ++it) { + Instruction* inst = *it; + if (inst->opcode == Opcode::setStore) { + if (storeIt != block->insts.end()) { + Instruction* sinst = *storeIt; + Value* arg0 = sinst->args[0]; + if (count == 0) { + block->insts.erase(storeIt); + } else if (arg0->type() == ValueType::Local) { + sinst->args[0] = getStoreQuery[arg0]; + } + } + count = 0; + storeIt = it; + continue; + } + switch (inst->opcode) { + case Opcode::pushStack: + case Opcode::pushQueueBack: + case Opcode::pushQueueFront: + case Opcode::popQueue: + case Opcode::popStack: + ++count; + break; + case Opcode::getStore: { + // setStore constant folding to fight getStore() corruption happen because of + // removed setStores Based on the fact setStore(x) x is either getStore() or imm + // setStore(local) is what's making things complicated + // + // Starting from i = 0, + // Fist local = getStore() value is resolved absolutely (either actual getStore() + // value or setStore(imm)) second getStore() value is from setStore(local) or + // setStore(imm) only available local = first local -> resvoled third getStore() + // value is from setStore(local) or setStore(local2) or setStore(imm) + // -> resolved -> so on + // + if (storeIt == block->insts.end()) { + // First getStorage query + getStoreQuery.emplace(inst->output, inst->output); + break; + } + if ((*storeIt)->args[0]->type() == ValueType::Constant) { + getStoreQuery.emplace(inst->output, (*storeIt)->args[0]); + } else { // Local + Local* getStoreRes = dynamic_cast((*storeIt)->args[0]); + const auto it = getStoreQuery.find(getStoreRes); + ASSERT(it != getStoreQuery.end()) // This should not assert; it means there's + // no avaiable local value. + getStoreQuery.emplace(inst->output, it->second); + } + break; + } + default: + break; + } + } + + // I shouldn't delete last setStore but must constand fold it + if (storeIt != block->insts.end()) { + Instruction* sinst = *storeIt; + Value* arg0 = sinst->args[0]; + if (arg0->type() == ValueType::Local) { + sinst->args[0] = getStoreQuery[arg0]; + } + } + return true; +} diff --git a/src/AheuiJIT/IR/Pass/ConstantFoldPass.cpp b/src/AheuiJIT/IR/Pass/ConstantFoldPass.cpp new file mode 100644 index 0000000..d7238da --- /dev/null +++ b/src/AheuiJIT/IR/Pass/ConstantFoldPass.cpp @@ -0,0 +1,73 @@ +#include + +#include +#include + +#include "Pass.h" + +using namespace aheuijit; + +bool ConstantFoldPass::runOnBlock(Builder& b, BasicBlock* block) { + std::map foldedValues; + + const auto doALU = [](Opcode op, int64_t lhs, int64_t rhs) { + switch (op) { + case Opcode::add: + return lhs + rhs; + case Opcode::sub: + return lhs - rhs; + case Opcode::mul: + return lhs * rhs; + case Opcode::mod: + return lhs % rhs; + case Opcode::div: + return lhs / rhs; + case Opcode::cmp: + return (Word)(lhs >= rhs); + default: + ASSERT(false) + } + }; + + for (auto it = block->insts.begin(); it != block->insts.end();) { + Instruction* inst = *it; + for (int i = 0; i < inst->args.size(); ++i) { + if (inst->args[i] && inst->args[i]->type() == ValueType::Local) { + const auto it = foldedValues.find(inst->args[i]); + if (it != foldedValues.end()) { + inst->args[i] = it->second; + } + } + } + switch (inst->opcode) { + case Opcode::add: + case Opcode::sub: + case Opcode::mul: + case Opcode::mod: + case Opcode::div: + case Opcode::cmp: { + if (inst->args[0]->type() == ValueType::Constant && + inst->args[1]->type() == ValueType::Constant) { + Constant* lhs = dynamic_cast(inst->args[0]); + Constant* rhs = dynamic_cast(inst->args[1]); + lhs->imm = doALU(inst->opcode, lhs->imm, rhs->imm); + foldedValues.emplace(inst->output, lhs); + block->insts.erase(it++); + } + break; + } + default: + break; + } + ++it; + } + + if (block->terminal->getType() == TerminalType::Conditional) { + ConditionalTerminal* term = dynamic_cast(block->terminal); + const auto it = foldedValues.find(term->predicate); + if (it != foldedValues.end()) { + term->predicate = it->second; + } + } + return true; +} diff --git a/src/AheuiJIT/IR/Pass/EndpointPass.cpp b/src/AheuiJIT/IR/Pass/EndpointPass.cpp new file mode 100644 index 0000000..1dd042c --- /dev/null +++ b/src/AheuiJIT/IR/Pass/EndpointPass.cpp @@ -0,0 +1,50 @@ +#include + +#include "Pass.h" + +using namespace aheuijit; + +bool EndpointPass::runOnBlock(Builder& b, BasicBlock* block) { + for (auto inst : block->insts) { + for (int i = 0; i < inst->args.size(); ++i) { + Value* value = inst->args[i]; + if (value && value->type() == ValueType::Local) { + dynamic_cast(value)->endpoint = inst->offset; + } + } + } + return true; +} + +static bool isNoSideEffectOp(Opcode op) { + switch (op) { + case Opcode::add: + case Opcode::sub: + case Opcode::div: + case Opcode::mod: + case Opcode::cmp: + case Opcode::mul: + case Opcode::getStore: + return true; + default: + return false; + } +} + +bool RemoveDeadAssignmentPass::runOnBlock(Builder& b, BasicBlock* block) { + Value* pred = nullptr; + if (block->terminal->getType() == TerminalType::Conditional) { + ConditionalTerminal* term = dynamic_cast(block->terminal); + pred = term->predicate; + } + for (auto it = block->insts.begin(); it != block->insts.end();) { + const Instruction* inst = *it; + const Local* output = inst->output; + if (output && !output->endpoint && isNoSideEffectOp(inst->opcode) && output != pred) { + block->insts.erase(it++); + continue; + } + ++it; + } + return true; +} diff --git a/src/AheuiJIT/IR/Pass/Pass.h b/src/AheuiJIT/IR/Pass/Pass.h new file mode 100644 index 0000000..72f249e --- /dev/null +++ b/src/AheuiJIT/IR/Pass/Pass.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +namespace aheuijit { + +struct Pass { + Pass() { + } + virtual ~Pass() { + } +}; + +struct BasicBlockPass : public Pass { + virtual ~BasicBlockPass() { + } + virtual bool runOnBlock(Builder& b, BasicBlock* block) = 0; +}; + +struct AggregatePushPopPass : public BasicBlockPass { + ~AggregatePushPopPass() { + } + bool runOnBlock(Builder& b, BasicBlock* block) override; +}; + +struct RemoveDeadStoreUpdate : public BasicBlockPass { + ~RemoveDeadStoreUpdate() { + } + bool runOnBlock(Builder& b, BasicBlock* block) override; +}; + +struct EndpointPass : public BasicBlockPass { + ~EndpointPass() { + } + bool runOnBlock(Builder& b, BasicBlock* block) override; +}; + +struct RemoveDeadAssignmentPass : public BasicBlockPass { + ~RemoveDeadAssignmentPass() { + } + bool runOnBlock(Builder& b, BasicBlock* block) override; +}; + +struct ConstantFoldPass : public BasicBlockPass { + ~ConstantFoldPass() { + } + bool runOnBlock(Builder& b, BasicBlock* block) override; +}; + +} // namespace aheuijit diff --git a/src/AheuiJIT/IR/Storage.h b/src/AheuiJIT/IR/Storage.h index 642a276..8da6f2d 100644 --- a/src/AheuiJIT/IR/Storage.h +++ b/src/AheuiJIT/IR/Storage.h @@ -1,4 +1,19 @@ +#pragma once -enum class Storage { - A +namespace aheuijit { + +struct Storage { + explicit Storage(bool stack, int num) : stack(stack), num(num) { + } + Storage() = default; + ~Storage() = default; + + int toImm() const { + return !stack ? 0 : num + 1; + } + + bool stack{ false }; + int num{ 0 }; }; + +} // namespace aheuijit diff --git a/src/AheuiJIT/IR/Value.cpp b/src/AheuiJIT/IR/Value.cpp index e69de29..af0e825 100644 --- a/src/AheuiJIT/IR/Value.cpp +++ b/src/AheuiJIT/IR/Value.cpp @@ -0,0 +1,21 @@ +#include "Value.h" + +#include + +using namespace aheuijit; + +ValueType Local::type() const { + return ValueType::Local; +} + +std::string Local::description() const { + return fmt::format("%{}", id); +} + +std::string Constant::description() const { + return fmt::format("#{}", imm); +} + +ValueType Constant::type() const { + return ValueType::Constant; +} diff --git a/src/AheuiJIT/IR/Value.h b/src/AheuiJIT/IR/Value.h index 5c5597e..459e66a 100644 --- a/src/AheuiJIT/IR/Value.h +++ b/src/AheuiJIT/IR/Value.h @@ -1,37 +1,56 @@ #pragma once -#include #include +#include +#include +#include +#include +#include + +namespace aheuijit { + +using Word = int64_t; + +enum class ValueType { Local, Constant, Void }; + struct Value { - Value(); - virtual ~Value(); + Value() { + } + virtual ~Value() { + } + virtual std::string description() const = 0; + virtual ValueType type() const { + return ValueType::Void; + } }; -struct RegValue : public Value { - explicit RegValue(uint64_t id); - ~RegValue() = default; +using Void = Value; + +struct Local : public Value { + Local() : id(0) { + } + ~Local() = default; + explicit Local(uint64_t id) : id(id) { + } + std::string description() const override; - uint64_t getID() const; + ValueType type() const override; - void setEndpoint(size_t endpoint); - size_t getEndpoint() const; -private: uint64_t id; - size_t endpoint; + std::optional endpoint = std::nullopt; }; -struct ConstantValue : public Value { - ConstantValue(uint64_t value); - ~ConstantValue() = default; +struct Constant : public Value { + Constant() : imm(0) { + } + ~Constant() = default; + explicit Constant(Word imm) : imm(imm) { + } + std::string description() const override; - uint64_t get(); -private: - uint64_t value; -}; + ValueType type() const override; -struct StorageValue : public Value { - StorageValue(Storage store); - ~StorageValue() = default; -private: - Storage store; + Word imm; }; + +} // namespace aheuijit diff --git a/src/AheuiJIT/Runtime/JITContext.h b/src/AheuiJIT/Runtime/JITContext.h new file mode 100644 index 0000000..62f017d --- /dev/null +++ b/src/AheuiJIT/Runtime/JITContext.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace aheuijit { + +struct Runtime; + +constexpr int HASH_TABLE_BEANS = 128; +constexpr int HASH_TABLE_BEAN_SIZE = 32; +constexpr int HAST_TABLE_BEAN_SHIFT = 9; +struct JITHashTableKeyValue { + uint64_t key; + uint64_t value; +}; + +struct JITHashTable { + JITHashTable() { + beans.fill({ 0, 0 }); + } + ~JITHashTable() = default; + std::array beans; + void setValue(uint64_t key, uint64_t value) { + const size_t bean = key % HASH_TABLE_BEANS; + const size_t beanOffset = bean * HASH_TABLE_BEAN_SIZE; + for (size_t i = 0; i < HASH_TABLE_BEAN_SIZE - 1; ++i) { + auto& kv = beans[beanOffset + i]; + ASSERT(kv.key != key) + if (kv.key == 0 && kv.value == 0) { + kv.key = key; + kv.value = value; + return; + } + } + ASSERT(false) + } +}; + +struct JITContext { + JITContext() { + storageBuffer.fill(0); + stackFronts.fill(0); + }; + ~JITContext() = default; + + Runtime* runtime{ nullptr }; + Location location; + int storage{ 0 }; + std::array stackFronts; + uint64_t queueFront{ 0 }; + uint64_t queueBack{ 0 }; + uint64_t queueCursor{ 0 }; + std::array storageBuffer; // [0] = queueTop + JITHashTable* exhaustPatchTable{ nullptr }; +}; + +} // namespace aheuijit diff --git a/src/AheuiJIT/Runtime/Runtime.cpp b/src/AheuiJIT/Runtime/Runtime.cpp index d28229a..5c07268 100644 --- a/src/AheuiJIT/Runtime/Runtime.cpp +++ b/src/AheuiJIT/Runtime/Runtime.cpp @@ -1,4 +1,128 @@ +#include "Runtime.h" -struct Runtime { +#include -}; \ No newline at end of file +#include +#include + +using namespace aheuijit; + +Runtime::Runtime(std::ostream& out, std::istream& in) + : ctx(std::make_unique()), translator(*this), out(out), in(in) { + storages.fill(0); +} + +Word Runtime::run(const std::u16string& code) { + resetState(); + + TranslatedFunc func = translator.translate(code, tlbTable); + do { + ctx->location = Location(); + func(ctx.get()); + if (ctx->location != Location()) { + // JIT translation requested + LOG("Retranslate at location: {}", ctx->location.description()); + const Location failLocation = translator.calculateFailLocation(ctx->location); + const Location validFailLocation = translator.stepToValidLocation(failLocation); + const auto it = entryTlbTable.find(validFailLocation); + if (it != entryTlbTable.end()) { + func = reinterpret_cast(it->second); + } else { + func = translator.translate(validFailLocation, tlbTable); + entryTlbTable.emplace(validFailLocation, func); + } + ASSERT(tlbTable.find(validFailLocation) != tlbTable.end()) + ctx->exhaustPatchTable->setValue(ctx->location.hash(), tlbTable[validFailLocation]); + } + } while (ctx->location != Location()); + + if (ctx->storage == 0) { + if (ctx->storageBuffer[0] != ctx->queueCursor) { + return *reinterpret_cast(ctx->queueCursor); + } + } else { + if (ctx->storageBuffer[ctx->storage] != ctx->stackFronts[ctx->storage]) { + return *reinterpret_cast(ctx->storageBuffer[ctx->storage]); + } + } + return 0; +} + +void Runtime::resetState() { + for (int i = 0; i < storages.size(); ++i) { + if (storages[i]) { + delete[] storages[i]; + } + storages[i] = new uint64_t[MAX_STORAGE_SIZE]; + } + if (ctx->exhaustPatchTable) { + delete ctx->exhaustPatchTable; + } + ctx->exhaustPatchTable = new JITHashTable; + ctx->runtime = this; + ctx->queueBack = reinterpret_cast(storages[0]); + ctx->queueFront = reinterpret_cast(storages[0]) + 8 * MAX_STORAGE_SIZE; + ctx->queueCursor = reinterpret_cast(storages[0]) + (8 * MAX_STORAGE_SIZE / 2); + ctx->storageBuffer[0] = reinterpret_cast(storages[0]) + (8 * MAX_STORAGE_SIZE / 2); + ctx->location = Location(); + for (int i = 1; i < storages.size(); ++i) { + ctx->storageBuffer[i] = reinterpret_cast(storages[i]) + 8 * MAX_STORAGE_SIZE; + ctx->stackFronts[i] = ctx->storageBuffer[i]; + } + ctx->storage = 1; + tlbTable.clear(); + entryTlbTable.clear(); +} + +void Runtime::printNum(Word word) { + out << word; +} + +Word Runtime::inputNum() { + Word word; + std::cin >> word; + return word; +} + +// https://github.com/aheui/caheui/blob/master/aheui.c#L113 + +inline Word readOneChar(std::istream& in) { + char ch; + in.get(ch); + return (unsigned char)ch; +} + +Word Runtime::inputChar() { + Word a = readOneChar(in); + + if (a < 0x80) { + return a; + } else if ((a & 0xf0) == 0xf0) { + return ((a & 0x07) << 18) + ((readOneChar(in) & 0x3f) << 12) + + ((readOneChar(in) & 0x3f) << 6) + (readOneChar(in) & 0x3f); + } else if ((a & 0xe0) == 0xe0) { + return ((a & 0x0f) << 12) + ((readOneChar(in) & 0x3f) << 6) + (readOneChar(in) & 0x3f); + } else if ((a & 0xc0) == 0xc0) { + return ((a & 0x1f) << 6) + (readOneChar(in) & 0x3f); + } else { + return -1; + } +} + +void Runtime::printChar(Word word) { + if (word < 0x80) { + out << (char)word; + } else if (word < 0x0800) { + out << (char)(0xc0 | (word >> 6)); + out << (char)(0x80 | ((word >> 0) & 0x3f)); + } else if (word < 0x10000) { + out << (char)(0xe0 | (word >> 12)); + out << (char)(0x80 | ((word >> 6) & 0x3f)); + out << (char)(0x80 | ((word >> 0) & 0x3f)); + } else if (word < 0x110000) { + out << (char)(0xf0 | (word >> 18)); + out << (char)(0x80 | ((word >> 12) & 0x3f)); + out << (char)(0x80 | ((word >> 6) & 0x3f)); + out << (char)(0x80 | ((word >> 0) & 0x3f)); + } +} diff --git a/src/AheuiJIT/Runtime/Runtime.h b/src/AheuiJIT/Runtime/Runtime.h index e69de29..447df96 100644 --- a/src/AheuiJIT/Runtime/Runtime.h +++ b/src/AheuiJIT/Runtime/Runtime.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include + +namespace aheuijit { + +constexpr int MAX_STORAGE_SIZE = 2048; + +struct Runtime { + Runtime() = delete; + explicit Runtime(std::ostream& out, std::istream& in); + ~Runtime() = default; + + Word run(const std::u16string& code); + void printChar(Word word); + void printNum(Word word); + Word inputChar(); + Word inputNum(); + + private: + void resetState(); + void fixupStorage(); + + Translator translator; + std::unique_ptr ctx; + std::map entryTlbTable; + TLBTable tlbTable; + std::ostream& out; + std::istream& in; + std::array storages; +}; + +} // namespace aheuijit diff --git a/src/AheuiJIT/Sample/CMakeLists.txt b/src/AheuiJIT/Sample/CMakeLists.txt deleted file mode 100644 index e4af853..0000000 --- a/src/AheuiJIT/Sample/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_executable(AheuiJITSample main.cpp) -target_link_libraries(AheuiJITSample AheuiJIT) \ No newline at end of file diff --git a/src/AheuiJIT/Sample/main.cpp b/src/AheuiJIT/Sample/main.cpp deleted file mode 100644 index a5b03db..0000000 --- a/src/AheuiJIT/Sample/main.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include - - -int main() { - return 0; -} diff --git a/src/AheuiJIT/Translator/Decoder.cpp b/src/AheuiJIT/Translator/Decoder.cpp new file mode 100644 index 0000000..3a47cc8 --- /dev/null +++ b/src/AheuiJIT/Translator/Decoder.cpp @@ -0,0 +1,135 @@ +#include "Translator.h" + +using namespace aheuijit; + +bool Translator::decodeNOP(Builder &b, const Token &token) { + return true; +} + +bool Translator::decodeADD(Builder &b, const Token &token) { + Local *rhs = b.pop(cur.pointer.queue); + Local *lhs = b.pop(cur.pointer.queue); + Local *value = b.add(lhs, rhs); + b.push(cur.pointer.queue, value); + return true; +} + +bool Translator::decodeSUB(Builder &b, const Token &token) { + Local *rhs = b.pop(cur.pointer.queue); + Local *lhs = b.pop(cur.pointer.queue); + Local *value = b.sub(lhs, rhs); + b.push(cur.pointer.queue, value); + return true; +} + +bool Translator::decodeMUL(Builder &b, const Token &token) { + Local *rhs = b.pop(cur.pointer.queue); + Local *lhs = b.pop(cur.pointer.queue); + Local *value = b.mul(lhs, rhs); + b.push(cur.pointer.queue, value); + return true; +} + +bool Translator::decodeDIV(Builder &b, const Token &token) { + Local *rhs = b.pop(cur.pointer.queue); + Local *lhs = b.pop(cur.pointer.queue); + Local *value = b.div(lhs, rhs); + b.push(cur.pointer.queue, value); + return true; +} + +bool Translator::decodeMOD(Builder &b, const Token &token) { + Local *rhs = b.pop(cur.pointer.queue); + Local *lhs = b.pop(cur.pointer.queue); + Local *value = b.mod(lhs, rhs); + b.push(cur.pointer.queue, value); + return true; +} + +bool Translator::decodeCMP(Builder &b, const Token &token) { + Local *rhs = b.pop(cur.pointer.queue); + Local *lhs = b.pop(cur.pointer.queue); + Local *value = b.cmp(lhs, rhs); + b.push(cur.pointer.queue, value); + return true; +} + +bool Translator::decodeCHANGE(Builder &b, const Token &token) { + Local *top = b.pop(cur.pointer.queue); + Local *next = b.pop(cur.pointer.queue); + if (cur.pointer.queue) { + b.pushQueueFront(top); + b.pushQueueFront(next); + + } else { + b.pushStack(top); + b.pushStack(next); + } + return true; +} + +bool Translator::decodeDUP(Builder &b, const Token &token) { + Local *val = b.pop(cur.pointer.queue); + if (cur.pointer.queue) { + b.pushQueueFront(val); + b.pushQueueFront(val); + } else { + b.pushStack(val); + b.pushStack(val); + } + return true; +} + +bool Translator::decodeSELECT(Builder &b, const Token &token) { + const Storage store = token.storage.toStorage(); + const bool newQueue = !store.stack; + cur.pointer.queue = newQueue; + Constant *val = b.createConstant(store.toImm()); + b.setStore(val); + return true; +} + +bool Translator::decodePUSH(Builder &b, const Token &token) { + Value *value; + if (token.storage == TokenStorage::OO) { + value = b.inputNum(nullptr); + } else if (token.storage == TokenStorage::HIET) { + value = b.inputChar(nullptr); + } else { + value = b.createConstant(token.storage.toNumConst()); + } + b.push(cur.pointer.queue, value); + return true; +} + +bool Translator::decodeMOV(Builder &b, const Token &token) { + Local *value = b.pop(cur.pointer.queue); + Local *backup = b.getStore(nullptr); + Storage store = token.storage.toStorage(); + Constant *storeValue = b.createConstant(store.toImm()); + b.setStore(storeValue); + b.push(!store.stack, value); + b.setStore(backup); + return true; +} + +bool Translator::decodePOP(Builder &b, const Token &token) { + Local *value = b.pop(cur.pointer.queue); + if (token.storage == TokenStorage::OO) { + b.outputNum(value); + } else if (token.storage == TokenStorage::HIET) { + b.outputChar(value); + } + return true; +} + +bool Translator::decodeEXIT(Builder &b, const Token &token) { + b.setExitTerminal(); + return false; +} + +bool Translator::decodeCBZ(Builder &b, const Token &token) { + Local *pred = b.pop(cur.pointer.queue); + b.setConditionalTerminal(pred); + return false; +} diff --git a/src/AheuiJIT/Translator/Emitter.cpp b/src/AheuiJIT/Translator/Emitter.cpp index e69de29..9a16607 100644 --- a/src/AheuiJIT/Translator/Emitter.cpp +++ b/src/AheuiJIT/Translator/Emitter.cpp @@ -0,0 +1,467 @@ +#include "Emitter.h" + +#include +#include + +#include + +using namespace asmjit; +using namespace aheuijit; + +#ifdef _WIN32 + +std::array CALLER_SAVE_REGS = { x86::rax, x86::rcx, x86::rdx, x86::r8, + x86::r9, x86::r10, x86::r11 }; + +std::array CALLE_SAVE_REGS = { x86::rsi, x86::rdi, x86::rbx, x86::r12, + x86::r13, x86::r14, x86::r15 }; + +std::array PARAM_REGS = { x86::rcx, x86::rdx }; + +constexpr int SHADOW_SPACE = 32; + +#else + +std::array CALLER_SAVE_REGS = { x86::rax, x86::rcx, x86::rdx, x86::rsi, x86::rdi, + x86::r8, x86::r9, x86::r10, x86::r11 }; + +std::array PARAM_REGS = { x86::rdi, x86::rsi }; + +std::array CALLE_SAVE_REGS = { x86::rbx, x86::r12, x86::r13, x86::r14, x86::r15 }; + +constexpr int SHADOW_SPACE = 0; + +#endif + +void Emitter::emit(BasicBlock *bb, const TLBTable &table, std::set &emitted) { + emitPrologue(); + std::stack todo; + std::set done; + exit = code.newLabel(); + + if (table.find(bb->location) != table.end()) { + code.jmp(table.at(bb->location)); + } else { + todo.push(bb); + } + + const auto getOrCreateLabel = [&](uint64_t id) { + if (labels.find(id) == labels.end()) { + labels.emplace(id, code.newNamedLabel(std::to_string(id).c_str())); + } + return labels[id]; + }; + + while (!todo.empty()) { + block = todo.top(); + todo.pop(); + if (done.find(block->id) != done.end()) { + continue; + } + emitted.insert(block); + + const Label label = getOrCreateLabel(block->id); + code.bind(label); + regAlloc.emitInit(); + + const auto lock = regAlloc.allocSystem(x86::rbp); + const auto lock2 = regAlloc.allocSystem(x86::rsp); + const auto lock3 = regAlloc.allocSystem(x86::rdi); + + Location prevLocation; + popFixup = false; + for (auto inst : block->insts) { + if (inst->location != prevLocation) { + popFixup = false; + } + regAlloc.setInstructionIndex(inst->offset); + visit(inst); + prevLocation = inst->location; + } + + done.insert(block->id); + + switch (block->terminal->getType()) { + case TerminalType::Exit: + regAlloc.emitDeinit(); + regAlloc.reset(); + code.jmp(exit); + break; + case TerminalType::Link: { + regAlloc.emitDeinit(); + regAlloc.reset(); + LinkTerminal *term = dynamic_cast(block->terminal); + const auto it = table.find(term->block->location); + if (it != table.end()) { + code.jmp(it->second); + } else { + const Label label_ = getOrCreateLabel(term->block->id); + code.jmp(label_); + todo.push(term->block); + } + break; + } + case TerminalType::Conditional: { + const auto lock4 = regAlloc.allocSystem(x86::rax); + ConditionalTerminal *term = dynamic_cast(block->terminal); + Reg predicate = unwrapValue(term->predicate); + code.mov(x86::rax, predicate.get()); + regAlloc.emitDeinit(); + regAlloc.reset(); + code.cmp(x86::rax, 0); + + auto it = table.find(term->pass->location); + if (it != table.end()) { + code.je(it->second); + } else { + Label pass = getOrCreateLabel(term->pass->id); + code.je(pass); + todo.push(term->pass); + } + + it = table.find(term->fail->location); + if (it != table.end()) { + code.jmp(it->second); + } else { + Label fail = getOrCreateLabel(term->fail->id); + code.jmp(fail); + todo.push(term->fail); + } + + break; + } + } + } + code.bind(exit); + emitEpilouge(); +} + +void Emitter::emitMethodCall(void *func, asmjit::x86::Gp arg0, asmjit::x86::Gp arg1) { + for (int i = 0; i < CALLER_SAVE_REGS.size(); ++i) { + code.push(CALLER_SAVE_REGS[i]); + } + code.sub(x86::rsp, 8); + code.sub(x86::rsp, SHADOW_SPACE); + code.mov(PARAM_REGS[0], arg0); + code.mov(PARAM_REGS[1], arg1); + code.call(reinterpret_cast(func)); + code.add(x86::rsp, SHADOW_SPACE); + code.add(x86::rsp, 8); + for (int i = CALLER_SAVE_REGS.size() - 1; i >= 0; --i) { + code.pop(CALLER_SAVE_REGS[i]); + } +} + +void Emitter::emitRetMethodCall(void *func, asmjit::x86::Gp arg0, asmjit::x86::Gp ret) { + bool filtered = false; + for (int i = 0; i < CALLER_SAVE_REGS.size(); ++i) { + if (CALLER_SAVE_REGS[i] != ret) { + code.push(CALLER_SAVE_REGS[i]); + } else { + filtered = true; + } + } + if (!filtered) + code.sub(x86::rsp, 8); + code.sub(x86::rsp, SHADOW_SPACE); + code.mov(PARAM_REGS[0], arg0); + code.call(reinterpret_cast(func)); + code.mov(ret, x86::rax); + code.add(x86::rsp, SHADOW_SPACE); + if (!filtered) + code.add(x86::rsp, 8); + for (int i = CALLER_SAVE_REGS.size() - 1; i >= 0; --i) { + if (CALLER_SAVE_REGS[i] != ret) { + code.pop(CALLER_SAVE_REGS[i]); + } + } +} + +void Emitter::emitFunctionCall(void *func, asmjit::x86::Gp arg0) { + for (int i = 0; i < CALLER_SAVE_REGS.size(); ++i) { + code.push(CALLER_SAVE_REGS[i]); + } + code.sub(x86::rsp, 8); + code.sub(x86::rsp, SHADOW_SPACE); + code.mov(PARAM_REGS[0], arg0); + code.call(reinterpret_cast(func)); + code.add(x86::rsp, SHADOW_SPACE); + code.add(x86::rsp, 8); + for (int i = CALLER_SAVE_REGS.size() - 1; i >= 0; --i) { + code.pop(CALLER_SAVE_REGS[i]); + } +} + +void Emitter::emitPrologue() { + code.push(x86::rbp); + code.mov(x86::rbp, x86::rsp); + code.sub(x86::rsp, 8); + + for (int i = 0; i < CALLE_SAVE_REGS.size(); ++i) { + code.push(CALLE_SAVE_REGS[i]); + } + + code.mov(x86::rdi, PARAM_REGS[0]); +} + +void Emitter::emitEpilouge() { + for (int i = CALLE_SAVE_REGS.size() - 1; i >= 0; --i) { + code.pop(CALLE_SAVE_REGS[i]); + } + + code.mov(x86::rsp, x86::rbp); + code.pop(x86::rbp); + code.ret(); +} + +void Emitter::emitJITRequest() { + regAlloc.emitDeinitStub(); + if (popFixup) { + if (inst->location.pointer.queue) { + x86::Mem cursorPtr = x86::ptr(x86::rdi, offsetof(JITContext, queueCursor)); + code.mov(x86::rbx, cursorPtr); + code.lea(x86::rbx, x86::ptr(x86::rbx, -8)); + code.mov(cursorPtr, x86::rbx); + } else { + code.mov(x86::rax, x86::ptr(x86::rdi, offsetof(JITContext, storage))); + x86::Mem stackPtr = + x86::ptr(x86::rdi, x86::rax, 3, offsetof(JITContext, storageBuffer)); + code.mov(x86::rbx, stackPtr); + code.lea(x86::rbx, x86::ptr(x86::rbx, -8)); + code.mov(stackPtr, x86::rbx); + } + } + x86::Mem patchTable = x86::ptr(x86::rdi, offsetof(JITContext, exhaustPatchTable)); + code.mov(x86::rax, patchTable); + code.lea(x86::rax, x86::ptr(x86::rax, offsetof(JITHashTable, beans))); + code.mov(x86::rdx, imm(inst->location.hash())); + const uint64_t beanOffset = (inst->location.hash() & (HASH_TABLE_BEANS - 1)) + << HAST_TABLE_BEAN_SHIFT; + code.lea(x86::rax, x86::qword_ptr(x86::rax, beanOffset)); + code.xor_(x86::rcx, x86::rcx); + const Label loopBegin = code.newLabel(); + const Label jitRequest = code.newLabel(); + code.bind(loopBegin); + code.mov(x86::r9, x86::qword_ptr(x86::rax, x86::rcx)); + code.mov(x86::r10, x86::qword_ptr(x86::rax, x86::rcx, 0, 8)); + code.mov(x86::r8, x86::r10); + code.or_(x86::r10, x86::r9); + code.cmp(x86::r10, 0); + code.je(jitRequest); + code.add(x86::rcx, 16); + code.cmp(x86::r9, x86::rdx); + code.jne(loopBegin); + code.jmp(x86::r8); + code.bind(jitRequest); + emitSetLocation(inst->location); + code.jmp(exit); +} + +void Emitter::emitSetLocation(const Location &location) { + x86::Mem locationPtr = x86::dword_ptr(x86::rdi, offsetof(JITContext, location)); + code.mov(locationPtr, location.x); + locationPtr.addOffset(4); + code.mov(locationPtr, location.y); + locationPtr.addOffset(4); + code.mov(locationPtr, location.pointer.queue); + locationPtr.addOffset(4); + code.mov(locationPtr, location.pointer.vx); + locationPtr.addOffset(4); + code.mov(locationPtr, location.pointer.vy); +} + +void Emitter::visit(Instruction *inst) { + InstructionVisitorCaller caller; + this->inst = inst; + caller.call(*this, inst); +} + +inline Reg Emitter::unwrapValue(Value *value) { + Reg out; + if (value->type() == ValueType::Local) { + out = regAlloc.allocLocal(dynamic_cast(value)); + } else { + out = regAlloc.allocTmp(); + Constant *cc = dynamic_cast(value); + code.mov(out.get(), cc->imm); + } + return out; +} + +void Emitter::add(Value *lhs, Value *rhs) { + Reg dest = regAlloc.allocLocal(inst->output); + Reg a = unwrapValue(lhs); + Reg b = unwrapValue(rhs); + code.mov(dest.get(), a.get()); + code.add(dest.get(), b.get()); +} + +void Emitter::sub(Value *lhs, Value *rhs) { + Reg dest = regAlloc.allocLocal(inst->output); + Reg a = unwrapValue(lhs); + Reg b = unwrapValue(rhs); + code.mov(dest.get(), a.get()); + code.sub(dest.get(), b.get()); +} + +void Emitter::mul(Value *lhs, Value *rhs) { + const auto lock = regAlloc.allocSystem(x86::rax); + const auto lock2 = regAlloc.allocSystem(x86::rdx); + Reg dest = regAlloc.allocLocal(inst->output); + Reg a = unwrapValue(lhs); + Reg b = unwrapValue(rhs); + code.mov(x86::rax, a.get()); + code.imul(b.get()); + code.mov(dest.get(), x86::rax); +} + +void Emitter::div(Value *lhs, Value *rhs) { + const auto lock = regAlloc.allocSystem(x86::rax); + const auto lock2 = regAlloc.allocSystem(x86::rdx); + Reg dest = regAlloc.allocLocal(inst->output); + Reg a = unwrapValue(lhs); + Reg b = unwrapValue(rhs); + code.mov(x86::rax, a.get()); + code.cqo(x86::rdx, x86::rax); + code.idiv(b.get()); + code.mov(dest.get().r32(), x86::eax); +} + +void Emitter::mod(Value *lhs, Value *rhs) { + const auto lock = regAlloc.allocSystem(x86::rax); + const auto lock2 = regAlloc.allocSystem(x86::rdx); + Reg dest = regAlloc.allocLocal(inst->output); + Reg a = unwrapValue(lhs); + Reg b = unwrapValue(rhs); + code.mov(x86::rax, a.get()); + code.cqo(x86::rdx, x86::rax); + code.idiv(b.get()); + code.mov(dest.get().r32(), x86::edx); +} + +void Emitter::cmp(Value *lhs, Value *rhs) { + Reg dest = regAlloc.allocLocal(inst->output); + Reg a = unwrapValue(lhs); + Reg b = unwrapValue(rhs); + code.xor_(dest.get(), dest.get()); + code.cmp(a.get(), b.get()); + code.setge(dest.get().r8()); +} + +void Emitter::pushStack(Value *value) { + Reg valueReg = unwrapValue(value); + Reg store = regAlloc.allocTmp(); + Reg stack = regAlloc.allocTmp(); + code.mov(store.get().r32(), x86::ptr(x86::rdi, offsetof(JITContext, storage))); + x86::Mem stackPtr = x86::ptr(x86::rdi, store.get(), 3, offsetof(JITContext, storageBuffer)); + code.mov(stack.get(), stackPtr); + code.lea(stack.get(), x86::ptr(stack.get(), -8)); + code.mov(x86::ptr(stack.get()), valueReg.get()); + code.mov(stackPtr, stack.get()); +} + +void Emitter::popStack(Void *) { + Label die = code.newLabel(); + Label end = code.newLabel(); + Reg outputReg = regAlloc.allocLocal(inst->output); + Reg store = regAlloc.allocTmp(); + Reg stack = regAlloc.allocTmp(); + Reg stackFront = regAlloc.allocTmp(); + code.mov(store.get().r32(), x86::ptr(x86::rdi, offsetof(JITContext, storage))); + x86::Mem stackPtr = x86::ptr(x86::rdi, store.get(), 3, offsetof(JITContext, storageBuffer)); + x86::Mem stackFrontPtr = x86::ptr(x86::rdi, store.get(), 3, offsetof(JITContext, stackFronts)); + code.mov(stack.get(), stackPtr); + code.mov(stackFront.get(), stackFrontPtr); + code.cmp(stack.get(), stackFront.get()); + code.je(die); + code.mov(outputReg.get(), x86::ptr(stack.get())); + code.lea(stack.get(), x86::ptr(stack.get(), 8)); + code.mov(stackPtr, stack.get()); + code.jmp(end); + code.bind(die); + emitJITRequest(); + code.bind(end); + popFixup = true; +} + +void Emitter::pushQueueFront(Value *value) { + Reg valueReg = unwrapValue(value); + x86::Mem queueCurosrPtr = x86::ptr(x86::rdi, offsetof(JITContext, queueCursor)); + Reg queueCursor = regAlloc.allocTmp(); + code.mov(queueCursor.get(), queueCurosrPtr); + code.mov(x86::ptr(queueCursor.get()), valueReg.get()); + code.lea(queueCursor.get(), x86::ptr(queueCursor.get(), 8)); + code.mov(queueCurosrPtr, queueCursor.get()); +} + +void Emitter::pushQueueBack(Value *value) { + Reg valueReg = unwrapValue(value); + x86::Mem queueBottomPtr = x86::ptr(x86::rdi, offsetof(JITContext, storageBuffer)); + Reg queueBottom = regAlloc.allocTmp(); + code.mov(queueBottom.get(), queueBottomPtr); + code.lea(queueBottom.get(), x86::ptr(queueBottom.get(), -8)); + code.mov(x86::ptr(queueBottom.get()), valueReg.get()); + code.mov(queueBottomPtr, queueBottom.get()); +} + +void Emitter::popQueue(Void *) { + Label die = code.newLabel(); + Label end = code.newLabel(); + Reg outputReg = regAlloc.allocLocal(inst->output); + x86::Mem queueBottomPtr = x86::ptr(x86::rdi, offsetof(JITContext, storageBuffer)); + x86::Mem queueCurosrPtr = x86::ptr(x86::rdi, offsetof(JITContext, queueCursor)); + Reg queueCursor = regAlloc.allocTmp(); + Reg queueBottom = regAlloc.allocTmp(); + code.mov(queueCursor.get(), queueCurosrPtr); + code.mov(queueBottom.get(), queueBottomPtr); + code.cmp(queueCursor.get(), queueBottom.get()); + code.je(die); + code.lea(queueCursor.get(), x86::ptr(queueCursor.get(), -8)); + code.mov(outputReg.get(), x86::ptr(queueCursor.get())); + code.mov(queueCurosrPtr, queueCursor.get()); + code.jmp(end); + code.bind(die); + emitJITRequest(); + code.bind(end); + popFixup = true; +} + +void Emitter::setStore(Value *value) { + Reg valueReg = unwrapValue(value); + code.mov(x86::ptr(x86::rdi, offsetof(JITContext, storage)), valueReg.get()); +} + +void Emitter::getStore(Void *) { + Reg dest = regAlloc.allocLocal(inst->output); + code.mov(dest.get(), x86::ptr(x86::rdi, offsetof(JITContext, storage))); +} + +void Emitter::outputNum(Value *value) { + Reg runtimePtr = regAlloc.allocTmp(); + Reg valueReg = unwrapValue(value); + code.mov(runtimePtr.get(), x86::ptr(x86::rdi, offsetof(JITContext, runtime))); + emitMethodCall(METHOD_TO_POINTER(Runtime, printNum, void, Word), runtimePtr.get(), + valueReg.get()); +} + +void Emitter::outputChar(Value *value) { + Reg runtimePtr = regAlloc.allocTmp(); + Reg valueReg = unwrapValue(value); + code.mov(runtimePtr.get(), x86::ptr(x86::rdi, offsetof(JITContext, runtime))); + emitMethodCall(METHOD_TO_POINTER(Runtime, printChar, void, Word), runtimePtr.get(), + valueReg.get()); +} + +void Emitter::inputNum(Void *) { + Reg runtimePtr = regAlloc.allocTmp(); + Reg dest = regAlloc.allocLocal(inst->output); + code.mov(runtimePtr.get(), x86::ptr(x86::rdi, offsetof(JITContext, runtime))); + emitRetMethodCall(METHOD_TO_POINTER(Runtime, inputNum, Word), runtimePtr.get(), dest.get()); +} + +void Emitter::inputChar(Void *) { + Reg runtimePtr = regAlloc.allocTmp(); + Reg dest = regAlloc.allocLocal(inst->output); + code.mov(runtimePtr.get(), x86::ptr(x86::rdi, offsetof(JITContext, runtime))); + emitRetMethodCall(METHOD_TO_POINTER(Runtime, inputChar, Word), runtimePtr.get(), dest.get()); +} diff --git a/src/AheuiJIT/Translator/Emitter.h b/src/AheuiJIT/Translator/Emitter.h index e69de29..cf7d6b6 100644 --- a/src/AheuiJIT/Translator/Emitter.h +++ b/src/AheuiJIT/Translator/Emitter.h @@ -0,0 +1,68 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +#define METHOD_TO_POINTER(class, method, ret, ...) \ + ([]() { \ + ret (__thiscall class ::*pFunc)(__VA_ARGS__) = &class ::method; \ + return (void *&)pFunc; \ + }()) + +#else + +#define METHOD_TO_POINTER(class, method, ret, ...) \ + ([]() { \ + ret (class ::*pFunc)(__VA_ARGS__) = &class ::method; \ + return (void *&)pFunc; \ + }()) + +#endif + +namespace aheuijit { + +using TLBTable = std::map; + +struct Emitter { + Emitter() = delete; + + explicit Emitter(Runtime &rt, asmjit::x86::Assembler &code) + : rt(rt), code(code), regAlloc(code){}; + + ~Emitter() = default; + + void emit(BasicBlock *bb, const TLBTable &table, std::set &emitted); + void visit(Instruction *inst); + +#define INST(name, ret, ...) void name(__VA_ARGS__); +#include +#undef INST + + private: + void emitPrologue(); + void emitEpilouge(); + + void emitMethodCall(void *func, asmjit::x86::Gp arg0, asmjit::x86::Gp arg1); + void emitRetMethodCall(void *func, asmjit::x86::Gp arg0, asmjit::x86::Gp ret); + void emitFunctionCall(void *func, asmjit::x86::Gp arg0); + void emitJITRequest(); + void emitSetLocation(const Location &location); + + inline Reg unwrapValue(Value *value); + + bool popFixup{ false }; + RegAlloc regAlloc; + Runtime &rt; + asmjit::x86::Assembler &code; + BasicBlock *block{ nullptr }; + Instruction *inst{ nullptr }; + std::map labels; + asmjit::Label exit; +}; + +} // namespace aheuijit diff --git a/src/AheuiJIT/Translator/RegAlloc.cpp b/src/AheuiJIT/Translator/RegAlloc.cpp index e69de29..786f4f1 100644 --- a/src/AheuiJIT/Translator/RegAlloc.cpp +++ b/src/AheuiJIT/Translator/RegAlloc.cpp @@ -0,0 +1,225 @@ +#include "RegAlloc.h" + +#include + +using namespace aheuijit; +using namespace asmjit; + +RegAlloc::RegAlloc(x86::Assembler &code) : code(code) { + active.fill(0); +} + +void RegAlloc::setInstructionIndex(size_t index) { + this->index = index; + expireOldActiveIntervals(); +} + +Reg RegAlloc::allocSystem(x86::Gp reg) { + if (active[reg.id()]) { + spillAtInterval(active[reg.id()]); + expireActiveInterval(active[reg.id()]); + } + // TODO: I can pool this or just not do dynamic allocation + std::unique_ptr interval = + std::make_unique(next_id++, index); + RegAllocInterval *ptr = interval.get(); + registerInterval(std::move(interval)); + systemRegs.insert(ptr->id); + active[reg.id()] = ptr->id; + + return Reg( + ptr->id, [reg]() { return reg; }, [this, reg]() { this->active[reg.id()] = 0; }); +} + +Reg RegAlloc::allocLocal(Local *local) { + auto it = localMap.find(local->id); + if (it != localMap.end()) { + RegAllocInterval *ptr = intervals.find(it->second)->second.get(); + return Reg( + ptr->id, [this, ptr]() { return this->alloc(ptr->id); }, nullptr); + } + auto interval = std::make_unique( + next_id++, index, local->endpoint ? local->endpoint.value() : MAX_ENDPOINT); + RegAllocInterval *ptr = interval.get(); + registerInterval(std::move(interval)); + localMap.emplace(local->id, ptr->id); + return Reg( + ptr->id, [this, ptr]() { return this->alloc(ptr->id); }, nullptr); +} + +Reg RegAlloc::allocTmp() { + std::unique_ptr interval = + std::make_unique(next_id++, index); + RegAllocInterval *ptr = interval.get(); + registerInterval(std::move(interval)); + return Reg( + ptr->id, [this, ptr]() { return this->alloc(ptr->id); }, + [this, ptr]() { this->unregisterInteval(ptr->id); }); +} + +void RegAlloc::release(uint64_t id) { + unregisterInteval(id); +} + +void RegAlloc::registerInterval(std::unique_ptr interval) { + RegAllocInterval *ptr = interval.get(); + intervals.emplace(interval->id, std::move(interval)); +} + +void RegAlloc::unregisterInteval(uint64_t id) { + expireActiveInterval(id); + intervals.erase(id); +} + +static inline x86::Gp indexToGp(uint8_t index) { + return x86::Reg::fromTypeAndId(BaseReg::RegType::kTypeGp64, index).as(); +} + +x86::Gp RegAlloc::alloc(uint64_t id) { + RegAllocInterval *ptr = intervals.find(id)->second.get(); + int emptyI = -1; + for (int i = 0; i < active.size(); ++i) { + if (active[i] == id) { + return indexToGp(i); + } + if (active[i] == 0) { + emptyI = i; + } + } + ptr->activeTs = ts++; + if (emptyI != -1) { + unspillAtInterval(id, emptyI); + active[emptyI] = id; + active_intervals_by_start.push_back(ptr); + active_intervals_by_end.insert(ptr); + ptr->activeIndex = emptyI; + return indexToGp(emptyI); + } else { + RegAllocInterval *old = *active_intervals_by_start.begin(); + spillAtInterval(old->id); + expireActiveInterval(old->id); + active_intervals_by_start.push_back(ptr); + active_intervals_by_end.insert(ptr); + unspillAtInterval(id, old->activeIndex); + active[old->activeIndex] = id; + ptr->activeIndex = old->activeIndex; + return indexToGp(old->activeIndex); + } +} + +void RegAlloc::expireActiveInterval(uint64_t id) { + RegAllocInterval *ptr = intervals.find(id)->second.get(); + for (int i = 0; i < active.size(); ++i) { + if (active[i] == id) { + active[i] = 0; + } + } + + for (auto it = active_intervals_by_start.begin(); it != active_intervals_by_start.end(); ++it) { + if ((*it)->id == ptr->id) { + active_intervals_by_start.erase(it); + break; + } + } + + auto it2 = active_intervals_by_end.lower_bound(ptr); + while (it2 != active_intervals_by_end.end() && (*it2)->endpoint == ptr->endpoint) { + if ((*it2)->id == ptr->id) { + active_intervals_by_end.erase(it2); + break; + } + ++it2; + } +} + +void RegAlloc::unspillAtInterval(uint64_t id, uint8_t phyId) { + const auto it = spillMap.find(id); + if (it != spillMap.end()) { + const x86::Gp reg = indexToGp(phyId); + const int64_t offset = it->second; + code.mov(reg, x86::ptr(x86::rsp, offset)); + } +} + +void RegAlloc::expireOldActiveIntervals() { + auto it = active_intervals_by_end.begin(); + while (it != active_intervals_by_end.end()) { + RegAllocInterval *interval = *it; + if (interval->endpoint >= index) { + break; + } + expireActiveInterval(interval->id); + it = active_intervals_by_end.begin(); + } +} + +void RegAlloc::spillAtInterval(uint64_t id) { + uint8_t phyId = 0xFF; + for (int i = 0; i < active.size(); ++i) { + if (active[i] == id) { + phyId = i; + break; + } + } + ASSERT(phyId != 0xFF); + if (spillCursor == 0) { + spillTop += 16; + spillCursor = 16; + } + const x86::Gp reg = indexToGp(phyId); + const uint64_t offset = spillTop - spillCursor; + code.mov(x86::ptr(x86::rsp, offset), reg); + spillMap[id] = offset; + spillCursor -= 8; +} + +void RegAlloc::emitStub() { + stubOffset = code.offset(); + code.long_().sub(x86::rsp, 0); +} + +void RegAlloc::patchStub() { + const int64_t backup = code.offset(); + code.setOffset(stubOffset); + code.long_().sub(x86::rsp, spillTop); + code.setOffset(backup); +} + +void RegAlloc::emitInit() { + emitStub(); +} + +void RegAlloc::emitDeinit() { + patchStub(); + code.add(x86::rsp, spillTop); + for (auto offset : deinitStubOffsets) { + const int64_t backup = code.offset(); + code.setOffset(offset); + code.long_().add(x86::rsp, spillTop); + code.setOffset(backup); + } +} + +void RegAlloc::emitDeinitStub() { + deinitStubOffsets.push_back(code.offset()); + code.long_().sub(x86::rsp, 0); +} + +void RegAlloc::reset() { + spillTop = 0; + for (int i = 0; i < active.size(); ++i) { + active[i] = 0; + } + spillMap.clear(); + active_intervals_by_end.clear(); + active_intervals_by_start.clear(); + localMap.clear(); + intervals.clear(); + systemRegs.clear(); + deinitStubOffsets.clear(); + index = 0; + spillCursor = 0; + stubOffset = 0; + ts = 1; + next_id = 1; +} diff --git a/src/AheuiJIT/Translator/RegAlloc.h b/src/AheuiJIT/Translator/RegAlloc.h index e69de29..665aed6 100644 --- a/src/AheuiJIT/Translator/RegAlloc.h +++ b/src/AheuiJIT/Translator/RegAlloc.h @@ -0,0 +1,134 @@ +#pragma once +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace aheuijit { + +struct Reg { + uint64_t id; + + Reg() = default; + explicit Reg(uint64_t id, std::function getter, + std::function deleter) + : id(id), getter(getter), deleter(deleter) { + } + + ~Reg() { + if (deleter) { + deleter(); + } + } + + asmjit::x86::Gp get() { + return getter(); + } + + Reg(Reg &&o) noexcept { + std::swap(deleter, o.deleter); + std::swap(getter, o.getter); + std::swap(id, o.id); + } + + Reg &operator=(Reg &&o) noexcept { + std::swap(deleter, o.deleter); + std::swap(getter, o.getter); + std::swap(id, o.id); + return *this; + } + + private: + std::function getter = nullptr; + std::function deleter = nullptr; +}; + +constexpr size_t MAX_ENDPOINT = 1U << 31; +constexpr size_t MAX_PHY_REGS = 16; + +struct RegAllocInterval { + RegAllocInterval() = delete; + explicit RegAllocInterval(uint64_t id, uint64_t start) : id(id), start(start) { + } + explicit RegAllocInterval(uint64_t id, uint64_t start, uint64_t endpoint) + : id(id), start(start), endpoint(endpoint) { + } + + ~RegAllocInterval() = default; + uint64_t id; + size_t start; + size_t activeIndex = 0; + size_t activeTs = 0; + size_t endpoint = 0; +}; + +struct RegAllocIntervalStartCompare { + bool operator()(RegAllocInterval *lhs, RegAllocInterval *rhs) const { + if (lhs->start == rhs->start) { + return lhs->activeTs < rhs->activeTs; + } + return lhs->start < rhs->start; + } +}; + +struct RegAllocIntervalEndCompare { + bool operator()(RegAllocInterval *lhs, RegAllocInterval *rhs) const { + return lhs->endpoint < rhs->endpoint; + } +}; + +struct RegAlloc { + RegAlloc() = delete; + RegAlloc(asmjit::x86::Assembler &code); + ~RegAlloc() = default; + + void setInstructionIndex(size_t index); + + Reg allocSystem(asmjit::x86::Gp reg); + Reg allocLocal(Local *local); + Reg allocTmp(); + + void release(uint64_t id); + + void emitInit(); + void emitDeinit(); + void emitDeinitStub(); + + void reset(); + + private: + asmjit::x86::Assembler &code; + size_t index = 0; + uint64_t next_id = 1; + uint64_t ts = 1; + + asmjit::x86::Gp alloc(uint64_t id); + void expireActiveInterval(uint64_t id); + void expireOldActiveIntervals(); + void unspillAtInterval(uint64_t id, uint8_t phyId); + void spillAtInterval(uint64_t id); + void registerInterval(std::unique_ptr interval); + void unregisterInteval(uint64_t id); + void patchStub(); + void emitStub(); + + std::array active; + std::map localMap; + std::set systemRegs; + std::map spillMap; + int64_t spillTop{ 0 }; + int64_t spillCursor{ 0 }; + int64_t stubOffset{ 0 }; + std::list deinitStubOffsets; + + std::list active_intervals_by_start; + std::multiset active_intervals_by_end; + std::map> intervals; +}; + +} // namespace aheuijit diff --git a/src/AheuiJIT/Translator/Token.h b/src/AheuiJIT/Translator/Token.h new file mode 100644 index 0000000..c513cee --- /dev/null +++ b/src/AheuiJIT/Translator/Token.h @@ -0,0 +1,236 @@ +#pragma once + +#include +#include + +namespace aheuijit { + +enum class TokenOp { +#define OP(op) op, +#include "TokenOp.inc" +#undef OP +}; + +static std::string describeTokenOp(TokenOp op) { + switch (op) { +#define OP(opp) \ + case TokenOp::opp: \ + return #opp; +#include "TokenOp.inc" +#undef OP + } + return ""; +} + +struct TokenDir { + enum Value : uint8_t { + RIGHT, + LEFT, + UP, + DOWN, + RIGHT2, + LEFT2, + UP2, + DOWN2, + VERTWALL, + HORIWALL, + WALL, + NONE + }; + + TokenDir() = default; + TokenDir(Value value) : value(value) { + } + + bool isPrimitive() const { + switch (value) { + case RIGHT: + case LEFT: + case RIGHT2: + case LEFT2: + case UP: + case DOWN: + case UP2: + case DOWN2: + return true; + default: + return false; + } + } + + Pointer toPointer(bool queue) const { + int vx = 0; + int vy = 0; + switch (value) { + case RIGHT: + vx = 1; + break; + case RIGHT2: + vx = 2; + break; + case LEFT: + vx = -1; + break; + case LEFT2: + vx = -2; + break; + case DOWN: + vy = 1; + break; + case DOWN2: + vy = 2; + break; + case UP: + vy = -1; + break; + case UP2: + vy = -2; + break; + } + return Pointer(queue, vx, vy); + } + + bool operator<(const TokenDir &other) const { + return value < other.value; + } + + bool operator==(const TokenDir &other) const { + return value == other.value; + } + + operator Value() const { + return value; + } + explicit operator bool() = delete; + + private: + Value value{ NONE }; +}; + +struct TokenStorage { + enum Value : uint8_t { + BLANK, + GIUK, + GIUKGIUK, + GIUKSIOT, + NIEN, + NIENJIOT, + NIENHIET, + DIGUEK, + RIEL, + RIELGIUK, + RIELMIEM, + RIELBIEB, + RIELSIOT, + RIELTIGT, + RIELPIEP, + RIELHIET, + MIEM, + BIEB, + BS, + SIOT, + SIOTSIOT, + OO, + JIOT, + CHIOT, + KIEK, + TIGT, + PIEP, + HIET + }; + + TokenStorage() = default; + TokenStorage(Value value) : value(value) { + } + + operator Value() const { + return value; + } + explicit operator bool() = delete; + + Storage toStorage() const { + if (value == OO) { + return Storage(false, 0); + } else if (value > OO) { + return Storage(true, value - 1); + } else { + return Storage(true, value); + } + } + + size_t toNumConst() const { + switch (value) { + case GIUK: + return 2; + case NIEN: + return 2; + case DIGUEK: + return 3; + case RIEL: + return 5; + case MIEM: + return 4; + case BIEB: + return 4; + case SIOT: + return 2; + case JIOT: + return 3; + case CHIOT: + return 4; + case KIEK: + return 3; + case TIGT: + return 4; + case PIEP: + return 4; + case GIUKGIUK: + return 4; + case GIUKSIOT: + return 4; + case NIENJIOT: + return 5; + case NIENHIET: + return 5; + case RIELGIUK: + return 7; + case RIELMIEM: + return 9; + case RIELBIEB: + return 9; + case RIELSIOT: + return 7; + case RIELTIGT: + return 9; + case RIELPIEP: + return 9; + case RIELHIET: + return 8; + case BS: + return 6; + case SIOTSIOT: + return 4; + case BLANK: + case OO: + case HIET: + return 0; + }; + } + + private: + Value value{ BLANK }; +}; + +struct Token { + explicit Token(TokenOp op, TokenDir dir, TokenStorage storage) + : op(op), dir(dir), storage(storage) { + } + Token() = default; + ~Token() = default; + + TokenOp op = TokenOp::NOP; + TokenDir dir; + TokenStorage storage; +}; + +} // namespace aheuijit diff --git a/src/AheuiJIT/Translator/TokenOp.inc b/src/AheuiJIT/Translator/TokenOp.inc new file mode 100644 index 0000000..c3f1f8f --- /dev/null +++ b/src/AheuiJIT/Translator/TokenOp.inc @@ -0,0 +1,15 @@ +OP(NOP) +OP(EXIT) +OP(ADD) +OP(SUB) +OP(MUL) +OP(DIV) +OP(MOD) +OP(POP) +OP(PUSH) +OP(DUP) +OP(CHANGE) +OP(SELECT) +OP(MOV) +OP(CMP) +OP(CBZ) diff --git a/src/AheuiJIT/Translator/Translator.cpp b/src/AheuiJIT/Translator/Translator.cpp index e69de29..bce985b 100644 --- a/src/AheuiJIT/Translator/Translator.cpp +++ b/src/AheuiJIT/Translator/Translator.cpp @@ -0,0 +1,317 @@ +#include "Translator.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace asmjit; +using namespace aheuijit; + +IRBuffer::IRBuffer() { +} + +BasicBlock *IRBuffer::createBlock(const Location &location) { + auto block = std::make_unique(nextId++); + BasicBlock *out = block.get(); + blocks.emplace(location, std::move(block)); + return out; +} + +BasicBlock *IRBuffer::findBlock(const Location &location) { + const auto it = blocks.find(location); + if (it == blocks.end()) + return nullptr; + return it->second.get(); +} + +void IRBuffer::print() { + const auto block = findBlock(DEFAULT_LOCATION); + fmt::print("{}\n", block->description()); +} + +void IRBuffer::reset() { + blocks.clear(); +} + +Translator::Translator(Runtime &rt) + : parent(rt), rt(new asmjit::JitRuntime, [](asmjit::JitRuntime *rt) { delete rt; }) { + passManager.addBasicBlockPass(std::make_unique()); + passManager.addBasicBlockPass(std::make_unique()); + // FIXME: passManager.addBasicBlockPass(std::make_unique()); + passManager.addBasicBlockPass(std::make_unique()); + passManager.addBasicBlockPass(std::make_unique()); +} + +Token Translator::parseChar(const char16_t &ch) { + static TokenOp TOKEN_OP_TABLE[] = { TokenOp::NOP, TokenOp::NOP, TokenOp::DIV, TokenOp::ADD, + TokenOp::MUL, TokenOp::MOD, TokenOp::POP, TokenOp::PUSH, + TokenOp::DUP, TokenOp::SELECT, TokenOp::MOV, TokenOp::NOP, + TokenOp::CMP, TokenOp::NOP, TokenOp::CBZ, TokenOp::NOP, + TokenOp::SUB, TokenOp::CHANGE, TokenOp::EXIT }; + + static TokenDir TOKEN_DIR_TABLE[] = { TokenDir::RIGHT, TokenDir::NONE, TokenDir::RIGHT2, + TokenDir::NONE, TokenDir::LEFT, TokenDir::NONE, + TokenDir::LEFT2, TokenDir::NONE, TokenDir::UP, + TokenDir::NONE, TokenDir::NONE, TokenDir::NONE, + TokenDir::UP2, TokenDir::DOWN, TokenDir::NONE, + TokenDir::NONE, TokenDir::NONE, TokenDir::DOWN2, + TokenDir::HORIWALL, TokenDir::WALL, TokenDir::VERTWALL }; + + const wchar_t chNorm = ch - 0xAC00; + const size_t cho = static_cast(chNorm / 28 / 21); + const size_t jung = static_cast((chNorm / 28) % 21); + const TokenStorage::Value jong = static_cast(chNorm % 28); + + return Token(TOKEN_OP_TABLE[cho], TOKEN_DIR_TABLE[jung], TokenStorage(jong)); +}; + +void Translator::setCode(const std::u16string &code) { + codeStride = 0; + codeHeight = 1; + size_t cursor = 0; + for (int i = 0; i < code.size(); ++i) { + if (code[i] == u'\n') { + codeStride = std::max(cursor, codeStride); + ++codeHeight; + cursor = 0; + } else { + ++cursor; + } + } + + codeStride = std::max(cursor, codeStride); + this->code.resize(codeHeight * codeStride); + codeWidth.resize(codeHeight); + std::fill(this->code.begin(), this->code.end(), u' '); + std::fill(codeWidth.begin(), codeWidth.end(), 0); + + cursor = 0; + size_t y = 0; + for (int i = 0; i < code.size(); ++i) { + if (code[i] == u'\n') { + codeWidth[y] = cursor; + ++y; + cursor = 0; + } else { + getCode(y, cursor) = code[i]; + ++cursor; + } + } + + if (cursor != 0) { + codeWidth[y] = cursor; + } +} + +inline bool Translator::isBlank(char16_t ch) { + return !(ch >= 0xac00 && ch <= 0xd7a3); +} + +inline char16_t &Translator::getCode(const Location &location) { + return this->code[codeStride * location.y + location.x]; +} + +inline char16_t &Translator::getCode(int y, int x) { + return this->code[codeStride * y + x]; +} + +void Translator::buildBlocks(const Location &location) { + std::stack> trStack; + trStack.push(std::make_tuple(location, nullptr)); + + Builder b; + while (!trStack.empty()) { + auto [loc_, label_] = trStack.top(); + trStack.pop(); + cur = stepToValidLocation(loc_); + BasicBlock *bb = irBuffer.findBlock(cur); + if (bb) { + if (label_) { + *label_ = bb; + } + continue; + } + bb = irBuffer.createBlock(cur); + bb->location = cur; + BasicBlock *nextBlock = nullptr; + b.setBasicBlock(bb); + bool notTermed; + bool loop = false; + Token token; + std::set visited; + do { + b.setCurrentLocation(cur); + visited.insert(cur); + token = parseChar(getCode(cur)); + LOG("Translating x: {} y: {} op: {} orig: {}", cur.x, cur.y, describeTokenOp(token.op), + covnert_u16_to_utf8(getCode(cur))) + notTermed = decodeToken(b, token); + if (notTermed) { + cur = updateLocationPointer(cur, token.dir); + cur = stepLocation(cur); + } + cur = stepToValidLocation(cur); + nextBlock = irBuffer.findBlock(cur); + loop = visited.find(cur) != visited.end(); + } while (notTermed && !nextBlock && !loop); + + if (!notTermed) { + if (bb->terminal->getType() == TerminalType::Link) { + LinkTerminal *term = dynamic_cast(bb->terminal); + term->block = nextBlock; + } else if (bb->terminal->getType() == TerminalType::Conditional) { + ConditionalTerminal *term = dynamic_cast(bb->terminal); + Location next = updateLocationPointer(cur, token.dir); + trStack.push(std::make_tuple(stepLocation(next), &term->fail)); + next.pointer.flip(); + trStack.push(std::make_tuple(stepLocation(next), &term->pass)); + } + } else if (nextBlock) { + b.setLinkTerminal(nextBlock); + } else if (loop) { + b.setLinkTerminal(nullptr); + LinkTerminal *term = dynamic_cast(bb->terminal); + trStack.push(std::make_tuple(cur, &term->block)); + } + passManager.doBasicBlockPass(b, bb); + + if (label_) { + *label_ = bb; + } + } +} + +Location Translator::calculateFailLocation(const Location &location) { + Location out = location; + const Token token = parseChar(getCode(location)); + out = updateLocationPointer(out, token.dir); + out.pointer.flip(); + return stepLocation(out); +} + +IRBuffer &Translator::debugIR(const std::u16string &code_) { + setCode(code_); + buildBlocks(DEFAULT_LOCATION); + return irBuffer; +} + +TranslatedFunc Translator::translate(const std::u16string &code_, TLBTable &table) { + irBuffer.reset(); + setCode(code_); + return translate(stepToValidLocation(DEFAULT_LOCATION), table); +} + +Location Translator::stepToValidLocation(const Location &location) { + Location out = location; + while (isBlank(getCode(out))) { + out = stepLocation(out); + } + return out; +} + +TranslatedFunc Translator::translate(const Location &location, TLBTable &table) { + buildBlocks(location); + + CodeHolder codeHolder; + codeHolder.init(rt->environment()); + x86::Assembler codeAsm(&codeHolder); + + Emitter emitter(parent, codeAsm); + const auto block = irBuffer.findBlock(location); + std::set emitted; + emitter.emit(block, table, emitted); + TranslatedFunc func; + Error error = rt->add(&func, &codeHolder); + ASSERT(!error) + + for (auto b : emitted) { + const auto labelId = codeHolder.labelIdByName(std::to_string(b->id).c_str()); + table.emplace(b->location, + reinterpret_cast(func) + codeHolder.labelOffset(labelId)); + } + +#ifdef AHEUI_JIT_LOG + disassembleX86((void *)func, codeHolder.codeSize()); +#endif + return func; +} + +Location Translator::addLocationX(const Location &location, int val) { + if (!val) + return location; + Location out = location; + out.x += val; + + if (out.x < 0) { + out.x = codeWidth[out.y] + out.x; + } + if (out.x >= codeWidth[out.y]) { + out.x = cur.x - codeWidth[out.y]; + } + return out; +} + +Location Translator::addLocationY(const Location &location, int val) { + if (!val) + return location; + Location out = location; + out.y += val; + + if (out.y < 0) { + out.y = codeHeight + out.y; + } + if (out.y >= codeHeight) { + out.y = out.y - codeHeight; + } + return out; +} + +Location Translator::stepLocation(const Location &location) { + Location out = location; + out = addLocationX(out, out.pointer.vx); + out = addLocationY(out, out.pointer.vy); + return out; +} + +Location Translator::updateLocationPointer(const Location &location, const TokenDir &next) { + Location out = location; + switch (next) { + case TokenDir::VERTWALL: + if (out.pointer.isHorizontal()) { + out.pointer.flip(); + } + break; + case TokenDir::HORIWALL: + if (out.pointer.isVertical()) { + out.pointer.flip(); + } + break; + case TokenDir::WALL: + out.pointer.flip(); + break; + case TokenDir::NONE: + break; + default: + out.pointer = next.toPointer(out.pointer.queue); + }; + return out; +} + +bool Translator::decodeToken(Builder &b, const Token &token) { + switch (token.op) { +#define OP(op) \ + case TokenOp::op: \ + return decode##op(b, token); +#include "TokenOp.inc" +#undef OP + default: + fmt::print("Unknown aheui opcode: {}", token.op); + return false; + }; +} diff --git a/src/AheuiJIT/Translator/Translator.h b/src/AheuiJIT/Translator/Translator.h index e69de29..a9577af 100644 --- a/src/AheuiJIT/Translator/Translator.h +++ b/src/AheuiJIT/Translator/Translator.h @@ -0,0 +1,106 @@ +#pragma once +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace asmjit { +class JitRuntime; +} + +namespace aheuijit { + +struct Runtime; +struct JITContext; +struct Builder; + +using TLBTable = std::map; +using TranslatedFunc = void (*)(JITContext *ctx); +using BasicBlockPtr = std::unique_ptr; + +struct IRBuffer { + IRBuffer(); + ~IRBuffer() = default; + + BasicBlock *findBlock(const Location &location); + BasicBlock *createBlock(const Location &location); + + void print(); + void reset(); + + private: + uint64_t nextId = 1; + std::map blocks; +}; + +struct PassManager { + PassManager() { + } + ~PassManager() { + } + + void addBasicBlockPass(std::unique_ptr pass) { + basicBlockPasses.push_back(std::move(pass)); + } + + void doBasicBlockPass(Builder &b, BasicBlock *block) { + for (auto &pass : basicBlockPasses) { + pass->runOnBlock(b, block); + } + } + + private: + std::list> basicBlockPasses; +}; + +using AsmJitRuntimePtr = + std::unique_ptr>; + +struct Translator { + Translator(Runtime &rt); + ~Translator() = default; + + TranslatedFunc translate(const std::u16string &code, TLBTable &table); + TranslatedFunc translate(const Location &location, TLBTable &table); + IRBuffer &debugIR(const std::u16string &code); + + static Token parseChar(const char16_t &ch); + + Location calculateFailLocation(const Location &location); + Location updateLocationPointer(const Location &location, const TokenDir &next); + Location addLocationX(const Location &location, int val); + Location addLocationY(const Location &location, int val); + Location stepLocation(const Location &location); + Location stepToValidLocation(const Location &location); + + private: + bool decodeToken(Builder &b, const Token &token); +#define OP(op) bool decode##op(Builder &b, const Token &token); +#include "TokenOp.inc" +#undef OP + void buildBlocks(const Location &location); + void setCode(const std::u16string &code); + inline char16_t &getCode(int x, int y); + inline char16_t &getCode(const Location &location); + inline bool isBlank(char16_t ch); + + PassManager passManager; + Runtime &parent; + IRBuffer irBuffer; + Location cur; + size_t codeStride{ 0 }; + size_t codeHeight{ 0 }; + std::vector codeWidth; + std::vector code; + AsmJitRuntimePtr rt; +}; + +} // namespace aheuijit diff --git a/src/AheuiJIT/Util/Disasm.cpp b/src/AheuiJIT/Util/Disasm.cpp new file mode 100644 index 0000000..2d49b56 --- /dev/null +++ b/src/AheuiJIT/Util/Disasm.cpp @@ -0,0 +1,20 @@ +#include "Disasm.h" + +#include +#include + +void disassembleX86(void *buf, size_t size) { + csh handle; + cs_insn *insn; + cs_err err = cs_open(CS_ARCH_X86, CS_MODE_64, &handle); + size_t count = cs_disasm(handle, (uint8_t *)buf, size - 1, 0, 0, &insn); + if (count > 0) { + size_t j; + int n; + for (j = 0; j < count; j++) { + cs_insn *i = &(insn[j]); + fmt::print("0x{:x}: {} {}\n", reinterpret_cast(buf) + i->address, + i->mnemonic, i->op_str); + } + } +} diff --git a/src/AheuiJIT/Util/Disasm.h b/src/AheuiJIT/Util/Disasm.h new file mode 100644 index 0000000..2db98c2 --- /dev/null +++ b/src/AheuiJIT/Util/Disasm.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace aheuijit { + +void disassembleX86(void *buf, size_t size); + +} diff --git a/src/AheuiJIT/Util/Util.h b/src/AheuiJIT/Util/Util.h new file mode 100644 index 0000000..b610ad9 --- /dev/null +++ b/src/AheuiJIT/Util/Util.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +namespace aheuijit { + +static std::string covnert_utf16_to_utf8(const std::u16string &str) { + std::wstring_convert, char16_t> conv; + return conv.to_bytes(str); +} + +static std::string covnert_utf16_to_utf8(const char16_t &ch) { + std::u16string str; + str.push_back(ch); + return covnert_utf16_to_utf8(std::u16string(str)); +} + +static std::u16string covnert_utf8_to_utf16(const std::string &str) { + std::wstring_convert, char16_t> conv; + return conv.from_bytes(str.c_str()); +} + +#define ASSERT(pred) \ + if (!(pred)) { \ + std::cout << "Assertion failed: " << #pred; \ + std::terminate(); \ + } + +#ifdef AHEUI_JIT_LOG +#define LOG(format, ...) fmt::print(format + std::string("\n"), ##__VA_ARGS__); +#else +#define LOG(...) +#endif + +} // namespace aheuijit diff --git a/tests.png b/tests.png new file mode 100644 index 0000000..443c216 Binary files /dev/null and b/tests.png differ