// Copyright (c) 2012 DotNetAnywhere
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "Compat.h"
#include "Sys.h"

#include "JIT.h"

#include "JIT_OpCodes.h"
#include "CIL_OpCodes.h"

#include "MetaData.h"
#include "Types.h"
#include "Type.h"
#include "InternalCall.h"
#include "Heap.h"
#include "PInvoke.h"

#define CorILMethod_TinyFormat 0x02
#define CorILMethod_MoreSects 0x08

#define CorILMethod_Sect_EHTable 0x01
#define CorILMethod_Sect_FatFormat 0x40
#define CorILMethod_Sect_MoreSects 0x80

#define DYNAMIC_OK 0x100
#define DYNAMIC_JUMP_TARGET 0x200
#define DYNAMIC_EX_START 0x400
#define DYNAMIC_EX_END 0x800
#define DYNAMIC_BYTE_COUNT_MASK 0xff

typedef struct tOps_ tOps;
struct tOps_ {
	U32 *p;
	U32 capacity;
	U32 ofs;
};

typedef struct tTypeStack_ tTypeStack;
struct tTypeStack_ {
	tMD_TypeDef **ppTypes;
	U32 ofs;
	U32 maxBytes; // The max size of the stack in bytes
};

#define InitOps(ops_, initialCapacity) ops_.capacity = initialCapacity; ops_.ofs = 0; ops_.p = malloc((initialCapacity) * sizeof(I32));
#define DeleteOps(ops_) free(ops_.p)

// Turn this into a MACRO at some point?
static U32 Translate(U32 op, U32 getDynamic) {
	if (op >= JIT_OPCODE_MAXNUM) {
		Crash("Illegal opcode: %d", op);
	}
	if (jitCodeInfo[op].pEnd == NULL) {
		Crash("Opcode not available: 0x%08x", op);
	}
	if (getDynamic) {
		return (U32)jitCodeInfo[op].isDynamic;
	} else {
		return (U32)jitCodeInfo[op].pStart;
	}
}

#ifdef GEN_COMBINED_OPCODES
#define PushU32(v) PushU32_(&ops, (U32)(v)); PushU32_(&isDynamic, 0)
#define PushI32(v) PushU32_(&ops, (U32)(v)); PushU32_(&isDynamic, 0)
#define PushFloat(v) convFloat.f=(float)(v); PushU32_(&ops, convFloat.u32); PushU32_(&isDynamic, 0)
#define PushDouble(v) convDouble.d=(double)(v); PushU32_(&ops, convDouble.u32.a); PushU32_(&ops, convDouble.u32.b); PushU32_(&isDynamic, 0); PushU32_(&isDynamic, 0)
#define PushPTR(ptr) PushU32_(&ops, (U32)(ptr)); PushU32_(&isDynamic, 0)
#define PushOp(op) PushU32_(&ops, Translate((U32)(op), 0)); PushU32_(&isDynamic, Translate((U32)(op), 1))
#define PushOpParam(op, param) PushOp(op); PushU32_(&ops, (U32)(param)); PushU32_(&isDynamic, 0)
#else
#define PushU32(v) PushU32_(&ops, (U32)(v))
#define PushI32(v) PushU32_(&ops, (U32)(v))
#define PushFloat(v) convFloat.f=(float)(v); PushU32_(&ops, convFloat.u32)
#define PushDouble(v) convDouble.d=(double)(v); PushU32_(&ops, convDouble.u32.a); PushU32_(&ops, convDouble.u32.b)
#define PushPTR(ptr) PushU32_(&ops, (U32)(ptr))
#define PushOp(op) PushU32_(&ops, Translate((U32)(op), 0))
#define PushOpParam(op, param) PushOp(op); PushU32_(&ops, (U32)(param))
#endif

#define PushBranch() PushU32_(&branchOffsets, ops.ofs)

#define PushStackType(type) PushStackType_(&typeStack, type);
#define PopStackType() (typeStack.ppTypes[--typeStack.ofs])
#define PopStackTypeDontCare() typeStack.ofs--
#define PopStackTypeMulti(number) typeStack.ofs -= number
#define PopStackTypeAll() typeStack.ofs = 0;

#define MayCopyTypeStack() if (u32Value > cilOfs) ppTypeStacks[u32Value] = DeepCopyTypeStack(&typeStack)

static void PushStackType_(tTypeStack *pTypeStack, tMD_TypeDef *pType) {
	U32 i, size;

	MetaData_Fill_TypeDef(pType, NULL, NULL);
	pTypeStack->ppTypes[pTypeStack->ofs++] = pType;
	// Count current stack size in bytes
	size = 0;
	for (i=0; i<pTypeStack->ofs; i++) {
		size += pTypeStack->ppTypes[i]->stackSize;
	}
	if (size > pTypeStack->maxBytes) {
		pTypeStack->maxBytes = size;
	}
	//printf("Stack ofs = %d; Max stack size: %d (0x%x)\n", pTypeStack->ofs, size, size);
}

static void PushU32_(tOps *pOps, U32 v) {
	if (pOps->ofs >= pOps->capacity) {
		pOps->capacity <<= 1;
//		printf("a.pOps->p = 0x%08x size=%d\n", pOps->p, pOps->capacity * sizeof(U32));
		pOps->p = realloc(pOps->p, pOps->capacity * sizeof(U32));
	}
	pOps->p[pOps->ofs++] = v;
}

static U32 GetUnalignedU32(U8 *pCIL, U32 *pCILOfs) {
	U32 a,b,c,d;
	a = pCIL[(*pCILOfs)++];
	b = pCIL[(*pCILOfs)++];
	c = pCIL[(*pCILOfs)++];
	d = pCIL[(*pCILOfs)++];
	return a | (b << 8) | (c << 16) | (d << 24);
}

static tTypeStack* DeepCopyTypeStack(tTypeStack *pToCopy) {
	tTypeStack *pCopy;

	pCopy = TMALLOC(tTypeStack);
	pCopy->maxBytes = pToCopy->maxBytes;
	pCopy->ofs = pToCopy->ofs;
	if (pToCopy->ofs > 0) {
		pCopy->ppTypes = malloc(pToCopy->ofs * sizeof(tMD_TypeDef*));
		memcpy(pCopy->ppTypes, pToCopy->ppTypes, pToCopy->ofs * sizeof(tMD_TypeDef*));
	} else {
		pCopy->ppTypes = NULL;
	}
	return pCopy;
}

static void RestoreTypeStack(tTypeStack *pMainStack, tTypeStack *pCopyFrom) {
	// This does not effect maxBytes, as the current value will always be equal
	// or greater than the value being copied from.
	if (pCopyFrom == NULL) {
		pMainStack->ofs = 0;
	} else {
		pMainStack->ofs = pCopyFrom->ofs;
		if (pCopyFrom->ppTypes != NULL) {
			memcpy(pMainStack->ppTypes, pCopyFrom->ppTypes, pCopyFrom->ofs * sizeof(tMD_TypeDef*));
		}
	}
}

#ifdef GEN_COMBINED_OPCODES
static U32 FindOpCode(void *pAddr) {
	U32 i;
	for (i=0; i<JIT_OPCODE_MAXNUM; i++) {
		if (jitCodeInfo[i].pStart == pAddr) {
			return i;
		}
	}
	Crash("Cannot find opcode for address: 0x%08x", (U32)pAddr);
	FAKE_RETURN;
}

static U32 combinedMemSize = 0;
static U32 GenCombined(tOps *pOps, tOps *pIsDynamic, U32 startOfs, U32 count, U32 *pCombinedSize, void **ppMem) {
	U32 memSize;
	U32 ofs;
	void *pCombined;
	U32 opCopyToOfs;
	U32 shrinkOpsBy;
	U32 goNextSize = (U32)((char*)jitCodeGoNext.pEnd - (char*)jitCodeGoNext.pStart);

	// Get length of final combined code chunk
	memSize = 0;
	for (ofs=0; ofs < count; ofs++) {
		U32 opcode = FindOpCode((void*)pOps->p[startOfs + ofs]);
		U32 size = (U32)((char*)jitCodeInfo[opcode].pEnd - (char*)jitCodeInfo[opcode].pStart);
		memSize += size;
		ofs += (pIsDynamic->p[startOfs + ofs] & DYNAMIC_BYTE_COUNT_MASK) >> 2;
	}
	// Add length of GoNext code
	memSize += goNextSize;

	pCombined = malloc(memSize);
	*ppMem = pCombined;
	combinedMemSize += memSize;
	*pCombinedSize = memSize;
	//log_f(0, "Combined JIT size: %d\n", combinedMemSize);

	// Copy the bits of code into place
	memSize = 0;
	opCopyToOfs = 1;
	for (ofs=0; ofs < count; ofs++) {
		U32 extraOpBytes;
		U32 opcode = FindOpCode((void*)pOps->p[startOfs + ofs]);
		U32 size = (U32)((char*)jitCodeInfo[opcode].pEnd - (char*)jitCodeInfo[opcode].pStart);
		memcpy((char*)pCombined + memSize, jitCodeInfo[opcode].pStart, size);
		memSize += size;
		extraOpBytes = pIsDynamic->p[startOfs + ofs] & DYNAMIC_BYTE_COUNT_MASK;
		memmove(&pOps->p[startOfs + opCopyToOfs], &pOps->p[startOfs + ofs + 1], extraOpBytes);
		opCopyToOfs += extraOpBytes >> 2;
		ofs += extraOpBytes >> 2;
	}
	shrinkOpsBy = ofs - opCopyToOfs;
	// Add GoNext code
	memcpy((char*)pCombined + memSize, jitCodeGoNext.pStart, goNextSize);
	pOps->p[startOfs] = (U32)pCombined;

	return shrinkOpsBy;
}
#endif

