mirror of
https://github.com/phabrics/Run-Sun3-SunOS-4.1.1.git
synced 2026-04-29 19:12:58 -04:00
390 lines
14 KiB
C
390 lines
14 KiB
C
/* $Id: recode-insns.c,v 1.4 2010/02/07 17:32:01 fredette Exp $ */
|
|
|
|
/* libtme/recode-insns.c - generic recode instruction support: */
|
|
|
|
/*
|
|
* Copyright (c) 2008 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.
|
|
*/
|
|
|
|
#include <tme/common.h>
|
|
_TME_RCSID("$Id: recode-insns.c,v 1.4 2010/02/07 17:32:01 fredette Exp $");
|
|
|
|
#if TME_HAVE_RECODE
|
|
|
|
/* includes: */
|
|
#include "recode-impl.h"
|
|
|
|
/* macros: */
|
|
|
|
/* the undefined flags offset: */
|
|
#define TME_RECODE_FLAGS_OFFSET_UNDEF (0 - (tme_uint32_t) 1)
|
|
|
|
/* this returns the thunk offset for a new instructions thunk. it
|
|
returns less than zero when thunks memory is exhausted and all
|
|
instructions thunks are flushed: */
|
|
tme_recode_thunk_off_t
|
|
tme_recode_insns_thunk(struct tme_recode_ic *ic,
|
|
const struct tme_recode_insns_group *insns_group)
|
|
{
|
|
signed long reg_guest;
|
|
signed long ruses_record_tmp;
|
|
signed long ruses_record_right;
|
|
tme_uint32_t flags_offset;
|
|
tme_recode_uguest_t flags_needed;
|
|
tme_uint32_t flags_offset_else;
|
|
tme_recode_uguest_t flags_needed_else;
|
|
struct tme_recode_insn *insns;
|
|
struct tme_recode_insn *insn;
|
|
tme_uint32_t opcode_mask;
|
|
signed long operand_i;
|
|
tme_uint32_t ruses;
|
|
const struct tme_recode_flags_thunk *flags_thunk;
|
|
const struct tme_recode_conds_thunk *conds_thunk;
|
|
|
|
/* initialize the mapping from host register to read-uses count for
|
|
a cached guest register, to all host registers free: */
|
|
#if TME_RECODE_REG_RUSES_FREE != 0
|
|
#error "TME_RECODE_REG_RUSES_FREE changed"
|
|
#endif
|
|
memset(ic->tme_recode_ic_reg_host_to_ruses,
|
|
TME_RECODE_REG_RUSES_FREE,
|
|
sizeof(ic->tme_recode_ic_reg_host_to_ruses));
|
|
|
|
/* there are no reserved registers: */
|
|
ic->tme_recode_ic_reg_host_reserve_next = 0;
|
|
|
|
/* initialize the read-uses counts for all guest registers. this
|
|
also marks the guest register tags as invalid: */
|
|
reg_guest = TME_RECODE_REG_GUEST(ic->tme_recode_ic_reg_count - 1);
|
|
do {
|
|
ic->tme_recode_ic_reginfo[reg_guest].tme_recode_reginfo_tags_ruses = (TME_RECODE_REG_RUSES_FREE + 1);
|
|
} while (--reg_guest >= TME_RECODE_REG_GUEST(0));
|
|
|
|
/* the largest guest register number must fit in
|
|
[TME_RECODE_REG_RUSES_RECORD_REG_GUEST(0)..TME_RECODE_REG_RUSES_RECORD_UNDEF): */
|
|
assert (ic->tme_recode_ic_reg_count
|
|
< (TME_RECODE_REG_RUSES_RECORD_UNDEF
|
|
- TME_RECODE_REG_RUSES_RECORD_REG_GUEST(0)));
|
|
|
|
/* reset the read-uses records: */
|
|
ruses_record_tmp = 0;
|
|
ruses_record_right = ic->tme_recode_ic_reg_guest_ruses_record_count;
|
|
assert (ic->tme_recode_ic_reg_guest_ruses_records[ruses_record_right]
|
|
== TME_RECODE_REG_RUSES_RECORD_UNDEF);
|
|
|
|
/* we haven't found any flags register yet: */
|
|
flags_offset = TME_RECODE_FLAGS_OFFSET_UNDEF;
|
|
flags_needed = 0;
|
|
flags_offset_else = TME_RECODE_FLAGS_OFFSET_UNDEF;
|
|
flags_needed_else = 0;
|
|
|
|
/* loop over the instructions, from last to first: */
|
|
insns = insns_group->tme_recode_insns_group_insns;
|
|
insn = insns_group->tme_recode_insns_group_insns_end;
|
|
do {
|
|
insn--;
|
|
|
|
/* get the bitmask for this instruction's opcode: */
|
|
opcode_mask = (1 << insn->tme_recode_insn_opcode);
|
|
|
|
/* if this is an else instruction, or an endif instruction, or a
|
|
guest instruction with unknown destination registers: */
|
|
if ((opcode_mask
|
|
& ((1 << TME_RECODE_OPCODE_ELSE)
|
|
| (1 << TME_RECODE_OPCODE_ENDIF)))
|
|
|| ((opcode_mask & (1 << TME_RECODE_OPCODE_GUEST))
|
|
&& insn->tme_recode_insn_operand_dst == TME_RECODE_OPERAND_NULL)) {
|
|
|
|
/* if there are no guest register writes between this else,
|
|
endif, or guest instruction and the next else, endif, or
|
|
guest instruction: */
|
|
if (ic->tme_recode_ic_reg_guest_ruses_records[ruses_record_right]
|
|
>= TME_RECODE_REG_RUSES_RECORD_REG_GUEST(0)) {
|
|
|
|
/* if we can, make a delimiter between any initial read-uses
|
|
records that we're about to make, and the initial read-uses
|
|
records that we previously made: */
|
|
if (ruses_record_right > 0) {
|
|
ruses_record_right--;
|
|
ic->tme_recode_ic_reg_guest_ruses_records[ruses_record_right]
|
|
= TME_RECODE_REG_RUSES_RECORD_UNDEF;
|
|
}
|
|
}
|
|
|
|
/* loop over any temporary read-uses records: */
|
|
for (; ruses_record_tmp > 0; ) {
|
|
|
|
/* if this temporary read-uses record was overwritten by a
|
|
write read-uses record: */
|
|
ruses_record_tmp--;
|
|
if (ruses_record_tmp >= ruses_record_right) {
|
|
|
|
/* skip to the last temporary read-uses record that hasn't
|
|
been overwritten yet: */
|
|
ruses_record_tmp = ruses_record_right;
|
|
continue;
|
|
}
|
|
|
|
/* get the guest register: */
|
|
reg_guest = ic->tme_recode_ic_reg_guest_ruses_records[ruses_record_tmp];
|
|
|
|
/* get this guest register's read-uses count: */
|
|
ruses = ic->tme_recode_ic_reginfo[reg_guest].tme_recode_reginfo_tags_ruses;
|
|
|
|
/* if this guest register has a read use before any first
|
|
write after this else, endif, or guest instruction: */
|
|
if (ruses > (TME_RECODE_REG_RUSES_FREE + 1)) {
|
|
|
|
/* reset this guest register's read-uses count: */
|
|
ic->tme_recode_ic_reginfo[reg_guest].tme_recode_reginfo_tags_ruses = (TME_RECODE_REG_RUSES_FREE + 1);
|
|
|
|
/* if we can't make another initial read-uses record, stop now: */
|
|
if (ruses_record_right < 2) {
|
|
break;
|
|
}
|
|
|
|
/* make the initial read-uses record: */
|
|
ruses_record_right -= 2;
|
|
ic->tme_recode_ic_reg_guest_ruses_records[ruses_record_right + 0]
|
|
= TME_RECODE_REG_RUSES_RECORD_REG_GUEST(reg_guest);
|
|
ic->tme_recode_ic_reg_guest_ruses_records[ruses_record_right + 1] = ruses;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if this is an integer, or guest, or read/write instruction: */
|
|
if (opcode_mask
|
|
& (((1 << TME_RECODE_OPCODES_INTEGER) - 1)
|
|
| (1 << TME_RECODE_OPCODE_GUEST)
|
|
| (1 << TME_RECODE_OPCODE_RW))) {
|
|
|
|
/* if the destination operand is a guest register: */
|
|
reg_guest = insn->tme_recode_insn_operand_dst;
|
|
if (reg_guest >= TME_RECODE_REG_GUEST(0)) {
|
|
|
|
/* if this guest register is not fixed: */
|
|
if ((ic->tme_recode_ic_reginfo[reg_guest].tme_recode_reginfo_all
|
|
& TME_RECODE_REGINFO_TYPE_FIXED) == 0) {
|
|
|
|
/* get and reset this guest register's read-uses count: */
|
|
ruses = ic->tme_recode_ic_reginfo[reg_guest].tme_recode_reginfo_tags_ruses;
|
|
ic->tme_recode_ic_reginfo[reg_guest].tme_recode_reginfo_tags_ruses = (TME_RECODE_REG_RUSES_FREE + 1);
|
|
|
|
/* make a write read-uses record: */
|
|
if (ruses_record_right > 0) {
|
|
ruses_record_right--;
|
|
}
|
|
ic->tme_recode_ic_reg_guest_ruses_records[ruses_record_right] = ruses;
|
|
}
|
|
}
|
|
|
|
/* all integer and guest instructions can have a guest register
|
|
as the first source operand. all of those instructions can
|
|
also have a guest register as the second source operand,
|
|
except for the zero- and sign-extension instructions, which
|
|
always have a TME_RECODE_SIZE for their second source
|
|
operand: */
|
|
operand_i
|
|
= ((opcode_mask
|
|
& ((1 << TME_RECODE_OPCODE_EXTZ)
|
|
| (1 << TME_RECODE_OPCODE_EXTS)))
|
|
== 0);
|
|
|
|
/* loop over the source operands that can be guest registers: */
|
|
do {
|
|
|
|
/* if this source operand is a guest register: */
|
|
reg_guest = insn->tme_recode_insn_operand_src[operand_i];
|
|
if (reg_guest >= TME_RECODE_REG_GUEST(0)) {
|
|
|
|
/* get this guest register's current read-uses count: */
|
|
ruses = ic->tme_recode_ic_reginfo[reg_guest].tme_recode_reginfo_tags_ruses;
|
|
|
|
/* if this guest register's current read-uses count is the
|
|
minimum: */
|
|
if (ruses == (TME_RECODE_REG_RUSES_FREE + 1)) {
|
|
|
|
/* if we can, make a temporary read-uses record for this
|
|
guest register: */
|
|
if (ruses_record_tmp < ruses_record_right) {
|
|
ic->tme_recode_ic_reg_guest_ruses_records[ruses_record_tmp] = reg_guest;
|
|
ruses_record_tmp++;
|
|
}
|
|
}
|
|
|
|
/* increment the guest register's current read-uses count,
|
|
unless it would become TME_RECODE_REG_RUSES_RESERVED: */
|
|
if (__tme_predict_true(ruses < (TME_RECODE_REG_RUSES_RESERVED - 1))) {
|
|
ruses++;
|
|
}
|
|
ic->tme_recode_ic_reginfo[reg_guest].tme_recode_reginfo_tags_ruses = ruses;
|
|
}
|
|
} while (--operand_i >= 0);
|
|
}
|
|
|
|
/* if this is an integer instruction: */
|
|
if (opcode_mask & ((1 << TME_RECODE_OPCODES_INTEGER) - 1)) {
|
|
|
|
/* if the second source operand is a zero, and the operation is
|
|
commutative: */
|
|
if (insn->tme_recode_insn_operand_src[1] == TME_RECODE_OPERAND_ZERO) {
|
|
if (opcode_mask
|
|
& ((1 << TME_RECODE_OPCODE_AND)
|
|
| (1 << TME_RECODE_OPCODE_OR)
|
|
| (1 << TME_RECODE_OPCODE_XOR)
|
|
| (1 << TME_RECODE_OPCODE_ADD)
|
|
| (1 << TME_RECODE_OPCODE_ADDC))) {
|
|
|
|
/* we have the convention of always putting a zero source
|
|
operand first whenever possible, so swap the first and
|
|
second source operands: */
|
|
insn->tme_recode_insn_operand_src[1] = insn->tme_recode_insn_operand_src[0];
|
|
insn->tme_recode_insn_operand_src[0] = TME_RECODE_OPERAND_ZERO;
|
|
}
|
|
}
|
|
|
|
/* if this integer instruction can change flags: */
|
|
flags_thunk = insn->tme_recode_insn_flags_thunk;
|
|
if (flags_thunk != NULL) {
|
|
|
|
/* if this integer instruction changes flags in a different
|
|
flags register: */
|
|
if (__tme_predict_false(flags_offset != flags_thunk->tme_recode_flags_thunk_flags_offset)) {
|
|
|
|
/* switch to this different flags register, and need all of
|
|
its flags: */
|
|
flags_offset = flags_thunk->tme_recode_flags_thunk_flags_offset;
|
|
flags_needed = 0 - (tme_recode_uguest_t) 1;
|
|
}
|
|
|
|
/* if this integer instruction doesn't define any of the flags
|
|
in this flags register needed by later instructions: */
|
|
if ((flags_needed & flags_thunk->tme_recode_flags_thunk_flags_defined) == 0) {
|
|
|
|
/* this integer instruction doesn't need to define flags any
|
|
more: */
|
|
insn->tme_recode_insn_flags_thunk = NULL;
|
|
}
|
|
|
|
/* any earlier instruction that also defines flags in this
|
|
flags register, doesn't need to define any of the flags
|
|
that this instruction changes: */
|
|
flags_needed &= ~flags_thunk->tme_recode_flags_thunk_flags_changed;
|
|
}
|
|
}
|
|
|
|
/* if this is a defc instruction: */
|
|
else if (opcode_mask == (1 << TME_RECODE_OPCODE_DEFC)) {
|
|
|
|
/* get the conditions thunk for this instruction: */
|
|
conds_thunk = insn->tme_recode_insn_conds_thunk;
|
|
|
|
/* if this defc instruction tests flags in a different flags register: */
|
|
if (__tme_predict_false(flags_offset != conds_thunk->tme_recode_conds_thunk_flags_offset)) {
|
|
|
|
/* switch to this different flags register, and need all of
|
|
its flags: */
|
|
flags_offset = conds_thunk->tme_recode_conds_thunk_flags_offset;
|
|
flags_needed = 0 - (tme_recode_uguest_t) 1;
|
|
}
|
|
|
|
/* any earlier instruction that can define flags tested by this
|
|
defc instruction, needs to define those flags: */
|
|
flags_needed |= conds_thunk->tme_recode_conds_thunk_flags;
|
|
}
|
|
|
|
/* if this is an endif instruction: */
|
|
else if (opcode_mask == (1 << TME_RECODE_OPCODE_ENDIF)) {
|
|
|
|
/* if we find an earlier else, as an optimization we can restore
|
|
the flags needed by the instructions after the endif, so save
|
|
them now: */
|
|
flags_offset_else = flags_offset;
|
|
flags_needed_else = flags_needed;
|
|
}
|
|
|
|
/* if this is an else instruction: */
|
|
else if (opcode_mask == (1 << TME_RECODE_OPCODE_ELSE)) {
|
|
|
|
/* restore the flags needed by the instructions after the endif: */
|
|
flags_offset = flags_offset_else;
|
|
flags_needed = flags_needed_else;
|
|
}
|
|
|
|
/* otherwise, this must be a guest, read/write, or if instruction: */
|
|
else {
|
|
assert (opcode_mask
|
|
& ((1 << TME_RECODE_OPCODE_GUEST)
|
|
| (1 << TME_RECODE_OPCODE_RW)
|
|
| (1 << TME_RECODE_OPCODE_IF)
|
|
));
|
|
|
|
/* since a guest function may fault and never return, we have to
|
|
make sure that all flags are correct in the guest ic state at
|
|
the time of the fault.
|
|
|
|
since a read/write instruction may fault and never return, we
|
|
have to make sure that all flags are correct in the guest ic
|
|
state at the time of the fault.
|
|
|
|
since an if body may not run, we have to make sure that all
|
|
flags are correct in the guest ic state at the time of the
|
|
if.
|
|
|
|
since a jump may never return, we have to make sure that all
|
|
flags are correct in the guest ic state at the time of the
|
|
jump.
|
|
|
|
in all of these cases, we need any earlier instruction that
|
|
can define flags to do so: */
|
|
flags_needed = 0 - (tme_recode_uguest_t) 1;
|
|
}
|
|
|
|
} while (insn > insns);
|
|
|
|
/* set the next read-uses record: */
|
|
ic->tme_recode_ic_reg_guest_ruses_record_next = ruses_record_right;
|
|
|
|
/* build the new instructions thunk: */
|
|
return (tme_recode_host_insns_thunk_new(ic,
|
|
insns_group));
|
|
}
|
|
|
|
/* this invalidates all instructions thunks: */
|
|
void
|
|
tme_recode_insns_thunk_invalidate_all(struct tme_recode_ic *ic)
|
|
{
|
|
|
|
/* invalidate all instructions thunks: */
|
|
tme_recode_host_thunk_invalidate_all(ic, ic->tme_recode_ic_thunk_off_variable);
|
|
}
|
|
|
|
#endif /* TME_HAVE_RECODE */
|