private static IILArgumentsProvider BuildPushrExpression(ILInstruction instruction, ILCompilationUnit result) { // Since we treat registers as variables, we should interpret the operand as a variable and add it // as an argument to the expression instead of keeping it just as an operand. This makes it easier // to apply analysis and transformations (such as variable inlining) later, in the same way we do // that with normal variables. IILArgumentsProvider expression = new ILInstructionExpression(instruction); ILVariable registerVar; if (instruction.Operand is VMRegisters.FL) { var dataSources = instruction.ProgramState.Registers[VMRegisters.FL].DataSources .Select(i => i.Offset) .ToArray(); var flagsVariable = result.GetOrCreateFlagsVariable(dataSources); registerVar = flagsVariable; } else { registerVar = result.GetOrCreateVariable(instruction.Operand.ToString()); } var varExpression = new ILVariableExpression(registerVar); expression.Arguments.Add(varExpression); return(expression); }
private static void IntroduceRegisterVariables(ILCompilationUnit result) { for (int i = 0; i < (int)VMRegisters.Max; i++) { var register = (VMRegisters)i; var registerVar = result.GetOrCreateVariable(register.ToString()); registerVar.VariableType = register == VMRegisters.FL ? VMType.Byte : VMType.Object; } }
private ILExpression BuildExpression(ILInstruction instruction, ILCompilationUnit result) { IILArgumentsProvider expression; switch (instruction.OpCode.Code) { case ILCode.VCALL: expression = new ILVCallExpression((VCallAnnotation)instruction.Annotation); break; case ILCode.PUSHR_BYTE: case ILCode.PUSHR_WORD: case ILCode.PUSHR_DWORD: case ILCode.PUSHR_QWORD: case ILCode.PUSHR_OBJECT: expression = BuildPushrExpression(instruction, result); break; default: expression = new ILInstructionExpression(instruction); break; } for (int i = 0; i < instruction.Dependencies.Count; i++) { ILExpression argument; var firstDataSource = instruction.Dependencies[i].DataSources.First(); if (firstDataSource.Offset == InstructionProcessor.PushExceptionOffset) { var exceptionType = (ITypeDefOrRef)firstDataSource.Operand; argument = new ILExceptionExpression(exceptionType); } else { // Get the variable containing the value of the argument and add it as an argument to the expression. string variableName = GetOperandVariableName(instruction, i); if (_sharedVariables.TryGetValue(variableName, out string realName)) { variableName = realName; } argument = new ILVariableExpression( result.GetOrCreateVariable(variableName)); } expression.Arguments.Add(argument); } return((ILExpression)expression); }
private static ILStatement BuildCallStatement(ILCompilationUnit result, ILInstruction instruction, ILExpression expression) { // CALL instructions that call non-void methods store the result in R0. // TODO: Respect frame layout instead of hardcoding R0 as return value. var callAnnotation = (CallAnnotation)instruction.Annotation; var statement = callAnnotation.Function.FrameLayout.ReturnsValue ? (ILStatement) new ILAssignmentStatement( result.GetOrCreateVariable(nameof(VMRegisters.R0)), expression) : new ILExpressionStatement(expression); return(statement); }
private static ILStatement BuildPopStatement(ILCompilationUnit result, ILInstruction instruction, ILExpression expression) { // Since we treat registers as variables, we should treat POP instructions as assignment // statements instead of a normal ILExpressionStatement. This makes it easier to apply // analysis and transformations (such as variable inlining) later, in the same way we do // that with normal variables. var register = (VMRegisters)instruction.Operand; // Get the variable associated to the register. FL registers need special care, as they // are used in various optimisation stages. var registerVar = register == VMRegisters.FL ? result.GetOrCreateFlagsVariable(new[] { instruction.Offset }) : result.GetOrCreateVariable(register.ToString()); var value = (ILExpression)((IILArgumentsProvider)expression).Arguments[0].Remove(); return(new ILAssignmentStatement(registerVar, value)); }
private static ILStatement BuildRetStatement(ILCompilationUnit result, ILInstruction instruction, ILExpression expression) { // TODO: Respect frame layout instead of hardcoding R0 as return value. var returnExpr = (IILArgumentsProvider)expression; foreach (var use in expression.AcceptVisitor(VariableUsageCollector.Instance)) { use.Variable = null; } returnExpr.Arguments.Clear(); if (result.FrameLayout.ReturnsValue && !instruction.ProgramState.IgnoreExitKey) { var registerVar = result.GetOrCreateVariable(nameof(VMRegisters.R0)); returnExpr.Arguments.Add(new ILVariableExpression(registerVar)); } return(new ILExpressionStatement(expression)); }
private ILExpression BuildExpression(ILInstruction instruction, ILCompilationUnit result) { IILArgumentsProvider expression; switch (instruction.OpCode.Code) { case ILCode.VCALL: expression = new ILVCallExpression((VCallAnnotation)instruction.Annotation); break; case ILCode.PUSHR_BYTE: case ILCode.PUSHR_WORD: case ILCode.PUSHR_DWORD: case ILCode.PUSHR_QWORD: case ILCode.PUSHR_OBJECT: // Since we treat registers as variables, we should interpret the operand as a variable and add it // as an argument to the expression instead of keeping it just as an operand. This makes it easier // to apply analysis and transformations (such as variable inlining) later, in the same way we do // that with normal variables. expression = new ILInstructionExpression(instruction); ILVariable registerVar; if (instruction.Operand is VMRegisters.FL) { var dataSources = instruction.ProgramState.Registers[VMRegisters.FL].DataSources .Select(i => i.Offset) .ToArray(); var flagsVariable = result.GetOrCreateFlagsVariable(dataSources); registerVar = flagsVariable; } else { registerVar = result.GetOrCreateVariable(instruction.Operand.ToString()); } var varExpression = new ILVariableExpression(registerVar); expression.Arguments.Add(varExpression); break; default: expression = new ILInstructionExpression(instruction); break; } for (int i = 0; i < instruction.Dependencies.Count; i++) { ILExpression argument; var firstDataSource = instruction.Dependencies[i].DataSources.First(); if (firstDataSource.Offset == InstructionProcessor.PushExceptionOffset) { var exceptionType = (ITypeDefOrRef)firstDataSource.Operand; argument = new ILExceptionExpression(exceptionType); } else { // Get the variable containing the value of the argument and add it as an argument to the expression. string variableName = GetOperandVariableName(instruction, i); if (_sharedVariables.TryGetValue(variableName, out string realName)) { variableName = realName; } argument = new ILVariableExpression( result.GetOrCreateVariable(variableName)); } expression.Arguments.Add(argument); } return((ILExpression)expression); }
private void BuildAstBlocks(ILCompilationUnit result, IDictionary <int, ILVariable> resultVariables) { foreach (var node in result.ControlFlowGraph.Nodes) { var ilBlock = (ILBasicBlock)node.UserData[ILBasicBlock.BasicBlockProperty]; var astBlock = new ILAstBlock(node); foreach (var instruction in ilBlock.Instructions) { // Build expression. var expression = BuildExpression(instruction, result); switch (instruction.OpCode.Code) { case ILCode.POP: { // Since we treat registers as variables, we should treat POP instructions as assignment // statements instead of a normal ILExpressionStatement. This makes it easier to apply // analysis and transformations (such as variable inlining) later, in the same way we do // that with normal variables. var registerVar = result.GetOrCreateVariable(instruction.Operand.ToString()); var value = (ILExpression)((IILArgumentsProvider)expression).Arguments[0].Remove(); var assignment = new ILAssignmentStatement(registerVar, value); astBlock.Statements.Add(assignment); break; } case ILCode.CALL: { // CALL instructions that call non-void methods store the result in R0. // TODO: Respect frame layout instead of hardcoding R0 as return value. var callAnnotation = (CallAnnotation)instruction.Annotation; var statement = callAnnotation.Function.FrameLayout.ReturnsValue ? (ILStatement) new ILAssignmentStatement( result.GetOrCreateVariable(VMRegisters.R0.ToString()), expression) : new ILExpressionStatement(expression); astBlock.Statements.Add(statement); break; } case ILCode.RET: { // TODO: Respect frame layout instead of hardcoding R0 as return value. var returnExpr = (IILArgumentsProvider)expression; foreach (var use in expression.AcceptVisitor(VariableUsageCollector.Instance)) { use.Variable = null; } returnExpr.Arguments.Clear(); if (result.FrameLayout.ReturnsValue && !instruction.ProgramState.IgnoreExitKey) { var registerVar = result.GetOrCreateVariable(VMRegisters.R0.ToString()); returnExpr.Arguments.Add(new ILVariableExpression(registerVar)); } astBlock.Statements.Add(new ILExpressionStatement(expression)); break; } default: { // Build statement around expression. var statement = resultVariables.TryGetValue(instruction.Offset, out var resultVariable) ? (ILStatement) new ILAssignmentStatement(resultVariable, expression) : new ILExpressionStatement(expression); astBlock.Statements.Add(statement); break; } } } node.UserData[ILAstBlock.AstBlockProperty] = astBlock; } }
private IDictionary <int, ILVariable> IntroduceResultVariables(ILCompilationUnit result) { // Determine result variables based on where the value is used by other instructions. // Find for each instruction the dependent instructions and assign to each of those dependent instructions // the same variable. var resultVariables = new Dictionary <int, ILVariable>(); var instructions = result.ControlFlowGraph.Nodes .Select(x => (ILBasicBlock)x.UserData[ILBasicBlock.BasicBlockProperty]) .SelectMany(x => x.Instructions); foreach (var instruction in instructions) { if (instruction.OpCode.Code == ILCode.CALL) { // Calls have implicit dependencies to the parameters pushed onto the stack. // We need to add them for the expression builder to work properly. var callAnnotation = (CallAnnotation)instruction.Annotation; int parametersCount = callAnnotation.Function.FrameLayout.Parameters.Count; // Create a copy of the stack and pop the first dependency (the call address) from it. var stackCopy = instruction.ProgramState.Stack.Copy(); stackCopy.Pop(); // TODO: Respect the frame layout rather than hardcoding it. // Pop all arguments from the stack. var arguments = new SymbolicValue[parametersCount]; for (int i = parametersCount - 1; i >= 0; i--) { arguments[i] = stackCopy.Pop(); } // Add new dependencies. for (int i = 0; i < parametersCount; i++) { instruction.Dependencies.AddOrMerge(i + 1, arguments[i]); } } for (int i = 0; i < instruction.Dependencies.Count; i++) { var dep = instruction.Dependencies[i]; // Check if the pushed value already has a result variable assigned to it. var resultVar = GetExistingVariableForStackSlot(resultVariables, dep); string variableName = GetOperandVariableName(instruction, i); if (resultVar == null) { // Introduce variable for dependency. resultVar = result.GetOrCreateVariable(variableName); resultVar.VariableType = dep.Type; // Assign this variable to all instructions that determine the value of this dependency. foreach (var source in dep.DataSources) { resultVariables[source.Offset] = resultVar; } } else { _sharedVariables[variableName] = resultVar.Name; } } } return(resultVariables); }