Mono Runtime Notes

April 17, 2009

Marshalling In Runtime

Filed under: Uncategorized — Tags: , , , — kjambunathan @ 3:07 pm

Marshalling In Runtime

1 Introduction

In this post I attempt to explore an important component of the JIT
execution environment The Marshalling Wrappers.

The intention here is not to exhaust all eventualities and
practicalities concerning marshalling wrappers. The goal is to
merely explore a not too complex facet of the same. This I would do
by considering a simple example.

Before we move ahead with the example, let’s see how a dictionary
defines the word – Marshal (v). The following description fits our
current purpose.

Marshal1

  1. to place in proper rank or position <marshaling the troops>
  2. to bring together and order in an appropriate or effective way

2 Example Assembly

  // file: CommandLine.cs

  using System;

  class HelloWorld
  {
    public static void Main(string[] args)
    {
        for (int i = 0; i < args.Length; ++i) {
            Console.WriteLine(args[i]);
        }
    }
  }
  $ mcs CommandLine.cs

3 JIT Execute Assembly

  $ mono CommandLine.exe HelloWorld

As the runtime prepares to call in to the entry point method of
CommandLine.exe, it parses the shell command line and sets up the
caller side stack exactly as the Main () expects it. It then invokes
the the target method – Main () in the loaded assembly.

The later invocation happens via a Method Wrapper called Runtime Invoke Wrapper. We will come to this Wrapper shortly.

In essence, there are two steps to executing the assembly

  1. Parameter Marshalling
  2. Target Method Invocation

The code trace for the above sequence is as follows –

       // STEP-1: PARAMETER MARSHALLING
      
      // Snippet of code from mono_jit_exec (MonoDomain *domain, MonoAssembly *assembly, int argc, char *argv[])

       MonoImage *image = mono_assembly_get_image (assembly);
       guint32 entry = mono_image_get_entry_point (image);

       MonoMethod *method = mono_get_method (image, entry, NULL);
           if (method == NULL){
           g_print ("The entry point method could not be loaded\n");
           mono_environment_exitcode_set (1);
           return 1;
       }

       // Snippet of code from mono_runtime_run_main
       args = (MonoArray*)mono_array_new (domain, mono_defaults.string_class, argc);

       for (i = 0; i < argc; ++i) {
           gchar *str = mono_utf8_from_external (argv [i]);
           MonoString *arg = mono_string_new (domain, str);
           mono_array_setref (args, i, arg);
           g_free (str);
       }

       // Snippet of code from mono_runtime_exec_main
       gpointer pa [1];
       pa[2] = args;


       // STEP-2: TARGET METHOD INVOCATION
      
       // This ultimately calls mono_jit_runtime_invoke ()
       mono_runtime_invoke (method, NULL, pa, exc);
  }

Let’s walk the above code and analyse the listed steps in detail.

3.1 Parameter Marshalling

In this step, the runtime ‘constructs’ the Main’s args of type
string[] using the argc , argv[] pair passed to it as part of
the c-runtime.

It is worth noting that the Memory pointed to by MonoArray*
args and MonoString arg in the above given trace are first
class CLR objects. These are ‘managed’ and hence would be Garbage
Collected by the Mono Runtime itself. On the other hand, the memory
pointed to by gchar *str comes from the unmanaged pool and has
to be explitictly freed.

3.2 Target Method Invocation

Once the paramters are marshalled, the next step is to invoke the
target method. This happens in four distinct steps –

  1. Build Runtime Invoke Wrapper
  2. JIT compile Invoke Wrapper
  3. JIT Compile Target Method
  4. Invoke Target Method via Invoke Wrapper

The precise code trace for these steps is –

  static MonoObject*
  mono_jit_runtime_invoke (MonoMethod *method, void *obj, void **params, MonoObject **exc)
  {
       // Entry from mono_runtime_invoke ()

       MonoObject *(*runtime_invoke) (MonoObject *this, void **params, MonoObject **exc, void* compiled_method);

       // STEP-2.1: BUILD RUNTIME INVOKE WRAPPER 
       MonoMethod *invoke =  mono_marshal_get_runtime_invoke (method);

       // STEP-2.2: JIT COMPILE THE WRAPPER
       runtime_invoke = mono_jit_compile_method (invoke);


       // STEP-2.3: JIT COMPILE TARGET METHOD
       MonoMethod *to_compile = method;
       void * compiled_method =  mono_jit_compile_method (to_compile);

       // STEP-2.4: INVOKE TARGET METHOD VIA THE WRAPPER
       return runtime_invoke (obj, params, exc, compiled_method);
  }

