New tests.
[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                                 if (IsRangeFat (eh.HandlerStart, eh.HandlerEnd, seh.Container.Instructions))
300                                         return true;
301
302                                 switch (eh.Type) {
303                                 case ExceptionHandlerType.Filter :
304                                         if (IsRangeFat (eh.FilterStart, eh.FilterEnd, seh.Container.Instructions))
305                                                 return true;
306                                         break;
307                                 }
308                         }
309
310                         return false;
311                 }
312
313                 void WriteExceptionHandlerCollection (ExceptionHandlerCollection seh)
314                 {
315                         m_codeWriter.QuadAlign ();
316
317                         if (seh.Count < 0x15 && !IsFat (seh)) {
318                                 m_codeWriter.Write ((byte) MethodDataSection.EHTable);
319                                 m_codeWriter.Write ((byte) (seh.Count * 12 + 4));
320                                 m_codeWriter.Write (new byte [2]);
321                                 foreach (ExceptionHandler eh in seh) {
322                                         m_codeWriter.Write ((ushort) eh.Type);
323                                         m_codeWriter.Write ((ushort) eh.TryStart.Offset);
324                                         m_codeWriter.Write ((byte) (eh.TryEnd.Offset - eh.TryStart.Offset));
325                                         m_codeWriter.Write ((ushort) eh.HandlerStart.Offset);
326                                         m_codeWriter.Write ((byte) GetLength (eh.HandlerStart, eh.HandlerEnd, seh.Container.Instructions));
327                                         WriteHandlerSpecific (eh);
328                                 }
329                         } else {
330                                 m_codeWriter.Write ((byte) (MethodDataSection.FatFormat | MethodDataSection.EHTable));
331                                 WriteFatBlockSize (seh);
332                                 foreach (ExceptionHandler eh in seh) {
333                                         m_codeWriter.Write ((uint) eh.Type);
334                                         m_codeWriter.Write ((uint) eh.TryStart.Offset);
335                                         m_codeWriter.Write ((uint) (eh.TryEnd.Offset - eh.TryStart.Offset));
336                                         m_codeWriter.Write ((uint) eh.HandlerStart.Offset);
337                                         m_codeWriter.Write ((uint) GetLength (eh.HandlerStart, eh.HandlerEnd, seh.Container.Instructions));
338                                         WriteHandlerSpecific (eh);
339                                 }
340                         }
341                 }
342
343                 void WriteFatBlockSize (ExceptionHandlerCollection seh)
344                 {
345                         int size = seh.Count * 24 + 4;
346                         m_codeWriter.Write ((byte) (size & 0xff));
347                         m_codeWriter.Write ((byte) ((size >> 8) & 0xff));
348                         m_codeWriter.Write ((byte) ((size >> 16) & 0xff));
349                 }
350
351                 void WriteHandlerSpecific (ExceptionHandler eh)
352                 {
353                         switch (eh.Type) {
354                         case ExceptionHandlerType.Catch :
355                                 WriteToken (eh.CatchType.MetadataToken);
356                                 break;
357                         case ExceptionHandlerType.Filter :
358                                 m_codeWriter.Write ((uint) eh.FilterStart.Offset);
359                                 break;
360                         default :
361                                 m_codeWriter.Write (0);
362                                 break;
363                         }
364                 }
365
366                 public override void VisitVariableDefinitionCollection (VariableDefinitionCollection variables)
367                 {
368                         MethodBody body = variables.Container as MethodBody;
369                         if (body == null)
370                                 return;
371
372                         uint sig = m_reflectWriter.SignatureWriter.AddLocalVarSig (
373                                         GetLocalVarSig (variables));
374
375                         if (m_localSigCache.Contains (sig)) {
376                                 body.LocalVarToken = (int) m_localSigCache [sig];
377                                 return;
378                         }
379
380                         StandAloneSigTable sasTable = m_reflectWriter.MetadataTableWriter.GetStandAloneSigTable ();
381                         StandAloneSigRow sasRow = m_reflectWriter.MetadataRowWriter.CreateStandAloneSigRow (
382                                 sig);
383
384                         sasTable.Rows.Add (sasRow);
385                         body.LocalVarToken = sasTable.Rows.Count;
386                         m_localSigCache [sig] = body.LocalVarToken;
387                 }
388
389                 public override void TerminateMethodBody (MethodBody body)
390                 {
391                         long pos = m_binaryWriter.BaseStream.Position;
392
393                         if (body.Variables.Count > 0 || body.ExceptionHandlers.Count > 0
394                                 || m_codeWriter.BaseStream.Length >= 64 || body.MaxStack > 8) {
395
396                                 MethodHeader header = MethodHeader.FatFormat;
397                                 if (body.InitLocals)
398                                         header |= MethodHeader.InitLocals;
399                                 if (body.ExceptionHandlers.Count > 0)
400                                         header |= MethodHeader.MoreSects;
401
402                                 m_binaryWriter.Write ((byte) header);
403                                 m_binaryWriter.Write ((byte) 0x30); // (header size / 4) << 4
404                                 m_binaryWriter.Write ((short) body.MaxStack);
405                                 m_binaryWriter.Write ((int) m_codeWriter.BaseStream.Length);
406                                 m_binaryWriter.Write (((int) TokenType.Signature | body.LocalVarToken));
407
408                                 WriteExceptionHandlerCollection (body.ExceptionHandlers);
409                         } else
410                                 m_binaryWriter.Write ((byte) ((byte) MethodHeader.TinyFormat |
411                                         m_codeWriter.BaseStream.Length << 2));
412
413                         m_binaryWriter.Write (m_codeWriter);
414                         m_binaryWriter.QuadAlign ();
415
416                         m_reflectWriter.MetadataWriter.AddData (
417                                 (int) (m_binaryWriter.BaseStream.Position - pos));
418                 }
419
420                 public LocalVarSig.LocalVariable GetLocalVariableSig (VariableDefinition var)
421                 {
422                         LocalVarSig.LocalVariable lv = new LocalVarSig.LocalVariable ();
423                         TypeReference type = var.VariableType;
424
425                         lv.CustomMods = m_reflectWriter.GetCustomMods (type);
426
427                         if (type is PinnedType) {
428                                 lv.Constraint |= Constraint.Pinned;
429                                 type = (type as PinnedType).ElementType;
430                         }
431
432                         if (type is ReferenceType) {
433                                 lv.ByRef = true;
434                                 type = (type as ReferenceType).ElementType;
435                         }
436
437                         lv.Type = m_reflectWriter.GetSigType (type);
438
439                         return lv;
440                 }
441
442                 public LocalVarSig GetLocalVarSig (VariableDefinitionCollection vars)
443                 {
444                         LocalVarSig lvs = new LocalVarSig ();
445                         lvs.CallingConvention |= 0x7;
446                         lvs.Count = vars.Count;
447                         lvs.LocalVariables = new LocalVarSig.LocalVariable [lvs.Count];
448                         for (int i = 0; i < lvs.Count; i++) {
449                                 lvs.LocalVariables [i] = GetLocalVariableSig (vars [i]);
450                         }
451
452                         return lvs;
453                 }
454
455                 static void ComputeMaxStack (InstructionCollection instructions)
456                 {
457                         InstructionCollection ehs = new InstructionCollection (null);
458                         foreach (ExceptionHandler eh in instructions.Container.ExceptionHandlers) {
459                                 switch (eh.Type) {
460                                 case ExceptionHandlerType.Catch :
461                                         ehs.Add (eh.HandlerStart);
462                                         break;
463                                 case ExceptionHandlerType.Filter :
464                                         ehs.Add (eh.FilterStart);
465                                         break;
466                                 }
467                         }
468
469                         int max = 0, current = 0;
470                         foreach (Instruction instr in instructions) {
471
472                                 if (ehs.Contains (instr))
473                                         current++;
474
475                                 switch (instr.OpCode.StackBehaviourPush) {
476                                 case StackBehaviour.Push1:
477                                 case StackBehaviour.Pushi:
478                                 case StackBehaviour.Pushi8:
479                                 case StackBehaviour.Pushr4:
480                                 case StackBehaviour.Pushr8:
481                                 case StackBehaviour.Pushref:
482                                 case StackBehaviour.Varpush:
483                                         current++;
484                                         break;
485                                 case StackBehaviour.Push1_push1:
486                                         current += 2;
487                                         break;
488                                 }
489
490                                 if (max < current)
491                                         max = current;
492
493                                 switch (instr.OpCode.StackBehaviourPop) {
494                                 case StackBehaviour.Varpop:
495                                         break;
496                                 case StackBehaviour.Pop1:
497                                 case StackBehaviour.Popi:
498                                 case StackBehaviour.Popref:
499                                         current--;
500                                         break;
501                                 case StackBehaviour.Pop1_pop1:
502                                 case StackBehaviour.Popi_pop1:
503                                 case StackBehaviour.Popi_popi:
504                                 case StackBehaviour.Popi_popi8:
505                                 case StackBehaviour.Popi_popr4:
506                                 case StackBehaviour.Popi_popr8:
507                                 case StackBehaviour.Popref_pop1:
508                                 case StackBehaviour.Popref_popi:
509                                         current -= 2;
510                                         break;
511                                 case StackBehaviour.Popi_popi_popi:
512                                 case StackBehaviour.Popref_popi_popi:
513                                 case StackBehaviour.Popref_popi_popi8:
514                                 case StackBehaviour.Popref_popi_popr4:
515                                 case StackBehaviour.Popref_popi_popr8:
516                                 case StackBehaviour.Popref_popi_popref:
517                                         current -= 3;
518                                         break;
519                                 }
520
521                                 if (current < 0)
522                                         current = 0;
523                         }
524
525                         instructions.Container.MaxStack = max;
526                 }
527         }
528 }