MODULE ARMDecoder;

IMPORT SYSTEM, Decoder, Streams, KernelLog;

CONST

	objFileSuffix = "Oba";
	none = -1;

	(* argument structure *)
	ArgNone = -2;
	ArgImm = 1;
	ArgReg = 2;
	ArgShift = 3;
	ArgRegImm = 11;
	ArgRegReg = 12;
	ArgRegShift = 13;
	ArgRegMem = 14;
	ArgRegRList = 15;
	ArgRegRegImm = 21;
	ArgRegRegReg = 22;
	ArgRegRegShift = 23;
	ArgRegRegMem = 24;
	ArgRegRegRegReg = 31;
	ArgRegRegRegImm = 32;
	ArgRegRegRegShift = 33;
	ArgCProcRegMem = 41;
	ArgCProcImmRegRegRegImm = 42;

	(* registers *)
	FP = 12; SP = 13; LR = 14; PC = 15; CPSR = 16; SPSR = 17;

	(* status register bits *)
	SRegC = 0; SRegX = 1; SRegS = 2; SRegF = 3;

	(* instructions *)
	opUNDEFINED = -2;
	opADC = 1;
	opADD = 2;
	opAND = 3;
	opASR = 4;
	opB = 5;
	opBIC = 6;
	opBKPT = 7;
	opBL = 8;
	opBLX = 9;
	opBX = 10;
	opCDP = 11;
	opCDP2 = 12;
	opCLZ = 13;
	opCMN = 14;
	opCMP = 15;
	opEOR = 16;
	opLDC = 17;
	opLDC2 = 18;
	opLDM = 19;
	opLDR = 20;
	opLDRB = 21;
	opLDRBT = 22;
	opLDRH = 23;
	opLDRSB = 24;
	opLDRSH = 25;
	opLDRT = 26;
	opLSL = 27;
	opLSR = 28;
	opMCR = 29;
	opMCR2 = 30;
	opMLA = 31;
	opMOV = 32;
	opMRC = 33;
	opMRC2 = 34;
	opMRS = 61;
	opMSR = 35;
	opMUL = 36;
	opMVN = 37;
	opORR = 38;
	opROR = 39;
	opRRX = 40;
	opRSB = 41;
	opRSC = 42;
	opSBC = 43;
	opSMLAL = 44;
	opSMULL = 45;
	opSTC = 46;
	opSTC2 = 47;
	opSTM = 48;
	opSTR = 49;
	opSTRB = 50;
	opSTRBT = 51;
	opSTRH = 52;
	opSTRT = 53;
	opSUB = 54;
	opSWI = 55;
	opSWP = 56;
	opSWPB = 62;
	opTEQ = 57;
	opTST = 58;
	opUMLAL = 59;
	opUMULL = 60;

	(* conditions *)
	EQ = 0;
	NE = 1;
	CSHS = 2;
	CCLO = 3;
	MI = 4;
	PL = 5;
	VS = 6;
	VC = 7;
	HI = 8;
	LS = 9;
	GE = 10;
	LT = 11;
	GT = 12;
	LE = 13;
	AL = 14;
	NV = 15;

	(* argument representations *)
	RepInt = 1;
	RepHex = 2;
	RepRelJmp = 10;

	(* immediate reg operations *)
	LSL = 0;
	LSR = 1;
	ASR = 2;
	ROR = 3;
	RRX = 4;

	(* memory access addressing modes *)
	AddrModeReg = 1;
	AddrModeRegImm = 2;
	AddrModeRegReg = 3;
	AddrModeRegRegScale = 4;
	AddrModeDA = 5;
	AddrModeIA = 6;
	AddrModeDB = 7;
	AddrModeIB = 8;
	RegUpdateNone = 0;
	RegUpdatePre = 1;
	RegUpdatePost = 2;

