Haus
Haus

Reputation: 1492

Baking a byte array into dynamic IL

I am writing a performance-oriented data deserializer by emitting IL. The serialized data is UTF8, and the fields are denoted as strings.

[FieldA]: 22
[FieldB]: 16

I have already written a custom reader that properly tokenizes the serialized data and provides a ReadOnlySpan<byte> as it steps through the serialized data.

I would like to have a static, inline deserializer that is able to have the byte signatures of the fields packed in so that I can easily create a jump table and set the appropriate fields.

How non-dynamic code might look:

// byteSpan is a ReadOnlySpan<byte> containing the signature

var signatureA = Encoding.UTF8.GetBytes( "FieldA" );
var signatureB = Encoding.UTF8.GetBytes( "FieldB" );
if( byteSpan.SequenceEqual( signatureA ) )
  DoSomething();
else if ( byteSpan.SequenceEqual( signatureB ) )
  DoSomething();
...

How the jump table is being emitted:

var fieldSignatures = GetTypeSignatures<T>(); // Returns a Tuple<byte[], FieldInfo>
var setFieldLabels = new List<Tuple<FieldInfo, Label>>();

foreach( (byte[] signature, FieldInfo field) in fieldSignatures )
{
  var setFieldLabel = il.DefineLabel();
  setFieldLabels.Add( Tuple.Create( field, setFieldLabel ) );

  il.Emit( OpCodes.Ldloc_1 ); // Load the current ReadOnlySpan<byte>
  // Inline load byte[] signature here
  il.Emit( OpCodes.Call, METHOD_SEQUENCEEQUAL );
  il.Emit( OpCodes.Brtrue, setFieldLabel );
}

EmitFieldSetters( setFieldLabels, ref il );

Is there a way I can bake the signature byte arrays directly into the IL that I am emitting so that they are a part of the delegate?

These signatures are generated at runtime based on type information, so manually defining them in a static class isn't feasible. A workaround could be to define a new dynamic Assembly and Type and store the bytes in there, but I would like to avoid having to do that if possible.

Upvotes: 1

Views: 532

Answers (1)

Iridium
Iridium

Reputation: 23721

What you might want to do is pass an array of signature byte arrays (byte[][]) as a hidden first argument to the dynamic method.

You could load the appropriate byte array via something like:

// Load the first byte[][] signatures array argument
il.Emit( OpCodes.LdArg_0 );
// Load the index into the signatures array
il.Emit( OpCodes.Ldc_I4, signatureIndex );
// Fetch the signature byte[] element from the array
il.Emit( OpCodes.Ldelem_Ref );

Then when you create the delegate from the dynamic method, you can use the overload that takes a target object, which becomes the (hidden) first argument:

var deserializerDelegate = dynamicMethod.CreateDelegate(typeof(YourDelegateType), signatures);

This all being said, see my comment on your question above regarding looking at using an alternative signature lookup algorithm which may be more optimal than a linear search, even with dynamically generated IL.

Upvotes: 2

Related Questions