First public release

This commit is contained in:
Steven Flintham 2014-06-25 18:47:24 +01:00
commit 5d5ddba7f1
89 changed files with 17305 additions and 0 deletions

42
.gitignore vendored Normal file
View File

@ -0,0 +1,42 @@
*~
*.o
*.lo
.deps
.libs
Makefile
Makefile.in
aclocal.m4
autom4te.cache
config.guess
config.h
config.log
config.status
config.sub
configure
depcomp
examples/.dirstamp
examples/lib1
install-sh
lib6502-jit*
lib6502-jit*
libtool
ltmain.sh
m4/libtool.m4
m4/ltoptions.m4
m4/ltsugar.m4
m4/ltversion.m4
m4/lt~obsolete.m4
missing
run6502
stamp-h1
test/.dirstamp
test/*.mc
test/basic-callback
test/call-illegal-callback-modify-code
test/irq-nmi
test/setjmp-trick
test/stack-code-brk
test/stack-code-jsr
test/write-callback-modify-code
test/z-self-modify-1.mc
test/z-self-modify-1.out

42
AddressRange.cpp Normal file
View File

@ -0,0 +1,42 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#include "AddressRange.h"
#include <assert.h>
#include "const.h"
AddressRange::AddressRange(uint16_t addr)
: range_begin_(addr), range_end_(range_begin_ + 1)
{
}
AddressRange::AddressRange(uint32_t range_begin, uint32_t range_end)
: range_begin_(range_begin), range_end_(range_end)
{
assert(range_begin_ < memory_size);
assert(range_end_ <= (memory_size + 0xff));
assert(range_begin_ < range_end_);
}
bool AddressRange::all_memory() const
{
// This doesn't catch some degenerate cases (e.g. range_begin_ = 0x1,
// range_end_ = 0x10002) but that doesn't matter.
return (range_begin_ == 0) && (range_end_ == memory_size);
}

101
AddressRange.h Normal file
View File

@ -0,0 +1,101 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
// An AddressRange represents a contiguous range of addresses in the emulated
// memory, expressed as a half-open interval ("begin" is included, "end" is
// excluded). To allow convenient handling of cases where addresses wrap around
// at the top of memory, end may be as large as 0x100ff; this allows the
// effective address range of an instruction like LDA &ffff,Y to be represented.
// (The "largest" address accessed is &00fe, and since the interval is half-open
// end needs to allow a value one larger.)
#ifndef ADDRESSRANGE_H
#define ADDRESSRANGE_H
#include <stdint.h>
class AddressRange
{
public:
// Convenience function; equivalent to AddressRange(addr, addr + 1) without
// any need to worry about whether addr + 1 will wrap to 0.
AddressRange(uint16_t addr);
AddressRange(uint32_t range_begin, uint32_t range_end);
uint32_t range_begin() const
{
return range_begin_;
}
uint32_t range_end() const
{
return range_end_;
}
// Return true iff AddressRange covers the whole of memory.
bool all_memory() const;
class const_iterator
{
friend class AddressRange;
public:
uint16_t operator*() const
{
// Truncating down to 16 bits gives exactly the behaviour we
// require if this is a range which uses values >= 0x10000 to
// indicate wrapping around to the start of memory.
return static_cast<uint16_t>(v_);
}
const_iterator &operator++()
{
++v_;
return *this;
}
bool operator!=(const const_iterator &rhs)
{
return v_ != rhs.v_;
}
private:
const_iterator(uint32_t v)
: v_(v)
{
}
uint32_t v_;
};
const_iterator begin() const
{
return const_iterator(range_begin_);
}
const_iterator end() const
{
return const_iterator(range_end_);
}
private:
uint32_t range_begin_;
uint32_t range_end_;
};
#endif

95
AddressSet.cpp Normal file
View File

@ -0,0 +1,95 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#include "AddressSet.h"
#include <assert.h>
#include <sstream>
#include <stddef.h>
#include "AddressRange.h"
#include "util.h"
void AddressSet::insert(uint16_t address)
{
set_.insert(address);
}
void AddressSet::insert(const AddressRange &range)
{
for (AddressRange::const_iterator it = range.begin(); it != range.end();
++it)
{
set_.insert(*it);
}
}
namespace
{
std::string dump_range(uint32_t range_start, uint32_t range_end)
{
std::stringstream s;
s << std::hex << std::setfill('0');
if ((range_start + 1) == range_end)
{
s << "0x" << std::setw(4) << range_start;
}
else
{
// It's probably more readable to dump in this (inclusive) format
// than to insist on using the half-open intervals which are
// "natural" in the code itself.
s << "0x" << std::setw(4) << range_start << "-" <<
"0x" << std::setw(4) << (range_end - 1);
}
return s.str();
}
}
std::string AddressSet::dump(int indent) const
{
std::stringstream s;
bool in_range = false;
uint32_t range_start;
uint32_t range_last;
for (AddressSet::const_iterator it = set_.begin(); it != set_.end(); ++it)
{
uint16_t i = *it;
if (!in_range)
{
range_start = i;
range_last = i;
in_range = true;
}
else
{
if (i != (range_last + 1))
{
s << spaces(indent) <<
dump_range(range_start, range_last + 1) << "\n";
range_start = i;
}
range_last = i;
}
}
if (in_range)
{
s << spaces(indent) << dump_range(range_start, range_last + 1) << "\n";
}
return s.str();
}

66
AddressSet.h Normal file
View File

@ -0,0 +1,66 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#ifndef ADDRESSSET_H
#define ADDRESSSET_H
#include <set>
#include <stdint.h>
#include <string>
class AddressRange;
class AddressSet
{
private:
// This might not be the perfect representation, but it's simple and clean,
// so let's stick with it unless profiling shows this is a problem.
typedef std::set<uint16_t> Container;
public:
AddressSet()
{
}
void insert(uint16_t address);
void insert(const AddressRange &range);
typedef Container::const_iterator const_iterator;
const_iterator begin() const
{
return set_.begin();
}
const_iterator end() const
{
return set_.end();
}
Container::size_type size() const
{
return set_.size();
}
std::string dump(int indent) const;
private:
std::set<uint16_t> set_;
};
#endif

60
COPYING Normal file
View File

@ -0,0 +1,60 @@
TL;DR: If you're redistributing this you should read through the text below and
examine the headers on the individual files, but basically the C/C++ source
code (with the exception of valgrind.h, which can be removed if necessary) was
all written by Ian Piumarta or Steven Flintham and is licensed under the "MIT
(X11 flavour)" licence at the bottom of this file, just as lib6502 itself is.
The autotools infrastructure support is GPL licensed but has exceptions for use
(as is the case here) in autoconfigured packages.
valgrind.h has its own license; see the comments at the top of that file.
build-aux/tap-driver.sh (used as part of "make check") is GPLv2 licensed with
an exception (which I believe applies to this package) allowing distribution
under "the same distribution terms that you use for the rest of that program".
See the comments at the top of that file for more details.
m4/boost.m4 (used to autoconfigure the build against the Boost libraries) is
GPLv3 licensed with an exception (which I believe applies to this package)
allowing distribution under "terms of your choice". See the comments at the top
of that file for more details.
The text below is from Ian Piumarta's lib6502's COPYING file. lib6502-jit
contains almost all of the code and documentation from lib6502 itself.
As the author of the remaining parts of lib6502-jit, I am granting the same
permissions and have added my own copyright notice, but the text below is
otherwise unchanged.
-- Steven Flintham
Distasteful though it is for me to have to induce from afar any perturbation
into your pursuit of happiness, this MIT (X11 flavour) license is at least
relatively benign. Investigation into copyright stupidity reveals that it is
effectively impossible to dedicate (formally) any software to the public
domain (the only sure path to this most enlightened status being to leave the
software to expire naturally from its 25-, 50-, 75- or whatever-year copyright
rot). I fear this is not going to change before the revolution comes. In the
meantime the only way I can *guarantee* you any rights at all to this software
would (unfortunately) appear to be...
Copyright (c) 2005 Ian Piumarta
Copyright (c) 2014 Steven Flintham
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the 'Software'), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to
do so, provided that the above copyright notice(s) and this permission
notice appear in all copies or substantial portions of the Software.
Inclusion of the above copyright notice(s) and this permission notice in
supporting documentation would be appreciated, but is not required.
THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.

33
CREDITS Normal file
View File

@ -0,0 +1,33 @@
lib6502-jit implements (nearly) the same API as Ian Piumarta's lib6502
(http://www.piumarta.com/software/lib6502/) and includes virtually all of
lib6502's code and documentation with only minor modifications; the lib6502
emulation code is used to implement the interpreted and hybrid emulation modes
in lib6502-jit. The contents of the examples and man directories are almost
verbatim copies of those in lib6502. Thanks to Ian for making lib6502
available. Please do not send bug reports regarding lib6502-jit to Ian!
This distribution itself doesn't contain any LLVM code, but obviously without
the LLVM project lib6502-jit could not exist.
valgrind.h is taken from Valgrind (http://valgrind.org/).
build-aux/tap-driver.sh is part of GNU Automake and was taken from
https://raw.githubusercontent.com/kergoth/automake/master/lib/tap-driver.sh.
m4/boost.m4 (used to autoconfigure the build against the Boost libraries) is
taken from https://github.com/tsuna/boost.m4.
While I'd be lying if I said I enjoyed working with Autotools, I am grateful
for the work people have put in to make it possible to build packages portably
on a range of different platforms.
The technique (but not the code) used to translate a JITted function's machine
code into assembly in Function::dump_machine_code() is taken from the libjit
(https://www.gnu.org/software/libjit/) dump_object_code() function.
The algorithm used to implement ADC/SDC in decimal mode is taken from
http://www.6502.org/tutorials/decimal_mode.html. The test program on the same
page was used to validate the implementation.
Klaus Dormann's "6502 functional test" and "65C02 extended opcodes test" were
used to validate the behaviour of the emulation.

417
Function.cpp Normal file
View File

@ -0,0 +1,417 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#include "Function.h"
#include <errno.h>
#include <sstream>
#include <stdexcept>
#include <unistd.h>
#include "valgrind.h"
#include "const.h"
#include "LLVMStuff.h"
#include "M6502Internal.h"
#include "Registers.h"
#include "util.h"
// Note that we call update_memory_snapshot() after invoking callbacks here, but
// not before. It would be correct to do so, but it's not necessary. Firstly, we
// arrange that the memory snapshot is kept up-to-date during execution under
// our control (i.e. not involving callbacks), so it isn't necessary. Secondly,
// even if it were necessary, it would be redundant, since any actions needed
// as a result of the update can wait until after the callback is called and the
// call after the callback would perform them.
namespace
{
// We have the callback_pc argument to allow us to special-case the
// contents of the PC register for lib6502 compatibility. Without this
// we would always pass registers.pc, which is "address of the next
// instruction to execute if the callback doesn't intervene" in PC;
// this agrees with lib6502 for JMP (absolute and indirect) but not for JSR
// or BRK.
uint16_t handle_call_callback(M6502 *mpu, uint16_t callback_pc,
uint8_t opcode)
{
Registers &registers = mpu->internal->registers_;
uint16_t default_next_pc = registers.pc;
if (mpu->callbacks->call[registers.pc] != 0)
{
registers.pc = callback_pc;
registers.to_M6502_Registers(mpu);
TRACE("Call callback, mpu " << mpu << ", address 0x" << std::hex <<
std::setfill('0') << std::setw(4) << default_next_pc <<
", data 0x" << std::setw(2) << static_cast<int>(opcode));
uint16_t address = default_next_pc;
if (opcode == opcode_brk)
{
address = callback_pc - 2; // lib6502 does this
}
int callback_result =
mpu->callbacks->call[default_next_pc](mpu, address, opcode);
TRACE("Callback returned 0x" << std::hex << std::setfill('0') <<
std::setw(4) << callback_result);
registers.from_M6502_Registers(mpu);
mpu->internal->function_manager_.update_memory_snapshot();
if (callback_result != 0)
{
return callback_result;
}
}
return default_next_pc;
}
uint16_t get_stacked_pc(M6502 *mpu, int offset)
{
uint8_t s = mpu->internal->registers_.s;
for (; offset > 0; --offset)
{
++s;
}
++s;
uint8_t pushed_pc_low = mpu->memory[0x100 + s];
++s;
uint8_t pushed_pc_high = mpu->memory[0x100 + s];
return pushed_pc_low | (pushed_pc_high << 8);
}
uint16_t handle_push_and_control_transfer_opcode(
M6502 *mpu, uint16_t callback_pc, uint8_t opcode, int bytes_pushed)
{
assert(bytes_pushed >= 2);
uint8_t s = mpu->internal->registers_.s;
for (int i = 0; i < bytes_pushed; ++i)
{
++s;
mpu->internal->function_manager_.code_modified_at(0x100 + s);
}
return handle_call_callback(mpu, callback_pc, opcode);
}
}
Function::Function(
M6502 *mpu, uint16_t address, const AddressSet &code_range,
const AddressSet &optimistic_writes, llvm::Function *llvm_function)
: mpu_(mpu),
llvm_stuff_(mpu->internal->llvm_stuff_),
address_(address),
code_range_(code_range),
optimistic_writes_(optimistic_writes),
llvm_function_(llvm_function),
jitted_function_(reinterpret_cast<Function::JitFunction>(
llvm_stuff_.execution_engine_->getPointerToFunction(llvm_function)))
{
llvm_stuff_.execution_engine_->runJITOnFunction(llvm_function_, &mci_);
}
Function::~Function()
{
TRACE("Destructor for Function at address " << std::hex <<
std::setfill('0') << std::setw(4) << address_);
VALGRIND_DISCARD_TRANSLATIONS(mci_.address(), mci_.size());
llvm_function_->eraseFromParent();
}
void Function::handle_complex_result(FunctionBuilder::Result result) const
{
Registers &registers = mpu_->internal->registers_;
switch (result)
{
case FunctionBuilder::result_control_transfer_direct:
CANT_HAPPEN("Direct case reached handle_complex_result()");
case FunctionBuilder::result_control_transfer_indirect:
registers.pc = handle_call_callback(mpu_, registers.pc,
registers.data);
break;
case FunctionBuilder::result_brk:
registers.pc = handle_push_and_control_transfer_opcode(
mpu_, get_stacked_pc(mpu_, 1), opcode_brk, 3);
break;
case FunctionBuilder::result_jsr_complex:
registers.pc = handle_push_and_control_transfer_opcode(
mpu_, get_stacked_pc(mpu_, 0) + 1, opcode_jsr, 2);
break;
case FunctionBuilder::result_illegal_instruction:
{
registers.to_M6502_Registers(mpu_);
TRACE("Illegal instruction callback, mpu " << mpu_ <<
", address 0x" << std::hex << std::setfill('0') <<
std::setw(4) << registers.addr << ", data 0x" <<
std::setw(2) << static_cast<int>(registers.data));
uint16_t new_pc =
mpu_->callbacks->illegal_instruction[registers.data](
mpu_, registers.addr, registers.data);
TRACE("Callback returned 0x" << std::hex << std::setfill('0') <<
std::setw(4) << new_pc);
registers.from_M6502_Registers(mpu_);
mpu_->internal->function_manager_.update_memory_snapshot();
if (new_pc != 0)
{
registers.pc = new_pc;
}
break;
}
case FunctionBuilder::result_write_to_code:
TRACE("Code modified at 0x" << std::hex << std::setfill('0') <<
std::setw(4) << registers.addr);
mpu_->internal->function_manager_.code_modified_at(registers.addr);
break;
case FunctionBuilder::result_write_callback:
{
TRACE("Write callback at 0x" << std::hex << std::setfill('0') <<
std::setw(4) << registers.addr << " with data 0x" <<
std::setw(4) << static_cast<int>(registers.data));
// We *don't* invoke Registers.{to,from}_M6502Registers() before
// and after the callback. We could do this, but lib6502 itself
// (and therefore the lib6502 code used for interpreting in
// lib6502-jit) doesn't do that, so this could be confusing
// for client code. (For example, a callback might be written
// to rely on this, it would work if called from compiled code
// but wouldn't work if called from interpreted mode. So its
// behaviour in hybrid mode would be random.)
(void) mpu_->callbacks->write[registers.addr](
mpu_, registers.addr, registers.data);
mpu_->internal->function_manager_.update_memory_snapshot();
break;
}
case FunctionBuilder::result_invalid_bounds:
CANT_HAPPEN("Invalid bounds inside Function for address 0x" <<
std::hex << std::setfill('0') << std::setw(4) <<
address_);
default:
CANT_HAPPEN("Unknown result " << result << " from JIT function");
}
}
#ifdef LOG
namespace
{
std::string indent(int n, const std::string &s)
{
std::string prefix = spaces(n);
return apply_prefix(prefix, s);
}
}
std::string Function::dump_all() const
{
std::stringstream s;
s << "Function at 0x" << std::hex << std::setfill('0') << std::setw(4) <<
address_ << ":\n";
s << spaces(1) << "Code range:\n" << code_range_.dump(2) << "\n";
s << spaces(1) << "Optimistic writes at:\n" << optimistic_writes_.dump(2) <<
"\n";
s << spaces(1) << "6502 machine code:\n" << indent(2, disassembly_) << "\n";
s << spaces(1) << "Unoptimised IR:\n" << indent(2, unoptimised_ir_) << "\n";
s << spaces(1) << "Optimised IR:\n" << indent(2, optimised_ir_) << "\n";;
s << spaces(1) << "Host machine code:\n" << indent(2, dump_machine_code());
return s.str();
}
#endif
namespace
{
template <class Handle, class CloseFnType, CloseFnType close_fn>
class AutoClose : boost::noncopyable
{
public:
AutoClose(Handle h)
: open_(true), h_(h)
{
}
int close()
{
open_ = false;
return close_fn(h_);
}
~AutoClose()
{
if (open_)
{
close_fn(h_); // ignore return code, nothing we can do if it fails
}
}
private:
bool open_;
Handle h_;
};
typedef int (*FdClose)(int);
typedef AutoClose<int, FdClose, ::close> FdAutoClose;
typedef int (*PopenClose)(FILE *);
typedef AutoClose<FILE *, PopenClose, ::pclose> PopenAutoClose;
}
#ifdef LOG
std::string Function::dump_machine_code() const
{
try
{
// What a performance! The basic idea of outputting .bytes directives,
// assembling those and then disassembling the result is taken from
// libjit's dump_object_code(); the implementation is not copied.
char as_output_file[] = "/tmp/lib6502-jit-XXXXXX";
errno = 0;
// mkstemp() creates a unique filename and opens it. We unlink the file
// immediately so it has no name; this minimises (but does not
// eliminate; we might be killed between mkstemp() and unlink()) the
// chance of the file being left lying around. Since we need a name for
// the 'as' and 'objdump' commands, we use /dev/fd/nn to refer to it
// afterwards.
int fd = mkstemp(as_output_file);
if (fd == -1)
{
fail_errno_or("mkstemp() failed");
}
FdAutoClose auto_close_fd(fd);
if (unlink(as_output_file) == -1)
{
fail_errno_or("unlink() failed");
}
{
std::stringstream as_command;
as_command << "as -o /dev/fd/" << fd << " 2>/dev/null";
FILE *f = popen(as_command.str().c_str(), "w");
if (f == 0)
{
fail_errno_or("popen() failed (for 'as')");
}
PopenAutoClose auto_close_f(f);
unsigned char *p = static_cast<unsigned char *>(mci_.address());
unsigned char *end = p + mci_.size();
for (; p < end; ++p)
{
if (fprintf(f, ".byte %d\n", *p) < 0)
{
fail("Error writing to 'as' pipe");
}
}
if (auto_close_f.close() != 0)
{
fail_errno_or("Error closing 'as' pipe");
}
}
if (lseek(fd, 0, SEEK_SET) == static_cast<off_t>(-1))
{
fail_errno_or("Error seeking on temporary file");
}
std::stringstream objdump_command;
// As far as I can tell, there's no guarantee how mci_.address() [a
// pointer type] will be represented in the stringstream, but in
// practice this code is not very portable anyway and this is the least
// of our worries...
objdump_command << "objdump --adjust-vma=" <<
mci_.address() << " -d /dev/fd/" << fd << " 2>&1";
FILE *g = popen(objdump_command.str().c_str(), "r");
if (g == 0)
{
fail_errno_or("popen() failed (for 'objdump')");
}
PopenAutoClose auto_close_g(g);
std::stringstream code;
char buffer[1024];
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), g)) > 0)
{
code << std::string(buffer, bytes_read);
}
if (ferror(g))
{
fail("Error reading from 'objdump' pipe");
}
if (auto_close_g.close() != 0)
{
fail_errno_or("Error closing 'objdump' pipe");
}
if (auto_close_fd.close() != 0)
{
fail_errno_or("Error closing temporary file");
}
return code.str();
}
catch (std::exception &e)
{
// Dumping out the generated machine code is decidedly not critical, so
// we don't allow the exception to propagate.
return std::string("Unable to dump machine code: ") + e.what();
}
}
void Function::fail(const std::string &error) const
{
throw std::runtime_error(error);
}
void Function::fail_errno_or(const std::string &error) const
{
if (errno == 0)
{
fail(error);
}
else
{
// strerror_r() exists in various versions. If you have problems getting
// this to compile, it's probably OK to just use:
// const char *error = strerror(errno);
// given a) the limited amount of threading here and b) the fact this is
// only used to report rare errors in debug-only logging code. If push
// really comes to shove you can just do:
// const char *error = 0;
// and you'll just get unhelpful error messages.
char buffer[1024];
const char *error = strerror_r(errno, buffer, sizeof(buffer));
if (error != 0)
{
fail(error);
}
else
{
fail("Error occurred, and strerror() probably failed as well");
}
}
}
#endif

112
Function.h Normal file
View File

@ -0,0 +1,112 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#ifndef FUNCTION_H
#define FUNCTION_H
#include <boost/shared_ptr.hpp>
#include <boost/utility.hpp>
#include "llvm/CodeGen/MachineCodeInfo.h"
#include "llvm/IR/Value.h"
#include "AddressSet.h"
#include "FunctionBuilder.h"
#include "lib6502.h"
struct LLVMStuff;
class Function : boost::noncopyable
{
public:
Function(M6502 *mpu, uint16_t address, const AddressSet &code_range,
const AddressSet &optimistic_writes,
llvm::Function *llvm_function);
~Function();
uint16_t address() const
{
return address_;
}
const AddressSet &code_range() const
{
return code_range_;
}
const AddressSet &optimistic_writes() const
{
return optimistic_writes_;
}
void execute() const
{
FunctionBuilder::Result result =
static_cast<FunctionBuilder::Result>((*jitted_function_)());
if (result != FunctionBuilder::result_control_transfer_direct)
{
handle_complex_result(result);
}
}
#ifdef LOG
void set_disassembly(const std::string &s)
{
disassembly_ = s;
}
void set_unoptimised_ir(const std::string &s)
{
unoptimised_ir_ = s;
}
void set_optimised_ir(const std::string &s)
{
optimised_ir_ = s;
}
std::string dump_all() const;
std::string dump_machine_code() const;
#endif
private:
void handle_complex_result(FunctionBuilder::Result result) const;
#ifdef LOG
void fail(const std::string &error) const;
void fail_errno_or(const std::string &error) const;
#endif
M6502 *mpu_;
LLVMStuff &llvm_stuff_;
uint16_t address_;
AddressSet code_range_;
AddressSet optimistic_writes_;
llvm::Function *llvm_function_;
llvm::MachineCodeInfo mci_;
typedef int (*JitFunction)();
JitFunction jitted_function_;
#ifdef LOG
std::string disassembly_;
std::string unoptimised_ir_;
std::string optimised_ir_;
#endif
};
#endif

3571
FunctionBuilder.cpp Normal file

File diff suppressed because it is too large Load Diff

364
FunctionBuilder.h Normal file
View File

@ -0,0 +1,364 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#ifndef FUNCTIONBUILDER_H
#define FUNCTIONBUILDER_H
#include <boost/shared_ptr.hpp>
#include <boost/utility.hpp>
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/TypeBuilder.h"
#include "llvm/IR/Value.h"
#include <map>
#include <set>
#include <sstream>
#include "AddressSet.h"
#include "const.h"
#include "JitBool.h"
#include "lib6502.h"
class Function;
struct LLVMStuff;
class FunctionBuilder : boost::noncopyable
{
public:
// Create a FunctionBuilder object which can be used to build a Function
// representing the code starting at 'address'. The Function object built
// will operate on the given M6502 object. The 'code_at_address' array
// will be used at compile time and at runtime to decide if writes to
// memory may invalidate already JITted code. The memory inside the M6502
// object will be used when the Funtion object executes, but ct_memory
// will be used at compile time to determine the instructions to compile;
// see FunctionManager for more on this.
FunctionBuilder(M6502 *mpu, const uint8_t *ct_memory,
JitBool *code_at_address, uint16_t address);
boost::shared_ptr<Function> build();
// Status codes returned by the JITted function
enum Result
{
// Control has transferred to the address in registers.pc. No call
// callback should be invoked, either because the JITted function knows
// there is no applicable call callback or because the control transfer
// is via an instruction which does not trigger call callbacks.
result_control_transfer_direct,
// Control has transferred to the address in registers.pc via an
// instruction which is eligible for call callbacks. registers.data
// contains the opcode of the instruction which transferred
// control. The caller should check for an applicable call
// callback. registers.addr is *not* updated; the addr value for
// the callback is registers.pc.
result_control_transfer_indirect,
// A BRK instruction has just been executed and registers.pc updated
// to point to the BRK vector. The caller should check to see if the
// stack pushes implicitly performed by BRK have invalidated any
// already-JITted code and for a call callback on the BRK vector.
// Neither registers.addr nor registers.data are updated.
result_brk,
// A JSR instruction has just been executed and registers.pc
// updated to point to the destination address. One or both of the
// following may be true: - the stack pushes implicitly performed
// have invalidated some
// already-JITted code
// - a call callback is registered on the destination address It is not
// guaranteed that either of these is the case, although in practice
// with this implementation at least one should be true. Not all JSR
// instructions will necessarily cause the JITted function to return
// this value, hence the result code is result_jsr_*complex* not just
// result_jsr. Neither registers.addr nor registers.data are updated.
result_jsr_complex,
// An illegal instruction has been executed and registers.pc updated to
// point to the following opcode. registers.addr contains the address
// of the illegal instruction and registers.data its opcode. The
// caller should check to see if a callback is registered.
result_illegal_instruction,
// A memory write has been executed which changed an address marked
// as holding code. registers.addr contains the address modified. The
// caller should invalidate any JITted functions for this address.
result_write_to_code,
// A memory write has occurred which triggers a write callback. Memory
// has not been updated. registers.addr and registers.data contain the
// address and the data being written respectively. The caller should
// invoke the write callback and check for writes to already-JITted
// code.
result_write_callback,
// Internal bounds generated for an instruction's address range were
// found to be invalid by self-checking code. This can only occur
// in debug builds and then only if there is a bug in FunctionBuilder.
result_invalid_bounds
};
private:
uint16_t build_at(uint16_t ct_pc);
uint8_t operand8(uint16_t opcode_at);
uint16_t operand16(uint16_t opcode_at);
llvm::Value *constant_i1(bool c);
llvm::Value *constant_u8(uint8_t c);
llvm::Value *constant_u16(uint16_t c);
llvm::Value *constant_u32(uint32_t c);
llvm::Value *constant_u64(uint64_t c);
template <class T>
llvm::Value *constant_ptr(T *p, const std::string &name)
{
llvm::Value *v = constant_u64(reinterpret_cast<unsigned long>(p));
// The name passed in never seems to be used, but maybe this will
// change in the future. It doesn't really do us any harm to pass
// it in anyway.
return builder_.CreateIntToPtr(
v, llvm::TypeBuilder<T *, false>::get(llvm::getGlobalContext()),
name);
}
llvm::Value *constant_i(int c);
llvm::Value *constant_jb(JitBool c);
llvm::Value *convert_i1_to_jb(llvm::Value *v);
llvm::Value *convert_i8_to_jb(llvm::Value *v);
llvm::Value *convert_i16_to_jb(llvm::Value *v);
llvm::Value *jit_bool_is_true(llvm::Value *v);
llvm::Value *jit_bool_is_false(llvm::Value *v);
llvm::Value *convert_i1_to_i8(llvm::Value *v);
llvm::Value *zext_i16(llvm::Value *v);
llvm::Value *zext_i32(llvm::Value *v);
llvm::Value *sext_i16(llvm::Value *v);
llvm::Value *trunc_i8(llvm::Value *v);
llvm::Value *create_u16(llvm::Value *low_byte, llvm::Value *high_byte);
struct Register
{
llvm::Value *v_;
bool modified_;
};
void initialise_i8_reg(Register &r, int structure_index,
const std::string &name);
void initialise_jb_reg(Register &r, int structure_index,
const std::string &name);
void ensure_address_block_created(uint16_t addr);
void return_pc(Result result, llvm::Value *new_pc);
void return_pc_addr(Result result, llvm::Value *new_pc, llvm::Value *addr);
void return_pc_data(Result result, llvm::Value *new_pc, llvm::Value *data);
void return_pc_addr_data(Result result, llvm::Value *new_pc,
llvm::Value *addr, llvm::Value *data);
void return_control_transfer_direct(llvm::Value *new_pc);
void return_control_transfer_indirect(llvm::Value *new_pc, uint8_t opcode);
void return_brk(llvm::Value *new_pc);
void return_jsr_complex(llvm::Value *new_pc);
void return_illegal_instruction(uint16_t new_pc, uint16_t opcode_at,
uint8_t opcode);
void return_write_to_code(uint16_t new_pc, llvm::Value *addr);
void return_write_callback(uint16_t new_pc, llvm::Value *addr,
llvm::Value *data);
void return_invalid_bounds();
class BoundedAddress;
llvm::Value *register_load(const Register &r);
void register_store(llvm::Value *v, Register &r);
typedef llvm::Value *(FunctionBuilder::*OpFn)(llvm::Value *data);
void register_op(OpFn op, Register &r);
void memory_op(OpFn op, const BoundedAddress &ba, uint16_t next_opcode_at);
llvm::Value *is_code_at(const BoundedAddress &addr);
void adc(llvm::Value *data);
void adc_llvm(llvm::Value *data);
void adc_binary(llvm::Value *data);
void adc_decimal(llvm::Value *data);
void adc_binary_llvm(llvm::Value *data);
void adc_decimal_llvm(llvm::Value *data);
void And(llvm::Value *data);
llvm::Value *asl(llvm::Value *data);
void bit(llvm::Value *data);
void branch(Register &flag, bool branch_if, uint16_t target);
void cmp(llvm::Value *r, llvm::Value *data);
void cmp_llvm(llvm::Value *r, llvm::Value *data);
llvm::Value *dec(llvm::Value *data);
void eor(llvm::Value *data);
llvm::Value *inc(llvm::Value *data);
void ld(Register &r, llvm::Value *data);
llvm::Value *lsr(llvm::Value *data);
void ora(llvm::Value *data);
void pop_flags();
llvm::Value *pop_u8();
llvm::Value *pop_u16();
void push_u8_raw(llvm::Value *data);
void push_u16_raw(uint16_t u);
void push_u8(llvm::Value *data, uint16_t next_opcode_at);
llvm::Value *rol(llvm::Value *data);
llvm::Value *ror(llvm::Value *data);
void sbc(llvm::Value *data);
void sbc_binary(llvm::Value *data);
void sbc_decimal(llvm::Value *data);
void sbc_overflow(llvm::Value *data,
llvm::Value *borrow);
void transfer(const Register &from, Register &to);
llvm::Value *trb(llvm::Value *data);
llvm::Value *tsb(llvm::Value *data);
void set_nz(llvm::Value *data);
void set_z(llvm::Value *data);
llvm::Value *flag_byte();
void flag_byte_bit(const Register &flag_reg, uint8_t flag_bit);
void illegal_instruction(uint16_t &ct_pc, int bytes);
BoundedAddress zp(uint8_t addr);
BoundedAddress abs(uint16_t addr);
BoundedAddress abs_index(llvm::Value *abs,
llvm::Value *index);
BoundedAddress zp_index(llvm::Value *zp,
llvm::Value *r);
BoundedAddress zp_post_index(
llvm::Value *zp, llvm::Value *index);
BoundedAddress zp_pre_index(
llvm::Value *zp, llvm::Value *index);
llvm::Value *check_predicted_rts(uint16_t subroutine_addr);
// A special opcode used as the third argument to control_transfer_to
// when there is no explicit opcode causing the control transfer; this
// is just a documented way to signal that the control transfer is direct
// and cannot trigger a call callback.
enum {
opcode_implicit = 0xff
};
void control_transfer_to(llvm::Value *target, uint8_t opcode);
llvm::Value *memory_read(const BoundedAddress &ba);
llvm::Value *memory_read_untrapped(const BoundedAddress &ba);
void memory_write(const BoundedAddress &ba,
llvm::Value *data, uint16_t next_opcode_at);
void memory_write_untrapped(const BoundedAddress &ba,
llvm::Value *data, uint16_t next_opcode_at);
void memory_write_raw(const BoundedAddress &ba,
llvm::Value *data);
llvm::Value *call_callback(
llvm::Value *callback, llvm::Value *addr,
llvm::Value *data);
llvm::Value *call_read_callback(
llvm::Value *callback, llvm::Value *addr);
void disassemble1(uint16_t &addr, const std::string &s);
void disassemble2(uint16_t &addr, const std::string &prefix,
uint8_t &operand, const std::string &suffix = "");
void disassemble3(uint16_t &addr, const std::string &prefix,
uint16_t &operand, const std::string &suffix = "");
void disassemble_branch(uint16_t &addr, const std::string &s,
uint16_t &target);
void disassemble_hex_dump(uint16_t addr, int bytes);
bool built_;
M6502 *const mpu_;
JitBool *code_at_address_;
const uint16_t address_;
const uint8_t *const ct_memory_;
// callbacks_ is strictly redundant as it's available inside mpu, but
// it's convenient.
const M6502_Callbacks &callbacks_;
AddressSet code_range_;
AddressSet optimistic_writes_;
std::stringstream disassembly_;
int instructions_;
const int max_instructions_;
// This could be an AddressSet but since we "rely" on the order of
// iteration for pending_ it seems better to be explicit; we don't need
// any of the range-handling convenience of AddressSet here anyway.
std::set<uint16_t> pending_;
std::map<uint16_t, AddressSet> predicted_rts_targets_;
llvm::LLVMContext &context_;
llvm::Type *const native_int_type_;
llvm::PointerType *const callback_type_;
llvm::Type *const i1_type_;
llvm::Type *const i8_type_;
llvm::Type *const i16_type_;
llvm::Type *const i32_type_;
llvm::Type *const i64_type_;
llvm::Type *const jit_bool_type_;
llvm::IRBuilder<> &builder_;
llvm::Function *llvm_function_;
llvm::Value *registers_;
llvm::Value *code_at_address_llvm_;
llvm::Value *read_callbacks_;
llvm::Value *write_callbacks_;
llvm::Value *call_callbacks_;
llvm::Value *memory_base_;
llvm::Value *mpu_llvm_;
llvm::Value *function_result_;
// Note that address_block_ and code_generated_for_address_ aren't
// redundant; address_block_ elements are created (for example) when
// a branch means the corresponding address must have a BasicBlock
// created for use as a branch target, but that doesn't mean code has
// been generated for it yet.
llvm::BasicBlock *address_block_[memory_size];
bool code_generated_for_address_[memory_size];
Register a_;
Register x_;
Register y_;
Register s_;
Register flag_n_;
Register flag_v_;
Register flag_d_;
Register flag_i_;
Register flag_z_;
Register flag_c_;
llvm::Value *pc_;
llvm::Value *read_callback_result_;
llvm::Value *p_tmp_;
llvm::Value *l_tmp_;
llvm::Value *s_tmp_;
llvm::Value *t_tmp_;
llvm::BasicBlock *epilogue_;
};
#endif

310
FunctionManager.cpp Normal file
View File

@ -0,0 +1,310 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#include "FunctionManager.h"
#include <functional>
#include "Function.h"
#include "FunctionBuilder.h"
#include "M6502Internal.h"
#include "Registers.h"
#include "util.h"
FunctionManager::FunctionManager(M6502 *mpu)
: jit_thread_idle_(true), work_available_(false), quit_(false), mpu_(mpu),
memory_snapshot_(), function_for_address_(), code_at_address_()
{
}
FunctionManager::~FunctionManager()
{
if (jit_thread_.get_id() != boost::thread::id())
{
TRACE("Notifying JIT thread to quit");
{
boost::mutex::scoped_lock lock(jit_thread_cv_mutex_);
quit_ = true;
}
jit_thread_cv_.notify_all();
TRACE("Joining with JIT thread");
jit_thread_.join();
}
}
bool FunctionManager::jit_thread_idle()
{
boost::mutex::scoped_lock lock(jit_thread_idle_mutex_);
return jit_thread_idle_;
}
void FunctionManager::update_memory_snapshot()
{
assert(jit_thread_idle());
const uint8_t *memory = mpu_->memory;
for (size_t i = 0; i < memory_size; ++i)
{
if (code_at_address_[i] && (memory_snapshot_[i] != memory[i]))
{
code_modified_at(i);
}
memory_snapshot_[i] = memory[i];
}
}
Function *FunctionManager::build_function_internal(
uint16_t address, const uint8_t *ct_memory)
{
Registers &registers = mpu_->internal->registers_;
TRACE("Building Function for code at 0x" << std::hex << std::setfill('0') <<
std::setw(4) << registers.pc);
FunctionBuilder fb(mpu_, ct_memory, code_at_address_, registers.pc);
boost::shared_ptr<Function> f(fb.build());
add_function(f);
return f.get();
}
Function *FunctionManager::build_function(uint16_t address,
const uint8_t *ct_memory)
{
Function *f;
int pass = 0;
do
{
assert(pass < 2);
++pass;
f = build_function_internal(address, ct_memory);
bool f_is_optimistic_self_writer = false;
const AddressSet &code_range = f->code_range();
for (AddressSet::const_iterator it = code_range.begin();
it != code_range.end(); ++it)
{
uint16_t i = *it;
if (code_at_address_[i] &&
!optimistic_writers_for_address_[i].empty())
{
// There is now code at an address where optimistic writes are
// performed. Future code generation won't create optimistic
// writes there because code_at_address_[i] has now been set,
// but we need to destroy existing functions which perform
// that write so they will be regenerated.
const FunctionSet &optimistic_writers =
optimistic_writers_for_address_[i];
f_is_optimistic_self_writer =
(optimistic_writers.find(f) != optimistic_writers.end());
destroy_functions_in_set(optimistic_writers_for_address_[i]);
if (f_is_optimistic_self_writer)
{
// destroy_functions_in_set() has now destroyed f, so a)
// code_range is no longer a valid reference b) there's
// no need to continue iterating over f's code range.
break;
}
}
}
// We might just have destroyed the function we built, if it modified
// its own code, so we need to loop round if so.
f = function_for_address_[address];
if (f == 0)
{
assert(f_is_optimistic_self_writer);
TRACE("Rebuilding just-created function");
}
}
while (f == 0);
TRACE(f->dump_all());
return f;
}
void FunctionManager::build_function_lazy(uint16_t address)
{
assert(jit_thread_idle());
TRACE("Will build Function for address 0x" << std::hex <<
std::setfill('0') << std::setw(4) << address << " in background");
// We only create the JIT thread the first time it's needed; this avoids it
// existing if the library is being used in interpreted or compiled mode.
if (jit_thread_.get_id() == boost::thread::id())
{
TRACE("Creating JIT thread");
boost::thread t(
std::mem_fun(&FunctionManager::build_function_thread), this);
jit_thread_.swap(t);
}
{
boost::mutex::scoped_lock lock(jit_thread_idle_mutex_);
jit_thread_idle_ = false;
}
{
boost::mutex::scoped_lock lock(jit_thread_cv_mutex_);
work_available_ = true;
jit_thread_address_ = address;
}
jit_thread_cv_.notify_all();
}
void FunctionManager::build_function_thread()
{
try
{
TRACE("JIT thread started");
boost::mutex::scoped_lock jit_thread_cv_mutex_lock(
jit_thread_cv_mutex_);
while (true)
{
while (!quit_ && !work_available_)
{
TRACE("JIT thread waiting to be signalled");
jit_thread_cv_.wait(jit_thread_cv_mutex_lock);
}
if (quit_)
{
TRACE("JIT thread quitting");
return;
}
else
{
TRACE("JIT thread about to build Function at address 0x" <<
std::hex << std::setfill('0') << std::setw(4) <<
jit_thread_address_);
assert(work_available_);
assert(!jit_thread_idle_);
// Note that we translate code from memory_snapshot_
// not mpu_->memory. This is important, even though we
// have update_memory_snapshot() which "should" invalidate
// Function objects which depend on modified code before any
// of them are used. The reason is that if a memory location
// is temporarily modified by the interpreter before it can
// be translated, then modified back to its original value
// by the interpreter before update_memory_snapshot() is
// called, update_memory_snapshot() can't notice the change,
// but the change has been compiled into the Function object.
// (See test/z-self-modify-2.xa; this breaks in hybrid mode
// if memory_snapshot_ isn't used here.)
build_function(jit_thread_address_, memory_snapshot_);
work_available_ = false;
boost::mutex::scoped_lock jit_thread_idle_lock(
jit_thread_idle_mutex_);
jit_thread_idle_ = true;
}
}
}
catch (std::exception &e)
{
die(e.what());
}
}
void FunctionManager::add_function(const boost::shared_ptr<Function> &f)
{
function_for_address_[f->address()] = f.get();
function_for_address_owner_[f->address()] = f;
const AddressSet &code_range = f->code_range();
for (AddressSet::const_iterator it = code_range.begin();
it != code_range.end(); ++it)
{
uint16_t i = *it;
functions_covering_address_[i].insert(f.get());
code_at_address_[i] = true;
}
const AddressSet &optimistic_writes = f->optimistic_writes();
for (AddressSet::const_iterator it = optimistic_writes.begin();
it != optimistic_writes.end(); ++it)
{
uint16_t i = *it;
optimistic_writers_for_address_[i].insert(f.get());
}
}
void FunctionManager::code_modified_at(uint16_t address)
{
// We could just return immediately if code_at_address_[address] is false;
// sometimes we call this function without bothering to check first.
// In practice I doubt this has a significant impact on performance.
TRACE("Code modified at 0x" << std::hex << std::setfill('0') <<
std::setw(4) << address);
destroy_functions_in_set(functions_covering_address_[address]);
// Keep memory_snapshot_ up-to-date; this avoids harmless-but-inefficient
// destruction of perfectly valid Function objects when
// update_memory_snapshot() is called next.
memory_snapshot_[address] = mpu_->memory[address];
}
void FunctionManager::destroy_functions_in_set(FunctionSet &function_set)
{
// We iterate over the set like this because destroy_function() will erase
// the function from function_set, thereby invalidating any iterator we are
// holding on to.
while (!function_set.empty())
{
destroy_function(*function_set.begin());
}
}
void FunctionManager::destroy_function(Function *f)
{
const AddressSet &code_range = f->code_range();
for (AddressSet::const_iterator it = code_range.begin();
it != code_range.end(); ++it)
{
uint16_t i = *it;
size_t erased_count = functions_covering_address_[i].erase(f);
ASSERT_EQUAL(erased_count, 1);
// We do *not* clear code_at_address_[i] even if
// functions_covering_address_[i] is now empty; this records the fact
// that we have executed code at this address. This is critical for
// the current implementation of build_function(); code_at_address_
// being set is used to control optimistic vs non-optimistic writes,
// and if code_at_address_ was cleared when a function was destroyed
// a self-modifying function would cause an infinite loop inside
// build_function(). It would be OK to clear code_at_address_ for any
// addresses with empty functions_covering_address_ sets at the end
// of build_function(), but we currently don't.
}
const AddressSet &optimistic_writes = f->optimistic_writes();
for (AddressSet::const_iterator it = optimistic_writes.begin();
it != optimistic_writes.end(); ++it)
{
uint16_t i = *it;
size_t erased_count = optimistic_writers_for_address_[i].erase(f);
ASSERT_EQUAL(erased_count, 1);
}
assert(function_for_address_[f->address()] == f);
function_for_address_[f->address()] = 0;
// Do this last as it will cause the Function object to be deleted.
assert(function_for_address_owner_[f->address()].get() == f);
function_for_address_owner_[f->address()].reset();
}

151
FunctionManager.h Normal file
View File

@ -0,0 +1,151 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#ifndef FUNCTIONMANAGER_H
#define FUNCTIONMANAGER_H
#include <assert.h>
#include <boost/shared_ptr.hpp>
#include <boost/thread/condition_variable.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/thread.hpp>
#include <boost/utility.hpp>
#include <set>
#include <stdint.h>
#include "const.h"
#include "JitBool.h"
#include "lib6502.h"
class Function;
class FunctionManager : boost::noncopyable
{
public:
FunctionManager(M6502 *mpu);
~FunctionManager();
bool jit_thread_idle();
void update_memory_snapshot();
// Return a Function object representing the code starting at 'address'; if
// one does not already exist it will be created. This never returns null.
Function *get_function(uint16_t address)
{
Function *f = function_for_address_[address];
if (f != 0)
{
return f;
}
else
{
return build_function(address, mpu_->memory);
}
}
// Return a Function object representing the code starting at 'address',
// if one is available, otherwise return null. When null is returned
// a background thread may be used to generate a Function object which
// can be returned if the request is repeated in the future.
//
// This function may only be called if the last call to jit_thread_idle()
// returned true and no call has been made to get_function_lazy() since
// jit_thread_idle() was called.
//
// Currently a background thread will *always* be invoked if null is
// returned, but this is not guaranteed. For example, we may wish to
// refuse to waste time building a Function object which we expect to
// be invalidated by self-modifying code shortly afterwards.
Function *get_function_lazy(uint16_t address)
{
// This assert() is perfectly correct, but it single-handedly destroys
// the performance of a debug build; it's just not *that* valuable.
// assert(jit_thread_idle());
Function *f = function_for_address_[address];
if (f != 0)
{
return f;
}
else
{
build_function_lazy(address);
return 0;
}
}
void code_modified_at(uint16_t address);
private:
void add_function(const boost::shared_ptr<Function> &f);
Function *build_function(uint16_t address, const uint8_t *ct_memory);
Function *build_function_internal(uint16_t address,
const uint8_t *ct_memory);
void build_function_lazy(uint16_t address);
void build_function_thread();
typedef std::set<Function *> FunctionSet;
void destroy_functions_in_set(FunctionSet &function_set);
void destroy_function(Function *f);
boost::thread jit_thread_;
boost::mutex jit_thread_idle_mutex_;
bool jit_thread_idle_;
boost::mutex jit_thread_cv_mutex_;
boost::condition_variable jit_thread_cv_;
bool work_available_;
uint16_t jit_thread_address_;
bool quit_;
M6502 *mpu_;
// A copy of the emulated CPU's memory, used to detect changes to already
// JITted code which happen in callbacks and to avoid problems with JITting
// while the interpreter is running (in hybrid mode).
uint8_t memory_snapshot_[memory_size];
// We maintain this array of shared_ptr's which actually own the
// Function objects.
boost::shared_ptr<Function> function_for_address_owner_[memory_size];
// We maintain a parallel array of raw pointers here so that we have
// the option to allow JITted code to access it.
Function *function_for_address_[memory_size];
// This tracks the Function objects which contain code generated based on
// individual addresses, i.e. the Function objects which are invalidated by
// a store to a given memory location.
FunctionSet functions_covering_address_[memory_size];
// This tracks the Function objects which perform optimistic writes to
// individual addresses, i.e. the Function objects which are invalidated if
// it turns out an address is in fact used to hold code.
FunctionSet optimistic_writers_for_address_[memory_size];
// This tracks whether we have ever executed code at a given address;
// destroying all the functions in the corresponding element of
// functions_covering_address does *not* mean this is cleared.
JitBool code_at_address_[memory_size];
};
#endif

32
JitBool.h Normal file
View File

@ -0,0 +1,32 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
// JitBool is a typedef representing the type used for boolean flags in the
// JITted code, i.e. the CPU flag values and the 'code modified at' flag for
// each memory address. In reality this is not likely to change, but this at
// least helps to identify code which needs to change to support a different
// representation. FunctionBuilder.cpp also contains a number of helper
// functions which depend on the underlying type of JitBool.
#ifndef JITBOOL_H
#define JITBOOL_H
typedef uint8_t JitBool;
const JitBool jit_bool_false = 0;
const JitBool jit_bool_true = 1;
#endif

41
LLVMStuff.cpp Normal file
View File

@ -0,0 +1,41 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#include "LLVMStuff.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/Support/TargetSelect.h"
LLVMStuff::LLVMStuff()
: module_(new llvm::Module("lib6502-jit", llvm::getGlobalContext())),
builder_(llvm::getGlobalContext())
{
llvm::InitializeNativeTarget();
std::string error;
execution_engine_ =
llvm::EngineBuilder(module_.get()).setErrorStr(&error).create();
if (execution_engine_ == 0)
{
throw std::runtime_error("Could not create LLVM ExecutionEngine: " +
error);
}
}
LLVMStuff::~LLVMStuff()
{
}

39
LLVMStuff.h Normal file
View File

@ -0,0 +1,39 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#ifndef LLVMSTUFF_H
#define LLVMSTUFF_H
#include <boost/shared_ptr.hpp>
#include <boost/utility.hpp>
#include "llvm/ExecutionEngine/ExecutionEngine.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Module.h"
#include <stdexcept>
struct LLVMStuff : boost::noncopyable
{
LLVMStuff();
~LLVMStuff();
llvm::ExecutionEngine *execution_engine_;
boost::shared_ptr<llvm::Module> module_;
llvm::IRBuilder<> builder_;
};
#endif

43
M6502Internal.h Normal file
View File

@ -0,0 +1,43 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#ifndef M6502INTERNAL_H
#define M6502INTERNAL_H
#include "FunctionManager.h"
#include "lib6502.h"
#include "LLVMStuff.h"
#include "Registers.h"
struct _M6502_Internal
{
_M6502_Internal(M6502 *mpu)
: function_manager_(mpu), mode_(M6502_ModeHybrid),
max_instructions_(default_max_instructions_)
{
}
Registers registers_;
LLVMStuff llvm_stuff_;
FunctionManager function_manager_;
M6502_Mode mode_;
static const int default_max_instructions_ = 500;
int max_instructions_;
};
#endif

130
Makefile.am Normal file
View File

@ -0,0 +1,130 @@
ACLOCAL_AMFLAGS = -I m4
AM_CPPFLAGS = `$(LLVMCONFIG) --cppflags` $(BOOST_CPPFLAGS)
# lib6502.c generates spurious warnings with -Wall, so we want -Wno-parentheses
# too. It's not easy to have per-source-file build flags in automake, so we
# just apply this to all C files.
AM_CFLAGS = -Wall -Wno-parentheses
AM_CXXFLAGS = `$(LLVMCONFIG) --cxxflags` -fexceptions -Wall
AM_LDFLAGS = $(BOOST_THREAD_LDFLAGS)
LIBS = `$(LLVMCONFIG) --ldflags --libs core jit native --system-libs` $(BOOST_THREAD_LIBS)
# Some of these are included automatically, but I'd rather be explicit.
EXTRA_DIST = \
examples/README \
COPYING \
CREDITS \
lib6502-compatibility.txt \
README \
README.lib6502 \
TODO \
man/* \
test/*.xa \
test/*.mst \
test/run-c-tests.sh \
test/run-run6502-tests.sh \
test/run-c-tests.py \
test/run-run6502-tests.py
man1_MANS = man/*.1
man3_MANS = man/*.3
lib_LTLIBRARIES = lib6502-jit.la
include_HEADERS = lib6502.h
bin_PROGRAMS = run6502
noinst_PROGRAMS = \
examples/lib1
check_PROGRAMS = \
test/basic-callback \
test/call-illegal-callback-modify-code \
test/irq-nmi \
test/setjmp-trick \
test/stack-code-brk \
test/stack-code-jsr \
test/write-callback-modify-code
lib6502_jit_la_SOURCES = \
AddressRange.cpp \
AddressRange.h \
AddressSet.cpp \
AddressSet.h \
const.h \
Function.cpp \
Function.h \
FunctionBuilder.cpp \
FunctionBuilder.h \
FunctionManager.cpp \
FunctionManager.h \
JitBool.h \
lib6502.c \
lib6502.h \
lib6502-jit.cpp \
LLVMStuff.cpp \
LLVMStuff.h \
M6502Internal.h \
Registers.cpp \
Registers.h \
util.cpp \
util.h \
valgrind.h
run6502_SOURCES = \
run6502.c
run6502_LINK = $(CXXLINK)
run6502_LDADD = lib6502-jit.la
examples_lib1_SOURCES = \
examples/lib1.c
examples_lib1_LINK = $(CXXLINK)
examples_lib1_LDADD = lib6502-jit.la
test_basic_callback_SOURCES = \
test/basic-callback.c \
test/test-utils.c \
test/test-utils.h
test_basic_callback_LINK = $(CXXLINK)
test_basic_callback_LDADD = lib6502-jit.la
test_call_illegal_callback_modify_code_SOURCES = \
test/call-illegal-callback-modify-code.c \
test/test-utils.c \
test/test-utils.h
test_call_illegal_callback_modify_code_LINK = $(CXXLINK)
test_call_illegal_callback_modify_code_LDADD = lib6502-jit.la
test_irq_nmi_SOURCES = \
test/irq-nmi.c \
test/test-utils.c \
test/test-utils.h
test_irq_nmi_LINK = $(CXXLINK)
test_irq_nmi_LDADD = lib6502-jit.la
test_setjmp_trick_SOURCES = \
test/setjmp-trick.c \
test/test-utils.c \
test/test-utils.h
test_setjmp_trick_LINK = $(CXXLINK)
test_setjmp_trick_LDADD = lib6502-jit.la
test_stack_code_brk_SOURCES = \
test/stack-code-brk.c \
test/test-utils.c \
test/test-utils.h
test_stack_code_brk_LINK = $(CXXLINK)
test_stack_code_brk_LDADD = lib6502-jit.la
test_stack_code_jsr_SOURCES = \
test/stack-code-jsr.c \
test/test-utils.c \
test/test-utils.h
test_stack_code_jsr_LINK = $(CXXLINK)
test_stack_code_jsr_LDADD = lib6502-jit.la
test_write_callback_modify_code_SOURCES = \
test/write-callback-modify-code.c \
test/test-utils.c \
test/test-utils.h
test_write_callback_modify_code_LINK = $(CXXLINK)
test_write_callback_modify_code_LDADD = lib6502-jit.la
TESTS = \
test/run-c-tests.sh \
test/run-run6502-tests.sh

84
README Normal file
View File

@ -0,0 +1,84 @@
lib6502-jit is a (mostly) compatible implementation of Ian Piumarta's lib6502
which uses LLVM to perform JIT compilation of 6502 machine code to host code.
This will doubtless be useful to the large community of people stuck doing
number-crunching tasks with legacy 6502 code. :-)
README.lib6502 is a copy of the original lib6502 README. You should probably go
and read that before reading any further.
lib6502-compatibility.txt documents the differences between lib6502 and
lib6502-jit.
CREDITS contains acknowledgements of the various people and groups on whose
work lib6502-jit is built.
COPYING contains license details for lib6502-jit.
TODO contains some notes on possible enhancements to lib6502-jit.
How to build:
You'll need the following installed:
- a C/C++ compiler (I've tested with gcc 4.7.2, gcc 4.8.2 and clang 3.5)
- LLVM development libraries (I've tested with various 3.5 pre-release snapshots)
- boost (including boost::thread) (I've tested with 1.49, 1.54 and 1.55)
I have somewhat reluctantly set up an autotools build system; compiling and
linking against LLVM and boost::thread on different platforms was otherwise
just that bit too fiddly. So in theory all you need to do is:
./configure
make
I suggest you actually do:
CFLAGS='-g -O3' CXXFLAGS='-g -O3' ./configure
to increase the optimisation level. (I would have made that the default, but
apparently that would go against user expectations for an autotools build
system.)
"make install" should work as well if you feel inclined to do so, but it's not
necessary.
I've tested on three platforms, and for what it's worth here are more detailed
instructions for those:
Ubuntu (14.04 x86):
apt-get install libboost-dev libboost-thread-dev llvm-3.5-dev libedit-dev
export CFLAGS='-g -O3'
export CXXFLAGS='-g -O3'
./configure --with-llvm-config=llvm-config-3.5
make
Debian (7.5 x86-64):
apt-get install libboost-dev libboost-thread-dev
[I used the llvm-3.5-dev package from the wheezy repository here: http://llvm.org/apt/]
export CFLAGS='-g -O3'
export CXXFLAGS='-g -O3'
./configure
make
FreeBSD (10.0-RELEASE x86-64):
pkg install boost-all-1.55.0
pkg install llvm-devel-3.5.r203994
export CFLAGS='-g -O3'
export CXXFLAGS='-g -O3'
./configure --with-llvm-config=/usr/local/llvm-devel/bin/llvm-config
make
There are some tests which will run if you type "make check". Some will be
skipped unless you have the "xa" assembler
(http://www.floodgap.com/retrotech/xa/) on your PATH.
The above assumes you downloaded a lib6502-jit*tar.bz2 package, which will
contain a "configure" script. This is not (following what I understand to be
best practice) checked into source control, so if you downloaded the source
using something like git or svn, you need to either:
- download the tarball - it will be much easier, especially if you're just
taking a quick look at lib6502-jit and don't plan to make changes to the code
(yet)
- install autoconf, automake and libtool, then cross your fingers and run
"autoreconf -i", which will generate a "configure" script for you if you're
lucky.
If you have any queries, comments or bug reports, please drop me (Steven
Flintham) an e-mail at lib6502-jit@lemma.co.uk.

136
README.lib6502 Normal file
View File

@ -0,0 +1,136 @@
lib6502 - 6502 Microprocessor Emulator
Version: 1.0
WHAT IF I'M TOO LAZY TO READ 'README'S?
make
make install
more examples/README
WHAT IS LIB6502?
lib6502 is a library that emulates the 6502 microprocessor. It
comes with a small 'shell', run6502, that can execute 6502 programs
from the command line.
lib6502 is distributed under the MIT license: it is non-infectious
and will not make your projects contagious to others the instant you
choose to use lib6502 in them. See the file COPYING for details.
WHERE IS THE LATEST SOURCE CODE?
Source code for lib6502 is available from the author's home page at
'http://piumarta.com/software'. You can download the most recent
release or use Subversion to get the very latest sources.
WHERE IS THE DOCUMENTATION?
Manual pages for run6502 and lib6502 (and all the functions it
exports) should be available once it is installed. Each includes a
short 'examples' section. Use the 'man' command to read them.
Your best place to start looking for documentation on the 6502
itself is 'http://6502.org'. A google search of the web will also
turn up vast quantities of information about (and programs for) the
6502.
HOW DO I INSTALL IT?
It's not really big enough to warrant the whole 'configure' thing.
Any system with an ANSI compiler and C library should be able to
compile it out of the box. After unpacking the archive, just type:
make
to build it. If the compiler blows up immediately, edit the
Makefile and play with the '-g' and '-O' flags and then try again.
If you really can't make the compiler happy you've found a bug (read
the next section but one). Otherwise, if you want it put it
somewhere more permanent then type:
make install
(as root) to install it. It goes into /usr/local by default; if you
want it elsewhere then set PREFIX in the make command. For example:
make install PREFIX=/usr
will put everything under '/usr'.
When you get bored with it, go back to the source directory and
type:
make uninstall
(with the same PREFIX you specified during the install, if
necessary.)
WHAT CAN I DO WITH IT?
See the file EXAMPLES for some suggestions (all of them polite).
If that leaves you wanting more, read the source for run6502 -- it
exercises just about every feature in lib6502.
HOW DO I REPORT PROBLEMS?^W^WCONTACT THE ORIGINAL AUTHOR?
[If you wish to get in touch with the author of lib6502, this is the
address to use. Since lib6502-jit is based on lib6502 but has been
heavily modified, please do *not* report problems to this address;
use the address in README instead. -- Steve]
Send e-mail to the author at: firstName (at) lastName (dot) com
(For suitable values of firstName and lastName, see the last section
of this file.)
If you're still confused, contact him at: http://piumarta.com
HOW CAN I HELP?
Use it. Find bugs. Fix bugs. Make it faster. Evangelism: spread
it to as many other projects as possible, especially those that
might be using a slower emulator! Read the manual pages to see
what's considered missing, then add it, then send it in.
(One thing that would be be really handy, and isn't mentioned in the
manual pages, is a test suite. Figure out how to test every mode in
every instruction with every possible combination of operand values
and condition codes and verify the behaviour is correct. Then write
it down in the form of a program and send it in. If it's a
self-contained program that runs once to completion then we can
probably find some real hardware to test against the test suite.)
If you know how to write software that emulates peripheral hardware
devices, google up some details on the popular 6502-based
microcomputers (Acorn, Commodore, etc.) and add some serious system
emulation to run6502. Make it all pluggable (think dynamic
libraries over an 'agnostic' core), so we can change machines at the
flip of a (command-line) switch. (The callback mechanism in lib6502
was designed with this kind of 'pluggable hardware emulation' in
mind.)
WHO WROTE THIS STUFF, AND WHY?
lib6502 was written by Ian Piumarta.
While writing ccg (an entirely different project that creates
runtime assemblers for dynamic code generators) he decided to
include support for an 8-bit microprocessor, just for fun. He chose
the 6502 because it was used in the first computer he owned and
programmed (an Ohio Scientific Superboard II, when he was 14) as
well as the second (an Acorn 'BBC Model B', about four years later).
lib6502 started as a 'glorified switch statement' that ran some
small test programs spewed into memory by ccg, but rapidly got out
of control over the course of a weekend. You're looking at the
result.

59
Registers.cpp Normal file
View File

@ -0,0 +1,59 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#include "Registers.h"
#include "const.h"
#include "lib6502.h"
#include "M6502Internal.h"
void Registers::to_M6502_Registers(M6502 *mpu) const
{
M6502_Registers &er = *(mpu->registers);
Registers &ir = mpu->internal->registers_;
er.a = ir.a;
er.x = ir.x;
er.y = ir.y;
er.s = ir.s;
er.p = 0;
if (ir.flag_n) er.p |= flagN;
if (ir.flag_v) er.p |= flagV;
if (ir.flag_d) er.p |= flagD;
if (ir.flag_i) er.p |= flagI;
if (ir.flag_z) er.p |= flagZ;
if (ir.flag_c) er.p |= flagC;
er.pc = ir.pc;
}
void Registers::from_M6502_Registers(const M6502 *mpu)
{
M6502_Registers &er = *(mpu->registers);
Registers &ir = mpu->internal->registers_;
ir.a = er.a;
ir.x = er.x;
ir.y = er.y;
ir.s = er.s;
ir.flag_n = ((er.p & flagN) != 0);
ir.flag_v = ((er.p & flagV) != 0);
ir.flag_d = ((er.p & flagD) != 0);
ir.flag_i = ((er.p & flagI) != 0);
ir.flag_z = ((er.p & flagZ) != 0);
ir.flag_c = ((er.p & flagC) != 0);
ir.pc = er.pc;
}

51
Registers.h Normal file
View File

@ -0,0 +1,51 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#ifndef REGISTERS_H
#define REGISTERS_H
#include <boost/utility.hpp>
#include <stdint.h>
#include "JitBool.h"
typedef struct _M6502 M6502;
struct Registers : boost::noncopyable
{
uint8_t a;
uint8_t x;
uint8_t y;
uint8_t s;
JitBool flag_n;
JitBool flag_v;
JitBool flag_d;
JitBool flag_i;
JitBool flag_z;
JitBool flag_c;
uint16_t pc;
// Pseudo-registers used to communicate state for callbacks; see the
// comment describing the Result enumeration in FunctionBuilder.h.
uint16_t addr;
uint8_t data;
void to_M6502_Registers(M6502 *mpu) const;
void from_M6502_Registers(const M6502 *mpu);
};
#endif

67
TODO Normal file
View File

@ -0,0 +1,67 @@
It would be interesting to see if this works OK on an ARM machine.
Running e.g. z-self-modify-1 to completion in -mc -mx 1 mode shows the memory
for the run6502 process grows steadily, but valgrind doesn't show any leaks. A
quick web search suggests this might be internal leaks in LLVM (which are only
exposed by things like this which continually JIT). I am inclined to leave this
and perhaps come back to it once LLVM 3.5 is actuallly released; if there's
still a problem then it might be worth tracking it down.
Would it be helpful to pass branch weights to CreateCondBr()? For example,
where we have a computed address which might trigger a read/write callback, we
could calculate the proportion of addresses in the address range which have
callbacks on them and use that as the probability of taking the callback-exists
branch.
We could potentially use Function objects to deduce properties of stretches of
code and use that information to improve the generated code. For example, if we
observed that a Function object didn't contain any external calls or any
stack-modification instructions except RTS then we could inline it in any
callers (adding its code ranges to their code ranges, of course) and the RTS
could be a no-op. (For 100% accuracy, the JSR should still push the return
address on the stack but not modify the stack pointer. Code executed later on
might peek at the stack and expect those values to be there.) This might in
turn allow the callers of that Function to be inlined themselves. This is just
an example. It may be that in practice deciding when to re-translate code would
cause a sufficient performance impact to just not be worth it in the first
place.
We could add support for counting the number of cycles executed by the JITted
code; lib6502 itself has some support for this in the form of the tick* macros,
but they don't do anything by default.
Would there be any performance improvement to be had by having Function objects
(tail) call one another where possible?
Hybrid mode currently makes no attempt to avoid re-generating Function objects
which are continually being invalidated due to self-modifying code. It might be
nice if some heuristic caused us to avoid this unnecessary work and just let
the interpreter always handle that code.
On a related but distinct note, currently once an element of
FunctionManager::code_at_address_ is set, it is never cleared. This might cause
us to avoid optimistic writes which in reality would be OK. We could use some
heuristic to decide when to destroy Function objects which have not been
executed in a long time, and start clearing code_at_address_ elements when all
functions covering an address are removed. (See the note in
FunctionManager::destroyFunction(); this clearing must be done *outside* the
loop in FunctionManager::buildFunction(), or the implementation of
buildFunction() must be tweaked.)
However, it may be that it just isn't worth being that clever. Any such code
would need to be triggered inside the main loop between executions of Function
objects. We could do it only every nth time, and keeping track of how many
times we've been round probably wouldn't significantly harm performance, but be
careful.
Would a different default value for max_instructions be better?
Are there any other LLVM optimisation passes which would be helpful?

649
build-aux/tap-driver.sh Executable file
View File

@ -0,0 +1,649 @@
#! /bin/sh
# Copyright (C) 2011 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# As a special exception to the GNU General Public License, if you
# distribute this file as part of a program that contains a
# configuration script generated by Autoconf, you may include it under
# the same distribution terms that you use for the rest of that program.
# This file is maintained in Automake, please report
# bugs to <bug-automake@gnu.org> or send patches to
# <automake-patches@gnu.org>.
scriptversion=2011-12-27.17; # UTC
# Make unconditional expansion of undefined variables an error. This
# helps a lot in preventing typo-related bugs.
set -u
me=tap-driver.sh
fatal ()
{
echo "$me: fatal: $*" >&2
exit 1
}
usage_error ()
{
echo "$me: $*" >&2
print_usage >&2
exit 2
}
print_usage ()
{
cat <<END
Usage:
tap-driver.sh --test-name=NAME --log-file=PATH --trs-file=PATH
[--expect-failure={yes|no}] [--color-tests={yes|no}]
[--enable-hard-errors={yes|no}] [--ignore-exit]
[--diagnostic-string=STRING] [--merge|--no-merge]
[--comments|--no-comments] [--] TEST-COMMAND
The \`--test-name', \`--log-file' and \`--trs-file' options are mandatory.
END
}
# TODO: better error handling in option parsing (in particular, ensure
# TODO: $log_file, $trs_file and $test_name are defined).
test_name= # Used for reporting.
log_file= # Where to save the result and output of the test script.
trs_file= # Where to save the metadata of the test run.
expect_failure=0
color_tests=0
merge=0
ignore_exit=0
comments=0
diag_string='#'
while test $# -gt 0; do
case $1 in
--help) print_usage; exit $?;;
--version) echo "$me $scriptversion"; exit $?;;
--test-name) test_name=$2; shift;;
--log-file) log_file=$2; shift;;
--trs-file) trs_file=$2; shift;;
--color-tests) color_tests=$2; shift;;
--expect-failure) expect_failure=$2; shift;;
--enable-hard-errors) shift;; # No-op.
--merge) merge=1;;
--no-merge) merge=0;;
--ignore-exit) ignore_exit=1;;
--comments) comments=1;;
--no-comments) comments=0;;
--diagnostic-string) diag_string=$2; shift;;
--) shift; break;;
-*) usage_error "invalid option: '$1'";;
esac
shift
done
test $# -gt 0 || usage_error "missing test command"
case $expect_failure in
yes) expect_failure=1;;
*) expect_failure=0;;
esac
if test $color_tests = yes; then
init_colors='
color_map["red"]="" # Red.
color_map["grn"]="" # Green.
color_map["lgn"]="" # Light green.
color_map["blu"]="" # Blue.
color_map["mgn"]="" # Magenta.
color_map["std"]="" # No color.
color_for_result["ERROR"] = "mgn"
color_for_result["PASS"] = "grn"
color_for_result["XPASS"] = "red"
color_for_result["FAIL"] = "red"
color_for_result["XFAIL"] = "lgn"
color_for_result["SKIP"] = "blu"'
else
init_colors=''
fi
{
(
# Ignore common signals (in this subshell only!), to avoid potential
# problems with Korn shells. Some Korn shells are known to propagate
# to themselves signals that have killed a child process they were
# waiting for; this is done at least for SIGINT (and usually only for
# it, in truth). Without the `trap' below, such a behaviour could
# cause a premature exit in the current subshell, e.g., in case the
# test command it runs gets terminated by a SIGINT. Thus, the awk
# script we are piping into would never seen the exit status it
# expects on its last input line (which is displayed below by the
# last `echo $?' statement), and would thus die reporting an internal
# error.
# For more information, see the Autoconf manual and the threads:
# <http://lists.gnu.org/archive/html/bug-autoconf/2011-09/msg00004.html>
# <http://mail.opensolaris.org/pipermail/ksh93-integration-discuss/2009-February/004121.html>
trap : 1 3 2 13 15
if test $merge -gt 0; then
exec 2>&1
else
exec 2>&3
fi
"$@"
echo $?
) | LC_ALL=C ${AM_TAP_AWK-awk} \
-v me="$me" \
-v test_script_name="$test_name" \
-v log_file="$log_file" \
-v trs_file="$trs_file" \
-v expect_failure="$expect_failure" \
-v merge="$merge" \
-v ignore_exit="$ignore_exit" \
-v comments="$comments" \
-v diag_string="$diag_string" \
'
# FIXME: the usages of "cat >&3" below could be optimized when using
# FIXME: GNU awk, and/on on systems that supports /dev/fd/.
# Implementation note: in what follows, `result_obj` will be an
# associative array that (partly) simulates a TAP result object
# from the `TAP::Parser` perl module.
## ----------- ##
## FUNCTIONS ##
## ----------- ##
function fatal(msg)
{
print me ": " msg | "cat >&2"
exit 1
}
function abort(where)
{
fatal("internal error " where)
}
# Convert a boolean to a "yes"/"no" string.
function yn(bool)
{
return bool ? "yes" : "no";
}
function add_test_result(result)
{
if (!test_results_index)
test_results_index = 0
test_results_list[test_results_index] = result
test_results_index += 1
test_results_seen[result] = 1;
}
# Whether the test script should be re-run by "make recheck".
function must_recheck()
{
for (k in test_results_seen)
if (k != "XFAIL" && k != "PASS" && k != "SKIP")
return 1
return 0
}
# Whether the content of the log file associated to this test should
# be copied into the "global" test-suite.log.
function copy_in_global_log()
{
for (k in test_results_seen)
if (k != "PASS")
return 1
return 0
}
# FIXME: this can certainly be improved ...
function get_global_test_result()
{
if ("ERROR" in test_results_seen)
return "ERROR"
if ("FAIL" in test_results_seen || "XPASS" in test_results_seen)
return "FAIL"
all_skipped = 1
for (k in test_results_seen)
if (k != "SKIP")
all_skipped = 0
if (all_skipped)
return "SKIP"
return "PASS";
}
function stringify_result_obj(result_obj)
{
if (result_obj["is_unplanned"] || result_obj["number"] != testno)
return "ERROR"
if (plan_seen == LATE_PLAN)
return "ERROR"
if (result_obj["directive"] == "TODO")
return result_obj["is_ok"] ? "XPASS" : "XFAIL"
if (result_obj["directive"] == "SKIP")
return result_obj["is_ok"] ? "SKIP" : COOKED_FAIL;
if (length(result_obj["directive"]))
abort("in function stringify_result_obj()")
return result_obj["is_ok"] ? COOKED_PASS : COOKED_FAIL
}
function decorate_result(result)
{
color_name = color_for_result[result]
if (color_name)
return color_map[color_name] "" result "" color_map["std"]
# If we are not using colorized output, or if we do not know how
# to colorize the given result, we should return it unchanged.
return result
}
function report(result, details)
{
if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/)
{
msg = ": " test_script_name
add_test_result(result)
}
else if (result == "#")
{
msg = " " test_script_name ":"
}
else
{
abort("in function report()")
}
if (length(details))
msg = msg " " details
# Output on console might be colorized.
print decorate_result(result) msg
# Log the result in the log file too, to help debugging (this is
# especially true when said result is a TAP error or "Bail out!").
print result msg | "cat >&3";
}
function testsuite_error(error_message)
{
report("ERROR", "- " error_message)
}
function handle_tap_result()
{
details = result_obj["number"];
if (length(result_obj["description"]))
details = details " " result_obj["description"]
if (plan_seen == LATE_PLAN)
{
details = details " # AFTER LATE PLAN";
}
else if (result_obj["is_unplanned"])
{
details = details " # UNPLANNED";
}
else if (result_obj["number"] != testno)
{
details = sprintf("%s # OUT-OF-ORDER (expecting %d)",
details, testno);
}
else if (result_obj["directive"])
{
details = details " # " result_obj["directive"];
if (length(result_obj["explanation"]))
details = details " " result_obj["explanation"]
}
report(stringify_result_obj(result_obj), details)
}
# `skip_reason` should be empty whenever planned > 0.
function handle_tap_plan(planned, skip_reason)
{
planned += 0 # Avoid getting confused if, say, `planned` is "00"
if (length(skip_reason) && planned > 0)
abort("in function handle_tap_plan()")
if (plan_seen)
{
# Error, only one plan per stream is acceptable.
testsuite_error("multiple test plans")
return;
}
planned_tests = planned
# The TAP plan can come before or after *all* the TAP results; we speak
# respectively of an "early" or a "late" plan. If we see the plan line
# after at least one TAP result has been seen, assume we have a late
# plan; in this case, any further test result seen after the plan will
# be flagged as an error.
plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN)
# If testno > 0, we have an error ("too many tests run") that will be
# automatically dealt with later, so do not worry about it here. If
# $plan_seen is true, we have an error due to a repeated plan, and that
# has already been dealt with above. Otherwise, we have a valid "plan
# with SKIP" specification, and should report it as a particular kind
# of SKIP result.
if (planned == 0 && testno == 0)
{
if (length(skip_reason))
skip_reason = "- " skip_reason;
report("SKIP", skip_reason);
}
}
function extract_tap_comment(line)
{
if (index(line, diag_string) == 1)
{
# Strip leading `diag_string` from `line`.
line = substr(line, length(diag_string) + 1)
# And strip any leading and trailing whitespace left.
sub("^[ \t]*", "", line)
sub("[ \t]*$", "", line)
# Return what is left (if any).
return line;
}
return "";
}
# When this function is called, we know that line is a TAP result line,
# so that it matches the (perl) RE "^(not )?ok\b".
function setup_result_obj(line)
{
# Get the result, and remove it from the line.
result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0)
sub("^(not )?ok[ \t]*", "", line)
# If the result has an explicit number, get it and strip it; otherwise,
# automatically assing the next progresive number to it.
if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/)
{
match(line, "^[0-9]+")
# The final `+ 0` is to normalize numbers with leading zeros.
result_obj["number"] = substr(line, 1, RLENGTH) + 0
line = substr(line, RLENGTH + 1)
}
else
{
result_obj["number"] = testno
}
if (plan_seen == LATE_PLAN)
# No further test results are acceptable after a "late" TAP plan
# has been seen.
result_obj["is_unplanned"] = 1
else if (plan_seen && testno > planned_tests)
result_obj["is_unplanned"] = 1
else
result_obj["is_unplanned"] = 0
# Strip trailing and leading whitespace.
sub("^[ \t]*", "", line)
sub("[ \t]*$", "", line)
# This will have to be corrected if we have a "TODO"/"SKIP" directive.
result_obj["description"] = line
result_obj["directive"] = ""
result_obj["explanation"] = ""
if (index(line, "#") == 0)
return # No possible directive, nothing more to do.
# Directives are case-insensitive.
rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*"
# See whether we have the directive, and if yes, where.
pos = match(line, rx "$")
if (!pos)
pos = match(line, rx "[^a-zA-Z0-9_]")
# If there was no TAP directive, we have nothing more to do.
if (!pos)
return
# Let`s now see if the TAP directive has been escaped. For example:
# escaped: ok \# SKIP
# not escaped: ok \\# SKIP
# escaped: ok \\\\\# SKIP
# not escaped: ok \ # SKIP
if (substr(line, pos, 1) == "#")
{
bslash_count = 0
for (i = pos; i > 1 && substr(line, i - 1, 1) == "\\"; i--)
bslash_count += 1
if (bslash_count % 2)
return # Directive was escaped.
}
# Strip the directive and its explanation (if any) from the test
# description.
result_obj["description"] = substr(line, 1, pos - 1)
# Now remove the test description from the line, that has been dealt
# with already.
line = substr(line, pos)
# Strip the directive, and save its value (normalized to upper case).
sub("^[ \t]*#[ \t]*", "", line)
result_obj["directive"] = toupper(substr(line, 1, 4))
line = substr(line, 5)
# Now get the explanation for the directive (if any), with leading
# and trailing whitespace removed.
sub("^[ \t]*", "", line)
sub("[ \t]*$", "", line)
result_obj["explanation"] = line
}
function get_test_exit_message(status)
{
if (status == 0)
return ""
if (status !~ /^[1-9][0-9]*$/)
abort("getting exit status")
if (status < 127)
exit_details = ""
else if (status == 127)
exit_details = " (command not found?)"
else if (status >= 128 && status <= 255)
exit_details = sprintf(" (terminated by signal %d?)", status - 128)
else if (status > 256 && status <= 384)
# We used to report an "abnormal termination" here, but some Korn
# shells, when a child process die due to signal number n, can leave
# in $? an exit status of 256+n instead of the more standard 128+n.
# Apparently, both behaviours are allowed by POSIX (2008), so be
# prepared to handle them both. See also Austing Group report ID
# 0000051 <http://www.austingroupbugs.net/view.php?id=51>
exit_details = sprintf(" (terminated by signal %d?)", status - 256)
else
# Never seen in practice.
exit_details = " (abnormal termination)"
return sprintf("exited with status %d%s", status, exit_details)
}
function write_test_results()
{
print ":global-test-result: " get_global_test_result() > trs_file
print ":recheck: " yn(must_recheck()) > trs_file
print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file
for (i = 0; i < test_results_index; i += 1)
print ":test-result: " test_results_list[i] > trs_file
close(trs_file);
}
BEGIN {
## ------- ##
## SETUP ##
## ------- ##
'"$init_colors"'
# Properly initialized once the TAP plan is seen.
planned_tests = 0
COOKED_PASS = expect_failure ? "XPASS": "PASS";
COOKED_FAIL = expect_failure ? "XFAIL": "FAIL";
# Enumeration-like constants to remember which kind of plan (if any)
# has been seen. It is important that NO_PLAN evaluates "false" as
# a boolean.
NO_PLAN = 0
EARLY_PLAN = 1
LATE_PLAN = 2
testno = 0 # Number of test results seen so far.
bailed_out = 0 # Whether a "Bail out!" directive has been seen.
# Whether the TAP plan has been seen or not, and if yes, which kind
# it is ("early" is seen before any test result, "late" otherwise).
plan_seen = NO_PLAN
## --------- ##
## PARSING ##
## --------- ##
is_first_read = 1
while (1)
{
# Involutions required so that we are able to read the exit status
# from the last input line.
st = getline
if (st < 0) # I/O error.
fatal("I/O error while reading from input stream")
else if (st == 0) # End-of-input
{
if (is_first_read)
abort("in input loop: only one input line")
break
}
if (is_first_read)
{
is_first_read = 0
nextline = $0
continue
}
else
{
curline = nextline
nextline = $0
$0 = curline
}
# Copy any input line verbatim into the log file.
print | "cat >&3"
# Parsing of TAP input should stop after a "Bail out!" directive.
if (bailed_out)
continue
# TAP test result.
if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/)
{
testno += 1
setup_result_obj($0)
handle_tap_result()
}
# TAP plan (normal or "SKIP" without explanation).
else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/)
{
# The next two lines will put the number of planned tests in $0.
sub("^1\\.\\.", "")
sub("[^0-9]*$", "")
handle_tap_plan($0, "")
continue
}
# TAP "SKIP" plan, with an explanation.
else if ($0 ~ /^1\.\.0+[ \t]*#/)
{
# The next lines will put the skip explanation in $0, stripping
# any leading and trailing whitespace. This is a little more
# tricky in truth, since we want to also strip a potential leading
# "SKIP" string from the message.
sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "")
sub("[ \t]*$", "");
handle_tap_plan(0, $0)
}
# "Bail out!" magic.
# Older versions of prove and TAP::Harness (e.g., 3.17) did not
# recognize a "Bail out!" directive when preceded by leading
# whitespace, but more modern versions (e.g., 3.23) do. So we
# emulate the latter, "more modern" behaviour.
else if ($0 ~ /^[ \t]*Bail out!/)
{
bailed_out = 1
# Get the bailout message (if any), with leading and trailing
# whitespace stripped. The message remains stored in `$0`.
sub("^[ \t]*Bail out![ \t]*", "");
sub("[ \t]*$", "");
# Format the error message for the
bailout_message = "Bail out!"
if (length($0))
bailout_message = bailout_message " " $0
testsuite_error(bailout_message)
}
# Maybe we have too look for dianogtic comments too.
else if (comments != 0)
{
comment = extract_tap_comment($0);
if (length(comment))
report("#", comment);
}
}
## -------- ##
## FINISH ##
## -------- ##
# A "Bail out!" directive should cause us to ignore any following TAP
# error, as well as a non-zero exit status from the TAP producer.
if (!bailed_out)
{
if (!plan_seen)
{
testsuite_error("missing test plan")
}
else if (planned_tests != testno)
{
bad_amount = testno > planned_tests ? "many" : "few"
testsuite_error(sprintf("too %s tests run (expected %d, got %d)",
bad_amount, planned_tests, testno))
}
if (!ignore_exit)
{
# Fetch exit status from the last line.
exit_message = get_test_exit_message(nextline)
if (exit_message)
testsuite_error(exit_message)
}
}
write_test_results()
exit 0
} # End of "BEGIN" block.
'
# TODO: document that we consume the file descriptor 3 :-(
} 3>"$log_file"
test $? -eq 0 || fatal "I/O or internal error"
# Local Variables:
# mode: shell-script
# sh-indentation: 2
# eval: (add-hook 'write-file-hooks 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-time-zone: "UTC"
# time-stamp-end: "; # UTC"
# End:

89
config.h.in Normal file
View File

@ -0,0 +1,89 @@
/* config.h.in. Generated from configure.ac by autoheader. */
/* Defined if the requested minimum BOOST version is satisfied */
#undef HAVE_BOOST
/* Define to 1 if you have <boost/scoped_ptr.hpp> */
#undef HAVE_BOOST_SCOPED_PTR_HPP
/* Define to 1 if you have <boost/shared_ptr.hpp> */
#undef HAVE_BOOST_SHARED_PTR_HPP
/* Define to 1 if you have <boost/system/error_code.hpp> */
#undef HAVE_BOOST_SYSTEM_ERROR_CODE_HPP
/* Define to 1 if you have <boost/thread.hpp> */
#undef HAVE_BOOST_THREAD_HPP
/* Define to 1 if you have the <dlfcn.h> header file. */
#undef HAVE_DLFCN_H
/* Define to 1 if you have the <inttypes.h> header file. */
#undef HAVE_INTTYPES_H
/* Set to 1 if you have the "llvm/Analysis/Verifier.h" header file */
#undef HAVE_LLVM_ANALYSIS_VERIFIER_H
/* Set to 1 if you have the llvm::DataLayoutPass class */
#undef HAVE_LLVM_DATA_LAYOUT_PASS
/* Set to 1 if you have the "llvm/IR/Verifier.h" header file */
#undef HAVE_LLVM_IR_VERIFIER_H
/* Define to 1 if you have the <memory.h> header file. */
#undef HAVE_MEMORY_H
/* Define to 1 if you have the <stdint.h> header file. */
#undef HAVE_STDINT_H
/* Define to 1 if you have the <stdlib.h> header file. */
#undef HAVE_STDLIB_H
/* Define to 1 if you have the <strings.h> header file. */
#undef HAVE_STRINGS_H
/* Define to 1 if you have the <string.h> header file. */
#undef HAVE_STRING_H
/* Define to 1 if you have the <sys/stat.h> header file. */
#undef HAVE_SYS_STAT_H
/* Define to 1 if you have the <sys/types.h> header file. */
#undef HAVE_SYS_TYPES_H
/* Define to 1 if you have the <unistd.h> header file. */
#undef HAVE_UNISTD_H
/* Define to the sub-directory in which libtool stores uninstalled libraries.
*/
#undef LT_OBJDIR
/* Name of package */
#undef PACKAGE
/* Define to the address where bug reports for this package should be sent. */
#undef PACKAGE_BUGREPORT
/* Package copyright */
#undef PACKAGE_COPYRIGHT
/* Define to the full name of this package. */
#undef PACKAGE_NAME
/* Define to the full name and version of this package. */
#undef PACKAGE_STRING
/* Define to the one symbol short name of this package. */
#undef PACKAGE_TARNAME
/* Define to the home page for this package. */
#undef PACKAGE_URL
/* Define to the version of this package. */
#undef PACKAGE_VERSION
/* Define to 1 if you have the ANSI C header files. */
#undef STDC_HEADERS
/* Version number of package */
#undef VERSION