TYPE
	ARMArg = OBJECT
	END ARMArg;

	ARMArgImm = OBJECT (ARMArg)
		VAR
			imm, rep : LONGINT;
		PROCEDURE &New *(imm, rep : LONGINT);
		BEGIN
			SELF.imm := imm;
			SELF.rep := rep
		END New;
	END ARMArgImm;

	ARMArgReg = OBJECT (ARMArg)
		VAR
			reg : LONGINT;
			isCReg : BOOLEAN; (* coprocessor register? *)
			sregMask : SET; (* for CPSR, SPSR registers *)
		PROCEDURE &New *(reg : LONGINT);
		BEGIN
			SELF.reg := reg;
			isCReg := FALSE
		END New;
	END ARMArgReg;

	ARMArgRList = OBJECT (ARMArg)
		VAR
			regs : SET;
			addrMode : LONGINT;
		PROCEDURE &New *(regs : SET);
		BEGIN
			SELF.regs := regs;
		END New;
	END ARMArgRList;

	ARMArgMem = OBJECT (ARMArg)
		VAR
			addrMode, reg, regOffs, regScale, shift, immOffs : LONGINT;
			width : LONGINT;
			signed, translation : BOOLEAN;
			regUpdate : LONGINT;
		PROCEDURE &New *(adrMode, reg : LONGINT);
		BEGIN
			SELF.addrMode := adrMode;
			SELF.reg := reg;
			width := 4;
			regUpdate := RegUpdateNone;
			signed := FALSE;
			translation := FALSE
		END New;
	END ARMArgMem;

	ARMArgShift = OBJECT (ARMArg)
		VAR
			shiftImmOrReg, operation : LONGINT;
			reg : BOOLEAN;
		PROCEDURE &New *(operation, shiftImmOrReg : LONGINT; reg : BOOLEAN);
		BEGIN
			SELF.operation := operation;
			SELF.shiftImmOrReg := shiftImmOrReg;
			SELF.reg := reg
		END New;
	END ARMArgShift;

	ARMArgCProc = OBJECT (ARMArg)
		VAR
			cproc : LONGINT;
		PROCEDURE &New *(cproc : LONGINT);
		BEGIN
			SELF.cproc := cproc
		END New;

	END ARMArgCProc;

	ARMOpcode = OBJECT (Decoder.Opcode)
	VAR
		argStructure : LONGINT;
		op, cond : LONGINT;
		ccUpdate : BOOLEAN;
		arg1, arg2, arg3, arg4, arg5, arg6 : ARMArg;

		PROCEDURE &New*(proc : Decoder.ProcedureInfo; stream : Streams.Writer);
		BEGIN
			New^(proc, stream);
			instr := -1;
			ccUpdate := FALSE;
			argStructure := none
		END New;

		PROCEDURE PrintOpcodeBytes (w : Streams.Writer);
		BEGIN
			w.Hex(op, 0)
		END PrintOpcodeBytes;

		PROCEDURE PrintInstruction (w : Streams.Writer);
		VAR str : ARRAY 20 OF CHAR; writeCondition, writeLSMAddrMode : BOOLEAN;
		BEGIN
			writeCondition := TRUE; writeLSMAddrMode := FALSE;
			CASE instr OF
				opADC : str := "ADC"
				| opADD : str := "ADD"
				| opAND : str := "AND"
				| opASR : str := "ASR"
				| opB : str := "B"
				| opBIC : str := "BIC"
				| opBKPT : str := "BKPT"
				| opBL : str := "BL"
				| opBLX : str := "BLX"; writeCondition := FALSE
				| opBX : str := "BX"
				| opCDP : str := "CDP"
				| opCDP2 : str := "CDP2"; writeCondition := FALSE
				| opCLZ : str := "CLZ"
				| opCMN : str := "CMN"
				| opCMP : str := "CMP"
				| opEOR : str := "EOR"
				| opLDC : str := "LDC"
				| opLDC2 : str := "LDC2"; writeCondition := FALSE
				| opLDM : str := "LDM"; writeLSMAddrMode := TRUE
				| opLDR : str := "LDR"
				| opLDRB : str := "LDRB"
				| opLDRBT : str := "LDRBT"
				| opLDRH : str := "LDRH"
				| opLDRSB : str := "LDRSB"
				| opLDRSH : str := "LDRSH"
				| opLDRT : str := "LDRT"
				| opLSL : str := "LSL"
				| opLSR : str := "LSR"
				| opMCR : str := "MCR"
				| opMCR2 : str := "MCR2"; writeCondition := FALSE
				| opMLA : str := "MLA"
				| opMOV : str := "MOV"
				| opMRC : str := "MRC"
				| opMRC2 : str := "MRC2"; writeCondition := FALSE
				| opMRS : str := "MRS"
				| opMSR : str := "MSR"
				| opMUL : str := "MUL"
				| opMVN : str := "MVN"
				| opORR : str := "ORR"
				| opROR : str := "ROR"
				| opRRX : str := "RRX"
				| opRSB : str := "RSB"
				| opRSC : str := "RSC"
				| opSBC : str := "SBC"
				| opSMLAL : str := "SMLAL"
				| opSMULL : str := "SMULL"
				| opSTC : str := "STC"
				| opSTC2 : str := "STC2"; writeCondition := FALSE
				| opSTM : str := "STM"; writeLSMAddrMode := TRUE
				| opSTR : str := "STR"
				| opSTRB : str := "STRB"
				| opSTRBT : str := "STRBT"
				| opSTRH : str := "STRH"
				| opSTRT : str := "STRT"
				| opSUB : str := "SUB"
				| opSWI : str := "SWI"
				| opSWP : str := "SWP"
				| opSWPB : str := "SWPB"
				| opTEQ : str := "TEQ"
				| opTST : str := "TST"
				| opUMLAL : str := "UMLAL"
				| opUMULL : str := "UMULL"
				| opUNDEFINED : str := "{UNDEFINED}"
			ELSE
				KernelLog.String("Unknown instr = "); KernelLog.Int(instr, 0); KernelLog.String(", op = "); KernelLog.Hex(op, -1); KernelLog.Ln;
				str := "[unknown]"
			END;
			w.String(str);
			IF writeCondition THEN PrintCondition(w) END;
			IF writeLSMAddrMode THEN PrintLSMAddrMode(w) END;
			IF ccUpdate THEN w.String("  S")
			ELSIF argStructure = ArgRegMem THEN
				w.String("  ");
				IF arg2(ARMArgMem).signed THEN w.Char('S') END;
				IF arg2(ARMArgMem).width = 1 THEN w.Char('B')
				ELSIF arg2(ARMArgMem).width = 2 THEN w.Char('H')
				END;
				IF arg2(ARMArgMem).translation THEN w.Char('T') END;
			END
		END PrintInstruction;

		PROCEDURE PrintCondition (w : Streams.Writer);
		VAR str : ARRAY 6 OF CHAR;
		BEGIN
			w.Char(' ');
			CASE cond OF
				EQ : str := "EQ"
				| NE : str := "NE"
				| CSHS : str := "CS/HS"
				| CCLO : str := "CC/LO"
				| MI : str := "MI"
				| PL : str := "PL"
				| VS : str := "VS"
				| VC : str := "VC"
				| HI : str := "HI"
				| LS : str := "LS"
				| GE : str := "GE"
				| LT : str := "LT"
				| GT : str := "GT"
				| LE : str := "LE"
				| AL : str := ""
				| NV : str := "NV"
			END;
			w.String(str)
		END PrintCondition;

		PROCEDURE PrintLSMAddrMode (w : Streams.Writer);
		VAR str : ARRAY 6 OF CHAR;
		BEGIN
			ASSERT(argStructure = ArgRegRList);
			w.Char(' ');
			CASE arg2(ARMArgRList).addrMode OF
				AddrModeIA : str := "IA"
				| AddrModeIB : str := "IB"
				| AddrModeDA : str := "DA"
				| AddrModeDB : str := "DB"
			ELSE str := ""
			END;
			w.String(str)
		END PrintLSMAddrMode;

		PROCEDURE PrintArguments (w : Streams.Writer);
		BEGIN
			(* KernelLog.String("*argStructure = "); KernelLog.Int(argStructure, 0); KernelLog.String(", instr = "); KernelLog.Int(instr, 0); KernelLog.Ln; *)
			CASE argStructure OF
				ArgNone :
				| ArgImm : WriteImm(arg1(ARMArgImm), w)
				| ArgReg : WriteReg(arg1(ARMArgReg), w)
				| ArgRegReg : WriteReg(arg1(ARMArgReg), w); w.String(", "); WriteReg(arg2(ARMArgReg), w)
				| ArgRegRList : WriteRegRList(arg1(ARMArgReg), arg2(ARMArgRList), w)
				| ArgRegImm : WriteReg(arg1(ARMArgReg), w); w.String(", "); WriteImm(arg2(ARMArgImm), w)
				| ArgRegShift : WriteReg(arg1(ARMArgReg), w); w.String(", "); WriteShift(arg2(ARMArgShift), w)
				| ArgRegMem : WriteReg(arg1(ARMArgReg), w); w.String(", "); WriteMem(arg2(ARMArgMem), w)
				| ArgRegRegReg : WriteReg(arg1(ARMArgReg), w); w.String(", "); WriteReg(arg2(ARMArgReg), w); w.String(", "); WriteReg(arg3(ARMArgReg), w)
				| ArgRegRegImm : WriteReg(arg1(ARMArgReg), w); w.String(", "); WriteReg(arg2(ARMArgReg), w); w.String(", "); WriteImm(arg3(ARMArgImm), w)
				| ArgRegRegShift : WriteReg(arg1(ARMArgReg), w); w.String(", "); WriteReg(arg2(ARMArgReg), w); w.String(", "); WriteShift(arg3(ARMArgShift), w)
				| ArgRegRegMem : WriteReg(arg1(ARMArgReg), w); w.String(", "); WriteReg(arg2(ARMArgReg), w); w.String(", "); WriteMem(arg3(ARMArgMem), w)
				| ArgRegRegRegReg : WriteReg(arg1(ARMArgReg), w); w.String(", "); WriteReg(arg2(ARMArgReg), w); w.String(", "); WriteReg(arg3(ARMArgReg), w); w.String(", "); WriteReg(arg4(ARMArgReg), w)
				| ArgRegRegRegImm : WriteReg(arg1(ARMArgReg), w); w.String(", "); WriteReg(arg2(ARMArgReg), w); w.String(", "); WriteReg(arg3(ARMArgReg), w); w.String(", "); WriteImm(arg4(ARMArgImm), w)
				| ArgRegRegRegShift : WriteReg(arg1(ARMArgReg), w); w.String(", "); WriteReg(arg2(ARMArgReg), w); w.String(", "); WriteReg(arg3(ARMArgReg), w); w.String(", "); WriteShift(arg4(ARMArgShift), w)
				| ArgCProcRegMem : WriteCProc(arg1(ARMArgCProc), w); w.String(", "); WriteReg(arg2(ARMArgReg), w); w.String(", "); WriteMem(arg3(ARMArgMem), w)
				| ArgCProcImmRegRegRegImm : WriteCProc(arg1(ARMArgCProc), w); w.String(", "); WriteImm(arg2(ARMArgImm), w); w.String(", "); WriteReg(arg3(ARMArgReg), w);  w.String(", "); WriteReg(arg4(ARMArgReg), w); w.String(", "); WriteReg(arg5(ARMArgReg), w);  w.String(", "); WriteImm(arg2(ARMArgImm), w)
			ELSE
				KernelLog.String("argStructure = "); KernelLog.Int(argStructure, 0); KernelLog.Ln;
				w.String("{argStructure not specified!}")
			END
		END PrintArguments;

		PROCEDURE WriteImm (immArg : ARMArgImm; w :  Streams.Writer);
			PROCEDURE WriteHex;
			VAR absImm : LONGINT;
			BEGIN
				absImm := immArg.imm;
				IF immArg.rep = RepRelJmp THEN
					(* add opcode position and length of full opcode to immediate argument value *)
					INC(absImm, offset + 4)
				END;
				Hex(absImm, w)
			END WriteHex;

		BEGIN
			IF immArg.rep = RepInt THEN
				w.Int(immArg.imm, 0)
			ELSIF immArg.rep = RepHex THEN
				WriteHex
			ELSE
				w.Int(immArg.imm, 0);
				w.String(" (");
				WriteHex;
				w.Char(')')
			END
		END WriteImm;

		PROCEDURE WriteReg (regArg : ARMArgReg; w : Streams.Writer);
		BEGIN
			IF regArg.isCReg THEN
				w.String("CR"); w.Int(regArg.reg, 0)
			ELSE
				WriteRegSymbol(regArg.reg, w);
				IF regArg.reg >= CPSR THEN
					IF regArg.sregMask * {SRegC} # {} THEN w.String("c") END;
					IF regArg.sregMask * {SRegX} # {} THEN w.String("x") END;
					IF regArg.sregMask * {SRegS} # {} THEN w.String("s") END;
					IF regArg.sregMask * {SRegF} # {} THEN w.String("f") END
				END
			END
		END WriteReg;

		PROCEDURE WriteRegSymbol (reg : LONGINT; w : Streams.Writer);
		BEGIN
			CASE reg OF
				FP : w.String("FP")
				| SP : w.String("SP")
				| LR : w.String("LR")
				| PC : w.String("PC")
				| CPSR : w.String("CPSR")
				| SPSR : w.String("SPSR")
			ELSE
				w.Char('R'); w.Int(reg, 0)
			END
		END WriteRegSymbol;

		PROCEDURE WriteShiftSymbol (op : LONGINT; w : Streams.Writer);
		BEGIN
			CASE op OF
				LSL : w.String("LSL ")
				| LSR : w.String("LSR ")
				| ASR : w.String("ASR ")
				| ROR : w.String("ROR ")
				| RRX : w.String("RRX ")
			END;
		END WriteShiftSymbol;


		PROCEDURE WriteShift (shiftArg : ARMArgShift; w :  Streams.Writer);
		BEGIN
			WriteShiftSymbol(shiftArg.operation, w);
			IF shiftArg.operation # RRX THEN
				IF shiftArg.reg THEN
					ASSERT(shiftArg.shiftImmOrReg < CPSR);
					WriteRegSymbol(shiftArg.shiftImmOrReg, w)
				ELSE
					w.Char('#');
					Hex(shiftArg.shiftImmOrReg, w)
				END
			END
		END WriteShift;

		PROCEDURE WriteMem (memArg : ARMArgMem; w : Streams.Writer);

			PROCEDURE WriteEnd;
			BEGIN
				IF memArg.regUpdate # RegUpdatePost THEN
					w.Char("]");
					IF memArg.regUpdate = RegUpdatePre THEN w.Char('!') END
				END
			END WriteEnd;
		BEGIN
			w.Char('[');
			WriteRegSymbol(memArg.reg, w);
			IF memArg.regUpdate = RegUpdatePost THEN w.Char(']') END;
			CASE memArg.addrMode OF
				AddrModeReg : w.Char(']')
				| AddrModeRegImm :
					w.String(", #");
					IF memArg.immOffs < 0 THEN w.Char('-'); Hex(-memArg.immOffs, w) ELSE Hex(memArg.immOffs, w) END;
					WriteEnd
				| AddrModeRegReg :
					w.String(", "); WriteRegSymbol(memArg.regOffs, w); WriteEnd
				| AddrModeRegRegScale : w.String(", "); WriteRegSymbol(memArg.regOffs, w); w.String(", "); WriteShiftSymbol(memArg.shift, w); w.String(" #"); w.Int(memArg.regScale, 0); WriteEnd
			END
		END WriteMem;

		PROCEDURE WriteRegRList (regArg : ARMArgReg; rListArg : ARMArgRList; w : Streams.Writer);
		VAR i, lastStart : LONGINT; notFirst : BOOLEAN;

			PROCEDURE PrintRegs (start, end : LONGINT);
			BEGIN
				IF start >= 0 THEN
					IF notFirst THEN w.String(", ") END;
					IF start < end THEN (* print range *)
						WriteRegSymbol(start, w); w.String(" - "); WriteRegSymbol(end, w)
					ELSE (* print single reg *)
						WriteRegSymbol(start, w)
					END;
					notFirst := TRUE
				END
			END PrintRegs;

		BEGIN
			WriteRegSymbol(regArg.reg, w);
			IF rListArg.addrMode # none THEN w.Char('!') END;
			w.String(", {");
			lastStart := -1;
			notFirst := FALSE;
			FOR i := 0 TO PC DO
				IF rListArg.regs * {i} # {} THEN IF lastStart < 0 THEN lastStart := i END
				ELSE
					PrintRegs(lastStart, i-1); lastStart := -1
				END;
			END;
			PrintRegs(lastStart, PC);
			w.Char('}')
		END WriteRegRList;

		PROCEDURE WriteCProc (cProcArg : ARMArgCProc; w : Streams.Writer);
		BEGIN
			w.Char('p'); w.Int(cProcArg.cproc, 0)
		END WriteCProc;
	END ARMOpcode;

	ARMDecoder = OBJECT (Decoder.Decoder)
	VAR
		bit24To27, bit20To23, bit16To19, bit12To15, bit8To11, bit4To7, bit0To3 : LONGINT;

		PROCEDURE NewOpcode() : Decoder.Opcode;
		VAR
			opcode : ARMOpcode;
		BEGIN
			NEW(opcode, currentProc, outputStreamWriter);
			RETURN opcode
		END NewOpcode;

		PROCEDURE DecodeThis(opcode : Decoder.Opcode);
		VAR
			armOpcode : ARMOpcode;
			opSet : SET;
			code, category : LONGINT;
		BEGIN
			armOpcode := opcode(ARMOpcode);
			armOpcode.op := ReadLInt();
			IF bigEndian THEN SwapBytes(armOpcode.op) END;
			opSet := SYSTEM.VAL(SET, armOpcode.op);
			(* KernelLog.String("op = "); KernelLog.Hex(armOpcode.op, 0); KernelLog.String(" -> "); *)

			code := armOpcode.op;
			bit0To3 := code MOD 10H; code := code DIV 10H;	(* split instruction *)
			bit4To7 := code MOD 10H; code := code DIV 10H;
			bit8To11 := code MOD 10H; code := code DIV 10H;
			bit12To15 := code MOD 10H; code := code DIV 10H;
			bit16To19 := code MOD 10H; code := code DIV 10H;
			bit20To23 := code MOD 10H; code := code DIV 10H;
			bit24To27 := code MOD 10H; code := code DIV 10H;
			armOpcode.cond := code MOD 10H;

			category := bit24To27 DIV 2;
			IF armOpcode.cond = 15 THEN
				IF category = 5 THEN
					BranchToThumb(armOpcode, armOpcode.op);
					RETURN
				ELSIF ((category = 7) & (bit24To27 = 0EH)) OR (category = 6) THEN (* not undefined *)
				ELSE
					armOpcode.instr := opUNDEFINED;
					armOpcode.argStructure := ArgNone;
					RETURN
				END
			END;
 			CASE category OF
				0 :
					IF (opSet * {4, 7}) = {4, 7} THEN
						MultipliesExtraLS(armOpcode)
					ELSIF (opSet * {20, 23, 24}) = {24} THEN Miscellaneous(armOpcode)
					ELSE DataProcessing(armOpcode)
					END
				| 1 :
					IF (opSet * {20, 21, 23, 24}) = {21, 24} THEN
						SRegTransfer(armOpcode)
					ELSIF (opSet * {20, 21, 23, 24}) = {24} THEN
						armOpcode.instr := opUNDEFINED;
						armOpcode.argStructure := ArgNone
					ELSE DataProcessing(armOpcode)
					END
				| 2 : LoadStore(armOpcode)
				| 3 :
					IF (opSet * {4}) = {4} THEN
						armOpcode.instr := opUNDEFINED;
						armOpcode.argStructure := ArgNone
					ELSE LoadStore(armOpcode)
					END
				| 4 : LoadStoreMultiple(armOpcode)
				| 5 : Branch(armOpcode)
				| 6 : CoprocLoadStoreDRegTransfer(armOpcode)
				| 7 : KernelLog.String("check2"); KernelLog.Ln;
					IF (opSet * {24}) = {24} THEN SoftwareInterrupt(armOpcode)
					ELSIF (opSet * {4}) = {4} THEN CoprocRegTransfer(armOpcode)
					ELSE CoprocDataProcessing(armOpcode)
					END
			END
		END DecodeThis;

		PROCEDURE DecodeShifterOperand(op : LONGINT; VAR argStructure : LONGINT; VAR arg1, arg2 : ARMArg);
		VAR
			Rm, shiftImmOrReg, operation, mode : LONGINT;
			regOp : BOOLEAN;
			regArg : ARMArgReg;
			immArg : ARMArgImm;
			shiftArg : ARMArgShift;
		BEGIN
			IF (SYSTEM.VAL(SET, op) * {25}) = {25} THEN (* I Bit *)
				argStructure := ArgImm;
				NEW(immArg, SYSTEM.ROT(op MOD 100H, -2*bit8To11), RepHex); arg1 := immArg;
			ELSE
				Rm := op MOD 10H;
				NEW(regArg, Rm); arg1 := regArg;
				argStructure := ArgRegShift;
				mode := bit4To7 MOD 8; (* type of register addressing mode *)
				IF mode = 0 THEN (* Register / Logical shift left by immediate *)
					shiftImmOrReg := (op MOD 1000H) DIV 80H;
					IF shiftImmOrReg = 0 THEN argStructure := ArgReg
					ELSE
						NEW(shiftArg, LSL, shiftImmOrReg, FALSE); arg2 := shiftArg
					END
				ELSIF ((op MOD 1000H) DIV 10H) = 6 THEN (* rotate right with extend *)
					NEW(shiftArg, RRX, 0, FALSE); arg2 := shiftArg
				ELSE
					CASE mode OF
						1 : operation := LSL
						| 2,3 : operation := LSR
						| 4,5 : operation := ASR
						| 6,7 : operation := ROR
					END;
					IF ODD(mode) THEN
						regOp := TRUE;
						shiftImmOrReg := bit8To11
					ELSE
						regOp := FALSE;
						shiftImmOrReg := (op MOD 1000H) DIV 80H
					END;
					NEW(shiftArg, operation, shiftImmOrReg, regOp); arg2 := shiftArg
				END
			END
		END DecodeShifterOperand;

		PROCEDURE DataProcessing (opcode : ARMOpcode);
		VAR SBit : BOOLEAN; Rn, Rd, type : LONGINT;
			regArg : ARMArgReg; argSh1, argSh2 : ARMArg;
			shStruct : LONGINT;
		BEGIN
			CASE (opcode.op MOD 2000000H) DIV 200000H OF (* opcode *)
				0 : opcode.instr := opAND; type := 3
				| 1 : opcode.instr := opEOR; type := 3
				| 2 : opcode.instr := opSUB; type := 3
				| 3 : opcode.instr := opRSB; type := 3
				| 4 : opcode.instr := opADD; type := 3
				| 5 : opcode.instr := opADC; type := 3
				| 6 : opcode.instr := opSBC; type := 3
				| 7 : opcode.instr := opRSC; type := 3
				| 8 : opcode.instr := opTST; type := 2
				| 9 : opcode.instr := opTEQ; type := 2
				| 10 : opcode.instr := opCMP; type := 2
				| 11 : opcode.instr := opCMN; type := 2
				| 12 : opcode.instr := opORR; type := 3
				| 13 : opcode.instr := opMOV; type := 1
				| 14 : opcode.instr := opBIC; type := 3
				| 15 : opcode.instr := opMVN; type := 1
			END;
			DecodeShifterOperand(opcode.op, shStruct, argSh1, argSh2);
			IF (SYSTEM.VAL(SET, opcode.op) * {20}) = {20} THEN SBit := TRUE ELSE SBit := FALSE END;
			opcode.ccUpdate := SBit & (type # 2);
			Rn := bit16To19;
			Rd := bit12To15;
			IF (type = 1) OR (type = 2) THEN
				opcode.argStructure := shStruct + 10;
				IF type = 1 THEN NEW(regArg, Rd) ELSE NEW(regArg, Rn) END;
				opcode.arg1 := regArg; opcode.arg2 := argSh1; opcode.arg3 := argSh2
			ELSIF type = 3 THEN
				opcode.argStructure := shStruct + 20;
				NEW(regArg, Rd); opcode.arg1 := regArg;
				NEW(regArg, Rn); opcode.arg2 := regArg;
				opcode.arg3 := argSh1; opcode.arg4 := argSh2
			END
		END DataProcessing;

		PROCEDURE Miscellaneous (opcode : ARMOpcode);
		VAR regArg : ARMArgReg; immArg : ARMArgImm;
		BEGIN
			CASE bit4To7 MOD 2 OF
				0 : SRegTransfer(opcode)
				| 1 :
					IF bit20To23 = 6 THEN
						opcode.instr := opCLZ;
						opcode.argStructure := ArgRegReg;
						NEW(regArg, bit12To15); opcode.arg1 := regArg;
						NEW(regArg, bit0To3); opcode.arg2 := regArg
					ELSIF SYSTEM.LSH(opcode.op, -20) = 0E12H THEN
						IF bit4To7 = 7 THEN
							opcode.instr := opBKPT;
							NEW(immArg, (((opcode.op MOD 100000) DIV 100H) * 10H) + (opcode.op MOD 10H), RepHex);
							opcode.argStructure := ArgImm;
							opcode.arg1 := immArg
						ELSIF bit4To7 = 1 THEN
							opcode.instr := opBX;
							NEW(regArg, bit0To3); opcode.arg1 := regArg;
							opcode.argStructure := ArgReg
						END
					END
			END
		END Miscellaneous;

		PROCEDURE MultipliesExtraLS (opcode : ARMOpcode);
		VAR op1: LONGINT;
			regA, regB, regC, regD : ARMArgReg;  memArg : ARMArgMem;
			argSh1, argSh2 : ARMArg;
			shStruct : LONGINT;
		BEGIN
			op1 := bit20To23 DIV 2;
			opcode.ccUpdate := (bit20To23 MOD 2) = 1; (* S bit set? *)
			NEW(regA, bit16To19);
			NEW(regB, bit12To15);
			NEW(regC, bit8To11);
			NEW(regD, bit0To3);
			IF bit24To27 = 1 THEN (* SWP/SWPB *)
				NEW(memArg, AddrModeReg, regA.reg);
				opcode.argStructure := ArgRegRegMem;
				opcode.arg1 := regB; opcode.arg2 := regD; opcode.arg3 := memArg;
				IF bit20To23 = 0 THEN opcode.instr := opSWP ELSE opcode.instr := opSWPB END
			ELSE
				opcode.argStructure := ArgRegRegRegReg;
				opcode.arg1 := regB; opcode.arg2 := regA; opcode.arg3 := regD; opcode.arg4 := regC;
				CASE op1 OF
					0 : opcode.instr := opMUL; opcode.argStructure := ArgRegRegReg; opcode.arg1 := regA; opcode.arg2 := regD; opcode.arg3 := regC
					| 1 : opcode.instr := opMLA; opcode.arg1 := regA; opcode.arg2 := regD; opcode.arg3 := regC; opcode.arg4 := regB
					| 3 : opcode.instr := opRSB; opcode.arg1 := regB; opcode.arg2 := regA; DecodeShifterOperand(opcode.op, shStruct, argSh1, argSh2); opcode.argStructure := shStruct + 20; opcode.arg3 := argSh1; opcode.arg4 := argSh2
					| 4 : opcode.instr := opUMULL
					| 5 : opcode.instr := opUMLAL
					| 6 : opcode.instr := opSMULL
					| 7 : opcode.instr := opSMLAL
				END
			END
		END MultipliesExtraLS;

		PROCEDURE SRegTransfer (opcode : ARMOpcode);
		VAR regArg : ARMArgReg; immArg : ARMArgImm;
		BEGIN
			opcode.argStructure := ArgRegReg;
			IF (bit20To23 DIV 4) = 1 THEN NEW(regArg, SPSR) ELSE NEW(regArg, CPSR) END;
			IF (bit20To23 MOD 4) # 2 THEN
				opcode.instr := opMRS;
				opcode.arg2 := regArg;
				NEW(regArg, bit12To15); opcode.arg1 := regArg
			ELSE
				opcode.instr := opMSR;
				regArg.sregMask := SYSTEM.VAL(SET, bit16To19);
				opcode.arg1 := regArg;
				IF (bit24To27 DIV 2) = 1 THEN (* immediate operand *)
					NEW(immArg, SYSTEM.ROT(opcode.op MOD 100H, -2*bit8To11), RepHex); (* 8 bit imm rotated by rotate imm *)
					opcode.arg2 := immArg
				ELSE NEW(regArg, bit0To3); opcode.arg2 := regArg
				END
			END
		END SRegTransfer;

		PROCEDURE LoadStore (opcode : ARMOpcode);
		VAR regArg : ARMArgReg; memArg : ARMArgMem;
			P, L, W, B, U : BOOLEAN;
			offset12 : LONGINT;
		BEGIN
			opcode.argStructure := ArgRegMem;
			NEW(regArg, bit12To15); opcode.arg1 := regArg;
			P := bit24To27 MOD 2 = 1;
			L := bit20To23 MOD 2 = 1;
			W := (bit20To23 MOD 4) DIV 2 = 1;
			B := (bit20To23 MOD 8) DIV 4 = 1;
			U := bit20To23 DIV 8 = 1;
			offset12 := opcode.op MOD 1000H;
			(* determine memory location and addressing mode *)
			IF (bit24To27 DIV 2) = 2 THEN (* immediate offset/index *)
				IF offset12 = 0 THEN 	NEW(memArg, AddrModeReg, bit16To19)
				ELSE
					NEW(memArg, AddrModeRegImm, bit16To19);
					IF U THEN memArg.immOffs := offset12 ELSE memArg.immOffs := -offset12 END
				END
			ELSE (* register offset/index *)
				IF (bit4To7 = 0) & (bit8To11 = 0) THEN
					NEW(memArg, AddrModeRegReg, bit16To19); memArg.regOffs := bit0To3
				ELSE
					NEW(memArg, AddrModeRegRegScale, bit16To19); memArg.regOffs := bit0To3;
					memArg.shift :=  (bit4To7 MOD 8) DIV 2;
					memArg.regScale := (opcode.op MOD 1000H) DIV 80H
				END
			END;
			IF L THEN opcode.instr := opLDR ELSE opcode.instr := opSTR END;
			IF  ~P THEN (* P = 0, post index addressing *)
				IF memArg.addrMode # AddrModeReg THEN memArg.regUpdate := RegUpdatePost END;
				memArg.immOffs := opcode.op MOD 1000H;
			ELSE (* P = 1, offset or pre-indexed addressing *)
				IF W THEN (* base register update *)
					memArg.regUpdate := RegUpdatePre;
				END
			END;
			IF W THEN memArg.translation := TRUE END;
			IF B THEN memArg.width := 1 END;
			opcode.arg2 := memArg
		END LoadStore;

		PROCEDURE LoadStoreMultiple (opcode : ARMOpcode);
		VAR regArg : ARMArgReg; regListArg : ARMArgRList;
			P, U, S, W, L : BOOLEAN;
		BEGIN
			P := bit24To27 MOD 2 = 1;
			U := bit20To23 DIV 8 = 1;
			S := (bit20To23 MOD 8) DIV 4 = 1;
			W := (bit20To23 MOD 4) DIV 2 = 1;
			L := bit20To23 MOD 2 = 1;
			IF L THEN opcode.instr := opLDM ELSE opcode.instr := opSTM END;
			NEW(regArg, bit16To19);
			NEW(regListArg, SYSTEM.VAL(SET, opcode.op MOD 10000H));
			IF W THEN
				IF P THEN IF U THEN regListArg.addrMode := AddrModeIB ELSE regListArg.addrMode := AddrModeDB END
				ELSE IF U THEN regListArg.addrMode := AddrModeIA ELSE regListArg.addrMode := AddrModeDA END
				END
			ELSE regListArg.addrMode := none END;
			opcode.argStructure := ArgRegRList;
			opcode.arg1 := regArg;
			opcode.arg2 := regListArg
		END LoadStoreMultiple;

		PROCEDURE Branch (opcode : ARMOpcode);
		VAR immArg : ARMArgImm;
		BEGIN
			IF (SYSTEM.VAL(SET, opcode.op) * {24}) = {24} THEN opcode.instr := opBL
			ELSE opcode.instr := opB
			END;
			opcode.argStructure := ArgImm;
			NEW(immArg, (opcode.op MOD 1000000H), RepRelJmp);
			SignExtension(immArg.imm, 24);
			immArg.imm := (immArg.imm + 1) *4;
			opcode.arg1 := immArg
		END Branch;

		PROCEDURE BranchToThumb (opcode : ARMOpcode; op : LONGINT);
		VAR sImmed24, targetAddr : LONGINT; immArg : ARMArgImm; regArg : ARMArgReg;
		BEGIN
			opcode.instr := opBLX;
			IF opcode.cond = 15 THEN
				sImmed24 := opcode.op MOD 1000000H;
				targetAddr := (bit24To27 MOD 2)*2 + ASH(SYSTEM.LSH(sImmed24, 8), -6); (* sign extend *)
				NEW(immArg, targetAddr, RepRelJmp); opcode.arg1 := immArg;
				opcode.argStructure := ArgImm
			ELSE
				NEW(regArg, bit0To3); opcode.arg1 := regArg;
				opcode.argStructure := ArgReg
			END
		END BranchToThumb;

		PROCEDURE CoprocLoadStoreDRegTransfer (opcode : ARMOpcode);
		VAR cProcArg : ARMArgCProc; memArg : ARMArgMem; regArg : ARMArgReg;
			P, U, W, mul4 : BOOLEAN;
		BEGIN
			IF (bit20To23 MOD 2) = 1 THEN opcode.instr := opLDC ELSE opcode.instr := opSTC END;
			P := (bit24To27 MOD 2) = 1; (* P bit *)
			U := (bit20To23 DIV 8) = 1; (* U bit *)
			W := ((bit20To23 MOD 4) DIV 2) = 1; (* W bit *)
			mul4 := TRUE;
			opcode.argStructure := ArgCProcRegMem;
			NEW(memArg, AddrModeRegImm, bit16To19);
			IF P THEN
				memArg.regUpdate := RegUpdatePost;
				IF ~W THEN mul4 := FALSE END (* this is the option argument, therefore it is not multiplied by 4, but represented the same way like a post update offset *)
			ELSE
				IF W THEN memArg.regUpdate := RegUpdatePre
				ELSE memArg.regUpdate := RegUpdateNone
				END
			END;
			IF U THEN memArg.immOffs := (opcode.op MOD 100H) ELSE memArg.immOffs := - (opcode.op MOD 100H) END;
			IF mul4 THEN memArg.immOffs := memArg.immOffs * 4 END;
			NEW(cProcArg, bit8To11); opcode.arg1 := cProcArg;
			NEW(regArg, bit12To15); regArg.isCReg := TRUE;
			opcode.arg1 := cProcArg; opcode.arg2 := regArg; opcode.arg3 := memArg
		END CoprocLoadStoreDRegTransfer;

		PROCEDURE CoprocDataProcessing (opcode : ARMOpcode);
		BEGIN
			CoprocRegTransfer(opcode);
			IF opcode.cond = 15 THEN opcode.instr := opCDP2 ELSE opcode.instr := opCDP END;
			opcode.arg2(ARMArgImm).imm := bit20To23
		END CoprocDataProcessing;

		PROCEDURE CoprocRegTransfer (opcode : ARMOpcode);
		VAR immArg : ARMArgImm; regArg : ARMArgReg; cProcArg : ARMArgCProc;
		BEGIN
			IF (bit20To23 MOD 2) = 1 THEN
				IF opcode.cond = 15 THEN opcode.instr := opMRC2 ELSE opcode.instr := opMRC END
			ELSE
				IF opcode.cond = 15 THEN opcode.instr := opMCR2 ELSE opcode.instr := opMCR END
			END;
			opcode.argStructure := ArgCProcImmRegRegRegImm;
			NEW(cProcArg, bit8To11); opcode.arg1 := cProcArg;
			NEW(immArg, bit20To23 DIV 2, RepInt); opcode.arg2 := immArg; (* opcode-1 *)
			NEW(regArg, bit12To15); opcode.arg3 := regArg;
			NEW(regArg, bit16To19); regArg.isCReg := TRUE; opcode.arg4 := regArg;
			NEW(regArg, bit0To3); regArg.isCReg := TRUE; opcode.arg5 := regArg;
			NEW(immArg, bit4To7 DIV 2, RepInt); opcode.arg6 := immArg
		END CoprocRegTransfer;

		PROCEDURE SoftwareInterrupt (opcode : ARMOpcode);
		VAR immArg : ARMArgImm;
		BEGIN
			NEW(immArg, opcode.op MOD 1000000H, RepHex);
			opcode.instr := opSWI;
			opcode.argStructure := ArgImm;
			opcode.arg1 := immArg
		END SoftwareInterrupt;

		PROCEDURE SignExtension (VAR x : LONGINT; length : LONGINT);
		BEGIN
			ASSERT((length > 0) & (length <= 32));
			x := SYSTEM.LSH(x, 32-length);
			x := ASH(x, length-32)
		END SignExtension;
	END ARMDecoder;

VAR
	bigEndian : BOOLEAN;

PROCEDURE SetBigEndian*;
BEGIN
	bigEndian := TRUE;
END SetBigEndian;

PROCEDURE SetLittleEndian*;
BEGIN
	bigEndian := FALSE;
END SetLittleEndian;

PROCEDURE Hex(n: LONGINT; w : Streams.Writer); (* procedure from PCARMDecoder.Mod *)
 	VAR i, j: INTEGER; s, t : ARRAY 10 OF CHAR;
BEGIN
	i := 0;
	REPEAT
		IF n MOD 10H < 10 THEN s[i] := CHR(n MOD 10H +ORD("0")) ELSE s[i] := CHR(n MOD 10H - 10 + ORD("A")) END;
		n := n DIV 10H MOD 10000000H; INC(i);
	UNTIL n = 0;
	j := 0;
	WHILE i>0 DO DEC(i); t[j] := s[i]; INC(j) END; t[j]:="H"; t[j+1] := 0X;
	w.String(t)
END Hex;

PROCEDURE SwapBytes(VAR code: LONGINT);
TYPE Opcode = ARRAY 4 OF CHAR;
VAR opcode: Opcode;
	tmp: CHAR;
BEGIN
	opcode := SYSTEM.VAL(Opcode, code);
	tmp := opcode[0]; opcode[0] := opcode[3]; opcode[3] := tmp;
	tmp := opcode[1]; opcode[1] := opcode[2]; opcode[2] := tmp;
	code := SYSTEM.VAL(LONGINT, opcode);
END SwapBytes;

PROCEDURE ARMDecoderFactory (reader : Streams.Reader) : Decoder.Decoder;
VAR
	armDecoder : ARMDecoder;
BEGIN
	NEW(armDecoder, reader);
	RETURN armDecoder
END ARMDecoderFactory;

PROCEDURE CodeScaleCallback(VAR codeSize: LONGINT);
BEGIN
	codeSize := codeSize * 4
END CodeScaleCallback;

PROCEDURE Init*;
BEGIN
	Decoder.RegisterDecoder("Oba", ARMDecoderFactory, CodeScaleCallback);
	KernelLog.String("ARMDecoder installed."); KernelLog.Ln;
END Init;

BEGIN
	bigEndian := FALSE;
END ARMDecoder.