├── .gitmodules ├── .clang-format ├── src ├── front │ ├── strpool.cpp │ ├── strpool.h │ ├── wrapper.h │ ├── wrapper.cpp │ ├── token.h │ ├── eeyore.l │ ├── tigger.l │ ├── tigger.y │ └── eeyore.y ├── vmconf.h ├── version.h ├── mem │ ├── dense.cpp │ ├── dense.h │ ├── sparse.h │ ├── pool.h │ └── sparse.cpp ├── debugger │ ├── minidbg │ │ ├── minieval.h │ │ ├── srcreader.h │ │ ├── srcreader.cpp │ │ ├── minieval.cpp │ │ ├── minidbg.h │ │ └── minidbg.cpp │ ├── debugger.h │ ├── debugger.cpp │ └── expreval.h ├── vm │ ├── symbol.h │ ├── symbol.cpp │ ├── vm.h │ ├── define.h │ ├── instcont.h │ ├── README.md │ ├── vm.cpp │ └── instcont.cpp ├── back │ ├── c │ │ ├── codegen.h │ │ ├── embed │ │ │ └── vm.c │ │ └── codegen.cpp │ ├── codegen.h │ └── codegen.cpp ├── main.cpp └── vmconf.cpp ├── .gitignore ├── cmake └── FindReadline.cmake ├── README.md ├── .github └── workflows │ └── build-test.yml ├── CHANGELOG.md └── CMakeLists.txt /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/xstl"] 2 | path = 3rdparty/xstl 3 | url = https://github.com/MaxXSoft/XSTL/ 4 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # Max's C++ coding style 2 | # By MaxXing, 2019 3 | 4 | BasedOnStyle: Google 5 | ColumnLimit: 76 6 | 7 | BreakBeforeBraces: Custom 8 | BraceWrapping: 9 | BeforeCatch: true 10 | BeforeElse: true 11 | -------------------------------------------------------------------------------- /src/front/strpool.cpp: -------------------------------------------------------------------------------- 1 | #include "front/strpool.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace { 7 | 8 | std::unordered_set strs; 9 | 10 | } // namespace 11 | 12 | const char *minivm::front::NewStr(const char *str) { 13 | return strs.insert(str).first->c_str(); 14 | } 15 | 16 | void minivm::front::FreeAllStrs() { 17 | strs.clear(); 18 | } 19 | -------------------------------------------------------------------------------- /src/front/strpool.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_FRONT_STRPOOL_H_ 2 | #define MINIVM_FRONT_STRPOOL_H_ 3 | 4 | namespace minivm::front { 5 | 6 | // NOTE: thread unsafe! 7 | 8 | // allocate memory for 'str', copy it's value into it 9 | // returns the pointer to that memory 10 | const char *NewStr(const char *str); 11 | 12 | // free all allocated strings 13 | void FreeAllStrs(); 14 | 15 | } // namespace minivm::front 16 | 17 | #endif // MINIVM_FRONT_STRPOOL_H_ 18 | -------------------------------------------------------------------------------- /src/vmconf.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_VMCONF_H_ 2 | #define MINIVM_VMCONF_H_ 3 | 4 | // MiniVM configuration 5 | 6 | #include 7 | 8 | #include "vm/vm.h" 9 | 10 | // type definition of VM initializer 11 | using VMInit = std::function; 12 | 13 | // initialize a Eeyore mode MiniVM instance 14 | void InitEeyoreVM(minivm::vm::VM &vm); 15 | // initialize a Tigger mode MiniVM instance 16 | void InitTiggerVM(minivm::vm::VM &vm); 17 | 18 | #endif // MINIVM_VMCONF_H_ 19 | -------------------------------------------------------------------------------- /src/version.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_VERSION_H_ 2 | #define MINIVM_VERSION_H_ 3 | 4 | // version information of MiniVM 5 | 6 | #ifndef APP_NAME 7 | #define APP_NAME "MiniVM Eeyore/Tigger Virtual Machine" 8 | #endif 9 | 10 | #ifndef APP_VERSION 11 | #define APP_VERSION "0.0.0" 12 | #endif 13 | 14 | #ifndef APP_VERSION_MAJOR 15 | #define APP_VERSION_MAJOR 0 16 | #endif 17 | 18 | #ifndef APP_VERSION_MINOR 19 | #define APP_VERSION_MINOR 0 20 | #endif 21 | 22 | #ifndef APP_VERSION_PATCH 23 | #define APP_VERSION_PATCH 0 24 | #endif 25 | 26 | #endif // MINIVM_VERSION_H_ 27 | -------------------------------------------------------------------------------- /src/front/wrapper.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_FRONT_WRAPPER_H_ 2 | #define MINIVM_FRONT_WRAPPER_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "vm/instcont.h" 8 | 9 | namespace minivm::front { 10 | 11 | // type definition of parser function 12 | using Parser = std::function; 13 | 14 | // Eeyore parser 15 | // returns false if parsing failed 16 | bool ParseEeyore(std::string_view file, vm::VMInstContainer &cont); 17 | 18 | // Tigger parser 19 | // returns false if parsing failed 20 | bool ParseTigger(std::string_view file, vm::VMInstContainer &cont); 21 | 22 | } // namespace minivm::front 23 | 24 | #endif // MINIVM_FRONT_WRAPPER_H_ 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/macos 2 | 3 | ### macOS ### 4 | *.DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | .com.apple.timemachine.donotpresent 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | # End of https://www.gitignore.io/api/macos 32 | 33 | # VS Code 34 | .vscode 35 | 36 | # Build & Debug 37 | build 38 | debug 39 | -------------------------------------------------------------------------------- /cmake/FindReadline.cmake: -------------------------------------------------------------------------------- 1 | # Search for the path containing library's headers 2 | find_path(Readline_ROOT_DIR 3 | NAMES include/readline/readline.h 4 | ) 5 | 6 | # Search for include directory 7 | find_path(Readline_INCLUDE_DIR 8 | NAMES readline/readline.h 9 | HINTS ${Readline_ROOT_DIR}/include 10 | ) 11 | 12 | # Search for library 13 | find_library(Readline_LIBRARY 14 | NAMES readline 15 | HINTS ${Readline_ROOT_DIR}/lib 16 | ) 17 | 18 | # Conditionally set READLINE_FOUND value 19 | if(Readline_INCLUDE_DIR AND Readline_LIBRARY) 20 | set(READLINE_FOUND TRUE) 21 | endif() 22 | 23 | # Hide these variables in cmake GUIs 24 | mark_as_advanced( 25 | Readline_ROOT_DIR 26 | Readline_INCLUDE_DIR 27 | Readline_LIBRARY 28 | ) 29 | -------------------------------------------------------------------------------- /src/mem/dense.cpp: -------------------------------------------------------------------------------- 1 | #include "mem/dense.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace minivm::mem; 8 | 9 | void DenseMemoryPool::FreeMems() { 10 | std::free(mems_); 11 | } 12 | 13 | MemId DenseMemoryPool::Allocate(std::uint32_t size, bool init) { 14 | // allocate memory 15 | auto id = mem_size_; 16 | mem_size_ += size; 17 | mems_ = reinterpret_cast(std::realloc(mems_, mem_size_)); 18 | std::memset(mems_ + id, init ? 0 : 0x98, size); 19 | return id; 20 | } 21 | 22 | void *DenseMemoryPool::GetAddress(MemId id) { 23 | if (id >= mem_size_) return nullptr; 24 | return mems_ + id; 25 | } 26 | 27 | void DenseMemoryPool::SaveState() { 28 | states_.push(mem_size_); 29 | } 30 | 31 | void DenseMemoryPool::RestoreState() { 32 | auto last_size = states_.top(); 33 | states_.pop(); 34 | mems_ = reinterpret_cast(std::realloc(mems_, last_size)); 35 | } 36 | -------------------------------------------------------------------------------- /src/debugger/minidbg/minieval.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_DEBUGGER_MINIDBG_MINIEVAL_H_ 2 | #define MINIVM_DEBUGGER_MINIDBG_MINIEVAL_H_ 3 | 4 | #include "debugger/expreval.h" 5 | #include "vm/vm.h" 6 | 7 | namespace minivm::debugger::minidbg { 8 | 9 | // expression evaluator for MiniVM 10 | class MiniEvaluator : public ExprEvaluatorBase { 11 | public: 12 | MiniEvaluator(vm::VM &vm) : vm_(vm) {} 13 | 14 | protected: 15 | std::optional GetValueOfSym(std::string_view sym) override; 16 | std::optional GetValueOfAddr(vm::VMOpr addr) override; 17 | 18 | private: 19 | // get value of symbol in environment 20 | std::optional GetSymVal(std::string_view sym); 21 | // get value of static registers or PC 22 | std::optional GetRegVal(std::string_view reg); 23 | 24 | // current MiniVM instance 25 | vm::VM &vm_; 26 | }; 27 | 28 | } // namespace minivm::debugger::minidbg 29 | 30 | #endif // MINIVM_DEBUGGER_MINIDBG_MINIEVAL_H_ 31 | -------------------------------------------------------------------------------- /src/mem/dense.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_MEM_DENSE_H_ 2 | #define MINIVM_MEM_DENSE_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "mem/pool.h" 9 | 10 | namespace minivm::mem { 11 | 12 | // dense memory pool 13 | // all memory will be allocated contiguously in one place 14 | class DenseMemoryPool : public MemoryPoolInterface { 15 | public: 16 | DenseMemoryPool() : mems_(nullptr), mem_size_(0) {} 17 | ~DenseMemoryPool() { FreeMems(); } 18 | 19 | MemId Allocate(std::uint32_t size, bool init) override; 20 | void *GetAddress(MemId id) override; 21 | void SaveState() override; 22 | void RestoreState() override; 23 | 24 | private: 25 | // free all allocated memories 26 | void FreeMems(); 27 | 28 | // all allocated memories 29 | std::uint8_t *mems_; 30 | // size of allocated memories 31 | std::uint32_t mem_size_; 32 | // stack of saved states 33 | std::stack states_; 34 | }; 35 | 36 | } // namespace minivm::mem 37 | 38 | #endif // MINIVM_MEM_DENSE_H_ 39 | -------------------------------------------------------------------------------- /src/mem/sparse.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_MEM_SPARSE_H_ 2 | #define MINIVM_MEM_SPARSE_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "mem/pool.h" 11 | 12 | namespace minivm::mem { 13 | 14 | // sparse memory pool 15 | // no boundary check for any accessing operation 16 | class SparseMemoryPool : public MemoryPoolInterface { 17 | public: 18 | SparseMemoryPool() : mem_size_(0) {} 19 | 20 | MemId Allocate(std::uint32_t size, bool init) override; 21 | void *GetAddress(MemId id) override; 22 | void SaveState() override; 23 | void RestoreState() override; 24 | 25 | private: 26 | using BytesPtr = std::unique_ptr; 27 | 28 | // all allocated memory 29 | std::map> mems_; 30 | // size of all allocated memory 31 | std::uint32_t mem_size_; 32 | // stack of saved states 33 | std::stack states_; 34 | }; 35 | 36 | } // namespace minivm::mem 37 | 38 | #endif // MINIVM_MEM_SPARSE_H_ 39 | -------------------------------------------------------------------------------- /src/mem/pool.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_MEM_POOL_H_ 2 | #define MINIVM_MEM_POOL_H_ 3 | 4 | #include 5 | #include 6 | 7 | namespace minivm::mem { 8 | 9 | // type of memory id 10 | using MemId = std::uint32_t; 11 | 12 | // interface of memory pool 13 | class MemoryPoolInterface { 14 | public: 15 | virtual ~MemoryPoolInterface() = default; 16 | 17 | // allocate a new memory with the specific size 18 | // returns memory id 19 | virtual MemId Allocate(std::uint32_t size, bool init) = 0; 20 | // get the memory base address of the specific memory id 21 | // returns 'nullptr' if failed 22 | virtual void *GetAddress(MemId id) = 0; 23 | 24 | // memory pool state manipulations 25 | // handle size of allocated memory only 26 | // don't care about the contents of memory 27 | // 28 | // save current state 29 | virtual void SaveState() = 0; 30 | // restore the previous state 31 | virtual void RestoreState() = 0; 32 | }; 33 | 34 | // pointer to memory pool 35 | using MemPoolPtr = std::unique_ptr; 36 | 37 | } // namespace minivm::mem 38 | 39 | #endif // MINIVM_MEM_POOL_H_ 40 | -------------------------------------------------------------------------------- /src/vm/symbol.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_VM_SYMBOL_H_ 2 | #define MINIVM_VM_SYMBOL_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "vm/define.h" 12 | 13 | namespace minivm::vm { 14 | 15 | // symbol pool, storing all symbols 16 | class SymbolPool { 17 | public: 18 | SymbolPool() {} 19 | 20 | // reset internal states 21 | void Reset(); 22 | 23 | // query & get id of the specific symbol 24 | // create a new symbol if not found 25 | SymId LogId(std::string_view symbol); 26 | // query id of the specific symbol 27 | std::optional FindId(std::string_view symbol) const; 28 | // query symbol by id 29 | std::optional FindSymbol(SymId id) const; 30 | 31 | private: 32 | using SymbolPtr = std::unique_ptr; 33 | 34 | // push a new symbol to pool 35 | void PushNewSymbol(std::string_view symbol); 36 | 37 | std::unordered_map defs_; 38 | std::vector pool_; 39 | }; 40 | 41 | } // namespace minivm::vm 42 | 43 | #endif // MINIVM_VM_SYMBOL_H_ 44 | -------------------------------------------------------------------------------- /src/vm/symbol.cpp: -------------------------------------------------------------------------------- 1 | #include "vm/symbol.h" 2 | 3 | #include 4 | 5 | using namespace minivm::vm; 6 | 7 | void SymbolPool::PushNewSymbol(std::string_view symbol) { 8 | auto sym_ptr = std::make_unique(symbol.size() + 1); 9 | symbol.copy(sym_ptr.get(), symbol.size()); 10 | sym_ptr[symbol.size()] = '\0'; 11 | pool_.push_back(std::move(sym_ptr)); 12 | } 13 | 14 | void SymbolPool::Reset() { 15 | defs_.clear(); 16 | pool_.clear(); 17 | } 18 | 19 | SymId SymbolPool::LogId(std::string_view symbol) { 20 | // try to find symbol 21 | auto it = defs_.find(symbol); 22 | if (it != defs_.end()) { 23 | return it->second; 24 | } 25 | else { 26 | // store to pool 27 | SymId id = pool_.size(); 28 | PushNewSymbol(symbol); 29 | // update symbol definition 30 | return defs_[pool_.back().get()] = id; 31 | } 32 | } 33 | 34 | std::optional SymbolPool::FindId(std::string_view symbol) const { 35 | auto it = defs_.find(symbol); 36 | if (it != defs_.end()) return it->second; 37 | return {}; 38 | } 39 | 40 | std::optional SymbolPool::FindSymbol(SymId id) const { 41 | if (id < pool_.size()) return pool_[id].get(); 42 | return {}; 43 | } 44 | -------------------------------------------------------------------------------- /src/debugger/minidbg/srcreader.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_DEBUGGER_MINIDBG_SRCREADER_H_ 2 | #define MINIVM_DEBUGGER_MINIDBG_SRCREADER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace minivm::debugger::minidbg { 12 | 13 | // source file reader 14 | class SourceReader { 15 | public: 16 | SourceReader(std::string_view src_file) : ifs_(std::string(src_file)) { 17 | InitTotalLines(); 18 | } 19 | 20 | // read the content of the specific line number in source file 21 | // returns 'nullopt' when 'line_num' is out of range 22 | std::optional ReadLine(std::uint32_t line_num); 23 | 24 | private: 25 | // initialize total line number 26 | void InitTotalLines(); 27 | // goto the specific line number in file 28 | void SeekLine(std::uint32_t line_num); 29 | 30 | // stream of source file 31 | std::ifstream ifs_; 32 | // total line number 33 | std::uint32_t total_lines_; 34 | // buffer of all read lines 35 | std::unordered_map lines_; 36 | }; 37 | 38 | } // namespace minivm::debugger::minidbg 39 | 40 | #endif // MINIVM_DEBUGGER_MINIDBG_SRCREADER_H_ 41 | -------------------------------------------------------------------------------- /src/debugger/minidbg/srcreader.cpp: -------------------------------------------------------------------------------- 1 | #include "debugger/minidbg/srcreader.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace minivm::debugger::minidbg; 10 | 11 | void SourceReader::InitTotalLines() { 12 | total_lines_ = std::count(std::istreambuf_iterator(ifs_), 13 | std::istreambuf_iterator(), '\n'); 14 | } 15 | 16 | void SourceReader::SeekLine(std::uint32_t line_num) { 17 | ifs_.seekg(std::ios::beg); 18 | for (std::uint32_t i = 0; i < line_num - 1; ++i) { 19 | ifs_.ignore(std::numeric_limits::max(), '\n'); 20 | } 21 | } 22 | 23 | std::optional SourceReader::ReadLine( 24 | std::uint32_t line_num) { 25 | // check if 'line_num' is invalid 26 | if (line_num > total_lines_) return {}; 27 | // try to find in buffer 28 | auto it = lines_.find(line_num); 29 | if (it != lines_.end()) { 30 | return it->second; 31 | } 32 | else { 33 | // read line from file 34 | SeekLine(line_num); 35 | std::string line; 36 | std::getline(ifs_, line); 37 | assert(ifs_); 38 | // add to buffer 39 | auto ret = lines_.insert({line_num, std::move(line)}); 40 | assert(ret.second); 41 | return ret.first->second; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/front/wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "front/wrapper.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "front/strpool.h" 8 | #include "xstl/style.h" 9 | 10 | namespace { 11 | 12 | // print file error to stderr 13 | bool PrintFileError() { 14 | using namespace xstl; 15 | std::cerr << style("Br") << "file error: "; 16 | std::perror(""); 17 | return false; 18 | } 19 | 20 | } // namespace 21 | 22 | using namespace minivm::vm; 23 | 24 | // defined in Flex/Bison generated files 25 | extern std::FILE *eeyore_in; 26 | int eeyore_parse(void *cont); 27 | extern std::FILE *tigger_in; 28 | int tigger_parse(void *cont); 29 | 30 | bool minivm::front::ParseEeyore(std::string_view file, 31 | VMInstContainer &cont) { 32 | if (!(eeyore_in = std::fopen(std::string(file).c_str(), "r"))) { 33 | return PrintFileError(); 34 | } 35 | auto ret = eeyore_parse(&cont); 36 | cont.SealContainer(); 37 | std::fclose(eeyore_in); 38 | FreeAllStrs(); 39 | return !ret; 40 | } 41 | 42 | bool minivm::front::ParseTigger(std::string_view file, 43 | VMInstContainer &cont) { 44 | if (!(tigger_in = std::fopen(std::string(file).c_str(), "r"))) { 45 | return PrintFileError(); 46 | } 47 | auto ret = tigger_parse(&cont); 48 | cont.SealContainer(); 49 | std::fclose(tigger_in); 50 | FreeAllStrs(); 51 | return !ret; 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MiniVM 2 | 3 | [![Build and Test](https://github.com/pku-minic/MiniVM/workflows/Build%20and%20Test/badge.svg)](https://github.com/pku-minic/MiniVM) 4 | 5 | MiniVM is a virtual machine for executing Eeyore/Tigger IR, which is designed for PKU compiler course. 6 | 7 | ## Building from Source 8 | 9 | Before building MiniVM, please make sure you have installed the following dependencies: 10 | 11 | * `cmake` 3.13 or later 12 | * C++ compiler supporting C++17 13 | * `flex` and `bison` 14 | * `readline` (optional, see [Building Without the Built-in Debugger](#building-without-the-built-in-debugger)) 15 | 16 | Then you can build this repository by executing the following command lines: 17 | 18 | ``` 19 | $ git clone --recursive https://github.com/pku-minic/MiniVM.git 20 | $ cd MiniVM 21 | $ mkdir build 22 | $ cd build 23 | $ cmake .. && make -j8 24 | ``` 25 | 26 | ### Building Without the Built-in Debugger 27 | 28 | You can turn the CMake option `NO_DEBUGGER` on to disable the built-in debugger (MiniDbg): 29 | 30 | ``` 31 | $ cmake -DNO_DEBUGGER=ON .. 32 | $ make -j8 33 | ``` 34 | 35 | With this option turned on, you can build MiniVM without `readline`. 36 | 37 | ## How does MiniVM Work? 38 | 39 | See [the documentation](src/vm/README.md) about the VM part of MiniVM. 40 | 41 | ## Changelog 42 | 43 | See [CHANGELOG.md](CHANGELOG.md) 44 | 45 | ## License 46 | 47 | Copyright (C) 2010-2021 MaxXing. License GPLv3. 48 | -------------------------------------------------------------------------------- /src/front/token.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_FRONT_TOKEN_H_ 2 | #define MINIVM_FRONT_TOKEN_H_ 3 | 4 | // all supported operators 5 | #define TOKEN_OPERATORS(e) \ 6 | e(Add, "+") e(Sub, "-") e(Mul, "*") e(Div, "/") e(Mod, "%") \ 7 | e(Not, "!") e(Ne, "!=") e(Eq, "==") e(Gt, ">") e(Lt, "<") \ 8 | e(Ge, ">=") e(Le, "<=") e(Or, "||") e(And, "&&") 9 | // all supported registers 10 | #define TOKEN_REGISTERS(e) \ 11 | e(X0, "x0") e(S0, "s0") e(S1, "s1") e(S2, "s2") e(S3, "s3") \ 12 | e(S4, "s4") e(S5, "s5") e(S6, "s6") e(S7, "s7") e(S8, "s8") \ 13 | e(S9, "s9") e(S10, "s10") e(S11, "s11") e(T0, "t0") \ 14 | e(T1, "t1") e(T2, "t2") e(T3, "t3") e(T4, "t4") e(T5, "t5") \ 15 | e(T6, "t6") e(A0, "a0") e(A1, "a1") e(A2, "a2") e(A3, "a3") \ 16 | e(A4, "a4") e(A5, "a5") e(A6, "a6") e(A7, "a7") 17 | 18 | // expand first element to comma-separated list 19 | #define TOKEN_EXPAND_FIRST(i, ...) i, 20 | // expand second element to comma-separated list 21 | #define TOKEN_EXPAND_SECOND(i, j, ...) j, 22 | // expand elements to ones 23 | #define TOKEN_EXPAND_ONES(...) 1+ 24 | // count all elements 25 | #define TOKEN_COUNT(e) (e(TOKEN_EXPAND_ONES)0) 26 | 27 | namespace minivm::front { 28 | 29 | enum class TokenOp { TOKEN_OPERATORS(TOKEN_EXPAND_FIRST) }; 30 | enum class TokenReg { TOKEN_REGISTERS(TOKEN_EXPAND_FIRST) }; 31 | 32 | } // namespace minivm::front 33 | 34 | #endif // MINIVM_FRONT_TOKEN_H_ 35 | -------------------------------------------------------------------------------- /src/back/c/codegen.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_BACK_C_CODEGEN_H_ 2 | #define MINIVM_BACK_C_CODEGEN_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "back/codegen.h" 10 | #include "debugger/minidbg/srcreader.h" 11 | 12 | namespace minivm::back::c { 13 | 14 | // C code generator 15 | class CCodeGen : public CodeGenerator { 16 | public: 17 | CCodeGen(const vm::VMInstContainer &cont, bool tigger_mode) 18 | : CodeGenerator(cont), tigger_mode_(tigger_mode), 19 | src_reader_(cont.src_file()) {} 20 | 21 | void Dump(std::ostream &os) const override; 22 | 23 | protected: 24 | void Reset() override; 25 | void GenerateOnFunc(vm::VMAddr pc, const FuncBody &func) override; 26 | void GenerateOnEntry(vm::VMAddr pc, const FuncBody &func) override; 27 | 28 | private: 29 | // get symbol name for C code generation 30 | std::optional GetSymbol(vm::SymId sym_id, vm::VMAddr pc); 31 | // push value to stack 32 | // generate instruction 33 | bool GenerateInst(std::ostringstream &oss, vm::VMAddr pc, 34 | const vm::VMInst &inst); 35 | 36 | // generate Tigger mode code 37 | bool tigger_mode_; 38 | // string stream for storing generated C code 39 | std::ostringstream global_, code_; 40 | // last line position 41 | std::uint32_t last_line_; 42 | // source file reader 43 | debugger::minidbg::SourceReader src_reader_; 44 | }; 45 | 46 | } // namespace minivm::back::c 47 | 48 | #endif // MINIVM_BACK_C_CODEGEN_H_ 49 | -------------------------------------------------------------------------------- /src/mem/sparse.cpp: -------------------------------------------------------------------------------- 1 | #include "mem/sparse.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace minivm::mem; 8 | 9 | namespace { 10 | 11 | // make a unique pointer of uninitialized array type 12 | template 13 | std::unique_ptr MakeUninit(std::uint32_t size) { 14 | auto ptr = std::unique_ptr(new std::remove_extent_t[size]); 15 | std::memset(ptr.get(), 0x5b, size * sizeof(std::remove_extent_t)); 16 | return ptr; 17 | } 18 | 19 | } // namespace 20 | 21 | MemId SparseMemoryPool::Allocate(std::uint32_t size, bool init) { 22 | // allocate memory 23 | auto id = mem_size_; 24 | auto mem = init ? std::make_unique(size) 25 | : MakeUninit(size); 26 | auto ret = mems_.insert({id, std::move(mem)}).second; 27 | static_cast(ret); 28 | assert(ret); 29 | // update allocated memory size 30 | mem_size_ += size; 31 | return id; 32 | } 33 | 34 | void *SparseMemoryPool::GetAddress(MemId id) { 35 | if (id >= mem_size_) return nullptr; 36 | auto it = mems_.lower_bound(id); 37 | if (it == mems_.end()) return nullptr; 38 | return it->second.get() + (id - it->first); 39 | } 40 | 41 | void SparseMemoryPool::SaveState() { 42 | states_.push(mem_size_); 43 | } 44 | 45 | void SparseMemoryPool::RestoreState() { 46 | // restore to the previous memory size 47 | mem_size_ = states_.top(); 48 | states_.pop(); 49 | // remove all allocated memory after current state 50 | auto it = mems_.find(mem_size_); 51 | if (it == mems_.end()) return; 52 | mems_.erase(mems_.begin(), ++it); 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | env: 12 | BUILD_TYPE: Debug 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | if: "!contains(github.event.head_commit.message, 'skip-ci')" 18 | timeout-minutes: 30 19 | strategy: 20 | matrix: 21 | no-debugger: ['OFF', 'ON'] 22 | config: 23 | - {cc: gcc-9, cxx: g++-9} 24 | - {cc: gcc-10, cxx: g++-10} 25 | - {cc: clang, cxx: clang++} 26 | 27 | steps: 28 | - name: Checkout MiniVM 29 | uses: actions/checkout@v2 30 | with: 31 | submodules: recursive 32 | 33 | - name: Checkout Test Scripts 34 | uses: actions/checkout@v2 35 | with: 36 | repository: pku-minic/MiniVM-test 37 | path: test 38 | submodules: true 39 | 40 | - name: Create Build Environment 41 | run: cmake -E make_directory ${{runner.workspace}}/build 42 | 43 | - name: Configure CMake 44 | shell: bash 45 | working-directory: ${{runner.workspace}}/build 46 | env: 47 | CC: ${{matrix.config.cc}} 48 | CXX: ${{matrix.config.cxx}} 49 | run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DNO_DEBUGGER=${{matrix.no-debugger}} 50 | 51 | - name: Build 52 | working-directory: ${{runner.workspace}}/build 53 | shell: bash 54 | run: cmake --build . --config $BUILD_TYPE 55 | 56 | - name: Test 57 | working-directory: ${{runner.workspace}}/build 58 | run: $GITHUB_WORKSPACE/test/run_test.sh ./minivm 59 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to the MiniVM will be documented in this file. 4 | 5 | ## 0.2.1 - 2021-12-03 6 | 7 | ### Changed 8 | 9 | * MiniVM will disrupt all local allocated memories. 10 | * MiniVM will not zero initialize Tigger registers. 11 | 12 | ## 0.2.0 - 2021-10-14 13 | 14 | ### Added 15 | 16 | * C backend for converting Eeyore/Tigger programs into C programs. 17 | 18 | ### Fixed 19 | 20 | * Bug about reading/writing `x0` register in Tigger mode. 21 | 22 | ## 0.1.4 - 2021-10-13 23 | 24 | ### Added 25 | 26 | * Detection about functions without `return` statements. 27 | 28 | ### Changed 29 | 30 | * MiniVM will not perform initialization in the local allocation, but will still initialize all global memory. 31 | * Tigger library functions will disrupt all caller-saved registers. 32 | 33 | ### Fixed 34 | 35 | * Problem about printing error message for the `x` command in MiniDbg. 36 | 37 | ## 0.1.3 - 2021-05-12 38 | 39 | ### Fixed 40 | 41 | * Problem about parsing non-existent files. 42 | * Bug about handling function return values. 43 | 44 | ## 0.1.2 - 2021-05-06 45 | 46 | ### Added 47 | 48 | * Supported `NO_DEBUGGER` mode to disable the built-in debugger (MiniDbg). 49 | 50 | ### Fixed 51 | 52 | * Made GCC/G++ happy. 53 | 54 | ## 0.1.1 - 2021-04-08 55 | 56 | ### Fixed 57 | 58 | * Problem about the line number in front end error message. 59 | 60 | ## 0.1.0 - 2021-04-03 61 | 62 | ### Added 63 | 64 | * The brand new debugger: MiniDbg. 65 | 66 | ### Changed 67 | 68 | * Supported returning error code when VM performs an error. 69 | 70 | ### Fixed 71 | 72 | * Bug in `Break` instruction. 73 | 74 | ## 0.0.1 - 2020-12-21 75 | -------------------------------------------------------------------------------- /src/debugger/minidbg/minieval.cpp: -------------------------------------------------------------------------------- 1 | #include "debugger/minidbg/minieval.h" 2 | 3 | #include "front/token.h" 4 | #include "vm/define.h" 5 | 6 | using namespace minivm::debugger::minidbg; 7 | using namespace minivm::vm; 8 | 9 | namespace { 10 | 11 | // name of static registers 12 | constexpr std::string_view kRegNames[] = { 13 | TOKEN_REGISTERS(TOKEN_EXPAND_SECOND) 14 | }; 15 | 16 | } // namespace 17 | 18 | std::optional MiniEvaluator::GetSymVal(std::string_view sym) { 19 | // try to get symbol id 20 | auto id = vm_.sym_pool().FindId(sym); 21 | if (!id) return {}; 22 | // try to find in current environment 23 | const auto &env = vm_.env_addr_pair().first; 24 | auto it = env->find(*id); 25 | if (it != env->end()) return it->second; 26 | // try to find in global environment 27 | it = vm_.global_env()->find(*id); 28 | if (it != vm_.global_env()->end()) return it->second; 29 | return {}; 30 | } 31 | 32 | std::optional MiniEvaluator::GetRegVal(std::string_view reg) { 33 | if (reg == "pc") { 34 | return vm_.pc(); 35 | } 36 | else { 37 | // check if in Tigger mode 38 | const auto &env = vm_.env_addr_pair().first; 39 | auto id = vm_.sym_pool().FindId(kVMFrame); 40 | if (!id || env->find(*id) == env->end()) return {}; 41 | // find register by name 42 | for (RegId i = 0; i < TOKEN_COUNT(TOKEN_REGISTERS); ++i) { 43 | if (kRegNames[i] == reg) return vm_.regs(i); 44 | } 45 | return {}; 46 | } 47 | } 48 | 49 | std::optional MiniEvaluator::GetValueOfSym(std::string_view sym) { 50 | static_assert(kVMFrame[0] == '$'); 51 | return sym == kVMFrame || sym[0] != '$' ? GetSymVal(sym) 52 | : GetRegVal(sym.substr(1)); 53 | } 54 | 55 | std::optional MiniEvaluator::GetValueOfAddr(VMOpr addr) { 56 | // try to find in memory pool 57 | auto ptr = vm_.mem_pool()->GetAddress(addr); 58 | if (!ptr) return {}; 59 | return *reinterpret_cast(ptr); 60 | } 61 | -------------------------------------------------------------------------------- /src/front/eeyore.l: -------------------------------------------------------------------------------- 1 | %option yylineno 2 | %option noyywrap 3 | %option nounput 4 | %option noinput 5 | %option never-interactive 6 | 7 | %{ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "front/token.h" 14 | #include "front/strpool.h" 15 | 16 | #include "eeyore.parser.hpp" 17 | 18 | using namespace minivm::front; 19 | 20 | // get 'op_val' of the specific operator 21 | static TokenOp GetOpVal(std::string_view op); 22 | 23 | %} 24 | 25 | WhiteSp [ \t\r\v\f] 26 | Comment \/\/.* 27 | Label l[0-9]+ 28 | Function f_[_a-zA-Z][_a-zA-Z0-9]* 29 | Num -?[1-9][0-9]*|0 30 | Symbol [Ttp][0-9]+ 31 | Op [\+\-\*\/\%\!] 32 | LogicOp !=|==|>|<|>=|<=|\|\||&& 33 | 34 | %% 35 | 36 | {WhiteSp} { /* ignore white spaces */ } 37 | {Comment} { /* ignore line comments */ } 38 | 39 | "\n" { eeyore_lloc.first_line = yylineno; 40 | eeyore_lloc.last_line = yylineno; 41 | return EOL; } 42 | 43 | "var" { return VAR; } 44 | "if" { return IF; } 45 | "goto" { return GOTO; } 46 | "param" { return PARAM; } 47 | "call" { return CALL; } 48 | "return" { return RETURN; } 49 | "end" { return END; } 50 | 51 | {Label} { eeyore_lval.str_val = NewStr(yytext); return LABEL; } 52 | {Function} { eeyore_lval.str_val = NewStr(yytext); return FUNCTION; } 53 | {Symbol} { eeyore_lval.str_val = NewStr(yytext); return SYMBOL; } 54 | {Num} { eeyore_lval.int_val = std::atoi(yytext); return NUM; } 55 | [\[\]:=] { return yytext[0]; } 56 | {Op} { eeyore_lval.op_val = GetOpVal(yytext); return OP; } 57 | {LogicOp} { eeyore_lval.op_val = GetOpVal(yytext); return LOGICOP; } 58 | 59 | %% 60 | 61 | static TokenOp GetOpVal(std::string_view op) { 62 | const char *kOpStr[] = {TOKEN_OPERATORS(TOKEN_EXPAND_SECOND)}; 63 | int op_val = 0; 64 | for (const auto &i : kOpStr) { 65 | if (op == i) return static_cast(op_val); 66 | ++op_val; 67 | } 68 | assert(false); 69 | return TokenOp::Add; 70 | } 71 | -------------------------------------------------------------------------------- /src/back/codegen.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_BACK_CODEGEN_H_ 2 | #define MINIVM_BACK_CODEGEN_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "vm/instcont.h" 11 | 12 | namespace minivm::back { 13 | 14 | // code generator for Gopher 15 | class CodeGenerator { 16 | public: 17 | CodeGenerator(const vm::VMInstContainer &cont) : cont_(cont) {} 18 | virtual ~CodeGenerator() = default; 19 | 20 | // generate code 21 | void Generate(); 22 | 23 | // dump generated code to the specific stream 24 | virtual void Dump(std::ostream &os) const = 0; 25 | 26 | // getters 27 | bool has_error() const { return has_error_; } 28 | 29 | protected: 30 | // function body 31 | using FuncBody = std::vector; 32 | 33 | // check if there is a label on the specific address 34 | bool IsLabel(vm::VMAddr addr) const { return labels_.count(addr); } 35 | // print error message to stderr 36 | void LogError(std::string_view message, vm::VMAddr pc); 37 | 38 | // reset internal state 39 | virtual void Reset() {}; 40 | // generate code on function 41 | virtual void GenerateOnFunc(vm::VMAddr pc, const FuncBody &func) = 0; 42 | // generate code on entry function 43 | virtual void GenerateOnEntry(vm::VMAddr pc, const FuncBody &func) = 0; 44 | 45 | // getters 46 | const vm::VMInstContainer &cont() const { return cont_; } 47 | 48 | private: 49 | // collect all label information 50 | void CollectLabelInfo(); 51 | // split code into functions 52 | void BuildFunctions(); 53 | 54 | // instruction container 55 | const vm::VMInstContainer &cont_; 56 | // error flag 57 | bool has_error_; 58 | // label definitions 59 | std::unordered_set labels_; 60 | // pc of entry function 61 | vm::VMAddr entry_pc_; 62 | // functions 63 | std::vector> funcs_; 64 | // entry function 65 | FuncBody entry_func_; 66 | }; 67 | 68 | } // namespace minivm::back 69 | 70 | #endif // MINIVM_BACK_CODEGEN_H_ 71 | -------------------------------------------------------------------------------- /src/front/tigger.l: -------------------------------------------------------------------------------- 1 | %option yylineno 2 | %option noyywrap 3 | %option nounput 4 | %option noinput 5 | %option never-interactive 6 | 7 | %{ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "front/token.h" 14 | #include "front/strpool.h" 15 | 16 | #include "tigger.parser.hpp" 17 | 18 | using namespace minivm::front; 19 | 20 | // get 'op_val' of the specific operator 21 | static TokenOp GetOpVal(std::string_view op); 22 | // get 'reg_val' of the specific register 23 | static TokenReg GetRegVal(std::string_view reg); 24 | 25 | %} 26 | 27 | WhiteSp [ \t\r\v\f] 28 | Comment \/\/.* 29 | Label l[0-9]+ 30 | Function f_[_a-zA-Z][_a-zA-Z0-9]* 31 | Num -?[1-9][0-9]*|0 32 | Variable v[0-9]+ 33 | Register x0|s[0-9]|s1[01]|t[0-6]|a[0-7] 34 | Op [\+\-\*\/\%\!] 35 | LogicOp !=|==|>|<|>=|<=|\|\||&& 36 | 37 | %% 38 | 39 | {WhiteSp} { /* ignore white spaces */ } 40 | {Comment} { /* ignore line comments */ } 41 | 42 | "\n" { tigger_lloc.first_line = yylineno; 43 | tigger_lloc.last_line = yylineno; 44 | return EOL; } 45 | 46 | "if" { return IF; } 47 | "goto" { return GOTO; } 48 | "call" { return CALL; } 49 | "return" { return RETURN; } 50 | "end" { return END; } 51 | "load" { return LOAD; } 52 | "store" { return STORE; } 53 | "loadaddr" { return LOADADDR; } 54 | "malloc" { return MALLOC; } 55 | 56 | {Label} { tigger_lval.str_val = NewStr(yytext); return LABEL; } 57 | {Function} { tigger_lval.str_val = NewStr(yytext); return FUNCTION; } 58 | {Variable} { tigger_lval.str_val = NewStr(yytext); return VARIABLE; } 59 | {Num} { tigger_lval.int_val = std::atoi(yytext); return NUM; } 60 | [\[\]:=] { return yytext[0]; } 61 | {Register} { tigger_lval.reg_val = GetRegVal(yytext); return REG; } 62 | {Op} { tigger_lval.op_val = GetOpVal(yytext); return OP; } 63 | {LogicOp} { tigger_lval.op_val = GetOpVal(yytext); return LOGICOP; } 64 | 65 | %% 66 | 67 | static TokenOp GetOpVal(std::string_view op) { 68 | const char *kOpStr[] = {TOKEN_OPERATORS(TOKEN_EXPAND_SECOND)}; 69 | int op_val = 0; 70 | for (const auto &i : kOpStr) { 71 | if (op == i) return static_cast(op_val); 72 | ++op_val; 73 | } 74 | assert(false); 75 | return TokenOp::Add; 76 | } 77 | 78 | static TokenReg GetRegVal(std::string_view reg) { 79 | const char *kRegStr[] = {TOKEN_REGISTERS(TOKEN_EXPAND_SECOND)}; 80 | int reg_val = 0; 81 | for (const auto &i : kRegStr) { 82 | if (reg == i) return static_cast(reg_val); 83 | ++reg_val; 84 | } 85 | assert(false); 86 | return TokenReg::X0; 87 | } 88 | -------------------------------------------------------------------------------- /src/back/codegen.cpp: -------------------------------------------------------------------------------- 1 | #include "back/codegen.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "xstl/style.h" 8 | 9 | using namespace minivm::back; 10 | using namespace minivm::vm; 11 | 12 | void CodeGenerator::CollectLabelInfo() { 13 | // traverse all instructions 14 | for (VMAddr i = 0; i < cont_.inst_count(); ++i) { 15 | const auto &inst = cont_.insts()[i]; 16 | // handle by opcode 17 | switch (static_cast(inst.op)) { 18 | case InstOp::Bnz: { 19 | // in-block label 20 | labels_.insert(inst.opr); 21 | break; 22 | } 23 | case InstOp::Jmp: { 24 | // check if is the first instruction (Jmp kVMEntry) 25 | if (!i) { 26 | // treat 'kVMEntry' as the entry function 27 | entry_pc_ = inst.opr; 28 | } 29 | else { 30 | // in-block label 31 | labels_.insert(inst.opr); 32 | } 33 | break; 34 | } 35 | // ignore all other instructions 36 | default:; 37 | } 38 | } 39 | } 40 | 41 | void CodeGenerator::BuildFunctions() { 42 | FuncBody *cur_func = nullptr; 43 | // traverse all instructions 44 | // except the first instruction (Jmp kVMEntry) 45 | assert(static_cast(cont_.insts()->op) == InstOp::Jmp); 46 | for (VMAddr i = 1; i < cont_.inst_count(); ++i) { 47 | // check if is boundary of a function 48 | if (i == entry_pc_) { 49 | cur_func = &entry_func_; 50 | } 51 | else if (cont_.func_pcs().count(i)) { 52 | cur_func = &funcs_.emplace_back(i, FuncBody()).second; 53 | } 54 | // push instruction to the current function 55 | cur_func->push_back(cont_.insts()[i]); 56 | } 57 | } 58 | 59 | void CodeGenerator::LogError(std::string_view message, vm::VMAddr pc) { 60 | using namespace xstl; 61 | std::cerr << style("Br") << "error "; 62 | // try to get line number 63 | auto line_num = cont_.FindLineNum(pc); 64 | if (line_num) { 65 | std::cerr << style("B") << "(line " << *line_num << "): "; 66 | } 67 | else { 68 | std::cerr << style("B") << "(pc " << pc << "): "; 69 | } 70 | // print error message 71 | std::cerr << message << std::endl; 72 | has_error_ = true; 73 | } 74 | 75 | void CodeGenerator::Generate() { 76 | // reset internal state 77 | has_error_ = false; 78 | labels_.clear(); 79 | funcs_.clear(); 80 | entry_func_.clear(); 81 | Reset(); 82 | // generate labels & functions 83 | CollectLabelInfo(); 84 | BuildFunctions(); 85 | // generate on all functions 86 | for (const auto &[pc, func] : funcs_) { 87 | GenerateOnFunc(pc, func); 88 | } 89 | GenerateOnEntry(entry_pc_, entry_func_); 90 | } 91 | -------------------------------------------------------------------------------- /src/debugger/debugger.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_DEBUGGER_DEBUGGER_H_ 2 | #define MINIVM_DEBUGGER_DEBUGGER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace minivm::debugger { 14 | 15 | // command line interface of debugger 16 | class DebuggerBase { 17 | public: 18 | DebuggerBase(std::string_view prompt) : prompt_(prompt) { 19 | InitSignalHandler(); 20 | InitCommands(); 21 | RegisterDebugger(); 22 | } 23 | virtual ~DebuggerBase() { UnregisterDebugger(); } 24 | 25 | // setters 26 | void set_prompt(std::string_view prompt) { prompt_ = prompt; } 27 | 28 | protected: 29 | // debugger command handler, returns true if need to quit CLI 30 | using CmdHandler = std::function; 31 | // 'sigint' handler 32 | using SigIntHandler = std::function; 33 | 34 | // enter command line interface 35 | void EnterCLI(); 36 | // register a command 37 | void RegisterCommand(std::string_view cmd, std::string_view abbr, 38 | CmdHandler handler, std::string_view args, 39 | std::string_view description, 40 | std::string_view details); 41 | 42 | // setters 43 | void set_sigint_handler(SigIntHandler handler) { 44 | sigint_handler_ = handler; 45 | } 46 | 47 | private: 48 | // command line information 49 | struct CmdInfo { 50 | std::string_view name; 51 | std::string abbr; 52 | CmdHandler handler; 53 | std::string args; 54 | std::string description; 55 | std::string details; 56 | }; 57 | 58 | // 'sigint' handler 59 | static void SignalHandler(int sig); 60 | // initialize 'sigint' handler 61 | static void InitSignalHandler(); 62 | // register the current debugger 63 | void RegisterDebugger(); 64 | // unregister the current debugger 65 | void UnregisterDebugger(); 66 | 67 | // initialize command map 68 | void InitCommands(); 69 | // get command info by name or abbreviation 70 | // show error message when command not found 71 | const CmdInfo *GetCommandInfo(const std::string &cmd) const; 72 | // parse command line, returns true if need to quit CLI 73 | bool ParseCommand(std::istream &is); 74 | // 'help' command 75 | bool ShowHelpInfo(std::istream &is); 76 | 77 | // flag for signal handler register status 78 | static volatile std::sig_atomic_t sig_registered_; 79 | // flag for enable/disable signal handler 80 | static volatile std::sig_atomic_t sig_disabled_; 81 | // all active debugger instances 82 | static std::unordered_set dbg_insts_; 83 | // 'sigint' handler 84 | SigIntHandler sigint_handler_; 85 | 86 | // prompt of command line interface 87 | std::string prompt_; 88 | // all registered commands 89 | // order should be maintained because we will 90 | // traverse it and show some messages 91 | std::map cmds_; 92 | // abbreviation of commands 93 | std::unordered_map abbrs_; 94 | }; 95 | 96 | } // namespace minivm::debugger 97 | 98 | #endif // MINIVM_DEBUGGER_DEBUGGER_H_ 99 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(MiniVM VERSION "0.2.1") 3 | 4 | # set CMake module path 5 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} 6 | ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 7 | 8 | # # set compiler path 9 | # set(CMAKE_C_COMPILER "/usr/local/opt/gcc/bin/gcc-10") 10 | # set(CMAKE_CXX_COMPILER "/usr/local/opt/gcc/bin/g++-10") 11 | # set(CMAKE_CXX_COMPILER_ID "GNU") 12 | 13 | # C++17 standard support 14 | set(CMAKE_CXX_STANDARD 17) 15 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 16 | set(CMAKE_CXX_EXTENSIONS OFF) 17 | 18 | # enable all warnings and treat them as errors 19 | if(MSVC) 20 | add_compile_options(/W3 /WX) 21 | else() 22 | # disable warnings caused by old version of Flex 23 | add_compile_options(-Wall -Werror -Wno-register) 24 | endif() 25 | 26 | # definitions about version information 27 | add_compile_definitions(APP_NAME="MiniVM Eeyore/Tigger Virtual Machine") 28 | add_compile_definitions(APP_VERSION="${PROJECT_VERSION}") 29 | add_compile_definitions(APP_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}) 30 | add_compile_definitions(APP_VERSION_MINOR=${PROJECT_VERSION_MINOR}) 31 | add_compile_definitions(APP_VERSION_PATCH=${PROJECT_VERSION_PATCH}) 32 | 33 | # no debugger: disable the built-in debugger of MiniVM 34 | option(NO_DEBUGGER "disable the built-in debugger" OFF) 35 | if(NO_DEBUGGER) 36 | message("The built-in debugger (MiniDbg) has been disabled.") 37 | add_compile_definitions(NO_DEBUGGER) 38 | endif() 39 | 40 | # find Flex/Bison 41 | find_package(FLEX REQUIRED) 42 | find_package(BISON REQUIRED) 43 | 44 | # find Readline 45 | if(NOT NO_DEBUGGER) 46 | find_package(Readline) 47 | endif() 48 | 49 | # generate lexer/parser of Eeyore 50 | flex_target(EeyoreLexer src/front/eeyore.l 51 | ${CMAKE_CURRENT_BINARY_DIR}/eeyore.lexer.cpp 52 | COMPILE_FLAGS "-P eeyore_") 53 | bison_target(EeyoreParser src/front/eeyore.y 54 | ${CMAKE_CURRENT_BINARY_DIR}/eeyore.parser.cpp 55 | COMPILE_FLAGS "-p eeyore_") 56 | add_flex_bison_dependency(EeyoreLexer EeyoreParser) 57 | 58 | # generate lexer/parser of Tigger 59 | flex_target(TiggerLexer src/front/tigger.l 60 | ${CMAKE_CURRENT_BINARY_DIR}/tigger.lexer.cpp 61 | COMPILE_FLAGS "-P tigger_") 62 | bison_target(TiggerParser src/front/tigger.y 63 | ${CMAKE_CURRENT_BINARY_DIR}/tigger.parser.cpp 64 | COMPILE_FLAGS "-p tigger_") 65 | add_flex_bison_dependency(TiggerLexer TiggerParser) 66 | 67 | # project include directories 68 | include_directories(src) 69 | include_directories(3rdparty/xstl) 70 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) 71 | if(NOT NO_DEBUGGER) 72 | include_directories(${Readline_INCLUDE_DIR}) 73 | endif() 74 | 75 | # embedded text files 76 | file(GLOB_RECURSE EMBEDDED_FILES "src/back/c/embed/*.c") 77 | 78 | # all of C++ source files 79 | file(GLOB_RECURSE SOURCES "src/*.cpp") 80 | if (NO_DEBUGGER) 81 | file(GLOB_RECURSE DEBUGGER_SRCS "src/debugger/*.cpp") 82 | # do not remove 'srcreader.cpp' as it's used by the C backend 83 | list(REMOVE_ITEM DEBUGGER_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/minidbg/srcreader.cpp") 84 | list(REMOVE_ITEM SOURCES ${DEBUGGER_SRCS}) 85 | endif() 86 | list(REMOVE_ITEM SOURCES ${EMBEDDED_FILES}) 87 | set(SOURCES ${SOURCES} ${FLEX_EeyoreLexer_OUTPUTS} 88 | ${BISON_EeyoreParser_OUTPUT_SOURCE}) 89 | set(SOURCES ${SOURCES} ${FLEX_TiggerLexer_OUTPUTS} 90 | ${BISON_TiggerParser_OUTPUT_SOURCE}) 91 | 92 | # executable 93 | add_executable(minivm ${SOURCES}) 94 | if(NOT NO_DEBUGGER) 95 | target_link_libraries(minivm ${Readline_LIBRARY}) 96 | endif() 97 | -------------------------------------------------------------------------------- /src/vm/vm.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_VM_VM_H_ 2 | #define MINIVM_VM_VM_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "vm/define.h" 15 | #include "vm/symbol.h" 16 | #include "vm/instcont.h" 17 | #include "mem/pool.h" 18 | 19 | namespace minivm::vm { 20 | 21 | // MiniVM instance 22 | class VM { 23 | public: 24 | // environment 25 | using Environment = std::unordered_map; 26 | using EnvPtr = std::shared_ptr; 27 | // pair of environment and function return address 28 | using EnvAddrPair = std::pair; 29 | // static registers 30 | // external functions 31 | using ExtFunc = std::function; 32 | 33 | VM(SymbolPool &sym_pool, VMInstContainer &cont) 34 | : sym_pool_(sym_pool), cont_(cont) {} 35 | 36 | // register an external function 37 | bool RegisterFunction(std::string_view name, ExtFunc func); 38 | // read the value of the parameter in current memory pool 39 | std::optional GetParamFromCurPool(std::size_t param_id) const; 40 | 41 | // reset internal states 42 | void Reset(); 43 | // run VM, 'Reset' method must be called before 44 | // returns top of stack (success) or 'nullopt' (failed) 45 | std::optional Run(); 46 | 47 | // setters 48 | // set memory pool 49 | void set_mem_pool(mem::MemPoolPtr mem_pool) { 50 | mem_pool_ = std::move(mem_pool); 51 | } 52 | // set count of static registers 53 | void set_static_reg_count(std::uint32_t count) { 54 | regs_.clear(); 55 | regs_.resize(count, 0xdeadc0de); 56 | } 57 | // set id of return value register 58 | void set_ret_reg_id(RegId ret_reg_id) { ret_reg_id_ = ret_reg_id; } 59 | 60 | // getters 61 | // symbol pool 62 | SymbolPool &sym_pool() { return sym_pool_; } 63 | // instruction container 64 | VMInstContainer &cont() { return cont_; } 65 | // program counter 66 | VMAddr pc() const { return pc_; } 67 | // operand stack 68 | std::stack &oprs() { return oprs_; } 69 | // memory pool 70 | const mem::MemPoolPtr &mem_pool() const { return mem_pool_; } 71 | // current environment & return address 72 | EnvAddrPair &env_addr_pair() { return envs_.top(); } 73 | // global environment 74 | const EnvPtr &global_env() const { return global_env_; } 75 | // static registers 76 | VMOpr ®s(RegId id) { return regs_[id]; } 77 | // error code 78 | std::size_t error_code() const { return error_code_; } 79 | 80 | private: 81 | // update the error code, and print the related error message to stderr 82 | void LogError(std::size_t code); 83 | // pop value from stack and return it 84 | VMOpr PopValue(); 85 | // get reference of the top of stack 86 | VMOpr &GetOpr(); 87 | // get address of memory by id 88 | VMOpr *GetAddrById(mem::MemId id); 89 | // get address of memory by symbol 90 | VMOpr *GetAddrBySym(SymId sym); 91 | // make a new environment 92 | EnvPtr MakeEnv(); 93 | // perform initialization before function call 94 | void InitFuncCall(); 95 | 96 | // symbol pool 97 | SymbolPool &sym_pool_; 98 | // instruction container 99 | VMInstContainer &cont_; 100 | // program counter 101 | VMAddr pc_; 102 | // operand stack 103 | std::stack oprs_; 104 | // memory pool 105 | mem::MemPoolPtr mem_pool_; 106 | // environment stack 107 | std::stack envs_; 108 | // global environment 109 | EnvPtr global_env_; 110 | // static registers 111 | std::vector regs_; 112 | // id of return value register 113 | RegId ret_reg_id_; 114 | // external function table 115 | std::unordered_map ext_funcs_; 116 | // error code 117 | std::size_t error_code_; 118 | }; 119 | 120 | } // namespace minivm::vm 121 | 122 | #endif // MINIVM_VM_VM_H_ 123 | -------------------------------------------------------------------------------- /src/vm/define.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_VM_DEFINE_H_ 2 | #define MINIVM_VM_DEFINE_H_ 3 | 4 | #include 5 | #include 6 | 7 | // all supported VM instructions 8 | // for more details, see `src/vm/README.md` 9 | #define VM_INSTS(e) \ 10 | /* memory allocation */ \ 11 | e(Var) e(Arr) \ 12 | /* load & store */ \ 13 | e(Ld) e(LdVar) e(LdReg) e(St) e(StVar) e(StVarP) \ 14 | e(StReg) e(StRegP) e(Imm) e(ImmHi) \ 15 | /* control transfer (with absolute target address) */ \ 16 | e(Bnz) e(Jmp) \ 17 | /* function call, with absolute target address \ 18 | or symbol name (external function) */ \ 19 | e(Call) e(CallExt) e(Ret) \ 20 | /* debugging */ \ 21 | e(Break) e(Error) \ 22 | /* logical operations */ \ 23 | e(LNot) e(LAnd) e(LOr) \ 24 | /* comparisons */ \ 25 | e(Eq) e(Ne) e(Gt) e(Lt) e(Ge) e(Le) \ 26 | /* arithmetic operations */ \ 27 | e(Neg) e(Add) e(Sub) e(Mul) e(Div) e(Mod) \ 28 | /* operand stack operations */ \ 29 | e(Pop) e(Clear) 30 | // expand macro to comma-separated list 31 | #define VM_EXPAND_LIST(i) i, 32 | // expand macro to comma-separated string array 33 | #define VM_EXPAND_STR_ARRAY(i) #i, 34 | // expand macro to label list 35 | #define VM_EXPAND_LABEL_LIST(i) &&VML_##i, 36 | // define a label of VM threading 37 | #define VM_LABEL(l) VML_##l: 38 | // goto a label of VM threading 39 | #define VM_GOTO(l) goto VML_##l 40 | 41 | namespace minivm::vm { 42 | 43 | // length of VM instruction (in bits) 44 | constexpr std::size_t kVMInstLen = 32; 45 | // length of opcode field (in bits) 46 | constexpr std::size_t kVMInstOpLen = 8; 47 | // length of operand field (in bits) 48 | constexpr std::size_t kVMInstImmLen = kVMInstLen - kVMInstOpLen; 49 | 50 | // opcode of VM instructions 51 | enum class InstOp { VM_INSTS(VM_EXPAND_LIST) }; 52 | 53 | // VM instruction (packed, 'kVMInstLen' bits long) 54 | struct VMInst { 55 | // opcode 56 | std::uint32_t op : kVMInstOpLen; 57 | // symbol reference/immediate/absolute target address 58 | std::uint32_t opr : kVMInstImmLen; 59 | }; 60 | 61 | // symbol identifiers 62 | using SymId = std::uint32_t; 63 | // static register identifiers 64 | using RegId = std::uint32_t; 65 | // pc addresses 66 | using VMAddr = std::uint32_t; 67 | // operands 68 | using VMOpr = std::int32_t; 69 | 70 | // name of entry point 71 | constexpr const char *kVMEntry = "$entry"; 72 | // name of frame area 73 | constexpr const char *kVMFrame = "$frame"; 74 | // name of debugger callback 75 | constexpr const char *kVMDebugger = "$debugger"; 76 | // name of main function 77 | constexpr const char *kVMMain = "f_main"; 78 | 79 | // error codes 80 | // 81 | // no error 82 | constexpr std::size_t kVMErrorNoError = 0; 83 | // accessing empty operand stack 84 | constexpr std::size_t kVMErrorEmptyOprStack = 150; 85 | // invalid memory pool address 86 | constexpr std::size_t kVMErrorInvalidMemPoolAddr = 151; 87 | // symbol not found 88 | constexpr std::size_t kVMErrorSymbolNotFound = 152; 89 | // redefining symbol 90 | constexpr std::size_t kVMErrorSymbolRedef = 153; 91 | // invalid register number 92 | constexpr std::size_t kVMErrorInvalidRegNum = 154; 93 | // invalid external function 94 | constexpr std::size_t kVMErrorInvalidExtFunc = 155; 95 | // external function error 96 | constexpr std::size_t kVMErrorExtFuncError = 156; 97 | // invalid PC address 98 | constexpr std::size_t kVMErrorInvalidPCAddr = 157; 99 | // VM irrelevant error 100 | constexpr std::size_t kVMErrorVMIrrelevant = 255; 101 | 102 | } // namespace minivm::vm 103 | 104 | #endif // MINIVM_VM_DEFINE_H_ 105 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "xstl/argparse.h" 10 | 11 | #include "version.h" 12 | #include "vm/symbol.h" 13 | #include "vm/instcont.h" 14 | #include "back/c/codegen.h" 15 | #include "front/wrapper.h" 16 | #include "vm/vm.h" 17 | #include "vmconf.h" 18 | #ifndef NO_DEBUGGER 19 | #include "debugger/minidbg/minidbg.h" 20 | #endif 21 | 22 | using namespace std; 23 | using namespace minivm::vm; 24 | using namespace minivm::back::c; 25 | using namespace minivm::front; 26 | using namespace minivm::debugger::minidbg; 27 | 28 | namespace { 29 | 30 | xstl::ArgParser GetArgp() { 31 | xstl::ArgParser argp; 32 | argp.AddArgument("input", "input Eeyore/Tigger IR file"); 33 | argp.AddOption("help", "h", "show this message", false); 34 | argp.AddOption("version", "v", "show version info", false); 35 | argp.AddOption("tigger", "t", "run in Tigger mode", false); 36 | #ifndef NO_DEBUGGER 37 | argp.AddOption("debug", "d", "enable debugger", false); 38 | #endif 39 | argp.AddOption("output", "o", "output file, default to stdout", 40 | ""); 41 | argp.AddOption("dump-gopher", "dg", "dump Gopher to output", 42 | false); 43 | // TODO: implement this option 44 | argp.AddOption("dump-bytecode", "db", "dump bytecode to output", 45 | false); 46 | argp.AddOption("compile", "c", "compile input file to C code", 47 | false); 48 | return argp; 49 | } 50 | 51 | void PrintVersion() { 52 | cout << APP_NAME << " version " << APP_VERSION << endl << endl; 53 | cout << "MiniVM is a virtual machine for interpreting "; 54 | cout << "Eeyore/Tigger IR," << endl; 55 | cout << "which is designed for PKU compiler course."; 56 | cout << endl << endl; 57 | cout << "Copyright (C) 2010-2021 MaxXing. License GPLv3."; 58 | cout << endl; 59 | } 60 | 61 | void ParseArgument(xstl::ArgParser &argp, int argc, const char *argv[]) { 62 | auto ret = argp.Parse(argc, argv); 63 | // check if need to exit program 64 | if (argp.GetValue("help")) { 65 | argp.PrintHelp(); 66 | exit(0); 67 | } 68 | else if (argp.GetValue("version")) { 69 | PrintVersion(); 70 | exit(0); 71 | } 72 | else if (!ret) { 73 | cerr << "invalid input, run '"; 74 | cerr << argp.program_name() << " -h' for help" << endl; 75 | exit(1); 76 | } 77 | } 78 | 79 | optional RunVM(xstl::ArgParser &argp, string_view file, ostream &os, 80 | Parser parser, VMInit vm_init, bool tigger_mode) { 81 | SymbolPool symbols; 82 | VMInstContainer cont(symbols, file); 83 | // parse input file 84 | if (!parser(file, cont)) return {}; 85 | if (argp.GetValue("dump-gopher")) { 86 | // dump Gopher 87 | cont.Dump(os); 88 | return 0; 89 | } 90 | else if (argp.GetValue("compile")) { 91 | // compile to C code 92 | CCodeGen gen(cont, tigger_mode); 93 | gen.Generate(); 94 | if (gen.has_error()) return {}; 95 | gen.Dump(os); 96 | return 0; 97 | } 98 | // run MiniVM 99 | std::optional ret; 100 | VM vm(symbols, cont); 101 | vm_init(vm); 102 | #ifdef NO_DEBUGGER 103 | ret = vm.Run(); 104 | #else 105 | if (argp.GetValue("debug")) { 106 | // debug mode 107 | MiniDebugger debugger(vm); 108 | PrintVersion(); 109 | ret = vm.Run(); 110 | // print exit code 111 | if (!ret) { 112 | std::cout << "VM instance ended with error code "; 113 | std::cout << vm.error_code() << std::endl; 114 | } 115 | else { 116 | std::cout << "VM instance exited with code " << *ret << std::endl; 117 | } 118 | } 119 | else { 120 | ret = vm.Run(); 121 | } 122 | #endif 123 | return ret ? *ret : static_cast(vm.error_code()); 124 | } 125 | 126 | optional RunEeyore(xstl::ArgParser &argp, string_view file, 127 | ostream &os) { 128 | return RunVM(argp, file, os, ParseEeyore, InitEeyoreVM, false); 129 | } 130 | 131 | optional RunTigger(xstl::ArgParser &argp, string_view file, 132 | ostream &os) { 133 | return RunVM(argp, file, os, ParseTigger, InitTiggerVM, true); 134 | } 135 | 136 | } // namespace 137 | 138 | int main(int argc, const char *argv[]) { 139 | // set up argument parser & parse argument 140 | auto argp = GetArgp(); 141 | ParseArgument(argp, argc, argv); 142 | 143 | // get input file name & output stream 144 | auto in_file = argp.GetValue("input"); 145 | auto out_file = argp.GetValue("output"); 146 | ofstream ofs; 147 | if (!out_file.empty()) ofs.open(out_file); 148 | auto &os = out_file.empty() ? cout : ofs; 149 | 150 | // parse & run 151 | optional ret; 152 | if (argp.GetValue("tigger")) { 153 | ret = RunTigger(argp, in_file, os); 154 | } 155 | else { 156 | ret = RunEeyore(argp, in_file, os); 157 | } 158 | return ret ? *ret : kVMErrorVMIrrelevant; 159 | } 160 | -------------------------------------------------------------------------------- /src/debugger/minidbg/minidbg.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_DEBUGGER_MINIDBG_MINIDBG_H_ 2 | #define MINIVM_DEBUGGER_MINIDBG_MINIDBG_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "debugger/debugger.h" 10 | #include "vm/vm.h" 11 | #include "debugger/minidbg/minieval.h" 12 | #include "debugger/minidbg/srcreader.h" 13 | 14 | namespace minivm::debugger::minidbg { 15 | 16 | // debugger for MiniVM 17 | class MiniDebugger : public DebuggerBase { 18 | public: 19 | MiniDebugger(vm::VM &vm) 20 | : DebuggerBase("minidbg> "), vm_(vm), eval_(vm), next_id_(0), 21 | layout_fmt_(LayoutFormat::Source), 22 | src_reader_(vm.cont().src_file()) { 23 | InitDebuggerCommands(); 24 | RegisterDebuggerCallback(); 25 | InitSigIntHandler(); 26 | // enable trap mode 27 | vm_.cont().ToggleTrapMode(true); 28 | } 29 | 30 | private: 31 | // breakpoint information 32 | struct BreakInfo { 33 | // PC address of breakpoint 34 | vm::VMAddr addr; 35 | // hit count 36 | std::uint32_t hit_count; 37 | }; 38 | 39 | // watchpoint information 40 | struct WatchInfo { 41 | // expression record id (in 'ExprEvaluator') 42 | std::uint32_t record_id; 43 | // last value 44 | vm::VMOpr last_val; 45 | // hit count 46 | std::uint32_t hit_count; 47 | }; 48 | 49 | // layout type of auto-disasm 50 | enum class LayoutFormat { 51 | Source, Asm, 52 | }; 53 | 54 | // initialize debugger commands 55 | void InitDebuggerCommands(); 56 | // register debugger callback for current instance 57 | void RegisterDebuggerCallback(); 58 | // initialize 'sigint' handler 59 | void InitSigIntHandler(); 60 | // debugger callback 61 | bool DebuggerCallback(); 62 | // log error message to stdout 63 | void LogError(std::string_view message); 64 | 65 | // read POS from input stream 66 | std::optional ReadPosition(std::istream &is); 67 | // read EXPR from input stream (with record) 68 | std::optional ReadExpression(std::istream &is); 69 | // read EXPR from input stream 70 | std::optional ReadExpression(std::istream &is, bool record); 71 | // delete the specific breakpoint 72 | // returns false if breakpoint not found 73 | bool DeleteBreak(std::uint32_t id); 74 | // delete the specific watchpoint 75 | // returns false if watchpoint not found 76 | bool DeleteWatch(std::uint32_t id); 77 | // check breakpoint status when callback has been called 78 | void CheckBreakpoints(); 79 | // check watchpoint status 80 | void CheckWatchpoints(); 81 | // next line handlers 82 | void NextLineHandler(std::uint32_t line, std::size_t depth); 83 | // next inst handlers 84 | void NextInstHandler(std::size_t n); 85 | void NextInstHandler(std::size_t n, std::size_t next_pc, 86 | std::size_t depth); 87 | // step line handler 88 | void StepLineHandler(std::optional line); 89 | // print operand stack info 90 | void PrintStackInfo(); 91 | // print environment info 92 | void PrintEnv(const vm::VM::EnvPtr &env); 93 | void PrintEnvInfo(); 94 | // print static register info 95 | void PrintRegInfo(); 96 | // print breakpoint info 97 | void PrintBreakInfo(); 98 | // print watchpoint info 99 | void PrintWatchInfo(); 100 | // show disassembly near current PC 101 | void ShowDisasm(); 102 | // show disassembly 103 | // parameter 'n' may represent the number of instructions or lines 104 | void ShowDisasm(vm::VMAddr pc, std::size_t n); 105 | 106 | // create a new breakpoint ('break [POS]') 107 | bool CreateBreak(std::istream &is); 108 | // create a new watchpoint ('watch EXPR') 109 | bool CreateWatch(std::istream &is); 110 | // delete breakpoint/watchpoint ('delete [N]') 111 | bool DeletePoint(std::istream &is); 112 | // continue running ('continue') 113 | bool Continue(std::istream &is); 114 | // source level single step, stepping over calls ('next') 115 | bool NextLine(std::istream &is); 116 | // instruction level single step, stepping over calls ('nexti [N]') 117 | bool NextInst(std::istream &is); 118 | // source level single step, stepping into calls ('step') 119 | bool StepLine(std::istream &is); 120 | // instruction level single step, stepping into calls ('stepi [N]') 121 | bool StepInst(std::istream &is); 122 | // print value of expression ('print [EXPR]') 123 | bool PrintExpr(std::istream &is); 124 | // examine memory ('x N EXPR') 125 | bool ExamineMem(std::istream &is); 126 | // print information ('info ITEM') 127 | bool PrintInfo(std::istream &is); 128 | // set layout of automatic disassemble ('layout TYPE') 129 | bool SetLayout(std::istream &is); 130 | // disassemble memory ('disasm [N POS]') 131 | bool DisasmMem(std::istream &is); 132 | 133 | // current MiniVM instance 134 | vm::VM &vm_; 135 | // expression evaluator 136 | MiniEvaluator eval_; 137 | // next breakpoint/watchpoint id 138 | std::uint32_t next_id_; 139 | // all breakpoints 140 | std::unordered_map breaks_; 141 | // hashmap of PC address to breakpoint info reference 142 | std::unordered_map pc_bp_; 143 | // all watchpoints 144 | std::unordered_map watches_; 145 | // layout format 146 | LayoutFormat layout_fmt_; 147 | // source code reader 148 | SourceReader src_reader_; 149 | }; 150 | 151 | } // namespace minivm::debugger::minidbg 152 | 153 | #endif // MINIVM_DEBUGGER_MINIDBG_MINIDBG_H_ 154 | -------------------------------------------------------------------------------- /src/debugger/debugger.cpp: -------------------------------------------------------------------------------- 1 | #include "debugger/debugger.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "readline/readline.h" 12 | #include "readline/history.h" 13 | 14 | using namespace minivm::debugger; 15 | 16 | // definition of all static members 17 | volatile std::sig_atomic_t DebuggerBase::sig_registered_ = 0; 18 | volatile std::sig_atomic_t DebuggerBase::sig_disabled_ = 0; 19 | std::unordered_set DebuggerBase::dbg_insts_; 20 | 21 | void DebuggerBase::SignalHandler(int sig) { 22 | assert(sig == SIGINT); 23 | if (sig_disabled_) return; 24 | // call all 'sigint' handlers 25 | for (const auto &inst : DebuggerBase::dbg_insts_) { 26 | if (inst->sigint_handler_) inst->sigint_handler_(); 27 | } 28 | } 29 | 30 | void DebuggerBase::InitSignalHandler() { 31 | // check if is registered 32 | if (sig_registered_) return; 33 | sig_registered_ = 1; 34 | // register signal handler 35 | std::signal(SIGINT, SignalHandler); 36 | } 37 | 38 | void DebuggerBase::RegisterDebugger() { 39 | sig_disabled_ = 1; 40 | dbg_insts_.insert(this); 41 | sig_disabled_ = 0; 42 | } 43 | 44 | void DebuggerBase::UnregisterDebugger() { 45 | sig_disabled_ = 1; 46 | dbg_insts_.erase(this); 47 | sig_disabled_ = 0; 48 | } 49 | 50 | void DebuggerBase::InitCommands() { 51 | // register 'help' command 52 | RegisterCommand( 53 | "help", "", [this](std::istream &is) { return ShowHelpInfo(is); }, 54 | "[CMD]", "show help message of CMD", 55 | "Show a list of all debugger commands, or give details about a " 56 | "specific command."); 57 | // register 'quit' command 58 | RegisterCommand( 59 | "quit", "q", 60 | [](std::istream &is) { 61 | std::exit(0); 62 | return false; 63 | }, 64 | "", "quit debugger", "Quit debugger."); 65 | } 66 | 67 | const DebuggerBase::CmdInfo *DebuggerBase::GetCommandInfo( 68 | const std::string &cmd) const { 69 | // try to find in registered commands 70 | auto it = cmds_.find(cmd); 71 | if (it != cmds_.end()) { 72 | return &it->second; 73 | } 74 | else { 75 | // try to find in abbreviations 76 | auto it = abbrs_.find(cmd); 77 | if (it != abbrs_.end()) { 78 | return it->second; 79 | } 80 | else { 81 | // command not found 82 | std::cout << "unknown command, run 'help' to see command list" 83 | << std::endl; 84 | return nullptr; 85 | } 86 | } 87 | } 88 | 89 | bool DebuggerBase::ParseCommand(std::istream &is) { 90 | // read command 91 | std::string cmd; 92 | is >> cmd; 93 | // call handler 94 | auto info = GetCommandInfo(cmd); 95 | return info ? info->handler(is) : false; 96 | } 97 | 98 | bool DebuggerBase::ShowHelpInfo(std::istream &is) { 99 | if (is.eof()) { 100 | // show brief help messages of all commands 101 | std::cout << "Debugger commands:" << std::endl; 102 | // get the length of the longest command 103 | std::size_t cmd_len = 0, args_len = 0; 104 | for (const auto &[name, info] : cmds_) { 105 | std::size_t cur_len = name.size() + info.abbr.size(); 106 | if (cur_len > cmd_len) cmd_len = cur_len; 107 | if (info.args.size() > args_len) args_len = info.args.size(); 108 | } 109 | // show messages 110 | for (const auto &[name, info] : cmds_) { 111 | auto cmd = name; 112 | if (!info.abbr.empty()) cmd += '/' + info.abbr; 113 | std::cout << " " << std::setw(cmd_len + 2) << std::setfill(' ') 114 | << cmd << std::setw(args_len + 2) << info.args; 115 | std::cout << " --- " << info.description << std::endl; 116 | } 117 | } 118 | else { 119 | // show detailed help messages of the specific command 120 | std::string cmd; 121 | is >> cmd; 122 | if (auto info = GetCommandInfo(cmd)) { 123 | cmd = info->name; 124 | if (!info->abbr.empty()) cmd += '/' + info->abbr; 125 | std::cout << "Syntax: " << cmd << " " << info->args << std::endl; 126 | std::cout << " " << info->details << std::endl; 127 | } 128 | } 129 | return false; 130 | } 131 | 132 | void DebuggerBase::EnterCLI() { 133 | bool quit = false; 134 | std::istringstream iss; 135 | while (!quit) { 136 | std::cout << std::endl; 137 | // read line from terminal 138 | auto line = readline(prompt_.c_str()); 139 | if (!line) { 140 | // EOF, just exit 141 | std::cout << "quit" << std::endl; 142 | std::exit(0); 143 | } 144 | if (*line) { 145 | // add current line to history 146 | add_history(line); 147 | // reset string stream 148 | iss.str(line); 149 | iss.clear(); 150 | // parse command 151 | quit = ParseCommand(iss); 152 | } 153 | // free line buffer 154 | std::free(line); 155 | } 156 | } 157 | 158 | void DebuggerBase::RegisterCommand(std::string_view cmd, 159 | std::string_view abbr, 160 | CmdHandler handler, 161 | std::string_view args, 162 | std::string_view description, 163 | std::string_view details) { 164 | CmdInfo info = {{}, std::string(abbr), handler, std::string(args), 165 | std::string(description), std::string(details)}; 166 | // insert to command map 167 | auto ret = cmds_.insert({std::string(cmd), std::move(info)}); 168 | ret.first->second.name = ret.first->first; 169 | assert(ret.second); 170 | // insert to abbreviation map 171 | if (!abbr.empty()) { 172 | const auto &it = ret.first; 173 | auto succ = abbrs_.insert({it->second.abbr, &it->second}).second; 174 | assert(succ); 175 | static_cast(succ); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/vm/instcont.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_VM_INSTCONT_H_ 2 | #define MINIVM_VM_INSTCONT_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "vm/define.h" 17 | #include "vm/symbol.h" 18 | 19 | namespace minivm::vm { 20 | 21 | // container for storing VM instructions 22 | class VMInstContainer { 23 | public: 24 | // callback for step counters 25 | using StepCallback = std::function; 26 | 27 | VMInstContainer(SymbolPool &sym_pool, std::string_view src_file) 28 | : sym_pool_(sym_pool) { 29 | Reset(src_file); 30 | } 31 | 32 | // reset internal states 33 | void Reset(std::string_view src_file); 34 | 35 | // instruction generators, for frontends 36 | // 37 | void PushVar(std::string_view sym); 38 | void PushArr(std::string_view sym); 39 | void PushLabel(std::string_view label); 40 | void PushLoad(); 41 | void PushLoad(std::string_view sym); 42 | void PushLoad(VMOpr imm); 43 | void PushLdReg(RegId reg_id); 44 | void PushLdFrame(VMOpr offset); 45 | void PushLdFrameAddr(VMOpr offset); 46 | void PushStore(); 47 | void PushStore(std::string_view sym); 48 | void PushStReg(RegId reg_id); 49 | void PushStFrame(VMOpr offset); 50 | void PushBnz(std::string_view label); 51 | void PushJump(std::string_view label); 52 | void PushCall(std::string_view label); 53 | void PushError(std::size_t code); 54 | void PushOp(InstOp op); 55 | 56 | // instruction metadata logger, for frontends 57 | // 58 | // print error message to stderr 59 | void LogError(std::string_view message); 60 | void LogError(std::string_view message, std::uint32_t line_num); 61 | void LogError(std::string_view message, std::string_view sym); 62 | void LogError(std::string_view message, std::string_view sym, 63 | std::uint32_t line_num); 64 | // update line definition 65 | // should be called before instruction insertion 66 | void LogLineNum(std::uint32_t line_num); 67 | // enter function environment 68 | // should be called before instruction insertion 69 | void EnterFunc(std::uint32_t param_count); 70 | void EnterFunc(std::uint32_t param_count, std::uint32_t slot_count, 71 | std::uint32_t line_num); 72 | // exit function environment 73 | void ExitFunc(); 74 | // perform label backfilling, and seal current container 75 | // exit if any error occurred 76 | void SealContainer(); 77 | 78 | // debug information queryer, for debuggers 79 | // 80 | // enable/disable the breakpoint on the specific pc address 81 | void ToggleBreakpoint(VMAddr pc, bool enable); 82 | // enable/disable trap mode 83 | // in trap mode, when MiniVM tries to fetch instructions from 84 | // the container, a 'Break' instruction will always be returned 85 | void ToggleTrapMode(bool enable) { trap_mode_ = enable; } 86 | // add a new step counter for stepping debugging 87 | // after next 'n' (n >= 0) steps, MiniVM will be breaked 88 | // if 'callback' is null, otherwise the callback will be called 89 | void AddStepCounter(std::size_t n, StepCallback callback); 90 | // same as 'AddStepCounter' but no callback 91 | void AddStepCounter(std::size_t n) { AddStepCounter(n, nullptr); } 92 | // dump the specific instruction 93 | bool Dump(std::ostream &os, VMAddr pc) const; 94 | // dump all stored instructions 95 | void Dump(std::ostream &os) const; 96 | // query pc by line number 97 | std::optional FindPC(std::uint32_t line_num) const; 98 | // query pc by label 99 | std::optional FindPC(std::string_view label) const; 100 | // query line number by pc 101 | std::optional FindLineNum(VMAddr pc) const; 102 | // getter, symbol pool 103 | const SymbolPool &sym_pool() const { return sym_pool_; } 104 | // getter, path to source file 105 | std::string_view src_file() const { return src_file_; } 106 | // getter, instruction data 107 | const VMInst *insts() const { return insts_.data(); } 108 | // getter, instruction count 109 | std::size_t inst_count() const { return insts_.size(); } 110 | // getter, pc of all defined functions 111 | const std::unordered_set func_pcs() const { return func_pcs_; } 112 | 113 | // instruction fetcher, for MiniVM instances 114 | // 115 | // get pointer of the specific instruction 116 | const VMInst *GetInst(VMAddr pc); 117 | 118 | private: 119 | struct BackfillInfo { 120 | // indicates current label has already been defined 121 | bool defined; 122 | // pc of current label 123 | VMAddr pc; 124 | // pc of all instructions that related to current label 125 | std::vector related_insts; 126 | }; 127 | 128 | // push instruction to container 129 | void PushInst(InstOp op); 130 | void PushInst(InstOp op, std::uint32_t opr); 131 | // get reference to last instruction 132 | // returns 'nullptr' if failed 133 | VMInst *GetLastInst(); 134 | // define new symbol, and check for conflict 135 | SymId DefSymbol(std::string_view sym); 136 | // check if symbol has not been defined, and get symbol id 137 | SymId GetSymbol(std::string_view sym); 138 | // add next pc address to backfill list 139 | // should be used before instruction insertion 140 | void LogRelatedInsts(std::string_view label); 141 | 142 | // symbol pool 143 | SymbolPool &sym_pool_; 144 | // error occurred during instruction generation 145 | bool has_error_; 146 | // current line number & function parameter count 147 | std::uint32_t cur_line_num_; 148 | // global & local & current environment 149 | std::unordered_set global_env_, local_env_, *cur_env_; 150 | // path to current source file (for debugging) 151 | std::string src_file_; 152 | // line number corresponding to pc addresses (for debugging) 153 | std::unordered_map line_defs_; 154 | // line number of pc addresses (for debugging) 155 | std::map> pc_defs_; 156 | // pc address of labels (for debugging & backfilling) 157 | std::unordered_map label_defs_; 158 | // last defined label 159 | std::string_view last_label_; 160 | // pc of all defined functions 161 | std::unordered_set func_pcs_; 162 | // all instructions 163 | std::vector insts_, global_insts_; 164 | // all breakpoints 165 | std::unordered_map breakpoints_; 166 | // whether the container is in trap mode 167 | bool trap_mode_; 168 | // queue for step counters 169 | std::queue> step_counters_; 170 | }; 171 | 172 | } // namespace minivm::vm 173 | 174 | #endif // MINIVM_VM_INSTCONT_H_ 175 | -------------------------------------------------------------------------------- /src/back/c/embed/vm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) 9 | #define WIN32_LEAN_AND_MEAN 10 | #include 11 | struct timeval { 12 | long tv_sec; 13 | long tv_usec; 14 | }; 15 | static int gettimeofday(struct timeval *tp, struct timezone *tzp) { 16 | const uint64_t kEpoch = ((uint64_t)116444736000000000ULL); 17 | SYSTEMTIME system_time; 18 | FILETIME file_time; 19 | uint64_t time; 20 | GetSystemTime(&system_time); 21 | SystemTimeToFileTime(&system_time, &file_time); 22 | time = ((uint64_t)file_time.dwLowDateTime); 23 | time += ((uint64_t)file_time.dwHighDateTime) << 32; 24 | tp->tv_sec = (long)((time - kEpoch) / 10000000L); 25 | tp->tv_usec = (long)(system_time.wMilliseconds * 1000); 26 | return 0; 27 | } 28 | #else 29 | #include 30 | #endif 31 | 32 | #ifndef STACK_SIZE 33 | #define STACK_SIZE 4096 34 | #endif 35 | #ifndef MEM_POOL_SIZE 36 | #define MEM_POOL_SIZE 1048576 37 | #endif 38 | 39 | typedef int32_t vmopr_t; 40 | typedef uint32_t vmaddr_t; 41 | 42 | static vmopr_t *opr_stack; 43 | static uint8_t *mem_pool; 44 | static vmaddr_t pool_sp = 0; 45 | static size_t stack_sp = 0, timer_id = 0, last_line; 46 | static uint64_t last_time_us, total_time_us = 0; 47 | 48 | #ifdef TIGGER_MODE 49 | #ifndef REG_COUNT 50 | #define REG_COUNT 28 51 | #endif 52 | #ifndef ARG_REG_ID 53 | #define ARG_REG_ID 20 54 | #endif 55 | #ifndef RET_REG_ID 56 | #define RET_REG_ID 20 57 | #endif 58 | static vmopr_t regs[REG_COUNT]; 59 | #endif 60 | 61 | static void PrintTime(uint64_t us) { 62 | const uint64_t kSecond = 1000 * 1000; 63 | const uint64_t kMinute = 60 * kSecond; 64 | const uint64_t kHour = 60 * kMinute; 65 | uint64_t h = us / kHour; 66 | us %= kHour; 67 | uint64_t m = us / kMinute; 68 | us %= kMinute; 69 | fprintf(stderr, "%" PRIu64 "H-%" PRIu64 "M-%" PRIu64 "S-%" PRIu64 "us\n", 70 | h, m, us / kSecond, us % kSecond); 71 | } 72 | 73 | static void InitVM(size_t stack_size, size_t mem_pool_size) { 74 | opr_stack = (vmopr_t *)malloc(sizeof(vmopr_t) * stack_size); 75 | mem_pool = (uint8_t *)malloc(sizeof(uint8_t) * mem_pool_size); 76 | } 77 | 78 | static void DestructVM() { 79 | if (timer_id) { 80 | fprintf(stderr, "TOTAL: "); 81 | PrintTime(total_time_us); 82 | } 83 | free(opr_stack); 84 | free(mem_pool); 85 | } 86 | 87 | static void VMEntry(); 88 | 89 | #define INLINE static inline __attribute__((always_inline)) 90 | INLINE void PushValue(vmopr_t val) { opr_stack[stack_sp++] = val; } 91 | INLINE void PokeValue(vmopr_t val) { opr_stack[stack_sp - 1] = val; } 92 | INLINE vmopr_t PopValue() { return opr_stack[--stack_sp]; } 93 | INLINE vmopr_t PeekValue() { return opr_stack[stack_sp - 1]; } 94 | INLINE void Clear() { stack_sp = 0; } 95 | INLINE size_t StackSize() { return stack_sp; } 96 | INLINE void Break() { raise(SIGTRAP); } 97 | 98 | #define APPLY(x) x 99 | #define READ_PARAMS_IMPL(N, ...) APPLY(READ_PARAMS_##N(__VA_ARGS__)) 100 | #ifdef TIGGER_MODE 101 | #define READ_PARAM(x) vmopr_t x = *__cur_arg++; 102 | #define RETURN(x) regs[RET_REG_ID] = (x); 103 | #define READ_PARAMS_1(a) READ_PARAM(a) 104 | #define READ_PARAMS_2(a, b) READ_PARAM(a) READ_PARAM(b) 105 | #define READ_PARAMS_3(a, ...) \ 106 | READ_PARAM(a) APPLY(READ_PARAMS_2(__VA_ARGS__)) 107 | #define READ_PARAMS_4(a, ...) \ 108 | READ_PARAM(a) APPLY(READ_PARAMS_3(__VA_ARGS__)) 109 | #define READ_PARAMS_5(a, ...) \ 110 | READ_PARAM(a) APPLY(READ_PARAMS_4(__VA_ARGS__)) 111 | #define READ_PARAMS(N, ...) \ 112 | vmopr_t *__cur_arg = regs + ARG_REG_ID; \ 113 | READ_PARAMS_IMPL(N, __VA_ARGS__) 114 | #else 115 | #define READ_PARAM(x) vmopr_t x = PopValue(); 116 | #define RETURN(x) PushValue(x) 117 | #define READ_PARAMS_1(a) READ_PARAM(a) 118 | #define READ_PARAMS_2(a, b) READ_PARAM(b) READ_PARAM(a) 119 | #define READ_PARAMS_3(a, ...) \ 120 | APPLY(READ_PARAMS_2(__VA_ARGS__)) READ_PARAM(a) 121 | #define READ_PARAMS_4(a, ...) \ 122 | APPLY(READ_PARAMS_3(__VA_ARGS__)) READ_PARAM(a) 123 | #define READ_PARAMS_5(a, ...) \ 124 | APPLY(READ_PARAMS_4(__VA_ARGS__)) READ_PARAM(a) 125 | #define READ_PARAMS(N, ...) READ_PARAMS_IMPL(N, __VA_ARGS__) 126 | #endif 127 | 128 | static void f_getint() { 129 | vmopr_t val; 130 | scanf("%" SCNd32, &val); 131 | RETURN(val); 132 | } 133 | static void f_getch() { 134 | RETURN(getchar()); 135 | } 136 | static void f_getarray() { 137 | READ_PARAMS(1, arr); 138 | vmopr_t len; 139 | scanf("%" SCNd32, &len); 140 | for (int i = 0; i < len; ++i) { 141 | scanf("%" SCNd32, (vmopr_t *)(mem_pool + arr + i * 4)); 142 | } 143 | RETURN(len); 144 | } 145 | static void f_putint() { 146 | READ_PARAMS(1, val); 147 | printf("%" PRId32, val); 148 | } 149 | static void f_putch() { 150 | READ_PARAMS(1, val); 151 | putchar(val); 152 | } 153 | static void f_putarray() { 154 | READ_PARAMS(2, len, arr); 155 | printf("%" PRId32 ":", len); 156 | for (int i = 0; i < len; ++i) { 157 | printf(" %" PRId32, *(vmopr_t *)(mem_pool + arr + i * 4)); 158 | } 159 | printf("\n"); 160 | } 161 | static void f__sysy_starttime() { 162 | READ_PARAMS(1, line); 163 | last_line = line; 164 | struct timeval tv; 165 | gettimeofday(&tv, NULL); 166 | last_time_us = 1000000 * tv.tv_sec + tv.tv_usec; 167 | } 168 | static void f__sysy_stoptime() { 169 | READ_PARAMS(1, line); 170 | struct timeval tv; 171 | gettimeofday(&tv, NULL); 172 | uint64_t us = (1000000 * tv.tv_sec + tv.tv_usec) - last_time_us; 173 | total_time_us += us; 174 | fprintf(stderr, "Timer#%03zu@%04zu-%04" PRId32 ": ", timer_id++, 175 | last_line, line); 176 | PrintTime(us); 177 | } 178 | 179 | int main(int argc, const char *argv[]) { 180 | size_t stack_size = STACK_SIZE, mem_pool_size = MEM_POOL_SIZE; 181 | if (argc > 1) { 182 | for (int i = 1; i < argc; ++i) { 183 | if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { 184 | printf( 185 | "This is a program generated by the MiniVM C backend.\n" 186 | "usage: %s [-h | --help]" 187 | " [-s SIZE | --stack-size SIZE]" 188 | " [-m SIZE | --mem-pool-size SIZE]\n", 189 | argv[0]); 190 | return 0; 191 | } 192 | else if ((!strcmp(argv[i], "-s") || 193 | !strcmp(argv[i], "--stack-size")) && 194 | i + 1 < argc) { 195 | stack_size = atoi(argv[++i]); 196 | } 197 | else if ((!strcmp(argv[i], "-m") || 198 | !strcmp(argv[i], "--mem-pool-size")) && 199 | i + 1 < argc) { 200 | mem_pool_size = atoi(argv[++i]); 201 | } 202 | else { 203 | printf("Invalid argument, try '%s -h'.\n", argv[0]); 204 | return 1; 205 | } 206 | } 207 | } 208 | InitVM(stack_size, mem_pool_size); 209 | VMEntry(); 210 | vmopr_t ret; 211 | #ifdef TIGGER_MODE 212 | ret = regs[RET_REG_ID]; 213 | #else 214 | ret = PopValue(); 215 | #endif 216 | DestructVM(); 217 | return ret; 218 | } 219 | -------------------------------------------------------------------------------- /src/front/tigger.y: -------------------------------------------------------------------------------- 1 | /* NOTE: 2 | * 3 | * In order to be compatible with the built-in version of Bison 4 | * in macOS, we must avoid using most of the new features that 5 | * appeared after Bison 2.3. 6 | * 7 | * Apple sucks! 8 | * 9 | * By MaxXing. 10 | */ 11 | 12 | %{ 13 | 14 | #include 15 | 16 | #include "vm/instcont.h" 17 | #include "front/token.h" 18 | 19 | using namespace minivm::front; 20 | using namespace minivm::vm; 21 | 22 | // some magic, in order to be compatible with the new version 23 | // and the old version of Bison, since the newer version of 24 | // Bison will put the declaration of 'yyparse' function to 25 | // the generated header, but we can not include VM related 26 | // definitions in the header, because the older version does 27 | // not support '%code requires'. 28 | #define CONT() (*reinterpret_cast(cont)) 29 | 30 | // line number in lexer 31 | extern const int tigger_lineno; 32 | 33 | // lexer 34 | int yylex(); 35 | 36 | // error logger 37 | void yyerror(void *cont, const char *message); 38 | 39 | // convert binary 'TokenOp' to 'InstOp' 40 | static InstOp GetBinaryOp(VMInstContainer &cont, TokenOp bin_op); 41 | 42 | %} 43 | 44 | // enable line number 45 | %locations 46 | 47 | // parameter of 'yyparse' function 48 | %parse-param { void *cont } 49 | 50 | // actual value of token/non-terminals 51 | %union { 52 | const char *str_val; 53 | std::int32_t int_val; 54 | minivm::front::TokenOp op_val; 55 | minivm::front::TokenReg reg_val; 56 | } 57 | 58 | // all tokens 59 | %token EOL IF GOTO CALL RETURN END LOAD STORE LOADADDR MALLOC 60 | %token LABEL FUNCTION VARIABLE 61 | %token NUM 62 | %token REG 63 | %token OP LOGICOP 64 | 65 | // type of some non-terminals 66 | %type BinOp 67 | %type Reg 68 | 69 | %% 70 | 71 | Program 72 | : /* Empty */ 73 | | Program GlobalVarDecl EOL 74 | | Program FunctionDef EOL 75 | | Program EOL 76 | ; 77 | 78 | GlobalVarDecl 79 | : VARIABLE '=' NUM { 80 | CONT().LogLineNum(@$.first_line); 81 | CONT().PushLoad(4); 82 | CONT().PushArr($1); 83 | CONT().PushLoad($3); 84 | CONT().PushLoad($1); 85 | CONT().PushStore(); 86 | } 87 | | VARIABLE '=' MALLOC NUM { 88 | CONT().LogLineNum(@$.first_line); 89 | CONT().PushLoad($4); 90 | CONT().PushArr($1); 91 | } 92 | ; 93 | 94 | FunctionDef 95 | : FunctionHeader EOL Expressions FunctionEnd 96 | ; 97 | 98 | FunctionHeader 99 | : FUNCTION '[' NUM ']' '[' NUM ']' { 100 | CONT().LogLineNum(@$.first_line); 101 | CONT().PushLabel($1); 102 | CONT().EnterFunc($3, $6, @$.first_line); 103 | } 104 | ; 105 | 106 | Expressions 107 | : FullExpression 108 | | Expressions FullExpression 109 | ; 110 | 111 | FunctionEnd 112 | : END FUNCTION { CONT().ExitFunc(); } 113 | ; 114 | 115 | FullExpression 116 | : Expression EOL 117 | | EOL 118 | ; 119 | 120 | Expression 121 | : Reg '=' Reg BinOp Reg { 122 | CONT().LogLineNum(@$.first_line); 123 | CONT().PushLdReg($3); 124 | CONT().PushLdReg($5); 125 | CONT().PushOp(GetBinaryOp(CONT(), $4)); 126 | CONT().PushStReg($1); 127 | } 128 | | Reg '=' Reg BinOp NUM { 129 | CONT().LogLineNum(@$.first_line); 130 | CONT().PushLdReg($3); 131 | CONT().PushLoad($5); 132 | CONT().PushOp(GetBinaryOp(CONT(), $4)); 133 | CONT().PushStReg($1); 134 | } 135 | | Reg '=' OP Reg { 136 | CONT().LogLineNum(@$.first_line); 137 | CONT().PushLdReg($4); 138 | auto op = InstOp::Add; 139 | switch ($3) { 140 | case TokenOp::Sub: op = InstOp::Neg; break; 141 | case TokenOp::Not: op = InstOp::LNot; break; 142 | default: CONT().LogError("invalid unary operator"); break; 143 | } 144 | CONT().PushOp(op); 145 | CONT().PushStReg($1); 146 | } 147 | | Reg '=' Reg { 148 | CONT().LogLineNum(@$.first_line); 149 | CONT().PushLdReg($3); 150 | CONT().PushStReg($1); 151 | } 152 | | Reg '=' NUM { 153 | CONT().LogLineNum(@$.first_line); 154 | CONT().PushLoad($3); 155 | CONT().PushStReg($1); 156 | } 157 | | Reg '[' NUM ']' '=' Reg { 158 | CONT().LogLineNum(@$.first_line); 159 | CONT().PushLdReg($6); 160 | CONT().PushLdReg($1); 161 | CONT().PushLoad($3); 162 | CONT().PushOp(InstOp::Add); 163 | CONT().PushStore(); 164 | } 165 | | Reg '=' Reg '[' NUM ']' { 166 | CONT().LogLineNum(@$.first_line); 167 | CONT().PushLdReg($3); 168 | CONT().PushLoad($5); 169 | CONT().PushOp(InstOp::Add); 170 | CONT().PushLoad(); 171 | CONT().PushStReg($1); 172 | } 173 | | IF Reg LOGICOP Reg GOTO LABEL { 174 | CONT().LogLineNum(@$.first_line); 175 | CONT().PushLdReg($2); 176 | CONT().PushLdReg($4); 177 | CONT().PushOp(GetBinaryOp(CONT(), $3)); 178 | CONT().PushBnz($6); 179 | } 180 | | GOTO LABEL { 181 | CONT().LogLineNum(@$.first_line); 182 | CONT().PushJump($2); 183 | } 184 | | LABEL ':' { CONT().PushLabel($1); } 185 | | CALL FUNCTION { 186 | CONT().LogLineNum(@$.first_line); 187 | CONT().PushCall($2); 188 | } 189 | | RETURN { 190 | CONT().LogLineNum(@$.first_line); 191 | CONT().PushOp(InstOp::Ret); 192 | } 193 | | STORE Reg NUM { 194 | CONT().LogLineNum(@$.first_line); 195 | CONT().PushLdReg($2); 196 | CONT().PushStFrame($3); 197 | } 198 | | LOAD NUM Reg { 199 | CONT().LogLineNum(@$.first_line); 200 | CONT().PushLdFrame($2); 201 | CONT().PushStReg($3); 202 | } 203 | | LOAD VARIABLE Reg { 204 | CONT().LogLineNum(@$.first_line); 205 | CONT().PushLoad($2); 206 | CONT().PushLoad(); 207 | CONT().PushStReg($3); 208 | } 209 | | LOADADDR NUM Reg { 210 | CONT().LogLineNum(@$.first_line); 211 | CONT().PushLdFrameAddr($2); 212 | CONT().PushStReg($3); 213 | } 214 | | LOADADDR VARIABLE Reg { 215 | CONT().LogLineNum(@$.first_line); 216 | CONT().PushLoad($2); 217 | CONT().PushStReg($3); 218 | } 219 | ; 220 | 221 | BinOp 222 | : OP { $$ = $1; } 223 | | LOGICOP { $$ = $1; } 224 | ; 225 | 226 | Reg 227 | : REG { $$ = static_cast($1); } 228 | ; 229 | 230 | %% 231 | 232 | void yyerror(void *cont, const char *message) { 233 | CONT().LogError(message, tigger_lineno); 234 | } 235 | 236 | static InstOp GetBinaryOp(VMInstContainer &cont, TokenOp bin_op) { 237 | auto op = InstOp::Add; 238 | switch (bin_op) { 239 | case TokenOp::Add: op = InstOp::Add; break; 240 | case TokenOp::Sub: op = InstOp::Sub; break; 241 | case TokenOp::Mul: op = InstOp::Mul; break; 242 | case TokenOp::Div: op = InstOp::Div; break; 243 | case TokenOp::Mod: op = InstOp::Mod; break; 244 | case TokenOp::Ne: op = InstOp::Ne; break; 245 | case TokenOp::Eq: op = InstOp::Eq; break; 246 | case TokenOp::Gt: op = InstOp::Gt; break; 247 | case TokenOp::Lt: op = InstOp::Lt; break; 248 | case TokenOp::Ge: op = InstOp::Ge; break; 249 | case TokenOp::Le: op = InstOp::Le; break; 250 | case TokenOp::Or: op = InstOp::LOr; break; 251 | case TokenOp::And: op = InstOp::LAnd; break; 252 | default: cont.LogError("invalid binary operator"); break; 253 | } 254 | return op; 255 | } 256 | -------------------------------------------------------------------------------- /src/front/eeyore.y: -------------------------------------------------------------------------------- 1 | /* NOTE: 2 | * 3 | * In order to be compatible with the built-in version of Bison 4 | * in macOS, we must avoid using most of the new features that 5 | * appeared after Bison 2.3. 6 | * 7 | * Apple sucks! 8 | * 9 | * By MaxXing. 10 | */ 11 | 12 | %{ 13 | 14 | #include 15 | 16 | #include "vm/instcont.h" 17 | #include "front/token.h" 18 | 19 | using namespace minivm::front; 20 | using namespace minivm::vm; 21 | 22 | // some magic, in order to be compatible with the new version 23 | // and the old version of Bison, since the newer version of 24 | // Bison will put the declaration of 'yyparse' function to 25 | // the generated header, but we can not include VM related 26 | // definitions in the header, because the older version does 27 | // not support '%code requires'. 28 | #define CONT() (*reinterpret_cast(cont)) 29 | 30 | // line number in lexer 31 | extern const int eeyore_lineno; 32 | 33 | // lexer 34 | int yylex(); 35 | 36 | // error logger 37 | void yyerror(void *cont, const char *message); 38 | 39 | // convert binary 'TokenOp' to 'InstOp' 40 | static InstOp GetBinaryOp(VMInstContainer &cont, TokenOp bin_op); 41 | 42 | %} 43 | 44 | // enable line number 45 | %locations 46 | 47 | // parameter of 'yyparse' function 48 | %parse-param { void *cont } 49 | 50 | // actual value of token/non-terminals 51 | %union { 52 | const char *str_val; 53 | std::int32_t int_val; 54 | minivm::front::TokenOp op_val; 55 | // definition for non-terminal 'RightValue' 56 | struct { 57 | // accept a symbol 58 | void Accept(const char *sym) { 59 | val.sym = sym; 60 | is_sym = true; 61 | } 62 | 63 | // accept a number 64 | void Accept(std::int32_t num) { 65 | val.num = num; 66 | is_sym = false; 67 | } 68 | 69 | // generate load instruction 70 | template 71 | void GenerateLoad(InstCont &cont) const { 72 | if (is_sym) { 73 | cont.PushLoad(val.sym); 74 | } 75 | else { 76 | cont.PushLoad(val.num); 77 | } 78 | } 79 | 80 | union { 81 | const char *sym; 82 | std::int32_t num; 83 | } val; 84 | bool is_sym; 85 | } right_val; 86 | } 87 | 88 | // all tokens 89 | %token EOL VAR IF GOTO PARAM CALL RETURN END 90 | %token LABEL FUNCTION SYMBOL 91 | %token NUM 92 | %token OP LOGICOP 93 | 94 | // type of some non-terminals 95 | %type RightValue 96 | %type BinOp 97 | 98 | %% 99 | 100 | Program 101 | : /* Empty */ 102 | | Program Declaration EOL 103 | | Program Initialization EOL 104 | | Program FunctionDef EOL 105 | | Program EOL 106 | ; 107 | 108 | Declaration 109 | : VAR SYMBOL { 110 | CONT().LogLineNum(@$.first_line); 111 | CONT().PushVar($2); 112 | } 113 | | VAR NUM SYMBOL { 114 | CONT().LogLineNum(@$.first_line); 115 | CONT().PushLoad($2); 116 | CONT().PushArr($3); 117 | } 118 | ; 119 | 120 | Initialization 121 | : SYMBOL '=' NUM { 122 | CONT().LogLineNum(@$.first_line); 123 | CONT().PushLoad($3); 124 | CONT().PushStore($1); 125 | } 126 | | SYMBOL '[' NUM ']' '=' NUM { 127 | CONT().LogLineNum(@$.first_line); 128 | CONT().PushLoad($6); 129 | CONT().PushLoad($3); 130 | CONT().PushLoad($1); 131 | CONT().PushOp(InstOp::Add); 132 | CONT().PushStore(); 133 | } 134 | ; 135 | 136 | FunctionDef 137 | : FunctionHeader EOL Statements FunctionEnd 138 | ; 139 | 140 | FunctionHeader 141 | : FUNCTION '[' NUM ']' { 142 | CONT().LogLineNum(@$.first_line); 143 | CONT().PushLabel($1); 144 | CONT().EnterFunc($3); 145 | } 146 | ; 147 | 148 | Statements 149 | : Statement 150 | | Statements Statement 151 | ; 152 | 153 | FunctionEnd 154 | : END FUNCTION { CONT().ExitFunc(); } 155 | ; 156 | 157 | Statement 158 | : Expression EOL 159 | | Declaration EOL 160 | | EOL 161 | ; 162 | 163 | Expression 164 | : SYMBOL '=' RightValue BinOp RightValue { 165 | CONT().LogLineNum(@$.first_line); 166 | $3.GenerateLoad(CONT()); 167 | $5.GenerateLoad(CONT()); 168 | CONT().PushOp(GetBinaryOp(CONT(), $4)); 169 | CONT().PushStore($1); 170 | } 171 | | SYMBOL '=' OP RightValue { 172 | CONT().LogLineNum(@$.first_line); 173 | $4.GenerateLoad(CONT()); 174 | auto op = InstOp::Add; 175 | switch ($3) { 176 | case TokenOp::Sub: op = InstOp::Neg; break; 177 | case TokenOp::Not: op = InstOp::LNot; break; 178 | default: CONT().LogError("invalid unary operator"); break; 179 | } 180 | CONT().PushOp(op); 181 | CONT().PushStore($1); 182 | } 183 | | SYMBOL '=' RightValue { 184 | CONT().LogLineNum(@$.first_line); 185 | $3.GenerateLoad(CONT()); 186 | CONT().PushStore($1); 187 | } 188 | | SYMBOL '[' RightValue ']' '=' RightValue { 189 | CONT().LogLineNum(@$.first_line); 190 | $6.GenerateLoad(CONT()); 191 | $3.GenerateLoad(CONT()); 192 | CONT().PushLoad($1); 193 | CONT().PushOp(InstOp::Add); 194 | CONT().PushStore(); 195 | } 196 | | SYMBOL '=' SYMBOL '[' RightValue ']' { 197 | CONT().LogLineNum(@$.first_line); 198 | $5.GenerateLoad(CONT()); 199 | CONT().PushLoad($3); 200 | CONT().PushOp(InstOp::Add); 201 | CONT().PushLoad(); 202 | CONT().PushStore($1); 203 | } 204 | | IF RightValue LOGICOP RightValue GOTO LABEL { 205 | CONT().LogLineNum(@$.first_line); 206 | $2.GenerateLoad(CONT()); 207 | $4.GenerateLoad(CONT()); 208 | CONT().PushOp(GetBinaryOp(CONT(), $3)); 209 | CONT().PushBnz($6); 210 | } 211 | | GOTO LABEL { 212 | CONT().LogLineNum(@$.first_line); 213 | CONT().PushJump($2); 214 | } 215 | | LABEL ':' { CONT().PushLabel($1); } 216 | | PARAM RightValue { 217 | CONT().LogLineNum(@$.first_line); 218 | $2.GenerateLoad(CONT()); 219 | } 220 | | CALL FUNCTION { 221 | CONT().LogLineNum(@$.first_line); 222 | CONT().PushCall($2); 223 | CONT().PushOp(InstOp::Clear); 224 | } 225 | | SYMBOL '=' CALL FUNCTION { 226 | CONT().LogLineNum(@$.first_line); 227 | CONT().PushCall($4); 228 | CONT().PushStore($1); 229 | } 230 | | RETURN RightValue { 231 | CONT().LogLineNum(@$.first_line); 232 | $2.GenerateLoad(CONT()); 233 | CONT().PushOp(InstOp::Ret); 234 | } 235 | | RETURN { 236 | CONT().LogLineNum(@$.first_line); 237 | CONT().PushOp(InstOp::Ret); 238 | } 239 | ; 240 | 241 | RightValue 242 | : SYMBOL { $$.Accept($1); } 243 | | NUM { $$.Accept($1); } 244 | ; 245 | 246 | BinOp 247 | : OP { $$ = $1; } 248 | | LOGICOP { $$ = $1; } 249 | ; 250 | 251 | %% 252 | 253 | void yyerror(void *cont, const char *message) { 254 | CONT().LogError(message, eeyore_lineno); 255 | } 256 | 257 | static InstOp GetBinaryOp(VMInstContainer &cont, TokenOp bin_op) { 258 | auto op = InstOp::Add; 259 | switch (bin_op) { 260 | case TokenOp::Add: op = InstOp::Add; break; 261 | case TokenOp::Sub: op = InstOp::Sub; break; 262 | case TokenOp::Mul: op = InstOp::Mul; break; 263 | case TokenOp::Div: op = InstOp::Div; break; 264 | case TokenOp::Mod: op = InstOp::Mod; break; 265 | case TokenOp::Ne: op = InstOp::Ne; break; 266 | case TokenOp::Eq: op = InstOp::Eq; break; 267 | case TokenOp::Gt: op = InstOp::Gt; break; 268 | case TokenOp::Lt: op = InstOp::Lt; break; 269 | case TokenOp::Ge: op = InstOp::Ge; break; 270 | case TokenOp::Le: op = InstOp::Le; break; 271 | case TokenOp::Or: op = InstOp::LOr; break; 272 | case TokenOp::And: op = InstOp::LAnd; break; 273 | default: cont.LogError("invalid binary operator"); break; 274 | } 275 | return op; 276 | } 277 | -------------------------------------------------------------------------------- /src/vmconf.cpp: -------------------------------------------------------------------------------- 1 | #include "vmconf.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "front/token.h" 10 | #include "mem/sparse.h" 11 | #include "mem/dense.h" 12 | 13 | using namespace minivm::vm; 14 | using namespace minivm::mem; 15 | using namespace minivm::front; 16 | 17 | // add all library functions to a VM instance 18 | #define ADD_LIBS(vm) \ 19 | do { \ 20 | vm.RegisterFunction("f_getint", GetInt); \ 21 | vm.RegisterFunction("f_getch", GetCh); \ 22 | vm.RegisterFunction("f_getarray", GetArray); \ 23 | vm.RegisterFunction("f_putint", PutInt); \ 24 | vm.RegisterFunction("f_putch", PutCh); \ 25 | vm.RegisterFunction("f_putarray", PutArray); \ 26 | vm.RegisterFunction("f__sysy_starttime", StartTime); \ 27 | vm.RegisterFunction("f__sysy_stoptime", StopTime); \ 28 | } while (0) 29 | 30 | namespace { 31 | // implementation of SysY library functions 32 | 33 | // core implementations 34 | namespace impl { 35 | 36 | // type definitions about timer 37 | using Clock = std::chrono::high_resolution_clock; 38 | using TimePoint = std::chrono::time_point; 39 | 40 | // identifier of timer used in 'StartTime' & 'StopTime' 41 | std::size_t timer_id = 0; 42 | // last line number 43 | std::size_t last_line_num; 44 | // last time point 45 | TimePoint last_time_point; 46 | // total time of all timers 47 | std::chrono::microseconds total_time; 48 | 49 | // timer guard 50 | // print total elapsed time to stderr when the program exits 51 | // if 'StartTime'/'StopTime' have ever been called 52 | static struct TimerGuard { 53 | ~TimerGuard() { 54 | if (!timer_id) return; 55 | auto us = total_time.count(); 56 | std::cerr << "TOTAL: "; 57 | PrintTime(us); 58 | std::cerr << std::endl; 59 | } 60 | 61 | static void PrintTime(std::uint64_t us) { 62 | constexpr std::uint64_t kSecond = 1000 * 1000; 63 | constexpr std::uint64_t kMinute = 60 * kSecond; 64 | constexpr std::uint64_t kHour = 60 * kMinute; 65 | std::cerr << us / kHour << "H-"; 66 | us %= kHour; 67 | std::cerr << us / kMinute << "M-"; 68 | us %= kMinute; 69 | std::cerr << us / kSecond << "S-" << us % kSecond << "us"; 70 | } 71 | } timer_guard; 72 | 73 | bool GetArray(VM &vm, VMOpr &ret, MemId arr) { 74 | // get length 75 | std::cin >> ret; 76 | // get address of array 77 | auto ptr = vm.mem_pool()->GetAddress(arr); 78 | if (!ptr) return false; 79 | // read elements 80 | for (int i = 0; i < ret; ++i) { 81 | std::cin >> reinterpret_cast(ptr)[i]; 82 | } 83 | return true; 84 | } 85 | 86 | bool PutArray(VM &vm, VMOpr len, MemId arr) { 87 | // put length 88 | std::cout << len << ':'; 89 | // get address of array 90 | auto ptr = vm.mem_pool()->GetAddress(arr); 91 | if (!ptr) return false; 92 | // put elements 93 | for (int i = 0; i < len; ++i) { 94 | std::cout << ' ' << reinterpret_cast(ptr)[i]; 95 | } 96 | std::cout << std::endl; 97 | return true; 98 | } 99 | 100 | bool StartTime(VMOpr line_num) { 101 | last_line_num = line_num; 102 | last_time_point = std::chrono::high_resolution_clock::now(); 103 | return true; 104 | } 105 | 106 | bool StopTime(VMOpr line_num) { 107 | using namespace std::chrono; 108 | // get elapsed time 109 | auto cur_time_point = high_resolution_clock::now(); 110 | auto elapsed = cur_time_point - last_time_point; 111 | auto us = duration_cast(elapsed).count(); 112 | // increase total time 113 | total_time += duration_cast(elapsed); 114 | // print timer info to stderr 115 | std::cerr << "Timer#"; 116 | std::cerr << std::setw(3) << std::setfill('0') << timer_id++ << '@'; 117 | std::cerr << std::setw(4) << std::setfill('0') << last_line_num << '-'; 118 | std::cerr << std::setw(4) << std::setfill('0') << line_num << ": "; 119 | timer_guard.PrintTime(us); 120 | std::cerr << std::endl; 121 | return true; 122 | } 123 | 124 | } // namespace impl 125 | 126 | // Eeyore mode wrapper 127 | namespace eeyore { 128 | 129 | bool GetInt(VM &vm) { 130 | VMOpr val; 131 | std::cin >> val; 132 | vm.oprs().push(val); 133 | return true; 134 | } 135 | 136 | bool GetCh(VM &vm) { 137 | vm.oprs().push(std::cin.get()); 138 | return true; 139 | } 140 | 141 | bool GetArray(VM &vm) { 142 | VMOpr ret; 143 | auto arr = vm.GetParamFromCurPool(0); 144 | if (!arr) return false; 145 | if (!impl::GetArray(vm, ret, *arr)) return false; 146 | vm.oprs().push(ret); 147 | return true; 148 | } 149 | 150 | bool PutInt(VM &vm) { 151 | auto val = vm.GetParamFromCurPool(0); 152 | if (!val) return false; 153 | std::cout << *val; 154 | return true; 155 | } 156 | 157 | bool PutCh(VM &vm) { 158 | auto val = vm.GetParamFromCurPool(0); 159 | if (!val) return false; 160 | std::cout.put(*val); 161 | return true; 162 | } 163 | 164 | bool PutArray(VM &vm) { 165 | auto len = vm.GetParamFromCurPool(0); 166 | auto arr = vm.GetParamFromCurPool(1); 167 | if (!len || !arr) return false; 168 | return impl::PutArray(vm, *len, *arr); 169 | } 170 | 171 | bool StartTime(VM &vm) { 172 | auto line_num = vm.GetParamFromCurPool(0); 173 | if (!line_num) return false; 174 | return impl::StartTime(*line_num); 175 | } 176 | 177 | bool StopTime(VM &vm) { 178 | auto line_num = vm.GetParamFromCurPool(0); 179 | if (!line_num) return false; 180 | return impl::StopTime(*line_num); 181 | } 182 | 183 | } // namespace eeyore 184 | 185 | // Tigger mode wrapper 186 | namespace tigger { 187 | 188 | inline VMOpr &GetRetVal(VM &vm) { 189 | return vm.regs(static_cast(TokenReg::A0)); 190 | } 191 | 192 | inline VMOpr &GetParam(VM &vm, std::size_t param_id) { 193 | return vm.regs(static_cast(TokenReg::A0) + param_id); 194 | } 195 | 196 | void ResetCallerSaveRegs(VM &vm) { 197 | for (RegId i = static_cast(TokenReg::T0); 198 | i < static_cast(TokenReg::A7); ++i) { 199 | vm.regs(i) = 0xdeadc0de; 200 | } 201 | } 202 | 203 | bool GetInt(VM &vm) { 204 | ResetCallerSaveRegs(vm); 205 | std::cin >> GetRetVal(vm); 206 | return true; 207 | } 208 | 209 | bool GetCh(VM &vm) { 210 | ResetCallerSaveRegs(vm); 211 | GetRetVal(vm) = std::cin.get(); 212 | return true; 213 | } 214 | 215 | bool GetArray(VM &vm) { 216 | auto arr = GetParam(vm, 0); 217 | ResetCallerSaveRegs(vm); 218 | return impl::GetArray(vm, GetRetVal(vm), arr); 219 | } 220 | 221 | bool PutInt(VM &vm) { 222 | std::cout << GetParam(vm, 0); 223 | ResetCallerSaveRegs(vm); 224 | return true; 225 | } 226 | 227 | bool PutCh(VM &vm) { 228 | std::cout.put(GetParam(vm, 0)); 229 | ResetCallerSaveRegs(vm); 230 | return true; 231 | } 232 | 233 | bool PutArray(VM &vm) { 234 | auto len = GetParam(vm, 0), arr = GetParam(vm, 1); 235 | ResetCallerSaveRegs(vm); 236 | return impl::PutArray(vm, len, arr); 237 | } 238 | 239 | bool StartTime(VM &vm) { 240 | auto line_num = GetParam(vm, 0); 241 | ResetCallerSaveRegs(vm); 242 | return impl::StartTime(line_num); 243 | } 244 | 245 | bool StopTime(VM &vm) { 246 | auto line_num = GetParam(vm, 0); 247 | ResetCallerSaveRegs(vm); 248 | return impl::StopTime(line_num); 249 | } 250 | 251 | } // namespace tigger 252 | } // namespace 253 | 254 | void InitEeyoreVM(VM &vm) { 255 | using namespace eeyore; 256 | // set memory pool factory function 257 | vm.set_mem_pool(std::make_unique()); 258 | // add library functions 259 | ADD_LIBS(vm); 260 | vm.Reset(); 261 | } 262 | 263 | void InitTiggerVM(VM &vm) { 264 | using namespace tigger; 265 | // set memory pool factory function 266 | vm.set_mem_pool(std::make_unique()); 267 | // initialize static registers 268 | vm.set_static_reg_count(TOKEN_COUNT(TOKEN_REGISTERS)); 269 | vm.set_ret_reg_id(static_cast(TokenReg::A0)); 270 | // add library functions 271 | ADD_LIBS(vm); 272 | vm.Reset(); 273 | vm.regs(static_cast(TokenReg::X0)) = 0; 274 | } 275 | -------------------------------------------------------------------------------- /src/vm/README.md: -------------------------------------------------------------------------------- 1 | # A Brief Introduction to MiniVM 2 | 3 | MiniVM is a virtual machine for interpreting Eeyore/Tigger IR, so its design is deeply influenced by Eeyore and Tigger. 4 | 5 | The current version of MiniVM is implemented in C++ language, but in fact, there are no restrictions on the host language that implements MiniVM. 6 | 7 | ## Architecture of Virtual Machine 8 | 9 | MiniVM is a stack-based virtual machine, which means most of the operands required by instructions come from the internal stack of the virtual machine. In details, MiniVM has the following built-in structures: 10 | 11 | * **Operand stack**: storing operands or parameters. 12 | * **Environment stack**: holding environments and return addresses for each active functions. 13 | * **Memory pool**: holding all dynamically allocated memory. 14 | * **Static registers**: storing some static data, designed to execute Tigger IR. 15 | * **Symbol pool**: holding all symbols, which can be variable names or external function names. 16 | * **Instruction container**: holding all instructions and debugging information. 17 | * **Program counter**: holding a pointer to the instruction currently being executed. 18 | * **External function table**: holding all definitions of external functions. 19 | 20 | Considering that static registers are closely related to the target architecture supported by Tigger, although the current Tigger IR only supports the RISC-V architecture, it may support more architectures in the future. Therefore, the count of the static registers should not be fixed. 21 | 22 | The external function table can be modified before MiniVM starts. Developers can register any host language function to the table, and assign a symbol to it, in order to provide library functions such as `putint` for programs running in MiniVM. 23 | 24 | ## Instruction Definition 25 | 26 | MiniVM can not directly execute Eeyore or Tigger, so there should be a front end to read Eeyore/Tigger source files, and generate instructions that can be recognized by MiniVM. The instruction set of MiniVM is called Gopher (in order to match with Eeyore and Tigger). 27 | 28 | The structure of Gopher instruction is defined as follows: 29 | 30 | ``` 31 | 0 7 8 31 32 | +----+---------+ 33 | | op | opr | 34 | +----+---------+ 35 | ``` 36 | 37 | As we can see, it consists of two field `op` and `opr`, the former is the opcode of instruction, and the latter is the operand, which can be: 38 | 39 | * `sym`: symbol reference, which represents a symbol in the symbol pool. 40 | * `imm`: 24-bit sign-extended immediate. 41 | * `pc`: absolute target address, used in control transfer instructions. 42 | * `reg`: id of static register. 43 | 44 | MiniVM supports the following instructions: 45 | 46 | * **Memory allocation**: Var, Arr. 47 | * **Load and store**: Ld, LdVar, LdReg, LdAddr, St, StVar, StVarP, StReg, StRegP, Imm, ImmHi. 48 | * **Control transfer**: Bnz, Jmp. 49 | * **Function call**: Call, CallExt, Ret, Param. 50 | * **Debugging**: Break. 51 | * **Logical operations**: LNot, LAnd, LOr. 52 | * **Comparisons**: Eq, Ne, Gt, Lt, Ge, Le. 53 | * **Arithmetic operations**: Neg, Add, Sub, Mul, Div, Mod. 54 | * **Operand stack operations**: Clear. 55 | 56 | Details as follows: 57 | 58 | > **Note** 59 | > 60 | > In the `Stack operand` column, the rightmost element is at the top of the stack. 61 | 62 | | Opcode | Operand | Stack Operand | Description | 63 | | --- | --- | --- | --- | 64 | | Var | `sym` | N/A | allocate a slot for variable `sym` | 65 | | Arr | `sym` | size (in bytes) | allocate memory for array `sym` | 66 | | Ld | N/A | addr | load 32-bit data from addr to stack | 67 | | LdVar | `sym` | N/A | load 32-bit data from `sym` to stack | 68 | | LdReg | `reg` | N/A | load 32-bit data from `reg` to stack | 69 | | St | N/A | val, addr | pop & store val to addr | 70 | | StVar | `sym` | val | pop & store val to `sym` | 71 | | StVarP | `sym` | val (preserved) | preserve & store val to `sym` | 72 | | StReg | `reg` | val | pop & store val to `reg` | 73 | | StRegP | `reg` | val (preserved) | preserve & store val to `reg` | 74 | | Imm | `imm` | N/A | load 24-bit `imm` to stack (sign extended) | 75 | | ImmHi | `imm` | val (preserved) | load `imm & 255` to upper 8-bit of val | 76 | | Bnz | `pc` | cond | jump to `pc` if cond is not zero | 77 | | Jmp | `pc` | N/A | jump to `pc` | 78 | | Call | `pc` | N/A | call function at `pc` | 79 | | CallExt | `sym` | N/A | call external function `sym` | 80 | | Ret | N/A | N/A | return from a function call | 81 | | Break | N/A | N/A | breakpoint, inserted by debugger | 82 | | Error | `code` | N/A | raise an error with error code `code` | 83 | | LNot | N/A | opr | perform logical negation | 84 | | LAnd | N/A | lhs, rhs | perform logical AND operation | 85 | | LOr | N/A | lhs, rhs | perform logical OR operation | 86 | | Eq | N/A | lhs, rhs | push (lhs == rhs) to stack | 87 | | Ne | N/A | lhs, rhs | push (lhs != rhs) to stack | 88 | | Gt | N/A | lhs, rhs | push (lhs > rhs) to stack | 89 | | Lt | N/A | lhs, rhs | push (lhs < rhs) to stack | 90 | | Ge | N/A | lhs, rhs | push (lhs >= rhs) to stack | 91 | | Le | N/A | lhs, rhs | push (lhs <= rhs) to stack | 92 | | Neg | N/A | opr | perform negation | 93 | | Add | N/A | lhs, rhs | perform addition | 94 | | Sub | N/A | lhs, rhs | perform subtraction | 95 | | Mul | N/A | lhs, rhs | perform multiplication | 96 | | Div | N/A | lhs, rhs | perform division | 97 | | Mod | N/A | lhs, rhs | Perform modulo operation | 98 | | Pop | N/A | N/A | discard the top value on the stack | 99 | | Clear | N/A | N/A | Clear the operand stack | 100 | 101 | ## Calling Conventions 102 | 103 | When executing a `Call`/`CallExt` instruction, MiniVM will: 104 | 105 | 1. Create a new environment, and push it to the environment stack. 106 | 2. Check the operand stack, if there are any values in it, pop them and add them to the environment, then assign symbol to it, such as `p0`, `p1`, etc. 107 | 3. Jump to target pc address, or call the specific external function. 108 | 109 | On the contrast, when executing a `Ret` instruction, MiniVM will: 110 | 111 | * If the size of the environment stack is 1, it means that instructions are currently being executed in the global environment, just stop the execution. 112 | * Otherwise, pop an environment from the top of the stack and release it, and then jump to the return address. 113 | 114 | So what about the details of external functions? We make the following conventions: 115 | 116 | ### Form of External Functions 117 | 118 | MiniVM can accept external functions in the following form: 119 | 120 | ``` 121 | func externalFunc(vm_instance: MiniVM): Boolean 122 | ``` 123 | 124 | Therefore, external functions can access the current environment and all static registers in MiniVM. 125 | 126 | If any error occurs during the external function call, the function should return `False`, otherwise it should return `True`. 127 | 128 | ### Passing Parameters 129 | 130 | To be compatible with Eeyore and Tigger, MiniVM supports two methods for passing parameters: 131 | 132 | 1. Store parameter #1, parameter #2... to function's environment with the symbol `p1`, `p2`..., or 133 | 2. Store parameters to the specific static registers or an data block with symbol `$frame` in memory pool, depends on the target architecture of Tigger. 134 | 135 | MiniVM ***must ensure***: 136 | 137 | * Before the `Call`/`CallExt` instructions are executed, there is no value in the operand stack, except for function parameters. 138 | * When executing Eeyore generated instructions, there are no data blocks with symbol `$frame` in the current environment. 139 | * When executing Tigger generated instructions, there must be a data blocks with symbol `$frame` in all of the local environment, and, all `$frame`s should be placed consecutively in the same area of the memory pool. 140 | 141 | Considering the impact on performance, we strongly recommend that developers should create two versions of external functions for MiniVM running in Eeyore mode and Tigger mode. 142 | 143 | ### Passing Return Values 144 | 145 | To be compatible with Eeyore and Tigger, MiniVM supports two methods for passing return values: 146 | 147 | 1. Push return value to operand stack, or 148 | 2. Store return value to the specific static register, depends on the target architecture of Tigger. 149 | 150 | Developer should make sure that the external function uses the correct method to pass the return value, when MiniVM is running in either Eeyore mode or Tigger mode. 151 | 152 | ## Debugger of MiniVM 153 | 154 | MiniVM does not have a built-in debugger, but it supports the `Break` instruction. When this instruction is executed, MiniVM will try to find and call an external function called `$debugger`. 155 | 156 | Unlike the `Call`/`CallExt` instruction, there are no new environments will be created at this time. All internal states of MiniVM, such as the operand stack and the static registers, will be retained. 157 | 158 | When `$debugger` returns, MiniVM will check the return value. If it's `False`, the execution process will be interrupted. Otherwise, MiniVM will ***re-execute*** the current instruction. 159 | 160 | ## Error Handling 161 | 162 | Some errors may occur while MiniVM is running. When an error occurs, MiniVM will print the error message to `stderr` and record the error code of that error. 163 | 164 | The error codes and their meanings are shown in the table below: 165 | 166 | | Error Code | Meaning | 167 | | --- | --- | 168 | | 0 | No error. | 169 | | 150 | Accessing empty operand stack. | 170 | | 151 | Invalid memory pool address. | 171 | | 152 | Symbol not found. | 172 | | 153 | Redefining symbol. | 173 | | 154 | Invalid register number. | 174 | | 155 | Invalid external function. | 175 | | 156 | External function error. | 176 | | 157 | Invalid PC address. | 177 | | 255 | VM irrelevant error. | 178 | 179 | The error codes are designed mainly to facilitate the implementation of certain automated test scripts. 180 | 181 | ## Gopher Bytecode File Format 182 | 183 | > Still WIP. 184 | 185 | ## Extending the Gopher 186 | 187 | > Still WIP. 188 | -------------------------------------------------------------------------------- /src/back/c/codegen.cpp: -------------------------------------------------------------------------------- 1 | #include "back/c/codegen.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "xstl/embed.h" 7 | 8 | using namespace minivm::back::c; 9 | using namespace minivm::vm; 10 | 11 | // embbedded C code snippets 12 | static const char *kCCodeTiggerMode = "#define TIGGER_MODE\n"; 13 | XSTL_EMBED_STR(kCCodeVM, "back/c/embed/vm.c"); 14 | #ifdef LET_CMAKE_KNOW_THERE_IS_AN_EMBEDDED_FILE 15 | #include "back/c/embed/vm.c" 16 | #endif 17 | 18 | namespace { 19 | 20 | // indention 21 | constexpr const char *kIndent = " "; 22 | // indention x2 23 | constexpr const char *kIndent2 = " "; 24 | // prefix of label 25 | constexpr const char *kPrefixLabel = "label"; 26 | // prefix of parameter array 27 | constexpr const char *kPrefixParams = "params"; 28 | // prefix of function 29 | constexpr const char *kPrefixFunc = "VMFunc"; 30 | // entry function 31 | constexpr const char *kEntryFunc = "VMEntry"; 32 | // label of function end 33 | constexpr const char *kLabelFuncEnd = "label_end"; 34 | // stack push operation 35 | constexpr const char *kStackPush = "PushValue"; 36 | // stack poke operation 37 | constexpr const char *kStackPoke = "PokeValue"; 38 | // stack pop operation 39 | constexpr const char *kStackPop = "PopValue()"; 40 | // stack peek operation 41 | constexpr const char *kStackPeek = "PeekValue()"; 42 | // stack clear operation 43 | constexpr const char *kStackClear = "Clear()"; 44 | // stack size 45 | constexpr const char *kStackSize = "StackSize()"; 46 | // breakpoint 47 | constexpr const char *kBreakpoint = "Break()"; 48 | 49 | } // namespace 50 | 51 | std::optional CCodeGen::GetSymbol(SymId sym_id, VMAddr pc) { 52 | // get symbol 53 | auto sym = cont().sym_pool().FindSymbol(sym_id); 54 | if (!sym) { 55 | LogError("symbol not found", pc); 56 | return {}; 57 | } 58 | if (sym->front() == 'p') { 59 | // convert parameter symbol to array access 60 | std::string ret = kPrefixParams; 61 | ret += '['; 62 | ret += sym->substr(1); 63 | ret += ']'; 64 | return ret; 65 | } 66 | else if (sym->front() == '$') { 67 | // handle symbol starting with '$' 68 | std::string ret = "builtin_"; 69 | ret += sym->substr(1); 70 | return ret; 71 | } 72 | else { 73 | return std::string(*sym); 74 | } 75 | } 76 | 77 | bool CCodeGen::GenerateInst(std::ostringstream &oss, VMAddr pc, 78 | const VMInst &inst) { 79 | // generate pc info 80 | oss << kIndent << "// pc: " << pc << '\n'; 81 | // generate line info 82 | auto line = cont().FindLineNum(pc); 83 | if (line && *line != last_line_) { 84 | last_line_ = *line; 85 | auto src = src_reader_.ReadLine(*line); 86 | if (src) oss << kIndent << "// " << *src << '\n'; 87 | oss << "#line " << *line << " \"" << cont().src_file() << "\"\n"; 88 | } 89 | // generate label 90 | if (IsLabel(pc)) oss << kPrefixLabel << pc << ":\n"; 91 | // generate instruction 92 | auto opcode = static_cast(inst.op); 93 | switch (opcode) { 94 | case InstOp::Var: { 95 | // get symbol of variable 96 | auto sym = GetSymbol(inst.opr, pc); 97 | if (!sym) return false; 98 | // emit C code 99 | oss << kIndent << "vmopr_t " << *sym << ";\n"; 100 | break; 101 | } 102 | case InstOp::Arr: { 103 | // get symbol of array 104 | auto sym = GetSymbol(inst.opr, pc); 105 | if (!sym) return false; 106 | // emit C code 107 | oss << kIndent << "vmaddr_t " << *sym << " = pool_sp;\n"; 108 | oss << kIndent << "pool_sp += " << kStackPop << ";\n"; 109 | break; 110 | } 111 | case InstOp::Ld: { 112 | oss << kIndent << kStackPush << "(*(vmopr_t *)(mem_pool + " 113 | << kStackPop << "));\n"; 114 | break; 115 | } 116 | case InstOp::LdVar: { 117 | // get symbol of variable 118 | auto sym = GetSymbol(inst.opr, pc); 119 | if (!sym) return false; 120 | // emit C code 121 | oss << kIndent << kStackPush << '(' << *sym << ");\n"; 122 | break; 123 | } 124 | case InstOp::LdReg: { 125 | oss << kIndent << kStackPush << "(regs[" << inst.opr << "]);\n"; 126 | break; 127 | } 128 | case InstOp::St: { 129 | oss << kIndent << "{\n"; 130 | oss << kIndent2 << "vmopr_t *ptr = (vmopr_t *)(mem_pool + " 131 | << kStackPop << ");\n"; 132 | oss << kIndent2 << "*ptr = " << kStackPop << ";\n"; 133 | oss << kIndent << "}\n"; 134 | break; 135 | } 136 | case InstOp::StVar: { 137 | // get symbol of variable 138 | auto sym = GetSymbol(inst.opr, pc); 139 | if (!sym) return false; 140 | // emit C code 141 | oss << kIndent << *sym << " = " << kStackPop << ";\n"; 142 | break; 143 | } 144 | case InstOp::StVarP: { 145 | // get symbol of variable 146 | auto sym = GetSymbol(inst.opr, pc); 147 | if (!sym) return false; 148 | // emit C code 149 | oss << kIndent << *sym << " = " << kStackPeek << ";\n"; 150 | break; 151 | } 152 | case InstOp::StReg: { 153 | oss << kIndent << "regs[" << inst.opr << "] = " << kStackPop << ";\n"; 154 | break; 155 | } 156 | case InstOp::StRegP: { 157 | oss << kIndent << "regs[" << inst.opr << "] = " << kStackPeek 158 | << ";\n"; 159 | break; 160 | } 161 | case InstOp::Imm: { 162 | constexpr auto kSignBit = 1u << (kVMInstImmLen - 1); 163 | constexpr auto kUpperOnes = (1u << (32 - kVMInstImmLen)) - 1; 164 | auto val = inst.opr; 165 | if (val & kSignBit) val |= kUpperOnes << kVMInstImmLen; 166 | oss << kIndent << kStackPush << "((vmopr_t)" << val << ");\n"; 167 | break; 168 | } 169 | case InstOp::ImmHi: { 170 | constexpr auto kMaskLo = (1u << kVMInstImmLen) - 1; 171 | constexpr auto kMaskHi = (1u << (32 - kVMInstImmLen)) - 1; 172 | oss << kIndent << kStackPoke << '(' << kStackPeek << " & " << kMaskLo 173 | << ");\n"; 174 | oss << kIndent << kStackPoke << '(' << kStackPeek << " | (vmopr_t)" 175 | << ((inst.opr & kMaskHi) << kVMInstImmLen) << ");\n"; 176 | break; 177 | } 178 | case InstOp::Bnz: { 179 | oss << kIndent << "if (" << kStackPop << ") goto " << kPrefixLabel 180 | << inst.opr << ";\n"; 181 | break; 182 | } 183 | case InstOp::Jmp: { 184 | oss << kIndent << "goto " << kPrefixLabel << inst.opr << ";\n"; 185 | break; 186 | } 187 | case InstOp::Call: { 188 | oss << kIndent << kPrefixFunc << inst.opr << "();\n"; 189 | break; 190 | } 191 | case InstOp::CallExt: { 192 | // get symbol of function 193 | auto sym = GetSymbol(inst.opr, pc); 194 | if (!sym) return false; 195 | // emit C code 196 | oss << kIndent << *sym << "();\n"; 197 | break; 198 | } 199 | case InstOp::Ret: { 200 | oss << kIndent << "goto " << kLabelFuncEnd << ";\n"; 201 | break; 202 | } 203 | case InstOp::Break: { 204 | oss << kIndent << kBreakpoint << ";\n"; 205 | break; 206 | } 207 | case InstOp::Error: { 208 | oss << kIndent << "fprintf(stderr, \"VM error, code " << inst.opr 209 | << "\");\n"; 210 | oss << kIndent << "exit(" << inst.opr << ");\n"; 211 | break; 212 | } 213 | case InstOp::Pop: { 214 | oss << kIndent << kStackPop << ";\n"; 215 | break; 216 | } 217 | case InstOp::Clear: { 218 | oss << kIndent << kStackClear << ";\n"; 219 | break; 220 | } 221 | default: { 222 | // arithmetic & logical operations 223 | if (opcode == InstOp::LNot || opcode == InstOp::Neg) { 224 | // unary operation 225 | oss << kIndent << kStackPush << '('; 226 | oss << "-!"[opcode == InstOp::LNot]; 227 | oss << kStackPop << ");\n"; 228 | } 229 | else { 230 | // binary operation 231 | oss << kIndent << "{\n"; 232 | oss << kIndent2 << "vmopr_t rhs = " << kStackPop << ";\n"; 233 | oss << kIndent2 << kStackPoke << '(' << kStackPeek << ' '; 234 | switch (opcode) { 235 | case InstOp::LAnd: oss << "&&"; break; 236 | case InstOp::LOr: oss << "||"; break; 237 | case InstOp::Eq: oss << "=="; break; 238 | case InstOp::Ne: oss << "!="; break; 239 | case InstOp::Gt: oss << ">"; break; 240 | case InstOp::Lt: oss << "<"; break; 241 | case InstOp::Ge: oss << ">="; break; 242 | case InstOp::Le: oss << "<="; break; 243 | case InstOp::Add: oss << '+'; break; 244 | case InstOp::Sub: oss << '-'; break; 245 | case InstOp::Mul: oss << '*'; break; 246 | case InstOp::Div: oss << '/'; break; 247 | case InstOp::Mod: oss << '%'; break; 248 | default: assert(false); 249 | } 250 | oss << " rhs);\n"; 251 | oss << kIndent << "}\n"; 252 | } 253 | } 254 | } 255 | return true; 256 | } 257 | 258 | void CCodeGen::Reset() { 259 | // reset internal state 260 | global_.str(""); 261 | global_.clear(); 262 | code_.str(""); 263 | code_.clear(); 264 | last_line_ = 0; 265 | // add code snippets 266 | if (tigger_mode_) global_ << kCCodeTiggerMode; 267 | global_ << kCCodeVM << '\n'; 268 | } 269 | 270 | void CCodeGen::GenerateOnFunc(VMAddr pc, const FuncBody &func) { 271 | // generate function body 272 | auto cur_pc = pc; 273 | std::ostringstream body; 274 | for (const auto &inst : func) { 275 | if (!GenerateInst(body, cur_pc, inst)) return; 276 | ++cur_pc; 277 | } 278 | // generate function 279 | code_ << "static void " << kPrefixFunc << pc << "() {\n"; 280 | code_ << kIndent << "vmaddr_t pool_bp = pool_sp;\n"; 281 | if (!tigger_mode_) { 282 | code_ << kIndent << "vmopr_t *" << kPrefixParams 283 | << " = (vmopr_t *)(mem_pool + pool_sp);\n"; 284 | code_ << kIndent << "pool_sp += " << kStackSize << " * 4;\n"; 285 | code_ << kIndent << "while (" << kStackSize << ") {\n"; 286 | code_ << kIndent2 << "size_t i = " << kStackSize << " - 1;\n"; 287 | code_ << kIndent2 << kPrefixParams << "[i] = " << kStackPop << ";\n"; 288 | code_ << kIndent << "}\n\n"; 289 | } 290 | else { 291 | code_ << '\n'; 292 | } 293 | code_ << body.str() << '\n'; 294 | code_ << kLabelFuncEnd << ":\n"; 295 | code_ << kIndent << "pool_sp = pool_bp;\n"; 296 | code_ << "}\n\n"; 297 | } 298 | 299 | void CCodeGen::GenerateOnEntry(VMAddr pc, const FuncBody &func) { 300 | // generate function body 301 | auto cur_pc = pc; 302 | std::ostringstream body; 303 | for (const auto &inst : func) { 304 | // generate instructions 305 | switch (static_cast(inst.op)) { 306 | // global variables 307 | case InstOp::Var: { 308 | // get symbol of variable 309 | auto sym = GetSymbol(inst.opr, pc); 310 | if (!sym) return; 311 | // emit C code 312 | global_ << "static vmopr_t " << *sym << ";\n"; 313 | break; 314 | } 315 | // global arrays 316 | case InstOp::Arr: { 317 | // get symbol of array 318 | auto sym = GetSymbol(inst.opr, pc); 319 | if (!sym) return; 320 | // emit C code 321 | global_ << "static vmaddr_t " << *sym << ";\n"; 322 | body << kIndent << *sym << " = pool_sp;\n"; 323 | body << kIndent << "pool_sp += " << kStackPop << ";\n"; 324 | break; 325 | } 326 | // other instructions 327 | default: { 328 | if (!GenerateInst(body, cur_pc, inst)) return; 329 | break; 330 | } 331 | } 332 | // update current pc 333 | ++cur_pc; 334 | } 335 | // generate functions 336 | code_ << "static void " << kEntryFunc << "() {\n"; 337 | code_ << body.str() << '\n'; 338 | code_ << kLabelFuncEnd << ":\n"; 339 | code_ << kIndent << "(void)0;\n"; 340 | code_ << "}\n\n"; 341 | } 342 | 343 | void CCodeGen::Dump(std::ostream &os) const { 344 | os << global_.str() << '\n' << code_.str(); 345 | } 346 | -------------------------------------------------------------------------------- /src/vm/vm.cpp: -------------------------------------------------------------------------------- 1 | #include "vm/vm.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "xstl/style.h" 9 | 10 | using namespace minivm::vm; 11 | 12 | // assertion with VM runtime info 13 | #ifdef NDEBUG 14 | #define VM_ASSERT(e, code) static_cast(e) 15 | #else 16 | #define VM_STR1(s) #s 17 | #define VM_STR(s) VM_STR1(s) 18 | #define VM_ASSERT(e, code) \ 19 | do { \ 20 | if (!(e)) { \ 21 | std::cerr << "assertion failed: " #e \ 22 | ", file " VM_STR(__FILE__) ", line " VM_STR(__LINE__) \ 23 | << std::endl; \ 24 | LogError(code); \ 25 | std::_Exit(code); \ 26 | } \ 27 | } while (0) 28 | #endif 29 | 30 | void VM::LogError(std::size_t code) { 31 | using namespace xstl; 32 | // print header 33 | std::cerr << style("Br") << "error"; 34 | if (auto line_num = cont_.FindLineNum(pc_)) { 35 | std::cerr << style("B") << " (line " << *line_num 36 | << ", pc " << pc_ << ')'; 37 | } 38 | std::cerr << ": "; 39 | // print error message 40 | switch (code) { 41 | case kVMErrorEmptyOprStack: { 42 | std::cerr << "accessing empty operand stack"; 43 | break; 44 | } 45 | case kVMErrorInvalidMemPoolAddr: { 46 | std::cerr << "invalid memory pool address"; 47 | break; 48 | } 49 | case kVMErrorSymbolNotFound: { 50 | std::cerr << "symbol not found"; 51 | break; 52 | } 53 | case kVMErrorSymbolRedef: { 54 | std::cerr << "redefining symbol"; 55 | break; 56 | } 57 | case kVMErrorInvalidRegNum: { 58 | std::cerr << "invalid register number"; 59 | break; 60 | } 61 | case kVMErrorInvalidExtFunc: { 62 | std::cerr << "invalid external function"; 63 | break; 64 | } 65 | case kVMErrorExtFuncError: { 66 | std::cerr << "error occurred during external function call"; 67 | break; 68 | } 69 | case kVMErrorInvalidPCAddr: { 70 | std::cerr << "invalid PC address (function without 'return'?)"; 71 | break; 72 | } 73 | default: assert(false); 74 | } 75 | std::cerr << std::endl; 76 | // update error code 77 | error_code_ = code; 78 | } 79 | 80 | VMOpr VM::PopValue() { 81 | VM_ASSERT(!oprs_.empty(), kVMErrorEmptyOprStack); 82 | auto ret = oprs_.top(); 83 | oprs_.pop(); 84 | return ret; 85 | } 86 | 87 | VMOpr &VM::GetOpr() { 88 | VM_ASSERT(!oprs_.empty(), kVMErrorEmptyOprStack); 89 | return oprs_.top(); 90 | } 91 | 92 | VMOpr *VM::GetAddrById(mem::MemId id) { 93 | // find in memory pool 94 | auto ptr = mem_pool_->GetAddress(id); 95 | if (!ptr) { 96 | LogError(kVMErrorInvalidMemPoolAddr); 97 | return nullptr; 98 | } 99 | return reinterpret_cast(ptr); 100 | } 101 | 102 | VMOpr *VM::GetAddrBySym(SymId sym) { 103 | // find in current environment 104 | const auto &env = envs_.top().first; 105 | auto it = env->find(sym); 106 | if (it == env->end()) { 107 | // find in global environment 108 | it = global_env_->find(sym); 109 | if (it == global_env_->end()) { 110 | LogError(kVMErrorSymbolNotFound); 111 | return nullptr; 112 | } 113 | } 114 | return &it->second; 115 | } 116 | 117 | VM::EnvPtr VM::MakeEnv() { 118 | return std::make_shared(); 119 | } 120 | 121 | void VM::InitFuncCall() { 122 | // save the state of memory pool 123 | mem_pool_->SaveState(); 124 | // add a new environment & return address to stack 125 | envs_.push({MakeEnv(), pc_ + 1}); 126 | auto &env = envs_.top().first; 127 | // push parameters 128 | while (!oprs_.empty()) { 129 | // get symbol id of parameter 130 | auto sym = sym_pool_.LogId("p" + std::to_string(oprs_.size() - 1)); 131 | // write value 132 | auto ret = env->insert({sym, PopValue()}).second; 133 | static_cast(ret); 134 | VM_ASSERT(ret, kVMErrorSymbolRedef); 135 | } 136 | } 137 | 138 | bool VM::RegisterFunction(std::string_view name, ExtFunc func) { 139 | auto id = sym_pool_.LogId(name); 140 | return ext_funcs_.insert({id, func}).second; 141 | } 142 | 143 | std::optional VM::GetParamFromCurPool(std::size_t param_id) const { 144 | auto sym = sym_pool_.FindId("p" + std::to_string(param_id)); 145 | if (!sym) return {}; 146 | const auto &env = envs_.top().first; 147 | auto it = env->find(*sym); 148 | if (it == env->end()) return {}; 149 | return it->second; 150 | } 151 | 152 | void VM::Reset() { 153 | // reset pc to zero 154 | pc_ = 0; 155 | // clear all stacks 156 | while (!oprs_.empty()) oprs_.pop(); 157 | while (!envs_.empty()) envs_.pop(); 158 | // make a new environment for global environment 159 | envs_.push({MakeEnv(), 0}); 160 | global_env_ = envs_.top().first; 161 | // save current state of memory pool 162 | mem_pool_->SaveState(); 163 | // reset all static registers 164 | regs_.assign(regs_.size(), 0xdeadc0de); 165 | // reset error code 166 | error_code_ = 0; 167 | } 168 | 169 | std::optional VM::Run() { 170 | #define VM_NEXT(pc_ofs) \ 171 | do { \ 172 | pc_ += pc_ofs; \ 173 | inst = cont_.GetInst(pc_); \ 174 | goto *kInstLabels[inst->op]; \ 175 | } while (0) 176 | 177 | const void *kInstLabels[] = {VM_INSTS(VM_EXPAND_LABEL_LIST)}; 178 | const VMInst *inst; 179 | VM_NEXT(0); 180 | 181 | // allocate memory for variable 182 | VM_LABEL(Var) { 183 | // initialize variable if is in global environment 184 | auto init = envs_.size() == 1 ? 0 : 0xdeadc0de; 185 | auto succ = envs_.top().first->insert({inst->opr, init}).second; 186 | VM_ASSERT(succ, kVMErrorSymbolRedef); 187 | static_cast(succ); 188 | VM_NEXT(1); 189 | } 190 | 191 | // allocate memory for array 192 | VM_LABEL(Arr) { 193 | // add a new entry to environment 194 | auto ret = envs_.top().first->insert({inst->opr, 0}); 195 | VM_ASSERT(ret.second, kVMErrorSymbolRedef); 196 | // allocate a new memory if success 197 | if (ret.second) { 198 | // allocate initialized memory if is in global environment 199 | ret.first->second = 200 | mem_pool_->Allocate(PopValue(), envs_.size() == 1); 201 | } 202 | VM_NEXT(1); 203 | } 204 | 205 | // load value from address 206 | VM_LABEL(Ld) { 207 | // get address from memory pool 208 | auto ptr = GetAddrById(PopValue()); 209 | if (!ptr) return {}; 210 | // push to stack 211 | oprs_.push(*ptr); 212 | VM_NEXT(1); 213 | } 214 | 215 | // load variable 216 | VM_LABEL(LdVar) { 217 | // get address from memory pool 218 | auto ptr = GetAddrBySym(inst->opr); 219 | if (!ptr) return {}; 220 | // push to stack 221 | oprs_.push(*ptr); 222 | VM_NEXT(1); 223 | } 224 | 225 | // load static register 226 | VM_LABEL(LdReg) { 227 | VM_ASSERT(inst->opr < regs_.size(), kVMErrorInvalidRegNum); 228 | oprs_.push(regs_[inst->opr]); 229 | VM_NEXT(1); 230 | } 231 | 232 | // store value to address 233 | VM_LABEL(St) { 234 | // get address from memory pool 235 | auto ptr = GetAddrById(PopValue()); 236 | if (!ptr) return {}; 237 | // write value 238 | *ptr = PopValue(); 239 | VM_NEXT(1); 240 | } 241 | 242 | // store variable 243 | VM_LABEL(StVar) { 244 | // get address from memory pool 245 | auto ptr = GetAddrBySym(inst->opr); 246 | if (!ptr) return {}; 247 | // write value 248 | *ptr = PopValue(); 249 | VM_NEXT(1); 250 | } 251 | 252 | // store variable and preserve 253 | VM_LABEL(StVarP) { 254 | // get address from memory pool 255 | auto ptr = GetAddrBySym(inst->opr); 256 | if (!ptr) return {}; 257 | // write value 258 | *ptr = GetOpr(); 259 | VM_NEXT(1); 260 | } 261 | 262 | // store static register 263 | VM_LABEL(StReg) { 264 | VM_ASSERT(inst->opr < regs_.size(), kVMErrorInvalidRegNum); 265 | regs_[inst->opr] = PopValue(); 266 | VM_NEXT(1); 267 | } 268 | 269 | // store static register and preserve 270 | VM_LABEL(StRegP) { 271 | VM_ASSERT(inst->opr < regs_.size(), kVMErrorInvalidRegNum); 272 | regs_[inst->opr] = GetOpr(); 273 | VM_NEXT(1); 274 | } 275 | 276 | // load immediate (sign-extended) 277 | VM_LABEL(Imm) { 278 | constexpr auto kSignBit = 1u << (kVMInstImmLen - 1); 279 | constexpr auto kUpperOnes = (1u << (32 - kVMInstImmLen)) - 1; 280 | auto val = inst->opr; 281 | if (val & kSignBit) val |= kUpperOnes << kVMInstImmLen; 282 | oprs_.push(val); 283 | VM_NEXT(1); 284 | } 285 | 286 | // load immediate to upper bits 287 | VM_LABEL(ImmHi) { 288 | constexpr auto kMaskLo = (1u << kVMInstImmLen) - 1; 289 | constexpr auto kMaskHi = (1u << (32 - kVMInstImmLen)) - 1; 290 | GetOpr() &= kMaskLo; 291 | GetOpr() |= (inst->opr & kMaskHi) << kVMInstImmLen; 292 | VM_NEXT(1); 293 | } 294 | 295 | // branch if not zero 296 | VM_LABEL(Bnz) { 297 | if (PopValue()) { 298 | pc_ = inst->opr; 299 | VM_NEXT(0); 300 | } 301 | else { 302 | VM_NEXT(1); 303 | } 304 | } 305 | 306 | // jump to target 307 | VM_LABEL(Jmp) { 308 | pc_ = inst->opr; 309 | VM_NEXT(0); 310 | } 311 | 312 | // call function 313 | VM_LABEL(Call) { 314 | InitFuncCall(); 315 | pc_ = inst->opr; 316 | VM_NEXT(0); 317 | } 318 | 319 | // call external function 320 | VM_LABEL(CallExt) { 321 | // get external function 322 | auto it = ext_funcs_.find(inst->opr); 323 | if (it == ext_funcs_.end()) { 324 | LogError(kVMErrorInvalidExtFunc); 325 | return {}; 326 | } 327 | // perform function call 328 | InitFuncCall(); 329 | if (!it->second(*this)) { 330 | LogError(kVMErrorExtFuncError); 331 | return {}; 332 | } 333 | // perform return operation 334 | VM_GOTO(Ret); 335 | } 336 | 337 | // return from function call 338 | VM_LABEL(Ret) { 339 | // restore the state of memory pool 340 | mem_pool_->RestoreState(); 341 | // get offset of return address 342 | auto addr_ofs = envs_.top().second - pc_; 343 | envs_.pop(); 344 | // check if need to stop execution 345 | if (envs_.empty()) { 346 | return regs_.empty() ? PopValue() : regs_[ret_reg_id_]; 347 | } 348 | VM_NEXT(addr_ofs); 349 | } 350 | 351 | // breakpoint 352 | VM_LABEL(Break) { 353 | // find debugger callback symbol 354 | if (auto id = sym_pool_.FindId(kVMDebugger)) { 355 | // find debugger callback 356 | auto it = ext_funcs_.find(*id); 357 | if (it != ext_funcs_.end()) { 358 | // call debugger 359 | if (!it->second(*this)) return 0; 360 | } 361 | } 362 | VM_NEXT(0); 363 | } 364 | 365 | // error 366 | VM_LABEL(Error) { 367 | LogError(inst->opr); 368 | return {}; 369 | } 370 | 371 | // logical negation 372 | VM_LABEL(LNot) { 373 | GetOpr() = !GetOpr(); 374 | VM_NEXT(1); 375 | } 376 | 377 | // logical AND 378 | VM_LABEL(LAnd) { 379 | auto rhs = PopValue(); 380 | GetOpr() = GetOpr() && rhs; 381 | VM_NEXT(1); 382 | } 383 | 384 | // logical OR 385 | VM_LABEL(LOr) { 386 | auto rhs = PopValue(); 387 | GetOpr() = GetOpr() || rhs; 388 | VM_NEXT(1); 389 | } 390 | 391 | // set if equal 392 | VM_LABEL(Eq) { 393 | auto rhs = PopValue(); 394 | GetOpr() = GetOpr() == rhs; 395 | VM_NEXT(1); 396 | } 397 | 398 | // set if not equal 399 | VM_LABEL(Ne) { 400 | auto rhs = PopValue(); 401 | GetOpr() = GetOpr() != rhs; 402 | VM_NEXT(1); 403 | } 404 | 405 | // set if greater than 406 | VM_LABEL(Gt) { 407 | auto rhs = PopValue(); 408 | GetOpr() = GetOpr() > rhs; 409 | VM_NEXT(1); 410 | } 411 | 412 | // set if less than 413 | VM_LABEL(Lt) { 414 | auto rhs = PopValue(); 415 | GetOpr() = GetOpr() < rhs; 416 | VM_NEXT(1); 417 | } 418 | 419 | // set if greater than or equal 420 | VM_LABEL(Ge) { 421 | auto rhs = PopValue(); 422 | GetOpr() = GetOpr() >= rhs; 423 | VM_NEXT(1); 424 | } 425 | 426 | // set if less than or equal 427 | VM_LABEL(Le) { 428 | auto rhs = PopValue(); 429 | GetOpr() = GetOpr() <= rhs; 430 | VM_NEXT(1); 431 | } 432 | 433 | // negation 434 | VM_LABEL(Neg) { 435 | GetOpr() = -GetOpr(); 436 | VM_NEXT(1); 437 | } 438 | 439 | // addition 440 | VM_LABEL(Add) { 441 | auto rhs = PopValue(); 442 | GetOpr() += rhs; 443 | VM_NEXT(1); 444 | } 445 | 446 | // subtraction 447 | VM_LABEL(Sub) { 448 | auto rhs = PopValue(); 449 | GetOpr() -= rhs; 450 | VM_NEXT(1); 451 | } 452 | 453 | // multiplication 454 | VM_LABEL(Mul) { 455 | auto rhs = PopValue(); 456 | GetOpr() *= rhs; 457 | VM_NEXT(1); 458 | } 459 | 460 | // division 461 | VM_LABEL(Div) { 462 | auto rhs = PopValue(); 463 | GetOpr() /= rhs; 464 | VM_NEXT(1); 465 | } 466 | 467 | // modulo operation 468 | VM_LABEL(Mod) { 469 | auto rhs = PopValue(); 470 | GetOpr() %= rhs; 471 | VM_NEXT(1); 472 | } 473 | 474 | // discard the top value on the stack 475 | VM_LABEL(Pop) { 476 | PopValue(); 477 | VM_NEXT(1); 478 | } 479 | 480 | // clear operand stack 481 | VM_LABEL(Clear) { 482 | while (!oprs_.empty()) oprs_.pop(); 483 | VM_NEXT(1); 484 | } 485 | 486 | #undef VM_NEXT 487 | } 488 | -------------------------------------------------------------------------------- /src/debugger/expreval.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVM_DEBUGGER_EXPREVAL_H_ 2 | #define MINIVM_DEBUGGER_EXPREVAL_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "xstl/guard.h" 21 | 22 | namespace minivm::debugger { 23 | 24 | // base class of expression evaluator 25 | template 26 | class ExprEvaluatorBase { 27 | public: 28 | // 'ValType' must be an integral 29 | static_assert(std::is_integral_v, "integral required"); 30 | 31 | ExprEvaluatorBase() : next_id_(0) {} 32 | virtual ~ExprEvaluatorBase() = default; 33 | 34 | // evaluate expression with record 35 | std::optional Eval(std::string_view expr) { 36 | return Eval(expr, true); 37 | } 38 | 39 | // evaluate expression 40 | std::optional Eval(std::string_view expr, bool record) { 41 | // reset string stream 42 | iss_.str({expr.data(), expr.size()}); 43 | iss_.clear(); 44 | last_char_ = ' '; 45 | // call lexer & parser 46 | NextToken(); 47 | auto val = Parse(); 48 | if (!val) return std::nullopt; 49 | // record expression 50 | if (record) { 51 | // trim expression string 52 | expr.remove_prefix( 53 | std::min(expr.find_first_not_of(" "), expr.size())); 54 | auto pos = expr.find_last_not_of(" "); 55 | if (pos != expr.npos) expr.remove_suffix(expr.size() - pos - 1); 56 | // store to record 57 | records_.insert({next_id_++, std::string(expr)}); 58 | } 59 | return val; 60 | } 61 | 62 | // evaluate expression by the specific record id 63 | std::optional Eval(std::uint32_t id) { 64 | auto it = records_.find(id); 65 | if (it == records_.end()) return std::nullopt; 66 | return Eval(it->second, false); 67 | } 68 | 69 | // show expression by the specific record id 70 | void PrintExpr(std::ostream &os, std::uint32_t id) { 71 | auto it = records_.find(id); 72 | assert(it != records_.end()); 73 | os << it->second; 74 | } 75 | 76 | // remove the specific record 77 | void RemoveRecord(std::uint32_t id) { records_.erase(id); } 78 | 79 | // remove all records 80 | void Clear() { records_.clear(); } 81 | 82 | // getters 83 | // next record id 84 | std::uint32_t next_id() const { return next_id_; } 85 | 86 | protected: 87 | // get value of the specific symbol 88 | virtual std::optional GetValueOfSym(std::string_view sym) = 0; 89 | // get value of the specific memory address 90 | virtual std::optional GetValueOfAddr(ValType addr) = 0; 91 | 92 | private: 93 | /* 94 | EBNF of expressions: 95 | 96 | binary ::= unary bin_op unray 97 | unary ::= una_op value 98 | value ::= NUM | SYMBOL | '(' binary ')' 99 | */ 100 | 101 | enum class Token { 102 | End, Error, Char, 103 | Num, Symbol, ValRef, 104 | Operator, 105 | }; 106 | 107 | enum class Operator { 108 | Add, Sub, Mul, Div, Mod, 109 | And, Or, Not, Xor, Shl, Shr, 110 | LogicAnd, LogicOr, LogicNot, 111 | Equal, NotEqual, 112 | LessThan, LessEqual, GreaterThan, GreaterEqual, 113 | }; 114 | 115 | // print lexer error message to stderr 116 | Token LogLexerError(std::string_view msg) { 117 | std::cout << "ERROR (expr.lexer): " << msg << std::endl; 118 | return cur_token_ = Token::Error; 119 | } 120 | 121 | // print parser error message to stderr 122 | static std::optional LogParserError(std::string_view msg) { 123 | std::cout << "ERROR (expr.parser): " << msg << std::endl; 124 | return std::nullopt; 125 | } 126 | 127 | // check if specific character can appear in operators 128 | static bool IsOperatorChar(char c) { 129 | assert(c); 130 | constexpr const char kOpChar[] = "+-*/%&|~^!=<>"; 131 | for (const auto &i : kOpChar) { 132 | if (c == i) return true; 133 | } 134 | return false; 135 | } 136 | 137 | // get the precedence of the specific operator 138 | static int GetOpPrec(Operator op) { 139 | constexpr int kOpPrec[] = { 140 | 90, 90, 100, 100, 100, 50, 30, -1, 40, 80, 141 | 80, 20, 10, -1, 60, 60, 70, 70, 70, 70, 142 | }; 143 | return kOpPrec[static_cast(op)]; 144 | } 145 | 146 | // perform binary operation 147 | static ValType CalcByOperator(Operator op, ValType lhs, ValType rhs) { 148 | switch (op) { 149 | case Operator::Add: return lhs + rhs; 150 | case Operator::Sub: return lhs - rhs; 151 | case Operator::Mul: return lhs * rhs; 152 | case Operator::Div: return lhs / rhs; 153 | case Operator::And: return lhs & rhs; 154 | case Operator::Or: return lhs | rhs; 155 | case Operator::Xor: return lhs ^ rhs; 156 | case Operator::Shl: return lhs << rhs; 157 | case Operator::Shr: return lhs >> rhs; 158 | case Operator::LogicAnd: return lhs && rhs; 159 | case Operator::LogicOr: return lhs || rhs; 160 | case Operator::Equal: return lhs == rhs; 161 | case Operator::NotEqual: return lhs != rhs; 162 | case Operator::LessThan: return lhs < rhs; 163 | case Operator::LessEqual: return lhs <= rhs; 164 | case Operator::GreaterThan: return lhs > rhs; 165 | case Operator::GreaterEqual: return lhs >= rhs; 166 | default: assert(false); return 0; 167 | } 168 | } 169 | 170 | // (lexer) read the next character 171 | void NextChar() { iss_ >> last_char_; } 172 | 173 | // (lexer) read the next token 174 | Token NextToken() { 175 | // skip spaces 176 | while (!iss_.eof() && std::isspace(last_char_)) NextChar(); 177 | // end of stream 178 | if (iss_.eof()) return cur_token_ = Token::End; 179 | // numbers 180 | if (std::isdigit(last_char_)) return HandleNum(); 181 | // value references or symbols 182 | if (last_char_ == '$') return HandleValRef(); 183 | // symbols 184 | if (std::isalpha(last_char_)) return HandleSymbol(); 185 | // operators 186 | if (IsOperatorChar(last_char_)) return HandleOperator(); 187 | // other characters 188 | char_val_ = last_char_; 189 | NextChar(); 190 | return cur_token_ = Token::Char; 191 | } 192 | 193 | // (lexer) read numbers 194 | Token HandleNum() { 195 | std::string num; 196 | bool is_hex = false; 197 | // check if is hexadecimal number 198 | if (last_char_ == '0') { 199 | NextChar(); 200 | if (std::tolower(last_char_) == 'x') { 201 | // is hexadecimal 202 | is_hex = true; 203 | NextChar(); 204 | } 205 | else if (!std::isdigit(last_char_)) { 206 | // just zero 207 | num_val_ = 0; 208 | return cur_token_ = Token::Num; 209 | } 210 | } 211 | // read number string 212 | while (!iss_.eof() && std::isxdigit(last_char_)) { 213 | num += last_char_; 214 | NextChar(); 215 | } 216 | // convert to number 217 | char *end_pos; 218 | num_val_ = std::strtol(num.c_str(), &end_pos, is_hex ? 16 : 10); 219 | if (*end_pos) return LogLexerError("invalid number literal"); 220 | return cur_token_ = Token::Num; 221 | } 222 | 223 | // (lexer) read variable references 224 | Token HandleValRef() { 225 | std::string ref; 226 | // eat '$' 227 | NextChar(); 228 | if (std::isalpha(last_char_)) { 229 | // get symbol name 230 | ref += '$'; 231 | while (!iss_.eof() && std::isalnum(last_char_)) { 232 | ref += last_char_; 233 | NextChar(); 234 | } 235 | // update last symbol 236 | sym_val_ = ref; 237 | return cur_token_ = Token::Symbol; 238 | } 239 | else if (std::isdigit(last_char_)) { 240 | // get value reference number (record id) 241 | while (!iss_.eof() && std::isdigit(last_char_)) { 242 | ref += last_char_; 243 | NextChar(); 244 | } 245 | // convert to number 246 | char *end_pos; 247 | val_ref_ = std::strtol(ref.c_str(), &end_pos, 10); 248 | if (*end_pos || records_.find(val_ref_) == records_.end()) { 249 | return LogLexerError("invalid value reference"); 250 | } 251 | return cur_token_ = Token::ValRef; 252 | } 253 | else { 254 | return LogLexerError("invalid '$' expression"); 255 | } 256 | } 257 | 258 | // (lexer) read symbols 259 | Token HandleSymbol() { 260 | std::string sym; 261 | // get symbol string 262 | do { 263 | sym += last_char_; 264 | NextChar(); 265 | } while (!iss_.eof() && std::isalnum(last_char_)); 266 | // update last symbol 267 | sym_val_ = sym; 268 | return cur_token_ = Token::Symbol; 269 | } 270 | 271 | // (lexer) read operators 272 | Token HandleOperator() { 273 | std::string op; 274 | // get operator string 275 | do { 276 | op += last_char_; 277 | NextChar(); 278 | } while (!iss_.eof() && IsOperatorChar(last_char_)); 279 | // check is a valid operator 280 | constexpr std::string_view kOpList[] = { 281 | "+", "-", "*", "/", "%", "&", "|", "~", "^", "<<", 282 | ">>", "&&", "||", "!", "==", "!=", "<", "<=", ">", ">=", 283 | }; 284 | for (std::size_t i = 0; i < sizeof(kOpList) / sizeof(std::string_view); 285 | ++i) { 286 | if (kOpList[i] == op) { 287 | op_val_ = static_cast(i); 288 | return cur_token_ = Token::Operator; 289 | } 290 | } 291 | return LogLexerError("invalid operator"); 292 | } 293 | 294 | // (parser) parse the current expression 295 | std::optional Parse() { 296 | if (cur_token_ == Token::End) return std::nullopt; 297 | return ParseBinary(); 298 | } 299 | 300 | // (parser) parse binary expressions 301 | std::optional ParseBinary() { 302 | std::stack oprs; 303 | std::stack ops; 304 | // get the first value 305 | auto val = ParseUnary(); 306 | if (!val) return std::nullopt; 307 | oprs.push(*val); 308 | // calculate using stack 309 | while (cur_token_ == Token::Operator) { 310 | // get operator 311 | auto op = op_val_; 312 | if (GetOpPrec(op) < 0) break; 313 | NextToken(); 314 | // handle operator 315 | while (!ops.empty() && GetOpPrec(ops.top()) >= GetOpPrec(op)) { 316 | // get current operator & operand 317 | auto cur_op = ops.top(); 318 | ops.pop(); 319 | auto rhs = oprs.top(); 320 | oprs.pop(); 321 | auto lhs = oprs.top(); 322 | oprs.pop(); 323 | // calculate 324 | oprs.push(CalcByOperator(cur_op, lhs, rhs)); 325 | } 326 | // push & get next value 327 | ops.push(op); 328 | if (!(val = ParseUnary())) return false; 329 | oprs.push(*val); 330 | } 331 | // clear stacks 332 | while (!ops.empty()) { 333 | auto cur_op = ops.top(); 334 | ops.pop(); 335 | auto rhs = oprs.top(); 336 | oprs.pop(); 337 | auto lhs = oprs.top(); 338 | oprs.pop(); 339 | oprs.push(CalcByOperator(cur_op, lhs, rhs)); 340 | } 341 | return oprs.top(); 342 | } 343 | 344 | // (parser) parse unary expressions 345 | std::optional ParseUnary() { 346 | // check if need to get operator 347 | if (cur_token_ == Token::Operator) { 348 | auto op = op_val_; 349 | NextToken(); 350 | // get operand 351 | auto opr = ParseUnary(); 352 | if (!opr) return std::nullopt; 353 | // calculate 354 | switch (op) { 355 | case Operator::Add: return *opr; 356 | case Operator::Sub: return -*opr; 357 | case Operator::LogicNot: return !*opr; 358 | case Operator::Not: return ~*opr; 359 | case Operator::Mul: return GetValueOfAddr(*opr); 360 | default: return LogParserError("invalid unary operator"); 361 | } 362 | } 363 | else { 364 | return ParseValue(); 365 | } 366 | } 367 | 368 | // (parser) parse values 369 | std::optional ParseValue() { 370 | xstl::Guard guard([this] { NextToken(); }); 371 | switch (cur_token_) { 372 | case Token::Num: { 373 | // just number 374 | return num_val_; 375 | } 376 | case Token::Symbol: { 377 | // get value of the symbol 378 | return GetValueOfSym(sym_val_); 379 | } 380 | case Token::ValRef: { 381 | // store current state 382 | auto iss = std::move(iss_); 383 | auto last_char = last_char_; 384 | auto cur_token = cur_token_; 385 | // evaluate record 386 | auto val = Eval(val_ref_); 387 | assert(val); 388 | // restore current state 389 | iss_ = std::move(iss); 390 | last_char_ = last_char; 391 | cur_token_ = cur_token; 392 | return val; 393 | } 394 | case Token::Char: { 395 | // check & eat '(' 396 | if (char_val_ != '(') return LogParserError("expected '('"); 397 | NextToken(); 398 | // parse inner binary expression 399 | auto val = ParseBinary(); 400 | if (!val) return std::nullopt; 401 | // check ')' 402 | if (cur_token_ != Token::Char || char_val_ != ')') { 403 | return LogParserError("expected ')'"); 404 | } 405 | return val; 406 | } 407 | default: return LogParserError("invalid value"); 408 | } 409 | } 410 | 411 | // all stored records 412 | std::unordered_map records_; 413 | // next record id 414 | std::uint32_t next_id_; 415 | 416 | // lexer related stuffs 417 | // 418 | // current expression 419 | std::istringstream iss_; 420 | // last character 421 | char last_char_; 422 | // last character value 423 | char char_val_; 424 | // last number 425 | ValType num_val_; 426 | // last value reference 427 | std::uint32_t val_ref_; 428 | // last symbol value 429 | std::string sym_val_; 430 | // last operator 431 | Operator op_val_; 432 | 433 | // parser related stuffs 434 | // 435 | // current token 436 | Token cur_token_; 437 | }; 438 | 439 | } // namespace minivm::debugger 440 | 441 | #endif // MINIVM_DEBUGGER_EXPREVAL_H_ 442 | -------------------------------------------------------------------------------- /src/vm/instcont.cpp: -------------------------------------------------------------------------------- 1 | #include "vm/instcont.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "xstl/style.h" 9 | 10 | using namespace minivm::vm; 11 | 12 | namespace { 13 | 14 | // string of all opcodes 15 | const char *kInstOpStr[] = {VM_INSTS(VM_EXPAND_STR_ARRAY)}; 16 | 17 | // a 'Break' instruction 18 | const VMInst kBreakInst = {static_cast(InstOp::Break)}; 19 | 20 | } // namespace 21 | 22 | void VMInstContainer::PushInst(InstOp op) { 23 | PushInst(op, 0); 24 | } 25 | 26 | void VMInstContainer::PushInst(InstOp op, std::uint32_t opr) { 27 | auto &insts = cur_env_ == &global_env_ ? global_insts_ : insts_; 28 | VMInst inst = {static_cast(op), opr}; 29 | insts.push_back(inst); 30 | } 31 | 32 | VMInst *VMInstContainer::GetLastInst() { 33 | // get current instruction container 34 | auto &insts = cur_env_ == &global_env_ ? global_insts_ : insts_; 35 | // try to get last label definition 36 | auto it = label_defs_.find(std::string(last_label_)); 37 | if (it != label_defs_.end()) { 38 | // check if last label points to current pc 39 | // we must treat all labels as barriers to prevent over-optimization 40 | const auto &info = it->second; 41 | if (info.defined && info.pc == insts.size()) return nullptr; 42 | } 43 | return insts.empty() ? nullptr : &insts.back(); 44 | } 45 | 46 | SymId VMInstContainer::DefSymbol(std::string_view sym) { 47 | auto id = sym_pool_.LogId(sym); 48 | if (global_env_.count(id) || !cur_env_->insert(id).second) { 49 | LogError("symbol has already been defined", sym); 50 | return -1; 51 | } 52 | return id; 53 | } 54 | 55 | SymId VMInstContainer::GetSymbol(std::string_view sym) { 56 | auto id = sym_pool_.FindId(sym); 57 | if (!id || (!cur_env_->count(*id) && !global_env_.count(*id))) { 58 | LogError("using undefined symbol", sym); 59 | return -1; 60 | } 61 | return *id; 62 | } 63 | 64 | void VMInstContainer::LogRelatedInsts(std::string_view label) { 65 | if (cur_env_ == &global_env_) { 66 | return LogError("using label reference in global environment"); 67 | } 68 | auto &info = label_defs_[std::string(label)]; 69 | info.related_insts.push_back(insts_.size()); 70 | } 71 | 72 | void VMInstContainer::Reset(std::string_view src_file) { 73 | sym_pool_.Reset(); 74 | has_error_ = false; 75 | global_env_.clear(); 76 | local_env_.clear(); 77 | src_file_ = src_file; 78 | line_defs_.clear(); 79 | pc_defs_.clear(); 80 | label_defs_.clear(); 81 | func_pcs_.clear(); 82 | insts_.clear(); 83 | global_insts_.clear(); 84 | breakpoints_.clear(); 85 | trap_mode_ = false; 86 | while (!step_counters_.empty()) step_counters_.pop(); 87 | // insert jump instruction to entry point 88 | cur_env_ = &local_env_; 89 | LogRelatedInsts(kVMEntry); 90 | PushInst(InstOp::Jmp); 91 | cur_env_ = &global_env_; 92 | } 93 | 94 | void VMInstContainer::PushVar(std::string_view sym) { 95 | PushInst(InstOp::Var, DefSymbol(sym)); 96 | } 97 | 98 | void VMInstContainer::PushArr(std::string_view sym) { 99 | PushInst(InstOp::Arr, DefSymbol(sym)); 100 | } 101 | 102 | void VMInstContainer::PushLabel(std::string_view name) { 103 | // try to insert a new entry 104 | auto it = label_defs_.insert({std::string(name), {false}}).first; 105 | auto &info = it->second; 106 | // check if label has already been defined 107 | if (info.defined) { 108 | LogError("label has already been defined", name); 109 | } 110 | else { 111 | info.defined = true; 112 | info.pc = insts_.size(); 113 | last_label_ = it->first; 114 | } 115 | } 116 | 117 | void VMInstContainer::PushLoad() { 118 | PushInst(InstOp::Ld); 119 | } 120 | 121 | void VMInstContainer::PushLoad(std::string_view sym) { 122 | auto sym_id = GetSymbol(sym); 123 | // check if last instruction is 'StVar sym' 124 | /* NOTE: 125 | * the 'StVarP sym' instruction can not be rewritten, 126 | * consider the following Eeyore statement: 127 | * t0 = t0 + t0 128 | */ 129 | if (auto last = GetLastInst(); 130 | last && last->op == static_cast(InstOp::StVar)) { 131 | // check if is the same symbol 132 | if (last->opr == sym_id) { 133 | // just rewrite last instruction as 'StVarP' 134 | last->op = static_cast(InstOp::StVarP); 135 | return; 136 | } 137 | } 138 | PushInst(InstOp::LdVar, sym_id); 139 | } 140 | 141 | void VMInstContainer::PushLoad(VMOpr imm) { 142 | constexpr auto kLower = -(1 << (kVMInstImmLen - 1)); 143 | constexpr auto kUpper = (1 << (kVMInstImmLen - 1)) - 1; 144 | constexpr auto kLowerMask = (1u << kVMInstImmLen) - 1; 145 | constexpr auto kUpperMask = (1u << (32 - kVMInstImmLen)) - 1; 146 | if (imm >= kLower && imm <= kUpper) { 147 | PushInst(InstOp::Imm, imm); 148 | } 149 | else { 150 | PushInst(InstOp::Imm, imm & kLowerMask); 151 | PushInst(InstOp::ImmHi, (imm >> kVMInstImmLen) & kUpperMask); 152 | } 153 | } 154 | 155 | void VMInstContainer::PushLdReg(RegId reg_id) { 156 | // check if last instruction is 'StReg reg_id' 157 | /* NOTE: 158 | * the 'StRegP reg' instruction can not be rewritten, 159 | * consider the following Eeyore statement: 160 | * t0 = t0 + t0 161 | */ 162 | if (auto last = GetLastInst(); 163 | last && last->op == static_cast(InstOp::StReg)) { 164 | // check if is the same register id, but not 'x0' 165 | if (reg_id && last->opr == reg_id) { 166 | // just rewrite last instruction as 'StRegP' 167 | last->op = static_cast(InstOp::StRegP); 168 | return; 169 | } 170 | } 171 | // generate load register 172 | if (reg_id) { 173 | PushInst(InstOp::LdReg, reg_id); 174 | } 175 | else { 176 | // loading 'x0', just load zero 177 | PushInst(InstOp::Imm, 0); 178 | } 179 | } 180 | 181 | void VMInstContainer::PushLdFrame(VMOpr offset) { 182 | PushLdFrameAddr(offset); 183 | PushLoad(); 184 | } 185 | 186 | void VMInstContainer::PushLdFrameAddr(VMOpr offset) { 187 | PushLoad(offset * 4); 188 | PushLoad(kVMFrame); 189 | PushOp(InstOp::Add); 190 | } 191 | 192 | void VMInstContainer::PushStore() { 193 | PushInst(InstOp::St); 194 | } 195 | 196 | void VMInstContainer::PushStore(std::string_view sym) { 197 | PushInst(InstOp::StVar, GetSymbol(sym)); 198 | } 199 | 200 | void VMInstContainer::PushStReg(RegId reg_id) { 201 | if (reg_id) { 202 | PushInst(InstOp::StReg, reg_id); 203 | } 204 | else { 205 | // storing 'x0', discard the top value on the stack 206 | PushInst(InstOp::Pop); 207 | } 208 | } 209 | 210 | void VMInstContainer::PushStFrame(VMOpr offset) { 211 | PushLdFrameAddr(offset); 212 | PushStore(); 213 | } 214 | 215 | void VMInstContainer::PushBnz(std::string_view label) { 216 | LogRelatedInsts(label); 217 | PushInst(InstOp::Bnz); 218 | } 219 | 220 | void VMInstContainer::PushJump(std::string_view label) { 221 | LogRelatedInsts(label); 222 | PushInst(InstOp::Jmp); 223 | } 224 | 225 | void VMInstContainer::PushCall(std::string_view label) { 226 | LogRelatedInsts(label); 227 | PushInst(InstOp::Call); 228 | } 229 | 230 | void VMInstContainer::PushError(std::size_t code) { 231 | PushInst(InstOp::Error, code); 232 | } 233 | 234 | void VMInstContainer::PushOp(InstOp op) { 235 | PushInst(op); 236 | } 237 | 238 | void VMInstContainer::LogError(std::string_view message) { 239 | LogError(message, cur_line_num_); 240 | } 241 | 242 | void VMInstContainer::LogError(std::string_view message, 243 | std::uint32_t line_num) { 244 | using namespace xstl; 245 | std::cerr << style("Br") << "error "; 246 | std::cerr << style("B") << "(line " << line_num << "): "; 247 | std::cerr << message << std::endl; 248 | has_error_ = true; 249 | } 250 | 251 | void VMInstContainer::LogError(std::string_view message, 252 | std::string_view sym) { 253 | LogError(message, sym, cur_line_num_); 254 | } 255 | 256 | void VMInstContainer::LogError(std::string_view message, 257 | std::string_view sym, 258 | std::uint32_t line_num) { 259 | using namespace xstl; 260 | std::cerr << style("Br") << "error "; 261 | std::cerr << style("B") << "(line " << line_num 262 | << ", sym \"" << sym << "\"): "; 263 | std::cerr << message << std::endl; 264 | has_error_ = true; 265 | } 266 | 267 | void VMInstContainer::LogLineNum(std::uint32_t line_num) { 268 | cur_line_num_ = line_num; 269 | // store local line number definitions only 270 | if (cur_env_ == &global_env_) return; 271 | line_defs_[line_num] = insts_.size(); 272 | pc_defs_[insts_.size()] = line_num; 273 | } 274 | 275 | void VMInstContainer::EnterFunc(std::uint32_t param_count) { 276 | // switch environment 277 | if (cur_env_ != &global_env_) { 278 | return LogError("nested function is unsupported"); 279 | } 280 | cur_env_ = &local_env_; 281 | // initialize parameters 282 | for (std::uint32_t i = 0; i < param_count; ++i) { 283 | auto param = "p" + std::to_string(i); 284 | DefSymbol(param); 285 | } 286 | // log function definition 287 | func_pcs_.insert(insts_.size()); 288 | } 289 | 290 | void VMInstContainer::EnterFunc(std::uint32_t param_count, 291 | std::uint32_t slot_count, 292 | std::uint32_t line_num) { 293 | EnterFunc(param_count); 294 | // create stack frame 295 | LogLineNum(line_num); 296 | PushLoad(slot_count * 4); 297 | PushArr(kVMFrame); 298 | } 299 | 300 | void VMInstContainer::ExitFunc() { 301 | // raise error in case user forgets to put 302 | // a return statement before exiting the function 303 | PushError(kVMErrorInvalidPCAddr); 304 | // update environment 305 | local_env_.clear(); 306 | cur_env_ = &global_env_; 307 | } 308 | 309 | void VMInstContainer::SealContainer() { 310 | // insert label for entry point 311 | PushLabel(kVMEntry); 312 | // insert all global instructions 313 | insts_.insert(insts_.end(), global_insts_.begin(), global_insts_.end()); 314 | global_insts_.clear(); 315 | // insert main function call & return 316 | cur_env_ = &local_env_; 317 | PushCall(kVMMain); 318 | PushOp(InstOp::Ret); 319 | // traverse all label definitions 320 | for (auto it = label_defs_.begin(); it != label_defs_.end();) { 321 | auto &&[label, info] = *it; 322 | if (info.defined) { 323 | // backfill pc to 'imm' field of all related instructions 324 | for (const auto &pc : info.related_insts) { 325 | insts_[pc].opr = info.pc; 326 | } 327 | info.related_insts.clear(); 328 | ++it; 329 | } 330 | else { 331 | // check to see if there are any non-function call instructions 332 | for (const auto &pc : info.related_insts) { 333 | auto &inst = insts_[pc]; 334 | if (inst.op == static_cast(InstOp::Call)) { 335 | // function call found, convert to external function call 336 | inst.op = static_cast(InstOp::CallExt); 337 | inst.opr = sym_pool_.LogId(label); 338 | } 339 | else { 340 | // current label is indeed undefined 341 | auto line_num = FindLineNum(pc); 342 | assert(line_num); 343 | LogError("using undefined label", label, *line_num); 344 | } 345 | } 346 | // remove current label definition 347 | it = label_defs_.erase(it); 348 | } 349 | } 350 | // exit if error occurred 351 | if (has_error_) std::exit(-1); 352 | // release resources 353 | global_env_.clear(); 354 | local_env_.clear(); 355 | } 356 | 357 | void VMInstContainer::ToggleBreakpoint(VMAddr pc, bool enable) { 358 | if (enable) { 359 | // set breakpoint 360 | breakpoints_[pc] = insts_[pc].op; 361 | insts_[pc].op = static_cast(InstOp::Break); 362 | } 363 | else { 364 | // remove breakpoint 365 | auto it = breakpoints_.find(pc); 366 | if (it != breakpoints_.end()) { 367 | insts_[pc].op = it->second; 368 | breakpoints_.erase(it); 369 | } 370 | } 371 | } 372 | 373 | void VMInstContainer::AddStepCounter(std::size_t n, StepCallback callback) { 374 | step_counters_.push({n, callback}); 375 | } 376 | 377 | bool VMInstContainer::Dump(std::ostream &os, VMAddr pc) const { 378 | if (pc >= insts_.size()) return false; 379 | // get the actual instruction 380 | auto inst = insts_[pc]; 381 | auto it = breakpoints_.find(pc); 382 | if (it != breakpoints_.end()) inst.op = it->second; 383 | // dump instruction 384 | os << kInstOpStr[static_cast(inst.op)] << '\t'; 385 | // NOTE: the order of 'case' statements is important 386 | switch (static_cast(inst.op)) { 387 | case InstOp::Var: case InstOp::Arr: case InstOp::LdVar: 388 | case InstOp::StVar: case InstOp::StVarP: case InstOp::CallExt: { 389 | // dump as 'sym' 390 | auto sym = sym_pool_.FindSymbol(inst.opr); 391 | assert(sym); 392 | os << *sym; 393 | break; 394 | } 395 | case InstOp::LdReg: case InstOp::StReg: case InstOp::StRegP: 396 | case InstOp::Imm: case InstOp::ImmHi: case InstOp::Bnz: 397 | case InstOp::Jmp: case InstOp::Call: case InstOp::Error: { 398 | // dump 'opr' field directly 399 | os << inst.opr; 400 | break; 401 | } 402 | default:; 403 | } 404 | return true; 405 | } 406 | 407 | void VMInstContainer::Dump(std::ostream &os) const { 408 | for (VMAddr pc = 0; pc < insts_.size(); ++pc) { 409 | os << pc << ":\t"; 410 | Dump(os, pc); 411 | os << std::endl; 412 | } 413 | } 414 | 415 | std::optional VMInstContainer::FindPC( 416 | std::uint32_t line_num) const { 417 | auto it = line_defs_.find(line_num); 418 | if (it != line_defs_.end()) return it->second; 419 | return {}; 420 | } 421 | 422 | std::optional VMInstContainer::FindPC( 423 | std::string_view label) const { 424 | auto it = label_defs_.find(std::string(label)); 425 | if (it != label_defs_.end()) return it->second.pc; 426 | return {}; 427 | } 428 | 429 | std::optional VMInstContainer::FindLineNum( 430 | VMAddr pc) const { 431 | auto entry_pc = FindPC(kVMEntry); 432 | assert(entry_pc); 433 | if (pc >= *entry_pc) return {}; 434 | auto it = pc_defs_.lower_bound(pc); 435 | if (it == pc_defs_.end()) return {}; 436 | return it->second; 437 | } 438 | 439 | const VMInst *VMInstContainer::GetInst(VMAddr pc) { 440 | auto inst = insts_.data() + pc; 441 | bool break_flag = false; 442 | // handle step counters 443 | if (!step_counters_.empty()) { 444 | auto size = step_counters_.size(); 445 | while (size--) { 446 | auto &&[n, callback] = step_counters_.front(); 447 | bool discard = false; 448 | if (!n) { 449 | discard = true; 450 | // call the callback, or return 'Break' instruction 451 | if (callback) { 452 | callback(*this); 453 | } 454 | else { 455 | break_flag = true; 456 | } 457 | } 458 | else { 459 | // decrease the current step counter 460 | --n; 461 | } 462 | // discard or push to queue again 463 | if (!discard) step_counters_.push(step_counters_.front()); 464 | step_counters_.pop(); 465 | } 466 | } 467 | return trap_mode_ || break_flag ? &kBreakInst : inst; 468 | } 469 | -------------------------------------------------------------------------------- /src/debugger/minidbg/minidbg.cpp: -------------------------------------------------------------------------------- 1 | #include "debugger/minidbg/minidbg.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "front/token.h" 17 | #include "xstl/style.h" 18 | 19 | using namespace minivm::debugger::minidbg; 20 | using namespace minivm::vm; 21 | 22 | // create a new anonymous command handler 23 | #define CMD_HANDLER(func) [this](std::istream &is) { return func(is); } 24 | 25 | namespace { 26 | 27 | // instruction information (for disassembler) 28 | struct InstInfo { 29 | bool is_breakpoint; 30 | VMAddr addr; 31 | std::string disasm; 32 | }; 33 | 34 | // line information (for disassembler) 35 | struct LineInfo { 36 | bool is_breakpoint; 37 | std::uint32_t addr; // line number 38 | std::string_view disasm; 39 | }; 40 | 41 | // name of static registers 42 | const char *kRegNames[] = {TOKEN_REGISTERS(TOKEN_EXPAND_SECOND)}; 43 | 44 | // item of 'info' command 45 | enum class InfoItem { 46 | Stack, Env, Reg, Break, Watch, 47 | }; 48 | 49 | // string -> InfoItem 50 | const std::unordered_map kInfoItems = { 51 | {"stack", InfoItem::Stack}, {"s", InfoItem::Stack}, 52 | {"env", InfoItem::Env}, {"e", InfoItem::Env}, 53 | {"reg", InfoItem::Reg}, {"r", InfoItem::Reg}, 54 | {"break", InfoItem::Break}, {"b", InfoItem::Break}, 55 | {"watch", InfoItem::Watch}, {"w", InfoItem::Watch}, 56 | }; 57 | 58 | // print instructions to stdout 59 | template 60 | void PrintInst(const std::vector &info, Addr cur_addr) { 61 | using namespace xstl; 62 | assert(!info.empty()); 63 | // check if there is a breakpoint 64 | bool print_bp = std::find_if(info.begin(), info.end(), [](const Info &i) { 65 | return i.is_breakpoint; 66 | }) != info.end(); 67 | // get maximum address 68 | auto max_addr = std::max_element(info.begin(), info.end(), 69 | [](const Info &l, const Info &r) { 70 | return l.addr < r.addr; 71 | })->addr; 72 | auto addr_padd = 73 | static_cast(std::ceil(std::log10(max_addr + 1))) + 2; 74 | // print instruction info 75 | std::cout << std::endl; 76 | for (const auto &[is_break, addr, disasm] : info) { 77 | // print breakpoint info 78 | if (print_bp) { 79 | if (is_break) { 80 | std::cout << style("D") << " B> "; 81 | } 82 | else { 83 | std::cout << " "; 84 | } 85 | } 86 | // print current address 87 | if (addr == cur_addr) { 88 | std::cout << style("I") << std::setw(addr_padd) << std::setfill(' ') 89 | << std::right << addr << style("R") << ": "; 90 | } 91 | else { 92 | std::cout << std::setw(addr_padd) << std::setfill(' ') << std::right 93 | << addr << ": "; 94 | } 95 | // print disassembly 96 | std::cout << style("B") << disasm; 97 | std::cout << std::endl; 98 | } 99 | } 100 | 101 | } // namespace 102 | 103 | void MiniDebugger::InitDebuggerCommands() { 104 | RegisterCommand("break", "b", CMD_HANDLER(CreateBreak), "[POS]", 105 | "set breakpoint at POS", 106 | "Set a breakpoint at specific address (PC), " 107 | "POS defaults to current PC."); 108 | RegisterCommand("watch", "w", CMD_HANDLER(CreateWatch), "EXPR", 109 | "set watchpoint at EXPR", 110 | "Set a watchpoint for a specific expression, " 111 | "pause when EXPR changes.\n" 112 | " Setting watchpoints may cause MiniVM to run slowly."); 113 | RegisterCommand("delete", "d", CMD_HANDLER(DeletePoint), "[N]", 114 | "delete breakpoint/watchpoint", 115 | "Delete breakpoint/watchpoint N, delete all " 116 | "breakpoints and watchpoints by default."); 117 | RegisterCommand("continue", "c", CMD_HANDLER(Continue), "", 118 | "continue running", "Continue running current program."); 119 | RegisterCommand("next", "n", CMD_HANDLER(NextLine), "", 120 | "stepping over calls (source level)", 121 | "Source level single step, stepping over calls."); 122 | RegisterCommand("nexti", "ni", CMD_HANDLER(NextInst), "[N]", 123 | "stepping over calls (instruction level)", 124 | "Perform N instruction level single steps, " 125 | "stepping over calls. N defaults to 1."); 126 | RegisterCommand("step", "s", CMD_HANDLER(StepLine), "", 127 | "stepping into calls (source level)", 128 | "Source level single step, stepping into calls."); 129 | RegisterCommand("stepi", "si", CMD_HANDLER(StepInst), "[N]", 130 | "stepping into calls (instruction level)", 131 | "Perform N instruction level single steps, " 132 | "stepping into calls. N defaults to 1."); 133 | RegisterCommand("print", "p", CMD_HANDLER(PrintExpr), "[EXPR]", 134 | "show value of EXPR", 135 | "Show value of EXPR, or just show the last value."); 136 | RegisterCommand("x", "", CMD_HANDLER(ExamineMem), "N EXPR", 137 | "examine memory at EXPR", 138 | "Examine N units memory at address EXPR, " 139 | "4 bytes per unit."); 140 | RegisterCommand("info", "", CMD_HANDLER(PrintInfo), "ITEM", 141 | "show information of ITEM", 142 | "Show information of ITEM.\n\n" 143 | "ITEM:\n" 144 | " stack/s --- operand stack\n" 145 | " env/e --- environment stack\n" 146 | " reg/r --- static registers\n" 147 | " break/b --- breakpoints\n" 148 | " watch/w --- watchpoints"); 149 | RegisterCommand("layout", "", CMD_HANDLER(SetLayout), "FMT", 150 | "set layout of disassembler", 151 | "Set layout of disassembler, FMT can be 'src' or 'asm'."); 152 | RegisterCommand("disasm", "da", CMD_HANDLER(DisasmMem), "[N POS]", 153 | "Show source code, or disassemble VM instructions", 154 | "Disassemble N loc/instructions at POS, " 155 | "disassemble 10 loc near current PC by default."); 156 | } 157 | 158 | void MiniDebugger::RegisterDebuggerCallback() { 159 | auto ret = vm_.RegisterFunction( 160 | kVMDebugger, [this](VM &vm) { return DebuggerCallback(); }); 161 | assert(ret); 162 | static_cast(ret); 163 | } 164 | 165 | void MiniDebugger::InitSigIntHandler() { 166 | set_sigint_handler([this] { vm_.cont().ToggleTrapMode(true); }); 167 | } 168 | 169 | bool MiniDebugger::DebuggerCallback() { 170 | if (!vm_.cont().FindLineNum(vm_.pc())) { 171 | // skip when line number unavailable 172 | StepLineHandler({}); 173 | } 174 | else { 175 | // check breakpoint status 176 | CheckBreakpoints(); 177 | // show disassembly 178 | ShowDisasm(); 179 | // enter command line interface 180 | EnterCLI(); 181 | } 182 | // disable trap mode and continue 183 | vm_.cont().ToggleTrapMode(false); 184 | return true; 185 | } 186 | 187 | void MiniDebugger::LogError(std::string_view message) { 188 | std::cout << "ERROR (debugger): " << message << std::endl; 189 | } 190 | 191 | std::optional MiniDebugger::ReadPosition(std::istream &is) { 192 | // read position string 193 | std::string pos; 194 | is >> pos; 195 | if (pos[0] == ':') { 196 | // line number 197 | char *end_pos; 198 | auto line_num = std::strtol(pos.c_str() + 1, &end_pos, 10); 199 | if (*end_pos) { 200 | LogError("invalid line number"); 201 | return {}; 202 | } 203 | // get PC address by line number 204 | auto addr = vm_.cont().FindPC(line_num); 205 | if (!addr) LogError("line number out of range"); 206 | return addr; 207 | } 208 | else if (std::isdigit(pos[0])) { 209 | // PC address 210 | char *end_pos; 211 | VMAddr addr = std::strtol(pos.c_str(), &end_pos, 10); 212 | if (*end_pos) { 213 | LogError("invalid PC address"); 214 | return {}; 215 | } 216 | return addr; 217 | } 218 | else { 219 | // function or label 220 | auto addr = vm_.cont().FindPC(pos); 221 | if (!addr) LogError("function/label not found"); 222 | return addr; 223 | } 224 | } 225 | 226 | std::optional MiniDebugger::ReadExpression(std::istream &is) { 227 | return ReadExpression(is, true); 228 | } 229 | 230 | std::optional MiniDebugger::ReadExpression(std::istream &is, 231 | bool record) { 232 | // read expression from input stream 233 | std::string expr; 234 | if (!std::getline(is, expr)) { 235 | LogError("invalid 'EXPR'"); 236 | return {}; 237 | } 238 | // evaluate expression 239 | return eval_.Eval(expr, record); 240 | } 241 | 242 | bool MiniDebugger::DeleteBreak(std::uint32_t id) { 243 | // try to find breakpoint info 244 | auto it = breaks_.find(id); 245 | if (it == breaks_.end()) return false; 246 | const auto &info = it->second; 247 | // delete breakpoint 248 | vm_.cont().ToggleBreakpoint(info.addr, false); 249 | pc_bp_.erase(info.addr); 250 | breaks_.erase(it); 251 | return true; 252 | } 253 | 254 | bool MiniDebugger::DeleteWatch(std::uint32_t id) { 255 | // try to find watchpoint info 256 | auto it = watches_.find(id); 257 | if (it == watches_.end()) return false; 258 | const auto &info = it->second; 259 | // delete watchpoint 260 | eval_.RemoveRecord(info.record_id); 261 | watches_.erase(it); 262 | return true; 263 | } 264 | 265 | void MiniDebugger::CheckBreakpoints() { 266 | auto cur_pc = vm_.pc(); 267 | auto &cont = vm_.cont(); 268 | // check if breakpoint is hit 269 | if (auto it = pc_bp_.find(cur_pc); it != pc_bp_.end()) { 270 | // toggle breakpoint 271 | cont.ToggleBreakpoint(cur_pc, false); 272 | cont.AddStepCounter(1, [this, cur_pc](VMInstContainer &cont) { 273 | if (pc_bp_.count(cur_pc)) cont.ToggleBreakpoint(cur_pc, true); 274 | }); 275 | // update hit counter 276 | ++it->second->hit_count; 277 | // print PC address 278 | std::cout << "breakpoint hit, pc = " << cur_pc; 279 | // print line number 280 | auto line = cont.FindLineNum(cur_pc); 281 | assert(line); 282 | std::cout << ", at line " << *line << std::endl; 283 | } 284 | } 285 | 286 | void MiniDebugger::CheckWatchpoints() { 287 | bool break_flag = false; 288 | for (auto &&it : watches_) { 289 | // get watchpoint info 290 | auto &info = it.second; 291 | // evaluate new value 292 | auto val = eval_.Eval(info.record_id); 293 | if (val && *val != info.last_val) { 294 | break_flag = true; 295 | // record change 296 | std::cout << "watchpoint #" << it.first << " hit ($" 297 | << info.record_id << ")" << std::endl; 298 | std::cout << " old value: " << info.last_val << std::endl; 299 | std::cout << " new value: " << *val << std::endl; 300 | // update watchpoint info 301 | info.last_val = *val; 302 | ++info.hit_count; 303 | } 304 | } 305 | // break if there are any watchpoints changed 306 | if (break_flag) vm_.cont().ToggleTrapMode(true); 307 | // update step counter 308 | if (!watches_.empty()) { 309 | vm_.cont().AddStepCounter(0, [this](VMInstContainer &cont) { 310 | CheckWatchpoints(); 311 | }); 312 | } 313 | } 314 | 315 | void MiniDebugger::NextLineHandler(std::uint32_t line, std::size_t depth) { 316 | auto cur_line = vm_.cont().FindLineNum(vm_.pc()); 317 | if (!depth && cur_line != line) { 318 | // the line after the function call has been fetched 319 | vm_.cont().ToggleTrapMode(true); 320 | } 321 | else { 322 | // still in function call 323 | auto cur_inst = vm_.cont().insts() + vm_.pc(); 324 | // update depth 325 | auto new_depth = depth; 326 | switch (static_cast(cur_inst->op)) { 327 | case InstOp::Call: ++new_depth; break; 328 | case InstOp::Ret: --new_depth; break; 329 | default:; 330 | } 331 | // update step counter 332 | vm_.cont().AddStepCounter( 333 | 0, [this, line, new_depth](VMInstContainer &cont) { 334 | NextLineHandler(line, new_depth); 335 | }); 336 | } 337 | } 338 | 339 | void MiniDebugger::NextInstHandler(std::size_t n) { 340 | if (!n) { 341 | // stop execution 342 | vm_.cont().ToggleTrapMode(true); 343 | } 344 | else { 345 | // step over the current instruction 346 | auto cur_inst = vm_.cont().insts() + vm_.pc(); 347 | if (static_cast(cur_inst->op) == InstOp::Call) { 348 | // wait until instruction at 'pc + 1' has been fetched 349 | NextInstHandler(n - 1, vm_.pc() + 1, 0); 350 | } 351 | else { 352 | // just step 353 | vm_.cont().AddStepCounter(0, [this, n](VMInstContainer &cont) { 354 | NextInstHandler(n - 1); 355 | }); 356 | } 357 | } 358 | } 359 | 360 | void MiniDebugger::NextInstHandler(std::size_t n, std::size_t next_pc, 361 | std::size_t depth) { 362 | if (vm_.pc() == next_pc && !depth) { 363 | // the instruction after the 'Call' instruction has been fetched 364 | if (!n) { 365 | // stop execution 366 | vm_.cont().ToggleTrapMode(true); 367 | } 368 | else { 369 | // go for next step 370 | vm_.cont().AddStepCounter(0, [this, n](VMInstContainer &cont) { 371 | NextInstHandler(n - 1); 372 | }); 373 | } 374 | } 375 | else { 376 | // still in function call 377 | auto cur_inst = vm_.cont().insts() + vm_.pc(); 378 | // update depth 379 | auto new_depth = depth; 380 | switch (static_cast(cur_inst->op)) { 381 | case InstOp::Call: ++new_depth; break; 382 | case InstOp::Ret: --new_depth; break; 383 | default:; 384 | } 385 | // update step counter 386 | vm_.cont().AddStepCounter( 387 | 0, [this, n, next_pc, new_depth](VMInstContainer &cont) { 388 | NextInstHandler(n, next_pc, new_depth); 389 | }); 390 | } 391 | } 392 | 393 | void MiniDebugger::StepLineHandler(std::optional line) { 394 | auto cur_line = vm_.cont().FindLineNum(vm_.pc()); 395 | if (cur_line == line) { 396 | // line position not changed, continue stepping 397 | vm_.cont().AddStepCounter(0, [this, line](VMInstContainer &cont) { 398 | StepLineHandler(line); 399 | }); 400 | } 401 | else { 402 | // stop execution 403 | vm_.cont().ToggleTrapMode(true); 404 | } 405 | } 406 | 407 | void MiniDebugger::PrintStackInfo() { 408 | const auto &oprs = vm_.oprs(); 409 | std::cout << "operand stack size: " << oprs.size() << std::endl; 410 | if (!oprs.empty()) { 411 | std::cout << "top of stack: " << oprs.top() << std::endl; 412 | } 413 | } 414 | 415 | void MiniDebugger::PrintEnv(const VM::EnvPtr &env) { 416 | if (env->empty()) { 417 | std::cout << " " << std::endl; 418 | } 419 | else { 420 | for (const auto &[sym_id, val] : *env) { 421 | auto sym = vm_.sym_pool().FindSymbol(sym_id); 422 | assert(sym); 423 | std::cout << " " << *sym << " = " << val << std::endl; 424 | } 425 | } 426 | } 427 | 428 | void MiniDebugger::PrintEnvInfo() { 429 | const auto &[env, addr] = vm_.env_addr_pair(); 430 | std::cout << "return address: " << addr << std::endl; 431 | std::cout << "current environment:" << std::endl; 432 | PrintEnv(env); 433 | std::cout << "global environment:" << std::endl; 434 | PrintEnv(vm_.global_env()); 435 | } 436 | 437 | void MiniDebugger::PrintRegInfo() { 438 | // print PC 439 | std::cout << "current PC address: " << vm_.pc() << std::endl; 440 | // check if in Tigger mode 441 | const auto &env = vm_.env_addr_pair().first; 442 | auto id = vm_.sym_pool().FindId(kVMFrame); 443 | if (!id || env->find(*id) == env->end()) { 444 | return LogError("MiniVM may not currently run in Tigger mode, " 445 | "static registers should not be used."); 446 | } 447 | // print value of static registers 448 | std::cout << "static registers:" << std::endl << " "; 449 | int count = 0; 450 | for (RegId i = 0; i < TOKEN_COUNT(TOKEN_REGISTERS); ++i) { 451 | // print value of register 452 | auto val = vm_.regs(i); 453 | std::cout << std::setw(4) << std::setfill(' ') << std::left 454 | << kRegNames[i] << std::hex << std::setw(8) 455 | << std::setfill('0') << std::right << val << " "; 456 | // print new line 457 | if (count++ == 4) { 458 | count = 0; 459 | std::cout << std::endl << " "; 460 | } 461 | } 462 | if (count) std::cout << std::endl; 463 | std::cout << std::dec; 464 | } 465 | 466 | void MiniDebugger::PrintBreakInfo() { 467 | if (breaks_.empty()) { 468 | std::cout << "no breakpoints currently set" << std::endl; 469 | } 470 | else { 471 | std::cout << "number of breakpoints: " << breaks_.size() << std::endl; 472 | for (const auto &[id, info] : breaks_) { 473 | std::cout << " breakpoint #" << id << ": pc = " << info.addr 474 | << ", hit_count = " << info.hit_count << std::endl; 475 | } 476 | } 477 | } 478 | 479 | void MiniDebugger::PrintWatchInfo() { 480 | if (watches_.empty()) { 481 | std::cout << "no watchpoints currently set" << std::endl; 482 | } 483 | else { 484 | std::cout << "number of watchpoints: " << watches_.size() << std::endl; 485 | for (const auto &[id, info] : watches_) { 486 | std::cout << " watchpoint #" << id << ": $" << info.record_id 487 | << " = '"; 488 | eval_.PrintExpr(std::cout, info.record_id); 489 | std::cout << "', value = " << info.last_val 490 | << ", hit_count = " << info.hit_count << std::endl; 491 | } 492 | } 493 | } 494 | 495 | void MiniDebugger::ShowDisasm() { 496 | auto pc = vm_.pc(); 497 | if (layout_fmt_ == LayoutFormat::Asm) { 498 | pc = pc >= 2 ? pc - 2 : 0; 499 | } 500 | else { 501 | assert(layout_fmt_ == LayoutFormat::Source); 502 | // get line number of current PC 503 | auto line = vm_.cont().FindLineNum(pc); 504 | assert(line); 505 | // get start PC address 506 | auto line_start = *line >= 2 ? *line - 2 : 0; 507 | auto pc_start = vm_.cont().FindPC(line_start); 508 | if (pc_start) pc = *pc_start; 509 | } 510 | ShowDisasm(pc, 10); 511 | } 512 | 513 | void MiniDebugger::ShowDisasm(VMAddr pc, std::size_t n) { 514 | if (layout_fmt_ == LayoutFormat::Asm) { 515 | std::vector info; 516 | // add instructions to 'info' 517 | for (std::size_t i = 0; i < n; ++i) { 518 | VMAddr cur_pc = pc + i; 519 | std::ostringstream oss; 520 | if (!vm_.cont().Dump(oss, cur_pc)) break; 521 | info.push_back({!!pc_bp_.count(cur_pc), cur_pc, oss.str()}); 522 | } 523 | // print instruction info list 524 | PrintInst(info, vm_.pc()); 525 | } 526 | else { 527 | assert(layout_fmt_ == LayoutFormat::Source); 528 | std::vector info; 529 | // get start line number of the pc & current line number 530 | auto line_no = vm_.cont().FindLineNum(pc); 531 | auto cur_line_no = vm_.cont().FindLineNum(vm_.pc()); 532 | assert(line_no && cur_line_no); 533 | // add lines to 'info' 534 | for (std::size_t i = 0; i < n; ++i) { 535 | // read the current line 536 | std::uint32_t cur_no = *line_no + i; 537 | auto line = src_reader_.ReadLine(cur_no); 538 | if (!line) break; 539 | // check if there is a breakpoint 540 | auto cur_pc = vm_.cont().FindPC(cur_no); 541 | auto is_break = cur_pc && pc_bp_.count(*cur_pc); 542 | info.push_back({is_break, cur_no, *line}); 543 | } 544 | // print line info list 545 | PrintInst(info, *cur_line_no); 546 | } 547 | } 548 | 549 | bool MiniDebugger::CreateBreak(std::istream &is) { 550 | // get address of breakpoint 551 | auto addr = is.eof() ? vm_.pc() : ReadPosition(is); 552 | // check for duplicates 553 | if (pc_bp_.count(*addr)) { 554 | LogError("there is already a breakpoint at the specific POS"); 555 | return false; 556 | } 557 | // toggle breakpoint 558 | vm_.cont().ToggleBreakpoint(*addr, true); 559 | // store breakpoint info 560 | auto ret = breaks_.insert({next_id_++, {*addr, 0}}); 561 | assert(ret.second); 562 | pc_bp_.insert({*addr, &ret.first->second}); 563 | return false; 564 | } 565 | 566 | bool MiniDebugger::CreateWatch(std::istream &is) { 567 | // get record id of expression 568 | auto id = eval_.next_id(); 569 | // evaluate expression and record 570 | auto val = ReadExpression(is); 571 | if (!val) { 572 | LogError("invalid expression"); 573 | return false; 574 | } 575 | // store watchpoint info 576 | auto succ = watches_.insert({next_id_++, {id, *val, 0}}).second; 577 | assert(succ); 578 | static_cast(succ); 579 | // create step counter for watchpoint updating 580 | if (watches_.size() == 1) CheckWatchpoints(); 581 | return false; 582 | } 583 | 584 | bool MiniDebugger::DeletePoint(std::istream &is) { 585 | if (is.eof()) { 586 | // show confirm message 587 | std::cout << "are you sure to delete all " 588 | "breakpoints & watchpoints? [y/n] "; 589 | std::string line; 590 | std::cin >> line; 591 | if (!std::cin || line.size() != 1 || std::tolower(line[0]) != 'y') { 592 | return false; 593 | } 594 | // delete all breakpoints 595 | auto breaks = breaks_; 596 | for (const auto &i : breaks) DeleteBreak(i.first); 597 | // delete all watchpoints 598 | auto watches = watches_; 599 | for (const auto &i : watches) DeleteWatch(i.first); 600 | } 601 | else { 602 | // get id from input 603 | std::uint32_t n; 604 | is >> n; 605 | if (!is) { 606 | LogError("invalid breakpoint/watchpoint id"); 607 | return false; 608 | } 609 | // delete point by id 610 | if (!DeleteBreak(n) && !DeleteWatch(n)) { 611 | LogError("breakpoint/watchpoint not found"); 612 | } 613 | } 614 | return false; 615 | } 616 | 617 | bool MiniDebugger::Continue(std::istream &is) { 618 | // just quit CLI and continue 619 | return true; 620 | } 621 | 622 | bool MiniDebugger::NextLine(std::istream &is) { 623 | auto line = vm_.cont().FindLineNum(vm_.pc()); 624 | auto cur_inst = vm_.cont().insts() + vm_.pc(); 625 | if (static_cast(cur_inst->op) == InstOp::Call) { 626 | // step until reaching the next line 627 | assert(line); 628 | auto cur_line = *line; 629 | vm_.cont().AddStepCounter(0, [this, cur_line](VMInstContainer &cont) { 630 | NextLineHandler(cur_line, 0); 631 | }); 632 | } 633 | else { 634 | // just same as step into 635 | StepLineHandler(line); 636 | } 637 | return true; 638 | } 639 | 640 | bool MiniDebugger::NextInst(std::istream &is) { 641 | std::size_t n = 1; 642 | if (!is.eof()) { 643 | // n steps 644 | is >> n; 645 | if (!is || !n) { 646 | LogError("invalid step count"); 647 | return false; 648 | } 649 | } 650 | // add step counter 651 | vm_.cont().AddStepCounter(0, [this, n](VMInstContainer &cont) { 652 | NextInstHandler(n); 653 | }); 654 | return true; 655 | } 656 | 657 | bool MiniDebugger::StepLine(std::istream &is) { 658 | auto line = vm_.cont().FindLineNum(vm_.pc()); 659 | StepLineHandler(line); 660 | return true; 661 | } 662 | 663 | bool MiniDebugger::StepInst(std::istream &is) { 664 | std::size_t n = 1; 665 | if (!is.eof()) { 666 | // n steps 667 | is >> n; 668 | if (!is || !n) { 669 | LogError("invalid step count"); 670 | return false; 671 | } 672 | } 673 | // enable step mode 674 | vm_.cont().AddStepCounter(n); 675 | return true; 676 | } 677 | 678 | bool MiniDebugger::PrintExpr(std::istream &is) { 679 | std::optional value; 680 | std::uint32_t id; 681 | // get expression 682 | if (is.eof()) { 683 | // show last value 684 | id = eval_.next_id(); 685 | while (!value) { 686 | if (!id) { 687 | LogError("there is no last value available"); 688 | return false; 689 | } 690 | value = eval_.Eval(--id); 691 | } 692 | } 693 | else { 694 | // evaluate expression 695 | id = eval_.next_id(); 696 | if (!(value = ReadExpression(is))) { 697 | LogError("invalid expression"); 698 | return false; 699 | } 700 | } 701 | // print result 702 | std::cout << "$" << id << " = " << *value << std::endl; 703 | return false; 704 | } 705 | 706 | bool MiniDebugger::ExamineMem(std::istream &is) { 707 | // get number N 708 | std::uint32_t n; 709 | is >> n; 710 | if (!is || !n) { 711 | LogError("invalid number N"); 712 | return false; 713 | } 714 | // get expression 715 | auto val = ReadExpression(is, false); 716 | if (!val) { 717 | LogError("invalid expression"); 718 | return false; 719 | } 720 | // print memory units 721 | auto addr = *val; 722 | while (n--) { 723 | // print address 724 | std::cout << std::hex << std::setfill('0') << std::setw(8) << addr; 725 | // get pointer of current unit 726 | auto ptr = reinterpret_cast(vm_.mem_pool()->GetAddress(addr)); 727 | if (!ptr) { 728 | std::cout << std::dec << std::endl; 729 | LogError("invalid memory address"); 730 | break; 731 | } 732 | addr += 4; 733 | // print contents 734 | std::cout << ": " << std::setw(2) << std::setfill('0') 735 | << (static_cast(*ptr++) & 0xff) << ' '; 736 | std::cout << std::setw(2) << std::setfill('0') 737 | << (static_cast(*ptr++) & 0xff) << ' '; 738 | std::cout << std::setw(2) << std::setfill('0') 739 | << (static_cast(*ptr++) & 0xff) << ' '; 740 | std::cout << std::setw(2) << std::setfill('0') 741 | << (static_cast(*ptr++) & 0xff); 742 | std::cout << std::dec << std::endl; 743 | } 744 | return false; 745 | } 746 | 747 | bool MiniDebugger::PrintInfo(std::istream &is) { 748 | // get items 749 | std::string item_str; 750 | is >> item_str; 751 | auto it = kInfoItems.find(item_str); 752 | if (it == kInfoItems.end()) { 753 | LogError("invalid 'ITEM'"); 754 | return false; 755 | } 756 | // print information 757 | switch (it->second) { 758 | case InfoItem::Stack: PrintStackInfo(); break; 759 | case InfoItem::Env: PrintEnvInfo(); break; 760 | case InfoItem::Reg: PrintRegInfo(); break; 761 | case InfoItem::Break: PrintBreakInfo(); break; 762 | case InfoItem::Watch: PrintWatchInfo(); break; 763 | default: assert(false); 764 | } 765 | return false; 766 | } 767 | 768 | bool MiniDebugger::SetLayout(std::istream &is) { 769 | // get format 770 | std::string fmt; 771 | is >> fmt; 772 | // parse format 773 | if (fmt == "src") { 774 | layout_fmt_ = LayoutFormat::Source; 775 | } 776 | else if (fmt == "asm") { 777 | layout_fmt_ = LayoutFormat::Asm; 778 | } 779 | else { 780 | LogError("invalid layout format"); 781 | } 782 | return false; 783 | } 784 | 785 | bool MiniDebugger::DisasmMem(std::istream &is) { 786 | // no parameters 787 | if (is.eof()) { 788 | ShowDisasm(); 789 | } 790 | else { 791 | // get parameters 792 | std::size_t n; 793 | is >> n; 794 | if (!is || !n) { 795 | LogError("invalid count 'N'"); 796 | return false; 797 | } 798 | auto pos = ReadPosition(is); 799 | if (!pos) { 800 | LogError("invalid 'POS'"); 801 | return false; 802 | } 803 | // show disassembly 804 | ShowDisasm(*pos, n); 805 | } 806 | return false; 807 | } 808 | --------------------------------------------------------------------------------