void InitLocalsRefs2(CilType plainType, BoxedType boxedType, bool arg, int index) { if (arg) { code.NewInstruction(plainType.LoadOpcode, null, index); } else { code.NewInstruction(plainType.InitOpcode, null, null); } stackMap.PushStack(plainType); if (boxedType != null) { boxedType.BoxValue(code); } else if (plainType.IsGenericParameter) { GenericUtil.ValueClone(code); } else { CilMethod.ValueMethod(CilMethod.ValueClone, code); code.NewInstruction(0xC0 /* checkcast */, plainType, null); } code.NewInstruction(0x3A /* astore */, null, index); stackMap.PopStack(CilMain.Where); }
public static void ValueCopy(CilType valueType, JavaCode code, bool swap = false) { // if 'from' value is pushed before 'into' object, call with swap == false // if 'into' object is pushed before 'from' value, call with swap == true if (valueType.IsGenericParameter) { if (swap) { code.NewInstruction(0x5F /* swap */, null, null); } else { // if storing a primitive value into a generic type, // and the generic type can be resolved to a primitive type, // then use a boxed-set method call var stackArray = code.StackMap.StackArray(); int stackArrayLen = stackArray.Length; if (stackArrayLen > 0 && (!stackArray[stackArrayLen - 1].IsReference)) { var genericMark = CilMain.GenericStack.Mark(); var(primitiveType, _) = CilMain.GenericStack.Resolve(valueType.JavaName); CilMain.GenericStack.Release(genericMark); if (!primitiveType.IsReference) { var boxedType = new BoxedType(primitiveType, false); code.NewInstruction(0xC0 /* checkcast */, boxedType, null); boxedType.SetValueVO(code); return; } } } code.NewInstruction(0xB8 /* invokestatic */, SystemGenericType, new JavaMethod("Copy", JavaType.VoidType, JavaType.ObjectType, JavaType.ObjectType)); } else { if (swap) { code.NewInstruction(0x5F /* swap */, null, null); } CilMethod.ValueMethod(CilMethod.ValueCopyTo, code); } }
void Load(CilType arrayType, CilType elemType, Mono.Cecil.Cil.Instruction inst) { if (arrayType == null) { arrayType = elemType.AdjustRank(1); } /*Console.WriteLine("(LOAD) ARRAY TYPE = " + arrayType + "," + arrayType.ArrayRank + " ELEM TYPE = " + elemType + "," + elemType.ArrayRank + " ELEMVAL? " + elemType.IsValueClass + " ELEMGEN? " + elemType.IsGenericParameter);*/ if (object.ReferenceEquals(arrayType, GenericArrayType) || elemType.IsGenericParameter || arrayType.IsGenericParameter) { code.NewInstruction(0xB8 /* invokestatic */, SystemArrayType, LoadArrayMethod); if (elemType.ArrayRank != 0) { elemType = GenericArrayType; } } else { if (elemType.PrimitiveType == TypeCode.Int16 && (arrayType.PrimitiveType == TypeCode.Char || arrayType.PrimitiveType == TypeCode.UInt16)) { // ldelem.i2 with a char[] array, should be 'caload' not 'saload' elemType = arrayType.AdjustRank(-arrayType.ArrayRank); } code.NewInstruction(elemType.LoadArrayOpcode, null, null); if (arrayType.IsValueClass || elemType.IsValueClass) { CilMethod.ValueMethod(CilMethod.ValueClone, code); } } stackMap.PushStack(elemType); }
void LoadObject(Code cilOp, object data) { if (data is TypeReference typeRef) { var dataType = CilType.From(typeRef); var fromType = (CilType)code.StackMap.PopStack(CilMain.Where); if (CodeSpan.LoadStore(true, fromType, null, dataType, code)) { return; } if ((!dataType.IsReference) && cilOp == Code.Ldobj && fromType is BoxedType fromBoxedType && dataType.PrimitiveType == fromBoxedType.UnboxedType.PrimitiveType) { // 'ldobj primitive' with a corresponding boxed type on the stack. // we implement by unboxing the boxed type into a primitive value. fromBoxedType.GetValue(code); stackMap.PushStack(fromBoxedType.UnboxedType); return; } if (dataType.IsGenericParameter || (dataType.IsValueClass && dataType.Equals(fromType))) { code.StackMap.PushStack(dataType); if (SkipClone(cilInst.Next, fromType)) { // see below for the several cases where we determine // that we can safely avoid making a clone of the value code.NewInstruction(0x00 /* nop */, null, null); } else if (dataType.IsGenericParameter) { GenericUtil.ValueClone(code); } else if (cilOp == Code.Ldobj) { code.NewInstruction(0x00 /* nop */, null, null); } else { CilMethod.ValueMethod(CilMethod.ValueClone, code); } return; } if (dataType.IsReference && cilOp == Code.Box) { // 'box' is permitted on reference types, we treat it as a cast code.StackMap.PushStack(fromType); CastToClass(data); return; } if (!dataType.IsReference) { var boxedType = new BoxedType(dataType, false); code.StackMap.PushStack(boxedType); boxedType.BoxValue(code); return; } } throw new InvalidProgramException(); bool SkipClone(Mono.Cecil.Cil.Instruction next, CilType checkType) { if (checkType.IsByReference) { return(true); } if (next == null) { return(false); } var op = next.OpCode.Code; if (IsBrTrueBrFalseIsInst(op)) { // if 'ldobj' or 'box' is followed by a check for null, // we don't actually need to clone just for the test return(true); } if (op == Code.Box) { if (next.Operand is TypeReference nextTypeRef) { // 'ldobj' may be followed by 'box', to load the value // of a byref value type, and then box it into an object. // effectively we only need to clone once, in such a case. return(CilType.From(nextTypeRef).Equals(checkType)); } } var(storeType, _) = locals.GetLocalFromStoreInst(op, next.Operand); if (storeType != null) { // 'ldobj' or 'box' may be followed by a store instruction. // if storing into a variable of the same value type, the // next instruction will copy the value, so skip clone. return(storeType.Equals(checkType)); } if (op == Code.Ret && checkType.IsClonedAtTop) { // if the value on the stack was cloned/boxed at the top of // the method, then we can avoid clone and return it directly. // see also: CilType.MakeClonedAtTop and its callers. return(true); } if (op == Code.Unbox_Any) { if (next.Operand is TypeReference nextTypeRef) { // 'ldobj' or 'box' may be followed by 'unbox' and // then one of the instructions above, e.g. 'brtrue' // or 'stloc'. we still want to detect such a case // and prevent a needless clone. return(SkipClone(next.Next, CilType.From(nextTypeRef))); } } return(false); } }
void Store(CilType elemType) { stackMap.PopStack(CilMain.Where); // value stackMap.PopStack(CilMain.Where); // index var arrayType = stackMap.PopStack(CilMain.Where) as CilType; // array var arrayElemType = arrayType.AdjustRank(-arrayType.ArrayRank); if (elemType == null) { elemType = arrayElemType; } /*Console.WriteLine("(STORE) ARRAY TYPE = " + arrayType + "," + arrayType.ArrayRank + " ELEM TYPE = " + elemType + "," + elemType.ArrayRank + " ELEMVAL? " + elemType.IsValueClass + " ELEMGEN? " + elemType.IsGenericParameter);*/ if (object.ReferenceEquals(arrayType, GenericArrayType)) { // stelem.any T into generic array T[] code.NewInstruction(0xB8 /* invokestatic */, SystemArrayType, StoreArrayMethod); } else if (arrayElemType.IsValueClass && elemType.IsValueClass) { // storing a value type into an array of value types. // we use ValueType.ValueCopy to write over the element. int localIndex = locals.GetTempIndex(elemType); code.NewInstruction(elemType.StoreOpcode, null, localIndex); code.NewInstruction(arrayType.LoadArrayOpcode, null, null); code.NewInstruction(elemType.LoadOpcode, null, localIndex); locals.FreeTempIndex(localIndex); // we can pass any type that is not a generic parameter GenericUtil.ValueCopy(CilType.SystemTypeType, code, true); } else if (arrayType.ArrayRank > 1) { // always 'aastore' if multidimensional array code.NewInstruction(arrayType.StoreArrayOpcode, null, null); } else { if (elemType.PrimitiveType == TypeCode.Int16 && (arrayType.PrimitiveType == TypeCode.Char || arrayType.PrimitiveType == TypeCode.UInt16)) { // stelem.i2 with a char[] array, should be 'castore' not 'sastore' elemType = arrayType.AdjustRank(-arrayType.ArrayRank); } if (arrayType.IsValueClass || elemType.IsValueClass) { CilMethod.ValueMethod(CilMethod.ValueClone, code); } code.NewInstruction(elemType.StoreArrayOpcode, null, null); } }
void Store(CilType elemType, Mono.Cecil.Cil.Instruction inst) { stackMap.PopStack(CilMain.Where); // value stackMap.PopStack(CilMain.Where); // index var arrayType = stackMap.PopStack(CilMain.Where) as CilType; // array var arrayElemType = arrayType.AdjustRank(-arrayType.ArrayRank); if (elemType == null) { elemType = arrayElemType; } /*Console.WriteLine("(STORE) ARRAY TYPE = " + arrayType + "," + arrayType.ArrayRank + " ELEM TYPE = " + elemType + "," + elemType.ArrayRank + " ELEMVAL? " + elemType.IsValueClass + " ELEMGEN? " + elemType.IsGenericParameter);*/ if (object.ReferenceEquals(arrayType, GenericArrayType)) { // stelem.any T into generic array T[] code.NewInstruction(0xB8 /* invokestatic */, SystemArrayType, StoreArrayMethod); } else if (arrayElemType.IsValueClass && elemType.IsValueClass) { // storing a value type into an array of value types. // we use ValueType.ValueCopy to write over the element. int localIndex = locals.GetTempIndex(elemType); code.NewInstruction(elemType.StoreOpcode, null, localIndex); code.NewInstruction(arrayType.LoadArrayOpcode, null, null); code.NewInstruction(elemType.LoadOpcode, null, localIndex); locals.FreeTempIndex(localIndex); // we can pass any type that is not a generic parameter GenericUtil.ValueCopy(CilType.SystemTypeType, code, true); } else if (arrayType.ArrayRank > 1) { // always 'aastore' if multidimensional array code.NewInstruction(arrayType.StoreArrayOpcode, null, null); } else { if (elemType.PrimitiveType == TypeCode.Int16 && (arrayType.PrimitiveType == TypeCode.Char || arrayType.PrimitiveType == TypeCode.UInt16)) { // stelem.i2 with a char[] array, should be 'castore' not 'sastore' elemType = arrayType.AdjustRank(-arrayType.ArrayRank); } else { // Android AOT crashes the compilation if an immediate value // is stored into a byte or short array, and the value does // not fit within the range -128..127 or -32768..32767. // simply checing if the previous instruction loaded the // constant is not enough, because due to method inlining // by the Android ART JIT, the immediate value might actually // originate in a calling method. // so we always force the value into range using i2b/i2s. // see also: CodeNumber::ConvertToInteger if (arrayType.PrimitiveType == TypeCode.Boolean || arrayType.PrimitiveType == TypeCode.SByte || arrayType.PrimitiveType == TypeCode.Byte) { code.NewInstruction(0x91 /* i2b */, null, null); } else if (arrayType.PrimitiveType == TypeCode.Int16) { code.NewInstruction(0x93 /* i2s */, null, null); } } if (arrayType.IsValueClass || elemType.IsValueClass) { CilMethod.ValueMethod(CilMethod.ValueClone, code); } code.NewInstruction(elemType.StoreArrayOpcode, null, null); } }
void Load(CilType arrayType, CilType elemType, Mono.Cecil.Cil.Instruction inst) { if (arrayType == null) { arrayType = elemType.AdjustRank(1); } /*Console.WriteLine("(LOAD) ARRAY TYPE = " + arrayType + "," + arrayType.ArrayRank + " ELEM TYPE = " + elemType + "," + elemType.ArrayRank + " ELEMVAL? " + elemType.IsValueClass + " ELEMGEN? " + elemType.IsGenericParameter);*/ if (object.ReferenceEquals(arrayType, GenericArrayType) || elemType.IsGenericParameter || arrayType.IsGenericParameter) { code.NewInstruction(0xB8 /* invokestatic */, SystemArrayType, LoadArrayMethod); if (elemType.ArrayRank != 0) { elemType = GenericArrayType; } } else { if (elemType.PrimitiveType == TypeCode.Int16 && (arrayType.PrimitiveType == TypeCode.Char || arrayType.PrimitiveType == TypeCode.UInt16)) { // ldelem.i2 with a char[] array, should be 'caload' not 'saload' elemType = arrayType.AdjustRank(-arrayType.ArrayRank); } code.NewInstruction(elemType.LoadArrayOpcode, null, null); if (elemType.PrimitiveType == TypeCode.Byte) { // unsigned byte result should be truncated to 8-bits // (unless already followed by "ldc.i4 255 ; and") bool followedByAndWith255 = CodeBuilder.IsLoadConstant(inst.Next) == 0xFF && inst.Next.Next?.OpCode.Code == Code.And; if (!followedByAndWith255) { stackMap.PushStack(JavaType.IntegerType); code.NewInstruction(0x12 /* ldc */, null, (int)0xFF); code.NewInstruction(0x7E /* iand */, null, null); stackMap.PopStack(CilMain.Where); } } if (arrayType.IsValueClass || elemType.IsValueClass) { CilMethod.ValueMethod(CilMethod.ValueClone, code); if (elemType.IsValueClass) { code.NewInstruction(0xC0 /* checkcast */, elemType, null); } } } stackMap.PushStack(elemType); }