Over the course of the semester, the compilers make greater use of specialized runtime functionality defined in various C files. We need to implement a similar functionality for our interpreter.
Our runtimes will be implemented in pure Racket. Racket functions must be made to interpret values from the registers and stack, and then return their results by pushing to the appropriate register or stack, emulating the behavior of the a86 call instruction. Ideally, this behavior would be (mostly?) automated, such that client compilers would need only to define the functions in terms of normal arguments and return values and provide them to the interpreter, and the details of handling registers/the stack are abstracted away completely.
The functions can be passed in as a hash from function names to implementations during the call to initialize-state. These external functions will be queried with procedure-arity to determine their separate arities, and then converted into functions that can read the appropriate number of arguments from the registers and stack and which will push their results onto the stack at the end. Client compilers will still need to declare the functions' existence via an Extern instruction for no reason other than enforcing good code style for implementing the compiler in other environments. (The Extern instructions could be automatically generated based on the input hash otherwise.)
To-do
Observational Inequality of Register Modification
In a traditional compilation environment, modification of the registers performed by external functions is visible to the client code, even if such values are not considered "returned" values. In our runtime implementation, such modifications will be invisible to the client code because it will happen in Racket natively instead of passing through the interpreter. Although this means our interpreter will deviate observationally from traditionally compiled code, I think the difference is unlikely to have significant impact in student projects since students are expected to follow standard calling conventions. Maybe make a note in the code about it, though.
Over the course of the semester, the compilers make greater use of specialized runtime functionality defined in various C files. We need to implement a similar functionality for our interpreter.
Our runtimes will be implemented in pure Racket. Racket functions must be made to interpret values from the registers and stack, and then return their results by pushing to the appropriate register or stack, emulating the behavior of the a86
callinstruction. Ideally, this behavior would be (mostly?) automated, such that client compilers would need only to define the functions in terms of normal arguments and return values and provide them to the interpreter, and the details of handling registers/the stack are abstracted away completely.The functions can be passed in as a
hashfrom function names to implementations during the call toinitialize-state. These external functions will be queried withprocedure-arityto determine their separate arities, and then converted into functions that can read the appropriate number of arguments from the registers and stack and which will push their results onto the stack at the end. Client compilers will still need to declare the functions' existence via anExterninstruction for no reason other than enforcing good code style for implementing the compiler in other environments. (TheExterninstructions could be automatically generated based on the inputhashotherwise.)To-do
Externlabels for functions defined in the runtime environment.procedure-arityto determine the number of arguments to read from registers/the stack.encodefunction for handling native Racket values?Stateto hold ahashin a field namedruntime(or something like it).initialize-stateto read thishashand convert each of the functions into an assembly-compatible function.stepfunction.Externfunction labels. (Just checks that the label corresponds to a function defined in the runtimehash.)Callinstructions.read-bytewrite-byteObservational Inequality of Register Modification
In a traditional compilation environment, modification of the registers performed by external functions is visible to the client code, even if such values are not considered "returned" values. In our runtime implementation, such modifications will be invisible to the client code because it will happen in Racket natively instead of passing through the interpreter. Although this means our interpreter will deviate observationally from traditionally compiled code, I think the difference is unlikely to have significant impact in student projects since students are expected to follow standard calling conventions. Maybe make a note in the code about it, though.