94
configure.ac Normal file
View File

@ -0,0 +1,94 @@
AC_INIT([lib6502-jit], [1.0], [lib6502-jit@lemma.co.uk])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIR([m4])
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects no-dist-gzip dist-bzip2])
AM_MAINTAINER_MODE([enable])
LT_INIT([disable-shared])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([Makefile])
AC_REQUIRE_AUX_FILE([tap-driver.sh])
# Copyright for configure.ac *only*
AC_COPYRIGHT([Copyright (c) 2014 Steven Flintham])
AC_DEFINE([PACKAGE_COPYRIGHT], ["(C) - see COPYING"], [Package copyright])
# for tap-driver.sh
AC_PROG_AWK
AC_PROG_CC
AC_PROG_CXX
BOOST_REQUIRE
BOOST_SMART_PTR
BOOST_THREAD
# I want to:
# - use "llvm-config" (relying on PATH) if the user doesn't do anything
# special, but
# - allow the user to say --with-llvm-config=XXX to use XXX instead of
# llvm-config, where XXX might need to be found on the PATH (e.g. if
# the program is called llvm-config-3.5) or might be an absolute/
# relative filename
# In both of the above cases, I want to actually check explicitly the
# llvm-config program can be found. This doesn't seem to be supported by
# autoconf:
# - AC_CHECK_PROG() and AC_PATH_PROG() both insist on the program name being a
# leaf name with no included path.
# - AC_CHECK_FILE() (not unreasonably) doesn't look on PATH for the file
# (and wouldn't check for executability)
# So I have to just hack it with "which" and hope.
AC_ARG_WITH(
[llvm-config],
[AS_HELP_STRING(
[--with-llvm-config=FILE],
[filename of llvm-config executable (if not on PATH)])],
[LLVMCONFIG="$withval"],
[LLVMCONFIG="llvm-config"])
echo -n "checking for $LLVMCONFIG... "
AS_IF(
[which "$LLVMCONFIG" >/dev/null],
[echo yes],
[echo no
AC_MSG_ERROR([llvm-config not found; try --with-llvm-config=FILE?])])
AC_SUBST(LLVMCONFIG)
# These variables are sacred to the user. But we need to set them in order for
# configure's test programs to find the LLVM headers. I am probably doing this
# completely wrong. In twenty years or so maybe I will achieve auto-enlightenment
# and look back at this and laugh.
SACRED_CPPFLAGS="$CPPFLAGS"
SACRED_CXXFLAGS="$CXXFLAGS"
CPPFLAGS=["`$LLVMCONFIG --cppflags` $CPPFLAGS"]
CXXFLAGS=["`$LLVMCONFIG --cxxflags` -fexceptions $CXXFLAGS"]
AC_LANG(C++)
# This header moves around a bit, check for the two known possible locations.
AC_CHECK_HEADER(
[llvm/IR/Verifier.h],
[AC_DEFINE([HAVE_LLVM_IR_VERIFIER_H], 1, [Set to 1 if you have the "llvm/IR/Verifier.h" header file])])
AC_CHECK_HEADER(
[llvm/Analysis/Verifier.h],
[AC_DEFINE([HAVE_LLVM_ANALYSIS_VERIFIER_H], 1, [Set to 1 if you have the "llvm/Analysis/Verifier.h" header file])])
# TODO: Can I get configure to fail if neither of the previous tests
# succeeds? Otherwise configure will succeed but the build will fail.
# This header always exists, but DataLayoutPass isn't always present.
AC_CHECK_HEADER(
[llvm/IR/DataLayout.h],
[],
[AC_MSG_ERROR([llvm/IR/DataLayout.h not found])])
AC_CHECK_TYPE(
[llvm::DataLayoutPass],
[AC_DEFINE([HAVE_LLVM_DATA_LAYOUT_PASS], 1, [Set to 1 if you have the llvm::DataLayoutPass class])],
[],
[#include "llvm/IR/DataLayout.h"])
CPPFLAGS="$SACRED_CPPFLAGS"
CXXFLAGS="$SACRED_CXXFLAGS"
AC_OUTPUT

58
const.h Normal file
View File

@ -0,0 +1,58 @@
/* Copyright (c) 2005 Ian Piumarta
* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#ifndef CONST_H
#define CONST_H
#include <stdint.h>
namespace
{
const uint8_t opcode_brk = 0x00;
const uint8_t opcode_rti = 0x40;
const uint8_t opcode_rts = 0x60;
const uint8_t opcode_bra = 0x80;
const uint8_t opcode_bcc = 0x90;
const uint8_t opcode_bcs = 0xb0;
const uint8_t opcode_bvc = 0x50;
const uint8_t opcode_bvs = 0x70;
const uint8_t opcode_beq = 0xf0;
const uint8_t opcode_bne = 0xd0;
const uint8_t opcode_bpl = 0x10;
const uint8_t opcode_bmi = 0x30;
const uint8_t opcode_jsr = 0x20;
const uint8_t opcode_jmp_abs = 0x4c;
const uint8_t opcode_jmp_ind_abs = 0x6c;
const uint8_t opcode_jmp_indx_abs = 0x7c;
enum {
flagN= (1<<7), /* negative */
flagV= (1<<6), /* overflow */
flagX= (1<<5), /* unused */
flagB= (1<<4), /* irq from brk */
flagD= (1<<3), /* decimal mode */
flagI= (1<<2), /* irq disable */
flagZ= (1<<1), /* zero */
flagC= (1<<0) /* carry */
};
const uint32_t memory_size = 0x10000;
const uint16_t stack = 0x100;
}
#endif

