2009-01-05 Sebastien Pouliot <sebastien at ximian.com>
[mcs.git] / class / Mono.Cecil / Mono.Cecil.Cil / CodeWriter.cs
blobf6abc5029fc0fae229f7c8a7ed7925ad92424b11
1 //
2 // CodeWriter.cs
3 //
4 // Author:
5 // Jb Evain (jbevain@gmail.com)
6 //
7 // (C) 2005 - 2007 Jb Evain
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 namespace Mono.Cecil.Cil {
31 using System;
32 using System.Collections;
34 using Mono.Cecil;
35 using Mono.Cecil.Binary;
36 using Mono.Cecil.Metadata;
37 using Mono.Cecil.Signatures;
39 sealed class CodeWriter : BaseCodeVisitor {
41 ReflectionWriter m_reflectWriter;
42 MemoryBinaryWriter m_binaryWriter;
43 MemoryBinaryWriter m_codeWriter;
45 IDictionary m_localSigCache;
46 IDictionary m_standaloneSigCache;
48 IDictionary m_stackSizes;
50 bool stripped;
52 public bool Stripped {
53 get { return stripped; }
54 set { stripped = value; }
57 public CodeWriter (ReflectionWriter reflectWriter, MemoryBinaryWriter writer)
59 m_reflectWriter = reflectWriter;
60 m_binaryWriter = writer;
61 m_codeWriter = new MemoryBinaryWriter ();
63 m_localSigCache = new Hashtable ();
64 m_standaloneSigCache = new Hashtable ();
66 m_stackSizes = new Hashtable ();
69 public RVA WriteMethodBody (MethodDefinition meth)
71 if (meth.Body == null)
72 return RVA.Zero;
74 RVA ret = m_reflectWriter.MetadataWriter.GetDataCursor ();
75 meth.Body.Accept (this);
76 return ret;
79 public override void VisitMethodBody (MethodBody body)
81 m_codeWriter.Empty ();
84 void WriteToken (MetadataToken token)
86 if (token.RID == 0)
87 m_codeWriter.Write (0);
88 else
89 m_codeWriter.Write (token.ToUInt ());
92 static int GetParameterIndex (MethodBody body, ParameterDefinition p)
94 int idx = body.Method.Parameters.IndexOf (p);
95 if (idx == -1 && p == body.Method.This)
96 return 0;
97 if (body.Method.HasThis)
98 idx++;
100 return idx;
103 public override void VisitInstructionCollection (InstructionCollection instructions)
105 MethodBody body = instructions.Container;
106 long start = m_codeWriter.BaseStream.Position;
108 ComputeMaxStack (instructions);
110 foreach (Instruction instr in instructions) {
112 instr.Offset = (int) (m_codeWriter.BaseStream.Position - start);
114 if (instr.OpCode.Size == 1)
115 m_codeWriter.Write (instr.OpCode.Op2);
116 else {
117 m_codeWriter.Write (instr.OpCode.Op1);
118 m_codeWriter.Write (instr.OpCode.Op2);
121 if (instr.OpCode.OperandType != OperandType.InlineNone &&
122 instr.Operand == null)
123 throw new ReflectionException ("OpCode {0} have null operand", instr.OpCode.Name);
125 switch (instr.OpCode.OperandType) {
126 case OperandType.InlineNone :
127 break;
128 case OperandType.InlineSwitch :
129 Instruction [] targets = (Instruction []) instr.Operand;
130 for (int i = 0; i < targets.Length + 1; i++)
131 m_codeWriter.Write ((uint) 0);
132 break;
133 case OperandType.ShortInlineBrTarget :
134 m_codeWriter.Write ((byte) 0);
135 break;
136 case OperandType.InlineBrTarget :
137 m_codeWriter.Write (0);
138 break;
139 case OperandType.ShortInlineI :
140 if (instr.OpCode == OpCodes.Ldc_I4_S)
141 m_codeWriter.Write ((sbyte) instr.Operand);
142 else
143 m_codeWriter.Write ((byte) instr.Operand);
144 break;
145 case OperandType.ShortInlineVar :
146 m_codeWriter.Write ((byte) body.Variables.IndexOf (
147 (VariableDefinition) instr.Operand));
148 break;
149 case OperandType.ShortInlineParam :
150 m_codeWriter.Write ((byte) GetParameterIndex (body, (ParameterDefinition) instr.Operand));
151 break;
152 case OperandType.InlineSig :
153 WriteToken (GetCallSiteToken ((CallSite) instr.Operand));
154 break;
155 case OperandType.InlineI :
156 m_codeWriter.Write ((int) instr.Operand);
157 break;
158 case OperandType.InlineVar :
159 m_codeWriter.Write ((short) body.Variables.IndexOf (
160 (VariableDefinition) instr.Operand));
161 break;
162 case OperandType.InlineParam :
163 m_codeWriter.Write ((short) GetParameterIndex (
164 body, (ParameterDefinition) instr.Operand));
165 break;
166 case OperandType.InlineI8 :
167 m_codeWriter.Write ((long) instr.Operand);
168 break;
169 case OperandType.ShortInlineR :
170 m_codeWriter.Write ((float) instr.Operand);
171 break;
172 case OperandType.InlineR :
173 m_codeWriter.Write ((double) instr.Operand);
174 break;
175 case OperandType.InlineString :
176 WriteToken (new MetadataToken (TokenType.String,
177 m_reflectWriter.MetadataWriter.AddUserString (instr.Operand as string)));
178 break;
179 case OperandType.InlineField :
180 case OperandType.InlineMethod :
181 case OperandType.InlineType :
182 case OperandType.InlineTok :
183 if (instr.Operand is TypeReference)
184 WriteToken (GetTypeToken ((TypeReference) instr.Operand));
185 else if (instr.Operand is GenericInstanceMethod)
186 WriteToken (m_reflectWriter.GetMethodSpecToken (instr.Operand as GenericInstanceMethod));
187 else if (instr.Operand is MemberReference)
188 WriteToken (m_reflectWriter.GetMemberRefToken ((MemberReference) instr.Operand));
189 else if (instr.Operand is IMetadataTokenProvider)
190 WriteToken (((IMetadataTokenProvider) instr.Operand).MetadataToken);
191 else
192 throw new ReflectionException (
193 string.Format ("Wrong operand for {0} OpCode: {1}",
194 instr.OpCode.OperandType,
195 instr.Operand.GetType ().FullName));
196 break;
200 // patch branches
201 long pos = m_codeWriter.BaseStream.Position;
203 foreach (Instruction instr in instructions) {
204 switch (instr.OpCode.OperandType) {
205 case OperandType.InlineSwitch :
206 m_codeWriter.BaseStream.Position = instr.Offset + instr.OpCode.Size;
207 Instruction [] targets = (Instruction []) instr.Operand;
208 m_codeWriter.Write ((uint) targets.Length);
209 foreach (Instruction tgt in targets)
210 m_codeWriter.Write ((tgt.Offset - (instr.Offset +
211 instr.OpCode.Size + (4 * (targets.Length + 1)))));
212 break;
213 case OperandType.ShortInlineBrTarget :
214 m_codeWriter.BaseStream.Position = instr.Offset + instr.OpCode.Size;
215 m_codeWriter.Write ((byte) (((Instruction) instr.Operand).Offset -
216 (instr.Offset + instr.OpCode.Size + 1)));
217 break;
218 case OperandType.InlineBrTarget :
219 m_codeWriter.BaseStream.Position = instr.Offset + instr.OpCode.Size;
220 m_codeWriter.Write(((Instruction) instr.Operand).Offset -
221 (instr.Offset + instr.OpCode.Size + 4));
222 break;
226 m_codeWriter.BaseStream.Position = pos;
229 MetadataToken GetTypeToken (TypeReference type)
231 return m_reflectWriter.GetTypeDefOrRefToken (type);
234 MetadataToken GetCallSiteToken (CallSite cs)
236 uint sig;
237 int sentinel = cs.GetSentinel ();
238 if (sentinel > 0)
239 sig = m_reflectWriter.SignatureWriter.AddMethodDefSig (
240 m_reflectWriter.GetMethodDefSig (cs));
241 else
242 sig = m_reflectWriter.SignatureWriter.AddMethodRefSig (
243 m_reflectWriter.GetMethodRefSig (cs));
245 if (m_standaloneSigCache.Contains (sig))
246 return (MetadataToken) m_standaloneSigCache [sig];
248 StandAloneSigTable sasTable = m_reflectWriter.MetadataTableWriter.GetStandAloneSigTable ();
249 StandAloneSigRow sasRow = m_reflectWriter.MetadataRowWriter.CreateStandAloneSigRow (sig);
251 sasTable.Rows.Add(sasRow);
253 MetadataToken token = new MetadataToken (TokenType.Signature, (uint) sasTable.Rows.Count);
254 m_standaloneSigCache [sig] = token;
255 return token;
258 static int GetLength (Instruction start, Instruction end, InstructionCollection instructions)
260 Instruction last = instructions [instructions.Count - 1];
261 return (end == instructions.Outside ? last.Offset + last.GetSize () : end.Offset) - start.Offset;
264 static bool IsRangeFat (Instruction start, Instruction end, InstructionCollection instructions)
266 return GetLength (start, end, instructions) >= 256 ||
267 start.Offset >= 65536;
270 static bool IsFat (ExceptionHandlerCollection seh)
272 for (int i = 0; i < seh.Count; i++) {
273 ExceptionHandler eh = seh [i];
274 if (IsRangeFat (eh.TryStart, eh.TryEnd, seh.Container.Instructions))
275 return true;
277 if (IsRangeFat (eh.HandlerStart, eh.HandlerEnd, seh.Container.Instructions))
278 return true;
280 switch (eh.Type) {
281 case ExceptionHandlerType.Filter :
282 if (IsRangeFat (eh.FilterStart, eh.FilterEnd, seh.Container.Instructions))
283 return true;
284 break;
288 return false;
291 void WriteExceptionHandlerCollection (ExceptionHandlerCollection seh)
293 m_codeWriter.QuadAlign ();
295 if (seh.Count < 0x15 && !IsFat (seh)) {
296 m_codeWriter.Write ((byte) MethodDataSection.EHTable);
297 m_codeWriter.Write ((byte) (seh.Count * 12 + 4));
298 m_codeWriter.Write (new byte [2]);
299 foreach (ExceptionHandler eh in seh) {
300 m_codeWriter.Write ((ushort) eh.Type);
301 m_codeWriter.Write ((ushort) eh.TryStart.Offset);
302 m_codeWriter.Write ((byte) (eh.TryEnd.Offset - eh.TryStart.Offset));
303 m_codeWriter.Write ((ushort) eh.HandlerStart.Offset);
304 m_codeWriter.Write ((byte) GetLength (eh.HandlerStart, eh.HandlerEnd, seh.Container.Instructions));
305 WriteHandlerSpecific (eh);
307 } else {
308 m_codeWriter.Write ((byte) (MethodDataSection.FatFormat | MethodDataSection.EHTable));
309 WriteFatBlockSize (seh);
310 foreach (ExceptionHandler eh in seh) {
311 m_codeWriter.Write ((uint) eh.Type);
312 m_codeWriter.Write ((uint) eh.TryStart.Offset);
313 m_codeWriter.Write ((uint) (eh.TryEnd.Offset - eh.TryStart.Offset));
314 m_codeWriter.Write ((uint) eh.HandlerStart.Offset);
315 m_codeWriter.Write ((uint) GetLength (eh.HandlerStart, eh.HandlerEnd, seh.Container.Instructions));
316 WriteHandlerSpecific (eh);
321 void WriteFatBlockSize (ExceptionHandlerCollection seh)
323 int size = seh.Count * 24 + 4;
324 m_codeWriter.Write ((byte) (size & 0xff));
325 m_codeWriter.Write ((byte) ((size >> 8) & 0xff));
326 m_codeWriter.Write ((byte) ((size >> 16) & 0xff));
329 void WriteHandlerSpecific (ExceptionHandler eh)
331 switch (eh.Type) {
332 case ExceptionHandlerType.Catch :
333 WriteToken (GetTypeToken (eh.CatchType));
334 break;
335 case ExceptionHandlerType.Filter :
336 m_codeWriter.Write ((uint) eh.FilterStart.Offset);
337 break;
338 default :
339 m_codeWriter.Write (0);
340 break;
344 public override void VisitVariableDefinitionCollection (VariableDefinitionCollection variables)
346 MethodBody body = variables.Container as MethodBody;
347 if (body == null || stripped)
348 return;
350 uint sig = m_reflectWriter.SignatureWriter.AddLocalVarSig (
351 GetLocalVarSig (variables));
353 if (m_localSigCache.Contains (sig)) {
354 body.LocalVarToken = (int) m_localSigCache [sig];
355 return;
358 StandAloneSigTable sasTable = m_reflectWriter.MetadataTableWriter.GetStandAloneSigTable ();
359 StandAloneSigRow sasRow = m_reflectWriter.MetadataRowWriter.CreateStandAloneSigRow (
360 sig);
362 sasTable.Rows.Add (sasRow);
363 body.LocalVarToken = sasTable.Rows.Count;
364 m_localSigCache [sig] = body.LocalVarToken;
367 public override void TerminateMethodBody (MethodBody body)
369 long pos = m_binaryWriter.BaseStream.Position;
371 if (body.HasVariables || body.HasExceptionHandlers
372 || m_codeWriter.BaseStream.Length >= 64 || body.MaxStack > 8) {
374 MethodHeader header = MethodHeader.FatFormat;
375 if (body.InitLocals)
376 header |= MethodHeader.InitLocals;
377 if (body.HasExceptionHandlers)
378 header |= MethodHeader.MoreSects;
380 m_binaryWriter.Write ((byte) header);
381 m_binaryWriter.Write ((byte) 0x30); // (header size / 4) << 4
382 m_binaryWriter.Write ((short) body.MaxStack);
383 m_binaryWriter.Write ((int) m_codeWriter.BaseStream.Length);
384 // the token should be zero if there are no variables
385 int token = body.HasVariables ? ((int) TokenType.Signature | body.LocalVarToken) : 0;
386 m_binaryWriter.Write (token);
388 if (body.HasExceptionHandlers)
389 WriteExceptionHandlerCollection (body.ExceptionHandlers);
390 } else
391 m_binaryWriter.Write ((byte) ((byte) MethodHeader.TinyFormat |
392 m_codeWriter.BaseStream.Length << 2));
394 m_binaryWriter.Write (m_codeWriter);
395 m_binaryWriter.QuadAlign ();
397 m_reflectWriter.MetadataWriter.AddData (
398 (int) (m_binaryWriter.BaseStream.Position - pos));
401 public LocalVarSig.LocalVariable GetLocalVariableSig (VariableDefinition var)
403 LocalVarSig.LocalVariable lv = new LocalVarSig.LocalVariable ();
404 TypeReference type = var.VariableType;
406 lv.CustomMods = m_reflectWriter.GetCustomMods (type);
408 if (type is PinnedType) {
409 lv.Constraint |= Constraint.Pinned;
410 type = (type as PinnedType).ElementType;
413 if (type is ReferenceType) {
414 lv.ByRef = true;
415 type = (type as ReferenceType).ElementType;
418 lv.Type = m_reflectWriter.GetSigType (type);
420 return lv;
423 public LocalVarSig GetLocalVarSig (VariableDefinitionCollection vars)
425 LocalVarSig lvs = new LocalVarSig ();
426 lvs.CallingConvention |= 0x7;
427 lvs.Count = vars.Count;
428 lvs.LocalVariables = new LocalVarSig.LocalVariable [lvs.Count];
429 for (int i = 0; i < lvs.Count; i++) {
430 lvs.LocalVariables [i] = GetLocalVariableSig (vars [i]);
433 return lvs;
436 void ComputeMaxStack (InstructionCollection instructions)
438 int current = 0;
439 int max = 0;
440 m_stackSizes.Clear ();
442 foreach (ExceptionHandler eh in instructions.Container.ExceptionHandlers) {
443 switch (eh.Type) {
444 case ExceptionHandlerType.Catch :
445 case ExceptionHandlerType.Filter :
446 m_stackSizes [eh.HandlerStart] = 1;
447 max = 1;
448 break;
452 foreach (Instruction instr in instructions) {
454 object savedSize = m_stackSizes [instr];
455 if (savedSize != null)
456 current = (int) savedSize;
458 current -= GetPopDelta (instructions.Container.Method, instr, current);
460 if (current < 0)
461 current = 0;
463 current += GetPushDelta (instr);
465 if (current > max)
466 max = current;
468 // for forward branches, copy the stack size for the instruction that is being branched to
469 switch (instr.OpCode.OperandType) {
470 case OperandType.InlineBrTarget:
471 case OperandType.ShortInlineBrTarget:
472 m_stackSizes [instr.Operand] = current;
473 break;
474 case OperandType.InlineSwitch:
475 foreach (Instruction target in (Instruction []) instr.Operand)
476 m_stackSizes [target] = current;
477 break;
480 switch (instr.OpCode.FlowControl) {
481 case FlowControl.Branch:
482 case FlowControl.Throw:
483 case FlowControl.Return:
484 // next statement is not reachable from this statement, so reset the stack depth to 0
485 current = 0;
486 break;
490 instructions.Container.MaxStack = max + 1; // you never know
493 static int GetPushDelta (Instruction instruction)
495 OpCode code = instruction.OpCode;
496 switch (code.StackBehaviourPush) {
497 case StackBehaviour.Push0:
498 return 0;
500 case StackBehaviour.Push1:
501 case StackBehaviour.Pushi:
502 case StackBehaviour.Pushi8:
503 case StackBehaviour.Pushr4:
504 case StackBehaviour.Pushr8:
505 case StackBehaviour.Pushref:
506 return 1;
508 case StackBehaviour.Push1_push1:
509 return 2;
511 case StackBehaviour.Varpush:
512 if (code.FlowControl != FlowControl.Call)
513 break;
515 IMethodSignature method = (IMethodSignature) instruction.Operand;
516 return IsVoid (method.ReturnType.ReturnType) ? 0 : 1;
519 throw new NotSupportedException ();
522 static int GetPopDelta (MethodDefinition current, Instruction instruction, int height)
524 OpCode code = instruction.OpCode;
525 switch (code.StackBehaviourPop) {
526 case StackBehaviour.Pop0:
527 return 0;
528 case StackBehaviour.Popi:
529 case StackBehaviour.Popref:
530 case StackBehaviour.Pop1:
531 return 1;
533 case StackBehaviour.Pop1_pop1:
534 case StackBehaviour.Popi_pop1:
535 case StackBehaviour.Popi_popi:
536 case StackBehaviour.Popi_popi8:
537 case StackBehaviour.Popi_popr4:
538 case StackBehaviour.Popi_popr8:
539 case StackBehaviour.Popref_pop1:
540 case StackBehaviour.Popref_popi:
541 return 2;
543 case StackBehaviour.Popi_popi_popi:
544 case StackBehaviour.Popref_popi_popi:
545 case StackBehaviour.Popref_popi_popi8:
546 case StackBehaviour.Popref_popi_popr4:
547 case StackBehaviour.Popref_popi_popr8:
548 case StackBehaviour.Popref_popi_popref:
549 return 3;
551 case StackBehaviour.PopAll:
552 return height;
554 case StackBehaviour.Varpop:
555 if (code == OpCodes.Ret)
556 return IsVoid (current.ReturnType.ReturnType) ? 0 : 1;
558 if (code.FlowControl != FlowControl.Call)
559 break;
561 IMethodSignature method = (IMethodSignature) instruction.Operand;
562 int count = method.HasParameters ? method.Parameters.Count : 0;
563 if (method.HasThis && code != OpCodes.Newobj)
564 ++count;
566 return count;
569 throw new NotSupportedException ();
572 static bool IsVoid (TypeReference type)
574 return type.FullName == Constants.Void;