context: //
  // CodeWriter.cs
  //
  // Author:
  //   Jb Evain (jbevain@gmail.com)
  //
  // Copyright (c) 2008 - 2011 Jb Evain
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  //
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  
  using System;
  using System.Collections.Generic;
  
  using Mono.Collections.Generic;
  
  using Mono.Cecil.Metadata;
  using Mono.Cecil.PE;
  
  using RVA = System.UInt32;
  
  #if !READ_ONLY
  
  namespace Mono.Cecil.Cil {
  
  	sealed class CodeWriter : ByteBuffer {
  
  		readonly RVA code_base;
  		internal readonly MetadataBuilder metadata;
  		readonly Dictionary<uint, MetadataToken> standalone_signatures;
  
  		RVA current;
  		MethodBody body;
  
  		public CodeWriter (MetadataBuilder metadata)
  			: base (0)
  		{
  			this.code_base = metadata.text_map.GetNextRVA (TextSegment.CLIHeader);
  			this.current = code_base;
  			this.metadata = metadata;
  			this.standalone_signatures = new Dictionary<uint, MetadataToken> ();
  		}
  
  		public RVA WriteMethodBody (MethodDefinition method)
  		{
  			var rva = BeginMethod ();
  
  			if (IsUnresolved (method)) {
  				if (method.rva == 0)
  					return 0;
  
  				WriteUnresolvedMethodBody (method);
  			} else {
  				if (IsEmptyMethodBody (method.Body))
  					return 0;
  
  				WriteResolvedMethodBody (method);
  			}
  
  			Align (4);
  
  			EndMethod ();
  			return rva;
  		}
  
  		static bool IsEmptyMethodBody (MethodBody body)
  		{
  			return body.instructions.IsNullOrEmpty ()
  				&& body.variables.IsNullOrEmpty ();
  		}
  
  		static bool IsUnresolved (MethodDefinition method)
  		{
  			return method.HasBody && method.HasImage && method.body == null;
  		}
  
  		void WriteUnresolvedMethodBody (MethodDefinition method)
  		{
  			var code_reader = metadata.module.Read (method, (_, reader) => reader.code);
  
  			MethodSymbols symbols;
  			var buffer = code_reader.PatchRawMethodBody (method, this, out symbols);
  
  			WriteBytes (buffer);
  
  			if (symbols.instructions.IsNullOrEmpty ())
  				return;
  
  			symbols.method_token = method.token;
  			symbols.local_var_token = GetLocalVarToken (buffer, symbols);
  
  			var symbol_writer = metadata.symbol_writer;
  			if (symbol_writer != null)
  				symbol_writer.Write (symbols);
  		}
  
  		static MetadataToken GetLocalVarToken (ByteBuffer buffer, MethodSymbols symbols)
  		{
  			if (symbols.variables.IsNullOrEmpty ())
  				return MetadataToken.Zero;
  
  			buffer.position = 8;
  			return new MetadataToken (buffer.ReadUInt32 ());
  		}
  
  		void WriteResolvedMethodBody (MethodDefinition method)
  		{
  			body = method.Body;
  			ComputeHeader ();
  			if (RequiresFatHeader ())
  				WriteFatHeader ();
  			else
  				WriteByte ((byte) (0x2 | (body.CodeSize << 2))); // tiny
  
  			WriteInstructions ();
  
  			if (body.HasExceptionHandlers)
  				WriteExceptionHandlers ();
  
  			var symbol_writer = metadata.symbol_writer;
  			if (symbol_writer != null)
  				symbol_writer.Write (body);
  		}
  
  		void WriteFatHeader ()
  		{
  			var body = this.body;
  			byte flags = 0x3;	// fat
  			if (body.InitLocals)
  				flags |= 0x10;	// init locals
  			if (body.HasExceptionHandlers)
  				flags |= 0x8;	// more sections
  
  			WriteByte (flags);
  			WriteByte (0x30);
  			WriteInt16 ((short) body.max_stack_size);
  			WriteInt32 (body.code_size);
  			body.local_var_token = body.HasVariables
  				? GetStandAloneSignature (body.Variables)
  				: MetadataToken.Zero;
  			WriteMetadataToken (body.local_var_token);
  		}
  
  		void WriteInstructions ()
  		{
  			var instructions = body.Instructions;
  			var items = instructions.items;
  			var size = instructions.size;
  
  			for (int i = 0; i < size; i++) {
  				var instruction = items [i];
  				WriteOpCode (instruction.opcode);
  				WriteOperand (instruction);
  			}
  		}
  
  		void WriteOpCode (OpCode opcode)
  		{
  			if (opcode.Size == 1) {
  				WriteByte (opcode.Op2);
  			} else {
  				WriteByte (opcode.Op1);
  				WriteByte (opcode.Op2);
  			}
  		}
  
  		void WriteOperand (Instruction instruction)
  		{
  			var opcode = instruction.opcode;
  			var operand_type = opcode.OperandType;
  			if (operand_type == OperandType.InlineNone)
  				return;
  
  			var operand = instruction.operand;
  			if (operand == null)
  				throw new ArgumentException ();
  
  			switch (operand_type) {
  			case OperandType.InlineSwitch: {
  				var targets = (Instruction []) operand;
  				WriteInt32 (targets.Length);
  				var diff = instruction.Offset + opcode.Size + (4 * (targets.Length + 1));
  				for (int i = 0; i < targets.Length; i++)
  					WriteInt32 (GetTargetOffset (targets [i]) - diff);
  				break;
  			}
  			case OperandType.ShortInlineBrTarget: {
  				var target = (Instruction) operand;
  				WriteSByte ((sbyte) (GetTargetOffset (target) - (instruction.Offset + opcode.Size + 1)));
  				break;
  			}
  			case OperandType.InlineBrTarget: {
  				var target = (Instruction) operand;
  				WriteInt32 (GetTargetOffset (target) - (instruction.Offset + opcode.Size + 4));
  				break;
  			}
  			case OperandType.ShortInlineVar:
  				WriteByte ((byte) GetVariableIndex ((VariableDefinition) operand));
  				break;
  			case OperandType.ShortInlineArg:
  				WriteByte ((byte) GetParameterIndex ((ParameterDefinition) operand));
  				break;
  			case OperandType.InlineVar:
  				WriteInt16 ((short) GetVariableIndex ((VariableDefinition) operand));
  				break;
  			case OperandType.InlineArg:
  				WriteInt16 ((short) GetParameterIndex ((ParameterDefinition) operand));
  				break;
  			case OperandType.InlineSig:
  				WriteMetadataToken (GetStandAloneSignature ((CallSite) operand));
  				break;
  			case OperandType.ShortInlineI:
  				if (opcode == OpCodes.Ldc_I4_S)
  					WriteSByte ((sbyte) operand);
  				else
  					WriteByte ((byte) operand);
  				break;
  			case OperandType.InlineI:
  				WriteInt32 ((int) operand);
  				break;
  			case OperandType.InlineI8:
  				WriteInt64 ((long) operand);
  				break;
  			case OperandType.ShortInlineR:
  				WriteSingle ((float) operand);
  				break;
  			case OperandType.InlineR:
  				WriteDouble ((double) operand);
  				break;
  			case OperandType.InlineString:
  				WriteMetadataToken (
  					new MetadataToken (
  						TokenType.String,
  						GetUserStringIndex ((string) operand)));
  				break;
  			case OperandType.InlineType:
  			case OperandType.InlineField:
  			case OperandType.InlineMethod:
  			case OperandType.InlineTok:
  				WriteMetadataToken (metadata.LookupToken ((IMetadataTokenProvider) operand));
  				break;
  			default:
  				throw new ArgumentException ();
  			}
  		}
  
  		int GetTargetOffset (Instruction instruction)
  		{
  			if (instruction == null) {
  				var last = body.instructions [body.instructions.size - 1];
  				return last.offset + last.GetSize ();
  			}
  
  			return instruction.offset;
  		}
  
  		uint GetUserStringIndex (string @string)
  		{
  			if (@string == null)
  				return 0;
  
  			return metadata.user_string_heap.GetStringIndex (@string);
  		}
  
  		static int GetVariableIndex (VariableDefinition variable)
  		{
  			return variable.Index;
  		}
  
  		int GetParameterIndex (ParameterDefinition parameter)
  		{
  			if (body.method.HasThis) {
  
gt: 				if (parameter == body.this_parameter)