ЯRotor

Extending Rotor with Structural Reflection to support Dynamic Languages

We have extended the introspective capabilities of .Net CLI adding new namespace to the BCL: System.Reflection.Structural. We have also extended the reflective facilities of the virtual machine, enhancing the semantics of some IL instructions. The programmer could combine these facilities with the introspective services already offered by the .Net platform, making the CLI an appropriate platform to develop language-neutral adaptive software.

System.Reflection.Structural

A new NativeStructural utility class that centralizes all reflection primitives has been created in the System.Reflection.Structural namespace. In this class the implemented functionality can be grouped into:

  • Primitives to add/remove/modify methods to single objects or classes (System.Type). Any existing method can be added to any object or class. If we want to create a new method to add, we could use the already existing System.Reflection.Emit namespace to create the specific method we want, adding it later. These methods receive an object or class (System.Type) as a first parameter, indicating whether we want to modify (or inspect) a single object or a shared behavior. The second parameter is a MethodInfo object of the System.Reflection namespace. This object uniquely describes the identifier, parameters, return type, attributes and modifiers of a method. The IsStatic property of this object is used to select the schema evolution behavior (prototype-based language) or class (static) member adaptation (class-based language). Any existing or newly created method can be added to any object or class using its associated MethodInfo instance. The detailed signature of these primitives at virtual machine level is:

  •         void addMethod(object owner, MethodWrap miP, bool isStatic);
            bool removeMethod(object owner, string name, bool overrideDeleted);
            void alterMethod(object owner, string sig, object val, bool isStatic);
            bool existMethod(object owner, string sig);
            object internalGetMethod(object owner, string signature);
                            
  • The invoke primitive, that executes the method of an object or class specifying its name, return type and parameters. Our goal is to support duck typing, as called in the dynamic language community. If no reflection has been used (we are running "static" programs) a fast concatenation strategy is used. However, in the execution of reflective dynamic languages, method invocation is based on delegation. A MissingMethodException is thrown if the message has not been implemented in the hierarchy. The detailed signature of this primitive is:

  •         object invoke(object target, string ret, string mName, object param);
                            
  • Primitives to add/remove/modify the runtime structure of single objects (prototype-based model) or their common schema (classes or trait objects. In case of we want to add fields, new ones can be created on-the-fly thanks to our new functionality. The {add, remove, alter, get, exist}Field methods modify the runtime structure of single objects (prototype-based model) or their common schema (classes or trait objects) passed as the first parameter. The second parameter is an instance of a new RuntimeStructuralFieldInfo class (derived from the .Net FieldInfo class) that describes the field's type, visibility, and its attributes. We use this new class to create new fields at will. Once again, the Static attribute of the second parameter selects the schema evolution behavior (class-based and Python models) or class (static) member adaptation (class-based and Ruby semantics).

  •         object getField (object owner, string name);
            object getFieldValue (object owner, string name);
            void addField (object owner, RuntimeStructuralFieldInfo rsfi);
            void removeField (object owner, string name);
            void alterField (object owner, RuntimeStructuralFieldInfo rsfi);
            void setFieldValue (RuntimeStructuralFieldInfo field, object val);
            bool existField (object owner, string name);
                            

IL Semantics Enhancement

When using the new reflective BCL services, the customization of running programs should be reflected in its execution. However, legacy non-reflective code does not make explicit calls to the BCL Reflection.Structural namespace, and, thus, reflective changes will not be taken into account within the original program. This is the reason why the reflective model requires extending the semantics of some specific IL statements: to make existing applications adaptable.

An example to clarify this idea is the method invocation mechanism. The old behavior is based on a statically typed class model. The new platform offers a dynamic message passing mechanism that can be obtained by calling the invoke method of the NativeStructural class. Although the explicit execution of this method is possible, the idea is to obtain this new behavior with the original program (i.e. using the call and callvirt IL statements) when any class or object is reflectively modified. What we achieve with this approach is making existing .Net components or applications adaptable without needing to recompile them.

Following our motivating example, we present an example of how to invoke a method following the static and dynamic typing approach. We first send a bark message of the statically inferred type Dog. In case the compiler does not know the type of the called object, a dynamic typing message call is needed (bark); then, the Object (or any other) type should be used instead. What we have done to produce this behavior is extend the semantics of the callvirt IL statement with the computational model previously described.

        // * Static typing message passing
        ldloc  dog
        callvirt instance void  Dog::bark()
        // * Dynamic typing message passing
        ldloc dog
        callvirt instance void System.Object::bark()
                        

In order to achieve this goal, we have modified the native code generated by the JIT compiler when executing the following IL statements:

  • ldfld, ldsfld and ldflda: Loads into the stack the (instance or class) field value (or address) following the computational model previously described.

  • stfld and stsfld: Stores a value into a (instance or class) field, deciding its appropriate memory location at runtime.

  • call and callvirt: Executes a method following both the concatenation and delegation inheritance strategies. The extension of these IL statements must enable any program to access its reflective information added at runtime, not available when the code is compiled.