// Copyright (c) 2010 Daniel Grunwald // // 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. using System; using System.Collections.Generic; using System.Diagnostics; using Mono.Cecil; using Mono.Cecil.Cil; namespace ICSharpCode.Decompiler.FlowAnalysis { /// /// Constructs "SsaForm" graph for a CFG. /// This class transforms the method from stack-based IL to a register-based IL language. /// Then it calls into TransformToSsa to convert the resulting graph to static single assignment form. /// public sealed class SsaFormBuilder { public static SsaForm Build(MethodDefinition method) { if (method == null) throw new ArgumentNullException("method"); var cfg = ControlFlowGraphBuilder.Build(method.Body); cfg.ComputeDominance(); cfg.ComputeDominanceFrontier(); var ssa = BuildRegisterIL(method, cfg); TransformToSsa.Transform(cfg, ssa); return ssa; } public static SsaForm BuildRegisterIL(MethodDefinition method, ControlFlowGraph cfg) { if (method == null) throw new ArgumentNullException("method"); if (cfg == null) throw new ArgumentNullException("cfg"); return new SsaFormBuilder(method, cfg).Build(); } readonly MethodDefinition method; readonly ControlFlowGraph cfg; readonly SsaBlock[] blocks; // array index = block index readonly int[] stackSizeAtBlockStart; // array index = block index readonly SsaVariable[] parameters; // array index = parameter number readonly SsaVariable[] locals; // array index = local number readonly SsaVariable[] stackLocations; // array index = position on the IL evaluation stack SsaForm ssaForm; private SsaFormBuilder(MethodDefinition method, ControlFlowGraph cfg) { this.method = method; this.cfg = cfg; this.blocks = new SsaBlock[cfg.Nodes.Count]; this.stackSizeAtBlockStart = new int[cfg.Nodes.Count]; for (int i = 0; i < stackSizeAtBlockStart.Length; i++) { stackSizeAtBlockStart[i] = -1; } stackSizeAtBlockStart[cfg.EntryPoint.BlockIndex] = 0; this.parameters = new SsaVariable[method.Parameters.Count + (method.HasThis ? 1 : 0)]; if (method.HasThis) parameters[0] = new SsaVariable(method.Body.ThisParameter); for (int i = 0; i < method.Parameters.Count; i++) parameters[i + (method.HasThis ? 1 : 0)] = new SsaVariable(method.Parameters[i]); this.locals = new SsaVariable[method.Body.Variables.Count]; for (int i = 0; i < locals.Length; i++) locals[i] = new SsaVariable(method.Body.Variables[i]); this.stackLocations = new SsaVariable[method.Body.MaxStackSize]; for (int i = 0; i < stackLocations.Length; i++) { stackLocations[i] = new SsaVariable(i); } } internal SsaForm Build() { CreateGraphStructure(); this.ssaForm = new SsaForm(blocks, parameters, locals, stackLocations, method.HasThis); CreateInstructions(cfg.EntryPoint.BlockIndex); CreateSpecialInstructions(); return ssaForm; } void CreateGraphStructure() { for (int i = 0; i < blocks.Length; i++) { blocks[i] = new SsaBlock(cfg.Nodes[i]); } for (int i = 0; i < blocks.Length; i++) { foreach (ControlFlowNode node in cfg.Nodes[i].Successors) { blocks[i].Successors.Add(blocks[node.BlockIndex]); blocks[node.BlockIndex].Predecessors.Add(blocks[i]); } } } void CreateInstructions(int blockIndex) { ControlFlowNode cfgNode = cfg.Nodes[blockIndex]; SsaBlock block = blocks[blockIndex]; int stackSize = stackSizeAtBlockStart[blockIndex]; Debug.Assert(stackSize >= 0); List prefixes = new List(); foreach (Instruction inst in cfgNode.Instructions) { if (inst.OpCode.OpCodeType == OpCodeType.Prefix) { prefixes.Add(inst); continue; } int popCount = inst.GetPopDelta() ?? stackSize; stackSize -= popCount; if (stackSize < 0) throw new InvalidProgramException("IL stack underflow"); int pushCount = inst.GetPushDelta(); if (stackSize + pushCount > stackLocations.Length) throw new InvalidProgramException("IL stack overflow"); SsaVariable target; SsaVariable[] operands; DetermineOperands(stackSize, inst, popCount, pushCount, out target, out operands); Instruction[] prefixArray = prefixes.Count > 0 ? prefixes.ToArray() : null; prefixes.Clear(); // ignore NOP instructions if (!(inst.OpCode == OpCodes.Nop || inst.OpCode == OpCodes.Pop)) { block.Instructions.Add(new SsaInstruction(block, inst, target, operands, prefixArray)); } stackSize += pushCount; } foreach (ControlFlowEdge edge in cfgNode.Outgoing) { int newStackSize; switch (edge.Type) { case JumpType.Normal: newStackSize = stackSize; break; case JumpType.EndFinally: if (stackSize != 0) throw new NotSupportedException("stacksize must be 0 in endfinally edge"); newStackSize = 0; break; case JumpType.JumpToExceptionHandler: switch (edge.Target.NodeType) { case ControlFlowNodeType.FinallyOrFaultHandler: newStackSize = 0; break; case ControlFlowNodeType.ExceptionalExit: case ControlFlowNodeType.CatchHandler: newStackSize = 1; break; default: throw new NotSupportedException("unsupported target node type: " + edge.Target.NodeType); } break; default: throw new NotSupportedException("unsupported jump type: " + edge.Type); } int nextStackSize = stackSizeAtBlockStart[edge.Target.BlockIndex]; if (nextStackSize == -1) { stackSizeAtBlockStart[edge.Target.BlockIndex] = newStackSize; CreateInstructions(edge.Target.BlockIndex); } else if (nextStackSize != newStackSize) { throw new InvalidProgramException("Stack size doesn't match"); } } } void DetermineOperands(int stackSize, Instruction inst, int popCount, int pushCount, out SsaVariable target, out SsaVariable[] operands) { switch (inst.OpCode.Code) { case Code.Ldarg: operands = new SsaVariable[] { ssaForm.GetOriginalVariable((ParameterReference)inst.Operand) }; target = stackLocations[stackSize]; break; case Code.Starg: operands = new SsaVariable[] { stackLocations[stackSize] }; target = ssaForm.GetOriginalVariable((ParameterReference)inst.Operand); break; case Code.Ldloc: operands = new SsaVariable[] { ssaForm.GetOriginalVariable((VariableReference)inst.Operand) }; target = stackLocations[stackSize]; break; case Code.Stloc: operands = new SsaVariable[] { stackLocations[stackSize] }; target = ssaForm.GetOriginalVariable((VariableReference)inst.Operand); break; case Code.Dup: operands = new SsaVariable[] { stackLocations[stackSize] }; target = stackLocations[stackSize + 1]; break; default: operands = new SsaVariable[popCount]; for (int i = 0; i < popCount; i++) { operands[i] = stackLocations[stackSize + i]; } switch (pushCount) { case 0: target = null; break; case 1: target = stackLocations[stackSize]; break; default: throw new NotSupportedException("unsupported pushCount=" + pushCount); } break; } } void CreateSpecialInstructions() { // Everything needs an initial write for the SSA transformation to work correctly. foreach (SsaVariable v in parameters) { ssaForm.EntryPoint.Instructions.Add(new SsaInstruction(ssaForm.EntryPoint, null, v, null, specialOpCode: SpecialOpCode.Parameter)); } foreach (SsaVariable v in locals) { ssaForm.EntryPoint.Instructions.Add(new SsaInstruction(ssaForm.EntryPoint, null, v, null, specialOpCode: SpecialOpCode.Uninitialized)); } foreach (SsaVariable v in stackLocations) { ssaForm.EntryPoint.Instructions.Add(new SsaInstruction(ssaForm.EntryPoint, null, v, null, specialOpCode: SpecialOpCode.Uninitialized)); } foreach (SsaBlock b in blocks) { if (b.NodeType == ControlFlowNodeType.CatchHandler) { b.Instructions.Add(new SsaInstruction(b, null, stackLocations[0], null, specialOpCode: SpecialOpCode.Exception, typeOperand: cfg.Nodes[b.BlockIndex].ExceptionHandler.CatchType)); } } } } }