3.2.1 Build a Runtime Invoke Wrapper

The Runtime Invoke wrapper for the invocation of the Main () is
generated by the runtime on the fly. The method is built using
Method Builder Interface within the runtime and is subsequently
JIT compiled.

The signature of the Invoke Wrapper looks like this –

  HelloWorld:runtime_invoke_void_object (object,intptr,intptr,intptr)
      ^                      ^           arg0    arg1   arg2   arg3 
      |                      |          [obj]  [params] [exc] [fnptr] 
      |                      |          
      |                      |          
  Target Class            Ret Type        

The IL code is listed down below –

Please note that the Runtime Invoke Wrapper, in addition to
calling the target method also surrounds it with a a
behind-the-scene try-filter block2. The filter block makes it’s
filtering decision based on whether or not the caller provided a
‘exc’ reference to it. The handler block does some special case
handling for ThreadAbortException thrown by the target method.

Additional Note: CIL code is also made more interesting for it
exposes some of the Mono’s private opcodes that are used withing
the the runtime. We would have time to explore these opcodes –
mono_ldpt, mono_not_taken, mono_icall
in the context of a possibly different post.

   runtime_invoke_void_object (object,intptr,intptr,intptr)
                                arg0   arg1   arg2   arg3
                               [obj] [params] [exc] [fnptr] 

   IL_fffffd84: ldarg.2
   IL_fffffd85: brfalse.s IL_fffffd8a

   IL_fffffd87: ldarg.2
   IL_fffffd88: ldnull
   IL_fffffd89: stind.ref

   IL_fffffd8a: mono_ldptr2
   IL_fffffd90: ldind.u4
   IL_fffffd91: brfalse   IL_fffffd9e

   IL_fffffd96: mono_not_taken
                                       // icall mono_thread_force_interruption_checkpoint:
   IL_fffffd98: mono_icall3

   IL_fffffd9b: nop
   IL_fffffd9c: nop
   IL_fffffd9d: nop

                                       // invoke_Main(string[] args)
   IL_fffffd9e: ldarg.1
   IL_fffffd9f: ldind.ref
   IL_fffffda0: ldarg.3
   IL_fffffda1: calli     0x00000004
                                       // store ret val null in loc0
   IL_fffffda6: ldnull
   IL_fffffda7: stloc.0

   IL_fffffda8: leave     IL_fffffdcd
                                       // try block ends
                                       // filter block starts
   IL_fffffdad: pop
   IL_fffffdae: ldarg.2
   IL_fffffdaf: ldc.i4.0
   IL_fffffdb0: cgt.un
   IL_fffffdb2: endfilter
                                       // filter block ends
                                       // handler block starts

   IL_fffffdb4: stloc.1                // loc1 = Exception Object
   IL_fffffdb5: ldarg.2                //
   IL_fffffdb6: ldloc.1                //
   IL_fffffdb7: stind.ref              // *arg2 = loc1
   IL_fffffdb8: ldnull
   IL_fffffdb9: stloc.0                // loc0 = null
   IL_fffffdba: ldloc.1
   IL_fffffdbb: isinst    0x00000005   // TypeOf (loc1) == mono_defaults.threadabortexception_class
   IL_fffffdc0: brfalse.s IL_fffffdc8
   IL_fffffdc2: mono_icall6            // icall ves_icall_System_Threading_Thread_ResetAbort
   IL_fffffdc5: nop
   IL_fffffdc6: nop
   IL_fffffdc7: nop
   IL_fffffdc8: leave     IL_fffffdcd
                                       // handler block ends
                                       // pop ret val; return

   IL_fffffdcd: ldloc.0
   IL_fffffdce: ret

