2007-07-19 Jb Evain <jbevain@novell.com>
[mono.git] / mcs / class / Mono.Cecil / Mono.Cecil.Cil / CodeWriter.cs
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:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
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.
27 //
28
29 namespace Mono.Cecil.Cil {
30
31         using System;
32         using System.Collections;
33
34         using Mono.Cecil;
35         using Mono.Cecil.Binary;
36         using Mono.Cecil.Metadata;
37         using Mono.Cecil.Signatures;
38
39         class CodeWriter : BaseCodeVisitor {
40
41                 ReflectionWriter m_reflectWriter;
42                 MemoryBinaryWriter m_binaryWriter;
43                 MemoryBinaryWriter m_codeWriter;
44
45                 IDictionary m_localSigCache;
46                 IDictionary m_standaloneSigCache;
47
48                 public CodeWriter (ReflectionWriter reflectWriter, MemoryBinaryWriter writer)
49                 {
50                         m_reflectWriter = reflectWriter;
51                         m_binaryWriter = writer;
52                         m_codeWriter = new MemoryBinaryWriter ();
53
54                         m_localSigCache = new Hashtable ();
55                         m_standaloneSigCache = new Hashtable ();
56                 }
57
58                 public RVA WriteMethodBody (MethodDefinition meth)
59                 {
60                         if (meth.Body == null)
61                                 return RVA.Zero;
62
63                         RVA ret = m_reflectWriter.MetadataWriter.GetDataCursor ();
64                         meth.Body.Accept (this);
65                         return ret;
66                 }
67
68                 public override void VisitMethodBody (MethodBody body)
69                 {
70                         m_codeWriter.Empty ();
71                 }
72
73                 void WriteToken (MetadataToken token)
74                 {
75                         if (token.RID == 0)
76                                 m_codeWriter.Write (0);
77                         else
78                                 m_codeWriter.Write (token.ToUInt ());
79                 }
80
81                 static int GetParameterIndex (MethodBody body, ParameterDefinition p)
82                 {
83                         int idx = body.Method.Parameters.IndexOf (p);
84                         if (idx == -1 && p == body.Method.This)
85                                 return 0;
86                         if (body.Method.HasThis)
87                                 idx++;
88
89                         return idx;
90                 }
91
92                 public override void VisitInstructionCollection (InstructionCollection instructions)
93                 {
94                         MethodBody body = instructions.Container;
95                         long start = m_codeWriter.BaseStream.Position;
96
97                         ComputeMaxStack (instructions);
98
99                         foreach (Instruction instr in instructions) {
100
101                                 instr.Offset = (int) (m_codeWriter.BaseStream.Position - start);
102
103                                 if (instr.OpCode.Size == 1)
104                                         m_codeWriter.Write (instr.OpCode.Op2);
105                                 else {
106                                         m_codeWriter.Write (instr.OpCode.Op1);
107                                         m_codeWriter.Write (instr.OpCode.Op2);
108                                 }
109
110                                 if (instr.OpCode.OperandType != OperandType.InlineNone &&
111                                         instr.Operand == null)
112                                         throw new ReflectionException ("OpCode {0} have null operand", instr.OpCode.Name);
113
114                                 switch (instr.OpCode.OperandType) {
115                                 case OperandType.InlineNone :
116                                         break;
117                                 case OperandType.InlineSwitch :
118                                         Instruction [] targets = (Instruction []) instr.Operand;
119                                         for (int i = 0; i < targets.Length + 1; i++)
120                                                 m_codeWriter.Write ((uint) 0);
121                                         break;
122                                 case OperandType.ShortInlineBrTarget :
123                                         m_codeWriter.Write ((byte) 0);
124                                         break;
125                                 case OperandType.InlineBrTarget :
126                                         m_codeWriter.Write (0);
127                                         break;
128                                 case OperandType.ShortInlineI :
129                                         if (instr.OpCode == OpCodes.Ldc_I4_S)
130                                                 m_codeWriter.Write ((sbyte) instr.Operand);
131                                         else
132                                                 m_codeWriter.Write ((byte) instr.Operand);
133                                         break;
134                                 case OperandType.ShortInlineVar :
135                                         m_codeWriter.Write ((byte) body.Variables.IndexOf (
136                                                 (VariableDefinition) instr.Operand));
137                                         break;
138                                 case OperandType.ShortInlineParam :
139                                         m_codeWriter.Write ((byte) GetParameterIndex (body, (ParameterDefinition) instr.Operand));
140                                         break;
141                                 case OperandType.InlineSig :
142                                         WriteToken (GetCallSiteToken ((CallSite) instr.Operand));
143                                         break;
144                                 case OperandType.InlineI :
145                                         m_codeWriter.Write ((int) instr.Operand);
146                                         break;
147                                 case OperandType.InlineVar :
148                                         m_codeWriter.Write ((short) body.Variables.IndexOf (
149                                                 (VariableDefinition) instr.Operand));
150                                         break;
151                                 case OperandType.InlineParam :
152                                         m_codeWriter.Write ((short) GetParameterIndex (
153                                                         body, (ParameterDefinition) instr.Operand));
154                                         break;
155                                 case OperandType.InlineI8 :
156                                         m_codeWriter.Write ((long) instr.Operand);
157                                         break;
158                                 case OperandType.ShortInlineR :
159                                         m_codeWriter.Write ((float) instr.Operand);
160                                         break;
161                                 case OperandType.InlineR :
162                                         m_codeWriter.Write ((double) instr.Operand);
163                                         break;
164                                 case OperandType.InlineString :
165                                         WriteToken (new MetadataToken (TokenType.String,
166                                                         m_reflectWriter.MetadataWriter.AddUserString (instr.Operand as string)));
167                                         break;
168                                 case OperandType.InlineField :
169                                 case OperandType.InlineMethod :
170                                 case OperandType.InlineType :
171                                 case OperandType.InlineTok :
172                                         if (instr.Operand is TypeReference)
173                                                 WriteToken (m_reflectWriter.GetTypeDefOrRefToken (
174                                                                 instr.Operand as TypeReference));
175                                         else if (instr.Operand is GenericInstanceMethod)
176                                                 WriteToken (m_reflectWriter.GetMethodSpecToken (instr.Operand as GenericInstanceMethod));
177                                         else if (instr.Operand is MemberReference)
178                                                 WriteToken (m_reflectWriter.GetMemberRefToken ((MemberReference) instr.Operand));
179                                         else if (instr.Operand is IMetadataTokenProvider)
180                                                 WriteToken (((IMetadataTokenProvider) instr.Operand).MetadataToken);
181                                         else
182                                                 throw new ReflectionException (
183                                                         string.Format ("Wrong operand for {0} OpCode: {1}",
184                                                                 instr.OpCode.OperandType,
185                                                                 instr.Operand.GetType ().FullName));
186                                         break;
187                                 }
188                         }
189
190                         // patch branches
191                         long pos = m_codeWriter.BaseStream.Position;
192
193                         foreach (Instruction instr in instructions) {
194                                 switch (instr.OpCode.OperandType) {
195                                 case OperandType.InlineSwitch :
196                                         m_codeWriter.BaseStream.Position = instr.Offset + instr.OpCode.Size;
197                                         Instruction [] targets = (Instruction []) instr.Operand;
198                                         m_codeWriter.Write ((uint) targets.Length);
199                                         foreach (Instruction tgt in targets)
200                                                 m_codeWriter.Write ((tgt.Offset - (instr.Offset +
201                                                         instr.OpCode.Size + (4 * (targets.Length + 1)))));
202                                         break;
203                                 case OperandType.ShortInlineBrTarget :
204                                         m_codeWriter.BaseStream.Position = instr.Offset + instr.OpCode.Size;
205                                         m_codeWriter.Write ((byte) (((Instruction) instr.Operand).Offset -
206                                                 (instr.Offset + instr.OpCode.Size + 1)));
207                                         break;
208                                 case OperandType.InlineBrTarget :
209                                         m_codeWriter.BaseStream.Position = instr.Offset + instr.OpCode.Size;
210                                         m_codeWriter.Write(((Instruction) instr.Operand).Offset -
211                                                 (instr.Offset + instr.OpCode.Size + 4));
212                                         break;
213                                 }
214                         }
215
216                         m_codeWriter.BaseStream.Position = pos;
217                 }
218
219                 MetadataToken GetCallSiteToken (CallSite cs)
220                 {
221                         uint sig;
222                         int sentinel = cs.GetSentinel ();
223                         if (sentinel > 0)
224                                 sig = m_reflectWriter.SignatureWriter.AddMethodDefSig (
225                                         m_reflectWriter.GetMethodDefSig (cs));
226                         else
227                                 sig = m_reflectWriter.SignatureWriter.AddMethodRefSig (
228                                         m_reflectWriter.GetMethodRefSig (cs));
229
230                         if (m_standaloneSigCache.Contains (sig))
231                                 return (MetadataToken) m_standaloneSigCache [sig];
232
233                         StandAloneSigTable sasTable = m_reflectWriter.MetadataTableWriter.GetStandAloneSigTable ();
234                         StandAloneSigRow sasRow = m_reflectWriter.MetadataRowWriter.CreateStandAloneSigRow (sig);
235
236                         sasTable.Rows.Add(sasRow);
237
238                         MetadataToken token = new MetadataToken (TokenType.Signature, (uint) sasTable.Rows.Count);
239                         m_standaloneSigCache [sig] = token;
240                         return token;
241                 }
242
243                 static int GetLength (Instruction start, Instruction end, InstructionCollection instructions)
244                 {
245                         Instruction last = instructions [instructions.Count - 1];
246                         return (end == instructions.Outside ? last.Offset + GetSize (last) : end.Offset) - start.Offset;
247                 }
248
249                 static int GetSize (Instruction i)
250                 {
251                         int size = i.OpCode.Size;
252
253                         switch (i.OpCode.OperandType) {
254                         case OperandType.InlineSwitch:
255                                 size += ((Instruction []) i.Operand).Length * 4;
256                                 break;
257                         case OperandType.InlineI8:
258                         case OperandType.InlineR:
259                                 size += 8;
260                                 break;
261                         case OperandType.InlineBrTarget:
262                         case OperandType.InlineField:
263                         case OperandType.InlineI:
264                         case OperandType.InlineMethod:
265                         case OperandType.InlineString:
266                         case OperandType.InlineTok:
267                         case OperandType.InlineType:
268                         case OperandType.ShortInlineR:
269                                 size += 4;
270                                 break;
271                         case OperandType.InlineParam:
272                         case OperandType.InlineVar:
273                                 size += 2;
274                                 break;
275                         case OperandType.ShortInlineBrTarget:
276                         case OperandType.ShortInlineI:
277                         case OperandType.ShortInlineParam:
278                         case OperandType.ShortInlineVar:
279                                 size += 1;
280                                 break;
281                         }
282
283                         return size;
284                 }
285
286                 static bool IsRangeFat (Instruction start, Instruction end, InstructionCollection instructions)
287                 {
288                         return GetLength (start, end, instructions) >= 256 ||
289                                 start.Offset >= 65536;
290                 }
291
292                 static bool IsFat (ExceptionHandlerCollection seh)
293                 {
294                         for (int i = 0; i < seh.Count; i++) {
295                                 ExceptionHandler eh = seh [i];
296                                 if (IsRangeFat (eh.TryStart, eh.TryEnd, seh.Container.Instructions))
297                                         return true;
298
299                                 switch (eh.Type) {
300                                 case ExceptionHandlerType.Catch :
301                                 case ExceptionHandlerType.Fault :
302                                 case ExceptionHandlerType.Finally :
303                                         if (IsRangeFat (eh.HandlerStart, eh.HandlerEnd, seh.Container.Instructions))
304                                                 return true;
305                                         break;
306                                 case ExceptionHandlerType.Filter :
307                                         if (IsRangeFat (eh.FilterStart, eh.FilterEnd, seh.Container.Instructions))
308                                                 return true;
309                                         break;
310                                 }
311                         }
312
313                         return false;
314                 }
315
316                 void WriteExceptionHandlerCollection (ExceptionHandlerCollection seh)
317                 {
318                         m_codeWriter.QuadAlign ();
319
320                         if (seh.Count < 0x15 && !IsFat (seh)) {
321                                 m_codeWriter.Write ((byte) MethodDataSection.EHTable);
322                                 m_codeWriter.Write ((byte) (seh.Count * 12 + 4));
323                                 m_codeWriter.Write (new byte [2]);
324                                 foreach (ExceptionHandler eh in seh) {
325                                         m_codeWriter.Write ((ushort) eh.Type);
326                                         m_codeWriter.Write ((ushort) eh.TryStart.Offset);
327                                         m_codeWriter.Write ((byte) (eh.TryEnd.Offset - eh.TryStart.Offset));
328                                         m_codeWriter.Write ((ushort) eh.HandlerStart.Offset);
329                                         m_codeWriter.Write ((byte) GetLength (eh.HandlerStart, eh.HandlerEnd, seh.Container.Instructions));
330                                         WriteHandlerSpecific (eh);
331                                 }
332                         } else {
333                                 m_codeWriter.Write ((byte) (MethodDataSection.FatFormat | MethodDataSection.EHTable));
334                                 WriteFatBlockSize (seh);
335                                 foreach (ExceptionHandler eh in seh) {
336                                         m_codeWriter.Write ((uint) eh.Type);
337                                         m_codeWriter.Write ((uint) eh.TryStart.Offset);
338                                         m_codeWriter.Write ((uint) (eh.TryEnd.Offset - eh.TryStart.Offset));
339                                         m_codeWriter.Write ((uint) eh.HandlerStart.Offset);
340                                         m_codeWriter.Write ((uint) GetLength (eh.HandlerStart, eh.HandlerEnd, seh.Container.Instructions));
341                                         WriteHandlerSpecific (eh);
342                                 }
343                         }
344                 }
345
346                 void WriteFatBlockSize (ExceptionHandlerCollection seh)
347                 {
348                         int size = seh.Count * 24 + 4;
349                         m_codeWriter.Write ((byte) (size & 0xff));
350                         m_codeWriter.Write ((byte) ((size >> 8) & 0xff));
351                         m_codeWriter.Write ((byte) ((size >> 16) & 0xff));
352                 }
353
354                 void WriteHandlerSpecific (ExceptionHandler eh)
355                 {
356                         switch (eh.Type) {
357                         case ExceptionHandlerType.Catch :
358                                 WriteToken (eh.CatchType.MetadataToken);
359                                 break;
360                         case ExceptionHandlerType.Filter :
361                                 m_codeWriter.Write ((uint) eh.FilterStart.Offset);
362                                 break;
363                         default :
364                                 m_codeWriter.Write (0);
365                                 break;
366                         }
367                 }
368
369                 public override void VisitVariableDefinitionCollection (VariableDefinitionCollection variables)
370                 {
371                         MethodBody body = variables.Container as MethodBody;
372                         if (body == null)
373                                 return;
374
375                         uint sig = m_reflectWriter.SignatureWriter.AddLocalVarSig (
376                                         GetLocalVarSig (variables));
377
378                         if (m_localSigCache.Contains (sig)) {
379                                 body.LocalVarToken = (int) m_localSigCache [sig];
380                                 return;
381                         }
382
383                         StandAloneSigTable sasTable = m_reflectWriter.MetadataTableWriter.GetStandAloneSigTable ();
384                         StandAloneSigRow sasRow = m_reflectWriter.MetadataRowWriter.CreateStandAloneSigRow (
385                                 sig);
386
387                         sasTable.Rows.Add (sasRow);
388                         body.LocalVarToken = sasTable.Rows.Count;
389                         m_localSigCache [sig] = body.LocalVarToken;
390                 }
391
392                 public override void TerminateMethodBody (MethodBody body)
393                 {
394                         long pos = m_binaryWriter.BaseStream.Position;
395
396                         if (body.Variables.Count > 0 || body.ExceptionHandlers.Count > 0
397                                 || m_codeWriter.BaseStream.Length >= 64 || body.MaxStack > 8) {
398
399                                 MethodHeader header = MethodHeader.FatFormat;
400                                 if (body.InitLocals)
401                                         header |= MethodHeader.InitLocals;
402                                 if (body.ExceptionHandlers.Count > 0)
403                                         header |= MethodHeader.MoreSects;
404
405                                 m_binaryWriter.Write ((byte) header);
406                                 m_binaryWriter.Write ((byte) 0x30); // (header size / 4) << 4
407                                 m_binaryWriter.Write ((short) body.MaxStack);
408                                 m_binaryWriter.Write ((int) m_codeWriter.BaseStream.Length);
409                                 m_binaryWriter.Write (((int) TokenType.Signature | body.LocalVarToken));
410
411                                 WriteExceptionHandlerCollection (body.ExceptionHandlers);
412                         } else
413                                 m_binaryWriter.Write ((byte) ((byte) MethodHeader.TinyFormat |
414                                         m_codeWriter.BaseStream.Length << 2));
415
416                         m_binaryWriter.Write (m_codeWriter);
417                         m_binaryWriter.QuadAlign ();
418
419                         m_reflectWriter.MetadataWriter.AddData (
420                                 (int) (m_binaryWriter.BaseStream.Position - pos));
421                 }
422
423                 LocalVarSig GetLocalVarSig (VariableDefinitionCollection vars)
424                 {
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                                 LocalVarSig.LocalVariable lv = new LocalVarSig.LocalVariable ();
431                                 TypeReference type = vars [i].VariableType;
432
433                                 lv.CustomMods = m_reflectWriter.GetCustomMods (type);
434
435                                 if (type is PinnedType) {
436                                         lv.Constraint |= Constraint.Pinned;
437                                         type = (type as PinnedType).ElementType;
438                                 }
439
440                                 if (type is ReferenceType) {
441                                         lv.ByRef = true;
442                                         type = (type as ReferenceType).ElementType;
443                                 }
444
445                                 lv.Type = m_reflectWriter.GetSigType (type);
446
447                                 lvs.LocalVariables [i] = lv;
448                         }
449                         return lvs;
450                 }
451
452                 static void ComputeMaxStack (InstructionCollection instructions)
453                 {
454                         InstructionCollection ehs = new InstructionCollection (null);
455                         foreach (ExceptionHandler eh in instructions.Container.ExceptionHandlers) {
456                                 switch (eh.Type) {
457                                 case ExceptionHandlerType.Catch :
458                                         ehs.Add (eh.HandlerStart);
459                                         break;
460                                 case ExceptionHandlerType.Filter :
461                                         ehs.Add (eh.FilterStart);
462                                         break;
463                                 }
464                         }
465
466                         int max = 0, current = 0;
467                         foreach (Instruction instr in instructions) {
468
469                                 if (ehs.Contains (instr))
470                                         current++;
471
472                                 switch (instr.OpCode.StackBehaviourPush) {
473                                 case StackBehaviour.Push1:
474                                 case StackBehaviour.Pushi:
475                                 case StackBehaviour.Pushi8:
476                                 case StackBehaviour.Pushr4:
477                                 case StackBehaviour.Pushr8:
478                                 case StackBehaviour.Pushref:
479                                 case StackBehaviour.Varpush:
480                                         current++;
481                                         break;
482                                 case StackBehaviour.Push1_push1:
483                                         current += 2;
484                                         break;
485                                 }
486
487                                 if (max < current)
488                                         max = current;
489
490                                 switch (instr.OpCode.StackBehaviourPop) {
491                                 case StackBehaviour.Varpop:
492                                         break;
493                                 case StackBehaviour.Pop1:
494                                 case StackBehaviour.Popi:
495                                 case StackBehaviour.Popref:
496                                         current--;
497                                         break;
498                                 case StackBehaviour.Pop1_pop1:
499                                 case StackBehaviour.Popi_pop1:
500                                 case StackBehaviour.Popi_popi:
501                                 case StackBehaviour.Popi_popi8:
502                                 case StackBehaviour.Popi_popr4:
503                                 case StackBehaviour.Popi_popr8:
504                                 case StackBehaviour.Popref_pop1:
505                                 case StackBehaviour.Popref_popi:
506                                         current -= 2;
507                                         break;
508                                 case StackBehaviour.Popi_popi_popi:
509                                 case StackBehaviour.Popref_popi_popi:
510                                 case StackBehaviour.Popref_popi_popi8:
511                                 case StackBehaviour.Popref_popi_popr4:
512                                 case StackBehaviour.Popref_popi_popr8:
513                                 case StackBehaviour.Popref_popi_popref:
514                                         current -= 3;
515                                         break;
516                                 }
517
518                                 if (current < 0)
519                                         current = 0;
520                         }
521
522                         instructions.Container.MaxStack = max;
523                 }
524         }
525 }