mirror of
https://github.com/holub/mame
synced 2025-05-04 21:43:05 +03:00
394 lines
13 KiB
C++
394 lines
13 KiB
C++
// license:BSD-3-Clause
|
|
// copyright-holders:Aaron Giles
|
|
/***************************************************************************
|
|
|
|
drcfe.c
|
|
|
|
Generic dynamic recompiler frontend structures and utilities.
|
|
|
|
****************************************************************************
|
|
|
|
Future improvements/changes:
|
|
|
|
* more aggressive handling of needed registers for conditional
|
|
intrablock branches
|
|
|
|
***************************************************************************/
|
|
|
|
#include "emu.h"
|
|
#include "drcfe.h"
|
|
|
|
|
|
namespace {
|
|
|
|
//**************************************************************************
|
|
// CONSTANTS
|
|
//**************************************************************************
|
|
|
|
constexpr u32 MAX_STACK_DEPTH = 100;
|
|
|
|
|
|
|
|
//**************************************************************************
|
|
// TYPE DEFINITIONS
|
|
//**************************************************************************
|
|
|
|
// an entry that maps branches for our code walking
|
|
struct pc_stack_entry
|
|
{
|
|
offs_t targetpc;
|
|
offs_t srcpc;
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
|
|
//**************************************************************************
|
|
// DRC FRONTEND
|
|
//**************************************************************************
|
|
|
|
//-------------------------------------------------
|
|
// drc_frontend - constructor
|
|
//-------------------------------------------------
|
|
|
|
drc_frontend::drc_frontend(device_t &cpu, u32 window_start, u32 window_end, u32 max_sequence)
|
|
: m_window_start(window_start)
|
|
, m_window_end(window_end)
|
|
, m_max_sequence(max_sequence)
|
|
, m_cpudevice(downcast<cpu_device &>(cpu))
|
|
, m_program(m_cpudevice.space(AS_PROGRAM))
|
|
, m_pageshift(m_cpudevice.space_config(AS_PROGRAM)->m_page_shift)
|
|
, m_desc_array(window_end + window_start + 2, nullptr)
|
|
{
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// ~drc_frontend - destructor
|
|
//-------------------------------------------------
|
|
|
|
drc_frontend::~drc_frontend()
|
|
{
|
|
// release any descriptions we've accumulated
|
|
release_descriptions();
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// describe_code - describe a sequence of code
|
|
// that falls within the configured window
|
|
// relative to the specified startpc
|
|
//-------------------------------------------------
|
|
|
|
const opcode_desc *drc_frontend::describe_code(offs_t startpc)
|
|
{
|
|
// release any descriptions we've accumulated
|
|
release_descriptions();
|
|
|
|
// add the initial PC to the stack
|
|
pc_stack_entry pcstack[MAX_STACK_DEPTH];
|
|
pc_stack_entry *pcstackptr = &pcstack[0];
|
|
pcstackptr->srcpc = 0;
|
|
pcstackptr->targetpc = startpc;
|
|
pcstackptr++;
|
|
|
|
// loop while we still have a stack
|
|
offs_t const minpc = startpc - (std::min)(m_window_start, startpc);
|
|
offs_t const maxpc = startpc + (std::min)(m_window_end, 0xffffffff - startpc);
|
|
while (pcstackptr != &pcstack[0])
|
|
{
|
|
// if we've already hit this PC, just mark it a branch target and continue
|
|
pc_stack_entry *const curstack = --pcstackptr;
|
|
opcode_desc *curdesc = m_desc_array[curstack->targetpc - minpc];
|
|
if (curdesc != nullptr)
|
|
{
|
|
curdesc->flags |= OPFLAG_IS_BRANCH_TARGET;
|
|
|
|
// if the branch crosses a page boundary, mark the target as needing to revalidate
|
|
if (m_pageshift != 0 && ((curstack->srcpc ^ curdesc->pc) >> m_pageshift) != 0)
|
|
curdesc->flags |= OPFLAG_VALIDATE_TLB | OPFLAG_CAN_CAUSE_EXCEPTION;
|
|
|
|
// continue processing
|
|
continue;
|
|
}
|
|
|
|
// loop until we exit the block
|
|
for (offs_t curpc = curstack->targetpc; curpc >= minpc && curpc < maxpc && m_desc_array[curpc - minpc] == nullptr; curpc += m_desc_array[curpc - minpc]->length)
|
|
{
|
|
// allocate a new description and describe this instruction
|
|
m_desc_array[curpc - minpc] = curdesc = describe_one(curpc, curdesc);
|
|
|
|
// first instruction in a sequence is always a branch target
|
|
if (curpc == curstack->targetpc)
|
|
curdesc->flags |= OPFLAG_IS_BRANCH_TARGET;
|
|
|
|
// stop if we hit a page fault
|
|
if (curdesc->flags & OPFLAG_COMPILER_PAGE_FAULT)
|
|
break;
|
|
|
|
// if we are the first instruction in the whole window, we must validate the TLB
|
|
if (curpc == startpc && m_pageshift != 0)
|
|
curdesc->flags |= OPFLAG_VALIDATE_TLB | OPFLAG_CAN_CAUSE_EXCEPTION;
|
|
|
|
// if we are a branch within the block range, add the branch target to our stack
|
|
if ((curdesc->flags & OPFLAG_IS_BRANCH) && curdesc->targetpc >= minpc && curdesc->targetpc < maxpc && pcstackptr < &pcstack[MAX_STACK_DEPTH])
|
|
{
|
|
curdesc->flags |= OPFLAG_INTRABLOCK_BRANCH;
|
|
pcstackptr->srcpc = curdesc->pc;
|
|
pcstackptr->targetpc = curdesc->targetpc;
|
|
pcstackptr++;
|
|
}
|
|
|
|
// if we're done, we're done
|
|
if (curdesc->flags & OPFLAG_END_SEQUENCE)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// now build the list of descriptions in order
|
|
// first from startpc -> maxpc, then from minpc -> startpc
|
|
build_sequence(startpc - minpc, maxpc - minpc, OPFLAG_REDISPATCH);
|
|
build_sequence(minpc - minpc, startpc - minpc, OPFLAG_RETURN_TO_START);
|
|
return m_desc_live_list.first();
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// describe_one - describe a single instruction,
|
|
// recursively describing opcodes in delay
|
|
// slots of branches as well
|
|
//-------------------------------------------------
|
|
|
|
opcode_desc *drc_frontend::describe_one(offs_t curpc, opcode_desc const *prevdesc, bool in_delay_slot)
|
|
{
|
|
// initialize the description
|
|
opcode_desc *const desc = m_desc_allocator.alloc();
|
|
desc->m_next = nullptr;
|
|
desc->branch = nullptr;
|
|
desc->delay.reset();
|
|
desc->pc = curpc;
|
|
desc->physpc = curpc;
|
|
desc->targetpc = BRANCH_TARGET_DYNAMIC;
|
|
memset(&desc->opptr, 0x00, sizeof(desc->opptr));
|
|
desc->length = 0;
|
|
desc->delayslots = 0;
|
|
desc->skipslots = 0;
|
|
// set the delay slot flag
|
|
desc->flags = in_delay_slot ? OPFLAG_IN_DELAY_SLOT : 0;
|
|
desc->userflags = 0;
|
|
desc->userdata0 = 0;
|
|
desc->cycles = 0;
|
|
memset(desc->regin, 0x00, sizeof(desc->regin));
|
|
memset(desc->regout, 0x00, sizeof(desc->regout));
|
|
memset(desc->regreq, 0x00, sizeof(desc->regreq));
|
|
|
|
// call the callback to describe an instruction
|
|
if (!describe(*desc, prevdesc))
|
|
{
|
|
desc->flags |= OPFLAG_WILL_CAUSE_EXCEPTION | OPFLAG_INVALID_OPCODE;
|
|
return desc;
|
|
}
|
|
|
|
// validate the TLB if we are exactly at the start of a page, or if we cross a page boundary
|
|
if (m_pageshift != 0 && (((curpc - 1) ^ (curpc + desc->length - 1)) >> m_pageshift) != 0)
|
|
desc->flags |= OPFLAG_VALIDATE_TLB | OPFLAG_CAN_CAUSE_EXCEPTION;
|
|
|
|
// validate stuff
|
|
assert(desc->length > 0 || (desc->flags & OPFLAG_VIRTUAL_NOOP) != 0);
|
|
|
|
// if we are a branch with delay slots, recursively walk those
|
|
if (desc->flags & OPFLAG_IS_BRANCH)
|
|
{
|
|
// iterate over slots and describe them
|
|
offs_t delaypc = curpc + desc->length;
|
|
// If this is a delay slot it is the true branch fork and the pc should be the previous branch target
|
|
if (desc->flags & OPFLAG_IN_DELAY_SLOT) {
|
|
if (prevdesc->targetpc != BRANCH_TARGET_DYNAMIC) {
|
|
delaypc = prevdesc->targetpc;
|
|
//printf("drc_frontend::describe_one Branch in delay slot. curpc=%08X delaypc=%08X\n", curpc, delaypc);
|
|
} else {
|
|
//printf("drc_frontend::describe_one Warning! Branch in delay slot of dynamic target. curpc=%08X\n", curpc);
|
|
}
|
|
}
|
|
opcode_desc *prev = desc;
|
|
for (u8 slotnum = 0; slotnum < desc->delayslots; slotnum++)
|
|
{
|
|
// recursively describe the next instruction
|
|
opcode_desc *delaydesc = describe_one(delaypc, prev, true);
|
|
if (delaydesc == nullptr)
|
|
break;
|
|
desc->delay.append(*delaydesc);
|
|
prev = desc;
|
|
|
|
// set a pointer back to the original branch
|
|
delaydesc->branch = desc;
|
|
|
|
// stop if we hit a page fault
|
|
if (delaydesc->flags & OPFLAG_COMPILER_PAGE_FAULT)
|
|
break;
|
|
|
|
// otherwise, advance
|
|
delaypc += delaydesc->length;
|
|
}
|
|
}
|
|
return desc;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// build_sequence - build an ordered sequence
|
|
// of instructions
|
|
//-------------------------------------------------
|
|
|
|
void drc_frontend::build_sequence(int start, int end, u32 endflag)
|
|
{
|
|
// iterate in order from start to end, picking up all non-NULL instructions
|
|
int consecutive = 0;
|
|
int seqstart = -1;
|
|
int skipsleft = 0;
|
|
for (int descnum = start; descnum < end; descnum++)
|
|
if (m_desc_array[descnum] != nullptr)
|
|
{
|
|
// determine the next instruction, taking skips into account
|
|
opcode_desc *curdesc = m_desc_array[descnum];
|
|
int nextdescnum = descnum + curdesc->length;
|
|
opcode_desc *nextdesc = (nextdescnum < end) ? m_desc_array[nextdescnum] : nullptr;
|
|
for (u8 skipnum = 0; skipnum < curdesc->skipslots && nextdesc != nullptr; skipnum++)
|
|
{
|
|
nextdescnum = nextdescnum + nextdesc->length;
|
|
nextdesc = (nextdescnum < end) ? m_desc_array[nextdescnum] : nullptr;
|
|
}
|
|
|
|
// start a new sequence if we aren't already in the middle of one
|
|
if (seqstart == -1 && skipsleft == 0)
|
|
{
|
|
// tag all start-of-sequence instructions as needing TLB verification
|
|
curdesc->flags |= OPFLAG_VALIDATE_TLB | OPFLAG_CAN_CAUSE_EXCEPTION;
|
|
seqstart = descnum;
|
|
}
|
|
|
|
// if we are the last instruction, indicate end-of-sequence and redispatch
|
|
if (nextdesc == nullptr)
|
|
{
|
|
curdesc->flags |= OPFLAG_END_SEQUENCE;
|
|
if (endflag != OPFLAG_RETURN_TO_START || nextdescnum == end)
|
|
curdesc->flags |= endflag;
|
|
}
|
|
|
|
// otherwise, do some analysis based on the next instruction
|
|
else
|
|
{
|
|
// if there are instructions between us and the next instruction, we must end our sequence here
|
|
int scandescnum;
|
|
opcode_desc *scandesc = nullptr;
|
|
for (scandescnum = descnum + 1; scandescnum < end; scandescnum++)
|
|
{
|
|
scandesc = m_desc_array[scandescnum];
|
|
if (scandesc != nullptr || scandesc == nextdesc)
|
|
break;
|
|
}
|
|
if (scandesc != nextdesc)
|
|
curdesc->flags |= OPFLAG_END_SEQUENCE;
|
|
|
|
// if the next instruction is a branch target, mark this instruction as end of sequence
|
|
if (nextdesc->flags & OPFLAG_IS_BRANCH_TARGET)
|
|
curdesc->flags |= OPFLAG_END_SEQUENCE;
|
|
}
|
|
|
|
// if we exceed the maximum consecutive count, cut off the sequence
|
|
if (++consecutive >= m_max_sequence)
|
|
curdesc->flags |= OPFLAG_END_SEQUENCE;
|
|
if (curdesc->flags & OPFLAG_END_SEQUENCE)
|
|
consecutive = 0;
|
|
|
|
// if this is the end of a sequence, work backwards
|
|
if (curdesc->flags & OPFLAG_END_SEQUENCE)
|
|
{
|
|
// figure out which registers we *must* generate, assuming at the end all must be
|
|
u32 reqmask[4] = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff };
|
|
if (seqstart != -1)
|
|
for (int backdesc = descnum; backdesc != seqstart - 1; backdesc--)
|
|
if (m_desc_array[backdesc] != nullptr)
|
|
accumulate_required_backwards(*m_desc_array[backdesc], reqmask);
|
|
|
|
// reset the register states
|
|
seqstart = -1;
|
|
}
|
|
|
|
// if we have instructions remaining to be skipped, and this instruction is a branch target
|
|
// belay the skip order
|
|
if (skipsleft > 0 && (curdesc->flags & OPFLAG_IS_BRANCH_TARGET))
|
|
skipsleft = 0;
|
|
|
|
// if we're not getting skipped, add us to the end of the list and clear our array slot
|
|
if (skipsleft == 0)
|
|
m_desc_live_list.append(*curdesc);
|
|
else
|
|
m_desc_allocator.reclaim(*curdesc);
|
|
|
|
// if the current instruction starts skipping, reset our skip count
|
|
// otherwise, just decrement
|
|
if (curdesc->skipslots > 0)
|
|
skipsleft = curdesc->skipslots;
|
|
else if (skipsleft > 0)
|
|
skipsleft--;
|
|
}
|
|
|
|
// zap the array
|
|
memset(&m_desc_array[start], 0, (end - start) * sizeof(m_desc_array[0]));
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// accumulate_required_backwards - recursively
|
|
// accumulate live register liveness information
|
|
// walking in a backwards direction
|
|
//-------------------------------------------------
|
|
|
|
void drc_frontend::accumulate_required_backwards(opcode_desc &desc, u32 *reqmask)
|
|
{
|
|
// recursively handle delay slots
|
|
if (desc.delay.first() != nullptr)
|
|
accumulate_required_backwards(*desc.delay.first(), reqmask);
|
|
|
|
// if this is a branch, we have to reset our requests
|
|
if (desc.flags & OPFLAG_IS_BRANCH)
|
|
reqmask[0] = reqmask[1] = reqmask[2] = reqmask[3] = 0xffffffff;
|
|
|
|
// determine the required registers
|
|
desc.regreq[0] = desc.regout[0] & reqmask[0];
|
|
desc.regreq[1] = desc.regout[1] & reqmask[1];
|
|
desc.regreq[2] = desc.regout[2] & reqmask[2];
|
|
desc.regreq[3] = desc.regout[3] & reqmask[3];
|
|
|
|
// any registers modified by this instruction aren't required upstream until referenced
|
|
reqmask[0] &= ~desc.regout[0];
|
|
reqmask[1] &= ~desc.regout[1];
|
|
reqmask[2] &= ~desc.regout[2];
|
|
reqmask[3] &= ~desc.regout[3];
|
|
|
|
// any registers required by this instruction now get marked required
|
|
reqmask[0] |= desc.regin[0];
|
|
reqmask[1] |= desc.regin[1];
|
|
reqmask[2] |= desc.regin[2];
|
|
reqmask[3] |= desc.regin[3];
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// release_descriptions - release any
|
|
// descriptions we've allocated back to the
|
|
// free list
|
|
//------------------------------------------------
|
|
|
|
void drc_frontend::release_descriptions()
|
|
{
|
|
// release all delay slots first
|
|
for (opcode_desc *curdesc = m_desc_live_list.first(); curdesc != nullptr; curdesc = curdesc->next())
|
|
m_desc_allocator.reclaim_all(curdesc->delay);
|
|
|
|
// reclaim all the descriptors
|
|
m_desc_allocator.reclaim_all(m_desc_live_list);
|
|
}
|