/*
 * Copyright (C) 2014-2016 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

/*
 * For most of the comments, infos, ... see:
 * ARMv7-A/R:
 *	ARM Architecture Reference Manual
 *	ARMv7-A and ARMv7-R edition
 *	ARM DDI 0406C.c (ID051414)
 * ARMv7-M:
 *	ARMv7-M Architecture
 *	Reference Manual
 *	ARM DDI 0403E.b (ID120114)
 */

#if (CONFIG_VERSION == 6 && CONFIG_THUMB2) || 7 <= CONFIG_VERSION
static void
NAME_(insn_exec_thumb2)(struct cpssp *cpssp, uint32_t insn)
{
	uint32_t offset;
	uint32_t addr;
	uint32_t op0, op1, res;
	unsigned int op;
	unsigned int sop;
	uint64_t res64;
	uint32_t tmp32, shift;
	uint16_t reglist;
	uint8_t rd, rdlo, rdhi, rn, rm, rt;
	int p, u, l, w;
	int c, v;
	int s;

	if (! NAME_(it_true)(cpssp)) {
		return;
	}

	COUNT_INST(inst32);

	/*
	 * 32-bit Thumb instruction encoding
	 * ARMv7-M: A5-135
	 */
	switch ((insn >> 27) & 0b11) {
	case 0b00:
		/* Cannot happen. 16-bit instruction. */
		assert(0);

	case 0b01:
		switch ((insn >> 25) & 3) {
		case 0b00:
			/*
			 * Load Multiple and Store Multiple
			 * ARMv7-M: A5-142
			 * Load/store dual or exclusive, table branch
			 * ARMv7-M: A5-143
			 */
			if (! ((insn >> 22) & 1)) {
				/*
				 * Load Multiple and Store Multiple
				 * ARMv7-M: A5-142
				 *
				 * STM/STMIA/STMEA, ARMv7-M: A7-422
				 * LDM/LDMIA/LDMFD, ARMv7-M: A7-248
				 * POP, ARMv7-M: A7-348
				 * STMDB/STMFD, ARMv7-M: A7-424
				 * PUSH, ARMv7-M: A7-350
				 * LDMDB/LDMEA, ARMv7-M: A7-250
				 */
				p = (insn >> 24) & 1;
				u = (insn >> 23) & 1;
				s = 0;
				w = (insn >> 21) & 1;
				l = (insn >> 20) & 1;
				rn = (insn >> 16) & 0xf;
				reglist = (insn >> 0) & 0xffff;

				addr = NAME_(addr_mode_4_pre)(cpssp, rn);

				addr = NAME_(ldstm)(cpssp, p, u, s, l,
						addr, reglist);

				NAME_(addr_mode_4_post)(cpssp, w, rn, addr);

			} else {
				/*
				 * Load/store dual or exclusive, table branch
				 * ARMv7-M: A5-143
				 */
				assert(0); /* FIXME */
			}
			break;
		case 0b01:
			/*
			 * Data processing (shifted register)
			 * ARMv7-M: A5-148
			 */
			op = (insn >> 21) & 0b1111;
			s = (insn >> 20) & 1;
			rn = (insn >> 16) & 0xf;
			rd = (insn >> 8) & 0xf;
			rm = (insn >> 0) & 0xf;
			shift = ((insn >> 12) & 0x7) << 2;
			shift |= ((insn >> 6) & 0x3) << 0;
			sop = (insn >> 4) & 0x3;

			if ((op == 0b0010 /* ORR/MOV */
			  || op == 0b0011 /* ORN/MVN */)
			 && rn == 0xf) {
				op0 = 0;
			} else {
				op0 = NAME_(load_reg)(cpssp, rn);
			}
			op1 = NAME_(shift)(cpssp, rm, sop, shift);

			switch (op) {
			case 0b0000:
				/*
				 * AND (register), ARMv7-M: A7-201
				 * TST (register), ARMv7-M: A7-466
				 */
				res = NAME_(ands)(cpssp, op0, op1, &c, &v);
				break;

			case 0b0001:
				/*
				 * BIC (register), ARMv7-M: A7-213
				 */
				assert(rd != 0xd); /* UNPREDICTABLE */
				assert(rd != 0xf); /* UNPREDICTABLE */
				assert(rm != 0xd); /* UNPREDICTABLE */
				assert(rm != 0xf); /* UNPREDICTABLE */
				assert(rn != 0xd); /* UNPREDICTABLE */
				assert(rn != 0xf); /* UNPREDICTABLE */

				res = NAME_(bics)(cpssp, op0, op1, &c, &v);
				break;

			case 0b0010:
				/*
				 * ORR (register), ARMv7-M: A7-333
				 * MOV (register), ARMv7-M: A7-314
				 * LSL (immediate), ARMv7-M: A7-298
				 * LSR (immediate), ARMv7-M: A7-302
				 * ASR (immediate), ARMv7-M: A7-203
				 * ROR (immediate), ARMv7-M: A7-366
				 * RRX, ARMv7-M: A7-370
				 */
				res = NAME_(orrs)(cpssp, op0, op1, &c, &v);
				break;

			case 0b0011:
				/*
				 * ORN (register), ARMv7-M: A7-333
				 * MVN (register), ARMv7-M: A7-328
				 */
				res = NAME_(orns)(cpssp, op0, op1, &c, &v);
				break;

			case 0b0100:
				/*
				 * EOR (register), ARMv7-M: A7-239
				 * TEQ (register), ARMv7-M: A7-464
				 */
				res = NAME_(eors)(cpssp, op0, op1, &c, &v);
				break;

			case 0b0101:
				assert(0); /* UNDEFINED */
				break;

			case 0b0110:
				/*
				 * PKHBT, ARMv7-M: A7-338
				 * PKHTB, ARMv7-M: A7-338
				 */
				if (! ((insn >> 5) & 1)) {
					res = (op0 & 0xffff0000) | (op1 & 0x0000ffff);
				} else {
					res = (op1 & 0xffff0000) | (op0 & 0x0000ffff);
				}
				c = cpssp->NAME.flag_c;
				v = cpssp->NAME.flag_v;
				if (s) {
					assert(0); /* UNDEFINED */
				}
				break;

			case 0b0111:
				assert(0); /* UNDEFINED */
				break;

			case 0b1000:
				/*
				 * ADD (register), ARMv7-M: A7-191
				 * CMN (register), ARMv7-M: A7-227
				 */
				res = NAME_(adcs)(cpssp, op0, op1, 0, &c, &v);
				break;

			case 0b1001:
				assert(0); /* UNDEFINED */
				break;

			case 0b1010:
				/*
				 * ADC (register), ARMv7-M: A7-187
				 */
				assert(rd != 0xd); /* UNPREDICTABLE */
				assert(rd != 0xf); /* UNPREDICTABLE */
				assert(rm != 0xd); /* UNPREDICTABLE */
				assert(rm != 0xf); /* UNPREDICTABLE */
				assert(rn != 0xd); /* UNPREDICTABLE */
				assert(rn != 0xf); /* UNPREDICTABLE */

				res = NAME_(adcs)(cpssp, op0, op1, cpssp->NAME.flag_c, &c, &v);
				break;

			case 0b1011:
				/*
				 * SBC (register), ARMv7-M: A7-380
				 */
				assert(rd != 0xd); /* UNPREDICTABLE */
				assert(rd != 0xf); /* UNPREDICTABLE */
				assert(rm != 0xd); /* UNPREDICTABLE */
				assert(rm != 0xf); /* UNPREDICTABLE */
				assert(rn != 0xd); /* UNPREDICTABLE */
				assert(rn != 0xf); /* UNPREDICTABLE */

				res = NAME_(sbcs)(cpssp, op0, op1, cpssp->NAME.flag_c, &c, &v);
				break;

			case 0b1100:
				assert(0); /* UNDEFINED */
				break;

			case 0b1101:
				/*
				 * SUB (register), ARMv7-M: A7-450
				 * CMP (register), ARMv7-M: A7-231
				 */
				res = NAME_(sbcs)(cpssp, op0, op1, 1, &c, &v);
				break;

			case 0b1110:
				/*
				 * RSB (register), ARMv7-M: A7-374
				 */
				assert(rd != 0xd); /* UNPREDICTABLE */
				assert(rd != 0xf); /* UNPREDICTABLE */
				assert(rm != 0xd); /* UNPREDICTABLE */
				assert(rm != 0xf); /* UNPREDICTABLE */
				assert(rn != 0xd); /* UNPREDICTABLE */
				assert(rn != 0xf); /* UNPREDICTABLE */

				res = NAME_(sbcs)(cpssp, op1, op0, 1, &c, &v);
				break;

			default:
				assert(0); /* UNDEFINED */
			}

			if ((op != 0b0000 /* AND/TST */
			  && op != 0b0100 /* EOR/TEQ */
			  && op != 0b1000 /* ADD/CMN */
			  && op != 0b1101 /* SUB/CMP */)
			 || rd != 0xf) {
				NAME_(store_reg)(cpssp, rd, res);
			}
			if (s) {
				NAME_(store_flags)(cpssp, c, v, res);
			}
			break;
		case 0b10:
		case 0b11:
#if 7 <= CONFIG_VERSION
			/*
			 * Coprocessor instructions
			 * ARMv7-M: A5-156
			 */
			fprintf(stderr, "insn=0x%08x\n", insn);
			if (((insn >> 20) & 0b100001) == 0b000000
			 && ((insn >> 20) & 0b111010) != 0b000000) {
				/*
				 * STC, STC2
				 * ARMv7: A7-420
				 */
				assert(0); /* FIXME */

			} else if (((insn >> 20) & 0b100001) == 0b000001
				&& ((insn >> 20) & 0b111010) != 0b000000) {
				/*
				 * LDC, LDC2 (immediate)
				 * ARMv7: A7-244
				 * LDC, LDC2 (literal)
				 * ARMv7: A7-246
				 */
				assert(0); /* FIXME */

			} else if (((insn >> 20) & 0b111111) == 0b000100) {
				/*
				 * MCRR, MCRR2
				 * ARMv7: A7-308
				 */
				assert(0); /* FIXME */

			} else if (((insn >> 20) & 0b111111) == 0b000101) {
				/*
				 * MRR, MRRC2
				 * ARMv7: A7-320
				 */
				assert(0); /* FIXME */

			} else if (((insn >> 20) & 0b110000) == 0b100000
				&& ((insn >> 4) & 1) == 0) {
				/*
				 * CDP, CDP2
				 * ARMv7: A7-221
				 */
				assert(0); /* FIXME */

			} else if (((insn >> 20) & 0b110001) == 0b100000
				&& ((insn >> 4) & 1) == 1) {
				/*
				 * MCR, MCR2
				 * ARMv7: A7-306
				 */
				rt = (insn >> 12) & 0xf;

				assert(rt != 0xd); /* UNPREDICTABLE */
				assert(rt != 0xf); /* UNPREDICTABLE */

				op0 = NAME_(load_reg)(cpssp, rt);

				NAME_(mcr)(cpssp, insn, op0);

			} else if (((insn >> 20) & 0b110001) == 0b100001
				&& ((insn >> 4) & 1) == 1) {
				/*
				 * MRC, MRC2
				 * ARMv7: A7-318
				 */
				rt = (insn >> 12) & 0xf;

				assert(rt != 0xd); /* UNPREDICTABLE */
				assert(rt != 0xf); /* UNPREDICTABLE */

				NAME_(mrc)(cpssp, insn, &res);

				NAME_(store_reg)(cpssp, rt, res);

			} else {
				/*
				 * UNDEFINED
				 */
				assert(0); /* FIXME */
			}
#else /* CONFIG_VERSION < 7 */
			assert(0); /* UNDEFINED */
#endif /* CONFIG_VERSION < 7 */
			break;

		default:
			assert(0); /* Cannot happen. */
		}
		break;

	case 0b10:
		/*
		 * Data processing (modified immediate)
		 * ARMv7-M: A5-136
		 * Data processing (plain binary immediate)
		 * ARMv7-M: A5-139
		 * Branches and miscellaneous control
		 * ARMv7-M: A5-140
		 */
		if (! ((insn >> 15) & 1)) {
			/*
			 * Data processing (modified immediate)
			 * ARMv7-M: A5-136
			 * Data processing (plain binary immediate)
			 * ARMv7-M: A5-139
			 */
			if (! ((insn >> 25) & 1)) {
				/*
				 * Data processing (modified immediate)
				 * ARMv7-M: A5-136
				 */
				int i;
				uint8_t imm3;
				uint8_t abcdefgh;

				op = (insn >> 21) & 0xf;
				i = (insn >> 26) & 1;
				s = (insn >> 20) & 1;
				rn = (insn >> 16) & 0xf;
				imm3 = (insn >> 12) & 0x7;
				rd = (insn >> 8) & 0xf;
				abcdefgh = (insn >> 0) & 0xff;

				if ((op == 0b0010 /* ORR/MOV */
				  || op == 0b0011 /* ORN/MVN */)
				 && rn == 0xf) {
					op0 = 0;
				} else {
					op0 = NAME_(load_reg)(cpssp, rn);
				}
				if (((i << 1) | (imm3 >> 2)) == 0) {
					switch (imm3 & 3) {
					case 0:
						op1 = abcdefgh;
						break;
					case 1:
						assert(abcdefgh); /* UNPREDICTABLE */
						op1 = (abcdefgh << 16) | (abcdefgh << 0);
						break;
					case 2:
						assert(abcdefgh); /* UNPREDICTABLE */
						op1 = (abcdefgh << 24) | (abcdefgh << 8);
						break;
					case 3:
						assert(abcdefgh); /* UNPREDICTABLE */
						op1 = (abcdefgh << 24) | (abcdefgh << 16) | (abcdefgh << 8) | (abcdefgh << 0);
						break;
					default:
						assert(0); /* Cannot happen. */
					}
				} else {
					unsigned int count;

					count = (i << 4) | (imm3 << 1) | ((abcdefgh >> 7) & 1);
					assert(8 <= count && count < 32);
					abcdefgh |= 1 << 7;
					op1 = abcdefgh << (32 - count);
				}

				switch (op) {
				case 0b0000:
					/* AND, ARMv7-M: A7-199 */
					/* TST, ARMv7-M: A7-465 */
					res = NAME_(ands)(cpssp, op0, op1, &c, &v);
					break;
				case 0b0001:
					/* BIC, ARMv7-M: A7-211 */
					res = NAME_(bics)(cpssp, op0, op1, &c, &v);
					break;
				case 0b0010:
					/* ORR, ARMv7-M: A7-334 */
					/* MOV, ARMv7-M: A7-312 */
					res = NAME_(orrs)(cpssp, op0, op1, &c, &v);
					break;
				case 0b0011:
					/* MVN, ARMv7-M: A7-326 */
					/* ORN, ARMv7-M: A7-332 */
					res = NAME_(orns)(cpssp, op0, op1, &c, &v);
					break;
				case 0b0100:
					/* EOR, ARMv7-M: A7-238 */
					/* TEQ, ARMv7-M: A7-463 */
					res = NAME_(eors)(cpssp, op0, op1, &c, &v);
					break;
				case 0b0101:
					assert(0); /* UNDEFINED */
					break;
				case 0b0110:
					assert(0); /* UNDEFINED */
					break;
				case 0b0111:
					assert(0); /* UNDEFINED */
					break;
				case 0b1000:
					/* ADD, ARMv7-M: A7-189 */
					/* CMN, ARMv7-M: A7-225 */
					res = NAME_(adcs)(cpssp, op0, op1, 0, &c, &v);
					break;
				case 0b1001:
					assert(0); /* UNDEFINED */
					break;
				case 0b1010:
					/* ADC, ARMv7-M: A7-185 */
					res = NAME_(adcs)(cpssp, op0, op1, cpssp->NAME.flag_c, &c, &v);
					break;
				case 0b1011:
					/* SBC, ARMv7-M: A7-379 */
					res = NAME_(sbcs)(cpssp, op0, op1, cpssp->NAME.flag_c, &c, &v);
					break;
				case 0b1100:
					assert(0); /* UNDEFINED */
					break;
				case 0b1101:
					/* CMP, ARMv7-M: A7-229 */
					/* SUB, ARMv7-M: A7-448 */
					res = NAME_(sbcs)(cpssp, op0, op1, 1, &c, &v);
					break;
				case 0b1110:
					/* RSB, ARMv7-M: A7-372 */
					res = NAME_(sbcs)(cpssp, op1, op0, 1, &c, &v);
					break;
				case 0b1111:
					assert(0); /* UNDEFINED */
					break;
				default:
					assert(0); /* Cannot happen. */
				}
				if ((op != 0b0000 /* AND/TST */
				  && op != 0b0100 /* EOR/TEQ */
				  && op != 0b1000 /* ADD/CMN */
				  && op != 0b1101 /* SUB/CMP */)
				 || rd != 0xf) {
				       NAME_(store_reg)(cpssp, rd, res);
				}
				if (s) {
					NAME_(store_flags)(cpssp, c, v, res);
				}

			} else {
				/*
				 * Data processing (plain binary immediate)
				 * ARMv7-M: A5-139
				 *
				 * ADD (immediate), ARMv7-M: A7-189
				 * ADR, ARMv7-M: A7-197
				 * BFC, ARMv7-M: A7-209
				 * BFI, ARMv7-M: A7-210
				 * MOV (immediate), ARMv7-M: A7-312
				 * MOVT, ARMv7-M: A7-317
				 * SUB (immediate), ARMv7-M: A7-448
				 * ...
				 * UBFX, ARMv7-M: A7-470
				 */
#if CONFIG_VERSION <= 7
				uint32_t op2;
				uint8_t lsb;
				uint8_t msb;
#endif

				rn = (insn >> 16) & 0xf;
				rd = (insn >> 8) & 0xf;
				offset = ((insn >> 26) & 1) << 11;
				offset |= ((insn >> 12) & 0x7) << 8;
				offset |= (insn >> 0) & 0xff;

				switch ((insn >> 20) & 0b11111) {
				case 0b00000:
					if (rn != 0xf) {
						/* ADD (immediate), A6-22 */
						assert(0); /* FIXME */
					} else {
						/* ADR (T3), A6-30 */
						assert(rd != 0xd); /* UNPREDICTABLE */
						assert(rd != 0xf); /* UNPREDICTABLE */

						assert(0); /* FIXME */
					}
					break;
				case 0b00100:
					/* MOV (immediate), A6-148 */
					assert(rd != 0xd); /* UNPREDICTABLE */
					assert(rd != 0xf); /* UNPREDICTABLE */

					offset |= rn << 12;

					NAME_(store_reg)(cpssp, rd, offset);
					break;
				case 0b01010:
					if (rn != 0xf) {
						/* SUB (immediate), A6-244 */
						assert(0); /* FIXME */
					} else {
						/* ADR, A6-30 */
						assert(0); /* FIXME */
					}
					break;
				case 0b01100:
					/* MOVT, A6-153 */
					assert(rd != 0xd); /* UNPREDICTABLE */
					assert(rd != 0xf); /* UNPREDICTABLE */

					offset |= rn << 12;

					res = NAME_(load_reg)(cpssp, rd);
					res &= 0x0000ffff;
					res |= offset << 16;
					NAME_(store_reg)(cpssp, rd, res);
					break;
				case 0b10000:
					assert(0); /* FIXME */
				case 0b10010:
					assert(0); /* FIXME */
				case 0b10100:
					assert(0); /* FIXME */
#if CONFIG_VERSION <= 7
				case 0b10110:
					/*
					 * BFC, ARMv7-M: A7-209
					 * BFI, ARMv7-M: A7-210
					 */
					lsb = (((insn >> 12) & 0x7) << 2)
						| (((insn >> 6) & 0x3) << 0);
					msb = (insn >> 0) & 0x1f;

					assert(rd != 0xd); /* UNPREDICTABLE */
					assert(rd != 0xf); /* UNPREDICTABLE */
					assert(rn != 0xd); /* UNPREDICTABLE */
					assert(lsb <= msb); /* UNPREDICTABLE */

					op0 = NAME_(load_reg)(cpssp, rd);
					if (rn != 0xf) {
						op1 = NAME_(load_reg)(cpssp, rn);
					} else {
						op1 = 0x00000000;
					}

					res = NAME_(bfi)(cpssp, op0, op1, msb, lsb);

					NAME_(store_reg)(cpssp, rd, res);
					break;
#endif /* CONFIG_VERSION <= 7 */
				case 0b11000:
					assert(0); /* FIXME */
				case 0b11010:
					assert(0); /* FIXME */
#if CONFIG_VERSION <= 7
				case 0b11100:
					/* UBFX, ARMv7-M: A7-470 */
					assert(rn != 0xd); /* UNPREDICTABLE */
					assert(rn != 0xf); /* UNPREDICTABLE */
					assert(rd != 0xd); /* UNPREDICTABLE */
					assert(rd != 0xf); /* UNPREDICTABLE */

					op0 = NAME_(load_reg)(cpssp, rn);
					op1 = ((insn >> 12) & 0x7) << 2;
					op1 |= (insn >> 6) & 0x3;
					op2 = ((insn >> 0) & 0x1f) + 1;
					assert(op2 + op1 < 32); /* UNPREDICTABLE */

					res = op0 >> op1;
					res &= (1 << (op2 + 1)) - 1;

					NAME_(store_reg)(cpssp, rd, res);
					break;
#endif /* CONFIG_VERSION <= 7 */
				default:
					assert(0); /* UNDEFINED */
				}
			}

		} else {
			/*
			 * Branch and miscellaneous control
			 * ARMv7: A5-140
			 */
			uint8_t op1, op2;

			op1 = (insn >> 20) & 0x7f;
			op2 = (insn >> 12) & 0x7;

			if ((op2 & 0b101) == 0b000
			 && (op1 & 0b0111000) != 0b0111000) {
				/*
				 * Conditional branch (B) (T3)
				 * ARMv7-M: A7-207
				 */
				int j1, j2;
				uint8_t cond;

				s = (insn >> 26) & 1;
				j1 = (insn >> 13) & 1;
				j2 = (insn >> 11) & 1;
				cond = (insn >> 22) & 0xf;

				assert(cond != 0xe);

				offset = (s << 20)
					| (j2 << 19)
					| (j1 << 18)
					| (((insn >> 16) & 0x3f) << 12)
					| (((insn >> 0) & 0x7ff) << 1)
					| (0 << 0);
				offset = ((int32_t) offset << 11) >> 11;

				if (NAME_(condition)(cpssp, cond)) {
					addr = cpssp->NAME.pc_next + offset;
					NAME_(store_pc)(cpssp, addr);
				}

			} else if ((op2 & 0b101) == 0b000
				&& (op1 & 0b1111110) == 0b0111000) {
				/*
				 * MSR (register)
				 * ARMv7-M, A6-159
				 */
				rn = (insn >> 16) & 0xf;
				tmp32 = NAME_(load_reg)(cpssp, rn);

				NAME_(store_spec)(cpssp, (insn >> 0) & 0xff, tmp32);

			} else if ((op2 & 0b101) == 0b000
				&& op1 == 0b0111010) {
				/*
				 * Hint instructions
				 * ARMv7: A5-19
				 */
				assert(0); /* FIXME */
				break;

			} else if ((op2 & 0b101) == 0b000
				&& op1 == 0b0111011) {
				/*
				 * Miscellaneous control instructions
				 * ARMv7: A5-19
				 */
				switch ((insn >> 4) & 0b1111) {
				case 0b0100:
					/* DSB */
					break;
				case 0b0101:
					/* DMB */
					break;
				case 0b0110:
					/* ISB */
					break;
				default:
					assert(0); /* FIXME */
				}

			} else if ((op2 & 0b101) == 0b000
				&& (op1 & 0b1111110) == 0b0111110) {
				/*
				 * Move from Special Register (MRS)
				 * ARMv7: A6-158, B4-4
				 */
				rd = (insn >> 8) & 0xf;

				tmp32 = NAME_(load_spec)(cpssp, (insn >> 0) & 0xff);

				NAME_(store_reg)(cpssp, rd, tmp32);

			} else if (op2 == 0b010
				&& op1 == 0b1111111) {
				/*
				 * Permanently UNDEFINED
				 */
				assert(0); /* UNDEFINED */

			} else if ((op2 & 0b001) == 0b001) {
				/*
				 * B, ARMv7: A6-40
				 * BL, ARMv7: A6-49
				 */
				int s, j1, j2, i1, i2;

				s = (insn >> 26) & 1;
				j1 = (insn >> 13) & 1;
				j2 = (insn >> 11) & 1;
				i1 = ! (j1 ^ s);
				i2 = ! (j2 ^ s);

				offset = (s << 24)
					| (i1 << 23)
					| (i2 << 22)
					| (((insn >> 16) & 0x3ff) << 12)
					| (((insn >> 0) & 0x7ff) << 1)
					| (0 << 0);
				offset = ((int32_t) offset << 7) >> 7;

				addr = cpssp->NAME.pc_next + offset;
				if (op2 & 0b100) {
					NAME_(store_lr)(cpssp, cpssp->NAME.pc_next | cpssp->NAME.flag_t);
				}
				NAME_(store_pc)(cpssp, addr);
			
			} else {
				assert(0); /* UNDEFINED */
			}
		}
		break;

	case 0b11:
		/*
		 * Store single data item
		 * ARMv7-M: A5-147
		 * Load byte, memory hints
		 * ARMv7-M: A5-146
		 * Load halfword, memory hints
		 * ARMv7-M: A5-145
		 * Load word
		 * ARMv7-M: A5-144
		 * UNDEFINED
		 * Data processing (register)
		 * ARMv7-M: A5-150
		 * Multiply, multiply accumulate, and absolute difference
		 * ARMv7-M: A5-154
		 * Long multiply, long multiply accumulate, and divide
		 * ARMv7-M: A5-154
		 * Coprocessor instructions
		 * ARMv7-M: A5-156
		 */
		if (((insn >> 20) & 0b1110001) == 0b0000000) {
			/*
			 * Store single data item
			 * ARMv7-M: A5-147
			 *
			 * STR (immediate), A6-222
			 * STR (register), A6-224
			 * STRB (immediate), A6-226
			 * STRB (register), A6-228
			 * STRH (immediate), A6-238
			 * STRH (register), A6-240
			 */
			if ((insn >> 23) & 1) {
				/*
				 * Long immediate
				 *
				 * STRB (immediate, T2), A6-226
				 * STRH (immediate, T2), A6-238
				 * STR (immediate, T3), A6-222
				 */
				p = 1;
				u = 1;
				w = 0;
				rn = (insn >> 16) & 0xf;
				rt = (insn >> 12) & 0xf;
				offset = (insn >> 0) & 0xfff;

				addr = NAME_(addr_mode_2_pre)(cpssp,
						p, u, w, rn, offset);

				res = NAME_(load_reg)(cpssp, rt);

				switch ((insn >> 21) & 3) {
				case 0:
					/* STRB (immediate, T2), A6-226 */
					NAME_(st8)(cpssp, addr, res);
					break;
				case 1:
					/* STRH (immediate, T2), A6-238 */
					NAME_(st16)(cpssp, addr, res);
					break;
				case 2:
					/* STR (immediate, T3), A6-222 */
					NAME_(st32)(cpssp, addr, res);
					break;
				case 3:
					assert(0); /* FIXME */
					break;
				default:
					assert(0); /* Cannot happen. */
				}

				NAME_(store_reg)(cpssp, rt, res);

				NAME_(addr_mode_2_post)(cpssp,
						p, u, w, rn, addr, offset);

			} else if ((insn >> 11) & 1) {
				/*
				 * Immediate, pre/post
				 *
				 * STRB (immediate, T3), A6-226
				 * STRH (immediate, T3), A6-238
				 * STR (immediate, T4), A6-222
				 */
				rn = (insn >> 16) & 0xf;
				rt = (insn >> 12) & 0xf;
				p = (insn >> 10) & 1;
				u = (insn >> 9) & 1;
				w = (insn >> 8) & 1;
				offset = (insn >> 0) & 0xff;

				addr = NAME_(addr_mode_2_pre)(cpssp,
						p, u, w, rn, offset);

				res = NAME_(load_reg)(cpssp, rt);

				switch ((insn >> 21) & 3) {
				case 0:
					/* STRB (immediate, T3), A6-226 */
					NAME_(st8)(cpssp, addr, res);
					break;
				case 1:
					/* STRH (immediate, T3), A6-238 */
					NAME_(st16)(cpssp, addr, res);
					break;
				case 2:
					/* STR (immediate, T4), A6-222 */
					NAME_(st32)(cpssp, addr, res);
					break;
				case 3:
					assert(0); /* FIXME */
					break;
				default:
					assert(0); /* Cannot happen. */
				}

				NAME_(store_reg)(cpssp, rt, res);

				NAME_(addr_mode_2_post)(cpssp,
						p, u, w, rn, addr, offset);

			} else {
				/*
				 * Register
				 *
				 * STRB (register, T2), A6-228
				 * STRH (register, T2), A6-240
				 * STR (register, T2), A6-224
				 */
				p = 1;
				u = 1;
				w = 0;
				rn = (insn >> 16) & 0xf;
				rt = (insn >> 12) & 0xf;
				shift = (insn >> 4) & 0x3;
				rm = (insn >> 0) & 0xf;

				offset = NAME_(shift)(cpssp, rm, 0, shift);

				addr = NAME_(addr_mode_2_pre)(cpssp,
						p, u, w, rn, offset);

				res = NAME_(load_reg)(cpssp, rt);

				switch ((insn >> 21) & 3) {
				case 0:
					/* STRB (register, T2), A6-228 */
					NAME_(st8)(cpssp, addr, res);
					break;
				case 1:
					/* STRH (register, T2), A6-240 */
					NAME_(st16)(cpssp, addr, res);
					break;
				case 2:
					/* STR (register, T2), A6-224 */
					NAME_(st32)(cpssp, addr, res);
					break;
				case 3:
					assert(0); /* FIXME */
					break;
				default:
					assert(0); /* Cannot happen. */
				}

				NAME_(store_reg)(cpssp, rt, res);

				NAME_(addr_mode_2_post)(cpssp,
						p, u, w, rn, addr, offset);
			}

		} else if (((insn >> 20) & 0b1100101) == 0b0000001) {
			/*
			 * Load byte, memory hints
			 * ARMv7-M: A5-146
			 * Load halfword, memory hints
			 * ARMv7-M: A5-145
			 */
			rn = (insn >> 16) & 0xf;
			rt = (insn >> 12) & 0xf;

			if (rt == 0xf) {
				if (! ((insn >> 21) & 1)) {
					/*
					 * Memory hint
					 */
					/* Nothing to do... */

				} else {
					/*
					 * UNPREDICTABLE
					 * Unallocated memory hint
					 */
					assert(0); /* FIXME */
				}

			} else if (rn == 0xf) {
				assert(rt != 0xf);

				if (! ((insn >> 24) & 1)) {
					if (! ((insn >> 21) & 1)) {
						/*
						 * LDRB (literal)
						 * ARMv7-M: A7-260
						 */
						assert(0); /* FIXME */
					} else {
						/*
						 * LDRH (literal)
						 * ARMv7-M: A7-276
						 */
						assert(0); /* FIXME */
					}

				} else {
					if (! ((insn >> 21) & 1)) {
						/*
						 * LDRSB (literal)
						 * ARMv7-M: A7-286
						 */
						assert(0); /* FIXME */
					} else {
						/*
						 * LDRSH (literal)
						 * ARMv7-M: A7-292
						 */
						assert(0); /* FIXME */
					}
				}
			} else {
				assert(rn != 0xf);
				assert(rt != 0xf);

				if (! ((insn >> 23) & 1)
				 && ((((insn >> 8) & 0b1001) == 0b1001)
				  || (((insn >> 8) & 0b1111) == 0b1100))) {
					/* LDRB (immediate), ARMv7-M: */
					/* LDRH (immediate), ARMv7-M: */
					/* LDRSB (immediate), ARMv7-M: */
					/* LDRSH (immediate), ARMv7-M: */
					p = (insn >> 10) & 1;
					u = (insn >> 9) & 1;
					w = (insn >> 8) & 1;
					offset = (insn >> 0) & 0xff;

					addr = NAME_(addr_mode_2_pre)(cpssp,
							p, u, w, rn, offset);

					res = NAME_(ldusbh)(cpssp,
							(insn >> 24) & 1,
							(insn >> 21) & 1,
							addr);

					NAME_(store_reg)(cpssp, rt, res);

					NAME_(addr_mode_2_post)(cpssp,
							p, u, w, rn, addr, offset);

				} else if ((insn >> 23) & 1) {
					/* LDRB (immediate), ARMv7-M: */
					/* LDRH (immediate), ARMv7-M: */
					/* LDRSB (immediate), ARMv7-M: */
					/* LDRSH (immediate), ARMv7-M: */
					p = 1;
					u = 1;
					w = 0;
					offset = (insn >> 0) & 0xfff;

					addr = NAME_(addr_mode_2_pre)(cpssp,
							p, u, w, rn, offset);

					res = NAME_(ldusbh)(cpssp,
						(insn >> 24) & 1,
						(insn >> 21) & 1,
						addr);

					NAME_(store_reg)(cpssp, rt, res);

					NAME_(addr_mode_2_post)(cpssp,
							p, u, w, rn, addr, offset);

				} else if (! ((insn >> 23) & 1)
					&& ((insn >> 6) & 0b111111) == 0b000000) {
					/*
					 * LDRB (register)
					 * ARMv7-M: A7-262
					 * LDRH (register)
					 * ARMv7-M: A7-278
					 * LDRSB (register)
					 * ARMv7-M: A7-286
					 * LDRSH (register)
					 * ARMv7-M: A7-294
					 */
					p = 1;
					u = 1;
					w = 0;
					sop = 0;
					shift = (insn >> 4) & 0x3;
					rm = (insn >> 0) & 0xf;

					offset = NAME_(shift)(cpssp, rm, sop, shift);

					addr = NAME_(addr_mode_2_pre)(cpssp,
							p, u, w, rn, offset);

					res = NAME_(ldusbh)(cpssp,
							(insn >> 24) & 1,
							(insn >> 21) & 1,
							addr);

					NAME_(store_reg)(cpssp, rt, res);

					NAME_(addr_mode_2_post)(cpssp,
							p, u, w, rn, addr, offset);

				} else if (! ((insn >> 23) & 1)
					&& ((insn >> 6) & 0b111100) == 0b111000) {
					if (! ((insn >> 24) & 1)) {
						if (! ((insn >> 21) & 1)) {
							/*
							 * LDRBT
							 * ARMv7-M: A7-264
							 */
							assert(0); /* FIXME */
						} else {
							/*
							 * LDRHT
							 * ARMv7-M: A7-280
							 */
							assert(0); /* FIXME */
						}
					} else {
						if (! ((insn >> 21) & 1)) {
							/*
							 * LDRSBT
							 * ARMv7-M: A7-288
							 */
							assert(0); /* FIXME */
						} else {
							/*
							 * LDRSHT
							 * ARMv7-M: A7-296
							 */
							assert(0); /* FIXME */
						}
					}

				} else {
					assert(0); /* UNDEFINED */
				}
			}

		} else if (((insn >> 20) & 0b1100111) == 0b0000101) {
			/*
			 * Load word
			 * ARMv7-M: A5-144
			 */
			rn = (insn >> 16) & 0xf;
			rt = (insn >> 12) & 0xf;

			if (! ((insn >> 24) & 1)) {
				if (rn != 0xf) {
					if ((insn >> 23) & 1) {
						/*
						 * LDR (immediate, T3), ARMv7-M: A7-252
						 */
						p = 1;
						u = 1;
						w = 0;
						offset = (insn >> 0) & 0xfff;

						addr = NAME_(addr_mode_2_pre)(cpssp,
								p, u, w, rn, offset);
						res = NAME_(ld32)(cpssp, addr);

						NAME_(store_reg)(cpssp, rt, res);

						NAME_(addr_mode_2_post)(cpssp,
								p, u, w, rn, addr, offset);

					} else if (! ((insn >> 11) & 1)) {
						/* LDR (register, T2), A6-92 */
						p = 1;
						u = 1;
						w = 0;
						sop = 0;
						shift = (insn >> 4) & 0x3;
						rm = (insn >> 0) & 0xf;

						offset = NAME_(shift)(cpssp, rm, sop, shift);

						addr = NAME_(addr_mode_2_pre)(cpssp,
								p, u, w, rn, offset);

						res = NAME_(ld32)(cpssp, addr);

						NAME_(store_reg)(cpssp, rt, res);

						NAME_(addr_mode_2_post)(cpssp,
								p, u, w, rn, addr, offset);

					} else {
						/*
						 * LDR (immediate, T4), ARMv7-M: A7-252
						 * LDRT (T1), A6-133
						 */
						p = (insn >> 10) & 1;
						u = (insn >> 9) & 1;
						w = (insn >> 8) & 1;
						offset = (insn >> 0) & 0xff;

						addr = NAME_(addr_mode_2_pre)(cpssp,
								p, u, w, rn, offset);

						res = NAME_(load_reg)(cpssp, rt);

						switch ((insn >> 8) & 7) {
						case 0:
						case 1:
						case 2:
						case 3:
						case 4:
						case 5:
						case 7:
							res = NAME_(ld32)(cpssp, addr);
							break;
						case 6:
							/* LDRT (T1), A6-133 */
							assert(0); /* FIXME */
						default:
							assert(0); /* Cannot happen. */
						}

						NAME_(store_reg)(cpssp, rt, res);

						NAME_(addr_mode_2_post)(cpssp,
								p, u, w, rn, addr, offset);
					}
				} else {
					/*
					 * LDR (literal)
					 * ARMv7-M: A7-254
					 */
					p = 1;
					u = (insn >> 23) & 1;
					w = 0;
					offset = (insn >> 0) & 0xfff;

					addr = NAME_(addr_mode_2_pre)(cpssp,
							p, u, w, rn, offset);
					
					res = NAME_(load_reg)(cpssp, rt);

					res = NAME_(ld32)(cpssp, addr);

					NAME_(store_reg)(cpssp, rt, res);

					NAME_(addr_mode_2_post)(cpssp,
							p, u, w, rn, addr, offset);
				}
			} else {
				assert(0); /* UNDEFINED */
			}

		} else if (((insn >> 20) & 0b1100111) == 0b0000111) {
			/*
			 * UNDEFINED
			 */
			assert(0); /* UNDEFINED */

		} else if (((insn >> 20) & 0b1110000) == 0b0100000) {
			/*
			 * Data processing (register)
			 * ARMv7-M: A5-150
			 */
			assert(((insn >> 12) & 0xf) == 0xf); /* UNDEFINED */

			if (! ((insn >> 23) & 1)) {
				/*
				 * Shift/Extend
				 */
				if (! ((insn >> 7) & 1)) {
					/*
					 * ASR (register), A7-205
					 * LSL (register), A7-300
					 * LSR (register), A7-304
					 * ROR (register), A7-368
					 */
					s = (insn >> 20) & 1;
					rn = (insn >> 16) & 0xf;
					rd = (insn >> 8) & 0xf;
					rm = (insn >> 0) & 0xf;

					op0 = NAME_(load_reg)(cpssp, rn);
					op1 = NAME_(load_reg)(cpssp, rm);

					switch ((insn >> 21) & 3) {
					case 0:
						/* LSL (register), A7-300 */
						op1 &= 0xff;
						if (32 < op1) {
							c = 0;
							res = 0;
						} else if (32 == op1) {
							c = op0 & 1;
							res = 0;
						} else {
							c = (op0 >> (32 - op1)) & 1;
							res = op0 << op1;
						}
						break;
					case 1:
						/* LSR (register), A7-304 */
						op1 &= 0xff;
						if (32 < op1) {
							c = 0;
							res = 0;
						} else if (32 == op1) {
							c = (op0 >> 31) & 1;
							res = 0;
						} else {
							c = (op0 >> (op1 - 1)) & 1;
							res = (uint32_t) op0 >> op1;
						}
						break;
					case 2:
						/* ASR (register), A7-205 */
						assert(0); /* FIXME */
						break;
					case 3:
						/* ROR (register), A7-368 */
						assert(0); /* FIXME */
						break;
					default:
						assert(0); /* Cannot happen. */
					}
					v = cpssp->NAME.flag_v;

					if (s) {
						NAME_(store_flags)(cpssp, c, v, res);
					}
					NAME_(store_reg)(cpssp, rd, res);

				} else {
					/*
					 * SXTAB, ARMv7-M: A7-456
					 * SXTAH, ARMv7-M: A7-458
					 * SXTB, ARMv7-M: A7-459
					 * SXTH, ARMv7-M: A7-461
					 * UXTAH, ARMv7-M: A7-497
					 * UXTAB, ARMv7-M: A7-495
					 * UXTB, ARMv7-M: A7-498
					 * UXTH, ARMv7-M: A7-500
					 */
					unsigned int rot;

					rn = (insn >> 16) & 0xf;
					rd = (insn >> 8) & 0xf;
					rm = (insn >> 0) & 0xf;

					switch ((insn >> 20) & 0b1111) {
					case 0b0000:
						/*
						 * SXTAH, ARMv7-M: A7-458
						 * SXTH, ARMv7-M: A7-461
						 */
						rot = (insn >> 4) & 0x3;
						rot *= 8;

						op0 = NAME_(load_reg)(cpssp, rm);
						if (rot) {
							op0 = (op0 >> rot)
								| (op0 << (32 - rot));
						}

						res = (int32_t) (int16_t) op0;
						if (rn != 0xf) {
							res += NAME_(load_reg)(cpssp, rn);
						}

						NAME_(store_reg)(cpssp, rd, res);
						break;
					case 0b0001:
						/*
						 * UXTAH, ARMv7-M: A7-497
						 * UXTH, ARMv7-M: A7-500
						 */
						rot = (insn >> 4) & 0x3;
						rot *= 8;

						op0 = NAME_(load_reg)(cpssp, rm);
						if (rot) {
							op0 = (op0 >> rot)
								| (op0 << (32 - rot));
						}

						res = (uint32_t) (uint16_t) op0;
						if (rn != 0xf) {
							res += NAME_(load_reg)(cpssp, rn);
						}

						NAME_(store_reg)(cpssp, rd, res);
						break;
					case 0b0010:
						assert(0); /* FIXME */
					case 0b0011:
						assert(0); /* FIXME */
					case 0b0100:
						/*
						 * SXTAB, ARMv7-M: A7-456
						 * SXTB, ARMv7-M: A7-459
						 */
						rot = (insn >> 4) & 0x3;
						rot *= 8;

						op0 = NAME_(load_reg)(cpssp, rm);
						if (rot) {
							op0 = (op0 >> rot)
								| (op0 << (32 - rot));
						}

						res = (int32_t) (int8_t) op0;
						if (rn != 0xf) {
							res += NAME_(load_reg)(cpssp, rn);
						}

						NAME_(store_reg)(cpssp, rd, res);
						break;
					case 0b0101:
						/*
						 * UXTAB, ARMv7-M: A7-495
						 * UXTB, ARMv7-M: A7-498
						 */
						rot = (insn >> 4) & 0x3;
						rot *= 8;

						op0 = NAME_(load_reg)(cpssp, rm);
						if (rot) {
							op0 = (op0 >> rot)
								| (op0 << (32 - rot));
						}

						res = (uint32_t) (uint8_t) op0;
						if (rn != 0xf) {
							res += NAME_(load_reg)(cpssp, rn);
						}

						NAME_(store_reg)(cpssp, rd, res);
						break;
					default:
						assert(0); /* UNDEFINED */
					}
				}
			} else {
				/*
				 * Parallel/Miscellaneous
				 */
				if (! ((insn >> 23) & 1)) {
					/*
					 * Parallel addition and
					 * subtraction
					 */
					assert(0); /* FIXME */
				} else {
					/*
					 * Miscellaneous operations
					 * ARMv7-M: A5-153
					 */
					rd = (insn >> 8) & 0xf;
					rm = (insn >> 0) & 0xf;

					assert(rd != 0xd); /* UNPREDICTABLE */
					assert(rd != 0xf); /* UNPREDICTABLE */
					assert(rm != 0xd); /* UNPREDICTABLE */
					assert(rm != 0xf); /* UNPREDICTABLE */

					op0 = NAME_(load_reg)(cpssp, rm);

					switch ((insn >> 20) & 0b11) {
					case 0b00:
						assert(0); /* FIXME */
					case 0b01:
						switch ((insn >> 4) & 0b11) {
						case 0b00:
							assert(0); /* FIXME */
						case 0b01:
							assert(0); /* FIXME */
						case 0b10:
							/*
							 * RBIT, ARMv7-M: A7-362
							 */
							res = NAME_(rbit)(cpssp, op0);
							break;
						case 0b11:
							assert(0); /* FIXME */
						default: assert(0); /* Cannot happen. */
						}
						break;
					case 0b10:
						assert(0); /* FIXME */
					case 0b11:
						switch ((insn >> 4) & 0b11) {
						case 0b00:
							/*
							 * CLZ, ARMv7-M: A7-224
							 */

							res = NAME_(clz)(cpssp, op0);
							break;
						case 0b01:
							assert(0); /* FIXME */
						case 0b10:
							assert(0); /* FIXME */
						case 0b11:
							assert(0); /* FIXME */
						default: assert(0); /* Cannot happen. */
						}
						break;
					default: assert(0); /* Cannot happen. */
					}

					NAME_(store_reg)(cpssp, rd, res);
				}
			}

		} else if (((insn >> 20) & 0b1111000) == 0b0110000) {
			/*
			 * Multiply, and multiply accumulate, and absolute difference
			 * ARMv7-M: A5-154
			 *
			 * ...
			 * MLA, ARMv7-M: A7-310
			 * MUL, ARMv7-M: A7-324
			 * SMLA??, ARMv7-M: A7-392
			 * SMUL??, ARMv7-M: A7-410
			 * ...
			 */
			uint8_t ra;
#if CONFIG_VERSION <= 7
			uint32_t op2;
#endif

			assert(((insn >> 6) & 0b11) == 0b00); /* UNDEFINED */

			ra = (insn >> 12) & 0xf;

			switch ((insn >> 20) & 0b111) {
#if CONFIG_VERSION <= 7
			case 0b000:
				switch ((insn >> 4) & 0b11) {
				case 0b00:
					/*
					 * MLA, ARMv7-M: A7-310
					 * MUL, ARMv7-M: A7-324
					 */
					rn = (insn >> 16) & 0xf;
					rd = (insn >> 8) & 0xf;
					rm = (insn >> 0) & 0xf;

					assert(rn != 0xd); /* UNPREDICTABLE */
					assert(rn != 0xf); /* UNPREDICTABLE */
					assert(ra != 0xd); /* UNPREDICTABLE */
					assert(rd != 0xd); /* UNPREDICTABLE */
					assert(rd != 0xf); /* UNPREDICTABLE */
					assert(rm != 0xd); /* UNPREDICTABLE */
					assert(rm != 0xf); /* UNPREDICTABLE */

					op0 = NAME_(load_reg)(cpssp, rn);
					op1 = NAME_(load_reg)(cpssp, rm);
					if (ra == 0xf) {
						op2 = 0x00000000;
					} else {
						op2 = NAME_(load_reg)(cpssp, ra);
					}

					res = NAME_(mlas)(cpssp, op0, op1, op2,
							&c, &v);

					NAME_(store_reg)(cpssp, rd, res);
					break;
				case 0b01:
					assert(0); /* FIXME */
					break;
				default:
					assert(0); /* UNDEFINED */
					break;
				}
				break;
#endif /* CONFIG_VERSION <= 7 */
#if CONFIG_VERSION <= 7
			case 0b001:
				/*
				 * Signed Multiply Accumulate, Halfwords
				 * Signed Multiply, Halfwords
				 *
				 * SMLA??, ARMv7-M: A7-392
				 * SMUL??, ARMv7-M: A7-410
				 */
				rn = (insn >> 16) & 0xf;
				rd = (insn >> 8) & 0xf;
				rm = (insn >> 0) & 0xf;

				assert(rn != 0xd); /* UNPREDICTABLE */
				assert(rn != 0xf); /* UNPREDICTABLE */
				assert(ra != 0xd); /* UNPREDICTABLE */
				assert(rd != 0xd); /* UNPREDICTABLE */
				assert(rd != 0xf); /* UNPREDICTABLE */
				assert(rm != 0xd); /* UNPREDICTABLE */
				assert(rm != 0xf); /* UNPREDICTABLE */

				op0 = NAME_(load_reg)(cpssp, rn);
				op1 = NAME_(load_reg)(cpssp, rm);

				if ((insn >> 5) & 1) {
					op0 = (op0 >> 16) & 0xffff;
				} else {
					op0 = (op0 >> 0) & 0xffff;
				}
				if ((insn >> 4) & 1) {
					op1 = (op1 >> 16) & 0xffff;
				} else {
					op1 = (op1 >> 0) & 0xffff;
				}

				if (ra == 0b1111) {
					op2 = 0x00000000;
				} else {
					op2 = NAME_(load_reg)(cpssp, ra);
				}

				res = NAME_(mlas)(cpssp, op0, op1, op2,
						&c, &v);
				/* Set Q-Flag? FIXME */

				NAME_(store_reg)(cpssp, rd, res);
				break;
#endif /* CONFIG_VERSION <= 7 */
			case 0b010:
				switch ((insn >> 4) & 0b11) {
				case 00:
				case 01:
					if (ra != 0b1111) {
						assert(0); /* FIXME */
					} else {
						assert(0); /* FIXME */
					}
					break;
				default:
					assert(0); /* UNDEFINED */
					break;
				}
				break;
			case 0b011:
				switch ((insn >> 4) & 0b11) {
				case 00:
				case 01:
					if (ra != 0b1111) {
						assert(0); /* FIXME */
					} else {
						assert(0); /* FIXME */
					}
					break;
				default:
					assert(0); /* UNDEFINED */
					break;
				}
				break;
			case 0b100:
				switch ((insn >> 4) & 0b11) {
				case 00:
				case 01:
					if (ra != 0b1111) {
						assert(0); /* FIXME */
					} else {
						assert(0); /* FIXME */
					}
					break;
				default:
					assert(0); /* UNDEFINED */
					break;
				}
				break;
			case 0b101:
				switch ((insn >> 4) & 0b11) {
				case 00:
				case 01:
					if (ra != 0b1111) {
						assert(0); /* FIXME */
					} else {
						assert(0); /* FIXME */
					}
					break;
				default:
					assert(0); /* UNDEFINED */
					break;
				}
				break;
			case 0b110:
				switch ((insn >> 4) & 0b11) {
				case 00:
				case 01:
					assert(0); /* FIXME */
					break;
				default:
					assert(0); /* UNDEFINED */
					break;
				}
				break;
			case 0b111:
				switch ((insn >> 4) & 0b11) {
				case 00:
					if (ra != 0b1111) {
						assert(0); /* FIXME */
					} else {
						assert(0); /* FIXME */
					}
					break;
				default:
					assert(0); /* UNDEFINED */
					break;
				}
				break;
			default:
				assert(0); /* UNDEFINED */
			}

		} else if (((insn >> 20) & 0b1111000) == 0b0111000) {
			/*
			 * Long multiply, long multiply accumulate, and divide
			 * ARMv7-M: A5-154
			 */
			switch ((insn >> 20) & 7) {
			case 0:
				/* SMULL, ARMv7-M: A6-214 */
				assert(0); /* FIXME */
				break;
			case 1:
				/* SDIV, ARMv7-M: A6-210 */
				assert(0); /* FIXME */
				break;
			case 2:
				/* UMULL, ARMv7-M: A6-269 */
				rn = (insn >> 16) & 0xf;
				rdlo = (insn >> 12) & 0xf;
				rdhi = (insn >> 8) & 0xf;
				rm = (insn >> 0) & 0xf;

				op0 = NAME_(load_reg)(cpssp, rn);
				op1 = NAME_(load_reg)(cpssp, rm);

				res64 = NAME_(umulls)(cpssp, op0, op1, &c, &v);

				NAME_(store_reg)(cpssp, rdhi, (res64 >> 32) & 0xffffffff);
				NAME_(store_reg)(cpssp, rdlo, (res64 >> 0) & 0xffffffff);
				break;

			case 3:
				/* UDIV, ARMv7-M: A6-267 */
				rn = (insn >> 16) & 0xf;
				rd = (insn >> 8) & 0xf;
				rm = (insn >> 0) & 0xf;

				op0 = NAME_(load_reg)(cpssp, rn);
				op1 = NAME_(load_reg)(cpssp, rm);

				assert(op1 != 0); /* FIXME */
				res = op0 / op1;

				NAME_(store_reg)(cpssp, rd, res);
				break;

			case 4:
				/* SMLAL, ARMv7-M: A6-213 */
				assert(0); /* FIXME */
				break;
			case 5:
				/* UMLAL, ARMv7-M: A6-268 */
				assert(0); /* FIXME */
				break;
			case 6:
				assert(0); /* UNDEFINED */
				break;
			case 7:
				assert(0); /* UNDEFINED */
				break;
			default: assert(0); /* Cannot happen. */
			}

		} else if (((insn >> 20) & 0b1000000) == 0b1000000) {
			/*
			 * Coprocessor instructions
			 * ARMv7-M: A5-156
			 */
			assert(0); /* FIXME */

		} else {
			assert(0); /* Cannot happen. */
		}
	}
}
#endif /* (CONFIG_VERSION == 6 && CONFIG_THUMB2) || 7 <= CONFIG_VERSION */

