f6abc5029fc0fae229f7c8a7ed7925ad92424b11
[mono.git] / mcs / tools / cil-strip / 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         sealed 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                 IDictionary m_stackSizes;
49
50                 bool stripped;
51
52                 public bool Stripped {
53                         get { return stripped; }
54                         set { stripped = value; }
55                 }
56
57                 public CodeWriter (ReflectionWriter reflectWriter, MemoryBinaryWriter writer)
58                 {
59                         m_reflectWriter = reflectWriter;
60                         m_binaryWriter = writer;
61                         m_codeWriter = new MemoryBinaryWriter ();
62
63                         m_localSigCache = new Hashtable ();
64                         m_standaloneSigCache = new Hashtable ();
65
66                         m_stackSizes = new Hashtable ();
67                 }
68
69                 public RVA WriteMethodBody (MethodDefinition meth)
70                 {
71                         if (meth.Body == null)
72                                 return RVA.Zero;
73
74                         RVA ret = m_reflectWriter.MetadataWriter.GetDataCursor ();
75                         meth.Body.Accept (this);
76                         return ret;
77                 }
78
79                 public override void VisitMethodBody (MethodBody body)
80                 {
81                         m_codeWriter.Empty ();
82                 }
83
84                 void WriteToken (MetadataToken token)
85                 {
86                         if (token.RID == 0)
87                                 m_codeWriter.Write (0);
88                         else
89                                 m_codeWriter.Write (token.ToUInt ());
90                 }
91
92                 static int GetParameterIndex (MethodBody body, ParameterDefinition p)
93                 {
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++;
99
100                         return idx;
101                 }
102
103                 public override void VisitInstructionCollection (InstructionCollection instructions)
104                 {
105                         MethodBody body = instructions.Container;
106                         long start = m_codeWriter.BaseStream.Position;
107
108                         ComputeMaxStack (instructions);
109
110                         foreach (Instruction instr in instructions) {
111
112                                 instr.Offset = (int) (m_codeWriter.BaseStream.Position - start);
113
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);
119                                 }
120
121                                 if (instr.OpCode.OperandType != OperandType.InlineNone &&
122                                         instr.Operand == null)
123                                         throw new ReflectionException ("OpCode {0} have null operand", instr.OpCode.Name);
124
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;
197                                 }
198                         }
199
200                         // patch branches
201                         long pos = m_codeWriter.BaseStream.Position;
202
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;
223                                 }
224                         }
225
226                         m_codeWriter.BaseStream.Position = pos;
227                 }
228
229                 MetadataToken GetTypeToken (TypeReference type)
230                 {
231                         return m_reflectWriter.GetTypeDefOrRefToken (type);
232                 }
233
234                 MetadataToken GetCallSiteToken (CallSite cs)
235                 {
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));
244
245                         if (m_standaloneSigCache.Contains (sig))
246                                 return (MetadataToken) m_standaloneSigCache [sig];
247
248                         StandAloneSigTable sasTable = m_reflectWriter.MetadataTableWriter.GetStandAloneSigTable ();
249                         StandAloneSigRow sasRow = m_reflectWriter.MetadataRowWriter.CreateStandAloneSigRow (sig);
250
251                         sasTable.Rows.Add(sasRow);
252
253                         MetadataToken token = new MetadataToken (TokenType.Signature, (uint) sasTable.Rows.Count);
254                         m_standaloneSigCache [sig] = token;
255                         return token;
256                 }
257
258                 static int GetLength (Instruction start, Instruction end, InstructionCollection instructions)
259                 {
260                         Instruction last = instructions [instructions.Count - 1];
261                         return (end == instructions.Outside ? last.Offset + last.GetSize () : end.Offset) - start.Offset;
262                 }
263
264                 static bool IsRangeFat (Instruction start, Instruction end, InstructionCollection instructions)
265                 {
266                         return GetLength (start, end, instructions) >= 256 ||
267                                 start.Offset >= 65536;
268                 }
269
270                 static bool IsFat (ExceptionHandlerCollection seh)
271                 {
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;
276
277                                 if (IsRangeFat (eh.HandlerStart, eh.HandlerEnd, seh.Container.Instructions))
278                                         return true;
279
280                                 switch (eh.Type) {
281                                 case ExceptionHandlerType.Filter :
282                                         if (IsRangeFat (eh.FilterStart, eh.FilterEnd, seh.Container.Instructions))
283                                                 return true;
284                                         break;
285                                 }
286                         }
287
288                         return false;
289                 }
290
291                 void WriteExceptionHandlerCollection (ExceptionHandlerCollection seh)
292                 {
293                         m_codeWriter.QuadAlign ();
294
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);
306                                 }
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);
317                                 }
318                         }
319                 }
320
321                 void WriteFatBlockSize (ExceptionHandlerCollection seh)
322                 {
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));
327                 }
328
329                 void WriteHandlerSpecific (ExceptionHandler eh)
330                 {
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;
341                         }
342                 }
343
344                 public override void VisitVariableDefinitionCollection (VariableDefinitionCollection variables)
345                 {
346                         MethodBody body = variables.Container as MethodBody;
347                         if (body == null || stripped)
348                                 return;
349
350                         uint sig = m_reflectWriter.SignatureWriter.AddLocalVarSig (
351                                         GetLocalVarSig (variables));
352
353                         if (m_localSigCache.Contains (sig)) {
354                                 body.LocalVarToken = (int) m_localSigCache [sig];
355                                 return;
356                         }
357
358                         StandAloneSigTable sasTable = m_reflectWriter.MetadataTableWriter.GetStandAloneSigTable ();
359                         StandAloneSigRow sasRow = m_reflectWriter.MetadataRowWriter.CreateStandAloneSigRow (
360                                 sig);
361
362                         sasTable.Rows.Add (sasRow);
363                         body.LocalVarToken = sasTable.Rows.Count;
364                         m_localSigCache [sig] = body.LocalVarToken;
365                 }
366
367                 public override void TerminateMethodBody (MethodBody body)
368                 {
369                         long pos = m_binaryWriter.BaseStream.Position;
370
371                         if (body.HasVariables || body.HasExceptionHandlers
372                                 || m_codeWriter.BaseStream.Length >= 64 || body.MaxStack > 8) {
373
374                                 MethodHeader header = MethodHeader.FatFormat;
375                                 if (body.InitLocals)
376                                         header |= MethodHeader.InitLocals;
377                                 if (body.HasExceptionHandlers)
378                                         header |= MethodHeader.MoreSects;
379
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);
387
388                                 if (body.HasExceptionHandlers)
389                                         WriteExceptionHandlerCollection (body.ExceptionHandlers);
390                         } else
391                                 m_binaryWriter.Write ((byte) ((byte) MethodHeader.TinyFormat |
392                                         m_codeWriter.BaseStream.Length << 2));
393
394                         m_binaryWriter.Write (m_codeWriter);
395                         m_binaryWriter.QuadAlign ();
396
397                         m_reflectWriter.MetadataWriter.AddData (
398                                 (int) (m_binaryWriter.BaseStream.Position - pos));
399                 }
400
401                 public LocalVarSig.LocalVariable GetLocalVariableSig (VariableDefinition var)
402                 {
403                         LocalVarSig.LocalVariable lv = new LocalVarSig.LocalVariable ();
404                         TypeReference type = var.VariableType;
405
406                         lv.CustomMods = m_reflectWriter.GetCustomMods (type);
407
408                         if (type is PinnedType) {
409                                 lv.Constraint |= Constraint.Pinned;
410                                 type = (type as PinnedType).ElementType;
411                         }
412
413                         if (type is ReferenceType) {
414                                 lv.ByRef = true;
415                                 type = (type as ReferenceType).ElementType;
416                         }
417
418                         lv.Type = m_reflectWriter.GetSigType (type);
419
420                         return lv;
421                 }
422
423                 public 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                                 lvs.LocalVariables [i] = GetLocalVariableSig (vars [i]);
431                         }
432
433                         return lvs;
434                 }
435
436                 void ComputeMaxStack (InstructionCollection instructions)
437                 {
438                         int current = 0;
439                         int max = 0;
440                         m_stackSizes.Clear ();
441
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;
449                                 }
450                         }
451
452                         foreach (Instruction instr in instructions) {
453
454                                 object savedSize = m_stackSizes [instr];
455                                 if (savedSize != null)
456                                         current = (int) savedSize;
457
458                                 current -= GetPopDelta (instructions.Container.Method, instr, current);
459
460                                 if (current < 0)
461                                         current = 0;
462
463                                 current += GetPushDelta (instr);
464
465                                 if (current > max)
466                                         max = current;
467
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;
478                                 }
479
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;
487                                 }
488                         }
489
490                         instructions.Container.MaxStack = max + 1; // you never know
491                 }
492
493                 static int GetPushDelta (Instruction instruction)
494                 {
495                         OpCode code = instruction.OpCode;
496                         switch (code.StackBehaviourPush) {
497                         case StackBehaviour.Push0:
498                                 return 0;
499
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;
507
508                         case StackBehaviour.Push1_push1:
509                                 return 2;
510
511                         case StackBehaviour.Varpush:
512                                 if (code.FlowControl != FlowControl.Call)
513                                         break;
514
515                                 IMethodSignature method = (IMethodSignature) instruction.Operand;
516                                 return IsVoid (method.ReturnType.ReturnType) ? 0 : 1;
517                         }
518
519                         throw new NotSupportedException ();
520                 }
521
522                 static int GetPopDelta (MethodDefinition current, Instruction instruction, int height)
523                 {
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;
532
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;
542
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;
550
551                         case StackBehaviour.PopAll:
552                                 return height;
553
554                         case StackBehaviour.Varpop:
555                                 if (code == OpCodes.Ret)
556                                         return IsVoid (current.ReturnType.ReturnType) ? 0 : 1;
557
558                                 if (code.FlowControl != FlowControl.Call)
559                                         break;
560
561                                 IMethodSignature method = (IMethodSignature) instruction.Operand;
562                                 int count = method.HasParameters ? method.Parameters.Count : 0;
563                                 if (method.HasThis && code != OpCodes.Newobj)
564                                         ++count;
565
566                                 return count;
567                         }
568
569                         throw new NotSupportedException ();
570                 }
571
572                 static bool IsVoid (TypeReference type)
573                 {
574                         return type.FullName == Constants.Void;
575                 }
576         }
577 }