看到ILRuntime介绍的时候,一直好奇寄存器模式到底是干什么的,十分迫切的看起了源码
这里只讲寄存器模式的代码如何运行,如果对il指令不太了解的可以上我上一篇
ILRuntime是通过加载dll运行的,也就是说代码不会被转成il2cpp
public void LoadAssembly(System.IO.Stream stream, System.IO.Stream symbol, ISymbolReaderProvider symbolReader)
{
var module = ModuleDefinition.ReadModule(stream); //从MONO中加载模块
if (symbolReader != null && symbol != null)
{
module.ReadSymbols(symbolReader.GetSymbolReader(module, symbol)); //加载符号表
}
if (module.HasTypes)
{
List<ILType> types = new List<ILType>();
foreach (var t in module.GetTypes()) //获取所有此模块定义的类型
{
ILType type = new ILType(t, this);
mapType[t.FullName] = type;
mapTypeToken[type.GetHashCode()] = type;
types.Add(type);
}
}
......
}
加载后,把所有类转成ILType,里面的方法转成ILMethod,CLR绑定转成CLRType,方法转成CLRMethod
internal void InitCodeBody(bool register)
{
if (def.HasBody)
{
localVarCnt = def.Body.Variables.Count;
Dictionary<Mono.Cecil.Cil.Instruction, int> addr = new Dictionary<Mono.Cecil.Cil.Instruction, int>();
bool noRelease = false;
if (register)
{
JITCompiler jit = new JITCompiler(appdomain, declaringType, this);
bodyRegister = jit.Compile(out stackRegisterCnt, out jumptablesR, addr, out registerSymbols);
}
......
}
}
调用ShouldUseRegisterVM会初始化body,如果使用寄存器模式,就创建JITCompiler,生成寄存器指令
例如下面的C#源码
private static int TestA(int a,int b)
{
int c = a + b;
int d = c * 100;
return d;
}
转成il指令是这样子
ILRuntime用寄存器指令表示,大大优化了指令的数量,而且只生成一次,第二次使用直接运行指令,效率大大提升
也是模拟了mono jit编译
那指令是如何转换的呢,就在JITCompiler里面的
void Translate(CodeBasicBlock block, Instruction ins, short locVarRegStart, ref short baseRegIdx)
会把所有指令转换成OpCodeR
struct OpCodeR
{
[FieldOffset(0)]
public OpCodeREnum Code;
[FieldOffset(4)]
public short Register1;
[FieldOffset(6)]
public short Register2;
[FieldOffset(8)]
public short Register3;
[FieldOffset(10)]
public short Register4;
[FieldOffset(8)]
public int Operand;
[FieldOffset(8)]
public float OperandFloat;
[FieldOffset(12)]
public int Operand2;
[FieldOffset(16)]
public int Operand3;
[FieldOffset(12)]
public long OperandLong;
[FieldOffset(12)]
public double OperandDouble;
[FieldOffset(20)]
public int Operand4;
.......
}
首先会为每条需要存储值的指令分配一个寄存器索引,
并且把这类指令Code改成OpCodes.OpCodeREnum.Move
case Code.Ldarg_0:
case Code.Ldarg_1:
case Code.Ldarg_2:
case Code.Ldarg_3:
op.Code = OpCodes.OpCodeREnum.Move;
op.Register1 = baseRegIdx++;
op.Register2 = (short)(code.Code - (Code.Ldarg_0));
break;
case Code.Add:
case Code.Mul:
op.Register1 = (short)(baseRegIdx - 2); //explicit use dest register for optimization
op.Register2 = (short)(baseRegIdx - 2);
op.Register3 = (short)(baseRegIdx - 1);
baseRegIdx--;
break;
case Code.Stloc_0:
op.Code = OpCodes.OpCodeREnum.Move;
op.Register1 = locVarRegStart;
op.Register2 = --baseRegIdx;
break;
case Code.Ldloc_0:
op.Code = OpCodes.OpCodeREnum.Move;
op.Register1 = baseRegIdx++;
op.Register2 = locVarRegStart;
break;
case Code.Ldc_I4_S:
op.Register1 = baseRegIdx++;
op.Operand = (sbyte)token;
break;
转换完后,大概是这样子,用寄存器r表示
Optimizer.ForwardCopyPropagation(blocks, hasReturn, baseRegStart);
Optimizer.BackwardsCopyPropagation(blocks, hasReturn, baseRegStart);
Optimizer.ForwardCopyPropagation(blocks, hasReturn, baseRegStart);
Optimizer.EliminateConstantLoad(blocks, hasReturn);
然后再通过优化剔除不需要的指令,比如int c = a + b;用堆栈的话就需要把a推入堆栈,b推入堆栈,然后相加,再把结果存储到c中。用寄存机模式,就不要把a和b推入堆栈了,调用函数的时候已经在分配的寄存器中,一条指令搞定了以前4条指令需要做的事情,效率大幅提升
如果觉得运行时生成指令比较卡顿,ILRuntime也提供了预热机制,可以在空闲的时候初始化或生成指令
现在我们对ILRuntime寄存器模式有所了解了,也不是真的用了硬件层面的寄存器,而是一种模拟
看完ILRuntime源码感觉受益匪浅,代码写的很漂亮,这里只讲了一部份的内容,如果有兴趣还是很推荐阅读下源码的