static uint32_t
NAME_(lsls)(struct cpssp *cpssp, uint32_t op0, uint32_t op1, int *cp, int *vp)
{
	uint32_t res;

	op1 &= 0xff;

	if (op1 == 0) {
		res = op0;
		*cp = cpssp->NAME.flag_c;
	} else if (op1 < 32) {
		res = op0 << op1;
		*cp = ((op0 << (op1 - 1)) >> 31) & 1;
	} else if (op1 == 32) {
		res = 0;
		*cp = op0 & 1;
	} else {
		res = 0;
		*cp = 0;
	}
	*vp = cpssp->NAME.flag_v;

	return res;
}

static uint32_t
NAME_(lsrs)(struct cpssp *cpssp, uint32_t op0, uint32_t op1, int *cp, int *vp)
{
	uint32_t res;

	op1 &= 0xff;

	if (op1 == 0) {
		res = op0;
		*cp = cpssp->NAME.flag_c;
	} else if (op1 < 32) {
		res = op0 >> op1;
		*cp = (op0 >> (op1 - 1)) & 1;
	} else if (op1 == 32) {
		res = 0;
		*cp = (op0 >> 31) & 1;
	} else {
		res = 0;
		*cp = 0;
	}
	*vp = cpssp->NAME.flag_v;

	return res;
}

static uint32_t
NAME_(asrs)(struct cpssp *cpssp, uint32_t op0, uint32_t op1, int *cp, int *vp)
{
	uint32_t res;

	op1 &= 0xff;

	if (op1 == 0) {
		res = op0;
		*cp = cpssp->NAME.flag_c;
	} else if (op1 < 32) {
		res = (int32_t) op0 >> (int32_t) op1;
		*cp = ((int32_t) op0 >> ((int32_t) op1 - 1)) & 1;
	} else if (op1 == 32) {
		res = (int32_t) op0 >> 31;
		*cp = (op0 >> 31) & 1;
	} else {
		res = (int32_t) op0 >> 31;
		*cp = (op0 >> 31) & 1;
	}
	*vp = cpssp->NAME.flag_v;

	return res;
}

static uint32_t
NAME_(rors)(struct cpssp *cpssp, uint32_t op0, uint32_t op1, int *cp, int *vp)
{
	uint32_t res;

	if (op1 == 0) {
		res = op0;
		*cp = cpssp->NAME.flag_c;
	} else {
		res = (op0 >> op1) | (op0 << (32 - op1));
		*cp = (op0 >> (op1 - 1)) & 1;
	}
	*vp = cpssp->NAME.flag_v;

	return res;
}

static void
NAME_(insn_exec_thumb)(struct cpssp *cpssp, uint16_t insn16)
{
	uint16_t reglist;
	uint16_t op;
	uint16_t rd;
	uint16_t rm;
	uint16_t rn;
	uint32_t offset;
	uint8_t cond;
	int inside;
	uint32_t addr;
	uint32_t op0;
	uint32_t op1;
	uint32_t res;
	int c, v;
	int i;

	inside = NAME_(it_inside)(cpssp);
	if (! NAME_(it_true)(cpssp)) {
		return;
	}

	/*
	 * 16-bit Thumb instruction encoding, ARMv7-A/R: A5-127
	 */
	switch ((insn16 >> 12) & 0xf) {
	case 0x0: case 0x1:
		if (((insn16 >> 11) & 0x3) != 0x3) {
			/*
			 * Move shifted register.
			 *
			 * ASR(immediate), ARMv7-A/R: A8-330, ARMv7-M: A7-203
			 * LSL(immediate), ARMv7-A/R: A8-468, ARMv7-M: A7-298
			 * LSR(immediate), ARMv7-A/R: A8-472, ARMv7-M: A7-304
			 */
			COUNT_INST(shift);

			offset = (insn16 >> 6) & 0x1f;
			rm = (insn16 >> 3) & 0x7;
			rd = (insn16 >> 0) & 0x7;

			op0 = NAME_(load_reg)(cpssp, rm);
			op1 = offset;

			switch ((insn16 >> 11) & 0b11) {
			case 0b00:
				/* LSL(immediate), ARMv7-A/R: A8-468, ARMv7-M: A7-298 */
				res = NAME_(lsls)(cpssp, op0, op1, &c, &v);
				break;

			case 0b01:
				/* LSR(immediate), ARMv7-A/R: A8-472, ARMv7-M: A7-304 */
				res = NAME_(lsrs)(cpssp, op0, op1, &c, &v);
				break;

			case 0b10:
				/* ASR(immediate), ARMv7-A/R: A8-330, ARMv7-M: A7-203 */
				res = NAME_(asrs)(cpssp, op0, op1, &c, &v);
				break;

			case 0b11:
			default:
				assert(0); /* Cannot happen. */
			}

			NAME_(store_reg)(cpssp, rd, res);
			if (! inside) {
				NAME_(store_flags)(cpssp, c, v, res);
			}

		} else if ((insn16 >> 10) & 1) {
			/*
			 * Add/subtract immediate.
			 *
			 * ADD(immediate), ARMv7-A/R: A8-306, ARMv7-M: A7-189
			 * SUB(immediate), ARMv7-A/R: A8-708, ARMv7-M: A7-448
			 */
			COUNT_INST(addsub);

			offset = (insn16 >> 6) & 0x7;
			rn = (insn16 >> 3) & 0x7;
			rd = (insn16 >> 0) & 0x7;

			op0 = NAME_(load_reg)(cpssp, rn);
			op1 = offset;

			switch ((insn16 >> 9) & 1) {
			case 0:
				/* ADD(immediate), ARMv7-A/R: A8-306, ARMv7-M: A7-189 */
				res = NAME_(adcs)(cpssp, op0, op1, 0, &c, &v);
				break;
			case 1:
				/* SUB(immediate), ARMv7-A/R: A8-708, ARMv7-M: A7-448 */
				res = NAME_(sbcs)(cpssp, op0, op1, 1, &c, &v);
				break;
			default:
				assert(0); /* Cannot happen. */
			}

			NAME_(store_reg)(cpssp, rd, res);
			if (! inside) {
				NAME_(store_flags)(cpssp, c, v, res);
			}

		} else {
			/*
			 * Add/subtract register.
			 *
			 * ADD(register), ARMv7-A/R: A8-310, ARMv7-M: A7-191
			 * SUB(register), ARMv7-A/R: A8-712, ARMv7-M: A7-450
			 */
			COUNT_INST(addsub);

			rm = (insn16 >> 6) & 0x7;
			rn = (insn16 >> 3) & 0x7;
			rd = (insn16 >> 0) & 0x7;

			op0 = NAME_(load_reg)(cpssp, rn);
			op1 = NAME_(load_reg)(cpssp, rm);

			switch ((insn16 >> 9) & 1) {
			case 0:
				/* ADD(register), ARMv7-A/R: A8-310, ARMv7-M: A7-191 */
				res = NAME_(adcs)(cpssp, op0, op1, 0, &c, &v);
				break;
			case 1:
				/* SUB(register), ARMv7-A/R: A8-712, ARMv7-M: A7-450 */
				res = NAME_(sbcs)(cpssp, op0, op1, 1, &c, &v);
				break;
			default:
				assert(0); /* Cannot happen. */
			}

			NAME_(store_reg)(cpssp, rd, res);
			if (! inside) {
				NAME_(store_flags)(cpssp, c, v, res);
			}
		}
		break;

	case 0x2: case 0x3:
		/*
		 * Move/compare/add/subtract immediate.
		 *
		 * ADD(immediate), ARMv7-A/R: A8-306, ARMv7-M: A7-189
		 * CMP(immediate), ARMv7-A/R: A8-370, ARMv7-M: A7-229
		 * MOV(immediate), ARMv7-A/R: A8-484, ARMv7-M: A7-312
		 * SUB(immediate), ARMv7-A/R: A8-708, ARMv7-M: A7-448
		 */
		COUNT_INST(immediate);

		op = (insn16 >> 11) & 0x3;
		rd = (insn16 >> 8) & 0x7;
		offset = (insn16 >> 0) & 0xff;

		op0 = NAME_(load_reg)(cpssp, rd);
		op1 = offset;

		switch (op) {
		case 0x0:
			/* MOV(immediate), ARMv7-A/R: A8-484, ARMv7-M: A7-312 */
			res = NAME_(movs)(cpssp, op0, op1, &c, &v);
			break;
		case 0x1:
			/* CMP(immediate), ARMv7-A/R: A8-370, ARMv7-M: A7-229 */
			res = NAME_(sbcs)(cpssp, op0, op1, 1, &c, &v);
			break;
		case 0x2:
			/* ADD(immediate), ARMv7-A/R: A8-306, ARMv7-M: A7-189 */
			res = NAME_(adcs)(cpssp, op0, op1, 0, &c, &v);
			break;
		case 0x3:
			/* SUB(immediate), ARMv7-A/R: A8-708, ARMv7-M: A7-448 */
			res = NAME_(sbcs)(cpssp, op0, op1, 1, &c, &v);
			break;
		default:
			assert(0); /* Cannot happen. */
		}

		if (! inside
		 || op == 0x1) { /* CMP */
			NAME_(store_flags)(cpssp, c, v, res);
		}
		if (op != 0x1) { /* CMP */
			NAME_(store_reg)(cpssp, rd, res);
		}
		break;

	case 0x4:
		switch ((insn16 >> 10) & 0x3) {
		case 0x0:
			/*
			 * Data processing, ARMv7-A/R: A6-225, ARMv7-M: A5-129
			 *
			 * ADC, ARMv7-A/R: A8-302, ARMv7-M: A7-187
			 * AND, ARMv7-A/R: A8-326, ARMv7-M: A7-201
			 * ASR, ARMv7-A/R: A8-332, ARMv7-M: A7-205
			 * BIC, ARMv7-A/R: A8-342, ARMv7-M: A7-213
			 * CMN, ARMv7-A/R: A8-366, ARMv7-M: A7-227
			 * CMP, ARMv7-A/R: A8-372, ARMv7-M: A7-231
			 * EOR, ARMv7-A/R: A8-384, ARMv7-M: A7-239
			 * LSL, ARMv7-A/R: A8-468, ARMv7-M: A7-300
			 * LSR, ARMv7-A/R: A8-472, ARMv7-M: A7-304
			 * MVN, ARMv7-A/R: A8-506, ARMv7-M: A7-328
			 * MUL, ARMv7-A/R: A8-502, ARMv7-M: A7-324
			 * RSB, ARMv7-A/R: A8-574, ARMv7-M: A7-372
			 * ORR, ARMv7-A/R: A8-518, ARMv7-M: A7-336
			 * ROR, ARMv7-A/R: A8-570, ARMv7-M: A7-368
			 * SBC, ARMv7-A/R: A8-594, ARMv7-M: A7-380
			 * TST, ARMv7-A/R: A8-746, ARMv7-M: A7-466
			 */
			COUNT_INST(alu);

			op = (insn16 >> 6) & 0xf;
			rm = (insn16 >> 3) & 0x7;
			rd = (insn16 >> 0) & 0x7;

			op0 = NAME_(load_reg)(cpssp, rd);
			op1 = NAME_(load_reg)(cpssp, rm);

			switch (op) {
			case 0x0:
				/* AND, ARMv7-A/R: A8-326, ARMv7-M: A7-201 */
				res = NAME_(ands)(cpssp, op0, op1, &c, &v);
				break;
			case 0x1:
				/* EOR, ARMv7-A/R: A8-384, ARMv7-M: A7-239 */
				res = NAME_(eors)(cpssp, op0, op1, &c, &v);
				break;
			case 0x2:
				/* LSL, ARMv7-A/R: A8-468, ARMv7-M: A7-300 */
				res = NAME_(lsls)(cpssp, op0, op1, &c, &v);
				break;
			case 0x3:
				/* LSR, ARMv7-A/R: A8-472, ARMv7-M: A7-304 */
				res = NAME_(lsrs)(cpssp, op0, op1, &c, &v);
				break;
			case 0x4:
				/* ASR, ARMv7-A/R: A8-332, ARMv7-M: A7-205 */
				res = NAME_(asrs)(cpssp, op0, op1, &c, &v);
				break;
			case 0x5:
				/* ADC, ARMv7-A/R: A8-302, ARMv7-M: A7-187 */
				res = NAME_(adcs)(cpssp, op0, op1, cpssp->NAME.flag_c, &c, &v);
				break;
			case 0x6:
				/* SBC, ARMv7-A/R: A8-594, ARMv7-M: A7-380 */
				res = NAME_(sbcs)(cpssp, op0, op1, cpssp->NAME.flag_c, &c, &v);
				break;
			case 0x7:
				/* ROR, ARMv7-A/R: A8-570, ARMv7-M: A7-368 */
				res = NAME_(rors)(cpssp, op0, op1, &c, &v);
				break;
			case 0x8:
				/* TST, ARMv7-A/R: A8-746, ARMv7-M: A7-466 */
				res = NAME_(ands)(cpssp, op0, op1, &c, &v);
				break;
			case 0x9:
				/* RSB, ARMv7-A/R: A8-574, ARMv7-M: A7-372 */
				res = NAME_(sbcs)(cpssp, 0, op1, 1, &c, &v);
				break;
			case 0xa:
				/* CMP, ARMv7-A/R: A8-372, ARMv7-M: A7-231 */
				res = NAME_(sbcs)(cpssp, op0, op1, 1, &c, &v);
				break;
			case 0xb:
				/* CMN, ARMv7-A/R: A8-366, ARMv7-M: A7-227 */
				res = NAME_(adcs)(cpssp, op0, op1, 0, &c, &v);
				break;
			case 0xc:
				/* ORR, ARMv7-A/R: A8-518, ARMv7-M: A7-336 */
				res = NAME_(orrs)(cpssp, op0, op1, &c, &v);
				break;
			case 0xd:
				/* MUL, ARMv7-A/R: A8-502, ARMv7-M: A7-324 */
				res = NAME_(mlas)(cpssp, op0, op1, 0, &c, &v);
				break;
			case 0xe:
				/* BIC, ARMv7-A/R: A8-342, ARMv7-M: A7-213 */
				res = NAME_(bics)(cpssp, op0, op1, &c, &v);
				break;
			case 0xf:
				/* MVN, ARMv7-A/R: A8-506, ARMv7-M: A7-328 */
				res = NAME_(mvns)(cpssp, 0, op1, &c, &v);
				break;
			default:
				assert(0); /* Cannot happen. */
			}

			if (! inside
			 || op == 0x8 /* TST */
			 || op == 0xa /* CMP */
			 || op == 0xb /* CMN */) {
				NAME_(store_flags)(cpssp, c, v, res);
			}
			if (op != 0x8 /* TST */
			 && op != 0xa /* CMP */
			 && op != 0xb /* CMN */) {
				NAME_(store_reg)(cpssp, rd, res);
			}
			break;

		case 0x1:
			/*
			 * Hi register operations/branch exchange
			 *
			 * ADD, ARMv7-A/R: 310, ARMv7-M: A7-191
			 * BLX, ARMv7-A/R: 350, ARMv7-M: A7-217
			 * BX,  ARMv7-A/R: 352, ARMv7-M: A7-218
			 * CMP, ARMv7-A/R: 372, ARMv7-M: A7-231
			 * MOV, ARMv7-A/R: 486, ARMv7-M: A7-314
			 */
			COUNT_INST(hireg);

			rm = (insn16 >> 3) & 0x7;
			rm |= ((insn16 >> 6) & 0x1) << 3;
			rd = (insn16 >> 0) & 0x7;
			rd |= ((insn16 >> 7) & 0x1) << 3;

			switch ((insn16 >> 8) & 0x3) {
			case 0x0:
				/* ADD, ARMv7-A/R: 310, ARMv7-M: A7-191 */
				op0 = NAME_(load_reg)(cpssp, rd);
				op1 = NAME_(load_reg)(cpssp, rm);
				res = NAME_(adcs)(cpssp, op0, op1, 0,
						&c, &v);
				NAME_(store_reg)(cpssp, rd, res);
				/* Don't modify flags! */
				break;
			case 0x1:
				/* CMP, ARMv7-A/R: 372, ARMv7-M: A7-231 */
				op0 = NAME_(load_reg)(cpssp, rd);
				op1 = NAME_(load_reg)(cpssp, rm);
				res = NAME_(sbcs)(cpssp, op0, op1, 1, &c, &v);
				NAME_(store_flags)(cpssp, c, v, res);
				break;
			case 0x2:
				/* MOV, ARMv7-A/R: 486, ARMv7-M: A7-314 */
				op0 = NAME_(load_reg)(cpssp, rm);
				res = op0;
				NAME_(store_reg)(cpssp, rd, res);
				/* Don't modify flags! */
				break;
			case 0x3:
				/* BLX, ARMv7-A/R: 350, ARMv7-M: A7-217 */
				/* BX,  ARMv7-A/R: 352, ARMv7-M: A7-218 */
				if ((insn16 >> 7) & 1) {
					NAME_(store_lr)(cpssp,
							cpssp->NAME.pc_next | cpssp->NAME.flag_t);
				}
				op0 = NAME_(load_reg)(cpssp, rm);
				res = op0;
				cpssp->NAME.flag_t = res & 1;
				NAME_(store_pc)(cpssp, res & ~1);
				/* Don't modify flags! */
				break;
			default:
				assert(0); /* Cannot happen. */
			}
			break;

		case 0x2: case 0x3:
			/*
			 * LDR(literal), ARMv7-A/R: A8-410, ARMv7-M: A7-254
			 */
			COUNT_INST(mempcrel);

			rd = (insn16 >> 8) & 0x7;
			offset = (insn16 >> 0) & 0xff;

			addr = NAME_(load_pc)(cpssp);
			addr &= ~3;
			addr += offset << 2;

			res = NAME_(ld32)(cpssp, addr);

			NAME_(store_reg)(cpssp, rd, res);
			break;

		default:
			assert(0); /* Cannot happen. */
		}
		break;

	case 0x5:
		/*
		 * Load/Store, ARMv7-A/R: A6-227
		 *
		 * LDR(register), ARMv7-A/R: A8-412, ARMv7-M: A7-256
		 * LDRB(register), ARMv7-A/R: A8-422, ARMv7-M: A7-262
		 * LDRH(register), ARMv7-A/R: A8-446, ARMv7-M: A7-278
		 * LDRSB(register), ARMv7-A/R: A8-454, ARMv7-M: A7-286
		 * LDRSH(register), ARMv7-A/R: A8-462, ARMv7-M: A7-294
		 * STR(register), ARMv7-A/R: A8-676, ARMv7-M: A7-428
		 * STRB(register), ARMv7-A/R: A8-682, ARMv7-M: A7-432
		 * STRH(register), ARMv7-A/R: A8-702, ARMv7-M: A7-444
		 */
		COUNT_INST(memreg);

		rm = (insn16 >> 6) & 0x7;
		rn = (insn16 >> 3) & 0x7;
		rd = (insn16 >> 0) & 0x7;

		op0 = NAME_(load_reg)(cpssp, rm);
		op1 = NAME_(load_reg)(cpssp, rn);
		res = NAME_(load_reg)(cpssp, rd);

		addr = op0 + op1;

		switch ((insn16 >> 9) & 0b111) {
		case 0b000:
			/* STR(register), ARMv7-A/R: A8-676, ARMv7-M: A7-428 */
			NAME_(st32)(cpssp, addr, res);
			break;
		case 0b001:
			/* STRH(register), ARMv7-A/R: A8-702, ARMv7-M: A7-444 */
			NAME_(st16)(cpssp, addr, (res >> 0) & 0xffff);
			break;
		case 0b010:
			/* STRB(register), ARMv7-A/R: A8-682, ARMv7-M: A7-432 */
			NAME_(st8)(cpssp, addr, (res >> 0) & 0xff);
			break;
		case 0b011:
			/* LDRSB(register), ARMv7-A/R: A8-454, ARMv7-M: A7-286 */
			res = (uint32_t) (int8_t) NAME_(ld8)(cpssp, addr);
			break;
		case 0b100:
			/* LDR(register), ARMv7-A/R: A8-412, ARMv7-M: A7-256 */
			res = NAME_(ld32)(cpssp, addr);
			break;
		case 0b101:
			/* LDRH(register), ARMv7-A/R: A8-446, ARMv7-M: A7-278 */
			res = (uint32_t) (uint16_t) NAME_(ld16)(cpssp, addr);
			break;
		case 0b110:
			/* LDRB(register), ARMv7-A/R: A8-422, ARMv7-M: A7-262 */
			res = (uint32_t) (uint8_t) NAME_(ld8)(cpssp, addr);
			break;
		case 0b111:
			/* LDRSH(register), ARMv7-A/R: A8-462, ARMv7-M: A7-294 */
			res = (uint32_t) (uint16_t) NAME_(ld16)(cpssp, addr);
			break;
		default:
			assert(0); /* Cannot happen. */
		}

		NAME_(store_reg)(cpssp, rd, res);
		break;

	case 0x6:
	case 0x7:
		/*
		 * load/store with immediate offset, ARMv7-A/R: A6-227
		 *
		 * LDR(immediate), ARMv7-A/R: A8-406, ARMv7-M: A7-252
		 * LDRB(immediate), ARMv7-A/R: A8-416, ARMv7-M: A7-258
		 * STR(immediate), ARMv7-A/R: A8-672, ARMv7-M: A7-426
		 * STRB(immediate), ARMv7-A/R: A8-678, ARMv7-M: A7-430
		 */
		COUNT_INST(memimmediate);

		offset = (insn16 >> 6) & 0x1f;
		rn = (insn16 >> 3) & 0x7;
		rd = (insn16 >> 0) & 0x7;

		op0 = NAME_(load_reg)(cpssp, rn);
		if ((insn16 >> 12) & 1) {
			/* Byte access. */
			addr = op0 + offset;
		} else {
			/* Word access. */
			addr = op0 + (offset << 2);
		}

		NAME_(ldst)(cpssp, 1, (insn16 >> 12) & 1, 0,
				(insn16 >> 11) & 1, rd, addr);
		break;

	case 0x8:
		/*
		 * load/store halfword, ARMv7-A/R: A6-227
		 *
		 * LDRH(immediate), ARMv7-A/R: A8-440, ARMv7-M: A7-274
		 * STRH(immediate), ARMv7-A/R: A8-698, ARMv7-M: A7-442
		 */
		COUNT_INST(memimmediate);

		offset = (insn16 >> 6) & 0x1f;
		rn = (insn16 >> 3) & 0x7;
		rd = (insn16 >> 0) & 0x7;

		op0 = NAME_(load_reg)(cpssp, rn);

		addr = op0 + (offset << 1);
		res = NAME_(load_reg)(cpssp, rd);

		switch ((insn16 >> 11) & 1) {
		case 0:
			/* STRH(immediate), ARMv7-A/R: A8-698, ARMv7-M: A7-442 */
			NAME_(st16)(cpssp, addr, res);
			break;
		case 1:
			/* LDRH(immediate), ARMv7-A/R: A8-440, ARMv7-M: A7-274 */
			res = NAME_(ld16)(cpssp, addr);
			break;
		default:
			assert(0); /* Cannot happen. */
		}

		NAME_(store_reg)(cpssp, rd, res);
		break;

	case 0x9:
		/*
		 * SP-relative load/store, ARMv7-A/R: A6-227
		 *
		 * LDR(immediate), ARMv7-A/R: 406, ARMv7-M: A7-252
		 * STR(immediate), ARMv7-A/R: 672, ARMv7-M: A7-426
		 */
		COUNT_INST(memsprel);

		rd = (insn16 >> 8) & 0x7;
		offset = (insn16 >> 0) & 0xff;

		op0 = NAME_(load_sp)(cpssp);

		addr = op0 + (offset << 2);
		res = NAME_(load_reg)(cpssp, rd);
		
		switch ((insn16 >> 11) & 1) {
		case 0:
			/* STR(immediate), ARMv7-A/R: 672, ARMv7-M: A7-426 */
			NAME_(st32)(cpssp, addr, res);
			break;
		case 1:
			/* LDR(immediate), ARMv7-A/R: 406, ARMv7-M: A7-252 */
			res = NAME_(ld32)(cpssp, addr);
			break;
		default:
			assert(0); /* Cannot happen. */
		}

		NAME_(store_reg)(cpssp, rd, res);
		break;

	case 0xa:
		/*
		 * Load address
		 *
		 * ADD(SP plus immediate), ARMv7-A/R: A8-316, ARMv7-M: A7-193
		 * ADR, ARMv7-A/R: A8-322, ARMv7-M: A7-197
		 */
		COUNT_INST(lea);

		rd = (insn16 >> 8) & 7;
		offset = (insn16 & 0xff);

		switch ((insn16 >> 11) & 1) {
		case 0:
			/* ADR, ARMv7-A/R: A8-322, ARMv7-M: A7-197 */
			op0 = NAME_(load_pc)(cpssp);
			op0 &= ~3;
			break;
		case 1:
			/* ADD(SP plus immediate), ARMv7-A/R: A8-316, ARMv7-M: A7-193 */
			op0 = NAME_(load_sp)(cpssp);
			break;
		default:
			assert(0); /* Cannot happen. */
		}

		res = NAME_(adcs)(cpssp, op0, offset << 2, 0, &c, &v);
			
		NAME_(store_reg)(cpssp, rd, res);
		break;

	case 0xb:
		/* Misc */
		op = (insn16 >> 8) & 0xf;
		switch (op) {
		case 0x0:
			/*
			 * Add offset to stack pointer
			 *
			 * ADD(SP plus immediate), ARMv7-A/R: A8-316, ARMv7-M: A7-193
			 * SUB(SP minus immediate), ARMv7-A/R: A8-716, ARMv7-M: A7-452
			 */
			COUNT_INST(addsubsp);

			offset = (insn16 >> 0) & 0x7f;

			op0 = NAME_(load_sp)(cpssp);
			op1 = offset << 2;

			switch ((insn16 >> 7) & 1) {
			case 0:
				/* ADD(SP plus immediate), ARMv7-A/R: A8-316, ARMv7-M: A7-193 */
				res = NAME_(adcs)(cpssp, op0, op1, 0, &c, &v);
				break;
			case 1:
				/* SUB(SP minus immediate), ARMv7-A/R: A8-716, ARMv7-M: A7-452 */
				res = NAME_(sbcs)(cpssp, op0, op1, 1, &c, &v);
				break;
			default:
				assert(0); /* Cannot happen. */
			}

			NAME_(store_sp)(cpssp, res);
			break;

#if 7 <= CONFIG_VERSION
		case 0x1: case 0x3:
		case 0x9: case 0xb:
			/*
			 * Compare and Branch on Zero
			 *
			 * CBNZ, CBZ, ARMv7-A/R: A8-356, ARMv7-M: A7-219
			 */
			offset = ((insn16 >> 9) & 1) << 5;
			offset |= ((insn16 >> 3) & 0x1f) << 0;
			rn = (insn16 >> 0) & 0x7;

			op0 = NAME_(load_reg)(cpssp, rn);

			if (((insn16 >> 11) & 1) ^ (op0 == 0)) {
				addr = NAME_(load_pc)(cpssp);
				addr += offset << 1;
				NAME_(store_pc)(cpssp, addr);
			}
			break;
#endif /* CONFIG_VERSION < 7 */

#if 6 <= CONFIG_VERSION
		case 0x2:
			/*
			 * sign/zero extend.
			 *
			 * SXTB, ARMv7-A/R: A8-730, ARMv7-M: A7-459
			 * SXTH, ARMv7-A/R: A8-734, ARMv7-M: A7-461
			 * UXTB, ARMv7-A/R: A8-812, ARMv7-M: A7-498
			 * UXTH, ARMv7-A/R: A8-816, ARMv7-M: A7-500
			 */
			COUNT_INST(extend);

			rd = (insn16 >> 0) & 0x7;
			rm = (insn16 >> 3) & 0x7;

			op0 = NAME_(load_reg)(cpssp, rm);

			switch ((insn16 >> 6) & 0x3) {
			case 0x0:
				/* SXTH, ARMv7-A/R: A8-734, ARMv7-M: A7-461 */
				res = (int32_t) (int16_t) op0;
				break;
			case 0x1:
				/* SXTB, ARMv7-A/R: A8-730, ARMv7-M: A7-459 */
				res = (int32_t) (int8_t) op0;
				break;
			case 0x2:
				/* UXTH, ARMv7-A/R: A8-816, ARMv7-M: A7-500 */
				res = (uint32_t) (uint16_t) op0;
				break;
			case 0x3:
				/* UXTB, ARMv7-A/R: A8-812, ARMv7-M: A7-498 */
				res = (uint32_t) (uint8_t) op0;
				break;
			default:
				assert(0); /* Cannot happen. */
			}

			NAME_(store_reg)(cpssp, rd, res);
			break;
#endif /* 6 <= CONFIG_VERSION */

		case 0x4: case 0x5:
		case 0xc: case 0xd:
			/*
			 * push/pop registers
			 *
			 * POP, ARMv7-A/R: A8-534, ARMv7-M: A7-348
			 * PUSH, ARMv7-A/R: A8-538, ARMv7-M: A7-350
			 */
			COUNT_INST(pushpop);

			reglist = (insn16 >> 0) & 0xff;

			switch ((insn16 >> 11) & 1) {
			case 0:
				/* PUSH, ARMv7-A/R: A8-538, ARMv7-M: A7-350 */
				if ((insn16 >> 8) & 1) {
					/* Push link register. */
					addr = NAME_(load_sp)(cpssp);
					addr -= 4;
					res = NAME_(load_lr)(cpssp);
					NAME_(st32)(cpssp, addr, res);
					NAME_(store_sp)(cpssp, addr);
				}
				for (i = 7; 0 <= i; i--) {
					if (! ((reglist >> i) & 1)) {
						continue;
					}
					/* Push register. */
					addr = NAME_(load_sp)(cpssp);
					addr -= 4;
					res = NAME_(load_reg)(cpssp, i);
					NAME_(st32)(cpssp, addr, res);
					NAME_(store_sp)(cpssp, addr);
				}
				break;
			case 1:
				/* POP, ARMv7-A/R: A8-534, ARMv7-M: A7-348 */
				for (i = 0; i <= 7; i++) {
					if (! ((reglist >> i) & 1)) {
						continue;
					}

					addr = NAME_(load_sp)(cpssp);
					res = NAME_(ld32)(cpssp, addr);
					NAME_(store_reg)(cpssp, i, res);
					addr += 4;
					NAME_(store_sp)(cpssp, addr);
				}
				if ((insn16 >> 8) & 1) {
					addr = NAME_(load_sp)(cpssp);
					res = NAME_(ld32)(cpssp, addr);
					NAME_(store_pc)(cpssp, res);
					addr += 4;
					NAME_(store_sp)(cpssp, addr);
				}
				break;
			default:
				assert(0); /* Cannot happen. */
			}
			break;

		case 0x6:
			/*
			 * Change Processor State
			 *
			 * CPS, ARMv7-A/R: B9-1978, ARMv7-M: B5-731
			 */
			COUNT_INST(cps);

			switch ((insn16 >> 5) & 0x7) {
			case 0x3:
				/* CPS, ARMv7-A/R: B9-1978, ARMv7-M: B5-731 */
				if (cpssp->NAME.current_mode == MODE_HANDLER
				 || ! cpssp->NAME.control_npriv) {
					op0 = (insn16 >> 4) & 1;
					if ((insn16 >> 1) & 1) {
						NAME_(primask_set)(cpssp, op0);
					}
					if ((insn16 >> 0) & 1) {
						assert(0); /* FIXME */
					}
				}
				break;
			default:
				assert(0); /* UNDEFINED */
			}
			break;

		case 0x7:
			assert(0); /* UNDEFINED */
			break;
		case 0x8:
			assert(0); /* UNDEFINED */
			break;

#if 6 <= CONFIG_VERSION
		case 0xa:
			/*
			 * Reverse
			 *
			 * REV, ARMv7-A/R: A8-564, ARMv7-M: A7-363
			 * REV16, ARMv7-A/R: A8-564, ARMv7-M: A7-364
			 * REVSH, ARMv7-A/R: A8-566, ARMv7-M: A7-365
			 */
			COUNT_INST(reverse);

			rd = (insn16 >> 0) & 0x7;
			rm = (insn16 >> 3) & 0x7;

			op0 = NAME_(load_reg)(cpssp, rm);

			switch ((insn16 >> 6) & 0b11) {
			case 0b00:
				/* REV, ARMv7-A/R: A8-564, ARMv7-M: A7-363 */
				res = ((op0 >> 24) & 0xff)
					| ((op0 >> 8) & 0xff00)
					| ((op0 << 8) & 0xff0000)
					| ((op0 << 24) & 0xff000000);
				break;
			case 0b01:
				/* REV16, ARMv7-A/R: A8-564, ARMv7-M: A7-364 */
				res = ((op0 >> 8) & 0xff0000)
					| ((op0 << 8) & 0xff000000)
					| ((op0 >> 8) & 0xff)
					| ((op0 << 8) & 0xff00);
				break;
			case 0b11:
				/* REVSH, ARMv7-A/R: A8-566, ARMv7-M: A7-365 */
				op0 = ((op0 >> 8) & 0xff)
					| ((op0 << 8) & 0xff00);
				res = (int32_t) (int16_t) op0;
				break;
			default:
				assert(0); /* UNDEFINED */
			}

			NAME_(store_reg)(cpssp, rd, res);
			break;
#endif /* 6 <= CONFIG_VERSION */

#if 5 <= CONFIG_VERSION
		case 0xe:
			/*
			 * Software breakpoint
			 *
			 * BKPT, ARMv7-A/R: A8-346, ARMv7-M: A7-215
			 */
			assert(0); /* FIXME */
			break;
#endif /* 5 <= CONFIG_VERSION */

#if (CONFIG_VERSION == 6 && CONFIG_THUMB2) || 7 <= CONFIG_VERSION
		case 0xf:
			/*
			 * If-Then and Hints, ARMv7-A/R: A6-229, ARMv7-M: A5-133
			 *
			 * IT, ARMv7-A/R: A8-390, ARMv7-M: A7-242
			 * NOP, ARMv7-A/R: A8-510, ARMv7-M: A7-331
			 * SEV, ARMv7-A/R: A8-606, ARMv7-M: A7-385
			 * WFE, ARMv7-A/R: A8-1108, ARMv7-M: A7-560
			 * WFI, ARMv7-A/R: A8-1106, ARMv7-M: A7-561
			 * YIELD, ARMv7-A/R: A8-1108, ARMv7-M: A7-562
			 */
			COUNT_INST(hint);

			if (((insn16 >> 0) & 0xf) != 0x0) {
				/* IT, ARMv7-A/R: A8-390, ARMv7-M: A7-242 */
				cpssp->NAME.itstate = insn16 & 0xff;

			} else
#if CONFIG_VERSION < 7
			{
				/* NOP, ARMv7-A/R: A8-510, ARMv7-M: A7-331 */
				/* SEV, ARMv7-A/R: A8-606, ARMv7-M: A7-385 */
				/* WFE, ARMv7-A/R: A8-1108, ARMv7-M: A7-560 */
				/* WFI, ARMv7-A/R: A8-1106, ARMv7-M: A7-561 */
				/* YIELD, ARMv7-A/R: A8-1108, ARMv7-M: A7-562 */
				/* Nothing to do... */
			}
#else /* CONFIG_VERSION < 7 */
			switch ((insn16 >> 4) & 0xf) {
			case 0x0:
				/* NOP, ARMv7-A/R: A8-510, ARMv7-M: A7-331 */
				/* Nothing to do... */
				break;
			case 0x1:
				/* YIELD, ARMv7-A/R: A8-1108, ARMv7-M: A7-562 */
				/* FIXME */
				break;
			case 0x2:
				/* WFE, ARMv7-A/R: A8-1108, ARMv7-M: A7-560 */
				/* FIXME */
				break;
			case 0x3:
				/* WFI, ARMv7-A/R: A8-1106, ARMv7-M: A7-561 */
				cpssp->NAME.wfi = 1;
				NAME_(stop)(cpssp, cpssp->NAME.sleepdeep);
				break;
			case 0x4:
				/* SEV, ARMv7-A/R: A8-606, ARMv7-M: A7-385 */
				/* FIXME */
				break;
			default:
				assert(0); /* UNDEFINED */
			}
#endif /* CONFIG_VERSION < 7 */
			break;
#endif /* (CONFIG_VERSION == 6 && CONFIG_THUMB2) || 7 <= CONFIG_VERSION */

		default:
			assert(0); /* UNDEFINED */
		}
		break;

	case 0xc:
		/*
		 * Multiple load/store, ARMv7-M: A4-115
		 *
		 * LDM, LDMIA, ARMv7-A/R: A8-396, ARMv7-M: A7-248
		 * STMIA, ARMv7-A/R: A8-664, ARMv7-M: A7-422
		 */
		COUNT_INST(memmultiple);

		rn = (insn16 >> 8) & 0x7;
		reglist = (insn16 >> 0) & 0xff;

		addr = NAME_(load_reg)(cpssp, rn);

		switch ((insn16 >> 11) & 1) {
		case 0:
			/* STMIA, ARMv7-A/R: A8-664, ARMv7-M: A7-422 */
			for (i = 0; i < 8; i++) {
				if ((reglist >> i) & 1) {
					res = NAME_(load_reg)(cpssp, i);
					NAME_(st32)(cpssp, addr, res);
					addr += 4;
				}
			}
			NAME_(store_reg)(cpssp, rn, addr);
			break;
		case 1:
			/* LDM, LDMIA, ARMv7-A/R: A8-396, ARMv7-M: A7-248 */
			for (i = 0; i < 8; i++) {
				if ((reglist >> i) & 1) {
					res = NAME_(ld32)(cpssp, addr);
					NAME_(store_reg)(cpssp, i, res);
					addr += 4;
				}
			}
			if (! ((reglist >> rn) & 1)) {
				NAME_(store_reg)(cpssp, rn, addr);
			}
			break;
		default:
			assert(0); /* Cannot happen. */
		}
		break;

	case 0xd:
		cond = (insn16 >> 8) & 0xf;

		switch (cond) {
		case 0b1110:
			/*
			 * permanently undefined
			 *
			 * UDF, ARMv7-A/R: A8-758, ARMv7-M: A7-471
			 */
			assert(0); /* UNDEFINED */
			break;

		case 0b1111:
			/*
			 * Supervisor call, Software interrupt
			 *
			 * SVC, SWI, ARMv7-A/R: A8-720, ARMv7-M: A7-455
			 */
			NAME_(SVCall_pending_set)(cpssp, 1);
			assert(cpssp->NAME.exception_pending);
			break;

		default:
			/*
			 * conditional branch
			 *
			 * B, ARMv7-A/R: A8-334, ARMv7-M: A7-207
			 */
			COUNT_INST(branchconditional);

			offset = (int32_t) (int8_t) ((insn16 >> 0) & 0xff);

			if (NAME_(condition)(cpssp, cond)) {
				addr = NAME_(load_pc)(cpssp);
				addr += offset << 1;
				NAME_(store_pc)(cpssp, addr);
			}
			break;
		}
		break;

	case 0xe:
		if (((insn16 >> 11) & 1) == 0) {
			/*
			 * unconditional branch
			 *
			 * B, ARMv7-A/R: A8-334, ARMv7-M: A7-207
			 */
			COUNT_INST(branchunconditional);

			offset = ((int32_t) insn16 << 21) >> 21;

			addr = NAME_(load_pc)(cpssp);
			addr += offset << 1;
			NAME_(store_pc)(cpssp, addr);

		} else {
			/* first part of thumb2 instruction */
			/* A5.1 */
			assert(0); /* Mustn't happen. */
		}
		break;

	case 0xf:
		/*
		 * first part of thumb2 instruction
		 */
		assert(0); /* Mustn't happen. */
		break;

	default:
		assert(0); /* Cannot happen. */
	}
}

