Fixed Optimize for ldc_i4
[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 + last.GetSize () : end.Offset) - start.Offset;
247                 }
248
249                 static bool IsRangeFat (Instruction start, Instruction end, InstructionCollection instructions)
250                 {
251                         return GetLength (start, end, instructions) >= 256 ||
252                                 start.Offset >= 65536;
253                 }
254
255                 static bool IsFat (ExceptionHandlerCollection seh)
256                 {
257                         for (int i = 0; i < seh.Count; i++) {
258                                 ExceptionHandler eh = seh [i];
259                                 if (IsRangeFat (eh.TryStart, eh.TryEnd, seh.Container.Instructions))
260                                         return true;
261
262                                 if (IsRangeFat (eh.HandlerStart, eh.HandlerEnd, seh.Container.Instructions))
263                                         return true;
264
265                                 switch (eh.Type) {
266                                 case ExceptionHandlerType.Filter :
267                                         if (IsRangeFat (eh.FilterStart, eh.FilterEnd, seh.Container.Instructions))
268                                                 return true;
269                                         break;
270                                 }
271                         }
272
273                         return false;
274                 }
275
276                 void WriteExceptionHandlerCollection (ExceptionHandlerCollection seh)
277                 {
278                         m_codeWriter.QuadAlign ();
279
280                         if (seh.Count < 0x15 && !IsFat (seh)) {
281                                 m_codeWriter.Write ((byte) MethodDataSection.EHTable);
282                                 m_codeWriter.Write ((byte) (seh.Count * 12 + 4));
283                                 m_codeWriter.Write (new byte [2]);
284                                 foreach (ExceptionHandler eh in seh) {
285                                         m_codeWriter.Write ((ushort) eh.Type);
286                                         m_codeWriter.Write ((ushort) eh.TryStart.Offset);
287                                         m_codeWriter.Write ((byte) (eh.TryEnd.Offset - eh.TryStart.Offset));
288                                         m_codeWriter.Write ((ushort) eh.HandlerStart.Offset);
289                                         m_codeWriter.Write ((byte) GetLength (eh.HandlerStart, eh.HandlerEnd, seh.Container.Instructions));
290                                         WriteHandlerSpecific (eh);
291                                 }
292                         } else {
293                                 m_codeWriter.Write ((byte) (MethodDataSection.FatFormat | MethodDataSection.EHTable));
294                                 WriteFatBlockSize (seh);
295                                 foreach (ExceptionHandler eh in seh) {
296                                         m_codeWriter.Write ((uint) eh.Type);
297                                         m_codeWriter.Write ((uint) eh.TryStart.Offset);
298                                         m_codeWriter.Write ((uint) (eh.TryEnd.Offset - eh.TryStart.Offset));
299                                         m_codeWriter.Write ((uint) eh.HandlerStart.Offset);
300                                         m_codeWriter.Write ((uint) GetLength (eh.HandlerStart, eh.HandlerEnd, seh.Container.Instructions));
301                                         WriteHandlerSpecific (eh);
302                                 }
303                         }
304                 }
305
306                 void WriteFatBlockSize (ExceptionHandlerCollection seh)
307                 {
308                         int size = seh.Count * 24 + 4;
309                         m_codeWriter.Write ((byte) (size & 0xff));
310                         m_codeWriter.Write ((byte) ((size >> 8) & 0xff));
311                         m_codeWriter.Write ((byte) ((size >> 16) & 0xff));
312                 }
313
314                 void WriteHandlerSpecific (ExceptionHandler eh)
315                 {
316                         switch (eh.Type) {
317                         case ExceptionHandlerType.Catch :
318                                 WriteToken (eh.CatchType.MetadataToken);
319                                 break;
320                         case ExceptionHandlerType.Filter :
321                                 m_codeWriter.Write ((uint) eh.FilterStart.Offset);
322                                 break;
323                         default :
324                                 m_codeWriter.Write (0);
325                                 break;
326                         }
327                 }
328
329                 public override void VisitVariableDefinitionCollection (VariableDefinitionCollection variables)
330                 {
331                         MethodBody body = variables.Container as MethodBody;
332                         if (body == null)
333                                 return;
334
335                         uint sig = m_reflectWriter.SignatureWriter.AddLocalVarSig (
336                                         GetLocalVarSig (variables));
337
338                         if (m_localSigCache.Contains (sig)) {
339                                 body.LocalVarToken = (int) m_localSigCache [sig];
340                                 return;
341                         }
342
343                         StandAloneSigTable sasTable = m_reflectWriter.MetadataTableWriter.GetStandAloneSigTable ();
344                         StandAloneSigRow sasRow = m_reflectWriter.MetadataRowWriter.CreateStandAloneSigRow (
345                                 sig);
346
347                         sasTable.Rows.Add (sasRow);
348                         body.LocalVarToken = sasTable.Rows.Count;
349                         m_localSigCache [sig] = body.LocalVarToken;
350                 }
351
352                 public override void TerminateMethodBody (MethodBody body)
353                 {
354                         long pos = m_binaryWriter.BaseStream.Position;
355
356                         if (body.Variables.Count > 0 || body.ExceptionHandlers.Count > 0
357                                 || m_codeWriter.BaseStream.Length >= 64 || body.MaxStack > 8) {
358
359                                 MethodHeader header = MethodHeader.FatFormat;
360                                 if (body.InitLocals)
361                                         header |= MethodHeader.InitLocals;
362                                 if (body.ExceptionHandlers.Count > 0)
363                                         header |= MethodHeader.MoreSects;
364
365                                 m_binaryWriter.Write ((byte) header);
366                                 m_binaryWriter.Write ((byte) 0x30); // (header size / 4) << 4
367                                 m_binaryWriter.Write ((short) body.MaxStack);
368                                 m_binaryWriter.Write ((int) m_codeWriter.BaseStream.Length);
369                                 m_binaryWriter.Write (((int) TokenType.Signature | body.LocalVarToken));
370
371                                 WriteExceptionHandlerCollection (body.ExceptionHandlers);
372                         } else
373                                 m_binaryWriter.Write ((byte) ((byte) MethodHeader.TinyFormat |
374                                         m_codeWriter.BaseStream.Length << 2));
375
376                         m_binaryWriter.Write (m_codeWriter);
377                         m_binaryWriter.QuadAlign ();
378
379                         m_reflectWriter.MetadataWriter.AddData (
380                                 (int) (m_binaryWriter.BaseStream.Position - pos));
381                 }
382
383                 public LocalVarSig.LocalVariable GetLocalVariableSig (VariableDefinition var)
384                 {
385                         LocalVarSig.LocalVariable lv = new LocalVarSig.LocalVariable ();
386                         TypeReference type = var.VariableType;
387
388                         lv.CustomMods = m_reflectWriter.GetCustomMods (type);
389
390                         if (type is PinnedType) {
391                                 lv.Constraint |= Constraint.Pinned;
392                                 type = (type as PinnedType).ElementType;
393                         }
394
395                         if (type is ReferenceType) {
396                                 lv.ByRef = true;
397                                 type = (type as ReferenceType).ElementType;
398                         }
399
400                         lv.Type = m_reflectWriter.GetSigType (type);
401
402                         return lv;
403                 }
404
405                 public LocalVarSig GetLocalVarSig (VariableDefinitionCollection vars)
406                 {
407                         LocalVarSig lvs = new LocalVarSig ();
408                         lvs.CallingConvention |= 0x7;
409                         lvs.Count = vars.Count;
410                         lvs.LocalVariables = new LocalVarSig.LocalVariable [lvs.Count];
411                         for (int i = 0; i < lvs.Count; i++) {
412                                 lvs.LocalVariables [i] = GetLocalVariableSig (vars [i]);
413                         }
414
415                         return lvs;
416                 }
417
418                 static void ComputeMaxStack (InstructionCollection instructions)
419                 {
420                         InstructionCollection ehs = new InstructionCollection (null);
421                         foreach (ExceptionHandler eh in instructions.Container.ExceptionHandlers) {
422                                 switch (eh.Type) {
423                                 case ExceptionHandlerType.Catch :
424                                         ehs.Add (eh.HandlerStart);
425                                         break;
426                                 case ExceptionHandlerType.Filter :
427                                         ehs.Add (eh.FilterStart);
428                                         break;
429                                 }
430                         }
431
432                         int max = 0;
433                         foreach (Instruction instr in instructions) {
434                                 if (ehs.Contains (instr))
435                                         max++;
436
437                                 switch (instr.OpCode.StackBehaviourPush) {
438                                 case StackBehaviour.Push1:
439                                 case StackBehaviour.Pushi:
440                                 case StackBehaviour.Pushi8:
441                                 case StackBehaviour.Pushr4:
442                                 case StackBehaviour.Pushr8:
443                                 case StackBehaviour.Pushref:
444                                 case StackBehaviour.Varpush:
445                                         max++;
446                                         break;
447                                 case StackBehaviour.Push1_push1:
448                                         max += 2;
449                                         break;
450                                 }
451
452                                 if (instr.OpCode.OperandType == OperandType.InlineMethod) {
453                                         IMethodSignature signature = instr.Operand as IMethodSignature;
454                                         if (signature != null && signature.ReturnType.ReturnType.FullName != Constants.Void)
455                                                 max++;
456                                 }
457                         }
458
459                         instructions.Container.MaxStack = max;
460                 }
461         }
462 }