lib6502-jit/FunctionBuilder.cpp

3572 lines
118 KiB
C++

/* 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 "FunctionBuilder.h"
// Throughout this file we must be careful to avoid incorrect wrap-around
// handling; for example, it's wrong to do memory[pc + 2] because if pc is
// 0xffff this will access off the end of memory. We must always use uint16_t
// intermediate values to get the right wrapping behaviour. Similar
// considerations apply when using zero-page addressing; we must ensure we wrap
// around at 0xff.
#include "config.h"
#include <algorithm>
#include <assert.h>
#include <iomanip>
#include "llvm/Analysis/Passes.h"
#include "llvm/ExecutionEngine/ExecutionEngine.h"
#include "llvm/ExecutionEngine/JIT.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/TypeBuilder.h"
#if defined HAVE_LLVM_ANALYSIS_VERIFIER_H
#include "llvm/Analysis/Verifier.h"
#elif defined HAVE_LLVM_IR_VERIFIER_H
#include "llvm/IR/Verifier.h"
#else
#error Need LLVM Verifier.h
#endif
#include "llvm/PassManager.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Transforms/Scalar.h"
#include <sstream>
#include "AddressRange.h"
#include "const.h"
#include "Function.h"
#include "LLVMStuff.h"
#include "M6502Internal.h"
#include "Registers.h"
#include "util.h"
namespace llvm
{
template<bool xcompile>
class TypeBuilder<M6502, xcompile>
{
public:
static StructType *get(LLVMContext &context)
{
static StructType *t = StructType::create(context, "M6502");
return t;
}
};
template<bool xcompile>
class TypeBuilder<Registers, xcompile>
{
public:
static StructType *get(LLVMContext &context)
{
static StructType *t = StructType::create("Registers",
TypeBuilder<types::i<8>, xcompile>::get(context), // a
TypeBuilder<types::i<8>, xcompile>::get(context), // x
TypeBuilder<types::i<8>, xcompile>::get(context), // y
TypeBuilder<types::i<8>, xcompile>::get(context), // s
TypeBuilder<JitBool , xcompile>::get(context), // flag_n
TypeBuilder<JitBool , xcompile>::get(context), // flag_v
TypeBuilder<JitBool , xcompile>::get(context), // flag_d
TypeBuilder<JitBool , xcompile>::get(context), // flag_i
TypeBuilder<JitBool , xcompile>::get(context), // flag_z
TypeBuilder<JitBool , xcompile>::get(context), // flag_c
TypeBuilder<types::i<16>, xcompile>::get(context), // pc
TypeBuilder<types::i<16>, xcompile>::get(context), // addr
TypeBuilder<types::i<8>, xcompile>::get(context), // data
NULL);
return t;
}
};
}
namespace
{
const std::string hex_prefix = "&";
bool callback_in_bounds(const M6502_Callback *callbacks,
const AddressRange &bounds)
{
for (AddressRange::const_iterator it = bounds.begin();
it != bounds.end(); ++it)
{
if (callbacks[*it] != 0)
{
return true;
}
}
return false;
}
}
// BoundedAddress contains an llvm::Value of type i16 which refers to
// an address in the emulated memory. It additionally contains a range of
// possible addresses which the llvm::Value can evaluate to (derived from the
// addressing mode which created it). This is used to optimise the generated
// code.
class FunctionBuilder::BoundedAddress
{
public:
// Construct a BoundedAddress with the widest possible bounds; this
// is always safe, but if possible should be avoided as it reduces
// optimisation potential.
BoundedAddress(FunctionBuilder &fb, llvm::Value *addr);
// Construct a BoundedAddress with the given bounds.
BoundedAddress(FunctionBuilder &fb, llvm::Value *addr,
const AddressRange &bounds);
llvm::Value *addr() const
{
return addr_;
}
const AddressRange &bounds() const
{
return bounds_;
}
friend
std::ostream &operator<<(std::ostream &s, const BoundedAddress &ba)
{
std::stringstream t;
t << "[0x" << std::hex << std::setfill('0') << std::setw(4) <<
ba.bounds().range_begin() << ", 0x" << std::setw(4) <<
ba.bounds().range_end() << ")";
s << t.str();
return s;
}
private:
llvm::Value *addr_;
AddressRange bounds_;
};
FunctionBuilder::BoundedAddress::BoundedAddress(
FunctionBuilder &fb, llvm::Value *addr)
: addr_(addr), bounds_(0, memory_size)
{
assert(addr->getType() == fb.i16_type_);
}
FunctionBuilder::BoundedAddress::BoundedAddress(
FunctionBuilder &fb, llvm::Value *addr, const AddressRange &bounds)
: addr_(addr), bounds_(bounds)
{
assert(addr->getType() == fb.i16_type_);
#ifndef NDEBUG
llvm::ConstantInt *addr_ci = llvm::dyn_cast<llvm::ConstantInt>(addr);
if (addr_ci != 0)
{
// We can verify the claimed bounds at compile time.
uint16_t addr16 = addr_ci->getLimitedValue();
assert(addr16 == bounds.range_begin());
assert(addr16 == (bounds.range_end() - 1));
}
else
{
// We can't verify the claimed bounds at compile time, so generate code
// to check at runtime.
llvm::BasicBlock *bounds_maybe_ok_block =
llvm::BasicBlock::Create(fb.context_, "bounds_maybe_ok_block",
fb.llvm_function_);
llvm::BasicBlock *bounds_not_ok_block =
llvm::BasicBlock::Create(fb.context_, "bounds_not_ok");
llvm::BasicBlock *bounds_ok_block =
llvm::BasicBlock::Create(fb.context_, "bounds_ok");
if (bounds.range_end() <= memory_size)
{
TRACE("Generating bounds check code for non-wrapped case");
llvm::Value *lower_bound_ok =
fb.builder_.CreateICmpUGE(
addr, fb.constant_u16(bounds.range_begin()));
fb.builder_.CreateCondBr(lower_bound_ok, bounds_maybe_ok_block,
bounds_not_ok_block);
fb.builder_.SetInsertPoint(bounds_maybe_ok_block);
llvm::Value *upper_bound_ok =
fb.builder_.CreateICmpULE(
addr, fb.constant_u16(bounds.range_end() - 1));
fb.builder_.CreateCondBr(upper_bound_ok, bounds_ok_block,
bounds_not_ok_block);
}
else
{
TRACE("Generating bounds check code for wrapped case");
llvm::Value *in_upper_range =
fb.builder_.CreateICmpUGE(
addr, fb.constant_u16(bounds.range_begin()));
fb.builder_.CreateCondBr(in_upper_range, bounds_ok_block,
bounds_maybe_ok_block);
fb.builder_.SetInsertPoint(bounds_maybe_ok_block);
// We want to truncate bounds.range_end() - 1 to 16 bits here.
llvm::Value *in_lower_range =
fb.builder_.CreateICmpULE(
addr, fb.constant_u16(bounds.range_end() - 1));
fb.builder_.CreateCondBr(in_lower_range, bounds_ok_block,
bounds_not_ok_block);
}
fb.llvm_function_->getBasicBlockList().push_back(bounds_not_ok_block);
fb.builder_.SetInsertPoint(bounds_not_ok_block);
fb.return_invalid_bounds();
fb.llvm_function_->getBasicBlockList().push_back(bounds_ok_block);
fb.builder_.SetInsertPoint(bounds_ok_block);
}
#endif
}
FunctionBuilder::FunctionBuilder(
M6502 *mpu, const uint8_t *ct_memory, JitBool *code_at_address,
uint16_t address)
: built_(false),
mpu_(mpu),
code_at_address_(code_at_address),
address_(address),
ct_memory_(ct_memory),
callbacks_(*(mpu->callbacks)),
instructions_(0),
max_instructions_(std::max(1, mpu->internal->max_instructions_)),
context_(llvm::getGlobalContext()),
native_int_type_(llvm::TypeBuilder<int, false>::get(context_)),
callback_type_(llvm::TypeBuilder<M6502_Callback, false>::get(context_)),
i1_type_(llvm::TypeBuilder<llvm::types::i<1>, false>::get(context_)),
i8_type_(llvm::TypeBuilder<llvm::types::i<8>, false>::get(context_)),
i16_type_(llvm::TypeBuilder<llvm::types::i<16>, false>::get(context_)),
i32_type_(llvm::TypeBuilder<llvm::types::i<32>, false>::get(context_)),
i64_type_(llvm::TypeBuilder<llvm::types::i<64>, false>::get(context_)),
jit_bool_type_(llvm::TypeBuilder<JitBool, false>::get(context_)),
builder_(mpu_->internal->llvm_stuff_.builder_),
address_block_(),
code_generated_for_address_()
{
llvm::FunctionType *ft = llvm::TypeBuilder<int(), false>::get(context_);
std::stringstream name;
name << "x" << std::hex << std::setw(4) << std::setfill('0') << address_;
llvm_function_ = llvm::Function::Create(
ft, llvm::Function::PrivateLinkage, name.str(),
mpu_->internal->llvm_stuff_.module_.get());
llvm::BasicBlock *BB =
llvm::BasicBlock::Create(context_, "prologue", llvm_function_);
builder_.SetInsertPoint(BB);
mpu_llvm_ = constant_ptr(mpu, "mpu");
code_at_address_llvm_ = constant_ptr(code_at_address, "code_at_address");
registers_ = constant_ptr(&(mpu->internal->registers_), "registers");
read_callbacks_ = constant_ptr(callbacks_.read, "read_callbacks");
write_callbacks_ = constant_ptr(callbacks_.write, "write_callbacks");
call_callbacks_ = constant_ptr(callbacks_.call, "call_callbacks");
memory_base_ = constant_ptr(mpu->memory, "memory");
function_result_ =
builder_.CreateAlloca(native_int_type_, 0, "function_result");
// Function prologue: Copy the registers from Registers into local
// variables for use. The epilogue will reverse this process before the
// function returns for registers which actually get modified. (The
// LLVM optimiser is then able to remove loads which would just load
// unused values.)
initialise_i8_reg(a_ , 0, "a");
initialise_i8_reg(x_ , 1, "x");
initialise_i8_reg(y_ , 2, "y");
initialise_i8_reg(s_ , 3, "s");
initialise_jb_reg(flag_n_, 4, "flag_n");
initialise_jb_reg(flag_v_, 5, "flag_v");
initialise_jb_reg(flag_d_, 6, "flag_d");
initialise_jb_reg(flag_i_, 7, "flag_i");
initialise_jb_reg(flag_z_, 8, "flag_z");
initialise_jb_reg(flag_c_, 9, "flag_c");
pc_ = builder_.CreateAlloca(i16_type_, 0, "pc");
builder_.CreateStore(
builder_.CreateLoad(
builder_.CreateStructGEP(registers_, 10), false, "pc"),
pc_);
// Temporary variable used when invoking read callbacks; no need to
// initialise.
read_callback_result_ =
builder_.CreateAlloca(i8_type_, 0, "read_callback_result");
// Temporary variables for ADC/SBC implementation; no need to initialise.
p_tmp_ = builder_.CreateAlloca(i8_type_, 0, "p_tmp");
l_tmp_ = builder_.CreateAlloca(i8_type_, 0, "l_tmp");
s_tmp_ = builder_.CreateAlloca(i16_type_, 0, "s_tmp");
t_tmp_ = builder_.CreateAlloca(i16_type_, 0, "t_tmp");
epilogue_ = llvm::BasicBlock::Create(context_, "epilogue");
}
// The Register objects are initialised using these functions instead of
// constructors mainly because we need a builder_ with an associated BasicBlock
// to initialise a Register, and we don't have that when the FunctionBuilder
// object is first constructed.
void FunctionBuilder::initialise_i8_reg(
Register &r, int structure_index, const std::string &name)
{
llvm::Value *v = builder_.CreateAlloca(i8_type_, 0, name);
builder_.CreateStore(
builder_.CreateLoad(
builder_.CreateStructGEP(registers_, structure_index), false, name),
v);
r.v_ = v;
r.modified_ = false;
}
void FunctionBuilder::initialise_jb_reg(
Register &r, int structure_index, const std::string &name)
{
llvm::Value *v = builder_.CreateAlloca(jit_bool_type_, 0, name);
builder_.CreateStore(
builder_.CreateLoad(
builder_.CreateStructGEP(registers_, structure_index), false, name),
v);
r.v_ = v;
r.modified_ = false;
}
void FunctionBuilder::ensure_address_block_created(uint16_t addr)
{
if (address_block_[addr] == 0)
{
std::stringstream s;
s << "l" << std::hex << std::setw(4) << std::setfill('0') << addr;
address_block_[addr] =
llvm::BasicBlock::Create(context_, s.str(), llvm_function_);
}
}
boost::shared_ptr<Function> FunctionBuilder::build()
{
// This can't be invoked twice on the same FunctionBuilder object;
// at present, for example, attempts to insert into 'epilogue_' crash
// (presumably because it's been used to generate code already). There
// is no reason to do this and I'm not going to convolute things to make
// this pointless case work. Even asserting that this doesn't happen
// seems like overkill, but let's do it anyway.
assert(!built_);
// While it doesn't strictly matter, the fact that pending_ is a std::set
// means it will internally sort the addresses. This makes it more likely
// that multiple backward jumps will only result in one stretch of code
// being produced, since the furthest jump backwards will be JITted first.
pending_.insert(address_);
while (!pending_.empty())
{
// We take addresses to JIT at from pending_ to start with, and when
// there's no "better" address...
uint16_t ct_pc = *(pending_.begin());
// ... but if we can continue JITting where we left off, we prefer
// to do that. Since each block of code emitted by build_at() is
// independent, this doesn't alter the behaviour of the generated
// code, but it avoids gratuitous discontinuities in the generated
// code compared with the source machine code.
do
{
pending_.erase(ct_pc);
uint16_t new_ct_pc = build_at(ct_pc);
if (new_ct_pc == ct_pc)
{
// build_at() did no work.
}
else if (new_ct_pc > ct_pc)
{
code_range_.insert(AddressRange(ct_pc, new_ct_pc));
}
else
{
// PC wrapped around during the translation.
uint32_t range_end = new_ct_pc;
range_end += memory_size;
code_range_.insert(AddressRange(ct_pc, range_end));
}
ct_pc = new_ct_pc;
}
while (pending_.find(ct_pc) != pending_.end());
}
LLVMStuff &llvm_stuff = mpu_->internal->llvm_stuff_;
llvm::FunctionPassManager fpm(llvm_stuff.module_.get());
#ifdef HAVE_LLVM_DATA_LAYOUT_PASS
fpm.add(new llvm::DataLayoutPass(llvm_stuff.module_.get()));
#else
fpm.add(
new llvm::DataLayout(*llvm_stuff.execution_engine_->getDataLayout()));
#endif
fpm.add(llvm::createBasicAliasAnalysisPass());
fpm.add(llvm::createPromoteMemoryToRegisterPass());
fpm.add(llvm::createInstructionCombiningPass());
fpm.add(llvm::createReassociatePass());
fpm.add(llvm::createGVNPass());
fpm.add(llvm::createCFGSimplificationPass());
fpm.doInitialization();
// We could have passed llvm_function_ to BasicBlock::Create() earlier
// and then we wouldn't need to do this push_back() here, but doing
// this means the epilogue appears at the end of the IR. It makes no
// functional difference but it seems slightly more logical to read.
llvm_function_->getBasicBlockList().push_back(epilogue_);
builder_.SetInsertPoint(epilogue_);
if (a_.modified_)
{
builder_.CreateStore(
builder_.CreateLoad(a_.v_),
builder_.CreateStructGEP(registers_, 0));
}
if (x_.modified_)
{
builder_.CreateStore(
builder_.CreateLoad(x_.v_),
builder_.CreateStructGEP(registers_, 1));
}
if (y_.modified_)
{
builder_.CreateStore(
builder_.CreateLoad(y_.v_),
builder_.CreateStructGEP(registers_, 2));
}
if (s_.modified_)
{
builder_.CreateStore(
builder_.CreateLoad(s_.v_),
builder_.CreateStructGEP(registers_, 3));
}
if (flag_n_.modified_)
{
builder_.CreateStore(
register_load(flag_n_),
builder_.CreateStructGEP(registers_, 4));
}
if (flag_v_.modified_)
{
builder_.CreateStore(
register_load(flag_v_),
builder_.CreateStructGEP(registers_, 5));
}
if (flag_d_.modified_)
{
builder_.CreateStore(
register_load(flag_d_),
builder_.CreateStructGEP(registers_, 6));
}
if (flag_i_.modified_)
{
builder_.CreateStore(
register_load(flag_i_),
builder_.CreateStructGEP(registers_, 7));
}
if (flag_z_.modified_)
{
builder_.CreateStore(
register_load(flag_z_),
builder_.CreateStructGEP(registers_, 8));
}
if (flag_c_.modified_)
{
builder_.CreateStore(
register_load(flag_c_),
builder_.CreateStructGEP(registers_, 9));
}
builder_.CreateStore(
builder_.CreateLoad(pc_),
builder_.CreateStructGEP(registers_, 10));
builder_.CreateRet(builder_.CreateLoad(function_result_));
#ifdef LOG
std::string unoptimised_ir;
{
llvm::raw_string_ostream s(unoptimised_ir);
llvm_function_->print(s);
s.str();
}
#endif
llvm::verifyFunction(*llvm_function_);
fpm.run(*llvm_function_);
#ifdef LOG
std::string optimised_ir;
{
llvm::raw_string_ostream s(optimised_ir);
llvm_function_->print(s);
s.str();
}
#endif
boost::shared_ptr<Function> f(
new Function(mpu_, address_, code_range_, optimistic_writes_,
llvm_function_));
#ifdef LOG
f->set_disassembly(disassembly_.str());
f->set_unoptimised_ir(unoptimised_ir);
f->set_optimised_ir(optimised_ir);
#endif
built_ = true;
return f;
}
// This translates a linear stream of 6502 instructions into LLVM IR. The
// generation stops either when we've translated enough 6502 instructions
// or when we hit an instruction which unconditionally transfers control
// elsewhere. Branch targets found during the translation are added to pending_
// for further consideration; at a minimum, address_block[] entries with
// associated code to transfer control to those addresses must be generated
// for each of these before terminating the build process for the function.
//
// The address of the first byte not translated is returned.
uint16_t FunctionBuilder::build_at(uint16_t ct_pc)
{
TRACE("Translating linear stream of instructions at 0x" << std::hex <<
std::setfill('0') << std::setw(4) << ct_pc);
const uint16_t original_ct_pc = ct_pc;
// If we already translated this stretch of code, we don't need to do
// anything at all.
if (code_generated_for_address_[ct_pc])
{
TRACE("Already translated this linear stream");
return ct_pc;
}
while (true)
{
TRACE("Translating at 0x" << std::hex << std::setfill('0') <<
std::setw(4) << ct_pc << ", opcode 0x" << std::setw(2) <<
static_cast<int>(ct_memory_[ct_pc]));
const uint16_t this_opcode_at = ct_pc;
if (code_generated_for_address_[ct_pc])
{
// We already translated this instruction, so we can stop
// translating and just jump there. Since this is just linear
// flow of control from the perspective of the 6502 code, this
// cannot trigger a call callback.
TRACE("Already translated this instruction");
if (builder_.GetInsertBlock()->getTerminator() == 0)
{
control_transfer_to(constant_u16(ct_pc), opcode_implicit);
}
break;
}
// Each instruction forms its own basic block (since we build up the
// IR as we go, we can't know where we might want to branch into,
// so we cannot merge multiple instructions into a single basic
// block). Basic blocks must end with a terminator, so if there isn't
// already a terminator at the end of the previous instruction's basic
// block, we insert an unconditional branch to this instruction's
// basic block. If there is already a terminator, we stop translating
// this stream of instructions unless this is the first instruction
// in this linear sequence; this way we avoid generating unreachable
// code if the previous instruction (for example) returned some kind
// of status code to our caller. (If the following instruction is
// reachable in some other way, it will be translated separately -
// as the first instruction in a linear sequence - because it will
// be present in pending.)
bool insert_block_has_terminator =
(builder_.GetInsertBlock()->getTerminator() != 0);
if (insert_block_has_terminator && (ct_pc != original_ct_pc))
{
TRACE("Not translating as not first instruction in linear stream "
"and previous instruction's basic block has a terminator");
break;
}
ensure_address_block_created(ct_pc);
if (!insert_block_has_terminator)
{
builder_.CreateBr(address_block_[ct_pc]);
}
builder_.SetInsertPoint(address_block_[ct_pc]);
// Note that we only set this flag for the opcode byte, not the
// whole length of the instruction. Apart from being easiest,
// this is actually correct. Someone might do LDA #<opcode for
// LDA #>:STA <opcode for RTS> or something weird like that and
// interleave instructions.
code_generated_for_address_[ct_pc] = true;
if (instructions_ >= max_instructions_)
{
TRACE("Translated maximum number of instructions");
// We must *not* use control_transfer_to() here; it would see
// that we have set code_generated_for_address_ and generate a
// branch to here, i.e. an infinite loop. It is correct that we
// have set code_generated_for_address_ since we must set that
// if we generate a corresponding address_block entry and we must
// do that so that any branches to this address can be resolved.
return_control_transfer_direct(constant_u16(ct_pc));
break;
}
++instructions_;
uint8_t opcode = ct_memory_[ct_pc];
if (opcode == opcode_brk)
{
disassemble1(ct_pc, "BRK");
llvm::Value *new_pc_low = memory_read(abs(0xfffe));
llvm::Value *new_pc_high = memory_read(abs(0xffff));
llvm::Value *new_pc = create_u16(new_pc_low, new_pc_high);
// Because BRK pushes three bytes onto the stack, we devolve
// responsibility for checking for code living on the stack
// being modified to our caller (by returning result_brk), so
// we use push*raw() here. (We don't support optimistic writes;
// BRK isn't performance critical so there's no payoff for the
// extra complexity.)
uint16_t pc_to_stack = this_opcode_at + 2;
push_u16_raw(pc_to_stack);
llvm::Value *p = flag_byte();
p = builder_.CreateOr(p, constant_u8(flagB | flagX));
push_u8_raw(p);
register_store(constant_jb(jit_bool_true), flag_i_);
register_store(constant_jb(jit_bool_false), flag_d_);
return_brk(new_pc);
}
else if (opcode == 0x01)
{
uint8_t operand;
disassemble2(ct_pc, "ORA (", operand, ",X)");
ora(memory_read(
zp_pre_index(constant_u8(operand), register_load(x_))));
}
else if (opcode == 0x02)
{
illegal_instruction(ct_pc, 2);
}
else if (opcode == 0x03)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x04)
{
uint8_t operand;
disassemble2(ct_pc, "TSB ", operand);
memory_op(&FunctionBuilder::tsb, zp(operand), ct_pc);
}
else if (opcode == 0x05)
{
uint8_t operand;
disassemble2(ct_pc, "ORA ", operand);
ora(memory_read(zp(operand)));
}
else if (opcode == 0x06)
{
uint8_t operand;
disassemble2(ct_pc, "ASL ", operand);
memory_op(&FunctionBuilder::asl, zp(operand), ct_pc);
}
else if (opcode == 0x07)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x08)
{
disassemble1(ct_pc, "PHP");
llvm::Value *p = flag_byte();
p = builder_.CreateOr(p, constant_u8(flagB | flagX));
push_u8(p, ct_pc);
}
else if (opcode == 0x09)
{
uint8_t operand;
disassemble2(ct_pc, "ORA #", operand);
ora(constant_u8(operand));
}
else if (opcode == 0x0a)
{
disassemble1(ct_pc, "ASL A");
register_op(&FunctionBuilder::asl, a_);
}
else if (opcode == 0x0b)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x0c)
{
uint16_t operand;
disassemble3(ct_pc, "TSB ", operand);
memory_op(&FunctionBuilder::tsb, abs(operand), ct_pc);
}
else if (opcode == 0x0d)
{
uint16_t operand;
disassemble3(ct_pc, "ORA ", operand);
ora(memory_read(abs(operand)));
}
else if (opcode == 0x0e)
{
uint16_t operand;
disassemble3(ct_pc, "ASL ", operand);
memory_op(&FunctionBuilder::asl, abs(operand), ct_pc);
}
else if (opcode == 0x0f)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == opcode_bpl)
{
uint16_t target;
disassemble_branch(ct_pc, "BPL ", target);
pending_.insert(target);
branch(flag_n_, false, target);
}
else if (opcode == 0x11)
{
uint8_t operand;
disassemble2(ct_pc, "ORA (", operand, "),Y");
ora(memory_read(
zp_post_index(constant_u8(operand), register_load(y_))));
}
else if (opcode == 0x12)
{
uint8_t operand;
disassemble2(ct_pc, "ORA (", operand, ")");
ora(memory_read(
zp_post_index(constant_u8(operand), constant_u8(0))));
}
else if (opcode == 0x13)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x14)
{
uint8_t operand;
disassemble2(ct_pc, "TRB ", operand);
memory_op(&FunctionBuilder::trb, zp(operand), ct_pc);
}
else if (opcode == 0x15)
{
uint8_t operand;
disassemble2(ct_pc, "ORA ", operand, ",X");
ora(memory_read(zp_index(constant_u8(operand), register_load(x_))));
}
else if (opcode == 0x16)
{
uint8_t operand;
disassemble2(ct_pc, "ASL ", operand, ",X");
memory_op(&FunctionBuilder::asl,
zp_index(constant_u8(operand), register_load(x_)), ct_pc);
}
else if (opcode == 0x17)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x18)
{
disassemble1(ct_pc, "CLC");
register_store(constant_jb(jit_bool_false), flag_c_);
}
else if (opcode == 0x19)
{
uint16_t operand;
disassemble3(ct_pc, "ORA ", operand, ",Y");
ora(memory_read(
abs_index(constant_u16(operand), register_load(y_))));
}
else if (opcode == 0x1a)
{
disassemble1(ct_pc, "INC A");
register_op(&FunctionBuilder::inc, a_);
}
else if (opcode == 0x1b)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x1c)
{
uint16_t operand;
disassemble3(ct_pc, "TRB ", operand);
memory_op(&FunctionBuilder::trb, abs(operand), ct_pc);
}
else if (opcode == 0x1d)
{
uint16_t operand;
disassemble3(ct_pc, "ORA ", operand, ",X");
ora(memory_read(
abs_index(constant_u16(operand), register_load(x_))));
}
else if (opcode == 0x1e)
{
uint16_t operand;
disassemble3(ct_pc, "ASL ", operand, ",X");
memory_op(
&FunctionBuilder::asl,
abs_index(constant_u16(operand), register_load(x_)),
ct_pc);
}
else if (opcode == 0x1f)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == opcode_jsr)
{
uint16_t operand;
disassemble3(ct_pc, "JSR ", operand);
uint16_t mangled_return_addr = ct_pc - 1;
// We are pushing two bytes onto the stack here and possibly
// requiring our caller to handle the control transfer, so the
// standard mechanisms for handling writes to code and control
// transfer aren't enough. control_transfer_to() contains special
// logic for JSR and we just use push_u16_raw() here.
push_u16_raw(mangled_return_addr);
// We generally want to translate the subroutine code into
// this function, so control_transfer_to() can perform the
// control transfer with a simple branch. However, if there is
// a call callback, control_transfer_to() will have to arrange
// a control transfer via the generated function's caller. It
// would be strictly harmless for us to translate the subroutine
// code anyway, as it will just never be executed, but it is
// both pointless and makes the generated IR less readable (it
// has a superficially buggy appearance, since it will show a
// translation of possibly junk code at the callback address
// which may never actually execute).
bool is_call_callback = (callbacks_.call[operand] != 0);
if (!is_call_callback)
{
pending_.insert(operand);
// We can predict that the RTS in the subroutine we are
// about to call will return to the immediately following
// instruction. (This is not guaranteed; the subroutine
// might fiddle with the stack. If that happens the "code"
// at ct_pc might be junk, but that's an acceptable risk;
// we will translate it but it will never be executed, and
// any stream of bytes can be translated even if the code
// is nonsense.)
pending_.insert(ct_pc);
predicted_rts_targets_[operand].insert(ct_pc);
}
control_transfer_to(constant_u16(operand), opcode);
}
else if (opcode == 0x21)
{
uint8_t operand;
disassemble2(ct_pc, "AND (", operand, ",X)");
And(memory_read(
zp_pre_index(constant_u8(operand), register_load(x_))));
}
else if (opcode == 0x22)
{
illegal_instruction(ct_pc, 2);
}
else if (opcode == 0x23)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x24)
{
uint8_t operand;
disassemble2(ct_pc, "BIT ", operand);
bit(memory_read(zp(operand)));
}
else if (opcode == 0x25)
{
uint8_t operand;
disassemble2(ct_pc, "AND ", operand);
And(memory_read(zp(operand)));
}
else if (opcode == 0x26)
{
uint8_t operand;
disassemble2(ct_pc, "ROL ", operand);
memory_op(&FunctionBuilder::rol, zp(operand), ct_pc);
}
else if (opcode == 0x27)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x28)
{
disassemble1(ct_pc, "PLP");
pop_flags();
}
else if (opcode == 0x29)
{
uint8_t operand;
disassemble2(ct_pc, "AND #", operand);
And(constant_u8(operand));
}
else if (opcode == 0x2a)
{
disassemble1(ct_pc, "ROL A");
register_op(&FunctionBuilder::rol, a_);
}
else if (opcode == 0x2b)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x2c)
{
uint16_t operand;
disassemble3(ct_pc, "BIT ", operand);
bit(memory_read(abs(operand)));
}
else if (opcode == 0x2d)
{
uint16_t operand;
disassemble3(ct_pc, "AND ", operand);
And(memory_read(abs(operand)));
}
else if (opcode == 0x2e)
{
uint16_t operand;
disassemble3(ct_pc, "ROL ", operand);
memory_op(&FunctionBuilder::rol, abs(operand), ct_pc);
}
else if (opcode == 0x2f)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == opcode_bmi)
{
uint16_t target;
disassemble_branch(ct_pc, "BMI ", target);
pending_.insert(target);
branch(flag_n_, true, target);
}
else if (opcode == 0x31)
{
uint8_t operand;
disassemble2(ct_pc, "AND (", operand, "),Y");
And(memory_read(
zp_post_index(constant_u8(operand), register_load(y_))));
}
else if (opcode == 0x32)
{
uint8_t operand;
disassemble2(ct_pc, "AND (", operand, ")");
And(memory_read(
zp_post_index(constant_u8(operand), constant_u8(0))));
}
else if (opcode == 0x33)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x34)
{
uint8_t operand;
disassemble2(ct_pc, "BIT ", operand, ",X");
bit(memory_read(zp_index(constant_u8(operand), register_load(x_))));
}
else if (opcode == 0x35)
{
uint8_t operand;
disassemble2(ct_pc, "AND ", operand, ",X");
And(memory_read(zp_index(constant_u8(operand), register_load(x_))));
}
else if (opcode == 0x36)
{
uint8_t operand;
disassemble2(ct_pc, "ROL ", operand, ",X");
memory_op(&FunctionBuilder::rol,
zp_index(constant_u8(operand), register_load(x_)), ct_pc);
}
else if (opcode == 0x37)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x38)
{
disassemble1(ct_pc, "SEC");
register_store(constant_jb(jit_bool_true), flag_c_);
}
else if (opcode == 0x39)
{
uint16_t operand;
disassemble3(ct_pc, "AND ", operand, ",Y");
And(memory_read(
abs_index(constant_u16(operand), register_load(y_))));
}
else if (opcode == 0x3a)
{
disassemble1(ct_pc, "DEC A");
register_op(&FunctionBuilder::dec, a_);
}
else if (opcode == 0x3b)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x3c)
{
uint16_t operand;
disassemble3(ct_pc, "BIT ", operand, ",X");
bit(memory_read(
abs_index(constant_u16(operand), register_load(x_))));
}
else if (opcode == 0x3d)
{
uint16_t operand;
disassemble3(ct_pc, "AND ", operand, ",X");
And(memory_read(
abs_index(constant_u16(operand), register_load(x_))));
}
else if (opcode == 0x3e)
{
uint16_t operand;
disassemble3(ct_pc, "ROL ", operand, ",X");
memory_op(
&FunctionBuilder::rol,
abs_index(constant_u16(operand), register_load(x_)),
ct_pc);
}
else if (opcode == 0x3f)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == opcode_rti)
{
disassemble1(ct_pc, "RTI");
pop_flags();
llvm::Value *new_pc = pop_u16();
control_transfer_to(new_pc, opcode);
}
else if (opcode == 0x41)
{
uint8_t operand;
disassemble2(ct_pc, "EOR (", operand, ",X)");
eor(memory_read(
zp_pre_index(constant_u8(operand), register_load(x_))));
}
else if (opcode == 0x42)
{
illegal_instruction(ct_pc, 2);
}
else if (opcode == 0x43)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x44)
{
illegal_instruction(ct_pc, 2);
}
else if (opcode == 0x45)
{
uint8_t operand;
disassemble2(ct_pc, "EOR ", operand);
eor(memory_read(zp(operand)));
}
else if (opcode == 0x46)
{
uint8_t operand;
disassemble2(ct_pc, "LSR ", operand);
memory_op(&FunctionBuilder::lsr, zp(operand), ct_pc);
}
else if (opcode == 0x47)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x48)
{
disassemble1(ct_pc, "PHA");
push_u8(register_load(a_), ct_pc);
}
else if (opcode == 0x49)
{
uint8_t operand;
disassemble2(ct_pc, "EOR #", operand);
eor(constant_u8(operand));
}
else if (opcode == 0x4a)
{
disassemble1(ct_pc, "LSR A");
register_op(&FunctionBuilder::lsr, a_);
}
else if (opcode == 0x4b)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == opcode_jmp_abs)
{
uint16_t operand;
disassemble3(ct_pc, "JMP ", operand);
pending_.insert(operand);
control_transfer_to(constant_u16(operand), opcode);
}
else if (opcode == 0x4d)
{
uint16_t operand;
disassemble3(ct_pc, "EOR ", operand);
eor(memory_read(abs(operand)));
}
else if (opcode == 0x4e)
{
uint16_t operand;
disassemble3(ct_pc, "LSR ", operand);
memory_op(&FunctionBuilder::lsr, abs(operand), ct_pc);
}
else if (opcode == 0x4f)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == opcode_bvc)
{
uint16_t target;
disassemble_branch(ct_pc, "BVC ", target);
pending_.insert(target);
branch(flag_v_, false, target);
}
else if (opcode == 0x51)
{
uint8_t operand;
disassemble2(ct_pc, "EOR (", operand, "),Y");
eor(memory_read(
zp_post_index(constant_u8(operand), register_load(y_))));
}
else if (opcode == 0x52)
{
uint8_t operand;
disassemble2(ct_pc, "EOR (", operand, ")");
eor(memory_read(
zp_post_index(constant_u8(operand), constant_u8(0))));
}
else if (opcode == 0x53)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x54)
{
illegal_instruction(ct_pc, 2);
}
else if (opcode == 0x55)
{
uint8_t operand;
disassemble2(ct_pc, "EOR ", operand, ",X");
eor(memory_read(zp_index(constant_u8(operand), register_load(x_))));
}
else if (opcode == 0x56)
{
uint8_t operand;
disassemble2(ct_pc, "LSR ", operand, ",X");
memory_op(&FunctionBuilder::lsr,
zp_index(constant_u8(operand), register_load(x_)), ct_pc);
}
else if (opcode == 0x57)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x58)
{
disassemble1(ct_pc, "CLI");
register_store(constant_jb(jit_bool_false), flag_i_);
}
else if (opcode == 0x59)
{
uint16_t operand;
disassemble3(ct_pc, "EOR ", operand, ",Y");
eor(memory_read(
abs_index(constant_u16(operand), register_load(y_))));
}
else if (opcode == 0x5a)
{
disassemble1(ct_pc, "PHY");
push_u8(register_load(y_), ct_pc);
}
else if (opcode == 0x5b)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x5c)
{
illegal_instruction(ct_pc, 3);
}
else if (opcode == 0x5d)
{
uint16_t operand;
disassemble3(ct_pc, "EOR ", operand, ",X");
eor(memory_read(
abs_index(constant_u16(operand), register_load(x_))));
}
else if (opcode == 0x5e)
{
uint16_t operand;
disassemble3(ct_pc, "LSR ", operand, ",X");
memory_op(
&FunctionBuilder::lsr,
abs_index(constant_u16(operand), register_load(x_)),
ct_pc);
}
else if (opcode == 0x5f)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == opcode_rts)
{
disassemble1(ct_pc, "RTS");
llvm::Value *new_pc = check_predicted_rts(original_ct_pc);
control_transfer_to(new_pc, opcode);
}
else if (opcode == 0x61)
{
uint8_t operand;
disassemble2(ct_pc, "ADC (", operand, ",X)");
adc(memory_read(
zp_pre_index(constant_u8(operand), register_load(x_))));
}
else if (opcode == 0x62)
{
illegal_instruction(ct_pc, 2);
}
else if (opcode == 0x63)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x64)
{
uint8_t operand;
disassemble2(ct_pc, "STZ ", operand);
memory_write(zp(operand), constant_u8(0), ct_pc);
}
else if (opcode == 0x65)
{
uint8_t operand;
disassemble2(ct_pc, "ADC ", operand);
adc(memory_read(zp(operand)));
}
else if (opcode == 0x66)
{
uint8_t operand;
disassemble2(ct_pc, "ROR ", operand);
memory_op(&FunctionBuilder::ror, zp(operand), ct_pc);
}
else if (opcode == 0x67)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x68)
{
disassemble1(ct_pc, "PLA");
llvm::Value *data = pop_u8();
register_store(data, a_);
set_nz(data);
}
else if (opcode == 0x69)
{
uint8_t operand;
disassemble2(ct_pc, "ADC #", operand);
adc(constant_u8(operand));
}
else if (opcode == 0x6a)
{
disassemble1(ct_pc, "ROR A");
register_op(&FunctionBuilder::ror, a_);
}
else if (opcode == 0x6b)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == opcode_jmp_ind_abs)
{
uint16_t operand;
disassemble3(ct_pc, "JMP (", operand, ")");
llvm::Value *low_byte = memory_read_untrapped(abs(operand));
// We're emulating the 65C02 here so we don't wrap if operand
// is of the form &xxFF. (Unless xx is FF, of course.)
uint16_t high_byte_at = operand + 1;
llvm::Value *high_byte = memory_read_untrapped(abs(high_byte_at));
llvm::Value *new_pc = create_u16(low_byte, high_byte);
control_transfer_to(new_pc, opcode);
}
else if (opcode == 0x6d)
{
uint16_t operand;
disassemble3(ct_pc, "ADC ", operand);
adc(memory_read(abs(operand)));
}
else if (opcode == 0x6e)
{
uint16_t operand;
disassemble3(ct_pc, "ROR ", operand);
memory_op(&FunctionBuilder::ror, abs(operand), ct_pc);
}
else if (opcode == 0x6f)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == opcode_bvs)
{
uint16_t target;
disassemble_branch(ct_pc, "BVS ", target);
pending_.insert(target);
branch(flag_v_, true, target);
}
else if (opcode == 0x71)
{
uint8_t operand;
disassemble2(ct_pc, "ADC (", operand, "),Y");
adc(memory_read(
zp_post_index(constant_u8(operand), register_load(y_))));
}
else if (opcode == 0x72)
{
uint8_t operand;
disassemble2(ct_pc, "ADC (", operand, ")");
adc(memory_read(
zp_post_index(constant_u8(operand), constant_u8(0))));
}
else if (opcode == 0x73)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x74)
{
uint8_t operand;
disassemble2(ct_pc, "STZ ", operand, ",X");
memory_write(zp_index(constant_u8(operand), register_load(x_)),
constant_u8(0), ct_pc);
}
else if (opcode == 0x75)
{
uint8_t operand;
disassemble2(ct_pc, "ADC ", operand, ",X");
adc(memory_read(zp_index(constant_u8(operand), register_load(x_))));
}
else if (opcode == 0x76)
{
uint8_t operand;
disassemble2(ct_pc, "ROR ", operand, ",X");
memory_op(&FunctionBuilder::ror,
zp_index(constant_u8(operand), register_load(x_)), ct_pc);
}
else if (opcode == 0x77)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x78)
{
disassemble1(ct_pc, "SEI");
register_store(constant_jb(jit_bool_true), flag_i_);
}
else if (opcode == 0x79)
{
uint16_t operand;
disassemble3(ct_pc, "ADC ", operand, ",Y");
adc(memory_read(
abs_index(constant_u16(operand), register_load(y_))));
}
else if (opcode == 0x7a)
{
disassemble1(ct_pc, "PLY");
llvm::Value *data = pop_u8();
register_store(data, y_);
set_nz(data);
}
else if (opcode == 0x7b)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == opcode_jmp_indx_abs)
{
uint16_t operand;
disassemble3(ct_pc, "JMP (", operand, ",X)");
llvm::Value *low_byte_at =
builder_.CreateAdd(
constant_u16(operand),
zext_i16(register_load(x_)));
llvm::Value *high_byte_at =
builder_.CreateAdd(low_byte_at, constant_u16(1));
llvm::Value *low_byte =
memory_read_untrapped(BoundedAddress(*this, low_byte_at));
llvm::Value *high_byte =
memory_read_untrapped(BoundedAddress(*this, high_byte_at));
llvm::Value *new_pc = create_u16(low_byte, high_byte);
control_transfer_to(new_pc, opcode);
}
else if (opcode == 0x7d)
{
uint16_t operand;
disassemble3(ct_pc, "ADC ", operand, ",X");
adc(memory_read(
abs_index(constant_u16(operand), register_load(x_))));
}
else if (opcode == 0x7e)
{
uint16_t operand;
disassemble3(ct_pc, "ROR ", operand, ",X");
memory_op(
&FunctionBuilder::ror,
abs_index(constant_u16(operand), register_load(x_)),
ct_pc);
}
else if (opcode == 0x7f)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == opcode_bra)
{
uint16_t target;
disassemble_branch(ct_pc, "BRA ", target);
pending_.insert(target);
control_transfer_to(constant_u16(target), opcode);
}
else if (opcode == 0x81)
{
uint8_t operand;
disassemble2(ct_pc, "STA (", operand, ",X)");
memory_write(zp_pre_index(constant_u8(operand), register_load(x_)),
register_load(a_), ct_pc);
}
else if (opcode == 0x82)
{
illegal_instruction(ct_pc, 2);
}
else if (opcode == 0x83)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x84)
{
uint8_t operand;
disassemble2(ct_pc, "STY ", operand);
memory_write(zp(operand), register_load(y_), ct_pc);
}
else if (opcode == 0x85)
{
uint8_t operand;
disassemble2(ct_pc, "STA ", operand);
memory_write(zp(operand), register_load(a_), ct_pc);
}
else if (opcode == 0x86)
{
uint8_t operand;
disassemble2(ct_pc, "STX ", operand);
memory_write(zp(operand), register_load(x_), ct_pc);
}
else if (opcode == 0x87)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x88)
{
disassemble1(ct_pc, "DEY");
register_op(&FunctionBuilder::dec, y_);
}
else if (opcode == 0x89)
{
uint8_t operand;
disassemble2(ct_pc, "BIT #", operand);
// Note that unlike other BIT opcodes, this one only affects
// the Z flag.
llvm::Value *tmp =
builder_.CreateAnd(register_load(a_), constant_u8(operand));
set_z(tmp);
}
else if (opcode == 0x8a)
{
disassemble1(ct_pc, "TXA");
transfer(x_, a_);
}
else if (opcode == 0x8b)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x8c)
{
uint16_t operand;
disassemble3(ct_pc, "STY ", operand);
memory_write(abs(operand), register_load(y_), ct_pc);
}
else if (opcode == 0x8d)
{
uint16_t operand;
disassemble3(ct_pc, "STA ", operand);
memory_write(abs(operand), register_load(a_), ct_pc);
}
else if (opcode == 0x8e)
{
uint16_t operand;
disassemble3(ct_pc, "STX ", operand);
memory_write(abs(operand), register_load(x_), ct_pc);
}
else if (opcode == 0x8f)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == opcode_bcc)
{
uint16_t target;
disassemble_branch(ct_pc, "BCC ", target);
pending_.insert(target);
branch(flag_c_, false, target);
}
else if (opcode == 0x91)
{
uint8_t operand;
disassemble2(ct_pc, "STA (", operand, "),Y");
memory_write(zp_post_index(constant_u8(operand), register_load(y_)),
register_load(a_), ct_pc);
}
else if (opcode == 0x92)
{
uint8_t operand;
disassemble2(ct_pc, "STA (", operand, ")");
memory_write(zp_post_index(constant_u8(operand), constant_u8(0)),
register_load(a_), ct_pc);
}
else if (opcode == 0x93)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x94)
{
uint8_t operand;
disassemble2(ct_pc, "STY ", operand, ",X");
memory_write(zp_index(constant_u8(operand), register_load(x_)),
register_load(y_), ct_pc);
}
else if (opcode == 0x95)
{
uint8_t operand;
disassemble2(ct_pc, "STA ", operand, ",X");
memory_write(zp_index(constant_u8(operand), register_load(x_)),
register_load(a_), ct_pc);
}
else if (opcode == 0x96)
{
uint8_t operand;
disassemble2(ct_pc, "STX ", operand, ",Y");
memory_write(zp_index(constant_u8(operand), register_load(y_)),
register_load(x_), ct_pc);
}
else if (opcode == 0x97)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x98)
{
disassemble1(ct_pc, "TYA");
transfer(y_, a_);
}
else if (opcode == 0x99)
{
uint16_t operand;
disassemble3(ct_pc, "STA ", operand, ",Y");
memory_write(abs_index(constant_u16(operand), register_load(y_)),
register_load(a_), ct_pc);
}
else if (opcode == 0x9a)
{
disassemble1(ct_pc, "TXS");
// We don't use transfer() even though we do for TSX; TXS doesn't
// set any flags.
register_store(register_load(x_), s_);
}
else if (opcode == 0x9b)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0x9c)
{
uint16_t operand;
disassemble3(ct_pc, "STZ ", operand);
memory_write(abs(operand), constant_u8(0), ct_pc);
}
else if (opcode == 0x9d)
{
uint16_t operand;
disassemble3(ct_pc, "STA ", operand, ",X");
memory_write(abs_index(constant_u16(operand), register_load(x_)),
register_load(a_), ct_pc);
}
else if (opcode == 0x9e)
{
uint16_t operand;
disassemble3(ct_pc, "STZ ", operand, ",X");
memory_write(abs_index(constant_u16(operand), register_load(x_)),
constant_u8(0), ct_pc);
}
else if (opcode == 0x9f)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xa0)
{
uint8_t operand;
disassemble2(ct_pc, "LDY #", operand);
ld(y_, constant_u8(operand));
}
else if (opcode == 0xa1)
{
uint8_t operand;
disassemble2(ct_pc, "LDA (", operand, ",X)");
ld(a_, memory_read(
zp_pre_index(constant_u8(operand), register_load(x_))));
}
else if (opcode == 0xa2)
{
uint8_t operand;
disassemble2(ct_pc, "LDX #", operand);
ld(x_, constant_u8(operand));
}
else if (opcode == 0xa3)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xa4)
{
uint8_t operand;
disassemble2(ct_pc, "LDY ", operand);
ld(y_, memory_read(zp(operand)));
}
else if (opcode == 0xa5)
{
uint8_t operand;
disassemble2(ct_pc, "LDA ", operand);
ld(a_, memory_read(zp(operand)));
}
else if (opcode == 0xa6)
{
uint8_t operand;
disassemble2(ct_pc, "LDX ", operand);
ld(x_, memory_read(zp(operand)));
}
else if (opcode == 0xa7)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xa8)
{
disassemble1(ct_pc, "TAY");
transfer(a_, y_);
}
else if (opcode == 0xa9)
{
uint8_t operand;
disassemble2(ct_pc, "LDA #", operand);
ld(a_, constant_u8(operand));
}
else if (opcode == 0xaa)
{
disassemble1(ct_pc, "TAX");
transfer(a_, x_);
}
else if (opcode == 0xab)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xac)
{
uint16_t operand;
disassemble3(ct_pc, "LDY ", operand);
ld(y_, memory_read(abs(operand)));
}
else if (opcode == 0xad)
{
uint16_t operand;
disassemble3(ct_pc, "LDA ", operand);
ld(a_, memory_read(abs(operand)));
}
else if (opcode == 0xae)
{
uint16_t operand;
disassemble3(ct_pc, "LDX ", operand);
ld(x_, memory_read(abs(operand)));
}
else if (opcode == 0xaf)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == opcode_bcs)
{
uint16_t target;
disassemble_branch(ct_pc, "BCS ", target);
pending_.insert(target);
branch(flag_c_, true, target);
}
else if (opcode == 0xb1)
{
uint8_t operand;
disassemble2(ct_pc, "LDA (", operand, "),Y");
ld(a_, memory_read(
zp_post_index(constant_u8(operand), register_load(y_))));
}
else if (opcode == 0xb2)
{
uint8_t operand;
disassemble2(ct_pc, "LDA (", operand, ")");
ld(a_, memory_read(
zp_post_index(constant_u8(operand), constant_u8(0))));
}
else if (opcode == 0xb3)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xb4)
{
uint8_t operand;
disassemble2(ct_pc, "LDY ", operand, ",X");
ld(y_, memory_read(
zp_index(constant_u8(operand), register_load(x_))));
}
else if (opcode == 0xb5)
{
uint8_t operand;
disassemble2(ct_pc, "LDA ", operand, ",X");
ld(a_, memory_read(
zp_index(constant_u8(operand), register_load(x_))));
}
else if (opcode == 0xb6)
{
uint8_t operand;
disassemble2(ct_pc, "LDX ", operand, ",Y");
ld(x_, memory_read(
zp_index(constant_u8(operand), register_load(y_))));
}
else if (opcode == 0xb7)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xb8)
{
disassemble1(ct_pc, "CLV");
register_store(constant_jb(jit_bool_false), flag_v_);
}
else if (opcode == 0xb9)
{
uint16_t operand;
disassemble3(ct_pc, "LDA ", operand, ",Y");
ld(a_, memory_read(
abs_index(constant_u16(operand), register_load(y_))));
}
else if (opcode == 0xba)
{
disassemble1(ct_pc, "TSX");
transfer(s_, x_);
}
else if (opcode == 0xbb)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xbc)
{
uint16_t operand;
disassemble3(ct_pc, "LDY ", operand, ",X");
ld(y_, memory_read(
abs_index(constant_u16(operand), register_load(x_))));
}
else if (opcode == 0xbd)
{
uint16_t operand;
disassemble3(ct_pc, "LDA ", operand, ",X");
ld(a_, memory_read(
abs_index(constant_u16(operand), register_load(x_))));
}
else if (opcode == 0xbe)
{
uint16_t operand;
disassemble3(ct_pc, "LDX ", operand, ",Y");
ld(x_, memory_read(
abs_index(constant_u16(operand), register_load(y_))));
}
else if (opcode == 0xbf)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xc0)
{
uint8_t operand;
disassemble2(ct_pc, "CPY #", operand);
cmp(register_load(y_), constant_u8(operand));
}
else if (opcode == 0xc1)
{
uint8_t operand;
disassemble2(ct_pc, "CMP (", operand, ",X)");
cmp(register_load(a_),
memory_read(
zp_pre_index(constant_u8(operand), register_load(x_))));
}
else if (opcode == 0xc2)
{
illegal_instruction(ct_pc, 2);
}
else if (opcode == 0xc3)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xc4)
{
uint8_t operand;
disassemble2(ct_pc, "CPY ", operand);
cmp(register_load(y_), memory_read(zp(operand)));
}
else if (opcode == 0xc5)
{
uint8_t operand;
disassemble2(ct_pc, "CMP ", operand);
cmp(register_load(a_), memory_read(zp(operand)));
}
else if (opcode == 0xc6)
{
uint8_t operand;
disassemble2(ct_pc, "DEC ", operand);
memory_op(&FunctionBuilder::dec, zp(operand), ct_pc);
}
else if (opcode == 0xc7)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xc8)
{
disassemble1(ct_pc, "INY");
register_op(&FunctionBuilder::inc, y_);
}
else if (opcode == 0xc9)
{
uint8_t operand;
disassemble2(ct_pc, "CMP #", operand);
cmp(register_load(a_), constant_u8(operand));
}
else if (opcode == 0xca)
{
disassemble1(ct_pc, "DEX");
register_op(&FunctionBuilder::dec, x_);
}
else if (opcode == 0xcb)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xcc)
{
uint16_t operand;
disassemble3(ct_pc, "CPY ", operand);
cmp(register_load(y_), memory_read(abs(operand)));
}
else if (opcode == 0xcd)
{
uint16_t operand;
disassemble3(ct_pc, "CMP ", operand);
cmp(register_load(a_), memory_read(abs(operand)));
}
else if (opcode == 0xce)
{
uint16_t operand;
disassemble3(ct_pc, "DEC ", operand);
memory_op(&FunctionBuilder::dec, abs(operand), ct_pc);
}
else if (opcode == 0xcf)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == opcode_bne)
{
uint16_t target;
disassemble_branch(ct_pc, "BNE ", target);
pending_.insert(target);
branch(flag_z_, false, target);
}
else if (opcode == 0xd1)
{
uint8_t operand;
disassemble2(ct_pc, "CMP (", operand, "),Y");
cmp(register_load(a_),
memory_read(
zp_post_index(constant_u8(operand), register_load(y_))));
}
else if (opcode == 0xd2)
{
uint8_t operand;
disassemble2(ct_pc, "CMP (", operand, ")");
cmp(register_load(a_),
memory_read(
zp_post_index(constant_u8(operand), constant_u8(0))));
}
else if (opcode == 0xd3)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xd4)
{
illegal_instruction(ct_pc, 2);
}
else if (opcode == 0xd5)
{
uint8_t operand;
disassemble2(ct_pc, "CMP ", operand, ",X");
cmp(register_load(a_),
memory_read(
zp_index(constant_u8(operand), register_load(x_))));
}
else if (opcode == 0xd6)
{
uint8_t operand;
disassemble2(ct_pc, "DEC ", operand, ",X");
memory_op(&FunctionBuilder::dec,
zp_index(constant_u8(operand), register_load(x_)), ct_pc);
}
else if (opcode == 0xd7)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xd8)
{
disassemble1(ct_pc, "CLD");
register_store(constant_jb(jit_bool_false), flag_d_);
}
else if (opcode == 0xd9)
{
uint16_t operand;
disassemble3(ct_pc, "CMP ", operand, ",Y");
cmp(register_load(a_),
memory_read(
abs_index(constant_u16(operand), register_load(y_))));
}
else if (opcode == 0xda)
{
disassemble1(ct_pc, "PHX");
push_u8(register_load(x_), ct_pc);
}
else if (opcode == 0xdb)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xdc)
{
illegal_instruction(ct_pc, 3);
}
else if (opcode == 0xdd)
{
uint16_t operand;
disassemble3(ct_pc, "CMP ", operand, ",X");
cmp(register_load(a_),
memory_read(
abs_index(constant_u16(operand), register_load(x_))));
}
else if (opcode == 0xde)
{
uint16_t operand;
disassemble3(ct_pc, "DEC ", operand, ",X");
memory_op(
&FunctionBuilder::dec,
abs_index(constant_u16(operand), register_load(x_)),
ct_pc);
}
else if (opcode == 0xdf)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xe0)
{
uint8_t operand;
disassemble2(ct_pc, "CPX #", operand);
cmp(register_load(x_), constant_u8(operand));
}
else if (opcode == 0xe1)
{
uint8_t operand;
disassemble2(ct_pc, "SBC (", operand, ",X)");
sbc(memory_read(
zp_pre_index(constant_u8(operand), register_load(x_))));
}
else if (opcode == 0xe2)
{
illegal_instruction(ct_pc, 2);
}
else if (opcode == 0xe3)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xe4)
{
uint8_t operand;
disassemble2(ct_pc, "CPX ", operand);
cmp(register_load(x_), memory_read(zp(operand)));
}
else if (opcode == 0xe5)
{
uint8_t operand;
disassemble2(ct_pc, "SBC ", operand);
sbc(memory_read(zp(operand)));
}
else if (opcode == 0xe6)
{
uint8_t operand;
disassemble2(ct_pc, "INC ", operand);
memory_op(&FunctionBuilder::inc, zp(operand), ct_pc);
}
else if (opcode == 0xe7)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xe8)
{
disassemble1(ct_pc, "INX");
register_op(&FunctionBuilder::inc, x_);
}
else if (opcode == 0xe9)
{
uint8_t operand;
disassemble2(ct_pc, "SBC #", operand);
sbc(constant_u8(operand));
}
else if (opcode == 0xea)
{
disassemble1(ct_pc, "NOP");
}
else if (opcode == 0xeb)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xec)
{
uint16_t operand;
disassemble3(ct_pc, "CPX ", operand);
cmp(register_load(x_), memory_read(abs(operand)));
}
else if (opcode == 0xed)
{
uint16_t operand;
disassemble3(ct_pc, "SBC ", operand);
sbc(memory_read(abs(operand)));
}
else if (opcode == 0xee)
{
uint16_t operand;
disassemble3(ct_pc, "INC ", operand);
memory_op(&FunctionBuilder::inc, abs(operand), ct_pc);
}
else if (opcode == 0xef)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == opcode_beq)
{
uint16_t target;
disassemble_branch(ct_pc, "BEQ ", target);
pending_.insert(target);
branch(flag_z_, true, target);
}
else if (opcode == 0xf1)
{
uint8_t operand;
disassemble2(ct_pc, "SBC (", operand, "),Y");
sbc(memory_read(
zp_post_index(constant_u8(operand), register_load(y_))));
}
else if (opcode == 0xf2)
{
uint8_t operand;
disassemble2(ct_pc, "SBC (", operand, ")");
sbc(memory_read(
zp_post_index(constant_u8(operand), constant_u8(0))));
}
else if (opcode == 0xf3)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xf4)
{
illegal_instruction(ct_pc, 2);
}
else if (opcode == 0xf5)
{
uint8_t operand;
disassemble2(ct_pc, "SBC ", operand, ",X");
sbc(memory_read(zp_index(constant_u8(operand), register_load(x_))));
}
else if (opcode == 0xf6)
{
uint8_t operand;
disassemble2(ct_pc, "INC ", operand, ",X");
memory_op(&FunctionBuilder::inc,
zp_index(constant_u8(operand), register_load(x_)), ct_pc);
}
else if (opcode == 0xf7)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xf8)
{
disassemble1(ct_pc, "SED");
register_store(constant_jb(jit_bool_true), flag_d_);
}
else if (opcode == 0xf9)
{
uint16_t operand;
disassemble3(ct_pc, "SBC ", operand, ",Y");
sbc(memory_read(
abs_index(constant_u16(operand), register_load(y_))));
}
else if (opcode == 0xfa)
{
disassemble1(ct_pc, "PLX");
llvm::Value *data = pop_u8();
register_store(data, x_);
set_nz(data);
}
else if (opcode == 0xfb)
{
illegal_instruction(ct_pc, 1);
}
else if (opcode == 0xfc)
{
illegal_instruction(ct_pc, 3);
}
else if (opcode == 0xfd)
{
uint16_t operand;
disassemble3(ct_pc, "SBC ", operand, ",X");
sbc(memory_read(
abs_index(constant_u16(operand), register_load(x_))));
}
else if (opcode == 0xfe)
{
uint16_t operand;
disassemble3(ct_pc, "INC ", operand, ",X");
memory_op(
&FunctionBuilder::inc,
abs_index(constant_u16(operand), register_load(x_)),
ct_pc);
}
else if (opcode == 0xff)
{
illegal_instruction(ct_pc, 1);
}
else
{
CANT_HAPPEN("Unknown opcode 0x" << std::hex << opcode);
}
}
return ct_pc;
}
// Return the 8-bit operand of the instruction whose opcode is located at
// the given address.
uint8_t FunctionBuilder::operand8(uint16_t opcode_at)
{
uint16_t addr = opcode_at;
return ct_memory_[++addr];
}
// Return the 16-bit operand of the instruction whose opcode is located at
// the given address.
uint16_t FunctionBuilder::operand16(uint16_t opcode_at)
{
uint16_t addr = opcode_at;
uint8_t operand_low = ct_memory_[++addr];
uint8_t operand_high = ct_memory_[++addr];
return operand_low | (operand_high << 8);
}
llvm::Value *FunctionBuilder::constant_i1(bool c)
{
return llvm::ConstantInt::get(i1_type_, c);
}
llvm::Value *FunctionBuilder::constant_u8(uint8_t c)
{
return llvm::ConstantInt::get(i8_type_, c);
}
llvm::Value *FunctionBuilder::constant_u16(uint16_t c)
{
return llvm::ConstantInt::get(i16_type_, c);
}
llvm::Value *FunctionBuilder::constant_u32(uint32_t c)
{
return llvm::ConstantInt::get(i32_type_, c);
}
llvm::Value *FunctionBuilder::constant_u64(uint64_t c)
{
return llvm::ConstantInt::get(i64_type_, c);
}
llvm::Value *FunctionBuilder::constant_i(int c)
{
return llvm::ConstantInt::get(native_int_type_, c);
}
llvm::Value *FunctionBuilder::constant_jb(JitBool c)
{
return llvm::ConstantInt::get(jit_bool_type_, c);
}
llvm::Value *FunctionBuilder::convert_i1_to_jb(llvm::Value *v)
{
assert(v->getType() == i1_type_);
return builder_.CreateZExt(v, jit_bool_type_);
}
llvm::Value *FunctionBuilder::convert_i8_to_jb(llvm::Value *v)
{
assert(v->getType() == i8_type_);
return v;
}
llvm::Value *FunctionBuilder::convert_i16_to_jb(llvm::Value *v)
{
assert(v->getType() == i16_type_);
return convert_i1_to_jb(builder_.CreateICmpNE(v, constant_u16(0)));
}
// JitBool values should be tested via jit_bool_is_*() and not directly;
// this is because they use a 0=false, non-0=true representation. It's not
// correct to assume they are either 0 or 1.
llvm::Value *FunctionBuilder::jit_bool_is_true(llvm::Value *v)
{
assert(v->getType() == jit_bool_type_);
return builder_.CreateICmpNE(v, constant_u8(0));
}
llvm::Value *FunctionBuilder::jit_bool_is_false(llvm::Value *v)
{
assert(v->getType() == jit_bool_type_);
return builder_.CreateICmpEQ(v, constant_u8(0));
}
llvm::Value *FunctionBuilder::convert_i1_to_i8(llvm::Value *v)
{
assert(v->getType() == i1_type_);
return builder_.CreateZExt(v, i8_type_);
}
llvm::Value *FunctionBuilder::zext_i16(llvm::Value *v)
{
return builder_.CreateZExt(v, i16_type_);
}
llvm::Value *FunctionBuilder::zext_i32(llvm::Value *v)
{
return builder_.CreateZExt(v, i32_type_);
}
llvm::Value *FunctionBuilder::sext_i16(llvm::Value *v)
{
return builder_.CreateSExt(v, i16_type_);
}
llvm::Value *FunctionBuilder::trunc_i8(llvm::Value *v)
{
return builder_.CreateTrunc(v, i8_type_);
}
llvm::Value *FunctionBuilder::create_u16(
llvm::Value *low_byte, llvm::Value *high_byte)
{
return builder_.CreateOr(
zext_i16(low_byte),
builder_.CreateShl(zext_i16(high_byte), 8));
}
llvm::Value *FunctionBuilder::register_load(const Register &r)
{
return builder_.CreateLoad(r.v_);
}
void FunctionBuilder::register_store(llvm::Value *v, Register &r)
{
builder_.CreateStore(v, r.v_);
r.modified_ = true;
}
void FunctionBuilder::register_op(OpFn op, Register &r)
{
llvm::Value *data = register_load(r);
data = (this->*op)(data);
register_store(data, r);
}
void FunctionBuilder::memory_op(
OpFn op, const BoundedAddress &ba, uint16_t next_opcode_at)
{
llvm::Value *data = memory_read(ba);
data = (this->*op)(data);
memory_write(ba, data, next_opcode_at);
}
void FunctionBuilder::adc(llvm::Value *data)
{
llvm::BasicBlock *done_adc_block =
llvm::BasicBlock::Create(context_, "done_adc");
llvm::BasicBlock *adc_binary_block =
llvm::BasicBlock::Create(context_, "adc_binary", llvm_function_);
llvm::BasicBlock *adc_decimal_block =
llvm::BasicBlock::Create(context_, "adc_decimal", llvm_function_);
llvm::Value *d_clear = jit_bool_is_false(register_load(flag_d_));
builder_.CreateCondBr(d_clear, adc_binary_block, adc_decimal_block);
llvm_function_->getBasicBlockList().push_back(done_adc_block);
builder_.SetInsertPoint(adc_binary_block);
adc_binary(data);
builder_.CreateBr(done_adc_block);
builder_.SetInsertPoint(adc_decimal_block);
adc_decimal(data);
builder_.CreateBr(done_adc_block);
builder_.SetInsertPoint(done_adc_block);
}
void FunctionBuilder::adc_binary(llvm::Value *data)
{
llvm::Value *carry_16 = zext_i16(jit_bool_is_true(register_load(flag_c_)));
llvm::Value *a_u16 = zext_i16(register_load(a_));
llvm::Value *data_u16 = zext_i16(data);
llvm::Value *sum_u16 =
builder_.CreateAdd(builder_.CreateAdd(a_u16, data_u16), carry_16);
llvm::Value *a_s16 = builder_.CreateSExt(register_load(a_), i16_type_);
llvm::Value *data_s16 = builder_.CreateSExt(data, i16_type_);
llvm::Value *sum_s16 =
builder_.CreateAdd(builder_.CreateAdd(a_s16, data_s16), carry_16);
llvm::Value *new_a = trunc_i8(sum_u16);
register_store(new_a, a_);
set_nz(new_a);
llvm::Value *b8 = builder_.CreateAnd(
sum_u16,
constant_u16(0x100));
register_store(convert_i16_to_jb(b8), flag_c_);
llvm::Value *negative_as_unsigned =
jit_bool_is_true(register_load(flag_n_));
llvm::Value *negative_as_signed =
builder_.CreateICmpSLT(sum_s16, constant_u16(0));
llvm::Value *new_v_as_i1 =
builder_.CreateXor(negative_as_unsigned, negative_as_signed);
register_store(convert_i1_to_jb(new_v_as_i1), flag_v_);
}
void FunctionBuilder::adc_decimal(llvm::Value *data)
{
// This algorithm taken from http://www.6502.org/tutorials/decimal_mode.html
llvm::Value *carry = jit_bool_is_true(register_load(flag_c_));
builder_.CreateStore(
builder_.CreateAdd(
builder_.CreateAdd(
builder_.CreateAnd(
register_load(a_),
constant_u8(0x0f)),
builder_.CreateAnd(
data,
constant_u8(0x0f))),
convert_i1_to_i8(carry)),
l_tmp_);
llvm::BasicBlock *adjust_l_block =
llvm::BasicBlock::Create(context_, "adjust_l", llvm_function_);
llvm::BasicBlock *l_done_block =
llvm::BasicBlock::Create(context_, "l_done", llvm_function_);
builder_.CreateCondBr(
builder_.CreateICmpUGE(
builder_.CreateLoad(l_tmp_),
constant_u8(0x0a)),
adjust_l_block, l_done_block);
builder_.SetInsertPoint(adjust_l_block);
builder_.CreateStore(
builder_.CreateAdd(
builder_.CreateAnd(
builder_.CreateAdd(
builder_.CreateLoad(l_tmp_),
constant_u8(0x06)),
constant_u8(0x0f)),
constant_u8(0x10)),
l_tmp_);
builder_.CreateBr(l_done_block);
builder_.SetInsertPoint(l_done_block);
llvm::Value *a_and_0xf0 =
builder_.CreateAnd(
register_load(a_),
constant_u8(0xf0));
llvm::Value *data_and_0xf0 =
builder_.CreateAnd(
data,
constant_u8(0xf0));
builder_.CreateStore(
builder_.CreateAdd(
builder_.CreateAdd(
zext_i16(a_and_0xf0),
zext_i16(data_and_0xf0)),
zext_i16(builder_.CreateLoad(l_tmp_))),
s_tmp_);
llvm::BasicBlock *adjust_s_block =
llvm::BasicBlock::Create(context_, "adjust_s", llvm_function_);
llvm::BasicBlock *s_done_block =
llvm::BasicBlock::Create(context_, "s_done", llvm_function_);
builder_.CreateCondBr(
builder_.CreateICmpUGE(
builder_.CreateLoad(s_tmp_),
constant_u16(0xa0)),
adjust_s_block, s_done_block);
builder_.SetInsertPoint(adjust_s_block);
builder_.CreateStore(
builder_.CreateAdd(
builder_.CreateLoad(s_tmp_),
constant_u16(0x60)),
s_tmp_);
builder_.CreateBr(s_done_block);
builder_.SetInsertPoint(s_done_block);
builder_.CreateStore(
builder_.CreateAdd(
builder_.CreateAdd(
sext_i16(a_and_0xf0),
sext_i16(data_and_0xf0)),
zext_i16(builder_.CreateLoad(l_tmp_))),
t_tmp_);
llvm::BasicBlock *v_not_done_block =
llvm::BasicBlock::Create(context_, "v_not_done", llvm_function_);
llvm::BasicBlock *v_false_block =
llvm::BasicBlock::Create(context_, "v_false", llvm_function_);
llvm::BasicBlock *v_done_block =
llvm::BasicBlock::Create(context_, "v_done", llvm_function_);
register_store(constant_jb(jit_bool_true), flag_v_);
builder_.CreateCondBr(
builder_.CreateICmpSLT(
builder_.CreateLoad(t_tmp_),
constant_u16(-128)),
v_done_block, v_not_done_block);
builder_.SetInsertPoint(v_not_done_block);
builder_.CreateCondBr(
builder_.CreateICmpSGT(
builder_.CreateLoad(t_tmp_),
constant_u16(127)),
v_done_block, v_false_block);
builder_.SetInsertPoint(v_false_block);
register_store(constant_jb(jit_bool_false), flag_v_);
builder_.CreateBr(v_done_block);
builder_.SetInsertPoint(v_done_block);
register_store(trunc_i8(builder_.CreateLoad(s_tmp_)), a_);
set_nz(register_load(a_));
register_store(
convert_i1_to_jb(
builder_.CreateICmpUGE(
builder_.CreateLoad(s_tmp_),
constant_u16(0x100))),
flag_c_);
}
void FunctionBuilder::And(llvm::Value *data)
{
llvm::Value *result = builder_.CreateAnd(register_load(a_), data);
register_store(result, a_);
set_nz(result);
}
llvm::Value *FunctionBuilder::asl(llvm::Value *data)
{
register_store(
convert_i8_to_jb(builder_.CreateAnd(data, constant_u8(0x80))), flag_c_);
llvm::Value *result = builder_.CreateShl(data, 1);
set_nz(result);
return result;
}
void FunctionBuilder::bit(llvm::Value *data)
{
register_store(
convert_i8_to_jb(builder_.CreateAnd(data, constant_u8(0x80))), flag_n_);
register_store(
convert_i8_to_jb(builder_.CreateAnd(data, constant_u8(0x40))), flag_v_);
llvm::Value *tmp = builder_.CreateAnd(register_load(a_), data);
set_z(tmp);
}
void FunctionBuilder::branch(Register &flag, bool branch_if, uint16_t target)
{
llvm::BasicBlock *not_taken_block =
llvm::BasicBlock::Create(context_, "branch_not_taken", llvm_function_);
ensure_address_block_created(target);
llvm::Value *flag_set = jit_bool_is_true(register_load(flag));
if (branch_if)
{
builder_.CreateCondBr(flag_set, address_block_[target],
not_taken_block);
}
else
{
builder_.CreateCondBr(flag_set, not_taken_block,
address_block_[target]);
}
builder_.SetInsertPoint(not_taken_block);
}
void FunctionBuilder::cmp(llvm::Value *r, llvm::Value *data)
{
llvm::Value *sum = builder_.CreateSub(r, data);
set_nz(sum);
register_store(convert_i1_to_jb(builder_.CreateICmpUGE(r, data)), flag_c_);
}
llvm::Value *FunctionBuilder::dec(llvm::Value *data)
{
llvm::Value *result = builder_.CreateSub(data, constant_u8(1));
set_nz(result);
return result;
}
void FunctionBuilder::eor(llvm::Value *data)
{
llvm::Value *result = builder_.CreateXor(register_load(a_), data);
register_store(result, a_);
set_nz(result);
}
llvm::Value *FunctionBuilder::inc(llvm::Value *data)
{
llvm::Value *result = builder_.CreateAdd(data, constant_u8(1));
set_nz(result);
return result;
}
void FunctionBuilder::ld(Register &r, llvm::Value *data)
{
register_store(data, r);
set_nz(data);
}
llvm::Value *FunctionBuilder::lsr(llvm::Value *data)
{
register_store(
convert_i8_to_jb(builder_.CreateAnd(data, constant_u8(0x1))), flag_c_);
llvm::Value *result = builder_.CreateLShr(data, 1);
set_nz(result);
return result;
}
void FunctionBuilder::ora(llvm::Value *data)
{
llvm::Value *result = builder_.CreateOr(register_load(a_), data);
register_store(result, a_);
set_nz(result);
}
void FunctionBuilder::pop_flags()
{
llvm::Value *p = pop_u8();
register_store(
convert_i8_to_jb(builder_.CreateAnd(p, constant_u8(flagN))), flag_n_);
register_store(
convert_i8_to_jb(builder_.CreateAnd(p, constant_u8(flagV))), flag_v_);
register_store(
convert_i8_to_jb(builder_.CreateAnd(p, constant_u8(flagD))), flag_d_);
register_store(
convert_i8_to_jb(builder_.CreateAnd(p, constant_u8(flagI))), flag_i_);
register_store(
convert_i8_to_jb(builder_.CreateAnd(p, constant_u8(flagZ))), flag_z_);
register_store(
convert_i8_to_jb(builder_.CreateAnd(p, constant_u8(flagC))), flag_c_);
}
llvm::Value *FunctionBuilder::pop_u8()
{
llvm::Value *new_s = builder_.CreateAdd(register_load(s_), constant_u8(1));
register_store(new_s, s_);
return memory_read_untrapped(abs_index(constant_u16(stack), new_s));
}
llvm::Value *FunctionBuilder::pop_u16()
{
llvm::Value *low_byte = pop_u8();
llvm::Value *high_byte = pop_u8();
return create_u16(low_byte, high_byte);
}
void FunctionBuilder::push_u8_raw(llvm::Value *data)
{
memory_write_raw(abs_index(constant_u16(stack), register_load(s_)), data);
register_store(builder_.CreateSub(register_load(s_), constant_u8(1)), s_);
}
void FunctionBuilder::push_u16_raw(uint16_t u)
{
uint8_t high_byte = u >> 8;
uint8_t low_byte = u & 0xff;
push_u8_raw(constant_u8(high_byte));
push_u8_raw(constant_u8(low_byte));
}
// Push the given value onto the stack.
//
// Note that because the push may invalidate code living on the stack,
// this may generate intructions which return control to the caller to
// deal with that, so within a given opcode being translated, no further
// code-generating functions should be called after this.
void FunctionBuilder::push_u8(llvm::Value *data, uint16_t next_opcode_at)
{
llvm::Value *old_s = register_load(s_);
const BoundedAddress &ba = abs_index(constant_u16(stack), old_s);
register_store(builder_.CreateSub(old_s, constant_u8(1)), s_);
memory_write_untrapped(ba, data, next_opcode_at);
}
llvm::Value *FunctionBuilder::rol(llvm::Value *data)
{
llvm::Value *new_low_bit =
convert_i1_to_i8(jit_bool_is_true(register_load(flag_c_)));
register_store(
convert_i8_to_jb(builder_.CreateAnd(data, constant_u8(0x80))), flag_c_);
llvm::Value *result =
builder_.CreateOr(builder_.CreateShl(data, 1), new_low_bit);
set_nz(result);
return result;
}
llvm::Value *FunctionBuilder::ror(llvm::Value *data)
{
llvm::Value *c_as_bit =
convert_i1_to_i8(jit_bool_is_true(register_load(flag_c_)));
llvm::Value *new_high_bit = builder_.CreateShl(c_as_bit, 7);
register_store(
convert_i8_to_jb(builder_.CreateAnd(data, constant_u8(0x1))), flag_c_);
llvm::Value *result =
builder_.CreateOr(builder_.CreateLShr(data, 1), new_high_bit);
set_nz(result);
return result;
}
void FunctionBuilder::sbc(llvm::Value *data)
{
llvm::BasicBlock *done_sbc_block =
llvm::BasicBlock::Create(context_, "done_sbc");
llvm::BasicBlock *sbc_binary_block =
llvm::BasicBlock::Create(context_, "sbc_binary", llvm_function_);
llvm::BasicBlock *sbc_decimal_block =
llvm::BasicBlock::Create(context_, "sbc_decimal", llvm_function_);
llvm::Value *d_clear = jit_bool_is_false(register_load(flag_d_));
builder_.CreateCondBr(d_clear, sbc_binary_block, sbc_decimal_block);
llvm_function_->getBasicBlockList().push_back(done_sbc_block);
builder_.SetInsertPoint(sbc_binary_block);
sbc_binary(data);
builder_.CreateBr(done_sbc_block);
builder_.SetInsertPoint(sbc_decimal_block);
sbc_decimal(data);
builder_.CreateBr(done_sbc_block);
builder_.SetInsertPoint(done_sbc_block);
}
void FunctionBuilder::sbc_binary(llvm::Value *data)
{
llvm::Value *borrow_16 =
zext_i16(jit_bool_is_false(register_load(flag_c_)));
sbc_overflow(data, borrow_16); // must do this before storing new value to a
llvm::Value *a_u16 = zext_i16(register_load(a_));
llvm::Value *data_u16 = zext_i16(data);
llvm::Value *result_u16 =
builder_.CreateSub(builder_.CreateSub(a_u16, data_u16), borrow_16);
llvm::Value *new_a = trunc_i8(result_u16);
register_store(new_a, a_);
set_nz(new_a);
register_store(
convert_i1_to_jb(
builder_.CreateICmpEQ(
builder_.CreateAnd(result_u16, constant_u16(0x100)),
constant_u16(0))),
flag_c_);
}
void FunctionBuilder::sbc_decimal(llvm::Value *data)
{
llvm::Value *borrow = jit_bool_is_false(register_load(flag_c_));
llvm::Value *borrow_16 = zext_i16(borrow);
sbc_overflow(data, borrow_16); // must do this before modifying a
builder_.CreateStore(
builder_.CreateSub(
builder_.CreateSub(
builder_.CreateAnd(
register_load(a_),
constant_u8(0x0f)),
builder_.CreateAnd(
data,
constant_u8(0x0f))),
convert_i1_to_i8(borrow)),
l_tmp_);
builder_.CreateStore(
builder_.CreateSub(
builder_.CreateSub(
zext_i16(register_load(a_)),
zext_i16(data)),
borrow_16),
s_tmp_);
register_store(
convert_i1_to_jb(
builder_.CreateICmpEQ(
builder_.CreateAnd(
builder_.CreateLoad(s_tmp_),
constant_u16(0x100)),
constant_u16(0))),
flag_c_);
llvm::BasicBlock *s_adjust1_block =
llvm::BasicBlock::Create(context_, "s_adjust1", llvm_function_);
llvm::BasicBlock *done_s_adjust1_block =
llvm::BasicBlock::Create(context_, "done_s_adjust1", llvm_function_);
builder_.CreateCondBr(
builder_.CreateICmpSLT(
builder_.CreateLoad(s_tmp_),
constant_u16(0)),
s_adjust1_block,
done_s_adjust1_block);
builder_.SetInsertPoint(s_adjust1_block);
builder_.CreateStore(
builder_.CreateSub(
builder_.CreateLoad(s_tmp_),
constant_u16(0x60)),
s_tmp_);
builder_.CreateBr(done_s_adjust1_block);
builder_.SetInsertPoint(done_s_adjust1_block);
llvm::BasicBlock *s_adjust2_block =
llvm::BasicBlock::Create(context_, "s_adjust2", llvm_function_);
llvm::BasicBlock *done_s_adjust2_block =
llvm::BasicBlock::Create(context_, "done_s_adjust2", llvm_function_);
builder_.CreateCondBr(
builder_.CreateICmpSLT(
builder_.CreateLoad(l_tmp_),
constant_u8(0)),
s_adjust2_block,
done_s_adjust2_block);
builder_.SetInsertPoint(s_adjust2_block);
builder_.CreateStore(
builder_.CreateSub(
builder_.CreateLoad(s_tmp_),
constant_u16(0x06)),
s_tmp_);
builder_.CreateBr(done_s_adjust2_block);
builder_.SetInsertPoint(done_s_adjust2_block);
register_store(trunc_i8(builder_.CreateLoad(s_tmp_)), a_);
set_nz(register_load(a_));
}
void FunctionBuilder::sbc_overflow(
llvm::Value *data, llvm::Value *borrow_16)
{
llvm::Value *a_s16 = sext_i16(register_load(a_));
llvm::Value *data_s16 = sext_i16(data);
llvm::Value *result_s16 =
builder_.CreateSub(builder_.CreateSub(a_s16, data_s16), borrow_16);
llvm::Value *negative_as_unsigned =
builder_.CreateICmpNE(
builder_.CreateAnd(result_s16, constant_u16(0x80)),
constant_u16(0));
llvm::Value *negative_as_signed =
builder_.CreateICmpSLT(result_s16, constant_u16(0));
register_store(
convert_i1_to_jb(
builder_.CreateXor(negative_as_unsigned, negative_as_signed)),
flag_v_);
}
void FunctionBuilder::transfer(
const Register &from, Register &to)
{
llvm::Value *data = builder_.CreateLoad(from.v_);
register_store(data, to);
set_nz(data);
}
llvm::Value *FunctionBuilder::trb(llvm::Value *data)
{
set_z(builder_.CreateAnd(data, register_load(a_)));
llvm::Value *result =
builder_.CreateAnd(
data,
builder_.CreateXor(
register_load(a_),
constant_u8(0xff)));
return result;
}
llvm::Value *FunctionBuilder::tsb(llvm::Value *data)
{
set_z(builder_.CreateAnd(data, register_load(a_)));
llvm::Value *result =
builder_.CreateOr(
data,
register_load(a_));
return result;
}
void FunctionBuilder::set_nz(llvm::Value *data)
{
register_store(convert_i8_to_jb(builder_.CreateAnd(data, 0x80)), flag_n_);
set_z(data);
}
void FunctionBuilder::set_z(llvm::Value *data)
{
register_store(
convert_i1_to_jb(builder_.CreateICmpEQ(data, constant_u8(0))), flag_z_);
}
llvm::Value *FunctionBuilder::flag_byte()
{
builder_.CreateStore(constant_u8(0), p_tmp_);
flag_byte_bit(flag_n_, flagN);
flag_byte_bit(flag_v_, flagV);
flag_byte_bit(flag_d_, flagD);
flag_byte_bit(flag_i_, flagI);
flag_byte_bit(flag_z_, flagZ);
flag_byte_bit(flag_c_, flagC);
return builder_.CreateLoad(p_tmp_);
}
void FunctionBuilder::flag_byte_bit(const Register &flag_reg, uint8_t flag_bit)
{
llvm::BasicBlock *bit_set_block =
llvm::BasicBlock::Create(context_, "bit_set", llvm_function_);
llvm::BasicBlock *bit_done_block =
llvm::BasicBlock::Create(context_, "bit_done", llvm_function_);
llvm::Value *bit_set = jit_bool_is_true(register_load(flag_reg));
builder_.CreateCondBr(bit_set, bit_set_block, bit_done_block);
builder_.SetInsertPoint(bit_set_block);
builder_.CreateStore(
builder_.CreateOr(builder_.CreateLoad(p_tmp_), flag_bit), p_tmp_);
builder_.CreateBr(bit_done_block);
builder_.SetInsertPoint(bit_done_block);
}
void FunctionBuilder::illegal_instruction(uint16_t &ct_pc, int bytes)
{
uint16_t opcode_at = ct_pc;
uint8_t opcode = ct_memory_[opcode_at];
std::stringstream s;
s << "illegal " << hex_prefix << std::hex << std::setw(2) <<
std::setfill('0') << static_cast<int>(opcode) << " ";
switch (bytes)
{
case 1:
disassemble1(ct_pc, s.str());
break;
case 2:
{
uint8_t operand;
disassemble2(ct_pc, s.str(), operand);
break;
}
case 3:
{
uint16_t operand;
disassemble3(ct_pc, s.str(), operand);
break;
}
default:
CANT_HAPPEN("Invalid byte count (ct_pc 0x" << std::hex << ct_pc <<
", " << std::dec << "bytes " << bytes << ")");
}
if (callbacks_.illegal_instruction[opcode] != 0)
{
return_illegal_instruction(ct_pc, opcode_at, opcode);
}
else
{
// Illegal instructions are defined on the 65C02 to be no-ops.
}
}
FunctionBuilder::BoundedAddress FunctionBuilder::zp(uint8_t addr)
{
// We still generate a u16 for the actual llvm::Value. It probably doesn't
// make any difference but it seems logical as memory address "are" 16 bits,
// even if 8-bit ones are handled more efficiently on a real 6502.
return BoundedAddress(*this, constant_u16(addr), AddressRange(addr));
}
FunctionBuilder::BoundedAddress FunctionBuilder::abs(uint16_t addr)
{
return BoundedAddress(*this, constant_u16(addr), AddressRange(addr));
}
FunctionBuilder::BoundedAddress FunctionBuilder::abs_index(
llvm::Value *abs, llvm::Value *index)
{
assert(abs->getType() == i16_type_);
assert(index->getType() == i8_type_);
llvm::ConstantInt *abs_ci = llvm::cast<llvm::ConstantInt>(abs);
uint16_t range_begin = abs_ci->getLimitedValue();
uint32_t range_end = range_begin;
range_end += 0x100;
return BoundedAddress(*this, builder_.CreateAdd(abs, zext_i16(index)),
AddressRange(range_begin, range_end));
}
FunctionBuilder::BoundedAddress FunctionBuilder::zp_index(
llvm::Value *zp, llvm::Value *index)
{
assert(zp->getType() == i8_type_);
assert(index->getType() == i8_type_);
return BoundedAddress(*this, zext_i16(builder_.CreateAdd(zp, index)),
AddressRange(0, 0x100));
}
FunctionBuilder::BoundedAddress FunctionBuilder::zp_post_index(
llvm::Value *zp, llvm::Value *index)
{
assert(zp->getType() == i8_type_);
assert(index->getType() == i8_type_);
llvm::Value *low_byte =
memory_read_untrapped(BoundedAddress(*this, zext_i16(zp)));
llvm::Value *high_byte_at = builder_.CreateAdd(zp, constant_u8(1));
llvm::Value *high_byte =
memory_read_untrapped(BoundedAddress(*this, zext_i16(high_byte_at)));
llvm::Value *base_addr = create_u16(low_byte, high_byte);
return BoundedAddress(*this,
builder_.CreateAdd(base_addr, zext_i16(index)));
}
FunctionBuilder::BoundedAddress FunctionBuilder::zp_pre_index(
llvm::Value *zp, llvm::Value *index)
{
assert(zp->getType() == i8_type_);
assert(index->getType() == i8_type_);
llvm::Value *low_byte_at = builder_.CreateAdd(zp, index);
llvm::Value *high_byte_at = builder_.CreateAdd(low_byte_at, constant_u8(1));
llvm::Value *low_byte =
memory_read_untrapped(BoundedAddress(*this, zext_i16(low_byte_at)));
llvm::Value *high_byte =
memory_read_untrapped(BoundedAddress(*this, zext_i16(high_byte_at)));
return BoundedAddress(*this, create_u16(low_byte, high_byte));
}
llvm::Value *FunctionBuilder::check_predicted_rts(uint16_t subroutine_addr)
{
llvm::Value *mangled_pc = pop_u16();
llvm::Value *new_pc = builder_.CreateAdd(mangled_pc, constant_u16(1));
// It would be correct to just return new_pc at this point; our caller
// will use it to arrange a control transfer. Since that is a run-time
// determined value, the control transfer would have to be done by
// returning from the generated function. We may be able to make some
// plausible guesses (currently never guaranteed to be correct) which
// we can verify at run time and which if correct allow the RTS to be
// handled as a branch within the generated function. This should save
// a bit of overhead on not returning from the function and re-entering
// another and may also allow the optimiser some additional leeway.
const AddressSet &targets = predicted_rts_targets_[subroutine_addr];
TRACE("Generating predicted RTS code; " << targets.size() << " target(s)");
for (AddressSet::const_iterator it = targets.begin(); it != targets.end();
++it)
{
const uint16_t target = *it;
llvm::BasicBlock *prediction_correct =
llvm::BasicBlock::Create(context_, "prediction_correct",
llvm_function_);
llvm::BasicBlock *prediction_incorrect =
llvm::BasicBlock::Create(context_, "prediction_incorrect",
llvm_function_);
builder_.CreateCondBr(
builder_.CreateICmpEQ(constant_u16(target), new_pc),
prediction_correct, prediction_incorrect);
builder_.SetInsertPoint(prediction_correct);
control_transfer_to(constant_u16(target), opcode_rts);
builder_.SetInsertPoint(prediction_incorrect);
}
return new_pc;
}
void FunctionBuilder::control_transfer_to(llvm::Value *target, uint8_t opcode)
{
assert(target->getType() == i16_type_);
switch (opcode)
{
case opcode_rts:
case opcode_rti:
case opcode_bra:
case opcode_bcc:
case opcode_bcs:
case opcode_bvc:
case opcode_bvs:
case opcode_beq:
case opcode_bne:
case opcode_bmi:
case opcode_bpl:
case opcode_implicit:
// This control transfer never triggers a call callback.
break;
case opcode_jsr:
{
// This control transfer triggers a call callback if present. The
// target address is known at compile time.
llvm::ConstantInt *target_ci =
llvm::cast<llvm::ConstantInt>(target);
uint16_t target16 = target_ci->getLimitedValue();
if (callbacks_.call[target16] != 0)
{
return_jsr_complex(target);
return;
}
// We also need to check if the two bytes pushed onto the stack by
// the JSR have invalidated any JITted code and return control to
// our caller if so.
//
// Note that we work with a tmp_s i8 local so that if the stack
// pointer wrapped during the JSR pushes we will still work
// correctly here.
llvm::Value *tmp_s =
builder_.CreateAdd(register_load(s_), constant_u8(1));
llvm::Value *stack_addr1 =
builder_.CreateAdd(constant_u16(stack), zext_i16(tmp_s));
tmp_s = builder_.CreateAdd(tmp_s, constant_u8(1));
llvm::Value *stack_addr2 =
builder_.CreateAdd(constant_u16(stack), zext_i16(tmp_s));
llvm::BasicBlock *code_not_modified_block =
llvm::BasicBlock::Create(context_, "code_not_modified");
llvm::BasicBlock *code_addr1_not_modified_block =
llvm::BasicBlock::Create(context_, "code_addr1_not_modified",
llvm_function_);
llvm::BasicBlock *code_modified_block =
llvm::BasicBlock::Create(context_, "code_modified",
llvm_function_);
const AddressRange stack_range(stack, stack + 0x100);
llvm::Value *stack_addr1_is_code =
is_code_at(BoundedAddress(*this, stack_addr1, stack_range));
builder_.CreateCondBr(stack_addr1_is_code, code_modified_block,
code_addr1_not_modified_block);
builder_.SetInsertPoint(code_addr1_not_modified_block);
llvm::Value *stack_addr2_is_code =
is_code_at(BoundedAddress(*this, stack_addr2, stack_range));
builder_.CreateCondBr(stack_addr2_is_code, code_modified_block,
code_not_modified_block);
builder_.SetInsertPoint(code_modified_block);
return_jsr_complex(target);
llvm_function_->getBasicBlockList().push_back(
code_not_modified_block);
builder_.SetInsertPoint(code_not_modified_block);
break;
}
case opcode_jmp_abs:
{
// This control transfer triggers a call callback if present. The
// target address is known at compile time.
llvm::ConstantInt *target_ci =
llvm::cast<llvm::ConstantInt>(target);
uint16_t target16 = target_ci->getLimitedValue();
if (callbacks_.call[target16] != 0)
{
return_control_transfer_indirect(target, opcode);
return;
}
break;
}
case opcode_jmp_ind_abs:
case opcode_jmp_indx_abs:
{
// This control transfer triggers a call callback if present. The
// target address is only known at run time.
assert(!llvm::isa<llvm::ConstantInt>(target));
llvm::Value *call_callback_addr = builder_.CreateGEP(
call_callbacks_,
llvm::ArrayRef<llvm::Value *>(zext_i32(target)));
llvm::Value *call_callback =
builder_.CreateLoad(call_callback_addr);
llvm::BasicBlock *call_callback_block =
llvm::BasicBlock::Create(context_, "call_callback",
llvm_function_);
llvm::BasicBlock *no_call_callback_block =
llvm::BasicBlock::Create(context_, "no_call_callback",
llvm_function_);
llvm::Value *call_callback_not_null =
builder_.CreateIsNotNull(call_callback);
builder_.CreateCondBr(call_callback_not_null, call_callback_block,
no_call_callback_block);
builder_.SetInsertPoint(call_callback_block);
return_control_transfer_indirect(target, opcode);
builder_.SetInsertPoint(no_call_callback_block);
break;
}
default:
CANT_HAPPEN("Unexpected opcode 0x" << std::hex << opcode);
}
llvm::ConstantInt *target_ci = llvm::dyn_cast<llvm::ConstantInt>(target);
if ((target_ci != 0) && (
code_generated_for_address_[target_ci->getLimitedValue()] ||
(pending_.find(target_ci->getLimitedValue()) != pending_.end())))
{
ensure_address_block_created(target_ci->getLimitedValue());
// The target is within this function, so we can just branch there.
builder_.CreateBr(address_block_[target_ci->getLimitedValue()]);
}
else
{
// The target isn't (knowably) within this function, so we have to
// get there via our caller.
return_control_transfer_direct(target);
}
}
// All memory reads should be done via a call to this function, unless they are
// explicitly exempt from read callbacks.
llvm::Value *FunctionBuilder::memory_read(const BoundedAddress &ba)
{
llvm::Value *addr = ba.addr();
llvm::ConstantInt *addr_ci = llvm::dyn_cast<llvm::ConstantInt>(addr);
if (addr_ci != 0)
{
uint16_t addr16 = addr_ci->getLimitedValue();
TRACE("Load at compile-time constant address 0x" << std::hex <<
std::setfill('0') << std::setw(4) << addr16);
if (callbacks_.read[addr16] != 0)
{
TRACE("Read callback exists at constant address");
llvm::Value *callback =
constant_ptr(callbacks_.read[addr16], "read_callback");
return call_read_callback(callback, addr);
}
// Actually do the read from memory.
return memory_read_untrapped(ba);
}
else
{
if (callback_in_bounds(callbacks_.read, ba.bounds()))
{
TRACE("Read callback may exist; runtime check required");
llvm::Value *read_callback_addr = builder_.CreateGEP(
read_callbacks_, llvm::ArrayRef<llvm::Value *>(zext_i32(addr)));
llvm::Value *read_callback =
builder_.CreateLoad(read_callback_addr);
llvm::BasicBlock *read_callback_block =
llvm::BasicBlock::Create(context_, "read_callback",
llvm_function_);
llvm::BasicBlock *no_read_callback_block =
llvm::BasicBlock::Create(context_, "no_read_callback",
llvm_function_);
llvm::BasicBlock *memory_read_done_block =
llvm::BasicBlock::Create(context_, "memory_read_done");
llvm::Value *read_callback_not_null =
builder_.CreateIsNotNull(read_callback);
builder_.CreateCondBr(read_callback_not_null, read_callback_block,
no_read_callback_block);
builder_.SetInsertPoint(read_callback_block);
llvm::Value *result = call_read_callback(read_callback, ba.addr());
builder_.CreateStore(result, read_callback_result_);
builder_.CreateBr(memory_read_done_block);
builder_.SetInsertPoint(no_read_callback_block);
builder_.CreateStore(memory_read_untrapped(ba),
read_callback_result_);
builder_.CreateBr(memory_read_done_block);
llvm_function_->getBasicBlockList().push_back(
memory_read_done_block);
builder_.SetInsertPoint(memory_read_done_block);
return builder_.CreateLoad(read_callback_result_);
}
else
{
TRACE("No read callback within address bounds");
// Actually do the read from memory.
return memory_read_untrapped(ba);
}
}
}
llvm::Value *FunctionBuilder::memory_read_untrapped(const BoundedAddress &ba)
{
llvm::Value *host_addr = builder_.CreateGEP(
memory_base_, llvm::ArrayRef<llvm::Value *>(zext_i32(ba.addr())));
return builder_.CreateLoad(host_addr);
}
// All memory writes should be done via a call to this function, unless they
// are explicitly exempt from triggering write callbacks.
//
// Note that because this may return to the caller to indicate
// result_write_to_code or result_write_callback, it must be the last
// code-generation function called when translating an opcode, as any
// subsequent code may not be executed.
void FunctionBuilder::memory_write(const BoundedAddress &ba,
llvm::Value *data, uint16_t next_opcode_at)
{
llvm::ConstantInt *addr_ci = llvm::dyn_cast<llvm::ConstantInt>(ba.addr());
if (addr_ci != 0)
{
uint16_t addr16 = addr_ci->getLimitedValue();
TRACE("Store at compile-time constant address 0x" << std::hex <<
std::setfill('0') << std::setw(4) << addr16);
if (callbacks_.write[addr16] != 0)
{
TRACE("Write callback exists at constant address");
return_write_callback(next_opcode_at, ba.addr(), data);
return;
}
}
else
{
if (callback_in_bounds(callbacks_.write, ba.bounds()))
{
TRACE("Write callback may exist; runtime check required");
llvm::Value *write_callback_addr = builder_.CreateGEP(
write_callbacks_,
llvm::ArrayRef<llvm::Value *>(zext_i32(ba.addr())));
llvm::Value *write_callback =
builder_.CreateLoad(write_callback_addr);
llvm::BasicBlock *write_callback_block =
llvm::BasicBlock::Create(context_, "write_callback",
llvm_function_);
llvm::BasicBlock *no_write_callback_block =
llvm::BasicBlock::Create(context_, "no_write_callback",
llvm_function_);
llvm::Value *write_callback_not_null =
builder_.CreateIsNotNull(write_callback);
builder_.CreateCondBr(write_callback_not_null, write_callback_block,
no_write_callback_block);
builder_.SetInsertPoint(write_callback_block);
return_write_callback(next_opcode_at, ba.addr(), data);
builder_.SetInsertPoint(no_write_callback_block);
}
else
{
TRACE("No write callback within address bounds");
}
}
memory_write_untrapped(ba, data, next_opcode_at);
}
// Note that (like lib6502 proper) we don't externalise our registers before
// invoking the (read/write) callback or internalise them afterwards, so
// the callback doesn't see correct information if it examines the CPU state.
llvm::Value *FunctionBuilder::call_callback(
llvm::Value *callback, llvm::Value *addr,
llvm::Value *data)
{
return builder_.CreateCall3(callback, mpu_llvm_, addr, data,
"callback_result");
}
llvm::Value *FunctionBuilder::call_read_callback(
llvm::Value *callback, llvm::Value *addr)
{
llvm::Value *result_int = call_callback(callback, addr, constant_u8(0));
return builder_.CreateTrunc(result_int, i8_type_);
}
// Write to memory with no checks for modification of already JITted code or
// write callbacks.
void FunctionBuilder::memory_write_raw(const BoundedAddress &ba,
llvm::Value *data)
{
llvm::Value *host_addr = builder_.CreateGEP(
memory_base_, llvm::ArrayRef<llvm::Value *>(zext_i32(ba.addr())));
builder_.CreateStore(data, host_addr);
}
llvm::Value *FunctionBuilder::is_code_at(const BoundedAddress &ba)
{
const AddressRange &bounds = ba.bounds();
bool use_optimistic_write = !bounds.all_memory();
for (AddressRange::const_iterator it = bounds.begin();
use_optimistic_write && (it != bounds.end()); ++it)
{
uint16_t i = *it;
if (code_at_address_[i])
{
TRACE("BoundedAddress " << ba <<
" includes known code at 0x" << std::hex <<
std::setfill('0') << std::setw(4) << i <<
"; can't use optimistic write");
use_optimistic_write = false;
}
}
if (use_optimistic_write)
{
optimistic_writes_.insert(ba.bounds());
return constant_i1(false);
}
else
{
llvm::Value *code_at_address_flag_addr = builder_.CreateGEP(
code_at_address_llvm_,
llvm::ArrayRef<llvm::Value *>(zext_i32(ba.addr())));
return jit_bool_is_true(builder_.CreateLoad(code_at_address_flag_addr));
}
}
// Write to memory, checking for modification of already JITted code but
// not for write callbacks.
//
// Note that because this may return to the caller to indicate
// result_write_to_code, it must be the last code-generation function called
// when translating an opcode, as any subsequent code may not be executed.
void FunctionBuilder::memory_write_untrapped(
const BoundedAddress &ba, llvm::Value *data,
uint16_t next_opcode_at)
{
// Actually do the write.
memory_write_raw(ba, data);
// Check for writes which modify JITted code.
llvm::Value *just_modified_code = is_code_at(ba);
// The optimiser would eliminate the dead branches if just_modified_code
// is a constant false value, but to make the IR easier to read and perhaps
// help the optimiser out, let's not generate pointless code in this case.
llvm::ConstantInt *just_modified_ci =
llvm::dyn_cast<llvm::ConstantInt>(just_modified_code);
if ((just_modified_ci != 0) && !(just_modified_ci->getLimitedValue()))
{
return;
}
llvm::BasicBlock *code_modified_block =
llvm::BasicBlock::Create(context_, "code_modified", llvm_function_);
llvm::BasicBlock *code_not_modified_block =
llvm::BasicBlock::Create(context_, "code_not_modified", llvm_function_);
builder_.CreateCondBr(just_modified_code, code_modified_block,
code_not_modified_block);
builder_.SetInsertPoint(code_modified_block);
return_write_to_code(next_opcode_at, ba.addr());
builder_.SetInsertPoint(code_not_modified_block);
}
void FunctionBuilder::return_pc(Result result, llvm::Value *new_pc)
{
builder_.CreateStore(constant_i(result), function_result_);
builder_.CreateStore(new_pc, pc_);
builder_.CreateBr(epilogue_);
}
void FunctionBuilder::return_pc_addr(Result result, llvm::Value *new_pc,
llvm::Value *addr)
{
builder_.CreateStore(constant_i(result), function_result_);
builder_.CreateStore(new_pc, pc_);
builder_.CreateStore(addr, builder_.CreateStructGEP(registers_, 11));
builder_.CreateBr(epilogue_);
}
void FunctionBuilder::return_pc_data(Result result, llvm::Value *new_pc,
llvm::Value *data)
{
builder_.CreateStore(constant_i(result), function_result_);
builder_.CreateStore(new_pc, pc_);
builder_.CreateStore(data, builder_.CreateStructGEP(registers_, 12));
builder_.CreateBr(epilogue_);
}
void FunctionBuilder::return_pc_addr_data(
Result result, llvm::Value *new_pc, llvm::Value *addr, llvm::Value *data)
{
builder_.CreateStore(constant_i(result), function_result_);
builder_.CreateStore(new_pc, pc_);
builder_.CreateStore(addr, builder_.CreateStructGEP(registers_, 11));
builder_.CreateStore(data, builder_.CreateStructGEP(registers_, 12));
builder_.CreateBr(epilogue_);
}
void FunctionBuilder::return_control_transfer_direct(llvm::Value *new_pc)
{
return_pc(result_control_transfer_direct, new_pc);
}
void FunctionBuilder::return_control_transfer_indirect(
llvm::Value *new_pc, uint8_t opcode)
{
return_pc_data(result_control_transfer_indirect, new_pc,
constant_u8(opcode));
}
void FunctionBuilder::return_brk(llvm::Value *new_pc)
{
return_pc(result_brk, new_pc);
}
void FunctionBuilder::return_jsr_complex(llvm::Value *new_pc)
{
return_pc(result_jsr_complex, new_pc);
}
void FunctionBuilder::return_illegal_instruction(
uint16_t new_pc, uint16_t opcode_at, uint8_t opcode)
{
return_pc_addr_data(result_illegal_instruction, constant_u16(new_pc),
constant_u16(opcode_at), constant_u8(opcode));
}
void FunctionBuilder::return_write_to_code(uint16_t new_pc, llvm::Value *addr)
{
return_pc_addr(result_write_to_code, constant_u16(new_pc), addr);
}
void FunctionBuilder::return_write_callback(
uint16_t new_pc, llvm::Value *addr, llvm::Value *data)
{
return_pc_addr_data(
result_write_callback, constant_u16(new_pc), addr, data);
}
void FunctionBuilder::return_invalid_bounds()
{
builder_.CreateStore(constant_i(result_invalid_bounds), function_result_);
builder_.CreateBr(epilogue_);
}
void FunctionBuilder::disassemble1(uint16_t &addr, const std::string &s)
{
disassemble_hex_dump(addr, 1);
disassembly_ << s << "\n";
++addr;
}
void FunctionBuilder::disassemble2(
uint16_t &addr, const std::string &prefix, uint8_t &operand,
const std::string &suffix)
{
disassemble_hex_dump(addr, 2);
operand = operand8(addr);
disassembly_ << prefix << hex_prefix << std::setw(2) <<
static_cast<int>(operand) << suffix;
// This is a bit of a special case, but it works so...
std::string::size_type l = prefix.length();
if ((l > 1) && (prefix[l - 1] == '#') && isprint(operand))
{
disassembly_ << " ('" << static_cast<char>(operand) << "')";
}
disassembly_ << "\n";
addr += 2;
}
void FunctionBuilder::disassemble3(
uint16_t &addr, const std::string &prefix, uint16_t &operand,
const std::string &suffix)
{
disassemble_hex_dump(addr, 3);
operand = operand16(addr);
disassembly_ << prefix << hex_prefix << std::setw(4) << operand << suffix <<
"\n";
addr += 3;
}
void FunctionBuilder::disassemble_branch(
uint16_t &addr, const std::string &s, uint16_t &target)
{
disassemble_hex_dump(addr, 2);
uint8_t operand = operand8(addr);
int offset = (operand < 0x80) ? operand : -(0x100 - operand);
// The branch is relative to the PC *after* it's been moved past the
// branch instruction.
addr += 2;
target = addr + offset;
disassembly_ << s << hex_prefix << std::setw(4) << target << "\n";
}
void FunctionBuilder::disassemble_hex_dump(uint16_t addr, int bytes)
{
assert(bytes <= 3);
disassembly_ << std::hex << std::setw(4) << std::setfill('0') << addr <<
" ";
for (int i = 0; i < 3; ++i)
{
if (i < bytes)
{
disassembly_ << std::setw(2) <<
static_cast<int>(ct_memory_[addr + i]) << " ";
}
else
{
disassembly_ << " ";
}
}
}