static void
NAME_(exc)(struct cpssp *cpssp)
{       
	uint8_t vec;
	uint8_t frameptralign;
	uint32_t frameptr;
	uint32_t xpsr;
	uint32_t start;
	
	vec = NAME_(exc_ack)(cpssp);
	
	/*
	 * Exception entry behavior
	 * B1.5.6
	 */
	/* PushStack(); */
	if (cpssp->NAME.control_spsel
	 && cpssp->NAME.current_mode == MODE_THREAD) {
		frameptralign = (cpssp->NAME.sp_process >> 2) & 0b1;
		cpssp->NAME.sp_process = (cpssp->NAME.sp_process - 0x20)
				& ~0b100;
		frameptr = cpssp->NAME.sp_process;
	} else {
		frameptralign = (cpssp->NAME.sp_main >> 2) & 0b1;
		cpssp->NAME.sp_main = (cpssp->NAME.sp_main - 0x20)
				& ~0b100;
		frameptr = cpssp->NAME.sp_main;
	}
	NAME_(st32)(cpssp, frameptr + 0x00, NAME_(load_reg)(cpssp, 0));
	NAME_(st32)(cpssp, frameptr + 0x04, NAME_(load_reg)(cpssp, 1));
	NAME_(st32)(cpssp, frameptr + 0x08, NAME_(load_reg)(cpssp, 2));
	NAME_(st32)(cpssp, frameptr + 0x0c, NAME_(load_reg)(cpssp, 3));
	NAME_(st32)(cpssp, frameptr + 0x10, NAME_(load_reg)(cpssp, 12));
	NAME_(st32)(cpssp, frameptr + 0x14, NAME_(load_lr)(cpssp));
	switch (vec) {
	case 0x00:
		/* Reset Stack Pointer */
		assert(0);
	case 0x01:
		/* Reset */
		assert(0);
	case 0x02:
		/* NMI */
		assert(0);
	case 0x03:
		/* HardFault */
		assert(0);
	case 0x04 ... 0x0a:
		/* Reserved */
		assert(0);
	case 0x0b:
		/* SVC */
		NAME_(st32)(cpssp, frameptr + 0x18, cpssp->NAME.pc_next);
		break;
	case 0x0c ... 0x0d:
		/* Reserved */
		assert(0);
	case 0x0e:
		/* PendSV */
		/*FALLTHROUGH*/
	case 0x0f:
		/* SysTick */
		/*FALLTHROUGH*/
	case 0x10 ... 0x30:
		/* Interrupt */
		NAME_(st32)(cpssp, frameptr + 0x18, cpssp->NAME.pc_next);
		break;
	default:
		assert(0); /* FIXME */
	}
	xpsr = NAME_(load_xpsr)(cpssp);
	xpsr &= ~(1 << 9);
	xpsr |= frameptralign << 9; 
	NAME_(st32)(cpssp, frameptr + 0x1c, xpsr);
	if (cpssp->NAME.current_mode == MODE_HANDLER) {
		NAME_(store_lr)(cpssp, 0xfffffff1);
	} else if (cpssp->NAME.control_spsel == 0) {
		NAME_(store_lr)(cpssp, 0xfffffff9);
	} else {
		NAME_(store_lr)(cpssp, 0xfffffffd);
	}
	
	/* ExceptionTaken(); */
	/* FIXME */
	cpssp->NAME.current_mode = MODE_HANDLER;
	NAME_(store_ipsr)(cpssp, vec);
	cpssp->NAME.control_spsel = 0;
	/* cpssp->NAME.control_npriv unchanged. */
	/* FIXME */
	start = NAME_(ld32)(cpssp, cpssp->NAME.vtor | (vec << 2));
	cpssp->NAME.flag_t = (start >> 0) & 1;
	NAME_(store_pc)(cpssp, start & ~1);
}

