MODULE PCGAMD64;	(** AUTHOR "negelef"; PURPOSE "AMD64 code generator"; *)

IMPORT
		SYSTEM, PCLIR, PCM, PCBT, PCB, PCA := PCAAMD64, ASM := ASMAMD64;

CONST

	AlignParameters = TRUE;

	rAX = 0;
	rCX = 1;
	rDX = 2;
	rBX = 3;
	rSP = 4;
	rBP = 5;
	rSI = 6;
	rDI = 7;
	r8 = 8;
	r9 = 9;
	r10 = 10;
	r11 = 11;
	r12 = 12;
	r13 = 13;
	r14 = 14;
	r15 = 15;

	(* standard predefined register *)
	predefinedGppRegisters = {rAX, rCX, rDX, rSP, rBP, rDI, rSI};
	predefinedXmmRegisters = {};

	LegacyIntegers = {PCLIR.Int8, PCLIR.Int16, PCLIR.Int32};

TYPE

	XMM32 = OBJECT (PCA.XMMReg)
	END XMM32;

	XMM64 = OBJECT (PCA.XMMReg)
	END XMM64;

	Fixup = OBJECT (PCLIR.InstructionAttribute)
	VAR
		pc: LONGINT;
		next: Fixup;
	END Fixup;

	CaseLine = OBJECT (PCLIR.InstructionAttribute)
	VAR
		first, last: BOOLEAN;
		from, to: LONGINT;
	END CaseLine;

	Case = OBJECT (PCLIR.InstructionAttribute)
	VAR
		reg: PCA.Reg;
		prevCase, nextCase: Fixup;
		curCasePC: LONGINT;
		curCaseLine: CaseLine;
	END Case;

	(* one set per register size *)
	RegisterSet = RECORD gpp, xmm: SET END;

VAR
	assembly: PCA.Assembly;

	currentRegisters: RegisterSet;
	savedRegisters: ARRAY 10 OF RegisterSet;
	saveLevel : INTEGER;

PROCEDURE IsFloat (size: PCLIR.Size): BOOLEAN;
BEGIN RETURN (size = PCLIR.Float32) OR (size = PCLIR.Float64)
END IsFloat;

PROCEDURE NewXMM32 (index: LONGINT): XMM32;
VAR xmm32: XMM32;
BEGIN NEW (xmm32, index); RETURN xmm32;
END NewXMM32;

PROCEDURE NewXMM64 (index: LONGINT): XMM64;
VAR xmm64: XMM64;
BEGIN NEW (xmm64, index); RETURN xmm64;
END NewXMM64;

(* create a new register with the given size *)
PROCEDURE NewReg (size: PCLIR.Size; index: LONGINT): PCA.Reg;
BEGIN CASE size OF
	| PCLIR.Int8: RETURN PCA.NewReg8 (index);
	| PCLIR.Int16: RETURN PCA.NewReg16 (index);
	| PCLIR.Int32: RETURN PCA.NewReg32 (index);
	| PCLIR.Int64: RETURN PCA.NewReg64 (index);
	| PCLIR.Float32: RETURN NewXMM32 (index);
	| PCLIR.Float64: RETURN NewXMM64 (index);
	END;
END NewReg;

PROCEDURE AllocReg (size: PCLIR.Size; index: LONGINT);
BEGIN IF size IN PCLIR.FloatSize THEN
		INCL (currentRegisters.xmm, index);
	ELSE
		INCL (currentRegisters.gpp, index);
	END;
END AllocReg;

PROCEDURE FreeReg (size: PCLIR.Size; index: LONGINT);
BEGIN IF size IN PCLIR.FloatSize THEN
		EXCL (currentRegisters.xmm, index);
	ELSE
		EXCL (currentRegisters.gpp, index);
	END;
END FreeReg;

PROCEDURE GetNextFreeReg (registerSet: SET): LONGINT;
VAR  index: LONGINT;
BEGIN index := 0;
	WHILE index IN registerSet DO INC (index) END;
	ASSERT (index <= r15);
	RETURN index;
END GetNextFreeReg;

(* look within the current register set for a free register with the given size *)
PROCEDURE AcquireReg (VAR instr: PCLIR.Instruction);
VAR index: LONGINT; reg: PCA.Reg;
BEGIN
	IF instr.info = NIL THEN

		IF instr.dstSize IN PCLIR.FloatSize THEN
			index := GetNextFreeReg (currentRegisters.xmm + predefinedXmmRegisters) ;
		ELSE
			index := GetNextFreeReg (currentRegisters.gpp + predefinedGppRegisters) ;
		END;

		AllocReg (instr.dstSize, index);

		instr.info := NewReg (instr.dstSize, index);
	END;

	reg := instr.info(PCA.Reg);
	AllocReg (GetSize (reg), reg.index);
END AcquireReg;

(* return the given source register if this is its only access otherwise aquire a free register*)
PROCEDURE AcquireSourceReg (VAR instr: PCLIR.Instruction; VAR source: PCLIR.Register; piece: PCLIR.Piece);
VAR reg: PCA.Reg;
BEGIN
	IF (instr.info = NIL) & (source >= 0) & (piece.instr[source].dstCount = 1) THEN
		instr.info := NewReg (instr.dstSize, piece.instr[source].info(PCA.Reg).index);
		source := PCLIR.none;
	ELSE
		AcquireReg (instr);
		reg := GetReg (source, piece);
		IF instr.info(PCA.Reg).index = reg.index THEN
			DEC (piece.instr[source].dstCount);
			source := PCLIR.none;
		ELSE
			assembly.Emit (ASM.opMOV, instr.info(PCA.Reg), reg, NIL);
			ReleaseSourceReg (source, piece);
		END;
	END;
END AcquireSourceReg;

(* return a source register *)
PROCEDURE GetReg (source: PCLIR.Register; piece: PCLIR.Piece): PCA.Reg;
CONST HwReg = PCLIR.HwReg + 8;
BEGIN
	IF source >= 0 THEN
		RETURN piece.instr[source].info(PCA.Reg);
	ELSE
		CASE source OF
		| PCLIR.FP:
			RETURN PCA.NewReg64 (rBP);
		| PCLIR.SP:
			RETURN PCA.NewReg64 (rSP);
		| PCLIR.Absolute:
			RETURN NIL;

		| HwReg - PCB.regRDI .. HwReg - PCB.regRAX:
			RETURN PCA.NewReg64 (HwReg - PCB.regRAX - source);
		| HwReg - PCB.regR15, HwReg - PCB.regR8:
			RETURN PCA.NewReg64 (HwReg - PCB.regR8 + 8 - source);

		| HwReg - PCB.regEDI .. HwReg - PCB.regEAX:
			RETURN PCA.NewReg32 (HwReg - PCB.regEAX - source);
		| HwReg - PCB.regR15D, HwReg - PCB.regR8D:
			RETURN PCA.NewReg32 (HwReg - PCB.regR8D + 8 - source);

		| HwReg - PCB.regBX .. HwReg - PCB.regAX:
			RETURN PCA.NewReg16 (HwReg - PCB.regAX - source);
		| HwReg - PCB.regR15W, HwReg - PCB.regR8W:
			RETURN PCA.NewReg16 (HwReg - PCB.regR8W + 8 - source);

		| HwReg - PCB.regBL, HwReg - PCB.regAL:
			RETURN PCA.NewReg8 (HwReg - PCB.regAL - source);
		| HwReg - PCB.regR15B, HwReg - PCB.regR8B:
			RETURN PCA.NewReg8 (HwReg - PCB.regR8B + 8 - source);

		END;
	END;
END GetReg;

PROCEDURE GetSize (reg: PCA.Reg): PCLIR.Size;
BEGIN
	IF reg IS PCA.Reg8 THEN
		RETURN PCLIR.Int8;
	ELSIF reg IS PCA.Reg16 THEN
		RETURN PCLIR.Int16;
	ELSIF reg IS PCA.Reg32 THEN
		RETURN PCLIR.Int32;
	ELSIF reg IS PCA.Reg64 THEN
		RETURN PCLIR.Int64;
	ELSIF reg IS PCA.Reg64 THEN
		RETURN PCLIR.Int64;
	ELSIF reg IS XMM32 THEN
		RETURN PCLIR.Float32;
	ELSIF reg IS XMM64 THEN
		RETURN PCLIR.Float64;
	END;
END GetSize;

(* release a register and return it to the register pool if there are no more accesses to it *)
PROCEDURE ReleaseReg (VAR instr: PCLIR.Instruction);
BEGIN
	DEC (instr.dstCount);

	IF instr.dstCount = 0 THEN
		FreeReg (instr.dstSize, instr.info(PCA.Reg).index);
	END;
END ReleaseReg;

(* release a source register and return it to the register pool if there are no more accesses to it *)
PROCEDURE ReleaseSourceReg (source: PCLIR.Register; piece: PCLIR.Piece);
BEGIN
	IF source >= 0 THEN
		ReleaseReg (piece.instr[source]);
	END;
END ReleaseSourceReg;

PROCEDURE NewImm (size: PCLIR.Size; val: LONGINT): PCA.Imm;
BEGIN
	CASE size OF
	| PCLIR.Int8: RETURN PCA.NewImm8 (val);
	| PCLIR.Int16: RETURN PCA.NewImm16 (val);
	| PCLIR.Int32: RETURN PCA.NewImm32 (val);
	| PCLIR.Int64: RETURN PCA.NewImm64 (val);
	END;
END NewImm;

PROCEDURE NewMem (size: PCLIR.Size; reg: PCA.Reg; displacement: LONGINT): PCA.Mem;
BEGIN
	CASE size OF
	| PCLIR.Int8: RETURN PCA.NewMem8 (reg, displacement);
	| PCLIR.Int16: RETURN PCA.NewMem16 (reg, displacement);
	| PCLIR.Int32: RETURN PCA.NewMem32 (reg, displacement);
	| PCLIR.Int64: RETURN PCA.NewMem64 (reg, displacement);
	| PCLIR.Float32: RETURN PCA.NewMem32 (reg, displacement);
	| PCLIR.Float64: RETURN PCA.NewMem64 (reg, displacement);
	END;
