This is a continuation of my blog series about writing a hybrid C# code analyzer and interpreter.
Compared to part one, I renamed the variable references from "TrackedVariableReference" to "EvaluatedObjectReference" and a variable is now known as an "EvalutedObject" or any subtypes derived from it.
This part will focus on the simulation of the code execution. The core part is the object that stores the current execution state which is of type "CodeEvaluatorExecutionState". The most important things stored in this state object are the objects of the type "CodeEvaluatorExecutionFrame". These objects represent a method call and store all the local parameters corresponding to a method call. These parameters include the local "this" reference, the local objects created inside the method, the passed parameters from caller of the method and a special slot for a reference which contains the results of an expression. This last slot is important because when we access a member from a variable we store it inside this slot. When we call a method, the result is also stored in this slot. Basically anything operation which returns any sort of object is stored in this slot.
The structure of the "CodeEvaluatorExecutionFrame" looks like this:
When we first start to simulate the execution of a program, an initial execution frame is generated from the method that we passed as a starting point to the simulator. When a method is called inside the code, another execution frame is pushed in the execution state object and becomes the active execution frame. This means that from now on all newly created references for objects will be stored in the new frame and newly created objects will only be accessible from references stored in the new frame.
When a the end of a called method is reached, the current execution frame is deleted and removed from the execution state object. If the method returns any objects then they will be stored in the expression result slot from the previous frame from which the method was called. This result can be used for another expression which will produce another result and so on. An expression result is actually "consumed" by next expression in a chain of expressions. For example we can chain multiple method calls calling another method on the result returned by the previous method call.
A constructor or property is seen as a normal method call. For a constructor we do not pass the "this" reference but instead we create it from scratch and it always returns a result, the newly created object. Properties are seen like normal methods but with a predefined and standard parameter for the "set" accessor and a method that always return something for the "get" accessor.
The data is passed around inside the simulator by objects which have the type "EvaluatedObject" or a subtype of it like I mentioned in the beginning. For each object we only store the fields as normal references. The structure of an object looks like this:
The rest of the object data like the methods and properties are stored in a common place in the objects type. So for each object we have a type definition that is called "EvaluatedTypeInfo". This type stores all the methods that an object has plus links to the base types and interfaces. It's structure looks like this:
Another thing worth mentioning is the fact that when we access a method, they are wrapped around a special "delegate" object. This object can be passed around just like any other object but the method stored inside it can be invoked. It also stores a references to the initial object on which the method was retrieved.
Also each object type definition contains a special static object that contains all the static references for that type. These instances are shared for all the objects of that type.
That's about it for now. In part 3 I run explain how to handle the actual code execution simulation,
Compared to part one, I renamed the variable references from "TrackedVariableReference" to "EvaluatedObjectReference" and a variable is now known as an "EvalutedObject" or any subtypes derived from it.
This part will focus on the simulation of the code execution. The core part is the object that stores the current execution state which is of type "CodeEvaluatorExecutionState". The most important things stored in this state object are the objects of the type "CodeEvaluatorExecutionFrame". These objects represent a method call and store all the local parameters corresponding to a method call. These parameters include the local "this" reference, the local objects created inside the method, the passed parameters from caller of the method and a special slot for a reference which contains the results of an expression. This last slot is important because when we access a member from a variable we store it inside this slot. When we call a method, the result is also stored in this slot. Basically anything operation which returns any sort of object is stored in this slot.
The structure of the "CodeEvaluatorExecutionFrame" looks like this:
When we first start to simulate the execution of a program, an initial execution frame is generated from the method that we passed as a starting point to the simulator. When a method is called inside the code, another execution frame is pushed in the execution state object and becomes the active execution frame. This means that from now on all newly created references for objects will be stored in the new frame and newly created objects will only be accessible from references stored in the new frame.
When a the end of a called method is reached, the current execution frame is deleted and removed from the execution state object. If the method returns any objects then they will be stored in the expression result slot from the previous frame from which the method was called. This result can be used for another expression which will produce another result and so on. An expression result is actually "consumed" by next expression in a chain of expressions. For example we can chain multiple method calls calling another method on the result returned by the previous method call.
A constructor or property is seen as a normal method call. For a constructor we do not pass the "this" reference but instead we create it from scratch and it always returns a result, the newly created object. Properties are seen like normal methods but with a predefined and standard parameter for the "set" accessor and a method that always return something for the "get" accessor.
The data is passed around inside the simulator by objects which have the type "EvaluatedObject" or a subtype of it like I mentioned in the beginning. For each object we only store the fields as normal references. The structure of an object looks like this:
The rest of the object data like the methods and properties are stored in a common place in the objects type. So for each object we have a type definition that is called "EvaluatedTypeInfo". This type stores all the methods that an object has plus links to the base types and interfaces. It's structure looks like this:
Another thing worth mentioning is the fact that when we access a method, they are wrapped around a special "delegate" object. This object can be passed around just like any other object but the method stored inside it can be invoked. It also stores a references to the initial object on which the method was retrieved.
Also each object type definition contains a special static object that contains all the static references for that type. These instances are shared for all the objects of that type.
That's about it for now. In part 3 I run explain how to handle the actual code execution simulation,
Comments
Post a Comment