static void
NAME_(exc_return)(struct cpssp *cpssp)
{
	uint8_t exc_return;
	int vec;
	uint32_t frameptr;
	uint32_t psr;
	uint32_t pc;

	/*
	 * Exception return behavior
	 * B1.5.8
	 */
	exc_return = (cpssp->NAME.pc_next >> 0) & 0b1111;
	vec = NAME_(load_ipsr)(cpssp);

	switch (exc_return) {
	case 0b0000:
	case 0b0001:
		/* Return to Handler. */
		frameptr = cpssp->NAME.sp_main;
		cpssp->NAME.current_mode = MODE_HANDLER;
		cpssp->NAME.control_spsel = 0;
		break;
	case 0b1000:
	case 0b1001:
		/* Return to Thread using Main stack. */
		frameptr = cpssp->NAME.sp_main;
		cpssp->NAME.current_mode = MODE_THREAD;
		cpssp->NAME.control_spsel = 0;
		break;
	case 0b1100:
	case 0b1101:
		/* Return to Thread using Process stack. */
		frameptr = cpssp->NAME.sp_process;
		cpssp->NAME.current_mode = MODE_THREAD;
		cpssp->NAME.control_spsel = 1;
		break;
	default:
		assert(0); /* FIXME */
	}

	/* Deactivate */
	NAME_(exc_eoi)(cpssp, vec);

	/* PopStack */
	NAME_(store_reg)(cpssp, 0, NAME_(ld32)(cpssp, frameptr + 0x00));
	NAME_(store_reg)(cpssp, 1, NAME_(ld32)(cpssp, frameptr + 0x04));
	NAME_(store_reg)(cpssp, 2, NAME_(ld32)(cpssp, frameptr + 0x08));
	NAME_(store_reg)(cpssp, 3, NAME_(ld32)(cpssp, frameptr + 0x0c));
	NAME_(store_reg)(cpssp, 12, NAME_(ld32)(cpssp, frameptr + 0x10));
	NAME_(store_lr)(cpssp, NAME_(ld32)(cpssp, frameptr + 0x14));
	pc = NAME_(ld32)(cpssp, frameptr + 0x18);
	NAME_(store_pc)(cpssp, pc & ~1);
	cpssp->NAME.flag_t = pc & 1;
	psr = NAME_(ld32)(cpssp, frameptr + 0x1c);
	switch (exc_return) {
	case 0b0000:
	case 0b0001: /* Returning to Handler mode. */
	case 0b1000:
	case 0b1001: /* Returning to Thread mode using Main stack. */
		cpssp->NAME.sp_main = (cpssp->NAME.sp_main + 0x20)
				| (((psr >> 9) & 1) << 2);
		break;
	case 0b1100:
	case 0b1101: /* Returning to Thread mode using Process stack. */
		cpssp->NAME.sp_process = (cpssp->NAME.sp_process + 0x20)
				| (((psr >> 9) & 1) << 2);
		break;
	default:
		assert(0); /* FIXME */
	}
	NAME_(store_apsr)(cpssp, psr & 0xf0000000);
	if (cpssp->NAME.current_mode == MODE_THREAD
	 && cpssp->NAME.control_npriv) {
		NAME_(store_ipsr)(cpssp, 0x00000000);
	} else {
		NAME_(store_ipsr)(cpssp, psr & 0x0000003f);
	}
	NAME_(store_epsr)(cpssp, psr & (1 << 24));
}

