mirror of
https://github.com/libretro/Mu.git
synced 2026-07-08 17:57:01 +00:00
542 lines
19 KiB
C++
542 lines
19 KiB
C++
#include <assert.h>
|
|
|
|
#include "asmcode.h"
|
|
#include "cpu.h"
|
|
#include "cpudefs.h"
|
|
#include "debug.h"
|
|
#include "mmu.h"
|
|
|
|
// Detect overflow after an addition or subtraction
|
|
#define ADD_OVERFLOW(left, right, sum) ((int32_t)(((left) ^ (sum)) & ((right) ^ (sum))) < 0)
|
|
#define SUB_OVERFLOW(left, right, sum) ((int32_t)(((left) ^ (right)) & ((left) ^ (sum))) < 0)
|
|
|
|
static uint32_t add(uint32_t left, uint32_t right, int carry, int setcc) {
|
|
uint32_t sum = left + right + carry;
|
|
if (!setcc)
|
|
return sum;
|
|
|
|
if (sum < left) carry = 1;
|
|
if (sum > left) carry = 0;
|
|
arm.cpsr_c = carry;
|
|
arm.cpsr_v = ADD_OVERFLOW(left, right, sum);
|
|
return sum;
|
|
}
|
|
|
|
// "uint8_t shift_val" is correct here. If it is a shift by register, only the bottom 8 bits are looked at.
|
|
static uint32_t shift(uint32_t value, uint8_t shift_type, uint8_t shift_val, bool setcc, bool has_rs)
|
|
{
|
|
if(shift_val == 0)
|
|
{
|
|
if(unlikely(!has_rs))
|
|
{
|
|
switch(shift_type)
|
|
{
|
|
case SH_ROR:
|
|
{
|
|
// RRX
|
|
bool carry = arm.cpsr_c;
|
|
if(setcc) arm.cpsr_c = value & 1;
|
|
return value >> 1 | uint32_t(carry) << 31;
|
|
}
|
|
case SH_ASR:
|
|
case SH_LSR: // #32 is encoded as LSR #0
|
|
return shift(value, shift_type, 32, setcc, false);
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
else if(likely(shift_val < 32))
|
|
{
|
|
switch(shift_type)
|
|
{
|
|
case SH_LSL:
|
|
if(setcc) arm.cpsr_c = (value >> (32 - shift_val)) & 1;
|
|
return value << shift_val;
|
|
case SH_LSR:
|
|
if(setcc) arm.cpsr_c = (value >> (shift_val - 1)) & 1;
|
|
return value >> shift_val;
|
|
case SH_ASR:
|
|
if(setcc) arm.cpsr_c = (value >> (shift_val - 1)) & 1;
|
|
if(value & (1u << 31)) //TODO: Verify!
|
|
return ~((~value) >> shift_val);
|
|
else
|
|
return value >> shift_val;
|
|
case SH_ROR:
|
|
if(setcc) arm.cpsr_c = (value >> (shift_val - 1)) & 1;
|
|
return value >> shift_val | (value << (32 - shift_val));
|
|
}
|
|
}
|
|
else if(shift_val == 32 || shift_type == SH_ASR || shift_type == SH_ROR)
|
|
{
|
|
switch(shift_type)
|
|
{
|
|
case SH_LSL: if(setcc) arm.cpsr_c = value & 1; return 0;
|
|
case SH_LSR: if(setcc) arm.cpsr_c = !!(value & (1u << 31)); return 0;
|
|
case SH_ASR: if(setcc) arm.cpsr_c = !!(value & (1u << 31));
|
|
if(value & (1u << 31))
|
|
return 0xFFFFFFFF;
|
|
else
|
|
return 0x00000000;
|
|
case SH_ROR: return shift(value, SH_ROR, shift_val & 0b11111, setcc, false);
|
|
}
|
|
}
|
|
else // shift_val > 32
|
|
{
|
|
if(setcc)
|
|
arm.cpsr_c = 0;
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t addr_mode_2(Instruction i)
|
|
{
|
|
if(!i.mem_proc.not_imm)
|
|
return i.mem_proc.immed;
|
|
|
|
return shift(reg_pc(i.mem_proc.rm), i.mem_proc.shift, i.mem_proc.shift_imm, false, false);
|
|
}
|
|
|
|
static uint32_t rotated_imm(Instruction i, bool setcc)
|
|
{
|
|
uint32_t imm = i.data_proc.immed_8;
|
|
uint8_t count = i.data_proc.rotate_imm << 1;
|
|
if(count == 0)
|
|
return imm;
|
|
|
|
imm = (imm >> count) | (imm << (32 - count));
|
|
if(setcc)
|
|
arm.cpsr_c = !!(imm & (1u << 31));
|
|
return imm;
|
|
}
|
|
|
|
static uint32_t addr_mode_1(Instruction i, bool setcc)
|
|
{
|
|
if(i.data_proc.imm)
|
|
return rotated_imm(i, setcc);
|
|
|
|
if(i.data_proc.reg_shift)
|
|
return shift(reg_pc(i.data_proc.rm), i.data_proc.shift, reg(i.data_proc.rs), setcc, true);
|
|
else
|
|
return shift(reg_pc(i.data_proc.rm), i.data_proc.shift, i.data_proc.shift_imm, setcc, false);
|
|
}
|
|
|
|
static inline void set_nz_flags(uint32_t value) {
|
|
arm.cpsr_n = value >> 31;
|
|
arm.cpsr_z = value == 0;
|
|
}
|
|
|
|
static inline void set_nz_flags_64(uint64_t value) {
|
|
arm.cpsr_n = value >> 63;
|
|
arm.cpsr_z = value == 0;
|
|
}
|
|
|
|
void do_arm_instruction(Instruction i)
|
|
{
|
|
bool exec = true;
|
|
|
|
// Shortcut for unconditional instructions
|
|
if(likely(i.cond == CC_AL))
|
|
goto always;
|
|
|
|
switch(i.cond)
|
|
{
|
|
case CC_EQ: case CC_NE: exec = arm.cpsr_z; break;
|
|
case CC_CS: case CC_CC: exec = arm.cpsr_c; break;
|
|
case CC_MI: case CC_PL: exec = arm.cpsr_n; break;
|
|
case CC_VS: case CC_VC: exec = arm.cpsr_v; break;
|
|
case CC_HI: case CC_LS: exec = !arm.cpsr_z && arm.cpsr_c; break;
|
|
case CC_GE: case CC_LT: exec = arm.cpsr_n == arm.cpsr_v; break;
|
|
case CC_GT: case CC_LE: exec = !arm.cpsr_z && arm.cpsr_n == arm.cpsr_v; break;
|
|
case CC_NV:
|
|
if((i.raw & 0xFD70F000) == 0xF550F000)
|
|
return;
|
|
else if((i.raw & 0xFE000000) == 0xFA000000)
|
|
{
|
|
// BLX
|
|
arm.reg[14] = arm.reg[15];
|
|
arm.reg[15] += 4 + ((int32_t) (i.raw << 8) >> 6) + (i.raw >> 23 & 2);//TODO: this signed int shift is undefined behavior by the C standard
|
|
arm.cpsr_low28 |= 0x20; // Enter Thumb mode
|
|
return;
|
|
}
|
|
else
|
|
undefined_instruction();
|
|
|
|
return;
|
|
}
|
|
|
|
exec ^= i.cond & 1;
|
|
|
|
if(!exec)
|
|
return;
|
|
|
|
always:
|
|
uint32_t insn = i.raw;
|
|
|
|
if((insn & 0xE000090) == 0x0000090)
|
|
{
|
|
// MUL, SWP, etc.
|
|
// LDRH, STRSH, etc.
|
|
int type = insn >> 5 & 3;
|
|
if (type == 0) {
|
|
if ((insn & 0xFC000F0) == 0x0000090) {
|
|
/* MUL, MLA: 32x32 to 32 multiplications */
|
|
uint32_t res = reg(insn & 15)
|
|
* reg(insn >> 8 & 15);
|
|
if (insn & 0x0200000)
|
|
res += reg(insn >> 12 & 15);
|
|
|
|
set_reg(insn >> 16 & 15, res);
|
|
if (insn & 0x0100000) set_nz_flags(res);
|
|
} else if ((insn & 0xF8000F0) == 0x0800090) {
|
|
/* UMULL, UMLAL, SMULL, SMLAL: 32x32 to 64 multiplications */
|
|
uint32_t left = reg(insn & 15);
|
|
uint32_t right = reg(insn >> 8 & 15);
|
|
uint32_t reg_lo = insn >> 12 & 15;
|
|
uint32_t reg_hi = insn >> 16 & 15;
|
|
|
|
if (reg_lo == reg_hi)
|
|
error("RdLo and RdHi cannot be same for 64-bit multiply");
|
|
|
|
uint64_t res;
|
|
if (insn & 0x0400000) res = (int64_t)(int32_t)left * (int32_t)right;
|
|
else res = (uint64_t)left * right;
|
|
if (insn & 0x0200000) {
|
|
/* Accumulate */
|
|
res += (uint64_t)reg(reg_hi) << 32 | reg(reg_lo);
|
|
}
|
|
|
|
set_reg(reg_lo, res);
|
|
set_reg(reg_hi, res >> 32);
|
|
if (insn & 0x0100000) set_nz_flags_64(res);
|
|
} else if ((insn & 0xFB00FF0) == 0x1000090) {
|
|
/* SWP, SWPB */
|
|
uint32_t addr = reg(insn >> 16 & 15);
|
|
uint32_t ld, st = reg(insn & 15);
|
|
if (insn & 0x0400000) {
|
|
ld = read_byte(addr); write_byte(addr, st);
|
|
} else {
|
|
ld = read_word(addr); write_word(addr, st);
|
|
}
|
|
set_reg(insn >> 12 & 15, ld);
|
|
} else {
|
|
undefined_instruction();
|
|
}
|
|
} else {
|
|
/* Load/store halfword, signed byte/halfword, or doubleword */
|
|
int base_reg = insn >> 16 & 15;
|
|
int data_reg = insn >> 12 & 15;
|
|
int offset = (insn & (1 << 22))
|
|
? (insn & 0x0F) | (insn >> 4 & 0xF0)
|
|
: reg(insn & 15);
|
|
bool writeback = false;
|
|
uint32_t addr = reg_pc(base_reg);
|
|
|
|
if (!(insn & (1 << 23))) // Subtracted offset
|
|
offset = -offset;
|
|
|
|
if (insn & (1 << 24)) { // Offset or pre-indexed addressing
|
|
addr += offset;
|
|
offset = 0;
|
|
writeback = insn & (1 << 21);
|
|
} else {
|
|
if(insn & (1 << 21))
|
|
mmu_check_priv(addr, !((insn & (1 << 20)) || type == 2));
|
|
|
|
writeback = true;
|
|
}
|
|
|
|
if (insn & (1 << 20)) {
|
|
uint32_t data;
|
|
if (base_reg == data_reg && writeback)
|
|
error("Load instruction modifies base register twice");
|
|
if (type == 1) data = read_half(addr); /* LDRH */
|
|
else if (type == 2) data = (int8_t) read_byte(addr); /* LDRSB */
|
|
else data = (int16_t)read_half(addr); /* LDRSH */
|
|
set_reg(data_reg, data);
|
|
} else if (type == 1) { /* STRH */
|
|
write_half(addr, reg(data_reg));
|
|
} else {
|
|
if (data_reg & 1) error("LDRD/STRD with odd-numbered data register");
|
|
if (type == 2) { /* LDRD */
|
|
if ((base_reg & ~1) == data_reg && writeback)
|
|
error("Load instruction modifies base register twice");
|
|
uint32_t low = read_word(addr);
|
|
uint32_t high = read_word(addr + 4);
|
|
set_reg(data_reg, low);
|
|
set_reg(data_reg + 1, high);
|
|
} else { /* STRD */
|
|
write_word(addr, reg(data_reg));
|
|
write_word(addr + 4, reg(data_reg + 1));
|
|
}
|
|
}
|
|
if (writeback)
|
|
set_reg(base_reg, addr + offset);
|
|
}
|
|
}
|
|
else if((insn & 0xD900000) == 0x1000000)
|
|
{
|
|
// BLX, MRS, MSR, SMUL, etc.
|
|
if ((insn & 0xFFFFFD0) == 0x12FFF10) {
|
|
/* B(L)X: Branch(, link,) and exchange T bit */
|
|
uint32_t target = reg_pc(insn & 15);
|
|
if (insn & 0x20)
|
|
arm.reg[14] = arm.reg[15];
|
|
set_reg_bx(15, target);
|
|
} else if ((insn & 0xFBF0FFF) == 0x10F0000) {
|
|
/* MRS: Move reg <- status */
|
|
set_reg(insn >> 12 & 15, (insn & 0x0400000) ? get_spsr() : get_cpsr());
|
|
} else if ((insn & 0xFB0FFF0) == 0x120F000 ||
|
|
(insn & 0xFB0F000) == 0x320F000) {
|
|
/* MSR: Move status <- reg/imm */
|
|
uint32_t val, mask = 0;
|
|
if (insn & 0x2000000)
|
|
val = rotated_imm(i, false);
|
|
else
|
|
val = reg(insn & 15);
|
|
if (insn & 0x0080000) mask |= 0xFF000000;
|
|
if (insn & 0x0040000) mask |= 0x00FF0000;
|
|
if (insn & 0x0020000) mask |= 0x0000FF00;
|
|
if (insn & 0x0010000) mask |= 0x000000FF;
|
|
if (insn & 0x0400000)
|
|
set_spsr(val, mask);
|
|
else
|
|
set_cpsr(val, mask);
|
|
} else if ((insn & 0xF900090) == 0x1000080) {
|
|
int32_t left = reg(insn & 15);
|
|
int16_t right = reg((insn >> 8) & 15) >> ((insn & 0x40) ? 16 : 0);
|
|
int32_t product;
|
|
int type = insn >> 21 & 3;
|
|
|
|
if (type == 1) {
|
|
/* SMULW<y>, SMLAW<y>: Signed 32x16 to 48 multiply, uses only top 32 bits */
|
|
product = (int64_t)left * right >> 16;
|
|
if (!(insn & 0x20))
|
|
goto accumulate;
|
|
} else {
|
|
/* SMUL<x><y>, SMLA<x><y>, SMLAL<x><y>: Signed 16x16 to 32 multiply */
|
|
product = (int16_t)(left >> ((insn & 0x20) ? 16 : 0)) * right;
|
|
}
|
|
if (type == 2) {
|
|
/* SMLAL<x><y>: 64-bit accumulate */
|
|
uint32_t reg_lo = insn >> 12 & 15;
|
|
uint32_t reg_hi = insn >> 16 & 15;
|
|
int64_t sum;
|
|
if (reg_lo == reg_hi)
|
|
error("RdLo and RdHi cannot be same for 64-bit accumulate");
|
|
sum = product + ((uint64_t)reg(reg_hi) << 32 | reg(reg_lo));
|
|
set_reg(reg_lo, sum);
|
|
set_reg(reg_hi, sum >> 32);
|
|
} else if (type == 0) accumulate: {
|
|
/* SMLA<x><y>, SMLAW<y>: 32-bit accumulate */
|
|
int32_t acc = reg(insn >> 12 & 15);
|
|
int32_t sum = product + acc;
|
|
/* Set Q flag on overflow */
|
|
arm.cpsr_low28 |= ADD_OVERFLOW(product, acc, sum) << 27;
|
|
set_reg(insn >> 16 & 15, sum);
|
|
} else {
|
|
/* SMUL<x><y>, SMULW<y>: No accumulate */
|
|
set_reg(insn >> 16 & 15, product);
|
|
}
|
|
} else if ((insn & 0xF900FF0) == 0x1000050) {
|
|
/* QADD, QSUB, QDADD, QDSUB: Saturated arithmetic */
|
|
int32_t left = reg(insn & 15);
|
|
int32_t right = reg(insn >> 16 & 15);
|
|
int32_t res, overflow;
|
|
if (insn & 0x400000) {
|
|
/* Doubled right operand */
|
|
res = right << 1;
|
|
if (ADD_OVERFLOW(right, right, res)) {
|
|
/* Overflow, set Q flag and saturate */
|
|
arm.cpsr_low28 |= 1 << 27;
|
|
res = (res < 0) ? 0x7FFFFFFF : 0x80000000;
|
|
}
|
|
right = res;
|
|
}
|
|
if (!(insn & 0x200000)) {
|
|
res = left + right;
|
|
overflow = ADD_OVERFLOW(left, right, res);
|
|
} else {
|
|
res = left - right;
|
|
overflow = SUB_OVERFLOW(left, right, res);
|
|
}
|
|
if (overflow) {
|
|
/* Set Q flag and saturate */
|
|
arm.cpsr_low28 |= 1 << 27;
|
|
res = (res < 0) ? 0x7FFFFFFF : 0x80000000;
|
|
}
|
|
set_reg(insn >> 12 & 15, res);
|
|
} else if ((insn & 0xFFF0FF0) == 0x16F0F10) {
|
|
/* CLZ: Count leading zeros */
|
|
int32_t value = reg(insn & 15);
|
|
uint32_t zeros;
|
|
for (zeros = 0; zeros < 32 && value >= 0; zeros++)
|
|
value <<= 1;
|
|
set_reg(insn >> 12 & 15, zeros);
|
|
} else if ((insn & 0xFFF000F0) == 0xE1200070) {
|
|
gui_debug_printf("Software breakpoint at %08X (%04X)\n",
|
|
arm.reg[15], (insn >> 4 & 0xFFF0) | (insn & 0xF));
|
|
debugger(DBG_EXEC_BREAKPOINT, 0);
|
|
} else
|
|
undefined_instruction();
|
|
}
|
|
else if(likely((insn & 0xC000000) == 0x0000000))
|
|
{
|
|
// Data processing
|
|
bool carry = arm.cpsr_c,
|
|
setcc = i.data_proc.s;
|
|
|
|
uint32_t left = reg_pc(i.data_proc.rn),
|
|
right = addr_mode_1(i, setcc),
|
|
res = 0;
|
|
|
|
switch(i.data_proc.op)
|
|
{
|
|
case OP_AND: res = left & right; break;
|
|
case OP_EOR: res = left ^ right; break;
|
|
case OP_SUB: res = add( left, ~right, 1, setcc); break;
|
|
case OP_RSB: res = add(~left, right, 1, setcc); break;
|
|
case OP_ADD: res = add( left, right, 0, setcc); break;
|
|
case OP_ADC: res = add( left, right, carry, setcc); break;
|
|
case OP_SBC: res = add( left, ~right, carry, setcc); break;
|
|
case OP_RSC: res = add(~left, right, carry, setcc); break;
|
|
case OP_TST: res = left & right; break;
|
|
case OP_TEQ: res = left ^ right; break;
|
|
case OP_CMP: res = add( left, ~right, 1, setcc); break;
|
|
case OP_CMN: res = add( left, right, 0, setcc); break;
|
|
case OP_ORR: res = left | right; break;
|
|
case OP_MOV: res = right; break;
|
|
case OP_BIC: res = left & ~right; break;
|
|
case OP_MVN: res = ~right; break;
|
|
}
|
|
|
|
if(i.data_proc.op < OP_TST || i.data_proc.op > OP_CMN)
|
|
set_reg_pc(i.data_proc.rd, res);
|
|
|
|
if(setcc)
|
|
{
|
|
// Used for returning from exceptions, for instance
|
|
if(i.data_proc.rd == 15)
|
|
set_cpsr_full(get_spsr());
|
|
else
|
|
{
|
|
arm.cpsr_n = res >> 31;
|
|
arm.cpsr_z = res == 0;
|
|
}
|
|
}
|
|
}
|
|
else if((insn & 0xFF000F0) == 0x7F000F0)
|
|
undefined_instruction();
|
|
else if((insn & 0xC000000) == 0x4000000)
|
|
{
|
|
// LDR, STRB, etc.
|
|
uint32_t base = reg_pc(i.mem_proc.rn),
|
|
offset = addr_mode_2(i);
|
|
if(!i.mem_proc.u)
|
|
offset = -offset;
|
|
|
|
// Pre-indexed or offset
|
|
if(i.mem_proc.p)
|
|
base += offset; // Writeback for pre-indexed handled after access
|
|
else if(i.mem_proc.w) // Usermode Access
|
|
mmu_check_priv(base, !i.mem_proc.l);
|
|
|
|
// Byte access
|
|
if(i.mem_proc.b)
|
|
{
|
|
if(i.mem_proc.l) set_reg_bx(i.mem_proc.rd, read_byte(base));
|
|
else write_byte(base, reg_pc_mem(i.mem_proc.rd));
|
|
}
|
|
else
|
|
{
|
|
if(i.mem_proc.l) set_reg_bx(i.mem_proc.rd, read_word(base));
|
|
else write_word(base, reg_pc_mem(i.mem_proc.rd));
|
|
}
|
|
|
|
// Post-indexed addressing
|
|
if(!i.mem_proc.p)
|
|
base += offset;
|
|
|
|
// Writeback
|
|
if(!i.mem_proc.p || i.mem_proc.w)
|
|
set_reg(i.mem_proc.rn, base);
|
|
}
|
|
else if((insn & 0xE000000) == 0x8000000)
|
|
{
|
|
// LDM, STM, etc.
|
|
int base_reg = insn >> 16 & 15;
|
|
uint32_t addr = reg(base_reg);
|
|
uint32_t new_base = addr;
|
|
int count = __builtin_popcount(i.mem_multi.reglist);
|
|
|
|
if (i.mem_multi.u) { // Increasing
|
|
if (i.mem_multi.w) // Writeback
|
|
new_base += count * 4;
|
|
if (i.mem_multi.p) // Preincrement
|
|
addr += 4;
|
|
} else { // Decreasing
|
|
addr -= count * 4;
|
|
if (i.mem_multi.w) // Writeback
|
|
new_base = addr;
|
|
if (!i.mem_multi.p) // Postdecrement
|
|
addr += 4;
|
|
}
|
|
|
|
for (unsigned reg = 0, reglist = i.mem_multi.reglist; reglist && reg < 15; reglist >>= 1, reg++) {
|
|
if ((reglist & 1) == 0)
|
|
continue;
|
|
|
|
uint32_t *reg_ptr = &arm.reg[reg];
|
|
if (i.mem_multi.s && !i.mem_multi.w && !(i.mem_multi.reglist & (1<<15))) {
|
|
// User-mode registers
|
|
int mode = arm.cpsr_low28 & 0x1F;
|
|
if (reg >= 13) {
|
|
if (mode != MODE_USR && mode != MODE_SYS) reg_ptr = &arm.r13_usr[reg - 13];
|
|
} else if (reg >= 8) {
|
|
if (mode == MODE_FIQ) reg_ptr = &arm.r8_usr[reg - 8];
|
|
}
|
|
}
|
|
if (i.mem_multi.l) { // Load
|
|
if (reg_ptr == &arm.reg[base_reg]) {
|
|
if (i.mem_multi.w) // Writeback
|
|
error("Load instruction modifies base register twice");
|
|
reg_ptr = &new_base;
|
|
}
|
|
*reg_ptr = read_word(addr);
|
|
} else { // Store
|
|
write_word(addr, *reg_ptr);
|
|
}
|
|
addr += 4;
|
|
}
|
|
if (i.mem_multi.reglist & (1 << 15)) {
|
|
if (i.mem_multi.l) // Load
|
|
set_reg_bx(15, read_word(addr));
|
|
else // Store
|
|
write_word(addr, reg_pc_mem(15));
|
|
}
|
|
arm.reg[base_reg] = new_base;
|
|
if (i.mem_multi.l && i.mem_multi.s && i.mem_multi.reglist & (1<<15))
|
|
set_cpsr_full(get_spsr());
|
|
}
|
|
else if((insn & 0xE000000) == 0xA000000)
|
|
{
|
|
// B and BL
|
|
if(i.branch.l)
|
|
arm.reg[14] = arm.reg[15];
|
|
arm.reg[15] += (int32_t) (i.branch.immed << 8) >> 6;//TODO: this signed int shift is undefined behavior by the C standard
|
|
arm.reg[15] += 4;
|
|
if(arm.reg[15] >= 0x2009B130 && arm.reg[15] <= 0x200C7EEB && i.branch.l)
|
|
gui_debug_printf("ARM DAL function call, jump from 0x%08X to 0x%08X\n", arm.reg[14] - 4, arm.reg[15]);
|
|
}
|
|
else if((insn & 0xF000F10) == 0xE000F10)
|
|
do_cp15_instruction(i);
|
|
else if((insn & 0xF000F10) == 0xE000E10)
|
|
do_cp14_instruction(i);
|
|
else if((insn & 0xF000000) == 0xF000000)
|
|
cpu_exception(EX_SWI);
|
|
else
|
|
undefined_instruction();
|
|
}
|