| /* $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 */ |
| } |