void EmitSimpleStream(Schema schema, ValueWriter writer) { // push *value Emit.LoadArgument(0); LoadIndirect(schema.NetType); // push stream Emit.LoadArgument(ARG_STREAM); // push buffer Emit.LoadArgument(ARG_BUFFER); // push &available LocalAvailable = Emit.DeclareLocal(typeof(int), "available"); PushTotalAvailable(); Emit.StoreLocal(LocalAvailable); Emit.LoadLocalAddress(LocalAvailable); // push &ptr; Emit.LoadArgumentAddress(ARG_POINTER); // call writer Emit.Call(writer.MethodInfo); Flush(resetAvailable: false, writtenTop: false); Emit.Return(); }
void EmitSimpleComplete(Schema schema, ValueWriter writer) { var effective = schema.NetType; Sigil.Label theEnd = null; if (Nullable.GetUnderlyingType(effective) != null) { Emit.Duplicate(); // prepare argument 0 for GetValueOrDefault call Emit.Call(effective.GetProperty("HasValue").GetGetMethod()); var hasValueTrue = Emit.DefineLabel("HasValue_true"); Emit.BranchIfTrue(hasValueTrue); Emit.Pop(); // discard argument 0 // byte* destination Emit.LoadArgument(1); // available if (Destination == DestinationType.Stream) { Emit.LoadLocal(LocalAvailable); } else { Emit.LoadArgument(2); } // .WriteNull Emit.Call(WriteNull); if (Destination == DestinationType.Stream) { Flush(); } theEnd = Emit.DefineLabel("theEnd"); Emit.Branch(theEnd); Emit.MarkLabel(hasValueTrue); Emit.Call(effective.GetMethod("GetValueOrDefault", Type.EmptyTypes)); effective = Nullable.GetUnderlyingType(effective); } CallWriter(writer, effective, false); if (Destination == DestinationType.Stream) { Flush(); } if (theEnd != null) { Emit.MarkLabel(theEnd); } Emit.Return(); }
void CallWriter(ValueWriter writer, Type effective, bool useLocals) { if (writer.MethodInfo == null) { if (effective == typeof(System.Net.IPAddress)) { EmitIPAddress(useLocals); return; } throw new NotImplementedException(); } if (Destination == DestinationType.Pointer || writer.MaxLength.HasValue) { var firstArg = writer.MethodInfo.GetParameters()[0].ParameterType; if (firstArg != effective) { Emit.Convert(firstArg); } if (useLocals) { Emit.LoadLocal(LocalDestination); Emit.LoadLocal(LocalAvailable); } else { Emit.LoadArgument(1); if (Destination == DestinationType.Pointer) { Emit.LoadArgument(2); } else { Emit.LoadLocal(LocalAvailable); } } } else { Emit.LoadArgument(ARG_STREAM); Emit.LoadArgument(ARG_BUFFER); Emit.LoadLocalAddress(LocalAvailable); Emit.LoadLocalAddress(LocalDestination); } Emit.Call(writer.MethodInfo); }
/// <summary>Must be called with value on top of the stack</summary> void EmitInline(Schema schema) { if (Nullable.GetUnderlyingType(schema.NetType) != null) { PushAddress(schema.NetType); } switch (schema.JsonType) { case JsonType.Array: if (schema.NetType.IsArray) { EmitArray(schema, pushResult: false); } else { EmitEnumerable(schema, pushResult: false); } break; case JsonType.Object: if (schema.Keys != null) { EmitDictionary(schema, pushResult: false); } else { EmitObject(schema, pushResult: false); } break; case JsonType.String: case JsonType.Number: case JsonType.Integer: case JsonType.Boolean: ValueWriter simpleWriter; if (ValueWriter.TryGetWriter(schema.NetType, Destination, out simpleWriter)) { EmitSimpleInline(schema, simpleWriter); return; } throw new ArgumentException("JSON primitive writer not found for " + schema.NetType.Name); default: throw new ArgumentException("Unknown JsonType " + schema.JsonType); } }
static ValueWriter From <T>(PrimitiveWriter <T> writer) { var w = new ValueWriter { MethodInfo = writer.Method, Type = typeof(T), }; var attr = w.MethodInfo.GetCustomAttribute <ValueWriterAttribute>(); if (attr != null && attr.MaxLength > 0) { w.MaxLength = attr.MaxLength; } return(w); }
internal static bool TryGetWriter(Type type, DestinationType destination, out ValueWriter writer) { var effective = Nullable.GetUnderlyingType(type) ?? type; // TODO support enums as strings too - this won't apply then if (effective.IsEnum) { effective = effective.GetEnumUnderlyingType(); } // check for common (no options writer first) var lookup = new Lookup(effective); if (_Writers.TryGetValue(lookup, out writer)) { return(true); } // fallback to using the destination lookup = new Lookup(effective, destination); return(_Writers.TryGetValue(lookup, out writer)); }
static ValueWriter() { Add <int>(Serializer.WriteInt32, _Writers); Add <uint>(Serializer.WriteUInt32, _Writers); Add <long>(Serializer.WriteInt64, _Writers); Add <ulong>(Serializer.WriteUInt64, _Writers); Add <bool>(Serializer.WriteBoolean, _Writers); Add <Guid>(Serializer.WriteGuidFormatD, _Writers); Add <DateTime>(Serializer.WriteDateTime8601, _Writers); Add <float>(Serializer.WriteSingle, _Writers); Add <double>(Serializer.WriteDouble, _Writers); Add <decimal>(Serializer.WriteDecimal, _Writers); Add <char>(ConvertUTF.WriteCharUtf8, _Writers); _Writers[typeof(byte)] = new ValueWriter { MaxLength = 3, Type = typeof(byte), MethodInfo = _Writers[typeof(uint)].MethodInfo, }; _Writers[typeof(sbyte)] = new ValueWriter { MaxLength = 4, Type = typeof(sbyte), MethodInfo = _Writers[typeof(int)].MethodInfo, }; _Writers[typeof(ushort)] = new ValueWriter { MaxLength = 5, Type = typeof(ushort), MethodInfo = _Writers[typeof(uint)].MethodInfo, }; _Writers[typeof(short)] = new ValueWriter { MaxLength = 6, Type = typeof(short), MethodInfo = _Writers[typeof(int)].MethodInfo, }; _Writers[typeof(System.Net.IPAddress)] = new ValueWriter { MaxLength = 48, Type = typeof(System.Net.IPAddress), MethodInfo = null, // IPAddress is a special case to handle v4 and v6 effeciently }; // 2 string options Add <string>(ConvertUTF.WriteStringUtf8, _Writers, (long)DestinationType.Pointer); _Writers[new Lookup(typeof(string), DestinationType.Stream)] = new ValueWriter { Type = typeof(string), MethodInfo = typeof(ConvertUTF).GetMethod("WriteToStreamUtf8", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public), }; // 2 byte[] -> base64 options Add <byte[]>(Serializer.WriteBase64, _Writers, (long)DestinationType.Pointer); _Writers[new Lookup(typeof(byte[]), DestinationType.Stream)] = new ValueWriter { Type = typeof(byte[]), MethodInfo = typeof(Serializer).GetMethod("WriteBase64ToStream", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public), }; }
static void Add <T>(PrimitiveWriter <T> writer, Dictionary <Lookup, ValueWriter> dict, long flags = 0) { _Writers[new Lookup(typeof(T), flags)] = ValueWriter.From(writer); }
static Sigil.NonGeneric.Emit Create <T>(Schema schema, DestinationType destination) { ValueWriter simpleWriter = null; switch (schema.JsonType) { case JsonType.Array: case JsonType.Object: // handle these after the switch break; case JsonType.String: case JsonType.Number: case JsonType.Integer: case JsonType.Boolean: if (ValueWriter.TryGetWriter(schema.NetType, destination, out simpleWriter)) { break; } throw new ArgumentException("JSON primitive writer not found for " + schema.NetType.Name); default: throw new ArgumentException("Unknown JsonType " + schema.JsonType); } var builder = new DelegateBuilder { Destination = destination, }; Sigil.NonGeneric.Emit emit; string methodName = schema.NetType.Name + "_toJSON"; if (destination == DestinationType.Pointer) { emit = Sigil.NonGeneric.Emit.NewDynamicMethod(typeof(int), new[] { typeof(T).MakeByRefType(), // value typeof(byte).MakePointerType(), // destination typeof(int), // available }, methodName, doVerify: UseSigilVerify); } else if (destination == DestinationType.Stream) { emit = Sigil.NonGeneric.Emit.NewDynamicMethod(typeof(void), new[] { typeof(T).MakeByRefType(), // value typeof(byte).MakePointerType(), // buffer typeof(System.IO.Stream), // destination typeof(byte).MakeArrayType(), // buffer }, methodName, doVerify: UseSigilVerify); } else { throw new ArgumentException(); } builder.Emit = emit; if (simpleWriter != null) { if (destination == DestinationType.Stream) { if (!simpleWriter.MaxLength.HasValue) { // special case writers that might have to Flush multiple times builder.EmitSimpleStream(schema, simpleWriter); return(emit); } builder.LocalAvailable = emit.DeclareLocal(typeof(int), "available"); builder.PushTotalAvailable(); emit.StoreLocal(builder.LocalAvailable); } emit.LoadArgument(0); builder.LoadIndirect(schema.NetType); // deref argument 0 builder.EmitSimpleComplete(schema, simpleWriter); return(emit); } Sigil.Local local; // local for dst builder.LocalDestination = local = emit.DeclareLocal(typeof(byte *), "destination"); emit.LoadArgument(1); emit.StoreLocal(local); // local for avail builder.LocalAvailable = local = emit.DeclareLocal(typeof(int), "available"); builder.PushTotalAvailable(); emit.StoreLocal(local); bool pushResult = destination == DestinationType.Pointer; // CRITICAL before calling any composite methods top of stack must be `value` followed by `available` when pushResult is true if (pushResult) { emit.LoadLocal(local); } emit.LoadArgument(0); builder.LoadIndirect(schema.NetType); // deref argument 0 if (schema.JsonType == JsonType.Array) { if (schema.NetType.IsArray) { builder.EmitArray(schema, pushResult: pushResult); } else { builder.EmitEnumerable(schema, pushResult: pushResult); } } else if (schema.JsonType == JsonType.Object) { if (schema.Keys != null) { builder.EmitDictionary(schema, pushResult: pushResult); } else { builder.EmitObject(schema, pushResult: pushResult); } } if (destination == DestinationType.Stream) { builder.Flush(writtenTop: false); } emit.Return(); return(emit); }
// never pushes result void EmitSimpleInline(Schema schema, ValueWriter writer) { var effective = schema.NetType; Sigil.Label ifNull = null; var underlying = Nullable.GetUnderlyingType(effective); if (underlying != null) { ifNull = DefineLabel("ifNull"); var hasValueTrue = DefineLabel("hasValueTrue"); Emit.Duplicate(); // preserve value Emit.Call(effective.GetProperty("HasValue").GetGetMethod()); Emit.BranchIfTrue(hasValueTrue); Emit.Pop(); // discard value WriteConstant("null", push: false); Emit.Branch(ifNull); Emit.MarkLabel(hasValueTrue); Emit.Call(effective.GetMethod("GetValueOrDefault", Type.EmptyTypes)); effective = underlying; } if (writer.MaxLength.HasValue && Destination == DestinationType.Stream) { // flush if available < writer.MaxLength var afterFlush = DefineLabel("afterFlush"); Emit.LoadLocal(LocalAvailable); Emit.LoadConstant(writer.MaxLength.Value); Emit.BranchIfGreaterOrEqual(afterFlush); Flush(writtenTop: false, resetAvailable: true); Emit.MarkLabel(afterFlush); } CallWriter(writer, effective, true); // simple writers must flush to stream directly if (Destination == DestinationType.Pointer) { Emit.Duplicate(); // preserve the written count // check if the write succeeded var success = DefineLabel("success"); Emit.LoadConstant(0); Emit.BranchIfGreater(success); { Depth++; ReturnFailed(); Depth--; } Emit.MarkLabel(success); } if (Destination == DestinationType.Pointer || writer.MaxLength.HasValue) { Emit.Duplicate(); // preserve written count // advance LocalDestination Emit.Convert <IntPtr>(); Emit.LoadLocal(LocalDestination); Emit.Add(); Emit.StoreLocal(LocalDestination); // Emit.Duplicate(); // preserve written count // decrement LocalAvailable Emit.LoadConstant(-1); Emit.Multiply(); Emit.LoadLocal(LocalAvailable); Emit.Add(); Emit.StoreLocal(LocalAvailable); } if (underlying != null) { Emit.MarkLabel(ifNull); } }