static void
NAME_(insn_step)(struct cpssp *cpssp)
{
	uint32_t pc_next;

	pc_next = cpssp->NAME.pc_next;

#if 0
	if (pc_next == 0x8000a34) {
		loglevel = 1;
	}
#endif
	if (unlikely(
	    (cpssp->NAME.brkstart[0] <= pc_next && pc_next < cpssp->NAME.brkend[0])
	 || (cpssp->NAME.brkstart[1] <= pc_next && pc_next < cpssp->NAME.brkend[1])
	 || (cpssp->NAME.brkstart[2] <= pc_next && pc_next < cpssp->NAME.brkend[2])
	 || (cpssp->NAME.brkstart[3] <= pc_next && pc_next < cpssp->NAME.brkend[3]))) {
		gdb_stop(cpssp->NAME.gdb, 5); /* SIGTRAP */

	} else if (unlikely(cpssp->NAME.wfi)) {
		/*
		 * Waiting for Interrupt
		 */
		/* Nothing to do... */
		COUNT_INST(wfi);

	} else if (! cpssp->NAME.flush
		&& ((pc_next >> 28) & 0xf) == 0xf) {
		/*
		 * Exception Return
		 */
		if (DEBUG_CONTROL_FLOW
		 && loglevel) {
			fprintf(stderr, "Exception return\n");
		}
		NAME_(exc_return)(cpssp);

	} else if (! cpssp->NAME.flush
		&& cpssp->NAME.exception_pending
		&& ! cpssp->NAME.insn_prev) {
		/*
		 * Exception/Interrupt Pending
		 */
		if (DEBUG_CONTROL_FLOW
		 && loglevel) {
			fprintf(stderr, "Exception at %08lx\n",
					cpssp->NAME.pc_next);
		}
		NAME_(exc)(cpssp);
	} else {
		/*
		 * Execute Instruction
		 */
		if (DEBUG_CONTROL_FLOW
		 && loglevel) {
			fprintf(stderr, "Executing %08lx at %08lx\n", cpssp->NAME.insn_next, pc_next);
#if DEBUG_CONTROL_FLOW_REGS
			NAME_(dump)(cpssp);
#endif
		}

		if (cpssp->NAME.stall) {
			/*
			 * Pipeline stalled because of ld/st.
			 */
			/* Just wait... */
			cpssp->NAME.stall = 0;

		} else if (cpssp->NAME.flush) {
			/*
			 * Flush pipeline.
			 */
			cpssp->NAME.pc = cpssp->NAME.pc_next;
			cpssp->NAME.pc_next = cpssp->NAME.pc_next_next;
			cpssp->NAME.pc_next_next += 2;

			cpssp->NAME.insn = cpssp->NAME.insn_next;
			cpssp->NAME.insn_next
				= NAME_(ld16_code)(cpssp, cpssp->NAME.pc_next);

			cpssp->NAME.flush = 0;

#if (CONFIG_VERSION == 6 && CONFIG_THUMB2) || CONFIG_VERSION == 7
		} else if (cpssp->NAME.insn_prev) {
			/*
			 * Second part of 32-bit instruction.
			 */
			/* Don't touch cpssp->NAME.pc! */
			cpssp->NAME.pc_next = cpssp->NAME.pc_next_next;
			cpssp->NAME.pc_next_next += 2;

			cpssp->NAME.insn = (cpssp->NAME.insn_prev << 16) 
					| cpssp->NAME.insn_next;
			cpssp->NAME.insn_next
				= NAME_(ld16_code)(cpssp, cpssp->NAME.pc_next);

			NAME_(insn_exec_thumb2)(cpssp, cpssp->NAME.insn);

			cpssp->NAME.insn_prev = 0;

		} else if (((cpssp->NAME.insn_next >> 11) & 0b11111) == 0b11101
			|| ((cpssp->NAME.insn_next >> 11) & 0b11110) == 0b11110) {
			/*
			 * First part of 32-bit instruction.
			 */
			cpssp->NAME.pc = cpssp->NAME.pc_next;
			cpssp->NAME.pc_next = cpssp->NAME.pc_next_next;
			cpssp->NAME.pc_next_next += 2;

			cpssp->NAME.insn_prev = cpssp->NAME.insn_next;
			cpssp->NAME.insn_next
				= NAME_(ld16_code)(cpssp, cpssp->NAME.pc_next);
#endif /* (CONFIG_VERSION == 6 && CONFIG_THUMB2) || CONFIG_VERSION == 7 */

		} else {
			/*
			 * 16-bit instruction.
			 */
			cpssp->NAME.pc = cpssp->NAME.pc_next;
			cpssp->NAME.pc_next = cpssp->NAME.pc_next_next;
			cpssp->NAME.pc_next_next += 2;

			cpssp->NAME.insn = cpssp->NAME.insn_next;
			cpssp->NAME.insn_next
				= NAME_(ld16_code)(cpssp, cpssp->NAME.pc_next);
			
			NAME_(insn_exec_thumb)(cpssp, cpssp->NAME.insn);
		}
	}
}
