├── .gitignore ├── AddressRange.cpp ├── AddressRange.h ├── AddressSet.cpp ├── AddressSet.h ├── COPYING ├── CREDITS ├── Function.cpp ├── Function.h ├── FunctionBuilder.cpp ├── FunctionBuilder.h ├── FunctionManager.cpp ├── FunctionManager.h ├── JitBool.h ├── LLVMStuff.cpp ├── LLVMStuff.h ├── M6502Internal.h ├── Makefile.am ├── README ├── README.lib6502 ├── Registers.cpp ├── Registers.h ├── TODO ├── build-aux └── tap-driver.sh ├── config.h.in ├── configure.ac ├── const.h ├── examples ├── README ├── hex2bin └── lib1.c ├── lib6502-compatibility.txt ├── lib6502-jit.cpp ├── lib6502.c ├── lib6502.h ├── m4 └── boost.m4 ├── man ├── M6502_delete.3 ├── M6502_disassemble.3 ├── M6502_dump.3 ├── M6502_getCallback.3 ├── M6502_getVector.3 ├── M6502_irq.3 ├── M6502_new.3 ├── M6502_nmi.3 ├── M6502_reset.3 ├── M6502_run.3 ├── M6502_setCallback.3 ├── M6502_setMode.3 ├── M6502_setVector.3 ├── lib6502.3 └── run6502.1 ├── run6502.c ├── test ├── addr-wrap-1.mst ├── addr-wrap-1.xa ├── basic-callback.c ├── basic-callback.mst ├── call-illegal-callback-modify-code.c ├── call-illegal-callback-modify-code.mst ├── config.xa ├── interleave.mst ├── interleave.xa ├── irq-nmi.c ├── irq-nmi.mst ├── pc-wrap-1.mst ├── pc-wrap-1.xa ├── pc-wrap-2.mst ├── pc-wrap-2.xa ├── run-c-tests.py ├── run-c-tests.sh ├── run-run6502-tests.py ├── run-run6502-tests.sh ├── setjmp-trick.c ├── setjmp-trick.mst ├── stack-code-brk.c ├── stack-code-brk.mst ├── stack-code-jsr.c ├── stack-code-jsr.mst ├── test-utils.c ├── test-utils.h ├── trivial-test.mst ├── trivial-test.xa ├── write-callback-modify-code.c ├── write-callback-modify-code.mst ├── z-self-modify-1.mst ├── z-self-modify-1.xa ├── z-self-modify-2.mst └── z-self-modify-2.xa ├── util.cpp ├── util.h └── valgrind.h /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.o 3 | *.lo 4 | .deps 5 | .libs 6 | Makefile 7 | Makefile.in 8 | aclocal.m4 9 | autom4te.cache 10 | config.guess 11 | config.h 12 | config.log 13 | config.status 14 | config.sub 15 | configure 16 | depcomp 17 | examples/.dirstamp 18 | examples/lib1 19 | install-sh 20 | lib6502-jit* 21 | lib6502-jit* 22 | libtool 23 | ltmain.sh 24 | m4/libtool.m4 25 | m4/ltoptions.m4 26 | m4/ltsugar.m4 27 | m4/ltversion.m4 28 | m4/lt~obsolete.m4 29 | missing 30 | run6502 31 | stamp-h1 32 | test/.dirstamp 33 | test/*.mc 34 | test/basic-callback 35 | test/call-illegal-callback-modify-code 36 | test/irq-nmi 37 | test/setjmp-trick 38 | test/stack-code-brk 39 | test/stack-code-jsr 40 | test/write-callback-modify-code 41 | test/z-self-modify-1.mc 42 | test/z-self-modify-1.out 43 | -------------------------------------------------------------------------------- /AddressRange.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | #include "AddressRange.h" 19 | 20 | #include 21 | 22 | #include "const.h" 23 | 24 | AddressRange::AddressRange(uint16_t addr) 25 | : range_begin_(addr), range_end_(range_begin_ + 1) 26 | { 27 | } 28 | 29 | AddressRange::AddressRange(uint32_t range_begin, uint32_t range_end) 30 | : range_begin_(range_begin), range_end_(range_end) 31 | { 32 | assert(range_begin_ < memory_size); 33 | assert(range_end_ <= (memory_size + 0xff)); 34 | assert(range_begin_ < range_end_); 35 | } 36 | 37 | bool AddressRange::all_memory() const 38 | { 39 | // This doesn't catch some degenerate cases (e.g. range_begin_ = 0x1, 40 | // range_end_ = 0x10002) but that doesn't matter. 41 | return (range_begin_ == 0) && (range_end_ == memory_size); 42 | } 43 | -------------------------------------------------------------------------------- /AddressRange.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | // An AddressRange represents a contiguous range of addresses in the emulated 19 | // memory, expressed as a half-open interval ("begin" is included, "end" is 20 | // excluded). To allow convenient handling of cases where addresses wrap around 21 | // at the top of memory, end may be as large as 0x100ff; this allows the 22 | // effective address range of an instruction like LDA &ffff,Y to be represented. 23 | // (The "largest" address accessed is &00fe, and since the interval is half-open 24 | // end needs to allow a value one larger.) 25 | 26 | #ifndef ADDRESSRANGE_H 27 | #define ADDRESSRANGE_H 28 | 29 | #include 30 | 31 | class AddressRange 32 | { 33 | public: 34 | // Convenience function; equivalent to AddressRange(addr, addr + 1) without 35 | // any need to worry about whether addr + 1 will wrap to 0. 36 | AddressRange(uint16_t addr); 37 | 38 | AddressRange(uint32_t range_begin, uint32_t range_end); 39 | 40 | uint32_t range_begin() const 41 | { 42 | return range_begin_; 43 | } 44 | 45 | uint32_t range_end() const 46 | { 47 | return range_end_; 48 | } 49 | 50 | // Return true iff AddressRange covers the whole of memory. 51 | bool all_memory() const; 52 | 53 | class const_iterator 54 | { 55 | friend class AddressRange; 56 | 57 | public: 58 | uint16_t operator*() const 59 | { 60 | // Truncating down to 16 bits gives exactly the behaviour we 61 | // require if this is a range which uses values >= 0x10000 to 62 | // indicate wrapping around to the start of memory. 63 | return static_cast(v_); 64 | } 65 | 66 | const_iterator &operator++() 67 | { 68 | ++v_; 69 | return *this; 70 | } 71 | 72 | bool operator!=(const const_iterator &rhs) 73 | { 74 | return v_ != rhs.v_; 75 | } 76 | 77 | private: 78 | const_iterator(uint32_t v) 79 | : v_(v) 80 | { 81 | } 82 | 83 | uint32_t v_; 84 | }; 85 | 86 | const_iterator begin() const 87 | { 88 | return const_iterator(range_begin_); 89 | } 90 | 91 | const_iterator end() const 92 | { 93 | return const_iterator(range_end_); 94 | } 95 | 96 | private: 97 | uint32_t range_begin_; 98 | uint32_t range_end_; 99 | }; 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /AddressSet.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | #include "AddressSet.h" 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "AddressRange.h" 25 | #include "util.h" 26 | 27 | void AddressSet::insert(uint16_t address) 28 | { 29 | set_.insert(address); 30 | } 31 | 32 | void AddressSet::insert(const AddressRange &range) 33 | { 34 | for (AddressRange::const_iterator it = range.begin(); it != range.end(); 35 | ++it) 36 | { 37 | set_.insert(*it); 38 | } 39 | } 40 | 41 | namespace 42 | { 43 | std::string dump_range(uint32_t range_start, uint32_t range_end) 44 | { 45 | std::stringstream s; 46 | s << std::hex << std::setfill('0'); 47 | if ((range_start + 1) == range_end) 48 | { 49 | s << "0x" << std::setw(4) << range_start; 50 | } 51 | else 52 | { 53 | // It's probably more readable to dump in this (inclusive) format 54 | // than to insist on using the half-open intervals which are 55 | // "natural" in the code itself. 56 | s << "0x" << std::setw(4) << range_start << "-" << 57 | "0x" << std::setw(4) << (range_end - 1); 58 | } 59 | return s.str(); 60 | } 61 | } 62 | 63 | std::string AddressSet::dump(int indent) const 64 | { 65 | std::stringstream s; 66 | 67 | bool in_range = false; 68 | uint32_t range_start; 69 | uint32_t range_last; 70 | for (AddressSet::const_iterator it = set_.begin(); it != set_.end(); ++it) 71 | { 72 | uint16_t i = *it; 73 | if (!in_range) 74 | { 75 | range_start = i; 76 | range_last = i; 77 | in_range = true; 78 | } 79 | else 80 | { 81 | if (i != (range_last + 1)) 82 | { 83 | s << spaces(indent) << 84 | dump_range(range_start, range_last + 1) << "\n"; 85 | range_start = i; 86 | } 87 | range_last = i; 88 | } 89 | } 90 | if (in_range) 91 | { 92 | s << spaces(indent) << dump_range(range_start, range_last + 1) << "\n"; 93 | } 94 | return s.str(); 95 | } 96 | -------------------------------------------------------------------------------- /AddressSet.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | #ifndef ADDRESSSET_H 19 | #define ADDRESSSET_H 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | class AddressRange; 26 | 27 | class AddressSet 28 | { 29 | private: 30 | // This might not be the perfect representation, but it's simple and clean, 31 | // so let's stick with it unless profiling shows this is a problem. 32 | typedef std::set Container; 33 | 34 | public: 35 | AddressSet() 36 | { 37 | } 38 | 39 | void insert(uint16_t address); 40 | 41 | void insert(const AddressRange &range); 42 | 43 | typedef Container::const_iterator const_iterator; 44 | 45 | const_iterator begin() const 46 | { 47 | return set_.begin(); 48 | } 49 | 50 | const_iterator end() const 51 | { 52 | return set_.end(); 53 | } 54 | 55 | Container::size_type size() const 56 | { 57 | return set_.size(); 58 | } 59 | 60 | std::string dump(int indent) const; 61 | 62 | private: 63 | std::set set_; 64 | }; 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | TL;DR: If you're redistributing this you should read through the text below and 2 | examine the headers on the individual files, but basically the C/C++ source 3 | code (with the exception of valgrind.h, which can be removed if necessary) was 4 | all written by Ian Piumarta or Steven Flintham and is licensed under the "MIT 5 | (X11 flavour)" licence at the bottom of this file, just as lib6502 itself is. 6 | The autotools infrastructure support is GPL licensed but has exceptions for use 7 | (as is the case here) in autoconfigured packages. 8 | 9 | 10 | 11 | valgrind.h has its own license; see the comments at the top of that file. 12 | 13 | build-aux/tap-driver.sh (used as part of "make check") is GPLv2 licensed with 14 | an exception (which I believe applies to this package) allowing distribution 15 | under "the same distribution terms that you use for the rest of that program". 16 | See the comments at the top of that file for more details. 17 | 18 | m4/boost.m4 (used to autoconfigure the build against the Boost libraries) is 19 | GPLv3 licensed with an exception (which I believe applies to this package) 20 | allowing distribution under "terms of your choice". See the comments at the top 21 | of that file for more details. 22 | 23 | The text below is from Ian Piumarta's lib6502's COPYING file. lib6502-jit 24 | contains almost all of the code and documentation from lib6502 itself. 25 | 26 | As the author of the remaining parts of lib6502-jit, I am granting the same 27 | permissions and have added my own copyright notice, but the text below is 28 | otherwise unchanged. 29 | 30 | -- Steven Flintham 31 | 32 | 33 | 34 | Distasteful though it is for me to have to induce from afar any perturbation 35 | into your pursuit of happiness, this MIT (X11 flavour) license is at least 36 | relatively benign. Investigation into copyright stupidity reveals that it is 37 | effectively impossible to dedicate (formally) any software to the public 38 | domain (the only sure path to this most enlightened status being to leave the 39 | software to expire naturally from its 25-, 50-, 75- or whatever-year copyright 40 | rot). I fear this is not going to change before the revolution comes. In the 41 | meantime the only way I can *guarantee* you any rights at all to this software 42 | would (unfortunately) appear to be... 43 | 44 | Copyright (c) 2005 Ian Piumarta 45 | Copyright (c) 2014 Steven Flintham 46 | 47 | All rights reserved. 48 | 49 | Permission is hereby granted, free of charge, to any person obtaining a copy 50 | of this software and associated documentation files (the 'Software'), to 51 | deal in the Software without restriction, including without limitation the 52 | rights to use, copy, modify, merge, publish, distribute, and/or sell copies 53 | of the Software, and to permit persons to whom the Software is furnished to 54 | do so, provided that the above copyright notice(s) and this permission 55 | notice appear in all copies or substantial portions of the Software. 56 | 57 | Inclusion of the above copyright notice(s) and this permission notice in 58 | supporting documentation would be appreciated, but is not required. 59 | 60 | THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 61 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | lib6502-jit implements (nearly) the same API as Ian Piumarta's lib6502 2 | (http://www.piumarta.com/software/lib6502/) and includes virtually all of 3 | lib6502's code and documentation with only minor modifications; the lib6502 4 | emulation code is used to implement the interpreted and hybrid emulation modes 5 | in lib6502-jit. The contents of the examples and man directories are almost 6 | verbatim copies of those in lib6502. Thanks to Ian for making lib6502 7 | available. Please do not send bug reports regarding lib6502-jit to Ian! 8 | 9 | This distribution itself doesn't contain any LLVM code, but obviously without 10 | the LLVM project lib6502-jit could not exist. 11 | 12 | valgrind.h is taken from Valgrind (http://valgrind.org/). 13 | 14 | build-aux/tap-driver.sh is part of GNU Automake and was taken from 15 | https://raw.githubusercontent.com/kergoth/automake/master/lib/tap-driver.sh. 16 | 17 | m4/boost.m4 (used to autoconfigure the build against the Boost libraries) is 18 | taken from https://github.com/tsuna/boost.m4. 19 | 20 | While I'd be lying if I said I enjoyed working with Autotools, I am grateful 21 | for the work people have put in to make it possible to build packages portably 22 | on a range of different platforms. 23 | 24 | The technique (but not the code) used to translate a JITted function's machine 25 | code into assembly in Function::dump_machine_code() is taken from the libjit 26 | (https://www.gnu.org/software/libjit/) dump_object_code() function. 27 | 28 | The algorithm used to implement ADC/SDC in decimal mode is taken from 29 | http://www.6502.org/tutorials/decimal_mode.html. The test program on the same 30 | page was used to validate the implementation. 31 | 32 | Klaus Dormann's "6502 functional test" and "65C02 extended opcodes test" were 33 | used to validate the behaviour of the emulation. 34 | -------------------------------------------------------------------------------- /Function.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | #include "Function.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "valgrind.h" 25 | 26 | #include "const.h" 27 | #include "LLVMStuff.h" 28 | #include "M6502Internal.h" 29 | #include "Registers.h" 30 | #include "util.h" 31 | 32 | // Note that we call update_memory_snapshot() after invoking callbacks here, but 33 | // not before. It would be correct to do so, but it's not necessary. Firstly, we 34 | // arrange that the memory snapshot is kept up-to-date during execution under 35 | // our control (i.e. not involving callbacks), so it isn't necessary. Secondly, 36 | // even if it were necessary, it would be redundant, since any actions needed 37 | // as a result of the update can wait until after the callback is called and the 38 | // call after the callback would perform them. 39 | 40 | namespace 41 | { 42 | // We have the callback_pc argument to allow us to special-case the 43 | // contents of the PC register for lib6502 compatibility. Without this 44 | // we would always pass registers.pc, which is "address of the next 45 | // instruction to execute if the callback doesn't intervene" in PC; 46 | // this agrees with lib6502 for JMP (absolute and indirect) but not for JSR 47 | // or BRK. 48 | uint16_t handle_call_callback(M6502 *mpu, uint16_t callback_pc, 49 | uint8_t opcode) 50 | { 51 | Registers ®isters = mpu->internal->registers_; 52 | uint16_t default_next_pc = registers.pc; 53 | if (mpu->callbacks->call[registers.pc] != 0) 54 | { 55 | registers.pc = callback_pc; 56 | registers.to_M6502_Registers(mpu); 57 | TRACE("Call callback, mpu " << mpu << ", address 0x" << std::hex << 58 | std::setfill('0') << std::setw(4) << default_next_pc << 59 | ", data 0x" << std::setw(2) << static_cast(opcode)); 60 | uint16_t address = default_next_pc; 61 | if (opcode == opcode_brk) 62 | { 63 | address = callback_pc - 2; // lib6502 does this 64 | } 65 | int callback_result = 66 | mpu->callbacks->call[default_next_pc](mpu, address, opcode); 67 | TRACE("Callback returned 0x" << std::hex << std::setfill('0') << 68 | std::setw(4) << callback_result); 69 | registers.from_M6502_Registers(mpu); 70 | mpu->internal->function_manager_.update_memory_snapshot(); 71 | if (callback_result != 0) 72 | { 73 | return callback_result; 74 | } 75 | } 76 | return default_next_pc; 77 | } 78 | 79 | uint16_t get_stacked_pc(M6502 *mpu, int offset) 80 | { 81 | uint8_t s = mpu->internal->registers_.s; 82 | 83 | for (; offset > 0; --offset) 84 | { 85 | ++s; 86 | } 87 | 88 | ++s; 89 | uint8_t pushed_pc_low = mpu->memory[0x100 + s]; 90 | ++s; 91 | uint8_t pushed_pc_high = mpu->memory[0x100 + s]; 92 | return pushed_pc_low | (pushed_pc_high << 8); 93 | } 94 | 95 | uint16_t handle_push_and_control_transfer_opcode( 96 | M6502 *mpu, uint16_t callback_pc, uint8_t opcode, int bytes_pushed) 97 | { 98 | assert(bytes_pushed >= 2); 99 | 100 | uint8_t s = mpu->internal->registers_.s; 101 | for (int i = 0; i < bytes_pushed; ++i) 102 | { 103 | ++s; 104 | mpu->internal->function_manager_.code_modified_at(0x100 + s); 105 | } 106 | 107 | return handle_call_callback(mpu, callback_pc, opcode); 108 | } 109 | } 110 | 111 | Function::Function( 112 | M6502 *mpu, uint16_t address, const AddressSet &code_range, 113 | const AddressSet &optimistic_writes, llvm::Function *llvm_function) 114 | : mpu_(mpu), 115 | llvm_stuff_(mpu->internal->llvm_stuff_), 116 | address_(address), 117 | code_range_(code_range), 118 | optimistic_writes_(optimistic_writes), 119 | llvm_function_(llvm_function), 120 | jitted_function_(reinterpret_cast( 121 | llvm_stuff_.execution_engine_->getPointerToFunction(llvm_function))) 122 | { 123 | llvm_stuff_.execution_engine_->runJITOnFunction(llvm_function_, &mci_); 124 | } 125 | 126 | Function::~Function() 127 | { 128 | TRACE("Destructor for Function at address " << std::hex << 129 | std::setfill('0') << std::setw(4) << address_); 130 | 131 | VALGRIND_DISCARD_TRANSLATIONS(mci_.address(), mci_.size()); 132 | llvm_function_->eraseFromParent(); 133 | } 134 | 135 | void Function::handle_complex_result(FunctionBuilder::Result result) const 136 | { 137 | Registers ®isters = mpu_->internal->registers_; 138 | 139 | switch (result) 140 | { 141 | case FunctionBuilder::result_control_transfer_direct: 142 | CANT_HAPPEN("Direct case reached handle_complex_result()"); 143 | 144 | case FunctionBuilder::result_control_transfer_indirect: 145 | registers.pc = handle_call_callback(mpu_, registers.pc, 146 | registers.data); 147 | break; 148 | 149 | case FunctionBuilder::result_brk: 150 | registers.pc = handle_push_and_control_transfer_opcode( 151 | mpu_, get_stacked_pc(mpu_, 1), opcode_brk, 3); 152 | break; 153 | 154 | case FunctionBuilder::result_jsr_complex: 155 | registers.pc = handle_push_and_control_transfer_opcode( 156 | mpu_, get_stacked_pc(mpu_, 0) + 1, opcode_jsr, 2); 157 | break; 158 | 159 | case FunctionBuilder::result_illegal_instruction: 160 | { 161 | registers.to_M6502_Registers(mpu_); 162 | TRACE("Illegal instruction callback, mpu " << mpu_ << 163 | ", address 0x" << std::hex << std::setfill('0') << 164 | std::setw(4) << registers.addr << ", data 0x" << 165 | std::setw(2) << static_cast(registers.data)); 166 | uint16_t new_pc = 167 | mpu_->callbacks->illegal_instruction[registers.data]( 168 | mpu_, registers.addr, registers.data); 169 | TRACE("Callback returned 0x" << std::hex << std::setfill('0') << 170 | std::setw(4) << new_pc); 171 | registers.from_M6502_Registers(mpu_); 172 | mpu_->internal->function_manager_.update_memory_snapshot(); 173 | if (new_pc != 0) 174 | { 175 | registers.pc = new_pc; 176 | } 177 | break; 178 | } 179 | 180 | case FunctionBuilder::result_write_to_code: 181 | TRACE("Code modified at 0x" << std::hex << std::setfill('0') << 182 | std::setw(4) << registers.addr); 183 | mpu_->internal->function_manager_.code_modified_at(registers.addr); 184 | break; 185 | 186 | case FunctionBuilder::result_write_callback: 187 | { 188 | TRACE("Write callback at 0x" << std::hex << std::setfill('0') << 189 | std::setw(4) << registers.addr << " with data 0x" << 190 | std::setw(4) << static_cast(registers.data)); 191 | // We *don't* invoke Registers.{to,from}_M6502Registers() before 192 | // and after the callback. We could do this, but lib6502 itself 193 | // (and therefore the lib6502 code used for interpreting in 194 | // lib6502-jit) doesn't do that, so this could be confusing 195 | // for client code. (For example, a callback might be written 196 | // to rely on this, it would work if called from compiled code 197 | // but wouldn't work if called from interpreted mode. So its 198 | // behaviour in hybrid mode would be random.) 199 | (void) mpu_->callbacks->write[registers.addr]( 200 | mpu_, registers.addr, registers.data); 201 | mpu_->internal->function_manager_.update_memory_snapshot(); 202 | break; 203 | } 204 | 205 | case FunctionBuilder::result_invalid_bounds: 206 | CANT_HAPPEN("Invalid bounds inside Function for address 0x" << 207 | std::hex << std::setfill('0') << std::setw(4) << 208 | address_); 209 | 210 | default: 211 | CANT_HAPPEN("Unknown result " << result << " from JIT function"); 212 | } 213 | } 214 | 215 | #ifdef LOG 216 | 217 | namespace 218 | { 219 | std::string indent(int n, const std::string &s) 220 | { 221 | std::string prefix = spaces(n); 222 | return apply_prefix(prefix, s); 223 | } 224 | } 225 | 226 | std::string Function::dump_all() const 227 | { 228 | std::stringstream s; 229 | s << "Function at 0x" << std::hex << std::setfill('0') << std::setw(4) << 230 | address_ << ":\n"; 231 | s << spaces(1) << "Code range:\n" << code_range_.dump(2) << "\n"; 232 | s << spaces(1) << "Optimistic writes at:\n" << optimistic_writes_.dump(2) << 233 | "\n"; 234 | s << spaces(1) << "6502 machine code:\n" << indent(2, disassembly_) << "\n"; 235 | s << spaces(1) << "Unoptimised IR:\n" << indent(2, unoptimised_ir_) << "\n"; 236 | s << spaces(1) << "Optimised IR:\n" << indent(2, optimised_ir_) << "\n";; 237 | s << spaces(1) << "Host machine code:\n" << indent(2, dump_machine_code()); 238 | return s.str(); 239 | } 240 | 241 | #endif 242 | 243 | namespace 244 | { 245 | template 246 | class AutoClose : boost::noncopyable 247 | { 248 | public: 249 | AutoClose(Handle h) 250 | : open_(true), h_(h) 251 | { 252 | } 253 | 254 | int close() 255 | { 256 | open_ = false; 257 | return close_fn(h_); 258 | } 259 | 260 | ~AutoClose() 261 | { 262 | if (open_) 263 | { 264 | close_fn(h_); // ignore return code, nothing we can do if it fails 265 | } 266 | } 267 | 268 | private: 269 | bool open_; 270 | Handle h_; 271 | }; 272 | 273 | typedef int (*FdClose)(int); 274 | typedef AutoClose FdAutoClose; 275 | typedef int (*PopenClose)(FILE *); 276 | typedef AutoClose PopenAutoClose; 277 | } 278 | 279 | #ifdef LOG 280 | 281 | std::string Function::dump_machine_code() const 282 | { 283 | try 284 | { 285 | // What a performance! The basic idea of outputting .bytes directives, 286 | // assembling those and then disassembling the result is taken from 287 | // libjit's dump_object_code(); the implementation is not copied. 288 | 289 | char as_output_file[] = "/tmp/lib6502-jit-XXXXXX"; 290 | 291 | errno = 0; 292 | 293 | // mkstemp() creates a unique filename and opens it. We unlink the file 294 | // immediately so it has no name; this minimises (but does not 295 | // eliminate; we might be killed between mkstemp() and unlink()) the 296 | // chance of the file being left lying around. Since we need a name for 297 | // the 'as' and 'objdump' commands, we use /dev/fd/nn to refer to it 298 | // afterwards. 299 | int fd = mkstemp(as_output_file); 300 | if (fd == -1) 301 | { 302 | fail_errno_or("mkstemp() failed"); 303 | } 304 | FdAutoClose auto_close_fd(fd); 305 | if (unlink(as_output_file) == -1) 306 | { 307 | fail_errno_or("unlink() failed"); 308 | } 309 | 310 | { 311 | std::stringstream as_command; 312 | as_command << "as -o /dev/fd/" << fd << " 2>/dev/null"; 313 | FILE *f = popen(as_command.str().c_str(), "w"); 314 | if (f == 0) 315 | { 316 | fail_errno_or("popen() failed (for 'as')"); 317 | } 318 | PopenAutoClose auto_close_f(f); 319 | unsigned char *p = static_cast(mci_.address()); 320 | unsigned char *end = p + mci_.size(); 321 | for (; p < end; ++p) 322 | { 323 | if (fprintf(f, ".byte %d\n", *p) < 0) 324 | { 325 | fail("Error writing to 'as' pipe"); 326 | } 327 | } 328 | if (auto_close_f.close() != 0) 329 | { 330 | fail_errno_or("Error closing 'as' pipe"); 331 | } 332 | } 333 | 334 | if (lseek(fd, 0, SEEK_SET) == static_cast(-1)) 335 | { 336 | fail_errno_or("Error seeking on temporary file"); 337 | } 338 | 339 | std::stringstream objdump_command; 340 | // As far as I can tell, there's no guarantee how mci_.address() [a 341 | // pointer type] will be represented in the stringstream, but in 342 | // practice this code is not very portable anyway and this is the least 343 | // of our worries... 344 | objdump_command << "objdump --adjust-vma=" << 345 | mci_.address() << " -d /dev/fd/" << fd << " 2>&1"; 346 | FILE *g = popen(objdump_command.str().c_str(), "r"); 347 | if (g == 0) 348 | { 349 | fail_errno_or("popen() failed (for 'objdump')"); 350 | } 351 | PopenAutoClose auto_close_g(g); 352 | 353 | std::stringstream code; 354 | char buffer[1024]; 355 | size_t bytes_read; 356 | while ((bytes_read = fread(buffer, 1, sizeof(buffer), g)) > 0) 357 | { 358 | code << std::string(buffer, bytes_read); 359 | } 360 | if (ferror(g)) 361 | { 362 | fail("Error reading from 'objdump' pipe"); 363 | } 364 | if (auto_close_g.close() != 0) 365 | { 366 | fail_errno_or("Error closing 'objdump' pipe"); 367 | } 368 | if (auto_close_fd.close() != 0) 369 | { 370 | fail_errno_or("Error closing temporary file"); 371 | } 372 | 373 | return code.str(); 374 | } 375 | catch (std::exception &e) 376 | { 377 | // Dumping out the generated machine code is decidedly not critical, so 378 | // we don't allow the exception to propagate. 379 | return std::string("Unable to dump machine code: ") + e.what(); 380 | } 381 | } 382 | 383 | void Function::fail(const std::string &error) const 384 | { 385 | throw std::runtime_error(error); 386 | } 387 | 388 | void Function::fail_errno_or(const std::string &error) const 389 | { 390 | if (errno == 0) 391 | { 392 | fail(error); 393 | } 394 | else 395 | { 396 | // strerror_r() exists in various versions. If you have problems getting 397 | // this to compile, it's probably OK to just use: 398 | // const char *error = strerror(errno); 399 | // given a) the limited amount of threading here and b) the fact this is 400 | // only used to report rare errors in debug-only logging code. If push 401 | // really comes to shove you can just do: 402 | // const char *error = 0; 403 | // and you'll just get unhelpful error messages. 404 | char buffer[1024]; 405 | const char *error = strerror_r(errno, buffer, sizeof(buffer)); 406 | if (error != 0) 407 | { 408 | fail(error); 409 | } 410 | else 411 | { 412 | fail("Error occurred, and strerror() probably failed as well"); 413 | } 414 | } 415 | } 416 | 417 | #endif 418 | -------------------------------------------------------------------------------- /Function.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | #ifndef FUNCTION_H 19 | #define FUNCTION_H 20 | 21 | #include 22 | #include 23 | #include "llvm/CodeGen/MachineCodeInfo.h" 24 | #include "llvm/IR/Value.h" 25 | 26 | #include "AddressSet.h" 27 | #include "FunctionBuilder.h" 28 | #include "lib6502.h" 29 | 30 | struct LLVMStuff; 31 | 32 | class Function : boost::noncopyable 33 | { 34 | public: 35 | Function(M6502 *mpu, uint16_t address, const AddressSet &code_range, 36 | const AddressSet &optimistic_writes, 37 | llvm::Function *llvm_function); 38 | ~Function(); 39 | 40 | uint16_t address() const 41 | { 42 | return address_; 43 | } 44 | 45 | const AddressSet &code_range() const 46 | { 47 | return code_range_; 48 | } 49 | 50 | const AddressSet &optimistic_writes() const 51 | { 52 | return optimistic_writes_; 53 | } 54 | 55 | void execute() const 56 | { 57 | FunctionBuilder::Result result = 58 | static_cast((*jitted_function_)()); 59 | if (result != FunctionBuilder::result_control_transfer_direct) 60 | { 61 | handle_complex_result(result); 62 | } 63 | } 64 | 65 | #ifdef LOG 66 | void set_disassembly(const std::string &s) 67 | { 68 | disassembly_ = s; 69 | } 70 | 71 | void set_unoptimised_ir(const std::string &s) 72 | { 73 | unoptimised_ir_ = s; 74 | } 75 | 76 | void set_optimised_ir(const std::string &s) 77 | { 78 | optimised_ir_ = s; 79 | } 80 | 81 | std::string dump_all() const; 82 | 83 | std::string dump_machine_code() const; 84 | #endif 85 | 86 | private: 87 | void handle_complex_result(FunctionBuilder::Result result) const; 88 | 89 | #ifdef LOG 90 | void fail(const std::string &error) const; 91 | void fail_errno_or(const std::string &error) const; 92 | #endif 93 | 94 | M6502 *mpu_; 95 | LLVMStuff &llvm_stuff_; 96 | uint16_t address_; 97 | AddressSet code_range_; 98 | AddressSet optimistic_writes_; 99 | llvm::Function *llvm_function_; 100 | llvm::MachineCodeInfo mci_; 101 | 102 | typedef int (*JitFunction)(); 103 | JitFunction jitted_function_; 104 | 105 | #ifdef LOG 106 | std::string disassembly_; 107 | std::string unoptimised_ir_; 108 | std::string optimised_ir_; 109 | #endif 110 | }; 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /FunctionBuilder.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | #ifndef FUNCTIONBUILDER_H 19 | #define FUNCTIONBUILDER_H 20 | 21 | #include 22 | #include 23 | #include "llvm/IR/IRBuilder.h" 24 | #include "llvm/IR/TypeBuilder.h" 25 | #include "llvm/IR/Value.h" 26 | #include 27 | #include 28 | #include 29 | 30 | #include "AddressSet.h" 31 | #include "const.h" 32 | #include "JitBool.h" 33 | #include "lib6502.h" 34 | 35 | class Function; 36 | struct LLVMStuff; 37 | 38 | class FunctionBuilder : boost::noncopyable 39 | { 40 | public: 41 | // Create a FunctionBuilder object which can be used to build a Function 42 | // representing the code starting at 'address'. The Function object built 43 | // will operate on the given M6502 object. The 'code_at_address' array 44 | // will be used at compile time and at runtime to decide if writes to 45 | // memory may invalidate already JITted code. The memory inside the M6502 46 | // object will be used when the Funtion object executes, but ct_memory 47 | // will be used at compile time to determine the instructions to compile; 48 | // see FunctionManager for more on this. 49 | FunctionBuilder(M6502 *mpu, const uint8_t *ct_memory, 50 | JitBool *code_at_address, uint16_t address); 51 | 52 | boost::shared_ptr build(); 53 | 54 | // Status codes returned by the JITted function 55 | enum Result 56 | { 57 | // Control has transferred to the address in registers.pc. No call 58 | // callback should be invoked, either because the JITted function knows 59 | // there is no applicable call callback or because the control transfer 60 | // is via an instruction which does not trigger call callbacks. 61 | result_control_transfer_direct, 62 | 63 | // Control has transferred to the address in registers.pc via an 64 | // instruction which is eligible for call callbacks. registers.data 65 | // contains the opcode of the instruction which transferred 66 | // control. The caller should check for an applicable call 67 | // callback. registers.addr is *not* updated; the addr value for 68 | // the callback is registers.pc. 69 | result_control_transfer_indirect, 70 | 71 | // A BRK instruction has just been executed and registers.pc updated 72 | // to point to the BRK vector. The caller should check to see if the 73 | // stack pushes implicitly performed by BRK have invalidated any 74 | // already-JITted code and for a call callback on the BRK vector. 75 | // Neither registers.addr nor registers.data are updated. 76 | result_brk, 77 | 78 | // A JSR instruction has just been executed and registers.pc 79 | // updated to point to the destination address. One or both of the 80 | // following may be true: - the stack pushes implicitly performed 81 | // have invalidated some 82 | // already-JITted code 83 | // - a call callback is registered on the destination address It is not 84 | // guaranteed that either of these is the case, although in practice 85 | // with this implementation at least one should be true. Not all JSR 86 | // instructions will necessarily cause the JITted function to return 87 | // this value, hence the result code is result_jsr_*complex* not just 88 | // result_jsr. Neither registers.addr nor registers.data are updated. 89 | result_jsr_complex, 90 | 91 | // An illegal instruction has been executed and registers.pc updated to 92 | // point to the following opcode. registers.addr contains the address 93 | // of the illegal instruction and registers.data its opcode. The 94 | // caller should check to see if a callback is registered. 95 | result_illegal_instruction, 96 | 97 | // A memory write has been executed which changed an address marked 98 | // as holding code. registers.addr contains the address modified. The 99 | // caller should invalidate any JITted functions for this address. 100 | result_write_to_code, 101 | 102 | // A memory write has occurred which triggers a write callback. Memory 103 | // has not been updated. registers.addr and registers.data contain the 104 | // address and the data being written respectively. The caller should 105 | // invoke the write callback and check for writes to already-JITted 106 | // code. 107 | result_write_callback, 108 | 109 | // Internal bounds generated for an instruction's address range were 110 | // found to be invalid by self-checking code. This can only occur 111 | // in debug builds and then only if there is a bug in FunctionBuilder. 112 | result_invalid_bounds 113 | }; 114 | 115 | private: 116 | uint16_t build_at(uint16_t ct_pc); 117 | 118 | uint8_t operand8(uint16_t opcode_at); 119 | uint16_t operand16(uint16_t opcode_at); 120 | 121 | llvm::Value *constant_i1(bool c); 122 | llvm::Value *constant_u8(uint8_t c); 123 | llvm::Value *constant_u16(uint16_t c); 124 | llvm::Value *constant_u32(uint32_t c); 125 | llvm::Value *constant_u64(uint64_t c); 126 | 127 | template 128 | llvm::Value *constant_ptr(T *p, const std::string &name) 129 | { 130 | llvm::Value *v = constant_u64(reinterpret_cast(p)); 131 | // The name passed in never seems to be used, but maybe this will 132 | // change in the future. It doesn't really do us any harm to pass 133 | // it in anyway. 134 | return builder_.CreateIntToPtr( 135 | v, llvm::TypeBuilder::get(llvm::getGlobalContext()), 136 | name); 137 | } 138 | 139 | llvm::Value *constant_i(int c); 140 | 141 | llvm::Value *constant_jb(JitBool c); 142 | llvm::Value *convert_i1_to_jb(llvm::Value *v); 143 | llvm::Value *convert_i8_to_jb(llvm::Value *v); 144 | llvm::Value *convert_i16_to_jb(llvm::Value *v); 145 | llvm::Value *jit_bool_is_true(llvm::Value *v); 146 | llvm::Value *jit_bool_is_false(llvm::Value *v); 147 | 148 | llvm::Value *convert_i1_to_i8(llvm::Value *v); 149 | 150 | llvm::Value *zext_i16(llvm::Value *v); 151 | llvm::Value *zext_i32(llvm::Value *v); 152 | llvm::Value *sext_i16(llvm::Value *v); 153 | llvm::Value *trunc_i8(llvm::Value *v); 154 | llvm::Value *create_u16(llvm::Value *low_byte, llvm::Value *high_byte); 155 | 156 | struct Register 157 | { 158 | llvm::Value *v_; 159 | bool modified_; 160 | }; 161 | void initialise_i8_reg(Register &r, int structure_index, 162 | const std::string &name); 163 | void initialise_jb_reg(Register &r, int structure_index, 164 | const std::string &name); 165 | 166 | void ensure_address_block_created(uint16_t addr); 167 | 168 | void return_pc(Result result, llvm::Value *new_pc); 169 | void return_pc_addr(Result result, llvm::Value *new_pc, llvm::Value *addr); 170 | void return_pc_data(Result result, llvm::Value *new_pc, llvm::Value *data); 171 | void return_pc_addr_data(Result result, llvm::Value *new_pc, 172 | llvm::Value *addr, llvm::Value *data); 173 | void return_control_transfer_direct(llvm::Value *new_pc); 174 | void return_control_transfer_indirect(llvm::Value *new_pc, uint8_t opcode); 175 | void return_brk(llvm::Value *new_pc); 176 | void return_jsr_complex(llvm::Value *new_pc); 177 | void return_illegal_instruction(uint16_t new_pc, uint16_t opcode_at, 178 | uint8_t opcode); 179 | void return_write_to_code(uint16_t new_pc, llvm::Value *addr); 180 | void return_write_callback(uint16_t new_pc, llvm::Value *addr, 181 | llvm::Value *data); 182 | void return_invalid_bounds(); 183 | 184 | class BoundedAddress; 185 | 186 | llvm::Value *register_load(const Register &r); 187 | void register_store(llvm::Value *v, Register &r); 188 | 189 | typedef llvm::Value *(FunctionBuilder::*OpFn)(llvm::Value *data); 190 | void register_op(OpFn op, Register &r); 191 | void memory_op(OpFn op, const BoundedAddress &ba, uint16_t next_opcode_at); 192 | 193 | llvm::Value *is_code_at(const BoundedAddress &addr); 194 | 195 | void adc(llvm::Value *data); 196 | void adc_llvm(llvm::Value *data); 197 | void adc_binary(llvm::Value *data); 198 | void adc_decimal(llvm::Value *data); 199 | void adc_binary_llvm(llvm::Value *data); 200 | void adc_decimal_llvm(llvm::Value *data); 201 | void And(llvm::Value *data); 202 | llvm::Value *asl(llvm::Value *data); 203 | void bit(llvm::Value *data); 204 | void branch(Register &flag, bool branch_if, uint16_t target); 205 | void cmp(llvm::Value *r, llvm::Value *data); 206 | void cmp_llvm(llvm::Value *r, llvm::Value *data); 207 | llvm::Value *dec(llvm::Value *data); 208 | void eor(llvm::Value *data); 209 | llvm::Value *inc(llvm::Value *data); 210 | void ld(Register &r, llvm::Value *data); 211 | llvm::Value *lsr(llvm::Value *data); 212 | void ora(llvm::Value *data); 213 | void pop_flags(); 214 | llvm::Value *pop_u8(); 215 | llvm::Value *pop_u16(); 216 | void push_u8_raw(llvm::Value *data); 217 | void push_u16_raw(uint16_t u); 218 | void push_u8(llvm::Value *data, uint16_t next_opcode_at); 219 | llvm::Value *rol(llvm::Value *data); 220 | llvm::Value *ror(llvm::Value *data); 221 | void sbc(llvm::Value *data); 222 | void sbc_binary(llvm::Value *data); 223 | void sbc_decimal(llvm::Value *data); 224 | void sbc_overflow(llvm::Value *data, 225 | llvm::Value *borrow); 226 | void transfer(const Register &from, Register &to); 227 | llvm::Value *trb(llvm::Value *data); 228 | llvm::Value *tsb(llvm::Value *data); 229 | 230 | void set_nz(llvm::Value *data); 231 | void set_z(llvm::Value *data); 232 | 233 | llvm::Value *flag_byte(); 234 | void flag_byte_bit(const Register &flag_reg, uint8_t flag_bit); 235 | 236 | void illegal_instruction(uint16_t &ct_pc, int bytes); 237 | 238 | BoundedAddress zp(uint8_t addr); 239 | BoundedAddress abs(uint16_t addr); 240 | BoundedAddress abs_index(llvm::Value *abs, 241 | llvm::Value *index); 242 | BoundedAddress zp_index(llvm::Value *zp, 243 | llvm::Value *r); 244 | BoundedAddress zp_post_index( 245 | llvm::Value *zp, llvm::Value *index); 246 | BoundedAddress zp_pre_index( 247 | llvm::Value *zp, llvm::Value *index); 248 | 249 | llvm::Value *check_predicted_rts(uint16_t subroutine_addr); 250 | 251 | // A special opcode used as the third argument to control_transfer_to 252 | // when there is no explicit opcode causing the control transfer; this 253 | // is just a documented way to signal that the control transfer is direct 254 | // and cannot trigger a call callback. 255 | enum { 256 | opcode_implicit = 0xff 257 | }; 258 | void control_transfer_to(llvm::Value *target, uint8_t opcode); 259 | 260 | llvm::Value *memory_read(const BoundedAddress &ba); 261 | llvm::Value *memory_read_untrapped(const BoundedAddress &ba); 262 | 263 | void memory_write(const BoundedAddress &ba, 264 | llvm::Value *data, uint16_t next_opcode_at); 265 | void memory_write_untrapped(const BoundedAddress &ba, 266 | llvm::Value *data, uint16_t next_opcode_at); 267 | void memory_write_raw(const BoundedAddress &ba, 268 | llvm::Value *data); 269 | 270 | llvm::Value *call_callback( 271 | llvm::Value *callback, llvm::Value *addr, 272 | llvm::Value *data); 273 | llvm::Value *call_read_callback( 274 | llvm::Value *callback, llvm::Value *addr); 275 | 276 | void disassemble1(uint16_t &addr, const std::string &s); 277 | void disassemble2(uint16_t &addr, const std::string &prefix, 278 | uint8_t &operand, const std::string &suffix = ""); 279 | void disassemble3(uint16_t &addr, const std::string &prefix, 280 | uint16_t &operand, const std::string &suffix = ""); 281 | void disassemble_branch(uint16_t &addr, const std::string &s, 282 | uint16_t &target); 283 | void disassemble_hex_dump(uint16_t addr, int bytes); 284 | 285 | bool built_; 286 | 287 | M6502 *const mpu_; 288 | JitBool *code_at_address_; 289 | const uint16_t address_; 290 | const uint8_t *const ct_memory_; 291 | // callbacks_ is strictly redundant as it's available inside mpu, but 292 | // it's convenient. 293 | const M6502_Callbacks &callbacks_; 294 | 295 | AddressSet code_range_; 296 | AddressSet optimistic_writes_; 297 | 298 | std::stringstream disassembly_; 299 | 300 | int instructions_; 301 | const int max_instructions_; 302 | 303 | // This could be an AddressSet but since we "rely" on the order of 304 | // iteration for pending_ it seems better to be explicit; we don't need 305 | // any of the range-handling convenience of AddressSet here anyway. 306 | std::set pending_; 307 | 308 | std::map predicted_rts_targets_; 309 | 310 | llvm::LLVMContext &context_; 311 | 312 | llvm::Type *const native_int_type_; 313 | llvm::PointerType *const callback_type_; 314 | llvm::Type *const i1_type_; 315 | llvm::Type *const i8_type_; 316 | llvm::Type *const i16_type_; 317 | llvm::Type *const i32_type_; 318 | llvm::Type *const i64_type_; 319 | llvm::Type *const jit_bool_type_; 320 | 321 | llvm::IRBuilder<> &builder_; 322 | 323 | llvm::Function *llvm_function_; 324 | 325 | llvm::Value *registers_; 326 | llvm::Value *code_at_address_llvm_; 327 | llvm::Value *read_callbacks_; 328 | llvm::Value *write_callbacks_; 329 | llvm::Value *call_callbacks_; 330 | llvm::Value *memory_base_; 331 | llvm::Value *mpu_llvm_; 332 | 333 | llvm::Value *function_result_; 334 | 335 | // Note that address_block_ and code_generated_for_address_ aren't 336 | // redundant; address_block_ elements are created (for example) when 337 | // a branch means the corresponding address must have a BasicBlock 338 | // created for use as a branch target, but that doesn't mean code has 339 | // been generated for it yet. 340 | llvm::BasicBlock *address_block_[memory_size]; 341 | bool code_generated_for_address_[memory_size]; 342 | 343 | Register a_; 344 | Register x_; 345 | Register y_; 346 | Register s_; 347 | Register flag_n_; 348 | Register flag_v_; 349 | Register flag_d_; 350 | Register flag_i_; 351 | Register flag_z_; 352 | Register flag_c_; 353 | llvm::Value *pc_; 354 | 355 | llvm::Value *read_callback_result_; 356 | llvm::Value *p_tmp_; 357 | llvm::Value *l_tmp_; 358 | llvm::Value *s_tmp_; 359 | llvm::Value *t_tmp_; 360 | 361 | llvm::BasicBlock *epilogue_; 362 | }; 363 | 364 | #endif 365 | -------------------------------------------------------------------------------- /FunctionManager.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | #include "FunctionManager.h" 19 | 20 | #include 21 | 22 | #include "Function.h" 23 | #include "FunctionBuilder.h" 24 | #include "M6502Internal.h" 25 | #include "Registers.h" 26 | #include "util.h" 27 | 28 | FunctionManager::FunctionManager(M6502 *mpu) 29 | : jit_thread_idle_(true), work_available_(false), quit_(false), mpu_(mpu), 30 | memory_snapshot_(), function_for_address_(), code_at_address_() 31 | { 32 | } 33 | 34 | FunctionManager::~FunctionManager() 35 | { 36 | if (jit_thread_.get_id() != boost::thread::id()) 37 | { 38 | TRACE("Notifying JIT thread to quit"); 39 | { 40 | boost::mutex::scoped_lock lock(jit_thread_cv_mutex_); 41 | quit_ = true; 42 | } 43 | jit_thread_cv_.notify_all(); 44 | TRACE("Joining with JIT thread"); 45 | jit_thread_.join(); 46 | } 47 | } 48 | 49 | bool FunctionManager::jit_thread_idle() 50 | { 51 | boost::mutex::scoped_lock lock(jit_thread_idle_mutex_); 52 | return jit_thread_idle_; 53 | } 54 | 55 | void FunctionManager::update_memory_snapshot() 56 | { 57 | assert(jit_thread_idle()); 58 | 59 | const uint8_t *memory = mpu_->memory; 60 | for (size_t i = 0; i < memory_size; ++i) 61 | { 62 | if (code_at_address_[i] && (memory_snapshot_[i] != memory[i])) 63 | { 64 | code_modified_at(i); 65 | } 66 | memory_snapshot_[i] = memory[i]; 67 | } 68 | } 69 | 70 | Function *FunctionManager::build_function_internal( 71 | uint16_t address, const uint8_t *ct_memory) 72 | { 73 | Registers ®isters = mpu_->internal->registers_; 74 | TRACE("Building Function for code at 0x" << std::hex << std::setfill('0') << 75 | std::setw(4) << registers.pc); 76 | FunctionBuilder fb(mpu_, ct_memory, code_at_address_, registers.pc); 77 | boost::shared_ptr f(fb.build()); 78 | add_function(f); 79 | return f.get(); 80 | } 81 | 82 | Function *FunctionManager::build_function(uint16_t address, 83 | const uint8_t *ct_memory) 84 | { 85 | Function *f; 86 | int pass = 0; 87 | do 88 | { 89 | assert(pass < 2); 90 | ++pass; 91 | 92 | f = build_function_internal(address, ct_memory); 93 | 94 | bool f_is_optimistic_self_writer = false; 95 | const AddressSet &code_range = f->code_range(); 96 | for (AddressSet::const_iterator it = code_range.begin(); 97 | it != code_range.end(); ++it) 98 | { 99 | uint16_t i = *it; 100 | if (code_at_address_[i] && 101 | !optimistic_writers_for_address_[i].empty()) 102 | { 103 | // There is now code at an address where optimistic writes are 104 | // performed. Future code generation won't create optimistic 105 | // writes there because code_at_address_[i] has now been set, 106 | // but we need to destroy existing functions which perform 107 | // that write so they will be regenerated. 108 | const FunctionSet &optimistic_writers = 109 | optimistic_writers_for_address_[i]; 110 | f_is_optimistic_self_writer = 111 | (optimistic_writers.find(f) != optimistic_writers.end()); 112 | destroy_functions_in_set(optimistic_writers_for_address_[i]); 113 | if (f_is_optimistic_self_writer) 114 | { 115 | // destroy_functions_in_set() has now destroyed f, so a) 116 | // code_range is no longer a valid reference b) there's 117 | // no need to continue iterating over f's code range. 118 | break; 119 | } 120 | 121 | } 122 | } 123 | 124 | // We might just have destroyed the function we built, if it modified 125 | // its own code, so we need to loop round if so. 126 | f = function_for_address_[address]; 127 | if (f == 0) 128 | { 129 | assert(f_is_optimistic_self_writer); 130 | TRACE("Rebuilding just-created function"); 131 | } 132 | } 133 | while (f == 0); 134 | 135 | TRACE(f->dump_all()); 136 | 137 | return f; 138 | } 139 | 140 | void FunctionManager::build_function_lazy(uint16_t address) 141 | { 142 | assert(jit_thread_idle()); 143 | 144 | TRACE("Will build Function for address 0x" << std::hex << 145 | std::setfill('0') << std::setw(4) << address << " in background"); 146 | 147 | // We only create the JIT thread the first time it's needed; this avoids it 148 | // existing if the library is being used in interpreted or compiled mode. 149 | if (jit_thread_.get_id() == boost::thread::id()) 150 | { 151 | TRACE("Creating JIT thread"); 152 | boost::thread t( 153 | std::mem_fun(&FunctionManager::build_function_thread), this); 154 | jit_thread_.swap(t); 155 | } 156 | 157 | { 158 | boost::mutex::scoped_lock lock(jit_thread_idle_mutex_); 159 | jit_thread_idle_ = false; 160 | } 161 | { 162 | boost::mutex::scoped_lock lock(jit_thread_cv_mutex_); 163 | work_available_ = true; 164 | jit_thread_address_ = address; 165 | } 166 | jit_thread_cv_.notify_all(); 167 | } 168 | 169 | void FunctionManager::build_function_thread() 170 | { 171 | try 172 | { 173 | TRACE("JIT thread started"); 174 | boost::mutex::scoped_lock jit_thread_cv_mutex_lock( 175 | jit_thread_cv_mutex_); 176 | while (true) 177 | { 178 | while (!quit_ && !work_available_) 179 | { 180 | TRACE("JIT thread waiting to be signalled"); 181 | jit_thread_cv_.wait(jit_thread_cv_mutex_lock); 182 | } 183 | 184 | if (quit_) 185 | { 186 | TRACE("JIT thread quitting"); 187 | return; 188 | } 189 | else 190 | { 191 | TRACE("JIT thread about to build Function at address 0x" << 192 | std::hex << std::setfill('0') << std::setw(4) << 193 | jit_thread_address_); 194 | assert(work_available_); 195 | assert(!jit_thread_idle_); 196 | 197 | // Note that we translate code from memory_snapshot_ 198 | // not mpu_->memory. This is important, even though we 199 | // have update_memory_snapshot() which "should" invalidate 200 | // Function objects which depend on modified code before any 201 | // of them are used. The reason is that if a memory location 202 | // is temporarily modified by the interpreter before it can 203 | // be translated, then modified back to its original value 204 | // by the interpreter before update_memory_snapshot() is 205 | // called, update_memory_snapshot() can't notice the change, 206 | // but the change has been compiled into the Function object. 207 | // (See test/z-self-modify-2.xa; this breaks in hybrid mode 208 | // if memory_snapshot_ isn't used here.) 209 | build_function(jit_thread_address_, memory_snapshot_); 210 | work_available_ = false; 211 | 212 | boost::mutex::scoped_lock jit_thread_idle_lock( 213 | jit_thread_idle_mutex_); 214 | jit_thread_idle_ = true; 215 | } 216 | } 217 | } 218 | catch (std::exception &e) 219 | { 220 | die(e.what()); 221 | } 222 | } 223 | 224 | void FunctionManager::add_function(const boost::shared_ptr &f) 225 | { 226 | function_for_address_[f->address()] = f.get(); 227 | function_for_address_owner_[f->address()] = f; 228 | 229 | const AddressSet &code_range = f->code_range(); 230 | for (AddressSet::const_iterator it = code_range.begin(); 231 | it != code_range.end(); ++it) 232 | { 233 | uint16_t i = *it; 234 | functions_covering_address_[i].insert(f.get()); 235 | code_at_address_[i] = true; 236 | } 237 | 238 | const AddressSet &optimistic_writes = f->optimistic_writes(); 239 | for (AddressSet::const_iterator it = optimistic_writes.begin(); 240 | it != optimistic_writes.end(); ++it) 241 | { 242 | uint16_t i = *it; 243 | optimistic_writers_for_address_[i].insert(f.get()); 244 | } 245 | } 246 | 247 | void FunctionManager::code_modified_at(uint16_t address) 248 | { 249 | // We could just return immediately if code_at_address_[address] is false; 250 | // sometimes we call this function without bothering to check first. 251 | // In practice I doubt this has a significant impact on performance. 252 | 253 | TRACE("Code modified at 0x" << std::hex << std::setfill('0') << 254 | std::setw(4) << address); 255 | 256 | destroy_functions_in_set(functions_covering_address_[address]); 257 | 258 | // Keep memory_snapshot_ up-to-date; this avoids harmless-but-inefficient 259 | // destruction of perfectly valid Function objects when 260 | // update_memory_snapshot() is called next. 261 | memory_snapshot_[address] = mpu_->memory[address]; 262 | } 263 | 264 | void FunctionManager::destroy_functions_in_set(FunctionSet &function_set) 265 | { 266 | // We iterate over the set like this because destroy_function() will erase 267 | // the function from function_set, thereby invalidating any iterator we are 268 | // holding on to. 269 | while (!function_set.empty()) 270 | { 271 | destroy_function(*function_set.begin()); 272 | } 273 | } 274 | 275 | void FunctionManager::destroy_function(Function *f) 276 | { 277 | const AddressSet &code_range = f->code_range(); 278 | for (AddressSet::const_iterator it = code_range.begin(); 279 | it != code_range.end(); ++it) 280 | { 281 | uint16_t i = *it; 282 | size_t erased_count = functions_covering_address_[i].erase(f); 283 | ASSERT_EQUAL(erased_count, 1); 284 | // We do *not* clear code_at_address_[i] even if 285 | // functions_covering_address_[i] is now empty; this records the fact 286 | // that we have executed code at this address. This is critical for 287 | // the current implementation of build_function(); code_at_address_ 288 | // being set is used to control optimistic vs non-optimistic writes, 289 | // and if code_at_address_ was cleared when a function was destroyed 290 | // a self-modifying function would cause an infinite loop inside 291 | // build_function(). It would be OK to clear code_at_address_ for any 292 | // addresses with empty functions_covering_address_ sets at the end 293 | // of build_function(), but we currently don't. 294 | } 295 | 296 | const AddressSet &optimistic_writes = f->optimistic_writes(); 297 | for (AddressSet::const_iterator it = optimistic_writes.begin(); 298 | it != optimistic_writes.end(); ++it) 299 | { 300 | uint16_t i = *it; 301 | size_t erased_count = optimistic_writers_for_address_[i].erase(f); 302 | ASSERT_EQUAL(erased_count, 1); 303 | } 304 | 305 | assert(function_for_address_[f->address()] == f); 306 | function_for_address_[f->address()] = 0; 307 | // Do this last as it will cause the Function object to be deleted. 308 | assert(function_for_address_owner_[f->address()].get() == f); 309 | function_for_address_owner_[f->address()].reset(); 310 | } 311 | -------------------------------------------------------------------------------- /FunctionManager.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | #ifndef FUNCTIONMANAGER_H 19 | #define FUNCTIONMANAGER_H 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "const.h" 31 | #include "JitBool.h" 32 | #include "lib6502.h" 33 | 34 | class Function; 35 | 36 | class FunctionManager : boost::noncopyable 37 | { 38 | public: 39 | FunctionManager(M6502 *mpu); 40 | ~FunctionManager(); 41 | 42 | bool jit_thread_idle(); 43 | 44 | void update_memory_snapshot(); 45 | 46 | // Return a Function object representing the code starting at 'address'; if 47 | // one does not already exist it will be created. This never returns null. 48 | Function *get_function(uint16_t address) 49 | { 50 | Function *f = function_for_address_[address]; 51 | if (f != 0) 52 | { 53 | return f; 54 | } 55 | else 56 | { 57 | return build_function(address, mpu_->memory); 58 | } 59 | } 60 | 61 | // Return a Function object representing the code starting at 'address', 62 | // if one is available, otherwise return null. When null is returned 63 | // a background thread may be used to generate a Function object which 64 | // can be returned if the request is repeated in the future. 65 | // 66 | // This function may only be called if the last call to jit_thread_idle() 67 | // returned true and no call has been made to get_function_lazy() since 68 | // jit_thread_idle() was called. 69 | // 70 | // Currently a background thread will *always* be invoked if null is 71 | // returned, but this is not guaranteed. For example, we may wish to 72 | // refuse to waste time building a Function object which we expect to 73 | // be invalidated by self-modifying code shortly afterwards. 74 | Function *get_function_lazy(uint16_t address) 75 | { 76 | // This assert() is perfectly correct, but it single-handedly destroys 77 | // the performance of a debug build; it's just not *that* valuable. 78 | // assert(jit_thread_idle()); 79 | 80 | Function *f = function_for_address_[address]; 81 | if (f != 0) 82 | { 83 | return f; 84 | } 85 | else 86 | { 87 | build_function_lazy(address); 88 | return 0; 89 | } 90 | } 91 | 92 | void code_modified_at(uint16_t address); 93 | 94 | private: 95 | void add_function(const boost::shared_ptr &f); 96 | 97 | Function *build_function(uint16_t address, const uint8_t *ct_memory); 98 | Function *build_function_internal(uint16_t address, 99 | const uint8_t *ct_memory); 100 | 101 | void build_function_lazy(uint16_t address); 102 | void build_function_thread(); 103 | 104 | typedef std::set FunctionSet; 105 | void destroy_functions_in_set(FunctionSet &function_set); 106 | 107 | void destroy_function(Function *f); 108 | 109 | boost::thread jit_thread_; 110 | 111 | boost::mutex jit_thread_idle_mutex_; 112 | bool jit_thread_idle_; 113 | 114 | boost::mutex jit_thread_cv_mutex_; 115 | boost::condition_variable jit_thread_cv_; 116 | bool work_available_; 117 | uint16_t jit_thread_address_; 118 | bool quit_; 119 | 120 | M6502 *mpu_; 121 | 122 | // A copy of the emulated CPU's memory, used to detect changes to already 123 | // JITted code which happen in callbacks and to avoid problems with JITting 124 | // while the interpreter is running (in hybrid mode). 125 | uint8_t memory_snapshot_[memory_size]; 126 | 127 | // We maintain this array of shared_ptr's which actually own the 128 | // Function objects. 129 | boost::shared_ptr function_for_address_owner_[memory_size]; 130 | 131 | // We maintain a parallel array of raw pointers here so that we have 132 | // the option to allow JITted code to access it. 133 | Function *function_for_address_[memory_size]; 134 | 135 | // This tracks the Function objects which contain code generated based on 136 | // individual addresses, i.e. the Function objects which are invalidated by 137 | // a store to a given memory location. 138 | FunctionSet functions_covering_address_[memory_size]; 139 | 140 | // This tracks the Function objects which perform optimistic writes to 141 | // individual addresses, i.e. the Function objects which are invalidated if 142 | // it turns out an address is in fact used to hold code. 143 | FunctionSet optimistic_writers_for_address_[memory_size]; 144 | 145 | // This tracks whether we have ever executed code at a given address; 146 | // destroying all the functions in the corresponding element of 147 | // functions_covering_address does *not* mean this is cleared. 148 | JitBool code_at_address_[memory_size]; 149 | }; 150 | 151 | #endif 152 | -------------------------------------------------------------------------------- /JitBool.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | // JitBool is a typedef representing the type used for boolean flags in the 19 | // JITted code, i.e. the CPU flag values and the 'code modified at' flag for 20 | // each memory address. In reality this is not likely to change, but this at 21 | // least helps to identify code which needs to change to support a different 22 | // representation. FunctionBuilder.cpp also contains a number of helper 23 | // functions which depend on the underlying type of JitBool. 24 | 25 | #ifndef JITBOOL_H 26 | #define JITBOOL_H 27 | 28 | typedef uint8_t JitBool; 29 | const JitBool jit_bool_false = 0; 30 | const JitBool jit_bool_true = 1; 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /LLVMStuff.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | #include "LLVMStuff.h" 19 | 20 | #include "llvm/IR/LLVMContext.h" 21 | #include "llvm/Support/TargetSelect.h" 22 | 23 | LLVMStuff::LLVMStuff() 24 | : module_(new llvm::Module("lib6502-jit", llvm::getGlobalContext())), 25 | builder_(llvm::getGlobalContext()) 26 | { 27 | llvm::InitializeNativeTarget(); 28 | 29 | std::string error; 30 | execution_engine_ = 31 | llvm::EngineBuilder(module_.get()).setErrorStr(&error).create(); 32 | if (execution_engine_ == 0) 33 | { 34 | throw std::runtime_error("Could not create LLVM ExecutionEngine: " + 35 | error); 36 | } 37 | } 38 | 39 | LLVMStuff::~LLVMStuff() 40 | { 41 | } 42 | -------------------------------------------------------------------------------- /LLVMStuff.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | #ifndef LLVMSTUFF_H 19 | #define LLVMSTUFF_H 20 | 21 | #include 22 | #include 23 | #include "llvm/ExecutionEngine/ExecutionEngine.h" 24 | #include "llvm/IR/IRBuilder.h" 25 | #include "llvm/IR/Module.h" 26 | #include 27 | 28 | struct LLVMStuff : boost::noncopyable 29 | { 30 | LLVMStuff(); 31 | ~LLVMStuff(); 32 | 33 | llvm::ExecutionEngine *execution_engine_; 34 | boost::shared_ptr module_; 35 | llvm::IRBuilder<> builder_; 36 | 37 | }; 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /M6502Internal.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | #ifndef M6502INTERNAL_H 19 | #define M6502INTERNAL_H 20 | 21 | #include "FunctionManager.h" 22 | #include "lib6502.h" 23 | #include "LLVMStuff.h" 24 | #include "Registers.h" 25 | 26 | struct _M6502_Internal 27 | { 28 | _M6502_Internal(M6502 *mpu) 29 | : function_manager_(mpu), mode_(M6502_ModeHybrid), 30 | max_instructions_(default_max_instructions_) 31 | { 32 | } 33 | 34 | Registers registers_; 35 | LLVMStuff llvm_stuff_; 36 | FunctionManager function_manager_; 37 | 38 | M6502_Mode mode_; 39 | static const int default_max_instructions_ = 500; 40 | int max_instructions_; 41 | }; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ACLOCAL_AMFLAGS = -I m4 2 | AM_CPPFLAGS = `$(LLVMCONFIG) --cppflags` $(BOOST_CPPFLAGS) 3 | # lib6502.c generates spurious warnings with -Wall, so we want -Wno-parentheses 4 | # too. It's not easy to have per-source-file build flags in automake, so we 5 | # just apply this to all C files. 6 | AM_CFLAGS = -Wall -Wno-parentheses 7 | AM_CXXFLAGS = `$(LLVMCONFIG) --cxxflags` -fexceptions -Wall 8 | AM_LDFLAGS = $(BOOST_THREAD_LDFLAGS) 9 | LIBS = `$(LLVMCONFIG) --ldflags --libs core jit native --system-libs` $(BOOST_THREAD_LIBS) 10 | 11 | # Some of these are included automatically, but I'd rather be explicit. 12 | EXTRA_DIST = \ 13 | examples/README \ 14 | COPYING \ 15 | CREDITS \ 16 | lib6502-compatibility.txt \ 17 | README \ 18 | README.lib6502 \ 19 | TODO \ 20 | man/* \ 21 | test/*.xa \ 22 | test/*.mst \ 23 | test/run-c-tests.sh \ 24 | test/run-run6502-tests.sh \ 25 | test/run-c-tests.py \ 26 | test/run-run6502-tests.py 27 | 28 | man1_MANS = man/*.1 29 | man3_MANS = man/*.3 30 | lib_LTLIBRARIES = lib6502-jit.la 31 | include_HEADERS = lib6502.h 32 | bin_PROGRAMS = run6502 33 | noinst_PROGRAMS = \ 34 | examples/lib1 35 | check_PROGRAMS = \ 36 | test/basic-callback \ 37 | test/call-illegal-callback-modify-code \ 38 | test/irq-nmi \ 39 | test/setjmp-trick \ 40 | test/stack-code-brk \ 41 | test/stack-code-jsr \ 42 | test/write-callback-modify-code 43 | 44 | lib6502_jit_la_SOURCES = \ 45 | AddressRange.cpp \ 46 | AddressRange.h \ 47 | AddressSet.cpp \ 48 | AddressSet.h \ 49 | const.h \ 50 | Function.cpp \ 51 | Function.h \ 52 | FunctionBuilder.cpp \ 53 | FunctionBuilder.h \ 54 | FunctionManager.cpp \ 55 | FunctionManager.h \ 56 | JitBool.h \ 57 | lib6502.c \ 58 | lib6502.h \ 59 | lib6502-jit.cpp \ 60 | LLVMStuff.cpp \ 61 | LLVMStuff.h \ 62 | M6502Internal.h \ 63 | Registers.cpp \ 64 | Registers.h \ 65 | util.cpp \ 66 | util.h \ 67 | valgrind.h 68 | 69 | run6502_SOURCES = \ 70 | run6502.c 71 | run6502_LINK = $(CXXLINK) 72 | run6502_LDADD = lib6502-jit.la 73 | 74 | examples_lib1_SOURCES = \ 75 | examples/lib1.c 76 | examples_lib1_LINK = $(CXXLINK) 77 | examples_lib1_LDADD = lib6502-jit.la 78 | 79 | test_basic_callback_SOURCES = \ 80 | test/basic-callback.c \ 81 | test/test-utils.c \ 82 | test/test-utils.h 83 | test_basic_callback_LINK = $(CXXLINK) 84 | test_basic_callback_LDADD = lib6502-jit.la 85 | 86 | test_call_illegal_callback_modify_code_SOURCES = \ 87 | test/call-illegal-callback-modify-code.c \ 88 | test/test-utils.c \ 89 | test/test-utils.h 90 | test_call_illegal_callback_modify_code_LINK = $(CXXLINK) 91 | test_call_illegal_callback_modify_code_LDADD = lib6502-jit.la 92 | 93 | test_irq_nmi_SOURCES = \ 94 | test/irq-nmi.c \ 95 | test/test-utils.c \ 96 | test/test-utils.h 97 | test_irq_nmi_LINK = $(CXXLINK) 98 | test_irq_nmi_LDADD = lib6502-jit.la 99 | 100 | test_setjmp_trick_SOURCES = \ 101 | test/setjmp-trick.c \ 102 | test/test-utils.c \ 103 | test/test-utils.h 104 | test_setjmp_trick_LINK = $(CXXLINK) 105 | test_setjmp_trick_LDADD = lib6502-jit.la 106 | 107 | test_stack_code_brk_SOURCES = \ 108 | test/stack-code-brk.c \ 109 | test/test-utils.c \ 110 | test/test-utils.h 111 | test_stack_code_brk_LINK = $(CXXLINK) 112 | test_stack_code_brk_LDADD = lib6502-jit.la 113 | 114 | test_stack_code_jsr_SOURCES = \ 115 | test/stack-code-jsr.c \ 116 | test/test-utils.c \ 117 | test/test-utils.h 118 | test_stack_code_jsr_LINK = $(CXXLINK) 119 | test_stack_code_jsr_LDADD = lib6502-jit.la 120 | 121 | test_write_callback_modify_code_SOURCES = \ 122 | test/write-callback-modify-code.c \ 123 | test/test-utils.c \ 124 | test/test-utils.h 125 | test_write_callback_modify_code_LINK = $(CXXLINK) 126 | test_write_callback_modify_code_LDADD = lib6502-jit.la 127 | 128 | TESTS = \ 129 | test/run-c-tests.sh \ 130 | test/run-run6502-tests.sh 131 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | lib6502-jit is a (mostly) compatible implementation of Ian Piumarta's lib6502 2 | which uses LLVM to perform JIT compilation of 6502 machine code to host code. 3 | This will doubtless be useful to the large community of people stuck doing 4 | number-crunching tasks with legacy 6502 code. :-) 5 | 6 | README.lib6502 is a copy of the original lib6502 README. You should probably go 7 | and read that before reading any further. 8 | 9 | lib6502-compatibility.txt documents the differences between lib6502 and 10 | lib6502-jit. 11 | 12 | CREDITS contains acknowledgements of the various people and groups on whose 13 | work lib6502-jit is built. 14 | 15 | COPYING contains license details for lib6502-jit. 16 | 17 | TODO contains some notes on possible enhancements to lib6502-jit. 18 | 19 | How to build: 20 | 21 | You'll need the following installed: 22 | - a C/C++ compiler (I've tested with gcc 4.7.2, gcc 4.8.2 and clang 3.5) 23 | - LLVM development libraries (I've tested with various 3.5 pre-release snapshots) 24 | - boost (including boost::thread) (I've tested with 1.49, 1.54 and 1.55) 25 | 26 | I have somewhat reluctantly set up an autotools build system; compiling and 27 | linking against LLVM and boost::thread on different platforms was otherwise 28 | just that bit too fiddly. So in theory all you need to do is: 29 | 30 | ./configure 31 | make 32 | 33 | I suggest you actually do: 34 | CFLAGS='-g -O3' CXXFLAGS='-g -O3' ./configure 35 | to increase the optimisation level. (I would have made that the default, but 36 | apparently that would go against user expectations for an autotools build 37 | system.) 38 | 39 | "make install" should work as well if you feel inclined to do so, but it's not 40 | necessary. 41 | 42 | I've tested on three platforms, and for what it's worth here are more detailed 43 | instructions for those: 44 | 45 | Ubuntu (14.04 x86): 46 | apt-get install libboost-dev libboost-thread-dev llvm-3.5-dev libedit-dev 47 | export CFLAGS='-g -O3' 48 | export CXXFLAGS='-g -O3' 49 | ./configure --with-llvm-config=llvm-config-3.5 50 | make 51 | 52 | Debian (7.5 x86-64): 53 | apt-get install libboost-dev libboost-thread-dev 54 | [I used the llvm-3.5-dev package from the wheezy repository here: http://llvm.org/apt/] 55 | export CFLAGS='-g -O3' 56 | export CXXFLAGS='-g -O3' 57 | ./configure 58 | make 59 | 60 | FreeBSD (10.0-RELEASE x86-64): 61 | pkg install boost-all-1.55.0 62 | pkg install llvm-devel-3.5.r203994 63 | export CFLAGS='-g -O3' 64 | export CXXFLAGS='-g -O3' 65 | ./configure --with-llvm-config=/usr/local/llvm-devel/bin/llvm-config 66 | make 67 | 68 | There are some tests which will run if you type "make check". Some will be 69 | skipped unless you have the "xa" assembler 70 | (http://www.floodgap.com/retrotech/xa/) on your PATH. 71 | 72 | The above assumes you downloaded a lib6502-jit*tar.bz2 package, which will 73 | contain a "configure" script. This is not (following what I understand to be 74 | best practice) checked into source control, so if you downloaded the source 75 | using something like git or svn, you need to either: 76 | - download the tarball - it will be much easier, especially if you're just 77 | taking a quick look at lib6502-jit and don't plan to make changes to the code 78 | (yet) 79 | - install autoconf, automake and libtool, then cross your fingers and run 80 | "autoreconf -i", which will generate a "configure" script for you if you're 81 | lucky. 82 | 83 | If you have any queries, comments or bug reports, please drop me (Steven 84 | Flintham) an e-mail at lib6502-jit@lemma.co.uk. 85 | -------------------------------------------------------------------------------- /README.lib6502: -------------------------------------------------------------------------------- 1 | lib6502 - 6502 Microprocessor Emulator 2 | 3 | Version: 1.0 4 | 5 | 6 | WHAT IF I'M TOO LAZY TO READ 'README'S? 7 | 8 | make 9 | make install 10 | more examples/README 11 | 12 | 13 | WHAT IS LIB6502? 14 | 15 | lib6502 is a library that emulates the 6502 microprocessor. It 16 | comes with a small 'shell', run6502, that can execute 6502 programs 17 | from the command line. 18 | 19 | lib6502 is distributed under the MIT license: it is non-infectious 20 | and will not make your projects contagious to others the instant you 21 | choose to use lib6502 in them. See the file COPYING for details. 22 | 23 | 24 | WHERE IS THE LATEST SOURCE CODE? 25 | 26 | Source code for lib6502 is available from the author's home page at 27 | 'http://piumarta.com/software'. You can download the most recent 28 | release or use Subversion to get the very latest sources. 29 | 30 | 31 | WHERE IS THE DOCUMENTATION? 32 | 33 | Manual pages for run6502 and lib6502 (and all the functions it 34 | exports) should be available once it is installed. Each includes a 35 | short 'examples' section. Use the 'man' command to read them. 36 | 37 | Your best place to start looking for documentation on the 6502 38 | itself is 'http://6502.org'. A google search of the web will also 39 | turn up vast quantities of information about (and programs for) the 40 | 6502. 41 | 42 | 43 | HOW DO I INSTALL IT? 44 | 45 | It's not really big enough to warrant the whole 'configure' thing. 46 | Any system with an ANSI compiler and C library should be able to 47 | compile it out of the box. After unpacking the archive, just type: 48 | 49 | make 50 | 51 | to build it. If the compiler blows up immediately, edit the 52 | Makefile and play with the '-g' and '-O' flags and then try again. 53 | If you really can't make the compiler happy you've found a bug (read 54 | the next section but one). Otherwise, if you want it put it 55 | somewhere more permanent then type: 56 | 57 | make install 58 | 59 | (as root) to install it. It goes into /usr/local by default; if you 60 | want it elsewhere then set PREFIX in the make command. For example: 61 | 62 | make install PREFIX=/usr 63 | 64 | will put everything under '/usr'. 65 | 66 | When you get bored with it, go back to the source directory and 67 | type: 68 | 69 | make uninstall 70 | 71 | (with the same PREFIX you specified during the install, if 72 | necessary.) 73 | 74 | 75 | WHAT CAN I DO WITH IT? 76 | 77 | See the file EXAMPLES for some suggestions (all of them polite). 78 | 79 | If that leaves you wanting more, read the source for run6502 -- it 80 | exercises just about every feature in lib6502. 81 | 82 | 83 | HOW DO I REPORT PROBLEMS?^W^WCONTACT THE ORIGINAL AUTHOR? 84 | 85 | [If you wish to get in touch with the author of lib6502, this is the 86 | address to use. Since lib6502-jit is based on lib6502 but has been 87 | heavily modified, please do *not* report problems to this address; 88 | use the address in README instead. -- Steve] 89 | 90 | Send e-mail to the author at: firstName (at) lastName (dot) com 91 | 92 | (For suitable values of firstName and lastName, see the last section 93 | of this file.) 94 | 95 | If you're still confused, contact him at: http://piumarta.com 96 | 97 | 98 | HOW CAN I HELP? 99 | 100 | Use it. Find bugs. Fix bugs. Make it faster. Evangelism: spread 101 | it to as many other projects as possible, especially those that 102 | might be using a slower emulator! Read the manual pages to see 103 | what's considered missing, then add it, then send it in. 104 | 105 | (One thing that would be be really handy, and isn't mentioned in the 106 | manual pages, is a test suite. Figure out how to test every mode in 107 | every instruction with every possible combination of operand values 108 | and condition codes and verify the behaviour is correct. Then write 109 | it down in the form of a program and send it in. If it's a 110 | self-contained program that runs once to completion then we can 111 | probably find some real hardware to test against the test suite.) 112 | 113 | If you know how to write software that emulates peripheral hardware 114 | devices, google up some details on the popular 6502-based 115 | microcomputers (Acorn, Commodore, etc.) and add some serious system 116 | emulation to run6502. Make it all pluggable (think dynamic 117 | libraries over an 'agnostic' core), so we can change machines at the 118 | flip of a (command-line) switch. (The callback mechanism in lib6502 119 | was designed with this kind of 'pluggable hardware emulation' in 120 | mind.) 121 | 122 | 123 | WHO WROTE THIS STUFF, AND WHY? 124 | 125 | lib6502 was written by Ian Piumarta. 126 | 127 | While writing ccg (an entirely different project that creates 128 | runtime assemblers for dynamic code generators) he decided to 129 | include support for an 8-bit microprocessor, just for fun. He chose 130 | the 6502 because it was used in the first computer he owned and 131 | programmed (an Ohio Scientific Superboard II, when he was 14) as 132 | well as the second (an Acorn 'BBC Model B', about four years later). 133 | lib6502 started as a 'glorified switch statement' that ran some 134 | small test programs spewed into memory by ccg, but rapidly got out 135 | of control over the course of a weekend. You're looking at the 136 | result. 137 | -------------------------------------------------------------------------------- /Registers.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | #include "Registers.h" 19 | 20 | #include "const.h" 21 | #include "lib6502.h" 22 | #include "M6502Internal.h" 23 | 24 | void Registers::to_M6502_Registers(M6502 *mpu) const 25 | { 26 | M6502_Registers &er = *(mpu->registers); 27 | Registers &ir = mpu->internal->registers_; 28 | 29 | er.a = ir.a; 30 | er.x = ir.x; 31 | er.y = ir.y; 32 | er.s = ir.s; 33 | er.p = 0; 34 | if (ir.flag_n) er.p |= flagN; 35 | if (ir.flag_v) er.p |= flagV; 36 | if (ir.flag_d) er.p |= flagD; 37 | if (ir.flag_i) er.p |= flagI; 38 | if (ir.flag_z) er.p |= flagZ; 39 | if (ir.flag_c) er.p |= flagC; 40 | er.pc = ir.pc; 41 | } 42 | 43 | void Registers::from_M6502_Registers(const M6502 *mpu) 44 | { 45 | M6502_Registers &er = *(mpu->registers); 46 | Registers &ir = mpu->internal->registers_; 47 | 48 | ir.a = er.a; 49 | ir.x = er.x; 50 | ir.y = er.y; 51 | ir.s = er.s; 52 | ir.flag_n = ((er.p & flagN) != 0); 53 | ir.flag_v = ((er.p & flagV) != 0); 54 | ir.flag_d = ((er.p & flagD) != 0); 55 | ir.flag_i = ((er.p & flagI) != 0); 56 | ir.flag_z = ((er.p & flagZ) != 0); 57 | ir.flag_c = ((er.p & flagC) != 0); 58 | ir.pc = er.pc; 59 | } 60 | -------------------------------------------------------------------------------- /Registers.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | #ifndef REGISTERS_H 19 | #define REGISTERS_H 20 | 21 | #include 22 | #include 23 | 24 | #include "JitBool.h" 25 | 26 | typedef struct _M6502 M6502; 27 | 28 | struct Registers : boost::noncopyable 29 | { 30 | uint8_t a; 31 | uint8_t x; 32 | uint8_t y; 33 | uint8_t s; 34 | JitBool flag_n; 35 | JitBool flag_v; 36 | JitBool flag_d; 37 | JitBool flag_i; 38 | JitBool flag_z; 39 | JitBool flag_c; 40 | uint16_t pc; 41 | 42 | // Pseudo-registers used to communicate state for callbacks; see the 43 | // comment describing the Result enumeration in FunctionBuilder.h. 44 | uint16_t addr; 45 | uint8_t data; 46 | 47 | void to_M6502_Registers(M6502 *mpu) const; 48 | void from_M6502_Registers(const M6502 *mpu); 49 | }; 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | It would be interesting to see if this works OK on an ARM machine. 2 | 3 | 4 | Running e.g. z-self-modify-1 to completion in -mc -mx 1 mode shows the memory 5 | for the run6502 process grows steadily, but valgrind doesn't show any leaks. A 6 | quick web search suggests this might be internal leaks in LLVM (which are only 7 | exposed by things like this which continually JIT). I am inclined to leave this 8 | and perhaps come back to it once LLVM 3.5 is actuallly released; if there's 9 | still a problem then it might be worth tracking it down. 10 | 11 | 12 | Would it be helpful to pass branch weights to CreateCondBr()? For example, 13 | where we have a computed address which might trigger a read/write callback, we 14 | could calculate the proportion of addresses in the address range which have 15 | callbacks on them and use that as the probability of taking the callback-exists 16 | branch. 17 | 18 | 19 | We could potentially use Function objects to deduce properties of stretches of 20 | code and use that information to improve the generated code. For example, if we 21 | observed that a Function object didn't contain any external calls or any 22 | stack-modification instructions except RTS then we could inline it in any 23 | callers (adding its code ranges to their code ranges, of course) and the RTS 24 | could be a no-op. (For 100% accuracy, the JSR should still push the return 25 | address on the stack but not modify the stack pointer. Code executed later on 26 | might peek at the stack and expect those values to be there.) This might in 27 | turn allow the callers of that Function to be inlined themselves. This is just 28 | an example. It may be that in practice deciding when to re-translate code would 29 | cause a sufficient performance impact to just not be worth it in the first 30 | place. 31 | 32 | 33 | We could add support for counting the number of cycles executed by the JITted 34 | code; lib6502 itself has some support for this in the form of the tick* macros, 35 | but they don't do anything by default. 36 | 37 | 38 | Would there be any performance improvement to be had by having Function objects 39 | (tail) call one another where possible? 40 | 41 | 42 | Hybrid mode currently makes no attempt to avoid re-generating Function objects 43 | which are continually being invalidated due to self-modifying code. It might be 44 | nice if some heuristic caused us to avoid this unnecessary work and just let 45 | the interpreter always handle that code. 46 | 47 | On a related but distinct note, currently once an element of 48 | FunctionManager::code_at_address_ is set, it is never cleared. This might cause 49 | us to avoid optimistic writes which in reality would be OK. We could use some 50 | heuristic to decide when to destroy Function objects which have not been 51 | executed in a long time, and start clearing code_at_address_ elements when all 52 | functions covering an address are removed. (See the note in 53 | FunctionManager::destroyFunction(); this clearing must be done *outside* the 54 | loop in FunctionManager::buildFunction(), or the implementation of 55 | buildFunction() must be tweaked.) 56 | 57 | However, it may be that it just isn't worth being that clever. Any such code 58 | would need to be triggered inside the main loop between executions of Function 59 | objects. We could do it only every nth time, and keeping track of how many 60 | times we've been round probably wouldn't significantly harm performance, but be 61 | careful. 62 | 63 | 64 | Would a different default value for max_instructions be better? 65 | 66 | 67 | Are there any other LLVM optimisation passes which would be helpful? 68 | -------------------------------------------------------------------------------- /config.h.in: -------------------------------------------------------------------------------- 1 | /* config.h.in. Generated from configure.ac by autoheader. */ 2 | 3 | /* Defined if the requested minimum BOOST version is satisfied */ 4 | #undef HAVE_BOOST 5 | 6 | /* Define to 1 if you have */ 7 | #undef HAVE_BOOST_SCOPED_PTR_HPP 8 | 9 | /* Define to 1 if you have */ 10 | #undef HAVE_BOOST_SHARED_PTR_HPP 11 | 12 | /* Define to 1 if you have */ 13 | #undef HAVE_BOOST_SYSTEM_ERROR_CODE_HPP 14 | 15 | /* Define to 1 if you have */ 16 | #undef HAVE_BOOST_THREAD_HPP 17 | 18 | /* Define to 1 if you have the header file. */ 19 | #undef HAVE_DLFCN_H 20 | 21 | /* Define to 1 if you have the header file. */ 22 | #undef HAVE_INTTYPES_H 23 | 24 | /* Set to 1 if you have the "llvm/Analysis/Verifier.h" header file */ 25 | #undef HAVE_LLVM_ANALYSIS_VERIFIER_H 26 | 27 | /* Set to 1 if you have the llvm::DataLayoutPass class */ 28 | #undef HAVE_LLVM_DATA_LAYOUT_PASS 29 | 30 | /* Set to 1 if you have the "llvm/IR/Verifier.h" header file */ 31 | #undef HAVE_LLVM_IR_VERIFIER_H 32 | 33 | /* Define to 1 if you have the header file. */ 34 | #undef HAVE_MEMORY_H 35 | 36 | /* Define to 1 if you have the header file. */ 37 | #undef HAVE_STDINT_H 38 | 39 | /* Define to 1 if you have the header file. */ 40 | #undef HAVE_STDLIB_H 41 | 42 | /* Define to 1 if you have the header file. */ 43 | #undef HAVE_STRINGS_H 44 | 45 | /* Define to 1 if you have the header file. */ 46 | #undef HAVE_STRING_H 47 | 48 | /* Define to 1 if you have the header file. */ 49 | #undef HAVE_SYS_STAT_H 50 | 51 | /* Define to 1 if you have the header file. */ 52 | #undef HAVE_SYS_TYPES_H 53 | 54 | /* Define to 1 if you have the header file. */ 55 | #undef HAVE_UNISTD_H 56 | 57 | /* Define to the sub-directory in which libtool stores uninstalled libraries. 58 | */ 59 | #undef LT_OBJDIR 60 | 61 | /* Name of package */ 62 | #undef PACKAGE 63 | 64 | /* Define to the address where bug reports for this package should be sent. */ 65 | #undef PACKAGE_BUGREPORT 66 | 67 | /* Package copyright */ 68 | #undef PACKAGE_COPYRIGHT 69 | 70 | /* Define to the full name of this package. */ 71 | #undef PACKAGE_NAME 72 | 73 | /* Define to the full name and version of this package. */ 74 | #undef PACKAGE_STRING 75 | 76 | /* Define to the one symbol short name of this package. */ 77 | #undef PACKAGE_TARNAME 78 | 79 | /* Define to the home page for this package. */ 80 | #undef PACKAGE_URL 81 | 82 | /* Define to the version of this package. */ 83 | #undef PACKAGE_VERSION 84 | 85 | /* Define to 1 if you have the ANSI C header files. */ 86 | #undef STDC_HEADERS 87 | 88 | /* Version number of package */ 89 | #undef VERSION 90 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT([lib6502-jit], [1.0], [lib6502-jit@lemma.co.uk]) 2 | AC_CONFIG_AUX_DIR([build-aux]) 3 | AC_CONFIG_MACRO_DIR([m4]) 4 | AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects no-dist-gzip dist-bzip2]) 5 | AM_MAINTAINER_MODE([enable]) 6 | LT_INIT([disable-shared]) 7 | AC_CONFIG_HEADERS([config.h]) 8 | AC_CONFIG_FILES([Makefile]) 9 | AC_REQUIRE_AUX_FILE([tap-driver.sh]) 10 | 11 | # Copyright for configure.ac *only* 12 | AC_COPYRIGHT([Copyright (c) 2014 Steven Flintham]) 13 | 14 | AC_DEFINE([PACKAGE_COPYRIGHT], ["(C) - see COPYING"], [Package copyright]) 15 | 16 | # for tap-driver.sh 17 | AC_PROG_AWK 18 | 19 | AC_PROG_CC 20 | AC_PROG_CXX 21 | 22 | BOOST_REQUIRE 23 | BOOST_SMART_PTR 24 | BOOST_THREAD 25 | 26 | # I want to: 27 | # - use "llvm-config" (relying on PATH) if the user doesn't do anything 28 | # special, but 29 | # - allow the user to say --with-llvm-config=XXX to use XXX instead of 30 | # llvm-config, where XXX might need to be found on the PATH (e.g. if 31 | # the program is called llvm-config-3.5) or might be an absolute/ 32 | # relative filename 33 | # In both of the above cases, I want to actually check explicitly the 34 | # llvm-config program can be found. This doesn't seem to be supported by 35 | # autoconf: 36 | # - AC_CHECK_PROG() and AC_PATH_PROG() both insist on the program name being a 37 | # leaf name with no included path. 38 | # - AC_CHECK_FILE() (not unreasonably) doesn't look on PATH for the file 39 | # (and wouldn't check for executability) 40 | # So I have to just hack it with "which" and hope. 41 | AC_ARG_WITH( 42 | [llvm-config], 43 | [AS_HELP_STRING( 44 | [--with-llvm-config=FILE], 45 | [filename of llvm-config executable (if not on PATH)])], 46 | [LLVMCONFIG="$withval"], 47 | [LLVMCONFIG="llvm-config"]) 48 | echo -n "checking for $LLVMCONFIG... " 49 | AS_IF( 50 | [which "$LLVMCONFIG" >/dev/null], 51 | [echo yes], 52 | [echo no 53 | AC_MSG_ERROR([llvm-config not found; try --with-llvm-config=FILE?])]) 54 | 55 | AC_SUBST(LLVMCONFIG) 56 | 57 | # These variables are sacred to the user. But we need to set them in order for 58 | # configure's test programs to find the LLVM headers. I am probably doing this 59 | # completely wrong. In twenty years or so maybe I will achieve auto-enlightenment 60 | # and look back at this and laugh. 61 | SACRED_CPPFLAGS="$CPPFLAGS" 62 | SACRED_CXXFLAGS="$CXXFLAGS" 63 | 64 | CPPFLAGS=["`$LLVMCONFIG --cppflags` $CPPFLAGS"] 65 | CXXFLAGS=["`$LLVMCONFIG --cxxflags` -fexceptions $CXXFLAGS"] 66 | 67 | AC_LANG(C++) 68 | 69 | # This header moves around a bit, check for the two known possible locations. 70 | 71 | AC_CHECK_HEADER( 72 | [llvm/IR/Verifier.h], 73 | [AC_DEFINE([HAVE_LLVM_IR_VERIFIER_H], 1, [Set to 1 if you have the "llvm/IR/Verifier.h" header file])]) 74 | AC_CHECK_HEADER( 75 | [llvm/Analysis/Verifier.h], 76 | [AC_DEFINE([HAVE_LLVM_ANALYSIS_VERIFIER_H], 1, [Set to 1 if you have the "llvm/Analysis/Verifier.h" header file])]) 77 | # TODO: Can I get configure to fail if neither of the previous tests 78 | # succeeds? Otherwise configure will succeed but the build will fail. 79 | 80 | # This header always exists, but DataLayoutPass isn't always present. 81 | AC_CHECK_HEADER( 82 | [llvm/IR/DataLayout.h], 83 | [], 84 | [AC_MSG_ERROR([llvm/IR/DataLayout.h not found])]) 85 | AC_CHECK_TYPE( 86 | [llvm::DataLayoutPass], 87 | [AC_DEFINE([HAVE_LLVM_DATA_LAYOUT_PASS], 1, [Set to 1 if you have the llvm::DataLayoutPass class])], 88 | [], 89 | [#include "llvm/IR/DataLayout.h"]) 90 | 91 | CPPFLAGS="$SACRED_CPPFLAGS" 92 | CXXFLAGS="$SACRED_CXXFLAGS" 93 | 94 | AC_OUTPUT 95 | -------------------------------------------------------------------------------- /const.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2005 Ian Piumarta 2 | * Copyright (c) 2014 Steven Flintham 3 | * 4 | * All rights reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the 'Software'), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, provided that the above copyright notice(s) and this 12 | * permission notice appear in all copies of the Software and that both the 13 | * above copyright notice(s) and this permission notice appear in supporting 14 | * documentation. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 17 | */ 18 | 19 | #ifndef CONST_H 20 | #define CONST_H 21 | 22 | #include 23 | 24 | namespace 25 | { 26 | const uint8_t opcode_brk = 0x00; 27 | const uint8_t opcode_rti = 0x40; 28 | const uint8_t opcode_rts = 0x60; 29 | const uint8_t opcode_bra = 0x80; 30 | const uint8_t opcode_bcc = 0x90; 31 | const uint8_t opcode_bcs = 0xb0; 32 | const uint8_t opcode_bvc = 0x50; 33 | const uint8_t opcode_bvs = 0x70; 34 | const uint8_t opcode_beq = 0xf0; 35 | const uint8_t opcode_bne = 0xd0; 36 | const uint8_t opcode_bpl = 0x10; 37 | const uint8_t opcode_bmi = 0x30; 38 | const uint8_t opcode_jsr = 0x20; 39 | const uint8_t opcode_jmp_abs = 0x4c; 40 | const uint8_t opcode_jmp_ind_abs = 0x6c; 41 | const uint8_t opcode_jmp_indx_abs = 0x7c; 42 | 43 | enum { 44 | flagN= (1<<7), /* negative */ 45 | flagV= (1<<6), /* overflow */ 46 | flagX= (1<<5), /* unused */ 47 | flagB= (1<<4), /* irq from brk */ 48 | flagD= (1<<3), /* decimal mode */ 49 | flagI= (1<<2), /* irq disable */ 50 | flagZ= (1<<1), /* zero */ 51 | flagC= (1<<0) /* carry */ 52 | }; 53 | 54 | const uint32_t memory_size = 0x10000; 55 | const uint16_t stack = 0x100; 56 | } 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /examples/README: -------------------------------------------------------------------------------- 1 | lib6502 - 6502 Microprocessor Emulator 2 | 3 | EXAMPLES 4 | 5 | This file has three sections: 6 | 7 | 1. PROGRAMS that you can compile and run 8 | 2. COMMANDS that you can copy and paste into a terminal 9 | 3. ADVANCED stuff that requires some additional setup 10 | 11 | A few numbered footnotes appear at the end and are referenced in the 12 | text in square brackets [6]. 13 | 14 | ---------------------------------------------------------------- 15 | 16 | 1. PROGRAMS 17 | 18 | (We're going to start in 'serious mode'. Bear with me.) 19 | 20 | The file 'lib1.c' contains the example from the run6502 manual page. 21 | Just compile and run it: 22 | 23 | cc -o lib1 lib1.c 24 | ./lib1 25 | 26 | The file has been commented extensively to explain exactly what is 27 | going on. 28 | 29 | ---------------------------------------------------------------- 30 | 31 | 2. COMMANDS 32 | 33 | (Much more fun: this is the section that appeals to the geek in me.) 34 | 35 | 6502 machine code is pretty straightforward. (Many 6502 programmers 36 | remember a time from their misguided childhood when they could 37 | compose and edit programs directly in hexadecimal using their 'front 38 | panel' monitor program -- the next best thing to programming with a 39 | row of switches and lamps, but I digress and will leave that story 40 | until the pdp11 emulator is ready. ;-) We can use this fact to 41 | generate an entire program without needing an assembler. The 'perl' 42 | program is available on most Unixy (and several other) systems and 43 | makes it easy to create binary files from a string of hex digits. 44 | (There is a program called 'xxd' that's very good at this kind of 45 | thing, but you might not have it.) 46 | 47 | First the program (stolen from lib1.c): 48 | 49 | 1000 ldx #41 A241 50 | 1002 txa 8A 51 | 1003 jsr FFEE 20EEFF 52 | 1006 inx E8 53 | 1007 cpx #5B E05B 54 | 1009 bne 1002 D0F7 55 | 100B lda #0A A90A 56 | 100D jsr FFEE 20EEFF 57 | 1010 brk 00 58 | 59 | In C-like syntax it is equivalent to: 60 | 61 | regX = 'A'; 62 | do { 63 | regA = regX; 64 | putchar(regA); 65 | } while (regX != 'Z' + 1); 66 | putchar('\n'); 67 | 68 | (which by today's standards is a *huge* amount of stuff packed into 69 | just 17 bytes of 'compiled' code -- on a 386 the same program is 70 | around 65 bytes [1], and more like 88 bytes on a 32-bit RISC [2]). 71 | 72 | The column on the right is the machine code in hexadecimal. When 73 | strung out in a line it looks like this: 74 | 75 | A2418A20EEFFE8E05BD0F7A90A20EEFF00 76 | 77 | We can tell perl to 'pack' this hexadecimal string into binary and 78 | save the output in a file: 79 | 80 | echo A2418A20EEFFE8E05BD0F7A90A20EEFF00 | 81 | perl -e 'print pack "H*",' > temp.img 82 | 83 | To check the contents of the file, we can load it into run6502 and 84 | then disassemble it: 85 | 86 | run6502 -l 1000 temp.img -d 1000 +11 -x 87 | 88 | The '-l 1000 temp.img' loads the file into the 6502's memory at 89 | address 0x1000, and the '-d 1000 +11' disassembles 17 bytes (11 in 90 | hex) of code starting at 0x1000. The final '-x' tells run6502 not 91 | to try to execute the code. The output should look just like the 92 | program listing above. 93 | 94 | This is almost all we need to run it; just a few details remain. 95 | 96 | - The emulator doesn't know where to start execution. We need to 97 | set the 'reset' vector to 0x1000 -- the address of the first 98 | instruction in the program. The '-R 1000' option does this. 99 | 100 | - The program calls the 'putchar' function at address 0xFFEE to 101 | send a character to the terminal. run6502 can emulate this for 102 | us, with the '-P FFEE' option. 103 | 104 | - We have to have some way to make the processor stop execution 105 | (there is no 'halt' instruction on the 6502, at least not the 106 | early versions). The trick is in the last instruction 'BRK', 107 | that generates a 'software interrupt' -- eventually jumping to 108 | the addres in the 'interrupt vector'. If we don't set the 109 | interrupt vector explicitly it remains empty (zero) and BRK will 110 | try to transfer control to address 0. The '-X 0' option tells 111 | run6502 to stop executing if/when the program attempts to 112 | transfer control to address 0 -- which it will, when it executes 113 | the 'BRK' instruction with an empty interrupt vector. QED :-) 114 | 115 | Here, then, is the complete command to run our program: 116 | 117 | run6502 -l 1000 temp.img -R 1000 -P FFEE -X 0 118 | 119 | This program is relocatable. You can load it at address 4321 120 | (change both the -l and -R options) and it will work just fine. 121 | 122 | Google for "6502 Reference Card" (with the quotes), grab a pencil 123 | and paper, and you can start writing 6502 programs immediately! (If 124 | you really want to experience what it was like in the late 1970s, 125 | but without the added fun of entering each hex digit one at a time 126 | into a monitor program, simply avoid the temptation ever to look at 127 | your hand-assembled code with the '-d' option. ;-) 128 | 129 | If you really start liking this and want to write longer programs in 130 | text files with the hex split over many lines, you'll need a perl 131 | script that can deal with newlines in the input. Something like 132 | this should do the trick... 133 | 134 | #!/usr/bin/perl 135 | 136 | while () { 137 | chomp; 138 | print pack "H*", $_ 139 | } 140 | 141 | (This script is included in the 'examples' directory, in a file 142 | called 'hex2bin', to save you 15 seconds of copy and paste.) 143 | 144 | Need a fun project? Write a 6502 assembler... in 6502 machine code, 145 | of course! Read in the assembly language text via 'getchar' (see 146 | the '-G' option) and write out the assembled binary via 'putchar' 147 | (the '-P' option, that we've already seen). Soon you'll be able to: 148 | 149 | cat prog.s | 150 | run6502 -l 1000 asm.img -R 1000 -G FFE0 -P FFEE -X 0 > prog.img 151 | 152 | run6502 -l 1000 prog.img -R 1000 -G FFE0 -P FFEE -X 0 153 | 154 | (The first prog.s you write should probably be the assembler itself, 155 | transcribed from the paper copy used to hand-assemble the assembler 156 | binary. This significant milestone can be reached with a 157 | surprisingly simple assembler. After this pivotal moment the 158 | assembler, assembling itself, can very quickly become very 159 | powerful.) 160 | 161 | ---------------------------------------------------------------- 162 | 163 | 3. ADVANCED 164 | 165 | (Official justification: let's run something big and non-trivial. 166 | More likely: a flimsy excuse for a trip down memory lane.) 167 | 168 | The remaining examples assume that you have access to two ROM images 169 | from the Acorn 'BBC Model B' microcomputer: the operating system and 170 | the BASIC language . (Just crawl into the attic, fire up the old 171 | Beeb, '*SAVE' the images into files, and then transfer them to your 172 | Unix box over RS423. Under no circumstances should you google for 173 | 'Acorn BBC B OS ROMs zip', without the quotes. That would be 174 | naughty, and probably illegal -- at least until the glorious day 175 | when the revolution finally comes.) 176 | 177 | After brushing yourself down (the attic is kind of dusty, no?) save 178 | the two ROM images as 'OS12.ROM' and 'BASIC2.ROM'. 179 | 180 | The first thing we can do is use run6502 as an editor to merge the 181 | two ROMs into a single image file: 182 | 183 | run6502 \ 184 | -l C000 OS12.ROM \ 185 | -l 8000 BASIC2.ROM \ 186 | -s 0000 +10000 bbc.img \ 187 | -x 188 | 189 | (This is a single command, with '\' continuation characters joining 190 | the lines into one. Your shell should figure it out if you just 191 | copy and paste.) It leaves a file 'bbc.img' containing both the OS 192 | and BASIC. 193 | 194 | To run this image we need the '-B' option. It enables some minimal, 195 | totally lame, hardware emulation of the BBC computer -- just enough 196 | to boot the 'virtual beeb' into BASIC [3]: 197 | 198 | run6502 -l 0 bbc.img -B 199 | 200 | If all goes well, you should be greeted with a 'beep' and a message 201 | telling you what computer you have (BBC Computer), how much RAM is 202 | available (32K), the language you've been dropped into (BASIC), and 203 | a '>' prompt. Turn on 'CAPS LOCK' (many of us remember those days, 204 | and some of us even used to speak in ALL CAPS) and play: 205 | 206 | PRINT 3+4 207 | 208 | or maybe: 209 | 210 | 10 FOR A%=1 TO 10 211 | 20 PRINT A% 212 | 30 NEXT 213 | LIST 214 | RUN 215 | 216 | or even: 217 | 218 | 10 P%=&2800 219 | 20 O%=P% 220 | 30 [ 221 | 40 opt3 222 | 50 lda #10 223 | 60 jsr &FFEE 224 | 70 ldx #65 225 | 80 .l txa 226 | 90 jsr &FFEE 227 | 100 inx 228 | 110 cpx #91 229 | 120 bne l 230 | 130 lda #10 231 | 140 jmp &FFEE 232 | 150 ] 233 | 160 CALL &2800 234 | LIST 235 | RUN 236 | 237 | (How cool is that? ;-) 238 | 239 | One final thing: there is an option '-i' that works just like '-l' 240 | except that it looks to see if the image file begins with '#!'. If 241 | so, it skips over the first line of the file, up to and including 242 | the first newline. Why? The system call that executes programs on 243 | Unixy systems makes the same check. If the user executes a text 244 | file 'foo' staring with '#!prog ...' then the OS loads and runs 245 | 'prog' instead, passing all the '...'s and the name of the text file 246 | 'foo' as arguments [4]. If you have 'temp.img' left over from from 247 | the second example, open it in a text editor and add a single line 248 | at the beginning that reads: 249 | 250 | #!run6502 -i 1000 251 | 252 | (If 'run6502' is not in your current working directory then you will 253 | have to use the full path to the file: '#!/usr/bin/run6502' or 254 | '#!/usr/local/bin/6502' or whatever. No spaces before the '#'!) 255 | 256 | Now make the image executable: 257 | 258 | chmod +x temp.img 259 | 260 | and then (as if you hadn't already guessed) execute it: 261 | 262 | ./temp.img 263 | 264 | Saves an awful lot of tedious typing. [5] 265 | 266 | Have fun! 267 | 268 | ---------------------------------------------------------------- 269 | 270 | FOOTNOTES 271 | 272 | 273 | [1] Here is the 'alphabet' program, verbatim, compiled (with 274 | optimisation) on a 386. It's 66 bytes long, almost four times 275 | longer than the 6502 version. (If I were more generous I might 276 | consider that fair: 32 bits divided by 8 bits is four.) 277 | 278 | 0: 55 push %ebp 279 | 1: 89 e5 mov %esp,%ebp 280 | 3: 53 push %ebx 281 | 4: 83 ec 14 sub $0x14,%esp 282 | 7: bb 41 00 00 00 mov $0x41,%ebx 283 | c: a1 00 00 00 00 mov 0x0,%eax 284 | 11: 89 44 24 04 mov %eax,0x4(%esp) 285 | 15: 89 1c 24 mov %ebx,(%esp) 286 | 18: e8 fc ff ff ff call 19 287 | 1d: 43 inc %ebx 288 | 1e: 83 fb 5b cmp $0x5b,%ebx 289 | 21: 75 e9 jne c 290 | 23: a1 00 00 00 00 mov 0x0,%eax 291 | 28: 89 44 24 04 mov %eax,0x4(%esp) 292 | 2c: c7 04 24 0a 00 00 00 movl $0xa,(%esp) 293 | 33: e8 fc ff ff ff call 34 294 | 38: b8 00 00 00 00 mov $0x0,%eax 295 | 3d: 83 c4 14 add $0x14,%esp 296 | 40: 5b pop %ebx 297 | 41: 5d pop %ebp 298 | 42: c3 ret 299 | 300 | 301 | [2] Here is the 'alphabet' program, verbatim, compiled (with 302 | optimisation) on a PowerPC. It's 88 bytes long, more than five 303 | times longer than the 6502 version. (I don't care what you say: 304 | Apple Macs rule and mine has oodles of RAM to spare.) 305 | 306 | 00000000 mfspr r0,lr 307 | 00000004 stmw r29,0xfff4(r1) 308 | 00000008 stw r0,0x8(r1) 309 | 0000000c stwu r1,0xffb0(r1) 310 | 00000010 bcl 20,31,0x14 311 | 00000014 mfspr r31,lr 312 | 00000018 li r30,0x41 313 | 0000001c addis r2,r31,ha16(0xa4-0x14) 314 | 00000020 lwz r29,lo16(0xa4-0x14)(r2) 315 | 00000024 or r3,r30,r30 316 | 00000028 addi r4,r29,0x58 317 | 0000002c bl 0x7c ; symbol stub for: _fputc 318 | 00000030 cmpwi cr7,r30,0x5a 319 | 00000034 addi r30,r30,0x1 320 | 00000038 bne cr7,0x24 321 | 0000003c li r3,0xa 322 | 00000040 bl 0x5c ; symbol stub for: _fputc 323 | 00000044 li r3,0x0 324 | 00000048 lwz r0,0x58(r1) 325 | 0000004c addi r1,r1,0x50 326 | 00000050 mtspr lr,r0 327 | 00000054 lmw r29,0xfff4(r1) 328 | 00000058 blr 329 | 330 | 331 | [3] Time to 'fess up with an undocumented 'feature'. We ran our 332 | 'bbc.img' file like this: 333 | 334 | run6502 -l 0 bbc.img -B 335 | 336 | I grew tired of typing all those '-'s and made run6502 check to 337 | see if it was invoked with a single, non-option argument. 338 | Running: 339 | 340 | run6502 bbc.img 341 | 342 | is precisely equivalent to the '-l -B' form above. I don't feel 343 | too guilty about this since the manual page suggests that 344 | providing a single, non-option argument is illegal usage. 345 | 346 | 347 | [4] Okay, that might be a little confusing. Here it is written out in 348 | full. If you have a text file called 'foo' containing 349 | 350 | #!/usr/bin/prog -gobble 351 | blah blah blah 352 | blah blah blah 353 | 354 | that is executable, and then you execute it like a compiled 355 | program 356 | 357 | ./foo 358 | 359 | then the OS will notice the '#!' and run the following command 360 | instead: 361 | 362 | /usr/bin/prog -gobble ./foo 363 | 364 | The '-gobble' tells 'prog' to eat the first line, leaving just the 365 | blah that follows. (The reason for choosing '#!' is that '#' is 366 | the comment character in the standard Unix shell, with the obvious 367 | happy consequences for shell scripts.) 368 | 369 | 370 | [5] We can play the same '#!' game with our 'bbc.img' file. Open it 371 | up and add the line 372 | 373 | #!/usr/local/bin/run6502 -B -l 0 374 | 375 | (or whatever, according to the location of the 'run6502' program), 376 | make it executable 377 | 378 | chmod +x bbc.img 379 | 380 | and execute it: 381 | 382 | ./bbc.img 383 | 384 | To save a whopping 32K of zeros at the beginning of the file, 385 | create the image again with 386 | 387 | run6502 \ 388 | -l C000 OS12.ROM \ 389 | -l 8000 BASIC2.ROM \ 390 | -s 8000 +8000 bbc.img \ 391 | -x 392 | 393 | and run it with 394 | 395 | run6502 -l 0 bbc.img -B 396 | 397 | and, if you like, insert the single line 398 | 399 | #!/usr/local/bin/run6502 -B -l 8000 400 | 401 | at the start of the image file and make it executable: 402 | 403 | ./bbc.img 404 | 405 | 406 | [6] There is no footnote 6. 407 | -------------------------------------------------------------------------------- /examples/hex2bin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | while () { 4 | chomp; 5 | print pack "H*", $_ 6 | } 7 | -------------------------------------------------------------------------------- /examples/lib1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "lib6502.h" 5 | 6 | /* Emulated OS functions. */ 7 | 8 | #define WRCH 0xFFEE /* Write accumulator to stdout. */ 9 | 10 | /* Write the accumulator to stdout. This function will be invoked 11 | * when the emulated program calls 0xFFEE. 12 | */ 13 | int wrch(M6502 *mpu, uint16_t address, uint8_t data) 14 | { 15 | int pc; 16 | 17 | /* Write the character. 18 | */ 19 | putchar(mpu->registers->a); 20 | 21 | /* We arrived here from a JSR instruction. The stack contains the 22 | * saved PC. Pop it off the stack. 23 | */ 24 | pc = mpu->memory[++mpu->registers->s + 0x100]; 25 | pc |= mpu->memory[++mpu->registers->s + 0x100] << 8; 26 | 27 | /* The JSR instruction pushes the value of PC before it has been 28 | * incremented to point to the instruction after the JSR. Return PC 29 | * + 1 as the address for the next insn. Returning non-zero 30 | * indicates that we handled the 'subroutine' ourselves, and the 31 | * emulator should pretend the original 'JSR' neveer happened at 32 | * all. 33 | */ 34 | return pc + 1; /* JSR pushes next insn addr - 1 */ 35 | } 36 | 37 | 38 | /* Exit gracefully. We arrange for this function to be called when 39 | * the emulator tries to transfer control to address 0. 40 | */ 41 | int done(M6502 *mpu, uint16_t address, uint8_t data) 42 | { 43 | char buffer[64]; 44 | 45 | /* Dump the internal state of the processor. 46 | */ 47 | M6502_dump(mpu, buffer); 48 | 49 | /* Print a cute message and quit. 50 | */ 51 | printf("\nBRK instruction\n%s\n", buffer); 52 | exit(0); 53 | } 54 | 55 | int main() 56 | { 57 | M6502 *mpu = M6502_new(0, 0, 0); /* Make a 6502 */ 58 | unsigned pc = 0x1000; /* PC for 'assembly' */ 59 | 60 | /* Install the two callback functions defined above. 61 | */ 62 | M6502_setCallback(mpu, call, WRCH, wrch); /* Calling FFEE -> wrch() */ 63 | M6502_setCallback(mpu, call, 0, done); /* Calling 0 -> done() */ 64 | 65 | /* A few macros that dump bytes into the 6502's memory. 66 | */ 67 | # define gen1(X) (mpu->memory[pc++]= (uint8_t)(X)) 68 | # define gen2(X,Y) gen1(X); gen1(Y) 69 | # define gen3(X,Y,Z) gen1(X); gen2(Y,Z) 70 | 71 | /* Hand-assemble the program. 72 | */ 73 | gen2(0xA2, 'A' ); // LDX #'A' 74 | gen1(0x8A ); // TXA 75 | gen3(0x20,0xEE,0xFF); // JSR FFEE 76 | gen1(0xE8 ); // INX 77 | gen2(0xE0, 'Z'+1 ); // CPX #'Z'+1 78 | gen2(0xD0, -9 ); // BNE 0x1002 79 | gen2(0xA9, '\n' ); // LDA #'\n' 80 | gen3(0x20,0xEE,0xFF); // JSR FFEE 81 | gen2(0x00,0x00 ); // BRK 82 | 83 | /* Just for fun: disssemble the program. 84 | */ 85 | { 86 | char insn[64]; 87 | uint16_t ip= 0x1000; 88 | while (ip < pc) 89 | { 90 | int isz = M6502_disassemble(mpu, ip, insn); 91 | printf("%04X %s\n", ip, insn); 92 | ip += isz; 93 | } 94 | } 95 | 96 | /* Point the RESET vector at the first instruction in the assembled 97 | * program. 98 | */ 99 | M6502_setVector(mpu, RST, 0x1000); 100 | 101 | /* Reset the 6502 and run the program. 102 | */ 103 | M6502_reset(mpu); 104 | M6502_run(mpu); 105 | M6502_delete(mpu); /* We never reach here, but what the hey. */ 106 | 107 | return 0; 108 | } 109 | -------------------------------------------------------------------------------- /lib6502-compatibility.txt: -------------------------------------------------------------------------------- 1 | At the time of writing the latest lib6502 release is v1.3; older versions are 2 | not considered here. 3 | 4 | Some things which work fine with lib6502 itself are not supported when using 5 | lib6502-jit in hybrid (the default) or compiled execution modes. All of the 6 | following will result in undefined behaviour unless interpreted mode is used: 7 | 8 | * Modifying memory which contains 6502 code (whether executed yet or not) 9 | inside a read callback. (All other types of callbacks are allowed to 10 | modify memory freely, including modifying code.) 11 | 12 | * Defining a callback after calling M6502_run(); for example, doing so inside 13 | another callback. 14 | 15 | * Checking the B and X flags in the processor status register 16 | (M6502_Registers.p) inside a callback. lib6502 tracks these flags as if they 17 | have a real existence at all times. lib6502-jit's compiler only sets them 18 | appropriately when pushing a copy of the processor status register onto the 19 | stack. This difference is *not* visible to code executing on the emulated CPU, 20 | only to callbacks. In hybrid mode, which behaviour you get will depend on 21 | whether your callback is invoked from the interpreter or compiled code. 22 | 23 | The following differences exist between lib6502 and lib6502-jit in all modes, 24 | including interpreted mode: 25 | 26 | * lib6502 is likely to be slightly faster than lib6502-jit in interpreted mode, 27 | since the latter's interpreter code contains additional tests to stop 28 | executing at certain points after n instructions have been executed. 29 | 30 | * Illegal instructions are treated as no-ops by default in lib6502-jit; lib6502 31 | aborts if an illegal instruction is executed. 32 | 33 | * Illegal instruction callbacks are a lib6502-jit extension and are not 34 | available in lib6502. 35 | 36 | * Call callbacks in lib6502 always receive a 0 as the data argument; 37 | lib6502-jit supplies the opcode triggering the callback as the data argument. 38 | 39 | * A few bugs in lib6502's emulation are resolved in lib6502-jit: 40 | - BRK clears the D flag 41 | - ADC/SBC exactly match the behaviour of a real 65C02 in decimal mode 42 | - BIT #imm only modifies the Z flag, leaving N and V untouched 43 | - TSB sets the Z flag correctly 44 | - TRB sets the Z flag and updates memory correctly 45 | 46 | * lib6502's run6502 -B option skips every other (ROM name) argument; 47 | lib6502-jit's doesn't. 48 | 49 | lib6502-jit's stance is that anything the code executing on the emulated CPU 50 | does is fair game and must be handled, but that the library's client code has a 51 | responsibility to cooperate and not do tricky things like those documented 52 | above. If you have what you think is a reasonable requirement for behaviour 53 | which is supported by lib6502 but doesn't work on lib6502-jit please get in 54 | touch. 55 | -------------------------------------------------------------------------------- /lib6502-jit.cpp: -------------------------------------------------------------------------------- 1 | /* lib6502-jit.cpp -- MOS Technology 6502 emulator -*- C -*- */ 2 | 3 | /* Copyright (c) 2005 Ian Piumarta 4 | * Copyright (c) 2014 Steven Flintham 5 | * 6 | * All rights reserved. 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a 9 | * copy of this software and associated documentation files (the 'Software'), 10 | * to deal in the Software without restriction, including without limitation 11 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, provided that the above copyright notice(s) and this 14 | * permission notice appear in all copies of the Software and that both the 15 | * above copyright notice(s) and this permission notice appear in supporting 16 | * documentation. 17 | * 18 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 19 | */ 20 | 21 | #include "const.h" 22 | #include "Function.h" 23 | #include "FunctionBuilder.h" 24 | #include "FunctionManager.h" 25 | #include "M6502Internal.h" 26 | #include "Registers.h" 27 | #include "util.h" 28 | 29 | static void outOfMemory(void) 30 | { 31 | die("out of memory"); 32 | } 33 | 34 | M6502 *M6502_new(M6502_Registers *registers, M6502_Memory memory, M6502_Callbacks *callbacks) 35 | { 36 | M6502 *mpu= (M6502 *) calloc(1, sizeof(M6502)); 37 | if (!mpu) outOfMemory(); 38 | 39 | if (!registers) { registers = (M6502_Registers *)calloc(1, sizeof(M6502_Registers)); mpu->flags |= M6502_RegistersAllocated; } 40 | if (!memory ) { memory = (uint8_t *)calloc(1, sizeof(M6502_Memory )); mpu->flags |= M6502_MemoryAllocated; } 41 | if (!callbacks) { callbacks = (M6502_Callbacks *)calloc(1, sizeof(M6502_Callbacks)); mpu->flags |= M6502_CallbacksAllocated; } 42 | 43 | if (!registers || !memory || !callbacks) outOfMemory(); 44 | 45 | mpu->registers = registers; 46 | mpu->memory = memory; 47 | mpu->callbacks = callbacks; 48 | 49 | try 50 | { 51 | mpu->internal = new _M6502_Internal(mpu); 52 | } 53 | catch (std::exception &e) 54 | { 55 | die(e.what()); 56 | } 57 | 58 | return mpu; 59 | } 60 | 61 | void M6502_delete(M6502 *mpu) 62 | { 63 | if (mpu->flags & M6502_CallbacksAllocated) free(mpu->callbacks); 64 | if (mpu->flags & M6502_MemoryAllocated ) free(mpu->memory); 65 | if (mpu->flags & M6502_RegistersAllocated) free(mpu->registers); 66 | delete mpu->internal; 67 | 68 | free(mpu); 69 | } 70 | 71 | void M6502_setMode(M6502 *mpu, M6502_Mode mode, int arg) 72 | { 73 | mpu->internal->mode_ = mode; 74 | 75 | if (arg == 0) 76 | { 77 | arg = M6502_Internal::default_max_instructions_; 78 | } 79 | mpu->internal->max_instructions_ = arg; 80 | } 81 | 82 | extern "C" void M6502_run_interpreted(M6502 *mpu, int instructions_left); 83 | 84 | // I don't know if it's "supposed" to work, but it doesn't seem completely 85 | // unreasonable for a lib6502 client to do a setjmp() before invoking 86 | // M6502_run() and have a callback function longjmp() out of the emulation. I 87 | // believe this will work with lib6502 itself, and I would like this emulation 88 | // to do the same. (Note that currently for both lib6502 and lib6502-jit, 89 | // read/write callbacks don't see an up-to-date M6502_Registers object and so 90 | // the setjmp/longjmp trick would result in restarting execution in the wrong 91 | // place with the wrong registers. Call callbacks and illegal instruction 92 | // callbacks should work though.) 93 | // 94 | // To this end, M6502_run_compiled() and M6502_run_hybrid() both update the 95 | // Registers object from the M6502_Registers object on entry to pick up the 96 | // current state. They also both ensure they call update_memory_snapshot() as 97 | // appropriate in case the caller modified memory before invoking M6502_run() 98 | // again. 99 | 100 | static void M6502_run_compiled(M6502 *mpu) 101 | { 102 | FunctionManager &function_manager = mpu->internal->function_manager_; 103 | function_manager.update_memory_snapshot(); 104 | 105 | Registers ®isters = mpu->internal->registers_; 106 | registers.from_M6502_Registers(mpu); 107 | 108 | while (true) 109 | { 110 | Function *f = function_manager.get_function(registers.pc); 111 | TRACE("Executing Function object for address 0x" << std::hex << 112 | std::setfill('0') << std::setw(4) << registers.pc); 113 | f->execute(); 114 | } 115 | } 116 | 117 | #ifdef LOG 118 | 119 | static std::string M6502_dump_str(M6502 *mpu) 120 | { 121 | char buffer[64]; 122 | M6502_dump(mpu, buffer); 123 | return buffer; 124 | } 125 | 126 | #endif 127 | 128 | static void M6502_run_hybrid(M6502 *mpu) 129 | { 130 | FunctionManager &function_manager = mpu->internal->function_manager_; 131 | Registers ®isters = mpu->internal->registers_; 132 | registers.from_M6502_Registers(mpu); 133 | TRACE("About to interpret, CPU state: " << M6502_dump_str(mpu)); 134 | while (true) 135 | { 136 | const int instructions_to_interpret = 100; 137 | M6502_run_interpreted(mpu, instructions_to_interpret); 138 | if (function_manager.jit_thread_idle()) 139 | { 140 | TRACE("JIT thread is idle"); 141 | registers.from_M6502_Registers(mpu); 142 | function_manager.update_memory_snapshot(); 143 | Function *f; 144 | while ((f = function_manager.get_function_lazy(registers.pc)) != 0) 145 | { 146 | TRACE("Executing Function object for address 0x" << std::hex << 147 | std::setfill('0') << std::setw(4) << registers.pc); 148 | f->execute(); 149 | } 150 | TRACE("No Function object available for address 0x" << std::hex << 151 | std::setfill('0') << std::setw(4) << registers.pc << 152 | ", falling back to interpreter"); 153 | registers.to_M6502_Registers(mpu); 154 | TRACE("About to interpret, CPU state: " << M6502_dump_str(mpu)); 155 | } 156 | } 157 | } 158 | 159 | void M6502_run(M6502 *mpu) 160 | { 161 | try 162 | { 163 | switch (mpu->internal->mode_) 164 | { 165 | case M6502_ModeInterpreted: 166 | while (true) 167 | { 168 | M6502_run_interpreted(mpu, std::numeric_limits::max()); 169 | } 170 | break; 171 | 172 | case M6502_ModeCompiled: 173 | M6502_run_compiled(mpu); 174 | break; 175 | 176 | case M6502_ModeHybrid: 177 | M6502_run_hybrid(mpu); 178 | break; 179 | 180 | default: 181 | die("Unknown execution mode in M6502_run()"); 182 | } 183 | 184 | die("M6502_run() returned!"); 185 | } 186 | catch (std::exception &e) 187 | { 188 | die(e.what()); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /lib6502.h: -------------------------------------------------------------------------------- 1 | /* lib6502.h -- MOS Technology 6502 emulator -*- C -*- */ 2 | 3 | /* Copyright (c) 2005 Ian Piumarta 4 | * Copyright (c) 2014 Steven Flintham 5 | * 6 | * All rights reserved. 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a 9 | * copy of this software and associated documentation files (the 'Software'), 10 | * to deal in the Software without restriction, including without limitation 11 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, provided that the above copyright notice(s) and this 14 | * permission notice appear in all copies of the Software and that both the 15 | * above copyright notice(s) and this permission notice appear in supporting 16 | * documentation. 17 | * 18 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 19 | */ 20 | 21 | #ifndef __m6502_h 22 | #define __m6502_h 23 | 24 | #include 25 | #include 26 | 27 | #ifdef __cplusplus 28 | extern "C" 29 | { 30 | #endif 31 | 32 | typedef struct _M6502 M6502; 33 | typedef struct _M6502_Registers M6502_Registers; 34 | typedef struct _M6502_Callbacks M6502_Callbacks; 35 | typedef struct _M6502_Internal M6502_Internal; 36 | 37 | typedef int (*M6502_Callback)(M6502 *mpu, uint16_t address, uint8_t data); 38 | 39 | typedef M6502_Callback M6502_CallbackTable[0x10000]; 40 | typedef M6502_Callback M6502_IllegalInstructionCallbackTable[0x100]; 41 | typedef uint8_t M6502_Memory[0x10000]; 42 | 43 | enum { 44 | M6502_NMIVector= 0xfffa, M6502_NMIVectorLSB= 0xfffa, M6502_NMIVectorMSB= 0xfffb, 45 | M6502_RSTVector= 0xfffc, M6502_RSTVectorLSB= 0xfffc, M6502_RSTVectorMSB= 0xfffd, 46 | M6502_IRQVector= 0xfffe, M6502_IRQVectorLSB= 0xfffe, M6502_IRQVectorMSB= 0xffff 47 | }; 48 | 49 | struct _M6502_Registers 50 | { 51 | uint8_t a; /* accumulator */ 52 | uint8_t x; /* X index register */ 53 | uint8_t y; /* Y index register */ 54 | uint8_t p; /* processor status register */ 55 | uint8_t s; /* stack pointer */ 56 | uint16_t pc; /* program counter */ 57 | }; 58 | 59 | struct _M6502_Callbacks 60 | { 61 | M6502_CallbackTable read; 62 | M6502_CallbackTable write; 63 | M6502_CallbackTable call; 64 | M6502_IllegalInstructionCallbackTable illegal_instruction; 65 | }; 66 | 67 | struct _M6502_Internal; 68 | 69 | struct _M6502 70 | { 71 | M6502_Registers *registers; 72 | uint8_t *memory; 73 | M6502_Callbacks *callbacks; 74 | unsigned int flags; 75 | 76 | /* The following is implementation-specific; client code should only use the 77 | * above members. 78 | */ 79 | M6502_Internal *internal; 80 | }; 81 | 82 | enum { 83 | M6502_RegistersAllocated = 1 << 0, 84 | M6502_MemoryAllocated = 1 << 1, 85 | M6502_CallbacksAllocated = 1 << 2 86 | }; 87 | 88 | typedef enum { 89 | M6502_ModeInterpreted, 90 | M6502_ModeCompiled, 91 | M6502_ModeHybrid 92 | } M6502_Mode; 93 | 94 | extern M6502 *M6502_new(M6502_Registers *registers, M6502_Memory memory, M6502_Callbacks *callbacks); 95 | extern void M6502_reset(M6502 *mpu); 96 | extern void M6502_nmi(M6502 *mpu); 97 | extern void M6502_irq(M6502 *mpu); 98 | extern void M6502_run(M6502 *mpu); 99 | extern int M6502_disassemble(M6502 *mpu, uint16_t addr, char buffer[64]); 100 | extern void M6502_dump(M6502 *mpu, char buffer[64]); 101 | extern void M6502_delete(M6502 *mpu); 102 | extern void M6502_setMode(M6502 *mpu, M6502_Mode mode, int arg); 103 | 104 | #define M6502_getVector(MPU, VEC) \ 105 | ( ( ((MPU)->memory[M6502_##VEC##VectorLSB]) ) \ 106 | | ((MPU)->memory[M6502_##VEC##VectorMSB] << 8) ) 107 | 108 | #define M6502_setVector(MPU, VEC, ADDR) \ 109 | ( ( ((MPU)->memory[M6502_##VEC##VectorLSB]= ((uint8_t)(ADDR)) & 0xff) ) \ 110 | , ((MPU)->memory[M6502_##VEC##VectorMSB]= (uint8_t)((ADDR) >> 8)) ) 111 | 112 | #define M6502_getCallback(MPU, TYPE, ADDR) ((MPU)->callbacks->TYPE[ADDR]) 113 | #define M6502_setCallback(MPU, TYPE, ADDR, FN) ((MPU)->callbacks->TYPE[ADDR]= (FN)) 114 | 115 | 116 | #ifdef __cplusplus 117 | } 118 | #endif 119 | 120 | #endif /* __m6502_h */ 121 | -------------------------------------------------------------------------------- /man/M6502_delete.3: -------------------------------------------------------------------------------- 1 | .so man3/lib6502.3 2 | -------------------------------------------------------------------------------- /man/M6502_disassemble.3: -------------------------------------------------------------------------------- 1 | .so man3/lib6502.3 2 | -------------------------------------------------------------------------------- /man/M6502_dump.3: -------------------------------------------------------------------------------- 1 | .so man3/lib6502.3 2 | -------------------------------------------------------------------------------- /man/M6502_getCallback.3: -------------------------------------------------------------------------------- 1 | .so man3/lib6502.3 2 | -------------------------------------------------------------------------------- /man/M6502_getVector.3: -------------------------------------------------------------------------------- 1 | .so man3/lib6502.3 2 | -------------------------------------------------------------------------------- /man/M6502_irq.3: -------------------------------------------------------------------------------- 1 | .so man3/lib6502.3 2 | -------------------------------------------------------------------------------- /man/M6502_new.3: -------------------------------------------------------------------------------- 1 | .so man3/lib6502.3 2 | -------------------------------------------------------------------------------- /man/M6502_nmi.3: -------------------------------------------------------------------------------- 1 | .so man3/lib6502.3 2 | -------------------------------------------------------------------------------- /man/M6502_reset.3: -------------------------------------------------------------------------------- 1 | .so man3/lib6502.3 2 | -------------------------------------------------------------------------------- /man/M6502_run.3: -------------------------------------------------------------------------------- 1 | .so man3/lib6502.3 2 | -------------------------------------------------------------------------------- /man/M6502_setCallback.3: -------------------------------------------------------------------------------- 1 | .so man3/lib6502.3 2 | -------------------------------------------------------------------------------- /man/M6502_setMode.3: -------------------------------------------------------------------------------- 1 | .so man3/lib6502.3 2 | -------------------------------------------------------------------------------- /man/M6502_setVector.3: -------------------------------------------------------------------------------- 1 | .so man3/lib6502.3 2 | -------------------------------------------------------------------------------- /man/run6502.1: -------------------------------------------------------------------------------- 1 | .\" Copyright (c) 2005 Ian Piumarta 2 | .\" Copyright (c) 2014 Steven Flintham 3 | .\" 4 | .\" Permission is hereby granted, free of charge, to any person 5 | .\" obtaining a copy of this software and associated documentation 6 | .\" files (the 'Software'), to deal in the Software without 7 | .\" restriction, including without limitation the rights to use, copy, 8 | .\" modify, merge, publish, distribute, and/or sell copies of the 9 | .\" Software, and to permit persons to whom the Software is furnished 10 | .\" to do so, provided that the above copyright notice(s) and this 11 | .\" permission notice appear in all copies of the Software and that 12 | .\" both the above copyright notice(s) and this permission notice 13 | .\" appear in supporting documentation. 14 | .\" 15 | .\" THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | .\" 17 | .Dd October 31, 2005 18 | .Dt RUN6502 1 LOCAL 19 | .Os "" 20 | .\" ---------------------------------------------------------------- 21 | .Sh NAME 22 | .\" 23 | .Nm run6502 24 | .Nd execute a 6502 microprocessor program 25 | .\" ---------------------------------------------------------------- 26 | .Sh SYNOPSIS 27 | .\" 28 | .Nm run6502 29 | .Op Ar option ... 30 | .Nm run6502 31 | .Op Ar option ... 32 | .Fl B 33 | .Op Ar 34 | .\" ---------------------------------------------------------------- 35 | .Sh DESCRIPTION 36 | The 37 | .Nm run6502 38 | command emulates the execution of a 6502 microprocessor. It creates a 39 | memory image from the contents of one or more files on the command 40 | line and then simulates a power-on hardware reset to begin execution. 41 | .Pp 42 | In its first form, 43 | .Nm run6502 44 | emulates an embedded 6502 processor with 64 kilobytes of RAM, no 45 | memory-mapped hardware, and no input-output capabilities. Limited 46 | interaction with the machine is possible only through the 47 | .Fl G , M 48 | and 49 | .Fl P 50 | options. 51 | .Pp 52 | In its second form (with the 53 | .Fl B 54 | option) 55 | .Nm run6502 56 | provides minimal emulation of Acorn 'BBC Model B' hardware with 32 57 | kilobytes of RAM, 16 kilobytes of paged language ROMs, and 16 58 | kilobytes of operating system ROM. A few MOS calls are intercepted to 59 | provide keyboard input and screen output via stdin and stdout. 60 | Switching between the sixteen paged read-only memory banks is also 61 | supported by the usual memory-mapped control register. Any 62 | .Ar file 63 | arguments after the 64 | .Fl B 65 | are loaded into successive paged ROM banks (starting at 15 and working 66 | down towards 0) before execution begins. 67 | .\" ---------------------------------------------------------------- 68 | .Ss Options 69 | .\" 70 | .Bl -tag -width indent 71 | .It Fl B 72 | enable minimal Acorn 'BBC Model B' hardware emulation: 73 | .Bl -bullet 74 | .It 75 | the contents of memory between addresses 0x8000 and 0xBFFF are copied 76 | into paged ROM number 0; 77 | .It 78 | memory between 0x8000 and 0xBFFF becomes bank-switchable between 79 | sixteen different ROM images; 80 | .It 81 | the memory-mapped pages ('FRED', 'JIM' and 'SHEILA') between 0xFC00 82 | and 0xFEFF are initialised to harmless values; 83 | .It 84 | the upper half of the address space is write-protected; and 85 | .It 86 | callbacks are installed on several OS entry points to provide 87 | input-output via stdin and stdout. 88 | .El 89 | .Pp 90 | Any remaining non-option arguments on the command line will name files 91 | to be loaded successively into paged ROMs, starting at 15 and working 92 | downwards towards 0. 93 | .It Fl d Ar addr Ar end 94 | dump memory from the address 95 | .Ar addr 96 | (given in hexadecimal) up to (but not including) 97 | .Ar end . 98 | The 99 | .Ar end 100 | argument is either an absolute address or a relative address specified 101 | as a '+' character followed by the number (in hexadecimal) of bytes to 102 | dump. In other words, the following two options dump the same region 103 | of memory: 104 | .Bd -ragged -offset indent 105 | .Fl d 106 | 8000 C000 107 | .Ed 108 | .Bd -ragged -offset indent -compact 109 | .Fl d 110 | 8000 +4000 111 | .Ed 112 | .Pp 113 | The format of the dump cannot currently be modified and consists of 114 | the current address followed by one, two or three hexadecimal bytes, 115 | and a symbolic representation of the instruction at that address. 116 | .It Fl G Ar addr 117 | arrange that subroutine calls to 118 | .Ar addr 119 | will behave as if there were an implementation of 120 | .Xr getchar 3 121 | at that address, reading a character from stdin and returning it in 122 | the accumulator. 123 | .It Fl h 124 | print a summary of the available options and then exit. 125 | .It Fl I Ar addr 126 | set the IRQ (interrupt request) vector (the address to which the 127 | processor will transfer control upon execution of a BRK instruction). 128 | Setting this address to zero will cause execution to halt (and the 129 | emulator to exit) when a BRK instruction is encountered. 130 | .It Fl i Ar addr Ar file 131 | Load 132 | .Ar file 133 | into the memory image at the address 134 | .Ar addr 135 | (in hexadecimal), skipping over any initial '#!' interpreter line. 136 | .It Fl l Ar addr Ar file 137 | Load 138 | .Ar file 139 | into the memory image at the address 140 | .Ar addr 141 | (in hexadecimal). 142 | .It Fl M Ar addrio 143 | arrange that memory reads from address 144 | .Ar addrio 145 | will return the next character on stdin (blocking if necessary), and 146 | memory writes to 147 | .Ar addrio 148 | will send the value written to stdout. 149 | .It Fl mc 150 | use compiled emulation mode. All code is compiled into host machine 151 | code. This can make the emulation very jerky as execution halts 152 | while compiling. 153 | .It Fl mh 154 | use hybrid emulation mode. Code is compiled into 155 | host machine code, but while this is happening an interpreter allows 156 | execution to continue. This is the default mode. 157 | .It Fl mi 158 | use interpreted emulation mode. All code is interpreted. 159 | .It Fl mx Ar count 160 | in compiled and hybrid emulation modes, set the maximum number of 161 | 6502 instructions which are translated as a unit to 162 | .Ar count . 163 | This has no effect in interpreted mode. A reasonable default is 164 | chosen if this is not specified. 165 | .It Fl N Ar addr 166 | set the NMI (non-maskable interrupt) vector to 167 | .Ar addr . 168 | .It Fl P Ar addr 169 | arrange that subroutine calls to 170 | .Ar addr 171 | will behave as if there were an implementation of 172 | .Xr putchar 3 173 | at that address, writing the contents of the accumulator to stdout. 174 | .It Fl R Ar addr 175 | set the RST (hardware reset) vector. The processor will transfer 176 | control to this address when emulated execution begins. 177 | .It Fl s Ar addr Ar end Ar file 178 | save the contents of memory from the address 179 | .Ar addr 180 | up to 181 | .Ar end 182 | (exclusive) to the given 183 | .Ar file . 184 | As with the 185 | .Fl d 186 | option, 187 | .Ar end 188 | can be absolute or '+' followed by a byte count. 189 | .It Fl v 190 | print version information and then exit. 191 | .It Fl X Ar addr 192 | arrange that any transfer of control to the address 193 | .Ar addr 194 | will cause an immediate exit with zero exit status. 195 | .It Fl x 196 | exit immediately. (Useful after 197 | .Fl d 198 | or when 199 | .Nm run6502 200 | is being used as a trivial 'image editor', with several 201 | .Fl l 202 | options followed by 203 | .Fl s 204 | and 205 | .Fl x . ) 206 | .It Ar 207 | following a 208 | .Fl B 209 | option, load one or more ROM image 210 | files 211 | into successive paged ROM slots. Other than the paging aspect, this 212 | is equivalent to: 213 | .Bd -ragged -offset indent 214 | .Fl l Ar 8000 Ar image 215 | .Ed 216 | .El 217 | .\" ---------------------------------------------------------------- 218 | .Sh EXAMPLES 219 | .\" 220 | .Ss A Very Simple Program 221 | The 222 | .Xr perl 1 223 | command can be used to create a binary file from hexadecimal input: 224 | .Bd -literal 225 | echo a2418a20eeffe8e05bd0f7a90a20eeff00 | 226 | perl -e 'print pack "H*",' > temp.img 227 | .Ed 228 | .Pp 229 | The file can be loaded and executed with: 230 | .Bd -literal 231 | run6502 -l 1000 temp.img -R 1000 -P FFEE -X 0 232 | .Ed 233 | .Pp 234 | The contents of the file can be inspected symbolically with: 235 | .Bd -literal 236 | run6502 -l 1000 temp.img -d 1000 +12 237 | .Ed 238 | .Pp 239 | The options passed to 240 | .Nm run6502 241 | in the above examples have the following effects: 242 | .Bl -tag -width offset 243 | .It \-l 1000 temp.img 244 | loads the file 245 | .Pa temp.img 246 | into memory at address 0x8000. 247 | .It \-R 1000 248 | sets the reset vector (the address of first instruction to be executed 249 | after 'power on') to 0x1000. 250 | .It \-P FFEE 251 | arranges for calls to address 0xFFEE to behave as if there were an 252 | implementation of 253 | .Xr putchar 3 254 | at that address. 255 | .It \-X 0 256 | arranges for transfers of control to address 0 to exit from the 257 | emulator. This works in the above example because the final 'BRK' 258 | instruction causes an implicit subroutine call through an 259 | uninitialised interrupt vector to location 0. To see this 260 | instruction... 261 | .It \-d 1000 +12 262 | disassembles 18 bytes of memory at address 0x8000. 263 | .El 264 | .Ss Standalone Images 265 | The 266 | .Fl i 267 | option is designed for use in the 'interpreter command' appearing on 268 | the first line of an executable script. Adding the line 269 | .Bd -literal 270 | #!run6502 -R 1000 -P FFEE -X 0 -i 1000 271 | .Ed 272 | .Pp 273 | (with no leading spaces and a single trailing newline character) 274 | to the 275 | .Pa temp.img 276 | file from the first example turns it into a script. If the file is 277 | made executable with 278 | .Bd -literal 279 | chmod +x temp.img 280 | .Ed 281 | .Pp 282 | it can be run like a standalone program: 283 | .Bd -literal 284 | ./temp.img 285 | .Ed 286 | .Ss A Very Complex Program 287 | Consider a pair of files named 288 | .Pa os1.2 289 | and 290 | .Pa basic2 291 | containing (legally-acquired, of course) ROM images of Acorn MOS 1.2 292 | and BBC Basic 2. The following command loads each of the images into 293 | memory at the appropriate address, cleans up the regions of memory 294 | containing memory-mapped i/o on the BBC computer, saves a snapshot of 295 | the entire memory to the file 296 | .Pa image 297 | and then exits: 298 | .Bd -literal 299 | run6502 -l C000 os1.2 -l 8000 basic2 -B -s0 +10000 image -x 300 | .Ed 301 | .Pp 302 | Running the generated image with 303 | .Bd -literal 304 | run6502 image 305 | .Ed 306 | .Pp 307 | will cold-start the emulated hardware, run the OS for a while, and 308 | then drop into the language ROM. Basic programs can then be entered, 309 | edited and run from the terminal. 310 | .Pp 311 | More details are given in the 312 | .Pa README 313 | file available in the 314 | .Pa examples 315 | directory of the distribution. 316 | .Ss Exercises 317 | Create a standalone image (one that can be run as a program, with 318 | a '#!' interpreter line at the beginning) that contains Basic2 and 319 | OS1.2 (as described above). This image should be no larger than 32K 320 | (memory below 0x8000, which would be full of zeroes, should not appear 321 | in the image file). 322 | .\" ---------------------------------------------------------------- 323 | .Sh DIAGNOSTICS 324 | .\" 325 | If nothing goes wrong, none. Otherwise lots. They should be 326 | self-explanatory. I'm too lazy to enumerate them. 327 | .\" ---------------------------------------------------------------- 328 | .Sh COMPATIBILITY 329 | .\" 330 | See 331 | .Xr lib6502 3 332 | for a discussion of the emulated instruction set. 333 | .\" ---------------------------------------------------------------- 334 | .Sh SEE ALSO 335 | .\" 336 | .Xr lib6502 3 337 | .Pp 338 | The file 339 | .Pa examples/README 340 | in the lib6502 distribution. (Depending on your system this may be 341 | installed in 342 | .Pa /usr/doc/lib6502 , 343 | .Pa /usr/local/doc/lib6502 , 344 | .Pa /usr/share/doc/lib6502 , 345 | or similar.) 346 | .Pp 347 | .Pa http://piumarta.com/software/lib6502 348 | for updates and documentation to lib6502. 349 | .Pp 350 | .Pa https://github.com/ZornsLemma/lib6502-jit 351 | for updates and documentation to lib6502-jit. 352 | .Pp 353 | .Pa http://6502.org 354 | for lots of 6502-related resources. 355 | .\" ---------------------------------------------------------------- 356 | .Sh AUTHORS 357 | .\" 358 | The original lib6502 software and manual pages were written by Ian Piumarta. 359 | Additional changes to create lib6502-jit were made by Steven Flintham. 360 | .Pp 361 | The software is provided as-is, with absolutely no warranty, in the 362 | hope that you will enjoy and benefit from it. You may use (entirely 363 | at your own risk) and redistribute it under the terms of a very 364 | liberal license that does not seek to restrict your rights in any way 365 | (unlike certain so-called 'open source' licenses that significantly 366 | limit your freedom in the name of 'free' software that is, ultimately, 367 | anything but free). See the file COPYING for details. 368 | .\" ---------------------------------------------------------------- 369 | .Sh BUGS 370 | .\" 371 | .Bl -bullet 372 | .It 373 | Options must appear one at a time. 374 | .It 375 | Any attempt (in a load or save operation) to transfer data beyond 376 | 0xFFFF is silently truncated at the end of memory. 377 | .It 378 | There is no way to specify the slot into which a ROM image should be 379 | loaded, other than implicitly according to the order of arguments on 380 | the command line. 381 | .It 382 | Execution can only be started via the emulated power-up reset. There 383 | is no support for 'warm-starting' execution in an image at an 384 | arbitrary address. 385 | .It 386 | Even though the emulator fully supports them, there is no way to 387 | artificially generate a hardware interrupt request, non-maskable 388 | interrupt, or reset condition. If you need these, read 389 | .Xr lib6502 3 390 | and write your own shell. 391 | .It 392 | The Acorn 'BBC Model B' hardware emulation is totally lame. 393 | .El 394 | .Pp 395 | Please send bug reports (and feature requests) to : 396 | lib6502-jit@lemma.co.uk. 397 | -------------------------------------------------------------------------------- /run6502.c: -------------------------------------------------------------------------------- 1 | /* run6502.c -- 6502 emulator shell -*- C -*- */ 2 | 3 | /* Copyright (c) 2005 Ian Piumarta 4 | * 5 | * All rights reserved. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the 'Software'), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, provided that the above copyright notice(s) and this 13 | * permission notice appear in all copies of the Software and that both the 14 | * above copyright notice(s) and this permission notice appear in supporting 15 | * documentation. 16 | * 17 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 18 | */ 19 | 20 | /* Last edited: 2005-11-02 01:18:58 by piumarta on margaux.local 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "config.h" 32 | #include "lib6502.h" 33 | 34 | #undef VERSION 35 | #define VERSION PACKAGE_NAME " " PACKAGE_VERSION " " PACKAGE_COPYRIGHT 36 | 37 | typedef uint8_t byte; 38 | typedef uint16_t word; 39 | 40 | static char *program= 0; 41 | 42 | static M6502_Mode mode= M6502_ModeHybrid; 43 | static int max_insns= 0; /* default */ 44 | 45 | static byte bank[0x10][0x4000]; 46 | 47 | static uint64_t system_time_base; 48 | 49 | 50 | void fail(const char *fmt, ...) 51 | { 52 | va_list ap; 53 | fflush(stdout); 54 | va_start(ap, fmt); 55 | vfprintf(stderr, fmt, ap); 56 | va_end(ap); 57 | fprintf(stderr, "\n"); 58 | exit(1); 59 | } 60 | 61 | 62 | void pfail(const char *msg) 63 | { 64 | fflush(stdout); 65 | perror(msg); 66 | exit(1); 67 | } 68 | 69 | 70 | #define rts \ 71 | { \ 72 | word pc; \ 73 | pc = mpu->memory[++mpu->registers->s + 0x100]; \ 74 | pc |= mpu->memory[++mpu->registers->s + 0x100] << 8; \ 75 | return pc + 1; \ 76 | } 77 | 78 | 79 | uint64_t pseudo_system_time(void) 80 | { 81 | struct timespec t; 82 | if (clock_gettime(CLOCK_MONOTONIC, &t) == -1) 83 | { 84 | pfail("clock_gettime() failed"); 85 | } 86 | long hsec= t.tv_nsec / 10000000; 87 | return (((uint64_t) t.tv_sec) * 100) + hsec; 88 | } 89 | 90 | int osword(M6502 *mpu, word address, byte data) 91 | { 92 | byte *params= mpu->memory + mpu->registers->x + (mpu->registers->y << 8); 93 | 94 | switch (mpu->registers->a) 95 | { 96 | case 0x00: /* input line */ 97 | /* On entry: XY+0,1=>string area, 98 | * XY+2=maximum line length, 99 | * XY+3=minimum acceptable ASCII value, 100 | * XY+4=maximum acceptable ASCII value. 101 | * On exit: Y is the line length (excluding CR), 102 | * C is set if Escape terminated input. 103 | */ 104 | { 105 | word offset= params[0] + (params[1] << 8); 106 | byte *buffer= mpu->memory + offset; 107 | byte length= params[2], minVal= params[3], maxVal= params[4], b= 0; 108 | if (!fgets((char *) buffer, length, stdin)) 109 | { 110 | putchar('\n'); 111 | exit(0); 112 | } 113 | for (b= 0; b < length; ++b) 114 | if ((buffer[b] < minVal) || (buffer[b] > maxVal) || ('\n' == buffer[b])) 115 | break; 116 | buffer[b]= 13; 117 | mpu->registers->y= b; 118 | mpu->registers->p &= 0xFE; 119 | break; 120 | } 121 | 122 | case 0x01: /* read system time */ 123 | /* On exit: XY+0..4=>5 byte time in hundredths of a second 124 | */ 125 | { 126 | uint64_t system_time= pseudo_system_time() - system_time_base; 127 | int i; 128 | for (i= 0; i < 5; ++i) 129 | { 130 | params[i]= system_time & 0xFF; 131 | system_time>>= 8; 132 | } 133 | break; 134 | } 135 | 136 | case 0x05: /* read I/O processor memory */ 137 | /* On entry: XY+0..3=>address to read from 138 | * On exit: XY+4 =>the byte read 139 | */ 140 | { 141 | word addr= params[0] + (params[1] << 8); 142 | params[4]= mpu->memory[addr]; 143 | break; 144 | } 145 | 146 | default: 147 | { 148 | char state[64]; 149 | M6502_dump(mpu, state); 150 | fflush(stdout); 151 | fprintf(stderr, "\nOSWORD %s\n", state); 152 | fail("ABORT"); 153 | } 154 | break; 155 | } 156 | 157 | rts; 158 | } 159 | 160 | 161 | int osbyte(M6502 *mpu, word address, byte data) 162 | { 163 | switch (mpu->registers->a) 164 | { 165 | case 0x7A: /* perform keyboard scan */ 166 | mpu->registers->x= 0x00; 167 | break; 168 | 169 | case 0x7E: /* acknowledge detection of escape condition */ 170 | return 1; 171 | break; 172 | 173 | case 0x82: /* read machine higher order address */ 174 | mpu->registers->y= 0x00; 175 | mpu->registers->x= 0x00; 176 | break; 177 | 178 | case 0x83: /* read top of OS ram address (OSHWM) */ 179 | mpu->registers->y= 0x0E; 180 | mpu->registers->x= 0x00; 181 | break; 182 | 183 | case 0x84: /* read bottom of display ram address */ 184 | mpu->registers->y= 0x80; 185 | mpu->registers->x= 0x00; 186 | break; 187 | 188 | case 0x89: /* motor control */ 189 | break; 190 | 191 | case 0xDA: /* read/write number of items in vdu queue (stored at 0x026A) */ 192 | return 0; 193 | break; 194 | 195 | default: 196 | { 197 | char state[64]; 198 | M6502_dump(mpu, state); 199 | fflush(stdout); 200 | fprintf(stderr, "\nOSBYTE %s\n", state); 201 | fail("ABORT"); 202 | } 203 | break; 204 | } 205 | 206 | rts; 207 | } 208 | 209 | 210 | int oscli(M6502 *mpu, word address, byte data) 211 | { 212 | byte *params= mpu->memory + mpu->registers->x + (mpu->registers->y << 8); 213 | char command[1024], *ptr= command; 214 | int ret; 215 | while (('*' == *params) || (' ' == *params)) 216 | ++params; 217 | while (13 != *params) 218 | *ptr++= *params++; 219 | *ptr= '\0'; 220 | ret= system(command); 221 | if ((ret == -1) || (WIFEXITED(ret) && (WEXITSTATUS(ret) == 127))) 222 | { 223 | fflush(stdout); 224 | fprintf(stderr, "\nsystem() failed\n"); 225 | } 226 | rts; 227 | } 228 | 229 | 230 | int oswrch(M6502 *mpu, word address, byte data) 231 | { 232 | switch (mpu->registers->a) 233 | { 234 | case 0x0C: 235 | fputs("\033[2J\033[H", stdout); 236 | break; 237 | 238 | default: 239 | putchar(mpu->registers->a); 240 | break; 241 | } 242 | fflush(stdout); 243 | rts; 244 | } 245 | 246 | 247 | static int writeROM(M6502 *mpu, word address, byte value) 248 | { 249 | return 0; 250 | } 251 | 252 | 253 | static int bankSelect(M6502 *mpu, word address, byte value) 254 | { 255 | memcpy(mpu->memory + 0x8000, bank[value & 0x0F], 0x4000); 256 | return 0; 257 | } 258 | 259 | 260 | static int doBtraps(int argc, char **argv, M6502 *mpu) 261 | { 262 | unsigned addr; 263 | 264 | /* Acorn Model B ROM and memory-mapped IO */ 265 | 266 | for (addr= 0x8000; addr <= 0xFBFF; ++addr) mpu->callbacks->write[addr]= writeROM; 267 | for (addr= 0xFC00; addr <= 0xFEFF; ++addr) mpu->memory[addr]= 0xFF; 268 | for (addr= 0xFE30; addr <= 0xFE33; ++addr) mpu->callbacks->write[addr]= bankSelect; 269 | for (addr= 0xFE40; addr <= 0xFE4F; ++addr) mpu->memory[addr]= 0x00; 270 | for (addr= 0xFF00; addr <= 0xFFFF; ++addr) mpu->callbacks->write[addr]= writeROM; 271 | 272 | /* anything already loaded at 0x8000 appears in bank 0 */ 273 | 274 | memcpy(bank[0x00], mpu->memory + 0x8000, 0x4000); 275 | 276 | /* fake a few interesting OS calls */ 277 | 278 | # define trap(vec, addr, func) mpu->callbacks->call[addr]= (func) 279 | trap(0x020C, 0xFFF1, osword); 280 | trap(0x020A, 0xFFF4, osbyte); 281 | //trap(0x0208, 0xFFF7, oscli ); /* enable this to send '*COMMAND's to system(3) :-) */ 282 | trap(0x020E, 0xFFEE, oswrch); 283 | trap(0x020E, 0xE0A4, oswrch); /* NVWRCH */ 284 | #undef trap 285 | 286 | system_time_base= pseudo_system_time(); 287 | 288 | return 0; 289 | } 290 | 291 | 292 | static void usage(int status) 293 | { 294 | FILE *stream= status ? stderr : stdout; 295 | fprintf(stream, VERSION"\n"); 296 | fprintf(stream, "please send bug reports to: %s\n", PACKAGE_BUGREPORT); 297 | fprintf(stream, "\n"); 298 | fprintf(stream, "usage: %s [option ...]\n", program); 299 | fprintf(stream, " %s [option ...] -B [image ...]\n", program); 300 | fprintf(stream, " -B -- minimal Acorn 'BBC Model B' compatibility\n"); 301 | fprintf(stream, " -d addr last -- dump memory between addr and last\n"); 302 | fprintf(stream, " -G addr -- emulate getchar(3) at addr\n"); 303 | fprintf(stream, " -h -- help (print this message)\n"); 304 | fprintf(stream, " -I addr -- set IRQ vector\n"); 305 | fprintf(stream, " -l addr file -- load file at addr\n"); 306 | fprintf(stream, " -M addr -- emulate memory-mapped stdio at addr\n"); 307 | fprintf(stream, " -mc -- use compiled emulation mode\n"); 308 | fprintf(stream, " -mh -- use hybrid emulation mode (default)\n"); 309 | fprintf(stream, " -mi -- use interpreted emulation mode\n"); 310 | fprintf(stream, " -mx count -- maximum instructions to JIT (-mc/-mh)\n"); 311 | fprintf(stream, " -N addr -- set NMI vector\n"); 312 | fprintf(stream, " -P addr -- emulate putchar(3) at addr\n"); 313 | fprintf(stream, " -R addr -- set RST vector\n"); 314 | fprintf(stream, " -s addr last file -- save memory from addr to last in file\n"); 315 | fprintf(stream, " -v -- print version number then exit\n"); 316 | fprintf(stream, " -X addr -- terminate emulation if PC reaches addr\n"); 317 | fprintf(stream, " -x -- exit without further ado\n"); 318 | fprintf(stream, " image -- '-l 8000 image' in available ROM slot\n"); 319 | fprintf(stream, "\n"); 320 | fprintf(stream, "'last' can be an address (non-inclusive) or '+size' (in bytes)\n"); 321 | exit(status); 322 | } 323 | 324 | 325 | static int doHelp(int argc, char **argv, M6502 *mpu) 326 | { 327 | usage(0); 328 | return 0; 329 | } 330 | 331 | 332 | static int doVersion(int argc, char **argv, M6502 *mpu) 333 | { 334 | puts(VERSION); 335 | exit(0); 336 | return 0; 337 | } 338 | 339 | 340 | static unsigned long htol(char *hex) 341 | { 342 | char *end; 343 | unsigned long l= strtol(hex, &end, 16); 344 | if (*end) fail("bad hex number: %s", hex); 345 | return l; 346 | } 347 | 348 | 349 | static int loadInterpreter(M6502 *mpu, word start, const char *path) 350 | { 351 | FILE *file= 0; 352 | int count= 0; 353 | byte *memory= mpu->memory + start; 354 | size_t max= 0x10000 - start; 355 | int c= 0; 356 | 357 | if ((!(file= fopen(path, "r"))) || ('#' != fgetc(file)) || ('!' != fgetc(file))) 358 | return 0; 359 | while ((c= fgetc(file)) >= ' ') 360 | ; 361 | while ((count= fread(memory, 1, max, file)) > 0) 362 | { 363 | memory += count; 364 | max -= count; 365 | } 366 | fclose(file); 367 | return 1; 368 | } 369 | 370 | 371 | static int save(M6502 *mpu, word address, unsigned length, const char *path) 372 | { 373 | FILE *file= 0; 374 | int count= 0; 375 | if (!(file= fopen(path, "w"))) 376 | return 0; 377 | while ((count= fwrite(mpu->memory + address, 1, length, file))) 378 | { 379 | address += count; 380 | length -= count; 381 | } 382 | fclose(file); 383 | return 1; 384 | } 385 | 386 | 387 | static int load(M6502 *mpu, word address, const char *path) 388 | { 389 | FILE *file= 0; 390 | int count= 0; 391 | size_t max= 0x10000 - address; 392 | if (!(file= fopen(path, "r"))) 393 | return 0; 394 | while ((count= fread(mpu->memory + address, 1, max, file)) > 0) 395 | { 396 | address += count; 397 | max -= count; 398 | } 399 | fclose(file); 400 | return 1; 401 | } 402 | 403 | 404 | static int doLoadInterpreter(int argc, char **argv, M6502 *mpu) 405 | { 406 | if (argc < 3) usage(1); 407 | if (!loadInterpreter(mpu, htol(argv[1]), argv[2])) pfail(argv[2]); 408 | return 2; 409 | } 410 | 411 | 412 | static int doLoad(int argc, char **argv, M6502 *mpu) /* -l addr file */ 413 | { 414 | if (argc < 3) usage(1); 415 | if (!load(mpu, htol(argv[1]), argv[2])) pfail(argv[2]); 416 | return 2; 417 | } 418 | 419 | 420 | static int doSave(int argc, char **argv, M6502 *mpu) /* -l addr size file */ 421 | { 422 | if (argc < 4) usage(1); 423 | if (!save(mpu, htol(argv[1]), htol(argv[2]), argv[3])) pfail(argv[3]); 424 | return 3; 425 | } 426 | 427 | 428 | static int doMode(M6502_Mode m) 429 | { 430 | mode= m; 431 | return 0; 432 | } 433 | 434 | 435 | static int doMaxInsns(int argc, char **argv, M6502 *mpu) 436 | { 437 | if (argc < 2) usage(1); 438 | char *end; 439 | unsigned long l= strtol(argv[1], &end, 10); 440 | if (*end) fail("bad number: %s", argv[1]); 441 | max_insns= l; 442 | return 1; 443 | } 444 | 445 | 446 | #define doVEC(VEC) \ 447 | static int do##VEC(int argc, char **argv, M6502 *mpu) \ 448 | { \ 449 | unsigned addr= 0; \ 450 | if (argc < 2) usage(1); \ 451 | addr= htol(argv[1]); \ 452 | M6502_setVector(mpu, VEC, addr); \ 453 | return 1; \ 454 | } 455 | 456 | doVEC(IRQ); 457 | doVEC(NMI); 458 | doVEC(RST); 459 | 460 | #undef doVEC 461 | 462 | 463 | static int gTrap(M6502 *mpu, word addr, byte data) { mpu->registers->a= getchar(); rts; } 464 | static int pTrap(M6502 *mpu, word addr, byte data) { putchar(mpu->registers->a); rts; } 465 | 466 | static int doGtrap(int argc, char **argv, M6502 *mpu) 467 | { 468 | unsigned addr; 469 | if (argc < 2) usage(1); 470 | addr= htol(argv[1]); 471 | M6502_setCallback(mpu, call, addr, gTrap); 472 | return 1; 473 | } 474 | 475 | static int doPtrap(int argc, char **argv, M6502 *mpu) 476 | { 477 | unsigned addr; 478 | if (argc < 2) usage(1); 479 | addr= htol(argv[1]); 480 | M6502_setCallback(mpu, call, addr, pTrap); 481 | return 1; 482 | } 483 | 484 | 485 | static int mTrapRead(M6502 *mpu, word addr, byte data) { return getchar(); } 486 | static int mTrapWrite(M6502 *mpu, word addr, byte data) { return putchar(data); } 487 | 488 | static int doMtrap(int argc, char **argv, M6502 *mpu) 489 | { 490 | unsigned addr= 0; 491 | if (argc < 2) usage(1); 492 | addr= htol(argv[1]); 493 | M6502_setCallback(mpu, read, addr, mTrapRead); 494 | M6502_setCallback(mpu, write, addr, mTrapWrite); 495 | return 1; 496 | } 497 | 498 | 499 | static int xTrap(M6502 *mpu, word addr, byte data) { exit(0); return 0; } 500 | 501 | static int doXtrap(int argc, char **argv, M6502 *mpu) 502 | { 503 | unsigned addr= 0; 504 | if (argc < 2) usage(1); 505 | addr= htol(argv[1]); 506 | M6502_setCallback(mpu, call, addr, xTrap); 507 | return 1; 508 | } 509 | 510 | 511 | static int doDisassemble(int argc, char **argv, M6502 *mpu) 512 | { 513 | unsigned addr= 0, last= 0; 514 | if (argc < 3) usage(1); 515 | addr= htol(argv[1]); 516 | last= ('+' == *argv[2]) ? addr + htol(1 + argv[2]) : htol(argv[2]); 517 | while (addr < last) 518 | { 519 | char insn[64]; 520 | int i= 0, size= M6502_disassemble(mpu, addr, insn); 521 | printf("%04X ", addr); 522 | while (i++ < size) printf("%02X", mpu->memory[addr + i - 1]); 523 | while (i++ < 4) printf(" "); 524 | putchar(' '); 525 | i= 0; 526 | while (i++ < size) putchar(isgraph(mpu->memory[addr + i - 1]) ? mpu->memory[addr + i - 1] : ' '); 527 | while (i++ < 4) putchar(' '); 528 | printf(" %s\n", insn); 529 | addr += size; 530 | } 531 | return 2; 532 | } 533 | 534 | 535 | int main(int argc, char **argv) 536 | { 537 | M6502 *mpu= M6502_new(0, 0, 0); 538 | int bTraps= 0; 539 | 540 | program= argv[0]; 541 | 542 | if ((2 == argc) && ('-' != *argv[1])) 543 | { 544 | if ((!loadInterpreter(mpu, 0, argv[1])) && (!load(mpu, 0, argv[1]))) 545 | pfail(argv[1]); 546 | doBtraps(0, 0, mpu); 547 | } 548 | else 549 | while (++argv, --argc > 0) 550 | { 551 | int n= 0; 552 | if (!strcmp(*argv, "-B")) bTraps= 1; 553 | else if (!strcmp(*argv, "-d")) n= doDisassemble(argc, argv, mpu); 554 | else if (!strcmp(*argv, "-G")) n= doGtrap(argc, argv, mpu); 555 | else if (!strcmp(*argv, "-h")) n= doHelp(argc, argv, mpu); 556 | else if (!strcmp(*argv, "-i")) n= doLoadInterpreter(argc, argv, mpu); 557 | else if (!strcmp(*argv, "-I")) n= doIRQ(argc, argv, mpu); 558 | else if (!strcmp(*argv, "-l")) n= doLoad(argc, argv, mpu); 559 | else if (!strcmp(*argv, "-M")) n= doMtrap(argc, argv, mpu); 560 | else if (!strcmp(*argv, "-mc")) n= doMode(M6502_ModeCompiled); 561 | else if (!strcmp(*argv, "-mh")) n= doMode(M6502_ModeHybrid); 562 | else if (!strcmp(*argv, "-mi")) n= doMode(M6502_ModeInterpreted); 563 | else if (!strcmp(*argv, "-mx")) n= doMaxInsns(argc, argv, mpu); 564 | else if (!strcmp(*argv, "-N")) n= doNMI(argc, argv, mpu); 565 | else if (!strcmp(*argv, "-P")) n= doPtrap(argc, argv, mpu); 566 | else if (!strcmp(*argv, "-R")) n= doRST(argc, argv, mpu); 567 | else if (!strcmp(*argv, "-s")) n= doSave(argc, argv, mpu); 568 | else if (!strcmp(*argv, "-v")) n= doVersion(argc, argv, mpu); 569 | else if (!strcmp(*argv, "-X")) n= doXtrap(argc, argv, mpu); 570 | else if (!strcmp(*argv, "-x")) exit(0); 571 | else if ('-' == **argv) usage(1); 572 | else 573 | { 574 | /* doBtraps() left 0x8000+0x4000 in bank 0, so load */ 575 | /* additional images starting at 15 and work down */ 576 | static int bankSel= 0x0F; 577 | if (!bTraps) usage(1); 578 | if (bankSel < 0) fail("too many images"); 579 | if (!load(mpu, 0x8000, argv[0])) pfail(argv[0]); 580 | memcpy(bank[bankSel--], 581 | 0x8000 + mpu->memory, 582 | 0x4000); 583 | n= 0; 584 | } 585 | argc -= n; 586 | argv += n; 587 | } 588 | 589 | M6502_setMode(mpu, mode, max_insns); 590 | 591 | if (bTraps) 592 | doBtraps(0, 0, mpu); 593 | 594 | M6502_reset(mpu); 595 | M6502_run(mpu); 596 | M6502_delete(mpu); 597 | 598 | return 0; 599 | } 600 | -------------------------------------------------------------------------------- /test/addr-wrap-1.mst: -------------------------------------------------------------------------------- 1 | Y -------------------------------------------------------------------------------- /test/addr-wrap-1.xa: -------------------------------------------------------------------------------- 1 | #include "config.xa" 2 | 3 | LDA #1 4 | STA $00 5 | STA $05 6 | STA $0A 7 | LDY #$80 8 | CLC 9 | LDA #0 10 | LOOP 11 | ADC $FF80,Y 12 | INY 13 | BNE LOOP 14 | CMP #3 15 | BNE FAIL 16 | 17 | SUCCESS 18 | LDA #'Y' 19 | JSR OSWRCH 20 | JMP QUIT 21 | 22 | FAIL 23 | LDA #'N' 24 | JSR OSWRCH 25 | JMP QUIT 26 | -------------------------------------------------------------------------------- /test/basic-callback.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2005 Ian Piumarta 2 | * Copyright (c) 2014 Steven Flintham 3 | * 4 | * All rights reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the 'Software'), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, provided that the above copyright notice(s) and this 12 | * permission notice appear in all copies of the Software and that both the 13 | * above copyright notice(s) and this permission notice appear in supporting 14 | * documentation. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include "lib6502.h" 23 | #include "test-utils.h" 24 | 25 | int done(M6502 *mpu, uint16_t address, uint8_t data) 26 | { 27 | char buffer[64]; 28 | M6502_dump_masked(mpu, buffer); 29 | printf("\nBRK instruction: address %04X opcode %02X\n%s\n", address, data, buffer); 30 | exit(0); 31 | } 32 | 33 | int call(M6502 *mpu, uint16_t address, uint8_t data) 34 | { 35 | char buffer[64]; 36 | M6502_dump_masked(mpu, buffer); 37 | printf("\ncall: address %04X opcode %02X\n%s\n", address, data, buffer); 38 | return 0; 39 | } 40 | 41 | int rd(M6502 *mpu, uint16_t address, uint8_t data) 42 | { 43 | char buffer[64]; 44 | M6502_dump_masked(mpu, buffer); 45 | printf("\nrd: address %04X opcode %02X\n%s\n", address, data, buffer); 46 | return 0; 47 | } 48 | 49 | int wr(M6502 *mpu, uint16_t address, uint8_t data) 50 | { 51 | char buffer[64]; 52 | M6502_dump_masked(mpu, buffer); 53 | printf("\nwr: address %04X opcode %02X\n%s\n", address, data, buffer); 54 | return 0; 55 | } 56 | 57 | int ill(M6502 *mpu, uint16_t address, uint8_t data) 58 | { 59 | char buffer[64]; 60 | M6502_dump_masked(mpu, buffer); 61 | printf("\nill: address %04X opcode %02X memory %02X\n%s\n", address, data, mpu->memory[address], buffer); 62 | return 0; 63 | } 64 | 65 | int main(int argc, char *argv[]) 66 | { 67 | M6502 *mpu = M6502_new(0, 0, 0); 68 | parse_args(argc, argv, mpu); 69 | 70 | unsigned pc = 0x1000; 71 | 72 | M6502_setCallback(mpu, call, 0, done); 73 | M6502_setCallback(mpu, call, 0x2000, call); 74 | M6502_setCallback(mpu, call, 0x3000, call); 75 | M6502_setCallback(mpu, call, 0x4000, call); 76 | M6502_setCallback(mpu, read, 0x5000, rd ); 77 | M6502_setCallback(mpu, write, 0x5000, wr ); 78 | M6502_setCallback(mpu, illegal_instruction, 0x13, ill ); 79 | M6502_setCallback(mpu, illegal_instruction, 0x44, ill ); 80 | M6502_setCallback(mpu, illegal_instruction, 0x5c, ill ); 81 | 82 | # define gen1(X) (mpu->memory[pc++]= (uint8_t)(X)) 83 | # define gen2(X,Y) gen1(X); gen1(Y) 84 | # define gen3(X,Y,Z) gen1(X); gen2(Y,Z) 85 | 86 | gen1(0x13 ); 87 | gen1(0x44 ); 88 | gen1(0x13 ); // not executed, 0x44 is a two-byte illegal instruction 89 | gen1(0x5C ); 90 | gen1(0x13 ); // not executed, 0x5C is a two-byte illegal instruction 91 | gen1(0x13 ); // not executed, 0x5C is a two-byte illegal instruction 92 | gen3(0x20,0x00,0x20); // JSR &2000 93 | gen3(0xad,0x00,0x50); // LDA &5000 94 | gen2(0x64,0x70 ); // STZ &70 95 | gen2(0xa9,0x50 ); // LDA #&50 96 | gen2(0x85,0x71 ); // STA &71 97 | gen2(0xb2,0x70 ); // LDA (&70) 98 | gen2(0x92,0x70 ); // STA (&70) 99 | gen2(0x00,0x00 ); // BRK 100 | 101 | pc = 0x2000; 102 | gen3(0x8d,0x00,0x50); // STA &5000 103 | gen3(0x4c,0x00,0x30); // JMP &3000 104 | 105 | pc = 0x3000; 106 | gen2(0xa9,0x00 ); // LDA #0 107 | gen3(0x8d,0x76,0x32); // STA &3276 108 | gen2(0xa9,0x40 ); // LDA #&40 109 | gen3(0x8d,0x77,0x32); // STA &3277 110 | gen3(0x6c,0x76,0x32); // JMP (&3276) 111 | 112 | pc = 0x4000; 113 | gen1(0x60 ); // RTS 114 | 115 | M6502_setVector(mpu, RST, 0x1000); 116 | 117 | M6502_reset(mpu); 118 | M6502_run(mpu); 119 | M6502_delete(mpu); /* We never reach here, but what the hey. */ 120 | 121 | return 0; 122 | } 123 | -------------------------------------------------------------------------------- /test/basic-callback.mst: -------------------------------------------------------------------------------- 1 | 2 | ill: address 1000 opcode 13 memory 13 3 | PC=1001 SP=0100 A=00 X=00 Y=00 P=04 -----I-- 4 | 5 | ill: address 1001 opcode 44 memory 44 6 | PC=1003 SP=0100 A=00 X=00 Y=00 P=04 -----I-- 7 | 8 | ill: address 1003 opcode 5C memory 5C 9 | PC=1006 SP=0100 A=00 X=00 Y=00 P=04 -----I-- 10 | 11 | call: address 2000 opcode 20 12 | PC=1009 SP=01FE A=00 X=00 Y=00 P=04 -----I-- 13 | 14 | wr: address 5000 opcode 00 15 | PC=1009 SP=01FE A=00 X=00 Y=00 P=04 -----I-- 16 | 17 | call: address 3000 opcode 4C 18 | PC=3000 SP=01FE A=00 X=00 Y=00 P=04 -----I-- 19 | 20 | call: address 4000 opcode 6C 21 | PC=4000 SP=01FE A=40 X=00 Y=00 P=04 -----I-- 22 | 23 | rd: address 5000 opcode 00 24 | PC=4000 SP=01FE A=40 X=00 Y=00 P=04 -----I-- 25 | 26 | rd: address 5000 opcode 00 27 | PC=4000 SP=01FE A=40 X=00 Y=00 P=04 -----I-- 28 | 29 | wr: address 5000 opcode 00 30 | PC=4000 SP=01FE A=40 X=00 Y=00 P=04 -----I-- 31 | 32 | BRK instruction: address 1016 opcode 00 33 | PC=1018 SP=01FD A=00 X=00 Y=00 P=06 -----IZ- 34 | -------------------------------------------------------------------------------- /test/call-illegal-callback-modify-code.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2005 Ian Piumarta 2 | * Copyright (c) 2014 Steven Flintham 3 | * 4 | * All rights reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the 'Software'), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, provided that the above copyright notice(s) and this 12 | * permission notice appear in all copies of the Software and that both the 13 | * above copyright notice(s) and this permission notice appear in supporting 14 | * documentation. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include "lib6502.h" 23 | #include "test-utils.h" 24 | 25 | static uint16_t call_modify1_addr; 26 | static uint16_t call_modify2_addr; 27 | static uint16_t ill_modify1_addr; 28 | static uint16_t ill_modify2_addr; 29 | 30 | int done(M6502 *mpu, uint16_t address, uint8_t data) 31 | { 32 | char buffer[64]; 33 | M6502_dump_masked(mpu, buffer); 34 | printf("\nBRK instruction: address %04X opcode %02X\n%s\n", address, data, buffer); 35 | exit(0); 36 | } 37 | 38 | int call(M6502 *mpu, uint16_t address, uint8_t data) 39 | { 40 | char buffer[64]; 41 | M6502_dump_masked(mpu, buffer); 42 | printf("\ncall: address %04X opcode %02X\n%s\n", address, data, buffer); 43 | mpu->memory[call_modify1_addr] += 1; 44 | mpu->memory[call_modify2_addr] += 2; 45 | return 0; 46 | } 47 | 48 | int ill(M6502 *mpu, uint16_t address, uint8_t data) 49 | { 50 | char buffer[64]; 51 | M6502_dump_masked(mpu, buffer); 52 | printf("\nill: address %04X opcode %02X memory %02X\n%s\n", address, data, mpu->memory[address], buffer); 53 | mpu->memory[ill_modify1_addr] += 1; 54 | mpu->memory[ill_modify2_addr] += 2; 55 | return 0; 56 | } 57 | 58 | int oswrch(M6502 *mpu, uint16_t address, uint8_t data) 59 | { 60 | putchar(mpu->registers->a); 61 | mpu->memory[0xffee] = 0x60; // RTS 62 | return 0; 63 | } 64 | 65 | int main(int argc, char *argv[]) 66 | { 67 | M6502 *mpu = M6502_new(0, 0, 0); 68 | parse_args(argc, argv, mpu); 69 | 70 | unsigned pc = 0x1000; 71 | 72 | M6502_setCallback(mpu, call, 0, done ); 73 | M6502_setCallback(mpu, call, 0x2000, call ); 74 | M6502_setCallback(mpu, illegal_instruction, 0x13, ill ); 75 | M6502_setCallback(mpu, call, 0xffee, oswrch); 76 | 77 | # define gen1(X) (mpu->memory[pc++]= (uint8_t)(X)) 78 | # define gen2(X,Y) gen1(X); gen1(Y) 79 | # define gen3(X,Y,Z) gen1(X); gen2(Y,Z) 80 | 81 | gen3(0x20,0x00,0x30); // JSR &3000 82 | gen1(0x13 ); // ill &13 83 | gen3(0x20,0x00,0x30); // JSR &3000 84 | gen1(0x13 ); // ill &13 85 | gen3(0x20,0x00,0x30); // JSR &3000 86 | gen3(0x20,0x00,0x20); // JSR &2000 87 | gen3(0x20,0x00,0x30); // JSR &3000 88 | gen3(0x20,0x00,0x20); // JSR &2000 89 | gen3(0x20,0x00,0x30); // JSR &3000 90 | gen2(0x00,0x00 ); // BRK 91 | 92 | pc = 0x2000; 93 | gen1(0x60 ); // RTS 94 | 95 | pc = 0x3000; 96 | gen2(0xa9,'C' ); // LDA #'C' 97 | gen3(0x20,0xee,0xff); // JSR &FFEE 98 | call_modify1_addr = pc + 1; 99 | gen2(0xa9,'A' ); // LDA #'A' 100 | gen3(0x20,0xee,0xff); // JSR &FFEE 101 | call_modify2_addr = pc + 1; 102 | gen2(0xa9,'A' ); // LDA #'A' 103 | gen3(0x20,0xee,0xff); // JSR &FFEE 104 | ill_modify1_addr = pc + 1; 105 | gen2(0xa9,'A' ); // LDA #'A' 106 | gen3(0x20,0xee,0xff); // JSR &FFEE 107 | ill_modify2_addr = pc + 1; 108 | gen2(0xa9,'A' ); // LDA #'A' 109 | gen3(0x20,0xee,0xff); // JSR &FFEE 110 | gen2(0xa9,'\n' ); // LDA #'\n' 111 | gen3(0x20,0xee,0xff); // JSR &FFEE 112 | gen1(0x60 ); // RTS 113 | 114 | M6502_setVector(mpu, RST, 0x1000); 115 | 116 | M6502_reset(mpu); 117 | M6502_run(mpu); 118 | M6502_delete(mpu); /* We never reach here, but what the hey. */ 119 | 120 | return 0; 121 | } 122 | -------------------------------------------------------------------------------- /test/call-illegal-callback-modify-code.mst: -------------------------------------------------------------------------------- 1 | CAAAA 2 | 3 | ill: address 1003 opcode 13 memory 13 4 | PC=1004 SP=0100 A=0A X=00 Y=00 P=04 -----I-- 5 | CAABC 6 | 7 | ill: address 1007 opcode 13 memory 13 8 | PC=1008 SP=0100 A=0A X=00 Y=00 P=04 -----I-- 9 | CAACE 10 | 11 | call: address 2000 opcode 20 12 | PC=100E SP=01FE A=0A X=00 Y=00 P=04 -----I-- 13 | CBCCE 14 | 15 | call: address 2000 opcode 20 16 | PC=1014 SP=01FE A=0A X=00 Y=00 P=04 -----I-- 17 | CCECE 18 | 19 | BRK instruction: address 1017 opcode 00 20 | PC=1019 SP=01FD A=0A X=00 Y=00 P=04 -----I-- 21 | -------------------------------------------------------------------------------- /test/config.xa: -------------------------------------------------------------------------------- 1 | OSWRCH = $FFEE 2 | QUIT = $F000 3 | 4 | *= $1E00 5 | -------------------------------------------------------------------------------- /test/interleave.mst: -------------------------------------------------------------------------------- 1 | Y -------------------------------------------------------------------------------- /test/interleave.xa: -------------------------------------------------------------------------------- 1 | #include "config.xa" 2 | 3 | JSR SETX10 4 | CPX #10 5 | BNE FAIL 6 | JSR SETX30 7 | CPX #30 8 | BNE FAIL 9 | JSR SETX20 10 | CPX #20 11 | BNE FAIL 12 | JSR SETX30 13 | CPX #30 14 | BNE FAIL 15 | JSR SETX10 16 | CPX #10 17 | BNE FAIL 18 | JSR SETX20 19 | CPX #20 20 | BNE FAIL 21 | 22 | SUCCESS 23 | LDA #'Y' 24 | JSR OSWRCH 25 | JMP QUIT 26 | 27 | FAIL 28 | LDA #'N' 29 | JSR OSWRCH 30 | JMP QUIT 31 | 32 | ; example taken from http://www.6502.org/tutorials/6502opcodes.html 33 | SETX10 LDX #10 34 | .byte $2C 35 | SETX20 LDX #20 36 | .byte $2C 37 | SETX30 LDX #30 38 | RTS 39 | -------------------------------------------------------------------------------- /test/irq-nmi.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2005 Ian Piumarta 2 | * Copyright (c) 2014 Steven Flintham 3 | * 4 | * All rights reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the 'Software'), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, provided that the above copyright notice(s) and this 12 | * permission notice appear in all copies of the Software and that both the 13 | * above copyright notice(s) and this permission notice appear in supporting 14 | * documentation. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include "lib6502.h" 23 | #include "test-utils.h" 24 | 25 | int brk(M6502 *mpu, uint16_t address, uint8_t data) 26 | { 27 | char buffer[64]; 28 | M6502_dump_masked(mpu, buffer); 29 | printf("\nBRK: address %04X opcode %02X\n%s\n", address, data, buffer); 30 | exit(0); 31 | } 32 | 33 | int ill(M6502 *mpu, uint16_t address, uint8_t data) 34 | { 35 | char buffer[64]; 36 | M6502_dump_masked(mpu, buffer); 37 | printf("\nill: address %04X opcode %02X memory %02X\n%s\n", address, data, mpu->memory[address], buffer); 38 | if (data == 0x03) 39 | { 40 | M6502_nmi(mpu); 41 | } 42 | else if (data == 0x13) 43 | { 44 | M6502_irq(mpu); 45 | } 46 | 47 | return 0; 48 | } 49 | 50 | int oswrch(M6502 *mpu, uint16_t address, uint8_t data) 51 | { 52 | putchar(mpu->registers->a); 53 | mpu->memory[0xffee] = 0x60; // RTS 54 | return 0; 55 | } 56 | 57 | 58 | int main(int argc, char *argv[]) 59 | { 60 | M6502 *mpu = M6502_new(0, 0, 0); 61 | parse_args(argc, argv, mpu); 62 | 63 | unsigned pc = 0x1000; 64 | 65 | /* 0x3000 is the IRQ/BRK vector, but call callbacks don't trigger on 66 | * interrupts, so this is only called on BRK. 67 | */ 68 | M6502_setCallback(mpu, call, 0x3000, brk ); 69 | 70 | M6502_setCallback(mpu, illegal_instruction, 0x03, ill ); 71 | M6502_setCallback(mpu, illegal_instruction, 0x13, ill ); 72 | M6502_setCallback(mpu, call, 0xffee, oswrch); 73 | 74 | # define gen1(X) (mpu->memory[pc++]= (uint8_t)(X)) 75 | # define gen2(X,Y) gen1(X); gen1(Y) 76 | # define gen3(X,Y,Z) gen1(X); gen2(Y,Z) 77 | 78 | gen1(0x58 ); // CLI 79 | gen2(0xa9,'A' ); // LDA #'A' 80 | gen3(0x20,0xee,0xff); // JSR &ffee 81 | gen1(0x03 ); // NMI 82 | gen2(0xa9,'B' ); // LDA #'B' 83 | gen3(0x20,0xee,0xff); // JSR &ffee 84 | gen1(0x13 ); // IRQ 85 | gen2(0xa9,'C' ); // LDA #'C' 86 | gen3(0x20,0xee,0xff); // JSR &ffee 87 | gen1(0x78 ); // SEI 88 | gen1(0x13 ); // IRQ (ignored) 89 | gen1(0x03 ); // NMI 90 | gen1(0x13 ); // IRQ (ignored) 91 | gen2(0xa9,'D' ); // LDA #'D' 92 | gen3(0x20,0xee,0xff); // JSR &ffee 93 | gen1(0x58 ); // CLI 94 | gen1(0x13 ); // IRQ 95 | gen2(0x00,0x00 ); // BRK 96 | 97 | pc = 0x2000; 98 | gen2(0xa9,'N' ); // LDA #'N' 99 | gen3(0x20,0xee,0xff); // JSR &ffee 100 | gen1(0x40 ); // RTI 101 | 102 | pc = 0x3000; 103 | gen2(0xa9,'I' ); // LDA #'I' 104 | gen3(0x20,0xee,0xff); // JSR &ffee 105 | gen1(0x40 ); // RTI 106 | 107 | M6502_setVector(mpu, RST, 0x1000); 108 | M6502_setVector(mpu, NMI, 0x2000); 109 | M6502_setVector(mpu, IRQ, 0x3000); 110 | 111 | M6502_reset(mpu); 112 | M6502_run(mpu); 113 | M6502_delete(mpu); /* We never reach here, but what the hey. */ 114 | 115 | return 0; 116 | } 117 | -------------------------------------------------------------------------------- /test/irq-nmi.mst: -------------------------------------------------------------------------------- 1 | A 2 | ill: address 1006 opcode 03 memory 03 3 | PC=1007 SP=0100 A=41 X=00 Y=00 P=00 -------- 4 | NB 5 | ill: address 100C opcode 13 memory 13 6 | PC=100D SP=0100 A=42 X=00 Y=00 P=00 -------- 7 | IC 8 | ill: address 1013 opcode 13 memory 13 9 | PC=1014 SP=0100 A=43 X=00 Y=00 P=04 -----I-- 10 | 11 | ill: address 1014 opcode 03 memory 03 12 | PC=1015 SP=0100 A=43 X=00 Y=00 P=04 -----I-- 13 | N 14 | ill: address 1015 opcode 13 memory 13 15 | PC=1016 SP=0100 A=4E X=00 Y=00 P=04 -----I-- 16 | D 17 | ill: address 101C opcode 13 memory 13 18 | PC=101D SP=0100 A=44 X=00 Y=00 P=00 -------- 19 | I 20 | BRK: address 101D opcode 00 21 | PC=101F SP=01FD A=49 X=00 Y=00 P=04 -----I-- 22 | -------------------------------------------------------------------------------- /test/pc-wrap-1.mst: -------------------------------------------------------------------------------- 1 | Y -------------------------------------------------------------------------------- /test/pc-wrap-1.xa: -------------------------------------------------------------------------------- 1 | #include "config.xa" 2 | 3 | ; It's not important this is self-modifying code, this is just the easiest way 4 | ; to get code at the relevant addresses without fighting with the assembler and 5 | ; the fact run6502 will clobber the top of memory to set up various vectors. 6 | 7 | LDA #$A9 ; LDA #n 8 | STA $FFFE 9 | STA $00 10 | LDA #'N' 11 | STA $FFFF 12 | LDA #'Y' 13 | STA $01 14 | 15 | LDA #$20 ; JSR abs 16 | STA $02 17 | LDA #$EE 18 | STA $03 19 | LDA #$FF 20 | STA $04 21 | LDA #$4C ; JMP abs 22 | STA $05 23 | LDA #QUIT 26 | STA $07 27 | 28 | JMP $FFFE 29 | -------------------------------------------------------------------------------- /test/pc-wrap-2.mst: -------------------------------------------------------------------------------- 1 | Y -------------------------------------------------------------------------------- /test/pc-wrap-2.xa: -------------------------------------------------------------------------------- 1 | #include "config.xa" 2 | 3 | ; It's not important this is self-modifying code, this is just the easiest way 4 | ; to get code at the relevant addresses without fighting with the assembler and 5 | ; the fact run6502 will clobber the top of memory to set up various vectors. 6 | 7 | LDA #$A9 ; LDA #n 8 | STA $FFFD 9 | STA $FFFF 10 | LDA #'N' 11 | STA $FFFE 12 | LDA #'Y' 13 | STA $00 14 | 15 | LDA #$20 ; JSR abs 16 | STA $01 17 | LDA #$EE 18 | STA $02 19 | LDA #$FF 20 | STA $03 21 | LDA #$4C ; JMP abs 22 | STA $04 23 | LDA #QUIT 26 | STA $06 27 | 28 | JMP $FFFD 29 | -------------------------------------------------------------------------------- /test/run-c-tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from __future__ import print_function 4 | import subprocess 5 | 6 | tests = [ 7 | 'basic-callback', 8 | 'call-illegal-callback-modify-code', 9 | 'irq-nmi', 10 | 'setjmp-trick', 11 | 'stack-code-brk', 12 | 'stack-code-jsr', 13 | 'write-callback-modify-code' 14 | ] 15 | 16 | test_args = [ 17 | '-mi', 18 | '-mh', 19 | '-mc -mx 1', 20 | '-mc' 21 | ] 22 | 23 | print('1..', len(tests) * len(test_args), sep='') 24 | i = 1 25 | for test_arg in test_args: 26 | for test in tests: 27 | result = subprocess.check_output(['test/' + test] + test_arg.split()) 28 | expected_result = open('test/' + test + '.mst', 'rb').read() 29 | if result == expected_result: 30 | print('ok', i, test, test_arg) 31 | else: 32 | print('not ok', i, test, test_arg) 33 | i += 1 34 | -------------------------------------------------------------------------------- /test/run-c-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | python test/run-c-tests.py 3 | -------------------------------------------------------------------------------- /test/run-run6502-tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from __future__ import print_function 4 | import glob 5 | import os 6 | import subprocess 7 | 8 | os.chdir('test') 9 | 10 | # It's quite likely the "xa" assembler is not installed; don't generate 11 | # scary test failures if that's the case. 12 | xa_installed = True 13 | try: 14 | result = subprocess.check_output(['xa', '--version']) 15 | if result.find(b'xa65') == -1: 16 | xa_installed = False 17 | except: 18 | xa_installed = False 19 | 20 | # By default we skip slow tests (those with names starting z-) in '-mc' 21 | # modes. 22 | skip_slow_mc = (os.getenv('RUN_SLOW_TESTS', '0') == '0') 23 | 24 | # Since we didn't have to hard-code the test names in the Makefile.am, we 25 | # use wildcards here. 26 | tests = sorted([t for t in glob.glob('*.xa') if t != 'config.xa']) 27 | 28 | test_args = [ 29 | '-mi', 30 | '-mh', 31 | '-mc -mx 1', 32 | '-mc' 33 | ] 34 | 35 | print('1..', len(tests) * len(test_args), sep='') 36 | i = 0 37 | for test_arg in test_args: 38 | for test in tests: 39 | i += 1 40 | basename = test[0:-3] 41 | 42 | if not xa_installed: 43 | print('ok', i, '# skipped (xa not installed):', test, test_arg) 44 | continue 45 | 46 | if skip_slow_mc and basename[0:2] == 'z-' and test_arg[0:3] == '-mc': 47 | print('ok', i, '# skipped (slow -mc):', test, test_arg) 48 | continue 49 | 50 | xa_out = basename + '.mc' 51 | subprocess.check_call(['xa', '-o', xa_out, test]) 52 | result = subprocess.check_output( 53 | ['../run6502', '-l', '1e00', xa_out, '-R', '1e00', '-G', 'ffe0', 54 | '-P', 'ffee', '-X', 'f000'] + test_arg.split()) 55 | expected_result = open(basename + '.mst', 'rb').read() 56 | if result == expected_result: 57 | print('ok', i, test, test_arg) 58 | else: 59 | print('not ok', i, test, test_arg) 60 | -------------------------------------------------------------------------------- /test/run-run6502-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | python test/run-run6502-tests.py 3 | -------------------------------------------------------------------------------- /test/setjmp-trick.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2005 Ian Piumarta 2 | * Copyright (c) 2014 Steven Flintham 3 | * 4 | * All rights reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the 'Software'), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, provided that the above copyright notice(s) and this 12 | * permission notice appear in all copies of the Software and that both the 13 | * above copyright notice(s) and this permission notice appear in supporting 14 | * documentation. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "lib6502.h" 24 | #include "test-utils.h" 25 | 26 | static jmp_buf env; 27 | 28 | int done(M6502 *mpu, uint16_t address, uint8_t data) 29 | { 30 | char buffer[64]; 31 | M6502_dump_masked(mpu, buffer); 32 | printf("\nBRK instruction: address %04X opcode %02X\n%s\n", address, data, buffer); 33 | longjmp(env, 1); 34 | exit(0); 35 | } 36 | 37 | int call(M6502 *mpu, uint16_t address, uint8_t data) 38 | { 39 | char buffer[64]; 40 | M6502_dump_masked(mpu, buffer); 41 | printf("\ncall: address %04X opcode %02X\n%s\n", address, data, buffer); 42 | mpu->registers->pc = address; 43 | longjmp(env, 2); 44 | return 0; 45 | } 46 | 47 | int ill(M6502 *mpu, uint16_t address, uint8_t data) 48 | { 49 | char buffer[64]; 50 | M6502_dump_masked(mpu, buffer); 51 | printf("\nill: address %04X opcode %02X memory %02X\n%s\n", address, data, mpu->memory[address], buffer); 52 | longjmp(env, 3); 53 | return 0; 54 | } 55 | 56 | int main(int argc, char *argv[]) 57 | { 58 | M6502 *mpu = M6502_new(0, 0, 0); 59 | parse_args(argc, argv, mpu); 60 | 61 | unsigned pc = 0x1000; 62 | 63 | /* Read and write callbacks don't provide the correct, up-to-date CPU state 64 | * in the M6502 object, so this trick is a non-starter with them. 65 | */ 66 | 67 | M6502_setCallback(mpu, call, 0, done); 68 | M6502_setCallback(mpu, call, 0x2000, call); 69 | M6502_setCallback(mpu, call, 0x3000, call); 70 | M6502_setCallback(mpu, call, 0x4000, call); 71 | M6502_setCallback(mpu, illegal_instruction, 0x13, ill ); 72 | M6502_setCallback(mpu, illegal_instruction, 0x44, ill ); 73 | M6502_setCallback(mpu, illegal_instruction, 0x5c, ill ); 74 | 75 | # define gen1(X) (mpu->memory[pc++]= (uint8_t)(X)) 76 | # define gen2(X,Y) gen1(X); gen1(Y) 77 | # define gen3(X,Y,Z) gen1(X); gen2(Y,Z) 78 | 79 | gen1(0x13 ); 80 | gen1(0x44 ); 81 | gen1(0x13 ); // not executed, 0x44 is a two-byte illegal instruction 82 | gen1(0x5C ); 83 | gen1(0x13 ); // not executed, 0x5C is a two-byte illegal instruction 84 | gen1(0x13 ); // not executed, 0x5C is a two-byte illegal instruction 85 | gen3(0x20,0x00,0x20); // JSR &2000 86 | gen3(0xad,0x00,0x50); // LDA &5000 87 | gen2(0x00,0x00 ); // BRK 88 | 89 | pc = 0x2000; 90 | gen3(0x8d,0x00,0x50); // STA &5000 91 | gen3(0x4c,0x00,0x30); // JMP &3000 92 | 93 | pc = 0x3000; 94 | gen2(0xa9,0x00 ); // LDA #0 95 | gen3(0x8d,0x76,0x32); // STA &3276 96 | gen2(0xa9,0x40 ); // LDA #&40 97 | gen3(0x8d,0x77,0x32); // STA &3277 98 | gen3(0x6c,0x76,0x32); // JMP (&3276) 99 | 100 | pc = 0x4000; 101 | gen1(0x60 ); // RTS 102 | 103 | M6502_setVector(mpu, RST, 0x1000); 104 | 105 | M6502_reset(mpu); 106 | while (1) 107 | { 108 | volatile int result = setjmp(env); 109 | if (result == 0) 110 | { 111 | M6502_run(mpu); 112 | } 113 | else 114 | { 115 | printf("\nsetjmp() returned %d\n", result); 116 | if (result == 1) 117 | { 118 | break; 119 | } 120 | } 121 | } 122 | M6502_delete(mpu); 123 | 124 | return 0; 125 | } 126 | -------------------------------------------------------------------------------- /test/setjmp-trick.mst: -------------------------------------------------------------------------------- 1 | 2 | ill: address 1000 opcode 13 memory 13 3 | PC=1001 SP=0100 A=00 X=00 Y=00 P=04 -----I-- 4 | 5 | setjmp() returned 3 6 | 7 | ill: address 1001 opcode 44 memory 44 8 | PC=1003 SP=0100 A=00 X=00 Y=00 P=04 -----I-- 9 | 10 | setjmp() returned 3 11 | 12 | ill: address 1003 opcode 5C memory 5C 13 | PC=1006 SP=0100 A=00 X=00 Y=00 P=04 -----I-- 14 | 15 | setjmp() returned 3 16 | 17 | call: address 2000 opcode 20 18 | PC=1009 SP=01FE A=00 X=00 Y=00 P=04 -----I-- 19 | 20 | setjmp() returned 2 21 | 22 | call: address 3000 opcode 4C 23 | PC=3000 SP=01FE A=00 X=00 Y=00 P=04 -----I-- 24 | 25 | setjmp() returned 2 26 | 27 | call: address 4000 opcode 6C 28 | PC=4000 SP=01FE A=40 X=00 Y=00 P=04 -----I-- 29 | 30 | setjmp() returned 2 31 | 32 | BRK instruction: address 100C opcode 00 33 | PC=100E SP=01FD A=00 X=00 Y=00 P=06 -----IZ- 34 | 35 | setjmp() returned 1 36 | -------------------------------------------------------------------------------- /test/stack-code-brk.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2005 Ian Piumarta 2 | * Copyright (c) 2014 Steven Flintham 3 | * 4 | * All rights reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the 'Software'), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, provided that the above copyright notice(s) and this 12 | * permission notice appear in all copies of the Software and that both the 13 | * above copyright notice(s) and this permission notice appear in supporting 14 | * documentation. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include "lib6502.h" 23 | #include "test-utils.h" 24 | 25 | int done(M6502 *mpu, uint16_t address, uint8_t data) 26 | { 27 | exit(0); 28 | } 29 | 30 | int oswrch(M6502 *mpu, uint16_t address, uint8_t data) 31 | { 32 | putchar(mpu->registers->a); 33 | mpu->memory[0xffee] = 0x60; // RTS 34 | return 0; 35 | } 36 | 37 | # define gen1(X) (mpu->memory[pc++]= (uint8_t)(X)) 38 | # define gen2(X,Y) gen1(X); gen1(Y) 39 | # define gen3(X,Y,Z) gen1(X); gen2(Y,Z) 40 | 41 | int main(int argc, char *argv[]) 42 | { 43 | M6502 *mpu = M6502_new(0, 0, 0); 44 | parse_args(argc, argv, mpu); 45 | 46 | unsigned pc = 0x1000; 47 | unsigned saved_pc; 48 | 49 | M6502_setCallback(mpu, call, 0xf000, done ); 50 | M6502_setCallback(mpu, call, 0xffee, oswrch); 51 | 52 | gen2(0xa2, 0xff ); // LDX #&FF 53 | gen1(0x9a ); // TXS 54 | gen2(0xa9, 'A' ); // LDA #'A' 55 | 56 | // LDA #'B' is 0xa9, 0x42. So if we execute a BRK at 0x42a7, it will 57 | // push 0x42, 0xa9 and the flags onto the stack. Since the stack grows 58 | // downwards those bytes will be in the right order for execution. We'll 59 | // additionally push an LDX immediate opcode so we can "execute" the flags 60 | // value. We can nearly force the flags to be whatever we like using PLP, 61 | // although the BRK will set the B and X bits in the stacked value. We 62 | // demonstrate this by explicitly masking off those bits in the values we 63 | // force into the flags. 64 | enum { 65 | flagX= (1<<5), /* unused */ 66 | flagB= (1<<4) /* irq from brk */ 67 | }; 68 | uint8_t mask = ~(flagX | flagB); 69 | gen2(0xa0, '0' & mask); // LDY #('0' with B/X masked off) 70 | gen1(0x5a ); // PHY 71 | gen1(0x28 ); // PLP 72 | gen3(0x4c, 0xa7, 0x42); // JMP &42A7 73 | pc = 0x42a7; 74 | gen2(0x00, 0x00 ); // BRK 75 | saved_pc = pc; 76 | pc = 0x0; // BRK vector 77 | gen2(0xa9, 0xa2 ); // LDA # 78 | gen1(0x48 ); // PHA 79 | gen3(0x4c, 0xfc, 0x01); // JMP &01FC 80 | pc = 0x200; 81 | gen3(0x20, 0xee, 0xff); // JSR &FFEE 82 | gen1(0x8a ); // TXA 83 | gen3(0x20, 0xee, 0xff); // JSR &FFEE 84 | gen1(0x68 ); // PLA 85 | gen1(0x40 ); // RTI 86 | pc = saved_pc; 87 | 88 | // Let's do the same thing again, but this time code has already been 89 | // executed from that address on the stack, so we're verifying the change 90 | // is picked up. We do LDA #'C' this time, so we execute the BRK from 91 | // 0x43a7. 92 | gen2(0xa0, '1' & mask); // LDY #('1' with B/X masked off) 93 | gen1(0x5a ); // PHY 94 | gen1(0x28 ); // PLP 95 | gen3(0x4c, 0xa7, 0x43); // JMP &43A7 96 | pc = 0x43a7; 97 | gen2(0x00, 0x00 ); // BRK 98 | 99 | gen3(0x4c, 0x00, 0xf0); // JMP &F000 (quit) 100 | 101 | M6502_setVector(mpu, RST, 0x1000); 102 | 103 | M6502_reset(mpu); 104 | M6502_run(mpu); 105 | M6502_delete(mpu); /* We never reach here, but what the hey. */ 106 | 107 | return 0; 108 | } 109 | -------------------------------------------------------------------------------- /test/stack-code-brk.mst: -------------------------------------------------------------------------------- 1 | B0C1 -------------------------------------------------------------------------------- /test/stack-code-jsr.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2005 Ian Piumarta 2 | * Copyright (c) 2014 Steven Flintham 3 | * 4 | * All rights reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the 'Software'), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, provided that the above copyright notice(s) and this 12 | * permission notice appear in all copies of the Software and that both the 13 | * above copyright notice(s) and this permission notice appear in supporting 14 | * documentation. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include "lib6502.h" 23 | #include "test-utils.h" 24 | 25 | int done(M6502 *mpu, uint16_t address, uint8_t data) 26 | { 27 | char buffer[64]; 28 | M6502_dump_masked(mpu, buffer); 29 | printf("\nBRK instruction: address %04X opcode %02X\n%s\n", address, data, buffer); 30 | exit(0); 31 | } 32 | 33 | int oswrch(M6502 *mpu, uint16_t address, uint8_t data) 34 | { 35 | putchar(mpu->registers->a); 36 | mpu->memory[0xffee] = 0x60; // RTS 37 | return 0; 38 | } 39 | 40 | # define gen1(X) (mpu->memory[pc++]= (uint8_t)(X)) 41 | # define gen2(X,Y) gen1(X); gen1(Y) 42 | # define gen3(X,Y,Z) gen1(X); gen2(Y,Z) 43 | 44 | int main(int argc, char *argv[]) 45 | { 46 | M6502 *mpu = M6502_new(0, 0, 0); 47 | parse_args(argc, argv, mpu); 48 | 49 | unsigned pc = 0x1000; 50 | unsigned saved_pc; 51 | 52 | M6502_setCallback(mpu, call, 0, done ); 53 | M6502_setCallback(mpu, call, 0xffee, oswrch); 54 | 55 | gen2(0xa2, 0xff ); // LDX #&FF 56 | gen1(0x9a ); // TXS 57 | gen2(0xa9, 'A' ); // LDA #'A' 58 | 59 | // LDA #'B' is 0xa9, 0x42. So if we execute a JSR at 0x42a7, it will 60 | // push 0x42 and then 0xa9 onto the stack. Since the stack grows downwards 61 | // those bytes will be in the right order for execution. 62 | gen3(0x4c, 0xa7, 0x42); // JMP &42A7 63 | pc = 0x42a7; 64 | gen3(0x20, 0x00, 0x30); // JSR &3000 65 | saved_pc = pc; 66 | pc = 0x3000; 67 | gen3(0x4c, 0xfe, 0x01); // JMP &01FE 68 | pc = 0x200; 69 | gen3(0x20, 0xee, 0xff); // JSR &FFEE 70 | gen1(0x60 ); // RTS 71 | pc = saved_pc; 72 | 73 | // Let's do the same thing again, but this time code has already been 74 | // executed from that address on the stack, so we're verifying the change 75 | // is picked up. We do LDA #'C' this time, so we execute the JSR from 76 | // 0x43a7. 77 | gen3(0x4c, 0xa7, 0x43); // JMP &43A7 78 | pc = 0x43a7; 79 | gen3(0x20, 0x00, 0x30); // JSR &3000 80 | 81 | gen2(0x00, 0x00 ); // BRK 82 | 83 | M6502_setVector(mpu, RST, 0x1000); 84 | 85 | M6502_reset(mpu); 86 | M6502_run(mpu); 87 | M6502_delete(mpu); /* We never reach here, but what the hey. */ 88 | 89 | return 0; 90 | } 91 | -------------------------------------------------------------------------------- /test/stack-code-jsr.mst: -------------------------------------------------------------------------------- 1 | BC 2 | BRK instruction: address 43AA opcode 00 3 | PC=43AC SP=01FC A=43 X=FF Y=00 P=04 -----I-- 4 | -------------------------------------------------------------------------------- /test/test-utils.c: -------------------------------------------------------------------------------- 1 | /* parse-args.c -- utility function for C test programs */ 2 | 3 | /* Copyright (c) 2005 Ian Piumarta 4 | * Copyright (c) 2014 Steven Flintham 5 | * 6 | * All rights reserved. 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a 9 | * copy of this software and associated documentation files (the 'Software'), 10 | * to deal in the Software without restriction, including without limitation 11 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, provided that the above copyright notice(s) and this 14 | * permission notice appear in all copies of the Software and that both the 15 | * above copyright notice(s) and this permission notice appear in supporting 16 | * documentation. 17 | * 18 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 19 | */ 20 | 21 | /* Some of this code is copy-and-pasted from run6502.c, but there's not enough 22 | * of it for me to want to complicate things even slightly by trying to share 23 | * it, especially since this is test code and somewhat distinct. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "lib6502.h" 32 | 33 | static const char *program= 0; 34 | static M6502_Mode mode= M6502_ModeHybrid; 35 | static int max_insns= 0; /* default */ 36 | 37 | enum { 38 | flagX= (1<<5), /* unused */ 39 | flagB= (1<<4) /* irq from brk */ 40 | }; 41 | 42 | void fail(const char *fmt, ...) 43 | { 44 | va_list ap; 45 | fflush(stdout); 46 | va_start(ap, fmt); 47 | vfprintf(stderr, fmt, ap); 48 | va_end(ap); 49 | fprintf(stderr, "\n"); 50 | exit(1); 51 | } 52 | 53 | static void usage(int status) 54 | { 55 | FILE *stream = stderr; 56 | fprintf(stream, "usage: %s [option ...]\n", program); 57 | fprintf(stream, " -h -- help (print this message)\n"); 58 | fprintf(stream, " -mc -- use compiled emulation mode\n"); 59 | fprintf(stream, " -mh -- use hybrid emulation mode (default)\n"); 60 | fprintf(stream, " -mi -- use interpreted emulation mode\n"); 61 | fprintf(stream, " -mx count -- maximum instructions to JIT (-mc/-mh)\n"); 62 | exit(status); 63 | } 64 | 65 | static int doMode(M6502_Mode m) 66 | { 67 | mode= m; 68 | return 0; 69 | } 70 | 71 | static int doMaxInsns(int argc, char **argv, M6502 *mpu) 72 | { 73 | if (argc < 2) usage(1); 74 | char *end; 75 | unsigned long l= strtol(argv[1], &end, 10); 76 | if (*end) fail("bad number: %s", argv[1]); 77 | max_insns= l; 78 | return 1; 79 | } 80 | 81 | void parse_args(int argc, char *argv[], M6502 *mpu) 82 | { 83 | program= argv[0]; 84 | while (++argv, --argc > 0) 85 | { 86 | int n= 0; 87 | if (!strcmp(*argv, "-h")) usage(0); 88 | else if (!strcmp(*argv, "-mc")) n= doMode(M6502_ModeCompiled); 89 | else if (!strcmp(*argv, "-mh")) n= doMode(M6502_ModeHybrid); 90 | else if (!strcmp(*argv, "-mi")) n= doMode(M6502_ModeInterpreted); 91 | else if (!strcmp(*argv, "-mx")) n= doMaxInsns(argc, argv, mpu); 92 | else usage(1); 93 | argc -= n; 94 | argv += n; 95 | } 96 | 97 | M6502_setMode(mpu, mode, max_insns); 98 | } 99 | 100 | void M6502_dump_masked(M6502 *mpu, char buffer[64]) 101 | { 102 | uint8_t orig_p = mpu->registers->p; 103 | mpu->registers->p &= ~(flagB | flagX); 104 | M6502_dump(mpu, buffer); 105 | mpu->registers->p = orig_p; 106 | } 107 | -------------------------------------------------------------------------------- /test/test-utils.h: -------------------------------------------------------------------------------- 1 | /* test-utils.h -- utility functions for C test programs */ 2 | 3 | /* Copyright (c) 2005 Ian Piumarta 4 | * Copyright (c) 2014 Steven Flintham 5 | * 6 | * All rights reserved. 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a 9 | * copy of this software and associated documentation files (the 'Software'), 10 | * to deal in the Software without restriction, including without limitation 11 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, provided that the above copyright notice(s) and this 14 | * permission notice appear in all copies of the Software and that both the 15 | * above copyright notice(s) and this permission notice appear in supporting 16 | * documentation. 17 | * 18 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 19 | */ 20 | 21 | #ifndef PARSEARGS_H 22 | #define PARSEARGS_H 23 | 24 | #include "lib6502.h" 25 | 26 | void parse_args(int argc, char *argv[], M6502 *mpu); 27 | 28 | void M6502_dump_masked(M6502 *mpu, char buffer[64]); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /test/trivial-test.mst: -------------------------------------------------------------------------------- 1 | Y -------------------------------------------------------------------------------- /test/trivial-test.xa: -------------------------------------------------------------------------------- 1 | #include "config.xa" 2 | 3 | LDA #'Y' 4 | JSR OSWRCH 5 | JMP QUIT 6 | -------------------------------------------------------------------------------- /test/write-callback-modify-code.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2005 Ian Piumarta 2 | * Copyright (c) 2014 Steven Flintham 3 | * 4 | * All rights reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the 'Software'), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, provided that the above copyright notice(s) and this 12 | * permission notice appear in all copies of the Software and that both the 13 | * above copyright notice(s) and this permission notice appear in supporting 14 | * documentation. 15 | * 16 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include "lib6502.h" 23 | #include "test-utils.h" 24 | 25 | int done(M6502 *mpu, uint16_t address, uint8_t data) 26 | { 27 | char buffer[64]; 28 | M6502_dump_masked(mpu, buffer); 29 | printf("\nBRK instruction: address %04X opcode %02X\n%s\n", address, data, buffer); 30 | exit(0); 31 | } 32 | 33 | int oswrch(M6502 *mpu, uint16_t address, uint8_t data) 34 | { 35 | putchar(mpu->registers->a); 36 | mpu->memory[0xffee] = 0x60; // RTS 37 | return 0; 38 | } 39 | 40 | # define gen1(X) (mpu->memory[pc++]= (uint8_t)(X)) 41 | # define gen2(X,Y) gen1(X); gen1(Y) 42 | # define gen3(X,Y,Z) gen1(X); gen2(Y,Z) 43 | 44 | int wr(M6502 *mpu, uint16_t address, uint8_t data) 45 | { 46 | if (address != 0x42) 47 | { 48 | abort(); 49 | } 50 | 51 | unsigned pc = 0x6000; 52 | gen2(0xa9, data); // LDA #data 53 | gen3(0x4c, 0x00, 0x20); // JMP &2000 54 | return 0; 55 | } 56 | 57 | int main(int argc, char *argv[]) 58 | { 59 | M6502 *mpu = M6502_new(0, 0, 0); 60 | parse_args(argc, argv, mpu); 61 | 62 | unsigned pc = 0x1000; 63 | 64 | M6502_setCallback(mpu, call, 0, done); 65 | M6502_setCallback(mpu, call, 0xffee, oswrch); 66 | M6502_setCallback(mpu, write, 0x42, wr ); 67 | 68 | gen2(0xa9, '>' ); // LDA #'>' 69 | gen3(0x20, 0xee, 0xff); // JSR &FFEE 70 | gen2(0xa2, 'A' ); // LDX #'A' 71 | gen3(0x8e, 0x42, 0x00); // STX &0042 72 | gen3(0x20, 0x00, 0x60); // JSR &6000 73 | gen1(0xe8 ); // INX 74 | gen2(0xe0, 'Z'+1 ); // CPX #('Z'+1) 75 | gen2(0x90, 0xf5 ); // BCC to STX 76 | 77 | gen2(0xa0, 0x05 ); // LDY #&05 78 | gen2(0xa9, '>' ); // LDA #'>' 79 | gen3(0x20, 0xee, 0xff); // JSR &FFEE 80 | gen2(0xa2, 'A' ); // LDX #'A' 81 | gen2(0x96, 0x42-0x05 ); // STX (&42-&05),Y 82 | gen3(0x20, 0x00, 0x60); // JSR &6000 83 | gen1(0xe8 ); // INX 84 | gen2(0xe0, 'Z'+1 ); // CPX #('Z'+1) 85 | gen2(0x90, 0xf6 ); // BCC to STX 86 | 87 | gen2(0x00, 0x00 ); // BRK 88 | 89 | pc = 0x2000; 90 | gen3(0x20, 0xee, 0xff); // JSR &FFEE 91 | gen1(0x60 ); // RTS 92 | 93 | M6502_setVector(mpu, RST, 0x1000); 94 | 95 | M6502_reset(mpu); 96 | M6502_run(mpu); 97 | M6502_delete(mpu); /* We never reach here, but what the hey. */ 98 | 99 | return 0; 100 | } 101 | -------------------------------------------------------------------------------- /test/write-callback-modify-code.mst: -------------------------------------------------------------------------------- 1 | >ABCDEFGHIJKLMNOPQRSTUVWXYZ>ABCDEFGHIJKLMNOPQRSTUVWXYZ 2 | BRK instruction: address 1025 opcode 00 3 | PC=1027 SP=01FD A=5A X=5B Y=05 P=07 -----IZC 4 | -------------------------------------------------------------------------------- /test/z-self-modify-1.mst: -------------------------------------------------------------------------------- 1 | Y -------------------------------------------------------------------------------- /test/z-self-modify-1.xa: -------------------------------------------------------------------------------- 1 | ; This test attempts to confirm that in hybrid mode, the JITted code is 2 | ; discarded correctly if it's modified by the interpreter. 3 | 4 | #include "config.xa" 5 | 6 | COUNT1 = $71 7 | COUNT2 = $72 8 | COUNT3 = $73 9 | 10 | ; We loop lots to get as much chance of a problem occurring as possible. 11 | STZ COUNT1 12 | LOOP1 13 | LDY #0 14 | LOOP2 15 | LDX #0 16 | LOOP3 17 | 18 | ; The heart of the test. We LDA #n, then CMP
. If the two don't 19 | ; match we have a problem. 20 | LDAOP 21 | LDA #3 22 | CMP LDAOP+1 23 | BNE FAIL 24 | 25 | ; We now modify the LDA operand... 26 | INC LDAOP+1 27 | 28 | ; ... and occupy as much of the interpreter's time as possible while the JIT 29 | ; thread picks up the modified version (if it's not working from the snapshot). 30 | ; In reality we probably go round multiple times before the JIT completes. 31 | NOP 32 | NOP 33 | NOP 34 | NOP 35 | NOP 36 | NOP 37 | NOP 38 | NOP 39 | NOP 40 | NOP 41 | NOP 42 | NOP 43 | NOP 44 | NOP 45 | NOP 46 | NOP 47 | NOP 48 | NOP 49 | NOP 50 | NOP 51 | NOP 52 | NOP 53 | NOP 54 | NOP 55 | NOP 56 | NOP 57 | NOP 58 | NOP 59 | NOP 60 | NOP 61 | NOP 62 | NOP 63 | NOP 64 | NOP 65 | NOP 66 | NOP 67 | NOP 68 | NOP 69 | NOP 70 | NOP 71 | NOP 72 | NOP 73 | NOP 74 | NOP 75 | NOP 76 | NOP 77 | NOP 78 | 79 | ; And round and round we go. 80 | DEX 81 | BNE LOOP3 82 | DEY 83 | BNE LOOP2 84 | DEC COUNT1 85 | BNE LOOP1 86 | 87 | OK 88 | LDA #'Y' 89 | JSR OSWRCH 90 | JMP QUIT 91 | FAIL 92 | LDA #'N' 93 | JSR OSWRCH 94 | JMP QUIT 95 | -------------------------------------------------------------------------------- /test/z-self-modify-2.mst: -------------------------------------------------------------------------------- 1 | Y -------------------------------------------------------------------------------- /test/z-self-modify-2.xa: -------------------------------------------------------------------------------- 1 | ; This test attempts to confirm that as subtle potential bug in the hybrid JIT 2 | ; implementation is not present. 3 | ; 4 | ; The potential problem is as follows: 5 | ; - we decide to JIT some code 6 | ; - we take a snapshot of memory 7 | ; - we kick off a JIT thread which *works off the main memory array*, not the 8 | ; snapshot 9 | ; - in the meantime the interpreter executes some code which modifies the code 10 | ; being JITted before it is actually jitted. 11 | ; - we JIT the modified version of the code 12 | ; - the interpreter then executes some code which reverts the change (A) 13 | ; - we decide to execute the JITted function. We check memory against the memory 14 | ; snapshot taken when we started JITting and find no differences in any 15 | ; addresses which contain code, because of the previous step marked (A). 16 | ; - boom, our JITted code is not doing what it should. 17 | ; 18 | ; The fix for this problem is simply to ensure that the JIT thread works off 19 | ; the snapshot of memory taken when we launched the JIT thread. Note that even 20 | ; if we fail to do this, self-modifying code which doesn't "undo" itself will 21 | ; be noticed when we use the memory snapshot to decide if the JITted code is 22 | ; still valid. 23 | ; 24 | ; This test case should execute correctly in all modes (of course), but in 25 | ; hybrid mode it should *fail* if the implementation is temporarily changed to 26 | ; JIT from mpu->memory and not memory_snapshot. At the time of writing it does. 27 | 28 | 29 | 30 | #include "config.xa" 31 | 32 | COUNT1 = $71 33 | COUNT2 = $72 34 | COUNT3 = $73 35 | 36 | ; We loop lots to get as much chance of a problem occurring as possible. 37 | STZ COUNT1 38 | LOOP1 39 | LDY #0 40 | LOOP2 41 | LDX #0 42 | LOOP3 43 | 44 | ; The heart of the test. We LDA #n, then CMP
. If the two don't 45 | ; match we have a problem. 46 | LDAOP 47 | LDA #3 48 | CMP LDAOP+1 49 | BNE FAIL 50 | 51 | ; We now modify the LDA operand... 52 | INC LDAOP+1 53 | 54 | ; ... and occupy as much of the interpreter's time as possible while the JIT 55 | ; thread picks up the modified version (if it's not working from the snapshot). 56 | ; In reality we probably go round multiple times before the JIT completes. 57 | NOP 58 | NOP 59 | NOP 60 | NOP 61 | NOP 62 | NOP 63 | NOP 64 | NOP 65 | NOP 66 | NOP 67 | NOP 68 | NOP 69 | NOP 70 | NOP 71 | NOP 72 | NOP 73 | NOP 74 | NOP 75 | NOP 76 | NOP 77 | NOP 78 | NOP 79 | NOP 80 | NOP 81 | NOP 82 | NOP 83 | NOP 84 | NOP 85 | NOP 86 | NOP 87 | NOP 88 | NOP 89 | NOP 90 | NOP 91 | NOP 92 | NOP 93 | NOP 94 | NOP 95 | NOP 96 | NOP 97 | NOP 98 | NOP 99 | NOP 100 | NOP 101 | NOP 102 | NOP 103 | NOP 104 | 105 | ; We now put the operand back. Since we only switch from interpreting to JITting 106 | ; on a control transfer, we know the transition will occur at a point when we've 107 | ; put the operand back, which is helpful. 108 | DEC LDAOP+1 109 | 110 | ; And round and round we go. 111 | DEX 112 | BNE LOOP3 113 | DEY 114 | BNE LOOP2 115 | DEC COUNT1 116 | BNE LOOP1 117 | 118 | OK 119 | LDA #'Y' 120 | JSR OSWRCH 121 | JMP QUIT 122 | FAIL 123 | LDA #'N' 124 | JSR OSWRCH 125 | JMP QUIT 126 | -------------------------------------------------------------------------------- /util.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | #include "util.h" 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | boost::mutex log_mutex; 25 | 26 | void log(const std::string &s) 27 | { 28 | boost::mutex::scoped_lock scoped_lock(log_mutex); 29 | std::cerr << s << std::endl; 30 | } 31 | 32 | void die(const char *s) 33 | { 34 | fflush(stdout); 35 | fprintf(stderr, "\n%s\n", s); 36 | abort(); 37 | } 38 | 39 | std::string spaces(int n) 40 | { 41 | return std::string(4 * n, ' '); 42 | } 43 | 44 | std::string apply_prefix(const std::string &prefix, const std::string &s) 45 | { 46 | std::string result = prefix; 47 | for (std::string::size_type i = 0; i < s.length(); ++i) 48 | { 49 | result += s[i]; 50 | if ((s[i] == '\n') && ((i + 1) < s.length())) 51 | { 52 | result.append(prefix); 53 | } 54 | } 55 | return result; 56 | } 57 | 58 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Steven Flintham 2 | * 3 | * All rights reserved. 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a 6 | * copy of this software and associated documentation files (the 'Software'), 7 | * to deal in the Software without restriction, including without limitation 8 | * the rights to use, copy, modify, merge, publish, distribute, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, provided that the above copyright notice(s) and this 11 | * permission notice appear in all copies of the Software and that both the 12 | * above copyright notice(s) and this permission notice appear in supporting 13 | * documentation. 14 | * 15 | * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. 16 | */ 17 | 18 | #ifndef UTIL_H 19 | #define UTIL_H 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | #define CANT_HAPPEN(s) \ 30 | do { \ 31 | std::stringstream stream; \ 32 | stream << __FILE__ << ":" << __LINE__ << ":" << s; \ 33 | throw std::runtime_error(stream.str()); \ 34 | } \ 35 | while (false) 36 | 37 | #ifdef LOG 38 | #define TRACE(s) \ 39 | do { \ 40 | std::stringstream prefix; \ 41 | prefix << __FILE__ << ":" << __LINE__ << "\t" << \ 42 | boost::this_thread::get_id() << "\t"; \ 43 | std::stringstream message; \ 44 | message << s; \ 45 | log(apply_prefix(prefix.str(), message.str())); \ 46 | } \ 47 | while (false) 48 | #else 49 | #define TRACE(s) \ 50 | do { \ 51 | } \ 52 | while (false) 53 | #endif 54 | 55 | // Avoid spurious "unused variable" warnings from regular assert(). 56 | #ifndef NDEBUG 57 | #define ASSERT_EQUAL(x, y) assert((x) == (y)) 58 | #else 59 | #define ASSERT_EQUAL(x, y) \ 60 | do { \ 61 | x = x; \ 62 | } \ 63 | while (0); 64 | #endif 65 | 66 | extern boost::mutex log_mutex; 67 | void log(const std::string &s); 68 | void die(const char *s); 69 | 70 | std::string spaces(int n); 71 | std::string apply_prefix(const std::string &prefix, const std::string &s); 72 | 73 | #endif 74 | --------------------------------------------------------------------------------