blob: ea04e98c1ae4b466fb51d852dcc51596c1ebcae6 [file] [log] [blame]
/* $Id: sparc-execute.c,v 1.10 2010/02/20 21:58:15 fredette Exp $ */
/* ic/sparc/sparc-execute.c - executes SPARC instructions: */
/*
* Copyright (c) 2005, 2009 Matt Fredette
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Matt Fredette.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
_TME_RCSID("$Id: sparc-execute.c,v 1.10 2010/02/20 21:58:15 fredette Exp $");
/* includes: */
#include "sparc-auto.h"
#if (TME_SPARC_VERSION(ic) < 9)
#define tme_sparc_ireg_t tme_uint32_t
#define tme_sparc_ireg(x) tme_sparc_ireg_uint32(x)
#define tme_sparc_idle_pcs tme_sparc_idle_pcs_32
#define TME_PRIxSPARCREG "0x%08" TME_PRIx32
#else /* TME_SPARC_VERSION(ic) >= 9 */
#define tme_sparc_ireg_t tme_uint64_t
#define tme_sparc_ireg(x) tme_sparc_ireg_uint64(x)
#define tme_sparc_idle_pcs tme_sparc_idle_pcs_64
#define TME_PRIxSPARCREG "0x%016" TME_PRIx64
#endif /* TME_SPARC_VERSION(ic) >= 9 */
/* the sparc instruction executor: */
static void
_TME_SPARC_EXECUTE_NAME(struct tme_sparc *ic)
{
tme_uint32_t asi_mask_insn;
tme_uint32_t asi_mask_data;
struct tme_sparc_tlb *itlb_current;
struct tme_sparc_tlb itlb_invalid;
struct tme_token token_invalid;
tme_sparc_ireg_t pc_previous;
tme_sparc_ireg_t pc;
tme_uint32_t insn;
tme_uint32_t tlb_hash;
const tme_shared tme_uint8_t *emulator_off;
unsigned int opcode;
unsigned int reg_rs1;
unsigned int reg_rs2;
unsigned int reg_rd;
int annulled;
int branch_dot;
tme_uint32_t branch_dot_burst;
#if TME_SPARC_VERSION(ic) >= 9
unsigned int cc;
tme_uint64_t value_rs1;
#endif /* TME_SPARC_VERSION(ic) >= 9 */
tme_uint8_t conds_mask_icc;
tme_uint8_t conds_mask_fcc;
tme_uint16_t conds_mask;
unsigned int cond;
tme_int32_t disp;
tme_sparc_ireg_t pc_next_next;
unsigned int reg_o0;
/* get the default address space identifiers and masks: */
if (TME_SPARC_VERSION(ic) < 9) {
if (TME_SPARC_PRIV(ic)) {
asi_mask_insn = TME_SPARC32_ASI_MASK_SI;
asi_mask_data = TME_SPARC32_ASI_MASK_SD;
}
else {
asi_mask_insn = TME_SPARC32_ASI_MASK_UI;
asi_mask_data = TME_SPARC32_ASI_MASK_UD;
}
}
else {
if (__tme_predict_false((TME_SPARC_MEMORY_FLAGS(ic) & TME_SPARC_MEMORY_FLAG_HAS_NUCLEUS)
&& ic->tme_sparc64_ireg_tl > 0)) {
asi_mask_insn
= TME_SPARC64_ASI_MASK_NUCLEUS(!TME_SPARC64_ASI_FLAG_LITTLE);
ic->tme_sparc_memory_context_default = 0;
}
else {
asi_mask_insn
= TME_SPARC64_ASI_MASK_REQUIRED_UNRESTRICTED((TME_SPARC_PRIV(ic)
? !TME_SPARC64_ASI_MASK_FLAG_INSN_AS_IF_USER
: TME_SPARC64_ASI_MASK_FLAG_INSN_AS_IF_USER)
+ !TME_SPARC64_ASI_FLAG_SECONDARY
+ !TME_SPARC64_ASI_FLAG_NO_FAULT
+ !TME_SPARC64_ASI_FLAG_LITTLE);
ic->tme_sparc_memory_context_default = ic->tme_sparc_memory_context_primary;
}
asi_mask_data = asi_mask_insn;
if (__tme_predict_false(ic->tme_sparc64_ireg_pstate & TME_SPARC64_PSTATE_CLE)) {
assert ((TME_SPARC64_ASI_MASK_NUCLEUS(!TME_SPARC64_ASI_FLAG_LITTLE)
^ TME_SPARC64_ASI_MASK_NUCLEUS(TME_SPARC64_ASI_FLAG_LITTLE))
== (TME_SPARC64_ASI_MASK_REQUIRED_UNRESTRICTED(!TME_SPARC64_ASI_FLAG_LITTLE)
^ TME_SPARC64_ASI_MASK_REQUIRED_UNRESTRICTED(TME_SPARC64_ASI_FLAG_LITTLE)));
asi_mask_data
^= (TME_SPARC64_ASI_MASK_NUCLEUS(!TME_SPARC64_ASI_FLAG_LITTLE)
^ TME_SPARC64_ASI_MASK_NUCLEUS(TME_SPARC64_ASI_FLAG_LITTLE));
}
}
ic->tme_sparc_asi_mask_insn = asi_mask_insn;
ic->tme_sparc_asi_mask_data = asi_mask_data;
#if TME_SPARC_HAVE_RECODE(ic)
/* set the recode read/write TLB flags mask to and with the flags
from a read/write thunk, before being tested against the flags in
a recode DTLB entry. this TLB flags mask must clear flags that
do not apply, based on the current state: */
ic->tme_sparc_recode_rw_tlb_flags
= (TME_RECODE_TLB_FLAGS_MASK(ic->tme_sparc_recode_ic)
- (
/* the load and store flags for the other privilege level do
not apply, because we're not at that privilege level: */
(TME_SPARC_PRIV(ic)
? (TME_SPARC_RECODE_TLB_FLAG_LD_USER(ic)
+ TME_SPARC_RECODE_TLB_FLAG_ST_USER(ic))
: (TME_SPARC_RECODE_TLB_FLAG_LD_PRIV(ic)
+ TME_SPARC_RECODE_TLB_FLAG_ST_PRIV(ic)))
/* on a v9 CPU, if the ASI register has the default data
ASI, but with the no-fault bit set, the ASI register is
correct for no-fault loads, and the no-fault load bit
doesn't apply: */
+ ((TME_SPARC_VERSION(ic) >= 9
&& ((TME_SPARC_MEMORY_FLAGS(ic) & TME_SPARC_MEMORY_FLAG_HAS_NUCLEUS) == 0
|| ic->tme_sparc64_ireg_tl == 0)
&& (ic->tme_sparc64_ireg_asi
== (TME_SPARC_ASI_MASK_WHICH(asi_mask_data)
+ TME_SPARC64_ASI_FLAG_NO_FAULT)))
? TME_SPARC_RECODE_TLB_FLAG_LD_NF(ic)
: 0)));
/* set the recode chain TLB flags mask to and with the flags from
the chain thunk, before being tested against the flags in a
recode ITLB entry. this TLB flags mask must clear flags that do
not apply, based on the current state: */
ic->tme_sparc_recode_chain_tlb_flags
= (TME_RECODE_TLB_FLAGS_MASK(ic->tme_sparc_recode_ic)
- (
/* the fetch flags for the other privilege level do not
apply, because we're not at that privilege level: */
(TME_SPARC_PRIV(ic)
? TME_SPARC_RECODE_TLB_FLAG_CHAIN_USER(ic)
: TME_SPARC_RECODE_TLB_FLAG_CHAIN_PRIV(ic))
));
#endif /* TME_SPARC_HAVE_RECODE(ic) */
/* create an invalid instruction TLB entry, and use it as the initial
current instruction TLB entry: */
tme_token_init(&token_invalid);
itlb_invalid.tme_sparc_tlb_addr_first = 1;
itlb_invalid.tme_sparc_tlb_addr_last = 0;
itlb_invalid.tme_sparc_tlb_bus_tlb.tme_bus_tlb_token = &token_invalid;
itlb_current = &itlb_invalid;
/* busy the invalid instruction TLB entry: */
assert (ic->_tme_sparc_itlb_current_token == NULL);
tme_token_busy(&token_invalid);
ic->_tme_sparc_itlb_current_token = &token_invalid;
/* the first instruction will not be annulled: */
annulled = FALSE;
/* the last instruction was not a taken branch to .: */
branch_dot = FALSE;
branch_dot_burst = 0;
for (;;) {
/* if we have used up our instruction burst: */
if (__tme_predict_false(ic->_tme_sparc_instruction_burst_remaining == 0)) {
/* if the last instruction was a taken branch to .: */
if (__tme_predict_false(branch_dot)) {
/* clear the taken branch to . flag and restore the
instruction burst that had been remaining: */
branch_dot = FALSE;
ic->_tme_sparc_instruction_burst_remaining = branch_dot_burst;
/* if the next instruction will be annulled: */
if (__tme_predict_false(annulled)) {
/* a taken branch to . that annuls its branch delay slot
must be a "ba,a .", since taken conditional branches
never annul. "ba,a ." makes an infinite loop.
we can just go idle here, but we must make sure that any
trap sees %pc on the branch to ., and not its branch
delay slot (since we don't track the annulled bit in the
processor structure), and we must make sure that %pc_next
is the branch to . delay slot (because otherwise it would
look like we didn't loop even once): */
ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT)
= ic->tme_sparc_ireg(TME_SPARC_IREG_PC);
ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT_NEXT)
= (ic->tme_sparc_ireg(TME_SPARC_IREG_PC)
+ sizeof(tme_uint32_t));
if (TME_SPARC_VERSION(ic) >= 9) {
ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT_NEXT) &= ic->tme_sparc_address_mask;
}
tme_sparc_do_idle(ic);
/* NOTREACHED */
}
/* if the branch delay instruction immediately follows the
branch to .: */
if (ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT)
== (ic->tme_sparc_ireg(TME_SPARC_IREG_PC)
+ sizeof(tme_uint32_t))) {
/* if this branch to . is not a timing loop, this will
return. if it's a timing loop that doesn't sleep, this
will return. otherwise, this won't return: */
tme_sparc_timing_loop_start(ic);
}
/* continue now, to finish the instruction burst that
had been remaining: */
continue;
}
/* if this was a full instruction burst: */
if (!ic->_tme_sparc_instruction_burst_other) {
/* if it's time to update the runlength: */
if (ic->tme_sparc_runlength_update_next == 0) {
/* update the runlength: */
#ifndef _TME_SPARC_RECODE_VERIFY
tme_runlength_update(&ic->tme_sparc_runlength);
#endif /* !_TME_SPARC_RECODE_VERIFY */
/* start another runlength update period: */
ic->tme_sparc_runlength_update_next = ic->tme_sparc_runlength_update_period;
}
/* advance in the runlength update period: */
ic->tme_sparc_runlength_update_next--;
/* we are not in a full instruction burst: */
ic->_tme_sparc_instruction_burst_other = TRUE;
}
/* if the next instruction will be annulled: */
if (__tme_predict_false(annulled)) {
/* NB that we have to handle the next instruction now, in the
immediate next iteration of the execution loop, since we
don't track the annulled bit in the processor structure,
and we want to do good emulation and actually fetch the
instruction (as opposed to just advancing the PCs now).
start an instruction burst of one instruction: */
ic->_tme_sparc_instruction_burst_remaining = 1;
continue;
}
/* if we need to do an external check: */
if (tme_memory_atomic_read_flag(&ic->tme_sparc_external_flag)) {
/* do an external check: */
tme_memory_atomic_write_flag(&ic->tme_sparc_external_flag, FALSE);
tme_memory_barrier(ic, sizeof(*ic), TME_MEMORY_BARRIER_READ_BEFORE_READ);
(*ic->_tme_sparc_external_check)(ic, TME_SPARC_EXTERNAL_CHECK_NULL);
}
/* start a new instruction burst: */
ic->_tme_sparc_instruction_burst_remaining
= ic->_tme_sparc_instruction_burst;
ic->_tme_sparc_instruction_burst_other = FALSE;
/* if the next PC might be in an idle PC range: */
pc = ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT);
if (__tme_predict_false(pc < ic->tme_sparc_idle_pcs[1])) {
/* if we haven't detected the idle PC yet: */
if (__tme_predict_false(TME_SPARC_IDLE_TYPE_PC_STATE(ic->tme_sparc_idle_pcs[0]) != 0)) {
/* nothing to do */
}
/* if the next PC and the delay PC are both in the idle PC
range, and this idle type has an idle PC range: */
else if (__tme_predict_false(pc >= ic->tme_sparc_idle_pcs[0])) {
if (TME_SPARC_IDLE_TYPE_IS(ic, TME_SPARC_IDLE_TYPES_PC_RANGE)
&& ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT_NEXT) >= ic->tme_sparc_idle_pcs[0]
&& ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT_NEXT) < ic->tme_sparc_idle_pcs[1]) {
/* if we haven't marked any idles yet, or if we have
marked one and the next PC is at or behind that PC: */
if (ic->tme_sparc_idle_marks == 0
|| (ic->tme_sparc_idle_marks == 1
&& pc <= ic->tme_sparc_idle_pcs[2])) {
/* mark the idle: */
ic->tme_sparc_idle_marks++;
/* we won't mark another idle until we detect a
backwards control transfer in the idle PC range,
indicating another iteration of the idle loop: */
ic->tme_sparc_idle_pcs[2] = pc;
}
}
}
}
/* if we have marked any idles: */
if (__tme_predict_false(ic->tme_sparc_idle_marks != 0)) {
/* if we have marked one idle: */
if (ic->tme_sparc_idle_marks == 1) {
/* start a new idle instruction burst: */
ic->_tme_sparc_instruction_burst_remaining
= ic->_tme_sparc_instruction_burst_idle;
ic->_tme_sparc_instruction_burst_other = TRUE;
}
/* otherwise, we have marked two consecutive idles without a
trap: */
else {
assert (ic->tme_sparc_idle_marks == 2);
/* idle: */
tme_sparc_do_idle(ic);
}
}
/* if this is a cooperative threading system: */
#if TME_THREADS_COOPERATIVE
/* unbusy the current instruction TLB entry: */
assert (ic->_tme_sparc_itlb_current_token
== itlb_current->tme_sparc_tlb_bus_tlb.tme_bus_tlb_token);
tme_sparc_tlb_unbusy(itlb_current);
ic->_tme_sparc_itlb_current_token = NULL;
/* yield: */
tme_thread_yield();
#endif /* TME_THREADS_COOPERATIVE */
/* if we may update the runlength with this instruction burst,
note its start time: */
if (ic->tme_sparc_runlength_update_next == 0) {
ic->tme_sparc_runlength.tme_runlength_cycles_start = tme_misc_cycles();
}
}
/* we can't know that this instruction is a taken branch to .: */
assert (!branch_dot);
/* we are going to use one instruction in the burst: */
ic->_tme_sparc_instruction_burst_remaining--;
#ifdef _TME_SPARC_STATS
ic->tme_sparc_stats.tme_sparc_stats_insns_total++;
#endif /* _TME_SPARC_STATS */
/* save the previous PC: */
pc_previous = ic->tme_sparc_ireg(TME_SPARC_IREG_PC);
/* if we're replaying recoded instructions: */
if (tme_sparc_recode_verify_replay_last_pc(ic) != 0) {
/* if the previous instruction was the last instruction to
verify, return now: */
if (__tme_predict_false(tme_sparc_recode_verify_replay_last_pc(ic) == pc_previous)) {
assert (ic->_tme_sparc_itlb_current_token != &token_invalid);
return;
}
/* poison pc_previous to prevent all recoding: */
pc_previous = ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT) - sizeof(tme_uint32_t);
}
/* update the PCs and get the PC of the instruction to execute: */
pc = ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT);
ic->tme_sparc_ireg(TME_SPARC_IREG_PC) = pc;
pc_next_next = ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT_NEXT);
ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT) = pc_next_next;
pc_next_next += sizeof(tme_uint32_t);
if (TME_SPARC_VERSION(ic) >= 9) {
pc_next_next &= ic->tme_sparc_address_mask;
assert ((pc | ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT))
< ic->tme_sparc_address_mask);
}
ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT_NEXT) = pc_next_next;
/* NB that we only save instruction TLB entries that allow fast
reading, and we also change tme_sparc_tlb_addr_last to be the
last PC covered by the entry (it's normally the last address
covered by the entry). this allows us to do minimal checking
of the current instruction TLB entry at itlb_current: */
/* if the current instruction TLB entry covers this address: */
if (__tme_predict_true(((tme_sparc_ireg_t) itlb_current->tme_sparc_tlb_addr_first) <= pc
&& pc <= ((tme_sparc_ireg_t) itlb_current->tme_sparc_tlb_addr_last))) {
/* the current instruction TLB entry must cover this
address and allow reading: */
assert (TME_SPARC_TLB_ASI_MASK_OK(itlb_current, asi_mask_insn)
&& (itlb_current->tme_sparc_tlb_context > ic->tme_sparc_memory_context_max
|| itlb_current->tme_sparc_tlb_context == ic->tme_sparc_memory_context_default)
&& itlb_current->tme_sparc_tlb_addr_first <= pc
&& pc <= itlb_current->tme_sparc_tlb_addr_last);
/* fetch the instruction: */
insn = tme_memory_bus_read32((const tme_shared tme_uint32_t *) (itlb_current->tme_sparc_tlb_emulator_off_read + pc),
itlb_current->tme_sparc_tlb_bus_rwlock,
sizeof(tme_uint32_t),
sizeof(tme_sparc_ireg_t));
insn = tme_betoh_u32(insn);
}
/* otherwise, our current TLB entry doesn't cover this address: */
else {
/* unbusy the current instruction TLB entry: */
assert (ic->_tme_sparc_itlb_current_token
== itlb_current->tme_sparc_tlb_bus_tlb.tme_bus_tlb_token);
tme_sparc_tlb_unbusy(itlb_current);
/* rehash the current instruction TLB entry: */
tlb_hash = TME_SPARC_TLB_HASH(ic, ic->tme_sparc_memory_context_default, pc);
itlb_current = &ic->tme_sparc_tlbs[TME_SPARC_ITLB_ENTRY(ic, tlb_hash)];
/* busy the current instruction TLB entry: */
tme_sparc_tlb_busy(itlb_current);
ic->_tme_sparc_itlb_current_token = itlb_current->tme_sparc_tlb_bus_tlb.tme_bus_tlb_token;
/* if the new current instruction TLB entry is valid and covers
this address: */
if (tme_bus_tlb_is_valid(&itlb_current->tme_sparc_tlb_bus_tlb)
&& __tme_predict_true(TME_SPARC_TLB_ASI_MASK_OK(itlb_current, asi_mask_insn)
&& (itlb_current->tme_sparc_tlb_context > ic->tme_sparc_memory_context_max
|| itlb_current->tme_sparc_tlb_context == ic->tme_sparc_memory_context_default)
&& pc >= (tme_sparc_ireg_t) itlb_current->tme_sparc_tlb_addr_first
&& pc <= (tme_sparc_ireg_t) itlb_current->tme_sparc_tlb_addr_last)) {
/* fetch the instruction: */
insn = tme_memory_bus_read32((const tme_shared tme_uint32_t *) (itlb_current->tme_sparc_tlb_emulator_off_read + pc),
itlb_current->tme_sparc_tlb_bus_rwlock,
sizeof(tme_uint32_t),
sizeof(tme_sparc_ireg_t));
insn = tme_betoh_u32(insn);
}
/* otherwise, the new current instruction TLB entry is not valid
or does not cover this address: */
else {
/* the slow fetch will manage unbusying and busying the
current instruction TLB entry, so make sure that doesn't
happen at unlock and relock time: */
ic->_tme_sparc_itlb_current_token = NULL;
/* fetch the instruction: */
emulator_off =
#if TME_SPARC_VERSION(ic) < 9
tme_sparc32_ls
#else /* TME_SPARC_VERSION(ic) >= 9 */
tme_sparc64_ls
#endif /* TME_SPARC_VERSION(ic) >= 9 */
(ic,
pc,
(tme_sparc_ireg_t *) NULL,
(TME_SPARC_LSINFO_SIZE(sizeof(tme_uint32_t))
+ TME_SPARC_LSINFO_ASI(TME_SPARC_ASI_MASK_WHICH(asi_mask_insn))
+ TME_SPARC_LSINFO_A
+ TME_SPARC_LSINFO_OP_FETCH
+ (annulled
? TME_SPARC_LSINFO_NO_FAULT
: 0)));
assert (emulator_off != TME_EMULATOR_OFF_UNDEF);
/* unbusy and busy the current instruction TLB entry at unlock
and relock time again: */
ic->_tme_sparc_itlb_current_token = itlb_current->tme_sparc_tlb_bus_tlb.tme_bus_tlb_token;
/* if this current instruction TLB entry covers the entire
instruction and allows fast reading: */
if (__tme_predict_true(emulator_off == itlb_current->tme_sparc_tlb_emulator_off_read)) {
/* the current instruction TLB entry must now cover this
address and allow reading: */
/* NB that tme_sparc_tlb_addr_last has not been changed yet: */
assert (TME_SPARC_TLB_ASI_MASK_OK(itlb_current, asi_mask_insn)
&& (itlb_current->tme_sparc_tlb_context > ic->tme_sparc_memory_context_max
|| itlb_current->tme_sparc_tlb_context == ic->tme_sparc_memory_context_default)
&& itlb_current->tme_sparc_tlb_addr_first <= pc
&& (pc + sizeof(tme_uint32_t) - 1) <= itlb_current->tme_sparc_tlb_addr_last);
/* fetch the instruction: */
insn = tme_memory_bus_read32((const tme_shared tme_uint32_t *) (itlb_current->tme_sparc_tlb_emulator_off_read + pc),
itlb_current->tme_sparc_tlb_bus_rwlock,
sizeof(tme_uint32_t),
sizeof(tme_sparc_ireg_t));
insn = tme_betoh_u32(insn);
/* modify tme_sparc_tlb_addr_last of this first to represent the last valid
PC covered by the entry: */
itlb_current->tme_sparc_tlb_addr_last
&= (((tme_bus_addr_t) 0) - sizeof(tme_uint32_t));
}
/* otherwise, this instruction TLB entry does not cover the
entire instruction and/or it does not allow fast reading.
the instruction has already been loaded into the memory
buffer: */
else {
/* unbusy the current instruction TLB entry and poison it,
so we won't try to do any fast fetches with it: */
assert (ic->_tme_sparc_itlb_current_token
== itlb_current->tme_sparc_tlb_bus_tlb.tme_bus_tlb_token);
tme_sparc_tlb_unbusy(itlb_current);
itlb_current->tme_sparc_tlb_addr_first = 1;
itlb_current->tme_sparc_tlb_addr_last = 0;
ic->_tme_sparc_itlb_current_token = NULL;
/* fetch the instruction from the memory buffer: */
assert ((emulator_off + pc) == ic->tme_sparc_memory_buffer.tme_sparc_memory_buffer8s);
insn = ic->tme_sparc_memory_buffer.tme_sparc_memory_buffer32s[0];
insn = tme_betoh_u32(insn);
#ifdef _TME_SPARC_STATS
ic->tme_sparc_stats.tme_sparc_stats_insns_slow++;
#endif /* _TME_SPARC_STATS */
/* busy the invalid instruction TLB entry: */
itlb_current = &itlb_invalid;
assert (ic->_tme_sparc_itlb_current_token == NULL);
tme_sparc_tlb_busy(itlb_current);
ic->_tme_sparc_itlb_current_token = itlb_current->tme_sparc_tlb_bus_tlb.tme_bus_tlb_token;
}
}
/* if this instruction has been annulled: */
if (__tme_predict_false(annulled)) {
/* make this instruction a nop: */
insn = 0x01000000;
/* when an annulled instruction also happens to be a branch
target, we can't run or make an instructions thunk
associated with its PC, since instructions thunks don't
take the annulled bit as any kind of parameter. we poison
pc_previous to prevent this from happening. annulled
instructions that are also branch targets should be pretty
rare anyways: */
pc_previous = pc - sizeof(tme_uint32_t);
}
/* the next instruction will not be annulled: */
annulled = FALSE;
}
/* start this instruction: */
ic->_tme_sparc_insn = insn;
/* set %g0 to zero: */
ic->tme_sparc_ireg(TME_SPARC_G0_OFFSET(ic) + TME_SPARC_IREG_G0) = 0;
#if TME_SPARC_HAVE_RECODE(ic)
/* if this is the idle PC, and the idle type marks the idle when
control reaches the idle PC: */
if (__tme_predict_false(pc == ic->tme_sparc_idle_pcs[0])) {
if (TME_SPARC_IDLE_TYPE_IS(ic,
(TME_SPARC_IDLE_TYPES_TARGET_CALL
| TME_SPARC_IDLE_TYPES_TARGET_BRANCH
))) {
/* mark the idle: */
TME_SPARC_IDLE_MARK(ic);
/* poison the previous PC to prevent all recoding, to
guarantee that we always see the idle PC (if we allowed the
idle PC to be recoded, it might get chained to): */
pc_previous = pc - sizeof(tme_uint32_t);
}
}
/* if this PC does not follow the previous PC, but the next PC
follows this PC, this PC is a simple control transfer target: */
if (__tme_predict_false(((tme_sparc_ireg_t) (pc - sizeof(tme_uint32_t)))
!= pc_previous)) {
if (__tme_predict_true(((tme_sparc_ireg_t) (pc + sizeof(tme_uint32_t)))
== ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT))) {
tme_recode_thunk_off_t insns_thunk;
/* if the current instruction TLB entry is not the invalid TLB
entry, and there is an instructions thunk for this PC: */
if (__tme_predict_true(itlb_current != &itlb_invalid
&& (insns_thunk
= tme_sparc_recode(ic,
itlb_current,
((const tme_shared tme_uint32_t *)
(itlb_current->tme_sparc_tlb_emulator_off_read
+ pc)))) != 0)) {
/* begin verifying this instructions thunk: */
tme_sparc_recode_verify_begin(ic);
/* like this execution loop, the recode instructions thunks
expect PC_next to be the next instruction to execute.
we've already updated the PCs above, so we have to undo
the update of PC_next. NB that we don't have to undo PC
or PC_next_next, since the instructions thunks don't read
them: */
pc = ic->tme_sparc_ireg(TME_SPARC_IREG_PC);
ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT) = pc;
/* run the recode instructions thunk: */
TME_SPARC_STAT_N(ic, tme_sparc_stats_insns_total, -1);
tme_recode_insns_thunk_run(&ic->tme_sparc_ic,
ic->tme_sparc_recode_insns_group.tme_recode_insns_group_chain_thunk,
insns_thunk);
/* set PC_next_next from PC_next, since the recode
instructions thunks usually don't. (this won't destroy
any specially set PC_next_next, because any instruction
that sets one is supposed to redispatch.) */
pc = ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT);
ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT_NEXT) = pc + sizeof(tme_uint32_t);
/* end verifying this instructions thunk: */
tme_sparc_recode_verify_end(ic, TME_SPARC_TRAP_none);
/* we force a PC to make it look like a control transfer has
happened (one probably has), to encourage creation of
another instructions thunk. this is something like the
opposite of poisoning: */
ic->tme_sparc_ireg(TME_SPARC_IREG_PC) = pc;
/* instead of figuring out what the currently busy
instruction TLB entry is, we simply unbusy the currently
busy instruction TLB token and make the current
instruction TLB entry invalid: */
assert (ic->_tme_sparc_itlb_current_token != NULL);
tme_token_unbusy(ic->_tme_sparc_itlb_current_token);
itlb_current = &itlb_invalid;
tme_token_busy(&token_invalid);
ic->_tme_sparc_itlb_current_token = &token_invalid;
/* restart the loop: */
continue;
}
}
}
#endif /* TME_SPARC_HAVE_RECODE(ic) */
/* if this is a format three instruction (op is two or three): */
if (__tme_predict_true(insn >= 0x80000000)) {
/* if the i bit is zero: */
if (__tme_predict_true((insn & TME_BIT(13)) == 0)) {
/* decode rs2: */
reg_rs2 = TME_FIELD_MASK_EXTRACTU(insn, TME_SPARC_FORMAT3_MASK_RS2);
TME_SPARC_REG_INDEX(ic, reg_rs2);
}
/* otherwise, the i bit is one: */
else {
/* decode simm13: */
ic->tme_sparc_ireg(TME_SPARC_IREG_TMP(0)) = TME_FIELD_MASK_EXTRACTS(insn, (tme_sparc_ireg_t) 0x1fff);
reg_rs2 = TME_SPARC_IREG_TMP(0);
}
/* decode rs1: */
reg_rs1 = TME_FIELD_MASK_EXTRACTU(insn, TME_SPARC_FORMAT3_MASK_RS1);
TME_SPARC_REG_INDEX(ic, reg_rs1);
/* decode rd: */
reg_rd = TME_FIELD_MASK_EXTRACTU(insn, TME_SPARC_FORMAT3_MASK_RD);
TME_SPARC_REG_INDEX(ic, reg_rd);
/* form the opcode index: */
opcode = TME_FIELD_MASK_EXTRACTU(insn, (0x3f << 19));
opcode += ((insn >> (30 - 6)) & 0x40);
/* run the instruction: */
(*_TME_SPARC_EXECUTE_OPMAP[opcode])
(ic,
&ic->tme_sparc_ireg(reg_rs1),
&ic->tme_sparc_ireg(reg_rs2),
&ic->tme_sparc_ireg(reg_rd));
}
/* otherwise, if this is a format two instruction: */
else if (__tme_predict_true(insn < 0x40000000)) {
/* dispatch on op2: */
switch (TME_FIELD_MASK_EXTRACTU(insn, (0x7 << 22))) {
#if TME_SPARC_VERSION(ic) >= 9
case 1: /* BPcc */
/* if cc0 is set, this is an illegal instruction: */
if (__tme_predict_false(insn & TME_BIT(20))) {
TME_SPARC_INSN_TRAP(TME_SPARC_TRAP(ic,illegal_instruction));
}
/* get %icc or %xcc: */
cc = ic->tme_sparc64_ireg_ccr;
if (insn & TME_BIT(21)) {
cc /= (TME_SPARC64_CCR_XCC / TME_SPARC64_CCR_ICC);
}
cc = TME_FIELD_MASK_EXTRACTU(cc, TME_SPARC64_CCR_ICC);
/* get the conditions mask: */
conds_mask = _tme_sparc_conds_icc[cc];
/* add the not-conditions to the conditions mask: */
conds_mask += ((conds_mask ^ 0xff) << 8);
/* clear cc1, cc0, and p: */
insn &= ~(TME_BIT(21) + TME_BIT(20) + TME_BIT(19));
/* flip the most significant bit of the disp19: */
insn ^= TME_BIT(18);
/* sign-extend the disp19 to a disp22: */
/* NB: this potentially destroys op2: */
insn += TME_BIT(22) - TME_BIT(18);
break;
case 3: /* BPr */
/* if bit 28 is set, or if the least significant two bits of
cond are clear, this is an illegal instruction: */
if (__tme_predict_false((insn & TME_BIT(28))
|| (insn & (0x3 << 25)) == TME_SPARC_COND_N)) {
TME_SPARC_INSN_TRAP(TME_SPARC_TRAP(ic,illegal_instruction));
}
/* decode rs1: */
reg_rs1 = TME_FIELD_MASK_EXTRACTU(insn, TME_SPARC_FORMAT3_MASK_RS1);
TME_SPARC_REG_INDEX(ic, reg_rs1);
/* make a conditions mask, with the E and LE conditions if the
register is zero, and with the L and LE conditions if the
register is less than zero: */
value_rs1 = ic->tme_sparc_ireg(reg_rs1);
conds_mask
= (((value_rs1 == 0)
* (TME_BIT(TME_SPARC_COND_E)
+ TME_BIT(TME_SPARC_COND_LE)))
| ((((tme_int64_t) value_rs1) < 0)
* (TME_BIT(TME_SPARC_COND_L)
+ TME_BIT(TME_SPARC_COND_LE))));
/* add the not-conditions to the conditions mask: */
conds_mask += ((conds_mask ^ 0xf) << 4);
/* clear rs1 and p, move d16hi down, and clear d16hi: */
insn
= ((insn & ~((2 << 21) - (1 << 14)))
+ ((insn & (3 << 20)) >> (20 - 14)));
/* flip the most significant bit of the disp16: */
insn ^= TME_BIT(15);
/* sign-extend the disp16 to a disp22: */
/* NB: this potentially destroys op2: */
insn += TME_BIT(22) - TME_BIT(15);
break;
case 5: /* FBPfcc: */
TME_SPARC_INSN_FPU;
/* get the right %fcc: */
cc = TME_FIELD_MASK_EXTRACTU(insn, (0x3 << 20));
if (cc == 0) {
cc = ic->tme_sparc_fpu_fsr / _TME_FIELD_MASK_FACTOR(TME_SPARC_FSR_FCC);
}
else {
cc = ic->tme_sparc_fpu_xfsr >> (2 * (cc - 1));
}
cc &= (TME_SPARC_FSR_FCC / _TME_FIELD_MASK_FACTOR(TME_SPARC_FSR_FCC));
/* get the conditions mask: */
conds_mask = _tme_sparc_conds_fcc[cc];
/* add the not-conditions to the conditions mask: */
conds_mask += ((conds_mask ^ 0xff) << 8);
/* clear cc1, cc0, and p: */
insn &= ~(TME_BIT(21) + TME_BIT(20) + TME_BIT(19));
/* flip the most significant bit of the disp19: */
insn ^= TME_BIT(18);
/* sign-extend the disp19 to a disp22: */
/* NB: this potentially destroys op2: */
insn += TME_BIT(22) - TME_BIT(18);
break;
#endif /* TME_SPARC_VERSION(ic) >= 9 */
default:
case 0: /* UNIMP: */
TME_SPARC_INSN_TRAP(TME_SPARC_TRAP(ic,illegal_instruction));
continue;
case 2: /* Bicc: */
conds_mask_icc = _tme_sparc_conds_icc[
#if TME_SPARC_VERSION(ic) < 9
TME_FIELD_MASK_EXTRACTU(ic->tme_sparc32_ireg_psr, TME_SPARC32_PSR_ICC)
#else /* TME_SPARC_VERSION(ic) >= 9 */
TME_FIELD_MASK_EXTRACTU(ic->tme_sparc64_ireg_ccr, TME_SPARC64_CCR_ICC)
#endif /* TME_SPARC_VERSION(ic) >= 9 */
];
/* add the not-conditions to the conditions mask: */
conds_mask = conds_mask_icc ^ 0xff;
conds_mask = (conds_mask << 8) | conds_mask_icc;
break;
case 4: /* SETHI: */
/* decode rd: */
reg_rd = TME_FIELD_MASK_EXTRACTU(insn, TME_SPARC_FORMAT3_MASK_RD);
TME_SPARC_REG_INDEX(ic, reg_rd);
ic->tme_sparc_ireg(reg_rd) = (insn << 10);
continue;
case 6: /* FBfcc: */
TME_SPARC_INSN_FPU;
conds_mask_fcc = _tme_sparc_conds_fcc[TME_FIELD_MASK_EXTRACTU(ic->tme_sparc_fpu_fsr, TME_SPARC_FSR_FCC)];
/* add the not-conditions to the conditions mask: */
conds_mask = conds_mask_fcc ^ 0xff;
conds_mask = (conds_mask << 8) | conds_mask_fcc;
break;
}
/* get the condition field: */
cond = TME_FIELD_MASK_EXTRACTU(insn, (0xf << 25));
/* if this conditional branch is taken: */
if (conds_mask & TME_BIT(cond)) {
/* get the raw displacement: */
disp = TME_FIELD_MASK_EXTRACTS(insn, 0x003fffff);
/* if there is no recode support, and the raw displacement is zero: */
if (__tme_predict_false(!TME_SPARC_HAVE_RECODE(ic)
&& disp == 0)) {
/* a taken branch to . is probably a timing loop. instead
of handling that here, which would involve function calls
that would probably hurt register allocation, instead we
just set a flag and pretend that this is the last
instruction in the burst. when we start a new burst
above, we will find the flag set and do the handling
then: */
branch_dot = TRUE;
branch_dot_burst = ic->_tme_sparc_instruction_burst_remaining;
ic->_tme_sparc_instruction_burst_remaining = 0;
/* the raw displacement is zero: */
/* NB: this is not necessary for correctness, but is an
attempt to encourage better register allocation: */
disp = 0;
}
/* do the delayed control transfer: */
pc_next_next
= (ic->tme_sparc_ireg(TME_SPARC_IREG_PC)
+ (disp << 2));
if (TME_SPARC_VERSION(ic) >= 9) {
pc_next_next &= ic->tme_sparc_address_mask;
}
ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT_NEXT) = pc_next_next;
/* if there is no recode support, and the delayed control
transfer target is the idle PC, and this idle type marks
the idle on a branch to the idle PC: */
if (__tme_predict_false(!TME_SPARC_HAVE_RECODE(ic)
&& pc_next_next == ic->tme_sparc_idle_pcs[0])) {
if (TME_SPARC_IDLE_TYPE_IS(ic, TME_SPARC_IDLE_TYPES_TARGET_BRANCH)) {
/* mark the idle: */
TME_SPARC_IDLE_MARK(ic);
}
}
/* if this was a conditional branch, clear the annul bit in
the instruction image: */
if (cond & 7) {
insn &= ~TME_BIT(29);
}
}
/* if the annul bit it set: */
if (insn & TME_BIT(29)) {
/* the next instruction will be annulled. to get the
execution loop to pay attention to the annulled bit,
make the current instruction TLB entry invalid: */
annulled = TRUE;
assert (ic->_tme_sparc_itlb_current_token
== itlb_current->tme_sparc_tlb_bus_tlb.tme_bus_tlb_token);
tme_sparc_tlb_unbusy(itlb_current);
itlb_current = &itlb_invalid;
tme_token_busy(&token_invalid);
ic->_tme_sparc_itlb_current_token = &token_invalid;
}
}
/* otherwise, this is a format one instruction: */
else {
/* get the current PC: */
pc = ic->tme_sparc_ireg(TME_SPARC_IREG_PC);
/* write the PC of the CALL into r[15]: */
ic->tme_sparc_ireg(((ic)->tme_sparc_reg8_offset[15 / 8] * 8) + 15) = pc;
/* get the delayed control transfer target: */
pc_next_next = pc + (tme_int32_t) (insn << 2);
if (TME_SPARC_VERSION(ic) >= 9) {
pc_next_next &= ic->tme_sparc_address_mask;
}
/* if there is no recode support, and the delayed control
transfer target is the idle PC, and this idle type marks
the idle on a call to the idle PC: */
if (__tme_predict_false(!TME_SPARC_HAVE_RECODE(ic)
&& pc_next_next == ic->tme_sparc_idle_pcs[0])) {
if (TME_SPARC_IDLE_TYPE_IS(ic, TME_SPARC_IDLE_TYPES_TARGET_CALL)) {
/* mark the idle: */
TME_SPARC_IDLE_MARK(ic);
}
}
/* log the call: */
reg_o0 = 8;
TME_SPARC_REG_INDEX(ic, reg_o0);
tme_sparc_log(ic, 250, TME_OK,
(TME_SPARC_LOG_HANDLE(ic),
_("call " TME_PRIxSPARCREG " %%o0 " TME_PRIxSPARCREG " %%o1 " TME_PRIxSPARCREG " %%o2 " TME_PRIxSPARCREG " %%o3 " TME_PRIxSPARCREG " %%o4 " TME_PRIxSPARCREG " %%o5 " TME_PRIxSPARCREG),
pc_next_next,
ic->tme_sparc_ireg(reg_o0 + 0),
ic->tme_sparc_ireg(reg_o0 + 1),
ic->tme_sparc_ireg(reg_o0 + 2),
ic->tme_sparc_ireg(reg_o0 + 3),
ic->tme_sparc_ireg(reg_o0 + 4),
ic->tme_sparc_ireg(reg_o0 + 5)));
/* do the delayed control transfer: */
ic->tme_sparc_ireg(TME_SPARC_IREG_PC_NEXT_NEXT) = pc_next_next;
}
}
/* NOTREACHED */
}