| /* simulator.c -- Interface for the AArch64 simulator. |
| |
| Copyright (C) 2015-2021 Free Software Foundation, Inc. |
| |
| Contributed by Red Hat. |
| |
| This file is part of GDB. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
| |
| /* This must come before any other includes. */ |
| #include "defs.h" |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <math.h> |
| #include <time.h> |
| #include <limits.h> |
| |
| #include "simulator.h" |
| #include "cpustate.h" |
| #include "memory.h" |
| |
| #include "sim-signal.h" |
| |
| #define NO_SP 0 |
| #define SP_OK 1 |
| |
| #define TST(_flag) (aarch64_test_CPSR_bit (cpu, _flag)) |
| #define IS_SET(_X) (TST (( _X )) ? 1 : 0) |
| #define IS_CLEAR(_X) (TST (( _X )) ? 0 : 1) |
| |
| /* Space saver macro. */ |
| #define INSTR(HIGH, LOW) uimm (aarch64_get_instr (cpu), (HIGH), (LOW)) |
| |
| #define HALT_UNALLOC \ |
| do \ |
| { \ |
| TRACE_DISASM (cpu, aarch64_get_PC (cpu)); \ |
| TRACE_INSN (cpu, \ |
| "Unallocated instruction detected at sim line %d," \ |
| " exe addr %" PRIx64, \ |
| __LINE__, aarch64_get_PC (cpu)); \ |
| sim_engine_halt (CPU_STATE (cpu), cpu, NULL, aarch64_get_PC (cpu),\ |
| sim_stopped, SIM_SIGILL); \ |
| } \ |
| while (0) |
| |
| #define HALT_NYI \ |
| do \ |
| { \ |
| TRACE_DISASM (cpu, aarch64_get_PC (cpu)); \ |
| TRACE_INSN (cpu, \ |
| "Unimplemented instruction detected at sim line %d," \ |
| " exe addr %" PRIx64, \ |
| __LINE__, aarch64_get_PC (cpu)); \ |
| if (! TRACE_ANY_P (cpu)) \ |
| sim_io_eprintf (CPU_STATE (cpu), "SIM Error: Unimplemented instruction: %#08x\n", \ |
| aarch64_get_instr (cpu)); \ |
| sim_engine_halt (CPU_STATE (cpu), cpu, NULL, aarch64_get_PC (cpu),\ |
| sim_stopped, SIM_SIGABRT); \ |
| } \ |
| while (0) |
| |
| #define NYI_assert(HI, LO, EXPECTED) \ |
| do \ |
| { \ |
| if (INSTR ((HI), (LO)) != (EXPECTED)) \ |
| HALT_NYI; \ |
| } \ |
| while (0) |
| |
| /* Helper functions used by expandLogicalImmediate. */ |
| |
| /* for i = 1, ... N result<i-1> = 1 other bits are zero */ |
| static inline uint64_t |
| ones (int N) |
| { |
| return (N == 64 ? (uint64_t)-1UL : ((1UL << N) - 1)); |
| } |
| |
| /* result<0> to val<N> */ |
| static inline uint64_t |
| pickbit (uint64_t val, int N) |
| { |
| return pickbits64 (val, N, N); |
| } |
| |
| static uint64_t |
| expand_logical_immediate (uint32_t S, uint32_t R, uint32_t N) |
| { |
| uint64_t mask; |
| uint64_t imm; |
| unsigned simd_size; |
| |
| /* The immediate value is S+1 bits to 1, left rotated by SIMDsize - R |
| (in other words, right rotated by R), then replicated. */ |
| if (N != 0) |
| { |
| simd_size = 64; |
| mask = 0xffffffffffffffffull; |
| } |
| else |
| { |
| switch (S) |
| { |
| case 0x00 ... 0x1f: /* 0xxxxx */ simd_size = 32; break; |
| case 0x20 ... 0x2f: /* 10xxxx */ simd_size = 16; S &= 0xf; break; |
| case 0x30 ... 0x37: /* 110xxx */ simd_size = 8; S &= 0x7; break; |
| case 0x38 ... 0x3b: /* 1110xx */ simd_size = 4; S &= 0x3; break; |
| case 0x3c ... 0x3d: /* 11110x */ simd_size = 2; S &= 0x1; break; |
| default: return 0; |
| } |
| mask = (1ull << simd_size) - 1; |
| /* Top bits are IGNORED. */ |
| R &= simd_size - 1; |
| } |
| |
| /* NOTE: if S = simd_size - 1 we get 0xf..f which is rejected. */ |
| if (S == simd_size - 1) |
| return 0; |
| |
| /* S+1 consecutive bits to 1. */ |
| /* NOTE: S can't be 63 due to detection above. */ |
| imm = (1ull << (S + 1)) - 1; |
| |
| /* Rotate to the left by simd_size - R. */ |
| if (R != 0) |
| imm = ((imm << (simd_size - R)) & mask) | (imm >> R); |
| |
| /* Replicate the value according to SIMD size. */ |
| switch (simd_size) |
| { |
| case 2: imm = (imm << 2) | imm; |
| case 4: imm = (imm << 4) | imm; |
| case 8: imm = (imm << 8) | imm; |
| case 16: imm = (imm << 16) | imm; |
| case 32: imm = (imm << 32) | imm; |
| case 64: break; |
| default: return 0; |
| } |
| |
| return imm; |
| } |
| |
| /* Instr[22,10] encodes N immr and imms. we want a lookup table |
| for each possible combination i.e. 13 bits worth of int entries. */ |
| #define LI_TABLE_SIZE (1 << 13) |
| static uint64_t LITable[LI_TABLE_SIZE]; |
| |
| void |
| aarch64_init_LIT_table (void) |
| { |
| unsigned index; |
| |
| for (index = 0; index < LI_TABLE_SIZE; index++) |
| { |
| uint32_t N = uimm (index, 12, 12); |
| uint32_t immr = uimm (index, 11, 6); |
| uint32_t imms = uimm (index, 5, 0); |
| |
| LITable [index] = expand_logical_immediate (imms, immr, N); |
| } |
| } |
| |
| static void |
| dexNotify (sim_cpu *cpu) |
| { |
| /* instr[14,0] == type : 0 ==> method entry, 1 ==> method reentry |
| 2 ==> exit Java, 3 ==> start next bytecode. */ |
| uint32_t type = INSTR (14, 0); |
| |
| TRACE_EVENTS (cpu, "Notify Insn encountered, type = 0x%x", type); |
| |
| switch (type) |
| { |
| case 0: |
| /* aarch64_notifyMethodEntry (aarch64_get_reg_u64 (cpu, R23, 0), |
| aarch64_get_reg_u64 (cpu, R22, 0)); */ |
| break; |
| case 1: |
| /* aarch64_notifyMethodReentry (aarch64_get_reg_u64 (cpu, R23, 0), |
| aarch64_get_reg_u64 (cpu, R22, 0)); */ |
| break; |
| case 2: |
| /* aarch64_notifyMethodExit (); */ |
| break; |
| case 3: |
| /* aarch64_notifyBCStart (aarch64_get_reg_u64 (cpu, R23, 0), |
| aarch64_get_reg_u64 (cpu, R22, 0)); */ |
| break; |
| } |
| } |
| |
| /* secondary decode within top level groups */ |
| |
| static void |
| dexPseudo (sim_cpu *cpu) |
| { |
| /* assert instr[28,27] = 00 |
| |
| We provide 2 pseudo instructions: |
| |
| HALT stops execution of the simulator causing an immediate |
| return to the x86 code which entered it. |
| |
| CALLOUT initiates recursive entry into x86 code. A register |
| argument holds the address of the x86 routine. Immediate |
| values in the instruction identify the number of general |
| purpose and floating point register arguments to be passed |
| and the type of any value to be returned. */ |
| |
| uint32_t PSEUDO_HALT = 0xE0000000U; |
| uint32_t PSEUDO_CALLOUT = 0x00018000U; |
| uint32_t PSEUDO_CALLOUTR = 0x00018001U; |
| uint32_t PSEUDO_NOTIFY = 0x00014000U; |
| uint32_t dispatch; |
| |
| if (aarch64_get_instr (cpu) == PSEUDO_HALT) |
| { |
| TRACE_EVENTS (cpu, " Pseudo Halt Instruction"); |
| sim_engine_halt (CPU_STATE (cpu), cpu, NULL, aarch64_get_PC (cpu), |
| sim_stopped, SIM_SIGTRAP); |
| } |
| |
| dispatch = INSTR (31, 15); |
| |
| /* We do not handle callouts at the moment. */ |
| if (dispatch == PSEUDO_CALLOUT || dispatch == PSEUDO_CALLOUTR) |
| { |
| TRACE_EVENTS (cpu, " Callout"); |
| sim_engine_halt (CPU_STATE (cpu), cpu, NULL, aarch64_get_PC (cpu), |
| sim_stopped, SIM_SIGABRT); |
| } |
| |
| else if (dispatch == PSEUDO_NOTIFY) |
| dexNotify (cpu); |
| |
| else |
| HALT_UNALLOC; |
| } |
| |
| /* Load-store single register (unscaled offset) |
| These instructions employ a base register plus an unscaled signed |
| 9 bit offset. |
| |
| N.B. the base register (source) can be Xn or SP. all other |
| registers may not be SP. */ |
| |
| /* 32 bit load 32 bit unscaled signed 9 bit. */ |
| static void |
| ldur32 (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, aarch64_get_mem_u32 |
| (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + offset)); |
| } |
| |
| /* 64 bit load 64 bit unscaled signed 9 bit. */ |
| static void |
| ldur64 (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, aarch64_get_mem_u64 |
| (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + offset)); |
| } |
| |
| /* 32 bit load zero-extended byte unscaled signed 9 bit. */ |
| static void |
| ldurb32 (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, aarch64_get_mem_u8 |
| (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + offset)); |
| } |
| |
| /* 32 bit load sign-extended byte unscaled signed 9 bit. */ |
| static void |
| ldursb32 (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, (uint32_t) aarch64_get_mem_s8 |
| (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + offset)); |
| } |
| |
| /* 64 bit load sign-extended byte unscaled signed 9 bit. */ |
| static void |
| ldursb64 (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_s64 (cpu, rt, NO_SP, aarch64_get_mem_s8 |
| (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + offset)); |
| } |
| |
| /* 32 bit load zero-extended short unscaled signed 9 bit */ |
| static void |
| ldurh32 (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, aarch64_get_mem_u16 |
| (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + offset)); |
| } |
| |
| /* 32 bit load sign-extended short unscaled signed 9 bit */ |
| static void |
| ldursh32 (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, (uint32_t) aarch64_get_mem_s16 |
| (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + offset)); |
| } |
| |
| /* 64 bit load sign-extended short unscaled signed 9 bit */ |
| static void |
| ldursh64 (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_s64 (cpu, rt, NO_SP, aarch64_get_mem_s16 |
| (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + offset)); |
| } |
| |
| /* 64 bit load sign-extended word unscaled signed 9 bit */ |
| static void |
| ldursw (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, (uint32_t) aarch64_get_mem_s32 |
| (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + offset)); |
| } |
| |
| /* N.B. with stores the value in source is written to the address |
| identified by source2 modified by offset. */ |
| |
| /* 32 bit store 32 bit unscaled signed 9 bit. */ |
| static void |
| stur32 (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_mem_u32 (cpu, |
| aarch64_get_reg_u64 (cpu, rn, SP_OK) + offset, |
| aarch64_get_reg_u32 (cpu, rd, NO_SP)); |
| } |
| |
| /* 64 bit store 64 bit unscaled signed 9 bit */ |
| static void |
| stur64 (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_mem_u64 (cpu, |
| aarch64_get_reg_u64 (cpu, rn, SP_OK) + offset, |
| aarch64_get_reg_u64 (cpu, rd, NO_SP)); |
| } |
| |
| /* 32 bit store byte unscaled signed 9 bit */ |
| static void |
| sturb (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_mem_u8 (cpu, |
| aarch64_get_reg_u64 (cpu, rn, SP_OK) + offset, |
| aarch64_get_reg_u8 (cpu, rd, NO_SP)); |
| } |
| |
| /* 32 bit store short unscaled signed 9 bit */ |
| static void |
| sturh (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_mem_u16 (cpu, |
| aarch64_get_reg_u64 (cpu, rn, SP_OK) + offset, |
| aarch64_get_reg_u16 (cpu, rd, NO_SP)); |
| } |
| |
| /* Load single register pc-relative label |
| Offset is a signed 19 bit immediate count in words |
| rt may not be SP. */ |
| |
| /* 32 bit pc-relative load */ |
| static void |
| ldr32_pcrel (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, |
| aarch64_get_mem_u32 |
| (cpu, aarch64_get_PC (cpu) + offset * 4)); |
| } |
| |
| /* 64 bit pc-relative load */ |
| static void |
| ldr_pcrel (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, |
| aarch64_get_mem_u64 |
| (cpu, aarch64_get_PC (cpu) + offset * 4)); |
| } |
| |
| /* sign extended 32 bit pc-relative load */ |
| static void |
| ldrsw_pcrel (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, |
| aarch64_get_mem_s32 |
| (cpu, aarch64_get_PC (cpu) + offset * 4)); |
| } |
| |
| /* float pc-relative load */ |
| static void |
| fldrs_pcrel (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned int rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_vec_u32 (cpu, rd, 0, |
| aarch64_get_mem_u32 |
| (cpu, aarch64_get_PC (cpu) + offset * 4)); |
| } |
| |
| /* double pc-relative load */ |
| static void |
| fldrd_pcrel (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned int st = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_vec_u64 (cpu, st, 0, |
| aarch64_get_mem_u64 |
| (cpu, aarch64_get_PC (cpu) + offset * 4)); |
| } |
| |
| /* long double pc-relative load. */ |
| static void |
| fldrq_pcrel (sim_cpu *cpu, int32_t offset) |
| { |
| unsigned int st = INSTR (4, 0); |
| uint64_t addr = aarch64_get_PC (cpu) + offset * 4; |
| FRegister a; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_get_mem_long_double (cpu, addr, & a); |
| aarch64_set_FP_long_double (cpu, st, a); |
| } |
| |
| /* This can be used to scale an offset by applying |
| the requisite shift. the second argument is either |
| 16, 32 or 64. */ |
| |
| #define SCALE(_offset, _elementSize) \ |
| ((_offset) << ScaleShift ## _elementSize) |
| |
| /* This can be used to optionally scale a register derived offset |
| by applying the requisite shift as indicated by the Scaling |
| argument. The second argument is either Byte, Short, Word |
| or Long. The third argument is either Scaled or Unscaled. |
| N.B. when _Scaling is Scaled the shift gets ANDed with |
| all 1s while when it is Unscaled it gets ANDed with 0. */ |
| |
| #define OPT_SCALE(_offset, _elementType, _Scaling) \ |
| ((_offset) << (_Scaling ? ScaleShift ## _elementType : 0)) |
| |
| /* This can be used to zero or sign extend a 32 bit register derived |
| value to a 64 bit value. the first argument must be the value as |
| a uint32_t and the second must be either UXTW or SXTW. The result |
| is returned as an int64_t. */ |
| |
| static inline int64_t |
| extend (uint32_t value, Extension extension) |
| { |
| union |
| { |
| uint32_t u; |
| int32_t n; |
| } x; |
| |
| /* A branchless variant of this ought to be possible. */ |
| if (extension == UXTW || extension == NoExtension) |
| return value; |
| |
| x.u = value; |
| return x.n; |
| } |
| |
| /* Scalar Floating Point |
| |
| FP load/store single register (4 addressing modes) |
| |
| N.B. the base register (source) can be the stack pointer. |
| The secondary source register (source2) can only be an Xn register. */ |
| |
| /* Load 32 bit unscaled signed 9 bit with pre- or post-writeback. */ |
| static void |
| fldrs_wb (sim_cpu *cpu, int32_t offset, WriteBack wb) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned st = INSTR (4, 0); |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| |
| if (wb != Post) |
| address += offset; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_vec_u32 (cpu, st, 0, aarch64_get_mem_u32 (cpu, address)); |
| if (wb == Post) |
| address += offset; |
| |
| if (wb != NoWriteBack) |
| aarch64_set_reg_u64 (cpu, rn, SP_OK, address); |
| } |
| |
| /* Load 8 bit with unsigned 12 bit offset. */ |
| static void |
| fldrb_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| unsigned rd = INSTR (4, 0); |
| unsigned rn = INSTR (9, 5); |
| uint64_t addr = aarch64_get_reg_u64 (cpu, rn, SP_OK) + offset; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_vec_u8 (cpu, rd, 0, aarch64_get_mem_u32 (cpu, addr)); |
| } |
| |
| /* Load 16 bit scaled unsigned 12 bit. */ |
| static void |
| fldrh_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| unsigned rd = INSTR (4, 0); |
| unsigned rn = INSTR (9, 5); |
| uint64_t addr = aarch64_get_reg_u64 (cpu, rn, SP_OK) + SCALE (offset, 16); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_vec_u16 (cpu, rd, 0, aarch64_get_mem_u16 (cpu, addr)); |
| } |
| |
| /* Load 32 bit scaled unsigned 12 bit. */ |
| static void |
| fldrs_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| unsigned rd = INSTR (4, 0); |
| unsigned rn = INSTR (9, 5); |
| uint64_t addr = aarch64_get_reg_u64 (cpu, rn, SP_OK) + SCALE (offset, 32); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_vec_u32 (cpu, rd, 0, aarch64_get_mem_u32 (cpu, addr)); |
| } |
| |
| /* Load 64 bit scaled unsigned 12 bit. */ |
| static void |
| fldrd_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| unsigned rd = INSTR (4, 0); |
| unsigned rn = INSTR (9, 5); |
| uint64_t addr = aarch64_get_reg_u64 (cpu, rn, SP_OK) + SCALE (offset, 64); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_vec_u64 (cpu, rd, 0, aarch64_get_mem_u64 (cpu, addr)); |
| } |
| |
| /* Load 128 bit scaled unsigned 12 bit. */ |
| static void |
| fldrq_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| unsigned rd = INSTR (4, 0); |
| unsigned rn = INSTR (9, 5); |
| uint64_t addr = aarch64_get_reg_u64 (cpu, rn, SP_OK) + SCALE (offset, 128); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_vec_u64 (cpu, rd, 0, aarch64_get_mem_u64 (cpu, addr)); |
| aarch64_set_vec_u64 (cpu, rd, 1, aarch64_get_mem_u64 (cpu, addr + 8)); |
| } |
| |
| /* Load 32 bit scaled or unscaled zero- or sign-extended |
| 32-bit register offset. */ |
| static void |
| fldrs_scale_ext (sim_cpu *cpu, Scaling scaling, Extension extension) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned st = INSTR (4, 0); |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| int64_t extended = extend (aarch64_get_reg_u32 (cpu, rm, NO_SP), extension); |
| uint64_t displacement = OPT_SCALE (extended, 32, scaling); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_vec_u32 (cpu, st, 0, aarch64_get_mem_u32 |
| (cpu, address + displacement)); |
| } |
| |
| /* Load 64 bit unscaled signed 9 bit with pre- or post-writeback. */ |
| static void |
| fldrd_wb (sim_cpu *cpu, int32_t offset, WriteBack wb) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned st = INSTR (4, 0); |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| |
| if (wb != Post) |
| address += offset; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_vec_u64 (cpu, st, 0, aarch64_get_mem_u64 (cpu, address)); |
| |
| if (wb == Post) |
| address += offset; |
| |
| if (wb != NoWriteBack) |
| aarch64_set_reg_u64 (cpu, rn, SP_OK, address); |
| } |
| |
| /* Load 64 bit scaled or unscaled zero- or sign-extended 32-bit register offset. */ |
| static void |
| fldrd_scale_ext (sim_cpu *cpu, Scaling scaling, Extension extension) |
| { |
| unsigned rm = INSTR (20, 16); |
| int64_t extended = extend (aarch64_get_reg_u32 (cpu, rm, NO_SP), extension); |
| uint64_t displacement = OPT_SCALE (extended, 64, scaling); |
| |
| fldrd_wb (cpu, displacement, NoWriteBack); |
| } |
| |
| /* Load 128 bit unscaled signed 9 bit with pre- or post-writeback. */ |
| static void |
| fldrq_wb (sim_cpu *cpu, int32_t offset, WriteBack wb) |
| { |
| FRegister a; |
| unsigned rn = INSTR (9, 5); |
| unsigned st = INSTR (4, 0); |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| |
| if (wb != Post) |
| address += offset; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_get_mem_long_double (cpu, address, & a); |
| aarch64_set_FP_long_double (cpu, st, a); |
| |
| if (wb == Post) |
| address += offset; |
| |
| if (wb != NoWriteBack) |
| aarch64_set_reg_u64 (cpu, rn, SP_OK, address); |
| } |
| |
| /* Load 128 bit scaled or unscaled zero- or sign-extended 32-bit register offset */ |
| static void |
| fldrq_scale_ext (sim_cpu *cpu, Scaling scaling, Extension extension) |
| { |
| unsigned rm = INSTR (20, 16); |
| int64_t extended = extend (aarch64_get_reg_u32 (cpu, rm, NO_SP), extension); |
| uint64_t displacement = OPT_SCALE (extended, 128, scaling); |
| |
| fldrq_wb (cpu, displacement, NoWriteBack); |
| } |
| |
| /* Memory Access |
| |
| load-store single register |
| There are four addressing modes available here which all employ a |
| 64 bit source (base) register. |
| |
| N.B. the base register (source) can be the stack pointer. |
| The secondary source register (source2)can only be an Xn register. |
| |
| Scaled, 12-bit, unsigned immediate offset, without pre- and |
| post-index options. |
| Unscaled, 9-bit, signed immediate offset with pre- or post-index |
| writeback. |
| scaled or unscaled 64-bit register offset. |
| scaled or unscaled 32-bit extended register offset. |
| |
| All offsets are assumed to be raw from the decode i.e. the |
| simulator is expected to adjust scaled offsets based on the |
| accessed data size with register or extended register offset |
| versions the same applies except that in the latter case the |
| operation may also require a sign extend. |
| |
| A separate method is provided for each possible addressing mode. */ |
| |
| /* 32 bit load 32 bit scaled unsigned 12 bit */ |
| static void |
| ldr32_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| /* The target register may not be SP but the source may be. */ |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, aarch64_get_mem_u32 |
| (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + SCALE (offset, 32))); |
| } |
| |
| /* 32 bit load 32 bit unscaled signed 9 bit with pre- or post-writeback. */ |
| static void |
| ldr32_wb (sim_cpu *cpu, int32_t offset, WriteBack wb) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| uint64_t address; |
| |
| if (rn == rt && wb != NoWriteBack) |
| HALT_UNALLOC; |
| |
| address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| |
| if (wb != Post) |
| address += offset; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, aarch64_get_mem_u32 (cpu, address)); |
| |
| if (wb == Post) |
| address += offset; |
| |
| if (wb != NoWriteBack) |
| aarch64_set_reg_u64 (cpu, rn, SP_OK, address); |
| } |
| |
| /* 32 bit load 32 bit scaled or unscaled |
| zero- or sign-extended 32-bit register offset */ |
| static void |
| ldr32_scale_ext (sim_cpu *cpu, Scaling scaling, Extension extension) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| /* rn may reference SP, rm and rt must reference ZR */ |
| |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| int64_t extended = extend (aarch64_get_reg_u32 (cpu, rm, NO_SP), extension); |
| uint64_t displacement = OPT_SCALE (extended, 32, scaling); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, |
| aarch64_get_mem_u32 (cpu, address + displacement)); |
| } |
| |
| /* 64 bit load 64 bit scaled unsigned 12 bit */ |
| static void |
| ldr_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| /* The target register may not be SP but the source may be. */ |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, aarch64_get_mem_u64 |
| (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + SCALE (offset, 64))); |
| } |
| |
| /* 64 bit load 64 bit unscaled signed 9 bit with pre- or post-writeback. */ |
| static void |
| ldr_wb (sim_cpu *cpu, int32_t offset, WriteBack wb) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| uint64_t address; |
| |
| if (rn == rt && wb != NoWriteBack) |
| HALT_UNALLOC; |
| |
| address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| |
| if (wb != Post) |
| address += offset; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, aarch64_get_mem_u64 (cpu, address)); |
| |
| if (wb == Post) |
| address += offset; |
| |
| if (wb != NoWriteBack) |
| aarch64_set_reg_u64 (cpu, rn, SP_OK, address); |
| } |
| |
| /* 64 bit load 64 bit scaled or unscaled zero- |
| or sign-extended 32-bit register offset. */ |
| static void |
| ldr_scale_ext (sim_cpu *cpu, Scaling scaling, Extension extension) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| /* rn may reference SP, rm and rt must reference ZR */ |
| |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| int64_t extended = extend (aarch64_get_reg_u32 (cpu, rm, NO_SP), extension); |
| uint64_t displacement = OPT_SCALE (extended, 64, scaling); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, |
| aarch64_get_mem_u64 (cpu, address + displacement)); |
| } |
| |
| /* 32 bit load zero-extended byte scaled unsigned 12 bit. */ |
| static void |
| ldrb32_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| /* The target register may not be SP but the source may be |
| there is no scaling required for a byte load. */ |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, |
| aarch64_get_mem_u8 |
| (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) + offset)); |
| } |
| |
| /* 32 bit load zero-extended byte unscaled signed 9 bit with pre- or post-writeback. */ |
| static void |
| ldrb32_wb (sim_cpu *cpu, int32_t offset, WriteBack wb) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| uint64_t address; |
| |
| if (rn == rt && wb != NoWriteBack) |
| HALT_UNALLOC; |
| |
| address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| |
| if (wb != Post) |
| address += offset; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, aarch64_get_mem_u8 (cpu, address)); |
| |
| if (wb == Post) |
| address += offset; |
| |
| if (wb != NoWriteBack) |
| aarch64_set_reg_u64 (cpu, rn, SP_OK, address); |
| } |
| |
| /* 32 bit load zero-extended byte scaled or unscaled zero- |
| or sign-extended 32-bit register offset. */ |
| static void |
| ldrb32_scale_ext (sim_cpu *cpu, Scaling scaling, Extension extension) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| /* rn may reference SP, rm and rt must reference ZR */ |
| |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| int64_t displacement = extend (aarch64_get_reg_u32 (cpu, rm, NO_SP), |
| extension); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| /* There is no scaling required for a byte load. */ |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, |
| aarch64_get_mem_u8 (cpu, address + displacement)); |
| } |
| |
| /* 64 bit load sign-extended byte unscaled signed 9 bit |
| with pre- or post-writeback. */ |
| static void |
| ldrsb_wb (sim_cpu *cpu, int32_t offset, WriteBack wb) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| uint64_t address; |
| int64_t val; |
| |
| if (rn == rt && wb != NoWriteBack) |
| HALT_UNALLOC; |
| |
| address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| |
| if (wb != Post) |
| address += offset; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| val = aarch64_get_mem_s8 (cpu, address); |
| aarch64_set_reg_s64 (cpu, rt, NO_SP, val); |
| |
| if (wb == Post) |
| address += offset; |
| |
| if (wb != NoWriteBack) |
| aarch64_set_reg_u64 (cpu, rn, SP_OK, address); |
| } |
| |
| /* 64 bit load sign-extended byte scaled unsigned 12 bit. */ |
| static void |
| ldrsb_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| ldrsb_wb (cpu, offset, NoWriteBack); |
| } |
| |
| /* 64 bit load sign-extended byte scaled or unscaled zero- |
| or sign-extended 32-bit register offset. */ |
| static void |
| ldrsb_scale_ext (sim_cpu *cpu, Scaling scaling, Extension extension) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| /* rn may reference SP, rm and rt must reference ZR */ |
| |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| int64_t displacement = extend (aarch64_get_reg_u32 (cpu, rm, NO_SP), |
| extension); |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| /* There is no scaling required for a byte load. */ |
| aarch64_set_reg_s64 (cpu, rt, NO_SP, |
| aarch64_get_mem_s8 (cpu, address + displacement)); |
| } |
| |
| /* 32 bit load zero-extended short scaled unsigned 12 bit. */ |
| static void |
| ldrh32_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| uint32_t val; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| /* The target register may not be SP but the source may be. */ |
| val = aarch64_get_mem_u16 (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + SCALE (offset, 16)); |
| aarch64_set_reg_u32 (cpu, rt, NO_SP, val); |
| } |
| |
| /* 32 bit load zero-extended short unscaled signed 9 bit |
| with pre- or post-writeback. */ |
| static void |
| ldrh32_wb (sim_cpu *cpu, int32_t offset, WriteBack wb) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| uint64_t address; |
| |
| if (rn == rt && wb != NoWriteBack) |
| HALT_UNALLOC; |
| |
| address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| |
| if (wb != Post) |
| address += offset; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u32 (cpu, rt, NO_SP, aarch64_get_mem_u16 (cpu, address)); |
| |
| if (wb == Post) |
| address += offset; |
| |
| if (wb != NoWriteBack) |
| aarch64_set_reg_u64 (cpu, rn, SP_OK, address); |
| } |
| |
| /* 32 bit load zero-extended short scaled or unscaled zero- |
| or sign-extended 32-bit register offset. */ |
| static void |
| ldrh32_scale_ext (sim_cpu *cpu, Scaling scaling, Extension extension) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| /* rn may reference SP, rm and rt must reference ZR */ |
| |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| int64_t extended = extend (aarch64_get_reg_u32 (cpu, rm, NO_SP), extension); |
| uint64_t displacement = OPT_SCALE (extended, 16, scaling); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u32 (cpu, rt, NO_SP, |
| aarch64_get_mem_u16 (cpu, address + displacement)); |
| } |
| |
| /* 32 bit load sign-extended short scaled unsigned 12 bit. */ |
| static void |
| ldrsh32_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| int32_t val; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| /* The target register may not be SP but the source may be. */ |
| val = aarch64_get_mem_s16 (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + SCALE (offset, 16)); |
| aarch64_set_reg_s32 (cpu, rt, NO_SP, val); |
| } |
| |
| /* 32 bit load sign-extended short unscaled signed 9 bit |
| with pre- or post-writeback. */ |
| static void |
| ldrsh32_wb (sim_cpu *cpu, int32_t offset, WriteBack wb) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| uint64_t address; |
| |
| if (rn == rt && wb != NoWriteBack) |
| HALT_UNALLOC; |
| |
| address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| |
| if (wb != Post) |
| address += offset; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_s32 (cpu, rt, NO_SP, |
| (int32_t) aarch64_get_mem_s16 (cpu, address)); |
| |
| if (wb == Post) |
| address += offset; |
| |
| if (wb != NoWriteBack) |
| aarch64_set_reg_u64 (cpu, rn, SP_OK, address); |
| } |
| |
| /* 32 bit load sign-extended short scaled or unscaled zero- |
| or sign-extended 32-bit register offset. */ |
| static void |
| ldrsh32_scale_ext (sim_cpu *cpu, Scaling scaling, Extension extension) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| /* rn may reference SP, rm and rt must reference ZR */ |
| |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| int64_t extended = extend (aarch64_get_reg_u32 (cpu, rm, NO_SP), extension); |
| uint64_t displacement = OPT_SCALE (extended, 16, scaling); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_s32 (cpu, rt, NO_SP, |
| (int32_t) aarch64_get_mem_s16 |
| (cpu, address + displacement)); |
| } |
| |
| /* 64 bit load sign-extended short scaled unsigned 12 bit. */ |
| static void |
| ldrsh_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| int64_t val; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| /* The target register may not be SP but the source may be. */ |
| val = aarch64_get_mem_s16 (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + SCALE (offset, 16)); |
| aarch64_set_reg_s64 (cpu, rt, NO_SP, val); |
| } |
| |
| /* 64 bit load sign-extended short unscaled signed 9 bit |
| with pre- or post-writeback. */ |
| static void |
| ldrsh64_wb (sim_cpu *cpu, int32_t offset, WriteBack wb) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| uint64_t address; |
| int64_t val; |
| |
| if (rn == rt && wb != NoWriteBack) |
| HALT_UNALLOC; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| |
| if (wb != Post) |
| address += offset; |
| |
| val = aarch64_get_mem_s16 (cpu, address); |
| aarch64_set_reg_s64 (cpu, rt, NO_SP, val); |
| |
| if (wb == Post) |
| address += offset; |
| |
| if (wb != NoWriteBack) |
| aarch64_set_reg_u64 (cpu, rn, SP_OK, address); |
| } |
| |
| /* 64 bit load sign-extended short scaled or unscaled zero- |
| or sign-extended 32-bit register offset. */ |
| static void |
| ldrsh_scale_ext (sim_cpu *cpu, Scaling scaling, Extension extension) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| |
| /* rn may reference SP, rm and rt must reference ZR */ |
| |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| int64_t extended = extend (aarch64_get_reg_u32 (cpu, rm, NO_SP), extension); |
| uint64_t displacement = OPT_SCALE (extended, 16, scaling); |
| int64_t val; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| val = aarch64_get_mem_s16 (cpu, address + displacement); |
| aarch64_set_reg_s64 (cpu, rt, NO_SP, val); |
| } |
| |
| /* 64 bit load sign-extended 32 bit scaled unsigned 12 bit. */ |
| static void |
| ldrsw_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| int64_t val; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| val = aarch64_get_mem_s32 (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + SCALE (offset, 32)); |
| /* The target register may not be SP but the source may be. */ |
| return aarch64_set_reg_s64 (cpu, rt, NO_SP, val); |
| } |
| |
| /* 64 bit load sign-extended 32 bit unscaled signed 9 bit |
| with pre- or post-writeback. */ |
| static void |
| ldrsw_wb (sim_cpu *cpu, int32_t offset, WriteBack wb) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| uint64_t address; |
| |
| if (rn == rt && wb != NoWriteBack) |
| HALT_UNALLOC; |
| |
| address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| |
| if (wb != Post) |
| address += offset; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_s64 (cpu, rt, NO_SP, aarch64_get_mem_s32 (cpu, address)); |
| |
| if (wb == Post) |
| address += offset; |
| |
| if (wb != NoWriteBack) |
| aarch64_set_reg_u64 (cpu, rn, SP_OK, address); |
| } |
| |
| /* 64 bit load sign-extended 32 bit scaled or unscaled zero- |
| or sign-extended 32-bit register offset. */ |
| static void |
| ldrsw_scale_ext (sim_cpu *cpu, Scaling scaling, Extension extension) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| /* rn may reference SP, rm and rt must reference ZR */ |
| |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| int64_t extended = extend (aarch64_get_reg_u32 (cpu, rm, NO_SP), extension); |
| uint64_t displacement = OPT_SCALE (extended, 32, scaling); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_s64 (cpu, rt, NO_SP, |
| aarch64_get_mem_s32 (cpu, address + displacement)); |
| } |
| |
| /* N.B. with stores the value in source is written to the |
| address identified by source2 modified by source3/offset. */ |
| |
| /* 32 bit store scaled unsigned 12 bit. */ |
| static void |
| str32_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| /* The target register may not be SP but the source may be. */ |
| aarch64_set_mem_u32 (cpu, (aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + SCALE (offset, 32)), |
| aarch64_get_reg_u32 (cpu, rt, NO_SP)); |
| } |
| |
| /* 32 bit store unscaled signed 9 bit with pre- or post-writeback. */ |
| static void |
| str32_wb (sim_cpu *cpu, int32_t offset, WriteBack wb) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| uint64_t address; |
| |
| if (rn == rt && wb != NoWriteBack) |
| HALT_UNALLOC; |
| |
| address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| if (wb != Post) |
| address += offset; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_mem_u32 (cpu, address, aarch64_get_reg_u32 (cpu, rt, NO_SP)); |
| |
| if (wb == Post) |
| address += offset; |
| |
| if (wb != NoWriteBack) |
| aarch64_set_reg_u64 (cpu, rn, SP_OK, address); |
| } |
| |
| /* 32 bit store scaled or unscaled zero- or |
| sign-extended 32-bit register offset. */ |
| static void |
| str32_scale_ext (sim_cpu *cpu, Scaling scaling, Extension extension) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| int64_t extended = extend (aarch64_get_reg_u32 (cpu, rm, NO_SP), extension); |
| uint64_t displacement = OPT_SCALE (extended, 32, scaling); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_mem_u32 (cpu, address + displacement, |
| aarch64_get_reg_u64 (cpu, rt, NO_SP)); |
| } |
| |
| /* 64 bit store scaled unsigned 12 bit. */ |
| static void |
| str_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_mem_u64 (cpu, |
| aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + SCALE (offset, 64), |
| aarch64_get_reg_u64 (cpu, rt, NO_SP)); |
| } |
| |
| /* 64 bit store unscaled signed 9 bit with pre- or post-writeback. */ |
| static void |
| str_wb (sim_cpu *cpu, int32_t offset, WriteBack wb) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| uint64_t address; |
| |
| if (rn == rt && wb != NoWriteBack) |
| HALT_UNALLOC; |
| |
| address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| |
| if (wb != Post) |
| address += offset; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_mem_u64 (cpu, address, aarch64_get_reg_u64 (cpu, rt, NO_SP)); |
| |
| if (wb == Post) |
| address += offset; |
| |
| if (wb != NoWriteBack) |
| aarch64_set_reg_u64 (cpu, rn, SP_OK, address); |
| } |
| |
| /* 64 bit store scaled or unscaled zero- |
| or sign-extended 32-bit register offset. */ |
| static void |
| str_scale_ext (sim_cpu *cpu, Scaling scaling, Extension extension) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| /* rn may reference SP, rm and rt must reference ZR */ |
| |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| int64_t extended = extend (aarch64_get_reg_u32 (cpu, rm, NO_SP), |
| extension); |
| uint64_t displacement = OPT_SCALE (extended, 64, scaling); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_mem_u64 (cpu, address + displacement, |
| aarch64_get_reg_u64 (cpu, rt, NO_SP)); |
| } |
| |
| /* 32 bit store byte scaled unsigned 12 bit. */ |
| static void |
| strb_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| /* The target register may not be SP but the source may be. |
| There is no scaling required for a byte load. */ |
| aarch64_set_mem_u8 (cpu, |
| aarch64_get_reg_u64 (cpu, rn, SP_OK) + offset, |
| aarch64_get_reg_u8 (cpu, rt, NO_SP)); |
| } |
| |
| /* 32 bit store byte unscaled signed 9 bit with pre- or post-writeback. */ |
| static void |
| strb_wb (sim_cpu *cpu, int32_t offset, WriteBack wb) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| uint64_t address; |
| |
| if (rn == rt && wb != NoWriteBack) |
| HALT_UNALLOC; |
| |
| address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| |
| if (wb != Post) |
| address += offset; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_mem_u8 (cpu, address, aarch64_get_reg_u8 (cpu, rt, NO_SP)); |
| |
| if (wb == Post) |
| address += offset; |
| |
| if (wb != NoWriteBack) |
| aarch64_set_reg_u64 (cpu, rn, SP_OK, address); |
| } |
| |
| /* 32 bit store byte scaled or unscaled zero- |
| or sign-extended 32-bit register offset. */ |
| static void |
| strb_scale_ext (sim_cpu *cpu, Scaling scaling, Extension extension) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| /* rn may reference SP, rm and rt must reference ZR */ |
| |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| int64_t displacement = extend (aarch64_get_reg_u32 (cpu, rm, NO_SP), |
| extension); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| /* There is no scaling required for a byte load. */ |
| aarch64_set_mem_u8 (cpu, address + displacement, |
| aarch64_get_reg_u8 (cpu, rt, NO_SP)); |
| } |
| |
| /* 32 bit store short scaled unsigned 12 bit. */ |
| static void |
| strh_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| /* The target register may not be SP but the source may be. */ |
| aarch64_set_mem_u16 (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + SCALE (offset, 16), |
| aarch64_get_reg_u16 (cpu, rt, NO_SP)); |
| } |
| |
| /* 32 bit store short unscaled signed 9 bit with pre- or post-writeback. */ |
| static void |
| strh_wb (sim_cpu *cpu, int32_t offset, WriteBack wb) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| uint64_t address; |
| |
| if (rn == rt && wb != NoWriteBack) |
| HALT_UNALLOC; |
| |
| address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| |
| if (wb != Post) |
| address += offset; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_mem_u16 (cpu, address, aarch64_get_reg_u16 (cpu, rt, NO_SP)); |
| |
| if (wb == Post) |
| address += offset; |
| |
| if (wb != NoWriteBack) |
| aarch64_set_reg_u64 (cpu, rn, SP_OK, address); |
| } |
| |
| /* 32 bit store short scaled or unscaled zero- |
| or sign-extended 32-bit register offset. */ |
| static void |
| strh_scale_ext (sim_cpu *cpu, Scaling scaling, Extension extension) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| /* rn may reference SP, rm and rt must reference ZR */ |
| |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| int64_t extended = extend (aarch64_get_reg_u32 (cpu, rm, NO_SP), extension); |
| uint64_t displacement = OPT_SCALE (extended, 16, scaling); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_mem_u16 (cpu, address + displacement, |
| aarch64_get_reg_u16 (cpu, rt, NO_SP)); |
| } |
| |
| /* Prefetch unsigned 12 bit. */ |
| static void |
| prfm_abs (sim_cpu *cpu, uint32_t offset) |
| { |
| /* instr[4,0] = prfop : 00000 ==> PLDL1KEEP, 00001 ==> PLDL1STRM, |
| 00010 ==> PLDL2KEEP, 00001 ==> PLDL2STRM, |
| 00100 ==> PLDL3KEEP, 00101 ==> PLDL3STRM, |
| 10000 ==> PSTL1KEEP, 10001 ==> PSTL1STRM, |
| 10010 ==> PSTL2KEEP, 10001 ==> PSTL2STRM, |
| 10100 ==> PSTL3KEEP, 10101 ==> PSTL3STRM, |
| ow ==> UNALLOC |
| PrfOp prfop = prfop (instr, 4, 0); |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + SCALE (offset, 64). */ |
| |
| /* TODO : implement prefetch of address. */ |
| } |
| |
| /* Prefetch scaled or unscaled zero- or sign-extended 32-bit register offset. */ |
| static void |
| prfm_scale_ext (sim_cpu *cpu, Scaling scaling, Extension extension) |
| { |
| /* instr[4,0] = prfop : 00000 ==> PLDL1KEEP, 00001 ==> PLDL1STRM, |
| 00010 ==> PLDL2KEEP, 00001 ==> PLDL2STRM, |
| 00100 ==> PLDL3KEEP, 00101 ==> PLDL3STRM, |
| 10000 ==> PSTL1KEEP, 10001 ==> PSTL1STRM, |
| 10010 ==> PSTL2KEEP, 10001 ==> PSTL2STRM, |
| 10100 ==> PSTL3KEEP, 10101 ==> PSTL3STRM, |
| ow ==> UNALLOC |
| rn may reference SP, rm may only reference ZR |
| PrfOp prfop = prfop (instr, 4, 0); |
| uint64_t base = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| int64_t extended = extend (aarch64_get_reg_u32 (cpu, rm, NO_SP), |
| extension); |
| uint64_t displacement = OPT_SCALE (extended, 64, scaling); |
| uint64_t address = base + displacement. */ |
| |
| /* TODO : implement prefetch of address */ |
| } |
| |
| /* 64 bit pc-relative prefetch. */ |
| static void |
| prfm_pcrel (sim_cpu *cpu, int32_t offset) |
| { |
| /* instr[4,0] = prfop : 00000 ==> PLDL1KEEP, 00001 ==> PLDL1STRM, |
| 00010 ==> PLDL2KEEP, 00001 ==> PLDL2STRM, |
| 00100 ==> PLDL3KEEP, 00101 ==> PLDL3STRM, |
| 10000 ==> PSTL1KEEP, 10001 ==> PSTL1STRM, |
| 10010 ==> PSTL2KEEP, 10001 ==> PSTL2STRM, |
| 10100 ==> PSTL3KEEP, 10101 ==> PSTL3STRM, |
| ow ==> UNALLOC |
| PrfOp prfop = prfop (instr, 4, 0); |
| uint64_t address = aarch64_get_PC (cpu) + offset. */ |
| |
| /* TODO : implement this */ |
| } |
| |
| /* Load-store exclusive. */ |
| |
| static void |
| ldxr (sim_cpu *cpu) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| int size = INSTR (31, 30); |
| /* int ordered = INSTR (15, 15); */ |
| /* int exclusive = ! INSTR (23, 23); */ |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| switch (size) |
| { |
| case 0: |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, aarch64_get_mem_u8 (cpu, address)); |
| break; |
| case 1: |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, aarch64_get_mem_u16 (cpu, address)); |
| break; |
| case 2: |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, aarch64_get_mem_u32 (cpu, address)); |
| break; |
| case 3: |
| aarch64_set_reg_u64 (cpu, rt, NO_SP, aarch64_get_mem_u64 (cpu, address)); |
| break; |
| } |
| } |
| |
| static void |
| stxr (sim_cpu *cpu) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rt = INSTR (4, 0); |
| unsigned rs = INSTR (20, 16); |
| uint64_t address = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| int size = INSTR (31, 30); |
| uint64_t data = aarch64_get_reg_u64 (cpu, rt, NO_SP); |
| |
| switch (size) |
| { |
| case 0: aarch64_set_mem_u8 (cpu, address, data); break; |
| case 1: aarch64_set_mem_u16 (cpu, address, data); break; |
| case 2: aarch64_set_mem_u32 (cpu, address, data); break; |
| case 3: aarch64_set_mem_u64 (cpu, address, data); break; |
| } |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rs, NO_SP, 0); /* Always exclusive... */ |
| } |
| |
| static void |
| dexLoadLiteral (sim_cpu *cpu) |
| { |
| /* instr[29,27] == 011 |
| instr[25,24] == 00 |
| instr[31,30:26] = opc: 000 ==> LDRW, 001 ==> FLDRS |
| 010 ==> LDRX, 011 ==> FLDRD |
| 100 ==> LDRSW, 101 ==> FLDRQ |
| 110 ==> PRFM, 111 ==> UNALLOC |
| instr[26] ==> V : 0 ==> GReg, 1 ==> FReg |
| instr[23, 5] == simm19 */ |
| |
| /* unsigned rt = INSTR (4, 0); */ |
| uint32_t dispatch = (INSTR (31, 30) << 1) | INSTR (26, 26); |
| int32_t imm = simm32 (aarch64_get_instr (cpu), 23, 5); |
| |
| switch (dispatch) |
| { |
| case 0: ldr32_pcrel (cpu, imm); break; |
| case 1: fldrs_pcrel (cpu, imm); break; |
| case 2: ldr_pcrel (cpu, imm); break; |
| case 3: fldrd_pcrel (cpu, imm); break; |
| case 4: ldrsw_pcrel (cpu, imm); break; |
| case 5: fldrq_pcrel (cpu, imm); break; |
| case 6: prfm_pcrel (cpu, imm); break; |
| case 7: |
| default: |
| HALT_UNALLOC; |
| } |
| } |
| |
| /* Immediate arithmetic |
| The aimm argument is a 12 bit unsigned value or a 12 bit unsigned |
| value left shifted by 12 bits (done at decode). |
| |
| N.B. the register args (dest, source) can normally be Xn or SP. |
| the exception occurs for flag setting instructions which may |
| only use Xn for the output (dest). */ |
| |
| /* 32 bit add immediate. */ |
| static void |
| add32 (sim_cpu *cpu, uint32_t aimm) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, SP_OK, |
| aarch64_get_reg_u32 (cpu, rn, SP_OK) + aimm); |
| } |
| |
| /* 64 bit add immediate. */ |
| static void |
| add64 (sim_cpu *cpu, uint32_t aimm) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, SP_OK, |
| aarch64_get_reg_u64 (cpu, rn, SP_OK) + aimm); |
| } |
| |
| static void |
| set_flags_for_add32 (sim_cpu *cpu, int32_t value1, int32_t value2) |
| { |
| int32_t result = value1 + value2; |
| int64_t sresult = (int64_t) value1 + (int64_t) value2; |
| uint64_t uresult = (uint64_t)(uint32_t) value1 |
| + (uint64_t)(uint32_t) value2; |
| uint32_t flags = 0; |
| |
| if (result == 0) |
| flags |= Z; |
| |
| if (result & (1 << 31)) |
| flags |= N; |
| |
| if (uresult != (uint32_t)uresult) |
| flags |= C; |
| |
| if (sresult != (int32_t)sresult) |
| flags |= V; |
| |
| aarch64_set_CPSR (cpu, flags); |
| } |
| |
| #define NEG(a) (((a) & signbit) == signbit) |
| #define POS(a) (((a) & signbit) == 0) |
| |
| static void |
| set_flags_for_add64 (sim_cpu *cpu, uint64_t value1, uint64_t value2) |
| { |
| uint64_t result = value1 + value2; |
| uint32_t flags = 0; |
| uint64_t signbit = 1ULL << 63; |
| |
| if (result == 0) |
| flags |= Z; |
| |
| if (NEG (result)) |
| flags |= N; |
| |
| if ( (NEG (value1) && NEG (value2)) |
| || (NEG (value1) && POS (result)) |
| || (NEG (value2) && POS (result))) |
| flags |= C; |
| |
| if ( (NEG (value1) && NEG (value2) && POS (result)) |
| || (POS (value1) && POS (value2) && NEG (result))) |
| flags |= V; |
| |
| aarch64_set_CPSR (cpu, flags); |
| } |
| |
| static void |
| set_flags_for_sub32 (sim_cpu *cpu, uint32_t value1, uint32_t value2) |
| { |
| uint32_t result = value1 - value2; |
| uint32_t flags = 0; |
| uint32_t signbit = 1U << 31; |
| |
| if (result == 0) |
| flags |= Z; |
| |
| if (NEG (result)) |
| flags |= N; |
| |
| if ( (NEG (value1) && POS (value2)) |
| || (NEG (value1) && POS (result)) |
| || (POS (value2) && POS (result))) |
| flags |= C; |
| |
| if ( (NEG (value1) && POS (value2) && POS (result)) |
| || (POS (value1) && NEG (value2) && NEG (result))) |
| flags |= V; |
| |
| aarch64_set_CPSR (cpu, flags); |
| } |
| |
| static void |
| set_flags_for_sub64 (sim_cpu *cpu, uint64_t value1, uint64_t value2) |
| { |
| uint64_t result = value1 - value2; |
| uint32_t flags = 0; |
| uint64_t signbit = 1ULL << 63; |
| |
| if (result == 0) |
| flags |= Z; |
| |
| if (NEG (result)) |
| flags |= N; |
| |
| if ( (NEG (value1) && POS (value2)) |
| || (NEG (value1) && POS (result)) |
| || (POS (value2) && POS (result))) |
| flags |= C; |
| |
| if ( (NEG (value1) && POS (value2) && POS (result)) |
| || (POS (value1) && NEG (value2) && NEG (result))) |
| flags |= V; |
| |
| aarch64_set_CPSR (cpu, flags); |
| } |
| |
| static void |
| set_flags_for_binop32 (sim_cpu *cpu, uint32_t result) |
| { |
| uint32_t flags = 0; |
| |
| if (result == 0) |
| flags |= Z; |
| else |
| flags &= ~ Z; |
| |
| if (result & (1 << 31)) |
| flags |= N; |
| else |
| flags &= ~ N; |
| |
| aarch64_set_CPSR (cpu, flags); |
| } |
| |
| static void |
| set_flags_for_binop64 (sim_cpu *cpu, uint64_t result) |
| { |
| uint32_t flags = 0; |
| |
| if (result == 0) |
| flags |= Z; |
| else |
| flags &= ~ Z; |
| |
| if (result & (1ULL << 63)) |
| flags |= N; |
| else |
| flags &= ~ N; |
| |
| aarch64_set_CPSR (cpu, flags); |
| } |
| |
| /* 32 bit add immediate set flags. */ |
| static void |
| adds32 (sim_cpu *cpu, uint32_t aimm) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| /* TODO : do we need to worry about signs here? */ |
| int32_t value1 = aarch64_get_reg_s32 (cpu, rn, SP_OK); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, value1 + aimm); |
| set_flags_for_add32 (cpu, value1, aimm); |
| } |
| |
| /* 64 bit add immediate set flags. */ |
| static void |
| adds64 (sim_cpu *cpu, uint32_t aimm) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| uint64_t value1 = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| uint64_t value2 = aimm; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, value1 + value2); |
| set_flags_for_add64 (cpu, value1, value2); |
| } |
| |
| /* 32 bit sub immediate. */ |
| static void |
| sub32 (sim_cpu *cpu, uint32_t aimm) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, SP_OK, |
| aarch64_get_reg_u32 (cpu, rn, SP_OK) - aimm); |
| } |
| |
| /* 64 bit sub immediate. */ |
| static void |
| sub64 (sim_cpu *cpu, uint32_t aimm) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, SP_OK, |
| aarch64_get_reg_u64 (cpu, rn, SP_OK) - aimm); |
| } |
| |
| /* 32 bit sub immediate set flags. */ |
| static void |
| subs32 (sim_cpu *cpu, uint32_t aimm) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| uint32_t value1 = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| uint32_t value2 = aimm; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, value1 - value2); |
| set_flags_for_sub32 (cpu, value1, value2); |
| } |
| |
| /* 64 bit sub immediate set flags. */ |
| static void |
| subs64 (sim_cpu *cpu, uint32_t aimm) |
| { |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| uint64_t value1 = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| uint32_t value2 = aimm; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, value1 - value2); |
| set_flags_for_sub64 (cpu, value1, value2); |
| } |
| |
| /* Data Processing Register. */ |
| |
| /* First two helpers to perform the shift operations. */ |
| |
| static inline uint32_t |
| shifted32 (uint32_t value, Shift shift, uint32_t count) |
| { |
| switch (shift) |
| { |
| default: |
| case LSL: |
| return (value << count); |
| case LSR: |
| return (value >> count); |
| case ASR: |
| { |
| int32_t svalue = value; |
| return (svalue >> count); |
| } |
| case ROR: |
| { |
| uint32_t top = value >> count; |
| uint32_t bottom = value << (32 - count); |
| return (bottom | top); |
| } |
| } |
| } |
| |
| static inline uint64_t |
| shifted64 (uint64_t value, Shift shift, uint32_t count) |
| { |
| switch (shift) |
| { |
| default: |
| case LSL: |
| return (value << count); |
| case LSR: |
| return (value >> count); |
| case ASR: |
| { |
| int64_t svalue = value; |
| return (svalue >> count); |
| } |
| case ROR: |
| { |
| uint64_t top = value >> count; |
| uint64_t bottom = value << (64 - count); |
| return (bottom | top); |
| } |
| } |
| } |
| |
| /* Arithmetic shifted register. |
| These allow an optional LSL, ASR or LSR to the second source |
| register with a count up to the register bit count. |
| |
| N.B register args may not be SP. */ |
| |
| /* 32 bit ADD shifted register. */ |
| static void |
| add32_shift (sim_cpu *cpu, Shift shift, uint32_t count) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, |
| aarch64_get_reg_u32 (cpu, rn, NO_SP) |
| + shifted32 (aarch64_get_reg_u32 (cpu, rm, NO_SP), |
| shift, count)); |
| } |
| |
| /* 64 bit ADD shifted register. */ |
| static void |
| add64_shift (sim_cpu *cpu, Shift shift, uint32_t count) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, |
| aarch64_get_reg_u64 (cpu, rn, NO_SP) |
| + shifted64 (aarch64_get_reg_u64 (cpu, rm, NO_SP), |
| shift, count)); |
| } |
| |
| /* 32 bit ADD shifted register setting flags. */ |
| static void |
| adds32_shift (sim_cpu *cpu, Shift shift, uint32_t count) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| uint32_t value1 = aarch64_get_reg_u32 (cpu, rn, NO_SP); |
| uint32_t value2 = shifted32 (aarch64_get_reg_u32 (cpu, rm, NO_SP), |
| shift, count); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, value1 + value2); |
| set_flags_for_add32 (cpu, value1, value2); |
| } |
| |
| /* 64 bit ADD shifted register setting flags. */ |
| static void |
| adds64_shift (sim_cpu *cpu, Shift shift, uint32_t count) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| uint64_t value1 = aarch64_get_reg_u64 (cpu, rn, NO_SP); |
| uint64_t value2 = shifted64 (aarch64_get_reg_u64 (cpu, rm, NO_SP), |
| shift, count); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, value1 + value2); |
| set_flags_for_add64 (cpu, value1, value2); |
| } |
| |
| /* 32 bit SUB shifted register. */ |
| static void |
| sub32_shift (sim_cpu *cpu, Shift shift, uint32_t count) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, |
| aarch64_get_reg_u32 (cpu, rn, NO_SP) |
| - shifted32 (aarch64_get_reg_u32 (cpu, rm, NO_SP), |
| shift, count)); |
| } |
| |
| /* 64 bit SUB shifted register. */ |
| static void |
| sub64_shift (sim_cpu *cpu, Shift shift, uint32_t count) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, |
| aarch64_get_reg_u64 (cpu, rn, NO_SP) |
| - shifted64 (aarch64_get_reg_u64 (cpu, rm, NO_SP), |
| shift, count)); |
| } |
| |
| /* 32 bit SUB shifted register setting flags. */ |
| static void |
| subs32_shift (sim_cpu *cpu, Shift shift, uint32_t count) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| uint32_t value1 = aarch64_get_reg_u32 (cpu, rn, NO_SP); |
| uint32_t value2 = shifted32 (aarch64_get_reg_u32 (cpu, rm, NO_SP), |
| shift, count); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, value1 - value2); |
| set_flags_for_sub32 (cpu, value1, value2); |
| } |
| |
| /* 64 bit SUB shifted register setting flags. */ |
| static void |
| subs64_shift (sim_cpu *cpu, Shift shift, uint32_t count) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| uint64_t value1 = aarch64_get_reg_u64 (cpu, rn, NO_SP); |
| uint64_t value2 = shifted64 (aarch64_get_reg_u64 (cpu, rm, NO_SP), |
| shift, count); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, value1 - value2); |
| set_flags_for_sub64 (cpu, value1, value2); |
| } |
| |
| /* First a couple more helpers to fetch the |
| relevant source register element either |
| sign or zero extended as required by the |
| extension value. */ |
| |
| static uint32_t |
| extreg32 (sim_cpu *cpu, unsigned int lo, Extension extension) |
| { |
| switch (extension) |
| { |
| case UXTB: return aarch64_get_reg_u8 (cpu, lo, NO_SP); |
| case UXTH: return aarch64_get_reg_u16 (cpu, lo, NO_SP); |
| case UXTW: /* Fall through. */ |
| case UXTX: return aarch64_get_reg_u32 (cpu, lo, NO_SP); |
| case SXTB: return aarch64_get_reg_s8 (cpu, lo, NO_SP); |
| case SXTH: return aarch64_get_reg_s16 (cpu, lo, NO_SP); |
| case SXTW: /* Fall through. */ |
| case SXTX: /* Fall through. */ |
| default: return aarch64_get_reg_s32 (cpu, lo, NO_SP); |
| } |
| } |
| |
| static uint64_t |
| extreg64 (sim_cpu *cpu, unsigned int lo, Extension extension) |
| { |
| switch (extension) |
| { |
| case UXTB: return aarch64_get_reg_u8 (cpu, lo, NO_SP); |
| case UXTH: return aarch64_get_reg_u16 (cpu, lo, NO_SP); |
| case UXTW: return aarch64_get_reg_u32 (cpu, lo, NO_SP); |
| case UXTX: return aarch64_get_reg_u64 (cpu, lo, NO_SP); |
| case SXTB: return aarch64_get_reg_s8 (cpu, lo, NO_SP); |
| case SXTH: return aarch64_get_reg_s16 (cpu, lo, NO_SP); |
| case SXTW: return aarch64_get_reg_s32 (cpu, lo, NO_SP); |
| case SXTX: |
| default: return aarch64_get_reg_s64 (cpu, lo, NO_SP); |
| } |
| } |
| |
| /* Arithmetic extending register |
| These allow an optional sign extension of some portion of the |
| second source register followed by an optional left shift of |
| between 1 and 4 bits (i.e. a shift of 0-4 bits???) |
| |
| N.B output (dest) and first input arg (source) may normally be Xn |
| or SP. However, for flag setting operations dest can only be |
| Xn. Second input registers are always Xn. */ |
| |
| /* 32 bit ADD extending register. */ |
| static void |
| add32_ext (sim_cpu *cpu, Extension extension, uint32_t shift) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, SP_OK, |
| aarch64_get_reg_u32 (cpu, rn, SP_OK) |
| + (extreg32 (cpu, rm, extension) << shift)); |
| } |
| |
| /* 64 bit ADD extending register. |
| N.B. This subsumes the case with 64 bit source2 and UXTX #n or LSL #0. */ |
| static void |
| add64_ext (sim_cpu *cpu, Extension extension, uint32_t shift) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, SP_OK, |
| aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| + (extreg64 (cpu, rm, extension) << shift)); |
| } |
| |
| /* 32 bit ADD extending register setting flags. */ |
| static void |
| adds32_ext (sim_cpu *cpu, Extension extension, uint32_t shift) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| uint32_t value1 = aarch64_get_reg_u32 (cpu, rn, SP_OK); |
| uint32_t value2 = extreg32 (cpu, rm, extension) << shift; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, value1 + value2); |
| set_flags_for_add32 (cpu, value1, value2); |
| } |
| |
| /* 64 bit ADD extending register setting flags */ |
| /* N.B. this subsumes the case with 64 bit source2 and UXTX #n or LSL #0 */ |
| static void |
| adds64_ext (sim_cpu *cpu, Extension extension, uint32_t shift) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| uint64_t value1 = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| uint64_t value2 = extreg64 (cpu, rm, extension) << shift; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, value1 + value2); |
| set_flags_for_add64 (cpu, value1, value2); |
| } |
| |
| /* 32 bit SUB extending register. */ |
| static void |
| sub32_ext (sim_cpu *cpu, Extension extension, uint32_t shift) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, SP_OK, |
| aarch64_get_reg_u32 (cpu, rn, SP_OK) |
| - (extreg32 (cpu, rm, extension) << shift)); |
| } |
| |
| /* 64 bit SUB extending register. */ |
| /* N.B. this subsumes the case with 64 bit source2 and UXTX #n or LSL #0. */ |
| static void |
| sub64_ext (sim_cpu *cpu, Extension extension, uint32_t shift) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, SP_OK, |
| aarch64_get_reg_u64 (cpu, rn, SP_OK) |
| - (extreg64 (cpu, rm, extension) << shift)); |
| } |
| |
| /* 32 bit SUB extending register setting flags. */ |
| static void |
| subs32_ext (sim_cpu *cpu, Extension extension, uint32_t shift) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| uint32_t value1 = aarch64_get_reg_u32 (cpu, rn, SP_OK); |
| uint32_t value2 = extreg32 (cpu, rm, extension) << shift; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, value1 - value2); |
| set_flags_for_sub32 (cpu, value1, value2); |
| } |
| |
| /* 64 bit SUB extending register setting flags */ |
| /* N.B. this subsumes the case with 64 bit source2 and UXTX #n or LSL #0 */ |
| static void |
| subs64_ext (sim_cpu *cpu, Extension extension, uint32_t shift) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| uint64_t value1 = aarch64_get_reg_u64 (cpu, rn, SP_OK); |
| uint64_t value2 = extreg64 (cpu, rm, extension) << shift; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, value1 - value2); |
| set_flags_for_sub64 (cpu, value1, value2); |
| } |
| |
| static void |
| dexAddSubtractImmediate (sim_cpu *cpu) |
| { |
| /* instr[31] = size : 0 ==> 32 bit, 1 ==> 64 bit |
| instr[30] = op : 0 ==> ADD, 1 ==> SUB |
| instr[29] = set : 0 ==> no flags, 1 ==> set flags |
| instr[28,24] = 10001 |
| instr[23,22] = shift : 00 == LSL#0, 01 = LSL#12 1x = UNALLOC |
| instr[21,10] = uimm12 |
| instr[9,5] = Rn |
| instr[4,0] = Rd */ |
| |
| /* N.B. the shift is applied at decode before calling the add/sub routine. */ |
| uint32_t shift = INSTR (23, 22); |
| uint32_t imm = INSTR (21, 10); |
| uint32_t dispatch = INSTR (31, 29); |
| |
| NYI_assert (28, 24, 0x11); |
| |
| if (shift > 1) |
| HALT_UNALLOC; |
| |
| if (shift) |
| imm <<= 12; |
| |
| switch (dispatch) |
| { |
| case 0: add32 (cpu, imm); break; |
| case 1: adds32 (cpu, imm); break; |
| case 2: sub32 (cpu, imm); break; |
| case 3: subs32 (cpu, imm); break; |
| case 4: add64 (cpu, imm); break; |
| case 5: adds64 (cpu, imm); break; |
| case 6: sub64 (cpu, imm); break; |
| case 7: subs64 (cpu, imm); break; |
| } |
| } |
| |
| static void |
| dexAddSubtractShiftedRegister (sim_cpu *cpu) |
| { |
| /* instr[31] = size : 0 ==> 32 bit, 1 ==> 64 bit |
| instr[30,29] = op : 00 ==> ADD, 01 ==> ADDS, 10 ==> SUB, 11 ==> SUBS |
| instr[28,24] = 01011 |
| instr[23,22] = shift : 0 ==> LSL, 1 ==> LSR, 2 ==> ASR, 3 ==> UNALLOC |
| instr[21] = 0 |
| instr[20,16] = Rm |
| instr[15,10] = count : must be 0xxxxx for 32 bit |
| instr[9,5] = Rn |
| instr[4,0] = Rd */ |
| |
| uint32_t size = INSTR (31, 31); |
| uint32_t count = INSTR (15, 10); |
| Shift shiftType = INSTR (23, 22); |
| |
| NYI_assert (28, 24, 0x0B); |
| NYI_assert (21, 21, 0); |
| |
| /* Shift encoded as ROR is unallocated. */ |
| if (shiftType == ROR) |
| HALT_UNALLOC; |
| |
| /* 32 bit operations must have count[5] = 0 |
| or else we have an UNALLOC. */ |
| if (size == 0 && uimm (count, 5, 5)) |
| HALT_UNALLOC; |
| |
| /* Dispatch on size:op i.e instr [31,29]. */ |
| switch (INSTR (31, 29)) |
| { |
| case 0: add32_shift (cpu, shiftType, count); break; |
| case 1: adds32_shift (cpu, shiftType, count); break; |
| case 2: sub32_shift (cpu, shiftType, count); break; |
| case 3: subs32_shift (cpu, shiftType, count); break; |
| case 4: add64_shift (cpu, shiftType, count); break; |
| case 5: adds64_shift (cpu, shiftType, count); break; |
| case 6: sub64_shift (cpu, shiftType, count); break; |
| case 7: subs64_shift (cpu, shiftType, count); break; |
| } |
| } |
| |
| static void |
| dexAddSubtractExtendedRegister (sim_cpu *cpu) |
| { |
| /* instr[31] = size : 0 ==> 32 bit, 1 ==> 64 bit |
| instr[30] = op : 0 ==> ADD, 1 ==> SUB |
| instr[29] = set? : 0 ==> no flags, 1 ==> set flags |
| instr[28,24] = 01011 |
| instr[23,22] = opt : 0 ==> ok, 1,2,3 ==> UNALLOC |
| instr[21] = 1 |
| instr[20,16] = Rm |
| instr[15,13] = option : 000 ==> UXTB, 001 ==> UXTH, |
| 000 ==> LSL|UXTW, 001 ==> UXTZ, |
| 000 ==> SXTB, 001 ==> SXTH, |
| 000 ==> SXTW, 001 ==> SXTX, |
| instr[12,10] = shift : 0,1,2,3,4 ==> ok, 5,6,7 ==> UNALLOC |
| instr[9,5] = Rn |
| instr[4,0] = Rd */ |
| |
| Extension extensionType = INSTR (15, 13); |
| uint32_t shift = INSTR (12, 10); |
| |
| NYI_assert (28, 24, 0x0B); |
| NYI_assert (21, 21, 1); |
| |
| /* Shift may not exceed 4. */ |
| if (shift > 4) |
| HALT_UNALLOC; |
| |
| /* Dispatch on size:op:set?. */ |
| switch (INSTR (31, 29)) |
| { |
| case 0: add32_ext (cpu, extensionType, shift); break; |
| case 1: adds32_ext (cpu, extensionType, shift); break; |
| case 2: sub32_ext (cpu, extensionType, shift); break; |
| case 3: subs32_ext (cpu, extensionType, shift); break; |
| case 4: add64_ext (cpu, extensionType, shift); break; |
| case 5: adds64_ext (cpu, extensionType, shift); break; |
| case 6: sub64_ext (cpu, extensionType, shift); break; |
| case 7: subs64_ext (cpu, extensionType, shift); break; |
| } |
| } |
| |
| /* Conditional data processing |
| Condition register is implicit 3rd source. */ |
| |
| /* 32 bit add with carry. */ |
| /* N.B register args may not be SP. */ |
| |
| static void |
| adc32 (sim_cpu *cpu) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, |
| aarch64_get_reg_u32 (cpu, rn, NO_SP) |
| + aarch64_get_reg_u32 (cpu, rm, NO_SP) |
| + IS_SET (C)); |
| } |
| |
| /* 64 bit add with carry */ |
| static void |
| adc64 (sim_cpu *cpu) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, |
| aarch64_get_reg_u64 (cpu, rn, NO_SP) |
| + aarch64_get_reg_u64 (cpu, rm, NO_SP) |
| + IS_SET (C)); |
| } |
| |
| /* 32 bit add with carry setting flags. */ |
| static void |
| adcs32 (sim_cpu *cpu) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| uint32_t value1 = aarch64_get_reg_u32 (cpu, rn, NO_SP); |
| uint32_t value2 = aarch64_get_reg_u32 (cpu, rm, NO_SP); |
| uint32_t carry = IS_SET (C); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, value1 + value2 + carry); |
| set_flags_for_add32 (cpu, value1, value2 + carry); |
| } |
| |
| /* 64 bit add with carry setting flags. */ |
| static void |
| adcs64 (sim_cpu *cpu) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| uint64_t value1 = aarch64_get_reg_u64 (cpu, rn, NO_SP); |
| uint64_t value2 = aarch64_get_reg_u64 (cpu, rm, NO_SP); |
| uint64_t carry = IS_SET (C); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, value1 + value2 + carry); |
| set_flags_for_add64 (cpu, value1, value2 + carry); |
| } |
| |
| /* 32 bit sub with carry. */ |
| static void |
| sbc32 (sim_cpu *cpu) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); /* ngc iff rn == 31. */ |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, |
| aarch64_get_reg_u32 (cpu, rn, NO_SP) |
| - aarch64_get_reg_u32 (cpu, rm, NO_SP) |
| - 1 + IS_SET (C)); |
| } |
| |
| /* 64 bit sub with carry */ |
| static void |
| sbc64 (sim_cpu *cpu) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, |
| aarch64_get_reg_u64 (cpu, rn, NO_SP) |
| - aarch64_get_reg_u64 (cpu, rm, NO_SP) |
| - 1 + IS_SET (C)); |
| } |
| |
| /* 32 bit sub with carry setting flags */ |
| static void |
| sbcs32 (sim_cpu *cpu) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| uint32_t value1 = aarch64_get_reg_u32 (cpu, rn, NO_SP); |
| uint32_t value2 = aarch64_get_reg_u32 (cpu, rm, NO_SP); |
| uint32_t carry = IS_SET (C); |
| uint32_t result = value1 - value2 + 1 - carry; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, result); |
| set_flags_for_sub32 (cpu, value1, value2 + 1 - carry); |
| } |
| |
| /* 64 bit sub with carry setting flags */ |
| static void |
| sbcs64 (sim_cpu *cpu) |
| { |
| unsigned rm = INSTR (20, 16); |
| unsigned rn = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| |
| uint64_t value1 = aarch64_get_reg_u64 (cpu, rn, NO_SP); |
| uint64_t value2 = aarch64_get_reg_u64 (cpu, rm, NO_SP); |
| uint64_t carry = IS_SET (C); |
| uint64_t result = value1 - value2 + 1 - carry; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, result); |
| set_flags_for_sub64 (cpu, value1, value2 + 1 - carry); |
| } |
| |
| static void |
| dexAddSubtractWithCarry (sim_cpu *cpu) |
| { |
| /* instr[31] = size : 0 ==> 32 bit, 1 ==> 64 bit |
| instr[30] = op : 0 ==> ADC, 1 ==> SBC |
| instr[29] = set? : 0 ==> no flags, 1 ==> set flags |
| instr[28,21] = 1 1010 000 |
| instr[20,16] = Rm |
| instr[15,10] = op2 : 00000 ==> ok, ow ==> UNALLOC |
| instr[9,5] = Rn |
| instr[4,0] = Rd */ |
| |
| uint32_t op2 = INSTR (15, 10); |
| |
| NYI_assert (28, 21, 0xD0); |
| |
| if (op2 != 0) |
| HALT_UNALLOC; |
| |
| /* Dispatch on size:op:set?. */ |
| switch (INSTR (31, 29)) |
| { |
| case 0: adc32 (cpu); break; |
| case 1: adcs32 (cpu); break; |
| case 2: sbc32 (cpu); break; |
| case 3: sbcs32 (cpu); break; |
| case 4: adc64 (cpu); break; |
| case 5: adcs64 (cpu); break; |
| case 6: sbc64 (cpu); break; |
| case 7: sbcs64 (cpu); break; |
| } |
| } |
| |
| static uint32_t |
| testConditionCode (sim_cpu *cpu, CondCode cc) |
| { |
| /* This should be reduceable to branchless logic |
| by some careful testing of bits in CC followed |
| by the requisite masking and combining of bits |
| from the flag register. |
| |
| For now we do it with a switch. */ |
| int res; |
| |
| switch (cc) |
| { |
| case EQ: res = IS_SET (Z); break; |
| case NE: res = IS_CLEAR (Z); break; |
| case CS: res = IS_SET (C); break; |
| case CC: res = IS_CLEAR (C); break; |
| case MI: res = IS_SET (N); break; |
| case PL: res = IS_CLEAR (N); break; |
| case VS: res = IS_SET (V); break; |
| case VC: res = IS_CLEAR (V); break; |
| case HI: res = IS_SET (C) && IS_CLEAR (Z); break; |
| case LS: res = IS_CLEAR (C) || IS_SET (Z); break; |
| case GE: res = IS_SET (N) == IS_SET (V); break; |
| case LT: res = IS_SET (N) != IS_SET (V); break; |
| case GT: res = IS_CLEAR (Z) && (IS_SET (N) == IS_SET (V)); break; |
| case LE: res = IS_SET (Z) || (IS_SET (N) != IS_SET (V)); break; |
| case AL: |
| case NV: |
| default: |
| res = 1; |
| break; |
| } |
| return res; |
| } |
| |
| static void |
| CondCompare (sim_cpu *cpu) /* aka: ccmp and ccmn */ |
| { |
| /* instr[31] = size : 0 ==> 32 bit, 1 ==> 64 bit |
| instr[30] = compare with positive (1) or negative value (0) |
| instr[29,21] = 1 1101 0010 |
| instr[20,16] = Rm or const |
| instr[15,12] = cond |
| instr[11] = compare reg (0) or const (1) |
| instr[10] = 0 |
| instr[9,5] = Rn |
| instr[4] = 0 |
| instr[3,0] = value for CPSR bits if the comparison does not take place. */ |
| signed int negate; |
| unsigned rm; |
| unsigned rn; |
| |
| NYI_assert (29, 21, 0x1d2); |
| NYI_assert (10, 10, 0); |
| NYI_assert (4, 4, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| if (! testConditionCode (cpu, INSTR (15, 12))) |
| { |
| aarch64_set_CPSR (cpu, INSTR (3, 0)); |
| return; |
| } |
| |
| negate = INSTR (30, 30) ? 1 : -1; |
| rm = INSTR (20, 16); |
| rn = INSTR ( 9, 5); |
| |
| if (INSTR (31, 31)) |
| { |
| if (INSTR (11, 11)) |
| set_flags_for_sub64 (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK), |
| negate * (uint64_t) rm); |
| else |
| set_flags_for_sub64 (cpu, aarch64_get_reg_u64 (cpu, rn, SP_OK), |
| negate * aarch64_get_reg_u64 (cpu, rm, SP_OK)); |
| } |
| else |
| { |
| if (INSTR (11, 11)) |
| set_flags_for_sub32 (cpu, aarch64_get_reg_u32 (cpu, rn, SP_OK), |
| negate * rm); |
| else |
| set_flags_for_sub32 (cpu, aarch64_get_reg_u32 (cpu, rn, SP_OK), |
| negate * aarch64_get_reg_u32 (cpu, rm, SP_OK)); |
| } |
| } |
| |
| static void |
| do_vec_MOV_whole_vector (sim_cpu *cpu) |
| { |
| /* MOV Vd.T, Vs.T (alias for ORR Vd.T, Vn.T, Vm.T where Vn == Vm) |
| |
| instr[31] = 0 |
| instr[30] = half(0)/full(1) |
| instr[29,21] = 001110101 |
| instr[20,16] = Vs |
| instr[15,10] = 000111 |
| instr[9,5] = Vs |
| instr[4,0] = Vd */ |
| |
| unsigned vs = INSTR (9, 5); |
| unsigned vd = INSTR (4, 0); |
| |
| NYI_assert (29, 21, 0x075); |
| NYI_assert (15, 10, 0x07); |
| |
| if (INSTR (20, 16) != vs) |
| HALT_NYI; |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| if (INSTR (30, 30)) |
| aarch64_set_vec_u64 (cpu, vd, 1, aarch64_get_vec_u64 (cpu, vs, 1)); |
| |
| aarch64_set_vec_u64 (cpu, vd, 0, aarch64_get_vec_u64 (cpu, vs, 0)); |
| } |
| |
| static void |
| do_vec_SMOV_into_scalar (sim_cpu *cpu) |
| { |
| /* instr[31] = 0 |
| instr[30] = word(0)/long(1) |
| instr[29,21] = 00 1110 000 |
| instr[20,16] = element size and index |
| instr[15,10] = 00 0010 11 |
| instr[9,5] = V source |
| instr[4,0] = R dest */ |
| |
| unsigned vs = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| unsigned imm5 = INSTR (20, 16); |
| unsigned full = INSTR (30, 30); |
| int size, index; |
| |
| NYI_assert (29, 21, 0x070); |
| NYI_assert (15, 10, 0x0B); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| |
| if (imm5 & 0x1) |
| { |
| size = 0; |
| index = (imm5 >> 1) & 0xF; |
| } |
| else if (imm5 & 0x2) |
| { |
| size = 1; |
| index = (imm5 >> 2) & 0x7; |
| } |
| else if (full && (imm5 & 0x4)) |
| { |
| size = 2; |
| index = (imm5 >> 3) & 0x3; |
| } |
| else |
| HALT_UNALLOC; |
| |
| switch (size) |
| { |
| case 0: |
| if (full) |
| aarch64_set_reg_s64 (cpu, rd, NO_SP, |
| aarch64_get_vec_s8 (cpu, vs, index)); |
| else |
| aarch64_set_reg_s32 (cpu, rd, NO_SP, |
| aarch64_get_vec_s8 (cpu, vs, index)); |
| break; |
| |
| case 1: |
| if (full) |
| aarch64_set_reg_s64 (cpu, rd, NO_SP, |
| aarch64_get_vec_s16 (cpu, vs, index)); |
| else |
| aarch64_set_reg_s32 (cpu, rd, NO_SP, |
| aarch64_get_vec_s16 (cpu, vs, index)); |
| break; |
| |
| case 2: |
| aarch64_set_reg_s64 (cpu, rd, NO_SP, |
| aarch64_get_vec_s32 (cpu, vs, index)); |
| break; |
| |
| default: |
| HALT_UNALLOC; |
| } |
| } |
| |
| static void |
| do_vec_UMOV_into_scalar (sim_cpu *cpu) |
| { |
| /* instr[31] = 0 |
| instr[30] = word(0)/long(1) |
| instr[29,21] = 00 1110 000 |
| instr[20,16] = element size and index |
| instr[15,10] = 00 0011 11 |
| instr[9,5] = V source |
| instr[4,0] = R dest */ |
| |
| unsigned vs = INSTR (9, 5); |
| unsigned rd = INSTR (4, 0); |
| unsigned imm5 = INSTR (20, 16); |
| unsigned full = INSTR (30, 30); |
| int size, index; |
| |
| NYI_assert (29, 21, 0x070); |
| NYI_assert (15, 10, 0x0F); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| |
| if (!full) |
| { |
| if (imm5 & 0x1) |
| { |
| size = 0; |
| index = (imm5 >> 1) & 0xF; |
| } |
| else if (imm5 & 0x2) |
| { |
| size = 1; |
| index = (imm5 >> 2) & 0x7; |
| } |
| else if (imm5 & 0x4) |
| { |
| size = 2; |
| index = (imm5 >> 3) & 0x3; |
| } |
| else |
| HALT_UNALLOC; |
| } |
| else if (imm5 & 0x8) |
| { |
| size = 3; |
| index = (imm5 >> 4) & 0x1; |
| } |
| else |
| HALT_UNALLOC; |
| |
| switch (size) |
| { |
| case 0: |
| aarch64_set_reg_u32 (cpu, rd, NO_SP, |
| aarch64_get_vec_u8 (cpu, vs, index)); |
| break; |
| |
| case 1: |
| aarch64_set_reg_u32 (cpu, rd, NO_SP, |
| aarch64_get_vec_u16 (cpu, vs, index)); |
| break; |
| |
| case 2: |
| aarch64_set_reg_u32 (cpu, rd, NO_SP, |
| aarch64_get_vec_u32 (cpu, vs, index)); |
| break; |
| |
| case 3: |
| aarch64_set_reg_u64 (cpu, rd, NO_SP, |
| aarch64_get_vec_u64 (cpu, vs, index)); |
| break; |
| |
| default: |
| HALT_UNALLOC; |
| } |
| } |
| |
| static void |
| do_vec_INS (sim_cpu *cpu) |
| { |
| /* instr[31,21] = 01001110000 |
| instr[20,16] = element size and index |
| instr[15,10] = 000111 |
| instr[9,5] = W source |
| instr[4,0] = V dest */ |
| |
| int index; |
| unsigned rs = INSTR (9, 5); |
| unsigned vd = INSTR (4, 0); |
| |
| NYI_assert (31, 21, 0x270); |
| NYI_assert (15, 10, 0x07); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| if (INSTR (16, 16)) |
| { |
| index = INSTR (20, 17); |
| aarch64_set_vec_u8 (cpu, vd, index, |
| aarch64_get_reg_u8 (cpu, rs, NO_SP)); |
| } |
| else if (INSTR (17, 17)) |
| { |
| index = INSTR (20, 18); |
| aarch64_set_vec_u16 (cpu, vd, index, |
| aarch64_get_reg_u16 (cpu, rs, NO_SP)); |
| } |
| else if (INSTR (18, 18)) |
| { |
| index = INSTR (20, 19); |
| aarch64_set_vec_u32 (cpu, vd, index, |
| aarch64_get_reg_u32 (cpu, rs, NO_SP)); |
| } |
| else if (INSTR (19, 19)) |
| { |
| index = INSTR (20, 20); |
| aarch64_set_vec_u64 (cpu, vd, index, |
| aarch64_get_reg_u64 (cpu, rs, NO_SP)); |
| } |
| else |
| HALT_NYI; |
| } |
| |
| static void |
| do_vec_DUP_vector_into_vector (sim_cpu *cpu) |
| { |
| /* instr[31] = 0 |
| instr[30] = half(0)/full(1) |
| instr[29,21] = 00 1110 000 |
| instr[20,16] = element size and index |
| instr[15,10] = 0000 01 |
| instr[9,5] = V source |
| instr[4,0] = V dest. */ |
| |
| unsigned full = INSTR (30, 30); |
| unsigned vs = INSTR (9, 5); |
| unsigned vd = INSTR (4, 0); |
| int i, index; |
| |
| NYI_assert (29, 21, 0x070); |
| NYI_assert (15, 10, 0x01); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| if (INSTR (16, 16)) |
| { |
| index = INSTR (20, 17); |
| |
| for (i = 0; i < (full ? 16 : 8); i++) |
| aarch64_set_vec_u8 (cpu, vd, i, aarch64_get_vec_u8 (cpu, vs, index)); |
| } |
| else if (INSTR (17, 17)) |
| { |
| index = INSTR (20, 18); |
| |
| for (i = 0; i < (full ? 8 : 4); i++) |
| aarch64_set_vec_u16 (cpu, vd, i, aarch64_get_vec_u16 (cpu, vs, index)); |
| } |
| else if (INSTR (18, 18)) |
| { |
| index = INSTR (20, 19); |
| |
| for (i = 0; i < (full ? 4 : 2); i++) |
| aarch64_set_vec_u32 (cpu, vd, i, aarch64_get_vec_u32 (cpu, vs, index)); |
| } |
| else |
| { |
| if (INSTR (19, 19) == 0) |
| HALT_UNALLOC; |
| |
| if (! full) |
| HALT_UNALLOC; |
| |
| index = INSTR (20, 20); |
| |
| for (i = 0; i < 2; i++) |
| aarch64_set_vec_u64 (cpu, vd, i, aarch64_get_vec_u64 (cpu, vs, index)); |
| } |
| } |
| |
| static void |
| do_vec_TBL (sim_cpu *cpu) |
| { |
| /* instr[31] = 0 |
| instr[30] = half(0)/full(1) |
| instr[29,21] = 00 1110 000 |
| instr[20,16] = Vm |
| instr[15] = 0 |
| instr[14,13] = vec length |
| instr[12,10] = 000 |
| instr[9,5] = V start |
| instr[4,0] = V dest */ |
| |
| int full = INSTR (30, 30); |
| int len = INSTR (14, 13) + 1; |
| unsigned vm = INSTR (20, 16); |
| unsigned vn = INSTR (9, 5); |
| unsigned vd = INSTR (4, 0); |
| unsigned i; |
| |
| NYI_assert (29, 21, 0x070); |
| NYI_assert (12, 10, 0); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| for (i = 0; i < (full ? 16 : 8); i++) |
| { |
| unsigned int selector = aarch64_get_vec_u8 (cpu, vm, i); |
| uint8_t val; |
| |
| if (selector < 16) |
| val = aarch64_get_vec_u8 (cpu, vn, selector); |
| else if (selector < 32) |
| val = len < 2 ? 0 : aarch64_get_vec_u8 (cpu, vn + 1, selector - 16); |
| else if (selector < 48) |
| val = len < 3 ? 0 : aarch64_get_vec_u8 (cpu, vn + 2, selector - 32); |
| else if (selector < 64) |
| val = len < 4 ? 0 : aarch64_get_vec_u8 (cpu, vn + 3, selector - 48); |
| else |
| val = 0; |
| |
| aarch64_set_vec_u8 (cpu, vd, i, val); |
| } |
| } |
| |
| static void |
| do_vec_TRN (sim_cpu *cpu) |
| { |
| /* instr[31] = 0 |
| instr[30] = half(0)/full(1) |
| instr[29,24] = 00 1110 |
| instr[23,22] = size |
| instr[21] = 0 |
| instr[20,16] = Vm |
| instr[15] = 0 |
| instr[14] = TRN1 (0) / TRN2 (1) |
| instr[13,10] = 1010 |
| instr[9,5] = V source |
| instr[4,0] = V dest. */ |
| |
| int full = INSTR (30, 30); |
| int second = INSTR (14, 14); |
| unsigned vm = INSTR (20, 16); |
| unsigned vn = INSTR (9, 5); |
| unsigned vd = INSTR (4, 0); |
| unsigned i; |
| |
| NYI_assert (29, 24, 0x0E); |
| NYI_assert (13, 10, 0xA); |
| |
| TRACE_DECODE (cpu, "emulated at line %d", __LINE__); |
| switch (INSTR (23, 22)) |
| { |
| case 0: |
| for (i = 0; i < (full ? 8 : 4); i++) |
| { |
| aarch64_set_vec_u8 |
| (cpu, vd, i * 2, |
| aarch64_get_vec_u8 (cpu, second ? vm : vn, i * 2)); |
| aarch64_set_vec_u8 |
| (cpu, vd, 1 * 2 + 1, |
| aarch64_get_vec_u8 (cpu, second ? vn : vm, i * 2 + 1)); |
| } |
| break; |
| |
| case 1: |
| for (i = 0; i < (full ? 4 : 2); i++) |
| { |
| aarch64_set_vec_u16 |
| (cpu, vd, i * 2 |