For the utterly curious, the corresponding native code looks like
this. For now it is sufficient to make a mental note of how a
compiled try-filter-handler block looks like. We would have a chance
to examine the try blocks while exploring Exception Handling in the
runtime.

   HelloWorld:runtime_invoke_void_object (object,intptr,intptr,intptr)
       @0xb79911c0

    0: push   ebp
    1: mov    ebp,esp
    3: sub    esp,0x38
    6: and    esp,0xfffffff0
    9: mov    DWORD PTR [ebp-32],0x0
   10: mov    DWORD PTR [ebp-36],0x0

   17: cmp    DWORD PTR [ebp+16],0x0
   1b: je     26

   1d: mov    eax,DWORD PTR [ebp+16]
   20: mov    DWORD PTR [eax],0x0

   26: mov    eax,DWORD PTR ds: 0x82a1df0
   2c: test   eax,eax
   2e: je     35

   30: call   fffffe70                 // path not taken ???

   35: mov    eax,DWORD PTR [ebp+12]
   38: mov    eax,DWORD PTR [eax]
   3a: sub    esp,0xc
   3d: push   eax
   3e: mov    eax,DWORD PTR [ebp+20]
   41: call   eax
   43: add    esp,0x10
   46: mov    DWORD PTR [ebp-32],0x0
   4d: jmp    b3                       // leave try block
                                       // filter block starts
   52: mov    DWORD PTR [ebp-20],esp
   55: cmp    DWORD PTR [ebp+16],0x0
   59: seta   al
   5c: movzx  eax,al
   5f: mov    esp,DWORD PTR [ebp-20]
   62: ret
                                       // filter block ends
                                       // handler block starts
   63: mov    DWORD PTR [ebp-16],esp
   66: mov    eax,DWORD PTR [ebp-28]
   69: mov    ecx,DWORD PTR [ebp-28]
   6c: mov    DWORD PTR [ebp-36],ecx
   6f: mov    eax,DWORD PTR [ebp+16]
   72: mov    DWORD PTR [eax],ecx
   74: mov    DWORD PTR [ebp-32],0x0
   7b: mov    eax,DWORD PTR [ebp-36]
   7e: mov    DWORD PTR [ebp-8],eax
   81: mov    eax,DWORD PTR [ebp-36]
   84: mov    DWORD PTR [ebp-4],eax
   87: cmp    DWORD PTR [ebp-36],0x0
   8b: je     a6

   8d: mov    eax,DWORD PTR [ebp-8]    // eax = loc1 = exception object
   90: mov    eax,DWORD PTR [eax]
   92: mov    eax,DWORD PTR [eax]
   94: cmp    eax,0x82d8d3c
   99: jne    9d

   9b: jmp    a6

   9d: mov    DWORD PTR [ebp-4],0x0
   a4: jmp    a6

   a6: cmp    DWORD PTR [ebp-4],0x0
   aa: je     b1

   ac: call   507abaf0                 // ves_icall_System_Threading_Thread_ResetAbort

   b1: jmp    b3                       

                                       // setup ret val and exit
   b3: mov    eax,DWORD PTR [ebp-32]
   b6: jmp    b8

   b8: leave
   b9: ret

3.2.2 JIT compile Invoke Wrapper

This will be discussed elsewhere.

3.2.3 JIT Compile Target Method

This will be discussed elsewhere.

3.2.4 Invoke Target Method via Invoke Wrapper

This step is self-explanatory.

4 A Word to my Readers

I thank Kojo, Markus Johnsson, Mitch and Tobias for lots of
encouraging words to my first post.

I thank Johannes for a suggestion to move away from my earlier
WordPress theme. Hope you find the new layout more reader friendly.

I thank KW for a suggestion to generate a full post. I hope this
post comes in full. Otherwise, there is something amiss somewhere
which I need to track down.

Footnotes:

1 Definition from Merriam-Webster

2 At this point in time, I am unable to explain the purpose of this
try-filter block. I am sure the very purpose of the Invoke Wrapper
is to provide a place holder for this special case handling of
exceptions thrown from the invoked routines.

Author: Jambunathan K. Consult About page for my Inbox information.

Date: 2009-04-17 14:58:29 IST

Create a free website or blog at WordPress.com.