text: ﻿#if FEAT_COMPILER
  //#define DEBUG_COMPILE
  using System;
  using System.Threading;
  using ProtoBuf.Meta;
  using ProtoBuf.Serializers;
  
  #if FEAT_IKVM
  using Type = IKVM.Reflection.Type;
  using IKVM.Reflection;
  using IKVM.Reflection.Emit;
  #else
  using System.Reflection;
  using System.Reflection.Emit;
  #endif
  
  
  namespace ProtoBuf.Compiler
  {
      internal struct CodeLabel
      {
          public readonly Label Value;
          public readonly int Index;
          public CodeLabel(Label value, int index)
          {
              this.Value = value;
              this.Index = index;
          }
      }
      internal class CompilerContext
      {
          public TypeModel Model { get { return model; } }
  
  #if !(FX11 || FEAT_IKVM)
          readonly DynamicMethod method;
          static int next;
  #endif
  
          internal CodeLabel DefineLabel()
          {
              CodeLabel result = new CodeLabel(il.DefineLabel(), nextLabel++);
              return result;
          }
          internal void MarkLabel(CodeLabel label)
          {
              il.MarkLabel(label.Value);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine("#: " + label.Index);
  #endif
          }
  
  #if !(FX11 || FEAT_IKVM)
          public static ProtoSerializer BuildSerializer(IProtoSerializer head, TypeModel model)
          {
              Type type = head.ExpectedType;
              CompilerContext ctx = new CompilerContext(type, true, true, model);
              ctx.LoadValue(Local.InputValue);
              ctx.CastFromObject(type);
              ctx.WriteNullCheckedTail(type, head, null);
              ctx.Emit(OpCodes.Ret);
              return (ProtoSerializer)ctx.method.CreateDelegate(
                  typeof(ProtoSerializer));
          }
          /*public static ProtoCallback BuildCallback(IProtoTypeSerializer head)
          {
              Type type = head.ExpectedType;
              CompilerContext ctx = new CompilerContext(type, true, true);
              using (Local typedVal = new Local(ctx, type))
              {
                  ctx.LoadValue(Local.InputValue);
                  ctx.CastFromObject(type);
                  ctx.StoreValue(typedVal);
                  CodeLabel[] jumpTable = new CodeLabel[4];
                  for(int i = 0 ; i < jumpTable.Length ; i++) {
                      jumpTable[i] = ctx.DefineLabel();
                  }
                  ctx.LoadReaderWriter();
                  ctx.Switch(jumpTable);
                  ctx.Return();
                  for(int i = 0 ; i < jumpTable.Length ; i++) {
                      ctx.MarkLabel(jumpTable[i]);
                      if (head.HasCallbacks((TypeModel.CallbackType)i))
                      {
                          head.EmitCallback(ctx, typedVal, (TypeModel.CallbackType)i);
                      }
                      ctx.Return();
                  }                
              }
              
              ctx.Emit(OpCodes.Ret);
              return (ProtoCallback)ctx.method.CreateDelegate(
                  typeof(ProtoCallback));
          }*/
          public static ProtoDeserializer BuildDeserializer(IProtoSerializer head, TypeModel model)
          {
              Type type = head.ExpectedType;
              CompilerContext ctx = new CompilerContext(type, false, true, model);
              
              using (Local typedVal = new Local(ctx, type))
              {
                  if (!type.IsValueType)
                  {
                      ctx.LoadValue(Local.InputValue);
                      ctx.CastFromObject(type);
                      ctx.StoreValue(typedVal);
                  }
                  else
                  {   
                      ctx.LoadValue(Local.InputValue);
                      CodeLabel notNull = ctx.DefineLabel(), endNull = ctx.DefineLabel();
                      ctx.BranchIfTrue(notNull, true);
  
                      ctx.LoadAddress(typedVal, type);
                      ctx.EmitCtor(type);
                      ctx.Branch(endNull, true);
  
                      ctx.MarkLabel(notNull);
                      ctx.LoadValue(Local.InputValue);
                      ctx.CastFromObject(type);
                      ctx.StoreValue(typedVal);
  
                      ctx.MarkLabel(endNull);
                  }
                  head.EmitRead(ctx, typedVal);
  
                  if (head.ReturnsValue) {
                      ctx.StoreValue(typedVal);
                  }
  
                  ctx.LoadValue(typedVal);
                  ctx.CastToObject(type);
              }
              ctx.Emit(OpCodes.Ret);
              return (ProtoDeserializer)ctx.method.CreateDelegate(
                  typeof(ProtoDeserializer));
          }
  #endif
          internal void Return()
          {
              Emit(OpCodes.Ret);
          }
  
          static bool IsObject(Type type)
          {
  #if FEAT_IKVM
              return type.FullName == "System.Object";
  #else
              return type == typeof(object);
  #endif
          }
          internal void CastToObject(Type type)
          {
              if(IsObject(type))
              { }
              else if (type.IsValueType)
              {
                  il.Emit(OpCodes.Box, type);
  #if DEBUG_COMPILE
                  Helpers.DebugWriteLine(OpCodes.Box + ": " + type);
  #endif
              }
              else
              {
                  il.Emit(OpCodes.Castclass, MapType(typeof(object)));
  #if DEBUG_COMPILE
                  Helpers.DebugWriteLine(OpCodes.Castclass + ": " + type);
  #endif
              }
          }
  
          internal void CastFromObject(Type type)
          {
              if (IsObject(type))
              { }
              else if (type.IsValueType)
              {
                  switch (MetadataVersion)
                  {
                      case ILVersion.Net1:
                          il.Emit(OpCodes.Unbox, type);
                          il.Emit(OpCodes.Ldobj, type);
  #if DEBUG_COMPILE
                          Helpers.DebugWriteLine(OpCodes.Unbox + ": " + type);
                          Helpers.DebugWriteLine(OpCodes.Ldobj + ": " + type);
  #endif
                          break;
                      default:
  #if FX11
                          throw new NotSupportedException();
  #else
                          il.Emit(OpCodes.Unbox_Any, type);
  #if DEBUG_COMPILE
                          Helpers.DebugWriteLine(OpCodes.Unbox_Any + ": " + type);
  #endif
                          break;
  #endif
                  }
              }
              else
              {
                  il.Emit(OpCodes.Castclass, type);
  #if DEBUG_COMPILE
                  Helpers.DebugWriteLine(OpCodes.Castclass + ": " + type);
  #endif
              }
          }
          private readonly bool isStatic;
  #if !SILVERLIGHT
          private readonly RuntimeTypeModel.SerializerPair[] methodPairs;
  
          internal MethodBuilder GetDedicatedMethod(int metaKey, bool read)
          {
              if (methodPairs == null) return null;
              // but if we *do* have pairs, we demand that we find a match...
              for (int i = 0; i < methodPairs.Length; i++ )
              {
                  if (methodPairs[i].MetaKey == metaKey) { return read ? methodPairs[i].Deserialize : methodPairs[i].Serialize; }
              }
              throw new ArgumentException("Meta-key not found", "metaKey");
          }
  
          internal int MapMetaKeyToCompiledKey(int metaKey)
          {
              if (metaKey < 0 || methodPairs == null) return metaKey; // all meta, or a dummy/wildcard key
  
              for (int i = 0; i < methodPairs.Length; i++)
              {
                  if (methodPairs[i].MetaKey == metaKey) return i;
              }
              throw new ArgumentException("Key could not be mapped: " + metaKey, "metaKey");
          }
  #else
          internal int MapMetaKeyToCompiledKey(int metaKey)
          {
              return metaKey;
          }
  #endif
  
  
  
          private readonly bool nonPublic, isWriter;
          internal bool NonPublic { get { return nonPublic; } }
  
  
  #if !(SILVERLIGHT || PHONE8)
          private readonly string assemblyName;
          internal CompilerContext(ILGenerator il, bool isStatic, bool isWriter, RuntimeTypeModel.SerializerPair[] methodPairs, TypeModel model, ILVersion metadataVersion, string assemblyName)
          {
              if (il == null) throw new ArgumentNullException("il");
              if (methodPairs == null) throw new ArgumentNullException("methodPairs");
              if (model == null) throw new ArgumentNullException("model");
              if (Helpers.IsNullOrEmpty(assemblyName)) throw new ArgumentNullException("assemblyName");
              this.assemblyName = assemblyName;
              this.isStatic = isStatic;
              this.methodPairs = methodPairs;
              this.il = il;
              nonPublic = false;
              this.isWriter = isWriter;
              this.model = model;
              this.metadataVersion = metadataVersion;
          }
  #endif
  #if !(FX11 || FEAT_IKVM)
          private CompilerContext(Type associatedType, bool isWriter, bool isStatic, TypeModel model)
          {
              if (model == null) throw new ArgumentNullException("model");
  #if FX11
              metadataVersion = ILVersion.Net1;
  #else
              metadataVersion = ILVersion.Net2;
  #endif
              this.isStatic = isStatic;
              this.isWriter = isWriter;
              this.model = model;
              nonPublic = true;
              Type[] paramTypes;
              Type returnType;
              if (isWriter)
              {
                  returnType = typeof(void);
                  paramTypes = new Type[] { typeof(object), typeof(ProtoWriter) };
              }
              else
              {
                  returnType = typeof(object);
                  paramTypes = new Type[] { typeof(object), typeof(ProtoReader) };
              }
              int uniqueIdentifier;
  #if PLAT_NO_INTERLOCKED
              uniqueIdentifier = ++next;
  #else
              uniqueIdentifier = Interlocked.Increment(ref next);
  #endif
              method = new DynamicMethod("proto_" + uniqueIdentifier.ToString(), returnType, paramTypes, associatedType.IsInterface ? typeof(object) : associatedType, true);
              this.il = method.GetILGenerator();
          }
  #endif
          private readonly ILGenerator il;
  
          private void Emit(OpCode opcode)
          {
              il.Emit(opcode);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(opcode.ToString());
  #endif
          }
          public void LoadValue(string value)
          {
              if (value == null)
              {
                  LoadNullRef();
              }
              else
              {
                  il.Emit(OpCodes.Ldstr, value);
  #if DEBUG_COMPILE
                  Helpers.DebugWriteLine(OpCodes.Ldstr + ": " + value);
  #endif
              }
          }
          public void LoadValue(float value)
          {
              il.Emit(OpCodes.Ldc_R4, value);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(OpCodes.Ldc_R4 + ": " + value);
  #endif
          }
          public void LoadValue(double value)
          {
              il.Emit(OpCodes.Ldc_R8, value);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(OpCodes.Ldc_R8 + ": " + value);
  #endif
          }
          public void LoadValue(long value)
          {
              il.Emit(OpCodes.Ldc_I8, value);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(OpCodes.Ldc_I8 + ": " + value);
  #endif
          }
          public void LoadValue(int value)
          {
              switch (value)
              {
                  case 0: Emit(OpCodes.Ldc_I4_0); break;
                  case 1: Emit(OpCodes.Ldc_I4_1); break;
                  case 2: Emit(OpCodes.Ldc_I4_2); break;
                  case 3: Emit(OpCodes.Ldc_I4_3); break;
                  case 4: Emit(OpCodes.Ldc_I4_4); break;
                  case 5: Emit(OpCodes.Ldc_I4_5); break;
                  case 6: Emit(OpCodes.Ldc_I4_6); break;
                  case 7: Emit(OpCodes.Ldc_I4_7); break;
                  case 8: Emit(OpCodes.Ldc_I4_8); break;
                  case -1: Emit(OpCodes.Ldc_I4_M1); break;
                  default:
                      if (value >= -128 && value <= 127)
                      {
                          il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
  #if DEBUG_COMPILE
                          Helpers.DebugWriteLine(OpCodes.Ldc_I4_S + ": " + value);
  #endif
                      }
                      else
                      {
                          il.Emit(OpCodes.Ldc_I4, value);
  #if DEBUG_COMPILE
                          Helpers.DebugWriteLine(OpCodes.Ldc_I4 + ": " + value);
  #endif
                      }
                      break;
  
              }
          }
  
          MutableList locals = new MutableList();
          internal LocalBuilder GetFromPool(Type type)
          {
              int count = locals.Count;
              for (int i = 0; i < count; i++)
              {
                  LocalBuilder item = (LocalBuilder)locals[i];
                  if (item != null && item.LocalType == type)
                  {
                      locals[i] = null; // remove from pool
                      return item;
                  }
              }
              LocalBuilder result = il.DeclareLocal(type);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine("$ " + result + ": " + type);
  #endif
              return result;
          }
          //
          internal void ReleaseToPool(LocalBuilder value)
          {
              int count = locals.Count;
              for (int i = 0; i < count; i++)
              {
                  if (locals[i] == null)
                  {
                      locals[i] = value; // released into existing slot
                      return;
                  }
              }
              locals.Add(value); // create a new slot
          }
          public void LoadReaderWriter()
          {
              Emit(isStatic ? OpCodes.Ldarg_1 : OpCodes.Ldarg_2);
          }
          public void StoreValue(Local local)
          {
              if (local == Local.InputValue)
              {
                  byte b = isStatic ? (byte) 0 : (byte)1;
                  il.Emit(OpCodes.Starg_S, b);
  #if DEBUG_COMPILE
                  Helpers.DebugWriteLine(OpCodes.Starg_S + ": $" + b);
  #endif                
              }
              else
              {
  #if !FX11
                  switch (local.Value.LocalIndex)
                  {
                      case 0: Emit(OpCodes.Stloc_0); break;
                      case 1: Emit(OpCodes.Stloc_1); break;
                      case 2: Emit(OpCodes.Stloc_2); break;
                      case 3: Emit(OpCodes.Stloc_3); break;
                      default:
  #endif
                          OpCode code = UseShortForm(local) ? OpCodes.Stloc_S : OpCodes.Stloc;
                          il.Emit(code, local.Value);
  #if DEBUG_COMPILE
                          Helpers.DebugWriteLine(code + ": $" + local.Value);
  #endif
  #if !FX11
                          break;
                  }
  #endif
              }
          }
          public void LoadValue(Local local)
          {
              if (local == null) { /* nothing to do; top of stack */}
              else if (local == Local.InputValue)
              {
                  Emit(isStatic ? OpCodes.Ldarg_0 : OpCodes.Ldarg_1);
              }
              else
              {
  #if !FX11
                  switch (local.Value.LocalIndex)
                  {
                      case 0: Emit(OpCodes.Ldloc_0); break;
                      case 1: Emit(OpCodes.Ldloc_1); break;
                      case 2: Emit(OpCodes.Ldloc_2); break;
                      case 3: Emit(OpCodes.Ldloc_3); break;
                      default:
  #endif             
                          OpCode code = UseShortForm(local) ? OpCodes.Ldloc_S :  OpCodes.Ldloc;
                          il.Emit(code, local.Value);
  #if DEBUG_COMPILE
                          Helpers.DebugWriteLine(code + ": $" + local.Value);
  #endif
  #if !FX11
                          break;
                  }
  #endif
              }
          }
          public Local GetLocalWithValue(Type type, Compiler.Local fromValue)
          {
              if (fromValue != null) { return fromValue.AsCopy(); }
              // need to store the value from the stack
              Local result = new Local(this, type);
              StoreValue(result);
              return result;
          }
          internal void EmitBasicRead(string methodName, Type expectedType)
          {
              MethodInfo method = MapType(typeof(ProtoReader)).GetMethod(
                  methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
              if (method == null || method.ReturnType != expectedType
                  || method.GetParameters().Length != 0) throw new ArgumentException("methodName");
              LoadReaderWriter();
              EmitCall(method);            
          }
          internal void EmitBasicRead(Type helperType, string methodName, Type expectedType)
          {
              MethodInfo method = helperType.GetMethod(
                  methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
              if (method == null || method.ReturnType != expectedType
                  || method.GetParameters().Length != 1) throw new ArgumentException("methodName");
              LoadReaderWriter();
              EmitCall(method);
          }
          internal void EmitBasicWrite(string methodName, Compiler.Local fromValue)
          {
              if (Helpers.IsNullOrEmpty(methodName)) throw new ArgumentNullException("methodName");
              LoadValue(fromValue);
              LoadReaderWriter();
              EmitCall(GetWriterMethod(methodName));
          }
          private MethodInfo GetWriterMethod(string methodName)
          {
              Type writerType = MapType(typeof(ProtoWriter));
              MethodInfo[] methods = writerType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
              foreach (MethodInfo method in methods)
              {
                  if(method.Name != methodName) continue;
                  ParameterInfo[] pis = method.GetParameters();
                  if (pis.Length == 2 && pis[1].ParameterType == writerType) return method;
              }
              throw new ArgumentException("No suitable method found for: " + methodName, "methodName");
          }
          internal void EmitWrite(Type helperType, string methodName, Compiler.Local valueFrom)
          {
              if (Helpers.IsNullOrEmpty(methodName)) throw new ArgumentNullException("methodName");
              MethodInfo method = helperType.GetMethod(
                  methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
              if (method == null || method.ReturnType != MapType(typeof(void))) throw new ArgumentException("methodName");
              LoadValue(valueFrom);
              LoadReaderWriter();
              EmitCall(method);
          }
          public void EmitCall(MethodInfo method)
          {
              Helpers.DebugAssert(method != null);
              CheckAccessibility(method);
              OpCode opcode = (method.IsStatic || method.DeclaringType.IsValueType) ? OpCodes.Call : OpCodes.Callvirt;
              il.EmitCall(opcode, method, null);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(opcode + ": " + method + " on " + method.DeclaringType);
  #endif
          }
          /// <summary>
          /// Pushes a null reference onto the stack. Note that this should only
          /// be used to return a null (or set a variable to null); for null-tests
          /// use BranchIfTrue / BranchIfFalse.
          /// </summary>
          public void LoadNullRef()
          {
              Emit(OpCodes.Ldnull);
          }
  
          private int nextLabel;
  
          internal void WriteNullCheckedTail(Type type, IProtoSerializer tail, Compiler.Local valueFrom)
          {
              if (type.IsValueType)
              {
                  Type underlyingType = null;
  #if !FX11
                  underlyingType = Helpers.GetUnderlyingType(type);
  #endif
                  if (underlyingType == null)
                  { // not a nullable T; can invoke directly
                      tail.EmitWrite(this, valueFrom);
                  }
                  else
                  { // nullable T; check HasValue
                      using (Compiler.Local valOrNull = GetLocalWithValue(type, valueFrom))
                      {
                          LoadAddress(valOrNull, type);
                          LoadValue(type.GetProperty("HasValue"));
                          CodeLabel @end = DefineLabel();
                          BranchIfFalse(@end, false);
                          LoadAddress(valOrNull, type);
                          EmitCall(type.GetMethod("GetValueOrDefault", Helpers.EmptyTypes));
                          tail.EmitWrite(this, null);
                          MarkLabel(@end);
                      }
                  }
              }
              else
              { // ref-type; do a null-check
                  LoadValue(valueFrom);
                  CopyValue();
                  CodeLabel hasVal = DefineLabel(), @end = DefineLabel();
                  BranchIfTrue(hasVal, true);
                  DiscardValue();
                  Branch(@end, false);
                  MarkLabel(hasVal);
                  tail.EmitWrite(this, null);
                  MarkLabel(@end);
              }
          }
  
          internal void ReadNullCheckedTail(Type type, IProtoSerializer tail, Compiler.Local valueFrom)
          {
  #if !FX11
              Type underlyingType;
              
              if (type.IsValueType && (underlyingType = Helpers.GetUnderlyingType(type)) != null)
              {
                  if(tail.RequiresOldValue)
                  {
                      // we expect the input value to be in valueFrom; need to unpack it from T?
                      using (Local loc = GetLocalWithValue(type, valueFrom))
                      {
                          LoadAddress(loc, type);
                          EmitCall(type.GetMethod("GetValueOrDefault", Helpers.EmptyTypes));
                      }
                  }
                  else
                  {
                      Helpers.DebugAssert(valueFrom == null); // not expecting a valueFrom in this case
                  }
                  tail.EmitRead(this, null); // either unwrapped on the stack or not provided
                  if (tail.ReturnsValue)
                  {
                      // now re-wrap the value
                      EmitCtor(type, underlyingType);
                  }
                  return;
              }
  #endif
              // either a ref-type of a non-nullable struct; treat "as is", even if null
              // (the type-serializer will handle the null case; it needs to allow null
              // inputs to perform the correct type of subclass creation)
              tail.EmitRead(this, valueFrom);
          }
  
          public void EmitCtor(Type type)
          {
              EmitCtor(type, Helpers.EmptyTypes);
          }
  
          public void EmitCtor(ConstructorInfo ctor)
          {
              if (ctor == null) throw new ArgumentNullException("ctor");
              CheckAccessibility(ctor);
              il.Emit(OpCodes.Newobj, ctor);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(OpCodes.Newobj + ": " + ctor.DeclaringType);
  #endif
          }
  
          public void EmitCtor(Type type, params Type[] parameterTypes)
          {
              Helpers.DebugAssert(type != null);
              Helpers.DebugAssert(parameterTypes != null);
              if (type.IsValueType && parameterTypes.Length == 0)
              {
                  il.Emit(OpCodes.Initobj, type);
  #if DEBUG_COMPILE
                  Helpers.DebugWriteLine(OpCodes.Initobj + ": " + type);
  #endif
              }
              else
              {
                  ConstructorInfo ctor =  Helpers.GetConstructor(type, parameterTypes, true);
                  if (ctor == null) throw new InvalidOperationException("No suitable constructor found for " + type.FullName);
                  EmitCtor(ctor);
              }
          }
  #if !(PHONE8 || SILVERLIGHT || FX11)
          BasicList knownTrustedAssemblies, knownUntrustedAssemblies;
  #endif
          bool InternalsVisible(Assembly assembly)
          {
  #if PHONE8 || SILVERLIGHT || FX11
              return false;
  #else
              if (Helpers.IsNullOrEmpty(assemblyName)) return false;
              if (knownTrustedAssemblies != null)
              {
                  if (knownTrustedAssemblies.IndexOfReference(assembly) >= 0)
                  {
                      return true;
                  }
              }
              if (knownUntrustedAssemblies != null)
              {
                  if (knownUntrustedAssemblies.IndexOfReference(assembly) >= 0)
                  {
                      return false;
                  }
              }
              bool isTrusted = false;
              Type attributeType = MapType(typeof(System.Runtime.CompilerServices.InternalsVisibleToAttribute));
              if(attributeType == null) return false;
  #if FEAT_IKVM
              foreach (CustomAttributeData attrib in assembly.__GetCustomAttributes(attributeType, false))
              {
                  if (attrib.ConstructorArguments.Count == 1)
                  {
                      string privelegedAssembly = attrib.ConstructorArguments[0].Value as string;
                      if (privelegedAssembly == assemblyName)
                      {
                          isTrusted = true;
                          break;
                      }
                  }
              }
  #else
              foreach(System.Runtime.CompilerServices.InternalsVisibleToAttribute attrib in assembly.GetCustomAttributes(attributeType, false))
              {
                  if (attrib.AssemblyName == assemblyName)
                  {
                      isTrusted = true;
                      break;
                  }
              }
  #endif
              if (isTrusted)
              {
                  if (knownTrustedAssemblies == null) knownTrustedAssemblies = new BasicList();
                  knownTrustedAssemblies.Add(assembly);
              }
              else
              {
                  if (knownUntrustedAssemblies == null) knownUntrustedAssemblies = new BasicList();
                  knownUntrustedAssemblies.Add(assembly);
              }
              return isTrusted;
  #endif
          }
          internal void CheckAccessibility(MemberInfo member)
          {
              if (member == null)
              {
                  throw new ArgumentNullException("member");
              }
  
              if (!NonPublic)
              {
                  bool isPublic;
                  switch (member.MemberType)
                  {
                      case MemberTypes.TypeInfo:
                          // top-level type
                          isPublic = ((Type)member).IsPublic || InternalsVisible(((Type)member).Assembly);
                          break;
                      case MemberTypes.NestedType:
                          Type type = (Type)member;
                          do
                          {
                              isPublic = type.IsNestedPublic || type.IsPublic || ((type.DeclaringType == null || type.IsNestedAssembly || type.IsNestedFamORAssem) && InternalsVisible(type.Assembly));
                          } while (isPublic && (type = type.DeclaringType) != null); // ^^^ !type.IsNested, but not all runtimes have that
                          break;
                      case MemberTypes.Field:
                          FieldInfo field = ((FieldInfo)member);
                          isPublic = field.IsPublic || ((field.IsAssembly || field.IsFamilyOrAssembly) && InternalsVisible(field.DeclaringType.Assembly));
                          break;
                      case MemberTypes.Constructor:
                          ConstructorInfo ctor = ((ConstructorInfo)member);
                          isPublic = ctor.IsPublic || ((ctor.IsAssembly || ctor.IsFamilyOrAssembly) && InternalsVisible(ctor.DeclaringType.Assembly));
                          break;
                      case MemberTypes.Method:
                          MethodInfo method = ((MethodInfo)member);
                          isPublic = method.IsPublic || ((method.IsAssembly || method.IsFamilyOrAssembly) && InternalsVisible(method.DeclaringType.Assembly));
                          if (!isPublic)
                          {
                              // allow calls to TypeModel protected methods, and methods we are in the process of creating
                              if(
  #if !SILVERLIGHT
                                  member is MethodBuilder ||
  #endif                
                                  member.DeclaringType == MapType(typeof(TypeModel))) isPublic = true; 
                          }
                          break;
                      case MemberTypes.Property:
                          isPublic = true; // defer to get/set
                          break;
                      default:
                          throw new NotSupportedException(member.MemberType.ToString());
                  }
                  if (!isPublic)
                  {
                      switch (member.MemberType)
                      {
                          case MemberTypes.TypeInfo:
                          case MemberTypes.NestedType:
                              throw new InvalidOperationException("Non-public type cannot be used with full dll compilation: " +
                                  ((Type)member).FullName);
                          default:
                              throw new InvalidOperationException("Non-public member cannot be used with full dll compilation: " +
                                  member.DeclaringType.FullName + "." + member.Name);
                      }
                      
                  }
              }
          }
  
          public void LoadValue(FieldInfo field)
          {
              CheckAccessibility(field);
              OpCode code = field.IsStatic ? OpCodes.Ldsfld : OpCodes.Ldfld;
              il.Emit(code, field);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(code + ": " + field + " on " + field.DeclaringType);
  #endif
          }
  #if FEAT_IKVM
          public void StoreValue(System.Reflection.FieldInfo field)
          {
              StoreValue(MapType(field.DeclaringType).GetField(field.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance));
          }
          public void StoreValue(System.Reflection.PropertyInfo property)
          {
              StoreValue(MapType(property.DeclaringType).GetProperty(property.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance));
          }
          public void LoadValue(System.Reflection.FieldInfo field)
          {
              LoadValue(MapType(field.DeclaringType).GetField(field.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance));
          }
          public void LoadValue(System.Reflection.PropertyInfo property)
          {
              LoadValue(MapType(property.DeclaringType).GetProperty(property.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance));
          }
  #endif
          public void StoreValue(FieldInfo field)
          {
              CheckAccessibility(field);
              OpCode code = field.IsStatic ? OpCodes.Stsfld : OpCodes.Stfld;
              il.Emit(code, field);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(code + ": " + field + " on " + field.DeclaringType);
  #endif
          }
          public void LoadValue(PropertyInfo property)
          {
              CheckAccessibility(property);
              EmitCall(Helpers.GetGetMethod(property, true, true));
          }
          public void StoreValue(PropertyInfo property)
          {
              CheckAccessibility(property);
              EmitCall(Helpers.GetSetMethod(property, true, true));
          }
  
          internal void EmitInstance()
          {
              if (isStatic) throw new InvalidOperationException();
              Emit(OpCodes.Ldarg_0);
          }
  
          internal static void LoadValue(ILGenerator il, int value)
          {
              switch (value)
              {
                  case 0: il.Emit(OpCodes.Ldc_I4_0); break;
                  case 1: il.Emit(OpCodes.Ldc_I4_1); break;
                  case 2: il.Emit(OpCodes.Ldc_I4_2); break;
                  case 3: il.Emit(OpCodes.Ldc_I4_3); break;
                  case 4: il.Emit(OpCodes.Ldc_I4_4); break;
                  case 5: il.Emit(OpCodes.Ldc_I4_5); break;
                  case 6: il.Emit(OpCodes.Ldc_I4_6); break;
                  case 7: il.Emit(OpCodes.Ldc_I4_7); break;
                  case 8: il.Emit(OpCodes.Ldc_I4_8); break;
                  case -1: il.Emit(OpCodes.Ldc_I4_M1); break;
                  default: il.Emit(OpCodes.Ldc_I4, value); break;
              }
          }
  
          private bool UseShortForm(Local local)
          {
  #if FX11
              return locals.Count < 256;
  #else
              return local.Value.LocalIndex < 256;
  #endif
          }
  #if FEAT_IKVM
          internal void LoadAddress(Local local, System.Type type)
          {
              LoadAddress(local, MapType(type));
          }
  #endif
          internal void LoadAddress(Local local, Type type)
          {
              if (type.IsValueType)
              {
                  if (local == null)
                  {
                      throw new InvalidOperationException("Cannot load the address of a struct at the head of the stack");
                  }
  
                  if (local == Local.InputValue)
                  {
                      il.Emit(OpCodes.Ldarga_S, (isStatic ? (byte)0 : (byte)1));
  #if DEBUG_COMPILE
                      Helpers.DebugWriteLine(OpCodes.Ldarga_S + ": $" + (isStatic ? 0 : 1));
  #endif
                  }
                  else
                  {
                      OpCode code = UseShortForm(local) ? OpCodes.Ldloca_S : OpCodes.Ldloca;
                      il.Emit(code, local.Value);
  #if DEBUG_COMPILE
                      Helpers.DebugWriteLine(code + ": $" + local.Value);
  #endif
                  }
  
              }
              else
              {   // reference-type; already *is* the address; just load it
                  LoadValue(local);
              }
          }
          internal void Branch(CodeLabel label, bool @short)
          {
              OpCode code = @short ? OpCodes.Br_S : OpCodes.Br;
              il.Emit(code, label.Value);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(code + ": " + label.Index);
  #endif
          }
          internal void BranchIfFalse(CodeLabel label, bool @short)
          {
              OpCode code = @short ? OpCodes.Brfalse_S :  OpCodes.Brfalse;
              il.Emit(code, label.Value);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(code + ": " + label.Index);
  #endif
          }
  
  
          internal void BranchIfTrue(CodeLabel label, bool @short)
          {
              OpCode code = @short ? OpCodes.Brtrue_S : OpCodes.Brtrue;
              il.Emit(code, label.Value);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(code + ": " + label.Index);
  #endif
          }
          internal void BranchIfEqual(CodeLabel label, bool @short)
          {
              OpCode code = @short ? OpCodes.Beq_S : OpCodes.Beq;
              il.Emit(code, label.Value);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(code + ": " + label.Index);
  #endif
          }
          internal void TestEqual()
          {
              Emit(OpCodes.Ceq);
          }
  
  
          internal void CopyValue()
          {
              Emit(OpCodes.Dup);
          }
  
          internal void BranchIfGreater(CodeLabel label, bool @short)
          {
              OpCode code = @short ? OpCodes.Bgt_S : OpCodes.Bgt;
              il.Emit(code, label.Value);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(code + ": " + label.Index);
  #endif
          }
  
          internal void BranchIfLess(CodeLabel label, bool @short)
          {
              OpCode code = @short ? OpCodes.Blt_S : OpCodes.Blt;
              il.Emit(code, label.Value);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(code + ": " + label.Index);
  #endif
          }
  
          internal void DiscardValue()
          {
              Emit(OpCodes.Pop);
          }
  
          public void Subtract()
          {
              Emit(OpCodes.Sub);
          }
  
  
  
          public void Switch(CodeLabel[] jumpTable)
          {
              Label[] labels = new Label[jumpTable.Length];
  #if DEBUG_COMPILE
              StringBuilder sb = new StringBuilder(OpCodes.Switch.ToString());
  #endif
              for (int i = 0; i < labels.Length; i++)
              {
                  labels[i] = jumpTable[i].Value;
  #if DEBUG_COMPILE
                  sb.Append("; ").Append(i).Append("=>").Append(jumpTable[i].Index);
  #endif
              }
  
              il.Emit(OpCodes.Switch, labels);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(sb.ToString());
  #endif
          }
  
          internal void EndFinally()
          {
              il.EndExceptionBlock();
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine("EndExceptionBlock");
  #endif
          }
  
          internal void BeginFinally()
          {
              il.BeginFinallyBlock();
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine("BeginFinallyBlock");
  #endif
          }
  
          internal void EndTry(CodeLabel label, bool @short)
          {
              OpCode code = @short ? OpCodes.Leave_S : OpCodes.Leave;
              il.Emit(code, label.Value);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(code + ": " + label.Index);
  #endif
          }
  
          internal CodeLabel BeginTry()
          {
              CodeLabel label = new CodeLabel(il.BeginExceptionBlock(), nextLabel++);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine("BeginExceptionBlock: " + label.Index);
  #endif
              return label;
          }
  #if !FX11
          internal void Constrain(Type type)
          {
              il.Emit(OpCodes.Constrained, type);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(OpCodes.Constrained + ": " + type);
  #endif
          }
  #endif
  
          internal void TryCast(Type type)
          {
              il.Emit(OpCodes.Isinst, type);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(OpCodes.Isinst + ": " + type);
  #endif
          }
  
          internal void Cast(Type type)
          {
              il.Emit(OpCodes.Castclass, type);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(OpCodes.Castclass + ": " + type);
  #endif
          }
          public IDisposable Using(Local local)
          {
              return new UsingBlock(this, local);
          }
          private class UsingBlock : IDisposable{
              private Local local;
              CompilerContext ctx;
              CodeLabel label;
              /// <summary>
              /// Creates a new "using" block (equivalent) around a variable;
              /// the variable must exist, and note that (unlike in C#) it is
              /// the variables *final* value that gets disposed. If you need
              /// *original* disposal, copy your variable first.
              /// 
              /// It is the callers responsibility to ensure that the variable's
              /// scope fully-encapsulates the "using"; if not, the variable
              /// may be re-used (and thus re-assigned) unexpectedly.
              /// </summary>
              public UsingBlock(CompilerContext ctx, Local local)
              {
                  if (ctx == null) throw new ArgumentNullException("ctx");
                  if (local == null) throw new ArgumentNullException("local");
  
                  Type type = local.Type;
                  // check if **never** disposable
                  if ((type.IsValueType || type.IsSealed) &&
                      !ctx.MapType(typeof(IDisposable)).IsAssignableFrom(type))
                  {
                      return; // nothing to do! easiest "using" block ever
                      // (note that C# wouldn't allow this as a "using" block,
                      // but we'll be generous and simply not do anything)
                  }
                  this.local = local;
                  this.ctx = ctx;
                  label = ctx.BeginTry();
                  
              }
              public void Dispose()
              {
                  if (local == null || ctx == null) return;
  
                  ctx.EndTry(label, false);
                  ctx.BeginFinally();
                  Type disposableType = ctx.MapType(typeof (IDisposable));
                  MethodInfo dispose = disposableType.GetMethod("Dispose");
                  Type type = local.Type;
                  // remember that we've already (in the .ctor) excluded the case
                  // where it *cannot* be disposable
                  if (type.IsValueType)
                  {
                      ctx.LoadAddress(local, type);
                      switch (ctx.MetadataVersion)
                      {
                          case ILVersion.Net1:
                              ctx.LoadValue(local);
                              ctx.CastToObject(type);
                              break;
                          default:
  #if FX11
                              throw new NotSupportedException();
  #else
                              ctx.Constrain(type);
                              break;
  #endif
                      }
                      ctx.EmitCall(dispose);                    
                  }
                  else
                  {
                      Compiler.CodeLabel @null = ctx.DefineLabel();
                      if (disposableType.IsAssignableFrom(type))
                      {   // *known* to be IDisposable; just needs a null-check                            
                          ctx.LoadValue(local);
                          ctx.BranchIfFalse(@null, true);
                          ctx.LoadAddress(local, type);
                      }
                      else
                      {   // *could* be IDisposable; test via "as"
                          using (Compiler.Local disp = new Compiler.Local(ctx, disposableType))
                          {
                              ctx.LoadValue(local);
                              ctx.TryCast(disposableType);
                              ctx.CopyValue();
                              ctx.StoreValue(disp);
                              ctx.BranchIfFalse(@null, true);
                              ctx.LoadAddress(disp, disposableType);
                          }
                      }
                      ctx.EmitCall(dispose);
                      ctx.MarkLabel(@null);
                  }
                  ctx.EndFinally();
                  this.local = null;
                  this.ctx = null;
                  label = new CodeLabel(); // default
              }
          }
  
          internal void Add()
          {
              Emit(OpCodes.Add);
          }
  
          internal void LoadLength(Local arr, bool zeroIfNull)
          {
              Helpers.DebugAssert(arr.Type.IsArray && arr.Type.GetArrayRank() == 1);
  
              if (zeroIfNull)
              {
                  Compiler.CodeLabel notNull = DefineLabel(), done = DefineLabel();
                  LoadValue(arr);
                  CopyValue(); // optimised for non-null case
                  BranchIfTrue(notNull, true);
                  DiscardValue();
                  LoadValue(0);
                  Branch(done, true);
                  MarkLabel(notNull);
                  Emit(OpCodes.Ldlen);
                  Emit(OpCodes.Conv_I4);
                  MarkLabel(done);
              }
              else
              {
                  LoadValue(arr);
                  Emit(OpCodes.Ldlen);
                  Emit(OpCodes.Conv_I4);
              }
          }
  
          internal void CreateArray(Type elementType, Local length)
          {
              LoadValue(length);
              il.Emit(OpCodes.Newarr, elementType);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(OpCodes.Newarr + ": " + elementType);
  #endif
  
          }
  
          internal void LoadArrayValue(Local arr, Local i)
          {
              Type type = arr.Type;
              Helpers.DebugAssert(type.IsArray && arr.Type.GetArrayRank() == 1);
              type = type.GetElementType();
              Helpers.DebugAssert(type != null, "Not an array: " + arr.Type.FullName);
              LoadValue(arr);
              LoadValue(i);
              switch(Helpers.GetTypeCode(type)) {
                  case ProtoTypeCode.SByte: Emit(OpCodes.Ldelem_I1); break;
                  case ProtoTypeCode.Int16: Emit(OpCodes.Ldelem_I2); break;
                  case ProtoTypeCode.Int32: Emit(OpCodes.Ldelem_I4); break;
                  case ProtoTypeCode.Int64: Emit(OpCodes.Ldelem_I8); break;
  
                  case ProtoTypeCode.Byte: Emit(OpCodes.Ldelem_U1); break;
                  case ProtoTypeCode.UInt16: Emit(OpCodes.Ldelem_U2); break;
                  case ProtoTypeCode.UInt32: Emit(OpCodes.Ldelem_U4); break;
                  case ProtoTypeCode.UInt64: Emit(OpCodes.Ldelem_I8); break; // odd, but this is what C# does...
  
                  case ProtoTypeCode.Single: Emit(OpCodes.Ldelem_R4); break;
                  case ProtoTypeCode.Double: Emit(OpCodes.Ldelem_R8); break;
                  default:
                      if (type.IsValueType)
                      {
                          il.Emit(OpCodes.Ldelema, type);
                          il.Emit(OpCodes.Ldobj, type);
  #if DEBUG_COMPILE
                          Helpers.DebugWriteLine(OpCodes.Ldelema + ": " + type);
                          Helpers.DebugWriteLine(OpCodes.Ldobj + ": " + type);
  #endif
                      }
                      else
                      {
                          Emit(OpCodes.Ldelem_Ref);
                      }
               
                      break;
              }
              
          }
  
  
  
          internal void LoadValue(Type type)
          {
              il.Emit(OpCodes.Ldtoken, type);
  #if DEBUG_COMPILE
              Helpers.DebugWriteLine(OpCodes.Ldtoken + ": " + type);
  #endif
              EmitCall(MapType(typeof(System.Type)).GetMethod("GetTypeFromHandle"));
          }
  
          internal void ConvertToInt32(ProtoTypeCode typeCode, bool uint32Overflow)
          {
              switch (typeCode)
              {
                  case ProtoTypeCode.Byte:
                  case ProtoTypeCode.SByte:
                  case ProtoTypeCode.Int16:
                  case ProtoTypeCode.UInt16:
                      Emit(OpCodes.Conv_I4);
                      break;
                  case ProtoTypeCode.Int32:
                      break;
                  case ProtoTypeCode.Int64:
                      Emit(OpCodes.Conv_Ovf_I4);
                      break;
                  case ProtoTypeCode.UInt32:
                      Emit(uint32Overflow ? OpCodes.Conv_Ovf_I4_Un : OpCodes.Conv_Ovf_I4);
                      break;
                  case ProtoTypeCode.UInt64:
                      Emit(OpCodes.Conv_Ovf_I4_Un);
                      break;
                  default:
                      throw new InvalidOperationException("ConvertToInt32 not implemented for: " + typeCode);
              }
          }
  
          internal void ConvertFromInt32(ProtoTypeCode typeCode, bool uint32Overflow)
          {
              switch (typeCode)
              {
                  case ProtoTypeCode.SByte: Emit(OpCodes.Conv_Ovf_I1); break;
                  case ProtoTypeCode.Byte: Emit(OpCodes.Conv_Ovf_U1); break;
                  case ProtoTypeCode.Int16: Emit(OpCodes.Conv_Ovf_I2); break;
                  case ProtoTypeCode.UInt16: Emit(OpCodes.Conv_Ovf_U2); break;
                  case ProtoTypeCode.Int32: break;
                  case ProtoTypeCode.UInt32: Emit(uint32Overflow ? OpCodes.Conv_Ovf_U4 : OpCodes.Conv_U4); break;
                  case ProtoTypeCode.Int64: Emit(OpCodes.Conv_I8); break;
                  case ProtoTypeCode.UInt64: Emit(OpCodes.Conv_U8); break;
                  default: throw new InvalidOperationException();
              }
          }
  
          internal void LoadValue(decimal value)
          {
              if (value == 0M)
              {
                  LoadValue(typeof(decimal).GetField("Zero"));
              }
              else
              {
                  int[] bits = decimal.GetBits(value);
                  LoadValue(bits[0]); // lo
                  LoadValue(bits[1]); // mid
                  LoadValue(bits[2]); // hi
                  LoadValue((int)(((uint)bits[3]) >> 31)); // isNegative (bool, but int for CLI purposes)
                  LoadValue((bits[3] >> 16) & 0xFF); // scale (byte, but int for CLI purposes)
  
                  EmitCtor(MapType(typeof(decimal)), new Type[] { MapType(typeof(int)), MapType(typeof(int)), MapType(typeof(int)), MapType(typeof(bool)), MapType(typeof(byte)) });
              }
          }
  
          internal void LoadValue(Guid value)
          {
              if (value == Guid.Empty)
              {
                  LoadValue(typeof(Guid).GetField("Empty"));
              }
              else
              { // note we're adding lots of shorts/bytes here - but at the IL level they are I4, not I1/I2 (which barely exist)
                  byte[] bytes = value.ToByteArray();
                  int i = (bytes[0]) | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
                  LoadValue(i);
                  short s = (short)((bytes[4]) | (bytes[5] << 8));
                  LoadValue(s);
                  s = (short)((bytes[6]) | (bytes[7] << 8));
                  LoadValue(s);
                  for (i = 8; i <= 15; i++)
                  {
                      LoadValue(bytes[i]);
                  }
                  EmitCtor(MapType(typeof(Guid)), new Type[] { MapType(typeof(int)), MapType(typeof(short)), MapType(typeof(short)),
                              MapType(typeof(byte)), MapType(typeof(byte)), MapType(typeof(byte)), MapType(typeof(byte)), MapType(typeof(byte)), MapType(typeof(byte)), MapType(typeof(byte)), MapType(typeof(byte)) });
              }
          }
  
          internal void LoadValue(bool value)
          {
              Emit(value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
          }
  
          internal void LoadSerializationContext()
          {
              LoadReaderWriter();
              LoadValue((isWriter ? typeof(ProtoWriter) : typeof(ProtoReader)).GetProperty("Context"));
          }
  
          private readonly TypeModel model;
  
          internal Type MapType(System.Type type)
          {
              return model.MapType(type);
          }
  
          private readonly ILVersion metadataVersion;
          public ILVersion MetadataVersion { get { return metadataVersion; } }
          public enum ILVersion
          {
              Net1, Net2
          }
  
          internal bool AllowInternal(PropertyInfo property)
          {
              return nonPublic ? true : InternalsVisible(property.DeclaringType.Assembly);
          }
      }
  }
  #endif