├── .gitignore ├── log.h ├── log.cpp ├── .clang-format ├── dis.h ├── dis ├── memory-region.cpp ├── memory-region.h ├── dcheck.c ├── disassembler.h ├── assembler-utils.cpp ├── disassembler.cpp ├── vector.h ├── utils.h ├── dcheck.h ├── globals.h ├── assembler-utils.h ├── assembler-x64.cpp └── assembler-x64.h ├── Makefile ├── post.md └── interp.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.o 3 | *.so 4 | /interp 5 | -------------------------------------------------------------------------------- /log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define LOG(...) \ 4 | do { \ 5 | emitLog(__FILE__, __LINE__, __func__, __VA_ARGS__); \ 6 | } while (0) 7 | 8 | void emitLog(const char* file, int line, const char* func, ...); 9 | -------------------------------------------------------------------------------- /log.cpp: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void emitLog(const char* file, int line, const char* func, ...) { 9 | // fprintf(stderr, "%s:%d %s: ", file, line, func); 10 | va_list args; 11 | va_start(args, func); 12 | const char* fmt = va_arg(args, const char*); 13 | vfprintf(stderr, fmt, args); 14 | va_end(args); 15 | fputc('\n', stderr); 16 | } 17 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | AlignEscapedNewlinesLeft: false 3 | DerivePointerAlignment: false 4 | PointerAlignment: Left 5 | IncludeBlocks: Regroup 6 | IncludeCategories: 7 | # C system headers 8 | - Regex: '^<.*\.h?>' 9 | Priority: 1 10 | # C++ standard library headers 11 | - Regex: '^<.*' 12 | Priority: 2 13 | # Project headers 14 | - Regex: '.*' 15 | Priority: 4 16 | IncludeIsMainRegex: "(-linux)?(-darwin)?(-x64)?(-test)?$" 17 | -------------------------------------------------------------------------------- /dis.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "dis/disassembler.h" 6 | #include "dis/assembler-x64.h" 7 | 8 | static const int kMaxDisassemblySize = 4096; 9 | 10 | std::string disassembleToString(byte* code, uword length) { 11 | // Some padding in case it's longer than expected. 12 | char buffer[kMaxDisassemblySize]; 13 | std::memset(buffer, 0, sizeof buffer); 14 | dis::DisassembleToMemory formatter(buffer, sizeof buffer); 15 | dis::Disassembler::disassemble( 16 | reinterpret_cast(code), 17 | reinterpret_cast(code) + length, &formatter); 18 | return buffer; 19 | } 20 | -------------------------------------------------------------------------------- /dis/memory-region.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) 2 | // Copyright (c) 2013, the Dart project authors and Facebook, Inc. and its 3 | // affiliates. Please see the AUTHORS-Dart file for details. All rights 4 | // reserved. Use of this source code is governed by a BSD-style license that 5 | // can be found in the LICENSE-Dart file. 6 | 7 | #include "memory-region.h" 8 | 9 | #include 10 | 11 | #include "dcheck.h" 12 | 13 | void MemoryRegion::copyFrom(uword offset, MemoryRegion from) const { 14 | DCHECK(size() >= from.size(), "source cannot be larger than destination"); 15 | DCHECK(offset <= (size() - from.size()), "offset is too large"); 16 | std::memmove(reinterpret_cast(start() + offset), from.pointer(), 17 | from.size()); 18 | } 19 | -------------------------------------------------------------------------------- /dis/memory-region.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) */ 2 | // Copyright (c) 2013, the Dart project authors and Facebook, Inc. and its 3 | // affiliates. Please see the AUTHORS-Dart file for details. All rights 4 | // reserved. Use of this source code is governed by a BSD-style license that 5 | // can be found in the LICENSE-Dart file. 6 | 7 | #pragma once 8 | 9 | #include "globals.h" 10 | 11 | class MemoryRegion { 12 | public: 13 | MemoryRegion(void* pointer, word size) : pointer_(pointer), size_(size) {} 14 | 15 | void* pointer() const { return pointer_; } 16 | uword size() const { return size_; } 17 | 18 | void copyFrom(uword offset, MemoryRegion from) const; 19 | 20 | private: 21 | uword start() const { return reinterpret_cast(pointer_); } 22 | uword end() const { return start() + size_; } 23 | 24 | void* pointer_; 25 | uword size_; 26 | }; 27 | -------------------------------------------------------------------------------- /dis/dcheck.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) 2 | #include "dcheck.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | void checkFailed(const char *file, int line, const char *func, const char *expr, 10 | ...) { 11 | fprintf(stderr, "%s:%d %s: %s: ", file, line, func, expr); 12 | va_list args; 13 | va_start(args, expr); 14 | const char *fmt = va_arg(args, const char *); 15 | vfprintf(stderr, fmt, args); 16 | va_end(args); 17 | fputc('\n', stderr); 18 | abort(); 19 | } 20 | 21 | void checkIndexFailed(const char *file, int line, const char *func, intptr_t index, 22 | intptr_t high) { 23 | fprintf(stderr, "%s:%d %s: index out of range, %ld not in [0..%ld) : \n", 24 | file, line, func, index, high); 25 | abort(); 26 | } 27 | 28 | void checkBoundFailed(const char *file, int line, const char *func, intptr_t value, 29 | intptr_t low, intptr_t high) { 30 | fprintf(stderr, "%s:%d %s: bounds violation, %ld not in [%ld..%ld] : \n", 31 | file, line, func, value, low, high); 32 | abort(); 33 | } 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXX=clang++ 2 | CXXFLAGS+=-std=c++11 3 | 4 | # If on an M1, M2, or other new Apple Silicon, you have to both compile and run 5 | # using the "arch" tool so that both the final binary and the JIT-compiled code 6 | # can run in x86_64 mode. 7 | UNAME_S:=$(shell uname -s) 8 | UNAME_M:=$(shell uname -m) 9 | ifeq ($(UNAME_S),Darwin) 10 | ifeq ($(UNAME_M),arm64) 11 | COMPILEPREFIX=arch -x86_64 12 | endif 13 | endif 14 | 15 | ifneq ($(DEBUG),) 16 | CFLAGS+=-g -O0 -gdwarf-4 17 | CXXFLAGS+=-g -O0 -gdwarf-4 18 | endif 19 | 20 | ifneq ($(UBSAN),) 21 | CFLAGS+=-fsanitize=undefined 22 | CXXFLAGS+=-fsanitize=undefined 23 | endif 24 | 25 | ifneq ($(ASAN),) 26 | CFLAGS+=-fsanitize=address 27 | CXXFLAGS+=-fsanitize=address 28 | endif 29 | 30 | all: interp 31 | 32 | # TODO(max): Figure out how to list headers here without including them in $^ 33 | interp: interp.o dis.so 34 | $(COMPILEPREFIX) $(CXX) $(CXXFLAGS) -Wl,-rpath . interp.o dis.so -o $@ $(LDFLAGS) 35 | 36 | interp.o: interp.cpp *.h dis/*.h 37 | $(COMPILEPREFIX) $(CXX) $(CXXFLAGS) interp.cpp -c -o $@ 38 | 39 | # TODO(max): Figure out how to list headers here without including them in $^ 40 | dis.so: dis/disassembler.cpp dis/disassembler-x64.cpp dis/memory-region.cpp \ 41 | dis/assembler-utils.cpp dis/assembler-x64.cpp \ 42 | dis/dcheck.o *.h dis/*.h 43 | $(COMPILEPREFIX) $(CXX) $(CXXFLAGS) -fPIC -shared dis/*.cpp dis/dcheck.o -o $@ $(LDFLAGS) 44 | 45 | dis/dcheck.o: dis/dcheck.h dis/dcheck.c 46 | $(COMPILEPREFIX) $(CC) $(CFLAGS) -fPIC -c dis/dcheck.c -o $@ 47 | 48 | .PHONY: test 49 | test: interp 50 | $(COMPILEPREFIX) ./interp 51 | 52 | .PHONY: clean 53 | clean: 54 | find . -name '*.o' -delete 55 | find . -name '*.so' -delete 56 | -------------------------------------------------------------------------------- /dis/disassembler.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) */ 2 | // Copyright (c) 2013, the Dart project authors and Facebook, Inc. and its 3 | // affiliates. Please see the AUTHORS-Dart file for details. All rights 4 | // reserved. Use of this source code is governed by a BSD-style license that 5 | // can be found in the LICENSE-Dart file. 6 | 7 | #pragma once 8 | 9 | #include "globals.h" 10 | 11 | namespace dis { 12 | 13 | // Forward declaration. 14 | class CodeComments; 15 | 16 | // Disassembly formatter interface, which consumes the 17 | // disassembled instructions in any desired form. 18 | class DisassemblyFormatter { 19 | public: 20 | DisassemblyFormatter() {} 21 | virtual ~DisassemblyFormatter() {} 22 | 23 | // Consume the decoded instruction at the given pc. 24 | virtual void consumeInstruction(char* hex_buffer, intptr_t hex_size, 25 | char* human_buffer, intptr_t human_size, 26 | uword pc) = 0; 27 | 28 | // print a formatted message. 29 | virtual void print(const char* format, ...) = 0; 30 | }; 31 | 32 | // Basic disassembly formatter that outputs the disassembled instruction 33 | // to stdout. 34 | class DisassembleToStdout : public DisassemblyFormatter { 35 | public: 36 | DisassembleToStdout() : DisassemblyFormatter() {} 37 | ~DisassembleToStdout() {} 38 | 39 | virtual void consumeInstruction(char* hex_buffer, intptr_t hex_size, 40 | char* human_buffer, intptr_t human_size, 41 | uword pc); 42 | 43 | virtual void print(const char* format, ...); 44 | 45 | private: 46 | DISALLOW_HEAP_ALLOCATION(); 47 | DISALLOW_COPY_AND_ASSIGN(DisassembleToStdout); 48 | }; 49 | 50 | // Basic disassembly formatter that outputs the disassembled instruction 51 | // to a memory buffer. This is only intended for test writing. 52 | class DisassembleToMemory : public DisassemblyFormatter { 53 | public: 54 | DisassembleToMemory(char* buffer, uintptr_t length) 55 | : DisassemblyFormatter(), 56 | buffer_(buffer), 57 | remaining_(length), 58 | overflowed_(false) {} 59 | ~DisassembleToMemory() {} 60 | 61 | virtual void consumeInstruction(char* hex_buffer, intptr_t hex_size, 62 | char* human_buffer, intptr_t human_size, 63 | uword pc); 64 | 65 | virtual void print(const char* format, ...); 66 | 67 | private: 68 | char* buffer_; 69 | int remaining_; 70 | bool overflowed_; 71 | DISALLOW_HEAP_ALLOCATION(); 72 | DISALLOW_COPY_AND_ASSIGN(DisassembleToMemory); 73 | }; 74 | 75 | // Disassemble instructions. 76 | class Disassembler { 77 | public: 78 | // disassemble instructions between start and end. 79 | // (The assumption is that start is at a valid instruction). 80 | // Return true if all instructions were successfully decoded, false otherwise. 81 | static void disassemble(uword start, uword end, 82 | DisassemblyFormatter* formatter, 83 | const CodeComments* comments = nullptr); 84 | 85 | static void disassemble(uword start, uword end) { 86 | DisassembleToStdout stdout_formatter; 87 | disassemble(start, end, &stdout_formatter); 88 | } 89 | 90 | static void disassemble(uword start, uword end, char* buffer, 91 | uintptr_t buffer_size) { 92 | DisassembleToMemory memory_formatter(buffer, buffer_size); 93 | disassemble(start, end, &memory_formatter); 94 | } 95 | 96 | // Decodes one instruction. 97 | // Writes a hexadecimal representation into the hex_buffer and a 98 | // human-readable representation into the human_buffer. 99 | // Writes the length of the decoded instruction in bytes in out_instr_len. 100 | static void decodeInstruction(char* hex_buffer, intptr_t hex_size, 101 | char* human_buffer, intptr_t human_size, 102 | int* out_instr_len, uword pc); 103 | 104 | private: 105 | static const int kHexadecimalBufferSize = 32; 106 | static const int kUserReadableBufferSize = 256; 107 | }; 108 | 109 | } 110 | -------------------------------------------------------------------------------- /dis/assembler-utils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) 2 | // Copyright (c) 2013, the Dart project authors and Facebook, Inc. and its 3 | // affiliates. Please see the AUTHORS-Dart file for details. All rights 4 | // reserved. Use of this source code is governed by a BSD-style license that 5 | // can be found in the LICENSE-Dart file. 6 | 7 | #include "assembler-utils.h" 8 | 9 | #include 10 | 11 | #include "assembler-x64.h" 12 | #include "memory-region.h" 13 | #include "utils.h" 14 | 15 | namespace dis { 16 | 17 | #ifndef NDEBUG 18 | AssemblerBuffer::EnsureCapacity::EnsureCapacity(AssemblerBuffer* buffer) { 19 | if (buffer->cursor() >= buffer->limit()) buffer->extendCapacity(); 20 | // In debug mode, we save the assembler buffer along with the gap 21 | // size before we start emitting to the buffer. This allows us to 22 | // check that any single generated instruction doesn't overflow the 23 | // limit implied by the minimum gap size. 24 | buffer_ = buffer; 25 | gap_ = computeGap(); 26 | // Make sure that extending the capacity leaves a big enough gap 27 | // for any kind of instruction. 28 | DCHECK(gap_ >= kMinimumGap, "assert()"); 29 | // Mark the buffer as having ensured the capacity. 30 | DCHECK(!buffer->hasEnsuredCapacity(), "assert()"); // Cannot nest. 31 | buffer->has_ensured_capacity_ = true; 32 | } 33 | 34 | AssemblerBuffer::EnsureCapacity::~EnsureCapacity() { 35 | // Unmark the buffer, so we cannot emit after this. 36 | buffer_->has_ensured_capacity_ = false; 37 | // Make sure the generated instruction doesn't take up more 38 | // space than the minimum gap. 39 | word delta = gap_ - computeGap(); 40 | DCHECK(delta <= kMinimumGap, "assert()"); 41 | } 42 | #endif 43 | 44 | AssemblerBuffer::AssemblerBuffer() { 45 | static const word initial_buffer_capacity = 4 * kKiB; 46 | contents_ = reinterpret_cast(std::malloc(initial_buffer_capacity)); 47 | cursor_ = contents_; 48 | limit_ = computeLimit(contents_, initial_buffer_capacity); 49 | fixup_ = nullptr; 50 | #ifndef NDEBUG 51 | has_ensured_capacity_ = false; 52 | fixups_processed_ = false; 53 | #endif 54 | 55 | // Verify internal state. 56 | DCHECK(capacity() == initial_buffer_capacity, "assert()"); 57 | DCHECK(size() == 0, "assert()"); 58 | } 59 | 60 | AssemblerBuffer::~AssemblerBuffer() { 61 | std::free(reinterpret_cast(contents_)); 62 | cursor_ = 0; 63 | } 64 | 65 | void AssemblerBuffer::processFixups(MemoryRegion region) { 66 | AssemblerFixup* fixup = fixup_; 67 | while (fixup != nullptr) { 68 | fixup->process(region, fixup->position()); 69 | fixup = fixup->previous(); 70 | } 71 | } 72 | 73 | void AssemblerBuffer::finalizeInstructions(MemoryRegion instructions) { 74 | // Copy the instructions from the buffer. 75 | MemoryRegion from(reinterpret_cast(contents()), size()); 76 | instructions.copyFrom(0, from); 77 | // Process fixups in the instructions. 78 | processFixups(instructions); 79 | #ifndef NDEBUG 80 | fixups_processed_ = true; 81 | #endif 82 | } 83 | 84 | void AssemblerBuffer::extendCapacity() { 85 | word old_size = size(); 86 | word old_capacity = capacity(); 87 | word new_capacity = Utils::minimum(old_capacity * 2, old_capacity + 1 * kMiB); 88 | if (new_capacity < old_capacity) { 89 | UNREACHABLE("Unexpected overflow in AssemblerBuffer::ExtendCapacity"); 90 | } 91 | 92 | // Allocate the new data area and copy contents of the old one to it. 93 | uword new_contents = reinterpret_cast(std::malloc(new_capacity)); 94 | memmove(reinterpret_cast(new_contents), 95 | reinterpret_cast(contents_), old_size); 96 | 97 | // Compute the relocation delta and switch to the new contents area. 98 | word delta = new_contents - contents_; 99 | std::free(reinterpret_cast(contents_)); 100 | contents_ = new_contents; 101 | 102 | // Update the cursor and recompute the limit. 103 | cursor_ += delta; 104 | limit_ = computeLimit(new_contents, new_capacity); 105 | 106 | // Verify internal state. 107 | DCHECK(capacity() == new_capacity, "assert()"); 108 | DCHECK(size() == old_size, "assert()"); 109 | } 110 | 111 | } // namespace dis 112 | -------------------------------------------------------------------------------- /dis/disassembler.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) 2 | // Copyright (c) 2013, the Dart project authors and Facebook, Inc. and its 3 | // affiliates. Please see the AUTHORS-Dart file for details. All rights 4 | // reserved. Use of this source code is governed by a BSD-style license that 5 | // can be found in the LICENSE-Dart file. 6 | 7 | #include "disassembler.h" 8 | 9 | #include 10 | #include 11 | 12 | #include "assembler-utils.h" 13 | 14 | namespace dis { 15 | 16 | static const int kHexColumnWidth = 23; 17 | 18 | void DisassembleToStdout::consumeInstruction(char* hex_buffer, 19 | intptr_t hex_size, 20 | char* human_buffer, 21 | intptr_t human_size, uword pc) { 22 | (void)hex_size; 23 | (void)human_size; 24 | std::fprintf(stdout, "0x%" PRIX64 " %s", static_cast(pc), 25 | hex_buffer); 26 | int hex_length = strlen(hex_buffer); 27 | if (hex_length < kHexColumnWidth) { 28 | for (int i = kHexColumnWidth - hex_length; i > 0; i--) { 29 | std::fprintf(stdout, " "); 30 | } 31 | } 32 | std::fprintf(stdout, "%s", human_buffer); 33 | std::fprintf(stdout, "\n"); 34 | } 35 | 36 | void DisassembleToStdout::print(const char* format, ...) { 37 | va_list args; 38 | va_start(args, format); 39 | std::vprintf(format, args); 40 | va_end(args); 41 | } 42 | 43 | void DisassembleToMemory::consumeInstruction(char* hex_buffer, 44 | intptr_t hex_size, 45 | char* human_buffer, 46 | intptr_t human_size, uword pc) { 47 | (void)human_size; 48 | (void)hex_buffer; 49 | (void)hex_size; 50 | (void)pc; 51 | if (overflowed_) { 52 | return; 53 | } 54 | intptr_t len = strlen(human_buffer); 55 | if (remaining_ < len + 100) { 56 | *buffer_++ = '.'; 57 | *buffer_++ = '.'; 58 | *buffer_++ = '.'; 59 | *buffer_++ = '\n'; 60 | *buffer_++ = '\0'; 61 | overflowed_ = true; 62 | return; 63 | } 64 | memmove(buffer_, human_buffer, len); 65 | buffer_ += len; 66 | remaining_ -= len; 67 | *buffer_++ = '\n'; 68 | remaining_--; 69 | *buffer_ = '\0'; 70 | } 71 | 72 | void DisassembleToMemory::print(const char* format, ...) { 73 | if (overflowed_) { 74 | return; 75 | } 76 | va_list args; 77 | va_start(args, format); 78 | intptr_t len = std::vsnprintf(nullptr, 0, format, args); 79 | va_end(args); 80 | if (remaining_ < len + 100) { 81 | *buffer_++ = '.'; 82 | *buffer_++ = '.'; 83 | *buffer_++ = '.'; 84 | *buffer_++ = '\n'; 85 | *buffer_++ = '\0'; 86 | overflowed_ = true; 87 | return; 88 | } 89 | va_start(args, format); 90 | intptr_t len2 = std::vsnprintf(buffer_, len, format, args); 91 | va_end(args); 92 | DCHECK(len == len2, ""); 93 | buffer_ += len; 94 | remaining_ -= len; 95 | *buffer_++ = '\n'; 96 | remaining_--; 97 | *buffer_ = '\0'; 98 | } 99 | 100 | void Disassembler::disassemble(uword start, uword end, 101 | DisassemblyFormatter* formatter, 102 | const CodeComments* comments) { 103 | DCHECK(formatter != nullptr, ""); 104 | char hex_buffer[kHexadecimalBufferSize]; // Instruction in hexadecimal form. 105 | char human_buffer[kUserReadableBufferSize]; // Human-readable instruction. 106 | uword pc = start; 107 | intptr_t comment_finger = 0; 108 | while (pc < end) { 109 | const intptr_t offset = pc - start; 110 | while (comments && comment_finger < comments->length() && 111 | comments->offsetAt(comment_finger) <= offset) { 112 | formatter->print(" ;; %s\n", comments->commentAt(comment_finger)); 113 | comment_finger++; 114 | } 115 | int instruction_length; 116 | decodeInstruction(hex_buffer, sizeof(hex_buffer), human_buffer, 117 | sizeof(human_buffer), &instruction_length, pc); 118 | // TODO(emacs): Add flag to disassemble relative? Then use offset instead 119 | // of pc. 120 | formatter->consumeInstruction(hex_buffer, sizeof(hex_buffer), human_buffer, 121 | sizeof(human_buffer), pc); 122 | pc += instruction_length; 123 | } 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /dis/vector.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) */ 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "globals.h" 12 | #include "utils.h" 13 | 14 | // A partial clone of std::vector<>. Only supports POD types. Based on llvm's 15 | // SmallVector, but simpler. 16 | // 17 | // Use `const Vector&` in functions that wish to accept a vector reference. 18 | template 19 | class Vector { 20 | // We can add support for non-POD types but its a lot harder to get right, so 21 | // we'll start with the simple thing. 22 | static_assert(IS_TRIVIALLY_COPYABLE(T), "Vector only supports POD types"); 23 | 24 | public: 25 | using size_type = word; 26 | using iterator = T*; 27 | using const_iterator = const T*; 28 | 29 | Vector() = default; 30 | 31 | ~Vector() { release(); } 32 | 33 | Vector(const Vector& other) { *this = other; } 34 | 35 | Vector& operator=(const Vector& other) { 36 | clear(); 37 | auto const s = other.size(); 38 | reserve(s); 39 | end_ = begin_ + s; 40 | DCHECK(end_ <= end_storage_, "vector assignment overflow"); 41 | std::memcpy(begin_, other.begin_, s * sizeof(T)); 42 | return *this; 43 | } 44 | 45 | Vector(Vector&& other) { *this = std::move(other); } 46 | 47 | Vector& operator=(Vector&& other) { 48 | // If the other vector's storage is heap allocated, the simplest possible 49 | // thing is to just take ownership of its storage. Even if the current 50 | // vector is small, and has sufficient capacity, the malloc has already 51 | // been paid for. 52 | release(); 53 | std::swap(begin_, other.begin_); 54 | std::swap(end_, other.end_); 55 | std::swap(end_storage_, other.end_storage_); 56 | return *this; 57 | } 58 | 59 | iterator begin() { return begin_; } 60 | const_iterator begin() const { return begin_; } 61 | iterator end() { return end_; } 62 | const_iterator end() const { return end_; } 63 | 64 | T& operator[](size_type idx) { 65 | DCHECK_INDEX(idx, size()); 66 | return begin()[idx]; 67 | } 68 | const T& operator[](size_type idx) const { 69 | DCHECK_INDEX(idx, size()); 70 | return begin()[idx]; 71 | } 72 | 73 | T& front() { 74 | DCHECK(!empty(), "front on empty"); 75 | return begin()[0]; 76 | } 77 | const T& front() const { 78 | DCHECK(!empty(), "front on empty"); 79 | return begin()[0]; 80 | } 81 | 82 | T& back() { 83 | DCHECK(!empty(), "back on empty"); 84 | return end()[-1]; 85 | } 86 | const T& back() const { 87 | DCHECK(!empty(), "back on empty"); 88 | return end()[-1]; 89 | } 90 | 91 | size_type size() const { return end_ - begin_; } 92 | 93 | bool empty() const { return size() == 0; } 94 | 95 | size_type capacity() const { return end_storage_ - begin_; } 96 | 97 | void reserve(size_type new_capacity) { 98 | DCHECK(new_capacity > 0, "invalid new_capacity %ld", new_capacity); 99 | if (new_capacity <= 0) { 100 | return; 101 | } 102 | grow(new_capacity); 103 | } 104 | 105 | void clear() { end_ = begin_; } 106 | 107 | void push_back(const T& t) { // NOLINT 108 | if (end_ >= end_storage_) { 109 | grow(0); 110 | } 111 | *end_ = t; 112 | end_++; 113 | } 114 | 115 | void pop_back() { // NOLINT 116 | DCHECK(!empty(), "pop back on empty"); 117 | end_--; 118 | } 119 | 120 | void release() { 121 | std::free(begin_); 122 | begin_ = nullptr; 123 | end_ = nullptr; 124 | end_storage_ = nullptr; 125 | } 126 | 127 | private: 128 | static constexpr int kGrowthFactor = 2; 129 | 130 | void grow(size_type new_cap) { 131 | auto const old_cap = capacity(); 132 | auto const old_size = size(); 133 | 134 | if (new_cap == 0) { 135 | if (old_cap == 0) { 136 | new_cap = 4; 137 | } else { 138 | new_cap = old_cap * kGrowthFactor; 139 | } 140 | } 141 | 142 | if (old_cap >= new_cap) { 143 | return; 144 | } 145 | 146 | auto old_begin = begin_; 147 | 148 | begin_ = static_cast(std::malloc(new_cap * sizeof(T))); 149 | DCHECK(begin_ != nullptr, "out of memory"); 150 | end_storage_ = begin_ + new_cap; 151 | end_ = begin_ + old_size; 152 | 153 | if (old_begin != nullptr) { 154 | std::memcpy(begin_, old_begin, old_size * sizeof(T)); 155 | std::free(old_begin); 156 | } 157 | } 158 | 159 | T* begin_ = nullptr; 160 | T* end_storage_ = nullptr; 161 | T* end_ = nullptr; 162 | }; 163 | -------------------------------------------------------------------------------- /dis/utils.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) */ 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "dcheck.h" 10 | #include "globals.h" 11 | 12 | class Utils { 13 | public: 14 | template 15 | static bool isAligned(T x, int n) { 16 | DCHECK(isPowerOfTwo(n), "must be power of 2"); 17 | return (x & (n - 1)) == 0; 18 | } 19 | 20 | template 21 | static bool isPowerOfTwo(T x) { 22 | return (x & (x - 1)) == 0; 23 | } 24 | 25 | template 26 | static bool fits(word value) { 27 | return static_cast(static_cast(value)) == value; 28 | } 29 | 30 | template 31 | static T roundDown(T x, int n) { 32 | DCHECK(isPowerOfTwo(n), "must be power of 2"); 33 | return (x & -n); 34 | } 35 | 36 | template 37 | static T roundUp(T x, int n) { 38 | return roundDown(x + n - 1, n); 39 | } 40 | 41 | template 42 | static T roundUpDiv(T denominator, int divisor) { 43 | if (isPowerOfTwo(divisor)) { 44 | return roundUp(denominator, divisor) >> (highestBit(divisor) - 1); 45 | } 46 | return (denominator + (divisor - 1)) / divisor; 47 | } 48 | 49 | template 50 | static T nextPowerOfTwo(T x) { 51 | // Turn off all but msb. 52 | while ((x & (x - 1u)) != 0) { 53 | x &= x - 1u; 54 | } 55 | return x << 1u; 56 | } 57 | 58 | template 59 | static T rotateLeft(T x, int n) { 60 | return (x << n) | (x >> (-n & (sizeof(T) * kBitsPerByte - 1))); 61 | } 62 | 63 | template 64 | static T maximum(T x, T y) { 65 | return x > y ? x : y; 66 | } 67 | 68 | // Search forwards through `haystack` looking for `needle`. Return the byte 69 | // offset, or -1 if not found. 70 | static word memoryFind(const byte* haystack, word haystack_len, 71 | const byte* needle, word needle_len); 72 | 73 | // Search forwards through `haystack` looking for `needle`. Return the byte 74 | // offset, or -1 if not found. 75 | static word memoryFindChar(const byte* haystack, word haystack_len, 76 | byte needle); 77 | 78 | // Search backwards through `haystack` looking for `needle`. Return the byte 79 | // offset, or -1 if not found. 80 | static word memoryFindCharReverse(const byte* haystack, word haystack_len, 81 | byte needle); 82 | 83 | // Search backwards through `haystack` looking for `needle`. Return the byte 84 | // offset, or -1 if not found. 85 | static word memoryFindReverse(const byte* haystack, word haystack_len, 86 | const byte* needle, word needle_len); 87 | 88 | template 89 | static T minimum(T x, T y) { 90 | return x < y ? x : y; 91 | } 92 | 93 | static int highestBit(word x) { 94 | return x == 0 ? 0 : kBitsPerWord - __builtin_clzl(x); 95 | } 96 | 97 | // Returns the number of leading redundant sign bits. 98 | // This is equivalent to gccs __builtin_clrsbl but works for any compiler. 99 | static int numRedundantSignBits(word x) { 100 | #if __has_builtin(__builtin_clrsbl) || \ 101 | (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) 102 | static_assert(sizeof(x) == sizeof(long), 103 | "Need to choose matching clrsbX builtin"); 104 | return __builtin_clrsbl(x); 105 | #else 106 | if (x < 0) x = ~x; 107 | if (x == 0) return sizeof(x) * kBitsPerByte - 1; 108 | return __builtin_clzl(x) - 1; 109 | #endif 110 | } 111 | 112 | template 113 | static T readBytes(void* addr) { 114 | T dest; 115 | std::memcpy(&dest, addr, sizeof(dest)); 116 | return dest; 117 | } 118 | 119 | // Print the current traceback, information about the pending exception, if 120 | // one is set, and call std::abort(). 121 | [[noreturn]] static void printDebugInfoAndAbort(); 122 | 123 | private: 124 | DISALLOW_IMPLICIT_CONSTRUCTORS(Utils); 125 | }; 126 | 127 | // Convenience wrappers to enable templates based on signed or unsigned integral 128 | // types. 129 | template 130 | using if_signed_t = typename std::enable_if::value, R>::type; 131 | template 132 | using if_unsigned_t = 133 | typename std::enable_if::value, R>::type; 134 | 135 | // std::unique_ptr for objects created with std::malloc() rather than new. 136 | struct FreeDeleter { 137 | void operator()(void* ptr) const { std::free(ptr); } 138 | }; 139 | template 140 | using unique_c_ptr = std::unique_ptr; 141 | -------------------------------------------------------------------------------- /dis/dcheck.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) 2 | #pragma once 3 | 4 | #include 5 | 6 | // Branch prediction hints for the compiler. Use in performance critial code 7 | // which almost always branches one way. 8 | #define LIKELY(x) __builtin_expect(!!(x), 1) 9 | #define UNLIKELY(x) __builtin_expect(!!(x), 0) 10 | 11 | #if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) 12 | #define DCHECK_IS_ON() 0 13 | #else 14 | #define DCHECK_IS_ON() 1 15 | #endif 16 | 17 | #define CHECK(expr, ...) \ 18 | do { \ 19 | if (UNLIKELY(!(expr))) { \ 20 | checkFailed(__FILE__, __LINE__, __func__, "check '" #expr "' failed", \ 21 | __VA_ARGS__); \ 22 | } \ 23 | } while (0) 24 | 25 | #define CHECK_BOUND(val, high) CHECK_RANGE(val, 0, high) 26 | 27 | #define CHECK_INDEX(index, high) \ 28 | do { \ 29 | if (UNLIKELY(!((index >= 0) && (index < high)))) { \ 30 | checkIndexFailed(__FILE__, __LINE__, __func__, (intptr_t)(index), \ 31 | (intptr_t)(high)); \ 32 | } \ 33 | } while (0) 34 | 35 | #define CHECK_RANGE(val, low, high) \ 36 | do { \ 37 | if (UNLIKELY(!((val >= low) && (val <= high)))) { \ 38 | checkBoundFailed(__FILE__, __LINE__, __func__, (intptr_t)(val), \ 39 | (intptr_t)(low), (intptr_t)(high)); \ 40 | } \ 41 | } while (0) 42 | 43 | #if DCHECK_IS_ON() 44 | #define DCHECK(...) CHECK(__VA_ARGS__) 45 | #define DCHECK_BOUND(val, high) CHECK_BOUND(val, high) 46 | #define DCHECK_INDEX(index, high) CHECK_INDEX(index, high) 47 | #define DCHECK_RANGE(val, low, high) CHECK_RANGE(val, low, high) 48 | #else 49 | #define DCHECK(...) \ 50 | if (false) { \ 51 | CHECK(__VA_ARGS__); \ 52 | } 53 | #define DCHECK_BOUND(val, high) DCHECK_RANGE(val, 0, high) 54 | #define DCHECK_INDEX(index, high) \ 55 | if (false) { \ 56 | CHECK_INDEX(index, high); \ 57 | } 58 | #define DCHECK_RANGE(val, low, high) \ 59 | if (false) { \ 60 | CHECK_RANGE(val, low, high); \ 61 | } 62 | #endif 63 | 64 | #define UNIMPLEMENTED(...) \ 65 | checkFailed(__FILE__, __LINE__, __func__, "unimplemented", __VA_ARGS__) 66 | 67 | #define UNREACHABLE(...) \ 68 | checkFailed(__FILE__, __LINE__, __func__, "unreachable", __VA_ARGS__) 69 | 70 | // From https://github.com/Moonstroke/PUCA 71 | #if defined(__cplusplus) && __cplusplus >= 201103L /* ISO C++11 */ 72 | #define NORETURN [[noreturn]] 73 | #else /* C++11 */ 74 | #ifdef __GNUC__ 75 | #define NORETURN __attribute__((__noreturn__)) 76 | #else /* __GNUC__ */ 77 | #ifdef _MSC_VER 78 | #define NORETURN __declspec(noreturn) 79 | #else /* _MSC_VER */ 80 | #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L /* ISO C11 */ 81 | #define NORETURN _Noreturn 82 | #else /* C11 */ 83 | #define NORETURN 84 | #endif /* C11 */ 85 | #endif /* _MSC_VER */ 86 | #endif /* __GNUC__ */ 87 | #endif /* C++11 */ 88 | 89 | #ifdef __cplusplus 90 | extern "C" { 91 | #endif 92 | 93 | NORETURN void checkFailed(const char *file, int line, const char *func, 94 | const char *expr, ...); 95 | NORETURN void checkIndexFailed(const char *file, int line, const char *func, 96 | intptr_t index, intptr_t high); 97 | NORETURN void checkBoundFailed(const char *file, int line, const char *func, 98 | intptr_t value, intptr_t low, intptr_t high); 99 | 100 | #ifdef __cplusplus 101 | } 102 | #endif 103 | -------------------------------------------------------------------------------- /dis/globals.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) */ 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | typedef unsigned char byte; 11 | typedef intptr_t word; 12 | typedef uintptr_t uword; 13 | 14 | static_assert(sizeof(word) == sizeof(size_t), 15 | "word must be the same size as size_t"); 16 | 17 | const int kBoolSize = sizeof(bool); 18 | const int kByteSize = sizeof(byte); 19 | const int kDoubleSize = sizeof(double); 20 | const int kFloatSize = sizeof(float); 21 | const int kIntSize = sizeof(int); 22 | const int kLongSize = sizeof(long); 23 | const int kLongLongSize = sizeof(long long); 24 | const int kPointerSize = sizeof(void*); 25 | const int kShortSize = sizeof(short); 26 | const int kWcharSize = sizeof(wchar_t); 27 | const int kWordSize = sizeof(word); 28 | 29 | const int kWordSizeLog2 = 3; 30 | 31 | const int kUwordDigits10 = 19; 32 | const uword kUwordDigits10Pow = 10000000000000000000ul; 33 | const int kBitsPerHexDigit = 4; 34 | const int kBitsPerOctDigit = 3; 35 | 36 | const int kBitsPerByte = 8; 37 | const int kBitsPerPointer = kBitsPerByte * kWordSize; 38 | const int kBitsPerWord = kBitsPerByte * kWordSize; 39 | const int kBitsPerDouble = kBitsPerByte * kDoubleSize; 40 | 41 | const int kDoubleMantissaBits = 52; 42 | const double kDoubleNaN = std::numeric_limits::quiet_NaN(); 43 | const double kDoubleInfinity = std::numeric_limits::infinity(); 44 | const auto kDoubleMinExp = std::numeric_limits::min_exponent; 45 | const auto kDoubleMaxExp = std::numeric_limits::max_exponent; 46 | const auto kDoubleDigits = std::numeric_limits::digits; 47 | 48 | const int16_t kMaxInt16 = INT16_MAX; 49 | const int16_t kMinInt16 = INT16_MIN; 50 | const int32_t kMaxInt32 = INT32_MAX; 51 | const int32_t kMinInt32 = INT32_MIN; 52 | const int64_t kMaxInt64 = INT64_MAX; 53 | const int64_t kMinInt64 = INT64_MIN; 54 | const uint16_t kMaxUint16 = UINT16_MAX; 55 | const uint32_t kMaxUint32 = UINT32_MAX; 56 | const uint64_t kMaxUint64 = UINT64_MAX; 57 | 58 | const byte kMaxByte = 0xFF; 59 | 60 | const long kMinLong = std::numeric_limits::min(); 61 | const long kMaxLong = std::numeric_limits::max(); 62 | 63 | const word kMinWord = INTPTR_MIN; 64 | const word kMaxWord = INTPTR_MAX; 65 | const uword kMaxUword = UINTPTR_MAX; 66 | 67 | const int kMaxASCII = 127; 68 | const int kMaxUnicode = 0x10ffff; 69 | const int32_t kReplacementCharacter = 0xFFFD; 70 | 71 | const int kKiB = 1024; 72 | const int kMiB = kKiB * kKiB; 73 | const int kGiB = kKiB * kKiB * kKiB; 74 | 75 | const int kMillisecondsPerSecond = 1000; 76 | const int kMicrosecondsPerMillisecond = 1000; 77 | const int kMicrosecondsPerSecond = 78 | kMillisecondsPerSecond * kMicrosecondsPerMillisecond; 79 | const int kNanosecondsPerMicrosecond = 1000; 80 | const int kNanosecondsPerSecond = 81 | kMicrosecondsPerSecond * kNanosecondsPerMicrosecond; 82 | 83 | // Equivalent to _PyHash_BITS. This is NOT the maximum size of a hash value, 84 | // that would is either RawHeader::kHashCodeBits or SmallInt::kMaxValue 85 | // depending on whether the hash value is cached in the object header. 86 | const word kArithmeticHashBits = 61; 87 | // Equivalent to _PyHASH_MODULUS. Should be a mersenne prime. 88 | const word kArithmeticHashModulus = ((word{1} << kArithmeticHashBits) - 1); 89 | const word kHashInf = 314159; 90 | const word kHashNan = 0; 91 | const word kHashImag = 1000003; 92 | 93 | #ifndef __has_builtin 94 | #define __has_builtin(x) 0 95 | #endif 96 | #ifndef __has_cpp_atttribute 97 | #define __has_cpp_atttribute(x) 0 98 | #endif 99 | 100 | #if __GNUG__ && __GNUC__ < 5 101 | #define IS_TRIVIALLY_COPYABLE(T) __has_trivial_copy(T) 102 | #else 103 | #define IS_TRIVIALLY_COPYABLE(T) std::is_trivially_copyable::value 104 | #endif 105 | 106 | template 107 | static inline D bit_cast(const S& src) { 108 | static_assert(sizeof(S) == sizeof(D), "src and dst must be the same size"); 109 | static_assert(IS_TRIVIALLY_COPYABLE(S), "src must be trivially copyable"); 110 | static_assert(std::is_trivial::value, "dst must be trivial"); 111 | D dst; 112 | std::memcpy(&dst, &src, sizeof(dst)); 113 | return dst; 114 | } 115 | 116 | #define ARRAYSIZE(x) (sizeof(x) / sizeof((x)[0])) 117 | 118 | #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ 119 | TypeName(const TypeName&) = delete; \ 120 | void operator=(const TypeName&) = delete 121 | 122 | #define DISALLOW_HEAP_ALLOCATION() \ 123 | void* operator new(size_t size) = delete; \ 124 | void* operator new[](size_t size) = delete 125 | 126 | #define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ 127 | TypeName() = delete; \ 128 | DISALLOW_COPY_AND_ASSIGN(TypeName) 129 | 130 | #define PY_EXPORT extern "C" __attribute__((visibility("default"))) 131 | 132 | // FORMAT_ATTRIBUTE allows typechecking by the compiler. 133 | // string_index: The function argument index where the format index is. 134 | // this has the implicit index 1. 135 | // varargs_index: The function argument index where varargs start. 136 | // The archetype chosen is printf since this is being used for output strings. 137 | #define FORMAT_ATTRIBUTE(string_index, first_to_check) \ 138 | __attribute__((format(printf, string_index, first_to_check))) 139 | 140 | // Branch prediction hints for the compiler. Use in performance critial code 141 | // which almost always branches one way. 142 | #define LIKELY(x) __builtin_expect(!!(x), 1) 143 | #define UNLIKELY(x) __builtin_expect(!!(x), 0) 144 | 145 | // Old-school version of offsetof() that works with non-standard layout classes. 146 | #define OFFSETOF(ty, memb) \ 147 | (reinterpret_cast(&reinterpret_cast(16)->memb) - \ 148 | reinterpret_cast(16)) 149 | 150 | #if __has_cpp_atttribute(nodiscard) || \ 151 | (__GNUC__ >= 5 || __GNUC__ == 4 && __GNUC_MINOR__ >= 8) 152 | #define NODISCARD [[nodiscard]] 153 | #else 154 | #define NODISCARD __attribute__((warn_unused_result)) 155 | #endif 156 | 157 | #define ALIGN_16 __attribute__((aligned(16))) 158 | 159 | #define ALWAYS_INLINE inline __attribute__((always_inline)) 160 | 161 | #if defined(__clang__) 162 | #define FALLTHROUGH [[clang::fallthrough]] 163 | #elif defined(__GNUC__) 164 | #define FALLTHROUGH __attribute__((fallthrough)) 165 | #else 166 | #define FALLTHROUGH \ 167 | do { \ 168 | } while (0) 169 | #endif 170 | 171 | #define NEVER_INLINE __attribute__((noinline)) 172 | 173 | #define USED __attribute__((used)) 174 | 175 | #define UNUSED __attribute__((unused)) 176 | 177 | #define WARN_UNUSED __attribute__((warn_unused)) 178 | 179 | // Endian enum (as proposed in the C++20 draft). 180 | enum class endian { 181 | // Implementation for gcc + clang compilers. 182 | #if defined(__ORDER_LITTLE_ENDIAN__) && defined(__ORDER_BIG_ENDIAN__) && \ 183 | defined(__BYTE_ORDER__) 184 | little = __ORDER_LITTLE_ENDIAN__, 185 | big = __ORDER_BIG_ENDIAN__, 186 | native = __BYTE_ORDER__ 187 | #else 188 | #error "endian class not implemented for this compiler" 189 | #endif 190 | }; 191 | -------------------------------------------------------------------------------- /dis/assembler-utils.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) */ 2 | // Copyright (c) 2013, the Dart project authors and Facebook, Inc. and its 3 | // affiliates. Please see the AUTHORS-Dart file for details. All rights 4 | // reserved. Use of this source code is governed by a BSD-style license that 5 | // can be found in the LICENSE-Dart file. 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include "globals.h" 12 | #include "memory-region.h" 13 | #include "vector.h" 14 | 15 | namespace dis { 16 | 17 | // Forward declarations. 18 | namespace x64 { 19 | class Assembler; 20 | } 21 | class AssemblerBuffer; 22 | 23 | class WARN_UNUSED Label { 24 | public: 25 | Label() : position_(0), unresolved_(0) { 26 | #ifndef NDEBUG 27 | for (int i = 0; i < kMaxUnresolvedBranches; i++) { 28 | unresolved_near_positions_[i] = -1; 29 | } 30 | #endif 31 | } 32 | 33 | ~Label() { 34 | // Assert if label is being destroyed with unresolved branches pending. 35 | DCHECK(!isLinked(), "assert()"); 36 | DCHECK(!hasNear(), "assert()"); 37 | } 38 | 39 | // Returns the position for bound and linked labels. Cannot be used 40 | // for unused labels. 41 | word position() const { 42 | DCHECK(!isUnused(), "assert()"); 43 | return isBound() ? -position_ - kBias : position_ - kBias; 44 | } 45 | 46 | word linkPosition() const { 47 | DCHECK(isLinked(), "assert()"); 48 | return position_ - kBias; 49 | } 50 | 51 | word nearPosition() { 52 | DCHECK(hasNear(), "assert()"); 53 | return unresolved_near_positions_[--unresolved_]; 54 | } 55 | 56 | bool isBound() const { return position_ < 0; } 57 | bool isUnused() const { return position_ == 0 && unresolved_ == 0; } 58 | bool isLinked() const { return position_ > 0; } 59 | bool hasNear() const { return unresolved_ != 0; } 60 | 61 | private: 62 | static const int kMaxUnresolvedBranches = 20; 63 | // Zero position_ means unused (neither bound nor linked to). 64 | // Thus we offset actual positions by the given bias to prevent zero 65 | // positions from occurring. 66 | static const int kBias = 4; 67 | 68 | word position_; 69 | word unresolved_; 70 | word unresolved_near_positions_[kMaxUnresolvedBranches]; 71 | 72 | void reinitialize() { position_ = 0; } 73 | 74 | void bindTo(word position) { 75 | DCHECK(!isBound(), "assert()"); 76 | DCHECK(!hasNear(), "assert()"); 77 | position_ = -position - kBias; 78 | DCHECK(isBound(), "assert()"); 79 | } 80 | 81 | void linkTo(word position) { 82 | DCHECK(!isBound(), "assert()"); 83 | position_ = position + kBias; 84 | DCHECK(isLinked(), "assert()"); 85 | } 86 | 87 | void nearLinkTo(word position) { 88 | DCHECK(!isBound(), "assert()"); 89 | DCHECK(unresolved_ < kMaxUnresolvedBranches, "assert()"); 90 | unresolved_near_positions_[unresolved_++] = position; 91 | } 92 | 93 | friend class x64::Assembler; 94 | DISALLOW_COPY_AND_ASSIGN(Label); 95 | }; 96 | 97 | // Assembler fixups are positions in generated code that hold relocation 98 | // information that needs to be processed before finalizing the code 99 | // into executable memory. 100 | class AssemblerFixup { 101 | public: 102 | virtual void process(MemoryRegion region, word position) = 0; 103 | 104 | // It would be ideal if the destructor method could be made private, 105 | // but the g++ compiler complains when this is subclassed. 106 | virtual ~AssemblerFixup() { UNREACHABLE("~AssemblerFixup"); } 107 | 108 | private: 109 | AssemblerFixup* previous_; 110 | word position_; 111 | 112 | AssemblerFixup* previous() const { return previous_; } 113 | void setPrevious(AssemblerFixup* previous) { previous_ = previous; } 114 | 115 | word position() const { return position_; } 116 | void setPosition(word position) { position_ = position; } 117 | 118 | friend class AssemblerBuffer; 119 | }; 120 | 121 | // Assembler buffers are used to emit binary code. They grow on demand. 122 | class AssemblerBuffer { 123 | public: 124 | AssemblerBuffer(); 125 | ~AssemblerBuffer(); 126 | 127 | // Basic support for emitting, loading, and storing. 128 | template 129 | void emit(T value) { 130 | DCHECK(hasEnsuredCapacity(), "assert()"); 131 | std::memcpy(reinterpret_cast(cursor_), &value, sizeof(T)); 132 | cursor_ += sizeof(T); 133 | } 134 | 135 | template 136 | void remit() { 137 | DCHECK(size() >= static_cast(sizeof(T)), "assert()"); 138 | cursor_ -= sizeof(T); 139 | } 140 | 141 | // Return address to code at |position| bytes. 142 | uword address(word position) { return contents_ + position; } 143 | 144 | template 145 | T load(word position) { 146 | DCHECK(position >= 0, "assert()"); 147 | DCHECK(position <= (size() - static_cast(sizeof(T))), "assert()"); 148 | return *reinterpret_cast(contents_ + position); 149 | } 150 | 151 | template 152 | void store(word position, T value) { 153 | DCHECK(position >= 0, "assert()"); 154 | DCHECK(position <= (size() - static_cast(sizeof(T))), "assert()"); 155 | *reinterpret_cast(contents_ + position) = value; 156 | } 157 | 158 | const Vector& pointerOffsets() const { 159 | #ifndef NDEBUG 160 | DCHECK(fixups_processed_, "assert()"); 161 | #endif 162 | return pointer_offsets_; 163 | } 164 | 165 | // Emit a fixup at the current location. 166 | void emitFixup(AssemblerFixup* fixup) { 167 | fixup->setPrevious(fixup_); 168 | fixup->setPosition(size()); 169 | fixup_ = fixup; 170 | } 171 | 172 | // Count the fixups that produce a pointer offset, without processing 173 | // the fixups. 174 | word countPointerOffsets() const; 175 | 176 | // Get the size of the emitted code. 177 | word size() const { return cursor_ - contents_; } 178 | uword contents() const { return contents_; } 179 | 180 | // Copy the assembled instructions into the specified memory block 181 | // and apply all fixups. 182 | void finalizeInstructions(MemoryRegion instructions); 183 | 184 | // To emit an instruction to the assembler buffer, the EnsureCapacity helper 185 | // must be used to guarantee that the underlying data area is big enough to 186 | // hold the emitted instruction. Usage: 187 | // 188 | // AssemblerBuffer buffer; 189 | // AssemblerBuffer::EnsureCapacity ensured(&buffer); 190 | // ... emit bytes for single instruction ... 191 | 192 | #ifndef NDEBUG 193 | class EnsureCapacity { 194 | public: 195 | explicit EnsureCapacity(AssemblerBuffer* buffer); 196 | ~EnsureCapacity(); 197 | 198 | private: 199 | AssemblerBuffer* buffer_; 200 | word gap_; 201 | 202 | word computeGap() { return buffer_->capacity() - buffer_->size(); } 203 | }; 204 | 205 | bool has_ensured_capacity_; 206 | bool hasEnsuredCapacity() const { return has_ensured_capacity_; } 207 | #else 208 | class EnsureCapacity { 209 | public: 210 | explicit EnsureCapacity(AssemblerBuffer* buffer) { 211 | if (buffer->cursor() >= buffer->limit()) buffer->extendCapacity(); 212 | } 213 | }; 214 | 215 | // When building the C++ tests, assertion code is enabled. To allow 216 | // asserting that the user of the assembler buffer has ensured the 217 | // capacity needed for emitting, we add a dummy method in non-debug mode. 218 | bool hasEnsuredCapacity() const { return true; } 219 | #endif 220 | 221 | // Returns the position in the instruction stream. 222 | word getPosition() const { return cursor_ - contents_; } 223 | 224 | void reset() { cursor_ = contents_; } 225 | 226 | private: 227 | // The limit is set to kMinimumGap bytes before the end of the data area. 228 | // This leaves enough space for the longest possible instruction and allows 229 | // for a single, fast space check per instruction. 230 | static const word kMinimumGap = 32; 231 | 232 | uword contents_; 233 | uword cursor_; 234 | uword limit_; 235 | AssemblerFixup* fixup_; 236 | Vector pointer_offsets_; 237 | #ifndef NDEBUG 238 | bool fixups_processed_; 239 | #endif 240 | 241 | uword cursor() const { return cursor_; } 242 | uword limit() const { return limit_; } 243 | word capacity() const { 244 | DCHECK(limit_ >= contents_, "assert()"); 245 | return (limit_ - contents_) + kMinimumGap; 246 | } 247 | 248 | // Process the fixup chain. 249 | void processFixups(MemoryRegion region); 250 | 251 | // Compute the limit based on the data area and the capacity. See 252 | // description of kMinimumGap for the reasoning behind the value. 253 | static uword computeLimit(uword data, word capacity) { 254 | return data + capacity - kMinimumGap; 255 | } 256 | 257 | void extendCapacity(); 258 | 259 | friend class AssemblerFixup; 260 | }; 261 | 262 | class CodeComment { 263 | public: 264 | CodeComment(word offset, const char* comment) 265 | : offset_(offset), comment_(comment) {} 266 | 267 | word offset() const { return offset_; } 268 | const std::string& comment() const { return comment_; } 269 | 270 | private: 271 | word offset_ = -1; 272 | std::string comment_; 273 | 274 | DISALLOW_COPY_AND_ASSIGN(CodeComment); 275 | }; 276 | 277 | class CodeComments { 278 | public: 279 | CodeComments() = default; 280 | 281 | ~CodeComments() { 282 | for (word i = 0; i < comments_.size(); i++) { 283 | delete comments_[i]; 284 | } 285 | } 286 | 287 | void add(CodeComment* comment) { comments_.push_back(comment); } 288 | 289 | const char* commentAt(word index) const { 290 | return comments_[index]->comment().c_str(); 291 | } 292 | 293 | word length() const { return comments_.size(); } 294 | 295 | word offsetAt(word index) const { return comments_[index]->offset(); } 296 | 297 | private: 298 | Vector comments_; 299 | 300 | DISALLOW_COPY_AND_ASSIGN(CodeComments); 301 | }; 302 | 303 | } // namespace dis 304 | -------------------------------------------------------------------------------- /dis/assembler-x64.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) 2 | // Copyright (c) 2013, the Dart project authors and Facebook, Inc. and its 3 | // affiliates. Please see the AUTHORS-Dart file for details. All rights 4 | // reserved. Use of this source code is governed by a BSD-style license that 5 | // can be found in the LICENSE-Dart file. 6 | 7 | #include "assembler-x64.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "globals.h" 14 | 15 | namespace dis { 16 | namespace x64 { 17 | 18 | void Assembler::initializeMemoryWithBreakpoints(uword data, word length) { 19 | std::memset(reinterpret_cast(data), 0xcc, length); 20 | } 21 | 22 | void Assembler::call(Label* label) { 23 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 24 | static const int size = 5; 25 | emitUint8(0xe8); 26 | emitLabel(label, size); 27 | } 28 | 29 | void Assembler::pushq(Register reg) { 30 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 31 | emitRegisterREX(reg, REX_NONE); 32 | emitUint8(0x50 | (reg & 7)); 33 | } 34 | 35 | void Assembler::pushq(Immediate imm) { 36 | if (imm.isInt8()) { 37 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 38 | emitUint8(0x6a); 39 | emitUint8(imm.value() & 0xff); 40 | } else { 41 | DCHECK(imm.isInt32(), "assert()"); 42 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 43 | emitUint8(0x68); 44 | emitImmediate(imm); 45 | } 46 | } 47 | 48 | void Assembler::popq(Register reg) { 49 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 50 | emitRegisterREX(reg, REX_NONE); 51 | emitUint8(0x58 | (reg & 7)); 52 | } 53 | 54 | void Assembler::setcc(Condition condition, Register dst) { 55 | DCHECK(dst != kNoRegister, "assert()"); 56 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 57 | if (dst >= 8) { 58 | emitUint8(REX_PREFIX | (((dst & 0x08) != 0) ? REX_B : REX_NONE)); 59 | } 60 | emitUint8(0x0f); 61 | emitUint8(0x90 + condition); 62 | emitUint8(0xc0 + (dst & 0x07)); 63 | } 64 | 65 | void Assembler::emitQ(int reg, Address address, int opcode, int prefix2, 66 | int prefix1) { 67 | DCHECK(reg <= XMM15, "assert()"); 68 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 69 | if (prefix1 >= 0) { 70 | emitUint8(prefix1); 71 | } 72 | emitOperandREX(reg, address, REX_W); 73 | if (prefix2 >= 0) { 74 | emitUint8(prefix2); 75 | } 76 | emitUint8(opcode); 77 | emitOperand(reg & 7, address); 78 | } 79 | 80 | void Assembler::emitB(Register reg, Address address, int opcode, int prefix2, 81 | int prefix1) { 82 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 83 | if (prefix1 >= 0) { 84 | emitUint8(prefix1); 85 | } 86 | emitOperandREX(reg, address, byteRegisterREX(reg)); 87 | if (prefix2 >= 0) { 88 | emitUint8(prefix2); 89 | } 90 | emitUint8(opcode); 91 | emitOperand(reg & 7, address); 92 | } 93 | 94 | void Assembler::emitL(int reg, Address address, int opcode, int prefix2, 95 | int prefix1) { 96 | DCHECK(reg <= XMM15, "assert()"); 97 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 98 | if (prefix1 >= 0) { 99 | emitUint8(prefix1); 100 | } 101 | emitOperandREX(reg, address, REX_NONE); 102 | if (prefix2 >= 0) { 103 | emitUint8(prefix2); 104 | } 105 | emitUint8(opcode); 106 | emitOperand(reg & 7, address); 107 | } 108 | 109 | void Assembler::emitW(Register reg, Address address, int opcode, int prefix2, 110 | int prefix1) { 111 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 112 | if (prefix1 >= 0) { 113 | emitUint8(prefix1); 114 | } 115 | emitOperandSizeOverride(); 116 | emitOperandREX(reg, address, REX_NONE); 117 | if (prefix2 >= 0) { 118 | emitUint8(prefix2); 119 | } 120 | emitUint8(opcode); 121 | emitOperand(reg & 7, address); 122 | } 123 | 124 | void Assembler::movl(Register dst, Immediate imm) { 125 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 126 | Operand operand(dst); 127 | emitOperandREX(0, operand, REX_NONE); 128 | emitUint8(0xc7); 129 | emitOperand(0, operand); 130 | DCHECK(imm.isInt32(), "assert()"); 131 | emitImmediate(imm); 132 | } 133 | 134 | void Assembler::movl(Address dst, Immediate imm) { 135 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 136 | Operand operand(dst); 137 | emitOperandREX(0, dst, REX_NONE); 138 | emitUint8(0xc7); 139 | emitOperand(0, dst); 140 | DCHECK(imm.isInt32(), "assert()"); 141 | emitImmediate(imm); 142 | } 143 | 144 | void Assembler::movb(Address dst, Immediate imm) { 145 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 146 | emitOperandREX(0, dst, REX_NONE); 147 | emitUint8(0xc6); 148 | emitOperand(0, dst); 149 | DCHECK(imm.isInt8(), "assert()"); 150 | emitUint8(imm.value() & 0xff); 151 | } 152 | 153 | void Assembler::movw(Register /*dst*/, Address /*src*/) { 154 | UNIMPLEMENTED("Use movzxw or movsxw instead."); 155 | } 156 | 157 | void Assembler::movw(Address dst, Immediate imm) { 158 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 159 | emitOperandSizeOverride(); 160 | emitOperandREX(0, dst, REX_NONE); 161 | emitUint8(0xc7); 162 | emitOperand(0, dst); 163 | emitUint8(imm.value() & 0xff); 164 | emitUint8((imm.value() >> 8) & 0xff); 165 | } 166 | 167 | void Assembler::leaq(Register dst, Label* label) { 168 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 169 | // Emit RIP-relative lea 170 | emitUint8(REX_PREFIX | REX_W | (dst > 7 ? REX_R : REX_NONE)); 171 | emitUint8(0x8d); 172 | Address address(Address::addressRIPRelative(0xdeadbeef)); 173 | emitOperand(dst & 7, address); 174 | // Overwrite the fake displacement with a label or label link 175 | buffer_.remit(); 176 | static const int size = 7; 177 | emitLabel(label, size); 178 | } 179 | 180 | void Assembler::movq(Register dst, Immediate imm) { 181 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 182 | if (imm.isUint32()) { 183 | // Pick single byte B8 encoding if possible. If dst < 8 then we also omit 184 | // the Rex byte. 185 | emitRegisterREX(dst, REX_NONE); 186 | emitUint8(0xb8 | (dst & 7)); 187 | emitUInt32(imm.value()); 188 | } else if (imm.isInt32()) { 189 | // Sign extended C7 Cx encoding if we have a negative input. 190 | Operand operand(dst); 191 | emitOperandREX(0, operand, REX_W); 192 | emitUint8(0xc7); 193 | emitOperand(0, operand); 194 | emitImmediate(imm); 195 | } else { 196 | // Full 64 bit immediate encoding. 197 | emitRegisterREX(dst, REX_W); 198 | emitUint8(0xb8 | (dst & 7)); 199 | emitImmediate(imm); 200 | } 201 | } 202 | 203 | void Assembler::movq(Address dst, Immediate imm) { 204 | CHECK(imm.isInt32(), "this instruction only exists for 32bit immediates"); 205 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 206 | emitOperandREX(0, dst, REX_W); 207 | emitUint8(0xC7); 208 | emitOperand(0, dst); 209 | emitImmediate(imm); 210 | } 211 | 212 | void Assembler::movsb() { 213 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 214 | emitUint8(0xa4); 215 | } 216 | 217 | void Assembler::movsw() { 218 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 219 | emitOperandSizeOverride(); 220 | emitUint8(0xa5); 221 | } 222 | 223 | void Assembler::movsl() { 224 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 225 | emitUint8(0xa5); 226 | } 227 | 228 | void Assembler::movsq() { 229 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 230 | emitUint8(REX_PREFIX | REX_W); 231 | emitUint8(0xa5); 232 | } 233 | 234 | void Assembler::repMovsb() { 235 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 236 | emitUint8(REP); 237 | emitUint8(0xa4); 238 | } 239 | 240 | void Assembler::repMovsw() { 241 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 242 | emitUint8(REP); 243 | emitOperandSizeOverride(); 244 | emitUint8(0xa5); 245 | } 246 | 247 | void Assembler::repMovsl() { 248 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 249 | emitUint8(REP); 250 | emitUint8(0xa5); 251 | } 252 | 253 | void Assembler::repMovsq() { 254 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 255 | emitUint8(REP); 256 | emitUint8(REX_PREFIX | REX_W); 257 | emitUint8(0xa5); 258 | } 259 | 260 | void Assembler::repnzMovsb() { 261 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 262 | emitUint8(REPNZ); 263 | emitUint8(0xa4); 264 | } 265 | 266 | void Assembler::repnzMovsw() { 267 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 268 | emitUint8(REPNZ); 269 | emitOperandSizeOverride(); 270 | emitUint8(0xa5); 271 | } 272 | 273 | void Assembler::repnzMovsl() { 274 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 275 | emitUint8(REPNZ); 276 | emitUint8(0xa5); 277 | } 278 | 279 | void Assembler::repnzMovsq() { 280 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 281 | emitUint8(REPNZ); 282 | emitUint8(REX_PREFIX | REX_W); 283 | emitUint8(0xa5); 284 | } 285 | 286 | void Assembler::emitSimple(int opcode, int opcode2) { 287 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 288 | emitUint8(opcode); 289 | if (opcode2 != -1) { 290 | emitUint8(opcode2); 291 | } 292 | } 293 | 294 | void Assembler::emitQ(int dst, int src, int opcode, int prefix2, int prefix1) { 295 | DCHECK(src <= XMM15, "assert()"); 296 | DCHECK(dst <= XMM15, "assert()"); 297 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 298 | if (prefix1 >= 0) { 299 | emitUint8(prefix1); 300 | } 301 | emitRegRegREX(dst, src, REX_W); 302 | if (prefix2 >= 0) { 303 | emitUint8(prefix2); 304 | } 305 | emitUint8(opcode); 306 | emitRegisterOperand(dst & 7, src); 307 | } 308 | 309 | void Assembler::emitL(int dst, int src, int opcode, int prefix2, int prefix1) { 310 | DCHECK(src <= XMM15, "assert()"); 311 | DCHECK(dst <= XMM15, "assert()"); 312 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 313 | if (prefix1 >= 0) { 314 | emitUint8(prefix1); 315 | } 316 | emitRegRegREX(dst, src); 317 | if (prefix2 >= 0) { 318 | emitUint8(prefix2); 319 | } 320 | emitUint8(opcode); 321 | emitRegisterOperand(dst & 7, src); 322 | } 323 | 324 | void Assembler::emitW(Register dst, Register src, int opcode, int prefix2, 325 | int prefix1) { 326 | DCHECK(src <= R15, "assert()"); 327 | DCHECK(dst <= R15, "assert()"); 328 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 329 | if (prefix1 >= 0) { 330 | emitUint8(prefix1); 331 | } 332 | emitOperandSizeOverride(); 333 | emitRegRegREX(dst, src); 334 | if (prefix2 >= 0) { 335 | emitUint8(prefix2); 336 | } 337 | emitUint8(opcode); 338 | emitRegisterOperand(dst & 7, src); 339 | } 340 | 341 | void Assembler::cmpPS(XmmRegister dst, XmmRegister src, int condition) { 342 | emitL(dst, src, 0xc2, 0x0f); 343 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 344 | emitUint8(condition); 345 | } 346 | 347 | void Assembler::set1ps(XmmRegister dst, Register tmp, Immediate imm) { 348 | // load 32-bit immediate value into tmp1. 349 | movl(tmp, imm); 350 | // Move value from tmp1 into dst. 351 | movd(dst, tmp); 352 | // Broadcast low lane into other three lanes. 353 | shufps(dst, dst, Immediate(0x0)); 354 | } 355 | 356 | void Assembler::shufps(XmmRegister dst, XmmRegister src, Immediate mask) { 357 | emitL(dst, src, 0xc6, 0x0f); 358 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 359 | DCHECK(mask.isUint8(), "assert()"); 360 | emitUint8(mask.value()); 361 | } 362 | 363 | void Assembler::shufpd(XmmRegister dst, XmmRegister src, Immediate mask) { 364 | emitL(dst, src, 0xc6, 0x0f, 0x66); 365 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 366 | DCHECK(mask.isUint8(), "assert()"); 367 | emitUint8(mask.value()); 368 | } 369 | 370 | void Assembler::roundsd(XmmRegister dst, XmmRegister src, RoundingMode mode) { 371 | DCHECK(src <= XMM15, "assert()"); 372 | DCHECK(dst <= XMM15, "assert()"); 373 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 374 | emitUint8(0x66); 375 | emitRegRegREX(dst, src); 376 | emitUint8(0x0f); 377 | emitUint8(0x3a); 378 | emitUint8(0x0b); 379 | emitRegisterOperand(dst & 7, src); 380 | // Mask precision exeption. 381 | emitUint8(static_cast(mode) | 0x8); 382 | } 383 | 384 | void Assembler::fldl(Address src) { 385 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 386 | emitUint8(0xdd); 387 | emitOperand(0, src); 388 | } 389 | 390 | void Assembler::fstpl(Address dst) { 391 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 392 | emitUint8(0xdd); 393 | emitOperand(3, dst); 394 | } 395 | 396 | void Assembler::ffree(word value) { 397 | DCHECK(value < 7, "assert()"); 398 | emitSimple(0xdd, 0xc0 + value); 399 | } 400 | 401 | void Assembler::emitTestB(Operand operand, Immediate imm) { 402 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 403 | if (operand.hasRegister(RAX)) { 404 | emitUint8(0xa8); 405 | } else { 406 | emitOperandREX(0, operand, byteOperandREX(operand)); 407 | emitUint8(0xf6); 408 | emitOperand(0, operand); 409 | } 410 | DCHECK(imm.isInt8(), "immediate too large"); 411 | emitUint8(imm.value()); 412 | } 413 | 414 | void Assembler::testb(Register dst, Register src) { 415 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 416 | emitRegRegREX(dst, src, byteRegisterREX(dst) | byteRegisterREX(src)); 417 | emitUint8(0x84); 418 | emitRegisterOperand(dst & 7, src); 419 | } 420 | 421 | void Assembler::testb(Register reg, Immediate imm) { 422 | emitTestB(Operand(reg), imm); 423 | } 424 | 425 | void Assembler::testb(Address address, Immediate imm) { 426 | emitTestB(address, imm); 427 | } 428 | 429 | void Assembler::testb(Address address, Register reg) { 430 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 431 | emitOperandREX(reg, address, byteRegisterREX(reg)); 432 | emitUint8(0x84); 433 | emitOperand(reg & 7, address); 434 | } 435 | 436 | void Assembler::emitTestQ(Operand operand, Immediate imm) { 437 | // Try to emit a small instruction if the value of the immediate lets us. For 438 | // Address operands, this relies on the fact that x86 is little-endian. 439 | if (imm.isUint8()) { 440 | emitTestB(operand, Immediate(static_cast(imm.value()))); 441 | } else if (imm.isUint32()) { 442 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 443 | if (operand.hasRegister(RAX)) { 444 | emitUint8(0xa9); 445 | } else { 446 | emitOperandREX(0, operand, REX_NONE); 447 | emitUint8(0xf7); 448 | emitOperand(0, operand); 449 | } 450 | emitUInt32(imm.value()); 451 | } else { 452 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 453 | // Sign extended version of 32 bit test. 454 | DCHECK(imm.isInt32(), "assert()"); 455 | emitOperandREX(0, operand, REX_W); 456 | if (operand.hasRegister(RAX)) { 457 | emitUint8(0xa9); 458 | } else { 459 | emitUint8(0xf7); 460 | emitOperand(0, operand); 461 | } 462 | emitInt32(imm.value()); 463 | } 464 | } 465 | 466 | void Assembler::testq(Register reg, Immediate imm) { 467 | emitTestQ(Operand(reg), imm); 468 | } 469 | 470 | void Assembler::testq(Address address, Immediate imm) { 471 | emitTestQ(address, imm); 472 | } 473 | 474 | void Assembler::aluB(uint8_t modrm_opcode, Register dst, Immediate imm) { 475 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 476 | Operand dst_operand(dst); 477 | emitOperandREX(modrm_opcode, dst_operand, byteRegisterREX(dst)); 478 | emitComplexB(modrm_opcode, dst_operand, imm); 479 | } 480 | 481 | void Assembler::aluL(uint8_t modrm_opcode, Register dst, Immediate imm) { 482 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 483 | emitRegisterREX(dst, REX_NONE); 484 | emitComplex(modrm_opcode, Operand(dst), imm); 485 | } 486 | 487 | void Assembler::aluB(uint8_t modrm_opcode, Address dst, Immediate imm) { 488 | DCHECK(imm.isUint8() || imm.isInt8(), "assert()"); 489 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 490 | emitOperandREX(modrm_opcode, dst, REX_NONE); 491 | emitUint8(0x80); 492 | emitOperand(modrm_opcode, dst); 493 | emitUint8(imm.value() & 0xff); 494 | } 495 | 496 | void Assembler::aluW(uint8_t modrm_opcode, Address dst, Immediate imm) { 497 | DCHECK(imm.isInt16() || imm.isUint16(), "assert()"); 498 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 499 | emitOperandSizeOverride(); 500 | emitOperandREX(modrm_opcode, dst, REX_NONE); 501 | if (imm.isInt8()) { 502 | emitSignExtendedInt8(modrm_opcode, dst, imm); 503 | } else { 504 | emitUint8(0x81); 505 | emitOperand(modrm_opcode, dst); 506 | emitUint8(imm.value() & 0xff); 507 | emitUint8((imm.value() >> 8) & 0xff); 508 | } 509 | } 510 | 511 | void Assembler::aluL(uint8_t modrm_opcode, Address dst, Immediate imm) { 512 | DCHECK(imm.isInt32(), "assert()"); 513 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 514 | emitOperandREX(modrm_opcode, dst, REX_NONE); 515 | emitComplex(modrm_opcode, dst, imm); 516 | } 517 | 518 | void Assembler::aluQ(uint8_t modrm_opcode, uint8_t /*opcode*/, Register dst, 519 | Immediate imm) { 520 | Operand operand(dst); 521 | if (modrm_opcode == 4 && imm.isUint32()) { 522 | // We can use andl for andq. 523 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 524 | emitRegisterREX(dst, REX_NONE); 525 | // Would like to use emitComplex here, but it doesn't like uint32 526 | // immediates. 527 | if (imm.isInt8()) { 528 | emitSignExtendedInt8(modrm_opcode, operand, imm); 529 | } else { 530 | if (dst == RAX) { 531 | emitUint8(0x25); 532 | } else { 533 | emitUint8(0x81); 534 | emitOperand(modrm_opcode, operand); 535 | } 536 | emitUInt32(imm.value()); 537 | } 538 | } else { 539 | DCHECK(imm.isInt32(), "assert()"); 540 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 541 | emitRegisterREX(dst, REX_W); 542 | emitComplex(modrm_opcode, operand, imm); 543 | } 544 | } 545 | 546 | void Assembler::aluQ(uint8_t modrm_opcode, uint8_t /*opcode*/, Address dst, 547 | Immediate imm) { 548 | DCHECK(imm.isInt32(), "assert()"); 549 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 550 | emitOperandREX(modrm_opcode, dst, REX_W); 551 | emitComplex(modrm_opcode, dst, imm); 552 | } 553 | 554 | void Assembler::cqo() { 555 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 556 | emitRegisterREX(RAX, REX_W); 557 | emitUint8(0x99); 558 | } 559 | 560 | void Assembler::emitUnaryQ(Register reg, int opcode, int modrm_code) { 561 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 562 | emitRegisterREX(reg, REX_W); 563 | emitUint8(opcode); 564 | emitOperand(modrm_code, Operand(reg)); 565 | } 566 | 567 | void Assembler::emitUnaryL(Register reg, int opcode, int modrm_code) { 568 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 569 | emitRegisterREX(reg, REX_NONE); 570 | emitUint8(opcode); 571 | emitOperand(modrm_code, Operand(reg)); 572 | } 573 | 574 | void Assembler::emitUnaryQ(Address address, int opcode, int modrm_code) { 575 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 576 | Operand operand(address); 577 | emitOperandREX(modrm_code, operand, REX_W); 578 | emitUint8(opcode); 579 | emitOperand(modrm_code, operand); 580 | } 581 | 582 | void Assembler::emitUnaryL(Address address, int opcode, int modrm_code) { 583 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 584 | Operand operand(address); 585 | emitOperandREX(modrm_code, operand, REX_NONE); 586 | emitUint8(opcode); 587 | emitOperand(modrm_code, operand); 588 | } 589 | 590 | void Assembler::imull(Register reg, Immediate imm) { 591 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 592 | Operand operand(reg); 593 | emitOperandREX(reg, operand, REX_NONE); 594 | emitUint8(0x69); 595 | emitOperand(reg & 7, Operand(reg)); 596 | emitImmediate(imm); 597 | } 598 | 599 | void Assembler::shll(Register reg, Immediate imm) { 600 | emitGenericShift(false, 4, reg, imm); 601 | } 602 | 603 | void Assembler::shll(Register operand, Register shifter) { 604 | emitGenericShift(false, 4, operand, shifter); 605 | } 606 | 607 | void Assembler::shrl(Register reg, Immediate imm) { 608 | emitGenericShift(false, 5, reg, imm); 609 | } 610 | 611 | void Assembler::shrl(Register operand, Register shifter) { 612 | emitGenericShift(false, 5, operand, shifter); 613 | } 614 | 615 | void Assembler::sarl(Register reg, Immediate imm) { 616 | emitGenericShift(false, 7, reg, imm); 617 | } 618 | 619 | void Assembler::sarl(Register operand, Register shifter) { 620 | emitGenericShift(false, 7, operand, shifter); 621 | } 622 | 623 | void Assembler::shldl(Register dst, Register src, Immediate imm) { 624 | emitL(src, dst, 0xa4, 0x0f); 625 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 626 | DCHECK(imm.isInt8(), "assert()"); 627 | emitUint8(imm.value() & 0xff); 628 | } 629 | 630 | void Assembler::shlq(Register reg, Immediate imm) { 631 | emitGenericShift(true, 4, reg, imm); 632 | } 633 | 634 | void Assembler::shlq(Register operand, Register shifter) { 635 | emitGenericShift(true, 4, operand, shifter); 636 | } 637 | 638 | void Assembler::shrq(Register reg, Immediate imm) { 639 | emitGenericShift(true, 5, reg, imm); 640 | } 641 | 642 | void Assembler::shrq(Register operand, Register shifter) { 643 | emitGenericShift(true, 5, operand, shifter); 644 | } 645 | 646 | void Assembler::sarq(Register reg, Immediate imm) { 647 | emitGenericShift(true, 7, reg, imm); 648 | } 649 | 650 | void Assembler::sarq(Register operand, Register shifter) { 651 | emitGenericShift(true, 7, operand, shifter); 652 | } 653 | 654 | void Assembler::shldq(Register dst, Register src, Immediate imm) { 655 | emitQ(src, dst, 0xa4, 0x0f); 656 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 657 | DCHECK(imm.isInt8(), "assert()"); 658 | emitUint8(imm.value() & 0xff); 659 | } 660 | 661 | void Assembler::btq(Register base, int bit) { 662 | DCHECK(bit >= 0 && bit < 64, "assert()"); 663 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 664 | Operand operand(base); 665 | emitOperandREX(4, operand, bit >= 32 ? REX_W : REX_NONE); 666 | emitUint8(0x0f); 667 | emitUint8(0xba); 668 | emitOperand(4, operand); 669 | emitUint8(bit); 670 | } 671 | 672 | void Assembler::enter(Immediate imm) { 673 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 674 | emitUint8(0xc8); 675 | DCHECK(imm.isUint16(), "assert()"); 676 | emitUint8(imm.value() & 0xff); 677 | emitUint8((imm.value() >> 8) & 0xff); 678 | emitUint8(0x00); 679 | } 680 | 681 | void Assembler::nop(int size) { 682 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 683 | // There are nops up to size 15, but for now just provide up to size 8. 684 | DCHECK(0 < size && size <= kMaxNopSize, "assert()"); 685 | switch (size) { 686 | case 1: 687 | emitUint8(0x90); 688 | break; 689 | case 2: 690 | emitUint8(0x66); 691 | emitUint8(0x90); 692 | break; 693 | case 3: 694 | emitUint8(0x0f); 695 | emitUint8(0x1f); 696 | emitUint8(0x00); 697 | break; 698 | case 4: 699 | emitUint8(0x0f); 700 | emitUint8(0x1f); 701 | emitUint8(0x40); 702 | emitUint8(0x00); 703 | break; 704 | case 5: 705 | emitUint8(0x0f); 706 | emitUint8(0x1f); 707 | emitUint8(0x44); 708 | emitUint8(0x00); 709 | emitUint8(0x00); 710 | break; 711 | case 6: 712 | emitUint8(0x66); 713 | emitUint8(0x0f); 714 | emitUint8(0x1f); 715 | emitUint8(0x44); 716 | emitUint8(0x00); 717 | emitUint8(0x00); 718 | break; 719 | case 7: 720 | emitUint8(0x0f); 721 | emitUint8(0x1f); 722 | emitUint8(0x80); 723 | emitUint8(0x00); 724 | emitUint8(0x00); 725 | emitUint8(0x00); 726 | emitUint8(0x00); 727 | break; 728 | case 8: 729 | emitUint8(0x0f); 730 | emitUint8(0x1f); 731 | emitUint8(0x84); 732 | emitUint8(0x00); 733 | emitUint8(0x00); 734 | emitUint8(0x00); 735 | emitUint8(0x00); 736 | emitUint8(0x00); 737 | break; 738 | default: 739 | UNIMPLEMENTED("default"); 740 | } 741 | } 742 | 743 | void Assembler::nops(int size) { 744 | DCHECK(size >= 0, "Can't emit negative nops"); 745 | if (size == 0) return; 746 | while (size > kMaxNopSize) { 747 | nop(kMaxNopSize); 748 | size -= kMaxNopSize; 749 | } 750 | nop(size); 751 | } 752 | 753 | void Assembler::ud2() { 754 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 755 | emitUint8(0x0f); 756 | emitUint8(0x0b); 757 | } 758 | 759 | void Assembler::jcc(Condition condition, Label* label, bool near) { 760 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 761 | if (label->isBound()) { 762 | static const int short_size = 2; 763 | static const int long_size = 6; 764 | word offset = label->position() - buffer_.size(); 765 | DCHECK(offset <= 0, "assert()"); 766 | if (Utils::fits(offset - short_size)) { 767 | emitUint8(0x70 + condition); 768 | emitUint8((offset - short_size) & 0xff); 769 | } else { 770 | emitUint8(0x0f); 771 | emitUint8(0x80 + condition); 772 | emitInt32(offset - long_size); 773 | } 774 | } else if (near) { 775 | emitUint8(0x70 + condition); 776 | emitNearLabelLink(label); 777 | } else { 778 | emitUint8(0x0f); 779 | emitUint8(0x80 + condition); 780 | emitLabelLink(label); 781 | } 782 | } 783 | 784 | void Assembler::jmp(Label* label, bool near) { 785 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 786 | if (label->isBound()) { 787 | static const int short_size = 2; 788 | static const int long_size = 5; 789 | word offset = label->position() - buffer_.size(); 790 | DCHECK(offset <= 0, "assert()"); 791 | if (Utils::fits(offset - short_size)) { 792 | emitUint8(0xeb); 793 | emitUint8((offset - short_size) & 0xff); 794 | } else { 795 | emitUint8(0xe9); 796 | emitInt32(offset - long_size); 797 | } 798 | } else if (near) { 799 | emitUint8(0xeb); 800 | emitNearLabelLink(label); 801 | } else { 802 | emitUint8(0xe9); 803 | emitLabelLink(label); 804 | } 805 | } 806 | 807 | void Assembler::bind(Label* label) { 808 | word bound = buffer_.size(); 809 | DCHECK(!label->isBound(), "assert()"); // Labels can only be bound once. 810 | while (label->isLinked()) { 811 | word position = label->linkPosition(); 812 | word next = buffer_.load(position); 813 | buffer_.store(position, bound - (position + 4)); 814 | label->position_ = next; 815 | } 816 | while (label->hasNear()) { 817 | word position = label->nearPosition(); 818 | word offset = bound - (position + 1); 819 | DCHECK(Utils::fits(offset), "assert()"); 820 | buffer_.store(position, offset); 821 | } 822 | label->bindTo(bound); 823 | } 824 | 825 | void Assembler::comment(const char* format, ...) { 826 | char comment_buffer[1024]; 827 | va_list args; 828 | ::va_start(args, format); 829 | std::vsnprintf(comment_buffer, sizeof comment_buffer, format, args); 830 | ::va_end(args); 831 | comments_.add(new CodeComment(buffer_.getPosition(), comment_buffer)); 832 | } 833 | 834 | void Assembler::align(int alignment) { 835 | DCHECK(Utils::isPowerOfTwo(alignment), "assert()"); 836 | word pos = buffer_.getPosition(); 837 | int mod = pos & (alignment - 1); 838 | if (mod == 0) { 839 | return; 840 | } 841 | word bytes_needed = alignment - mod; 842 | while (bytes_needed > kMaxNopSize) { 843 | nop(kMaxNopSize); 844 | bytes_needed -= kMaxNopSize; 845 | } 846 | if (bytes_needed) { 847 | nop(bytes_needed); 848 | } 849 | DCHECK((buffer_.getPosition() & (alignment - 1)) == 0, "assert()"); 850 | } 851 | 852 | void Assembler::emitOperand(int rm, Operand operand) { 853 | DCHECK(rm >= 0 && rm < 8, "assert()"); 854 | const word length = operand.length_; 855 | DCHECK(length > 0, "assert()"); 856 | // emit the ModRM byte updated with the given RM value. 857 | DCHECK((operand.encoding_[0] & 0x38) == 0, "assert()"); 858 | emitUint8(operand.encoding_[0] + (rm << 3)); 859 | // emit the rest of the encoded operand. 860 | for (word i = 1; i < length; i++) { 861 | emitUint8(operand.encoding_[i]); 862 | } 863 | } 864 | 865 | void Assembler::emitRegisterOperand(int rm, int reg) { 866 | Operand operand; 867 | operand.setModRM(3, static_cast(reg)); 868 | emitOperand(rm, operand); 869 | } 870 | 871 | void Assembler::emitImmediate(Immediate imm) { 872 | if (imm.isInt32()) { 873 | emitInt32(static_cast(imm.value())); 874 | } else { 875 | emitInt64(imm.value()); 876 | } 877 | } 878 | 879 | void Assembler::emitSignExtendedInt8(int rm, Operand operand, 880 | Immediate immediate) { 881 | emitUint8(0x83); 882 | emitOperand(rm, operand); 883 | emitUint8(immediate.value() & 0xff); 884 | } 885 | 886 | void Assembler::emitComplexB(int rm, Operand operand, Immediate imm) { 887 | DCHECK(rm >= 0 && rm < 8, "assert()"); 888 | DCHECK(imm.isUint8() || imm.isInt8(), "immediate too large"); 889 | if (operand.hasRegister(RAX)) { 890 | // Use short form if the destination is al. 891 | emitUint8(0x04 + (rm << 3)); 892 | } else { 893 | emitUint8(0x80); 894 | emitOperand(rm, operand); 895 | } 896 | emitUint8(imm.value()); 897 | } 898 | 899 | void Assembler::emitComplex(int rm, Operand operand, Immediate immediate) { 900 | DCHECK(rm >= 0 && rm < 8, "assert()"); 901 | DCHECK(immediate.isInt32(), "assert()"); 902 | if (immediate.isInt8()) { 903 | emitSignExtendedInt8(rm, operand, immediate); 904 | } else if (operand.hasRegister(RAX)) { 905 | // Use short form if the destination is rax. 906 | emitUint8(0x05 + (rm << 3)); 907 | emitImmediate(immediate); 908 | } else { 909 | emitUint8(0x81); 910 | emitOperand(rm, operand); 911 | emitImmediate(immediate); 912 | } 913 | } 914 | 915 | void Assembler::emitLabel(Label* label, word instruction_size) { 916 | if (label->isBound()) { 917 | word offset = label->position() - buffer_.size(); 918 | DCHECK(offset <= 0, "assert()"); 919 | emitInt32(offset - instruction_size); 920 | } else { 921 | emitLabelLink(label); 922 | } 923 | } 924 | 925 | void Assembler::emitLabelLink(Label* label) { 926 | DCHECK(!label->isBound(), "assert()"); 927 | word position = buffer_.size(); 928 | emitInt32(label->position_); 929 | label->linkTo(position); 930 | } 931 | 932 | void Assembler::emitNearLabelLink(Label* label) { 933 | DCHECK(!label->isBound(), "assert()"); 934 | word position = buffer_.size(); 935 | emitUint8(0); 936 | label->nearLinkTo(position); 937 | } 938 | 939 | void Assembler::emitGenericShift(bool wide, int rm, Register reg, 940 | Immediate imm) { 941 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 942 | DCHECK(imm.isInt8(), "assert()"); 943 | if (wide) { 944 | emitRegisterREX(reg, REX_W); 945 | } else { 946 | emitRegisterREX(reg, REX_NONE); 947 | } 948 | if (imm.value() == 1) { 949 | emitUint8(0xd1); 950 | emitOperand(rm, Operand(reg)); 951 | } else { 952 | emitUint8(0xc1); 953 | emitOperand(rm, Operand(reg)); 954 | emitUint8(imm.value() & 0xff); 955 | } 956 | } 957 | 958 | void Assembler::emitGenericShift(bool wide, int rm, Register operand, 959 | Register shifter) { 960 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); 961 | DCHECK(shifter == RCX, "assert()"); 962 | emitRegisterREX(operand, wide ? REX_W : REX_NONE); 963 | emitUint8(0xd3); 964 | emitOperand(rm, Operand(operand)); 965 | } 966 | 967 | } // namespace x64 968 | } // namespace dis 969 | -------------------------------------------------------------------------------- /dis/assembler-x64.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) */ 2 | // Copyright (c) 2013, the Dart project authors and Facebook, Inc. and its 3 | // affiliates. Please see the AUTHORS-Dart file for details. All rights 4 | // reserved. Use of this source code is governed by a BSD-style license that 5 | // can be found in the LICENSE-Dart file. 6 | 7 | #pragma once 8 | 9 | #include "assembler-utils.h" 10 | #include "globals.h" 11 | #include "utils.h" 12 | 13 | namespace dis { 14 | 15 | namespace x64 { 16 | 17 | enum Register { 18 | RAX = 0, 19 | RCX = 1, 20 | RDX = 2, 21 | RBX = 3, 22 | RSP = 4, 23 | RBP = 5, 24 | RSI = 6, 25 | RDI = 7, 26 | R8 = 8, 27 | R9 = 9, 28 | R10 = 10, 29 | R11 = 11, 30 | R12 = 12, 31 | R13 = 13, 32 | R14 = 14, 33 | R15 = 15, 34 | kNoRegister = -1, // Signals an illegal register. 35 | kNumRegisters = 16, 36 | }; 37 | 38 | enum XmmRegister { 39 | XMM0 = 0, 40 | XMM1 = 1, 41 | XMM2 = 2, 42 | XMM3 = 3, 43 | XMM4 = 4, 44 | XMM5 = 5, 45 | XMM6 = 6, 46 | XMM7 = 7, 47 | XMM8 = 8, 48 | XMM9 = 9, 49 | XMM10 = 10, 50 | XMM11 = 11, 51 | XMM12 = 12, 52 | XMM13 = 13, 53 | XMM14 = 14, 54 | XMM15 = 15, 55 | kNoXmmRegister = -1, // Signals an illegal register. 56 | kNumXmmRegisters = 16, 57 | }; 58 | 59 | enum RexBits { 60 | REX_NONE = 0, 61 | REX_B = 1 << 0, 62 | REX_X = 1 << 1, 63 | REX_R = 1 << 2, 64 | REX_W = 1 << 3, 65 | REX_PREFIX = 1 << 6 66 | }; 67 | 68 | enum Prefix { 69 | LOCK = 0xf0, 70 | REPNZ = 0xf2, 71 | REP = 0xf3, 72 | }; 73 | 74 | enum Condition { 75 | // This weird name avoids conflicts with the OVERFLOW macro in math.h on some 76 | // platforms. 77 | YES_OVERFLOW = 0, 78 | NOT_OVERFLOW = 1, 79 | BELOW = 2, 80 | ABOVE_EQUAL = 3, 81 | EQUAL = 4, 82 | NOT_EQUAL = 5, 83 | BELOW_EQUAL = 6, 84 | ABOVE = 7, 85 | SIGN = 8, 86 | NOT_SIGN = 9, 87 | PARITY = 10, 88 | NOT_PARITY = 11, 89 | LESS = 12, 90 | GREATER_EQUAL = 13, 91 | LESS_EQUAL = 14, 92 | GREATER = 15, 93 | 94 | ZERO = EQUAL, 95 | NOT_ZERO = NOT_EQUAL, 96 | CARRY = BELOW, 97 | NOT_CARRY = ABOVE_EQUAL, 98 | PARITY_EVEN = PARITY, 99 | PARITY_ODD = NOT_PARITY, 100 | 101 | // Platform-independent variants declared for all platforms 102 | UNSIGNED_LESS = BELOW, 103 | UNSIGNED_LESS_EQUAL = BELOW_EQUAL, 104 | UNSIGNED_GREATER = ABOVE, 105 | UNSIGNED_GREATER_EQUAL = ABOVE_EQUAL, 106 | 107 | INVALID_CONDITION = -1 108 | }; 109 | 110 | inline Condition invert(Condition cond) { 111 | switch (cond) { 112 | case BELOW: 113 | return ABOVE_EQUAL; 114 | case LESS: 115 | return GREATER_EQUAL; 116 | default: 117 | UNREACHABLE("unknown condition"); 118 | } 119 | } 120 | 121 | enum ScaleFactor { 122 | TIMES_1 = 0, 123 | TIMES_2 = 1, 124 | TIMES_4 = 2, 125 | TIMES_8 = 3, 126 | TIMES_16 = 4, 127 | }; 128 | 129 | // The largest multibyte nop we will emit. This could go up to 15 if it 130 | // becomes important to us. 131 | const int kMaxNopSize = 8; 132 | 133 | class Immediate { 134 | public: 135 | explicit Immediate(int64_t value) : value_(value) {} 136 | 137 | Immediate(const Immediate& other) : value_(other.value_) {} 138 | 139 | int64_t value() const { return value_; } 140 | 141 | bool isInt8() const { return Utils::fits(value_); } 142 | bool isUint8() const { return Utils::fits(value_); } 143 | bool isInt16() const { return Utils::fits(value_); } 144 | bool isUint16() const { return Utils::fits(value_); } 145 | bool isInt32() const { return Utils::fits(value_); } 146 | bool isUint32() const { return Utils::fits(value_); } 147 | 148 | private: 149 | const int64_t value_; 150 | }; 151 | 152 | class Operand { 153 | public: 154 | uint8_t rex() const { return rex_; } 155 | 156 | uint8_t mod() const { return (encodingAt(0) >> 6) & 3; } 157 | 158 | Register rm() const { 159 | int rm_rex = (rex_ & REX_B) << 3; 160 | return static_cast(rm_rex + (encodingAt(0) & 7)); 161 | } 162 | 163 | ScaleFactor scale() const { 164 | return static_cast((encodingAt(1) >> 6) & 3); 165 | } 166 | 167 | Register index() const { 168 | int index_rex = (rex_ & REX_X) << 2; 169 | return static_cast(index_rex + ((encodingAt(1) >> 3) & 7)); 170 | } 171 | 172 | Register base() const { 173 | int base_rex = (rex_ & REX_B) << 3; 174 | return static_cast(base_rex + (encodingAt(1) & 7)); 175 | } 176 | 177 | int8_t disp8() const { 178 | DCHECK(length_ >= 2, "assert()"); 179 | return static_cast(encoding_[length_ - 1]); 180 | } 181 | 182 | int32_t disp32() const { 183 | DCHECK(length_ >= 5, "assert()"); 184 | int32_t result; 185 | std::memcpy(&result, &encoding_[length_ - 4], sizeof(result)); 186 | return result; 187 | } 188 | 189 | Operand(const Operand& other) : length_(other.length_), rex_(other.rex_) { 190 | std::memmove(&encoding_[0], &other.encoding_[0], other.length_); 191 | } 192 | 193 | Operand& operator=(const Operand& other) { 194 | length_ = other.length_; 195 | rex_ = other.rex_; 196 | memmove(&encoding_[0], &other.encoding_[0], other.length_); 197 | return *this; 198 | } 199 | 200 | bool operator==(const Operand& other) const { 201 | if (length_ != other.length_) return false; 202 | if (rex_ != other.rex_) return false; 203 | for (uint8_t i = 0; i < length_; i++) { 204 | if (encoding_[i] != other.encoding_[i]) return false; 205 | } 206 | return true; 207 | } 208 | 209 | protected: 210 | Operand() : length_(0), rex_(REX_NONE) {} // Needed by subclass Address. 211 | 212 | void setModRM(int mod, Register rm) { 213 | DCHECK((mod & ~3) == 0, "assert()"); 214 | if ((rm > 7) && !((rm == R12) && (mod != 3))) { 215 | rex_ |= REX_B; 216 | } 217 | encoding_[0] = (mod << 6) | (rm & 7); 218 | length_ = 1; 219 | } 220 | 221 | void setSIB(ScaleFactor scale, Register index, Register base) { 222 | DCHECK(length_ == 1, "assert()"); 223 | DCHECK((scale & ~3) == 0, "assert()"); 224 | if (base > 7) { 225 | DCHECK((rex_ & REX_B) == 0, 226 | "assert()"); // Must not have REX.B already set. 227 | rex_ |= REX_B; 228 | } 229 | if (index > 7) rex_ |= REX_X; 230 | encoding_[1] = (scale << 6) | ((index & 7) << 3) | (base & 7); 231 | length_ = 2; 232 | } 233 | 234 | void setDisp8(int8_t disp) { 235 | DCHECK(length_ == 1 || length_ == 2, "assert()"); 236 | encoding_[length_++] = static_cast(disp); 237 | } 238 | 239 | void setDisp32(int32_t disp) { 240 | DCHECK(length_ == 1 || length_ == 2, "assert()"); 241 | memmove(&encoding_[length_], &disp, sizeof(disp)); 242 | length_ += sizeof(disp); 243 | } 244 | 245 | private: 246 | uint8_t length_; 247 | uint8_t rex_; 248 | uint8_t encoding_[6]; 249 | 250 | explicit Operand(Register reg) : rex_(REX_NONE) { setModRM(3, reg); } 251 | 252 | // Get the operand encoding byte at the given index. 253 | uint8_t encodingAt(word index) const { 254 | DCHECK_BOUND(index, length_); 255 | return encoding_[index]; 256 | } 257 | 258 | // Returns whether or not this register is a direct register operand 259 | // referencing a specific register. Used from the assembler to generate better 260 | // encodings. 261 | bool hasRegister(Register reg) const { 262 | return isRegister() && this->reg() == reg; 263 | } 264 | 265 | // Returns whether or not this operand represents a direct register operand. 266 | bool isRegister() const { 267 | return (encodingAt(0) & 0xf8) == 0xc0; // mod bits of ModR/M 268 | } 269 | 270 | // Returns the register represented by the rm field of this operand. 271 | Register reg() const { 272 | DCHECK(isRegister(), "reg() called on non-register Operand"); 273 | return static_cast( 274 | (encodingAt(0) & 0x7) | // r/m bits of ModR/M 275 | ((rex_ & REX_B) ? 0x4 : 0x0)); // REX.B extension 276 | } 277 | 278 | friend class Assembler; 279 | }; 280 | 281 | class Address : public Operand { 282 | public: 283 | Address(Register base, int32_t disp) { 284 | if ((disp == 0) && ((base & 7) != RBP)) { 285 | setModRM(0, base); 286 | if ((base & 7) == RSP) { 287 | setSIB(TIMES_1, RSP, base); 288 | } 289 | } else if (Utils::fits(disp)) { 290 | setModRM(1, base); 291 | if ((base & 7) == RSP) { 292 | setSIB(TIMES_1, RSP, base); 293 | } 294 | setDisp8(disp); 295 | } else { 296 | setModRM(2, base); 297 | if ((base & 7) == RSP) { 298 | setSIB(TIMES_1, RSP, base); 299 | } 300 | setDisp32(disp); 301 | } 302 | } 303 | 304 | Address(Register index, ScaleFactor scale, int32_t disp) { 305 | DCHECK(index != RSP, "Illegal addressing mode"); 306 | setModRM(0, RSP); 307 | setSIB(scale, index, RBP); 308 | setDisp32(disp); 309 | } 310 | 311 | Address(Register base, Register index, ScaleFactor scale, int32_t disp) { 312 | DCHECK(index != RSP, "Illegal addressing mode"); 313 | if ((disp == 0) && ((base & 7) != RBP)) { 314 | setModRM(0, RSP); 315 | setSIB(scale, index, base); 316 | } else if (Utils::fits(disp)) { 317 | setModRM(1, RSP); 318 | setSIB(scale, index, base); 319 | setDisp8(disp); 320 | } else { 321 | setModRM(2, RSP); 322 | setSIB(scale, index, base); 323 | setDisp32(disp); 324 | } 325 | } 326 | 327 | Address(const Address& other) : Operand(other) {} 328 | 329 | Address& operator=(const Address& other) { 330 | Operand::operator=(other); 331 | return *this; 332 | } 333 | 334 | static Address addressRIPRelative(int32_t disp) { 335 | return Address(RIPRelativeDisp(disp)); 336 | } 337 | static Address addressBaseImm32(Register base, int32_t disp) { 338 | return Address(base, disp, ForceDisp32{}); 339 | } 340 | 341 | private: 342 | // Only used to invoke the alternate constructor below. 343 | enum class ForceDisp32 {}; 344 | 345 | Address(Register base, int32_t disp, ForceDisp32) { 346 | setModRM(2, base); 347 | if ((base & 7) == RSP) { 348 | setSIB(TIMES_1, RSP, base); 349 | } 350 | setDisp32(disp); 351 | } 352 | 353 | struct RIPRelativeDisp { 354 | explicit RIPRelativeDisp(int32_t disp) : disp_(disp) {} 355 | const int32_t disp_; 356 | }; 357 | 358 | explicit Address(const RIPRelativeDisp& disp) { 359 | setModRM(0, static_cast(0x5)); 360 | setDisp32(disp.disp_); 361 | } 362 | }; 363 | 364 | class Assembler { 365 | public: 366 | Assembler() = default; 367 | 368 | static const bool kNearJump = true; 369 | static const bool kFarJump = false; 370 | 371 | word codeSize() const { return buffer_.size(); } 372 | 373 | uword codeAddress(word offset) { return buffer_.address(offset); } 374 | 375 | void finalizeInstructions(MemoryRegion instructions) { 376 | buffer_.finalizeInstructions(instructions); 377 | } 378 | 379 | void call(Register reg) { emitUnaryL(reg, 0xff, 2); } 380 | void call(Address address) { emitUnaryL(address, 0xff, 2); } 381 | void call(Label* label); 382 | 383 | void pushq(Register reg); 384 | void pushq(Address address) { emitUnaryL(address, 0xff, 6); } 385 | void pushq(Immediate imm); 386 | 387 | void popq(Register reg); 388 | void popq(Address address) { emitUnaryL(address, 0x8f, 0); } 389 | 390 | void setcc(Condition condition, Register dst); 391 | 392 | #define X86_ZERO_OPERAND_1_BYTE_INSTRUCTIONS(F) \ 393 | F(ret, 0xc3) \ 394 | F(leave, 0xc9) \ 395 | F(hlt, 0xf4) \ 396 | F(cld, 0xfc) \ 397 | F(std, 0xfd) \ 398 | F(int3, 0xcc) \ 399 | F(pushad, 0x60) \ 400 | F(popad, 0x61) \ 401 | F(pushfd, 0x9c) \ 402 | F(popfd, 0x9d) \ 403 | F(sahf, 0x9e) \ 404 | F(cdq, 0x99) \ 405 | F(fwait, 0x9b) \ 406 | F(cmpsb, 0xa6) \ 407 | F(cmpsl, 0xa7) 408 | 409 | #define X86_ALU_CODES(F) \ 410 | F(and, 4) \ 411 | F(or, 1) \ 412 | F(xor, 6) \ 413 | F(add, 0) \ 414 | F(adc, 2) \ 415 | F(sub, 5) \ 416 | F(sbb, 3) \ 417 | F(cmp, 7) 418 | 419 | #define XMM_ALU_CODES(F) \ 420 | F(bad0, 0) \ 421 | F(sqrt, 1) \ 422 | F(rsqrt, 2) \ 423 | F(rcp, 3) \ 424 | F(and, 4) \ 425 | F(bad1, 5) \ 426 | F(or, 6) \ 427 | F(xor, 7) \ 428 | F(add, 8) \ 429 | F(mul, 9) \ 430 | F(bad2, 0xa) \ 431 | F(bad3, 0xb) \ 432 | F(sub, 0xc) \ 433 | F(min, 0xd) \ 434 | F(div, 0xe) \ 435 | F(max, 0xf) 436 | 437 | // Table 3-1, first part 438 | #define XMM_CONDITIONAL_CODES(F) \ 439 | F(eq, 0) \ 440 | F(lt, 1) \ 441 | F(le, 2) \ 442 | F(unord, 3) \ 443 | F(neq, 4) \ 444 | F(nlt, 5) \ 445 | F(nle, 6) \ 446 | F(ord, 7) 447 | 448 | #define X86_CONDITIONAL_SUFFIXES(F) \ 449 | F(o, YES_OVERFLOW) \ 450 | F(no, NOT_OVERFLOW) \ 451 | F(c, CARRY) \ 452 | F(nc, NOT_CARRY) \ 453 | F(z, ZERO) \ 454 | F(nz, NOT_ZERO) \ 455 | F(na, BELOW_EQUAL) \ 456 | F(a, ABOVE) \ 457 | F(s, SIGN) \ 458 | F(ns, NOT_SIGN) \ 459 | F(pe, PARITY_EVEN) \ 460 | F(po, PARITY_ODD) \ 461 | F(l, LESS) \ 462 | F(ge, GREATER_EQUAL) \ 463 | F(le, LESS_EQUAL) \ 464 | F(g, GREATER) \ 465 | /* Some alternative names */ \ 466 | F(e, EQUAL) \ 467 | F(ne, NOT_EQUAL) 468 | 469 | // Register-register, register-address and address-register instructions. 470 | #define RR(width, name, ...) \ 471 | void name(Register dst, Register src) { emit##width(dst, src, __VA_ARGS__); } 472 | #define RA(width, name, ...) \ 473 | void name(Register dst, Address src) { emit##width(dst, src, __VA_ARGS__); } 474 | #define AR(width, name, ...) \ 475 | void name(Address dst, Register src) { emit##width(src, dst, __VA_ARGS__); } 476 | #define REGULAR_INSTRUCTION(name, ...) \ 477 | RA(W, name##w, __VA_ARGS__) \ 478 | RA(L, name##l, __VA_ARGS__) \ 479 | RA(Q, name##q, __VA_ARGS__) \ 480 | RR(W, name##w, __VA_ARGS__) \ 481 | RR(L, name##l, __VA_ARGS__) \ 482 | RR(Q, name##q, __VA_ARGS__) 483 | REGULAR_INSTRUCTION(test, 0x85) 484 | REGULAR_INSTRUCTION(xchg, 0x87) 485 | REGULAR_INSTRUCTION(imul, 0xaf, 0x0f) 486 | REGULAR_INSTRUCTION(bsr, 0xbd, 0x0f) 487 | #undef REGULAR_INSTRUCTION 488 | RA(Q, movsxd, 0x63) 489 | RR(Q, movsxd, 0x63) 490 | AR(B, movb, 0x88) 491 | AR(L, movl, 0x89) 492 | AR(Q, movq, 0x89) 493 | AR(W, movw, 0x89) 494 | RA(B, movb, 0x8a) 495 | RA(L, movl, 0x8b) 496 | RA(Q, movq, 0x8b) 497 | RR(L, movl, 0x8b) 498 | RA(Q, leaq, 0x8d) 499 | RA(L, leal, 0x8d) 500 | AR(L, cmpxchgl, 0xb1, 0x0f) 501 | AR(Q, cmpxchgq, 0xb1, 0x0f) 502 | RA(L, cmpxchgl, 0xb1, 0x0f) 503 | RA(Q, cmpxchgq, 0xb1, 0x0f) 504 | RR(L, cmpxchgl, 0xb1, 0x0f) 505 | RR(Q, cmpxchgq, 0xb1, 0x0f) 506 | RA(L, movzbl, 0xb6, 0x0f) 507 | RR(L, movzbl, 0xb6, 0x0f) 508 | RA(Q, movzbq, 0xb6, 0x0f) 509 | RR(Q, movzbq, 0xb6, 0x0f) 510 | RA(Q, movzwq, 0xb7, 0x0f) 511 | RR(Q, movzwq, 0xb7, 0x0f) 512 | RA(Q, movsbq, 0xbe, 0x0f) 513 | RR(Q, movsbq, 0xbe, 0x0f) 514 | RA(Q, movswq, 0xbf, 0x0f) 515 | RR(Q, movswq, 0xbf, 0x0f) 516 | #define DECLARE_CMOV(name, code) \ 517 | RR(Q, cmov##name##q, 0x40 + code, 0x0f) \ 518 | RR(L, cmov##name##l, 0x40 + code, 0x0f) \ 519 | RA(Q, cmov##name##q, 0x40 + code, 0x0f) \ 520 | RA(L, cmov##name##l, 0x40 + code, 0x0f) 521 | X86_CONDITIONAL_SUFFIXES(DECLARE_CMOV) 522 | #undef DECLARE_CMOV 523 | #undef AA 524 | #undef RA 525 | #undef AR 526 | 527 | #define SIMPLE(name, ...) \ 528 | void name() { emitSimple(__VA_ARGS__); } 529 | SIMPLE(cpuid, 0x0f, 0xa2) 530 | SIMPLE(fcos, 0xd9, 0xff) 531 | SIMPLE(fincstp, 0xd9, 0xf7) 532 | SIMPLE(fsin, 0xd9, 0xfe) 533 | #undef SIMPLE 534 | // XmmRegister operations with another register or an address. 535 | #define XX(width, name, ...) \ 536 | void name(XmmRegister dst, XmmRegister src) { \ 537 | emit##width(dst, src, __VA_ARGS__); \ 538 | } 539 | #define XA(width, name, ...) \ 540 | void name(XmmRegister dst, Address src) { \ 541 | emit##width(dst, src, __VA_ARGS__); \ 542 | } 543 | #define AX(width, name, ...) \ 544 | void name(Address dst, XmmRegister src) { \ 545 | emit##width(src, dst, __VA_ARGS__); \ 546 | } 547 | // We could add movupd here, but movups does the same and is shorter. 548 | XA(L, movups, 0x10, 0x0f); 549 | XA(L, movsd, 0x10, 0x0f, 0xf2) 550 | XA(L, movss, 0x10, 0x0f, 0xf3) 551 | AX(L, movups, 0x11, 0x0f); 552 | AX(L, movsd, 0x11, 0x0f, 0xf2) 553 | AX(L, movss, 0x11, 0x0f, 0xf3) 554 | XX(L, movhlps, 0x12, 0x0f) 555 | XX(L, unpcklps, 0x14, 0x0f) 556 | XX(L, unpcklpd, 0x14, 0x0f, 0x66) 557 | XX(L, unpckhps, 0x15, 0x0f) 558 | XX(L, unpckhpd, 0x15, 0x0f, 0x66) 559 | XX(L, movlhps, 0x16, 0x0f) 560 | XX(L, movaps, 0x28, 0x0f) 561 | XX(L, comisd, 0x2f, 0x0f, 0x66) 562 | #define DECLARE_XMM(name, code) \ 563 | XX(L, name##ps, 0x50 + code, 0x0f) \ 564 | XA(L, name##ps, 0x50 + code, 0x0f) \ 565 | AX(L, name##ps, 0x50 + code, 0x0f) \ 566 | XX(L, name##pd, 0x50 + code, 0x0f, 0x66) \ 567 | XA(L, name##pd, 0x50 + code, 0x0f, 0x66) \ 568 | AX(L, name##pd, 0x50 + code, 0x0f, 0x66) \ 569 | XX(L, name##sd, 0x50 + code, 0x0f, 0xf2) \ 570 | XA(L, name##sd, 0x50 + code, 0x0f, 0xf2) \ 571 | AX(L, name##sd, 0x50 + code, 0x0f, 0xf2) \ 572 | XX(L, name##ss, 0x50 + code, 0x0f, 0xf3) \ 573 | XA(L, name##ss, 0x50 + code, 0x0f, 0xf3) \ 574 | AX(L, name##ss, 0x50 + code, 0x0f, 0xf3) 575 | XMM_ALU_CODES(DECLARE_XMM) 576 | #undef DECLARE_XMM 577 | XX(L, cvtps2pd, 0x5a, 0x0f) 578 | XX(L, cvtpd2ps, 0x5a, 0x0f, 0x66) 579 | XX(L, cvtsd2ss, 0x5a, 0x0f, 0xf2) 580 | XX(L, cvtss2sd, 0x5a, 0x0f, 0xf3) 581 | XX(L, pxor, 0xef, 0x0f, 0x66) 582 | XX(L, subpl, 0xfa, 0x0f, 0x66) 583 | XX(L, addpl, 0xfe, 0x0f, 0x66) 584 | #undef XX 585 | #undef AX 586 | #undef XA 587 | 588 | #define DECLARE_CMPPS(name, code) \ 589 | void cmpps##name(XmmRegister dst, XmmRegister src) { \ 590 | emitL(dst, src, 0xc2, 0x0f); \ 591 | AssemblerBuffer::EnsureCapacity ensured(&buffer_); \ 592 | emitUint8(code); \ 593 | } 594 | XMM_CONDITIONAL_CODES(DECLARE_CMPPS) 595 | #undef DECLARE_CMPPS 596 | 597 | #define DECLARE_SIMPLE(name, opcode) \ 598 | void name() { emitSimple(opcode); } 599 | X86_ZERO_OPERAND_1_BYTE_INSTRUCTIONS(DECLARE_SIMPLE) 600 | #undef DECLARE_SIMPLE 601 | 602 | void movl(Register dst, Immediate imm); 603 | void movl(Address dst, Immediate imm); 604 | 605 | void movb(Address dst, Immediate imm); 606 | 607 | void movw(Register dst, Address src); 608 | void movw(Address dst, Immediate imm); 609 | 610 | // RIP-relative lea 611 | void leaq(Register dst, Label* label); 612 | void movq(Register dst, Immediate imm); 613 | void movq(Address dst, Immediate imm); 614 | 615 | // Destination and source are reversed for some reason. 616 | void movq(Register dst, XmmRegister src) { 617 | emitQ(src, dst, 0x7e, 0x0f, 0x66); 618 | } 619 | void movl(Register dst, XmmRegister src) { 620 | emitL(src, dst, 0x7e, 0x0f, 0x66); 621 | } 622 | void movss(XmmRegister dst, XmmRegister src) { 623 | emitL(src, dst, 0x11, 0x0f, 0xf3); 624 | } 625 | void movsd(XmmRegister dst, XmmRegister src) { 626 | emitL(src, dst, 0x11, 0x0f, 0xf2); 627 | } 628 | 629 | // Use the reversed operand order and the 0x89 bytecode instead of the 630 | // obvious 0x88 encoding for this some, because it is expected by gdb older 631 | // than 7.3.1 when disassembling a function's prologue (movq rbp, rsp). 632 | void movq(Register dst, Register src) { emitQ(src, dst, 0x89); } 633 | 634 | void movq(XmmRegister dst, Register src) { 635 | emitQ(dst, src, 0x6e, 0x0f, 0x66); 636 | } 637 | 638 | void movd(XmmRegister dst, Register src) { 639 | emitL(dst, src, 0x6e, 0x0f, 0x66); 640 | } 641 | void cvtsi2sdq(XmmRegister dst, Register src) { 642 | emitQ(dst, src, 0x2a, 0x0f, 0xf2); 643 | } 644 | void cvtsi2sdl(XmmRegister dst, Register src) { 645 | emitL(dst, src, 0x2a, 0x0f, 0xf2); 646 | } 647 | void cvttsd2siq(Register dst, XmmRegister src) { 648 | emitQ(dst, src, 0x2c, 0x0f, 0xf2); 649 | } 650 | void cvttsd2sil(Register dst, XmmRegister src) { 651 | emitL(dst, src, 0x2c, 0x0f, 0xf2); 652 | } 653 | void movmskpd(Register dst, XmmRegister src) { 654 | emitL(dst, src, 0x50, 0x0f, 0x66); 655 | } 656 | void movmskps(Register dst, XmmRegister src) { emitL(dst, src, 0x50, 0x0f); } 657 | 658 | void movsb(); 659 | void movsl(); 660 | void movsq(); 661 | void movsw(); 662 | void repMovsb(); 663 | void repMovsl(); 664 | void repMovsq(); 665 | void repMovsw(); 666 | void repnzMovsb(); 667 | void repnzMovsl(); 668 | void repnzMovsq(); 669 | void repnzMovsw(); 670 | 671 | void btl(Register dst, Register src) { emitL(src, dst, 0xa3, 0x0f); } 672 | void btq(Register dst, Register src) { emitQ(src, dst, 0xa3, 0x0f); } 673 | 674 | void notps(XmmRegister dst, XmmRegister src); 675 | void negateps(XmmRegister dst, XmmRegister src); 676 | void absps(XmmRegister dst, XmmRegister src); 677 | void zerowps(XmmRegister dst, XmmRegister src); 678 | 679 | void set1ps(XmmRegister dst, Register tmp, Immediate imm); 680 | void shufps(XmmRegister dst, XmmRegister src, Immediate mask); 681 | 682 | void negatepd(XmmRegister dst, XmmRegister src); 683 | void abspd(XmmRegister dst, XmmRegister src); 684 | void shufpd(XmmRegister dst, XmmRegister src, Immediate mask); 685 | 686 | enum RoundingMode { 687 | kRoundToNearest = 0x0, 688 | kRoundDown = 0x1, 689 | kRoundUp = 0x2, 690 | kRoundToZero = 0x3 691 | }; 692 | void roundsd(XmmRegister dst, XmmRegister src, RoundingMode mode); 693 | 694 | void testb(Register dst, Register src); 695 | void testb(Register reg, Immediate imm); 696 | void testb(Address address, Immediate imm); 697 | void testb(Address address, Register reg); 698 | 699 | void testl(Register reg, Immediate imm) { testq(reg, imm); } 700 | 701 | // TODO(T47100904): These functions will emit a testl or a testb when possible 702 | // based on the value of `imm`. This behavior is desired in most cases, but 703 | // probably belongs in a differently-named function. 704 | void testq(Register reg, Immediate imm); 705 | void testq(Address address, Immediate imm); 706 | 707 | void shldq(Register dst, Register src, Register shifter) { 708 | DCHECK(shifter == RCX, "assert()"); 709 | emitQ(src, dst, 0xa5, 0x0f); 710 | } 711 | void shrdq(Register dst, Register src, Register shifter) { 712 | DCHECK(shifter == RCX, "assert()"); 713 | emitQ(src, dst, 0xad, 0x0f); 714 | } 715 | 716 | #define DECLARE_ALU(op, c) \ 717 | void op##w(Register dst, Register src) { emitW(dst, src, c * 8 + 3); } \ 718 | void op##l(Register dst, Register src) { emitL(dst, src, c * 8 + 3); } \ 719 | void op##q(Register dst, Register src) { emitQ(dst, src, c * 8 + 3); } \ 720 | void op##w(Register dst, Address src) { emitW(dst, src, c * 8 + 3); } \ 721 | void op##l(Register dst, Address src) { emitL(dst, src, c * 8 + 3); } \ 722 | void op##q(Register dst, Address src) { emitQ(dst, src, c * 8 + 3); } \ 723 | void op##w(Address dst, Register src) { emitW(src, dst, c * 8 + 1); } \ 724 | void op##l(Address dst, Register src) { emitL(src, dst, c * 8 + 1); } \ 725 | void op##q(Address dst, Register src) { emitQ(src, dst, c * 8 + 1); } \ 726 | void op##l(Register dst, Immediate imm) { aluL(c, dst, imm); } \ 727 | void op##q(Register dst, Immediate imm) { aluQ(c, c * 8 + 3, dst, imm); } \ 728 | void op##b(Register dst, Immediate imm) { aluB(c, dst, imm); } \ 729 | void op##b(Address dst, Immediate imm) { aluB(c, dst, imm); } \ 730 | void op##w(Address dst, Immediate imm) { aluW(c, dst, imm); } \ 731 | void op##l(Address dst, Immediate imm) { aluL(c, dst, imm); } \ 732 | void op##q(Address dst, Immediate imm) { aluQ(c, c * 8 + 3, dst, imm); } 733 | 734 | X86_ALU_CODES(DECLARE_ALU) 735 | 736 | #undef DECLARE_ALU 737 | #undef ALU_OPS 738 | 739 | void cqo(); 740 | 741 | #define REGULAR_UNARY(name, opcode, modrm) \ 742 | void name##q(Register reg) { emitUnaryQ(reg, opcode, modrm); } \ 743 | void name##l(Register reg) { emitUnaryL(reg, opcode, modrm); } \ 744 | void name##q(Address address) { emitUnaryQ(address, opcode, modrm); } \ 745 | void name##l(Address address) { emitUnaryL(address, opcode, modrm); } 746 | REGULAR_UNARY(not, 0xf7, 2) 747 | REGULAR_UNARY(neg, 0xf7, 3) 748 | REGULAR_UNARY(mul, 0xf7, 4) 749 | REGULAR_UNARY(div, 0xf7, 6) 750 | REGULAR_UNARY(idiv, 0xf7, 7) 751 | REGULAR_UNARY(inc, 0xff, 0) 752 | REGULAR_UNARY(dec, 0xff, 1) 753 | #undef REGULAR_UNARY 754 | 755 | void imull(Register reg, Immediate imm); 756 | void imulq(Register dst, Immediate imm); 757 | 758 | void shll(Register reg, Immediate imm); 759 | void shll(Register operand, Register shifter); 760 | void shrl(Register reg, Immediate imm); 761 | void shrl(Register operand, Register shifter); 762 | void sarl(Register reg, Immediate imm); 763 | void sarl(Register operand, Register shifter); 764 | void shldl(Register dst, Register src, Immediate imm); 765 | 766 | void shlq(Register reg, Immediate imm); 767 | void shlq(Register operand, Register shifter); 768 | void shrq(Register reg, Immediate imm); 769 | void shrq(Register operand, Register shifter); 770 | void sarq(Register reg, Immediate imm); 771 | void sarq(Register operand, Register shifter); 772 | void shldq(Register dst, Register src, Immediate imm); 773 | 774 | void btq(Register base, int bit); 775 | 776 | void enter(Immediate imm); 777 | 778 | void fldl(Address src); 779 | void fstpl(Address dst); 780 | 781 | void ffree(word value); 782 | 783 | // 'size' indicates size in bytes and must be in the range 1..8. 784 | void nop(int size = 1); 785 | 786 | // 'size' may be arbitrarily large, and multiple nops will be used if needed. 787 | void nops(int size); 788 | 789 | void ud2(); 790 | 791 | void jcc(Condition condition, Label* label, bool near); 792 | void jmp(Register reg) { emitUnaryL(reg, 0xff, 4); } 793 | void jmp(Address address) { emitUnaryL(address, 0xff, 4); } 794 | void jmp(Label* label, bool near); 795 | 796 | void lockCmpxchgq(Address address, Register reg) { 797 | emitUint8(LOCK); 798 | cmpxchgq(address, reg); 799 | } 800 | 801 | void lockCmpxchgl(Address address, Register reg) { 802 | emitUint8(LOCK); 803 | cmpxchgl(address, reg); 804 | } 805 | 806 | void align(int alignment); 807 | void bind(Label* label); 808 | 809 | void comment(const char* format, ...); 810 | 811 | // Debugging and bringup support. 812 | void breakpoint() { int3(); } 813 | 814 | static void initializeMemoryWithBreakpoints(uword data, word length); 815 | 816 | private: 817 | void aluB(uint8_t modrm_opcode, Register dst, Immediate imm); 818 | void aluL(uint8_t modrm_opcode, Register dst, Immediate imm); 819 | void aluB(uint8_t modrm_opcode, Address dst, Immediate imm); 820 | void aluW(uint8_t modrm_opcode, Address dst, Immediate imm); 821 | void aluL(uint8_t modrm_opcode, Address dst, Immediate imm); 822 | void aluQ(uint8_t modrm_opcode, uint8_t opcode, Register dst, Immediate imm); 823 | void aluQ(uint8_t modrm_opcode, uint8_t opcode, Address dst, Immediate imm); 824 | 825 | void emitSimple(int opcode, int opcode2 = -1); 826 | void emitUnaryQ(Register reg, int opcode, int modrm_code); 827 | void emitUnaryL(Register reg, int opcode, int modrm_code); 828 | void emitUnaryQ(Address address, int opcode, int modrm_code); 829 | void emitUnaryL(Address address, int opcode, int modrm_code); 830 | // The prefixes are in reverse order due to the rules of default arguments in 831 | // C++. 832 | void emitQ(int reg, Address address, int opcode, int prefix2 = -1, 833 | int prefix1 = -1); 834 | void emitL(int reg, Address address, int opcode, int prefix2 = -1, 835 | int prefix1 = -1); 836 | void emitB(Register reg, Address address, int opcode, int prefix2 = -1, 837 | int prefix1 = -1); 838 | void emitW(Register reg, Address address, int opcode, int prefix2 = -1, 839 | int prefix1 = -1); 840 | void emitQ(int dst, int src, int opcode, int prefix2 = -1, int prefix1 = -1); 841 | void emitL(int dst, int src, int opcode, int prefix2 = -1, int prefix1 = -1); 842 | void emitW(Register dst, Register src, int opcode, int prefix2 = -1, 843 | int prefix1 = -1); 844 | void cmpPS(XmmRegister dst, XmmRegister src, int condition); 845 | void emitTestB(Operand operand, Immediate imm); 846 | void emitTestQ(Operand operand, Immediate imm); 847 | 848 | void emitUint8(uint8_t value); 849 | void emitInt32(int32_t value); 850 | void emitUInt32(uint32_t value); 851 | void emitInt64(int64_t value); 852 | 853 | static uint8_t byteRegisterREX(Register reg); 854 | static uint8_t byteOperandREX(Operand operand); 855 | 856 | void emitRegisterREX(Register reg, uint8_t rex); 857 | void emitOperandREX(int rm, Operand operand, uint8_t rex); 858 | void emitRegisterOperand(int rm, int reg); 859 | void emitFixup(AssemblerFixup* fixup); 860 | void emitOperandSizeOverride(); 861 | void emitRegRegREX(int reg, int base, uint8_t rex = REX_NONE); 862 | void emitOperand(int rm, Operand operand); 863 | void emitImmediate(Immediate imm); 864 | void emitComplexB(int rm, Operand operand, Immediate imm); 865 | void emitComplex(int rm, Operand operand, Immediate immediate); 866 | void emitSignExtendedInt8(int rm, Operand operand, Immediate immediate); 867 | void emitLabel(Label* label, word instruction_size); 868 | void emitLabelLink(Label* label); 869 | void emitNearLabelLink(Label* label); 870 | 871 | void emitGenericShift(bool wide, int rm, Register reg, Immediate imm); 872 | void emitGenericShift(bool wide, int rm, Register operand, Register shifter); 873 | 874 | AssemblerBuffer buffer_; // Contains position independent code. 875 | CodeComments comments_; 876 | 877 | DISALLOW_COPY_AND_ASSIGN(Assembler); 878 | }; 879 | 880 | inline void Assembler::emitUint8(uint8_t value) { 881 | buffer_.emit(value); 882 | } 883 | 884 | inline void Assembler::emitInt32(int32_t value) { 885 | buffer_.emit(value); 886 | } 887 | 888 | inline void Assembler::emitUInt32(uint32_t value) { 889 | buffer_.emit(value); 890 | } 891 | 892 | inline void Assembler::emitInt64(int64_t value) { 893 | buffer_.emit(value); 894 | } 895 | 896 | inline uint8_t Assembler::byteRegisterREX(Register reg) { 897 | // SPL, BPL, SIL, or DIL require a REX prefix. 898 | return reg >= RSP && reg <= RDI ? REX_PREFIX : REX_NONE; 899 | } 900 | 901 | inline uint8_t Assembler::byteOperandREX(Operand operand) { 902 | return operand.isRegister() ? byteRegisterREX(operand.reg()) 903 | : uint8_t{REX_NONE}; 904 | } 905 | 906 | inline void Assembler::emitRegisterREX(Register reg, uint8_t rex) { 907 | DCHECK(reg != kNoRegister && reg <= R15, "assert()"); 908 | DCHECK(rex == REX_NONE || rex == REX_W, "assert()"); 909 | rex |= (reg > 7 ? REX_B : REX_NONE); 910 | if (rex != REX_NONE) emitUint8(REX_PREFIX | rex); 911 | } 912 | 913 | inline void Assembler::emitOperandREX(int rm, Operand operand, uint8_t rex) { 914 | rex |= (rm > 7 ? REX_R : REX_NONE) | operand.rex(); 915 | if (rex != REX_NONE) emitUint8(REX_PREFIX | rex); 916 | } 917 | 918 | inline void Assembler::emitRegRegREX(int reg, int base, uint8_t rex) { 919 | DCHECK(reg != kNoRegister && reg <= R15, "assert()"); 920 | DCHECK(base != kNoRegister && base <= R15, "assert()"); 921 | DCHECK(rex == REX_NONE || rex == REX_W || rex == REX_PREFIX, "assert()"); 922 | if (reg > 7) rex |= REX_R; 923 | if (base > 7) rex |= REX_B; 924 | if (rex != REX_NONE) emitUint8(REX_PREFIX | rex); 925 | } 926 | 927 | inline void Assembler::emitFixup(AssemblerFixup* fixup) { 928 | buffer_.emitFixup(fixup); 929 | } 930 | 931 | inline void Assembler::emitOperandSizeOverride() { emitUint8(0x66); } 932 | 933 | } // namespace x64 934 | 935 | } // namespace dis 936 | -------------------------------------------------------------------------------- /post.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Destination-driven code generation 3 | --- 4 | 5 | I saw [Phil Eaton][eatonphil] post about this paper, [Destination-Driven Code 6 | Generation][ddcg-paper] (PDF) on Twitter. He posted about how the V8 team used 7 | it in the early days to do really fast but still half-decent codegen for their 8 | compiler. It sounded really interesting, so I skimmed the paper, saw a lot of 9 | greek symbols, and bailed out. Then I saw that Phil had posted [a 10 | talk][ddcg-talk] (PDF) by a V8 engineer that laid out the paper's core ideas 11 | really clearly. To the paper's credit, the actual text is extremely clear. I 12 | think I just wasn't ready for Greek letters that day. 13 | 14 | [eatonphil]: https://twitter.com/phil_eaton/ 15 | [ddcg-paper]: https://legacy.cs.indiana.edu/~dyb/pubs/ddcg.pdf 16 | [ddcg-talk]: https://raw.githubusercontent.com/eatonphil/one-pass-code-generation-in-v8/main/One-pass%20Code%20Generation%20in%20V8.pdf 17 | 18 | In any case, while V8 ostensibly used this strategy in their early codegen, I 19 | couldn't find their implementation, nor could I find any other 20 | implementation[^jsc] of this paper. So I decided to [write 21 | one](https://github.com/tekknolagi/ddcg)! 22 | 23 | [^jsc]: I was writing this post, I discovered Phil's 24 | [implementation](https://github.com/eatonphil/jsc) of this paper in his 25 | ahead-of-time JavaScript compiler, *jsc* that compiles JS to Node C-API 26 | calls. Note that this is a different `jsc` than Apple's 27 | [JavaScriptCore](https://github.com/WebKit/WebKit/tree/main/Source/JavaScriptCore), 28 | the runtime that powers WebKit. 29 | 30 | Then I decided to look even deeper into the V8 source history for all 31 | commits by the author of the talk, Kevin Millikin, and eventually found 32 | [this commit](https://github.com/v8/v8/commit/1528bf7240586d876d2deef18d1e1b4302866c0b). 33 | 34 | It appears to be the cutover point between the "classic" compiler and the 35 | [Crankshaft][crankshaft][^crankshaft] compiler---and the classic compiler 36 | is the one referenced in the talk! See [codegen-x64.h][codegen-x64.h] and 37 | [codegen-x64.cc][codegen-x64.cc] for more info. 38 | 39 | [crankshaft]: https://blog.chromium.org/2010/12/new-crankshaft-for-v8.html 40 | [codegen-x64.h]: https://github.com/v8/v8/blob/1528bf7240586d876d2deef18d1e1b4302866c0b/src/x64/codegen-x64.h 41 | [codegen-x64.cc]: https://github.com/v8/v8/blob/1528bf7240586d876d2deef18d1e1b4302866c0b/src/x64/codegen-x64.cc 42 | 43 | [^crankshaft]: See also Andy Wingo's [closer look at 44 | Crankshaft][wingo-crankshaft] and Jay Conrod's [tour of V8: 45 | Crankshaft][conrod-crankshaft]. 46 | 47 | [wingo-crankshaft]: https://wingolog.org/archives/2011/08/02/a-closer-look-at-crankshaft-v8s-optimizing-compiler 48 | [conrod-crankshaft]: https://www.jayconrod.com/posts/54/a-tour-of-v8-crankshaft-the-optimizing-compiler 49 | 50 | I followed along with the talk in C++. I decided to write a reference 51 | interpreter first to help me nail down the expression types and their expected 52 | behavior---and then wrote a small test suite to exercise it and find bugs. I 53 | didn't get so far as writing a full parser or anything[^tiger-impl], just the 54 | abstract syntax tree (AST) types[^unique-ptr]. We'll start with them. 55 | 56 | [^tiger-impl]: I have half of a small [Tiger][tiger] implementation in C++ 57 | sitting around, which includes a reasonable homemade lexer and parser. I 58 | could probably repurpose those for the blog post if people want a full 59 | end-to-end playground for this code generation style. 60 | 61 | [tiger]: https://www.cs.princeton.edu/~appel/modern/ 62 | 63 | [^unique-ptr]: I initially thought about using `std::unique_ptr` to manage the 64 | AST ownership here, but it kind of gets in the way of what is really just 65 | an elaborate test harness. Then I thought about bump allocating the nodes, 66 | since I already have a small bump allocator that I wrote for Cinder. Then I 67 | realized that that bump allocator didn't work for heterogeneous types (the 68 | Cinder allocator only ever allocated one type per allocator). Then I looked 69 | at `std::align` for a bit and decided I didn't care so much. So here we 70 | are, with a bunch of leaks. Ah well. 71 | 72 | ## Datatypes 73 | 74 | Right. Let's look at some ASTs. There are not so many, and I try to match them 75 | up with the types shown in the talk. There are five types of expression: 76 | 77 | * integer literal 78 | * integer add 79 | * variable load 80 | * variable store 81 | * less-than comparison 82 | 83 | and an abstract base class. 84 | 85 | ```c++ 86 | // Shorthand for a machine word's worth of data. 87 | typedef intptr_t word; 88 | 89 | enum class ExprType { 90 | kIntLit, 91 | kAddExpr, 92 | kVarRef, 93 | kVarAssign, 94 | kLessThan, 95 | }; 96 | 97 | struct Expr { 98 | explicit Expr(ExprType type) : type(type) {} 99 | ExprType type; 100 | }; 101 | 102 | struct IntLit : public Expr { 103 | explicit IntLit(word value) : Expr(ExprType::kIntLit), value(value) {} 104 | word value; 105 | }; 106 | 107 | struct AddExpr : public Expr { 108 | explicit AddExpr(Expr* left, Expr* right) 109 | : Expr(ExprType::kAddExpr), left(left), right(right) {} 110 | Expr* left; 111 | Expr* right; 112 | }; 113 | 114 | struct VarRef : public Expr { 115 | explicit VarRef(word offset) : Expr(ExprType::kVarRef), offset(offset) {} 116 | word offset; 117 | }; 118 | 119 | struct VarAssign : public Expr { 120 | explicit VarAssign(VarRef* left, Expr* right) 121 | : Expr(ExprType::kVarAssign), left(left), right(right) {} 122 | VarRef* left; 123 | Expr* right; 124 | }; 125 | 126 | struct LessThan : public Expr { 127 | explicit LessThan(Expr* left, Expr* right) 128 | : Expr(ExprType::kLessThan), left(left), right(right) {} 129 | Expr* left; 130 | Expr* right; 131 | }; 132 | ``` 133 | 134 | It's kind of weird that variable assignment is an expression, but since it 135 | returns a value (the right-hand side of the assignment), I suppose it makes 136 | sense. That seems to be [how C does it][c99-spec] (PDF), according to section 137 | 6.5.16 (page 91)[^thanks-gurity]: 138 | 139 | [c99-spec]: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf 140 | 141 | [^thanks-gurity]: Thanks, Gurity, for your 142 | [StackOverflow answer](https://stackoverflow.com/a/25578647/569183). 143 | 144 | ``` 145 | assignment-expression: 146 | conditional-expression 147 | unary-expression assignment-operator assignment-expression 148 | ``` 149 | 150 | The expressions are what you might expect from a little language. For example, 151 | `v0 = v1 + v2` corresponds to the C++ AST 152 | 153 | ```c++ 154 | new VarAssign(new VarRef(0), new AddExpr(new VarRef(1), new VarRef(2))) 155 | ``` 156 | 157 | The statements are a little different. Unlike expressions, which return values, 158 | statements return no values and are only there for effect and sequencing. 159 | 160 | We have three kinds: 161 | 162 | * expression statements (`expr;`) 163 | * block statements (`{ a; b; }`) 164 | * and if-statements (`if (a) { ...} else { ... }`) 165 | 166 | ```c++ 167 | enum class StmtType { 168 | kExpr, 169 | kBlock, 170 | kIf, 171 | }; 172 | 173 | struct Stmt { 174 | explicit Stmt(StmtType type) : type(type) {} 175 | StmtType type; 176 | }; 177 | 178 | struct ExprStmt : public Stmt { 179 | explicit ExprStmt(Expr* expr) : Stmt(StmtType::kExpr), expr(expr) {} 180 | Expr* expr; 181 | }; 182 | 183 | struct BlockStmt : public Stmt { 184 | explicit BlockStmt(const std::vector& body) 185 | : Stmt(StmtType::kBlock), body(body) {} 186 | std::vector body; 187 | }; 188 | 189 | struct IfStmt : public Stmt { 190 | explicit IfStmt(Expr* cond, Stmt* cons, Stmt* alt) 191 | : Stmt(StmtType::kIf), cond(cond), cons(cons), alt(alt) {} 192 | Expr* cond; 193 | Stmt* cons; 194 | Stmt* alt; 195 | }; 196 | ``` 197 | 198 | I went a little terse on the variable naming: `cond` is short for "condition", 199 | the if-expression in the parentheses; `cons` is short for "consequent", the 200 | if-true case; `alt` is short for "alternate", the if-false case. I feel like I 201 | heard these terms first in a Lisp scenario. 202 | 203 | The talk assumes code generation, but since I am also writing an interpreter, I 204 | added a little data structure I call `State` that holds an array of slots for 205 | variables. This would normally be part of the native call frame, probably an 206 | array starting at the base pointer (RBP). 207 | 208 | ```c++ 209 | constexpr word kNumVars = 26; 210 | 211 | struct State { 212 | State set(word offset, word value) const { 213 | State result = *this; 214 | result.vars[offset] = value; 215 | return result; 216 | } 217 | // operator== is used for testing only, to check for the desired variable 218 | // assignment effects 219 | bool operator==(const State& other) { 220 | for (word i = 0; i < kNumVars; i++) { 221 | if (vars[i] != other.vars[i]) { 222 | return false; 223 | } 224 | } 225 | return true; 226 | } 227 | word vars[kNumVars] = {}; 228 | }; 229 | ``` 230 | 231 | I didn't really want to go through and figure out how many variables were 232 | needed in the AST, so I fixed the number of variables at 26. I had a vague 233 | notion that if I were writing a little parser, I could use single-letter 234 | variable names and cleanly map those to indices... but that never materialized. 235 | 236 | ## Interpreter 237 | 238 | So! Now we have our datatypes. Let's think about how we want to structure our 239 | interpreter. In abstract, we have two functions: one to evaluate expressions 240 | and one to evaluate statements. Both need a `State` because both can have side 241 | effects. 242 | 243 | ```c++ 244 | class Evaluator { 245 | public: 246 | virtual word interpret(State* state, const Expr* expr) = 0; 247 | virtual void interpret(State* state, const Stmt* stmt) = 0; 248 | }; 249 | ``` 250 | 251 | Our first evaluator is a little tree-walk interpreter. It implements both 252 | functions. Here's the expression evaluator: 253 | 254 | ```c++ 255 | class Interpreter : public Evaluator { 256 | public: 257 | virtual word interpret(State* state, const Expr* expr) { 258 | switch (expr->type) { 259 | case ExprType::kIntLit: { 260 | return static_cast(expr)->value; 261 | } 262 | case ExprType::kAddExpr: { 263 | auto add = static_cast(expr); 264 | word left = interpret(state, add->left); 265 | word right = interpret(state, add->right); 266 | return left + right; 267 | } 268 | case ExprType::kVarRef: { 269 | return state->vars[static_cast(expr)->offset]; 270 | } 271 | case ExprType::kVarAssign: { 272 | auto assign = static_cast(expr); 273 | word result = interpret(state, assign->right); 274 | state->vars[assign->left->offset] = result; 275 | return result; 276 | } 277 | case ExprType::kLessThan: { 278 | auto less = static_cast(expr); 279 | word left = interpret(state, less->left); 280 | word right = interpret(state, less->right); 281 | return left < right; 282 | } 283 | default: { 284 | UNREACHABLE("unsupported expr type"); 285 | break; 286 | } 287 | } 288 | } 289 | // ... 290 | }; 291 | ``` 292 | 293 | Note that `interpret(State*, Expr*)` returns a `word` because expressions have 294 | values. `interpret(State*, Stmt*)`, on the other hand, returns no values: 295 | 296 | ```c++ 297 | class Interpreter : public Evaluator { 298 | public: 299 | // ... 300 | virtual void interpret(State* state, const Stmt* stmt) { 301 | switch (stmt->type) { 302 | case StmtType::kExpr: { 303 | interpret(state, static_cast(stmt)->expr); 304 | break; 305 | } 306 | case StmtType::kBlock: { 307 | auto block = static_cast(stmt); 308 | for (size_t i = 0; i < block->body.size(); i++) { 309 | interpret(state, block->body[i]); 310 | } 311 | break; 312 | } 313 | case StmtType::kIf: { 314 | auto if_ = static_cast(stmt); 315 | word result = interpret(state, if_->cond); 316 | if (result) { 317 | interpret(state, if_->cons); 318 | } else { 319 | interpret(state, if_->alt); 320 | } 321 | break; 322 | } 323 | default: { 324 | UNREACHABLE("unsupported stmt type"); 325 | break; 326 | } 327 | } 328 | } 329 | }; 330 | ``` 331 | 332 | There are no compound conditionals, but if you were to add them, the 333 | interpreter and test suite would be a good place to specify the semantics. (Is 334 | `and` eager? Short-circuiting? etc) 335 | 336 | ## Tests 337 | 338 | Now, a good test-driven-development (TDD) practitioner would have written tests 339 | first and implementation second. But I am not that and I did not do that. So 340 | here we arrive at the tests after writing the implementation. 341 | 342 | Each expression test is comprised of a beginning `State`, an `Expr` to 343 | evaluate, and the expected result from evaluating the expression. 344 | 345 | ```c++ 346 | struct ExprTest { 347 | State state; 348 | Expr* expr; 349 | word expected; 350 | }; 351 | ``` 352 | 353 | There is a beginning state so we can test the implementation of `VarRef` 354 | independently of the implementation of `VarAssign`; otherwise, we would need to 355 | use both at the same time. 356 | 357 | ```c++ 358 | int main() { 359 | ExprTest expr_tests[] = { 360 | {State{}, new IntLit(123), 123}, 361 | {State{}, new AddExpr(new IntLit(123), new IntLit(456)), 579}, 362 | {State{}.set(3, 123), new VarRef(3), 123}, 363 | {State{}, new VarAssign(new VarRef(3), new IntLit(123)), 123}, 364 | {State{}, new LessThan(new IntLit(1), new IntLit(2)), 1}, 365 | {State{}, new LessThan(new IntLit(2), new IntLit(2)), 0}, 366 | {State{}, new LessThan(new IntLit(3), new IntLit(2)), 0}, 367 | {State{}, nullptr, 0}, // Sentinel 368 | }; 369 | // ... 370 | } 371 | ``` 372 | 373 | The statement tests are similar, but simpler: each test has a `Stmt` and an 374 | expected end `State`. There is no beginning `State` supplied. 375 | 376 | ```c++ 377 | struct StmtTest { 378 | Stmt* stmt; 379 | State expected; 380 | }; 381 | ``` 382 | 383 | I suppose we could have re-used the same test struct, since expressions can 384 | have side effects too. Hmm. I'll think about making this simpler later. 385 | 386 | There are some more `Stmt` tests than `Expr` because of the interactions 387 | between multiple statements. `IfStmt` in particular has a bunch of cases. 388 | 389 | ```c++ 390 | int main() { 391 | // ... 392 | StmtTest stmt_tests[] = { 393 | {new ExprStmt(new IntLit(123)), State{}}, 394 | {new ExprStmt(new VarAssign(new VarRef(3), new IntLit(123))), 395 | State{}.set(3, 123)}, 396 | {new IfStmt(new IntLit(1), 397 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(123))), 398 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(456)))), 399 | State{}.set(0, 123)}, 400 | // ... 401 | {new BlockStmt({ 402 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(123))), 403 | new ExprStmt(new VarAssign(new VarRef(1), new IntLit(456))), 404 | new ExprStmt(new VarAssign( 405 | new VarRef(2), new AddExpr(new VarRef(0), new VarRef(1)))), 406 | }), 407 | State{}.set(0, 123).set(1, 456).set(2, 123 + 456)}, 408 | {nullptr, State{}}, // Sentinel 409 | }; 410 | // ... 411 | } 412 | ``` 413 | 414 | Now that we have the tests, we can run them on our implementations. I made the 415 | test runner polymorphic from the beginning because I knew that we would have 416 | multiple implementations to test on the same test cases. 417 | 418 | The function `test_interpreter` is not very interesting (it's a for loop with a 419 | function call) so I have omitted it in the interest of brevity. Feel free to 420 | check out the implementation [in the repo](https://github.com/tekknolagi/ddcg). 421 | 422 | ```c++ 423 | int main() { 424 | // ... 425 | fprintf(stderr, "Testing interpreter (expr) "); 426 | test_interpreter(expr_tests); 427 | fprintf(stderr, "Testing interpreter (stmt) "); 428 | test_interpreter(stmt_tests); 429 | } 430 | ``` 431 | 432 | And that's it. If all went well on your end, you should see something like this 433 | in your terminal: 434 | 435 | ``` 436 | $ ./interp 437 | Testing interpreter (expr) ....... 438 | Testing interpreter (stmt) ........... 439 | $ 440 | ``` 441 | 442 | This indicates that we have laid a good base upon which we can experiment. 443 | 444 | ## Compiler 445 | 446 | Ahhh, finally onto the real meat of the post: the compiler! We'll follow the 447 | talk, meaning that we'll look at three different compilers: 448 | 449 | * the baseline compiler, with very simple code generation 450 | * the destination-driven compiler 451 | * the destination-driven compiler *with control destinations* 452 | 453 | That way we can compare results across the board. We won't look at performance 454 | (see [my commentary](/inline-caching/#performance-analysis) on performance 455 | analysis, which I should probably turn into a separate post). Instead we will 456 | look at one or two assembly listings across the different implementations and 457 | note the differences. 458 | 459 | To organize the implementations, we have an abstract JIT that does a lot of the 460 | assembler and code management. It also has some utility functions like `varAt` 461 | that put together the memory addressing mode for a given variable index. 462 | 463 | ```c++ 464 | class JIT : public Evaluator { 465 | public: 466 | virtual word interpret(State* state, const Expr* expr) { 467 | emitPrologue(); 468 | compileExpr(expr); 469 | emitEpilogue(); 470 | MemoryRegion region = finalizeCode(&as); 471 | JitFunction function = codeAsFunction(region); 472 | word result = function(state->vars); 473 | unmapCode(region); 474 | return result; 475 | } 476 | 477 | Address varAt(word index) { 478 | return Address(RDI, index * sizeof(word)); 479 | } 480 | 481 | virtual void compileExpr(const Expr* expr) = 0; 482 | virtual void compileStmt(const Stmt* stmt) = 0; 483 | // ... 484 | }; 485 | ``` 486 | 487 | `compileExpr` and `compileStmt` are left as exercises for the subclasses. 488 | 489 | All of the implementations will be using a version of the [Dart][dart-sdk] 490 | assembler that was ported for the [Skybison][skybison] project a couple of 491 | years ago. It's nice because it's only a couple of files but provides a C++-y 492 | x86_64 assembler interface. 493 | 494 | [dart-sdk]: https://github.com/dart-lang/sdk 495 | [skybison]: https://github.com/tekknolagi/skybison 496 | 497 | Assembling instructions from C++ will look like: 498 | 499 | ```c++ 500 | void emitAdd(Assembler* as) { 501 | __ addq(RAX, RCX); 502 | } 503 | ``` 504 | 505 | Where `__` is a shorthand for `as.` to avoid visual clutter. 506 | 507 | ### Baseline 508 | 509 | Now that we've expressed our desired behavior in the form of a test suite, we 510 | can change the implementation and maybe see if we've broken things. When I was 511 | writing the compiler, I broke a number of tests. It was really valuable to have 512 | a bunch of tests that exercise different combinations of expression types, 513 | since those can isolate compiler bugs. 514 | 515 | We'll start off with the baseline compiler, which emits very straightforward 516 | context-unaware code. Compiling `AddExpr`, for example, uses the stack heavily: 517 | 518 | * compile left hand side, store on stack 519 | * compile right hand side, store on stack 520 | * pop from stack 521 | * add 522 | * store result on stack 523 | 524 | It's very convenient to be able to treat the hardware as a stack machine. I 525 | think I got this codegen right the first time, which is *very* different from 526 | my experience with the other techniques. 527 | 528 | ```c++ 529 | class BaselineJIT : public JIT { 530 | public: 531 | virtual void compileExpr(const Expr* expr) { 532 | compileExprHelper(expr); 533 | // Return the value from the top of the stack. 534 | __ popq(RAX); 535 | } 536 | 537 | void compileExprHelper(const Expr* expr) { 538 | // RCX is caller-saved, meaning that callees (our code) can do whatever we 539 | // want with it. 540 | Register tmp = RCX; 541 | switch (expr->type) { 542 | case ExprType::kIntLit: { 543 | word value = static_cast(expr)->value; 544 | __ pushq(Immediate(value)); 545 | break; 546 | } 547 | case ExprType::kAddExpr: { 548 | auto add = static_cast(expr); 549 | compileExprHelper(add->left); 550 | compileExprHelper(add->right); 551 | __ popq(tmp); 552 | __ popq(RAX); 553 | __ addq(RAX, tmp); 554 | __ pushq(RAX); 555 | break; 556 | } 557 | case ExprType::kVarRef: { 558 | word offset = static_cast(expr)->offset; 559 | __ pushq(varAt(offset)); 560 | break; 561 | } 562 | case ExprType::kVarAssign: { 563 | auto assign = static_cast(expr); 564 | compileExprHelper(assign->right); 565 | // Leave the value on the stack for assignment chains like a = b = 1 566 | __ movq(RAX, Address(RSP, 0)); 567 | __ movq(varAt(assign->left->offset), RAX); 568 | break; 569 | } 570 | case ExprType::kLessThan: { 571 | auto less = static_cast(expr); 572 | compileExprHelper(less->left); 573 | compileExprHelper(less->right); 574 | __ popq(tmp); 575 | __ popq(RAX); 576 | __ subq(RAX, tmp); 577 | __ movq(RAX, Immediate(0)); 578 | __ setcc(LESS, RAX); 579 | __ pushq(RAX); 580 | break; 581 | } 582 | default: { 583 | UNREACHABLE("unsupported expr type"); 584 | break; 585 | } 586 | } 587 | } 588 | // ... 589 | }; 590 | ``` 591 | 592 | Let's look at a sample bit of code generated for adding two numbers: 593 | 594 | ```c++ 595 | Expr* expr = new AddExpr(new IntLit(123), new IntLit(456)); 596 | BaselineJIT jit; 597 | State state; 598 | jit.interpret(&state, expr); 599 | jit.dis(); 600 | fprintf(stderr, "code size: %ld bytes\n", jit.codeSize()); 601 | // 0x621000057900 55 push rbp 602 | // 0x621000057901 4889e5 movq rbp,rsp 603 | // 0x621000057904 6a7b push 0X7B 604 | // 0x621000057906 68c8010000 push 0X1C8 605 | // 0x62100005790B 59 pop rcx 606 | // 0x62100005790C 58 pop rax 607 | // 0x62100005790D 4803c1 addq rax,rcx 608 | // 0x621000057910 50 push rax 609 | // 0x621000057911 58 pop rax 610 | // 0x621000057912 4889ec movq rsp,rbp 611 | // 0x621000057915 5d pop rbp 612 | // 0x621000057916 c3 ret 613 | // code size: 23 bytes 614 | ``` 615 | 616 | See all of the stack motion? We push `0x7B` and `0x1C8` only to immediately pop 617 | them again---and the same thing with the result. It's all a bit silly. If I 618 | were writing this by hand, I would probably write something like: 619 | 620 | ``` 621 | mov rax, 123 622 | mov rcx, 456 623 | add rax, rcx 624 | ``` 625 | 626 | The statement compilation code is not so remarkable except for `IfStmt`. In 627 | `IfStmt`, we pull the condition off the stack and jump to either the consequent 628 | or the alternate code paths. Then they both jump or fall through to the next 629 | bit of code (`exit`). 630 | 631 | ```c++ 632 | class BaselineJIT : public JIT { 633 | public: 634 | virtual void compileStmt(const Stmt* stmt) { 635 | switch (stmt->type) { 636 | case StmtType::kExpr: { 637 | compileExprHelper(static_cast(stmt)->expr); 638 | __ popq(RAX); 639 | break; 640 | } 641 | case StmtType::kBlock: { 642 | auto block = static_cast(stmt); 643 | for (size_t i = 0; i < block->body.size(); i++) { 644 | compileStmt(block->body[i]); 645 | } 646 | break; 647 | } 648 | case StmtType::kIf: { 649 | auto if_ = static_cast(stmt); 650 | Label alt; 651 | Label exit; 652 | compileExprHelper(if_->cond); 653 | __ popq(RAX); 654 | cmpZero(RAX); 655 | __ jcc(EQUAL, &alt, Assembler::kNearJump); 656 | // true: 657 | compileStmt(if_->cons); 658 | __ jmp(&exit, Assembler::kNearJump); 659 | // false: 660 | __ bind(&alt); 661 | compileStmt(if_->alt); 662 | // exit: 663 | __ bind(&exit); 664 | break; 665 | } 666 | default: { 667 | UNREACHABLE("unsupported stmt type"); 668 | break; 669 | } 670 | } 671 | } 672 | }; 673 | ``` 674 | 675 | Which looks just fine. Sure, there's a little stack action, but that's par for 676 | the course in this compiler. Let's look at some code generated for an `IfStmt` 677 | with a `LessThan` operator: 678 | 679 | ```c++ 680 | Stmt* stmt = new IfStmt(new LessThan(new IntLit(1), new IntLit(2)), 681 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(123))), 682 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(456)))); 683 | BaselineJIT jit; 684 | State state; 685 | jit.interpret(&state, stmt); 686 | jit.dis(); 687 | fprintf(stderr, "code size: %ld bytes\n", jit.codeSize()); 688 | // 0x621000057900 55 push rbp 689 | // 0x621000057901 4889e5 movq rbp,rsp 690 | // 0x621000057904 6a01 push 1 691 | // 0x621000057906 6a02 push 2 692 | // 0x621000057908 59 pop rcx 693 | // 0x621000057909 58 pop rax 694 | // 0x62100005790A 482bc1 subq rax,rcx 695 | // 0x62100005790D b800000000 movl rax,0 696 | // 0x621000057912 0f9cc0 setll rax 697 | // 0x621000057915 50 push rax 698 | // 0x621000057916 58 pop rax 699 | // 0x621000057917 4885c0 testq rax,rax 700 | // 0x62100005791A 740c jz 0X0000621000057928 701 | // 0x62100005791C 6a7b push 0X7B 702 | // 0x62100005791E 488b0424 movq rax,[rsp] 703 | // 0x621000057922 488907 movq [rdi],rax 704 | // 0x621000057925 58 pop rax 705 | // 0x621000057926 eb0d jmp 0X0000621000057935 706 | // 0x621000057928 68c8010000 push 0X1C8 707 | // 0x62100005792D 488b0424 movq rax,[rsp] 708 | // 0x621000057931 488907 movq [rdi],rax 709 | // 0x621000057934 58 pop rax 710 | // 0x621000057935 4889ec movq rsp,rbp 711 | // 0x621000057938 5d pop rbp 712 | // 0x621000057939 c3 ret 713 | // code size: 58 bytes 714 | ``` 715 | 716 | Ah, this code is not great. In fact, it's positively enormous! Not only do we 717 | push and pop our inputs to the stack, but we also materialize the result of the 718 | comparison (`subq`) and then test *that* (`testq`) for the `IfStmt`. There is 719 | no need to do this: we should instead be able to use the flags already set by 720 | the `subq`. 721 | 722 | Let's try to figure out how to improve it. 723 | 724 | ### Destination-driven 725 | 726 | The first approach we'll try is destination-driven code generation. Just the 727 | destination, not the "control destination". Since the compiler has an idea 728 | about where it wants its result to end up, it can guide compilation of 729 | sub-expressions instead of expecting everything to end up on the stack. 730 | 731 | We have three possible destinations: the stack (as before), an accumulator 732 | (`RAX`), and something totally different, "nowhere". This helps fix the 733 | `ExprStmt`, for example, where we push data on the stack only to throw it away 734 | immediately after. 735 | 736 | ```c++ 737 | enum class Destination { 738 | kStack, 739 | kAccumulator, 740 | kNowhere, 741 | }; 742 | ``` 743 | 744 | Let's take a look at how we do this. Ignore the `plug` function for now; we'll 745 | talk about it in a minute. You can think of it as a smarter compile-time `mov` 746 | instruction. 747 | 748 | To compare implementations, let's take a look at `AddExpr`. Instead of pushing 749 | both the intermediate results on the stack, we only need to push one. The other 750 | we can store directly in `RAX`, since we know we are going to use it 751 | immediately and it's not going to get overwritten. This saves some stack 752 | traffic. 753 | 754 | ```c++ 755 | class DestinationDrivenJIT : public JIT { 756 | public: 757 | virtual void compileExpr(const Expr* expr) { 758 | compileExpr(expr, Destination::kAccumulator); 759 | } 760 | 761 | void compileExpr(const Expr* expr, Destination dest) { 762 | Register tmp = RCX; 763 | switch (expr->type) { 764 | case ExprType::kIntLit: { 765 | word value = static_cast(expr)->value; 766 | plug(dest, Immediate(value)); 767 | break; 768 | } 769 | case ExprType::kAddExpr: { 770 | auto add = static_cast(expr); 771 | compileExpr(add->left, Destination::kStack); 772 | compileExpr(add->right, Destination::kAccumulator); 773 | __ popq(tmp); 774 | __ addq(RAX, tmp); 775 | plug(dest, RAX); 776 | break; 777 | } 778 | case ExprType::kVarRef: { 779 | word offset = static_cast(expr)->offset; 780 | plug(dest, varAt(offset)); 781 | break; 782 | } 783 | case ExprType::kVarAssign: { 784 | auto assign = static_cast(expr); 785 | compileExpr(assign->right, Destination::kAccumulator); 786 | __ movq(varAt(assign->left->offset), RAX); 787 | plug(dest, RAX); 788 | break; 789 | } 790 | case ExprType::kLessThan: { 791 | auto less = static_cast(expr); 792 | compileExpr(less->left, Destination::kStack); 793 | compileExpr(less->right, Destination::kAccumulator); 794 | Label cons; 795 | Label alt; 796 | Label exit; 797 | __ popq(tmp); 798 | __ cmpq(tmp, RAX); 799 | __ jcc(GREATER_EQUAL, &alt, Assembler::kNearJump); 800 | // true: 801 | plug(dest, Immediate(1)); 802 | __ jmp(&exit, Assembler::kNearJump); 803 | // false: 804 | __ bind(&alt); 805 | plug(dest, Immediate(0)); 806 | // exit: 807 | __ bind(&exit); 808 | break; 809 | } 810 | default: { 811 | UNREACHABLE("unsupported expr type"); 812 | break; 813 | } 814 | } 815 | } 816 | // ... 817 | }; 818 | ``` 819 | 820 | Let's verify by looking at the generated code. 821 | 822 | ```c++ 823 | Expr* expr = new AddExpr(new IntLit(123), new IntLit(456)); 824 | DestinationDrivenJIT jit; 825 | State state; 826 | jit.interpret(&state, expr); 827 | jit.dis(); 828 | fprintf(stderr, "code size: %ld bytes\n", jit.codeSize()); 829 | // 0x621000057900 55 push rbp 830 | // 0x621000057901 4889e5 movq rbp,rsp 831 | // 0x621000057904 6a7b push 0X7B 832 | // 0x621000057906 b8c8010000 movl rax,0X1C8 833 | // 0x62100005790B 59 pop rcx 834 | // 0x62100005790C 4803c1 addq rax,rcx 835 | // 0x62100005790F 4889ec movq rsp,rbp 836 | // 0x621000057912 5d pop rbp 837 | // 0x621000057913 c3 ret 838 | // code size: 20 bytes 839 | ``` 840 | 841 | If you compare with the baseline code, you can see right off the bat that there 842 | is less stack activity: yes, we push `0x7B`, but we move `0x1C8` into a 843 | register, `RCX`. Then we don't push and pop the result! We just keep it in 844 | `RAX`. A huge improvement. 845 | 846 | Now let's compile some statements. See that `ExprStmt` puts its result in 847 | "nowhere". 848 | 849 | ```c++ 850 | class DestinationDrivenJIT : public JIT { 851 | public: 852 | // ... 853 | virtual void compileStmt(const Stmt* stmt) { 854 | switch (stmt->type) { 855 | case StmtType::kExpr: { 856 | compileExpr(static_cast(stmt)->expr, 857 | Destination::kNowhere); 858 | break; 859 | } 860 | case StmtType::kBlock: { 861 | auto block = static_cast(stmt); 862 | for (size_t i = 0; i < block->body.size(); i++) { 863 | compileStmt(block->body[i]); 864 | } 865 | break; 866 | } 867 | case StmtType::kIf: { 868 | auto if_ = static_cast(stmt); 869 | compileExpr(if_->cond, Destination::kAccumulator); 870 | Label alt; 871 | Label exit; 872 | cmpZero(RAX); // check if falsey 873 | __ jcc(EQUAL, &alt, Assembler::kNearJump); 874 | // true: 875 | compileStmt(if_->cons); 876 | __ jmp(&exit, Assembler::kNearJump); 877 | // false: 878 | __ bind(&alt); 879 | compileStmt(if_->alt); 880 | // exit: 881 | __ bind(&exit); 882 | break; 883 | } 884 | default: { 885 | UNREACHABLE("unsupported stmt type"); 886 | break; 887 | } 888 | } 889 | } 890 | ``` 891 | 892 | Okay, now let's talk about `plug`. This function is the connective glue between 893 | the computation. There are three functions, but really nine different variants. 894 | This would probably be one function in a language with pattern matching, but 895 | here we are. 896 | 897 | These functions are thoroughly unremarkable because they look like they do what 898 | we were doing before, but they cut out a lot of the intermediate data motion. 899 | No need to pass an immediate through the stack if we want it to end up in RAX. 900 | No need to push something to the stack only to drop it. etc. 901 | 902 | ```c++ 903 | class DestinationDrivenJIT : public JIT { 904 | public: 905 | // ... 906 | void plug(Destination dest, Immediate imm) { 907 | switch (dest) { 908 | case Destination::kStack: { 909 | __ pushq(imm); 910 | break; 911 | } 912 | case Destination::kAccumulator: { 913 | __ movq(RAX, imm); 914 | break; 915 | } 916 | case Destination::kNowhere: { 917 | // Nothing to do 918 | break; 919 | } 920 | } 921 | } 922 | 923 | void plug(Destination dest, Register reg) { 924 | // We don't (yet?) generate code that tries to move any register other than 925 | // RAX. This is not a fundamental limitation. 926 | DCHECK(reg == RAX, "unimplemented: moving non-RAX registers"); 927 | switch (dest) { 928 | case Destination::kStack: { 929 | __ pushq(reg); 930 | break; 931 | } 932 | case Destination::kAccumulator: 933 | case Destination::kNowhere: { 934 | // Nothing to do 935 | break; 936 | } 937 | } 938 | } 939 | 940 | void plug(Destination dest, Address mem) { 941 | Register tmp = RCX; 942 | switch (dest) { 943 | case Destination::kStack: { 944 | __ movq(tmp, mem); 945 | __ pushq(tmp); 946 | break; 947 | } 948 | case Destination::kAccumulator: { 949 | __ movq(RAX, mem); 950 | break; 951 | } 952 | case Destination::kNowhere: { 953 | // Nothing to do 954 | break; 955 | } 956 | } 957 | } 958 | }; 959 | ``` 960 | 961 | The generated code for the `IfStmt` example is slightly less impressive than 962 | for `AddExpr`. While it reduces the number of instructions and elides the 963 | `setcc` dance, there is still a bunch of unnecessary code. 964 | 965 | ```c++ 966 | Stmt* stmt = new IfStmt(new LessThan(new IntLit(1), new IntLit(2)), 967 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(123))), 968 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(456)))); 969 | DestinationDrivenJIT jit; 970 | State state; 971 | jit.interpret(&state, stmt); 972 | jit.dis(); 973 | fprintf(stderr, "code size: %ld bytes\n", jit.codeSize()); 974 | // 0x621000057900 55 push rbp 975 | // 0x621000057901 4889e5 movq rbp,rsp 976 | // 0x621000057904 6a01 push 1 977 | // 0x621000057906 b802000000 movl rax,2 978 | // 0x62100005790B 59 pop rcx 979 | // 0x62100005790C 483bc8 cmpq rcx,rax 980 | // 0x62100005790F 7d07 jge 0X0000621000057918 981 | // 0x621000057911 b801000000 movl rax,1 <-- result of comparison 982 | // 0x621000057916 eb05 jmp 0X000062100005791D 983 | // 0x621000057918 b800000000 movl rax,0 <-- result of comparison 984 | // 0x62100005791D 4885c0 testq rax,rax <-- why do we need this? 985 | // 0x621000057920 740a jz 0X000062100005792C 986 | // 0x621000057922 b87b000000 movl rax,0X7B 987 | // 0x621000057927 488907 movq [rdi],rax 988 | // 0x62100005792A eb08 jmp 0X0000621000057934 989 | // 0x62100005792C b8c8010000 movl rax,0X1C8 990 | // 0x621000057931 488907 movq [rdi],rax 991 | // 0x621000057934 4889ec movq rsp,rbp 992 | // 0x621000057937 5d pop rbp 993 | // 0x621000057938 c3 ret 994 | // code size: 57 bytes 995 | ``` 996 | 997 | See how we materialize the result of the comparison in `rax` and then test it? 998 | That's totally unnecessary. We already know what branch the code should take 999 | depending on the flags set by the `cmpq`. Just jump straight there. 1000 | 1001 | The good news is that we can remove the jumping around with *control 1002 | destinations*. 1003 | 1004 | ### Add control destinations 1005 | 1006 | The idea of control destinations is similar to data destinations: pass in the 1007 | available jump locations from the top down so that the inner code that does the 1008 | comparison can use them. Concretely, it is a struct containing some number of 1009 | labels. 1010 | 1011 | Millikin's talk starts off with a simple definition: a "then" (I call it 1012 | "cons") and an "else" (I call it "alt"). 1013 | 1014 | ```c++ 1015 | struct ControlDestination2 { 1016 | explicit ControlDestination2(Label* cons, Label* alt) : cons(cons), alt(alt) {} 1017 | bool isUseful() const { return cons != alt; } 1018 | Label* cons{nullptr}; 1019 | Label* alt{nullptr}; 1020 | }; 1021 | ``` 1022 | 1023 | With that, you can do quite a bit better. Not a whole lot changes except for in 1024 | the implementations of `LessThan` and `If`, so I'll show those here. 1025 | 1026 | Whereas in `DestinationDrivenJIT` the implementation of `LessThan` does 1027 | conditional assignment of `0` or `1` to a register, in 1028 | `ControlDestination2DrivenJIT` the implementation plugs in the given control 1029 | destination. 1030 | 1031 | ```c++ 1032 | class ControlDestination2DrivenJIT : public JIT { 1033 | public: 1034 | // ... 1035 | void compileExpr(const Expr* expr, Destination dest, 1036 | ControlDestination2 cdest) { 1037 | Register tmp = RCX; 1038 | switch (expr->type) { 1039 | // ... 1040 | case ExprType::kLessThan: { 1041 | auto less = static_cast(expr); 1042 | compileExpr(less->left, Destination::kStack, cdest); 1043 | compileExpr(less->right, Destination::kAccumulator, cdest); 1044 | __ popq(tmp); 1045 | __ cmpq(tmp, RAX); 1046 | plug(dest, cdest, LESS); 1047 | break; 1048 | } 1049 | // ... 1050 | } 1051 | // ... 1052 | } 1053 | } 1054 | ``` 1055 | 1056 | 1057 | 1058 | ```c++ 1059 | class ControlDestination2DrivenJIT : public JIT { 1060 | public: 1061 | virtual void compileExpr(const Expr* expr) { 1062 | Label next; 1063 | compileExpr(expr, Destination::kAccumulator, 1064 | ControlDestination2(&next, &next)); 1065 | __ bind(&next); 1066 | } 1067 | 1068 | void compileExpr(const Expr* expr, Destination dest, 1069 | ControlDestination2 cdest) { 1070 | Register tmp = RCX; 1071 | switch (expr->type) { 1072 | case ExprType::kIntLit: { 1073 | word value = static_cast(expr)->value; 1074 | plug(dest, cdest, Immediate(value)); 1075 | break; 1076 | } 1077 | case ExprType::kAddExpr: { 1078 | auto add = static_cast(expr); 1079 | compileExpr(add->left, Destination::kStack, cdest); 1080 | compileExpr(add->right, Destination::kAccumulator, cdest); 1081 | __ popq(tmp); 1082 | __ addq(RAX, tmp); 1083 | plug(dest, cdest, RAX); 1084 | break; 1085 | } 1086 | case ExprType::kVarRef: { 1087 | word offset = static_cast(expr)->offset; 1088 | plug(dest, cdest, varAt(offset)); 1089 | break; 1090 | } 1091 | case ExprType::kVarAssign: { 1092 | auto assign = static_cast(expr); 1093 | compileExpr(assign->right, Destination::kAccumulator, cdest); 1094 | __ movq(varAt(assign->left->offset), RAX); 1095 | plug(dest, cdest, RAX); 1096 | break; 1097 | } 1098 | case ExprType::kLessThan: { 1099 | auto less = static_cast(expr); 1100 | compileExpr(less->left, Destination::kStack, cdest); 1101 | compileExpr(less->right, Destination::kAccumulator, cdest); 1102 | __ popq(tmp); 1103 | __ cmpq(tmp, RAX); 1104 | plug(dest, cdest, LESS); 1105 | break; 1106 | } 1107 | default: { 1108 | UNREACHABLE("unsupported expr type"); 1109 | break; 1110 | } 1111 | } 1112 | } 1113 | 1114 | virtual void compileStmt(const Stmt* stmt) { 1115 | Label next; 1116 | compileStmt(stmt, ControlDestination2(&next, &next)); 1117 | __ bind(&next); 1118 | } 1119 | 1120 | virtual void compileStmt(const Stmt* stmt, ControlDestination2 cdest) { 1121 | switch (stmt->type) { 1122 | case StmtType::kExpr: { 1123 | compileExpr(static_cast(stmt)->expr, 1124 | Destination::kNowhere, cdest); 1125 | break; 1126 | } 1127 | case StmtType::kBlock: { 1128 | auto block = static_cast(stmt); 1129 | for (size_t i = 0; i < block->body.size(); i++) { 1130 | Label next; 1131 | compileStmt(block->body[i], ControlDestination2(&next, &next)); 1132 | __ bind(&next); 1133 | } 1134 | break; 1135 | } 1136 | case StmtType::kIf: { 1137 | auto if_ = static_cast(stmt); 1138 | Label cons; 1139 | Label alt; 1140 | Label exit; 1141 | compileExpr(if_->cond, Destination::kNowhere, 1142 | ControlDestination2(&cons, &alt)); 1143 | // true: 1144 | __ bind(&cons); 1145 | compileStmt(if_->cons, cdest); 1146 | __ jmp(&exit, Assembler::kNearJump); 1147 | // false: 1148 | __ bind(&alt); 1149 | compileStmt(if_->alt, cdest); 1150 | // exit: 1151 | __ bind(&exit); 1152 | break; 1153 | } 1154 | default: { 1155 | UNREACHABLE("unsupported stmt type"); 1156 | break; 1157 | } 1158 | } 1159 | } 1160 | 1161 | virtual void plug(Destination dest, ControlDestination2 cdest, 1162 | Condition cond) { 1163 | switch (dest) { 1164 | case Destination::kStack: { 1165 | UNREACHABLE("TODO(max): implement plug(stack, cond)"); 1166 | break; 1167 | } 1168 | case Destination::kAccumulator: { 1169 | Label materialize_true; 1170 | __ jcc(cond, &materialize_true, Assembler::kNearJump); 1171 | __ movq(RAX, Immediate(0)); 1172 | __ jmp(cdest.alt, Assembler::kNearJump); 1173 | __ bind(&materialize_true); 1174 | __ movq(RAX, Immediate(1)); 1175 | __ jmp(cdest.cons, Assembler::kNearJump); 1176 | break; 1177 | } 1178 | case Destination::kNowhere: { 1179 | __ jcc(cond, cdest.cons, Assembler::kNearJump); 1180 | __ jmp(cdest.alt, Assembler::kNearJump); 1181 | break; 1182 | } 1183 | } 1184 | } 1185 | 1186 | void plug(Destination dest, ControlDestination2 cdest, Immediate imm) { 1187 | switch (dest) { 1188 | case Destination::kStack: { 1189 | __ pushq(imm); 1190 | break; 1191 | } 1192 | case Destination::kAccumulator: { 1193 | __ movq(RAX, imm); 1194 | break; 1195 | } 1196 | case Destination::kNowhere: { 1197 | if (!cdest.isUseful()) { 1198 | // Nothing to do; not supposed to be materialized anywhere. Likely 1199 | // from an ExprStmt. 1200 | return; 1201 | } 1202 | if (imm.value()) { 1203 | __ jmp(cdest.cons, Assembler::kNearJump); 1204 | } else { 1205 | __ jmp(cdest.alt, Assembler::kNearJump); 1206 | } 1207 | break; 1208 | } 1209 | } 1210 | } 1211 | 1212 | template 1213 | void jmpTruthiness(T op, ControlDestination2 cdest) { 1214 | if (!cdest.isUseful()) { 1215 | // Sometimes the input is ControlDestination2(next, next), in which 1216 | // case there is no need at all to check the truthiness of the input. 1217 | // Nobody depends on it. 1218 | return; 1219 | } 1220 | cmpZero(op); 1221 | __ jcc(NOT_EQUAL, cdest.cons, Assembler::kNearJump); 1222 | __ jmp(cdest.alt, Assembler::kNearJump); 1223 | } 1224 | 1225 | void plug(Destination dest, ControlDestination2 cdest, Register reg) { 1226 | switch (dest) { 1227 | case Destination::kStack: { 1228 | UNREACHABLE("TODO(max): see how to generate this code"); 1229 | __ pushq(reg); 1230 | break; 1231 | } 1232 | case Destination::kAccumulator: { 1233 | // Nothing to do; reg already in RAX or it's not supposed to materialize 1234 | // anywhere 1235 | DCHECK(reg == RAX, "expect RAX to be accumulator"); 1236 | break; 1237 | } 1238 | case Destination::kNowhere: { 1239 | jmpTruthiness(reg, cdest); 1240 | break; 1241 | } 1242 | } 1243 | } 1244 | 1245 | void plug(Destination dest, ControlDestination2 cdest, Address mem) { 1246 | Register tmp = RCX; 1247 | switch (dest) { 1248 | case Destination::kStack: { 1249 | __ movq(tmp, mem); 1250 | __ pushq(tmp); 1251 | break; 1252 | } 1253 | case Destination::kAccumulator: { 1254 | __ movq(RAX, mem); 1255 | break; 1256 | } 1257 | case Destination::kNowhere: { 1258 | jmpTruthiness(mem, cdest); 1259 | break; 1260 | } 1261 | } 1262 | } 1263 | }; 1264 | ``` 1265 | 1266 | 1267 | ```c++ 1268 | struct ControlDestination3 { 1269 | explicit ControlDestination3(Label* cons, Label* alt) : cons(cons), alt(alt) {} 1270 | explicit ControlDestination3(Label* cons, Label* alt, Label* fallthrough) 1271 | : cons(cons), alt(alt), fallthrough(fallthrough) {} 1272 | bool isUseful() const { return !(cons == alt && alt == fallthrough); } 1273 | Label* cons{nullptr}; 1274 | Label* alt{nullptr}; 1275 | Label* fallthrough{nullptr}; 1276 | }; 1277 | ``` 1278 | 1279 | ## Conclusion 1280 | 1281 | See also Charlie Cummings' [excellent blog post](https://redvice.org/2023/template-jits/) 1282 | 1283 | See also push/pop with a register stack https://github.com/k0kubun/ruby-jit-challenge 1284 | 1285 | ```ruby 1286 | STACK = [:r8, :r9] 1287 | 1288 | in :putobject_INT2FIX_1_ 1289 | asm.mov(STACK[stack_size], C.to_value(1)) 1290 | stack_size += 1 1291 | ``` 1292 | 1293 | 1294 |
1295 |
1296 | 1297 | -------------------------------------------------------------------------------- /interp.cpp: -------------------------------------------------------------------------------- 1 | #include /* for assert */ 2 | #include /* for NULL */ 3 | #include /* for memcpy */ 4 | #include /* for mmap and friends */ 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "dis.h" 15 | #include "dis/assembler-x64.h" 16 | #include "dis/dcheck.h" 17 | 18 | using namespace dis; 19 | using namespace dis::x64; 20 | 21 | // TODO(max): Write parser 22 | // TODO(max): Support else-less if 23 | 24 | enum class ExprType { 25 | kIntLit, 26 | kAddExpr, 27 | kVarRef, 28 | kVarAssign, 29 | kLessThan, 30 | }; 31 | 32 | struct Expr { 33 | explicit Expr(ExprType type) : type(type) {} 34 | ExprType type; 35 | }; 36 | 37 | struct IntLit : public Expr { 38 | explicit IntLit(word value) : Expr(ExprType::kIntLit), value(value) {} 39 | word value; 40 | }; 41 | 42 | struct AddExpr : public Expr { 43 | explicit AddExpr(Expr* left, Expr* right) 44 | : Expr(ExprType::kAddExpr), left(left), right(right) {} 45 | Expr* left; 46 | Expr* right; 47 | }; 48 | 49 | struct VarRef : public Expr { 50 | explicit VarRef(word offset) : Expr(ExprType::kVarRef), offset(offset) {} 51 | word offset; 52 | }; 53 | 54 | struct VarAssign : public Expr { 55 | explicit VarAssign(VarRef* left, Expr* right) 56 | : Expr(ExprType::kVarAssign), left(left), right(right) {} 57 | VarRef* left; 58 | Expr* right; 59 | }; 60 | 61 | struct LessThan : public Expr { 62 | explicit LessThan(Expr* left, Expr* right) 63 | : Expr(ExprType::kLessThan), left(left), right(right) {} 64 | Expr* left; 65 | Expr* right; 66 | }; 67 | 68 | enum class StmtType { 69 | kExpr, 70 | kBlock, 71 | kIf, 72 | }; 73 | 74 | struct Stmt { 75 | explicit Stmt(StmtType type) : type(type) {} 76 | StmtType type; 77 | }; 78 | 79 | struct ExprStmt : public Stmt { 80 | explicit ExprStmt(Expr* expr) : Stmt(StmtType::kExpr), expr(expr) {} 81 | Expr* expr; 82 | }; 83 | 84 | struct BlockStmt : public Stmt { 85 | explicit BlockStmt(const std::vector& body) 86 | : Stmt(StmtType::kBlock), body(body) {} 87 | std::vector body; 88 | }; 89 | 90 | struct IfStmt : public Stmt { 91 | explicit IfStmt(Expr* cond, Stmt* cons, Stmt* alt) 92 | : Stmt(StmtType::kIf), cond(cond), cons(cons), alt(alt) {} 93 | Expr* cond; 94 | Stmt* cons; 95 | Stmt* alt; 96 | }; 97 | 98 | 99 | template 100 | struct ParseResult { 101 | ParseResult(T *result, const char* next) : result(result), next(next){} 102 | bool isError() const { return result == nullptr; } 103 | 104 | // Let ParseResult pretend to be a subtype of ParseResult when T is a subtype of 105 | // S. 106 | template 107 | operator const ParseResult &() const { 108 | static_assert(std::is_base_of::value, "Only up-casts are permitted"); 109 | return *reinterpret_cast*>(this); 110 | } 111 | 112 | T *result; 113 | const char *next; 114 | }; 115 | 116 | class Parser { 117 | public: 118 | ParseResult readVarAssign(const char* src) { 119 | ParseResult left = readVarRef(src); 120 | if (left.isError()) return left; 121 | src = skipws(left.next); 122 | if (match('=', &src)) { 123 | ParseResult right = readExpr(src); 124 | if (right.isError()) return right; 125 | src = right.next; 126 | return ParseResult(new VarAssign(left.result, right.result), src); 127 | } 128 | return ParseResult(left.result, src); 129 | } 130 | 131 | ParseResult readExpr(const char* src) { 132 | src = skipws(src); 133 | if (std::isdigit(*src)) return readIntLit; 134 | } 135 | 136 | ParseResult readIntLit(const char* src) { 137 | src = skipws(src); 138 | DCHECK(std::isdigit(*src), "expected number to start with digit but found %c", *src); 139 | char digits[32 + 1] = {}; 140 | int chars_read; 141 | int items_read = std::sscanf(src, "%32[0-9]%n", digits, &chars_read); 142 | DCHECK(items_read == 1, "sscanf failure"); 143 | errno = 0; 144 | word result = std::strtol(digits, nullptr, 10); 145 | DCHECK(errno == 0, "could not parse digits '%s'", digits); 146 | return ParseResult(new IntLit(result), src+chars_read); 147 | } 148 | 149 | ParseResult readVarRef(const char* src) { 150 | src = skipws(src); 151 | DCHECK(std::isalpha(*src), "expected variable name to start with letter but found %c", *src); 152 | char varname[32 + 1] = {}; 153 | int chars_read; 154 | int items_read = std::sscanf(src, "%32[a-zA-z]%n", varname, &chars_read); 155 | DCHECK(items_read == 1, "sscanf failure"); 156 | word varidx = lookupOrAdd(varname); 157 | return ParseResult(new VarRef(varidx), src+chars_read); 158 | } 159 | 160 | bool match(char c, const char** src) { 161 | if (**src == c) { 162 | *src += 1; 163 | return true; 164 | } 165 | return false; 166 | } 167 | 168 | // ParseResult readAdd(const char* src) { 169 | // ParseResult left = readOne(src); 170 | // if (left.isError()) return left; 171 | // src = skipws(left.next); 172 | // Expr* result = left.result; 173 | // while (match('+', &src)) { 174 | // ParseResult right = readOne(src); 175 | // if (right.isError()) return right; 176 | // src = right.next; 177 | // result = new AddExpr(left, right); 178 | // } 179 | // return ParseResult(result, src); 180 | // } 181 | 182 | // ParseResult readStmt(const char* src); 183 | 184 | private: 185 | void pushScope() { 186 | scopes.push_back(vars.size()); 187 | } 188 | 189 | void popScope() { 190 | uword prev_size = scopes.back(); 191 | scopes.pop_back(); 192 | vars.resize(prev_size); 193 | } 194 | 195 | word lookupOrAdd(const std::string& varname) { 196 | for (word i = vars.size() - 1; i >= 0; i--) { 197 | if (vars[i] == varname) return i; 198 | } 199 | word result = vars.size(); 200 | vars.push_back(varname); 201 | return result; 202 | } 203 | 204 | static const char *skipws(const char *src) { 205 | while (isspace(*src)) { 206 | src++; 207 | } 208 | return src; 209 | } 210 | 211 | std::vector scopes; 212 | std::vector vars; 213 | }; 214 | 215 | std::ostream& operator<<(std::ostream& os, const Expr* expr) { 216 | switch (expr->type) { 217 | case ExprType::kIntLit: { 218 | os << static_cast(expr)->value; 219 | return os; 220 | } 221 | case ExprType::kVarRef: { 222 | os << "v" << static_cast(expr)->offset; 223 | return os; 224 | } 225 | case ExprType::kVarAssign: { 226 | const VarAssign* assign = static_cast(expr); 227 | os << assign->left << " = " << assign->right; 228 | return os; 229 | } 230 | default: 231 | UNREACHABLE("unknown type"); 232 | } 233 | } 234 | 235 | constexpr word kNumVars = 26; 236 | 237 | struct State { 238 | State set(word offset, word value) const { 239 | State result = *this; 240 | result.vars[offset] = value; 241 | return result; 242 | } 243 | bool operator==(const State& other) { 244 | for (word i = 0; i < kNumVars; i++) { 245 | if (vars[i] != other.vars[i]) { 246 | return false; 247 | } 248 | } 249 | return true; 250 | } 251 | word vars[kNumVars] = {}; 252 | }; 253 | 254 | class Evaluator { 255 | public: 256 | virtual word interpret(State* state, const Expr* expr) = 0; 257 | virtual void interpret(State* state, const Stmt* stmt) = 0; 258 | }; 259 | 260 | class Interpreter : public Evaluator { 261 | public: 262 | virtual word interpret(State* state, const Expr* expr) { 263 | switch (expr->type) { 264 | case ExprType::kIntLit: { 265 | return static_cast(expr)->value; 266 | } 267 | case ExprType::kAddExpr: { 268 | auto add = static_cast(expr); 269 | word left = interpret(state, add->left); 270 | word right = interpret(state, add->right); 271 | return left + right; 272 | } 273 | case ExprType::kVarRef: { 274 | return state->vars[static_cast(expr)->offset]; 275 | } 276 | case ExprType::kVarAssign: { 277 | auto assign = static_cast(expr); 278 | word result = interpret(state, assign->right); 279 | state->vars[assign->left->offset] = result; 280 | return result; 281 | } 282 | case ExprType::kLessThan: { 283 | auto less = static_cast(expr); 284 | word left = interpret(state, less->left); 285 | word right = interpret(state, less->right); 286 | return left < right; 287 | } 288 | default: { 289 | UNREACHABLE("unsupported expr type"); 290 | break; 291 | } 292 | } 293 | } 294 | 295 | virtual void interpret(State* state, const Stmt* stmt) { 296 | switch (stmt->type) { 297 | case StmtType::kExpr: { 298 | interpret(state, static_cast(stmt)->expr); 299 | break; 300 | } 301 | case StmtType::kBlock: { 302 | auto block = static_cast(stmt); 303 | for (size_t i = 0; i < block->body.size(); i++) { 304 | interpret(state, block->body[i]); 305 | } 306 | break; 307 | } 308 | case StmtType::kIf: { 309 | auto if_ = static_cast(stmt); 310 | word result = interpret(state, if_->cond); 311 | if (result) { 312 | interpret(state, if_->cons); 313 | } else { 314 | interpret(state, if_->alt); 315 | } 316 | break; 317 | } 318 | default: { 319 | UNREACHABLE("unsupported stmt type"); 320 | break; 321 | } 322 | } 323 | } 324 | }; 325 | 326 | #define __ as. 327 | 328 | // TODO(max): Fix camelCase vs snake_case naming conventions in this file 329 | 330 | MemoryRegion finalizeCode(Assembler* as) { 331 | word code_size = Utils::roundUp(as->codeSize(), getpagesize()); 332 | void* code = ::mmap(/*addr=*/nullptr, /*length=*/code_size, 333 | /*prot=*/PROT_READ | PROT_WRITE, 334 | /*flags=*/MAP_ANONYMOUS | MAP_PRIVATE, 335 | /*filedes=*/-1, /*offset=*/0); 336 | DCHECK(code != MAP_FAILED, "mmap failed: %s", ::strerror(errno)); 337 | MemoryRegion result(code, code_size); 338 | as->finalizeInstructions(result); 339 | int mprotect_result = ::mprotect(code, code_size, PROT_EXEC); 340 | DCHECK(mprotect_result == 0, "mprotect failed: %s", ::strerror(errno)); 341 | return result; 342 | } 343 | 344 | void unmapCode(MemoryRegion region) { 345 | int munmap_result = ::munmap(region.pointer(), region.size()); 346 | DCHECK(munmap_result == 0, "munmap(%p) failed: %s", region.pointer(), 347 | ::strerror(errno)); 348 | } 349 | 350 | // use passed-in vars as base pointer for locals (RDI) 351 | typedef word (*JitFunction)(word* vars); 352 | 353 | JitFunction codeAsFunction(MemoryRegion region) { 354 | void* code = region.pointer(); 355 | return *(JitFunction*)&code; 356 | } 357 | 358 | class JIT : public Evaluator { 359 | public: 360 | virtual word interpret(State* state, const Expr* expr) { 361 | emitPrologue(); 362 | compileExpr(expr); 363 | emitEpilogue(); 364 | MemoryRegion region = finalizeCode(&as); 365 | JitFunction function = codeAsFunction(region); 366 | word result = function(state->vars); 367 | unmapCode(region); 368 | return result; 369 | } 370 | 371 | virtual void interpret(State* state, const Stmt* stmt) { 372 | emitPrologue(); 373 | compileStmt(stmt); 374 | emitEpilogue(); 375 | MemoryRegion region = finalizeCode(&as); 376 | JitFunction function = codeAsFunction(region); 377 | function(state->vars); 378 | unmapCode(region); 379 | } 380 | 381 | virtual void emitPrologue() { 382 | as.pushq(RBP); 383 | as.movq(RBP, RSP); 384 | } 385 | 386 | virtual void emitEpilogue() { 387 | as.movq(RSP, RBP); 388 | as.popq(RBP); 389 | as.ret(); 390 | } 391 | 392 | word codeSize() const { return as.codeSize(); } 393 | 394 | void dis() { 395 | fprintf(stderr, "----\n"); 396 | uword address = as.codeAddress(0); 397 | dis::DisassembleToStdout dis_stdout; 398 | dis::Disassembler dis; 399 | dis.disassemble(address, address + codeSize(), &dis_stdout); 400 | } 401 | 402 | Address varAt(word index) { 403 | return Address(RDI, index * sizeof(State{}.vars[0])); 404 | } 405 | 406 | void cmpZero(Register reg) { __ testq(reg, reg); } 407 | 408 | void cmpZero(Address mem) { __ cmpq(mem, Immediate(0)); } 409 | 410 | virtual void compileExpr(const Expr* expr) = 0; 411 | 412 | virtual void compileStmt(const Stmt* stmt) = 0; 413 | 414 | protected: 415 | Assembler as; 416 | }; 417 | 418 | class BaselineJIT : public JIT { 419 | public: 420 | virtual void compileExpr(const Expr* expr) { 421 | compileExprHelper(expr); 422 | __ popq(RAX); 423 | } 424 | 425 | void compileExprHelper(const Expr* expr) { 426 | Register tmp = RCX; 427 | switch (expr->type) { 428 | case ExprType::kIntLit: { 429 | word value = static_cast(expr)->value; 430 | __ pushq(Immediate(value)); 431 | break; 432 | } 433 | case ExprType::kAddExpr: { 434 | auto add = static_cast(expr); 435 | compileExprHelper(add->left); 436 | compileExprHelper(add->right); 437 | __ popq(tmp); 438 | __ popq(RAX); 439 | __ addq(RAX, tmp); 440 | __ pushq(RAX); 441 | break; 442 | } 443 | case ExprType::kVarRef: { 444 | word offset = static_cast(expr)->offset; 445 | __ pushq(varAt(offset)); 446 | break; 447 | } 448 | case ExprType::kVarAssign: { 449 | auto assign = static_cast(expr); 450 | compileExprHelper(assign->right); 451 | __ movq(RAX, Address(RSP, 0)); 452 | __ movq(varAt(assign->left->offset), RAX); 453 | break; 454 | } 455 | case ExprType::kLessThan: { 456 | auto less = static_cast(expr); 457 | compileExprHelper(less->left); 458 | compileExprHelper(less->right); 459 | __ popq(tmp); 460 | __ popq(RAX); 461 | __ subq(RAX, tmp); 462 | __ movq(RAX, Immediate(0)); 463 | __ setcc(LESS, RAX); 464 | __ pushq(RAX); 465 | break; 466 | } 467 | default: { 468 | UNREACHABLE("unsupported expr type"); 469 | break; 470 | } 471 | } 472 | } 473 | 474 | virtual void compileStmt(const Stmt* stmt) { 475 | switch (stmt->type) { 476 | case StmtType::kExpr: { 477 | compileExprHelper(static_cast(stmt)->expr); 478 | __ popq(RAX); 479 | break; 480 | } 481 | case StmtType::kBlock: { 482 | auto block = static_cast(stmt); 483 | for (size_t i = 0; i < block->body.size(); i++) { 484 | compileStmt(block->body[i]); 485 | } 486 | break; 487 | } 488 | case StmtType::kIf: { 489 | auto if_ = static_cast(stmt); 490 | Label alt; 491 | Label exit; 492 | compileExprHelper(if_->cond); 493 | __ popq(RAX); 494 | cmpZero(RAX); 495 | __ jcc(EQUAL, &alt, Assembler::kNearJump); 496 | // true: 497 | compileStmt(if_->cons); 498 | __ jmp(&exit, Assembler::kNearJump); 499 | // false: 500 | __ bind(&alt); 501 | compileStmt(if_->alt); 502 | // exit: 503 | __ bind(&exit); 504 | break; 505 | } 506 | default: { 507 | UNREACHABLE("unsupported stmt type"); 508 | break; 509 | } 510 | } 511 | } 512 | }; 513 | 514 | enum class Destination { 515 | kStack, 516 | kAccumulator, 517 | kNowhere, 518 | }; 519 | 520 | class DestinationDrivenJIT : public JIT { 521 | public: 522 | virtual void compileExpr(const Expr* expr) { 523 | compileExpr(expr, Destination::kAccumulator); 524 | } 525 | 526 | void compileExpr(const Expr* expr, Destination dest) { 527 | Register tmp = RCX; 528 | switch (expr->type) { 529 | case ExprType::kIntLit: { 530 | word value = static_cast(expr)->value; 531 | plug(dest, Immediate(value)); 532 | break; 533 | } 534 | case ExprType::kAddExpr: { 535 | auto add = static_cast(expr); 536 | compileExpr(add->left, Destination::kStack); 537 | compileExpr(add->right, Destination::kAccumulator); 538 | __ popq(tmp); 539 | __ addq(RAX, tmp); 540 | plug(dest, RAX); 541 | break; 542 | } 543 | case ExprType::kVarRef: { 544 | word offset = static_cast(expr)->offset; 545 | plug(dest, varAt(offset)); 546 | break; 547 | } 548 | case ExprType::kVarAssign: { 549 | auto assign = static_cast(expr); 550 | compileExpr(assign->right, Destination::kAccumulator); 551 | __ movq(varAt(assign->left->offset), RAX); 552 | plug(dest, RAX); 553 | break; 554 | } 555 | case ExprType::kLessThan: { 556 | auto less = static_cast(expr); 557 | compileExpr(less->left, Destination::kStack); 558 | compileExpr(less->right, Destination::kAccumulator); 559 | Label cons; 560 | Label alt; 561 | Label exit; 562 | __ popq(tmp); 563 | __ cmpq(tmp, RAX); 564 | __ jcc(GREATER_EQUAL, &alt, Assembler::kNearJump); 565 | // true: 566 | plug(dest, Immediate(1)); 567 | __ jmp(&exit, Assembler::kNearJump); 568 | // false: 569 | __ bind(&alt); 570 | plug(dest, Immediate(0)); 571 | // exit: 572 | __ bind(&exit); 573 | break; 574 | } 575 | default: { 576 | UNREACHABLE("unsupported expr type"); 577 | break; 578 | } 579 | } 580 | } 581 | 582 | virtual void compileStmt(const Stmt* stmt) { 583 | switch (stmt->type) { 584 | case StmtType::kExpr: { 585 | compileExpr(static_cast(stmt)->expr, 586 | Destination::kNowhere); 587 | break; 588 | } 589 | case StmtType::kBlock: { 590 | auto block = static_cast(stmt); 591 | for (size_t i = 0; i < block->body.size(); i++) { 592 | compileStmt(block->body[i]); 593 | } 594 | break; 595 | } 596 | case StmtType::kIf: { 597 | auto if_ = static_cast(stmt); 598 | compileExpr(if_->cond, Destination::kAccumulator); 599 | Label alt; 600 | Label exit; 601 | cmpZero(RAX); // check if falsey 602 | __ jcc(EQUAL, &alt, Assembler::kNearJump); 603 | // true: 604 | compileStmt(if_->cons); 605 | __ jmp(&exit, Assembler::kNearJump); 606 | // false: 607 | __ bind(&alt); 608 | compileStmt(if_->alt); 609 | // exit: 610 | __ bind(&exit); 611 | break; 612 | } 613 | default: { 614 | UNREACHABLE("unsupported stmt type"); 615 | break; 616 | } 617 | } 618 | } 619 | 620 | void plug(Destination dest, Immediate imm) { 621 | Register tmp = RCX; 622 | switch (dest) { 623 | case Destination::kStack: { 624 | __ pushq(imm); 625 | break; 626 | } 627 | case Destination::kAccumulator: { 628 | __ movq(RAX, imm); 629 | break; 630 | } 631 | case Destination::kNowhere: { 632 | // Nothing to do 633 | break; 634 | } 635 | } 636 | } 637 | 638 | void plug(Destination dest, Register reg) { 639 | assert(reg == RAX); 640 | switch (dest) { 641 | case Destination::kStack: { 642 | __ pushq(reg); 643 | break; 644 | } 645 | case Destination::kAccumulator: 646 | case Destination::kNowhere: { 647 | // Nothing to do 648 | break; 649 | } 650 | } 651 | } 652 | 653 | void plug(Destination dest, Address mem) { 654 | Register tmp = RCX; 655 | switch (dest) { 656 | case Destination::kStack: { 657 | __ movq(tmp, mem); 658 | __ pushq(tmp); 659 | break; 660 | } 661 | case Destination::kAccumulator: { 662 | __ movq(RAX, mem); 663 | break; 664 | } 665 | case Destination::kNowhere: { 666 | // Nothing to do 667 | break; 668 | } 669 | } 670 | } 671 | }; 672 | 673 | struct ControlDestination2 { 674 | explicit ControlDestination2(Label* cons, Label* alt) 675 | : cons(cons), alt(alt) {} 676 | bool isUseful() const { return cons != alt; } 677 | Label* cons{nullptr}; 678 | Label* alt{nullptr}; 679 | }; 680 | 681 | class ControlDestination2DrivenJIT : public JIT { 682 | public: 683 | virtual void compileExpr(const Expr* expr) { 684 | Label next; 685 | compileExpr(expr, Destination::kAccumulator, 686 | ControlDestination2(&next, &next)); 687 | __ bind(&next); 688 | } 689 | 690 | void compileExpr(const Expr* expr, Destination dest, 691 | ControlDestination2 cdest) { 692 | Register tmp = RCX; 693 | switch (expr->type) { 694 | case ExprType::kIntLit: { 695 | word value = static_cast(expr)->value; 696 | plug(dest, cdest, Immediate(value)); 697 | break; 698 | } 699 | case ExprType::kAddExpr: { 700 | auto add = static_cast(expr); 701 | compileExpr(add->left, Destination::kStack, cdest); 702 | compileExpr(add->right, Destination::kAccumulator, cdest); 703 | __ popq(tmp); 704 | __ addq(RAX, tmp); 705 | plug(dest, cdest, RAX); 706 | break; 707 | } 708 | case ExprType::kVarRef: { 709 | word offset = static_cast(expr)->offset; 710 | plug(dest, cdest, varAt(offset)); 711 | break; 712 | } 713 | case ExprType::kVarAssign: { 714 | auto assign = static_cast(expr); 715 | compileExpr(assign->right, Destination::kAccumulator, cdest); 716 | __ movq(varAt(assign->left->offset), RAX); 717 | plug(dest, cdest, RAX); 718 | break; 719 | } 720 | case ExprType::kLessThan: { 721 | auto less = static_cast(expr); 722 | compileExpr(less->left, Destination::kStack, cdest); 723 | compileExpr(less->right, Destination::kAccumulator, cdest); 724 | __ popq(tmp); 725 | __ cmpq(tmp, RAX); 726 | plug(dest, cdest, LESS); 727 | break; 728 | } 729 | default: { 730 | UNREACHABLE("unsupported expr type"); 731 | break; 732 | } 733 | } 734 | } 735 | 736 | virtual void compileStmt(const Stmt* stmt) { 737 | Label next; 738 | compileStmt(stmt, ControlDestination2(&next, &next)); 739 | __ bind(&next); 740 | } 741 | 742 | virtual void compileStmt(const Stmt* stmt, ControlDestination2 cdest) { 743 | switch (stmt->type) { 744 | case StmtType::kExpr: { 745 | compileExpr(static_cast(stmt)->expr, 746 | Destination::kNowhere, cdest); 747 | break; 748 | } 749 | case StmtType::kBlock: { 750 | auto block = static_cast(stmt); 751 | for (size_t i = 0; i < block->body.size(); i++) { 752 | Label next; 753 | compileStmt(block->body[i], ControlDestination2(&next, &next)); 754 | __ bind(&next); 755 | } 756 | break; 757 | } 758 | case StmtType::kIf: { 759 | auto if_ = static_cast(stmt); 760 | Label cons; 761 | Label alt; 762 | Label exit; 763 | compileExpr(if_->cond, Destination::kNowhere, 764 | ControlDestination2(&cons, &alt)); 765 | // true: 766 | __ bind(&cons); 767 | compileStmt(if_->cons, cdest); 768 | __ jmp(&exit, Assembler::kNearJump); 769 | // false: 770 | __ bind(&alt); 771 | compileStmt(if_->alt, cdest); 772 | // exit: 773 | __ bind(&exit); 774 | break; 775 | } 776 | default: { 777 | UNREACHABLE("unsupported stmt type"); 778 | break; 779 | } 780 | } 781 | } 782 | 783 | virtual void plug(Destination dest, ControlDestination2 cdest, 784 | Condition cond) { 785 | switch (dest) { 786 | case Destination::kStack: { 787 | UNREACHABLE("TODO(max): implement plug(stack, cond)"); 788 | break; 789 | } 790 | case Destination::kAccumulator: { 791 | Label materialize_true; 792 | __ jcc(cond, &materialize_true, Assembler::kNearJump); 793 | __ movq(RAX, Immediate(0)); 794 | __ jmp(cdest.alt, Assembler::kNearJump); 795 | __ bind(&materialize_true); 796 | __ movq(RAX, Immediate(1)); 797 | __ jmp(cdest.cons, Assembler::kNearJump); 798 | break; 799 | } 800 | case Destination::kNowhere: { 801 | __ jcc(cond, cdest.cons, Assembler::kNearJump); 802 | __ jmp(cdest.alt, Assembler::kNearJump); 803 | break; 804 | } 805 | } 806 | } 807 | 808 | void plug(Destination dest, ControlDestination2 cdest, Immediate imm) { 809 | switch (dest) { 810 | case Destination::kStack: { 811 | __ pushq(imm); 812 | break; 813 | } 814 | case Destination::kAccumulator: { 815 | __ movq(RAX, imm); 816 | break; 817 | } 818 | case Destination::kNowhere: { 819 | if (!cdest.isUseful()) { 820 | // Nothing to do; not supposed to be materialized anywhere. Likely 821 | // from an ExprStmt. 822 | return; 823 | } 824 | if (imm.value()) { 825 | __ jmp(cdest.cons, Assembler::kNearJump); 826 | } else { 827 | __ jmp(cdest.alt, Assembler::kNearJump); 828 | } 829 | break; 830 | } 831 | } 832 | } 833 | 834 | template 835 | void jmpTruthiness(T op, ControlDestination2 cdest) { 836 | if (!cdest.isUseful()) { 837 | // Sometimes the input is ControlDestination2(next, next), in which 838 | // case there is no need at all to check the truthiness of the input. 839 | // Nobody depends on it. 840 | return; 841 | } 842 | cmpZero(op); 843 | __ jcc(NOT_EQUAL, cdest.cons, Assembler::kNearJump); 844 | __ jmp(cdest.alt, Assembler::kNearJump); 845 | } 846 | 847 | void plug(Destination dest, ControlDestination2 cdest, Register reg) { 848 | switch (dest) { 849 | case Destination::kStack: { 850 | UNREACHABLE("TODO(max): see how to generate this code"); 851 | __ pushq(reg); 852 | break; 853 | } 854 | case Destination::kAccumulator: { 855 | // Nothing to do; reg already in RAX or it's not supposed to materialize 856 | // anywhere 857 | DCHECK(reg == RAX, "expect RAX to be accumulator"); 858 | break; 859 | } 860 | case Destination::kNowhere: { 861 | jmpTruthiness(reg, cdest); 862 | break; 863 | } 864 | } 865 | } 866 | 867 | void plug(Destination dest, ControlDestination2 cdest, Address mem) { 868 | Register tmp = RCX; 869 | switch (dest) { 870 | case Destination::kStack: { 871 | __ movq(tmp, mem); 872 | __ pushq(tmp); 873 | break; 874 | } 875 | case Destination::kAccumulator: { 876 | __ movq(RAX, mem); 877 | break; 878 | } 879 | case Destination::kNowhere: { 880 | jmpTruthiness(mem, cdest); 881 | break; 882 | } 883 | } 884 | } 885 | }; 886 | 887 | struct ControlDestination3 { 888 | explicit ControlDestination3(Label* cons, Label* alt) 889 | : cons(cons), alt(alt) {} 890 | explicit ControlDestination3(Label* cons, Label* alt, Label* fallthrough) 891 | : cons(cons), alt(alt), fallthrough(fallthrough) {} 892 | bool isUseful() const { return !(cons == alt && alt == fallthrough); } 893 | Label* cons{nullptr}; 894 | Label* alt{nullptr}; 895 | Label* fallthrough{nullptr}; 896 | }; 897 | 898 | class ControlDestination3DrivenJIT : public JIT { 899 | public: 900 | virtual void compileExpr(const Expr* expr) { 901 | Label next; 902 | compileExpr(expr, Destination::kAccumulator, 903 | ControlDestination3(&next, &next, &next)); 904 | __ bind(&next); 905 | } 906 | 907 | void compileExpr(const Expr* expr, Destination dest, 908 | ControlDestination3 cdest) { 909 | Register tmp = RCX; 910 | switch (expr->type) { 911 | case ExprType::kIntLit: { 912 | word value = static_cast(expr)->value; 913 | plug(dest, cdest, Immediate(value)); 914 | break; 915 | } 916 | case ExprType::kAddExpr: { 917 | auto add = static_cast(expr); 918 | compileExpr(add->left, Destination::kStack, cdest); 919 | compileExpr(add->right, Destination::kAccumulator, cdest); 920 | __ popq(tmp); 921 | __ addq(RAX, tmp); 922 | plug(dest, cdest, RAX); 923 | break; 924 | } 925 | case ExprType::kVarRef: { 926 | word offset = static_cast(expr)->offset; 927 | plug(dest, cdest, varAt(offset)); 928 | break; 929 | } 930 | case ExprType::kVarAssign: { 931 | auto assign = static_cast(expr); 932 | compileExpr(assign->right, Destination::kAccumulator, cdest); 933 | __ movq(varAt(assign->left->offset), RAX); 934 | plug(dest, cdest, RAX); 935 | break; 936 | } 937 | case ExprType::kLessThan: { 938 | auto less = static_cast(expr); 939 | compileExpr(less->left, Destination::kStack, cdest); 940 | compileExpr(less->right, Destination::kAccumulator, cdest); 941 | __ popq(tmp); 942 | __ cmpq(tmp, RAX); 943 | plug(dest, cdest, LESS); 944 | break; 945 | } 946 | default: { 947 | UNREACHABLE("unsupported expr type"); 948 | break; 949 | } 950 | } 951 | } 952 | 953 | virtual void compileStmt(const Stmt* stmt) { 954 | Label next; 955 | compileStmt(stmt, ControlDestination3(&next, &next, &next)); 956 | __ bind(&next); 957 | } 958 | 959 | virtual void compileStmt(const Stmt* stmt, ControlDestination3 cdest) { 960 | switch (stmt->type) { 961 | case StmtType::kExpr: { 962 | compileExpr(static_cast(stmt)->expr, 963 | Destination::kNowhere, cdest); 964 | break; 965 | } 966 | case StmtType::kBlock: { 967 | auto block = static_cast(stmt); 968 | for (size_t i = 0; i < block->body.size(); i++) { 969 | Label next; 970 | compileStmt(block->body[i], ControlDestination3(&next, &next, &next)); 971 | __ bind(&next); 972 | } 973 | break; 974 | } 975 | case StmtType::kIf: { 976 | auto if_ = static_cast(stmt); 977 | Label cons; 978 | Label alt; 979 | Label exit; 980 | compileExpr(if_->cond, Destination::kNowhere, 981 | ControlDestination3(&cons, &alt, &cons)); 982 | // true: 983 | __ bind(&cons); 984 | compileStmt(if_->cons, cdest); 985 | __ jmp(&exit, Assembler::kNearJump); 986 | // false: 987 | __ bind(&alt); 988 | compileStmt(if_->alt, cdest); 989 | // exit: 990 | __ bind(&exit); 991 | break; 992 | } 993 | default: { 994 | UNREACHABLE("unsupported stmt type"); 995 | break; 996 | } 997 | } 998 | } 999 | 1000 | virtual void plug(Destination dest, ControlDestination3 cdest, 1001 | Condition cond) { 1002 | switch (dest) { 1003 | case Destination::kStack: { 1004 | UNREACHABLE("TODO(max): implement plug(stack, cond)"); 1005 | break; 1006 | } 1007 | case Destination::kAccumulator: { 1008 | Label materialize_true; 1009 | __ jcc(cond, &materialize_true, Assembler::kNearJump); 1010 | __ movq(RAX, Immediate(0)); 1011 | __ jmp(cdest.alt, Assembler::kNearJump); 1012 | __ bind(&materialize_true); 1013 | __ movq(RAX, Immediate(1)); 1014 | if (cdest.cons != cdest.fallthrough) { 1015 | __ jmp(cdest.cons, Assembler::kNearJump); 1016 | } 1017 | break; 1018 | } 1019 | case Destination::kNowhere: { 1020 | if (cdest.fallthrough == cdest.cons) { 1021 | __ jcc(invert(cond), cdest.alt, Assembler::kNearJump); 1022 | } else if (cdest.fallthrough == cdest.alt) { 1023 | UNREACHABLE("TODO(max): Figure out how to generate"); 1024 | __ jcc(cond, cdest.cons, Assembler::kNearJump); 1025 | } else { 1026 | UNREACHABLE("TODO(max): Figure out how to generate"); 1027 | __ jcc(cond, cdest.cons, Assembler::kNearJump); 1028 | __ jmp(cdest.alt, Assembler::kNearJump); 1029 | } 1030 | break; 1031 | } 1032 | } 1033 | } 1034 | 1035 | void plug(Destination dest, ControlDestination3 cdest, Immediate imm) { 1036 | switch (dest) { 1037 | case Destination::kStack: { 1038 | __ pushq(imm); 1039 | break; 1040 | } 1041 | case Destination::kAccumulator: { 1042 | __ movq(RAX, imm); 1043 | break; 1044 | } 1045 | case Destination::kNowhere: { 1046 | if (!cdest.isUseful()) { 1047 | // Nothing to do; not supposed to be materialized anywhere. Likely 1048 | // from an ExprStmt. 1049 | return; 1050 | } 1051 | if (imm.value()) { 1052 | __ jmp(cdest.cons, Assembler::kNearJump); 1053 | } else { 1054 | __ jmp(cdest.alt, Assembler::kNearJump); 1055 | } 1056 | break; 1057 | } 1058 | } 1059 | } 1060 | 1061 | template 1062 | void jmpTruthiness(T op, ControlDestination3 cdest) { 1063 | if (!cdest.isUseful()) { 1064 | // Sometimes the input is ControlDestination3(next, next, next), in which 1065 | // case there is no need at all to check the truthiness of the input. 1066 | // Nobody depends on it. 1067 | return; 1068 | } 1069 | cmpZero(op); 1070 | if (cdest.fallthrough == cdest.cons) { 1071 | __ jcc(EQUAL, cdest.alt, Assembler::kNearJump); 1072 | } else if (cdest.fallthrough == cdest.alt) { 1073 | UNREACHABLE("TODO(max): Figure out how to generate"); 1074 | __ jcc(NOT_EQUAL, cdest.cons, Assembler::kNearJump); 1075 | } else { 1076 | UNREACHABLE("TODO(max): Figure out how to generate"); 1077 | __ jcc(NOT_EQUAL, cdest.cons, Assembler::kNearJump); 1078 | __ jmp(cdest.alt, Assembler::kNearJump); 1079 | } 1080 | } 1081 | 1082 | void plug(Destination dest, ControlDestination3 cdest, Register reg) { 1083 | switch (dest) { 1084 | case Destination::kStack: { 1085 | UNREACHABLE("TODO(max): see how to generate this code"); 1086 | __ pushq(reg); 1087 | break; 1088 | } 1089 | case Destination::kAccumulator: { 1090 | // Nothing to do; reg already in RAX or it's not supposed to materialize 1091 | // anywhere 1092 | DCHECK(reg == RAX, "expect RAX to be accumulator"); 1093 | break; 1094 | } 1095 | case Destination::kNowhere: { 1096 | jmpTruthiness(reg, cdest); 1097 | break; 1098 | } 1099 | } 1100 | } 1101 | 1102 | void plug(Destination dest, ControlDestination3 cdest, Address mem) { 1103 | Register tmp = RCX; 1104 | switch (dest) { 1105 | case Destination::kStack: { 1106 | __ movq(tmp, mem); 1107 | __ pushq(tmp); 1108 | break; 1109 | } 1110 | case Destination::kAccumulator: { 1111 | __ movq(RAX, mem); 1112 | break; 1113 | } 1114 | case Destination::kNowhere: { 1115 | jmpTruthiness(mem, cdest); 1116 | break; 1117 | } 1118 | } 1119 | } 1120 | }; 1121 | 1122 | // TODO(max): Unify ExprTest and StmtTest 1123 | 1124 | struct ExprTest { 1125 | State state; 1126 | Expr* expr; 1127 | word expected; 1128 | }; 1129 | 1130 | struct StmtTest { 1131 | Stmt* stmt; 1132 | State expected; 1133 | }; 1134 | 1135 | struct ExprParseTest { 1136 | const char* src; 1137 | Expr* expected; 1138 | }; 1139 | 1140 | void print_results(const std::vector& failed) { 1141 | if (failed.size()) { 1142 | fprintf(stderr, "Failed tests:"); 1143 | for (word test : failed) { 1144 | fprintf(stderr, " %zu", test); 1145 | } 1146 | fprintf(stderr, "\n"); 1147 | } 1148 | } 1149 | 1150 | template 1151 | void test_interpreter(ExprTest tests[]) { 1152 | std::vector failed; 1153 | word total_size = 0; 1154 | for (word i = 0; tests[i].expr != nullptr; i++) { 1155 | T impl; 1156 | word result = impl.interpret(&tests[i].state, tests[i].expr); 1157 | if (std::is_base_of::value) { 1158 | total_size += reinterpret_cast(&impl)->codeSize(); 1159 | } 1160 | if (result == tests[i].expected) { 1161 | fprintf(stderr, "."); 1162 | } else { 1163 | failed.push_back(i); 1164 | fprintf(stderr, "E"); 1165 | } 1166 | } 1167 | if (total_size) { 1168 | fprintf(stderr, " (%ld bytes)", total_size); 1169 | } 1170 | fprintf(stderr, "\n"); 1171 | print_results(failed); 1172 | } 1173 | 1174 | template 1175 | void test_interpreter(StmtTest tests[]) { 1176 | std::vector failed; 1177 | word total_size = 0; 1178 | for (word i = 0; tests[i].stmt != nullptr; i++) { 1179 | State state; 1180 | T impl; 1181 | impl.interpret(&state, tests[i].stmt); 1182 | if (std::is_base_of::value) { 1183 | total_size += reinterpret_cast(&impl)->codeSize(); 1184 | } 1185 | if (state == tests[i].expected) { 1186 | fprintf(stderr, "."); 1187 | } else { 1188 | failed.push_back(i); 1189 | fprintf(stderr, "E"); 1190 | } 1191 | } 1192 | if (total_size) { 1193 | fprintf(stderr, " (%ld bytes)", total_size); 1194 | } 1195 | fprintf(stderr, "\n"); 1196 | print_results(failed); 1197 | } 1198 | 1199 | bool equal(const Expr* actual, const Expr* expected) { 1200 | DCHECK(expected != nullptr, "unexpected null expression"); 1201 | if (actual == nullptr) { 1202 | return false; 1203 | } 1204 | if (actual->type != expected->type) { 1205 | return false; 1206 | } 1207 | switch (expected->type) { 1208 | case ExprType::kIntLit: { 1209 | return static_cast(actual)->value == static_cast(expected)->value; 1210 | } 1211 | case ExprType::kVarRef: { 1212 | return static_cast(actual)->offset == static_cast(expected)->offset; 1213 | } 1214 | case ExprType::kVarAssign: { 1215 | const VarAssign *actual_assign = static_cast(actual); 1216 | const VarAssign *expected_assign = static_cast(expected); 1217 | return equal(actual_assign->left, expected_assign->left) && equal(actual_assign->right, expected_assign->right); 1218 | } 1219 | default: 1220 | UNREACHABLE("unknown expr type"); 1221 | } 1222 | } 1223 | 1224 | void test_expr_parser(ExprParseTest tests[]) { 1225 | std::vector failed; 1226 | word total_size = 0; 1227 | for (word i = 0; tests[i].src != nullptr; i++) { 1228 | Parser parser; 1229 | ParseResult result = parser.readExpr(tests[i].src); 1230 | if (result.isError() || !equal(result.result, tests[i].expected)) { 1231 | failed.push_back(i); 1232 | fprintf(stderr, "E"); 1233 | continue; 1234 | } 1235 | fprintf(stderr, "."); 1236 | } 1237 | fprintf(stderr, "\n"); 1238 | print_results(failed); 1239 | } 1240 | 1241 | template 1242 | word code_size(Stmt* stmt) { 1243 | T jit; 1244 | jit.compileStmt(stmt); 1245 | jit.dis(); 1246 | return jit.codeSize(); 1247 | } 1248 | 1249 | word perc_change(word before, word after) { 1250 | CHECK(before != 0, "can't divide by 0"); 1251 | return static_cast((after - before) / static_cast(before) * 1252 | 100); 1253 | } 1254 | 1255 | void compare_jit(Stmt* stmt) { 1256 | word baseline_size = code_size(stmt); 1257 | word dest_driven_size = code_size(stmt); 1258 | word control_dest2_size = code_size(stmt); 1259 | word control_dest3_size = code_size(stmt); 1260 | fprintf(stderr, "b: %ld\td: %ld (%ld%%)\tc2: %ld (%ld%%)\tc3: %ld (%ld%%)\n", 1261 | baseline_size, dest_driven_size, 1262 | perc_change(baseline_size, dest_driven_size), control_dest2_size, 1263 | perc_change(baseline_size, control_dest2_size), control_dest3_size, 1264 | perc_change(baseline_size, control_dest3_size)); 1265 | CHECK(control_dest2_size <= dest_driven_size, 1266 | "control destinations didn't help code size!"); 1267 | CHECK(control_dest3_size <= control_dest2_size, 1268 | "control destinations didn't help code size!"); 1269 | } 1270 | 1271 | int main() { 1272 | ExprTest expr_tests[] = { 1273 | {State{}, new IntLit(123), 123}, 1274 | {State{}, new AddExpr(new IntLit(123), new IntLit(456)), 579}, 1275 | {State{}.set(3, 123), new VarRef(3), 123}, 1276 | {State{}, new VarAssign(new VarRef(3), new IntLit(123)), 123}, 1277 | {State{}, new LessThan(new IntLit(1), new IntLit(2)), 1}, 1278 | {State{}, new LessThan(new IntLit(2), new IntLit(2)), 0}, 1279 | {State{}, new LessThan(new IntLit(3), new IntLit(2)), 0}, 1280 | // TODO(max): Test compound conditionals 1281 | {State{}, nullptr, 0}, 1282 | }; 1283 | StmtTest stmt_tests[] = { 1284 | {new ExprStmt(new IntLit(123)), State{}}, 1285 | {new ExprStmt(new VarAssign(new VarRef(3), new IntLit(123))), 1286 | State{}.set(3, 123)}, 1287 | {new BlockStmt({ 1288 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(123))), 1289 | new ExprStmt(new VarAssign(new VarRef(1), new IntLit(456))), 1290 | }), 1291 | State{}.set(0, 123).set(1, 456)}, 1292 | {new IfStmt(new IntLit(1), 1293 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(123))), 1294 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(456)))), 1295 | State{}.set(0, 123)}, 1296 | {new IfStmt(new IntLit(7), 1297 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(123))), 1298 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(456)))), 1299 | State{}.set(0, 123)}, 1300 | {new IfStmt(new IntLit(-7), 1301 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(123))), 1302 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(456)))), 1303 | State{}.set(0, 123)}, 1304 | {new IfStmt(new IntLit(0), 1305 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(123))), 1306 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(456)))), 1307 | State{}.set(0, 456)}, 1308 | {new IfStmt(new LessThan(new IntLit(1), new IntLit(2)), 1309 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(123))), 1310 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(456)))), 1311 | State{}.set(0, 123)}, 1312 | {new IfStmt(new LessThan(new IntLit(2), new IntLit(2)), 1313 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(123))), 1314 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(456)))), 1315 | State{}.set(0, 456)}, 1316 | {new IfStmt(new LessThan(new IntLit(3), new IntLit(2)), 1317 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(123))), 1318 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(456)))), 1319 | State{}.set(0, 456)}, 1320 | {new BlockStmt({ 1321 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(123))), 1322 | new ExprStmt(new VarAssign(new VarRef(1), new IntLit(456))), 1323 | new ExprStmt(new VarAssign( 1324 | new VarRef(2), new AddExpr(new VarRef(0), new VarRef(1)))), 1325 | }), 1326 | State{}.set(0, 123).set(1, 456).set(2, 123 + 456)}, 1327 | {new BlockStmt({ 1328 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(123))), 1329 | // Test memory (the var ref) with a destination of nowhere. 1330 | new ExprStmt(new VarRef(0)), 1331 | }), 1332 | State{}.set(0, 123)}, 1333 | {new BlockStmt({ 1334 | // TODO(max): Use beginning state instead of explicit VarAssign 1335 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(1))), 1336 | new IfStmt( 1337 | new VarRef(0), 1338 | new ExprStmt(new VarAssign(new VarRef(1), new IntLit(2))), 1339 | new ExprStmt(new VarAssign(new VarRef(1), new IntLit(3)))), 1340 | 1341 | }), 1342 | State{}.set(0, 1).set(1, 2)}, 1343 | {new BlockStmt({ 1344 | // TODO(max): Use beginning state instead of explicit VarAssign 1345 | new ExprStmt(new VarAssign(new VarRef(0), new IntLit(0))), 1346 | new IfStmt( 1347 | new VarRef(0), 1348 | new ExprStmt(new VarAssign(new VarRef(1), new IntLit(2))), 1349 | new ExprStmt(new VarAssign(new VarRef(1), new IntLit(3)))), 1350 | 1351 | }), 1352 | State{}.set(0, 0).set(1, 3)}, 1353 | {new BlockStmt({ 1354 | // TODO(max): Use beginning state instead of explicit VarAssign 1355 | new ExprStmt(new VarAssign( 1356 | new VarRef(0), new AddExpr(new IntLit(1), new IntLit(1)))), 1357 | new IfStmt( 1358 | new VarRef(0), 1359 | new ExprStmt(new VarAssign(new VarRef(1), new IntLit(2))), 1360 | new ExprStmt(new VarAssign(new VarRef(1), new IntLit(3)))), 1361 | 1362 | }), 1363 | State{}.set(0, 2).set(1, 2)}, 1364 | // TODO(max): Test nested if 1365 | {nullptr, State{}}, 1366 | }; 1367 | ExprParseTest expr_parse_tests[] = { 1368 | {"123", new IntLit(123)}, 1369 | {"abc", new VarRef(0)}, 1370 | {"abc + abc", new AddExpr(new VarRef(0), new VarRef(0))}, 1371 | {"abc + xyz", new AddExpr(new VarRef(0), new VarRef(1))}, 1372 | {"abc = 123", new VarAssign(new VarRef(0), new IntLit(123))}, 1373 | {nullptr, nullptr} 1374 | }; 1375 | fprintf(stderr, "Testing parser (expr) "); 1376 | test_expr_parser(expr_parse_tests); 1377 | fprintf(stderr, "Testing interpreter (expr) "); 1378 | test_interpreter(expr_tests); 1379 | fprintf(stderr, "Testing interpreter (stmt) "); 1380 | test_interpreter(stmt_tests); 1381 | fprintf(stderr, "Testing baseline jit (expr) "); 1382 | test_interpreter(expr_tests); 1383 | fprintf(stderr, "Testing baseline jit (stmt) "); 1384 | test_interpreter(stmt_tests); 1385 | fprintf(stderr, "Testing destination jit (expr) "); 1386 | test_interpreter(expr_tests); 1387 | fprintf(stderr, "Testing destination jit (stmt) "); 1388 | test_interpreter(stmt_tests); 1389 | fprintf(stderr, "Testing control destination 2 jit (expr) "); 1390 | test_interpreter(expr_tests); 1391 | fprintf(stderr, "Testing control destination 2 jit (stmt) "); 1392 | test_interpreter(stmt_tests); 1393 | fprintf(stderr, "Testing control destination 3 jit (expr) "); 1394 | test_interpreter(expr_tests); 1395 | fprintf(stderr, "Testing control destination 3 jit (stmt) "); 1396 | test_interpreter(stmt_tests); 1397 | 1398 | Parser parser; 1399 | ParseResult result = parser.readVarAssign("abc = 123"); 1400 | DCHECK(!result.isError(), "uh oh"); 1401 | DCHECK(result.result != nullptr, "uh oh"); 1402 | std::cerr << result.result << std::endl; 1403 | DCHECK(result.result->type == ExprType::kVarAssign, "uh oh"); 1404 | } 1405 | --------------------------------------------------------------------------------