406
examples/README Normal file
View File

@ -0,0 +1,406 @@
lib6502 - 6502 Microprocessor Emulator
EXAMPLES
This file has three sections:
1. PROGRAMS that you can compile and run
2. COMMANDS that you can copy and paste into a terminal
3. ADVANCED stuff that requires some additional setup
A few numbered footnotes appear at the end and are referenced in the
text in square brackets [6].
----------------------------------------------------------------
1. PROGRAMS
(We're going to start in 'serious mode'. Bear with me.)
The file 'lib1.c' contains the example from the run6502 manual page.
Just compile and run it:
cc -o lib1 lib1.c
./lib1
The file has been commented extensively to explain exactly what is
going on.
----------------------------------------------------------------
2. COMMANDS
(Much more fun: this is the section that appeals to the geek in me.)
6502 machine code is pretty straightforward. (Many 6502 programmers
remember a time from their misguided childhood when they could
compose and edit programs directly in hexadecimal using their 'front
panel' monitor program -- the next best thing to programming with a
row of switches and lamps, but I digress and will leave that story
until the pdp11 emulator is ready. ;-) We can use this fact to
generate an entire program without needing an assembler. The 'perl'
program is available on most Unixy (and several other) systems and
makes it easy to create binary files from a string of hex digits.
(There is a program called 'xxd' that's very good at this kind of
thing, but you might not have it.)
First the program (stolen from lib1.c):
1000 ldx #41 A241
1002 txa 8A
1003 jsr FFEE 20EEFF
1006 inx E8
1007 cpx #5B E05B
1009 bne 1002 D0F7
100B lda #0A A90A
100D jsr FFEE 20EEFF
1010 brk 00
In C-like syntax it is equivalent to:
regX = 'A';
do {
regA = regX;
putchar(regA);
} while (regX != 'Z' + 1);
putchar('\n');
(which by today's standards is a *huge* amount of stuff packed into
just 17 bytes of 'compiled' code -- on a 386 the same program is
around 65 bytes [1], and more like 88 bytes on a 32-bit RISC [2]).
The column on the right is the machine code in hexadecimal. When
strung out in a line it looks like this:
A2418A20EEFFE8E05BD0F7A90A20EEFF00
We can tell perl to 'pack' this hexadecimal string into binary and
save the output in a file:
echo A2418A20EEFFE8E05BD0F7A90A20EEFF00 |
perl -e 'print pack "H*",<STDIN>' > temp.img
To check the contents of the file, we can load it into run6502 and
then disassemble it:
run6502 -l 1000 temp.img -d 1000 +11 -x
The '-l 1000 temp.img' loads the file into the 6502's memory at
address 0x1000, and the '-d 1000 +11' disassembles 17 bytes (11 in
hex) of code starting at 0x1000. The final '-x' tells run6502 not
to try to execute the code. The output should look just like the
program listing above.
This is almost all we need to run it; just a few details remain.
- The emulator doesn't know where to start execution. We need to
set the 'reset' vector to 0x1000 -- the address of the first
instruction in the program. The '-R 1000' option does this.
- The program calls the 'putchar' function at address 0xFFEE to
send a character to the terminal. run6502 can emulate this for
us, with the '-P FFEE' option.
- We have to have some way to make the processor stop execution
(there is no 'halt' instruction on the 6502, at least not the
early versions). The trick is in the last instruction 'BRK',
that generates a 'software interrupt' -- eventually jumping to
the addres in the 'interrupt vector'. If we don't set the
interrupt vector explicitly it remains empty (zero) and BRK will
try to transfer control to address 0. The '-X 0' option tells
run6502 to stop executing if/when the program attempts to
transfer control to address 0 -- which it will, when it executes
the 'BRK' instruction with an empty interrupt vector. QED :-)
Here, then, is the complete command to run our program:
run6502 -l 1000 temp.img -R 1000 -P FFEE -X 0
This program is relocatable. You can load it at address 4321
(change both the -l and -R options) and it will work just fine.
Google for "6502 Reference Card" (with the quotes), grab a pencil
and paper, and you can start writing 6502 programs immediately! (If
you really want to experience what it was like in the late 1970s,
but without the added fun of entering each hex digit one at a time
into a monitor program, simply avoid the temptation ever to look at
your hand-assembled code with the '-d' option. ;-)
If you really start liking this and want to write longer programs in
text files with the hex split over many lines, you'll need a perl
script that can deal with newlines in the input. Something like
this should do the trick...
#!/usr/bin/perl
while (<STDIN>) {
chomp;
print pack "H*", $_
}
(This script is included in the 'examples' directory, in a file
called 'hex2bin', to save you 15 seconds of copy and paste.)
Need a fun project? Write a 6502 assembler... in 6502 machine code,
of course! Read in the assembly language text via 'getchar' (see
the '-G' option) and write out the assembled binary via 'putchar'
(the '-P' option, that we've already seen). Soon you'll be able to:
cat prog.s |
run6502 -l 1000 asm.img -R 1000 -G FFE0 -P FFEE -X 0 > prog.img
run6502 -l 1000 prog.img -R 1000 -G FFE0 -P FFEE -X 0
(The first prog.s you write should probably be the assembler itself,
transcribed from the paper copy used to hand-assemble the assembler
binary. This significant milestone can be reached with a
surprisingly simple assembler. After this pivotal moment the
assembler, assembling itself, can very quickly become very
powerful.)
----------------------------------------------------------------
3. ADVANCED
(Official justification: let's run something big and non-trivial.
More likely: a flimsy excuse for a trip down memory lane.)
The remaining examples assume that you have access to two ROM images
from the Acorn 'BBC Model B' microcomputer: the operating system and
the BASIC language . (Just crawl into the attic, fire up the old
Beeb, '*SAVE' the images into files, and then transfer them to your
Unix box over RS423. Under no circumstances should you google for
'Acorn BBC B OS ROMs zip', without the quotes. That would be
naughty, and probably illegal -- at least until the glorious day
when the revolution finally comes.)
After brushing yourself down (the attic is kind of dusty, no?) save
the two ROM images as 'OS12.ROM' and 'BASIC2.ROM'.
The first thing we can do is use run6502 as an editor to merge the
two ROMs into a single image file:
run6502 \
-l C000 OS12.ROM \
-l 8000 BASIC2.ROM \
-s 0000 +10000 bbc.img \
-x
(This is a single command, with '\' continuation characters joining
the lines into one. Your shell should figure it out if you just
copy and paste.) It leaves a file 'bbc.img' containing both the OS
and BASIC.
To run this image we need the '-B' option. It enables some minimal,
totally lame, hardware emulation of the BBC computer -- just enough
to boot the 'virtual beeb' into BASIC [3]:
run6502 -l 0 bbc.img -B
If all goes well, you should be greeted with a 'beep' and a message
telling you what computer you have (BBC Computer), how much RAM is
available (32K), the language you've been dropped into (BASIC), and
a '>' prompt. Turn on 'CAPS LOCK' (many of us remember those days,
and some of us even used to speak in ALL CAPS) and play:
PRINT 3+4
or maybe:
10 FOR A%=1 TO 10
20 PRINT A%
30 NEXT
LIST
RUN
or even:
10 P%=&2800
20 O%=P%
30 [
40 opt3
50 lda #10
60 jsr &FFEE
70 ldx #65
80 .l txa
90 jsr &FFEE
100 inx
110 cpx #91
120 bne l
130 lda #10
140 jmp &FFEE
150 ]
160 CALL &2800
LIST
RUN
(How cool is that? ;-)
One final thing: there is an option '-i' that works just like '-l'
except that it looks to see if the image file begins with '#!'. If
so, it skips over the first line of the file, up to and including
the first newline. Why? The system call that executes programs on
Unixy systems makes the same check. If the user executes a text
file 'foo' staring with '#!prog ...' then the OS loads and runs
'prog' instead, passing all the '...'s and the name of the text file
'foo' as arguments [4]. If you have 'temp.img' left over from from
the second example, open it in a text editor and add a single line
at the beginning that reads:
#!run6502 -i 1000
(If 'run6502' is not in your current working directory then you will
have to use the full path to the file: '#!/usr/bin/run6502' or
'#!/usr/local/bin/6502' or whatever. No spaces before the '#'!)
Now make the image executable:
chmod +x temp.img
and then (as if you hadn't already guessed) execute it:
./temp.img
Saves an awful lot of tedious typing. [5]
Have fun!
----------------------------------------------------------------
FOOTNOTES
[1] Here is the 'alphabet' program, verbatim, compiled (with
optimisation) on a 386. It's 66 bytes long, almost four times
longer than the 6502 version. (If I were more generous I might
consider that fair: 32 bits divided by 8 bits is four.)
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 53 push %ebx
4: 83 ec 14 sub $0x14,%esp
7: bb 41 00 00 00 mov $0x41,%ebx
c: a1 00 00 00 00 mov 0x0,%eax
11: 89 44 24 04 mov %eax,0x4(%esp)
15: 89 1c 24 mov %ebx,(%esp)
18: e8 fc ff ff ff call 19 <fputc>
1d: 43 inc %ebx
1e: 83 fb 5b cmp $0x5b,%ebx
21: 75 e9 jne c <prog+0xc>
23: a1 00 00 00 00 mov 0x0,%eax
28: 89 44 24 04 mov %eax,0x4(%esp)
2c: c7 04 24 0a 00 00 00 movl $0xa,(%esp)
33: e8 fc ff ff ff call 34 <fputc>
38: b8 00 00 00 00 mov $0x0,%eax
3d: 83 c4 14 add $0x14,%esp
40: 5b pop %ebx
41: 5d pop %ebp
42: c3 ret
[2] Here is the 'alphabet' program, verbatim, compiled (with
optimisation) on a PowerPC. It's 88 bytes long, more than five
times longer than the 6502 version. (I don't care what you say:
Apple Macs rule and mine has oodles of RAM to spare.)
00000000 mfspr r0,lr
00000004 stmw r29,0xfff4(r1)
00000008 stw r0,0x8(r1)
0000000c stwu r1,0xffb0(r1)
00000010 bcl 20,31,0x14
00000014 mfspr r31,lr
00000018 li r30,0x41
0000001c addis r2,r31,ha16(0xa4-0x14)
00000020 lwz r29,lo16(0xa4-0x14)(r2)
00000024 or r3,r30,r30
00000028 addi r4,r29,0x58
0000002c bl 0x7c ; symbol stub for: _fputc
00000030 cmpwi cr7,r30,0x5a
00000034 addi r30,r30,0x1
00000038 bne cr7,0x24
0000003c li r3,0xa
00000040 bl 0x5c ; symbol stub for: _fputc
00000044 li r3,0x0
00000048 lwz r0,0x58(r1)
0000004c addi r1,r1,0x50
00000050 mtspr lr,r0
00000054 lmw r29,0xfff4(r1)
00000058 blr
[3] Time to 'fess up with an undocumented 'feature'. We ran our
'bbc.img' file like this:
run6502 -l 0 bbc.img -B
I grew tired of typing all those '-'s and made run6502 check to
see if it was invoked with a single, non-option argument.
Running:
run6502 bbc.img
is precisely equivalent to the '-l -B' form above. I don't feel
too guilty about this since the manual page suggests that
providing a single, non-option argument is illegal usage.
[4] Okay, that might be a little confusing. Here it is written out in
full. If you have a text file called 'foo' containing
#!/usr/bin/prog -gobble
blah blah blah
blah blah blah
that is executable, and then you execute it like a compiled
program
./foo
then the OS will notice the '#!' and run the following command
instead:
/usr/bin/prog -gobble ./foo
The '-gobble' tells 'prog' to eat the first line, leaving just the
blah that follows. (The reason for choosing '#!' is that '#' is
the comment character in the standard Unix shell, with the obvious
happy consequences for shell scripts.)
[5] We can play the same '#!' game with our 'bbc.img' file. Open it
up and add the line
#!/usr/local/bin/run6502 -B -l 0
(or whatever, according to the location of the 'run6502' program),
make it executable
chmod +x bbc.img
and execute it:
./bbc.img
To save a whopping 32K of zeros at the beginning of the file,
create the image again with
run6502 \
-l C000 OS12.ROM \
-l 8000 BASIC2.ROM \
-s 8000 +8000 bbc.img \
-x
and run it with
run6502 -l 0 bbc.img -B
and, if you like, insert the single line
#!/usr/local/bin/run6502 -B -l 8000
at the start of the image file and make it executable:
./bbc.img
[6] There is no footnote 6.

6
examples/hex2bin Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/perl
while (<STDIN>) {
chomp;
print pack "H*", $_
}

108
examples/lib1.c Normal file
View File

@ -0,0 +1,108 @@
#include <stdio.h>
#include <stdlib.h>
#include "lib6502.h"
/* Emulated OS functions. */
#define WRCH 0xFFEE /* Write accumulator to stdout. */
/* Write the accumulator to stdout. This function will be invoked
* when the emulated program calls 0xFFEE.
*/
int wrch(M6502 *mpu, uint16_t address, uint8_t data)
{
int pc;
/* Write the character.
*/
putchar(mpu->registers->a);
/* We arrived here from a JSR instruction. The stack contains the
* saved PC. Pop it off the stack.
*/
pc = mpu->memory[++mpu->registers->s + 0x100];
pc |= mpu->memory[++mpu->registers->s + 0x100] << 8;
/* The JSR instruction pushes the value of PC before it has been
* incremented to point to the instruction after the JSR. Return PC
* + 1 as the address for the next insn. Returning non-zero
* indicates that we handled the 'subroutine' ourselves, and the
* emulator should pretend the original 'JSR' neveer happened at
* all.
*/
return pc + 1; /* JSR pushes next insn addr - 1 */
}
/* Exit gracefully. We arrange for this function to be called when
* the emulator tries to transfer control to address 0.
*/
int done(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
/* Dump the internal state of the processor.
*/
M6502_dump(mpu, buffer);
/* Print a cute message and quit.
*/
printf("\nBRK instruction\n%s\n", buffer);
exit(0);
}
int main()
{
M6502 *mpu = M6502_new(0, 0, 0); /* Make a 6502 */
unsigned pc = 0x1000; /* PC for 'assembly' */
/* Install the two callback functions defined above.
*/
M6502_setCallback(mpu, call, WRCH, wrch); /* Calling FFEE -> wrch() */
M6502_setCallback(mpu, call, 0, done); /* Calling 0 -> done() */
/* A few macros that dump bytes into the 6502's memory.
*/
# define gen1(X) (mpu->memory[pc++]= (uint8_t)(X))
# define gen2(X,Y) gen1(X); gen1(Y)
# define gen3(X,Y,Z) gen1(X); gen2(Y,Z)
/* Hand-assemble the program.
*/
gen2(0xA2, 'A' ); // LDX #'A'
gen1(0x8A ); // TXA
gen3(0x20,0xEE,0xFF); // JSR FFEE
gen1(0xE8 ); // INX
gen2(0xE0, 'Z'+1 ); // CPX #'Z'+1
gen2(0xD0, -9 ); // BNE 0x1002
gen2(0xA9, '\n' ); // LDA #'\n'
gen3(0x20,0xEE,0xFF); // JSR FFEE
gen2(0x00,0x00 ); // BRK
/* Just for fun: disssemble the program.
*/
{
char insn[64];
uint16_t ip= 0x1000;
while (ip < pc)
{
int isz = M6502_disassemble(mpu, ip, insn);
printf("%04X %s\n", ip, insn);
ip += isz;
}
}
/* Point the RESET vector at the first instruction in the assembled
* program.
*/
M6502_setVector(mpu, RST, 0x1000);
/* Reset the 6502 and run the program.
*/
M6502_reset(mpu);
M6502_run(mpu);
M6502_delete(mpu); /* We never reach here, but what the hey. */
return 0;
}

54
lib6502-compatibility.txt Normal file
View File

@ -0,0 +1,54 @@
At the time of writing the latest lib6502 release is v1.3; older versions are
not considered here.
Some things which work fine with lib6502 itself are not supported when using
lib6502-jit in hybrid (the default) or compiled execution modes. All of the
following will result in undefined behaviour unless interpreted mode is used:
* Modifying memory which contains 6502 code (whether executed yet or not)
inside a read callback. (All other types of callbacks are allowed to
modify memory freely, including modifying code.)
* Defining a callback after calling M6502_run(); for example, doing so inside
another callback.
* Checking the B and X flags in the processor status register
(M6502_Registers.p) inside a callback. lib6502 tracks these flags as if they
have a real existence at all times. lib6502-jit's compiler only sets them
appropriately when pushing a copy of the processor status register onto the
stack. This difference is *not* visible to code executing on the emulated CPU,
only to callbacks. In hybrid mode, which behaviour you get will depend on
whether your callback is invoked from the interpreter or compiled code.
The following differences exist between lib6502 and lib6502-jit in all modes,
including interpreted mode:
* lib6502 is likely to be slightly faster than lib6502-jit in interpreted mode,
since the latter's interpreter code contains additional tests to stop
executing at certain points after n instructions have been executed.
* Illegal instructions are treated as no-ops by default in lib6502-jit; lib6502
aborts if an illegal instruction is executed.
* Illegal instruction callbacks are a lib6502-jit extension and are not
available in lib6502.
* Call callbacks in lib6502 always receive a 0 as the data argument;
lib6502-jit supplies the opcode triggering the callback as the data argument.
* A few bugs in lib6502's emulation are resolved in lib6502-jit:
- BRK clears the D flag
- ADC/SBC exactly match the behaviour of a real 65C02 in decimal mode
- BIT #imm only modifies the Z flag, leaving N and V untouched
- TSB sets the Z flag correctly
- TRB sets the Z flag and updates memory correctly
* lib6502's run6502 -B option skips every other (ROM name) argument;
lib6502-jit's doesn't.
lib6502-jit's stance is that anything the code executing on the emulated CPU
does is fair game and must be handled, but that the library's client code has a
responsibility to cooperate and not do tricky things like those documented
above. If you have what you think is a reasonable requirement for behaviour
which is supported by lib6502 but doesn't work on lib6502-jit please get in
touch.

190
lib6502-jit.cpp Normal file
View File

@ -0,0 +1,190 @@
/* lib6502-jit.cpp -- MOS Technology 6502 emulator -*- C -*- */
/* Copyright (c) 2005 Ian Piumarta
* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#include "const.h"
#include "Function.h"
#include "FunctionBuilder.h"
#include "FunctionManager.h"
#include "M6502Internal.h"
#include "Registers.h"
#include "util.h"
static void outOfMemory(void)
{
die("out of memory");
}
M6502 *M6502_new(M6502_Registers *registers, M6502_Memory memory, M6502_Callbacks *callbacks)
{
M6502 *mpu= (M6502 *) calloc(1, sizeof(M6502));
if (!mpu) outOfMemory();
if (!registers) { registers = (M6502_Registers *)calloc(1, sizeof(M6502_Registers)); mpu->flags |= M6502_RegistersAllocated; }
if (!memory ) { memory = (uint8_t *)calloc(1, sizeof(M6502_Memory )); mpu->flags |= M6502_MemoryAllocated; }
if (!callbacks) { callbacks = (M6502_Callbacks *)calloc(1, sizeof(M6502_Callbacks)); mpu->flags |= M6502_CallbacksAllocated; }
if (!registers || !memory || !callbacks) outOfMemory();
mpu->registers = registers;
mpu->memory = memory;
mpu->callbacks = callbacks;
try
{
mpu->internal = new _M6502_Internal(mpu);
}
catch (std::exception &e)
{
die(e.what());
}
return mpu;
}
void M6502_delete(M6502 *mpu)
{
if (mpu->flags & M6502_CallbacksAllocated) free(mpu->callbacks);
if (mpu->flags & M6502_MemoryAllocated ) free(mpu->memory);
if (mpu->flags & M6502_RegistersAllocated) free(mpu->registers);
delete mpu->internal;
free(mpu);
}
void M6502_setMode(M6502 *mpu, M6502_Mode mode, int arg)
{
mpu->internal->mode_ = mode;
if (arg == 0)
{
arg = M6502_Internal::default_max_instructions_;
}
mpu->internal->max_instructions_ = arg;
}
extern "C" void M6502_run_interpreted(M6502 *mpu, int instructions_left);
// I don't know if it's "supposed" to work, but it doesn't seem completely
// unreasonable for a lib6502 client to do a setjmp() before invoking
// M6502_run() and have a callback function longjmp() out of the emulation. I
// believe this will work with lib6502 itself, and I would like this emulation
// to do the same. (Note that currently for both lib6502 and lib6502-jit,
// read/write callbacks don't see an up-to-date M6502_Registers object and so
// the setjmp/longjmp trick would result in restarting execution in the wrong
// place with the wrong registers. Call callbacks and illegal instruction
// callbacks should work though.)
//
// To this end, M6502_run_compiled() and M6502_run_hybrid() both update the
// Registers object from the M6502_Registers object on entry to pick up the
// current state. They also both ensure they call update_memory_snapshot() as
// appropriate in case the caller modified memory before invoking M6502_run()
// again.
static void M6502_run_compiled(M6502 *mpu)
{
FunctionManager &function_manager = mpu->internal->function_manager_;
function_manager.update_memory_snapshot();
Registers &registers = mpu->internal->registers_;
registers.from_M6502_Registers(mpu);
while (true)
{
Function *f = function_manager.get_function(registers.pc);
TRACE("Executing Function object for address 0x" << std::hex <<
std::setfill('0') << std::setw(4) << registers.pc);
f->execute();
}
}
#ifdef LOG
static std::string M6502_dump_str(M6502 *mpu)
{
char buffer[64];
M6502_dump(mpu, buffer);
return buffer;
}
#endif
static void M6502_run_hybrid(M6502 *mpu)
{
FunctionManager &function_manager = mpu->internal->function_manager_;
Registers &registers = mpu->internal->registers_;
registers.from_M6502_Registers(mpu);
TRACE("About to interpret, CPU state: " << M6502_dump_str(mpu));
while (true)
{
const int instructions_to_interpret = 100;
M6502_run_interpreted(mpu, instructions_to_interpret);
if (function_manager.jit_thread_idle())
{
TRACE("JIT thread is idle");
registers.from_M6502_Registers(mpu);
function_manager.update_memory_snapshot();
Function *f;
while ((f = function_manager.get_function_lazy(registers.pc)) != 0)
{
TRACE("Executing Function object for address 0x" << std::hex <<
std::setfill('0') << std::setw(4) << registers.pc);
f->execute();
}
TRACE("No Function object available for address 0x" << std::hex <<
std::setfill('0') << std::setw(4) << registers.pc <<
", falling back to interpreter");
registers.to_M6502_Registers(mpu);
TRACE("About to interpret, CPU state: " << M6502_dump_str(mpu));
}
}
}
void M6502_run(M6502 *mpu)
{
try
{
switch (mpu->internal->mode_)
{
case M6502_ModeInterpreted:
while (true)
{
M6502_run_interpreted(mpu, std::numeric_limits<int>::max());
}
break;
case M6502_ModeCompiled:
M6502_run_compiled(mpu);
break;
case M6502_ModeHybrid:
M6502_run_hybrid(mpu);
break;
default:
die("Unknown execution mode in M6502_run()");
}
die("M6502_run() returned!");
}
catch (std::exception &e)
{
die(e.what());
}
}

910
lib6502.c Normal file
View File

@ -0,0 +1,910 @@
/* lib6502.c -- MOS Technology 6502 emulator -*- C -*- */
/* Copyright (c) 2005 Ian Piumarta
* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
/* BUGS:
* - RTS and RTI do not check the return address for a callback
* - the disassembler cannot be configured to read two bytes for BRK
* - architectural variations (unimplemented/extended instructions) not implemented
* - ANSI versions (from from gcc extensions) of the dispatch macros are missing
* - emulator+disassembler in same object file (library is kind of pointless)
*/
#include <stdio.h>
#include <stdlib.h>
#include "lib6502.h"
typedef uint8_t byte;
typedef uint16_t word;
enum {
flagN= (1<<7), /* negative */
flagV= (1<<6), /* overflow */
flagX= (1<<5), /* unused */
flagB= (1<<4), /* irq from brk */
flagD= (1<<3), /* decimal mode */
flagI= (1<<2), /* irq disable */
flagZ= (1<<1), /* zero */
flagC= (1<<0) /* carry */
};
#define getN() (P & flagN)
#define getV() (P & flagV)
#define getB() (P & flagB)
#define getD() (P & flagD)
#define getI() (P & flagI)
#define getZ() (P & flagZ)
#define getC() (P & flagC)
#define setNVZC(N,V,Z,C) (P= (P & ~(flagN | flagV | flagZ | flagC)) | (N) | ((V)<<6) | ((Z)<<1) | (C))
#define setNZC(N,Z,C) (P= (P & ~(flagN | flagZ | flagC)) | (N) | ((Z)<<1) | (C))
#define setNZ(N,Z) (P= (P & ~(flagN | flagZ )) | (N) | ((Z)<<1) )
#define setZ(Z) (P= (P & ~( flagZ )) | ((Z)<<1) )
#define setC(C) (P= (P & ~( flagC)) | (C))
#define NAND(P, Q) (!((P) & (Q)))
#define tick(n)
#define tickIf(p)
/* memory access (indirect if callback installed) -- ARGUMENTS ARE EVALUATED MORE THAN ONCE! */
#define putMemory(ADDR, BYTE) \
( writeCallback[ADDR] \
? writeCallback[ADDR](mpu, ADDR, BYTE) \
: (memory[ADDR]= BYTE) )
#define getMemory(ADDR) \
( readCallback[ADDR] \
? readCallback[ADDR](mpu, ADDR, 0) \
: memory[ADDR] )
/* stack access (always direct) */
#define push(BYTE) (memory[0x0100 + S--]= (BYTE))
#define pop() (memory[++S + 0x0100])
/* adressing modes (memory access direct) */
#define implied(ticks) \
tick(ticks);
#define immediate(ticks) \
tick(ticks); \
ea= PC++;
#define abs(ticks) \
tick(ticks); \
ea= memory[PC] + (memory[PC + 1] << 8); \
PC += 2;
#define relative(ticks) \
tick(ticks); \
ea= memory[PC++]; \
if (ea & 0x80) ea -= 0x100; \
tickIf((ea >> 8) != (PC >> 8));
#define indirect(ticks) \
tick(ticks); \
{ \
word tmp; \
tmp= memory[PC] + (memory[PC + 1] << 8); \
ea = memory[tmp] + (memory[tmp + 1] << 8); \
PC += 2; \
}
#define absx(ticks) \
tick(ticks); \
ea= memory[PC] + (memory[PC + 1] << 8); \
PC += 2; \
tickIf((ticks == 4) && ((ea >> 8) != ((ea + X) >> 8))); \
ea += X;
#define absy(ticks) \
tick(ticks); \
ea= memory[PC] + (memory[PC + 1] << 8); \
PC += 2; \
tickIf((ticks == 4) && ((ea >> 8) != ((ea + Y) >> 8))); \
ea += Y
#define zp(ticks) \
tick(ticks); \
ea= memory[PC++];
#define zpx(ticks) \
tick(ticks); \
ea= memory[PC++] + X; \
ea &= 0x00ff;
#define zpy(ticks) \
tick(ticks); \
ea= memory[PC++] + Y; \
ea &= 0x00ff;
#define indx(ticks) \
tick(ticks); \
{ \
byte tmp= memory[PC++] + X; \
ea= memory[tmp] + (memory[tmp + 1] << 8); \
}
#define indy(ticks) \
tick(ticks); \
{ \
byte tmp= memory[PC++]; \
ea= memory[tmp] + (memory[tmp + 1] << 8); \
tickIf((ticks == 5) && ((ea >> 8) != ((ea + Y) >> 8))); \
ea += Y; \
}
#define indabsx(ticks) \
tick(ticks); \
{ \
word tmp; \
tmp= memory[PC ] + (memory[PC + 1] << 8) + X; \
ea = memory[tmp] + (memory[tmp + 1] << 8); \
}
#define indzp(ticks) \
tick(ticks); \
{ \
byte tmp; \
tmp= memory[PC++]; \
ea = memory[tmp] + (memory[tmp + 1] << 8); \
}
/* insns */
#define adc(ticks, adrmode) \
adrmode(ticks); \
{ \
byte B= getMemory(ea); \
if (!getD()) \
{ \
int c= A + B + getC(); \
int v= (int8_t)A + (int8_t)B + getC(); \
fetch(); \
A= c; \
setNVZC((A & 0x80), (((A & 0x80) > 0) ^ (v < 0)), (A == 0), ((c & 0x100) > 0)); \
next(); \
} \
else \
{ \
/* Algorithm taken from http://www.6502.org/tutorials/decimal_mode.html */ \
/* inelegant & slow, but consistent with the hw for illegal digits */ \
int l, s, t, v; \
l= (A & 0x0F) + (B & 0x0F) + getC(); \
if (l >= 0x0A) { l = ((l + 0x06) & 0x0F) + 0x10; } \
s= (A & 0xF0) + (B & 0xF0) + l; \
t= (int8_t)(A & 0xF0) + (int8_t)(B & 0xF0) + (int8_t)l; \
v= (t < -128) || (t > 127); \
if (s >= 0xA0) { s += 0x60; } \
fetch(); \
A= s; \
/* only C is valid on NMOS 6502 */ \
setNVZC(s & 0x80, v, !A, (s >= 0x100)); \
tick(1); \
next(); \
} \
}
#define sbc(ticks, adrmode) \
adrmode(ticks); \
{ \
byte B= getMemory(ea); \
if (!getD()) \
{ \
int b= 1 - (P &0x01); \
int c= A - B - b; \
int v= (int8_t)A - (int8_t) B - b; \
fetch(); \
A= c; \
setNVZC(A & 0x80, ((A & 0x80) > 0) ^ ((v & 0x100) != 0), A == 0, c >= 0); \
next(); \
} \
else \
{ \
/* Algorithm taken from http://www.6502.org/tutorials/decimal_mode.html */ \
int b= 1 - (P &0x01); \
int l= (A & 0x0F) - (B & 0x0F) - b; \
int s= A - B + getC() - 1; \
int c= !(s & 0x100); \
int v= (int8_t)A - (int8_t) B - b; \
if (s < 0) { s -= 0x60; } \
if (l < 0) { s -= 0x06; } \
fetch(); \
A = s; \
/* only C is valid on NMOS 6502 */ \
setNVZC(s & 0x80, ((v & 0x80) > 0) ^ ((v & 0x100) != 0), !A, c); \
tick(1); \
next(); \
} \
}
#define cmpR(ticks, adrmode, R) \
adrmode(ticks); \
fetch(); \
{ \
byte B= getMemory(ea); \
byte d= R - B; \
setNZC(d & 0x80, !d, R >= B); \
} \
next();
#define cmp(ticks, adrmode) cmpR(ticks, adrmode, A)
#define cpx(ticks, adrmode) cmpR(ticks, adrmode, X)
#define cpy(ticks, adrmode) cmpR(ticks, adrmode, Y)
#define dec(ticks, adrmode) \
adrmode(ticks); \
fetch(); \
{ \
byte B= getMemory(ea); \
--B; \
putMemory(ea, B); \
setNZ(B & 0x80, !B); \
} \
next();
#define decR(ticks, adrmode, R) \
fetch(); \
tick(ticks); \
--R; \
setNZ(R & 0x80, !R); \
next();
#define dea(ticks, adrmode) decR(ticks, adrmode, A)
#define dex(ticks, adrmode) decR(ticks, adrmode, X)
#define dey(ticks, adrmode) decR(ticks, adrmode, Y)
#define inc(ticks, adrmode) \
adrmode(ticks); \
fetch(); \
{ \
byte B= getMemory(ea); \
++B; \
putMemory(ea, B); \
setNZ(B & 0x80, !B); \
} \
next();
#define incR(ticks, adrmode, R) \
fetch(); \
tick(ticks); \
++R; \
setNZ(R & 0x80, !R); \
next();
#define ina(ticks, adrmode) incR(ticks, adrmode, A)
#define inx(ticks, adrmode) incR(ticks, adrmode, X)
#define iny(ticks, adrmode) incR(ticks, adrmode, Y)
#define bit(ticks, adrmode) \
adrmode(ticks); \
fetch(); \
{ \
byte B= getMemory(ea); \
P= (P & ~(flagN | flagV | flagZ)) \
| (B & (0xC0)) | (((A & B) == 0) << 1); \
} \
next();
/* BIT is unique in varying its behaviour based on addressing mode;
* BIT immediate only modifies the Z flag.
* http://6502.org/tutorials/65c02opcodes.html
*/
#define bim(ticks, adrmode) \
adrmode(ticks); \
fetch(); \
{ \
byte B= getMemory(ea); \
setZ((A & B) == 0); \
} \
next();
#define tsb(ticks, adrmode) \
adrmode(ticks); \
fetch(); \
{ \
byte b= getMemory(ea); \
setZ(!(b & A)); \
b |= A; \
putMemory(ea, b); \
} \
next();
#define trb(ticks, adrmode) \
adrmode(ticks); \
fetch(); \
{ \
byte b= getMemory(ea); \
setZ(!(b & A)); \
b &= (A ^ 0xFF); \
putMemory(ea, b); \
} \
next();
#define bitwise(ticks, adrmode, op) \
adrmode(ticks); \
fetch(); \
A op##= getMemory(ea); \
setNZ(A & 0x80, !A); \
next();
#define and(ticks, adrmode) bitwise(ticks, adrmode, &)
#define eor(ticks, adrmode) bitwise(ticks, adrmode, ^)
#define ora(ticks, adrmode) bitwise(ticks, adrmode, |)
#define asl(ticks, adrmode) \
adrmode(ticks); \
{ \
unsigned int i= getMemory(ea) << 1; \
putMemory(ea, i); \
fetch(); \
setNZC(i & 0x80, !i, i >> 8); \
} \
next();
#define asla(ticks, adrmode) \
tick(ticks); \
fetch(); \
{ \
int c= A >> 7; \
A <<= 1; \
setNZC(A & 0x80, !A, c); \
} \
next();
#define lsr(ticks, adrmode) \
adrmode(ticks); \
{ \
byte b= getMemory(ea); \
int c= b & 1; \
fetch(); \
b >>= 1; \
putMemory(ea, b); \
setNZC(0, !b, c); \
} \
next();
#define lsra(ticks, adrmode) \
tick(ticks); \
fetch(); \
{ \
int c= A & 1; \
A >>= 1; \
setNZC(0, !A, c); \
} \
next();
#define rol(ticks, adrmode) \
adrmode(ticks); \
{ \
word b= (getMemory(ea) << 1) | getC(); \
fetch(); \
putMemory(ea, b); \
setNZC(b & 0x80, !(b & 0xFF), b >> 8); \
} \
next();
#define rola(ticks, adrmode) \
tick(ticks); \
fetch(); \
{ \
word b= (A << 1) | getC(); \
A= b; \
setNZC(A & 0x80, !A, b >> 8); \
} \
next();
#define ror(ticks, adrmode) \
adrmode(ticks); \
{ \
int c= getC(); \
byte m= getMemory(ea); \
byte b= (c << 7) | (m >> 1); \
fetch(); \
putMemory(ea, b); \
setNZC(b & 0x80, !b, m & 1); \
} \
next();
#define rora(ticks, adrmode) \
adrmode(ticks); \
{ \
int ci= getC(); \
int co= A & 1; \
fetch(); \
A= (ci << 7) | (A >> 1); \
setNZC(A & 0x80, !A, co); \
} \
next();
#define tRS(ticks, adrmode, R, S) \
fetch(); \
tick(ticks); \
S= R; \
setNZ(S & 0x80, !S); \
next();
#define tax(ticks, adrmode) tRS(ticks, adrmode, A, X)
#define txa(ticks, adrmode) tRS(ticks, adrmode, X, A)
#define tay(ticks, adrmode) tRS(ticks, adrmode, A, Y)
#define tya(ticks, adrmode) tRS(ticks, adrmode, Y, A)
#define tsx(ticks, adrmode) tRS(ticks, adrmode, S, X)
#define txs(ticks, adrmode) \
fetch(); \
tick(ticks); \
S= X; \
next();
#define ldR(ticks, adrmode, R) \
adrmode(ticks); \
fetch(); \
R= getMemory(ea); \
setNZ(R & 0x80, !R); \
next();
#define lda(ticks, adrmode) ldR(ticks, adrmode, A)
#define ldx(ticks, adrmode) ldR(ticks, adrmode, X)
#define ldy(ticks, adrmode) ldR(ticks, adrmode, Y)
#define stR(ticks, adrmode, R) \
adrmode(ticks); \
fetch(); \
putMemory(ea, R); \
next();
#define sta(ticks, adrmode) stR(ticks, adrmode, A)
#define stx(ticks, adrmode) stR(ticks, adrmode, X)
#define sty(ticks, adrmode) stR(ticks, adrmode, Y)
#define stz(ticks, adrmode) stR(ticks, adrmode, 0)
/* We only set keep_running to false if we branch; this is just
* an attempt to pick points to JIT at which we have a chance of
* hitting a second time.
*/
#define branch(ticks, adrmode, cond) \
if (cond) \
{ \
adrmode(ticks); \
PC += ea; \
tick(1); \
keep_running= (instructions_left > 0); \
} \
else \
{ \
tick(ticks); \
PC++; \
} \
fetch(); \
next();
#define bcc(ticks, adrmode) branch(ticks, adrmode, !getC())
#define bcs(ticks, adrmode) branch(ticks, adrmode, getC())
#define bne(ticks, adrmode) branch(ticks, adrmode, !getZ())
#define beq(ticks, adrmode) branch(ticks, adrmode, getZ())
#define bpl(ticks, adrmode) branch(ticks, adrmode, !getN())
#define bmi(ticks, adrmode) branch(ticks, adrmode, getN())
#define bvc(ticks, adrmode) branch(ticks, adrmode, !getV())
#define bvs(ticks, adrmode) branch(ticks, adrmode, getV())
#define bra(ticks, adrmode) \
adrmode(ticks); \
PC += ea; \
keep_running= (instructions_left > 0); \
fetch(); \
tick(1); \
next();
#define jmp(ticks, adrmode) \
{ \
adrmode(ticks); \
byte opcode= mpu->memory[PC-3]; \
PC= ea; \
if (mpu->callbacks->call[ea]) \
{ \
word addr; \
externalise(); \
if ((addr= mpu->callbacks->call[ea](mpu, ea, opcode)))\
{ \
internalise(); \
PC= addr; \
} \
} \
keep_running= (instructions_left > 0); \
fetch(); \
next(); \
}
#define jsr(ticks, adrmode) \
PC++; \
push(PC >> 8); \
push(PC & 0xff); \
PC--; \
adrmode(ticks); \
if (mpu->callbacks->call[ea]) \
{ \
word addr; \
externalise(); \
if ((addr= mpu->callbacks->call[ea](mpu, ea, 0x20))) \
{ \
internalise(); \
PC= addr; \
keep_running= (instructions_left > 0); \
fetch(); \
next(); \
} \
} \
PC=ea; \
keep_running= (instructions_left > 0); \
fetch(); \
next();
#define rts(ticks, adrmode) \
tick(ticks); \
PC = pop(); \
PC |= (pop() << 8); \
PC++; \
keep_running= (instructions_left > 0); \
fetch(); \
next();
#define brk(ticks, adrmode) \
tick(ticks); \
PC++; \
push(PC >> 8); \
push(PC & 0xff); \
P |= flagB; \
/* http://www.6502.org/tutorials/65c02opcodes.html - unlike
* the 6502, the 65C02 clears D on BRK.
*/ \
P &= ~flagD; \
push(P | flagX); \
P |= flagI; \
{ \
word hdlr= getMemory(0xfffe) + (getMemory(0xffff) << 8); \
if (mpu->callbacks->call[hdlr]) \
{ \
word addr; \
externalise(); \
if ((addr= mpu->callbacks->call[hdlr](mpu, PC - 2, 0))) \
{ \
internalise(); \
hdlr= addr; \
} \
} \
PC= hdlr; \
} \
keep_running= (instructions_left > 0); \
fetch(); \
next();
#define rti(ticks, adrmode) \
tick(ticks); \
P= pop(); \
PC= pop(); \
PC |= (pop() << 8); \
keep_running= (instructions_left > 0); \
fetch(); \
next();
#define nop(ticks, adrmode) \
fetch(); \
tick(ticks); \
next();
#define ill(ticks, adrmode) \
{ \
word addr= PC-1; \
byte instruction= memory[addr]; \
tick(ticks); \
if (mpu->callbacks->illegal_instruction[instruction]) \
{ \
adrmode(ticks); \
externalise(); \
if (addr= (mpu->callbacks->illegal_instruction[instruction](mpu, addr, \
instruction))) \
{ \
mpu->registers->pc= addr; \
} \
internalise(); \
fetch(); \
next(); \
} \
else \
{ \
adrmode(ticks); \
fetch(); \
next(); \
} \
};
#define phR(ticks, adrmode, R) \
fetch(); \
tick(ticks); \
push(R); \
next();
#define pha(ticks, adrmode) phR(ticks, adrmode, A)
#define phx(ticks, adrmode) phR(ticks, adrmode, X)
#define phy(ticks, adrmode) phR(ticks, adrmode, Y)
#define php(ticks, adrmode) phR(ticks, adrmode, P | flagX | flagB)
#define plR(ticks, adrmode, R) \
fetch(); \
tick(ticks); \
R= pop(); \
setNZ(R & 0x80, !R); \
next();
#define pla(ticks, adrmode) plR(ticks, adrmode, A)
#define plx(ticks, adrmode) plR(ticks, adrmode, X)
#define ply(ticks, adrmode) plR(ticks, adrmode, Y)
#define plp(ticks, adrmode) \
fetch(); \
tick(ticks); \
P= pop(); \
next();
#define clF(ticks, adrmode, F) \
fetch(); \
tick(ticks); \
P &= ~F; \
next();
#define clc(ticks, adrmode) clF(ticks, adrmode, flagC)
#define cld(ticks, adrmode) clF(ticks, adrmode, flagD)
#define cli(ticks, adrmode) clF(ticks, adrmode, flagI)
#define clv(ticks, adrmode) clF(ticks, adrmode, flagV)
#define seF(ticks, adrmode, F) \
fetch(); \
tick(ticks); \
P |= F; \
next();
#define sec(ticks, adrmode) seF(ticks, adrmode, flagC)
#define sed(ticks, adrmode) seF(ticks, adrmode, flagD)
#define sei(ticks, adrmode) seF(ticks, adrmode, flagI)
#define do_insns(_) \
_(00, brk, implied, 7); _(01, ora, indx, 6); _(02, ill, zp, 2); _(03, ill, implied, 2); \
_(04, tsb, zp, 3); _(05, ora, zp, 3); _(06, asl, zp, 5); _(07, ill, implied, 2); \
_(08, php, implied, 3); _(09, ora, immediate, 3); _(0a, asla,implied, 2); _(0b, ill, implied, 2); \
_(0c, tsb, abs, 4); _(0d, ora, abs, 4); _(0e, asl, abs, 6); _(0f, ill, implied, 2); \
_(10, bpl, relative, 2); _(11, ora, indy, 5); _(12, ora, indzp, 3); _(13, ill, implied, 2); \
_(14, trb, zp, 3); _(15, ora, zpx, 4); _(16, asl, zpx, 6); _(17, ill, implied, 2); \
_(18, clc, implied, 2); _(19, ora, absy, 4); _(1a, ina, implied, 2); _(1b, ill, implied, 2); \
_(1c, trb, abs, 4); _(1d, ora, absx, 4); _(1e, asl, absx, 7); _(1f, ill, implied, 2); \
_(20, jsr, abs, 6); _(21, and, indx, 6); _(22, ill, zp, 2); _(23, ill, implied, 2); \
_(24, bit, zp, 3); _(25, and, zp, 3); _(26, rol, zp, 5); _(27, ill, implied, 2); \
_(28, plp, implied, 4); _(29, and, immediate, 3); _(2a, rola,implied, 2); _(2b, ill, implied, 2); \
_(2c, bit, abs, 4); _(2d, and, abs, 4); _(2e, rol, abs, 6); _(2f, ill, implied, 2); \
_(30, bmi, relative, 2); _(31, and, indy, 5); _(32, and, indzp, 3); _(33, ill, implied, 2); \
_(34, bit, zpx, 4); _(35, and, zpx, 4); _(36, rol, zpx, 6); _(37, ill, implied, 2); \
_(38, sec, implied, 2); _(39, and, absy, 4); _(3a, dea, implied, 2); _(3b, ill, implied, 2); \
_(3c, bit, absx, 4); _(3d, and, absx, 4); _(3e, rol, absx, 7); _(3f, ill, implied, 2); \
_(40, rti, implied, 6); _(41, eor, indx, 6); _(42, ill, zp, 2); _(43, ill, implied, 2); \
_(44, ill, zp, 3); _(45, eor, zp, 3); _(46, lsr, zp, 5); _(47, ill, implied, 2); \
_(48, pha, implied, 3); _(49, eor, immediate, 3); _(4a, lsra,implied, 2); _(4b, ill, implied, 2); \
_(4c, jmp, abs, 3); _(4d, eor, abs, 4); _(4e, lsr, abs, 6); _(4f, ill, implied, 2); \
_(50, bvc, relative, 2); _(51, eor, indy, 5); _(52, eor, indzp, 3); _(53, ill, implied, 2); \
_(54, ill, zp, 4); _(55, eor, zpx, 4); _(56, lsr, zpx, 6); _(57, ill, implied, 2); \
_(58, cli, implied, 2); _(59, eor, absy, 4); _(5a, phy, implied, 3); _(5b, ill, implied, 2); \
_(5c, ill, abs, 8); _(5d, eor, absx, 4); _(5e, lsr, absx, 7); _(5f, ill, implied, 2); \
_(60, rts, implied, 6); _(61, adc, indx, 6); _(62, ill, zp, 2); _(63, ill, implied, 2); \
_(64, stz, zp, 3); _(65, adc, zp, 3); _(66, ror, zp, 5); _(67, ill, implied, 2); \
_(68, pla, implied, 4); _(69, adc, immediate, 3); _(6a, rora,implied, 2); _(6b, ill, implied, 2); \
_(6c, jmp, indirect, 5); _(6d, adc, abs, 4); _(6e, ror, abs, 6); _(6f, ill, implied, 2); \
_(70, bvs, relative, 2); _(71, adc, indy, 5); _(72, adc, indzp, 3); _(73, ill, implied, 2); \
_(74, stz, zpx, 4); _(75, adc, zpx, 4); _(76, ror, zpx, 6); _(77, ill, implied, 2); \
_(78, sei, implied, 2); _(79, adc, absy, 4); _(7a, ply, implied, 4); _(7b, ill, implied, 2); \
_(7c, jmp, indabsx, 6); _(7d, adc, absx, 4); _(7e, ror, absx, 7); _(7f, ill, implied, 2); \
_(80, bra, relative, 2); _(81, sta, indx, 6); _(82, ill, zp, 2); _(83, ill, implied, 2); \
_(84, sty, zp, 2); _(85, sta, zp, 2); _(86, stx, zp, 2); _(87, ill, implied, 2); \
_(88, dey, implied, 2); _(89, bim, immediate, 2); _(8a, txa, implied, 2); _(8b, ill, implied, 2); \
_(8c, sty, abs, 4); _(8d, sta, abs, 4); _(8e, stx, abs, 4); _(8f, ill, implied, 2); \
_(90, bcc, relative, 2); _(91, sta, indy, 6); _(92, sta, indzp, 3); _(93, ill, implied, 2); \
_(94, sty, zpx, 4); _(95, sta, zpx, 4); _(96, stx, zpy, 4); _(97, ill, implied, 2); \
_(98, tya, implied, 2); _(99, sta, absy, 5); _(9a, txs, implied, 2); _(9b, ill, implied, 2); \
_(9c, stz, abs, 4); _(9d, sta, absx, 5); _(9e, stz, absx, 5); _(9f, ill, implied, 2); \
_(a0, ldy, immediate, 3); _(a1, lda, indx, 6); _(a2, ldx, immediate, 3); _(a3, ill, implied, 2); \
_(a4, ldy, zp, 3); _(a5, lda, zp, 3); _(a6, ldx, zp, 3); _(a7, ill, implied, 2); \
_(a8, tay, implied, 2); _(a9, lda, immediate, 3); _(aa, tax, implied, 2); _(ab, ill, implied, 2); \
_(ac, ldy, abs, 4); _(ad, lda, abs, 4); _(ae, ldx, abs, 4); _(af, ill, implied, 2); \
_(b0, bcs, relative, 2); _(b1, lda, indy, 5); _(b2, lda, indzp, 3); _(b3, ill, implied, 2); \
_(b4, ldy, zpx, 4); _(b5, lda, zpx, 4); _(b6, ldx, zpy, 4); _(b7, ill, implied, 2); \
_(b8, clv, implied, 2); _(b9, lda, absy, 4); _(ba, tsx, implied, 2); _(bb, ill, implied, 2); \
_(bc, ldy, absx, 4); _(bd, lda, absx, 4); _(be, ldx, absy, 4); _(bf, ill, implied, 2); \
_(c0, cpy, immediate, 3); _(c1, cmp, indx, 6); _(c2, ill, zp, 2); _(c3, ill, implied, 2); \
_(c4, cpy, zp, 3); _(c5, cmp, zp, 3); _(c6, dec, zp, 5); _(c7, ill, implied, 2); \
_(c8, iny, implied, 2); _(c9, cmp, immediate, 3); _(ca, dex, implied, 2); _(cb, ill, implied, 2); \
_(cc, cpy, abs, 4); _(cd, cmp, abs, 4); _(ce, dec, abs, 6); _(cf, ill, implied, 2); \
_(d0, bne, relative, 2); _(d1, cmp, indy, 5); _(d2, cmp, indzp, 3); _(d3, ill, implied, 2); \
_(d4, ill, zp, 4); _(d5, cmp, zpx, 4); _(d6, dec, zpx, 6); _(d7, ill, implied, 2); \
_(d8, cld, implied, 2); _(d9, cmp, absy, 4); _(da, phx, implied, 3); _(db, ill, implied, 2); \
_(dc, ill, abs, 4); _(dd, cmp, absx, 4); _(de, dec, absx, 7); _(df, ill, implied, 2); \
_(e0, cpx, immediate, 3); _(e1, sbc, indx, 6); _(e2, ill, zp, 2); _(e3, ill, implied, 2); \
_(e4, cpx, zp, 3); _(e5, sbc, zp, 3); _(e6, inc, zp, 5); _(e7, ill, implied, 2); \
_(e8, inx, implied, 2); _(e9, sbc, immediate, 3); _(ea, nop, implied, 2); _(eb, ill, implied, 2); \
_(ec, cpx, abs, 4); _(ed, sbc, abs, 4); _(ee, inc, abs, 6); _(ef, ill, implied, 2); \
_(f0, beq, relative, 2); _(f1, sbc, indy, 5); _(f2, sbc, indzp, 3); _(f3, ill, implied, 2); \
_(f4, ill, zp, 4); _(f5, sbc, zpx, 4); _(f6, inc, zpx, 6); _(f7, ill, implied, 2); \
_(f8, sed, implied, 2); _(f9, sbc, absy, 4); _(fa, plx, implied, 4); _(fb, ill, implied, 2); \
_(fc, ill, abs, 4); _(fd, sbc, absx, 4); _(fe, inc, absx, 7); _(ff, ill, implied, 2);
void M6502_irq(M6502 *mpu)
{
if (!(mpu->registers->p & flagI))
{
mpu->memory[0x0100 + mpu->registers->s--] = (byte)(mpu->registers->pc >> 8);
mpu->memory[0x0100 + mpu->registers->s--] = (byte)(mpu->registers->pc & 0xff);
mpu->memory[0x0100 + mpu->registers->s--] = mpu->registers->p;
mpu->registers->p &= ~flagB;
mpu->registers->p |= flagI;
mpu->registers->pc = M6502_getVector(mpu, IRQ);
}
}
void M6502_nmi(M6502 *mpu)
{
mpu->memory[0x0100 + mpu->registers->s--] = (byte)(mpu->registers->pc >> 8);
mpu->memory[0x0100 + mpu->registers->s--] = (byte)(mpu->registers->pc & 0xff);
mpu->memory[0x0100 + mpu->registers->s--] = mpu->registers->p;
mpu->registers->p &= ~flagB;
mpu->registers->p |= flagI;
mpu->registers->pc = M6502_getVector(mpu, NMI);
}
void M6502_reset(M6502 *mpu)
{
mpu->registers->p &= ~flagD;
mpu->registers->p |= flagI;
mpu->registers->pc = M6502_getVector(mpu, RST);
}
/* the compiler should elminate all call to this function */
static void oops(void)
{
fprintf(stderr, "\noops -- instruction dispatch missing\n");
}
void M6502_run_interpreted(M6502 *mpu, int instructions_left)
{
int keep_running= 1;
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
static void *itab[256]= { &&_00, &&_01, &&_02, &&_03, &&_04, &&_05, &&_06, &&_07, &&_08, &&_09, &&_0a, &&_0b, &&_0c, &&_0d, &&_0e, &&_0f,
&&_10, &&_11, &&_12, &&_13, &&_14, &&_15, &&_16, &&_17, &&_18, &&_19, &&_1a, &&_1b, &&_1c, &&_1d, &&_1e, &&_1f,
&&_20, &&_21, &&_22, &&_23, &&_24, &&_25, &&_26, &&_27, &&_28, &&_29, &&_2a, &&_2b, &&_2c, &&_2d, &&_2e, &&_2f,
&&_30, &&_31, &&_32, &&_33, &&_34, &&_35, &&_36, &&_37, &&_38, &&_39, &&_3a, &&_3b, &&_3c, &&_3d, &&_3e, &&_3f,
&&_40, &&_41, &&_42, &&_43, &&_44, &&_45, &&_46, &&_47, &&_48, &&_49, &&_4a, &&_4b, &&_4c, &&_4d, &&_4e, &&_4f,
&&_50, &&_51, &&_52, &&_53, &&_54, &&_55, &&_56, &&_57, &&_58, &&_59, &&_5a, &&_5b, &&_5c, &&_5d, &&_5e, &&_5f,
&&_60, &&_61, &&_62, &&_63, &&_64, &&_65, &&_66, &&_67, &&_68, &&_69, &&_6a, &&_6b, &&_6c, &&_6d, &&_6e, &&_6f,
&&_70, &&_71, &&_72, &&_73, &&_74, &&_75, &&_76, &&_77, &&_78, &&_79, &&_7a, &&_7b, &&_7c, &&_7d, &&_7e, &&_7f,
&&_80, &&_81, &&_82, &&_83, &&_84, &&_85, &&_86, &&_87, &&_88, &&_89, &&_8a, &&_8b, &&_8c, &&_8d, &&_8e, &&_8f,
&&_90, &&_91, &&_92, &&_93, &&_94, &&_95, &&_96, &&_97, &&_98, &&_99, &&_9a, &&_9b, &&_9c, &&_9d, &&_9e, &&_9f,
&&_a0, &&_a1, &&_a2, &&_a3, &&_a4, &&_a5, &&_a6, &&_a7, &&_a8, &&_a9, &&_aa, &&_ab, &&_ac, &&_ad, &&_ae, &&_af,
&&_b0, &&_b1, &&_b2, &&_b3, &&_b4, &&_b5, &&_b6, &&_b7, &&_b8, &&_b9, &&_ba, &&_bb, &&_bc, &&_bd, &&_be, &&_bf,
&&_c0, &&_c1, &&_c2, &&_c3, &&_c4, &&_c5, &&_c6, &&_c7, &&_c8, &&_c9, &&_ca, &&_cb, &&_cc, &&_cd, &&_ce, &&_cf,
&&_d0, &&_d1, &&_d2, &&_d3, &&_d4, &&_d5, &&_d6, &&_d7, &&_d8, &&_d9, &&_da, &&_db, &&_dc, &&_dd, &&_de, &&_df,
&&_e0, &&_e1, &&_e2, &&_e3, &&_e4, &&_e5, &&_e6, &&_e7, &&_e8, &&_e9, &&_ea, &&_eb, &&_ec, &&_ed, &&_ee, &&_ef,
&&_f0, &&_f1, &&_f2, &&_f3, &&_f4, &&_f5, &&_f6, &&_f7, &&_f8, &&_f9, &&_fa, &&_fb, &&_fc, &&_fd, &&_fe, &&_ff };
register void **itabp= &itab[0];
register void *tpc;
# define begin() ++instructions_left; fetch(); next()
# define fetch() tpc= itabp[memory[PC++]]
# define next() --instructions_left; if (keep_running) goto *tpc; else goto done
# define dispatch(num, name, mode, cycles) _##num: name(cycles, mode) oops(); next()
# define end() done: --PC
#else /* (!__GNUC__) || (__STRICT_ANSI__) */
# define begin() for (;keep_running;--instructions_left) switch (memory[PC++]) {
# define fetch()
# define next() break
# define dispatch(num, name, mode, cycles) case 0x##num: name(cycles, mode); next()
# define end() }
#endif
register byte *memory= mpu->memory;
register word PC;
word ea;
byte A, X, Y, P, S;
M6502_Callback *readCallback= mpu->callbacks->read;
M6502_Callback *writeCallback= mpu->callbacks->write;
# define internalise() A= mpu->registers->a; X= mpu->registers->x; Y= mpu->registers->y; P= mpu->registers->p; S= mpu->registers->s; PC= mpu->registers->pc
# define externalise() mpu->registers->a= A; mpu->registers->x= X; mpu->registers->y= Y; mpu->registers->p= P; mpu->registers->s= S; mpu->registers->pc= PC
internalise();
begin();
do_insns(dispatch);
end();
externalise();
# undef begin
# undef internalise
# undef externalise
# undef fetch
# undef next
# undef dispatch
# undef end
}
int M6502_disassemble(M6502 *mpu, word ip, char buffer[64])
{
char *s= buffer;
byte *b= mpu->memory + ip;
switch (b[0])
{
# define _implied return 1;
# define _immediate sprintf(s, "#%02X", b[1]); return 2;
# define _zp sprintf(s, "%02X", b[1]); return 2;
# define _zpx sprintf(s, "%02X,X", b[1]); return 2;
# define _zpy sprintf(s, "%02X,Y", b[1]); return 2;
# define _abs sprintf(s, "%02X%02X", b[2], b[1]); return 3;
# define _absx sprintf(s, "%02X%02X,X", b[2], b[1]); return 3;
# define _absy sprintf(s, "%02X%02X,Y", b[2], b[1]); return 3;
# define _relative sprintf(s, "%04X", ip + 2 + (int8_t)b[1]); return 2;
# define _indirect sprintf(s, "(%02X%02X)", b[2], b[1]); return 3;
# define _indzp sprintf(s, "(%02X)", b[1]); return 2;
# define _indx sprintf(s, "(%02X,X)", b[1]); return 2;
# define _indy sprintf(s, "(%02X),Y", b[1]); return 2;
# define _indabsx sprintf(s, "(%02X%02X,X)", b[2], b[1]); return 3;
# define disassemble(num, name, mode, cycles) case 0x##num: s += sprintf(s, "%s ", #name); _##mode
do_insns(disassemble);
# undef _do
}
return 0;
}
void M6502_dump(M6502 *mpu, char buffer[64])
{
M6502_Registers *r= mpu->registers;
uint8_t p= r->p;
# define P(N,C) (p & (1 << (N)) ? (C) : '-')
sprintf(buffer, "PC=%04X SP=%04X A=%02X X=%02X Y=%02X P=%02X %c%c%c%c%c%c%c%c",
r->pc, 0x0100 + r->s,
r->a, r->x, r->y, r->p,
P(7,'N'), P(6,'V'), P(5,'?'), P(4,'B'), P(3,'D'), P(2,'I'), P(1,'Z'), P(0,'C'));
# undef P
}

120
lib6502.h Normal file
View File

@ -0,0 +1,120 @@
/* lib6502.h -- MOS Technology 6502 emulator -*- C -*- */
/* Copyright (c) 2005 Ian Piumarta
* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#ifndef __m6502_h
#define __m6502_h
#include <stdio.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C"
{
#endif
typedef struct _M6502 M6502;
typedef struct _M6502_Registers M6502_Registers;
typedef struct _M6502_Callbacks M6502_Callbacks;
typedef struct _M6502_Internal M6502_Internal;
typedef int (*M6502_Callback)(M6502 *mpu, uint16_t address, uint8_t data);
typedef M6502_Callback M6502_CallbackTable[0x10000];
typedef M6502_Callback M6502_IllegalInstructionCallbackTable[0x100];
typedef uint8_t M6502_Memory[0x10000];
enum {
M6502_NMIVector= 0xfffa, M6502_NMIVectorLSB= 0xfffa, M6502_NMIVectorMSB= 0xfffb,
M6502_RSTVector= 0xfffc, M6502_RSTVectorLSB= 0xfffc, M6502_RSTVectorMSB= 0xfffd,
M6502_IRQVector= 0xfffe, M6502_IRQVectorLSB= 0xfffe, M6502_IRQVectorMSB= 0xffff
};
struct _M6502_Registers
{
uint8_t a; /* accumulator */
uint8_t x; /* X index register */
uint8_t y; /* Y index register */
uint8_t p; /* processor status register */
uint8_t s; /* stack pointer */
uint16_t pc; /* program counter */
};
struct _M6502_Callbacks
{
M6502_CallbackTable read;
M6502_CallbackTable write;
M6502_CallbackTable call;
M6502_IllegalInstructionCallbackTable illegal_instruction;
};
struct _M6502_Internal;
struct _M6502
{
M6502_Registers *registers;
uint8_t *memory;
M6502_Callbacks *callbacks;
unsigned int flags;
/* The following is implementation-specific; client code should only use the
* above members.
*/
M6502_Internal *internal;
};
enum {
M6502_RegistersAllocated = 1 << 0,
M6502_MemoryAllocated = 1 << 1,
M6502_CallbacksAllocated = 1 << 2
};
typedef enum {
M6502_ModeInterpreted,
M6502_ModeCompiled,
M6502_ModeHybrid
} M6502_Mode;
extern M6502 *M6502_new(M6502_Registers *registers, M6502_Memory memory, M6502_Callbacks *callbacks);
extern void M6502_reset(M6502 *mpu);
extern void M6502_nmi(M6502 *mpu);
extern void M6502_irq(M6502 *mpu);
extern void M6502_run(M6502 *mpu);
extern int M6502_disassemble(M6502 *mpu, uint16_t addr, char buffer[64]);
extern void M6502_dump(M6502 *mpu, char buffer[64]);
extern void M6502_delete(M6502 *mpu);
extern void M6502_setMode(M6502 *mpu, M6502_Mode mode, int arg);
#define M6502_getVector(MPU, VEC) \
( ( ((MPU)->memory[M6502_##VEC##VectorLSB]) ) \
| ((MPU)->memory[M6502_##VEC##VectorMSB] << 8) )
#define M6502_setVector(MPU, VEC, ADDR) \
( ( ((MPU)->memory[M6502_##VEC##VectorLSB]= ((uint8_t)(ADDR)) & 0xff) ) \
, ((MPU)->memory[M6502_##VEC##VectorMSB]= (uint8_t)((ADDR) >> 8)) )
#define M6502_getCallback(MPU, TYPE, ADDR) ((MPU)->callbacks->TYPE[ADDR])
#define M6502_setCallback(MPU, TYPE, ADDR, FN) ((MPU)->callbacks->TYPE[ADDR]= (FN))
#ifdef __cplusplus
}
#endif
#endif /* __m6502_h */

1338
m4/boost.m4 Normal file

File diff suppressed because it is too large Load Diff

1
man/M6502_delete.3 Normal file
View File

@ -0,0 +1 @@
.so man3/lib6502.3

1
man/M6502_disassemble.3 Normal file
View File

@ -0,0 +1 @@
.so man3/lib6502.3

1
man/M6502_dump.3 Normal file
View File

@ -0,0 +1 @@
.so man3/lib6502.3

1
man/M6502_getCallback.3 Normal file
View File

@ -0,0 +1 @@
.so man3/lib6502.3

1
man/M6502_getVector.3 Normal file
View File

@ -0,0 +1 @@
.so man3/lib6502.3

1
man/M6502_irq.3 Normal file
View File

@ -0,0 +1 @@
.so man3/lib6502.3

1
man/M6502_new.3 Normal file
View File

@ -0,0 +1 @@
.so man3/lib6502.3

1
man/M6502_nmi.3 Normal file
View File

@ -0,0 +1 @@
.so man3/lib6502.3

1
man/M6502_reset.3 Normal file
View File

@ -0,0 +1 @@
.so man3/lib6502.3

1
man/M6502_run.3 Normal file
View File

@ -0,0 +1 @@
.so man3/lib6502.3

1
man/M6502_setCallback.3 Normal file
View File

@ -0,0 +1 @@
.so man3/lib6502.3

1
man/M6502_setMode.3 Normal file
View File

@ -0,0 +1 @@
.so man3/lib6502.3

1
man/M6502_setVector.3 Normal file
View File

@ -0,0 +1 @@
.so man3/lib6502.3

555
man/lib6502.3 Normal file
View File

@ -0,0 +1,555 @@
.\" Copyright (c) 2005 Ian Piumarta
.\" Copyright (c) 2014 Steven Flintham
.\"
.\" Permission is hereby granted, free of charge, to any person
.\" obtaining a copy of this software and associated documentation
.\" files (the 'Software'), to deal in the Software without
.\" restriction, including without limitation the rights to use, copy,
.\" modify, merge, publish, distribute, and/or sell copies of the
.\" Software, and to permit persons to whom the Software is furnished
.\" to do so, provided that the above copyright notice(s) and this
.\" permission notice appear in all copies of the Software and that
.\" both the above copyright notice(s) and this permission notice
.\" appear in supporting documentation.
.\"
.\" THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
.\"
.Dd June 7, 2014
.Dt LIB6502 3 LOCAL
.Os ""
.\" ----------------------------------------------------------------
.Sh NAME
.\"
.Nm lib6502
.Nd 6502 microprocessor emulator
.\" ----------------------------------------------------------------
.Sh SYNOPSIS
.\"
.In stdint.h
.In lib6502.h
.Ft M6502 *
.Fn M6502_new "M6502_Registers *registers" "M6502_Memory memory" "M6502_Callbacks *callbacks"
.Ft void
.Fn M6502_reset "M6502 *mpu"
.Ft void
.Fn M6502_nmi "M6502 *mpu"
.Ft void
.Fn M6502_irq "M6502 *mpu"
.Ft uint16_t
.Fn M6502_getVector "M6502 *mpu" "vector"
.Ft uint16_t
.Fn M6502_setVector "M6502 *mpu" "vector" "uint16_t address"
.Ft M6502_Callback
.Fn M6502_getCallback "M6502 *mpu" "type" "uint16_t address"
.Ft M6502_Callback
.Fn M6502_setCallback "M6502 *mpu" "type" "uint16_t address" "M6502_Callback callback"
.Ft void
.Fn M6502_run "M6502 *mpu"
.Ft int
.Fn M6502_disassemble "M6502 *mpu" "uint16_t address" "char buffer[64]"
.Ft void
.Fn M6502_dump "M6502 *mpu" "char buffer[64]"
.Ft void
.Fn M6502_delete "M6502 *mpu"
.Ft void
.Fn M6502_setMode "M6502 *mpu" "M6502_Mode mode" "int arg"
.\" ----------------------------------------------------------------
.Sh DESCRIPTION
.\"
.Fn M6502_new
creates an instance of a 6502 microprocessor.
.Fn M6502_reset ,
.Fn M6502_nmi
and
.Fn M6502_irq
place it into the states associated with the hardware signals for
reset, non-maskable interrupt and interrupt request, respectively.
The macros
.Fn M6502_getVector
and
.Fn M6502_setVector
read and write the vectors through which the processor jumps in
response to the above signals. The macros
.Fn M6502_getCallback
and
.Fn M6502_setVector
read and write client-supplied functions that intercept accesses to
memory.
.Fn M6502_run
begins emulated execution.
.Fn M6502_dump
and
.Fn M6502_disassemble
create human-readable representations of processor or memory state.
.Fn M6502_delete
frees all resources associated with a processor instance.
.Fn M6502_setMode
specifies the emulation mode to use for a processor instance. Each of
these functions and macros is described in more detail below.
.Pp
.Fn M6502_new
returns a pointer to a
.Fa M6502
structure containing at least the following members:
.Bd -literal
struct _M6502
{
M6502_Registers *registers; /* processor state */
uint8_t *memory; /* memory image */
M6502_Callbacks *callbacks; /* r/w/x/illegal callbacks */
};
.Ed
.Pp
These members are initialised according to the supplied
.Fa registers ,
.Fa memory
and
.Fa callbacks
arguments. If a given argument is NULL, the corresponding member is
initialised automatically with a suitable (non-NULL) value.
.Pp
The members of
.Fa M6502
are as follows:
.Bl -tag -width ".Fa callbacks"
.It Fa registers
the processor state, containing all registers and condition codes.
.It Fa memory
a block of at least 64 kilobytes of storage containing the processor's
memory. (An array type
.Vt M6502_Memory,
suitable for defining values to pass as the
.Fa memory
argument, is defined in the
.In lib6502.h
include file.)
.It Fa callbacks
a structure mapping processor memory accesses to client callback
functions.
.El
.Pp
Access to the contents of the
.Fa registers
and
.Fa memory
members can be made directly.
The
.Fa registers
member is a
.Vt M6502_Registers
containing the following members:
.Bd -literal
struct _M6502_Registers
{
uint8_t a; /* accumulator */
uint8_t x; /* X index register */
uint8_t y; /* Y index register */
uint8_t p; /* processor status register */
uint8_t s; /* stack pointer */
uint16_t pc; /* program counter */
};
.Ed
.Pp
The
.Fa memory
member is an array of
.Vt unsigned char
and can be indexed directly. In addition, two convenience macros
.Fn M6502_getVector
and
.Fn M6502_setVector
provide access to the reset and interrupt vectors within
.Fa memory .
.Fn M6502_getVector
returns the address stored in the named
.Fa vector
which must be precisely one of the following:
.Bl -tag -width ".Dv RST" -offset indent
.It Dv RST
the reset vector.
.It Dv NMI
the non-maskable interrupt vector.
.It Dv IRQ
the interrupt request vector.
.El
.Pp
.Fn M6502_setVector
stores its
.Fa address
argument in the named
.Fa vector
and returns the new value.
.Pp
The
.Fa callbacks
member contains an opaque structure mapping processor memory accesses
to client callback functions. Whenever the processor performs an
access for which a corresponding entry exists in the the
.Fa callbacks
structure, the emulator suspends execution and invokes the callback to
complete the operation. Each callback function should have a
signature equivalent to:
.Bd -ragged -offset indent
int
.Va callback
(M6502 *mpu, uint16_t address, uint8_t data);
.Ed
.Pp
The macros
.Fn M6502_getCallback
and
.Fn M6502_setCallback
read and write entries in the
.Fa callbacks
structure. These macros identify a unique memory access operation
from the specified
.Fa address
on which it operates and
.Fa type
of access involved. The
.Fa type
argument must be one of the following:
.Bl -tag -width ".Dv write"
.It Dv read
the
.Fa callback
is invoked when the processor attempts to read from the
given address. The emulator passes the effective address of the
operation to the callback in its
.Fa address
argument. (The
.Fa data
argument is undefined.) The value returned by the callback will be
used by the emulator as the result of the read operation.
.It Dv write
the
.Fa callback
is invoked when the processor attempts to write to the
given address. The emulator passes the effective address of the
operation to the callback in its
.Fa address
argument and the byte being written in the
.Fa data
argument. The emulator will not perform the write operation before
invoking the callback; if the write should complete, the callback must
modify the processor's
.Fa memory
explicitly. The valued returned from the callback is ignored.
.It Dv call
the
.Fa callback
is invoked when the processor attempts to transfer control to the
given address by any instruction other than a relative branch. The
emulator passes the destination address to the callback in its
.Fa address
argument and the instruction that initiated the control transfer in
its
.Fa data
argument (one of JMP, JSR, BRK, RTS or RTI). If the callback returns
zero (the callback refuses to handle the operation) the emulator will
allow the operation to complete as normal. If the callback returns a
non-zero address (indicating that the callback has handled the
operation internally) the emulator will transfer control to that
address.
.It Dv illegal_instruction
the
.Fa callback
is invoked when the processor attempts to execute the illegal instruction
whose opcode is the given "address". The emulator passes the address of the
instruction to the callback in its
.Fa address
argument and the instruction itself in the
.Fa data
argument. If the callback returns a non-zero address the
emulator will transfer control to that address, otherwise execution will
continue at the next instruction.
.El
.Pp
.Fn M6502_getCallback
returns zero if there is no callback associated with the given
.Fa type
and
.Fa address .
Passing zero as the
.Fa callback
argument of
.Fn M6502_setCallback
removes any callback that might have been associated with
.Fa type
and
.Fa address .
.Pp
.Fn M6502_run
emulates processor execution in the given
.Fa mpu
by repeatedly fetching the instruction addressed by
.Fa pc
and dispatching to it. This function normally never returns.
.Pp
.Fn M6502_dump
writes a (NUL-terminated) symbolic representation of the processor's
internal state into the supplied
.Fa buffer .
Typical output resembles:
.Bd -literal -offset indent
PC=1010 SP=01FE A=0A X=5B Y=00 P=D1 NV-B---C
.Ed
.Pp
.Fn M6502_disassemble
writes a (NUL-terminated) symbolic representation of the instruction
in the processor's memory at the given
.Fa address
into the supplied
.Fa buffer .
It returns the size (in bytes) of the instruction. (In other words,
the amount by which
.Fa address
should be incremented to arrive at the next instruction.)
Typical output resembles:
.Bd -literal -offset indent
1009 cpx #5B
.Ed
.Pp
(The
.Fa buffer
arguments are oversized to allow for future expansion.)
.Pp
.Fn M6502_delete
frees the resources associated with the given
.Fa mpu.
Any members that were allocated implicitly (passed as NULL to
.Fn M6502_new )
are deallocated. Members that were initialised from non-NULL
arguments are not deallocated.
.Pp
.Fn M6502_setMode
is a lib6502-jit extension which sets the emulation mode to use for the
instance to
.Fa mode ,
which must be precisely one of the following:
.Bl -tag -width ".Dv RST" -offset indent
.It Dv M6502_ModeInterpreted
6502 code will be interpreted, much as in lib6502 itself.
.It Dv M6502_ModeCompiled
6502 code will always be compiled to host code before executing. This can result
in jerky execution as emulation halts during compilation. Self-modifying code
will work correctly, but if this happens a lot the repeated re-compilations
will result in very slow execution.
.It Dv M6502_ModeHybrid
6502 code will be compiled to host code but the interpreter will be used to
continue execution during compilation. Execution will be smooth and relatively
fast but performance of repeatedly executed code will vary (in theory, improve)
over time. Repeated self-modification by code will cause re-compilations but
performance will still be reasonable as the interpreter will continue execution;
the main downside is that CPU will be taken up by the compilation. (On a
machine with two or more idle cores, this is wasteful but should not
significantly harm performance, as one core will run the interpreter while the
other handles the compilation.) This is the default mode.
.El
.Pp
.Fa arg
is the maximum number of 6502 instructions to be compiled into a single unit
of code when hybrid or compiled mode is selected; it is ignored in interpreted
mode. Specifying 0 will give a reasonable default value.
.Pp
.\" ----------------------------------------------------------------
.Sh IMPLEMENTATION NOTES
.\"
You can share the
.Fa memory
and
.Fa callbacks
members of
.Vt M6502
between multiple instances to simulate multiprocessor hardware.
.\" ----------------------------------------------------------------
.Sh RETURN VALUES
.\"
.Fn M6502_new
returns a pointer to a
.Vt M6502
structure.
.Fn M6502_getVector
and
.Fn M6502_setVector
return the contents of the given
.Fa vector .
.Fn M6502_getCallback
and
.Fn M6502_setCallback
return the
.Vt M6502_Callback
function associated with the given
.Fa address
and access
.Fa type .
.Fn M6502_disassemble
returns the size (in bytes) of the instruction at the given
.Fa address .
.Fn M6502_reset ,
.Fn M6502_nmi ,
.Fn M6502_irq ,
.Fn M6502_run ,
.Fn M6502_dump,
.Fn M6502_delete
and
.Fn M6502_setMode
don't return anything (unless you forgot to include
.In lib6502.h ) .
.\" ----------------------------------------------------------------
.Sh EXAMPLES
.\"
The following program creates a 6502 processor, sets up callbacks for
printing characters and halting after a BRK instruction, stores a
program into memory that prints the alphabet, disassembles the program
on stdout, and then executes the program.
.Bd -literal -offset indent -compact
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include "lib6502.h"
#define WRCH 0xFFEE
int wrch(M6502 *mpu, uint16_t address, uint8_t data)
{
int pc;
putchar(mpu->registers->a);
pc = mpu->memory[++mpu->registers->s + 0x100];
pc |= mpu->memory[++mpu->registers->s + 0x100] << 8;
return pc + 1; /* JSR pushes next insn addr - 1 */
}
int done(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
M6502_dump(mpu, buffer);
printf("\\nBRK instruction\\n%s\\n", buffer);
exit(0);
}
int main(int argc, char **argv)
{
M6502 *mpu = M6502_new(0, 0, 0);
unsigned pc = 0x1000;
mpu->callbacks->call[WRCH] = wrch; /* write character */
mpu->callbacks->call[0000] = done; /* reached after BRK */
# define gen1(X) (mpu->memory[pc++] = (uint8_t)(X))
# define gen2(X,Y) gen1(X); gen1(Y)
# define gen3(X,Y,Z) gen1(X); gen2(Y,Z)
gen2(0xA2, 'A' ); /* LDX #'A' */
gen1(0x8A ); /* TXA */
gen3(0x20,0xEE,0xFF); /* JSR FFEE */
gen1(0xE8 ); /* INX */
gen2(0xE0, 'Z'+1 ); /* CPX #'Z'+1 */
gen2(0xD0, -9 ); /* BNE 1002 */
gen2(0xA9, '\\n' ); /* LDA #'\\n' */
gen3(0x20,0xEE,0xFF); /* JSR FFEE */
gen2(0x00,0x00 ); /* BRK */
{
uint16_t ip = 0x1000;
while (ip < pc)
{
char insn[64];
ip += M6502_disassemble(mpu, ip, insn);
printf("%04X %s\\n", ip, insn);
}
}
M6502_setVector(mpu, RST, 0x1000);
M6502_reset(mpu);
M6502_run(mpu);
M6502_delete(mpu);
return 0;
}
.Ed
.\" ----------------------------------------------------------------
.Sh DIAGNOSTICS
.\"
If
.Fn M6502_new
cannot allocate sufficient memory it prints "out of memory" to stderr
and exits with a non-zero status.
.Pp
If
.Fn M6502_run
encounters an illegal or undefined instruction, it prints "undefined
instruction" and the processor's state to stderr, then exits with a
non-zero status.
.\" ----------------------------------------------------------------
.Sh COMPATIBILITY
.\"
M6502 is a generic name. The initial letter is mandated by C naming
conventions and chosen in deference to MOS Technology, the original
designers of the processor. To the best of my knowledge the 'M'
prefix was never stamped on a physical 6502.
.Pp
The emulator implements the CMOS version of the processor (NMOS bugs
in effective address calculations involving page boundaries are
corrected). lib6502 does not tolerate the execution of undefined
instructions (which were all no-ops in the first-generation CMOS
hardware); lib6502-jit treats them as no-ops. It would be nice to
support the several alternative instruction sets (model-specific
undocumented instructions in NMOS models, and various documented
extensions in the later CMOS models) but there are currently no plans
to do so.
.Pp
The emulated 6502 will run much faster than real hardware on any
modern computer. The fastest 6502 hardware available at the time of
writing has a clock speed of 14 MHz. On a 2 GHz PowerPC, the emulated
6502 runs at almost 300 MHz (in interpreted mode).
.\" ----------------------------------------------------------------
.Sh SEE ALSO
.\"
.Xr run6502 1
.Pp
For development tools, documentation and source code:
.Pa http://6502.org
.\" ----------------------------------------------------------------
.Sh AUTHORS
.\"
The original lib6502 software and manual pages were written by Ian Piumarta.
Additional changes to create lib6502-jit were made by Steven Flintham.
.Pp
The software is provided as-is, with absolutely no warranty, in the
hope that you will enjoy and benefit from it. You may use (entirely
at your own risk) and redistribute it under the terms of a very
liberal license that does not seek to restrict your rights in any way
(unlike certain so-called 'open source' licenses that significantly
limit your freedom in the name of 'free' software that is, ultimately,
anything but free). See the file COPYING for details.
.\" ----------------------------------------------------------------
.Sh BUGS
.\"
.Fn M6502_getVector
and
.Fn M6502_setVector
evaluate their arguments more than once.
.Pp
The out-of-memory condition and attempted execution of
illegal/undefined instructions should not be fatal errors.
.Pp
There is no way to limit the duration of execution within
.Fn M6502_run
to a certain number of instructions or cycles.
.Pp
The emulator should support some means of implicit interrupt
generation, either by polling or in response to (Unix) signals.
.Pp
The
.Sx COMPATIBILITY
section in this manual page has been diverted from its legitimate
purpose.
.Pp
The plural of 'callback' really aught to be 'callsback'.
.Pp
Please send bug reports (and feature requests) to :
lib6502-jit@lemma.co.uk.

396
man/run6502.1 Normal file
View File

@ -0,0 +1,396 @@
.\" Copyright (c) 2005 Ian Piumarta
.\" Copyright (c) 2014 Steven Flintham
.\"
.\" Permission is hereby granted, free of charge, to any person
.\" obtaining a copy of this software and associated documentation
.\" files (the 'Software'), to deal in the Software without
.\" restriction, including without limitation the rights to use, copy,
.\" modify, merge, publish, distribute, and/or sell copies of the
.\" Software, and to permit persons to whom the Software is furnished
.\" to do so, provided that the above copyright notice(s) and this
.\" permission notice appear in all copies of the Software and that
.\" both the above copyright notice(s) and this permission notice
.\" appear in supporting documentation.
.\"
.\" THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
.\"
.Dd October 31, 2005
.Dt RUN6502 1 LOCAL
.Os ""
.\" ----------------------------------------------------------------
.Sh NAME
.\"
.Nm run6502
.Nd execute a 6502 microprocessor program
.\" ----------------------------------------------------------------
.Sh SYNOPSIS
.\"
.Nm run6502
.Op Ar option ...
.Nm run6502
.Op Ar option ...
.Fl B
.Op Ar
.\" ----------------------------------------------------------------
.Sh DESCRIPTION
The
.Nm run6502
command emulates the execution of a 6502 microprocessor. It creates a
memory image from the contents of one or more files on the command
line and then simulates a power-on hardware reset to begin execution.
.Pp
In its first form,
.Nm run6502
emulates an embedded 6502 processor with 64 kilobytes of RAM, no
memory-mapped hardware, and no input-output capabilities. Limited
interaction with the machine is possible only through the
.Fl G , M
and
.Fl P
options.
.Pp
In its second form (with the
.Fl B
option)
.Nm run6502
provides minimal emulation of Acorn 'BBC Model B' hardware with 32
kilobytes of RAM, 16 kilobytes of paged language ROMs, and 16
kilobytes of operating system ROM. A few MOS calls are intercepted to
provide keyboard input and screen output via stdin and stdout.
Switching between the sixteen paged read-only memory banks is also
supported by the usual memory-mapped control register. Any
.Ar file
arguments after the
.Fl B
are loaded into successive paged ROM banks (starting at 15 and working
down towards 0) before execution begins.
.\" ----------------------------------------------------------------
.Ss Options
.\"
.Bl -tag -width indent
.It Fl B
enable minimal Acorn 'BBC Model B' hardware emulation:
.Bl -bullet
.It
the contents of memory between addresses 0x8000 and 0xBFFF are copied
into paged ROM number 0;
.It
memory between 0x8000 and 0xBFFF becomes bank-switchable between
sixteen different ROM images;
.It
the memory-mapped pages ('FRED', 'JIM' and 'SHEILA') between 0xFC00
and 0xFEFF are initialised to harmless values;
.It
the upper half of the address space is write-protected; and
.It
callbacks are installed on several OS entry points to provide
input-output via stdin and stdout.
.El
.Pp
Any remaining non-option arguments on the command line will name files
to be loaded successively into paged ROMs, starting at 15 and working
downwards towards 0.
.It Fl d Ar addr Ar end
dump memory from the address
.Ar addr
(given in hexadecimal) up to (but not including)
.Ar end .
The
.Ar end
argument is either an absolute address or a relative address specified
as a '+' character followed by the number (in hexadecimal) of bytes to
dump. In other words, the following two options dump the same region
of memory:
.Bd -ragged -offset indent
.Fl d
8000 C000
.Ed
.Bd -ragged -offset indent -compact
.Fl d
8000 +4000
.Ed
.Pp
The format of the dump cannot currently be modified and consists of
the current address followed by one, two or three hexadecimal bytes,
and a symbolic representation of the instruction at that address.
.It Fl G Ar addr
arrange that subroutine calls to
.Ar addr
will behave as if there were an implementation of
.Xr getchar 3
at that address, reading a character from stdin and returning it in
the accumulator.
.It Fl h
print a summary of the available options and then exit.
.It Fl I Ar addr
set the IRQ (interrupt request) vector (the address to which the
processor will transfer control upon execution of a BRK instruction).
Setting this address to zero will cause execution to halt (and the
emulator to exit) when a BRK instruction is encountered.
.It Fl i Ar addr Ar file
Load
.Ar file
into the memory image at the address
.Ar addr
(in hexadecimal), skipping over any initial '#!' interpreter line.
.It Fl l Ar addr Ar file
Load
.Ar file
into the memory image at the address
.Ar addr
(in hexadecimal).
.It Fl M Ar addrio
arrange that memory reads from address
.Ar addrio
will return the next character on stdin (blocking if necessary), and
memory writes to
.Ar addrio
will send the value written to stdout.
.It Fl mc
use compiled emulation mode. All code is compiled into host machine
code. This can make the emulation very jerky as execution halts
while compiling.
.It Fl mh
use hybrid emulation mode. Code is compiled into
host machine code, but while this is happening an interpreter allows
execution to continue. This is the default mode.
.It Fl mi
use interpreted emulation mode. All code is interpreted.
.It Fl mx Ar count
in compiled and hybrid emulation modes, set the maximum number of
6502 instructions which are translated as a unit to
.Ar count .
This has no effect in interpreted mode. A reasonable default is
chosen if this is not specified.
.It Fl N Ar addr
set the NMI (non-maskable interrupt) vector to
.Ar addr .
.It Fl P Ar addr
arrange that subroutine calls to
.Ar addr
will behave as if there were an implementation of
.Xr putchar 3
at that address, writing the contents of the accumulator to stdout.
.It Fl R Ar addr
set the RST (hardware reset) vector. The processor will transfer
control to this address when emulated execution begins.
.It Fl s Ar addr Ar end Ar file
save the contents of memory from the address
.Ar addr
up to
.Ar end
(exclusive) to the given
.Ar file .
As with the
.Fl d
option,
.Ar end
can be absolute or '+' followed by a byte count.
.It Fl v
print version information and then exit.
.It Fl X Ar addr
arrange that any transfer of control to the address
.Ar addr
will cause an immediate exit with zero exit status.
.It Fl x
exit immediately. (Useful after
.Fl d
or when
.Nm run6502
is being used as a trivial 'image editor', with several
.Fl l
options followed by
.Fl s
and
.Fl x . )
.It Ar
following a
.Fl B
option, load one or more ROM image
files
into successive paged ROM slots. Other than the paging aspect, this
is equivalent to:
.Bd -ragged -offset indent
.Fl l Ar 8000 Ar image
.Ed
.El
.\" ----------------------------------------------------------------
.Sh EXAMPLES
.\"
.Ss A Very Simple Program
The
.Xr perl 1
command can be used to create a binary file from hexadecimal input:
.Bd -literal
echo a2418a20eeffe8e05bd0f7a90a20eeff00 |
perl -e 'print pack "H*",<STDIN>' > temp.img
.Ed
.Pp
The file can be loaded and executed with:
.Bd -literal
run6502 -l 1000 temp.img -R 1000 -P FFEE -X 0
.Ed
.Pp
The contents of the file can be inspected symbolically with:
.Bd -literal
run6502 -l 1000 temp.img -d 1000 +12
.Ed
.Pp
The options passed to
.Nm run6502
in the above examples have the following effects:
.Bl -tag -width offset
.It \-l 1000 temp.img
loads the file
.Pa temp.img
into memory at address 0x8000.
.It \-R 1000
sets the reset vector (the address of first instruction to be executed
after 'power on') to 0x1000.
.It \-P FFEE
arranges for calls to address 0xFFEE to behave as if there were an
implementation of
.Xr putchar 3
at that address.
.It \-X 0
arranges for transfers of control to address 0 to exit from the
emulator. This works in the above example because the final 'BRK'
instruction causes an implicit subroutine call through an
uninitialised interrupt vector to location 0. To see this
instruction...
.It \-d 1000 +12
disassembles 18 bytes of memory at address 0x8000.
.El
.Ss Standalone Images
The
.Fl i
option is designed for use in the 'interpreter command' appearing on
the first line of an executable script. Adding the line
.Bd -literal
#!run6502 -R 1000 -P FFEE -X 0 -i 1000
.Ed
.Pp
(with no leading spaces and a single trailing newline character)
to the
.Pa temp.img
file from the first example turns it into a script. If the file is
made executable with
.Bd -literal
chmod +x temp.img
.Ed
.Pp
it can be run like a standalone program:
.Bd -literal
./temp.img
.Ed
.Ss A Very Complex Program
Consider a pair of files named
.Pa os1.2
and
.Pa basic2
containing (legally-acquired, of course) ROM images of Acorn MOS 1.2
and BBC Basic 2. The following command loads each of the images into
memory at the appropriate address, cleans up the regions of memory
containing memory-mapped i/o on the BBC computer, saves a snapshot of
the entire memory to the file
.Pa image
and then exits:
.Bd -literal
run6502 -l C000 os1.2 -l 8000 basic2 -B -s0 +10000 image -x
.Ed
.Pp
Running the generated image with
.Bd -literal
run6502 image
.Ed
.Pp
will cold-start the emulated hardware, run the OS for a while, and
then drop into the language ROM. Basic programs can then be entered,
edited and run from the terminal.
.Pp
More details are given in the
.Pa README
file available in the
.Pa examples
directory of the distribution.
.Ss Exercises
Create a standalone image (one that can be run as a program, with
a '#!' interpreter line at the beginning) that contains Basic2 and
OS1.2 (as described above). This image should be no larger than 32K
(memory below 0x8000, which would be full of zeroes, should not appear
in the image file).
.\" ----------------------------------------------------------------
.Sh DIAGNOSTICS
.\"
If nothing goes wrong, none. Otherwise lots. They should be
self-explanatory. I'm too lazy to enumerate them.
.\" ----------------------------------------------------------------
.Sh COMPATIBILITY
.\"
See
.Xr lib6502 3
for a discussion of the emulated instruction set.
.\" ----------------------------------------------------------------
.Sh SEE ALSO
.\"
.Xr lib6502 3
.Pp
The file
.Pa examples/README
in the lib6502 distribution. (Depending on your system this may be
installed in
.Pa /usr/doc/lib6502 ,
.Pa /usr/local/doc/lib6502 ,
.Pa /usr/share/doc/lib6502 ,
or similar.)
.Pp
.Pa http://piumarta.com/software/lib6502
for updates and documentation to lib6502.
.Pp
.Pa https://github.com/ZornsLemma/lib6502-jit
for updates and documentation to lib6502-jit.
.Pp
.Pa http://6502.org
for lots of 6502-related resources.
.\" ----------------------------------------------------------------
.Sh AUTHORS
.\"
The original lib6502 software and manual pages were written by Ian Piumarta.
Additional changes to create lib6502-jit were made by Steven Flintham.
.Pp
The software is provided as-is, with absolutely no warranty, in the
hope that you will enjoy and benefit from it. You may use (entirely
at your own risk) and redistribute it under the terms of a very
liberal license that does not seek to restrict your rights in any way
(unlike certain so-called 'open source' licenses that significantly
limit your freedom in the name of 'free' software that is, ultimately,
anything but free). See the file COPYING for details.
.\" ----------------------------------------------------------------
.Sh BUGS
.\"
.Bl -bullet
.It
Options must appear one at a time.
.It
Any attempt (in a load or save operation) to transfer data beyond
0xFFFF is silently truncated at the end of memory.
.It
There is no way to specify the slot into which a ROM image should be
loaded, other than implicitly according to the order of arguments on
the command line.
.It
Execution can only be started via the emulated power-up reset. There
is no support for 'warm-starting' execution in an image at an
arbitrary address.
.It
Even though the emulator fully supports them, there is no way to
artificially generate a hardware interrupt request, non-maskable
interrupt, or reset condition. If you need these, read
.Xr lib6502 3
and write your own shell.
.It
The Acorn 'BBC Model B' hardware emulation is totally lame.
.El
.Pp
Please send bug reports (and feature requests) to :
lib6502-jit@lemma.co.uk.

599
run6502.c Normal file
View File

@ -0,0 +1,599 @@
/* run6502.c -- 6502 emulator shell -*- C -*- */
/* Copyright (c) 2005 Ian Piumarta
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
/* Last edited: 2005-11-02 01:18:58 by piumarta on margaux.local
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <sys/wait.h>
#include <time.h>
#include "config.h"
#include "lib6502.h"
#undef VERSION
#define VERSION PACKAGE_NAME " " PACKAGE_VERSION " " PACKAGE_COPYRIGHT
typedef uint8_t byte;
typedef uint16_t word;
static char *program= 0;
static M6502_Mode mode= M6502_ModeHybrid;
static int max_insns= 0; /* default */
static byte bank[0x10][0x4000];
static uint64_t system_time_base;
void fail(const char *fmt, ...)
{
va_list ap;
fflush(stdout);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
exit(1);
}
void pfail(const char *msg)
{
fflush(stdout);
perror(msg);
exit(1);
}
#define rts \
{ \
word pc; \
pc = mpu->memory[++mpu->registers->s + 0x100]; \
pc |= mpu->memory[++mpu->registers->s + 0x100] << 8; \
return pc + 1; \
}
uint64_t pseudo_system_time(void)
{
struct timespec t;
if (clock_gettime(CLOCK_MONOTONIC, &t) == -1)
{
pfail("clock_gettime() failed");
}
long hsec= t.tv_nsec / 10000000;
return (((uint64_t) t.tv_sec) * 100) + hsec;
}
int osword(M6502 *mpu, word address, byte data)
{
byte *params= mpu->memory + mpu->registers->x + (mpu->registers->y << 8);
switch (mpu->registers->a)
{
case 0x00: /* input line */
/* On entry: XY+0,1=>string area,
* XY+2=maximum line length,
* XY+3=minimum acceptable ASCII value,
* XY+4=maximum acceptable ASCII value.
* On exit: Y is the line length (excluding CR),
* C is set if Escape terminated input.
*/
{
word offset= params[0] + (params[1] << 8);
byte *buffer= mpu->memory + offset;
byte length= params[2], minVal= params[3], maxVal= params[4], b= 0;
if (!fgets((char *) buffer, length, stdin))
{
putchar('\n');
exit(0);
}
for (b= 0; b < length; ++b)
if ((buffer[b] < minVal) || (buffer[b] > maxVal) || ('\n' == buffer[b]))
break;
buffer[b]= 13;
mpu->registers->y= b;
mpu->registers->p &= 0xFE;
break;
}
case 0x01: /* read system time */
/* On exit: XY+0..4=>5 byte time in hundredths of a second
*/
{
uint64_t system_time= pseudo_system_time() - system_time_base;
int i;
for (i= 0; i < 5; ++i)
{
params[i]= system_time & 0xFF;
system_time>>= 8;
}
break;
}
case 0x05: /* read I/O processor memory */
/* On entry: XY+0..3=>address to read from
* On exit: XY+4 =>the byte read
*/
{
word addr= params[0] + (params[1] << 8);
params[4]= mpu->memory[addr];
break;
}
default:
{
char state[64];
M6502_dump(mpu, state);
fflush(stdout);
fprintf(stderr, "\nOSWORD %s\n", state);
fail("ABORT");
}
break;
}
rts;
}
int osbyte(M6502 *mpu, word address, byte data)
{
switch (mpu->registers->a)
{
case 0x7A: /* perform keyboard scan */
mpu->registers->x= 0x00;
break;
case 0x7E: /* acknowledge detection of escape condition */
return 1;
break;
case 0x82: /* read machine higher order address */
mpu->registers->y= 0x00;
mpu->registers->x= 0x00;
break;
case 0x83: /* read top of OS ram address (OSHWM) */
mpu->registers->y= 0x0E;
mpu->registers->x= 0x00;
break;
case 0x84: /* read bottom of display ram address */
mpu->registers->y= 0x80;
mpu->registers->x= 0x00;
break;
case 0x89: /* motor control */
break;
case 0xDA: /* read/write number of items in vdu queue (stored at 0x026A) */
return 0;
break;
default:
{
char state[64];
M6502_dump(mpu, state);
fflush(stdout);
fprintf(stderr, "\nOSBYTE %s\n", state);
fail("ABORT");
}
break;
}
rts;
}
int oscli(M6502 *mpu, word address, byte data)
{
byte *params= mpu->memory + mpu->registers->x + (mpu->registers->y << 8);
char command[1024], *ptr= command;
int ret;
while (('*' == *params) || (' ' == *params))
++params;
while (13 != *params)
*ptr++= *params++;
*ptr= '\0';
ret= system(command);
if ((ret == -1) || (WIFEXITED(ret) && (WEXITSTATUS(ret) == 127)))
{
fflush(stdout);
fprintf(stderr, "\nsystem() failed\n");
}
rts;
}
int oswrch(M6502 *mpu, word address, byte data)
{
switch (mpu->registers->a)
{
case 0x0C:
fputs("\033[2J\033[H", stdout);
break;
default:
putchar(mpu->registers->a);
break;
}
fflush(stdout);
rts;
}
static int writeROM(M6502 *mpu, word address, byte value)
{
return 0;
}
static int bankSelect(M6502 *mpu, word address, byte value)
{
memcpy(mpu->memory + 0x8000, bank[value & 0x0F], 0x4000);
return 0;
}
static int doBtraps(int argc, char **argv, M6502 *mpu)
{
unsigned addr;
/* Acorn Model B ROM and memory-mapped IO */
for (addr= 0x8000; addr <= 0xFBFF; ++addr) mpu->callbacks->write[addr]= writeROM;
for (addr= 0xFC00; addr <= 0xFEFF; ++addr) mpu->memory[addr]= 0xFF;
for (addr= 0xFE30; addr <= 0xFE33; ++addr) mpu->callbacks->write[addr]= bankSelect;
for (addr= 0xFE40; addr <= 0xFE4F; ++addr) mpu->memory[addr]= 0x00;
for (addr= 0xFF00; addr <= 0xFFFF; ++addr) mpu->callbacks->write[addr]= writeROM;
/* anything already loaded at 0x8000 appears in bank 0 */
memcpy(bank[0x00], mpu->memory + 0x8000, 0x4000);
/* fake a few interesting OS calls */
# define trap(vec, addr, func) mpu->callbacks->call[addr]= (func)
trap(0x020C, 0xFFF1, osword);
trap(0x020A, 0xFFF4, osbyte);
//trap(0x0208, 0xFFF7, oscli ); /* enable this to send '*COMMAND's to system(3) :-) */
trap(0x020E, 0xFFEE, oswrch);
trap(0x020E, 0xE0A4, oswrch); /* NVWRCH */
#undef trap
system_time_base= pseudo_system_time();
return 0;
}
static void usage(int status)
{
FILE *stream= status ? stderr : stdout;
fprintf(stream, VERSION"\n");
fprintf(stream, "please send bug reports to: %s\n", PACKAGE_BUGREPORT);
fprintf(stream, "\n");
fprintf(stream, "usage: %s [option ...]\n", program);
fprintf(stream, " %s [option ...] -B [image ...]\n", program);
fprintf(stream, " -B -- minimal Acorn 'BBC Model B' compatibility\n");
fprintf(stream, " -d addr last -- dump memory between addr and last\n");
fprintf(stream, " -G addr -- emulate getchar(3) at addr\n");
fprintf(stream, " -h -- help (print this message)\n");
fprintf(stream, " -I addr -- set IRQ vector\n");
fprintf(stream, " -l addr file -- load file at addr\n");
fprintf(stream, " -M addr -- emulate memory-mapped stdio at addr\n");
fprintf(stream, " -mc -- use compiled emulation mode\n");
fprintf(stream, " -mh -- use hybrid emulation mode (default)\n");
fprintf(stream, " -mi -- use interpreted emulation mode\n");
fprintf(stream, " -mx count -- maximum instructions to JIT (-mc/-mh)\n");
fprintf(stream, " -N addr -- set NMI vector\n");
fprintf(stream, " -P addr -- emulate putchar(3) at addr\n");
fprintf(stream, " -R addr -- set RST vector\n");
fprintf(stream, " -s addr last file -- save memory from addr to last in file\n");
fprintf(stream, " -v -- print version number then exit\n");
fprintf(stream, " -X addr -- terminate emulation if PC reaches addr\n");
fprintf(stream, " -x -- exit without further ado\n");
fprintf(stream, " image -- '-l 8000 image' in available ROM slot\n");
fprintf(stream, "\n");
fprintf(stream, "'last' can be an address (non-inclusive) or '+size' (in bytes)\n");
exit(status);
}
static int doHelp(int argc, char **argv, M6502 *mpu)
{
usage(0);
return 0;
}
static int doVersion(int argc, char **argv, M6502 *mpu)
{
puts(VERSION);
exit(0);
return 0;
}
static unsigned long htol(char *hex)
{
char *end;
unsigned long l= strtol(hex, &end, 16);
if (*end) fail("bad hex number: %s", hex);
return l;
}
static int loadInterpreter(M6502 *mpu, word start, const char *path)
{
FILE *file= 0;
int count= 0;
byte *memory= mpu->memory + start;
size_t max= 0x10000 - start;
int c= 0;
if ((!(file= fopen(path, "r"))) || ('#' != fgetc(file)) || ('!' != fgetc(file)))
return 0;
while ((c= fgetc(file)) >= ' ')
;
while ((count= fread(memory, 1, max, file)) > 0)
{
memory += count;
max -= count;
}
fclose(file);
return 1;
}
static int save(M6502 *mpu, word address, unsigned length, const char *path)
{
FILE *file= 0;
int count= 0;
if (!(file= fopen(path, "w")))
return 0;
while ((count= fwrite(mpu->memory + address, 1, length, file)))
{
address += count;
length -= count;
}
fclose(file);
return 1;
}
static int load(M6502 *mpu, word address, const char *path)
{
FILE *file= 0;
int count= 0;
size_t max= 0x10000 - address;
if (!(file= fopen(path, "r")))
return 0;
while ((count= fread(mpu->memory + address, 1, max, file)) > 0)
{
address += count;
max -= count;
}
fclose(file);
return 1;
}
static int doLoadInterpreter(int argc, char **argv, M6502 *mpu)
{
if (argc < 3) usage(1);
if (!loadInterpreter(mpu, htol(argv[1]), argv[2])) pfail(argv[2]);
return 2;
}
static int doLoad(int argc, char **argv, M6502 *mpu) /* -l addr file */
{
if (argc < 3) usage(1);
if (!load(mpu, htol(argv[1]), argv[2])) pfail(argv[2]);
return 2;
}
static int doSave(int argc, char **argv, M6502 *mpu) /* -l addr size file */
{
if (argc < 4) usage(1);
if (!save(mpu, htol(argv[1]), htol(argv[2]), argv[3])) pfail(argv[3]);
return 3;
}
static int doMode(M6502_Mode m)
{
mode= m;
return 0;
}
static int doMaxInsns(int argc, char **argv, M6502 *mpu)
{
if (argc < 2) usage(1);
char *end;
unsigned long l= strtol(argv[1], &end, 10);
if (*end) fail("bad number: %s", argv[1]);
max_insns= l;
return 1;
}
#define doVEC(VEC) \
static int do##VEC(int argc, char **argv, M6502 *mpu) \
{ \
unsigned addr= 0; \
if (argc < 2) usage(1); \
addr= htol(argv[1]); \
M6502_setVector(mpu, VEC, addr); \
return 1; \
}
doVEC(IRQ);
doVEC(NMI);
doVEC(RST);
#undef doVEC
static int gTrap(M6502 *mpu, word addr, byte data) { mpu->registers->a= getchar(); rts; }
static int pTrap(M6502 *mpu, word addr, byte data) { putchar(mpu->registers->a); rts; }
static int doGtrap(int argc, char **argv, M6502 *mpu)
{
unsigned addr;
if (argc < 2) usage(1);
addr= htol(argv[1]);
M6502_setCallback(mpu, call, addr, gTrap);
return 1;
}
static int doPtrap(int argc, char **argv, M6502 *mpu)
{
unsigned addr;
if (argc < 2) usage(1);
addr= htol(argv[1]);
M6502_setCallback(mpu, call, addr, pTrap);
return 1;
}
static int mTrapRead(M6502 *mpu, word addr, byte data) { return getchar(); }
static int mTrapWrite(M6502 *mpu, word addr, byte data) { return putchar(data); }
static int doMtrap(int argc, char **argv, M6502 *mpu)
{
unsigned addr= 0;
if (argc < 2) usage(1);
addr= htol(argv[1]);
M6502_setCallback(mpu, read, addr, mTrapRead);
M6502_setCallback(mpu, write, addr, mTrapWrite);
return 1;
}
static int xTrap(M6502 *mpu, word addr, byte data) { exit(0); return 0; }
static int doXtrap(int argc, char **argv, M6502 *mpu)
{
unsigned addr= 0;
if (argc < 2) usage(1);
addr= htol(argv[1]);
M6502_setCallback(mpu, call, addr, xTrap);
return 1;
}
static int doDisassemble(int argc, char **argv, M6502 *mpu)
{
unsigned addr= 0, last= 0;
if (argc < 3) usage(1);
addr= htol(argv[1]);
last= ('+' == *argv[2]) ? addr + htol(1 + argv[2]) : htol(argv[2]);
while (addr < last)
{
char insn[64];
int i= 0, size= M6502_disassemble(mpu, addr, insn);
printf("%04X ", addr);
while (i++ < size) printf("%02X", mpu->memory[addr + i - 1]);
while (i++ < 4) printf(" ");
putchar(' ');
i= 0;
while (i++ < size) putchar(isgraph(mpu->memory[addr + i - 1]) ? mpu->memory[addr + i - 1] : ' ');
while (i++ < 4) putchar(' ');
printf(" %s\n", insn);
addr += size;
}
return 2;
}
int main(int argc, char **argv)
{
M6502 *mpu= M6502_new(0, 0, 0);
int bTraps= 0;
program= argv[0];
if ((2 == argc) && ('-' != *argv[1]))
{
if ((!loadInterpreter(mpu, 0, argv[1])) && (!load(mpu, 0, argv[1])))
pfail(argv[1]);
doBtraps(0, 0, mpu);
}
else
while (++argv, --argc > 0)
{
int n= 0;
if (!strcmp(*argv, "-B")) bTraps= 1;
else if (!strcmp(*argv, "-d")) n= doDisassemble(argc, argv, mpu);
else if (!strcmp(*argv, "-G")) n= doGtrap(argc, argv, mpu);
else if (!strcmp(*argv, "-h")) n= doHelp(argc, argv, mpu);
else if (!strcmp(*argv, "-i")) n= doLoadInterpreter(argc, argv, mpu);
else if (!strcmp(*argv, "-I")) n= doIRQ(argc, argv, mpu);
else if (!strcmp(*argv, "-l")) n= doLoad(argc, argv, mpu);
else if (!strcmp(*argv, "-M")) n= doMtrap(argc, argv, mpu);
else if (!strcmp(*argv, "-mc")) n= doMode(M6502_ModeCompiled);
else if (!strcmp(*argv, "-mh")) n= doMode(M6502_ModeHybrid);
else if (!strcmp(*argv, "-mi")) n= doMode(M6502_ModeInterpreted);
else if (!strcmp(*argv, "-mx")) n= doMaxInsns(argc, argv, mpu);
else if (!strcmp(*argv, "-N")) n= doNMI(argc, argv, mpu);
else if (!strcmp(*argv, "-P")) n= doPtrap(argc, argv, mpu);
else if (!strcmp(*argv, "-R")) n= doRST(argc, argv, mpu);
else if (!strcmp(*argv, "-s")) n= doSave(argc, argv, mpu);
else if (!strcmp(*argv, "-v")) n= doVersion(argc, argv, mpu);
else if (!strcmp(*argv, "-X")) n= doXtrap(argc, argv, mpu);
else if (!strcmp(*argv, "-x")) exit(0);
else if ('-' == **argv) usage(1);
else
{
/* doBtraps() left 0x8000+0x4000 in bank 0, so load */
/* additional images starting at 15 and work down */
static int bankSel= 0x0F;
if (!bTraps) usage(1);
if (bankSel < 0) fail("too many images");
if (!load(mpu, 0x8000, argv[0])) pfail(argv[0]);
memcpy(bank[bankSel--],
0x8000 + mpu->memory,
0x4000);
n= 0;
}
argc -= n;
argv += n;
}
M6502_setMode(mpu, mode, max_insns);
if (bTraps)
doBtraps(0, 0, mpu);
M6502_reset(mpu);
M6502_run(mpu);
M6502_delete(mpu);
return 0;
}

1
test/addr-wrap-1.mst Normal file
View File

@ -0,0 +1 @@
Y

25
test/addr-wrap-1.xa Normal file
View File

@ -0,0 +1,25 @@
#include "config.xa"
LDA #1
STA $00
STA $05
STA $0A
LDY #$80
CLC
LDA #0
LOOP
ADC $FF80,Y
INY
BNE LOOP
CMP #3
BNE FAIL
SUCCESS
LDA #'Y'
JSR OSWRCH
JMP QUIT
FAIL
LDA #'N'
JSR OSWRCH
JMP QUIT

122
test/basic-callback.c Normal file
View File

@ -0,0 +1,122 @@
/* Copyright (c) 2005 Ian Piumarta
* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#include <stdio.h>
#include <stdlib.h>
#include "lib6502.h"
#include "test-utils.h"
int done(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
M6502_dump_masked(mpu, buffer);
printf("\nBRK instruction: address %04X opcode %02X\n%s\n", address, data, buffer);
exit(0);
}
int call(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
M6502_dump_masked(mpu, buffer);
printf("\ncall: address %04X opcode %02X\n%s\n", address, data, buffer);
return 0;
}
int rd(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
M6502_dump_masked(mpu, buffer);
printf("\nrd: address %04X opcode %02X\n%s\n", address, data, buffer);
return 0;
}
int wr(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
M6502_dump_masked(mpu, buffer);
printf("\nwr: address %04X opcode %02X\n%s\n", address, data, buffer);
return 0;
}
int ill(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
M6502_dump_masked(mpu, buffer);
printf("\nill: address %04X opcode %02X memory %02X\n%s\n", address, data, mpu->memory[address], buffer);
return 0;
}
int main(int argc, char *argv[])
{
M6502 *mpu = M6502_new(0, 0, 0);
parse_args(argc, argv, mpu);
unsigned pc = 0x1000;
M6502_setCallback(mpu, call, 0, done);
M6502_setCallback(mpu, call, 0x2000, call);
M6502_setCallback(mpu, call, 0x3000, call);
M6502_setCallback(mpu, call, 0x4000, call);
M6502_setCallback(mpu, read, 0x5000, rd );
M6502_setCallback(mpu, write, 0x5000, wr );
M6502_setCallback(mpu, illegal_instruction, 0x13, ill );
M6502_setCallback(mpu, illegal_instruction, 0x44, ill );
M6502_setCallback(mpu, illegal_instruction, 0x5c, ill );
# define gen1(X) (mpu->memory[pc++]= (uint8_t)(X))
# define gen2(X,Y) gen1(X); gen1(Y)
# define gen3(X,Y,Z) gen1(X); gen2(Y,Z)
gen1(0x13 );
gen1(0x44 );
gen1(0x13 ); // not executed, 0x44 is a two-byte illegal instruction
gen1(0x5C );
gen1(0x13 ); // not executed, 0x5C is a two-byte illegal instruction
gen1(0x13 ); // not executed, 0x5C is a two-byte illegal instruction
gen3(0x20,0x00,0x20); // JSR &2000
gen3(0xad,0x00,0x50); // LDA &5000
gen2(0x64,0x70 ); // STZ &70
gen2(0xa9,0x50 ); // LDA #&50
gen2(0x85,0x71 ); // STA &71
gen2(0xb2,0x70 ); // LDA (&70)
gen2(0x92,0x70 ); // STA (&70)
gen2(0x00,0x00 ); // BRK
pc = 0x2000;
gen3(0x8d,0x00,0x50); // STA &5000
gen3(0x4c,0x00,0x30); // JMP &3000
pc = 0x3000;
gen2(0xa9,0x00 ); // LDA #0
gen3(0x8d,0x76,0x32); // STA &3276
gen2(0xa9,0x40 ); // LDA #&40
gen3(0x8d,0x77,0x32); // STA &3277
gen3(0x6c,0x76,0x32); // JMP (&3276)
pc = 0x4000;
gen1(0x60 ); // RTS
M6502_setVector(mpu, RST, 0x1000);
M6502_reset(mpu);
M6502_run(mpu);
M6502_delete(mpu); /* We never reach here, but what the hey. */
return 0;
}

33
test/basic-callback.mst Normal file
View File

@ -0,0 +1,33 @@
ill: address 1000 opcode 13 memory 13
PC=1001 SP=0100 A=00 X=00 Y=00 P=04 -----I--
ill: address 1001 opcode 44 memory 44
PC=1003 SP=0100 A=00 X=00 Y=00 P=04 -----I--
ill: address 1003 opcode 5C memory 5C
PC=1006 SP=0100 A=00 X=00 Y=00 P=04 -----I--
call: address 2000 opcode 20
PC=1009 SP=01FE A=00 X=00 Y=00 P=04 -----I--
wr: address 5000 opcode 00
PC=1009 SP=01FE A=00 X=00 Y=00 P=04 -----I--
call: address 3000 opcode 4C
PC=3000 SP=01FE A=00 X=00 Y=00 P=04 -----I--
call: address 4000 opcode 6C
PC=4000 SP=01FE A=40 X=00 Y=00 P=04 -----I--
rd: address 5000 opcode 00
PC=4000 SP=01FE A=40 X=00 Y=00 P=04 -----I--
rd: address 5000 opcode 00
PC=4000 SP=01FE A=40 X=00 Y=00 P=04 -----I--
wr: address 5000 opcode 00
PC=4000 SP=01FE A=40 X=00 Y=00 P=04 -----I--
BRK instruction: address 1016 opcode 00
PC=1018 SP=01FD A=00 X=00 Y=00 P=06 -----IZ-

View File

@ -0,0 +1,121 @@
/* Copyright (c) 2005 Ian Piumarta
* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#include <stdio.h>
#include <stdlib.h>
#include "lib6502.h"
#include "test-utils.h"
static uint16_t call_modify1_addr;
static uint16_t call_modify2_addr;
static uint16_t ill_modify1_addr;
static uint16_t ill_modify2_addr;
int done(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
M6502_dump_masked(mpu, buffer);
printf("\nBRK instruction: address %04X opcode %02X\n%s\n", address, data, buffer);
exit(0);
}
int call(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
M6502_dump_masked(mpu, buffer);
printf("\ncall: address %04X opcode %02X\n%s\n", address, data, buffer);
mpu->memory[call_modify1_addr] += 1;
mpu->memory[call_modify2_addr] += 2;
return 0;
}
int ill(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
M6502_dump_masked(mpu, buffer);
printf("\nill: address %04X opcode %02X memory %02X\n%s\n", address, data, mpu->memory[address], buffer);
mpu->memory[ill_modify1_addr] += 1;
mpu->memory[ill_modify2_addr] += 2;
return 0;
}
int oswrch(M6502 *mpu, uint16_t address, uint8_t data)
{
putchar(mpu->registers->a);
mpu->memory[0xffee] = 0x60; // RTS
return 0;
}
int main(int argc, char *argv[])
{
M6502 *mpu = M6502_new(0, 0, 0);
parse_args(argc, argv, mpu);
unsigned pc = 0x1000;
M6502_setCallback(mpu, call, 0, done );
M6502_setCallback(mpu, call, 0x2000, call );
M6502_setCallback(mpu, illegal_instruction, 0x13, ill );
M6502_setCallback(mpu, call, 0xffee, oswrch);
# define gen1(X) (mpu->memory[pc++]= (uint8_t)(X))
# define gen2(X,Y) gen1(X); gen1(Y)
# define gen3(X,Y,Z) gen1(X); gen2(Y,Z)
gen3(0x20,0x00,0x30); // JSR &3000
gen1(0x13 ); // ill &13
gen3(0x20,0x00,0x30); // JSR &3000
gen1(0x13 ); // ill &13
gen3(0x20,0x00,0x30); // JSR &3000
gen3(0x20,0x00,0x20); // JSR &2000
gen3(0x20,0x00,0x30); // JSR &3000
gen3(0x20,0x00,0x20); // JSR &2000
gen3(0x20,0x00,0x30); // JSR &3000
gen2(0x00,0x00 ); // BRK
pc = 0x2000;
gen1(0x60 ); // RTS
pc = 0x3000;
gen2(0xa9,'C' ); // LDA #'C'
gen3(0x20,0xee,0xff); // JSR &FFEE
call_modify1_addr = pc + 1;
gen2(0xa9,'A' ); // LDA #'A'
gen3(0x20,0xee,0xff); // JSR &FFEE
call_modify2_addr = pc + 1;
gen2(0xa9,'A' ); // LDA #'A'
gen3(0x20,0xee,0xff); // JSR &FFEE
ill_modify1_addr = pc + 1;
gen2(0xa9,'A' ); // LDA #'A'
gen3(0x20,0xee,0xff); // JSR &FFEE
ill_modify2_addr = pc + 1;
gen2(0xa9,'A' ); // LDA #'A'
gen3(0x20,0xee,0xff); // JSR &FFEE
gen2(0xa9,'\n' ); // LDA #'\n'
gen3(0x20,0xee,0xff); // JSR &FFEE
gen1(0x60 ); // RTS
M6502_setVector(mpu, RST, 0x1000);
M6502_reset(mpu);
M6502_run(mpu);
M6502_delete(mpu); /* We never reach here, but what the hey. */
return 0;
}

View File

@ -0,0 +1,20 @@
CAAAA
ill: address 1003 opcode 13 memory 13
PC=1004 SP=0100 A=0A X=00 Y=00 P=04 -----I--
CAABC
ill: address 1007 opcode 13 memory 13
PC=1008 SP=0100 A=0A X=00 Y=00 P=04 -----I--
CAACE
call: address 2000 opcode 20
PC=100E SP=01FE A=0A X=00 Y=00 P=04 -----I--
CBCCE
call: address 2000 opcode 20
PC=1014 SP=01FE A=0A X=00 Y=00 P=04 -----I--
CCECE
BRK instruction: address 1017 opcode 00
PC=1019 SP=01FD A=0A X=00 Y=00 P=04 -----I--

4
test/config.xa Normal file
View File

@ -0,0 +1,4 @@
OSWRCH = $FFEE
QUIT = $F000
*= $1E00

1
test/interleave.mst Normal file
View File

@ -0,0 +1 @@
Y

38
test/interleave.xa Normal file
View File

@ -0,0 +1,38 @@
#include "config.xa"
JSR SETX10
CPX #10
BNE FAIL
JSR SETX30
CPX #30
BNE FAIL
JSR SETX20
CPX #20
BNE FAIL
JSR SETX30
CPX #30
BNE FAIL
JSR SETX10
CPX #10
BNE FAIL
JSR SETX20
CPX #20
BNE FAIL
SUCCESS
LDA #'Y'
JSR OSWRCH
JMP QUIT
FAIL
LDA #'N'
JSR OSWRCH
JMP QUIT
; example taken from http://www.6502.org/tutorials/6502opcodes.html
SETX10 LDX #10
.byte $2C
SETX20 LDX #20
.byte $2C
SETX30 LDX #30
RTS

116
test/irq-nmi.c Normal file
View File

@ -0,0 +1,116 @@
/* Copyright (c) 2005 Ian Piumarta
* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#include <stdio.h>
#include <stdlib.h>
#include "lib6502.h"
#include "test-utils.h"
int brk(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
M6502_dump_masked(mpu, buffer);
printf("\nBRK: address %04X opcode %02X\n%s\n", address, data, buffer);
exit(0);
}
int ill(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
M6502_dump_masked(mpu, buffer);
printf("\nill: address %04X opcode %02X memory %02X\n%s\n", address, data, mpu->memory[address], buffer);
if (data == 0x03)
{
M6502_nmi(mpu);
}
else if (data == 0x13)
{
M6502_irq(mpu);
}
return 0;
}
int oswrch(M6502 *mpu, uint16_t address, uint8_t data)
{
putchar(mpu->registers->a);
mpu->memory[0xffee] = 0x60; // RTS
return 0;
}
int main(int argc, char *argv[])
{
M6502 *mpu = M6502_new(0, 0, 0);
parse_args(argc, argv, mpu);
unsigned pc = 0x1000;
/* 0x3000 is the IRQ/BRK vector, but call callbacks don't trigger on
* interrupts, so this is only called on BRK.
*/
M6502_setCallback(mpu, call, 0x3000, brk );
M6502_setCallback(mpu, illegal_instruction, 0x03, ill );
M6502_setCallback(mpu, illegal_instruction, 0x13, ill );
M6502_setCallback(mpu, call, 0xffee, oswrch);
# define gen1(X) (mpu->memory[pc++]= (uint8_t)(X))
# define gen2(X,Y) gen1(X); gen1(Y)
# define gen3(X,Y,Z) gen1(X); gen2(Y,Z)
gen1(0x58 ); // CLI
gen2(0xa9,'A' ); // LDA #'A'
gen3(0x20,0xee,0xff); // JSR &ffee
gen1(0x03 ); // NMI
gen2(0xa9,'B' ); // LDA #'B'
gen3(0x20,0xee,0xff); // JSR &ffee
gen1(0x13 ); // IRQ
gen2(0xa9,'C' ); // LDA #'C'
gen3(0x20,0xee,0xff); // JSR &ffee
gen1(0x78 ); // SEI
gen1(0x13 ); // IRQ (ignored)
gen1(0x03 ); // NMI
gen1(0x13 ); // IRQ (ignored)
gen2(0xa9,'D' ); // LDA #'D'
gen3(0x20,0xee,0xff); // JSR &ffee
gen1(0x58 ); // CLI
gen1(0x13 ); // IRQ
gen2(0x00,0x00 ); // BRK
pc = 0x2000;
gen2(0xa9,'N' ); // LDA #'N'
gen3(0x20,0xee,0xff); // JSR &ffee
gen1(0x40 ); // RTI
pc = 0x3000;
gen2(0xa9,'I' ); // LDA #'I'
gen3(0x20,0xee,0xff); // JSR &ffee
gen1(0x40 ); // RTI
M6502_setVector(mpu, RST, 0x1000);
M6502_setVector(mpu, NMI, 0x2000);
M6502_setVector(mpu, IRQ, 0x3000);
M6502_reset(mpu);
M6502_run(mpu);
M6502_delete(mpu); /* We never reach here, but what the hey. */
return 0;
}

21
test/irq-nmi.mst Normal file
View File

@ -0,0 +1,21 @@
A
ill: address 1006 opcode 03 memory 03
PC=1007 SP=0100 A=41 X=00 Y=00 P=00 --------
NB
ill: address 100C opcode 13 memory 13
PC=100D SP=0100 A=42 X=00 Y=00 P=00 --------
IC
ill: address 1013 opcode 13 memory 13
PC=1014 SP=0100 A=43 X=00 Y=00 P=04 -----I--
ill: address 1014 opcode 03 memory 03
PC=1015 SP=0100 A=43 X=00 Y=00 P=04 -----I--
N
ill: address 1015 opcode 13 memory 13
PC=1016 SP=0100 A=4E X=00 Y=00 P=04 -----I--
D
ill: address 101C opcode 13 memory 13
PC=101D SP=0100 A=44 X=00 Y=00 P=00 --------
I
BRK: address 101D opcode 00
PC=101F SP=01FD A=49 X=00 Y=00 P=04 -----I--

1
test/pc-wrap-1.mst Normal file
View File

@ -0,0 +1 @@
Y

28
test/pc-wrap-1.xa Normal file
View File

@ -0,0 +1,28 @@
#include "config.xa"
; It's not important this is self-modifying code, this is just the easiest way
; to get code at the relevant addresses without fighting with the assembler and
; the fact run6502 will clobber the top of memory to set up various vectors.
LDA #$A9 ; LDA #n
STA $FFFE
STA $00
LDA #'N'
STA $FFFF
LDA #'Y'
STA $01
LDA #$20 ; JSR abs
STA $02
LDA #$EE
STA $03
LDA #$FF
STA $04
LDA #$4C ; JMP abs
STA $05
LDA #<QUIT
STA $06
LDA #>QUIT
STA $07
JMP $FFFE

1
test/pc-wrap-2.mst Normal file
View File

@ -0,0 +1 @@
Y

28
test/pc-wrap-2.xa Normal file
View File

@ -0,0 +1,28 @@
#include "config.xa"
; It's not important this is self-modifying code, this is just the easiest way
; to get code at the relevant addresses without fighting with the assembler and
; the fact run6502 will clobber the top of memory to set up various vectors.
LDA #$A9 ; LDA #n
STA $FFFD
STA $FFFF
LDA #'N'
STA $FFFE
LDA #'Y'
STA $00
LDA #$20 ; JSR abs
STA $01
LDA #$EE
STA $02
LDA #$FF
STA $03
LDA #$4C ; JMP abs
STA $04
LDA #<QUIT
STA $05
LDA #>QUIT
STA $06
JMP $FFFD

33
test/run-c-tests.py Executable file
View File

@ -0,0 +1,33 @@
#!/usr/bin/python
from __future__ import print_function
import subprocess
tests = [
'basic-callback',
'call-illegal-callback-modify-code',
'irq-nmi',
'setjmp-trick',
'stack-code-brk',
'stack-code-jsr',
'write-callback-modify-code'
]
test_args = [
'-mi',
'-mh',
'-mc -mx 1',
'-mc'
]
print('1..', len(tests) * len(test_args), sep='')
i = 1
for test_arg in test_args:
for test in tests:
result = subprocess.check_output(['test/' + test] + test_arg.split())
expected_result = open('test/' + test + '.mst', 'rb').read()
if result == expected_result:
print('ok', i, test, test_arg)
else:
print('not ok', i, test, test_arg)
i += 1

2
test/run-c-tests.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
python test/run-c-tests.py

59
test/run-run6502-tests.py Executable file
View File

@ -0,0 +1,59 @@
#!/usr/bin/python
from __future__ import print_function
import glob
import os
import subprocess
os.chdir('test')
# It's quite likely the "xa" assembler is not installed; don't generate
# scary test failures if that's the case.
xa_installed = True
try:
result = subprocess.check_output(['xa', '--version'])
if result.find(b'xa65') == -1:
xa_installed = False
except:
xa_installed = False
# By default we skip slow tests (those with names starting z-) in '-mc'
# modes.
skip_slow_mc = (os.getenv('RUN_SLOW_TESTS', '0') == '0')
# Since we didn't have to hard-code the test names in the Makefile.am, we
# use wildcards here.
tests = sorted([t for t in glob.glob('*.xa') if t != 'config.xa'])
test_args = [
'-mi',
'-mh',
'-mc -mx 1',
'-mc'
]
print('1..', len(tests) * len(test_args), sep='')
i = 0
for test_arg in test_args:
for test in tests:
i += 1
basename = test[0:-3]
if not xa_installed:
print('ok', i, '# skipped (xa not installed):', test, test_arg)
continue
if skip_slow_mc and basename[0:2] == 'z-' and test_arg[0:3] == '-mc':
print('ok', i, '# skipped (slow -mc):', test, test_arg)
continue
xa_out = basename + '.mc'
subprocess.check_call(['xa', '-o', xa_out, test])
result = subprocess.check_output(
['../run6502', '-l', '1e00', xa_out, '-R', '1e00', '-G', 'ffe0',
'-P', 'ffee', '-X', 'f000'] + test_arg.split())
expected_result = open(basename + '.mst', 'rb').read()
if result == expected_result:
print('ok', i, test, test_arg)
else:
print('not ok', i, test, test_arg)

2
test/run-run6502-tests.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
python test/run-run6502-tests.py

125
test/setjmp-trick.c Normal file
View File

@ -0,0 +1,125 @@
/* Copyright (c) 2005 Ian Piumarta
* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include "lib6502.h"
#include "test-utils.h"
static jmp_buf env;
int done(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
M6502_dump_masked(mpu, buffer);
printf("\nBRK instruction: address %04X opcode %02X\n%s\n", address, data, buffer);
longjmp(env, 1);
exit(0);
}
int call(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
M6502_dump_masked(mpu, buffer);
printf("\ncall: address %04X opcode %02X\n%s\n", address, data, buffer);
mpu->registers->pc = address;
longjmp(env, 2);
return 0;
}
int ill(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
M6502_dump_masked(mpu, buffer);
printf("\nill: address %04X opcode %02X memory %02X\n%s\n", address, data, mpu->memory[address], buffer);
longjmp(env, 3);
return 0;
}
int main(int argc, char *argv[])
{
M6502 *mpu = M6502_new(0, 0, 0);
parse_args(argc, argv, mpu);
unsigned pc = 0x1000;
/* Read and write callbacks don't provide the correct, up-to-date CPU state
* in the M6502 object, so this trick is a non-starter with them.
*/
M6502_setCallback(mpu, call, 0, done);
M6502_setCallback(mpu, call, 0x2000, call);
M6502_setCallback(mpu, call, 0x3000, call);
M6502_setCallback(mpu, call, 0x4000, call);
M6502_setCallback(mpu, illegal_instruction, 0x13, ill );
M6502_setCallback(mpu, illegal_instruction, 0x44, ill );
M6502_setCallback(mpu, illegal_instruction, 0x5c, ill );
# define gen1(X) (mpu->memory[pc++]= (uint8_t)(X))
# define gen2(X,Y) gen1(X); gen1(Y)
# define gen3(X,Y,Z) gen1(X); gen2(Y,Z)
gen1(0x13 );
gen1(0x44 );
gen1(0x13 ); // not executed, 0x44 is a two-byte illegal instruction
gen1(0x5C );
gen1(0x13 ); // not executed, 0x5C is a two-byte illegal instruction
gen1(0x13 ); // not executed, 0x5C is a two-byte illegal instruction
gen3(0x20,0x00,0x20); // JSR &2000
gen3(0xad,0x00,0x50); // LDA &5000
gen2(0x00,0x00 ); // BRK
pc = 0x2000;
gen3(0x8d,0x00,0x50); // STA &5000
gen3(0x4c,0x00,0x30); // JMP &3000
pc = 0x3000;
gen2(0xa9,0x00 ); // LDA #0
gen3(0x8d,0x76,0x32); // STA &3276
gen2(0xa9,0x40 ); // LDA #&40
gen3(0x8d,0x77,0x32); // STA &3277
gen3(0x6c,0x76,0x32); // JMP (&3276)
pc = 0x4000;
gen1(0x60 ); // RTS
M6502_setVector(mpu, RST, 0x1000);
M6502_reset(mpu);
while (1)
{
volatile int result = setjmp(env);
if (result == 0)
{
M6502_run(mpu);
}
else
{
printf("\nsetjmp() returned %d\n", result);
if (result == 1)
{
break;
}
}
}
M6502_delete(mpu);
return 0;
}

35
test/setjmp-trick.mst Normal file
View File

@ -0,0 +1,35 @@
ill: address 1000 opcode 13 memory 13
PC=1001 SP=0100 A=00 X=00 Y=00 P=04 -----I--
setjmp() returned 3
ill: address 1001 opcode 44 memory 44
PC=1003 SP=0100 A=00 X=00 Y=00 P=04 -----I--
setjmp() returned 3
ill: address 1003 opcode 5C memory 5C
PC=1006 SP=0100 A=00 X=00 Y=00 P=04 -----I--
setjmp() returned 3
call: address 2000 opcode 20
PC=1009 SP=01FE A=00 X=00 Y=00 P=04 -----I--
setjmp() returned 2
call: address 3000 opcode 4C
PC=3000 SP=01FE A=00 X=00 Y=00 P=04 -----I--
setjmp() returned 2
call: address 4000 opcode 6C
PC=4000 SP=01FE A=40 X=00 Y=00 P=04 -----I--
setjmp() returned 2
BRK instruction: address 100C opcode 00
PC=100E SP=01FD A=00 X=00 Y=00 P=06 -----IZ-
setjmp() returned 1

108
test/stack-code-brk.c Normal file
View File

@ -0,0 +1,108 @@
/* Copyright (c) 2005 Ian Piumarta
* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#include <stdio.h>
#include <stdlib.h>
#include "lib6502.h"
#include "test-utils.h"
int done(M6502 *mpu, uint16_t address, uint8_t data)
{
exit(0);
}
int oswrch(M6502 *mpu, uint16_t address, uint8_t data)
{
putchar(mpu->registers->a);
mpu->memory[0xffee] = 0x60; // RTS
return 0;
}
# define gen1(X) (mpu->memory[pc++]= (uint8_t)(X))
# define gen2(X,Y) gen1(X); gen1(Y)
# define gen3(X,Y,Z) gen1(X); gen2(Y,Z)
int main(int argc, char *argv[])
{
M6502 *mpu = M6502_new(0, 0, 0);
parse_args(argc, argv, mpu);
unsigned pc = 0x1000;
unsigned saved_pc;
M6502_setCallback(mpu, call, 0xf000, done );
M6502_setCallback(mpu, call, 0xffee, oswrch);
gen2(0xa2, 0xff ); // LDX #&FF
gen1(0x9a ); // TXS
gen2(0xa9, 'A' ); // LDA #'A'
// LDA #'B' is 0xa9, 0x42. So if we execute a BRK at 0x42a7, it will
// push 0x42, 0xa9 and the flags onto the stack. Since the stack grows
// downwards those bytes will be in the right order for execution. We'll
// additionally push an LDX immediate opcode so we can "execute" the flags
// value. We can nearly force the flags to be whatever we like using PLP,
// although the BRK will set the B and X bits in the stacked value. We
// demonstrate this by explicitly masking off those bits in the values we
// force into the flags.
enum {
flagX= (1<<5), /* unused */
flagB= (1<<4) /* irq from brk */
};
uint8_t mask = ~(flagX | flagB);
gen2(0xa0, '0' & mask); // LDY #('0' with B/X masked off)
gen1(0x5a ); // PHY
gen1(0x28 ); // PLP
gen3(0x4c, 0xa7, 0x42); // JMP &42A7
pc = 0x42a7;
gen2(0x00, 0x00 ); // BRK
saved_pc = pc;
pc = 0x0; // BRK vector
gen2(0xa9, 0xa2 ); // LDA #<LDX # opcode>
gen1(0x48 ); // PHA
gen3(0x4c, 0xfc, 0x01); // JMP &01FC
pc = 0x200;
gen3(0x20, 0xee, 0xff); // JSR &FFEE
gen1(0x8a ); // TXA
gen3(0x20, 0xee, 0xff); // JSR &FFEE
gen1(0x68 ); // PLA
gen1(0x40 ); // RTI
pc = saved_pc;
// Let's do the same thing again, but this time code has already been
// executed from that address on the stack, so we're verifying the change
// is picked up. We do LDA #'C' this time, so we execute the BRK from
// 0x43a7.
gen2(0xa0, '1' & mask); // LDY #('1' with B/X masked off)
gen1(0x5a ); // PHY
gen1(0x28 ); // PLP
gen3(0x4c, 0xa7, 0x43); // JMP &43A7
pc = 0x43a7;
gen2(0x00, 0x00 ); // BRK
gen3(0x4c, 0x00, 0xf0); // JMP &F000 (quit)
M6502_setVector(mpu, RST, 0x1000);
M6502_reset(mpu);
M6502_run(mpu);
M6502_delete(mpu); /* We never reach here, but what the hey. */
return 0;
}

1
test/stack-code-brk.mst Normal file
View File

@ -0,0 +1 @@
B0C1

90
test/stack-code-jsr.c Normal file
View File

@ -0,0 +1,90 @@
/* Copyright (c) 2005 Ian Piumarta
* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#include <stdio.h>
#include <stdlib.h>
#include "lib6502.h"
#include "test-utils.h"
int done(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
M6502_dump_masked(mpu, buffer);
printf("\nBRK instruction: address %04X opcode %02X\n%s\n", address, data, buffer);
exit(0);
}
int oswrch(M6502 *mpu, uint16_t address, uint8_t data)
{
putchar(mpu->registers->a);
mpu->memory[0xffee] = 0x60; // RTS
return 0;
}
# define gen1(X) (mpu->memory[pc++]= (uint8_t)(X))
# define gen2(X,Y) gen1(X); gen1(Y)
# define gen3(X,Y,Z) gen1(X); gen2(Y,Z)
int main(int argc, char *argv[])
{
M6502 *mpu = M6502_new(0, 0, 0);
parse_args(argc, argv, mpu);
unsigned pc = 0x1000;
unsigned saved_pc;
M6502_setCallback(mpu, call, 0, done );
M6502_setCallback(mpu, call, 0xffee, oswrch);
gen2(0xa2, 0xff ); // LDX #&FF
gen1(0x9a ); // TXS
gen2(0xa9, 'A' ); // LDA #'A'
// LDA #'B' is 0xa9, 0x42. So if we execute a JSR at 0x42a7, it will
// push 0x42 and then 0xa9 onto the stack. Since the stack grows downwards
// those bytes will be in the right order for execution.
gen3(0x4c, 0xa7, 0x42); // JMP &42A7
pc = 0x42a7;
gen3(0x20, 0x00, 0x30); // JSR &3000
saved_pc = pc;
pc = 0x3000;
gen3(0x4c, 0xfe, 0x01); // JMP &01FE
pc = 0x200;
gen3(0x20, 0xee, 0xff); // JSR &FFEE
gen1(0x60 ); // RTS
pc = saved_pc;
// Let's do the same thing again, but this time code has already been
// executed from that address on the stack, so we're verifying the change
// is picked up. We do LDA #'C' this time, so we execute the JSR from
// 0x43a7.
gen3(0x4c, 0xa7, 0x43); // JMP &43A7
pc = 0x43a7;
gen3(0x20, 0x00, 0x30); // JSR &3000
gen2(0x00, 0x00 ); // BRK
M6502_setVector(mpu, RST, 0x1000);
M6502_reset(mpu);
M6502_run(mpu);
M6502_delete(mpu); /* We never reach here, but what the hey. */
return 0;
}

3
test/stack-code-jsr.mst Normal file
View File

@ -0,0 +1,3 @@
BC
BRK instruction: address 43AA opcode 00
PC=43AC SP=01FC A=43 X=FF Y=00 P=04 -----I--

106
test/test-utils.c Normal file
View File

@ -0,0 +1,106 @@
/* parse-args.c -- utility function for C test programs */
/* Copyright (c) 2005 Ian Piumarta
* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
/* Some of this code is copy-and-pasted from run6502.c, but there's not enough
* of it for me to want to complicate things even slightly by trying to share
* it, especially since this is test code and somewhat distinct.
*/
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lib6502.h"
static const char *program= 0;
static M6502_Mode mode= M6502_ModeHybrid;
static int max_insns= 0; /* default */
enum {
flagX= (1<<5), /* unused */
flagB= (1<<4) /* irq from brk */
};
void fail(const char *fmt, ...)
{
va_list ap;
fflush(stdout);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
exit(1);
}
static void usage(int status)
{
FILE *stream = stderr;
fprintf(stream, "usage: %s [option ...]\n", program);
fprintf(stream, " -h -- help (print this message)\n");
fprintf(stream, " -mc -- use compiled emulation mode\n");
fprintf(stream, " -mh -- use hybrid emulation mode (default)\n");
fprintf(stream, " -mi -- use interpreted emulation mode\n");
fprintf(stream, " -mx count -- maximum instructions to JIT (-mc/-mh)\n");
exit(status);
}
static int doMode(M6502_Mode m)
{
mode= m;
return 0;
}
static int doMaxInsns(int argc, char **argv, M6502 *mpu)
{
if (argc < 2) usage(1);
char *end;
unsigned long l= strtol(argv[1], &end, 10);
if (*end) fail("bad number: %s", argv[1]);
max_insns= l;
return 1;
}
void parse_args(int argc, char *argv[], M6502 *mpu)
{
program= argv[0];
while (++argv, --argc > 0)
{
int n= 0;
if (!strcmp(*argv, "-h")) usage(0);
else if (!strcmp(*argv, "-mc")) n= doMode(M6502_ModeCompiled);
else if (!strcmp(*argv, "-mh")) n= doMode(M6502_ModeHybrid);
else if (!strcmp(*argv, "-mi")) n= doMode(M6502_ModeInterpreted);
else if (!strcmp(*argv, "-mx")) n= doMaxInsns(argc, argv, mpu);
else usage(1);
argc -= n;
argv += n;
}
M6502_setMode(mpu, mode, max_insns);
}
void M6502_dump_masked(M6502 *mpu, char buffer[64])
{
uint8_t orig_p = mpu->registers->p;
mpu->registers->p &= ~(flagB | flagX);
M6502_dump(mpu, buffer);
mpu->registers->p = orig_p;
}

30
test/test-utils.h Normal file
View File

@ -0,0 +1,30 @@
/* test-utils.h -- utility functions for C test programs */
/* Copyright (c) 2005 Ian Piumarta
* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#ifndef PARSEARGS_H
#define PARSEARGS_H
#include "lib6502.h"
void parse_args(int argc, char *argv[], M6502 *mpu);
void M6502_dump_masked(M6502 *mpu, char buffer[64]);
#endif

1
test/trivial-test.mst Normal file
View File

@ -0,0 +1 @@
Y

5
test/trivial-test.xa Normal file
View File

@ -0,0 +1,5 @@
#include "config.xa"
LDA #'Y'
JSR OSWRCH
JMP QUIT

View File

@ -0,0 +1,100 @@
/* Copyright (c) 2005 Ian Piumarta
* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#include <stdio.h>
#include <stdlib.h>
#include "lib6502.h"
#include "test-utils.h"
int done(M6502 *mpu, uint16_t address, uint8_t data)
{
char buffer[64];
M6502_dump_masked(mpu, buffer);
printf("\nBRK instruction: address %04X opcode %02X\n%s\n", address, data, buffer);
exit(0);
}
int oswrch(M6502 *mpu, uint16_t address, uint8_t data)
{
putchar(mpu->registers->a);
mpu->memory[0xffee] = 0x60; // RTS
return 0;
}
# define gen1(X) (mpu->memory[pc++]= (uint8_t)(X))
# define gen2(X,Y) gen1(X); gen1(Y)
# define gen3(X,Y,Z) gen1(X); gen2(Y,Z)
int wr(M6502 *mpu, uint16_t address, uint8_t data)
{
if (address != 0x42)
{
abort();
}
unsigned pc = 0x6000;
gen2(0xa9, data); // LDA #data
gen3(0x4c, 0x00, 0x20); // JMP &2000
return 0;
}
int main(int argc, char *argv[])
{
M6502 *mpu = M6502_new(0, 0, 0);
parse_args(argc, argv, mpu);
unsigned pc = 0x1000;
M6502_setCallback(mpu, call, 0, done);
M6502_setCallback(mpu, call, 0xffee, oswrch);
M6502_setCallback(mpu, write, 0x42, wr );
gen2(0xa9, '>' ); // LDA #'>'
gen3(0x20, 0xee, 0xff); // JSR &FFEE
gen2(0xa2, 'A' ); // LDX #'A'
gen3(0x8e, 0x42, 0x00); // STX &0042
gen3(0x20, 0x00, 0x60); // JSR &6000
gen1(0xe8 ); // INX
gen2(0xe0, 'Z'+1 ); // CPX #('Z'+1)
gen2(0x90, 0xf5 ); // BCC to STX
gen2(0xa0, 0x05 ); // LDY #&05
gen2(0xa9, '>' ); // LDA #'>'
gen3(0x20, 0xee, 0xff); // JSR &FFEE
gen2(0xa2, 'A' ); // LDX #'A'
gen2(0x96, 0x42-0x05 ); // STX (&42-&05),Y
gen3(0x20, 0x00, 0x60); // JSR &6000
gen1(0xe8 ); // INX
gen2(0xe0, 'Z'+1 ); // CPX #('Z'+1)
gen2(0x90, 0xf6 ); // BCC to STX
gen2(0x00, 0x00 ); // BRK
pc = 0x2000;
gen3(0x20, 0xee, 0xff); // JSR &FFEE
gen1(0x60 ); // RTS
M6502_setVector(mpu, RST, 0x1000);
M6502_reset(mpu);
M6502_run(mpu);
M6502_delete(mpu); /* We never reach here, but what the hey. */
return 0;
}

View File

@ -0,0 +1,3 @@
>ABCDEFGHIJKLMNOPQRSTUVWXYZ>ABCDEFGHIJKLMNOPQRSTUVWXYZ
BRK instruction: address 1025 opcode 00
PC=1027 SP=01FD A=5A X=5B Y=05 P=07 -----IZC

1
test/z-self-modify-1.mst Normal file
View File

@ -0,0 +1 @@
Y

94
test/z-self-modify-1.xa Normal file
View File

@ -0,0 +1,94 @@
; This test attempts to confirm that in hybrid mode, the JITted code is
; discarded correctly if it's modified by the interpreter.
#include "config.xa"
COUNT1 = $71
COUNT2 = $72
COUNT3 = $73
; We loop lots to get as much chance of a problem occurring as possible.
STZ COUNT1
LOOP1
LDY #0
LOOP2
LDX #0
LOOP3
; The heart of the test. We LDA #n, then CMP <address of n>. If the two don't
; match we have a problem.
LDAOP
LDA #3
CMP LDAOP+1
BNE FAIL
; We now modify the LDA operand...
INC LDAOP+1
; ... and occupy as much of the interpreter's time as possible while the JIT
; thread picks up the modified version (if it's not working from the snapshot).
; In reality we probably go round multiple times before the JIT completes.
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
; And round and round we go.
DEX
BNE LOOP3
DEY
BNE LOOP2
DEC COUNT1
BNE LOOP1
OK
LDA #'Y'
JSR OSWRCH
JMP QUIT
FAIL
LDA #'N'
JSR OSWRCH
JMP QUIT

1
test/z-self-modify-2.mst Normal file
View File

@ -0,0 +1 @@
Y

125
test/z-self-modify-2.xa Normal file
View File

@ -0,0 +1,125 @@
; This test attempts to confirm that as subtle potential bug in the hybrid JIT
; implementation is not present.
;
; The potential problem is as follows:
; - we decide to JIT some code
; - we take a snapshot of memory
; - we kick off a JIT thread which *works off the main memory array*, not the
; snapshot
; - in the meantime the interpreter executes some code which modifies the code
; being JITted before it is actually jitted.
; - we JIT the modified version of the code
; - the interpreter then executes some code which reverts the change (A)
; - we decide to execute the JITted function. We check memory against the memory
; snapshot taken when we started JITting and find no differences in any
; addresses which contain code, because of the previous step marked (A).
; - boom, our JITted code is not doing what it should.
;
; The fix for this problem is simply to ensure that the JIT thread works off
; the snapshot of memory taken when we launched the JIT thread. Note that even
; if we fail to do this, self-modifying code which doesn't "undo" itself will
; be noticed when we use the memory snapshot to decide if the JITted code is
; still valid.
;
; This test case should execute correctly in all modes (of course), but in
; hybrid mode it should *fail* if the implementation is temporarily changed to
; JIT from mpu->memory and not memory_snapshot. At the time of writing it does.
#include "config.xa"
COUNT1 = $71
COUNT2 = $72
COUNT3 = $73
; We loop lots to get as much chance of a problem occurring as possible.
STZ COUNT1
LOOP1
LDY #0
LOOP2
LDX #0
LOOP3
; The heart of the test. We LDA #n, then CMP <address of n>. If the two don't
; match we have a problem.
LDAOP
LDA #3
CMP LDAOP+1
BNE FAIL
; We now modify the LDA operand...
INC LDAOP+1
; ... and occupy as much of the interpreter's time as possible while the JIT
; thread picks up the modified version (if it's not working from the snapshot).
; In reality we probably go round multiple times before the JIT completes.
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
; We now put the operand back. Since we only switch from interpreting to JITting
; on a control transfer, we know the transition will occur at a point when we've
; put the operand back, which is helpful.
DEC LDAOP+1
; And round and round we go.
DEX
BNE LOOP3
DEY
BNE LOOP2
DEC COUNT1
BNE LOOP1
OK
LDA #'Y'
JSR OSWRCH
JMP QUIT
FAIL
LDA #'N'
JSR OSWRCH
JMP QUIT

57
util.cpp Normal file
View File

@ -0,0 +1,57 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#include "util.h"
#include <boost/thread/thread.hpp>
#include <iostream>
#include <stdio.h>
boost::mutex log_mutex;
void log(const std::string &s)
{
boost::mutex::scoped_lock scoped_lock(log_mutex);
std::cerr << s << std::endl;
}
void die(const char *s)
{
fflush(stdout);
fprintf(stderr, "\n%s\n", s);
abort();
}
std::string spaces(int n)
{
return std::string(4 * n, ' ');
}
std::string apply_prefix(const std::string &prefix, const std::string &s)
{
std::string result = prefix;
for (std::string::size_type i = 0; i < s.length(); ++i)
{
result += s[i];
if ((s[i] == '\n') && ((i + 1) < s.length()))
{
result.append(prefix);
}
}
return result;
}

73
util.h Normal file
View File

@ -0,0 +1,73 @@
/* Copyright (c) 2014 Steven Flintham
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the 'Software'),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, provided that the above copyright notice(s) and this
* permission notice appear in all copies of the Software and that both the
* above copyright notice(s) and this permission notice appear in supporting
* documentation.
*
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
*/
#ifndef UTIL_H
#define UTIL_H
#include <assert.h>
#include <iomanip>
#include <sstream>
#include <stdexcept>
#include <boost/thread/mutex.hpp>
#include <boost/thread/thread.hpp>
#define CANT_HAPPEN(s) \
do { \
std::stringstream stream; \
stream << __FILE__ << ":" << __LINE__ << ":" << s; \
throw std::runtime_error(stream.str()); \
} \
while (false)
#ifdef LOG
#define TRACE(s) \
do { \
std::stringstream prefix; \
prefix << __FILE__ << ":" << __LINE__ << "\t" << \
boost::this_thread::get_id() << "\t"; \
std::stringstream message; \
message << s; \
log(apply_prefix(prefix.str(), message.str())); \
} \
while (false)
#else
#define TRACE(s) \
do { \
} \
while (false)
#endif
// Avoid spurious "unused variable" warnings from regular assert().
#ifndef NDEBUG
#define ASSERT_EQUAL(x, y) assert((x) == (y))
#else
#define ASSERT_EQUAL(x, y) \
do { \
x = x; \
} \
while (0);
#endif
extern boost::mutex log_mutex;
void log(const std::string &s);
void die(const char *s);
std::string spaces(int n);
std::string apply_prefix(const std::string &prefix, const std::string &s);
#endif

4060
valgrind.h Normal file

File diff suppressed because it is too large Load Diff