END NewMem;

(*
PROCEDURE Dump (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN
	PCM.log.CharLn;
	PCM.log.CharStr(PCLIR.InstructionSet[instr.op].name); PCM.log.CharLn;
	PCM.log.CharStr("  code.barrier: "); PCM.log.CharNum (code.barrier); PCM.log.CharLn;
	PCM.log.CharStr("  instr.src1: "); PCM.log.CharNum (instr.src1); PCM.log.CharLn;
	PCM.log.CharStr("  instr.src2: "); PCM.log.CharNum (instr.src2); PCM.log.CharLn;
	PCM.log.CharStr("  instr.src3: "); PCM.log.CharNum (instr.src3); PCM.log.CharLn;
	PCM.log.CharStr("  instr.val: "); PCM.log.CharNum (instr.val); PCM.log.CharLn;
	PCM.log.CharStr("  instr.adr: "); PCM.log.CharBool (instr.adr # NIL); PCM.log.CharLn;
	IF (instr.adr # NIL) & (instr.adr IS PCBT.Procedure) THEN
		PCM.log.CharStr("    procedure ");  PCM.log.CharLn;
	END;
	IF (instr.adr # NIL) & (instr.adr IS PCBT.Variable) THEN
		PCM.log.CharStr("   Variable:");  PCM.log.CharLn;
		PCM.log.CharStr("    offset"); PCM.log.CharNum (instr.adr(PCBT.Variable).offset); PCM.log.CharLn;
	END;
	PCM.log.CharStr("  instr.suppress: "); PCM.log.CharBool (instr.suppress); PCM.log.CharLn;
	PCM.log.CharStr("  instr.dstCount: "); PCM.log.CharNum (instr.dstCount); PCM.log.CharLn;
	PCM.log.CharStr("  instr.dstSize: "); PCM.log.CharNum (instr.dstSize); PCM.log.CharLn;
	PCM.log.CharStr("  instr.dstSigned: "); PCM.log.CharBool (instr.dstSigned); PCM.log.CharLn;
	PCM.log.CharStr("  instr.info: "); PCM.log.CharBool (instr.info # NIL); PCM.log.CharLn;
	PCM.log.CharStr("  pc: "); PCM.log.CharNum (pc); PCM.log.CharLn;
END Dump;
*)

PROCEDURE InstructionInit(VAR instr: PCLIR.Instruction);
END InstructionInit;

PROCEDURE IsAbsolute (adr: PCM.Attribute): BOOLEAN;
BEGIN
	IF adr # NIL THEN
		IF adr IS PCBT.GlobalVariable THEN
			RETURN TRUE;
		ELSIF adr IS PCBT.Procedure THEN
			RETURN TRUE
		END;
	END;
	RETURN FALSE;
END IsAbsolute;

PROCEDURE FixAbsolute (adr: PCM.Attribute; pc: LONGINT);
BEGIN
	IF adr # NIL THEN
		IF adr IS PCBT.GlobalVariable THEN
			PCBT.context.UseVariable (adr(PCBT.GlobalVariable), pc)
		ELSIF adr IS PCBT.Procedure THEN
			PCBT.context.UseProcedure (adr(PCBT.Procedure), pc)
		END;
	END;
END FixAbsolute;

PROCEDURE FixFixups (VAR fixup: Fixup);
VAR prevPC: LONGINT;
BEGIN
	IF fixup # NIL THEN
		prevPC := assembly.pc;
		REPEAT
			assembly.SetPC (fixup.pc - 4);
			assembly.PutDWord (prevPC - fixup.pc);
			fixup := fixup.next;
		UNTIL fixup = NIL;
		assembly.SetPC (prevPC);
		fixup := NIL;
	END
END FixFixups;

(* Code Generation Procedures *)

PROCEDURE EmitPush (reg: PCA.Reg);
VAR RSP: PCA.Reg;
BEGIN
	IF AlignParameters THEN
		assembly.Emit (ASM.opPUSH, PCA.NewReg64 (reg.index), NIL, NIL);
	ELSE
		CASE GetSize (reg) OF
		PCLIR.Int16, PCLIR.Int64:
			assembly.Emit (ASM.opPUSH, reg, NIL, NIL);
		| PCLIR.Int32:
			RSP := PCA.NewReg64 (rSP);
			assembly.Emit (ASM.opSUB, RSP, PCA.NewImm8 (4), NIL);
			assembly.Emit (ASM.opMOV, PCA.NewMem32 (RSP, 0), reg, NIL);
		END;
	END;
END EmitPush;

PROCEDURE EmitPop (reg: PCA.Reg);
VAR RSP: PCA.Reg;
BEGIN
	IF AlignParameters THEN
		assembly.Emit (ASM.opPOP, PCA.NewReg64 (reg.index), NIL, NIL);
	ELSE
		CASE GetSize (reg) OF
		PCLIR.Int16, PCLIR.Int64:
			assembly.Emit (ASM.opPOP, reg, NIL, NIL);
		| PCLIR.Int32:
			RSP := PCA.NewReg64 (rSP);
			assembly.Emit (ASM.opMOV, reg, PCA.NewMem32 (RSP, 0), NIL);
			assembly.Emit (ASM.opADD, RSP, PCA.NewImm8 (4), NIL);
		END;
	END;
END EmitPop;

PROCEDURE EmitResult (VAR instr: PCLIR.Instruction; srcReg: LONGINT);
VAR op: LONGINT; source: PCA.Reg;
BEGIN
	AcquireReg (instr);

	CASE instr.dstSize OF
	PCLIR.Int8 .. PCLIR.Int64:
		op := ASM.opMOV;
		source := NewReg (instr.dstSize, srcReg);
	| PCLIR.Float32:
		op := ASM.opMOVD;
		source := PCA.NewReg32 (srcReg);
	| PCLIR.Float64:
		op := ASM.opMOVD;
		source := PCA.NewReg64 (srcReg);
	END;

	assembly.Emit (op, instr.info(PCA.Reg), source, NIL);
	FreeReg (PCLIR.Int64, srcReg);
END EmitResult;

PROCEDURE EmitReturn (code: PCLIR.Code; VAR instr: PCLIR.Instruction; destReg: LONGINT);
VAR
	piece1: PCLIR.Piece;
	reg1, dest: PCA.Reg;
	sourceSize: PCLIR.Size;
	op: LONGINT;

BEGIN
	code.GetPiece (instr.src1, piece1);
	reg1 := GetReg (instr.src1, piece1);

	sourceSize := GetSize (reg1);

	CASE sourceSize OF
	PCLIR.Int8 .. PCLIR.Int64:
		op := ASM.opMOV;
		dest := NewReg (sourceSize, destReg);
	| PCLIR.Float32:
		op := ASM.opMOVD;
		dest := PCA.NewReg32 (destReg);
	| PCLIR.Float64:
		op := ASM.opMOVD;
		dest := PCA.NewReg64 (destReg);
	END;

	assembly.Emit (op, dest, reg1, NIL);
	AllocReg (sourceSize, destReg);

	ReleaseSourceReg (instr.src1, piece1);

END EmitReturn;

PROCEDURE EmitJmp (opCode: LONGINT; code: PCLIR.Code;  VAR instr: PCLIR.Instruction);
VAR piece1: PCLIR.Piece; offset: LONGINT; fixup: Fixup;
BEGIN
	code.GetPiece (instr.val, piece1);

	IF piece1.instr[instr.val].val = 0 THEN
		assembly.Emit (opCode, PCA.NewImm32 (0), NIL, NIL);
		NEW (fixup); fixup.pc := assembly.pc;
		IF piece1.instr[instr.val].info # NIL THEN
			fixup.next := piece1.instr[instr.val].info(Fixup);
		END;
		piece1.instr[instr.val].info := fixup;
	ELSE
		assembly.Emit (opCode, PCA.NewOffset32 (piece1.instr[instr.val].val), NIL, NIL);
	END;

END EmitJmp;

PROCEDURE EmitType1 (op: LONGINT; code: PCLIR.Code;  VAR instr: PCLIR.Instruction; operand: PCA.Operand);
VAR piece1: PCLIR.Piece;
BEGIN
	code.GetPiece (instr.src1, piece1);
	AcquireSourceReg (instr, instr.src1, piece1);

	(* minor optimizations *)
	IF (instr.src1 = PCLIR.none) & (operand # NIL) & (operand IS PCA.Imm) THEN
		IF (op = ASM.opADD) & (operand(PCA.Imm).val = 1) THEN op := ASM.opINC; operand := NIL END;
		IF (op = ASM.opADD) & (operand(PCA.Imm).val = -1) THEN op := ASM.opDEC; operand := NIL END;
		IF (op = ASM.opSUB) & (operand(PCA.Imm).val = 1) THEN op := ASM.opDEC; operand := NIL END;
		IF (op = ASM.opSUB) & (operand(PCA.Imm).val = -1) THEN op := ASM.opINC; operand := NIL END;
	END;

	IF (op = ASM.opIMUL) & (GetSize (operand(PCA.Reg)) = PCLIR.Int8) THEN
		assembly.Emit (ASM.opMOV, PCA.NewReg8 (rAX), operand, NIL);
		assembly.Emit (op, instr.info(PCA.Reg), NIL, NIL);
	ELSIF op # ASM.opNOP THEN
		assembly.Emit (op, instr.info(PCA.Reg), operand, NIL);
	END;
END EmitType1;

PROCEDURE EmitType2 (op: LONGINT; code: PCLIR.Code;  VAR instr: PCLIR.Instruction);
VAR piece2: PCLIR.Piece;
BEGIN
	IF instr.src2 = PCLIR.none THEN
		EmitType1 (op, code, instr, PCA.NewImm (PCA.default, instr.val));
	ELSE
		code.GetPiece (instr.src2, piece2);
		EmitType1 (op, code, instr, GetReg (instr.src2, piece2));
		ReleaseSourceReg (instr.src2, piece2);
	END;
END EmitType2;

PROCEDURE EmitSSEBitOp (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  op, bit: LONGINT; invert: BOOLEAN);
VAR RAX, tmp: PCA.Reg;
BEGIN
	RAX := PCA.NewReg64 (rAX);
	assembly.Emit (ASM.opXOR, RAX, RAX, NIL);
	assembly.Emit (ASM.opBTS, RAX, PCA.NewImm8 (bit), NIL);
	IF invert THEN
		assembly.Emit (ASM.opNOT, RAX, NIL, NIL);
	END;
	tmp := PCA.NewXMMReg (GetNextFreeReg (currentRegisters.xmm + predefinedXmmRegisters));
	assembly.Emit (ASM.opMOVD, tmp, RAX, NIL);
	EmitType1 (op, code, instr, tmp);
END EmitSSEBitOp;

PROCEDURE EmitMove(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  op: LONGINT);
VAR
	piece1, piece2, piece3: PCLIR.Piece;
	from, to, size: PCA.Reg;
	RSI, RDI, RCX: PCA.Reg;
BEGIN

	code.GetPiece (instr.src1, piece1);
	code.GetPiece (instr.src2, piece2);
	code.GetPiece (instr.src3, piece3);

	from := GetReg (instr.src1, piece1);
	to := GetReg (instr.src2, piece2);
	size := GetReg (instr.src3, piece3);

	RSI := NewReg (GetSize (from), rSI);
	RDI := NewReg (GetSize (to), rDI);
	RCX := NewReg (GetSize (size), rCX);

	assembly.Emit (ASM.opMOV,  RSI, from, NIL);
	assembly.Emit (ASM.opMOV,  RDI, to, NIL);
	assembly.Emit (ASM.opMOV,  RCX, size, NIL);

	assembly.Emit (op, NIL, NIL, NIL);

	assembly.EmitPrefix (ASM.prfREP);
	assembly.Emit (ASM.opMOVSB, NIL, NIL, NIL);

	ReleaseSourceReg (instr.src1, piece1);
	ReleaseSourceReg (instr.src2, piece2);
	ReleaseSourceReg (instr.src3, piece3);

END EmitMove;

PROCEDURE EmitCmpJmp (reg: PCA.Reg; val: LONGINT; op: LONGINT; VAR fixup: Fixup);
VAR fix: Fixup;
BEGIN
	assembly.Emit (ASM.opCMP, reg, PCA.NewImm (PCA.default, val), NIL);
	assembly.Emit (op, PCA.NewImm32 (0), NIL, NIL);
	NEW (fix); fix.pc := assembly.pc; fix.next := fixup; fixup := fix
END EmitCmpJmp;

(* GenEnter - Create Procedure activation frame of given size and clear the stack *)
PROCEDURE GenEnter (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
CONST PushLoopUnrollSize = 4;
VAR stackSize, i: LONGINT;  adr: PCBT.Procedure;

	PROCEDURE FixAddress;
	VAR prevPC, nextPC: LONGINT;
	BEGIN
		prevPC := assembly.pc;
		WHILE adr.fixlist # PCBT.FixupSentinel DO
			assembly.SetPC (adr.fixlist);
			nextPC := assembly.GetDWord ();
			assembly.SetPC (adr.fixlist);
			assembly.PutDWord (prevPC - 4 - adr.fixlist);
			adr.fixlist := nextPC;
		END;
		assembly.SetPC (prevPC);
	END FixAddress;

BEGIN
	IF (instr.adr # NIL) & (instr.adr IS PCBT.Procedure) THEN
		adr := instr.adr(PCBT.Procedure);
		PCBT.context.AddOwnProc (adr, assembly.pc);
		FixAddress;
		stackSize := adr.locsize
	ELSE
		stackSize := 0
	END;

	(* check calling convention *)
	IF instr.val = PCBT.OberonPassivateCC THEN

		(* push the current stack frame on the stack *)
		assembly.Emit (ASM.opPUSH, PCA.NewReg64 (rBP), NIL, NIL);
		(* assign the first argument to the stack frame (16 = 8 for RIP pushed by caller + 8 for preceding push) *)
		assembly.Emit (ASM.opMOV, PCA.NewReg64 (rBP), PCA.NewMem64 (PCA.NewReg64 (rSP), 16), NIL);

	ELSIF PCM.FullStackInit IN PCM.codeOptions THEN

		(* ENTER instruction instead of PUSH RBP, MOV RBP, RSP; both versions need 4 byte in 64-bit mode *)
		assembly.Emit (ASM.opENTER, PCA.NewImm16 (0), PCA.NewImm8 (0), NIL);

		(* clear the stack and choose smallest encoding for given stack size (needs some performance heuristic ?) *)

		ASSERT (stackSize MOD 8 = 0);

		IF stackSize > 120 THEN
			assembly.Emit (ASM.opXOR, PCA.NewReg32 (rAX), PCA.NewReg32 (rAX), NIL);

			assembly.Emit (ASM.opMOV, PCA.NewReg32 (rCX), PCA.NewImm (PCA.default, stackSize DIV (8 * PushLoopUnrollSize)), NIL);

			stackSize := stackSize MOD (8 * PushLoopUnrollSize);

			WHILE stackSize # 0 DO
				assembly.Emit (ASM.opPUSH, PCA.NewReg64 (rAX), NIL, NIL);
				DEC (stackSize, 8);
			END;

			stackSize := assembly.pc;

			assembly.Emit (ASM.opDEC, PCA.NewReg32 (rCX), NIL, NIL);

			FOR i := 0 TO PushLoopUnrollSize - 1 DO
				assembly.Emit (ASM.opPUSH, PCA.NewReg64 (rAX), NIL, NIL);
			END;

			assembly.Emit (ASM.opJNZ, PCA.NewOffset8 (stackSize), NIL, NIL);

		ELSIF stackSize > 16 THEN
			assembly.Emit (ASM.opXOR, PCA.NewReg32 (rAX), PCA.NewReg32 (rAX), NIL);
			WHILE stackSize # 0 DO
				assembly.Emit (ASM.opPUSH, PCA.NewReg64 (rAX), NIL, NIL);
				DEC (stackSize, 8);
			END;
		ELSE
			WHILE stackSize # 0 DO
				assembly.Emit (ASM.opPUSH, PCA.NewImm8 (0), NIL, NIL);
				DEC (stackSize, 8);
			END;
		END;
	ELSE
		(* performs PUSH RBP; MOV RBP, RSP; SUB RSP, stackSize *)
		assembly.Emit (ASM.opENTER, PCA.NewImm16 (stackSize), PCA.NewImm8 (0), NIL);
	END;

	currentRegisters.gpp := {};
	currentRegisters.xmm := {};
	saveLevel := 0;

END GenEnter;

(* GenExit - Remove procedure activation frame, remove the give size of parameters and return to the caller *)
PROCEDURE GenExit (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN
	IF instr.val = PCBT.OberonPassivateCC THEN
		(* pop the stack frame from the stack *)
		assembly.Emit (ASM.opPOP, PCA.NewReg64 (rBP), NIL, NIL);
		(* return to caller popping the first argument *)
		assembly.Emit (ASM.opRET, PCA.NewImm16 (8), NIL, NIL);
	ELSE
		assembly.Emit (ASM.opLEAVE, NIL, NIL, NIL);

		FreeReg (PCLIR.Int64, rAX); FreeReg (PCLIR.Int64, rDX);

		IF instr.src1 = 0 THEN
			assembly.Emit (ASM.opRET, NIL, NIL, NIL);
		ELSE
			assembly.Emit (ASM.opRET, PCA.NewImm16 (instr.src1), NIL, NIL);
		END
	END;
END GenExit;

PROCEDURE GenTrap (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN
	(* prohibit pushing a 16-bit immediate which decrements the stack pointer by two not by 8 *)
	IF instr.val < 80H THEN
		assembly.Emit (ASM.opPUSH, PCA.NewImm8 (instr.val), NIL, NIL);
	ELSE
		assembly.Emit (ASM.opPUSH, PCA.NewImm32 (instr.val), NIL, NIL);
	END;
	assembly.Emit (ASM.opINT3, NIL, NIL, NIL);
END GenTrap;

PROCEDURE GenTrapcc (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR
	piece1, piece2: PCLIR.Piece;
	reg1, reg2: PCA.Reg;
	cmpop, jmpop: LONGINT;
BEGIN
	code.GetPiece (instr.src1, piece1);
	code.GetPiece (instr.src2, piece2);

	reg1 := GetReg (instr.src1, piece1);
	reg2 := GetReg (instr.src2, piece2);

	CASE instr.op OF
	PCLIR.tae:
		cmpop := ASM.opCMP; jmpop := ASM.opJB;
	| PCLIR.tne:
		cmpop := ASM.opCMP; jmpop := ASM.opJE;
	END;

	CASE GetSize (reg1) OF
	PCLIR.Int8 .. PCLIR.Int64:
	| PCLIR.Float32:
		cmpop := ASM.opCOMISS;
	| PCLIR.Float64:
		cmpop := ASM.opCOMISD;
	END;

	assembly.Emit (cmpop, reg1, reg2, NIL);

	IF instr.val < 80H THEN
		assembly.Emit (jmpop, PCA.NewImm8 (3), NIL, NIL); (* one byte push in trap *)
	ELSE
		assembly.Emit (jmpop, PCA.NewImm8 (3), NIL, NIL); (* four byte push in trap *)
	END;

	GenTrap (code, instr, pc);

	ReleaseSourceReg (instr.src1, piece1);
	ReleaseSourceReg (instr.src2, piece2);
END GenTrapcc;

PROCEDURE GenSaveRegisters (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR index: LONGINT; RAX: PCA.Reg;
BEGIN
	RAX := PCA.NewReg64 (rAX);
	FOR index := rAX TO r15 DO
		IF index IN currentRegisters.gpp THEN
			assembly.Emit (ASM.opPUSH, PCA.NewReg64 (index), NIL, NIL);
		END;
		IF index IN currentRegisters.xmm THEN
			assembly.Emit (ASM.opMOVD, RAX, PCA.NewXMMReg (index), NIL);
			assembly.Emit (ASM.opPUSH, RAX, NIL, NIL);
		END;
	END;
	savedRegisters[saveLevel] := currentRegisters; INC (saveLevel);
END GenSaveRegisters;

PROCEDURE GenRestoreRegisters (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR index: LONGINT; RAX: PCA.Reg;
BEGIN
	DEC (saveLevel);
	RAX := PCA.NewReg64 (rAX);
	FOR index := r15 TO rAX BY -1 DO
		IF index IN savedRegisters[saveLevel].xmm THEN
			assembly.Emit (ASM.opPOP, RAX, NIL, NIL);
			assembly.Emit (ASM.opMOVD, PCA.NewXMMReg (index), RAX, NIL);
		END;
		IF index IN savedRegisters[saveLevel].gpp THEN
			assembly.Emit (ASM.opPOP, PCA.NewReg64 (index), NIL, NIL);
		END;
	END;
	currentRegisters.gpp := currentRegisters.gpp + savedRegisters[saveLevel].gpp;
	currentRegisters.xmm := currentRegisters.xmm + savedRegisters[saveLevel].xmm;
END GenRestoreRegisters;

PROCEDURE GenPush (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR
	piece1: PCLIR.Piece;
	reg1, tmp: PCA.Reg;

BEGIN
	code.GetPiece (instr.src1, piece1);
	reg1 := GetReg (instr.src1, piece1);

	CASE GetSize (reg1) OF
	PCLIR.Int8 .. PCLIR.Int64:
		EmitPush (reg1);
	| PCLIR.Float32:
		tmp := PCA.NewReg32 (rAX);
		assembly.Emit (ASM.opMOVD, tmp, reg1, NIL);
		EmitPush (tmp);
	| PCLIR.Float64:
		tmp := PCA.NewReg64 (rAX);
		assembly.Emit (ASM.opMOVD, tmp, reg1, NIL);
		EmitPush (tmp);
	END;

	ReleaseSourceReg (instr.src1, piece1);
END GenPush;

PROCEDURE GenPop (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR tmp: PCA.Reg;
BEGIN
	AcquireReg (instr);

	CASE instr.dstSize OF
	PCLIR.Int8 .. PCLIR.Int64:
		EmitPop (instr.info(PCA.Reg));
	| PCLIR.Float32:
		tmp := PCA.NewReg32 (rAX);
		EmitPop (tmp);
		assembly.Emit (ASM.opCVTSI2SS, instr.info(PCA.Reg), tmp, NIL);
	| PCLIR.Float64:
		tmp := PCA.NewReg64 (rAX);
		EmitPop (tmp);
		assembly.Emit (ASM.opCVTSI2SD, instr.info(PCA.Reg), tmp, NIL);
	END;
END GenPop;

(* GenResult - Allocate the registers for functions results (after a call) *)
PROCEDURE GenResult (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN EmitResult (instr, rAX);
END GenResult;

PROCEDURE GenResult2 (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN EmitResult (instr, rDX);
END GenResult2;

PROCEDURE GenReturn (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN EmitReturn (code, instr, rAX);
END GenReturn;

PROCEDURE GenReturn2 (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN EmitReturn (code, instr, rDX);
END GenReturn2;

PROCEDURE GenLoad (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR
	piece1: PCLIR.Piece;
	reg1, tmp: PCA.Reg;
	imm: PCA.Imm;
	op: LONGINT;
BEGIN
	code.GetPiece (instr.src1, piece1);

	reg1 := GetReg (instr.src1, piece1);

	IF instr.dstCount # 0 THEN

		AcquireReg (instr);

		CASE instr.dstSize OF
		PCLIR.Int8 .. PCLIR.Int64:
			op := ASM.opMOV;
		| PCLIR.Float32:
			op := ASM.opMOVSS;
		| PCLIR.Float64:
			op := ASM.opMOVSD;
		END;

		IF IsAbsolute (instr.adr) THEN
			tmp := PCA.NewReg64 (instr.info(PCA.Reg).index);
			imm := PCA.NewImm64 (instr.val);
			assembly.Emit (ASM.opMOV, tmp, imm, NIL);
			assembly.Emit (op, instr.info(PCA.Reg), NewMem (instr.dstSize, tmp, 0), NIL);
			FixAbsolute (instr.adr, imm.pc);
		ELSE
			assembly.Emit (op, instr.info(PCA.Reg), NewMem (instr.dstSize, reg1, instr.val), NIL);
		END;

	END;

	ReleaseSourceReg (instr.src1, piece1);
END GenLoad;

PROCEDURE GenLoadC (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR imm: PCA.Imm;
BEGIN
	ASSERT (instr.dstSize IN PCLIR.IntSize);

	IF instr.dstCount # 0 THEN

		AcquireReg (instr);

		IF IsAbsolute (instr.adr) THEN
			imm := PCA.NewImm64 (instr.val);
			assembly.Emit (ASM.opMOV,  instr.info(PCA.Reg), imm, NIL);
			FixAbsolute (instr.adr, imm.pc);
		ELSIF (instr.val = 0) & (instr.adr = NIL) THEN
			assembly.Emit (ASM.opXOR, instr.info(PCA.Reg), instr.info(PCA.Reg), NIL);
		ELSIF (instr.dstSize = PCLIR.Int64) & (SYSTEM.VAL(LONGINT, instr.val) = instr.val) THEN
			(*	since the upper half of the 64bit wide target register is signed extended using
				this MOV instruction, one can save the leading bits of the 64bit wide immediate value	*)
			assembly.Emit (ASM.opMOV,  instr.info(PCA.Reg), PCA.NewImm32 (instr.val), NIL);
		ELSE
			assembly.Emit (ASM.opMOV,  instr.info(PCA.Reg), NewImm (instr.dstSize, instr.val), NIL);
		END;
	END;
END GenLoadC;

PROCEDURE GenLoadSP(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR piece1: PCLIR.Piece; reg1: PCA.Reg;
BEGIN
	code.GetPiece (instr.src1, piece1);
	reg1 := GetReg (instr.src1, piece1);
	assembly.Emit (ASM.opMOV, PCA.NewReg64 (rSP), reg1, NIL);
	ReleaseSourceReg (instr.src1, piece1);
END GenLoadSP;

PROCEDURE GenLoadFP(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR piece1: PCLIR.Piece; reg1: PCA.Reg;
BEGIN
	code.GetPiece (instr.src1, piece1);
	reg1 := GetReg (instr.src1, piece1);
	assembly.Emit (ASM.opMOV, PCA.NewReg64 (rBP), reg1, NIL);
	ReleaseSourceReg (instr.src1, piece1);
END GenLoadFP;

PROCEDURE GenStore (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR
	piece1, piece2: PCLIR.Piece;
	dest: PCA.Reg;
	source: PCA.Operand;
	dstSize: PCLIR.Size;
	op: LONGINT;
BEGIN
	code.GetPiece (instr.src1, piece1);
	code.GetPiece (instr.src2, piece2);

	dest := GetReg (instr.src1, piece1);

	IF (instr.src2 >= 0) & (piece2.instr[instr.src2].suppress) & (piece2.instr[instr.src2].op = PCLIR.loadc) THEN

		source := PCA.NewImm (PCA.default, piece2.instr[instr.src2].val);

		dstSize := piece2.instr[instr.src2].dstSize;

		IF dstSize = PCLIR.Int64 THEN dstSize := PCLIR.Int32 END;

		op := ASM.opMOV;

	ELSE
		source := GetReg (instr.src2, piece2);

		dstSize := GetSize (source(PCA.Reg));

		CASE dstSize OF
		PCLIR.Int8 .. PCLIR.Int64:
			op := ASM.opMOV;
		| PCLIR.Float32:
			op := ASM.opMOVSS;
		| PCLIR.Float64:
			op := ASM.opMOVSD;
		END;
	END;

	IF instr.src1 <= PCLIR.HwReg THEN
		ASSERT (op = ASM.opMOV);
		assembly.Emit (op, dest, source, NIL);
	ELSE
		assembly.Emit (op, NewMem (dstSize, dest, instr.val), source, NIL);
	END;

	ReleaseSourceReg (instr.src1, piece1);
	ReleaseSourceReg (instr.src2, piece2);
END GenStore;

PROCEDURE GenOut (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR
	piece1, piece2: PCLIR.Piece;
	reg1, reg2, port, source: PCA.Reg;
BEGIN
	code.GetPiece (instr.src1, piece1);
	code.GetPiece (instr.src2, piece2);

	reg1 := GetReg (instr.src1, piece1);
	reg2 := GetReg (instr.src2, piece2);

	port := PCA.NewReg16 (rDX);
	source := NewReg (GetSize (reg2), rAX);

	IF GetSize (reg1) = PCLIR.Int8 THEN
		assembly.Emit (ASM.opMOVSX, port, reg1, NIL);
	ELSE
		assembly.Emit (ASM.opMOV, port, NewReg (PCLIR.Int16, reg1.index), NIL);
	END;

	assembly.Emit (ASM.opMOV, source, reg2, NIL);
	assembly.Emit (ASM.opOUT, port, source, NIL);

	ReleaseSourceReg (instr.src1, piece1);
	ReleaseSourceReg (instr.src2, piece2);
END GenOut;

PROCEDURE GenIn (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR
	piece1: PCLIR.Piece;
	reg1, port, dest: PCA.Reg;
BEGIN
	ASSERT (instr.dstSize IN PCLIR.IntSize);

	code.GetPiece (instr.src1, piece1);

	reg1 := GetReg (instr.src1, piece1);

	port := PCA.NewReg16 (rDX);
	dest := NewReg (instr.dstSize, rAX);

	AcquireReg (instr);

	IF GetSize (reg1) = PCLIR.Int8 THEN
		assembly.Emit (ASM.opMOVSX, port, reg1, NIL);
	ELSE
		assembly.Emit (ASM.opMOV, port, PCA.NewReg16 (reg1.index), NIL);
	END;

	assembly.Emit (ASM.opIN, dest, port, NIL);
	assembly.Emit (ASM.opMOV, instr.info(PCA.Reg), dest, NIL);

	ReleaseSourceReg (instr.src1, piece1);
END GenIn;

PROCEDURE GenNop (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN assembly.Emit (ASM.opNOP, NIL, NIL, NIL);
END GenNop;

PROCEDURE GenLabel(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN
	IF instr.val = 0 THEN
		instr.val := assembly.pc;
	ELSIF assembly.pc >= PCM.breakpc THEN
		PCM.Error(400, instr.val, ""); PCM.breakpc := MAX(LONGINT)
	END;
	IF  instr.info # NIL THEN
		FixFixups (instr.info (Fixup));
	END;
END GenLabel;

PROCEDURE GenJcc(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR
	piece1, piece2: PCLIR.Piece;
	reg1: PCA.Reg; op2: PCA.Operand;
	cmpop, jmpop: LONGINT;
BEGIN
	code.GetPiece (instr.src1, piece1);
	code.GetPiece (instr.src2, piece2);

	reg1 := GetReg (instr.src1, piece1);

	IF instr.src2 = PCLIR.none THEN
		op2 := PCA.NewImm (PCA.default, instr.val);
	ELSE
		op2 := GetReg (instr.src2, piece2);
	END;

	CASE instr.op OF
	PCLIR.je:
		cmpop := ASM.opCMP; jmpop := ASM.opJE;
	| PCLIR.jne:
		cmpop := ASM.opCMP; jmpop := ASM.opJNE;
	| PCLIR.jlt:
		cmpop := ASM.opCMP; IF IsFloat (GetSize (reg1)) THEN jmpop := ASM.opJB ELSE jmpop := ASM.opJL END;
	| PCLIR.jle:
		cmpop := ASM.opCMP; IF IsFloat (GetSize (reg1)) THEN jmpop := ASM.opJBE ELSE jmpop := ASM.opJLE END;
	| PCLIR.jgt:
		cmpop := ASM.opCMP; IF IsFloat (GetSize (reg1)) THEN jmpop := ASM.opJA ELSE jmpop := ASM.opJG END;
	| PCLIR.jge:
		cmpop := ASM.opCMP; IF IsFloat (GetSize (reg1)) THEN jmpop := ASM.opJAE ELSE jmpop := ASM.opJGE END;
	| PCLIR.jb:
		cmpop := ASM.opCMP; jmpop := ASM.opJB;
	| PCLIR.jbe:
		cmpop := ASM.opCMP; jmpop := ASM.opJBE;
	| PCLIR.ja:
		cmpop := ASM.opCMP; jmpop := ASM.opJA;
	| PCLIR.jae:
		cmpop := ASM.opCMP; jmpop := ASM.opJAE;
	| PCLIR.jf:
		cmpop := ASM.opBT; jmpop := ASM.opJC;
	| PCLIR.jnf:
		cmpop := ASM.opBT; jmpop := ASM.opJNC;
	END;

	CASE GetSize (reg1) OF
	PCLIR.Int8 .. PCLIR.Int64:
	| PCLIR.Float32:
		ASSERT (cmpop = ASM.opCMP);
		cmpop := ASM.opCOMISS;
	| PCLIR.Float64:
		ASSERT (cmpop = ASM.opCMP);
		cmpop := ASM.opCOMISD;
	END;

	assembly.Emit (cmpop, reg1, op2, NIL);
	EmitJmp (jmpop, code, instr);

	ReleaseSourceReg (instr.src1, piece1);
	ReleaseSourceReg (instr.src2, piece2);
END GenJcc;

PROCEDURE GenJmp (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN
	EmitJmp (ASM.opJMP, code, instr);
END GenJmp;

PROCEDURE GenCall(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR adr: PCBT.Procedure; imm: PCA.Imm; dest: PCA.Reg;
BEGIN
	adr := instr.adr(PCBT.Procedure);
	IF (adr.owner # PCBT.context) THEN
		imm := PCA.NewImm64 (0);
		dest := PCA.NewReg64 (rAX);
		assembly.Emit (ASM.opMOV, dest, imm, NIL);
		assembly.Emit (ASM.opCALL, dest, NIL, NIL);
		PCBT.context.UseProcedure (adr, imm.pc);
	ELSIF adr.codeoffset # 0 THEN
		assembly.Emit (ASM.opCALL, PCA.NewOffset32 (adr.codeoffset), NIL, NIL);
	ELSE
		imm := PCA.NewImm32 (adr.fixlist);
		assembly.Emit (ASM.opCALL, imm, NIL, NIL);
		adr.fixlist := imm.pc
	END
END GenCall;

PROCEDURE GenCallReg(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR piece1: PCLIR.Piece; reg1: PCA.Reg;
BEGIN
	code.GetPiece (instr.src1, piece1);
	reg1 := GetReg (instr.src1, piece1);

	assembly.Emit (ASM.opCALL, reg1, NIL, NIL);

	ReleaseSourceReg (instr.src1, piece1);
END GenCallReg;

PROCEDURE GenSysCall(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR imm: PCA.Imm; dest: PCA.Reg;
BEGIN
	imm := PCA.NewImm64 (0);
	dest := PCA.NewReg64 (rAX);
	assembly.Emit (ASM.opMOV, dest, imm, NIL);
	assembly.Emit (ASM.opCALL, dest, NIL, NIL);
	PCBT.context.UseSyscall (instr.val, imm.pc);
END GenSysCall;

PROCEDURE GenSetcc(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR
	piece1, piece2: PCLIR.Piece;
	reg1: PCA.Reg; op2: PCA.Operand;
	cmpop, setop: LONGINT;
BEGIN
	AcquireReg (instr);

	code.GetPiece (instr.src1, piece1);
	code.GetPiece (instr.src2, piece2);

	reg1 := GetReg (instr.src1, piece1);

	IF instr.src2 = PCLIR.none THEN
		op2 := PCA.NewImm (PCA.default, instr.val);
	ELSE
		op2 := GetReg (instr.src2, piece2);
	END;

	CASE instr.op OF
	PCLIR.sete:
		cmpop := ASM.opCMP; setop := ASM.opSETE;
	| PCLIR.setne:
		cmpop := ASM.opCMP; setop := ASM.opSETNE;
	| PCLIR.setlt:
		cmpop := ASM.opCMP; IF IsFloat (GetSize (reg1)) THEN setop := ASM.opSETB ELSE setop := ASM.opSETL END;
	| PCLIR.setle:
		cmpop := ASM.opCMP; IF IsFloat (GetSize (reg1)) THEN setop := ASM.opSETBE ELSE setop := ASM.opSETLE END;
	| PCLIR.setgt:
		cmpop := ASM.opCMP; IF IsFloat (GetSize (reg1)) THEN setop := ASM.opSETA ELSE setop := ASM.opSETG END;
	| PCLIR.setge:
		cmpop := ASM.opCMP; IF IsFloat (GetSize (reg1)) THEN setop := ASM.opSETAE ELSE setop := ASM.opSETGE END;
	| PCLIR.setb:
		cmpop := ASM.opCMP; setop := ASM.opSETB;
	| PCLIR.setbe:
		cmpop := ASM.opCMP; setop := ASM.opSETBE;
	| PCLIR.seta:
		cmpop := ASM.opCMP; setop := ASM.opSETA;
	| PCLIR.setae:
		cmpop := ASM.opCMP; setop := ASM.opSETAE;
	| PCLIR.setf:
		cmpop := ASM.opBT; setop := ASM.opSETC;
	| PCLIR.setnf:
		cmpop := ASM.opBT; setop := ASM.opSETNC;
	END;

	CASE GetSize (reg1) OF
	PCLIR.Int8 .. PCLIR.Int64:
	| PCLIR.Float32:
		ASSERT (cmpop = ASM.opCMP);
		cmpop := ASM.opCOMISS;
	| PCLIR.Float64:
		ASSERT (cmpop = ASM.opCMP);
		cmpop := ASM.opCOMISD;
	END;

	assembly.Emit (cmpop, reg1, op2, NIL);

	IF instr.dstSize # PCLIR.Int8 THEN
		assembly.Emit (ASM.opXOR, instr.info(PCA.Reg), instr.info(PCA.Reg), NIL);
	END;

	assembly.Emit (setop, instr.info(PCA.Reg), NIL, NIL);

	ReleaseSourceReg (instr.src1, piece1);
	ReleaseSourceReg (instr.src2, piece2);
END GenSetcc;

PROCEDURE GenKill(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR piece1: PCLIR.Piece; reg1: PCA.Reg;
BEGIN
	code.GetPiece (instr.src1, piece1);
	ReleaseSourceReg (instr.src1, piece1);
END GenKill;

PROCEDURE GenPhi(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR
	piece1, piece2: PCLIR.Piece;
	reg1, reg2: PCA.Reg;
BEGIN
	ASSERT (instr.dstSize IN PCLIR.IntSize);
	ASSERT (instr.info = NIL);

	IF instr.src1 > instr.src2 THEN PCLIR.SwapSources (instr) END;

	code.GetPiece (instr.src1, piece1);
	code.GetPiece (instr.src2, piece2);

	(* The first source register must be acquired
	before this phi instruction *)

	ASSERT (piece1.instr[instr.src1].info # NIL);
	reg1 := GetReg (instr.src1, piece1);

	(* the second source register might not be acquired now *)

	IF piece2.instr[instr.src2].info = NIL THEN
		reg2 := reg1;
		piece2.instr[instr.src2].info := reg2;
	ELSE
		reg2 := GetReg (instr.src2, piece2);
	END;

	(* only the same register is mergeable *)
	ASSERT (reg1.index = reg2.index);
	ASSERT (GetSize (reg1) = GetSize (reg2));

	instr.info := reg1;
	AllocReg (instr.dstSize, reg1.index);
END GenPhi;

PROCEDURE GenConv (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR
	piece1: PCLIR.Piece;
	reg1, tmp: PCA.Reg;
	srcSize: PCLIR.Size;
	op: LONGINT;
BEGIN
	ASSERT (instr.info = NIL);

	code.GetPiece (instr.src1, piece1);

	reg1 := GetReg (instr.src1, piece1);

	srcSize := GetSize (reg1);

	IF instr.dstSize = srcSize THEN

		AcquireSourceReg (instr, instr.src1, piece1);

	ELSIF instr.dstSize IN PCLIR.FloatSize THEN

		AcquireReg (instr);

		CASE srcSize OF
		| PCLIR.Int8, PCLIR.Int16:
			IF instr.dstSize = PCLIR.Float32 THEN
				tmp := PCA.NewReg32 (rAX);
				op := ASM.opCVTSI2SS;
			ELSE
				op := ASM.opCVTSI2SD;
				tmp := PCA.NewReg64 (rAX);
			END;
			assembly.Emit (ASM.opMOVSX, tmp, reg1, NIL);
			reg1 := tmp;
		| PCLIR.Int32, PCLIR.Int64:
			IF instr.dstSize = PCLIR.Float32 THEN
				op := ASM.opCVTSI2SS;
			ELSE
				op := ASM.opCVTSI2SD;
			END;
		| PCLIR.Float32:
			op := ASM.opCVTSS2SD;
		| PCLIR.Float64:
			op := ASM.opCVTSD2SS;
		END;

		assembly.Emit (op, instr.info(PCA.Reg), reg1, NIL);

		ReleaseSourceReg (instr.src1, piece1);

	ELSE

		CASE srcSize OF
		PCLIR.Int8 .. PCLIR.Int64:

			IF instr.dstSize < srcSize THEN
				AcquireSourceReg (instr, instr.src1, piece1);
			ELSIF instr.op = PCLIR.convs THEN
				AcquireReg (instr);

				IF (instr.dstSize = PCLIR.Int64) & (srcSize = PCLIR.Int32) THEN
					assembly.Emit (ASM.opMOVSXD, instr.info(PCA.Reg), reg1, NIL);
				ELSE
					assembly.Emit (ASM.opMOVSX, instr.info(PCA.Reg), reg1, NIL);
				END;
				ReleaseSourceReg (instr.src1, piece1);
			ELSE
				IF (instr.dstSize = PCLIR.Int64) & (srcSize = PCLIR.Int32) THEN
					AcquireSourceReg (instr, instr.src1, piece1);
				ELSE
					AcquireReg (instr);
					assembly.Emit (ASM.opMOVZX, instr.info(PCA.Reg), reg1, NIL);
					ReleaseSourceReg (instr.src1, piece1);
				END;
			END;

		| PCLIR.Float32:
			AcquireReg (instr);
			assembly.Emit (ASM.opCVTSS2SI, instr.info(PCA.Reg), reg1, NIL);
			ReleaseSourceReg (instr.src1, piece1);

		| PCLIR.Float64:
			AcquireReg (instr);
			assembly.Emit (ASM.opCVTSD2SI, instr.info(PCA.Reg), reg1, NIL);
			ReleaseSourceReg (instr.src1, piece1);
		END;
	END;
END GenConv;

PROCEDURE GenNegNot (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN
	CASE instr.dstSize OF
	PCLIR.Int8 .. PCLIR.Int64:
		IF (instr.op = PCLIR.neg) OR (instr.dstSigned) THEN
			EmitType1 (ASM.opNEG, code, instr, NIL);
		ELSE
			EmitType1 (ASM.opNOT, code, instr, NIL);
		END;
	| PCLIR.Float32:
		EmitSSEBitOp (code, instr, ASM.opXORPS, 31, FALSE);
	| PCLIR.Float64:
		EmitSSEBitOp (code, instr, ASM.opXORPD, 63, FALSE);
	END;
END GenNegNot;

PROCEDURE GenAbs(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR
	piece1: PCLIR.Piece;
	reg1, dest, source: PCA.Reg;
	imm: PCA.Imm;
BEGIN
	ASSERT (instr.dstSigned);

	CASE instr.dstSize OF
	PCLIR.Int8 .. PCLIR.Int64:
		code.GetPiece (instr.src1, piece1);

		reg1 := GetReg (instr.src1, piece1);

		AcquireReg (instr);
		dest := instr.info(PCA.Reg);

		source := NewReg (instr.dstSize, rAX);
		assembly.Emit (ASM.opMOV, source, reg1, NIL);

		CASE instr.dstSize OF
		| PCLIR.Int8: imm := PCA.NewImm8 (7);
		| PCLIR.Int16: imm := PCA.NewImm8 (15);
		| PCLIR.Int32: imm := PCA.NewImm8 (31);
		| PCLIR.Int64: imm := PCA.NewImm8 (63);
		END;

		assembly.Emit (ASM.opMOV, dest, source, NIL);
		assembly.Emit (ASM.opSAR, source, imm, NIL);
		assembly.Emit (ASM.opXOR, dest, source, NIL);
		assembly.Emit (ASM.opSUB, dest, source, NIL);

		ReleaseSourceReg (instr.src1, piece1);

	| PCLIR.Float32:
		EmitSSEBitOp (code, instr, ASM.opANDPS, 31, TRUE);
	| PCLIR.Float64:
		EmitSSEBitOp (code, instr, ASM.opANDPD, 63, TRUE);
	END;
END GenAbs;

PROCEDURE GenBts (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN
	ASSERT (instr.dstSize IN PCLIR.IntSize);
	EmitType2 (ASM.opBTS, code, instr);
END GenBts;

PROCEDURE GenBtc (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN
	ASSERT (instr.dstSize IN PCLIR.IntSize);
	EmitType2 (ASM.opBTR, code, instr);
END GenBtc;

PROCEDURE GenMul(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN
(*	ASSERT (instr.dstSigned); *)
	CASE instr.dstSize OF
	PCLIR.Int8 .. PCLIR.Int64:
		EmitType2 (ASM.opIMUL, code, instr);
	| PCLIR.Float32:
		EmitType2 (ASM.opMULSS, code, instr);
	| PCLIR.Float64:
		EmitType2 (ASM.opMULSD, code, instr);
	END;
END GenMul;

PROCEDURE GenDivMod(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR
	piece1, piece2: PCLIR.Piece;
	reg1, reg2, dividend, quotient, remainder: PCA.Reg;
	offset: PCA.Imm; prevPC: LONGINT;
BEGIN
(*	ASSERT (instr.dstSigned); *)

	CASE instr.dstSize OF
	PCLIR.Int8 .. PCLIR.Int64:

		code.GetPiece (instr.src1, piece1);
		code.GetPiece (instr.src2, piece2);

		reg1 := GetReg (instr.src1, piece1);
		reg2 := GetReg (instr.src2, piece2);

		IF instr.dstSize = PCLIR.Int8 THEN
			dividend := PCA.NewReg16 (rAX);
			quotient := PCA.NewReg8 (rAX);
			remainder := quotient; (* eventually AH *)
		ELSE
			dividend := NewReg (instr.dstSize, rAX);
			quotient := dividend;
			remainder := NewReg (instr.dstSize, rDX);
		END;

		assembly.Emit (ASM.opMOV, quotient, reg1, NIL);

		(* sign extend dividend (rDX or AX register) *)
		CASE instr.dstSize OF
		PCLIR.Int8:
			assembly.Emit (ASM.opCBW, NIL, NIL, NIL);
		| PCLIR.Int16:
			assembly.Emit (ASM.opCWD, NIL, NIL, NIL);
		| PCLIR.Int32:
			assembly.Emit (ASM.opCDQ, NIL, NIL, NIL);
		| PCLIR.Int64:
			assembly.Emit (ASM.opCQO, NIL, NIL, NIL);
		END;

		assembly.Emit (ASM.opIDIV, reg2, NIL, NIL);

		IF instr.dstSize = PCLIR.Int8 THEN
			assembly.Emit (ASM.opSAR, dividend, PCA.NewImm8 (8), NIL);
		END;

		AcquireSourceReg (instr, instr.src1, piece1);

		IF instr.op = PCLIR.mod THEN
			assembly.Emit (ASM.opCMP, remainder, PCA.NewImm8 (0), NIL);
			offset := PCA.NewImm8 (0);
			assembly.Emit (ASM.opJGE, offset, NIL, NIL);
			assembly.Emit (ASM.opADD, remainder, reg2, NIL);
			prevPC := assembly.pc;
			assembly.SetPC (offset.pc);
			assembly.PutByte (prevPC - offset.pc - 1);
			assembly.SetPC (prevPC);
			assembly.Emit (ASM.opMOV, instr.info(PCA.Reg), remainder, NIL);
		ELSE
			assembly.Emit (ASM.opSHL, remainder, PCA.NewImm8 (1), NIL);
			assembly.Emit (ASM.opSBB, quotient, PCA.NewImm8 (0), NIL);
			assembly.Emit (ASM.opMOV, instr.info(PCA.Reg), quotient, NIL);
		END;

		ReleaseSourceReg (instr.src2, piece2);

	| PCLIR.Float32:
		EmitType2 (ASM.opDIVSS, code, instr);
	| PCLIR.Float64:
		EmitType2 (ASM.opDIVSD, code, instr);
	END;
END GenDivMod;

PROCEDURE GenAdd (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN
	CASE instr.dstSize OF
	PCLIR.Int8 .. PCLIR.Int64:
		IF (instr.src2 = PCLIR.none) & (instr.val = 0) THEN
			EmitType1 (ASM.opNOP, code, instr, NIL);
		ELSE
			EmitType2 (ASM.opADD, code, instr);
		END;
	| PCLIR.Float32:
		EmitType2 (ASM.opADDSS, code, instr);
	| PCLIR.Float64:
		EmitType2 (ASM.opADDSD, code, instr);
	END;
END GenAdd;

PROCEDURE GenSub (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN
	CASE instr.dstSize OF
	PCLIR.Int8 .. PCLIR.Int64:
		IF (instr.src2 = PCLIR.none) & (instr.val = 0) THEN
			EmitType1 (ASM.opNOP, code, instr, NIL);
		ELSE
			EmitType2 (ASM.opSUB, code, instr);
		END;
	| PCLIR.Float32:
		EmitType2 (ASM.opSUBSS, code, instr);
	| PCLIR.Float64:
		EmitType2 (ASM.opSUBSD, code, instr);
	END;
END GenSub;

PROCEDURE GenAnd (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN
	ASSERT (instr.dstSize IN PCLIR.IntSize);
	EmitType2 (ASM.opAND, code, instr);
END GenAnd;

PROCEDURE GenOr (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN
	ASSERT (instr.dstSize IN PCLIR.IntSize);
	EmitType2 (ASM.opOR, code, instr);
END GenOr;

PROCEDURE GenXor (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN
	ASSERT (instr.dstSize IN PCLIR.IntSize);
	EmitType2 (ASM.opXOR, code, instr);
END GenXor;

PROCEDURE GenShift(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR
	piece1, piece2: PCLIR.Piece;
	reg2, shift, CL: PCA.Reg;
	op: LONGINT;

	prevPC: LONGINT;
	thenOffset, elseOffset: PCA.Imm;

BEGIN
	ASSERT (instr.dstSize IN PCLIR.IntSize);

	IF instr.src2 = PCLIR.none THEN
		CASE instr.op OF
		PCLIR.bsh:
			IF instr.val < 0 THEN op := ASM.opSHR ELSE op := ASM.opSHL END
		| PCLIR.ash:
			IF instr.val < 0 THEN op := ASM.opSAR ELSE op := ASM.opSAL END
		| PCLIR.rot:
			IF instr.val < 0 THEN op := ASM.opROR ELSE op := ASM.opROL END
		END;

		EmitType1 (op, code, instr, PCA.NewImm8 (ABS (instr.val)));

	ELSE

		code.GetPiece (instr.src1, piece1);
		code.GetPiece (instr.src2, piece2);

		AcquireSourceReg (instr, instr.src1, piece1);

		reg2 := GetReg (instr.src2, piece2);
		shift := PCA.NewReg8 (rCX);

		assembly.Emit (ASM.opMOV, shift, PCA.NewReg8 (reg2.index), NIL);

		assembly.Emit (ASM.opCMP, shift, PCA.NewImm (PCA.default, 0), NIL);

		thenOffset := PCA.NewImm8 (0);
		assembly.Emit (ASM.opJL, thenOffset, NIL, NIL);

		CASE instr.op OF
		PCLIR.bsh: op := ASM.opSHL;
		| PCLIR.ash: op := ASM.opSAL;
		| PCLIR.rot: op := ASM.opROL;
		END;

		assembly.Emit (op, instr.info(PCA.Reg), shift, NIL);

		elseOffset := PCA.NewImm8 (0);
		assembly.Emit (ASM.opJMP, elseOffset, NIL, NIL);

		prevPC := assembly.pc;
		assembly.SetPC (thenOffset.pc); assembly.PutByte (prevPC - thenOffset.pc - 1);
		assembly.SetPC (prevPC);

		assembly.Emit (ASM.opNEG, shift, NIL, NIL);

		CASE instr.op OF
		PCLIR.bsh: op := ASM.opSHR;
		| PCLIR.ash: op := ASM.opSAR;
		| PCLIR.rot: op := ASM.opROR;
		END;

		assembly.Emit (op, instr.info(PCA.Reg), shift, NIL);

		prevPC := assembly.pc;
		assembly.SetPC (elseOffset.pc); assembly.PutByte (prevPC - elseOffset.pc - 1);
		assembly.SetPC (prevPC);

		ReleaseSourceReg (instr.src2, piece2);
	END;

END GenShift;

PROCEDURE GenMoveDown(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN EmitMove (code, instr, ASM.opSTD);
END GenMoveDown;

PROCEDURE GenMove(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
BEGIN EmitMove (code, instr, ASM.opCLD);
END GenMove;

PROCEDURE GenInline(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR
	inline: PCLIR.AsmInline;
	block: PCLIR.AsmBlock;
	fixup: PCLIR.AsmFixup;
	i: LONGINT;
BEGIN
	inline := instr.adr(PCLIR.AsmInline);

	fixup := inline.fixup;
	WHILE fixup # NIL DO
		FixAbsolute (fixup.adr, assembly.pc + fixup.offset);
		fixup := fixup.next;
	END;

	block := inline.code;
	WHILE block # NIL DO
		FOR i := 0 TO block.len - 1 DO assembly.PutByte(ORD(block.code[i])) END;
		block := block.next
	END;
END GenInline;

PROCEDURE GenCase(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR
	piece1: PCLIR.Piece;
	case: Case;
BEGIN
	code.GetPiece (instr.src1, piece1);

	case := instr.info(Case);
	case.reg := GetReg (instr.src1, piece1);
	case.curCasePC := -2;

	ReleaseSourceReg (instr.src1, piece1);
END GenCase;

PROCEDURE GenCaseLine(code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT);
VAR
	piece1, piece2: PCLIR.Piece;
	case: Case; caseLine: CaseLine;

BEGIN
	code.GetPiece (instr.src1, piece1);
	case := piece1.instr[instr.src1].info(Case);

	FixFixups (case.prevCase);

	IF instr.op = PCLIR.casel THEN
		caseLine := instr.info(CaseLine);
		IF caseLine.last THEN
			IF caseLine.from = caseLine.to THEN
				EmitCmpJmp (case.reg, caseLine.from, ASM.opJNE, case.prevCase);
			ELSE
				EmitCmpJmp (case.reg, caseLine.from, ASM.opJL, case.prevCase);
				EmitCmpJmp (case.reg, caseLine.to, ASM.opJG, case.prevCase);
			END;
			FixFixups (case.nextCase);
		ELSIF caseLine.from = caseLine.to THEN
			EmitCmpJmp (case.reg, instr.val, ASM.opJE, case.nextCase);
		ELSE
			EmitCmpJmp (case.reg, caseLine.from, ASM.opJL, case.prevCase);
			EmitCmpJmp (case.reg, caseLine.to, ASM.opJLE, case.nextCase);
		END;
		case.curCasePC := pc;
	END;

(*

	IF instr.op = PCLIR.casel THEN

		IF instr.src2 = pc THEN
			assembly.Emit (ASM.opCMP, case.reg, PCA.NewImm (PCA.default, instr.val), NIL);
			assembly.Emit (ASM.opJNE, PCA.NewImm32 (0), NIL, NIL);
			NEW (case.fixup); case.fixup.pc := assembly.pc;
		ELSE
			code.GetPiece (instr.src2, piece2);

			assembly.Emit (ASM.opCMP, case.reg, PCA.NewImm (PCA.default, instr.val), NIL);
			assembly.Emit (ASM.opJL, PCA.NewImm32 (0), NIL, NIL);
			NEW (case.fixup); case.fixup.pc := assembly.pc;

			assembly.Emit (ASM.opCMP, case.reg, PCA.NewImm (PCA.default, piece2.instr[instr.src2].val), NIL);
			assembly.Emit (ASM.opJG, PCA.NewImm32 (0), NIL, NIL);
			NEW (case.fixup.next);
			case.fixup.next.pc := assembly.pc;
		END;
	END;
	case.prevCasePC := pc;
*)
END GenCaseLine;

(* Debug Procedures *)
PROCEDURE DumpCode (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT;  context: ANY);
END DumpCode;

PROCEDURE Optimize (code: PCLIR.Code;  VAR instr: PCLIR.Instruction;  pc: LONGINT;  context: ANY);
VAR
	piece1, piece2: PCLIR.Piece;
	src1, src2: PCLIR.Register;
	case: Case; caseLine: CaseLine;

	PROCEDURE UseResult (VAR instr: PCLIR.Instruction);
	BEGIN
		DEC (instr.dstCount);
		IF instr.dstCount = 0 THEN
			instr.suppress := TRUE;
		END;
	END UseResult;

	PROCEDURE SetConstant (VAR reg: PCLIR.Register);
	VAR
		src: PCLIR.Register;
		piece: PCLIR.Piece;
	BEGIN
		src := reg;

		IF src >= 0 THEN
			code.GetPiece (src, piece);

			IF (piece.instr[src].op = PCLIR.loadc) & (piece.instr[src].dstSize IN PCLIR.IntSize) & (piece.instr[src].adr = NIL) THEN
				instr.val := piece.instr[src].val;
				UseResult (piece.instr[src]);
				reg := PCLIR.none;
			END;
		END;
	END SetConstant;

	PROCEDURE SetResult (VAR reg: PCLIR.Register);
	VAR
		src: PCLIR.Register;
		piece: PCLIR.Piece;
	BEGIN
		src := reg;

		IF src >= 0 THEN
			code.GetPiece (src, piece);

			IF (piece.instr[src].dstSize IN PCLIR.IntSize) & (piece.instr[src].dstCount = 1) THEN
				IF piece.instr[src].op = PCLIR.result THEN
					piece.instr[src].info := NewReg (piece.instr[src].dstSize, rAX);
					UseResult (piece.instr[src]);
				ELSIF piece.instr[src].op = PCLIR.result2 THEN
					piece.instr[src].info := NewReg (piece.instr[src].dstSize, rDX);
					UseResult (piece.instr[src]);
				END;
			END;
		END;
	END SetResult;

BEGIN

	src1 := instr.src1;
	src2 := instr.src2;

	IF (src1 >= 0) & (src1 < code.pc) THEN code.GetPiece (src1, piece1) ELSE piece1 := NIL END;
	IF (src2 >= 0) & (src2 < code.pc) THEN code.GetPiece (src2, piece2) ELSE piece2 := NIL END;

	CASE instr.op OF
	PCLIR.ash, PCLIR.bsh, PCLIR.rot:
		SetConstant (instr.src2);
	| PCLIR.add, PCLIR.sub, PCLIR.and, PCLIR.or, PCLIR.xor:
		SetConstant (instr.src2);
	| PCLIR.sete .. PCLIR.setnf:
		SetConstant (instr.src2);
	| PCLIR.store:
		IF (src1 >= 0) & (piece1.instr[src1].op = PCLIR.add) THEN
			IF (piece1.instr[src1].dstCount = 1) & (piece1.instr[src1].src2 = PCLIR.none) THEN
				INC (instr.val, piece1.instr[src1].val);
				instr.src1 := piece1.instr[src1].src1;
				UseResult (piece1.instr[src1]);
			END;
		END;
		IF (src2 >= 0) & (piece2.instr[src2].op = PCLIR.loadc) THEN
			IF (piece2.instr[src2].dstCount = 1) & (piece2.instr[src2].dstSize IN LegacyIntegers) & (piece2.instr[src2].adr = NIL) THEN
				UseResult (piece2.instr[src2]);
			END;
		END;
(*		SetResult (instr.src2); *)
	| PCLIR.loadc:
		(* workaround for paco generating code that assigns too large values to bytes *)
		IF instr.dstSize = PCLIR.Int8 THEN
			instr.val := SYSTEM.VAL (SHORTINT, instr.val);
		END;
	| PCLIR.ret:
		IF src1 >= 0 THEN
			CASE piece1.instr[src1].op OF
			PCLIR.loadc:
				piece1.instr[src1].info := NewReg (piece1.instr[src1].dstSize, rAX);
				instr.suppress := TRUE;
			| PCLIR.sete .. PCLIR.setnf:
				piece1.instr[src1].info := PCA.NewReg8 (rAX);
				instr.suppress := TRUE;
			ELSE
			END
		END
	| PCLIR.case:

		NEW (case);
		instr.info := case;
		case.curCasePC := -2;

	| PCLIR.casel:

		case := piece1.instr[src1].info(Case);

		IF pc = case.curCasePC + 1 THEN
			src2 := case.curCasePC;
			code.GetPiece (src2, piece2);
			piece2.instr[src2].info(CaseLine).last := FALSE;

			IF instr.val = case.curCaseLine.to + 1 THEN
				caseLine := case.curCaseLine;
				instr.suppress := TRUE;
			ELSE
				NEW (caseLine);
				caseLine.first := FALSE;
				caseLine.from := instr.val;
				case.curCaseLine := caseLine;
			END;
		ELSE
			NEW (caseLine);
			caseLine.first := TRUE;
			caseLine.from := instr.val;
			case.curCaseLine := caseLine;
		END;

		instr.info := caseLine;
		caseLine.to := instr.val;
		case.curCasePC := pc;
		caseLine.last := TRUE;

(*

		(* if the case value is one ahead the previous case ignore this instruction and adapt the first case instruction *)
		IF (case.firstCasePC # -1) & (pc = case.prevCasePC + 1) & (instr.val = case.prevCaseValue + 1) THEN
			src2 := case.firstCasePC;
			code.GetPiece (src2, piece2);
			piece2.instr[src2].src2 := pc;
			instr.suppress := TRUE;
		ELSE
			(* src2 stores the pc of the last case line defining the range down to this instruction *)
			instr.src2 := pc;
			case.firstCasePC := pc;
		END;
		case.prevCasePC := pc;
		case.prevCaseValue := instr.val;
*)
	ELSE
	END;

END Optimize;

PROCEDURE DoOptimize (code: PCLIR.Code);
BEGIN code.Traverse (Optimize, FALSE, NIL);
END DoOptimize;

(* Init - Initialize code generator - Installed in PCBT.CG *)
PROCEDURE Init (): BOOLEAN;
BEGIN
	NEW (assembly, PCM.diagnostics, NIL);

	currentRegisters.gpp := {};
	currentRegisters.xmm := {};
	saveLevel := 0;

	RETURN TRUE
END Init;

(* Done - Code generator results - Installed in PCBT.CG *)
PROCEDURE Done (VAR result: LONGINT);
BEGIN result := 0;
END Done;

PROCEDURE GetCode (VAR codeArr: PCLIR.CodeArray; VAR length, hdrlength, addressFactor: LONGINT);
VAR i: LONGINT;
BEGIN
	length := assembly.pc;
	hdrlength := length;
	addressFactor := 1;
	assembly.SetPC (0);
	NEW (codeArr, length);
	FOR i := 0 TO length - 1 DO
		codeArr[i] := assembly.GetByte ();
	END;

(*	SendFile.SendData ("code.bin", codeArr^, length);    *)

END GetCode;

(* Module Initialization and Configuration *)
PROCEDURE Install*;
VAR  i: PCLIR.Opcode;
BEGIN
	PCLIR.CG.Init := Init;
	PCLIR.CG.Done := Done;
	PCLIR.CG.GetCode := GetCode;
	PCLIR.CG.DumpCode := DumpCode;
	PCLIR.CG.Optimize := DoOptimize;
	PCLIR.CG.MaxCodeSize := 40000H; (* should depend also on object filegenerator *)

	IF AlignParameters THEN
		PCLIR.CG.ParamAlign := 8;
	ELSE
		PCLIR.CG.ParamAlign := 2;
	END;

	PCBT.SetNumberOfSyscalls(PCBT.DefaultNofSysCalls);
	NEW(PCLIR.CG.SysCallMap, PCBT.NofSysCalls);
	PCLIR.InitDefaultSyscalls;

	PCLIR.Address := PCLIR.Int64;
	PCLIR.Set := PCLIR.Int64;
	PCLIR.SizeType := PCLIR.Int64;

	PCLIR.InstructionInit := InstructionInit;
	PCLIR.SetMethods(PCLIR.enter, GenEnter);
	PCLIR.SetMethods(PCLIR.exit, GenExit);
	PCLIR.SetMethods(PCLIR.trap, GenTrap);
	FOR i := PCLIR.tae TO PCLIR.tne DO
		PCLIR.SetMethods(i, GenTrapcc)
	END;
	PCLIR.SetMethods(PCLIR.saveregs, GenSaveRegisters);
	PCLIR.SetMethods(PCLIR.loadregs, GenRestoreRegisters);
	PCLIR.SetMethods(PCLIR.ret, GenReturn);
	PCLIR.SetMethods(PCLIR.ret2, GenReturn2);
	PCLIR.SetMethods(PCLIR.result, GenResult);
	PCLIR.SetMethods(PCLIR.result2, GenResult2);
	PCLIR.SetMethods(PCLIR.pop, GenPop);
	PCLIR.SetMethods(PCLIR.load, GenLoad);
	PCLIR.SetMethods(PCLIR.loadc, GenLoadC);
	PCLIR.SetMethods(PCLIR.store, GenStore);
	PCLIR.SetMethods(PCLIR.in, GenIn);
	PCLIR.SetMethods(PCLIR.out, GenOut);
	PCLIR.SetMethods(PCLIR.nop, GenNop);
	PCLIR.SetMethods(PCLIR.label, GenLabel);
	PCLIR.SetMethods(PCLIR.finallylabel, GenLabel);
	FOR i := PCLIR.je TO PCLIR.jnf DO
		PCLIR.SetMethods(i, GenJcc)
	END;
	PCLIR.SetMethods(PCLIR.jmp, GenJmp);
	PCLIR.SetMethods(PCLIR.call, GenCall);
	PCLIR.SetMethods(PCLIR.callreg, GenCallReg);
	PCLIR.SetMethods(PCLIR.syscall, GenSysCall);
	FOR i := PCLIR.sete TO PCLIR.setnf DO
		PCLIR.SetMethods(i, GenSetcc)
	END;
	PCLIR.SetMethods(PCLIR.kill, GenKill);
	PCLIR.SetMethods(PCLIR.phi, GenPhi);
	PCLIR.SetMethods(PCLIR.push, GenPush);
	PCLIR.SetMethods(PCLIR.loadsp, GenLoadSP);
	PCLIR.SetMethods(PCLIR.loadfp, GenLoadFP);

	PCLIR.SetMethods(PCLIR.convs, GenConv);
	PCLIR.SetMethods(PCLIR.convu, GenConv);
	PCLIR.SetMethods(PCLIR.copy, GenConv);

	PCLIR.SetMethods(PCLIR.not, GenNegNot);
	PCLIR.SetMethods(PCLIR.neg, GenNegNot);
	PCLIR.SetMethods(PCLIR.abs, GenAbs);
	PCLIR.SetMethods(PCLIR.bts, GenBts);
	PCLIR.SetMethods(PCLIR.btc, GenBtc);
	PCLIR.SetMethods(PCLIR.mul, GenMul);
	PCLIR.SetMethods(PCLIR.div, GenDivMod);
	PCLIR.SetMethods(PCLIR.mod, GenDivMod);
	PCLIR.SetMethods(PCLIR.sub, GenSub);
	PCLIR.SetMethods(PCLIR.add, GenAdd);
	PCLIR.SetMethods(PCLIR.and, GenAnd);
	PCLIR.SetMethods(PCLIR.or, GenOr);
	PCLIR.SetMethods(PCLIR.xor, GenXor);

	PCLIR.SetMethods(PCLIR.ash, GenShift);
	PCLIR.SetMethods(PCLIR.bsh, GenShift);
	PCLIR.SetMethods(PCLIR.rot, GenShift);
	PCLIR.SetMethods(PCLIR.move, GenMove);
	PCLIR.SetMethods(PCLIR.moveDown, GenMoveDown);
	PCLIR.SetMethods(PCLIR.inline, GenInline);
	PCLIR.SetMethods(PCLIR.case, GenCase);
	PCLIR.SetMethods(PCLIR.casel, GenCaseLine);
	PCLIR.SetMethods(PCLIR.casee, GenCaseLine);

	PCM.log.String ("AMD64 code generator installed"); PCM.log.Ln;
END Install;

END PCGAMD64.