static U32* JITit(tMD_MethodDef *pMethodDef, U8 *pCIL, U32 codeSize, tParameter *pLocals, tJITted *pJITted, U32 genCombinedOpcodes) {
	U32 maxStack = pJITted->maxStack;
	U32 i;
	U32 cilOfs;
	tOps ops; // The JITted op-codes
	tOps branchOffsets; // Filled with all the branch instructions that need offsets fixing
	U32 *pJITOffsets;	// To store the JITted code offset of each CIL byte.
						// Only CIL bytes that are the first byte of an instruction will have meaningful data
	tTypeStack **ppTypeStacks; // To store the evaluation stack state for forward jumps
	U32 *pFinalOps;
	tMD_TypeDef *pStackType;
	tTypeStack typeStack;

#ifdef GEN_COMBINED_OPCODES
	tOps isDynamic;
#endif

	I32 i32Value;
	U32 u32Value, u32Value2, ofs;
	uConvFloat convFloat;
	uConvDouble convDouble;
	tMD_TypeDef *pTypeA, *pTypeB;
	PTR pMem;
	tMetaData *pMetaData;

	pMetaData = pMethodDef->pMetaData;
	pJITOffsets = malloc(codeSize * sizeof(U32));
	// + 1 to handle cases where the stack is being restored at the last instruction in a method
	ppTypeStacks = malloc((codeSize + 1) * sizeof(tTypeStack*));
	memset(ppTypeStacks, 0, (codeSize + 1) * sizeof(tTypeStack*));
	typeStack.maxBytes = 0;
	typeStack.ofs = 0;
	typeStack.ppTypes = malloc(maxStack * sizeof(tMD_TypeDef*));

	// Set up all exception 'catch' blocks with the correct stack information,
	// So they'll have just the exception type on the stack when entered
	for (i=0; i<pJITted->numExceptionHandlers; i++) {
		tExceptionHeader *pEx;

		pEx = &pJITted->pExceptionHeaders[i]; 
		if (pEx->flags == COR_ILEXCEPTION_CLAUSE_EXCEPTION) {
			tTypeStack *pTypeStack;

			ppTypeStacks[pEx->handlerStart] = pTypeStack = TMALLOC(tTypeStack);
			pTypeStack->maxBytes = 4;
			pTypeStack->ofs = 1;
			pTypeStack->ppTypes = TMALLOC(tMD_TypeDef*);
			pTypeStack->ppTypes[0] = pEx->u.pCatchTypeDef;
		}
	}

	InitOps(ops, 32);
	InitOps(branchOffsets, 16);
#ifdef GEN_COMBINED_OPCODES
	InitOps(isDynamic, 32);
#endif

	cilOfs = 0;

	do {
		U8 op;

		// Set the JIT offset for this CIL opcode
		pJITOffsets[cilOfs] = ops.ofs;

		op = pCIL[cilOfs++];
		//printf("Opcode: 0x%02x\n", op);

		switch (op) {
			case CIL_NOP:
				PushOp(JIT_NOP);
				break;

			case CIL_LDNULL:
				PushOp(JIT_LOAD_NULL);
				PushStackType(types[TYPE_SYSTEM_OBJECT]);
				break;

			case CIL_DUP:
				pStackType = PopStackType();
				PushStackType(pStackType);
				PushStackType(pStackType);
				switch (pStackType->stackSize) {
				case 4:
					PushOp(JIT_DUP_4);
					break;
				case 8:
					PushOp(JIT_DUP_8);
					break;
				default:
					PushOpParam(JIT_DUP_GENERAL, pStackType->stackSize);
					break;
				}
				break;

			case CIL_POP:
				pStackType = PopStackType();
				if (pStackType->stackSize == 4) {
					PushOp(JIT_POP_4);
				} else {
					PushOpParam(JIT_POP, pStackType->stackSize);
				}
				break;

			case CIL_LDC_I4_M1:
			case CIL_LDC_I4_0:
			case CIL_LDC_I4_1:
			case CIL_LDC_I4_2:
			case CIL_LDC_I4_3:
			case CIL_LDC_I4_4:
			case CIL_LDC_I4_5:
			case CIL_LDC_I4_6:
			case CIL_LDC_I4_7:
			case CIL_LDC_I4_8:
				i32Value = (I8)op - (I8)CIL_LDC_I4_0;
				goto cilLdcI4;

			case CIL_LDC_I4_S:
				i32Value = (I8)pCIL[cilOfs++];
				goto cilLdcI4;

			case CIL_LDC_I4:
				i32Value = (I32)GetUnalignedU32(pCIL, &cilOfs);
cilLdcI4:
				if (i32Value >= -1 && i32Value <= 2) {
					PushOp(JIT_LOAD_I4_0 + i32Value);
				} else {
					PushOp(JIT_LOAD_I32);
					PushI32(i32Value);
				}
				PushStackType(types[TYPE_SYSTEM_INT32]);
				break;

			case CIL_LDC_I8:
				PushOp(JIT_LOAD_I64);
				u32Value = GetUnalignedU32(pCIL, &cilOfs);
				PushU32(u32Value);
				u32Value = GetUnalignedU32(pCIL, &cilOfs);
				PushU32(u32Value);
				PushStackType(types[TYPE_SYSTEM_INT64]);
				break;

			case CIL_LDC_R4:
				convFloat.u32 = GetUnalignedU32(pCIL, &cilOfs);
				PushStackType(types[TYPE_SYSTEM_SINGLE]);
				PushOp(JIT_LOAD_F32);
				PushFloat(convFloat.f);
				break;

			case CIL_LDC_R8:
				convDouble.u32.a = GetUnalignedU32(pCIL, &cilOfs);
				convDouble.u32.b = GetUnalignedU32(pCIL, &cilOfs);
				PushStackType(types[TYPE_SYSTEM_DOUBLE]);
				PushOp(JIT_LOAD_F64);
				PushDouble(convDouble.d);
				break;

			case CIL_LDARG_0:
			case CIL_LDARG_1:
			case CIL_LDARG_2:
			case CIL_LDARG_3:
				u32Value = op - CIL_LDARG_0;
				goto cilLdArg;

			case CIL_LDARG_S:
				u32Value = pCIL[cilOfs++];
cilLdArg:
				pStackType = pMethodDef->pParams[u32Value].pTypeDef;
				ofs = pMethodDef->pParams[u32Value].offset;
				if (pStackType->stackSize == 4 && ofs < 32) {
					PushOp(JIT_LOADPARAMLOCAL_0 + (ofs >> 2));
				} else {
					PushOpParam(JIT_LOADPARAMLOCAL_TYPEID + pStackType->stackType, ofs);
					// if it's a valuetype then push the TypeDef of it afterwards
					if (pStackType->stackType == EVALSTACK_VALUETYPE) {
						PushPTR(pStackType);
					}
				}
				PushStackType(pStackType);
				break;

			case CIL_LDARGA_S:
				// Get the argument number to load the address of
				u32Value = pCIL[cilOfs++];
				PushOpParam(JIT_LOAD_PARAMLOCAL_ADDR, pMethodDef->pParams[u32Value].offset);
				PushStackType(types[TYPE_SYSTEM_INTPTR]);
				break;

			case CIL_STARG_S:
				// Get the argument number to store the arg of
				u32Value = pCIL[cilOfs++];
				pStackType = PopStackType();
				ofs = pMethodDef->pParams[u32Value].offset;
				if (pStackType->stackSize == 4 && ofs < 32) {
					PushOp(JIT_STOREPARAMLOCAL_0 + (ofs >> 2));
				} else {
					PushOpParam(JIT_STOREPARAMLOCAL_TYPEID + pStackType->stackType, ofs);
					// if it's a valuetype then push the TypeDef of it afterwards
					if (pStackType->stackType == EVALSTACK_VALUETYPE) {
						PushPTR(pStackType);
					}
				}
				break;

			case CIL_LDLOC_0:
			case CIL_LDLOC_1:
			case CIL_LDLOC_2:
			case CIL_LDLOC_3:
				// Push opcode and offset into locals memory
				u32Value = op - CIL_LDLOC_0;
				goto cilLdLoc;

			case CIL_LDLOC_S:
				// Push opcode and offset into locals memory
				u32Value = pCIL[cilOfs++];
cilLdLoc:
				pStackType = pLocals[u32Value].pTypeDef;
				ofs = pMethodDef->parameterStackSize + pLocals[u32Value].offset;
				if (pStackType->stackSize == 4 && ofs < 32) {
					PushOp(JIT_LOADPARAMLOCAL_0 + (ofs >> 2));
				} else {
					PushOpParam(JIT_LOADPARAMLOCAL_TYPEID + pStackType->stackType, ofs);
					// if it's a valuetype then push the TypeDef of it afterwards
					if (pStackType->stackType == EVALSTACK_VALUETYPE) {
						PushPTR(pStackType);
					}
				}
				PushStackType(pStackType);
				break;

			case CIL_STLOC_0:
			case CIL_STLOC_1:
			case CIL_STLOC_2:
			case CIL_STLOC_3:
				u32Value = op - CIL_STLOC_0;
				goto cilStLoc;

			case CIL_STLOC_S:
				u32Value = pCIL[cilOfs++];
cilStLoc:
				pStackType = PopStackType();
				ofs = pMethodDef->parameterStackSize + pLocals[u32Value].offset;
				if (pStackType->stackSize == 4 && ofs < 32) {
					PushOp(JIT_STOREPARAMLOCAL_0 + (ofs >> 2));
				} else {
					PushOpParam(JIT_STOREPARAMLOCAL_TYPEID + pStackType->stackType, ofs);
					// if it's a valuetype then push the TypeDef of it afterwards
					if (pStackType->stackType == EVALSTACK_VALUETYPE) {
						PushPTR(pStackType);
					}
				}
				break;

			case CIL_LDLOCA_S:
				// Get the local number to load the address of
				u32Value = pCIL[cilOfs++];
				PushOpParam(JIT_LOAD_PARAMLOCAL_ADDR, pMethodDef->parameterStackSize + pLocals[u32Value].offset);
				PushStackType(types[TYPE_SYSTEM_INTPTR]);
				break;

			case CIL_LDIND_I1:
				u32Value = TYPE_SYSTEM_SBYTE;
				goto cilLdInd;
			case CIL_LDIND_U1:
				u32Value = TYPE_SYSTEM_BYTE;
				goto cilLdInd;
			case CIL_LDIND_I2:
				u32Value = TYPE_SYSTEM_INT16;
				goto cilLdInd;
			case CIL_LDIND_U2:
				u32Value = TYPE_SYSTEM_UINT16;
				goto cilLdInd;
			case CIL_LDIND_I4:
				u32Value = TYPE_SYSTEM_INT32;
				goto cilLdInd;
			case CIL_LDIND_U4:
				u32Value = TYPE_SYSTEM_UINT32;
				goto cilLdInd;
			case CIL_LDIND_I8:
				u32Value = TYPE_SYSTEM_INT64;
				goto cilLdInd;
			case CIL_LDIND_R4:
				u32Value = TYPE_SYSTEM_SINGLE;
				goto cilLdInd;
			case CIL_LDIND_R8:
				u32Value = TYPE_SYSTEM_DOUBLE;
				goto cilLdInd;
			case CIL_LDIND_REF:
				u32Value = TYPE_SYSTEM_OBJECT;
				goto cilLdInd;
			case CIL_LDIND_I:
				u32Value = TYPE_SYSTEM_INTPTR;
cilLdInd:
				PopStackTypeDontCare(); // don't care what it is
				PushOp(JIT_LOADINDIRECT_I8 + (op - CIL_LDIND_I1));
				PushStackType(types[u32Value]);
				break;

			case CIL_STIND_REF:
			case CIL_STIND_I1:
			case CIL_STIND_I2:
			case CIL_STIND_I4:
				PopStackTypeMulti(2); // Don't care what they are
				PushOp(JIT_STOREINDIRECT_REF + (op - CIL_STIND_REF));
				break;

			case CIL_RET:
				PushOp(JIT_RETURN);
				RestoreTypeStack(&typeStack, ppTypeStacks[cilOfs]);
				break;

			case CIL_CALL:
			case CIL_CALLVIRT:
				{
					tMD_MethodDef *pCallMethod;
					tMD_TypeDef *pBoxCallType;
					U32 derefRefType;

					u32Value2 = 0;

cilCallVirtConstrained:
					pBoxCallType = NULL;
					derefRefType = 0;

					u32Value = GetUnalignedU32(pCIL, &cilOfs);
					pCallMethod = MetaData_GetMethodDefFromDefRefOrSpec(pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
					if (pCallMethod->isFilled == 0) {
						tMD_TypeDef *pTypeDef;
						
						pTypeDef = MetaData_GetTypeDefFromMethodDef(pCallMethod);
						MetaData_Fill_TypeDef(pTypeDef, NULL, NULL);
					}

					if (u32Value2 != 0) {
						// There is a 'constrained' prefix
						tMD_TypeDef *pConstrainedType;

						pConstrainedType = MetaData_GetTypeDefFromDefRefOrSpec(pMetaData, u32Value2, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
						if (TYPE_ISINTERFACE(pCallMethod->pParentType)) {
							u32Value2 = 0xffffffff;
							// Find the interface that we're dealing with
							for (i=0; i<pConstrainedType->numInterfaces; i++) {
								if (pConstrainedType->pInterfaceMaps[i].pInterface == pCallMethod->pParentType) {
									u32Value2 = pConstrainedType->pInterfaceMaps[i].pVTableLookup[pCallMethod->vTableOfs];
									break;
								}
							}
							Assert(u32Value2 != 0xffffffff);
							if (pConstrainedType->pVTable[u32Value2]->pParentType == pConstrainedType) {
								// This method is implemented on this class, so make it a normal CALL op
								op = CIL_CALL;
								pCallMethod = pConstrainedType->pVTable[u32Value2];
							}
						} else {
							if (pConstrainedType->isValueType) {
								tMD_MethodDef *pImplMethod;
								// If pConstraintedType directly implements the call then don't do anything
								// otherwise the 'this' pointer must be boxed (BoxedCall)
								pImplMethod = pConstrainedType->pVTable[pCallMethod->vTableOfs];
								if (pImplMethod->pParentType == pConstrainedType) {
									op = CIL_CALL;
									pCallMethod = pImplMethod;
								} else {
									pBoxCallType = pConstrainedType;
								}
							} else {
								// Reference-type, so dereference the PTR to 'this' and use that for the 'this' for the call.
								derefRefType = 1;
							}
						}
					}

					// Pop stack type for each argument. Don't actually care what these are,
					// except the last one which will be the 'this' object type of a non-static method
					//printf("Call %s() - popping %d stack args\n", pCallMethod->name, pCallMethod->numberOfParameters);
					for (i=0; i<pCallMethod->numberOfParameters; i++) {
						pStackType = PopStackType();
					}
					// the stack type of the 'this' object will now be in stackType (if there is one)
					if (METHOD_ISSTATIC(pCallMethod)) {
						pStackType = types[TYPE_SYSTEM_OBJECT];
					}
					MetaData_Fill_TypeDef(pStackType, NULL, NULL);
					if (TYPE_ISINTERFACE(pCallMethod->pParentType) && op == CIL_CALLVIRT) {
						PushOp(JIT_CALL_INTERFACE);
					} else if (pCallMethod->pParentType->pParent == types[TYPE_SYSTEM_MULTICASTDELEGATE]) {
						PushOp(JIT_INVOKE_DELEGATE);
					} else {
						switch (pStackType->stackType)
						{
						case EVALSTACK_INTNATIVE: // Not really right, but it'll work on 32-bit
						case EVALSTACK_O:
							if (derefRefType) {
								PushOp(JIT_DEREF_CALLVIRT);
							} else {
								if (pBoxCallType != NULL) {
									PushOp(JIT_BOX_CALLVIRT);
									PushPTR(pBoxCallType);
								} else {
									PushOp((op == CIL_CALL)?JIT_CALL_O:JIT_CALLVIRT_O);
								}
							}
							break;
						case EVALSTACK_PTR:
						case EVALSTACK_VALUETYPE:
							if (derefRefType) {
								PushOp(JIT_DEREF_CALLVIRT);
							} else if (pBoxCallType != NULL) {
								PushOp(JIT_BOX_CALLVIRT);
								PushPTR(pBoxCallType);
							} else {
								PushOp(JIT_CALL_PTR);
							}
							break;
						default:
							Crash("JITit(): Cannot CALL or CALLVIRT with stack type: %d", pStackType->stackType);
						}
					}
					PushPTR(pCallMethod);

					if (pCallMethod->pReturnType != NULL) {
						PushStackType(pCallMethod->pReturnType);
					}
				}
				break;

			case CIL_BR_S: // unconditional branch
				u32Value = (I8)pCIL[cilOfs++];
				goto cilBr;

			case CIL_BR:
				u32Value = GetUnalignedU32(pCIL, &cilOfs);
cilBr:
				// Put a temporary CIL offset value into the JITted code. This will be updated later
				u32Value = cilOfs + (I32)u32Value;
				MayCopyTypeStack();
				PushOp(JIT_BRANCH);
				PushBranch();
				PushU32(u32Value);
				// Restore the stack state
				RestoreTypeStack(&typeStack, ppTypeStacks[cilOfs]);
				break;

			case CIL_SWITCH:
				// This is the int containing the switch value. Don't care what it is.
				PopStackTypeDontCare();
				// The number of switch jump targets
				i32Value = (I32)GetUnalignedU32(pCIL, &cilOfs);
				// Set up the offset from which the jump offsets are calculated
				u32Value2 = cilOfs + (i32Value << 2);
				PushOpParam(JIT_SWITCH, i32Value);
				for (i=0; i<(U32)i32Value; i++) {
					// A jump target
					u32Value = u32Value2 + (I32)GetUnalignedU32(pCIL, &cilOfs);
					PushBranch();
					MayCopyTypeStack();
					// Push the jump target.
					// It is needed to allow the branch offset to be correctly updated later.
					PushU32(u32Value);
				}
				break;

			case CIL_BRFALSE_S:
			case CIL_BRTRUE_S:
				u32Value = (I8)pCIL[cilOfs++];
				u32Value2 = JIT_BRANCH_FALSE + (op - CIL_BRFALSE_S);
				goto cilBrFalseTrue;

			case CIL_BRFALSE:
			case CIL_BRTRUE:
				u32Value = GetUnalignedU32(pCIL, &cilOfs);
				u32Value2 = JIT_BRANCH_FALSE + (op - CIL_BRFALSE);
cilBrFalseTrue:
				PopStackTypeDontCare(); // Don't care what it is
				// Put a temporary CIL offset value into the JITted code. This will be updated later
				u32Value = cilOfs + (I32)u32Value;
				MayCopyTypeStack();
				PushOp(u32Value2);
				PushBranch();
				PushU32(u32Value);
				break;

			case CIL_BEQ_S:
			case CIL_BGE_S:
			case CIL_BGT_S:
			case CIL_BLE_S:
			case CIL_BLT_S:
			case CIL_BNE_UN_S:
			case CIL_BGE_UN_S:
			case CIL_BGT_UN_S:
			case CIL_BLE_UN_S:
			case CIL_BLT_UN_S:
				u32Value = (I8)pCIL[cilOfs++];
				u32Value2 = CIL_BEQ_S;
				goto cilBrCond;

			case CIL_BEQ:
			case CIL_BGE:
			case CIL_BGT:
			case CIL_BLE:
			case CIL_BLT:
			case CIL_BNE_UN:
			case CIL_BGE_UN:
			case CIL_BGT_UN:
			case CIL_BLE_UN:
			case CIL_BLT_UN:
				u32Value = GetUnalignedU32(pCIL, &cilOfs);
				u32Value2 = CIL_BEQ;
cilBrCond:
				pTypeB = PopStackType();
				pTypeA = PopStackType();
				u32Value = cilOfs + (I32)u32Value;
				MayCopyTypeStack();
				if ((pTypeA->stackType == EVALSTACK_INT32 && pTypeB->stackType == EVALSTACK_INT32) ||
					(pTypeA->stackType == EVALSTACK_O && pTypeB->stackType == EVALSTACK_O)) {
					PushOp(JIT_BEQ_I32I32 + (op - u32Value2));
				} else if (pTypeA->stackType == EVALSTACK_INT64 && pTypeB->stackType == EVALSTACK_INT64) {
					PushOp(JIT_BEQ_I64I64 + (op - u32Value2));
				} else if (pTypeA->stackType == EVALSTACK_F32 && pTypeB->stackType == EVALSTACK_F32) {
					PushOp(JIT_BEQ_F32F32 + (op - u32Value2));
				} else if (pTypeA->stackType == EVALSTACK_F64 && pTypeB->stackType == EVALSTACK_F64) {
					PushOp(JIT_BEQ_F64F64 + (op - u32Value2));
				} else {
					Crash("JITit(): Cannot perform conditional branch on stack types: %d and %d", pTypeA->stackType, pTypeB->stackType);
				}
				PushBranch();
				PushU32(u32Value);
				break;

			case CIL_ADD_OVF:
			case CIL_ADD_OVF_UN:
			case CIL_MUL_OVF:
			case CIL_MUL_OVF_UN:
			case CIL_SUB_OVF:
			case CIL_SUB_OVF_UN:
				u32Value = (CIL_ADD_OVF - CIL_ADD) + (JIT_ADD_I32I32 - JIT_ADD_OVF_I32I32);
				goto cilBinaryArithOp;
			case CIL_ADD:
			case CIL_SUB:
			case CIL_MUL:
			case CIL_DIV:
			case CIL_DIV_UN:
			case CIL_REM:
			case CIL_REM_UN:
			case CIL_AND:
			case CIL_OR:
			case CIL_XOR:
				u32Value = 0;
cilBinaryArithOp:
				pTypeB = PopStackType();
				pTypeA = PopStackType();
				if (pTypeA->stackType == EVALSTACK_INT32 && pTypeB->stackType == EVALSTACK_INT32) {
					PushOp(JIT_ADD_I32I32 + (op - CIL_ADD) - u32Value);
					PushStackType(types[TYPE_SYSTEM_INT32]);
				} else if (pTypeA->stackType == EVALSTACK_INT64 && pTypeB->stackType == EVALSTACK_INT64) {
					PushOp(JIT_ADD_I64I64 + (op - CIL_ADD) - u32Value);
					PushStackType(types[TYPE_SYSTEM_INT64]);
				} else if (pTypeA->stackType == EVALSTACK_F32 && pTypeB->stackType == EVALSTACK_F32) {
					PushOp(JIT_ADD_F32F32 + (op - CIL_ADD) - u32Value);
					PushStackType(pTypeA);
				} else if (pTypeA->stackType == EVALSTACK_F64 && pTypeB->stackType == EVALSTACK_F64) {
					PushOp(JIT_ADD_F64F64 + (op - CIL_ADD) - u32Value);
					PushStackType(pTypeA);
				} else {
					Crash("JITit(): Cannot perform binary numeric operand on stack types: %d and %d", pTypeA->stackType, pTypeB->stackType);
				}
				break;

			case CIL_NEG:
			case CIL_NOT:
				pTypeA = PopStackType();
				if (pTypeA->stackType == EVALSTACK_INT32) {
					PushOp(JIT_NEG_I32 + (op - CIL_NEG));
					PushStackType(types[TYPE_SYSTEM_INT32]);
				} else if (pTypeA->stackType == EVALSTACK_INT64) {
					PushOp(JIT_NEG_I64 + (op - CIL_NEG));
					PushStackType(types[TYPE_SYSTEM_INT64]);
				} else {
					Crash("JITit(): Cannot perform unary operand on stack types: %d", pTypeA->stackType);
				}
				break;

			case CIL_SHL:
			case CIL_SHR:
			case CIL_SHR_UN:
				PopStackTypeDontCare(); // Don't care about the shift amount
				pTypeA = PopStackType(); // Do care about the value to shift
				if (pTypeA->stackType == EVALSTACK_INT32) {
					PushOp(JIT_SHL_I32 - CIL_SHL + op);
					PushStackType(types[TYPE_SYSTEM_INT32]);
				} else if (pTypeA->stackType == EVALSTACK_INT64) {
					PushOp(JIT_SHL_I64 - CIL_SHL + op);
					PushStackType(types[TYPE_SYSTEM_INT64]);
				} else {
					Crash("JITit(): Cannot perform shift operation on type: %s", pTypeA->name);
				}
				break;

				// Conversion operations
				{
					U32 toType;
					U32 toBitCount;
					U32 convOpOffset;
			case CIL_CONV_I1:
			case CIL_CONV_OVF_I1: // Fix this later - will never overflow
			case CIL_CONV_OVF_I1_UN: // Fix this later - will never overflow
				toBitCount = 8;
				toType = TYPE_SYSTEM_SBYTE;
				goto cilConvInt32;
			case CIL_CONV_I2:
			case CIL_CONV_OVF_I2: // Fix this later - will never overflow
			case CIL_CONV_OVF_I2_UN: // Fix this later - will never overflow
				toBitCount = 16;
				toType = TYPE_SYSTEM_INT16;
				goto cilConvInt32;
			case CIL_CONV_I4:
			case CIL_CONV_OVF_I4: // Fix this later - will never overflow
			case CIL_CONV_OVF_I4_UN: // Fix this later - will never overflow
			case CIL_CONV_I: // Only on 32-bit
			case CIL_CONV_OVF_I_UN: // Only on 32-bit; Fix this later - will never overflow
				toBitCount = 32;
				toType = TYPE_SYSTEM_INT32;
cilConvInt32:
				convOpOffset = JIT_CONV_OFFSET_I32;
				goto cilConv;
			case CIL_CONV_U1:
			case CIL_CONV_OVF_U1: // Fix this later - will never overflow
			case CIL_CONV_OVF_U1_UN: // Fix this later - will never overflow
				toBitCount = 8;
				toType = TYPE_SYSTEM_BYTE;
				goto cilConvUInt32;
			case CIL_CONV_U2:
			case CIL_CONV_OVF_U2: // Fix this later - will never overflow
			case CIL_CONV_OVF_U2_UN: // Fix this later - will never overflow
				toBitCount = 16;
				toType = TYPE_SYSTEM_UINT16;
				goto cilConvUInt32;
			case CIL_CONV_U4:
			case CIL_CONV_OVF_U4: // Fix this later - will never overflow
			case CIL_CONV_OVF_U4_UN: // Fix this later - will never overflow
			case CIL_CONV_U: // Only on 32-bit
			case CIL_CONV_OVF_U_UN: // Only on 32-bit; Fix this later - will never overflow
				toBitCount = 32;
				toType = TYPE_SYSTEM_UINT32;
cilConvUInt32:
				convOpOffset = JIT_CONV_OFFSET_U32;
				goto cilConv;
			case CIL_CONV_I8:
			case CIL_CONV_OVF_I8: // Fix this later - will never overflow
			case CIL_CONV_OVF_I8_UN: // Fix this later - will never overflow
				toType = TYPE_SYSTEM_INT64;
				convOpOffset = JIT_CONV_OFFSET_I64;
				goto cilConv;
			case CIL_CONV_U8:
			case CIL_CONV_OVF_U8: // Fix this later - will never overflow
			case CIL_CONV_OVF_U8_UN: // Fix this later - will never overflow
				toType = TYPE_SYSTEM_UINT64;
				convOpOffset = JIT_CONV_OFFSET_U64;
				goto cilConv;
			case CIL_CONV_R4:
				toType = TYPE_SYSTEM_SINGLE;
				convOpOffset = JIT_CONV_OFFSET_R32;
				goto cilConv;
			case CIL_CONV_R8:
			case CIL_CONV_R_UN:
				toType = TYPE_SYSTEM_DOUBLE;
				convOpOffset = JIT_CONV_OFFSET_R64;
				goto cilConv;
cilConv:
				pStackType = PopStackType();
				{
					U32 opCodeBase;
					U32 useParam = 0, param;
					// This is the types that the conversion is from.
					switch (pStackType->stackType) {
					case EVALSTACK_INT64:
						opCodeBase = (pStackType == types[TYPE_SYSTEM_INT64])?JIT_CONV_FROM_I64:JIT_CONV_FROM_U64;
						break;
					case EVALSTACK_INT32:
					case EVALSTACK_PTR: // Only on 32-bit
						opCodeBase =
							(pStackType == types[TYPE_SYSTEM_BYTE] ||
							pStackType == types[TYPE_SYSTEM_UINT16] ||
							pStackType == types[TYPE_SYSTEM_UINT32] ||
							pStackType == types[TYPE_SYSTEM_UINTPTR])?JIT_CONV_FROM_U32:JIT_CONV_FROM_I32;
						break;
					case EVALSTACK_F64:
						opCodeBase = JIT_CONV_FROM_R64;
						break;
					case EVALSTACK_F32:
						opCodeBase = JIT_CONV_FROM_R32;
						break;
					default:
						Crash("JITit() Conv cannot handle stack type %d", pStackType->stackType);
					}
					// This is the types that the conversion is to.
					switch (convOpOffset) {
					case JIT_CONV_OFFSET_I32:
						useParam = 1;
						param = 32 - toBitCount;
						break;
					case JIT_CONV_OFFSET_U32:
						useParam = 1;
						// Next line is really (1 << toBitCount) - 1
						// But it's done like this to work when toBitCount == 32
						param = (((1 << (toBitCount - 1)) - 1) << 1) + 1;
						break;
					case JIT_CONV_OFFSET_I64:
					case JIT_CONV_OFFSET_U64:
					case JIT_CONV_OFFSET_R32:
					case JIT_CONV_OFFSET_R64:
						break;
					default:
						Crash("JITit() Conv cannot handle convOpOffset %d", convOpOffset);
					}
					PushOp(opCodeBase + convOpOffset);
					if (useParam) {
						PushU32(param);
					}
				}
				PushStackType(types[toType]);
				break;
				}

#ifdef OLD_CONV
			case CIL_CONV_OVF_I1:
			case CIL_CONV_OVF_I2:
			case CIL_CONV_OVF_I4:
				u32Value = TYPE_SYSTEM_INT32;
				goto convOvf;
			case CIL_CONV_OVF_I8:
				u32Value = TYPE_SYSTEM_INT64;
				goto convOvf;
			case CIL_CONV_OVF_U1:
			case CIL_CONV_OVF_U2:
			case CIL_CONV_OVF_U4:
				u32Value = TYPE_SYSTEM_UINT32;
				goto convOvf;
			case CIL_CONV_OVF_U8:
				u32Value = TYPE_SYSTEM_UINT64;
convOvf:
				pStackType = PopStackType();
				PushOpParam(JIT_CONV_OVF_I1 + (op - CIL_CONV_OVF_I1), pStackType->stackType);
				PushStackType(types[u32Value]);
				break;

			case CIL_CONV_I1:
			case CIL_CONV_I2:
			case CIL_CONV_I4:
				u32Value = TYPE_SYSTEM_INT32;
				goto conv1;
			case CIL_CONV_I8:
				u32Value = TYPE_SYSTEM_INT64;
				goto conv1;
			case CIL_CONV_R4:
				u32Value = TYPE_SYSTEM_SINGLE;
				goto conv1;
			case CIL_CONV_R8:
				u32Value = TYPE_SYSTEM_DOUBLE;
				goto conv1;
			case CIL_CONV_U4:
				u32Value = TYPE_SYSTEM_UINT32;
				goto conv1;
			case CIL_CONV_U8:
				u32Value = TYPE_SYSTEM_UINT64;
conv1:
				pStackType = PopStackType();
				PushOpParam(JIT_CONV_I1 + (op - CIL_CONV_I1), pStackType->stackType);
				PushStackType(types[u32Value]);
				break;

			case CIL_CONV_U2:
			case CIL_CONV_U1:
				u32Value = TYPE_SYSTEM_UINT32;
				goto conv2;
			case CIL_CONV_I:
				u32Value = TYPE_SYSTEM_INT32; // Only on 32-bit
conv2:
				pStackType = PopStackType();
				PushOpParam(JIT_CONV_U2 + (op - CIL_CONV_U2), pStackType->stackType);
				PushStackType(types[u32Value]);
				break;

			case CIL_CONV_U:
				pStackType = PopStackType();
				PushOpParam(JIT_CONV_U_NATIVE, pStackType->stackType);
				PushStackType(types[TYPE_SYSTEM_UINTPTR]);
				break;
#endif

			case CIL_LDOBJ:
				{
					tMD_TypeDef *pTypeDef;

					PopStackTypeDontCare(); // Don't care what this is
					u32Value = GetUnalignedU32(pCIL, &cilOfs);
					pTypeDef = MetaData_GetTypeDefFromDefRefOrSpec(pMethodDef->pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
					PushOp(JIT_LOADOBJECT);
					PushPTR(pTypeDef);
					PushStackType(pTypeDef);
				}
				break;

			case CIL_STOBJ:
				{
					tMD_TypeDef *pTypeDef;

					u32Value = GetUnalignedU32(pCIL, &cilOfs);
					pTypeDef = MetaData_GetTypeDefFromDefRefOrSpec(pMethodDef->pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
					PopStackTypeMulti(2);
					if (pTypeDef->isValueType && pTypeDef->arrayElementSize != 4) {
						// If it's a value-type then do this
						PushOpParam(JIT_STORE_OBJECT_VALUETYPE, pTypeDef->arrayElementSize);
					} else {
						// If it's a ref type, or a value-type with size 4, then can do this instead
						// (it executes faster)
						PushOp(JIT_STOREINDIRECT_REF);
					}
					break;
				}

			case CIL_LDSTR:
				u32Value = GetUnalignedU32(pCIL, &cilOfs) & 0x00ffffff;
				PushOpParam(JIT_LOAD_STRING, u32Value);
				PushStackType(types[TYPE_SYSTEM_STRING]);
				break;

			case CIL_NEWOBJ:
				{
					tMD_MethodDef *pConstructorDef;

					u32Value = GetUnalignedU32(pCIL, &cilOfs);
					pConstructorDef = MetaData_GetMethodDefFromDefRefOrSpec(pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
					if (pConstructorDef->isFilled == 0) {
						tMD_TypeDef *pTypeDef;

						pTypeDef = MetaData_GetTypeDefFromMethodDef(pConstructorDef);
						MetaData_Fill_TypeDef(pTypeDef, NULL, NULL);
					}
					if (pConstructorDef->pParentType->isValueType) {
						PushOp(JIT_NEWOBJECT_VALUETYPE);
					} else {
						PushOp(JIT_NEWOBJECT);
					}
					// -1 because the param count includes the 'this' parameter that is sent to the constructor
					PopStackTypeMulti(pConstructorDef->numberOfParameters - 1);
					PushPTR(pConstructorDef);
					PushStackType(pConstructorDef->pParentType);
				}
				break;

			case CIL_CASTCLASS:
				{
					tMD_TypeDef *pCastToType;

					PushOp(JIT_CAST_CLASS);
					u32Value = GetUnalignedU32(pCIL, &cilOfs);
					pCastToType = MetaData_GetTypeDefFromDefRefOrSpec(pMethodDef->pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
					PushPTR(pCastToType);
				}
				break;

			case CIL_ISINST:
				{
					tMD_TypeDef *pIsInstanceOfType;

					PushOp(JIT_IS_INSTANCE);
					u32Value = GetUnalignedU32(pCIL, &cilOfs);
					pIsInstanceOfType = MetaData_GetTypeDefFromDefRefOrSpec(pMethodDef->pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
					PushPTR(pIsInstanceOfType);
				}
				break;

			case CIL_NEWARR:
				{
					tMD_TypeDef *pTypeDef;

					u32Value = GetUnalignedU32(pCIL, &cilOfs);
					pTypeDef = MetaData_GetTypeDefFromDefRefOrSpec(pMethodDef->pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
					PopStackTypeDontCare(); // Don't care what it is
					PushOp(JIT_NEW_VECTOR);
					MetaData_Fill_TypeDef(pTypeDef, NULL, NULL);
					pTypeDef = Type_GetArrayTypeDef(pTypeDef, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
					PushPTR(pTypeDef);
					PushStackType(pTypeDef);
				}
				break;

			case CIL_LDLEN:
				PopStackTypeDontCare(); // Don't care what it is
				PushOp(JIT_LOAD_VECTOR_LEN);
				PushStackType(types[TYPE_SYSTEM_INT32]);
				break;

			case CIL_LDELEM_I1:
			case CIL_LDELEM_U1:
			case CIL_LDELEM_I2:
			case CIL_LDELEM_U2:
			case CIL_LDELEM_I4:
			case CIL_LDELEM_U4:
				PopStackTypeMulti(2); // Don't care what any of these are
				PushOp(JIT_LOAD_ELEMENT_I8 + (op - CIL_LDELEM_I1));
				PushStackType(types[TYPE_SYSTEM_INT32]);
				break;

			case CIL_LDELEM_I8:
				PopStackTypeMulti(2); // Don't care what any of these are
				PushOp(JIT_LOAD_ELEMENT_I64);
				PushStackType(types[TYPE_SYSTEM_INT64]);
				break;

			case CIL_LDELEM_R4:
				PopStackTypeMulti(2); // Don't care what any of these are
				PushOp(JIT_LOAD_ELEMENT_R32);
				PushStackType(types[TYPE_SYSTEM_SINGLE]);
				break;

			case CIL_LDELEM_R8:
				PopStackTypeMulti(2); // Don't care what any of these are
				PushOp(JIT_LOAD_ELEMENT_R64);
				PushStackType(types[TYPE_SYSTEM_DOUBLE]);
				break;

			case CIL_LDELEM_REF:
				PopStackTypeMulti(2); // Don't care what any of these are
				PushOp(JIT_LOAD_ELEMENT_U32);
				PushStackType(types[TYPE_SYSTEM_OBJECT]);
				break;

			case CIL_LDELEM_ANY:
				u32Value = GetUnalignedU32(pCIL, &cilOfs);
				pStackType = (tMD_TypeDef*)MetaData_GetTypeDefFromDefRefOrSpec(pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
				PopStackTypeMulti(2); // Don't care what these are
				PushOpParam(JIT_LOAD_ELEMENT, pStackType->stackSize);
				PushStackType(pStackType);
				break;

			case CIL_LDELEMA:
				PopStackTypeMulti(2); // Don't care what any of these are
				GetUnalignedU32(pCIL, &cilOfs); // Don't care what this is
				PushOp(JIT_LOAD_ELEMENT_ADDR);
				PushStackType(types[TYPE_SYSTEM_INTPTR]);
				break;

			case CIL_STELEM_I1:
			case CIL_STELEM_I2:
			case CIL_STELEM_I4:
			case CIL_STELEM_R4:
			case CIL_STELEM_REF:
				PopStackTypeMulti(3); // Don't care what any of these are
				PushOp(JIT_STORE_ELEMENT_32);
				break;

			case CIL_STELEM_I8:
			case CIL_STELEM_R8:
				PopStackTypeMulti(3); // Don't care what any of these are
				PushOp(JIT_STORE_ELEMENT_64);
				break;

			case CIL_STELEM_ANY:
				GetUnalignedU32(pCIL, &cilOfs); // Don't need this token, as the type stack will contain the same type
				pStackType = PopStackType(); // This is the type to store
				PopStackTypeMulti(2); // Don't care what these are
				PushOpParam(JIT_STORE_ELEMENT, pStackType->stackSize);
				break;

			case CIL_STFLD:
				{
					tMD_FieldDef *pFieldDef;

					// Get the stack type of the value to store
					pStackType = PopStackType();
					PushOp(JIT_STOREFIELD_TYPEID + pStackType->stackType);
					// Get the FieldRef or FieldDef of the field to store
					u32Value = GetUnalignedU32(pCIL, &cilOfs);
					pFieldDef = MetaData_GetFieldDefFromDefOrRef(pMethodDef->pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
					PushPTR(pFieldDef);
					// Pop the object/valuetype on which to store the field. Don't care what it is
					PopStackTypeDontCare();
				}
				break;

			case CIL_LDFLD:
				{
					tMD_FieldDef *pFieldDef;

					// Get the FieldRef or FieldDef of the field to load
					u32Value = GetUnalignedU32(pCIL, &cilOfs);
					pFieldDef = MetaData_GetFieldDefFromDefOrRef(pMethodDef->pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
					// Pop the object/valuetype on which to load the field.
					pStackType = PopStackType();
					if (pStackType->stackType == EVALSTACK_VALUETYPE) {
						PushOpParam(JIT_LOADFIELD_VALUETYPE, pStackType->stackSize);
						PushPTR(pFieldDef);
					} else {
						if (pFieldDef->memSize <= 4) {
							PushOp(JIT_LOADFIELD_4);
							PushU32(pFieldDef->memOffset);
						} else {
							PushOp(JIT_LOADFIELD);
							PushPTR(pFieldDef);
						}
					}
					// Push the stack type of the just-read field
					PushStackType(pFieldDef->pType);
				}
				break;

			case CIL_LDFLDA:
				{
					tMD_FieldDef *pFieldDef;
					tMD_TypeDef *pTypeDef;

					// Get the FieldRef or FieldDef of the field to load
					u32Value = GetUnalignedU32(pCIL, &cilOfs);
					pFieldDef = MetaData_GetFieldDefFromDefOrRef(pMethodDef->pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
					// Sometimes, the type def will not have been filled, so ensure it's filled.
					pTypeDef = MetaData_GetTypeDefFromFieldDef(pFieldDef);
					MetaData_Fill_TypeDef(pTypeDef, NULL, NULL);
					PopStackTypeDontCare(); // Don't care what it is
					PushOpParam(JIT_LOAD_FIELD_ADDR, pFieldDef->memOffset);
					PushStackType(types[TYPE_SYSTEM_INTPTR]);
				}
				break;

			case CIL_STSFLD: // Store static field
				{
					tMD_FieldDef *pFieldDef;
					tMD_TypeDef *pTypeDef;

					// Get the FieldRef or FieldDef of the static field to store
					PopStackTypeDontCare(); // Don't care what it is
					u32Value = GetUnalignedU32(pCIL, &cilOfs);
					pFieldDef = MetaData_GetFieldDefFromDefOrRef(pMethodDef->pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
					// Sometimes, the type def will not have been filled, so ensure it's filled.
					pTypeDef = MetaData_GetTypeDefFromFieldDef(pFieldDef);
					MetaData_Fill_TypeDef(pTypeDef, NULL, NULL);
					pStackType = pFieldDef->pType;
					PushOp(JIT_STORESTATICFIELD_TYPEID + pStackType->stackType);
					PushPTR(pFieldDef);
				}
				break;

			case CIL_LDSFLD: // Load static field
				{
					tMD_FieldDef *pFieldDef;
					tMD_TypeDef *pTypeDef;

					// Get the FieldRef or FieldDef of the static field to load
					u32Value = GetUnalignedU32(pCIL, &cilOfs);
					pFieldDef = MetaData_GetFieldDefFromDefOrRef(pMethodDef->pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
					// Sometimes, the type def will not have been filled, so ensure it's filled.
					pTypeDef = MetaData_GetTypeDefFromFieldDef(pFieldDef);
					MetaData_Fill_TypeDef(pTypeDef, NULL, NULL);
					pStackType = pFieldDef->pType;
					PushOp(JIT_LOADSTATICFIELD_CHECKTYPEINIT_TYPEID + pStackType->stackType);
					PushPTR(pFieldDef);
					PushStackType(pStackType);
				}
				break;

			case CIL_LDSFLDA: // Load static field address
				{
					tMD_FieldDef *pFieldDef;
					tMD_TypeDef *pTypeDef;

					// Get the FieldRef or FieldDef of the field to load
					u32Value = GetUnalignedU32(pCIL, &cilOfs);
					pFieldDef = MetaData_GetFieldDefFromDefOrRef(pMethodDef->pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
					// Sometimes, the type def will not have been filled, so ensure it's filled.
					pTypeDef = MetaData_GetTypeDefFromFieldDef(pFieldDef);
					MetaData_Fill_TypeDef(pTypeDef, NULL, NULL);
					PushOp(JIT_LOADSTATICFIELDADDRESS_CHECKTYPEINIT);
					PushPTR(pFieldDef);
					PushStackType(types[TYPE_SYSTEM_INTPTR]);
				}
				break;

			case CIL_BOX:
				{
					tMD_TypeDef *pTypeDef;

					pStackType = PopStackType();
					// Get the TypeDef(or Ref) token of the valuetype to box
					u32Value = GetUnalignedU32(pCIL, &cilOfs);
					pTypeDef = MetaData_GetTypeDefFromDefRefOrSpec(pMethodDef->pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
					MetaData_Fill_TypeDef(pTypeDef, NULL, NULL);
					if (pTypeDef->pGenericDefinition == types[TYPE_SYSTEM_NULLABLE]) {
						// This is a nullable type, so special boxing code is needed.
						PushOp(JIT_BOX_NULLABLE);
						// Push the underlying type of the nullable type, not the nullable type itself
						PushPTR(pTypeDef->ppClassTypeArgs[0]);
					} else {
						PushOp(JIT_BOX_TYPEID + pStackType->stackType);
						PushPTR(pTypeDef);
					}
					// This is correct - cannot push underlying type, as then references are treated as value-types
					PushStackType(types[TYPE_SYSTEM_OBJECT]);
				}
				break;

			case CIL_UNBOX_ANY:
				{
					tMD_TypeDef *pTypeDef;

					PopStackTypeDontCare(); // Don't care what it is
					u32Value = GetUnalignedU32(pCIL, &cilOfs);
					pTypeDef = MetaData_GetTypeDefFromDefRefOrSpec(pMethodDef->pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
					if (pTypeDef->pGenericDefinition == types[TYPE_SYSTEM_NULLABLE]) {
						// This is a nullable type, so special unboxing is required.
						PushOp(JIT_UNBOX_NULLABLE);
						// For nullable types, push the underlying type
						PushPTR(pTypeDef->ppClassTypeArgs[0]);
					} else if (pTypeDef->isValueType) {
						PushOp(JIT_UNBOX2VALUETYPE);
					} else {
						PushOp(JIT_UNBOX2OBJECT);
					}
					PushStackType(pTypeDef);
				}
				break;

			case CIL_LDTOKEN:
				u32Value = GetUnalignedU32(pCIL, &cilOfs);
				pMem = MetaData_GetTypeMethodField(pMethodDef->pMetaData, u32Value, &u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
				PushOp(JIT_LOADTOKEN_BASE + u32Value);
				PushPTR(pMem);
				PushStackType(types[
					(u32Value==0)?TYPE_SYSTEM_RUNTIMETYPEHANDLE:
						((u32Value==1)?TYPE_SYSTEM_RUNTIMEFIELDHANDLE:TYPE_SYSTEM_RUNTIMEMETHODHANDLE)
				]);
				break;

			case CIL_THROW:
				PopStackTypeDontCare(); // Don't care what it is
				PushOp(JIT_THROW);
				RestoreTypeStack(&typeStack, ppTypeStacks[cilOfs]);
				break;

			case CIL_LEAVE_S:
				u32Value = (I8)pCIL[cilOfs++];
				goto cilLeave;

			case CIL_LEAVE:
				u32Value = GetUnalignedU32(pCIL, &cilOfs);
cilLeave:
				// Put a temporary CIL offset value into the JITted code. This will be updated later
				u32Value = cilOfs + (I32)u32Value;
				MayCopyTypeStack();
				RestoreTypeStack(&typeStack, ppTypeStacks[cilOfs]);
				PushOp(JIT_LEAVE);
				PushBranch();
				PushU32(u32Value);
				break;

			case CIL_ENDFINALLY:
				PushOp(JIT_END_FINALLY);
				RestoreTypeStack(&typeStack, ppTypeStacks[cilOfs]);
				break;

			case CIL_EXTENDED:
				op = pCIL[cilOfs++];

				switch (op)
				{
				case CILX_INITOBJ:
					{
						tMD_TypeDef *pTypeDef;

						PopStackTypeDontCare(); // Don't care what it is
						u32Value = GetUnalignedU32(pCIL, &cilOfs);
						pTypeDef = MetaData_GetTypeDefFromDefRefOrSpec(pMethodDef->pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
						if (pTypeDef->isValueType) {
							PushOp(JIT_INIT_VALUETYPE);
							PushPTR(pTypeDef);
						} else {
							PushOp(JIT_INIT_OBJECT);
						}
					}
					break;

				case CILX_LOADFUNCTION:
					{
						tMD_MethodDef *pFuncMethodDef;

						u32Value = GetUnalignedU32(pCIL, &cilOfs);
						pFuncMethodDef = MetaData_GetMethodDefFromDefRefOrSpec(pMethodDef->pMetaData, u32Value, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
						PushOp(JIT_LOADFUNCTION);
						PushPTR(pFuncMethodDef);
						PushStackType(types[TYPE_SYSTEM_INTPTR]);
					}
					break;

				case CILX_CEQ:
				case CILX_CGT:
				case CILX_CGT_UN:
				case CILX_CLT:
				case CILX_CLT_UN:
					pTypeB = PopStackType();
					pTypeA = PopStackType();
					if ((pTypeA->stackType == EVALSTACK_INT32 && pTypeB->stackType == EVALSTACK_INT32) ||
						(pTypeA->stackType == EVALSTACK_O && pTypeB->stackType == EVALSTACK_O) ||
						// Next line: only on 32-bit
						(pTypeA->stackType == EVALSTACK_PTR && pTypeB->stackType == EVALSTACK_PTR)) {
						PushOp(JIT_CEQ_I32I32 + (op - CILX_CEQ));
					} else if (pTypeA->stackType == EVALSTACK_INT64 && pTypeB->stackType == EVALSTACK_INT64) {
						PushOp(JIT_CEQ_I64I64 + (op - CILX_CEQ));
					} else if (pTypeA->stackType == EVALSTACK_F32 && pTypeB->stackType == EVALSTACK_F32) {
						PushOp(JIT_CEQ_F32F32 + (op - CILX_CEQ));
					} else if (pTypeA->stackType == EVALSTACK_F64 && pTypeB->stackType == EVALSTACK_F64) {
						PushOp(JIT_CEQ_F64F64 + (op - CILX_CEQ));
					} else {
						Crash("JITit(): Cannot perform comparison operand on stack types: %s and %s", pTypeA->name, pTypeB->name);
					}
					PushStackType(types[TYPE_SYSTEM_INT32]);
					break;
					
				case CILX_RETHROW:
					PushOp(JIT_RETHROW);
					break;

				case CILX_CONSTRAINED:
					u32Value2 = GetUnalignedU32(pCIL, &cilOfs);
					cilOfs++;
					goto cilCallVirtConstrained;

				case CILX_READONLY:
					// Do nothing
					break;

				default:
					Crash("JITit(): JITter cannot handle extended op-code:0x%02x", op);

				}
				break;

			default:
				Crash("JITit(): JITter cannot handle op-code: 0x%02x", op);
		}

	} while (cilOfs < codeSize);

	// Apply branch offset fixes
	for (i=0; i<branchOffsets.ofs; i++) {
		U32 ofs, jumpTarget;

		ofs = branchOffsets.p[i];
		jumpTarget = ops.p[ofs];
		// Rewrite the branch offset
		jumpTarget = pJITOffsets[jumpTarget];
		ops.p[ofs] = jumpTarget;
#ifdef GEN_COMBINED_OPCODES
		isDynamic.p[jumpTarget] |= DYNAMIC_JUMP_TARGET;
#endif
	}

	// Apply expection handler offset fixes
	for (i=0; i<pJITted->numExceptionHandlers; i++) {
		tExceptionHeader *pEx;

		pEx = &pJITted->pExceptionHeaders[i];
		pEx->tryEnd = pJITOffsets[pEx->tryStart + pEx->tryEnd];
		pEx->tryStart = pJITOffsets[pEx->tryStart];
		pEx->handlerEnd = pJITOffsets[pEx->handlerStart + pEx->handlerEnd];
		pEx->handlerStart = pJITOffsets[pEx->handlerStart];
#ifdef GEN_COMBINED_OPCODES
		isDynamic.p[pEx->tryStart] |= DYNAMIC_EX_START | DYNAMIC_JUMP_TARGET;
		isDynamic.p[pEx->tryEnd] |= DYNAMIC_EX_END | DYNAMIC_JUMP_TARGET;
		isDynamic.p[pEx->handlerStart] |= DYNAMIC_EX_START | DYNAMIC_JUMP_TARGET;
		isDynamic.p[pEx->handlerEnd] |= DYNAMIC_EX_END | DYNAMIC_JUMP_TARGET;
#endif
	}

#ifdef GEN_COMBINED_OPCODES
	// Find any candidates for instruction combining
	if (genCombinedOpcodes) {
		U32 inst0 = 0;
		while (inst0 < ops.ofs) {
			U32 opCodeCount = 0;
			U32 instCount = 0;
			U32 shrinkOpsBy;
			U32 isFirstInst;
			while (!(isDynamic.p[inst0] & DYNAMIC_OK)) {
				inst0++;
				if (inst0 >= ops.ofs) {
					goto combineDone;
				}
			}
			isFirstInst = 1;
			while (isDynamic.p[inst0 + instCount] & DYNAMIC_OK) {
				if (isFirstInst) {
					isFirstInst = 0;
				} else {
					if (isDynamic.p[inst0 + instCount] & DYNAMIC_JUMP_TARGET) {
						// Cannot span a jump target
						break;
					}
				}
				instCount += 1 + ((isDynamic.p[inst0 + instCount] & DYNAMIC_BYTE_COUNT_MASK) >> 2);
				opCodeCount++;
			}
			shrinkOpsBy = 0;
			if (opCodeCount > 1) {
				U32 combinedSize;
				tCombinedOpcodesMem *pCOMem = TMALLOC(tCombinedOpcodesMem);
				shrinkOpsBy = GenCombined(&ops, &isDynamic, inst0, instCount, &combinedSize, &pCOMem->pMem);
				pCOMem->pNext = pJITted->pCombinedOpcodesMem;
				pJITted->pCombinedOpcodesMem = pCOMem;
				pJITted->opsMemSize += combinedSize;
				memmove(&ops.p[inst0 + instCount - shrinkOpsBy], &ops.p[inst0 + instCount], (ops.ofs - inst0 - instCount) << 2);
				memmove(&isDynamic.p[inst0 + instCount - shrinkOpsBy], &isDynamic.p[inst0 + instCount], (ops.ofs - inst0 - instCount) << 2);
				ops.ofs -= shrinkOpsBy;
				isDynamic.ofs -= shrinkOpsBy;
				for (i=0; i<branchOffsets.ofs; i++) {
					U32 ofs;
					if (branchOffsets.p[i] > inst0) {
						branchOffsets.p[i] -= shrinkOpsBy;
					}
					ofs = branchOffsets.p[i];
					if (ops.p[ofs] > inst0) {
						ops.p[ofs] -= shrinkOpsBy;
					}
				}
				for (i=0; i<pJITted->numExceptionHandlers; i++) {
					tExceptionHeader *pEx;

					pEx = &pJITted->pExceptionHeaders[i];
					if (pEx->tryStart > inst0) {
						pEx->tryStart -= shrinkOpsBy;
					}
					if (pEx->tryEnd > inst0) {
						pEx->tryEnd -= shrinkOpsBy;
					}
					if (pEx->handlerStart > inst0) {
						pEx->handlerStart -= shrinkOpsBy;
					}
					if (pEx->handlerEnd > inst0) {
						pEx->handlerEnd -= shrinkOpsBy;
					}
				}
			}
			inst0 += instCount - shrinkOpsBy;
		}
	}
combineDone:
#endif

	// Change maxStack to indicate the number of bytes needed on the evaluation stack.
	// This is the largest number of bytes needed by all objects/value-types on the stack,
	pJITted->maxStack = typeStack.maxBytes;

	free(typeStack.ppTypes);

	for (i=0; i<codeSize; i++) {
		if (ppTypeStacks[i] != NULL) {
			free(ppTypeStacks[i]->ppTypes);
		}
	}
	free(ppTypeStacks);

	DeleteOps(branchOffsets);
	free(pJITOffsets);

	// Copy ops to some memory of exactly the correct size. To not waste memory.
	u32Value = ops.ofs * sizeof(U32);
	pFinalOps = genCombinedOpcodes?malloc(u32Value):mallocForever(u32Value);
	memcpy(pFinalOps, ops.p, u32Value);
	DeleteOps(ops);
#ifdef GEN_COMBINED_OPCODES
	pJITted->opsMemSize += u32Value;
	DeleteOps(isDynamic);
#endif

	return pFinalOps;
}

// Prepare a method for execution
// This makes sure that the method has been JITed.
void JIT_Prepare(tMD_MethodDef *pMethodDef, U32 genCombinedOpcodes) {
	tMetaData *pMetaData;
	U8 *pMethodHeader;
	tJITted *pJITted;
	FLAGS16 flags;
	U32 codeSize;
	IDX_TABLE localsToken;
	U8 *pCIL;
	SIG sig;
	U32 i, sigLength, numLocals;
	tParameter *pLocals;

	log_f(2, "JIT:   %s\n", Sys_GetMethodDesc(pMethodDef));

	pMetaData = pMethodDef->pMetaData;
	pJITted = (genCombinedOpcodes)?TMALLOC(tJITted):TMALLOCFOREVER(tJITted);
#ifdef GEN_COMBINED_OPCODES
	pJITted->pCombinedOpcodesMem = NULL;
	pJITted->opsMemSize = 0;
	if (genCombinedOpcodes) {
		pMethodDef->pJITtedCombined = pJITted;
	} else {
		pMethodDef->pJITted = pJITted;
	}
#else
	pMethodDef->pJITted = pJITted;
#endif

	if ((pMethodDef->implFlags & METHODIMPLATTRIBUTES_INTERNALCALL) ||
		((pMethodDef->implFlags & METHODIMPLATTRIBUTES_CODETYPE_MASK) == METHODIMPLATTRIBUTES_CODETYPE_RUNTIME)) {
		tJITCallNative *pCallNative;

		// Internal call
		if (strcmp(pMethodDef->name, ".ctor") == 0) {
			// Internal constructor needs enough evaluation stack space to return itself
			pJITted->maxStack = pMethodDef->pParentType->stackSize;
		} else {
			pJITted->maxStack = (pMethodDef->pReturnType == NULL)?0:pMethodDef->pReturnType->stackSize; // For return value
		}
		pCallNative = TMALLOCFOREVER(tJITCallNative);
		pCallNative->opCode = Translate(JIT_CALL_NATIVE, 0);
		pCallNative->pMethodDef = pMethodDef;
		pCallNative->fn = InternalCall_Map(pMethodDef);
		pCallNative->retOpCode = Translate(JIT_RETURN, 0);

		pJITted->localsStackSize = 0;
		pJITted->pOps = (U32*)pCallNative;

		return;
	}
	if (pMethodDef->flags & METHODATTRIBUTES_PINVOKEIMPL) {
		tJITCallPInvoke *pCallPInvoke;

		// PInvoke call
		tMD_ImplMap *pImplMap = MetaData_GetImplMap(pMetaData, pMethodDef->tableIndex);
		fnPInvoke fn = PInvoke_GetFunction(pMetaData, pImplMap);
		if (fn == NULL) {
			Crash("PInvoke library or function not found: %s()", pImplMap->importName);
		}

		pCallPInvoke = TMALLOCFOREVER(tJITCallPInvoke);
		pCallPInvoke->opCode = Translate(JIT_CALL_PINVOKE, 0);
		pCallPInvoke->fn = fn;
		pCallPInvoke->pMethod = pMethodDef;
		pCallPInvoke->pImplMap = pImplMap;

		pJITted->localsStackSize = 0;
		pJITted->maxStack = (pMethodDef->pReturnType == NULL)?0:pMethodDef->pReturnType->stackSize; // For return value
		pJITted->pOps = (U32*)pCallPInvoke;

		return;
	}

	pMethodHeader = (U8*)pMethodDef->pCIL;
	if ((*pMethodHeader & 0x3) == CorILMethod_TinyFormat) {
		// Tiny header
		flags = *pMethodHeader & 0x3;
		pJITted->maxStack = 8;
		codeSize = (*pMethodHeader & 0xfc) >> 2;
		localsToken = 0;
		pCIL = pMethodHeader + 1;
	} else {
		// Fat header
		flags = *(U16*)pMethodHeader & 0x0fff;
		pJITted->maxStack = *(U16*)&pMethodHeader[2];
		codeSize = *(U32*)&pMethodHeader[4];
		localsToken = *(IDX_TABLE*)&pMethodHeader[8];
		pCIL = pMethodHeader + ((pMethodHeader[1] & 0xf0) >> 2);
	}
	if (flags & CorILMethod_MoreSects) {
		U32 numClauses;

		pMethodHeader = pCIL + ((codeSize + 3) & (~0x3));
		if (*pMethodHeader & CorILMethod_Sect_FatFormat) {
			U32 exSize;
			// Fat header
			numClauses = ((*(U32*)pMethodHeader >> 8) - 4) / 24;
			//pJITted->pExceptionHeaders = (tExceptionHeader*)(pMethodHeader + 4);
			exSize = numClauses * sizeof(tExceptionHeader);
			pJITted->pExceptionHeaders =
				(tExceptionHeader*)(genCombinedOpcodes?malloc(exSize):mallocForever(exSize));
			memcpy(pJITted->pExceptionHeaders, pMethodHeader + 4, exSize);
		} else {
			// Thin header
			tExceptionHeader *pExHeaders;
			U32 exSize;

			numClauses = (((U8*)pMethodHeader)[1] - 4) / 12;
			exSize = numClauses * sizeof(tExceptionHeader);
			pMethodHeader += 4;
			//pExHeaders = pJITted->pExceptionHeaders = (tExceptionHeader*)mallocForever(numClauses * sizeof(tExceptionHeader));
			pExHeaders = pJITted->pExceptionHeaders =
				(tExceptionHeader*)(genCombinedOpcodes?malloc(exSize):mallocForever(exSize));
			for (i=0; i<numClauses; i++) {
				pExHeaders[i].flags = ((U16*)pMethodHeader)[0];
				pExHeaders[i].tryStart = ((U16*)pMethodHeader)[1];
				pExHeaders[i].tryEnd = ((U8*)pMethodHeader)[4];
				pExHeaders[i].handlerStart = ((U8*)pMethodHeader)[5] | (((U8*)pMethodHeader)[6] << 8);
				pExHeaders[i].handlerEnd = ((U8*)pMethodHeader)[7];
				pExHeaders[i].u.classToken = ((U32*)pMethodHeader)[2];

				pMethodHeader += 12;
			}
		}
		pJITted->numExceptionHandlers = numClauses;
		// replace all classToken's with the actual tMD_TypeDef*
		for (i=0; i<numClauses; i++) {
			if (pJITted->pExceptionHeaders[i].flags == COR_ILEXCEPTION_CLAUSE_EXCEPTION) {
				pJITted->pExceptionHeaders[i].u.pCatchTypeDef =
					MetaData_GetTypeDefFromDefRefOrSpec(pMethodDef->pMetaData, pJITted->pExceptionHeaders[i].u.classToken, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
			}
		}
	} else {
		pJITted->numExceptionHandlers = 0;
		pJITted->pExceptionHeaders = NULL;
	}

	// Analyse the locals
	if (localsToken == 0) {
		// No locals
		pJITted->localsStackSize = 0;
		pLocals = NULL;
	} else {
		tMD_StandAloneSig *pStandAloneSig;
		U32 i, totalSize;

		pStandAloneSig = (tMD_StandAloneSig*)MetaData_GetTableRow(pMethodDef->pMetaData, localsToken);
		sig = MetaData_GetBlob(pStandAloneSig->signature, &sigLength);
		MetaData_DecodeSigEntry(&sig); // Always 0x07
		numLocals = MetaData_DecodeSigEntry(&sig);
		pLocals = (tParameter*)malloc(numLocals * sizeof(tParameter));
		totalSize = 0;
		for (i=0; i<numLocals; i++) {
			tMD_TypeDef *pTypeDef;

			pTypeDef = Type_GetTypeFromSig(pMethodDef->pMetaData, &sig, pMethodDef->pParentType->ppClassTypeArgs, pMethodDef->ppMethodTypeArgs);
			MetaData_Fill_TypeDef(pTypeDef, NULL, NULL);
			pLocals[i].pTypeDef = pTypeDef;
			pLocals[i].offset = totalSize;
			pLocals[i].size = pTypeDef->stackSize;
			totalSize += pTypeDef->stackSize;
		}
		pJITted->localsStackSize = totalSize;
	}

	// JIT the CIL code
	pJITted->pOps = JITit(pMethodDef, pCIL, codeSize, pLocals, pJITted, genCombinedOpcodes);

	free(pLocals);
}