mirror of
https://github.com/danbulant/Cosmos
synced 2026-05-26 21:42:11 +00:00
This commit is contained in:
parent
b09d8af85e
commit
d6a2c080fa
8 changed files with 423 additions and 361 deletions
|
|
@ -5,370 +5,299 @@ using CPUx86 = Indy.IL2CPU.Assembler.X86;
|
||||||
using Cosmos.IL2CPU.ILOpCodes;
|
using Cosmos.IL2CPU.ILOpCodes;
|
||||||
namespace Cosmos.IL2CPU.X86.IL
|
namespace Cosmos.IL2CPU.X86.IL
|
||||||
{
|
{
|
||||||
[Cosmos.IL2CPU.OpCode( ILOpCode.Code.Newobj )]
|
[Cosmos.IL2CPU.OpCode(ILOpCode.Code.Newobj)]
|
||||||
public class Newobj : ILOp
|
public class Newobj : ILOp
|
||||||
{
|
{
|
||||||
public Newobj( Cosmos.IL2CPU.Assembler aAsmblr )
|
public Newobj(Cosmos.IL2CPU.Assembler aAsmblr)
|
||||||
: base( aAsmblr )
|
: base(aAsmblr)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Execute( MethodInfo aMethod, ILOpCode aOpCode )
|
public override void Execute(MethodInfo aMethod, ILOpCode aOpCode)
|
||||||
{
|
{
|
||||||
OpMethod xMethod = ( OpMethod )aOpCode;
|
OpMethod xMethod = (OpMethod)aOpCode;
|
||||||
// Is this checking for plugs?
|
string xCurrentLabel = GetLabel(aMethod, aOpCode);
|
||||||
// if (DynamicMethodEmit.GetHasDynamicMethod(CtorDef)) {
|
// TODO: enable this again
|
||||||
// CtorDef = DynamicMethodEmit.GetDynamicMethod(CtorDef);
|
// if (DynamicMethodEmit.GetHasDynamicMethod(CtorDef)) {
|
||||||
// }
|
// CtorDef = DynamicMethodEmit.GetDynamicMethod(CtorDef);
|
||||||
|
// }
|
||||||
|
|
||||||
//var xAllocInfo = GetService<IMetaDataInfoService>().GetMethodInfo(GCImplementationRefs.AllocNewObjectRef, false);
|
//var xAllocInfo = GetService<IMetaDataInfoService>().GetMethodInfo(GCImplementationRefs.AllocNewObjectRef, false);
|
||||||
|
|
||||||
// Assemble(
|
// Assemble(
|
||||||
// Assembler,
|
// Assembler,
|
||||||
// CtorDef,
|
// CtorDef,
|
||||||
// GetService<IMetaDataInfoService>().GetTypeIdLabel(CtorDef.DeclaringType),
|
// GetService<IMetaDataInfoService>().GetTypeIdLabel(CtorDef.DeclaringType),
|
||||||
// CurrentLabel,
|
// CurrentLabel,
|
||||||
// MethodInformation,
|
// MethodInformation,
|
||||||
// (int)ILOffset,
|
// (int)ILOffset,
|
||||||
// mNextLabel,
|
// mNextLabel,
|
||||||
// GetService<IMetaDataInfoService>().GetTypeInfo(CtorDef.DeclaringType),
|
// GetService<IMetaDataInfoService>().GetTypeInfo(CtorDef.DeclaringType),
|
||||||
// GetService<IMetaDataInfoService>().GetMethodInfo(CtorDef, false),
|
// GetService<IMetaDataInfoService>().GetMethodInfo(CtorDef, false),
|
||||||
// GetServiceProvider(),
|
// GetServiceProvider(),
|
||||||
// xAllocInfo.LabelName
|
// xAllocInfo.LabelName
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// public static void Assemble(
|
// public static void Assemble(
|
||||||
// Assembler.Assembler aAssembler,
|
// Assembler.Assembler aAssembler,
|
||||||
// MethodBase aCtorDef,
|
// MethodBase aCtorDef,
|
||||||
// string aTypeId,
|
// string aTypeId,
|
||||||
// string aCurrentLabel,
|
// string aCurrentLabel,
|
||||||
// MethodInformation aCurrentMethodInformation,
|
// MethodInformation aCurrentMethodInformation,
|
||||||
// int aCurrentILOffset,
|
// int aCurrentILOffset,
|
||||||
// string aNextLabel,
|
// string aNextLabel,
|
||||||
// TypeInformation aCtorDeclTypeInfo,
|
// TypeInformation aCtorDeclTypeInfo,
|
||||||
// MethodInformation aCtorMethodInfo,
|
// MethodInformation aCtorMethodInfo,
|
||||||
// IServiceProvider aServiceProvider,
|
// IServiceProvider aServiceProvider,
|
||||||
// string aAllocMemLabel
|
// string aAllocMemLabel
|
||||||
// )
|
// )
|
||||||
|
|
||||||
//TODO: What is this for?
|
//TODO: What is this for?
|
||||||
// if (aCtorDef != null) {
|
// if (aCtorDef != null) {
|
||||||
// //if (!aCtorDef.DeclaringType.FullName.StartsWith("Indy.IL2CPU.MultiArrayEmit.ContType"))
|
// //if (!aCtorDef.DeclaringType.FullName.StartsWith("Indy.IL2CPU.MultiArrayEmit.ContType"))
|
||||||
// // Engine.QueueMethod(aCtorDef);
|
// // Engine.QueueMethod(aCtorDef);
|
||||||
// } else {
|
// } else {
|
||||||
// throw new ArgumentNullException("aCtorDef");
|
// throw new ArgumentNullException("aCtorDef");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
var xType = xMethod.Value.DeclaringType;
|
var xType = xMethod.Value.DeclaringType;
|
||||||
|
|
||||||
// If not ValueType, then we need gc
|
// If not ValueType, then we need gc
|
||||||
if( !xType.IsValueType )
|
if (!xType.IsValueType)
|
||||||
{
|
{
|
||||||
var xParams = xMethod.Value.GetParameters();
|
var xParams = xMethod.Value.GetParameters();
|
||||||
for( int i = 1; i < xParams.Length; i++ )
|
for (int i = 1; i < xParams.Length; i++)
|
||||||
{
|
{
|
||||||
Assembler.Stack.Pop();
|
Assembler.Stack.Pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint xMemSize = GetFieldStorageSize( xType );
|
uint xMemSize = GetFieldStorageSize(xType);
|
||||||
int xExtraSize = 20;
|
int xExtraSize = 20;
|
||||||
new CPUx86.Push { DestinationValue = ( uint )( xMemSize + xExtraSize ) };
|
new CPUx86.Push { DestinationValue = (uint)(xMemSize + xExtraSize) };
|
||||||
|
|
||||||
new CPUx86.Call { DestinationLabel = MethodInfoLabelGenerator.GenerateLabelName( GCImplementationRefs.AllocNewObjectRef ) };
|
// todo: probably we want to check for exceptions after calling Alloc
|
||||||
new CPUx86.Test { DestinationReg = CPUx86.Registers.ECX, SourceValue = 2 };
|
new CPUx86.Call { DestinationLabel = MethodInfoLabelGenerator.GenerateLabelName(GCImplementationRefs.AllocNewObjectRef) };
|
||||||
new CPUx86.Push { DestinationReg = CPUx86.Registers.ESP, DestinationIsIndirect = true };
|
new CPUx86.Push { DestinationReg = CPUx86.Registers.ESP, DestinationIsIndirect = true };
|
||||||
new CPUx86.Push { DestinationReg = CPUx86.Registers.ESP, DestinationIsIndirect = true };
|
new CPUx86.Push { DestinationReg = CPUx86.Registers.ESP, DestinationIsIndirect = true };
|
||||||
new CPUx86.Push { DestinationReg = CPUx86.Registers.ESP, DestinationIsIndirect = true };
|
new CPUx86.Push { DestinationReg = CPUx86.Registers.ESP, DestinationIsIndirect = true };
|
||||||
new CPUx86.Push { DestinationReg = CPUx86.Registers.ESP, DestinationIsIndirect = true };
|
new CPUx86.Push { DestinationReg = CPUx86.Registers.ESP, DestinationIsIndirect = true };
|
||||||
//var xIncRefInfo = aServiceProvider.GetService<IMetaDataInfoService>().GetMethodInfo( GCImplementationRefs.IncRefCountRef, false );
|
|
||||||
// TODO: Why call incref twice?
|
|
||||||
// new Assembler.X86.Call { DestinationLabel = xIncRefInfo.LabelName };
|
|
||||||
// new CPUx86.Call { DestinationLabel = xIncRefInfo.LabelName };
|
|
||||||
|
|
||||||
new CPUx86.Call { DestinationLabel = MethodInfoLabelGenerator.GenerateLabelName( GCImplementationRefs.IncRefCountRef ) };
|
// We IncRef here twice because it's effectively pushed on the stack twice:
|
||||||
|
// * once for the .ctor parameter
|
||||||
|
// * once for the "returnvalue" of the newobj il op
|
||||||
|
|
||||||
uint xObjSize = 0;
|
// todo: probably we want to check for exceptions after calling IncRef
|
||||||
//int xGCFieldCount = ( from item in aCtorDeclTypeInfo.Fields.Values
|
new CPUx86.Call { DestinationLabel = MethodInfoLabelGenerator.GenerateLabelName(GCImplementationRefs.IncRefCountRef) };
|
||||||
//where item.NeedsGC
|
new CPUx86.Call { DestinationLabel = MethodInfoLabelGenerator.GenerateLabelName(GCImplementationRefs.IncRefCountRef) };
|
||||||
//select item ).Count();
|
|
||||||
|
|
||||||
//int xGCFieldCount = ( from item in aCtorDeclTypeInfo.Fields.Values
|
uint xObjSize = 0;
|
||||||
//where item.NeedsGC
|
//int xGCFieldCount = ( from item in aCtorDeclTypeInfo.Fields.Values
|
||||||
//select item ).Count();
|
//where item.NeedsGC
|
||||||
int xGCFieldCount = xType.GetFields().Count( x => x.FieldType.IsValueType );
|
//select item ).Count();
|
||||||
|
|
||||||
string strTypeId = xMethod.Value.DeclaringType.FullName;
|
//int xGCFieldCount = ( from item in aCtorDeclTypeInfo.Fields.Values
|
||||||
|
//where item.NeedsGC
|
||||||
|
//select item ).Count();
|
||||||
|
int xGCFieldCount = xType.GetFields().Count(x => x.FieldType.IsValueType);
|
||||||
|
|
||||||
new CPUx86.Pop { DestinationReg = CPUx86.Registers.EAX };
|
string strTypeId = xMethod.Value.DeclaringType.FullName;
|
||||||
new CPUx86.Move { DestinationReg = CPUx86.Registers.EBX, SourceRef = Indy.IL2CPU.Assembler.ElementReference.New( strTypeId ), SourceIsIndirect = true };
|
|
||||||
new CPUx86.Move { DestinationReg = CPUx86.Registers.EAX, DestinationIsIndirect = true, SourceReg = CPUx86.Registers.EBX };
|
|
||||||
new CPUx86.Move { DestinationReg = CPUx86.Registers.EAX, DestinationIsIndirect = true, DestinationDisplacement = 4, SourceValue = ( uint )InstanceTypeEnum.NormalObject, Size = 32 };
|
|
||||||
new CPUx86.Move { DestinationReg = CPUx86.Registers.EAX, DestinationIsIndirect = true, DestinationDisplacement = 8, SourceValue = ( uint )xGCFieldCount, Size = 32 };
|
|
||||||
uint xSize = ( uint )( ( ( from item in xParams
|
|
||||||
let xQSize = Align(GetFieldStorageSize( item.GetType() ), 4 )
|
|
||||||
select ( int )xQSize ).Take( xParams.Length - 1 ).Sum() ) );
|
|
||||||
|
|
||||||
for( int i = 1; i < xParams.Length; i++ )
|
new CPUx86.Pop { DestinationReg = CPUx86.Registers.EAX };
|
||||||
{
|
new CPUx86.Move { DestinationReg = CPUx86.Registers.EBX, SourceRef = Indy.IL2CPU.Assembler.ElementReference.New(strTypeId), SourceIsIndirect = true };
|
||||||
new Comment( String.Format( "Arg {0}: {1}", i, GetFieldStorageSize( xParams[i].GetType() ) ) );
|
new CPUx86.Move { DestinationReg = CPUx86.Registers.EAX, DestinationIsIndirect = true, SourceReg = CPUx86.Registers.EBX };
|
||||||
for( int j = 0; j < GetFieldStorageSize( xParams[ i ].GetType() ); j += 4 )
|
new CPUx86.Move { DestinationReg = CPUx86.Registers.EAX, DestinationIsIndirect = true, DestinationDisplacement = 4, SourceValue = (uint)InstanceTypeEnum.NormalObject, Size = 32 };
|
||||||
{
|
new CPUx86.Move { DestinationReg = CPUx86.Registers.EAX, DestinationIsIndirect = true, DestinationDisplacement = 8, SourceValue = (uint)xGCFieldCount, Size = 32 };
|
||||||
new CPUx86.Push { DestinationReg = CPUx86.Registers.ESP, DestinationIsIndirect = true, DestinationDisplacement = ( int )( xSize + 4 ) };
|
uint xSize = (uint)(((from item in xParams
|
||||||
}
|
let xQSize = Align(GetFieldStorageSize(item.GetType()), 4)
|
||||||
}
|
select (int)xQSize).Take(xParams.Length - 1).Sum()));
|
||||||
|
|
||||||
new CPUx86.Call { DestinationLabel = MethodInfoLabelGenerator.GenerateLabelName( xMethod.Value ) };
|
for (int i = 1; i < xParams.Length; i++)
|
||||||
new CPUx86.Test { DestinationReg = CPUx86.Registers.ECX, SourceValue = 2 };
|
{
|
||||||
//new CPUx86.ConditionalJump { Condition = CPUx86.ConditionalTestEnum.Equal, DestinationLabel = aCurrentLabel + "_NO_ERROR_4" };
|
new Comment(String.Format("Arg {0}: {1}", i, GetFieldStorageSize(xParams[i].GetType())));
|
||||||
throw new Exception("Notimpl");
|
for (int j = 0; j < GetFieldStorageSize(xParams[i].GetType()); j += 4)
|
||||||
|
{
|
||||||
|
new CPUx86.Push { DestinationReg = CPUx86.Registers.ESP, DestinationIsIndirect = true, DestinationDisplacement = (int)(xSize + 4) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//for( int i = 1; i < aCtorMethodInfo.Arguments.Length; i++ )
|
new CPUx86.Call { DestinationLabel = MethodInfoLabelGenerator.GenerateLabelName(xMethod.Value) };
|
||||||
//{
|
new CPUx86.Test { DestinationReg = CPUx86.Registers.ECX, SourceValue = 2 };
|
||||||
// new CPUx86.Add
|
new CPUx86.ConditionalJump { Condition = CPUx86.ConditionalTestEnum.Equal, DestinationLabel = xCurrentLabel + "_NO_ERROR_4" };
|
||||||
// {
|
|
||||||
// DestinationReg = CPUx86.Registers.ESP,
|
|
||||||
// SourceValue = ( aCtorMethodInfo.Arguments[ i ].Size % 4 == 0
|
|
||||||
// ? aCtorMethodInfo.Arguments[ i ].Size
|
|
||||||
// : ( ( aCtorMethodInfo.Arguments[ i ].Size / 4 ) * 4 ) + 1 )
|
|
||||||
// };
|
|
||||||
//}
|
|
||||||
PushAlignedParameterSize( xMethod.Value );
|
|
||||||
|
|
||||||
new CPUx86.Add { DestinationReg = CPUx86.Registers.ESP, SourceValue = 4 };
|
//for( int i = 1; i < aCtorMethodInfo.Arguments.Length; i++ )
|
||||||
//foreach( StackContent xStackInt in Assembler.Stack )
|
//{
|
||||||
//{
|
// new CPUx86.Add
|
||||||
// new CPUx86.Add { DestinationReg = CPUx86.Registers.ESP, SourceValue = ( uint )xStackInt.Size };
|
// {
|
||||||
//}
|
// DestinationReg = CPUx86.Registers.ESP,
|
||||||
//Call.EmitExceptionLogic( aAssembler,
|
// SourceValue = ( aCtorMethodInfo.Arguments[ i ].Size % 4 == 0
|
||||||
// ( uint )aCurrentILOffset,
|
// ? aCtorMethodInfo.Arguments[ i ].Size
|
||||||
// aCurrentMethodInformation,
|
// : ( ( aCtorMethodInfo.Arguments[ i ].Size / 4 ) * 4 ) + 1 )
|
||||||
// aCurrentLabel + "_NO_ERROR_4",
|
// };
|
||||||
// false,
|
//}
|
||||||
// null );
|
PushAlignedParameterSize(xMethod.Value);
|
||||||
//new Label( aCurrentLabel + "_NO_ERROR_4" );
|
// an exception occurred, we need to cleanup the stack, and jump to the exit
|
||||||
new CPUx86.Pop { DestinationReg = CPUx86.Registers.EAX };
|
new CPUx86.Add { DestinationReg = CPUx86.Registers.ESP, SourceValue = 4 };
|
||||||
|
foreach (var xStackInt in Assembler.Stack)
|
||||||
|
{
|
||||||
|
new CPUx86.Add { DestinationReg = CPUx86.Registers.ESP, SourceValue = (uint)xStackInt.Size };
|
||||||
|
}
|
||||||
|
JumpToExceptionExit(aMethod);
|
||||||
|
|
||||||
//for( int i = 1; i < aCtorMethodInfo.Arguments.Length; i++ )
|
new Label(xCurrentLabel + "_NO_ERROR_4");
|
||||||
//{
|
new CPUx86.Pop { DestinationReg = CPUx86.Registers.EAX };
|
||||||
// new CPUx86.Add
|
|
||||||
// {
|
|
||||||
// DestinationReg = CPUx86.Registers.ESP,
|
|
||||||
// SourceValue = ( aCtorMethodInfo.Arguments[ i ].Size % 4 == 0
|
|
||||||
// ? aCtorMethodInfo.Arguments[ i ].Size
|
|
||||||
// : ( ( aCtorMethodInfo.Arguments[ i ].Size / 4 ) * 4 ) + 1 )
|
|
||||||
// };
|
|
||||||
//}
|
|
||||||
PushAlignedParameterSize( xMethod.Value );
|
|
||||||
|
|
||||||
new CPUx86.Push { DestinationReg = CPUx86.Registers.EAX };
|
//for( int i = 1; i < aCtorMethodInfo.Arguments.Length; i++ )
|
||||||
Assembler.Stack.Push( 4, xMethod.Value.DeclaringType );
|
//{
|
||||||
throw new NotImplementedException();
|
// new CPUx86.Add
|
||||||
}
|
// {
|
||||||
else
|
// DestinationReg = CPUx86.Registers.ESP,
|
||||||
{
|
// SourceValue = ( aCtorMethodInfo.Arguments[ i ].Size % 4 == 0
|
||||||
// /*
|
// ? aCtorMethodInfo.Arguments[ i ].Size
|
||||||
// * Current sitation on stack:
|
// : ( ( aCtorMethodInfo.Arguments[ i ].Size / 4 ) * 4 ) + 1 )
|
||||||
// * $ESP Arg
|
// };
|
||||||
// * $ESP+.. other items
|
//}
|
||||||
// *
|
PushAlignedParameterSize(xMethod.Value);
|
||||||
// * What should happen:
|
|
||||||
// * + The stack should be increased to allow space to contain:
|
|
||||||
// * + .ctor arguments
|
|
||||||
// * + struct _pointer_ (ref to start of emptied space)
|
|
||||||
// * + empty space for struct
|
|
||||||
// * + arguments should be copied to the new place
|
|
||||||
// * + old place where arguments were should be cleared
|
|
||||||
// * + pointer should be set
|
|
||||||
// * + call .ctor
|
|
||||||
// */
|
|
||||||
// var xStorageSize = aCtorDeclTypeInfo.StorageSize;
|
|
||||||
// if (xStorageSize % 4 != 0)
|
|
||||||
// {
|
|
||||||
// xStorageSize += 4 - (xStorageSize % 4);
|
|
||||||
// }
|
|
||||||
// uint xArgSize = 0;
|
|
||||||
// foreach (var xArg in aCtorMethodInfo.Arguments.Skip(1))
|
|
||||||
// {
|
|
||||||
// xArgSize += xArg.Size + (xArg.Size % 4 == 0
|
|
||||||
// ? 0
|
|
||||||
// : (4 - (xArg.Size % 4)));
|
|
||||||
// }
|
|
||||||
// int xExtraArgSize = (int)(xStorageSize - xArgSize);
|
|
||||||
// if (xExtraArgSize < 0)
|
|
||||||
// {
|
|
||||||
// xExtraArgSize = 0;
|
|
||||||
// }
|
|
||||||
// if (xExtraArgSize>0)
|
|
||||||
// {
|
|
||||||
// new CPUx86.Sub { DestinationReg = CPUx86.Registers.ESP, SourceValue = (uint)xExtraArgSize };
|
|
||||||
// }
|
|
||||||
// new CPUx86.Push { DestinationReg = Registers.ESP };
|
|
||||||
// aAssembler.Stack.Push(new StackContent(4));
|
|
||||||
// //at this point, we need to move copy all arguments over.
|
|
||||||
// for (int i = 0;i<(xArgSize/4);i++)
|
|
||||||
// {
|
|
||||||
// new CPUx86.Push { DestinationReg = Registers.ESP, DestinationIsIndirect = true, DestinationDisplacement = (int)(xStorageSize + 4) }; // + 4 because the ptr is pushed too
|
|
||||||
// new CPUx86.Move { DestinationReg = CPUx86.Registers.ESP, DestinationIsIndirect = true, DestinationDisplacement = (int)(xStorageSize + 4 + 4), SourceValue = 0, Size = 32 };
|
|
||||||
// }
|
|
||||||
// var xCall = new Call(aCtorDef,
|
|
||||||
// (uint)aCurrentILOffset,
|
|
||||||
// true,
|
|
||||||
// aNextLabel);
|
|
||||||
// xCall.SetServiceProvider(aServiceProvider);
|
|
||||||
// xCall.Assembler = aAssembler;
|
|
||||||
// xCall.Assemble();
|
|
||||||
// aAssembler.Stack.Push(new StackContent((int)xStorageSize,
|
|
||||||
// aCtorDef.DeclaringType));
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private uint Align( uint aSize, uint aAlign )
|
new CPUx86.Push { DestinationReg = CPUx86.Registers.EAX };
|
||||||
{
|
Assembler.Stack.Push(4, xMethod.Value.DeclaringType);
|
||||||
return aSize % 4 == 0 ? aSize : ( ( aSize / aAlign ) * aAlign ) + 1;
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
// /*
|
||||||
|
// * Current sitation on stack:
|
||||||
|
// * $ESP Arg
|
||||||
|
// * $ESP+.. other items
|
||||||
|
// *
|
||||||
|
// * What should happen:
|
||||||
|
// * + The stack should be increased to allow space to contain:
|
||||||
|
// * + .ctor arguments
|
||||||
|
// * + struct _pointer_ (ref to start of emptied space)
|
||||||
|
// * + empty space for struct
|
||||||
|
// * + arguments should be copied to the new place
|
||||||
|
// * + old place where arguments were should be cleared
|
||||||
|
// * + pointer should be set
|
||||||
|
// * + call .ctor
|
||||||
|
// */
|
||||||
|
// var xStorageSize = aCtorDeclTypeInfo.StorageSize;
|
||||||
|
// if (xStorageSize % 4 != 0)
|
||||||
|
// {
|
||||||
|
// xStorageSize += 4 - (xStorageSize % 4);
|
||||||
|
// }
|
||||||
|
// uint xArgSize = 0;
|
||||||
|
// foreach (var xArg in aCtorMethodInfo.Arguments.Skip(1))
|
||||||
|
// {
|
||||||
|
// xArgSize += xArg.Size + (xArg.Size % 4 == 0
|
||||||
|
// ? 0
|
||||||
|
// : (4 - (xArg.Size % 4)));
|
||||||
|
// }
|
||||||
|
// int xExtraArgSize = (int)(xStorageSize - xArgSize);
|
||||||
|
// if (xExtraArgSize < 0)
|
||||||
|
// {
|
||||||
|
// xExtraArgSize = 0;
|
||||||
|
// }
|
||||||
|
// if (xExtraArgSize>0)
|
||||||
|
// {
|
||||||
|
// new CPUx86.Sub { DestinationReg = CPUx86.Registers.ESP, SourceValue = (uint)xExtraArgSize };
|
||||||
|
// }
|
||||||
|
// new CPUx86.Push { DestinationReg = Registers.ESP };
|
||||||
|
// aAssembler.Stack.Push(new StackContent(4));
|
||||||
|
// //at this point, we need to move copy all arguments over.
|
||||||
|
// for (int i = 0;i<(xArgSize/4);i++)
|
||||||
|
// {
|
||||||
|
// new CPUx86.Push { DestinationReg = Registers.ESP, DestinationIsIndirect = true, DestinationDisplacement = (int)(xStorageSize + 4) }; // + 4 because the ptr is pushed too
|
||||||
|
// new CPUx86.Move { DestinationReg = CPUx86.Registers.ESP, DestinationIsIndirect = true, DestinationDisplacement = (int)(xStorageSize + 4 + 4), SourceValue = 0, Size = 32 };
|
||||||
|
// }
|
||||||
|
// var xCall = new Call(aCtorDef,
|
||||||
|
// (uint)aCurrentILOffset,
|
||||||
|
// true,
|
||||||
|
// aNextLabel);
|
||||||
|
// xCall.SetServiceProvider(aServiceProvider);
|
||||||
|
// xCall.Assembler = aAssembler;
|
||||||
|
// xCall.Assemble();
|
||||||
|
// aAssembler.Stack.Push(new StackContent((int)xStorageSize,
|
||||||
|
// aCtorDef.DeclaringType));
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void PushAlignedParameterSize( System.Reflection.MethodBase aMethod )
|
private void JumpToExceptionExit(MethodInfo aMethod)
|
||||||
{
|
{
|
||||||
System.Reflection.ParameterInfo[] xParams = aMethod.GetParameters();
|
// todo: port to numeric labels
|
||||||
|
new CPUx86.Jump{DestinationLabel= MethodInfoLabelGenerator.GenerateLabelName(aMethod.MethodBase) + "___EXCEPTION___EXIT"};
|
||||||
|
}
|
||||||
|
|
||||||
uint xSize;
|
private void JumpToExit(MethodInfo aMethod)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
for( int i = 1; i < xParams.Length; i++ )
|
private uint Align(uint aSize, uint aAlign)
|
||||||
{
|
{
|
||||||
xSize = GetFieldStorageSize( xParams[ i ].GetType() );
|
return aSize % 4 == 0 ? aSize : ((aSize / aAlign) * aAlign) + 1;
|
||||||
new CPUx86.Add { DestinationReg = CPUx86.Registers.ESP, SourceValue = Align( xSize, 4 ) };
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
// using System.Collections.Generic;
|
|
||||||
// using System.Diagnostics;
|
|
||||||
// using System.Linq;
|
|
||||||
// using System.Reflection;
|
|
||||||
// using Indy.IL2CPU.Assembler;
|
|
||||||
// using Indy.IL2CPU.Assembler.X86;
|
|
||||||
// using CPU=Indy.IL2CPU.Assembler;
|
|
||||||
// using CPUx86=Indy.IL2CPU.Assembler.X86;
|
|
||||||
// using Asm=Indy.IL2CPU.Assembler;
|
|
||||||
// using Assembler=Indy.IL2CPU.Assembler.Assembler;
|
|
||||||
// using Indy.IL2CPU.Compiler;
|
|
||||||
//
|
|
||||||
// namespace Indy.IL2CPU.IL.X86
|
|
||||||
// {
|
|
||||||
// [OpCode(OpCodeEnum.Newobj)]
|
|
||||||
// public class Newobj : Op
|
|
||||||
// {
|
|
||||||
// public MethodBase CtorDef;
|
|
||||||
// public string CurrentLabel;
|
|
||||||
// public uint ILOffset;
|
|
||||||
// public MethodInformation MethodInformation;
|
|
||||||
//
|
|
||||||
// public static void ScanOp(MethodBase aCtor, IServiceProvider aProvider)
|
|
||||||
// {
|
|
||||||
// Call.ScanOp(aCtor, aProvider);
|
|
||||||
// Call.ScanOp(GCImplementationRefs.AllocNewObjectRef, aProvider);
|
|
||||||
// Call.ScanOp(CPU.Assembler.CurrentExceptionOccurredRef, aProvider);
|
|
||||||
// Call.ScanOp(GCImplementationRefs.IncRefCountRef, aProvider);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public static void ScanOp(ILReader aReader, MethodInformation aMethodInfo, SortedList<string, object> aMethodData, IServiceProvider aProvider)
|
|
||||||
// {
|
|
||||||
// var xCtorDef = aReader.OperandValueMethod;
|
|
||||||
// ScanOp(xCtorDef, aProvider);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public Newobj(ILReader aReader,
|
|
||||||
// MethodInformation aMethodInfo)
|
|
||||||
// : base(aReader,
|
|
||||||
// aMethodInfo)
|
|
||||||
// {
|
|
||||||
// CtorDef = aReader.OperandValueMethod;
|
|
||||||
// CurrentLabel = GetInstructionLabel(aReader);
|
|
||||||
// MethodInformation = aMethodInfo;
|
|
||||||
// ILOffset = aReader.Position;
|
|
||||||
// mNextLabel = GetInstructionLabel(aReader.NextPosition);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private string mNextLabel;
|
|
||||||
// }
|
|
||||||
|
|
||||||
//TODO: Likely this is used by other things besides newobj
|
private void PushAlignedParameterSize(System.Reflection.MethodBase aMethod)
|
||||||
// and thus should be moved to a different location
|
{
|
||||||
private uint GetFieldStorageSize( Type aType )
|
System.Reflection.ParameterInfo[] xParams = aMethod.GetParameters();
|
||||||
{
|
|
||||||
if( aType.FullName == "System.Void" )
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
else if( ( !aType.IsValueType && aType.IsClass ) || aType.IsInterface )
|
|
||||||
{
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
switch( aType.FullName )
|
|
||||||
{
|
|
||||||
case "System.Char":
|
|
||||||
return 2;
|
|
||||||
case "System.Byte":
|
|
||||||
case "System.SByte":
|
|
||||||
return 1;
|
|
||||||
case "System.UInt16":
|
|
||||||
case "System.Int16":
|
|
||||||
return 2;
|
|
||||||
case "System.UInt32":
|
|
||||||
case "System.Int32":
|
|
||||||
return 4;
|
|
||||||
case "System.UInt64":
|
|
||||||
case "System.Int64":
|
|
||||||
return 8;
|
|
||||||
//TODO: for now hardcode IntPtr and UIntPtr to be 32-bit
|
|
||||||
case "System.UIntPtr":
|
|
||||||
case "System.IntPtr":
|
|
||||||
return 4;
|
|
||||||
case "System.Boolean":
|
|
||||||
return 1;
|
|
||||||
case "System.Single":
|
|
||||||
return 4;
|
|
||||||
case "System.Double":
|
|
||||||
return 8;
|
|
||||||
case "System.Decimal":
|
|
||||||
return 16;
|
|
||||||
case "System.Guid":
|
|
||||||
return 16;
|
|
||||||
case "System.Enum":
|
|
||||||
return 4;
|
|
||||||
case "System.DateTime":
|
|
||||||
return 8;
|
|
||||||
}
|
|
||||||
if( aType.FullName != null && aType.FullName.EndsWith( "*" ) )
|
|
||||||
{
|
|
||||||
// pointer
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
// array
|
|
||||||
//TypeSpecification xTypeSpec = aType as TypeSpecification;
|
|
||||||
//if (xTypeSpec != null) {
|
|
||||||
// return 4;
|
|
||||||
//}
|
|
||||||
if( aType.IsEnum )
|
|
||||||
{
|
|
||||||
return GetFieldStorageSize( aType.GetField( "value__" ).FieldType );
|
|
||||||
}
|
|
||||||
if( aType.IsValueType )
|
|
||||||
{
|
|
||||||
var xSla = aType.StructLayoutAttribute;
|
|
||||||
if( xSla != null )
|
|
||||||
{
|
|
||||||
if( xSla.Size > 0 )
|
|
||||||
{
|
|
||||||
return ( uint )xSla.Size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uint xResult;
|
|
||||||
//GetTypeFieldInfo(aType, out xResult);
|
|
||||||
throw new Exception( "TODO" );
|
|
||||||
return xResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
uint xSize;
|
||||||
|
|
||||||
|
for (int i = 1; i < xParams.Length; i++)
|
||||||
|
{
|
||||||
|
xSize = GetFieldStorageSize(xParams[i].GetType());
|
||||||
|
new CPUx86.Add { DestinationReg = CPUx86.Registers.ESP, SourceValue = Align(xSize, 4) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// using System.Collections.Generic;
|
||||||
|
// using System.Diagnostics;
|
||||||
|
// using System.Linq;
|
||||||
|
// using System.Reflection;
|
||||||
|
// using Indy.IL2CPU.Assembler;
|
||||||
|
// using Indy.IL2CPU.Assembler.X86;
|
||||||
|
// using CPU=Indy.IL2CPU.Assembler;
|
||||||
|
// using CPUx86=Indy.IL2CPU.Assembler.X86;
|
||||||
|
// using Asm=Indy.IL2CPU.Assembler;
|
||||||
|
// using Assembler=Indy.IL2CPU.Assembler.Assembler;
|
||||||
|
// using Indy.IL2CPU.Compiler;
|
||||||
|
//
|
||||||
|
// namespace Indy.IL2CPU.IL.X86
|
||||||
|
// {
|
||||||
|
// [OpCode(OpCodeEnum.Newobj)]
|
||||||
|
// public class Newobj : Op
|
||||||
|
// {
|
||||||
|
// public MethodBase CtorDef;
|
||||||
|
// public string CurrentLabel;
|
||||||
|
// public uint ILOffset;
|
||||||
|
// public MethodInformation MethodInformation;
|
||||||
|
//
|
||||||
|
// public static void ScanOp(MethodBase aCtor, IServiceProvider aProvider)
|
||||||
|
// {
|
||||||
|
// Call.ScanOp(aCtor, aProvider);
|
||||||
|
// Call.ScanOp(GCImplementationRefs.AllocNewObjectRef, aProvider);
|
||||||
|
// Call.ScanOp(CPU.Assembler.CurrentExceptionOccurredRef, aProvider);
|
||||||
|
// Call.ScanOp(GCImplementationRefs.IncRefCountRef, aProvider);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public static void ScanOp(ILReader aReader, MethodInformation aMethodInfo, SortedList<string, object> aMethodData, IServiceProvider aProvider)
|
||||||
|
// {
|
||||||
|
// var xCtorDef = aReader.OperandValueMethod;
|
||||||
|
// ScanOp(xCtorDef, aProvider);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public Newobj(ILReader aReader,
|
||||||
|
// MethodInformation aMethodInfo)
|
||||||
|
// : base(aReader,
|
||||||
|
// aMethodInfo)
|
||||||
|
// {
|
||||||
|
// CtorDef = aReader.OperandValueMethod;
|
||||||
|
// CurrentLabel = GetInstructionLabel(aReader);
|
||||||
|
// MethodInformation = aMethodInfo;
|
||||||
|
// ILOffset = aReader.Position;
|
||||||
|
// mNextLabel = GetInstructionLabel(aReader.NextPosition);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private string mNextLabel;
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,29 +1,61 @@
|
||||||
using System;
|
using System;
|
||||||
|
using Cosmos.IL2CPU.ILOpCodes;
|
||||||
|
using CPUx86 = Indy.IL2CPU.Assembler.X86;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using Indy.IL2CPU;
|
||||||
|
|
||||||
namespace Cosmos.IL2CPU.X86.IL
|
namespace Cosmos.IL2CPU.X86.IL
|
||||||
{
|
{
|
||||||
[Cosmos.IL2CPU.OpCode(ILOpCode.Code.Stloc)]
|
[Cosmos.IL2CPU.OpCode(ILOpCode.Code.Stloc)]
|
||||||
public class Stloc: ILOp
|
public class Stloc : ILOp
|
||||||
{
|
{
|
||||||
public Stloc(Cosmos.IL2CPU.Assembler aAsmblr):base(aAsmblr)
|
public Stloc(Cosmos.IL2CPU.Assembler aAsmblr)
|
||||||
|
: base(aAsmblr)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Execute(MethodInfo aMethod, ILOpCode aOpCode) {
|
private uint GetStackCount(MethodInfo aMethod, LocalVariableInfo aField)
|
||||||
throw new NotImplementedException();
|
{
|
||||||
}
|
var xSize = GetFieldStorageSize(aField.LocalType);
|
||||||
|
var xResult = xSize / 4;
|
||||||
|
if (xSize % 4 == 0)
|
||||||
|
{
|
||||||
|
xResult++;
|
||||||
|
}
|
||||||
|
return xResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint GetEBPOffsetForLocal(MethodInfo aMethod, OpVar aOp)
|
||||||
|
{
|
||||||
|
var xBody = aMethod.MethodBase.GetMethodBody();
|
||||||
|
uint xOffset = 0;
|
||||||
|
foreach (var xField in xBody.LocalVariables)
|
||||||
|
{
|
||||||
|
xOffset += GetStackCount(aMethod, xField);
|
||||||
|
}
|
||||||
|
return xOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Execute(MethodInfo aMethod, ILOpCode aOpCode)
|
||||||
|
{
|
||||||
|
var xField = aOpCode as ILOpCodes.OpVar;
|
||||||
|
var xFieldInfo = aMethod.MethodBase.GetMethodBody().LocalVariables[xField.Value];
|
||||||
|
var xEBPOffset = GetEBPOffsetForLocal(aMethod, xField);
|
||||||
|
if (!xFieldInfo.LocalType.IsValueType)
|
||||||
|
{
|
||||||
|
new CPUx86.Push { DestinationReg = CPUx86.Registers.EBP, DestinationIsIndirect = true, DestinationDisplacement =(int) xEBPOffset };
|
||||||
|
new CPUx86.Call { DestinationLabel = MethodInfoLabelGenerator.GenerateLabelName(GCImplementationRefs.DecRefCountRef) };
|
||||||
|
}
|
||||||
|
for (int i = (int)GetStackCount(aMethod, xFieldInfo) - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
new CPUx86.Pop { DestinationReg = CPUx86.Registers.EAX }; ;
|
||||||
|
new CPUx86.Move { DestinationReg = CPUx86.Registers.EBP, DestinationIsIndirect = true, DestinationDisplacement =(int)( xEBPOffset + (i * 4)), SourceReg = CPUx86.Registers.EAX };
|
||||||
|
}
|
||||||
|
// no need to inc again, items on the transient stack are also counted
|
||||||
|
Assembler.Stack.Pop();
|
||||||
|
}
|
||||||
|
|
||||||
// using System;
|
|
||||||
// using System.Linq;
|
|
||||||
// using Indy.IL2CPU.Assembler;
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// using CPUx86 = Indy.IL2CPU.Assembler.X86;
|
|
||||||
//
|
|
||||||
// namespace Indy.IL2CPU.IL.X86 {
|
|
||||||
// [OpCode(OpCodeEnum.Stloc)]
|
|
||||||
// public class Stloc: Op {
|
|
||||||
// private bool mNeedsGC = false;
|
// private bool mNeedsGC = false;
|
||||||
// private MethodInformation.Variable mLocal;
|
// private MethodInformation.Variable mLocal;
|
||||||
// private string mBaseLabel;
|
// private string mBaseLabel;
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,10 @@ namespace Cosmos.IL2CPU.X86
|
||||||
{
|
{
|
||||||
Assembler = ( Assembler )aAsmblr;
|
Assembler = ( Assembler )aAsmblr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected string GetLabel(MethodInfo aMethod, ILOpCode aOpCode)
|
||||||
|
{
|
||||||
|
return MethodInfoLabelGenerator.GenerateLabelName(aMethod.MethodBase) + "__DOT__" + aOpCode.Position.ToString("X8").ToUpper();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ namespace Cosmos.IL2CPU {
|
||||||
protected ILOp[] mILOpsHi = new ILOp[ 256 ];
|
protected ILOp[] mILOpsHi = new ILOp[ 256 ];
|
||||||
|
|
||||||
// Contains info on the current stack structure. What type are on the stack, etc
|
// Contains info on the current stack structure. What type are on the stack, etc
|
||||||
public readonly StackContents Stack = new StackContents();
|
public readonly StackContents Stack = new StackContents();
|
||||||
|
|
||||||
private static ReaderWriterLocker mCurrentInstanceLocker = new ReaderWriterLocker();
|
private static ReaderWriterLocker mCurrentInstanceLocker = new ReaderWriterLocker();
|
||||||
private static SortedList<int, Stack<Assembler>> mCurrentInstance = new SortedList<int, Stack<Assembler>>();
|
private static SortedList<int, Stack<Assembler>> mCurrentInstance = new SortedList<int, Stack<Assembler>>();
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@
|
||||||
<Compile Include="ILReader.cs" />
|
<Compile Include="ILReader.cs" />
|
||||||
<Compile Include="Instruction.cs" />
|
<Compile Include="Instruction.cs" />
|
||||||
<Compile Include="Label.cs" />
|
<Compile Include="Label.cs" />
|
||||||
|
<Compile Include="MethodAndTypeLabelsHolder.cs" />
|
||||||
<Compile Include="MethodInfo.cs" />
|
<Compile Include="MethodInfo.cs" />
|
||||||
<Compile Include="MethodInfoLabelGenerator.cs" />
|
<Compile Include="MethodInfoLabelGenerator.cs" />
|
||||||
<Compile Include="OpCodeAttribute.cs" />
|
<Compile Include="OpCodeAttribute.cs" />
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,80 @@ namespace Cosmos.IL2CPU
|
||||||
// could be used for other things, profiling, analysis, reporting, etc
|
// could be used for other things, profiling, analysis, reporting, etc
|
||||||
public abstract void Execute( MethodInfo aMethod, ILOpCode aOpCode );
|
public abstract void Execute( MethodInfo aMethod, ILOpCode aOpCode );
|
||||||
|
|
||||||
|
protected static uint GetFieldStorageSize(Type aType)
|
||||||
|
{
|
||||||
|
if (aType.FullName == "System.Void")
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if ((!aType.IsValueType && aType.IsClass) || aType.IsInterface)
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
switch (aType.FullName)
|
||||||
|
{
|
||||||
|
case "System.Char":
|
||||||
|
return 2;
|
||||||
|
case "System.Byte":
|
||||||
|
case "System.SByte":
|
||||||
|
return 1;
|
||||||
|
case "System.UInt16":
|
||||||
|
case "System.Int16":
|
||||||
|
return 2;
|
||||||
|
case "System.UInt32":
|
||||||
|
case "System.Int32":
|
||||||
|
return 4;
|
||||||
|
case "System.UInt64":
|
||||||
|
case "System.Int64":
|
||||||
|
return 8;
|
||||||
|
//TODO: for now hardcode IntPtr and UIntPtr to be 32-bit
|
||||||
|
case "System.UIntPtr":
|
||||||
|
case "System.IntPtr":
|
||||||
|
return 4;
|
||||||
|
case "System.Boolean":
|
||||||
|
return 1;
|
||||||
|
case "System.Single":
|
||||||
|
return 4;
|
||||||
|
case "System.Double":
|
||||||
|
return 8;
|
||||||
|
case "System.Decimal":
|
||||||
|
return 16;
|
||||||
|
case "System.Guid":
|
||||||
|
return 16;
|
||||||
|
case "System.Enum":
|
||||||
|
return 4;
|
||||||
|
case "System.DateTime":
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
if (aType.FullName != null && aType.FullName.EndsWith("*"))
|
||||||
|
{
|
||||||
|
// pointer
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
// array
|
||||||
|
//TypeSpecification xTypeSpec = aType as TypeSpecification;
|
||||||
|
//if (xTypeSpec != null) {
|
||||||
|
// return 4;
|
||||||
|
//}
|
||||||
|
if (aType.IsEnum)
|
||||||
|
{
|
||||||
|
return GetFieldStorageSize(aType.GetField("value__").FieldType);
|
||||||
|
}
|
||||||
|
if (aType.IsValueType)
|
||||||
|
{
|
||||||
|
var xSla = aType.StructLayoutAttribute;
|
||||||
|
if (xSla != null)
|
||||||
|
{
|
||||||
|
if (xSla.Size > 0)
|
||||||
|
{
|
||||||
|
return (uint)xSla.Size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint xResult;
|
||||||
|
//GetTypeFieldInfo(aType, out xResult);
|
||||||
|
throw new Exception("TODO");
|
||||||
|
return xResult;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
source2/IL2PCU/Cosmos.IL2CPU/MethodAndTypeLabelsHolder.cs
Normal file
12
source2/IL2PCU/Cosmos.IL2CPU/MethodAndTypeLabelsHolder.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Cosmos.IL2CPU
|
||||||
|
{
|
||||||
|
public static class MethodAndTypeLabelsHolder
|
||||||
|
{
|
||||||
|
//public static readonly uint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -80,5 +80,13 @@ namespace Cosmos.IL2CPU {
|
||||||
mStack.Push(new Item(aSize, aIsNumber, aIsFloat, aIsSigned));
|
mStack.Push(new Item(aSize, aIsNumber, aIsFloat, aIsSigned));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerator<Item> GetEnumerator()
|
||||||
|
{
|
||||||
|
foreach (var item in mStack)
|
||||||
|
{
|
||||||
|
yield return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue